Skip to main content

rupg_analyze/
plpgsql.rs

1//! The hooks of PL/pgSQL in the analysis: the variables of a function that a query names, as `plpgsql_post_column_ref` and `plpgsql_param_ref` of `pl_comp.c` find them, and the casts of the values of the variables, as `exec_cast_value` of `pl_exec.c` makes them.
2
3use rupg_common::{Error, Result, SqlState};
4use rupg_sql::nodes::{ColumnRef, List, Node, RangeVar};
5use rupg_types::oid;
6
7use crate::coerce::{AtOpt, Context};
8use crate::expr::{Expr, ExprKind};
9use crate::select::{Query, Target};
10use crate::{Analyzer, Env, Params, types};
11
12/// The target of an assignment of PL/pgSQL, whose type the value takes in the analysis.
13pub(crate) struct Assign {
14    pub(crate) ty: u32,
15    pub(crate) typmod: i32,
16    /// The name of the variable, for the errors.
17    pub(crate) name: String,
18    /// The variable and the subscripts after it, for an assignment to a part of the variable, as `x[1] := 5`.
19    pub(crate) indirect: Option<(Expr, List)>,
20}
21
22/// A variable of a function that a query reads as a parameter.
23#[derive(Clone, Copy, Debug, PartialEq, Eq)]
24pub struct Variable {
25    /// The number of the parameter, from 1.
26    pub param: usize,
27    pub ty: u32,
28    pub typmod: i32,
29}
30
31/// The variables of a PL/pgSQL function that the queries of the function can name.
32pub trait Variables {
33    /// `resolve_column_ref`: the variable that the names of a column reference name, such as `x`, `r.f` or `block.x`, or `None` when no variable has the names. `*` is not a name here.
34    ///
35    /// # Errors
36    ///
37    /// The error of a field that a record does not have, and of a record with no value yet.
38    fn column(&self, names: &[&str], at: Option<usize>) -> Result<Option<Variable>>;
39    /// `plpgsql_param_ref`: the variable of `$n`, which is the argument `n` of the function, or `None`.
40    fn param(&self, number: usize) -> Option<Variable>;
41}
42
43/// [`crate::analyze`] of a query of a PL/pgSQL function, whose names can be variables of the function. A name that is a column and a variable is an error, as `plpgsql.variable_conflict` with the value `error` gives it.
44///
45/// # Errors
46///
47/// The errors of the analysis, and `42702` for a name that is a column and a variable.
48pub fn analyze_with(
49    stmt: &Node,
50    env: &dyn Env,
51    params: &Params,
52    variables: &dyn Variables,
53) -> Result<Query> {
54    crate::analyze_hooked(stmt, env, params, Some(variables))
55}
56
57/// `transformPLAssignStmt`: [`analyze_with`] of the value of an assignment to the variable `name` of the type `ty` with the typmod `typmod`. The value takes the type of the variable with the casts of PL/pgSQL, so that the error of a cast points into the assignment.
58///
59/// # Errors
60///
61/// The errors of [`analyze_with`], `42601` for a value that is not one column and `42804` for a value that has no cast to the type of the variable.
62pub fn analyze_assign(
63    stmt: &Node,
64    env: &dyn Env,
65    variables: &dyn Variables,
66    (ty, typmod): (u32, i32),
67    name: &str,
68) -> Result<Query> {
69    let assign = Assign { ty, typmod, name: name.to_owned(), indirect: None };
70    crate::analyze_assigned(Analyzer::new(env, &Params::default()), stmt, variables, assign)
71}
72
73/// `transformPLAssignStmt` of an assignment to a part of a variable, as `x[i] := 5` and `r.f[1:2] := a`: [`analyze_with`] of the `PLAssignStmt` that [`rupg_sql::parse_plpgsql_assign`] gives. The value of the query is the new value of the variable, which is the array with the new value of the element or of the slice.
74///
75/// # Errors
76///
77/// The errors of [`analyze_with`], `42601` for a value that is not one column, and `42804` for a target that has no subscripts and for a value that has no cast to the type of the element or of the slice.
78pub fn analyze_assign_part(stmt: &Node, env: &dyn Env, variables: &dyn Variables) -> Result<Query> {
79    let Node::PLAssignStmt(s) = stmt else {
80        return Err(Error::internal("an assignment is not a PLAssignStmt"));
81    };
82    let Some(val) = &s.val else { return Err(Error::internal("an assignment has no value")) };
83    let name = s.name.as_deref().unwrap_or_default();
84    // The names after the first name of the target are at the start of the indirection.
85    let split = usize::try_from(s.nnames).unwrap_or(1).saturating_sub(1).min(s.indirection.len());
86    let (names, indirection) = s.indirection.split_at(split);
87    let mut fields = vec![Some(Node::String(name.into()))];
88    fields.extend(names.iter().cloned());
89    let cref = Node::ColumnRef(Box::new(ColumnRef { fields, location: s.location }));
90    let mut analyzer = Analyzer::new(env, &Params::default());
91    analyzer.variables = Some(variables);
92    let target = analyzer.transform(Some(&cref))?;
93    let (ty, typmod) = (target.ty, target.typmod);
94    let indirect = Some((target, indirection.to_vec()));
95    let assign = Assign { ty, typmod, name: name.to_owned(), indirect };
96    crate::analyze_assigned(analyzer, &Node::SelectStmt(val.clone()), variables, assign)
97}
98
99impl Analyzer<'_> {
100    /// `transformPLAssignStmtTarget`: the value of an assignment, coerced to the type of the variable. A row value for a row variable of another row type stays as it is, and the executor converts it.
101    pub(crate) fn assign_target(&mut self, targets: &mut [Target], assign: Assign) -> Result<()> {
102        let Assign { ty, typmod, name, indirect } = assign;
103        let [target] = targets else {
104            let n = targets.len();
105            let word = if n == 1 { "column" } else { "columns" };
106            return Err(Error::new(
107                SqlState::SYNTAX_ERROR,
108                format!("assignment source returned {n} {word}"),
109            ));
110        };
111        if let Some((base, indirection)) = indirect {
112            let rhs = std::mem::replace(&mut target.expr, Expr::new(ExprKind::CaseTest, 0));
113            let at = base.place();
114            let parts = (base, &name[..], (ty, typmod));
115            target.expr =
116                self.assignment_indirection(parts, &indirection, rhs, Context::Plpgsql, at)?;
117            return Ok(());
118        }
119        let source = target.expr.ty;
120        let composite = |t: u32| t == oid::RECORD || types::is_complex(t);
121        if ty != source && composite(ty) && composite(source) {
122            return Ok(());
123        }
124        let expr = std::mem::replace(&mut target.expr, Expr::new(ExprKind::CaseTest, 0));
125        let at = expr.location;
126        let Some(expr) = self.coerce_to_target(expr, ty, typmod, Context::Plpgsql, None)? else {
127            return Err(Error::new(
128                SqlState::DATATYPE_MISMATCH,
129                format!(
130                    "variable \"{name}\" is of type {} but expression is of type {}",
131                    types::name(ty),
132                    types::name(source)
133                ),
134            )
135            .with_hint("You will need to rewrite or cast the expression.")
136            .at_opt(at));
137        };
138        target.expr = expr;
139        Ok(())
140    }
141}
142
143/// `get_cast_hashentry`: the query `SELECT $1` that casts a value of the type `source` to the type `target` with the typmod `typmod`, in the context of PL/pgSQL, where the output and the input functions cast a value when no other cast exists.
144///
145/// # Errors
146///
147/// `42846` when there is no cast, as for a type that is `internal`.
148pub fn plpgsql_cast(env: &dyn Env, source: (u32, i32), target: u32, typmod: i32) -> Result<Query> {
149    let mut analyzer = Analyzer::new(env, &Params { types: vec![source.0], variable: false });
150    let param = Expr { kind: ExprKind::Param(1), ty: source.0, typmod: source.1, location: None };
151    let Some(expr) = analyzer.coerce_to_target(param, target, typmod, Context::Plpgsql, None)?
152    else {
153        return Err(Error::new(
154            SqlState::CANNOT_COERCE,
155            format!("cannot cast type {} to {}", types::name(source.0), types::name(target)),
156        ));
157    };
158    let target = Target { name: "?column?".into(), expr, origin: None, junk: false };
159    Ok(Query { targets: vec![target], params: vec![source.0], ..Query::default() })
160}
161
162/// The name, the type and the typmod of each field of a row type, as `build_row_from_rowtype` reads them, or `None` for a type that is not the row type of a relation. A dropped column has no name and the type 0.
163pub fn row_type_fields(env: &dyn Env, ty: u32) -> Option<Vec<(String, u32, i32)>> {
164    let analyzer = Analyzer::new(env, &Params::default());
165    let columns = analyzer.row_columns(ty)?;
166    Some(columns.into_iter().map(|c| (c.name, c.ty, c.typmod)).collect())
167}
168
169/// The OID of the relation with the qualified name `names`, as `RangeVarGetRelid` finds it with the search path, or `None`.
170///
171/// # Errors
172///
173/// `0A000` for a name with a database that is not the current database.
174pub fn relation_by_names(env: &dyn Env, names: &[&str]) -> Result<Option<u32>> {
175    let mut rv = RangeVar::default();
176    match names {
177        [rel] => rv.relname = Some((*rel).into()),
178        [schema, rel] => {
179            rv.schemaname = Some((*schema).into());
180            rv.relname = Some((*rel).into());
181        }
182        [database, schema, rel] => {
183            rv.catalogname = Some((*database).into());
184            rv.schemaname = Some((*schema).into());
185            rv.relname = Some((*rel).into());
186        }
187        _ => return Ok(None),
188    }
189    Analyzer::new(env, &Params::default()).lookup_relation(&rv)
190}
191
192/// `plpgsql_parse_cwordtype`: the type and the typmod of the column `column` of the relation with the qualified name `names`, or `None` when the relation or the column does not exist.
193///
194/// # Errors
195///
196/// The errors of [`relation_by_names`].
197pub fn column_type(env: &dyn Env, names: &[&str], column: &str) -> Result<Option<(u32, i32)>> {
198    let Some(relid) = relation_by_names(env, names)? else { return Ok(None) };
199    let analyzer = Analyzer::new(env, &Params::default());
200    let Ok(columns) = analyzer.open_relation(relid, "", None) else { return Ok(None) };
201    Ok(columns.into_iter().find(|c| c.name == column).map(|c| (c.ty, c.typmod)))
202}
203
204/// `plpgsql_parse_wordrowtype`: the row type of the relation with the qualified name `names`, or `None`.
205///
206/// # Errors
207///
208/// The errors of [`relation_by_names`].
209pub fn relation_row_type(env: &dyn Env, names: &[&str]) -> Result<Option<u32>> {
210    let Some(relid) = relation_by_names(env, names)? else { return Ok(None) };
211    if let Some(rel) = env.catalog().and_then(|c| c.relation(relid)) {
212        return Ok(Some(rel.row_type).filter(|&ty| ty != 0));
213    }
214    Ok(rupg_pgcatalog::builtin::types().iter().find(|t| t.relid == relid).map(|t| t.oid))
215}
216
217impl Analyzer<'_> {
218    /// `plpgsql_post_column_ref` around `transformColumnRef`: the column, or the variable when no column has the name. A name that is both is an error.
219    pub(crate) fn column_or_variable(
220        &mut self,
221        c: &ColumnRef,
222        variables: &dyn Variables,
223    ) -> Result<Expr> {
224        let at = crate::typename::place(c.location);
225        let names: Option<Vec<&str>> = c
226            .fields
227            .iter()
228            .map(|f| match f {
229                Some(Node::String(s)) => Some(&**s),
230                _ => None,
231            })
232            .collect();
233        let core = self.column_ref(c);
234        let missing =
235            |e: &Error| matches!(e.state(), SqlState::UNDEFINED_COLUMN | SqlState::UNDEFINED_TABLE);
236        let Some(names) = names else {
237            // `resolve_column_ref` for `r.*`: the whole value of a variable of a composite type.
238            if let Some((Some(Node::A_Star(_)), prefix)) = c.fields.split_last()
239                && let Err(error) = &core
240                && missing(error)
241            {
242                let prefix: Option<Vec<&str>> = prefix
243                    .iter()
244                    .map(|f| match f {
245                        Some(Node::String(s)) => Some(&**s),
246                        _ => None,
247                    })
248                    .collect();
249                if let Some(prefix) = prefix.filter(|p| !p.is_empty())
250                    && let Some(variable) = self.composite_variable(&prefix, at)?
251                {
252                    return Ok(self.variable_param(variable, at));
253                }
254            }
255            return core;
256        };
257        match core {
258            Ok(expr) => match variables.column(&names, at)? {
259                Some(_) => Err(Error::new(
260                    SqlState::AMBIGUOUS_COLUMN,
261                    format!("column reference \"{}\" is ambiguous", names.join(".")),
262                )
263                .with_detail("It could refer to either a PL/pgSQL variable or a table column.")
264                .at_opt(at)),
265                None => Ok(expr),
266            },
267            Err(error) if missing(&error) => match variables.column(&names, at)? {
268                Some(variable) => Ok(self.variable_param(variable, at)),
269                None => Err(error),
270            },
271            Err(error) => Err(error),
272        }
273    }
274
275    /// The variable of the PL/pgSQL function with the names `names` when its type is composite or `record`, as `resolve_column_ref` takes a row or a record for `r.*`, or `None`.
276    pub(crate) fn composite_variable(
277        &self,
278        names: &[&str],
279        at: Option<usize>,
280    ) -> Result<Option<Variable>> {
281        let Some(variables) = self.variables else { return Ok(None) };
282        let Some(variable) = variables.column(names, at)? else { return Ok(None) };
283        let composite =
284            variable.ty == oid::RECORD || self.row_columns(types::base(variable.ty)).is_some();
285        Ok(composite.then_some(variable))
286    }
287
288    /// The parameter of a variable. The parameters of the query grow to its number, and it gets the type of the variable.
289    pub(crate) fn variable_param(&mut self, variable: Variable, at: Option<usize>) -> Expr {
290        if self.params.len() < variable.param {
291            self.params.resize(variable.param, 0);
292        }
293        self.params[variable.param - 1] = variable.ty;
294        Expr {
295            kind: ExprKind::Param(variable.param),
296            ty: variable.ty,
297            typmod: variable.typmod,
298            location: at,
299        }
300    }
301}