evaluate a list of expressions

main
Ziyang Hu 1 year ago
parent 2db82d0185
commit 974dfc2e2a

@ -20,7 +20,7 @@ use rustyline::history::DefaultHistory;
use rustyline::Changeset; use rustyline::Changeset;
use serde_json::{json, Value}; use serde_json::{json, Value};
use cozo::{DataValue, DbInstance, NamedRows}; use cozo::{DataValue, DbInstance, evaluate_expressions, NamedRows};
struct Indented; struct Indented;
@ -208,6 +208,12 @@ fn process_line(
.split_once(|c: char| c.is_whitespace()) .split_once(|c: char| c.is_whitespace())
.unwrap_or((remaining, "")); .unwrap_or((remaining, ""));
match op { match op {
"eval" => {
let out = evaluate_expressions(payload, params, params)?;
for val in out {
println!("{val}");
}
}
"set" => { "set" => {
let (key, v_str) = payload let (key, v_str) = payload
.trim() .trim()

@ -266,4 +266,6 @@ fts_expr = {fts_term ~ (fts_op ~ fts_term)*}
fts_op = _{fts_and | fts_or | fts_not} fts_op = _{fts_and | fts_or | fts_not}
fts_and = {"AND"} fts_and = {"AND"}
fts_or = {"OR" | "," | ";"} fts_or = {"OR" | "," | ";"}
fts_not = {"NOT"} fts_not = {"NOT"}
expression_script = {SOI ~ expr ~ ("," ~ expr)* ~ EOI}

@ -335,7 +335,6 @@ impl Expr {
#[derive(Debug, Error, Diagnostic)] #[derive(Debug, Error, Diagnostic)]
#[error("Cannot find binding {0}")] #[error("Cannot find binding {0}")]
#[diagnostic(code(eval::bad_binding))] #[diagnostic(code(eval::bad_binding))]
#[diagnostic(help("This could indicate a system problem"))]
struct BadBindingError(String, #[label] SourceSpan); struct BadBindingError(String, #[label] SourceSpan);
let found_idx = *binding_map let found_idx = *binding_map

@ -74,6 +74,7 @@ pub use crate::parse::SourceSpan;
pub use crate::runtime::callback::CallbackOp; pub use crate::runtime::callback::CallbackOp;
pub use crate::runtime::db::Poison; pub use crate::runtime::db::Poison;
pub use crate::runtime::db::TransactionPayload; pub use crate::runtime::db::TransactionPayload;
pub use crate::runtime::db::evaluate_expressions;
pub(crate) mod data; pub(crate) mod data;
pub(crate) mod fixed_rule; pub(crate) mod fixed_rule;
@ -543,7 +544,7 @@ impl MultiTransaction {
pub fn format_error_as_json(mut err: Report, source: Option<&str>) -> JsonValue { pub fn format_error_as_json(mut err: Report, source: Option<&str>) -> JsonValue {
if err.source_code().is_none() { if err.source_code().is_none() {
if let Some(src) = source { if let Some(src) = source {
err = err.with_source_code(src.to_string()); err = err.with_source_code(format!("{src} "));
} }
} }
let mut text_err = String::new(); let mut text_err = String::new();

@ -21,18 +21,19 @@ use thiserror::Error;
use crate::data::program::InputProgram; use crate::data::program::InputProgram;
use crate::data::relation::NullableColType; use crate::data::relation::NullableColType;
use crate::data::value::{DataValue, ValidityTs}; use crate::data::value::{DataValue, ValidityTs};
use crate::parse::expr::build_expr;
use crate::parse::imperative::parse_imperative_block; use crate::parse::imperative::parse_imperative_block;
use crate::parse::query::parse_query; use crate::parse::query::parse_query;
use crate::parse::schema::parse_nullable_type; use crate::parse::schema::parse_nullable_type;
use crate::parse::sys::{parse_sys, SysOp}; use crate::parse::sys::{parse_sys, SysOp};
use crate::FixedRule; use crate::{Expr, FixedRule};
pub(crate) mod expr; pub(crate) mod expr;
pub(crate) mod fts;
pub(crate) mod imperative; pub(crate) mod imperative;
pub(crate) mod query; pub(crate) mod query;
pub(crate) mod schema; pub(crate) mod schema;
pub(crate) mod sys; pub(crate) mod sys;
pub(crate) mod fts;
#[derive(pest_derive::Parser)] #[derive(pest_derive::Parser)]
#[grammar = "cozoscript.pest"] #[grammar = "cozoscript.pest"]
@ -209,6 +210,34 @@ pub(crate) fn parse_type(src: &str) -> Result<NullableColType> {
parse_nullable_type(parsed.into_inner().next().unwrap()) parse_nullable_type(parsed.into_inner().next().unwrap())
} }
pub(crate) fn parse_expressions(
src: &str,
param_pool: &BTreeMap<String, DataValue>,
) -> Result<Vec<Expr>> {
let mut ret = vec![];
let parsed = CozoScriptParser::parse(Rule::expression_script, src)
.map_err(|err| {
let span = match err.location {
InputLocation::Pos(p) => SourceSpan(p, 0),
InputLocation::Span((start, end)) => SourceSpan(start, end - start),
};
ParseError { span }
})?
.next()
.unwrap()
.into_inner();
for rule in parsed {
match rule.as_rule() {
Rule::expr => {
ret.push(build_expr(rule, param_pool)?);
}
Rule::EOI => {}
_ => unreachable!(),
}
}
Ok(ret)
}
pub(crate) fn parse_script( pub(crate) fn parse_script(
src: &str, src: &str,
param_pool: &BTreeMap<String, DataValue>, param_pool: &BTreeMap<String, DataValue>,
@ -257,3 +286,14 @@ impl ExtractSpan for Pair<'_> {
SourceSpan(start, end - start) SourceSpan(start, end - start)
} }
} }
#[cfg(test)]
mod tests {
use crate::parse::parse_expressions;
#[test]
fn test_expressions() {
let x = parse_expressions("null, 1, 2, 3, 5, 6 > 7", &Default::default()).unwrap();
println!("{:?}", x);
}
}

@ -104,7 +104,7 @@ impl<'a> SessionTx<'a> {
if err.source_code().is_some() { if err.source_code().is_some() {
err err
} else { } else {
err.with_source_code(trigger.to_string()) err.with_source_code(format!("{trigger}" ))
} }
})?; })?;
to_clear.extend(cleanups); to_clear.extend(cleanups);
@ -729,7 +729,7 @@ impl<'a> SessionTx<'a> {
if err.source_code().is_some() { if err.source_code().is_some() {
err err
} else { } else {
err.with_source_code(trigger.to_string()) err.with_source_code(format!("{trigger} "))
} }
})?; })?;
to_clear.extend(cleanups); to_clear.extend(cleanups);
@ -1059,7 +1059,7 @@ impl<'a> SessionTx<'a> {
if err.source_code().is_some() { if err.source_code().is_some() {
err err
} else { } else {
err.with_source_code(trigger.to_string()) err.with_source_code(format!("{trigger} "))
} }
})?; })?;
to_clear.extend(cleanups); to_clear.extend(cleanups);

@ -41,7 +41,7 @@ use crate::data::value::{DataValue, ValidityTs, LARGEST_UTF_CHAR};
use crate::fixed_rule::DEFAULT_FIXED_RULES; use crate::fixed_rule::DEFAULT_FIXED_RULES;
use crate::fts::TokenizerCache; use crate::fts::TokenizerCache;
use crate::parse::sys::SysOp; use crate::parse::sys::SysOp;
use crate::parse::{parse_script, CozoScript, SourceSpan}; use crate::parse::{parse_expressions, parse_script, CozoScript, SourceSpan};
use crate::query::compile::{CompiledProgram, CompiledRule, CompiledRuleSet}; use crate::query::compile::{CompiledProgram, CompiledRule, CompiledRuleSet};
use crate::query::ra::{ use crate::query::ra::{
FilteredRA, FtsSearchRA, HnswSearchRA, InnerJoin, LshSearchRA, NegJoin, RelAlgebra, ReorderRA, FilteredRA, FtsSearchRA, HnswSearchRA, InnerJoin, LshSearchRA, NegJoin, RelAlgebra, ReorderRA,
@ -51,11 +51,13 @@ use crate::query::ra::{
use crate::runtime::callback::{ use crate::runtime::callback::{
CallbackCollector, CallbackDeclaration, CallbackOp, EventCallbackRegistry, CallbackCollector, CallbackDeclaration, CallbackOp, EventCallbackRegistry,
}; };
use crate::runtime::relation::{extend_tuple_from_v, AccessLevel, InsufficientAccessLevel, RelationHandle, RelationId}; use crate::runtime::relation::{
extend_tuple_from_v, AccessLevel, InsufficientAccessLevel, RelationHandle, RelationId,
};
use crate::runtime::transact::SessionTx; use crate::runtime::transact::SessionTx;
use crate::storage::temp::TempStorage; use crate::storage::temp::TempStorage;
use crate::storage::{Storage, StoreTx}; use crate::storage::{Storage, StoreTx};
use crate::{decode_tuple_from_kv, FixedRule}; use crate::{decode_tuple_from_kv, FixedRule, Symbol};
pub(crate) struct RunningQueryHandle { pub(crate) struct RunningQueryHandle {
pub(crate) started_at: f64, pub(crate) started_at: f64,
@ -375,9 +377,9 @@ impl<'s, S: Storage<'s>> Db<S> {
/// ///
/// `relations` contains names of the stored relations to export. /// `relations` contains names of the stored relations to export.
pub fn export_relations<I, T>(&'s self, relations: I) -> Result<BTreeMap<String, NamedRows>> pub fn export_relations<I, T>(&'s self, relations: I) -> Result<BTreeMap<String, NamedRows>>
where where
T: AsRef<str>, T: AsRef<str>,
I: Iterator<Item=T>, I: Iterator<Item = T>,
{ {
let tx = self.transact()?; let tx = self.transact()?;
let mut ret: BTreeMap<String, NamedRows> = BTreeMap::new(); let mut ret: BTreeMap<String, NamedRows> = BTreeMap::new();
@ -687,8 +689,8 @@ impl<'s, S: Storage<'s>> Db<S> {
} }
/// Register a custom fixed rule implementation. /// Register a custom fixed rule implementation.
pub fn register_fixed_rule<R>(&self, name: String, rule_impl: R) -> Result<()> pub fn register_fixed_rule<R>(&self, name: String, rule_impl: R) -> Result<()>
where where
R: FixedRule + 'static, R: FixedRule + 'static,
{ {
match self.fixed_rules.write().unwrap().entry(name) { match self.fixed_rules.write().unwrap().entry(name) {
Entry::Vacant(ent) => { Entry::Vacant(ent) => {
@ -757,7 +759,7 @@ impl<'s, S: Storage<'s>> Db<S> {
ret.is_some() ret.is_some()
} }
pub(crate) fn obtain_relation_locks<'a, T: Iterator<Item=&'a SmartString<LazyCompact>>>( pub(crate) fn obtain_relation_locks<'a, T: Iterator<Item = &'a SmartString<LazyCompact>>>(
&'s self, &'s self,
rels: T, rels: T,
) -> Vec<Arc<ShardedLock<()>>> { ) -> Vec<Arc<ShardedLock<()>>> {
@ -829,7 +831,7 @@ impl<'s, S: Storage<'s>> Db<S> {
callback_collector: &mut CallbackCollector, callback_collector: &mut CallbackCollector,
) -> Result<NamedRows> { ) -> Result<NamedRows> {
#[allow(unused_variables)] #[allow(unused_variables)]
let sleep_opt = p.out_opts.sleep; let sleep_opt = p.out_opts.sleep;
let (q_res, q_cleanups) = let (q_res, q_cleanups) =
self.run_query(tx, p, cur_vld, callback_targets, callback_collector, true)?; self.run_query(tx, p, cur_vld, callback_targets, callback_collector, true)?;
cleanups.extend(q_cleanups); cleanups.extend(q_cleanups);
@ -968,28 +970,28 @@ impl<'s, S: Storage<'s>> Db<S> {
("fixed", json!(null), json!(null), json!(null)) ("fixed", json!(null), json!(null), json!(null))
} }
RelAlgebra::TempStore(TempStoreRA { RelAlgebra::TempStore(TempStoreRA {
storage_key, storage_key,
filters, filters,
.. ..
}) => ( }) => (
"load_mem", "load_mem",
json!(storage_key.to_string()), json!(storage_key.to_string()),
json!(null), json!(null),
json!(filters.iter().map(|f| f.to_string()).collect_vec()), json!(filters.iter().map(|f| f.to_string()).collect_vec()),
), ),
RelAlgebra::Stored(StoredRA { RelAlgebra::Stored(StoredRA {
storage, filters, .. storage, filters, ..
}) => ( }) => (
"load_stored", "load_stored",
json!(format!(":{}", storage.name)), json!(format!(":{}", storage.name)),
json!(null), json!(null),
json!(filters.iter().map(|f| f.to_string()).collect_vec()), json!(filters.iter().map(|f| f.to_string()).collect_vec()),
), ),
RelAlgebra::StoredWithValidity(StoredWithValidityRA { RelAlgebra::StoredWithValidity(StoredWithValidityRA {
storage, storage,
filters, filters,
.. ..
}) => ( }) => (
"load_stored_with_validity", "load_stored_with_validity",
json!(format!(":{}", storage.name)), json!(format!(":{}", storage.name)),
json!(null), json!(null),
@ -1028,10 +1030,10 @@ impl<'s, S: Storage<'s>> Db<S> {
("reorder", json!(null), json!(null), json!(null)) ("reorder", json!(null), json!(null), json!(null))
} }
RelAlgebra::Filter(FilteredRA { RelAlgebra::Filter(FilteredRA {
parent, parent,
filters: pred, filters: pred,
.. ..
}) => { }) => {
rel_stack.push(parent); rel_stack.push(parent);
( (
"filter", "filter",
@ -1041,12 +1043,12 @@ impl<'s, S: Storage<'s>> Db<S> {
) )
} }
RelAlgebra::Unification(UnificationRA { RelAlgebra::Unification(UnificationRA {
parent, parent,
binding, binding,
expr, expr,
is_multi, is_multi,
.. ..
}) => { }) => {
rel_stack.push(parent); rel_stack.push(parent);
( (
if *is_multi { "multi-unify" } else { "unify" }, if *is_multi { "multi-unify" } else { "unify" },
@ -1056,8 +1058,8 @@ impl<'s, S: Storage<'s>> Db<S> {
) )
} }
RelAlgebra::HnswSearch(HnswSearchRA { RelAlgebra::HnswSearch(HnswSearchRA {
hnsw_search, .. hnsw_search, ..
}) => ( }) => (
"hnsw_index", "hnsw_index",
json!(format!(":{}", hnsw_search.query.name)), json!(format!(":{}", hnsw_search.query.name)),
json!(hnsw_search.query.name), json!(hnsw_search.query.name),
@ -1440,7 +1442,7 @@ impl<'s, S: Storage<'s>> Db<S> {
if let Some(tuple) = result_store.all_iter().next() { if let Some(tuple) = result_store.all_iter().next() {
#[derive(Debug, Error, Diagnostic)] #[derive(Debug, Error, Diagnostic)]
#[error( #[error(
"The query is asserted to return no result, but a tuple {0:?} is found" "The query is asserted to return no result, but a tuple {0:?} is found"
)] )]
#[diagnostic(code(eval::assert_none_failure))] #[diagnostic(code(eval::assert_none_failure))]
struct AssertNoneFailure(Tuple, #[label] SourceSpan); struct AssertNoneFailure(Tuple, #[label] SourceSpan);
@ -1493,7 +1495,8 @@ impl<'s, S: Storage<'s>> Db<S> {
) )
.wrap_err_with(|| format!("when executing against relation '{}'", meta.name))?; .wrap_err_with(|| format!("when executing against relation '{}'", meta.name))?;
clean_ups.extend(to_clear); clean_ups.extend(to_clear);
let returned_rows = tx.get_returning_rows(callback_collector, &meta.name, returning)?; let returned_rows =
tx.get_returning_rows(callback_collector, &meta.name, returning)?;
Ok((returned_rows, clean_ups)) Ok((returned_rows, clean_ups))
} else { } else {
// not sorting outputs // not sorting outputs
@ -1548,7 +1551,8 @@ impl<'s, S: Storage<'s>> Db<S> {
) )
.wrap_err_with(|| format!("when executing against relation '{}'", meta.name))?; .wrap_err_with(|| format!("when executing against relation '{}'", meta.name))?;
clean_ups.extend(to_clear); clean_ups.extend(to_clear);
let returned_rows = tx.get_returning_rows(callback_collector, &meta.name, returning)?; let returned_rows =
tx.get_returning_rows(callback_collector, &meta.name, returning)?;
Ok((returned_rows, clean_ups)) Ok((returned_rows, clean_ups))
} else { } else {
@ -1756,6 +1760,41 @@ impl<'s, S: Storage<'s>> Db<S> {
} }
} }
/// Evaluate a string expression in the context of a set of parameters and variables
pub fn evaluate_expressions(
src: &str,
params: &BTreeMap<String, DataValue>,
vars: &BTreeMap<String, DataValue>,
) -> Result<Vec<DataValue>> {
_evaluate_expressions(src, params, vars).map_err(|err| {
if err.source().is_none() {
err.with_source_code(format!("{src} "))
} else {
err
}
})
}
fn _evaluate_expressions(
src: &str,
params: &BTreeMap<String, DataValue>,
vars: &BTreeMap<String, DataValue>,
) -> Result<Vec<DataValue>> {
let mut exprs = parse_expressions(src, params)?;
let mut ctx = vec![];
let mut binding_map = BTreeMap::new();
for (i, (k, v)) in vars.iter().enumerate() {
ctx.push(v.clone());
binding_map.insert(Symbol::new(k, Default::default()), i);
}
let mut ret = vec![];
for expr in exprs.iter_mut() {
expr.fill_binding_indices(&binding_map)?;
ret.push(expr.eval(&ctx)?);
}
Ok(ret)
}
/// Used for user-initiated termination of running queries /// Used for user-initiated termination of running queries
#[derive(Clone, Default)] #[derive(Clone, Default)]
pub struct Poison(pub(crate) Arc<AtomicBool>); pub struct Poison(pub(crate) Arc<AtomicBool>);
@ -1792,7 +1831,7 @@ impl Poison {
pub(crate) fn seconds_since_the_epoch() -> Result<f64> { pub(crate) fn seconds_since_the_epoch() -> Result<f64> {
#[cfg(not(target_arch = "wasm32"))] #[cfg(not(target_arch = "wasm32"))]
let now = SystemTime::now(); let now = SystemTime::now();
#[cfg(not(target_arch = "wasm32"))] #[cfg(not(target_arch = "wasm32"))]
return Ok(now return Ok(now
.duration_since(UNIX_EPOCH) .duration_since(UNIX_EPOCH)

@ -395,9 +395,32 @@ impl CozoDbMulTx {
} }
} }
#[pyfunction]
fn eval_expressions(
py: Python<'_>,
query: &str,
params: &PyDict,
bindings: &PyDict,
) -> PyResult<Vec<PyObject>> {
let params = convert_params(params).unwrap();
let bindings = convert_params(bindings).unwrap();
match evaluate_expressions(query, &params, &bindings) {
Ok(rows) => Ok(rows.into_iter().map(|r| value_to_py(r, py)).collect()),
Err(err) => {
let reports = format_error_as_json(err, Some(query)).to_string();
let json_mod = py.import("json")?;
let loads_fn = json_mod.getattr("loads")?;
let args = PyTuple::new(py, [PyString::new(py, &reports)]);
let msg = loads_fn.call1(args)?;
Err(PyException::new_err(PyObject::from(msg)))
}
}
}
#[pymodule] #[pymodule]
fn cozo_embedded(_py: Python<'_>, m: &PyModule) -> PyResult<()> { fn cozo_embedded(_py: Python<'_>, m: &PyModule) -> PyResult<()> {
m.add_class::<CozoDbPy>()?; m.add_class::<CozoDbPy>()?;
m.add_class::<CozoDbMulTx>()?; m.add_class::<CozoDbMulTx>()?;
m.add_function(wrap_pyfunction!(eval_expressions, m)?)?;
Ok(()) Ok(())
} }

Loading…
Cancel
Save