need help with drizzle transactions and foreign key constraints

I have a circular database schema: service references one service_generation (as the latest_generation_id) and service_generation references service.id, which poses an issue when trying to insert. To get around this, I am trying to use deferrable fk constraints + transactions. Since drizzle does not support sqlite deferred foreign key constraints, I've manually added DEFERRABLE INITIALLY DEFERRED to the FOREIGN KEY line in the migration. The error:
22:17:46.594Z debug database: PRAGMA defer_foreign_keys = true;
22:17:46.594Z debug database: insert into "service" ("id", "name", "project_id", "latest_generation_id", "redeploy_secret", "deployed_generation_id", "created_at") values ((uuid_generate_v7()), ?, ?, ?, ?, null, CURRENT_TIMESTAMP) returning "id"
22:17:46.596Z error trpc:server: Internal server error on mutation: projects.services.create FOREIGN KEY constraint failed
SqliteError: FOREIGN KEY constraint failed
at PreparedQuery.values (/home/derock/Documents/Code/hostforge/node_modules/.pnpm/[email protected]_@[email protected]_@[email protected]_@[email protected]_bet_2qanqj7n56pb6fe7hpfsaedbzq/node_modules/src/better-sqlite3/session.ts:156:26)
... 4 lines matching cause stack trace ...
at process.processTicksAndRejections (node:internal/process/task_queues:95:5) {
[stack]: 'SqliteError: FOREIGN KEY constraint failed\n' +
' at PreparedQuery.values (node_modules/.pnpm/[email protected]_@[email protected]_@[email protected]_@[email protected]_bet_2qanqj7n56pb6fe7hpfsaedbzq/node_modules/src/better-sqlite3/session.ts:156:26)\n' +
' at PreparedQuery.all (node_modules/.pnpm/[email protected]_@[email protected]_@[email protected]_@[email protected]_bet_2qanqj7n56pb6fe7hpfsaedbzq/node_modules/src/better-sqlite3/session.ts:124:21)\n' +
' at QueryPromise.all (node_modules/.pnpm/[email protected]_[..truncated..]/node_modules/src/sqlite-core/query-builders/insert.ts:355:26)\n' +
' at QueryPromise.execute (node_modules/.pnpm/[email protected]_[..truncated..]/node_modules/src/sqlite-core/query-builders/insert.ts:367:40)\n' +
' at QueryPromise.then (node_modules/.pnpm/[email protected]_[..truncated..]/node_modules/src/query-promise.ts:31:15)\n' +
' at process.processTicksAndRejections (node:internal/process/task_queues:95:5)'
22:17:46.594Z debug database: PRAGMA defer_foreign_keys = true;
22:17:46.594Z debug database: insert into "service" ("id", "name", "project_id", "latest_generation_id", "redeploy_secret", "deployed_generation_id", "created_at") values ((uuid_generate_v7()), ?, ?, ?, ?, null, CURRENT_TIMESTAMP) returning "id"
22:17:46.596Z error trpc:server: Internal server error on mutation: projects.services.create FOREIGN KEY constraint failed
SqliteError: FOREIGN KEY constraint failed
at PreparedQuery.values (/home/derock/Documents/Code/hostforge/node_modules/.pnpm/[email protected]_@[email protected]_@[email protected]_@[email protected]_bet_2qanqj7n56pb6fe7hpfsaedbzq/node_modules/src/better-sqlite3/session.ts:156:26)
... 4 lines matching cause stack trace ...
at process.processTicksAndRejections (node:internal/process/task_queues:95:5) {
[stack]: 'SqliteError: FOREIGN KEY constraint failed\n' +
' at PreparedQuery.values (node_modules/.pnpm/[email protected]_@[email protected]_@[email protected]_@[email protected]_bet_2qanqj7n56pb6fe7hpfsaedbzq/node_modules/src/better-sqlite3/session.ts:156:26)\n' +
' at PreparedQuery.all (node_modules/.pnpm/[email protected]_@[email protected]_@[email protected]_@[email protected]_bet_2qanqj7n56pb6fe7hpfsaedbzq/node_modules/src/better-sqlite3/session.ts:124:21)\n' +
' at QueryPromise.all (node_modules/.pnpm/[email protected]_[..truncated..]/node_modules/src/sqlite-core/query-builders/insert.ts:355:26)\n' +
' at QueryPromise.execute (node_modules/.pnpm/[email protected]_[..truncated..]/node_modules/src/sqlite-core/query-builders/insert.ts:367:40)\n' +
' at QueryPromise.then (node_modules/.pnpm/[email protected]_[..truncated..]/node_modules/src/query-promise.ts:31:15)\n' +
' at process.processTicksAndRejections (node:internal/process/task_queues:95:5)'
Related code:
await db.transaction(async (trx) => {
// mark all deferrable
// error is same with and without this line
trx.run(sql`PRAGMA defer_foreign_keys = true;`);

// create the service
const [data] = await trx
.insert(service)
.values({
latestGenerationId: "",
[...]
})
.returning({
id: serviceGeneration.id,
});

assert(data?.id, "Expected service data to be returned");

// create initial generation
trx.run(sql`PRAGMA defer_foreign_keys = true;`);
const [generation] = await trx
.insert(serviceGeneration)
.values({
serviceId: data.id,
[...]
})
.returning({
id: serviceGeneration.id,
});

assert(generation?.id, "Expected generation data to be returned");

// update the service with the generation id
await trx
.update(service)
.set({
latestGenerationId: generation.id,
})
.where(eq(service.id, data.id))
.execute();
});
await db.transaction(async (trx) => {
// mark all deferrable
// error is same with and without this line
trx.run(sql`PRAGMA defer_foreign_keys = true;`);

// create the service
const [data] = await trx
.insert(service)
.values({
latestGenerationId: "",
[...]
})
.returning({
id: serviceGeneration.id,
});

assert(data?.id, "Expected service data to be returned");

// create initial generation
trx.run(sql`PRAGMA defer_foreign_keys = true;`);
const [generation] = await trx
.insert(serviceGeneration)
.values({
serviceId: data.id,
[...]
})
.returning({
id: serviceGeneration.id,
});

assert(generation?.id, "Expected generation data to be returned");

// update the service with the generation id
await trx
.update(service)
.set({
latestGenerationId: generation.id,
})
.where(eq(service.id, data.id))
.execute();
});
Any ideas on why my deferrable fk's arent working? normally SQLite should only check the fks at the end of the transaction when commit; is ran. PRAGMA defer_foreign_keys = true; should also completely disable fk checks until the end of the transaction.
No description
3 Replies
Derock
DerockOP7mo ago
bump are drizzle transactions not real database transactions?
Derock
DerockOP7mo ago
No description
Derock
DerockOP7mo ago
this errors with cannot commit - no transaction is active ok so they are actual transactions, https://discord.com/channels/1043890932593987624/1240832420945596416/1240958720033165354 still tryna figure out why this isn't working then was able to get this working in a very hacky way:
// create a generation for the service
const trxResult = await ctx.db.transaction(async (trx) => {
// @ts-expect-error using drizzle-orm doesnt work, keep getting foreign key constraint error after the first insert despite it being deferred
const db: Database = trx.session.client;

db.pragma(`defer_foreign_keys = ON`);

const generationId = uuidv7();
const serviceId = uuidv7();

// create initial generation
const dialect = new SQLiteSyncDialect();
const createGenerationQuery = dialect.sqlToQuery(
trx
.insert(serviceGeneration)
.values({
id: generationId,
serviceId: serviceId,
source: ServiceSource.Docker,
dockerImage: "traefik/whoami",
})
.getSQL(),
);

const genCreateResult = db
.prepare(createGenerationQuery.sql)
.run(...createGenerationQuery.params);

logger.debug(
"inserted generation",
createGenerationQuery.sql,
genCreateResult,
);

// create the service
const createServiceQuery = dialect.sqlToQuery(
trx
.insert(service)
.values({
id: serviceId,
name: input.name,
projectId: ctx.project.getData().id,
latestGenerationId: generationId,
redeploySecret: randomBytes(env.REDEPLOY_SECRET_BYTES).toString(
"hex",
),
})
.returning({
id: serviceGeneration.id,
})
.getSQL(),
);

const createResult = db
.prepare(createServiceQuery.sql)
.run(...createServiceQuery.params);

logger.debug("inserted service", createServiceQuery.sql, createResult);

return serviceId;
})
// create a generation for the service
const trxResult = await ctx.db.transaction(async (trx) => {
// @ts-expect-error using drizzle-orm doesnt work, keep getting foreign key constraint error after the first insert despite it being deferred
const db: Database = trx.session.client;

db.pragma(`defer_foreign_keys = ON`);

const generationId = uuidv7();
const serviceId = uuidv7();

// create initial generation
const dialect = new SQLiteSyncDialect();
const createGenerationQuery = dialect.sqlToQuery(
trx
.insert(serviceGeneration)
.values({
id: generationId,
serviceId: serviceId,
source: ServiceSource.Docker,
dockerImage: "traefik/whoami",
})
.getSQL(),
);

const genCreateResult = db
.prepare(createGenerationQuery.sql)
.run(...createGenerationQuery.params);

logger.debug(
"inserted generation",
createGenerationQuery.sql,
genCreateResult,
);

// create the service
const createServiceQuery = dialect.sqlToQuery(
trx
.insert(service)
.values({
id: serviceId,
name: input.name,
projectId: ctx.project.getData().id,
latestGenerationId: generationId,
redeploySecret: randomBytes(env.REDEPLOY_SECRET_BYTES).toString(
"hex",
),
})
.returning({
id: serviceGeneration.id,
})
.getSQL(),
);

const createResult = db
.prepare(createServiceQuery.sql)
.run(...createServiceQuery.params);

logger.debug("inserted service", createServiceQuery.sql, createResult);

return serviceId;
})
trying that exact same approach with drizzle-orm doesn't work:
// create a generation for the service
const trxResult = await ctx.db.transaction(async (trx) => {
// @ts-expect-error using drizzle-orm doesnt work, keep getting foreign key constraint error after the first insert despite it being deferred
const db: Database = trx.session.client;

db.pragma(`defer_foreign_keys = ON`);

const generationId = uuidv7();
const serviceId = uuidv7();

// create initial generation
await trx.insert(serviceGeneration).values({
id: generationId,
serviceId: serviceId,
source: ServiceSource.Docker,
dockerImage: "traefik/whoami",
});

logger.debug("inserted generation");

// create the service
await trx
.insert(service)
.values({
id: serviceId,
name: input.name,
projectId: ctx.project.getData().id,
latestGenerationId: generationId,
redeploySecret: randomBytes(env.REDEPLOY_SECRET_BYTES).toString(
"hex",
),
})
.returning({
id: serviceGeneration.id,
});

logger.debug("inserted service");

return serviceId;
})
// create a generation for the service
const trxResult = await ctx.db.transaction(async (trx) => {
// @ts-expect-error using drizzle-orm doesnt work, keep getting foreign key constraint error after the first insert despite it being deferred
const db: Database = trx.session.client;

db.pragma(`defer_foreign_keys = ON`);

const generationId = uuidv7();
const serviceId = uuidv7();

// create initial generation
await trx.insert(serviceGeneration).values({
id: generationId,
serviceId: serviceId,
source: ServiceSource.Docker,
dockerImage: "traefik/whoami",
});

logger.debug("inserted generation");

// create the service
await trx
.insert(service)
.values({
id: serviceId,
name: input.name,
projectId: ctx.project.getData().id,
latestGenerationId: generationId,
redeploySecret: randomBytes(env.REDEPLOY_SECRET_BYTES).toString(
"hex",
),
})
.returning({
id: serviceGeneration.id,
});

logger.debug("inserted service");

return serviceId;
})
15:20:57.412Z debug database: insert into "service_generation" ("id", "service_id", "deployment_id", "source", "environment", "docker_image", "docker_registry_username", "docker_registry_password", "github_username", "github_repository", "github_branch", "git_url", "git_branch", "build_method", "build_path", "command", "entrypoint", "replicas", "max_replicas_per_node", "deploy_mode", "zero_downtime", "max_cpu", "max_memory", "max_pids", "restart", "restart_delay", "restart_max_attempts", "healthcheck_enabled", "healthcheck_command", "healthcheck_interval", "healthcheck_timeout", "healthcheck_retries", "healthcheck_start_period", "logging_max_size", "logging_max_files", "created_at") values (?, ?, null, ?, null, ?, null, null, null, null, null, null, null, ?, ?, null, null, ?, null, ?, ?, ?, ?, ?, ?, ?, null, ?, null, ?, ?, ?, ?, ?, ?, CURRENT_TIMESTAMP)
15:20:57.421Z error trpc:server: Internal server error on mutation: projects.services.create FOREIGN KEY constraint failed
SqliteError: FOREIGN KEY constraint failed
at PreparedQuery.run (/home/derock/Documents/Code/hostforge/node_modules/.pnpm/[email protected]_@[email protected]_@[email protected]_@[email protected]_bett_hndeffta6o6q5vdmfsppooayoi/node_modules/src/better-sqlite3/session.ts:103:20)
... 3 lines matching cause stack trace ...
at process.processTicksAndRejections (node:internal/process/task_queues:95:5) {
[stack]: 'SqliteError: FOREIGN KEY constraint failed\n' +
' at PreparedQuery.run (/home/derock/Documents/Code/hostforge/node_modules/.pnpm/[email protected]_@[email protected]_@[email protected]_@[email protected]_bett_hndeffta6o6q5vdmfsppooayoi/node_modules/src/better-sqlite3/session.ts:103:20)\n' +
15:20:57.412Z debug database: insert into "service_generation" ("id", "service_id", "deployment_id", "source", "environment", "docker_image", "docker_registry_username", "docker_registry_password", "github_username", "github_repository", "github_branch", "git_url", "git_branch", "build_method", "build_path", "command", "entrypoint", "replicas", "max_replicas_per_node", "deploy_mode", "zero_downtime", "max_cpu", "max_memory", "max_pids", "restart", "restart_delay", "restart_max_attempts", "healthcheck_enabled", "healthcheck_command", "healthcheck_interval", "healthcheck_timeout", "healthcheck_retries", "healthcheck_start_period", "logging_max_size", "logging_max_files", "created_at") values (?, ?, null, ?, null, ?, null, null, null, null, null, null, null, ?, ?, null, null, ?, null, ?, ?, ?, ?, ?, ?, ?, null, ?, null, ?, ?, ?, ?, ?, ?, CURRENT_TIMESTAMP)
15:20:57.421Z error trpc:server: Internal server error on mutation: projects.services.create FOREIGN KEY constraint failed
SqliteError: FOREIGN KEY constraint failed
at PreparedQuery.run (/home/derock/Documents/Code/hostforge/node_modules/.pnpm/[email protected]_@[email protected]_@[email protected]_@[email protected]_bett_hndeffta6o6q5vdmfsppooayoi/node_modules/src/better-sqlite3/session.ts:103:20)
... 3 lines matching cause stack trace ...
at process.processTicksAndRejections (node:internal/process/task_queues:95:5) {
[stack]: 'SqliteError: FOREIGN KEY constraint failed\n' +
' at PreparedQuery.run (/home/derock/Documents/Code/hostforge/node_modules/.pnpm/[email protected]_@[email protected]_@[email protected]_@[email protected]_bett_hndeffta6o6q5vdmfsppooayoi/node_modules/src/better-sqlite3/session.ts:103:20)\n' +
Want results from more Discord servers?
Add your server