Skip to content

refactor: execution metadata lives on AST (ADR 205)#393

Merged
SevInf merged 13 commits intomainfrom
ast-codec-ids-v2
Apr 30, 2026
Merged

refactor: execution metadata lives on AST (ADR 205)#393
SevInf merged 13 commits intomainfrom
ast-codec-ids-v2

Conversation

@SevInf
Copy link
Copy Markdown
Contributor

@SevInf SevInf commented Apr 29, 2026

closes TML-2311

Intent

Make the SQL AST the single source of truth for execution metadata. Today two channels carry it: the AST itself, and a sidecar bag on PlanMeta (refs, paramDescriptors, annotations.codecs, projectionTypes). Both builder paths populate the sidecar by walking the AST and flattening per-node info into per-alias and per-index maps; the runtime then chooses inconsistently which channel to read. With the beforeCompile middleware hook now in main (PR #373), AST rewrites silently invalidate every alias-keyed or index-keyed sidecar entry, and the recently merged re-derive paramDescriptors after middleware AST rewrite workaround lives exactly to paper over that drift.

ADR 205 collapses the two channels into one. ProjectionItem carries the projection's output codec; ParamRef already carries its parameter codec. The decoder builds its alias→codec lookup by walking the AST; the encoder reads from ParamRef.codecId. PlanMeta shrinks to identification + policy fields. Raw plans — which have no AST — pass parameters and rows through to the driver and back, consistent with the escape-hatch contract from ADR 012.

Change map

  • AST + contract typespackages/1-framework/0-foundation/contract/src/types.ts, packages/2-sql/4-lanes/relational-core/src/ast/types.ts, packages/2-sql/4-lanes/relational-core/src/types.ts. ProjectionItem.codecId is now optional. Insert/Update/Delete.returning is ProjectionItem[]. Mutation ASTs gain rewrite() methods that descend into returning. PlanMeta loses paramDescriptors/refs/projection/projectionTypes/annotations.codecs. ParamDescriptor and PlanRefs are deleted from the contract package. RawTemplateOptions loses refs and projection. The dead AST traversal helpers (collectRefs on QueryAst/FromSource/all subclasses, plus mergeRefsInto/addColumnRefToRefSets/sortRefs) are removed too.
  • Producerspackages/2-sql/4-lanes/sql-builder/src/runtime/builder-base.ts, packages/2-sql/4-lanes/sql-builder/src/runtime/mutation-impl.ts, packages/3-extensions/sql-orm-client/src/query-plan-{meta,select,mutations,aggregate}.ts, packages/3-extensions/sql-orm-client/src/where-binding.ts. Every emitted ProjectionItem is now stamped with the codec ID the producer already had in scope (column lookup for SELECTs and RETURNINGs, scope-field lookup for the DSL builder). Both producers stop emitting the four sidecar fields.
  • Runtimepackages/2-sql/5-runtime/src/codecs/{decoding,encoding}.ts, packages/2-sql/5-runtime/src/middleware/{before-compile-chain,budgets}.ts, packages/2-sql/5-runtime/src/guardrails/raw.ts. The decoder walks the AST's projection/returning list to build its alias→codec lookup; the encoder reads codecId off each ParamRef collected from the AST. runBeforeCompileChain no longer re-derives paramDescriptors. The budgets middleware derives the primary table from ast.from instead of meta.refs.tables[0]; the raw heuristic collapses to a SQL-text LIMIT regex. Raw guardrails drop the refs-based index-coverage path.
  • Adapterpackages/3-targets/6-adapters/postgres/src/core/sql-renderer.ts, packages/3-targets/6-adapters/sqlite/src/core/adapter.ts. Postgres and SQLite render RETURNING from ProjectionItem[] with alias-aware emission: <table>.<column> when alias matches column, <table>.<column> AS <alias> when it differs, <expr> AS <alias> for non-ColumnRef projections.
  • Cross-package fixture cleanup — Mongo query-builder/ORM/state-classes producers and the Mongo plan-JSON serializer drop paramDescriptors. Test fixtures across framework-components, middleware-telemetry, mongo-family, mongo-target, and cross-package integration tests are updated to the narrowed PlanMeta shape.
  • Docs + project workspacedocs/architecture docs/adrs/ADR 012 - Raw SQL Escape Hatch.md carries a banner marking its optional structured-annotations branch retired by ADR 205. The driving spec + plan ship under projects/execution-metadata-on-ast/.

The story

The change starts at the AST. ProjectionItem previously carried only (alias, expr); ADR 205 makes its codec ID a first-class field on the node. The mutation returning lists were ColumnRef[] — no alias, no codec — and now they are ProjectionItem[], putting RETURNING outputs on the same footing as SELECT projections. To keep beforeCompile rewrites correct-by-construction, the mutation ASTs gain rewrite() methods that descend into each ProjectionItem.expr of returning, mirroring SelectAst.projection's long-standing rewrite path.

Once the AST can carry the metadata, the producers stop emitting the sidecar. buildOrmQueryPlan previously called resolveProjectionCodecs(contract, ast) to flatten projections into a projectionTypes map; that work moves up to the construction sites where the codec is already in hand (buildProjection, buildReturningColumns, the aggregate-projection builder, the DSL select(...) resolver). buildQueryPlan in the SQL builder lane likewise stamps codecs at projection-construction time and stops walking the row-fields map for sidecar purposes. where-binding's bindSelectAst preserves codecId when it rebuilds projections so the rebound AST stays equivalent.

The runtime then collapses two consumers to one source. The decoder used to read meta.annotations.codecs first, then fall back to meta.projectionTypes, then build a column-ref index from meta.refs.columns for error messages and JSON-Schema validator keys. After this PR it walks the AST's projection (or returning) list once per plan, builds an alias→codec lookup and an alias→{table, column} map from ProjectionItem.expr when the expression is a ColumnRef, and recognises subquery / json-array-agg projections as include aggregates (replacing the old include:-prefixed string convention in meta.projection). For raw plans — no AST — the decoder hands wire values straight back. The encoder follows the same pattern: ordered, deduplicated ast.collectParamRefs() produces metadata aligned with plan.params, raw plans pass values through.

runBeforeCompileChain had a comment-block workaround explaining that AST rewrites invalidate paramDescriptors and that we re-derive them. With descriptors gone, that whole workaround is gone — the AST is what the encoder reads, so middleware that introduces ParamRefs cannot drift.

The lints and budgets middleware lose their refs-driven paths. evaluateAstLints already covered the AST-backed cases; raw plans now get only the SQL-string heuristics ADR 205 expressly degrades to. The budgets middleware reads the primary table off ast.from; the raw fallback collapses to a LIMIT-regex check rather than chasing meta.refs.tables[0]. The Postgres and SQLite renderers swap their RETURNING <ColumnRef>[] lowering for ProjectionItem[]-aware rendering, emitting AS <alias> only when the alias diverges from the column name to keep the snapshot diff minimal.

Test fixtures that hand-constructed plans with meta: { paramDescriptors: [...], projectionTypes: {...}, refs: {...} } migrate to either pass an AST holding the equivalent ParamRef.codecId / ProjectionItem.codecId, or — where they were exercising raw-plan encoding — drop the codec-driven assertion since raw plans no longer codec-encode.

Behavior changes & evidence

  • Decoded values, encoded parameters, lints, and budget evaluations for AST-backed plans are unchanged. The decoder finds the same codec for the same alias; the encoder finds the same codec for the same param. (codecs/decoding.ts, codecs/encoding.ts, middleware/budgets.ts)
  • RETURNING SQL text changes only when an alias differs from the underlying column name — the AS <alias> suffix is now emitted in that case. The alias-matches-column case is byte-identical to the old output. (postgres/src/core/sql-renderer.ts, sqlite/src/core/adapter.ts)
  • Raw plans drop per-parameter codec encoding and per-alias codec decoding. Index-coverage lints and the refs.tables[0] row-count heuristic that depended on raw-plan annotations are removed; select-star, missing LIMIT, and mutation-without-WHERE SQL-string heuristics still run on raw plans. (guardrails/raw.ts)
  • beforeCompile AST rewrites that introduce, swap, or remove ParamRefs no longer require any sidecar maintenance. A new test in before-compile-chain.test.ts asserts that a beforeCompile rewriter that swaps a projection alias decodes correctly end-to-end through the AST-driven decoder. (before-compile-chain.test.ts)

Compatibility / migration / risk

  • No backwards-compatibility shims. Per repo policy, every in-repo call site that constructed a plan with the removed sidecars is updated at the point of change. ParamDescriptor and PlanRefs are deleted outright, not deprecated.
  • Plan identity is unaffected. ADR 013 already excluded the removed fields from identity hashing.
  • Telemetry is unaffected. Timing, row counts, and error codes flow through paths independent of the removed sidecars.
  • Mongo family changes are limited to dropping paramDescriptors from PlanMeta call sites. Cross-family invariant ("AST is the source when one exists") applies family-wide; specific Mongo-family migration of ProjectionItem-style metadata is separate work.

Follow-ups / open questions

  • The projects/execution-metadata-on-ast/ workspace ships with the PR for reviewability; it can be deleted in a follow-up close-out commit. ADR 205 is canonical under docs/architecture docs/adrs/.
  • ProjectionItem.codecId is optional. Once all in-repo producers stamp it (this PR), it's a candidate for promotion to required in a future change. Out of scope here.

Non-goals / intentionally out of scope

  • Restoring the unindexed-predicate lint or the refs-based row-count budget for raw plans. ADR 205 accepts this as a known degradation.
  • New lints on top of the AST. Lint coverage stays at parity (or strictly degrades for raw plans only).
  • The ADR 012 minimal annotation schema (intent, isMutation, hasWhere, hasLimit). Unchanged.
  • Cross-family Mongo migration of execution metadata onto the Mongo AST. Tracked separately.

Summary by CodeRabbit

  • New Features

    • Execution metadata (codec IDs, param info, projection mapping) is now carried on AST nodes, not plan metadata.
  • Behavior Changes

    • Raw SQL plans forward caller parameters and return wire rows unchanged (no automatic codec encoding/decoding).
    • Linting and row-budget heuristics now run only for AST-backed plans; raw plans use existing SQL-text heuristics.
  • Documentation

    • ADR and architecture notes updated to reflect the new metadata and behavioral expectations.

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Apr 29, 2026

Warning

Rate limit exceeded

@SevInf has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 57 minutes and 45 seconds before requesting another review.

To keep reviews running without waiting, you can enable usage-based add-on for your organization. This allows additional reviews beyond the hourly cap. Account admins can enable it under billing.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yml

Review profile: CHILL

Plan: Pro

Run ID: 9db6e8bd-9340-4f81-a5b8-e959cf82f24d

📥 Commits

Reviewing files that changed from the base of the PR and between b1eb015 and f8ad1c1.

📒 Files selected for processing (86)
  • .github/workflows/pr-code-security.yml
  • docs/architecture docs/adrs/ADR 012 - Raw SQL Escape Hatch.md
  • docs/architecture docs/adrs/ADR 205 - Execution metadata lives on AST.md
  • examples/prisma-next-demo/test/runtime.offline.integration.test.ts
  • packages/1-framework/0-foundation/contract/README.md
  • packages/1-framework/0-foundation/contract/src/exports/types.ts
  • packages/1-framework/0-foundation/contract/src/types.ts
  • packages/1-framework/0-foundation/contract/test/types.test.ts
  • packages/1-framework/1-core/framework-components/test/mock-family.test.ts
  • packages/1-framework/1-core/framework-components/test/query-plan.types.test-d.ts
  • packages/1-framework/1-core/framework-components/test/run-with-middleware.test.ts
  • packages/1-framework/1-core/framework-components/test/runtime-core.test.ts
  • packages/1-framework/1-core/framework-components/test/runtime-core.types.test-d.ts
  • packages/2-mongo-family/4-query/query-ast/test/query-plan.types.test-d.ts
  • packages/2-mongo-family/5-query-builders/orm/src/collection.ts
  • packages/2-mongo-family/5-query-builders/orm/src/compile.ts
  • packages/2-mongo-family/5-query-builders/orm/src/mongo-raw.ts
  • packages/2-mongo-family/5-query-builders/query-builder/src/builder.ts
  • packages/2-mongo-family/5-query-builders/query-builder/src/query.ts
  • packages/2-mongo-family/5-query-builders/query-builder/src/state-classes.ts
  • packages/2-mongo-family/7-runtime/test/mongo-execution-plan.types.test-d.ts
  • packages/2-mongo-family/7-runtime/test/mongo-middleware.test.ts
  • packages/2-mongo-family/7-runtime/test/raw-commands.test.ts
  • packages/2-mongo-family/7-runtime/test/setup.ts
  • packages/2-sql/4-lanes/relational-core/README.md
  • packages/2-sql/4-lanes/relational-core/src/ast/types.ts
  • packages/2-sql/4-lanes/relational-core/src/ast/util.ts
  • packages/2-sql/4-lanes/relational-core/src/plan.ts
  • packages/2-sql/4-lanes/relational-core/src/types.ts
  • packages/2-sql/4-lanes/relational-core/test/ast/builders.test.ts
  • packages/2-sql/4-lanes/relational-core/test/ast/common.test.ts
  • packages/2-sql/4-lanes/relational-core/test/ast/delete.test.ts
  • packages/2-sql/4-lanes/relational-core/test/ast/insert.test.ts
  • packages/2-sql/4-lanes/relational-core/test/ast/rich-ast.test.ts
  • packages/2-sql/4-lanes/relational-core/test/ast/test-helpers.ts
  • packages/2-sql/4-lanes/relational-core/test/ast/update.test.ts
  • packages/2-sql/4-lanes/relational-core/test/ast/util.test.ts
  • packages/2-sql/4-lanes/relational-core/test/plan.test.ts
  • packages/2-sql/4-lanes/relational-core/test/sql-execution-plan.types.test-d.ts
  • packages/2-sql/4-lanes/sql-builder/src/runtime/builder-base.ts
  • packages/2-sql/4-lanes/sql-builder/src/runtime/mutation-impl.ts
  • packages/2-sql/5-runtime/src/codecs/decoding.ts
  • packages/2-sql/5-runtime/src/codecs/encoding.ts
  • packages/2-sql/5-runtime/src/guardrails/raw.ts
  • packages/2-sql/5-runtime/src/middleware/before-compile-chain.ts
  • packages/2-sql/5-runtime/src/middleware/budgets.ts
  • packages/2-sql/5-runtime/src/sql-runtime.ts
  • packages/2-sql/5-runtime/test/async-iterable-result.test.ts
  • packages/2-sql/5-runtime/test/before-compile-chain.test.ts
  • packages/2-sql/5-runtime/test/budgets.test.ts
  • packages/2-sql/5-runtime/test/codec-async.test.ts
  • packages/2-sql/5-runtime/test/json-schema-validation.test.ts
  • packages/2-sql/5-runtime/test/lints.test.ts
  • packages/2-sql/5-runtime/test/sql-family-adapter.test.ts
  • packages/2-sql/5-runtime/test/sql-runtime.test.ts
  • packages/3-extensions/middleware-telemetry/test/telemetry-middleware.test.ts
  • packages/3-extensions/sql-orm-client/src/query-plan-aggregate.ts
  • packages/3-extensions/sql-orm-client/src/query-plan-meta.ts
  • packages/3-extensions/sql-orm-client/src/query-plan-mutations.ts
  • packages/3-extensions/sql-orm-client/src/query-plan-select.ts
  • packages/3-extensions/sql-orm-client/src/where-binding.ts
  • packages/3-extensions/sql-orm-client/test/collection-mutation-dispatch.test.ts
  • packages/3-extensions/sql-orm-client/test/query-plan-aggregate.test.ts
  • packages/3-extensions/sql-orm-client/test/query-plan-meta.test.ts
  • packages/3-extensions/sql-orm-client/test/query-plan-mutations.test.ts
  • packages/3-extensions/sql-orm-client/test/query-plan-select.test.ts
  • packages/3-extensions/sql-orm-client/test/raw-compiled-query.test.ts
  • packages/3-extensions/sql-orm-client/test/rich-collection.test.ts
  • packages/3-extensions/sql-orm-client/test/rich-query-plans.test.ts
  • packages/3-mongo-target/1-mongo-target/src/core/mongo-ops-serializer.ts
  • packages/3-mongo-target/1-mongo-target/test/data-transform.test.ts
  • packages/3-mongo-target/1-mongo-target/test/migration-e2e.test.ts
  • packages/3-mongo-target/1-mongo-target/test/mongo-ops-serializer.dml.test.ts
  • packages/3-mongo-target/1-mongo-target/test/mongo-runner.test.ts
  • packages/3-mongo-target/2-mongo-adapter/test/mongo-adapter.test.ts
  • packages/3-mongo-target/2-mongo-adapter/test/mongo-runner.test.ts
  • packages/3-targets/6-adapters/postgres/src/core/sql-renderer.ts
  • packages/3-targets/6-adapters/postgres/test/adapter.test.ts
  • packages/3-targets/6-adapters/postgres/test/control-adapter-lower-parity.test.ts
  • packages/3-targets/6-adapters/postgres/test/migrations/data-transform.test.ts
  • packages/3-targets/6-adapters/postgres/test/rich-adapter.test.ts
  • packages/3-targets/6-adapters/sqlite/src/core/adapter.ts
  • packages/3-targets/6-adapters/sqlite/test/adapter.test.ts
  • test/e2e/framework/vitest.config.ts
  • test/integration/test/cross-package/cross-family-middleware.test.ts
  • test/integration/test/mongo/expr-filter.test.ts
📝 Walkthrough

Walkthrough

This pull request implements ADR 205 and ADR 012, moving execution metadata from PlanMeta to AST nodes. It removes ParamDescriptor and PlanRefs types, adds codecId to ProjectionItem, changes mutation AST returning from ColumnRef[] to ProjectionItem[], and updates encoding/decoding/guardrails/budgets to derive metadata from AST instead of plan metadata. Hundreds of test fixtures are updated accordingly.

Changes

Cohort / File(s) Summary
Architecture Decision Records
docs/architecture/adrs/ADR 012 - Raw SQL Escape Hatch.md, docs/architecture/adrs/ADR 205 - Execution metadata lives on AST.md
Introduces ADR 205 defining execution metadata movement to AST, and updates ADR 012 to clarify raw-plan applicability of lints and heuristics that require structural knowledge.
Contract Schema Changes
packages/1-framework/0-foundation/contract/src/types.ts, packages/1-framework/0-foundation/contract/src/exports/types.ts, packages/1-framework/0-foundation/contract/README.md
Deletes ParamDescriptor and PlanRefs exported types; removes paramDescriptors, refs, projection, projectionTypes properties from PlanMeta; narrows annotations to untyped index signature.
AST Type System
packages/2-sql/4-lanes/relational-core/src/ast/types.ts, packages/2-sql/4-lanes/relational-core/src/plan.ts, packages/2-sql/4-lanes/relational-core/src/types.ts
Adds codecId field to ProjectionItem; changes InsertAst/UpdateAst/DeleteAst.returning from ColumnRef[] to ProjectionItem[]; adds rewrite() methods; removes augmentDescriptorWithColumnMeta and PlanRefs-based reference collection.
SQL Encoding/Decoding
packages/2-sql/5-runtime/src/codecs/encoding.ts, packages/2-sql/5-runtime/src/codecs/decoding.ts
Shifts parameter encoding from ParamDescriptor to ParamRef metadata; shifts row decoding from plan.meta projection mapping to AST-derived DecodeContext; adds fallback for raw plans without codec info.
SQL Query Planning
packages/2-sql/4-lanes/sql-builder/src/runtime/builder-base.ts, packages/2-sql/4-lanes/sql-builder/src/runtime/mutation-impl.ts
Removes paramDescriptors/projectionTypes/codec-annotation construction; threads codecId directly into ProjectionItem; changes buildQueryPlan signature to remove rowFields parameter.
SQL Middleware/Runtime
packages/2-sql/5-runtime/src/middleware/before-compile-chain.ts, packages/2-sql/5-runtime/src/middleware/budgets.ts, packages/2-sql/5-runtime/src/guardrails/raw.ts, packages/2-sql/5-runtime/src/sql-runtime.ts
Removes paramDescriptors re-derivation after middleware rewrites; refactors budgets/row estimation to rely on AST instead of plan.meta.refs; removes index-coverage linting for raw plans.
SQL Adapter RETURNING
packages/3-targets/6-adapters/postgres/src/core/sql-renderer.ts, packages/3-targets/6-adapters/sqlite/src/core/adapter.ts
Adds rendering logic for RETURNING clauses built from ProjectionItem instances instead of raw ColumnRef objects.
ORM Query Planning
packages/3-extensions/sql-orm-client/src/query-plan-meta.ts, packages/3-extensions/sql-orm-client/src/query-plan-select.ts, packages/3-extensions/sql-orm-client/src/query-plan-mutations.ts, packages/3-extensions/sql-orm-client/src/query-plan-aggregate.ts, packages/3-extensions/sql-orm-client/src/where-binding.ts
Removes paramDescriptors construction from plan builders; threads codecId into ProjectionItem from contract storage metadata; simplifies buildOrmQueryPlan signature.
Mongo Query Plans
packages/2-mongo-family/5-query-builders/orm/src/collection.ts, packages/2-mongo-family/5-query-builders/orm/src/compile.ts, packages/2-mongo-family/5-query-builders/orm/src/mongo-raw.ts, packages/2-mongo-family/5-query-builders/query-builder/src/builder.ts, packages/2-mongo-family/5-query-builders/query-builder/src/query.ts, packages/2-mongo-family/5-query-builders/query-builder/src/state-classes.ts
Removes paramDescriptors field from PlanMeta objects constructed across all Mongo query builders.
Mongo Serialization
packages/3-mongo-target/1-mongo-target/src/core/mongo-ops-serializer.ts
Removes paramDescriptors and projection-metadata fields from PlanMetaJson schema and deserialization.
Test Fixtures (Contract/Framework)
packages/1-framework/0-foundation/contract/test/types.test.ts, packages/1-framework/1-core/framework-components/test/*.test.ts
Updates PlanMeta test constants to remove paramDescriptors field.
Test Fixtures (SQL AST)
packages/2-sql/4-lanes/relational-core/test/ast/*.test.ts
Removes collectRefs test suite; adds ProjectionItem codec tests; updates returning construction helpers and assertions to use ProjectionItem instead of ColumnRef.
Test Fixtures (SQL Runtime)
packages/2-sql/5-runtime/test/*.test.ts
Removes paramDescriptors from meta fixtures; updates encoding/decoding tests to use AST-driven codec metadata; removes plan.meta.refs-based budget estimation tests; updates before-compile tests to assert codec behavior via AST.
Test Fixtures (ORM)
packages/3-extensions/sql-orm-client/test/*.test.ts
Updates assertions to validate codec metadata via plan.ast.collectParamRefs() and ProjectionItem.codecId instead of plan.meta.paramDescriptors/projectionTypes; adds projection-building helpers with codec support.
Test Fixtures (Adapters/Targets)
packages/3-targets/6-adapters/postgres/test/*.test.ts, packages/3-targets/6-adapters/sqlite/test/*.test.ts, packages/3-mongo-target/*/test/*.test.ts
Removes paramDescriptors from meta; updates RETURNING clause assertions to use ProjectionItem instead of ColumnRef; removes projection-metadata assertions.
Test Fixtures (Integration/E2E)
examples/prisma-next-demo/test/runtime.offline.integration.test.ts, test/integration/test/*.test.ts, test/e2e/framework/vitest.config.ts
Updates test projections to include codec metadata; removes paramDescriptors from plan fixtures; adjusts test timeout configuration.

Estimated code review effort

🎯 5 (Critical) | ⏱️ ~120 minutes

Possibly related PRs

  • PR #256: Directly related; both PRs modify the same execution metadata surface—moving codec metadata into ParamRef/ProjectionItem, adding collectParamRefs(), and removing plan-level paramDescriptors/descriptor machinery.

Suggested reviewers

  • aqrln

Poem

🐰 The metadata hops from plan to tree,
Where codec IDs dance in AST,
ProjectionItems wrapped with care,
Returning clauses everywhere!
No more descriptors in the way,
The AST leads from night to day. 🌲✨

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 5.06% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main architectural change: moving execution metadata from PlanMeta onto the AST as specified in ADR 205, which is the primary objective of this large refactoring.
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.

✏️ 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 ast-codec-ids-v2

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
Review rate limit: 0/1 reviews remaining, refill in 57 minutes and 45 seconds.

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

@SevInf SevInf force-pushed the ast-codec-ids-v2 branch from c0e7d51 to 8245deb Compare April 29, 2026 15:28
Comment thread packages/2-sql/5-runtime/src/codecs/encoding.ts
@pkg-pr-new
Copy link
Copy Markdown

pkg-pr-new Bot commented Apr 29, 2026

Open in StackBlitz

@prisma-next/mongo-runtime

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

@prisma-next/family-mongo

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

@prisma-next/sql-runtime

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

@prisma-next/family-sql

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

@prisma-next/middleware-telemetry

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

@prisma-next/mongo

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

@prisma-next/extension-paradedb

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

@prisma-next/extension-pgvector

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

@prisma-next/postgres

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

@prisma-next/sql-orm-client

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

@prisma-next/sqlite

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

@prisma-next/target-mongo

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

@prisma-next/adapter-mongo

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

@prisma-next/driver-mongo

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

@prisma-next/contract

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

@prisma-next/utils

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

@prisma-next/config

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

@prisma-next/errors

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

@prisma-next/framework-components

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

@prisma-next/operations

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

@prisma-next/ts-render

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

@prisma-next/contract-authoring

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

@prisma-next/ids

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

@prisma-next/psl-parser

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

@prisma-next/psl-printer

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

@prisma-next/cli

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

@prisma-next/emitter

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

@prisma-next/migration-tools

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

prisma-next

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

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

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

@prisma-next/mongo-codec

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

@prisma-next/mongo-contract

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

@prisma-next/mongo-value

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

@prisma-next/mongo-contract-psl

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

@prisma-next/mongo-contract-ts

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

@prisma-next/mongo-emitter

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

@prisma-next/mongo-schema-ir

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

@prisma-next/mongo-query-ast

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

@prisma-next/mongo-orm

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

@prisma-next/mongo-query-builder

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

@prisma-next/mongo-lowering

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

@prisma-next/mongo-wire

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

@prisma-next/sql-contract

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

@prisma-next/sql-errors

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

@prisma-next/sql-operations

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

@prisma-next/sql-schema-ir

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

@prisma-next/sql-contract-psl

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

@prisma-next/sql-contract-ts

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

@prisma-next/sql-contract-emitter

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

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

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

@prisma-next/sql-relational-core

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

@prisma-next/sql-builder

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

@prisma-next/target-postgres

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

@prisma-next/target-sqlite

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

@prisma-next/adapter-postgres

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

@prisma-next/adapter-sqlite

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

@prisma-next/driver-postgres

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

@prisma-next/driver-sqlite

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

commit: f8ad1c1

Comment thread packages/2-sql/5-runtime/test/before-compile-chain.test.ts Outdated
@SevInf SevInf force-pushed the ast-codec-ids-v2 branch from 3d6422f to 3720761 Compare April 30, 2026 06:45
@SevInf SevInf marked this pull request as ready for review April 30, 2026 07:31
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: 5

Caution

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

⚠️ Outside diff range comments (2)
packages/3-extensions/sql-orm-client/src/query-plan-mutations.ts (1)

19-32: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Validate RETURNING columns before stamping codecId.

This path now silently builds ProjectionItems with codecId: undefined when tableName is unknown or a requested returning column is missing. That defers the failure to SQL execution and also disables decode-time codec lookup for that projection. Please mirror the validation style used in toParamAssignments() / resolveTableColumns() and then access table.columns[column] directly.

Suggested fix
 function buildReturningColumns(
   contract: Contract<SqlStorage>,
   tableName: string,
   returningColumns: readonly string[] | undefined,
 ): ReadonlyArray<ProjectionItem> {
   const columns =
     returningColumns && returningColumns.length > 0
       ? [...returningColumns]
       : resolveTableColumns(contract, tableName);

-  const table = contract.storage.tables[tableName];
-  return columns.map((column) =>
-    ProjectionItem.of(column, ColumnRef.of(tableName, column), table?.columns[column]?.codecId),
-  );
+  const table = contract.storage.tables[tableName];
+  if (!table) {
+    throw new Error(`Unknown table "${tableName}"`);
+  }
+
+  return columns.map((column) => {
+    const codecId = table.columns[column]?.codecId;
+    if (!codecId) {
+      throw new Error(`Unknown column "${column}" in table "${tableName}"`);
+    }
+    return ProjectionItem.of(column, ColumnRef.of(tableName, column), codecId);
+  });
 }

As per coding guidelines, "Remove optional chaining and conditional property spreading for properties that are guaranteed to exist after validation - access required properties directly without ?. operators".

🤖 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/query-plan-mutations.ts` around
lines 19 - 32, In buildReturningColumns, validate that the table exists and each
requested returning column exists before creating ProjectionItem instances:
first resolve the table via contract.storage.tables[tableName] and throw an
error if missing (matching resolveTableColumns/toParamAssignments style), then
iterate the chosen columns and verify table.columns[column] exists, throwing if
a column is unknown; only after these validations access
table.columns[column].codecId directly (remove the optional chaining/table?
usage) when constructing ProjectionItem.of.
packages/2-sql/5-runtime/src/middleware/before-compile-chain.ts (1)

13-28: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Preserve meta-only beforeCompile rewrites.

This now treats ast identity as the only change signal, so a middleware that returns the same AST with updated meta is dropped on the floor. That breaks both downstream middleware chaining and the final draft returned to SqlRuntimeImpl.runBeforeCompile(). Please keep the new draft when either ast or meta changes, or narrow the middleware contract to AST-only rewrites.

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

In `@packages/2-sql/5-runtime/src/middleware/before-compile-chain.ts` around lines
13 - 28, The middleware loop in before-compile-chain.ts currently ignores
rewrites that only change meta because it only compares result.ast ===
current.ast; update the logic in the loop that handles mw.beforeCompile (the
comparison around result.ast and current.ast) to treat the draft as changed when
either result.ast !== current.ast OR result.meta !== current.meta (or use a
deep/appropriate equality check for meta), so that current is replaced with
result when meta-only updates occur; keep the ctx.log.debug middleware.rewrite
behavior and ensure this preserves drafts passed back to
SqlRuntimeImpl.runBeforeCompile() and downstream middleware chaining.
🧹 Nitpick comments (3)
docs/architecture docs/adrs/ADR 205 - Execution metadata lives on AST.md (1)

108-117: ⚡ Quick win

Move implementation mechanics out of the ADR body.

This section reads like migration/implementation guidance (specific producers, visitors, adapter lowering changes). In this repo, ADRs are expected to stay at decision/constraint level; detailed rollout mechanics are better in subsystem docs or follow-up implementation notes.

Based on learnings: “ADRs in prisma/prisma-next should document architectural design decisions and the key constraints/assumptions only... move implementation guidance to subsystem documentation.”

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

In `@docs/architecture` docs/adrs/ADR 205 - Execution metadata lives on AST.md
around lines 108 - 117, The ADR includes low-level implementation and migration
guidance that should be removed from the decision document and moved into
subsystem implementation notes; specifically, remove or relocate the procedural
details referencing buildQueryPlan, buildOrmQueryPlan, the row decoder change to
walk projection lists, the RETURNING lowering changes
(InsertAst/UpdateAst/DeleteAst and ProjectionItem), the PlanMeta pruning, and
the test-migration guidance about annotations.codecs and
refs/paramDescriptors/raw-plan options—retain only the architectural decision,
constraints, and key assumptions in this ADR and create a separate
implementation doc (or link) containing the detailed steps, visitor/rewriter
edits, adapter lowering changes, and test migration instructions.
packages/2-sql/4-lanes/relational-core/test/ast/common.test.ts (1)

116-119: ⚡ Quick win

Prefer a single object matcher for related stamped field assertions.

This keeps failure output and style consistent with the test guidelines.

♻️ Suggested change
-    expect(stamped.codecId).toBe('pg/int4@1');
-    expect(stamped.alias).toBe('id');
-    expect(stamped.expr).toBe(expr);
+    expect(stamped).toMatchObject({
+      codecId: 'pg/int4@1',
+      alias: 'id',
+      expr,
+    });

As per coding guidelines, "Prefer object matchers (toMatchObject) over multiple individual expect().toBe() calls when checking 2 or more related values in tests".

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

In `@packages/2-sql/4-lanes/relational-core/test/ast/common.test.ts` around lines
116 - 119, Replace the three separate assertions on the stamped object with a
single object matcher: after creating stamped via
withoutCodec.withCodecId('pg/int4@1'), assert stamped matches an object
containing codecId: 'pg/int4@1', alias: 'id', and expr: expr (use toMatchObject
or equivalent) so the related fields are checked together; update the test that
references stamped, withoutCodec.withCodecId, codecId, alias, and expr
accordingly.
packages/2-sql/4-lanes/relational-core/src/ast/types.ts (1)

1071-1087: ⚡ Quick win

Expose codecId through the incremental projection API too.

ProjectionItem now carries execution metadata, but SelectAst.addProjection() still has no way to supply it. That makes the common incremental builder path lossy and easy to misuse after ADR 205.

Follow-up change
-  addProjection(alias: string, expr: ProjectionExpr): SelectAst {
+  addProjection(alias: string, expr: ProjectionExpr, codecId?: string): SelectAst {
     return new SelectAst({
       ...this,
-      projection: [...this.projection, new ProjectionItem(alias, expr)],
+      projection: [...this.projection, new ProjectionItem(alias, expr, codecId)],
     });
   }

As per coding guidelines: Keep public API stable while moving internals during refactoring of large monolithic files.

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

In `@packages/2-sql/4-lanes/relational-core/src/ast/types.ts` around lines 1071 -
1087, SelectAst.addProjection currently cannot accept ProjectionItem.execution
metadata (codecId) causing loss when building incrementally; update the
SelectAst.addProjection signature to accept an optional codecId: string |
undefined, and pass that codecId through when constructing ProjectionItem (use
new ProjectionItem(alias, expr, codecId) or ProjectionItem.of/withCodecId as
appropriate). Also update any incremental builder helpers that call
addProjection to accept/forward codecId (and adjust call sites that construct
ProjectionItem via ProjectionItem.of or withCodecId) so the execution metadata
is preserved across the API boundary.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@docs/architecture` docs/adrs/ADR 012 - Raw SQL Escape Hatch.md:
- Around line 3-4: The ADR text is inconsistent: the banner says the optional
structured-annotations branch (`refs`, `projection`, `codecs`,
`paramDescriptors`) was removed but the body still documents them as current;
update ADR 012 - Raw SQL Escape Hatch to either (a) mark all sections that
describe `refs`/`projection`/`codecs`/`paramDescriptors` as historical/retired
(prepend a "Deprecated / Historical" note and link to ADR 205) or (b) delete
those sections and rewrite any runtime behavior text to describe current
behavior (i.e., raw plans no longer provide those fields, they pass parameters
as supplied and surface wire-level row values), while ensuring the minimal
annotation schema (`intent`, `isMutation`, `hasWhere`, `hasLimit`) is clearly
presented as the active spec.

In `@packages/2-sql/5-runtime/src/codecs/decoding.ts`:
- Around line 47-55: The current logic treats an empty projection list
(projection === []) as valid metadata, causing aliases to be set to an empty
array and decodeRow to produce empty objects; update the early-return after
calling projectionListFromAst(plan.ast) so that it treats both falsy and
empty-array results as "no projection metadata" (i.e., when projection is
null/undefined OR projection.length === 0) and return aliases: undefined (and
the same empty codecs/columnRefs/includeAliases) so decodeRow preserves raw row
keys; locate the check around projectionListFromAst and adjust the condition
accordingly for projection and downstream use in decodeRow/withProjection
handling.

In `@packages/2-sql/5-runtime/src/codecs/encoding.ts`:
- Around line 85-88: When a stamped codec is missing, fail fast instead of
returning the raw value: in the branch where you call
registry.get(metadata.codecId) and receive undefined, check whether
metadata.codecId is set and, if so, throw a descriptive error (including the
missing metadata.codecId and relevant param/ref identifier) rather than
returning value; keep the existing behavior of returning value only when no
codecId is present. This change should be applied where codec is obtained
(registry.get), using the symbols metadata.codecId, codec and the surrounding
encoding/encode function logic.

In `@packages/2-sql/5-runtime/test/budgets.test.ts`:
- Around line 223-234: The test currently sets a limit via
SelectAst.withLimit(5) so the estimated rows will be 5 regardless of whether
budgets() uses tableRows.user or defaultTableRows; update the test to make the
assertion depend on the FROM lookup by removing or changing the limit and
adjusting budgets() inputs — for example, remove .withLimit(5) (or set a higher
limit) and set budgets({ maxRows: 4, defaultTableRows: 10_000, tableRows: {
user: 5 }}) or keep maxRows high and set defaultTableRows < tableRows.user so
that the planner’s estimate (from createPlan and budgets) differs when FROM
lookup is broken; ensure you reference SelectAst.from, .withProjection,
.withLimit, createPlan, and budgets in the change.

---

Outside diff comments:
In `@packages/2-sql/5-runtime/src/middleware/before-compile-chain.ts`:
- Around line 13-28: The middleware loop in before-compile-chain.ts currently
ignores rewrites that only change meta because it only compares result.ast ===
current.ast; update the logic in the loop that handles mw.beforeCompile (the
comparison around result.ast and current.ast) to treat the draft as changed when
either result.ast !== current.ast OR result.meta !== current.meta (or use a
deep/appropriate equality check for meta), so that current is replaced with
result when meta-only updates occur; keep the ctx.log.debug middleware.rewrite
behavior and ensure this preserves drafts passed back to
SqlRuntimeImpl.runBeforeCompile() and downstream middleware chaining.

In `@packages/3-extensions/sql-orm-client/src/query-plan-mutations.ts`:
- Around line 19-32: In buildReturningColumns, validate that the table exists
and each requested returning column exists before creating ProjectionItem
instances: first resolve the table via contract.storage.tables[tableName] and
throw an error if missing (matching resolveTableColumns/toParamAssignments
style), then iterate the chosen columns and verify table.columns[column] exists,
throwing if a column is unknown; only after these validations access
table.columns[column].codecId directly (remove the optional chaining/table?
usage) when constructing ProjectionItem.of.

---

Nitpick comments:
In `@docs/architecture` docs/adrs/ADR 205 - Execution metadata lives on AST.md:
- Around line 108-117: The ADR includes low-level implementation and migration
guidance that should be removed from the decision document and moved into
subsystem implementation notes; specifically, remove or relocate the procedural
details referencing buildQueryPlan, buildOrmQueryPlan, the row decoder change to
walk projection lists, the RETURNING lowering changes
(InsertAst/UpdateAst/DeleteAst and ProjectionItem), the PlanMeta pruning, and
the test-migration guidance about annotations.codecs and
refs/paramDescriptors/raw-plan options—retain only the architectural decision,
constraints, and key assumptions in this ADR and create a separate
implementation doc (or link) containing the detailed steps, visitor/rewriter
edits, adapter lowering changes, and test migration instructions.

In `@packages/2-sql/4-lanes/relational-core/src/ast/types.ts`:
- Around line 1071-1087: SelectAst.addProjection currently cannot accept
ProjectionItem.execution metadata (codecId) causing loss when building
incrementally; update the SelectAst.addProjection signature to accept an
optional codecId: string | undefined, and pass that codecId through when
constructing ProjectionItem (use new ProjectionItem(alias, expr, codecId) or
ProjectionItem.of/withCodecId as appropriate). Also update any incremental
builder helpers that call addProjection to accept/forward codecId (and adjust
call sites that construct ProjectionItem via ProjectionItem.of or withCodecId)
so the execution metadata is preserved across the API boundary.

In `@packages/2-sql/4-lanes/relational-core/test/ast/common.test.ts`:
- Around line 116-119: Replace the three separate assertions on the stamped
object with a single object matcher: after creating stamped via
withoutCodec.withCodecId('pg/int4@1'), assert stamped matches an object
containing codecId: 'pg/int4@1', alias: 'id', and expr: expr (use toMatchObject
or equivalent) so the related fields are checked together; update the test that
references stamped, withoutCodec.withCodecId, codecId, alias, and expr
accordingly.
🪄 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: 6c3adf38-5887-4752-b166-cce9405c55e6

📥 Commits

Reviewing files that changed from the base of the PR and between 975b674 and b1eb015.

📒 Files selected for processing (83)
  • docs/architecture docs/adrs/ADR 012 - Raw SQL Escape Hatch.md
  • docs/architecture docs/adrs/ADR 205 - Execution metadata lives on AST.md
  • examples/prisma-next-demo/test/runtime.offline.integration.test.ts
  • packages/1-framework/0-foundation/contract/README.md
  • packages/1-framework/0-foundation/contract/src/exports/types.ts
  • packages/1-framework/0-foundation/contract/src/types.ts
  • packages/1-framework/0-foundation/contract/test/types.test.ts
  • packages/1-framework/1-core/framework-components/test/mock-family.test.ts
  • packages/1-framework/1-core/framework-components/test/query-plan.types.test-d.ts
  • packages/1-framework/1-core/framework-components/test/run-with-middleware.test.ts
  • packages/1-framework/1-core/framework-components/test/runtime-core.test.ts
  • packages/1-framework/1-core/framework-components/test/runtime-core.types.test-d.ts
  • packages/2-mongo-family/4-query/query-ast/test/query-plan.types.test-d.ts
  • packages/2-mongo-family/5-query-builders/orm/src/collection.ts
  • packages/2-mongo-family/5-query-builders/orm/src/compile.ts
  • packages/2-mongo-family/5-query-builders/orm/src/mongo-raw.ts
  • packages/2-mongo-family/5-query-builders/query-builder/src/builder.ts
  • packages/2-mongo-family/5-query-builders/query-builder/src/query.ts
  • packages/2-mongo-family/5-query-builders/query-builder/src/state-classes.ts
  • packages/2-mongo-family/7-runtime/test/mongo-execution-plan.types.test-d.ts
  • packages/2-mongo-family/7-runtime/test/mongo-middleware.test.ts
  • packages/2-mongo-family/7-runtime/test/raw-commands.test.ts
  • packages/2-mongo-family/7-runtime/test/setup.ts
  • packages/2-sql/4-lanes/relational-core/README.md
  • packages/2-sql/4-lanes/relational-core/src/ast/types.ts
  • packages/2-sql/4-lanes/relational-core/src/plan.ts
  • packages/2-sql/4-lanes/relational-core/src/types.ts
  • packages/2-sql/4-lanes/relational-core/test/ast/builders.test.ts
  • packages/2-sql/4-lanes/relational-core/test/ast/common.test.ts
  • packages/2-sql/4-lanes/relational-core/test/ast/delete.test.ts
  • packages/2-sql/4-lanes/relational-core/test/ast/insert.test.ts
  • packages/2-sql/4-lanes/relational-core/test/ast/rich-ast.test.ts
  • packages/2-sql/4-lanes/relational-core/test/ast/test-helpers.ts
  • packages/2-sql/4-lanes/relational-core/test/ast/update.test.ts
  • packages/2-sql/4-lanes/relational-core/test/plan.test.ts
  • packages/2-sql/4-lanes/relational-core/test/sql-execution-plan.types.test-d.ts
  • packages/2-sql/4-lanes/sql-builder/src/runtime/builder-base.ts
  • packages/2-sql/4-lanes/sql-builder/src/runtime/mutation-impl.ts
  • packages/2-sql/5-runtime/src/codecs/decoding.ts
  • packages/2-sql/5-runtime/src/codecs/encoding.ts
  • packages/2-sql/5-runtime/src/guardrails/raw.ts
  • packages/2-sql/5-runtime/src/middleware/before-compile-chain.ts
  • packages/2-sql/5-runtime/src/middleware/budgets.ts
  • packages/2-sql/5-runtime/src/sql-runtime.ts
  • packages/2-sql/5-runtime/test/async-iterable-result.test.ts
  • packages/2-sql/5-runtime/test/before-compile-chain.test.ts
  • packages/2-sql/5-runtime/test/budgets.test.ts
  • packages/2-sql/5-runtime/test/codec-async.test.ts
  • packages/2-sql/5-runtime/test/json-schema-validation.test.ts
  • packages/2-sql/5-runtime/test/lints.test.ts
  • packages/2-sql/5-runtime/test/sql-family-adapter.test.ts
  • packages/2-sql/5-runtime/test/sql-runtime.test.ts
  • packages/3-extensions/middleware-telemetry/test/telemetry-middleware.test.ts
  • packages/3-extensions/sql-orm-client/src/query-plan-aggregate.ts
  • packages/3-extensions/sql-orm-client/src/query-plan-meta.ts
  • packages/3-extensions/sql-orm-client/src/query-plan-mutations.ts
  • packages/3-extensions/sql-orm-client/src/query-plan-select.ts
  • packages/3-extensions/sql-orm-client/src/where-binding.ts
  • packages/3-extensions/sql-orm-client/test/collection-mutation-dispatch.test.ts
  • packages/3-extensions/sql-orm-client/test/query-plan-aggregate.test.ts
  • packages/3-extensions/sql-orm-client/test/query-plan-meta.test.ts
  • packages/3-extensions/sql-orm-client/test/query-plan-mutations.test.ts
  • packages/3-extensions/sql-orm-client/test/query-plan-select.test.ts
  • packages/3-extensions/sql-orm-client/test/raw-compiled-query.test.ts
  • packages/3-extensions/sql-orm-client/test/rich-collection.test.ts
  • packages/3-extensions/sql-orm-client/test/rich-query-plans.test.ts
  • packages/3-mongo-target/1-mongo-target/src/core/mongo-ops-serializer.ts
  • packages/3-mongo-target/1-mongo-target/test/data-transform.test.ts
  • packages/3-mongo-target/1-mongo-target/test/migration-e2e.test.ts
  • packages/3-mongo-target/1-mongo-target/test/mongo-ops-serializer.dml.test.ts
  • packages/3-mongo-target/1-mongo-target/test/mongo-runner.test.ts
  • packages/3-mongo-target/2-mongo-adapter/test/mongo-adapter.test.ts
  • packages/3-mongo-target/2-mongo-adapter/test/mongo-runner.test.ts
  • packages/3-targets/6-adapters/postgres/src/core/sql-renderer.ts
  • packages/3-targets/6-adapters/postgres/test/adapter.test.ts
  • packages/3-targets/6-adapters/postgres/test/control-adapter-lower-parity.test.ts
  • packages/3-targets/6-adapters/postgres/test/migrations/data-transform.test.ts
  • packages/3-targets/6-adapters/postgres/test/rich-adapter.test.ts
  • packages/3-targets/6-adapters/sqlite/src/core/adapter.ts
  • packages/3-targets/6-adapters/sqlite/test/adapter.test.ts
  • test/e2e/framework/vitest.config.ts
  • test/integration/test/cross-package/cross-family-middleware.test.ts
  • test/integration/test/mongo/expr-filter.test.ts
💤 Files with no reviewable changes (35)
  • packages/2-mongo-family/7-runtime/test/mongo-middleware.test.ts
  • packages/1-framework/1-core/framework-components/test/runtime-core.types.test-d.ts
  • packages/2-mongo-family/7-runtime/test/setup.ts
  • packages/2-mongo-family/5-query-builders/orm/src/compile.ts
  • test/integration/test/mongo/expr-filter.test.ts
  • packages/2-sql/5-runtime/test/lints.test.ts
  • packages/2-mongo-family/5-query-builders/query-builder/src/query.ts
  • packages/2-mongo-family/5-query-builders/query-builder/src/state-classes.ts
  • packages/2-mongo-family/5-query-builders/orm/src/mongo-raw.ts
  • packages/1-framework/1-core/framework-components/test/runtime-core.test.ts
  • packages/2-mongo-family/5-query-builders/orm/src/collection.ts
  • packages/3-mongo-target/2-mongo-adapter/test/mongo-adapter.test.ts
  • packages/3-mongo-target/1-mongo-target/test/data-transform.test.ts
  • packages/2-sql/4-lanes/relational-core/test/ast/rich-ast.test.ts
  • packages/1-framework/1-core/framework-components/test/run-with-middleware.test.ts
  • packages/2-mongo-family/4-query/query-ast/test/query-plan.types.test-d.ts
  • packages/3-targets/6-adapters/postgres/test/migrations/data-transform.test.ts
  • packages/3-extensions/middleware-telemetry/test/telemetry-middleware.test.ts
  • packages/3-extensions/sql-orm-client/test/rich-collection.test.ts
  • packages/2-mongo-family/7-runtime/test/mongo-execution-plan.types.test-d.ts
  • packages/2-sql/5-runtime/test/async-iterable-result.test.ts
  • packages/3-mongo-target/1-mongo-target/test/mongo-runner.test.ts
  • packages/3-extensions/sql-orm-client/test/raw-compiled-query.test.ts
  • packages/1-framework/0-foundation/contract/src/exports/types.ts
  • packages/3-extensions/sql-orm-client/test/collection-mutation-dispatch.test.ts
  • test/integration/test/cross-package/cross-family-middleware.test.ts
  • packages/1-framework/1-core/framework-components/test/mock-family.test.ts
  • packages/2-sql/4-lanes/relational-core/test/plan.test.ts
  • packages/2-sql/5-runtime/test/sql-family-adapter.test.ts
  • packages/2-mongo-family/7-runtime/test/raw-commands.test.ts
  • packages/2-sql/4-lanes/relational-core/src/plan.ts
  • packages/3-mongo-target/1-mongo-target/src/core/mongo-ops-serializer.ts
  • packages/2-mongo-family/5-query-builders/query-builder/src/builder.ts
  • packages/2-sql/4-lanes/relational-core/test/sql-execution-plan.types.test-d.ts
  • packages/1-framework/1-core/framework-components/test/query-plan.types.test-d.ts

Comment thread docs/architecture docs/adrs/ADR 012 - Raw SQL Escape Hatch.md
Comment on lines +47 to 55
const projection = projectionListFromAst(plan.ast);
if (!projection) {
return {
aliases: undefined,
codecs: new Map(),
columnRefs: new Map(),
includeAliases: EMPTY_INCLUDE_ALIASES,
};
}
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot Apr 30, 2026

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Treat an empty projection list like “no projection metadata”.

When projectionListFromAst(plan.ast) is [], this builds aliases: [], and decodeRow() returns {} for every row because it iterates zero aliases. That breaks AST-backed plans that intentionally use withProjection([]) as a valid shape, including fixtures in packages/2-sql/5-runtime/test/sql-runtime.test.ts and packages/2-sql/5-runtime/test/before-compile-chain.test.ts. Falling back to aliases: undefined for empty projections preserves the raw row keys until there is explicit projection metadata.

Suggested fix
   const projection = projectionListFromAst(plan.ast);
-  if (!projection) {
+  if (!projection || projection.length === 0) {
     return {
       aliases: undefined,
       codecs: new Map(),
       columnRefs: new Map(),
       includeAliases: EMPTY_INCLUDE_ALIASES,
     };
   }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/2-sql/5-runtime/src/codecs/decoding.ts` around lines 47 - 55, The
current logic treats an empty projection list (projection === []) as valid
metadata, causing aliases to be set to an empty array and decodeRow to produce
empty objects; update the early-return after calling
projectionListFromAst(plan.ast) so that it treats both falsy and empty-array
results as "no projection metadata" (i.e., when projection is null/undefined OR
projection.length === 0) and return aliases: undefined (and the same empty
codecs/columnRefs/includeAliases) so decodeRow preserves raw row keys; locate
the check around projectionListFromAst and adjust the condition accordingly for
projection and downstream use in decodeRow/withProjection handling.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

withProjection([]) will produce invalid SQL, it does not matter what metadata it has

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

@SevInf, that's a fair point — if withProjection([]) produces invalid SQL, the query never executes and decodeRow is never reached with an empty projection. My concern was a false positive. The current code is correct as-is.


🧠 Learnings used
Learnt from: wmadden
Repo: prisma/prisma-next PR: 38
File: packages/2-sql/3-tooling/family/src/core/migrations/plan-helpers.ts:40-47
Timestamp: 2025-12-26T23:02:40.679Z
Learning: In Prisma-next's TypeScript codebase, prefer using well-established utility libraries (e.g., Lodash) rather than building custom utilities for complex object operations such as deep freezing or deep cloning. Favor reliable, battle-tested functions and ensure dependencies are reviewed for tree-shaking and bundle size. If a lightweight utility suffices, consider modern native approaches or smaller libs.

Learnt from: wmadden
Repo: prisma/prisma-next PR: 191
File: packages/1-framework/3-tooling/cli/src/commands/contract-emit.ts:5-5
Timestamp: 2026-03-01T13:54:21.863Z
Learning: In the Prisma-next repository, prefer importing and using 'pathe' over the built-in 'node:path' module for path operations across the codebase (including CLI commands and tooling files). Apply this in TypeScript files, replacing imports from 'node:path' with 'pathe' and adjusting API usage accordingly. Ensure consistent usage across all modules and update any affected tests or tooling imports when refactoring.

Learnt from: wmadden
Repo: prisma/prisma-next PR: 234
File: packages/2-sql/4-lanes/sql-lane/test/rich-mutation.test.ts:2-2
Timestamp: 2026-03-23T10:01:15.075Z
Learning: In the prisma/prisma-next repo, prefer using `pathe` over Node’s built-in `node:path`. For any TypeScript file (including tests, test helpers, and integration tooling) replace imports like `import { dirname, join } from 'node:path'` with `import { dirname, join } from 'pathe'`—especially when code reads files/fixtures from disk.

Learnt from: wmadden
Repo: prisma/prisma-next PR: 294
File: packages/2-mongo-family/2-query/query-ast/src/read-plan.ts:4-10
Timestamp: 2026-04-04T10:08:34.220Z
Learning: In prisma/prisma-next, don’t flag declaration-emit/typecheck warnings/errors when a file uses an intentional nominal/branding pattern with a non-exported `declare const <brand>: unique symbol` as a computed property key on an exported interface (e.g., `readonly [__mongoReadPlanRow]: ...` / `readonly [aggregateResultBrand]: ...`). The `unique symbol` must remain unexported to prevent external forging, while the branded property key staying on the exported interface provides compile-time discrimination. If the build correctly emits declaration files for this pattern, treat it as intentional rather than a declaration-emit error.

Learnt from: wmadden
Repo: prisma/prisma-next PR: 293
File: packages/1-framework/1-core/shared/contract/src/canonicalization.ts:203-216
Timestamp: 2026-04-04T12:19:05.250Z
Learning: In the prisma/prisma-next repository, do not treat `Array.prototype.sort` comparators as “potentially non-deterministic” solely because the comparator does not include explicit tie-breaking keys. This is because the repo’s required runtime is Node >= 24, where `Array.prototype.sort` is stable (equal elements retain their original relative order via a stable sort such as TimSort). Therefore, equal-comparing elements should remain deterministic without additional tie-break logic.

Learnt from: wmadden
Repo: prisma/prisma-next PR: 334
File: packages/2-mongo-family/9-family/src/core/control-instance.ts:204-228
Timestamp: 2026-04-13T15:54:52.337Z
Learning: When reviewing TypeScript code in this repo, do not assume a property is potentially `undefined` at a call site just because an arktype schema definition uses the DSL syntax `'key?': 'type'` (a quoted string key with a trailing `?`). This DSL notation is not the same as TypeScript optional properties (`key?: type`). To determine whether `key` is truly optional/undefined-capable, verify the actual exported TypeScript type declaration (e.g., `contract-types.ts`) and the runtime validation schema (e.g., `validate-contract.ts`) for that property; only then should the review flag call-site handling for potential `undefined`.

Learnt from: wmadden
Repo: prisma/prisma-next PR: 339
File: test/integration/test/cli.init-templates.e2e.test.ts:96-96
Timestamp: 2026-04-15T19:33:48.733Z
Learning: In prisma/prisma-next, when writing tests that run TypeScript compilation (e.g., via `tsc --noEmit`), avoid hardcoded timeout numbers (such as `30_000`). Instead, use `prisma-next/test-utils` timeout helpers—specifically `timeouts.typeScriptCompilation` (implemented in `test/utils/src/timeouts.ts`) for `tsc` compilation scenarios. Use `timeouts.spinUpPpgDev` for PostgreSQL server startup and `timeouts.databaseOperation` for database operation scenarios.

Learnt from: saevarb
Repo: prisma/prisma-next PR: 394
File: packages/1-framework/3-tooling/migration/src/graph-ops.ts:50-54
Timestamp: 2026-04-29T12:44:59.376Z
Learning: In TypeScript overload implementations, an internal “implementation-only” cast (e.g., `state as unknown as <ReturnType>`) can be safe and intentional when the public overload signatures and their structural typing prevent callers from reaching the implementation with an incompatible type. In these cases, reviewers should not flag the cast as a blind/unsafe cast, since compile-time overload enforcement substitutes for a runtime type guard. Don’t require an added `typeof`/runtime check solely to silence the cast when the overload design already prevents invalid inputs.

Comment on lines +85 to 88
const codec: Codec | undefined = registry.get(metadata.codecId);
if (!codec) {
return value;
}
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot Apr 30, 2026

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Fail fast when a stamped codec is missing.

If ParamRef.codecId is present, returning the raw value here turns a runtime/configuration error into silent passthrough. That can skip required transforms like encryption or normalization and send the wrong wire shape to the driver.

Possible fix
   const codec: Codec | undefined = registry.get(metadata.codecId);
   if (!codec) {
-    return value;
+    const label = paramLabel(metadata, paramIndex);
+    throw runtimeError(
+      'RUNTIME.ENCODE_FAILED',
+      `Failed to encode parameter ${label} with codec '${metadata.codecId}': codec is not registered`,
+      { label, codec: metadata.codecId, paramIndex },
+    );
   }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/2-sql/5-runtime/src/codecs/encoding.ts` around lines 85 - 88, When a
stamped codec is missing, fail fast instead of returning the raw value: in the
branch where you call registry.get(metadata.codecId) and receive undefined,
check whether metadata.codecId is set and, if so, throw a descriptive error
(including the missing metadata.codecId and relevant param/ref identifier)
rather than returning value; keep the existing behavior of returning value only
when no codecId is present. This change should be applied where codec is
obtained (registry.get), using the symbols metadata.codecId, codec and the
surrounding encoding/encode function logic.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Eventually we will want to make codecId required and the problem will go away

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

@SevInf, understood — the current soft passthrough is consistent with codecId being intentionally optional for now (as noted in the PR description). Once codecId is made required on ProjectionItem (and ParamRef), the "codec stamped but not registered" case becomes a genuine misconfiguration and the fail-fast path should be added at that point.

It might be worth dropping a // TODO: throw when codecId is present but codec is not registered (once codecId is required) comment in the if (!codec) branch so the future hardening is discoverable, but that's entirely up to you.


✏️ Learnings added
Learnt from: SevInf
Repo: prisma/prisma-next PR: 393
File: packages/2-sql/5-runtime/src/codecs/encoding.ts:85-88
Timestamp: 2026-04-30T09:39:58.142Z
Learning: In prisma/prisma-next, `ProjectionItem.codecId` and `ParamRef.codecId` are intentionally optional for now (per ADR 205 and PR `#393`). The encoding path (`encodeParamValue` in `packages/2-sql/5-runtime/src/codecs/encoding.ts`) silently passes through the raw value when a stamped `codecId` cannot be resolved in the registry — this is by design until `codecId` is made required. Do not flag this as an unconditional bug; the fail-fast behaviour should only be enforced once `codecId` is required.

Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.


🧠 Learnings used
Learnt from: wmadden
Repo: prisma/prisma-next PR: 38
File: packages/2-sql/3-tooling/family/src/core/migrations/plan-helpers.ts:40-47
Timestamp: 2025-12-26T23:02:40.679Z
Learning: In Prisma-next's TypeScript codebase, prefer using well-established utility libraries (e.g., Lodash) rather than building custom utilities for complex object operations such as deep freezing or deep cloning. Favor reliable, battle-tested functions and ensure dependencies are reviewed for tree-shaking and bundle size. If a lightweight utility suffices, consider modern native approaches or smaller libs.

Learnt from: wmadden
Repo: prisma/prisma-next PR: 191
File: packages/1-framework/3-tooling/cli/src/commands/contract-emit.ts:5-5
Timestamp: 2026-03-01T13:54:21.863Z
Learning: In the Prisma-next repository, prefer importing and using 'pathe' over the built-in 'node:path' module for path operations across the codebase (including CLI commands and tooling files). Apply this in TypeScript files, replacing imports from 'node:path' with 'pathe' and adjusting API usage accordingly. Ensure consistent usage across all modules and update any affected tests or tooling imports when refactoring.

Learnt from: wmadden
Repo: prisma/prisma-next PR: 234
File: packages/2-sql/4-lanes/sql-lane/test/rich-mutation.test.ts:2-2
Timestamp: 2026-03-23T10:01:15.075Z
Learning: In the prisma/prisma-next repo, prefer using `pathe` over Node’s built-in `node:path`. For any TypeScript file (including tests, test helpers, and integration tooling) replace imports like `import { dirname, join } from 'node:path'` with `import { dirname, join } from 'pathe'`—especially when code reads files/fixtures from disk.

Learnt from: wmadden
Repo: prisma/prisma-next PR: 294
File: packages/2-mongo-family/2-query/query-ast/src/read-plan.ts:4-10
Timestamp: 2026-04-04T10:08:34.220Z
Learning: In prisma/prisma-next, don’t flag declaration-emit/typecheck warnings/errors when a file uses an intentional nominal/branding pattern with a non-exported `declare const <brand>: unique symbol` as a computed property key on an exported interface (e.g., `readonly [__mongoReadPlanRow]: ...` / `readonly [aggregateResultBrand]: ...`). The `unique symbol` must remain unexported to prevent external forging, while the branded property key staying on the exported interface provides compile-time discrimination. If the build correctly emits declaration files for this pattern, treat it as intentional rather than a declaration-emit error.

Learnt from: wmadden
Repo: prisma/prisma-next PR: 293
File: packages/1-framework/1-core/shared/contract/src/canonicalization.ts:203-216
Timestamp: 2026-04-04T12:19:05.250Z
Learning: In the prisma/prisma-next repository, do not treat `Array.prototype.sort` comparators as “potentially non-deterministic” solely because the comparator does not include explicit tie-breaking keys. This is because the repo’s required runtime is Node >= 24, where `Array.prototype.sort` is stable (equal elements retain their original relative order via a stable sort such as TimSort). Therefore, equal-comparing elements should remain deterministic without additional tie-break logic.

Learnt from: wmadden
Repo: prisma/prisma-next PR: 334
File: packages/2-mongo-family/9-family/src/core/control-instance.ts:204-228
Timestamp: 2026-04-13T15:54:52.337Z
Learning: When reviewing TypeScript code in this repo, do not assume a property is potentially `undefined` at a call site just because an arktype schema definition uses the DSL syntax `'key?': 'type'` (a quoted string key with a trailing `?`). This DSL notation is not the same as TypeScript optional properties (`key?: type`). To determine whether `key` is truly optional/undefined-capable, verify the actual exported TypeScript type declaration (e.g., `contract-types.ts`) and the runtime validation schema (e.g., `validate-contract.ts`) for that property; only then should the review flag call-site handling for potential `undefined`.

Learnt from: wmadden
Repo: prisma/prisma-next PR: 339
File: test/integration/test/cli.init-templates.e2e.test.ts:96-96
Timestamp: 2026-04-15T19:33:48.733Z
Learning: In prisma/prisma-next, when writing tests that run TypeScript compilation (e.g., via `tsc --noEmit`), avoid hardcoded timeout numbers (such as `30_000`). Instead, use `prisma-next/test-utils` timeout helpers—specifically `timeouts.typeScriptCompilation` (implemented in `test/utils/src/timeouts.ts`) for `tsc` compilation scenarios. Use `timeouts.spinUpPpgDev` for PostgreSQL server startup and `timeouts.databaseOperation` for database operation scenarios.

Learnt from: saevarb
Repo: prisma/prisma-next PR: 394
File: packages/1-framework/3-tooling/migration/src/graph-ops.ts:50-54
Timestamp: 2026-04-29T12:44:59.376Z
Learning: In TypeScript overload implementations, an internal “implementation-only” cast (e.g., `state as unknown as <ReturnType>`) can be safe and intentional when the public overload signatures and their structural typing prevent callers from reaching the implementation with an incompatible type. In these cases, reviewers should not flag the cast as a blind/unsafe cast, since compile-time overload enforcement substitutes for a runtime type guard. Don’t require an added `typeof`/runtime check solely to silence the cast when the overload design already prevents invalid inputs.

Comment thread packages/2-sql/5-runtime/src/codecs/encoding.ts
Comment thread packages/2-sql/5-runtime/test/budgets.test.ts
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 change does what ADR 205 describes: AST as the single source of execution metadata, runBeforeCompileChain correct-by-construction with no paramDescriptors re-derivation, sidecars deleted at the source with no compat shims. The new alias-rename test in before-compile-chain.test.ts is a strong end-to-end pin for the rewrite-then-decode story.

Four structural items are worth addressing or responding to before merge — left as inline comments on the relevant lines. None of them block merge in my view, so no re-review needed: address inline or reply with a deferral, then merge.

Structural (S1–S4):

  • S1: decodeRow silently masks alias/row mismatch for AST-backed plans (decoded row gets undefined for absent keys, no error).
  • S2: Encoder maps paramRefsByValueOrder(ast)[i] → plan.params[i] by index without verifying refs.length === paramCount — silent miscoding if any of the three independent walks of collectParamRefs() ever drift.
  • S3: Aggregate ProjectionItems carry no codecId; the decoder hands wire values through (Postgres count(*) arrives as a string "42"). Pre-existing, not a regression — but the PR's design (codec-on-projection) makes it fixable in exactly the place it belongs.
  • S4: No test exercises the alias-differs RETURNING SQL text or codec-decoded RETURNING via ProjectionItem.codecId. The renderer's <table>.<column> AS <alias> branch is implemented but unverified; AC22 (codec-decoded RETURNING) is reachable in production but unasserted.

Full review (system-design + code-review + walkthrough) is local under wip/review-code/pr-393/. Acceptance-criteria verification: 23 PASS, 2 WEAK (AC14, AC15 — both closed by S4), 1 NOT VERIFIED (AC22 — also closed by S4), 0 FAIL.

Follow-ups already noted in the PR description (e.g. promoting ProjectionItem.codecId to required) are out of scope here.

Comment thread packages/2-sql/5-runtime/src/codecs/decoding.ts
Comment thread packages/2-sql/5-runtime/src/codecs/encoding.ts
@@ -164,6 +167,6 @@ export function compileGroupedAggregate(
ast = ast.withHaving(validateGroupedHavingExpr(havingExpr));
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.

S3 — Aggregate ProjectionItems carry no codecId; the decoder passes wire values through.

Line 119 (compileAggregate) and line 154 (compileGroupedAggregate) emit ProjectionItem.of(alias, toAggregateExpr(tableName, selector)) with no codec. The decoder's path for !codec → return wire value as-is (decoding.ts:171–174) means the application sees whatever the driver hands back.

For Postgres, the pg driver returns bigint (count, sum-of-int) as a JavaScript string by default. The application sees "42" instead of 42 / 42n. For min/max/sum/avg of a typed column, the result type is inferable from the underlying column's codec; today it isn't propagated.

This is not a regression introduced by this PR — pre-PR compileAggregate also did not populate codec info for aggregate aliases (the old projectionTypes map likewise did not include them). But the PR's design (codec-on-ProjectionItem) makes this fixable in exactly the place it belongs, and the spec's text — "every ProjectionItem emitted by either producer carries codecId whenever the producer's existing scope-field machinery has type information for that alias" — reads as if it should be addressed here.

Suggestion: Either (a) stamp known aggregate codec IDs at the aggregate construction sites (countpg/int8@1; sum/avg/min/max → propagate from the underlying column codec via the operations registry the runtime already consults for SQL operations); or (b) explicitly defer with a code comment naming the follow-up. The current state — silently passing aggregate strings back to the application — is a real footgun, even though it pre-dates this PR.

Comment thread packages/3-targets/6-adapters/postgres/src/core/sql-renderer.ts
@SevInf SevInf enabled auto-merge (squash) April 30, 2026 10:43
@SevInf SevInf force-pushed the ast-codec-ids-v2 branch from 393e62d to cf5187b Compare April 30, 2026 10:43
SevInf added 13 commits April 30, 2026 15:39
Make the SQL AST the single source of truth for execution metadata.
Remove the `refs`, `paramDescriptors`, `annotations.codecs`, and
`projectionTypes` sidecars from `PlanMeta`; promote `ProjectionItem`
to carry an optional `codecId`; promote `Insert/Update/Delete.returning`
from `ColumnRef[]` to `ProjectionItem[]`; migrate the runtime decoder,
encoder, lints middleware, budgets middleware, and Postgres / SQLite
RETURNING lowering to read from the AST.

AST + contract types
- `ProjectionItem` gains an optional `codecId?: string` (constructor,
  `static of`, `withCodecId`).
- `Insert/Update/Delete.returning` is `ReadonlyArray<ProjectionItem>`;
  the mutation ASTs gain `rewrite()` methods that descend into each
  `ProjectionItem.expr` of `returning`, mirroring `SelectAst.projection`.
  `collectParamRefs()` walks the new shape.
- `PlanMeta` shrinks to identification + policy fields only; the
  `paramDescriptors` / `refs` / `projection` / `projectionTypes` /
  `annotations.codecs` fields are gone, along with `ParamDescriptor`
  and `PlanRefs` from `@prisma-next/contract`.
- `RawTemplateOptions` / `RawFunctionOptions` no longer accept `refs`
  or `projection`; raw plans pass parameters to the driver as supplied
  and surface wire-level row values back without codec transformation.
- The dead AST traversal helpers (`QueryAst.collectRefs`,
  `FromSource.collectRefs`, mutation/select `collectRefs` overrides,
  and the supporting `mergeRefsInto` / `addColumnRefToRefSets` /
  `sortRefs` helpers) are removed; nothing in production reads them
  after the sidecars are gone.

Producers
- `buildQueryPlan` (sql-builder) and `buildOrmQueryPlan` (sql-orm-client)
  stamp `codecId` onto each emitted `ProjectionItem` (SELECT projections
  and INSERT/UPDATE/DELETE RETURNING items) using the same scope-field /
  contract-column lookup that previously populated `projectionTypes`.
  Both producers stop emitting the four sidecar fields.

Runtime
- The row decoder builds its alias→codec lookup by walking the AST's
  projection list (or `returning` list for mutation plans). For plans
  without an AST (raw plans) the decoder hands wire-level row values to
  the caller without codec-based transformation.
- The parameter encoder reads `codecId` from each `ParamRef` collected
  off the AST. Raw plans pass parameters to the driver as supplied.
- `runBeforeCompileChain` no longer re-derives `paramDescriptors` after
  AST rewrites — the AST is now the source of truth, so middleware that
  introduces ParamRefs is correct-by-construction.
- The lints middleware runs structural lints only on AST-backed plans;
  raw plans get the existing SQL-string heuristics. The budgets
  middleware derives the primary table from `ast.from`; the raw-plan
  heuristic collapses to a SQL-text LIMIT regex. Raw guardrails drop
  the refs-based `evaluateIndexCoverage` path.

Adapter
- The Postgres and SQLite SQL renderers lower RETURNING from
  `ProjectionItem[]`. For column-ref projections they emit
  `<table>.<column>` when the alias matches the column name and
  `<table>.<column> AS <alias>` when it differs; non-`ColumnRef`
  projection expressions render through the expression renderer with
  `AS <alias>`.

Cross-package cleanup
- Mongo query-builder / ORM / state-classes producers and the Mongo
  plan-JSON serializer drop `paramDescriptors`. Test fixtures across
  framework-components, middleware-telemetry, mongo-family,
  mongo-target, and cross-package integration tests are updated to the
  narrowed `PlanMeta` shape and AST-backed plans.

Documentation
- ADR 012 carries a banner marking its optional structured-annotations
  branch (`refs`, `projection`, codec maps) as retired by ADR 205. The
  minimal annotation schema (`intent`, `isMutation`, `hasWhere`,
  `hasLimit`) is unchanged.
- The driving `projects/execution-metadata-on-ast/` workspace (spec +
  plan) is included; ADR 205 itself remains canonical under
  `docs/architecture docs/adrs/`.
Address review feedback on the ADR 205 PR:

- budgets middleware: drop the raw-plan SQL-string row-count heuristic
  (`evaluateWithHeuristics`). After ADR 205, raw plans get no budgets
  validation; only AST-backed SELECTs get the row-count check. Three
  obsolete tests in budgets.test.ts removed.
- sql-runtime.ts: restore a JSDoc on `runBeforeCompile` describing the
  no-sidecar reconciliation contract (paramDescriptors re-derivation is
  no longer needed because the encoder reads from the AST directly).
- before-compile-chain.test.ts: rewrite the explanatory comment above
  the alias-swap end-to-end test to be concrete about the test
  mechanics (rename, codec effect, what the assertion would prove
  false).
- contract/README.md, relational-core/README.md: drop references to
  deleted `ParamDescriptor` / `PlanRefs` / `augmentDescriptorWithColumnMeta`;
  point readers at ADR 205 and the AST-borne codec IDs instead.
Strip "(ADR 205)" / "Per ADR 205, ..." references from the JSDoc on
`runBeforeCompile` and the test explanation above the alias-swap
end-to-end test in `before-compile-chain.test.ts`. The comments now
explain the invariant directly without pointing readers off-site.
- Delete `projects/execution-metadata-on-ast/{plan,spec}.md` from the PR
  per the project close-out task; ADR 205 is canonical under
  `docs/architecture docs/adrs/`.
- Remove the multi-line explanatory comment above the alias-swap
  end-to-end test in `before-compile-chain.test.ts` (PR review).
The DSL builder now stamps `codecId` onto every `ProjectionItem` it
emits, so the offline integration test's `.toEqual(ProjectionItem.of(
alias, expr))` assertion no longer matches. Update both projection
expectations to include the contract codec IDs (`sql/char@1` for
`user.id`, `pg/text@1` for `user.email`).
The e2e framework suite ran with `timeouts.default` (100ms × multiplier),
which produced 200ms test timeouts on CI workers. Three SQLite migration
tests (additive, default-drift, fk-preservation) intermittently exceeded
that budget despite running in 11–69ms locally. e2e tests exercise real
databases (SQLite/Postgres migrations + DML), so `timeouts.databaseOperation`
(5s × multiplier) is the correct semantic ceiling.
Semgrep's `javascript.express.security.audit.remote-property-injection`
rule fires on legitimate bracket access where the index is a numeric
loop counter (e.g. `metadata[i] ?? NO_METADATA` in
`encoding.ts:125`). The rule is rooted in untrusted-input scenarios for
Express handlers — not relevant to this repo, which has no HTTP
request-borne indices in the surfaces this rule scans. Add it to the
existing `--exclude-rule` list alongside the wkhtmltoimage rule so PR
review noise doesn't accumulate.
The previous version of the budgets-middleware "reads from FROM clause"
test asserted no warning under
`maxRows: 10_000, defaultTableRows: 10_000, tableRows.user: 5, limit: 5`.
At those values the estimate is 5 whether the FROM lookup hits
`tableRows.user` (returning 5) or short-circuits to null (table
unresolved). The "no warning" assertion held either way, so the test
did not actually verify the invariant in its name.

Re-pick parameters so the assertion only passes when the FROM lookup
hits `tableRows.user`: `limit: 10`, `maxRows: 4`,
`tableRows.user: 5`, `defaultTableRows: 10_000`. Estimate becomes
`min(limit=10, tableRows.user=5) = 5`, exceeds maxRows=4, and emits
`BUDGET.ROWS_EXCEEDED` with `details.estimatedRows: 5`. If the FROM
lookup ever broke, the estimate would be null (no emit) or fall back
to defaultTableRows (`min(10, 10_000) = 10`), and the
`estimatedRows: 5` assertion would fail.
…ked plans

decodeRow derives `aliases` from the AST projection list and reads
`row[alias]`. Before this change, a row whose keys didn't match the AST
aliases (driver bug, adapter bug, identifier-quoting drift) silently
produced a decoded row with every aliased key set to `undefined` — the
`decodeField` short-circuit treated `wireValue === undefined` the same
as a SQL NULL.

For AST-backed plans only, assert presence of every alias on the row
via `Object.hasOwn` and throw `RUNTIME.DECODE_FAILED` on miss with
`{ alias, expectedAliases, presentKeys }`. Tighten `decodeField`'s
early short-circuit to `wireValue === null` so SQL NULL stays distinct
from "key absent".

Two new tests cover the alias-mismatch error and the preserved-null
behavior. Raw plans (no AST) still fall through to `Object.keys(row)`
and pass-through decode.
…entity-deduped walks

Four call sites independently dedupe `ast.collectParamRefs()` by
ParamRef identity in first-encounter order to align `plan.params[i]`
with metadata-by-index. The encoder relies on all four agreeing on
order and count; copy-pasted loops made that invariant fragile.

Extract `collectOrderedParamRefs(ast)` in
`relational-core/src/ast/util.ts`, exported via
`@prisma-next/sql-relational-core/ast`. Switch the four sites to
consume it: `deriveParamsFromAst` (orm-client), `buildQueryPlan`
(sql-builder), `encodeParams` (sql-runtime, replacing the now-deleted
local `paramRefsByValueOrder`), and the Postgres renderer's `$N`
index map.

SQLite's `?`-placeholder renderer keeps its non-deduped walk: it needs
one params entry per occurrence in the SQL.
For min/max aggregates, the result type is identical to the input
column's type, so propagate the underlying column's codecId from the
contract onto the emitted `ProjectionItem`. Both `compileAggregate`
and `compileGroupedAggregate` route through the same
`toAggregateProjection` helper that returns `{ expr, codecId }`.

count, sum, avg are intentionally left with `codecId: undefined`:
- count returns a target-specific bigint (mapping not derivable in
  the orm-client without target coupling).
- sum widens (int4 → int8 in Postgres) and avg → numeric; both need
  target+input-aware mapping that doesn't yet exist.

Three new tests pin the propagation for min/max (plain + grouped) and
the deferred-undefined state for count/sum/avg, so a future change to
add them is forced through tests.
Two coverage gaps in the previous test suite:

1. Postgres + SQLite renderers' `<table>.<column> AS <alias>`
   RETURNING branch was reached only by alias-equals-column tests, so
   the AS-suffix path was implemented but unverified.
2. The decoder treats SELECT projections and mutation RETURNING
   symmetrically, but no test specifically constructed a mutation AST
   with a codec on a `returning` ProjectionItem and asserted decodeRow
   applied that codec to the wire value.

Add three tests:
- adapter-postgres `rich-adapter.test.ts`: assert
  `RETURNING "user"."id" AS "user_id"` for an Insert AST whose
  returning alias differs from the underlying column.
- adapter-sqlite `adapter.test.ts`: SQLite parity (same shape with `?`
  placeholders).
- sql-runtime `before-compile-chain.test.ts`: build an Insert AST
  with `ProjectionItem.of('id', ColumnRef.of(...), 'pg/int4@1')` on
  returning, run decodeRow against a wire row, and assert the codec
  fired (decode adds 100, so wire 7 → decoded 107).
The new helper extracted in the previous commit had no direct unit
tests, dropping `src/ast/util.ts` coverage below the package
thresholds (statements 50% < 96%, branches 80% < 91%, functions 50% <
95%). Add four tests covering depth-first ordering, identity
deduplication, distinct instances with equal values, and the
empty-AST frozen-array path. Coverage now reads 100% across all
metrics.
@SevInf SevInf force-pushed the ast-codec-ids-v2 branch from 83eea8c to f8ad1c1 Compare April 30, 2026 13:39
@SevInf SevInf merged commit fba7f45 into main Apr 30, 2026
16 checks passed
@SevInf SevInf deleted the ast-codec-ids-v2 branch April 30, 2026 13:50
cursor Bot pushed a commit that referenced this pull request Apr 30, 2026
Biome organize-imports rule expects type/value imports interleaved
alphabetically rather than grouped. Resolves CI Lint failure introduced
by the post-#393 rebase merge of three import sources.
cursor Bot pushed a commit that referenced this pull request May 1, 2026
Biome organize-imports rule expects type/value imports interleaved
alphabetically rather than grouped. Resolves CI Lint failure introduced
by the post-#393 rebase merge of three import sources.
cursor Bot pushed a commit that referenced this pull request May 1, 2026
Biome organize-imports rule expects type/value imports interleaved
alphabetically rather than grouped. Resolves CI Lint failure introduced
by the post-#393 rebase merge of three import sources.
wmadden pushed a commit that referenced this pull request May 1, 2026
Biome organize-imports rule expects type/value imports interleaved
alphabetically rather than grouped. Resolves CI Lint failure introduced
by the post-#393 rebase merge of three import sources.
wmadden pushed a commit that referenced this pull request May 4, 2026
closes
[TML-2311](https://linear.app/prisma-company/issue/TML-2311/pn-execution-metadata-lives-on-ast)

## Intent

Make the SQL AST the single source of truth for execution metadata.
Today two channels carry it: the AST itself, and a sidecar bag on
`PlanMeta` (`refs`, `paramDescriptors`, `annotations.codecs`,
`projectionTypes`). Both builder paths populate the sidecar by walking
the AST and flattening per-node info into per-alias and per-index maps;
the runtime then chooses inconsistently which channel to read. With the
`beforeCompile` middleware hook now in main (PR #373), AST rewrites
silently invalidate every alias-keyed or index-keyed sidecar entry, and
the recently merged `re-derive paramDescriptors after middleware AST
rewrite` workaround lives exactly to paper over that drift.

ADR 205 collapses the two channels into one. `ProjectionItem` carries
the projection's output codec; `ParamRef` already carries its parameter
codec. The decoder builds its alias→codec lookup by walking the AST; the
encoder reads from `ParamRef.codecId`. `PlanMeta` shrinks to
identification + policy fields. Raw plans — which have no AST — pass
parameters and rows through to the driver and back, consistent with the
escape-hatch contract from ADR 012.

## Change map

- **AST + contract types** —
`packages/1-framework/0-foundation/contract/src/types.ts`,
`packages/2-sql/4-lanes/relational-core/src/ast/types.ts`,
`packages/2-sql/4-lanes/relational-core/src/types.ts`.
`ProjectionItem.codecId` is now optional.
`Insert/Update/Delete.returning` is `ProjectionItem[]`. Mutation ASTs
gain `rewrite()` methods that descend into `returning`. `PlanMeta` loses
`paramDescriptors`/`refs`/`projection`/`projectionTypes`/`annotations.codecs`.
`ParamDescriptor` and `PlanRefs` are deleted from the contract package.
`RawTemplateOptions` loses `refs` and `projection`. The dead AST
traversal helpers (`collectRefs` on `QueryAst`/`FromSource`/all
subclasses, plus `mergeRefsInto`/`addColumnRefToRefSets`/`sortRefs`) are
removed too.
- **Producers** —
`packages/2-sql/4-lanes/sql-builder/src/runtime/builder-base.ts`,
`packages/2-sql/4-lanes/sql-builder/src/runtime/mutation-impl.ts`,
`packages/3-extensions/sql-orm-client/src/query-plan-{meta,select,mutations,aggregate}.ts`,
`packages/3-extensions/sql-orm-client/src/where-binding.ts`. Every
emitted `ProjectionItem` is now stamped with the codec ID the producer
already had in scope (column lookup for SELECTs and RETURNINGs,
scope-field lookup for the DSL builder). Both producers stop emitting
the four sidecar fields.
- **Runtime** —
`packages/2-sql/5-runtime/src/codecs/{decoding,encoding}.ts`,
`packages/2-sql/5-runtime/src/middleware/{before-compile-chain,budgets}.ts`,
`packages/2-sql/5-runtime/src/guardrails/raw.ts`. The decoder walks the
AST's projection/returning list to build its alias→codec lookup; the
encoder reads `codecId` off each `ParamRef` collected from the AST.
`runBeforeCompileChain` no longer re-derives `paramDescriptors`. The
budgets middleware derives the primary table from `ast.from` instead of
`meta.refs.tables[0]`; the raw heuristic collapses to a SQL-text LIMIT
regex. Raw guardrails drop the refs-based index-coverage path.
- **Adapter** —
`packages/3-targets/6-adapters/postgres/src/core/sql-renderer.ts`,
`packages/3-targets/6-adapters/sqlite/src/core/adapter.ts`. Postgres and
SQLite render RETURNING from `ProjectionItem[]` with alias-aware
emission: `<table>.<column>` when alias matches column,
`<table>.<column> AS <alias>` when it differs, `<expr> AS <alias>` for
non-`ColumnRef` projections.
- **Cross-package fixture cleanup** — Mongo
query-builder/ORM/state-classes producers and the Mongo plan-JSON
serializer drop `paramDescriptors`. Test fixtures across
framework-components, middleware-telemetry, mongo-family, mongo-target,
and cross-package integration tests are updated to the narrowed
`PlanMeta` shape.
- **Docs + project workspace** — `docs/architecture docs/adrs/ADR 012 -
Raw SQL Escape Hatch.md` carries a banner marking its optional
structured-annotations branch retired by ADR 205. The driving spec +
plan ship under `projects/execution-metadata-on-ast/`.

## The story

The change starts at the AST. `ProjectionItem` previously carried only
`(alias, expr)`; ADR 205 makes its codec ID a first-class field on the
node. The mutation `returning` lists were `ColumnRef[]` — no alias, no
codec — and now they are `ProjectionItem[]`, putting RETURNING outputs
on the same footing as SELECT projections. To keep `beforeCompile`
rewrites correct-by-construction, the mutation ASTs gain `rewrite()`
methods that descend into each `ProjectionItem.expr` of `returning`,
mirroring `SelectAst.projection`'s long-standing rewrite path.

Once the AST can carry the metadata, the producers stop emitting the
sidecar. `buildOrmQueryPlan` previously called
`resolveProjectionCodecs(contract, ast)` to flatten projections into a
`projectionTypes` map; that work moves up to the construction sites
where the codec is already in hand (`buildProjection`,
`buildReturningColumns`, the aggregate-projection builder, the DSL
`select(...)` resolver). `buildQueryPlan` in the SQL builder lane
likewise stamps codecs at projection-construction time and stops walking
the row-fields map for sidecar purposes. `where-binding`'s
`bindSelectAst` preserves `codecId` when it rebuilds projections so the
rebound AST stays equivalent.

The runtime then collapses two consumers to one source. The decoder used
to read `meta.annotations.codecs` first, then fall back to
`meta.projectionTypes`, then build a column-ref index from
`meta.refs.columns` for error messages and JSON-Schema validator keys.
After this PR it walks the AST's projection (or returning) list once per
plan, builds an alias→codec lookup and an alias→`{table, column}` map
from `ProjectionItem.expr` when the expression is a `ColumnRef`, and
recognises subquery / json-array-agg projections as include aggregates
(replacing the old `include:`-prefixed string convention in
`meta.projection`). For raw plans — no AST — the decoder hands wire
values straight back. The encoder follows the same pattern: ordered,
deduplicated `ast.collectParamRefs()` produces metadata aligned with
`plan.params`, raw plans pass values through.

`runBeforeCompileChain` had a comment-block workaround explaining that
AST rewrites invalidate `paramDescriptors` and that we re-derive them.
With descriptors gone, that whole workaround is gone — the AST is what
the encoder reads, so middleware that introduces ParamRefs cannot drift.

The lints and budgets middleware lose their `refs`-driven paths.
`evaluateAstLints` already covered the AST-backed cases; raw plans now
get only the SQL-string heuristics ADR 205 expressly degrades to. The
budgets middleware reads the primary table off `ast.from`; the raw
fallback collapses to a LIMIT-regex check rather than chasing
`meta.refs.tables[0]`. The Postgres and SQLite renderers swap their
`RETURNING <ColumnRef>[]` lowering for `ProjectionItem[]`-aware
rendering, emitting `AS <alias>` only when the alias diverges from the
column name to keep the snapshot diff minimal.

Test fixtures that hand-constructed plans with `meta: {
paramDescriptors: [...], projectionTypes: {...}, refs: {...} }` migrate
to either pass an AST holding the equivalent `ParamRef.codecId` /
`ProjectionItem.codecId`, or — where they were exercising raw-plan
encoding — drop the codec-driven assertion since raw plans no longer
codec-encode.

## Behavior changes & evidence

- **Decoded values, encoded parameters, lints, and budget evaluations**
for AST-backed plans are unchanged. The decoder finds the same codec for
the same alias; the encoder finds the same codec for the same param.
([codecs/decoding.ts](packages/2-sql/5-runtime/src/codecs/decoding.ts),
[codecs/encoding.ts](packages/2-sql/5-runtime/src/codecs/encoding.ts),
[middleware/budgets.ts](packages/2-sql/5-runtime/src/middleware/budgets.ts))
- **RETURNING SQL text changes only when an alias differs from the
underlying column name** — the `AS <alias>` suffix is now emitted in
that case. The alias-matches-column case is byte-identical to the old
output.
([postgres/src/core/sql-renderer.ts](packages/3-targets/6-adapters/postgres/src/core/sql-renderer.ts),
[sqlite/src/core/adapter.ts](packages/3-targets/6-adapters/sqlite/src/core/adapter.ts))
- **Raw plans drop per-parameter codec encoding and per-alias codec
decoding.** Index-coverage lints and the `refs.tables[0]` row-count
heuristic that depended on raw-plan annotations are removed;
`select-star`, `missing LIMIT`, and `mutation-without-WHERE` SQL-string
heuristics still run on raw plans.
([guardrails/raw.ts](packages/2-sql/5-runtime/src/guardrails/raw.ts))
- **`beforeCompile` AST rewrites that introduce, swap, or remove
ParamRefs no longer require any sidecar maintenance.** A new test in
`before-compile-chain.test.ts` asserts that a `beforeCompile` rewriter
that swaps a projection alias decodes correctly end-to-end through the
AST-driven decoder.
([before-compile-chain.test.ts](packages/2-sql/5-runtime/test/before-compile-chain.test.ts))

## Compatibility / migration / risk

- **No backwards-compatibility shims.** Per repo policy, every in-repo
call site that constructed a plan with the removed sidecars is updated
at the point of change. `ParamDescriptor` and `PlanRefs` are deleted
outright, not deprecated.
- **Plan identity is unaffected.** ADR 013 already excluded the removed
fields from identity hashing.
- **Telemetry is unaffected.** Timing, row counts, and error codes flow
through paths independent of the removed sidecars.
- **Mongo family changes are limited to dropping `paramDescriptors` from
PlanMeta call sites.** Cross-family invariant ("AST is the source when
one exists") applies family-wide; specific Mongo-family migration of
ProjectionItem-style metadata is separate work.

## Follow-ups / open questions

- The `projects/execution-metadata-on-ast/` workspace ships with the PR
for reviewability; it can be deleted in a follow-up close-out commit.
ADR 205 is canonical under `docs/architecture docs/adrs/`.
- `ProjectionItem.codecId` is optional. Once all in-repo producers stamp
it (this PR), it's a candidate for promotion to required in a future
change. Out of scope here.

## Non-goals / intentionally out of scope

- Restoring the unindexed-predicate lint or the refs-based row-count
budget for raw plans. ADR 205 accepts this as a known degradation.
- New lints on top of the AST. Lint coverage stays at parity (or
strictly degrades for raw plans only).
- The ADR 012 minimal annotation schema (`intent`, `isMutation`,
`hasWhere`, `hasLimit`). Unchanged.
- Cross-family Mongo migration of execution metadata onto the Mongo AST.
Tracked separately.

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **New Features**
* Execution metadata (codec IDs, param info, projection mapping) is now
carried on AST nodes, not plan metadata.

* **Behavior Changes**
* Raw SQL plans forward caller parameters and return wire rows unchanged
(no automatic codec encoding/decoding).
* Linting and row-budget heuristics now run only for AST-backed plans;
raw plans use existing SQL-text heuristics.

* **Documentation**
* ADR and architecture notes updated to reflect the new metadata and
behavioral expectations.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
wmadden pushed a commit that referenced this pull request May 4, 2026
Biome organize-imports rule expects type/value imports interleaved
alphabetically rather than grouped. Resolves CI Lint failure introduced
by the post-#393 rebase merge of three import sources.
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