Skip to content

fix(adapters): cross-backend drift fixes from the architecture audit - #865

Merged
tada5hi merged 10 commits into
masterfrom
fix/adapter-drift-audit
Aug 2, 2026
Merged

tada5hi merged 10 commits into
masterfrom
fix/adapter-drift-audit

Conversation

@tada5hi

@tada5hi tada5hi commented Aug 2, 2026

Copy link
Copy Markdown
Owner

Follow-up to the 2026-08-02 architecture audit: the cross-backend drift it confirmed, fixed one commit per concern, each with the regression spec that would have caught it.

Fixes

  • adapter-memory: limit: 0 is a value, not absence. The pagination slicer returned every row for limit: 0 while typeorm/prisma/drizzle (engine-measured) return none, and drizzle's impossible-root encoding (limit: 0) leans on that reading. The slice now applies for any non-negative numeric limit; negative values stay absence. A drizzle engine spec pins the agreement with applyQuery.
  • adapter-prisma: drizzle hardening backported. Own-property provider lookups (inherited object members answer unknown instead of posing as presets), the distributeNegation invariant guards (typed failure instead of silently emitting the inverted positive form), and the typed ITSELF gate (previously an emitted $this key leaning on prisma's unknown-field error). Deliberately not ported because prisma is immune by construction: the metadata prototype hole (array-based walker) and numeric sort-name rejection (array-form orderBy).
  • adapter-prisma: findMany() no longer fails open on field visibility gates (Feature: location-aware KeyValidator (root-only fields — allow at query root, strip via include) #830). The runner refuses typed when the query's fields carry conditions, directing to execute() plus applyFieldConditions of @rapiq/adapter-memory; count() is unaffected. Documented in the Running the query section.
  • adapter-sql/typeorm: caseSensitive widened to string[] | boolean. Core and the other three backends already accept the blanket opt-out (true); the SQL pair silently did not. Passes through to planCondition unchanged. The option key split (visitor vs filters) is untouched; that is an API-freeze question for the GA checklist.

Tests

  • Engine parity matrices aligned: the rows prisma had and drizzle lacked are ported (ordering complements, to-one null column, to-many presence, elemMatch null interior), and both packages gain the operator-enrollment tripwire the typeorm suite established. The tripwire immediately caught gt/lte enrolled in neither matrix; both backends gain rows, verified against the live engines (sqlite locally; postgres via the tests-db matrix).
  • Prisma's inline engine condition list moved to test/data/matrix.ts so the tripwire runs in the default suite while the engine spec replays the same fixture.

Docs

  • Architecture guide records the audit rationales: the preset resolution invariant (user-supplied names throw typed everywhere; derived facts fall back documented), adapter-sql's protected members as an intended extension surface, and ICondition via planCondition as the filters contract of record (per-operator visitor methods are a legacy fast-path).

No public API breaks beyond the additive caseSensitive widening.

Summary by CodeRabbit

  • New Features

    • Added a global caseSensitive: true option for exact equality comparisons across supported SQL and TypeORM adapters.
    • Added documentation for case-sensitive filtering and safe handling of visibility-restricted Prisma queries.
  • Bug Fixes

    • Pagination with an explicit limit of 0 now correctly returns no rows.
    • Prisma rejects unsupported or unsafe field-condition queries instead of returning unfiltered results.
    • Provider resolution no longer accepts inherited object property names as valid providers.
  • Tests

    • Expanded coverage for filtering, relationships, null handling, negation, collection matching, and adapter parity.

tada5hi added 6 commits August 2, 2026 13:47
…ence

The pagination slicer applied a limit only when it was truthy and
positive, so limit: 0 returned every row. Every other backend treats 0
as a value (typeorm takes 0 rows; prisma take: 0 and drizzle limit: 0
both return no rows on a real engine), and drizzle's impossible-root
encoding (limit: 0) relies on that reading, making the reference
backend the only outlier. The slice now applies for any non-negative
numeric limit; negative values stay absence. A drizzle engine spec pins
the cross-backend agreement.
Three findings the drizzle review pass fixed exist identically here and
were never backported:

- own-property provider lookups: an inherited object member must
  answer unknown and reach the typed error instead of posing as a
  capability preset
- distributeNegation invariant guards: a residual negation wrapper
  around anything but mod/size, or a negated non-eq compare, now fails
  typed instead of silently emitting the positive (inverted) form if
  the core contract ever breaks
- typed ITSELF gate: a $this leaf throws featureUnsupported instead of
  emitting a { '$this': ... } key and leaning on prisma's
  unknown-field validation error

Deliberately not ported because prisma is immune by construction: the
metadata prototype hole (the datamodel walker is array-based) and the
numeric sort-name rejection (orderBy is array-form, key order cannot
reorder it).
…lity gates

Field visibility conditions (#830) are enforced post-fetch, but the
bound runner returned the delegate's rows raw while the serializer
force-projects the gate operands: a findMany() user silently received
unredacted gated columns. The runner now throws a typed AdapterError
directing to execute() plus applyFieldConditions of
@rapiq/adapter-memory. count() stays unaffected, since a gate changes
column visibility, never the row set; execute() stays the pure
serializer. The warning is documented in the Running the query docs
section, where a runner user actually looks.
Core's PlanConditionOptions accepts string[] | boolean and the
memory/prisma/drizzle adapters forward both shapes, but the sql
visitor options (and with them typeorm's forwarding surface) accepted
only the list form, so the blanket opt-out (true) silently did not
exist on the SQL pair. The option now passes through to planCondition
unchanged, which already handles the boolean. Specs pin the unfolded
comparison on both backends; the option KEY split (visitor vs filters)
stays untouched as an API-freeze question.
- preset resolution invariant: user-supplied names throw typed
  everywhere, derived facts fall back documented (resolves the
  typeorm-vs-prisma/drizzle unknown-name flag as two different inputs,
  not one policy)
- adapter-sql's exported base classes including protected members are
  an intended extension surface consumed by adapter-typeorm,
  semver-relevant for external subclassers
- filters contract of record: every backend consumes ICondition via
  planCondition; the per-operator IFilterVisitor methods are a legacy
  fast-path surface, removal sanctioned but unhurried
…ripwires

The audit measured drizzle's parity matrix weaker than prisma's; the
missing dialect-legal rows are ported (root ordering complements, the
to-one null column pair, to-many relation presence, elemMatch with a
null interior). Both packages gain the operator-enrollment tripwire
the typeorm parity suite established: a spec walking the matrix
conditions fails when a core semantics-table operator is neither
exercised by the matrix nor documented typed-unsupported. The tripwire
immediately caught gt and lte enrolled in NEITHER matrix; both gain
rows on both backends (verified against the live engines). Prisma's
inline condition list moves to test/data/matrix.ts so the tripwire
runs in the default suite while the engine spec replays the same
fixture.
Copilot AI review requested due to automatic review settings August 2, 2026 13:17
@coderabbitai

coderabbitai Bot commented Aug 2, 2026

Copy link
Copy Markdown

Review Change Stack

Note

Reviews paused

Use the following commands to manage reviews:

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

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

The PR expands adapter parity coverage, fixes zero-limit pagination, hardens Prisma provider and filter handling, blocks unsafe field-condition queries, and adds global caseSensitive: true support for SQL visitors.

Changes

Pagination and adapter parity

Layer / File(s) Summary
Zero-limit pagination behavior
packages/adapter-memory/src/parameter/pagination/module.ts, packages/adapter-memory/test/unit/pagination.spec.ts, packages/adapter-drizzle/test/unit/engine.spec.ts
A limit of 0 now returns no rows. Tests verify this behavior across memory and Drizzle adapters.
Drizzle filter parity coverage
packages/adapter-drizzle/test/data/matrix.ts, packages/adapter-drizzle/test/unit/enrollment.spec.ts
Parity cases cover ordering complements, nullable relations, collection presence, null interior values, and operator enrollment.

Prisma adapter hardening

Layer / File(s) Summary
Provider and field-condition execution
.agents/architecture.md, packages/adapter-prisma/src/provider/module.ts, packages/adapter-prisma/src/adapter/module.ts, packages/adapter-prisma/test/unit/metadata.spec.ts, packages/adapter-prisma/test/unit/run.spec.ts, packages/docs/packages/adapter-prisma.md
Provider resolution rejects inherited names. findMany rejects field-condition queries before delegate execution. Tests and documentation cover the execution paths.
Unsupported Prisma filter rendering
.agents/architecture.md, packages/adapter-prisma/src/adapter/where.ts, packages/adapter-prisma/test/unit/filters.spec.ts
Unsupported negation and ITSELF conditions now raise typed feature errors instead of producing invalid or incomplete filters.
Shared Prisma parity matrix
packages/adapter-prisma/test/data/matrix.ts, packages/adapter-prisma/test/unit/engine.db.spec.ts, packages/adapter-prisma/test/unit/enrollment.spec.ts
A shared matrix covers scalar, relation, same-element, elemMatch, nested negation, and supported-operator enrollment cases.

Global SQL case sensitivity

Layer / File(s) Summary
Global case-sensitive visitor option
.agents/architecture.md, packages/adapter-sql/src/visitor/types.ts, packages/adapter-sql/test/unit/interpreters/case-sensitivity.spec.ts, packages/adapter-typeorm/test/unit/filters.spec.ts, packages/docs/guide/filters.md
caseSensitive accepts true to apply exact equality comparisons to every field. SQL, TypeORM, and filter documentation cover the option.

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

Sequence Diagram(s)

sequenceDiagram
  participant Caller
  participant PrismaAdapter
  participant Delegate
  Caller->>PrismaAdapter: submit findMany query
  PrismaAdapter->>PrismaAdapter: detect field conditions
  PrismaAdapter-->>Caller: return FEATURE_UNSUPPORTED
  Caller->>PrismaAdapter: submit execute query
  PrismaAdapter->>Delegate: execute serialized selection
  Delegate-->>PrismaAdapter: return rows
Loading

Possibly related PRs

  • tada5hi/rapiq#792: Related through centralized filter semantics and parity coverage.
  • tada5hi/rapiq#838: Related through Prisma adapter execution, provider, and filter handling.
  • tada5hi/rapiq#862: Related through Drizzle parity tests and adapter behavior.

Suggested reviewers: copilot

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 33.33% 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 pull request's primary purpose: fixing cross-backend behavior drift identified by the architecture audit.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/adapter-drift-audit

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Pull request overview

This PR applies a set of cross-backend alignment fixes identified in the architecture audit, tightening adapter semantics (pagination and filter behavior), hardening Prisma adapter behavior around unsafe runner paths, and expanding parity/enrollment tests plus documentation to lock the fleet contract in place.

Changes:

  • Align pagination semantics so limit: 0 consistently means “return no rows” (including in the in-memory adapter) and add an engine-vs-memory parity spec for Drizzle.
  • Harden Prisma adapter behavior: safer provider preset resolution, typed failures for negation-distribution invariant breaks and unsupported $this usage, and a fail-closed findMany() guard when field visibility conditions exist.
  • Widen caseSensitive options for SQL/TypeORM to accept boolean | string[], add coverage, and expand/centralize engine parity matrices plus operator-enrollment “tripwire” tests.

Reviewed changes

Copilot reviewed 20 out of 20 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
packages/docs/packages/adapter-prisma.md Documents the new findMany() refusal when field visibility conditions exist and the required safe alternative.
packages/docs/guide/filters.md Documents blanket caseSensitive: true behavior across backends.
packages/adapter-typeorm/test/unit/filters.spec.ts Adds coverage for caseSensitive: true (blanket opt-out of folding).
packages/adapter-sql/test/unit/interpreters/case-sensitivity.spec.ts Adds coverage for blanket caseSensitive: true in SQL interpreter output.
packages/adapter-sql/src/visitor/types.ts Widens caseSensitive option type to `string[]
packages/adapter-prisma/test/unit/run.spec.ts Adds regression test ensuring findMany() fails closed when field conditions are present.
packages/adapter-prisma/test/unit/metadata.spec.ts Adds tests for rejecting prototype-chain/inherited provider names when resolving provider presets.
packages/adapter-prisma/test/unit/filters.spec.ts Adds cases for negated unsupported operators and ITSELF ($this) gating behavior.
packages/adapter-prisma/test/unit/enrollment.spec.ts New “operator enrollment” tripwire enforcing parity-matrix coverage for semantics-table operators.
packages/adapter-prisma/test/unit/engine.db.spec.ts Refactors engine parity suite to use shared parityConditions fixture.
packages/adapter-prisma/test/data/matrix.ts New centralized Prisma engine parity condition matrix (also used by enrollment tripwire).
packages/adapter-prisma/src/provider/module.ts Hardens provider preset lookup to own-properties only (prevents inherited keys from resolving).
packages/adapter-prisma/src/adapter/where.ts Adds invariant guards for residual negation, blocks ITSELF ($this) rendering, and enforces typed failure on unexpected negated ordering plans.
packages/adapter-prisma/src/adapter/module.ts Makes findMany() reject typed when field visibility conditions exist to prevent fail-open leakage.
packages/adapter-memory/test/unit/pagination.spec.ts Adds regression coverage for limit: 0 semantics and preserves negative/undefined handling.
packages/adapter-memory/src/parameter/pagination/module.ts Changes slicer logic to apply limit for any non-negative number (including 0).
packages/adapter-drizzle/test/unit/enrollment.spec.ts New operator enrollment tripwire for Drizzle’s parity matrices.
packages/adapter-drizzle/test/unit/engine.spec.ts Adds engine-vs-memory parity assertion for limit: 0.
packages/adapter-drizzle/test/data/matrix.ts Expands Drizzle parity matrices (ordering complements, null interior elemMatch, to-many presence semantics).
.agents/architecture.md Records architecture-audit rationales and clarifies fleet invariants and adapter extension/contract surfaces.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread packages/adapter-prisma/src/provider/module.ts Outdated
Comment thread packages/adapter-prisma/test/unit/metadata.spec.ts

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

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

Inline comments:
In `@packages/docs/guide/filters.md`:
- Around line 172-173: Update the paragraph describing caseSensitive: true to
qualify exact equality matching by backend collation, explicitly noting that
MySQL/MSSQL case-insensitive collations may still match differing cases,
including for caller-supplied authorization policies. Also update the
caseSensitive option documentation near Line 204 to describe its supported
string[] | true form.
🪄 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: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 593445f5-599c-4b41-bbb2-972f765bee9b

📥 Commits

Reviewing files that changed from the base of the PR and between c0db671 and 623d0f7.

📒 Files selected for processing (20)
  • .agents/architecture.md
  • packages/adapter-drizzle/test/data/matrix.ts
  • packages/adapter-drizzle/test/unit/engine.spec.ts
  • packages/adapter-drizzle/test/unit/enrollment.spec.ts
  • packages/adapter-memory/src/parameter/pagination/module.ts
  • packages/adapter-memory/test/unit/pagination.spec.ts
  • packages/adapter-prisma/src/adapter/module.ts
  • packages/adapter-prisma/src/adapter/where.ts
  • packages/adapter-prisma/src/provider/module.ts
  • packages/adapter-prisma/test/data/matrix.ts
  • packages/adapter-prisma/test/unit/engine.db.spec.ts
  • packages/adapter-prisma/test/unit/enrollment.spec.ts
  • packages/adapter-prisma/test/unit/filters.spec.ts
  • packages/adapter-prisma/test/unit/metadata.spec.ts
  • packages/adapter-prisma/test/unit/run.spec.ts
  • packages/adapter-sql/src/visitor/types.ts
  • packages/adapter-sql/test/unit/interpreters/case-sensitivity.spec.ts
  • packages/adapter-typeorm/test/unit/filters.spec.ts
  • packages/docs/guide/filters.md
  • packages/docs/packages/adapter-prisma.md

Comment thread packages/docs/guide/filters.md Outdated
@tada5hi

tada5hi commented Aug 2, 2026

Copy link
Copy Markdown
Owner Author

@coderabbitai pause

@coderabbitai

coderabbitai Bot commented Aug 2, 2026

Copy link
Copy Markdown
✅ Action performed

Reviews paused.

tada5hi added 4 commits August 2, 2026 15:25
The import prune alongside the matrix extraction removed inArray,
which the postgres-only branch of the wildcard-veto spec still uses;
the sqlite leg returns before reaching it, so local runs and the mysql
job stayed green while the postgres job failed with a ReferenceError.
The sorting and pagination section still documented limit: 0 as no
limit, contradicting the fleet-wide explicit-0-is-a-value fix.
Review finding: resolveProvider lowercases first, so valueOf/toString
can never reach the prototype chain; the only Object.prototype members
that survive normalization are constructor and __proto__. The specs
now assert those actually-hazardous names (keeping the camel-case
spellings as contract pins for inputs the normalization blocks), and
the twin provider comments in prisma and drizzle name a surviving
example instead of a misleading one.
…veat

Review finding: the sentence promised exact equality on all backends
while the warning below correctly states that MySQL/MSSQL delegate
equality to the column collation; a *_ci collated column keeps
matching case-insensitively regardless of the opt-out. The schema-side
option stays documented as a list: the boolean form exists only on the
adapter forwarding surface.
@tada5hi
tada5hi merged commit d54c80f into master Aug 2, 2026
9 checks passed
@github-actions github-actions Bot mentioned this pull request Aug 2, 2026
@tada5hi
tada5hi deleted the fix/adapter-drift-audit branch August 2, 2026 15:43
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