1mod binding;
10mod calls;
11mod constants;
12mod description;
13mod fusion;
14mod graph;
15mod ir;
16mod joins;
17mod predicates;
18#[cfg(test)]
19mod tests;
20use crate::semantics::graph_functions::{
21 default_graph_name as default_operator_graph, GraphNameCatalog,
22};
23use crate::{ast::BinaryOp, SQLError, SQLParam, ScalarExpr};
24pub use binding::{lower_sql_function_bound, lower_where_bound};
25pub use description::{describe_vector_call, VectorCallDescription};
26pub use ir::{AttentionSpec, MultiStageEntry, RetrievalExpr, TextScoringMode};
27pub use joins::lower_operator_join_table_function;
28use std::collections::BTreeSet;
29use uqa_core::{
30 retrieval::{Direction as DeepGraphDirection, ExternalPriorMode, GatingSpec, MultiStageCutoff},
31 Predicate, Value,
32};
33type BindingResult<T> = Result<T, SQLError>;
34
35pub type ConstantEvaluator<'a> = dyn Fn(&ScalarExpr, &[SQLParam]) -> Result<Value, SQLError> + 'a;
37pub struct RetrievalConstants<'a> {
38 pub params: &'a [SQLParam],
39 pub evaluate: &'a ConstantEvaluator<'a>,
40 pub stores: &'a dyn Fn(&str) -> bool,
42}
43impl RetrievalConstants<'_> {
44 fn without_parameters(&self) -> RetrievalConstants<'_> {
45 RetrievalConstants {
46 params: &[],
47 evaluate: self.evaluate,
48 stores: self.stores,
49 }
50 }
51}
52
53#[must_use]
55pub fn names_engine_pseudo_column(expression: &ScalarExpr) -> bool {
56 let mut names = false;
57 expression.visit(&mut |node| {
58 if let ScalarExpr::Column(column) | ScalarExpr::QualifiedColumn { column, .. } = node {
59 names |= crate::semantics::is_engine_pseudo_column(column);
60 }
61 });
62 names
63}
64pub trait RetrievalArguments: GraphNameCatalog {
66 fn evaluate_argument(
67 &self,
68 expression: &ScalarExpr,
69 params: &[SQLParam],
70 ) -> Result<Value, SQLError>;
71}
72
73use calls::{
74 bind_operator_argument, checked_retrieval_call_tree_present, lower_bayesian_match_with_prior,
75 lower_calibrated_vector_match, lower_multi_field_match, lower_operator_arg, lower_signal_arg,
76 lower_staged_retrieval, try_lower_fts_match, try_lower_knn_match, try_lower_text_match,
77 validate_checked_retrieval_call_tree, validate_operator_function_arity,
78 validate_probability_signal_contract,
79};
80use constants::{
81 const_bool, const_f64, const_f64_vector, const_gating, const_optional_string, const_string,
82 const_temporal_bound, const_usize, const_value, const_vector, named_arg_expr,
83};
84use fusion::{
85 lower_bayesian_evidence_fusion, lower_learned_fusion, lower_positive_evidence_pool,
86 try_lower_attention_fusion,
87};
88use graph::lower_graph_function;
89use predicates::{column_name, lower_comparison, lower_document_boolean, lower_function};
90
91enum OptionalStringConstant {
92 Null,
93 Value(String),
94}
95impl OptionalStringConstant {
96 fn into_option(self) -> Option<String> {
97 match self {
98 Self::Null => None,
99 Self::Value(value) => Some(value),
100 }
101 }
102}
103
104pub fn lower_where(expr: &ScalarExpr, constants: &RetrievalConstants<'_>) -> Option<RetrievalExpr> {
106 if let Some(membership) = crate::semantics::membership_operands(expr) {
107 return lower_membership(
108 membership.value,
109 membership.items,
110 membership.negated,
111 constants,
112 );
113 }
114 match expr {
115 ScalarExpr::And(parts) => {
116 if let Some(range) = predicates::lower_range(parts, constants) {
117 return Some(range);
118 }
119 let mut out: Vec<RetrievalExpr> = Vec::with_capacity(parts.len());
120 for p in parts {
121 out.push(lower_where(p, constants)?);
122 }
123 Some(lower_document_boolean(out, false))
124 }
125 ScalarExpr::Or(parts) => {
126 let mut out: Vec<RetrievalExpr> = Vec::with_capacity(parts.len());
127 for p in parts {
128 out.push(lower_where(p, constants)?);
129 }
130 Some(lower_document_boolean(out, true))
131 }
132 ScalarExpr::Not(inner) if crate::semantics::expr_is_null_free(inner) => Some(
139 RetrievalExpr::Complement(Box::new(lower_where(inner, constants)?)),
140 ),
141 ScalarExpr::Func { name, args, .. } => lower_function(name, args, constants),
142 ScalarExpr::Binary { op, lhs, rhs } => lower_comparison(*op, lhs, rhs, constants),
143 ScalarExpr::IsNull { expr, negated } => {
144 let field = predicates::filter_field(expr, constants)?;
145 let predicate = if *negated {
146 Predicate::IsNotNull
147 } else {
148 Predicate::IsNull
149 };
150 Some(RetrievalExpr::Filter {
151 field,
152 predicate,
153 source: None,
154 })
155 }
156 ScalarExpr::Between { expr, low, high } => {
157 let field = predicates::filter_field(expr, constants)?;
158 let lo = const_value(low, constants)?;
159 let hi = const_value(high, constants)?;
160 Some(RetrievalExpr::Filter {
161 field,
162 predicate: Predicate::Between { low: lo, high: hi },
163 source: None,
164 })
165 }
166 _ => None,
167 }
168}
169
170fn lower_membership(
171 expr: &ScalarExpr,
172 list: crate::semantics::MembershipItems<'_>,
173 negated: bool,
174 constants: &RetrievalConstants<'_>,
175) -> Option<RetrievalExpr> {
176 let field = predicates::filter_field(expr, constants)?;
177 let mut set: BTreeSet<Value> = BTreeSet::new();
178 let mut has_null = false;
179 for item in list.iter() {
180 let value = match item {
181 crate::semantics::MembershipItem::Expression(expression) => {
182 const_value(expression, constants)?
183 }
184 crate::semantics::MembershipItem::Constant(value) => value.clone(),
185 };
186 if matches!(value, Value::Array(_)) {
187 return None;
188 }
189 if matches!(value, Value::Null) {
190 has_null = true;
191 continue;
192 }
193 set.insert(value);
194 }
195 if negated {
196 if has_null {
200 return Some(RetrievalExpr::Empty);
201 }
202 let filter = RetrievalExpr::Filter {
203 field: field.clone(),
204 predicate: Predicate::InSet(set),
205 source: None,
206 };
207 let not_null = RetrievalExpr::Filter {
208 field,
209 predicate: Predicate::IsNotNull,
210 source: None,
211 };
212 return Some(RetrievalExpr::Intersect(vec![
213 RetrievalExpr::Complement(Box::new(filter)),
214 not_null,
215 ]));
216 }
217 Some(RetrievalExpr::Filter {
218 field,
219 predicate: Predicate::InSet(set),
220 source: None,
221 })
222}