Skip to content

Feat/pagination poc#7708

Draft
sclarkamlab wants to merge 10 commits into
masterfrom
feat/pagination-poc
Draft

Feat/pagination poc#7708
sclarkamlab wants to merge 10 commits into
masterfrom
feat/pagination-poc

Conversation

@sclarkamlab
Copy link
Copy Markdown
Contributor

🆔 Reference related issue.

✏️ A description of the changes proposed in the pull request

📝 Test scenarios

💻 Add screenshots for UI

📚 Any other comments that will help with documentation

👨 👩@mentions of the person or team responsible for reviewing proposed changes

This is a nice example: #1822

Introduces CursorPage/CursorRequest models with opaque Base64 keyset
cursors, and implements cursor pagination across MongoDB, JDBC, and
dataplane repositories for domains, applications, and users.
New endpoints with sort and totalCount support alongside existing
offset-based endpoints to avoid breaking changes.
Replaces offset pagination with cursor prev/next navigation in domains,
applications, and users list views. Adds external sorting support.
- Return 400 instead of 500 for malformed cursors
- Fix JDBC date parsing crash on null updatedAt (use epoch millis)
- Throw on invalid date cursor values instead of silent epoch-zero fallback
- Handle null username in MongoDB user cursor encoding
- Add COALESCE for NULL usernames in JDBC keyset queries
- Derive hasNext from nextCursor to prevent contradictory states
- Add totalCount to all JDBC cursor queries for consistency with MongoDB
- Encode sort field in JDBC cursors (4-arg encode)
- Reject conflicting sort field between cursor and query param
- Narrow SCIM FilterCriteria catch blocks
- Validate sortParam="-" edge case and null direction
Copy link
Copy Markdown
Contributor

@gemini-code-assist gemini-code-assist Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request implements cursor-based pagination for security domains, applications, and users across the Management API, data repositories (JDBC and MongoDB), and the UI to enhance performance at scale. Feedback highlights several critical issues in the repository layer, specifically where sort fields are hardcoded, causing the system to ignore user-specified sorting and potentially leading to NumberFormatException when parsing cursor values. Performance improvements are suggested to avoid full table scans caused by COALESCE in SQL clauses and to mitigate the overhead of executing full document counts on every request. Additionally, the manual manipulation of SQL strings to strip ordering was identified as a fragile implementation detail.

Comment on lines +631 to +634
sql.append(" AND (COALESCE(u.username, '') ").append(compareOp).append(" :lastSort")
.append(" OR (COALESCE(u.username, '') = :lastSort AND u.id ").append(compareOp).append(" :lastId))");
}
sql.append(" ORDER BY COALESCE(u.username, '') ").append(sortDir).append(", u.id ").append(sortDir)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

The use of COALESCE(u.username, '') in both the WHERE and ORDER BY clauses will likely prevent the database from utilizing an index on the username column. This can lead to full table scans, which significantly impacts performance at scale. Consider ensuring the sort column is non-nullable or using a strategy that maintains index eligibility.

Comment on lines +631 to +634
sql.append(" AND (COALESCE(u.username, '') ").append(compareOp).append(" :lastSort")
.append(" OR (COALESCE(u.username, '') = :lastSort AND u.id ").append(compareOp).append(" :lastId))");
}
sql.append(" ORDER BY COALESCE(u.username, '') ").append(sortDir).append(", u.id ").append(sortDir)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

The repository implementation hardcodes username as the sort field, ignoring the sortField specified in the CursorRequest. This will cause incorrect results or failures if the user attempts to sort by a different field (e.g., updatedAt) as supported by the UI.

Comment on lines +336 to +340
sql.append(" AND (a.updated_at ").append(compareOp).append(" :lastSort")
.append(" OR (a.updated_at = :lastSort AND a.id ").append(compareOp).append(" :lastId))");
}

sql.append(" ORDER BY a.updated_at ").append(sortDir).append(", a.id ").append(sortDir)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

The repository implementation hardcodes updated_at as the sort field, ignoring the sortField specified in the CursorRequest. This will cause incorrect results or failures if the user attempts to sort by a different field (e.g., name) as supported by the UI.


if (!cursor.isFirstPage()) {
try {
long epochMs = Long.parseLong(cursor.getLastSortValue());
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

This line assumes the lastSortValue is always a numeric timestamp. If the sort field is changed to name (as supported by the UI), Long.parseLong will throw a NumberFormatException. The logic should dynamically handle the type of the sort value based on the requested sort field.

Comment on lines +246 to +250
sql.append(" AND (LOWER(d.name) ").append(compareOp).append(" LOWER(:lastSort)")
.append(" OR (LOWER(d.name) = LOWER(:lastSort) AND d.id ").append(compareOp).append(" :lastId))");
}

sql.append(" ORDER BY LOWER(d.name) ").append(sortDir).append(", d.id ").append(sortDir)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

The repository implementation hardcodes name as the sort field, ignoring the sortField in the CursorRequest.

Comment on lines +261 to +262
return findCursorPage(usersCollection, baseFilter, cursor, FIELD_USERNAME,
this::convert, u -> u.getUsername() != null ? u.getUsername() : "", User::getId);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

The repository implementation hardcodes FIELD_USERNAME as the sort field, ignoring the sortField specified in the CursorRequest. This will cause incorrect results if the user attempts to sort by a different field.

Comment on lines +626 to +627
int orderByIdx = selectQuery.toUpperCase().lastIndexOf("ORDER BY");
String baseQuery = orderByIdx > 0 ? selectQuery.substring(0, orderByIdx) : selectQuery;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Manual string manipulation of SQL queries generated by databaseDialectHelper is fragile. Using lastIndexOf("ORDER BY") to strip ordering can fail if the query contains ORDER BY within subqueries or string literals. It is recommended to use a more robust approach for query modification or ensure the dialect helper provides a base query without ordering.


Bson sort = new BasicDBObject(sortFieldName, sortDir).append(FIELD_ID, sortDir);

Single<Long> countOperation = countItems(collection, baseFilter, countOptions());
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Executing a countDocuments operation on every page request can be expensive for large collections. Since cursor-based pagination is intended for performance at scale, consider making the total count optional or caching it to avoid the overhead of a full count on every request.

@sonarqubecloud
Copy link
Copy Markdown

sonarqubecloud Bot commented Apr 8, 2026

Quality Gate Failed Quality Gate failed

Failed conditions
0.0% Condition Coverage on New Code (required ≥ 50%)

See analysis details on SonarQube Cloud

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant