|
53 | 53 | import static com.linkedin.metadata.dao.utils.SQLSchemaUtils.*; |
54 | 54 | import static com.linkedin.metadata.dao.utils.SQLStatementUtils.*; |
55 | 55 |
|
| 56 | + |
56 | 57 | /** |
57 | 58 | * EBeanLocalAccess provides model agnostic data access (read / write) to MySQL database. |
58 | 59 | */ |
@@ -335,77 +336,23 @@ public int softDeleteAsset(@Nonnull URN urn, boolean isTestMode) { |
335 | 336 | @Override |
336 | 337 | public List<URN> listUrns(@Nullable IndexFilter indexFilter, @Nullable IndexSortCriterion indexSortCriterion, |
337 | 338 | @Nullable URN lastUrn, int pageSize) { |
338 | | - SqlQuery sqlQuery = createSelectFilterSqlQuery(indexFilter, indexSortCriterion, lastUrn, pageSize); |
| 339 | + SqlQuery sqlQuery = createFilterSqlQuery(indexFilter, indexSortCriterion, lastUrn, pageSize); |
339 | 340 | final List<SqlRow> sqlRows = sqlQuery.setFirstRow(0).findList(); |
340 | 341 | return sqlRows.stream().map(sqlRow -> getUrn(sqlRow.getString("urn"), _urnClass)).collect(Collectors.toList()); |
341 | 342 | } |
342 | 343 |
|
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 | | - */ |
358 | 344 | @Override |
359 | 345 | public ListResult<URN> listUrns(@Nullable IndexFilter indexFilter, @Nullable IndexSortCriterion indexSortCriterion, |
360 | 346 | 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); |
408 | 353 | } |
| 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); |
409 | 356 | } |
410 | 357 |
|
411 | 358 | @Override |
@@ -526,48 +473,29 @@ public Map<String, Long> countAggregate(@Nullable IndexFilter indexFilter, |
526 | 473 | } |
527 | 474 |
|
528 | 475 | /** |
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. |
539 | 480 | */ |
540 | | - private SqlQuery createSelectFilterSqlQuery(@Nullable IndexFilter indexFilter, |
| 481 | + private SqlQuery createFilterSqlQuery(@Nullable IndexFilter indexFilter, |
541 | 482 | @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()); |
562 | 490 | } |
563 | 491 |
|
564 | 492 | /** |
565 | 493 | * Produce {@link SqlQuery} for list urns by last urn. |
566 | 494 | */ |
567 | | - private SqlQuery createSelectFilterSqlQuery(@Nullable IndexFilter indexFilter, |
| 495 | + private SqlQuery createFilterSqlQuery(@Nullable IndexFilter indexFilter, |
568 | 496 | @Nullable IndexSortCriterion indexSortCriterion, @Nullable URN lastUrn, int pageSize) { |
569 | 497 | StringBuilder filterSql = new StringBuilder(); |
570 | | - filterSql.append(SQLStatementUtils.createSelectFilterSql(_entityType, indexFilter, _nonDollarVirtualColumnsEnabled, validator)); |
| 498 | + filterSql.append(SQLStatementUtils.createFilterSql(_entityType, indexFilter, false, _nonDollarVirtualColumnsEnabled, validator)); |
571 | 499 |
|
572 | 500 | if (lastUrn != null) { |
573 | 501 | // 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< |
667 | 595 | .build(); |
668 | 596 | } |
669 | 597 |
|
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 | | - |
715 | 598 | /** |
716 | 599 | * Given an AuditedAspect object, serialize it into a json string in a format that will be saved in DB. |
717 | 600 | * @param auditedAspect AuditedAspect object to be serialized |
|
0 commit comments