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

kharekartik 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 88f523afb22 Refactor table config validations for create and update 
table to TableConfigValidationUtils (#17679)
88f523afb22 is described below

commit 88f523afb225aebdecd74c532378bb8571c45635
Author: Krishan Goyal <[email protected]>
AuthorDate: Thu Feb 12 12:26:32 2026 +0530

    Refactor table config validations for create and update table to 
TableConfigValidationUtils (#17679)
    
    * Refactor table config validations for create and update table to 
TableConfigValidationUtils
    
    * Move translatePhysicalTableNamesWithDB to LogicalTableConfigUtils
    
    * cstyle fixes
    
    * javadoc update
    
    * Minor improvements
---
 .../common/utils/LogicalTableConfigUtils.java      | 22 +++++
 .../api/resources/PinotLogicalTableResource.java   | 27 +------
 .../api/resources/PinotTableRestletResource.java   | 55 +------------
 .../api/resources/TableConfigValidationUtils.java  | 94 ++++++++++++++++++++++
 .../controller/helix/ControllerRequestClient.java  | 24 ++++++
 .../segment/local/utils/TableConfigUtils.java      |  2 +-
 6 files changed, 148 insertions(+), 76 deletions(-)

diff --git 
a/pinot-common/src/main/java/org/apache/pinot/common/utils/LogicalTableConfigUtils.java
 
b/pinot-common/src/main/java/org/apache/pinot/common/utils/LogicalTableConfigUtils.java
index 9e1bbc3d8f0..99a593845e5 100644
--- 
a/pinot-common/src/main/java/org/apache/pinot/common/utils/LogicalTableConfigUtils.java
+++ 
b/pinot-common/src/main/java/org/apache/pinot/common/utils/LogicalTableConfigUtils.java
@@ -25,6 +25,8 @@ import java.util.HashSet;
 import java.util.Map;
 import java.util.Set;
 import java.util.function.Predicate;
+import java.util.stream.Collectors;
+import javax.ws.rs.core.HttpHeaders;
 import org.apache.commons.lang3.StringUtils;
 import org.apache.helix.store.zk.ZkHelixPropertyStore;
 import org.apache.helix.zookeeper.datamodel.ZNRecord;
@@ -284,4 +286,24 @@ public class LogicalTableConfigUtils {
       return physicalTableNames.contains(tableName);
     }
   }
+
+  public static void translatePhysicalTableNamesWithDB(LogicalTableConfig 
logicalTableConfig, HttpHeaders headers) {
+    // Translate physical table names to include the database name
+    Map<String, PhysicalTableConfig> physicalTableConfigMap = 
logicalTableConfig.getPhysicalTableConfigMap().entrySet()
+        .stream()
+        .map(entry -> 
Map.entry(DatabaseUtils.translateTableName(entry.getKey(), headers), 
entry.getValue()))
+        .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
+
+    logicalTableConfig.setPhysicalTableConfigMap(physicalTableConfigMap);
+
+    // Translate refOfflineTableName and refRealtimeTableName to include the 
database name
+    String refOfflineTableName = logicalTableConfig.getRefOfflineTableName();
+    if (refOfflineTableName != null) {
+      
logicalTableConfig.setRefOfflineTableName(DatabaseUtils.translateTableName(refOfflineTableName,
 headers));
+    }
+    String refRealtimeTableName = logicalTableConfig.getRefRealtimeTableName();
+    if (refRealtimeTableName != null) {
+      
logicalTableConfig.setRefRealtimeTableName(DatabaseUtils.translateTableName(refRealtimeTableName,
 headers));
+    }
+  }
 }
diff --git 
a/pinot-controller/src/main/java/org/apache/pinot/controller/api/resources/PinotLogicalTableResource.java
 
b/pinot-controller/src/main/java/org/apache/pinot/controller/api/resources/PinotLogicalTableResource.java
index 778d9b3724a..3cd4416307e 100644
--- 
a/pinot-controller/src/main/java/org/apache/pinot/controller/api/resources/PinotLogicalTableResource.java
+++ 
b/pinot-controller/src/main/java/org/apache/pinot/controller/api/resources/PinotLogicalTableResource.java
@@ -30,7 +30,6 @@ import io.swagger.annotations.SecurityDefinition;
 import io.swagger.annotations.SwaggerDefinition;
 import java.util.List;
 import java.util.Map;
-import java.util.stream.Collectors;
 import javax.inject.Inject;
 import javax.ws.rs.Consumes;
 import javax.ws.rs.DELETE;
@@ -47,6 +46,7 @@ import javax.ws.rs.core.Response;
 import org.apache.commons.lang3.tuple.Pair;
 import org.apache.pinot.common.exception.TableNotFoundException;
 import org.apache.pinot.common.utils.DatabaseUtils;
+import org.apache.pinot.common.utils.LogicalTableConfigUtils;
 import org.apache.pinot.controller.api.access.AccessControlFactory;
 import org.apache.pinot.controller.api.access.AccessType;
 import org.apache.pinot.controller.api.access.Authenticate;
@@ -58,7 +58,6 @@ import org.apache.pinot.core.auth.Authorize;
 import org.apache.pinot.core.auth.ManualAuthorization;
 import org.apache.pinot.core.auth.TargetType;
 import org.apache.pinot.spi.data.LogicalTableConfig;
-import org.apache.pinot.spi.data.PhysicalTableConfig;
 import org.apache.pinot.spi.utils.JsonUtils;
 import org.glassfish.grizzly.http.server.Request;
 import org.slf4j.Logger;
@@ -143,31 +142,11 @@ public class PinotLogicalTableResource {
     ResourceUtils.checkPermissionAndAccess(tableName, request, httpHeaders, 
AccessType.CREATE,
         Actions.Table.CREATE_TABLE, _accessControlFactory, LOGGER);
 
-    translatePhysicalTableNamesWithDB(logicalTableConfig, httpHeaders);
+    
LogicalTableConfigUtils.translatePhysicalTableNamesWithDB(logicalTableConfig, 
httpHeaders);
     SuccessResponse successResponse = addLogicalTable(logicalTableConfig);
     return new ConfigSuccessResponse(successResponse.getStatus(), 
logicalTableConfigAndUnrecognizedProps.getRight());
   }
 
-  private void translatePhysicalTableNamesWithDB(LogicalTableConfig 
logicalTableConfig, HttpHeaders headers) {
-    // Translate physical table names to include the database name
-    Map<String, PhysicalTableConfig> physicalTableConfigMap = 
logicalTableConfig.getPhysicalTableConfigMap().entrySet()
-        .stream()
-        .map(entry -> 
Map.entry(DatabaseUtils.translateTableName(entry.getKey(), headers), 
entry.getValue()))
-        .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
-
-    logicalTableConfig.setPhysicalTableConfigMap(physicalTableConfigMap);
-
-    // Translate refOfflineTableName and refRealtimeTableName to include the 
database name
-    String refOfflineTableName = logicalTableConfig.getRefOfflineTableName();
-    if (refOfflineTableName != null) {
-      
logicalTableConfig.setRefOfflineTableName(DatabaseUtils.translateTableName(refOfflineTableName,
 headers));
-    }
-    String refRealtimeTableName = logicalTableConfig.getRefRealtimeTableName();
-    if (refRealtimeTableName != null) {
-      
logicalTableConfig.setRefRealtimeTableName(DatabaseUtils.translateTableName(refRealtimeTableName,
 headers));
-    }
-  }
-
   @PUT
   @Produces(MediaType.APPLICATION_JSON)
   @Consumes(MediaType.APPLICATION_JSON)
@@ -193,7 +172,7 @@ public class PinotLogicalTableResource {
 
     tableName = DatabaseUtils.translateTableName(tableName, headers);
     logicalTableConfig.setTableName(tableName);
-    translatePhysicalTableNamesWithDB(logicalTableConfig, headers);
+    
LogicalTableConfigUtils.translatePhysicalTableNamesWithDB(logicalTableConfig, 
headers);
     SuccessResponse successResponse = updateLogicalTable(logicalTableConfig);
     return new ConfigSuccessResponse(successResponse.getStatus(), 
logicalTableConfigAndUnrecognizedProps.getRight());
   }
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 74df24014dd..9d934c4d184 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
@@ -248,24 +248,14 @@ public class PinotTableRestletResource {
 
       TableConfigTunerUtils.applyTunerConfigs(_pinotHelixResourceManager, 
tableConfig, schema, Collections.emptyMap());
 
-      // TableConfigUtils.validate(...) is used across table create/update.
-      TableConfigUtils.validate(tableConfig, schema, typesToSkip);
-      TableConfigUtils.validateTableName(tableConfig);
+      TableConfigValidationUtils.validateTableConfig(
+          tableConfig, schema, typesToSkip, _pinotHelixResourceManager, 
_controllerConf, _pinotTaskManager);
     } catch (TableAlreadyExistsException e) {
       throw new ControllerApplicationException(LOGGER, e.getMessage(), 
Response.Status.CONFLICT, e);
     } catch (Exception e) {
       throw new ControllerApplicationException(LOGGER, e.getMessage(), 
Response.Status.BAD_REQUEST, e);
     }
     try {
-      try {
-        TableConfigUtils.ensureMinReplicas(tableConfig, 
_controllerConf.getDefaultTableMinReplicas());
-        TableConfigUtils.ensureStorageQuotaConstraints(tableConfig, 
_controllerConf.getDimTableMaxSize());
-        
checkHybridTableConfig(TableNameBuilder.extractRawTableName(tableNameWithType), 
tableConfig);
-        TaskConfigUtils.validateTaskConfigs(tableConfig, schema, 
_pinotTaskManager, typesToSkip);
-        validateInstanceAssignment(tableConfig);
-      } catch (Exception e) {
-        throw new InvalidTableConfigException(e);
-      }
       if (!ignoreActiveTasks) {
         tableTasksValidation(tableConfig, _pinotHelixTaskResourceManager);
       }
@@ -760,7 +750,8 @@ public class PinotTableRestletResource {
 
       schema = _pinotHelixResourceManager.getTableSchema(tableNameWithType);
       Preconditions.checkState(schema != null, "Failed to find schema for 
table: %s", tableNameWithType);
-      TableConfigUtils.validate(tableConfig, schema, typesToSkip);
+      TableConfigValidationUtils.validateTableConfig(
+          tableConfig, schema, typesToSkip, _pinotHelixResourceManager, 
_controllerConf, _pinotTaskManager);
     } catch (Exception e) {
       String msg = String.format("Invalid table config: %s with error: %s", 
tableName, e.getMessage());
       throw new ControllerApplicationException(LOGGER, msg, 
Response.Status.BAD_REQUEST, e);
@@ -771,16 +762,6 @@ public class PinotTableRestletResource {
         throw new ControllerApplicationException(LOGGER, "Table " + 
tableNameWithType + " does not exist",
             Response.Status.NOT_FOUND);
       }
-
-      try {
-        TableConfigUtils.ensureMinReplicas(tableConfig, 
_controllerConf.getDefaultTableMinReplicas());
-        TableConfigUtils.ensureStorageQuotaConstraints(tableConfig, 
_controllerConf.getDimTableMaxSize());
-        
checkHybridTableConfig(TableNameBuilder.extractRawTableName(tableNameWithType), 
tableConfig);
-        TaskConfigUtils.validateTaskConfigs(tableConfig, schema, 
_pinotTaskManager, typesToSkip);
-        validateInstanceAssignment(tableConfig);
-      } catch (Exception e) {
-        throw new InvalidTableConfigException(e);
-      }
       _pinotHelixResourceManager.updateTableConfig(tableConfig);
     } catch (InvalidTableConfigException e) {
       String errStr = String.format("Failed to update configuration for %s due 
to: %s", tableName, e.getMessage());
@@ -1177,20 +1158,6 @@ public class PinotTableRestletResource {
     return TableNameBuilder.forType(tableType).tableNameWithType(tableName);
   }
 
-  private void checkHybridTableConfig(String rawTableName, TableConfig 
tableConfig) {
-    if (tableConfig.getTableType() == TableType.REALTIME) {
-      if (_pinotHelixResourceManager.hasOfflineTable(rawTableName)) {
-        TableConfigUtils.verifyHybridTableConfigs(rawTableName,
-            _pinotHelixResourceManager.getOfflineTableConfig(rawTableName), 
tableConfig);
-      }
-    } else {
-      if (_pinotHelixResourceManager.hasRealtimeTable(rawTableName)) {
-        TableConfigUtils.verifyHybridTableConfigs(rawTableName, tableConfig,
-            _pinotHelixResourceManager.getRealtimeTableConfig(rawTableName));
-      }
-    }
-  }
-
   @GET
   @Path("/tables/{tableName}/status")
   @Authorize(targetType = TargetType.TABLE, paramName = "tableName", action = 
Actions.Table.GET_METADATA)
@@ -1546,18 +1513,4 @@ public class PinotTableRestletResource {
 
     return timeBoundaryMs;
   }
-
-  /**
-   * Try to calculate the instance partitions for the given table config. 
Throws exception if it fails.
-   */
-  private void validateInstanceAssignment(TableConfig tableConfig) {
-    TableRebalancer tableRebalancer = new 
TableRebalancer(_pinotHelixResourceManager.getHelixZkManager());
-    try {
-      tableRebalancer.getInstancePartitionsMap(tableConfig, true, true, true);
-    } catch (Exception e) {
-      throw new RuntimeException(
-          "Failed to calculate instance partitions for table: " + 
tableConfig.getTableName() + ", reason: "
-              + e.getMessage());
-    }
-  }
 }
diff --git 
a/pinot-controller/src/main/java/org/apache/pinot/controller/api/resources/TableConfigValidationUtils.java
 
b/pinot-controller/src/main/java/org/apache/pinot/controller/api/resources/TableConfigValidationUtils.java
new file mode 100644
index 00000000000..1b3d1c0cfe7
--- /dev/null
+++ 
b/pinot-controller/src/main/java/org/apache/pinot/controller/api/resources/TableConfigValidationUtils.java
@@ -0,0 +1,94 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.pinot.controller.api.resources;
+
+import javax.annotation.Nullable;
+import org.apache.pinot.controller.ControllerConf;
+import org.apache.pinot.controller.helix.core.PinotHelixResourceManager;
+import org.apache.pinot.controller.helix.core.minion.PinotTaskManager;
+import org.apache.pinot.controller.helix.core.rebalance.TableRebalancer;
+import org.apache.pinot.controller.util.TaskConfigUtils;
+import org.apache.pinot.segment.local.utils.TableConfigUtils;
+import org.apache.pinot.spi.config.table.TableConfig;
+import org.apache.pinot.spi.config.table.TableType;
+import org.apache.pinot.spi.data.Schema;
+import org.apache.pinot.spi.utils.builder.TableNameBuilder;
+
+
+/**
+ * Utility class that encapsulates table config validation logic.
+ *
+ * <p>This lives in {@code pinot-controller} (not {@code 
pinot-segment-local}'s {@code TableConfigUtils})
+ * because validation requires controller-level dependencies like {@link 
PinotHelixResourceManager},
+ * {@link ControllerConf}, {@link PinotTaskManager}, and {@link 
TableRebalancer}.</p>
+ */
+public final class TableConfigValidationUtils {
+
+  private TableConfigValidationUtils() {
+  }
+
+  /**
+   * Validates a table config against the given schema and controller 
configuration.
+   *
+   * @param tableConfig   the table config to validate
+   * @param schema        the schema for the table (must not be null)
+   * @param typesToSkip   comma-separated list of validation types to skip 
(ALL|TASK|UPSERT), or null
+   * @param resourceManager the Helix resource manager
+   * @param controllerConf  the controller configuration
+   * @param taskManager     the task manager, or null to skip task validation
+   */
+  public static void validateTableConfig(TableConfig tableConfig, Schema 
schema,
+      @Nullable String typesToSkip, PinotHelixResourceManager resourceManager,
+      ControllerConf controllerConf, @Nullable PinotTaskManager taskManager) {
+    TableConfigUtils.validate(tableConfig, schema, typesToSkip);
+    TableConfigUtils.validateTableName(tableConfig);
+    TableConfigUtils.ensureMinReplicas(tableConfig, 
controllerConf.getDefaultTableMinReplicas());
+    TableConfigUtils.ensureStorageQuotaConstraints(tableConfig, 
controllerConf.getDimTableMaxSize());
+    checkHybridTableConfig(resourceManager, tableConfig);
+    TaskConfigUtils.validateTaskConfigs(tableConfig, schema, taskManager, 
typesToSkip);
+    validateInstanceAssignment(resourceManager, tableConfig);
+  }
+
+  private static void checkHybridTableConfig(PinotHelixResourceManager 
resourceManager, TableConfig tableConfig) {
+    String rawTableName = 
TableNameBuilder.extractRawTableName(tableConfig.getTableName());
+    if (tableConfig.getTableType() == TableType.REALTIME) {
+      if (resourceManager.hasOfflineTable(rawTableName)) {
+        TableConfigUtils.verifyHybridTableConfigs(rawTableName,
+            resourceManager.getOfflineTableConfig(rawTableName), tableConfig);
+      }
+    } else {
+      if (resourceManager.hasRealtimeTable(rawTableName)) {
+        TableConfigUtils.verifyHybridTableConfigs(rawTableName, tableConfig,
+            resourceManager.getRealtimeTableConfig(rawTableName));
+      }
+    }
+  }
+
+  private static void validateInstanceAssignment(PinotHelixResourceManager 
resourceManager,
+      TableConfig tableConfig) {
+    TableRebalancer tableRebalancer = new 
TableRebalancer(resourceManager.getHelixZkManager());
+    try {
+      tableRebalancer.getInstancePartitionsMap(tableConfig, true, true, true);
+    } catch (Exception e) {
+      throw new RuntimeException(
+          "Failed to calculate instance partitions for table: " + 
tableConfig.getTableName() + ", reason: "
+              + e.getMessage(), e);
+    }
+  }
+}
diff --git 
a/pinot-controller/src/main/java/org/apache/pinot/controller/helix/ControllerRequestClient.java
 
b/pinot-controller/src/main/java/org/apache/pinot/controller/helix/ControllerRequestClient.java
index 0b2ce9fd3c7..1c78ea13061 100644
--- 
a/pinot-controller/src/main/java/org/apache/pinot/controller/helix/ControllerRequestClient.java
+++ 
b/pinot-controller/src/main/java/org/apache/pinot/controller/helix/ControllerRequestClient.java
@@ -209,6 +209,30 @@ public class ControllerRequestClient {
     }
   }
 
+  public String getLogicalTable(String logicalTableName)
+      throws IOException {
+    try {
+      SimpleHttpResponse response = HttpClient.wrapAndThrowHttpException(
+          _httpClient.sendGetRequest(
+              new 
URI(_controllerRequestURLBuilder.forLogicalTableGet(logicalTableName)), 
_headers));
+      return response.getResponse();
+    } catch (HttpErrorStatusException | URISyntaxException e) {
+      throw new IOException(e);
+    }
+  }
+
+  public List<String> getLogicalTableNames()
+      throws IOException {
+    try {
+      SimpleHttpResponse response = HttpClient.wrapAndThrowHttpException(
+          _httpClient.sendGetRequest(
+              new URI(_controllerRequestURLBuilder.forLogicalTableNamesGet()), 
_headers));
+      return JsonUtils.stringToObject(response.getResponse(), List.class);
+    } catch (HttpErrorStatusException | URISyntaxException e) {
+      throw new IOException(e);
+    }
+  }
+
   public TableConfig getTableConfig(String tableName, TableType tableType)
       throws IOException {
     try {
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 f7e89f6660c..834516ef417 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
@@ -165,7 +165,7 @@ public final class TableConfigUtils {
    * TODO: Add more validations for each section (e.g. validate conditions are 
met for aggregateMetrics)
    */
   public static void validate(TableConfig tableConfig, Schema schema, 
@Nullable String typesToSkip) {
-    Preconditions.checkArgument(schema != null, "Schema should not be null");
+    Preconditions.checkArgument(schema != null, "Schema should not be null for 
table: %s", tableConfig.getTableName());
     Set<ValidationType> skipTypes = parseTypesToSkipString(typesToSkip);
     // Sanitize the table config before validation
     sanitize(tableConfig);


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

Reply via email to