TML-2986: postgres native types as bare PSL scalar types; Json→pg/json, new Jsonb#975
TML-2986: postgres native types as bare PSL scalar types; Json→pg/json, new Jsonb#975SevInf wants to merge 26 commits into
Conversation
Signed-off-by: Serhii Tatarintsev <tatarintsev@prisma.io>
…o-arg instantiability A top-level type constructor is now bare-eligible when it is instantiable with an empty argument list (all args optional, no entityRefArg, output template resolvable with no args), and the projection entry is exactly the zero-arg instantiation output — one resolution path for T and T(). The named-type bare-base path now threads the base descriptor typeParams through instead of dropping them, so bare T and T() emit identical storage in both syntactic positions. Signed-off-by: Serhii Tatarintsev <tatarintsev@prisma.io>
…namedTypes symbol kind
The ScalarSymbol/TypeAliasSymbol split was a pre-classification the
interpreter re-derives authoritatively; no consumer needed it from the
symbol table. types {} bindings now collect into a single namedTypes
record (kind: namedType) and the parser layer stays family-blind.
The LSP, the only surface that showed the distinction, re-derives it at
query time from the binding baseType/isConstructor and the control
stack scalarTypes it already holds (named-type-classification.ts); its
semantic-token and completion tests pass unweakened. The SQL
interpreter resolves scalar-refinement bindings ahead of alias and
constructor bindings, keeping emitted artifacts byte-identical to the
order the retired split induced.
Signed-off-by: Serhii Tatarintsev <tatarintsev@prisma.io>
…e constructors (postgres) The postgres adapter now contributes VarChar, Char, Numeric, Timestamp, Timestamptz, Time, Timetz, Uuid, SmallInt, Real, and Date as top-level type-constructor descriptors (postgresNativeAuthoringTypes, merged with the base scalars into postgresAuthoringTypes on the adapter descriptor). Parameterized constructors declare optional integer args whose typeParams keys materialize only when the argument is given, mirroring the legacy @db.* attribute path (NATIVE_TYPE_SPECS) exactly; Date pins the explicit { codecId: pg/timestamptz@1, nativeType: date } pair. Parity is proven live-vs-live: native-type-parity.test.ts emits each form through both the bare-type path and the @db.* path in the same run and deep-equals the storage shapes, for all eleven types, in both named-type and field position, arg-present and arg-omitted (incl. the Numeric(10) one-arg form), plus declarative-machinery rejection of out-of-range/non-integer/excess arguments. Note: per the slice spec table, Numeric precision has minimum 0 while the legacy @db.Numeric path rejected precision < 1 — flagged in the dispatch report. Signed-off-by: Serhii Tatarintsev <tatarintsev@prisma.io>
… (>= 1)
The spec table's "precision >= 0" was a transcription error; the parity
oracle is the law: the legacy @db.Numeric path rejects precision < 1
("positive integer precision"). Raise the declarative minimum to 1 and
prove Numeric(0) is rejected in both named-type and field position.
Signed-off-by: Serhii Tatarintsev <tatarintsev@prisma.io>
…ng pg/jsonb@1 The Json scalar contribution on the postgres adapter now means native json; the new bare Jsonb scalar carries the former pg/jsonb@1 binding. PSL value-object storage columns keep jsonb by preferring the target Jsonb scalar with a Json fallback (sqlite unchanged). The legacy @db.Json path (NATIVE_TYPE_SPECS) is untouched and byte-stable; sqlite/mongo Json bindings and the TS builder surface (field.json(), jsonbColumn) are unchanged. In-repo postgres schemas meaning jsonb migrate to Jsonb; upgrade-instruction entries recorded in both skill clusters (postgres-json-rebound-to-native-json). Signed-off-by: Serhii Tatarintsev <tatarintsev@prisma.io>
… trace ledger Signed-off-by: Serhii Tatarintsev <tatarintsev@prisma.io>
…-as-scalars Signed-off-by: Serhii Tatarintsev <tatarintsev@prisma.io>
…peDescriptors channel Main brought the lsp-interpret slice still referencing the deleted scalarTypeDescriptors context field. The interpretation context flows verbatim into provider interpret(), which now derives scalar types from authoringContributions.type itself, so the field is simply dropped: - config-resolution.ts: remove scalarTypeDescriptors from the assembled ContractSourceContext (unified channel rides along as authoringContributions) - config-resolution.test.ts: stub contributes Int via a zero-arg type constructor in authoringContributions.type and exposes scalarTypes; assertions follow Also tighten the framework-vocabulary ratchet 840 → 837 to lock in the symbol-table simplification reduction. Signed-off-by: Serhii Tatarintsev <tatarintsev@prisma.io>
|
Important Review skippedAuto reviews are disabled on base/target branches other than the default branch. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: Path: .coderabbit.yml Review profile: CHILL Plan: Pro Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
@prisma-next/extension-author-tools
@prisma-next/mongo-runtime
@prisma-next/family-mongo
@prisma-next/sql-runtime
@prisma-next/family-sql
@prisma-next/extension-arktype-json
@prisma-next/middleware-cache
@prisma-next/mongo
@prisma-next/extension-paradedb
@prisma-next/extension-pgvector
@prisma-next/extension-postgis
@prisma-next/postgres
@prisma-next/sql-orm-client
@prisma-next/sqlite
@prisma-next/extension-supabase
@prisma-next/target-mongo
@prisma-next/adapter-mongo
@prisma-next/driver-mongo
@prisma-next/contract
@prisma-next/utils
@prisma-next/config
@prisma-next/errors
@prisma-next/framework-components
@prisma-next/operations
@prisma-next/ts-render
@prisma-next/contract-authoring
@prisma-next/ids
@prisma-next/psl-parser
@prisma-next/psl-printer
@prisma-next/cli
@prisma-next/cli-telemetry
@prisma-next/config-loader
@prisma-next/emitter
@prisma-next/language-server
@prisma-next/migration-tools
prisma-next
@prisma-next/vite-plugin-contract-emit
@prisma-next/mongo-codec
@prisma-next/mongo-contract
@prisma-next/mongo-value
@prisma-next/mongo-contract-psl
@prisma-next/mongo-contract-ts
@prisma-next/mongo-emitter
@prisma-next/mongo-schema-ir
@prisma-next/mongo-query-ast
@prisma-next/mongo-orm
@prisma-next/mongo-query-builder
@prisma-next/mongo-lowering
@prisma-next/mongo-wire
@prisma-next/sql-contract
@prisma-next/sql-errors
@prisma-next/sql-operations
@prisma-next/sql-schema-ir
@prisma-next/sql-contract-psl
@prisma-next/sql-contract-ts
@prisma-next/sql-contract-emitter
@prisma-next/sql-lane-query-builder
@prisma-next/sql-relational-core
@prisma-next/sql-builder
@prisma-next/target-postgres
@prisma-next/target-sqlite
@prisma-next/adapter-postgres
@prisma-next/adapter-sqlite
@prisma-next/driver-postgres
@prisma-next/driver-sqlite
commit: |
size-limit report 📦
|
Signed-off-by: Serhii Tatarintsev <tatarintsev@prisma.io>
…nto tml-2986-native-types-as-scalars Signed-off-by: Serhii Tatarintsev <tatarintsev@prisma.io>
…rs dialect Main brought ts-psl-rls-parity.test.ts speaking the deleted channel. Mirror the already-migrated sibling ts-psl-parity.real-packs.test.ts: - derive descriptors via collectScalarTypeConstructors( stack.authoringContributions.type) instead of reading stack.scalarTypeDescriptors + codec-lookup joins - drop the retired scalarTypes option from buildSymbolTable - pass the interpreter input under its current name, scalarColumnDescriptors Signed-off-by: Serhii Tatarintsev <tatarintsev@prisma.io>
Signed-off-by: Serhii Tatarintsev <tatarintsev@prisma.io>
Signed-off-by: Serhii Tatarintsev <tatarintsev@prisma.io>
…age (QA F-1) `id Uuid @id @default(uuid())` silently emitted sql/char@1/char(36): the generator-default lowering let resolveGeneratedColumnDescriptor replace the field descriptor unless the field used a named type — an exemption built when named types were the only explicit-storage spelling. Bare native scalars (Uuid) and constructor calls (Char(36)) fell through it. Provenance now rides on the type descriptor: adapters mark family base scalars (String, Int, …) with `baseScalar: true`, meaning the storage is the target default choice rather than a user opinion. collectScalarTypeConstructors propagates the marker onto the scalar view, resolveFieldTypeDescriptor reports it for bare and zero-arg call forms alike (T ≡ T()), and the override applies only to base-scalar resolutions. Named types, enums, native scalars, presets, and entity-ref constructors always keep their declared storage; the legacy convenience (String @default(uuid()) → char(36)) is byte-identical. The marker never reaches emitted contracts: storage columns are built by picking named descriptor fields. Signed-off-by: Serhii Tatarintsev <tatarintsev@prisma.io>
…et at 837 The baseScalar marker docstring said the generator "may re-pick a column's concrete storage" — "column" is family vocabulary counted by lint-framework-vocabulary, pushing the framework scope to 838 > 837. "field's concrete storage" says the same thing in framework terms; no threshold bump needed. Signed-off-by: Serhii Tatarintsev <tatarintsev@prisma.io>
…tates storage The type position is the only storage decider (operator-decided 2026-07-15). Retired wholesale: generatedColumnDescriptor/resolveGeneratedColumnDescriptor in @prisma-next/ids and MutationDefaultGeneratorDescriptor, the override block in psl-field-resolution.ts, and the transitional baseScalar marker (framework authoring descriptor, scalar view, ColumnDescriptor, adapter contributions). New semantics pinned: String @default(uuid()) emits the target String storage (pg: text) plus the uuidv4 execution default; nanoid/cuid on String stay text; named type TId = String + generator stays text; Uuid @default(uuid()) keeps pg/uuid@1 by construction; a generator on an inapplicable codec still diagnoses PSL_INVALID_DEFAULT_APPLICABILITY (applicableCodecIds validation survives — it validates, it does not mutate). TS field presets are untouched and keep bundling char(N) explicitly. TS↔PSL parity fixtures re-pair preset spellings with explicit Char(N) on the PSL side; examples with committed char(36) artifacts re-author to Char(36) (byte-stable contracts and migration chains); the cloudflare-worker example regenerates to text (no committed DDL depends on its storage). Upgrade entry default-generators-no-longer-set-storage recorded in both 0.14-to-0.15 clusters. Signed-off-by: Serhii Tatarintsev <tatarintsev@prisma.io>
…-as-scalars Signed-off-by: Serhii Tatarintsev <tatarintsev@prisma.io> # Conflicts: # examples/prisma-next-cloudflare-worker/src/prisma/contract.d.ts # examples/prisma-next-cloudflare-worker/src/prisma/contract.json
…facts, relocate upgrade entries to 0.15-to-0.16 The origin/main merge took main's side of the cloudflare-worker contract artifacts (char(36) ids); the example's schema is still String-typed with generator defaults, so under the retired storage override they re-emit with text storage (8 columns sql/char@1 -> pg/text@1, storage hash updated). v0.15.0 shipped without this project's changes, so the three entries the project had authored into upgrades/0.14-to-0.15 (scalar-type-descriptors- channel-removed, postgres-json-rebound-to-native-json, default-generators- no-longer-set-storage) describe 0.15 -> 0.16 work. Both 0.14-to-0.15 instructions.md files reset to origin/main (shipped content only); the three entries move into both clusters' existing 0.15-to-0.16 files with their version references advanced (pre-0.15/from-0.15 -> pre-0.16/from-0.16). Signed-off-by: Serhii Tatarintsev <tatarintsev@prisma.io>
The v0.15.0 merge brought the expanded Supabase auth contract, authored before this branch re-bound postgres Json to pg/json@1. Its 18 bare Json fields mirror real Supabase jsonb columns, so they re-author to Jsonb per the slice edge-case rule; the committed contract.json/d.ts re-emit byte-identical (36 pg/jsonb@1 entries unchanged). The Payload named type keeps Json @db.Json — that column really is json. Signed-off-by: Serhii Tatarintsev <tatarintsev@prisma.io>
…race Signed-off-by: Serhii Tatarintsev <tatarintsev@prisma.io>
…es-as-scalars Signed-off-by: Serhii Tatarintsev <tatarintsev@prisma.io>
Signed-off-by: Serhii Tatarintsev <tatarintsev@prisma.io>
Signed-off-by: Serhii Tatarintsev <tatarintsev@prisma.io>
| if (typeof nativeType !== 'string') continue; | ||
| result.set(name, { codecId: value.output.codecId, nativeType }); | ||
| if (value.args?.some((arg) => arg.optional !== true)) continue; | ||
| try { |
There was a problem hiding this comment.
Why do we need this try catch? We already checked for optional arguments before, this should be enough
| */ | ||
| const CHAR_COLUMN_TYPE = { codecId: 'sql/char@1', nativeType: 'character' } as const; | ||
|
|
||
| const specCharLengthById: Record<Exclude<BuiltinGeneratorId, 'nanoid'>, number> = { |
There was a problem hiding this comment.
Why did we remove this out of generated column descriptor? It is possible to remove storage type change magic from PSL while keeping TS surface consistent.
| descriptor = scalarColumnDescriptors.get('Json'); | ||
| // Value objects store in the richest JSON storage the target contributes | ||
| // (postgres: Jsonb -> jsonb); targets without a Jsonb scalar fall back to Json. | ||
| descriptor = scalarColumnDescriptors.get('Jsonb') ?? scalarColumnDescriptors.get('Json'); |
There was a problem hiding this comment.
Bad robot! This leaks postgres-specific detail all the way down to authoring layer.
This should be handled by postgres/sqlite targets, not authoring layer.
Finding 1 — collectScalarTypeConstructors: replace the try/catch control flow with a declarative bare-eligibility check. The only residual throw after the all-optional-args and entityRefArg guards was a nativeType template that does not resolve without arguments (missing template or arg-ref with no default); resolve it explicitly with the existing resolveAuthoringTemplateValue machinery and exclude non-string results. Instantiation can no longer fail for an eligible constructor. Finding 2 — @prisma-next/ids: restore the generator metadata as the single source of the storage each TS spec helper bundles. Each entry in builtinGeneratorMetadataById now carries a generatedStorage resolver (char length per generator id, nanoid size resolution), consumed by createGeneratedSpec; the CHAR_COLUMN_TYPE/specCharLengthById/ specCharLength shadow table is merged into it. Named generatedStorage (rather than generatedColumnDescriptor) to keep the framework scope family-blind; the PSL interpreter still never consults it (no production import from contract-psl). Framework-vocabulary count drops 837->810->808; threshold locked at 808. Finding 3 — value-object storage: the family layer no longer hardcodes Jsonb/Json type names. Targets declare their value-object storage type via a valueObjectStorage marker on the contributed type constructor (postgres: Jsonb, sqlite: Json); the new framework helper findValueObjectStorageTypeName reads the single marked top-level constructor generically (throws on ambiguity), and psl-field-resolution looks the declared name up in the derived scalar map. A target that declares none skips value-object fields, preserving the pre-existing no-storage behavior. Pinned by tests: postgres value objects still emit pg/jsonb@1 (fixtures byte-stable), sqlite value objects store as sqlite/json@1 text, unmarked contributions skip the field even when Jsonb is present in the scalar map. Signed-off-by: Serhii Tatarintsev <tatarintsev@prisma.io>
Round-2 operator correction on PR #975 finding 3: replace the per- descriptor valueObjectStorage boolean marker with a single named property on the contributing component descriptor - authoring.valueObjectStorageType (postgres adapter declares Jsonb, sqlite declares Json). One slot per component makes within-component ambiguity impossible by shape: no namespace scan, no two-marks error path. The property rides through assembleAuthoringContributions like other component metadata: assembly rejects two components both declaring it with the existing contributor-attribution error style (naming both descriptors), and one validation replaces the scan - the declared name must exist as a top-level bare-eligible constructor in the assembled namespace (error naming the type and the declaring component). The assembled single value lands on AssembledAuthoringContributions and flows to the family interpreter through the existing context; psl- field-resolution reads it directly. findValueObjectStorageTypeName and the descriptor-level marker are deleted - no dual mechanism. Field resolution behavior is unchanged: postgres value objects still emit pg/jsonb@1 (fixtures byte-stable), sqlite value objects store as sqlite/json@1 text, and a stack that declares no value-object storage skips the field, all pinned by the reshaped tests. Framework-vocabulary count stays at 808. Signed-off-by: Serhii Tatarintsev <tatarintsev@prisma.io>
At a glance
The eleven former
@db.*native types become first-class bare PSL scalar types on the postgres target —Uuid,SmallInt,Real,Date,VarChar(191),Char(12),Numeric(10,2),Timestamp(3),Timestamptz(6),Time(3),Timetz(2)— authorable in named-type declarations and directly in field position. JSON executes its settled re-binding:Jsonnow means nativejson(pg/json@1); newJsonbcarriespg/jsonb@1(previously bareJsonmeant jsonb).@db.*continues to work untouched — its removal is slice 4, after slice 3 migrates consumers.Slice 2 of 4 of the remove-db-attributes project (spec:
projects/remove-db-attributes/spec.md).The decisions
Bare
Tis sugar forT(). The bare-name criterion broadened from "declares zero args" to "instantiable with an empty argument list": bare resolution is zero-arg instantiation — one resolution path, no duplicated template logic.VarCharsans parens works exactly as@db.VarChardid; constructors with required args or entity refs stay bare-ineligible with the existing diagnostic.The symbol table no longer knows scalar names (operator-gated simplification — landed).
buildSymbolTable'sscalarTypesinput and theisScalarBindingScalarSymbol/TypeAliasSymbol split are gone:types {}declarations collect into onenamedTypesymbol kind, and the interpreter (which always re-classified authoritatively) is the single classification authority. LSP presentation re-derives the distinction at query time; its tests pass unweakened. The parser layer is now family-blind — the framework-vocabulary ratchet tightened from 840 to 837.Native types are adapter contributions with parity proven against the live oracle. The eleven types live beside the base scalars in the postgres adapter's authoring contributions, with declarative optional args (omitted args omit typeParams keys, matching
@db.*exactly — includingNumeric's precision ≥ 1 rule). Parity tests emit each type through both the bare path and the live@db.*path in the same run and require deep-equality — a drift on either side fails.JSON re-bind with byte-stable legacy.
@db.Jsonstill resolves identically (NATIVE_TYPE_SPECSuntouched); value-object storage explicitly prefersJsonbso composite-type columns stay jsonb; TS↔PSL parity pairsfield.json()(jsonb, unchanged — TS surface is a project non-goal) with PSLJsonb. sqlite/mongoJsonbindings are pinned untouched.Consumer impact
Upgrade entry
postgres-json-rebound-to-native-jsonadded to both upgrade-skill clusters (upgrades/0.14-to-0.15/): postgres schemas usingJsonfor jsonb storage switch toJsonband re-emit.Verification
Parity suite (21 table-driven cases × 3 assertions), bare-vs-call equivalence tests, invalid-arg diagnostics,
@db.Jsonbyte-stability, per-target JSON-binding pins.pnpm typecheck,test:packages,fixtures:check(zero drift),lint:deps, framework-vocabulary (837/837),check:upgrade-coverage— green. Known environmental exceptions on the dev host only: mongodb-memory-server NixOS bootstrap failures and two 500ms CLI cold-start flakes, both pre-existing.Refs: TML-2986