PostgreSQL Composite Bindings
hffsql.BindComposite maps a schema-known HFF struct to a native PostgreSQL
composite column. It preserves row-null state, attribute nulls, nested structs,
and supported arrays while lowering the value into the backend's typed access
representation. Build does not connect to PostgreSQL or inspect types or rows;
the service must create compatible composite types and store values that match
the declared HFF schema.
Define and bind a composite column
Use a struct schema whose field names match the PostgreSQL composite attribute names. This example binds a nullable profile that contains a list of nullable person composites:
personType := schema.Struct(schema.Nullable(schema.String("name")))
e, err := engine.New(
engine.WithSchema(schema.Struct(
schema.Nullable(schema.Field("profile", schema.Struct(
schema.Nullable(schema.Int("age")),
schema.Nullable(schema.List("people", schema.NullableType(personType))),
))),
)),
engine.WithCapabilities(capabilities.Standard()),
)
if err != nil {
return err
}
program, err := e.Compile(ctx, `profile!.age>=18 profile.people:any(person=>person!.name="Alice")`)
if err != nil {
return err
}
query, err := hffsql.Build(ctx, program,
hffsql.WithDialect(hffsql.PostgreSQL),
hffsql.WithBindings(hffsql.BindComposite(
hff.Path{"profile"},
hffsql.Column("records", "profile"),
)),
)
if err != nil {
return err
}
rows, err := db.QueryContext(ctx,
`SELECT records.id FROM records WHERE `+query.Where(), query.Args()...)
The corresponding PostgreSQL types might be declared as:
CREATE TYPE hff_person AS (name text);
CREATE TYPE hff_profile AS (age bigint, people hff_person[]);
CREATE TABLE records (id bigint, profile hff_profile);
Insert a SQL-null parent and a non-null composite parent to see the physical distinction:
INSERT INTO records VALUES
(1, NULL),
(2, ROW(42, ARRAY[ROW('Alice')::hff_person, NULL::hff_person])::hff_profile),
(3, ROW(NULL, NULL)::hff_profile),
(4, ROW(0, ARRAY[]::hff_person[])::hff_profile);
For row 2, the reconstructed HFF value is
{"profile":{"age":42,"people":[{"name":"Alice"},null]}}; row 3 has a
present profile with both attributes null, and row 4 has a present profile
with an empty people list. Row 1 has a null profile. The filter above selects
row 2; rows 1, 3, and 4 do not match. The profile field in
this example is required but nullable, as declared in the schema, so it cannot
represent a missing profile. If the HFF field is optional, a native composite
still cannot distinguish absence from a present composite; use BindStruct or
another representation with an explicit presence channel.
The runnable composite example builds this predicate without database I/O:
go run ./examples/sql-composite
It prints Predicate built: true and Parameter count: 11.
The path identifies the HFF value, while Column contains trusted PostgreSQL
identifier components, not SQL source. A composite binding must target an
unambiguous struct schema. It cannot overlap another binding for a descendant
path or conflict with another physical format for the same column.
Presence, null, and supported attributes
A SQL NULL composite is an HFF null struct. A non-null row whose attributes
are all null remains an object containing those explicit null values. Nested
composites follow the same rule. PostgreSQL composite attributes always exist;
a null attribute is an explicit HFF null, never an absent optional field. A
composite therefore cannot represent absent optional fields. If the HFF schema
marks a field optional, use this binding only when the stored rows guarantee
that the attribute is present.
Supported leaf values are null, bool, int64, finite float64, and string.
Nested struct attributes and one-dimensional PostgreSQL arrays of supported
scalars or structs map to HFF structs and lists; nullable elements retain their
null values. Array elements retain logical order even when the PostgreSQL
array has a non-default lower bound. A SQL NULL array is HFF null; an empty
array is an empty list. Array rank is a caller storage precondition: Build
does not inspect row data to verify one-dimensionality.
Maps, dynamic Any, ambiguous non-null unions, bytes, timestamps, durations,
nested-list schemas, and unsupported physical representations are rejected by
Build. The application must ensure that composite attribute names and types,
array element types and dimensions, nullability, and actual row values satisfy
the schema. In particular, arrays must be one-dimensional. Build does not
introspect PostgreSQL declarations or validate stored rows.
Duration is supported for native scalar bigint bindings and direct flattened
field access, but not as a composite attribute or array element.
Selecting fields
A plain field path such as profile.age or profile.father.name reads the
corresponding native composite attributes directly. It does not construct JSON
for unrelated attributes or arrays. SQL NULL at the composite parent and a
null selected attribute retain their HFF null behavior. When a query needs the
whole composite value, or uses a guarded, dynamic, or method-based access, the
backend uses the general typed conversion path; that path converts the full
schema-known value. These are SQL-shape choices only and do not promise a
particular execution-time improvement.
Execution and verification
Composite translation is part of pure SQL construction. The returned predicate and ordered parameters are consumed by the caller's database API; the SQL backend does not execute queries, own connections, or set database timeouts. See the PostgreSQL backend overview for execution and limit contracts.
The tagged PostgreSQL integration suite checks composite rows against local HFF evaluation, including SQL-null rows, all-null rows, nested composites, arrays of nullable scalar and composite elements, null attributes, and non-default array lower bounds. The composite matrix covers 49 queries across six records (294 query/record pairs); the broader SQL matrix also covers 5,556 JSON/JSONB, 5,680 scalar, and 152 extension parity pairs on PostgreSQL 18.6:
HFF_POSTGRES_CONTAINER=<container-name-or-id> go test -tags=integration ./backends/sql
See Development checks for the test environment.