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

yuqi1129 pushed a commit to branch branch-1.3
in repository https://gitbox.apache.org/repos/asf/gravitino.git


The following commit(s) were added to refs/heads/branch-1.3 by this push:
     new a7704ccda4 [Cherry-pick to branch-1.3] [#14714] fix(postgresql): drop 
tables by schema-qualified name (#14715) (#14738)
a7704ccda4 is described below

commit a7704ccda42a1a8ee82c161f25a2591292db76e0
Author: github-actions[bot] 
<41898282+github-actions[bot]@users.noreply.github.com>
AuthorDate: Sat Nov 7 10:15:33 2026 +0800

    [Cherry-pick to branch-1.3] [#14714] fix(postgresql): drop tables by 
schema-qualified name (#14715) (#14738)
    
    **Cherry-pick Information:**
    - Original commit: 9107b3b2b72398bca6dd9c35e3a39a7236515537
    - Target branch: `branch-1.3`
    - Status: ✅ Clean cherry-pick (no conflicts)
    
    Co-authored-by: Qi Yu <[email protected]>
---
 .../operation/PostgreSqlTableOperations.java       | 21 +++++
 .../TestPostgreSqlTableDropPrivileges.java         | 92 ++++++++++++++++++++++
 .../operation/TestPostgreSqlTableOperations.java   | 42 ++++++++++
 3 files changed, 155 insertions(+)

diff --git 
a/catalogs/catalog-jdbc-postgresql/src/main/java/org/apache/gravitino/catalog/postgresql/operation/PostgreSqlTableOperations.java
 
b/catalogs/catalog-jdbc-postgresql/src/main/java/org/apache/gravitino/catalog/postgresql/operation/PostgreSqlTableOperations.java
index ddd92fa738..15911bca34 100644
--- 
a/catalogs/catalog-jdbc-postgresql/src/main/java/org/apache/gravitino/catalog/postgresql/operation/PostgreSqlTableOperations.java
+++ 
b/catalogs/catalog-jdbc-postgresql/src/main/java/org/apache/gravitino/catalog/postgresql/operation/PostgreSqlTableOperations.java
@@ -377,6 +377,27 @@ public class PostgreSqlTableOperations extends 
JdbcTableOperations
     return "DROP TABLE " + quotedIdentifier(tableName);
   }
 
+  /**
+   * Drops the table by its schema-qualified name. An unqualified name is 
resolved through the
+   * search path, and PostgreSQL leaves out a schema the catalog user has no 
USAGE privilege on. The
+   * drop would then report the table as missing instead of refusing it with 
42501.
+   *
+   * <p>On {@code branch-1.3}, that missing-table result can cause Gravitino 
to remove the
+   * registration of a table that still exists. On {@code main}, the 
registration is already
+   * preserved when the connector returns {@code false}; schema qualification 
ensures the permission
+   * failure is reported as 403.
+   */
+  @Override
+  protected void dropTable(String schemaName, String tableName) {
+    try (Connection connection = getConnection(schemaName)) {
+      JdbcConnectorUtils.executeUpdate(
+          connection,
+          "DROP TABLE " + quotedIdentifier(schemaName) + "." + 
quotedIdentifier(tableName));
+    } catch (final SQLException se) {
+      throw this.exceptionMapper.toGravitinoException(se);
+    }
+  }
+
   @Override
   protected String generatePurgeTableSql(String tableName) {
     throw new UnsupportedOperationException(
diff --git 
a/catalogs/catalog-jdbc-postgresql/src/test/java/org/apache/gravitino/catalog/postgresql/operation/TestPostgreSqlTableDropPrivileges.java
 
b/catalogs/catalog-jdbc-postgresql/src/test/java/org/apache/gravitino/catalog/postgresql/operation/TestPostgreSqlTableDropPrivileges.java
new file mode 100644
index 0000000000..4a7c2450b0
--- /dev/null
+++ 
b/catalogs/catalog-jdbc-postgresql/src/test/java/org/apache/gravitino/catalog/postgresql/operation/TestPostgreSqlTableDropPrivileges.java
@@ -0,0 +1,92 @@
+/*
+ * 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.gravitino.catalog.postgresql.operation;
+
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import java.sql.Connection;
+import java.sql.SQLException;
+import java.sql.Statement;
+import java.util.Map;
+import javax.sql.DataSource;
+import 
org.apache.gravitino.catalog.postgresql.converter.PostgreSqlColumnDefaultValueConverter;
+import 
org.apache.gravitino.catalog.postgresql.converter.PostgreSqlExceptionConverter;
+import 
org.apache.gravitino.catalog.postgresql.converter.PostgreSqlTypeConverter;
+import org.apache.gravitino.exceptions.ForbiddenException;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+/** A drop the catalog user may not run must be refused, not reported as a 
missing table. */
+public class TestPostgreSqlTableDropPrivileges {
+  private static final String QUALIFIED_DROP = "DROP TABLE 
\"restricted\".\"t\"\"x\"";
+
+  private Connection connection;
+  private Statement statement;
+  private PostgreSqlTableOperations operations;
+
+  @BeforeEach
+  void setUp() throws Exception {
+    DataSource source = mock(DataSource.class);
+    connection = mock(Connection.class);
+    statement = mock(Statement.class);
+    when(source.getConnection()).thenReturn(connection);
+    when(connection.createStatement()).thenReturn(statement);
+    operations = new PostgreSqlTableOperations();
+    operations.initialize(
+        source,
+        new PostgreSqlExceptionConverter(),
+        new PostgreSqlTypeConverter(),
+        new PostgreSqlColumnDefaultValueConverter(),
+        Map.of("jdbc-database", "database"));
+  }
+
+  @Test
+  void testDropUsesSchemaQualifiedName() throws Exception {
+    Assertions.assertTrue(operations.drop("restricted", "t\"x"));
+    verify(statement).executeUpdate(QUALIFIED_DROP);
+  }
+
+  @Test
+  void testSchemaWithoutUsageIsForbidden() throws Exception {
+    when(statement.executeUpdate(QUALIFIED_DROP))
+        .thenThrow(new SQLException("permission denied for schema restricted", 
"42501"));
+    ForbiddenException error =
+        Assertions.assertThrows(
+            ForbiddenException.class, () -> operations.drop("restricted", 
"t\"x"));
+    Assertions.assertTrue(error.getMessage().contains("permission denied"));
+    verify(connection).close();
+  }
+
+  @Test
+  void testMissingTableStillReportsNotDropped() throws Exception {
+    when(statement.executeUpdate(QUALIFIED_DROP))
+        .thenThrow(new SQLException("table \"t\" does not exist", "42P01"));
+    Assertions.assertFalse(operations.drop("restricted", "t\"x"));
+  }
+
+  @Test
+  void testMissingSchemaStillReportsNotDropped() throws Exception {
+    when(statement.executeUpdate(QUALIFIED_DROP))
+        .thenThrow(new SQLException("schema \"restricted\" does not exist", 
"3F000"));
+    Assertions.assertFalse(operations.drop("restricted", "t\"x"));
+  }
+}
diff --git 
a/catalogs/catalog-jdbc-postgresql/src/test/java/org/apache/gravitino/catalog/postgresql/operation/TestPostgreSqlTableOperations.java
 
b/catalogs/catalog-jdbc-postgresql/src/test/java/org/apache/gravitino/catalog/postgresql/operation/TestPostgreSqlTableOperations.java
index 8c59e49292..d4bac133f6 100644
--- 
a/catalogs/catalog-jdbc-postgresql/src/test/java/org/apache/gravitino/catalog/postgresql/operation/TestPostgreSqlTableOperations.java
+++ 
b/catalogs/catalog-jdbc-postgresql/src/test/java/org/apache/gravitino/catalog/postgresql/operation/TestPostgreSqlTableOperations.java
@@ -904,6 +904,48 @@ public class TestPostgreSqlTableOperations extends 
TestPostgreSql {
     }
   }
 
+  @Test
+  void testDropWithoutSchemaUsageIsForbiddenAndKeepsTable() throws 
SQLException {
+    String role = RandomNameUtils.genRandomName("drop_role");
+    String schema = RandomNameUtils.genRandomName("drop_schema");
+    String table = RandomNameUtils.genRandomName("drop_table");
+    try (Connection admin = DATA_SOURCE.getConnection()) {
+      JdbcConnectorUtils.executeUpdate(
+          admin, "CREATE ROLE " + role + " LOGIN PASSWORD 
'test-only-password'");
+      JdbcConnectorUtils.executeUpdate(admin, "CREATE SCHEMA " + schema);
+      try {
+        JdbcConnectorUtils.executeUpdate(admin, "REVOKE ALL ON SCHEMA " + 
schema + " FROM PUBLIC");
+        JdbcConnectorUtils.executeUpdate(
+            admin, "CREATE TABLE " + schema + "." + table + " (id int)");
+        Map<String, String> properties = new HashMap<>();
+        properties.put("jdbc-driver", CONTAINER.getDriverClassName());
+        properties.put("jdbc-url", CONTAINER.getJdbcUrl());
+        properties.put("jdbc-database", admin.getCatalog());
+        properties.put("jdbc-user", role);
+        properties.put("jdbc-password", "test-only-password");
+        properties.put("jdbc.pool.max-size", "1");
+        properties.put("jdbc.pool.min-size", "1");
+        try (BasicDataSource limitedSource =
+            (BasicDataSource) DataSourceUtils.createDataSource(properties)) {
+          PostgreSqlTableOperations limited = new PostgreSqlTableOperations();
+          limited.initialize(
+              limitedSource,
+              JDBC_EXCEPTION_CONVERTER,
+              new PostgreSqlTypeConverter(),
+              new PostgreSqlColumnDefaultValueConverter(),
+              properties);
+          Assertions.assertThrows(ForbiddenException.class, () -> 
limited.drop(schema, table));
+          Assertions.assertEquals(table, TABLE_OPERATIONS.load(schema, 
table).name());
+        }
+        Assertions.assertTrue(TABLE_OPERATIONS.drop(schema, table));
+        Assertions.assertFalse(TABLE_OPERATIONS.drop(schema, table));
+      } finally {
+        JdbcConnectorUtils.executeUpdate(admin, "DROP SCHEMA IF EXISTS " + 
schema + " CASCADE");
+        JdbcConnectorUtils.executeUpdate(admin, "DROP ROLE IF EXISTS " + role);
+      }
+    }
+  }
+
   @Test
   void testLeastPrivilegeCreateDistinguishesMissingSchemaFromForbidden() 
throws SQLException {
     String role = RandomNameUtils.genRandomName("limited_role");

Reply via email to