Skip to main content

uqa_sql/plpgsql/
parameters.rs

1//
2// Unified Query Algebra
3//
4// Copyright (c) 2023-2026 Cognica, Inc.
5//
6
7//! Bind procedural variables as invocation parameters, preserving catalog inputs.
8
9use super::{
10    binding::{bind_statement, ResolvedVariable, VariableResolver},
11    variable_conflicts::{statement_variable_names, VariableSiteResolver},
12    VariableConflict,
13};
14use crate::{
15    ast::{Expr, Statement},
16    SQLError, SQLParam,
17};
18
19#[derive(Debug, Clone)]
20pub enum PLpgSQLVariableReference {
21    Name(String),
22    Qualified { qualifier: String, column: String },
23    Parameter(usize),
24}
25impl PLpgSQLVariableReference {
26    pub fn read(&self, resolver: &mut dyn VariableResolver) -> Result<SQLParam, SQLError> {
27        let value = match self {
28            Self::Name(name) => resolver.resolve_name(name),
29            Self::Qualified { qualifier, column } => resolver.resolve_qualified(qualifier, column),
30            Self::Parameter(index) => resolver.resolve_param(*index),
31        }?
32        .ok_or_else(|| {
33            SQLError::Internal("prepared PL/pgSQL variable no longer resolves".into())
34        })?;
35        Ok(parameter(value, resolver))
36    }
37}
38fn parameter(value: ResolvedVariable, resolver: &dyn VariableResolver) -> SQLParam {
39    match value
40        .declared_type
41        .as_deref()
42        .and_then(|name| resolver.parameter_type(name))
43    {
44        Some(ty) => SQLParam::typed_scalar(value.value, ty),
45        None => SQLParam::scalar(value.value),
46    }
47}
48
49pub struct PLpgSQLVariableBindings {
50    pub statement: Statement,
51    pub references: Vec<PLpgSQLVariableReference>,
52    pub parameters: Vec<SQLParam>,
53}
54
55/// Reuse the ordinary PL name/column conflict resolver, then number only names
56/// it selected as variables. Repeated reads receive fresh invocation values.
57pub fn parameterize_statement_variables(
58    statement: &Statement,
59    resolver: &mut dyn VariableResolver,
60    conflict: VariableConflict,
61    resolve_sites: VariableSiteResolver<'_>,
62) -> Result<PLpgSQLVariableBindings, SQLError> {
63    let keep = statement_variable_names(statement, resolver, conflict, resolve_sites)?;
64    let mut binding = VariableBindings {
65        inner: resolver,
66        keep: &keep,
67        next_name: 0,
68        references: Vec::new(),
69        parameters: Vec::new(),
70    };
71    let statement = bind_statement(statement, &mut binding)?;
72    Ok(PLpgSQLVariableBindings {
73        statement,
74        references: binding.references,
75        parameters: binding.parameters,
76    })
77}
78struct VariableBindings<'a> {
79    inner: &'a mut dyn VariableResolver,
80    keep: &'a [bool],
81    next_name: usize,
82    references: Vec<PLpgSQLVariableReference>,
83    parameters: Vec<SQLParam>,
84}
85impl VariableBindings<'_> {
86    fn mark(
87        &mut self,
88        reference: PLpgSQLVariableReference,
89        value: Option<ResolvedVariable>,
90        named: bool,
91    ) -> Option<Expr> {
92        let value = value?;
93        if named {
94            let site = self.next_name;
95            self.next_name += 1;
96            if self.keep.get(site).copied().unwrap_or(false) {
97                return None;
98            }
99        }
100        self.references.push(reference);
101        self.parameters.push(parameter(value, self.inner));
102        Some(Expr::Param(self.references.len()))
103    }
104}
105impl VariableResolver for VariableBindings<'_> {
106    fn resolve_name(&mut self, name: &str) -> Result<Option<ResolvedVariable>, SQLError> {
107        self.inner.resolve_name(name)
108    }
109    fn resolve_qualified(
110        &mut self,
111        qualifier: &str,
112        column: &str,
113    ) -> Result<Option<ResolvedVariable>, SQLError> {
114        self.inner.resolve_qualified(qualifier, column)
115    }
116    fn resolve_param(&mut self, index: usize) -> Result<Option<ResolvedVariable>, SQLError> {
117        self.inner.resolve_param(index)
118    }
119    fn rewrite_name(&mut self, name: &str) -> Result<Option<Expr>, SQLError> {
120        let value = self.inner.resolve_name(name)?;
121        Ok(self.mark(
122            PLpgSQLVariableReference::Name(name.to_string()),
123            value,
124            true,
125        ))
126    }
127    fn rewrite_qualified(
128        &mut self,
129        qualifier: &str,
130        column: &str,
131    ) -> Result<Option<Expr>, SQLError> {
132        let value = self.inner.resolve_qualified(qualifier, column)?;
133        Ok(self.mark(
134            PLpgSQLVariableReference::Qualified {
135                qualifier: qualifier.to_string(),
136                column: column.to_string(),
137            },
138            value,
139            true,
140        ))
141    }
142    fn rewrite_param(&mut self, index: usize) -> Result<Option<Expr>, SQLError> {
143        let value = self.inner.resolve_param(index)?;
144        Ok(self.mark(PLpgSQLVariableReference::Parameter(index), value, false))
145    }
146}
147
148#[cfg(test)]
149mod tests;