039. Reuse qualified raw native columns in generic expressions

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

Problem and mechanism

Generic scalar expressions could add a LATERAL value projection even when a qualified native scalar binding already supplied the exact SQL column expression. Build now records a narrow proof for a direct scalar Bind whose SQL column reference has at least two literal identifier components. If a later Variable or Field node resolves to that exact registered column and has constant TRUE presence, the expression can reuse the column in the current projection scope.

Presence here means the internal no-value flag, not SQL nullability. Required, nullable, and optional scalar fields can all qualify: SQL NULL remains NULL, and consumers still apply their normal null rules. A guard's result keeps its own no-value handling even when its raw child column can be reused.

The proof does not apply to unqualified column references, guard results, BindJSON, Timestamp conversions, metadata, BindStruct parent-state expressions, BindComposite, BindRelation, computed expressions, or callback parameters. Those keep their existing projections and presence handling. A plain Bind for a field beneath required schema structs can qualify. Path caches remain isolated by callback scope and binding identity, so captures never replace shadowed parameter bindings.

Applicability

Expression Status Reason
Qualified native scalar Bind with an exact raw Variable or Field reference Applied Reuses the column with constant TRUE no-value presence, including nullable and optional fields.
Qualified raw column inside an eligible composed expression or callback capture Applied Existing projection-scope caches preserve binding identity and scope.
Unqualified references, computed method results, or guard results Not applied Keeps the existing binding, computed-value, or no-value projection.
JSON, metadata, Timestamp, structured-parent, relation, or callback-parameter values Not applied These are converted, stateful, or scoped carriers rather than the registered raw scalar.

Reproduction

The baseline is commit 2ac11c1. The shape checks used Go 1.27.1 on Darwin/arm64 and PostgreSQL 18.6 on Debian/aarch64 with extra_float_digits=1. They measured generated WHERE bytes and OFFSET 0 barriers, not execution time.

The shape fixture used the following empty temporary table:

CREATE TEMP TABLE records (
  i bigint, j bigint,
  f double precision, g double precision,
  s text, t text,
  p boolean, q boolean,
  b bytea, c bytea,
  d bigint, e bigint,
  items json, data json
);

The HFF schema used Int for i/j, Float for f/g, String for s/t, Bool for p/q, Bytes for b/c, and Duration for d/e (bigint nanoseconds). Each scalar used Bind(hff.Path{name}, Column("records", name)). All scalars were tested as nullable and, separately, required. items used List<Int> and data used Map<Any>, both bound with BindJSON(..., JSON). Representative filters were i+1>j, i=f, i!:exists, s:lower=t, b:len=c:len, items:any(x=>i:exists), and i+1>0 j+2<10 s="end".

The durable integration fixture native_column_projection_integration_test.go checks qualified identifiers, reserved-looking aliases and names, unqualified fallbacks, structured-parent state, callback captures and shadowing, and computed-expression controls. In addition to the earlier direct-filter matrix's 30,740 comparisons, 1,856 scoped comparisons passed against both baseline and candidate. The scope fixture has 29 filter/binding cases over 64 records (four left Int values, four right Int values, and four parent states: missing, null, zero age, and nonzero age). Its qualifier is __hff_1; physical names include v, p, value, ord, and a name containing a quote and dot. Unqualified v/p bindings are separate controls. The existing Float operand controls (168 comparisons), relation-alias controls (20 pairs), and repeated numeric-kernel controls (16,416 comparisons) also passed against both revisions.

Run the dedicated fixture with:

HFF_POSTGRES_CONTAINER=<container> GOTOOLCHAIN=go1.27.1 go test -tags=integration ./backends/sql -run '^TestPostgreSQLQualifiedNativeColumnScopes$'

The temporary shape generator and emitted SQL are not committed. The schema, binding shape, and representative filters above describe the measured cases; the integration fixture is the durable semantic oracle.

Results

The table reports nullable-field WHERE byte lengths and OFFSET 0 barrier counts before and after. Reductions are partial: operation projections and other barriers remain when an expression computes a new value.

Filter Baseline bytes / barriers Candidate bytes / barriers
i+1>j 610 / 5 478 / 3
i=f 867 / 5 751 / 3
i!:exists 290 / 3 225 / 2
f:floor>1 880 / 5 813 / 4
i:json=i:json 396 / 4 331 / 3
s:lower=t 441 / 4 309 / 2
b:len=c:len 439 / 5 305 / 3
d:nanoseconds:json=i:floor:json 599 / 7 465 / 5
items:any(x=>i:exists) 679 / 6 612 / 5
i+1>0 j+2<10 s="end" 1,197 / 8 1,067 / 6
JSON field data.i:exists control 523 / 5 523 / 5
Direct i=j control 91 / 0 91 / 0

Plain EXPLAIN on the fixture showed fewer nested-loop layers for affected expressions, for example four to two for i+1>j. The plans were inspected on empty fixtures without ANALYZE; no runtime speed claim follows.

Correctness and tradeoffs

The translator reuses a column only when the binding path resolves to the exact SQL expression registered during Build. Literal identifier components are preserved and quoted, and generated aliases avoid caller-owned names. Nullable raw columns keep SQL NULL without needing a leaf projection. A guard result, structured parent, JSON conversion, or computed method result does not acquire the raw-column proof. Callback captures can reuse an outer bound column only within the existing scope; a callback parameter or shadowed binding does not inherit that proof.

This removes projections for eligible raw references inside generic expressions. It does not eliminate projections for the operations around them: for example, i+1, s:lower, b:len, and f:floor still compute values and retain the barriers required by their current lowering. SQL shape and plan observations do not measure execution speed or guarantee plans for other schemas and data.

Decision and limits

Reuse exact qualified raw scalar columns only with the recorded path, SQL expression, and constant-presence proof. Keep the general lowering for unqualified, computed, converted, guarded, structured, and otherwise scoped values until each has a separate correctness proof.

Explore HFF
Search documentation

Search the language specification and guides.