Bug Report
Version: drizzle-kit@1.0.0-beta.17-67b1795 (also reproducible on recent beta releases)
Database: Turso (libSQL over HTTP)
Dialect: turso
Description
drizzle-kit push fails when a schema change requires table recreation (e.g., changing a foreign key's onDelete from CASCADE to SET NULL). SQLite doesn't support ALTER TABLE for FK changes, so drizzle-kit recreates the table — but the transaction wrapping breaks on Turso's HTTP protocol.
Error
QueryError: SQLITE_UNKNOWN: SQLite error: cannot commit - no transaction is active
sql: 'commit'
Root Cause
In src/cli/commands/push-sqlite.ts, the push handler wraps DDL statements in a manual begin/commit transaction:
if (!isD1) await db.run("begin");
try {
for (const statement of [...lossStatements, ...sqlStatements]) {
await db.run(statement);
}
if (!isD1) await db.run("commit");
} catch (e) {
if (!isD1) await db.run("rollback");
}
The D1 check (isD1) correctly skips transactions for Cloudflare D1's HTTP protocol. However, Turso/libSQL's HTTP client (@libsql/client) has the same limitation — client.execute("begin") / client.execute("commit") don't work as expected because each execute() call is an independent HTTP request with no shared transaction context.
The connectToLibSQL function already provides a batchWithPragma method that wraps client.migrate(), which is the correct API for running DDL batches. The push handler should use this instead of manual transaction control for libSQL connections.
Note: The DDL statements themselves do execute successfully (they auto-commit individually), so the schema change is applied — but the subsequent commit call fails and the process exits with an error code, which is misleading.
Workaround
Patch connectToLibSQL's run method to skip transaction control statements:
run: async (query) => {
if (/^\s*(begin|commit|rollback)\s*$/i.test(typeof query === 'string' ? query : query.sql || '')) return;
await client.execute(query).catch((e) => {
throw new QueryError(e, query, []);
});
},
Expected Behavior
drizzle-kit push with dialect: "turso" should handle table recreations without erroring on transaction management, just as it does for D1.
Steps to Reproduce
- Configure drizzle-kit with
dialect: "turso" pointing to a Turso database
- Create a schema with a foreign key using
onDelete: "cascade"
- Run
drizzle-kit push to sync
- Change the FK to
onDelete: "set null"
- Run
drizzle-kit push again — fails with the transaction error
Bug Report
Version:
drizzle-kit@1.0.0-beta.17-67b1795(also reproducible on recent beta releases)Database: Turso (libSQL over HTTP)
Dialect:
tursoDescription
drizzle-kit pushfails when a schema change requires table recreation (e.g., changing a foreign key'sonDeletefromCASCADEtoSET NULL). SQLite doesn't supportALTER TABLEfor FK changes, so drizzle-kit recreates the table — but the transaction wrapping breaks on Turso's HTTP protocol.Error
Root Cause
In
src/cli/commands/push-sqlite.ts, the push handler wraps DDL statements in a manualbegin/committransaction:The D1 check (
isD1) correctly skips transactions for Cloudflare D1's HTTP protocol. However, Turso/libSQL's HTTP client (@libsql/client) has the same limitation —client.execute("begin")/client.execute("commit")don't work as expected because eachexecute()call is an independent HTTP request with no shared transaction context.The
connectToLibSQLfunction already provides abatchWithPragmamethod that wrapsclient.migrate(), which is the correct API for running DDL batches. The push handler should use this instead of manual transaction control for libSQL connections.Note: The DDL statements themselves do execute successfully (they auto-commit individually), so the schema change is applied — but the subsequent
commitcall fails and the process exits with an error code, which is misleading.Workaround
Patch
connectToLibSQL'srunmethod to skip transaction control statements:Expected Behavior
drizzle-kit pushwithdialect: "turso"should handle table recreations without erroring on transaction management, just as it does for D1.Steps to Reproduce
dialect: "turso"pointing to a Turso databaseonDelete: "cascade"drizzle-kit pushto synconDelete: "set null"drizzle-kit pushagain — fails with the transaction error