Skip to content

feat(typelayer)!: validate, coerce and filter through chtypes - #882

Draft
EricAndrechek wants to merge 1 commit into
mainfrom
typelayer-chtypes-v2
Draft

EricAndrechek wants to merge 1 commit into
mainfrom
typelayer-chtypes-v2

Conversation

@EricAndrechek

Copy link
Copy Markdown
Member

Summary

WaveHouse stops modelling ClickHouse itself. Ingest validation, coercion, DEFAULT evaluation, insert checks and row-level security now run through chtypes (github.com/wave-rf/chtypes/go/v2), a cgo dlopen of a per-ClickHouse-line library that runs the server's own parser and analyzer in-process. Ingest hands the request body to ClickHouse's own reader and refuses it the way an INSERT would; the row the worker inserts is the library's export of what it judged. /v1/query and pipes ask ClickHouse to render their results. The Go that approximated ClickHouse (the canonicalizer, row evaluator and compact encoder) is deleted.

This is one squashed commit on current main, so the diff below is exactly what changes. It supersedes #712, which carries the same work on chtypes v1 with its full review history and is left open until this one is accepted.

Status: draft. It builds on the chtypes v2 dev SDK 2.0.0-dev.11, which fetches only from chtypes' dev channel. It moves to chtypes 2.0.0 (the stable fetch contract: release key, mirrors, --lock/--frozen) when that ships, and the docs that describe the dev channel move with it.

What changed since #712

  • chtypes v2 interface. Per-tenant server handles carry each tenant's server time zone, so tenants in different zones share one process, DEFAULT timezone() is answered, and no table depends on which tenant bound first (typelayer: the process image zone depends on which tenant binds first #726). Zone spellings no longer need to agree across tenants on one line (docs(typelayer): tenants on one ClickHouse line must report the same zone spelling (UTC ≠ Etc/UTC) #717).
  • Role column policy as ClickHouse's own grant (typelayer: a role denied a generator-DEFAULT column has every record declined (chtypes v2) #816). A role's writable columns become the INSERT's column list and chtypes' insertable_columns; an _eq check's value fills an omitted field through column_defaults, spelled in the body format's own field encoding. A column the role may not write keeps its own DEFAULT, so id UUID DEFAULT generateUUIDv4() now draws a value instead of declining the batch. Per-role compiled schemas and their 256-entry cache are gone: every role's insert runs on the table's one compiled handle.
  • Nested columns from a table created with flatten_nested=0 compile as the one Nested(...) column the server stores, instead of being split into n.a / n.b.
  • SDK dev.11 changes absorbed: a row's unsupported setting now declines through its outcome, and a row the library did not export has a nil span.
  • Build and release: cgo is required; releases ship Linux amd64 and arm64 archives only (macOS arm64 builds from source); Go 1.27.3. scripts/fetch-chtypes.sh --resolve asks the SDK (chtypes resolve) for the build it would fetch, so the CI cache key names the channel actually used.
  • Transport errors on /v1/query and pipes no longer echo the ClickHouse request URL (the tenant database, the role's caps and the bound filter values); the caller sees only what failed.
  • Docs describe the dev SDK as it behaves: the cache setting is a parent directory (<dir>/v2-dev), the mirror and trust overrides have no effect until chtypes 2.0.0, a cached build makes one revocation-status request per process, and upgrading.md gains a section for the next release (move ClickHouse to a published line first; 26.8 is the one tested).
  • main up to docs(release): prepare the 0.2.1 changelog and upgrade guide #877 is included (query metrics, trusted proxies, storage metrics, the 0.2.1 changelog); query metrics are recorded on the HTTP reader the query paths now use.

Documented contract changes (please review these first)

The full list, with before/after behaviour, is in CHANGELOG.md under Unreleased. The ones a client notices:

  • A malformed record refuses the whole body with ClickHouse's own code and the record's position (400 clickhouse.rejected, e.g. code 27 or 117), where main answered per record.
  • A column the role may not insert is ClickHouse's 117 ("Unknown field"), not WaveHouse's 403.
  • Values are stored as ClickHouse parses them (its own DEFAULTs, NaN/Inf as "nan"/"inf"/"-inf" on every surface, its DateTime rendering).
  • An API process needs a chtypes artifact for its ClickHouse line (fetched on the first bind by default; WH_CHTYPES_AUTOFETCH, WH_CHTYPES_CACHE, WH_CHTYPES_ARTIFACTS_URL). An ingest-only or sweeper-only process needs none.
  • Pipes whose SQL asks for an output format other than JSONEachRow (a trailing FORMAT JSON, FORMAT CSV, …) now fail every call (502 clickhouse.misconfigured, not retryable); 0.2.1's native protocol ignored the clause. Remove it from the pipe.
  • Go API: pkg/pipes.BindParams returns (string, error), the statement with its values inlined and escaped, where it returned (string, []any, error); an outside module importing pkg/pipes needs a one-line change.
  • Supported ClickHouse lines are those chtypes publishes: 26.3, 26.7, 26.8 and 26.9. A tenant on any other line is unavailable (503 on ingest, withheld rows on row-filtered streams).

Verification

  • make ci passed on this exact tree on darwin-arm64 against chtypes dev build 20261021.130847 and ClickHouse 26.8.15.10: Go unit 4,000, integration 218 + 99 + 6 + 1 + 346, e2e 96, TS SDK 270, every coverage gate, and the checkout stayed clean. It also passed on linux-arm64 against the same build at a revision before the last review fixes (an error-mapping change, one pinned read setting, tests and docs).
  • Earlier revisions of this branch passed make ci on linux-arm64 against every chtypes v2 dev build since 20261012.142240, and its type-layer, API, stream, ingest and app suites pass on darwin-arm64.
  • New regression tests: a role denied a generator-DEFAULT column gets its records accepted with a drawn value (unit and against a real ClickHouse server), per-format column_defaults spelling (JSON, CSV, TSV, positional CSV) with quotes, commas, tabs, newlines and backslashes, an unreadable claim value failing the check closed in every format, and flatten_nested=0 tables.

Ingest benchmark, main vs this PR (measured)

A linux-arm64 machine (Neoverse V2 cores), ClickHouse 26.8.15.10, chtypes dev build 20261021.130847, concurrency 1 / 8 / 32, each point the median of 3 × 60 s after a warm-up with the build order rotated: 36 points, 0 errors, and rows stored equal rows accepted. The compared trees differ only by this PR. The bench table has a Map(String, String) column, the shape chtypes' batch-path speedup targets.

setup rows/s, this PR ÷ main (c = 1 / 8 / 32) WaveHouse CPU per row, this PR ÷ main
default durable queue (embedded NATS on disk, SyncAlways) 0.78× / 0.99× / 1.00× 1.26× / 1.34× / 1.33×
queue on tmpfs (fsync free, isolating the API stage) 0.65× / 0.85× / 0.86× 1.32× / 1.39× / 1.43×
  • On the durable setup both builds are fsync-bound at about 8.5–9k rows/s from concurrency 8; the extra CPU shows as throughput only at concurrency 1.
  • On tmpfs this PR costs about 38–40 µs more WaveHouse CPU per row than main (167.9 / 141.0 / 125.3 against 127.6 / 101.2 / 87.4 µs). feat(typelayer)!: validate, coerce and filter through chtypes #712's earlier run on dev build 20261012.142240 measured 54–61 µs more, so the gap shrank by about a third; most of the cost is inside the chtypes library call (profiled in feat(typelayer)!: validate, coerce and filter through chtypes #712's run; not re-profiled here).
  • RSS is about 90–120 MiB higher, and the process runs up to 29 OS threads against main's 15.

Known limitations

Each is tracked; none is a regression against main unless it says so.

  • Table CHECK constraints are not evaluated at ingest (typelayer: table CHECK constraints are not evaluated at ingest #727): the type layer compiles each table on a neutral MergeTree, so a violating record is accepted, can reach the stream, and is parked by the worker when the server refuses it, as on main. Role check clauses are unaffected.
  • A check or row filter over a clock or generator value the library computes gets no verdict (typelayer: a check or row filter over a clock or generator value the library computes never gets a verdict (chtypes v2) #867): an insert check on a DEFAULT now()/rand() column the role may not write fails every record with 422, and a row filter on a MATERIALIZED now() column withholds every row.
  • Server-filled defaults (generateSnowflakeID(), rowNumberInAllBlocks()): a body in which any record omits such a column is declined (422); chtypes' opt-in deferral is not used yet.
  • Per-record type errors wait for per-record parsing in chtypes; until then a malformed record refuses the whole body.
  • Per-row cost: ingest spends more CPU per row than main (see the benchmark).
  • Row-filtered streams past 4,096 claim sets on one table (perf(stream): row-filtered fan-out recompiles once a table holds over 4,096 claim sets #880): chtypes compiles a filter's claim values into it, so once a table's subscribers hold more distinct claim sets than its filter cache, most per-event evaluations compile again. This is a regression against main, whose Go evaluator compiled nothing; the cost is inferred, not yet measured.
  • ClickHouse 26.3 keeps chtypes' documented list of inputs that crash its library on that line; crash handling is chtypes'.

Follow-ups (not in this PR)

Epic #827 collects them, each blocked by #826, which this PR closes.

Closes #826 (the gate its follow-ups in #827 are blocked by).
Fixes #387, fixes #516, fixes #547, fixes #558, fixes #722 (as in #712), fixes #726, fixes #717, fixes #816.

🤖 Generated with Claude Code

Ingest validation, type coercion, DEFAULT evaluation and row-level-security
evaluation now run through chtypes (github.com/wave-rf/chtypes/go/v2), which
runs ClickHouse's own parser and evaluator in-process. WaveHouse's Go-side
canonicalizer, row evaluator and compact encoder are deleted.

- Ingest: each body is judged by ClickHouse's parser for the tenant's
  ClickHouse line, on a per-tenant server handle that carries the server's
  time zone. A malformed record refuses the body with ClickHouse's own code,
  and the exported row is exactly what the worker inserts.
- Column policy: a role's writable columns are the INSERT's column list and
  its column grant (insertable_columns), and an _eq check's value fills an
  omitted field through column_defaults. A column the role may not write
  keeps its own DEFAULT, so a generator DEFAULT draws a value (#816).
- Stream row filters and insert checks evaluate as chtypes filters.
- chtypes artifacts are fetched per ClickHouse minor line and platform into a
  cache on first use, under strict cache checks; CI caches them, and the
  images carry the chtypes CLI.
- cgo is required; releases ship Linux amd64 and arm64 only; Go 1.27.3.
- Built on the chtypes v2 dev SDK 2.0.0-dev.11; it moves to 2.0.0 when that
  is released.

BREAKING: see CHANGELOG.md, Unreleased.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@EricAndrechek

Copy link
Copy Markdown
Member Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Oct 22, 2026 •

Copy link
Copy Markdown

Important

Review skipped

Too many files!

This PR contains 191 files, which is 91 over the limit of 100.

To get a review, reduce the PR to 100 files or fewer by splitting it into smaller PRs or changing its base branch.

Upgrade to a paid plan to raise the limit.

This review couldn't start because sufficient usage credits or metered capacity aren't available. Add credits or update usage-based reviews in the billing tab, then retry.

⚙️ Run configuration
  • Configuration used: Organization UI
  • Review profile: ASSERTIVE
  • Plan: Advanced
  • Run ID: da019c72-26bf-4623-8e57-35400d58ceac
📥 Commits

Reviewing files that changed from the base of the PR and between abd2b46 and 21fad2d.

⛔ Files ignored due to path filters: 1
  • go.sum is excluded by !**/*.sum
📒 Files selected for processing: 191
  • .github/actionlint.yaml
  • .github/actions/setup-env/action.yml
  • .github/labeler.yml
  • .github/workflows/README.md
  • .github/workflows/ci.yml
  • .github/workflows/goreleaser-validate.yml
  • .github/workflows/publish-dev.yml
  • .github/workflows/release.yml
  • .goreleaser.yaml
  • .testcoverage.yml
  • AGENTS.md
  • CHANGELOG.md
  • CONTRIBUTING.md
  • Makefile
  • README.md
  • SECURITY.md
  • clients/ts/src/cli/codegen.test.ts
  • clients/ts/src/cli/codegen.ts
  • clients/ts/src/query-builder.test.ts
  • clients/ts/src/query-builder.ts
  • clients/ts/src/sql.ts
  • clients/ts/src/stream/live-query.test.ts
  • clients/ts/src/stream/live-query.ts
  • clients/ts/src/table.test.ts
  • clients/ts/src/table.ts
  • clients/ts/src/timestamp.test.ts
  • clients/ts/src/timestamp.ts
  • clients/ts/src/types.ts
  • config.yaml
  • deployments/Dockerfile
  • deployments/Dockerfile.goreleaser
  • deployments/compose/dependencies.yaml
  • deployments/compose/standalone.yaml
  • docs/src/components/LiveDemo.astro
  • docs/src/content/docs/access-control.mdx
  • docs/src/content/docs/api.md
  • docs/src/content/docs/architecture.md
  • docs/src/content/docs/claude-code.md
  • docs/src/content/docs/configuration.mdx
  • docs/src/content/docs/deployment.md
  • docs/src/content/docs/development.md
  • docs/src/content/docs/durability.md
  • docs/src/content/docs/getting-started.md
  • docs/src/content/docs/index.mdx
  • docs/src/content/docs/ingest-pipeline.md
  • docs/src/content/docs/pipes.mdx
  • docs/src/content/docs/reverse-proxy.mdx
  • docs/src/content/docs/sdk/queries.md
  • docs/src/content/docs/sdk/reference.md
  • docs/src/content/docs/sdk/streaming.md
  • docs/src/content/docs/settings-directory.mdx
  • docs/src/content/docs/upgrading.md
  • docs/src/content/docs/why-wavehouse.md
  • go.mod
  • internal/api/bufpool.go
  • internal/api/cache_key.go
  • internal/api/cache_key_test.go
  • internal/api/cache_tenant_test.go
  • internal/api/ch_errors.go
  • internal/api/ch_errors_test.go
  • internal/api/ch_settings.go
  • internal/api/ch_settings_test.go
  • internal/api/clickhouse_exec_test.go
  • internal/api/clickhouse_http.go
  • internal/api/clickhouse_http_test.go
  • internal/api/content_type.go
  • internal/api/errors.go
  • internal/api/ingest.go
  • internal/api/ingest_bodies_test.go
  • internal/api/ingest_formats_test.go
  • internal/api/ingest_framing.go
  • internal/api/ingest_framing_test.go
  • internal/api/ingest_retention_test.go
  • internal/api/ingest_role_test.go
  • internal/api/ingest_seams.go
  • internal/api/ingest_seams_test.go
  • internal/api/ingest_test.go
  • internal/api/ingest_unavailable_test.go
  • internal/api/ingest_window_test.go
  • internal/api/metrics_test.go
  • internal/api/pipes.go
  • internal/api/pipes_test.go
  • internal/api/query.go
  • internal/api/query_test.go
  • internal/api/router_test.go
  • internal/api/sql_classify.go
  • internal/api/sql_classify_test.go
  • internal/api/structured_query.go
  • internal/api/structured_query_integration_test.go
  • internal/api/structured_query_test.go
  • internal/api/tenant_clickhouse_test.go
  • internal/api/tenant_helpers_test.go
  • internal/app/app.go
  • internal/app/app_test.go
  • internal/app/dedupe_dynamodb_test.go
  • internal/app/discoveries.go
  • internal/app/mq_nats_test.go
  • internal/app/roles_test.go
  • internal/app/types.go
  • internal/app/types_test.go
  • internal/app/wire.go
  • internal/app/wire_storage_metrics_test.go
  • internal/auth/auth_test.go
  • internal/chconn/chconn.go
  • internal/chversion/chversion.go
  • internal/chversion/chversion_test.go
  • internal/config/config.go
  • internal/config/config_test.go
  • internal/config/defaults_test.go
  • internal/discovery/discovery.go
  • internal/discovery/discovery_test.go
  • internal/discovery/timestamp.go
  • internal/discovery/timestamp_test.go
  • internal/discovery/validation.go
  • internal/discovery/validation_test.go
  • internal/ingest/compact.go
  • internal/ingest/compact_test.go
  • internal/ingest/types.go
  • internal/ingest/worker.go
  • internal/ingest/worker_test.go
  • internal/mq/external.go
  • internal/query/bind.go
  • internal/query/builder.go
  • internal/query/builder_test.go
  • internal/stream/hub.go
  • internal/stream/hub_test.go
  • internal/stream/metrics.go
  • internal/stream/roweval.go
  • internal/stream/roweval_test.go
  • internal/testutil/testutil.go
  • internal/typelayer/bodies_test.go
  • internal/typelayer/checks_test.go
  • internal/typelayer/compile.go
  • internal/typelayer/ddl.go
  • internal/typelayer/errors.go
  • internal/typelayer/fetch_test.go
  • internal/typelayer/filter.go
  • internal/typelayer/filter_test.go
  • internal/typelayer/helpers_test.go
  • internal/typelayer/ingest.go
  • internal/typelayer/ingest_test.go
  • internal/typelayer/roletable.go
  • internal/typelayer/roletable_test.go
  • internal/typelayer/server.go
  • internal/typelayer/server_integration_test.go
  • internal/typelayer/server_test.go
  • internal/typelayer/tenancy_test.go
  • internal/typelayer/typelayer.go
  • internal/typelayer/typelayer_test.go
  • internal/typelayer/typelayertest/bodies.go
  • internal/typelayer/typelayertest/typelayertest.go
  • pkg/chsql/chsql.go
  • pkg/chsql/chsql_test.go
  • pkg/pipes/pipes.go
  • pkg/pipes/pipes_test.go
  • pkg/policy/canonical.go
  • pkg/policy/numeric.go
  • pkg/policy/policy.go
  • pkg/policy/policy_test.go
  • pkg/policy/rowfilter.go
  • pkg/policy/rowfilter_test.go
  • pkg/settings/settings.go
  • pkg/settings/validate.go
  • scripts/build.sh
  • scripts/ci/prefetch-test-images.sh
  • scripts/ci/prefetch-test-images.test.sh
  • scripts/fetch-chtypes.sh
  • scripts/orchestrator/main.go
  • scripts/size.sh
  • tests/e2e/sdk/batching.test.ts
  • tests/e2e/sdk/dlq.test.ts
  • tests/e2e/sdk/ingest.test.ts
  • tests/e2e/sdk/ndjson.test.ts
  • tests/e2e/sdk/query.test.ts
  • tests/e2e/sdk/streaming.test.ts
  • tests/integration/coord_nats_test.go
  • tests/integration/discovery_metadata_test.go
  • tests/integration/identifier_roundtrip_test.go
  • tests/integration/ingest_isolation_test.go
  • tests/integration/ingest_test.go
  • tests/integration/query_binding_test.go
  • tests/integration/query_errors_test.go
  • tests/integration/query_limits_test.go
  • tests/integration/query_types_test.go
  • tests/integration/rowfilter_narrowing_test.go
  • tests/integration/rowfilter_stream_test.go
  • tests/integration/setup_test.go
  • tests/integration/shared_cache_test.go
  • tests/integration/testdata/query_types_pin.json
  • tests/integration/timestamp_canonicalization_test.go
  • tests/integration/typelayer_wire_test.go

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai

coderabbitai Bot commented Oct 22, 2026 •

Copy link
Copy Markdown
⚠️ Action not completed

Review skipped: 191 files exceed the limit of 100.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@github-actions github-actions Bot added documentation Improvements or additions to documentation dependencies Pull requests that update a dependency file github_actions Pull requests that update GitHub Actions code go Pull requests that update go code area/observability Metrics, logs, traces, health, profiling area/api HTTP handlers, routing, middleware area/ingest Ingest pipeline (Bento, batching, DLQ) area/query Structured query AST, SQL builder area/policy Access control policies (Hasura-style) area/pipes Named query pipes area/sdk TypeScript SDK (clients/ts/) area/docs Documentation, site/, README area/infra CI, build, deploy, Docker, release area/app Process wiring (internal/app): component build, run, release labels Oct 22, 2026
@github-actions

Copy link
Copy Markdown

📚 Docs preview

✅ Live: https://pr-882-wavehouse-docs.wave-rf.workers.dev (follows this PR)
This commit (21fad2d): https://530469cf-wavehouse-docs.wave-rf.workers.dev (exactly this commit; open it if the link above looks stale)

  • Commit: 21fad2d feat(typelayer)!: validate, coerce and filter through chtypes
  • Author: @EricAndrechek, Claude Opus 5.5
  • Committed: 2026-10-21 23:21 EDT
  • Uploaded: 2026-10-21 23:32 EDT (run)

@github-code-quality

Copy link
Copy Markdown
Contributor

Code coverage overview

Languages: Go

Go

main abd2b46 typelayer-chtypes-v2 21fad2d +/-
93% 93% 0%
View coverage of the 10 most impacted files.
File main abd2b46 typelayer-chtypes-v2 21fad2d +/-
internal/typelayer/ingest.go 0% 78% +78%
internal/api/cl...ckhouse_http.go 0% 89% +89%
internal/typelayer/filter.go 0% 91% +91%
internal/api/sql_classify.go 0% 93% +93%
internal/api/content_type.go 0% 93% +93%
internal/typela...er/typelayer.go 0% 94% +94%
internal/query/bind.go 0% 95% +95%
internal/api/ingest_framing.go 0% 96% +96%
internal/typelayer/server.go 0% 96% +96%
internal/typela...er/roletable.go 0% 100% +100%

ℹ️ Calculations represent line coverage. Results may change as calculations are updated.

This branch was successfully deployed

1 active deployment
preview/docs — 21fad2d3 Deployed Oct 22, 2026 by github-actions[bot]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area/api HTTP handlers, routing, middleware area/app Process wiring (internal/app): component build, run, release area/docs Documentation, site/, README area/infra CI, build, deploy, Docker, release area/ingest Ingest pipeline (Bento, batching, DLQ) area/observability Metrics, logs, traces, health, profiling area/pipes Named query pipes area/policy Access control policies (Hasura-style) area/query Structured query AST, SQL builder area/sdk TypeScript SDK (clients/ts/) dependencies Pull requests that update a dependency file documentation Improvements or additions to documentation github_actions Pull requests that update GitHub Actions code go Pull requests that update go code

Projects

Status: Backlog

1 participant