Fix `LOGICAL_ERROR` "Table has no columns." when `Merge` matches a dangling `Alias` by groeneai · Pull Request #104790 · ClickHouse/ClickHouse · GitHub
Skip to content

Fix LOGICAL_ERROR "Table has no columns." when Merge matches a dangling Alias#104790

Merged
azat merged 2 commits into
ClickHouse:masterfrom
groeneai:groeneai/fix-104740-merge-alias-missing-target
May 19, 2026
Merged

Fix LOGICAL_ERROR "Table has no columns." when Merge matches a dangling Alias#104790
azat merged 2 commits into
ClickHouse:masterfrom
groeneai:groeneai/fix-104740-merge-alias-missing-target

Conversation

@groeneai

@groeneai groeneai commented May 13, 2026

Copy link
Copy Markdown
Contributor

A Merge table whose regexp matches an Alias storage whose target table
has been dropped aborts the query with an internal LOGICAL_ERROR
"Table has no columns.". This shows up in CI as
STID 3593-560a / 3593-5990
and was flagged on #100752
("Enable snappy compression in HTTP interface") and
#102690
("Fix inserting into Time data type during JSON parsing") — both
unrelated to StorageMerge/StorageAlias.

Root cause

  • StorageAlias::getInMemoryMetadataPtr in src/Storages/StorageAlias.h
    returns the alias's own (empty) metadata when its target is missing.
    This is intentional so that metadata-only operations such as
    system.tables/system.columns queries on a dangling alias work
    (exercised by the existing 03636_storage_alias_basic test).
  • ReadFromMerge::createChildrenPlans in src/Storages/StorageMerge.cpp
    iterates the selected children, calls getInMemoryMetadataPtr on each,
    and throws LOGICAL_ERROR "Table has no columns." when the snapshot
    has no columns — assuming the only legitimate empty case is a
    parameterized view.

In CI the path is reached two ways:

  • Stress tests: ignore_drop_queries_probability leaks the
    alias_with_missing_target table from 03636_storage_alias_basic
    into a later test whose Merge(currentDatabase(), 'a') regexp
    matches it.
  • Regular workloads: TOCTOU race when the target is dropped between
    StorageMerge::getSelectedTables (which only locks the alias storage,
    not its target) and the metadata snapshot read in
    createChildrenPlans.

Fix

Detect the Alias case in the empty-metadata branch and throw
UNKNOWN_TABLE with an exception that names both the dangling alias
and the matching Merge table. This mirrors what
StorageMerge::isRemote already surfaces during analyzer resolution
via StorageAlias::isRemotegetTargetTable.

A new regression test 04236_merge_with_alias_missing_target_104740
locks down the user-visible contract: a SELECT through a Merge
whose regexp matches a dangling alias must throw UNKNOWN_TABLE,
never LOGICAL_ERROR.

Closes #104740.

CI evidence: 30-day CIDB shows 6 hits across arm_msan/arm_release/amd_tsan
stress runs on master and 4 distinct unrelated PRs.

Changelog category (leave one):

  • CI Fix or Improvement (changelog entry is not required)

Documentation entry for user-facing changes

  • Documentation is unchanged.

Version info

  • Merged into: 26.5.1.789

…ngling `Alias`

When the regexp of a `Merge` table matches an `Alias` storage whose target
table has been dropped, the query aborted with the internal
`LOGICAL_ERROR` "Table has no columns." raised in
`ReadFromMerge::createChildrenPlans`.

Root cause:
  * `StorageAlias::getInMemoryMetadataPtr` (`src/Storages/StorageAlias.h`)
    returns the alias's own empty metadata when its target is missing
    (`tryGetTargetTable` is null). This is necessary for metadata-only
    operations such as `system.tables`/`system.columns` queries on a
    dangling alias, exercised by the existing
    `03636_storage_alias_basic` test.
  * `ReadFromMerge::createChildrenPlans` (`src/Storages/StorageMerge.cpp`)
    iterates the selected children, calls `getInMemoryMetadataPtr` on
    each, and bails out with `LOGICAL_ERROR` "Table has no columns." if
    the snapshot has no columns — assuming the only legitimate empty
    case is a parameterized view.

In stress tests the path is reached via `ignore_drop_queries_probability`
leaking `alias_with_missing_target` from `03636_storage_alias_basic` into
later tests, where a `Merge(currentDatabase(), 'a')` regexp matches the
dangling alias. In regular workloads the same path is reachable via a
TOCTOU race: the target table is dropped between `getSelectedTables`
(which locks the alias storage, not its target) and the metadata
snapshot read in `createChildrenPlans`.

Fix: detect the `Alias` case in the empty-metadata branch and throw a
user-facing `UNKNOWN_TABLE` exception that names both the dangling alias
and the matching `Merge` table. This mirrors the analyzer-resolution
path, where `StorageMerge::isRemote` already surfaces `UNKNOWN_TABLE`
via `StorageAlias::isRemote` → `getTargetTable`.

A new regression test `04236_merge_with_alias_missing_target_104740`
locks down the user-visible contract: a `SELECT` through a `Merge`
whose regexp matches a dangling alias must throw `UNKNOWN_TABLE`, never
`LOGICAL_ERROR`.

Closes ClickHouse#104740 (STID 3593-560a / 3593-5990).
@groeneai

Copy link
Copy Markdown
Contributor Author

@groeneai

Copy link
Copy Markdown
Contributor Author

cc @alexey-milovidov @azat — could you review this? Fixes the chronic STID 3593-560a / 3593-5990 LOGICAL_ERROR "Table has no columns." by turning the assertion into a user-facing UNKNOWN_TABLE for the dangling-Alias case in ReadFromMerge::createChildrenPlans. Closes #104740, per the directive on #100752.

@Algunenano Algunenano added the can be tested Allows running workflows for external contributors label May 13, 2026
@clickhouse-gh

clickhouse-gh Bot commented May 13, 2026

Copy link
Copy Markdown
Contributor

Workflow [PR], commit [5b5c895]

Summary:


AI Review

Summary

This PR changes ReadFromMerge::createChildrenPlans to map the dangling-Alias empty-metadata case to UNKNOWN_TABLE instead of an internal LOGICAL_ERROR, and adds a focused stateless regression test that covers both plain read and join planning paths. I did not find a remaining contract violation in the touched paths of the current diff.

Missing context / blind spots
  • ⚠️ I could not validate full CI signal from the PR URL due token restrictions in this environment; direct S3 report lookup for this SHA returned an empty test list, so this review is based on code + test diff inspection.
Final Verdict
  • Status: ✅ Approve

@clickhouse-gh clickhouse-gh Bot added the pr-ci label May 13, 2026
@Avogar Avogar self-assigned this May 13, 2026
Comment thread src/Storages/StorageMerge.cpp
@groeneai

Copy link
Copy Markdown
Contributor Author

Standing down — @azat's PR #104919 supersedes this one.

Quick summary for future readers:

Aspect This PR #104790 @azat's PR #104919
Approach Catch empty-metadata in ReadFromMerge::createChildrenPlans and convert to UNKNOWN_TABLE Make StorageAlias::getInMemoryMetadataPtr throw on missing target; introduce tryGetInMemoryMetadataPtrForSystemTable() for the introspection paths
Files changed 2 10
Regression test reused 04236_merge_with_alias_missing_target_104740 Same

@azat's approach fixes the contract at the source (alias storage refuses to hand out empty metadata) and isolates the "tolerate dangling target" behavior to the four system-table call sites that actually need it. That's cleaner than guarding at one consumer (our approach).

Closing this PR in favor of #104919. Thanks for the alternative @azat!

Session: cron:clickhouse-ci-task-worker:20260514-091500

@groeneai

Copy link
Copy Markdown
Contributor Author

Superseded by #104919 (see above).

Comment thread src/Storages/StorageMerge.cpp Outdated
@azat azat enabled auto-merge May 18, 2026 20:21
@clickhouse-gh

clickhouse-gh Bot commented May 18, 2026

Copy link
Copy Markdown
Contributor

LLVM Coverage Report

Metric Baseline Current Δ
Lines 84.20% 84.10% -0.10%
Functions 91.40% 91.40% +0.00%
Branches 76.60% 76.60% +0.00%

Changed lines: 33.33% (3/9) · Uncovered code

Full report · Diff report

@groeneai

Copy link
Copy Markdown
Contributor Author

Thanks for the review and approval, @azat — and thanks for applying the comment-wording suggestion directly (commit 5b5c895). The new comment is much cleaner.

For context to anyone following this thread: I had earlier stood down in favor of #104919, but it looks like this PR's approach was kept after all. CI is currently rebuilding on the updated commit; will monitor for green and ping again if anything else surfaces.

@azat azat added this pull request to the merge queue May 19, 2026
Merged via the queue into ClickHouse:master with commit 3a00bfd May 19, 2026
166 checks passed
@clickgapai

Copy link
Copy Markdown
Contributor

@robot-ch-test-poll1 robot-ch-test-poll1 added the pr-synced-to-cloud The PR is synced to the cloud repo label May 19, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

can be tested Allows running workflows for external contributors pr-ci pr-synced-to-cloud The PR is synced to the cloud repo

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Logical error: 'Table has no columns.' (STID: 3593-5990)

6 participants