Skip to content

fix(schema-compiler): Use isNotDistinctFrom() for ClickHouse Tesseract multi-fact joins#10494

Open
rclapp wants to merge 4 commits intocube-js:masterfrom
rclapp:fix/clickhouse-tesseract-null-safe-join
Open

fix(schema-compiler): Use isNotDistinctFrom() for ClickHouse Tesseract multi-fact joins#10494
rclapp wants to merge 4 commits intocube-js:masterfrom
rclapp:fix/clickhouse-tesseract-null-safe-join

Conversation

@rclapp
Copy link

@rclapp rclapp commented Mar 13, 2026

Summary

  • Fixes ClickHouse Tesseract multi-fact queries failing with Cannot determine join keys in JOIN ON expression error
  • Adds support for a function-style expressions/is_not_distinct_from template as a fallback when the binary operator operators/is_not_distinct_from is not available
  • Registers ClickHouse's isNotDistinctFrom(left, right) function via the new expression template

Fixes #10493

Context

When CUBEJS_TESSERACT_SQL_PLANNER=true is enabled, multi-fact queries (measures from multiple cubes) generate null-safe JOIN conditions using OR (IS NULL AND IS NULL):

ON ((a.col = b.col OR ((a.col IS NULL) AND (b.col IS NULL))))

ClickHouse rejects this — it requires simple equality or function calls in JOIN ON clauses.

Other dialects (Postgres, BigQuery, Snowflake) use the IS NOT DISTINCT FROM binary operator, but ClickHouse's equivalent is the function isNotDistinctFrom(a, b) — not a binary operator. The existing operators/is_not_distinct_from template only supports binary operator syntax via the expressions/binary template ({{ left }} {{ op }} {{ right }}).

Changes

Rust (rust/cubesqlplanner/cubesqlplanner/src/planner/sql_templates/plan.rs):

  • Added supports_is_not_distinct_from_expr() check for expressions/is_not_distinct_from template
  • In join_by_dimension_conditions(), added fallback path: check binary operator first, then expression template, then existing OR (IS NULL AND IS NULL) fallback

TypeScript (packages/cubejs-schema-compiler/src/adapter/ClickHouseQuery.ts):

  • Registered expressions.is_not_distinct_from = 'isNotDistinctFrom({{ left }}, {{ right }})' in sqlTemplates()

This is minimally invasive — existing dialects continue using the operators/is_not_distinct_from binary operator path unchanged.

Check List

  • Tests have been run in packages where changes have been made if available
  • Linter has been run for changed code
  • Tests for the changes have been added if not covered yet
  • Docs have been added / updated if required

Test results

Rust (cargo test): 373 passed, 0 failed (368 existing + 5 new)
Rust (cargo fmt --check): No formatting issues

New unit tests (plan::tests::test_join_condition_*):

  • test_join_condition_no_null_check — simple equality without null handling
  • test_join_condition_null_check_fallback_or_is_null — OR/IS NULL fallback (no templates)
  • test_join_condition_null_check_binary_operator — Postgres/BigQuery/Snowflake path
  • test_join_condition_null_check_expression_template — ClickHouse isNotDistinctFrom() path
  • test_join_condition_binary_operator_takes_precedence_over_expression — priority ordering

Manual validation: Took the exact SQL Tesseract generates for 2-fact and 4-fact multi-fact queries, replaced the OR (IS NULL AND IS NULL) join conditions with isNotDistinctFrom(), and verified all queries execute correctly against ClickHouse.

🤖 Generated with Claude Code

…t multi-fact joins

ClickHouse does not support OR/IS NULL patterns in JOIN ON clauses,
which causes Tesseract multi-fact queries to fail. Add support for a
function-style is_not_distinct_from expression template and register
ClickHouse's isNotDistinctFrom() function as the implementation.

Fixes cube-js#10493

Co-Authored-By: Claude Opus 4.6 <[email protected]>
@rclapp rclapp requested a review from a team as a code owner March 13, 2026 16:43
@github-actions github-actions bot added rust Pull requests that update Rust code javascript Pull requests that update Javascript code pr:community Contribution from Cube.js community members. labels Mar 13, 2026
… fallback paths

Tests all three code paths in join_by_dimension_conditions:
- Binary operator (Postgres/BigQuery/Snowflake: IS NOT DISTINCT FROM)
- Expression template (ClickHouse: isNotDistinctFrom() function)
- OR (IS NULL AND IS NULL) fallback
- Binary operator takes precedence when both templates exist
- No null check produces simple equality

Co-Authored-By: Claude Opus 4.6 <[email protected]>
Remove 4 stale snapshot files that are no longer referenced by any
test, causing CI to fail with "unreferenced snapshots" error.

Co-Authored-By: Claude Opus 4.6 <[email protected]>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

javascript Pull requests that update Javascript code pr:community Contribution from Cube.js community members. rust Pull requests that update Rust code

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Tesseract: ClickHouse rejects null-safe JOIN ON with OR/IS NULL pattern in multi-fact queries

1 participant