Repository navigation
TML-3288: Index, check and policy SQL is written as a sql literal - #30550
Merged
Merged
Conversation
Spec, design, plan, decisions, research and the status file for resuming the work. Raw SQL in a schema is a value of the data type sql/expression. Signed-off-by: willbot <w.a.madden+machine@gmail.com> Signed-off-by: Will Madden <madden@prisma.io>
Signed-off-by: willbot <w.a.madden+machine@gmail.com> Signed-off-by: Will Madden <madden@prisma.io>
…d literal printer Signed-off-by: willbot <w.a.madden+machine@gmail.com> Signed-off-by: Will Madden <madden@prisma.io>
Signed-off-by: willbot <w.a.madden+machine@gmail.com> Signed-off-by: Will Madden <madden@prisma.io>
…emoved Signed-off-by: willbot <w.a.madden+machine@gmail.com> Signed-off-by: Will Madden <madden@prisma.io>
…odes Signed-off-by: willbot <w.a.madden+machine@gmail.com> Signed-off-by: Will Madden <madden@prisma.io>
Signed-off-by: willbot <w.a.madden+machine@gmail.com> Signed-off-by: Will Madden <madden@prisma.io>
Signed-off-by: willbot <w.a.madden+machine@gmail.com> Signed-off-by: Will Madden <madden@prisma.io>
Signed-off-by: willbot <w.a.madden+machine@gmail.com> Signed-off-by: Will Madden <madden@prisma.io>
…type slice Signed-off-by: willbot <w.a.madden+machine@gmail.com> Signed-off-by: Will Madden <madden@prisma.io>
…on data type slice Signed-off-by: willbot <w.a.madden+machine@gmail.com> Signed-off-by: Will Madden <madden@prisma.io>
Signed-off-by: willbot <w.a.madden+machine@gmail.com> Signed-off-by: Will Madden <madden@prisma.io>
Signed-off-by: willbot <w.a.madden+machine@gmail.com> Signed-off-by: Will Madden <madden@prisma.io>
Signed-off-by: willbot <w.a.madden+machine@gmail.com> Signed-off-by: Will Madden <madden@prisma.io>
Signed-off-by: willbot <w.a.madden+machine@gmail.com> Signed-off-by: Will Madden <madden@prisma.io>
…stack Signed-off-by: willbot <w.a.madden+machine@gmail.com> Signed-off-by: Will Madden <madden@prisma.io>
The cast rule already keeps the sql/expression entry out of literal defaults. Also corrects the wrapNamespaceBlock line reference in the design (F09). Signed-off-by: willbot <w.a.madden+machine@gmail.com> Signed-off-by: Will Madden <madden@prisma.io>
The code now reports only a single value written on a list column, which is a refusal about shape, not about type. Signed-off-by: willbot <w.a.madden+machine@gmail.com> Signed-off-by: Will Madden <madden@prisma.io>
A cast and a list cast that throw are PSL_INVALID_LITERAL. Every refusal row now asserts its whole message. Signed-off-by: willbot <w.a.madden+machine@gmail.com> Signed-off-by: Will Madden <madden@prisma.io>
It reads the canonical value through sqlTextFromCanonical, as the six places in slice 2b will. The reserved-name message names the tag through SQL_EXPRESSION_TAG. Signed-off-by: willbot <w.a.madden+machine@gmail.com> Signed-off-by: Will Madden <madden@prisma.io>
…s user lowerTaggedLiteral lowered nothing any more, and its refusal had the type of a default-function result. The sql/expression code moves out of data-type-default.ts, which holds no per-type code. Signed-off-by: willbot <w.a.madden+machine@gmail.com> Signed-off-by: Will Madden <madden@prisma.io>
The sqlExpressionDataType doc comment points at ADR 254. sqlTextReadsBack moves to slice 2b, next to its caller. The authoring entry exports only printTaggedLiteral from tagged-literal; each other function is added in the slice that first imports it from there. Signed-off-by: willbot <w.a.madden+machine@gmail.com> Signed-off-by: Will Madden <madden@prisma.io>
The code comment, the entry documentation the editor shows, and ADR 254 said "SQL text", which would admit a statement or a query. The extensions doc no longer teaches a view query as a sql literal or describes the removed prefixed tags. Signed-off-by: willbot <w.a.madden+machine@gmail.com> Signed-off-by: Will Madden <madden@prisma.io>
…nd text ADR 129 is retitled "Tagged literals write values of data types" and no longer says a tag names the pack that owns the text. ADR 254 states the prefix rule once, by the owner of the data type, and ADR 129 and the index link to it. The body is what is written between the quotes; the text is the canonical value. ADR 254 now says @default reports its cast-rule codes at the attribute. The error reference drops "a list holding another list", which PSL cannot write. Signed-off-by: willbot <w.a.madden+machine@gmail.com> Signed-off-by: Will Madden <madden@prisma.io>
They used pg.sql and sqlite.sql, which no longer exist, as the example of a valid dotted tag. Signed-off-by: willbot <w.a.madden+machine@gmail.com> Signed-off-by: Will Madden <madden@prisma.io>
Signed-off-by: willbot <w.a.madden+machine@gmail.com> Signed-off-by: Will Madden <madden@prisma.io>
assertNothingCastsFromSqlExpression throws CONTRACT.DATA_TYPE_CASTS_FROM_SQL_EXPRESSION for a cast or a list cast from sql/expression. The family instance runs it on every data type the stack registers, so an extension cannot turn a sql literal into a value of another type. runtimeError is exported from the shared codec entry so shared-plane code can raise the same kind of error as stack assembly. Signed-off-by: willbot <w.a.madden+machine@gmail.com> Signed-off-by: Will Madden <madden@prisma.io>
Signed-off-by: willbot <w.a.madden+machine@gmail.com> Signed-off-by: Will Madden <madden@prisma.io>
For Postgres with every shipped extension pack, and for SQLite: the stack registers the family objects themselves, and no registered data type casts from sql/expression. Signed-off-by: willbot <w.a.madden+machine@gmail.com> Signed-off-by: Will Madden <madden@prisma.io>
Deleting the adapter authoring tests removed the only tests of the Postgres and SQLite number classifiers, the boolean reader, and JSON and numeric read and print. The cases move to the targets, which own the entries; only the cases about lowering keys are dropped. Signed-off-by: willbot <w.a.madden+machine@gmail.com> Signed-off-by: Will Madden <madden@prisma.io>
…od test so it fails without the fix Signed-off-by: willbot <w.a.madden+machine@gmail.com> Signed-off-by: Will Madden <madden@prisma.io>
… as canonicalization now does Signed-off-by: willbot <w.a.madden+machine@gmail.com> Signed-off-by: Will Madden <madden@prisma.io>
…ng and infer skips only exact-named objects Signed-off-by: willbot <w.a.madden+machine@gmail.com> Signed-off-by: Will Madden <madden@prisma.io>
… has it and owns its change id Signed-off-by: willbot <w.a.madden+machine@gmail.com> Signed-off-by: Will Madden <madden@prisma.io>
…k lines at both ends, and a sql literal has two escapes Signed-off-by: willbot <w.a.madden+machine@gmail.com> Signed-off-by: Will Madden <madden@prisma.io>
…f data types Signed-off-by: willbot <w.a.madden+machine@gmail.com> Signed-off-by: Will Madden <madden@prisma.io>
…fusal messages Signed-off-by: willbot <w.a.madden+machine@gmail.com> Signed-off-by: Will Madden <madden@prisma.io>
…journey in a -- line comment Signed-off-by: willbot <w.a.madden+machine@gmail.com> Signed-off-by: Will Madden <madden@prisma.io>
…est that drives the language server in a Postgres project Signed-off-by: willbot <w.a.madden+machine@gmail.com> Signed-off-by: Will Madden <madden@prisma.io>
Signed-off-by: willbot <w.a.madden+machine@gmail.com> Signed-off-by: Will Madden <madden@prisma.io>
Signed-off-by: willbot <w.a.madden+machine@gmail.com> Signed-off-by: Will Madden <madden@prisma.io>
wmadden
approved these changes
Oct 8, 2026
github-merge-queue
Bot
removed this pull request from the merge queue due to failed status checks
Oct 8, 2026
Brings TML-3466, TML-3382, TML-3475, TML-3476, TML-3512 and the one-config-file project. No conflicts and no released fragment touched; the semantic clashes are fixed in the commits that follow. Signed-off-by: willbot <w.a.madden+machine@gmail.com> Signed-off-by: Will Madden <madden@prisma.io>
…pect a policy drop to be widening Signed-off-by: willbot <w.a.madden+machine@gmail.com> Signed-off-by: Will Madden <madden@prisma.io>
…ql literals Signed-off-by: willbot <w.a.madden+machine@gmail.com> Signed-off-by: Will Madden <madden@prisma.io>
…o data Signed-off-by: willbot <w.a.madden+machine@gmail.com> Signed-off-by: Will Madden <madden@prisma.io>
wmadden-electric
enabled auto-merge
October 8, 2026 07:42
github-merge-queue
Bot
removed this pull request from the merge queue due to failed status checks
Oct 8, 2026
Main's TML-3431 stores a Postgres full-text index as data and lets it span several weighted fields. The conflicts keep main's structure and this branch's rule that raw SQL is a sql literal of the data type sql/expression. - postgres/src/core/authoring.ts: main's multi-field @@fullTextIndex spec and lowering. The spec is a function of the spec context again, so where: receives dataTypeValue(SQL_EXPRESSION_DATA_TYPE_ID, ctx.dataTypes), and lowering stores where through sqlTextFromCanonical. - postgres/src/core/psl-print/model-attributes.ts: main's shared naming and full-text branch. The read-back refusal for an index's SQL texts now runs before the full-text branch, so it also covers a full-text index's where. - postgres/test/psl-full-text-index.test.ts: main's tests, with where written as a sql literal; this branch's refusal tests for a plain-string, number or boolean where are kept. - postgres/test/migrations/full-text-index-planning.test.ts: both sides' imports, since both are used. - postgres/README.md and skills/prisma-8/references/contract.md: main's prose and weighted example, plus this branch's partial-index example with a sql literal; the README keeps the paragraph that every raw-SQL argument takes a sql literal, and its expression-index example is a sql literal. Signed-off-by: willbot <w.a.madden+machine@gmail.com> Signed-off-by: Will Madden <madden@prisma.io>
… check Signed-off-by: willbot <w.a.madden+machine@gmail.com> Signed-off-by: Will Madden <madden@prisma.io>
…:) as a name, not SQL Signed-off-by: willbot <w.a.madden+machine@gmail.com> Signed-off-by: Will Madden <madden@prisma.io>
wmadden-electric
enabled auto-merge
October 8, 2026 16:22
Signed-off-by: willbot <w.a.madden+machine@gmail.com> Signed-off-by: Will Madden <madden@prisma.io>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Linked issue
Refs TML-3288, the third slice of the Linear project SQL expression literals. Records the decision as ADR 268.
Summary
Raw SQL in a Prisma 8 schema is now written one way everywhere: as a
sqlliteral. Index predicates and expressions, full-text index predicates, check expressions and policy predicates take the same literal@defaultalready took, and refuse a quoted string with a message that gives the rewrite.At a glance
Before this PR, raw SQL outside
@defaultwas a quoted string full of\":After it, every place takes the same
sqlliteral as@default:A quoted string in any of these places is refused, and the message gives the literal to write:
A codemod in the upgrade instructions rewrites an existing schema:
node scripts/rewrite-sql-strings.mjs '**/*.prisma'Decision
Raw SQL is a value of one data type,
sql/expression, and every place that holds raw SQL says it receives that type. The SQL family registers the type with the tagsqland declares that nothing converts into it. So the general rule that admits a written value, the one that refuses@default("abc")on anIntcolumn, admits only asqlliteral in these places and refuses a quoted string, a number or a boolean. There is no separate "strings not allowed" check, and Prisma never parses the SQL inside the literal.The places are
@@index(where:),@@index(expression:),@@fullTextIndex(where:),@@check(expression:), and a policy'susingandwithCheck.@defaultalready tooksqlliterals and keeps its own checks on the text (no;, noSELECT, and so on); the other places check nothing, because a row-level-security predicate often holdsEXISTS (SELECT ...).ADR 268 records the decision and its rejected alternatives. ADRs 129, 231, 234, 236, 243, 244, 249, 254 and 262 are amended to match.
How it fits together
sqlliteral is a value of a data type. TML-3296: sql is the tag of the SQL family's data type sql/expression #30534 madesql/expressiona data type the SQL family owns. TML-3367: Attribute arguments declare the data type they receive #30539 (shipped in rc.16) builtdataTypeValue, an argument type that admits a written value by the conversion rule and reports a refusal at the value.indexModelSpec,checkModelSpec,postgresFullTextIndexSpecand the three policy specs now builddataTypeValue(SQL_EXPRESSION_DATA_TYPE_ID, ctx.dataTypes)from their spec context. For the policy predicates this needs the block spec context to carry the stack's data types:BlockSpecContextis now{ symbols, dataTypes }, filled on every path that binds a block (the SQL and Mongo interpreters, the binder, the language server).sql/expressionconverts from nothing; the general refusal fires. Because the receiving type has a tag, the message adds the exact literal to write, when that literal would read back as the same text.Index.where,CheckConstraint.expressionand the policy predicates stay strings. Every fixture in the repository emits an identicalcontract.json.migration planthen does is in "Compatibility" below.contract inferandcontract printwrite these places assqlliterals.printSqlExpressionLiteralthrows for text that would read back changed, so no printer can produce it by accident.contract inferprints a Prisma-named object with its canonical text (same name) and skips an object named withmap:or@@mapwhose text would not read back, with a note saying how to recover;contract printrefuses it.sqlat every argument that receivessql/expression, including a policy'susing, and colours the literal;@@check(completes tocheck(expression: sql`${1:expression}`). The Supabase pack contract,examples/supabase, every.prismafixture, the docs and theprisma-8skill references move to the new form in this PR.Compatibility, migration and risk
upgrade-instructions/pending/sql-expression-literals-psl/app/, or rewrite by hand as each message says. The fragment describes the change from rc.16.migration planrecords the new text and has no operations. An index or check named withmap:makesmigration planstop with a conflict that asks for a migration written withmigration new, which has no operations. A policy named with@@mapis dropped and created again, becausemigration planallows destructive operations. A planner test pins each case.BlockSpecContext.dataTypesis required, andinterpretExtensionBlocks,createBinder,createSqlBinderandcreateMongoBindertakedataTypes.ControlDefaultRegistriesis deleted; a spec readsctx.defaultFunctionRegistryandctx.dataTypes. The SQL attribute spec factories all take the context.canonicalizeTaggedLiteralBodyis exported from@internal/framework-components/authoringonly. The extension fragment has the details.contract printrefuses an exact-named index, check or policy whose SQL would not read back, withCONTRACT.PRINT_UNSUPPORTED.Reviewer notes
contract-psl/src/sql-attribute-specs.tsandinterpreter.ts,target-postgres/src/core/authoring.ts(full-text index and policies), the infer and print code underpsl-build/,psl-infer/andpsl-print/, and the rewritten.prismafixtures..claude/scripts/enforce-tools.mjsblocks agent shells from running the full integration and end-to-end suites locally, as Will asked. It came in with commits0e83ea5e3fandff0a677271. The second also fixes the codemod (an unclosed backtick no longer stops it skipping later//comments) and edits ADR 129, ADR 268 and the Supabase skill reference, although its title names only the hook.--comment renders valid DDL since TML-3287: Raw SQL ending in a line comment renders valid DDL #30546; the new CLI journey now includes such a body.pnpm installin their scratch project refuses@vercel/detect-agentas a trust downgrade). CI is the check for them.projects/sql-expression-literals/holds the spec, design, plan, review reports and status. It is deleted at project close-out.Behavior changes and evidence
sqlliterals and refuse other literals. Evidence:contract-psl/test/interpreter.sql-expression-places.test.ts,target-postgres/test/psl-full-text-index.test.ts,psl-policy-predicates.test.ts, and the guardsql-expression-places.test.ts, which fails if a raw-SQL argument is written withstr().psl-parser/test/block-spec-context.test.ts,language-server/test/block-spec-context.test.ts, andcompletion-provider.test.ts, whereusing = |in apolicy_selectblock offerssqlon the real Postgres stack.target-postgres/test/sql-expression-wire-names.test.ts.sqlliterals and never one that reads back changed. Evidence:psl-infer/infer-sql-expression-literals.test.ts,psl-print/refusals-sql-text.test.ts,test/migrations/sql-text-canonical-planner.test.ts, and the CLI journeyssql-expression-literals.e2e.test.ts,infer-roundtrip-fidelity*andsign-the-database.scripts/codemods/rewrite-sql-strings.test.mjs(each position, both quote kinds, escapes, comments, literals inside strings, the byte-identical fragment copies).Testing performed
On the final head:
pnpm build,pnpm typecheck,pnpm lint,pnpm lint:deps,pnpm lint:casts(no increase),pnpm lint:throws(no increase),pnpm lint:skills,pnpm lint:framework-vocabulary,pnpm lint:rules:footprint,pnpm check:error-reference,pnpm fixtures:check(nocontract.jsonchange),pnpm test:scripts, andpnpm check:upgrade-coverage --mode pragainst the merge base.sql-expression-literalsCLI journey (now with texts ending in a--comment),lsp-sql-completion-in-blocks,test/authoring/**,test/psl-print/**,infer-roundtrip-fidelity*,sign-the-database, and the expression-index and RLS journeys. The full integration and end-to-end suites run in CI and the merge queue.projects/sql-expression-literals/manual-qa.mdreads each new message as a user would; its expected column now quotes the current messages.projects/sql-expression-literals/slice-reviews/2b/,2b-round-2/and2b-round-3/.Alternatives considered
sqlliterals. Rejected: two ways to write one value, and the escaped quotes stay.@default's checks on SQL text to every place. Rejected: they refuse valid row-level-security predicates such asEXISTS (SELECT ...).pg.sql. Rejected in TML-3296: sql is the tag of the SQL family's data type sql/expression #30534: the stack already names the target.Skill update
The
prisma-8skill referencescontract.md,queries-postgres.mdandsupabase.mdwrite raw SQL assqlliterals. The breaking change for schema authors, with the codemod, is inupgrade-instructions/pending/sql-expression-literals-psl/.Checklist
git commit -s) per the DCO.TML-NNNN: <sentence-case title>form.Agent: hammurabi-31 (taking over from charon-96 and marconi-29)
🤖 Generated with Claude Code