use crate::expr::{ExprFailure, ExprFailureCode};
use crate::plan::RelationKind;
use crate::value::Value;
pub type R = Result<Value, ExprFailure>;
pub fn add_native(a: Value, b: Value) -> R {
crate::expr::arith("add", &a, &b)
}
pub fn sub_native(a: Value, b: Value) -> R {
crate::expr::arith("sub", &a, &b)
}
pub fn mul_native(a: Value, b: Value) -> R {
crate::expr::arith("mul", &a, &b)
}
pub fn neg_native(a: Value) -> R {
crate::expr::neg(&a)
}
pub fn div_native(a: Value, b: Value) -> R {
crate::expr::div(&a, &b)
}
pub fn mod_native(a: Value, b: Value) -> R {
crate::expr::rem(&a, &b)
}
pub fn eq_native(a: Value, b: Value) -> R {
crate::expr::eq_ne("eq", &a, &b)
}
pub fn ne_native(a: Value, b: Value) -> R {
crate::expr::eq_ne("ne", &a, &b)
}
pub fn lt_native(a: Value, b: Value) -> R {
crate::expr::compare("lt", &a, &b)
}
pub fn le_native(a: Value, b: Value) -> R {
crate::expr::compare("le", &a, &b)
}
pub fn gt_native(a: Value, b: Value) -> R {
crate::expr::compare("gt", &a, &b)
}
pub fn ge_native(a: Value, b: Value) -> R {
crate::expr::compare("ge", &a, &b)
}
pub fn len_native(a: Value) -> R {
crate::expr::len(&a)
}
pub fn not_native(a: Value) -> R {
Ok(Value::Bool(!crate::expr::require_bool(&a, "not")?))
}
pub fn obj_native(pairs: Vec<(String, Value)>) -> R {
crate::expr::make_obj(pairs)
}
pub fn arr_native(elems: Vec<Value>) -> R {
Ok(Value::Arr(elems))
}
pub fn int_lit_native(s: &str) -> R {
crate::expr::int_lit(s)
}
pub fn float_lit_native(n: f64) -> R {
crate::expr::float_lit(n)
}
pub fn number_lit_native(n: f64) -> R {
crate::expr::number_lit(n)
}
pub fn null_native() -> R {
Ok(Value::Null)
}
pub fn bool_native(b: bool) -> R {
Ok(Value::Bool(b))
}
pub fn str_native(s: &str) -> R {
Ok(Value::Str(s.to_string()))
}
pub fn require_bool_native(v: Value, ctx: &str) -> Result<bool, ExprFailure> {
crate::expr::require_bool(&v, ctx)
}
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)
}