From 19bf371caa1ad9fda261ea4fef21cb4884333e92 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=93=D0=BB=D1=83=D1=85=D0=BE=D0=B2=20=D0=9A=D0=B8=D1=80?= =?UTF-8?q?=D0=B8=D0=BB=D0=BB=20=D0=92=D0=B8=D0=BA=D1=82=D0=BE=D1=80=D0=BE?= =?UTF-8?q?=D0=B2=D0=B8=D1=87?= Date: Thu, 1 Aug 2024 12:41:13 +0500 Subject: [PATCH 1/7] truncate too long delegate aux relation name in runtime --- packages/runtime/src/constants.ts | 4 ++ packages/runtime/src/cross/utils.ts | 24 ++++++++++++ packages/runtime/src/enhancements/delegate.ts | 3 +- .../src/plugins/prisma/schema-generator.ts | 38 +++---------------- .../with-delegate/enhanced-client.test.ts | 22 +++++++++++ .../tests/enhancements/with-delegate/utils.ts | 4 ++ 6 files changed, 62 insertions(+), 33 deletions(-) diff --git a/packages/runtime/src/constants.ts b/packages/runtime/src/constants.ts index 5fd8c2901..4eee90954 100644 --- a/packages/runtime/src/constants.ts +++ b/packages/runtime/src/constants.ts @@ -67,3 +67,7 @@ export const PRISMA_MINIMUM_VERSION = '5.0.0'; * Prefix for auxiliary relation field generated for delegated models */ export const DELEGATE_AUX_RELATION_PREFIX = 'delegate_aux'; + +// Some database providers like postgres and mysql have default limit to the length of identifiers +// Here we use a conservative value that should work for most cases, and truncate names if needed +export const IDENTIFIER_NAME_MAX_LENGTH = 50 - DELEGATE_AUX_RELATION_PREFIX.length; diff --git a/packages/runtime/src/cross/utils.ts b/packages/runtime/src/cross/utils.ts index 304b9b618..18606ae90 100644 --- a/packages/runtime/src/cross/utils.ts +++ b/packages/runtime/src/cross/utils.ts @@ -1,5 +1,6 @@ import { lowerCaseFirst } from 'lower-case-first'; import { requireField, type ModelInfo, type ModelMeta } from '.'; +import { IDENTIFIER_NAME_MAX_LENGTH } from '../constants'; /** * Gets field names in a data model entity, filtering out internal fields. @@ -75,3 +76,26 @@ export function getModelInfo( export function isDelegateModel(modelMeta: ModelMeta, model: string) { return !!getModelInfo(modelMeta, model)?.attributes?.some((attr) => attr.name === '@@delegate'); } + +const shortNameMap = new Map(); +export function truncate(name: string) { + if (name.length <= IDENTIFIER_NAME_MAX_LENGTH) { + return name; + } + + const shortName = name.slice(0, IDENTIFIER_NAME_MAX_LENGTH); + const entry = shortNameMap.get(shortName); + if (!entry) { + shortNameMap.set(shortName, [name]); + return `${shortName}_0`; + } else { + const index = entry.findIndex((n) => n === name); + if (index >= 0) { + return `${shortName}_${index}`; + } else { + const newIndex = entry.length; + entry.push(name); + return `${shortName}_${newIndex}`; + } + } +} diff --git a/packages/runtime/src/enhancements/delegate.ts b/packages/runtime/src/enhancements/delegate.ts index 361d19b4f..ce0b04e2e 100644 --- a/packages/runtime/src/enhancements/delegate.ts +++ b/packages/runtime/src/enhancements/delegate.ts @@ -13,6 +13,7 @@ import { getModelInfo, isDelegateModel, resolveField, + truncate, } from '../cross'; import type { CrudContract, DbClientContract } from '../types'; import type { InternalEnhancementOptions } from './create-enhancement'; @@ -1040,7 +1041,7 @@ export class DelegateProxyHandler extends DefaultPrismaProxyHandler { } private makeAuxRelationName(model: ModelInfo) { - return `${DELEGATE_AUX_RELATION_PREFIX}_${lowerCaseFirst(model.name)}`; + return `${DELEGATE_AUX_RELATION_PREFIX}_${truncate(lowerCaseFirst(model.name))}`; } private getModelName(model: string) { diff --git a/packages/schema/src/plugins/prisma/schema-generator.ts b/packages/schema/src/plugins/prisma/schema-generator.ts index d84b3d0a9..014de6d66 100644 --- a/packages/schema/src/plugins/prisma/schema-generator.ts +++ b/packages/schema/src/plugins/prisma/schema-generator.ts @@ -34,7 +34,7 @@ import { getPrismaVersion } from '@zenstackhq/sdk/prisma'; import { match, P } from 'ts-pattern'; import { getIdFields } from '../../utils/ast-utils'; -import { DELEGATE_AUX_RELATION_PREFIX, PRISMA_MINIMUM_VERSION } from '@zenstackhq/runtime'; +import { DELEGATE_AUX_RELATION_PREFIX, PRISMA_MINIMUM_VERSION, truncate } from '@zenstackhq/runtime'; import { getAttribute, getAttributeArg, @@ -82,10 +82,6 @@ const MODEL_PASSTHROUGH_ATTR = '@@prisma.passthrough'; const FIELD_PASSTHROUGH_ATTR = '@prisma.passthrough'; const PROVIDERS_SUPPORTING_NAMED_CONSTRAINTS = ['postgresql', 'mysql', 'cockroachdb']; -// Some database providers like postgres and mysql have default limit to the length of identifiers -// Here we use a conservative value that should work for most cases, and truncate names if needed -const IDENTIFIER_NAME_MAX_LENGTH = 50 - DELEGATE_AUX_RELATION_PREFIX.length; - /** * Generates Prisma schema file */ @@ -318,7 +314,7 @@ export class PrismaSchemaGenerator { // generate an optional relation field in delegate base model to each concrete model concreteModels.forEach((concrete) => { - const auxName = `${DELEGATE_AUX_RELATION_PREFIX}_${this.truncate(lowerCaseFirst(concrete.name))}`; + const auxName = `${DELEGATE_AUX_RELATION_PREFIX}_${truncate(lowerCaseFirst(concrete.name))}`; model.addField(auxName, new ModelFieldType(concrete.name, false, true)); }); } @@ -339,7 +335,7 @@ export class PrismaSchemaGenerator { const idFields = getIdFields(base); // add relation fields - const relationField = `${DELEGATE_AUX_RELATION_PREFIX}_${this.truncate(lowerCaseFirst(base.name))}`; + const relationField = `${DELEGATE_AUX_RELATION_PREFIX}_${truncate(lowerCaseFirst(base.name))}`; model.addField(relationField, base.name, [ new PrismaFieldAttribute('@relation', [ new PrismaAttributeArg( @@ -405,7 +401,7 @@ export class PrismaSchemaGenerator { // e.g., delegate_aux_User_myAsset_Video const auxRelationName = `${dataModel.name}_${field.name}_${concrete.name}`; const auxRelationField = model.addField( - `${DELEGATE_AUX_RELATION_PREFIX}_${this.truncate(auxRelationName)}`, + `${DELEGATE_AUX_RELATION_PREFIX}_${truncate(auxRelationName)}`, new ModelFieldType(concrete.name, field.type.array, field.type.optional) ); @@ -487,7 +483,7 @@ export class PrismaSchemaGenerator { // fix its name const addedFkFieldName = `${dataModel.name}_${origForeignKey.name}_${concreteModel.name}`; - addedFkField.name = `${DELEGATE_AUX_RELATION_PREFIX}_${this.truncate(addedFkFieldName)}`; + addedFkField.name = `${DELEGATE_AUX_RELATION_PREFIX}_${truncate(addedFkFieldName)}`; // we also need to make sure `@unique` constraint's `map` parameter is fixed to avoid conflict const uniqueAttr = addedFkField.attributes.find( @@ -552,28 +548,6 @@ export class PrismaSchemaGenerator { } } - private truncate(name: string) { - if (name.length <= IDENTIFIER_NAME_MAX_LENGTH) { - return name; - } - - const shortName = name.slice(0, IDENTIFIER_NAME_MAX_LENGTH); - const entry = this.shortNameMap.get(shortName); - if (!entry) { - this.shortNameMap.set(shortName, [name]); - return `${shortName}_0`; - } else { - const index = entry.findIndex((n) => n === name); - if (index >= 0) { - return `${shortName}_${index}`; - } else { - const newIndex = entry.length; - entry.push(name); - return `${shortName}_${newIndex}`; - } - } - } - private nameRelationsInheritedFromDelegate(model: PrismaDataModel, decl: DataModel) { if (this.mode !== 'logical') { return; @@ -620,7 +594,7 @@ export class PrismaSchemaGenerator { // relation name format: delegate_aux_[relationType]_[oppositeRelationField]_[concrete] const relAttr = getAttribute(f, '@relation'); const name = `${fieldType.name}_${oppositeRelationField.name}_${decl.name}`; - const relName = `${DELEGATE_AUX_RELATION_PREFIX}_${this.truncate(name)}`; + const relName = `${DELEGATE_AUX_RELATION_PREFIX}_${truncate(name)}`; if (relAttr) { const nameArg = getAttributeArg(relAttr, 'name'); diff --git a/tests/integration/tests/enhancements/with-delegate/enhanced-client.test.ts b/tests/integration/tests/enhancements/with-delegate/enhanced-client.test.ts index 73b25c1ca..faf2b5620 100644 --- a/tests/integration/tests/enhancements/with-delegate/enhanced-client.test.ts +++ b/tests/integration/tests/enhancements/with-delegate/enhanced-client.test.ts @@ -1196,6 +1196,28 @@ describe('Polymorphism Test', () => { ); }); + it('handles very long property names', async () => { + const { db } = await setup(); + + await db.videoWithVeryLongProperty.create({ + data: { + duration: 62, + url: 'https://whatever.com/example.mp4', + averyveryveryveryveryveryveryveryveryveryveryverylongProperty: 'whatever', + }, + }); + + const foundRecords = await db.asset.findMany({}); + + expect(foundRecords).toEqual( + expect.arrayContaining([ + expect.objectContaining({ + averyveryveryveryveryveryveryveryveryveryveryverylongProperty: 'whatever', + }) + ]) + ); + }); + it('typescript compilation plain prisma', async () => { const src = ` import { PrismaClient } from '@prisma/client'; diff --git a/tests/integration/tests/enhancements/with-delegate/utils.ts b/tests/integration/tests/enhancements/with-delegate/utils.ts index c04106ea2..b442bc1d5 100644 --- a/tests/integration/tests/enhancements/with-delegate/utils.ts +++ b/tests/integration/tests/enhancements/with-delegate/utils.ts @@ -44,6 +44,10 @@ model Gallery { id Int @id @default(autoincrement()) images Image[] } + +model VideoWithVeryLongProperty extends Video { + averyveryveryveryveryveryveryveryveryveryveryverylongProperty String +} `; export const POLYMORPHIC_MANY_TO_MANY_SCHEMA = ` From ff642ae7dad9cb985f3d2174f4117334d41483c6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=93=D0=BB=D1=83=D1=85=D0=BE=D0=B2=20=D0=9A=D0=B8=D1=80?= =?UTF-8?q?=D0=B8=D0=BB=D0=BB=20=D0=92=D0=B8=D0=BA=D1=82=D0=BE=D1=80=D0=BE?= =?UTF-8?q?=D0=B2=D0=B8=D1=87?= Date: Sat, 3 Aug 2024 01:09:53 +0500 Subject: [PATCH 2/7] test rewrited to cover concrete model long name issue --- .../with-delegate/enhanced-client.test.ts | 31 ++++++++++++------- .../tests/enhancements/with-delegate/utils.ts | 4 +-- 2 files changed, 22 insertions(+), 13 deletions(-) diff --git a/tests/integration/tests/enhancements/with-delegate/enhanced-client.test.ts b/tests/integration/tests/enhancements/with-delegate/enhanced-client.test.ts index faf2b5620..f7d18263c 100644 --- a/tests/integration/tests/enhancements/with-delegate/enhanced-client.test.ts +++ b/tests/integration/tests/enhancements/with-delegate/enhanced-client.test.ts @@ -1196,25 +1196,34 @@ describe('Polymorphism Test', () => { ); }); - it('handles very long property names', async () => { - const { db } = await setup(); + it('handles very long concrete model name', async () => { + const { db, user } = await setup(); - await db.videoWithVeryLongProperty.create({ + await db.veryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryLongModelName.create({ data: { + owner: { connect: { id: user.id } }, duration: 62, url: 'https://whatever.com/example.mp4', - averyveryveryveryveryveryveryveryveryveryveryverylongProperty: 'whatever', + veryLongModelNameOwnProperty: 'whatever', }, }); - const foundRecords = await db.asset.findMany({}); + const foundUser = await db.user.findFirst({ + where: { id: user.id }, + include: { + assets: true, + }, + }); - expect(foundRecords).toEqual( - expect.arrayContaining([ - expect.objectContaining({ - averyveryveryveryveryveryveryveryveryveryveryverylongProperty: 'whatever', - }) - ]) + expect(foundUser).toEqual( + expect.objectContaining({ + assets: expect.arrayContaining([ + expect.objectContaining({ + videoType: 'VeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryLongModelName', + veryLongModelNameOwnProperty: 'whatever', + }), + ]), + }), ); }); diff --git a/tests/integration/tests/enhancements/with-delegate/utils.ts b/tests/integration/tests/enhancements/with-delegate/utils.ts index b442bc1d5..6827f990c 100644 --- a/tests/integration/tests/enhancements/with-delegate/utils.ts +++ b/tests/integration/tests/enhancements/with-delegate/utils.ts @@ -45,8 +45,8 @@ model Gallery { images Image[] } -model VideoWithVeryLongProperty extends Video { - averyveryveryveryveryveryveryveryveryveryveryverylongProperty String +model VeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryLongModelName extends Video { + veryLongModelNameOwnProperty String } `; From 420c116048722764f19debc608275605d299e384 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=93=D0=BB=D1=83=D1=85=D0=BE=D0=B2=20=D0=9A=D0=B8=D1=80?= =?UTF-8?q?=D0=B8=D0=BB=D0=BB=20=D0=92=D0=B8=D0=BA=D1=82=D0=BE=D1=80=D0=BE?= =?UTF-8?q?=D0=B2=D0=B8=D1=87?= Date: Sun, 4 Aug 2024 23:26:17 +0500 Subject: [PATCH 3/7] field names truncating removed, added for unique constraint, keeped for relation names --- packages/runtime/src/constants.ts | 4 -- packages/runtime/src/cross/utils.ts | 24 ----------- packages/runtime/src/enhancements/delegate.ts | 3 +- .../src/plugins/prisma/schema-generator.ts | 40 +++++++++++++++---- 4 files changed, 34 insertions(+), 37 deletions(-) diff --git a/packages/runtime/src/constants.ts b/packages/runtime/src/constants.ts index 4eee90954..5fd8c2901 100644 --- a/packages/runtime/src/constants.ts +++ b/packages/runtime/src/constants.ts @@ -67,7 +67,3 @@ export const PRISMA_MINIMUM_VERSION = '5.0.0'; * Prefix for auxiliary relation field generated for delegated models */ export const DELEGATE_AUX_RELATION_PREFIX = 'delegate_aux'; - -// Some database providers like postgres and mysql have default limit to the length of identifiers -// Here we use a conservative value that should work for most cases, and truncate names if needed -export const IDENTIFIER_NAME_MAX_LENGTH = 50 - DELEGATE_AUX_RELATION_PREFIX.length; diff --git a/packages/runtime/src/cross/utils.ts b/packages/runtime/src/cross/utils.ts index 18606ae90..304b9b618 100644 --- a/packages/runtime/src/cross/utils.ts +++ b/packages/runtime/src/cross/utils.ts @@ -1,6 +1,5 @@ import { lowerCaseFirst } from 'lower-case-first'; import { requireField, type ModelInfo, type ModelMeta } from '.'; -import { IDENTIFIER_NAME_MAX_LENGTH } from '../constants'; /** * Gets field names in a data model entity, filtering out internal fields. @@ -76,26 +75,3 @@ export function getModelInfo( export function isDelegateModel(modelMeta: ModelMeta, model: string) { return !!getModelInfo(modelMeta, model)?.attributes?.some((attr) => attr.name === '@@delegate'); } - -const shortNameMap = new Map(); -export function truncate(name: string) { - if (name.length <= IDENTIFIER_NAME_MAX_LENGTH) { - return name; - } - - const shortName = name.slice(0, IDENTIFIER_NAME_MAX_LENGTH); - const entry = shortNameMap.get(shortName); - if (!entry) { - shortNameMap.set(shortName, [name]); - return `${shortName}_0`; - } else { - const index = entry.findIndex((n) => n === name); - if (index >= 0) { - return `${shortName}_${index}`; - } else { - const newIndex = entry.length; - entry.push(name); - return `${shortName}_${newIndex}`; - } - } -} diff --git a/packages/runtime/src/enhancements/delegate.ts b/packages/runtime/src/enhancements/delegate.ts index ce0b04e2e..89008dbf8 100644 --- a/packages/runtime/src/enhancements/delegate.ts +++ b/packages/runtime/src/enhancements/delegate.ts @@ -13,7 +13,6 @@ import { getModelInfo, isDelegateModel, resolveField, - truncate, } from '../cross'; import type { CrudContract, DbClientContract } from '../types'; import type { InternalEnhancementOptions } from './create-enhancement'; @@ -1041,7 +1040,7 @@ export class DelegateProxyHandler extends DefaultPrismaProxyHandler { } private makeAuxRelationName(model: ModelInfo) { - return `${DELEGATE_AUX_RELATION_PREFIX}_${truncate(lowerCaseFirst(model.name))}`; + return `${DELEGATE_AUX_RELATION_PREFIX}_${(lowerCaseFirst(model.name))}`; } private getModelName(model: string) { diff --git a/packages/schema/src/plugins/prisma/schema-generator.ts b/packages/schema/src/plugins/prisma/schema-generator.ts index 014de6d66..5a038f9df 100644 --- a/packages/schema/src/plugins/prisma/schema-generator.ts +++ b/packages/schema/src/plugins/prisma/schema-generator.ts @@ -34,7 +34,7 @@ import { getPrismaVersion } from '@zenstackhq/sdk/prisma'; import { match, P } from 'ts-pattern'; import { getIdFields } from '../../utils/ast-utils'; -import { DELEGATE_AUX_RELATION_PREFIX, PRISMA_MINIMUM_VERSION, truncate } from '@zenstackhq/runtime'; +import { DELEGATE_AUX_RELATION_PREFIX, PRISMA_MINIMUM_VERSION } from '@zenstackhq/runtime'; import { getAttribute, getAttributeArg, @@ -82,6 +82,10 @@ const MODEL_PASSTHROUGH_ATTR = '@@prisma.passthrough'; const FIELD_PASSTHROUGH_ATTR = '@prisma.passthrough'; const PROVIDERS_SUPPORTING_NAMED_CONSTRAINTS = ['postgresql', 'mysql', 'cockroachdb']; +// Some database providers like postgres and mysql have default limit to the length of identifiers +// Here we use a conservative value that should work for most cases, and truncate names if needed +export const IDENTIFIER_NAME_MAX_LENGTH = 50 - DELEGATE_AUX_RELATION_PREFIX.length; + /** * Generates Prisma schema file */ @@ -314,7 +318,7 @@ export class PrismaSchemaGenerator { // generate an optional relation field in delegate base model to each concrete model concreteModels.forEach((concrete) => { - const auxName = `${DELEGATE_AUX_RELATION_PREFIX}_${truncate(lowerCaseFirst(concrete.name))}`; + const auxName = `${DELEGATE_AUX_RELATION_PREFIX}_${(lowerCaseFirst(concrete.name))}`; model.addField(auxName, new ModelFieldType(concrete.name, false, true)); }); } @@ -335,7 +339,7 @@ export class PrismaSchemaGenerator { const idFields = getIdFields(base); // add relation fields - const relationField = `${DELEGATE_AUX_RELATION_PREFIX}_${truncate(lowerCaseFirst(base.name))}`; + const relationField = `${DELEGATE_AUX_RELATION_PREFIX}_${(lowerCaseFirst(base.name))}`; model.addField(relationField, base.name, [ new PrismaFieldAttribute('@relation', [ new PrismaAttributeArg( @@ -401,7 +405,7 @@ export class PrismaSchemaGenerator { // e.g., delegate_aux_User_myAsset_Video const auxRelationName = `${dataModel.name}_${field.name}_${concrete.name}`; const auxRelationField = model.addField( - `${DELEGATE_AUX_RELATION_PREFIX}_${truncate(auxRelationName)}`, + `${DELEGATE_AUX_RELATION_PREFIX}_${this.truncate(auxRelationName)}`, new ModelFieldType(concrete.name, field.type.array, field.type.optional) ); @@ -483,7 +487,7 @@ export class PrismaSchemaGenerator { // fix its name const addedFkFieldName = `${dataModel.name}_${origForeignKey.name}_${concreteModel.name}`; - addedFkField.name = `${DELEGATE_AUX_RELATION_PREFIX}_${truncate(addedFkFieldName)}`; + addedFkField.name = `${DELEGATE_AUX_RELATION_PREFIX}_${(addedFkFieldName)}`; // we also need to make sure `@unique` constraint's `map` parameter is fixed to avoid conflict const uniqueAttr = addedFkField.attributes.find( @@ -491,7 +495,7 @@ export class PrismaSchemaGenerator { ) as PrismaFieldAttribute; if (uniqueAttr) { const mapArg = uniqueAttr.args.find((arg) => arg.name === 'map'); - const constraintName = `${addedFkField.name}_unique`; + const constraintName = `${this.truncate(addedFkField.name)}_unique`; if (mapArg) { mapArg.value = new AttributeArgValue('String', constraintName); } else { @@ -548,6 +552,28 @@ export class PrismaSchemaGenerator { } } + private truncate(name: string) { + if (name.length <= IDENTIFIER_NAME_MAX_LENGTH) { + return name; + } + + const shortName = name.slice(0, IDENTIFIER_NAME_MAX_LENGTH); + const entry = this.shortNameMap.get(shortName); + if (!entry) { + this.shortNameMap.set(shortName, [name]); + return `${shortName}_0`; + } else { + const index = entry.findIndex((n) => n === name); + if (index >= 0) { + return `${shortName}_${index}`; + } else { + const newIndex = entry.length; + entry.push(name); + return `${shortName}_${newIndex}`; + } + } + } + private nameRelationsInheritedFromDelegate(model: PrismaDataModel, decl: DataModel) { if (this.mode !== 'logical') { return; @@ -594,7 +620,7 @@ export class PrismaSchemaGenerator { // relation name format: delegate_aux_[relationType]_[oppositeRelationField]_[concrete] const relAttr = getAttribute(f, '@relation'); const name = `${fieldType.name}_${oppositeRelationField.name}_${decl.name}`; - const relName = `${DELEGATE_AUX_RELATION_PREFIX}_${truncate(name)}`; + const relName = `${DELEGATE_AUX_RELATION_PREFIX}_${this.truncate(name)}`; if (relAttr) { const nameArg = getAttributeArg(relAttr, 'name'); From c3095d1efa5f455756f76e7e76403d78df6a0a28 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=93=D0=BB=D1=83=D1=85=D0=BE=D0=B2=20=D0=9A=D0=B8=D1=80?= =?UTF-8?q?=D0=B8=D0=BB=D0=BB=20=D0=92=D0=B8=D0=BA=D1=82=D0=BE=D1=80=D0=BE?= =?UTF-8?q?=D0=B2=D0=B8=D1=87?= Date: Sun, 4 Aug 2024 23:30:32 +0500 Subject: [PATCH 4/7] diff cleanup --- packages/schema/src/plugins/prisma/schema-generator.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/schema/src/plugins/prisma/schema-generator.ts b/packages/schema/src/plugins/prisma/schema-generator.ts index 5a038f9df..1d909f7a7 100644 --- a/packages/schema/src/plugins/prisma/schema-generator.ts +++ b/packages/schema/src/plugins/prisma/schema-generator.ts @@ -84,7 +84,7 @@ const PROVIDERS_SUPPORTING_NAMED_CONSTRAINTS = ['postgresql', 'mysql', 'cockroac // Some database providers like postgres and mysql have default limit to the length of identifiers // Here we use a conservative value that should work for most cases, and truncate names if needed -export const IDENTIFIER_NAME_MAX_LENGTH = 50 - DELEGATE_AUX_RELATION_PREFIX.length; +const IDENTIFIER_NAME_MAX_LENGTH = 50 - DELEGATE_AUX_RELATION_PREFIX.length; /** * Generates Prisma schema file @@ -556,7 +556,7 @@ export class PrismaSchemaGenerator { if (name.length <= IDENTIFIER_NAME_MAX_LENGTH) { return name; } - + const shortName = name.slice(0, IDENTIFIER_NAME_MAX_LENGTH); const entry = this.shortNameMap.get(shortName); if (!entry) { @@ -573,7 +573,7 @@ export class PrismaSchemaGenerator { } } } - + private nameRelationsInheritedFromDelegate(model: PrismaDataModel, decl: DataModel) { if (this.mode !== 'logical') { return; From 84617eca127eddeacea69604fc3bdb1a5cea2eab Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=93=D0=BB=D1=83=D1=85=D0=BE=D0=B2=20=D0=9A=D0=B8=D1=80?= =?UTF-8?q?=D0=B8=D0=BB=D0=BB=20=D0=92=D0=B8=D0=BA=D1=82=D0=BE=D1=80=D0=BE?= =?UTF-8?q?=D0=B2=D0=B8=D1=87?= Date: Sun, 4 Aug 2024 23:32:06 +0500 Subject: [PATCH 5/7] diff cleanup --- packages/runtime/src/enhancements/delegate.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/runtime/src/enhancements/delegate.ts b/packages/runtime/src/enhancements/delegate.ts index 89008dbf8..361d19b4f 100644 --- a/packages/runtime/src/enhancements/delegate.ts +++ b/packages/runtime/src/enhancements/delegate.ts @@ -1040,7 +1040,7 @@ export class DelegateProxyHandler extends DefaultPrismaProxyHandler { } private makeAuxRelationName(model: ModelInfo) { - return `${DELEGATE_AUX_RELATION_PREFIX}_${(lowerCaseFirst(model.name))}`; + return `${DELEGATE_AUX_RELATION_PREFIX}_${lowerCaseFirst(model.name)}`; } private getModelName(model: string) { From f2e4efd07b6767c45e081a2c0d3527118fd0166b Mon Sep 17 00:00:00 2001 From: ymc9 <104139426+ymc9@users.noreply.github.com> Date: Mon, 5 Aug 2024 14:39:00 -0700 Subject: [PATCH 6/7] fix: record the fullname-shortname map in model meta and carry it over to runtime - Changed the direction of name mapping to "full -> short" for easier consumption - Saved the map into model meta - Use the map at runtime to get shortened names --- packages/runtime/src/cross/model-meta.ts | 5 ++ packages/runtime/src/enhancements/delegate.ts | 5 +- packages/schema/src/cli/plugin-runner.ts | 19 +++++- .../src/plugins/enhancer/model-meta/index.ts | 1 + packages/schema/src/plugins/prisma/index.ts | 9 +-- .../src/plugins/prisma/schema-generator.ts | 61 +++++++++++-------- packages/sdk/src/model-meta-generator.ts | 19 ++++++ packages/sdk/src/types.ts | 12 ++++ .../with-delegate/enhanced-client.test.ts | 23 +++++-- .../tests/enhancements/with-delegate/utils.ts | 8 ++- 10 files changed, 123 insertions(+), 39 deletions(-) diff --git a/packages/runtime/src/cross/model-meta.ts b/packages/runtime/src/cross/model-meta.ts index a90b20685..3eb6b3786 100644 --- a/packages/runtime/src/cross/model-meta.ts +++ b/packages/runtime/src/cross/model-meta.ts @@ -161,6 +161,11 @@ export type ModelMeta = { * Name of model that backs the `auth()` function */ authModel?: string; + + /** + * Optional map from full names to shortened names, used for extra fields/relations generated by ZenStack + */ + shortNameMap?: Record; }; /** diff --git a/packages/runtime/src/enhancements/delegate.ts b/packages/runtime/src/enhancements/delegate.ts index 361d19b4f..e82d24d72 100644 --- a/packages/runtime/src/enhancements/delegate.ts +++ b/packages/runtime/src/enhancements/delegate.ts @@ -1040,7 +1040,10 @@ export class DelegateProxyHandler extends DefaultPrismaProxyHandler { } private makeAuxRelationName(model: ModelInfo) { - return `${DELEGATE_AUX_RELATION_PREFIX}_${lowerCaseFirst(model.name)}`; + const name = `${DELEGATE_AUX_RELATION_PREFIX}_${lowerCaseFirst(model.name)}`; + // make sure we look up into short name map to see if it's truncated + const shortName = this.options.modelMeta.shortNameMap?.[name]; + return shortName ?? name; } private getModelName(model: string) { diff --git a/packages/schema/src/cli/plugin-runner.ts b/packages/schema/src/cli/plugin-runner.ts index f66b57295..abf47bf6e 100644 --- a/packages/schema/src/cli/plugin-runner.ts +++ b/packages/schema/src/cli/plugin-runner.ts @@ -14,6 +14,7 @@ import { type OptionValue, type PluginDeclaredOptions, type PluginFunction, + type PluginOptions, type PluginResult, } from '@zenstackhq/sdk'; import { type DMMF } from '@zenstackhq/sdk/prisma'; @@ -131,11 +132,12 @@ export class PluginRunner { // run core plugins first let dmmf: DMMF.Document | undefined = undefined; + let shortNameMap: Map | undefined; let prismaClientPath = '@prisma/client'; const project = createProject(); for (const { name, description, run, options: pluginOptions } of corePlugins) { const options = { ...pluginOptions, prismaClientPath }; - const r = await this.runPlugin(name, description, run, runnerOptions, options, dmmf, project); + const r = await this.runPlugin(name, description, run, runnerOptions, options, dmmf, shortNameMap, project); warnings.push(...(r?.warnings ?? [])); // the null-check is for backward compatibility if (r.dmmf) { @@ -143,6 +145,11 @@ export class PluginRunner { dmmf = r.dmmf; } + if (r.shortNameMap) { + // use the model short name map returned by the plugin + shortNameMap = r.shortNameMap; + } + if (r.prismaClientPath) { // use the prisma client path returned by the plugin prismaClientPath = r.prismaClientPath; @@ -155,7 +162,7 @@ export class PluginRunner { // run user plugins for (const { name, description, run, options: pluginOptions } of userPlugins) { const options = { ...pluginOptions, prismaClientPath }; - const r = await this.runPlugin(name, description, run, runnerOptions, options, dmmf, project); + const r = await this.runPlugin(name, description, run, runnerOptions, options, dmmf, shortNameMap, project); warnings.push(...(r?.warnings ?? [])); // the null-check is for backward compatibility } @@ -308,6 +315,7 @@ export class PluginRunner { runnerOptions: PluginRunnerOptions, options: PluginDeclaredOptions, dmmf: DMMF.Document | undefined, + shortNameMap: Map | undefined, project: Project ) { const title = description ?? `Running plugin ${colors.cyan(name)}`; @@ -322,7 +330,12 @@ export class PluginRunner { options, }, async () => { - return await run(runnerOptions.schema, { ...options, schemaPath: runnerOptions.schemaPath }, dmmf, { + const finalOptions = { + ...options, + schemaPath: runnerOptions.schemaPath, + shortNameMap, + } as PluginOptions; + return await run(runnerOptions.schema, finalOptions, dmmf, { output: runnerOptions.output, compile: runnerOptions.compile, tsProject: project, diff --git a/packages/schema/src/plugins/enhancer/model-meta/index.ts b/packages/schema/src/plugins/enhancer/model-meta/index.ts index 9939ae346..38757ae6f 100644 --- a/packages/schema/src/plugins/enhancer/model-meta/index.ts +++ b/packages/schema/src/plugins/enhancer/model-meta/index.ts @@ -13,5 +13,6 @@ export async function generate(model: Model, options: PluginOptions, project: Pr output: outFile, generateAttributes: true, preserveTsFiles, + shortNameMap: options.shortNameMap, }); } diff --git a/packages/schema/src/plugins/prisma/index.ts b/packages/schema/src/plugins/prisma/index.ts index aeab332c3..d3a9c120a 100644 --- a/packages/schema/src/plugins/prisma/index.ts +++ b/packages/schema/src/plugins/prisma/index.ts @@ -1,6 +1,7 @@ -import { PluginError, PluginFunction, getLiteral, resolvePath } from '@zenstackhq/sdk'; +import { PluginError, type PluginFunction, type PluginOptions, getLiteral, resolvePath } from '@zenstackhq/sdk'; import { GeneratorDecl, isGeneratorDecl } from '@zenstackhq/sdk/ast'; import { getDMMF } from '@zenstackhq/sdk/prisma'; +import colors from 'colors'; import fs from 'fs'; import path from 'path'; import stripColor from 'strip-color'; @@ -8,7 +9,6 @@ import telemetry from '../../telemetry'; import { execPackage } from '../../utils/exec-utils'; import { findUp } from '../../utils/pkg-utils'; import { PrismaSchemaGenerator } from './schema-generator'; -import colors from 'colors'; export const name = 'Prisma'; export const description = 'Generating Prisma schema'; @@ -19,7 +19,8 @@ const run: PluginFunction = async (model, options, _dmmf, _globalOptions) => { ? resolvePath(options.output as string, options) : getDefaultPrismaOutputFile(options.schemaPath); - const warnings = await new PrismaSchemaGenerator(model).generate({ ...options, output }); + const mergedOptions = { ...options, output } as unknown as PluginOptions; + const { warnings, shortNameMap } = await new PrismaSchemaGenerator(model).generate(mergedOptions); let prismaClientPath = '@prisma/client'; if (options.generateClient !== false) { @@ -74,7 +75,7 @@ const run: PluginFunction = async (model, options, _dmmf, _globalOptions) => { datamodel: fs.readFileSync(output, 'utf-8'), }); - return { warnings, dmmf, prismaClientPath }; + return { warnings, dmmf, prismaClientPath, shortNameMap }; }; function getDefaultPrismaOutputFile(schemaPath: string) { diff --git a/packages/schema/src/plugins/prisma/schema-generator.ts b/packages/schema/src/plugins/prisma/schema-generator.ts index 1d909f7a7..35007e119 100644 --- a/packages/schema/src/plugins/prisma/schema-generator.ts +++ b/packages/schema/src/plugins/prisma/schema-generator.ts @@ -101,8 +101,8 @@ export class PrismaSchemaGenerator { private mode: 'logical' | 'physical' = 'physical'; - // a mapping from shortened names to their original full names - private shortNameMap = new Map(); + // a mapping from full names to shortened names + private shortNameMap = new Map(); constructor(private readonly zmodel: Model) {} @@ -160,7 +160,7 @@ export class PrismaSchemaGenerator { } } - return warnings; + return { warnings, shortNameMap: this.shortNameMap }; } private generateDataSource(prisma: PrismaModel, dataSource: DataSource) { @@ -318,7 +318,7 @@ export class PrismaSchemaGenerator { // generate an optional relation field in delegate base model to each concrete model concreteModels.forEach((concrete) => { - const auxName = `${DELEGATE_AUX_RELATION_PREFIX}_${(lowerCaseFirst(concrete.name))}`; + const auxName = this.truncate(`${DELEGATE_AUX_RELATION_PREFIX}_${lowerCaseFirst(concrete.name)}`); model.addField(auxName, new ModelFieldType(concrete.name, false, true)); }); } @@ -339,7 +339,7 @@ export class PrismaSchemaGenerator { const idFields = getIdFields(base); // add relation fields - const relationField = `${DELEGATE_AUX_RELATION_PREFIX}_${(lowerCaseFirst(base.name))}`; + const relationField = this.truncate(`${DELEGATE_AUX_RELATION_PREFIX}_${lowerCaseFirst(base.name)}`); model.addField(relationField, base.name, [ new PrismaFieldAttribute('@relation', [ new PrismaAttributeArg( @@ -403,9 +403,11 @@ export class PrismaSchemaGenerator { concreteModels.forEach((concrete) => { // aux relation name format: delegate_aux_[model]_[relationField]_[concrete] // e.g., delegate_aux_User_myAsset_Video - const auxRelationName = `${dataModel.name}_${field.name}_${concrete.name}`; + const auxRelationName = this.truncate( + `${DELEGATE_AUX_RELATION_PREFIX}_${dataModel.name}_${field.name}_${concrete.name}` + ); const auxRelationField = model.addField( - `${DELEGATE_AUX_RELATION_PREFIX}_${this.truncate(auxRelationName)}`, + auxRelationName, new ModelFieldType(concrete.name, field.type.array, field.type.optional) ); @@ -442,7 +444,10 @@ export class PrismaSchemaGenerator { const addedRel = new PrismaFieldAttribute('@relation', [ // use field name as relation name for disambiguation - new PrismaAttributeArg(undefined, new AttributeArgValue('String', nameArg?.value || auxRelationField.name)), + new PrismaAttributeArg( + undefined, + new AttributeArgValue('String', nameArg?.value || auxRelationField.name) + ), new PrismaAttributeArg('fields', fieldsArg), new PrismaAttributeArg('references', referencesArg), ]); @@ -487,7 +492,7 @@ export class PrismaSchemaGenerator { // fix its name const addedFkFieldName = `${dataModel.name}_${origForeignKey.name}_${concreteModel.name}`; - addedFkField.name = `${DELEGATE_AUX_RELATION_PREFIX}_${(addedFkFieldName)}`; + addedFkField.name = this.truncate(`${DELEGATE_AUX_RELATION_PREFIX}_${addedFkFieldName}`); // we also need to make sure `@unique` constraint's `map` parameter is fixed to avoid conflict const uniqueAttr = addedFkField.attributes.find( @@ -495,7 +500,7 @@ export class PrismaSchemaGenerator { ) as PrismaFieldAttribute; if (uniqueAttr) { const mapArg = uniqueAttr.args.find((arg) => arg.name === 'map'); - const constraintName = `${this.truncate(addedFkField.name)}_unique`; + const constraintName = this.truncate(`${addedFkField.name}_unique`); if (mapArg) { mapArg.value = new AttributeArgValue('String', constraintName); } else { @@ -557,21 +562,29 @@ export class PrismaSchemaGenerator { return name; } - const shortName = name.slice(0, IDENTIFIER_NAME_MAX_LENGTH); - const entry = this.shortNameMap.get(shortName); - if (!entry) { - this.shortNameMap.set(shortName, [name]); - return `${shortName}_0`; - } else { - const index = entry.findIndex((n) => n === name); - if (index >= 0) { - return `${shortName}_${index}`; - } else { - const newIndex = entry.length; - entry.push(name); - return `${shortName}_${newIndex}`; + const existing = this.shortNameMap.get(name); + if (existing) { + return existing; + } + + const baseName = name.slice(0, IDENTIFIER_NAME_MAX_LENGTH); + let index = 0; + let shortName = `${baseName}_${index}`; + + // eslint-disable-next-line no-constant-condition + while (true) { + const conflict = Array.from(this.shortNameMap.values()).find((v) => v === shortName); + if (!conflict) { + this.shortNameMap.set(name, shortName); + break; } + + // try next index + index++; + shortName = `${baseName}_${index}`; } + + return shortName; } private nameRelationsInheritedFromDelegate(model: PrismaDataModel, decl: DataModel) { @@ -620,7 +633,7 @@ export class PrismaSchemaGenerator { // relation name format: delegate_aux_[relationType]_[oppositeRelationField]_[concrete] const relAttr = getAttribute(f, '@relation'); const name = `${fieldType.name}_${oppositeRelationField.name}_${decl.name}`; - const relName = `${DELEGATE_AUX_RELATION_PREFIX}_${this.truncate(name)}`; + const relName = this.truncate(`${DELEGATE_AUX_RELATION_PREFIX}_${name}`); if (relAttr) { const nameArg = getAttributeArg(relAttr, 'name'); diff --git a/packages/sdk/src/model-meta-generator.ts b/packages/sdk/src/model-meta-generator.ts index 3529c6ec8..90834e4e4 100644 --- a/packages/sdk/src/model-meta-generator.ts +++ b/packages/sdk/src/model-meta-generator.ts @@ -54,6 +54,11 @@ export type ModelMetaGeneratorOptions = { * Whether to preserve the pre-compilation TypeScript files */ preserveTsFiles?: boolean; + + /** + * Map from full names to shortened names, used for extra fields/relations generated by ZenStack + */ + shortNameMap?: Map; }; export async function generate(project: Project, models: DataModel[], options: ModelMetaGeneratorOptions) { @@ -84,6 +89,7 @@ function generateModelMetadata( writeModels(sourceFile, writer, dataModels, options); writeDeleteCascade(writer, dataModels); writeAuthModel(writer, dataModels); + writeShortNameMap(options, writer); }); } @@ -126,6 +132,7 @@ function writeAuthModel(writer: CodeBlockWriter, dataModels: DataModel[]) { if (authModel) { writer.writeLine(`authModel: '${authModel.name}'`); } + writer.writeLine(','); } function writeDeleteCascade(writer: CodeBlockWriter, dataModels: DataModel[]) { @@ -529,3 +536,15 @@ function isAutoIncrement(field: DataModelField) { return isInvocationExpr(arg) && arg.function.$refText === 'autoincrement'; } + +function writeShortNameMap(options: ModelMetaGeneratorOptions, writer: CodeBlockWriter) { + if (options.shortNameMap) { + writer.write('shortNameMap:'); + writer.block(() => { + for (const [key, value] of options.shortNameMap!) { + writer.write(`${key}: '${value}',`); + } + }); + writer.write(','); + } +} diff --git a/packages/sdk/src/types.ts b/packages/sdk/src/types.ts index a6a4b8629..92c099717 100644 --- a/packages/sdk/src/types.ts +++ b/packages/sdk/src/types.ts @@ -30,6 +30,12 @@ export type PluginOptions = { * PrismaClient import path, either relative to `schemaPath` or absolute */ prismaClientPath?: string; + + /** + * An optional map of full names to shortened names + * @private + */ + shortNameMap?: Map; } & PluginDeclaredOptions; /** @@ -73,6 +79,12 @@ export type PluginResult = { * @private */ dmmf?: DMMF.Document; + + /** + * An optional map of full names to shortened names + * @private + */ + shortNameMap?: Map; }; /** diff --git a/tests/integration/tests/enhancements/with-delegate/enhanced-client.test.ts b/tests/integration/tests/enhancements/with-delegate/enhanced-client.test.ts index f7d18263c..4d71f26dc 100644 --- a/tests/integration/tests/enhancements/with-delegate/enhanced-client.test.ts +++ b/tests/integration/tests/enhancements/with-delegate/enhanced-client.test.ts @@ -1199,12 +1199,21 @@ describe('Polymorphism Test', () => { it('handles very long concrete model name', async () => { const { db, user } = await setup(); - await db.veryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryLongModelName.create({ + await db.veryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryLongModelNameA.create({ data: { owner: { connect: { id: user.id } }, duration: 62, url: 'https://whatever.com/example.mp4', - veryLongModelNameOwnProperty: 'whatever', + propA: 'propA', + }, + }); + + await db.veryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryLongModelNameB.create({ + data: { + owner: { connect: { id: user.id } }, + duration: 62, + url: 'https://whatever.com/example.mp4', + propB: 'propB', }, }); @@ -1219,11 +1228,15 @@ describe('Polymorphism Test', () => { expect.objectContaining({ assets: expect.arrayContaining([ expect.objectContaining({ - videoType: 'VeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryLongModelName', - veryLongModelNameOwnProperty: 'whatever', + videoType: 'VeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryLongModelNameA', + propA: 'propA', + }), + expect.objectContaining({ + videoType: 'VeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryLongModelNameB', + propB: 'propB', }), ]), - }), + }) ); }); diff --git a/tests/integration/tests/enhancements/with-delegate/utils.ts b/tests/integration/tests/enhancements/with-delegate/utils.ts index 6827f990c..66f29b221 100644 --- a/tests/integration/tests/enhancements/with-delegate/utils.ts +++ b/tests/integration/tests/enhancements/with-delegate/utils.ts @@ -45,8 +45,12 @@ model Gallery { images Image[] } -model VeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryLongModelName extends Video { - veryLongModelNameOwnProperty String +model VeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryLongModelNameA extends Video { + propA String +} + +model VeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryLongModelNameB extends Video { + propB String } `; From 457a3c3b8f202596e42b2524b9c90154b0c16e10 Mon Sep 17 00:00:00 2001 From: ymc9 <104139426+ymc9@users.noreply.github.com> Date: Mon, 5 Aug 2024 14:51:41 -0700 Subject: [PATCH 7/7] fix incorrect model meta generation --- packages/sdk/src/model-meta-generator.ts | 4 ++-- tests/regression/tests/issue-1466.test.ts | 18 +++++++++--------- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/packages/sdk/src/model-meta-generator.ts b/packages/sdk/src/model-meta-generator.ts index 90834e4e4..c0b266a21 100644 --- a/packages/sdk/src/model-meta-generator.ts +++ b/packages/sdk/src/model-meta-generator.ts @@ -131,8 +131,8 @@ function writeAuthModel(writer: CodeBlockWriter, dataModels: DataModel[]) { const authModel = getAuthModel(dataModels); if (authModel) { writer.writeLine(`authModel: '${authModel.name}'`); + writer.writeLine(','); } - writer.writeLine(','); } function writeDeleteCascade(writer: CodeBlockWriter, dataModels: DataModel[]) { @@ -538,7 +538,7 @@ function isAutoIncrement(field: DataModelField) { } function writeShortNameMap(options: ModelMetaGeneratorOptions, writer: CodeBlockWriter) { - if (options.shortNameMap) { + if (options.shortNameMap && options.shortNameMap.size > 0) { writer.write('shortNameMap:'); writer.block(() => { for (const [key, value] of options.shortNameMap!) { diff --git a/tests/regression/tests/issue-1466.test.ts b/tests/regression/tests/issue-1466.test.ts index 3ad17143f..f2526c85b 100644 --- a/tests/regression/tests/issue-1466.test.ts +++ b/tests/regression/tests/issue-1466.test.ts @@ -8,24 +8,24 @@ describe('issue 1466', () => { try { const r = await loadSchema( ` - model UserLongLongLongLongName { + model UserLongLongLongLongLongLongLongLongName { id Int @id @default(autoincrement()) level Int @default(0) - asset AssetLongLongLongLongName @relation(fields: [assetId], references: [id]) + asset AssetLongLongLongLongLongLongLongLongName @relation(fields: [assetId], references: [id]) assetId Int @unique } - model AssetLongLongLongLongName { + model AssetLongLongLongLongLongLongLongLongName { id Int @id @default(autoincrement()) createdAt DateTime @default(now()) viewCount Int @default(0) - owner UserLongLongLongLongName? + owner UserLongLongLongLongLongLongLongLongName? assetType String @@delegate(assetType) } - model VideoLongLongLongLongName extends AssetLongLongLongLongName { + model VideoLongLongLongLongLongLongLongLongName extends AssetLongLongLongLongLongLongLongLongName { duration Int } `, @@ -39,22 +39,22 @@ describe('issue 1466', () => { prisma = r.prisma; const db = r.enhance(); - const video = await db.VideoLongLongLongLongName.create({ + const video = await db.VideoLongLongLongLongLongLongLongLongName.create({ data: { duration: 100 }, }); - const user = await db.UserLongLongLongLongName.create({ + const user = await db.UserLongLongLongLongLongLongLongLongName.create({ data: { asset: { connect: { id: video.id } }, }, }); - const userWithAsset = await db.UserLongLongLongLongName.findFirst({ + const userWithAsset = await db.UserLongLongLongLongLongLongLongLongName.findFirst({ include: { asset: true }, }); expect(userWithAsset).toMatchObject({ - asset: { assetType: 'VideoLongLongLongLongName', duration: 100 }, + asset: { assetType: 'VideoLongLongLongLongLongLongLongLongName', duration: 100 }, }); } finally { if (prisma) {