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 da2b0b00edaef..3487e58a64678 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.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 cachedSqlInfo = - Collections.synchronizedMap(new EnumMap<>(SqlInfo.class)); + private final AtomicBoolean isCachePopulated = new AtomicBoolean(false); + private final Map cachedSqlInfo = new EnumMap<>(SqlInfo.class); private static final Map sqlTypesToFlightEnumConvertTypes = new HashMap<>(); static { @@ -729,10 +729,15 @@ private T getSqlInfoAndCacheIfCacheIsEmpty(final SqlInfo sqlInfoCommand, final Class 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 @@ private T getSqlInfoAndCacheIfCacheIsEmpty(final SqlInfo sqlInfoCommand, 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 75a7396931c8e..2b65f8f5a07ba 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 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 void getStreamPrimaryKeys(final CommandGetPrimaryKeys commandGetPrimaryKe 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 FlightInfo getFlightInfoCrossReference( final CommandGetCrossReference commandGetCrossReference, final CallContext callContext, final FlightDescriptor flightDescriptor) { - return getFightInfoExportedAndImportedKeys(commandGetCrossReference, flightDescriptor); + return getFlightInfoExportedAndImportedKeys(commandGetCrossReference, flightDescriptor); } @Override