Skip to main content

uqa_sql/retrieval/
mod.rs

1//
2// Unified Query Algebra
3//
4// Copyright (c) 2023-2026 Cognica, Inc.
5//
6
7//! SQL retrieval argument binding and lowering to runtime-independent expressions.
8
9mod binding;
10mod calls;
11mod constants;
12mod description;
13mod fusion;
14mod graph;
15mod ir;
16mod joins;
17mod predicates;
18use crate::semantics::graph_functions::{
19    default_graph_name as default_operator_graph, GraphNameCatalog,
20};
21use crate::{ast::BinaryOp, SQLError, SQLParam, ScalarExpr};
22pub use binding::{lower_sql_function_bound, lower_where_bound};
23pub use description::{describe_vector_call, VectorCallDescription};
24pub use ir::{AttentionSpec, MultiStageEntry, RetrievalExpr, TextScoringMode};
25pub use joins::lower_operator_join_table_function;
26use std::collections::BTreeSet;
27use uqa_core::{
28    retrieval::{Direction as DeepGraphDirection, ExternalPriorMode, GatingSpec, MultiStageCutoff},
29    Predicate, Value,
30};
31type BindingResult<T> = Result<T, SQLError>;
32
33/// Evaluates a scalar with the caller's parameter values and without runtime function hooks.
34pub type ConstantEvaluator<'a> = dyn Fn(&ScalarExpr, &[SQLParam]) -> Result<Value, SQLError> + 'a;
35pub struct RetrievalConstants<'a> {
36    pub params: &'a [SQLParam],
37    pub evaluate: &'a ConstantEvaluator<'a>,
38}
39impl RetrievalConstants<'_> {
40    fn without_parameters(&self) -> RetrievalConstants<'_> {
41        RetrievalConstants {
42            params: &[],
43            evaluate: self.evaluate,
44        }
45    }
46}
47/// Runtime scalar evaluation and graph catalog access; SQL owns recursion and validation order.
48pub trait RetrievalArguments: GraphNameCatalog {
49    fn evaluate_argument(
50        &self,
51        expression: &ScalarExpr,
52        params: &[SQLParam],
53    ) -> Result<Value, SQLError>;
54}
55
56use calls::{
57    bind_operator_argument, checked_retrieval_call_tree_present, lower_bayesian_match_with_prior,
58    lower_calibrated_vector_match, lower_multi_field_match, lower_operator_arg, lower_signal_arg,
59    lower_staged_retrieval, try_lower_fts_match, try_lower_knn_match, try_lower_text_match,
60    validate_checked_retrieval_call_tree, validate_operator_function_arity,
61    validate_probability_signal_contract,
62};
63use constants::{
64    const_bool, const_f64, const_f64_vector, const_gating, const_optional_string, const_string,
65    const_temporal_bound, const_usize, const_value, const_vector, named_arg_expr,
66};
67use fusion::{
68    lower_bayesian_evidence_fusion, lower_learned_fusion, lower_positive_evidence_pool,
69    try_lower_attention_fusion,
70};
71use graph::lower_graph_function;
72use predicates::{column_name, lower_comparison, lower_document_boolean, lower_function};
73
74enum OptionalStringConstant {
75    Null,
76    Value(String),
77}
78impl OptionalStringConstant {
79    fn into_option(self) -> Option<String> {
80        match self {
81            Self::Null => None,
82            Self::Value(value) => Some(value),
83        }
84    }
85}
86
87/// Lower representable SQL predicates using the supplied constant evaluator. Unsupported scalar shapes remain relational predicates.
88pub fn lower_where(expr: &ScalarExpr, constants: &RetrievalConstants<'_>) -> Option<RetrievalExpr> {
89    match expr {
90        ScalarExpr::And(parts) => {
91            let mut out: Vec<RetrievalExpr> = Vec::with_capacity(parts.len());
92            for p in parts {
93                out.push(lower_where(p, constants)?);
94            }
95            Some(lower_document_boolean(out, false))
96        }
97        ScalarExpr::Or(parts) => {
98            let mut out: Vec<RetrievalExpr> = Vec::with_capacity(parts.len());
99            for p in parts {
100                out.push(lower_where(p, constants)?);
101            }
102            Some(lower_document_boolean(out, true))
103        }
104        // Complement is only sound when the inner predicate cannot be
105        // NULL for any row (search functions, IS NULL tests). Column
106        // comparisons under NOT fall through to the wildcard `None`
107        // and keep three-valued semantics through the row-evaluator
108        // relational evaluation: `NOT (col = 5)` must not match rows whose `col`
109        // is NULL.
110        ScalarExpr::Not(inner) if crate::semantics::expr_is_null_free(inner) => Some(
111            RetrievalExpr::Complement(Box::new(lower_where(inner, constants)?)),
112        ),
113        ScalarExpr::Func { name, args, .. } => lower_function(name, args, constants),
114        ScalarExpr::Binary { op, lhs, rhs } => lower_comparison(*op, lhs, rhs, constants),
115        ScalarExpr::IsNull { expr, negated } => {
116            let field = column_name(expr)?;
117            let predicate = if *negated {
118                Predicate::IsNotNull
119            } else {
120                Predicate::IsNull
121            };
122            Some(RetrievalExpr::Filter {
123                field,
124                predicate,
125                source: None,
126            })
127        }
128        ScalarExpr::Between { expr, low, high } => {
129            let field = column_name(expr)?;
130            let lo = const_value(low, constants)?;
131            let hi = const_value(high, constants)?;
132            Some(RetrievalExpr::Filter {
133                field,
134                predicate: Predicate::Between { low: lo, high: hi },
135                source: None,
136            })
137        }
138        ScalarExpr::InList {
139            expr,
140            list,
141            negated,
142        } => {
143            let field = column_name(expr)?;
144            let mut set: BTreeSet<Value> = BTreeSet::new();
145            let mut has_null = false;
146            for v in list {
147                let value = const_value(v, constants)?;
148                if matches!(value, Value::Null) {
149                    has_null = true;
150                    continue;
151                }
152                set.insert(value);
153            }
154            if *negated {
155                // `col NOT IN (...)`: a NULL in the list means no row
156                // can ever satisfy it; otherwise complement the match
157                // set but keep NULL rows excluded (three-valued NOT).
158                if has_null {
159                    return Some(RetrievalExpr::Empty);
160                }
161                let filter = RetrievalExpr::Filter {
162                    field: field.clone(),
163                    predicate: Predicate::InSet(set),
164                    source: None,
165                };
166                let not_null = RetrievalExpr::Filter {
167                    field,
168                    predicate: Predicate::IsNotNull,
169                    source: None,
170                };
171                return Some(RetrievalExpr::Intersect(vec![
172                    RetrievalExpr::Complement(Box::new(filter)),
173                    not_null,
174                ]));
175            }
176            Some(RetrievalExpr::Filter {
177                field,
178                predicate: Predicate::InSet(set),
179                source: None,
180            })
181        }
182        _ => None,
183    }
184}