refactor: stop reading joinColumnName from relation field settings (#20304)
## Summary `joinColumnName` on relation field settings is always derivable from the field name (and the target object name for morph relations). This PR stops reading it from settings anywhere in production code; the stored value is no longer used. The settings field is **not** removed from data yet — a follow-up can drop it once we are confident nothing depends on the stored value. ## Helpers The helpers are split by layer because frontend and backend hold morph relations differently: the frontend has a base name plus a `morphRelations[]` array, the backend has one row per target with the name already morph-resolved. | Helper | Layer | When to use | |---|---|---| | `computeRelationGqlFieldJoinColumnName` | Shared / frontend (`gqlField`) | Non-morph relation on the frontend. | | `computeMorphRelationGqlFieldName` | Shared / frontend (`gqlField`) | Need the per-target morph gqlField name (e.g. `targetCompany`). | | `computeMorphRelationGqlFieldJoinColumnName` | Shared / frontend (`gqlField`) | Per-target morph join column on the frontend. Prefer over the non-morph helper for any morph field — it forces the per-target inputs. | | `computeMorphOrRelationFieldJoinColumnName` | Backend (`FlatFieldMetadata.name`) | Any backend read or write — the flat name is already morph-resolved, so one helper covers both cases. | | `computeMorphRelationFlatFieldName` | Backend (`FlatFieldMetadata.name`) | **Mutation paths only** (create / update / object rename). Reads consume the stored `field.name` and never call this. | ## Test plan - [x] Typecheck and lint (front, server, shared) - [x] Existing unit tests pass - [ ] CI green
This commit is contained in:
+12
-7
@@ -1,5 +1,6 @@
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
import { RelationType } from 'twenty-shared/types';
|
||||
|
||||
import { computeMorphOrRelationFieldJoinColumnName } from 'src/engine/metadata-modules/field-metadata/utils/compute-morph-or-relation-field-join-column-name.util';
|
||||
import { findManyFlatEntityByUniversalIdentifierInUniversalFlatEntityMapsOrThrow } from 'src/engine/metadata-modules/flat-entity/utils/find-many-flat-entity-by-universal-identifier-in-universal-flat-entity-maps-or-throw.util';
|
||||
import { isMorphOrRelationUniversalFlatFieldMetadata } from 'src/engine/metadata-modules/flat-field-metadata/utils/is-morph-or-relation-flat-field-metadata.util';
|
||||
import { type AllUniversalFlatEntityMaps } from 'src/engine/workspace-manager/workspace-migration/universal-flat-entity/types/all-universal-flat-entity-maps.type';
|
||||
@@ -24,12 +25,16 @@ export const buildUniversalFlatObjectFieldByNameAndJoinColumnMaps = ({
|
||||
for (const field of objectFields) {
|
||||
fieldUniversalIdentifierByName[field.name] = field.universalIdentifier;
|
||||
|
||||
if (isMorphOrRelationUniversalFlatFieldMetadata(field)) {
|
||||
if (isDefined(field.universalSettings.joinColumnName)) {
|
||||
fieldUniversalIdentifierByJoinColumnName[
|
||||
field.universalSettings.joinColumnName
|
||||
] = field.universalIdentifier;
|
||||
}
|
||||
if (
|
||||
isMorphOrRelationUniversalFlatFieldMetadata(field) &&
|
||||
field.universalSettings.relationType === RelationType.MANY_TO_ONE
|
||||
) {
|
||||
const joinColumnName = computeMorphOrRelationFieldJoinColumnName({
|
||||
name: field.name,
|
||||
});
|
||||
|
||||
fieldUniversalIdentifierByJoinColumnName[joinColumnName] =
|
||||
field.universalIdentifier;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+4
-7
@@ -5,6 +5,7 @@ import { isDefined } from 'twenty-shared/utils';
|
||||
import { type QueryRunner } from 'typeorm';
|
||||
import { v4 } from 'uuid';
|
||||
|
||||
import { computeMorphOrRelationFieldJoinColumnName } from 'src/engine/metadata-modules/field-metadata/utils/compute-morph-or-relation-field-join-column-name.util';
|
||||
import { WorkspaceMigrationRunnerActionHandler } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-runner/interfaces/workspace-migration-runner-action-handler-service.interface';
|
||||
|
||||
import { type MetadataFlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/types/metadata-flat-entity-maps.type';
|
||||
@@ -214,13 +215,9 @@ export class CreateFieldActionHandlerService extends WorkspaceMigrationRunnerAct
|
||||
targetFlatObjectMetadata,
|
||||
);
|
||||
|
||||
const joinColumnName = flatFieldMetadata.settings?.joinColumnName;
|
||||
|
||||
if (!isDefined(joinColumnName)) {
|
||||
throw new Error(
|
||||
'Join column name is not defined in a MANY_TO_ONE relation',
|
||||
);
|
||||
}
|
||||
const joinColumnName = computeMorphOrRelationFieldJoinColumnName({
|
||||
name: flatFieldMetadata.name,
|
||||
});
|
||||
|
||||
await this.workspaceSchemaManagerService.foreignKeyManager.createForeignKey(
|
||||
{
|
||||
|
||||
+29
-33
@@ -3,10 +3,12 @@ import { Injectable } from '@nestjs/common';
|
||||
import {
|
||||
FieldMetadataSettingsMapping,
|
||||
FieldMetadataType,
|
||||
RelationType,
|
||||
} from 'twenty-shared/types';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
import { ColumnType, type QueryRunner } from 'typeorm';
|
||||
|
||||
import { computeMorphOrRelationFieldJoinColumnName } from 'src/engine/metadata-modules/field-metadata/utils/compute-morph-or-relation-field-join-column-name.util';
|
||||
import { WorkspaceMigrationRunnerActionHandler } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-runner/interfaces/workspace-migration-runner-action-handler-service.interface';
|
||||
|
||||
import { FieldMetadataEntity } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity';
|
||||
@@ -223,35 +225,6 @@ export class UpdateFieldActionHandlerService extends WorkspaceMigrationRunnerAct
|
||||
}
|
||||
|
||||
if (isDefined(update.settings)) {
|
||||
// Handle joinColumnName rename
|
||||
if (isMorphOrRelationFlatFieldMetadata(optimisticFlatFieldMetadata)) {
|
||||
const fromSettings = optimisticFlatFieldMetadata.settings;
|
||||
const toSettings = update.settings as
|
||||
| FieldMetadataSettingsMapping['MORPH_RELATION']
|
||||
| FieldMetadataSettingsMapping['RELATION'];
|
||||
|
||||
if (
|
||||
isDefined(fromSettings?.joinColumnName) &&
|
||||
isDefined(toSettings?.joinColumnName) &&
|
||||
fromSettings.joinColumnName !== toSettings.joinColumnName
|
||||
) {
|
||||
await this.workspaceSchemaManagerService.columnManager.renameColumn({
|
||||
queryRunner,
|
||||
schemaName,
|
||||
tableName,
|
||||
oldColumnName: fromSettings.joinColumnName,
|
||||
newColumnName: toSettings.joinColumnName,
|
||||
});
|
||||
optimisticFlatFieldMetadata = {
|
||||
...optimisticFlatFieldMetadata,
|
||||
settings: {
|
||||
...optimisticFlatFieldMetadata.settings,
|
||||
joinColumnName: toSettings.joinColumnName,
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// Handle asExpression/generatedType change (for TS_VECTOR fields)
|
||||
if (
|
||||
isFlatFieldMetadataOfType(
|
||||
@@ -302,19 +275,26 @@ export class UpdateFieldActionHandlerService extends WorkspaceMigrationRunnerAct
|
||||
| FieldMetadataSettingsMapping['MORPH_RELATION']
|
||||
| FieldMetadataSettingsMapping['RELATION'];
|
||||
|
||||
const isManyToOne =
|
||||
optimisticFlatFieldMetadata.settings?.relationType ===
|
||||
RelationType.MANY_TO_ONE;
|
||||
|
||||
if (
|
||||
isDefined(optimisticFlatFieldMetadata.settings.joinColumnName) &&
|
||||
isManyToOne &&
|
||||
isDefined(fromSettings?.onDelete) &&
|
||||
isDefined(toSettings?.onDelete) &&
|
||||
toSettings.onDelete !== fromSettings.onDelete
|
||||
) {
|
||||
const joinColumnName = computeMorphOrRelationFieldJoinColumnName({
|
||||
name: optimisticFlatFieldMetadata.name,
|
||||
});
|
||||
const foreignKeyName =
|
||||
await this.workspaceSchemaManagerService.foreignKeyManager.getForeignKeyName(
|
||||
{
|
||||
queryRunner,
|
||||
schemaName,
|
||||
tableName,
|
||||
columnName: optimisticFlatFieldMetadata.settings.joinColumnName,
|
||||
columnName: joinColumnName,
|
||||
},
|
||||
);
|
||||
|
||||
@@ -351,7 +331,7 @@ export class UpdateFieldActionHandlerService extends WorkspaceMigrationRunnerAct
|
||||
schemaName,
|
||||
foreignKey: {
|
||||
tableName,
|
||||
columnName: optimisticFlatFieldMetadata.settings.joinColumnName,
|
||||
columnName: joinColumnName,
|
||||
referencedTableName,
|
||||
referencedColumnName: 'id',
|
||||
onDelete:
|
||||
@@ -403,7 +383,23 @@ export class UpdateFieldActionHandlerService extends WorkspaceMigrationRunnerAct
|
||||
newColumnName: toCompositeColumnName,
|
||||
});
|
||||
}
|
||||
} else if (!isMorphOrRelationFlatFieldMetadata(flatFieldMetadata)) {
|
||||
} else if (isMorphOrRelationFlatFieldMetadata(flatFieldMetadata)) {
|
||||
if (
|
||||
flatFieldMetadata.settings?.relationType === RelationType.MANY_TO_ONE
|
||||
) {
|
||||
await this.workspaceSchemaManagerService.columnManager.renameColumn({
|
||||
queryRunner,
|
||||
schemaName,
|
||||
tableName,
|
||||
oldColumnName: computeMorphOrRelationFieldJoinColumnName({
|
||||
name: fromName,
|
||||
}),
|
||||
newColumnName: computeMorphOrRelationFieldJoinColumnName({
|
||||
name: toName,
|
||||
}),
|
||||
});
|
||||
}
|
||||
} else {
|
||||
await this.workspaceSchemaManagerService.columnManager.renameColumn({
|
||||
queryRunner,
|
||||
schemaName,
|
||||
|
||||
+9
-4
@@ -1,7 +1,8 @@
|
||||
import { compositeTypeDefinitions } from 'twenty-shared/types';
|
||||
import { compositeTypeDefinitions, RelationType } from 'twenty-shared/types';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
import { type QueryRunner } from 'typeorm';
|
||||
|
||||
import { computeMorphOrRelationFieldJoinColumnName } from 'src/engine/metadata-modules/field-metadata/utils/compute-morph-or-relation-field-join-column-name.util';
|
||||
import { computeCompositeColumnName } from 'src/engine/metadata-modules/field-metadata/utils/compute-column-name.util';
|
||||
import { isCompositeFieldMetadataType } from 'src/engine/metadata-modules/field-metadata/utils/is-composite-field-metadata-type.util';
|
||||
import {
|
||||
@@ -41,14 +42,18 @@ export const computeFlatIndexFieldColumnNames = ({
|
||||
}
|
||||
|
||||
if (isMorphOrRelationFlatFieldMetadata(flatFieldMetadata)) {
|
||||
if (!isDefined(flatFieldMetadata.settings?.joinColumnName)) {
|
||||
if (
|
||||
flatFieldMetadata.settings?.relationType !== RelationType.MANY_TO_ONE
|
||||
) {
|
||||
throw new FlatEntityMapsException(
|
||||
'Join column name is not defined for relation field',
|
||||
'Cannot index a relation field that has no join column',
|
||||
FlatEntityMapsExceptionCode.ENTITY_NOT_FOUND,
|
||||
);
|
||||
}
|
||||
|
||||
return flatFieldMetadata.settings.joinColumnName;
|
||||
return computeMorphOrRelationFieldJoinColumnName({
|
||||
name: flatFieldMetadata.name,
|
||||
});
|
||||
}
|
||||
|
||||
if (isCompositeFieldMetadataType(flatFieldMetadata.type)) {
|
||||
|
||||
+11
-6
@@ -1,6 +1,12 @@
|
||||
import { type CompositeProperty, FieldMetadataType } from 'twenty-shared/types';
|
||||
import {
|
||||
type CompositeProperty,
|
||||
FieldMetadataType,
|
||||
RelationType,
|
||||
} from 'twenty-shared/types';
|
||||
|
||||
import { type ColumnType } from 'typeorm';
|
||||
|
||||
import { computeMorphOrRelationFieldJoinColumnName } from 'src/engine/metadata-modules/field-metadata/utils/compute-morph-or-relation-field-join-column-name.util';
|
||||
import { type CompositeFieldMetadataType } from 'src/engine/metadata-modules/field-metadata/types/composite-field-metadata-type.type';
|
||||
import {
|
||||
computeColumnName,
|
||||
@@ -109,14 +115,13 @@ const generateRelationColumnDefinition = (
|
||||
FieldMetadataType.RELATION | FieldMetadataType.MORPH_RELATION
|
||||
>,
|
||||
): WorkspaceSchemaColumnDefinition | null => {
|
||||
if (
|
||||
!flatFieldMetadata.settings ||
|
||||
!flatFieldMetadata.settings.joinColumnName
|
||||
) {
|
||||
if (flatFieldMetadata.settings?.relationType !== RelationType.MANY_TO_ONE) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const joinColumnName = flatFieldMetadata.settings.joinColumnName;
|
||||
const joinColumnName = computeMorphOrRelationFieldJoinColumnName({
|
||||
name: flatFieldMetadata.name,
|
||||
});
|
||||
|
||||
return {
|
||||
name: joinColumnName,
|
||||
|
||||
Reference in New Issue
Block a user