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_transaction, compile_truncate, compile_variable_set,
11    discard_target,
12};
13use super::dml::{compile_delete, compile_update};
14use super::drop_alter::{compile_alter_table, compile_drop, compile_rename};
15use super::merge::compile_merge;
16use super::relations::{
17    compile_create_foreign_server, compile_create_foreign_table, compile_create_schema,
18    compile_create_table_as, compile_create_view, compile_deallocate, compile_execute,
19    compile_prepare,
20};
21use super::routines::{compile_call, compile_create_function, compile_do};
22use super::sequences::{compile_alter_sequence, compile_create_sequence};
23use super::{
24    compile_create_index, compile_create_table, compile_insert, compile_select,
25    compile_values_lists, Node, NodeEnum, Result, SQLError, Statement,
26};
27
28pub fn compile(sql: &str) -> Result<Vec<Statement>> {
29    let parsed = pg_query::parse(sql)?;
30    let mut out = Vec::with_capacity(parsed.protobuf.stmts.len());
31    for raw in parsed.protobuf.stmts {
32        let node = raw
33            .stmt
34            .ok_or_else(|| SQLError::Internal("parser returned an empty statement".into()))?;
35        out.push(compile_stmt(&node)?);
36    }
37    Ok(out)
38}
39
40pub(super) fn compile_stmt(node: &Node) -> Result<Statement> {
41    let Some(inner) = node.node.as_ref() else {
42        return Err(SQLError::Unsupported("empty statement".into()));
43    };
44    match inner {
45        NodeEnum::CreateStmt(stmt) => compile_create_table(stmt).map(Statement::CreateTable),
46        NodeEnum::IndexStmt(stmt) => compile_create_index(stmt).map(Statement::CreateIndex),
47        NodeEnum::InsertStmt(stmt) => compile_insert(stmt).map(Statement::Insert),
48        NodeEnum::SelectStmt(stmt) => {
49            // Standalone `VALUES (...) (...)` parses as a SelectStmt
50            // with empty target_list + populated values_lists. Treat
51            // it as a relation-producing statement directly.
52            if stmt.target_list.is_empty()
53                && !stmt.values_lists.is_empty()
54                && stmt.locking_clause.is_empty()
55            {
56                let rows = compile_values_lists(&stmt.values_lists)?;
57                return Ok(Statement::Values { rows });
58            }
59            compile_select(stmt).map(|s| Statement::Select(Box::new(s)))
60        }
61        NodeEnum::UpdateStmt(stmt) => compile_update(stmt).map(Statement::Update),
62        NodeEnum::DeleteStmt(stmt) => compile_delete(stmt).map(Statement::Delete),
63        NodeEnum::DropStmt(stmt) => compile_drop(stmt),
64        NodeEnum::AlterTableStmt(stmt) => compile_alter_table(stmt).map(Statement::AlterTable),
65        NodeEnum::RenameStmt(stmt) => compile_rename(stmt).map(Statement::AlterTable),
66        NodeEnum::ViewStmt(stmt) => compile_create_view(stmt),
67        NodeEnum::CreateSchemaStmt(stmt) => compile_create_schema(stmt),
68        NodeEnum::ExplainStmt(stmt) => compile_explain(stmt),
69        NodeEnum::VacuumStmt(stmt) => compile_analyze(stmt),
70        NodeEnum::TruncateStmt(stmt) => compile_truncate(stmt),
71        NodeEnum::TransactionStmt(stmt) => compile_transaction(stmt),
72        NodeEnum::CreateSeqStmt(stmt) => {
73            compile_create_sequence(stmt).map(Statement::CreateSequence)
74        }
75        NodeEnum::AlterSeqStmt(stmt) => compile_alter_sequence(stmt).map(Statement::AlterSequence),
76        NodeEnum::CreateTableAsStmt(stmt) => compile_create_table_as(stmt),
77        NodeEnum::PrepareStmt(stmt) => compile_prepare(stmt),
78        NodeEnum::ExecuteStmt(stmt) => compile_execute(stmt),
79        NodeEnum::DeallocateStmt(stmt) => compile_deallocate(stmt),
80        NodeEnum::CreateForeignServerStmt(stmt) => {
81            compile_create_foreign_server(stmt).map(Statement::CreateForeignServer)
82        }
83        NodeEnum::CreateForeignTableStmt(stmt) => {
84            compile_create_foreign_table(stmt).map(Statement::CreateForeignTable)
85        }
86        NodeEnum::MergeStmt(stmt) => compile_merge(stmt).map(Statement::Merge),
87        NodeEnum::CreateFunctionStmt(stmt) => {
88            compile_create_function(stmt).map(|f| Statement::CreateFunction(Box::new(f)))
89        }
90        NodeEnum::DoStmt(stmt) => compile_do(stmt),
91        NodeEnum::CallStmt(stmt) => compile_call(stmt),
92        NodeEnum::VariableSetStmt(stmt) => compile_variable_set(stmt),
93        NodeEnum::VariableShowStmt(stmt) => Ok(Statement::ShowVariable {
94            name: stmt.name.clone(),
95        }),
96        NodeEnum::DiscardStmt(stmt) => Ok(Statement::Discard {
97            target: discard_target(stmt.target)?,
98        }),
99        NodeEnum::LoadStmt(stmt) => Ok(Statement::Load {
100            library: stmt.filename.clone(),
101        }),
102        other => Err(SQLError::Unsupported(format!(
103            "{}",
104            other_node_label(other)
105        ))),
106    }
107}
108
109/// Map `pg_query`'s `DiscardMode` enum (1=ALL, 2=PLANS, 3=SEQUENCES,
110/// 4=TEMP) to the AST's [`DiscardTarget`].
111pub(super) fn other_node_label(node: &NodeEnum) -> &'static str {
112    match node {
113        NodeEnum::ExplainStmt(_) => "EXPLAIN",
114        NodeEnum::ViewStmt(_) => "CREATE VIEW",
115        NodeEnum::TransactionStmt(_) => "BEGIN/COMMIT/ROLLBACK",
116        NodeEnum::PrepareStmt(_) | NodeEnum::ExecuteStmt(_) => "PREPARE/EXECUTE",
117        _ => "unknown statement",
118    }
119}
120
121// -------------------------------------------------------------------------
122// DROP TABLE / DROP INDEX [IF EXISTS] [CASCADE]
123// -------------------------------------------------------------------------
124
125/// Lower `DROP FUNCTION` / `DROP PROCEDURE`. Each target arrives as
126/// an `ObjectWithArgs`; the argument type list (when spelled) is
127/// preserved as a typed signature because routine identity includes
128/// `(schema, name, argument types)`.
129pub fn plan_only_for_test(sql: &str) -> Result<Vec<Statement>> {
130    compile(sql)
131}