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:
+4
-11
@@ -7,16 +7,13 @@ import {
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
import { type ColumnType, type EntitySchemaColumnOptions } from 'typeorm';
|
||||
|
||||
import { computeMorphOrRelationFieldJoinColumnName } from 'src/engine/metadata-modules/field-metadata/utils/compute-morph-or-relation-field-join-column-name.util';
|
||||
import { RelationType } from 'src/engine/metadata-modules/field-metadata/interfaces/relation-type.interface';
|
||||
|
||||
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 { isEnumFieldMetadataType } from 'src/engine/metadata-modules/field-metadata/utils/is-enum-field-metadata-type.util';
|
||||
import { serializeDefaultValue } from 'src/engine/metadata-modules/field-metadata/utils/serialize-default-value';
|
||||
import {
|
||||
TwentyORMException,
|
||||
TwentyORMExceptionCode,
|
||||
} from 'src/engine/twenty-orm/exceptions/twenty-orm.exception';
|
||||
import {
|
||||
type EntitySchemaFieldMetadata,
|
||||
type EntitySchemaFieldMetadataMaps,
|
||||
@@ -57,18 +54,14 @@ export class EntitySchemaColumnFactory {
|
||||
if (isRelation) {
|
||||
const isManyToOneRelation =
|
||||
fieldMetadata.settings?.relationType === RelationType.MANY_TO_ONE;
|
||||
const joinColumnName = fieldMetadata.settings?.joinColumnName;
|
||||
|
||||
if (!isManyToOneRelation) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!isDefined(joinColumnName)) {
|
||||
throw new TwentyORMException(
|
||||
`Field ${fieldMetadata.id} of type ${fieldMetadata.type} is a many to one relation but does not have a join column name`,
|
||||
TwentyORMExceptionCode.MALFORMED_METADATA,
|
||||
);
|
||||
}
|
||||
const joinColumnName = computeMorphOrRelationFieldJoinColumnName({
|
||||
name: fieldMetadata.name,
|
||||
});
|
||||
|
||||
entitySchemaColumnMap[joinColumnName] = {
|
||||
name: joinColumnName,
|
||||
|
||||
+15
-3
@@ -1,4 +1,8 @@
|
||||
import { type FieldMetadataType } from 'twenty-shared/types';
|
||||
import {
|
||||
type FieldMetadataType,
|
||||
RelationType as TwentyRelationType,
|
||||
} from 'twenty-shared/types';
|
||||
|
||||
import { type RelationType } from 'typeorm/metadata/types/RelationTypes';
|
||||
|
||||
import {
|
||||
@@ -10,6 +14,7 @@ import {
|
||||
type EntitySchemaFieldMetadataMaps,
|
||||
type EntitySchemaObjectMetadataMaps,
|
||||
} from 'src/engine/twenty-orm/global-workspace-datasource/types/entity-schema-metadata.type';
|
||||
import { computeMorphOrRelationFieldJoinColumnName } from 'src/engine/metadata-modules/field-metadata/utils/compute-morph-or-relation-field-join-column-name.util';
|
||||
import { converRelationTypeToTypeORMRelationType } from 'src/engine/twenty-orm/utils/convert-relation-type-to-typeorm-relation-type.util';
|
||||
|
||||
interface RelationDetails {
|
||||
@@ -65,12 +70,19 @@ export function determineSchemaRelationDetails(
|
||||
throw new Error('Target field metadata not found');
|
||||
}
|
||||
|
||||
const isManyToOne =
|
||||
fieldMetadata.settings.relationType === TwentyRelationType.MANY_TO_ONE;
|
||||
|
||||
return {
|
||||
relationType,
|
||||
target: targetObjectMetadata.nameSingular,
|
||||
inverseSide: targetFieldMetadata.name,
|
||||
joinColumn: fieldMetadata.settings.joinColumnName
|
||||
? { name: fieldMetadata.settings.joinColumnName }
|
||||
joinColumn: isManyToOne
|
||||
? {
|
||||
name: computeMorphOrRelationFieldJoinColumnName({
|
||||
name: fieldMetadata.name,
|
||||
}),
|
||||
}
|
||||
: undefined,
|
||||
};
|
||||
}
|
||||
|
||||
+2
-6
@@ -1,8 +1,8 @@
|
||||
import { computeMorphOrRelationFieldJoinColumnName } from 'src/engine/metadata-modules/field-metadata/utils/compute-morph-or-relation-field-join-column-name.util';
|
||||
import {
|
||||
type FieldMetadataSettingsMapping,
|
||||
RelationType,
|
||||
} from 'twenty-shared/types';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
|
||||
export const formatColumnNameForRelationField = (
|
||||
fieldName: string,
|
||||
@@ -13,11 +13,7 @@ export const formatColumnNameForRelationField = (
|
||||
}
|
||||
|
||||
if (fieldMetadataSettings.relationType === RelationType.MANY_TO_ONE) {
|
||||
if (!isDefined(fieldMetadataSettings.joinColumnName)) {
|
||||
throw new Error(`Join column name is not defined for field ${fieldName}`);
|
||||
}
|
||||
|
||||
return fieldMetadataSettings.joinColumnName;
|
||||
return computeMorphOrRelationFieldJoinColumnName({ name: fieldName });
|
||||
}
|
||||
|
||||
return fieldName;
|
||||
|
||||
+6
-7
@@ -47,6 +47,7 @@ import {
|
||||
isMatchingUUIDFilter,
|
||||
} from 'twenty-shared/utils';
|
||||
|
||||
import { computeMorphOrRelationFieldJoinColumnName } from 'src/engine/metadata-modules/field-metadata/utils/compute-morph-or-relation-field-join-column-name.util';
|
||||
import { getFlatFieldsFromFlatObjectMetadata } from 'src/engine/api/graphql/workspace-schema-builder/utils/get-flat-fields-for-flat-object-metadata.util';
|
||||
import { type FlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/types/flat-entity-maps.type';
|
||||
import { type FlatFieldMetadata } from 'src/engine/metadata-modules/flat-field-metadata/types/flat-field-metadata.type';
|
||||
@@ -208,8 +209,8 @@ export const isRecordMatchingRLSRowLevelPermissionPredicate = ({
|
||||
objectFields.find(
|
||||
(field) =>
|
||||
field.type === FieldMetadataType.RELATION &&
|
||||
(field.settings as { joinColumnName?: string } | undefined)
|
||||
?.joinColumnName === filterKey,
|
||||
computeMorphOrRelationFieldJoinColumnName({ name: field.name }) ===
|
||||
filterKey,
|
||||
);
|
||||
|
||||
if (!isDefined(objectMetadataField)) {
|
||||
@@ -412,11 +413,9 @@ export const isRecordMatchingRLSRowLevelPermissionPredicate = ({
|
||||
}
|
||||
case FieldMetadataType.RELATION: {
|
||||
const isJoinColumn =
|
||||
(
|
||||
objectMetadataField.settings as
|
||||
| { joinColumnName?: string }
|
||||
| undefined
|
||||
)?.joinColumnName === filterKey;
|
||||
computeMorphOrRelationFieldJoinColumnName({
|
||||
name: objectMetadataField.name,
|
||||
}) === filterKey;
|
||||
|
||||
if (isJoinColumn) {
|
||||
return isMatchingUUIDFilter({
|
||||
|
||||
Reference in New Issue
Block a user