Skip to main content

uqa_sql/compiler/
dispatch.rs

1//
2// Unified Query Algebra
3//
4// Copyright (c) 2023-2026 Cognica, Inc.
5//
6
7//! Parser entry point and exhaustive statement-family dispatch.
8
9use super::administrative::{
10    compile_analyze, compile_explain, compile_set_constraints, compile_transaction,
11    compile_truncate, compile_variable_set, discard_target,
12};
13use super::cursors::{compile_close_cursor, compile_declare_cursor, compile_fetch_cursor};
14use super::dml::{compile_delete, compile_update};
15use super::drop_alter::{
16    compile_alter_object_schema, compile_alter_table, compile_drop, compile_rename,
17};
18use super::events::{compile_create_rule, compile_create_trigger};
19use super::merge::compile_merge;
20use super::relations::{
21    compile_create_foreign_server, compile_create_foreign_table, compile_create_schema,
22    compile_create_table_as, compile_create_view, compile_deallocate, compile_execute,
23    compile_prepare, compile_refresh_materialized_view, compile_top_level_select,
24    defer_create_foreign_table, defer_create_table,
25};
26use super::routines::{
27    compile_alter_role, compile_alter_routine, compile_alter_routine_owner, compile_call,
28    compile_create_function, compile_create_role, compile_do, compile_drop_role, compile_grant,
29    compile_grant_role,
30};
31use super::sequences::{compile_alter_sequence, compile_create_sequence};
32use super::{
33    compile_create_index, compile_create_table, compile_insert, compile_values_lists, Node,
34    NodeEnum, Result, SQLError, Statement,
35};
36
37pub fn compile(sql: &str) -> Result<Vec<Statement>> {
38    let parsed = pg_query::parse(sql)?;
39    let mut out = Vec::with_capacity(parsed.protobuf.stmts.len());
40    for raw in parsed.protobuf.stmts {
41        let node = raw
42            .stmt
43            .ok_or_else(|| SQLError::Internal("parser returned an empty statement".into()))?;
44        out.push(compile_stmt(&node)?);
45    }
46    Ok(out)
47}
48
49pub fn resolve_deferred_create_table(
50    deferred: &crate::ast::DeferredCreateTable,
51) -> Result<crate::ast::CreateTable> {
52    let parsed = pg_query::parse(&deferred.definition_sql)?;
53    let [raw] = parsed.protobuf.stmts.as_slice() else {
54        return Err(SQLError::Internal(
55            "deferred CREATE TABLE did not contain exactly one statement".into(),
56        ));
57    };
58    let node = raw
59        .stmt
60        .as_deref()
61        .and_then(|node| node.node.as_ref())
62        .ok_or_else(|| SQLError::Internal("deferred CREATE TABLE is empty".into()))?;
63    let NodeEnum::CreateStmt(stmt) = node else {
64        return Err(SQLError::Internal(
65            "deferred CREATE TABLE changed statement kind".into(),
66        ));
67    };
68    let table = compile_create_table(stmt)?;
69    if !table.if_not_exists
70        || table.name != deferred.name
71        || table.persistence != deferred.persistence
72    {
73        return Err(SQLError::Internal(
74            "deferred CREATE TABLE changed target identity".into(),
75        ));
76    }
77    Ok(table)
78}
79
80pub fn resolve_deferred_create_foreign_table(
81    deferred: &crate::ast::DeferredCreateForeignTable,
82) -> Result<crate::ast::CreateForeignTable> {
83    let parsed = pg_query::parse(&deferred.definition_sql)?;
84    let [raw] = parsed.protobuf.stmts.as_slice() else {
85        return Err(SQLError::Internal(
86            "deferred CREATE FOREIGN TABLE did not contain exactly one statement".into(),
87        ));
88    };
89    let node = raw
90        .stmt
91        .as_deref()
92        .and_then(|node| node.node.as_ref())
93        .ok_or_else(|| SQLError::Internal("deferred CREATE FOREIGN TABLE is empty".into()))?;
94    let NodeEnum::CreateForeignTableStmt(stmt) = node else {
95        return Err(SQLError::Internal(
96            "deferred CREATE FOREIGN TABLE changed statement kind".into(),
97        ));
98    };
99    let table = compile_create_foreign_table(stmt)?;
100    if !table.if_not_exists
101        || table.name != deferred.name
102        || table.server_name != deferred.server_name
103    {
104        return Err(SQLError::Internal(
105            "deferred CREATE FOREIGN TABLE changed target identity".into(),
106        ));
107    }
108    Ok(table)
109}
110
111fn compile_create_table_statement(statement: &pg_query::protobuf::CreateStmt) -> Result<Statement> {
112    if statement.if_not_exists {
113        defer_create_table(statement).map(Statement::CreateTableIfNotExists)
114    } else {
115        compile_create_table(statement).map(Statement::CreateTable)
116    }
117}
118
119fn compile_create_foreign_table_statement(
120    statement: &pg_query::protobuf::CreateForeignTableStmt,
121) -> Result<Statement> {
122    if statement
123        .base_stmt
124        .as_ref()
125        .is_some_and(|base| base.if_not_exists)
126    {
127        defer_create_foreign_table(statement).map(Statement::CreateForeignTableIfNotExists)
128    } else {
129        compile_create_foreign_table(statement).map(Statement::CreateForeignTable)
130    }
131}
132
133pub(super) fn compile_stmt(node: &Node) -> Result<Statement> {
134    let Some(inner) = node.node.as_ref() else {
135        return Err(SQLError::Unsupported("empty statement".into()));
136    };
137    match inner {
138        NodeEnum::CreateStmt(stmt) => compile_create_table_statement(stmt),
139        NodeEnum::IndexStmt(stmt) => compile_create_index(stmt).map(Statement::CreateIndex),
140        NodeEnum::InsertStmt(stmt) => compile_insert(stmt).map(Statement::Insert),
141        NodeEnum::SelectStmt(stmt) => {
142            // Standalone `VALUES (...) (...)` parses as a SelectStmt
143            // with empty target_list + populated values_lists. Treat
144            // it as a relation-producing statement directly.
145            if stmt.target_list.is_empty()
146                && !stmt.values_lists.is_empty()
147                && stmt.locking_clause.is_empty()
148                && stmt.sort_clause.is_empty()
149                && stmt.limit_count.is_none()
150                && stmt.limit_offset.is_none()
151            {
152                let rows = compile_values_lists(&stmt.values_lists)?;
153                return Ok(Statement::Values { rows });
154            }
155            compile_top_level_select(stmt)
156        }
157        NodeEnum::UpdateStmt(stmt) => compile_update(stmt).map(Statement::Update),
158        NodeEnum::DeleteStmt(stmt) => compile_delete(stmt).map(Statement::Delete),
159        NodeEnum::DropStmt(stmt) => compile_drop(stmt),
160        NodeEnum::CreateTrigStmt(stmt) => {
161            compile_create_trigger(stmt).map(Statement::CreateTrigger)
162        }
163        NodeEnum::RuleStmt(stmt) => compile_create_rule(stmt).map(Statement::CreateRule),
164        NodeEnum::AlterTableStmt(stmt) => compile_alter_table(stmt),
165        NodeEnum::RenameStmt(stmt) => compile_rename(stmt),
166        NodeEnum::AlterObjectSchemaStmt(stmt) => compile_alter_object_schema(stmt),
167        NodeEnum::ViewStmt(stmt) => compile_create_view(stmt),
168        NodeEnum::CreateSchemaStmt(stmt) => compile_create_schema(stmt),
169        NodeEnum::NotifyStmt(stmt) => Ok(Statement::Notify {
170            channel: stmt.conditionname.clone(),
171            payload: stmt.payload.clone(),
172        }),
173        NodeEnum::ListenStmt(stmt) => Ok(Statement::Listen {
174            channel: stmt.conditionname.clone(),
175        }),
176        NodeEnum::UnlistenStmt(stmt) => Ok(Statement::Unlisten {
177            channel: (!stmt.conditionname.is_empty()).then(|| stmt.conditionname.clone()),
178        }),
179        NodeEnum::ExplainStmt(stmt) => compile_explain(stmt),
180        NodeEnum::VacuumStmt(stmt) => compile_analyze(stmt),
181        NodeEnum::TruncateStmt(stmt) => compile_truncate(stmt),
182        NodeEnum::TransactionStmt(stmt) => compile_transaction(stmt),
183        NodeEnum::DeclareCursorStmt(stmt) => compile_declare_cursor(stmt),
184        NodeEnum::FetchStmt(stmt) => compile_fetch_cursor(stmt),
185        NodeEnum::ClosePortalStmt(stmt) => Ok(compile_close_cursor(stmt)),
186        NodeEnum::CreateSeqStmt(stmt) => {
187            compile_create_sequence(stmt).map(Statement::CreateSequence)
188        }
189        NodeEnum::AlterSeqStmt(stmt) => compile_alter_sequence(stmt).map(Statement::AlterSequence),
190        NodeEnum::CreateTableAsStmt(stmt) => compile_create_table_as(stmt),
191        NodeEnum::RefreshMatViewStmt(stmt) => compile_refresh_materialized_view(stmt),
192        NodeEnum::PrepareStmt(stmt) => compile_prepare(stmt),
193        NodeEnum::ExecuteStmt(stmt) => compile_execute(stmt),
194        NodeEnum::DeallocateStmt(stmt) => compile_deallocate(stmt),
195        NodeEnum::CreateForeignServerStmt(stmt) => {
196            compile_create_foreign_server(stmt).map(Statement::CreateForeignServer)
197        }
198        NodeEnum::CreateForeignTableStmt(stmt) => compile_create_foreign_table_statement(stmt),
199        NodeEnum::MergeStmt(stmt) => compile_merge(stmt).map(Statement::Merge),
200        NodeEnum::CreateFunctionStmt(stmt) => {
201            compile_create_function(stmt).map(|f| Statement::CreateFunction(Box::new(f)))
202        }
203        NodeEnum::DoStmt(stmt) => compile_do(stmt),
204        NodeEnum::CallStmt(stmt) => compile_call(stmt),
205        NodeEnum::AlterFunctionStmt(stmt) => {
206            compile_alter_routine(stmt).map(Statement::AlterRoutine)
207        }
208        NodeEnum::AlterOwnerStmt(stmt) => compile_alter_routine_owner(stmt),
209        NodeEnum::GrantStmt(stmt) => compile_grant(stmt),
210        NodeEnum::GrantRoleStmt(stmt) => compile_grant_role(stmt),
211        NodeEnum::CreateRoleStmt(stmt) => compile_create_role(stmt),
212        NodeEnum::AlterRoleStmt(stmt) => compile_alter_role(stmt),
213        NodeEnum::DropRoleStmt(stmt) => compile_drop_role(stmt),
214        NodeEnum::VariableSetStmt(stmt) => compile_variable_set(stmt),
215        NodeEnum::ConstraintsSetStmt(stmt) => compile_set_constraints(stmt),
216        NodeEnum::VariableShowStmt(stmt) => Ok(Statement::ShowVariable {
217            name: stmt.name.clone(),
218        }),
219        NodeEnum::DiscardStmt(stmt) => Ok(Statement::Discard {
220            target: discard_target(stmt.target)?,
221        }),
222        NodeEnum::LoadStmt(stmt) => Ok(Statement::Load {
223            library: stmt.filename.clone(),
224        }),
225        other => Err(SQLError::Unsupported(format!(
226            "{}",
227            other_node_label(other)
228        ))),
229    }
230}
231
232/// Map `pg_query`'s `DiscardMode` enum (1=ALL, 2=PLANS, 3=SEQUENCES,
233/// 4=TEMP) to the AST's [`DiscardTarget`].
234pub(super) fn other_node_label(node: &NodeEnum) -> &'static str {
235    match node {
236        NodeEnum::ExplainStmt(_) => "EXPLAIN",
237        NodeEnum::ViewStmt(_) => "CREATE VIEW",
238        NodeEnum::TransactionStmt(_) => "BEGIN/COMMIT/ROLLBACK",
239        NodeEnum::DeclareCursorStmt(_) => "DECLARE CURSOR",
240        NodeEnum::FetchStmt(_) => "FETCH/MOVE",
241        NodeEnum::ClosePortalStmt(_) => "CLOSE CURSOR",
242        NodeEnum::PrepareStmt(_) | NodeEnum::ExecuteStmt(_) => "PREPARE/EXECUTE",
243        _ => "unknown statement",
244    }
245}
246
247// -------------------------------------------------------------------------
248// DROP TABLE / DROP INDEX [IF EXISTS] [CASCADE]
249// -------------------------------------------------------------------------
250
251/// Lower `DROP FUNCTION` / `DROP PROCEDURE`. Each target arrives as
252/// an `ObjectWithArgs`; the argument type list (when spelled) is
253/// preserved as a typed signature because routine identity includes
254/// `(schema, name, argument types)`.
255pub fn plan_only_for_test(sql: &str) -> Result<Vec<Statement>> {
256    compile(sql)
257}