FrankChen021 commented on code in PR #20379:
URL: https://github.com/apache/druid/pull/20379#discussion_r4302706567


##########
indexing-service/src/main/java/org/apache/druid/indexing/common/task/CompactionTask.java:
##########
@@ -1054,15 +1199,39 @@ public void fetchAndProcessIfNeeded()
 
             processRollup(index);
             processQueryGranularity(index);
-            processDimensionsSpec(index);
+            // Column data is read from a different index for a clustered 
segment, but rollup, query granularity,
+            // metrics and projections all come from the top-level metadata 
either way.
+            for (QueryableIndex columnIndex : columnIndexes(index)) {
+              processDimensionsSpec(columnIndex);
+              processMultiValuedDimensions(columnIndex);
+            }
             processMetricsSpec(index);
-            processMultiValuedDimensions(index);
             processProjections(index);
           }
         }
       }
     }
 
+    /**
+     * The indexes to read column data from. A clustered segment stores its 
rows in per-cluster-group containers and
+     * its top-level index deliberately holds no columns of its own, so 
reading the schema from it would find nothing.
+     * <p>
+     * Every group has to be read rather than taking the first as 
representative: a group omits a column's files
+     * entirely when it holds no values for that column, so a column can be 
absent from one group and present in a
+     * later one.
+     */
+    private static Iterable<QueryableIndex> columnIndexes(QueryableIndex index)
+    {
+      final ClusteredValueGroupsBaseTableSchema clusteredBaseTable = 
index.getMetadata().getClusteredBaseTable();
+      if (clusteredBaseTable == null || 
clusteredBaseTable.getClusterGroups().isEmpty()) {
+        return Collections.singletonList(index);
+      }
+      return Iterables.transform(
+          clusteredBaseTable.getClusterGroups(),
+          group -> index.getClusterGroupQueryableIndex(group, true)

Review Comment:
   [P1] Cluster-group indexes hide clustering columns from inference
   
   **Finding:** For unsealed base-table compaction, the analyzer reads each 
cluster-group sub-index. `getClusterGroupQueryableIndex(..., true)` injects 
clustering columns as constant holders, but the returned index deliberately 
exposes only non-clustering dimensions and ordering. 
`ExistingSegmentAnalyzer.processDimensionsSpec` therefore cannot discover an 
old clustering column that the current catalog layout no longer declares, so 
`undeclaredColumns` omits it and the rewrite silently drops stored data.
   
   **Suggestion:** Expose the segment-level clustering columns and ordering to 
schema inference, or merge that metadata into the inferred dimensions before 
computing undeclared columns and multi-valued-dimension state.



##########
indexing-service/src/main/java/org/apache/druid/indexing/common/task/CompactionTask.java:
##########
@@ -797,16 +831,66 @@ private static DataSchema createDataSchema(
       @Nonnull ClientCompactionTaskGranularitySpec granularitySpec,
       @Nullable List<AggregateProjectionSpec> projections,
       @Nullable BaseTableProjectionSpec baseTable,
+      boolean sealed,
+      boolean rangePartitioned,
       boolean needMultiValuedColumns
   )
   {
     if (baseTable != null) {
       // BaseTable mode: the spec owns dimensions/metrics and (via its 
query-granularity virtual column) query
       // granularity; segment granularity + intervals are the compaction 
config's. Attach the config's query
       // granularity up front (a no-op if absent/NONE); the generator's 
incremental index applies it at build time.
-      // Existing-segment analysis (dims/metrics/rollup inference) is bypassed 
— the spec is authoritative.
-      final BaseTableProjectionSpec baseTableSpec =
-          
baseTable.withQueryGranularity(granularitySpec.getQueryGranularity());
+      // A sealed spec is the complete description of the table, so the 
columns and projections it leaves undeclared
+      // are taken to not exist rather than inferred. Range partitioning is 
the exception that reads the segments even
+      // when sealed: only they can say whether a partition column is 
multi-valued, which MSQ cannot partition by.
+      final boolean analyzeSchema = !sealed;
+      final boolean analyzeProjections = !sealed && projections == null;
+      final boolean analyzeMultiValued = rangePartitioned;

Review Comment:
   [P2] Rollup base tables skip MVD analysis without range partitioning
   
   **Finding:** A catalog `rollupTable` base table with dynamic partitioning 
has `rangePartitioned == false`, so this branch never analyzes multi-valued 
dimensions even though `identifyMultiValuedDimensions()` requests that 
information for a base-table rollup task. `CombinedDataSchema` consequently 
leaves the MVD set null, and MSQ treats every string grouping column as 
potentially multi-valued, wrapping scalar strings with `mv_to_array` and 
converting them back with `array_to_mv`, which can change storage and 
multi-value/null semantics.
   
   **Suggestion:** Analyze multi-valued dimensions whenever 
`needMultiValuedColumns` is true, while retaining the range-partition trigger, 
and pass an empty set when analysis finds no actual multi-valued columns.



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to