Skip to main content

uqa_sql/plpgsql/
fragments.rs

1//
2// Unified Query Algebra
3//
4// Copyright (c) 2023-2026 Cognica, Inc.
5//
6
7//! Original embedded SQL and its optional definition-time validation tree.
8
9use super::PLpgSQLCursorArgument;
10use crate::{
11    ast::{Expr, Statement},
12    SQLError,
13};
14use std::sync::Arc;
15
16/// `PostgreSQL`'s raw grammar selected for an embedded PL/pgSQL expression.
17#[derive(Debug, Clone, Copy, PartialEq, Eq)]
18pub enum PLpgSQLParseMode {
19    Statement,
20    Expression,
21    Assignment1,
22    Assignment2,
23    Assignment3,
24}
25impl TryFrom<i64> for PLpgSQLParseMode {
26    type Error = SQLError;
27    fn try_from(mode: i64) -> Result<Self, SQLError> {
28        match mode {
29            0 => Ok(Self::Statement),
30            2 => Ok(Self::Expression),
31            3 => Ok(Self::Assignment1),
32            4 => Ok(Self::Assignment2),
33            5 => Ok(Self::Assignment3),
34            _ => Err(SQLError::Internal(format!(
35                "invalid PL/pgSQL parse mode {mode}"
36            ))),
37        }
38    }
39}
40impl PLpgSQLParseMode {
41    pub(super) fn parser_mode(self) -> pg_query::ParseMode {
42        match self {
43            Self::Statement => pg_query::ParseMode::Default,
44            Self::Expression => pg_query::ParseMode::PlPgSqlExpr,
45            Self::Assignment1 => pg_query::ParseMode::PlPgSqlAssign1,
46            Self::Assignment2 => pg_query::ParseMode::PlPgSqlAssign2,
47            Self::Assignment3 => pg_query::ParseMode::PlPgSqlAssign3,
48        }
49    }
50}
51
52/// One syntactic occurrence, shared by clones but never interned by its text.
53/// This carries no execution state; the execution owner supplies the cache.
54#[derive(Debug)]
55pub struct PLpgSQLSource {
56    pub query: Arc<str>,
57    pub mode: PLpgSQLParseMode,
58}
59
60/// The validation tree is absent when `PostgreSQL` compiles a runtime structure
61/// without checking SQL that execution might never reach.
62#[derive(Debug, Clone)]
63pub struct PLpgSQLFragment<T> {
64    source: Arc<PLpgSQLSource>,
65    validation: Option<Arc<T>>,
66}
67impl<T> PLpgSQLFragment<T> {
68    pub(super) fn new(source: Arc<PLpgSQLSource>, validation: Option<T>) -> Self {
69        Self {
70            source,
71            validation: validation.map(Arc::new),
72        }
73    }
74    pub fn source(&self) -> &Arc<PLpgSQLSource> {
75        &self.source
76    }
77    pub fn validation(&self) -> Option<&T> {
78        self.validation.as_deref()
79    }
80    /// Identity is valid for the lifetime of the source-owning compiled body.
81    pub fn site(&self) -> usize {
82        Arc::as_ptr(&self.source) as usize
83    }
84}
85pub type PLpgSQLExpression = PLpgSQLFragment<Expr>;
86pub type PLpgSQLStatement = PLpgSQLFragment<Statement>;
87pub type PLpgSQLCursorArguments = PLpgSQLFragment<Vec<PLpgSQLCursorArgument>>;
88
89impl PLpgSQLExpression {
90    pub fn parse_statement(&self) -> Result<Statement, SQLError> {
91        self.parse()
92            .map(super::variable_conflicts::expression_query)
93    }
94    pub fn parse(&self) -> Result<Expr, SQLError> {
95        super::lowering_expression::compile_expression_source(&self.source)
96    }
97}
98impl PLpgSQLStatement {
99    pub fn parse(&self) -> Result<Statement, SQLError> {
100        super::lowering_expression::compile_statement_source(&self.source)
101    }
102}
103impl PLpgSQLCursorArguments {
104    pub fn parse_statement(&self) -> Result<Statement, SQLError> {
105        let arguments = self.parse()?;
106        let Statement::Select(mut query) =
107            super::variable_conflicts::expression_query(Expr::Literal(uqa_core::Value::Null))
108        else {
109            unreachable!()
110        };
111        query.projections = arguments
112            .into_iter()
113            .map(|argument| crate::ast::Projection {
114                expr: argument.expr,
115                alias: argument.name,
116            })
117            .collect();
118        Ok(Statement::Select(query))
119    }
120    pub fn parse(&self) -> Result<Vec<PLpgSQLCursorArgument>, SQLError> {
121        super::lowering_expression::compile_cursor_arguments_source(&self.source)
122    }
123    pub fn empty() -> Self {
124        Self::new(
125            Arc::new(PLpgSQLSource {
126                query: Arc::from(""),
127                mode: PLpgSQLParseMode::Expression,
128            }),
129            Some(Vec::new()),
130        )
131    }
132}
133
134/// Only a clone of the same compilation shares first-use sites. A new parse,
135/// including replacement or a specialization, receives a fresh identity.
136#[derive(Debug, Clone, Default)]
137pub struct PLpgSQLCompilationIdentity(Arc<()>);
138impl PLpgSQLCompilationIdentity {
139    pub fn same(&self, other: &Self) -> bool {
140        Arc::ptr_eq(&self.0, &other.0)
141    }
142}
143
144#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
145pub enum PLpgSQLCompileMode {
146    #[default]
147    Validate,
148    Runtime,
149}
150impl PLpgSQLCompileMode {
151    pub(super) const fn validates(self) -> bool {
152        matches!(self, Self::Validate)
153    }
154}
155
156#[cfg(test)]
157mod tests;