diff --git a/plugin/trino-base-jdbc/src/main/java/io/trino/plugin/jdbc/BaseJdbcConfig.java b/plugin/trino-base-jdbc/src/main/java/io/trino/plugin/jdbc/BaseJdbcConfig.java index 6dd954d24f2f..be6474a37c7d 100644 --- a/plugin/trino-base-jdbc/src/main/java/io/trino/plugin/jdbc/BaseJdbcConfig.java +++ b/plugin/trino-base-jdbc/src/main/java/io/trino/plugin/jdbc/BaseJdbcConfig.java @@ -20,27 +20,31 @@ import io.airlift.units.Duration; import io.airlift.units.MinDuration; -import javax.annotation.PostConstruct; +import javax.validation.constraints.AssertTrue; import javax.validation.constraints.Min; import javax.validation.constraints.NotNull; import javax.validation.constraints.Pattern; +import java.util.Optional; import java.util.Set; import static com.google.common.base.Strings.nullToEmpty; -import static java.lang.String.format; import static java.util.concurrent.TimeUnit.MILLISECONDS; import static javax.validation.constraints.Pattern.Flag.CASE_INSENSITIVE; public class BaseJdbcConfig { public static final String METADATA_CACHE_TTL = "metadata.cache-ttl"; + public static final String METADATA_SCHEMAS_CACHE_TTL = "metadata.schemas.cache-ttl"; + public static final String METADATA_TABLES_CACHE_TTL = "metadata.tables.cache-ttl"; public static final String METADATA_CACHE_MAXIMUM_SIZE = "metadata.cache-maximum-size"; private String connectionUrl; private Set jdbcTypesMappedToVarchar = ImmutableSet.of(); public static final Duration CACHING_DISABLED = new Duration(0, MILLISECONDS); private Duration metadataCacheTtl = CACHING_DISABLED; + private Optional schemaNamesCacheTtl = Optional.empty(); + private Optional tableNamesCacheTtl = Optional.empty(); private boolean cacheMissing; public static final long DEFAULT_METADATA_CACHE_SIZE = 10000; private long cacheMaximumSize = DEFAULT_METADATA_CACHE_SIZE; @@ -87,6 +91,34 @@ public BaseJdbcConfig setMetadataCacheTtl(Duration metadataCacheTtl) return this; } + @NotNull + public Duration getSchemaNamesCacheTtl() + { + return schemaNamesCacheTtl.orElse(metadataCacheTtl); + } + + @Config(METADATA_SCHEMAS_CACHE_TTL) + @ConfigDescription("Determines how long schema names list information will be cached") + public BaseJdbcConfig setSchemaNamesCacheTtl(Duration schemaNamesCacheTtl) + { + this.schemaNamesCacheTtl = Optional.ofNullable(schemaNamesCacheTtl); + return this; + } + + @NotNull + public Duration getTableNamesCacheTtl() + { + return tableNamesCacheTtl.orElse(metadataCacheTtl); + } + + @Config(METADATA_TABLES_CACHE_TTL) + @ConfigDescription("Determines how long table names list information will be cached") + public BaseJdbcConfig setTableNamesCacheTtl(Duration tableNamesCacheTtl) + { + this.tableNamesCacheTtl = Optional.ofNullable(tableNamesCacheTtl); + return this; + } + public boolean isCacheMissing() { return cacheMissing; @@ -114,12 +146,21 @@ public BaseJdbcConfig setCacheMaximumSize(long cacheMaximumSize) return this; } - @PostConstruct - public void validate() + @AssertTrue(message = METADATA_CACHE_TTL + " must be set to a non-zero value when " + METADATA_CACHE_MAXIMUM_SIZE + " is set") + public boolean isCacheMaximumSizeConsistent() + { + return !metadataCacheTtl.equals(CACHING_DISABLED) || cacheMaximumSize == BaseJdbcConfig.DEFAULT_METADATA_CACHE_SIZE; + } + + @AssertTrue(message = METADATA_SCHEMAS_CACHE_TTL + " must not be set when " + METADATA_CACHE_TTL + " is not set") + public boolean isSchemaNamesCacheTtlConsistent() + { + return !metadataCacheTtl.equals(CACHING_DISABLED) || schemaNamesCacheTtl.isEmpty(); + } + + @AssertTrue(message = METADATA_TABLES_CACHE_TTL + " must not be set when " + METADATA_CACHE_TTL + " is not set") + public boolean isTableNamesCacheTtlConsistent() { - if (metadataCacheTtl.equals(CACHING_DISABLED) && cacheMaximumSize != BaseJdbcConfig.DEFAULT_METADATA_CACHE_SIZE) { - throw new IllegalArgumentException( - format("%s must be set to a non-zero value when %s is set", METADATA_CACHE_TTL, METADATA_CACHE_MAXIMUM_SIZE)); - } + return !metadataCacheTtl.equals(CACHING_DISABLED) || tableNamesCacheTtl.isEmpty(); } } diff --git a/plugin/trino-base-jdbc/src/main/java/io/trino/plugin/jdbc/CachingJdbcClient.java b/plugin/trino-base-jdbc/src/main/java/io/trino/plugin/jdbc/CachingJdbcClient.java index be069cce6b5f..3dc3a6ab087d 100644 --- a/plugin/trino-base-jdbc/src/main/java/io/trino/plugin/jdbc/CachingJdbcClient.java +++ b/plugin/trino-base-jdbc/src/main/java/io/trino/plugin/jdbc/CachingJdbcClient.java @@ -93,7 +93,15 @@ public CachingJdbcClient( IdentityCacheMapping identityMapping, BaseJdbcConfig config) { - this(delegate, sessionPropertiesProviders, identityMapping, config.getMetadataCacheTtl(), config.isCacheMissing(), config.getCacheMaximumSize()); + this( + delegate, + sessionPropertiesProviders, + identityMapping, + config.getMetadataCacheTtl(), + config.getSchemaNamesCacheTtl(), + config.getTableNamesCacheTtl(), + config.isCacheMissing(), + config.getCacheMaximumSize()); } public CachingJdbcClient( @@ -103,6 +111,26 @@ public CachingJdbcClient( Duration metadataCachingTtl, boolean cacheMissing, long cacheMaximumSize) + { + this(delegate, + sessionPropertiesProviders, + identityMapping, + metadataCachingTtl, + metadataCachingTtl, + metadataCachingTtl, + cacheMissing, + cacheMaximumSize); + } + + public CachingJdbcClient( + JdbcClient delegate, + Set sessionPropertiesProviders, + IdentityCacheMapping identityMapping, + Duration metadataCachingTtl, + Duration schemaNamesCachingTtl, + Duration tableNamesCachingTtl, + boolean cacheMissing, + long cacheMaximumSize) { this.delegate = requireNonNull(delegate, "delegate is null"); this.sessionProperties = sessionPropertiesProviders.stream() @@ -111,25 +139,27 @@ public CachingJdbcClient( this.cacheMissing = cacheMissing; this.identityMapping = requireNonNull(identityMapping, "identityMapping is null"); - EvictableCacheBuilder cacheBuilder = EvictableCacheBuilder.newBuilder() - .expireAfterWrite(metadataCachingTtl.toMillis(), MILLISECONDS) - .shareNothingWhenDisabled() - .recordStats(); + long cacheSize = metadataCachingTtl.equals(CACHING_DISABLED) + // Disables the cache entirely + ? 0 + : cacheMaximumSize; - if (metadataCachingTtl.equals(CACHING_DISABLED)) { - // Disables the cache entirely - cacheBuilder.maximumSize(0); - } - else { - cacheBuilder.maximumSize(cacheMaximumSize); - } + schemaNamesCache = buildCache(cacheSize, schemaNamesCachingTtl); + tableNamesCache = buildCache(cacheSize, tableNamesCachingTtl); + tableHandlesByNameCache = buildCache(cacheSize, metadataCachingTtl); + tableHandlesByQueryCache = buildCache(cacheSize, metadataCachingTtl); + columnsCache = buildCache(cacheSize, metadataCachingTtl); + statisticsCache = buildCache(cacheSize, metadataCachingTtl); + } - schemaNamesCache = cacheBuilder.build(); - tableNamesCache = cacheBuilder.build(); - tableHandlesByNameCache = cacheBuilder.build(); - tableHandlesByQueryCache = cacheBuilder.build(); - columnsCache = cacheBuilder.build(); - statisticsCache = cacheBuilder.build(); + private static Cache buildCache(long cacheSize, Duration cachingTtl) + { + return EvictableCacheBuilder.newBuilder() + .maximumSize(cacheSize) + .expireAfterWrite(cachingTtl.toMillis(), MILLISECONDS) + .shareNothingWhenDisabled() + .recordStats() + .build(); } @Override @@ -583,6 +613,12 @@ private void invalidateColumnsCache(SchemaTableName table) invalidateCache(columnsCache, key -> key.table.equals(table)); } + @VisibleForTesting + CacheStats getSchemaNamesCacheStats() + { + return schemaNamesCache.stats(); + } + @VisibleForTesting CacheStats getTableNamesCacheStats() { diff --git a/plugin/trino-base-jdbc/src/test/java/io/trino/plugin/jdbc/TestBaseJdbcConfig.java b/plugin/trino-base-jdbc/src/test/java/io/trino/plugin/jdbc/TestBaseJdbcConfig.java index c78ca674cc0d..7aa257e57ca6 100644 --- a/plugin/trino-base-jdbc/src/test/java/io/trino/plugin/jdbc/TestBaseJdbcConfig.java +++ b/plugin/trino-base-jdbc/src/test/java/io/trino/plugin/jdbc/TestBaseJdbcConfig.java @@ -19,11 +19,15 @@ import io.airlift.units.Duration; import org.testng.annotations.Test; +import javax.validation.constraints.AssertTrue; + import java.util.Map; import static io.airlift.configuration.testing.ConfigAssertions.assertFullMapping; import static io.airlift.configuration.testing.ConfigAssertions.assertRecordedDefaults; import static io.airlift.configuration.testing.ConfigAssertions.recordDefaults; +import static io.airlift.testing.ValidationAssertions.assertFailsValidation; +import static io.airlift.testing.ValidationAssertions.assertValidates; import static java.util.concurrent.TimeUnit.MINUTES; import static java.util.concurrent.TimeUnit.SECONDS; import static org.assertj.core.api.Assertions.assertThatThrownBy; @@ -40,6 +44,8 @@ public void testDefaults() .setConnectionUrl(null) .setJdbcTypesMappedToVarchar("") .setMetadataCacheTtl(ZERO) + .setSchemaNamesCacheTtl(null) + .setTableNamesCacheTtl(null) .setCacheMissing(false) .setCacheMaximumSize(10000)); } @@ -51,6 +57,8 @@ public void testExplicitPropertyMappings() .put("connection-url", "jdbc:h2:mem:config") .put("jdbc-types-mapped-to-varchar", "mytype,struct_type1") .put("metadata.cache-ttl", "1s") + .put("metadata.schemas.cache-ttl", "2s") + .put("metadata.tables.cache-ttl", "3s") .put("metadata.cache-missing", "true") .put("metadata.cache-maximum-size", "5000") .buildOrThrow(); @@ -59,6 +67,8 @@ public void testExplicitPropertyMappings() .setConnectionUrl("jdbc:h2:mem:config") .setJdbcTypesMappedToVarchar("mytype, struct_type1") .setMetadataCacheTtl(new Duration(1, SECONDS)) + .setSchemaNamesCacheTtl(new Duration(2, SECONDS)) + .setTableNamesCacheTtl(new Duration(3, SECONDS)) .setCacheMissing(true) .setCacheMaximumSize(5000); @@ -81,19 +91,34 @@ public void testConnectionUrlIsValid() @Test public void testCacheConfigValidation() { - BaseJdbcConfig explicitCacheSize = new BaseJdbcConfig() + assertValidates(new BaseJdbcConfig() + .setConnectionUrl("jdbc:h2:mem:config") .setMetadataCacheTtl(new Duration(1, SECONDS)) - .setCacheMaximumSize(5000); - explicitCacheSize.validate(); - BaseJdbcConfig defaultCacheSize = new BaseJdbcConfig() - .setMetadataCacheTtl(new Duration(1, SECONDS)); - defaultCacheSize.validate(); - assertThatThrownBy(() -> new BaseJdbcConfig() - .setMetadataCacheTtl(ZERO) - .setCacheMaximumSize(100000) - .validate()) - .isInstanceOf(IllegalArgumentException.class) - .hasMessageMatching("metadata.cache-ttl must be set to a non-zero value when metadata.cache-maximum-size is set"); + .setSchemaNamesCacheTtl(new Duration(2, SECONDS)) + .setTableNamesCacheTtl(new Duration(3, SECONDS)) + .setCacheMaximumSize(5000)); + + assertValidates(new BaseJdbcConfig() + .setConnectionUrl("jdbc:h2:mem:config") + .setMetadataCacheTtl(new Duration(1, SECONDS))); + + assertFailsValidation(new BaseJdbcConfig() + .setCacheMaximumSize(5000), + "cacheMaximumSizeConsistent", + "metadata.cache-ttl must be set to a non-zero value when metadata.cache-maximum-size is set", + AssertTrue.class); + + assertFailsValidation(new BaseJdbcConfig() + .setSchemaNamesCacheTtl(new Duration(1, SECONDS)), + "schemaNamesCacheTtlConsistent", + "metadata.schemas.cache-ttl must not be set when metadata.cache-ttl is not set", + AssertTrue.class); + + assertFailsValidation(new BaseJdbcConfig() + .setTableNamesCacheTtl(new Duration(1, SECONDS)), + "tableNamesCacheTtlConsistent", + "metadata.tables.cache-ttl must not be set when metadata.cache-ttl is not set", + AssertTrue.class); } private static void buildConfig(Map properties) diff --git a/plugin/trino-base-jdbc/src/test/java/io/trino/plugin/jdbc/TestCachingJdbcClient.java b/plugin/trino-base-jdbc/src/test/java/io/trino/plugin/jdbc/TestCachingJdbcClient.java index 3df1a2c8e71e..4ef2e806fc83 100644 --- a/plugin/trino-base-jdbc/src/test/java/io/trino/plugin/jdbc/TestCachingJdbcClient.java +++ b/plugin/trino-base-jdbc/src/test/java/io/trino/plugin/jdbc/TestCachingJdbcClient.java @@ -54,14 +54,12 @@ import static com.google.common.collect.ImmutableMap.toImmutableMap; import static com.google.common.collect.MoreCollectors.onlyElement; import static io.airlift.concurrent.Threads.daemonThreadsNamed; -import static io.trino.plugin.jdbc.TestCachingJdbcClient.CachingJdbcCache.STATISTICS_CACHE; -import static io.trino.plugin.jdbc.TestCachingJdbcClient.CachingJdbcCache.TABLE_HANDLES_BY_NAME_CACHE; -import static io.trino.plugin.jdbc.TestCachingJdbcClient.CachingJdbcCache.TABLE_HANDLES_BY_QUERY_CACHE; import static io.trino.spi.session.PropertyMetadata.stringProperty; import static io.trino.spi.testing.InterfaceTestUtils.assertAllMethodsOverridden; import static io.trino.spi.type.IntegerType.INTEGER; import static io.trino.testing.TestingConnectorSession.builder; import static io.trino.testing.TestingNames.randomNameSuffix; +import static io.trino.testing.assertions.Assert.assertEventually; import static java.lang.String.format; import static java.util.Collections.emptyList; import static java.util.Objects.requireNonNull; @@ -113,9 +111,30 @@ public void setUp() executor = newCachedThreadPool(daemonThreadsNamed("TestCachingJdbcClient-%s")); } - private CachingJdbcClient createCachingJdbcClient(Duration cacheTtl, boolean cacheMissing, long cacheMaximumSize) + private CachingJdbcClient createCachingJdbcClient( + Duration cacheTtl, + boolean cacheMissing, + long cacheMaximumSize) { - return new CachingJdbcClient(database.getJdbcClient(), SESSION_PROPERTIES_PROVIDERS, new SingletonIdentityCacheMapping(), cacheTtl, cacheMissing, cacheMaximumSize); + return createCachingJdbcClient(cacheTtl, cacheTtl, cacheTtl, cacheMissing, cacheMaximumSize); + } + + private CachingJdbcClient createCachingJdbcClient( + Duration cacheTtl, + Duration schemasCacheTtl, + Duration tablesCacheTtl, + boolean cacheMissing, + long cacheMaximumSize) + { + return new CachingJdbcClient( + database.getJdbcClient(), + SESSION_PROPERTIES_PROVIDERS, + new SingletonIdentityCacheMapping(), + cacheTtl, + schemasCacheTtl, + tablesCacheTtl, + cacheMissing, + cacheMaximumSize); } private CachingJdbcClient createCachingJdbcClient(boolean cacheMissing, long cacheMaximumSize) @@ -139,11 +158,20 @@ public void testSchemaNamesCached() String phantomSchema = "phantom_schema"; jdbcClient.createSchema(SESSION, phantomSchema); - assertThat(cachingJdbcClient.getSchemaNames(SESSION)).contains(phantomSchema); + assertSchemaNamesCache(cachingJdbcClient) + .misses(1) + .loads(1) + .afterRunning(() -> { + assertThat(cachingJdbcClient.getSchemaNames(SESSION)).contains(phantomSchema); + }); jdbcClient.dropSchema(SESSION, phantomSchema); assertThat(jdbcClient.getSchemaNames(SESSION)).doesNotContain(phantomSchema); - assertThat(cachingJdbcClient.getSchemaNames(SESSION)).contains(phantomSchema); + assertSchemaNamesCache(cachingJdbcClient) + .hits(1) + .afterRunning(() -> { + assertThat(cachingJdbcClient.getSchemaNames(SESSION)).contains(phantomSchema); + }); } @Test @@ -152,11 +180,20 @@ public void testTableNamesCached() SchemaTableName phantomTable = new SchemaTableName(schema, "phantom_table"); createTable(phantomTable); - assertThat(cachingJdbcClient.getTableNames(SESSION, Optional.of(schema))).contains(phantomTable); + assertTableNamesCache(cachingJdbcClient) + .misses(1) + .loads(1) + .afterRunning(() -> { + assertThat(cachingJdbcClient.getTableNames(SESSION, Optional.of(schema))).contains(phantomTable); + }); dropTable(phantomTable); assertThat(jdbcClient.getTableNames(SESSION, Optional.of(schema))).doesNotContain(phantomTable); - assertThat(cachingJdbcClient.getTableNames(SESSION, Optional.of(schema))).contains(phantomTable); + assertTableNamesCache(cachingJdbcClient) + .hits(1) + .afterRunning(() -> { + assertThat(cachingJdbcClient.getTableNames(SESSION, Optional.of(schema))).contains(phantomTable); + }); } @Test @@ -180,7 +217,7 @@ public void testTableHandleOfQueryCached() createTable(phantomTable); PreparedQuery query = new PreparedQuery(format("SELECT * FROM %s.phantom_table", schema), ImmutableList.of()); - JdbcTableHandle cachedTable = assertCacheStats(cachingJdbcClient, TABLE_HANDLES_BY_QUERY_CACHE) + JdbcTableHandle cachedTable = assertTableHandleByQueryCache(cachingJdbcClient) .misses(1) .loads(1) .calling(() -> cachingJdbcClient.getTableHandle(SESSION, query)); @@ -200,7 +237,7 @@ public void testTableHandleOfQueryCached() assertThatThrownBy(() -> jdbcClient.getTableHandle(SESSION, query)) .hasMessageContaining("Failed to get table handle for prepared query"); - assertCacheStats(cachingJdbcClient, TABLE_HANDLES_BY_QUERY_CACHE) + assertTableHandleByQueryCache(cachingJdbcClient) .hits(1) .afterRunning(() -> { assertThat(cachingJdbcClient.getTableHandle(SESSION, query)) @@ -222,7 +259,7 @@ public void testTableHandleOfQueryCached() cachingJdbcClient.createTable(SESSION, new ConnectorTableMetadata(phantomTable, emptyList())); - assertCacheStats(cachingJdbcClient, TABLE_HANDLES_BY_QUERY_CACHE) + assertTableHandleByQueryCache(cachingJdbcClient) .misses(1) .loads(1) .afterRunning(() -> { @@ -246,7 +283,7 @@ public void testTableHandleOfQueryCached() cachingJdbcClient.onDataChanged(phantomTable); - assertCacheStats(cachingJdbcClient, TABLE_HANDLES_BY_QUERY_CACHE) + assertTableHandleByQueryCache(cachingJdbcClient) .hits(1) .afterRunning(() -> { assertThat(cachingJdbcClient.getTableHandle(SESSION, query)) @@ -294,10 +331,10 @@ private void assertTableHandlesByNameCacheIsInvalidated(JdbcTableHandle table) { SchemaTableName tableName = table.asPlainTable().getSchemaTableName(); - assertCacheStats(cachingJdbcClient, TABLE_HANDLES_BY_NAME_CACHE).misses(1).loads(1).afterRunning(() -> { + assertTableHandleByNameCache(cachingJdbcClient).misses(1).loads(1).afterRunning(() -> { assertThat(cachingJdbcClient.getTableHandle(SESSION, tableName).orElseThrow()).isEqualTo(table); }); - assertCacheStats(cachingJdbcClient, TABLE_HANDLES_BY_NAME_CACHE).hits(1).afterRunning(() -> { + assertTableHandleByNameCache(cachingJdbcClient).hits(1).afterRunning(() -> { assertThat(cachingJdbcClient.getTableHandle(SESSION, tableName).orElseThrow()).isEqualTo(table); }); } @@ -838,6 +875,103 @@ public Optional getTableHandle(ConnectorSession session, Schema "com.google.common.util.concurrent.UncheckedExecutionException: java.lang.RuntimeException: first attempt is poised to fail"); } + @Test + public void testSpecificSchemaAndTableCaches() + { + CachingJdbcClient cachingJdbcClient = createCachingJdbcClient( + FOREVER, + Duration.succinctDuration(3, SECONDS), + Duration.succinctDuration(2, SECONDS), + false, // decreased ttl for schema and table names mostly makes sense with cacheMissing == false + 10000); + String secondSchema = schema + "_two"; + SchemaTableName firstName = new SchemaTableName(schema, "first_table"); + SchemaTableName secondName = new SchemaTableName(secondSchema, "second_table"); + + ConnectorSession session = createSession("asession"); + JdbcTableHandle first = createTable(firstName); + + // load schema names, tables names, table handles + assertSchemaNamesCache(cachingJdbcClient).loads(1).misses(1).afterRunning(() -> { + assertThat(cachingJdbcClient.getSchemaNames(session)) + .contains(schema) + .doesNotContain(secondSchema); + }); + assertTableNamesCache(cachingJdbcClient).loads(1).misses(1).afterRunning(() -> { + assertThat(cachingJdbcClient.getTableNames(session, Optional.empty())) + .contains(firstName) + .doesNotContain(secondName); + }); + assertTableHandleByNameCache(cachingJdbcClient).misses(1).loads(1).afterRunning(() -> { + assertThat(cachingJdbcClient.getTableHandle(session, firstName)).isNotEmpty(); + }); + assertTableHandleByNameCache(cachingJdbcClient).misses(1).loads(1).afterRunning(() -> { + assertThat(cachingJdbcClient.getTableHandle(session, secondName)).isEmpty(); + }); + + jdbcClient.createSchema(SESSION, secondSchema); + JdbcTableHandle second = createTable(secondName); + + // cached schema names, table names, table handles + assertSchemaNamesCache(cachingJdbcClient).hits(1).afterRunning(() -> { + assertThat(cachingJdbcClient.getSchemaNames(session)) + .contains(schema) + .doesNotContain(secondSchema); + }); + assertTableNamesCache(cachingJdbcClient).hits(1).afterRunning(() -> { + assertThat(cachingJdbcClient.getTableNames(session, Optional.empty())) + .contains(firstName) + .doesNotContain(secondName); + }); + assertTableHandleByNameCache(cachingJdbcClient).hits(1).afterRunning(() -> { + assertThat(cachingJdbcClient.getTableHandle(session, firstName)).isNotEmpty(); + }); + assertTableHandleByNameCache(cachingJdbcClient).hits(1).misses(1).loads(1).afterRunning(() -> { + assertThat(cachingJdbcClient.getTableHandle(session, secondName)).isNotEmpty(); + }); + + // reloads table names, retains schema names and table handles + assertEventually(Duration.succinctDuration(10, SECONDS), () -> { + assertSchemaNamesCache(cachingJdbcClient).hits(1).afterRunning(() -> { + assertThat(cachingJdbcClient.getSchemaNames(session)) + .contains(schema) + .doesNotContain(secondSchema); + }); + assertTableNamesCache(cachingJdbcClient).loads(1).misses(1).afterRunning(() -> { + assertThat(cachingJdbcClient.getTableNames(session, Optional.empty())) + .contains(firstName, secondName); + }); + assertTableHandleByNameCache(cachingJdbcClient).hits(1).afterRunning(() -> { + assertThat(cachingJdbcClient.getTableHandle(session, firstName)).isNotEmpty(); + }); + assertTableHandleByNameCache(cachingJdbcClient).hits(1).afterRunning(() -> { + assertThat(cachingJdbcClient.getTableHandle(session, secondName)).isNotEmpty(); + }); + }); + + // reloads tables names and schema names, but retains table handles + assertEventually(Duration.succinctDuration(10, SECONDS), () -> { + assertSchemaNamesCache(cachingJdbcClient).loads(1).misses(1).afterRunning(() -> { + assertThat(cachingJdbcClient.getSchemaNames(session)) + .contains(schema, secondSchema); + }); + assertTableNamesCache(cachingJdbcClient).loads(1).misses(1).afterRunning(() -> { + assertThat(cachingJdbcClient.getTableNames(session, Optional.empty())) + .contains(firstName, secondName); + }); + assertTableHandleByNameCache(cachingJdbcClient).hits(1).afterRunning(() -> { + assertThat(cachingJdbcClient.getTableHandle(session, firstName)).isNotEmpty(); + }); + assertTableHandleByNameCache(cachingJdbcClient).hits(1).afterRunning(() -> { + assertThat(cachingJdbcClient.getTableHandle(session, secondName)).isNotEmpty(); + }); + }); + + jdbcClient.dropTable(SESSION, first); + jdbcClient.dropTable(SESSION, second); + jdbcClient.dropSchema(SESSION, secondSchema); + } + private JdbcTableHandle getAnyTable(String schema) { SchemaTableName tableName = jdbcClient.getTableNames(SESSION, Optional.of(schema)) @@ -891,11 +1025,26 @@ public void testEverythingImplemented() assertAllMethodsOverridden(JdbcClient.class, CachingJdbcClient.class); } + private static SingleJdbcCacheStatsAssertions assertSchemaNamesCache(CachingJdbcClient client) + { + return assertCacheStats(client, CachingJdbcCache.SCHEMA_NAMES_CACHE); + } + private static SingleJdbcCacheStatsAssertions assertTableNamesCache(CachingJdbcClient client) { return assertCacheStats(client, CachingJdbcCache.TABLE_NAMES_CACHE); } + private static SingleJdbcCacheStatsAssertions assertTableHandleByNameCache(CachingJdbcClient client) + { + return assertCacheStats(client, CachingJdbcCache.TABLE_HANDLES_BY_NAME_CACHE); + } + + private static SingleJdbcCacheStatsAssertions assertTableHandleByQueryCache(CachingJdbcClient client) + { + return assertCacheStats(client, CachingJdbcCache.TABLE_HANDLES_BY_QUERY_CACHE); + } + private static SingleJdbcCacheStatsAssertions assertColumnCacheStats(CachingJdbcClient client) { return assertCacheStats(client, CachingJdbcCache.COLUMNS_CACHE); @@ -903,7 +1052,7 @@ private static SingleJdbcCacheStatsAssertions assertColumnCacheStats(CachingJdbc private static SingleJdbcCacheStatsAssertions assertStatisticsCacheStats(CachingJdbcClient client) { - return assertCacheStats(client, STATISTICS_CACHE); + return assertCacheStats(client, CachingJdbcCache.STATISTICS_CACHE); } private static SingleJdbcCacheStatsAssertions assertCacheStats(CachingJdbcClient client, CachingJdbcCache cache) @@ -1026,6 +1175,7 @@ public T calling(Callable callable) enum CachingJdbcCache { + SCHEMA_NAMES_CACHE(CachingJdbcClient::getSchemaNamesCacheStats), TABLE_NAMES_CACHE(CachingJdbcClient::getTableNamesCacheStats), TABLE_HANDLES_BY_NAME_CACHE(CachingJdbcClient::getTableHandlesByNameCacheStats), TABLE_HANDLES_BY_QUERY_CACHE(CachingJdbcClient::getTableHandlesByQueryCacheStats),