use crate::expr::{evaluate, ExprFailure, ExprFailureCode};
use crate::plan::RelationKind;
use crate::value::Value;
use serde_json::{json, Value as J};
pub type Expr = J;
pub type R = Result<Value, ExprFailure>;
pub fn r#ref(path: &[&str], scope: &[(String, Value)]) -> R {
evaluate(&json!({ "ref": path }), scope)
}
pub fn ref_opt(path: &[&str], scope: &[(String, Value)]) -> R {
evaluate(&json!({ "refOpt": path }), scope)
}
pub fn int_lit(literal: &str, scope: &[(String, Value)]) -> R {
evaluate(&json!({ "int": literal }), scope)
}
pub fn float_lit(n: f64, scope: &[(String, Value)]) -> R {
evaluate(&json!({ "float": n }), scope)
}
pub fn obj(fields: &J, scope: &[(String, Value)]) -> R {
evaluate(&json!({ "obj": fields }), scope)
}
pub fn arr(elems: &[J], scope: &[(String, Value)]) -> R {
evaluate(&json!({ "arr": elems }), scope)
}
pub fn add(a: &Expr, b: &Expr, scope: &[(String, Value)]) -> R {
evaluate(&json!({ "add": [a, b] }), scope)
}
pub fn sub(a: &Expr, b: &Expr, scope: &[(String, Value)]) -> R {
evaluate(&json!({ "sub": [a, b] }), scope)
}
pub fn mul(a: &Expr, b: &Expr, scope: &[(String, Value)]) -> R {
evaluate(&json!({ "mul": [a, b] }), scope)
}
pub fn neg(a: &Expr, scope: &[(String, Value)]) -> R {
evaluate(&json!({ "neg": [a] }), scope)
}
pub fn div(a: &Expr, b: &Expr, scope: &[(String, Value)]) -> R {
evaluate(&json!({ "div": [a, b] }), scope)
}
pub fn r#mod(a: &Expr, b: &Expr, scope: &[(String, Value)]) -> R {
evaluate(&json!({ "mod": [a, b] }), scope)
}
pub fn concat(parts: &[J], scope: &[(String, Value)]) -> R {
evaluate(&json!({ "concat": parts }), scope)
}
pub fn eq(a: &Expr, b: &Expr, scope: &[(String, Value)]) -> R {
evaluate(&json!({ "eq": [a, b] }), scope)
}
pub fn ne(a: &Expr, b: &Expr, scope: &[(String, Value)]) -> R {
evaluate(&json!({ "ne": [a, b] }), scope)
}
pub fn lt(a: &Expr, b: &Expr, scope: &[(String, Value)]) -> R {
evaluate(&json!({ "lt": [a, b] }), scope)
}
pub fn le(a: &Expr, b: &Expr, scope: &[(String, Value)]) -> R {
evaluate(&json!({ "le": [a, b] }), scope)
}
pub fn gt(a: &Expr, b: &Expr, scope: &[(String, Value)]) -> R {
evaluate(&json!({ "gt": [a, b] }), scope)
}
pub fn ge(a: &Expr, b: &Expr, scope: &[(String, Value)]) -> R {
evaluate(&json!({ "ge": [a, b] }), scope)
}
pub fn and(a: &Expr, b: &Expr, scope: &[(String, Value)]) -> R {
evaluate(&json!({ "and": [a, b] }), scope)
}
pub fn or(a: &Expr, b: &Expr, scope: &[(String, Value)]) -> R {
evaluate(&json!({ "or": [a, b] }), scope)
}
pub fn not(a: &Expr, scope: &[(String, Value)]) -> R {
evaluate(&json!({ "not": [a] }), scope)
}
pub fn coalesce(a: &Expr, b: &Expr, scope: &[(String, Value)]) -> R {
evaluate(&json!({ "coalesce": [a, b] }), scope)
}
pub fn cond(c: &Expr, then: &Expr, els: &Expr, scope: &[(String, Value)]) -> R {
evaluate(&json!({ "cond": [c, then, els] }), scope)
}
pub fn len(a: &Expr, scope: &[(String, Value)]) -> R {
evaluate(&json!({ "len": [a] }), scope)
}
pub fn eval_expr(node: &Expr, scope: &[(String, Value)]) -> R {
evaluate(node, scope)
}
fn expr_fail(code: ExprFailureCode, message: String) -> ExprFailure {
ExprFailure { code, message }
}
pub fn ref_native(path: &[&str], scope: &[(String, Value)]) -> R {
ref_walk(path, scope, false)
}
pub fn ref_opt_native(path: &[&str], scope: &[(String, Value)]) -> R {
ref_walk(path, scope, true)
}
fn ref_walk(path: &[&str], scope: &[(String, Value)], optional: bool) -> R {
let op = if optional { "refOpt" } else { "ref" };
if path.is_empty() {
return Err(expr_fail(
ExprFailureCode::InvalidNode,
format!("{op} expects a non-empty string path"),
));
}
let head = path[0];
let mut cur: Value = match scope.iter().find(|(k, _)| k == head) {
Some((_, v)) => v.clone(),
None => {
return Err(expr_fail(
ExprFailureCode::UnknownBinding,
format!("unknown binding: {head}"),
))
}
};
for &seg in &path[1..] {
match cur {
Value::Null => {
if optional {
return Ok(Value::Null);
}
return Err(expr_fail(
ExprFailureCode::NullRef,
format!("null intermediate at .{seg} (use ?.)"),
));
}
Value::Obj(ref pairs) => match pairs.iter().find(|(k, _)| k == seg) {
Some((_, v)) => cur = v.clone(),
None => {
return Err(expr_fail(
ExprFailureCode::MissingProp,
format!("missing property .{seg}"),
))
}
},
ref other => {
return Err(expr_fail(
ExprFailureCode::TypeMismatch,
format!("cannot access .{seg} on {}", other.type_name()),
))
}
}
}
Ok(cur)
}
pub fn concat_native(parts: &[Value]) -> R {
if parts.len() < 2 {
return Err(expr_fail(
ExprFailureCode::InvalidNode,
format!("concat expects >= 2 args, got {}", parts.len()),
));
}
let mut s = String::new();
for p in parts {
match p {
Value::Str(part) => s.push_str(part),
other => {
return Err(expr_fail(
ExprFailureCode::TypeMismatch,
format!(
"concat: strings only (got {}; no implicit toString)",
other.type_name()
),
))
}
}
}
Ok(Value::Str(s))
}
pub fn map_with_concurrency<T, U, F>(items: &[T], concurrency: i64, worker: F) -> Vec<U>
where
T: Sync,
U: Send,
F: Fn(&T, usize) -> U + Sync,
{
let n = items.len();
let limit = concurrency.max(1) as usize;
if limit <= 1 || n <= 1 {
return items
.iter()
.enumerate()
.map(|(i, it)| worker(it, i))
.collect();
}
let mut slots: Vec<Option<U>> = (0..n).map(|_| None).collect();
let cursor = std::sync::atomic::AtomicUsize::new(0);
let workers = limit.min(n);
let worker_ref = &worker;
let items_ref = items;
{
struct SlotsPtr<U>(*mut Option<U>);
unsafe impl<U: Send> Sync for SlotsPtr<U> {}
let base = SlotsPtr(slots.as_mut_ptr());
std::thread::scope(|s| {
for _ in 0..workers {
let cursor = &cursor;
let base = &base;
s.spawn(move || loop {
let i = cursor.fetch_add(1, std::sync::atomic::Ordering::SeqCst);
if i >= n {
break;
}
let out = worker_ref(&items_ref[i], i);
unsafe {
*base.0.add(i) = Some(out);
}
});
}
});
}
slots
.into_iter()
.map(|o| o.expect("all slots filled"))
.collect()
}
pub fn unproduced_value(kind: Option<RelationKind>) -> Value {
match kind {
Some(RelationKind::Connection) => skip_connection(),
_ => Value::Null,
}
}
pub fn skip_single() -> Value {
Value::Null
}
pub fn skip_connection() -> Value {
Value::Obj(vec![
("items".to_string(), Value::Arr(vec![])),
("cursor".to_string(), Value::Null),
])
}
pub fn hydrate_into(
over: &[Value],
key: &str,
kept_idx: &[usize],
values: &[Value],
) -> Result<Vec<Value>, String> {
let mut augmented: Vec<Value> = Vec::with_capacity(over.len());
let mut k = 0usize;
for (i, el) in over.iter().enumerate() {
if k < kept_idx.len() && kept_idx[k] == i {
match el {
Value::Obj(pairs) => {
let mut next: Vec<(String, Value)> = pairs.clone();
if let Some(slot) = next.iter_mut().find(|(kk, _)| kk == key) {
slot.1 = values[k].clone();
} else {
next.push((key.to_string(), values[k].clone()));
}
augmented.push(Value::Obj(next));
}
_ => {
return Err(format!(
"hydrate_into: element {i} is not an object (into requires object elements)"
))
}
}
k += 1;
} else {
augmented.push(el.clone());
}
}
Ok(augmented)
}