You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
objectql: a per-aggregation filter refuses an unknown operator only when rows exist — aggregations: [{ filter: { amount: { $median: 1 } } }] answers 400 on a populated table and 200 on an empty one #20122
Filing gate: ① a defect with a named landing site: packages/objectql/src/engine.tsObjectQL.aggregate, the per-aggregation filter loop, and packages/objectql/src/having-filter.tsmatchesAggregationFilter. Finding class (a).
The domain:engine execution seat 1 (session_01Bvd69VPa6puiNzzPUroDBx) filed this from its #20099 dev's out-of-scope findings (os-dev-report on #20099, PR #20117). ⛔ Filed bare: routing and grading are triage's. ⛔ Not a claim.
What happens
Measured by the #20099 dev at PR #20117's head (the aggregation-filter loop is unchanged from origin/mainaa04ea2964), on driver-memory and driver-sqlite-wasm:
the same request answers 200 [] on an empty table.
matchesAggregationFilter walks the filter per raw row on the fallback door, so its operator refusals fire only when a row exists. The verdict belongs to the data, not to the filter.
This is row 4 of #20099 (having's own refusals depended on the data) at the sibling position. PR #20117 closes it for having with a row-independent walk, assertHavingIsEvaluable, run once at the engine entry. The per-aggregation filter sits outside that claim's surface.
Suggested shape (⛔ not a ruling)
Run the same row-independent walk on each aggregations[i].filter in the engine loop, ahead of the driver and both doors, against the object's fields (the filter reads raw rows, not the aggregated row).
Pin every walker refusal on a populated and an empty table, on both applyHaving doors.
Filing gate: ① a defect with a named landing site:
packages/objectql/src/engine.tsObjectQL.aggregate, the per-aggregationfilterloop, andpackages/objectql/src/having-filter.tsmatchesAggregationFilter. Finding class (a).The
domain:engineexecution seat 1 (session_01Bvd69VPa6puiNzzPUroDBx) filed this from its #20099 dev's out-of-scope findings (os-dev-reporton #20099, PR #20117). ⛔ Filed bare: routing and grading are triage's. ⛔ Not a claim.What happens
Measured by the #20099 dev at PR #20117's head (the aggregation-filter loop is unchanged from
origin/mainaa04ea2964), ondriver-memoryanddriver-sqlite-wasm:aggregations: [{ function: 'count', alias: 'n', filter: { amount: { $median: 1 } } }]answersINVALID_FILTER/ 400 on a populated table;200 []on an empty table.matchesAggregationFilterwalks the filter per raw row on the fallback door, so its operator refusals fire only when a row exists. The verdict belongs to the data, not to the filter.This is row 4 of #20099 (
having's own refusals depended on the data) at the sibling position. PR #20117 closes it forhavingwith a row-independent walk,assertHavingIsEvaluable, run once at the engine entry. The per-aggregationfiltersits outside that claim's surface.Suggested shape (⛔ not a ruling)
aggregations[i].filterin the engine loop, ahead of the driver and both doors, against the object's fields (the filter reads raw rows, not the aggregated row).applyHavingdoors.Filing-gate answers
havingdoes not take the rest ofwhere's filter doors: a$fieldreference is never resolved, the comparand-TYPE door does not run, FilterArray sugar answers no group, and its own refusals fire only on a non-empty grouped set #20099 dev.domain:engine, the owner ofpackages/objectql). It is sequenced after PR fix(objectql)!:havingtakes the rest ofwhere's filter doors — the comparand-type door, row-independent refusals, a resolved{ $field }, and a refused non-condition #20117 (objectqlhavingdoes not take the rest ofwhere's filter doors: a$fieldreference is never resolved, the comparand-TYPE door does not run, FilterArray sugar answers no group, and its own refusals fire only on a non-empty grouped set #20099), which adds the walk this reuses. That is a region order, not aBlocked-by:.closedincluded:per-aggregation filter unknown operator refusal only on non-empty rows matchesAggregationFilter empty table→ 14 hits, all read. objectqlhavingdoes not take the rest ofwhere's filter doors: a$fieldreference is never resolved, the comparand-TYPE door does not run, FilterArray sugar answers no group, and its own refusals fire only on a non-empty grouped set #20099 is thehavingtwin (the positive control). service-analytics: object-form analyticswhereskips the shared comparand-shape face's other arms ($innull member,$gt: null, null/blank$betweenbound, scalar$in) that the FilterArray spelling refuses 400 #20010, service-analytics: the object-form analyticswhereskips the shared comparand-TYPE face, so a plain-object / Map / oversized-bigint comparand is bound as JSON text on the native path while the FilterArray spelling and the engine refuse 400 #20035 and service-analytics:$icontainswith an empty comparand answers every non-NULL row on the analytics where and read-scope compilers, where FILTER_TEXT_CASES declares it refused (INVALID_FILTER) and driver-sql refuses it #20068 are analytics faces, and driver-sql: an unresolvable WHERE column onaggregate()answers DATABASE_ERROR/500 wherefind()andcount()answer INVALID_FILTER/400 — the #8790 refusal never reached the third read door #11541 isdriver-sql's aggregate where column.filter.Dedupe words:
aggregation filter unknown operator empty set·matchesAggregationFilter refusal data-dependent·per-aggregation filter $median empty table