010. SQL Extension Translation

  • Status: Accepted
  • Author: @ornew
  • Date: 2026-10-11

Summary

An SQL extension is a dialect-specific, build-time translator for one immutable HFF method declaration. It receives typed expressions scoped to a translator call and constructs SQL from trusted templates and bound scalar values. HFF continues to own signature selection, capabilities, null/no-value behavior, parameter ordering, and source diagnostics. SQL execution and timeouts remain the responsibility of the calling service.

Motivation

The same HFF program can be built for multiple backends, but an extension may have an implementation for only some of them. Passing raw query strings into an extension would bypass parameter binding and expose backend internals. A typed, call-scoped construction API gives translators access to their arguments while keeping SQL artifacts owned by the backend.

Goals

  • Register a translator against an exact method declaration and selected SQL dialect.
  • Keep SQL construction pure, parameterized, immutable, and safe for concurrent builds.
  • Preserve core HFF overload, capability, null, no-value, and argument-order semantics around translated expressions.
  • Let callers inspect backend-enforceable execution guarantees and explicitly request limits; reject requests the selected backend cannot enforce.
  • Keep SQL text an implementation artifact and keep database I/O outside HFF.

Non-goals

  • Arbitrary SQL parsing, SQL injection filtering, or verification that a developer-authored template implements the declared method's semantics.
  • SQL database connections, execution, pooling, or database timeout policy.
  • Runtime registration of arbitrary third-party dialect implementations.
  • Claiming that the PostgreSQL backend enforces local HFF evaluation budgets.

Design

Translator registration

The extension declaration and SQL translator are separate registrations:

translator := sql.Implement(declaration, sql.PostgreSQL,
    func(call *sql.Call, receiver sql.Expression, args []sql.Expression) (sql.Expression, error) {
        suffix, err := call.Parameter(hff.String("!"))
        if err != nil {
            return sql.Expression{}, err
        }
        return call.Expression(schema.StringType, "{} || {}", receiver, suffix)
    },
)
query, err := sql.Build(ctx, program,
    sql.WithDialect(sql.PostgreSQL),
    sql.WithMethods(translator),
)

sql.Implement pairs one immutable hff.MethodDeclaration with one dialect and a translation function. sql.WithMethods copies the supplied registration slice into builder configuration. Build requires an exact declaration match for each used extension signature and rejects missing, incompatible, duplicate, or wrong-dialect registrations. The dialect must be explicitly selected; PostgreSQL is currently the only supported dialect.

The translator receives *sql.Call, a typed receiver, and typed arguments. Expressions are immutable and scoped to their call. Helpers may be used concurrently during the translation, but a translator must not retain the Call after returning. The Call exposes its context and dialect, Parameter for dialect-validated bound scalar values, and Expression for building a typed expression from a trusted developer-authored template. Each {} in a template corresponds to one supplied expression. Templates cannot contain $N placeholders; literal values must be bound with Parameter.

HFF validates expression ownership and the declared scalar type. It does not parse templates or validate that their SQL implements the declared operation. Templates are therefore trusted code, not a place for query or record input. Translators must preserve the declaration's behavior on their supported domain and return an error when faithful translation is unavailable. Build converts translation failures into source-located backend errors. It performs no SQL execution or database I/O.

HFF retains method-family dispatch and wraps translated branches with core presence, null, and type-selection behavior. No-value never reaches the translator as an HFF value. Null behavior follows the declaration's policy and nullable types. SQL expression and bound parameter storage are owned by the resulting Query; Where and Args expose the predicate and an ordered, defensive parameter slice.

Execution guarantees

sql.ExecutionGuarantees(dialect) reports what a dialect can enforce for HFF's per-record MaxSteps and MaxGeneratedValues budgets. A built Query also exposes Guarantees(). sql.WithExecutionLimits adds explicit positive requests to Build configuration. A zero field asks for no guarantee; a negative value is a configuration error. If a requested positive limit is not enforceable, Build returns a backend error instead of ignoring the request.

PostgreSQL currently reports neither guarantee. The Program's local runtime budgets do not become SQL execution constraints. Database-level timeouts and cancellation are configured by the service through its driver and context. The SQL builder does not claim a specific evaluation order or local short-circuit work count.

Compatibility and backend support

PostgreSQL extension translators are limited to the existing typed scalar expression profile. They do not extend support to collections, JSON, temporal values, or other types rejected by PostgreSQL Build. Unsupported built-in or extension semantics fail Build with a source-located backend error; they do not fall back to local evaluation.

Adding a dialect requires a concrete semantics matrix and parameter-encoding coverage, including parity tests for null, type guards, numeric boundaries, string ordering/regex restrictions where applicable, and extension methods. The current contract does not promise a user-pluggable dialect registry.

Alternatives considered

Give translators the entire public IR

An IR visitor would expose every operation shape to each translator and make the backend responsible for dispatch and parameter ownership. Typed expressions and a scoped Call helper provide only the construction surface needed for a single method translation while core HFF retains language dispatch.

Allow raw SQL strings as translator results

Raw fragments cannot express parameter ownership or prevent mixing data with SQL syntax. Templates with typed expression placeholders plus a dedicated parameter helper keep the boundary explicit. This does not prove template semantics; templates must remain trusted developer code.

Document execution limits without a request API

Documentation alone cannot stop a caller from assuming a requested budget was enforced. The builder exposes enforceable guarantees and rejects explicit positive requests that it cannot honor. Database timeouts remain a separate caller-owned setting.

Testing

Tests must cover declaration and dialect matching, missing/duplicate registrations, overloads, expression ownership, placeholder counts, forbidden $N templates, parameter ordering and encoding, null/no-value and guard dispatch, source spans on translator errors and panics, cancellation, concurrent Build calls, and explicit execution-limit rejection. PostgreSQL differential tests must compare translated results against local HFF semantics over scalar boundaries and extension fixtures. The site example verifies construction only; tagged integration tests verify live PostgreSQL behavior.

Limitations and open questions

PostgreSQL is the only supported SQL dialect. Its extension translators may construct only supported scalar expressions and cannot ask Build to validate template semantics. PostgreSQL cannot enforce HFF MaxSteps or MaxGeneratedValues; services that need database runtime bounds must configure them through their database integration.

Explore HFF
Search documentation

Search the language specification and guides.