This is an automated email from the ASF dual-hosted git repository.

gortiz pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/pinot.git


The following commit(s) were added to refs/heads/master by this push:
     new b5c9d2c4d7a Reject duplicate FieldConfig column names during table 
config validation (#18211)
b5c9d2c4d7a is described below

commit b5c9d2c4d7ac07e07332abbbc3922d7fb5501b05
Author: Anshul Singh <[email protected]>
AuthorDate: Mon Apr 20 21:04:58 2026 +0530

    Reject duplicate FieldConfig column names during table config validation 
(#18211)
    
    TableConfigUtils.validateIndexingConfigAndFieldConfigList did not check for
    duplicate column names in fieldConfigList. A config with two FieldConfig
    entries for the same column would pass validation but crash downstream in
    AbstractIndexType.convertToNewFormat (Collectors.toMap throws
    IllegalStateException: Duplicate key).
    
    Fix: add a duplicate column name check in the validation loop.
    Made-with: Cursor
---
 .../segment/local/utils/TableConfigUtils.java      |   2 +
 .../segment/local/utils/TableConfigUtilsTest.java  | 106 +++++++++++++++++++++
 2 files changed, 108 insertions(+)

diff --git 
a/pinot-segment-local/src/main/java/org/apache/pinot/segment/local/utils/TableConfigUtils.java
 
b/pinot-segment-local/src/main/java/org/apache/pinot/segment/local/utils/TableConfigUtils.java
index 0a874cba3a8..9396658bbc9 100644
--- 
a/pinot-segment-local/src/main/java/org/apache/pinot/segment/local/utils/TableConfigUtils.java
+++ 
b/pinot-segment-local/src/main/java/org/apache/pinot/segment/local/utils/TableConfigUtils.java
@@ -1480,8 +1480,10 @@ public final class TableConfigUtils {
     IndexingConfig indexingConfig = tableConfig.getIndexingConfig();
     List<FieldConfig> fieldConfigs = tableConfig.getFieldConfigList();
     if (CollectionUtils.isNotEmpty(fieldConfigs)) {
+      Set<String> seenColumns = new HashSet<>();
       for (FieldConfig fieldConfig : fieldConfigs) {
         String column = fieldConfig.getName();
+        Preconditions.checkState(seenColumns.add(column), "Duplicate 
FieldConfig for column: %s", column);
         Preconditions.checkState(schema.hasColumn(column), "Failed to find 
column: %s in schema", column);
 
         // Validate DELTA / DELTADELTA compression codecs compatibility
diff --git 
a/pinot-segment-local/src/test/java/org/apache/pinot/segment/local/utils/TableConfigUtilsTest.java
 
b/pinot-segment-local/src/test/java/org/apache/pinot/segment/local/utils/TableConfigUtilsTest.java
index 22a4fd04be9..822fca56db5 100644
--- 
a/pinot-segment-local/src/test/java/org/apache/pinot/segment/local/utils/TableConfigUtilsTest.java
+++ 
b/pinot-segment-local/src/test/java/org/apache/pinot/segment/local/utils/TableConfigUtilsTest.java
@@ -1466,6 +1466,112 @@ public class TableConfigUtilsTest {
     }
   }
 
+  @Test
+  public void testValidateFieldConfigDuplicateColumnName() {
+    final Schema schema = new Schema.SchemaBuilder().setSchemaName(TABLE_NAME)
+        .addSingleValueDimension("myCol1", FieldSpec.DataType.STRING)
+        .build();
+    final TableConfig tableConfig = new TableConfigBuilder(TableType.OFFLINE)
+        .setTableName(TABLE_NAME).build();
+
+    final FieldConfig fc1 = new FieldConfig("myCol1", 
FieldConfig.EncodingType.RAW, null, null, null, null, null);
+    final FieldConfig fc2 = new FieldConfig("myCol1", 
FieldConfig.EncodingType.RAW, null, null, null, null, null);
+    tableConfig.setFieldConfigList(Arrays.asList(fc1, fc2));
+
+    try {
+      TableConfigUtils.validate(tableConfig, schema);
+      fail("Should fail for duplicate FieldConfig column name");
+    } catch (Exception e) {
+      assertEquals(e.getMessage(), "Duplicate FieldConfig for column: myCol1");
+    }
+  }
+
+  @Test
+  public void testValidateFieldConfigDuplicateColumnNameDifferentEncoding() {
+    final Schema schema = new Schema.SchemaBuilder().setSchemaName(TABLE_NAME)
+        .addSingleValueDimension("myCol1", FieldSpec.DataType.STRING)
+        .build();
+    final TableConfig tableConfig = new TableConfigBuilder(TableType.OFFLINE)
+        .setTableName(TABLE_NAME).build();
+
+    final FieldConfig fc1 = new FieldConfig("myCol1", 
FieldConfig.EncodingType.RAW, null, null, null, null, null);
+    final FieldConfig fc2 =
+        new FieldConfig("myCol1", FieldConfig.EncodingType.DICTIONARY, null, 
null, null, null, null);
+    tableConfig.setFieldConfigList(Arrays.asList(fc1, fc2));
+
+    try {
+      TableConfigUtils.validate(tableConfig, schema);
+      fail("Should fail for duplicate FieldConfig column name even with 
different encoding");
+    } catch (Exception e) {
+      assertEquals(e.getMessage(), "Duplicate FieldConfig for column: myCol1");
+    }
+  }
+
+  @Test
+  public void testValidateFieldConfigDuplicateColumnNameWithIndexes()
+      throws Exception {
+    final Schema schema = new Schema.SchemaBuilder().setSchemaName(TABLE_NAME)
+        .addSingleValueDimension("myCol1", FieldSpec.DataType.STRING)
+        .build();
+    final TableConfig tableConfig = new TableConfigBuilder(TableType.OFFLINE)
+        .setTableName(TABLE_NAME).build();
+
+    final ObjectNode indexes = JsonNodeFactory.instance.objectNode();
+    indexes.set("forward", 
JsonNodeFactory.instance.objectNode().put("compressionCodec", "ZSTANDARD"));
+    final FieldConfig fc1 = new FieldConfig.Builder("myCol1")
+        .withEncodingType(FieldConfig.EncodingType.RAW)
+        .withIndexes(indexes)
+        .build();
+    final FieldConfig fc2 = new FieldConfig("myCol1", 
FieldConfig.EncodingType.RAW, null, null, null, null, null);
+    tableConfig.setFieldConfigList(Arrays.asList(fc1, fc2));
+
+    try {
+      TableConfigUtils.validate(tableConfig, schema);
+      fail("Should fail for duplicate FieldConfig column name");
+    } catch (Exception e) {
+      assertEquals(e.getMessage(), "Duplicate FieldConfig for column: myCol1");
+    }
+  }
+
+  @Test
+  public void testValidateFieldConfigNoDuplicates() {
+    final Schema schema = new Schema.SchemaBuilder().setSchemaName(TABLE_NAME)
+        .addSingleValueDimension("myCol1", FieldSpec.DataType.STRING)
+        .addSingleValueDimension("myCol2", FieldSpec.DataType.INT)
+        .build();
+    final TableConfig tableConfig = new TableConfigBuilder(TableType.OFFLINE)
+        .setTableName(TABLE_NAME).build();
+
+    final FieldConfig fc1 = new FieldConfig("myCol1", 
FieldConfig.EncodingType.RAW, null, null, null, null, null);
+    final FieldConfig fc2 =
+        new FieldConfig("myCol2", FieldConfig.EncodingType.DICTIONARY, null, 
null, null, null, null);
+    tableConfig.setFieldConfigList(Arrays.asList(fc1, fc2));
+
+    try {
+      TableConfigUtils.validate(tableConfig, schema);
+    } catch (Exception e) {
+      fail("Should not fail for distinct column names", e);
+    }
+  }
+
+  @Test
+  public void testValidateFieldConfigSingleEntry() {
+    final Schema schema = new Schema.SchemaBuilder().setSchemaName(TABLE_NAME)
+        .addSingleValueDimension("myCol1", FieldSpec.DataType.STRING)
+        .build();
+    final TableConfig tableConfig = new TableConfigBuilder(TableType.OFFLINE)
+        .setTableName(TABLE_NAME).build();
+
+    final FieldConfig fc1 = new FieldConfig("myCol1", 
FieldConfig.EncodingType.RAW, null, null, null, null, null);
+    tableConfig.setFieldConfigList(Arrays.asList(fc1));
+
+    try {
+      TableConfigUtils.validate(tableConfig, schema);
+    } catch (Exception e) {
+      fail("Should not fail for single FieldConfig entry", e);
+    }
+  }
+
   @Test
   public void testValidateBFOnBoolean() {
     Schema schema = new Schema.SchemaBuilder().setSchemaName(TABLE_NAME)


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

Reply via email to