Skip to content

Commit adc753f

Browse files
committed
include mixin-inherited @computed fields in model's computedFields schema property
1 parent 39a0a28 commit adc753f

File tree

2 files changed

+88
-1
lines changed

2 files changed

+88
-1
lines changed

packages/sdk/src/ts-schema-generator.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -453,7 +453,7 @@ export class TsSchemaGenerator {
453453
...(dm.isView ? [ts.factory.createPropertyAssignment('isView', ts.factory.createTrue())] : []),
454454
];
455455

456-
const computedFields = dm.fields.filter((f) => hasAttribute(f, '@computed'));
456+
const computedFields = allFields.filter((f) => hasAttribute(f, '@computed'));
457457

458458
if (computedFields.length > 0) {
459459
fields.push(
Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
import { createTestClient } from '@zenstackhq/testtools';
2+
import { describe, expect, it } from 'vitest';
3+
4+
// Bug: when a model with @computed fields inherited from a mixin type (the `with`
5+
// keyword) is fetched as a nested relation via an explicit `include`, ZenStack
6+
// emits the computed field name as a raw column reference inside `jsonb_build_object`
7+
// while the inner subquery SELECT only contains the real DB columns. This causes
8+
// PostgreSQL to fail with:
9+
// "column $$tN.field_name does not exist"
10+
//
11+
// Root cause: `buildSelectField` uses `fieldDef.originModel` (the mixin type name)
12+
// as the table alias when selecting the computed field into the inner subquery.
13+
// But the inner subquery aliases the actual table under the model name, not the
14+
// mixin type name, so the correlated subquery is never emitted and `parentCode`
15+
// is absent from the subquery's SELECT list. The outer `jsonb_build_object` then
16+
// references `$$tN.parentCode` which does not exist.
17+
//
18+
// The bug does NOT occur when:
19+
// - the @computed field is declared directly on the model (not via a mixin type)
20+
// - the relation is not explicitly included
21+
// - the model is queried directly (not as a nested include)
22+
23+
describe('Computed fields with nested include', () => {
24+
it('includes computed fields inherited from a mixin type when the model is explicitly included', async () => {
25+
const db = await createTestClient(
26+
`
27+
type ParentRelated {
28+
parentCode String? @computed
29+
}
30+
31+
model Parent {
32+
id Int @id @default(autoincrement())
33+
code String
34+
children Child[]
35+
}
36+
37+
model Child with ParentRelated {
38+
id Int @id @default(autoincrement())
39+
name String
40+
parentId Int
41+
parent Parent @relation(fields: [parentId], references: [id])
42+
}
43+
`,
44+
{
45+
provider: 'postgresql',
46+
computedFields: {
47+
Child: {
48+
// Correlated subquery — looks up Parent.code via the FK.
49+
// The computed field is inherited from the `ParentRelated` mixin,
50+
// so fieldDef.originModel === 'ParentRelated', which is the
51+
// alias incorrectly used by buildSelectField.
52+
parentCode: (eb: any) =>
53+
eb
54+
.selectFrom('Parent')
55+
.select('Parent.code')
56+
.whereRef('Parent.id', '=', 'parentId')
57+
.limit(1),
58+
},
59+
},
60+
} as any,
61+
);
62+
63+
const parent = await db.parent.create({
64+
data: { code: 'P-001', children: { create: [{ name: 'Alice' }, { name: 'Bob' }] } },
65+
});
66+
67+
// Direct query on Child works fine
68+
await expect(db.child.findFirst({ where: { parentId: parent.id } })).resolves.toMatchObject({
69+
parentCode: 'P-001',
70+
});
71+
72+
// Querying Parent with include: { children: true } should also work,
73+
// but currently fails with "column $$tN.parentCode does not exist"
74+
await expect(
75+
db.parent.findFirst({
76+
where: { id: parent.id },
77+
include: { children: true },
78+
}),
79+
).resolves.toMatchObject({
80+
code: 'P-001',
81+
children: expect.arrayContaining([
82+
expect.objectContaining({ name: 'Alice', parentCode: 'P-001' }),
83+
expect.objectContaining({ name: 'Bob', parentCode: 'P-001' }),
84+
]),
85+
});
86+
});
87+
});

0 commit comments

Comments
 (0)