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

yashmayya 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 a1e9f0a0554 Database name validation for logical tables (#15994)
a1e9f0a0554 is described below

commit a1e9f0a05545d768bbe8d6aac63dec4b0d4b89ea
Author: Abhishek Bafna <aba...@startree.ai>
AuthorDate: Tue Jun 17 09:16:54 2025 +0530

    Database name validation for logical tables (#15994)
---
 .../common/utils/LogicalTableConfigUtils.java      |  10 +++
 .../api/resources/PinotLogicalTableResource.java   |  25 +++++-
 .../resources/PinotLogicalTableResourceTest.java   | 100 ++++++++++++++++-----
 3 files changed, 110 insertions(+), 25 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 1f9d948728b..e1ac8e352e1 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
@@ -139,6 +139,7 @@ public class LogicalTableConfigUtils {
           "Invalid logical table. Reason: 'physicalTableConfigMap' should not 
be null or empty");
     }
 
+    String databaseName = 
DatabaseUtils.extractDatabaseFromFullyQualifiedTableName(logicalTableConfig.getTableName());
     Set<String> offlineTableNames = new HashSet<>();
     Set<String> realtimeTableNames = new HashSet<>();
 
@@ -146,6 +147,15 @@ public class LogicalTableConfigUtils {
       String physicalTableName = entry.getKey();
       PhysicalTableConfig physicalTableConfig = entry.getValue();
 
+      // validate database name matches
+      String physicalTableDatabaseName = 
DatabaseUtils.extractDatabaseFromFullyQualifiedTableName(physicalTableName);
+      if (!StringUtils.equalsIgnoreCase(databaseName, 
physicalTableDatabaseName)) {
+        throw new IllegalArgumentException(
+            "Invalid logical table. Reason: '" + physicalTableName
+                + "' should have the same database name as logical table: " + 
databaseName + " != "
+                + physicalTableDatabaseName);
+      }
+
       // validate physical table exists
       if (!physicalTableExistsPredicate.test(physicalTableName)) {
         throw new IllegalArgumentException(
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 56a9f02edd9..1192c8444fe 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
@@ -29,6 +29,7 @@ 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;
@@ -57,6 +58,7 @@ 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;
@@ -141,10 +143,31 @@ public class PinotLogicalTableResource {
     ResourceUtils.checkPermissionAndAccess(tableName, request, httpHeaders, 
AccessType.CREATE,
         Actions.Table.CREATE_TABLE, _accessControlFactory, LOGGER);
 
+    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)
@@ -170,7 +193,7 @@ public class PinotLogicalTableResource {
 
     tableName = DatabaseUtils.translateTableName(tableName, headers);
     logicalTableConfig.setTableName(tableName);
-
+    translatePhysicalTableNamesWithDB(logicalTableConfig, headers);
     SuccessResponse successResponse = updateLogicalTable(logicalTableConfig);
     return new ConfigSuccessResponse(successResponse.getStatus(), 
logicalTableConfigAndUnrecognizedProps.getRight());
   }
diff --git 
a/pinot-controller/src/test/java/org/apache/pinot/controller/api/resources/PinotLogicalTableResourceTest.java
 
b/pinot-controller/src/test/java/org/apache/pinot/controller/api/resources/PinotLogicalTableResourceTest.java
index 47eb75d62ca..f65c4a98b34 100644
--- 
a/pinot-controller/src/test/java/org/apache/pinot/controller/api/resources/PinotLogicalTableResourceTest.java
+++ 
b/pinot-controller/src/test/java/org/apache/pinot/controller/api/resources/PinotLogicalTableResourceTest.java
@@ -25,6 +25,7 @@ import java.util.List;
 import java.util.Map;
 import java.util.Set;
 import org.apache.helix.model.IdealState;
+import org.apache.pinot.common.utils.DatabaseUtils;
 import org.apache.pinot.common.utils.helix.HelixHelper;
 import org.apache.pinot.controller.helix.ControllerTest;
 import org.apache.pinot.controller.helix.core.PinotHelixResourceManager;
@@ -85,23 +86,23 @@ public class PinotLogicalTableResourceTest extends 
ControllerTest {
   @DataProvider
   public Object[][] tableNamesProvider() {
     return new Object[][]{
-        {"test_logical_table", List.of("test_table_1", "test_table_2"), 
List.of("test_table_3")},
-        {"test_logical_table", List.of("test_table_1", "db.test_table_2"), 
List.of("test_table_3")},
-        {"test_logical_table", List.of("test_table_1", "test_table_2"), 
List.of("db.test_table_3")},
-        {"test_logical_table", List.of("db.test_table_1", "db.test_table_2"), 
List.of("db.test_table_3")},
-        {"test_table", List.of("db1.test_table", "db2.test_table"), 
List.of("db3.test_table")},
-        {"db0.test_table", List.of("db1.test_table", "db2.test_table"), 
List.of("db3.test_table")},
-        {"db.test_logical_table", List.of("test_table_1", "test_table_2"), 
List.of("test_table_3")},
-        {"db.test_logical_table", List.of("test_table_1", "db.test_table_2"), 
List.of("test_table_3")},
-        {"db.test_logical_table", List.of("test_table_1", "test_table_2"), 
List.of("db.test_table_3")},
-        {"db.test_logical_table", List.of("db.test_table_1", 
"db.test_table_2"), List.of("db.test_table_3")},
+        {"test_logical_table", List.of("test_table_1", "test_table_2"), 
List.of("test_table_3"), Map.of()},
+        {"db.test_logical_table", List.of("db.test_table_1", 
"db.test_table_2"), List.of("db.test_table_3"), Map.of()},
+        {
+            "test_logical_table", List.of("db1.test_table_1", 
"db1.test_table_2"), List.of("db1.test_table_3"), Map.of(
+            CommonConstants.DATABASE, "db1")
+        },
     };
   }
 
   @Test(dataProvider = "tableNamesProvider")
   public void testCreateUpdateDeleteLogicalTables(String logicalTableName, 
List<String> physicalTableNames,
-      List<String> physicalTablesToUpdate)
+      List<String> physicalTablesToUpdate, Map<String, String> dbHeaders)
       throws IOException {
+    Map<String, String> headers = new HashMap<>(getHeaders());
+    headers.putAll(dbHeaders);
+    logicalTableName = DatabaseUtils.translateTableName(logicalTableName, 
headers.get(CommonConstants.DATABASE));
+
     addDummySchema(logicalTableName);
     // verify logical table does not exist
     String getLogicalTableUrl = 
_controllerRequestURLBuilder.forLogicalTableGet(logicalTableName);
@@ -118,7 +119,7 @@ public class PinotLogicalTableResourceTest extends 
ControllerTest {
 
     // create logical table
     String resp =
-        ControllerTest.sendPostRequest(_addLogicalTableUrl, 
logicalTableConfig.toSingleLineJsonString(), getHeaders());
+        ControllerTest.sendPostRequest(_addLogicalTableUrl, 
logicalTableConfig.toSingleLineJsonString(), headers);
     assertEquals(resp,
         "{\"unrecognizedProperties\":{},\"status\":\"" + logicalTableName + " 
logical table successfully added.\"}");
 
@@ -131,7 +132,7 @@ public class PinotLogicalTableResourceTest extends 
ControllerTest {
     logicalTableConfig = getDummyLogicalTableConfig(logicalTableName, 
tableNameToUpdateWithType, BROKER_TENANT);
 
     String response =
-        ControllerTest.sendPutRequest(updateLogicalTableUrl, 
logicalTableConfig.toSingleLineJsonString(), getHeaders());
+        ControllerTest.sendPutRequest(updateLogicalTableUrl, 
logicalTableConfig.toSingleLineJsonString(), headers);
     assertEquals(response,
         "{\"unrecognizedProperties\":{},\"status\":\"" + logicalTableName + " 
logical table successfully updated.\"}");
 
@@ -139,7 +140,7 @@ public class PinotLogicalTableResourceTest extends 
ControllerTest {
     verifyLogicalTableExists(getLogicalTableUrl, logicalTableConfig);
 
     // delete logical table
-    String deleteResponse = 
ControllerTest.sendDeleteRequest(deleteLogicalTableUrl, getHeaders());
+    String deleteResponse = 
ControllerTest.sendDeleteRequest(deleteLogicalTableUrl, headers);
     assertEquals(deleteResponse, "{\"status\":\"" + logicalTableName + " 
logical table successfully deleted.\"}");
 
     // verify logical table is deleted
@@ -271,6 +272,55 @@ public class PinotLogicalTableResourceTest extends 
ControllerTest {
     );
     assertTrue(aThrows.getMessage().contains("Reason: 'refRealtimeTableName' 
should be a realtime table type"),
         aThrows.getMessage());
+
+    // Test ref offline table is specified with a database prefix
+    aThrows = expectThrows(
+        IOException.class, () -> {
+          LogicalTableConfig logicalTableConfig =
+              getDummyLogicalTableConfig(LOGICAL_TABLE_NAME, 
physicalTableNamesWithType, BROKER_TENANT);
+          logicalTableConfig.setRefOfflineTableName("db.test_table_7_OFFLINE");
+          ControllerTest.sendPostRequest(_addLogicalTableUrl, 
logicalTableConfig.toSingleLineJsonString(),
+              getHeaders());
+        }
+    );
+    assertTrue(
+        aThrows.getMessage().contains("Reason: 'refOfflineTableName' should be 
one of the provided offline tables"),
+        aThrows.getMessage());
+
+    // Test ref realtime table is specified with a database prefix
+    aThrows = expectThrows(
+        IOException.class, () -> {
+          LogicalTableConfig logicalTableConfig =
+              getDummyLogicalTableConfig(LOGICAL_TABLE_NAME, 
physicalTableNamesWithType, BROKER_TENANT);
+          
logicalTableConfig.setRefRealtimeTableName("db.test_table_7_REALTIME");
+          ControllerTest.sendPostRequest(_addLogicalTableUrl, 
logicalTableConfig.toSingleLineJsonString(),
+              getHeaders());
+        }
+    );
+    assertTrue(
+        aThrows.getMessage().contains("Reason: 'refRealtimeTableName' should 
be one of the provided realtime tables"),
+        aThrows.getMessage());
+  }
+
+  @Test
+  public void testLogicalTableDatabaseValidation() {
+    String logicalTableName = "db1.test_logical_table";
+    LogicalTableConfig logicalTableConfig =
+        getDummyLogicalTableConfig(logicalTableName, 
List.of("test_table_1_OFFLINE", "test_table_2_REALTIME"),
+            BROKER_TENANT);
+    // Test add logical table with different database prefix
+    String msg = expectThrows(IOException.class, () -> {
+      addLogicalTableConfig(logicalTableConfig);
+    }).getMessage();
+    assertTrue(msg.contains(
+        "Reason: 'test_table_1_OFFLINE' should have the same database name as 
logical table: db1 != default"), msg);
+
+    // Test update logical table with different database prefix
+    msg = expectThrows(IOException.class, () -> 
updateLogicalTableConfig(logicalTableConfig)).getMessage();
+    assertTrue(
+        msg.contains(
+            "Reason: 'test_table_1_OFFLINE' should have the same database name 
as logical table: db1 != default"),
+        msg);
   }
 
   @Test(expectedExceptions = IOException.class,
@@ -374,7 +424,8 @@ public class PinotLogicalTableResourceTest extends 
ControllerTest {
     throwable = expectThrows(IOException.class, () -> {
       addDummySchema(LOGICAL_TABLE_NAME);
       LogicalTableConfig logicalTableConfig =
-          getDummyLogicalTableConfig("db." + LOGICAL_TABLE_NAME, 
physicalTableNamesWithType, BROKER_TENANT);
+          getDummyLogicalTableConfig("db." + LOGICAL_TABLE_NAME, 
createHybridTables(List.of("db.test_table_6")),
+              BROKER_TENANT);
       ControllerTest.sendPostRequest(_addLogicalTableUrl, 
logicalTableConfig.toSingleLineJsonString(), getHeaders());
     });
     assertTrue(throwable.getMessage()
@@ -463,8 +514,8 @@ public class PinotLogicalTableResourceTest extends 
ControllerTest {
     return new Object[][]{
         {LOGICAL_TABLE_NAME, List.of("test_table_1"), "unknown_table_OFFLINE"},
         {LOGICAL_TABLE_NAME, List.of("test_table_2"), 
"unknown_table_REALTIME"},
-        {LOGICAL_TABLE_NAME, List.of("test_table_1"), 
"db.test_table_1_OFFLINE"},
-        {LOGICAL_TABLE_NAME, List.of("test_table_2"), 
"db.test_table_2_REALTIME"},
+        {"db." + LOGICAL_TABLE_NAME, List.of("db.test_table_1"), 
"db.unknown_table_OFFLINE"},
+        {"db." + LOGICAL_TABLE_NAME, List.of("db.test_table_2"), 
"db.unknown_table_REALTIME"},
     };
   }
 
@@ -472,6 +523,7 @@ public class PinotLogicalTableResourceTest extends 
ControllerTest {
   public void testPhysicalTableShouldExist(String logicalTableName, 
List<String> physicalTableNames,
       String unknownTableName)
       throws IOException {
+    addDummySchema(logicalTableName);
     // setup physical tables
     List<String> physicalTableNamesWithType = 
createHybridTables(physicalTableNames);
     physicalTableNamesWithType.add(unknownTableName);
@@ -499,13 +551,14 @@ public class PinotLogicalTableResourceTest extends 
ControllerTest {
     // setup physical tables and logical tables
     List<String> logicalTableNames =
         List.of("db.test_logical_table_1", "default.test_logical_table_2", 
"test_logical_table_3");
-    List<String> physicalTableNames = List.of("test_table_1", "test_table_2", 
"db.test_table_3");
-    List<String> physicalTableNamesWithType = 
createHybridTables(physicalTableNames);
 
     for (int i = 0; i < logicalTableNames.size(); i++) {
-      addDummySchema(logicalTableNames.get(i));
-      LogicalTableConfig logicalTableConfig = 
getDummyLogicalTableConfig(logicalTableNames.get(i), List.of(
-          physicalTableNamesWithType.get(2 * i), 
physicalTableNamesWithType.get(2 * i + 1)), BROKER_TENANT);
+      String logicalTableName = logicalTableNames.get(i);
+      String databaseName = 
DatabaseUtils.extractDatabaseFromFullyQualifiedTableName(logicalTableName);
+      List<String> physicalTableNamesWithType = 
createHybridTables(List.of(databaseName + ".test_table_" + i));
+      addDummySchema(logicalTableName);
+      LogicalTableConfig logicalTableConfig =
+          getDummyLogicalTableConfig(logicalTableName, 
physicalTableNamesWithType, BROKER_TENANT);
 
       ControllerTest.sendPostRequest(_addLogicalTableUrl, 
logicalTableConfig.toSingleLineJsonString(), getHeaders());
     }
@@ -524,8 +577,7 @@ public class PinotLogicalTableResourceTest extends 
ControllerTest {
   }
 
   @Test
-  public void testLogicalTableDatabaseHeaderMismatchValidation()
-      throws IOException {
+  public void testLogicalTableDatabaseHeaderMismatchValidation() {
     Map<String, String> headers = new HashMap<>(getHeaders());
     headers.put(CommonConstants.DATABASE, "db1");
     String logicalTableName = "db2.test_logical_table";


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscr...@pinot.apache.org
For additional commands, e-mail: commits-h...@pinot.apache.org

Reply via email to