TML-3287: Raw SQL ending in a line comment renders valid DDL - #30546
wmadden-electric wants to merge 15 commits into
Conversation
Text containing a line comment renders with a trailing line break, so a comment on its last line cannot hide the rest of the statement. Signed-off-by: willbot <w.a.madden+machine@gmail.com> Signed-off-by: Will Madden <madden@prisma.io>
…ne comment A line break ends a -- comment, so moving it changes what the body means. normalizeSqlBody now keeps line breaks in bodies that contain --, and index, check and policy wire names change with them. Bodies without -- hash exactly as before. Signed-off-by: willbot <w.a.madden+machine@gmail.com> Signed-off-by: Will Madden <madden@prisma.io>
…ough renderOpaqueSql FunctionColumnDefault, CheckExpressionConstraint, PostgresCreatePolicy and PostgresCreateIndex now hold their SQL as OpaqueSql. The contract-free factories that migration files call keep taking strings. The Postgres and SQLite adapters, and the template-string sites that were never converted to DDL nodes (addCheckConstraint, buildColumnDefaultSql, alterColumnType USING), render the text through renderOpaqueSql. A body whose last line ends in a -- comment now produces valid DDL. CreateIndexElements moves next to DdlIndexElements in the DDL nodes module. Signed-off-by: willbot <w.a.madden+machine@gmail.com> Signed-off-by: Will Madden <madden@prisma.io>
…line comment ends with a line break Signed-off-by: willbot <w.a.madden+machine@gmail.com> Signed-off-by: Will Madden <madden@prisma.io>
…e instructions ADR 234 records the one normalizer change and why no committed wire name changes. The Migration System doc gains a section on OpaqueSql and renderOpaqueSql. The extension upgrade fragment covers the node field types and the one-time wire-name change for bodies with -- and a line break. Signed-off-by: willbot <w.a.madden+machine@gmail.com> Signed-off-by: Will Madden <madden@prisma.io>
…ql>")
The SQLite TypeScript renderer passed the whole OpaqueSql value to
jsonToTsSource, so a planned migration with a function default was
written as fn({ text: "..." }). It now reads .text, as the Postgres
renderer does.
Signed-off-by: willbot <w.a.madden+machine@gmail.com>
Signed-off-by: Will Madden <madden@prisma.io>
…ing in a line comment Signed-off-by: willbot <w.a.madden+machine@gmail.com> Signed-off-by: Will Madden <madden@prisma.io>
…uireDriver Replaces the repeated non-null assertions and says "opaque SQL" in the test names. Signed-off-by: willbot <w.a.madden+machine@gmail.com> Signed-off-by: Will Madden <madden@prisma.io>
…point DdlIndexElements now holds OpaqueSql, and createIndex takes CreateIndexElements. Exporting it gives code that typed its elements as DdlIndexElements a type to switch to. Signed-off-by: willbot <w.a.madden+machine@gmail.com> Signed-off-by: Will Madden <madden@prisma.io>
…ents normalizeSqlBody no longer says any change re-suffixes every wire name, and its summary states the line-comment rule. DdlIndexElements points at OpaqueSql for the opaque-SQL term. Signed-off-by: willbot <w.a.madden+machine@gmail.com> Signed-off-by: Will Madden <madden@prisma.io>
Migration System: say "opaque SQL" and cite ADR 244, call OpaqueSql a value, say the four template-string sites are future node work, and say why column defaults still refuse --. ADR 234: a normalizer change re-suffixes only the names whose normalized body changes; the line-comment rule is stated as part of the normalizer with an Amended note. Signed-off-by: willbot <w.a.madden+machine@gmail.com> Signed-off-by: Will Madden <madden@prisma.io>
The wire-name change moves to an app fragment, with a detection over contract.json, and says a policy's stored body changes too. The extension fragment lists DdlIndexElements and CreateIndexElements, matches namespace-qualified constructors, and says which stale reads the compiler does not report. Signed-off-by: willbot <w.a.madden+machine@gmail.com> Signed-off-by: Will Madden <madden@prisma.io>
…plan drops and recreates the object The app fragment no longer says a policy's stored body changes: authoring stores the body as written and normalizes only the hash input. The suffix of the name changes, so the planner drops and recreates the object rather than renaming it. The detection pattern skips escaped quotes. ADR 234 says the normalizer keeps every line break in a body that contains a line comment. The Migration System doc gives both reasons defaults refuse a line comment. The extension fragment no longer points at factories the target does not export. Signed-off-by: willbot <w.a.madden+machine@gmail.com> Signed-off-by: Will Madden <madden@prisma.io>
|
Navigate logical layers of code changes, visualize relationships, and explore their blast radius. 🧰 Additional context used📚 Code guidelines (2)No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Repository: prisma/orm/.coderabbit.yml Review profile: CHILL Plan: Advanced Run ID: 📒 Files selected for processing (8)
Included review availability: This review used your included allowance. Your plan provides up to 10 included reviews per hour; 9 remain after this review. 📝 WalkthroughWalkthroughSQL bodies containing line comments retain line breaks during normalization, which can change content hashes and wire-name suffixes. DDL expressions, predicates, and defaults use opaque SQL values in affected relational, PostgreSQL, and SQLite paths. Rendering rules, migration guidance, upgrade instructions, and tests are updated. ChangesSQL line-comment handling
Priority: ➖ Normal Estimated code review effort: 3 (Moderate) | ~30 minutes Change: Bug fix Sequence Diagram(s)sequenceDiagram
participant PostgresControlAdapter
participant renderOpaqueSql
participant PostgreSQL
PostgresControlAdapter->>renderOpaqueSql: render opaque DDL expression
renderOpaqueSql-->>PostgresControlAdapter: SQL text with newline when text contains --
PostgresControlAdapter->>PostgreSQL: execute rendered DDL
Suggested reviewers: Merge Risk: ⚪ Minimal · up to Affected SQL preserves line-comment boundaries, and the one-time recreation for changed wire names is documented. No concrete merge-blocking risk remains. Security Architecture ReviewSecurity architecture risk: 🔵 Low · up to The inspected paths preserve existing SQL-execution controls and correct line-comment handling. Affected database objects may be recreated once, and extensions need compatibility updates. Interruption and downgrade behavior are not fully established. Retained concerns Security review detailsSecurity Blast Radius
Trust Boundaries and Controls
Resilience and Maintainability Implications
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches 💡 1📝 Generate docstrings 💡
🧪 Generate unit tests (beta)
Comment |
@prisma/orm-extension-arktype-json
@prisma/orm-extension-middleware-cache
@prisma/orm-extension-paradedb
@prisma/orm-extension-pgvector
@prisma/orm-extension-postgis
@prisma/orm-extension-supabase
@prisma/orm-family-mongo
@prisma/orm-family-sql
@prisma/orm-framework
@prisma/orm-mongo
@prisma/orm-postgres
@prisma/orm-sqlite
@prisma/orm-target-mongo
@prisma/orm-target-postgres
@prisma/orm-target-sqlite
@prisma/orm-toolchain
commit: |
size-limit report 📦
|
…s-in-raw-sql Signed-off-by: willbot <w.a.madden+machine@gmail.com> Signed-off-by: Will Madden <madden@prisma.io> # Conflicts: # packages/3-targets/3-targets/postgres/src/core/migrations/op-factory-call.ts # packages/3-targets/3-targets/postgres/src/core/migrations/planner-ddl-builders.ts # packages/3-targets/3-targets/postgres/test/migrations/column-ddl-rendering.test.ts # packages/3-targets/3-targets/sqlite/src/core/migrations/column-ddl-rendering.ts # packages/3-targets/3-targets/sqlite/src/core/migrations/planner-ddl-builders.ts # packages/3-targets/6-adapters/postgres/src/core/control-adapter.ts # packages/3-targets/6-adapters/sqlite/src/core/control-adapter.ts # packages/3-targets/6-adapters/sqlite/test/structured-errors.test.ts
…s-in-raw-sql setDefault's autoincrement refusal, new on main, reads the text of the opaque default expression. Signed-off-by: willbot <w.a.madden+machine@gmail.com> Signed-off-by: Will Madden <madden@prisma.io>
Linked issue
Refs TML-3287, a slice of the Linear project SQL expression literals. Depends on no other slice.
Summary
A raw SQL body in a schema can end in a line comment. The planner pastes that body into a larger statement, so everything after it on the same line, such as the closing parenthesis of
CHECK (...), was swallowed by the comment and the migration failed. Now every place the planner puts contract SQL inside a statement holds it as anOpaqueSqlnode and renders it through one function, which ends text containing--with a line break. Wire names, which hash the SQL, keep line breaks in bodies that contain--, so moving a line break around a comment changes the name. Nothing changes for a body without--.Skill update
n/a. Schemas do not change. Two upgrade fragments under
upgrade-instructions/pending/line-comments-in-raw-sql/: the app fragment says a body with both a--comment and a line break gets a new wire name once; the extension fragment records the changed node constructors and types.At a glance
Before this PR, this check constraint produced invalid DDL:
After this PR the same schema migrates:
The rule is one function:
Decision
This PR ships three things.
OpaqueSqlis the DDL node for SQL text Prisma does not parse. A CHECK or policy predicate, an index element list or predicate, a column default expression, or an ALTER COLUMN TYPE conversion. ADR 244 already calls such text opaque; the node gives it a type. It lives inpackages/2-sql/4-lanes/relational-core/src/ast/opaque-sql.tswithrenderOpaqueSql.OpaqueSql, and every site that renders one goes throughrenderOpaqueSql.FunctionColumnDefault,CheckExpressionConstraint,PostgresCreatePolicyandPostgresCreateIndexhold the node; both adapters' renderers,addCheckConstraint, both targets'buildColumnDefaultSqland thealterColumnTypeUSING clause render through the function. The contract-free factories that migration files call keep taking strings and wrap them, so committed migration files load unchanged.--.normalizeSqlBodycollapses whitespace to one space as before, except in a body that contains--, where it keeps one line break per line. A line break ends a comment, so it is part of the SQL's meaning there. ADR 234 records the rule.How it fits together
OpaqueSqlcannot be interpolated into a template by accident; it has to go throughrenderOpaqueSql. The four template-string sites wrap their string withopaqueSql(...)before rendering, so they follow the same rule.--comment runs to the end of its line. Ending the text with a line break means whatever the statement writes next starts on a fresh line. For text without--the function returns the text unchanged, so every existing statement is byte-identical.a --c\nbanda --c bthe same name although they are different SQL. The normalizer keeps line breaks only when--is present, so every existing name is unchanged and a body with a comment and a line break gets a name that reflects its meaning.Reviewer notes
ops.json,migration.json, contract file or wire name contains--, andfixtures:checkshows a clean tree.buildColumnDefaultSqlsites never see a comment, becauseassertSafeDefaultExpressionalready refuses--in defaults. They render throughrenderOpaqueSqlonly so the rule holds at every site.CreateIndexElementsmoved fromoperations/indexes.tstoddl/nodes.ts, next toDdlIndexElements, and is exported from the Postgres target'sddlentry.--, at authoring and inassertSafeDefaultExpression, becausecontract infercompares a default as text. Their two render sites go throughrenderOpaqueSqlonly so every site follows one rule; the Migration System doc says so.fn("<sql>")on both targets; the SQLite renderer had passed the node object to the JSON printer, which the first review round caught and a whole-file render test now pins.opaqueSql(text)and read.text; the fragment has the table.pnpm installin their scratch project refuses@vercel/detect-agent@1.2.5). CI is the check for them.Behavior changes & evidence
packages/3-targets/6-adapters/postgres/src/core/control-adapter.ts,packages/3-targets/3-targets/postgres/src/core/migrations/operations/constraints.ts,columns.ts. Evidence:packages/3-targets/6-adapters/postgres/test/migrations/opaque-sql-line-comment.integration.test.tson PGlite: CREATE TABLE with a CHECK and a function default,addCheckConstraint, a policy with USING and WITH CHECK, an index whose element list ends in a comment, a partial index, and an ALTER COLUMN TYPE USING clause; each failed with a syntax error before the change.packages/3-targets/6-adapters/sqlite/src/core/control-adapter.ts. Evidence:packages/3-targets/6-adapters/sqlite/test/lower-to-execute-request.test.tsasserts the wholeCREATE TABLEfor a default ending in a line comment.packages/3-targets/3-targets/sqlite/src/core/migrations/op-factory-call.ts. Evidence:packages/3-targets/3-targets/sqlite/test/migrations/render-typescript.test.tsrenders a wholemigration.tsand assertsfn("datetime('now')").renderOpaqueSqland the node.packages/2-sql/4-lanes/relational-core/src/ast/opaque-sql.ts. Evidence:relational-core/test/ast/opaque-sql.test.ts(text without--unchanged;--anywhere, including inside a string constant, appends a line break; the node is frozen).--.packages/2-sql/1-core/schema-ir/src/naming.ts. Evidence:schema-ir/test/naming.test.tsandtarget-postgres/test/rls-canonicalize.test.ts(two bodies that differ only by a line break after a comment get different names; bodies without--and one-line bodies are unchanged; CRLF and lone CR end a line; the function is stable on its own output; the pinned hash table is unchanged).docs/architecture docs/subsystems/7. Migration System.md, new section "Opaque SQL in DDL".Compatibility / migration / risk
--is byte-identical.--comment and a line break gets a new index, check or policy name once; the stored body does not change. The nextmigration plandrops and recreates the object. The fragment says so.OpaqueSql; code reading.expression,.using,.withCheckor.wherereads.text, and a read inside a template string or a call that accepts any value is not caught by the compiler, so search for them.DdlIndexElementschanged shape andCreateIndexElementsis exported. The contract-free factories (fn,checkExpression,createPolicy,createIndex) still take strings.Testing performed
On the final HEAD:
pnpm build,pnpm typecheck,pnpm lint,pnpm lint:deps,pnpm lint:casts(delta 0),pnpm lint:throws(delta 0),pnpm check:error-reference,pnpm lint:framework-vocabulary,pnpm fixtures:check(tree clean),pnpm check:upgrade-coverage --mode pragainst the merge basepnpm test:packages: all files pass except the 3 tarball files (registry refusal above); one file that timed out under load passes alonetest:integrationsuite runs in CI.projects/sql-expression-literals/slice-reviews/1/on the project branch; every finding is fixedFollow-ups
--comment to its CLI journey once this merges.Alternatives considered
--inside a string constant, and the author's text should reach the database as written.--is present changes no committed name; a user body with a comment and a line break gets a new name once.OpaqueSql. Rejected: migration files already committed call them with strings and must keep loading.Checklist
git commit -s) per the DCO.TML-NNNN: <sentence-case title>form.Notes for the reviewer
See Reviewer notes above.
Agent: charon-96
Summary by CodeRabbit
--and a line break may receive new names when contracts are re-emitted. A migration plan drops and recreates each affected object once.