You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@polaris.apache.org by di...@apache.org on 2026/06/25 15:58:07 UTC

(polaris) branch main updated: Follow-up to #4795: use transaction connection for SELECT in inheritable policy check (#4810)

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

dimas-b pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/polaris.git


The following commit(s) were added to refs/heads/main by this push:
     new f89897481b Follow-up to #4795: use transaction connection for SELECT in inheritable policy check (#4810)
f89897481b is described below

commit f89897481ba9d3df956e2fb6a6614f63d3647a74
Author: vignesh_A <16...@users.noreply.github.com>
AuthorDate: Thu Jun 25 21:28:02 2026 +0530

    Follow-up to #4795: use transaction connection for SELECT in inheritable policy check (#4810)
    
    * Make policy mapping SELECT use transaction connection for inheritable policies (follow-up to #4795)
    
    - Added connection-aware executeSelect / executeSelectOverStream in DatasourceOperations
    - Added private fetch + public overload of loadPoliciesOnTargetByType that accepts Connection
    - Updated handleInheritablePolicy to pass the tx connection for the SELECT
    
    This addresses the review comment that the SELECT should also participate in the same transaction as the insert/update.
    
    * Address review: add @NonNull to Connection parameter in connection-aware executeSelect* and fetch methods
    
    * Extract private helper for SELECT, make overload package-private, add unit tests
    
    - Extract duplicated prepare/bind/execute logic into private executeSelectOverStreamWithConnection
    - Change connection-aware loadPoliciesOnTargetByType to package-private
    - Add unit tests for the new Connection-aware SELECT paths
    
    Follow-up to #4795
---
 .../relational/jdbc/DatasourceOperations.java      | 64 ++++++++++++++++++----
 .../relational/jdbc/JdbcBasePersistenceImpl.java   | 44 ++++++++++++++-
 .../relational/jdbc/DatasourceOperationsTest.java  | 32 +++++++++++
 3 files changed, 127 insertions(+), 13 deletions(-)

diff --git a/persistence/relational-jdbc/src/main/java/org/apache/polaris/persistence/relational/jdbc/DatasourceOperations.java b/persistence/relational-jdbc/src/main/java/org/apache/polaris/persistence/relational/jdbc/DatasourceOperations.java
index cea7f58b95..372ccfe1ca 100644
--- a/persistence/relational-jdbc/src/main/java/org/apache/polaris/persistence/relational/jdbc/DatasourceOperations.java
+++ b/persistence/relational-jdbc/src/main/java/org/apache/polaris/persistence/relational/jdbc/DatasourceOperations.java
@@ -173,22 +173,62 @@ public class DatasourceOperations {
       throws SQLException {
     withRetries(
         () -> {
-          logQuery(query);
-          try (Connection connection = borrowConnection();
-              PreparedStatement statement = connection.prepareStatement(query.sql())) {
-            List<Object> params = query.parameters();
-            for (int i = 0; i < params.size(); i++) {
-              statement.setObject(i + 1, params.get(i));
-            }
-            try (ResultSet resultSet = statement.executeQuery()) {
-              ResultSetIterator<T> iterator = new ResultSetIterator<>(resultSet, converterInstance);
-              consumer.accept(iterator.toStream());
-              return null;
-            }
+          try (Connection connection = borrowConnection()) {
+            executeSelectOverStreamWithConnection(query, converterInstance, consumer, connection);
+            return null;
           }
         });
   }
 
+  /** Connection-aware version for use inside runWithinTransaction. */
+  public <T> void executeSelectOverStream(
+      @NonNull Connection connection,
+      @NonNull PreparedQuery query,
+      @NonNull Converter<T> converterInstance,
+      @NonNull Consumer<Stream<T>> consumer)
+      throws SQLException {
+    withRetries(
+        () -> {
+          executeSelectOverStreamWithConnection(query, converterInstance, consumer, connection);
+          return null;
+        });
+  }
+
+  /**
+   * Internal implementation that executes the SELECT on the provided connection. Does not manage
+   * connection lifecycle or retries.
+   */
+  private <T> void executeSelectOverStreamWithConnection(
+      @NonNull PreparedQuery query,
+      @NonNull Converter<T> converterInstance,
+      @NonNull Consumer<Stream<T>> consumer,
+      @NonNull Connection connection)
+      throws SQLException {
+    logQuery(query);
+    try (PreparedStatement statement = connection.prepareStatement(query.sql())) {
+      List<Object> params = query.parameters();
+      for (int i = 0; i < params.size(); i++) {
+        statement.setObject(i + 1, params.get(i));
+      }
+      try (ResultSet resultSet = statement.executeQuery()) {
+        ResultSetIterator<T> iterator = new ResultSetIterator<>(resultSet, converterInstance);
+        consumer.accept(iterator.toStream());
+      }
+    }
+  }
+
+  /** Connection-aware version for use inside runWithinTransaction. */
+  public <T> List<T> executeSelect(
+      @NonNull Connection connection,
+      @NonNull PreparedQuery query,
+      @NonNull Converter<T> converterInstance)
+      throws SQLException {
+    ArrayList<T> results = new ArrayList<>();
+    executeSelectOverStream(
+        connection, query, converterInstance, stream -> stream.forEach(results::add));
+    return results;
+  }
+
   /**
    * Executes the UPDATE or INSERT Query
    *
diff --git a/persistence/relational-jdbc/src/main/java/org/apache/polaris/persistence/relational/jdbc/JdbcBasePersistenceImpl.java b/persistence/relational-jdbc/src/main/java/org/apache/polaris/persistence/relational/jdbc/JdbcBasePersistenceImpl.java
index b9bcc45141..fcc59c8e67 100644
--- a/persistence/relational-jdbc/src/main/java/org/apache/polaris/persistence/relational/jdbc/JdbcBasePersistenceImpl.java
+++ b/persistence/relational-jdbc/src/main/java/org/apache/polaris/persistence/relational/jdbc/JdbcBasePersistenceImpl.java
@@ -1084,7 +1084,11 @@ public class JdbcBasePersistenceImpl
       throws SQLException {
     List<PolarisPolicyMappingRecord> existingRecords =
         loadPoliciesOnTargetByType(
-            callCtx, record.getTargetCatalogId(), record.getTargetId(), record.getPolicyTypeCode());
+            callCtx,
+            record.getTargetCatalogId(),
+            record.getTargetId(),
+            record.getPolicyTypeCode(),
+            connection);
     if (existingRecords.size() > 1) {
       throw new PolicyMappingAlreadyExistsException(existingRecords.getFirst());
     } else if (existingRecords.size() == 1) {
@@ -1231,6 +1235,44 @@ public class JdbcBasePersistenceImpl
             ModelPolicyMappingRecord.ALL_COLUMNS, ModelPolicyMappingRecord.TABLE_NAME, params));
   }
 
+  /**
+   * Connection-aware version for use inside runWithinTransaction (for inheritable policy check).
+   */
+  private List<PolarisPolicyMappingRecord> fetchPolicyMappingRecords(
+      QueryGenerator.PreparedQuery query, @NonNull Connection connection) {
+    try {
+      var results =
+          datasourceOperations.executeSelect(connection, query, new ModelPolicyMappingRecord());
+      return results == null ? Collections.emptyList() : results;
+    } catch (SQLException e) {
+      throw new RuntimeException(
+          String.format("Failed to retrieve policy mapping records %s", e.getMessage()), e);
+    }
+  }
+
+  /** Connection-aware overload for use inside transaction. */
+  @NonNull List<PolarisPolicyMappingRecord> loadPoliciesOnTargetByType(
+      @NonNull PolarisCallContext callCtx,
+      long targetCatalogId,
+      long targetId,
+      int policyTypeCode,
+      @NonNull Connection connection) {
+    Map<String, Object> params =
+        Map.of(
+            "target_catalog_id",
+            targetCatalogId,
+            "target_id",
+            targetId,
+            "policy_type_code",
+            policyTypeCode,
+            "realm_id",
+            realmId);
+    return fetchPolicyMappingRecords(
+        QueryGenerator.generateSelectQuery(
+            ModelPolicyMappingRecord.ALL_COLUMNS, ModelPolicyMappingRecord.TABLE_NAME, params),
+        connection);
+  }
+
   @NonNull
   @Override
   public List<PolarisPolicyMappingRecord> loadAllPoliciesOnTarget(
diff --git a/persistence/relational-jdbc/src/test/java/org/apache/polaris/persistence/relational/jdbc/DatasourceOperationsTest.java b/persistence/relational-jdbc/src/test/java/org/apache/polaris/persistence/relational/jdbc/DatasourceOperationsTest.java
index 200579cabe..29bbd6c49b 100644
--- a/persistence/relational-jdbc/src/test/java/org/apache/polaris/persistence/relational/jdbc/DatasourceOperationsTest.java
+++ b/persistence/relational-jdbc/src/test/java/org/apache/polaris/persistence/relational/jdbc/DatasourceOperationsTest.java
@@ -144,6 +144,38 @@ public class DatasourceOperationsTest {
         SQLException.class, () -> datasourceOperations.executeSelect(query, new ModelEntity(1)));
   }
 
+  @Test
+  void testExecuteSelect_withConnection_usesProvidedConnection() throws Exception {
+    QueryGenerator.PreparedQuery query =
+        new QueryGenerator.PreparedQuery("SELECT * FROM users", List.of());
+    when(mockConnection.prepareStatement(query.sql())).thenReturn(mockPreparedStatement);
+    when(mockPreparedStatement.executeQuery()).thenThrow(new SQLException("demo", "42P07"));
+
+    // The connection-aware overload must use the caller-provided Connection
+    // (no internal borrow should occur for the SELECT itself).
+    assertThrows(
+        SQLException.class,
+        () -> datasourceOperations.executeSelect(mockConnection, query, new ModelEntity(1)));
+
+    verify(mockConnection).prepareStatement(query.sql());
+  }
+
+  @Test
+  void testExecuteSelectOverStream_withConnection_usesProvidedConnection() throws Exception {
+    QueryGenerator.PreparedQuery query =
+        new QueryGenerator.PreparedQuery("SELECT * FROM users", List.of());
+    when(mockConnection.prepareStatement(query.sql())).thenReturn(mockPreparedStatement);
+    when(mockPreparedStatement.executeQuery()).thenThrow(new SQLException("stream demo", "42P07"));
+
+    assertThrows(
+        SQLException.class,
+        () ->
+            datasourceOperations.executeSelectOverStream(
+                mockConnection, query, new ModelEntity(1), stream -> stream.forEach(x -> {})));
+
+    verify(mockConnection).prepareStatement(query.sql());
+  }
+
   @Test
   void testRunWithinTransaction_commit() throws Exception {
     // reset to ignore constructor interaction