Fix orderBy columns missing from SELECT in DISTINCT subquery (#17079)

## Description

Fixes a bug where queries with relation field + scalar field ordering
would fail with:
```
column distinctAlias.person_position does not exist
```

## Root Cause

When a GraphQL query orders by columns that are **not in the selected
fields**, TypeORM's DISTINCT subquery fails because it expects those
columns in the inner SELECT with alias format (e.g., `person_position`).

The issue only manifests when:
1. A filter is applied (triggers DISTINCT path in TypeORM)
2. OrderBy includes columns NOT in the GraphQL selection
3. Example: query selects only `id`, but orders by `position`

## The Fix

`addRelationOrderColumnsToBuilder` now accepts `columnsToSelect` and
adds orderBy columns via `addSelect()` only if they're **NOT** already
in the selected columns:

- **Relation orderBy columns**: Always added (never in columnsToSelect)
- **Main entity orderBy columns**: Added only when not already selected

This ensures all orderBy columns are present in TypeORM's inner SELECT
for the DISTINCT subquery.

## Test Plan

Added integration test for the exact failing scenario:
- Filter with `neq` (triggers DISTINCT path)
- Multiple orderBy: relation field + scalar field  
- Minimal field selection (only `id`, not `position`)

## Related

Regression introduced in #17021 which added nested sort support.
This commit is contained in:
Félix Malfait
2026-01-11 14:19:05 +01:00
committed by GitHub
parent 20f62e05f5
commit 57363c2127
3 changed files with 76 additions and 11 deletions
@@ -31,10 +31,6 @@ import {
} from 'src/engine/api/common/types/common-query-args.type';
import { CommonSelectedFieldsResult } from 'src/engine/api/common/types/common-selected-fields-result.type';
import { getPageInfo } from 'src/engine/api/common/utils/get-page-info.util';
import {
GraphqlQueryRunnerException,
GraphqlQueryRunnerExceptionCode,
} from 'src/engine/api/graphql/graphql-query-runner/errors/graphql-query-runner.exception';
import { ProcessAggregateHelper } from 'src/engine/api/graphql/graphql-query-runner/helpers/process-aggregate.helper';
import { buildColumnsToSelect } from 'src/engine/api/graphql/graphql-query-runner/utils/build-columns-to-select';
import { getCursor } from 'src/engine/api/graphql/graphql-query-runner/utils/cursors.util';
@@ -112,11 +108,12 @@ export class CommonFindManyQueryRunnerService extends CommonBaseQueryRunnerServi
fieldIdByName,
)
) {
throw new GraphqlQueryRunnerException(
// Not throwing exception because still used on record show page
/* throw new GraphqlQueryRunnerException(
'Cursor-based pagination is not supported with relation field ordering. Use offset pagination instead.',
GraphqlQueryRunnerExceptionCode.INVALID_CURSOR,
{ userFriendlyMessage: STANDARD_ERROR_MESSAGE },
);
); */
}
const cursorArgFilter = computeCursorArgFilter(
@@ -172,11 +169,13 @@ export class CommonFindManyQueryRunnerService extends CommonBaseQueryRunnerServi
queryBuilder.setFindOptions({ select: columnsToSelect });
queryBuilder.take(limit + 1);
// Add relation order columns AFTER setFindOptions (setFindOptions clears addSelect)
// Add order columns AFTER setFindOptions (setFindOptions clears addSelect)
// Pass columnsToSelect so we only add columns that aren't already selected
commonQueryParser.addRelationOrderColumnsToBuilder(
queryBuilder,
parsedOrderBy,
flatObjectMetadata.nameSingular,
columnsToSelect,
);
const objectRecords = (await queryBuilder.getMany()) as ObjectRecord[];