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::{compile_alter_table, compile_drop, compile_rename};
16use super::events::{compile_create_rule, compile_create_trigger};
17use super::merge::compile_merge;
18use super::relations::{
19    compile_create_foreign_server, compile_create_schema, compile_create_table_as,
20    compile_create_view, compile_deallocate, compile_execute, compile_prepare,
21    compile_refresh_materialized_view, compile_top_level_select, defer_create_foreign_table,
22    defer_create_table,
23};
24use super::routines::{
25    compile_alter_role, compile_alter_routine, compile_alter_routine_owner, compile_call,
26    compile_create_function, compile_create_role, compile_do, compile_drop_role, compile_grant,
27    compile_grant_role,
28};
29use super::schema_moves::compile_alter_object_schema;
30use super::sequences::{compile_alter_sequence, compile_create_sequence};
31use super::type_lifecycle;
32use super::{
33    compile_create_index, compile_create_table, compile_insert, compile_values_lists, Node,
34    NodeEnum, Result, SQLError, Statement,
35};
36use pg_query::protobuf::SelectStmt;
37
38/// A syntactically valid statement retaining its exact source slice. Compilation is separate so an execution boundary can analyze statements in order after preceding commands have completed.
39#[derive(Debug, Clone)]
40pub struct ParsedStatement<'sql> {
41    sql: &'sql str,
42    node: Box<Node>,
43    settings: crate::parser::ParserSettings,
44}
45
46impl<'sql> ParsedStatement<'sql> {
47    /// Original SQL, without reconstructing or rewriting the parser tree.
48    pub const fn sql(&self) -> &'sql str {
49        self.sql
50    }
51
52    /// Identify a direct LISTEN or UNLISTEN without semantic compilation or inspecting strings and routine bodies. Nested commands must still be checked when executed.
53    pub fn is_notification_listener_command(&self) -> bool {
54        matches!(
55            self.node.node.as_ref(),
56            Some(NodeEnum::ListenStmt(_) | NodeEnum::UnlistenStmt(_))
57        )
58    }
59
60    /// Compile this statement into the engine's internal SQL representation.
61    pub fn compile(&self) -> Result<Statement> {
62        crate::parser::with_settings(self.settings, || {
63            let mut statement = compile_stmt(&self.node)?;
64            if let Statement::CreateForeignServer(server) = &mut statement {
65                super::foreign_servers::retain_string_presence(self.sql, server)?;
66            }
67            Ok(statement)
68        })
69        .0
70    }
71}
72
73/// Parse an entire SQL message before exposing any statement for execution. `PostgreSQL` syntax errors reject the whole message; semantic compilation errors can be surfaced later, at the affected statement's boundary.
74pub fn parse_statements(sql: &str) -> Result<Vec<ParsedStatement<'_>>> {
75    let parsed = crate::parser::parse(sql)?;
76    let mut out = Vec::with_capacity(parsed.protobuf.stmts.len());
77    for raw in parsed.protobuf.stmts {
78        let node = raw
79            .stmt
80            .ok_or_else(|| SQLError::Internal("parser returned an empty statement".into()))?;
81        let start = usize::try_from(raw.stmt_location).map_err(|_| {
82            SQLError::Internal("parser returned a negative statement offset".into())
83        })?;
84        let end = if raw.stmt_len == 0 {
85            sql.len()
86        } else {
87            let len = usize::try_from(raw.stmt_len).map_err(|_| {
88                SQLError::Internal("parser returned a negative statement length".into())
89            })?;
90            start
91                .checked_add(len)
92                .ok_or_else(|| SQLError::Internal("parser statement offset overflow".into()))?
93        };
94        let source = sql.get(start..end).ok_or_else(|| {
95            SQLError::Internal("parser statement bounds do not match SQL text".into())
96        })?;
97        out.push(ParsedStatement {
98            sql: source,
99            node,
100            settings: crate::parser::settings(),
101        });
102    }
103    Ok(out)
104}
105
106pub fn compile(sql: &str) -> Result<Vec<Statement>> {
107    parse_statements(sql)?
108        .iter()
109        .map(ParsedStatement::compile)
110        .collect()
111}
112
113pub fn resolve_deferred_create_table(
114    deferred: &crate::ast::DeferredCreateTable,
115) -> Result<crate::ast::CreateTable> {
116    let parsed = crate::parser::parse(&deferred.definition_sql)?;
117    let [raw] = parsed.protobuf.stmts.as_slice() else {
118        return Err(SQLError::Internal(
119            "deferred CREATE TABLE did not contain exactly one statement".into(),
120        ));
121    };
122    let node = raw
123        .stmt
124        .as_deref()
125        .and_then(|node| node.node.as_ref())
126        .ok_or_else(|| SQLError::Internal("deferred CREATE TABLE is empty".into()))?;
127    let NodeEnum::CreateStmt(stmt) = node else {
128        return Err(SQLError::Internal(
129            "deferred CREATE TABLE changed statement kind".into(),
130        ));
131    };
132    let table = compile_create_table(stmt)?;
133    if !table.if_not_exists
134        || table.name != deferred.name
135        || table.persistence != deferred.persistence
136    {
137        return Err(SQLError::Internal(
138            "deferred CREATE TABLE changed target identity".into(),
139        ));
140    }
141    Ok(table)
142}
143
144fn compile_create_table_statement(statement: &pg_query::protobuf::CreateStmt) -> Result<Statement> {
145    if statement.if_not_exists {
146        defer_create_table(statement).map(Statement::CreateTableIfNotExists)
147    } else {
148        compile_create_table(statement).map(Statement::CreateTable)
149    }
150}
151
152fn compile_create_foreign_table_statement(
153    statement: &pg_query::protobuf::CreateForeignTableStmt,
154) -> Result<Statement> {
155    defer_create_foreign_table(statement).map(Statement::CreateForeignTableDefinition)
156}
157
158/// `ALTER TYPE | DOMAIN ... RENAME` changes a type object; other `RENAME` forms change relations, columns, constraints and routines.
159fn compile_rename_statement(statement: &pg_query::protobuf::RenameStmt) -> Result<Statement> {
160    match type_lifecycle::type_object_kind(statement.rename_type()) {
161        Some(kind) => type_lifecycle::compile_type_rename(statement, kind),
162        None => compile_rename(statement),
163    }
164}
165
166fn compile_alter_object_schema_statement(
167    statement: &pg_query::protobuf::AlterObjectSchemaStmt,
168) -> Result<Statement> {
169    match type_lifecycle::type_object_kind(statement.object_type()) {
170        Some(kind) => type_lifecycle::compile_type_set_schema(statement, kind),
171        None => compile_alter_object_schema(statement),
172    }
173}
174
175fn compile_alter_owner_statement(
176    statement: &pg_query::protobuf::AlterOwnerStmt,
177) -> Result<Statement> {
178    match type_lifecycle::type_object_kind(statement.object_type()) {
179        Some(kind) => type_lifecycle::compile_type_owner(statement, kind),
180        None => compile_alter_routine_owner(statement),
181    }
182}
183
184fn compile_grant_statement(statement: &pg_query::protobuf::GrantStmt) -> Result<Statement> {
185    match type_lifecycle::type_object_kind(statement.objtype()) {
186        Some(kind) => type_lifecycle::compile_grant_type(statement, kind),
187        None => compile_grant(statement),
188    }
189}
190
191pub(super) fn compile_stmt(node: &Node) -> Result<Statement> {
192    let Some(inner) = node.node.as_ref() else {
193        return Err(SQLError::Unsupported("empty statement".into()));
194    };
195    match inner {
196        NodeEnum::CreateStmt(stmt) => compile_create_table_statement(stmt),
197        NodeEnum::IndexStmt(stmt) => compile_create_index(stmt).map(Statement::CreateIndex),
198        NodeEnum::InsertStmt(stmt) => compile_insert(stmt).map(Statement::Insert),
199        NodeEnum::SelectStmt(stmt) => compile_select_statement(stmt),
200        NodeEnum::UpdateStmt(stmt) => compile_update(stmt).map(Statement::Update),
201        NodeEnum::DeleteStmt(stmt) => compile_delete(stmt).map(Statement::Delete),
202        NodeEnum::DropStmt(stmt) => compile_drop(stmt),
203        NodeEnum::CreateTrigStmt(stmt) => {
204            compile_create_trigger(stmt).map(Statement::CreateTrigger)
205        }
206        NodeEnum::RuleStmt(stmt) => compile_create_rule(stmt).map(Statement::CreateRule),
207        NodeEnum::AlterTableStmt(stmt) => compile_alter_table(stmt),
208        NodeEnum::RenameStmt(stmt) => compile_rename_statement(stmt),
209        NodeEnum::AlterObjectSchemaStmt(stmt) => compile_alter_object_schema_statement(stmt),
210        NodeEnum::ViewStmt(stmt) => compile_create_view(stmt),
211        NodeEnum::CreateSchemaStmt(stmt) => compile_create_schema(stmt),
212        NodeEnum::NotifyStmt(stmt) => Ok(Statement::Notify {
213            channel: stmt.conditionname.clone(),
214            payload: stmt.payload.clone(),
215        }),
216        NodeEnum::ListenStmt(stmt) => Ok(Statement::Listen {
217            channel: stmt.conditionname.clone(),
218        }),
219        NodeEnum::UnlistenStmt(stmt) => Ok(Statement::Unlisten {
220            channel: (!stmt.conditionname.is_empty()).then(|| stmt.conditionname.clone()),
221        }),
222        NodeEnum::ExplainStmt(stmt) => compile_explain(stmt),
223        NodeEnum::VacuumStmt(stmt) => compile_analyze(stmt),
224        NodeEnum::TruncateStmt(stmt) => compile_truncate(stmt),
225        NodeEnum::LockStmt(stmt) => super::locking::compile_lock_table(stmt),
226        NodeEnum::TransactionStmt(stmt) => compile_transaction(stmt),
227        NodeEnum::DeclareCursorStmt(stmt) => compile_declare_cursor(stmt),
228        NodeEnum::FetchStmt(stmt) => compile_fetch_cursor(stmt),
229        NodeEnum::ClosePortalStmt(stmt) => Ok(compile_close_cursor(stmt)),
230        NodeEnum::CreateSeqStmt(stmt) => {
231            compile_create_sequence(stmt).map(Statement::CreateSequence)
232        }
233        NodeEnum::AlterSeqStmt(stmt) => compile_alter_sequence(stmt).map(Statement::AlterSequence),
234        NodeEnum::CreateTableAsStmt(stmt) => compile_create_table_as(stmt),
235        NodeEnum::RefreshMatViewStmt(stmt) => compile_refresh_materialized_view(stmt),
236        NodeEnum::PrepareStmt(stmt) => compile_prepare(stmt),
237        NodeEnum::ExecuteStmt(stmt) => compile_execute(stmt),
238        NodeEnum::DeallocateStmt(stmt) => compile_deallocate(stmt),
239        NodeEnum::CreateFdwStmt(stmt) => {
240            super::foreign_wrappers::compile(stmt).map(Statement::CreateForeignWrapper)
241        }
242        NodeEnum::CreateForeignServerStmt(stmt) => {
243            compile_create_foreign_server(stmt).map(Statement::CreateForeignServer)
244        }
245        NodeEnum::CreateForeignTableStmt(stmt) => compile_create_foreign_table_statement(stmt),
246        NodeEnum::MergeStmt(stmt) => compile_merge(stmt).map(Statement::Merge),
247        NodeEnum::CreateDomainStmt(stmt) => {
248            super::domains::compile_create_domain(stmt).map(Statement::CreateDomain)
249        }
250        NodeEnum::AlterDomainStmt(stmt) => {
251            super::domains::compile_alter_domain(stmt).map(Statement::AlterDomain)
252        }
253        NodeEnum::CreateEnumStmt(stmt) => {
254            super::enums::compile_create_enum(stmt).map(Statement::CreateEnum)
255        }
256        NodeEnum::CompositeTypeStmt(stmt) => super::composites::compile_create_composite_type(stmt)
257            .map(Statement::CreateCompositeType),
258        NodeEnum::AlterEnumStmt(stmt) => {
259            super::enums::compile_alter_enum(stmt).map(Statement::AlterEnum)
260        }
261        NodeEnum::CreateFunctionStmt(stmt) => {
262            compile_create_function(stmt).map(|f| Statement::CreateFunction(Box::new(f)))
263        }
264        NodeEnum::DoStmt(stmt) => compile_do(stmt),
265        NodeEnum::CallStmt(stmt) => compile_call(stmt),
266        NodeEnum::AlterFunctionStmt(stmt) => {
267            compile_alter_routine(stmt).map(Statement::AlterRoutine)
268        }
269        NodeEnum::AlterOwnerStmt(stmt) => compile_alter_owner_statement(stmt),
270        NodeEnum::GrantStmt(stmt) => compile_grant_statement(stmt),
271        NodeEnum::GrantRoleStmt(stmt) => compile_grant_role(stmt),
272        NodeEnum::CreateRoleStmt(stmt) => compile_create_role(stmt),
273        NodeEnum::AlterRoleStmt(stmt) => compile_alter_role(stmt),
274        NodeEnum::DropRoleStmt(stmt) => compile_drop_role(stmt),
275        NodeEnum::VariableSetStmt(stmt) => compile_variable_set(stmt),
276        NodeEnum::ConstraintsSetStmt(stmt) => compile_set_constraints(stmt),
277        NodeEnum::VariableShowStmt(stmt) => Ok(Statement::ShowVariable {
278            name: stmt.name.clone(),
279        }),
280        NodeEnum::DiscardStmt(stmt) => Ok(Statement::Discard {
281            target: discard_target(stmt.target)?,
282        }),
283        NodeEnum::LoadStmt(stmt) => Ok(Statement::Load {
284            library: stmt.filename.clone(),
285        }),
286        other => Err(SQLError::Unsupported(format!(
287            "{}",
288            other_node_label(other)
289        ))),
290    }
291}
292
293/// Standalone `VALUES (...) (...)` parses as a `SelectStmt` with an empty target list and populated values lists; it is a relation-producing statement of its own.
294fn compile_select_statement(stmt: &SelectStmt) -> Result<Statement> {
295    if stmt.target_list.is_empty()
296        && !stmt.values_lists.is_empty()
297        && stmt.locking_clause.is_empty()
298        && stmt.sort_clause.is_empty()
299        && stmt.limit_count.is_none()
300        && stmt.limit_offset.is_none()
301    {
302        let rows = compile_values_lists(&stmt.values_lists)?;
303        return Ok(Statement::Values { rows });
304    }
305    compile_top_level_select(stmt)
306}
307
308/// Statement label reported for a parser node that compilation does not support.
309pub(super) fn other_node_label(node: &NodeEnum) -> &'static str {
310    match node {
311        NodeEnum::ExplainStmt(_) => "EXPLAIN",
312        NodeEnum::ViewStmt(_) => "CREATE VIEW",
313        NodeEnum::TransactionStmt(_) => "BEGIN/COMMIT/ROLLBACK",
314        NodeEnum::DeclareCursorStmt(_) => "DECLARE CURSOR",
315        NodeEnum::FetchStmt(_) => "FETCH/MOVE",
316        NodeEnum::ClosePortalStmt(_) => "CLOSE CURSOR",
317        NodeEnum::PrepareStmt(_) | NodeEnum::ExecuteStmt(_) => "PREPARE/EXECUTE",
318        _ => "unknown statement",
319    }
320}
321
322// -------------------------------------------------------------------------
323// DROP TABLE / DROP INDEX [IF EXISTS] [CASCADE]
324// -------------------------------------------------------------------------
325
326/// Lower `DROP FUNCTION` / `DROP PROCEDURE`. Each target arrives as
327/// an `ObjectWithArgs`; the argument type list (when spelled) is
328/// preserved as a typed signature because routine identity includes
329/// `(schema, name, argument types)`.
330pub fn plan_only_for_test(sql: &str) -> Result<Vec<Statement>> {
331    compile(sql)
332}