Skip to main content

uqa_sql/semantics/effects/
transaction_blocks.rs

1//
2// Unified Query Algebra
3//
4// Copyright (c) 2023-2026 Cognica, Inc.
5//
6
7//! Transaction commands that require an explicit SQL block.
8
9use crate::SQLError;
10
11pub fn transaction_requires_explicit_block(transaction: &crate::ast::TransactionStmt) -> bool {
12    matches!(
13        transaction,
14        crate::ast::TransactionStmt::Savepoint(_)
15            | crate::ast::TransactionStmt::ReleaseSavepoint(_)
16            | crate::ast::TransactionStmt::RollbackToSavepoint(_)
17            | crate::ast::TransactionStmt::CommitAndChain
18            | crate::ast::TransactionStmt::RollbackAndChain
19    )
20}
21
22pub fn no_active_transaction_error(transaction: &crate::ast::TransactionStmt) -> SQLError {
23    let command = match transaction {
24        crate::ast::TransactionStmt::Savepoint(_) => "SAVEPOINT",
25        crate::ast::TransactionStmt::ReleaseSavepoint(_) => "RELEASE SAVEPOINT",
26        crate::ast::TransactionStmt::RollbackToSavepoint(_) => "ROLLBACK TO SAVEPOINT",
27        crate::ast::TransactionStmt::CommitAndChain => "COMMIT AND CHAIN",
28        crate::ast::TransactionStmt::RollbackAndChain => "ROLLBACK AND CHAIN",
29        _ => unreachable!("only explicit-block transaction commands use this error"),
30    };
31    SQLError::Routine {
32        sqlstate: "25P01".into(),
33        message: format!("{command} can only be used in transaction blocks"),
34    }
35}
36
37#[cfg(test)]
38mod tests {
39    use super::{no_active_transaction_error, transaction_requires_explicit_block};
40    use crate::{ast::TransactionStmt, SQLError};
41
42    #[test]
43    fn explicit_block_commands_keep_their_individual_diagnostics() {
44        for (command, spelling) in [
45            (TransactionStmt::Savepoint("s".into()), "SAVEPOINT"),
46            (
47                TransactionStmt::ReleaseSavepoint("s".into()),
48                "RELEASE SAVEPOINT",
49            ),
50            (
51                TransactionStmt::RollbackToSavepoint("s".into()),
52                "ROLLBACK TO SAVEPOINT",
53            ),
54            (TransactionStmt::CommitAndChain, "COMMIT AND CHAIN"),
55            (TransactionStmt::RollbackAndChain, "ROLLBACK AND CHAIN"),
56        ] {
57            assert!(transaction_requires_explicit_block(&command));
58            assert!(
59                matches!(no_active_transaction_error(&command), SQLError::Routine { sqlstate, message } if sqlstate == "25P01" && message == format!("{spelling} can only be used in transaction blocks"))
60            );
61        }
62        for command in [
63            TransactionStmt::Begin,
64            TransactionStmt::Commit,
65            TransactionStmt::Rollback,
66        ] {
67            assert!(!transaction_requires_explicit_block(&command));
68        }
69    }
70}