Skip to content

Commit 796b06a

Browse files
committed
docs(changeset): name the per-aggregation shape refusal in its own words, and keep each measured claim to what was measured
The shape row now quotes the refusal as the code spells it, with the aggregation named; the zero-row filter is credited to its unit pin rather than to the real-driver measurement, which did not carry it; and the addDays note says the refusal names each aggregated column's class where driver-sql names a stored type. Claude-Session: https://claude.ai/code/session_01Bvd69VPa6puiNzzPUroDBx Co-authored-by: Claude <noreply@anthropic.com>
1 parent a17770a commit 796b06a

2 files changed

Lines changed: 3 additions & 3 deletions

File tree

‎.changeset/20122-aggregation-filter-row-independent.md‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,12 +22,12 @@ The engine evaluates a per-aggregation filter itself, in the in-memory fallback,
2222
| a `{ $field }` reference whose `addDays` is not an integer (`1.5`, `'7'`) | counted rows by the in-memory evaluator's own reading of that offset, which `FieldReferenceSchema` refuses | a whole-day `addDays` |
2323
| `{ amount: { $eq: { v: 1 } } }`, `{ amount: undefined }`, `{ $eq: new Map() }`, a function, an `undefined` `$in` member, a bigint beyond 2^53, or `{ $gt: { $field: 5 } }` | counted no row (each measured under the operator shown, or in the implicit slot). The same comparand in `where` is refused by the comparand-type door; the per-aggregation filter now gets that door's refusal, rooted at its own position | a string, number, bigint, boolean, `null` or `Date` |
2424
| a `Symbol` comparand | under `$ne`, counted every row; under `$gt`, threw a raw `TypeError` with no `code` and no `status` on a populated table | a literal of one of the types above |
25-
| a `filter` that is not a filter object: a string (`"stage = 'won'"`), a number, `true` / `false`, `''`, a `Map`, a `Date` or a `Set` | dropped: the aggregation read every row of its group, with no error. `driver-sql`'s native aggregate answered a non-empty string with `NOT_IMPLEMENTED` / 501. (The REST door already refused the JSON-expressible ones, through `AggregationNodeSchema`.) Now refused by the shape gate `where` takes (`'where' must be a filter object or condition array, received …`), in the same words, naming the aggregation | a filter object, `{ stage: 'won' }` |
25+
| a `filter` that is not a filter object: a string (`"stage = 'won'"`), a number, `true` / `false`, `''`, a `Map`, a `Date` or a `Set` | dropped: the aggregation read every row of its group, with no error. `driver-sql`'s native aggregate answered a non-empty string with `NOT_IMPLEMENTED` / 501. (The REST door already refused the JSON-expressible ones, through `AggregationNodeSchema`.) Now refused by the shape gate `where` takes, in `where`'s words with the aggregation named (`'aggregations[1].filter' must be a filter object or condition array, received …`) | a filter object, `{ stage: 'won' }` |
2626

2727
Not refused, but answering differently:
2828

2929
- **An exact-range bigint comparand is narrowed to a number, as it is in `where`.** `{ amount: { $in: [400n, 20n] } }` counted no row, because `[400n].includes(400)` is false. It now counts the rows it names. The caller's aggregation entry is not edited.
3030

3131
Who is affected: a per-aggregation filter reaches `engine.aggregate` from a direct engine call, from the REST aggregate query, and from the analytics service, which lowers a dataset measure's own `filter` onto it. On a populated table the refusals in the first and fourth rows above were already raised; what changes there is that an empty table refuses them too. The shape row is reachable in-process only: the REST door already refuses a non-object `filter`. Callers in a deployment were NOT measured.
3232

33-
Not changed: implicit equality, scalar ordering bounds, `$in` / `$nin` lists, a two-bound `$between`, `$icontains` / `$startsWith` with a non-empty string, `$ne: null`, `$exists`, `$null`, `$or` / `$not` composition, a `{ $field }` reference as the whole comparand of a scalar comparison (with or without a whole-day `addDays`), an exact bigint in the implicit slot, the zero-row filter `{ $not: {} }`, and `{}`, on both drivers, measured identical before and after. `null`, an absent `filter`, a null-prototype filter object and an array are not refused by the shape gate, as they are not on `where`: `[]` is still read as no filter, and a condition array (`[['amount', '>', 100]]`) is still read by the walker as an object whose index keys are column names, so it counts no row, as before — `AggregationNodeSchema.filter` does not declare that sugar, and the REST door refuses it.
33+
Not changed: implicit equality, scalar ordering bounds, `$in` / `$nin` lists, a two-bound `$between`, `$icontains` / `$startsWith` with a non-empty string, `$ne: null`, `$exists`, `$null`, `$or` / `$not` composition, a `{ $field }` reference as the whole comparand of a scalar comparison (with or without a whole-day `addDays`), an exact bigint in the implicit slot, and `{}`, on both drivers, measured identical before and after. The zero-row filter `{ $not: {} }`, which the analytics service lowers FALSE to, still counts no row (a unit pin, green against the base code too). `null`, an absent `filter`, a null-prototype filter object and an array are not refused by the shape gate, as they are not on `where`: `[]` is still read as no filter, and a condition array (`[['amount', '>', 100]]`) is still read by the walker as an object whose index keys are column names, so it counts no row, as before — `AggregationNodeSchema.filter` does not declare that sugar, and the REST door refuses it.

‎.changeset/20127-having-adddays-temporal-pair.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ An aggregated row has no declared field types, so each column's class is now rea
1818

1919
A column whose class the declaration cannot tell is not judged: an object with no field map, a field it does not declare, or a `formula` field.
2020

21-
`having` resolved every pair through `@objectstack/formula`'s evaluator, which reads a number as epoch milliseconds, while `driver-sql` refuses the same pair on `where`. The refusal reuses `driver-sql`'s sentence for that pair, with "is stored as" read as "is", because an aggregated column is computed rather than stored. Measured on the base through `engine.aggregate` on `driver-memory` and `driver-sql`, both paths, and through `POST /data/:object/query` on both, over three groups with a `sum` alias `total`, a `max` of a number `max_cap`, `max` / `min` of two `date` fields, `max` / `min` of two `datetime` fields and a `count` `n`:
21+
`having` resolved every pair through `@objectstack/formula`'s evaluator, which reads a number as epoch milliseconds, while `driver-sql` refuses the same pair on `where`. The refusal reuses `driver-sql`'s sentences for the pair, naming each aggregated column's class where `driver-sql` names a stored type ("is numeric" for "is stored as numeric"), because an aggregated column is computed rather than stored. Measured on the base through `engine.aggregate` on `driver-memory` and `driver-sql`, both paths, and through `POST /data/:object/query` on both, over three groups with a `sum` alias `total`, a `max` of a number `max_cap`, `max` / `min` of two `date` fields, `max` / `min` of two `datetime` fields and a `count` `n`:
2222

2323
| `having` | before | now |
2424
|:--|:--|:--|

0 commit comments

Comments
 (0)