Skip to content

[spec] FieldReferenceSchema is declared in the $between endpoints but NO backend resolves a $field inside a list #7596

Description

@os-zhuang

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

The fact

packages/spec/src/data/filter.zod.ts declares FieldReferenceSchema in both $between endpoints, in the documentation copy and in the ENFORCED copy:

$between: z.tuple([
  z.union([z.number(), z.date(), z.string(), FieldReferenceSchema]),
  z.union([z.number(), z.date(), z.string(), FieldReferenceSchema])
]).optional(),

$in / $nin are z.array(z.any()), so a reference is admitted there too.

No backend resolves it. The in-memory evaluator — the one implementation the repo has for $field — does not, and this is structural rather than an oversight. matches-filter.ts resolveValue returns early for an array:

function resolveValue(raw: unknown, record) {
  if (raw && typeof raw === 'object' && !Array.isArray(raw) && '$field' in raw) {  }
  return raw;   // ← an ARRAY comes back unchanged, members unresolved
}

evalOp resolves the whole comparand, never the members. So for $in/$nin the raw reference OBJECT is compared with looseEq against a stored value (never equal), and for $between the raw object becomes an ordering bound.

Why it matters

This is the ADR-0049 enforce-or-remove shape at a declared position: the spec promises a comparand form that nothing implements, and the failure is SILENT on the memory path — { amount: { $in: [{ $field: 'budget' }] } } matches nothing and reports nothing. On an RLS check that is a denied write with no diagnostic; the $nin direction loses an exclusion the author wrote.

driver-sql / driver-sqlite-wasm refuse these positions loudly (INVALID_FILTER / 400) — #5041 installed that and #5222 deliberately kept it, precisely because there is no correct in-memory semantics for SQL to be equivalent to. So today the declaration is honoured by nobody and refused by two backends.

The decision this needs

Either:

  • Remove FieldReferenceSchema from the $between endpoint unions (and rule $in/$nin members out explicitly), making declared = enforced; or
  • Implement member resolution in matches-filter.ts and then decide whether the SQL faces can compile it conformance-equivalently (per-member OR-expansion, whose NULL and type-affinity behaviour is the open question [spec] SqlDriver 将 $field 编译为列对列比较(cross-field comparison push-down) #5222 declined to guess at).

Not decided here — it is a contract-face call, same class as the #5222 rulings.

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