From 6b8438e823dd506cf9d0ff5d1e3ba41e7fedfce6 Mon Sep 17 00:00:00 2001 From: Ziyang Hu Date: Wed, 24 May 2023 13:23:15 +0800 Subject: [PATCH] query mutability --- cozo-bin/src/repl.rs | 14 +- cozo-bin/src/server.rs | 18 +- cozo-core/src/data/tests/exprs.rs | 10 +- cozo-core/src/data/tests/functions.rs | 128 ++-- cozo-core/src/data/tests/validity.rs | 59 +- .../src/fixed_rule/algos/shortest_path_bfs.rs | 10 +- cozo-core/src/lib.rs | 48 +- cozo-core/src/query/magic.rs | 7 +- cozo-core/src/query/ra.rs | 9 +- cozo-core/src/query/stratify.rs | 7 +- cozo-core/src/runtime/db.rs | 76 ++- cozo-core/src/runtime/imperative.rs | 9 +- cozo-core/src/runtime/tests.rs | 617 ++++++------------ cozo-core/tests/air_routes.rs | 239 +++---- cozo-lib-c/cozo_c.h | 5 +- cozo-lib-c/src/lib.rs | 3 +- cozo-lib-java/src/lib.rs | 2 +- cozo-lib-nodejs/index.js | 5 +- cozo-lib-nodejs/src/lib.rs | 11 +- cozo-lib-python/src/lib.rs | 20 +- cozo-lib-swift/src/lib.rs | 2 +- cozo-lib-wasm/src/lib.rs | 4 +- 22 files changed, 551 insertions(+), 752 deletions(-) diff --git a/cozo-bin/src/repl.rs b/cozo-bin/src/repl.rs index f51edda9..f4026906 100644 --- a/cozo-bin/src/repl.rs +++ b/cozo-bin/src/repl.rs @@ -20,7 +20,7 @@ use rustyline::history::DefaultHistory; use rustyline::Changeset; use serde_json::{json, Value}; -use cozo::{DataValue, DbInstance, evaluate_expressions, NamedRows}; +use cozo::{evaluate_expressions, DataValue, DbInstance, NamedRows, ScriptMutability}; struct Indented; @@ -83,13 +83,17 @@ pub(crate) fn repl_main(args: ReplArgs) -> Result<(), Box> { let db_copy = db.clone(); ctrlc::set_handler(move || { let running = db_copy - .run_script("::running", Default::default()) + .run_default("::running") .expect("Cannot determine running queries"); for row in running.rows { let id = row.into_iter().next().unwrap(); eprintln!("Killing running query {id}"); db_copy - .run_script("::kill $id", BTreeMap::from([("id".to_string(), id)])) + .run_script( + "::kill $id", + BTreeMap::from([("id".to_string(), id)]), + ScriptMutability::Mutable, + ) .expect("Cannot kill process"); } }) @@ -250,7 +254,7 @@ fn process_line( bail!("Run requires path to a script"); } let content = fs::read_to_string(path).into_diagnostic()?; - let out = db.run_script(&content, params.clone())?; + let out = db.run_script(&content, params.clone(), ScriptMutability::Mutable)?; process_out(out)?; } "restore" => { @@ -289,7 +293,7 @@ fn process_line( op => bail!("Unknown op: {}", op), } } else { - let out = db.run_script(line, params.clone())?; + let out = db.run_script(line, params.clone(), ScriptMutability::Mutable)?; process_out(out)?; } Ok(()) diff --git a/cozo-bin/src/server.rs b/cozo-bin/src/server.rs index 34d23f28..1b9df04f 100644 --- a/cozo-bin/src/server.rs +++ b/cozo-bin/src/server.rs @@ -35,7 +35,8 @@ use tower_http::compression::CompressionLayer; use tower_http::cors::{Any, CorsLayer}; use cozo::{ - format_error_as_json, DataValue, DbInstance, MultiTransaction, NamedRows, SimpleFixedRule, + format_error_as_json, DataValue, DbInstance, MultiTransaction, NamedRows, ScriptMutability, + SimpleFixedRule, }; #[derive(Args, Debug)] @@ -309,6 +310,7 @@ async fn finish_query( struct QueryPayload { script: String, params: BTreeMap, + immutable: Option, } async fn text_query( @@ -320,7 +322,19 @@ async fn text_query( .into_iter() .map(|(k, v)| (k, DataValue::from(v))) .collect(); - let result = spawn_blocking(move || st.db.run_script_fold_err(&payload.script, params)).await; + let immutable = payload.immutable.unwrap_or(false); + let result = spawn_blocking(move || { + st.db.run_script_fold_err( + &payload.script, + params, + if immutable { + ScriptMutability::Immutable + } else { + ScriptMutability::Mutable + }, + ) + }) + .await; match result { Ok(res) => wrap_json(res), Err(err) => internal_error(err), diff --git a/cozo-core/src/data/tests/exprs.rs b/cozo-core/src/data/tests/exprs.rs index 6c20f4e4..81366d55 100644 --- a/cozo-core/src/data/tests/exprs.rs +++ b/cozo-core/src/data/tests/exprs.rs @@ -6,28 +6,26 @@ * You can obtain one at https://mozilla.org/MPL/2.0/. */ -use crate::{new_cozo_mem, DataValue}; +use crate::{DataValue, DbInstance}; #[test] fn expression_eval() { - let db = new_cozo_mem().unwrap(); + let db = DbInstance::default(); let res = db - .run_script( + .run_default( r#" ?[a] := a = if(2 + 3 > 1 * 99999, 190291021 + 14341234212 / 2121) "#, - Default::default(), ) .unwrap(); assert_eq!(res.rows[0][0], DataValue::Null); let res = db - .run_script( + .run_default( r#" ?[a] := a = if(2 + 3 > 1, true, false) "#, - Default::default(), ) .unwrap(); assert_eq!(res.rows[0][0].get_bool().unwrap(), true); diff --git a/cozo-core/src/data/tests/functions.rs b/cozo-core/src/data/tests/functions.rs index 1525359d..5136b109 100644 --- a/cozo-core/src/data/tests/functions.rs +++ b/cozo-core/src/data/tests/functions.rs @@ -13,7 +13,7 @@ use serde_json::json; use crate::data::functions::*; use crate::data::value::{DataValue, RegexWrapper}; -use crate::new_cozo_mem; +use crate::DbInstance; #[test] fn test_add() { @@ -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,7 @@ fn test_slice() { DataValue::from(1), DataValue::from(4) ]) - .is_err()); + .is_err()); assert!(op_slice(&[ DataValue::List(vec![ @@ -1192,7 +1192,7 @@ fn test_slice() { DataValue::from(1), DataValue::from(3) ]) - .is_ok()); + .is_ok()); assert_eq!( op_slice(&[ @@ -1204,7 +1204,7 @@ fn test_slice() { DataValue::from(1), DataValue::from(-1) ]) - .unwrap(), + .unwrap(), DataValue::List(vec![DataValue::from(2)]) ); } @@ -1359,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!( @@ -1373,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!( @@ -1387,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()) ); } @@ -1456,39 +1456,33 @@ fn test_to_bool() { #[test] fn test_coalesce() { - let db = new_cozo_mem().unwrap(); - let res = db - .run_script("?[a] := a = null ~ 1 ~ 2", Default::default()) - .unwrap() - .rows; + let db = DbInstance::default(); + let res = db.run_default("?[a] := a = null ~ 1 ~ 2").unwrap().rows; assert_eq!(res[0][0], DataValue::from(1)); let res = db - .run_script("?[a] := a = null ~ null ~ null", Default::default()) + .run_default("?[a] := a = null ~ null ~ null") .unwrap() .rows; assert_eq!(res[0][0], DataValue::Null); - let res = db - .run_script("?[a] := a = 2 ~ null ~ 1", Default::default()) - .unwrap() - .rows; + let res = db.run_default("?[a] := a = 2 ~ null ~ 1").unwrap().rows; assert_eq!(res[0][0], DataValue::from(2)); } #[test] fn test_range() { - let db = new_cozo_mem().unwrap(); + let db = DbInstance::default(); let res = db - .run_script("?[a] := a = int_range(1, 5)", Default::default()) + .run_default("?[a] := a = int_range(1, 5)") .unwrap() .into_json(); assert_eq!(res["rows"][0][0], json!([1, 2, 3, 4])); let res = db - .run_script("?[a] := a = int_range(5)", Default::default()) + .run_default("?[a] := a = int_range(5)") .unwrap() .into_json(); assert_eq!(res["rows"][0][0], json!([0, 1, 2, 3, 4])); let res = db - .run_script("?[a] := a = int_range(15, 3, -2)", Default::default()) + .run_default("?[a] := a = int_range(15, 3, -2)") .unwrap() .into_json(); assert_eq!(res["rows"][0][0], json!([15, 13, 11, 9, 7, 5])); diff --git a/cozo-core/src/data/tests/validity.rs b/cozo-core/src/data/tests/validity.rs index 1cf9f019..dcb839c5 100644 --- a/cozo-core/src/data/tests/validity.rs +++ b/cozo-core/src/data/tests/validity.rs @@ -19,108 +19,97 @@ fn test_validity() { let _ = std::fs::remove_dir_all(path); let db_kind = env::var("COZO_TEST_DB_ENGINE").unwrap_or("mem".to_string()); println!("Using {} engine", db_kind); - let db = DbInstance::new(&db_kind, path, Default::default()).unwrap(); + let db = DbInstance::default(); - db.run_script(":create vld {a, v: Validity => d}", Default::default()) - .unwrap(); + db.run_default(":create vld {a, v: Validity => d}").unwrap(); assert!(db - .run_script( + .run_default( r#" ?[a, v, d] <- [[1, [9223372036854775807, true], null]] :put vld {a, v => d} "#, - Default::default(), ) .is_err()); assert!(db - .run_script( + .run_default( r#" ?[a, v, d] <- [[1, [-9223372036854775808, true], null]] :put vld {a, v => d} "#, - Default::default(), ) .is_err()); - db.run_script( + db.run_default( r#" ?[a, v, d] <- [[1, [0, true], 0]] :put vld {a, v => d} "#, - Default::default(), ) .unwrap(); let res = db - .run_script( + .run_default( r#" ?[a, v, d] := *vld{a, v, d @ "NOW"} "#, - Default::default(), ) .unwrap() .rows; assert_eq!(res.len(), 1); let res = db - .run_script( + .run_default( r#" ?[a, v, d] := *vld{a, v, d} "#, - Default::default(), ) .unwrap() .rows; assert_eq!(res.len(), 1); - db.run_script( + db.run_default( r#" ?[a, v, d] <- [[1, [1, false], 1]] :put vld {a, v => d} "#, - Default::default(), ) .unwrap(); let res = db - .run_script( + .run_default( r#" ?[a, v, d] := *vld{a, v, d @ "NOW"} "#, - Default::default(), ) .unwrap() .rows; assert_eq!(res.len(), 0); let res = db - .run_script( + .run_default( r#" ?[a, v, d] := *vld{a, v, d} "#, - Default::default(), ) .unwrap() .rows; assert_eq!(res.len(), 2); - db.run_script( + db.run_default( r#" ?[a, v, d] <- [[1, "ASSERT", 2]] :put vld {a, v => d} "#, - Default::default(), ) .unwrap(); let res = db - .run_script( + .run_default( r#" ?[a, v, d] := *vld{a, v, d @ "NOW"} "#, - Default::default(), ) .unwrap() .rows; @@ -128,72 +117,65 @@ fn test_validity() { assert_eq!(res[0][2].get_int().unwrap(), 2); let res = db - .run_script( + .run_default( r#" ?[a, v, d] := *vld{a, v, d} "#, - Default::default(), ) .unwrap() .rows; assert_eq!(res.len(), 3); - db.run_script( + db.run_default( r#" ?[a, v, d] <- [[1, "RETRACT", 3]] :put vld {a, v => d} "#, - Default::default(), ) .unwrap(); let res = db - .run_script( + .run_default( r#" ?[a, v, d] := *vld{a, v, d @ "NOW"} "#, - Default::default(), ) .unwrap() .rows; assert_eq!(res.len(), 0); let res = db - .run_script( + .run_default( r#" ?[a, v, d] := *vld{a, v, d} "#, - Default::default(), ) .unwrap() .rows; assert_eq!(res.len(), 4); - db.run_script( + db.run_default( r#" ?[a, v, d] <- [[1, [9223372036854775806, true], null]] :put vld {a, v => d} "#, - Default::default(), ) .unwrap(); let res = db - .run_script( + .run_default( r#" ?[a, v, d] := *vld{a, v, d @ "NOW"} "#, - Default::default(), ) .unwrap() .rows; assert_eq!(res.len(), 0); let res = db - .run_script( + .run_default( r#" ?[a, v, d] := *vld{a, v, d @ "END"} "#, - Default::default(), ) .unwrap() .rows; @@ -201,11 +183,10 @@ fn test_validity() { assert_eq!(res[0][2], DataValue::Null); let res = db - .run_script( + .run_default( r#" ?[a, v, d] := *vld{a, v, d} "#, - Default::default(), ) .unwrap() .rows; diff --git a/cozo-core/src/fixed_rule/algos/shortest_path_bfs.rs b/cozo-core/src/fixed_rule/algos/shortest_path_bfs.rs index 594b5622..de1ac390 100644 --- a/cozo-core/src/fixed_rule/algos/shortest_path_bfs.rs +++ b/cozo-core/src/fixed_rule/algos/shortest_path_bfs.rs @@ -125,13 +125,13 @@ impl FixedRule for ShortestPathBFS { mod tests { use crate::data::value::DataValue; - use crate::new_cozo_mem; + use crate::DbInstance; #[test] fn test_bfs_path() { - let db = new_cozo_mem().unwrap(); + let db = DbInstance::default(); let res = db - .run_script( + .run_default( r#" love[loving, loved] <- [['alice', 'eve'], ['bob', 'alice'], @@ -145,14 +145,13 @@ mod tests { end[] <- [['bob']] ?[fr, to, path] <~ ShortestPathBFS(love[], start[], end[]) "#, - Default::default(), ) .unwrap() .rows; println!("{:?}", res); assert_eq!(res[0][2].get_slice().unwrap().len(), 3); let res = db - .run_script( + .run_default( r#" love[loving, loved] <- [['alice', 'eve'], ['bob', 'alice'], @@ -166,7 +165,6 @@ mod tests { end[] <- [['george']] ?[fr, to, path] <~ ShortestPathBFS(love[], start[], end[]) "#, - Default::default(), ) .unwrap() .rows; diff --git a/cozo-core/src/lib.rs b/cozo-core/src/lib.rs index 44dbb4e8..04400f06 100644 --- a/cozo-core/src/lib.rs +++ b/cozo-core/src/lib.rs @@ -19,7 +19,7 @@ //! //! let db = DbInstance::new("mem", "", Default::default()).unwrap(); //! let script = "?[a] := a in [1, 2, 3]"; -//! let result = db.run_script(script, Default::default()).unwrap(); +//! let result = db.run_script(script, Default::default(), ScriptMutability::Immutable).unwrap(); //! println!("{:?}", result); //! ``` //! We created an in-memory database above. There are other persistent options: @@ -68,23 +68,24 @@ pub use storage::{Storage, StoreTx}; pub use crate::data::expr::Expr; use crate::data::json::JsonValue; pub use crate::data::symb::Symbol; -pub use crate::data::value::{Vector, JsonData}; +pub use crate::data::value::{JsonData, Vector}; pub use crate::fixed_rule::SimpleFixedRule; pub use crate::parse::SourceSpan; pub use crate::runtime::callback::CallbackOp; -pub use crate::runtime::db::Poison; -pub use crate::runtime::db::TransactionPayload; pub use crate::runtime::db::evaluate_expressions; pub use crate::runtime::db::get_variables; +pub use crate::runtime::db::Poison; +pub use crate::runtime::db::ScriptMutability; +pub use crate::runtime::db::TransactionPayload; pub(crate) mod data; pub(crate) mod fixed_rule; +pub(crate) mod fts; pub(crate) mod parse; pub(crate) mod query; pub(crate) mod runtime; pub(crate) mod storage; pub(crate) mod utils; -pub(crate) mod fts; /// A dispatcher for concrete storage implementations, wrapping [Db]. This is done so that /// client code does not have to deal with generic code constantly. You may prefer to use @@ -113,6 +114,12 @@ pub enum DbInstance { TiKv(Db), } +impl Default for DbInstance { + fn default() -> Self { + Self::new("mem", "", Default::default()).unwrap() + } +} + impl DbInstance { /// Create a DbInstance, which is a dispatcher for various concrete implementations. /// The valid engines are: @@ -168,19 +175,24 @@ impl DbInstance { &self, payload: &str, params: BTreeMap, + mutability: ScriptMutability, ) -> Result { match self { - DbInstance::Mem(db) => db.run_script(payload, params), + DbInstance::Mem(db) => db.run_script(payload, params, mutability), #[cfg(feature = "storage-sqlite")] - DbInstance::Sqlite(db) => db.run_script(payload, params), + DbInstance::Sqlite(db) => db.run_script(payload, params, mutability), #[cfg(feature = "storage-rocksdb")] - DbInstance::RocksDb(db) => db.run_script(payload, params), + DbInstance::RocksDb(db) => db.run_script(payload, params, mutability), #[cfg(feature = "storage-sled")] - DbInstance::Sled(db) => db.run_script(payload, params), + DbInstance::Sled(db) => db.run_script(payload, params, mutability), #[cfg(feature = "storage-tikv")] - DbInstance::TiKv(db) => db.run_script(payload, params), + DbInstance::TiKv(db) => db.run_script(payload, params, mutability), } } + /// `run_script` with mutable script and no parameters + pub fn run_default(&self, payload: &str) -> Result { + return self.run_script(payload, BTreeMap::new(), ScriptMutability::Mutable); + } /// Run the CozoScript passed in. The `params` argument is a map of parameters. /// Fold any error into the return JSON itself. /// See [crate::Db::run_script]. @@ -188,11 +200,12 @@ impl DbInstance { &self, payload: &str, params: BTreeMap, + mutability: ScriptMutability, ) -> JsonValue { #[cfg(not(target_arch = "wasm32"))] let start = Instant::now(); - match self.run_script(payload, params) { + match self.run_script(payload, params, mutability) { Ok(named_rows) => { let mut j_val = named_rows.into_json(); #[cfg(not(target_arch = "wasm32"))] @@ -209,7 +222,7 @@ impl DbInstance { } /// Run the CozoScript passed in. The `params` argument is a map of parameters formatted as JSON. /// See [crate::Db::run_script]. - pub fn run_script_str(&self, payload: &str, params: &str) -> String { + pub fn run_script_str(&self, payload: &str, params: &str, immutable: bool) -> String { let params_json = if params.is_empty() { BTreeMap::default() } else { @@ -224,7 +237,16 @@ impl DbInstance { } } }; - self.run_script_fold_err(payload, params_json).to_string() + self.run_script_fold_err( + payload, + params_json, + if immutable { + ScriptMutability::Immutable + } else { + ScriptMutability::Mutable + }, + ) + .to_string() } /// Dispatcher method. See [crate::Db::export_relations]. pub fn export_relations(&self, relations: I) -> Result> diff --git a/cozo-core/src/query/magic.rs b/cozo-core/src/query/magic.rs index 0ab0480f..f4ea0800 100644 --- a/cozo-core/src/query/magic.rs +++ b/cozo-core/src/query/magic.rs @@ -641,7 +641,7 @@ mod tests { #[test] fn strange_case() { - let db = DbInstance::new("mem", "", "").unwrap(); + let db = DbInstance::default(); let query = r#" x[A] := A = 1 @@ -653,10 +653,7 @@ mod tests { :disable_magic_rewrite true "#; - let res = db - .run_script(query, Default::default()) - .unwrap() - .into_json(); + let res = db.run_default(query).unwrap().into_json(); assert_eq!(res["rows"], json!([[0], [1]])); } } diff --git a/cozo-core/src/query/ra.rs b/cozo-core/src/query/ra.rs index 0c4a4beb..576dd91f 100644 --- a/cozo-core/src/query/ra.rs +++ b/cozo-core/src/query/ra.rs @@ -1036,7 +1036,7 @@ impl FtsSearchRA { coll.write_str(" OR ").unwrap(); } coll.write_str(&s).unwrap(); - }, + } d => bail!("Expected string for FTS search, got {:?}", d), } } @@ -2404,18 +2404,17 @@ impl<'a> Iterator for CachedMaterializedIterator<'a> { #[cfg(test)] mod tests { use crate::data::value::DataValue; - use crate::new_cozo_mem; + use crate::DbInstance; #[test] fn test_mat_join() { - let db = new_cozo_mem().unwrap(); + let db = DbInstance::default(); let res = db - .run_script( + .run_default( r#" data[a, b] <- [[1, 2], [1, 3], [2, 3]] ?[x] := a = 3, data[x, a] "#, - Default::default(), ) .unwrap() .rows; diff --git a/cozo-core/src/query/stratify.rs b/cozo-core/src/query/stratify.rs index 246677e2..afcc2030 100644 --- a/cozo-core/src/query/stratify.rs +++ b/cozo-core/src/query/stratify.rs @@ -310,13 +310,13 @@ impl NormalFormProgram { #[cfg(test)] mod tests { - use crate::new_cozo_mem; + use crate::DbInstance; #[test] fn test_dependencies() { - let db = new_cozo_mem().unwrap(); + let db = DbInstance::default(); let _res = db - .run_script( + .run_default( r#" x[a] <- [[1], [2]] w[a] := a in [2] @@ -328,7 +328,6 @@ mod tests { ?[a] := z[a] ?[a] := w[a] "#, - Default::default(), ) .unwrap() .rows; diff --git a/cozo-core/src/runtime/db.rs b/cozo-core/src/runtime/db.rs index a73cf661..42e0ffcb 100644 --- a/cozo-core/src/runtime/db.rs +++ b/cozo-core/src/runtime/db.rs @@ -83,6 +83,15 @@ pub struct DbManifest { pub storage_version: u64, } +/// Whether a script is mutable or immutable. +#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] +pub enum ScriptMutability { + /// The script is mutable. + Mutable, + /// The script is immutable. + Immutable, +} + /// The database object of Cozo. #[derive(Clone)] pub struct Db { @@ -369,10 +378,26 @@ impl<'s, S: Storage<'s>> Db { &'s self, payload: &str, params: BTreeMap, + mutability: ScriptMutability, ) -> Result { let cur_vld = current_validity(); - self.do_run_script(payload, ¶ms, cur_vld) + self.do_run_script( + payload, + ¶ms, + cur_vld, + mutability == ScriptMutability::Immutable, + ) } + /// Run the CozoScript passed in. The `params` argument is a map of parameters. + pub fn run_script_read_only( + &'s self, + payload: &str, + params: BTreeMap, + ) -> Result { + let cur_vld = current_validity(); + self.do_run_script(payload, ¶ms, cur_vld, true) + } + /// Export relations to JSON data. /// /// `relations` contains names of the stored relations to export. @@ -847,6 +872,7 @@ impl<'s, S: Storage<'s>> Db { payload: &str, param_pool: &BTreeMap, cur_vld: ValidityTs, + read_only: bool, ) -> Result { match parse_script( payload, @@ -854,16 +880,24 @@ impl<'s, S: Storage<'s>> Db { &self.fixed_rules.read().unwrap(), cur_vld, )? { - CozoScript::Single(p) => self.execute_single(cur_vld, p), - CozoScript::Imperative(ps) => self.execute_imperative(cur_vld, &ps), - CozoScript::Sys(op) => self.run_sys_op(op), + CozoScript::Single(p) => self.execute_single(cur_vld, p, read_only), + CozoScript::Imperative(ps) => self.execute_imperative(cur_vld, &ps, read_only), + CozoScript::Sys(op) => self.run_sys_op(op, read_only), } } - fn execute_single(&'s self, cur_vld: ValidityTs, p: InputProgram) -> Result { + fn execute_single( + &'s self, + cur_vld: ValidityTs, + p: InputProgram, + read_only: bool, + ) -> Result { let mut callback_collector = BTreeMap::new(); let write_lock_names = p.needs_write_lock(); let is_write = write_lock_names.is_some(); + if read_only && is_write { + bail!("write lock required for read-only query"); + } let write_lock = self.obtain_relation_locks(write_lock_names.iter()); let _write_lock_guards = if is_write { Some(write_lock[0].read().unwrap()) @@ -1130,7 +1164,7 @@ impl<'s, S: Storage<'s>> Db { Ok(NamedRows::new(headers, rows)) } - fn run_sys_op(&'s self, op: SysOp) -> Result { + fn run_sys_op(&'s self, op: SysOp, read_only: bool) -> Result { match op { SysOp::Explain(prog) => { let mut tx = self.transact()?; @@ -1142,6 +1176,9 @@ impl<'s, S: Storage<'s>> Db { self.explain_compiled(&compiled) } SysOp::Compact => { + if read_only { + bail!("Cannot compact in read-only mode"); + } self.compact_relation()?; Ok(NamedRows::new( vec![STATUS_STR.to_string()], @@ -1160,6 +1197,9 @@ impl<'s, S: Storage<'s>> Db { )) } SysOp::RemoveRelation(rel_names) => { + if read_only { + bail!("Cannot remove relations in read-only mode"); + } let rel_name_strs = rel_names.iter().map(|n| &n.name); let locks = self.obtain_relation_locks(rel_name_strs); let _guards = locks.iter().map(|l| l.read().unwrap()).collect_vec(); @@ -1189,6 +1229,9 @@ impl<'s, S: Storage<'s>> Db { )) } SysOp::CreateIndex(rel_name, idx_name, cols) => { + if read_only { + bail!("Cannot create index in read-only mode"); + } let lock = self .obtain_relation_locks(iter::once(&rel_name.name)) .pop() @@ -1203,6 +1246,9 @@ impl<'s, S: Storage<'s>> Db { )) } SysOp::CreateVectorIndex(config) => { + if read_only { + bail!("Cannot create vector index in read-only mode"); + } let lock = self .obtain_relation_locks(iter::once(&config.base_relation)) .pop() @@ -1217,6 +1263,9 @@ impl<'s, S: Storage<'s>> Db { )) } SysOp::CreateFtsIndex(config) => { + if read_only { + bail!("Cannot create fts index in read-only mode"); + } let lock = self .obtain_relation_locks(iter::once(&config.base_relation)) .pop() @@ -1231,6 +1280,9 @@ impl<'s, S: Storage<'s>> Db { )) } SysOp::CreateMinHashLshIndex(config) => { + if read_only { + bail!("Cannot create minhash lsh index in read-only mode"); + } let lock = self .obtain_relation_locks(iter::once(&config.base_relation)) .pop() @@ -1245,6 +1297,9 @@ impl<'s, S: Storage<'s>> Db { )) } SysOp::RemoveIndex(rel_name, idx_name) => { + if read_only { + bail!("Cannot remove index in read-only mode"); + } let lock = self .obtain_relation_locks(iter::once(&rel_name.name)) .pop() @@ -1264,6 +1319,9 @@ impl<'s, S: Storage<'s>> Db { SysOp::ListColumns(rs) => self.list_columns(&rs), SysOp::ListIndices(rs) => self.list_indices(&rs), SysOp::RenameRelation(rename_pairs) => { + if read_only { + bail!("Cannot rename relations in read-only mode"); + } let rel_names = rename_pairs.iter().flat_map(|(f, t)| [&f.name, &t.name]); let locks = self.obtain_relation_locks(rel_names); let _guards = locks.iter().map(|l| l.read().unwrap()).collect_vec(); @@ -1318,6 +1376,9 @@ impl<'s, S: Storage<'s>> Db { )) } SysOp::SetTriggers(name, puts, rms, replaces) => { + if read_only { + bail!("Cannot set triggers in read-only mode"); + } let mut tx = self.transact_write()?; tx.set_relation_triggers(name, puts, rms, replaces)?; tx.commit_tx()?; @@ -1327,6 +1388,9 @@ impl<'s, S: Storage<'s>> Db { )) } SysOp::SetAccessLevel(names, level) => { + if read_only { + bail!("Cannot set access level in read-only mode"); + } let mut tx = self.transact_write()?; for name in names { tx.set_access_level(name, level)?; diff --git a/cozo-core/src/runtime/imperative.rs b/cozo-core/src/runtime/imperative.rs index fb9a15ff..ed3df0bd 100644 --- a/cozo-core/src/runtime/imperative.rs +++ b/cozo-core/src/runtime/imperative.rs @@ -247,9 +247,13 @@ impl<'s, S: Storage<'s>> Db { &'s self, cur_vld: ValidityTs, ps: &ImperativeProgram, + read_only: bool, ) -> Result { let mut callback_collector = BTreeMap::new(); let mut write_lock_names = BTreeSet::new(); + if read_only && !write_lock_names.is_empty() { + bail!("Read-only imperative program attempted to acquire write locks"); + } for p in ps { p.needs_write_locks(&mut write_lock_names); } @@ -338,7 +342,10 @@ impl SessionTx<'_> { let k = k.replace('(', "_").replace(')', ""); let k = Symbol::new(k.clone(), Default::default()); if key_bindings.contains(&k) { - bail!("Duplicate variable name {}, please use distinct variables in `as` construct.", k); + bail!( + "Duplicate variable name {}, please use distinct variables in `as` construct.", + k + ); } key_bindings.push(k); } diff --git a/cozo-core/src/runtime/tests.rs b/cozo-core/src/runtime/tests.rs index f8ce12f5..ea417607 100644 --- a/cozo-core/src/runtime/tests.rs +++ b/cozo-core/src/runtime/tests.rs @@ -23,37 +23,28 @@ use crate::fts::{TokenizerCache, TokenizerConfig}; use crate::parse::SourceSpan; use crate::runtime::callback::CallbackOp; use crate::runtime::db::Poison; -use crate::{new_cozo_mem, DbInstance, FixedRule, RegularTempStore}; +use crate::{DbInstance, FixedRule, RegularTempStore, ScriptMutability}; #[test] fn test_limit_offset() { - let db = new_cozo_mem().unwrap(); + let db = DbInstance::default(); let res = db - .run_script("?[a] := a in [5,3,1,2,4] :limit 2", Default::default()) + .run_default("?[a] := a in [5,3,1,2,4] :limit 2") .unwrap() .into_json(); assert_eq!(res["rows"], json!([[3], [5]])); let res = db - .run_script( - "?[a] := a in [5,3,1,2,4] :limit 2 :offset 1", - Default::default(), - ) + .run_default("?[a] := a in [5,3,1,2,4] :limit 2 :offset 1") .unwrap() .into_json(); assert_eq!(res["rows"], json!([[1], [3]])); let res = db - .run_script( - "?[a] := a in [5,3,1,2,4] :limit 2 :offset 4", - Default::default(), - ) + .run_default("?[a] := a in [5,3,1,2,4] :limit 2 :offset 4") .unwrap() .into_json(); assert_eq!(res["rows"], json!([[4]])); let res = db - .run_script( - "?[a] := a in [5,3,1,2,4] :limit 2 :offset 5", - Default::default(), - ) + .run_default("?[a] := a in [5,3,1,2,4] :limit 2 :offset 5") .unwrap() .into_json(); assert_eq!(res["rows"], json!([])); @@ -61,25 +52,19 @@ fn test_limit_offset() { #[test] fn test_normal_aggr_empty() { - let db = new_cozo_mem().unwrap(); - let res = db - .run_script("?[count(a)] := a in []", Default::default()) - .unwrap() - .rows; + let db = DbInstance::default(); + let res = db.run_default("?[count(a)] := a in []").unwrap().rows; assert_eq!(res, vec![vec![DataValue::from(0)]]); } #[test] fn test_meet_aggr_empty() { - let db = new_cozo_mem().unwrap(); - let res = db - .run_script("?[min(a)] := a in []", Default::default()) - .unwrap() - .rows; + let db = DbInstance::default(); + let res = db.run_default("?[min(a)] := a in []").unwrap().rows; assert_eq!(res, vec![vec![DataValue::Null]]); let res = db - .run_script("?[min(a), count(a)] := a in []", Default::default()) + .run_default("?[min(a), count(a)] := a in []") .unwrap() .rows; assert_eq!(res, vec![vec![DataValue::Null, DataValue::from(0)]]); @@ -89,16 +74,15 @@ fn test_meet_aggr_empty() { fn test_layers() { let _ = env_logger::builder().is_test(true).try_init(); - let db = new_cozo_mem().unwrap(); + let db = DbInstance::default(); let res = db - .run_script( + .run_default( r#" y[a] := a in [1,2,3] x[sum(a)] := y[a] x[sum(a)] := a in [4,5,6] ?[sum(a)] := x[a] "#, - Default::default(), ) .unwrap() .rows; @@ -108,8 +92,8 @@ fn test_layers() { #[test] fn test_conditions() { let _ = env_logger::builder().is_test(true).try_init(); - let db = new_cozo_mem().unwrap(); - db.run_script( + let db = DbInstance::default(); + db.run_default( r#" { ?[code] <- [['a'],['b'],['c']] @@ -120,17 +104,15 @@ fn test_conditions() { :create route {fr, to => dist} } "#, - Default::default(), ) .unwrap(); debug!("real test begins"); let res = db - .run_script( + .run_default( r#" r[code, dist] := *airport{code}, *route{fr: code, dist}; ?[dist] := r['a', dist], dist > 0.5, dist <= 1.1; "#, - Default::default(), ) .unwrap() .rows; @@ -140,9 +122,9 @@ fn test_conditions() { #[test] fn test_classical() { let _ = env_logger::builder().is_test(true).try_init(); - let db = new_cozo_mem().unwrap(); + let db = DbInstance::default(); let res = db - .run_script( + .run_default( r#" parent[] <- [['joseph', 'jakob'], ['jakob', 'isaac'], @@ -150,7 +132,6 @@ parent[] <- [['joseph', 'jakob'], grandparent[gcld, gp] := parent[gcld, p], parent[p, gp] ?[who] := grandparent[who, 'abraham'] "#, - Default::default(), ) .unwrap() .rows; @@ -160,118 +141,98 @@ grandparent[gcld, gp] := parent[gcld, p], parent[p, gp] #[test] fn default_columns() { - let db = new_cozo_mem().unwrap(); + let db = DbInstance::default(); - db.run_script( + db.run_default( r#" :create status {uid: String, ts default now() => quitted: Bool, mood: String} "#, - Default::default(), ) .unwrap(); - db.run_script( + db.run_default( r#" ?[uid, quitted, mood] <- [['z', true, 'x']] :put status {uid => quitted, mood} "#, - Default::default(), ) .unwrap(); } #[test] fn rm_does_not_need_all_keys() { - let db = new_cozo_mem().unwrap(); - db.run_script(":create status {uid => mood}", Default::default()) - .unwrap(); + let db = DbInstance::default(); + db.run_default(":create status {uid => mood}").unwrap(); assert!(db - .run_script( - "?[uid, mood] <- [[1, 2]] :put status {uid => mood}", - Default::default(), - ) + .run_default("?[uid, mood] <- [[1, 2]] :put status {uid => mood}",) .is_ok()); assert!(db - .run_script( - "?[uid, mood] <- [[2]] :put status {uid}", - Default::default(), - ) + .run_default("?[uid, mood] <- [[2]] :put status {uid}",) .is_err()); assert!(db - .run_script( - "?[uid, mood] <- [[3, 2]] :rm status {uid => mood}", - Default::default(), - ) - .is_ok()); - assert!(db - .run_script("?[uid] <- [[1]] :rm status {uid}", Default::default()) + .run_default("?[uid, mood] <- [[3, 2]] :rm status {uid => mood}",) .is_ok()); + assert!(db.run_default("?[uid] <- [[1]] :rm status {uid}").is_ok()); } #[test] fn strict_checks_for_fixed_rules_args() { - let db = new_cozo_mem().unwrap(); - let res = db.run_script( + let db = DbInstance::default(); + let res = db.run_default( r#" r[] <- [[1, 2]] ?[] <~ PageRank(r[_, _]) "#, - Default::default(), ); assert!(res.is_ok()); - let db = new_cozo_mem().unwrap(); - let res = db.run_script( + let db = DbInstance::default(); + let res = db.run_default( r#" r[] <- [[1, 2]] ?[] <~ PageRank(r[a, b]) "#, - Default::default(), ); assert!(res.is_ok()); - let db = new_cozo_mem().unwrap(); - let res = db.run_script( + let db = DbInstance::default(); + let res = db.run_default( r#" r[] <- [[1, 2]] ?[] <~ PageRank(r[a, a]) "#, - Default::default(), ); assert!(res.is_err()); } #[test] fn do_not_unify_underscore() { - let db = new_cozo_mem().unwrap(); + let db = DbInstance::default(); let res = db - .run_script( + .run_default( r#" r1[] <- [[1, 'a'], [2, 'b']] r2[] <- [[2, 'B'], [3, 'C']] ?[l1, l2] := r1[_ , l1], r2[_ , l2] "#, - Default::default(), ) .unwrap() .rows; assert_eq!(res.len(), 4); - let res = db.run_script( + let res = db.run_default( r#" ?[_] := _ = 1 "#, - Default::default(), ); assert!(res.is_err()); let res = db - .run_script( + .run_default( r#" ?[x] := x = 1, _ = 1, _ = 2 "#, - Default::default(), ) .unwrap() .rows; @@ -281,9 +242,9 @@ fn do_not_unify_underscore() { #[test] fn imperative_script() { - // let db = new_cozo_mem().unwrap(); + // let db = DbInstance::default(); // let res = db - // .run_script( + // .run_default( // r#" // {:create _test {a}} // @@ -301,7 +262,7 @@ fn imperative_script() { // assert_eq!(res.rows.len(), 10); // // let res = db - // .run_script( + // .run_default( // r#" // {?[a] <- [[1], [2], [3]] // :replace _test {a}} @@ -322,7 +283,7 @@ fn imperative_script() { // .unwrap(); // assert_eq!(res.rows.len(), 0); // - // let res = db.run_script( + // let res = db.run_default( // r#" // {:create _test {a}} // @@ -345,7 +306,7 @@ fn imperative_script() { // assert_eq!(res.unwrap().rows.len(), 10); // // let res = db - // .run_script( + // .run_default( // r#" // {?[a] <- [[1], [2], [3]] // :replace _test {a}} @@ -362,42 +323,34 @@ fn imperative_script() { #[test] fn returning_relations() { - let db = new_cozo_mem().unwrap(); + let db = DbInstance::default(); let res = db - .run_script( + .run_default( r#" {:create _xxz {a}} {?[a] := a in [5,4,1,2,3] :put _xxz {a}} {?[a] := *_xxz[a], a % 2 == 0 :rm _xxz {a}} {?[a] := *_xxz[b], a = b * 2} "#, - Default::default(), ) .unwrap(); assert_eq!(res.into_json()["rows"], json!([[2], [6], [10]])); - let res = db.run_script( + let res = db.run_default( r#" {?[a] := *_xxz[b], a = b * 2} "#, - Default::default(), ); assert!(res.is_err()); } #[test] fn test_trigger() { - let db = new_cozo_mem().unwrap(); - db.run_script( - ":create friends {fr: Int, to: Int => data: Any}", - Default::default(), - ) - .unwrap(); - db.run_script( - ":create friends.rev {to: Int, fr: Int => data: Any}", - Default::default(), - ) - .unwrap(); - db.run_script( + let db = DbInstance::default(); + db.run_default(":create friends {fr: Int, to: Int => data: Any}") + .unwrap(); + db.run_default(":create friends.rev {to: Int, fr: Int => data: Any}") + .unwrap(); + db.run_default( r#" ::set_triggers friends @@ -412,14 +365,10 @@ fn test_trigger() { :rm friends.rev{ to, fr } } "#, - Default::default(), - ) - .unwrap(); - db.run_script( - r"?[fr, to, data] <- [[1,2,3]] :put friends {fr, to => data}", - Default::default(), ) .unwrap(); + db.run_default(r"?[fr, to, data] <- [[1,2,3]] :put friends {fr, to => data}") + .unwrap(); let ret = db .export_relations(["friends", "friends.rev"].into_iter()) .unwrap(); @@ -434,11 +383,8 @@ fn test_trigger() { vec![DataValue::from(2), DataValue::from(1), DataValue::from(3)], frs_rev.rows[0] ); - db.run_script( - r"?[fr, to] <- [[1,2], [2,3]] :rm friends {fr, to}", - Default::default(), - ) - .unwrap(); + db.run_default(r"?[fr, to] <- [[1,2], [2,3]] :rm friends {fr, to}") + .unwrap(); let ret = db .export_relations(["friends", "friends.rev"].into_iter()) .unwrap(); @@ -448,29 +394,17 @@ fn test_trigger() { #[test] fn test_callback() { - let db = new_cozo_mem().unwrap(); + let db = DbInstance::default(); let mut collected = vec![]; let (_id, receiver) = db.register_callback("friends", None); - db.run_script( - ":create friends {fr: Int, to: Int => data: Any}", - Default::default(), - ) - .unwrap(); - db.run_script( - r"?[fr, to, data] <- [[1,2,3],[4,5,6]] :put friends {fr, to => data}", - Default::default(), - ) - .unwrap(); - db.run_script( - r"?[fr, to, data] <- [[1,2,4],[4,7,6]] :put friends {fr, to => data}", - Default::default(), - ) - .unwrap(); - db.run_script( - r"?[fr, to] <- [[1,9],[4,5]] :rm friends {fr, to}", - Default::default(), - ) - .unwrap(); + db.run_default(":create friends {fr: Int, to: Int => data: Any}") + .unwrap(); + db.run_default(r"?[fr, to, data] <- [[1,2,3],[4,5,6]] :put friends {fr, to => data}") + .unwrap(); + db.run_default(r"?[fr, to, data] <- [[1,2,4],[4,7,6]] :put friends {fr, to => data}") + .unwrap(); + db.run_default(r"?[fr, to] <- [[1,9],[4,5]] :rm friends {fr, to}") + .unwrap(); std::thread::sleep(Duration::from_secs_f64(0.01)); while let Ok(d) = receiver.try_recv() { collected.push(d); @@ -497,35 +431,20 @@ fn test_callback() { #[test] fn test_update() { - let db = new_cozo_mem().unwrap(); - db.run_script( - ":create friends {fr: Int, to: Int => a: Any, b: Any, c: Any}", - Default::default(), - ) - .unwrap(); - db.run_script( - "?[fr, to, a, b, c] <- [[1,2,3,4,5]] :put friends {fr, to => a, b, c}", - Default::default(), - ) - .unwrap(); + let db = DbInstance::default(); + db.run_default(":create friends {fr: Int, to: Int => a: Any, b: Any, c: Any}") + .unwrap(); + db.run_default("?[fr, to, a, b, c] <- [[1,2,3,4,5]] :put friends {fr, to => a, b, c}") + .unwrap(); let res = db - .run_script( - "?[fr, to, a, b, c] := *friends{fr, to, a, b, c}", - Default::default(), - ) + .run_default("?[fr, to, a, b, c] := *friends{fr, to, a, b, c}") .unwrap() .into_json(); assert_eq!(res["rows"][0], json!([1, 2, 3, 4, 5])); - db.run_script( - "?[fr, to, b] <- [[1, 2, 100]] :update friends {fr, to => b}", - Default::default(), - ) - .unwrap(); + db.run_default("?[fr, to, b] <- [[1, 2, 100]] :update friends {fr, to => b}") + .unwrap(); let res = db - .run_script( - "?[fr, to, a, b, c] := *friends{fr, to, a, b, c}", - Default::default(), - ) + .run_default("?[fr, to, a, b, c] := *friends{fr, to, a, b, c}") .unwrap() .into_json(); assert_eq!(res["rows"][0], json!([1, 2, 3, 100, 5])); @@ -533,35 +452,23 @@ fn test_update() { #[test] fn test_index() { - let db = new_cozo_mem().unwrap(); - db.run_script( - ":create friends {fr: Int, to: Int => data: Any}", - Default::default(), - ) - .unwrap(); + let db = DbInstance::default(); + db.run_default(":create friends {fr: Int, to: Int => data: Any}") + .unwrap(); - db.run_script( - r"?[fr, to, data] <- [[1,2,3],[4,5,6]] :put friends {fr, to, data}", - Default::default(), - ) - .unwrap(); + db.run_default(r"?[fr, to, data] <- [[1,2,3],[4,5,6]] :put friends {fr, to, data}") + .unwrap(); assert!(db - .run_script("::index create friends:rev {to, no}", Default::default()) + .run_default("::index create friends:rev {to, no}") .is_err()); - db.run_script("::index create friends:rev {to, data}", Default::default()) + db.run_default("::index create friends:rev {to, data}") .unwrap(); - db.run_script( - r"?[fr, to, data] <- [[1,2,5],[6,5,7]] :put friends {fr, to => data}", - Default::default(), - ) - .unwrap(); - db.run_script( - r"?[fr, to] <- [[4,5]] :rm friends {fr, to}", - Default::default(), - ) - .unwrap(); + db.run_default(r"?[fr, to, data] <- [[1,2,5],[6,5,7]] :put friends {fr, to => data}") + .unwrap(); + db.run_default(r"?[fr, to] <- [[4,5]] :rm friends {fr, to}") + .unwrap(); let rels_data = db .export_relations(["friends", "friends:rev"].into_iter()) @@ -575,37 +482,26 @@ fn test_index() { json!([[2, 5, 1], [5, 7, 6]]) ); - let rels = db.run_script("::relations", Default::default()).unwrap(); + let rels = db.run_default("::relations").unwrap(); assert_eq!(rels.rows[1][0], DataValue::from("friends:rev")); assert_eq!(rels.rows[1][1], DataValue::from(3)); assert_eq!(rels.rows[1][2], DataValue::from("index")); - let cols = db - .run_script("::columns friends:rev", Default::default()) - .unwrap(); + let cols = db.run_default("::columns friends:rev").unwrap(); assert_eq!(cols.rows.len(), 3); let res = db - .run_script( - "?[fr, data] := *friends:rev{to: 2, fr, data}", - Default::default(), - ) + .run_default("?[fr, data] := *friends:rev{to: 2, fr, data}") .unwrap(); assert_eq!(res.into_json()["rows"], json!([[1, 5]])); let res = db - .run_script( - "?[fr, data] := *friends{to: 2, fr, data}", - Default::default(), - ) + .run_default("?[fr, data] := *friends{to: 2, fr, data}") .unwrap(); assert_eq!(res.into_json()["rows"], json!([[1, 5]])); let expl = db - .run_script( - "::explain { ?[fr, data] := *friends{to: 2, fr, data} }", - Default::default(), - ) + .run_default("::explain { ?[fr, data] := *friends{to: 2, fr, data} }") .unwrap(); let joins = expl.into_json()["rows"] .as_array() @@ -614,27 +510,24 @@ fn test_index() { .map(|row| row.as_array().unwrap()[5].clone()) .collect_vec(); assert!(joins.contains(&json!(":friends:rev"))); - db.run_script("::index drop friends:rev", Default::default()) - .unwrap(); + db.run_default("::index drop friends:rev").unwrap(); } #[test] fn test_json_objects() { - let db = new_cozo_mem().unwrap(); - db.run_script("?[a] := a = {'a': 1}", Default::default()) - .unwrap(); - db.run_script( + let db = DbInstance::default(); + db.run_default("?[a] := a = {'a': 1}").unwrap(); + db.run_default( r"?[a] := a = { 'a': 1 }", - Default::default(), ) .unwrap(); } #[test] fn test_custom_rules() { - let db = new_cozo_mem().unwrap(); + let db = DbInstance::default(); struct Custom; impl FixedRule for Custom { @@ -672,12 +565,11 @@ fn test_custom_rules() { db.register_fixed_rule("SumCols".to_string(), Custom) .unwrap(); let res = db - .run_script( + .run_default( r#" rel[] <- [[1,2,3,4],[5,6,7,8]] ?[x] <~ SumCols(rel[], mult: 100) "#, - Default::default(), ) .unwrap(); assert_eq!(res.into_json()["rows"], json!([[1000], [2600]])); @@ -685,32 +577,19 @@ fn test_custom_rules() { #[test] fn test_index_short() { - let db = new_cozo_mem().unwrap(); - db.run_script( - ":create friends {fr: Int, to: Int => data: Any}", - Default::default(), - ) - .unwrap(); - - db.run_script( - r"?[fr, to, data] <- [[1,2,3],[4,5,6]] :put friends {fr, to => data}", - Default::default(), - ) - .unwrap(); + let db = DbInstance::default(); + db.run_default(":create friends {fr: Int, to: Int => data: Any}") + .unwrap(); - db.run_script("::index create friends:rev {to}", Default::default()) + db.run_default(r"?[fr, to, data] <- [[1,2,3],[4,5,6]] :put friends {fr, to => data}") .unwrap(); - db.run_script( - r"?[fr, to, data] <- [[1,2,5],[6,5,7]] :put friends {fr, to => data}", - Default::default(), - ) - .unwrap(); - db.run_script( - r"?[fr, to] <- [[4,5]] :rm friends {fr, to}", - Default::default(), - ) - .unwrap(); + db.run_default("::index create friends:rev {to}").unwrap(); + + db.run_default(r"?[fr, to, data] <- [[1,2,5],[6,5,7]] :put friends {fr, to => data}") + .unwrap(); + db.run_default(r"?[fr, to] <- [[4,5]] :rm friends {fr, to}") + .unwrap(); let rels_data = db .export_relations(["friends", "friends:rev"].into_iter()) @@ -724,21 +603,16 @@ fn test_index_short() { json!([[2, 1], [5, 6]]) ); - let rels = db.run_script("::relations", Default::default()).unwrap(); + let rels = db.run_default("::relations").unwrap(); assert_eq!(rels.rows[1][0], DataValue::from("friends:rev")); assert_eq!(rels.rows[1][1], DataValue::from(2)); assert_eq!(rels.rows[1][2], DataValue::from("index")); - let cols = db - .run_script("::columns friends:rev", Default::default()) - .unwrap(); + let cols = db.run_default("::columns friends:rev").unwrap(); assert_eq!(cols.rows.len(), 2); let expl = db - .run_script( - "::explain { ?[fr, data] := *friends{to: 2, fr, data} }", - Default::default(), - ) + .run_default("::explain { ?[fr, data] := *friends{to: 2, fr, data} }") .unwrap() .into_json(); @@ -755,81 +629,63 @@ fn test_index_short() { assert!(joins.contains(&json!(":friends:rev"))); let res = db - .run_script( - "?[fr, data] := *friends{to: 2, fr, data}", - Default::default(), - ) + .run_default("?[fr, data] := *friends{to: 2, fr, data}") .unwrap(); assert_eq!(res.into_json()["rows"], json!([[1, 5]])); } #[test] fn test_multi_tx() { - let db = DbInstance::new("mem", "", "").unwrap(); + let db = DbInstance::default(); let tx = db.multi_transaction(true); tx.run_script(":create a {a}", Default::default()).unwrap(); - tx.run_script("?[a] <- [[1]] :put a {a}", Default::default()) - .unwrap(); + tx.run_script("?[a] <- [[1]] :put a {a}", Default::default()).unwrap(); assert!(tx.run_script(":create a {a}", Default::default()).is_err()); - tx.run_script("?[a] <- [[2]] :put a {a}", Default::default()) - .unwrap(); - tx.run_script("?[a] <- [[3]] :put a {a}", Default::default()) - .unwrap(); + tx.run_script("?[a] <- [[2]] :put a {a}", Default::default()).unwrap(); + tx.run_script("?[a] <- [[3]] :put a {a}", Default::default()).unwrap(); tx.commit().unwrap(); assert_eq!( - db.run_script("?[a] := *a[a]", Default::default()) - .unwrap() - .into_json()["rows"], + db.run_default("?[a] := *a[a]").unwrap().into_json()["rows"], json!([[1], [2], [3]]) ); - let db = DbInstance::new("mem", "", "").unwrap(); + let db = DbInstance::default(); let tx = db.multi_transaction(true); tx.run_script(":create a {a}", Default::default()).unwrap(); - tx.run_script("?[a] <- [[1]] :put a {a}", Default::default()) - .unwrap(); + tx.run_script("?[a] <- [[1]] :put a {a}", Default::default()).unwrap(); assert!(tx.run_script(":create a {a}", Default::default()).is_err()); - tx.run_script("?[a] <- [[2]] :put a {a}", Default::default()) - .unwrap(); - tx.run_script("?[a] <- [[3]] :put a {a}", Default::default()) - .unwrap(); + tx.run_script("?[a] <- [[2]] :put a {a}", Default::default()).unwrap(); + tx.run_script("?[a] <- [[3]] :put a {a}", Default::default()).unwrap(); tx.abort().unwrap(); - assert!(db.run_script("?[a] := *a[a]", Default::default()).is_err()); + assert!(db.run_default("?[a] := *a[a]").is_err()); } #[test] fn test_vec_types() { let db = DbInstance::new("mem", "", "").unwrap(); - db.run_script(":create a {k: String => v: }", Default::default()) + db.run_default(":create a {k: String => v: }") .unwrap(); - db.run_script( - "?[k, v] <- [['k', [1,2,3,4,5,6,7,8]]] :put a {k => v}", - Default::default(), - ) - .unwrap(); - let res = db - .run_script("?[k, v] := *a{k, v}", Default::default()) + db.run_default("?[k, v] <- [['k', [1,2,3,4,5,6,7,8]]] :put a {k => v}") .unwrap(); + let res = db.run_default("?[k, v] := *a{k, v}").unwrap(); assert_eq!( json!([1., 2., 3., 4., 5., 6., 7., 8.]), res.into_json()["rows"][0][1] ); let res = db - .run_script("?[v] <- [[vec([1,2,3,4,5,6,7,8])]]", Default::default()) + .run_default("?[v] <- [[vec([1,2,3,4,5,6,7,8])]]") .unwrap(); assert_eq!( json!([1., 2., 3., 4., 5., 6., 7., 8.]), res.into_json()["rows"][0][0] ); - let res = db - .run_script("?[v] <- [[rand_vec(5)]]", Default::default()) - .unwrap(); + let res = db.run_default("?[v] <- [[rand_vec(5)]]").unwrap(); assert_eq!(5, res.into_json()["rows"][0][0].as_array().unwrap().len()); let res = db - .run_script(r#" + .run_default(r#" val[v] <- [[vec([1,2,3,4,5,6,7,8])]] ?[x,y,z] := val[v], x=l2_dist(v, v), y=cos_dist(v, v), nv = l2_normalize(v), z=ip_dist(nv, nv) - "#, Default::default()) + "#) .unwrap(); println!("{}", res.into_json()); } @@ -837,7 +693,7 @@ fn test_vec_types() { #[test] fn test_vec_index() { let db = DbInstance::new("mem", "", "").unwrap(); - db.run_script( + db.run_default( r" ?[k, v] <- [['a', [1,2]], ['b', [2,3]], @@ -849,10 +705,9 @@ fn test_vec_index() { :create a {k: String => v: } ", - Default::default(), ) .unwrap(); - db.run_script( + db.run_default( r" ::hnsw create a:vec { dim: 2, @@ -865,10 +720,9 @@ fn test_vec_index() { #extend_candidates: true, #keep_pruned_connections: true, }", - Default::default(), ) .unwrap(); - db.run_script( + db.run_default( r" ?[k, v] <- [ ['a2', [1,25]], @@ -880,7 +734,6 @@ fn test_vec_index() { ] :put a {k => v} ", - Default::default(), ) .unwrap(); @@ -893,13 +746,12 @@ fn test_vec_index() { } let res = db - .run_script( + .run_default( r" #::explain { ?[dist, k, v] := ~a:vec{k, v | query: q, k: 2, ef: 20, bind_distance: dist}, q = vec([200, 34]) #} ", - Default::default(), ) .unwrap(); println!("results"); @@ -911,38 +763,34 @@ fn test_vec_index() { #[test] fn test_fts_indexing() { let db = DbInstance::new("mem", "", "").unwrap(); - db.run_script(r":create a {k: String => v: String}", Default::default()) + db.run_default(r":create a {k: String => v: String}") .unwrap(); - db.run_script( + db.run_default( r"?[k, v] <- [['a', 'hello world!'], ['b', 'the world is round']] :put a {k => v}", - Default::default(), ) .unwrap(); - db.run_script( + db.run_default( r"::fts create a:fts { extractor: v, tokenizer: Simple, filters: [Lowercase, Stemmer('English'), Stopwords('en')] }", - Default::default(), ) .unwrap(); - db.run_script( + db.run_default( r"?[k, v] <- [ ['b', 'the world is square!'], ['c', 'see you at the end of the world!'], ['d', 'the world is the world and makes the world go around'] ] :put a {k => v}", - Default::default(), ) .unwrap(); let res = db - .run_script( + .run_default( r" ?[word, src_k, offset_from, offset_to, position, total_length] := *a:fts{word, src_k, offset_from, offset_to, position, total_length} ", - Default::default(), ) .unwrap(); for row in res.into_json()["rows"].as_array().unwrap() { @@ -950,10 +798,7 @@ fn test_fts_indexing() { } println!("query"); let res = db - .run_script( - r"?[k, v, s] := ~a:fts{k, v | query: 'world', k: 2, bind_score: s}", - Default::default(), - ) + .run_default(r"?[k, v, s] := ~a:fts{k, v | query: 'world', k: 2, bind_score: s}") .unwrap(); for row in res.into_json()["rows"].as_array().unwrap() { println!("{}", row); @@ -965,23 +810,18 @@ fn test_lsh_indexing2() { for i in 1..10 { let f = i as f64 / 10.; let db = DbInstance::new("mem", "", "").unwrap(); - db.run_script(r":create a {k: String => v: String}", Default::default()) + db.run_default(r":create a {k: String => v: String}") .unwrap(); db.run_script( r"::lsh create a:lsh {extractor: v, tokenizer: NGram, n_gram: 3, target_threshold: $t }", - BTreeMap::from([("t".into(), f.into())]) + BTreeMap::from([("t".into(), f.into())]), + ScriptMutability::Mutable ) .unwrap(); - db.run_script( - "?[k, v] <- [['a', 'ewiygfspeoighjsfcfxzdfncalsdf']] :put a {k => v}", - Default::default(), - ) - .unwrap(); + db.run_default("?[k, v] <- [['a', 'ewiygfspeoighjsfcfxzdfncalsdf']] :put a {k => v}") + .unwrap(); let res = db - .run_script( - "?[k] := ~a:lsh{k | query: 'ewiygfspeoighjsfcfxzdfncalsdf', k: 1}", - Default::default(), - ) + .run_default("?[k] := ~a:lsh{k | query: 'ewiygfspeoighjsfcfxzdfncalsdf', k: 1}") .unwrap(); assert!(res.rows.len() > 0); } @@ -992,7 +832,7 @@ fn test_lsh_indexing3() { for i in 1..10 { let f = i as f64 / 10.; let db = DbInstance::new("mem", "", "").unwrap(); - db.run_script(r":create text {id: String, => text: String, url: String? default null, dt: Float default now(), dup_for: String? default null }", Default::default()) + db.run_default(r":create text {id: String, => text: String, url: String? default null, dt: Float default now(), dup_for: String? default null }") .unwrap(); db.run_script( r"::lsh create text:lsh { @@ -1004,18 +844,17 @@ fn test_lsh_indexing3() { n_gram: 7, }", BTreeMap::from([("t".into(), f.into())]), + ScriptMutability::Mutable, ) .unwrap(); - db.run_script( + db.run_default( "?[id, text] <- [['a', 'This function first generates 32 random bytes using the os.urandom function. It then base64 encodes these bytes using base64.urlsafe_b64encode, removes the padding, and decodes the result to a string.']] :put text {id, text}", - Default::default(), ) .unwrap(); let res = db - .run_script( + .run_default( r#"?[id, dup_for] := ~text:lsh{id: id, dup_for: dup_for, | query: "This function first generates 32 random bytes using the os.urandom function. It then base64 encodes these bytes using base64.urlsafe_b64encode, removes the padding, and decodes the result to a string.", }"#, - Default::default(), ) .unwrap(); assert!(res.rows.len() > 0); @@ -1026,112 +865,91 @@ fn test_lsh_indexing3() { #[test] fn test_lsh_indexing() { let db = DbInstance::new("mem", "", "").unwrap(); - db.run_script(r":create a {k: String => v: String}", Default::default()) + db.run_default(r":create a {k: String => v: String}") .unwrap(); - db.run_script( + db.run_default( r"?[k, v] <- [['a', 'hello world!'], ['b', 'the world is round']] :put a {k => v}", - Default::default(), ) .unwrap(); - db.run_script( + db.run_default( r"::lsh create a:lsh {extractor: v, tokenizer: Simple, n_gram: 3, target_threshold: 0.3 }", - Default::default(), ) .unwrap(); - db.run_script( + db.run_default( r"?[k, v] <- [ ['b', 'the world is square!'], ['c', 'see you at the end of the world!'], ['d', 'the world is the world and makes the world go around'], ['e', 'the world is the world and makes the world not go around'] ] :put a {k => v}", - Default::default(), ) .unwrap(); - let res = db - .run_script("::columns a:lsh", Default::default()) - .unwrap(); + let res = db.run_default("::columns a:lsh").unwrap(); for row in res.into_json()["rows"].as_array().unwrap() { println!("{}", row); } let _res = db - .run_script( + .run_default( r" ?[src_k, hash] := *a:lsh{src_k, hash} ", - Default::default(), ) .unwrap(); // for row in _res.into_json()["rows"].as_array().unwrap() { // println!("{}", row); // } let _res = db - .run_script( + .run_default( r" ?[k, minhash] := *a:lsh:inv{k, minhash} ", - Default::default(), ) .unwrap(); // for row in res.into_json()["rows"].as_array().unwrap() { // println!("{}", row); // } let res = db - .run_script( + .run_default( r" ?[k, v] := ~a:lsh{k, v | query: 'see him at the end of the world', } ", - Default::default(), ) .unwrap(); for row in res.into_json()["rows"].as_array().unwrap() { println!("{}", row); } - let res = db.run_script("::indices a", Default::default()).unwrap(); + let res = db.run_default("::indices a").unwrap(); for row in res.into_json()["rows"].as_array().unwrap() { println!("{}", row); } - db.run_script(r"::lsh drop a:lsh", Default::default()) - .unwrap(); + db.run_default(r"::lsh drop a:lsh").unwrap(); } #[test] fn test_insertions() { let db = DbInstance::new("mem", "", "").unwrap(); - db.run_script( - r":create a {k => v: default rand_vec(1536)}", - Default::default(), - ) - .unwrap(); - db.run_script(r"?[k] <- [[1]] :put a {k}", Default::default()) - .unwrap(); - db.run_script(r"?[k, v] := *a{k, v}", Default::default()) + db.run_default(r":create a {k => v: default rand_vec(1536)}") .unwrap(); - db.run_script( + db.run_default(r"?[k] <- [[1]] :put a {k}").unwrap(); + db.run_default(r"?[k, v] := *a{k, v}").unwrap(); + db.run_default( r"::hnsw create a:i { fields: [v], dim: 1536, ef: 16, filter: k % 3 == 0, m: 32 }", - Default::default(), ) .unwrap(); - db.run_script(r"?[count(fr_k)] := *a:i{fr_k}", Default::default()) + db.run_default(r"?[count(fr_k)] := *a:i{fr_k}").unwrap(); + db.run_default(r"?[k] <- [[1]] :put a {k}").unwrap(); + db.run_default(r"?[k] := k in int_range(300) :put a {k}") .unwrap(); - db.run_script(r"?[k] <- [[1]] :put a {k}", Default::default()) - .unwrap(); - db.run_script( - r"?[k] := k in int_range(300) :put a {k}", - Default::default(), - ) - .unwrap(); let res = db - .run_script( + .run_default( 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}", - Default::default(), ) .unwrap(); println!("results"); @@ -1185,7 +1003,7 @@ fn tokenizers() { #[test] fn multi_index_vec() { let db = DbInstance::new("mem", "", "").unwrap(); - db.run_script( + db.run_default( r#" :create product { id @@ -1197,10 +1015,9 @@ fn multi_index_vec() { description_vec: } "#, - Default::default(), ) .unwrap(); - db.run_script( + db.run_default( r#" ::hnsw create product:semantic{ fields: [name_vec, description_vec], @@ -1209,20 +1026,16 @@ fn multi_index_vec() { m: 32, } "#, - Default::default(), ) .unwrap(); - db.run_script( + db.run_default( r#" ?[id, name, description, price, name_vec, description_vec] <- [[1, "name", "description", 100, [1], [1]]] :put product {id => name, description, price, name_vec, description_vec} "#, - Default::default(), ).unwrap(); - let res = db - .run_script("::indices product", Default::default()) - .unwrap(); + let res = db.run_default("::indices product").unwrap(); for row in res.into_json()["rows"].as_array().unwrap() { println!("{}", row); } @@ -1231,7 +1044,7 @@ fn multi_index_vec() { #[test] fn ensure_not() { let db = DbInstance::new("mem", "", "").unwrap(); - db.run_script( + db.run_default( r" %ignore_error { :create id_alloc{id: Int => next_id: Int, last_id: Int}} %ignore_error { @@ -1239,7 +1052,6 @@ fn ensure_not() { :ensure_not id_alloc{id => next_id, last_id} } ", - Default::default(), ) .unwrap(); } @@ -1247,50 +1059,32 @@ fn ensure_not() { #[test] fn insertion() { let db = DbInstance::new("mem", "", "").unwrap(); - db.run_script(r":create a {x => y}", Default::default()) - .unwrap(); + db.run_default(r":create a {x => y}").unwrap(); assert!(db - .run_script( - r"?[x, y] <- [[1, 2]] :insert a {x => y}", - Default::default(), - ) + .run_default(r"?[x, y] <- [[1, 2]] :insert a {x => y}",) .is_ok()); assert!(db - .run_script( - r"?[x, y] <- [[1, 3]] :insert a {x => y}", - Default::default(), - ) + .run_default(r"?[x, y] <- [[1, 3]] :insert a {x => y}",) .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()); + db.run_default(r":create a {x => y}").unwrap(); + assert!(db.run_default(r"?[x] <- [[1]] :delete a {x}").is_err()); assert!(db - .run_script( - r"?[x, y] <- [[1, 2]] :insert a {x => y}", - Default::default(), - ) + .run_default(r"?[x, y] <- [[1, 2]] :insert a {x => y}",) .is_ok()); - db.run_script(r"?[x] <- [[1]] :delete a {x}", Default::default()) - .unwrap(); + db.run_default(r"?[x] <- [[1]] :delete a {x}").unwrap(); } #[test] fn returning() { let db = DbInstance::new("mem", "", "").unwrap(); - db.run_script(":create a {x => y}", Default::default()) - .unwrap(); + db.run_default(":create a {x => y}").unwrap(); let res = db - .run_script( - r"?[x, y] <- [[1, 2]] :insert a {x => y} ", - Default::default(), - ) + .run_default(r"?[x, y] <- [[1, 2]] :insert a {x => y} ") .unwrap(); assert_eq!(res.into_json()["rows"], json!([["OK"]])); // for row in res.into_json()["rows"].as_array().unwrap() { @@ -1298,10 +1092,7 @@ fn returning() { // } let res = db - .run_script( - r"?[x, y] <- [[1, 3], [2, 4]] :returning :put a {x => y} ", - Default::default(), - ) + .run_default(r"?[x, y] <- [[1, 3], [2, 4]] :returning :put a {x => y} ") .unwrap(); assert_eq!( res.into_json()["rows"], @@ -1313,10 +1104,7 @@ fn returning() { // } let res = db - .run_script( - r"?[x] <- [[1], [4]] :returning :rm a {x} ", - Default::default(), - ) + .run_default(r"?[x] <- [[1], [4]] :returning :rm a {x} ") .unwrap(); // println!("{:?}", res.headers); // for row in res.into_json()["rows"].as_array().unwrap() { @@ -1330,16 +1118,10 @@ fn returning() { ["deleted", 1, 3] ]) ); - db.run_script( - r":create todo{id:Uuid default rand_uuid_v1() => label: String, done: Bool}", - Default::default(), - ) - .unwrap(); + db.run_default(r":create todo{id:Uuid default rand_uuid_v1() => label: String, done: Bool}") + .unwrap(); let res = db - .run_script( - r"?[label,done] <- [['milk',false]] :put todo{label,done} :returning", - Default::default(), - ) + .run_default(r"?[label,done] <- [['milk',false]] :put todo{label,done} :returning") .unwrap(); assert_eq!(res.rows[0].len(), 4); for title in res.headers.iter() { @@ -1354,39 +1136,29 @@ fn returning() { #[test] fn parser_corner_case() { let db = DbInstance::new("mem", "", "").unwrap(); - db.run_script(r#"?[x] := x = 1 or x = 2"#, Default::default()) + db.run_default(r#"?[x] := x = 1 or x = 2"#).unwrap(); + db.run_default(r#"?[C] := C = 1 orx[C] := C = 1"#).unwrap(); + db.run_default(r#"?[C] := C = true, C inx[C] := C = 1"#) .unwrap(); - db.run_script(r#"?[C] := C = 1 orx[C] := C = 1"#, Default::default()) - .unwrap(); - db.run_script( - r#"?[C] := C = true, C inx[C] := C = 1"#, - Default::default(), - ) - .unwrap(); - db.run_script(r#"?[k] := k in int_range(300)"#, Default::default()) + db.run_default(r#"?[k] := k in int_range(300)"#).unwrap(); + db.run_default(r#"ywcc[a] <- [[1]] noto[A] := ywcc[A] ?[A] := noto[A]"#) .unwrap(); - db.run_script( - r#"ywcc[a] <- [[1]] noto[A] := ywcc[A] ?[A] := noto[A]"#, - Default::default(), - ) - .unwrap(); } #[test] fn as_store_in_imperative_script() { let db = DbInstance::new("mem", "", "").unwrap(); let res = db - .run_script( + .run_default( r#" { ?[x, y, z] <- [[1, 2, 3], [4, 5, 6]] } as _store { ?[x, y, z] := *_store{x, y, z} } "#, - Default::default(), ) .unwrap(); assert_eq!(res.into_json()["rows"], json!([[1, 2, 3], [4, 5, 6]])); let res = db - .run_script( + .run_default( r#" { ?[y] <- [[1], [2], [3]] @@ -1397,7 +1169,6 @@ fn as_store_in_imperative_script() { ?[x] := *_last{_kind: 'inserted', x} } "#, - Default::default(), ) .unwrap(); assert_eq!(3, res.rows.len()); @@ -1405,18 +1176,17 @@ fn as_store_in_imperative_script() { println!("{}", row); } assert!(db - .run_script( + .run_default( r#" { ?[x, x] := x = 1 } as _last - "#, - Default::default() + "# ) .is_err()); let res = db - .run_script( + .run_default( r#" { x[y] <- [[1], [2], [3]] @@ -1426,7 +1196,6 @@ fn as_store_in_imperative_script() { ?[sum_y] := *_last{sum_y} } "#, - Default::default(), ) .unwrap(); assert_eq!(1, res.rows.len()); diff --git a/cozo-core/tests/air_routes.rs b/cozo-core/tests/air_routes.rs index 7a3d0bdd..5dc7e8c7 100644 --- a/cozo-core/tests/air_routes.rs +++ b/cozo-core/tests/air_routes.rs @@ -31,7 +31,7 @@ lazy_static! { dbg!(creation.elapsed()); let init = Instant::now(); - db.run_script(r##" + db.run_default(r##" res[idx, label, typ, code, icao, desc, region, runways, longest, elev, country, city, lat, lon] <~ CsvReader(types: ['Int', 'Any', 'Any', 'Any', 'Any', 'Any', 'Any', 'Int?', 'Float?', 'Float?', 'Any', 'Any', 'Float?', 'Float?'], url: 'file://./tests/air-routes-latest-nodes.csv', @@ -55,9 +55,9 @@ lazy_static! { lat: Float, lon: Float } - "##, Default::default()).unwrap(); + "##).unwrap(); - db.run_script( + db.run_default( r##" res[idx, label, typ, code, icao, desc] <~ CsvReader(types: ['Int', 'Any', 'Any', 'Any', 'Any', 'Any'], @@ -73,11 +73,10 @@ lazy_static! { desc: String } "##, - Default::default(), ) .unwrap(); - db.run_script( + db.run_default( r##" res[idx, label, typ, code, icao, desc] <~ CsvReader(types: ['Int', 'Any', 'Any', 'Any', 'Any', 'Any'], @@ -93,11 +92,10 @@ lazy_static! { desc: String } "##, - Default::default(), ) .unwrap(); - db.run_script( + db.run_default( r##" res[idx, label, typ, code] <~ CsvReader(types: ['Int', 'Any', 'Any', 'Any'], @@ -108,11 +106,10 @@ lazy_static! { :replace idx2code { idx: Int => code: String } "##, - Default::default(), ) .unwrap(); - db.run_script( + db.run_default( r##" res[] <~ CsvReader(types: ['Int', 'Int', 'Int', 'String', 'Float?'], @@ -126,11 +123,10 @@ lazy_static! { :replace route { fr: String, to: String => dist: Float } "##, - Default::default(), ) .unwrap(); - db.run_script( + db.run_default( r##" res[] <~ CsvReader(types: ['Int', 'Int', 'Int', 'String'], @@ -145,12 +141,10 @@ lazy_static! { :replace contain { entity: String, contained: String } "##, - Default::default(), ) .unwrap(); - db.run_script("::remove idx2code", Default::default()) - .unwrap(); + db.run_default("::remove idx2code").unwrap(); dbg!(init.elapsed()); db @@ -162,12 +156,11 @@ fn dfs() { initialize(&TEST_DB); let dfs = Instant::now(); let rows = TEST_DB - .run_script( + .run_default( r#" starting[] <- [['PEK']] ?[] <~ DFS(*route[], *airport[code], starting[], condition: (code == 'LHR')) "#, - Default::default(), ) .unwrap() .rows; @@ -184,11 +177,10 @@ fn dfs() { #[test] fn empty() { initialize(&TEST_DB); - let res = TEST_DB.run_script( + let res = TEST_DB.run_default( r#" ?[id, name] <- [[]] "#, - Default::default(), ); assert!(res.is_err()); } @@ -197,7 +189,7 @@ fn empty() { fn parallel_counts() { initialize(&TEST_DB); let res = TEST_DB - .run_script( + .run_default( r#" a[count(fr)] := *route{fr} b[count(fr)] := *route{fr} @@ -206,7 +198,6 @@ fn parallel_counts() { e[count(fr)] := *route{fr} ?[x] := a[a], b[b], c[c], d[d], e[e], x = a + b + c + d + e "#, - Default::default(), ) .unwrap() .rows @@ -222,12 +213,11 @@ fn bfs() { initialize(&TEST_DB); let bfs = Instant::now(); let rows = TEST_DB - .run_script( + .run_default( r#" starting[] <- [['PEK']] ?[] <~ BFS(*route[], *airport[code], starting[], condition: (code == 'LHR')) "#, - Default::default(), ) .unwrap() .rows; @@ -247,12 +237,11 @@ fn scc() { initialize(&TEST_DB); let scc = Instant::now(); let _ = TEST_DB - .run_script( + .run_default( r#" res[] <~ StronglyConnectedComponents(*route[], *airport[code]); ?[grp, code] := res[code, grp], grp != 0; "#, - Default::default(), ) .unwrap() .rows; @@ -264,12 +253,11 @@ fn cc() { initialize(&TEST_DB); let cc = Instant::now(); let _ = TEST_DB - .run_script( + .run_default( r#" res[] <~ ConnectedComponents(*route[], *airport[code]); ?[grp, code] := res[code, grp], grp != 0; "#, - Default::default(), ) .unwrap() .rows; @@ -280,12 +268,12 @@ fn cc() { fn astar() { initialize(&TEST_DB); let astar = Instant::now(); - let _ = TEST_DB.run_script(r#" + let _ = TEST_DB.run_default(r#" code_lat_lon[code, lat, lon] := *airport{code, lat, lon} starting[code, lat, lon] := code = 'HFE', *airport{code, lat, lon}; goal[code, lat, lon] := code = 'LHR', *airport{code, lat, lon}; ?[] <~ ShortestPathAStar(*route[], code_lat_lon[node, lat1, lon1], starting[], goal[goal, lat2, lon2], heuristic: haversine_deg_input(lat1, lon1, lat2, lon2) * 3963); - "#, Default::default()).unwrap().rows; + "#).unwrap().rows; dbg!(astar.elapsed()); } @@ -294,14 +282,13 @@ fn deg_centrality() { initialize(&TEST_DB); let deg_centrality = Instant::now(); TEST_DB - .run_script( + .run_default( r#" deg_centrality[] <~ DegreeCentrality(*route[a, b]); ?[total, out, in] := deg_centrality[node, total, out, in]; :order -total; :limit 10; "#, - Default::default(), ) .unwrap() .rows; @@ -314,14 +301,13 @@ fn dijkstra() { let dijkstra = Instant::now(); TEST_DB - .run_script( + .run_default( r#" starting[] <- [['JFK']]; ending[] <- [['KUL']]; res[] <~ ShortestPathDijkstra(*route[], starting[], ending[]); ?[path] := res[src, dst, cost, path]; "#, - Default::default(), ) .unwrap() .rows; @@ -335,13 +321,12 @@ fn yen() { let yen = Instant::now(); TEST_DB - .run_script( + .run_default( r#" starting[] <- [['PEK']]; ending[] <- [['SIN']]; ?[] <~ KShortestPathYen(*route[], starting[], ending[], k: 5); "#, - Default::default(), ) .unwrap() .rows; @@ -354,11 +339,10 @@ fn starts_with() { initialize(&TEST_DB); let starts_with = Instant::now(); let rows = TEST_DB - .run_script( + .run_default( r#" ?[code] := *airport{code}, starts_with(code, 'US'); "#, - Default::default(), ) .unwrap() .into_json(); @@ -387,12 +371,11 @@ fn range_check() { let range_check = Instant::now(); let rows = TEST_DB - .run_script( + .run_default( r#" r[code, dist] := *airport{code}, *route{fr: code, dist}; ?[dist] := r['PEK', dist], dist > 7000, dist <= 7722; "#, - Default::default(), ) .unwrap() .into_json(); @@ -410,11 +393,10 @@ fn no_airports() { let no_airports = Instant::now(); let rows = TEST_DB - .run_script( + .run_default( r#" ?[desc] := *country{code, desc}, not *airport{country: code}; "#, - Default::default(), ) .unwrap() .into_json(); @@ -438,11 +420,10 @@ fn no_routes_airport() { let no_routes_airports = Instant::now(); let rows = TEST_DB - .run_script( + .run_default( r#" ?[code] := *airport{code}, not *route{fr: code}, not *route{to: code} "#, - Default::default(), ) .unwrap() .into_json(); @@ -467,11 +448,10 @@ fn runway_distribution() { let no_routes_airports = Instant::now(); let rows = TEST_DB - .run_script( + .run_default( r#" ?[runways, count(code)] := *airport{code, runways} "#, - Default::default(), ) .unwrap() .into_json(); @@ -497,13 +477,12 @@ fn most_out_routes() { let most_out_routes = Instant::now(); let rows = TEST_DB - .run_script( + .run_default( r#" route_count[fr, count(fr)] := *route{fr}; ?[code, n] := route_count[code, n], n > 180; :sort -n; "#, - Default::default(), ) .unwrap() .into_json(); @@ -530,13 +509,12 @@ fn most_out_routes_again() { let most_out_routes_again = Instant::now(); let rows = TEST_DB - .run_script( + .run_default( r#" route_count[count(fr), fr] := *route{fr}; ?[code, n] := route_count[n, code], n > 180; :sort -n; "#, - Default::default(), ) .unwrap() .into_json(); @@ -563,14 +541,13 @@ fn most_routes() { let most_routes = Instant::now(); let rows = TEST_DB - .run_script( + .run_default( r#" route_count[a, count(a)] := *route{fr: a} route_count[a, count(a)] := *route{to: a} ?[code, n] := route_count[code, n], n > 400 :sort -n; "#, - Default::default(), ) .unwrap() .into_json(); @@ -595,12 +572,11 @@ fn airport_with_one_route() { let airport_with_one_route = Instant::now(); let rows = TEST_DB - .run_script( + .run_default( r#" route_count[fr, count(fr)] := *route{fr} ?[count(a)] := route_count[a, n], n == 1; "#, - Default::default(), ) .unwrap() .into_json(); @@ -615,7 +591,7 @@ fn single_runway_with_most_routes() { let single_runway_with_most_routes = Instant::now(); let rows = TEST_DB - .run_script( + .run_default( r#" single_or_lgw[code] := code = 'LGW' single_or_lgw[code] := *airport{code, runways}, runways == 1 @@ -625,7 +601,6 @@ fn single_runway_with_most_routes() { :order -out_n; :limit 10; "#, - Default::default(), ) .unwrap() .into_json(); @@ -649,7 +624,7 @@ fn most_routes_in_canada() { let most_routes_in_canada = Instant::now(); let rows = TEST_DB - .run_script( + .run_default( r#" ca_airports[code, count(code)] := *airport{code, country: 'CA'}, *route{fr: code} ?[code, city, n_routes] := ca_airports[code, n_routes], *airport{code, city} @@ -657,7 +632,6 @@ fn most_routes_in_canada() { :order -n_routes; :limit 10; "#, - Default::default(), ) .unwrap() .into_json(); @@ -686,11 +660,10 @@ fn uk_count() { let uk_count = Instant::now(); let rows = TEST_DB - .run_script( + .run_default( r#" ?[region, count(region)] := *airport{country: 'UK', region} "#, - Default::default(), ) .unwrap() .into_json(); @@ -708,7 +681,7 @@ fn airports_by_country() { let airports_by_country = Instant::now(); let rows = TEST_DB - .run_script( + .run_default( r#" airports_by_country[country, count(code)] := *airport{code, country} ?[country, count] := airports_by_country[country, count]; @@ -716,7 +689,6 @@ fn airports_by_country() { :order count "#, - Default::default(), ) .unwrap() .into_json(); @@ -762,13 +734,12 @@ fn n_airports_by_continent() { let n_airports_by_continent = Instant::now(); let rows = TEST_DB - .run_script( + .run_default( r#" airports_by_continent[cont, count(code)] := *airport{code}, *contain[cont, code] ?[cont, max(count)] := *continent{code: cont}, airports_by_continent[cont, count] ?[cont, max(count)] := *continent{code: cont}, count = 0 "#, - Default::default(), ) .unwrap() .into_json(); @@ -789,12 +760,11 @@ fn routes_per_airport() { let routes_per_airport = Instant::now(); let rows = TEST_DB - .run_script( + .run_default( r#" given[] <- [['A' ++ 'U' ++ 'S'],['AMS'],['JFK'],['DUB'],['MEX']] ?[code, count(code)] := given[code], *route{fr: code} "#, - Default::default(), ) .unwrap() .into_json(); @@ -815,12 +785,11 @@ fn airports_by_route_number() { let airports_by_route_number = Instant::now(); let rows = TEST_DB - .run_script( + .run_default( r#" route_count[fr, count(fr)] := *route{fr} ?[n, collect(code)] := route_count[code, n], n = 106; "#, - Default::default(), ) .unwrap() .into_json(); @@ -835,13 +804,12 @@ fn out_from_aus() { let out_from_aus = Instant::now(); let rows = TEST_DB - .run_script( + .run_default( r#" out_by_runways[runways, count(code)] := *route{fr: 'AUS', to: code}, *airport{code, runways} two_hops[count(a)] := *route{fr: 'AUS', to: a}, *route{fr: a} ?[max(total), collect(coll)] := two_hops[total], out_by_runways[n, ct], coll = [n, ct]; "#, - Default::default(), ) .unwrap() .into_json(); @@ -860,11 +828,10 @@ fn const_return() { let const_return = Instant::now(); let rows = TEST_DB - .run_script( + .run_default( r#" ?[name, count(code)] := *airport{code, region: 'US-OK'}, name = 'OK'; "#, - Default::default(), ) .unwrap() .into_json(); @@ -879,7 +846,7 @@ fn multi_res() { let multi_res = Instant::now(); let rows = TEST_DB - .run_script( + .run_default( r#" total[count(code)] := *airport{code} high[count(code)] := *airport{code, runways}, runways >= 6 @@ -890,7 +857,6 @@ fn multi_res() { ?[total, high, low, four, france] := total[total], high[high], low[low], four[four], france[france]; "#, - Default::default(), ) .unwrap() .into_json(); @@ -908,12 +874,11 @@ fn multi_unification() { let multi_unification = Instant::now(); let rows = TEST_DB - .run_script( + .run_default( r#" target_airports[collect(code, 5)] := *airport{code} ?[a, count(a)] := target_airports[targets], a in targets, *route{fr: a} "#, - Default::default(), ) .unwrap() .into_json(); @@ -932,7 +897,7 @@ fn num_routes_from_eu_to_us() { let num_routes_from_eu_to_us = Instant::now(); let rows = TEST_DB - .run_script( + .run_default( r#" routes[unique(r)] := *contain['EU', fr], *route{fr, to}, @@ -940,7 +905,6 @@ fn num_routes_from_eu_to_us() { r = [fr, to] ?[n] := routes[rs], n = length(rs); "#, - Default::default(), ) .unwrap() .into_json(); @@ -955,13 +919,12 @@ fn num_airports_in_us_with_routes_from_eu() { let num_airports_in_us_with_routes_from_eu = Instant::now(); let rows = TEST_DB - .run_script( + .run_default( r#" ?[count_unique(to)] := *contain['EU', fr], *route{fr, to}, *airport{code: to, country: 'US'} "#, - Default::default(), ) .unwrap() .into_json(); @@ -976,12 +939,11 @@ fn num_routes_in_us_airports_from_eu() { let num_routes_in_us_airports_from_eu = Instant::now(); let rows = TEST_DB - .run_script( + .run_default( r#" ?[to, count(to)] := *contain['EU', fr], *route{fr, to}, *airport{code: to, country: 'US'} :order count(to); "#, - Default::default(), ) .unwrap() .into_json(); @@ -1008,14 +970,13 @@ fn routes_from_eu_to_us_starting_with_l() { let routes_from_eu_to_us_starting_with_l = Instant::now(); let rows = TEST_DB - .run_script( + .run_default( r#" ?[eu_code, us_code] := *contain['EU', eu_code], starts_with(eu_code, 'L'), *route{fr: eu_code, to: us_code}, *airport{code: us_code, country: 'US'} "#, - Default::default(), ) .unwrap() .into_json(); @@ -1046,13 +1007,12 @@ fn len_of_names_count() { let len_of_names_count = Instant::now(); let rows = TEST_DB - .run_script( + .run_default( r#" ?[sum(n)] := *route{fr: 'AUS', to}, *airport{code: to, city}, n = length(city) "#, - Default::default(), ) .unwrap() .into_json(); @@ -1070,7 +1030,7 @@ fn group_count_by_out() { let group_count_by_out = Instant::now(); let rows = TEST_DB - .run_script( + .run_default( r#" route_count[count(fr), fr] := *route{fr} rc[max(n), a] := route_count[n, a] @@ -1079,7 +1039,6 @@ fn group_count_by_out() { :order n; :limit 10; "#, - Default::default(), ) .unwrap() .into_json(); @@ -1100,13 +1059,12 @@ fn mean_group_count() { let mean_group_count = Instant::now(); let rows = TEST_DB - .run_script( + .run_default( r#" route_count[count(fr), fr] := *route{fr}; rc[max(n), a] := route_count[n, a] or (*airport{code: a}, n = 0); ?[mean(n)] := rc[n, _]; "#, - Default::default(), ) .unwrap() .rows; @@ -1123,11 +1081,10 @@ fn n_routes_from_london_uk() { let n_routes_from_london_uk = Instant::now(); let rows = TEST_DB - .run_script( + .run_default( r#" ?[code, count(code)] := *airport{code, city: 'London', region: 'GB-ENG'}, *route{fr: code} "#, - Default::default(), ) .unwrap() .into_json(); @@ -1148,13 +1105,12 @@ fn reachable_from_london_uk_in_two_hops() { let reachable_from_london_uk_in_two_hops = Instant::now(); let rows = TEST_DB - .run_script( + .run_default( r#" lon_uk_airports[code] := *airport{code, city: 'London', region: 'GB-ENG'} one_hop[to] := lon_uk_airports[fr], *route{fr, to}, not lon_uk_airports[to]; ?[count_unique(a3)] := one_hop[a2], *route{fr: a2, to: a3}, not lon_uk_airports[a3]; "#, - Default::default(), ) .unwrap() .into_json(); @@ -1169,12 +1125,11 @@ fn routes_within_england() { let routes_within_england = Instant::now(); let rows = TEST_DB - .run_script( + .run_default( r#" eng_aps[code] := *airport{code, region: 'GB-ENG'} ?[fr, to] := eng_aps[fr], *route{fr, to}, eng_aps[to], "#, - Default::default(), ) .unwrap() .into_json(); @@ -1204,12 +1159,11 @@ fn routes_within_england_time_no_dup() { let routes_within_england_time_no_dup = Instant::now(); let rows = TEST_DB - .run_script( + .run_default( r#" eng_aps[code] := *airport{code, region: 'GB-ENG'} ?[pair] := eng_aps[fr], *route{fr, to}, eng_aps[to], pair = sorted([fr, to]); "#, - Default::default(), ) .unwrap() .into_json(); @@ -1236,7 +1190,7 @@ fn hard_route_finding() { let hard_route_finding = Instant::now(); let rows = TEST_DB - .run_script( + .run_default( r#" reachable[to, choice(p)] := *route{fr: 'AUS', to}, to != 'YYZ', p = ['AUS', to]; reachable[to, choice(p)] := reachable[b, prev], *route{fr: b, to}, @@ -1245,7 +1199,6 @@ fn hard_route_finding() { :limit 1; "#, - Default::default(), ) .unwrap() .into_json(); @@ -1266,14 +1219,13 @@ fn na_from_india() { let na_from_india = Instant::now(); let rows = TEST_DB - .run_script( + .run_default( r#" ?[ind_a, na_a] := *airport{code: ind_a, country: 'IN'}, *route{fr: ind_a, to: na_a}, *airport{code: na_a, country}, country in ['US', 'CA'] "#, - Default::default(), ) .unwrap() .into_json(); @@ -1297,11 +1249,10 @@ fn eu_cities_reachable_from_fll() { let eu_cities_reachable_from_fll = Instant::now(); let rows = TEST_DB - .run_script( + .run_default( r#" ?[city] := *route{fr: 'FLL', to}, *contain['EU', to], *airport{code: to, city} "#, - Default::default(), ) .unwrap() .into_json(); @@ -1324,11 +1275,10 @@ fn clt_to_eu_or_sa() { let clt_to_eu_or_sa = Instant::now(); let rows = TEST_DB - .run_script( + .run_default( r#" ?[to] := *route{fr: 'CLT', to}, c_name in ['EU', 'SA'], *contain[c_name, to] "#, - Default::default(), ) .unwrap() .into_json(); @@ -1351,12 +1301,11 @@ fn london_to_us() { let london_to_us = Instant::now(); let rows = TEST_DB - .run_script( + .run_default( r#" ?[fr, to] := fr in ['LHR', 'LCY', 'LGW', 'LTN', 'STN'], *route{fr, to}, *airport{code: to, country: 'US'} "#, - Default::default(), ) .unwrap() .into_json(); @@ -1386,12 +1335,11 @@ fn tx_to_ny() { let tx_to_ny = Instant::now(); let rows = TEST_DB - .run_script( + .run_default( r#" ?[fr, to] := *airport{code: fr, region: 'US-TX'}, *route{fr, to}, *airport{code: to, region: 'US-NY'} "#, - Default::default(), ) .unwrap() .into_json(); @@ -1416,11 +1364,10 @@ fn denver_to_mexico() { let denver_to_mexico = Instant::now(); let rows = TEST_DB - .run_script( + .run_default( r#" ?[city] := *route{fr: 'DEN', to}, *airport{code: to, country: 'MX', city} "#, - Default::default(), ) .unwrap() .into_json(); @@ -1444,12 +1391,11 @@ fn three_cities() { let three_cities = Instant::now(); let rows = TEST_DB - .run_script( + .run_default( r#" three[code] := city in ['London', 'Munich', 'Paris'], *airport{code, city} ?[s, d] := three[s], *route{fr: s, to: d}, three[d] "#, - Default::default(), ) .unwrap() .into_json(); @@ -1476,12 +1422,11 @@ fn long_distance_from_lgw() { let long_distance_from_lgw = Instant::now(); let rows = TEST_DB - .run_script( + .run_default( r#" ?[city, dist] := *route{fr: 'LGW', to, dist}, dist > 4000, *airport{code: to, city} "#, - Default::default(), ) .unwrap() .into_json(); @@ -1512,11 +1457,10 @@ fn long_routes_one_dir() { let long_routes_one_dir = Instant::now(); let rows = TEST_DB - .run_script( + .run_default( r#" ?[fr, dist, to] := *route{fr, to, dist}, dist > 8000, fr < to; "#, - Default::default(), ) .unwrap() .into_json(); @@ -1545,13 +1489,12 @@ fn longest_routes() { let longest_routes = Instant::now(); let rows = TEST_DB - .run_script( + .run_default( r#" ?[fr, dist, to] := *route{fr, to, dist}, dist > 4000, fr < to; :sort -dist; :limit 20; "#, - Default::default(), ) .unwrap() .into_json(); @@ -1577,12 +1520,11 @@ fn longest_routes_from_each_airports() { let longest_routes_from_each_airports = Instant::now(); let rows = TEST_DB - .run_script( + .run_default( r#" ?[fr, max(dist), choice(to)] := *route{fr, dist, to} :limit 10; "#, - Default::default(), ) .unwrap() .into_json(); @@ -1606,12 +1548,11 @@ fn total_distance_from_three_cities() { let total_distance_from_three_cities = Instant::now(); let rows = TEST_DB - .run_script( + .run_default( r#" three[code] := city in ['London', 'Munich', 'Paris'], *airport{code, city} ?[sum(dist)] := three[a], *route{fr: a, dist} "#, - Default::default(), ) .unwrap() .into_json(); @@ -1629,12 +1570,11 @@ fn total_distance_within_three_cities() { let total_distance_within_three_cities = Instant::now(); let rows = TEST_DB - .run_script( + .run_default( r#" three[code] := city in ['London', 'Munich', 'Paris'], *airport{code, city} ?[sum(dist)] := three[a], *route{fr: a, dist, to}, three[to] "#, - Default::default(), ) .unwrap() .into_json(); @@ -1652,11 +1592,10 @@ fn specific_distance() { let specific_distance = Instant::now(); let rows = TEST_DB - .run_script( + .run_default( r#" ?[dist] := *route{fr: 'AUS', to: 'MEX', dist} "#, - Default::default(), ) .unwrap() .into_json(); @@ -1674,13 +1613,12 @@ fn n_routes_between() { let n_routes_between = Instant::now(); let rows = TEST_DB - .run_script( + .run_default( r#" us_a[a] := *contain['US', a] ?[count(fr)] := *route{fr, to, dist}, dist >= 100, dist <= 200, us_a[fr], us_a[to] "#, - Default::default(), ) .unwrap() .into_json(); @@ -1698,7 +1636,7 @@ fn one_stop_distance() { let one_stop_distance = Instant::now(); let rows = TEST_DB - .run_script( + .run_default( r#" ?[code, dist] := *route{fr: 'AUS', to: code, dist: dis1}, *route{fr: code, to: 'LHR', dist: dis2}, @@ -1706,7 +1644,6 @@ fn one_stop_distance() { :order dist; :limit 10; "#, - Default::default(), ) .unwrap() .into_json(); @@ -1729,13 +1666,12 @@ fn airport_most_routes() { let airport_most_routes = Instant::now(); let rows = TEST_DB - .run_script( + .run_default( r#" ?[fr, count(fr)] := *route{fr} :order -count(fr); :limit 10; "#, - Default::default(), ) .unwrap() .into_json(); @@ -1758,11 +1694,10 @@ fn north_of_77() { let north_of_77 = Instant::now(); let rows = TEST_DB - .run_script( + .run_default( r#" ?[city, latitude] := *airport{lat, city}, lat > 77, latitude = round(lat) "#, - Default::default(), ) .unwrap() .into_json(); @@ -1780,11 +1715,10 @@ fn greenwich_meridian() { let greenwich_meridian = Instant::now(); let rows = TEST_DB - .run_script( + .run_default( r#" ?[code] := *airport{lon, code}, lon > -0.1, lon < 0.1 "#, - Default::default(), ) .unwrap() .into_json(); @@ -1802,13 +1736,12 @@ fn box_around_heathrow() { let box_around_heathrow = Instant::now(); let rows = TEST_DB - .run_script( + .run_default( r#" h_box[lon, lat] := *airport{code: 'LHR', lon, lat} ?[code] := h_box[lhr_lon, lhr_lat], *airport{code, lon, lat}, abs(lhr_lon - lon) < 1, abs(lhr_lat - lat) < 1 "#, - Default::default(), ) .unwrap() .into_json(); @@ -1827,13 +1760,12 @@ fn dfw_by_region() { let dfw_by_region = Instant::now(); let rows = TEST_DB - .run_script( + .run_default( r#" ?[region, collect(to)] := *route{fr: 'DFW', to}, *airport{code: to, country: 'US', region}, region in ['US-CA', 'US-TX', 'US-FL', 'US-CO', 'US-IL'] "#, - Default::default(), ) .unwrap() .into_json(); @@ -1859,13 +1791,12 @@ fn great_circle_distance() { let great_circle_distance = Instant::now(); let rows = TEST_DB - .run_script( + .run_default( r#" ?[deg_diff] := *airport{code: 'SFO', lat: a_lat, lon: a_lon}, *airport{code: 'NRT', lat: b_lat, lon: b_lon}, deg_diff = round(haversine_deg_input(a_lat, a_lon, b_lat, b_lon)); "#, - Default::default(), ) .unwrap() .into_json(); @@ -1883,7 +1814,7 @@ fn aus_to_edi() { let aus_to_edi = Instant::now(); let rows = TEST_DB - .run_script( + .run_default( r#" us_uk_airports[code] := *airport{code, country: 'UK'} us_uk_airports[code] := *airport{code, country: 'US'} @@ -1894,7 +1825,6 @@ fn aus_to_edi() { path = append(prev, to); ?[path] := routes['EDI', path]; "#, - Default::default(), ) .unwrap() .into_json(); @@ -1912,7 +1842,7 @@ fn reachable_from_lhr() { let reachable_from_lhr = Instant::now(); let rows = TEST_DB - .run_script( + .run_default( r#" routes[to, shortest(path)] := *route{fr: 'LHR', to}, path = ['LHR', to]; @@ -1923,7 +1853,6 @@ fn reachable_from_lhr() { :order -len; :limit 10; "#, - Default::default(), ) .unwrap() .into_json(); @@ -1955,7 +1884,7 @@ fn furthest_from_lhr() { let furthest_from_lhr = Instant::now(); let rows = TEST_DB - .run_script( + .run_default( r#" routes[to, min_cost(cost_pair)] := *route{fr: 'LHR', to, dist}, path = ['LHR', to], @@ -1968,7 +1897,6 @@ fn furthest_from_lhr() { :order -cost; :limit 10; "#, - Default::default(), ) .unwrap() .into_json(); @@ -1993,11 +1921,10 @@ fn furthest_from_lhr() { fn skip_limit() { initialize(&TEST_DB); let rows = TEST_DB - .run_script( + .run_default( r#" ?[a] := a in [9, 9, 8, 9, 8, 7, 7, 6, 5, 9, 4, 4, 3] "#, - Default::default(), ) .unwrap() .into_json(); @@ -2005,11 +1932,10 @@ fn skip_limit() { assert_eq!(rows["rows"], json!([[3], [4], [5], [6], [7], [8], [9]])); let rows = TEST_DB - .run_script( + .run_default( r#" ?[a] := a in [9, 9, 8, 9, 8, 7, 7, 6, 5, 9, 4, 4, 3] "#, - Default::default(), ) .unwrap() .into_json(); @@ -2017,12 +1943,11 @@ fn skip_limit() { assert_eq!(rows["rows"], json!([[3], [4], [5], [6], [7], [8], [9]])); let rows = TEST_DB - .run_script( + .run_default( r#" ?[a] := a in [9, 9, 8, 9, 8, 7, 7, 6, 5, 9, 4, 4, 3] :limit 2 "#, - Default::default(), ) .unwrap() .into_json(); @@ -2030,13 +1955,12 @@ fn skip_limit() { assert_eq!(rows["rows"], json!([[8], [9]])); let rows = TEST_DB - .run_script( + .run_default( r#" ?[a] := a in [9, 9, 8, 9, 8, 7, 7, 6, 5, 9, 4, 4, 3] :limit 2 :offset 1 "#, - Default::default(), ) .unwrap() .into_json(); @@ -2044,13 +1968,12 @@ fn skip_limit() { assert_eq!(rows["rows"], json!([[7], [8]])); let rows = TEST_DB - .run_script( + .run_default( r#" ?[a] := a in [9, 9, 8, 9, 8, 7, 7, 6, 5, 9, 4, 4, 3] :limit 100 :offset 1 "#, - Default::default(), ) .unwrap() .into_json(); diff --git a/cozo-lib-c/cozo_c.h b/cozo-lib-c/cozo_c.h index f7f09a6f..7121f6f4 100644 --- a/cozo-lib-c/cozo_c.h +++ b/cozo-lib-c/cozo_c.h @@ -59,7 +59,10 @@ bool cozo_close_db(int32_t id); * Returns a UTF-8-encoded C-string that **must** be freed with `cozo_free_str`. * The string contains the JSON return value of the query. */ -char *cozo_run_query(int32_t db_id, const char *script_raw, const char *params_raw); +char *cozo_run_query(int32_t db_id, + const char *script_raw, + const char *params_raw, + bool immutable_query); /** * Import data into relations diff --git a/cozo-lib-c/src/lib.rs b/cozo-lib-c/src/lib.rs index 2580e161..fd696a26 100644 --- a/cozo-lib-c/src/lib.rs +++ b/cozo-lib-c/src/lib.rs @@ -107,6 +107,7 @@ pub unsafe extern "C" fn cozo_run_query( db_id: i32, script_raw: *const c_char, params_raw: *const c_char, + immutable_query: bool, ) -> *mut c_char { let script = match CStr::from_ptr(script_raw).to_str() { Ok(p) => p, @@ -141,7 +142,7 @@ pub unsafe extern "C" fn cozo_run_query( } }; - let result = db.run_script_str(script, params_str); + let result = db.run_script_str(script, params_str, immutable_query); CString::new(result).unwrap().into_raw() } diff --git a/cozo-lib-java/src/lib.rs b/cozo-lib-java/src/lib.rs index 293d0b43..b2abdb54 100644 --- a/cozo-lib-java/src/lib.rs +++ b/cozo-lib-java/src/lib.rs @@ -88,7 +88,7 @@ pub extern "system" fn Java_org_cozodb_CozoJavaBridge_runQuery( match get_db(id) { None => env.new_string(DB_NOT_FOUND).unwrap().into_raw(), Some(db) => { - let res = db.run_script_str(&script, ¶ms_str); + let res = db.run_script_str(&script, ¶ms_str, false); env.new_string(res).unwrap().into_raw() } } diff --git a/cozo-lib-nodejs/index.js b/cozo-lib-nodejs/index.js index 48639087..2a583a0c 100644 --- a/cozo-lib-nodejs/index.js +++ b/cozo-lib-nodejs/index.js @@ -29,6 +29,7 @@ class CozoTx { }) }) } + abort() { return native.abort_tx(this.tx_id) } @@ -51,7 +52,7 @@ class CozoDb { return new CozoTx(native.multi_transact(this.db_id, !!write)) } - run(script, params) { + run(script, params, immutable) { return new Promise((resolve, reject) => { params = params || {}; native.query_db(this.db_id, script, params, (err, result) => { @@ -60,7 +61,7 @@ class CozoDb { } else { resolve(result) } - }) + }, !!immutable) }) } diff --git a/cozo-lib-nodejs/src/lib.rs b/cozo-lib-nodejs/src/lib.rs index f1b4ed28..4efcbd88 100644 --- a/cozo-lib-nodejs/src/lib.rs +++ b/cozo-lib-nodejs/src/lib.rs @@ -406,11 +406,20 @@ fn query_db(mut cx: FunctionContext) -> JsResult { js2params(&mut cx, params_js, &mut params)?; let callback = cx.argument::(3)?.root(&mut cx); + let immutable = cx.argument::(4)?.value(&mut cx); let channel = cx.channel(); thread::spawn(move || { - let result = db.run_script(&query, params); + let result = db.run_script( + &query, + params, + if immutable { + ScriptMutability::Immutable + } else { + ScriptMutability::Mutable + }, + ); channel.send(move |mut cx| { let callback = callback.into_inner(&mut cx); let this = cx.undefined(); diff --git a/cozo-lib-python/src/lib.rs b/cozo-lib-python/src/lib.rs index 8e243030..02e1597e 100644 --- a/cozo-lib-python/src/lib.rs +++ b/cozo-lib-python/src/lib.rs @@ -213,10 +213,26 @@ impl CozoDbPy { Err(err) => Err(PyException::new_err(format!("{err:?}"))), } } - pub fn run_script(&self, py: Python<'_>, query: &str, params: &PyDict) -> PyResult { + pub fn run_script( + &self, + py: Python<'_>, + query: &str, + params: &PyDict, + immutable: bool, + ) -> PyResult { if let Some(db) = &self.db { let params = convert_params(params)?; - match py.allow_threads(|| db.run_script(query, params)) { + match py.allow_threads(|| { + db.run_script( + query, + params, + if immutable { + ScriptMutability::Immutable + } else { + ScriptMutability::Mutable + }, + ) + }) { Ok(rows) => Ok(named_rows_to_py(rows, py)), Err(err) => { let reports = format_error_as_json(err, Some(query)).to_string(); diff --git a/cozo-lib-swift/src/lib.rs b/cozo-lib-swift/src/lib.rs index 777dfb55..9e7201fd 100644 --- a/cozo-lib-swift/src/lib.rs +++ b/cozo-lib-swift/src/lib.rs @@ -15,7 +15,7 @@ mod ffi { fn new_cozo_db(engine: &str, path: &str, options: &str) -> Option; - fn run_script_str(&self, payload: &str, params: &str) -> String; + fn run_script_str(&self, payload: &str, params: &str, immutable: bool) -> String; fn export_relations_str(&self, data: &str) -> String; fn import_relations_str(&self, data: &str) -> String; fn backup_db_str(&self, out_file: &str) -> String; diff --git a/cozo-lib-wasm/src/lib.rs b/cozo-lib-wasm/src/lib.rs index fefcedab..594e436c 100644 --- a/cozo-lib-wasm/src/lib.rs +++ b/cozo-lib-wasm/src/lib.rs @@ -36,8 +36,8 @@ impl CozoDb { let db = DbInstance::new("mem", "", "").unwrap(); Self { db } } - pub fn run(&self, script: &str, params: &str) -> String { - self.db.run_script_str(script, params) + pub fn run(&self, script: &str, params: &str, immutable: bool) -> String { + self.db.run_script_str(script, params, immutable) } pub fn export_relations(&self, data: &str) -> String { self.db.export_relations_str(data)