Skip to content

Close a compound SQL statement when its END is lowercase - #15046

Open
erwinzhang7 wants to merge 3 commits into
cloudflare:mainfrom
erwinzhang7:fix-d1-splitter-lowercase-end
Open

Close a compound SQL statement when its END is lowercase#15046
erwinzhang7 wants to merge 3 commits into
cloudflare:mainfrom
erwinzhang7:fix-d1-splitter-lowercase-end

Conversation

@erwinzhang7

@erwinzhang7 erwinzhang7 commented Aug 5, 2026

Copy link
Copy Markdown

Spotted while investigating #14991. Independent of that issue and of any fix for it.

The bug

splitSqlQuery() matches the two ends of a compound statement asymmetrically:

function isCompoundStatementStart(str: string) { return /\s(BEGIN|CASE)\s$/i.test(str); }  // case insensitive
function isCompoundStatementEnd(str: string)   { return /\sEND[;\s]$/.test(str); }         // case sensitive

SQLite accepts either case. With a lowercase end, the compound statement never closes, so compoundStatementStack stays non-empty and every subsequent ; is treated as being inside the trigger body. Everything after the trigger is folded into it instead of being split out and executed.

CREATE TRIGGER IF NOT EXISTS update_trigger AFTER UPDATE ON items
begin
	DELETE FROM updates WHERE item_id=old.id;
end;
CREATE TABLE after_the_trigger (id TEXT PRIMARY KEY);

Before, 2 statements, with the CREATE TABLE swallowed:

[0] "CREATE TABLE t (id TEXT)"
[1] "CREATE TRIGGER g BEFORE DELETE ON t\nbegin\n  SELECT RAISE(ABORT,'no');\n..."

After, 3 statements, matching the uppercase behaviour exactly. CASE ... end inside a trigger body is affected the same way, collapsing 3 statements into 1.

Why it was not caught

should handle compound statements for BEGINs already exercises a lowercase begin, so case insensitivity was clearly intended. But every case in that test pairs it with an uppercase END, so only the start predicate was ever exercised in lowercase.

Change

One character: add the i flag to isCompoundStatementEnd, so both predicates agree.

This does not widen what counts as an end marker beyond the existing behaviour. \sEND still requires whitespace immediately before, so an identifier such as weekend; does not match, exactly as it did not before.


  • Tests
    • Tests included/updated
    • Automated tests not possible - manual testing has been completed as follows:
    • Additional testing not necessary because:
  • Public documentation
    • Cloudflare docs PR(s):
    • Documentation not necessary because: this restores the documented and already intended behaviour of statement splitting. DISTINCT/BEGIN/END case handling is not described anywhere in the public docs, and nothing about the supported SQL surface changes.

Added should handle a lowercase end closing a compound statement, placed next to the existing lowercase-begin coverage. Verified it is a real regression test rather than a passing assertion: with the i flag reverted the suite reports 1 failed of 16, and with the fix 16 of 16 pass. oxfmt --check is clean on both changed files.

splitSqlQuery matched the opening BEGIN/CASE of a compound statement case
insensitively but matched the closing END case sensitively. SQLite accepts
either case, so a trigger written with a lowercase end never closed and
every following statement was folded into the trigger body instead of
being split out.

The existing tests already covered a lowercase begin, but always paired it
with an uppercase END, so the asymmetry went unnoticed.
@changeset-bot

changeset-bot Bot commented Aug 5, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: d2dc2e8

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 3 packages
Name Type
wrangler Patch
@cloudflare/vite-plugin Patch
@cloudflare/vitest-pool-workers Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@workers-devprod
workers-devprod requested review from a team and dario-piotrowicz and removed request for a team August 5, 2026 17:10
@workers-devprod

workers-devprod commented Aug 5, 2026

Copy link
Copy Markdown
Contributor

Codeowners approval required for this PR:

  • ✅ @cloudflare/d1
  • ✅ @cloudflare/wrangler
Show detailed file reviewers

devin-ai-integration[bot]

This comment was marked as resolved.

@alsuren alsuren left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

approve once changeset is fixed

@erwinzhang7

Copy link
Copy Markdown
Author

Good catch, and correct on both counts. I had not read REVIEW.md before writing that changeset.

Rewritten in user-facing terms: it now says which commands are affected, what a user actually sees (statements after the block silently skipped), and keeps a short example. Dropped the reference to the internal helper and the paragraph about existing test coverage.

I also confirmed the CASE half of your suggestion before putting it in the changelog rather than taking it on faith. Without the fix, a trigger whose body contains a CASE ... end collapses from 3 statements to 1, so CASE blocks are affected the same way as BEGIN blocks and the changeset now mentions both.

@dario-piotrowicz dario-piotrowicz left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good to me! 😄

Thanks for the fix @erwinzhang7 😄

Comment on lines +334 to +336
// SQLite accepts either case. Only the BEGIN side was matched case
// insensitively, so a lowercase `end` never closed the compound statement
// and every following statement was swallowed into the trigger.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this comment helpful/necessary? 🤔

Suggested change
// SQLite accepts either case. Only the BEGIN side was matched case
// insensitively, so a lowercase `end` never closed the compound statement
// and every following statement was swallowed into the trigger.

Comment thread .changeset/d1-splitter-lowercase-end.md Outdated
"wrangler": patch
---

Run every statement in a D1 SQL file when a trigger or CASE block ends with a lowercase `end`

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

very nit: the changelog title doesn't feel super clear to me 🤔

I'd go with something along the lines of:

Suggested change
Run every statement in a D1 SQL file when a trigger or CASE block ends with a lowercase `end`
Fixes D1 SQL statements not handling lowercase `end`s correclty

maybe

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Noted with thanks :)

@workers-devprod workers-devprod left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Codeowners reviews satisfied

@github-project-automation github-project-automation Bot moved this from Untriaged to Approved in workers-sdk Aug 5, 2026
@pkg-pr-new

pkg-pr-new Bot commented Aug 5, 2026

Copy link
Copy Markdown
@cloudflare/autoconfig

npm i https://pkg.pr.new/@cloudflare/autoconfig@15046

@cloudflare/build-output-utils

npm i https://pkg.pr.new/@cloudflare/build-output-utils@15046

@cloudflare/config

npm i https://pkg.pr.new/@cloudflare/config@15046

create-cloudflare

npm i https://pkg.pr.new/create-cloudflare@15046

@cloudflare/deploy-helpers

npm i https://pkg.pr.new/@cloudflare/deploy-helpers@15046

@cloudflare/kv-asset-handler

npm i https://pkg.pr.new/@cloudflare/kv-asset-handler@15046

miniflare

npm i https://pkg.pr.new/miniflare@15046

@cloudflare/pages-functions

npm i https://pkg.pr.new/@cloudflare/pages-functions@15046

@cloudflare/pages-shared

npm i https://pkg.pr.new/@cloudflare/pages-shared@15046

@cloudflare/unenv-preset

npm i https://pkg.pr.new/@cloudflare/unenv-preset@15046

@cloudflare/vite-plugin

npm i https://pkg.pr.new/@cloudflare/vite-plugin@15046

@cloudflare/vitest-pool-workers

npm i https://pkg.pr.new/@cloudflare/vitest-pool-workers@15046

@cloudflare/workers-auth

npm i https://pkg.pr.new/@cloudflare/workers-auth@15046

@cloudflare/workers-editor-shared

npm i https://pkg.pr.new/@cloudflare/workers-editor-shared@15046

@cloudflare/workers-utils

npm i https://pkg.pr.new/@cloudflare/workers-utils@15046

wrangler

npm i https://pkg.pr.new/wrangler@15046

commit: d2dc2e8

@erwinzhang7

Copy link
Copy Markdown
Author

Both taken, thank you.

Dropped the comment: the test name already says what it checks, and the history of why it broke belongs in the changeset rather than the test body.

Took the changelog title too, with the typo corrected: "Fixes D1 SQL statements not handling lowercase ends correctly".

Also filled in the description checkboxes, which is what the Check job was failing on rather than anything in the code.

The two Windows test failures look unrelated to this change. The suite passes on Linux and macOS, the failing jobs are fixtures and packages-and-tools rather than anything under d1, and this PR only touches a regex flag in the SQL splitter plus its test. Per REVIEW.md that pattern reads as a flake, so a rerun is probably all that is needed, but say the word if you would like me to dig into either job.

@erwinzhang7

Copy link
Copy Markdown
Author

The checks on this one are still pending workflow approval, could someone kick them off when you get a chance?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: Approved

Development

Successfully merging this pull request may close these issues.

4 participants