fix: REST cursor encoding for nested order_by composite fields (#20974)
## Summary fix: REST cursor encoding for nested order_by composite fields Closes #20109 --- AI was used for assistance. --------- Co-authored-by: Matt Van Horn <455140+mvanhorn@users.noreply.github.com> Co-authored-by: Etienne <45695613+etiennejouan@users.noreply.github.com>
This commit is contained in:
+45
@@ -252,6 +252,51 @@ describe('computeCursorArgFilter', () => {
|
||||
]);
|
||||
});
|
||||
|
||||
it('should handle dotted composite cursor keys', () => {
|
||||
const cursor = {
|
||||
'fullName.firstName': 'John',
|
||||
'fullName.lastName': 'Doe',
|
||||
} as any;
|
||||
const orderBy = [
|
||||
{
|
||||
fullName: {
|
||||
firstName: OrderByDirection.AscNullsLast,
|
||||
lastName: OrderByDirection.AscNullsLast,
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
const result = computeCursorArgFilter(
|
||||
cursor,
|
||||
orderBy,
|
||||
flatObjectMetadata,
|
||||
flatFieldMetadataMaps,
|
||||
true,
|
||||
);
|
||||
|
||||
expect(result).toEqual([
|
||||
{
|
||||
fullName: {
|
||||
firstName: { gt: 'John' },
|
||||
},
|
||||
},
|
||||
{
|
||||
and: [
|
||||
{
|
||||
fullName: {
|
||||
firstName: { eq: 'John' },
|
||||
},
|
||||
},
|
||||
{
|
||||
fullName: {
|
||||
lastName: { gt: 'Doe' },
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
it('should handle composite field with backward pagination', () => {
|
||||
const cursor = {
|
||||
fullName: { firstName: 'John', lastName: 'Doe' },
|
||||
|
||||
@@ -24,7 +24,7 @@ import { buildFieldMapsFromFlatObjectMetadata } from 'src/engine/metadata-module
|
||||
import { type FlatObjectMetadata } from 'src/engine/metadata-modules/flat-object-metadata/types/flat-object-metadata.type';
|
||||
|
||||
type BuildCursorWhereConditionParams = {
|
||||
cursorKey: keyof ObjectRecord;
|
||||
cursorKey: string;
|
||||
cursorValue:
|
||||
| ObjectRecordCursorLeafScalarValue
|
||||
| ObjectRecordCursorLeafCompositeValue;
|
||||
@@ -49,7 +49,10 @@ export const buildCursorWhereCondition = ({
|
||||
flatObjectMetadata,
|
||||
);
|
||||
|
||||
const fieldMetadataId = fieldIdByName[cursorKey];
|
||||
const [fieldKey, ...subFieldPath] = cursorKey.split('.');
|
||||
const compositeSubFieldKey = subFieldPath.join('.');
|
||||
const fieldMetadataKey = fieldKey as keyof ObjectRecord;
|
||||
const fieldMetadataId = fieldIdByName[fieldMetadataKey];
|
||||
|
||||
const fieldMetadata = findFlatEntityByIdInFlatEntityMaps({
|
||||
flatEntityMaps: flatFieldMetadataMaps,
|
||||
@@ -58,16 +61,29 @@ export const buildCursorWhereCondition = ({
|
||||
|
||||
if (!fieldMetadata) {
|
||||
throw new GraphqlQueryRunnerException(
|
||||
`Field metadata not found for key: ${cursorKey}`,
|
||||
`Field metadata not found for key: ${String(cursorKey)}`,
|
||||
GraphqlQueryRunnerExceptionCode.INVALID_CURSOR,
|
||||
{ userFriendlyMessage: STANDARD_ERROR_MESSAGE },
|
||||
);
|
||||
}
|
||||
|
||||
if (isCompositeFieldMetadataType(fieldMetadata.type)) {
|
||||
if (compositeSubFieldKey.length > 0) {
|
||||
return buildCursorCompositeFieldWhereCondition({
|
||||
fieldType: fieldMetadata.type,
|
||||
fieldKey: fieldMetadataKey,
|
||||
orderBy,
|
||||
cursorValue: {
|
||||
[compositeSubFieldKey]: cursorValue,
|
||||
} as ObjectRecordCursorLeafCompositeValue,
|
||||
isForwardPagination,
|
||||
isEqualityCondition,
|
||||
});
|
||||
}
|
||||
|
||||
return buildCursorCompositeFieldWhereCondition({
|
||||
fieldType: fieldMetadata.type,
|
||||
fieldKey: cursorKey,
|
||||
fieldKey: fieldMetadataKey,
|
||||
orderBy,
|
||||
cursorValue: cursorValue as ObjectRecordCursorLeafCompositeValue,
|
||||
isForwardPagination,
|
||||
@@ -76,11 +92,14 @@ export const buildCursorWhereCondition = ({
|
||||
}
|
||||
|
||||
if (isEqualityCondition) {
|
||||
return { [cursorKey]: { eq: cursorValue } };
|
||||
return { [fieldMetadataKey]: { eq: cursorValue } };
|
||||
}
|
||||
|
||||
const keyOrderBy = validateAndGetOrderByForScalarField(cursorKey, orderBy);
|
||||
const orderByDirection = keyOrderBy[cursorKey];
|
||||
const keyOrderBy = validateAndGetOrderByForScalarField(
|
||||
fieldMetadataKey,
|
||||
orderBy,
|
||||
);
|
||||
const orderByDirection = keyOrderBy[fieldMetadataKey];
|
||||
|
||||
if (!isDefined(orderByDirection)) {
|
||||
throw new GraphqlQueryRunnerException(
|
||||
@@ -93,5 +112,5 @@ export const buildCursorWhereCondition = ({
|
||||
const isAscending = isAscendingOrder(orderByDirection);
|
||||
const computedOperator = computeOperator(isAscending, isForwardPagination);
|
||||
|
||||
return { [cursorKey]: { [computedOperator]: cursorValue } };
|
||||
return { [fieldMetadataKey]: { [computedOperator]: cursorValue } };
|
||||
};
|
||||
|
||||
@@ -75,9 +75,9 @@ export const validateAndGetOrderByForCompositeField = (
|
||||
key: keyof ObjectRecord,
|
||||
orderBy: ObjectRecordOrderBy,
|
||||
): ObjectRecordOrderByForCompositeField => {
|
||||
const keyOrderBy = orderBy.find((order) => key in order);
|
||||
const matchingOrderBys = orderBy.filter((order) => key in order);
|
||||
|
||||
if (!isDefined(keyOrderBy)) {
|
||||
if (matchingOrderBys.length === 0) {
|
||||
throw new GraphqlQueryRunnerException(
|
||||
'Invalid cursor',
|
||||
GraphqlQueryRunnerExceptionCode.INVALID_CURSOR,
|
||||
@@ -85,7 +85,20 @@ export const validateAndGetOrderByForCompositeField = (
|
||||
);
|
||||
}
|
||||
|
||||
if (!isOrderByForCompositeField(keyOrderBy, key)) {
|
||||
// Merge all orderBy entries for the same composite field key so that
|
||||
// separate { fullName: { firstName } } and { fullName: { lastName } } entries
|
||||
// are treated as a single composite orderBy
|
||||
const mergedValue = matchingOrderBys.reduce(
|
||||
(acc, orderByEntry) => ({
|
||||
...acc,
|
||||
...(orderByEntry[key as string] as Record<string, OrderByDirection>),
|
||||
}),
|
||||
{} as Record<string, OrderByDirection>,
|
||||
);
|
||||
|
||||
const mergedOrderBy = { [key as string]: mergedValue };
|
||||
|
||||
if (!isOrderByForCompositeField(mergedOrderBy, key)) {
|
||||
throw new GraphqlQueryRunnerException(
|
||||
'Expected composite field order by',
|
||||
GraphqlQueryRunnerExceptionCode.INVALID_CURSOR,
|
||||
@@ -93,7 +106,7 @@ export const validateAndGetOrderByForCompositeField = (
|
||||
);
|
||||
}
|
||||
|
||||
return keyOrderBy;
|
||||
return mergedOrderBy;
|
||||
};
|
||||
|
||||
export const countRelationFieldsInOrderBy = (
|
||||
|
||||
Reference in New Issue
Block a user