Skip to main content

rupg_analyze/
call.rs

1//! `transformCallStmt` of `analyze.c`: the call of a procedure.
2
3use rupg_common::{Error, Result};
4use rupg_pgcatalog::builtin;
5use rupg_sql::nodes::CallStmt;
6use rupg_types::oid;
7
8use crate::Analyzer;
9use crate::agg::Kind;
10use crate::expr::{Expr, ExprKind, Var};
11use crate::from::{FromFunction, FromItem, Relation};
12use crate::funcargs::{all_arguments, expand};
13use crate::select::{Query, Target};
14use crate::typename::{names, place};
15
16/// An argument of `CALL` for an `INOUT` or `OUT` argument of the procedure. A `CALL` in PL/pgSQL writes the value of the argument back to it, so it must be a variable.
17#[derive(Clone, Debug, PartialEq)]
18pub struct OutArg {
19    /// The argument after the coercion to the type of the argument of the procedure.
20    pub expr: Expr,
21    /// The name of the argument of the procedure, if it has one.
22    pub name: Option<String>,
23    /// The place of the argument in the arguments of the procedure, from 1.
24    pub position: usize,
25}
26
27impl Analyzer<'_> {
28    /// `transformCallStmt`: `CALL`, as a query that calls the procedure. The arguments of the call go in the order of the procedure, with the defaults, and the call keeps only the input arguments. The expressions of the output arguments are analyzed, but the call does not read them.
29    ///
30    /// A procedure with no output arguments gives a query with one target, which gives `void`. A procedure with output arguments gives a query that reads the call as a function in `FROM`, with a column for each output argument.
31    pub(crate) fn call(&mut self, stmt: &CallStmt) -> Result<Query> {
32        let Some(fc) = &stmt.funccall else { return Err(Error::internal("CALL with no call")) };
33        let at = place(fc.location);
34        let func_names = names(&fc.funcname);
35        let args = self.with_kind(Kind::CallArgument, |a| a.transform_list(&fc.args))?;
36        let mut call = self.make_func(&func_names, args, fc.func_variadic, None, true, at)?;
37        let ExprKind::Func(func) = &mut call.kind else {
38            return Err(Error::internal("CALL of a procedure that is not a function call"));
39        };
40        let proc = builtin::proc_by_oid(func.oid).expect("procedure in pg_proc");
41        let args = expand(&func.args, &all_arguments(proc))?.into_owned();
42        let out_args = args
43            .iter()
44            .zip(proc.argmodes.unwrap_or_default())
45            .zip(1..)
46            .filter(|((_, mode), _)| matches!(**mode, b'b' | b'o'))
47            .map(|((arg, _), position)| OutArg {
48                expr: arg.clone(),
49                name: proc
50                    .argnames
51                    .and_then(|names| names.get(position - 1))
52                    .filter(|name| !name.is_empty())
53                    .map(|name| String::from(*name)),
54                position,
55            })
56            .collect();
57        func.args = match proc.argmodes {
58            Some(modes) => args
59                .into_iter()
60                .zip(modes)
61                .filter(|(_, mode)| **mode != b'o')
62                .map(|(arg, _)| arg)
63                .collect(),
64            None => args,
65        };
66        let columns = crate::record::out_columns(func)?;
67        let name = proc.name.to_string();
68        let Some(columns) = columns.filter(|_| call.ty != oid::VOID) else {
69            let target = Target { name, expr: call, origin: None, junk: false };
70            return Ok(Query { targets: vec![target], out_args, ..Query::default() });
71        };
72        let targets = columns
73            .iter()
74            .zip(1..)
75            .map(|(column, attnum)| {
76                let var = Var { relation: 0, attnum, levels_up: 0 };
77                let mut expr = Expr::new(ExprKind::Var(var), column.ty);
78                expr.typmod = column.typmod;
79                Target { name: column.name.clone(), expr, origin: None, junk: false }
80            })
81            .collect();
82        let function = FromFunction {
83            calls: vec![(call, columns.len())],
84            ordinality: false,
85            coldefs: vec![false],
86        };
87        let mut relation = Relation::new(0, columns, None, Some(function), None);
88        relation.name = name;
89        Ok(Query {
90            relations: vec![relation],
91            from: vec![FromItem::Relation(0)],
92            targets,
93            out_args,
94            ..Query::default()
95        })
96    }
97}