diff --git a/cozo-core/src/cozoscript.pest b/cozo-core/src/cozoscript.pest index d40a3d81..47dc58f3 100644 --- a/cozo-core/src/cozoscript.pest +++ b/cozo-core/src/cozoscript.pest @@ -135,9 +135,10 @@ limit_option = {":limit" ~ expr} offset_option = {":offset" ~ expr} sort_option = {(":sort" | ":order") ~ (sort_arg ~ ",")* ~ sort_arg } relation_option = {relation_op ~ (compound_ident | underscore_ident) ~ table_schema?} -relation_op = _{relation_create | relation_replace | relation_put | relation_update | relation_rm | relation_ensure | relation_ensure_not} +relation_op = _{relation_create | relation_replace | relation_insert | relation_put | relation_update | relation_rm | relation_ensure_not | relation_ensure } relation_create = {":create"} relation_replace = {":replace"} +relation_insert = {":insert"} relation_put = {":put"} relation_update = {":update"} relation_rm = {":rm"} diff --git a/cozo-core/src/data/functions.rs b/cozo-core/src/data/functions.rs index 5aa07a38..b973484d 100644 --- a/cozo-core/src/data/functions.rs +++ b/cozo-core/src/data/functions.rs @@ -1727,13 +1727,13 @@ pub(crate) fn op_windows(args: &[DataValue]) -> Result { Ok(DataValue::List(res)) } -fn get_index(mut i: i64, total: usize) -> Result { +fn get_index(mut i: i64, total: usize, is_upper: bool) -> Result { if i < 0 { i += total as i64; } Ok(if i >= 0 { let i = i as usize; - if i >= total { + if i > total || (!is_upper && i == total) { bail!("index {} out of bound", i) } else { i @@ -1763,7 +1763,7 @@ fn get_impl(args: &[DataValue]) -> Result { let n = args[1] .get_int() .ok_or_else(|| miette!("second argument to 'get' mut be an integer"))?; - let idx = get_index(n, l.len())?; + let idx = get_index(n, l.len(), false)?; Ok(l[idx].clone()) } DataValue::Json(json) => { @@ -1830,8 +1830,8 @@ pub(crate) fn op_slice(args: &[DataValue]) -> Result { let n = args[2] .get_int() .ok_or_else(|| miette!("third argument to 'slice' mut be an integer"))?; - let m = get_index(m, l.len())?; - let n = get_index(n, l.len())?; + let m = get_index(m, l.len(), false)?; + let n = get_index(n, l.len(), true)?; Ok(DataValue::List(l[m..n].to_vec())) } diff --git a/cozo-core/src/data/program.rs b/cozo-core/src/data/program.rs index a2684c37..0482079f 100644 --- a/cozo-core/src/data/program.rs +++ b/cozo-core/src/data/program.rs @@ -76,15 +76,15 @@ impl Display for QueryOutOptions { writeln!(f, "{symb};")?; } if let Some(( - InputRelationHandle { - name, - metadata: StoredRelationMetadata { keys, non_keys }, - key_bindings, - dep_bindings, - .. - }, - op, - )) = &self.store_relation + InputRelationHandle { + name, + metadata: StoredRelationMetadata { keys, non_keys }, + key_bindings, + dep_bindings, + .. + }, + op, + )) = &self.store_relation { match op { RelationOp::Create => { @@ -93,6 +93,9 @@ impl Display for QueryOutOptions { RelationOp::Replace => { write!(f, ":replace ")?; } + RelationOp::Insert => { + write!(f, ":insert ")?; + } RelationOp::Put => { write!(f, ":put ")?; } @@ -178,6 +181,7 @@ pub(crate) enum RelationOp { Create, Replace, Put, + Insert, Update, Rm, Ensure, @@ -486,13 +490,13 @@ impl Display for InputProgram { } InputInlineRulesOrFixed::Fixed { fixed: - FixedRuleApply { - fixed_handle: handle, - rule_args, - options, - head, - .. - }, + FixedRuleApply { + fixed_handle: handle, + rule_args, + options, + head, + .. + }, } => { write!(f, "{name}")?; f.debug_list().entries(head).finish()?; @@ -627,7 +631,7 @@ impl InputProgram { inner: rule.body, span: rule.span, } - .disjunctive_normal_form(tx)?; + .disjunctive_normal_form(tx)?; let mut new_head = Vec::with_capacity(rule.head.len()); let mut seen: BTreeMap<&Symbol, Vec> = BTreeMap::default(); for symb in rule.head.iter() { @@ -989,7 +993,7 @@ pub(crate) struct FtsSearch { } impl HnswSearch { - pub(crate) fn all_bindings(&self) -> impl Iterator { + pub(crate) fn all_bindings(&self) -> impl Iterator { self.bindings .iter() .chain(self.bind_field.iter()) @@ -1000,7 +1004,7 @@ impl HnswSearch { } impl FtsSearch { - pub(crate) fn all_bindings(&self) -> impl Iterator { + pub(crate) fn all_bindings(&self) -> impl Iterator { self.bindings.iter().chain(self.bind_score.iter()) } } @@ -1652,12 +1656,12 @@ impl Display for InputAtom { } InputAtom::Unification { inner: - Unification { - binding, - expr, - one_many_unif, - .. - }, + Unification { + binding, + expr, + one_many_unif, + .. + }, } => { write!(f, "{binding}")?; if *one_many_unif { diff --git a/cozo-core/src/data/tests/functions.rs b/cozo-core/src/data/tests/functions.rs index 1d0e5b13..1525359d 100644 --- a/cozo-core/src/data/tests/functions.rs +++ b/cozo-core/src/data/tests/functions.rs @@ -127,7 +127,7 @@ fn test_is_in() { DataValue::from(1), DataValue::List(vec![DataValue::from(1), DataValue::from(2)]) ]) - .unwrap(), + .unwrap(), DataValue::from(true) ); assert_eq!( @@ -135,7 +135,7 @@ fn test_is_in() { DataValue::from(3), DataValue::List(vec![DataValue::from(1), DataValue::from(2)]) ]) - .unwrap(), + .unwrap(), DataValue::from(false) ); assert_eq!( @@ -251,7 +251,7 @@ fn test_comparators() { #[test] fn test_max_min() { - assert_eq!(op_max(&[DataValue::from(1),]).unwrap(), DataValue::from(1)); + assert_eq!(op_max(&[DataValue::from(1), ]).unwrap(), DataValue::from(1)); assert_eq!( op_max(&[ DataValue::from(1), @@ -259,7 +259,7 @@ fn test_max_min() { DataValue::from(3), DataValue::from(4) ]) - .unwrap(), + .unwrap(), DataValue::from(4) ); assert_eq!( @@ -269,7 +269,7 @@ fn test_max_min() { DataValue::from(3), DataValue::from(4) ]) - .unwrap(), + .unwrap(), DataValue::from(4) ); assert_eq!( @@ -279,12 +279,12 @@ fn test_max_min() { DataValue::from(3), DataValue::from(4.0) ]) - .unwrap(), + .unwrap(), DataValue::from(4.0) ); assert!(op_max(&[DataValue::from(true)]).is_err()); - assert_eq!(op_min(&[DataValue::from(1),]).unwrap(), DataValue::from(1)); + assert_eq!(op_min(&[DataValue::from(1), ]).unwrap(), DataValue::from(1)); assert_eq!( op_min(&[ DataValue::from(1), @@ -292,7 +292,7 @@ fn test_max_min() { DataValue::from(3), DataValue::from(4) ]) - .unwrap(), + .unwrap(), DataValue::from(1) ); assert_eq!( @@ -302,7 +302,7 @@ fn test_max_min() { DataValue::from(3), DataValue::from(4) ]) - .unwrap(), + .unwrap(), DataValue::from(1.0) ); assert_eq!( @@ -312,7 +312,7 @@ fn test_max_min() { DataValue::from(3), DataValue::from(4.0) ]) - .unwrap(), + .unwrap(), DataValue::from(1) ); assert!(op_max(&[DataValue::from(true)]).is_err()); @@ -570,7 +570,7 @@ fn test_bits() { DataValue::Bytes([0b111000].into()), DataValue::Bytes([0b010101].into()) ]) - .unwrap(), + .unwrap(), DataValue::Bytes([0b010000].into()) ); assert_eq!( @@ -578,7 +578,7 @@ fn test_bits() { DataValue::Bytes([0b111000].into()), DataValue::Bytes([0b010101].into()) ]) - .unwrap(), + .unwrap(), DataValue::Bytes([0b111101].into()) ); assert_eq!( @@ -590,7 +590,7 @@ fn test_bits() { DataValue::Bytes([0b111000].into()), DataValue::Bytes([0b010101].into()) ]) - .unwrap(), + .unwrap(), DataValue::Bytes([0b101101].into()) ); } @@ -628,7 +628,7 @@ fn test_concat() { DataValue::List(vec![DataValue::from(true), DataValue::from(false)]), DataValue::List(vec![DataValue::from(true)]) ]) - .unwrap(), + .unwrap(), DataValue::List(vec![ DataValue::from(true), DataValue::from(false), @@ -644,7 +644,7 @@ fn test_str_includes() { DataValue::Str("abcdef".into()), DataValue::Str("bcd".into()) ]) - .unwrap(), + .unwrap(), DataValue::from(true) ); assert_eq!( @@ -688,7 +688,7 @@ fn test_starts_ends_with() { DataValue::Str("abcdef".into()), DataValue::Str("abc".into()) ]) - .unwrap(), + .unwrap(), DataValue::from(true) ); assert_eq!( @@ -700,7 +700,7 @@ fn test_starts_ends_with() { DataValue::Str("abcdef".into()), DataValue::Str("def".into()) ]) - .unwrap(), + .unwrap(), DataValue::from(true) ); assert_eq!( @@ -716,7 +716,7 @@ fn test_regex() { DataValue::Str("abcdef".into()), DataValue::Regex(RegexWrapper(Regex::new("c.e").unwrap())) ]) - .unwrap(), + .unwrap(), DataValue::from(true) ); @@ -725,7 +725,7 @@ fn test_regex() { DataValue::Str("abcdef".into()), DataValue::Regex(RegexWrapper(Regex::new("c.ef$").unwrap())) ]) - .unwrap(), + .unwrap(), DataValue::from(true) ); @@ -734,7 +734,7 @@ fn test_regex() { DataValue::Str("abcdef".into()), DataValue::Regex(RegexWrapper(Regex::new("c.e$").unwrap())) ]) - .unwrap(), + .unwrap(), DataValue::from(false) ); @@ -744,7 +744,7 @@ fn test_regex() { DataValue::Regex(RegexWrapper(Regex::new("[be]").unwrap())), DataValue::Str("x".into()) ]) - .unwrap(), + .unwrap(), DataValue::Str("axcdef".into()) ); @@ -754,7 +754,7 @@ fn test_regex() { DataValue::Regex(RegexWrapper(Regex::new("[be]").unwrap())), DataValue::Str("x".into()) ]) - .unwrap(), + .unwrap(), DataValue::Str("axcdxf".into()) ); assert_eq!( @@ -762,7 +762,7 @@ fn test_regex() { DataValue::Str("abCDefGH".into()), DataValue::Regex(RegexWrapper(Regex::new("[xayef]|(GH)").unwrap())) ]) - .unwrap(), + .unwrap(), DataValue::List(vec![ DataValue::Str("a".into()), DataValue::Str("e".into()), @@ -775,7 +775,7 @@ fn test_regex() { DataValue::Str("abCDefGH".into()), DataValue::Regex(RegexWrapper(Regex::new("[xayef]|(GH)").unwrap())) ]) - .unwrap(), + .unwrap(), DataValue::Str("a".into()), ); assert_eq!( @@ -783,7 +783,7 @@ fn test_regex() { DataValue::Str("abCDefGH".into()), DataValue::Regex(RegexWrapper(Regex::new("xyz").unwrap())) ]) - .unwrap(), + .unwrap(), DataValue::List(vec![]) ); @@ -792,7 +792,7 @@ fn test_regex() { DataValue::Str("abCDefGH".into()), DataValue::Regex(RegexWrapper(Regex::new("xyz").unwrap())) ]) - .unwrap(), + .unwrap(), DataValue::Null ); } @@ -912,7 +912,7 @@ fn test_prepend_append() { DataValue::List(vec![DataValue::from(1), DataValue::from(2)]), DataValue::Null, ]) - .unwrap(), + .unwrap(), DataValue::List(vec![ DataValue::Null, DataValue::from(1), @@ -924,7 +924,7 @@ fn test_prepend_append() { DataValue::List(vec![DataValue::from(1), DataValue::from(2)]), DataValue::Null, ]) - .unwrap(), + .unwrap(), DataValue::List(vec![ DataValue::from(1), DataValue::from(2), @@ -967,7 +967,7 @@ fn test_sort_reverse() { DataValue::from(2), DataValue::Null, ])]) - .unwrap(), + .unwrap(), DataValue::List(vec![ DataValue::Null, DataValue::from(1), @@ -982,7 +982,7 @@ fn test_sort_reverse() { DataValue::from(2), DataValue::Null, ])]) - .unwrap(), + .unwrap(), DataValue::List(vec![ DataValue::Null, DataValue::from(2), @@ -1000,9 +1000,9 @@ fn test_haversine() { DataValue::from(0), DataValue::from(180), ]) - .unwrap() - .get_float() - .unwrap(); + .unwrap() + .get_float() + .unwrap(); assert!(d.abs_diff_eq(&f64::PI(), 1e-5)); let d = op_haversine_deg_input(&[ @@ -1011,9 +1011,9 @@ fn test_haversine() { DataValue::from(0), DataValue::from(123), ]) - .unwrap() - .get_float() - .unwrap(); + .unwrap() + .get_float() + .unwrap(); assert!(d.abs_diff_eq(&(f64::PI() / 2.), 1e-5)); let d = op_haversine(&[ @@ -1022,9 +1022,9 @@ fn test_haversine() { DataValue::from(0), DataValue::from(f64::PI()), ]) - .unwrap() - .get_float() - .unwrap(); + .unwrap() + .get_float() + .unwrap(); assert!(d.abs_diff_eq(&f64::PI(), 1e-5)); } @@ -1055,7 +1055,7 @@ fn test_first_last() { DataValue::from(1), DataValue::from(2), ])]) - .unwrap(), + .unwrap(), DataValue::from(1), ); assert_eq!( @@ -1063,7 +1063,7 @@ fn test_first_last() { DataValue::from(1), DataValue::from(2), ])]) - .unwrap(), + .unwrap(), DataValue::from(2), ); } @@ -1081,7 +1081,7 @@ fn test_chunks() { ]), DataValue::from(2), ]) - .unwrap(), + .unwrap(), DataValue::List(vec![ DataValue::List(vec![DataValue::from(1), DataValue::from(2)]), DataValue::List(vec![DataValue::from(3), DataValue::from(4)]), @@ -1099,7 +1099,7 @@ fn test_chunks() { ]), DataValue::from(2), ]) - .unwrap(), + .unwrap(), DataValue::List(vec![ DataValue::List(vec![DataValue::from(1), DataValue::from(2)]), DataValue::List(vec![DataValue::from(3), DataValue::from(4)]), @@ -1116,7 +1116,7 @@ fn test_chunks() { ]), DataValue::from(3), ]) - .unwrap(), + .unwrap(), DataValue::List(vec![ DataValue::List(vec![ DataValue::from(1), @@ -1149,7 +1149,7 @@ fn test_get() { ]), DataValue::from(1) ]) - .unwrap(), + .unwrap(), DataValue::from(2) ); assert_eq!( @@ -1165,7 +1165,7 @@ fn test_get() { ]), DataValue::from(1) ]) - .unwrap(), + .unwrap(), DataValue::from(2) ); } @@ -1181,7 +1181,18 @@ fn test_slice() { DataValue::from(1), DataValue::from(4) ]) - .is_err()); + .is_err()); + + assert!(op_slice(&[ + DataValue::List(vec![ + DataValue::from(1), + DataValue::from(2), + DataValue::from(3), + ]), + DataValue::from(1), + DataValue::from(3) + ]) + .is_ok()); assert_eq!( op_slice(&[ @@ -1193,7 +1204,7 @@ fn test_slice() { DataValue::from(1), DataValue::from(-1) ]) - .unwrap(), + .unwrap(), DataValue::List(vec![DataValue::from(2)]) ); } @@ -1348,7 +1359,7 @@ fn test_set_ops() { DataValue::List([2, 3, 4].into_iter().map(DataValue::from).collect()), DataValue::List([3, 4, 5].into_iter().map(DataValue::from).collect()) ]) - .unwrap(), + .unwrap(), DataValue::List([1, 2, 3, 4, 5].into_iter().map(DataValue::from).collect()) ); assert_eq!( @@ -1362,7 +1373,7 @@ fn test_set_ops() { DataValue::List([2, 3, 4].into_iter().map(DataValue::from).collect()), DataValue::List([3, 4, 5].into_iter().map(DataValue::from).collect()) ]) - .unwrap(), + .unwrap(), DataValue::List([3, 4].into_iter().map(DataValue::from).collect()) ); assert_eq!( @@ -1376,7 +1387,7 @@ fn test_set_ops() { DataValue::List([2, 3, 4].into_iter().map(DataValue::from).collect()), DataValue::List([3, 4, 5].into_iter().map(DataValue::from).collect()) ]) - .unwrap(), + .unwrap(), DataValue::List([1, 6].into_iter().map(DataValue::from).collect()) ); } diff --git a/cozo-core/src/parse/query.rs b/cozo-core/src/parse/query.rs index 62588b52..0a3f5a59 100644 --- a/cozo-core/src/parse/query.rs +++ b/cozo-core/src/parse/query.rs @@ -317,6 +317,7 @@ pub(crate) fn parse_query( Rule::relation_create => RelationOp::Create, Rule::relation_replace => RelationOp::Replace, Rule::relation_put => RelationOp::Put, + Rule::relation_insert => RelationOp::Insert, Rule::relation_update => RelationOp::Update, Rule::relation_rm => RelationOp::Rm, Rule::relation_ensure => RelationOp::Ensure, diff --git a/cozo-core/src/query/stored.rs b/cozo-core/src/query/stored.rs index 90054df3..fe8ad32a 100644 --- a/cozo-core/src/query/stored.rs +++ b/cozo-core/src/query/stored.rs @@ -44,7 +44,7 @@ impl<'a> SessionTx<'a> { pub(crate) fn execute_relation<'s, S: Storage<'s>>( &mut self, db: &Db, - res_iter: impl Iterator, + res_iter: impl Iterator, op: RelationOp, meta: &InputRelationHandle, headers: &[Symbol], @@ -88,7 +88,7 @@ impl<'a> SessionTx<'a> { &db.fixed_rules.read().unwrap(), cur_vld, )? - .get_single_program()?; + .get_single_program()?; let (_, cleanups) = db .run_query( @@ -178,7 +178,7 @@ impl<'a> SessionTx<'a> { key_bindings, *span, )?, - RelationOp::Create | RelationOp::Replace | RelationOp::Put => self.put_into_relation( + RelationOp::Create | RelationOp::Replace | RelationOp::Put | RelationOp::Insert => self.put_into_relation( db, res_iter, headers, @@ -191,6 +191,7 @@ impl<'a> SessionTx<'a> { metadata, key_bindings, dep_bindings, + op == RelationOp::Insert, *span, )?, }; @@ -201,7 +202,7 @@ impl<'a> SessionTx<'a> { fn put_into_relation<'s, S: Storage<'s>>( &mut self, db: &Db, - res_iter: impl Iterator, + res_iter: impl Iterator, headers: &[Symbol], cur_vld: ValidityTs, callback_targets: &BTreeSet>, @@ -212,6 +213,7 @@ impl<'a> SessionTx<'a> { metadata: &StoredRelationMetadata, key_bindings: &[Symbol], dep_bindings: &[Symbol], + is_insert: bool, span: SourceSpan, ) -> Result<()> { let is_callback_target = callback_targets.contains(&relation_store.name); @@ -233,7 +235,7 @@ impl<'a> SessionTx<'a> { let need_to_collect = !relation_store.is_temp && (is_callback_target - || (propagate_triggers && !relation_store.put_triggers.is_empty())); + || (propagate_triggers && !relation_store.put_triggers.is_empty())); let has_indices = !relation_store.indices.is_empty(); let has_hnsw_indices = !relation_store.hnsw_indices.is_empty(); let has_fts_indices = !relation_store.fts_indices.is_empty(); @@ -268,6 +270,16 @@ impl<'a> SessionTx<'a> { .map(|ex| ex.extract_data(&tuple, cur_vld)) .try_collect()?; + if is_insert { + if relation_store.exists(self, &extracted[..relation_store.metadata.keys.len()])? { + bail!(TransactAssertionFailure { + relation: relation_store.name.to_string(), + key: extracted, + notice: "key exists in database".to_string() + }); + } + } + let key = relation_store.encode_key_for_store(&extracted, span)?; let val = relation_store.encode_val_for_store(&extracted, span)?; @@ -496,7 +508,7 @@ impl<'a> SessionTx<'a> { fn update_in_relation<'s, S: Storage<'s>>( &mut self, db: &Db, - res_iter: impl Iterator, + res_iter: impl Iterator, headers: &[Symbol], cur_vld: ValidityTs, callback_targets: &BTreeSet>, @@ -527,7 +539,7 @@ impl<'a> SessionTx<'a> { let need_to_collect = !relation_store.is_temp && (is_callback_target - || (propagate_triggers && !relation_store.put_triggers.is_empty())); + || (propagate_triggers && !relation_store.put_triggers.is_empty())); let has_indices = !relation_store.indices.is_empty(); let has_hnsw_indices = !relation_store.hnsw_indices.is_empty(); let has_fts_indices = !relation_store.fts_indices.is_empty(); @@ -674,7 +686,7 @@ impl<'a> SessionTx<'a> { &db.fixed_rules.read().unwrap(), cur_vld, )? - .get_single_program()?; + .get_single_program()?; make_const_rule( &mut program, @@ -770,7 +782,7 @@ impl<'a> SessionTx<'a> { fn ensure_not_in_relation( &mut self, - res_iter: impl Iterator, + res_iter: impl Iterator, headers: &[Symbol], cur_vld: ValidityTs, relation_store: &mut RelationHandle, @@ -817,7 +829,7 @@ impl<'a> SessionTx<'a> { fn ensure_in_relation( &mut self, - res_iter: impl Iterator, + res_iter: impl Iterator, headers: &[Symbol], cur_vld: ValidityTs, relation_store: &mut RelationHandle, @@ -887,7 +899,7 @@ impl<'a> SessionTx<'a> { fn remove_from_relation<'s, S: Storage<'s>>( &mut self, db: &Db, - res_iter: impl Iterator, + res_iter: impl Iterator, headers: &[Symbol], cur_vld: ValidityTs, callback_targets: &BTreeSet>, @@ -917,7 +929,7 @@ impl<'a> SessionTx<'a> { let need_to_collect = !relation_store.is_temp && (is_callback_target - || (propagate_triggers && !relation_store.rm_triggers.is_empty())); + || (propagate_triggers && !relation_store.rm_triggers.is_empty())); let has_indices = !relation_store.indices.is_empty(); let has_hnsw_indices = !relation_store.hnsw_indices.is_empty(); let has_fts_indices = !relation_store.fts_indices.is_empty(); @@ -992,7 +1004,7 @@ impl<'a> SessionTx<'a> { &db.fixed_rules.read().unwrap(), cur_vld, )? - .get_single_program()?; + .get_single_program()?; make_const_rule(&mut program, "_new", k_bindings.clone(), new_tuples.clone()); diff --git a/cozo-core/src/runtime/tests.rs b/cozo-core/src/runtime/tests.rs index fc33491f..f107b2a7 100644 --- a/cozo-core/src/runtime/tests.rs +++ b/cozo-core/src/runtime/tests.rs @@ -58,6 +58,7 @@ fn test_limit_offset() { .into_json(); assert_eq!(res["rows"], json!([])); } + #[test] fn test_normal_aggr_empty() { let db = new_cozo_mem().unwrap(); @@ -67,6 +68,7 @@ fn test_normal_aggr_empty() { .rows; assert_eq!(res, vec![vec![DataValue::from(0)]]); } + #[test] fn test_meet_aggr_empty() { let db = new_cozo_mem().unwrap(); @@ -82,6 +84,7 @@ fn test_meet_aggr_empty() { .rows; assert_eq!(res, vec![vec![DataValue::Null, DataValue::from(0)]]); } + #[test] fn test_layers() { let _ = env_logger::builder().is_test(true).try_init(); @@ -101,6 +104,7 @@ fn test_layers() { .rows; assert_eq!(res[0][0], DataValue::from(21.)) } + #[test] fn test_conditions() { let _ = env_logger::builder().is_test(true).try_init(); @@ -118,7 +122,7 @@ fn test_conditions() { "#, Default::default(), ) - .unwrap(); + .unwrap(); debug!("real test begins"); let res = db .run_script( @@ -132,6 +136,7 @@ fn test_conditions() { .rows; assert_eq!(res[0][0], DataValue::from(1.1)) } + #[test] fn test_classical() { let _ = env_logger::builder().is_test(true).try_init(); @@ -163,7 +168,7 @@ fn default_columns() { "#, Default::default(), ) - .unwrap(); + .unwrap(); db.run_script( r#" @@ -172,7 +177,7 @@ fn default_columns() { "#, Default::default(), ) - .unwrap(); + .unwrap(); } #[test] @@ -183,19 +188,19 @@ fn rm_does_not_need_all_keys() { assert!(db .run_script( "?[uid, mood] <- [[1, 2]] :put status {uid => mood}", - Default::default() + Default::default(), ) .is_ok()); assert!(db .run_script( "?[uid, mood] <- [[2]] :put status {uid}", - Default::default() + Default::default(), ) .is_err()); assert!(db .run_script( "?[uid, mood] <- [[3, 2]] :rm status {uid => mood}", - Default::default() + Default::default(), ) .is_ok()); assert!(db @@ -386,12 +391,12 @@ fn test_trigger() { ":create friends {fr: Int, to: Int => data: Any}", Default::default(), ) - .unwrap(); + .unwrap(); db.run_script( ":create friends.rev {to: Int, fr: Int => data: Any}", Default::default(), ) - .unwrap(); + .unwrap(); db.run_script( r#" ::set_triggers friends @@ -409,12 +414,12 @@ fn test_trigger() { "#, Default::default(), ) - .unwrap(); + .unwrap(); db.run_script( r"?[fr, to, data] <- [[1,2,3]] :put friends {fr, to => data}", Default::default(), ) - .unwrap(); + .unwrap(); let ret = db .export_relations(["friends", "friends.rev"].into_iter()) .unwrap(); @@ -433,7 +438,7 @@ fn test_trigger() { r"?[fr, to] <- [[1,2], [2,3]] :rm friends {fr, to}", Default::default(), ) - .unwrap(); + .unwrap(); let ret = db .export_relations(["friends", "friends.rev"].into_iter()) .unwrap(); @@ -450,22 +455,22 @@ fn test_callback() { ":create friends {fr: Int, to: Int => data: Any}", Default::default(), ) - .unwrap(); + .unwrap(); db.run_script( r"?[fr, to, data] <- [[1,2,3],[4,5,6]] :put friends {fr, to => data}", Default::default(), ) - .unwrap(); + .unwrap(); db.run_script( r"?[fr, to, data] <- [[1,2,4],[4,7,6]] :put friends {fr, to => data}", Default::default(), ) - .unwrap(); + .unwrap(); db.run_script( r"?[fr, to] <- [[1,9],[4,5]] :rm friends {fr, to}", Default::default(), ) - .unwrap(); + .unwrap(); std::thread::sleep(Duration::from_secs_f64(0.01)); while let Ok(d) = receiver.try_recv() { collected.push(d); @@ -497,12 +502,12 @@ fn test_update() { ":create friends {fr: Int, to: Int => a: Any, b: Any, c: Any}", Default::default(), ) - .unwrap(); + .unwrap(); db.run_script( "?[fr, to, a, b, c] <- [[1,2,3,4,5]] :put friends {fr, to => a, b, c}", Default::default(), ) - .unwrap(); + .unwrap(); let res = db .run_script( "?[fr, to, a, b, c] := *friends{fr, to, a, b, c}", @@ -515,7 +520,7 @@ fn test_update() { "?[fr, to, b] <- [[1, 2, 100]] :update friends {fr, to => b}", Default::default(), ) - .unwrap(); + .unwrap(); let res = db .run_script( "?[fr, to, a, b, c] := *friends{fr, to, a, b, c}", @@ -533,13 +538,13 @@ fn test_index() { ":create friends {fr: Int, to: Int => data: Any}", Default::default(), ) - .unwrap(); + .unwrap(); db.run_script( r"?[fr, to, data] <- [[1,2,3],[4,5,6]] :put friends {fr, to, data}", Default::default(), ) - .unwrap(); + .unwrap(); assert!(db .run_script("::index create friends:rev {to, no}", Default::default()) @@ -551,12 +556,12 @@ fn test_index() { r"?[fr, to, data] <- [[1,2,5],[6,5,7]] :put friends {fr, to => data}", Default::default(), ) - .unwrap(); + .unwrap(); db.run_script( r"?[fr, to] <- [[4,5]] :rm friends {fr, to}", Default::default(), ) - .unwrap(); + .unwrap(); let rels_data = db .export_relations(["friends", "friends:rev"].into_iter()) @@ -624,7 +629,7 @@ fn test_json_objects() { }", Default::default(), ) - .unwrap(); + .unwrap(); } #[test] @@ -685,13 +690,13 @@ fn test_index_short() { ":create friends {fr: Int, to: Int => data: Any}", Default::default(), ) - .unwrap(); + .unwrap(); db.run_script( r"?[fr, to, data] <- [[1,2,3],[4,5,6]] :put friends {fr, to => data}", Default::default(), ) - .unwrap(); + .unwrap(); db.run_script("::index create friends:rev {to}", Default::default()) .unwrap(); @@ -700,12 +705,12 @@ fn test_index_short() { r"?[fr, to, data] <- [[1,2,5],[6,5,7]] :put friends {fr, to => data}", Default::default(), ) - .unwrap(); + .unwrap(); db.run_script( r"?[fr, to] <- [[4,5]] :rm friends {fr, to}", Default::default(), ) - .unwrap(); + .unwrap(); let rels_data = db .export_relations(["friends", "friends:rev"].into_iter()) @@ -801,7 +806,7 @@ fn test_vec_types() { "?[k, v] <- [['k', [1,2,3,4,5,6,7,8]]] :put a {k => v}", Default::default(), ) - .unwrap(); + .unwrap(); let res = db .run_script("?[k, v] := *a{k, v}", Default::default()) .unwrap(); @@ -846,7 +851,7 @@ fn test_vec_index() { ", Default::default(), ) - .unwrap(); + .unwrap(); db.run_script( r" ::hnsw create a:vec { @@ -862,7 +867,7 @@ fn test_vec_index() { }", Default::default(), ) - .unwrap(); + .unwrap(); db.run_script( r" ?[k, v] <- [ @@ -877,7 +882,7 @@ fn test_vec_index() { ", Default::default(), ) - .unwrap(); + .unwrap(); println!("all links"); for (_, nrows) in db.export_relations(["a:vec"].iter()).unwrap() { @@ -912,7 +917,7 @@ fn test_fts_indexing() { r"?[k, v] <- [['a', 'hello world!'], ['b', 'the world is round']] :put a {k => v}", Default::default(), ) - .unwrap(); + .unwrap(); db.run_script( r"::fts create a:fts { extractor: v, @@ -921,7 +926,7 @@ fn test_fts_indexing() { }", Default::default(), ) - .unwrap(); + .unwrap(); db.run_script( r"?[k, v] <- [ ['b', 'the world is square!'], @@ -930,7 +935,7 @@ fn test_fts_indexing() { ] :put a {k => v}", Default::default(), ) - .unwrap(); + .unwrap(); let res = db .run_script( r" @@ -964,12 +969,12 @@ fn test_lsh_indexing() { r"?[k, v] <- [['a', 'hello world!'], ['b', 'the world is round']] :put a {k => v}", Default::default(), ) - .unwrap(); + .unwrap(); db.run_script( r"::lsh create a:lsh {extractor: v, tokenizer: Simple, n_gram: 3, target_threshold: 0.3 }", Default::default(), ) - .unwrap(); + .unwrap(); db.run_script( r"?[k, v] <- [ ['b', 'the world is square!'], @@ -979,7 +984,7 @@ fn test_lsh_indexing() { ] :put a {k => v}", Default::default(), ) - .unwrap(); + .unwrap(); let res = db .run_script("::columns a:lsh", Default::default()) .unwrap(); @@ -1038,7 +1043,7 @@ fn test_insertions() { r":create a {k => v: default rand_vec(1536)}", Default::default(), ) - .unwrap(); + .unwrap(); db.run_script(r"?[k] <- [[1]] :put a {k}", Default::default()) .unwrap(); db.run_script(r"?[k, v] := *a{k, v}", Default::default()) @@ -1050,7 +1055,7 @@ fn test_insertions() { }", Default::default(), ) - .unwrap(); + .unwrap(); db.run_script(r"?[count(fr_k)] := *a:i{fr_k}", Default::default()) .unwrap(); db.run_script(r"?[k] <- [[1]] :put a {k}", Default::default()) @@ -1059,7 +1064,7 @@ fn test_insertions() { r"?[k] := k in int_range(300) :put a {k}", Default::default(), ) - .unwrap(); + .unwrap(); let res = db .run_script( r"?[dist, k] := ~a:i{k | query: v, bind_distance: dist, k:10, ef: 50, filter: k % 2 == 0, radius: 245}, *a{k: 96, v}", @@ -1131,7 +1136,7 @@ fn multi_index_vec() { "#, Default::default(), ) - .unwrap(); + .unwrap(); db.run_script( r#" ::hnsw create product:semantic{ @@ -1143,7 +1148,7 @@ fn multi_index_vec() { "#, Default::default(), ) - .unwrap(); + .unwrap(); db.run_script( r#" ?[id, name, description, price, name_vec, description_vec] <- [[1, "name", "description", 100, [1], [1]]] @@ -1160,6 +1165,28 @@ fn multi_index_vec() { } } +#[test] +fn ensure_not() { + let db = DbInstance::new("mem", "", "").unwrap(); + db.run_script(r" + %ignore_error { :create id_alloc{id: Int => next_id: Int, last_id: Int}} +%ignore_error { + ?[id, next_id, last_id] <- [[0, 1, 1000]]; + :ensure_not id_alloc{id => next_id, last_id} +} + ", Default::default()).unwrap(); +} + +#[test] +fn insertion() { + let db = DbInstance::new("mem", "", "").unwrap(); + db.run_script(r":create a {x => y}", Default::default()).unwrap(); + assert!(db.run_script(r"?[x, y] <- [[1, 2]] :insert a {x => y}", Default::default()) + .is_ok()); + assert!(db.run_script(r"?[x, y] <- [[1, 3]] :insert a {x => y}", Default::default()) + .is_err()); +} + #[test] fn parser_corner_case() { let db = DbInstance::new("mem", "", "").unwrap(); @@ -1171,12 +1198,12 @@ fn parser_corner_case() { r#"?[C] := C = true, C inx[C] := C = 1"#, Default::default(), ) - .unwrap(); + .unwrap(); db.run_script(r#"?[k] := k in int_range(300)"#, Default::default()) .unwrap(); db.run_script( r#"ywcc[a] <- [[1]] noto[A] := ywcc[A] ?[A] := noto[A]"#, Default::default(), ) - .unwrap(); + .unwrap(); }