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