mirror of
https://github.com/BerriAI/litellm.git
synced 2026-08-28 05:25:59 +00:00
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:
parent
3d16e327c6
commit
aabaa5151b
2 changed files with 78 additions and 4 deletions
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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):
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue