046. Check PostgreSQL collection predicates without materializing values

  • Status: Implemented
  • Date: 2026-10-14

Problem and mechanism

Some root collection filters observe only whether a collection is empty or its cardinality. The translator can answer those filters from a physical JSONB root or a correlated relation instead of decoding or aggregating the complete collection.

For an unguarded, statically typed root JSONB BindJSON without type metadata, empty List/Map/Struct equality and inequality and bare collection truth use a shape-aware comparison with [] or {}. SQL NULL and JSON null are false in filter position. For a List root, nonzero literal size() comparisons use jsonb_array_length only after a CASE checks that the stored root is an array. Zero-size comparisons reduce to an empty/nonempty shape check or a constant filter result.

For a plain BindRelation, empty-list equality and inequality, bare truth, and zero-cardinality comparisons reduce to correlated EXISTS or NOT EXISTS, a state test, or a constant result. The configured parent/relation presence and null state remains around the predicate. Nonzero relation thresholds retain the count-based lowering; no unique order is required for the zero-cardinality checks.

Applicability

Expression Status Reason
Root JSONB List/Map/Struct without metadata: empty equality/inequality or truth Applied Tests the typed empty container and JSON shape.
Root JSONB List size() with an Int literal Applied Zero uses a shape check or constant; nonzero uses guarded jsonb_array_length.
Root JSONB Map/Struct size() with a nonzero Int Not applied Retains general lowering.
Plain relation: empty equality/inequality, truth, or zero size() Applied Uses existence, state, or a constant result and preserves relation state.
Plain relation with a nonzero size() threshold Not applied Retains count-based lowering.
Guarded, computed, nested, metadata-backed, or dynamic receiver Not applied Retains general lowering and representation checks.

Reproduction

The baseline is commit d33d9a6; the candidate is the implementation in this revision. The focused SQL-shape tests are TestDirectJSONBCollectionPredicates and TestRelationEmptyAndZeroCardinalityUseExists. With Go 1.27.1, run:

go test ./backends/sql \
  -run '^(TestDirectJSONBCollectionPredicates|TestRelationEmptyAndZeroCardinalityUseExists)$' \
  -count=1

Focused PostgreSQL/local parity covered SQL NULL, JSON null, empty and nonempty JSONB Lists/Maps, and nullable/missing relation state. The JSONB integration fixture is exercised by TestPostgreSQLCollectionEmptinessMatchesLocal. The existing TestPostgreSQLRelationParity and TestPostgreSQLRelationStateParity fixtures include the relation empty/truth/zero-size forms.

Run the focused JSONB integration parity with a configured PostgreSQL test container:

HFF_POSTGRES_CONTAINER=<container> go test -tags=integration ./backends/sql \
  -run '^(TestPostgreSQLCollectionEmptinessMatchesLocal|TestPostgreSQLRelation(State)?Parity)$' \
  -count=1

The relation parity fixtures include the empty-list, truth, and zero-size queries for both default relation state and optional/nullable relation state.

Results

The direct path emits empty-container comparisons or correlated existence predicates for the listed forms. It uses guarded jsonb_array_length for nonzero List cardinality comparisons. Higher relation cardinalities retain the count-based path. No execution-time or allocation benchmark is reported.

Correctness and tradeoffs

The JSONB shape test distinguishes SQL NULL and JSON null from an empty List or object. A wrong JSON container shape does not match the declared collection filter. The CASE protects jsonb_array_length from values where PostgreSQL would raise a shape error, independent of Boolean evaluation order. Relation filters preserve configured missing/null state; a related row counts as present even when its item value is null.

Decision and limits

Use these direct predicates only for the applicability rows above. Nested, metadata-backed, guarded, computed, and dynamic values retain their previous lowering. Higher relation counts still use the count-based path. These SQL forms document translation behavior; they make no runtime-speed or planner guarantee.

Explore HFF
Search documentation

Search the language specification and guides.