Using the Go API
This guide shows how the public Go packages fit together in a service. For generated package documentation, see the Go API reference or pkg.go.dev. Query semantics are defined by the language specification.
Main packages
| Package | Purpose |
|---|---|
github.com/ornew/hff |
Immutable values, types, limits, errors, and compiled-plan inspection. |
github.com/ornew/hff/schema |
Struct, map, and primitive schema constructors. |
github.com/ornew/hff/adapters/govalue |
Explicit Go-to-HFF conversion or inferred Binding[T] and typed local filters. |
github.com/ornew/hff/capabilities |
Standard and custom capability sets. |
github.com/ornew/hff/engine |
Engine configuration, validation, and query compilation. |
github.com/ornew/hff/backends/local |
Reusable local filters and matching. |
github.com/ornew/hff/backends/sql |
Pure SQL translation and parameterized artifacts. |
Configure and compile
Configure the schema and capability policy once, then compile query strings to immutable programs:
e, err := engine.New(
engine.WithSchema(schema.Struct(
schema.String("name"),
schema.Int("age"),
)),
engine.WithCapabilities(capabilities.Standard()),
)
if err != nil {
return err
}
program, err := e.Compile(ctx, `name="Alice" age>=18`)
if err != nil {
return err
}
engine.Engine is safe for concurrent validation and compilation. A
hff.Program is immutable and can be passed to supported backend builders.
Compilation returns typed errors with source spans; inspect stable error codes
rather than branching on message text.
Errors and diagnostics
hff.Error contains Code, Message, optional Path and Span, and
one-based Line and Unicode-code-point Column when a query source location is
available. Span is a half-open byte range in the original UTF-8 source. A
single failure remains a *hff.Error. Multiple independent parse or compile
failures are returned as *hff.Diagnostics, which exposes Errors() []error
and implements Unwrap() []error. Thus errors.As can find either the group
or each member's *hff.Error, and errors.Is can inspect wrapped causes. The
group's error slice is copied. Diagnostics are source ordered; cancellation
and limit failures stop collection instead of being combined with ordinary
diagnostics. A recovered parse tree is never returned as a usable program.
Construct values and validate input
The explicit value API avoids reflection and conversions:
record := hff.Object(map[string]hff.Value{
"name": hff.String("Alice"),
"age": hff.Int(18),
})
input, err := e.Validate(ctx, record)
if err != nil {
return err
}
The schema package provides constructors such as schema.String,
schema.Int, schema.Float, schema.Bool, schema.List, schema.Map, and
schema.Struct. hff.Value and hff.Type are immutable. Use Value accessors
such as Kind, AsString, AsInt, and Lookup to inspect values.
AsList returns a copy of list elements for indexed access; Type.Equal
compares type descriptors. Input.Value() returns the validated immutable value and
Input.Compatible(type) checks exact schema compatibility.
For optional reflection-based Go-to-HFF conversion, use
adapters/govalue. govalue.New converts into an
explicit HFF schema with explicit struct mappings. govalue.NewBinding[T]
instead infers a schema from a static Go type and provides a typed local
filter. Neither replaces the explicit Value API.
Build and run a local filter
filter, err := local.Build(ctx, program)
if err != nil {
return err
}
matched, err := filter.Match(ctx, record)
// Or pass a previously validated Input to MatchInput.
The filter is immutable and safe for concurrent matches. Match validates its
record; MatchInput checks schema compatibility and reuses validated input.
Local runtime limits can be set with local.WithConfig, local.WithLimits, or
local.WithUnlimitedLimits. Inspect typed errors with errors.As into
*hff.Error.
Build a SQL artifact
SQL construction also accepts a hff.Program and performs no database I/O:
query, err := hffsql.Build(ctx, program,
hffsql.WithDialect(hffsql.PostgreSQL),
hffsql.WithBindings(
hffsql.Bind(hff.Path{"name"}, hffsql.Column("records", "name")),
hffsql.Bind(hff.Path{"age"}, hffsql.Column("records", "age")),
),
)
Inspect query.Where, query.Args, and query.Guarantees. Their accessors
return copies. The caller owns database connections, execution, transactions,
and timeouts. Consult the PostgreSQL backend reference
for supported operations and constraints.
To bind a schema-known value stored in a native JSON or JSONB column, use
hffsql.BindJSON with an explicit format:
hffsql.BindJSON(
hff.Path{"profile"},
hffsql.Column("records", "profile"),
hffsql.JSONB,
)
See the PostgreSQL JSON binding guide for the supported schema profile, collection access and equality behavior, and storage requirements.
Map HFF paths to PostgreSQL storage
An HFF Path names schema fields. A ColumnRef names separately quoted SQL
identifier components. For example, hff.Path{"profile", "name"} is a nested
HFF field path; hffsql.Column("records", "profile") is the physical
identifier "records"."profile". A dotted string inside one component is
literal, not split into a path. See the
PostgreSQL binding tutorial for a
worked record-to-table mapping.
| API | Physical shape | State represented |
|---|---|---|
Bind(path, Column(...)) |
One scalar SQL column | SQL NULL is explicit null; an optional field still needs an enclosing structure when missing must differ from null. |
BindJSON(path, column, JSON/JSONB) |
One JSON document | Schema-known nested objects/lists/maps, including missing object keys and JSON null. |
BindComposite(path, column) |
One native PostgreSQL composite | SQL-null row differs from a present row with null attributes; attributes themselves are always present. |
BindStruct(path, present, null, fields...) |
Struct spread across columns | Caller supplies parent presence and null predicates; child SQL nulls are explicit null fields. |
BindRelation(path, relation) |
Correlated child rows | A list; no matching rows are empty by default. Missing/null list states require explicit state predicates. |
For BindStruct, child paths are one field component relative to the bound
struct. For BindRelation, the item binding has an empty hff.Path{} root;
its child paths are relative to one row of the related source. WithRelationSource
selects one table or view, while WithCorrelation supplies a predicate that
relates that row to the outer record. Prefer the typed SQLPredicate
constructors; raw TrustedSQL is an escape hatch for fixed developer-authored
PostgreSQL text. It is not parsed or proven safe, deterministic, or equivalent
to HFF. Never put user or query data in raw text, and ensure raw state or
correlation expressions are deterministic and side-effect-free. The API does
not infer a foreign key or generate a join. WithUniqueOrder is required only
when the HFF consumer can observe the list's order.
Build validates the HFF binding and returns Query.Where() plus the ordered
Query.Args(). It does not inspect the physical schema, open a connection,
execute the statement, or validate stored rows. The service must use the
predicate in its own statement and pass arguments separately to its database
driver. The SQL predicate constructors quote identifiers and bind literal
values. TrustedSQL accepts raw PostgreSQL text; HFF does not parse it, prove
its Boolean semantics or purity, or sanitize it. Its ColumnRef parameters
support identifier alias bookkeeping only. Never concatenate user input into
trusted SQL.
For a struct stored in separate columns, hffsql.BindStruct requires trusted
parent-present and parent-null expressions, plus relative child bindings. A
caller-owned related row source uses hffsql.NewRelation with
WithRelationSource, WithCorrelation, and WithRelationItem; use
WithUniqueOrder only when an operation observes list order. See the
flattened struct guide and
relation guide for null/presence rules,
typed items, and ordering requirements.
For a native PostgreSQL composite column, bind an unambiguous struct schema
with hffsql.BindComposite(path, column). Composite attribute names must match
the schema field names; attributes are always present, so a null attribute is
an explicit null rather than an absent optional field. See the
composite binding guide for the current
physical type profile and its limits.
Inspect a compatibility profile
Profiles provide immutable snapshots of known backend and build facts. The
static local profile is available from local.Profile(); a built filter reports
its effective configuration through filter.Profile(). For SQL, use
hffsql.Profile(dialect) for the static translation profile and
query.Profile() for the built query's profile. SQL profile inspection does
not connect to or inspect a database.
profile := filter.Profile()
if support, known := profile.Feature(capabilities.Method("timestamp.year")); known {
// support.Level is Supported, Conditional, or Unsupported.
// Conditional entries include the condition in support.Conditions.
}
if limits, known := profile.RuntimeLimits(); known {
// A known zero field means explicitly unbounded for that stage.
_ = limits.MaxSteps
}
if version, known := profile.Version("tz"); known {
_ = version // Supplied zone-data version, when configured.
}
The aggregate Profile example
finds and prints the float.aggregation detail from Local and PostgreSQL
profiles. Run it with go run ./examples/aggregate-profiles.
Profile exposes Backend, Features, Feature, Capabilities,
CompileLimits, InputLimits, RuntimeLimits, Versions, Version,
Guarantees, HasGuarantee, Details, and Methods. Slice accessors return
copies. A missing feature or version is unknown; UnknownSupport is not
reported as supported. Each stage-limit accessor returns (Limits, bool): a
false boolean means unknown or outside that backend's responsibility, while a
true boolean with a zero applicable limit field means that ceiling is
explicitly unbounded. Other fields in a stage's Limits value are unused:
| Stage | Applicable fields |
|---|---|
| Compile | MaxQueryBytes, MaxSyntaxDepth, MaxCallbackDepth, MaxCompileSteps |
| Input | MaxInputDepth, MaxInputValues |
| Runtime | MaxSteps, MaxGeneratedValues, MaxJSONDepth |
The capability set reports service policy only when specified.
Backend feature support is a separate statement and does not grant permission.
Registered method declarations in Methods() likewise do not enable those
methods.
Profiles also expose backend-specific numeric details such as float.model,
float.parsing, float.arithmetic, float.rounding, and float.format.
These describe approximate Float behavior that can differ across backends;
they do not change exact Int behavior or mixed Int/Float comparison rules.
Profiles do not infer database, collation, regex, or time-zone data versions
from ambient state. A backend may report a separately versioned data table,
such as the Go Unicode tables used for PostgreSQL case conversion. A missing
version is unknown. Guarantees list only
behavior the backend reports; absence is not a promise. In particular, a SQL
profile does not claim that PostgreSQL enforces local evaluation budgets or
HFF evaluation order. Build remains authoritative for a specific program,
binding, and configuration, and database execution behavior remains under the
service's control.
Applications that define their own backend profile can construct one with
hff.NewProfile(hff.ProfileConfig{...}). The constructor copies slice and
limit-pointer inputs, validates the declarations, and returns a configuration
error for invalid or duplicate entries. Set a stage's *Limits pointer only
when that profile knows the effective limits; a nil pointer means unknown or
outside the backend's responsibility. GuaranteeID entries are affirmative
claims, not a list of missing or unsupported guarantees. Details are
backend-specific descriptive name/value pairs and do not alter capabilities
or enforcement.
Run the profile example
from the repository root with go run ./examples/profiles to inspect both a
built local filter and a translated PostgreSQL query.