Skip to main content

uqa_sql/result/
completion.rs

1//
2// Unified Query Algebra
3//
4// Copyright (c) 2023-2026 Cognica, Inc.
5//
6
7//! Command completion owned by the plan that executed, including delegated EXECUTE.
8
9use crate::ast::{
10    AlterRoutineKind, AlterTableAction, AlterViewKind, DiscardTarget, DropKind, TransactionStmt,
11};
12use crate::plan::{CommandPlan, UnifiedPlan};
13use crate::SQLResult;
14
15pub fn set_command_completion(
16    plan: &UnifiedPlan,
17    result: &mut SQLResult,
18    transaction_failed: bool,
19) {
20    let command = match plan {
21        UnifiedPlan::Query(_) => {
22            result.command_tag = Some(format!("SELECT {}", result.rows.len()));
23            return;
24        }
25        UnifiedPlan::Command(command) => command.as_ref(),
26    };
27    if matches!(
28        command,
29        CommandPlan::Execute { .. }
30            | CommandPlan::CreateTableAs { .. }
31            | CommandPlan::CreateMaterializedView { .. }
32    ) {
33        // Delegated execution and optional population own their completion.
34        return;
35    }
36    result.command_tag = Some(command_completion(command, result, transaction_failed));
37}
38
39#[expect(
40    clippy::too_many_lines,
41    reason = "exhaustive SQL command completion mapping"
42)]
43fn command_completion(
44    command: &CommandPlan,
45    result: &SQLResult,
46    transaction_failed: bool,
47) -> String {
48    let tag = match command {
49        CommandPlan::Insert(_) => return format!("INSERT 0 {}", result.affected_rows),
50        CommandPlan::Update(_) => return format!("UPDATE {}", result.affected_rows),
51        CommandPlan::Delete(_) => return format!("DELETE {}", result.affected_rows),
52        CommandPlan::Merge(_) => return format!("MERGE {}", result.affected_rows),
53        CommandPlan::CreateTableAs { .. } | CommandPlan::CreateMaterializedView { .. } => {
54            unreachable!("population execution owns completion")
55        }
56        CommandPlan::FetchCursor(fetch) => {
57            return if fetch.move_only {
58                format!("MOVE {}", result.affected_rows)
59            } else {
60                format!("FETCH {}", result.rows.len())
61            };
62        }
63        CommandPlan::CreateTable(_) | CommandPlan::CreateTableIfNotExists(_) => "CREATE TABLE",
64        CommandPlan::CreateIndex(_) => "CREATE INDEX",
65        CommandPlan::RenameIndex(_) => "ALTER INDEX",
66        CommandPlan::Drop(statement) => match statement.kind {
67            DropKind::Table => "DROP TABLE",
68            DropKind::ForeignTable => "DROP FOREIGN TABLE",
69            DropKind::Index => "DROP INDEX",
70            DropKind::View => "DROP VIEW",
71            DropKind::MaterializedView => "DROP MATERIALIZED VIEW",
72            DropKind::Schema => "DROP SCHEMA",
73            DropKind::Sequence => "DROP SEQUENCE",
74            DropKind::Domain => "DROP DOMAIN",
75        },
76        CommandPlan::AlterTable(statement) => match statement.actions.as_slice() {
77            [AlterTableAction::RenameTrigger { .. }] => "ALTER TRIGGER",
78            [AlterTableAction::RenameRule { .. }] => "ALTER RULE",
79            _ => "ALTER TABLE",
80        },
81        CommandPlan::AlterForeignTable(_) => "ALTER FOREIGN TABLE",
82        CommandPlan::AlterView(statement) => match statement.kind {
83            AlterViewKind::View => "ALTER VIEW",
84            AlterViewKind::MaterializedView => "ALTER MATERIALIZED VIEW",
85        },
86        CommandPlan::CreateView { .. } => "CREATE VIEW",
87        CommandPlan::RefreshMaterializedView { .. } => "REFRESH MATERIALIZED VIEW",
88        CommandPlan::CreateSchema { .. } => "CREATE SCHEMA",
89        CommandPlan::AlterSchemaOwner { .. } => "ALTER SCHEMA",
90        CommandPlan::Notify { .. } => "NOTIFY",
91        CommandPlan::Listen { .. } => "LISTEN",
92        CommandPlan::Unlisten { .. } => "UNLISTEN",
93        CommandPlan::SetVariable { .. } => "SET",
94        CommandPlan::ResetVariable { .. } | CommandPlan::ResetAllVariables => "RESET",
95        CommandPlan::SetConstraints { .. } => "SET CONSTRAINTS",
96        CommandPlan::ShowVariable { .. } => "SHOW",
97        CommandPlan::Discard { target } => match target {
98            DiscardTarget::All => "DISCARD ALL",
99            DiscardTarget::Plans => "DISCARD PLANS",
100            DiscardTarget::Sequences => "DISCARD SEQUENCES",
101            DiscardTarget::Temp => "DISCARD TEMP",
102        },
103        CommandPlan::Load { .. } => "LOAD",
104        CommandPlan::Explain { .. } => "EXPLAIN",
105        CommandPlan::Analyze { .. } => "ANALYZE",
106        CommandPlan::LockTable(_) => "LOCK TABLE",
107        CommandPlan::Vacuum(_) => "VACUUM",
108        CommandPlan::Truncate { .. } => "TRUNCATE TABLE",
109        CommandPlan::Transaction(statement) => {
110            transaction_completion(statement, transaction_failed)
111        }
112        CommandPlan::DeclareCursor { .. } => "DECLARE CURSOR",
113        CommandPlan::CloseCursor { name } => {
114            if name.is_some() {
115                "CLOSE CURSOR"
116            } else {
117                "CLOSE CURSOR ALL"
118            }
119        }
120        CommandPlan::CreateSequence(_) => "CREATE SEQUENCE",
121        CommandPlan::CreateDomain(_) => "CREATE DOMAIN",
122        CommandPlan::AlterSequence(_) => "ALTER SEQUENCE",
123        CommandPlan::Prepare { .. } => "PREPARE",
124        CommandPlan::Execute { .. } => {
125            unreachable!("EXECUTE preserves delegated command completion")
126        }
127        CommandPlan::Deallocate { name } => {
128            if name.is_some() {
129                "DEALLOCATE"
130            } else {
131                "DEALLOCATE ALL"
132            }
133        }
134        CommandPlan::CreateForeignServer(_) => "CREATE SERVER",
135        CommandPlan::CreateForeignTable(_) | CommandPlan::CreateForeignTableIfNotExists(_) => {
136            "CREATE FOREIGN TABLE"
137        }
138        CommandPlan::CreateFunction(statement) => {
139            if statement.is_procedure {
140                "CREATE PROCEDURE"
141            } else {
142                "CREATE FUNCTION"
143            }
144        }
145        CommandPlan::DropFunction(statement) => {
146            if statement.is_procedure {
147                "DROP PROCEDURE"
148            } else {
149                "DROP FUNCTION"
150            }
151        }
152        CommandPlan::AlterRoutine(statement) => alter_routine_completion(statement.kind),
153        CommandPlan::AlterRoutineOwner(statement) => alter_routine_completion(statement.kind),
154        CommandPlan::RenameRoutine(statement) => alter_routine_completion(statement.kind),
155        CommandPlan::GrantRoutine(statement) => grant_completion(statement.is_grant),
156        CommandPlan::GrantTable(statement) => grant_completion(statement.is_grant),
157        CommandPlan::GrantSequence(statement) => grant_completion(statement.is_grant),
158        CommandPlan::GrantDatabase(statement) => grant_completion(statement.is_grant),
159        CommandPlan::GrantSchema(statement) => grant_completion(statement.is_grant),
160        CommandPlan::GrantRole(statement) => {
161            if statement.is_grant {
162                "GRANT ROLE"
163            } else {
164                "REVOKE ROLE"
165            }
166        }
167        CommandPlan::CreateRole(_) => "CREATE ROLE",
168        CommandPlan::AlterRole(_) | CommandPlan::RenameRole(_) => "ALTER ROLE",
169        CommandPlan::DropRole(_) => "DROP ROLE",
170        CommandPlan::CreateTrigger(_) => "CREATE TRIGGER",
171        CommandPlan::DropTrigger(_) => "DROP TRIGGER",
172        CommandPlan::CreateRule(_) => "CREATE RULE",
173        CommandPlan::DropRule(_) => "DROP RULE",
174        CommandPlan::DoBlock { .. } => "DO",
175        CommandPlan::Call { .. } => "CALL",
176    };
177    tag.to_string()
178}
179
180pub const fn transaction_completion(statement: &TransactionStmt, failed: bool) -> &'static str {
181    match statement {
182        TransactionStmt::Begin | TransactionStmt::BeginWithCharacteristics(_) => "BEGIN",
183        TransactionStmt::Commit | TransactionStmt::CommitAndChain => {
184            if failed {
185                "ROLLBACK"
186            } else {
187                "COMMIT"
188            }
189        }
190        TransactionStmt::Rollback
191        | TransactionStmt::RollbackAndChain
192        | TransactionStmt::RollbackToSavepoint(_) => "ROLLBACK",
193        TransactionStmt::Savepoint(_) => "SAVEPOINT",
194        TransactionStmt::ReleaseSavepoint(_) => "RELEASE",
195        TransactionStmt::SetCharacteristics(_)
196        | TransactionStmt::SetSessionCharacteristics(_)
197        | TransactionStmt::SetSnapshot(_) => "SET",
198    }
199}
200
201const fn alter_routine_completion(kind: AlterRoutineKind) -> &'static str {
202    match kind {
203        AlterRoutineKind::Function => "ALTER FUNCTION",
204        AlterRoutineKind::Procedure => "ALTER PROCEDURE",
205        AlterRoutineKind::Routine => "ALTER ROUTINE",
206    }
207}
208
209const fn grant_completion(is_grant: bool) -> &'static str {
210    if is_grant {
211        "GRANT"
212    } else {
213        "REVOKE"
214    }
215}