Skip to main content

uqa_sql/ast/
cte.rs

1//
2// Unified Query Algebra
3//
4// Copyright (c) 2023-2026 Cognica, Inc.
5//
6
7//! Common-table-expression syntax and recursive traversal controls.
8
9use serde::{Deserialize, Serialize};
10
11use super::{DeleteStmt, Expr, InsertStmt, MergeStmt, SelectStmt, Statement, UpdateStmt};
12
13#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
14pub struct CTE {
15    pub name: String,
16    pub columns: Vec<String>,
17    pub recursive: bool,
18    #[serde(default)]
19    pub materialization: CteMaterialization,
20    #[serde(default, skip_serializing_if = "Option::is_none")]
21    pub search: Option<CteSearchClause>,
22    #[serde(default, skip_serializing_if = "Option::is_none")]
23    pub cycle: Option<CteCycleClause>,
24    #[serde(flatten)]
25    pub body: CteBody,
26}
27
28/// A CTE owns a query or a data-modifying statement. The serialized `query` arm retains the original SELECT-only catalog representation.
29#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
30pub enum CteBody {
31    #[serde(rename = "query")]
32    Query(Box<SelectStmt>),
33    #[serde(rename = "insert")]
34    Insert(Box<InsertStmt>),
35    #[serde(rename = "update")]
36    Update(Box<UpdateStmt>),
37    #[serde(rename = "delete")]
38    Delete(Box<DeleteStmt>),
39    #[serde(rename = "merge")]
40    Merge(Box<MergeStmt>),
41}
42
43impl CteBody {
44    pub fn query(&self) -> Option<&SelectStmt> {
45        match self {
46            Self::Query(query) => Some(query),
47            _ => None,
48        }
49    }
50
51    pub fn query_mut(&mut self) -> Option<&mut SelectStmt> {
52        match self {
53            Self::Query(query) => Some(query),
54            _ => None,
55        }
56    }
57
58    pub const fn modifies_data(&self) -> bool {
59        !matches!(self, Self::Query(_))
60    }
61
62    pub fn returning(&self) -> Option<&[super::Projection]> {
63        match self {
64            Self::Query(_) => None,
65            Self::Insert(command) => Some(&command.returning),
66            Self::Update(command) => Some(&command.returning),
67            Self::Delete(command) => Some(&command.returning),
68            Self::Merge(command) => Some(&command.returning),
69        }
70    }
71
72    pub fn into_statement(self) -> Statement {
73        match self {
74            Self::Query(query) => Statement::Select(query),
75            Self::Insert(command) => Statement::Insert(*command),
76            Self::Update(command) => Statement::Update(*command),
77            Self::Delete(command) => Statement::Delete(*command),
78            Self::Merge(command) => Statement::Merge(*command),
79        }
80    }
81}
82
83impl TryFrom<Statement> for CteBody {
84    type Error = crate::SQLError;
85
86    fn try_from(statement: Statement) -> Result<Self, Self::Error> {
87        match statement {
88            Statement::Select(query) => Ok(Self::Query(query)),
89            Statement::Insert(command) => Ok(Self::Insert(Box::new(command))),
90            Statement::Update(command) => Ok(Self::Update(Box::new(command))),
91            Statement::Delete(command) => Ok(Self::Delete(Box::new(command))),
92            Statement::Merge(command) => Ok(Self::Merge(Box::new(command))),
93            _ => Err(crate::SQLError::Unsupported(
94                "WITH body must be a SELECT, INSERT, UPDATE, DELETE, or MERGE statement".into(),
95            )),
96        }
97    }
98}
99
100/// The planning fence requested for one common-table expression.
101#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize)]
102pub enum CteMaterialization {
103    #[default]
104    Default,
105    Materialized,
106    NotMaterialized,
107}
108
109/// `PostgreSQL` recursive-CTE traversal-order metadata.
110#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
111pub struct CteSearchClause {
112    pub columns: Vec<String>,
113    pub breadth_first: bool,
114    pub sequence_column: String,
115}
116
117/// `PostgreSQL` recursive-CTE cycle detection metadata.
118#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
119pub struct CteCycleClause {
120    pub columns: Vec<String>,
121    pub mark_column: String,
122    pub mark_value: Expr,
123    pub mark_default: Expr,
124    pub path_column: String,
125}