040. Reuse repeated native operations within a projection scope

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

Problem and mechanism

Repeated native Int arithmetic and String case conversion could translate and project the same computation more than once in a PostgreSQL filter or callback scope. The translator can now reuse a completed native result and its presence for Int addition, subtraction, multiplication, division, modulo, negation, and built-in String lower() and upper() when the ordered operation and typed operands match within the existing projection scope.

Each occurrence is lowered and validated in source order before the lookup. Cache keys include the operation, result type, ordering class, and each operand's kind, presence, static type, and carrier. Int and String literal nodes, plus built-in conversions already folded by ordinary lowering, use their validated semantic values rather than fresh placeholder positions. On a cache hit, only arguments belonging to newly translated eligible literal operands are removed. A hit involving a nonliteral operand is allowed only when lowering that operand added no arguments. A cache miss can register a new result for reuse by a later nested expression.

Applicability

Repeated expression Status Reason
Same native Int +, -, *, /, %, or negation with matching typed operands Applied Reuses the completed value/presence projection in the same scope.
Same built-in String lower() or upper() with matching receiver and operands Applied Reuses the translated case-conversion result and its arguments.
Nested repeats in one callback or fold scope Applied Reuses results only within the existing scope and binding identity.
Different operands, operator, receiver, presence, type, or scope Not applied The cache key does not match; ordinary lowering remains.
Custom method translators Not applied They are invoked through the normal extension contract and are never constant-evaluated for cache keys.
A nonliteral cache hit that would add new parameters Not applied Its new arguments cannot be discarded safely.

Reproduction

The baseline is commit 2f47561. The fixture used Go 1.27.1 on Darwin/arm64 and PostgreSQL 18.6 on Debian/aarch64 with extra_float_digits=1. It measured generated WHERE bytes, OFFSET 0 barriers, and parameter counts; no execution timing or benchmark was collected.

The parity fixture bound native Int fields i and j, Float fields f and g, String fields s and t, Bool fields p and q, Bytes fields b and c, and Duration fields d and e (bigint nanoseconds), all nullable. It also bound items as List<Int>, data as Map<Any>, and words as List<String|null> in JSON. The identical fixture compared 40 filters over 121 records (11 values for each Int input), including null, int64 limits, negative and zero values, Unicode case ranges, signed zero, division/modulo failures, nested arithmetic, callback captures and shadowing, maps, and folds. All 4,840 PostgreSQL/local comparisons passed on both baseline and implementation.

The temporary shape fixture used this table and qualified native bindings:

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 bindings used nullable and required profiles for the scalar pairs; items was List<Int> and data was Map<Any>, each bound with BindJSON(..., JSON). Duration fields use bigint nanoseconds.

The durable parity fixture is native_operation_cache_integration_test.go. Unit fixtures cover argument ordering, Unicode conversion, cache scope, extension dispatch, text validation, and parameter-use integrity. Run them with:

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

The separate temporary shape fixture used the same nullable and required column bindings, plus List<Int> and Map<Any> JSON bindings. Representative filters include:

i+2>10 i+2<20
i+j>0 i+j<20
i/2>0.0 i/2<20.0
(-i)>0 (-i)<20
(i+2)*3>10 (i+2)*3<20
s:lower="a" s:lower="b"
s:lower:upper="A" s:lower:upper="B"
items:any(x=>x+2>0 x+2<20)
items:foldr(i,(x,i)=>(i+x)*2+(i+x)*2)>0

The temporary generator and emitted SQL are not committed. The integration fixture is the durable semantic oracle; the filters and binding families above describe the SQL-shape inputs.

Results

Generated WHERE byte counts, OFFSET 0 barriers, and arguments were counted for the same representative filters on baseline and implementation. The table reports the nullable scalar profile and measures SQL shape only.

Filter Baseline bytes / barriers / args Implementation bytes / barriers / args
i+2>10 i+2<20 1,050 / 7 / 4 715 / 5 / 3
i+j>0 i+j<20 1,052 / 7 / 2 716 / 5 / 2
i/2>0.0 i/2<20.0 742 / 5 / 4 571 / 4 / 3
(-i)>0 (-i)<20 644 / 5 / 2 494 / 4 / 2
(i+2)*3>10 (i+2)*3<20 1,752 / 11 / 6 1,064 / 7 / 4
s:lower="a" s:lower="b" 708 / 5 / 6 566 / 4 / 4
s:lower:upper="A" s:lower:upper="B" 1,021 / 7 / 10 722 / 5 / 6
items:any(x=>x+2>0 x+2<20) 1,800 / 13 / 4 1,464 / 11 / 3
items:foldr(i,(x,i)=>(i+x)*2+(i+x)*2)>0 3,500 / 20 / 3 2,807 / 16 / 2
Different Int constants or operators control 1,050 / 7 / 4 unchanged
Single-use arithmetic control 477 / 3 / 2 unchanged
Single-use lower() control 306 / 2 / 3 unchanged

Plain EXPLAIN on an empty table showed fewer nested-loop nodes in sampled repeated expressions: six to four for the root arithmetic example, ten to six for the nested arithmetic example (i+2)*3>10 (i+2)*3<20, and four to three for repeated lower(). These fixture-specific plans were inspected without ANALYZE; they do not establish execution speed or a plan for populated tables.

Correctness and tradeoffs

All operands are lowered and validated before the lookup, preserving source order and diagnostics. Tests assert that every retained SQL placeholder has an argument and every argument remains referenced. When a cache hit removes a literal's fresh placeholder, the first occurrence's parameter remains in its original position. Nonliteral cache hits that would add arguments fall back to ordinary lowering. Unicode coverage includes all Unicode case ranges, Turkish I, sigma variants, sharp S, and supplementary characters.

Different operators, constants, receivers, guarded-presence states, and callback scopes retain separate computations. Callback captures may reuse a value inside their scope, while shadowed bindings and separate callbacks do not share entries. Custom method implementations are invoked normally and are not evaluated to discover constant cache inputs. The tests verify that no unused placeholders remain.

Decision and limits

Reuse only completed native Int arithmetic/negation and built-in String case conversion results whose operation, typed operands, presence, result type, and projection scope match. The reduced SQL shape is not a runtime-speed claim. Other computed methods, custom translators, and cross-scope reuse retain their ordinary lowering.

Explore HFF
Search documentation

Search the language specification and guides.