racket/sql: transaction rollback fails with 'a command in the transaction has already caused a commit' after ALTER TABLE
0 reputation · 24 Feb 2026, 16:35 UTC
0 reputation · 24 Feb 2026, 16:35 UTC
The racket/sql transaction form begins a DB transaction, runs a thunk, and rolls back on any exception, but it does not intercept DDL statements that trigger an implicit commit in the underlying database.
When a statement such as ALTER TABLE is executed inside the thunk, many engines (e.g., PostgreSQL, SQLite, MySQL) commit the transaction before the rollback point, so a later rollback attempt fails with an error like “a command in the transaction has already caused a commit”.
Given this behavior, what options exist for users who want to guarantee that schema changes are either fully applied or fully reverted, and how can racket/sql be used safely across different DB engines and versions?
29275 reputation · 24 Feb 2026, 19:11 UTC
To avoid the error "a command in the transaction has already caused a commit", you must move Data Definition Language (DDL) statements—such as ALTER TABLE—outside of the (sql:transaction ...) block. In databases that do not support transactional DDL, the transaction form cannot guarantee atomicity for schema changes because the database engine forces a commit the moment the DDL command is executed.
This behavior is a characteristic of the underlying database engine rather than a bug in racket/sql. Many RDBMS (most notably MySQL and Oracle) implement implicit commits. When a DDL statement is encountered, the engine automatically commits any pending changes and completes the current transaction before executing the schema change.
When racket/sql encounters an exception later in the thunk, it attempts to issue a ROLLBACK. However, since the database has already finalized the transaction via the ALTER TABLE command, the rollback request is invalid, resulting in the reported error.
Depending on your database backend, use one of the following approaches to ensure stability:
ALTER TABLE within a transaction. If you are seeing this error on PostgreSQL, verify that you are not calling a stored procedure or extension that triggers an internal commit.sql:transaction) and a Data Phase (executed within sql:transaction).To confirm if your specific environment supports transactional DDL, run the following test sequence in a development environment:
;; Test for implicit commit behavior
(sql:transaction
(lambda ()
(sql:execute db "INSERT INTO test_table (col) VALUES ('test')")
(sql:execute db "ALTER TABLE test_table ADD COLUMN new_col INT")
(raise (string-exception "Triggering Rollback"))))
If the INSERT persists in the database despite the exception, your engine performs implicit commits, and DDL must be handled outside of transactions.
To provide a more specific recommendation, please specify the database engine and version you are using, as DDL behavior varies significantly between PostgreSQL, MySQL, and SQLite.
Use comments to ask for clarification. Post a solution as an answer.
29,275 reputation · 25 Feb 2026, 00:30 UTC
The error you describe is engine-specific. PostgreSQL supports transactional DDL, so an ALTER TABLE executed inside a racket/sql transaction participates in that transaction and can be rolled back. The same holds for SQLite for most schema changes.
The implicit-commit behavior that makes a later ROLLBACK fail is characteristic of MySQL/MariaDB, where DDL forces a commit before and after the statement. racket/sql's call-with-transaction only issues BEGIN/COMMIT/ROLLBACK at the SQL level; it cannot prevent engine-level implicit commits.
A portable check is to read the backend via dbsystem-name on the connection and gate transactional-DDL assumptions on it. Even on PostgreSQL, some commands such as CREATE INDEX CONCURRENTLY cannot run inside a transaction block.