Repository navigation
Description
Provenance (maintainer direct-route, 2026-08-11)
Filed and queued on the maintainer's instruction in the drivers PM session chat (session session_013ZU38vNzLXCscgdFZjKd53), verbatim:
mongodb dateGranularity 立卡排进队列
Routing label applied under the 2026-08-10 direct-route standing authorization (maintainer-commanded cards route directly; natural-inflow triage remains the triage seat's). driver-mongodb is unfrozen per #5499 comment 5249019855.
Background — what PR #7550 measured and deliberately did not do
PR #7550 (Fixes #6850 / Part of #6814) gave driver-mongodb a real structured-GroupByNode lowering, and chose to refuse a dateGranularity-bearing node (NOT_IMPLEMENTED / 501, ADR-0112 envelope) rather than implement or silently drop it. Its PR body records the measurement that sized the native lowering as a card of its own — this card.
The standard already exists — this card joins it, it does not invent it
- Capability record:
supports.queryDateGranularityis a per-granularity record ({ day, week, month, quarter, year }).packages/objectql/src/engine.ts(~:9295-9301, drifts — locate by content) pushes a bucketed groupBy down only for granularities the driver advertises; otherwise the engine buckets in memory (in-memory-aggregation.ts). So today mongodb aggregations still bucket correctly — one layer up, without index/server-side benefit. - Precedent + parity suites:
driver-sqlpublishes the capability and is held tosql-driver-date-bucket.test.ts(parity),sql-driver-date-bucket-storage.test.ts(storage forms),sql-driver-aggregate-datetime-window.test.ts/-temporal-output.test.ts. The engine's timezone semantics are pinned inengine-aggregate-timezone.test.ts. - Labels are the contract: buckets must come back as the ENGINE's label spellings (e.g.
2026-01, quarter and ISO-week forms) — byte-parity with what the in-memory fallback emits, or the two paths answer the same query differently depending on a capability bit.
Scope
- Lower a
dateGranularity-bearing groupBy node natively ($dateTruncor equivalent expression), emitting the engine's bucket labels. - Storage forms per ADR-0053: mongodb stores
datetimeas BSONDatebutdate/timeas text — measure which granularity × field-kind cells a native lowering can honestly serve, and only advertise those. - Publish
supports.queryDateGranularityfor exactly the cells implemented — declared = enforced; an advertised granularity the lowering mishandles is worse than the current honest refusal. - Keep the fix(driver-mongodb): take a structured
GroupByNode, and answercount/count_distinctlike every other backend #7550 refusal as local defense for direct callers with unadvertised granularities (the engine will not push those down once the capability record is honest). - Parity coverage in the shape of the driver-sql suites, server-free per driver-mongodb 测试:两个套件并发下载 MongoDB 二进制,mongodb-memory-server 的 rename 竞态让全绿测试 exit 1(队列 flaky) #5517 (the in-process evaluator from
mongodb-aggregation-translation.test.ts— note its stated bound: it holds the LOWERING to the table; live-mongod agreement stays out of reach in this fleet, and any modelled$dateTruncsemantics must carry the same written bound).
Acceptance
- For every advertised granularity × field-kind cell: pushdown result labels are byte-identical to the engine's in-memory fallback on the same rows (parity cases, both spellings of the node).
- Unadvertised cells keep refusing loudly at the builder (existing tests stay green or are consciously updated).
- Timezone semantics match
engine-aggregate-timezone.test.ts's pinned behavior — measure before implementing; if a faithful timezone treatment is not achievable server-free, narrow the advertised set and record why rather than approximating.
Refs: PR #7550 (measurement + refusal), #6850 / #6814 (the cell this grew from), #5499 (unfreeze), aggregation-conformance.ts:51 (capability note), ADR-0053 (storage forms), ADR-0112 (refusal envelope).
Activity
huangyiirene commentedon Aug 11, 2026
Claim: PM loop round 4 (drivers seat)
Session:
session_013ZU38vNzLXCscgdFZjKd53Branch:
claude/issue-7580-mongodb-date-granularityWorktree:
objectstack-issue-7580Domain:
domain:driversFile surface:
packages/drivers/driver-mongodb/src/mongodb-aggregation.ts(therefuseDateBucketedGroupByregion + a granularity lowering),packages/drivers/driver-mongodb/src/mongodb-driver.ts(thesupportsrecord),packages/drivers/driver-mongodb/src/mongodb-aggregation-translation.test.tsand/or a new parity suite in the same package,.changeset/*(stop on breach; explain in the report)Container & model: M,
mode:cloud,model: opusSerial constraints cleared: in-flight #7177 (PR #7579) edits findone/filter/temporal TEST files — disjoint from this card's aggregation surface. #7550 (same aggregation files) MERGED at
f067930; this card builds on its post-merge state. Hot-filesql-driver.tsuntouched (driver-sql suites are read as precedent only — ⛔ not edited).Authorization: maintainer direct-route instruction 2026-08-11 (verbatim in the card body's Provenance section); driver-mongodb unfrozen per #5499 comment
5249019855.Generated by Claude Code
huangyiirene commentedon Aug 11, 2026
os-dev report — #7580 · draft PR #7590 · branch
claude/issue-7580-mongodb-date-granularity· worktreeobjectstack-issue-7580· baseorigin/main@34c01a5Premises, verified against
origin/mainbefore implementingAll five PM anchors held; none was dead.
engine.ts:9295-9301granularityCaps?.[g.dateGranularity] === truegatesdrv.aggregate; otherwiseapplyInMemoryAggregation. Current mongodb behaviour is correct one layer up — this card moves work, it does not fix an answer.in-memory-aggregation.ts#bucketDateValue)'2024'/'2024-Q1'/'2024-01'/'2024-01-15'/'2025-W01'; null, missing and unparseable share one bucket (#3839).datetime→BSONDate,date→YYYY-MM-DDTEXT,time→HH:MM:SSTEXT (mongodb-temporal.tscanon table).mongodb-aggregation.ts:62, pins at…translation.test.ts:378-417engine-aggregate-timezone.test.ts)tzRequiresInMemoryforces in-memory for any non-UTC zone + date bucket, and the driver AST carries notimezonekey at all. UTC-only is not a narrowing — it is the only thing the lowering could mean. No advertised set needed narrowing on timezone grounds.Two premises measured directly (both decided a design choice):
new Date('14:30:00')→ Invalid Date, so aField.timecolumn buckets to the empty bucket on the engine path. The lowering matches it viaonError: null→ thetimecell is served faithfully-degenerately and needs no refusal (a refusal would have broken a query the engine answers today).String(999)='999'vs%Y='0999'— a shared, pre-existing property of every SQL face too. Recorded, not narrowed over.What was implemented
Native lowering via
$dateToString, not$dateTrunc(route declined with reasons: label-not-instant contract, MongoDB 3.6 floor instead of 5.0, and fewest unobserved operators — the governing constraint under #5517).$convert … onError/onNull: nullmakes the read total across all three ADR-0053 storage forms + null/missing/junk.quarterderived by$switchover the zero-padded%mstring so no numeric→string formatting is assumed; null propagation falls out of$concat, so no$condguard.Advertised: all five (
day,week,month,quarter,year) —week: truewheredriver-sql/SQLite isfalse, because%G/%Vgive MongoDB both halves of the ISO-8601 week date. Declared = enforced is a mechanism: the capability record and the builder's refusal read one exported constant, and the suite asserts identity, not equality.Gates
pnpm --filter @objectstack/driver-mongodb testpnpm --filter @objectstack/driver-mongodb typecheckpnpm check:driver-conformanceeslint --no-inline-configon all changed files--filter @objectstack/driver-mongodb...in_progress— PM owns convergence@objectstack/objectqltests not run: the objectql fallback's shared cases were not touched (read-only).Reverse-verification — every break executed, each had to fail
%G-W%V→%Y-W%V$convertremovedDeclared file-surface breach — one, reported as required
The claim named the surface as driver-mongodb source +
.changeset/*. Two additions go past it, both inside the driver package:packages/drivers/driver-mongodb/package.json—@objectstack/objectqladded as adevDependency. Measured: objectql depends on no driver, so the edge is acyclic, and a devDependency never enters the published package. It buys the acceptance criterion literally — parity against the realapplyInMemoryAggregationrather than the hand-copiedbucketDateValuethe three SQL bucket suites carry and whose drift their own⚠️ Keep in synccomments admit they cannot detect.turbo'stest → ^buildalready orders it. Revertable in isolation to the hand-copy precedent if the PM prefers the thinner dependency graph.mongodb-pipeline-evaluator.testkit.ts— fix(driver-mongodb): take a structuredGroupByNode, and answercount/count_distinctlike every other backend #7550's evaluator extracted verbatim (only date operators added) so both suites share one strict reader; a second, laxer copy is the easiest way to bless a broken lowering.packages/objectql,driver-memory,driver-sql,packages/verify,packages/qa/dogfoodandcontent/docs/releases/untouched.Open questions for the PM
checkDateBucketParityenrolment stays out of reach. It drives a LIVE driver throughcreate/find/aggregate, sodriver-mongodbcannot joinpackages/qa/dogfood/test/date-bucket-parity-conformance.test.tsserver-free. A follow-up card could add a server-free adapter there, or enrol mongodb the day the opt-in real-mongod half becomes runnable. Not filed — PM's call whether it is a card.MongoDBDriver.supportsdoc, beside the lowering), exactly as fix(driver-mongodb): take a structuredGroupByNode, and answercount/count_distinctlike every other backend #7550 did. The one thing a live mongod would settle: whether$convert's string→date arm accepts precisely the ISO-8601 set modelled here.'2024-01-15 10:00:00'with a space) buckets in memory and empties in the pushdown. Not a form this driver writes (coerceTemporalValuecanonicalises), and the identical exposure exists on every SQL face. Advertised anyway — flagging in case the PM reads that as a cell rather than a data condition.packages/objectql.Generated by Claude Code
huangyiirene commentedon Aug 11, 2026
ACCEPT — PR #7590 (review of record; flipping ready + queueing now).
packages/drivers/driver-mongodb+ changeset + the 3-line lockfile companion. The one declared breach —@objectstack/objectqlas a devDependency — is ACCEPTED: acyclic (measured), dev-only (never enters the published package), and it buys the acceptance criterion literally — parity against the REALapplyInMemoryAggregationinstead of a hand-copy whose own comments admit drift is undetectable.Validate Package Dependenciesgate agrees. The shared-evaluator testkit extraction is likewise accepted (one strict reader beats two drifting copies).$dateToStringover the PM-suggested$dateTrunc, for the governing reason under driver-mongodb 测试:两个套件并发下载 MongoDB 二进制,mongodb-memory-server 的 rename 竞态让全绿测试 exit 1(队列 flaky) #5517 — fewest unobserved operators — plus the label-not-instant contract and the lower server floor. The dispatch prompt's route was optional by construction; this is the third time this batch a dev's measured route beat the PM's sketch.week: truejustified by%G/%V(ISO both halves) where the SQL faces lack it. Timezone risk dissolved by measurement (engine forces in-memory for non-UTC; UTC-only is the lowering's only possible meaning).$convertremoval, quarter boundary, duplicate-literal capability record). This is what makes a documentation-derived parity claim worth something.checkDateBucketParityenrollment needs a server-free adapter or a runnable mongod — recorded on the seat card as a candidate follow-up for triage, not filed today; (2) the documentation-derived bound is stated in three places — accepted as the honest ceiling of this fleet; (3) the legacy non-canonical-text divergence is a data condition shared by every SQL face, asserted as a live difference in the suite — accepted as recorded, not a cell.completed: successread individually.Generated by Claude Code
feat(driver-mongodb): lower dateGranularity groupBy to $dateToString …