Repository navigation
[spec/drivers] AggregationNode.distinct is honoured by the in-memory fallback and ignored by every SQL face — one query, two numbers (ADR-0049) #6815
Description
Activity
Triage:
needs-user-decision+domain:spec+target:v17(labels and this comment land as a pair).- Classification basis: a clean ADR-0049 enforce-or-remove fork on a declared public contract key, with the card explicitly refusing to recommend — same shape as [spec] AggregationFunction 声明 8 个,SQL 族只实现 5 个 —— count_distinct / array_agg / string_agg 按 ADR-0049 enforce-or-remove 定去留 #6188, which was the maintainer's call too. Not queueable as-is: the two legs land in different places (ENFORCE: node-level flag reads in
driver-sql/driver-tursoemitters + frozen-pair debt rows; REMOVE: spec key deletion — an acceptance-surface change). The maintainer's pick creates the executable card(s). - Routing anchor: whichever leg wins, the pivot is
AggregationNodeSchemainpackages/spec/src/data/query.zod.ts— an acceptance-surface question, which isdomain:specterritory by the seat's standing red line (any card that changes accept/reject behavior, however small). target:v17basis (triage seat as sole producer): declared-but-unenforced public contract where the divergence yields a plausible wrong number (deduplicated vs. ordinary sum chosen by backend), not a refusal — category ② of the release-blocking test, sharpened by drivers: implementcount_distinctin the SQL family — enforcement half of the #6188 ruling #6409 makingcount_distinctwork everywhere beside it. A decision (either leg, or an explicit accepted-for-GA note) belongs on the v17 board; execution can be as small as removing one optional key.- Stale-premise check on
origin/main@6de592c:query.zod.ts:230declares the key;in-memory-aggregation.ts:173is the sole consumer (collectValues(rows, field, !!agg.distinct)); a.distinctgrep acrossdriver-sqlandservice-analyticsfinds only the unrelateddriver.distinct()API in tests — no node-level read. All three legs of the card's measurement reproduce. - Dedup: [P2] data:
QueryASTdeclares 12 members no executor runs — the liveness ledger governs metadata types, not the request surface #4286 swept the request surface but dispositionedQueryAST.distinctandAggregationNode.filter, not this key (the card's own accounting, verified plausible); [drivers] driver-memory / driver-mongodb disagree with the SQL family oncount_distinct— the two AGGREGATION_CASES cells #6409 left open #6814 is the siblingcount_distinctconformance card (distinct axis); no other open card namesAggregationNode.distinct.
本评论来自分诊座位 Routine(#5474 试点),不构成认领。
Generated by Claude Code
- Classification basis: a clean ADR-0049 enforce-or-remove fork on a declared public contract key, with the card explicitly refusing to recommend — same shape as [spec] AggregationFunction 声明 8 个,SQL 族只实现 5 个 —— count_distinct / array_agg / string_agg 按 ADR-0049 enforce-or-remove 定去留 #6188, which was the maintainer's call too. Not queueable as-is: the two legs land in different places (ENFORCE: node-level flag reads in
os-project-manager commented
on Aug 9, 2026 CollaboratorMore actionsMaintainer ruling (2026-08-09): REMOVE.
AggregationNode.distinctis retired per ADR-0049.pm:queue.Rationale (three-axis):
count_distinctalready covers the only spelling with measured demand, andSUM(DISTINCT …)/AVG(DISTINCT …)are near-universally a modelling mistake — no business pull for the ENFORCE leg. Long-term, "same query, two numbers, chosen by which backend answered" is the worst divergence class — a plausible wrong number instead of a refusal — and must not ship declared-but-unenforced. And for AI authors the flag is a perfect trap: it reads as supported, silently changes aggregates only on the in-memory fallback, and nothing surfaces the divergence.Implementation notes from the card, adopted: the key sits on the request surface (
QueryASTbuilder output /POST /data/:object/querybody), so no stored-metadata conversion is needed (the #4286 note applies verbatim); removal costs the in-memory fallback'scollectValuesflag and nothing else. The out-of-scope note stands —sum(*)-with-no-field dying as a raw dialect error is a separate gap, not to be smuggled into this PR.Maintainer directive (verbatim, covering all 25 decision-inbox cards this round): 「全部接受」. Recorded by PM session session_01LGRN2cSRfggfX9B2L83bQc. Veto window open — comment to overturn.
Generated by Claude Code
Claim: PM loop round 1 (seat restart; maintainer acceleration directive 2026-08-09 ~06:50Z)
Session:session_01PiRUoQkTSBBmpyXBY3cVn2
Branch:claude/issue-6815-aggregation-distinct-retired
Worktree:objectstack-issue-6815
Domain:domain:spec
File surface:packages/spec/src/data/query.zod.ts(retireAggregationNode.distinctper the 2026-08-09 REMOVE ruling),packages/spec/src/migrations/registry.ts(D3 entry + RETIRED_KEYS registration; ⛔ NO D2 — request surface, the #4286 note applies verbatim),packages/objectql/src/in-memory-aggregation.ts(drop thecollectValuesdistinct flag), client SDK builder sweep (packages/client— remove any producer, the #6866 precedent), pins/tests, spec generated trees. (Stop on breach; explain in the report.)
Serial constraints cleared: same-round siblings #4697 (automation/) and #6704 (api/export.zod.ts) are source-disjoint; all three collide on spec GENERATED artifacts, and this card additionally touchesmigrations/registry.ts— the current hottest table (#6866 took three re-merge laps) — so landing is serialized by this seat. #5499-frozen drivers (memory/mongodb): no changes needed there — removal makes every face uniform by construction.
Container assessment: M,mode:subagentshared container.
Generated by Claude Code
- added a commit that references this issue
on Aug 9, 2026 - added a commit that references this issue
on Aug 17, 2026 - added a commit that references this issue
on Oct 7, 2026
Filed unassigned from the #6409 lane. Found while lowering
count_distinct; adjacent to it, not created by it.The finding
AggregationNodeSchema(packages/spec/src/data/query.zod.ts) declares a per-aggregation flag:Exactly one consumer reads it.
packages/objectql/src/in-memory-aggregation.ts:Nothing else in the repo does — measured by grepping every consumer of
aggregations[]:agg.distinct?objectqlin-memory fallbackdriver-sqlSqlDriver.aggregatedriver-tursoRemoteTransport.aggregatedriver-mongodbbuildAggregationStagedriver-memorycomputeAggregateservice-analyticsAGGREGATE_SQLSo
{ function: 'sum', field: 'amount', distinct: true }returns a deduplicated sum when the engine falls back in memory and an ordinary sum on every SQL datasource. Same query, two numbers, chosen by which backend answered — the divergence class #6203 and #5907 each closed on the aggregate axis, still open on this key. And unlike those, the wrong answer here is a plausible number rather than a refusal, so nothing surfaces it.The key was NOT covered by #4286, which swept the request surface: that issue dispositioned
QueryAST.distinct(the query-level one, since retired) andAggregationNode.filter(marked[EXPERIMENTAL — not enforced]).AggregationNode.distinctis neither — it is declared plainly, has a real consumer, and reads as supported.Why it is worth a decision now rather than later
#6409 lowered
count_distincton the SQL family, which changes what this key looks like to an author.count_distinctis now a working, portable, deduplicated count;distinct: truebeside it is the affordance that reads as "…and the same forsum/avg". One of the two now works everywhere and the other works in one place, which is a sharper false affordance than it was yesterday.Two legs, per ADR-0049
ENFORCE.
SELECT SUM(DISTINCT col)is standard SQL and supported by SQLite, PostgreSQL and MySQL, so the lowering exists and is portable — the same argument that keptcount_distinctdeclared at #6188.driver-sql's anddriver-turso's lowering tables already carry adistinctflag as of #6409, so the emitters have the shape; what they lack is reading it off the NODE as well as off the function.driver-memory/driver-mongodbare #5499-frozen, which is the usual constraint on this leg.REMOVE.
count_distinctcovers the only spelling anyone has measured demand for,SUM(DISTINCT …)/AVG(DISTINCT …)are near-universally a modelling mistake, and the key has no stored-metadata authoring surface —QueryASTis the client SDK builder's output and thePOST /data/:object/querybody, so there is no conversion to write (the #4286 note on the request surface applies verbatim). Removal costs the in-memory fallback itscollectValuesflag and nothing else.No recommendation offered — this is the maintainer's call, in the same shape as #6188's.
What is NOT in scope of this issue
count(distinct *)and friends:count_distinctwith nofieldis refused withINVALID_QUERY/ 400 as of #6409, on both SQL faces. Separately,sum/avg/min/maxwritten with nofieldstill emitsum(*)and die as a dialect syntax error with no ADR-0112 envelope — a real but distinct gap, and one #6409 deliberately did not widen its refusal surface to cover.Refs: #6409, #6188, #4286, ADR-0049, #5499.