Fix text index on mapValues/mapKeys not used through a Distributed engine table#109188
Fix text index on mapValues/mapKeys not used through a Distributed engine table#109188groeneai wants to merge 5 commits into
Conversation
…gine table A text index defined on mapValues(map) or mapKeys(map) was used for a local MergeTree table and via cluster()/remote(), but silently NOT used when the same table was queried through a Distributed engine table with the analyzer, failing with INDEX_NOT_USED under force_data_skipping_indices. StorageDistributed only serializes the query to shards and never reads columns locally, but it inherited the default supportsOptimizationToSubcolumns() = true. The Distributed table has no secondary indices in its own metadata, so FunctionToSubcolumnsPass on the initiator rewrote mapValues(attributes) to the subcolumn attributes.values and serialized that form to the shards, where it no longer matched the index expression. IStorageCluster (cluster()/remote()) already returns false here for the same reason; StorageDistributed was the missing case. The shard re-runs the analyzer against the real underlying table, so legitimate subcolumn optimizations still happen shard-side where the physical read occurs. Closes ClickHouse#108874. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
cc @CurtizJ @novikd — could you review this? It fixes a text index on |
|
Workflow [PR], commit [320b49d] Summary: ❌
AI ReviewSummaryThis PR closes the original Missing context / blind spots
Final Verdict✅ No new inline findings. I did not post a batched review comment. |
A database with lazy_load_tables = 1 wraps each table in a StorageTableProxy. StorageProxy forwarded supportsSubcolumns() to the nested storage but not supportsOptimizationToSubcolumns(), which fell back to the IStorage default (tied to supportsSubcolumns() == true). A proxy around Distributed therefore re-advertised true, so FunctionToSubcolumnsPass rewrote mapValues(attributes) to a subcolumn on the initiator before serializing the shard query, and the text index defined on mapValues/mapKeys was missed again for a lazy-loaded Distributed engine table even after StorageDistributed opted out. Forward supportsOptimizationToSubcolumns() through the proxy to the nested storage. Extend the regression test with a lazy_load_tables = 1 case. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
… Merge StorageDistributed and StorageProxy already opt out of the subcolumn optimization so a text index on mapValues/mapKeys is not lost through a Distributed engine table. Two more wrapper storages still tied the capability to the IStorage default (supportsSubcolumns() == true): - StorageMaterializedView::readImpl forwards the already-analyzed query tree to the target table without rebuilding it, so a MV whose target is Distributed could still rewrite mapValues(attributes) -> attributes.values on the initiator. Forward supportsOptimizationToSubcolumns() to the target. - StorageMerge over a Distributed child could serialize attributes.values to the shards. Add a fail-closed override that returns false if any child table opts out (same traverseTablesUntil pattern as supportsPrewhere). Extends 02346_text_index_bug108874 with MV-over-Distributed and Merge-over-Distributed cases (mapValues and mapKeys), asserting the index is used via force_data_skipping_indices.
StorageBuffer read() and getQueryProcessingStage() forward the already-analyzed query to the destination table, so it must not let the initiator rewrite functions to subcolumns when the destination opts out (e.g. Distributed). It kept the IStorage default (supportsOptimizationToSubcolumns() == supportsSubcolumns() == true), so a Buffer over a Distributed destination still rewrote mapValues(attributes) / mapKeys(attributes) before the query was split, and the shard query missed the text index (or threw INDEX_NOT_USED under force_data_skipping_indices). Add the fail-closed override mirroring supportsPrewhere(): forward to the destination, false when there is none. Extends the regression to a flushed Buffer over logs_dist. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
Fixed in 50ad567. Verified both directions against
Regression |
Pre-PR validation gate (StorageBuffer follow-up, click to expand)
Session id: cron:clickhouse-worker-slot-5:20260702-163000 |
CI finish ledger — 50ad567Every failure below has an owner (a fixing PR). Neither failure is caused by this PR's diff (which only adds
Session id: cron:our-pr-ci-monitor:20260702-220000 |
|
@groeneai merge fresh master into the branch |
…-text-index-mapvalues-distributed
|
@novikd merged fresh master ( |
LLVM Coverage Report
Changed lines: Changed C/C++ lines covered: 26/29 (89.66%) · Uncovered code |
CI finish ledger — 320b49dEvery failure below has an owner: a fixing PR (ours or external), or a full-effort fix task whose fixing-PR link will be posted here when it opens. Only PR-caused gate: ZERO. This PR's diff is Session id: cron:our-pr-ci-monitor:20260703-210000 |

Closes: #108874
Changelog category (leave one):
Changelog entry (a user-readable short description of the changes that goes into CHANGELOG.md):
Fixed a text index defined on
mapValues(map)ormapKeys(map)being silently not used when a table was queried through aDistributedengine table with the analyzer. The index was used for the local table and viacluster()/remote(), but a query through aDistributedengine table skipped it (and failed withINDEX_NOT_USEDunderforce_data_skipping_indices).Description
Reported in #108874. A
textindex onmapValues(attributes)(ormapKeys(attributes)) is used when querying the localMergeTreetable and viacluster()/remote(), but silently not used when the same table is queried through aDistributedengine table with the analyzer.Root cause:
StorageDistributedonly serializes the query to shards and never reads columns locally, but it inherited the defaultsupportsOptimizationToSubcolumns() = supportsSubcolumns() = true. TheDistributedtable has no secondary indices in its own metadata, so the index-safety guard inFunctionToSubcolumnsPass(which refuses to rewrite columns required by a secondary index) never seesattributesas index-relevant on the initiator. The pass therefore rewritesmapValues(attributes)to the subcolumnattributes.valuesand serializes that form into the shard query, where it no longer matches the index defined onmapValues(attributes).IStorageCluster(which backscluster()/remote()) already returnsfalsefromsupportsOptimizationToSubcolumns()for exactly this reason (commit0c9926a7045d);StorageDistributedwas the missing case. This PR adds the same override.Because the shard re-runs the analyzer against the real underlying table (with full index metadata), legitimate subcolumn optimizations still happen shard-side where the physical read actually occurs, so there is no lost optimization.
Reproducer (needs the analyzer and a 2-shard cluster):
Added a regression test:
02346_text_index_bug108874.