Skip to content

[spec] parseFilterAST lowers ['a', '=', { $field: 'b' }] to a bare field spec no backend reads as equality #7597

Description

@os-zhuang

Measured while implementing #5222 (cross-field push-down). Filing unassigned — recording, not claiming.

The fact

parseFilterAST lowers an authored triple differently depending on the operator spelling. Measured on current main:

Authored Lowered to
[['amount', '>', { $field: 'budget' }]] {"amount":{"$gt":{"$field":"budget"}}}
[['amount', 'gt', { $field: 'budget' }]] {"amount":{"$gt":{"$field":"budget"}}}
[['amount', '=', { $field: 'budget' }]] {"amount":{"$field":"budget"}}bare
[['amount', 'equals', { $field: 'budget' }]] {"amount":{"$field":"budget"}}bare

The equality spellings drop the operator, because a literal comparand's implicit-equality form is { field: value } — correct for a literal, but for a field reference it produces a field spec whose only key is $field.

Nothing reads that shape as an equality. In matches-filter.ts, evalField sees an all-$ key set and dispatches $field to evalOp, which has no arm for it and returns its fail-closed false. So the filter silently matches no record. On the SQL side the same shape arrives as an OPERATOR named $field.

Why it matters

Two authored spellings of one intent, two fates: ['amount', '>', ref] works (post-#5222 on SQL, and always in memory), ['amount', '=', ref] silently matches nothing. The failure is invisible on the path that produces it.

#5222 narrowed the SQL side to give this shape an actionable refusal naming $eq rather than a generic operator list — but that is a driver-side mitigation of a lowering defect, and it deliberately did NOT compile the shape, because compiling it would make SQL answer rows for a filter the memory evaluator answers false for (a new divergence in the change that closed one).

The decision this needs

Most likely: parseFilterAST should lower an equality triple whose comparand is a FieldReferenceSchema to { field: { $eq: ref } } rather than the bare form — the spelling that IS implemented on both paths. That is a one-sink change (parseFilterAST is the single lowering sink per #5158), but it is a semantics change at the authoring face, so it wants a ruling rather than a drive-by fix.

Alternatively, matches-filter.ts could read a bare { $field } field spec as an implicit equality — but that changes the evaluator's unknown-operator posture, which #6520 examined and deliberately KEPT.

Refs

Metadata

Metadata

Assignees

Labels

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions