Skip to content

feat(postgres): make ilike adapter-specific via trait-targeted operations#330

Open
SevInf wants to merge 1 commit intomainfrom
worktree/fix-ilike
Open

feat(postgres): make ilike adapter-specific via trait-targeted operations#330
SevInf wants to merge 1 commit intomainfrom
worktree/fix-ilike

Conversation

@SevInf
Copy link
Copy Markdown
Contributor

@SevInf SevInf commented Apr 10, 2026

closes TML-2238

Intent

ilike (case-insensitive LIKE) is a PostgreSQL-specific operator that has no SQLite equivalent. Until now it lived in core as a BinaryOp / ComparisonMethod, available on every textual field regardless of target. SQLite threw a runtime error when it encountered an ilike AST node. This PR moves ilike to the postgres adapter so it is only available at the type level for postgres contracts — SQLite contracts never see it.

The deeper change: operation args can now target by codec trait (e.g. traits: 'textual') in addition to exact codecId. This lets a single adapter-provided operation match every codec that carries a given trait, without enumerating codec IDs.

Change map

Area What changed
Core operations ParamSpec gains optional traits (runtime: readonly string[]). New ReturnSpec (required codecId) split from ParamSpec for OperationEntry.returns.
Contract types New QueryOperationArgSpec with optional traits: string (type-level union). QueryOperationTypeEntry.args uses it.
SQL builder New TraitField / FieldSpec scope types. Expression<T> broadened to FieldSpec. ResolveExtArg + CodecIdsWithTrait resolve trait args to the union of matching codec IDs.
ORM client types ilike removed from ComparisonMethodFns and COMPARISON_METHODS_META. New OpMatchesField helper does codecId-exact OR trait-subset matching. IsBooleanReturn detects predicate ops → return AnyExpression instead of ComparisonMethods.
ORM client runtime Model accessor builds opsByTrait index alongside opsByCodecId. createExtensionMethodFactory returns OperationExpr directly for boolean-returning ops.
Postgres adapter New postgresQueryOperations array with ilike descriptor (traits: ['textual'], template {{self}} ILIKE {{arg0}}). queryOperations() wired into runtime descriptor. New operation-types export. ilike removed from operatorMap.
SQLite adapter Runtime ilike throw guard and operatorMap entry removed.
AST 'ilike' removed from BinaryOp union and BinaryExpr.ilike() static method.
tsdown / sql-contract-ts customExports hook preserves ./schema-sql JSON export that tsdown's export-sync was stripping.

The story

  1. Trait-targeted argsParamSpec and QueryOperationArgSpec gain an optional traits field. At the type level it's a string union ('textual'); at runtime it's an array (['textual']). This is the foundational primitive that lets operations match by codec capability rather than identity.

  2. ilike leaves core — Removed from BinaryOp, BinaryExpr, ComparisonMethodFns, and COMPARISON_METHODS_META. The AST, adapters, and ORM no longer treat it as a built-in comparison.

  3. Postgres adapter registers ilike — A new postgresQueryOperations array defines ilike with { traits: ['textual'] } as self arg, pg/text@1 as pattern arg, pg/bool@1 as return, and infix template {{self}} ILIKE {{arg0}}. The adapter's runtime descriptor exposes queryOperations(). Contract emission picks it up via types.queryOperationTypes in descriptor meta.

  4. Type-level matchingOpMatchesField checks whether an operation's self arg matches a field: either exact codecId match (existing pgvector path) or trait-subset match (new). FieldOperations uses this to surface ilike on text fields in postgres contracts and hide it from sqlite contracts.

  5. Predicate detectionIsBooleanReturn checks whether the return codec has the 'boolean' trait. If so, QueryOperationMethod returns AnyExpression directly (a WHERE predicate), not ComparisonMethods<...>. At runtime, createExtensionMethodFactory returns the OperationExpr directly for boolean-returning ops. This means user.name.ilike('%alice%') is a predicate expression, not a chainable value.

  6. SQL builder supportExpression<T> now accepts FieldSpec (= ScopeField | TraitField). ResolveExtArg maps trait-targeted args to Expression<{ codecId: CodecIdsWithTrait<CT, T> }>, so fns.ilike(f.name, '%test%') accepts any textual column expression.

Behavior changes & evidence

  • Postgres text fields have ilike: extension-operations.test-d.tsPostAccessor['title'] has ilike property, returns AnyExpression.
  • Non-textual fields don't have ilike: Same file — PostAccessor['views'] and PostAccessor['embedding'] do not expose ilike.
  • SQLite contracts don't have ilike: No QueryOperationTypes from the adapter → FieldOperations finds nothing → field type has no ilike.
  • SQL builder integration: extension-functions.test.ts — ilike filters case-insensitively against real postgres, returns correct rows.
  • SQL builder types: extension-functions.test-d.tsfns.ilike(f.name, '%alice%') typechecks and returns Expression<BooleanCodecType>.
  • pgvector unchanged: Existing codecId-based matching still works — cosineDistance/cosineSimilarity tests pass without changes.

Compatibility / migration / risk

  • Breaking for SQLite ilike users: Anyone calling .ilike() on a SQLite contract field will get a compile error instead of a runtime error. This is the intended improvement.
  • ParamSpec.codecId is now optional: Downstream code that assumes ParamSpec.codecId is always present needs adjustment. ReturnSpec (new) guarantees codecId for return types.
  • Extension operations with boolean returns: Any future extension operation returning a pg/bool@1 codec will automatically be treated as a predicate (returns AnyExpression). This is the correct behavior for boolean-returning operations.

Follow-ups / open questions

  • The emitter pipeline should be validated end-to-end: when a new contract is emitted with the postgres adapter, QueryOperationTypes should include PgAdapterQueryOps automatically (the descriptor meta is wired, but no e2e emit test exists yet).
  • Future postgres-specific operators (~, ~*, SIMILAR TO) can follow the same pattern.

Non-goals / intentionally out of scope

  • No changes to the contract emitter implementation — only test fixtures were updated.
  • No OperationTypes (codec-keyed) for the postgres adapter — only QueryOperationTypes (flat) is needed since trait matching handles the codec-to-operation mapping.

Summary by CodeRabbit

  • New Features

    • Postgres adapter exposes an ilike query operation; adapter-level query ops are now discoverable.
  • Improvements

    • Field specs support trait-based selection; parameter and return metadata are stricter for clearer typing.
    • Extension-function argument typing enhanced to resolve codec- or trait-based argument shapes.
  • Behavioral Changes

    • Global ilike operator removed from core; adapters handle ilike rendering and availability.
  • Tests

    • Updated tests and type assertions to reflect operator relocation and typing changes.

@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Apr 10, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Decouples return metadata from ParamSpec; introduces trait-based Field/Arg specs; removes AST-level ilike operator; exposes ilike as an adapter-provided, trait-gated query operation; updates adapters, ORM accessor/type logic, and tests to reflect trait-based operation dispatch.

Changes

Cohort / File(s) Summary
Core operations types
packages/1-framework/1-core/operations/src/index.ts
Added ReturnSpec; made ParamSpec.nullable required and added optional codecId/traits; OperationEntry.returns now uses ReturnSpec.
Contract types & re-exports
packages/2-sql/1-core/contract/src/types.ts, packages/2-sql/1-core/contract/src/exports/types.ts
Added QueryOperationArgSpec (nullable + optional codecId/traits); QueryOperationTypeEntry.args now readonly QueryOperationArgSpec[]; re-exported the type.
Scope & expression types
packages/2-sql/4-lanes/sql-builder/src/scope.ts, packages/2-sql/4-lanes/sql-builder/src/expression.ts
Added TraitField and FieldSpec union; broadened Expression generic to FieldSpec; introduced ResolveExtArg and codec-trait resolution logic for extension function argument typing.
Relational AST & tests
packages/2-sql/4-lanes/relational-core/src/ast/types.ts, packages/2-sql/4-lanes/relational-core/test/ast/predicate.test.ts
Removed 'ilike' from BinaryOp and deleted BinaryExpr.ilike() constructor; removed corresponding test case.
Extension function tests (runtime & types)
packages/2-sql/4-lanes/sql-builder/test/integration/extension-functions.test.ts, packages/2-sql/4-lanes/sql-builder/test/playground/extension-functions.test-d.ts
Added integration and type-level tests exercising ilike as an adapter-provided query operation.
ORM accessor & types
packages/3-extensions/sql-orm-client/src/model-accessor.ts, packages/3-extensions/sql-orm-client/src/types.ts
Indexed operations by trait (opsByTrait) and combined codec/trait-derived ops; removed ilike from comparison-method surface; added trait-aware return/type matching and updated extension factory behavior and typings.
ORM tests
packages/3-extensions/sql-orm-client/test/*
Adjusted tests to treat ilike as an extension operation: removed/rewrote assertions expecting AST ilike, added type-level assertions for ilike presence on textual fields, and updated related messages.
Postgres adapter: descriptor, types & exports
packages/3-targets/6-adapters/postgres/src/core/descriptor-meta.ts, .../src/types/operation-types.ts, .../src/exports/operation-types.ts, .../src/exports/runtime.ts, .../src/core/adapter.ts, .../tsdown.config.ts, .../package.json
Added postgresQueryOperations including an ilike query operation descriptor; added QueryOperationTypes export and ./operation-types package subpath; runtime descriptor now exposes queryOperations; removed ilike mapping from binary operator render map.
SQLite adapter & tests
packages/3-targets/6-adapters/sqlite/src/core/adapter.ts, packages/3-targets/6-adapters/sqlite/test/adapter.test.ts
Removed explicit rejection of ilike in renderer and deleted the negative test that asserted SQLite throws for ILIKE.
Consumer fixtures / examples
examples/prisma-next-demo/src/prisma/contract.d.ts, test/integration/test/fixtures/contract.d.ts
Updated exported QueryOperationTypes to include Postgres adapter PgAdapterQueryOps so consumer contracts include adapter-provided query operations.

Sequence Diagram

sequenceDiagram
    participant User as User Code
    participant MA as Model Accessor
    participant Ops as Operation Index
    participant RE as ResolveExtArg
    participant Expr as Expression Builder
    participant Adapter as Adapter Query Ops

    User->>MA: Access field (e.g., post.title)
    MA->>Ops: Lookup ops by codecId
    MA->>Ops: Lookup ops by trait(s)
    Ops-->>MA: Return combined ops list

    User->>MA: Call extension method (e.g., ilike(pattern))
    MA->>RE: Resolve arg spec (codecId or traits)
    RE-->>MA: Return resolved arg/expression types
    MA->>Adapter: Select query operation descriptor (ilike)
    MA->>Expr: Build OperationExpr node
    Expr-->>User: Return Boolean Expression
Loading

Estimated Code Review Effort

🎯 4 (Complex) | ⏱️ ~55 minutes

Poem

🐰 I nibbled traits and hopped through code,

ilike leapt out and found a new road,
Returns and args took tidy new cues,
Adapters sang, and tests chased the news,
A rabbit cheers these tiny, smart moves!

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title precisely identifies the main change: moving ilike from core to Postgres adapter via trait-targeted operations. It is specific, concise, and clearly summarizes the primary purpose of the changeset.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch worktree/fix-ilike

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 and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@packages/3-extensions/sql-orm-client/src/model-accessor.ts`:
- Around line 157-160: The code in model-accessor.ts currently maps userArgSpecs
to ParamRef.of(args[i], ...) but only forwards codecId, losing trait-based
metadata; update the astArgs mapping so ParamRef.of receives the trait metadata
when codecId is absent (e.g., include argSpec.traits or traitTarget in the
options object you pass to ParamRef.of), and add a minimal fail-fast guard in
the same mapping (inside the userArgSpecs.map) to throw a clear error if an
argSpec has neither codecId nor traits so unsupported untyped params fail early;
refer to the symbols userArgSpecs, astArgs, ParamRef.of, and
argSpec.traits/traitTarget when making the change.

In `@packages/3-extensions/sql-orm-client/src/types.ts`:
- Around line 236-247: QueryOperationMethod currently maps user args via
MapArgsToJsTypes which only uses CodecArgJsType and therefore yields unknown for
args that specify traits; update the mapping so trait-based args resolve to
their JS types by reusing the trait-aware resolution logic used in ResolveExtArg
(as in sql-builder) and/or OpMatchesField. Specifically, modify MapArgsToJsTypes
(and/or CodecArgJsType) to detect ext-arg shapes with traits and apply the same
ResolveExtArg/OpMatchesField resolution path so QueryOperationMethod's parameter
types become the concrete JS types rather than unknown; keep function/type names
QueryOperationMethod, MapArgsToJsTypes, CodecArgJsType, ResolveExtArg and
OpMatchesField as reference points when implementing the fix.
🪄 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: 6319e707-06b1-4abd-bffa-e05389f7bcea

📥 Commits

Reviewing files that changed from the base of the PR and between 5ab3375 and e22d894.

⛔ Files ignored due to path filters (2)
  • packages/2-sql/4-lanes/sql-builder/test/fixtures/generated/contract.d.ts is excluded by !**/generated/**
  • packages/3-extensions/sql-orm-client/test/fixtures/generated/contract.d.ts is excluded by !**/generated/**
📒 Files selected for processing (28)
  • packages/1-framework/1-core/operations/src/index.ts
  • packages/2-sql/1-core/contract/src/exports/types.ts
  • packages/2-sql/1-core/contract/src/types.ts
  • packages/2-sql/2-authoring/contract-ts/package.json
  • packages/2-sql/2-authoring/contract-ts/tsdown.config.ts
  • packages/2-sql/4-lanes/relational-core/src/ast/types.ts
  • packages/2-sql/4-lanes/relational-core/test/ast/predicate.test.ts
  • packages/2-sql/4-lanes/sql-builder/src/expression.ts
  • packages/2-sql/4-lanes/sql-builder/src/scope.ts
  • packages/2-sql/4-lanes/sql-builder/test/integration/extension-functions.test.ts
  • packages/2-sql/4-lanes/sql-builder/test/playground/extension-functions.test-d.ts
  • packages/3-extensions/sql-orm-client/src/model-accessor.ts
  • packages/3-extensions/sql-orm-client/src/types.ts
  • packages/3-extensions/sql-orm-client/test/extension-operations.test-d.ts
  • packages/3-extensions/sql-orm-client/test/filters.test.ts
  • packages/3-extensions/sql-orm-client/test/generated-contract-types.test-d.ts
  • packages/3-extensions/sql-orm-client/test/model-accessor.test.ts
  • packages/3-extensions/sql-orm-client/test/orm.types.test-d.ts
  • packages/3-extensions/sql-orm-client/test/where-binding.test.ts
  • packages/3-targets/6-adapters/postgres/package.json
  • packages/3-targets/6-adapters/postgres/src/core/adapter.ts
  • packages/3-targets/6-adapters/postgres/src/core/descriptor-meta.ts
  • packages/3-targets/6-adapters/postgres/src/exports/operation-types.ts
  • packages/3-targets/6-adapters/postgres/src/exports/runtime.ts
  • packages/3-targets/6-adapters/postgres/src/types/operation-types.ts
  • packages/3-targets/6-adapters/postgres/tsdown.config.ts
  • packages/3-targets/6-adapters/sqlite/src/core/adapter.ts
  • packages/3-targets/6-adapters/sqlite/test/adapter.test.ts
💤 Files with no reviewable changes (6)
  • packages/3-targets/6-adapters/postgres/src/core/adapter.ts
  • packages/2-sql/4-lanes/relational-core/test/ast/predicate.test.ts
  • packages/3-extensions/sql-orm-client/test/where-binding.test.ts
  • packages/3-targets/6-adapters/sqlite/src/core/adapter.ts
  • packages/3-targets/6-adapters/sqlite/test/adapter.test.ts
  • packages/3-extensions/sql-orm-client/test/model-accessor.test.ts

Comment thread packages/3-extensions/sql-orm-client/src/model-accessor.ts
Comment thread packages/3-extensions/sql-orm-client/src/types.ts
@SevInf SevInf force-pushed the worktree/fix-ilike branch from e22d894 to 128c098 Compare April 10, 2026 11:57
@pkg-pr-new
Copy link
Copy Markdown

pkg-pr-new bot commented Apr 10, 2026

Open in StackBlitz

@prisma-next/mongo-runtime

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

@prisma-next/family-mongo

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

@prisma-next/sql-runtime

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

@prisma-next/family-sql

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

@prisma-next/middleware-telemetry

npm i https://pkg.pr.new/prisma/prisma-next/@prisma-next/middleware-telemetry@330

@prisma-next/mongo

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

@prisma-next/extension-paradedb

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

@prisma-next/extension-pgvector

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

@prisma-next/postgres

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

@prisma-next/sql-orm-client

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

@prisma-next/sqlite

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

@prisma-next/target-mongo

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

@prisma-next/adapter-mongo

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

@prisma-next/driver-mongo

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

@prisma-next/contract

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

@prisma-next/utils

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

@prisma-next/config

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

@prisma-next/errors

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

@prisma-next/framework-components

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

@prisma-next/operations

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

@prisma-next/contract-authoring

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

@prisma-next/ids

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

@prisma-next/psl-parser

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

@prisma-next/psl-printer

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

@prisma-next/cli

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

@prisma-next/emitter

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

@prisma-next/migration-tools

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

prisma-next

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

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

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

@prisma-next/runtime-executor

npm i https://pkg.pr.new/prisma/prisma-next/@prisma-next/runtime-executor@330

@prisma-next/mongo-codec

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

@prisma-next/mongo-contract

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

@prisma-next/mongo-value

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

@prisma-next/mongo-contract-psl

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

@prisma-next/mongo-contract-ts

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

@prisma-next/mongo-emitter

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

@prisma-next/mongo-schema-ir

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

@prisma-next/mongo-query-ast

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

@prisma-next/mongo-orm

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

@prisma-next/mongo-pipeline-builder

npm i https://pkg.pr.new/prisma/prisma-next/@prisma-next/mongo-pipeline-builder@330

@prisma-next/mongo-lowering

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

@prisma-next/mongo-wire

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

@prisma-next/sql-contract

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

@prisma-next/sql-errors

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

@prisma-next/sql-operations

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

@prisma-next/sql-schema-ir

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

@prisma-next/sql-contract-psl

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

@prisma-next/sql-contract-ts

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

@prisma-next/sql-contract-emitter

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

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

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

@prisma-next/sql-relational-core

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

@prisma-next/sql-builder

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

@prisma-next/target-postgres

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

@prisma-next/target-sqlite

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

@prisma-next/adapter-postgres

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

@prisma-next/adapter-sqlite

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

@prisma-next/driver-postgres

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

@prisma-next/driver-sqlite

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

commit: bc433ef

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

♻️ Duplicate comments (1)
packages/3-extensions/sql-orm-client/src/model-accessor.ts (1)

157-160: ⚠️ Potential issue | 🟠 Major

Trait-targeted non-self args are still erased during AST binding.

Line 159 only forwards codecId into ParamRef. If an extension operation defines a user arg via traits, that metadata is dropped here and the ORM runtime builds an untyped param, so later lowering/binding cannot distinguish the trait-targeted contract anymore. This leaves trait-based args only partially implemented outside the self position.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/3-extensions/sql-orm-client/src/model-accessor.ts` around lines 157
- 160, The mapping that builds astArgs from userArgSpecs drops trait metadata
(only codecId is forwarded), so update the ParamRef construction in the
userArgSpecs -> astArgs code path to include argSpec.traits (and any other
metadata needed for trait-targeted non-self args) in the options passed to
ParamRef.of; ensure you read from entry.args / argSpec and pass through codecId
plus traits (or a combined metadata object) so trait-targeted args remain typed
and available for later lowering/binding.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@packages/2-sql/1-core/contract/src/types.ts`:
- Around line 167-170: The QueryOperationArgSpec type currently declares traits
as string which widens the API; change the traits field in QueryOperationArgSpec
to use the canonical codec trait union type used elsewhere (e.g., CodecTrait or
CodecTraits) instead of plain string, preserving readonly and optional semantics
(readonly traits?: CanonicalTraitUnion) so only valid trait members type-check
and typos are caught at compile time; update any imports to reference that union
where QueryOperationArgSpec is declared.

---

Duplicate comments:
In `@packages/3-extensions/sql-orm-client/src/model-accessor.ts`:
- Around line 157-160: The mapping that builds astArgs from userArgSpecs drops
trait metadata (only codecId is forwarded), so update the ParamRef construction
in the userArgSpecs -> astArgs code path to include argSpec.traits (and any
other metadata needed for trait-targeted non-self args) in the options passed to
ParamRef.of; ensure you read from entry.args / argSpec and pass through codecId
plus traits (or a combined metadata object) so trait-targeted args remain typed
and available for later lowering/binding.
🪄 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: cde64526-2ab9-40f9-b10e-82443440065f

📥 Commits

Reviewing files that changed from the base of the PR and between e22d894 and 128c098.

⛔ Files ignored due to path filters (2)
  • packages/2-sql/4-lanes/sql-builder/test/fixtures/generated/contract.d.ts is excluded by !**/generated/**
  • packages/3-extensions/sql-orm-client/test/fixtures/generated/contract.d.ts is excluded by !**/generated/**
📒 Files selected for processing (26)
  • packages/1-framework/1-core/operations/src/index.ts
  • packages/2-sql/1-core/contract/src/exports/types.ts
  • packages/2-sql/1-core/contract/src/types.ts
  • packages/2-sql/4-lanes/relational-core/src/ast/types.ts
  • packages/2-sql/4-lanes/relational-core/test/ast/predicate.test.ts
  • packages/2-sql/4-lanes/sql-builder/src/expression.ts
  • packages/2-sql/4-lanes/sql-builder/src/scope.ts
  • packages/2-sql/4-lanes/sql-builder/test/integration/extension-functions.test.ts
  • packages/2-sql/4-lanes/sql-builder/test/playground/extension-functions.test-d.ts
  • packages/3-extensions/sql-orm-client/src/model-accessor.ts
  • packages/3-extensions/sql-orm-client/src/types.ts
  • packages/3-extensions/sql-orm-client/test/extension-operations.test-d.ts
  • packages/3-extensions/sql-orm-client/test/filters.test.ts
  • packages/3-extensions/sql-orm-client/test/generated-contract-types.test-d.ts
  • packages/3-extensions/sql-orm-client/test/model-accessor.test.ts
  • packages/3-extensions/sql-orm-client/test/orm.types.test-d.ts
  • packages/3-extensions/sql-orm-client/test/where-binding.test.ts
  • packages/3-targets/6-adapters/postgres/package.json
  • packages/3-targets/6-adapters/postgres/src/core/adapter.ts
  • packages/3-targets/6-adapters/postgres/src/core/descriptor-meta.ts
  • packages/3-targets/6-adapters/postgres/src/exports/operation-types.ts
  • packages/3-targets/6-adapters/postgres/src/exports/runtime.ts
  • packages/3-targets/6-adapters/postgres/src/types/operation-types.ts
  • packages/3-targets/6-adapters/postgres/tsdown.config.ts
  • packages/3-targets/6-adapters/sqlite/src/core/adapter.ts
  • packages/3-targets/6-adapters/sqlite/test/adapter.test.ts
💤 Files with no reviewable changes (6)
  • packages/3-targets/6-adapters/sqlite/src/core/adapter.ts
  • packages/3-targets/6-adapters/postgres/src/core/adapter.ts
  • packages/2-sql/4-lanes/relational-core/test/ast/predicate.test.ts
  • packages/3-targets/6-adapters/sqlite/test/adapter.test.ts
  • packages/3-extensions/sql-orm-client/test/model-accessor.test.ts
  • packages/3-extensions/sql-orm-client/test/where-binding.test.ts
✅ Files skipped from review due to trivial changes (8)
  • packages/3-extensions/sql-orm-client/test/orm.types.test-d.ts
  • packages/2-sql/1-core/contract/src/exports/types.ts
  • packages/3-extensions/sql-orm-client/test/generated-contract-types.test-d.ts
  • packages/3-targets/6-adapters/postgres/tsdown.config.ts
  • packages/2-sql/4-lanes/sql-builder/test/integration/extension-functions.test.ts
  • packages/3-targets/6-adapters/postgres/package.json
  • packages/3-targets/6-adapters/postgres/src/exports/operation-types.ts
  • packages/3-targets/6-adapters/postgres/src/core/descriptor-meta.ts
🚧 Files skipped from review as they are similar to previous changes (10)
  • packages/3-extensions/sql-orm-client/test/filters.test.ts
  • packages/3-targets/6-adapters/postgres/src/exports/runtime.ts
  • packages/2-sql/4-lanes/sql-builder/src/scope.ts
  • packages/2-sql/4-lanes/sql-builder/test/playground/extension-functions.test-d.ts
  • packages/3-extensions/sql-orm-client/test/extension-operations.test-d.ts
  • packages/2-sql/4-lanes/relational-core/src/ast/types.ts
  • packages/1-framework/1-core/operations/src/index.ts
  • packages/3-targets/6-adapters/postgres/src/types/operation-types.ts
  • packages/2-sql/4-lanes/sql-builder/src/expression.ts
  • packages/3-extensions/sql-orm-client/src/types.ts

Comment thread packages/2-sql/1-core/contract/src/types.ts
@SevInf SevInf force-pushed the worktree/fix-ilike branch from 128c098 to 4ee59bb Compare April 10, 2026 12:52
Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

♻️ Duplicate comments (1)
packages/3-extensions/sql-orm-client/src/types.ts (1)

242-245: ⚠️ Potential issue | 🟠 Major

Trait-based operation args still degrade to unknown.

QueryOperationMethod now exposes the new trait-targeted operations, but its parameter tuple still comes from MapArgsToJsTypes -> CodecArgJsType, which only understands { codecId: ... }. Any non-self arg declared with { traits: ... } will therefore surface here as unknown, so the ORM method signatures still lose the concrete JS types this PR is trying to preserve.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/3-extensions/sql-orm-client/src/types.ts` around lines 242 - 245,
The method parameter types degrade to unknown because MapArgsToJsTypes and
CodecArgJsType only handle { codecId: ... } shapes and do not resolve args
declared with { traits: ... }; update the type pipeline so QueryOperationMethod
receives resolved JS types for trait-based args by extending CodecArgJsType (or
MapArgsToJsTypes) to detect a { traits: T } shape and map it to the
corresponding codec/JS type via the existing trait-to-codec lookup helper (or
add a small type-level resolver that converts trait names to their codec types),
ensuring MapArgsToJsTypes<...> returns concrete types for args with { traits:
... } instead of unknown so QueryOperationMethod signatures preserve the correct
JS types.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@packages/2-sql/4-lanes/sql-builder/src/expression.ts`:
- Around line 81-90: ResolveExtArg currently maps trait-based arguments to
Expression<...> while codecId-based arguments use ExpressionOrValue<...>,
causing asymmetric API surface; change the trait branch in ResolveExtArg to
allow both expressions and raw values by returning ExpressionOrValue<{ codecId:
CodecIdsWithTrait<CT, T>; nullable: N }, CT> instead of Expression<{ codecId:
CodecIdsWithTrait<CT, T>; nullable: N }>, so both codecId and trait paths accept
the same input range; update the ResolveExtArg conditional that references
"traits: infer T" and "nullable: infer N" to use ExpressionOrValue and keep
CT/CodecIdsWithTrait references intact.

In `@packages/3-extensions/sql-orm-client/src/types.ts`:
- Around line 255-264: The conditional currently treats `codecId` and `traits`
as alternatives, so a matching `codecId` bypasses trait checks; change the
branching in the type conditional so it first checks for the combined shape
(e.g., `Self extends { readonly codecId: CodecId; readonly traits: infer
RequiredTraits extends string }`) and if so ensure `CodecId extends keyof CT ?
CT[CodecId] extends { readonly traits: infer FieldTraits } ? RequiredTraits
extends FieldTraits ? true : false : false : false`; otherwise fall back to the
single-property branches for `codecId` or `traits`. This preserves existing
`CT`, `CodecId`, `Self`, `RequiredTraits`, and `FieldTraits` symbols while
enforcing both constraints when both fields are present.

In `@packages/3-targets/6-adapters/postgres/src/core/descriptor-meta.ts`:
- Around line 131-136: Update the operation type for the ilike operator so its
first argument is non-nullable to match the runtime descriptor: change the
nullable flag for the first ilike arg in the operation-types declaration (the
ilike entry in packages/.../operation-types.ts) from nullable: true/boolean to
nullable: false so it aligns with the runtime postgresQueryOperations descriptor
(ilike entry) and prevents nullable textual fields from being incorrectly typed
as supporting .ilike().

---

Duplicate comments:
In `@packages/3-extensions/sql-orm-client/src/types.ts`:
- Around line 242-245: The method parameter types degrade to unknown because
MapArgsToJsTypes and CodecArgJsType only handle { codecId: ... } shapes and do
not resolve args declared with { traits: ... }; update the type pipeline so
QueryOperationMethod receives resolved JS types for trait-based args by
extending CodecArgJsType (or MapArgsToJsTypes) to detect a { traits: T } shape
and map it to the corresponding codec/JS type via the existing trait-to-codec
lookup helper (or add a small type-level resolver that converts trait names to
their codec types), ensuring MapArgsToJsTypes<...> returns concrete types for
args with { traits: ... } instead of unknown so QueryOperationMethod signatures
preserve the correct JS types.
🪄 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: bfe58c26-5a99-49ef-b0a2-46183837bc3b

📥 Commits

Reviewing files that changed from the base of the PR and between 128c098 and 4ee59bb.

⛔ Files ignored due to path filters (3)
  • packages/2-sql/4-lanes/sql-builder/test/fixtures/generated/contract.d.ts is excluded by !**/generated/**
  • packages/3-extensions/sql-orm-client/test/fixtures/generated/contract.d.ts is excluded by !**/generated/**
  • test/e2e/framework/test/fixtures/generated/contract.d.ts is excluded by !**/generated/**
📒 Files selected for processing (28)
  • examples/prisma-next-demo/src/prisma/contract.d.ts
  • packages/1-framework/1-core/operations/src/index.ts
  • packages/2-sql/1-core/contract/src/exports/types.ts
  • packages/2-sql/1-core/contract/src/types.ts
  • packages/2-sql/4-lanes/relational-core/src/ast/types.ts
  • packages/2-sql/4-lanes/relational-core/test/ast/predicate.test.ts
  • packages/2-sql/4-lanes/sql-builder/src/expression.ts
  • packages/2-sql/4-lanes/sql-builder/src/scope.ts
  • packages/2-sql/4-lanes/sql-builder/test/integration/extension-functions.test.ts
  • packages/2-sql/4-lanes/sql-builder/test/playground/extension-functions.test-d.ts
  • packages/3-extensions/sql-orm-client/src/model-accessor.ts
  • packages/3-extensions/sql-orm-client/src/types.ts
  • packages/3-extensions/sql-orm-client/test/extension-operations.test-d.ts
  • packages/3-extensions/sql-orm-client/test/filters.test.ts
  • packages/3-extensions/sql-orm-client/test/generated-contract-types.test-d.ts
  • packages/3-extensions/sql-orm-client/test/model-accessor.test.ts
  • packages/3-extensions/sql-orm-client/test/orm.types.test-d.ts
  • packages/3-extensions/sql-orm-client/test/where-binding.test.ts
  • packages/3-targets/6-adapters/postgres/package.json
  • packages/3-targets/6-adapters/postgres/src/core/adapter.ts
  • packages/3-targets/6-adapters/postgres/src/core/descriptor-meta.ts
  • packages/3-targets/6-adapters/postgres/src/exports/operation-types.ts
  • packages/3-targets/6-adapters/postgres/src/exports/runtime.ts
  • packages/3-targets/6-adapters/postgres/src/types/operation-types.ts
  • packages/3-targets/6-adapters/postgres/tsdown.config.ts
  • packages/3-targets/6-adapters/sqlite/src/core/adapter.ts
  • packages/3-targets/6-adapters/sqlite/test/adapter.test.ts
  • test/integration/test/fixtures/contract.d.ts
💤 Files with no reviewable changes (6)
  • packages/3-targets/6-adapters/sqlite/test/adapter.test.ts
  • packages/3-targets/6-adapters/postgres/src/core/adapter.ts
  • packages/3-targets/6-adapters/sqlite/src/core/adapter.ts
  • packages/3-extensions/sql-orm-client/test/where-binding.test.ts
  • packages/2-sql/4-lanes/relational-core/test/ast/predicate.test.ts
  • packages/3-extensions/sql-orm-client/test/model-accessor.test.ts
✅ Files skipped from review due to trivial changes (10)
  • packages/3-targets/6-adapters/postgres/src/exports/operation-types.ts
  • packages/3-extensions/sql-orm-client/test/generated-contract-types.test-d.ts
  • packages/2-sql/4-lanes/sql-builder/test/integration/extension-functions.test.ts
  • packages/3-targets/6-adapters/postgres/tsdown.config.ts
  • packages/3-extensions/sql-orm-client/test/orm.types.test-d.ts
  • packages/3-targets/6-adapters/postgres/package.json
  • packages/2-sql/4-lanes/sql-builder/src/scope.ts
  • packages/2-sql/4-lanes/sql-builder/test/playground/extension-functions.test-d.ts
  • packages/3-targets/6-adapters/postgres/src/types/operation-types.ts
  • packages/2-sql/1-core/contract/src/exports/types.ts
🚧 Files skipped from review as they are similar to previous changes (7)
  • packages/3-extensions/sql-orm-client/test/filters.test.ts
  • packages/3-targets/6-adapters/postgres/src/exports/runtime.ts
  • packages/3-extensions/sql-orm-client/test/extension-operations.test-d.ts
  • packages/2-sql/1-core/contract/src/types.ts
  • packages/1-framework/1-core/operations/src/index.ts
  • packages/2-sql/4-lanes/relational-core/src/ast/types.ts
  • packages/3-extensions/sql-orm-client/src/model-accessor.ts

Comment thread packages/2-sql/4-lanes/sql-builder/src/expression.ts Outdated
Comment thread packages/3-extensions/sql-orm-client/src/types.ts
Comment thread packages/3-targets/6-adapters/postgres/src/core/descriptor-meta.ts
@SevInf SevInf force-pushed the worktree/fix-ilike branch from 4ee59bb to 5195631 Compare April 10, 2026 13:04
Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

♻️ Duplicate comments (2)
packages/2-sql/1-core/contract/src/types.ts (1)

167-170: ⚠️ Potential issue | 🟠 Major

Keep traits on the canonical codec-trait union.

readonly traits?: string makes misspellings type-check and weakens the compile-time gating this PR is adding for trait-targeted operations. This should stay aligned with the shared codec-trait type instead of widening to arbitrary strings.

Verify which canonical trait alias already exists before changing this field:

#!/bin/bash
set -euo pipefail

echo "== current QueryOperationArgSpec =="
sed -n '167,171p' packages/2-sql/1-core/contract/src/types.ts

echo
echo "== candidate canonical trait aliases in repo =="
rg -n --type=ts '\b(CodecTrait|CodecTraits)\b' packages
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/2-sql/1-core/contract/src/types.ts` around lines 167 - 170, The
QueryOperationArgSpec currently declares readonly traits?: string which weakens
type safety; change this field to use the existing canonical codec-trait alias
(e.g. CodecTrait or CodecTraits) used across the repo instead of plain string.
Locate the type QueryOperationArgSpec in types.ts and replace traits?: string
with the correct canonical type (nullable/optional as appropriate), but first
confirm which alias name is present in the codebase (search for CodecTrait or
CodecTraits) and import or reference that alias so the field aligns with the
shared codec-trait union.
packages/2-sql/4-lanes/sql-builder/src/expression.ts (1)

81-90: ⚠️ Potential issue | 🟡 Minor

Trait-targeted args should accept literals too.

The codecId branch returns ExpressionOrValue, but the trait branch only accepts Expression. That makes trait-only args stricter than codecId-targeted args and blocks raw literals for the same logical operation surface.

Suggested type fix
 type ResolveExtArg<Arg, CT extends Record<string, { readonly input: unknown }>> = Arg extends {
   readonly codecId: infer CId extends string;
   readonly nullable: infer N extends boolean;
 }
   ? ExpressionOrValue<{ codecId: CId; nullable: N }, CT>
   : Arg extends {
         readonly traits: infer T extends string;
         readonly nullable: infer N extends boolean;
       }
-    ? Expression<{ codecId: CodecIdsWithTrait<CT, T>; nullable: N }>
+    ? ExpressionOrValue<{ codecId: CodecIdsWithTrait<CT, T>; nullable: N }, CT>
     : never;

Verify whether any current operation types rely on trait-only args:

#!/bin/bash
set -euo pipefail

echo "== current ResolveExtArg trait branch =="
sed -n '81,91p' packages/2-sql/4-lanes/sql-builder/src/expression.ts

echo
echo "== trait-based operation arg specs =="
rg -n -C2 'traits:' packages/3-targets packages/2-sql test --type=ts
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/2-sql/4-lanes/sql-builder/src/expression.ts` around lines 81 - 90,
The trait-only branch of the ResolveExtArg type is too strict and disallows raw
literals; update the second conditional branch in ResolveExtArg so it returns
the same ExpressionOrValue wrapper as the codecId branch (using
CodecIdsWithTrait<CT, T> and the inferred N) instead of plain Expression, so
trait-targeted args accept literals; locate ResolveExtArg in expression.ts and
replace the trait branch's return type to ExpressionOrValue<{ codecId:
CodecIdsWithTrait<CT, T>; nullable: N }, CT> (keeping the existing inference of
T and N).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@packages/3-extensions/sql-orm-client/src/model-accessor.ts`:
- Around line 59-75: The current indexing and lookup widen trait matching by
indexing ops under each individual trait and later unioning them, and the else
if prevents trait indexing when a self arg has both codecId and traits; update
the code so codecId and trait paths run independently (remove the else if
branching) and, when selecting trait-based candidates from opsByTrait for a
field, only include operations whose self.traits are a subset of the field’s
traits by filtering with self.traits.every(t => field.traits.includes(t))
(reference opsByCodecId, opsByTrait, and the self.traits usage and the matching
logic that currently unions trait candidates).

---

Duplicate comments:
In `@packages/2-sql/1-core/contract/src/types.ts`:
- Around line 167-170: The QueryOperationArgSpec currently declares readonly
traits?: string which weakens type safety; change this field to use the existing
canonical codec-trait alias (e.g. CodecTrait or CodecTraits) used across the
repo instead of plain string. Locate the type QueryOperationArgSpec in types.ts
and replace traits?: string with the correct canonical type (nullable/optional
as appropriate), but first confirm which alias name is present in the codebase
(search for CodecTrait or CodecTraits) and import or reference that alias so the
field aligns with the shared codec-trait union.

In `@packages/2-sql/4-lanes/sql-builder/src/expression.ts`:
- Around line 81-90: The trait-only branch of the ResolveExtArg type is too
strict and disallows raw literals; update the second conditional branch in
ResolveExtArg so it returns the same ExpressionOrValue wrapper as the codecId
branch (using CodecIdsWithTrait<CT, T> and the inferred N) instead of plain
Expression, so trait-targeted args accept literals; locate ResolveExtArg in
expression.ts and replace the trait branch's return type to ExpressionOrValue<{
codecId: CodecIdsWithTrait<CT, T>; nullable: N }, CT> (keeping the existing
inference of T and N).
🪄 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: 5496665e-609a-486b-b3fa-d7bafa07ab92

📥 Commits

Reviewing files that changed from the base of the PR and between 4ee59bb and 5195631.

⛔ Files ignored due to path filters (3)
  • packages/2-sql/4-lanes/sql-builder/test/fixtures/generated/contract.d.ts is excluded by !**/generated/**
  • packages/3-extensions/sql-orm-client/test/fixtures/generated/contract.d.ts is excluded by !**/generated/**
  • test/e2e/framework/test/fixtures/generated/contract.d.ts is excluded by !**/generated/**
📒 Files selected for processing (28)
  • examples/prisma-next-demo/src/prisma/contract.d.ts
  • packages/1-framework/1-core/operations/src/index.ts
  • packages/2-sql/1-core/contract/src/exports/types.ts
  • packages/2-sql/1-core/contract/src/types.ts
  • packages/2-sql/4-lanes/relational-core/src/ast/types.ts
  • packages/2-sql/4-lanes/relational-core/test/ast/predicate.test.ts
  • packages/2-sql/4-lanes/sql-builder/src/expression.ts
  • packages/2-sql/4-lanes/sql-builder/src/scope.ts
  • packages/2-sql/4-lanes/sql-builder/test/integration/extension-functions.test.ts
  • packages/2-sql/4-lanes/sql-builder/test/playground/extension-functions.test-d.ts
  • packages/3-extensions/sql-orm-client/src/model-accessor.ts
  • packages/3-extensions/sql-orm-client/src/types.ts
  • packages/3-extensions/sql-orm-client/test/extension-operations.test-d.ts
  • packages/3-extensions/sql-orm-client/test/filters.test.ts
  • packages/3-extensions/sql-orm-client/test/generated-contract-types.test-d.ts
  • packages/3-extensions/sql-orm-client/test/model-accessor.test.ts
  • packages/3-extensions/sql-orm-client/test/orm.types.test-d.ts
  • packages/3-extensions/sql-orm-client/test/where-binding.test.ts
  • packages/3-targets/6-adapters/postgres/package.json
  • packages/3-targets/6-adapters/postgres/src/core/adapter.ts
  • packages/3-targets/6-adapters/postgres/src/core/descriptor-meta.ts
  • packages/3-targets/6-adapters/postgres/src/exports/operation-types.ts
  • packages/3-targets/6-adapters/postgres/src/exports/runtime.ts
  • packages/3-targets/6-adapters/postgres/src/types/operation-types.ts
  • packages/3-targets/6-adapters/postgres/tsdown.config.ts
  • packages/3-targets/6-adapters/sqlite/src/core/adapter.ts
  • packages/3-targets/6-adapters/sqlite/test/adapter.test.ts
  • test/integration/test/fixtures/contract.d.ts
💤 Files with no reviewable changes (5)
  • packages/3-extensions/sql-orm-client/test/where-binding.test.ts
  • packages/3-targets/6-adapters/sqlite/test/adapter.test.ts
  • packages/3-targets/6-adapters/sqlite/src/core/adapter.ts
  • packages/2-sql/4-lanes/relational-core/test/ast/predicate.test.ts
  • packages/3-targets/6-adapters/postgres/src/core/adapter.ts
✅ Files skipped from review due to trivial changes (6)
  • packages/3-targets/6-adapters/postgres/src/exports/operation-types.ts
  • packages/3-targets/6-adapters/postgres/package.json
  • packages/2-sql/1-core/contract/src/exports/types.ts
  • packages/3-extensions/sql-orm-client/test/generated-contract-types.test-d.ts
  • packages/2-sql/4-lanes/sql-builder/test/integration/extension-functions.test.ts
  • packages/3-targets/6-adapters/postgres/src/core/descriptor-meta.ts
🚧 Files skipped from review as they are similar to previous changes (12)
  • packages/3-targets/6-adapters/postgres/tsdown.config.ts
  • packages/3-extensions/sql-orm-client/test/orm.types.test-d.ts
  • test/integration/test/fixtures/contract.d.ts
  • packages/2-sql/4-lanes/sql-builder/src/scope.ts
  • packages/3-targets/6-adapters/postgres/src/types/operation-types.ts
  • packages/3-extensions/sql-orm-client/test/model-accessor.test.ts
  • examples/prisma-next-demo/src/prisma/contract.d.ts
  • packages/3-extensions/sql-orm-client/test/filters.test.ts
  • packages/3-extensions/sql-orm-client/test/extension-operations.test-d.ts
  • packages/2-sql/4-lanes/sql-builder/test/playground/extension-functions.test-d.ts
  • packages/1-framework/1-core/operations/src/index.ts
  • packages/3-extensions/sql-orm-client/src/types.ts

Comment thread packages/3-extensions/sql-orm-client/src/model-accessor.ts
@SevInf SevInf force-pushed the worktree/fix-ilike branch from 5195631 to b57097f Compare April 10, 2026 14:14
Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

♻️ Duplicate comments (1)
packages/2-sql/4-lanes/sql-builder/src/expression.ts (1)

81-90: ⚠️ Potential issue | 🟡 Minor

Keep trait-targeted args symmetric with codecId-targeted args.

Line 90 only accepts Expression<...> for trait-gated params, while line 85 accepts raw inputs via ExpressionOrValue. That makes trait-targeted operation args stricter than codecId-targeted ones and can block valid literal inputs for adapter ops.

Suggested fix
-    ? Expression<{ codecId: CodecIdsWithTrait<CT, T>; nullable: N }>
+    ? ExpressionOrValue<{ codecId: CodecIdsWithTrait<CT, T>; nullable: N }, CT>
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/2-sql/4-lanes/sql-builder/src/expression.ts` around lines 81 - 90,
ResolveExtArg is asymmetric: the codecId branch returns ExpressionOrValue but
the traits branch returns only Expression, disallowing literal inputs for
trait-gated params; change the traits branch to return ExpressionOrValue as well
(i.e., replace Expression<{ codecId: CodecIdsWithTrait<CT, T>; nullable: N }>
with ExpressionOrValue<{ codecId: CodecIdsWithTrait<CT, T>; nullable: N }, CT>)
so trait-targeted args accept both Expressions and raw values consistent with
the codecId path.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@packages/3-extensions/sql-orm-client/test/extension-operations.test-d.ts`:
- Around line 96-101: Update the type assertion in the 'ilike returns
AnyExpression (predicate)' test to use the non-deprecated matcher: replace the
expectTypeOf<ReturnType<IlikeFn>>().toMatchTypeOf<import('@prisma-next/sql-relational-core/ast').AnyExpression>()
call with
expectTypeOf<ReturnType<IlikeFn>>().toExtend<import('@prisma-next/sql-relational-core/ast').AnyExpression>();
locate the test by the IlikeFn alias (PostAccessor['title']['ilike']) and adjust
only the matcher name so the test verifies the return type extends
AnyExpression.

---

Duplicate comments:
In `@packages/2-sql/4-lanes/sql-builder/src/expression.ts`:
- Around line 81-90: ResolveExtArg is asymmetric: the codecId branch returns
ExpressionOrValue but the traits branch returns only Expression, disallowing
literal inputs for trait-gated params; change the traits branch to return
ExpressionOrValue as well (i.e., replace Expression<{ codecId:
CodecIdsWithTrait<CT, T>; nullable: N }> with ExpressionOrValue<{ codecId:
CodecIdsWithTrait<CT, T>; nullable: N }, CT>) so trait-targeted args accept both
Expressions and raw values consistent with the codecId path.
🪄 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: 04848f22-85e2-4aac-a0c3-4dd505ccf86f

📥 Commits

Reviewing files that changed from the base of the PR and between 5195631 and b57097f.

⛔ Files ignored due to path filters (3)
  • packages/2-sql/4-lanes/sql-builder/test/fixtures/generated/contract.d.ts is excluded by !**/generated/**
  • packages/3-extensions/sql-orm-client/test/fixtures/generated/contract.d.ts is excluded by !**/generated/**
  • test/e2e/framework/test/fixtures/generated/contract.d.ts is excluded by !**/generated/**
📒 Files selected for processing (28)
  • examples/prisma-next-demo/src/prisma/contract.d.ts
  • packages/1-framework/1-core/operations/src/index.ts
  • packages/2-sql/1-core/contract/src/exports/types.ts
  • packages/2-sql/1-core/contract/src/types.ts
  • packages/2-sql/4-lanes/relational-core/src/ast/types.ts
  • packages/2-sql/4-lanes/relational-core/test/ast/predicate.test.ts
  • packages/2-sql/4-lanes/sql-builder/src/expression.ts
  • packages/2-sql/4-lanes/sql-builder/src/scope.ts
  • packages/2-sql/4-lanes/sql-builder/test/integration/extension-functions.test.ts
  • packages/2-sql/4-lanes/sql-builder/test/playground/extension-functions.test-d.ts
  • packages/3-extensions/sql-orm-client/src/model-accessor.ts
  • packages/3-extensions/sql-orm-client/src/types.ts
  • packages/3-extensions/sql-orm-client/test/extension-operations.test-d.ts
  • packages/3-extensions/sql-orm-client/test/filters.test.ts
  • packages/3-extensions/sql-orm-client/test/generated-contract-types.test-d.ts
  • packages/3-extensions/sql-orm-client/test/model-accessor.test.ts
  • packages/3-extensions/sql-orm-client/test/orm.types.test-d.ts
  • packages/3-extensions/sql-orm-client/test/where-binding.test.ts
  • packages/3-targets/6-adapters/postgres/package.json
  • packages/3-targets/6-adapters/postgres/src/core/adapter.ts
  • packages/3-targets/6-adapters/postgres/src/core/descriptor-meta.ts
  • packages/3-targets/6-adapters/postgres/src/exports/operation-types.ts
  • packages/3-targets/6-adapters/postgres/src/exports/runtime.ts
  • packages/3-targets/6-adapters/postgres/src/types/operation-types.ts
  • packages/3-targets/6-adapters/postgres/tsdown.config.ts
  • packages/3-targets/6-adapters/sqlite/src/core/adapter.ts
  • packages/3-targets/6-adapters/sqlite/test/adapter.test.ts
  • test/integration/test/fixtures/contract.d.ts
💤 Files with no reviewable changes (5)
  • packages/3-targets/6-adapters/postgres/src/core/adapter.ts
  • packages/3-targets/6-adapters/sqlite/test/adapter.test.ts
  • packages/3-extensions/sql-orm-client/test/where-binding.test.ts
  • packages/3-targets/6-adapters/sqlite/src/core/adapter.ts
  • packages/2-sql/4-lanes/relational-core/test/ast/predicate.test.ts
✅ Files skipped from review due to trivial changes (9)
  • packages/3-extensions/sql-orm-client/test/orm.types.test-d.ts
  • packages/3-extensions/sql-orm-client/test/generated-contract-types.test-d.ts
  • packages/3-targets/6-adapters/postgres/tsdown.config.ts
  • packages/2-sql/4-lanes/sql-builder/test/integration/extension-functions.test.ts
  • packages/3-targets/6-adapters/postgres/src/exports/operation-types.ts
  • packages/2-sql/1-core/contract/src/exports/types.ts
  • packages/3-targets/6-adapters/postgres/package.json
  • packages/2-sql/4-lanes/relational-core/src/ast/types.ts
  • packages/3-extensions/sql-orm-client/src/types.ts
🚧 Files skipped from review as they are similar to previous changes (11)
  • packages/3-extensions/sql-orm-client/test/filters.test.ts
  • packages/2-sql/4-lanes/sql-builder/test/playground/extension-functions.test-d.ts
  • packages/3-targets/6-adapters/postgres/src/core/descriptor-meta.ts
  • packages/2-sql/4-lanes/sql-builder/src/scope.ts
  • packages/3-extensions/sql-orm-client/test/model-accessor.test.ts
  • packages/3-targets/6-adapters/postgres/src/exports/runtime.ts
  • packages/3-targets/6-adapters/postgres/src/types/operation-types.ts
  • packages/1-framework/1-core/operations/src/index.ts
  • packages/2-sql/1-core/contract/src/types.ts
  • packages/3-extensions/sql-orm-client/src/model-accessor.ts
  • examples/prisma-next-demo/src/prisma/contract.d.ts

@SevInf SevInf force-pushed the worktree/fix-ilike branch 5 times, most recently from 65d5bf2 to bf38492 Compare April 10, 2026 15:02
Copy link
Copy Markdown
Contributor

@wmadden wmadden left a comment

Choose a reason for hiding this comment

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

Approving preemptively — the architectural direction (moving ilike out of core, introducing trait-targeted extension-operation args) is right and the implementation is clean. Findings below are concentrated around the type/runtime representation mismatch for traits, validation gaps for the new optional-arg shape, and one missing emitter test. Please address or respond to the inline comments before merging — no re-review needed.

Full artifacts (local): wip/review-code/pr-330/{spec,system-design-review,code-review,walkthrough}.md.

What looks solid:

  • Layering is correct — ilike is no longer a core BinaryOp, and adapters now own their operators via the same queryOperationTypes import wiring as pgvector.
  • Predicate detection (IsBooleanReturn + the createExtensionMethodFactory short-circuit) is a real correctness win — boolean-returning ops now form WHERE-clause predicates instead of accidentally proxying ComparisonMethods.
  • Deletions are clean and in lockstep — SQLite throw guard, the unreachable operator-map entry, and the corresponding adapter test all go together with the core AST removal.
  • Type-level negative tests (@ts-expect-error on metadata.ilike, views.ilike) lock in the gating.

Deferred items (D01–D04 in code-review.md) are intentionally out of scope.

@@ -1,11 +1,17 @@
export interface ParamSpec {
readonly codecId?: string;
readonly traits?: readonly string[];
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.

F01 — traits shape diverges between runtime and contract-type layers

Runtime ParamSpec.traits: readonly string[] here disagrees with contract-type QueryOperationArgSpec.traits: CodecTrait (single literal) in packages/2-sql/1-core/contract/src/types.ts:170. At runtime the model-accessor uses self.traits.every((t) => codecTraits.includes(t)) — AND over a list. At the type level, only a single trait is exposed. Today every registered op is single-trait, so the asymmetry is latent, but:

  • OpMatchesField's RequiredTraits extends FieldTraits arm distributes if RequiredTraits is ever a union, producing true | false, which fails the extends true check upstream — the type-level semantics for a multi-trait arg become "no match" even when all traits are present.
  • Authors writing a future adapter op must remember the type-level shape is fundamentally narrower than what the runtime registrar supports.

Suggestion: Commit to a single representation at both layers, then encode that in the types. Either:

  • (a) Narrow the runtime to a single trait (simplest; matches current usage and the contract-type shape), or
  • (b) Generalize both to a tuple/array with explicit AND semantics (readonly traits?: readonly CodecTrait[]) and update OpMatchesField / CodecIdsWithTrait to intersect over the tuple.

Option (a) example:

export interface ParamSpec {
  readonly codecId?: string;
  readonly traits?: string;
  readonly nullable: boolean;
}
// postgres descriptor-meta.ts
{ traits: 'textual', nullable: false },
// model-accessor.ts trait branch
} else if (self?.traits) {
  for (const codec of context.codecs.values()) {
    if (codec.traits?.includes(self.traits as CodecTrait)) {
      registerOp(codec.id, op);
    }
  }
}

@@ -1,11 +1,17 @@
export interface ParamSpec {
readonly codecId?: string;
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.

F02 — No validation that a ParamSpec has at least one of codecId / traits

Both fields are now optional, but the registry (createOperationRegistry.register, lines 26–45 in this file) accepts any shape. A descriptor with neither codecId nor traits on its self arg will silently register against nothing — the op exists in the contract's queryOperations but never attaches to any field. Easy author error to introduce (copy-paste, delete a field) and hard to diagnose from the outside.

Suggestion: Assert at registration time that every arg has at least one of codecId / traits. Throw in register so the error surfaces at descriptor load time:

register(descriptor: OperationDescriptor<T>) {
  if (descriptor.method in operations) {
    throw new Error(`Operation "${descriptor.method}" is already registered`);
  }
  for (const [i, arg] of descriptor.args.entries()) {
    if (!arg.codecId && !arg.traits?.length) {
      throw new Error(
        `Operation "${descriptor.method}" arg[${i}] has neither codecId nor traits`,
      );
    }
  }
  // ... existing code
}

Pairs naturally with F06 (the silent codecId/traits precedence in model-accessor).

export type QueryOperationTypes = SqlQueryOperationTypes<{
readonly ilike: {
readonly args: readonly [
{ readonly traits: 'textual'; readonly nullable: boolean },
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.

F03 — Type-level first-arg nullable is boolean but runtime sets false

This hand-authored type declares { readonly traits: 'textual'; readonly nullable: boolean }boolean means "either" — yet the runtime descriptor in postgres/src/core/descriptor-meta.ts:135 fixes nullable: false. Small drift, but real: the type is more permissive than the runtime, future authors may copy this and hit mismatches, and the SQL builder cannot rely on the self arg being non-null at the type level.

Also worth noting: OpMatchesField currently doesn't consult nullable at all when matching, so this flag on the self arg has no effect on field surfacing either way. That may be intentional (ilike handles nulls fine in SQL), but a one-line comment would help a future reader distinguish "intentionally ignored" from "forgot to check".

Suggestion: Make the type match the runtime (nullable: false for the self arg). If boolean is principled, add a one-line comment explaining why.

Comment on lines +252 to +266
type OpMatchesField<Op, CodecId extends string, CT extends Record<string, unknown>> = Op extends {
readonly args: readonly [infer Self, ...(readonly unknown[])];
}
? Self extends { readonly codecId: CodecId }
? true
: Self extends { readonly traits: infer RequiredTraits extends string }
? CodecId extends keyof CT
? CT[CodecId] extends { readonly traits: infer FieldTraits }
? RequiredTraits extends FieldTraits
? true
: false
: false
: false
: false
: false;
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.

F04 — OpMatchesField multi-trait match has distribution hazard

The conditional RequiredTraits extends FieldTraits distributes over RequiredTraits. For single-trait self args (today's only case) this is fine. For a hypothetical traits: 'textual' | 'numeric', TS distributes the check, producing boolean instead of true/false, and the outer ... extends true ? OpName : never evaluates to never — the op silently disappears from the field's method surface. The symmetric hazard exists in CodecIdsWithTrait in packages/2-sql/4-lanes/sql-builder/src/expression.ts (~lines 70–80).

Suggestion: Either lock the contract-type representation to single traits only (see F01 option a), or wrap trait checks in tuple form ([RequiredTraits] extends [FieldTraits]) and switch to tuple-array AND semantics. The [T] extends [U] trick disables distribution and would match the runtime's AND intent for arrays of traits if the shape is generalized later.

Comment on lines +271 to +277
queryOperationTypes: {
import: {
package: '@prisma-next/adapter-postgres/operation-types',
named: 'QueryOperationTypes',
alias: 'PgAdapterQueryOps',
},
},
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.

F05 — No emitter end-to-end test for adapter-owned QueryOperationTypes

The PR description concedes: "The emitter pipeline should be validated end-to-end … but no e2e emit test exists yet." Five contract.d.ts fixtures were updated to include PgAdapterQueryOps. If they were regenerated from the emitter, the test plan is implicit; if hand-edited, the emitter never ran over these shapes. Either way, there is no guard against a regression where this queryOperationTypes.import block is dropped by the emitter. Given the whole surface gating depends on this import appearing in the generated contract.d.ts, the coverage gap is significant.

Suggestion: Add a focused emitter test that runs the real generator on a minimal postgres-adapter contract and asserts the presence of:

  • the import type { QueryOperationTypes as PgAdapterQueryOps } … line, and
  • the PgAdapterQueryOps & … intersection in the emitted QueryOperationTypes alias.

packages/2-sql/3-tooling/emitter/test/emitter-hook.generation.basic.test.ts already exercises queryOperationTypeImports; the new test can mirror that pattern.

Comment on lines +70 to +77
if (self?.codecId) {
registerOp(self.codecId, op);
} else if (self?.traits) {
for (const codec of context.codecs.values()) {
const codecTraits: readonly string[] = codec.traits ?? [];
if (self.traits.every((t) => codecTraits.includes(t))) {
registerOp(codec.id, op);
}
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.

F06 — Trait-registration loop silently ignores ops whose self arg sets both codecId and traits

The branch is if (self?.codecId) { … } else if (self?.traits) { … }. An op declaring both fields gets registered only by codecId, dropping the trait path. Probably correct semantically (codec-id is strictly more specific) but a silent precedence rule that deserves either a comment or an explicit guard.

Suggestion: Add a short comment to the effect of "codecId takes precedence when both are set", or — if "both is illegal" is the intended contract — assert it (if (self.codecId && self.traits) throw …). Pairs naturally with F02.

Comment on lines +58 to +65
function registerOp(codecId: string, op: [string, SqlOperationEntry]) {
let existing = opsByCodecId.get(codecId);
if (!existing) {
existing = [];
opsByCodecId.set(codecId, existing);
}
existing.push(op);
}
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.

F07 — [string, SqlOperationEntry] tuple rebuilt per op; minor but worth a named alias

The [string, SqlOperationEntry] shape is used as both the Map value element and a per-op tuple, with an inline type annotation at the call site (line 69). This reads awkwardly and obscures the intent (the tuple is really "op name + entry"). Not a correctness issue — readability only.

Suggestion: A local type alias, e.g.

type NamedOp = readonly [name: string, entry: SqlOperationEntry];

would name the pair and communicate that it's positional. registerOp then becomes registerOp(codecId, op: NamedOp).

Comment on lines +67 to +80
it('creates ilike as trait-matched extension operation returning predicate', () => {
const user = createModelAccessor(context, 'User');
const ilike = user['name']!.ilike;
const result = ilike('%ali%');
expect(result).toBeInstanceOf(OperationExpr);
const op = result as OperationExpr;
expect(op.method).toBe('ilike');
expect(op.self).toEqual(ColumnRef.of('users', 'name'));
});

it('does not expose ilike on non-textual fields', () => {
const post = createModelAccessor(context, 'Post');
const field = post['views'] as unknown as Record<string, unknown>;
expect(field['ilike']).toBeUndefined();
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.

F10 — Consider one multi-trait-subset regression test

These new tests validate the happy path (single-trait ilike on users.name) and the negative path (not on views). They don't pin the trait-subset AND semantics at the runtime layer — a single-trait test doesn't distinguish every from some. A tiny regression test with a synthetic op requiring two traits (e.g. ['equality', 'textual']) and fields with one vs both would lock in the runtime contract and make any future change to the registration loop a conscious edit.

Suggestion: Add one unit test that constructs a synthetic context with a multi-trait op and asserts it attaches only to codecs whose traits are a superset. Pairs naturally with either direction of F01.

…tions

Move ilike from a core comparison method to a postgres adapter-provided
extension operation. Operation args now support targeting by trait
(e.g., `traits: ['textual']`) in addition to codecId, allowing
adapter operations to match any codec with the required traits.

- Extend ParamSpec and QueryOperationArgSpec with optional `traits` field
- Add FieldSpec/TraitField to sql-builder scope for trait-targeted expressions
- Update FieldOperations type matching to support trait-based operations
- Boolean-trait return detection makes predicate ops return AnyExpression
- Register ilike in postgres adapter's queryOperations()
- Remove ilike from BinaryOp union, BinaryExpr, ComparisonMethodFns
- Remove ilike runtime guard from SQLite adapter
- Fix tsdown exports stripping ./schema-sql in sql-contract-ts
@SevInf SevInf force-pushed the worktree/fix-ilike branch from bf38492 to bc433ef Compare April 20, 2026 18:11
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.

2 participants