Skip to content

Commit ba0ad3a

Browse files
authored
Revert "Splitting filter queries (#579)" (#580)
This reverts commit 5583423.
1 parent 5583423 commit ba0ad3a

4 files changed

Lines changed: 89 additions & 372 deletions

File tree

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

Lines changed: 24 additions & 141 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@
5353
import static com.linkedin.metadata.dao.utils.SQLSchemaUtils.*;
5454
import static com.linkedin.metadata.dao.utils.SQLStatementUtils.*;
5555

56+
5657
/**
5758
* EBeanLocalAccess provides model agnostic data access (read / write) to MySQL database.
5859
*/
@@ -335,77 +336,23 @@ public int softDeleteAsset(@Nonnull URN urn, boolean isTestMode) {
335336
@Override
336337
public List<URN> listUrns(@Nullable IndexFilter indexFilter, @Nullable IndexSortCriterion indexSortCriterion,
337338
@Nullable URN lastUrn, int pageSize) {
338-
SqlQuery sqlQuery = createSelectFilterSqlQuery(indexFilter, indexSortCriterion, lastUrn, pageSize);
339+
SqlQuery sqlQuery = createFilterSqlQuery(indexFilter, indexSortCriterion, lastUrn, pageSize);
339340
final List<SqlRow> sqlRows = sqlQuery.setFirstRow(0).findList();
340341
return sqlRows.stream().map(sqlRow -> getUrn(sqlRow.getString("urn"), _urnClass)).collect(Collectors.toList());
341342
}
342343

343-
/**
344-
* Retrieves a paginated list of URNs matching the provided filter and sort criteria.
345-
* This method performs two queries:
346-
* <ul>
347-
* <li>A count query to determine the total number of matching URNs.</li>
348-
* <li>A paginated query to fetch the URNs for the requested page.</li>
349-
* </ul>
350-
* The results are returned as a {@link ListResult} containing the page of URNs, total count, and pagination metadata.
351-
*
352-
* @param indexFilter The filter criteria to apply to the URNs (nullable).
353-
* @param indexSortCriterion The sort criteria for ordering the URNs (nullable).
354-
* @param start The starting offset (zero-based) of the page.
355-
* @param pageSize The maximum number of URNs to return in the page.
356-
* @return A {@link ListResult} containing the page of URNs, total count, and pagination metadata.
357-
*/
358344
@Override
359345
public ListResult<URN> listUrns(@Nullable IndexFilter indexFilter, @Nullable IndexSortCriterion indexSortCriterion,
360346
int start, int pageSize) {
361-
// 1. Run the count query to get total count of matching rows
362-
final SqlQuery countQuery = createCountSqlQuery(indexFilter);
363-
final SqlRow countRow = countQuery.findOne();
364-
final int totalCount = (countRow == null) ? 0 : countRow.getInteger("total_count");
365-
366-
// If there are no matching rows, return an empty result immediately
367-
if (totalCount == 0) {
368-
return toListResult(0, start, pageSize);
369-
}
370-
371-
// 2. Run the paginated URN query for the current page
372-
final SqlQuery pageQuery = createSelectFilterSqlQuery(indexFilter, indexSortCriterion, start, pageSize);
373-
final List<SqlRow> sqlRows = pageQuery.findList();
374-
375-
// Map the SQL rows to URN objects
376-
final List<URN> values =
377-
sqlRows.stream().map(sqlRow -> getUrn(sqlRow.getString("urn"), _urnClass)).collect(Collectors.toList());
378-
379-
// 3. Build and return the ListResult with values, total count, and pagination metadata
380-
int adjustedCount = resolveTotalCount(values.size(), totalCount, start, pageSize);
381-
return toListResult(values, adjustedCount, start, pageSize);
382-
}
383-
384-
/**
385-
* Resolve totalCount to handle race conditions between count and data queries.
386-
*
387-
* @param valuesSize Number of results returned from the data query
388-
* @param totalCount Total count from the count query (potentially stale)
389-
* @param start Starting offset for pagination
390-
* @param pageSize Requested page size
391-
* @return Resolved totalCount that accounts for potential concurrent insertions/deletions
392-
*/
393-
protected int resolveTotalCount(int valuesSize, int totalCount, int start, int pageSize) {
394-
// If requesting beyond available data with no results, preserve original totalCount
395-
if (start >= totalCount && valuesSize == 0) {
396-
return totalCount;
397-
}
398-
399-
if (valuesSize < pageSize && start + valuesSize < totalCount) {
400-
// Deletion race condition detected: hit end early due to records being deleted between queries
401-
// Adjust totalCount to reflect the actual end position
402-
return start + valuesSize;
403-
} else {
404-
// Normal pagination OR insertion race condition:
405-
// - Normal case: use original totalCount
406-
// - Insertion case: ensure totalCount >= actual results returned to avoid undercount
407-
return Math.max(totalCount, start + valuesSize);
347+
final SqlQuery sqlQuery = createFilterSqlQuery(indexFilter, indexSortCriterion, start, pageSize);
348+
final List<SqlRow> sqlRows = sqlQuery.findList();
349+
if (sqlRows.isEmpty()) {
350+
final List<SqlRow> totalCountResults = createFilterSqlQuery(indexFilter, indexSortCriterion, 0, DEFAULT_PAGE_SIZE).findList();
351+
final int actualTotalCount = totalCountResults.isEmpty() ? 0 : totalCountResults.get(0).getInteger("_total_count");
352+
return toListResult(actualTotalCount, start, pageSize);
408353
}
354+
final List<URN> values = sqlRows.stream().map(sqlRow -> getUrn(sqlRow.getString("urn"), _urnClass)).collect(Collectors.toList());
355+
return toListResult(values, sqlRows, null, start, pageSize);
409356
}
410357

411358
@Override
@@ -526,48 +473,29 @@ public Map<String, Long> countAggregate(@Nullable IndexFilter indexFilter,
526473
}
527474

528475
/**
529-
* Builds a {@link SqlQuery} for fetching a paginated list of URNs matching the provided filter and sort criteria.
530-
* Generates a query like:
531-
* SELECT urn FROM table WHERE filters ORDER BY sort LIMIT pageSize OFFSET offset
532-
* Uses the provided IndexFilter and IndexSortCriterion to construct the WHERE and ORDER BY clauses.
533-
*
534-
* @param indexFilter The filter criteria to apply (nullable).
535-
* @param indexSortCriterion The sort criteria to apply (nullable).
536-
* @param offset The starting offset for pagination.
537-
* @param pageSize The maximum number of results to return.
538-
* @return A parametrized SqlQuery for fetching the paginated URNs.
476+
* Produce {@link SqlQuery} for list urn by offset (start) and limit (pageSize).
477+
* @param indexFilter index filter conditions
478+
* @param indexSortCriterion sorting criterion, default ACS
479+
* @return SqlQuery a SQL query which can be executed by ebean server.
539480
*/
540-
private SqlQuery createSelectFilterSqlQuery(@Nullable IndexFilter indexFilter,
481+
private SqlQuery createFilterSqlQuery(@Nullable IndexFilter indexFilter,
541482
@Nullable IndexSortCriterion indexSortCriterion, int offset, int pageSize) {
542-
String filterSql =
543-
SQLStatementUtils.createSelectFilterSql(_entityType, indexFilter, _nonDollarVirtualColumnsEnabled, validator)
544-
+ "\n" + parseSortCriteria(_entityType, indexSortCriterion, _nonDollarVirtualColumnsEnabled)
545-
+ String.format(" LIMIT %d", Math.max(pageSize, 0)) + String.format(" OFFSET %d", Math.max(offset, 0));
546-
return _server.createSqlQuery(filterSql);
547-
}
548-
549-
/**
550-
* Builds a {@link SqlQuery} for counting the number of rows matching the provided filter.
551-
* Generates a query like:
552-
* SELECT COUNT(urn) AS total_count FROM table WHERE filters ...
553-
* Uses the provided IndexFilter and schema validator to construct the WHERE clause.
554-
*
555-
* @param indexFilter The filter criteria to apply (nullable).
556-
* @return A parametrized SqlQuery for counting matching rows.
557-
*/
558-
private SqlQuery createCountSqlQuery(@Nullable IndexFilter indexFilter) {
559-
String countSql = SQLStatementUtils.createCountFilterSql(
560-
_entityType, indexFilter, _nonDollarVirtualColumnsEnabled, validator);
561-
return _server.createSqlQuery(countSql);
483+
StringBuilder filterSql = new StringBuilder();
484+
filterSql.append(SQLStatementUtils.createFilterSql(_entityType, indexFilter, true, _nonDollarVirtualColumnsEnabled, validator));
485+
filterSql.append("\n");
486+
filterSql.append(parseSortCriteria(_entityType, indexSortCriterion, _nonDollarVirtualColumnsEnabled));
487+
filterSql.append(String.format(" LIMIT %d", Math.max(pageSize, 0)));
488+
filterSql.append(String.format(" OFFSET %d", Math.max(offset, 0)));
489+
return _server.createSqlQuery(filterSql.toString());
562490
}
563491

564492
/**
565493
* Produce {@link SqlQuery} for list urns by last urn.
566494
*/
567-
private SqlQuery createSelectFilterSqlQuery(@Nullable IndexFilter indexFilter,
495+
private SqlQuery createFilterSqlQuery(@Nullable IndexFilter indexFilter,
568496
@Nullable IndexSortCriterion indexSortCriterion, @Nullable URN lastUrn, int pageSize) {
569497
StringBuilder filterSql = new StringBuilder();
570-
filterSql.append(SQLStatementUtils.createSelectFilterSql(_entityType, indexFilter, _nonDollarVirtualColumnsEnabled, validator));
498+
filterSql.append(SQLStatementUtils.createFilterSql(_entityType, indexFilter, false, _nonDollarVirtualColumnsEnabled, validator));
571499

572500
if (lastUrn != null) {
573501
// because createFilterSql will always include a WHERE clause to filter by deleted_ts is NULL
@@ -667,51 +595,6 @@ protected <T> ListResult<T> toListResult(@Nonnull List<T> values, @Nonnull List<
667595
.build();
668596
}
669597

670-
/**
671-
* Convert values + totalCount into {@link ListResult}.
672-
* This is used when totalCount comes from a separate COUNT query,
673-
* not from an embedded _total_count in the SqlRow.
674-
*
675-
* @param values the page of results
676-
* @param totalCount the total number of results matching the filter
677-
* @param start starting offset
678-
* @param pageSize number of rows in this page
679-
* @param <T> type of query response
680-
* @return {@link ListResult} containing results and pagination metadata
681-
*/
682-
@Nonnull
683-
protected <T> ListResult<T> toListResult(@Nonnull List<T> values,
684-
int totalCount,
685-
int start,
686-
int pageSize) {
687-
if (pageSize == 0) {
688-
pageSize = DEFAULT_PAGE_SIZE;
689-
}
690-
final int totalPageCount = ceilDiv(totalCount, pageSize);
691-
692-
boolean hasNext;
693-
int nextStart;
694-
695-
if (start + values.size() < totalCount) {
696-
hasNext = true;
697-
nextStart = start + values.size();
698-
} else {
699-
hasNext = false;
700-
nextStart = ListResult.INVALID_NEXT_START;
701-
}
702-
703-
return ListResult.<T>builder()
704-
.values(values)
705-
.metadata(null) // or construct ListResultMetadata if needed
706-
.nextStart(nextStart)
707-
.havingMore(hasNext)
708-
.totalCount(totalCount)
709-
.totalPageCount(totalPageCount)
710-
.pageSize(pageSize)
711-
.build();
712-
}
713-
714-
715598
/**
716599
* Given an AuditedAspect object, serialize it into a json string in a format that will be saved in DB.
717600
* @param auditedAspect AuditedAspect object to be serialized

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

Lines changed: 37 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@
3333
import static com.linkedin.metadata.dao.utils.SQLIndexFilterUtils.*;
3434
import static com.linkedin.metadata.dao.utils.SQLSchemaUtils.*;
3535

36+
3637
/**
3738
* SQL statement util class to generate executable SQL query / execution statements.
3839
*/
@@ -151,12 +152,15 @@ public class SQLStatementUtils {
151152
private static final String DELETE_BY_SOURCE_AND_ASPECT = "UPDATE %s SET deleted_ts=NOW() "
152153
+ "WHERE source = :source AND (aspect = :aspect OR aspect = :pegasus_aspect) AND deleted_ts IS NULL";
153154

154-
private static final String SQL_COUNT_TEMPLATE =
155-
"SELECT COUNT(urn) AS total_count FROM %s %s";
156-
157-
private static final String SQL_SELECT_URN_WHERE_TEMPLATE =
158-
"SELECT urn FROM %s %s";
159-
155+
/**
156+
* Filter query has pagination params in the existing APIs. To accommodate this, we use subquery to include total result counts in the query response.
157+
* For example, we will build the following filter query statement:
158+
*
159+
* <p>SELECT *, (SELECT COUNT(urn) FROM metadata_entity_foo WHERE i_aspectfoo$value >= 25\n"
160+
* AND i_aspectfoo$value < 50 AND JSON_EXTRACT(a_aspectfoo, '$.gma_deleted') IS NULL) as _total_count FROM metadata_entity_foo\n"
161+
* WHERE i_aspectfoo$value >= 25 AND i_aspectfoo$value < 50 AND JSON_EXTRACT(a_aspectfoo, '$.gma_deleted') IS NULL;
162+
*/
163+
private static final String SQL_FILTER_TEMPLATE = "SELECT *, (%s) as _total_count FROM %s";
160164
private static final String SQL_BROWSE_ASPECT_TEMPLATE =
161165
String.format("SELECT urn, %%s, lastmodifiedon, lastmodifiedby, (SELECT COUNT(urn) FROM %%s) as _total_count "
162166
+ "FROM %%s WHERE %s LIMIT %%d OFFSET %%d", SOFT_DELETED_CHECK);
@@ -321,6 +325,33 @@ public static <ASPECT extends RecordTemplate> String createAspectUpdateWithOptim
321325
columnName, columnName, columnName);
322326
}
323327
}
328+
329+
/**
330+
* Create filter SQL statement.
331+
* @param entityType entity type from urn
332+
* @param indexFilter index filter
333+
* @param hasTotalCount whether to calculate total count in SQL.
334+
* @param nonDollarVirtualColumnsEnabled true if virtual column does not contain $, false otherwise
335+
* @return translated SQL where statement
336+
*/
337+
public static String createFilterSql(String entityType, @Nullable IndexFilter indexFilter, boolean hasTotalCount, boolean nonDollarVirtualColumnsEnabled,
338+
@Nonnull SchemaValidatorUtil schemaValidator) {
339+
final String tableName = getTableName(entityType);
340+
String whereClause = parseIndexFilter(entityType, indexFilter, nonDollarVirtualColumnsEnabled, schemaValidator);
341+
String totalCountSql = String.format("SELECT COUNT(urn) FROM %s %s", tableName, whereClause);
342+
StringBuilder sb = new StringBuilder();
343+
344+
if (hasTotalCount) {
345+
sb.append(String.format(SQL_FILTER_TEMPLATE, totalCountSql, tableName));
346+
} else {
347+
sb.append("SELECT urn FROM ").append(tableName);
348+
}
349+
350+
sb.append("\n");
351+
sb.append(whereClause);
352+
return sb.toString();
353+
}
354+
324355
/**
325356
* Create index group by SQL statement.
326357
* @param entityType entity type
@@ -365,28 +396,6 @@ public static <ASPECT extends RecordTemplate> String createAspectBrowseSql(Strin
365396
Math.max(pageSize, 0), Math.max(offset, 0));
366397
}
367398

368-
/**
369-
* Creates an SQL statement for fetching URNs from an entity table, applying the provided filter.
370-
*/
371-
public static String createSelectFilterSql(String entityType, @Nullable IndexFilter indexFilter, boolean nonDollarVirtualColumnsEnabled,
372-
@Nonnull SchemaValidatorUtil schemaValidator) {
373-
final String tableName = getTableName(entityType);
374-
String whereClause = parseIndexFilter(entityType, indexFilter, nonDollarVirtualColumnsEnabled, schemaValidator);
375-
// Build select query
376-
return String.format(SQL_SELECT_URN_WHERE_TEMPLATE, tableName, whereClause);
377-
}
378-
379-
/**
380-
* Creates an SQL statement for counting rows from an entity table, applying the provided filter.
381-
*/
382-
public static String createCountFilterSql(String entityType, @Nullable IndexFilter indexFilter, boolean nonDollarVirtualColumnsEnabled,
383-
@Nonnull SchemaValidatorUtil schemaValidator) {
384-
final String tableName = getTableName(entityType);
385-
String whereClause = parseIndexFilter(entityType, indexFilter, nonDollarVirtualColumnsEnabled, schemaValidator);
386-
// Build count query
387-
return String.format(SQL_COUNT_TEMPLATE, tableName, whereClause);
388-
}
389-
390399
/**
391400
* Generate the create SQL statement for inserting local relationships. There can be multiple relationships added in
392401
* a single statement. The SQL generated should look like the following, where N is the number of relationships to insert:

0 commit comments

Comments
 (0)