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