Skip to main content

uqa_sql/assignment/
routines.rs

1//
2// Unified Query Algebra
3//
4// Copyright (c) 2023-2026 Cognica, Inc.
5//
6
7use crate::{
8    ast::ColumnType, expr::cast_value, type_resolution::canonical_routine_type_name, SQLError,
9};
10use uqa_core::Value;
11pub trait RoutineValueContext: super::AssignmentContext {
12    fn catalog_column_type(&self, name: &str) -> Option<ColumnType>;
13}
14pub fn coerce_routine_value(
15    context: &dyn RoutineValueContext,
16    value: &Value,
17    type_name: &str,
18) -> Result<Value, SQLError> {
19    coerce_routine_value_from(context, value, type_name, None)
20}
21
22pub fn coerce_routine_value_from(
23    context: &dyn RoutineValueContext,
24    value: &Value,
25    type_name: &str,
26    source: Option<&ColumnType>,
27) -> Result<Value, SQLError> {
28    match canonical_routine_type_name(type_name).as_str() {
29        "record" => match value {
30            Value::Record(_) | Value::Null => Ok(value.clone()),
31            Value::Row(row) if row.field_types().is_some() => Ok(value.clone()),
32            Value::Row(values) => Ok(Value::Record(
33                values
34                    .iter()
35                    .cloned()
36                    .enumerate()
37                    .map(|(index, value)| (format!("f{}", index + 1), value))
38                    .collect(),
39            )),
40            _ => Err(SQLError::Routine {
41                sqlstate: "42804".into(),
42                message: "cannot cast non-composite value to type record".into(),
43            }),
44        },
45        "trigger" => match value {
46            Value::Record(_) | Value::Row(_) | Value::Null => Ok(value.clone()),
47            _ => Err(SQLError::Routine {
48                sqlstate: "42804".into(),
49                message: "trigger function must return a row or NULL".into(),
50            }),
51        },
52        "anyarray" => match value {
53            Value::Array(_) | Value::LegacyVector(_) | Value::List(_) | Value::Null => {
54                Ok(value.clone())
55            }
56            _ => Err(SQLError::Routine {
57                sqlstate: "42804".into(),
58                message: "cannot cast non-array value to type anyarray".into(),
59            }),
60        },
61        "refcursor" => match value {
62            Value::Str(_) | Value::Null => Ok(value.clone()),
63            _ => Err(SQLError::Routine {
64                sqlstate: "42804".into(),
65                message: "cannot cast value to type refcursor".into(),
66            }),
67        },
68        "void" if matches!(value, Value::Null) => Ok(Value::Null),
69        "void" => Err(SQLError::Routine {
70            sqlstate: "42804".into(),
71            message: "cannot cast non-null value to type void".into(),
72        }),
73        _ => {
74            if let Some(target) = context.catalog_column_type(type_name) {
75                return crate::assignment::conversion::coerce_assignment_value(
76                    context,
77                    value.clone(),
78                    &target,
79                    source,
80                );
81            }
82            cast_value(value, type_name)
83        }
84    }
85}