Skip to content

Commit c78f8ad

Browse files
penalosaclaude[bot]alsurenpetebacondarwin
committed
[wrangler] Include column names in D1 SQL export (#12277)
Co-authored-by: claude[bot] <209825114+claude[bot]@users.noreply.github.com> Co-authored-by: Somhairle MacLeòid <penalosa@users.noreply.github.com> Co-authored-by: David Laban <dlaban@cloudflare.com> Co-authored-by: Pete Bacon Darwin <pbacondarwin@cloudflare.com>
1 parent 5191418 commit c78f8ad

3 files changed

Lines changed: 65 additions & 14 deletions

File tree

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
---
2+
"wrangler": patch
3+
---
4+
5+
Include column names in D1 SQL export INSERT statements
6+
7+
D1 SQL exports now include column names in INSERT statements (e.g., `INSERT INTO "table" ("col1","col2") VALUES(...)`). This ensures that exported SQL can be successfully imported even when the target table has columns in a different order than the original, which commonly occurs during iterative development when schemas evolve.

packages/miniflare/src/workers/d1/dumpSql.ts

Lines changed: 46 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,23 @@
55
// as possible, with any deviations noted.
66
import type { SqlStorage } from "@cloudflare/workers-types/experimental";
77

8+
/** Stats tracking for dumpSql. Will be mutated in place if provided. */
9+
export interface DumpSqlStats {
10+
rows_read: number;
11+
rows_written: number;
12+
// Stats for tracking INSERT statement sizes (column names are always included)
13+
/** Number of INSERT statements over 100KB (current size, with column names) */
14+
inserts_over_100kb_with_column_names: number;
15+
/** Number of INSERT statements that would be over 100KB without column names (for backward comparison) */
16+
inserts_already_over_100kb: number;
17+
/** Total number of INSERT statements generated */
18+
total_inserts: number;
19+
/** Maximum INSERT statement size without column names (hypothetical, for backward comparison) */
20+
max_insert_size: number;
21+
/** Maximum INSERT statement size (current size, with column names) */
22+
max_insert_size_with_column_names: number;
23+
}
24+
825
export function* dumpSql(
926
db: SqlStorage,
1027
options?: {
@@ -13,7 +30,7 @@ export function* dumpSql(
1330
tables?: string[];
1431
},
1532
/** Optional stats tracking. Will be mutated in place if provided */
16-
stats?: { rows_read: number; rows_written: number }
33+
stats?: DumpSqlStats
1734
) {
1835
// WARNING: the caller in D1 assumes non-empty exports, so think carefully before removing this initial yield.
1936
yield `PRAGMA defer_foreign_keys=TRUE;`;
@@ -83,6 +100,9 @@ export function* dumpSql(
83100

84101
const select = `SELECT ${columns.map((c) => escapeId(c.name)).join(", ")} FROM ${escapeId(table)};`;
85102
const rows_cursor = db.exec(select);
103+
const columnNames = columns.map((c) => escapeId(c.name)).join(",");
104+
// The column names portion is: " (" + columnNames + ")" = 3 + columnNames.length
105+
const columnNamesOverhead = 3 + columnNames.length;
86106
for (const dataRow of rows_cursor.raw()) {
87107
const formattedCells = dataRow.map((cell: unknown, i: number) => {
88108
const colType = columns[i].type;
@@ -109,7 +129,31 @@ export function* dumpSql(
109129
}
110130
});
111131

112-
yield `INSERT INTO ${escapeId(table)} VALUES(${formattedCells.join(",")});`;
132+
const insertStmt = `INSERT INTO ${escapeId(table)} (${columnNames}) VALUES(${formattedCells.join(",")});`;
133+
134+
// Track stats for INSERT statement sizes
135+
if (stats) {
136+
const currentSize = insertStmt.length;
137+
// Calculate what the size would be without column names (for comparison)
138+
const sizeWithoutColumnNames = currentSize - columnNamesOverhead;
139+
const LIMIT = 100 * 1024; // 100KB
140+
141+
stats.total_inserts++;
142+
if (sizeWithoutColumnNames > LIMIT) {
143+
stats.inserts_already_over_100kb++;
144+
}
145+
if (currentSize > LIMIT) {
146+
stats.inserts_over_100kb_with_column_names++;
147+
}
148+
if (sizeWithoutColumnNames > stats.max_insert_size) {
149+
stats.max_insert_size = sizeWithoutColumnNames;
150+
}
151+
if (currentSize > stats.max_insert_size_with_column_names) {
152+
stats.max_insert_size_with_column_names = currentSize;
153+
}
154+
}
155+
156+
yield insertStmt;
113157
}
114158
if (stats) {
115159
stats.rows_read += rows_cursor.rowsRead;

packages/wrangler/src/__tests__/d1/export.test.ts

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -73,14 +73,14 @@ describe("export", () => {
7373
const create_foo = "CREATE TABLE foo(id INTEGER PRIMARY KEY, value TEXT);";
7474
const create_bar = "CREATE TABLE bar(id INTEGER PRIMARY KEY, value TEXT);";
7575
const insert_foo = [
76-
`INSERT INTO "foo" VALUES(1,'xxx');`,
77-
`INSERT INTO "foo" VALUES(2,'yyy');`,
78-
`INSERT INTO "foo" VALUES(3,'zzz');`,
76+
`INSERT INTO "foo" ("id","value") VALUES(1,'xxx');`,
77+
`INSERT INTO "foo" ("id","value") VALUES(2,'yyy');`,
78+
`INSERT INTO "foo" ("id","value") VALUES(3,'zzz');`,
7979
];
8080
const insert_bar = [
81-
`INSERT INTO "bar" VALUES(1,'aaa');`,
82-
`INSERT INTO "bar" VALUES(2,'bbb');`,
83-
`INSERT INTO "bar" VALUES(3,'ccc');`,
81+
`INSERT INTO "bar" ("id","value") VALUES(1,'aaa');`,
82+
`INSERT INTO "bar" ("id","value") VALUES(2,'bbb');`,
83+
`INSERT INTO "bar" ("id","value") VALUES(3,'ccc');`,
8484
];
8585

8686
// Full export
@@ -311,13 +311,13 @@ describe("export", () => {
311311
[
312312
"PRAGMA defer_foreign_keys=TRUE;",
313313
"CREATE TABLE foo(id INTEGER PRIMARY KEY, value TEXT);",
314-
"INSERT INTO \"foo\" VALUES(1,'xxx');",
315-
"INSERT INTO \"foo\" VALUES(2,'yyy');",
316-
"INSERT INTO \"foo\" VALUES(3,'zzz');",
314+
'INSERT INTO "foo" ("id","value") VALUES(1,\'xxx\');',
315+
'INSERT INTO "foo" ("id","value") VALUES(2,\'yyy\');',
316+
'INSERT INTO "foo" ("id","value") VALUES(3,\'zzz\');',
317317
"CREATE TABLE baz(id INTEGER PRIMARY KEY, value TEXT);",
318-
"INSERT INTO \"baz\" VALUES(1,'111');",
319-
"INSERT INTO \"baz\" VALUES(2,'222');",
320-
"INSERT INTO \"baz\" VALUES(3,'333');",
318+
'INSERT INTO "baz" ("id","value") VALUES(1,\'111\');',
319+
'INSERT INTO "baz" ("id","value") VALUES(2,\'222\');',
320+
'INSERT INTO "baz" ("id","value") VALUES(3,\'333\');',
321321
].join("\n")
322322
);
323323
});

0 commit comments

Comments
 (0)