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

@ -7,10 +7,10 @@ export interface ObjectRecord {
deletedAt: string | null;
}
export type ObjectRecordFilter = {
export type ObjectRecordFilter = Partial<{
// eslint-disable-next-line @typescript-eslint/no-explicit-any
[Property in keyof ObjectRecord]: any;
};
}>;
export enum OrderByDirection {
AscNullsFirst = 'AscNullsFirst',
@ -19,11 +19,29 @@ export enum OrderByDirection {
DescNullsLast = 'DescNullsLast',
}
export type ObjectRecordOrderBy = Array<{
export type ObjectRecordOrderBy = Array<
ObjectRecordOrderByForScalarField | ObjectRecordOrderByForCompositeField
>;
export type ObjectRecordOrderByForScalarField = {
[Property in keyof ObjectRecord]?: OrderByDirection;
};
export type ObjectRecordOrderByForCompositeField = {
[Property in keyof ObjectRecord]?: Record<string, OrderByDirection>;
};
export type ObjectRecordCursorLeafScalarValue = string | number | boolean;
export type ObjectRecordCursorLeafCompositeValue = Record<
string,
ObjectRecordCursorLeafScalarValue
>;
export type ObjectRecordCursor = {
[Property in keyof ObjectRecord]?:
| OrderByDirection
| Record<string, OrderByDirection>;
}>;
| ObjectRecordCursorLeafScalarValue
| ObjectRecordCursorLeafCompositeValue;
};
export interface ObjectRecordDuplicateCriteria {
objectName: string;