1use uqa_core::{
10 memory::{Produced, ProductionControl},
11 Value,
12};
13
14use crate::ast::{ColumnType, InternalColumnRef};
15use crate::error::{Result, SQLError};
16use crate::params::SQLParam;
17use crate::result::ResultRow;
18
19mod casting;
20mod regtype;
21pub use casting::{
22 cast_value_with_type_resolution, cast_value_with_type_resolution_with_control,
23 coercion_type_name,
24};
25pub use regtype::{format_regtype_value, format_regtype_value_with_control};
26
27pub trait EngineHook {
32 fn transaction_timestamp_micros(&self) -> Option<i64> {
34 None
35 }
36
37 fn statement_timestamp_micros(&self) -> Option<i64> {
39 None
40 }
41
42 fn nextval(&self, name: &str) -> Result<i64>;
43 fn currval(&self, name: &str) -> Result<i64>;
44 fn lastval(&self) -> Result<i64> {
45 Err(SQLError::Unsupported(
46 "lastval requires an engine hook implementation".into(),
47 ))
48 }
49 fn setval(&self, name: &str, value: i64, is_called: bool) -> Result<i64>;
50
51 fn call_scalar_function(&self, _name: &str, _args: &[Value]) -> Option<Result<Value>> {
52 None
53 }
54
55 fn call_bound_builtin_function(
60 &self,
61 _binding: &crate::ast::FunctionBinding,
62 _args: &[(Option<String>, Value)],
63 ) -> Option<Result<Value>> {
64 None
65 }
66
67 fn has_scalar_functions(&self) -> bool {
68 true
69 }
70
71 fn resolve_type_name(&self, _name: &str) -> std::result::Result<Option<ColumnType>, String> {
73 Ok(None)
74 }
75
76 fn cast_domain(
78 &self,
79 _value: &Value,
80 _source: Option<&str>,
81 _target: &ColumnType,
82 ) -> Result<Option<Value>> {
83 Ok(None)
84 }
85
86 fn resolve_regtype_input(&self, _name: &str) -> Result<Option<i64>> {
88 Ok(None)
89 }
90
91 fn resolve_regclass(&self, _name: &str) -> std::result::Result<Option<i64>, String> {
93 Ok(None)
94 }
95
96 fn resolve_regclass_input(&self, name: &str) -> Result<Option<i64>> {
98 self.resolve_regclass(name).map_err(SQLError::Internal)
99 }
100
101 fn resolve_regprocedure(&self, _name: &str) -> std::result::Result<Option<i64>, String> {
103 Ok(None)
104 }
105
106 fn resolve_regrole(&self, _name: &str) -> Result<Option<i64>> {
108 Ok(None)
109 }
110
111 fn resolve_regnamespace(&self, name: &str) -> Result<Option<i64>> {
113 self.resolve_regobject(&ColumnType::Regnamespace, name)
114 }
115
116 fn resolve_regobject(&self, ty: &ColumnType, name: &str) -> Result<Option<i64>> {
118 match ty {
119 ColumnType::Regclass => self.resolve_regclass_input(name),
120 ColumnType::Regprocedure => self.resolve_regprocedure(name).map_err(SQLError::Internal),
121 ColumnType::Regrole => self.resolve_regrole(name),
122 ColumnType::Regproc | ColumnType::Regnamespace | ColumnType::Regtype => Ok(None),
123 _ => Err(SQLError::Internal(format!(
124 "unsupported regobject lookup type `{}`",
125 ty.sql_name()
126 ))),
127 }
128 }
129
130 fn resolve_regtype_output(
132 &self,
133 _ty: &ColumnType,
134 _oid: i64,
135 ) -> std::result::Result<Option<String>, String> {
136 Ok(None)
137 }
138
139 fn current_schema(&self) -> std::result::Result<Option<String>, String> {
143 Ok(None)
144 }
145
146 fn current_user(&self) -> std::result::Result<Option<String>, crate::SQLError> {
147 Ok(None)
148 }
149
150 fn session_user(&self) -> std::result::Result<Option<String>, crate::SQLError> {
151 Ok(None)
152 }
153
154 fn runtime_parameter(&self, _name: &str) -> Result<Option<String>> {
156 Err(SQLError::Unsupported(
157 "engine hook does not provide session settings".into(),
158 ))
159 }
160
161 fn current_schemas(
163 &self,
164 _include_implicit: bool,
165 ) -> std::result::Result<Option<Vec<String>>, String> {
166 Ok(None)
167 }
168
169 fn random_value(&self) -> std::result::Result<Option<f64>, String> {
172 Ok(None)
173 }
174
175 fn random_u64(&self) -> std::result::Result<Option<u64>, String> {
179 Ok(None)
180 }
181
182 fn set_random_seed(&self, _seed: f64) -> std::result::Result<bool, String> {
185 Ok(false)
186 }
187
188 fn call_user_function(
193 &self,
194 _name: &str,
195 _args: &[(Option<String>, Value)],
196 ) -> Option<Result<Value>> {
197 None
198 }
199
200 fn call_bound_user_function(
201 &self,
202 _binding: &crate::ast::FunctionBinding,
203 _args: &[(Option<String>, Value)],
204 ) -> Option<Result<Value>> {
205 None
206 }
207}
208
209pub trait RowLookup {
213 fn column(&self, name: &str) -> Option<&Value>;
214
215 fn column_is_ambiguous(&self, _name: &str) -> bool {
219 false
220 }
221
222 fn qualified_column(&self, qualifier: &str, column: &str) -> Option<&Value>;
223
224 fn qualified_column_is_ambiguous(&self, _qualifier: &str, _column: &str) -> bool {
226 false
227 }
228
229 fn positional_column(&self, _index: usize) -> Option<&Value> {
234 None
235 }
236
237 fn internal_column(&self, _column: InternalColumnRef) -> Option<&Value> {
240 None
241 }
242
243 fn score_source(&self, _qualifier: Option<&str>) -> Option<&Value> {
245 None
246 }
247
248 fn score_source_is_ambiguous(&self, _qualifier: Option<&str>) -> bool {
250 false
251 }
252
253 fn visit_columns(&self, _visitor: &mut dyn FnMut(&str, &Value)) {}
258}
259
260impl RowLookup for ResultRow {
261 fn column(&self, name: &str) -> Option<&Value> {
262 self.get(name)
263 }
264
265 fn qualified_column(&self, _qualifier: &str, _column: &str) -> Option<&Value> {
266 None
267 }
268
269 fn visit_columns(&self, visitor: &mut dyn FnMut(&str, &Value)) {
270 for (column, value) in self {
271 visitor(column, value);
272 }
273 }
274}
275
276pub struct EvalContext<'a> {
277 pub row: Option<&'a ResultRow>,
278 row_lookup: Option<&'a dyn RowLookup>,
279 pub params: &'a [SQLParam],
280 pub engine: Option<&'a dyn EngineHook>,
281}
282
283impl<'a> EvalContext<'a> {
284 pub fn new(row: Option<&'a ResultRow>, params: &'a [SQLParam]) -> Self {
285 Self {
286 row,
287 row_lookup: row.map(|row| row as &dyn RowLookup),
288 params,
289 engine: None,
290 }
291 }
292
293 pub fn from_row_lookup(row: &'a dyn RowLookup, params: &'a [SQLParam]) -> Self {
294 Self {
295 row: None,
299 row_lookup: Some(row),
300 params,
301 engine: None,
302 }
303 }
304
305 pub fn with_engine(mut self, engine: &'a dyn EngineHook) -> Self {
306 self.engine = Some(engine);
307 self
308 }
309
310 pub(super) fn row_lookup(&self) -> Result<&'a dyn RowLookup> {
311 self.row_lookup
312 .ok_or_else(|| SQLError::Internal("column reference without row context".into()))
313 }
314
315 pub fn column_value(&self, name: &str) -> Result<Value> {
319 self.column_value_with_control(name, &ProductionControl::uncontrolled())
320 .map(|value| value.into_uncontrolled().expect("ordinary column value"))
321 }
322
323 pub fn column_value_with_control(
325 &self,
326 name: &str,
327 control: &ProductionControl<'_>,
328 ) -> Result<Produced<Value>> {
329 control.check()?;
330 let row = self.row_lookup()?;
331 if row.column_is_ambiguous(name) {
332 return Err(SQLError::AmbiguousColumn(name.to_string()));
333 }
334 Ok(control.copy_value(row.column(name).unwrap_or(&Value::Null))?)
335 }
336
337 pub fn qualified_column_value(&self, qualifier: &str, column: &str) -> Result<Value> {
339 self.qualified_column_value_with_control(
340 qualifier,
341 column,
342 &ProductionControl::uncontrolled(),
343 )
344 .map(|value| {
345 value
346 .into_uncontrolled()
347 .expect("ordinary qualified column value")
348 })
349 }
350
351 pub fn qualified_column_value_with_control(
353 &self,
354 qualifier: &str,
355 column: &str,
356 control: &ProductionControl<'_>,
357 ) -> Result<Produced<Value>> {
358 control.check()?;
359 let row = self.row_lookup()?;
360 if row.qualified_column_is_ambiguous(qualifier, column) {
361 return Err(SQLError::AmbiguousColumn(format!("{qualifier}.{column}")));
362 }
363 Ok(control.copy_value(
364 row.qualified_column(qualifier, column)
365 .unwrap_or(&Value::Null),
366 )?)
367 }
368}
369
370#[cfg(test)]
371mod production_tests;