Skip to content

Commit

Permalink
fix(delegate): generated logical prisma schema has errors when abstra…
Browse files Browse the repository at this point in the history
…ct model is involved
  • Loading branch information
ymc9 committed Jun 7, 2024
1 parent ba636c8 commit 810070a
Show file tree
Hide file tree
Showing 4 changed files with 94 additions and 6 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import {
isDelegateModel,
} from '@zenstackhq/sdk';
import { AstNode, DiagnosticInfo, ValidationAcceptor, getDocument } from 'langium';
import { findUpInheritance } from '../../utils/ast-utils';
import { IssueCodes, SCALAR_TYPES } from '../constants';
import { AstValidator } from '../types';
import { getUniqueFields } from '../utils';
Expand Down Expand Up @@ -238,7 +239,7 @@ export default class DataModelValidator implements AstValidator<DataModel> {
return;
}

if (field.$container !== contextModel && isDelegateModel(field.$container as DataModel)) {
if (this.isFieldInheritedFromDelegateModel(field, contextModel)) {
// relation fields inherited from delegate model don't need opposite relation
return;
}
Expand Down Expand Up @@ -390,6 +391,16 @@ export default class DataModelValidator implements AstValidator<DataModel> {
}
}

// checks if the given field is inherited directly or indirectly from a delegate model
private isFieldInheritedFromDelegateModel(field: DataModelField, contextModel: DataModel) {
const basePath = findUpInheritance(contextModel, field.$container as DataModel);
if (basePath && basePath.some(isDelegateModel)) {
return true;
} else {
return false;
}
}

private validateBaseAbstractModel(model: DataModel, accept: ValidationAcceptor) {
model.superTypes.forEach((superType, index) => {
if (
Expand Down
44 changes: 39 additions & 5 deletions packages/schema/src/plugins/prisma/schema-generator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -579,18 +579,30 @@ export class PrismaSchemaGenerator {
// the logical schema needs to name relations inherited from delegate base models for disambiguation

decl.fields.forEach((f) => {
if (!f.$inheritedFrom || !isDelegateModel(f.$inheritedFrom) || !isDataModel(f.type.reference?.ref)) {
if (!isDataModel(f.type.reference?.ref)) {
// only process relation fields
return;
}

const prismaField = model.fields.find((field) => field.name === f.name);
if (!prismaField) {
if (!f.$inheritedFrom) {
// only process inherited fields
return;
}

// find the base field that this field is inherited from
const baseField = f.$inheritedFrom.fields.find((field) => field.name === f.name);
// Walk up the inheritance chain to find a field with matching name
// which is where this field is inherited from.
//
// Note that we can't walk all the way up to the $inheritedFrom model
// because it may have been eliminated because of being abstract.

const baseField = this.findUpMatchingFieldFromDelegate(decl, f);
if (!baseField) {
// only process fields inherited from delegate models
return;
}

const prismaField = model.fields.find((field) => field.name === f.name);
if (!prismaField) {
return;
}

Expand Down Expand Up @@ -629,6 +641,28 @@ export class PrismaSchemaGenerator {
});
}

private findUpMatchingFieldFromDelegate(start: DataModel, target: DataModelField): DataModelField | undefined {
for (const base of start.superTypes) {
if (isDataModel(base.ref)) {
if (isDelegateModel(base.ref)) {
const field = base.ref.fields.find((f) => f.name === target.name);
if (field) {
if (!field.$inheritedFrom || !isDelegateModel(field.$inheritedFrom)) {
// if this field is not inherited from an upper delegate, we're done
return field;
}
}
}

const upper = this.findUpMatchingFieldFromDelegate(base.ref, target);
if (upper) {
return upper;
}
}
}
return undefined;
}

private getOppositeRelationField(oppositeModel: DataModel, relationField: DataModelField) {
const relName = this.getRelationName(relationField);
return oppositeModel.fields.find(
Expand Down
16 changes: 16 additions & 0 deletions packages/schema/src/utils/ast-utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -296,3 +296,19 @@ export function getAllLoadedAndReachableDataModels(langiumDocuments: LangiumDocu

return allDataModels;
}

/**
* Walk up the inheritance chain to find the path from the start model to the target model
*/
export function findUpInheritance(start: DataModel, target: DataModel): DataModel[] | undefined {
for (const base of start.superTypes) {
if (base.ref === target) {
return [base.ref];
}
const path = findUpInheritance(base.ref as DataModel, target);
if (path) {
return [base.ref as DataModel, ...path];
}
}
return undefined;
}
27 changes: 27 additions & 0 deletions tests/regression/tests/issue-1474.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { loadSchema } from '@zenstackhq/testtools';
describe('issue 1474', () => {
it('regression', async () => {
await loadSchema(
`
model A {
id Int @id
cs C[]
}
abstract model B {
a A @relation(fields: [aId], references: [id])
aId Int
}
model C extends B {
id Int @id
type String
@@delegate(type)
}
model D extends C {
}
`
);
});
});

0 comments on commit 810070a

Please sign in to comment.