Skip to content
5 changes: 5 additions & 0 deletions .changeset/fix-d1-export-infinity.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"miniflare": patch
---

Fix Infinity/-Infinity being emitted as an invalid bare identifier in D1 export
3 changes: 3 additions & 0 deletions packages/miniflare/src/workers/d1/dumpSql.ts
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,9 @@ export function* dumpSql(
if (cell === null) {
return "NULL";
} else if (cellType === "number") {
if (!Number.isFinite(cell)) {
return (cell as number) > 0 ? "9e999" : "-9e999";
}
return cell;
} else if (cellType === "string") {
return outputQuotedEscapedString(cell);
Expand Down
67 changes: 67 additions & 0 deletions packages/miniflare/test/plugins/d1/suite.ts
Original file line number Diff line number Diff line change
Expand Up @@ -632,6 +632,73 @@ test("dumpSql exports and imports complete database structure and content correc
await isDatabaseEqual(expect, originalDb, mirrorDb);
});

/**
* Test that dumpSql emits a valid SQL literal for non-finite REAL values
* (Infinity / -Infinity), instead of the raw JS Infinity/-Infinity string,
* which SQLite parses back as a bare identifier and fails to re-import.
*/
test("dumpSql exports Infinity and -Infinity as valid, re-importable SQL literals", async ({
expect,
}) => {
const tmp1 = await useTmp();
const baseConfig = opts.workers[0].config;
const originalMF = new Miniflare({
resourcePersistencePath: tmp1,
workers: [
{ config: { ...baseConfig, env: { test: { type: "d1", id: "test" } } } },
],
});
useDispose(originalMF);
const originalDb = await originalMF.getD1Database("test");

await originalDb.exec(
`CREATE TABLE inf_test (id INTEGER PRIMARY KEY, r REAL)`
);
await originalDb.exec(
`INSERT INTO inf_test (id, r) VALUES (1, 9e999), (2, -9e999)`
);

const result = await originalDb
.prepare("PRAGMA miniflare_d1_export(?,?,?);")
.bind(0, 0)
.raw();
const [dumpStatements] = result as [string[]];
const dump = dumpStatements.join("\n");

// The dump must not contain the raw JS Infinity/-Infinity tokens as bare identifiers
expect(dump).not.toMatch(/VALUES\(1,Infinity\)/);
expect(dump).not.toMatch(/VALUES\(2,-Infinity\)/);

// Re-importing the dump must succeed and preserve the values
const tmp2 = await useTmp();
const mirrorMF = new Miniflare({
resourcePersistencePath: tmp2,
workers: [
{ config: { ...baseConfig, env: { test: { type: "d1", id: "test" } } } },
],
});
useDispose(mirrorMF);
const mirrorDb = await mirrorMF.getD1Database("test");

// exec(dump) must not throw, If the dump contained the bare `Infinity`/`-Infinity`
// identifiers, SQLite would reject them at parse time and exec would throw
await mirrorDb.exec(dump);

// Assert the exported SQL actually contains the correctly converted value for each row
expect(dump).toMatch(
/INSERT INTO "inf_test" \("id","r"\) VALUES\(1,9e999\);/
);
expect(dump).toMatch(
/INSERT INTO "inf_test" \("id","r"\) VALUES\(2,-9e999\);/
);

// And confirm the import actually succeeded (rows exist post-import)
const rows = await mirrorDb
.prepare(`SELECT id FROM inf_test ORDER BY id`)
.raw();
expect(rows).toEqual([[1], [2]]);
});

/**
* Populates a D1 database with test data for schema export testing.
* Creates tables with various schema features (foreign keys, special characters, etc.)
Expand Down
Loading