Jackie-Jiang commented on code in PR #19581:
URL: https://github.com/apache/pinot/pull/19581#discussion_r4067820176


##########
pinot-segment-local/src/main/java/org/apache/pinot/segment/local/segment/index/loader/defaultcolumn/BaseDefaultColumnHandler.java:
##########
@@ -378,65 +419,71 @@ protected void removeColumnIndices(String column) {
 
   /// Helper method to create the V1 indices (dictionary and forward index) 
for a column, returns `true` if the
   /// creation succeeds, `false` otherwise.
+  ///
+  /// A derived column whose argument is itself created in this same run is 
supported: [#updateDefaultColumns] visits
+  /// the columns in dependency order and [#getColumnMetadata] sees the 
metadata written a moment ago. A source
+  /// column with its forward index disabled is supported too, by regenerating 
that index for the duration of the
+  /// run. An argument that is genuinely absent from the segment still falls 
back to the default value.
   protected boolean createColumnV1Indices(String column)
       throws Exception {
     boolean errorOnFailure = _indexLoadingConfig.isErrorOnColumnBuildFailure();
-    IngestionConfig ingestionConfig = _tableConfig.getIngestionConfig();
-    if (ingestionConfig != null && ingestionConfig.getTransformConfigs() != 
null) {
-      List<TransformConfig> transformConfigs = 
ingestionConfig.getTransformConfigs();
-      for (TransformConfig transformConfig : transformConfigs) {
-        if (transformConfig.getColumnName().equals(column)) {
-          String transformFunction = transformConfig.getTransformFunction();
-          FunctionEvaluator functionEvaluator = 
FunctionEvaluatorFactory.getExpressionEvaluator(transformFunction);
-
-          // Check if all arguments exist in the segment
-          // TODO: Support chained derived column
-          List<String> arguments = functionEvaluator.getArguments();
-          List<ColumnMetadata> argumentsMetadata = new 
ArrayList<>(arguments.size());
-          for (String argument : arguments) {
-            ColumnMetadata columnMetadata = 
_segmentMetadata.getColumnMetadataFor(argument);
-            if (columnMetadata == null) {
-              LOGGER.warn("Assigning default value to derived column: {} 
because argument: {} does not exist in the "
-                  + "segment", column, argument);
-              createDefaultValueColumnV1Indices(column);
-              return true;
-            }
-            // TODO: Support creation of derived columns from forward index 
disabled columns
-            if (!_segmentWriter.hasIndexFor(argument, 
StandardIndexes.forward())) {
-              throw new UnsupportedOperationException(String.format("Operation 
not supported! Cannot create a derived "
-                      + "column %s because argument: %s does not have a 
forward index. Enable forward index and "
-                      + "refresh/backfill the segments to create a derived 
column from source column", column,
-                  argument));
-            }
-            argumentsMetadata.add(columnMetadata);
-          }
-
-          // TODO: Support forward index disabled derived column
-          if (isForwardIndexDisabled(column)) {
-            LOGGER.warn("Skip creating forward index disabled derived column: 
{}", column);
-            if (errorOnFailure) {
-              throw new UnsupportedOperationException(
-                  String.format("Failed to create forward index disabled 
derived column: %s", column));
-            }
-            return false;
+    TransformConfig transformConfig = getTransformConfig(column);
+    if (transformConfig != null) {
+      String transformFunction = transformConfig.getTransformFunction();
+      FunctionEvaluator functionEvaluator = getFunctionEvaluator(column, 
transformFunction);
+
+      // Check if all arguments can be read from the segment
+      List<String> arguments = functionEvaluator.getArguments();
+      List<ColumnMetadata> argumentsMetadata = new 
ArrayList<>(arguments.size());
+      for (String argument : arguments) {
+        ColumnMetadata columnMetadata = getColumnMetadata(argument);
+        if (columnMetadata == null) {
+          LOGGER.warn("Assigning default value to derived column: {} because 
argument: {} does not exist in the "
+              + "segment", column, argument);
+          createDefaultValueColumnV1Indices(column);
+          recordCreatedColumnMetadata(column);
+          return true;
+        }
+        // The values are read through the argument's forward index. When the 
argument has it disabled, regenerate
+        // it from the dictionary and inverted index for the duration of this 
run.
+        if (!materializeSourceForwardIndex(argument)) {
+          // Regeneration needs the argument's dictionary and inverted index; 
without them the only way to get the
+          // values back is a refresh or back-fill. This used to be an 
unconditional failure, so keep failing when
+          // the table asks for it, and otherwise degrade the way a missing 
argument already does.
+          if (errorOnFailure) {
+            throw new UnsupportedOperationException(String.format("Operation 
not supported! Cannot create a derived "
+                    + "column %s because argument: %s does not have a forward 
index and it could not be regenerated "
+                    + "from its dictionary and inverted index. Enable forward 
index and refresh/backfill the segments "
+                    + "to create a derived column from source column", column, 
argument));
           }
+          LOGGER.warn("Assigning default value to derived column: {} because 
argument: {} has no forward index and "
+              + "it could not be regenerated", column, argument);
+          createDefaultValueColumnV1Indices(column);
+          recordCreatedColumnMetadata(column);
+          return true;
+        }
+        argumentsMetadata.add(columnMetadata);

Review Comment:
   **[P1] Refresh argument metadata after regenerating its forward index**
   
   `columnMetadata` was obtained before `materializeSourceForwardIndex`, but 
regeneration can change the metadata needed to read the index. For example, 
rebuilding a disabled MV source containing duplicates reduces 
`TOTAL_NUMBER_OF_ENTRIES`; the old count then makes 
`FixedBitMVForwardIndexReader` calculate incorrect buffer offsets. A supported 
reload that enables a formerly disabled source with RAW encoding similarly 
opens the raw bytes with the old DICTIONARY reader. Have materialization return 
the refreshed column metadata and use it for argument readers, as 
`BaseIndexHandler.createForwardIndexIfNeeded` does.



##########
pinot-segment-local/src/main/java/org/apache/pinot/segment/local/segment/index/loader/defaultcolumn/BaseDefaultColumnHandler.java:
##########
@@ -1219,6 +1266,218 @@ private void putDictionaryCompressionStats(String 
column, long uncompressedValue
     compressionMetadata.applyTo(_segmentProperties, column);
   }
 
+  /// Reorders the actions so that a column is created after every column it 
reads that is also being created in
+  /// this run -- the dependency that makes chained derived columns work, and 
that also lets a derived column read a
+  /// plain default column added in the same reload. Non-derived columns keep 
their original relative order.
+  ///
+  /// A dependency cycle cannot be satisfied in any order, so the columns in 
it are emitted last, unordered: each
+  /// still gets created, falling back to its default value because its 
argument is not in the segment yet. The
+  /// cycle is logged rather than failing the segment load.
+  private Map<String, DefaultColumnAction> orderByDependencies(Map<String, 
DefaultColumnAction> actionMap) {
+    Map<String, List<String>> dependencies = new HashMap<>();
+    for (Map.Entry<String, DefaultColumnAction> entry : actionMap.entrySet()) {
+      if (entry.getValue().isAddAction()) {
+        List<String> arguments = derivedColumnArguments(entry.getKey());
+        if (arguments != null) {
+          List<String> pending = new ArrayList<>(arguments.size());
+          for (String argument : arguments) {
+            // Only columns created in this run constrain the order; anything 
already in the segment is readable.
+            DefaultColumnAction argumentAction = actionMap.get(argument);
+            if (argumentAction != null && argumentAction.isAddAction() && 
!argument.equals(entry.getKey())) {

Review Comment:
   **[P1] Include UPDATE actions in dependency ordering**
   
   A source being updated is not safely readable before its action completes. 
For example, let existing autogenerated INT `q` contain default `1`; change its 
schema default to `2` while adding `r=plus(q,1)` and `p=plus(r,1)`. The action 
map iterates `p,q,r`, but this traversal pulls `r` ahead of `q` because `q` has 
an UPDATE action rather than ADD. It permanently writes `r=2,p=3` instead of 
`r=3,p=4`. Before this change, `q` updated before `r`. Include ADD and UPDATE 
actions when constructing dependency edges, with V1/V3 coverage combining a 
source-default update and a new chain.



##########
pinot-segment-local/src/main/java/org/apache/pinot/segment/local/segment/index/loader/defaultcolumn/BaseDefaultColumnHandler.java:
##########
@@ -1219,6 +1266,218 @@ private void putDictionaryCompressionStats(String 
column, long uncompressedValue
     compressionMetadata.applyTo(_segmentProperties, column);
   }
 
+  /// Reorders the actions so that a column is created after every column it 
reads that is also being created in
+  /// this run -- the dependency that makes chained derived columns work, and 
that also lets a derived column read a
+  /// plain default column added in the same reload. Non-derived columns keep 
their original relative order.
+  ///
+  /// A dependency cycle cannot be satisfied in any order, so the columns in 
it are emitted last, unordered: each
+  /// still gets created, falling back to its default value because its 
argument is not in the segment yet. The
+  /// cycle is logged rather than failing the segment load.
+  private Map<String, DefaultColumnAction> orderByDependencies(Map<String, 
DefaultColumnAction> actionMap) {
+    Map<String, List<String>> dependencies = new HashMap<>();
+    for (Map.Entry<String, DefaultColumnAction> entry : actionMap.entrySet()) {
+      if (entry.getValue().isAddAction()) {
+        List<String> arguments = derivedColumnArguments(entry.getKey());
+        if (arguments != null) {
+          List<String> pending = new ArrayList<>(arguments.size());
+          for (String argument : arguments) {
+            // Only columns created in this run constrain the order; anything 
already in the segment is readable.
+            DefaultColumnAction argumentAction = actionMap.get(argument);
+            if (argumentAction != null && argumentAction.isAddAction() && 
!argument.equals(entry.getKey())) {
+              pending.add(argument);
+            }
+          }
+          if (!pending.isEmpty()) {
+            dependencies.put(entry.getKey(), pending);
+          }
+        }
+      }
+    }
+    if (dependencies.isEmpty()) {
+      return actionMap;
+    }
+
+    Map<String, DefaultColumnAction> ordered = new 
LinkedHashMap<>(actionMap.size());
+    Set<String> visiting = new LinkedHashSet<>();
+    for (String column : actionMap.keySet()) {
+      visitForOrdering(column, actionMap, dependencies, visiting, ordered);
+    }
+    // Columns dropped by a cycle still have to be created; they land last and 
take the default-value path.
+    for (Map.Entry<String, DefaultColumnAction> entry : actionMap.entrySet()) {
+      ordered.putIfAbsent(entry.getKey(), entry.getValue());
+    }
+    return ordered;
+  }
+
+  private void visitForOrdering(String column, Map<String, 
DefaultColumnAction> actionMap,
+      Map<String, List<String>> dependencies, Set<String> visiting, 
Map<String, DefaultColumnAction> ordered) {
+    if (ordered.containsKey(column)) {
+      return;
+    }
+    if (!visiting.add(column)) {
+      LOGGER.warn("Derived columns form a dependency cycle: {}. They will be 
assigned default values instead of "
+          + "being derived; break the cycle in the transform configs.", 
visiting);
+      return;
+    }
+    try {
+      for (String dependency : dependencies.getOrDefault(column, List.of())) {
+        visitForOrdering(dependency, actionMap, dependencies, visiting, 
ordered);
+      }
+      // A dependency stuck in a cycle is absent from `ordered`; emitting this 
column anyway is safe because the
+      // missing argument makes it take the default-value path.
+      ordered.put(column, actionMap.get(column));

Review Comment:
   **[P2] Explicitly apply the fallback to every detected cycle member**
   
   Returning when a node is already in `visiting` does not stop the enclosing 
calls from adding the cycle members here. For `a=plus(b,1), b=plus(a,1)`, the 
traversal emits one first, which defaults, and the other then reads that newly 
created value and evaluates its transform instead of receiving its own default. 
This contradicts the documented fallback and differs from the previous 
behavior. Track cycle members and explicitly default them, or reject the cycle. 
The new cycle test only checks cardinality; asserting each column's actual 
default value would expose this.



##########
pinot-segment-local/src/main/java/org/apache/pinot/segment/local/segment/index/loader/defaultcolumn/BaseDefaultColumnHandler.java:
##########
@@ -378,65 +419,71 @@ protected void removeColumnIndices(String column) {
 
   /// Helper method to create the V1 indices (dictionary and forward index) 
for a column, returns `true` if the
   /// creation succeeds, `false` otherwise.
+  ///
+  /// A derived column whose argument is itself created in this same run is 
supported: [#updateDefaultColumns] visits
+  /// the columns in dependency order and [#getColumnMetadata] sees the 
metadata written a moment ago. A source
+  /// column with its forward index disabled is supported too, by regenerating 
that index for the duration of the
+  /// run. An argument that is genuinely absent from the segment still falls 
back to the default value.
   protected boolean createColumnV1Indices(String column)
       throws Exception {
     boolean errorOnFailure = _indexLoadingConfig.isErrorOnColumnBuildFailure();
-    IngestionConfig ingestionConfig = _tableConfig.getIngestionConfig();
-    if (ingestionConfig != null && ingestionConfig.getTransformConfigs() != 
null) {
-      List<TransformConfig> transformConfigs = 
ingestionConfig.getTransformConfigs();
-      for (TransformConfig transformConfig : transformConfigs) {
-        if (transformConfig.getColumnName().equals(column)) {
-          String transformFunction = transformConfig.getTransformFunction();
-          FunctionEvaluator functionEvaluator = 
FunctionEvaluatorFactory.getExpressionEvaluator(transformFunction);
-
-          // Check if all arguments exist in the segment
-          // TODO: Support chained derived column
-          List<String> arguments = functionEvaluator.getArguments();
-          List<ColumnMetadata> argumentsMetadata = new 
ArrayList<>(arguments.size());
-          for (String argument : arguments) {
-            ColumnMetadata columnMetadata = 
_segmentMetadata.getColumnMetadataFor(argument);
-            if (columnMetadata == null) {
-              LOGGER.warn("Assigning default value to derived column: {} 
because argument: {} does not exist in the "
-                  + "segment", column, argument);
-              createDefaultValueColumnV1Indices(column);
-              return true;
-            }
-            // TODO: Support creation of derived columns from forward index 
disabled columns
-            if (!_segmentWriter.hasIndexFor(argument, 
StandardIndexes.forward())) {
-              throw new UnsupportedOperationException(String.format("Operation 
not supported! Cannot create a derived "
-                      + "column %s because argument: %s does not have a 
forward index. Enable forward index and "
-                      + "refresh/backfill the segments to create a derived 
column from source column", column,
-                  argument));
-            }
-            argumentsMetadata.add(columnMetadata);
-          }
-
-          // TODO: Support forward index disabled derived column
-          if (isForwardIndexDisabled(column)) {
-            LOGGER.warn("Skip creating forward index disabled derived column: 
{}", column);
-            if (errorOnFailure) {
-              throw new UnsupportedOperationException(
-                  String.format("Failed to create forward index disabled 
derived column: %s", column));
-            }
-            return false;
+    TransformConfig transformConfig = getTransformConfig(column);
+    if (transformConfig != null) {
+      String transformFunction = transformConfig.getTransformFunction();
+      FunctionEvaluator functionEvaluator = getFunctionEvaluator(column, 
transformFunction);
+
+      // Check if all arguments can be read from the segment
+      List<String> arguments = functionEvaluator.getArguments();
+      List<ColumnMetadata> argumentsMetadata = new 
ArrayList<>(arguments.size());
+      for (String argument : arguments) {
+        ColumnMetadata columnMetadata = getColumnMetadata(argument);

Review Comment:
   **[P1] Preserve intermediate transform values when evaluating chains**
   
   Reading a newly created dependency from its stored index changes the result 
relative to normal ingestion when the intermediate value needs type conversion. 
For example, with source `a=1`, INT `b=divide(a,2)`, and DOUBLE `c=plus(b,1)`, 
ingestion evaluates both expressions before `DataTypeTransformer`, so it stores 
`b=0,c=1.5`. This reload path first stores/coerces `b`, then reads `0` to 
produce `c=1.0`. Evaluate the newly created chain using intermediate expression 
results before storage conversion, and add a regression comparing ingestion and 
reload for this case.



##########
pinot-segment-local/src/main/java/org/apache/pinot/segment/local/segment/index/loader/defaultcolumn/BaseDefaultColumnHandler.java:
##########
@@ -1219,6 +1266,218 @@ private void putDictionaryCompressionStats(String 
column, long uncompressedValue
     compressionMetadata.applyTo(_segmentProperties, column);
   }
 
+  /// Reorders the actions so that a column is created after every column it 
reads that is also being created in
+  /// this run -- the dependency that makes chained derived columns work, and 
that also lets a derived column read a
+  /// plain default column added in the same reload. Non-derived columns keep 
their original relative order.
+  ///
+  /// A dependency cycle cannot be satisfied in any order, so the columns in 
it are emitted last, unordered: each
+  /// still gets created, falling back to its default value because its 
argument is not in the segment yet. The
+  /// cycle is logged rather than failing the segment load.
+  private Map<String, DefaultColumnAction> orderByDependencies(Map<String, 
DefaultColumnAction> actionMap) {
+    Map<String, List<String>> dependencies = new HashMap<>();
+    for (Map.Entry<String, DefaultColumnAction> entry : actionMap.entrySet()) {
+      if (entry.getValue().isAddAction()) {
+        List<String> arguments = derivedColumnArguments(entry.getKey());
+        if (arguments != null) {
+          List<String> pending = new ArrayList<>(arguments.size());
+          for (String argument : arguments) {
+            // Only columns created in this run constrain the order; anything 
already in the segment is readable.
+            DefaultColumnAction argumentAction = actionMap.get(argument);
+            if (argumentAction != null && argumentAction.isAddAction() && 
!argument.equals(entry.getKey())) {
+              pending.add(argument);
+            }
+          }
+          if (!pending.isEmpty()) {
+            dependencies.put(entry.getKey(), pending);
+          }
+        }
+      }
+    }
+    if (dependencies.isEmpty()) {
+      return actionMap;
+    }
+
+    Map<String, DefaultColumnAction> ordered = new 
LinkedHashMap<>(actionMap.size());
+    Set<String> visiting = new LinkedHashSet<>();
+    for (String column : actionMap.keySet()) {
+      visitForOrdering(column, actionMap, dependencies, visiting, ordered);
+    }
+    // Columns dropped by a cycle still have to be created; they land last and 
take the default-value path.
+    for (Map.Entry<String, DefaultColumnAction> entry : actionMap.entrySet()) {
+      ordered.putIfAbsent(entry.getKey(), entry.getValue());
+    }
+    return ordered;
+  }
+
+  private void visitForOrdering(String column, Map<String, 
DefaultColumnAction> actionMap,
+      Map<String, List<String>> dependencies, Set<String> visiting, 
Map<String, DefaultColumnAction> ordered) {
+    if (ordered.containsKey(column)) {
+      return;
+    }
+    if (!visiting.add(column)) {
+      LOGGER.warn("Derived columns form a dependency cycle: {}. They will be 
assigned default values instead of "
+          + "being derived; break the cycle in the transform configs.", 
visiting);
+      return;
+    }
+    try {
+      for (String dependency : dependencies.getOrDefault(column, List.of())) {
+        visitForOrdering(dependency, actionMap, dependencies, visiting, 
ordered);
+      }
+      // A dependency stuck in a cycle is absent from `ordered`; emitting this 
column anyway is safe because the
+      // missing argument makes it take the default-value path.
+      ordered.put(column, actionMap.get(column));
+    } finally {
+      visiting.remove(column);
+    }
+  }
+
+  /// The arguments of the transform function that derives this column, or 
null when the column is not derived.
+  @Nullable
+  private List<String> derivedColumnArguments(String column) {
+    TransformConfig transformConfig = getTransformConfig(column);
+    if (transformConfig == null) {
+      return null;
+    }
+    try {
+      return getFunctionEvaluator(column, 
transformConfig.getTransformFunction()).getArguments();
+    } catch (Exception e) {
+      // An unparseable transform function fails later in 
createColumnV1Indices with the full context; ordering just
+      // treats the column as dependency-free.
+      return null;
+    }
+  }
+
+  @Nullable
+  private TransformConfig getTransformConfig(String column) {
+    if (_transformConfigsByColumn == null) {
+      buildTransformConfigIndex();
+    }
+    return _transformConfigsByColumn.get(column);
+  }
+
+  /// Indexes the transform configs by the column they produce, and collects 
every column their expressions read.
+  /// An unparseable expression is left out of the argument set; it fails 
later in [#createColumnV1Indices] with the
+  /// transform function in the message.
+  private void buildTransformConfigIndex() {
+    _transformConfigsByColumn = new HashMap<>();
+    _derivedColumnArgumentNames = new HashSet<>();
+    IngestionConfig ingestionConfig = _tableConfig.getIngestionConfig();
+    List<TransformConfig> transformConfigs =
+        ingestionConfig != null ? ingestionConfig.getTransformConfigs() : null;
+    if (transformConfigs == null) {
+      return;
+    }
+    for (TransformConfig transformConfig : transformConfigs) {
+      _transformConfigsByColumn.putIfAbsent(transformConfig.getColumnName(), 
transformConfig);
+      try {
+        _derivedColumnArgumentNames.addAll(
+            getFunctionEvaluator(transformConfig.getColumnName(), 
transformConfig.getTransformFunction())
+                .getArguments());
+      } catch (Exception e) {
+        LOGGER.debug("Could not parse the transform function of column: {} 
while indexing the transform configs",
+            transformConfig.getColumnName(), e);
+      }
+    }
+  }
+
+  /// The parsed expression for a derived column. Cached because ordering the 
columns and deriving them both need it.
+  /// A parse failure propagates and is not cached, so the caller still sees 
it.
+  private FunctionEvaluator getFunctionEvaluator(String column, String 
transformFunction) {
+    return _functionEvaluators.computeIfAbsent(column,
+        k -> 
FunctionEvaluatorFactory.getExpressionEvaluator(transformFunction));
+  }
+
+  /// Metadata for a column, including one created earlier in this same run 
(see [#_createdColumnMetadata]).
+  @Nullable
+  private ColumnMetadata getColumnMetadata(String column) {
+    ColumnMetadata created = _createdColumnMetadata.get(column);
+    return created != null ? created : 
_segmentMetadata.getColumnMetadataFor(column);
+  }
+
+  /// Records the metadata just written for a created column so later columns 
in this run can read it. Only columns
+  /// that a transform function actually reads are read back: a schema 
evolution adding hundreds of plain default
+  /// columns would otherwise re-parse the metadata of every one of them for 
nothing.
+  private void recordCreatedColumnMetadata(String column) {
+    if (_transformConfigsByColumn == null) {
+      buildTransformConfigIndex();
+    }
+    if (!_derivedColumnArgumentNames.contains(column)) {
+      return;
+    }
+    try {
+      _createdColumnMetadata.put(column,
+          ColumnMetadataImpl.fromPropertiesConfiguration(_segmentProperties, 
_segmentMetadata.getTotalDocs(), column));
+    } catch (Exception e) {
+      // Only chained reads need this; a failure here must not fail the column 
that was created successfully.
+      LOGGER.warn("Could not read back the metadata of newly created column: 
{}; a derived column reading it in this "
+          + "same reload will fall back to its default value", column, e);
+    }
+  }
+
+  /// Makes a forward index available for a source column that has it 
disabled, by regenerating it from the
+  /// dictionary and inverted index. The regenerated index is temporary: 
[#removeTemporaryForwardIndexes] drops it
+  /// once every default column is created, so the column keeps the shape its 
config asks for.
+  ///
+  /// Returns false when regeneration is not possible -- no segment directory 
(the metadata-only path), or the
+  /// dictionary / inverted index the rebuild needs is missing. The caller 
then falls back to the default value
+  /// rather than failing the load.
+  private boolean materializeSourceForwardIndex(String column) {
+    if (_segmentWriter.hasIndexFor(column, StandardIndexes.forward())) {
+      return true;
+    }
+    if (_segmentDirectory == null) {
+      return false;
+    }
+    if (!_segmentWriter.hasIndexFor(column, StandardIndexes.dictionary())
+        || !_segmentWriter.hasIndexFor(column, StandardIndexes.inverted())) {
+      LOGGER.warn("Cannot regenerate the forward index of column: {} to derive 
from it: it needs both a dictionary "
+          + "({}) and an inverted index ({})", column,
+          _segmentWriter.hasIndexFor(column, StandardIndexes.dictionary()) ? 
"present" : "missing",
+          _segmentWriter.hasIndexFor(column, StandardIndexes.inverted()) ? 
"present" : "missing");
+      return false;
+    }
+    FieldIndexConfigs fieldIndexConfigs = 
_indexLoadingConfig.getFieldIndexConfig(column);
+    if (fieldIndexConfigs == null) {
+      return false;
+    }
+    try {
+      LOGGER.info("Temporarily regenerating the forward index of 
forward-index-disabled column: {} so a derived "
+          + "column can read it", column);
+      // The creator writes metadata.properties itself 
(SegmentMetadataUtils#updateMetadataProperties). Our own
+      // in-memory copy was loaded before this point and is saved at the end 
of the run, so flush the columns
+      // created so far to disk and re-read afterwards -- otherwise the save 
would discard the creator's writes.
+      if (_segmentProperties != null) {
+        SegmentMetadataUtils.savePropertiesConfiguration(_segmentProperties, 
_segmentMetadata.getIndexDir());
+      }
+      new 
InvertedIndexAndDictionaryBasedForwardIndexCreator(_segmentDirectory, 
_segmentWriter, _tableConfig, column,

Review Comment:
   **[P1] Supply newly created source metadata to the regenerator**
   
   When the same reload adds a plain MV default column with forward index 
disabled and a derived column reading it, the source has dictionary/inverted 
indexes and is visible through `_createdColumnMetadata`. However, this creator 
looks it up through `_segmentDirectory.getSegmentMetadata()` and dereferences 
null in its constructor because that metadata does not contain the new column. 
The catch then makes the dependent column take its default value, or fails 
loading when `errorOnColumnBuildFailure` is enabled. Flushing properties above 
is insufficient: the directory metadata is stale and the segment's column lists 
are only updated after this loop. Pass the current source metadata into 
regeneration, or create a temporary forward index for the new default source.



-- 
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