From d290e60dd820ea3bd98f777613ae7010dd5208af Mon Sep 17 00:00:00 2001 From: bintangakbarRK Date: Thu, 6 Aug 2026 00:06:14 +0700 Subject: [PATCH 1/2] fix: normalize CRLF before sending D1 commands to the remote query API The D1 query API splits multi-statement SQL on ';' server-side and mishandles CRLF line endings inside compound statements such as a CREATE TRIGGER ... BEGIN ... END; body, producing 'incomplete input: SQLITE_ERROR'. wrangler d1 migrations apply --remote sends the whole migration (plus the tracking INSERT) as one command string, so CRLF migrations with triggers failed while the same bytes via the import-based execute --remote --file succeeded. Normalize CRLF to LF in the command path before sending. Closes #14991. --- .changeset/d1-crlf-remote-query.md | 7 +++ .../wrangler/src/__tests__/d1/execute.test.ts | 59 ++++++++++++++++++- packages/wrangler/src/d1/execute.ts | 9 ++- 3 files changed, 69 insertions(+), 6 deletions(-) create mode 100644 .changeset/d1-crlf-remote-query.md diff --git a/.changeset/d1-crlf-remote-query.md b/.changeset/d1-crlf-remote-query.md new file mode 100644 index 00000000000..124eba53acd --- /dev/null +++ b/.changeset/d1-crlf-remote-query.md @@ -0,0 +1,7 @@ +--- +"wrangler": patch +--- + +fix: normalize CRLF line endings before sending D1 commands to the remote query API + +`wrangler d1 migrations apply --remote` and `wrangler d1 execute --remote --command` failed with `incomplete input: SQLITE_ERROR` when the SQL contained CRLF line endings inside a compound statement such as a `CREATE TRIGGER ... BEGIN ... END;` body. The command string is now normalized to LF before it is sent to the D1 query API. diff --git a/packages/wrangler/src/__tests__/d1/execute.test.ts b/packages/wrangler/src/__tests__/d1/execute.test.ts index 2ced79fc5e7..113a9f7fef1 100644 --- a/packages/wrangler/src/__tests__/d1/execute.test.ts +++ b/packages/wrangler/src/__tests__/d1/execute.test.ts @@ -302,9 +302,62 @@ To continue without logging in, rerun this command with \`--temporary\`. Wrangle expect(std.out).toMatch("🚣 Executed 1 command in 123.46ms"); }); - it("should format batch execution duration with 2 decimal places", async ({ - expect, - }) => { + it("should normalize CRLF line endings in commands sent to the remote query API", async ({ + expect, + }) => { + setIsTTY(false); + writeWranglerConfig({ + d1_databases: [ + { binding: "DATABASE", database_name: "db", database_id: "xxxx" }, + ], + }); + + msw.use( + ...getMswSuccessMembershipHandlers([ + { + id: "some-account-id", + name: "test-account", + }, + ]) + ); + + let sentSql: string | undefined; + msw.use( + http.get("*/accounts/:accountId/d1/database", async () => { + return HttpResponse.json( + createFetchResult([ + { uuid: "xxxx", name: "db", created_at: "", version: "alpha" }, + ]) + ); + }), + http.post( + "*/accounts/:accountId/d1/database/:databaseId/query", + async ({ request }) => { + sentSql = ((await request.json()) as { sql: string }).sql; + return HttpResponse.json( + createFetchResult([ + { + results: [{ result: 1 }], + success: true, + meta: { duration: 100 }, + }, + ]) + ); + } + ) + ); + + await runWrangler( + "d1 execute db --remote --command 'CREATE TRIGGER trg BEFORE DELETE ON probe_z\r\nBEGIN\r\n SELECT RAISE(ABORT, '\''no'\'');\r\nEND;'" + ); + + expect(sentSql).toBeDefined(); + expect(sentSql).not.toContain("\r"); + }); + + it("should format batch execution duration with 2 decimal places", async ({ + expect, + }) => { setIsTTY(false); writeWranglerConfig({ d1_databases: [ diff --git a/packages/wrangler/src/d1/execute.ts b/packages/wrangler/src/d1/execute.ts index 290a0a64fbb..f1dc9d450a6 100644 --- a/packages/wrangler/src/d1/execute.ts +++ b/packages/wrangler/src/d1/execute.ts @@ -503,14 +503,17 @@ async function executeRemotely({ }, ]; } else { + // The D1 query API splits multi-statement SQL on `;` server-side, and + // mishandles CRLF line endings inside compound statements such as a + // `CREATE TRIGGER ... BEGIN ... END;` body (issue #14991). Normalize + // to LF so the server receives the same input that works for LF files. + const sql = input.command?.replace(/\r\n/g, "\n"); const result = await d1ApiPost( config, accountId, db, "query", - { - sql: input.command, - } + { sql } ); logResult(result); return result; From 4527e2a1acdc45cee0bf4618162a9ab076798c3f Mon Sep 17 00:00:00 2001 From: bintangakbarRK Date: Thu, 6 Aug 2026 00:26:13 +0700 Subject: [PATCH 2/2] chore: fix changeset title and test indentation per review feedback - Remove the conventional-commit prefix from the changeset title (REVIEW.md forbids type prefixes in changeset titles). - Re-indent the new execute test to the describe nesting level and apply oxfmt so the repository formatting check passes. --- .changeset/d1-crlf-remote-query.md | 2 +- .../wrangler/src/__tests__/d1/execute.test.ts | 94 +++++++++---------- 2 files changed, 48 insertions(+), 48 deletions(-) diff --git a/.changeset/d1-crlf-remote-query.md b/.changeset/d1-crlf-remote-query.md index 124eba53acd..6e641c4946a 100644 --- a/.changeset/d1-crlf-remote-query.md +++ b/.changeset/d1-crlf-remote-query.md @@ -2,6 +2,6 @@ "wrangler": patch --- -fix: normalize CRLF line endings before sending D1 commands to the remote query API +Normalize CRLF line endings before sending D1 commands to the remote query API `wrangler d1 migrations apply --remote` and `wrangler d1 execute --remote --command` failed with `incomplete input: SQLITE_ERROR` when the SQL contained CRLF line endings inside a compound statement such as a `CREATE TRIGGER ... BEGIN ... END;` body. The command string is now normalized to LF before it is sent to the D1 query API. diff --git a/packages/wrangler/src/__tests__/d1/execute.test.ts b/packages/wrangler/src/__tests__/d1/execute.test.ts index 113a9f7fef1..d4041276d90 100644 --- a/packages/wrangler/src/__tests__/d1/execute.test.ts +++ b/packages/wrangler/src/__tests__/d1/execute.test.ts @@ -302,62 +302,62 @@ To continue without logging in, rerun this command with \`--temporary\`. Wrangle expect(std.out).toMatch("🚣 Executed 1 command in 123.46ms"); }); - it("should normalize CRLF line endings in commands sent to the remote query API", async ({ - expect, - }) => { - setIsTTY(false); - writeWranglerConfig({ - d1_databases: [ - { binding: "DATABASE", database_name: "db", database_id: "xxxx" }, - ], - }); + it("should normalize CRLF line endings in commands sent to the remote query API", async ({ + expect, + }) => { + setIsTTY(false); + writeWranglerConfig({ + d1_databases: [ + { binding: "DATABASE", database_name: "db", database_id: "xxxx" }, + ], + }); - msw.use( - ...getMswSuccessMembershipHandlers([ - { - id: "some-account-id", - name: "test-account", - }, - ]) - ); + msw.use( + ...getMswSuccessMembershipHandlers([ + { + id: "some-account-id", + name: "test-account", + }, + ]) + ); - let sentSql: string | undefined; - msw.use( - http.get("*/accounts/:accountId/d1/database", async () => { - return HttpResponse.json( - createFetchResult([ - { uuid: "xxxx", name: "db", created_at: "", version: "alpha" }, - ]) - ); - }), - http.post( - "*/accounts/:accountId/d1/database/:databaseId/query", - async ({ request }) => { - sentSql = ((await request.json()) as { sql: string }).sql; + let sentSql: string | undefined; + msw.use( + http.get("*/accounts/:accountId/d1/database", async () => { return HttpResponse.json( createFetchResult([ - { - results: [{ result: 1 }], - success: true, - meta: { duration: 100 }, - }, + { uuid: "xxxx", name: "db", created_at: "", version: "alpha" }, ]) ); - } - ) - ); + }), + http.post( + "*/accounts/:accountId/d1/database/:databaseId/query", + async ({ request }) => { + sentSql = ((await request.json()) as { sql: string }).sql; + return HttpResponse.json( + createFetchResult([ + { + results: [{ result: 1 }], + success: true, + meta: { duration: 100 }, + }, + ]) + ); + } + ) + ); - await runWrangler( - "d1 execute db --remote --command 'CREATE TRIGGER trg BEFORE DELETE ON probe_z\r\nBEGIN\r\n SELECT RAISE(ABORT, '\''no'\'');\r\nEND;'" - ); + await runWrangler( + "d1 execute db --remote --command 'CREATE TRIGGER trg BEFORE DELETE ON probe_z\r\nBEGIN\r\n SELECT RAISE(ABORT, '\''no'\'');\r\nEND;'" + ); - expect(sentSql).toBeDefined(); - expect(sentSql).not.toContain("\r"); - }); + expect(sentSql).toBeDefined(); + expect(sentSql).not.toContain("\r"); + }); - it("should format batch execution duration with 2 decimal places", async ({ - expect, - }) => { + it("should format batch execution duration with 2 decimal places", async ({ + expect, + }) => { setIsTTY(false); writeWranglerConfig({ d1_databases: [