{"record":{"id":"620fb07a80480724","repo":"remix-run/remix","slug":"nested-transactions-require-database-savepoint-sup","errorCode":null,"errorMessage":"Nested transactions require database savepoint support","messagePattern":"Nested transactions require database savepoint support","errorType":"exception","errorClass":"DataTableQueryError","httpStatus":null,"severity":"error","filePath":"packages/data-table/src/lib/database.ts","lineNumber":930,"sourceCode":"      } catch (error) {\n        try {\n          await this.#driver.rollbackTransaction(token)\n        } catch (rollbackError) {\n          throw new AggregateError(\n            [error, rollbackError],\n            'Database transaction and rollback both failed',\n            { cause: error },\n          )\n        }\n        throw error\n      }\n\n      await this.#driver.commitTransaction(token)\n      return result\n    }\n\n    if (!this.capabilities.savepoints) {\n      throw new DataTableQueryError('Nested transactions require database savepoint support')\n    }\n\n    let savepointName = 'sp_' + String(this.#savepointCounter.value)\n    this.#savepointCounter.value += 1\n\n    await this.#driver.createSavepoint(this.#token, savepointName)\n\n    let result: result\n    try {\n      result = await callback(this)\n    } catch (error) {\n      let failures: unknown[] = [error]\n      try {\n        await this.#driver.rollbackToSavepoint(this.#token, savepointName)\n      } catch (rollbackError) {\n        failures.push(rollbackError)\n      }\n      try {","sourceCodeStart":912,"sourceCodeEnd":948,"githubUrl":"https://github.com/remix-run/remix/blob/9696913134be3a4423513d2775f7b31d6917c049/packages/data-table/src/lib/database.ts#L912-L948","documentation":"Calling transaction() while already inside a transaction creates a nested transaction, implemented with SAVEPOINTs. The Database checks capabilities.savepoints first and throws if the driver doesn't support them, because a fake nested transaction would risk committing partial inner work.","triggerScenarios":"db.transaction(async (tx) => { await tx.transaction(cb) }) on a driver whose capabilities.savepoints is false (e.g. a minimal custom driver or a dialect without savepoint support).","commonSituations":"Custom drivers that never implemented createSavepoint/releaseSavepoint; helper functions that open their own transaction being called from inside another transaction.","solutions":["Restructure code so transactions aren't nested: accept the outer tx as a parameter instead of opening a new one (the classic 'transaction-in-transaction' refactor).","Driver authors: implement savepoints and set capabilities.savepoints = true.","Branch on db.capabilities.savepoints to decide whether nesting is allowed on the current driver."],"exampleFix":"// before\nasync function saveThing(db, data) {\n  await db.transaction(async (tx) => { ... })\n}\nawait db.transaction(async (tx) => saveThing(tx, data))\n\n// after\nasync function saveThing(db, data) {\n  await db.transaction(async (tx) => { ... })\n}\n// call saveThing(db, data) at top level, not inside another transaction","handlingStrategy":"validation","validationCode":"if (depth > 0 && !db.capabilities.savepoints) {\n  return callback(db) // run without a nested transaction\n}\nreturn db.transaction(callback)","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Pass the outer tx into helpers instead of opening nested transactions.","Check capabilities.savepoints before nesting."],"tags":["data-table","transactions","savepoints","capabilities"],"backgroundTag":"nested-transaction-unsupported","analyzedSha":"9696913134be3a4423513d2775f7b31d6917c049","analyzedAt":"2026-08-27T19:55:01.024Z","schemaVersion":2},"datasetVersion":"2026-08-28T00:17:15.603Z"}