From a3fad9e2f90e9772abf1ba92511fa0b01cbe8a30 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 6 Feb 2026 03:08:23 +0000 Subject: [PATCH 1/5] Initial plan From c0836071b042115435b30105144d21b296a91ef1 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 6 Feb 2026 03:17:45 +0000 Subject: [PATCH 2/5] Fix query error when select has only false fields Co-authored-by: jiashengguo <16688722+jiashengguo@users.noreply.github.com> --- .../orm/src/client/crud/operations/base.ts | 6 +++++ tests/e2e/orm/client-api/find.test.ts | 24 +++++++++++++++++++ 2 files changed, 30 insertions(+) diff --git a/packages/orm/src/client/crud/operations/base.ts b/packages/orm/src/client/crud/operations/base.ts index fc75cac9d..029ba4c3b 100644 --- a/packages/orm/src/client/crud/operations/base.ts +++ b/packages/orm/src/client/crud/operations/base.ts @@ -283,6 +283,12 @@ export abstract class BaseOperationHandler { // select if (args && 'select' in args && args.select) { + // check if all fields are false - if so, return empty array + const hasAnyTrueField = Object.values(args.select).some((value) => !!value); + if (!hasAnyTrueField) { + // when all fields are explicitly set to false, return empty objects + return []; + } // select is mutually exclusive with omit query = this.buildFieldSelection(model, query, args.select, model); } else { diff --git a/tests/e2e/orm/client-api/find.test.ts b/tests/e2e/orm/client-api/find.test.ts index 5b6ab5639..a8912eb08 100644 --- a/tests/e2e/orm/client-api/find.test.ts +++ b/tests/e2e/orm/client-api/find.test.ts @@ -835,6 +835,30 @@ describe('Client find tests ', () => { }); }); + it('returns empty array when all select fields are false', async () => { + const user = await createUser(client); + await createPosts(client, user.id); + + // findMany with only false fields in select should return empty array + const r1 = await client.user.findMany({ + select: { id: false } as any, + }); + expect(r1).toEqual([]); + + // findFirst with only false fields in select should return empty array + const r2 = await client.user.findFirst({ + select: { id: false, email: false } as any, + }); + expect(r2).toBeNull(); + + // findUnique with only false fields in select should return empty object + const r3 = await client.user.findUnique({ + where: { id: user.id }, + select: { id: false } as any, + }); + expect(r3).toBeNull(); + }); + it('allows field omission', async () => { const user = await createUser(client); await createPosts(client, user.id); From 5c54a58d5e67b6a3d5fbfce112b400ca68147cf2 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 6 Feb 2026 03:18:59 +0000 Subject: [PATCH 3/5] Fix comments to match actual behavior Co-authored-by: jiashengguo <16688722+jiashengguo@users.noreply.github.com> --- packages/orm/src/client/crud/operations/base.ts | 2 +- tests/e2e/orm/client-api/find.test.ts | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/orm/src/client/crud/operations/base.ts b/packages/orm/src/client/crud/operations/base.ts index 029ba4c3b..ebb3dee49 100644 --- a/packages/orm/src/client/crud/operations/base.ts +++ b/packages/orm/src/client/crud/operations/base.ts @@ -286,7 +286,7 @@ export abstract class BaseOperationHandler { // check if all fields are false - if so, return empty array const hasAnyTrueField = Object.values(args.select).some((value) => !!value); if (!hasAnyTrueField) { - // when all fields are explicitly set to false, return empty objects + // when all fields are explicitly set to false, return empty array return []; } // select is mutually exclusive with omit diff --git a/tests/e2e/orm/client-api/find.test.ts b/tests/e2e/orm/client-api/find.test.ts index a8912eb08..75f33e449 100644 --- a/tests/e2e/orm/client-api/find.test.ts +++ b/tests/e2e/orm/client-api/find.test.ts @@ -845,13 +845,13 @@ describe('Client find tests ', () => { }); expect(r1).toEqual([]); - // findFirst with only false fields in select should return empty array + // findFirst with only false fields in select should return null const r2 = await client.user.findFirst({ select: { id: false, email: false } as any, }); expect(r2).toBeNull(); - // findUnique with only false fields in select should return empty object + // findUnique with only false fields in select should return null const r3 = await client.user.findUnique({ where: { id: user.id }, select: { id: false } as any, From 3adea0a0cd0d1e17e5b44bb2622ea7c6163cb81f Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 6 Feb 2026 04:10:41 +0000 Subject: [PATCH 4/5] Add support for nested queries with only false select fields Co-authored-by: jiashengguo <16688722+jiashengguo@users.noreply.github.com> --- .../dialects/lateral-join-dialect-base.ts | 21 ++++++++++++++++++- packages/orm/src/client/result-processor.ts | 11 ++++++++++ tests/e2e/orm/client-api/find.test.ts | 15 +++++++++++++ 3 files changed, 46 insertions(+), 1 deletion(-) diff --git a/packages/orm/src/client/crud/dialects/lateral-join-dialect-base.ts b/packages/orm/src/client/crud/dialects/lateral-join-dialect-base.ts index 6bc1f887c..767610539 100644 --- a/packages/orm/src/client/crud/dialects/lateral-join-dialect-base.ts +++ b/packages/orm/src/client/crud/dialects/lateral-join-dialect-base.ts @@ -1,5 +1,5 @@ import { invariant } from '@zenstackhq/common-helpers'; -import { type AliasableExpression, type Expression, type ExpressionBuilder, type SelectQueryBuilder } from 'kysely'; +import { type AliasableExpression, type Expression, type ExpressionBuilder, type SelectQueryBuilder, sql } from 'kysely'; import type { FieldDef, GetModels, SchemaDef } from '../../../schema'; import { DELEGATE_JOINED_FIELD_PREFIX } from '../../constants'; import type { FindArgs } from '../../crud-types'; @@ -170,9 +170,20 @@ export abstract class LateralJoinDialectBase extends B parentResultName, ); + // Check if objArgs is empty (all select fields are false) + const hasFields = Object.keys(objArgs).length > 0; + if (relationFieldDef.array) { + if (!hasFields) { + // Return empty array for array relations when all select fields are false + return sql`CAST('[]' AS JSON)`.as('$data'); + } return this.buildArrayAgg(this.buildJsonObject(objArgs)).as('$data'); } else { + if (!hasFields) { + // Return null for single relations when all select fields are false + return sql`NULL`.as('$data'); + } return this.buildJsonObject(objArgs).as('$data'); } }); @@ -217,6 +228,14 @@ export abstract class LateralJoinDialectBase extends B })), ); } else if (payload.select) { + // check if all select fields are false + const hasAnyTrueField = Object.values(payload.select).some((value) => !!value); + if (!hasAnyTrueField) { + // when all fields are explicitly set to false, return empty objArgs + // this will be filtered out later + return objArgs; + } + // select specific fields Object.assign( objArgs, diff --git a/packages/orm/src/client/result-processor.ts b/packages/orm/src/client/result-processor.ts index fc8ae1938..ea17d5892 100644 --- a/packages/orm/src/client/result-processor.ts +++ b/packages/orm/src/client/result-processor.ts @@ -110,6 +110,17 @@ export class ResultProcessor { return value; } } + + // Filter out empty objects from array relations (caused by select with all false fields) + if (Array.isArray(relationData) && fieldDef.array) { + relationData = relationData.filter((item) => { + if (item && typeof item === 'object') { + return Object.keys(item).length > 0; + } + return true; + }); + } + return this.doProcessResult(relationData, fieldDef.type as GetModels); } diff --git a/tests/e2e/orm/client-api/find.test.ts b/tests/e2e/orm/client-api/find.test.ts index 75f33e449..8384e46cf 100644 --- a/tests/e2e/orm/client-api/find.test.ts +++ b/tests/e2e/orm/client-api/find.test.ts @@ -857,6 +857,21 @@ describe('Client find tests ', () => { select: { id: false } as any, }); expect(r3).toBeNull(); + + // nested query with only false fields in select should return empty array for relations + const r4 = await client.user.findUnique({ + where: { id: user.id }, + select: { + id: true, + email: true, + posts: { select: { id: false } as any }, + }, + }); + expect(r4).toMatchObject({ + id: user.id, + email: user.email, + posts: [], + }); }); it('allows field omission', async () => { From 2c8e9cb4565967d3ba0e8a83cd0fce4bbcc72e82 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 6 Feb 2026 04:12:08 +0000 Subject: [PATCH 5/5] Improve comments based on code review feedback Co-authored-by: jiashengguo <16688722+jiashengguo@users.noreply.github.com> --- .../orm/src/client/crud/dialects/lateral-join-dialect-base.ts | 4 ++-- packages/orm/src/client/result-processor.ts | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/orm/src/client/crud/dialects/lateral-join-dialect-base.ts b/packages/orm/src/client/crud/dialects/lateral-join-dialect-base.ts index 767610539..9e6cd020f 100644 --- a/packages/orm/src/client/crud/dialects/lateral-join-dialect-base.ts +++ b/packages/orm/src/client/crud/dialects/lateral-join-dialect-base.ts @@ -175,7 +175,7 @@ export abstract class LateralJoinDialectBase extends B if (relationFieldDef.array) { if (!hasFields) { - // Return empty array for array relations when all select fields are false + // Return JSON-formatted empty array literal for array relations when all select fields are false return sql`CAST('[]' AS JSON)`.as('$data'); } return this.buildArrayAgg(this.buildJsonObject(objArgs)).as('$data'); @@ -232,7 +232,7 @@ export abstract class LateralJoinDialectBase extends B const hasAnyTrueField = Object.values(payload.select).some((value) => !!value); if (!hasAnyTrueField) { // when all fields are explicitly set to false, return empty objArgs - // this will be filtered out later + // (filtered out in ResultProcessor.processRelation for array relations) return objArgs; } diff --git a/packages/orm/src/client/result-processor.ts b/packages/orm/src/client/result-processor.ts index ea17d5892..1682e2d12 100644 --- a/packages/orm/src/client/result-processor.ts +++ b/packages/orm/src/client/result-processor.ts @@ -111,7 +111,7 @@ export class ResultProcessor { } } - // Filter out empty objects from array relations (caused by select with all false fields) + // Filter out objects with no properties from array relations (occurs when all select fields are false) if (Array.isArray(relationData) && fieldDef.array) { relationData = relationData.filter((item) => { if (item && typeof item === 'object') {