Skip to content

fix(ogar-adapter-clickhouse-ddl): preserve backtick-quoted dotted table names on parse#40

Merged
AdaWorldAPI merged 1 commit into
mainfrom
claude/fix-clickhouse-dotted-name-roundtrip
Jun 5, 2026
Merged

fix(ogar-adapter-clickhouse-ddl): preserve backtick-quoted dotted table names on parse#40
AdaWorldAPI merged 1 commit into
mainfrom
claude/fix-clickhouse-dotted-name-roundtrip

Conversation

@AdaWorldAPI

Copy link
Copy Markdown
Owner

Summary

Closes Codex P2 on PR #38 (now merged) — last_ident split on . BEFORE stripping backticks, silently truncating `sale.order` to order and breaking the round-trip the PR claimed to support for Odoo dotted names. Per your instruction, fresh PR for visibility since #38 is on main.

PR #39 had no review comments — only #38 needed a fix.

The bug

ct.name (ObjectName) ──to_string──▶  "`sale.order`"
                     ──last_ident──▶  rsplit('.').next() = "order`"
                                      ──trim_matches('`')  = "order"
                                                            ^^^^^^^ WRONG

The . was treated as a database/table separator even though it was inside the quoted identifier. Result: emit + parse round-trip turned sale.order into order, breaking the round-trip the PR claimed to support for Odoo dotted names.

The fix

Bypass the string round-trip entirely. sqlparser already resolves the backtick/dot ambiguity structurally:

Input ObjectName shape
`sale.order` ONE part: Ident { value: "sale.order", quote_style: Some('') }` — backticks group it
db.table TWO parts: [Ident{value:"db"}, Ident{value:"table"}]. is the separator

New helper accesses the last part's Ident.value directly (the unquoted text):

fn last_ident_from_object_name(name: &ObjectName) -> String {
    name.0.last()
        .and_then(|part| part.as_ident())
        .map(|ident| ident.value.clone())
        .unwrap_or_default()
}

Correct for both cases without any string splitting or backtick-trimming heuristics.

Verifying tests (4 new; 12 prior → 16 total)

Test Asserts
parse_backtick_quoted_dotted_table_keeps_full_name `sale.order` → name == "sale.order" (not "order" — the Codex P2 motivating case)
parse_qualified_db_table_takes_last_segment my_db.users → name == "users" (qualified-db path still works)
round_trip_odoo_dotted_table_name Class::new("sale.order") + attribute → emit → parse → name + attribute byte-identical
round_trip_three_segment_odoo_name account.move.line three-segment name round-trips

Verification

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

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

Symmetry note

This is the same class of bug ogar-adapter-surrealql shipped a fix for in PR #36 (backtick-quoting on emit + symmetric parser handling). The two adapters now follow the same discipline:

  • surrealql adapter: emit backtick-quotes non-bare idents; walker accesses Path.start.text directly (unquoted text) — no string round-trip on the parse side.
  • clickhouse adapter: emit backtick-quotes non-bare idents; walker accesses ObjectNamePart::Identifier.value directly (unquoted text) — no string round-trip on the parse side.

Same lesson: don't string-round-trip when the AST already encodes the structural distinction. Worth pinning in a future ADR if a third adapter trips the same wire (the ogar-adapter-ttl adapter doesn't have this risk because Turtle has different identifier semantics).

Out of scope for this PR

The DOMAIN-INSTANCES.md §2.6 + RDF-OWL-ALIGNMENT.md §10 Phase 2c updates I was queuing for after lance-graph PR #473 lands — those are now also unblocked and will be a separate small docs PR. Per the user's instruction to ship the fix PR first for visibility, the docs PR follows.

https://claude.ai/code/session_01PBTGaPCSnnt6u3pjXpbLwY

…le names on parse

Closes Codex P2 on PR #38 (now merged).

# The bug

When the emitter produces `CREATE TABLE `sale.order` (…)` for an
Odoo dotted class name, the parser path went:

  ct.name (ObjectName) ──to_string──▶ "`sale.order`"
                       ──last_ident──▶ rsplit('.').next() = "order`"
                                       ──trim_matches('`') = "order"
                                                            ^^^^^^ WRONG

The `.` was treated as a database/table separator even though it was
INSIDE the quoted identifier. Result: emit + parse round-trip turned
`sale.order` into `order`, breaking the round-trip the PR claimed to
support for Odoo dotted names.

# The fix

Bypass the string round-trip entirely. sqlparser already resolves the
backtick / dot ambiguity structurally:

  - `\`sale.order\`` → `ObjectName(vec![ObjectNamePart::Identifier(
       Ident { value: "sale.order", quote_style: Some('`') })])`
       — ONE part because the backticks group it.
  - `db.table` → `ObjectName(vec![db_part, table_part])` — TWO parts.

The new helper accesses the last part's `Ident.value` directly (the
*unquoted* text), which is correct for both cases without splitting:

  fn last_ident_from_object_name(name: &ObjectName) -> String {
      name.0.last()
          .and_then(|part| part.as_ident())
          .map(|ident| ident.value.clone())
          .unwrap_or_default()
  }

# Verifying tests (4 new; 12 prior → 16 total)

  parse_backtick_quoted_dotted_table_keeps_full_name
    — `CREATE TABLE \`sale.order\` (…)` → name == "sale.order"
      (not "order" — the Codex P2 motivating case).
  parse_qualified_db_table_takes_last_segment
    — `CREATE TABLE my_db.users (…)` → name == "users"
      (qualified-db path still works).
  round_trip_odoo_dotted_table_name
    — build IR with Class::new("sale.order") + attribute, emit,
      parse, assert name + attribute survive byte-identical.
  round_trip_three_segment_odoo_name
    — `account.move.line` three-segment name round-trips.

# Verification

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

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

# Symmetry note

This is the same class of bug ogar-adapter-surrealql shipped a fix for
in PR #36 (backtick-quoting on emit + symmetric parser handling). The
two adapters now follow the same discipline:

  - surrealql adapter: emit backtick-quotes non-bare idents;
    walker accesses Path.start.text directly (unquoted) — no string
    round-trip on the parse side.
  - clickhouse adapter: emit backtick-quotes non-bare idents;
    walker accesses ObjectNamePart.Ident.value directly (unquoted)
    — no string round-trip on the parse side.

Same lesson: don't string-round-trip when the AST already encodes
the structural distinction.

https://claude.ai/code/session_01PBTGaPCSnnt6u3pjXpbLwY
@AdaWorldAPI AdaWorldAPI merged commit 0acdcd1 into main Jun 5, 2026
1 check passed
AdaWorldAPI pushed a commit that referenced this pull request Jul 4, 2026
…ns — transpile-chain LEG 2

ruff PR #40 (merged) shipped the is_a input end: a frontend-agnostic
`ruff_spo_triplet::Model.inherits: Vec<String>` populated by the Odoo
frontend from `_inherit` (self-reopen self-edge excluded upstream). The
lift previously consumed only `sti.inherits_from -> Class.parent` (Rails
STI) and dropped Model.inherits, so the Odoo is_a linkage never reached
the Core.

Route it to Class.mixins (extend), the multi-parent Vec shelf the vocab
already designates for it: Class::mixins doc names `_inherit = 'mixin.thread'`,
and Class::inheritance excludes mixins ("a SEPARATE axis ... never folded
in here"). So NOT parent/inheritance (STI single-parent spine) — no parent
widening, no information loss, no vocab-axis violation. Multi-parent handled
natively by the Vec. Frontend-agnostic: only the Odoo frontend populates
Model.inherits, so it is a no-op for Rails (sti) / C++ (bases).

Consequence for LEG 3 (render, D-VCW-3): the FieldMask compose must union
parent U mixins for Odoo inherited fields; render_rows stays concept-local.

Tests: +2 (odoo_inherit_lands_on_mixins_not_parent, empty_inherits_adds_no_mixins);
48 green in ogar-from-ruff, workspace check + clippy clean.
Ledger: docs/DISCOVERY-MAP.md D-OGAR-ODOO-INHERIT-MIXINS.
AdaWorldAPI pushed a commit that referenced this pull request Jul 4, 2026
ogar-from-ruff reads ruff_spo_triplet::Model::inherits (added in ruff #40).
With no committed Cargo.lock, the floating `branch = "main"` pin could
resolve to a ruff rev WITHOUT that field on a clean build and fail to
compile — and the prior commit's '61ce2b49' claim didn't travel. Pin all
three ruff refs (ruff_spo_triplet + ruff_spo_address in ogar-from-ruff,
ruff_ruby_spo in ogar-from-rails) to the exact rev 61ce2b49 (ruff main at
the #40 merge). Same rev across both crates: they share ruff_spo_triplet
internally, so a mismatch would double-check-out ruff.

Verified: ruff re-locks to 61ce2b49; ogar-from-ruff 48 tests + ogar-from-rails
green; workspace check + clippy -D warnings clean.
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