Skip to content

tml-2913: native scalar-list query & update operations#910

Open
SevInf wants to merge 6 commits into
mainfrom
scalar-lists-slice3
Open

tml-2913: native scalar-list query & update operations#910
SevInf wants to merge 6 commits into
mainfrom
scalar-lists-slice3

Conversation

@SevInf

@SevInf SevInf commented Jul 3, 2026

Copy link
Copy Markdown
Contributor

What & why

Slice 3 of native-scalar-arrays (TML-2913). Slices 1–2 made SQL scalar lists first-class in storage/authoring/migration/read (native text[]/int4[] columns with element-wise codecs). This slice makes them queryable and mutable — the operation surface.

Branches off main (has slices 1 & 2, merged). Independent of the check-constraint-unification PR.

What's here

Read predicates & array filters (registry ops on the sql-builder fns surface, lowering via OperationExpr templates — no new AST):

  • has (x = ANY(tags)) — element membership
  • arrayContains (@>), containedBy (<@), overlaps (&&) — array filters
  • length (cardinality), index (tags[i], element-typed, nullable)

Update mutators (element-preserving many: true return, on the update set callback):

  • arrayAppend (array_append), arrayRemove (array_remove), set-whole-array (raw-values path)

Comparison builtins widened to lists (FR16)eq/ne/gt/lt/gte/lte accept whole-list operands. Type-level only (they already lower to BinaryExpr(op), which Postgres applies to arrays). Added as disjoint overloads keyed on the many discriminant so scalar-call inference is untouched (NFR3); gated by the element codec's equality (eq/ne) / order (ordering) trait.

Decisions

  • arrayContains for @>: the natural name contains is already registered by the postgis extension (geometry), and the op registry is global-by-name — so the list "contains-all" filter is arrayContains.
  • Two incidental infra completions the operation layer needed: the framework op-registry learned the { many: true; elementTraits } self-arm, and the SQL emitter now projects many onto the storage-column .d.ts literal (so f.tags types as a list expression). The emitter change is guarded on many === true — non-list contracts are byte-identical (NFR2; only the one list-column fixture regenerated).

Acceptance criteria

  • AC3where((f,fns)=>fns.has(f.tags,x)) executes x = ANY(tags); wrong-typed element and scalar receiver are compile errors. ✅
  • AC7 — an update appends to and removes from tags String[] (array_append/array_remove) and the read returns the mutated list decoded element-wise. ✅
  • AC11fns.eq(f.tags,['a','b']) matches by array equality, fns.gt(f.tags,['a']) by lexicographic order; wrong-typed element and an ordering op on an unorderable-element list are compile errors. ✅

Verification

build 68/68 · typecheck 143/143 · lint 82/82 · lint:deps clean · fixtures:check byte-clean · cast ratchet delta 0 · adapter-postgres + sql-builder green · scalar-lists integration green (real Postgres). Runtime ACs proven on embedded Postgres; compile-error ACs via test-d.ts with load-bearing @ts-expect-error.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Added broader support for scalar list fields, including membership checks, list comparisons, length, indexing, and list append/remove updates.
    • Improved SQL generation so list operations use the expected database operators and functions.
  • Bug Fixes

    • Tightened validation for “self” configuration to prevent conflicting list and non-list settings.
    • Preserved list and element typing more accurately across query building and emitted types.

SevInf and others added 5 commits July 3, 2026 09:30
…ice 3 A]

Promote the prototyped `has` list operation into a live Postgres registry op.
`fns.has(f.tags, x)` now type-checks over a list column and executes as
`x = ANY(tags)` on real Postgres; a wrong-typed element and a scalar receiver
are compile errors.

Necessary unblocks discovered at DoR (both minimal and list-scoped):
- framework operation registry: accept the `{ many: true; elementTraits? }`
  self-spec arm (type + register validation), so list-self ops can register.
- SQL emitter: project `many: true` into the storage-table column `.d.ts`
  literal, so `Db<Contract>` types a `many` column as a list receiver
  (regenerated the scalar-lists fixture .d.ts; contract.json unchanged).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Signed-off-by: Serhii Tatarintsev <tatarintsev@prisma.io>
… 3 B]

Promote the read-side v1 list ops into live Postgres registry ops, mirroring
the `has` pattern (runtime impl in `descriptor-meta.ts`, type mirror in
`operation-types.ts`), each proven to render the right SQL and execute on real
Postgres, with compile-error cases for wrong-typed operands and scalar
receivers:

- `containedBy` (`{{self}} <@ {{arg0}}`) — whole-array operand, bool.
- `overlaps`    (`{{self}} && {{arg0}}`) — whole-array operand, bool.
- `length`      (`cardinality({{self}})`) — non-null pg/int4.
- `index`       (`{{self}}[{{arg0}}]`)   — 1-based access, nullable element codec.

Whole-array operands accept a `readonly T[]` literal (lowered to an `ARRAY[...]`
literal) or another `ScalarListExpression<CodecId>`; the element type is tied to
the receiver's `CodecId`, so a wrong-typed element is a compile error. `index`
returns the receiver's element codec (nullable, since out-of-range access is
NULL).

Dropped from this slice: the `contains` (`@>`) op named in the plan collides
with the postgis extension's existing geometry `contains` op. The base postgres
adapter is always a stack contributor, so registering `contains` there
double-registers in any postgres+postgis stack (`Operation "contains" is
already registered`) and breaks `pnpm build`. Reported rather than renamed
against spec or cast around.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Signed-off-by: Serhii Tatarintsev <tatarintsev@prisma.io>
Mirror the existing `containedBy` (`<@`) op as `arrayContains` (`@>`, "list
contains every element of the argument array") — same whole-array operand shape,
same element-type linkage to the receiver `CodecId`, same non-null bool return —
differing only in the infix template and the name.

Named `arrayContains` rather than `contains`: the op registry is global-by-name
and the postgis extension already registers `contains` for geometry, so a base
`contains` op would double-register in any postgres+postgis stack.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Signed-off-by: Serhii Tatarintsev <tatarintsev@prisma.io>
…et) [slice 3 C]

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Signed-off-by: Serhii Tatarintsev <tatarintsev@prisma.io>
…) [slice 3 D]

Add disjoint list-operand overloads to the six comparison builtins
(eq/ne/gt/gte/lt/lte) keyed on the `many` discriminant. Each builtin now
intersects its untouched scalar signature with a `ListComparison` call
signature that accepts a whole-list receiver plus a matching list expression or
literal array. Element-trait gating (`equality` for eq/ne, `order` for the
ordering ops) is enforced via `CodecIdsWithTrait`, so an ordering op over a list
whose element codec lacks `order` is a compile error. Type-level widening only;
the runtime already lowers array operands correctly via the shared param path.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Signed-off-by: Serhii Tatarintsev <tatarintsev@prisma.io>
@SevInf
SevInf requested a review from a team as a code owner July 3, 2026 11:21
@coderabbitai

coderabbitai Bot commented Jul 3, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yml

Review profile: CHILL

Plan: Pro

Run ID: 29e21a5d-cc61-40dc-9369-67585ea8a052

📥 Commits

Reviewing files that changed from the base of the PR and between 3abca2d and ca3cc31.

📒 Files selected for processing (3)
  • packages/2-sql/4-lanes/sql-builder/test/types/scalar-list-ops.types.test-d.ts
  • packages/3-targets/6-adapters/postgres/src/core/descriptor-meta.ts
  • packages/3-targets/6-adapters/postgres/src/types/operation-types.ts
🚧 Files skipped from review as they are similar to previous changes (2)
  • packages/2-sql/4-lanes/sql-builder/test/types/scalar-list-ops.types.test-d.ts
  • packages/3-targets/6-adapters/postgres/src/types/operation-types.ts

📝 Walkthrough

Walkthrough

Adds a many: true self-shaping mode to the operations registry, list-aware comparison typing in the SQL builder, and Postgres adapter implementations of scalar-list operations (has, containment, overlap, comparisons, length, index, append/remove), plus emitter support for many columns and integration/type tests.

Changes

Scalar-list operations support

Layer / File(s) Summary
Operation registry many self-spec
packages/1-framework/1-core/operations/src/index.ts, packages/1-framework/1-core/operations/test/operations-registry.test.ts
SelfSpec gains a mutually exclusive many: true variant with optional elementTraits; registry validation now enforces exactly one of codecId/traits/many, with tests for acceptance and conflict rejection.
Emitter and codec type exports for many columns
packages/2-sql/3-tooling/emitter/src/index.ts, packages/2-sql/3-tooling/emitter/test/emitter-hook.storage-column-types.test.ts, packages/2-sql/4-lanes/relational-core/src/expression.ts
Emitter injects readonly many: true into generated column literal types when applicable, with a new emitter test, and CodecIdsWithTrait is now exported.
SQL builder list comparison typings
packages/2-sql/4-lanes/sql-builder/test/types/scalar-list-ops.types.test-d.ts
Codec traits change to string-literal unions and a new ListCompare type built on CodecIdsWithTrait drives eq/ne/ordering overloads to accept whole-list operands, validated by expanded type tests.
Postgres list operation implementations
packages/3-targets/6-adapters/postgres/src/core/descriptor-meta.ts, packages/3-targets/6-adapters/postgres/src/types/operation-types.ts
Adds has, arrayContains, containedBy, overlaps, comparison operators, length, index, arrayAppend, arrayRemove lowering with helper functions and corresponding typed QueryOperationTypes entries.
Runtime integration tests for list reads and mutations
test/integration/test/scalar-lists/orm-list-read.integration.test.ts, test/integration/test/scalar-lists/scalar-list-mutations.integration.test.ts
Adds integration tests validating generated SQL and results for list read operators and for arrayAppend/arrayRemove/array-replacement mutations against a real Postgres database.
End-to-end type tests for list ops
test/integration/test/scalar-lists/scalar-list-ops.types.test-d.ts
Adds compile-time assertions covering list operator signatures and expected type errors for invalid usage across the full builder surface.

Estimated code review effort: 4 (Complex) | ~60 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Test as Integration Test
  participant Runtime as ORM Runtime
  participant Builder as sqlBuilder
  participant Adapter as Postgres Adapter
  participant DB as PostgreSQL

  Test->>Runtime: db.public.Item.create(tags)
  Runtime->>Adapter: lower insert with list column
  Adapter->>DB: INSERT ... tags
  Test->>Builder: build query with has/arrayContains/overlaps
  Builder->>Adapter: resolve list operation (elementCodecOf, arrayOperandToExpr)
  Adapter->>Adapter: lower to SQL (= ANY, @>, <@, &&, cardinality)
  Adapter->>Runtime: emit SQL expression
  Runtime->>DB: execute query
  DB-->>Runtime: rows
  Runtime-->>Test: decoded results
Loading

Possibly related PRs

  • prisma/prisma-next#846: Provides the foundational many/scalarList storage-column, expression, and runtime codec machinery that this PR's self-spec validation and Postgres lowering directly extend.

Suggested reviewers: aqrln, wmadden

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the main change: adding native scalar-list query and update operations.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch scalar-lists-slice3

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@pkg-pr-new

pkg-pr-new Bot commented Jul 3, 2026

Copy link
Copy Markdown

Open in StackBlitz

@prisma-next/extension-author-tools

npm i https://pkg.pr.new/@prisma-next/extension-author-tools@910

@prisma-next/mongo-runtime

npm i https://pkg.pr.new/@prisma-next/mongo-runtime@910

@prisma-next/family-mongo

npm i https://pkg.pr.new/@prisma-next/family-mongo@910

@prisma-next/sql-runtime

npm i https://pkg.pr.new/@prisma-next/sql-runtime@910

@prisma-next/family-sql

npm i https://pkg.pr.new/@prisma-next/family-sql@910

@prisma-next/extension-arktype-json

npm i https://pkg.pr.new/@prisma-next/extension-arktype-json@910

@prisma-next/middleware-cache

npm i https://pkg.pr.new/@prisma-next/middleware-cache@910

@prisma-next/mongo

npm i https://pkg.pr.new/@prisma-next/mongo@910

@prisma-next/extension-paradedb

npm i https://pkg.pr.new/@prisma-next/extension-paradedb@910

@prisma-next/extension-pgvector

npm i https://pkg.pr.new/@prisma-next/extension-pgvector@910

@prisma-next/extension-postgis

npm i https://pkg.pr.new/@prisma-next/extension-postgis@910

@prisma-next/postgres

npm i https://pkg.pr.new/@prisma-next/postgres@910

@prisma-next/sql-orm-client

npm i https://pkg.pr.new/@prisma-next/sql-orm-client@910

@prisma-next/sqlite

npm i https://pkg.pr.new/@prisma-next/sqlite@910

@prisma-next/extension-supabase

npm i https://pkg.pr.new/@prisma-next/extension-supabase@910

@prisma-next/target-mongo

npm i https://pkg.pr.new/@prisma-next/target-mongo@910

@prisma-next/adapter-mongo

npm i https://pkg.pr.new/@prisma-next/adapter-mongo@910

@prisma-next/driver-mongo

npm i https://pkg.pr.new/@prisma-next/driver-mongo@910

@prisma-next/contract

npm i https://pkg.pr.new/@prisma-next/contract@910

@prisma-next/utils

npm i https://pkg.pr.new/@prisma-next/utils@910

@prisma-next/config

npm i https://pkg.pr.new/@prisma-next/config@910

@prisma-next/errors

npm i https://pkg.pr.new/@prisma-next/errors@910

@prisma-next/framework-components

npm i https://pkg.pr.new/@prisma-next/framework-components@910

@prisma-next/operations

npm i https://pkg.pr.new/@prisma-next/operations@910

@prisma-next/ts-render

npm i https://pkg.pr.new/@prisma-next/ts-render@910

@prisma-next/contract-authoring

npm i https://pkg.pr.new/@prisma-next/contract-authoring@910

@prisma-next/ids

npm i https://pkg.pr.new/@prisma-next/ids@910

@prisma-next/psl-parser

npm i https://pkg.pr.new/@prisma-next/psl-parser@910

@prisma-next/psl-printer

npm i https://pkg.pr.new/@prisma-next/psl-printer@910

@prisma-next/cli

npm i https://pkg.pr.new/@prisma-next/cli@910

@prisma-next/cli-telemetry

npm i https://pkg.pr.new/@prisma-next/cli-telemetry@910

@prisma-next/config-loader

npm i https://pkg.pr.new/@prisma-next/config-loader@910

@prisma-next/emitter

npm i https://pkg.pr.new/@prisma-next/emitter@910

@prisma-next/language-server

npm i https://pkg.pr.new/@prisma-next/language-server@910

@prisma-next/migration-tools

npm i https://pkg.pr.new/@prisma-next/migration-tools@910

prisma-next

npm i https://pkg.pr.new/prisma-next@910

@prisma-next/vite-plugin-contract-emit

npm i https://pkg.pr.new/@prisma-next/vite-plugin-contract-emit@910

@prisma-next/mongo-codec

npm i https://pkg.pr.new/@prisma-next/mongo-codec@910

@prisma-next/mongo-contract

npm i https://pkg.pr.new/@prisma-next/mongo-contract@910

@prisma-next/mongo-value

npm i https://pkg.pr.new/@prisma-next/mongo-value@910

@prisma-next/mongo-contract-psl

npm i https://pkg.pr.new/@prisma-next/mongo-contract-psl@910

@prisma-next/mongo-contract-ts

npm i https://pkg.pr.new/@prisma-next/mongo-contract-ts@910

@prisma-next/mongo-emitter

npm i https://pkg.pr.new/@prisma-next/mongo-emitter@910

@prisma-next/mongo-schema-ir

npm i https://pkg.pr.new/@prisma-next/mongo-schema-ir@910

@prisma-next/mongo-query-ast

npm i https://pkg.pr.new/@prisma-next/mongo-query-ast@910

@prisma-next/mongo-orm

npm i https://pkg.pr.new/@prisma-next/mongo-orm@910

@prisma-next/mongo-query-builder

npm i https://pkg.pr.new/@prisma-next/mongo-query-builder@910

@prisma-next/mongo-lowering

npm i https://pkg.pr.new/@prisma-next/mongo-lowering@910

@prisma-next/mongo-wire

npm i https://pkg.pr.new/@prisma-next/mongo-wire@910

@prisma-next/sql-contract

npm i https://pkg.pr.new/@prisma-next/sql-contract@910

@prisma-next/sql-errors

npm i https://pkg.pr.new/@prisma-next/sql-errors@910

@prisma-next/sql-operations

npm i https://pkg.pr.new/@prisma-next/sql-operations@910

@prisma-next/sql-schema-ir

npm i https://pkg.pr.new/@prisma-next/sql-schema-ir@910

@prisma-next/sql-contract-psl

npm i https://pkg.pr.new/@prisma-next/sql-contract-psl@910

@prisma-next/sql-contract-ts

npm i https://pkg.pr.new/@prisma-next/sql-contract-ts@910

@prisma-next/sql-contract-emitter

npm i https://pkg.pr.new/@prisma-next/sql-contract-emitter@910

@prisma-next/sql-lane-query-builder

npm i https://pkg.pr.new/@prisma-next/sql-lane-query-builder@910

@prisma-next/sql-relational-core

npm i https://pkg.pr.new/@prisma-next/sql-relational-core@910

@prisma-next/sql-builder

npm i https://pkg.pr.new/@prisma-next/sql-builder@910

@prisma-next/target-postgres

npm i https://pkg.pr.new/@prisma-next/target-postgres@910

@prisma-next/target-sqlite

npm i https://pkg.pr.new/@prisma-next/target-sqlite@910

@prisma-next/adapter-postgres

npm i https://pkg.pr.new/@prisma-next/adapter-postgres@910

@prisma-next/adapter-sqlite

npm i https://pkg.pr.new/@prisma-next/adapter-sqlite@910

@prisma-next/driver-postgres

npm i https://pkg.pr.new/@prisma-next/driver-postgres@910

@prisma-next/driver-sqlite

npm i https://pkg.pr.new/@prisma-next/driver-sqlite@910

commit: ca3cc31

@github-actions

github-actions Bot commented Jul 3, 2026

Copy link
Copy Markdown

size-limit report 📦

Path Size
postgres / no-emit 149.49 KB (+0.35% 🔺)
postgres / emit 126.92 KB (+0.37% 🔺)
mongo / no-emit 98.2 KB (0%)
mongo / emit 89.39 KB (0%)
cf-worker / no-emit 176.06 KB (+0.31% 🔺)
cf-worker / emit 151.5 KB (+0.36% 🔺)

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
packages/1-framework/1-core/operations/src/index.ts (1)

44-61: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Error messages don't reflect the new many mode.

targetCount === 0 still throws "has neither codecId nor traits", and targetCount > 1 still throws "has both codecId and traits" — neither mentions many, even though a self: {} (no codecId/traits/many) or a conflicting { many: true, traits: [...] } self would now hit these same messages. This misleads whoever debugs a failing registration involving many. Note the test at operations-registry.test.ts (Line 122) already hardcodes the stale "both codecId and traits" text, so both need to move together.

✏️ Proposed fix for generic messaging
         if (targetCount === 0) {
-          throw new Error(`Operation "${name}" self has neither codecId nor traits`);
+          throw new Error(`Operation "${name}" self has none of codecId, traits, or many`);
         }
         if (targetCount > 1) {
-          throw new Error(`Operation "${name}" self has both codecId and traits`);
+          throw new Error(`Operation "${name}" self must have exactly one of codecId, traits, or many`);
         }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/1-framework/1-core/operations/src/index.ts` around lines 44 - 61,
Update the error handling in register so the messages account for
descriptor.self.many as a valid target alongside codecId and traits. In the
register name/descriptor flow, change the zero-target and multi-target Error
text to mention codecId, traits, and many consistently, and make sure any
assertions in operations-registry.test.ts that hardcode the old wording are
updated to match the new messages.
🧹 Nitpick comments (5)
packages/3-targets/6-adapters/postgres/src/core/descriptor-meta.ts (1)

183-189: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Use the elementCodecOf helper instead of re-inlining its body.

has (Lines 184-189) and index (Lines 268-273) duplicate the exact codec-resolution block that elementCodecOf already encapsulates and that arrayAppend/arrayRemove reuse. Consolidate to avoid divergence.

♻️ Reuse helper in `has`
-        const listCodec = codecOf(self);
-        const elementCodec =
-          listCodec === undefined
-            ? undefined
-            : listCodec.typeParams === undefined
-              ? { codecId: listCodec.codecId }
-              : { codecId: listCodec.codecId, typeParams: listCodec.typeParams };
+        const elementCodec = elementCodecOf(self);

Apply the same substitution in index (Lines 267-273).

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/3-targets/6-adapters/postgres/src/core/descriptor-meta.ts` around
lines 183 - 189, The codec-resolution logic in has and index is duplicated and
should reuse the existing elementCodecOf helper instead of re-inlining its body.
Update both locations to call elementCodecOf with the same self argument used in
codecOf(self), matching the pattern already used by arrayAppend and arrayRemove,
so the codec selection stays centralized in descriptor-meta.ts.
packages/2-sql/4-lanes/sql-builder/test/types/scalar-list-ops.types.test-d.ts (1)

91-127: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Missing negative test for eq/ne with a non-equality-trait element.

All fixtures in CT carry equality, so there's no @ts-expect-error case proving eq/ne reject a list whose element codec lacks equality (only the ordering-trait-missing case is tested via flags). Not blocking, but would round out the trait-gating coverage symmetrically with the ordering tests.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@packages/2-sql/4-lanes/sql-builder/test/types/scalar-list-ops.types.test-d.ts`
around lines 91 - 127, The scalar-list overload tests in
scalar-list-ops.types.test-d.ts are missing a negative case for equality-based
comparison on a list whose element codec does not have the equality trait.
Extend the existing `fns.eq`/`fns.ne` coverage in the `scalar comparison` /
`equality builtins` tests by adding an `@ts-expect-error` assertion using a list
fixture whose element type lacks `equality`, mirroring the existing
`fns.gt`/`fns.lt` rejection checks for `flags` and keeping the test names and
`fns`/`f` symbols aligned with the current suite.
packages/2-sql/4-lanes/sql-builder/src/expression.ts (1)

4-35: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Verbose doc comment on ListComparison.

The design (trait-gated list overload kept disjoint from scalar overload via the many discriminant) is non-trivial and the comment does explain real rationale, but per repo guidelines comments should be avoided when code can express intent directly. Consider trimming to the essential "why" (disjointness via many discriminant) and dropping the mechanical description of RequiredTraits, which is already conveyed by the type parameter name and usage sites.

As per coding guidelines, **/*.{ts,tsx,js,jsx,mts,mjs}: "Don't add comments if avoidable, prefer code that expresses its intent."

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/2-sql/4-lanes/sql-builder/src/expression.ts` around lines 4 - 35,
The doc comment above ListComparison is too verbose for a type that already
expresses most of its intent. Trim it down to the minimal rationale needed to
understand the overload separation, keeping only the point about the many
discriminant making the list and scalar call signatures disjoint, and remove the
detailed explanation of RequiredTraits and the mechanical lowering behavior.

Source: Coding guidelines

test/integration/test/scalar-lists/scalar-list-mutations.integration.test.ts (2)

168-219: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Whole-array replacement test doesn't assert on generated SQL.

Unlike the arrayAppend/arrayRemove test above (which asserts capturedSql contains array_append(/array_remove(), this test only checks the round-tripped row values. Since this test exists specifically to validate "whole-array replacement via the raw-values path" (per PR objectives), asserting the emitted SQL uses the expected raw-array lowering (rather than only checking end state) would give stronger confidence the intended code path is exercised.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@test/integration/test/scalar-lists/scalar-list-mutations.integration.test.ts`
around lines 168 - 219, The whole-array replacement integration test currently
only verifies the final row value, so it does not confirm that the raw-array
lowering path was used. Update the test named “replaces the whole list column
with a raw array literal” to capture the SQL emitted by the `runtime.execute`
call that uses `builder.public.item.update({ tags: ['x', 'y'] })`, and assert
the generated SQL reflects a raw array replacement rather than `array_append` or
`array_remove`. Keep the existing round-trip assertion, but add SQL-level
expectations so this test exercises the intended `sqlBuilder`/`rawCodecInferer`
path.

105-219: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Same duplicated setup pattern as the sibling read-ops test file.

Both it() blocks re-implement the schema reset / migrate / runtime+context construction sequence found in orm-list-read.integration.test.ts. Consider factoring a shared helper (in a common test-utils module for the scalar-lists suite) used by both files to avoid the same boilerplate being maintained in two places.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@test/integration/test/scalar-lists/scalar-list-mutations.integration.test.ts`
around lines 105 - 219, The two scalar list mutation tests duplicate the same
schema reset, migration, and runtime/context setup already used in the sibling
read-ops test file. Extract that repeated setup into a shared helper for the
scalar-lists suite, then have both `it()` blocks in
`scalar-list-mutations.integration.test.ts` call it instead of re-creating the
`withClient`, `migrateContract`, `createTestRuntimeFromClient`,
`createExecutionContext`, and `sqlBuilder` wiring inline. Keep the helper in a
common test-utils module so the setup stays in one place and both mutation tests
remain focused on their assertions.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@packages/3-targets/6-adapters/postgres/src/core/descriptor-meta.ts`:
- Around line 148-150: The arrayOperandToExpr path currently renders raw JS
arrays as untyped ARRAY literals, which breaks empty-array operands in
PostgreSQL array operators. Update arrayOperandToExpr and the related renderer
path in descriptor-meta.ts to preserve the receiver’s element codec or emit an
explicit ::type[] cast when creating LiteralExpr.of(operand), so @>, <@, and &&
always get a typed array literal even for empty arrays.

In `@test/integration/test/scalar-lists/orm-list-read.integration.test.ts`:
- Around line 148-411: The four integration tests repeat the same schema reset,
migration, runtime setup, and SQL-capture boilerplate, and the same pattern
appears in the related scalar-list mutation tests. Extract that shared setup
into a helper such as a reset/migrate step plus a setup function that builds the
`captureMiddleware`, `runtime`, `context`, and `capturedSql` for
`createTestRuntimeFromClient`/`createExecutionContext`, then update each `it()`
block to use the shared helper and keep only the test-specific assertions.

In `@test/integration/test/scalar-lists/scalar-list-ops.types.test-d.ts`:
- Around line 155-160: Move the `@ts-expect-error` marker from the comment above
the update callback to the specific id: assignment line in the
db.public.item.update test so it applies to the actual type error on
fns.arrayAppend(f.tags, 'x') and avoids TS2578; use the db.public.item.update,
fns.arrayAppend, and id property locations to place it precisely.

---

Outside diff comments:
In `@packages/1-framework/1-core/operations/src/index.ts`:
- Around line 44-61: Update the error handling in register so the messages
account for descriptor.self.many as a valid target alongside codecId and traits.
In the register name/descriptor flow, change the zero-target and multi-target
Error text to mention codecId, traits, and many consistently, and make sure any
assertions in operations-registry.test.ts that hardcode the old wording are
updated to match the new messages.

---

Nitpick comments:
In `@packages/2-sql/4-lanes/sql-builder/src/expression.ts`:
- Around line 4-35: The doc comment above ListComparison is too verbose for a
type that already expresses most of its intent. Trim it down to the minimal
rationale needed to understand the overload separation, keeping only the point
about the many discriminant making the list and scalar call signatures disjoint,
and remove the detailed explanation of RequiredTraits and the mechanical
lowering behavior.

In
`@packages/2-sql/4-lanes/sql-builder/test/types/scalar-list-ops.types.test-d.ts`:
- Around line 91-127: The scalar-list overload tests in
scalar-list-ops.types.test-d.ts are missing a negative case for equality-based
comparison on a list whose element codec does not have the equality trait.
Extend the existing `fns.eq`/`fns.ne` coverage in the `scalar comparison` /
`equality builtins` tests by adding an `@ts-expect-error` assertion using a list
fixture whose element type lacks `equality`, mirroring the existing
`fns.gt`/`fns.lt` rejection checks for `flags` and keeping the test names and
`fns`/`f` symbols aligned with the current suite.

In `@packages/3-targets/6-adapters/postgres/src/core/descriptor-meta.ts`:
- Around line 183-189: The codec-resolution logic in has and index is duplicated
and should reuse the existing elementCodecOf helper instead of re-inlining its
body. Update both locations to call elementCodecOf with the same self argument
used in codecOf(self), matching the pattern already used by arrayAppend and
arrayRemove, so the codec selection stays centralized in descriptor-meta.ts.

In
`@test/integration/test/scalar-lists/scalar-list-mutations.integration.test.ts`:
- Around line 168-219: The whole-array replacement integration test currently
only verifies the final row value, so it does not confirm that the raw-array
lowering path was used. Update the test named “replaces the whole list column
with a raw array literal” to capture the SQL emitted by the `runtime.execute`
call that uses `builder.public.item.update({ tags: ['x', 'y'] })`, and assert
the generated SQL reflects a raw array replacement rather than `array_append` or
`array_remove`. Keep the existing round-trip assertion, but add SQL-level
expectations so this test exercises the intended `sqlBuilder`/`rawCodecInferer`
path.
- Around line 105-219: The two scalar list mutation tests duplicate the same
schema reset, migration, and runtime/context setup already used in the sibling
read-ops test file. Extract that repeated setup into a shared helper for the
scalar-lists suite, then have both `it()` blocks in
`scalar-list-mutations.integration.test.ts` call it instead of re-creating the
`withClient`, `migrateContract`, `createTestRuntimeFromClient`,
`createExecutionContext`, and `sqlBuilder` wiring inline. Keep the helper in a
common test-utils module so the setup stays in one place and both mutation tests
remain focused on their assertions.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yml

Review profile: CHILL

Plan: Pro

Run ID: aeb15c2f-8477-403f-8fe6-8cfead5ff0f5

📥 Commits

Reviewing files that changed from the base of the PR and between 284a838 and 3abca2d.

⛔ Files ignored due to path filters (1)
  • test/integration/test/sql-orm-client/fixtures/scalar-lists/generated/contract.d.ts is excluded by !**/generated/**
📒 Files selected for processing (12)
  • packages/1-framework/1-core/operations/src/index.ts
  • packages/1-framework/1-core/operations/test/operations-registry.test.ts
  • packages/2-sql/3-tooling/emitter/src/index.ts
  • packages/2-sql/3-tooling/emitter/test/emitter-hook.storage-column-types.test.ts
  • packages/2-sql/4-lanes/relational-core/src/expression.ts
  • packages/2-sql/4-lanes/sql-builder/src/expression.ts
  • packages/2-sql/4-lanes/sql-builder/test/types/scalar-list-ops.types.test-d.ts
  • packages/3-targets/6-adapters/postgres/src/core/descriptor-meta.ts
  • packages/3-targets/6-adapters/postgres/src/types/operation-types.ts
  • test/integration/test/scalar-lists/orm-list-read.integration.test.ts
  • test/integration/test/scalar-lists/scalar-list-mutations.integration.test.ts
  • test/integration/test/scalar-lists/scalar-list-ops.types.test-d.ts

Comment thread packages/3-targets/6-adapters/postgres/src/core/descriptor-meta.ts
Comment on lines +148 to +411

it(
'filters rows through the native `has` membership op, lowering to `= ANY(...)`',
async () => {
if (!database) throw new Error('database not initialised');

await withClient(database.connectionString, async (client) => {
await client.query('DROP SCHEMA IF EXISTS public CASCADE');
await client.query('CREATE SCHEMA public');
await client.query('DROP SCHEMA IF EXISTS prisma_contract CASCADE');
});
await migrateContract(database.connectionString);

await withClient(database.connectionString, async (client) => {
const capturedSql: string[] = [];
const captureMiddleware: SqlMiddleware = {
name: 'capture-sql',
beforeExecute(plan) {
capturedSql.push(plan.sql);
},
};
const runtime = await createTestRuntimeFromClient(
contract as FrameworkContract<SqlStorage>,
client,
{ verifyMarker: false, middleware: [captureMiddleware] },
);

const context = createExecutionContext<Contract>({
contract,
stack: createSqlExecutionStack({
target: postgresRuntimeTarget,
adapter: postgresRuntimeAdapter,
extensionPacks: [],
}),
});

const db = orm({ runtime, context });
await db.public.Item.create({ id: 1, tags: ['react', 'vue'], scores: [1] });
await db.public.Item.create({ id: 2, tags: ['vue', 'svelte'], scores: [2] });
await db.public.Item.create({ id: 3, tags: ['svelte'], scores: [3] });

const builder = sqlBuilder({ context, rawCodecInferer: postgresRawCodecInferer });
const rows = await runtime.execute(
builder.public.item
.select('id')
.where((f, fns) => fns.has(f.tags, 'vue'))
.orderBy((f) => f.id)
.build(),
);

expect(capturedSql.some((s) => /= ANY\(/.test(s))).toBe(true);
expect(rows).toEqual([{ id: 1 }, { id: 2 }]);
});
},
timeouts.spinUpPpgDev,
);

it(
'filters rows through native array ops (arrayContains @>, containedBy <@, overlaps &&)',
async () => {
if (!database) throw new Error('database not initialised');

await withClient(database.connectionString, async (client) => {
await client.query('DROP SCHEMA IF EXISTS public CASCADE');
await client.query('CREATE SCHEMA public');
await client.query('DROP SCHEMA IF EXISTS prisma_contract CASCADE');
});
await migrateContract(database.connectionString);

await withClient(database.connectionString, async (client) => {
const capturedSql: string[] = [];
const captureMiddleware: SqlMiddleware = {
name: 'capture-sql',
beforeExecute(plan) {
capturedSql.push(plan.sql);
},
};
const runtime = await createTestRuntimeFromClient(
contract as FrameworkContract<SqlStorage>,
client,
{ verifyMarker: false, middleware: [captureMiddleware] },
);

const context = createExecutionContext<Contract>({
contract,
stack: createSqlExecutionStack({
target: postgresRuntimeTarget,
adapter: postgresRuntimeAdapter,
extensionPacks: [],
}),
});

const db = orm({ runtime, context });
await db.public.Item.create({ id: 1, tags: ['react', 'vue'], scores: [1] });
await db.public.Item.create({ id: 2, tags: ['vue', 'svelte'], scores: [2] });
await db.public.Item.create({ id: 3, tags: ['svelte'], scores: [3] });

const builder = sqlBuilder({ context, rawCodecInferer: postgresRawCodecInferer });

const within = await runtime.execute(
builder.public.item
.select('id')
.where((f, fns) => fns.containedBy(f.tags, ['vue', 'svelte']))
.orderBy((f) => f.id)
.build(),
);
expect(capturedSql.some((s) => s.includes('<@'))).toBe(true);
expect(capturedSql.some((s) => s.includes('ARRAY['))).toBe(true);
expect(within).toEqual([{ id: 2 }, { id: 3 }]);

const overlapping = await runtime.execute(
builder.public.item
.select('id')
.where((f, fns) => fns.overlaps(f.tags, ['react']))
.orderBy((f) => f.id)
.build(),
);
expect(capturedSql.some((s) => s.includes('&&'))).toBe(true);
expect(overlapping).toEqual([{ id: 1 }]);

const superset = await runtime.execute(
builder.public.item
.select('id')
.where((f, fns) => fns.arrayContains(f.tags, ['svelte']))
.orderBy((f) => f.id)
.build(),
);
expect(capturedSql.some((s) => s.includes('@>'))).toBe(true);
expect(capturedSql.some((s) => s.includes('ARRAY['))).toBe(true);
expect(superset).toEqual([{ id: 2 }, { id: 3 }]);
});
},
timeouts.spinUpPpgDev,
);

it(
'compares whole lists through the eq/gt comparison builtins (FR16)',
async () => {
if (!database) throw new Error('database not initialised');

await withClient(database.connectionString, async (client) => {
await client.query('DROP SCHEMA IF EXISTS public CASCADE');
await client.query('CREATE SCHEMA public');
await client.query('DROP SCHEMA IF EXISTS prisma_contract CASCADE');
});
await migrateContract(database.connectionString);

await withClient(database.connectionString, async (client) => {
const capturedSql: string[] = [];
const captureMiddleware: SqlMiddleware = {
name: 'capture-sql',
beforeExecute(plan) {
capturedSql.push(plan.sql);
},
};
const runtime = await createTestRuntimeFromClient(
contract as FrameworkContract<SqlStorage>,
client,
{ verifyMarker: false, middleware: [captureMiddleware] },
);

const context = createExecutionContext<Contract>({
contract,
stack: createSqlExecutionStack({
target: postgresRuntimeTarget,
adapter: postgresRuntimeAdapter,
extensionPacks: [],
}),
});

const db = orm({ runtime, context });
await db.public.Item.create({ id: 1, tags: ['react', 'vue'], scores: [1] });
await db.public.Item.create({ id: 2, tags: ['svelte'], scores: [2] });
await db.public.Item.create({ id: 3, tags: ['react', 'vue'], scores: [3] });

const builder = sqlBuilder({ context, rawCodecInferer: postgresRawCodecInferer });

const equal = await runtime.execute(
builder.public.item
.select('id')
.where((f, fns) => fns.eq(f.tags, ['react', 'vue']))
.orderBy((f) => f.id)
.build(),
);
expect(capturedSql.some((s) => /"tags" = \$\d+/.test(s))).toBe(true);
expect(equal).toEqual([{ id: 1 }, { id: 3 }]);

const greater = await runtime.execute(
builder.public.item
.select('id')
.where((f, fns) => fns.gt(f.tags, ['react']))
.orderBy((f) => f.id)
.build(),
);
expect(capturedSql.some((s) => /"tags" > \$\d+/.test(s))).toBe(true);
// ['react','vue'] and ['svelte'] both sort lexicographically after ['react']
expect(greater).toEqual([{ id: 1 }, { id: 2 }, { id: 3 }]);
});
},
timeouts.spinUpPpgDev,
);

it(
'computes list length (cardinality) and 1-based nullable element access',
async () => {
if (!database) throw new Error('database not initialised');

await withClient(database.connectionString, async (client) => {
await client.query('DROP SCHEMA IF EXISTS public CASCADE');
await client.query('CREATE SCHEMA public');
await client.query('DROP SCHEMA IF EXISTS prisma_contract CASCADE');
});
await migrateContract(database.connectionString);

await withClient(database.connectionString, async (client) => {
const capturedSql: string[] = [];
const captureMiddleware: SqlMiddleware = {
name: 'capture-sql',
beforeExecute(plan) {
capturedSql.push(plan.sql);
},
};
const runtime = await createTestRuntimeFromClient(
contract as FrameworkContract<SqlStorage>,
client,
{ verifyMarker: false, middleware: [captureMiddleware] },
);

const context = createExecutionContext<Contract>({
contract,
stack: createSqlExecutionStack({
target: postgresRuntimeTarget,
adapter: postgresRuntimeAdapter,
extensionPacks: [],
}),
});

const db = orm({ runtime, context });
await db.public.Item.create({ id: 1, tags: ['react', 'vue'], scores: [10, 20] });
await db.public.Item.create({ id: 2, tags: ['svelte'], scores: [30] });

const builder = sqlBuilder({ context, rawCodecInferer: postgresRawCodecInferer });
const rows = await runtime.execute(
builder.public.item
.select((f, fns) => ({
id: f.id,
len: fns.length(f.tags),
firstTag: fns.index(f.tags, 1),
secondScore: fns.index(f.scores, 2),
}))
.orderBy((f) => f.id)
.build(),
);

expect(capturedSql.some((s) => s.includes('cardinality('))).toBe(true);
expect(capturedSql.some((s) => /\[\$\d+/.test(s))).toBe(true);
expect(rows).toEqual([
{ id: 1, len: 2, firstTag: 'react', secondScore: 20 },
{ id: 2, len: 1, firstTag: 'svelte', secondScore: null },
]);
});
},
timeouts.spinUpPpgDev,
);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win

Extract shared setup to remove ~4x duplicated boilerplate.

Each it() block repeats an identical sequence: drop/recreate public schema, drop prisma_contract schema, migrateContract, build capturedSql/captureMiddleware, create runtime, and create context. This is copy-pasted four times (and again in scalar-list-mutations.integration.test.ts). Extracting a shared helper (e.g. a beforeEach that resets the schema/migrates, plus a setupCapturingRuntime(client) helper returning { runtime, context, capturedSql, builder }) would materially cut duplication and make each test focus on its actual assertions.

♻️ Sketch of a shared helper
+async function resetSchema(connectionString: string): Promise<void> {
+  await withClient(connectionString, async (client) => {
+    await client.query('DROP SCHEMA IF EXISTS public CASCADE');
+    await client.query('CREATE SCHEMA public');
+    await client.query('DROP SCHEMA IF EXISTS prisma_contract CASCADE');
+  });
+}
+
+function setupCapturingRuntime(client: Client) {
+  const capturedSql: string[] = [];
+  const captureMiddleware: SqlMiddleware = {
+    name: 'capture-sql',
+    beforeExecute(plan) {
+      capturedSql.push(plan.sql);
+    },
+  };
+  return { capturedSql, captureMiddleware };
+}

Then each it() becomes: await resetSchema(...); await migrateContract(...); await withClient(..., async (client) => { const { capturedSql, captureMiddleware } = setupCapturingRuntime(client); ... }).

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@test/integration/test/scalar-lists/orm-list-read.integration.test.ts` around
lines 148 - 411, The four integration tests repeat the same schema reset,
migration, runtime setup, and SQL-capture boilerplate, and the same pattern
appears in the related scalar-list mutation tests. Extract that shared setup
into a helper such as a reset/migrate step plus a setup function that builds the
`captureMiddleware`, `runtime`, `context`, and `capturedSql` for
`createTestRuntimeFromClient`/`createExecutionContext`, then update each `it()`
block to use the shared helper and keep only the test-specific assertions.

Comment on lines +155 to +160
test('a list mutator is rejected for a scalar column', () => {
// @ts-expect-error -- id is a scalar int column; a text list mutator is not assignable
db.public.item.update((f, fns) => ({
id: fns.arrayAppend(f.tags, 'x'),
}));
});

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== file snippet =="
sed -n '130,175p' test/integration/test/scalar-lists/scalar-list-ops.types.test-d.ts

echo
echo "== search for update/arrayAppend definitions =="
rg -n "arrayAppend|update\\(" test/integration src packages . -g '!**/node_modules/**' -g '!**/dist/**' -g '!**/build/**' | head -n 200

Repository: prisma/prisma-next

Length of output: 36280


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== file size =="
wc -l test/integration/test/scalar-lists/scalar-list-ops.types.test-d.ts

echo
echo "== snippet around target =="
nl -ba test/integration/test/scalar-lists/scalar-list-ops.types.test-d.ts | sed -n '145,165p'

echo
echo "== locate relevant symbols =="
rg -n "arrayAppend|arrayPrepend|update\\s*\\(" test/integration test src packages -g '!**/node_modules/**' -g '!**/dist/**' -g '!**/build/**' | head -n 200

Repository: prisma/prisma-next

Length of output: 312


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Try to locate the file that defines the database item update shape and array mutators.
for f in $(git ls-files | rg '(\.ts|\.d\.ts|\.tsx)$'); do
  if rg -q "arrayAppend|arrayPrepend|update\\s*\\(" "$f"; then
    echo "### $f"
    rg -n "arrayAppend|arrayPrepend|update\\s*\\(" "$f"
  fi
done | head -n 200

Repository: prisma/prisma-next

Length of output: 13573


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Read only the target test and inspect nearby declarations/types that affect the error location.
echo "== target test snippet =="
nl -ba test/integration/test/scalar-lists/scalar-list-ops.types.test-d.ts | sed -n '145,165p'

echo
echo "== nearby update/mutator declarations =="
rg -n -C 2 "arrayAppend|update\\s*\\(" test/integration/test/scalar-lists/scalar-list-ops.types.test-d.ts test/integration/test -g '!**/node_modules/**' | head -n 200

Repository: prisma/prisma-next

Length of output: 222


🏁 Script executed:

#!/bin/bash
set -euo pipefail

cat >/tmp/ts-expect-error-placement.ts <<'TS'
declare function fn(arg: { id: number }): void;
declare const bad: string;

// case 1: directive above the call line
// `@ts-expect-error`
fn({
  id: bad,
});

// case 2: directive above the offending property line
fn({
  // `@ts-expect-error`
  id: bad,
});
TS

if command -v tsc >/dev/null 2>&1; then
  tsc --noEmit --pretty false /tmp/ts-expect-error-placement.ts
else
  echo "tsc not available"
fi

Repository: prisma/prisma-next

Length of output: 367


Move @ts-expect-error to the id: line
@ts-expect-error only applies to the next line, so the current placement leaves the assignment error on id: fns.arrayAppend(f.tags, 'x') unsuppressed and can also trigger TS2578.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@test/integration/test/scalar-lists/scalar-list-ops.types.test-d.ts` around
lines 155 - 160, Move the `@ts-expect-error` marker from the comment above the
update callback to the specific id: assignment line in the db.public.item.update
test so it applies to the actual type error on fns.arrayAppend(f.tags, 'x') and
avoids TS2578; use the db.public.item.update, fns.arrayAppend, and id property
locations to place it precisely.

…builtins (FR16) [slice 3 D-redo]

Revert the Dispatch D edit to the shared BuiltinFunctions type: eq/ne/gt/gte/lt/lte are restored to their scalar-only signatures and the sql-builder runtime is untouched. Instead, register six list-comparison ops (eq/ne/gt/lt/gte/lte) in the postgres adapter alongside has/arrayContains, so the Functions = BuiltinFunctions & DeriveExtFunctions intersection widens fns.<op> into an overload: scalar calls resolve via the builtin, whole-list calls via the registry op. Element gating is enforced through CodecIdsWithTrait (equality for eq/ne, order for the ordering ops); the ops have {many: true} self specs so they never surface on the ORM scalar-comparison accessor.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Signed-off-by: Serhii Tatarintsev <tatarintsev@prisma.io>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant