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

manishswaminathan 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 7e4bc52158a UCT: Add getValidDocIdsType method for UpsertConfig 
handling and validation (#16812)
7e4bc52158a is described below

commit 7e4bc52158ad0b9695c4c70418280fbf5e89bd05
Author: Abhishek Bafna <[email protected]>
AuthorDate: Wed Sep 17 14:01:56 2025 +0530

    UCT: Add getValidDocIdsType method for UpsertConfig handling and validation 
(#16812)
    
    * UCT: Add getValidDocIdsType method for UpsertConfig handling and 
validation
    
    * addressing review comments
    
    * Fix lint issue
    
    * update integration test
    
    ---------
    
    Co-authored-by: abhishekbafna <[email protected]>
---
 .../tests/UpsertTableIntegrationTest.java          |   2 +-
 .../pinot/plugin/minion/tasks/MinionTaskUtils.java |  48 +++++++
 .../UpsertCompactionTaskExecutor.java              |   5 +-
 .../UpsertCompactionTaskGenerator.java             |  35 ++---
 .../plugin/minion/tasks/MinionTaskUtilsTest.java   | 158 ++++++++++++++++++++-
 .../UpsertCompactionTaskGeneratorTest.java         |  83 +++++++++++
 6 files changed, 303 insertions(+), 28 deletions(-)

diff --git 
a/pinot-integration-tests/src/test/java/org/apache/pinot/integration/tests/UpsertTableIntegrationTest.java
 
b/pinot-integration-tests/src/test/java/org/apache/pinot/integration/tests/UpsertTableIntegrationTest.java
index 40f57e2583c..8c01f6601d5 100644
--- 
a/pinot-integration-tests/src/test/java/org/apache/pinot/integration/tests/UpsertTableIntegrationTest.java
+++ 
b/pinot-integration-tests/src/test/java/org/apache/pinot/integration/tests/UpsertTableIntegrationTest.java
@@ -491,7 +491,7 @@ public class UpsertTableIntegrationTest extends 
BaseClusterIntegrationTest {
     TableConfig tableConfig = setUpTable(tableName, kafkaTopicName, 
upsertConfig);
     TableTaskConfig taskConfig = getCompactionTaskConfig();
     
taskConfig.getConfigsForTaskType(MinionConstants.UpsertCompactionTask.TASK_TYPE)
-        .put("validDocIdsType", ValidDocIdsType.IN_MEMORY.name());
+        .put("validDocIdsType", ValidDocIdsType.IN_MEMORY_WITH_DELETE.name());
     tableConfig.setTaskConfig(taskConfig);
     updateTableConfig(tableConfig);
 
diff --git 
a/pinot-plugins/pinot-minion-tasks/pinot-minion-builtin-tasks/src/main/java/org/apache/pinot/plugin/minion/tasks/MinionTaskUtils.java
 
b/pinot-plugins/pinot-minion-tasks/pinot-minion-builtin-tasks/src/main/java/org/apache/pinot/plugin/minion/tasks/MinionTaskUtils.java
index a8be2803544..91914dc802b 100644
--- 
a/pinot-plugins/pinot-minion-tasks/pinot-minion-builtin-tasks/src/main/java/org/apache/pinot/plugin/minion/tasks/MinionTaskUtils.java
+++ 
b/pinot-plugins/pinot-minion-tasks/pinot-minion-builtin-tasks/src/main/java/org/apache/pinot/plugin/minion/tasks/MinionTaskUtils.java
@@ -18,6 +18,7 @@
  */
 package org.apache.pinot.plugin.minion.tasks;
 
+import com.google.common.base.Preconditions;
 import java.net.URI;
 import java.text.SimpleDateFormat;
 import java.time.Instant;
@@ -28,10 +29,12 @@ import java.util.List;
 import java.util.Map;
 import java.util.TimeZone;
 import javax.annotation.Nullable;
+import org.apache.commons.lang3.StringUtils;
 import org.apache.helix.HelixAdmin;
 import org.apache.helix.model.ExternalView;
 import org.apache.helix.model.InstanceConfig;
 import org.apache.pinot.common.restlet.resources.ValidDocIdsBitmapResponse;
+import org.apache.pinot.common.restlet.resources.ValidDocIdsType;
 import org.apache.pinot.common.utils.RoaringBitmapUtils;
 import org.apache.pinot.common.utils.ServiceStatus;
 import org.apache.pinot.common.utils.config.InstanceUtils;
@@ -40,6 +43,7 @@ import 
org.apache.pinot.controller.util.ServerSegmentMetadataReader;
 import org.apache.pinot.minion.MinionContext;
 import org.apache.pinot.spi.config.table.TableConfig;
 import org.apache.pinot.spi.config.table.TableTaskConfig;
+import org.apache.pinot.spi.config.table.UpsertConfig;
 import org.apache.pinot.spi.env.PinotConfiguration;
 import org.apache.pinot.spi.filesystem.LocalPinotFS;
 import org.apache.pinot.spi.filesystem.PinotFS;
@@ -47,6 +51,7 @@ import org.apache.pinot.spi.filesystem.PinotFSFactory;
 import org.apache.pinot.spi.ingestion.batch.BatchConfigProperties;
 import org.apache.pinot.spi.plugin.PluginManager;
 import org.apache.pinot.spi.utils.CommonConstants;
+import org.apache.pinot.spi.utils.Enablement;
 import org.apache.pinot.spi.utils.IngestionConfigUtils;
 import org.apache.pinot.spi.utils.builder.TableNameBuilder;
 import org.roaringbitmap.RoaringBitmap;
@@ -265,4 +270,47 @@ public class MinionTaskUtils {
   public static long fromUTCString(String utcString) {
     return Instant.parse(utcString).toEpochMilli();
   }
+
+  /**
+   * Get the validDocIdsType based on the upsertConfig and taskConfigs.
+   * The default value is determined by whether delete is enabled in the 
upsertConfig. If delete is enabled,
+   * the default value is 'snapshot_with_delete', otherwise it is 'snapshot'.
+   * If delete is enabled, we override the user-specified value to 
'snapshot_with_delete' for backward compatibility
+   * except when it is 'in_memory_with_delete'.
+   * It also validates the combination of validDocIdsType, snapshot and 
deleteRecordColumn.
+   * @param upsertConfig upsertConfig of the table
+   * @param taskConfigs taskConfigs of the task
+   * @param validDocIdsTypeKey the key to get validDocIdsType from taskConfigs
+   * @return the validDocIdsType
+   */
+  public static ValidDocIdsType getValidDocIdsType(UpsertConfig upsertConfig, 
Map<String, String> taskConfigs,
+      String validDocIdsTypeKey) {
+    boolean isDeleteEnabled = 
StringUtils.isNotEmpty(upsertConfig.getDeleteRecordColumn());
+    ValidDocIdsType defaultValidDocIdsType =
+        isDeleteEnabled ? ValidDocIdsType.SNAPSHOT_WITH_DELETE : 
ValidDocIdsType.SNAPSHOT;
+    String validDocIdsTypeStr = taskConfigs.getOrDefault(validDocIdsTypeKey,
+        defaultValidDocIdsType.name()).toUpperCase();
+    ValidDocIdsType validDocIdsType = 
ValidDocIdsType.valueOf(validDocIdsTypeStr);
+
+    if (isDeleteEnabled && validDocIdsType != 
ValidDocIdsType.SNAPSHOT_WITH_DELETE
+        && validDocIdsType != ValidDocIdsType.IN_MEMORY_WITH_DELETE) {
+      LOGGER.warn(
+          "Overriding user-specified validDocIdsType '{}' to '{}' for backward 
compatibility because delete is "
+              + "enabled (deleteRecordColumn='{}').",
+          validDocIdsType, ValidDocIdsType.SNAPSHOT_WITH_DELETE, 
upsertConfig.getDeleteRecordColumn());
+      validDocIdsType = ValidDocIdsType.SNAPSHOT_WITH_DELETE;
+    }
+
+    if (validDocIdsType == ValidDocIdsType.SNAPSHOT || validDocIdsType == 
ValidDocIdsType.SNAPSHOT_WITH_DELETE) {
+      Preconditions.checkState(upsertConfig.getSnapshot() != 
Enablement.DISABLE,
+          "'snapshot' must not be 'DISABLE' with validDocIdsType: %s", 
validDocIdsType);
+    }
+
+    if (validDocIdsType == ValidDocIdsType.IN_MEMORY_WITH_DELETE
+        || validDocIdsType == ValidDocIdsType.SNAPSHOT_WITH_DELETE) {
+      Preconditions.checkState(isDeleteEnabled,
+          "'deleteRecordColumn' must be provided with validDocIdsType: %s", 
validDocIdsType);
+    }
+    return validDocIdsType;
+  }
 }
diff --git 
a/pinot-plugins/pinot-minion-tasks/pinot-minion-builtin-tasks/src/main/java/org/apache/pinot/plugin/minion/tasks/upsertcompaction/UpsertCompactionTaskExecutor.java
 
b/pinot-plugins/pinot-minion-tasks/pinot-minion-builtin-tasks/src/main/java/org/apache/pinot/plugin/minion/tasks/upsertcompaction/UpsertCompactionTaskExecutor.java
index 1766d1c6646..490387f727d 100644
--- 
a/pinot-plugins/pinot-minion-tasks/pinot-minion-builtin-tasks/src/main/java/org/apache/pinot/plugin/minion/tasks/upsertcompaction/UpsertCompactionTaskExecutor.java
+++ 
b/pinot-plugins/pinot-minion-tasks/pinot-minion-builtin-tasks/src/main/java/org/apache/pinot/plugin/minion/tasks/upsertcompaction/UpsertCompactionTaskExecutor.java
@@ -24,7 +24,6 @@ import java.util.Map;
 import org.apache.commons.io.FileUtils;
 import 
org.apache.pinot.common.metadata.segment.SegmentZKMetadataCustomMapModifier;
 import org.apache.pinot.common.metrics.MinionMeter;
-import org.apache.pinot.common.restlet.resources.ValidDocIdsType;
 import org.apache.pinot.core.common.MinionConstants;
 import org.apache.pinot.core.common.MinionConstants.UpsertCompactionTask;
 import org.apache.pinot.core.minion.PinotTaskConfig;
@@ -61,8 +60,8 @@ public class UpsertCompactionTaskExecutor extends 
BaseSingleSegmentConversionExe
     String tableNameWithType = configs.get(MinionConstants.TABLE_NAME_KEY);
     TableConfig tableConfig = getTableConfig(tableNameWithType);
 
-    String validDocIdsTypeStr =
-        configs.getOrDefault(UpsertCompactionTask.VALID_DOC_IDS_TYPE, 
ValidDocIdsType.SNAPSHOT.name());
+    String validDocIdsTypeStr = 
MinionTaskUtils.getValidDocIdsType(tableConfig.getUpsertConfig(), configs,
+        UpsertCompactionTask.VALID_DOC_IDS_TYPE).toString();
     SegmentMetadataImpl segmentMetadata = new SegmentMetadataImpl(indexDir);
     String originalSegmentCrcFromTaskGenerator = 
configs.get(MinionConstants.ORIGINAL_SEGMENT_CRC_KEY);
     String crcFromDeepStorageSegment = segmentMetadata.getCrc();
diff --git 
a/pinot-plugins/pinot-minion-tasks/pinot-minion-builtin-tasks/src/main/java/org/apache/pinot/plugin/minion/tasks/upsertcompaction/UpsertCompactionTaskGenerator.java
 
b/pinot-plugins/pinot-minion-tasks/pinot-minion-builtin-tasks/src/main/java/org/apache/pinot/plugin/minion/tasks/upsertcompaction/UpsertCompactionTaskGenerator.java
index 79baaef7bf4..27632ca182d 100644
--- 
a/pinot-plugins/pinot-minion-tasks/pinot-minion-builtin-tasks/src/main/java/org/apache/pinot/plugin/minion/tasks/upsertcompaction/UpsertCompactionTaskGenerator.java
+++ 
b/pinot-plugins/pinot-minion-tasks/pinot-minion-builtin-tasks/src/main/java/org/apache/pinot/plugin/minion/tasks/upsertcompaction/UpsertCompactionTaskGenerator.java
@@ -42,13 +42,13 @@ import 
org.apache.pinot.controller.util.ServerSegmentMetadataReader;
 import org.apache.pinot.core.common.MinionConstants;
 import org.apache.pinot.core.common.MinionConstants.UpsertCompactionTask;
 import org.apache.pinot.core.minion.PinotTaskConfig;
+import org.apache.pinot.plugin.minion.tasks.MinionTaskUtils;
 import org.apache.pinot.spi.annotations.minion.TaskGenerator;
 import org.apache.pinot.spi.config.table.TableConfig;
 import org.apache.pinot.spi.config.table.TableType;
 import org.apache.pinot.spi.config.table.UpsertConfig;
 import org.apache.pinot.spi.data.Schema;
 import org.apache.pinot.spi.utils.CommonConstants;
-import org.apache.pinot.spi.utils.Enablement;
 import org.apache.pinot.spi.utils.TimeUtils;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -102,6 +102,11 @@ public class UpsertCompactionTaskGenerator extends 
BaseTaskGenerator {
         continue;
       }
 
+      if (!tableConfig.isUpsertEnabled()) {
+        LOGGER.warn("Upsert config is not enabled for table: {}", 
tableNameWithType);
+        continue;
+      }
+
       Map<String, String> taskConfigs = 
tableConfig.getTaskConfig().getConfigsForTaskType(taskType);
       List<SegmentZKMetadata> allSegments = 
_clusterInfoAccessor.getSegmentsZKMetadata(tableNameWithType);
 
@@ -138,11 +143,8 @@ public class UpsertCompactionTaskGenerator extends 
BaseTaskGenerator {
           new ServerSegmentMetadataReader(_clusterInfoAccessor.getExecutor(),
               _clusterInfoAccessor.getConnectionManager());
 
-      // By default, we use 'snapshot' for validDocIdsType. This means that we 
will use the validDocIds bitmap from
-      // the snapshot from Pinot segment. This will require 'enableSnapshot' 
from UpsertConfig to be set to true.
-      String validDocIdsTypeStr =
-          taskConfigs.getOrDefault(UpsertCompactionTask.VALID_DOC_IDS_TYPE, 
ValidDocIdsType.SNAPSHOT.toString());
-      ValidDocIdsType validDocIdsType = 
ValidDocIdsType.valueOf(validDocIdsTypeStr.toUpperCase());
+      ValidDocIdsType validDocIdsType = 
MinionTaskUtils.getValidDocIdsType(tableConfig.getUpsertConfig(), taskConfigs,
+          UpsertCompactionTask.VALID_DOC_IDS_TYPE);
 
       // Number of segments to query per server request. If a table has a lot 
of segments, then we might send a
       // huge payload to pinot-server in request. Batching the requests will 
help in reducing the payload size.
@@ -317,21 +319,10 @@ public class UpsertCompactionTaskGenerator extends 
BaseTaskGenerator {
         
taskConfigs.containsKey(UpsertCompactionTask.INVALID_RECORDS_THRESHOLD_PERCENT) 
|| taskConfigs.containsKey(
             UpsertCompactionTask.INVALID_RECORDS_THRESHOLD_COUNT),
         "invalidRecordsThresholdPercent or invalidRecordsThresholdCount or 
both must be provided");
-    String validDocIdsType =
-        taskConfigs.getOrDefault(UpsertCompactionTask.VALID_DOC_IDS_TYPE, 
UpsertCompactionTask.SNAPSHOT);
-    if (validDocIdsType.equals(ValidDocIdsType.SNAPSHOT.toString())) {
-      UpsertConfig upsertConfig = tableConfig.getUpsertConfig();
-      assert upsertConfig != null;
-      // NOTE: Allow snapshot to be DEFAULT because it might be enabled at 
server level.
-      Preconditions.checkState(upsertConfig.getSnapshot() != 
Enablement.DISABLE,
-          "'snapshot' from UpsertConfig must not be 'DISABLE' for 
UpsertCompactionTask with validDocIdsType = %s",
-          validDocIdsType);
-    } else if 
(validDocIdsType.equals(ValidDocIdsType.IN_MEMORY_WITH_DELETE.toString())) {
-      UpsertConfig upsertConfig = tableConfig.getUpsertConfig();
-      assert upsertConfig != null;
-      Preconditions.checkNotNull(upsertConfig.getDeleteRecordColumn(), 
String.format(
-          "deleteRecordColumn must be provided for " + "UpsertCompactionTask 
with validDocIdsType = %s",
-          validDocIdsType));
-    }
+
+    // validate validDocIdsType default logic
+    UpsertConfig upsertConfig = tableConfig.getUpsertConfig();
+    assert upsertConfig != null;
+    MinionTaskUtils.getValidDocIdsType(upsertConfig, taskConfigs, 
UpsertCompactionTask.VALID_DOC_IDS_TYPE);
   }
 }
diff --git 
a/pinot-plugins/pinot-minion-tasks/pinot-minion-builtin-tasks/src/test/java/org/apache/pinot/plugin/minion/tasks/MinionTaskUtilsTest.java
 
b/pinot-plugins/pinot-minion-tasks/pinot-minion-builtin-tasks/src/test/java/org/apache/pinot/plugin/minion/tasks/MinionTaskUtilsTest.java
index c2456a9957f..a5ff73f8063 100644
--- 
a/pinot-plugins/pinot-minion-tasks/pinot-minion-builtin-tasks/src/test/java/org/apache/pinot/plugin/minion/tasks/MinionTaskUtilsTest.java
+++ 
b/pinot-plugins/pinot-minion-tasks/pinot-minion-builtin-tasks/src/test/java/org/apache/pinot/plugin/minion/tasks/MinionTaskUtilsTest.java
@@ -22,24 +22,30 @@ import java.net.URI;
 import java.util.Collections;
 import java.util.HashMap;
 import java.util.Map;
+import org.apache.pinot.common.restlet.resources.ValidDocIdsType;
 import org.apache.pinot.core.common.MinionConstants;
+import org.apache.pinot.core.common.MinionConstants.UpsertCompactionTask;
 import org.apache.pinot.spi.config.table.TableConfig;
 import org.apache.pinot.spi.config.table.TableTaskConfig;
 import org.apache.pinot.spi.config.table.TableType;
+import org.apache.pinot.spi.config.table.UpsertConfig;
 import org.apache.pinot.spi.filesystem.LocalPinotFS;
 import org.apache.pinot.spi.filesystem.PinotFS;
+import org.apache.pinot.spi.utils.Enablement;
 import org.apache.pinot.spi.utils.builder.TableConfigBuilder;
 import org.testng.annotations.Test;
 
 import static org.testng.Assert.assertEquals;
 import static org.testng.Assert.assertFalse;
 import static org.testng.Assert.assertTrue;
+import static org.testng.Assert.expectThrows;
 
 
 public class MinionTaskUtilsTest {
 
   @Test
-  public void testGetInputPinotFS() throws Exception {
+  public void testGetInputPinotFS()
+      throws Exception {
     Map<String, String> taskConfigs = new HashMap<>();
     taskConfigs.put("input.fs.className", 
"org.apache.pinot.spi.filesystem.LocalPinotFS");
     URI fileURI = new URI("file:///path/to/file");
@@ -50,7 +56,8 @@ public class MinionTaskUtilsTest {
   }
 
   @Test
-  public void testGetOutputPinotFS() throws Exception {
+  public void testGetOutputPinotFS()
+      throws Exception {
     Map<String, String> taskConfigs = new HashMap<>();
     taskConfigs.put("output.fs.className", 
"org.apache.pinot.spi.filesystem.LocalPinotFS");
     URI fileURI = new URI("file:///path/to/file");
@@ -105,4 +112,151 @@ public class MinionTaskUtilsTest {
     
assertFalse(MinionTaskUtils.extractMinionAllowDownloadFromServer(tableConfig,
         MinionConstants.MergeRollupTask.TASK_TYPE, false));
   }
+
+  @Test
+  public void testGetValidDocIdsTypeDefaultBehavior() {
+    // Test default behavior scenarios
+    UpsertConfig upsertConfig = new UpsertConfig();
+    Map<String, String> taskConfigs = new HashMap<>();
+
+    // Test 1: Default when delete is not enabled
+    ValidDocIdsType result1 =
+        MinionTaskUtils.getValidDocIdsType(upsertConfig, taskConfigs, 
UpsertCompactionTask.VALID_DOC_IDS_TYPE);
+    assertEquals(result1, ValidDocIdsType.SNAPSHOT);
+
+    // Test 2: Default when delete is enabled
+    upsertConfig.setDeleteRecordColumn("deleted");
+    ValidDocIdsType result2 =
+        MinionTaskUtils.getValidDocIdsType(upsertConfig, taskConfigs, 
UpsertCompactionTask.VALID_DOC_IDS_TYPE);
+    assertEquals(result2, ValidDocIdsType.SNAPSHOT_WITH_DELETE);
+  }
+
+  @Test
+  public void testGetValidDocIdsTypeExplicitConfigurations() {
+    // Test explicit configuration scenarios
+    UpsertConfig upsertConfig = new UpsertConfig();
+    Map<String, String> taskConfigs = new HashMap<>();
+
+    // Test 1: Explicit SNAPSHOT with enabled snapshot
+    upsertConfig.setSnapshot(Enablement.ENABLE);
+    taskConfigs.put(UpsertCompactionTask.VALID_DOC_IDS_TYPE, "SNAPSHOT");
+    ValidDocIdsType result1 =
+        MinionTaskUtils.getValidDocIdsType(upsertConfig, taskConfigs, 
UpsertCompactionTask.VALID_DOC_IDS_TYPE);
+    assertEquals(result1, ValidDocIdsType.SNAPSHOT);
+
+    // Test 2: Explicit SNAPSHOT with default snapshot enablement
+    upsertConfig = new UpsertConfig(); // Reset to default
+    taskConfigs.put(UpsertCompactionTask.VALID_DOC_IDS_TYPE, "SNAPSHOT");
+    ValidDocIdsType result2 =
+        MinionTaskUtils.getValidDocIdsType(upsertConfig, taskConfigs, 
UpsertCompactionTask.VALID_DOC_IDS_TYPE);
+    assertEquals(result2, ValidDocIdsType.SNAPSHOT);
+
+    // Test 3: Explicit IN_MEMORY without delete
+    taskConfigs.put(UpsertCompactionTask.VALID_DOC_IDS_TYPE, "IN_MEMORY");
+    ValidDocIdsType result3 =
+        MinionTaskUtils.getValidDocIdsType(upsertConfig, taskConfigs, 
UpsertCompactionTask.VALID_DOC_IDS_TYPE);
+    assertEquals(result3, ValidDocIdsType.IN_MEMORY);
+
+    // Test 4: Explicit IN_MEMORY_WITH_DELETE with delete column
+    upsertConfig.setDeleteRecordColumn("deleted");
+    taskConfigs.put(UpsertCompactionTask.VALID_DOC_IDS_TYPE, 
"IN_MEMORY_WITH_DELETE");
+    ValidDocIdsType result4 =
+        MinionTaskUtils.getValidDocIdsType(upsertConfig, taskConfigs, 
UpsertCompactionTask.VALID_DOC_IDS_TYPE);
+    assertEquals(result4, ValidDocIdsType.IN_MEMORY_WITH_DELETE);
+
+    // Test 5: Explicit SNAPSHOT_WITH_DELETE with delete column
+    taskConfigs.put(UpsertCompactionTask.VALID_DOC_IDS_TYPE, 
"SNAPSHOT_WITH_DELETE");
+    ValidDocIdsType result5 =
+        MinionTaskUtils.getValidDocIdsType(upsertConfig, taskConfigs, 
UpsertCompactionTask.VALID_DOC_IDS_TYPE);
+    assertEquals(result5, ValidDocIdsType.SNAPSHOT_WITH_DELETE);
+  }
+
+  @Test
+  public void testGetValidDocIdsTypeBackwardCompatibilityAndOverride() {
+    // Test backward compatibility behavior when delete is enabled
+    UpsertConfig upsertConfig = new UpsertConfig();
+    upsertConfig.setDeleteRecordColumn("deleted");
+    Map<String, String> taskConfigs = new HashMap<>();
+
+    // Test 1: SNAPSHOT gets overridden to SNAPSHOT_WITH_DELETE
+    taskConfigs.put(UpsertCompactionTask.VALID_DOC_IDS_TYPE, "SNAPSHOT");
+    ValidDocIdsType result1 =
+        MinionTaskUtils.getValidDocIdsType(upsertConfig, taskConfigs, 
UpsertCompactionTask.VALID_DOC_IDS_TYPE);
+    assertEquals(result1, ValidDocIdsType.SNAPSHOT_WITH_DELETE);
+
+    // Test 2: IN_MEMORY gets overridden to SNAPSHOT_WITH_DELETE
+    taskConfigs.put(UpsertCompactionTask.VALID_DOC_IDS_TYPE, "IN_MEMORY");
+    ValidDocIdsType result2 =
+        MinionTaskUtils.getValidDocIdsType(upsertConfig, taskConfigs, 
UpsertCompactionTask.VALID_DOC_IDS_TYPE);
+    assertEquals(result2, ValidDocIdsType.SNAPSHOT_WITH_DELETE);
+
+    // Test 3: SNAPSHOT_WITH_DELETE stays the same
+    taskConfigs.put(UpsertCompactionTask.VALID_DOC_IDS_TYPE, 
"SNAPSHOT_WITH_DELETE");
+    ValidDocIdsType result3 =
+        MinionTaskUtils.getValidDocIdsType(upsertConfig, taskConfigs, 
UpsertCompactionTask.VALID_DOC_IDS_TYPE);
+    assertEquals(result3, ValidDocIdsType.SNAPSHOT_WITH_DELETE);
+
+    // Test 4: IN_MEMORY_WITH_DELETE stays the same (not overridden)
+    taskConfigs.put(UpsertCompactionTask.VALID_DOC_IDS_TYPE, 
"IN_MEMORY_WITH_DELETE");
+    ValidDocIdsType result4 =
+        MinionTaskUtils.getValidDocIdsType(upsertConfig, taskConfigs, 
UpsertCompactionTask.VALID_DOC_IDS_TYPE);
+    assertEquals(result4, ValidDocIdsType.IN_MEMORY_WITH_DELETE);
+
+    // Test 5: Case insensitive override behavior
+    taskConfigs.put(UpsertCompactionTask.VALID_DOC_IDS_TYPE, 
"in_memory_with_delete");
+    ValidDocIdsType result5 =
+        MinionTaskUtils.getValidDocIdsType(upsertConfig, taskConfigs, 
UpsertCompactionTask.VALID_DOC_IDS_TYPE);
+    assertEquals(result5, ValidDocIdsType.IN_MEMORY_WITH_DELETE);
+  }
+
+  @Test
+  public void testGetValidDocIdsTypeValidationErrors() {
+    // Test validation error scenarios
+
+    // Test 1: SNAPSHOT with disabled snapshot
+    UpsertConfig upsertConfig1 = new UpsertConfig();
+    upsertConfig1.setSnapshot(Enablement.DISABLE);
+    Map<String, String> taskConfigs1 = new HashMap<>();
+    taskConfigs1.put(UpsertCompactionTask.VALID_DOC_IDS_TYPE, "SNAPSHOT");
+    IllegalStateException exception1 = expectThrows(
+        "Expected IllegalStateException when using SNAPSHOT with snapshot 
disabled",
+        IllegalStateException.class,
+        () -> MinionTaskUtils.getValidDocIdsType(upsertConfig1, taskConfigs1, 
UpsertCompactionTask.VALID_DOC_IDS_TYPE));
+    assertEquals(exception1.getMessage(), "'snapshot' must not be 'DISABLE' 
with validDocIdsType: SNAPSHOT");
+
+    // Test 2: IN_MEMORY_WITH_DELETE without delete column
+    UpsertConfig upsertConfig2 = new UpsertConfig();
+    Map<String, String> taskConfigs2 = new HashMap<>();
+    taskConfigs2.put(UpsertCompactionTask.VALID_DOC_IDS_TYPE, 
"IN_MEMORY_WITH_DELETE");
+    IllegalStateException exception2 = expectThrows(
+        "Expected IllegalStateException when using IN_MEMORY_WITH_DELETE 
without delete column",
+        IllegalStateException.class,
+        () -> MinionTaskUtils.getValidDocIdsType(upsertConfig2, taskConfigs2, 
UpsertCompactionTask.VALID_DOC_IDS_TYPE));
+    assertEquals(exception2.getMessage(),
+        "'deleteRecordColumn' must be provided with validDocIdsType: 
IN_MEMORY_WITH_DELETE");
+
+    // Test 3: SNAPSHOT_WITH_DELETE without delete column
+    UpsertConfig upsertConfig3 = new UpsertConfig();
+    Map<String, String> taskConfigs3 = new HashMap<>();
+    taskConfigs3.put(UpsertCompactionTask.VALID_DOC_IDS_TYPE, 
"SNAPSHOT_WITH_DELETE");
+    IllegalStateException exception3 = expectThrows(
+        "Expected IllegalStateException when using SNAPSHOT_WITH_DELETE 
without delete column",
+        IllegalStateException.class,
+        () -> MinionTaskUtils.getValidDocIdsType(upsertConfig3, taskConfigs3, 
UpsertCompactionTask.VALID_DOC_IDS_TYPE));
+    assertEquals(exception3.getMessage(),
+        "'deleteRecordColumn' must be provided with validDocIdsType: 
SNAPSHOT_WITH_DELETE");
+
+    // Test 4: SNAPSHOT_WITH_DELETE with disabled snapshot
+    UpsertConfig upsertConfig4 = new UpsertConfig();
+    upsertConfig4.setDeleteRecordColumn("deleted");
+    upsertConfig4.setSnapshot(Enablement.DISABLE);
+    Map<String, String> taskConfigs4 = new HashMap<>();
+    taskConfigs4.put(UpsertCompactionTask.VALID_DOC_IDS_TYPE, 
"SNAPSHOT_WITH_DELETE");
+    IllegalStateException exception4 = expectThrows(
+        "Expected IllegalStateException when using SNAPSHOT_WITH_DELETE with 
snapshot disabled",
+        IllegalStateException.class,
+        () -> MinionTaskUtils.getValidDocIdsType(upsertConfig4, taskConfigs4, 
UpsertCompactionTask.VALID_DOC_IDS_TYPE));
+    assertEquals(exception4.getMessage(),
+        "'snapshot' must not be 'DISABLE' with validDocIdsType: 
SNAPSHOT_WITH_DELETE");
+  }
 }
diff --git 
a/pinot-plugins/pinot-minion-tasks/pinot-minion-builtin-tasks/src/test/java/org/apache/pinot/plugin/minion/tasks/upsertcompaction/UpsertCompactionTaskGeneratorTest.java
 
b/pinot-plugins/pinot-minion-tasks/pinot-minion-builtin-tasks/src/test/java/org/apache/pinot/plugin/minion/tasks/upsertcompaction/UpsertCompactionTaskGeneratorTest.java
index e94dae11fc1..8879a8e4399 100644
--- 
a/pinot-plugins/pinot-minion-tasks/pinot-minion-builtin-tasks/src/test/java/org/apache/pinot/plugin/minion/tasks/upsertcompaction/UpsertCompactionTaskGeneratorTest.java
+++ 
b/pinot-plugins/pinot-minion-tasks/pinot-minion-builtin-tasks/src/test/java/org/apache/pinot/plugin/minion/tasks/upsertcompaction/UpsertCompactionTaskGeneratorTest.java
@@ -387,6 +387,89 @@ public class UpsertCompactionTaskGeneratorTest {
         () -> _taskGenerator.validateTaskConfigs(invalidTableConfig, new 
Schema(), upsertCompactionTaskConfig5));
   }
 
+  @Test
+  public void testGenerateNoTaskWhenUpsertIsNotEnabled() {
+    // Create a table config without upsert enabled
+    TableConfig tableConfigWithoutUpsert =
+        new 
TableConfigBuilder(TableType.REALTIME).setTableName(RAW_TABLE_NAME).setTimeColumnName(TIME_COLUMN_NAME)
+            .build();
+
+    // Mock cluster info accessor with completed segments
+    
when(_mockClusterInfoAccessor.getSegmentsZKMetadata(REALTIME_TABLE_NAME)).thenReturn(
+        Lists.newArrayList(_completedSegment, _completedSegment2));
+    
when(_mockClusterInfoAccessor.getIdealState(REALTIME_TABLE_NAME)).thenReturn(getIdealState(REALTIME_TABLE_NAME,
+        Lists.newArrayList(_completedSegment.getSegmentName(), 
_completedSegment2.getSegmentName())));
+
+    _taskGenerator.init(_mockClusterInfoAccessor);
+
+    // Generate tasks for table without upsert - should return empty list
+    List<PinotTaskConfig> pinotTaskConfigs = 
_taskGenerator.generateTasks(Lists.newArrayList(tableConfigWithoutUpsert));
+
+    // Verify no tasks are generated when upsert is not enabled
+    assertEquals(pinotTaskConfigs.size(), 0);
+  }
+
+  @Test
+  public void testValidateTaskConfigsValidDocIdsTypeSuccess() {
+    // Test valid configurations for different ValidDocIdsType values
+    testValidDocIdsTypeConfiguration("SNAPSHOT_WITH_DELETE", true, true);
+    testValidDocIdsTypeConfiguration("IN_MEMORY_WITH_DELETE", false, true);
+    testValidDocIdsTypeConfiguration("SNAPSHOT", true, false);
+    testValidDocIdsTypeConfiguration("IN_MEMORY", false, false);
+  }
+
+  @Test
+  public void testValidateTaskConfigsValidDocIdsTypeErrors() {
+    // Test error scenarios for ValidDocIdsType validation
+    testValidDocIdsTypeError("SNAPSHOT_WITH_DELETE", false, true,
+        "'snapshot' must not be 'DISABLE'");
+    testValidDocIdsTypeError("SNAPSHOT_WITH_DELETE", true, false,
+        "'deleteRecordColumn' must be provided");
+    testValidDocIdsTypeError("IN_MEMORY_WITH_DELETE", false, false,
+        "'deleteRecordColumn' must be provided");
+  }
+
+  private void testValidDocIdsTypeConfiguration(String validDocIdsType, 
boolean enableSnapshot,
+      boolean setDeleteColumn) {
+    TableConfig tableConfig = getTableConfig(validDocIdsType, enableSnapshot, 
setDeleteColumn);
+    Map<String, String> taskConfigs = 
tableConfig.getTaskConfig().getConfigsForTaskType("UpsertCompactionTask");
+
+    // Should not throw exception for valid configuration
+    _taskGenerator.validateTaskConfigs(tableConfig, new Schema(), taskConfigs);
+  }
+
+  private void testValidDocIdsTypeError(String validDocIdsType, boolean 
enableSnapshot,
+      boolean setDeleteColumn, String expectedError) {
+    TableConfig tableConfig = getTableConfig(validDocIdsType, enableSnapshot, 
setDeleteColumn);
+    Map<String, String> taskConfigs = 
tableConfig.getTaskConfig().getConfigsForTaskType("UpsertCompactionTask");
+
+    // Should throw exception with expected error message
+    IllegalStateException exception = 
Assert.expectThrows(IllegalStateException.class,
+        () -> _taskGenerator.validateTaskConfigs(tableConfig, new Schema(), 
taskConfigs));
+    assertTrue(exception.getMessage().contains(expectedError), 
exception.getMessage());
+  }
+
+  private static TableConfig getTableConfig(String validDocIdsType, boolean 
enableSnapshot, boolean setDeleteColumn) {
+    UpsertConfig upsertConfig = new UpsertConfig(UpsertConfig.Mode.FULL);
+
+    if (setDeleteColumn) {
+      upsertConfig.setDeleteRecordColumn("deleted");
+    }
+
+    upsertConfig.setSnapshot(enableSnapshot ? Enablement.ENABLE : 
Enablement.DISABLE);
+
+    Map<String, String> taskConfigs = new HashMap<>();
+    taskConfigs.put("invalidRecordsThresholdPercent", "10");
+    taskConfigs.put("validDocIdsType", validDocIdsType);
+
+    TableConfig tableConfig = new TableConfigBuilder(TableType.REALTIME)
+        .setTableName(RAW_TABLE_NAME)
+        .setUpsertConfig(upsertConfig)
+        .setTaskConfig(new 
TableTaskConfig(ImmutableMap.of("UpsertCompactionTask", taskConfigs)))
+        .build();
+    return tableConfig;
+  }
+
   private Map<String, String> getCompactionConfigs(String 
invalidRecordsThresholdPercent,
       String invalidRecordsThresholdCount) {
     Map<String, String> compactionConfigs = new HashMap<>();


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

Reply via email to