054. Skip guard-result reconstruction for immediate root observers
- Status: Implemented
- Date: 2026-10-16
Problem and mechanism
A JSON value can be guarded as a numeric-containing List or Map, then consumed
immediately by a root observer such as type(), exists(), or size().
Ordinary lowering normalizes the guarded collection to JSON even though the
observer needs only the full guard result, the root category, or collection
size. For this bounded shape, the builder still validates the ordinary value
path and preserves its diagnostics, but the emitted query evaluates the source
and full guard without rebuilding the normalized payload. Existing shortcuts
for declared identity guards remain unchanged.
The optimization can skip reconstruction only for the final numeric-containing
List/Map type guard when its result immediately feeds type(), exists(),
size(), or list count(). Root Union guard targets remain on ordinary
lowering. Any earlier guards still normalize their outputs. A later guard, a
value consumer such as json() or string(), predicate count(), or a
carrier without a proven JSON representation keeps ordinary lowering for the
final guard. A deferred value is eligible when ordinary lowering produces a
proven JSON carrier and the other conditions hold.
JSONB still requires numeric type metadata whenever the guard must observe an
Int/Float distinction lost in storage.
Applicability
| Shape | Status | Reason |
|---|---|---|
| Final numeric List/Map guard followed immediately by a root observer | Applied | Earlier guards retain normal output; the root observer does not consume the final normalized payload. |
| Eligible numeric guard followed by another guard or value consumer | Not applied | The later operation requires the final guard's normalized output. |
Root Union targets, predicate count(), carriers without a proven JSON representation, or uncertain source shape |
Not applied | These do not meet the immediate-root proof. |
| Local | Not applicable | This is a PostgreSQL SQL-lowering choice. |
Reproduction
The baseline is 58ad777.
The candidate is the implementation containing this record. The SQL-shape
fixture uses an Any field named data bound as JSON. Go 1.27.1 ran on
Darwin arm64; PostgreSQL 18.6 was used for plain plan inspection.
Run the focused Go tests:
go test ./backends/sql -run '^TestRootNumericGuardObservers' -count=1
HFF_POSTGRES_CONTAINER=<container> go test -tags=integration ./backends/sql \
-run '^TestPostgreSQLRoot' -count=1
The native JSON SQL-shape cases are data:[list[float]]:exists,
data:[list[int]]:size=2, data:[map[float]]:type="object", and
data:[map[list[float]]]:size=2. The focused PostgreSQL test
TestPostgreSQLRootNumericGuardObservers also checks root observers, value
consumers, and nested guard behavior across JSON and JSONB. Its numeric guard
matrix passed 532 query-record results; related root observer checks cover
guard chains, selective numeric restoration, empty/missing values, and
parameter inference.
Supplementary generated SQL and plan output are retained outside the
repository at /private/tmp/hff-root-guard-observer-{before,after}-{0,1,2,3}.sql
and /private/tmp/hff-root-guard-observer-{before,after}-explain.log.
Results
| Native JSON expression | Baseline WHERE bytes |
Candidate WHERE bytes |
Change | Uncertainty |
|---|---|---|---|---|
data:[list[float]]:exists |
4,831 | 1,107 | −3,724 bytes | Generated SQL size only |
data:[list[int]]:size=2 |
3,797 | 1,617 | −2,180 bytes | Generated SQL size only |
data:[map[float]]:type="object" |
4,926 | 1,434 | −3,492 bytes | Generated SQL size only |
data:[map[list[float]]]:size=2 |
6,870 | 2,039 | −4,831 bytes | Generated SQL size only |
For the List Float exists query, plain EXPLAIN showed one Aggregate node
before and none after, with 35 plan lines before and 15 after. The plan still
contains function scans used by the full guard validation. Other expression
shapes were not inspected with EXPLAIN. Guard-chain and JSON-value consumer
SQL remained byte-identical. These observations describe SQL shape and plan
placement only; no execution timing, allocation, EXPLAIN ANALYZE, or
runtime-speed result was collected.
Correctness and tradeoffs
The source expression and complete guard remain in the query. Only the final
normalized payload reconstruction is omitted when the immediate observer does
not consume it. Tests cover JSON and JSONB guard results, JSONB metadata,
decoder failures, null and missing values, cancellation, and controls that
still require normalized output. Build continues to validate expressions
that the emitted observer path does not consume.
Decision and limits
Use this path only for the immediate root observers listed above after a final numeric-containing List/Map guard. Earlier guards keep their normalized output; later guards, serialized values, callback predicates, and any case whose output depends on the final normalized collection keep the ordinary lowering. The extra validation can increase Build work; the SQL-size and plain-plan observations do not establish a runtime benefit.