Skip to content

feat(ogar-adapter-clickhouse-ddl): scaffold ClickHouse DDL adapter — Phase 2b#38

Merged
AdaWorldAPI merged 1 commit into
mainfrom
claude/phase-2b-clickhouse-ddl-adapter
Jun 5, 2026
Merged

feat(ogar-adapter-clickhouse-ddl): scaffold ClickHouse DDL adapter — Phase 2b#38
AdaWorldAPI merged 1 commit into
mainfrom
claude/phase-2b-clickhouse-ddl-adapter

Conversation

@AdaWorldAPI

Copy link
Copy Markdown
Owner

Summary

New crate crates/ogar-adapter-clickhouse-ddl — third source-language adapter in OGAR's brutal-upgrade ingestion lineup. Composes with bardioc PR #19's substrate-b-shadow — same Class IR as the schema model their EdgeDecoder interprets rows against.

source dialect adapter direction
SurrealQL DDL ogar-adapter-surrealql parse + emit (PR #32)
Turtle (RDF/OWL) ogar-adapter-ttl parse + emit (PR #37)
ClickHouse DDL ogar-adapter-clickhouse-ddl (this PR) parse + emit

Data flow it completes

ClickHouse CREATE TABLE  ──▶  [this adapter]  ──▶  Class IR (schema)
                                                          │
ClickHouse rows (RowBinary) ──▶ [substrate-b-shadow      │  schema
                                 EdgeDecoder] ─▶ ActionInvocation
                                                          │
                                          interprets ─────┘

Per ADR-023 (IR-as-wire-truth) the shared substrate is OGAR Class.

Type mapping (ClickHouse ⇄ OGAR canonical)

Normalized to OGAR's canonical lexicon — same set used by ogar-adapter-surrealql. A Class IR survives a SurrealQL → OGAR → ClickHouse → OGAR cycle byte-identical on the type axis.

ClickHouse OGAR type_name
String, FixedString(N) string
UInt8/16/32/64, Int8/16/32/64 int
Float32, Float64 float
Decimal(P,S) decimal
DateTime, DateTime64, Date datetime
Bool bool
UUID uuid
Nullable(<inner>) inner + options.required = Some(false)
anything else any

Emit reverses with default widths (Int64 / Float64 / Decimal(18, 4) / DateTime) — AR-pattern-sufficient.

Parse implementation

Uses sqlparser 0.59 with ClickHouseDialect. Earlier versions (≤ 0.50) choke on Nullable(X) in CREATE TABLE — probed before adopting. The walker:

  1. Drives Parser::parse_sql(&ClickHouseDialect, input)
  2. Filters for Statement::CreateTable
  3. Each ColumnDefAttribute via clickhouse_type_to_ogar; Nullable(X) stripped recursively (ClickHouse forbids nested Nullable)
  4. ORDER BY lifts to Class.record_order when scalar
  5. Lowercase-prefix family matching keeps type recognition robust across UInt8/16/32/64, Int8/16/32/64, Float32/64, DateTime/DateTime64 etc. without enumerating sqlparser variants

Identifier quoting

quote_ch_ident() — bare for [A-Za-z_][A-Za-z0-9_]*, backtick-quoted otherwise. Same discipline as ogar-adapter-surrealql's surrealql_ident — Odoo dotted names (sale.order) work across all three adapters consistently.

Tests (12 — 7 emit always, 5 parse + round-trip behind feature)

Emit (always):

  • emit_minimal_class_produces_create_table_mergetree
  • emit_class_with_string_column
  • emit_class_with_optional_column_wraps_with_nullable
  • emit_class_with_record_order_lifts_to_order_by
  • emit_quotes_non_bare_table_name
  • emit_full_class_with_multiple_columns
  • parse_returns_unimplemented_when_feature_off

Parse + round-trip (clickhouse-parser):

  • parse_minimal_create_table_lifts_one_class
  • parse_nullable_lifts_to_required_false — IR-canonical shape
  • parse_recognizes_type_families — all 9 type families
  • parse_rejects_invalid_ddl
  • round_trip_simple_class_preserves_attributes — load-bearing
  • round_trip_optional_primitive — Nullable round-trip

Workspace + CI

  • Cargo.toml: added crates/ogar-adapter-clickhouse-ddl to workspace members
  • .github/workflows/ci.yml: added cargo test -p ogar-adapter-clickhouse-ddl --features clickhouse-parser — same crate-scoped pattern as the other feature-gated adapter test steps

Verification

$ cargo test --workspace                                            -> clean
$ cargo test -p ogar-adapter-clickhouse-ddl                         -> 7/7
$ cargo test -p ogar-adapter-clickhouse-ddl --features clickhouse-parser -> 12/12
$ cargo check --workspace --all-targets                             -> clean
$ cargo test -p ogar-adapter-surrealql --features surrealdb-parser  -> 33/33
$ cargo test -p ogar-adapter-ttl --features ttl-parser              -> 9/9
$ cargo test -p ogar-knowable-from --features surrealql-hint        -> 10/10

PII abort-guard (word-boundary): CLEAN.

Position in sequencing

Phase Status
Phase 1 (#30) — RDF-OWL-ALIGNMENT doc ✅ merged
Phase 2a (#37) — ogar-adapter-ttl scaffold ✅ merged
Phase 2b (this) — ogar-adapter-clickhouse-ddl scaffold opens
Phase 2b — ogar-from-ecto queued
Phase 3 — VART backend on ogar-knowable-from queued
Phase 4+ — ogar-pattern, ogar-actionable, ... queued

https://claude.ai/code/session_01PBTGaPCSnnt6u3pjXpbLwY

…Phase 2b

New crate `crates/ogar-adapter-clickhouse-ddl` — third source-language
adapter in OGAR's brutal-upgrade ingestion lineup (per
docs/RDF-OWL-ALIGNMENT.md §10 Phase 2b).

  | source dialect       | adapter                       | direction |
  |----------------------|-------------------------------|-----------|
  | SurrealQL DDL        | ogar-adapter-surrealql        | parse+emit|
  | Turtle (RDF/OWL)     | ogar-adapter-ttl              | parse+emit|
  | ClickHouse DDL       | ogar-adapter-clickhouse-ddl   | parse+emit| (NEW)

# Composes with bardioc PR #19

bardioc's substrate-b-shadow crate (PR #19, merged) carries
`ClickHouseQuery` / `ClickHouseRow` / `ClickHouseColumn` for the
runtime row-decoding path. The OGAR-side concern this adapter fills
is the schema model: ClickHouse `CREATE TABLE` → `Class` IR.

  ClickHouse CREATE TABLE -> [this adapter] -> Class (schema)
                                                      |
  ClickHouse rows (RowBinary) -> [substrate-b-shadow EdgeDecoder]
                                  -> ActionInvocation (interprets rows
                                     against the Class IR)

Per ADR-023 (IR-as-wire-truth), the shared substrate is OGAR `Class`.

# Public API

  emit_clickhouse_ddl(classes) -> String       (always available)
  parse_clickhouse_ddl(ddl) -> Result<Vec<Class>, ParseError>
                                               (behind `clickhouse-parser`)

# Type mapping (ClickHouse ⇄ OGAR canonical)

Normalized to OGAR's canonical type-name lexicon — same set used by
ogar-adapter-surrealql, so a `Class` IR survives a Surreal → OGAR →
ClickHouse → OGAR cycle byte-identical on the type axis.

  String / FixedString(N)         -> "string"
  UInt8/16/32/64, Int8/16/32/64   -> "int"
  Float32, Float64                -> "float"
  Decimal(P, S)                   -> "decimal"
  DateTime, DateTime64, Date      -> "datetime"
  Bool                            -> "bool"
  UUID                            -> "uuid"
  Nullable(<inner>)               -> inner type +
                                     options.required = Some(false)
  anything else                   -> "any"

Emit reverses: default widths Int64/Float64/Decimal(18,4)/DateTime —
AR-pattern-sufficient.

# Parse implementation

Uses sqlparser 0.59 with the ClickHouseDialect. Earlier versions
(<=0.50) choke on `Nullable(X)` in CREATE TABLE — probed before
adopting. The walker:

  1. Drives `Parser::parse_sql(&ClickHouseDialect, input)`.
  2. Filters for `Statement::CreateTable`.
  3. Each `ColumnDef` lifts to an `Attribute` via
     `clickhouse_type_to_ogar`. Nullable(X) is stripped recursively
     (ClickHouse forbids nested Nullable; single-strip is sufficient).
  4. `ORDER BY` lifts to `Class.record_order` when scalar.
  5. Lowercase-prefix family matching keeps the type recognition
     robust across UInt8/16/32/64, Int8/16/32/64, Float32/64,
     DateTime/DateTime64, etc. without enumerating sqlparser variants.

# Identifier quoting

`quote_ch_ident()` — bare for `[A-Za-z_][A-Za-z0-9_]*`, backtick-quoted
otherwise. Same discipline as ogar-adapter-surrealql's
`surrealql_ident` — Odoo dotted names (`sale.order`) work cleanly
across all three adapters without further fixes.

# Tests (12 — 7 emit always, 5 parse + round-trip behind feature)

  Emit always:
    emit_minimal_class_produces_create_table_mergetree
    emit_class_with_string_column
    emit_class_with_optional_column_wraps_with_nullable
    emit_class_with_record_order_lifts_to_order_by
    emit_quotes_non_bare_table_name
    emit_full_class_with_multiple_columns
    parse_returns_unimplemented_when_feature_off

  Parse + round-trip (behind `clickhouse-parser`):
    parse_minimal_create_table_lifts_one_class
    parse_nullable_lifts_to_required_false        [IR-canonical shape]
    parse_recognizes_type_families                [all 9 type families]
    parse_rejects_invalid_ddl
    round_trip_simple_class_preserves_attributes
    round_trip_optional_primitive                 [Nullable round-trip]

# Workspace + CI

  - `Cargo.toml`: added `crates/ogar-adapter-clickhouse-ddl` to
    workspace members.
  - `.github/workflows/ci.yml`: added
    `cargo test -p ogar-adapter-clickhouse-ddl --features clickhouse-parser`
    — same crate-scoped pattern as the other feature-gated adapter
    test steps (`surrealdb-parser`, `surrealql-hint`, `ttl-parser`).

# Verification

  cargo test --workspace                                     -> clean
  cargo test -p ogar-adapter-clickhouse-ddl                  -> 7/7
  cargo test -p ogar-adapter-clickhouse-ddl --features clickhouse-parser
                                                             -> 12/12
  cargo check --workspace --all-targets                      -> clean

All previous feature-gated adapters still pass:
  cargo test -p ogar-adapter-surrealql --features surrealdb-parser
                                                             -> 33/33
  cargo test -p ogar-knowable-from --features surrealql-hint -> 10/10
  cargo test -p ogar-adapter-ttl --features ttl-parser       -> 9/9

PII abort-guard (word-boundary): CLEAN on all touched files.

# Position in sequencing

  Phase 1  (#30):  RDF-OWL-ALIGNMENT doc                     MERGED
  Phase 2a (#37):  ogar-adapter-ttl scaffold                 MERGED
  Phase 2b (this): ogar-adapter-clickhouse-ddl scaffold      OPENS
  Phase 2b:        ogar-from-ecto                            QUEUED
  Phase 3:         vart-backend                              QUEUED
  ...

https://claude.ai/code/session_01PBTGaPCSnnt6u3pjXpbLwY

@chatgpt-codex-connector chatgpt-codex-connector 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 01e3448585

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

let mut classes = Vec::new();
for stmt in stmts {
if let Statement::CreateTable(ct) = stmt {
let name = last_ident(&ct.name.to_string());

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Preserve quoted dotted table names on parse

When parsing a table emitted from a dotted class name such as Class::new("sale.order"), the emitter produces CREATE TABLE sale.order ..., but this path converts the parsed name to a string and last_ident then splits on . before trimming backticks. That round-trip recovers the class name as order instead of sale.order, breaking the stated support for Odoo dotted names and losing schema identity whenever those emitted DDL statements are parsed back.

Useful? React with 👍 / 👎.

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.

2 participants