in src/Core/Resolvers/SqlPaginationUtil.cs [206:290]
public static string MakeCursorFromJsonElement(
JsonElement element,
List<string> primaryKey,
List<OrderByColumn>? orderByColumns,
string entityName = "",
string schemaName = "",
string tableName = "",
ISqlMetadataProvider? sqlMetadataProvider = null,
bool isGroupByQuery = false)
{
List<NextLinkField> cursorJson = new();
JsonSerializerOptions options = new() { DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull };
// Hash set is used here to maintain linear runtime
// in the worst case for this function. If list is used
// we will have in the worst case quadratic runtime.
HashSet<string> remainingKeys = new();
if (!isGroupByQuery)
{
foreach (string key in primaryKey)
{
remainingKeys.Add(key);
}
}
// must include all orderByColumns to maintain
// correct pagination with sorting
if (orderByColumns is not null)
{
foreach (OrderByColumn column in orderByColumns)
{
string? exposedColumnName = GetExposedColumnName(entityName, column.ColumnName, sqlMetadataProvider);
if (TryResolveJsonElementToScalarVariable(element.GetProperty(exposedColumnName), out object? value))
{
cursorJson.Add(new NextLinkField(
entityName: entityName,
fieldName: exposedColumnName,
fieldValue: value,
direction: column.Direction));
}
else
{
throw new DataApiBuilderException(
message: "Incompatible data to create pagination cursor.",
statusCode: HttpStatusCode.BadRequest,
subStatusCode: DataApiBuilderException.SubStatusCodes.ErrorProcessingData);
}
remainingKeys.Remove(column.ColumnName);
}
}
// Primary key columns must be included in the orderBy query parameter in the nextLink cursor to break ties between result set records.
// Iterate through list of (composite) primary key(s) and when a primary key column exists in the remaining keys collection:
// 1.) Add that column as one of the pagination columns in the orderBy query parameter in the generated nextLink cursor.
// 2.) Remove the column from the remaining keys collection.
// This loop enables consistent iteration over the list of primary key columns which:
// - Maintains the order of the primary key columns as they exist in the database.
// - Ensures all primary key columns have been added to the nextLink cursor.
foreach (string column in primaryKey)
{
if (remainingKeys.Contains(column))
{
string? exposedColumnName = GetExposedColumnName(entityName, column, sqlMetadataProvider);
if (TryResolveJsonElementToScalarVariable(element.GetProperty(exposedColumnName), out object? value))
{
cursorJson.Add(new NextLinkField(
entityName: entityName,
fieldName: exposedColumnName,
fieldValue: value));
}
else
{
throw new DataApiBuilderException(
message: "Incompatible data to create pagination cursor.",
statusCode: HttpStatusCode.BadRequest,
subStatusCode: DataApiBuilderException.SubStatusCodes.ErrorProcessingData);
}
remainingKeys.Remove(column);
}
}
return Base64Encode(JsonSerializer.Serialize(cursorJson, options));
}