Skip to content

Commit daa3839

Browse files
authored
fix(delegate): delegate model shouldn't inherit @@index from an indirect abstract base (#1818)
1 parent d223819 commit daa3839

File tree

3 files changed

+92
-4
lines changed

3 files changed

+92
-4
lines changed

packages/schema/src/utils/ast-utils.ts

Lines changed: 23 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,13 @@ import {
1717
ModelImport,
1818
ReferenceExpr,
1919
} from '@zenstackhq/language/ast';
20-
import { getModelFieldsWithBases, getRecursiveBases, isDelegateModel, isFromStdlib } from '@zenstackhq/sdk';
20+
import {
21+
getInheritanceChain,
22+
getModelFieldsWithBases,
23+
getRecursiveBases,
24+
isDelegateModel,
25+
isFromStdlib,
26+
} from '@zenstackhq/sdk';
2127
import {
2228
AstNode,
2329
copyAstNode,
@@ -61,7 +67,7 @@ export function mergeBaseModels(model: Model, linker: Linker) {
6167
.concat(dataModel.fields);
6268

6369
dataModel.attributes = bases
64-
.flatMap((base) => base.attributes.filter((attr) => filterBaseAttribute(base, attr)))
70+
.flatMap((base) => base.attributes.filter((attr) => filterBaseAttribute(dataModel, base, attr)))
6571
.map((attr) => cloneAst(attr, dataModel, buildReference))
6672
.concat(dataModel.attributes);
6773
}
@@ -85,7 +91,7 @@ export function mergeBaseModels(model: Model, linker: Linker) {
8591
linkContentToContainer(model);
8692
}
8793

88-
function filterBaseAttribute(base: DataModel, attr: DataModelAttribute) {
94+
function filterBaseAttribute(forModel: DataModel, base: DataModel, attr: DataModelAttribute) {
8995
if (attr.$inheritedFrom) {
9096
// don't inherit from skip-level base
9197
return false;
@@ -101,13 +107,26 @@ function filterBaseAttribute(base: DataModel, attr: DataModelAttribute) {
101107
return false;
102108
}
103109

104-
if (isDelegateModel(base) && uninheritableFromDelegateAttributes.includes(attr.decl.$refText)) {
110+
if (
111+
// checks if the inheritance is from a delegate model or through one, if so,
112+
// the attribute shouldn't be inherited as the delegate already inherits it
113+
isInheritedFromOrThroughDelegate(forModel, base) &&
114+
uninheritableFromDelegateAttributes.includes(attr.decl.$refText)
115+
) {
105116
return false;
106117
}
107118

108119
return true;
109120
}
110121

122+
function isInheritedFromOrThroughDelegate(model: DataModel, base: DataModel) {
123+
if (isDelegateModel(base)) {
124+
return true;
125+
}
126+
const chain = getInheritanceChain(model, base);
127+
return !!chain?.some(isDelegateModel);
128+
}
129+
111130
// deep clone an AST, relink references, and set its container
112131
function cloneAst<T extends InheritableNode>(
113132
node: T,

packages/sdk/src/utils.ts

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -569,3 +569,24 @@ export function getInheritedFromDelegate(field: DataModelField) {
569569
const foundBase = bases.findLast((base) => base.fields.some((f) => f.name === field.name) && isDelegateModel(base));
570570
return foundBase;
571571
}
572+
573+
/**
574+
* Gets the inheritance chain from "from" to "to", excluding them.
575+
*/
576+
export function getInheritanceChain(from: DataModel, to: DataModel): DataModel[] | undefined {
577+
if (from === to) {
578+
return [];
579+
}
580+
581+
for (const base of from.superTypes) {
582+
if (base.ref === to) {
583+
return [];
584+
}
585+
const path = getInheritanceChain(base.ref!, to);
586+
if (path) {
587+
return [base.ref as DataModel, ...path];
588+
}
589+
}
590+
591+
return undefined;
592+
}
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
import { loadSchema } from '@zenstackhq/testtools';
2+
3+
describe('issue 1786', () => {
4+
it('regression', async () => {
5+
await loadSchema(
6+
`
7+
model User {
8+
id String @id @default(cuid())
9+
email String @unique @email @length(6, 32)
10+
password String @password @omit
11+
contents Content[]
12+
13+
// everybody can signup
14+
@@allow('create', true)
15+
16+
// full access by self
17+
@@allow('all', auth() == this)
18+
}
19+
20+
abstract model BaseContent {
21+
published Boolean @default(false)
22+
23+
@@index([published])
24+
}
25+
26+
model Content extends BaseContent {
27+
id String @id @default(cuid())
28+
createdAt DateTime @default(now())
29+
updatedAt DateTime @updatedAt
30+
owner User @relation(fields: [ownerId], references: [id])
31+
ownerId String
32+
contentType String
33+
34+
@@delegate(contentType)
35+
}
36+
37+
model Post extends Content {
38+
title String
39+
}
40+
41+
model Video extends Content {
42+
name String
43+
duration Int
44+
}
45+
`
46+
);
47+
});
48+
});

0 commit comments

Comments
 (0)