Skip to content

Commit 9888ac1

Browse files
rakhiagrRakhi AgrawalCopilot
authored
(feat) Add SchemaValidatorUtil to Enable Safe Decoupling from DB Schema Evolution (#537)
* Add SchemaValidatorUtil to Enable Safe Decoupling from DB Schema Evolution * Checkstyle * Checkstyle * Fix index * Fix index * Fix index * Fix index * Added TTL * Adding check column for filter/index * Adding unit test to SQLStatementUtilsTest * Adding unit test to EbeanLocalAccessTest * Checkstyle * Checkstyle * Fix unit test * Fix unit test * Checkstyle * Unit tests * Unit tests * Update dao-impl/ebean-dao/src/main/java/com/linkedin/metadata/dao/utils/SchemaValidatorUtil.java Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Update dao-impl/ebean-dao/src/main/java/com/linkedin/metadata/dao/utils/SchemaValidatorUtil.java Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Addressed comment --------- Co-authored-by: Rakhi Agrawal <rakagrawal@linkedin.com> Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
1 parent c034142 commit 9888ac1

File tree

10 files changed

+478
-71
lines changed

10 files changed

+478
-71
lines changed

dao-impl/ebean-dao/src/main/java/com/linkedin/metadata/dao/EbeanLocalAccess.java

Lines changed: 9 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
import com.linkedin.metadata.dao.utils.RecordUtils;
1313
import com.linkedin.metadata.dao.utils.SQLSchemaUtils;
1414
import com.linkedin.metadata.dao.utils.SQLStatementUtils;
15+
import com.linkedin.metadata.dao.utils.SchemaValidatorUtil;
1516
import com.linkedin.metadata.events.IngestionTrackingContext;
1617
import com.linkedin.metadata.query.ExtraInfo;
1718
import com.linkedin.metadata.query.ExtraInfoArray;
@@ -74,6 +75,7 @@ public class EbeanLocalAccess<URN extends Urn> implements IEbeanLocalAccess<URN>
7475
// key: table_name,
7576
// value: Set(column1, column2, column3 ...)
7677
private final Map<String, Set<String>> tableColumns = new ConcurrentHashMap<>();
78+
private final SchemaValidatorUtil validator;
7779

7880
public EbeanLocalAccess(EbeanServer server, ServerConfig serverConfig, @Nonnull Class<URN> urnClass,
7981
UrnPathExtractor<URN> urnPathExtractor, boolean nonDollarVirtualColumnsEnabled) {
@@ -83,6 +85,7 @@ public EbeanLocalAccess(EbeanServer server, ServerConfig serverConfig, @Nonnull
8385
_entityType = ModelUtils.getEntityTypeFromUrnClass(_urnClass);
8486
_schemaEvolutionManager = createSchemaEvolutionManager(serverConfig);
8587
_nonDollarVirtualColumnsEnabled = nonDollarVirtualColumnsEnabled;
88+
validator = new SchemaValidatorUtil(server);
8689
}
8790

8891
public void setUrnPathExtractor(@Nonnull UrnPathExtractor<URN> urnPathExtractor) {
@@ -276,7 +279,7 @@ public <ASPECT extends RecordTemplate> List<EbeanMetadataAspect> batchGetUnion(
276279
for (int index = position; index < end; index++) {
277280
final Urn entityUrn = aspectKeys.get(index).getUrn();
278281
final Class<ASPECT> aspectClass = (Class<ASPECT>) aspectKeys.get(index).getAspectClass();
279-
if (checkColumnExists(isTestMode ? getTestTableName(entityUrn) : getTableName(entityUrn),
282+
if (validator.columnExists(isTestMode ? getTestTableName(entityUrn) : getTableName(entityUrn),
280283
getAspectColumnName(entityUrn.getEntityType(), aspectClass))) {
281284
keysToQueryMap.computeIfAbsent(aspectClass, unused -> new HashSet<>()).add(entityUrn);
282285
}
@@ -326,7 +329,7 @@ public ListResult<URN> listUrns(@Nullable IndexFilter indexFilter, @Nullable Ind
326329
int start, int pageSize) {
327330
final SqlQuery sqlQuery = createFilterSqlQuery(indexFilter, indexSortCriterion, start, pageSize);
328331
final List<SqlRow> sqlRows = sqlQuery.findList();
329-
if (sqlRows.size() == 0) {
332+
if (sqlRows.isEmpty()) {
330333
final List<SqlRow> totalCountResults = createFilterSqlQuery(indexFilter, indexSortCriterion, 0, DEFAULT_PAGE_SIZE).findList();
331334
final int actualTotalCount = totalCountResults.isEmpty() ? 0 : totalCountResults.get(0).getInteger("_total_count");
332335
return toListResult(actualTotalCount, start, pageSize);
@@ -428,13 +431,13 @@ public Map<String, Long> countAggregate(@Nullable IndexFilter indexFilter,
428431
getGeneratedColumnName(_entityType, indexGroupByCriterion.getAspect(), indexGroupByCriterion.getPath(),
429432
_nonDollarVirtualColumnsEnabled);
430433
// first, check for existence of the column we want to GROUP BY
431-
if (!checkColumnExists(tableName, groupByColumn)) {
434+
if (!validator.columnExists(tableName, groupByColumn)) {
432435
// if we are trying to GROUP BY the results on a column that does not exist, just return an empty map
433436
return Collections.emptyMap();
434437
}
435438

436439
// now run the actual GROUP BY query
437-
final String groupBySql = SQLStatementUtils.createGroupBySql(_entityType, indexFilter, indexGroupByCriterion, _nonDollarVirtualColumnsEnabled);
440+
final String groupBySql = SQLStatementUtils.createGroupBySql(_entityType, indexFilter, indexGroupByCriterion, _nonDollarVirtualColumnsEnabled, validator);
438441
final SqlQuery sqlQuery = _server.createSqlQuery(groupBySql);
439442
final List<SqlRow> sqlRows = sqlQuery.findList();
440443
Map<String, Long> resultMap = new HashMap<>();
@@ -461,7 +464,7 @@ public Map<String, Long> countAggregate(@Nullable IndexFilter indexFilter,
461464
private SqlQuery createFilterSqlQuery(@Nullable IndexFilter indexFilter,
462465
@Nullable IndexSortCriterion indexSortCriterion, int offset, int pageSize) {
463466
StringBuilder filterSql = new StringBuilder();
464-
filterSql.append(SQLStatementUtils.createFilterSql(_entityType, indexFilter, true, _nonDollarVirtualColumnsEnabled));
467+
filterSql.append(SQLStatementUtils.createFilterSql(_entityType, indexFilter, true, _nonDollarVirtualColumnsEnabled, validator));
465468
filterSql.append("\n");
466469
filterSql.append(parseSortCriteria(_entityType, indexSortCriterion, _nonDollarVirtualColumnsEnabled));
467470
filterSql.append(String.format(" LIMIT %d", Math.max(pageSize, 0)));
@@ -475,7 +478,7 @@ private SqlQuery createFilterSqlQuery(@Nullable IndexFilter indexFilter,
475478
private SqlQuery createFilterSqlQuery(@Nullable IndexFilter indexFilter,
476479
@Nullable IndexSortCriterion indexSortCriterion, @Nullable URN lastUrn, int pageSize) {
477480
StringBuilder filterSql = new StringBuilder();
478-
filterSql.append(SQLStatementUtils.createFilterSql(_entityType, indexFilter, false, _nonDollarVirtualColumnsEnabled));
481+
filterSql.append(SQLStatementUtils.createFilterSql(_entityType, indexFilter, false, _nonDollarVirtualColumnsEnabled, validator));
479482

480483
if (lastUrn != null) {
481484
// because createFilterSql will only include a WHERE clause if there are non-urn filters, we need to make sure
@@ -621,23 +624,6 @@ private SchemaEvolutionManager createSchemaEvolutionManager(@Nonnull ServerConfi
621624
return new FlywaySchemaEvolutionManager(config);
622625
}
623626

624-
/**
625-
* Check column exists in table.
626-
*/
627-
public boolean checkColumnExists(@Nonnull String tableName, @Nonnull String columnName) {
628-
// Fetch table columns on very first read and cache it in tableColumns
629-
if (!tableColumns.containsKey(tableName)) {
630-
final List<SqlRow> rows = _server.createSqlQuery(SQLStatementUtils.getAllColumnForTable(tableName)).findList();
631-
Set<String> columns = new HashSet<>();
632-
for (SqlRow row : rows) {
633-
columns.add(row.getString("COLUMN_NAME").toLowerCase());
634-
}
635-
tableColumns.put(tableName, columns);
636-
}
637-
638-
return tableColumns.get(tableName).contains(columnName.toLowerCase());
639-
}
640-
641627
/**
642628
* SQL implementation of find the latest {@link EbeanMetadataAspect}.
643629
* @param connection {@link Connection} get from the current transaction, it should not be closed manually

dao-impl/ebean-dao/src/main/java/com/linkedin/metadata/dao/EbeanLocalRelationshipQueryDAO.java

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
import com.linkedin.metadata.dao.utils.RelationshipLookUpContext;
1313
import com.linkedin.metadata.dao.utils.SQLSchemaUtils;
1414
import com.linkedin.metadata.dao.utils.SQLStatementUtils;
15+
import com.linkedin.metadata.dao.utils.SchemaValidatorUtil;
1516
import com.linkedin.metadata.query.Condition;
1617
import com.linkedin.metadata.query.LocalRelationshipCriterion;
1718
import com.linkedin.metadata.query.LocalRelationshipFilter;
@@ -46,6 +47,7 @@ public class EbeanLocalRelationshipQueryDAO {
4647
public static final String RELATIONSHIP_RETURN_TYPE = "relationship.return.type";
4748
public static final String MG_INTERNAL_ASSET_RELATIONSHIP_TYPE = "AssetRelationship.proto";
4849
private static final int FILTER_BATCH_SIZE = 200;
50+
private static final String IDX_DESTINATION_DELETED_TS = "idx_destination_deleted_ts";
4951
private static final String FORCE_IDX_ON_DESTINATION = " FORCE INDEX (idx_destination_deleted_ts) ";
5052
private static final String DESTINATION_FIELD = "destination";
5153
private final EbeanServer _server;
@@ -55,17 +57,20 @@ public class EbeanLocalRelationshipQueryDAO {
5557

5658
private Set<String> _mgEntityTypeNameSet;
5759
private EbeanLocalDAO.SchemaConfig _schemaConfig = EbeanLocalDAO.SchemaConfig.NEW_SCHEMA_ONLY;
60+
private SchemaValidatorUtil _schemaValidatorUtil;
5861

5962
public EbeanLocalRelationshipQueryDAO(EbeanServer server, EBeanDAOConfig eBeanDAOConfig) {
6063
_server = server;
6164
_eBeanDAOConfig = eBeanDAOConfig;
6265
_sqlGenerator = new MultiHopsTraversalSqlGenerator(SUPPORTED_CONDITIONS);
66+
_schemaValidatorUtil = new SchemaValidatorUtil(server);
6367
}
6468

6569
public EbeanLocalRelationshipQueryDAO(EbeanServer server) {
6670
_server = server;
6771
_eBeanDAOConfig = new EBeanDAOConfig();
6872
_sqlGenerator = new MultiHopsTraversalSqlGenerator(SUPPORTED_CONDITIONS);
73+
_schemaValidatorUtil = new SchemaValidatorUtil(server);
6974
}
7075

7176
static final Map<Condition, String> SUPPORTED_CONDITIONS =
@@ -552,12 +557,14 @@ private String buildFindRelationshipSQL(
552557
// non-mg entity case, applying dest filter on relationship table
553558
filters.add(new Pair<>(destinationEntityFilter, "rt"));
554559
} else if (!relationshipFilter.getCriteria().isEmpty()) {
555-
//TODO: Add a safeguard to check if the FORCE_IDX_ON_DESTINATION is present in the table, we can check once on bootup and then caching the result
556-
// Check if any relationship-level filter is on "destination"
560+
// Apply FORCE INDEX if destination field is being filtered, and the index exists
557561
for (LocalRelationshipCriterion criterion : relationshipFilter.getCriteria()) {
558562
LocalRelationshipCriterion.Field field = criterion.getField();
559563
if (field.getUrnField() != null && DESTINATION_FIELD.equals(field.getUrnField().getName())) {
560-
sqlBuilder.append(FORCE_IDX_ON_DESTINATION);
564+
// Check if index exists on 'destination' before applying FORCE INDEX
565+
if (_schemaValidatorUtil.indexExists(relationshipTableName, IDX_DESTINATION_DELETED_TS)) {
566+
sqlBuilder.append(FORCE_IDX_ON_DESTINATION);
567+
}
561568
break;
562569
}
563570
}

dao-impl/ebean-dao/src/main/java/com/linkedin/metadata/dao/utils/SQLIndexFilterUtils.java

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
import java.util.stream.Collectors;
1616
import javax.annotation.Nonnull;
1717
import javax.annotation.Nullable;
18+
import lombok.extern.slf4j.Slf4j;
1819
import org.apache.commons.lang.StringEscapeUtils;
1920

2021
import static com.linkedin.metadata.dao.utils.SQLSchemaUtils.*;
@@ -24,6 +25,7 @@
2425
/**
2526
* Condition SQL script utils based on {@link IndexFilter}.
2627
*/
28+
@Slf4j
2729
public class SQLIndexFilterUtils {
2830

2931
private SQLIndexFilterUtils() {
@@ -90,11 +92,12 @@ public static String parseSortCriteria(@Nonnull String entityType, @Nullable Ind
9092
/**
9193
* Parse {@link IndexFilter} into MySQL syntax.
9294
* @param entityType entity type from the Urn
93-
* @param indexFilter index filter
95+
* @param indexFilter index filter
9496
* @param nonDollarVirtualColumnsEnabled whether to enable non-dollar virtual columns
9597
* @return translated SQL condition expression, e.g. WHERE ...
9698
*/
97-
public static String parseIndexFilter(@Nonnull String entityType, @Nullable IndexFilter indexFilter, boolean nonDollarVirtualColumnsEnabled) {
99+
public static String parseIndexFilter(@Nonnull String entityType, @Nullable IndexFilter indexFilter,
100+
boolean nonDollarVirtualColumnsEnabled, @Nonnull SchemaValidatorUtil schemaValidator) {
98101
List<String> sqlFilters = new ArrayList<>();
99102

100103
if (indexFilter == null || !indexFilter.hasCriteria()) {
@@ -115,6 +118,12 @@ public static String parseIndexFilter(@Nonnull String entityType, @Nullable Inde
115118
validateConditionAndValue(indexCriterion);
116119
final Condition condition = pathParams.getCondition();
117120
final String indexColumn = getGeneratedColumnName(entityType, aspect, pathParams.getPath(), nonDollarVirtualColumnsEnabled);
121+
final String tableName = SQLSchemaUtils.getTableName(entityType);
122+
// New: Skip filter if column doesn't exist
123+
if (!schemaValidator.columnExists(tableName, indexColumn)) {
124+
log.warn("Skipping filter: virtual column '{}' not found in table '{}'", indexColumn, tableName);
125+
continue;
126+
}
118127
sqlFilters.add(parseSqlFilter(indexColumn, condition, pathParams.getValue()));
119128
}
120129
}

dao-impl/ebean-dao/src/main/java/com/linkedin/metadata/dao/utils/SQLStatementUtils.java

Lines changed: 12 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
import javax.annotation.Nonnull;
2222
import javax.annotation.Nullable;
2323
import javax.annotation.ParametersAreNonnullByDefault;
24+
import lombok.extern.slf4j.Slf4j;
2425
import org.apache.commons.lang.StringEscapeUtils;
2526
import org.javatuples.Pair;
2627

@@ -31,6 +32,7 @@
3132
/**
3233
* SQL statement util class to generate executable SQL query / execution statements.
3334
*/
35+
@Slf4j
3436
public class SQLStatementUtils {
3537
private static final Escaper URN_ESCAPER = Escapers.builder()
3638
.addEscape('\'', "''")
@@ -112,8 +114,6 @@ public class SQLStatementUtils {
112114

113115
private static final String INDEX_GROUP_BY_CRITERION = "SELECT count(*) as COUNT, %s FROM %s";
114116

115-
private static final String SQL_GET_ALL_COLUMNS =
116-
"SELECT COLUMN_NAME FROM information_schema.COLUMNS WHERE TABLE_SCHEMA = database() AND TABLE_NAME = '%s'";
117117
private static final String SQL_URN_EXIST_TEMPLATE = "SELECT urn FROM %s WHERE urn = '%s'";
118118

119119
private static final String INSERT_LOCAL_RELATIONSHIP = "INSERT INTO %s (metadata, source, destination, source_type, "
@@ -302,9 +302,10 @@ public static <ASPECT extends RecordTemplate> String createAspectUpdateWithOptim
302302
* @param nonDollarVirtualColumnsEnabled true if virtual column does not contain $, false otherwise
303303
* @return translated SQL where statement
304304
*/
305-
public static String createFilterSql(String entityType, @Nullable IndexFilter indexFilter, boolean hasTotalCount, boolean nonDollarVirtualColumnsEnabled) {
305+
public static String createFilterSql(String entityType, @Nullable IndexFilter indexFilter, boolean hasTotalCount, boolean nonDollarVirtualColumnsEnabled,
306+
@Nonnull SchemaValidatorUtil schemaValidator) {
306307
final String tableName = getTableName(entityType);
307-
String whereClause = parseIndexFilter(entityType, indexFilter, nonDollarVirtualColumnsEnabled);
308+
String whereClause = parseIndexFilter(entityType, indexFilter, nonDollarVirtualColumnsEnabled, schemaValidator);
308309
String totalCountSql = String.format("SELECT COUNT(urn) FROM %s %s", tableName, whereClause);
309310
StringBuilder sb = new StringBuilder();
310311

@@ -328,23 +329,25 @@ public static String createFilterSql(String entityType, @Nullable IndexFilter in
328329
* @return translated group by SQL
329330
*/
330331
public static String createGroupBySql(String entityType, @Nullable IndexFilter indexFilter,
331-
@Nonnull IndexGroupByCriterion indexGroupByCriterion, boolean nonDollarVirtualColumnsEnabled) {
332+
@Nonnull IndexGroupByCriterion indexGroupByCriterion, boolean nonDollarVirtualColumnsEnabled, @Nonnull SchemaValidatorUtil schemaValidator) {
332333
final String tableName = getTableName(entityType);
333334
final String columnName =
334335
getGeneratedColumnName(entityType, indexGroupByCriterion.getAspect(), indexGroupByCriterion.getPath(),
335336
nonDollarVirtualColumnsEnabled);
337+
// Check if the column exists in the schema
338+
if (!schemaValidator.columnExists(tableName, columnName)) {
339+
log.warn("Skipping group-by: column '{}' not found in table '{}'", columnName, tableName);
340+
return ""; // skip query generation
341+
}
336342
StringBuilder sb = new StringBuilder();
337343
sb.append(String.format(INDEX_GROUP_BY_CRITERION, columnName, tableName));
338344
sb.append("\n");
339-
sb.append(parseIndexFilter(entityType, indexFilter, nonDollarVirtualColumnsEnabled));
345+
sb.append(parseIndexFilter(entityType, indexFilter, nonDollarVirtualColumnsEnabled, schemaValidator));
340346
sb.append("\nGROUP BY ");
341347
sb.append(columnName);
342348
return sb.toString();
343349
}
344350

345-
public static String getAllColumnForTable(String tableName) {
346-
return String.format(SQL_GET_ALL_COLUMNS, tableName);
347-
}
348351

349352
/**
350353
* Create aspect browse SQL statement.
Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
1+
package com.linkedin.metadata.dao.utils;
2+
3+
import com.github.benmanes.caffeine.cache.Cache;
4+
import com.github.benmanes.caffeine.cache.Caffeine;
5+
import com.google.common.annotations.VisibleForTesting;
6+
import io.ebean.EbeanServer;
7+
import io.ebean.SqlRow;
8+
import java.util.HashSet;
9+
import java.util.List;
10+
import java.util.Set;
11+
import java.util.concurrent.TimeUnit;
12+
import javax.annotation.Nonnull;
13+
import lombok.extern.slf4j.Slf4j;
14+
15+
16+
/**
17+
* Utility class for validating presence of columns and indexes in MySQL tables
18+
* by querying information_schema. Uses Caffeine caches to reduce DB load and
19+
* support eventual consistency of schema evolution.
20+
*/
21+
@Slf4j
22+
public class SchemaValidatorUtil {
23+
private final EbeanServer server;
24+
25+
// Cache: tableName → Set of index names
26+
// Configuration:
27+
// - expireAfterWrite(10 minutes): Ensures that newly added indexes (e.g., via Pretzel) are picked up automatically
28+
// without requiring a service restart. After 10 minutes, the next request will trigger a DB refresh.
29+
// - maximumSize(1000): Limits cache memory footprint by retaining entries for up to 1000 distinct tables.
30+
// Least recently used entries are evicted when the size limit is reached.
31+
private final Cache<String, Set<String>> indexCache = Caffeine.newBuilder()
32+
.expireAfterWrite(10, TimeUnit.MINUTES)
33+
.maximumSize(1000)
34+
.build();
35+
36+
// Cache: tableName → Set of column names
37+
private final Cache<String, Set<String>> columnCache = Caffeine.newBuilder()
38+
.expireAfterWrite(10, TimeUnit.MINUTES)
39+
.maximumSize(1000)
40+
.build();
41+
42+
private static final String SQL_GET_ALL_COLUMNS =
43+
"SELECT COLUMN_NAME FROM information_schema.COLUMNS WHERE TABLE_SCHEMA = database() AND TABLE_NAME = '%s'";
44+
private static final String SQL_GET_ALL_INDEXES =
45+
"SELECT DISTINCT INDEX_NAME FROM information_schema.STATISTICS WHERE TABLE_SCHEMA = database() AND TABLE_NAME = '%s'";
46+
47+
public SchemaValidatorUtil(EbeanServer server) {
48+
this.server = server;
49+
}
50+
51+
/**
52+
* Clears all caches, including indexCache, columnCache, missingColumnCache, and missingIndexCache.
53+
* Useful for testing.
54+
*/
55+
@VisibleForTesting
56+
void clearCaches() {
57+
indexCache.invalidateAll();
58+
columnCache.invalidateAll();
59+
}
60+
61+
62+
/**
63+
* Checks whether the given column exists in the specified table.
64+
*
65+
* @param tableName Table name
66+
* @param columnName Column name
67+
* @return true if column exists, false otherwise
68+
*/
69+
public boolean columnExists(@Nonnull String tableName, @Nonnull String columnName) {
70+
String lowerTable = tableName.toLowerCase();
71+
String lowerColumn = columnName.toLowerCase();
72+
73+
Set<String> columns = columnCache.get(lowerTable, tbl -> {
74+
log.info("Refreshing column cache for table '{}'", tbl);
75+
return loadColumns(tbl);
76+
});
77+
78+
return columns.contains(lowerColumn);
79+
}
80+
81+
/**
82+
* Checks whether the given index exists in the specified table.
83+
*
84+
* @param tableName Table name
85+
* @param indexName Index name
86+
* @return true if index exists, false otherwise
87+
*/
88+
public boolean indexExists(@Nonnull String tableName, @Nonnull String indexName) {
89+
String lowerTable = tableName.toLowerCase();
90+
String lowerIndex = indexName.toLowerCase();
91+
92+
Set<String> indexes = indexCache.get(lowerTable, tbl -> {
93+
log.info("Refreshing index cache for table '{}'", tbl);
94+
return loadIndexes(tbl);
95+
});
96+
97+
return indexes.contains(lowerIndex);
98+
}
99+
100+
/**
101+
* Loads all columns for the given table from information_schema.
102+
*
103+
* @param tableName Table to query
104+
* @return Set of lowercase column names
105+
*/
106+
private Set<String> loadColumns(String tableName) {
107+
List<SqlRow> rows = server.createSqlQuery(String.format(SQL_GET_ALL_COLUMNS, tableName)).findList();
108+
Set<String> columns = new HashSet<>();
109+
for (SqlRow row : rows) {
110+
columns.add(row.getString("COLUMN_NAME").toLowerCase());
111+
}
112+
return columns;
113+
}
114+
115+
/**
116+
* Loads all index names for the given table from information_schema.
117+
*
118+
* @param tableName Table to query
119+
* @return Set of lowercase index names
120+
*/
121+
private Set<String> loadIndexes(String tableName) {
122+
List<SqlRow> rows = server.createSqlQuery(String.format(SQL_GET_ALL_INDEXES, tableName)).findList();
123+
Set<String> indexes = new HashSet<>();
124+
for (SqlRow row : rows) {
125+
indexes.add(row.getString("INDEX_NAME").toLowerCase());
126+
}
127+
return indexes;
128+
}
129+
130+
}

0 commit comments

Comments
 (0)