{"id":"71fd8446e5470242","repo":"knex/knex","slug":"transaction-concluded-with-foreignviolations-len","errorCode":null,"errorMessage":"Transaction concluded with ${foreignViolations.length} foreign key violations","messagePattern":"Transaction concluded with (.+?) foreign key violations","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"lib/dialects/sqlite3/execution/sqlite-transaction.js","lineNumber":112,"sourceCode":"          `Refusing to create transaction: unable to change \\`foreign_keys\\` pragma inside a nested transaction`\n        );\n      }\n\n      let maybeWrappedContainer = container;\n      if (restoreForeignCheck === true) {\n        // in the case where we are turning foreign key checks off for the duration of a transaction,\n        // we need to assert that there are no violations once the work of the transaction has been\n        // completed. this relies on the fact that Transaction._onAcquire runs the \"container\" promise\n        // to completion before executing \"COMMIT\"\n        maybeWrappedContainer = async (trx) => {\n          const res = await container(trx);\n\n          const foreignViolations = await this.client\n            .raw(executeForeignCheck())\n            .connection(conn);\n\n          if (foreignViolations.length > 0) {\n            throw new Error(\n              `Transaction concluded with ${foreignViolations.length} foreign key violations`\n            );\n          }\n          return res;\n        };\n      }\n\n      try {\n        // call out to the base class to actually do the work as it normally would\n        // note: the await is required here! we need to resolve the promise, not\n        // return it\n        return await this._onAcquire(maybeWrappedContainer, conn);\n      } finally {\n        // set the foreign_keys pragma back to what it was before we performed the transaction\n        this._restoreForeignCheck(conn, restoreForeignCheck).catch((e) => {\n          // we were unable to put it back like we found it. dispose the connection and\n          // allow any further queries to re-acquire a new, clean connection\n          this._logAndDispose(","sourceCodeStart":94,"sourceCodeEnd":130,"githubUrl":"https://github.com/knex/knex/blob/e25d54bcb707714a17f5a5744eba5c4246bb4d1d/lib/dialects/sqlite3/execution/sqlite-transaction.js#L94-L130","documentation":"When a strict SQLite transaction temporarily disables foreign_keys (the connection had it ON and the caller requested enforceForeignCheck: false), Knex re-enables checking at commit time and runs PRAGMA foreign_key_check. If that returns any violation rows, the transaction is rejected with this error and will be rolled back. This makes 'temporarily relax FK during this transaction' safe-by-construction: data that violates constraints cannot silently escape.","triggerScenarios":"Issuing knex.transaction(cb, { enforceForeignCheck: false }) (or a DDL alter path that sets enforceForeignCheck to false when not already transacting) and then INSERTing/UPDATEing rows that reference non-existent parent rows, or deleting parent rows that still have dependents. The check fires only when foreign keys were originally enabled and got turned off for the transaction.","commonSituations":"Bulk imports or seeding that intentionally disable FK checks for speed but contain orphaned references. Reordering dependent/parent inserts so children are inserted before parents. DDL table rebuilds (Knex's SQLite alter strategy) that copy data while FK is off and the data is inconsistent.","solutions":["Inspect PRAGMA foreign_key_check output for the affected tables and fix the offending rows (missing parents, orphaned children) before retrying.","Reorder operations so parent rows exist before child rows that reference them.","Run a pre-flight integrity check (PRAGMA foreign_key_check) on the source data before disabling enforcement.","If violations are expected and acceptable, do not disable enforcement; instead insert in dependency order under enforceForeignCheck: true."],"exampleFix":"// before\nawait knex.transaction(async (trx) => {\n  await trx('child').insert({ parent_id: 999 }); // parent 999 missing\n}, { enforceForeignCheck: false });\n// after\nawait trx('parent').insert({ id: 999 });\nawait trx('child').insert({ parent_id: 999 });","handlingStrategy":"validation","validationCode":"// pre-flight: ensure no FK violations exist before disabling enforcement\nasync function assertNoFkViolations(knex) {\n  const rows = await knex.raw('pragma foreign_key_check');\n  if (rows.length) throw new Error('Existing FK violations: ' + JSON.stringify(rows));\n}\nawait assertNoFkViolations(knex);\nawait knex.transaction(async (trx) => { /* inserts in dependency order */ }, { enforceForeignCheck: false });","typeGuard":null,"tryCatchPattern":"try {\n  await knex.transaction(async (trx) => { /* ... */ }, { enforceForeignCheck: false });\n} catch (e) {\n  if (/foreign key violations/i.test(e.message)) {\n    const violations = await knex.raw('pragma foreign_key_check');\n    // repair rows listed in violations, then retry in dependency order\n  } else throw e;\n}","preventionTips":["Insert parent rows before child rows.","Run pragma foreign_key_check before and after bulk loads.","Prefer enforceForeignCheck: true when data integrity is uncertain."],"tags":["sqlite","foreign-keys","transactions","data-integrity"],"analyzedSha":"e25d54bcb707714a17f5a5744eba5c4246bb4d1d","analyzedAt":"2026-08-03T18:35:32.148Z","schemaVersion":2}