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
39fn command_completion(
40    command: &CommandPlan,
41    result: &SQLResult,
42    transaction_failed: bool,
43) -> String {
44    match command {
45        CommandPlan::Insert(_) => format!("INSERT 0 {}", result.affected_rows),
46        CommandPlan::Update(_) => format!("UPDATE {}", result.affected_rows),
47        CommandPlan::Delete(_) => format!("DELETE {}", result.affected_rows),
48        CommandPlan::Merge(_) => format!("MERGE {}", result.affected_rows),
49        CommandPlan::FetchCursor(fetch) if fetch.move_only => {
50            format!("MOVE {}", result.affected_rows)
51        }
52        CommandPlan::FetchCursor(_) => format!("FETCH {}", result.rows.len()),
53        CommandPlan::CreateTableAs { .. } | CommandPlan::CreateMaterializedView { .. } => {
54            unreachable!("population execution owns completion")
55        }
56        CommandPlan::Execute { .. } => {
57            unreachable!("EXECUTE preserves delegated command completion")
58        }
59        CommandPlan::Transaction(statement) => {
60            transaction_completion(statement, transaction_failed).to_string()
61        }
62        command => command_tag_name(command).to_string(),
63    }
64}
65
66/// The tag of a command without its row count, as `CreateCommandName` names a command in diagnostics such as the read-only transaction error.
67#[expect(clippy::too_many_lines, reason = "exhaustive SQL command tag mapping")]
68pub fn command_tag_name(command: &CommandPlan) -> &'static str {
69    match command {
70        CommandPlan::Insert(_) => "INSERT",
71        CommandPlan::Update(_) => "UPDATE",
72        CommandPlan::Delete(_) => "DELETE",
73        CommandPlan::Merge(_) => "MERGE",
74        CommandPlan::FetchCursor(fetch) => {
75            if fetch.move_only {
76                "MOVE"
77            } else {
78                "FETCH"
79            }
80        }
81        CommandPlan::CreateTableAs {
82            select_into: true, ..
83        } => "SELECT INTO",
84        CommandPlan::CreateTableAs { .. } => "CREATE TABLE AS",
85        CommandPlan::CreateMaterializedView { .. } => "CREATE MATERIALIZED VIEW",
86        CommandPlan::Execute { .. } => "EXECUTE",
87        CommandPlan::Transaction(statement) => transaction_completion(statement, false),
88        CommandPlan::CreateTable(_) | CommandPlan::CreateTableIfNotExists(_) => "CREATE TABLE",
89        CommandPlan::CreateIndex(_) => "CREATE INDEX",
90        CommandPlan::RenameIndex(_) => "ALTER INDEX",
91        CommandPlan::Drop(statement) => match statement.kind {
92            DropKind::Table => "DROP TABLE",
93            DropKind::ForeignTable => "DROP FOREIGN TABLE",
94            DropKind::ForeignServer => "DROP SERVER",
95            DropKind::ForeignWrapper => "DROP FOREIGN DATA WRAPPER",
96            DropKind::Index => "DROP INDEX",
97            DropKind::View => "DROP VIEW",
98            DropKind::MaterializedView => "DROP MATERIALIZED VIEW",
99            DropKind::Schema => "DROP SCHEMA",
100            DropKind::Sequence => "DROP SEQUENCE",
101            DropKind::Domain => "DROP DOMAIN",
102            DropKind::Type => "DROP TYPE",
103        },
104        CommandPlan::AlterTable(statement) => match statement.actions.as_slice() {
105            [AlterTableAction::RenameTrigger { .. }] => "ALTER TRIGGER",
106            [AlterTableAction::RenameRule { .. }] => "ALTER RULE",
107            _ => "ALTER TABLE",
108        },
109        CommandPlan::AlterForeignTable(_) => "ALTER FOREIGN TABLE",
110        CommandPlan::AlterView(statement) => match statement.kind {
111            AlterViewKind::View => "ALTER VIEW",
112            AlterViewKind::MaterializedView => "ALTER MATERIALIZED VIEW",
113        },
114        CommandPlan::CreateView { .. } => "CREATE VIEW",
115        CommandPlan::RefreshMaterializedView { .. } => "REFRESH MATERIALIZED VIEW",
116        CommandPlan::CreateSchema { .. } => "CREATE SCHEMA",
117        CommandPlan::AlterSchemaOwner { .. } | CommandPlan::RenameSchema { .. } => "ALTER SCHEMA",
118        CommandPlan::Notify { .. } => "NOTIFY",
119        CommandPlan::Listen { .. } => "LISTEN",
120        CommandPlan::Unlisten { .. } => "UNLISTEN",
121        CommandPlan::SetVariable { .. } => "SET",
122        CommandPlan::ResetVariable { .. } | CommandPlan::ResetAllVariables => "RESET",
123        CommandPlan::SetConstraints { .. } => "SET CONSTRAINTS",
124        CommandPlan::ShowVariable { .. } => "SHOW",
125        CommandPlan::Discard { target } => match target {
126            DiscardTarget::All => "DISCARD ALL",
127            DiscardTarget::Plans => "DISCARD PLANS",
128            DiscardTarget::Sequences => "DISCARD SEQUENCES",
129            DiscardTarget::Temp => "DISCARD TEMP",
130        },
131        CommandPlan::Load { .. } => "LOAD",
132        CommandPlan::Explain { .. } => "EXPLAIN",
133        CommandPlan::Analyze { .. } => "ANALYZE",
134        CommandPlan::LockTable(_) => "LOCK TABLE",
135        CommandPlan::Vacuum(_) => "VACUUM",
136        CommandPlan::Truncate { .. } => "TRUNCATE TABLE",
137        CommandPlan::DeclareCursor { .. } => "DECLARE CURSOR",
138        CommandPlan::CloseCursor { name } => {
139            if name.is_some() {
140                "CLOSE CURSOR"
141            } else {
142                "CLOSE CURSOR ALL"
143            }
144        }
145        CommandPlan::CreateSequence(_) => "CREATE SEQUENCE",
146        CommandPlan::CreateDomain(_) => "CREATE DOMAIN",
147        CommandPlan::AlterDomain(_) => "ALTER DOMAIN",
148        CommandPlan::CreateEnum(_) | CommandPlan::CreateCompositeType(_) => "CREATE TYPE",
149        CommandPlan::AlterEnum(_) => "ALTER TYPE",
150        CommandPlan::AlterTypeObject(statement) => match statement.kind {
151            crate::ast::TypeObjectKind::Type => "ALTER TYPE",
152            crate::ast::TypeObjectKind::Domain => "ALTER DOMAIN",
153        },
154        CommandPlan::GrantType(statement) => grant_completion(statement.is_grant),
155        CommandPlan::AlterSequence(_) => "ALTER SEQUENCE",
156        CommandPlan::Prepare { .. } => "PREPARE",
157        CommandPlan::Deallocate { name } => {
158            if name.is_some() {
159                "DEALLOCATE"
160            } else {
161                "DEALLOCATE ALL"
162            }
163        }
164        CommandPlan::CreateForeignWrapper(_) => "CREATE FOREIGN DATA WRAPPER",
165        CommandPlan::CreateForeignServer(_) => "CREATE SERVER",
166        CommandPlan::CreateForeignTable(_) | CommandPlan::CreateForeignTableDefinition(_) => {
167            "CREATE FOREIGN TABLE"
168        }
169        CommandPlan::CreateFunction(statement) => {
170            if statement.is_procedure {
171                "CREATE PROCEDURE"
172            } else {
173                "CREATE FUNCTION"
174            }
175        }
176        CommandPlan::DropFunction(statement) => {
177            if statement.is_procedure {
178                "DROP PROCEDURE"
179            } else {
180                "DROP FUNCTION"
181            }
182        }
183        CommandPlan::AlterRoutine(statement) => alter_routine_completion(statement.kind),
184        CommandPlan::AlterRoutineOwner(statement) => alter_routine_completion(statement.kind),
185        CommandPlan::RenameRoutine(statement) => alter_routine_completion(statement.kind),
186        CommandPlan::GrantRoutine(statement) => grant_completion(statement.is_grant),
187        CommandPlan::GrantTable(statement) => grant_completion(statement.is_grant),
188        CommandPlan::GrantSequence(statement) => grant_completion(statement.is_grant),
189        CommandPlan::GrantDatabase(statement) => grant_completion(statement.is_grant),
190        CommandPlan::GrantSchema(statement) => grant_completion(statement.is_grant),
191        CommandPlan::GrantRole(statement) => {
192            if statement.is_grant {
193                "GRANT ROLE"
194            } else {
195                "REVOKE ROLE"
196            }
197        }
198        CommandPlan::CreateRole(_) => "CREATE ROLE",
199        CommandPlan::AlterRole(_) | CommandPlan::RenameRole(_) => "ALTER ROLE",
200        CommandPlan::DropRole(_) => "DROP ROLE",
201        CommandPlan::CreateTrigger(_) => "CREATE TRIGGER",
202        CommandPlan::DropTrigger(_) => "DROP TRIGGER",
203        CommandPlan::CreateRule(_) => "CREATE RULE",
204        CommandPlan::DropRule(_) => "DROP RULE",
205        CommandPlan::DoBlock { .. } => "DO",
206        CommandPlan::Call { .. } => "CALL",
207    }
208}
209
210pub const fn transaction_completion(statement: &TransactionStmt, failed: bool) -> &'static str {
211    match statement {
212        TransactionStmt::Begin | TransactionStmt::BeginWithCharacteristics(_) => "BEGIN",
213        TransactionStmt::Commit | TransactionStmt::CommitAndChain => {
214            if failed {
215                "ROLLBACK"
216            } else {
217                "COMMIT"
218            }
219        }
220        TransactionStmt::Rollback
221        | TransactionStmt::RollbackAndChain
222        | TransactionStmt::RollbackToSavepoint(_) => "ROLLBACK",
223        TransactionStmt::Savepoint(_) => "SAVEPOINT",
224        TransactionStmt::ReleaseSavepoint(_) => "RELEASE",
225        TransactionStmt::SetCharacteristics(_)
226        | TransactionStmt::SetSessionCharacteristics(_)
227        | TransactionStmt::SetSnapshot(_) => "SET",
228    }
229}
230
231const fn alter_routine_completion(kind: AlterRoutineKind) -> &'static str {
232    match kind {
233        AlterRoutineKind::Function => "ALTER FUNCTION",
234        AlterRoutineKind::Procedure => "ALTER PROCEDURE",
235        AlterRoutineKind::Routine => "ALTER ROUTINE",
236    }
237}
238
239const fn grant_completion(is_grant: bool) -> &'static str {
240    if is_grant {
241        "GRANT"
242    } else {
243        "REVOKE"
244    }
245}