Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(delegate): generated logical prisma schema has errors when abstra… #1490

Merged
merged 1 commit into from
Jun 7, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 {
}
`
);
});
});
Loading