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, read_catalog_array_input, read_catalog_input, CatalogInputFunctions,
24};
25pub(crate) use casting::{requires_catalog_constant_input, requires_domain_array_input};
26pub use regtype::{
27 format_regtype_elements_with_control, format_regtype_value, format_regtype_value_with_control,
28};
29
30pub trait EngineHook {
35 fn require_builtin_execute(&self, _binding: &crate::ast::FunctionBinding) -> Result<()> {
37 Ok(())
38 }
39
40 fn transaction_timestamp_micros(&self) -> Option<i64> {
42 None
43 }
44
45 fn statement_timestamp_micros(&self) -> Option<i64> {
47 None
48 }
49
50 fn nextval(&self, name: &str) -> Result<i64>;
51 fn currval(&self, name: &str) -> Result<i64>;
52 fn lastval(&self) -> Result<i64> {
53 Err(SQLError::Unsupported(
54 "lastval requires an engine hook implementation".into(),
55 ))
56 }
57 fn setval(&self, name: &str, value: i64, is_called: bool) -> Result<i64>;
58
59 fn call_scalar_function(&self, _name: &str, _args: &[Value]) -> Option<Result<Value>> {
60 None
61 }
62
63 fn call_bound_builtin_function(
68 &self,
69 _binding: &crate::ast::FunctionBinding,
70 _args: &[(Option<String>, Value)],
71 ) -> Option<Result<Value>> {
72 None
73 }
74
75 fn has_scalar_functions(&self) -> bool {
76 true
77 }
78
79 fn enum_labels(&self) -> Option<&dyn super::enums::EnumLabelCatalog> {
81 None
82 }
83
84 fn composite_types(&self) -> Option<&dyn super::composites::CompositeTypeCatalog> {
86 None
87 }
88
89 fn resolve_type_name(&self, _name: &str) -> std::result::Result<Option<ColumnType>, String> {
91 Ok(None)
92 }
93
94 fn resolve_type_oid(&self, _oid: u32) -> std::result::Result<Option<ColumnType>, String> {
96 Ok(None)
97 }
98
99 fn cast_domain(
101 &self,
102 _value: &Value,
103 _source: Option<&str>,
104 _target: &ColumnType,
105 ) -> Result<Option<Value>> {
106 Ok(None)
107 }
108
109 fn resolve_regtype_input(&self, _name: &str) -> Result<Option<i64>> {
111 Ok(None)
112 }
113
114 fn resolve_regclass(&self, _name: &str) -> std::result::Result<Option<i64>, String> {
116 Ok(None)
117 }
118
119 fn resolve_regclass_input(&self, name: &str) -> Result<Option<i64>> {
121 self.resolve_regclass(name).map_err(SQLError::Internal)
122 }
123
124 fn resolve_regproc(&self, _name: &str) -> Result<Option<i64>> {
126 Ok(None)
127 }
128
129 fn resolve_regprocedure(&self, _name: &str) -> std::result::Result<Option<i64>, String> {
131 Ok(None)
132 }
133
134 fn resolve_regprocedure_input(&self, name: &str) -> Result<Option<i64>> {
136 self.resolve_regprocedure(name).map_err(SQLError::Internal)
137 }
138
139 fn resolve_regrole(&self, _name: &str) -> Result<Option<i64>> {
141 Ok(None)
142 }
143
144 fn resolve_regnamespace(&self, name: &str) -> Result<Option<i64>> {
146 self.resolve_regobject(&ColumnType::Regnamespace, name)
147 }
148
149 fn resolve_regcollation(&self, name: &str) -> Result<Option<i64>> {
151 self.resolve_regobject(&ColumnType::Regcollation, name)
152 }
153
154 fn resolve_regobject(&self, ty: &ColumnType, name: &str) -> Result<Option<i64>> {
156 match ty {
157 ColumnType::Regclass => self.resolve_regclass_input(name),
158 ColumnType::Regprocedure => self.resolve_regprocedure(name).map_err(SQLError::Internal),
159 ColumnType::Regrole => self.resolve_regrole(name),
160 ColumnType::Regproc
161 | ColumnType::Regcollation
162 | ColumnType::Regnamespace
163 | ColumnType::Regtype => Ok(None),
164 _ => Err(SQLError::Internal(format!(
165 "unsupported regobject lookup type `{}`",
166 ty.sql_name()
167 ))),
168 }
169 }
170
171 fn resolve_regtype_output(
173 &self,
174 _ty: &ColumnType,
175 _oid: i64,
176 ) -> std::result::Result<Option<String>, String> {
177 Ok(None)
178 }
179
180 fn current_schema(&self) -> std::result::Result<Option<String>, String> {
182 Ok(Some("public".into()))
183 }
184
185 fn current_user(&self) -> std::result::Result<Option<String>, crate::SQLError> {
186 Ok(None)
187 }
188
189 fn session_user(&self) -> std::result::Result<Option<String>, crate::SQLError> {
190 Ok(None)
191 }
192
193 fn runtime_parameter(&self, _name: &str) -> Result<Option<String>> {
195 Err(SQLError::Unsupported(
196 "engine hook does not provide session settings".into(),
197 ))
198 }
199
200 fn sleep(&self, _duration: std::time::Duration) -> Result<()> {
202 Err(SQLError::Unsupported(
203 "engine hook does not provide session sleeps".into(),
204 ))
205 }
206
207 fn set_runtime_parameter(
209 &self,
210 _name: &str,
211 _value: Option<&str>,
212 _local: bool,
213 ) -> Result<String> {
214 Err(SQLError::Unsupported(
215 "engine hook does not provide session settings".into(),
216 ))
217 }
218
219 fn current_schemas(
221 &self,
222 _include_implicit: bool,
223 ) -> std::result::Result<Option<Vec<String>>, String> {
224 Ok(None)
225 }
226
227 fn random_value(&self) -> std::result::Result<Option<f64>, String> {
230 Ok(None)
231 }
232
233 fn random_u64(&self) -> std::result::Result<Option<u64>, String> {
237 Ok(None)
238 }
239
240 fn set_random_seed(&self, _seed: f64) -> std::result::Result<bool, String> {
243 Ok(false)
244 }
245
246 fn call_user_function(
251 &self,
252 _name: &str,
253 _args: &[(Option<String>, Value)],
254 ) -> Option<Result<Value>> {
255 None
256 }
257
258 fn call_bound_user_function(
259 &self,
260 _binding: &crate::ast::FunctionBinding,
261 _args: &[(Option<String>, Value)],
262 ) -> Option<Result<Value>> {
263 None
264 }
265}
266
267pub trait RowLookup {
271 fn column(&self, name: &str) -> Option<&Value>;
272
273 fn column_is_ambiguous(&self, _name: &str) -> bool {
277 false
278 }
279
280 fn qualified_column(&self, qualifier: &str, column: &str) -> Option<&Value>;
281
282 fn qualified_column_is_ambiguous(&self, _qualifier: &str, _column: &str) -> bool {
284 false
285 }
286
287 fn positional_column(&self, _index: usize) -> Option<&Value> {
292 None
293 }
294
295 fn internal_column(&self, _column: InternalColumnRef) -> Option<&Value> {
298 None
299 }
300
301 fn score_source(&self, _qualifier: Option<&str>) -> Option<&Value> {
303 None
304 }
305
306 fn score_source_is_ambiguous(&self, _qualifier: Option<&str>) -> bool {
308 false
309 }
310
311 fn visit_columns(&self, _visitor: &mut dyn FnMut(&str, &Value)) {}
316}
317
318impl RowLookup for ResultRow {
319 fn column(&self, name: &str) -> Option<&Value> {
320 self.get(name)
321 }
322
323 fn qualified_column(&self, _qualifier: &str, _column: &str) -> Option<&Value> {
324 None
325 }
326
327 fn visit_columns(&self, visitor: &mut dyn FnMut(&str, &Value)) {
328 for (column, value) in self {
329 visitor(column, value);
330 }
331 }
332}
333
334pub struct EvalContext<'a> {
335 pub row: Option<&'a ResultRow>,
336 row_lookup: Option<&'a dyn RowLookup>,
337 pub params: &'a [SQLParam],
338 pub engine: Option<&'a dyn EngineHook>,
339 enum_comparison_states: [Option<&'a super::enums::EnumComparisonState>; 4],
340}
341
342impl<'a> EvalContext<'a> {
343 pub fn new(row: Option<&'a ResultRow>, params: &'a [SQLParam]) -> Self {
344 Self {
345 row,
346 row_lookup: row.map(|row| row as &dyn RowLookup),
347 params,
348 engine: None,
349 enum_comparison_states: [None; 4],
350 }
351 }
352
353 pub fn from_row_lookup(row: &'a dyn RowLookup, params: &'a [SQLParam]) -> Self {
354 Self {
355 row: None,
359 row_lookup: Some(row),
360 params,
361 engine: None,
362 enum_comparison_states: [None; 4],
363 }
364 }
365
366 pub fn with_engine(mut self, engine: &'a dyn EngineHook) -> Self {
367 self.engine = Some(engine);
368 self
369 }
370
371 pub fn with_enum_comparison_state(
373 mut self,
374 state: Option<&'a super::enums::EnumComparisonState>,
375 ) -> Self {
376 self.enum_comparison_states[0] = state;
377 self
378 }
379
380 pub fn with_enum_comparison_states(
383 mut self,
384 states: [Option<&'a super::enums::EnumComparisonState>; 4],
385 ) -> Self {
386 self.enum_comparison_states = states;
387 self
388 }
389
390 pub(super) fn enum_comparison_state(&self) -> Option<&super::enums::EnumComparisonState> {
391 self.enum_comparison_states[0]
392 }
393
394 pub(super) fn enum_comparison_state_at(
395 &self,
396 slot: usize,
397 ) -> Option<&super::enums::EnumComparisonState> {
398 self.enum_comparison_states[slot]
399 }
400
401 pub(super) fn row_lookup(&self) -> Result<&'a dyn RowLookup> {
402 self.row_lookup
403 .ok_or_else(|| SQLError::Internal("column reference without row context".into()))
404 }
405
406 pub fn column_value(&self, name: &str) -> Result<Value> {
410 self.column_value_with_control(name, &ProductionControl::uncontrolled())
411 .map(|value| value.into_uncontrolled().expect("ordinary column value"))
412 }
413
414 pub fn column_value_with_control(
416 &self,
417 name: &str,
418 control: &ProductionControl<'_>,
419 ) -> Result<Produced<Value>> {
420 control.check()?;
421 let row = self.row_lookup()?;
422 if row.column_is_ambiguous(name) {
423 return Err(SQLError::AmbiguousColumn(name.to_string()));
424 }
425 Ok(control.copy_value(row.column(name).unwrap_or(&Value::Null))?)
426 }
427
428 pub fn qualified_column_value(&self, qualifier: &str, column: &str) -> Result<Value> {
430 self.qualified_column_value_with_control(
431 qualifier,
432 column,
433 &ProductionControl::uncontrolled(),
434 )
435 .map(|value| {
436 value
437 .into_uncontrolled()
438 .expect("ordinary qualified column value")
439 })
440 }
441
442 pub fn qualified_column_value_with_control(
444 &self,
445 qualifier: &str,
446 column: &str,
447 control: &ProductionControl<'_>,
448 ) -> Result<Produced<Value>> {
449 control.check()?;
450 let row = self.row_lookup()?;
451 if row.qualified_column_is_ambiguous(qualifier, column) {
452 return Err(SQLError::AmbiguousColumn(format!("{qualifier}.{column}")));
453 }
454 Ok(control.copy_value(
455 row.qualified_column(qualifier, column)
456 .unwrap_or(&Value::Null),
457 )?)
458 }
459}
460
461#[cfg(test)]
462mod production_tests;