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.

Explore HFF
Search documentation

Search the language specification and guides.