Skip to content

Commit bdcb41d

Browse files
committed
feat(driver-mongodb): lower dateGranularity groupBy to $dateToString bucket labels, and publish supports.queryDateGranularity (#7580)
`driver-mongodb` published no `queryDateGranularity`, so `engine.aggregate` bucketed every granularity in memory: correct answers, but the whole result set shipped to the client before the rollup. #7550 refused a bucketed node at the builder rather than implement or silently drop it, and sized the native lowering as a card of its own. This is that card. The bucket LABELS are the engine's own spellings, because the engine picks between the pushed-down and in-memory paths per query and a drill-down can cross that seam. All five declared granularities are advertised — `week` included, where driver-sql on SQLite cannot, because `$dateToString` has both halves of the ISO-8601 week date (`%G`/`%V`). No `$dateTrunc`: it answers a truncated DATE that still needs formatting, raises the server floor to MongoDB 5.0, and adds binSize/startOfWeek semantics this fleet cannot observe. `$dateToString` answers the label directly out of one operator, and fewer unobserved operators is the whole argument. All three ADR-0053 storage forms are served through one total expression: `$convert … onError/onNull: null` mirrors `bucketDateValue`'s totality, so a BSON Date, `YYYY-MM-DD` text, `HH:MM:SS` text, null, missing and unparseable junk all bucket the way the engine buckets them. The capability record and the builder's refusal read ONE constant, so an advertised granularity the builder would refuse cannot exist — the failure worse than advertising nothing, since the engine stops bucketing in memory on the strength of the bit. Parity is proven by running the SAME rows through the real `applyInMemoryAggregation` and through the emitted pipeline. driver-mongodb can depend on objectql where driver-sql cannot (objectql depends on no driver), so this is a devDependency rather than the hand-copied `bucketDateValue` the three SQL bucket suites carry, and the drift their `⚠️ Keep in sync` comments admit they cannot detect does not exist here. The strict in-process evaluator moved to `mongodb-pipeline-evaluator.testkit.ts` so both suites share one reader; only the date operators were added. It models the manual independently — its own ISO-8601 grammar, `%G`/`%V` from the standard — so the two sides CAN disagree and the comparison means something. ⚠️ Nothing here has met a real mongod (#5517: proxy 403 on the binary). The bound is written into the suite header, onto the published capability, and beside the lowering. Fixes #7580 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019QkW7hhVvhTxh4V9Wmnqdy
1 parent 34c01a5 commit bdcb41d

9 files changed

Lines changed: 1220 additions & 259 deletions
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
---
2+
"@objectstack/driver-mongodb": patch
3+
---
4+
5+
feat(driver-mongodb): bucket `dateGranularity` groupBy server-side, and publish `supports.queryDateGranularity` (#7580)
6+
7+
`driver-mongodb` now lowers a `dateGranularity`-bearing `groupBy` into the
8+
aggregation pipeline and advertises the capability, so `engine.aggregate` pushes
9+
a bucketed aggregate down to MongoDB instead of fetching every matching row and
10+
bucketing it in JS.
11+
12+
**Answers do not change — where the work happens does.** `MongoDBDriver.supports`
13+
published no `queryDateGranularity`, so the engine already bucketed every
14+
granularity in memory and the results were correct. What was missing was the
15+
index/server-side half: a year-over-year rollup shipped the whole result set to
16+
the client first. #7550 refused a bucketed node at the builder rather than
17+
implement or silently drop it; this replaces that refusal with the lowering it
18+
described.
19+
20+
**All five granularities `DateGranularity` declares are advertised**`day`,
21+
`week`, `month`, `quarter`, `year`. The bucket LABELS are the engine's own
22+
spellings (`'2024'`, `'2024-Q1'`, `'2024-01'`, `'2024-01-15'`, ISO `'2025-W01'`),
23+
because the engine picks between the pushed-down and in-memory paths per query
24+
and a drill-down can cross that seam. `week: true` where `driver-sql` on SQLite
25+
carries `week: false`: MongoDB's `$dateToString` has both halves of the ISO-8601
26+
week date (`%G`/`%V`), SQLite has neither.
27+
28+
**All three ADR-0053 storage forms are served.** This driver stores `datetime` as
29+
a BSON `Date` but `date` and `time` as timezone-naive TEXT, so the lowering reads
30+
the instant through `$convert … onError/onNull: null` — total, exactly like the
31+
in-memory `bucketDateValue`, which puts null, missing and unparseable values in
32+
one empty bucket. A `Field.time` column is a wall clock and not an instant: both
33+
paths agree it has no bucket, rather than one of them inventing a day.
34+
35+
**Timezones are unchanged and stay engine-side.** `engine.aggregate` forces the
36+
in-memory path for any non-UTC reference zone (ADR-0053 Phase 2 D2) and the AST
37+
it hands a driver carries no `timezone`, so this bucketing is UTC by
38+
construction.
39+
40+
The #7550 refusal is kept for a granularity outside the advertised record —
41+
`NOT_IMPLEMENTED` / 501 in the ADR-0112 envelope, now naming what *is* bucketed
42+
here — and it reads the same constant the capability record publishes, so the
43+
two cannot drift.
44+
45+
⚠️ **Bound, stated because a green suite reads as more than it is.** Parity with
46+
the engine's labels is proven through a strict in-process pipeline evaluator, not
47+
against a live mongod: this environment cannot fetch a mongod binary (#5517). The
48+
`$convert` / `$dateToString` / `$concat` / `$switch` semantics the lowering stands
49+
on are documentation-derived. The bound is written into the suite header, onto
50+
the published capability, and beside the lowering.

packages/drivers/driver-mongodb/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
"nanoid": "^6.0.0"
2727
},
2828
"devDependencies": {
29+
"@objectstack/objectql": "workspace:*",
2930
"@types/node": "^26.1.2",
3031
"mongodb-memory-server": "^11.2.0",
3132
"typescript": "^6.0.3",

packages/drivers/driver-mongodb/src/index.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,11 @@ import { MongoDBDriver } from './mongodb-driver.js';
55
export { MongoDBDriver };
66
export type { MongoDBDriverConfig } from './mongodb-driver.js';
77
export { translateFilter } from './mongodb-filter.js';
8-
export { buildAggregationPipeline, postProcessAggregation } from './mongodb-aggregation.js';
8+
export {
9+
buildAggregationPipeline,
10+
postProcessAggregation,
11+
MONGODB_DATE_GRANULARITIES,
12+
} from './mongodb-aggregation.js';
913
export type { AggregationInput } from './mongodb-aggregation.js';
1014
export {
1115
MongoDBMultiTenantUnsupportedError,

0 commit comments

Comments
 (0)