Skip to content

Commit 3140d9b

Browse files
authored
fix: incorrect prisma query executed when count using a where filter involving a polymorphic base field (#1586)
1 parent a11ab8c commit 3140d9b

File tree

3 files changed

+121
-49
lines changed

3 files changed

+121
-49
lines changed

packages/runtime/src/enhancements/delegate.ts

Lines changed: 15 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,13 @@ import {
77
FieldInfo,
88
ModelInfo,
99
NestedWriteVisitor,
10+
clone,
1011
enumerate,
1112
getIdFields,
1213
getModelInfo,
1314
isDelegateModel,
1415
resolveField,
1516
} from '../cross';
16-
import { clone } from '../cross';
1717
import type { CrudContract, DbClientContract } from '../types';
1818
import type { InternalEnhancementOptions } from './create-enhancement';
1919
import { Logger } from './logger';
@@ -79,7 +79,7 @@ export class DelegateProxyHandler extends DefaultPrismaProxyHandler {
7979

8080
if (args.orderBy) {
8181
// `orderBy` may contain fields from base types
82-
args.orderBy = this.buildWhereHierarchy(this.model, args.orderBy);
82+
this.injectWhereHierarchy(this.model, args.orderBy);
8383
}
8484

8585
if (this.options.logPrismaQuery) {
@@ -95,7 +95,7 @@ export class DelegateProxyHandler extends DefaultPrismaProxyHandler {
9595
}
9696

9797
private injectWhereHierarchy(model: string, where: any) {
98-
if (!where || typeof where !== 'object') {
98+
if (!where || !isPlainObject(where)) {
9999
return;
100100
}
101101

@@ -108,44 +108,9 @@ export class DelegateProxyHandler extends DefaultPrismaProxyHandler {
108108

109109
const fieldInfo = resolveField(this.options.modelMeta, model, field);
110110
if (!fieldInfo?.inheritedFrom) {
111-
return;
112-
}
113-
114-
let base = this.getBaseModel(model);
115-
let target = where;
116-
117-
while (base) {
118-
const baseRelationName = this.makeAuxRelationName(base);
119-
120-
// prepare base layer where
121-
let thisLayer: any;
122-
if (target[baseRelationName]) {
123-
thisLayer = target[baseRelationName];
124-
} else {
125-
thisLayer = target[baseRelationName] = {};
126-
}
127-
128-
if (base.name === fieldInfo.inheritedFrom) {
129-
thisLayer[field] = value;
130-
delete where[field];
131-
break;
132-
} else {
133-
target = thisLayer;
134-
base = this.getBaseModel(base.name);
111+
if (fieldInfo?.isDataModel) {
112+
this.injectWhereHierarchy(fieldInfo.type, value);
135113
}
136-
}
137-
});
138-
}
139-
140-
private buildWhereHierarchy(model: string, where: any) {
141-
if (!where) {
142-
return undefined;
143-
}
144-
145-
where = clone(where);
146-
Object.entries(where).forEach(([field, value]) => {
147-
const fieldInfo = resolveField(this.options.modelMeta, model, field);
148-
if (!fieldInfo?.inheritedFrom) {
149114
return;
150115
}
151116

@@ -164,6 +129,9 @@ export class DelegateProxyHandler extends DefaultPrismaProxyHandler {
164129
}
165130

166131
if (base.name === fieldInfo.inheritedFrom) {
132+
if (fieldInfo.isDataModel) {
133+
this.injectWhereHierarchy(base.name, value);
134+
}
167135
thisLayer[field] = value;
168136
delete where[field];
169137
break;
@@ -173,8 +141,6 @@ export class DelegateProxyHandler extends DefaultPrismaProxyHandler {
173141
}
174142
}
175143
});
176-
177-
return where;
178144
}
179145

180146
private injectSelectIncludeHierarchy(model: string, args: any) {
@@ -189,7 +155,7 @@ export class DelegateProxyHandler extends DefaultPrismaProxyHandler {
189155
if (fieldInfo && value !== undefined) {
190156
if (value?.orderBy) {
191157
// `orderBy` may contain fields from base types
192-
value.orderBy = this.buildWhereHierarchy(fieldInfo.type, value.orderBy);
158+
this.injectWhereHierarchy(fieldInfo.type, value.orderBy);
193159
}
194160

195161
if (this.injectBaseFieldSelect(model, field, value, args, kind)) {
@@ -921,15 +887,15 @@ export class DelegateProxyHandler extends DefaultPrismaProxyHandler {
921887
args = clone(args);
922888

923889
if (args.cursor) {
924-
args.cursor = this.buildWhereHierarchy(this.model, args.cursor);
890+
this.injectWhereHierarchy(this.model, args.cursor);
925891
}
926892

927893
if (args.orderBy) {
928-
args.orderBy = this.buildWhereHierarchy(this.model, args.orderBy);
894+
this.injectWhereHierarchy(this.model, args.orderBy);
929895
}
930896

931897
if (args.where) {
932-
args.where = this.buildWhereHierarchy(this.model, args.where);
898+
this.injectWhereHierarchy(this.model, args.where);
933899
}
934900

935901
if (this.options.logPrismaQuery) {
@@ -949,11 +915,11 @@ export class DelegateProxyHandler extends DefaultPrismaProxyHandler {
949915
args = clone(args);
950916

951917
if (args?.cursor) {
952-
args.cursor = this.buildWhereHierarchy(this.model, args.cursor);
918+
this.injectWhereHierarchy(this.model, args.cursor);
953919
}
954920

955921
if (args?.where) {
956-
args.where = this.buildWhereHierarchy(this.model, args.where);
922+
this.injectWhereHierarchy(this.model, args.where);
957923
}
958924

959925
if (this.options.logPrismaQuery) {
@@ -989,7 +955,7 @@ export class DelegateProxyHandler extends DefaultPrismaProxyHandler {
989955
args = clone(args);
990956

991957
if (args.where) {
992-
args.where = this.buildWhereHierarchy(this.model, args.where);
958+
this.injectWhereHierarchy(this.model, args.where);
993959
}
994960

995961
if (this.options.logPrismaQuery) {

tests/integration/tests/enhancements/with-delegate/enhanced-client.test.ts

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -284,6 +284,70 @@ describe('Polymorphism Test', () => {
284284
});
285285
});
286286

287+
it('read with compound filter', async () => {
288+
const { enhance } = await loadSchema(
289+
`
290+
model Base {
291+
id Int @id @default(autoincrement())
292+
type String
293+
viewCount Int
294+
@@delegate(type)
295+
}
296+
297+
model Foo extends Base {
298+
name String
299+
}
300+
`,
301+
{ enhancements: ['delegate'] }
302+
);
303+
304+
const db = enhance();
305+
await db.foo.create({ data: { name: 'foo1', viewCount: 0 } });
306+
await db.foo.create({ data: { name: 'foo2', viewCount: 1 } });
307+
308+
await expect(db.foo.findMany({ where: { viewCount: { gt: 0 } } })).resolves.toHaveLength(1);
309+
await expect(db.foo.findMany({ where: { AND: { viewCount: { gt: 0 } } } })).resolves.toHaveLength(1);
310+
await expect(db.foo.findMany({ where: { AND: [{ viewCount: { gt: 0 } }] } })).resolves.toHaveLength(1);
311+
await expect(db.foo.findMany({ where: { OR: [{ viewCount: { gt: 0 } }] } })).resolves.toHaveLength(1);
312+
await expect(db.foo.findMany({ where: { NOT: { viewCount: { lte: 0 } } } })).resolves.toHaveLength(1);
313+
});
314+
315+
it('read with nested filter', async () => {
316+
const { enhance } = await loadSchema(
317+
`
318+
model Base {
319+
id Int @id @default(autoincrement())
320+
type String
321+
viewCount Int
322+
@@delegate(type)
323+
}
324+
325+
model Foo extends Base {
326+
name String
327+
bar Bar?
328+
}
329+
330+
model Bar extends Base {
331+
foo Foo @relation(fields: [fooId], references: [id])
332+
fooId Int @unique
333+
}
334+
`,
335+
{ enhancements: ['delegate'] }
336+
);
337+
338+
const db = enhance();
339+
340+
await db.bar.create({
341+
data: { foo: { create: { name: 'foo', viewCount: 2 } }, viewCount: 1 },
342+
});
343+
344+
await expect(
345+
db.bar.findMany({
346+
where: { viewCount: { gt: 0 }, foo: { viewCount: { gt: 1 } } },
347+
})
348+
).resolves.toHaveLength(1);
349+
});
350+
287351
it('order by base fields', async () => {
288352
const { db, user } = await setup();
289353

@@ -1013,6 +1077,18 @@ describe('Polymorphism Test', () => {
10131077
});
10141078
expect(count).toMatchObject({ _all: 1, rating: 1 });
10151079

1080+
count = await db.ratedVideo.count({
1081+
select: { _all: true, rating: true },
1082+
where: { AND: { viewCount: { gt: 0 }, rating: { gt: 10 } } },
1083+
});
1084+
expect(count).toMatchObject({ _all: 1, rating: 1 });
1085+
1086+
count = await db.ratedVideo.count({
1087+
select: { _all: true, rating: true },
1088+
where: { AND: [{ viewCount: { gt: 0 }, rating: { gt: 10 } }] },
1089+
});
1090+
expect(count).toMatchObject({ _all: 1, rating: 1 });
1091+
10161092
expect(() => db.ratedVideo.count({ select: { rating: true, viewCount: true } })).toThrow(
10171093
'count with fields from base type is not supported yet'
10181094
);
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
import { loadSchema } from '@zenstackhq/testtools';
2+
describe('issue 1585', () => {
3+
it('regression', async () => {
4+
const { enhance } = await loadSchema(
5+
`
6+
model Asset {
7+
id Int @id @default(autoincrement())
8+
type String
9+
views Int
10+
11+
@@allow('all', true)
12+
@@delegate(type)
13+
}
14+
15+
model Post extends Asset {
16+
title String
17+
}
18+
`
19+
);
20+
21+
const db = enhance();
22+
await db.post.create({ data: { title: 'Post1', views: 0 } });
23+
await db.post.create({ data: { title: 'Post2', views: 1 } });
24+
await expect(
25+
db.post.count({
26+
where: { views: { gt: 0 } },
27+
})
28+
).resolves.toBe(1);
29+
});
30+
});

0 commit comments

Comments
 (0)