query mutability

main
Ziyang Hu 1 year ago
parent a164a3919c
commit 6b8438e823

@ -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<dyn Error>> {
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(())

@ -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<String, serde_json::Value>,
immutable: Option<bool>,
}
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),

@ -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);

@ -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() {
@ -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]));

@ -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;

@ -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;

@ -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<TiKvStorage>),
}
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<String, DataValue>,
mutability: ScriptMutability,
) -> Result<NamedRows> {
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<NamedRows> {
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<String, DataValue>,
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<I, T>(&self, relations: I) -> Result<BTreeMap<String, NamedRows>>

@ -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]]));
}
}

@ -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;

@ -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;

@ -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<S> {
@ -369,10 +378,26 @@ impl<'s, S: Storage<'s>> Db<S> {
&'s self,
payload: &str,
params: BTreeMap<String, DataValue>,
mutability: ScriptMutability,
) -> Result<NamedRows> {
let cur_vld = current_validity();
self.do_run_script(
payload,
&params,
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<String, DataValue>,
) -> Result<NamedRows> {
let cur_vld = current_validity();
self.do_run_script(payload, &params, cur_vld)
self.do_run_script(payload, &params, 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<S> {
payload: &str,
param_pool: &BTreeMap<String, DataValue>,
cur_vld: ValidityTs,
read_only: bool,
) -> Result<NamedRows> {
match parse_script(
payload,
@ -854,16 +880,24 @@ impl<'s, S: Storage<'s>> Db<S> {
&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<NamedRows, Report> {
fn execute_single(
&'s self,
cur_vld: ValidityTs,
p: InputProgram,
read_only: bool,
) -> Result<NamedRows, Report> {
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<S> {
Ok(NamedRows::new(headers, rows))
}
fn run_sys_op(&'s self, op: SysOp) -> Result<NamedRows> {
fn run_sys_op(&'s self, op: SysOp, read_only: bool) -> Result<NamedRows> {
match op {
SysOp::Explain(prog) => {
let mut tx = self.transact()?;
@ -1142,6 +1176,9 @@ impl<'s, S: Storage<'s>> Db<S> {
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<S> {
))
}
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<S> {
))
}
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<S> {
))
}
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<S> {
))
}
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<S> {
))
}
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<S> {
))
}
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<S> {
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<S> {
))
}
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<S> {
))
}
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)?;

@ -247,9 +247,13 @@ impl<'s, S: Storage<'s>> Db<S> {
&'s self,
cur_vld: ValidityTs,
ps: &ImperativeProgram,
read_only: bool,
) -> Result<NamedRows, Report> {
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);
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

@ -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

@ -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()
}

@ -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, &params_str);
let res = db.run_script_str(&script, &params_str, false);
env.new_string(res).unwrap().into_raw()
}
}

@ -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)
})
}

@ -406,11 +406,20 @@ fn query_db(mut cx: FunctionContext) -> JsResult<JsUndefined> {
js2params(&mut cx, params_js, &mut params)?;
let callback = cx.argument::<JsFunction>(3)?.root(&mut cx);
let immutable = cx.argument::<JsBoolean>(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();

@ -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<PyObject> {
pub fn run_script(
&self,
py: Python<'_>,
query: &str,
params: &PyDict,
immutable: bool,
) -> PyResult<PyObject> {
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();

@ -15,7 +15,7 @@ mod ffi {
fn new_cozo_db(engine: &str, path: &str, options: &str) -> Option<DbInstance>;
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;

@ -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)

Loading…
Cancel
Save