feat(ogar-adapter-clickhouse-ddl): scaffold ClickHouse DDL adapter — Phase 2b#38
Conversation
…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
There was a problem hiding this comment.
💡 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()); |
There was a problem hiding this comment.
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 👍 / 👎.
Summary
New crate
crates/ogar-adapter-clickhouse-ddl— third source-language adapter in OGAR's brutal-upgrade ingestion lineup. Composes with bardioc PR #19'ssubstrate-b-shadow— same Class IR as the schema model theirEdgeDecoderinterprets rows against.ogar-adapter-surrealqlogar-adapter-ttlogar-adapter-clickhouse-ddl(this PR)Data flow it completes
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. AClassIR survives a SurrealQL → OGAR → ClickHouse → OGAR cycle byte-identical on the type axis.type_nameString,FixedString(N)stringUInt8/16/32/64,Int8/16/32/64intFloat32,Float64floatDecimal(P,S)decimalDateTime,DateTime64,DatedatetimeBoolboolUUIDuuidNullable(<inner>)options.required = Some(false)anyEmit reverses with default widths (
Int64/Float64/Decimal(18, 4)/DateTime) — AR-pattern-sufficient.Parse implementation
Uses
sqlparser0.59 withClickHouseDialect. Earlier versions (≤ 0.50) choke onNullable(X)in CREATE TABLE — probed before adopting. The walker:Parser::parse_sql(&ClickHouseDialect, input)Statement::CreateTableColumnDef→Attributeviaclickhouse_type_to_ogar;Nullable(X)stripped recursively (ClickHouse forbids nested Nullable)ORDER BYlifts toClass.record_orderwhen scalarUInt8/16/32/64,Int8/16/32/64,Float32/64,DateTime/DateTime64etc. without enumerating sqlparser variantsIdentifier quoting
quote_ch_ident()— bare for[A-Za-z_][A-Za-z0-9_]*, backtick-quoted otherwise. Same discipline asogar-adapter-surrealql'ssurrealql_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_mergetreeemit_class_with_string_columnemit_class_with_optional_column_wraps_with_nullableemit_class_with_record_order_lifts_to_order_byemit_quotes_non_bare_table_nameemit_full_class_with_multiple_columnsparse_returns_unimplemented_when_feature_offParse + round-trip (
clickhouse-parser):parse_minimal_create_table_lifts_one_classparse_nullable_lifts_to_required_false— IR-canonical shapeparse_recognizes_type_families— all 9 type familiesparse_rejects_invalid_ddlround_trip_simple_class_preserves_attributes— load-bearinground_trip_optional_primitive— Nullable round-tripWorkspace + CI
Cargo.toml: addedcrates/ogar-adapter-clickhouse-ddlto workspace members.github/workflows/ci.yml: addedcargo test -p ogar-adapter-clickhouse-ddl --features clickhouse-parser— same crate-scoped pattern as the other feature-gated adapter test stepsVerification
PII abort-guard (word-boundary): CLEAN.
Position in sequencing
ogar-adapter-ttlscaffoldogar-adapter-clickhouse-ddlscaffoldogar-from-ectoogar-knowable-fromogar-pattern,ogar-actionable, ...https://claude.ai/code/session_01PBTGaPCSnnt6u3pjXpbLwY