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

KKcorps 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 f7342013094 Run SPI-registered validators from /tables/validate 
preflight (#19420)
f7342013094 is described below

commit f734201309441838fa0122ebc307497fefe5bdb1
Author: Chaitanya Deepthi <[email protected]>
AuthorDate: Mon Aug 31 23:43:16 2026 -0700

    Run SPI-registered validators from /tables/validate preflight (#19420)
    
    PinotTableRestletResource#validateConfig, which serves POST 
/tables/validate,
    was invoking only TableConfigUtils.validate and 
TaskConfigUtils.validateTaskConfigs.
    The create and update paths (POST /tables, PUT /tables/{tableName}) route 
through
    TableConfigValidationUtils#validateTableConfig which additionally invokes
    TableConfigValidatorRegistry.validate, so any SPI-registered validator 
would be
    consulted at apply time but skipped at preflight time.
    
    A config that any registered validator would reject therefore passes the 
preflight
    green and then fails when the user actually applies it. Add the registry 
call to
    the preflight so validate/apply agree.
    
    TableConfigsRestletResource#validateConfig already invokes the registry, so 
its
    /tableConfigs endpoints (create, update, validate) are unaffected.
---
 .../api/resources/PinotTableRestletResource.java   |  5 ++-
 .../resources/PinotTableRestletResourceTest.java   | 47 ++++++++++++++++++++++
 2 files changed, 51 insertions(+), 1 deletion(-)

diff --git 
a/pinot-controller/src/main/java/org/apache/pinot/controller/api/resources/PinotTableRestletResource.java
 
b/pinot-controller/src/main/java/org/apache/pinot/controller/api/resources/PinotTableRestletResource.java
index 4b01d327ba8..6dff8b7eee6 100644
--- 
a/pinot-controller/src/main/java/org/apache/pinot/controller/api/resources/PinotTableRestletResource.java
+++ 
b/pinot-controller/src/main/java/org/apache/pinot/controller/api/resources/PinotTableRestletResource.java
@@ -121,6 +121,7 @@ import org.apache.pinot.core.auth.ManualAuthorization;
 import org.apache.pinot.core.auth.TargetType;
 import org.apache.pinot.segment.local.utils.TableConfigUtils;
 import org.apache.pinot.spi.config.table.TableConfig;
+import org.apache.pinot.spi.config.table.TableConfigValidatorRegistry;
 import org.apache.pinot.spi.config.table.TableStatsHumanReadable;
 import org.apache.pinot.spi.config.table.TableStatus;
 import org.apache.pinot.spi.config.table.TableType;
@@ -859,7 +860,8 @@ public class PinotTableRestletResource {
     return validationResponse;
   }
 
-  private ObjectNode validateConfig(TableConfig tableConfig, @Nullable String 
typesToSkip) {
+  @VisibleForTesting
+  ObjectNode validateConfig(TableConfig tableConfig, @Nullable String 
typesToSkip) {
     String tableNameWithType = tableConfig.getTableName();
     try {
       Schema schema = 
_pinotHelixResourceManager.getTableSchema(tableNameWithType);
@@ -869,6 +871,7 @@ public class PinotTableRestletResource {
       TableConfigUtils.validate(tableConfig, schema, typesToSkip,
           _pinotHelixResourceManager.getTableConfig(tableNameWithType));
       TaskConfigUtils.validateTaskConfigs(tableConfig, schema, 
_pinotTaskManager, typesToSkip);
+      TableConfigValidatorRegistry.validate(tableConfig, schema);
       ObjectNode tableConfigValidateStr = JsonUtils.newObjectNode();
       if (tableConfig.getTableType() == TableType.OFFLINE) {
         tableConfigValidateStr.set(TableType.OFFLINE.name(), 
tableConfig.toJsonNode());
diff --git 
a/pinot-controller/src/test/java/org/apache/pinot/controller/api/resources/PinotTableRestletResourceTest.java
 
b/pinot-controller/src/test/java/org/apache/pinot/controller/api/resources/PinotTableRestletResourceTest.java
index dde748ef622..1d9a446ef53 100644
--- 
a/pinot-controller/src/test/java/org/apache/pinot/controller/api/resources/PinotTableRestletResourceTest.java
+++ 
b/pinot-controller/src/test/java/org/apache/pinot/controller/api/resources/PinotTableRestletResourceTest.java
@@ -22,17 +22,29 @@ import com.fasterxml.jackson.databind.node.ObjectNode;
 import java.io.InputStream;
 import java.util.List;
 import java.util.Map;
+import 
org.apache.pinot.controller.api.exception.ControllerApplicationException;
 import org.apache.pinot.controller.helix.core.PinotHelixResourceManager;
 import org.apache.pinot.controller.helix.core.WatermarkInductionResult;
 import 
org.apache.pinot.controller.helix.core.realtime.PinotLLCRealtimeSegmentManager;
+import org.apache.pinot.spi.config.table.TableConfig;
+import org.apache.pinot.spi.config.table.TableConfigValidator;
+import org.apache.pinot.spi.config.table.TableConfigValidatorRegistry;
+import org.apache.pinot.spi.config.table.TableType;
+import org.apache.pinot.spi.data.FieldSpec;
+import org.apache.pinot.spi.data.Schema;
+import org.apache.pinot.spi.exception.ConfigValidationException;
 import org.apache.pinot.spi.stream.LongMsgOffset;
 import org.apache.pinot.spi.stream.StreamConfig;
 import org.apache.pinot.spi.stream.StreamMetadata;
 import org.apache.pinot.spi.utils.JsonUtils;
+import org.apache.pinot.spi.utils.builder.TableConfigBuilder;
 import org.mockito.Mockito;
 import org.testng.annotations.Test;
 
 import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertNotNull;
+import static org.testng.Assert.assertTrue;
+import static org.testng.Assert.expectThrows;
 
 public class PinotTableRestletResourceTest {
 
@@ -100,4 +112,39 @@ public class PinotTableRestletResourceTest {
         200L);
     
assertEquals(streamMetadata1.getPartitionGroupMetadataList().get(0).getSequenceNumber(),
 5);
   }
+
+  @Test
+  public void testValidateConfigRunsRegisteredValidators()
+      throws Exception {
+    String tableName = "preflightRegistryTest";
+    String tableNameWithType = tableName + "_OFFLINE";
+
+    // Real schema + real table config keep the validate/apply parity check 
honest.
+    Schema schema = new Schema.SchemaBuilder().setSchemaName(tableName)
+        .addSingleValueDimension("d1", FieldSpec.DataType.STRING).build();
+    TableConfig tableConfig = new 
TableConfigBuilder(TableType.OFFLINE).setTableName(tableName).build();
+
+    PinotHelixResourceManager helixResourceManager = 
Mockito.mock(PinotHelixResourceManager.class);
+    
Mockito.when(helixResourceManager.getTableSchema(tableNameWithType)).thenReturn(schema);
+    
Mockito.when(helixResourceManager.getTableConfig(tableNameWithType)).thenReturn(null);
+
+    PinotTableRestletResource resource = new PinotTableRestletResource();
+    resource._pinotHelixResourceManager = helixResourceManager;
+
+    TableConfigValidator rejecting = (tc, s) -> {
+      throw new ConfigValidationException("rejected-by-registry-probe");
+    };
+    TableConfigValidatorRegistry.register(rejecting);
+    try {
+      // Without the registry hookup at the preflight, this call would return 
a validation response; with it,
+      // the registered validator's rejection propagates as a BAD_REQUEST 
wrapping the probe's message.
+      ControllerApplicationException e = 
expectThrows(ControllerApplicationException.class,
+          () -> resource.validateConfig(tableConfig, null));
+      assertNotNull(e.getMessage());
+      assertTrue(e.getMessage().contains("rejected-by-registry-probe"),
+          "Preflight should surface the registered validator's rejection 
message. Actual: " + e.getMessage());
+    } finally {
+      TableConfigValidatorRegistry.unregister(rejecting);
+    }
+  }
 }


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

Reply via email to