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

lidavidm pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/arrow.git


The following commit(s) were added to refs/heads/main by this push:
     new 5583dbeca4 GH-38737: [Java] Fix JDBC caching of SqlInfo values (#38739)
5583dbeca4 is described below

commit 5583dbeca4f36d6eadba979c619c4a07dbb2095f
Author: James Duong <[email protected]>
AuthorDate: Thu Nov 16 06:20:12 2023 -0800

    GH-38737: [Java] Fix JDBC caching of SqlInfo values (#38739)
    
    ### Rationale for this change
    The cache of SqlInfo properties that ArrowDatabaseMetaData maintains isn't 
populated in a thread-safe way. This can cause JDBC applications trying to 
retrieve several properties from DatabaseMetaData to encounter missing 
properties when they shouldn't.
    
    ### What changes are included in this PR?
    - Changed the checking for the cache being populated to be based on an 
AtomicBoolean marking that the cache is fully populated, rather than just 
checking if the cache is empty.
    - Avoid having multiple threads call getSqlInfo() if they see that the 
cache is empty concurrently.
    
    ### Are these changes tested?
    Verified existing unit tests.
    
    ### Are there any user-facing changes?
    No.
    * Closes: #38737
    
    Authored-by: James Duong <[email protected]>
    Signed-off-by: David Li <[email protected]>
---
 .../apache/arrow/driver/jdbc/ArrowDatabaseMetadata.java  | 16 +++++++++++-----
 .../arrow/driver/jdbc/utils/MockFlightSqlProducer.java   | 10 +++++-----
 2 files changed, 16 insertions(+), 10 deletions(-)

diff --git 
a/java/flight/flight-sql-jdbc-core/src/main/java/org/apache/arrow/driver/jdbc/ArrowDatabaseMetadata.java
 
b/java/flight/flight-sql-jdbc-core/src/main/java/org/apache/arrow/driver/jdbc/ArrowDatabaseMetadata.java
index da2b0b00ed..3487e58a64 100644
--- 
a/java/flight/flight-sql-jdbc-core/src/main/java/org/apache/arrow/driver/jdbc/ArrowDatabaseMetadata.java
+++ 
b/java/flight/flight-sql-jdbc-core/src/main/java/org/apache/arrow/driver/jdbc/ArrowDatabaseMetadata.java
@@ -45,11 +45,11 @@ import java.sql.DatabaseMetaData;
 import java.sql.ResultSet;
 import java.sql.SQLException;
 import java.util.Arrays;
-import java.util.Collections;
 import java.util.EnumMap;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
+import java.util.concurrent.atomic.AtomicBoolean;
 import java.util.regex.Pattern;
 import java.util.stream.Collectors;
 
@@ -145,8 +145,8 @@ public class ArrowDatabaseMetadata extends 
AvaticaDatabaseMetaData {
           Field.notNullable("IS_AUTOINCREMENT", 
Types.MinorType.VARCHAR.getType()),
           Field.notNullable("IS_GENERATEDCOLUMN", 
Types.MinorType.VARCHAR.getType())
       ));
-  private final Map<SqlInfo, Object> cachedSqlInfo =
-      Collections.synchronizedMap(new EnumMap<>(SqlInfo.class));
+  private final AtomicBoolean isCachePopulated = new AtomicBoolean(false);
+  private final Map<SqlInfo, Object> cachedSqlInfo = new 
EnumMap<>(SqlInfo.class);
   private static final Map<Integer, Integer> sqlTypesToFlightEnumConvertTypes 
= new HashMap<>();
 
   static {
@@ -729,10 +729,15 @@ public class ArrowDatabaseMetadata extends 
AvaticaDatabaseMetaData {
                                                  final Class<T> desiredType)
       throws SQLException {
     final ArrowFlightConnection connection = getConnection();
-    if (cachedSqlInfo.isEmpty()) {
-      final FlightInfo sqlInfo = connection.getClientHandler().getSqlInfo();
+    if (!isCachePopulated.get()) {
+      // Lock-and-populate the cache. Only issue the call to getSqlInfo() once,
+      // populate the cache, then mark it as populated.
+      // Note that multiple callers from separate threads can see that the 
cache is not populated, but only
+      // one thread will try to populate the cache. Other threads will see the 
cache is non-empty when acquiring
+      // the lock on the cache and skip population.
       synchronized (cachedSqlInfo) {
         if (cachedSqlInfo.isEmpty()) {
+          final FlightInfo sqlInfo = 
connection.getClientHandler().getSqlInfo();
           try (final ResultSet resultSet =
                    ArrowFlightJdbcFlightStreamResultSet.fromFlightInfo(
                        connection, sqlInfo, null)) {
@@ -741,6 +746,7 @@ public class ArrowDatabaseMetadata extends 
AvaticaDatabaseMetaData {
                   resultSet.getObject("value"));
             }
           }
+          isCachePopulated.set(true);
         }
       }
     }
diff --git 
a/java/flight/flight-sql-jdbc-core/src/test/java/org/apache/arrow/driver/jdbc/utils/MockFlightSqlProducer.java
 
b/java/flight/flight-sql-jdbc-core/src/test/java/org/apache/arrow/driver/jdbc/utils/MockFlightSqlProducer.java
index 75a7396931..2b65f8f5a0 100644
--- 
a/java/flight/flight-sql-jdbc-core/src/test/java/org/apache/arrow/driver/jdbc/utils/MockFlightSqlProducer.java
+++ 
b/java/flight/flight-sql-jdbc-core/src/test/java/org/apache/arrow/driver/jdbc/utils/MockFlightSqlProducer.java
@@ -105,8 +105,8 @@ public final class MockFlightSqlProducer implements 
FlightSqlProducer {
 
   private final Map<String, Integer> actionTypeCounter = new HashMap<>();
 
-  private static FlightInfo getFightInfoExportedAndImportedKeys(final Message 
message,
-                                                                final 
FlightDescriptor descriptor) {
+  private static FlightInfo getFlightInfoExportedAndImportedKeys(final Message 
message,
+                                                                 final 
FlightDescriptor descriptor) {
     return getFlightInfo(message, Schemas.GET_IMPORTED_KEYS_SCHEMA, 
descriptor);
   }
 
@@ -529,14 +529,14 @@ public final class MockFlightSqlProducer implements 
FlightSqlProducer {
   public FlightInfo getFlightInfoExportedKeys(final CommandGetExportedKeys 
commandGetExportedKeys,
                                               final CallContext callContext,
                                               final FlightDescriptor 
flightDescriptor) {
-    return getFightInfoExportedAndImportedKeys(commandGetExportedKeys, 
flightDescriptor);
+    return getFlightInfoExportedAndImportedKeys(commandGetExportedKeys, 
flightDescriptor);
   }
 
   @Override
   public FlightInfo getFlightInfoImportedKeys(final CommandGetImportedKeys 
commandGetImportedKeys,
                                               final CallContext callContext,
                                               final FlightDescriptor 
flightDescriptor) {
-    return getFightInfoExportedAndImportedKeys(commandGetImportedKeys, 
flightDescriptor);
+    return getFlightInfoExportedAndImportedKeys(commandGetImportedKeys, 
flightDescriptor);
   }
 
   @Override
@@ -544,7 +544,7 @@ public final class MockFlightSqlProducer implements 
FlightSqlProducer {
       final CommandGetCrossReference commandGetCrossReference,
       final CallContext callContext,
       final FlightDescriptor flightDescriptor) {
-    return getFightInfoExportedAndImportedKeys(commandGetCrossReference, 
flightDescriptor);
+    return getFlightInfoExportedAndImportedKeys(commandGetCrossReference, 
flightDescriptor);
   }
 
   @Override

Reply via email to