use oqx::Value;
use oqx::ast::{BinaryOp, Expr, LogicalOp};
use rusqlite::types::Value as SqlValue;
use crate::context::{Target, to_sql};
#[derive(Clone, Copy, Debug)]
pub struct TranslateCtx<'a> {
pub target: Target,
pub self_alias: &'a str,
pub doc_alias: &'a str,
pub params: &'a [Value],
}
#[derive(Clone, Debug, PartialEq)]
pub struct Frag {
pub sql: String,
pub params: Vec<SqlValue>,
}
impl Frag {
fn bare(sql: impl Into<String>) -> Self {
Self {
sql: sql.into(),
params: Vec::new(),
}
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum Ty {
Text,
Int,
Num,
Bool,
Json,
Prop,
Null,
}
#[must_use]
pub fn comparable(op: BinaryOp, a: Ty, b: Ty) -> bool {
use Ty::{Int, Null, Num, Prop, Text};
let numeric = |t: Ty| matches!(t, Int | Num);
let both_numeric = numeric(a) && numeric(b);
if matches!(op, BinaryOp::Eq | BinaryOp::Ne) {
a == Text
|| b == Text
|| (a == Null && b != Prop)
|| (b == Null && a != Prop)
|| both_numeric
} else {
(a == Text && b == Text) || both_numeric
}
}
fn const_ty(v: &Value) -> Option<Ty> {
Some(match v {
Value::Str(_) => Ty::Text,
Value::Number(_) => Ty::Num,
Value::Bool(_) => Ty::Bool,
Value::Null | Value::Undefined => Ty::Null,
Value::Array(_) | Value::Object(_) | Value::Range(_) => return None,
})
}
#[must_use]
pub fn non_property_handles(t: Target) -> &'static [&'static str] {
match t {
Target::Docs => &[
"doc",
"blocks",
"nodes",
"out",
"in",
"out_edges",
"in_edges",
"frontmatter",
"inline",
],
Target::Blocks => &[
"block",
"doc",
"children",
"nodes",
"out_edges",
"section",
"attrs",
],
Target::Nodes => &[
"section",
"doc",
"block",
"blocks",
"subsections",
"children",
"attrs",
],
Target::Edges => &["doc"],
}
}
fn intrinsic_sql(name: &str, ctx: &TranslateCtx<'_>) -> Option<(String, Ty)> {
let (s, d) = (ctx.self_alias, ctx.doc_alias);
let sql = match (ctx.target, name) {
(Target::Docs, "$id") => format!("{s}.doc_id"),
(Target::Docs, "$path") => format!("{d}.path"),
(Target::Docs, "$content_hash") => format!("lower(hex({s}.file_hash))"),
(Target::Docs, "$updated_at") => format!(
"(SELECT c.ts FROM revisions r JOIN commits c ON c.commit_id = r.commit_id WHERE r.rev_id = {s}.current_rev)"
),
(Target::Blocks, "$id") => format!("{s}.block_id"),
(Target::Blocks, "$doc") => format!("{s}.doc_id"),
(Target::Blocks, "$path") => format!("{d}.path"),
(Target::Blocks, "$ordinal") => return Some((format!("{s}.ordinal"), Ty::Int)),
(Target::Blocks, "$depth") => return Some((format!("{s}.depth"), Ty::Int)),
(Target::Blocks, "$body") => format!("{s}.text"),
(Target::Blocks, "$content_hash") => format!("lower(hex({s}.raw_hash))"),
(Target::Nodes, "$id" | "$node_id") => format!("{s}.node_id"),
(Target::Nodes, "$doc_id") => format!("{s}.doc_id"),
(Target::Nodes, "$block_id") => format!("{s}.block_id"),
(Target::Nodes, "$path") => format!("{d}.path"),
(Target::Edges, "$id") => format!("{s}.edge_id"),
(Target::Edges, "$src") => format!("{s}.src_doc"),
(Target::Edges, "$dst") => format!("{s}.dst_node"),
(Target::Edges, "$src_block") => format!("{s}.src_block"),
(Target::Edges, "$via") => format!("{s}.via_node"),
(Target::Edges, "$from_commit") => format!("{s}.from_commit"),
(Target::Edges, "$path") => format!("{d}.path"),
(Target::Edges, "$dst_path") => {
format!("(SELECT dd.path FROM docs dd WHERE dd.doc_id = {s}.dst_node)")
}
(Target::Edges, "$dst_uri") => {
format!("(SELECT xn.uri FROM external_nodes xn WHERE xn.node_id = {s}.dst_node)")
}
_ => return None,
};
Some((sql, Ty::Text))
}
pub(crate) const RESERVED_DOC_BASENAMES: [&str; 5] =
["id", "path", "updated_at", "content_hash", "body"];
fn is_seg(s: &str) -> bool {
let mut chars = s.chars();
chars
.next()
.is_some_and(|c| c.is_ascii_alphabetic() || c == '_')
&& chars.all(|c| c.is_ascii_alphanumeric() || c == '_')
}
fn prop_scalar(doc_alias: &str, key: &str) -> Option<String> {
if !is_seg(key) {
return None;
}
Some(format!(
"(SELECT COALESCE(p.val_text, p.val_num, p.val_bool) FROM properties p \
WHERE p.doc_id = {doc_alias}.doc_id AND p.key = '{key}' AND p.card = 'scalar' AND p.deleted_commit IS NULL \
AND (SELECT COUNT(*) FROM properties p2 WHERE p2.doc_id = {doc_alias}.doc_id AND p2.key = '{key}' AND p2.deleted_commit IS NULL) = 1 \
LIMIT 1)"
))
}
fn json_extract(col: &str, segs: &[&str]) -> Option<String> {
if segs.iter().any(|s| !is_seg(s)) {
return None;
}
Some(format!("json_extract({col}, '$.{}')", segs.join(".")))
}
fn member_segments(e: &Expr) -> Option<Vec<&str>> {
match e {
Expr::Ident { name } => Some(vec![name.as_str()]),
Expr::Member { recv, name } => {
let mut base = member_segments(recv)?;
base.push(name.as_str());
Some(base)
}
_ => None,
}
}
pub fn translate_value(e: &Expr, ctx: &TranslateCtx<'_>) -> Option<Frag> {
typed_value(e, ctx).map(|(frag, _)| frag)
}
pub fn typed_value(e: &Expr, ctx: &TranslateCtx<'_>) -> Option<(Frag, Ty)> {
let (s, d, target) = (ctx.self_alias, ctx.doc_alias, ctx.target);
let text = |sql: String| Some((Frag::bare(sql), Ty::Text));
let constant = |v: &Value| {
Some((
Frag {
sql: "?".to_owned(),
params: vec![to_sql(v)],
},
const_ty(v)?,
))
};
match e {
Expr::Lit(v) => constant(v),
Expr::Binding { index } => constant(ctx.params.get(*index).unwrap_or(&Value::Undefined)),
Expr::Ident { name } => {
if name.starts_with('$') {
return intrinsic_sql(name, ctx).map(|(sql, ty)| (Frag::bare(sql), ty));
}
let name = name.as_str();
if non_property_handles(target).contains(&name) {
return None;
}
match target {
Target::Docs => {
if name == "format" {
return text(format!("{s}.format"));
}
if RESERVED_DOC_BASENAMES.contains(&name) {
return None;
}
prop_scalar(d, name).map(|sql| (Frag::bare(sql), Ty::Prop))
}
Target::Blocks => {
if name == "type" || name == "text" {
return text(format!("{s}.{name}"));
}
json_extract(&format!("{s}.attrs"), &[name])
.map(|sql| (Frag::bare(sql), Ty::Json))
}
Target::Nodes => {
if matches!(name, "kind" | "name" | "value") {
return text(format!("{s}.{name}"));
}
json_extract(&format!("{s}.attrs"), &[name])
.map(|sql| (Frag::bare(sql), Ty::Json))
}
Target::Edges => {
if matches!(
name,
"predicate" | "provenance" | "dst_kind" | "anchor" | "src_field"
) {
return text(format!("{s}.{name}"));
}
None
}
}
}
Expr::Member { .. } => {
let segs = member_segments(e)?;
let (head, rest) = segs.split_first()?;
if rest.is_empty() {
return None;
}
if *head == "attrs" && matches!(target, Target::Blocks | Target::Nodes) {
return json_extract(&format!("{s}.attrs"), rest)
.map(|sql| (Frag::bare(sql), Ty::Json));
}
if *head == "doc" {
if rest.len() != 1 {
return None;
}
let k = rest[0];
if k == "$path" {
return text(format!("{d}.path"));
}
if k == "format" {
return text(format!("{d}.format"));
}
if k.starts_with('$')
|| RESERVED_DOC_BASENAMES.contains(&k)
|| non_property_handles(Target::Docs).contains(&k)
{
return None;
}
return prop_scalar(d, k).map(|sql| (Frag::bare(sql), Ty::Prop));
}
if *head == "block"
&& target == Target::Nodes
&& rest.len() == 1
&& matches!(rest[0], "type" | "text")
{
return text(format!(
"(SELECT bb.{} FROM blocks bb WHERE bb.block_id = {s}.block_id)",
rest[0]
));
}
None
}
Expr::Call {
recv: Some(recv),
name,
args,
} if args.is_empty() && (name == "lower" || name == "upper") => {
let recv = translate_value(recv, ctx)?;
Some((
Frag {
sql: format!("{name}({})", recv.sql),
params: recv.params,
},
Ty::Text,
))
}
_ => None,
}
}
fn is_op(op: BinaryOp) -> Option<&'static str> {
Some(match op {
BinaryOp::Eq => "IS",
BinaryOp::Ne => "IS NOT",
BinaryOp::Lt => "<",
BinaryOp::Le => "<=",
BinaryOp::Gt => ">",
BinaryOp::Ge => ">=",
BinaryOp::Add | BinaryOp::Sub | BinaryOp::Mul | BinaryOp::Div | BinaryOp::Mod => {
return None;
}
})
}
pub fn translate_predicate(e: &Expr, ctx: &TranslateCtx<'_>) -> Option<Frag> {
match e {
Expr::Logical {
op: LogicalOp::And,
left,
right,
} => join2(
translate_predicate(left, ctx),
translate_predicate(right, ctx),
"AND",
),
Expr::Binary { op, left, right } => {
let sql_op = is_op(*op)?;
let (l, lt) = typed_value(left, ctx)?;
let (r, rt) = typed_value(right, ctx)?;
if !comparable(*op, lt, rt) {
return None;
}
let op = sql_op;
let mut params = l.params;
params.extend(r.params);
Some(Frag {
sql: format!("({} {op} {})", l.sql, r.sql),
params,
})
}
Expr::Call {
recv: Some(recv),
name,
args,
} if args.len() == 1 => {
let recv = translate_value(recv, ctx)?;
let arg = translate_value(&args[0], ctx)?;
let mut params = recv.params;
let sql = match name.as_str() {
"startsWith" => {
params.extend(arg.params.iter().cloned());
params.extend(arg.params);
format!("(substr({}, 1, length({a})) = {a})", recv.sql, a = arg.sql)
}
"endsWith" => {
params.extend(arg.params.iter().cloned());
params.extend(arg.params);
format!("(substr({}, -length({a})) = {a})", recv.sql, a = arg.sql)
}
"contains" => {
params.extend(arg.params);
format!("(instr({}, {}) > 0)", recv.sql, arg.sql)
}
_ => return None,
};
Some(Frag { sql, params })
}
_ => None,
}
}
fn join2(a: Option<Frag>, b: Option<Frag>, connective: &str) -> Option<Frag> {
let (a, b) = (a?, b?);
let mut params = a.params;
params.extend(b.params);
Some(Frag {
sql: format!("({} {connective} {})", a.sql, b.sql),
params,
})
}
#[cfg(test)]
mod tests {
use super::*;
use oqx::ast::Where;
const DOCS: TranslateCtx<'static> = TranslateCtx {
target: Target::Docs,
self_alias: "d",
doc_alias: "d",
params: &[],
};
fn text(s: &str) -> SqlValue {
SqlValue::Text(s.to_owned())
}
fn frag(sql: &str, params: &[SqlValue]) -> Option<Frag> {
Some(Frag {
sql: sql.to_owned(),
params: params.to_vec(),
})
}
fn pred(src: &str) -> Expr {
let q = oqx::parse_string(&format!("from docs where {src}")).expect("parses");
match q.r#where {
Some(Where::Scalar { expr }) => expr,
other => panic!("expected a single scalar predicate, got {other:?}"),
}
}
fn ident(name: &str) -> Box<Expr> {
Box::new(Expr::Ident {
name: name.to_owned(),
})
}
fn lit(s: &str) -> Box<Expr> {
Box::new(Expr::Lit(Value::from(s)))
}
fn eq(l: Box<Expr>, r: Box<Expr>) -> Box<Expr> {
Box::new(Expr::Binary {
op: BinaryOp::Eq,
left: l,
right: r,
})
}
#[test]
fn equality_is_null_safe_is() {
assert_eq!(
translate_predicate(&pred("$path == \"index.md\""), &DOCS),
frag("(d.path IS ?)", &[text("index.md")])
);
}
#[test]
fn inequality_is_null_safe_is_not() {
assert_eq!(
translate_predicate(&pred("$path != \"x\""), &DOCS),
frag("(d.path IS NOT ?)", &[text("x")])
);
}
#[test]
fn intrinsic_column_mapping() {
assert_eq!(
translate_predicate(&pred("$id == \"d_1\""), &DOCS),
frag("(d.doc_id IS ?)", &[text("d_1")])
);
}
#[test]
fn relational_ops_are_plain_comparisons() {
assert_eq!(
translate_predicate(&pred("$path < \"m\""), &DOCS),
frag("(d.path < ?)", &[text("m")])
);
assert_eq!(
translate_predicate(&pred("$path >= \"m\""), &DOCS),
frag("(d.path >= ?)", &[text("m")])
);
assert_eq!(translate_predicate(&pred("$path + 1"), &DOCS), None);
}
#[test]
fn starts_with_is_substr_equality() {
assert_eq!(
translate_predicate(&pred("$path.startsWith(\"lab/\")"), &DOCS),
frag(
"(substr(d.path, 1, length(?)) = ?)",
&[text("lab/"), text("lab/")]
)
);
}
#[test]
fn lower_then_starts_with_pushes_with_explicit_lower() {
assert_eq!(
translate_predicate(&pred("$path.lower().startsWith(\"lab/\")"), &DOCS),
frag(
"(substr(lower(d.path), 1, length(?)) = ?)",
&[text("lab/"), text("lab/")]
)
);
}
#[test]
fn contains_is_instr() {
assert_eq!(
translate_predicate(&pred("$path.contains(\"notes\")"), &DOCS),
frag("(instr(d.path, ?) > 0)", &[text("notes")])
);
}
#[test]
fn ends_with_is_negative_substr_equality() {
assert_eq!(
translate_predicate(&pred("$path.endsWith(\".md\")"), &DOCS),
frag(
"(substr(d.path, -length(?)) = ?)",
&[text(".md"), text(".md")]
)
);
}
#[test]
fn upper_wraps_the_receiver_in_value_position() {
assert_eq!(
translate_value(&pred("$path.upper()"), &DOCS),
frag("upper(d.path)", &[])
);
}
#[test]
fn bare_doc_property_is_the_scalar_in_scope_subquery() {
let f = translate_predicate(&pred("layer == \"canon\""), &DOCS).expect("pushable");
assert!(f.sql.contains("FROM properties p"), "{}", f.sql);
assert!(f.sql.contains("p.key = 'layer'"), "{}", f.sql);
assert!(f.sql.contains("p.card = 'scalar'"), "{}", f.sql);
assert!(
f.sql.starts_with('(') && f.sql.contains(" IS ?)"),
"{}",
f.sql
);
assert_eq!(f.params, vec![text("canon")]);
}
#[test]
fn updated_at_pushes_as_its_revisions_subquery() {
let f =
translate_predicate(&pred("$updated_at >= \"2026-01-01\""), &DOCS).expect("pushable");
assert!(
f.sql.contains("FROM revisions r JOIN commits c"),
"{}",
f.sql
);
}
#[test]
fn format_is_a_column_not_a_property() {
assert_eq!(
translate_predicate(&pred("format == \"markdown\""), &DOCS),
frag("(d.format IS ?)", &[text("markdown")])
);
}
#[test]
fn booleans_bind_as_one_and_zero() {
let blocks = TranslateCtx {
target: Target::Blocks,
self_alias: "b",
..DOCS
};
assert_eq!(
translate_predicate(&pred("type == true"), &blocks).map(|f| f.params),
Some(vec![SqlValue::Integer(1)])
);
assert_eq!(
translate_predicate(&pred("type == false"), &blocks).map(|f| f.params),
Some(vec![SqlValue::Integer(0)])
);
assert_eq!(
translate_predicate(&pred("$ordinal < 1000"), &blocks).map(|f| f.params),
Some(vec![SqlValue::Real(1000.0)])
);
assert_eq!(
translate_predicate(&pred("$path == null"), &DOCS).map(|f| f.params),
Some(vec![SqlValue::Null])
);
}
const REPRESENTATIVES: [(Ty, &str); 7] = [
(Ty::Text, "$path"),
(Ty::Int, "$ordinal"),
(Ty::Num, "1"),
(Ty::Bool, "true"),
(Ty::Null, "null"),
(Ty::Json, "checked"),
(Ty::Prop, "doc.layer"),
];
#[test]
fn representatives_carry_their_type() {
let blocks = TranslateCtx {
target: Target::Blocks,
self_alias: "b",
..DOCS
};
for (ty, src) in REPRESENTATIVES {
let (_, got) = typed_value(&pred(src), &blocks).expect(src);
assert_eq!(got, ty, "{src}");
}
assert_eq!(
typed_value(&pred("attrs.a.b"), &blocks).map(|(_, t)| t),
Some(Ty::Json)
);
assert_eq!(
typed_value(&pred("$depth"), &blocks).map(|(_, t)| t),
Some(Ty::Int)
);
assert_eq!(
typed_value(&pred("type.lower()"), &blocks).map(|(_, t)| t),
Some(Ty::Text)
);
assert_eq!(
typed_value(&pred("checked.upper()"), &blocks).map(|(_, t)| t),
Some(Ty::Text)
);
assert_eq!(
typed_value(&pred("layer"), &DOCS).map(|(_, t)| t),
Some(Ty::Prop)
);
assert_eq!(
typed_value(&pred("format"), &DOCS).map(|(_, t)| t),
Some(Ty::Text)
);
}
#[test]
fn the_comparison_matrix_decides_every_cell() {
const P: bool = true;
const D: bool = false;
#[rustfmt::skip]
const EQUALITY: [[bool; 7]; 7] = [
[P, P, P, P, P, P, P],
[P, P, P, D, P, D, D],
[P, P, P, D, P, D, D],
[P, D, D, D, P, D, D],
[P, P, P, P, P, P, D],
[P, D, D, D, P, D, D],
[P, D, D, D, D, D, D],
];
#[rustfmt::skip]
const RELATIONAL: [[bool; 7]; 7] = [
[P, D, D, D, D, D, D],
[D, P, P, D, D, D, D],
[D, P, P, D, D, D, D],
[D, D, D, D, D, D, D],
[D, D, D, D, D, D, D],
[D, D, D, D, D, D, D],
[D, D, D, D, D, D, D],
];
let blocks = TranslateCtx {
target: Target::Blocks,
self_alias: "b",
..DOCS
};
let ops = [
(BinaryOp::Eq, "==", &EQUALITY),
(BinaryOp::Ne, "!=", &EQUALITY),
(BinaryOp::Lt, "<", &RELATIONAL),
(BinaryOp::Le, "<=", &RELATIONAL),
(BinaryOp::Gt, ">", &RELATIONAL),
(BinaryOp::Ge, ">=", &RELATIONAL),
];
for (i, (a, l)) in REPRESENTATIVES.iter().enumerate() {
for (j, (b, r)) in REPRESENTATIVES.iter().enumerate() {
for (op, spelled, matrix) in ops {
let want = matrix[i][j];
assert_eq!(matrix[j][i], want, "the matrix is symmetric ({a:?}, {b:?})");
assert_eq!(
comparable(op, *a, *b),
want,
"comparable({spelled}, {a:?}, {b:?})"
);
let src = format!("{l} {spelled} {r}");
assert_eq!(
translate_predicate(&pred(&src), &blocks).is_some(),
want,
"{src}"
);
}
}
}
}
#[test]
fn the_spec_shapes_of_decline_a() {
let blocks = TranslateCtx {
target: Target::Blocks,
self_alias: "b",
..DOCS
};
assert_eq!(translate_predicate(&pred("checked == 1"), &blocks), None);
assert_eq!(translate_predicate(&pred("checked == true"), &blocks), None);
assert_eq!(
translate_predicate(&pred("attrs.checked == true"), &blocks),
None
);
assert_eq!(translate_predicate(&pred("verified == 1"), &DOCS), None);
assert_eq!(translate_predicate(&pred("verified == true"), &DOCS), None);
assert_eq!(translate_predicate(&pred("era < 1000"), &DOCS), None);
assert_eq!(translate_predicate(&pred("doc.era < 1000"), &blocks), None);
assert_eq!(
translate_predicate(&pred("$ordinal == true"), &blocks),
None
);
assert_eq!(
translate_predicate(&pred("$ordinal == 1"), &blocks),
frag("(b.ordinal IS ?)", &[SqlValue::Real(1.0)])
);
assert_eq!(translate_predicate(&pred("tags != null"), &DOCS), None);
assert_eq!(translate_predicate(&pred("tags == null"), &DOCS), None);
assert_eq!(
translate_predicate(&pred("doc.tags == null"), &blocks),
None
);
assert_eq!(
translate_predicate(&pred("checked == null"), &blocks),
frag(
"(json_extract(b.attrs, '$.checked') IS ?)",
&[SqlValue::Null]
)
);
assert_eq!(
translate_predicate(&pred("$ordinal != null"), &blocks),
frag("(b.ordinal IS NOT ?)", &[SqlValue::Null])
);
assert_eq!(
translate_predicate(&pred("checked == \"x\""), &blocks),
frag("(json_extract(b.attrs, '$.checked') IS ?)", &[text("x")])
);
assert!(translate_predicate(&pred("layer == \"canon\""), &DOCS).is_some());
assert!(translate_predicate(&pred("$ordinal == \"1\""), &blocks).is_some());
assert!(translate_predicate(&pred("$ordinal != \"1\""), &blocks).is_some());
assert_eq!(
translate_predicate(&pred("$ordinal < \"3\""), &blocks),
None
);
assert_eq!(
translate_predicate(&pred("\"3\" >= $ordinal"), &blocks),
None
);
assert_eq!(translate_predicate(&pred("$path > 5"), &DOCS), None);
assert_eq!(translate_predicate(&pred("type <= 1"), &blocks), None);
assert_eq!(
translate_predicate(&pred("$ordinal < 3"), &blocks),
frag("(b.ordinal < ?)", &[SqlValue::Real(3.0)])
);
assert_eq!(
translate_predicate(&pred("$path > \"m\""), &DOCS),
frag("(d.path > ?)", &[text("m")])
);
assert_eq!(
translate_predicate(&pred("$ordinal == checked"), &blocks),
None
);
assert_eq!(
translate_predicate(&pred("checked == level"), &blocks),
None
);
assert_eq!(
translate_predicate(&pred("doc.era == doc.year"), &blocks),
None
);
assert_eq!(translate_predicate(&pred("level < \"x\""), &blocks), None);
assert_eq!(translate_predicate(&pred("level < 3"), &blocks), None);
assert_eq!(translate_predicate(&pred("true == false"), &blocks), None);
assert_eq!(translate_predicate(&pred("$ordinal > null"), &blocks), None);
assert!(translate_predicate(&pred("$ordinal == $depth"), &blocks).is_some());
assert!(translate_predicate(&pred("$ordinal <= $depth"), &blocks).is_some());
assert!(translate_predicate(&pred("type == null"), &blocks).is_some());
}
#[test]
fn bindings_are_typed_by_their_value() {
let blocks = TranslateCtx {
target: Target::Blocks,
self_alias: "b",
..DOCS
};
let params = [
Value::from("s"),
Value::from(1.0),
Value::Bool(true),
Value::Null,
Value::Array(vec![]),
];
let ctx = TranslateCtx {
params: ¶ms,
..blocks
};
let against = |i: usize, rhs: &str| {
let e = Expr::Binary {
op: BinaryOp::Eq,
left: Box::new(Expr::Binding { index: i }),
right: Box::new(pred(rhs)),
};
translate_predicate(&e, &ctx).is_some()
};
assert!(against(0, "checked"));
assert!(!against(1, "checked"));
assert!(!against(2, "checked"));
assert!(!against(2, "$ordinal"));
assert!(against(1, "$ordinal"));
assert!(against(3, "checked"));
assert!(!against(3, "doc.tags"));
assert!(against(9, "checked"));
assert!(!against(9, "doc.tags"));
assert!(!against(4, "type"));
}
#[test]
fn relation_and_handle_names_are_not_property_reads() {
let blocks = TranslateCtx {
target: Target::Blocks,
self_alias: "b",
..DOCS
};
let nodes = TranslateCtx {
target: Target::Nodes,
self_alias: "n",
..DOCS
};
let edges = TranslateCtx {
target: Target::Edges,
self_alias: "e",
..DOCS
};
for (ctx, target) in [
(&DOCS, Target::Docs),
(&blocks, Target::Blocks),
(&nodes, Target::Nodes),
(&edges, Target::Edges),
] {
for name in non_property_handles(target) {
let src = format!("{name} == null");
assert_eq!(
translate_predicate(&pred(&src), ctx),
None,
"{target:?}: {src}"
);
let src = format!("{name} == \"x\"");
assert_eq!(
translate_predicate(&pred(&src), ctx),
None,
"{target:?}: {src}"
);
}
}
assert_eq!(translate_predicate(&pred("nodes == null"), &DOCS), None);
assert_eq!(
translate_predicate(&pred("frontmatter == null"), &DOCS),
None
);
assert_eq!(translate_predicate(&pred("nodes == null"), &blocks), None);
assert_eq!(translate_predicate(&pred("attrs == null"), &blocks), None);
assert_eq!(
translate_predicate(&pred("doc.nodes == null"), &blocks),
None
);
assert_eq!(
translate_predicate(&pred("doc.frontmatter == null"), &blocks),
None
);
assert_eq!(translate_predicate(&pred("doc.doc == null"), &DOCS), None);
assert!(translate_predicate(&pred("doc.layer == \"canon\""), &blocks).is_some());
assert!(translate_predicate(&pred("section == \"x\""), &DOCS).is_some());
assert!(translate_predicate(&pred("frontmatter == \"x\""), &blocks).is_some());
}
#[test]
fn handle_sets_match_the_store_context() {
use std::collections::HashMap;
use omgbase_reconcile::Config;
use omgbase_store::{BatchItem, Store};
use oqx::DataContext;
use crate::context::StoreContext;
let mut store = Store::open_in_memory().expect("store");
let repo = store.create_repo("handles").expect("repo");
let items = [
BatchItem::observed(
"a.md",
"---\ntitle: A\nlayer: canon\nverified: true\n---\n# Heading\n\nSee [b](b.md) and [[b]].\n\n- [ ] task\n - nested\n\nkey:: value\n\n## Sub\n\ntext\n",
),
BatchItem::observed("b.md", "# B\n\nBack to [a](a.md).\n"),
];
store
.observe_batch(
&repo,
&items,
"2026-09-26T00:00:00.000Z",
&Config::default(),
)
.expect("observe");
let ctx = StoreContext::new(store.conn(), &repo, HashMap::new());
let mut universe: Vec<&str> = [Target::Docs, Target::Blocks, Target::Nodes, Target::Edges]
.into_iter()
.flat_map(|t| non_property_handles(t).iter().copied())
.collect();
universe.extend([
"layer",
"title",
"verified",
"checked",
"level",
"format",
"type",
"text",
"kind",
"name",
"value",
"predicate",
"key",
"nope",
]);
universe.sort_unstable();
universe.dedup();
let is_shape = |v: &Value| matches!(v, Value::Array(_) | Value::Object(_));
for target in [Target::Docs, Target::Blocks, Target::Nodes, Target::Edges] {
let Value::Array(rows) = ctx.root(target.as_str()) else {
panic!("{target:?} root is not an array");
};
assert!(!rows.is_empty(), "{target:?} has rows");
let set = non_property_handles(target);
for name in &universe {
let mut shaped = 0;
for row in &rows {
let v = ctx.get(row, name).expect("get");
if set.contains(name) {
assert!(
v.is_absent() || is_shape(&v),
"{target:?}.{name} read as a scalar: {v:?}"
);
} else {
assert!(!is_shape(&v), "{target:?}.{name} is a handle: {v:?}");
}
shaped += usize::from(is_shape(&v));
}
if set.contains(name) {
assert!(
shaped > 0,
"{target:?}.{name} never resolved to rows or a bag"
);
}
}
}
}
#[test]
fn reserved_bare_basename_is_not_pushed() {
assert_eq!(translate_predicate(&pred("path == \"x\""), &DOCS), None);
assert_eq!(translate_predicate(&pred("body == \"x\""), &DOCS), None);
assert_eq!(translate_predicate(&pred("doc.path == \"x\""), &DOCS), None);
}
#[test]
fn docs_body_and_computed_intrinsics_are_not_columns() {
assert_eq!(translate_predicate(&pred("$body == \"x\""), &DOCS), None);
assert_eq!(translate_predicate(&pred("$title == \"x\""), &DOCS), None);
assert_eq!(translate_predicate(&pred("$tags == \"x\""), &DOCS), None);
}
#[test]
fn matches_needs_a_regexp_udf() {
assert_eq!(
translate_predicate(&pred("$path.matches(\"^lab/\")"), &DOCS),
None
);
}
#[test]
fn negation_as_a_nested_expr_is_not_and_safe() {
let e = Expr::Unary {
op: oqx::ast::UnaryOp::Not,
expr: ident("$path"),
};
assert_eq!(translate_predicate(&e, &DOCS), None);
}
#[test]
fn disjunction_as_a_nested_expr_is_declined() {
let e = Expr::Logical {
op: LogicalOp::Or,
left: eq(ident("$path"), lit("a")),
right: eq(ident("$path"), lit("b")),
};
assert_eq!(translate_predicate(&e, &DOCS), None);
}
#[test]
fn unmapped_node_intrinsic_is_not_pushed() {
let nodes = TranslateCtx {
target: Target::Nodes,
self_alias: "n",
..DOCS
};
assert_eq!(
translate_predicate(&pred("$locator == \"x\""), &nodes),
None
);
let blocks = TranslateCtx {
target: Target::Blocks,
self_alias: "b",
..DOCS
};
assert_eq!(
translate_predicate(&pred("$updated_at == \"x\""), &blocks),
None
);
}
#[test]
fn range_membership_in_and_bare_idents_are_declined() {
assert_eq!(translate_predicate(&pred("era in 800..1680"), &DOCS), None);
assert_eq!(
translate_predicate(&pred("\"a\" in list(tags)"), &DOCS),
None
);
assert_eq!(translate_predicate(&pred("verified"), &DOCS), None);
assert_eq!(translate_predicate(&pred("doc.verified"), &DOCS), None);
assert_eq!(translate_predicate(&pred("size(tags) > 1"), &DOCS), None);
assert_eq!(translate_predicate(&pred("$self.text(\"x\")"), &DOCS), None);
assert_eq!(translate_predicate(&pred("$value == \"x\""), &DOCS), None);
assert_eq!(translate_predicate(&pred("^slug == \"x\""), &DOCS), None);
assert_eq!(
translate_predicate(&pred("frontmatter.era == 1"), &DOCS),
None
);
}
#[test]
fn and_composes_two_pushable_comparisons() {
let e = Expr::Logical {
op: LogicalOp::And,
left: eq(ident("$path"), lit("a")),
right: Box::new(Expr::Binary {
op: BinaryOp::Ne,
left: ident("$id"),
right: lit("d_2"),
}),
};
assert_eq!(
translate_predicate(&e, &DOCS),
frag(
"((d.path IS ?) AND (d.doc_id IS NOT ?))",
&[text("a"), text("d_2")]
)
);
}
#[test]
fn and_declines_wholesale_if_either_side_is_not_pushable() {
let e = Expr::Logical {
op: LogicalOp::And,
left: eq(ident("$path"), lit("a")),
right: eq(ident("$body"), lit("x")),
};
assert_eq!(translate_predicate(&e, &DOCS), None);
}
#[test]
fn resolves_a_binding_to_its_param_value() {
let e = eq(ident("$path"), Box::new(Expr::Binding { index: 0 }));
let params = [Value::from("from-binding.md")];
let ctx = TranslateCtx {
params: ¶ms,
..DOCS
};
assert_eq!(
translate_predicate(&e, &ctx),
frag("(d.path IS ?)", &[text("from-binding.md")])
);
assert_eq!(
translate_predicate(&e, &DOCS),
frag("(d.path IS ?)", &[SqlValue::Null])
);
}
#[test]
fn blocks_and_nodes_flatten_bare_identifiers_into_attrs() {
let blocks = TranslateCtx {
target: Target::Blocks,
self_alias: "b",
..DOCS
};
let both = Expr::Logical {
op: LogicalOp::And,
left: eq(ident("type"), lit("task")),
right: eq(ident("marker"), lit("x")),
};
assert_eq!(
translate_predicate(&both, &blocks),
frag(
"((b.type IS ?) AND (json_extract(b.attrs, '$.marker') IS ?))",
&[text("task"), text("x")]
)
);
assert_eq!(
translate_predicate(&pred("attrs.marker == \"x\""), &blocks),
frag("(json_extract(b.attrs, '$.marker') IS ?)", &[text("x")])
);
assert_eq!(
translate_predicate(&pred("attrs.checked == true"), &blocks),
None
);
let nodes = TranslateCtx {
target: Target::Nodes,
self_alias: "n",
..DOCS
};
assert_eq!(
translate_predicate(&pred("kind == \"md:section\""), &nodes),
frag("(n.kind IS ?)", &[text("md:section")])
);
assert_eq!(
translate_predicate(&pred("level == \"1\""), &nodes),
frag("(json_extract(n.attrs, '$.level') IS ?)", &[text("1")])
);
assert_eq!(translate_predicate(&pred("level == 1"), &nodes), None);
assert_eq!(
translate_predicate(&pred("attrs.a.b == \"c\""), &nodes),
frag("(json_extract(n.attrs, '$.a.b') IS ?)", &[text("c")])
);
assert_eq!(
translate_predicate(&pred("attrs.marker == \"x\""), &DOCS),
None
);
}
#[test]
fn doc_and_block_reach_through() {
let blocks = TranslateCtx {
target: Target::Blocks,
self_alias: "b",
..DOCS
};
let f = translate_predicate(&pred("doc.type == \"lab-note\""), &blocks).expect("pushable");
assert!(
f.sql.contains("p.doc_id = d.doc_id AND p.key = 'type'"),
"{}",
f.sql
);
assert_eq!(
translate_predicate(&pred("doc.$path == \"a.md\""), &blocks),
frag("(d.path IS ?)", &[text("a.md")])
);
assert_eq!(
translate_predicate(&pred("doc.format == \"markdown\""), &blocks),
frag("(d.format IS ?)", &[text("markdown")])
);
assert_eq!(
translate_predicate(&pred("doc.$id == \"d_1\""), &blocks),
None
);
assert_eq!(translate_predicate(&pred("doc.a.b == 1"), &blocks), None);
let nodes = TranslateCtx {
target: Target::Nodes,
self_alias: "n",
..DOCS
};
assert_eq!(
translate_predicate(&pred("block.type == \"task\""), &nodes),
frag(
"((SELECT bb.type FROM blocks bb WHERE bb.block_id = n.block_id) IS ?)",
&[text("task")]
)
);
assert_eq!(
translate_predicate(&pred("block.type == \"task\""), &blocks),
None
);
assert_eq!(
translate_predicate(&pred("section.level == 1"), &nodes),
None
);
}
#[test]
fn edges_push_their_five_fields_and_intrinsics() {
let edges = TranslateCtx {
target: Target::Edges,
self_alias: "e",
..DOCS
};
assert_eq!(
translate_predicate(&pred("predicate == \"references\""), &edges),
frag("(e.predicate IS ?)", &[text("references")])
);
assert_eq!(
translate_predicate(&pred("$dst_path == \"index.md\""), &edges),
frag(
"((SELECT dd.path FROM docs dd WHERE dd.doc_id = e.dst_node) IS ?)",
&[text("index.md")]
)
);
assert_eq!(translate_predicate(&pred("weight == 1"), &edges), None);
}
#[test]
fn unsafe_identifier_segments_are_never_inlined() {
assert!(is_seg("layer") && is_seg("_x9"));
assert!(!is_seg("") && !is_seg("9a") && !is_seg("a-b") && !is_seg("a'b"));
assert_eq!(json_extract("n.attrs", &["ok", "no-pe"]), None);
assert_eq!(prop_scalar("d", "x'y"), None);
}
}