Fix paginated order by with composite fields (#7187)

## Context
Cursor is modifying the where object but does not handle properly
composite fields. I'm introducing field metadata as a source of truth to
fix this issue.
RAW_JSON for example (as a sub-field type) should be ignored in a lt/gt,
probably other field types as well.

## Before
```typescript
[
  {
    emails: {
      lt: {
        primaryEmail: "brenda.brown@example.com",
        additionalEmails: null,
      },
    },
  },
  {
    emails: {
      eq: {
        primaryEmail: "brenda.brown@example.com",
        additionalEmails: null,
      },
    },
    position: {
      gt: 877,
    },
  },
  {
    emails: {
      eq: {
        primaryEmail: "brenda.brown@example.com",
        additionalEmails: null,
      },
    },
    position: {
      eq: 877,
    },
    id: {
      gt: "fe43c45d-7560-4eb1-8fd3-c48fd0a4dcd4",
    },
  },
]
```


## After
```typescript
[
  {
    emails: {
      primaryEmail: {
        lt: "brenda.brown@example.com",
      },
    },
  },
  {
    emails: {
      primaryEmail: {
        eq: "brenda.brown@example.com",
      },
    },
    position: {
      gt: 877,
    },
  },
  {
    emails: {
      primaryEmail: {
        eq: "brenda.brown@example.com",
      },
    },
    position: {
      eq: 877,
    },
    id: {
      gt: "fe43c45d-7560-4eb1-8fd3-c48fd0a4dcd4",
    },
  },
]
```
This commit is contained in:
Weiko
2024-09-23 13:32:59 +02:00
committed by GitHub
parent aa7b3107f4
commit c8e171a929
2 changed files with 72 additions and 4 deletions

View File

@ -118,6 +118,7 @@ export class GraphqlQueryFindManyResolverService {
const cursorArgFilter = computeCursorArgFilter(
cursor,
orderByWithIdCondition,
objectMetadata.fields,
isForwardPagination,
);

View File

@ -8,10 +8,15 @@ import {
GraphqlQueryRunnerException,
GraphqlQueryRunnerExceptionCode,
} from 'src/engine/api/graphql/graphql-query-runner/errors/graphql-query-runner.exception';
import { compositeTypeDefinitions } from 'src/engine/metadata-modules/field-metadata/composite-types';
import { FieldMetadataType } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity';
import { isCompositeFieldMetadataType } from 'src/engine/metadata-modules/field-metadata/utils/is-composite-field-metadata-type.util';
import { FieldMetadataMap } from 'src/engine/metadata-modules/utils/generate-object-metadata-map.util';
export const computeCursorArgFilter = (
cursor: Record<string, any>,
orderBy: RecordOrderBy,
fieldMetadataMap: FieldMetadataMap,
isForwardPagination = true,
): RecordFilter[] => {
const cursorKeys = Object.keys(cursor ?? {});
@ -31,9 +36,12 @@ export const computeCursorArgFilter = (
) {
whereCondition = {
...whereCondition,
[cursorKeys[subConditionIndex]]: {
eq: cursorValues[subConditionIndex],
},
...buildWhereCondition(
cursorKeys[subConditionIndex],
cursorValues[subConditionIndex],
fieldMetadataMap,
'eq',
),
};
}
@ -60,7 +68,66 @@ export const computeCursorArgFilter = (
return {
...whereCondition,
...{ [key]: { [operator]: value } },
...buildWhereCondition(key, value, fieldMetadataMap, operator),
} as RecordFilter;
});
};
const buildWhereCondition = (
key: string,
value: any,
fieldMetadataMap: FieldMetadataMap,
operator: string,
): Record<string, any> => {
const fieldMetadata = fieldMetadataMap[key];
if (!fieldMetadata) {
throw new GraphqlQueryRunnerException(
`Field metadata not found for key: ${key}`,
GraphqlQueryRunnerExceptionCode.INVALID_CURSOR,
);
}
if (isCompositeFieldMetadataType(fieldMetadata.type)) {
return buildCompositeWhereCondition(
key,
value,
fieldMetadata.type,
operator,
);
}
return { [key]: { [operator]: value } };
};
const buildCompositeWhereCondition = (
key: string,
value: any,
fieldType: FieldMetadataType,
operator: string,
): Record<string, any> => {
const compositeType = compositeTypeDefinitions.get(fieldType);
if (!compositeType) {
throw new GraphqlQueryRunnerException(
`Composite type definition not found for type: ${fieldType}`,
GraphqlQueryRunnerExceptionCode.INVALID_CURSOR,
);
}
const result: Record<string, any> = {};
compositeType.properties.forEach((property) => {
if (
property.type !== FieldMetadataType.RAW_JSON &&
value[property.name] !== undefined
) {
result[key] = {
...result[key],
[property.name]: { [operator]: value[property.name] },
};
}
});
return result;
};