Skip to content

Commit 19bf371

Browse files
committed
truncate too long delegate aux relation name in runtime
1 parent bf2acfd commit 19bf371

File tree

6 files changed

+62
-33
lines changed

6 files changed

+62
-33
lines changed

packages/runtime/src/constants.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,3 +67,7 @@ export const PRISMA_MINIMUM_VERSION = '5.0.0';
6767
* Prefix for auxiliary relation field generated for delegated models
6868
*/
6969
export const DELEGATE_AUX_RELATION_PREFIX = 'delegate_aux';
70+
71+
// Some database providers like postgres and mysql have default limit to the length of identifiers
72+
// Here we use a conservative value that should work for most cases, and truncate names if needed
73+
export const IDENTIFIER_NAME_MAX_LENGTH = 50 - DELEGATE_AUX_RELATION_PREFIX.length;

packages/runtime/src/cross/utils.ts

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { lowerCaseFirst } from 'lower-case-first';
22
import { requireField, type ModelInfo, type ModelMeta } from '.';
3+
import { IDENTIFIER_NAME_MAX_LENGTH } from '../constants';
34

45
/**
56
* Gets field names in a data model entity, filtering out internal fields.
@@ -75,3 +76,26 @@ export function getModelInfo<Throw extends boolean = false>(
7576
export function isDelegateModel(modelMeta: ModelMeta, model: string) {
7677
return !!getModelInfo(modelMeta, model)?.attributes?.some((attr) => attr.name === '@@delegate');
7778
}
79+
80+
const shortNameMap = new Map<string, string[]>();
81+
export function truncate(name: string) {
82+
if (name.length <= IDENTIFIER_NAME_MAX_LENGTH) {
83+
return name;
84+
}
85+
86+
const shortName = name.slice(0, IDENTIFIER_NAME_MAX_LENGTH);
87+
const entry = shortNameMap.get(shortName);
88+
if (!entry) {
89+
shortNameMap.set(shortName, [name]);
90+
return `${shortName}_0`;
91+
} else {
92+
const index = entry.findIndex((n) => n === name);
93+
if (index >= 0) {
94+
return `${shortName}_${index}`;
95+
} else {
96+
const newIndex = entry.length;
97+
entry.push(name);
98+
return `${shortName}_${newIndex}`;
99+
}
100+
}
101+
}

packages/runtime/src/enhancements/delegate.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import {
1313
getModelInfo,
1414
isDelegateModel,
1515
resolveField,
16+
truncate,
1617
} from '../cross';
1718
import type { CrudContract, DbClientContract } from '../types';
1819
import type { InternalEnhancementOptions } from './create-enhancement';
@@ -1040,7 +1041,7 @@ export class DelegateProxyHandler extends DefaultPrismaProxyHandler {
10401041
}
10411042

10421043
private makeAuxRelationName(model: ModelInfo) {
1043-
return `${DELEGATE_AUX_RELATION_PREFIX}_${lowerCaseFirst(model.name)}`;
1044+
return `${DELEGATE_AUX_RELATION_PREFIX}_${truncate(lowerCaseFirst(model.name))}`;
10441045
}
10451046

10461047
private getModelName(model: string) {

packages/schema/src/plugins/prisma/schema-generator.ts

Lines changed: 6 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ import { getPrismaVersion } from '@zenstackhq/sdk/prisma';
3434
import { match, P } from 'ts-pattern';
3535
import { getIdFields } from '../../utils/ast-utils';
3636

37-
import { DELEGATE_AUX_RELATION_PREFIX, PRISMA_MINIMUM_VERSION } from '@zenstackhq/runtime';
37+
import { DELEGATE_AUX_RELATION_PREFIX, PRISMA_MINIMUM_VERSION, truncate } from '@zenstackhq/runtime';
3838
import {
3939
getAttribute,
4040
getAttributeArg,
@@ -82,10 +82,6 @@ const MODEL_PASSTHROUGH_ATTR = '@@prisma.passthrough';
8282
const FIELD_PASSTHROUGH_ATTR = '@prisma.passthrough';
8383
const PROVIDERS_SUPPORTING_NAMED_CONSTRAINTS = ['postgresql', 'mysql', 'cockroachdb'];
8484

85-
// Some database providers like postgres and mysql have default limit to the length of identifiers
86-
// Here we use a conservative value that should work for most cases, and truncate names if needed
87-
const IDENTIFIER_NAME_MAX_LENGTH = 50 - DELEGATE_AUX_RELATION_PREFIX.length;
88-
8985
/**
9086
* Generates Prisma schema file
9187
*/
@@ -318,7 +314,7 @@ export class PrismaSchemaGenerator {
318314

319315
// generate an optional relation field in delegate base model to each concrete model
320316
concreteModels.forEach((concrete) => {
321-
const auxName = `${DELEGATE_AUX_RELATION_PREFIX}_${this.truncate(lowerCaseFirst(concrete.name))}`;
317+
const auxName = `${DELEGATE_AUX_RELATION_PREFIX}_${truncate(lowerCaseFirst(concrete.name))}`;
322318
model.addField(auxName, new ModelFieldType(concrete.name, false, true));
323319
});
324320
}
@@ -339,7 +335,7 @@ export class PrismaSchemaGenerator {
339335
const idFields = getIdFields(base);
340336

341337
// add relation fields
342-
const relationField = `${DELEGATE_AUX_RELATION_PREFIX}_${this.truncate(lowerCaseFirst(base.name))}`;
338+
const relationField = `${DELEGATE_AUX_RELATION_PREFIX}_${truncate(lowerCaseFirst(base.name))}`;
343339
model.addField(relationField, base.name, [
344340
new PrismaFieldAttribute('@relation', [
345341
new PrismaAttributeArg(
@@ -405,7 +401,7 @@ export class PrismaSchemaGenerator {
405401
// e.g., delegate_aux_User_myAsset_Video
406402
const auxRelationName = `${dataModel.name}_${field.name}_${concrete.name}`;
407403
const auxRelationField = model.addField(
408-
`${DELEGATE_AUX_RELATION_PREFIX}_${this.truncate(auxRelationName)}`,
404+
`${DELEGATE_AUX_RELATION_PREFIX}_${truncate(auxRelationName)}`,
409405
new ModelFieldType(concrete.name, field.type.array, field.type.optional)
410406
);
411407

@@ -487,7 +483,7 @@ export class PrismaSchemaGenerator {
487483

488484
// fix its name
489485
const addedFkFieldName = `${dataModel.name}_${origForeignKey.name}_${concreteModel.name}`;
490-
addedFkField.name = `${DELEGATE_AUX_RELATION_PREFIX}_${this.truncate(addedFkFieldName)}`;
486+
addedFkField.name = `${DELEGATE_AUX_RELATION_PREFIX}_${truncate(addedFkFieldName)}`;
491487

492488
// we also need to make sure `@unique` constraint's `map` parameter is fixed to avoid conflict
493489
const uniqueAttr = addedFkField.attributes.find(
@@ -552,28 +548,6 @@ export class PrismaSchemaGenerator {
552548
}
553549
}
554550

555-
private truncate(name: string) {
556-
if (name.length <= IDENTIFIER_NAME_MAX_LENGTH) {
557-
return name;
558-
}
559-
560-
const shortName = name.slice(0, IDENTIFIER_NAME_MAX_LENGTH);
561-
const entry = this.shortNameMap.get(shortName);
562-
if (!entry) {
563-
this.shortNameMap.set(shortName, [name]);
564-
return `${shortName}_0`;
565-
} else {
566-
const index = entry.findIndex((n) => n === name);
567-
if (index >= 0) {
568-
return `${shortName}_${index}`;
569-
} else {
570-
const newIndex = entry.length;
571-
entry.push(name);
572-
return `${shortName}_${newIndex}`;
573-
}
574-
}
575-
}
576-
577551
private nameRelationsInheritedFromDelegate(model: PrismaDataModel, decl: DataModel) {
578552
if (this.mode !== 'logical') {
579553
return;
@@ -620,7 +594,7 @@ export class PrismaSchemaGenerator {
620594
// relation name format: delegate_aux_[relationType]_[oppositeRelationField]_[concrete]
621595
const relAttr = getAttribute(f, '@relation');
622596
const name = `${fieldType.name}_${oppositeRelationField.name}_${decl.name}`;
623-
const relName = `${DELEGATE_AUX_RELATION_PREFIX}_${this.truncate(name)}`;
597+
const relName = `${DELEGATE_AUX_RELATION_PREFIX}_${truncate(name)}`;
624598

625599
if (relAttr) {
626600
const nameArg = getAttributeArg(relAttr, 'name');

tests/integration/tests/enhancements/with-delegate/enhanced-client.test.ts

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1196,6 +1196,28 @@ describe('Polymorphism Test', () => {
11961196
);
11971197
});
11981198

1199+
it('handles very long property names', async () => {
1200+
const { db } = await setup();
1201+
1202+
await db.videoWithVeryLongProperty.create({
1203+
data: {
1204+
duration: 62,
1205+
url: 'https://whatever.com/example.mp4',
1206+
averyveryveryveryveryveryveryveryveryveryveryverylongProperty: 'whatever',
1207+
},
1208+
});
1209+
1210+
const foundRecords = await db.asset.findMany({});
1211+
1212+
expect(foundRecords).toEqual(
1213+
expect.arrayContaining([
1214+
expect.objectContaining({
1215+
averyveryveryveryveryveryveryveryveryveryveryverylongProperty: 'whatever',
1216+
})
1217+
])
1218+
);
1219+
});
1220+
11991221
it('typescript compilation plain prisma', async () => {
12001222
const src = `
12011223
import { PrismaClient } from '@prisma/client';

tests/integration/tests/enhancements/with-delegate/utils.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,10 @@ model Gallery {
4444
id Int @id @default(autoincrement())
4545
images Image[]
4646
}
47+
48+
model VideoWithVeryLongProperty extends Video {
49+
averyveryveryveryveryveryveryveryveryveryveryverylongProperty String
50+
}
4751
`;
4852

4953
export const POLYMORPHIC_MANY_TO_MANY_SCHEMA = `

0 commit comments

Comments
 (0)