Skip to content

fix(supersimple): two-sided join keys, table prefix, and case control - #21

Open
andrebaaij wants to merge 3 commits into
mainfrom
feat/supersimple-clickhouse
Open

fix(supersimple): two-sided join keys, table prefix, and case control#21
andrebaaij wants to merge 3 commits into
mainfrom
feat/supersimple-clickhouse

Conversation

@andrebaaij

Copy link
Copy Markdown
Contributor

Three changes needed to target a case-sensitive warehouse with prefixed tables. The first is a correctness bug that would have produced wrong numbers silently.

Two-sided join keys (the bug)

The emitter wrote join_key: <parent column> for every relation. That is Supersimple's shorthand for both sides share this column name — true for 20 of 23 relations here, wrong for the rest:

reference:  fct_orders.order_date -> dim_date.date_day
emitted:    join_key: date_day        # the child has no such column

supersimple validate caught three of these (references unknown property 'date_day' on related model). The dangerous case is the one it could not catch: the golden encoded a role-playing relation joining BILLING_CUSTOMER_SK as join_key: CUSTOMER_SK, a column that exists on both models. That validates cleanly and joins on the wrong column, returning plausible wrong answers.

The vendor's own discovery snapshot uses the two-sided form:

join_strategy:
  join_key_on_base: DATE_DAY
  join_key_on_related: ORDER_DATE

We now emit the shorthand only when both sides genuinely agree. The golden is updated because it captured the bug.

Two new options, both default-off

option why
table-prefix IR carries logical names; ClickHouse materialises fct_orders as marts__fct_orders
lowercase-identifiers upper-case suits Snowflake (unquoted identifiers fold up); ClickHouse is case-sensitive and MARTS__FCT_ORDERS does not exist

schema now honours an explicit empty value rather than always defaulting to MAIN — ClickHouse's two-part namespace makes MAIN.marts__fct_orders unresolvable.

Snowflake output is byte-identical with the options off.

Verification

38 models, 338 properties, 23 relations, 14 metrics. on_time_delivery_rate survives as a real operations expression rather than degrading to prose. No NOTES.md sidecar, so nothing was dropped. supersimple validateConfiguration is valid!

🤖 Generated with Claude Code

https://claude.ai/code/session_01P1Ae794MJcezTixWn2wZ4J

andrebaaij and others added 3 commits August 12, 2026 21:12
Hex's Semantic Model Sync parses dbt MetricFlow YAML straight from a git repo
(not via an API), and binds each semantic model to a physical table through
`config.meta.hex.table`. Without that key the sync imports models that resolve to
nothing: the agent then answers about no data, which a benchmark grades as a
wrong answer rather than the setup failure it is. That exact failure mode cost a
full Lightdash run earlier.

Adds two options, both off by default:

  table-prefix   logical -> physical name (ClickHouse materialises fct_orders
                 as marts__fct_orders)
  dbt-hex-meta   emit the config.meta.hex.table binding

`dbt` becomes Configurable to receive them. A plain dbt build is unchanged and
carries no Hex key — asserted by a test, because the dbt target also writes the
shared ground-truth reference every arm reads, and one vendor's meta must not
leak into it.

Also fixes a defaulting bug this surfaced. `schema` defaulted to MAIN
unconditionally, so the binding came out as `ecomm.MAIN.marts__fct_orders`, which
does not resolve on ClickHouse's two-part namespace. `schema` is now a pointer so
an EXPLICIT empty value is distinguishable from an omitted one; omitted still
defaults to MAIN. Changing the default globally was the wrong fix: ossie,
nao-yaml, supersimple and the warehouse dialects all read Schema, and Snowflake
targets genuinely need the qualified form.

Verified against the pinned eval reference: 38 semantic models, 22 measures, 14
metrics, all 38 bound to ecomm.marts__<name>.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The Schema *string change altered the struct's field-alignment column, which
gofmt reflows. CI checks gofmt -l and I did not run it before pushing.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Three changes needed to target a case-sensitive warehouse with prefixed tables.
The first is a correctness bug that would have produced wrong numbers silently.

TWO-SIDED JOIN KEYS. The emitter wrote `join_key: <parent column>` for every
relation. That is Supersimple's shorthand for "both sides share this column
name", which held for 20 of 23 relations and was WRONG for the rest:
fct_orders.order_date -> dim_date.date_day was emitted as `join_key: date_day`,
addressing a column the child does not have. `supersimple validate` caught three
of these ("references unknown property 'date_day' on related model"), but the
dangerous case is the one it could not catch — the golden encoded a role-playing
relation joining BILLING_CUSTOMER_SK as `join_key: CUSTOMER_SK`, a column that
DOES exist on both sides, so it would have joined on the wrong one and returned
plausible wrong answers rather than erroring. The vendor's own discovery snapshot
uses join_key_on_base/join_key_on_related; we now emit the shorthand only when
both sides genuinely agree. Golden updated: it captured the bug.

TablePrefix maps the IR's logical name to the physical one (ClickHouse
materialises fct_orders as marts__fct_orders). LowerCaseIdentifiers keeps
identifiers as spelled instead of upper-casing them: the upper-case default suits
Snowflake, where unquoted identifiers fold up, but ClickHouse is case-SENSITIVE
and MARTS__FCT_ORDERS does not exist. Both default off, so Snowflake output is
byte-identical.

Schema now honours an EXPLICIT empty value rather than always defaulting to
MAIN — ClickHouse has a two-part namespace, so MAIN.marts__fct_orders resolves
to nothing.

Verified end to end: 38 models, 338 properties, 23 relations, 14 metrics
(on_time_delivery_rate intact as a real operations expression, not degraded to
prose), no NOTES sidecar, `supersimple validate` reports Configuration is valid.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
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