diff --git a/packages/twenty-server/src/engine/metadata-modules/index-metadata/index-metadata.module.ts b/packages/twenty-server/src/engine/metadata-modules/index-metadata/index-metadata.module.ts index 01cb4a3e6..c22a56dfb 100644 --- a/packages/twenty-server/src/engine/metadata-modules/index-metadata/index-metadata.module.ts +++ b/packages/twenty-server/src/engine/metadata-modules/index-metadata/index-metadata.module.ts @@ -2,10 +2,15 @@ import { Module } from '@nestjs/common'; import { TypeOrmModule } from '@nestjs/typeorm'; import { IndexMetadataEntity } from 'src/engine/metadata-modules/index-metadata/index-metadata.entity'; +import { IndexMetadataService } from 'src/engine/metadata-modules/index-metadata/index-metadata.service'; +import { WorkspaceMigrationModule } from 'src/engine/metadata-modules/workspace-migration/workspace-migration.module'; @Module({ - imports: [TypeOrmModule.forFeature([IndexMetadataEntity], 'metadata')], - providers: [], - exports: [], + imports: [ + TypeOrmModule.forFeature([IndexMetadataEntity], 'metadata'), + WorkspaceMigrationModule, + ], + providers: [IndexMetadataService], + exports: [IndexMetadataService], }) export class IndexMetadataModule {} diff --git a/packages/twenty-server/src/engine/metadata-modules/index-metadata/index-metadata.service.ts b/packages/twenty-server/src/engine/metadata-modules/index-metadata/index-metadata.service.ts new file mode 100644 index 000000000..97d9fdb9b --- /dev/null +++ b/packages/twenty-server/src/engine/metadata-modules/index-metadata/index-metadata.service.ts @@ -0,0 +1,87 @@ +import { Injectable } from '@nestjs/common'; +import { InjectRepository } from '@nestjs/typeorm'; + +import { Repository } from 'typeorm'; + +import { FieldMetadataEntity } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity'; +import { IndexMetadataEntity } from 'src/engine/metadata-modules/index-metadata/index-metadata.entity'; +import { generateDeterministicIndexName } from 'src/engine/metadata-modules/index-metadata/utils/generate-deterministic-index-name'; +import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity'; +import { generateMigrationName } from 'src/engine/metadata-modules/workspace-migration/utils/generate-migration-name.util'; +import { + WorkspaceMigrationIndexActionType, + WorkspaceMigrationTableAction, + WorkspaceMigrationTableActionType, +} from 'src/engine/metadata-modules/workspace-migration/workspace-migration.entity'; +import { WorkspaceMigrationService } from 'src/engine/metadata-modules/workspace-migration/workspace-migration.service'; +import { computeObjectTargetTable } from 'src/engine/utils/compute-object-target-table.util'; + +@Injectable() +export class IndexMetadataService { + constructor( + @InjectRepository(IndexMetadataEntity, 'metadata') + private readonly indexMetadataRepository: Repository, + private readonly workspaceMigrationService: WorkspaceMigrationService, + ) {} + + async createIndex( + workspaceId: string, + objectMetadata: ObjectMetadataEntity, + fieldMetadataToIndex: Partial[], + ) { + const tableName = computeObjectTargetTable(objectMetadata); + + const columnNames: string[] = fieldMetadataToIndex.map( + (fieldMetadata) => fieldMetadata.name as string, + ); + + const indexName = `IDX_${generateDeterministicIndexName([tableName, ...columnNames])}`; + + let savedIndexMetadata: IndexMetadataEntity; + + try { + savedIndexMetadata = await this.indexMetadataRepository.save({ + name: indexName, + tableName, + indexFieldMetadatas: fieldMetadataToIndex.map( + (fieldMetadata, index) => { + return { + fieldMetadataId: fieldMetadata.id, + order: index, + }; + }, + ), + workspaceId, + objectMetadataId: objectMetadata.id, + }); + } catch (error) { + throw new Error( + `Failed to create index ${indexName} on object metadata ${objectMetadata.nameSingular}`, + ); + } + + if (!savedIndexMetadata) { + throw new Error( + `Failed to return saved index ${indexName} on object metadata ${objectMetadata.nameSingular}`, + ); + } + + const migration = { + name: tableName, + action: WorkspaceMigrationTableActionType.ALTER_INDEXES, + indexes: [ + { + action: WorkspaceMigrationIndexActionType.CREATE, + columns: columnNames, + name: indexName, + }, + ], + } satisfies WorkspaceMigrationTableAction; + + await this.workspaceMigrationService.createCustomMigration( + generateMigrationName(`create-${objectMetadata.nameSingular}-index`), + workspaceId, + [migration], + ); + } +} diff --git a/packages/twenty-server/src/engine/metadata-modules/relation-metadata/relation-metadata.module.ts b/packages/twenty-server/src/engine/metadata-modules/relation-metadata/relation-metadata.module.ts index b3d8efb8e..b1c954e8a 100644 --- a/packages/twenty-server/src/engine/metadata-modules/relation-metadata/relation-metadata.module.ts +++ b/packages/twenty-server/src/engine/metadata-modules/relation-metadata/relation-metadata.module.ts @@ -9,6 +9,7 @@ import { NestjsQueryTypeOrmModule } from '@ptc-org/nestjs-query-typeorm'; import { WorkspaceAuthGuard } from 'src/engine/guards/workspace-auth.guard'; import { FieldMetadataEntity } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity'; import { FieldMetadataModule } from 'src/engine/metadata-modules/field-metadata/field-metadata.module'; +import { IndexMetadataModule } from 'src/engine/metadata-modules/index-metadata/index-metadata.module'; import { ObjectMetadataModule } from 'src/engine/metadata-modules/object-metadata/object-metadata.module'; import { RelationMetadataGraphqlApiExceptionInterceptor } from 'src/engine/metadata-modules/relation-metadata/interceptors/relation-metadata-graphql-api-exception.interceptor'; import { RelationMetadataResolver } from 'src/engine/metadata-modules/relation-metadata/relation-metadata.resolver'; @@ -33,6 +34,7 @@ import { RelationMetadataDTO } from './dtos/relation-metadata.dto'; ), ObjectMetadataModule, FieldMetadataModule, + IndexMetadataModule, WorkspaceMigrationRunnerModule, WorkspaceMigrationModule, WorkspaceCacheStorageModule, diff --git a/packages/twenty-server/src/engine/metadata-modules/relation-metadata/relation-metadata.service.ts b/packages/twenty-server/src/engine/metadata-modules/relation-metadata/relation-metadata.service.ts index fa59ee997..affe7e1b7 100644 --- a/packages/twenty-server/src/engine/metadata-modules/relation-metadata/relation-metadata.service.ts +++ b/packages/twenty-server/src/engine/metadata-modules/relation-metadata/relation-metadata.service.ts @@ -13,6 +13,7 @@ import { FieldMetadataType, } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity'; import { FieldMetadataService } from 'src/engine/metadata-modules/field-metadata/field-metadata.service'; +import { IndexMetadataService } from 'src/engine/metadata-modules/index-metadata/index-metadata.service'; import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity'; import { ObjectMetadataService } from 'src/engine/metadata-modules/object-metadata/object-metadata.service'; import { CreateRelationInput } from 'src/engine/metadata-modules/relation-metadata/dtos/create-relation.input'; @@ -34,6 +35,7 @@ import { WorkspaceMigrationService } from 'src/engine/metadata-modules/workspace import { computeObjectTargetTable } from 'src/engine/utils/compute-object-target-table.util'; import { WorkspaceCacheStorageService } from 'src/engine/workspace-cache-storage/workspace-cache-storage.service'; import { WorkspaceMigrationRunnerService } from 'src/engine/workspace-manager/workspace-migration-runner/workspace-migration-runner.service'; +import { BASE_OBJECT_STANDARD_FIELD_IDS } from 'src/engine/workspace-manager/workspace-sync-metadata/constants/standard-field-ids'; import { RelationMetadataEntity, @@ -54,6 +56,7 @@ export class RelationMetadataService extends TypeOrmQueryService fieldMetadata.type === FieldMetadataType.UUID, + ); + + if (!foreignKeyFieldMetadata) { + throw new RelationMetadataException( + `ForeignKey field metadata not found`, + RelationMetadataExceptionCode.RELATION_METADATA_NOT_FOUND, + ); + } + + const deletedFieldMetadata = toObjectMetadata.fields.find( + (fieldMetadata) => + fieldMetadata.standardId === BASE_OBJECT_STANDARD_FIELD_IDS.deletedAt, + ); + + if (!deletedFieldMetadata) { + throw new RelationMetadataException( + `Deleted field metadata not found`, + RelationMetadataExceptionCode.RELATION_METADATA_NOT_FOUND, + ); + } + + await this.indexMetadataService.createIndex( + relationMetadataInput.workspaceId, + toObjectMetadata, + [foreignKeyFieldMetadata, deletedFieldMetadata], + ); + await this.workspaceMigrationRunnerService.executeMigrationFromPendingMigrations( relationMetadataInput.workspaceId, );