#7439·workerd

Whole-database quick_check fails with SQLITE_NOMEM as an empty schema grows

Author: NHMotorcyclesCreated Sep 19, 2026Updated Sep 19, 2026

Whole-database quick_check becomes unavailable as empty SQLite schema grows

Forfatter: Codex

Observed locally

Using Miniflare 4.20260708.1 with workerd 1.20260708.1, whole-database PRAGMA quick_check fails with SQLITE_NOMEM on an entirely empty database as the number of tables and CHECK expressions grows. The table-specific check still returns ok, but is not an equivalent global integrity check.

The reproducer creates only generic tables and inserts no rows. With 24 integer columns per table, each NOT NULL and constrained to nonnegative integers:

Tables EXPLAIN opcodes Whole quick_check
10 5006 ok
20 9906 ok
30 14806 ok
40 19706 ok
50 compile fails SQLITE_NOMEM

These are local-runtime measurements, not an attestation of a particular deployed D1 build. Distinct SQL comments were used for the EXPLAIN measurements to avoid reusing a previously prepared EXPLAIN across schema changes.

Minimal reproduction

With [email protected] available, run this Node ES module:

javascript
import assert from 'node:assert/strict';
import { Miniflare } from 'miniflare';

const mf = new Miniflare({
  modules: true,
  script: "export default {fetch(){return new Response('local')}}",
  compatibilityDate: '2026-07-15',
  d1Databases: { DB: 'generic-empty-database' },
  outboundService: () => { throw new Error('Network forbidden'); },
});

try {
  const db = await mf.getD1Database('DB');
  for (let table = 1; table <= 50; table++) {
    const columns = Array.from({ length: 24 }, (_, column) =>
      `c${column} INTEGER NOT NULL CHECK(typeof(c${column})='integer' AND c${column}>=0)`);
    await db.prepare(`CREATE TABLE synthetic_${table} (${columns.join(',')})`).run();
  }
  await assert.rejects(db.prepare('PRAGMA quick_check').all(), /SQLITE_NOMEM/);
  assert.deepEqual(
    (await db.prepare('PRAGMA quick_check(synthetic_1)').all()).results,
    [{ quick_check: 'ok' }],
  );
} finally {
  await mf.dispose();
}

Likely mechanism and question

The pinned workerd source sets SQLITE_LIMIT_VDBE_OP to 25000. SQLite's growOpArray checks the next allocation against that limit and returns an allocation error. A separate local boundary probe accepts 21504 instructions and rejects 21505, consistent with the doubling sequence from an initial 42-op allocation. That behavioral threshold alone does not identify the precise configured limit; 25000 comes from the source.

Is there a supported way to execute whole-database physical integrity checking on schemas beyond this compilation threshold, without disabling CHECK validation or replacing the global check with independent table checks? If not, could workerd provide a bounded mechanism for this internal integrity operation, or make this limitation/error more actionable? This report is not asking for a blanket removal of SQL resource limits.

For hosted D1, is this particular compilation limit plan-dependent? I have not found a documented Free/Paid distinction for it and do not infer hosted production parameters from this local reproduction. More storage or more databases would not by itself address compilation of this check.