Fix cursor-based pagination with lexicographic ordering for composite fields (#12467)

# Fix cursor-based pagination with lexicographic ordering for composite
fields

## Bug

The existing cursor-based pagination implementation had a bug when
handling composite fields.
When paginating through results sorted by composite fields (like
`fullName` with sub-properties `firstName` and`lastName`), the WHERE
conditions generated for cursor positioning were incorrect, leading to
records being skipped.

The previous implementation was generating wrong WHERE conditions:

For example, when paginating with a cursor like `{ firstName: 'John',
lastName: 'Doe' }`, it would generate:

```sql
WHERE firstName > 'John' AND lastName > 'Doe'
```

This is incorrect because it would miss records like `{ firstName:
'John', lastName: 'Smith' }` which should be included in forward
pagination.

## Fix

Create a new util to use proper lexicographic order when sorting a
composite field.

---------

Co-authored-by: Charles Bochet <charlesBochet@users.noreply.github.com>
Co-authored-by: Charles Bochet <charles@twenty.com>
This commit is contained in:
Raphaël Bosi
2025-06-11 16:48:03 +02:00
committed by GitHub
parent 4cea354838
commit d4995ab54e
15 changed files with 1710 additions and 183 deletions

View File

@ -6,6 +6,12 @@ type FindManyOperationFactoryParams = {
objectMetadataPluralName: string;
gqlFields: string;
filter?: object;
orderBy?: object;
limit?: number;
after?: string;
before?: string;
first?: number;
last?: number;
};
export const findManyOperationFactory = ({
@ -13,19 +19,38 @@ export const findManyOperationFactory = ({
objectMetadataPluralName,
gqlFields,
filter = {},
orderBy = {},
limit,
after,
before,
first,
last,
}: FindManyOperationFactoryParams) => ({
query: gql`
query ${capitalize(objectMetadataPluralName)}($filter: ${capitalize(objectMetadataSingularName)}FilterInput) {
${objectMetadataPluralName}(filter: $filter) {
query ${capitalize(objectMetadataPluralName)}($filter: ${capitalize(objectMetadataSingularName)}FilterInput, $orderBy: [${capitalize(objectMetadataSingularName)}OrderByInput], $limit: Int, $after: String, $before: String, $first: Int, $last: Int) {
${objectMetadataPluralName}(filter: $filter, orderBy: $orderBy, limit: $limit, after: $after, before: $before, first: $first, last: $last) {
edges {
node {
${gqlFields}
}
cursor
}
pageInfo {
hasNextPage
hasPreviousPage
startCursor
endCursor
}
}
}
`,
variables: {
filter,
orderBy,
limit,
after,
before,
first,
last,
},
});