Skip to content

feat(database): relation filters some/none/every and relation counts - #116

Open
artile wants to merge 7 commits into
MapleTechLabs:mainfrom
artile:feat/relation-filters
Open

artile wants to merge 7 commits into
MapleTechLabs:mainfrom
artile:feat/relation-filters

Conversation

@artile

@artile artile commented Oct 25, 2026 •

Copy link
Copy Markdown
Contributor

findMany and the builder cannot filter rows by their related rows or count related rows. Prisma code uses _count and some / every / none a lot, and Drizzle needs raw SQL for most of them.

Closes #80.
Part of #52.

Depends on #110; review only the commits after it.

Changes

  • Db.related(schema.table, $) gives the relations of a table on the $ of a builder query. Each relation has some(where?), none(where?), every(where) and count(where?).
  • some is EXISTS (…), none is NOT EXISTS (…), every is NOT EXISTS (… AND NOT (where)). The subquery reads the target under the alias <outer alias>__<relation>, so a self-relation works. Filters nest: the where of a filter gets its own r.
  • count(where?) is an Expr<number>: coalesce((SELECT count(*) …), 0). It works in a select and in a where.
  • findMany's where (at every level) gets the same object as its second argument: where: ($, r) => [r.posts.some()].
  • _count: { posts: true | { where } } in findMany adds _count: { posts: number } to the rows of that level. It keeps feat(database): relational reads with defineRelations and findMany #110's loading model: one grouped statement per counted relation, SELECT <to>, count(*) … WHERE <to> IN (<keys>) GROUP BY <to>, with the same tenant pin as a loaded level. Only many relations can be counted (type error otherwise).
  • Tenant scope: when both tables declare a tenant column, the subquery also gets <inner tenant> = <outer tenant>. A small change in compile.ts gives such a subquery the binding of the outer tenant column, so a filter under $.orgId.eq("a") stays "single-tenant". Only the relation filters set this internal mark, and they always write the equality. A filter does not scope an unscoped outer query. A filter from an untenanted table into a tenanted one is "cross-tenant" unless its own where pins the tenant.
  • A filter on an unqualified accessor (an update or a delete) dies with a QueryBuilderDefect, so it never correlates a column to itself.
  • Docs: new sections in docs/relations.md, section 6 in design/relations.md, docs/reference.md, one changeset.

ClickHouse (tested live on 26.8.2.7)

  • some, none, every, and nested filters work (correlated subqueries are on by default in 26.8).
  • count() in a where works.
  • count() in a select next to a where works only when the outer columns it correlates on are selected too. Otherwise ClickHouse fails with NOT_IMPLEMENTED ("can't find correlated column … in current header"). The docs say this, and the test selects the key and tenant columns.
  • A correlated count() over no rows returns NULL on ClickHouse, not 0. The coalesce handles it.
  • _count in findMany needs no correlation, so it works on every version.

Left out

  • _count of a one relation: r.author.some() says the same.
  • Relation filters in update / delete: their accessor is not qualified, so there is no alias to correlate on. Refused with a defect.
  • Ordering by a relation count (Prisma orderBy: { posts: { _count: "desc" } }). On a builder query, select count() and order by its alias.
  • SQLite, MySQL, D1: not on main. The SQL is plain EXISTS and count(*), so no dialect work is expected.

Validation

Run on the head commit against a live ClickHouse 26.8.2.7 (the CI matrix version), with EFFECT_ORM_CLICKHOUSE_URL set, so no suite is skipped:

  • bun run build: pass.
  • bun run typecheck: pass.
  • bunx vitest run --testTimeout=60000 --hookTimeout=60000: pass. Test files: 64 passed (64). Tests: 1065 passed (1065).
  • node scripts/check-doc-citations.mjs, check-exports-documented.mjs, check-doc-examples.mjs: pass.
  • bun scripts/check-package.ts (tarball, types, CommonJS consumer): pass.
  • The longer timeouts are only for the local machine, which was under load.
  • Upstream CI for fork PRs waits for a maintainer to approve the workflow run.

View with [code]smith Autofix with [code]smith
Need help on this PR? Tag @codesmith-bot with what you need.

Correlated EXISTS / NOT EXISTS conditions and a correlated count(*) over a
declared relation, on the builder (Db.related) and in findMany's where.
findMany's _count counts a many relation with one grouped statement per
level. A subquery that correlates on the outer tenant column takes that
column's tenant binding.
@coderabbitai

coderabbitai Bot commented Oct 25, 2026

Copy link
Copy Markdown

Warning

Review limit reached

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

Next included review available in 15 minutes.

Check out review usage here.

View limit details

Limit details: You’ve used the included review currently available.

Learn how review limits work.

Review configuration:

Run configuration
  • Source mode: Auto
  • Repository: Unavailable
  • Branch: Unavailable
  • File: No configuration file from this repository applied
  • Commit: Unavailable
  • Layers: defaults
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: dfd3b57f-9ff0-4b33-beb2-db58a4284218
Commits

Reviewing files that changed from the base of the PR and between c5fa679 and 8be9e66.

Files selected for processing: 17
  • .changeset/relation-filters.md
  • .changeset/relational-reads.md
  • README.md
  • design/relations.md
  • docs/README.md
  • docs/reference.md
  • docs/relations.md
  • src/ch/compile.ts
  • src/ch/tenant.ts
  • src/database.ts
  • src/database/relation-filters.test-d.ts
  • src/database/relation-filters.test.ts
  • src/database/relations.test-d.ts
  • src/database/relations.test.ts
  • src/database/relations.ts
  • tests/relation-filters.clickhouse.test.ts
  • tests/relations.clickhouse.test.ts
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

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.

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.

Relation counts and filters: _count, some, every, none

1 participant