Skip to content

feat(kit): effect-orm pull — definitions from an existing ClickHouse database - #35

Merged
Makisuo merged 3 commits into
mainfrom
feat/pull
Oct 15, 2026
Merged

Makisuo merged 3 commits into
mainfrom
feat/pull

Conversation

@Makisuo

@Makisuo Makisuo commented Oct 15, 2026 •

Copy link
Copy Markdown
Contributor

Why

Adopting effect-orm on a database that already has tables meant hand-writing every CH.table before the first typed query. pull writes them from the live database.

effect-orm pull                  # writes the config's schema module; --out, --tables a,b, --force
effect-orm generate --baseline
effect-orm migrate               # runs no statements, records the baseline as applied

What

  • src/migrate/introspect.ts reads system.tables, system.columns and system.data_skipping_indices into entities, written the way a table definition produces them. Keys come from engine_full as declared. A Replicated*/Shared* engine becomes its plain family, because replication is a render option.
  • src/kit/codegen.ts writes one CH.table per table: engine and arguments, keys, partition key, TTL, settings, comments, DEFAULT/MATERIALIZED/ALIAS, codecs and skipping indexes. Expressions are kept as the server's text.
    • Types use the built-in that writes the same SQL. Otherwise they become CH.custom with a fitting schema (UUID, Date, Decimal, Enum8 → its labels, …), or CH.untyped as a last resort.
    • Views and non-MergeTree tables are written as external: true.
    • Materialized views, SAMPLE BY and EPHEMERAL columns are listed in the file header for review.
  • CLI: effect-orm pull. pull is also exported from /kit.
  • verify fix: sorting and primary keys are now read from engine_full. It compared sorting_key, which has a VersionedCollapsingMergeTree's version column appended, so those tables always showed drift.

Postgres is not covered yet.

Testing

  • Offline round trip: a catalog captured from ClickHouse 26.2 is generated into src/kit/__fixtures__/pulled-clickhouse.ts, which is committed so tsc checks generated code. entitiesOf on that module must equal the input entities.
  • Parser and type-mapping unit tests, plus the CLI end to end with a fake driver.
  • Live (tests/pull.clickhouse.test.ts): plain DDL → pull → generate --baseline → migrate. Then generate finds nothing to do, and verify reports only the view and materialized view as unexpected.
  • typecheck, bun run test (728 passed), and the live suite against ClickHouse 26.2 (303 passed) all pass.

🤖 Generated with Claude Code


View with [code]smith Autofix with [code]smith
Need help on this PR? Tag @codesmith-bot with what you need.

Summary by CodeRabbit

  • New Features

    • Added effect-orm pull for ClickHouse databases. It generates schema definitions from existing tables, supports selecting tables and choosing an output file, and protects existing files from being overwritten unless requested.
    • Generated schemas include supported table and column details, with review notes for items that need attention. Views and certain unsupported tables are identified as external.
    • Added support for adopting a pulled schema as a migration baseline.
  • Bug Fixes

    • Improved ClickHouse verification of declared sorting and primary keys, reducing incorrect schema drift reports.

…atabase

Adopting effect-orm on a database that already has tables meant writing
every CH.table by hand before the first typed query. `pull` reads the
database the config's driver connects to and writes them.

- migrate/introspect.ts reads system.tables, system.columns and
  system.data_skipping_indices into entities, written the way a table
  definition produces them. Keys come from engine_full as declared; a
  Replicated or Shared engine becomes its plain family.
- kit/codegen.ts writes one CH.table per table. Column types map to the
  built-in that writes the same SQL, else CH.custom with a fitting schema,
  else CH.untyped. Views and non-MergeTree tables are external; materialized
  views, SAMPLE BY and EPHEMERAL columns are listed for review.
- `effect-orm pull [--out] [--tables] [--force]`, and `pull` from /kit.
- verify reads sorting and primary keys from engine_full. sorting_key has a
  VersionedCollapsingMergeTree's version appended, so such a table always
  showed drift.

Tested offline by generating from a catalog captured from ClickHouse 26.2
and checking entitiesOf on the committed output equals the input, and live:
plain DDL, pull, generate --baseline, migrate, then generate finds nothing
and verify reports only the views.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@maple-review-bot

maple-review-bot Bot commented Oct 15, 2026 •

Copy link
Copy Markdown

Maple review

🟡 Confidence 6/10 · needs attention
Every catalog field was checked against buildTable's requirements; the index-name gap is the one input the generated module cannot load and no test exercises.
quality 90/100 · 1 warning · tests partial · risk medium · 0/1 new units observable

pull writes ClickHouse definitions from a live database: new introspect and codegen modules, an effect-orm pull command, and a verify fix that reads keys from engine_full instead of sorting_key. The mapping matches the entity model except for index names, which can produce a module the next command cannot load.

  • introspect reads system.tables/columns/data_skipping_indices into entities and external tables
  • renderClickHouseSchema writes one CH.table per table, mapping types to built-ins, CH.custom or CH.untyped
  • CLI effect-orm pull writes the config's schema module, with --out, --tables, --force
  • verify takes sorting and primary keys from engine_full via declaredKeys

Findings

🟠 Warning · F1 · An index name that is not a plain identifier makes the pulled module fail generate --baseline

correctness · src/kit/codegen.ts:227

introspect routes a non-plain table or column name to external: true (introspect.ts:248), but it never checks index names, and buildTable rejects one: checkIdentifier(problems, ${name} index, spec.name) at define.ts:361. Pulling a table with INDEX `idx-a` ... therefore writes a modeled table whose module fails entitiesOf, and the effect-orm generate --baseline the CLI tells the user to run next dies with t index: "idx-a" is not a plain identifier ([A-Za-z_][A-Za-z0-9_]*), which no edit of the generated file can fix while keeping the index.

In `tableCode`, skip an index whose name fails `IDENTIFIER` from `../schema/problems` and push a line to `out.todos` (they are printed in the header and returned in `PullResult.review`), the way `SAMPLE BY` and `EPHEMERAL` are reported; or add the index names to `unplain` in introspect.ts:248 so the table is written `external: true`.
🤖 Prompt to fix this finding with an AI agent
Findings from an automated review of commit 6792323cfe9d2fe96ec2e16972ce0d997a9977a7. Verify each one against the current code before changing anything, fix only those that still apply, and keep each fix to the lines it names.

---

F1 · Warning · correctness · src/kit/codegen.ts:227
An index name that is not a plain identifier makes the pulled module fail `generate --baseline`
`introspect` routes a non-plain table or column name to `external: true` (introspect.ts:248), but it never checks index names, and `buildTable` rejects one: `checkIdentifier(problems, `${name} index`, spec.name)` at define.ts:361. Pulling a table with ``INDEX `idx-a` ...`` therefore writes a *modeled* table whose module fails `entitiesOf`, and the `effect-orm generate --baseline` the CLI tells the user to run next dies with `t index: "idx-a" is not a plain identifier ([A-Za-z_][A-Za-z0-9_]*)`, which no edit of the generated file can fix while keeping the index.
Suggested fix: In `tableCode`, skip an index whose name fails `IDENTIFIER` from `../schema/problems` and push a line to `out.todos` (they are printed in the header and returned in `PullResult.review`), the way `SAMPLE BY` and `EPHEMERAL` are reported; or add the index names to `unplain` in introspect.ts:248 so the table is written `external: true`.
What was checked
  • declaredKeys fixes the VersionedCollapsingMergeTree drift: coll has sorting_key K, Ver but engine_full ORDER BY K (fixture catalog lines 18-21)
  • External tables bypass buildTable, so plain (column my-col, catalog line 339) needs no plain identifiers (define.ts:303)
  • readClickHouseCatalog keeps verify's original WHERE clauses and columns, so the shared query changes no result
Observability coverage: 0 of 1 changes observable
Change Kind Observable Evidence
effect-orm pull CLI command cli no No tracer, OTel dependency or span helper anywhere in src, as in the existing generate/migrate/status commands; nothing for this diff to join.

6792323 · Updated on every push. Reply "won't fix" to dismiss a finding, or mention @maple-review-bot to ask about one.

@coderabbitai

coderabbitai Bot commented Oct 15, 2026 •

Copy link
Copy Markdown

Review in Change Stack →

Warning

Review limit reached

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

Next included review available in 34 minutes.

Check out review usage here.

View limit details

Limit details: You’ve used the included review currently available.

Learn how review limits work.

Review configuration:

⚙️ Run configuration
  • Configuration used: defaults
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: fbc797d2-ebd2-44c1-b406-717362572577
📥 Commits

Reviewing files that changed from the base of the PR and between 6792323 and 355317d.

📒 Files selected for processing: 10
  • docs/migrations.md
  • src/kit/__fixtures__/pulled-clickhouse.ts
  • src/kit/cli.ts
  • src/kit/codegen.ts
  • src/kit/generate.ts
  • src/kit/pull.test.ts
  • src/kit/pull.ts
  • src/migrate/introspect.ts
  • src/migrate/verify.ts
  • tests/pull.clickhouse.test.ts

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration
  • Configuration used: defaults
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: 0bb4a831-53a1-4ff3-9a04-3385737c92d2
📥 Commits

Reviewing files that changed from the base of the PR and between 0c1f47c and 6792323.

📒 Files selected for processing: 15
  • .changeset/pull-clickhouse.md
  • .gitignore
  • README.md
  • docs/migrations.md
  • docs/reference.md
  • src/kit.ts
  • src/kit/__fixtures__/clickhouse-catalog.json
  • src/kit/__fixtures__/pulled-clickhouse.ts
  • src/kit/cli.ts
  • src/kit/codegen.ts
  • src/kit/pull.test.ts
  • src/kit/pull.ts
  • src/migrate/introspect.ts
  • src/migrate/verify.ts
  • tests/pull.clickhouse.test.ts

Included review availability: This review used your included allowance. Your plan provides up to 1 included review per hour; 0 remain after this review.

📝 Walkthrough

This change adds ClickHouse catalog introspection and schema generation through effect-orm pull. It also updates ClickHouse verification to compare declared sorting and primary keys rather than relying only on catalog key fields.

Changes: ClickHouse schema import

Layer / File(s) Summary
Catalog introspection and schema conversion
src/migrate/introspect.ts, src/kit/__fixtures__/clickhouse-catalog.json, src/kit/pull.test.ts
Reads ClickHouse catalog metadata and converts supported tables, columns, and indexes into schema entities. Records external tables and notes for materialized views, omitted columns, and sampling keys.
Declared-key verification
src/migrate/verify.ts
Reads catalog data through the shared reader and derives declared keys from engine clauses for canonicalization and drift comparisons.
ClickHouse schema code generation
src/kit/codegen.ts, src/kit/__fixtures__/pulled-clickhouse.ts, src/kit/pull.test.ts
Generates CH definitions for supported types and table metadata. Uses untyped definitions when types are unsupported or mapped SQL would differ, and records review notes.
Pull command, exports, and adoption flow
src/kit/pull.ts, src/kit/cli.ts, src/kit.ts, src/kit/pull.test.ts, tests/pull.clickhouse.test.ts, README.md, docs/migrations.md, docs/reference.md, .changeset/pull-clickhouse.md, .gitignore
Adds the exported pull API and CLI command, including output selection, table filtering, and overwrite handling. Tests and documentation cover generated output, baseline adoption, and verification of database-owned views.

Priority: ➖ Normal

Estimated code review effort: 4 (Complex) | ~45 minutes

Change: Feature

Sequence Diagram(s)

sequenceDiagram
  participant CLI as kit CLI
  participant Pull as pull
  participant Introspect as introspectClickHouse
  participant Catalog as readClickHouseCatalog
  participant Codegen as renderClickHouseSchema
  participant Filesystem
  CLI->>Pull: pass config, output path, and options
  Pull->>Introspect: request ClickHouse schema
  Introspect->>Catalog: read database catalog
  Catalog-->>Introspect: return tables, columns, and indexes
  Introspect-->>Pull: return introspected schema
  Pull->>Codegen: render schema module
  Codegen-->>Pull: return source and review notes
  Pull->>Filesystem: create directory and write source
  Pull-->>CLI: return written path, tables, and notes
Loading
Merge Risk: ⚪ Minimal · up to 67923

No actionable issue was established that should delay merging.

Pre-merge checks: Passed 5
Check name Status Explanation
Description Check Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check Passed The title clearly and concisely identifies the main change: adding effect-orm pull to generate definitions from an existing ClickHouse database.
Docstring Coverage Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check. Docstring coverage is scoped to functions touched by this diff. Analyzed 0 functions across 9…
Linked Issues check Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Commit to this branch
  • Create a new PR
🧪 Generate unit tests (beta)
  • Commit to this branch
  • Create a new PR
  • 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.

@maple-review-bot maple-review-bot Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

1 inline note from Maple's review. The score and summary are in the review comment above.

Comment thread src/kit/codegen.ts
if (indexes.length > 0) {
l.push("\tindexes: [")
for (const index of indexes) {
l.push(`\t\tCH.index(${str(index.name)}, ${str(index.expr)}, ${str(index.type)}, ${index.granularity}),`)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Warning

An index name that is not a plain identifier makes the pulled module fail generate --baseline

F1 · Warning · correctness

introspect routes a non-plain table or column name to external: true (introspect.ts:248), but it never checks index names, and buildTable rejects one: checkIdentifier(problems, ${name} index, spec.name) at define.ts:361. Pulling a table with INDEX `idx-a` ... therefore writes a modeled table whose module fails entitiesOf, and the effect-orm generate --baseline the CLI tells the user to run next dies with t index: "idx-a" is not a plain identifier ([A-Za-z_][A-Za-z0-9_]*), which no edit of the generated file can fix while keeping the index.

In `tableCode`, skip an index whose name fails `IDENTIFIER` from `../schema/problems` and push a line to `out.todos` (they are printed in the header and returned in `PullResult.review`), the way `SAMPLE BY` and `EPHEMERAL` are reported; or add the index names to `unplain` in introspect.ts:248 so the table is written `external: true`.
🤖 Prompt to fix with an AI agent
In `src/kit/codegen.ts:227`: An index name that is not a plain identifier makes the pulled module fail `generate --baseline`.

`introspect` routes a non-plain table or column name to `external: true` (introspect.ts:248), but it never checks index names, and `buildTable` rejects one: `checkIdentifier(problems, `${name} index`, spec.name)` at define.ts:361. Pulling a table with ``INDEX `idx-a` ...`` therefore writes a *modeled* table whose module fails `entitiesOf`, and the `effect-orm generate --baseline` the CLI tells the user to run next dies with `t index: "idx-a" is not a plain identifier ([A-Za-z_][A-Za-z0-9_]*)`, which no edit of the generated file can fix while keeping the index.

Suggested fix: In `tableCode`, skip an index whose name fails `IDENTIFIER` from `../schema/problems` and push a line to `out.todos` (they are printed in the header and returned in `PullResult.review`), the way `SAMPLE BY` and `EPHEMERAL` are reported; or add the index names to `unplain` in introspect.ts:248 so the table is written `external: true`.

Verify the problem exists at that location before changing it, and keep the fix to those lines.

- External tables keep their columns' DEFAULT / MATERIALIZED / ALIAS kinds,
  so inserts may leave out what the server fills and never write computed
  columns.
- A Decimal wider than 15 digits is flagged for review: it decodes to a
  number, which rounds.
- A MergeTree in the old MergeTree(date, key, granularity) syntax is written
  as external rather than without its ORDER BY.
- Projections and constraints are noted instead of dropped silently.
- An EPHEMERAL column no longer decides whether a table's names are plain.
- pull defaults --out to a one-module schema array, and refuses an empty
  --tables.
- One stripDatabase for pull and verify, which no longer strips `db.` out of
  `my-db.`; one io helper; verify parses each engine_full once; columns and
  indexes are grouped by table once.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@maple-review-bot

maple-review-bot Bot commented Oct 15, 2026 •

Copy link
Copy Markdown

Maple review

🟢 Confidence 7/10 · likely safe to merge
Leading keys now come from engine_full and the round trip is pinned by a committed fixture, but the open non-plain index-name hole is untouched.
quality 90/100 · 1 warning · tests covered · risk medium · 0/2 new units observable

Adds effect-orm pull, which writes ClickHouse CH.table definitions from the live catalog (codegen + introspect), and fixes verify to read sorting/primary keys from engine_full. Sound and well tested apart from the open non-plain index-name finding.

  • pull writes the config's schema module from the live ClickHouse catalog, also exported from /kit
  • codegen maps column types onto built-ins, CH.custom or CH.untyped and writes tables, keys, indexes, TTL, settings
  • introspect builds entities from engine_full and routes views and non-MergeTree tables to external: true
  • verify compares declared keys from engine_full instead of sorting_key, ending false drift on versioned tables

Still open from earlier reviews

  • 🟠 Warning · F1 · An index name that is not a plain identifier makes the pulled module fail generate --baseline · src/kit/codegen.ts:227
What was checked
  • Generated module round-trips: entitiesOf equals the introspected entities (pull.test.ts:131-140)
  • stripDatabase lookbehind leaves x.db.w alone while stripping db.t (pull.test.ts:86)
  • declaredKeys feeds the same row objects as actualTables into keysOf, so the ! lookup cannot miss (verify.ts:143-174)
Observability coverage: 0 of 2 changes observable
Change Kind Observable Evidence
effect-orm pull CLI command CLI entrypoint no Sibling kit commands generate, check, status and verify create no span (cli.ts:102-211); only Migrate.run does (migrate/run.ts:270)
ClickHouse catalog read (readClickHouseCatalog) outbound database read no Runs through MigrationDriver/SqlClient (migrate/driver.ts:44-63), which carries the query spans; the shared catalog read adds none of its own

a76aa8c · Updated on every push. Reply "won't fix" to dismiss a finding, or mention @maple-review-bot to ask about one.

Pulling ClickHouse's public playground as its read-only user failed
outright: it may read system.tables and system.columns but not
system.data_skipping_indices. pull now writes the tables without indexes and
says why in the review list. verify still fails there, since skipping the
check would hide drift.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@maple-review-bot

maple-review-bot Bot commented Oct 15, 2026 •

Copy link
Copy Markdown

Maple review

🟢 Confidence 8/10 · likely safe to merge
The delta is contained, but F1 (a non-plain skipping-index name) is still live in introspect.ts/codegen.ts at this head.
quality 90/100 · 1 warning · tests covered · risk medium

This head lets pull continue when a read-only user cannot read system.data_skipping_indices (no indexes, a note saying so), and has verify read the catalog through the shared helper. Neither commit adds a new defect, but the feature still carries the open F1 and should not merge until it is fixed.

  • readClickHouseCatalog takes optionalIndexes; pull continues with no indexes and a note
  • verify reads the catalog strictly through the same helper
  • introspectClickHouse swallows an unreadable index query instead of failing

Still open from earlier reviews

  • 🟠 Warning · F1 · An index name that is not a plain identifier makes the pulled module fail generate --baseline · src/kit/codegen.ts:227
What was checked
  • readClickHouseCatalog has no other caller in src/ (grep), so the new parameter breaks none
  • declaredKeys and unwrapTuple unwrap both the expected and the actual key, so the comparison adds no drift
  • The unread-index path is pinned both ways by pull.test.ts:73 and pull.test.ts:76

355317d · Updated on every push. Reply "won't fix" to dismiss a finding, or mention @maple-review-bot to ask about one.

@Makisuo
Makisuo merged commit 4b2cadf into main Oct 15, 2026
4 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant