1use std::borrow::Cow;
10
11use uqa_core::{ArrayValue, Value};
12
13use crate::ast::{ColumnType, InternalColumnRef};
14use crate::error::{Result, SQLError};
15use crate::params::SQLParam;
16use crate::result::ResultRow;
17
18use super::casting::{cast_value_from, parse_pg_array_literal};
19use super::conversion::{array_value_to_string, value_to_string};
20
21#[must_use]
22pub fn coercion_type_name(ty: &ColumnType) -> String {
23 match ty {
24 ColumnType::Domain { base, .. } => coercion_type_name(base),
25 ColumnType::Array(element) => format!("{}[]", coercion_type_name(element)),
26 _ => ty.sql_name(),
27 }
28}
29
30fn regrole_array_type(ty: &ColumnType) -> bool {
31 match ty {
32 ColumnType::Array(element) => {
33 matches!(element.as_ref(), ColumnType::Regrole) || regrole_array_type(element)
34 }
35 _ => false,
36 }
37}
38
39fn array_leaf_type(ty: &ColumnType) -> &ColumnType {
40 match ty {
41 ColumnType::Array(element) => array_leaf_type(element),
42 _ => ty,
43 }
44}
45
46fn cast_regrole_array_elements(
47 values: &[Value],
48 source_ty: Option<&str>,
49 engine: Option<&dyn EngineHook>,
50) -> Result<Vec<Value>> {
51 values
52 .iter()
53 .map(|value| match value {
54 Value::List(nested) => {
55 cast_regrole_array_elements(nested, source_ty, engine).map(Value::List)
56 }
57 value => cast_value_with_type_resolution(value, source_ty, "regrole", engine),
58 })
59 .collect()
60}
61
62fn cast_regrole_array(
63 value: &Value,
64 source_ty: Option<&ColumnType>,
65 engine: Option<&dyn EngineHook>,
66) -> Result<Value> {
67 let array = match value {
68 Value::Array(array) => array.clone(),
69 Value::Str(text) => parse_pg_array_literal(text)?,
70 other => {
71 return Err(SQLError::TypeMismatch(format!(
72 "CAST AS regrole[]: expected array, got {other:?}"
73 )));
74 }
75 };
76 let source_name = source_ty.map(array_leaf_type).map(ColumnType::sql_name);
77 let elements = cast_regrole_array_elements(array.elements(), source_name.as_deref(), engine)?;
78 ArrayValue::with_lower_bounds(elements, array.lower_bounds().to_vec())
79 .map(Value::Array)
80 .ok_or_else(|| SQLError::TypeMismatch("array dimensions changed during cast".into()))
81}
82
83pub trait EngineHook {
88 fn nextval(&self, name: &str) -> Result<i64>;
89 fn currval(&self, name: &str) -> Result<i64>;
90 fn lastval(&self) -> Result<i64> {
91 Err(SQLError::Unsupported(
92 "lastval requires an engine hook implementation".into(),
93 ))
94 }
95 fn setval(&self, name: &str, value: i64, is_called: bool) -> Result<i64>;
96
97 fn call_scalar_function(&self, _name: &str, _args: &[Value]) -> Option<Result<Value>> {
98 None
99 }
100
101 fn call_bound_builtin_function(
106 &self,
107 _binding: &crate::ast::FunctionBinding,
108 _args: &[(Option<String>, Value)],
109 ) -> Option<Result<Value>> {
110 None
111 }
112
113 fn has_scalar_functions(&self) -> bool {
114 true
115 }
116
117 fn resolve_type_name(&self, _name: &str) -> std::result::Result<Option<ColumnType>, String> {
119 Ok(None)
120 }
121
122 fn resolve_regclass(&self, _name: &str) -> std::result::Result<Option<i64>, String> {
124 Ok(None)
125 }
126
127 fn resolve_regclass_input(&self, name: &str) -> Result<Option<i64>> {
129 self.resolve_regclass(name).map_err(SQLError::Internal)
130 }
131
132 fn resolve_regprocedure(&self, _name: &str) -> std::result::Result<Option<i64>, String> {
134 Ok(None)
135 }
136
137 fn resolve_regrole(&self, _name: &str) -> Result<Option<i64>> {
139 Ok(None)
140 }
141
142 fn resolve_regnamespace(&self, name: &str) -> Result<Option<i64>> {
144 self.resolve_regobject(&ColumnType::Regnamespace, name)
145 }
146
147 fn resolve_regobject(&self, ty: &ColumnType, name: &str) -> Result<Option<i64>> {
149 match ty {
150 ColumnType::Regclass => self.resolve_regclass_input(name),
151 ColumnType::Regprocedure => self.resolve_regprocedure(name).map_err(SQLError::Internal),
152 ColumnType::Regrole => self.resolve_regrole(name),
153 ColumnType::Regproc | ColumnType::Regnamespace | ColumnType::Regtype => Ok(None),
154 _ => Err(SQLError::Internal(format!(
155 "unsupported regobject lookup type `{}`",
156 ty.sql_name()
157 ))),
158 }
159 }
160
161 fn resolve_regtype_output(
163 &self,
164 _ty: &ColumnType,
165 _oid: i64,
166 ) -> std::result::Result<Option<String>, String> {
167 Ok(None)
168 }
169
170 fn current_schema(&self) -> std::result::Result<Option<String>, String> {
174 Ok(None)
175 }
176
177 fn current_user(&self) -> std::result::Result<Option<String>, String> {
178 Ok(None)
179 }
180
181 fn session_user(&self) -> std::result::Result<Option<String>, String> {
182 Ok(None)
183 }
184
185 fn current_schemas(
187 &self,
188 _include_implicit: bool,
189 ) -> std::result::Result<Option<Vec<String>>, String> {
190 Ok(None)
191 }
192
193 fn random_value(&self) -> std::result::Result<Option<f64>, String> {
196 Ok(None)
197 }
198
199 fn random_u64(&self) -> std::result::Result<Option<u64>, String> {
203 Ok(None)
204 }
205
206 fn set_random_seed(&self, _seed: f64) -> std::result::Result<bool, String> {
209 Ok(false)
210 }
211
212 fn call_user_function(
217 &self,
218 _name: &str,
219 _args: &[(Option<String>, Value)],
220 ) -> Option<Result<Value>> {
221 None
222 }
223
224 fn call_bound_user_function(
225 &self,
226 _binding: &crate::ast::FunctionBinding,
227 _args: &[(Option<String>, Value)],
228 ) -> Option<Result<Value>> {
229 None
230 }
231}
232
233pub fn format_regtype_value(
235 value: &Value,
236 ty: &ColumnType,
237 engine: Option<&dyn EngineHook>,
238) -> Result<Option<String>> {
239 if matches!(value, Value::Null) {
240 return Ok(None);
241 }
242 if let ColumnType::Array(element) = ty {
243 if !matches!(
244 element.as_ref(),
245 ColumnType::Regproc
246 | ColumnType::Regprocedure
247 | ColumnType::Regclass
248 | ColumnType::Regnamespace
249 | ColumnType::Regrole
250 | ColumnType::Regtype
251 ) {
252 return Ok(None);
253 }
254 let Value::Array(array) = value else {
255 return Ok(Some(value_to_string(value)));
256 };
257 let elements = format_regtype_array_elements(array.elements(), element, engine)?;
258 let formatted = array.with_elements(elements).ok_or_else(|| {
259 SQLError::Internal("regtype array output changed the array dimensions".into())
260 })?;
261 return Ok(Some(array_value_to_string(&formatted)));
262 }
263 if !matches!(
264 ty,
265 ColumnType::Regproc
266 | ColumnType::Regprocedure
267 | ColumnType::Regclass
268 | ColumnType::Regnamespace
269 | ColumnType::Regrole
270 | ColumnType::Regtype
271 ) {
272 return Ok(None);
273 }
274 let Value::Int(oid) = value else {
275 return Ok(Some(value_to_string(value)));
276 };
277 if *oid == 0 {
278 return Ok(Some("-".into()));
279 }
280 let resolved = engine
281 .map(|engine| engine.resolve_regtype_output(ty, *oid))
282 .transpose()
283 .map_err(SQLError::Internal)?
284 .flatten();
285 Ok(Some(resolved.unwrap_or_else(|| oid.to_string())))
286}
287
288fn format_regtype_array_elements(
289 values: &[Value],
290 element: &ColumnType,
291 engine: Option<&dyn EngineHook>,
292) -> Result<Vec<Value>> {
293 values
294 .iter()
295 .map(|value| match value {
296 Value::Null => Ok(Value::Null),
297 Value::List(nested) => {
298 format_regtype_array_elements(nested, element, engine).map(Value::List)
299 }
300 other => format_regtype_value(other, element, engine)
301 .map(|text| text.map_or_else(|| other.clone(), Value::Str)),
302 })
303 .collect()
304}
305
306pub fn cast_value_with_type_resolution(
308 value: &Value,
309 source_ty: Option<&str>,
310 target_ty: &str,
311 engine: Option<&dyn EngineHook>,
312) -> Result<Value> {
313 let resolved_source = match (engine, source_ty) {
314 (Some(engine), Some(source_ty)) => engine
315 .resolve_type_name(source_ty)
316 .map_err(SQLError::Internal)?
317 .map(|ty| coercion_type_name(&ty)),
318 _ => None,
319 };
320 let source_ty = resolved_source.as_deref().or(source_ty);
321 let resolved_target = engine
322 .map(|engine| engine.resolve_type_name(target_ty))
323 .transpose()
324 .map_err(SQLError::Internal)?
325 .flatten();
326 let target_ty = resolved_target.as_ref().map_or_else(
327 || Cow::Borrowed(target_ty),
328 |ty| Cow::Owned(coercion_type_name(ty)),
329 );
330 let target_column_type = resolved_target
331 .clone()
332 .or_else(|| ColumnType::from_sql_name(&target_ty).ok());
333 if target_column_type.as_ref().is_some_and(regrole_array_type) {
334 let source_column_type = source_ty.and_then(|name| ColumnType::from_sql_name(name).ok());
335 return cast_regrole_array(value, source_column_type.as_ref(), engine);
336 }
337 if target_ty.eq_ignore_ascii_case("text") {
338 if let Some(source_ty) = source_ty.and_then(|source| ColumnType::from_sql_name(source).ok())
339 {
340 if let Some(text) = format_regtype_value(value, &source_ty, engine)? {
341 return Ok(Value::Str(text));
342 }
343 }
344 }
345 if target_ty.eq_ignore_ascii_case("regclass") {
346 if let (Some(engine), Value::Str(name) | Value::FixedChar(name)) = (engine, value) {
347 return engine
348 .resolve_regclass_input(name)?
349 .map(Value::Int)
350 .ok_or_else(|| SQLError::Routine {
351 sqlstate: "42P01".into(),
352 message: format!("relation \"{name}\" does not exist"),
353 });
354 }
355 }
356 if target_ty.eq_ignore_ascii_case("regprocedure") {
357 if let (Some(engine), Value::Str(name) | Value::FixedChar(name)) = (engine, value) {
358 return engine
359 .resolve_regprocedure(name)
360 .map_err(SQLError::Internal)?
361 .map(Value::Int)
362 .ok_or_else(|| SQLError::Routine {
363 sqlstate: "42883".into(),
364 message: format!("function {name} does not exist"),
365 });
366 }
367 }
368 if target_ty.eq_ignore_ascii_case("regrole") {
369 if let (Some(engine), Value::Str(name) | Value::FixedChar(name)) = (engine, value) {
370 return engine
371 .resolve_regrole(name)?
372 .map(Value::Int)
373 .ok_or_else(|| SQLError::Routine {
374 sqlstate: "42704".into(),
375 message: format!("role \"{name}\" does not exist"),
376 });
377 }
378 }
379 if matches!(target_column_type.as_ref(), Some(ColumnType::Regnamespace)) {
380 if let (Some(engine), Value::Str(name) | Value::FixedChar(name)) = (engine, value) {
381 return engine
382 .resolve_regnamespace(name)?
383 .map(Value::Int)
384 .ok_or_else(|| SQLError::Routine {
385 sqlstate: "3F000".into(),
386 message: format!("schema \"{name}\" does not exist"),
387 });
388 }
389 }
390 cast_value_from(value, &target_ty, source_ty)
391}
392
393pub trait RowLookup {
397 fn column(&self, name: &str) -> Option<&Value>;
398
399 fn column_is_ambiguous(&self, _name: &str) -> bool {
403 false
404 }
405
406 fn qualified_column(&self, qualifier: &str, column: &str) -> Option<&Value>;
407
408 fn qualified_column_is_ambiguous(&self, _qualifier: &str, _column: &str) -> bool {
410 false
411 }
412
413 fn positional_column(&self, _index: usize) -> Option<&Value> {
418 None
419 }
420
421 fn internal_column(&self, _column: InternalColumnRef) -> Option<&Value> {
424 None
425 }
426
427 fn score_source(&self, _qualifier: Option<&str>) -> Option<&Value> {
429 None
430 }
431
432 fn score_source_is_ambiguous(&self, _qualifier: Option<&str>) -> bool {
434 false
435 }
436
437 fn visit_columns(&self, _visitor: &mut dyn FnMut(&str, &Value)) {}
442}
443
444impl RowLookup for ResultRow {
445 fn column(&self, name: &str) -> Option<&Value> {
446 self.get(name)
447 }
448
449 fn qualified_column(&self, _qualifier: &str, _column: &str) -> Option<&Value> {
450 None
451 }
452
453 fn visit_columns(&self, visitor: &mut dyn FnMut(&str, &Value)) {
454 for (column, value) in self {
455 visitor(column, value);
456 }
457 }
458}
459
460pub struct EvalContext<'a> {
461 pub row: Option<&'a ResultRow>,
462 row_lookup: Option<&'a dyn RowLookup>,
463 pub params: &'a [SQLParam],
464 pub engine: Option<&'a dyn EngineHook>,
465}
466
467impl<'a> EvalContext<'a> {
468 pub fn new(row: Option<&'a ResultRow>, params: &'a [SQLParam]) -> Self {
469 Self {
470 row,
471 row_lookup: row.map(|row| row as &dyn RowLookup),
472 params,
473 engine: None,
474 }
475 }
476
477 pub fn from_row_lookup(row: &'a dyn RowLookup, params: &'a [SQLParam]) -> Self {
478 Self {
479 row: None,
483 row_lookup: Some(row),
484 params,
485 engine: None,
486 }
487 }
488
489 pub fn with_engine(mut self, engine: &'a dyn EngineHook) -> Self {
490 self.engine = Some(engine);
491 self
492 }
493
494 pub(super) fn row_lookup(&self) -> Result<&'a dyn RowLookup> {
495 self.row_lookup
496 .ok_or_else(|| SQLError::Internal("column reference without row context".into()))
497 }
498
499 pub fn column_value(&self, name: &str) -> Result<Value> {
503 if self.row_lookup()?.column_is_ambiguous(name) {
504 return Err(SQLError::AmbiguousColumn(name.to_string()));
505 }
506 Ok(self
507 .row_lookup()?
508 .column(name)
509 .cloned()
510 .unwrap_or(Value::Null))
511 }
512
513 pub fn qualified_column_value(&self, qualifier: &str, column: &str) -> Result<Value> {
515 if self
516 .row_lookup()?
517 .qualified_column_is_ambiguous(qualifier, column)
518 {
519 return Err(SQLError::AmbiguousColumn(format!("{qualifier}.{column}")));
520 }
521 Ok(self
522 .row_lookup()?
523 .qualified_column(qualifier, column)
524 .cloned()
525 .unwrap_or(Value::Null))
526 }
527}