Skip to main content

uqa_sql/
prepared.rs

1//
2// Unified Query Algebra
3//
4// Copyright (c) 2023-2026 Cognica, Inc.
5//
6
7//! Prepared definitions, result descriptors, and SQL EXECUTE argument contracts.
8
9use crate::{ColumnType, RowSchema, SQLError, SQLParam};
10use uqa_core::Value;
11pub mod arguments;
12pub mod definition;
13pub mod entry;
14pub mod planning;
15
16pub fn declared_parameter_types(
17    resolver: &dyn crate::FunctionTypeResolver,
18    logical_plan: &mut crate::plan::UnifiedPlan,
19    declared: &[ColumnType],
20) -> Result<Vec<Option<ColumnType>>, SQLError> {
21    let mut parameter_types = declared
22        .iter()
23        .map(|ty| match ty {
24            crate::ast::ColumnType::Named(name) if is_unknown_type(name)? => Ok(None),
25            _ => crate::type_resolution::resolve_declared_column_type(resolver, ty).map(Some),
26        })
27        .collect::<Result<Vec<_>, _>>()?;
28    logical_plan.rewrite_scalar_expressions(&mut |expression| {
29        if let crate::ScalarExpr::Param(index) = expression {
30            parameter_types.resize(parameter_types.len().max(*index), None);
31        }
32    });
33    Ok(parameter_types)
34}
35
36fn is_unknown_type(name: &str) -> Result<bool, crate::SQLError> {
37    Ok(crate::parse_regtype_name(name)?.is_some_and(|parsed| {
38        parsed.array_dimensions == 0
39            && !parsed.has_type_modifiers
40            && match parsed.names.as_slice() {
41                [local] => local == "unknown",
42                [schema, local] => schema == "pg_catalog" && local == "unknown",
43                _ => false,
44            }
45    }))
46}
47
48pub fn analyze_prepared_plan(
49    routines: &dyn crate::routines::RoutineResolution,
50    plan: &crate::plan::UnifiedPlan,
51    parameter_types: &[Option<ColumnType>],
52    scope: &crate::binding::context::BindingContext<'_>,
53) -> Result<Option<RowSchema>, SQLError> {
54    let params = parameter_types
55        .iter()
56        .map(|ty| match ty {
57            Some(ty) => SQLParam::typed_scalar(Value::Null, ty.clone()),
58            None => SQLParam::Scalar(Value::Null),
59        })
60        .collect::<Vec<_>>();
61    match plan {
62        crate::plan::UnifiedPlan::Query(query) => {
63            crate::binding::analyze_query_plan_schema(routines, query, &params, scope, None)
64                .map(Some)
65        }
66        crate::plan::UnifiedPlan::Command(command) => {
67            crate::binding::analyze_prepared_command_schema(routines, command, &params, scope)
68        }
69    }
70}
71
72pub fn prepared_result_schema_matches(left: Option<&RowSchema>, right: Option<&RowSchema>) -> bool {
73    match (left, right) {
74        (None, None) => true,
75        (Some(left), Some(right)) => {
76            left.columns() == right.columns()
77                && left.column_types().len() == right.column_types().len()
78                && left
79                    .column_types()
80                    .iter()
81                    .zip(right.column_types())
82                    .all(|(left, right)| {
83                        prepared_type_identity(left.as_ref())
84                            == prepared_type_identity(right.as_ref())
85                    })
86        }
87        _ => false,
88    }
89}
90
91fn prepared_type_identity(ty: Option<&ColumnType>) -> Option<(u32, i32)> {
92    ty.map(|ty| {
93        if let ColumnType::Domain { oid, .. } = ty {
94            (*oid, -1)
95        } else {
96            let metadata = crate::catalog::result_type::postgres_result_type(ty);
97            (metadata.type_oid, metadata.type_modifier)
98        }
99    })
100}
101
102pub fn statement_error(sqlstate: &str, name: &str, reason: &str) -> SQLError {
103    error(sqlstate, format!("prepared statement \"{name}\" {reason}"))
104}
105
106pub fn execute_parameter_types(
107    name: &str,
108    types: Option<Vec<Option<ColumnType>>>,
109    argument_count: usize,
110) -> Result<Vec<Option<ColumnType>>, SQLError> {
111    let types = types.ok_or_else(|| statement_error("26000", name, "does not exist"))?;
112    // SQL EXECUTE ignores argument lists for parameterless definitions; protocol Bind checks its own arity.
113    if types.is_empty() {
114        return Ok(Vec::new());
115    }
116    if argument_count != types.len() {
117        return Err(error(
118            "42601",
119            format!("wrong number of parameters for prepared statement \"{name}\""),
120        ));
121    }
122    Ok(types)
123}
124
125pub(super) fn error(sqlstate: &str, message: impl Into<String>) -> SQLError {
126    SQLError::Routine {
127        sqlstate: sqlstate.into(),
128        message: message.into(),
129    }
130}
131
132#[cfg(test)]
133mod tests;