diff --git a/packages/twenty-server/src/engine/metadata-modules/object-metadata/utils/build-default-relation-flat-field-metadatas-for-custom-object.util.ts b/packages/twenty-server/src/engine/metadata-modules/object-metadata/utils/build-default-relation-flat-field-metadatas-for-custom-object.util.ts index 2e2cad7ce00..3c0a456ed51 100644 --- a/packages/twenty-server/src/engine/metadata-modules/object-metadata/utils/build-default-relation-flat-field-metadatas-for-custom-object.util.ts +++ b/packages/twenty-server/src/engine/metadata-modules/object-metadata/utils/build-default-relation-flat-field-metadatas-for-custom-object.util.ts @@ -239,7 +239,7 @@ export const buildDefaultRelationFlatFieldMetadatasForCustomObject = ({ }, flatRelationTargetObjectMetadata: fromFlatObjectMetadataToFlatObjectMetadataWithoutFields( - sourceFlatObjectMetadata, + targetFlatObjectMetadata, ), }, ], @@ -254,7 +254,7 @@ export const buildDefaultRelationFlatFieldMetadatasForCustomObject = ({ }, flatRelationTargetObjectMetadata: fromFlatObjectMetadataToFlatObjectMetadataWithoutFields( - targetFlatObjectMetadata, + sourceFlatObjectMetadata, ), }, ], diff --git a/packages/twenty-server/src/engine/twenty-orm/workspace-schema-manager/services/workspace-schema-foreign-key-manager.service.ts b/packages/twenty-server/src/engine/twenty-orm/workspace-schema-manager/services/workspace-schema-foreign-key-manager.service.ts index 68ad13d93a6..0c113f5ab22 100644 --- a/packages/twenty-server/src/engine/twenty-orm/workspace-schema-manager/services/workspace-schema-foreign-key-manager.service.ts +++ b/packages/twenty-server/src/engine/twenty-orm/workspace-schema-manager/services/workspace-schema-foreign-key-manager.service.ts @@ -7,29 +7,20 @@ export class WorkspaceSchemaForeignKeyManagerService { async createForeignKey({ queryRunner, schemaName, - tableName, foreignKey, }: { queryRunner: QueryRunner; schemaName: string; - tableName: string; foreignKey: WorkspaceSchemaForeignKeyDefinition; }): Promise { - const safeSchemaName = removeSqlDDLInjection(schemaName); - const safeTableName = removeSqlDDLInjection(tableName); - const safeForeignKeyName = removeSqlDDLInjection(foreignKey.name); - const safeReferencedTableName = removeSqlDDLInjection( - foreignKey.referencedTableName, + const foreignKeyName = queryRunner.connection.namingStrategy.foreignKeyName( + foreignKey.tableName, + [foreignKey.columnName], + `${schemaName}.${foreignKey.referencedTableName}`, + [foreignKey.referencedColumnName], ); - const quotedColumns = foreignKey.columnNames - .map((col) => `"${removeSqlDDLInjection(col)}"`) - .join(', '); - const quotedRefColumns = foreignKey.referencedColumnNames - .map((col) => `"${removeSqlDDLInjection(col)}"`) - .join(', '); - - let sql = `ALTER TABLE "${safeSchemaName}"."${safeTableName}" ADD CONSTRAINT "${safeForeignKeyName}" FOREIGN KEY (${quotedColumns}) REFERENCES "${safeSchemaName}"."${safeReferencedTableName}" (${quotedRefColumns})`; + let sql = `ALTER TABLE "${schemaName}"."${foreignKey.tableName}" ADD CONSTRAINT "${foreignKeyName}" FOREIGN KEY ("${foreignKey.columnName}") REFERENCES "${schemaName}"."${foreignKey.referencedTableName}" ("${foreignKey.referencedColumnName}")`; if (foreignKey.onDelete) { sql += ` ON DELETE ${foreignKey.onDelete}`; @@ -61,223 +52,6 @@ export class WorkspaceSchemaForeignKeyManagerService { await queryRunner.query(sql); } - async dropForeignKeyByColumn({ - queryRunner, - schemaName, - tableName, - columnName, - }: { - queryRunner: QueryRunner; - schemaName: string; - tableName: string; - columnName: string; - }): Promise { - const foreignKeyName = await this.getForeignKeyNameByColumn({ - queryRunner, - schemaName, - tableName, - columnName, - }); - - if (foreignKeyName) { - await this.dropForeignKey({ - queryRunner, - schemaName, - tableName, - foreignKeyName, - }); - } - } - - async foreignKeyExists({ - queryRunner, - schemaName, - tableName, - foreignKeyName, - }: { - queryRunner: QueryRunner; - schemaName: string; - tableName: string; - foreignKeyName: string; - }): Promise { - const safeSchemaName = removeSqlDDLInjection(schemaName); - const safeTableName = removeSqlDDLInjection(tableName); - const safeForeignKeyName = removeSqlDDLInjection(foreignKeyName); - - const result = await queryRunner.query( - `SELECT EXISTS ( - SELECT FROM information_schema.table_constraints - WHERE constraint_schema = $1 - AND table_name = $2 - AND constraint_name = $3 - AND constraint_type = 'FOREIGN KEY' - )`, - [safeSchemaName, safeTableName, safeForeignKeyName], - ); - - return result[0]?.exists || false; - } - - async getForeignKeyNameByColumn({ - queryRunner, - schemaName, - tableName, - columnName, - }: { - queryRunner: QueryRunner; - schemaName: string; - tableName: string; - columnName: string; - }): Promise { - const safeSchemaName = removeSqlDDLInjection(schemaName); - const safeTableName = removeSqlDDLInjection(tableName); - const safeColumnName = removeSqlDDLInjection(columnName); - - const result = await queryRunner.query( - `SELECT tc.constraint_name - FROM information_schema.table_constraints AS tc - JOIN information_schema.key_column_usage AS kcu - ON tc.constraint_name = kcu.constraint_name - AND tc.table_schema = kcu.table_schema - WHERE tc.constraint_type = 'FOREIGN KEY' - AND tc.table_schema = $1 - AND tc.table_name = $2 - AND kcu.column_name = $3`, - [safeSchemaName, safeTableName, safeColumnName], - ); - - return result[0]?.constraint_name || null; - } - - async getForeignKeysForTable({ - queryRunner, - schemaName, - tableName, - }: { - queryRunner: QueryRunner; - schemaName: string; - tableName: string; - }): Promise< - Array<{ - constraint_name: string; - column_name: string; - foreign_table_name: string; - foreign_column_name: string; - delete_rule: string; - update_rule: string; - }> - > { - const safeSchemaName = removeSqlDDLInjection(schemaName); - const safeTableName = removeSqlDDLInjection(tableName); - - const result = await queryRunner.query( - `SELECT - tc.constraint_name, - kcu.column_name, - ccu.table_name AS foreign_table_name, - ccu.column_name AS foreign_column_name, - rc.delete_rule, - rc.update_rule - FROM information_schema.table_constraints AS tc - JOIN information_schema.key_column_usage AS kcu - ON tc.constraint_name = kcu.constraint_name - AND tc.table_schema = kcu.table_schema - JOIN information_schema.constraint_column_usage AS ccu - ON ccu.constraint_name = tc.constraint_name - AND ccu.table_schema = tc.table_schema - JOIN information_schema.referential_constraints AS rc - ON tc.constraint_name = rc.constraint_name - AND tc.table_schema = rc.constraint_schema - WHERE tc.constraint_type = 'FOREIGN KEY' - AND tc.table_schema = $1 - AND tc.table_name = $2`, - [safeSchemaName, safeTableName], - ); - - return result; - } - - async createForeignKeyFromColumn({ - queryRunner, - schemaName, - tableName, - columnName, - referencedTableName, - referencedColumnName = 'id', - onDelete, - }: { - queryRunner: QueryRunner; - schemaName: string; - tableName: string; - columnName: string; - referencedTableName: string; - referencedColumnName?: string; - onDelete?: WorkspaceSchemaForeignKeyDefinition['onDelete']; - }): Promise { - const foreignKeyName = queryRunner.connection.namingStrategy.foreignKeyName( - tableName, - [columnName], - `${schemaName}.${referencedTableName}`, - [referencedColumnName], - ); - - const foreignKey: WorkspaceSchemaForeignKeyDefinition = { - name: foreignKeyName, - columnNames: [columnName], - referencedTableName, - referencedColumnNames: [referencedColumnName], - onDelete, - }; - - await this.createForeignKey({ - queryRunner, - schemaName, - tableName, - foreignKey, - }); - } - - async renameForeignKey({ - queryRunner, - schemaName, - tableName, - oldConstraintName, - newConstraintName, - }: { - queryRunner: QueryRunner; - schemaName: string; - tableName: string; - oldConstraintName: string; - newConstraintName: string; - }): Promise { - const safeSchemaName = removeSqlDDLInjection(schemaName); - const safeTableName = removeSqlDDLInjection(tableName); - const safeOldConstraintName = removeSqlDDLInjection(oldConstraintName); - const safeNewConstraintName = removeSqlDDLInjection(newConstraintName); - const sql = `ALTER TABLE "${safeSchemaName}"."${safeTableName}" RENAME CONSTRAINT "${safeOldConstraintName}" TO "${safeNewConstraintName}"`; - - await queryRunner.query(sql); - } - - async validateForeignKey({ - queryRunner, - schemaName, - tableName, - foreignKeyName, - }: { - queryRunner: QueryRunner; - schemaName: string; - tableName: string; - foreignKeyName: string; - }): Promise { - const safeSchemaName = removeSqlDDLInjection(schemaName); - const safeTableName = removeSqlDDLInjection(tableName); - const safeForeignKeyName = removeSqlDDLInjection(foreignKeyName); - const sql = `ALTER TABLE "${safeSchemaName}"."${safeTableName}" VALIDATE CONSTRAINT "${safeForeignKeyName}"`; - - await queryRunner.query(sql); - } - async setForeignKeyNotDeferrable({ queryRunner, schemaName, diff --git a/packages/twenty-server/src/engine/twenty-orm/workspace-schema-manager/types/workspace-schema-foreign-key-definition.type.ts b/packages/twenty-server/src/engine/twenty-orm/workspace-schema-manager/types/workspace-schema-foreign-key-definition.type.ts index 9e67f6fa773..f029b9c0263 100644 --- a/packages/twenty-server/src/engine/twenty-orm/workspace-schema-manager/types/workspace-schema-foreign-key-definition.type.ts +++ b/packages/twenty-server/src/engine/twenty-orm/workspace-schema-manager/types/workspace-schema-foreign-key-definition.type.ts @@ -1,8 +1,8 @@ export type WorkspaceSchemaForeignKeyDefinition = { - name: string; - columnNames: string[]; + tableName: string; + columnName: string; referencedTableName: string; - referencedColumnNames: string[]; + referencedColumnName: string; onDelete?: 'CASCADE' | 'SET NULL' | 'RESTRICT' | 'NO ACTION' | 'SET DEFAULT'; onUpdate?: 'CASCADE' | 'SET NULL' | 'RESTRICT' | 'NO ACTION' | 'SET DEFAULT'; }; diff --git a/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/action-handlers/field/services/create-field-action-handler.service.ts b/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/action-handlers/field/services/create-field-action-handler.service.ts index 59226368ef3..302766c0284 100644 --- a/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/action-handlers/field/services/create-field-action-handler.service.ts +++ b/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/action-handlers/field/services/create-field-action-handler.service.ts @@ -1,5 +1,8 @@ import { Injectable } from '@nestjs/common'; +import { RelationType } from 'twenty-shared/types'; +import { isDefined } from 'twenty-shared/utils'; + import { OptimisticallyApplyActionOnAllFlatEntityMapsArgs, WorkspaceMigrationRunnerActionHandler, @@ -7,9 +10,12 @@ import { import { AllFlatEntityMaps } from 'src/engine/core-modules/common/types/all-flat-entity-maps.type'; import { FieldMetadataEntity } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity'; +import { isMorphOrRelationFlatFieldMetadata } from 'src/engine/metadata-modules/flat-field-metadata/utils/is-morph-or-relation-flat-field-metadata.util'; import { addFlatFieldMetadataInFlatObjectMetadataMapsOrThrow } from 'src/engine/metadata-modules/flat-object-metadata-maps/utils/add-flat-field-metadata-in-flat-object-metadata-maps-or-throw.util'; import { findFlatObjectMetadataWithFlatFieldMapsInFlatObjectMetadataMapsOrThrow } from 'src/engine/metadata-modules/flat-object-metadata-maps/utils/find-flat-object-metadata-with-flat-field-maps-in-flat-object-metadata-maps-or-throw.util'; import { WorkspaceSchemaManagerService } from 'src/engine/twenty-orm/workspace-schema-manager/workspace-schema-manager.service'; +import { computeObjectTargetTable } from 'src/engine/utils/compute-object-target-table.util'; +import { convertOnDeleteActionToOnDelete } from 'src/engine/workspace-manager/workspace-migration-runner/utils/convert-on-delete-action-to-on-delete.util'; import { type CreateFieldAction } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/types/workspace-migration-field-action-v2'; import { type WorkspaceMigrationActionRunnerArgs } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/types/workspace-migration-action-runner-args.type'; import { generateColumnDefinitions } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/utils/generate-column-definitions.util'; @@ -106,5 +112,39 @@ export class CreateFieldActionHandlerService extends WorkspaceMigrationRunnerAct tableName, columnDefinitions, }); + + if ( + isMorphOrRelationFlatFieldMetadata(flatFieldMetadata) && + flatFieldMetadata.settings.relationType === RelationType.MANY_TO_ONE + ) { + const referencedTableName = computeObjectTargetTable( + flatFieldMetadata.flatRelationTargetObjectMetadata, + ); + + const joinColumnName = flatFieldMetadata.settings.joinColumnName; + + if (!isDefined(joinColumnName)) { + throw new Error( + 'Join column name is not defined in a MANY_TO_ONE relation', + ); + } + + await this.workspaceSchemaManagerService.foreignKeyManager.createForeignKey( + { + queryRunner, + schemaName, + foreignKey: { + tableName, + columnName: joinColumnName, + referencedTableName, + referencedColumnName: 'id', + onDelete: + convertOnDeleteActionToOnDelete( + flatFieldMetadata.settings.onDelete, + ) ?? 'CASCADE', + }, + }, + ); + } } }