uqa_sql/semantics/rules/
binding.rs1use crate::{
10 ast::Expr,
11 plpgsql::{ResolvedVariable, VariableResolver},
12 ResultRow as Document, SQLError,
13};
14use std::collections::BTreeMap;
15use uqa_core::{DocId, Value};
16
17pub trait RuleRowValues {
19 fn old_row(&self) -> Option<&Document>;
20 fn new_row(&self) -> Option<&Document>;
21 fn old_doc_id(&self) -> Option<DocId>;
22 fn new_doc_id(&self) -> Option<DocId>;
23}
24
25mod actions;
26pub use actions::{bind_insert_values_action, bind_set_oriented_action, BoundSetOrientedAction};
27
28pub struct RuleColumnMetadata {
29 pub ty: crate::ast::ColumnType,
30 pub collation_oid: Option<u32>,
31 pub uses_document_id: bool,
32 pub position: usize,
33}
34
35impl RuleColumnMetadata {
36 pub fn bind_input(&self, expression: crate::ScalarExpr) -> crate::ScalarExpr {
38 if !crate::schema::composites::type_is_collatable(&self.ty) {
39 return expression;
40 }
41 let oid = self.collation_oid.unwrap_or_else(|| {
42 u32::try_from(crate::catalog::type_metadata::pg_type_collation_oid(
43 &self.ty,
44 ))
45 .expect("type collation identity")
46 });
47 crate::ScalarExpr::Collated {
48 expr: Box::new(expression),
49 ty: self.ty.clone(),
50 collation: crate::ir::ResultCollation::new(
51 crate::catalog::collations::derivation::Derivation::implicit(i64::from(oid)),
52 oid,
53 ),
54 }
55 }
56}
57
58pub(super) struct RuntimeRuleResolver<'a> {
59 pub(super) old: Option<&'a Document>,
60 pub(super) new: Option<&'a Document>,
61 pub(super) old_doc_id: Option<DocId>,
62 pub(super) new_doc_id: Option<DocId>,
63 pub(super) columns: &'a BTreeMap<String, RuleColumnMetadata>,
64}
65
66impl RuntimeRuleResolver<'_> {
67 pub(super) fn field_expression(
68 &self,
69 record: Option<&Document>,
70 doc_id: Option<DocId>,
71 column: &str,
72 ) -> Result<Expr, SQLError> {
73 self.record_field(record, doc_id, column)
74 .map(ResolvedVariable::into_expression)
75 }
76
77 pub(super) fn record_field(
78 &self,
79 record: Option<&Document>,
80 doc_id: Option<DocId>,
81 column: &str,
82 ) -> Result<ResolvedVariable, SQLError> {
83 let (metadata, value) = self.field(record, doc_id, column)?;
84 Ok(ResolvedVariable {
85 collation_oid: metadata.collation_oid,
86 value,
87 declared_type: Some(metadata.ty.catalog_name()),
88 })
89 }
90
91 fn field(
92 &self,
93 record: Option<&Document>,
94 doc_id: Option<DocId>,
95 column: &str,
96 ) -> Result<(&RuleColumnMetadata, Value), SQLError> {
97 let metadata = self
98 .columns
99 .get(column)
100 .ok_or_else(|| SQLError::UnknownColumn(column.to_string()))?;
101 let value = if let Some(value) = record.and_then(|record| record.get(column).cloned()) {
102 value
103 } else if metadata.uses_document_id {
104 doc_id
105 .filter(|doc_id| crate::semantics::key_identity::is_key_document_id(*doc_id))
106 .map(i64::try_from)
107 .transpose()
108 .map_err(|_| {
109 SQLError::TypeMismatch("document id exceeds PostgreSQL bigint".into())
110 })?
111 .map_or(Value::Null, Value::Int)
112 } else {
113 Value::Null
114 };
115 Ok((metadata, value))
116 }
117
118 pub(super) fn record(
119 &self,
120 record: Option<&Document>,
121 doc_id: Option<DocId>,
122 ) -> Result<ResolvedVariable, SQLError> {
123 let mut columns = self.columns.iter().collect::<Vec<_>>();
124 columns.sort_by_key(|(_, metadata)| metadata.position);
125 let fields = columns
126 .into_iter()
127 .map(|(column, _)| {
128 self.record_field(record, doc_id, column)
129 .map(|field| (column.clone(), field.value))
130 })
131 .collect::<Result<Vec<_>, _>>()?;
132 Ok(ResolvedVariable::untyped(Value::Record(fields.into())))
133 }
134}
135
136impl VariableResolver for RuntimeRuleResolver<'_> {
137 fn resolve_name(&mut self, name: &str) -> Result<Option<ResolvedVariable>, SQLError> {
138 if name.eq_ignore_ascii_case("old") {
139 return self.record(self.old, self.old_doc_id).map(Some);
140 }
141 if name.eq_ignore_ascii_case("new") {
142 return self.record(self.new, self.new_doc_id).map(Some);
143 }
144 Ok(None)
145 }
146
147 fn resolve_qualified(
148 &mut self,
149 qualifier: &str,
150 column: &str,
151 ) -> Result<Option<ResolvedVariable>, SQLError> {
152 if qualifier.eq_ignore_ascii_case("old") {
153 return self
154 .record_field(self.old, self.old_doc_id, column)
155 .map(Some);
156 }
157 if qualifier.eq_ignore_ascii_case("new") {
158 return self
159 .record_field(self.new, self.new_doc_id, column)
160 .map(Some);
161 }
162 Ok(None)
163 }
164
165 fn resolve_param(&mut self, _index: usize) -> Result<Option<ResolvedVariable>, SQLError> {
166 Ok(None)
167 }
168
169 fn rewrite_qualified_whole_row(&mut self, qualifier: &str) -> Result<Option<Expr>, SQLError> {
170 Ok(self
171 .resolve_name(qualifier)?
172 .map(|record| Expr::Literal(record.value)))
173 }
174}