Development Checks

Run the repository's local verification entry point from the repository root:

sh hack/check.sh

The script requires Go 1.27.1 and rejects Go files that gofmt -l reports as unformatted. It then runs all Go tests with the race detector and go vet, regenerates the PEGO parser and checks that the generated parser has no diff, regenerates the Design Records introduction and checks that the generated Markdown has no diff, runs every examples/*/main.go program, tests and vets the site module, builds the documentation site (including its local-link checks), and finishes with git diff --check. Parser generation pins PEGO to a revision; PEGO_BIN can be set to a local compatible PEGO executable.

To regenerate the complete Design Records introduction after changing a record, run this from the repository root:

sh hack/generate-design-index.sh

The generator reads each numbered record's level-one title and adjacent Status, Author, and Date metadata. It omits the 000-template.md; see site maintenance for the required Markdown form. Check only the generated artifact with:

git -c core.fsmonitor=false diff --exit-code -- docs/design/README.md

The GitHub Actions workflow in .github/workflows/check.yml runs the same script on pushes and pull requests. It uses the Go 1.27.1 toolchain and only read access to repository contents. A successful local run does not establish that the remote workflow has completed; check the workflow result for the commit under review.

To run the JSONB numeric-metadata example by itself, use:

go run ./examples/sql-json-metadata

Run the complete CI check scripts

The main job runs three scripts with Go 1.27.1 and Node.js 24.19.0:

sh hack/check.sh
sh hack/check-wasm.sh
sh hack/check-fuzz.sh

check.sh enforces Go formatting, runs native race tests and vet, verifies generated parser and Design Records index freshness, runs all examples, checks and builds the site, and checks the diff. The WebAssembly script runs package tests through Node's js/wasm executor. The fuzz script runs all eight bounded parser, compiler, runtime, SQL Build, and playground bridge fuzz targets for 15 seconds each with two workers by default. Set HFF_FUZZ_TIME or HFF_FUZZ_PARALLEL to adjust that run locally.

A separate PostgreSQL job uses the pinned PostgreSQL 18.6 image initialized with UTF-8 encoding and C locale. It runs race-enabled tagged SQL integration tests and tagged vet:

go test -race -tags=integration ./backends/sql
go vet -tags=integration ./backends/sql

The workflow file documents the jobs; it does not prove a hosted run succeeded. Check GitHub Actions for the commit under review, and distinguish local checks from remote results.

Test Go packages on WebAssembly

Run the existing Go package tests for js/wasm through Node.js with:

sh hack/check-wasm.sh

The script requires Go 1.27.1 and Node.js. If node is not on PATH, set HFF_WASM_NODE to the absolute path of the Node.js executable. Arguments after the script replace the default ./... package pattern; for example:

sh hack/check-wasm.sh -count=1 ./engine ./backends/local

This runs Go tests using Node's WebAssembly executor and verifies package compatibility with js/wasm. It does not exercise the private playground bridge or establish a distribution contract or browser UI. See the private bridge guide for its separate native/WASM parity smoke test.

Test the internal Go-value conversion core

The internal converter tests exercise exact supported Go representations, explicit struct-field mapping, nil versus empty values, schema validation, cycles versus shared references, wrapper-aware input depth/value limits, cancellation without partial values, and concurrent conversion. These checks cover internal/govalue; they do not establish a public adapter API.

go test -race ./internal/govalue
go vet ./internal/govalue
sh hack/check-wasm.sh -count=1 ./internal/govalue

Run focused benchmarks

To reproduce the local validation, reusable-match, and compilation benchmark samples, run:

go test -run '^$' -bench . -benchmem ./engine ./backends/local

These measurements depend on the Go version, hardware, schema, and query shape. Treat short benchmark runs as comparisons for the measured cases, not as performance guarantees or evidence for changing default limits.

The parser benchmark covers empty and representative service queries, type guards, nested callbacks, Unicode, conjunctions of 10/100/1,000 terms, and parenthesis depths 4/16. It measures ParseQuery, including source preflight and tree checks, but excludes schema inference, capability checks, and backend construction.

go test ./internal/syntax -run '^$' -bench '^BenchmarkParseQuery$' -benchmem

The compile-step boundary test separately checks that a shallow query over a large schema completes or returns a span-bearing limit error according to MaxCompileSteps, with no partial program. It is a correctness check rather than a benchmark; see the finite-limit review.

go test -race ./engine -run '^TestDefaultCompileBudgetForRepeatedStructuralChecks$'

Measure Validate and Match around the default MaxInputValues ceiling with a flat list that the predicate does not inspect:

go test -benchtime=100ms -count=3 -run '^$' -bench '^BenchmarkDefaultInputValueCeiling$' -benchmem ./engine

The finite-limit review records the input shape, exact value counts, measured sample, and scope limits.

Measure successful and rejected local MaxGeneratedValues work with:

go test -benchtime=100ms -count=3 -run '^$' -bench '^BenchmarkDefaultGeneratedValueCeiling$' -benchmem ./backends/local

The finite-limit review describes the flatmap workload and its logical-value accounting. B/op is cumulative allocation, not peak memory.

To measure a deliberately repeated structural-check workload close to the default compile-work ceiling, run:

go test -benchtime=3x -count=3 -run '^$' -bench '^BenchmarkDefaultCompileWorkCeiling$' -benchmem ./engine

The finite-limit review records the schema shape, calibrated step boundary, sample costs, and remaining evidence gaps. This is one controlled workload, not a service performance guarantee.

The collection benchmark in collections_bench_test.go measures MatchInput on one-, 100-, and 1,000-element inputs for any first, last, and miss cases, all, map, fold, sort, and nested search. Input validation, compilation, and building are outside the measured loop. At 1,000 elements, nested search verifies that the finite default MaxSteps budget returns a limit error instead of disabling the limit.

Run this benchmark alone with allocation reporting:

go test ./backends/local -run '^$' -bench '^BenchmarkCollections$' -benchmem

The record-matching benchmark compares Match with MatchInput for scalar, service-regex, any first/last, and all queries over records with one, 100, and 1,000 struct items. Compilation, filter construction, and input validation are outside the timed loops. Match includes per-call validation of the entire record; MatchInput reuses a previously validated immutable input. The benchmark measures local evaluation and allocations, not a universal speedup.

go test ./backends/local -run '^$' -bench '^BenchmarkRecordMatching$' -benchmem

The finite-limit benchmark measures successful and rejected query-byte and input-value boundaries, plus completed and aborted local step-budget and generated-value workloads. Compilation, record construction, filter building, and input validation for the runtime cases are outside the measured loops. It checks typed limit errors and does not treat operation counts as wall-clock deadlines. See the finite-limit review for its measured scope and remaining gaps.

go test -count=5 -benchtime=100ms -run '^$' -bench '^BenchmarkDefaultLimitWorkloads$' -benchmem ./engine

To compare padded and dense sources at the query-byte ceiling, run:

go test -benchtime=100ms -count=3 -run '^$' -bench '^BenchmarkDefaultQueryByteCeiling$' -benchmem ./engine

The finite-limit review describes the 65,536-byte padded and dense queries, the one-byte-over rejection, and why generated-parser allocation samples vary between runs. This measures one schema and query pair, not a general query memory budget.

The depth-limit benchmark measures successful parsing/compilation, input validation, and JSON operations at selected default depth ceilings. It raises the syntax-depth limit in the callback case to isolate callback depth. Exact boundary rejection is covered separately by TestDefaultDepthBoundaries.

go test -count=5 -benchtime=100ms -run '^$' -bench '^BenchmarkDefaultDepthWorkloads$' -benchmem ./engine

The PostgreSQL structured-build benchmark measures pure translation for composite, JSON, and JSONB bindings with scalar, any(), and foldl() queries. It reports Build time and allocations together with generated SQL bytes and parameter count; it excludes database planning and execution. The short local run is a baseline for these cases, not a performance guarantee or a basis for changing default limits. Compare results with the same Go version, hardware, and query shapes.

go test ./backends/sql -run '^$' -bench '^BenchmarkPostgreSQLStructuredBuild$' -benchmem

Fuzz local matching

The local matcher fuzz target compiles a query, builds a filter, and generates records. It compares Match with Validate followed by MatchInput: completed results must agree, and when either path aborts, both paths must return an HFF error with the same error code. The retained seeds include base64 and temporal methods, Unicode regular expressions, and nested callbacks. Generated host values are bounded to 4 KiB and include additional boolean, floating-point, byte, duration, timestamp, and string-key lookup cases.

Run the target for 30 seconds with two workers:

go test ./backends/local -run '^$' -fuzz '^FuzzLocalMatch$' -fuzztime=30s -parallel=2

The local regex cancellation regression checks context after the standard regular-expression compiler returns, before invalid-pattern null propagation or matching, and after matching before inversion/result handling. It verifies typed cancellation and cause preservation for valid and invalid patterns, including !~; the standard-library calls themselves remain non-preemptible. Step accounting is unchanged.

go test -race ./backends/local -run '^TestRegexCancellationAtLibraryBoundaries$'

Fuzz input UTF-8 validation

This target compares HFF's chunked UTF-8 validation with Go's standard UTF-8 validator across valid, malformed, and chunk-boundary inputs. Separate race tests cancel during a long scan of a host string or map key and verify that no partial input is returned and the cancellation cause is preserved. The local JSON decoder uses the same chunked validator before token scanning, allowing cancellation before a late invalid UTF-8 suffix without double-charging input bytes; the standard-library JSON string decoder itself remains uninterruptible.

go test ./internal/core -run '^$' -fuzz '^FuzzInputUTF8$' -fuzztime=30s -parallel=2

Check cancellation during JSON input UTF-8 admission with:

go test -race ./backends/local -run '^TestJSONCancellationBeforeLateInvalidUTF8$'

Query UTF-8 admission uses the same chunked validator. Its invalid-input location rescan remains cancellation-aware and preserves source byte spans; the generated ParseAST call is still an uninterruptible boundary.

go test -race ./internal/syntax -run '^TestQueryUTF8AdmissionCancellation$'

The preflight cancellation regression uses long escaped quoted strings and quoted names to ensure skipped escape bytes cannot skip a cancellation checkpoint. This covers HFF's preflight loop only; it does not make generated ParseAST interruptible.

go test -race ./internal/syntax -run '^TestPreflightCancellationAcrossEscapes$'

The map-key ordering target compares HFF's context-aware merge order with Go's byte-lexical sort.Strings result, including long common prefixes and malformed UTF-8 byte sequences.

go test ./internal/core -run '^$' -fuzz '^FuzzInputKeyOrder$' -fuzztime=30s -parallel=2

Run shared semantic conformance cases

The shared fixture is provided by Cases and companion CompositeCases. It currently contains 59 base cases and six typed-composite cases. The parser, compiler, and local evaluator use the same sources through shared_conformance_test.go, shared_conformance_test.go, and shared_conformance_test.go. These tests assert observable parse/compile errors and match results rather than internal IR structure.

The current shared-fixture coverage maps to the contract this way:

Contract area Representative fixture cases Verification
Numeric behavior exact_mixed_number_comparison, fractional_integer_division, overflow_is_null Shared parser/compiler/local suites; TestPostgreSQLScalarParity covers the wider PostgreSQL numeric matrix.
Null, no-value, and missing values null_inequality_is_false, two_null_values_are_not_equal, type_guard_failure_is_not_null, guard_failure_is_not_null, nullable_guards_keep_presence, nonnull_guards_preserve_zero, nonnull_null_test_is_false, structural_equality_ignores_key_order, typed_composite_null_identity Shared parser/compiler/local suites; typed-composite SQL parity below.
Unicode and time conversions unicode_code_points, duration_compound_fraction, timestamp_utc_components, epoch_is_truthy, zero_duration_is_falsy Shared parser/compiler/local suites; named-zone profile behavior remains in local temporal tests.
Predicates, callbacks, collections empty_filter, any_and_all_empty_lists, callback_predicate_guard, nested_callback_captures, fold_argument_order, contains_requires_all_arguments, unique_is_a_predicate, map_preserves_null_and_order, flatmap_preserves_order, sort_mixed_numbers_and_reject_null, concat_preserves_order Shared parser/compiler/local suites.
Object access and enumeration object_lookup_distinguishes_missing, object_dynamic_key_lookup, object_enumeration_key_order Shared parser/compiler/local suites.
Short-circuit and budgets decisive_or_skips_budgeted_callback, decisive_and_skips_budgeted_callback Shared compiler/local suites; confirms the evaluated branch hits LimitError when reached and no partial match is returned.
Capability and input diagnostics disabled_structural_equality, disabled_callback_nesting, type_guard_limits_method_permissions, invalid_host_input_is_rejected Shared compile/local suites and SQL Build classification.

The shared cases now exercise contains, unique, any, all, map, flatmap, foldl, foldr, sort, concat, and object lookup/enumeration, along with decisive AND/OR budget short-circuiting. SQL explicitly classifies unsupported Any-based shapes instead of narrowing their schemas; the SQL execution-parity counts below remain limited to the supported cases.

Run all three suites with the race detector:

go test -race ./internal/syntax ./engine ./backends/local -run '^TestShared'

The parser and compiler fuzz targets also seed from every shared conformance source. Adding a case to the fixture therefore adds it to both fuzz corpora. Fuzzing checks parser/compiler robustness, typed diagnostics, source-bounded spans, and valid compiled programs; it does not decide each case's expected semantics. The shared conformance tests above remain the source of truth for those expected outcomes. The compiler target uses the fixture's schema, including Any, union, and collection fields.

Run either target for 30 seconds with two workers:

go test ./internal/syntax -run '^$' -fuzz '^FuzzParseQuery$' -fuzztime=30s -parallel=2
go test ./engine -run '^$' -fuzz '^FuzzCompile$' -fuzztime=30s -parallel=2

The shared fixture covers the parser/compiler/local contract. SQL tests classify every shared case by its expected compile outcome and PostgreSQL Build support; adding an unclassified case fails the tests. Execution parity currently covers ten supported scalar cases across 69 query/record pairs in each of the native, JSON, and JSONB binding forms (207 comparisons total), checking fixture expectations against both local matches and PostgreSQL results. The two shared regex cases produce typed unsupported-backend errors for all three forms. The remaining classifications do not claim PostgreSQL execution parity, and broader SQL semantics, extension evaluators, and a comprehensive runtime budget matrix remain open.

Run the SQL adapter against PostgreSQL:

HFF_POSTGRES_CONTAINER=<container-name-or-id> \
go test -tags=integration ./backends/sql -run '^TestPostgreSQLSharedScalarConformance$'

The shared fixture also exercises six typed-composite cases across native, JSON, and JSONB storage: null-composite identity versus a present row with null attributes; guarded access to a null child and an empty name; negative array indexing with a nondefault PostgreSQL lower bound; a repeated identical nullable-array type guard; order-sensitive structural equality for a typed array; and order-insensitive contains() for the same typed array. The last two cases distinguish [null, 2] from [2, null] for equality while checking membership independently. Across 27 fixture records and three storage forms, the integration test checks 81 PostgreSQL comparisons against shared expectations and local evaluation. These cases cover the declared typed mappings in the fixtures; they do not establish general support for dynamic Any or ambiguous numeric unions in structured SQL bindings.

Run this focused PostgreSQL test with:

HFF_POSTGRES_CONTAINER=<container-name-or-id> \
go test -race -tags=integration ./backends/sql -run '^TestPostgreSQLSharedCompositeConformance$' -v

JSON parsing and serialization have focused package tests and a round-trip fuzz target.

Fuzz and benchmark JSON conversion

The strict JSON codec fuzz target checks round trips and malformed-input handling, and compares recursive HFF JSON representation in addition to semantic equality. This catches changes to numeric type and float bits that HFF equality intentionally treats as equal, including nested arrays and objects. Regression seeds include integral floats and exponents, signed zero, subnormals, underflow, and the int64 maximum. A focused oracle test confirms that semantically equal values such as integer 1 versus float 1.0 and positive versus negative zero are rejected when their JSON representation changes. This is targeted codec coverage, not a claim that every JSON representation is exhaustively tested.

The benchmarks measure codec work at several object sizes; they do not establish a performance guarantee or justify changing default limits.

go test ./backends/local -run '^$' -fuzz '^FuzzJSONRoundTrip$' -fuzztime=10s -parallel=2
go test ./backends/local -run '^$' -bench JSON -benchmem

Test PostgreSQL integration

The SQL builder fuzz target repeatedly builds the same typed plan, including native composite and JSON/JSONB bindings. It checks deterministic SQL and arguments, typed Build errors with source spans, and absence of panics. It does not execute SQL or establish semantic parity; the tagged PostgreSQL integration suite below compares SQL results with local HFF evaluation.

Run the builder target for 30 seconds with two workers:

go test ./backends/sql -run '^$' -fuzz '^FuzzPostgreSQLBuild$' -fuzztime=30s -parallel=2

The SQL backend's tagged integration test uses a running PostgreSQL container through docker exec and psql. It compares supported SQL predicate results with local evaluation for the same compiled inputs. Provide a container name or ID explicitly; the test fails if it is absent rather than silently skipping:

HFF_POSTGRES_CONTAINER=<container-name-or-id> go test -tags=integration ./backends/sql

The ordinary SQL tests need no database. The integration suite currently covers the implemented PostgreSQL scalar subset; unsupported operations are checked as Build errors in package tests.

To measure server-side planning and execution for the controlled JSON/JSONB scan workload, run the dedicated benchmark with a PostgreSQL container:

HFF_POSTGRES_CONTAINER=<container-name-or-id> \
go test -tags=integration ./backends/sql -run '^$' \
  -bench '^BenchmarkPostgreSQLJSONExecution$' -benchtime=1x -count=3

Set HFF_POSTGRES_PLAN_DIR to save each case's first plan and SQL sample. The fixture setup and Docker/psql transport are excluded from the reported PostgreSQL EXPLAIN times. See the PostgreSQL execution baseline for workload details and the measured sample; it is not an index or service performance guarantee.

Benchmark PostgreSQL JSONB numeric-metadata restoration

The metadata benchmarks compare the same typed fixtures in native JSON and JSONB with a version-1 type-metadata column. Four query shapes cover one scalar access, repeated access, guards, and a callback. The Build benchmark reports translation time and allocations along with generated SQL bytes and parameter count; it does not contact PostgreSQL:

go test ./backends/sql -run '^$' \
  -bench '^BenchmarkPostgreSQLMetadataBuild$' \
  -benchtime=200ms -count=1 -cpu=1 -benchmem

The tagged execution benchmark checks each SQL result against local matching before measuring its EXPLAIN ANALYZE plan, so the reported server timings are for a warmed scan. It reports PostgreSQL planning time, execution time, plan nodes, input and matched rows, and SQL bytes separately. Fixture loading, local evaluation, Docker/psql transport, and result validation are outside the reported server timings. The controlled fixture uses temporary tables with JIT and parallel workers disabled and a custom plan; it has no indexes. This profile is useful for comparing these scans, not for predicting indexed or production workloads.

Run the two storage modes as alternating paired samples. Give every invocation a distinct plan directory because cases with the same name overwrite their artifacts:

HFF_POSTGRES_CONTAINER=<container-name-or-id> \
HFF_POSTGRES_PLAN_DIR=/private/tmp/hff-metadata-native-a \
go test -tags=integration ./backends/sql -run '^$' \
  -bench '^BenchmarkPostgreSQLMetadataExecution/NativeJSON$' \
  -benchtime=1x -count=1 -cpu=1

HFF_POSTGRES_CONTAINER=<container-name-or-id> \
HFF_POSTGRES_PLAN_DIR=/private/tmp/hff-metadata-jsonb-a \
go test -tags=integration ./backends/sql -run '^$' \
  -bench '^BenchmarkPostgreSQLMetadataExecution/JSONBMetadata$' \
  -benchtime=1x -count=1 -cpu=1

Each invocation includes 100- and 1,000-row fixtures and all four query shapes. For a paired run, reverse the mode order and use fresh plan directories. The benchmark checks matching IDs and actual plan row counts before reporting; it does not establish a general performance gain. Keep raw plans, IDs, and SQL samples outside Git unless they are deliberately versioned.

Explore HFF
Search documentation

Search the language specification and guides.