uqa_sql/retrieval/
joins.rs1use super::{
10 const_f64, const_optional_string, const_string, lower_where_bound, BindingResult,
11 RetrievalArguments, RetrievalConstants, RetrievalExpr, SQLError, ScalarExpr,
12};
13
14fn lower_join_operand(
15 source: &dyn RetrievalArguments,
16 expression: &ScalarExpr,
17 constants: &RetrievalConstants<'_>,
18 function_name: &str,
19) -> BindingResult<RetrievalExpr> {
20 lower_where_bound(source, expression, constants)?.ok_or_else(|| {
21 SQLError::TypeMismatch(format!(
22 "{function_name} operand cannot be represented by the operator IR"
23 ))
24 })
25}
26
27fn const_join_threshold(
28 expression: &ScalarExpr,
29 constants: &RetrievalConstants<'_>,
30 function_name: &str,
31 minimum: f64,
32 maximum: f64,
33) -> BindingResult<f64> {
34 let threshold = const_f64(expression, constants).ok_or_else(|| {
35 SQLError::TypeMismatch(format!(
36 "{function_name}.threshold must be a constant number"
37 ))
38 })?;
39 if !threshold.is_finite() || !(minimum..=maximum).contains(&threshold) {
40 return Err(SQLError::TypeMismatch(format!(
41 "{function_name}.threshold must be finite and in [{minimum}, {maximum}], got {threshold}"
42 )));
43 }
44 Ok(threshold)
45}
46
47pub fn lower_operator_join_table_function(
48 source: &dyn RetrievalArguments,
49 name: &str,
50 relations: Option<&crate::ast::OperatorJoinRelations>,
51 args: &[ScalarExpr],
52 constants: &RetrievalConstants<'_>,
53) -> BindingResult<(crate::ast::OperatorJoinRelations, RetrievalExpr)> {
54 let expected = match name {
55 "text_similarity_join" | "vector_similarity_join" => 5,
56 "graph_join" => 6,
57 "hybrid_join" | "cross_paradigm_join" => 4,
58 _ => {
59 return Err(SQLError::Unsupported(format!(
60 "operator join table function `{name}`"
61 )))
62 }
63 };
64 let relations = relations.ok_or_else(|| {
65 SQLError::TypeMismatch(format!("{name} requires left and right table identifiers"))
66 })?;
67 let actual = args.len() + 2;
68 if actual != expected {
69 return Err(SQLError::BadArity {
70 name: name.to_string(),
71 expected: expected.to_string(),
72 actual,
73 });
74 }
75 let left = lower_join_operand(source, &args[0], constants, name)?;
76 let right = lower_join_operand(source, &args[1], constants, name)?;
77 let tree = match name {
78 "text_similarity_join" => RetrievalExpr::TextSimilarityJoin {
79 left: Box::new(left),
80 right: Box::new(right),
81 threshold: const_join_threshold(&args[2], constants, "text_similarity_join", 0.0, 1.0)?,
82 },
83 "vector_similarity_join" => RetrievalExpr::VectorSimilarityJoin {
84 left: Box::new(left),
85 right: Box::new(right),
86 threshold: const_join_threshold(
87 &args[2],
88 constants,
89 "vector_similarity_join",
90 -1.0,
91 1.0,
92 )?,
93 },
94 "graph_join" => RetrievalExpr::GraphJoin {
95 left: Box::new(left),
96 right: Box::new(right),
97 label: const_optional_string(&args[2], constants)
98 .ok_or_else(|| {
99 SQLError::TypeMismatch(
100 "graph_join.label must be a constant string or NULL".into(),
101 )
102 })?
103 .into_option(),
104 graph: const_string(&args[3], constants).ok_or_else(|| {
105 SQLError::TypeMismatch("graph_join.graph must be a constant string".into())
106 })?,
107 },
108 "hybrid_join" => RetrievalExpr::HybridJoin {
109 left: Box::new(left),
110 right: Box::new(right),
111 },
112 "cross_paradigm_join" => RetrievalExpr::CrossParadigmJoin {
113 left: Box::new(left),
114 right: Box::new(right),
115 },
116 _ => unreachable!("operator join name validated above"),
117 };
118 Ok((relations.clone(), tree))
119}