From 57a326918d1e0b81f171a47bbd67913d34860f61 Mon Sep 17 00:00:00 2001 From: Ziyang Hu Date: Thu, 11 May 2023 23:02:11 +0800 Subject: [PATCH] create and delete --- cozo-core/src/cozoscript.pest | 3 +- cozo-core/src/data/program.rs | 4 ++ cozo-core/src/parse/query.rs | 1 + cozo-core/src/query/stored.rs | 86 ++++++++++++++--------- cozo-core/src/runtime/db.rs | 6 +- cozo-core/src/runtime/tests.rs | 123 +++++++++++++++++++++------------ 6 files changed, 143 insertions(+), 80 deletions(-) diff --git a/cozo-core/src/cozoscript.pest b/cozo-core/src/cozoscript.pest index 47dc58f3..241502f4 100644 --- a/cozo-core/src/cozoscript.pest +++ b/cozo-core/src/cozoscript.pest @@ -135,10 +135,11 @@ 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_insert | relation_put | relation_update | relation_rm | relation_ensure_not | relation_ensure } +relation_op = _{relation_create | relation_replace | relation_insert | relation_put | relation_update | relation_rm | relation_delete | relation_ensure_not | relation_ensure } relation_create = {":create"} relation_replace = {":replace"} relation_insert = {":insert"} +relation_delete = {":delete"} relation_put = {":put"} relation_update = {":update"} relation_rm = {":rm"} diff --git a/cozo-core/src/data/program.rs b/cozo-core/src/data/program.rs index 0482079f..9b40413d 100644 --- a/cozo-core/src/data/program.rs +++ b/cozo-core/src/data/program.rs @@ -105,6 +105,9 @@ impl Display for QueryOutOptions { RelationOp::Rm => { write!(f, ":rm ")?; } + RelationOp::Delete => { + write!(f, ":delete ")?; + } RelationOp::Ensure => { write!(f, ":ensure ")?; } @@ -184,6 +187,7 @@ pub(crate) enum RelationOp { Insert, Update, Rm, + Delete, Ensure, EnsureNot, } diff --git a/cozo-core/src/parse/query.rs b/cozo-core/src/parse/query.rs index 0a3f5a59..555e66d0 100644 --- a/cozo-core/src/parse/query.rs +++ b/cozo-core/src/parse/query.rs @@ -320,6 +320,7 @@ pub(crate) fn parse_query( Rule::relation_insert => RelationOp::Insert, Rule::relation_update => RelationOp::Update, Rule::relation_rm => RelationOp::Rm, + Rule::relation_delete => RelationOp::Delete, Rule::relation_ensure => RelationOp::Ensure, Rule::relation_ensure_not => RelationOp::EnsureNot, _ => unreachable!(), diff --git a/cozo-core/src/query/stored.rs b/cozo-core/src/query/stored.rs index fe8ad32a..e6874207 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( @@ -132,7 +132,7 @@ impl<'a> SessionTx<'a> { } = meta; match op { - RelationOp::Rm => self.remove_from_relation( + RelationOp::Rm | RelationOp::Delete => self.remove_from_relation( db, res_iter, headers, @@ -144,6 +144,7 @@ impl<'a> SessionTx<'a> { &mut relation_store, metadata, key_bindings, + op == RelationOp::Delete, *span, )?, RelationOp::Ensure => self.ensure_in_relation( @@ -178,22 +179,23 @@ impl<'a> SessionTx<'a> { key_bindings, *span, )?, - RelationOp::Create | RelationOp::Replace | RelationOp::Put | RelationOp::Insert => self.put_into_relation( - db, - res_iter, - headers, - cur_vld, - callback_targets, - callback_collector, - propagate_triggers, - &mut to_clear, - &mut relation_store, - metadata, - key_bindings, - dep_bindings, - op == RelationOp::Insert, - *span, - )?, + RelationOp::Create | RelationOp::Replace | RelationOp::Put | RelationOp::Insert => self + .put_into_relation( + db, + res_iter, + headers, + cur_vld, + callback_targets, + callback_collector, + propagate_triggers, + &mut to_clear, + &mut relation_store, + metadata, + key_bindings, + dep_bindings, + op == RelationOp::Insert, + *span, + )?, }; Ok(to_clear) @@ -202,7 +204,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>, @@ -235,7 +237,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(); @@ -270,8 +272,16 @@ impl<'a> SessionTx<'a> { .map(|ex| ex.extract_data(&tuple, cur_vld)) .try_collect()?; + let key = relation_store.encode_key_for_store(&extracted, span)?; + if is_insert { - if relation_store.exists(self, &extracted[..relation_store.metadata.keys.len()])? { + let already_exists = if relation_store.is_temp { + self.temp_store_tx.exists(&key, true)? + } else { + self.store_tx.exists(&key, true)? + }; + + if already_exists { bail!(TransactAssertionFailure { relation: relation_store.name.to_string(), key: extracted, @@ -280,7 +290,6 @@ impl<'a> SessionTx<'a> { } } - let key = relation_store.encode_key_for_store(&extracted, span)?; let val = relation_store.encode_val_for_store(&extracted, span)?; if need_to_collect @@ -508,7 +517,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>, @@ -539,7 +548,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(); @@ -686,7 +695,7 @@ impl<'a> SessionTx<'a> { &db.fixed_rules.read().unwrap(), cur_vld, )? - .get_single_program()?; + .get_single_program()?; make_const_rule( &mut program, @@ -782,7 +791,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, @@ -829,7 +838,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, @@ -899,7 +908,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>, @@ -909,6 +918,7 @@ impl<'a> SessionTx<'a> { relation_store: &mut RelationHandle, metadata: &StoredRelationMetadata, key_bindings: &[Symbol], + check_exists: bool, span: SourceSpan, ) -> Result<()> { let is_callback_target = callback_targets.contains(&relation_store.name); @@ -929,7 +939,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(); @@ -944,6 +954,20 @@ impl<'a> SessionTx<'a> { .map(|ex| ex.extract_data(&tuple, cur_vld)) .try_collect()?; let key = relation_store.encode_key_for_store(&extracted, span)?; + if check_exists { + let exists = if relation_store.is_temp { + self.temp_store_tx.exists(&key, false)? + } else { + self.store_tx.exists(&key, false)? + }; + if !exists { + bail!(TransactAssertionFailure { + relation: relation_store.name.to_string(), + key: extracted, + notice: "key does not exists in database".to_string() + }); + } + } if need_to_collect || has_indices || has_hnsw_indices || has_fts_indices { if let Some(existing) = self.store_tx.get(&key, false)? { let mut tup = extracted.clone(); @@ -1004,7 +1028,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/db.rs b/cozo-core/src/runtime/db.rs index bb985f29..3ab7dcda 100644 --- a/cozo-core/src/runtime/db.rs +++ b/cozo-core/src/runtime/db.rs @@ -1377,8 +1377,10 @@ impl<'s, S: Storage<'s>> Db { StoreRelationNotFoundError(meta.name.to_string()) ); - existing - .ensure_compatible(meta, *op == RelationOp::Rm || *op == RelationOp::Update)?; + existing.ensure_compatible( + meta, + *op == RelationOp::Rm || *op == RelationOp::Delete || *op == RelationOp::Update, + )?; } }; diff --git a/cozo-core/src/runtime/tests.rs b/cozo-core/src/runtime/tests.rs index f107b2a7..9fa6aed9 100644 --- a/cozo-core/src/runtime/tests.rs +++ b/cozo-core/src/runtime/tests.rs @@ -122,7 +122,7 @@ fn test_conditions() { "#, Default::default(), ) - .unwrap(); + .unwrap(); debug!("real test begins"); let res = db .run_script( @@ -168,7 +168,7 @@ fn default_columns() { "#, Default::default(), ) - .unwrap(); + .unwrap(); db.run_script( r#" @@ -177,7 +177,7 @@ fn default_columns() { "#, Default::default(), ) - .unwrap(); + .unwrap(); } #[test] @@ -391,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 @@ -414,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(); @@ -438,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(); @@ -455,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); @@ -502,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}", @@ -520,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}", @@ -538,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()) @@ -556,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()) @@ -629,7 +629,7 @@ fn test_json_objects() { }", Default::default(), ) - .unwrap(); + .unwrap(); } #[test] @@ -690,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(); @@ -705,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()) @@ -806,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(); @@ -851,7 +851,7 @@ fn test_vec_index() { ", Default::default(), ) - .unwrap(); + .unwrap(); db.run_script( r" ::hnsw create a:vec { @@ -867,7 +867,7 @@ fn test_vec_index() { }", Default::default(), ) - .unwrap(); + .unwrap(); db.run_script( r" ?[k, v] <- [ @@ -882,7 +882,7 @@ fn test_vec_index() { ", Default::default(), ) - .unwrap(); + .unwrap(); println!("all links"); for (_, nrows) in db.export_relations(["a:vec"].iter()).unwrap() { @@ -917,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, @@ -926,7 +926,7 @@ fn test_fts_indexing() { }", Default::default(), ) - .unwrap(); + .unwrap(); db.run_script( r"?[k, v] <- [ ['b', 'the world is square!'], @@ -935,7 +935,7 @@ fn test_fts_indexing() { ] :put a {k => v}", Default::default(), ) - .unwrap(); + .unwrap(); let res = db .run_script( r" @@ -969,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!'], @@ -984,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(); @@ -1043,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()) @@ -1055,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()) @@ -1064,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}", @@ -1136,7 +1136,7 @@ fn multi_index_vec() { "#, Default::default(), ) - .unwrap(); + .unwrap(); db.run_script( r#" ::hnsw create product:semantic{ @@ -1148,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]]] @@ -1168,23 +1168,54 @@ fn multi_index_vec() { #[test] fn ensure_not() { let db = DbInstance::new("mem", "", "").unwrap(); - db.run_script(r" + 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(); + ", + 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()) + 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()) + assert!(db + .run_script( + r"?[x, y] <- [[1, 3]] :insert a {x => y}", + Default::default() + ) + .is_err()); +} + +#[test] +fn deletion() { + let db = DbInstance::new("mem", "", "").unwrap(); + db.run_script(r":create a {x => y}", Default::default()) + .unwrap(); + assert!(db + .run_script(r"?[x] <- [[1]] :delete a {x}", Default::default()) .is_err()); + assert!(db + .run_script( + r"?[x, y] <- [[1, 2]] :insert a {x => y}", + Default::default() + ) + .is_ok()); + db + .run_script(r"?[x] <- [[1]] :delete a {x}", Default::default()).unwrap(); } #[test] @@ -1198,12 +1229,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(); }