diff --git a/.changeset/rich-ears-bathe.md b/.changeset/rich-ears-bathe.md new file mode 100644 index 0000000000..b86c884d39 --- /dev/null +++ b/.changeset/rich-ears-bathe.md @@ -0,0 +1,6 @@ +--- +'@directus/api': patch +--- + +Fixed deep `groupBy` queries for O2M relations, where results were not correctly grouped under their respective parent +items diff --git a/api/src/database/get-ast-from-query/lib/parse-fields.ts b/api/src/database/get-ast-from-query/lib/parse-fields.ts index 4d8be36e09..478b4d8ef5 100644 --- a/api/src/database/get-ast-from-query/lib/parse-fields.ts +++ b/api/src/database/get-ast-from-query/lib/parse-fields.ts @@ -4,7 +4,7 @@ import type { Knex } from 'knex'; import { isEmpty } from 'lodash-es'; import { fetchPermissions } from '../../../permissions/lib/fetch-permissions.js'; import { fetchPolicies } from '../../../permissions/lib/fetch-policies.js'; -import type { FieldNode, FunctionFieldNode, NestedCollectionNode } from '../../../types/index.js'; +import type { FieldNode, FunctionFieldNode, NestedCollectionNode, O2MNode } from '../../../types/index.js'; import { getRelationType } from '../../../utils/get-relation-type.js'; import { getDeepQuery } from '../utils/get-deep-query.js'; import { getRelatedCollection } from '../utils/get-related-collection.js'; @@ -252,8 +252,14 @@ export async function parseFields( whenCase: [], }; - if (relationType === 'o2m' && !child!.query.sort) { - child!.query.sort = [relation.meta?.sort_field || context.schema.collections[relation.collection]!.primary]; + if (isO2MNode(child) && !child.query.sort) { + child.query.sort = [relation.meta?.sort_field || context.schema.collections[relation.collection]!.primary]; + } + + if (isO2MNode(child) && child?.query.group && child.query.group[0] !== relation.field) { + // If a group by is used, the result needs to be grouped by the foreign key of the relation first, so results + // are correctly grouped under the foreign key when extracting the grouped results from the nested queries. + child.query.group.unshift(relation.field); } } @@ -275,3 +281,7 @@ export async function parseFields( return true; }); } + +export function isO2MNode(node: NestedCollectionNode | null): node is O2MNode { + return !!node && node.type === 'o2m'; +}