From 50b01fe5cacf4ae39aa4ed941eaf535fbd03debe Mon Sep 17 00:00:00 2001 From: ymc9 <104139426+ymc9@users.noreply.github.com> Date: Tue, 20 May 2025 11:18:45 -0700 Subject: [PATCH 1/2] fix(policy): Prisma extension computed fields are not returned when fields level polices are used fixes #2104 --- .../enhancements/node/policy/policy-utils.ts | 67 +++++++++---------- tests/regression/tests/issue-2104.test.ts | 53 +++++++++++++++ 2 files changed, 84 insertions(+), 36 deletions(-) create mode 100644 tests/regression/tests/issue-2104.test.ts diff --git a/packages/runtime/src/enhancements/node/policy/policy-utils.ts b/packages/runtime/src/enhancements/node/policy/policy-utils.ts index 0e655a3e5..2ee389476 100644 --- a/packages/runtime/src/enhancements/node/policy/policy-utils.ts +++ b/packages/runtime/src/enhancements/node/policy/policy-utils.ts @@ -1204,47 +1204,42 @@ export class PolicyUtil extends QueryUtils { return; } - let target: any; // injection target - let isInclude = false; // if the target is include or select - - if (args.select) { - target = args.select; - isInclude = false; - } else if (args.include) { - target = args.include; - isInclude = true; - } else { - target = args.select = this.makeAllScalarFieldSelect(model); - isInclude = false; - } - - if (!isInclude) { - // merge selects - for (const [k, v] of Object.entries(input.select)) { - if (v === true) { - if (!target[k]) { - target[k] = true; - } + // process scalar field selections first + for (const [k, v] of Object.entries(input.select)) { + const field = resolveField(this.modelMeta, model, k); + if (!field || field.isDataModel) { + continue; + } + if (v === true) { + if (!args.select) { + // do nothing since all scalar fields are selected by default + } else if (args.include) { + // do nothing since include implies selecting all scalar fields + } else { + args.select[k] = true; } } } - // recurse into nested selects (relation fields) + // process relation selections for (const [k, v] of Object.entries(input.select)) { - if (typeof v === 'object' && v?.select) { - const field = resolveField(this.modelMeta, model, k); - if (field?.isDataModel) { - // recurse into relation - if (isInclude && target[k] === true) { - // select all fields for the relation - target[k] = { select: this.makeAllScalarFieldSelect(field.type) }; - } else if (!target[k]) { - // ensure an empty select clause - target[k] = { select: {} }; - } - // recurse - this.doInjectReadCheckSelect(field.type, target[k], v); - } + const field = resolveField(this.modelMeta, model, k); + if (!field || !field.isDataModel) { + continue; + } + + // prepare the next level of args + let nextArgs = args.select ?? args.include; + if (!nextArgs) { + nextArgs = args.include = {}; + } + if (!nextArgs[k] || typeof nextArgs[k] !== 'object') { + nextArgs[k] = {}; + } + + if (v && typeof v === 'object') { + // recurse into relation + this.doInjectReadCheckSelect(field.type, nextArgs[k], v); } } } diff --git a/tests/regression/tests/issue-2104.test.ts b/tests/regression/tests/issue-2104.test.ts new file mode 100644 index 000000000..0b3ba912d --- /dev/null +++ b/tests/regression/tests/issue-2104.test.ts @@ -0,0 +1,53 @@ +import { loadSchema } from '@zenstackhq/testtools'; + +describe('issue 2104', () => { + it('regression', async () => { + const { enhance } = await loadSchema( + ` + model Post { + id Int @id @default(autoincrement()) + tickets TicketInPost[] + @@allow('all', true) + } + + model View { + id Int @id @default(autoincrement()) + ability String + tickets TicketInPost[] + @@allow('all', true) + } + + model TicketInPost { + id Int @id @default(autoincrement()) + postId Int + post Post @relation(fields: [postId], references: [id]) + viewId Int + view View @relation(fields: [viewId], references: [id]) + @@allow('all', true) + } + `, + { logPrismaQuery: true, enhancements: ['policy', 'delegate'] } + ); + + const db = enhance(); + const view = await db.view.create({ data: { ability: 'bla' } }); + const post = await db.post.create({ data: {} }); + const updatedPost = await db.post.update({ + where: { + id: post.id, + }, + data: { + tickets: { + deleteMany: {}, + create: { + viewId: view.id, + }, + }, + }, + include: { + tickets: true, + }, + }); + console.log('created!', updatedPost); + }); +}); From 154f4747456fdfbe6351295aaceb9acebae15194 Mon Sep 17 00:00:00 2001 From: ymc9 <104139426+ymc9@users.noreply.github.com> Date: Tue, 20 May 2025 11:21:38 -0700 Subject: [PATCH 2/2] update test --- tests/regression/tests/issue-2104.test.ts | 53 ----------------------- tests/regression/tests/issue-2117.test.ts | 43 ++++++++++++++++++ 2 files changed, 43 insertions(+), 53 deletions(-) delete mode 100644 tests/regression/tests/issue-2104.test.ts create mode 100644 tests/regression/tests/issue-2117.test.ts diff --git a/tests/regression/tests/issue-2104.test.ts b/tests/regression/tests/issue-2104.test.ts deleted file mode 100644 index 0b3ba912d..000000000 --- a/tests/regression/tests/issue-2104.test.ts +++ /dev/null @@ -1,53 +0,0 @@ -import { loadSchema } from '@zenstackhq/testtools'; - -describe('issue 2104', () => { - it('regression', async () => { - const { enhance } = await loadSchema( - ` - model Post { - id Int @id @default(autoincrement()) - tickets TicketInPost[] - @@allow('all', true) - } - - model View { - id Int @id @default(autoincrement()) - ability String - tickets TicketInPost[] - @@allow('all', true) - } - - model TicketInPost { - id Int @id @default(autoincrement()) - postId Int - post Post @relation(fields: [postId], references: [id]) - viewId Int - view View @relation(fields: [viewId], references: [id]) - @@allow('all', true) - } - `, - { logPrismaQuery: true, enhancements: ['policy', 'delegate'] } - ); - - const db = enhance(); - const view = await db.view.create({ data: { ability: 'bla' } }); - const post = await db.post.create({ data: {} }); - const updatedPost = await db.post.update({ - where: { - id: post.id, - }, - data: { - tickets: { - deleteMany: {}, - create: { - viewId: view.id, - }, - }, - }, - include: { - tickets: true, - }, - }); - console.log('created!', updatedPost); - }); -}); diff --git a/tests/regression/tests/issue-2117.test.ts b/tests/regression/tests/issue-2117.test.ts new file mode 100644 index 000000000..46be7c195 --- /dev/null +++ b/tests/regression/tests/issue-2117.test.ts @@ -0,0 +1,43 @@ +import { loadSchema } from '@zenstackhq/testtools'; + +describe('issue 2117', () => { + it('regression', async () => { + const { prisma, enhanceRaw, prismaModule } = await loadSchema( + ` + model User { + uuid String @id + email String @unique @deny('read', auth().uuid != this.uuid) + username String @unique + @@allow('all', true) + } + ` + ); + + const extPrisma = prisma.$extends( + prismaModule.defineExtension({ + name: 'urls-extension', + result: { + user: { + pageUrl: { + needs: { username: true }, + compute: () => `foo`, + }, + }, + }, + }) + ); + + const db = enhanceRaw(extPrisma, { user: { uuid: '1' } }, { logPrismaQuery: true }); + await db.user.create({ data: { uuid: '1', email: 'a@b.com', username: 'a' } }); + await expect(db.user.findFirst()).resolves.toMatchObject({ + uuid: '1', + email: 'a@b.com', + username: 'a', + pageUrl: 'foo', + }); + const r = await db.user.findFirst({ select: { email: true } }); + expect(r.email).toBeTruthy(); + expect(r.uuid).toBeUndefined(); + expect(r.pageUrl).toBeUndefined(); + }); +});