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(crate) use regtype::format_regtype_value_with_catalog_and_control;
27pub use regtype::{
28 format_regtype_elements_with_control, format_regtype_value, format_regtype_value_with_control,
29};
30
31pub trait EngineHook {
36 fn require_builtin_execute(&self, _binding: &crate::ast::FunctionBinding) -> Result<()> {
38 Ok(())
39 }
40
41 fn transaction_timestamp_micros(&self) -> Option<i64> {
43 None
44 }
45
46 fn statement_timestamp_micros(&self) -> Option<i64> {
48 None
49 }
50
51 fn nextval(&self, name: &str) -> Result<i64>;
52 fn currval(&self, name: &str) -> Result<i64>;
53 fn lastval(&self) -> Result<i64> {
54 Err(SQLError::Unsupported(
55 "lastval requires an engine hook implementation".into(),
56 ))
57 }
58 fn setval(&self, name: &str, value: i64, is_called: bool) -> Result<i64>;
59
60 fn call_scalar_function(&self, _name: &str, _args: &[Value]) -> Option<Result<Value>> {
61 None
62 }
63
64 fn call_bound_builtin_function(
69 &self,
70 _binding: &crate::ast::FunctionBinding,
71 _args: &[(Option<String>, Value)],
72 ) -> Option<Result<Value>> {
73 None
74 }
75
76 fn has_scalar_functions(&self) -> bool {
77 true
78 }
79
80 fn enum_labels(&self) -> Option<&dyn super::enums::EnumLabelCatalog> {
82 None
83 }
84
85 fn composite_types(&self) -> Option<&dyn super::composites::CompositeTypeCatalog> {
87 None
88 }
89
90 fn resolve_type_name(&self, _name: &str) -> std::result::Result<Option<ColumnType>, String> {
92 Ok(None)
93 }
94
95 fn resolve_type_oid(&self, _oid: u32) -> std::result::Result<Option<ColumnType>, String> {
97 Ok(None)
98 }
99
100 fn cast_domain(
102 &self,
103 _value: &Value,
104 _source: Option<&str>,
105 _target: &ColumnType,
106 ) -> Result<Option<Value>> {
107 Ok(None)
108 }
109
110 fn resolve_regtype_input(&self, _name: &str) -> Result<Option<i64>> {
112 Ok(None)
113 }
114
115 fn resolve_regclass(&self, _name: &str) -> std::result::Result<Option<i64>, String> {
117 Ok(None)
118 }
119
120 fn resolve_regclass_input(&self, name: &str) -> Result<Option<i64>> {
122 self.resolve_regclass(name).map_err(SQLError::Internal)
123 }
124
125 fn resolve_regproc(&self, _name: &str) -> Result<Option<i64>> {
127 Ok(None)
128 }
129
130 fn resolve_regprocedure(&self, _name: &str) -> std::result::Result<Option<i64>, String> {
132 Ok(None)
133 }
134
135 fn resolve_regprocedure_input(&self, name: &str) -> Result<Option<i64>> {
137 self.resolve_regprocedure(name).map_err(SQLError::Internal)
138 }
139
140 fn resolve_regrole(&self, _name: &str) -> Result<Option<i64>> {
142 Ok(None)
143 }
144
145 fn resolve_regnamespace(&self, name: &str) -> Result<Option<i64>> {
147 self.resolve_regobject(&ColumnType::Regnamespace, name)
148 }
149
150 fn resolve_regcollation(&self, name: &str) -> Result<Option<i64>> {
152 self.resolve_regobject(&ColumnType::Regcollation, name)
153 }
154
155 fn resolve_regobject(&self, ty: &ColumnType, name: &str) -> Result<Option<i64>> {
157 match ty {
158 ColumnType::Regclass => self.resolve_regclass_input(name),
159 ColumnType::Regprocedure => self.resolve_regprocedure(name).map_err(SQLError::Internal),
160 ColumnType::Regrole => self.resolve_regrole(name),
161 ColumnType::Regproc
162 | ColumnType::Regcollation
163 | ColumnType::Regnamespace
164 | ColumnType::Regtype => Ok(None),
165 _ => Err(SQLError::Internal(format!(
166 "unsupported regobject lookup type `{}`",
167 ty.sql_name()
168 ))),
169 }
170 }
171
172 fn resolve_regtype_output(
174 &self,
175 _ty: &ColumnType,
176 _oid: i64,
177 ) -> std::result::Result<Option<String>, String> {
178 Ok(None)
179 }
180
181 fn current_schema(&self) -> std::result::Result<Option<String>, String> {
183 Ok(Some("public".into()))
184 }
185
186 fn current_user(&self) -> std::result::Result<Option<String>, crate::SQLError> {
187 Ok(None)
188 }
189
190 fn session_user(&self) -> std::result::Result<Option<String>, crate::SQLError> {
191 Ok(None)
192 }
193
194 fn runtime_parameter(&self, _name: &str) -> Result<Option<String>> {
196 Err(SQLError::Unsupported(
197 "engine hook does not provide session settings".into(),
198 ))
199 }
200
201 fn temporal_interval_style(&self) -> Result<Option<uqa_core::TemporalIntervalStyle>> {
203 Ok(None)
204 }
205
206 fn temporal_time_zone(&self) -> Result<Option<uqa_core::TemporalTimeZone>> {
208 Ok(None)
209 }
210
211 fn sleep(&self, _duration: std::time::Duration) -> Result<()> {
213 Err(SQLError::Unsupported(
214 "engine hook does not provide session sleeps".into(),
215 ))
216 }
217
218 fn set_runtime_parameter(
220 &self,
221 _name: &str,
222 _value: Option<&str>,
223 _local: bool,
224 ) -> Result<String> {
225 Err(SQLError::Unsupported(
226 "engine hook does not provide session settings".into(),
227 ))
228 }
229
230 fn current_schemas(
232 &self,
233 _include_implicit: bool,
234 ) -> std::result::Result<Option<Vec<String>>, String> {
235 Ok(None)
236 }
237
238 fn random_value(&self) -> std::result::Result<Option<f64>, String> {
241 Ok(None)
242 }
243
244 fn random_u64(&self) -> std::result::Result<Option<u64>, String> {
248 Ok(None)
249 }
250
251 fn set_random_seed(&self, _seed: f64) -> std::result::Result<bool, String> {
254 Ok(false)
255 }
256
257 fn call_user_function(
262 &self,
263 _name: &str,
264 _args: &[(Option<String>, Value)],
265 ) -> Option<Result<Value>> {
266 None
267 }
268
269 fn call_bound_user_function(
270 &self,
271 _binding: &crate::ast::FunctionBinding,
272 _args: &[(Option<String>, Value)],
273 ) -> Option<Result<Value>> {
274 None
275 }
276}
277
278pub trait RowLookup {
282 fn column(&self, name: &str) -> Option<&Value>;
283
284 fn column_is_ambiguous(&self, _name: &str) -> bool {
288 false
289 }
290
291 fn qualified_column(&self, qualifier: &str, column: &str) -> Option<&Value>;
292
293 fn qualified_column_is_ambiguous(&self, _qualifier: &str, _column: &str) -> bool {
295 false
296 }
297
298 fn positional_column(&self, _index: usize) -> Option<&Value> {
303 None
304 }
305
306 fn internal_column(&self, _column: InternalColumnRef) -> Option<&Value> {
309 None
310 }
311
312 fn score_source(&self, _qualifier: Option<&str>) -> Option<&Value> {
314 None
315 }
316
317 fn score_source_is_ambiguous(&self, _qualifier: Option<&str>) -> bool {
319 false
320 }
321
322 fn visit_columns(&self, _visitor: &mut dyn FnMut(&str, &Value)) {}
327}
328
329impl RowLookup for ResultRow {
330 fn column(&self, name: &str) -> Option<&Value> {
331 self.get(name)
332 }
333
334 fn qualified_column(&self, _qualifier: &str, _column: &str) -> Option<&Value> {
335 None
336 }
337
338 fn visit_columns(&self, visitor: &mut dyn FnMut(&str, &Value)) {
339 for (column, value) in self {
340 visitor(column, value);
341 }
342 }
343}
344
345pub struct EvalContext<'a> {
346 pub row: Option<&'a ResultRow>,
347 row_lookup: Option<&'a dyn RowLookup>,
348 pub params: &'a [SQLParam],
349 pub engine: Option<&'a dyn EngineHook>,
350 enum_comparison_states: [Option<&'a super::enums::EnumComparisonState>; 4],
351}
352
353impl<'a> EvalContext<'a> {
354 pub fn new(row: Option<&'a ResultRow>, params: &'a [SQLParam]) -> Self {
355 Self {
356 row,
357 row_lookup: row.map(|row| row as &dyn RowLookup),
358 params,
359 engine: None,
360 enum_comparison_states: [None; 4],
361 }
362 }
363
364 pub fn from_row_lookup(row: &'a dyn RowLookup, params: &'a [SQLParam]) -> Self {
365 Self {
366 row: None,
370 row_lookup: Some(row),
371 params,
372 engine: None,
373 enum_comparison_states: [None; 4],
374 }
375 }
376
377 pub fn with_engine(mut self, engine: &'a dyn EngineHook) -> Self {
378 self.engine = Some(engine);
379 self
380 }
381
382 pub fn with_enum_comparison_state(
384 mut self,
385 state: Option<&'a super::enums::EnumComparisonState>,
386 ) -> Self {
387 self.enum_comparison_states[0] = state;
388 self
389 }
390
391 pub fn with_enum_comparison_states(
394 mut self,
395 states: [Option<&'a super::enums::EnumComparisonState>; 4],
396 ) -> Self {
397 self.enum_comparison_states = states;
398 self
399 }
400
401 pub(super) fn enum_comparison_state(&self) -> Option<&super::enums::EnumComparisonState> {
402 self.enum_comparison_states[0]
403 }
404
405 pub(super) fn enum_comparison_state_at(
406 &self,
407 slot: usize,
408 ) -> Option<&super::enums::EnumComparisonState> {
409 self.enum_comparison_states[slot]
410 }
411
412 pub(super) fn row_lookup(&self) -> Result<&'a dyn RowLookup> {
413 self.row_lookup
414 .ok_or_else(|| SQLError::Internal("column reference without row context".into()))
415 }
416
417 pub fn column_value(&self, name: &str) -> Result<Value> {
421 self.column_value_with_control(name, &ProductionControl::uncontrolled())
422 .map(|value| value.into_uncontrolled().expect("ordinary column value"))
423 }
424
425 pub fn column_value_with_control(
427 &self,
428 name: &str,
429 control: &ProductionControl<'_>,
430 ) -> Result<Produced<Value>> {
431 control.check()?;
432 let row = self.row_lookup()?;
433 if row.column_is_ambiguous(name) {
434 return Err(SQLError::AmbiguousColumn(name.to_string()));
435 }
436 Ok(control.copy_value(row.column(name).unwrap_or(&Value::Null))?)
437 }
438
439 pub fn qualified_column_value(&self, qualifier: &str, column: &str) -> Result<Value> {
441 self.qualified_column_value_with_control(
442 qualifier,
443 column,
444 &ProductionControl::uncontrolled(),
445 )
446 .map(|value| {
447 value
448 .into_uncontrolled()
449 .expect("ordinary qualified column value")
450 })
451 }
452
453 pub fn qualified_column_value_with_control(
455 &self,
456 qualifier: &str,
457 column: &str,
458 control: &ProductionControl<'_>,
459 ) -> Result<Produced<Value>> {
460 control.check()?;
461 let row = self.row_lookup()?;
462 if row.qualified_column_is_ambiguous(qualifier, column) {
463 return Err(SQLError::AmbiguousColumn(format!("{qualifier}.{column}")));
464 }
465 Ok(control.copy_value(
466 row.qualified_column(qualifier, column)
467 .unwrap_or(&Value::Null),
468 )?)
469 }
470}
471
472#[cfg(test)]
473mod production_tests;