docs: say where a marker goes for a dollar-quoted dynamic payload

A dollar-quoted payload is read as its own region rather than as a handed-off
string, so the marker belongs on the rewrite inside it. Pin that placement, and
pin that a marker on a DO block header never covers the block's body.
This commit is contained in:
mateo-berri 2026-08-21 18:43:04 -07:00
parent 3d16e327c6
commit aabaa5151b
2 changed files with 78 additions and 4 deletions

View file

@ -53,8 +53,12 @@ the migration, put `-- data-migration-ok: <reason>` on the statement or on the l
above it, naming what bounds it. The reason is required. A marker sharing a line
with the statement it follows exempts that statement alone, so the next statement
down is still checked rather than picking the marker up as its own. A marker on an
`EXECUTE` or on the assignment feeding one covers the SQL that statement hands off,
so it goes where the migration reads rather than inside the string.
`EXECUTE` or on the assignment feeding one covers the single-quoted SQL that
statement hands off, so it goes where the migration reads rather than inside the
string. A dollar-quoted payload is not a string to this check but a region read like
any other body, so a rewrite inside one takes its marker on the rewrite itself. That
placement is deliberate rather than an oversight: a marker covering a whole body
would let one written for a `DO` block silence a rewrite added to that block later.
`GRANDFATHERED` freezes the violations that predate this check. Prisma records a
checksum for every applied migration and this repo treats applied files as
@ -86,7 +90,6 @@ FIRST_WORD = re.compile(r"[A-Za-z_][A-Za-z0-9_]*")
STATEMENT = re.compile(r"[^;]+")
RUN_BY_NAME = re.compile(r"\bEXECUTE[ \t]+([A-Za-z_][A-Za-z0-9_]*)", re.IGNORECASE)
EXPLAIN_OPTIONS = re.compile(r"\bEXPLAIN\b(?:\s+(?:ANALYZE|ANALYSE|VERBOSE)\b)+", re.IGNORECASE)
NOT_A_NEWLINE = re.compile(r"[^\n]")
REWRITES_ROWS = frozenset({"UPDATE", "DELETE", "MERGE"})
@ -261,7 +264,7 @@ def strip_explain(statement: str) -> str:
the one worth reading: `EXPLAIN ANALYZE` runs it rather than only planning it, so a
rewrite underneath rewrites the table for real. The parenthesised option list needs
nothing here, already being blanked as a group."""
return EXPLAIN_OPTIONS.sub(lambda match: NOT_A_NEWLINE.sub(" ", match.group()), statement)
return EXPLAIN_OPTIONS.sub(lambda match: blank(match.group()), statement)
def leading_keyword(statement: str) -> re.Match[str] | None:

View file

@ -335,6 +335,42 @@ class TestEscapeHatch:
assert _keywords(tmp_path, sql) == ("UPDATE",)
assert _scan(tmp_path, sql)[0].line == 4
def test_marker_directly_above_a_do_block_does_not_exempt_its_body(self, tmp_path):
sql = (
"-- data-migration-ok: seeding two default rows\n"
"DO $$\n"
"BEGIN\n"
' INSERT INTO "Foo" ("a") VALUES (1);\n'
' UPDATE "Foo" SET "a" = 1;\n'
"END $$;"
)
assert _keywords(tmp_path, sql) == ("UPDATE",)
assert _scan(tmp_path, sql)[0].line == 5
def test_marker_on_the_do_line_does_not_exempt_its_body(self, tmp_path):
sql = (
"DO $$ -- data-migration-ok: bounded to one row\n"
"BEGIN\n"
' IF EXISTS (SELECT 1 FROM "Foo") THEN\n'
' UPDATE "Foo" SET "a" = 1;\n'
" END IF;\n"
"END $$;"
)
assert _keywords(tmp_path, sql) == ("UPDATE",)
assert _scan(tmp_path, sql)[0].line == 4
def test_a_marked_rewrite_does_not_exempt_a_later_one_in_the_same_block(self, tmp_path):
sql = (
"DO $$\n"
"BEGIN\n"
" -- data-migration-ok: bounded to one row\n"
' UPDATE "Foo" SET "a" = 1;\n'
' UPDATE "Bar" SET "b" = 2;\n'
"END $$;"
)
assert _keywords(tmp_path, sql) == ("UPDATE",)
assert _scan(tmp_path, sql)[0].line == 5
class TestDynamicSql:
def test_execute_of_a_quoted_update_is_flagged(self, tmp_path):
@ -515,6 +551,41 @@ class TestDynamicSql:
assert _keywords(tmp_path, sql) == ("DELETE",)
assert _scan(tmp_path, sql)[0].line == 4
def test_a_marker_on_an_execute_covers_its_single_quoted_payload(self, tmp_path):
sql = (
"DO $$\n"
"BEGIN\n"
" EXECUTE ' -- data-migration-ok: bounded to one row\n"
' UPDATE "Foo" SET "a" = 1;\n'
" ';\n"
"END $$;"
)
assert _keywords(tmp_path, sql) == ()
def test_a_marker_on_an_execute_does_not_reach_into_a_dollar_quoted_payload(self, tmp_path):
sql = (
"DO $$\n"
"BEGIN\n"
" EXECUTE $x$ -- data-migration-ok: bounded to one row\n"
' UPDATE "Foo" SET "a" = 1;\n'
" $x$;\n"
"END $$;"
)
assert _keywords(tmp_path, sql) == ("UPDATE",)
assert _scan(tmp_path, sql)[0].line == 4
def test_a_marker_inside_a_dollar_quoted_payload_exempts_its_rewrite(self, tmp_path):
sql = (
"DO $$\n"
"BEGIN\n"
" EXECUTE $x$\n"
" -- data-migration-ok: bounded to one row\n"
' UPDATE "Foo" SET "a" = 1;\n'
" $x$;\n"
"END $$;"
)
assert _keywords(tmp_path, sql) == ()
class TestExplain:
def test_explain_analyze_over_an_update_is_flagged(self, tmp_path):