This is an automated email from the ASF dual-hosted git repository. github-actions[bot] pushed a commit to branch cherry-pick-441e590b-to-branch-1.3 in repository https://gitbox.apache.org/repos/asf/gravitino.git
commit 71bc106293ea9e9e94ad1388191d2b926f357956 Author: StormSpirit <[email protected]> AuthorDate: Mon Sep 14 10:35:49 2026 +0800 [#12915] fix(clickhouse): skip function-based index expressions on load (#12917) ### What changes were proposed in this pull request? This pull request stops the ClickHouse index metadata parser from recursively removing function wrappers. Direct column identifiers and tuples containing only identifiers retain their existing behavior, while function expressions such as `lower(name)` are rejected by the existing field validation and handled by the existing per-index warning-and-skip path. The previous wrapper-unwrapping behavior is retained in a sharding-key-specific helper so function-wrapped sharding keys such as `cityHash64(user_id)` remain unchanged. Unsupported index warnings retain the index, table, and type context without logging the raw expression, `type_full`, or parser exception. Bloom-filter parameter parsing remains outside this skip boundary, so malformed parameter metadata continues to fail with index context. Unit coverage verifies rejection of direct and nested function expressions, including hash functions, and mixed tuples, while retaining existing arithmetic-expression rejection and simple/tuple compatibility coverage. A focused ClickHouse integration test verifies that a native expression index remains present in the source DDL, is not exposed as a different column index, and is not recreated with changed semantics. ### Why are the changes needed? Gravitino's public `Index.fieldNames()` contract can represent column references but not arbitrary ClickHouse expressions. Reporting `lower(name)` as the field `name` fabricates metadata and causes load-to-create round trips to produce a semantically different index. Skipping the unsupported index preserves metadata correctness without adding a new expression API or SQL parser. Fix: #12915 ### Does this PR introduce _any_ user-facing change? Yes. Loading a ClickHouse table no longer exposes a function-based data-skipping index as a plain-column Gravitino index. Unsupported expression indexes are omitted with a warning while the table remains loadable. Simple identifier and identifier-only tuple indexes are unchanged, and no public API or property key is added or removed. ### How was this patch tested? - `./gradlew :catalogs-contrib:catalog-jdbc-clickhouse:spotlessCheck --console=plain` - passed. - `./gradlew rat --console=plain` - passed. - `./gradlew :catalogs-contrib:catalog-jdbc-clickhouse:test -PskipITs --console=plain` - passed with 100 tests, 0 skipped, 0 failures, and 0 errors. - `./gradlew :catalogs-contrib:catalog-jdbc-clickhouse:test --tests 'org.apache.gravitino.catalog.clickhouse.integration.test.CatalogClickHouseIT.testLoadExpressionIndexDoesNotFabricateColumnIndex' -PskipDockerTests=false --console=plain --no-daemon` - passed against ClickHouse 24.8.14 with 1 test, 0 skipped, 0 failures, and 0 errors. - `./gradlew :catalogs-contrib:catalog-jdbc-clickhouse:test --tests 'org.apache.gravitino.catalog.clickhouse.integration.test.CatalogClickHouseClusterIT' -PskipDockerTests=false --console=plain --no-daemon` - passed against ClickHouse 24.8.14 with 19 tests, 0 skipped, 0 failures, and 0 errors. - `./gradlew :catalogs-contrib:catalog-jdbc-clickhouse:build -x test --console=plain` - passed. - `git diff --check` - passed. Signed-off-by: jiangxt2 <[email protected]> Co-authored-by: Qi Yu <[email protected]> # Conflicts: # catalogs-contrib/catalog-jdbc-clickhouse/src/main/java/org/apache/gravitino/catalog/clickhouse/operations/ClickHouseTableOperations.java # catalogs-contrib/catalog-jdbc-clickhouse/src/test/java/org/apache/gravitino/catalog/clickhouse/operations/TestClickHouseTableOperationsUnit.java --- .../operations/ClickHouseTableOperations.java | 24 ++++ .../operations/ClickHouseTableSqlUtils.java | 33 ++--- .../integration/test/CatalogClickHouseIT.java | 59 +++++++++ .../operations/TestClickHouseTableOperations.java | 4 +- .../TestClickHouseTableOperationsIndexParsing.java | 21 +++- .../TestClickHouseTableOperationsUnit.java | 137 +++++++++++++++++++++ 6 files changed, 255 insertions(+), 23 deletions(-) diff --git a/catalogs-contrib/catalog-jdbc-clickhouse/src/main/java/org/apache/gravitino/catalog/clickhouse/operations/ClickHouseTableOperations.java b/catalogs-contrib/catalog-jdbc-clickhouse/src/main/java/org/apache/gravitino/catalog/clickhouse/operations/ClickHouseTableOperations.java index 835b6d9fa6..b01debdd50 100644 --- a/catalogs-contrib/catalog-jdbc-clickhouse/src/main/java/org/apache/gravitino/catalog/clickhouse/operations/ClickHouseTableOperations.java +++ b/catalogs-contrib/catalog-jdbc-clickhouse/src/main/java/org/apache/gravitino/catalog/clickhouse/operations/ClickHouseTableOperations.java @@ -1734,6 +1734,7 @@ public class ClickHouseTableOperations extends JdbcTableOperations { String expression = resultSet.getString("expr"); long granularity = resultSet.getLong("granularity"); try { +<<<<<<< HEAD String[][] fields = parseIndexFields(expression); if (ArrayUtils.isEmpty(fields)) { continue; @@ -1754,6 +1755,29 @@ public class ClickHouseTableOperations extends JdbcTableOperations { databaseName, tableName, expression); +======= + indexType = getClickHouseIndexType(type); + } catch (IllegalArgumentException ignored) { + LOG.warn( + "Skip unsupported data skipping index {} for {}.{} with unsupported type {}", + name, + databaseName, + tableName, + type); + continue; + } + try { + fields = parseIndexFields(expression); + } catch (IllegalArgumentException ignored) { + LOG.warn( + "Skip unsupported data skipping index {} for {}.{} with type {} because its " + + "expression cannot be represented as index field names", + name, + databaseName, + tableName, + type); + continue; +>>>>>>> 441e590b9 ([#12915] fix(clickhouse): skip function-based index expressions on load (#12917)) } } } diff --git a/catalogs-contrib/catalog-jdbc-clickhouse/src/main/java/org/apache/gravitino/catalog/clickhouse/operations/ClickHouseTableSqlUtils.java b/catalogs-contrib/catalog-jdbc-clickhouse/src/main/java/org/apache/gravitino/catalog/clickhouse/operations/ClickHouseTableSqlUtils.java index f90d607f23..9681c0d301 100644 --- a/catalogs-contrib/catalog-jdbc-clickhouse/src/main/java/org/apache/gravitino/catalog/clickhouse/operations/ClickHouseTableSqlUtils.java +++ b/catalogs-contrib/catalog-jdbc-clickhouse/src/main/java/org/apache/gravitino/catalog/clickhouse/operations/ClickHouseTableSqlUtils.java @@ -84,7 +84,7 @@ final class ClickHouseTableSqlUtils { } static List<String> extractShardingKeyColumns(String shardingKey) { - String normalized = normalizeIndexExpression(shardingKey); + String normalized = normalizeShardingKeyExpression(shardingKey); if (StringUtils.isBlank(normalized)) { return Collections.emptyList(); } @@ -136,23 +136,10 @@ final class ClickHouseTableSqlUtils { } static String normalizeIndexExpression(String expression) { - String trimmed = expression.trim(); - - boolean stripped = true; - String current = trimmed; - while (stripped) { - stripped = false; - Matcher matcher = FUNCTION_WRAPPER_PATTERN.matcher(current); - if (matcher.matches()) { - current = matcher.group(2).trim(); - stripped = true; - } - } + String current = expression.trim(); if (StringUtils.startsWithIgnoreCase(current, "tuple(") && StringUtils.endsWith(current, ")")) { current = current.substring("tuple(".length(), current.length() - 1).trim(); - } else if (StringUtils.equalsIgnoreCase(current, "tuple()")) { - current = ""; } return current; @@ -175,6 +162,22 @@ final class ClickHouseTableSqlUtils { return StringUtils.isNotBlank(identifier) && identifier.matches("^[a-zA-Z_][a-zA-Z0-9_]*$"); } + private static String normalizeShardingKeyExpression(String expression) { + String current = expression.trim(); + + boolean stripped = true; + while (stripped) { + stripped = false; + Matcher matcher = FUNCTION_WRAPPER_PATTERN.matcher(current); + if (matcher.matches()) { + current = matcher.group(2).trim(); + stripped = true; + } + } + + return normalizeIndexExpression(current); + } + private static Transform parsePartitionExpression( String expression, String originalPartitionKey) { String trimmedExpression = StringUtils.trim(expression); diff --git a/catalogs-contrib/catalog-jdbc-clickhouse/src/test/java/org/apache/gravitino/catalog/clickhouse/integration/test/CatalogClickHouseIT.java b/catalogs-contrib/catalog-jdbc-clickhouse/src/test/java/org/apache/gravitino/catalog/clickhouse/integration/test/CatalogClickHouseIT.java index 45057b5eff..bd2cd475a5 100644 --- a/catalogs-contrib/catalog-jdbc-clickhouse/src/test/java/org/apache/gravitino/catalog/clickhouse/integration/test/CatalogClickHouseIT.java +++ b/catalogs-contrib/catalog-jdbc-clickhouse/src/test/java/org/apache/gravitino/catalog/clickhouse/integration/test/CatalogClickHouseIT.java @@ -485,6 +485,65 @@ public class CatalogClickHouseIT extends BaseIT { && Arrays.deepEquals(idx.fieldNames(), new String[][] {{"amount"}}))); } + @Test + void testLoadExpressionIndexDoesNotFabricateColumnIndex() { + String sourceTableName = GravitinoITUtils.genRandomName("expression_index_source"); + String recreatedTableName = GravitinoITUtils.genRandomName("expression_index_recreated"); + clickhouseService.executeQuery( + String.format( + "CREATE TABLE `%s`.`%s` (" + + "id UInt64, " + + "name String, " + + "INDEX idx_name name TYPE minmax GRANULARITY 1, " + + "INDEX idx_lower lower(name) TYPE minmax GRANULARITY 1" + + ") ENGINE = MergeTree ORDER BY id", + schemaName, sourceTableName)); + + String sourceCreateSql = + clickhouseService.executeQueryForResult( + String.format("SHOW CREATE TABLE `%s`.`%s`", schemaName, sourceTableName)); + String normalizedSourceCreateSql = sourceCreateSql.replace("`", "").replaceAll("\\s+", ""); + Assertions.assertTrue( + StringUtils.containsIgnoreCase( + normalizedSourceCreateSql, "INDEXidx_lowerlower(name)TYPEminmax"), + "Source table should retain its expression index: " + sourceCreateSql); + + TableCatalog tableCatalog = catalog.asTableCatalog(); + Table loaded = tableCatalog.loadTable(NameIdentifier.of(schemaName, sourceTableName)); + Index[] loadedIndexes = loaded.index(); + Index loadedSimpleIndex = + Arrays.stream(loadedIndexes) + .filter(index -> "idx_name".equals(index.name())) + .findFirst() + .orElseThrow(); + Assertions.assertEquals(Index.IndexType.DATA_SKIPPING_MINMAX, loadedSimpleIndex.type()); + Assertions.assertArrayEquals(new String[][] {{"name"}}, loadedSimpleIndex.fieldNames()); + Assertions.assertFalse( + Arrays.stream(loadedIndexes).anyMatch(index -> "idx_lower".equals(index.name()))); + + tableCatalog.createTable( + NameIdentifier.of(schemaName, recreatedTableName), + loaded.columns(), + loaded.comment(), + loaded.properties(), + loaded.partitioning(), + loaded.distribution(), + loaded.sortOrder(), + loaded.index()); + + String recreatedCreateSql = + clickhouseService.executeQueryForResult( + String.format("SHOW CREATE TABLE `%s`.`%s`", schemaName, recreatedTableName)); + String normalizedRecreatedCreateSql = + recreatedCreateSql.replace("`", "").replaceAll("\\s+", ""); + Assertions.assertTrue( + StringUtils.containsIgnoreCase(normalizedRecreatedCreateSql, "INDEXidx_namenametypeMINMAX"), + "Recreated table should retain the simple index: " + recreatedCreateSql); + Assertions.assertFalse( + StringUtils.containsIgnoreCase(normalizedRecreatedCreateSql, "idx_lower"), + "Recreated table must not contain a fabricated replacement index: " + recreatedCreateSql); + } + @Test void testCreateAndLoadCompositePrimaryKey() { String table = GravitinoITUtils.genRandomName("composite_primary_key"); diff --git a/catalogs-contrib/catalog-jdbc-clickhouse/src/test/java/org/apache/gravitino/catalog/clickhouse/operations/TestClickHouseTableOperations.java b/catalogs-contrib/catalog-jdbc-clickhouse/src/test/java/org/apache/gravitino/catalog/clickhouse/operations/TestClickHouseTableOperations.java index 5f3fdbd8ea..fc476b4056 100644 --- a/catalogs-contrib/catalog-jdbc-clickhouse/src/test/java/org/apache/gravitino/catalog/clickhouse/operations/TestClickHouseTableOperations.java +++ b/catalogs-contrib/catalog-jdbc-clickhouse/src/test/java/org/apache/gravitino/catalog/clickhouse/operations/TestClickHouseTableOperations.java @@ -1357,8 +1357,8 @@ public class TestClickHouseTableOperations extends TestClickHouse { String[][] fields = ops.parseIndexFields("tuple(`c2`, `c3`)"); Assertions.assertArrayEquals(new String[][] {{"c2"}, {"c3"}}, fields); - String[][] bloomFields = ops.parseIndexFields("bloom_filter(`c4`)"); - Assertions.assertArrayEquals(new String[][] {{"c4"}}, bloomFields); + Assertions.assertThrows( + IllegalArgumentException.class, () -> ops.parseIndexFields("bloom_filter(`c4`)")); } @Test diff --git a/catalogs-contrib/catalog-jdbc-clickhouse/src/test/java/org/apache/gravitino/catalog/clickhouse/operations/TestClickHouseTableOperationsIndexParsing.java b/catalogs-contrib/catalog-jdbc-clickhouse/src/test/java/org/apache/gravitino/catalog/clickhouse/operations/TestClickHouseTableOperationsIndexParsing.java index 83ad206a7f..90fc4334ea 100644 --- a/catalogs-contrib/catalog-jdbc-clickhouse/src/test/java/org/apache/gravitino/catalog/clickhouse/operations/TestClickHouseTableOperationsIndexParsing.java +++ b/catalogs-contrib/catalog-jdbc-clickhouse/src/test/java/org/apache/gravitino/catalog/clickhouse/operations/TestClickHouseTableOperationsIndexParsing.java @@ -29,17 +29,23 @@ public class TestClickHouseTableOperationsIndexParsing { String[][] single = operations.parseIndexFields("col_1"); Assertions.assertArrayEquals(new String[][] {{"col_1"}}, single); + String[][] quoted = operations.parseIndexFields("`quoted_col`"); + Assertions.assertArrayEquals(new String[][] {{"quoted_col"}}, quoted); + String[][] tuple = operations.parseIndexFields("tuple(`a`, b)"); Assertions.assertArrayEquals(new String[][] {{"a"}, {"b"}}, tuple); } @Test - public void testParseFunctionWrappedExpression() { - String[][] bloom = operations.parseIndexFields("bloom_filter(cityHash64(user_id))"); - Assertions.assertArrayEquals(new String[][] {{"user_id"}}, bloom); - - String[][] nested = operations.parseIndexFields("minmax(lower(`tenant_id`))"); - Assertions.assertArrayEquals(new String[][] {{"tenant_id"}}, nested); + public void testRejectFunctionWrappedExpression() { + Assertions.assertThrows( + IllegalArgumentException.class, () -> operations.parseIndexFields("lower(name)")); + Assertions.assertThrows( + IllegalArgumentException.class, + () -> operations.parseIndexFields("bloom_filter(cityHash64(user_id))")); + Assertions.assertThrows( + IllegalArgumentException.class, + () -> operations.parseIndexFields("minmax(lower(`tenant_id`))")); } @Test @@ -52,5 +58,8 @@ public class TestClickHouseTableOperationsIndexParsing { public void testUnsupportedExpression() { Assertions.assertThrows( IllegalArgumentException.class, () -> operations.parseIndexFields("cityHash64(id) % 16")); + Assertions.assertThrows( + IllegalArgumentException.class, + () -> operations.parseIndexFields("tuple(lower(name), id)")); } } diff --git a/catalogs-contrib/catalog-jdbc-clickhouse/src/test/java/org/apache/gravitino/catalog/clickhouse/operations/TestClickHouseTableOperationsUnit.java b/catalogs-contrib/catalog-jdbc-clickhouse/src/test/java/org/apache/gravitino/catalog/clickhouse/operations/TestClickHouseTableOperationsUnit.java index 357d2783ee..da7762d11e 100644 --- a/catalogs-contrib/catalog-jdbc-clickhouse/src/test/java/org/apache/gravitino/catalog/clickhouse/operations/TestClickHouseTableOperationsUnit.java +++ b/catalogs-contrib/catalog-jdbc-clickhouse/src/test/java/org/apache/gravitino/catalog/clickhouse/operations/TestClickHouseTableOperationsUnit.java @@ -715,6 +715,143 @@ public class TestClickHouseTableOperationsUnit { Assertions.assertTrue(exception.getMessage().contains("balanced")); } +<<<<<<< HEAD +======= + @Test + void testGetIndexesFailsOnMalformedParameterizedIndexMetadata() throws Exception { + ExposedClickHouseTableOperations ops = newOps(); + + PreparedStatement primaryKeyStmt = Mockito.mock(PreparedStatement.class); + ResultSet primaryKeyRs = Mockito.mock(ResultSet.class); + PreparedStatement secondaryStmt = Mockito.mock(PreparedStatement.class); + ResultSet secondaryRs = Mockito.mock(ResultSet.class); + + Mockito.when(primaryKeyRs.next()).thenReturn(false); + Mockito.when(primaryKeyStmt.executeQuery()).thenReturn(primaryKeyRs); + Mockito.when(secondaryRs.next()).thenReturn(true, false); + Mockito.when(secondaryStmt.executeQuery()).thenReturn(secondaryRs); + Mockito.when(secondaryRs.getString("name")).thenReturn("idx_bad"); + Mockito.when(secondaryRs.getString("type")).thenReturn("ngrambf_v1"); + Mockito.when(secondaryRs.getString("type_full")).thenReturn(null); + Mockito.when(secondaryRs.getString("expr")).thenReturn("col_1"); + Mockito.when(secondaryRs.getLong("granularity")).thenReturn(1L); + + Connection connection = Mockito.mock(Connection.class); + Mockito.when(connection.prepareStatement(Mockito.anyString())) + .thenReturn(primaryKeyStmt) + .thenReturn(secondaryStmt); + + IllegalArgumentException exception = + Assertions.assertThrows( + IllegalArgumentException.class, () -> ops.callGetIndexes(connection, "db", "tbl")); + Assertions.assertTrue(exception.getMessage().contains("idx_bad")); + Assertions.assertTrue(exception.getMessage().contains("type_full")); + } + + @Test + void testGetIndexesSkipsUnsupportedExpressionForParameterizedIndex() throws Exception { + ExposedClickHouseTableOperations ops = newOps(); + + PreparedStatement primaryKeyStmt = Mockito.mock(PreparedStatement.class); + ResultSet primaryKeyRs = Mockito.mock(ResultSet.class); + PreparedStatement secondaryStmt = Mockito.mock(PreparedStatement.class); + ResultSet secondaryRs = Mockito.mock(ResultSet.class); + + Mockito.when(primaryKeyRs.next()).thenReturn(false); + Mockito.when(primaryKeyStmt.executeQuery()).thenReturn(primaryKeyRs); + Mockito.when(secondaryRs.next()).thenReturn(true, true, false); + Mockito.when(secondaryStmt.executeQuery()).thenReturn(secondaryRs); + Mockito.when(secondaryRs.getString("name")).thenReturn("idx_bad_expr", "idx_valid"); + Mockito.when(secondaryRs.getString("type")).thenReturn("ngrambf_v1", "tokenbf_v1"); + Mockito.when(secondaryRs.getString("type_full")) + .thenReturn("ngrambf_v1(3, 512, 3, 0)", "tokenbf_v1(256, 2, 0)"); + Mockito.when(secondaryRs.getString("expr")).thenReturn("lower(col_1)", "col_2"); + Mockito.when(secondaryRs.getLong("granularity")).thenReturn(1L, 1L); + + Connection connection = Mockito.mock(Connection.class); + Mockito.when(connection.prepareStatement(Mockito.anyString())) + .thenReturn(primaryKeyStmt) + .thenReturn(secondaryStmt); + + List<Index> indexes = ops.callGetIndexes(connection, "db", "tbl"); + + Assertions.assertEquals(1, indexes.size()); + Assertions.assertEquals("idx_valid", indexes.get(0).name()); + Assertions.assertEquals(Index.IndexType.DATA_SKIPPING_TOKENBFV1, indexes.get(0).type()); + Assertions.assertArrayEquals(new String[][] {{"col_2"}}, indexes.get(0).fieldNames()); + Assertions.assertEquals( + Map.of( + "bloom_filter_size", "256", + "hash_functions", "2", + "random_seed", "0"), + indexes.get(0).properties()); + } + + @Test + void testGetIndexesFallsBackWhenTypeFullColumnIsMissing() throws Exception { + ExposedClickHouseTableOperations ops = newOps(); + + PreparedStatement primaryKeyStmt = Mockito.mock(PreparedStatement.class); + ResultSet primaryKeyRs = Mockito.mock(ResultSet.class); + PreparedStatement modernSecondaryStmt = Mockito.mock(PreparedStatement.class); + PreparedStatement legacySecondaryStmt = Mockito.mock(PreparedStatement.class); + ResultSet legacySecondaryRs = Mockito.mock(ResultSet.class); + + Mockito.when(primaryKeyRs.next()).thenReturn(false); + Mockito.when(primaryKeyStmt.executeQuery()).thenReturn(primaryKeyRs); + Mockito.when(modernSecondaryStmt.executeQuery()) + .thenThrow(new SQLException("Unknown identifier 'type_full'")); + Mockito.when(legacySecondaryStmt.executeQuery()).thenReturn(legacySecondaryRs); + Mockito.when(legacySecondaryRs.next()).thenReturn(true, false); + Mockito.when(legacySecondaryRs.getString("name")).thenReturn("idx_legacy"); + Mockito.when(legacySecondaryRs.getString("type")).thenReturn("ngrambf_v1(3, 512, 3, 0)"); + Mockito.when(legacySecondaryRs.getString("expr")).thenReturn("col_1"); + Mockito.when(legacySecondaryRs.getLong("granularity")).thenReturn(1L); + + Connection connection = Mockito.mock(Connection.class); + Mockito.when(connection.prepareStatement(Mockito.anyString())) + .thenReturn(primaryKeyStmt) + .thenReturn(modernSecondaryStmt) + .thenReturn(legacySecondaryStmt); + + List<Index> indexes = ops.callGetIndexes(connection, "db", "tbl"); + + Assertions.assertEquals(1, indexes.size()); + Assertions.assertEquals(Index.IndexType.DATA_SKIPPING_NGRAMBFV1, indexes.get(0).type()); + Assertions.assertEquals( + Map.of( + "ngram_size", "3", + "bloom_filter_size", "512", + "hash_functions", "3", + "random_seed", "0"), + indexes.get(0).properties()); + } + + @Test + void testGetIndexesDoesNotFallbackForOtherSqlErrors() throws Exception { + ExposedClickHouseTableOperations ops = newOps(); + + PreparedStatement primaryKeyStmt = Mockito.mock(PreparedStatement.class); + ResultSet primaryKeyRs = Mockito.mock(ResultSet.class); + PreparedStatement secondaryStmt = Mockito.mock(PreparedStatement.class); + Mockito.when(primaryKeyRs.next()).thenReturn(false); + Mockito.when(primaryKeyStmt.executeQuery()).thenReturn(primaryKeyRs); + Mockito.when(secondaryStmt.executeQuery()) + .thenThrow(new SQLException("Connection reset by peer")); + + Connection connection = Mockito.mock(Connection.class); + Mockito.when(connection.prepareStatement(Mockito.anyString())) + .thenReturn(primaryKeyStmt) + .thenReturn(secondaryStmt); + + GravitinoRuntimeException exception = + Assertions.assertThrows( + GravitinoRuntimeException.class, () -> ops.callGetIndexes(connection, "db", "tbl")); + Assertions.assertTrue(exception.getCause() instanceof SQLException); + Mockito.verify(connection, Mockito.times(2)).prepareStatement(Mockito.anyString()); + } + +>>>>>>> 441e590b9 ([#12915] fix(clickhouse): skip function-based index expressions on load (#12917)) private RenameMocks renameMocks(String storedComment, String engineFull) throws Exception { DataSource dataSource = Mockito.mock(DataSource.class); Connection connection = Mockito.mock(Connection.class);
