use serde_json::Value as Json;
use core_traits::{TypedValue, ValueType};
use edn::query::{
Binding as InBinding, OrWhereClause, PatternNonValuePlace, PatternValuePlace, Variable,
VariableOrPlaceholder, WhereClause,
};
use mentat_core::{HasSchema, Schema};
use public_traits::errors::{MentatError, Result};
use mentat_query_algebrizer::{QueryInputs, TemporalBound};
fn err<T>(msg: String) -> Result<T> {
Err(MentatError::BadQueryOptions(msg))
}
pub fn options_from_json(
schema: &Schema,
query: &str,
options: &Json,
) -> Result<(QueryInputs, Option<TemporalBound>)> {
let obj = match options {
Json::Null => return Ok((QueryInputs::default(), None)),
Json::Object(o) => o,
_ => return err("options must be a JSON object".into()),
};
let tx = |k: &str, v: &Json| match v.as_i64() {
Some(t) => Ok(t),
None => err(format!("\"{k}\" must be an integer tx id, got {v}")),
};
let (mut inputs, mut as_of, mut since) = (None, None, None);
for (k, v) in obj {
match k.as_str() {
"inputs" => match v {
Json::Array(a) => inputs = Some(a.clone()),
other => return err(format!("\"inputs\" must be an array, got {other}")),
},
"asOf" => as_of = Some(tx(k, v)?),
"since" => since = Some(tx(k, v)?),
other => {
return err(format!(
"unknown option \"{other}\" (expected inputs, asOf, since)"
))
}
}
}
let temporal = match (as_of, since) {
(Some(_), Some(_)) => return err("\"asOf\" and \"since\" are mutually exclusive".into()),
(Some(t), None) => Some(TemporalBound::AsOf(t)),
(None, Some(t)) => Some(TemporalBound::Since(t)),
(None, None) => None,
};
let inputs = match inputs {
Some(vals) => inputs_from_json(schema, query, vals)?,
None => QueryInputs::default(),
};
Ok((inputs, temporal))
}
fn ref_vars(clauses: &[WhereClause], schema: &Schema, out: &mut Vec<Variable>) {
for c in clauses {
match c {
WhereClause::Pattern(p) => {
for place in [&p.entity, &p.tx] {
if let PatternNonValuePlace::Variable(v) = place {
out.push(v.clone());
}
}
if let (PatternNonValuePlace::Ident(a), PatternValuePlace::Variable(v)) =
(&p.attribute, &p.value)
{
if schema
.attribute_for_ident(a)
.is_some_and(|(attr, _)| attr.value_type == ValueType::Ref)
{
out.push(v.clone());
}
}
}
WhereClause::NotJoin(n) => ref_vars(&n.clauses, schema, out),
WhereClause::OrJoin(o) => {
for oc in &o.clauses {
match oc {
OrWhereClause::Clause(c) => ref_vars(std::slice::from_ref(c), schema, out),
OrWhereClause::And(cs) => ref_vars(cs, schema, out),
}
}
}
_ => {}
}
}
}
fn json_to_typed(j: &Json, is_ref: bool) -> Result<TypedValue> {
Ok(match j {
Json::Bool(b) => TypedValue::Boolean(*b),
Json::Number(n) => match (n.as_i64(), n.as_f64()) {
(Some(i), _) if is_ref => TypedValue::Ref(i),
(Some(i), _) => TypedValue::Long(i),
(None, Some(f)) => TypedValue::from(f),
_ => return err(format!("unrepresentable number {n}")),
},
Json::String(s) if s.starts_with(':') => {
match edn::parse::value(s).map(|v| v.without_spans()) {
Ok(edn::Value::Keyword(k)) => TypedValue::from(k),
_ => return err(format!("invalid keyword input {s}")),
}
}
Json::String(s) => TypedValue::typed_string(s),
other => return err(format!("unsupported input value {other}")),
})
}
fn inputs_from_json(schema: &Schema, query: &str, vals: Vec<Json>) -> Result<QueryInputs> {
let parsed = edn::parse::parse_query(query)?;
if vals.len() != parsed.in_bindings.len() {
return err(format!(
"query has {} :in binding(s) but \"inputs\" has {} value(s)",
parsed.in_bindings.len(),
vals.len()
));
}
let mut refs = Vec::new();
ref_vars(&parsed.where_clauses, schema, &mut refs);
let tv = |v: &Variable, j: &Json| json_to_typed(j, refs.contains(v));
let array = |j: Json, what: &str| match j {
Json::Array(a) => Ok(a),
other => err(format!("{what} input must be a JSON array, got {other}")),
};
let row = |vps: &[VariableOrPlaceholder], vals: Vec<Json>| {
if vals.len() != vps.len() {
return err(format!(
"tuple needs {} value(s), got {}",
vps.len(),
vals.len()
));
}
let mut vars = Vec::new();
let mut out = Vec::new();
for (vp, j) in vps.iter().zip(vals) {
if let VariableOrPlaceholder::Variable(v) = vp {
out.push(tv(v, &j)?);
vars.push(v.clone());
}
}
Ok((vars, out))
};
let mut scalars = Vec::new();
let mut non_scalar = Vec::new();
for (b, j) in parsed.in_bindings.iter().zip(vals) {
match b {
InBinding::BindScalar(v) => scalars.push((v.clone(), tv(v, &j)?)),
InBinding::BindColl(v) => {
let xs = array(j, "collection [?x ...]")?;
let xs = xs.iter().map(|x| tv(v, x)).collect::<Result<_>>()?;
non_scalar.push(QueryInputs::with_collection(v.clone(), xs));
}
InBinding::BindTuple(vps) => {
let (vars, xs) = row(vps, array(j, "tuple [?a ?b]")?)?;
non_scalar.push(QueryInputs::with_tuple(vars, xs));
}
InBinding::BindRel(vps) => {
let mut vars: Vec<Variable> =
vps.iter().filter_map(|vp| vp.clone().into_var()).collect();
let mut rows = Vec::new();
for r in array(j, "relation [[?a ?b]]")? {
let (vs, xs) = row(vps, array(r, "relation row")?)?;
vars = vs;
rows.push(xs);
}
non_scalar.push(QueryInputs::with_relation(vars, rows));
}
}
}
let mut out = QueryInputs::with_value_sequence(scalars);
for ns in non_scalar {
out = match out.merge(ns) {
Ok(o) => o,
Err(e) => return err(format!("inputs: {e}")),
};
}
Ok(out)
}