Refactor schema migration runner service discovery (#13988)
## Context
This refactoring improves scalability in the workspace migration runner
in terms of actions. Instead of relying on a switch case and multiple
changes and services + duplicate things between metadata and workspace
schema, we now have a unique module for all kind of actions and a
registry pattern to register them and run them dynamically.
Example on how to add a new "create_role" action with the future design:
```typescript
// packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/action-handlers/role/services/create-role-action-handler.service.ts
export class CreateRoleActionHandlerService extends WorkspaceMigrationRunnerActionHandler(
'create_role',
) {
constructor(
private readonly workspaceSchemaManagerService: WorkspaceSchemaManagerService,
private readonly roleRepository: Repository<Role>,
) {}
async executeForMetadata(
context: WorkspaceMigrationActionRunnerArgs<CreateRoleAction>,
): Promise<void> {
this.roleRepository.save({...}); // technically could use the queryRunner from context param directly instead of injecting the repository
}
async executeForWorkspaceSchema(
context: WorkspaceMigrationActionRunnerArgs<CreateRoleAction>,
): Promise<void> {
// this.workspaceSchemaManagerService.tableManager... -> Nothing to do in the workspace schema in the role case
}
}
```
```typescript
// packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/action-handlers/workspace-schema-migration-runner-action-handlers.module.ts
@Module({
imports: [WorkspaceSchemaManagerModule, +TypeOrmModule.forFeature([Role]),
providers: [
...
+RoleCreateActionService
],
})
export class WorkspaceSchemaMigrationRunnerActionHandlersModule {}
```
```typescript
export type WorkspaceMigrationAction=
| WorkspaceMigrationObjectAction
...
+| WorkspaceMigrationRoleAction;
export type CreateRoleAction = {
type = 'create_role',
role: RoleEntity
};
export type WorkspaceMigrationRoleAction = CreateRoleAction;
```
This commit is contained in:
+1
-1
@@ -26,7 +26,7 @@ import { deleteFieldFromFlatObjectMetadataMapsOrThrow } from 'src/engine/metadat
|
|||||||
import { getSubFlatObjectMetadataMapsOrThrow } from 'src/engine/metadata-modules/flat-object-metadata-maps/utils/get-sub-flat-object-metadata-maps-or-throw.util';
|
import { getSubFlatObjectMetadataMapsOrThrow } from 'src/engine/metadata-modules/flat-object-metadata-maps/utils/get-sub-flat-object-metadata-maps-or-throw.util';
|
||||||
import { replaceFlatFieldMetadataInFlatObjectMetadataMapsOrThrow } from 'src/engine/metadata-modules/flat-object-metadata-maps/utils/replace-flat-field-metadata-in-flat-object-metadata-maps-or-throw.util';
|
import { replaceFlatFieldMetadataInFlatObjectMetadataMapsOrThrow } from 'src/engine/metadata-modules/flat-object-metadata-maps/utils/replace-flat-field-metadata-in-flat-object-metadata-maps-or-throw.util';
|
||||||
import { WorkspaceMetadataCacheService } from 'src/engine/metadata-modules/workspace-metadata-cache/services/workspace-metadata-cache.service';
|
import { WorkspaceMetadataCacheService } from 'src/engine/metadata-modules/workspace-metadata-cache/services/workspace-metadata-cache.service';
|
||||||
import { WorkspaceMigrationValidateBuildAndRunService } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-validate-build-and-run-service';
|
import { WorkspaceMigrationValidateBuildAndRunService } from 'src/engine/workspace-manager/workspace-migration-v2/services/workspace-migration-validate-build-and-run-service';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class FieldMetadataServiceV2 {
|
export class FieldMetadataServiceV2 {
|
||||||
|
|||||||
+1
-1
@@ -23,7 +23,7 @@ import {
|
|||||||
ObjectMetadataExceptionCode,
|
ObjectMetadataExceptionCode,
|
||||||
} from 'src/engine/metadata-modules/object-metadata/object-metadata.exception';
|
} from 'src/engine/metadata-modules/object-metadata/object-metadata.exception';
|
||||||
import { WorkspaceMetadataCacheService } from 'src/engine/metadata-modules/workspace-metadata-cache/services/workspace-metadata-cache.service';
|
import { WorkspaceMetadataCacheService } from 'src/engine/metadata-modules/workspace-metadata-cache/services/workspace-metadata-cache.service';
|
||||||
import { WorkspaceMigrationValidateBuildAndRunService } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-validate-build-and-run-service';
|
import { WorkspaceMigrationValidateBuildAndRunService } from 'src/engine/workspace-manager/workspace-migration-v2/services/workspace-migration-validate-build-and-run-service';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class ObjectMetadataServiceV2 {
|
export class ObjectMetadataServiceV2 {
|
||||||
|
|||||||
+1
-1
@@ -5,7 +5,7 @@ import {
|
|||||||
WorkspaceMigrationBuildArgs,
|
WorkspaceMigrationBuildArgs,
|
||||||
WorkspaceMigrationBuilderV2Service,
|
WorkspaceMigrationBuilderV2Service,
|
||||||
} from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/services/workspace-migration-builder-v2.service';
|
} from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/services/workspace-migration-builder-v2.service';
|
||||||
import { WorkspaceMigrationRunnerV2Service } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/workspace-migration-runner-v2.service';
|
import { WorkspaceMigrationRunnerV2Service } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/services/workspace-migration-runner-v2.service';
|
||||||
import {
|
import {
|
||||||
WorkspaceMigrationV2Exception,
|
WorkspaceMigrationV2Exception,
|
||||||
WorkspaceMigrationV2ExceptionCode,
|
WorkspaceMigrationV2ExceptionCode,
|
||||||
+5
@@ -8,3 +8,8 @@ export type WorkspaceMigrationActionV2 =
|
|||||||
| WorkspaceMigrationIndexActionV2;
|
| WorkspaceMigrationIndexActionV2;
|
||||||
|
|
||||||
export type WorkspaceMigrationActionTypeV2 = WorkspaceMigrationActionV2['type'];
|
export type WorkspaceMigrationActionTypeV2 = WorkspaceMigrationActionV2['type'];
|
||||||
|
|
||||||
|
export type ExtractAction<T extends WorkspaceMigrationActionTypeV2> = Extract<
|
||||||
|
WorkspaceMigrationActionV2,
|
||||||
|
{ type: T }
|
||||||
|
>;
|
||||||
|
|||||||
+85
@@ -0,0 +1,85 @@
|
|||||||
|
import { Injectable } from '@nestjs/common';
|
||||||
|
|
||||||
|
import { WorkspaceMigrationRunnerActionHandler } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/interfaces/workspace-migration-runner-action-handler-service.interface';
|
||||||
|
|
||||||
|
import { FieldMetadataEntity } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity';
|
||||||
|
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 { 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';
|
||||||
|
import { getWorkspaceSchemaContextForMigration } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/utils/get-workspace-schema-context-for-migration.util';
|
||||||
|
import {
|
||||||
|
collectEnumOperationsForField,
|
||||||
|
EnumOperation,
|
||||||
|
executeBatchEnumOperations,
|
||||||
|
} from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/utils/workspace-schema-enum-operations.util';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class CreateFieldActionHandlerService extends WorkspaceMigrationRunnerActionHandler(
|
||||||
|
'create_field',
|
||||||
|
) {
|
||||||
|
constructor(
|
||||||
|
private readonly workspaceSchemaManagerService: WorkspaceSchemaManagerService,
|
||||||
|
) {
|
||||||
|
super();
|
||||||
|
}
|
||||||
|
|
||||||
|
async executeForMetadata(
|
||||||
|
context: WorkspaceMigrationActionRunnerArgs<CreateFieldAction>,
|
||||||
|
): Promise<void> {
|
||||||
|
const { action, queryRunner } = context;
|
||||||
|
const fieldMetadataRepository =
|
||||||
|
queryRunner.manager.getRepository<FieldMetadataEntity>(
|
||||||
|
FieldMetadataEntity,
|
||||||
|
);
|
||||||
|
|
||||||
|
const { flatFieldMetadata } = action;
|
||||||
|
|
||||||
|
await fieldMetadataRepository.save(flatFieldMetadata);
|
||||||
|
}
|
||||||
|
|
||||||
|
async executeForWorkspaceSchema(
|
||||||
|
context: WorkspaceMigrationActionRunnerArgs<CreateFieldAction>,
|
||||||
|
): Promise<void> {
|
||||||
|
const { action, queryRunner, flatObjectMetadataMaps, workspaceId } =
|
||||||
|
context;
|
||||||
|
const { flatFieldMetadata } = action;
|
||||||
|
|
||||||
|
const flatObjectMetadataWithFlatFieldMaps =
|
||||||
|
findFlatObjectMetadataWithFlatFieldMapsInFlatObjectMetadataMapsOrThrow({
|
||||||
|
flatObjectMetadataMaps,
|
||||||
|
objectMetadataId: flatFieldMetadata.objectMetadataId,
|
||||||
|
});
|
||||||
|
|
||||||
|
const { schemaName, tableName } = getWorkspaceSchemaContextForMigration({
|
||||||
|
workspaceId,
|
||||||
|
flatObjectMetadata: flatObjectMetadataWithFlatFieldMaps,
|
||||||
|
});
|
||||||
|
|
||||||
|
const enumOperations = collectEnumOperationsForField({
|
||||||
|
flatFieldMetadata: flatFieldMetadata,
|
||||||
|
tableName,
|
||||||
|
operation: EnumOperation.CREATE,
|
||||||
|
});
|
||||||
|
|
||||||
|
await executeBatchEnumOperations({
|
||||||
|
enumOperations,
|
||||||
|
queryRunner,
|
||||||
|
schemaName,
|
||||||
|
workspaceSchemaManagerService: this.workspaceSchemaManagerService,
|
||||||
|
});
|
||||||
|
|
||||||
|
const columnDefinitions = generateColumnDefinitions({
|
||||||
|
flatFieldMetadata: flatFieldMetadata,
|
||||||
|
flatObjectMetadataWithoutFields: flatObjectMetadataWithFlatFieldMaps,
|
||||||
|
});
|
||||||
|
|
||||||
|
await this.workspaceSchemaManagerService.columnManager.addColumns({
|
||||||
|
queryRunner,
|
||||||
|
schemaName,
|
||||||
|
tableName,
|
||||||
|
columnDefinitions,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
+104
@@ -0,0 +1,104 @@
|
|||||||
|
import { Injectable } from '@nestjs/common';
|
||||||
|
|
||||||
|
import { In } from 'typeorm';
|
||||||
|
|
||||||
|
import { WorkspaceMigrationRunnerActionHandler } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/interfaces/workspace-migration-runner-action-handler-service.interface';
|
||||||
|
|
||||||
|
import { FieldMetadataEntity } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity';
|
||||||
|
import { findFlatFieldMetadataInFlatObjectMetadataMapsOrThrow } from 'src/engine/metadata-modules/flat-object-metadata-maps/utils/find-flat-field-metadata-in-flat-object-metadata-maps-or-throw.util';
|
||||||
|
import { findFlatObjectMetadataInFlatObjectMetadataMapsOrThrow } from 'src/engine/metadata-modules/flat-object-metadata-maps/utils/find-flat-object-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 { type DeleteFieldAction } 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';
|
||||||
|
import { getWorkspaceSchemaContextForMigration } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/utils/get-workspace-schema-context-for-migration.util';
|
||||||
|
import {
|
||||||
|
collectEnumOperationsForField,
|
||||||
|
EnumOperation,
|
||||||
|
executeBatchEnumOperations,
|
||||||
|
} from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/utils/workspace-schema-enum-operations.util';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class DeleteFieldActionHandlerService extends WorkspaceMigrationRunnerActionHandler(
|
||||||
|
'delete_field',
|
||||||
|
) {
|
||||||
|
constructor(
|
||||||
|
private readonly workspaceSchemaManagerService: WorkspaceSchemaManagerService,
|
||||||
|
) {
|
||||||
|
super();
|
||||||
|
}
|
||||||
|
|
||||||
|
async executeForMetadata(
|
||||||
|
context: WorkspaceMigrationActionRunnerArgs<DeleteFieldAction>,
|
||||||
|
) {
|
||||||
|
const { action, queryRunner } = context;
|
||||||
|
const fieldMetadataRepository =
|
||||||
|
queryRunner.manager.getRepository<FieldMetadataEntity>(
|
||||||
|
FieldMetadataEntity,
|
||||||
|
);
|
||||||
|
|
||||||
|
const { fieldMetadataId } = action;
|
||||||
|
|
||||||
|
await fieldMetadataRepository.delete({
|
||||||
|
id: In([fieldMetadataId]),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async executeForWorkspaceSchema(
|
||||||
|
context: WorkspaceMigrationActionRunnerArgs<DeleteFieldAction>,
|
||||||
|
) {
|
||||||
|
const { action, queryRunner, flatObjectMetadataMaps, workspaceId } =
|
||||||
|
context;
|
||||||
|
const { objectMetadataId, fieldMetadataId } = action;
|
||||||
|
|
||||||
|
const flatObjectMetadataWithFlatFieldMaps =
|
||||||
|
findFlatObjectMetadataWithFlatFieldMapsInFlatObjectMetadataMapsOrThrow({
|
||||||
|
flatObjectMetadataMaps,
|
||||||
|
objectMetadataId,
|
||||||
|
});
|
||||||
|
|
||||||
|
const fieldMetadata = findFlatFieldMetadataInFlatObjectMetadataMapsOrThrow({
|
||||||
|
flatObjectMetadataMaps,
|
||||||
|
objectMetadataId,
|
||||||
|
fieldMetadataId,
|
||||||
|
});
|
||||||
|
|
||||||
|
const { schemaName, tableName } = getWorkspaceSchemaContextForMigration({
|
||||||
|
workspaceId,
|
||||||
|
flatObjectMetadata: flatObjectMetadataWithFlatFieldMaps,
|
||||||
|
});
|
||||||
|
|
||||||
|
const flatObjectMetadata =
|
||||||
|
findFlatObjectMetadataInFlatObjectMetadataMapsOrThrow({
|
||||||
|
flatObjectMetadataMaps,
|
||||||
|
objectMetadataId,
|
||||||
|
});
|
||||||
|
|
||||||
|
const columnDefinitions = generateColumnDefinitions({
|
||||||
|
flatFieldMetadata: fieldMetadata,
|
||||||
|
flatObjectMetadataWithoutFields: flatObjectMetadata,
|
||||||
|
});
|
||||||
|
const columnNamesToDrop = columnDefinitions.map((def) => def.name);
|
||||||
|
|
||||||
|
await this.workspaceSchemaManagerService.columnManager.dropColumns({
|
||||||
|
queryRunner,
|
||||||
|
schemaName,
|
||||||
|
tableName,
|
||||||
|
columnNames: columnNamesToDrop,
|
||||||
|
});
|
||||||
|
|
||||||
|
const enumOperations = collectEnumOperationsForField({
|
||||||
|
flatFieldMetadata: fieldMetadata,
|
||||||
|
tableName,
|
||||||
|
operation: EnumOperation.DROP,
|
||||||
|
});
|
||||||
|
|
||||||
|
await executeBatchEnumOperations({
|
||||||
|
enumOperations,
|
||||||
|
queryRunner,
|
||||||
|
schemaName,
|
||||||
|
workspaceSchemaManagerService: this.workspaceSchemaManagerService,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
+70
-138
@@ -1,16 +1,17 @@
|
|||||||
import { Injectable } from '@nestjs/common';
|
import { Injectable } from '@nestjs/common';
|
||||||
|
|
||||||
import { type FromTo } from 'twenty-shared/types';
|
import { type EnumFieldMetadataType, type FromTo } from 'twenty-shared/types';
|
||||||
import { isDefined } from 'twenty-shared/utils';
|
import { isDefined } from 'twenty-shared/utils';
|
||||||
import { type QueryRunner } from 'typeorm';
|
import { type QueryRunner } from 'typeorm';
|
||||||
|
|
||||||
import { type FieldMetadataDefaultValueForAnyType } from 'src/engine/metadata-modules/field-metadata/interfaces/field-metadata-default-value.interface';
|
import { type FieldMetadataDefaultValueForAnyType } from 'src/engine/metadata-modules/field-metadata/interfaces/field-metadata-default-value.interface';
|
||||||
|
import { WorkspaceMigrationRunnerActionHandler } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/interfaces/workspace-migration-runner-action-handler-service.interface';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
FieldMetadataDefaultOption,
|
|
||||||
type FieldMetadataComplexOption,
|
type FieldMetadataComplexOption,
|
||||||
|
type FieldMetadataDefaultOption,
|
||||||
} from 'src/engine/metadata-modules/field-metadata/dtos/options.input';
|
} from 'src/engine/metadata-modules/field-metadata/dtos/options.input';
|
||||||
import { EnumFieldMetadataType } from 'src/engine/metadata-modules/field-metadata/types/enum-field-metadata-type.type';
|
import { FieldMetadataEntity } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity';
|
||||||
import { computeCompositeColumnName } from 'src/engine/metadata-modules/field-metadata/utils/compute-column-name.util';
|
import { computeCompositeColumnName } from 'src/engine/metadata-modules/field-metadata/utils/compute-column-name.util';
|
||||||
import { getCompositeTypeOrThrow } from 'src/engine/metadata-modules/field-metadata/utils/get-composite-type-or-throw.util';
|
import { getCompositeTypeOrThrow } from 'src/engine/metadata-modules/field-metadata/utils/get-composite-type-or-throw.util';
|
||||||
import { isCompositeFieldMetadataType } from 'src/engine/metadata-modules/field-metadata/utils/is-composite-field-metadata-type.util';
|
import { isCompositeFieldMetadataType } from 'src/engine/metadata-modules/field-metadata/utils/is-composite-field-metadata-type.util';
|
||||||
@@ -19,151 +20,84 @@ import { type FlatFieldMetadata } from 'src/engine/metadata-modules/flat-field-m
|
|||||||
import { isCompositeFlatFieldMetadata } from 'src/engine/metadata-modules/flat-field-metadata/utils/is-composite-flat-field-metadata.util';
|
import { isCompositeFlatFieldMetadata } from 'src/engine/metadata-modules/flat-field-metadata/utils/is-composite-flat-field-metadata.util';
|
||||||
import { isEnumFlatFieldMetadata } from 'src/engine/metadata-modules/flat-field-metadata/utils/is-enum-flat-field-metadata.util';
|
import { isEnumFlatFieldMetadata } from 'src/engine/metadata-modules/flat-field-metadata/utils/is-enum-flat-field-metadata.util';
|
||||||
import { isRelationFlatFieldMetadata } from 'src/engine/metadata-modules/flat-field-metadata/utils/is-relation-flat-field-metadata.util';
|
import { isRelationFlatFieldMetadata } from 'src/engine/metadata-modules/flat-field-metadata/utils/is-relation-flat-field-metadata.util';
|
||||||
import { findFlatObjectMetadataInFlatObjectMetadataMapsOrThrow } from 'src/engine/metadata-modules/flat-object-metadata-maps/utils/find-flat-object-metadata-in-flat-object-metadata-maps-or-throw.util';
|
import { findFlatFieldMetadataInFlatObjectMetadataMapsOrThrow } from 'src/engine/metadata-modules/flat-object-metadata-maps/utils/find-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 { type FlatObjectMetadata } from 'src/engine/metadata-modules/flat-object-metadata/types/flat-object-metadata.type';
|
import { type FlatObjectMetadata } from 'src/engine/metadata-modules/flat-object-metadata/types/flat-object-metadata.type';
|
||||||
import { WorkspaceSchemaManagerService } from 'src/engine/twenty-orm/workspace-schema-manager/workspace-schema-manager.service';
|
import { WorkspaceSchemaManagerService } from 'src/engine/twenty-orm/workspace-schema-manager/workspace-schema-manager.service';
|
||||||
import { isRelationFieldMetadataType } from 'src/engine/utils/is-relation-field-metadata-type.util';
|
import { isRelationFieldMetadataType } from 'src/engine/utils/is-relation-field-metadata-type.util';
|
||||||
|
import { type UpdateFieldAction } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/types/workspace-migration-field-action-v2';
|
||||||
import {
|
import {
|
||||||
type CreateFieldAction,
|
WorkspaceMigrationRunnerException,
|
||||||
type DeleteFieldAction,
|
WorkspaceMigrationRunnerExceptionCode,
|
||||||
type UpdateFieldAction,
|
} from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/exceptions/workspace-migration-runner.exception';
|
||||||
type WorkspaceMigrationFieldActionTypeV2,
|
|
||||||
} from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/types/workspace-migration-field-action-v2';
|
|
||||||
import { type RunnerMethodForActionType } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/types/runner-method-for-action-type';
|
|
||||||
import { type WorkspaceMigrationActionRunnerArgs } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/types/workspace-migration-action-runner-args.type';
|
import { type WorkspaceMigrationActionRunnerArgs } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/types/workspace-migration-action-runner-args.type';
|
||||||
import {
|
import { fromWorkspaceMigrationUpdateActionToPartialEntity } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/utils/from-workspace-migration-update-action-to-partial-field-or-object-entity.util';
|
||||||
WorkspaceSchemaMigrationException,
|
import { generateColumnDefinitions } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/utils/generate-column-definitions.util';
|
||||||
WorkspaceSchemaMigrationExceptionCode,
|
import { getWorkspaceSchemaContextForMigration } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/utils/get-workspace-schema-context-for-migration.util';
|
||||||
} from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/workspace-schema-migration-runner/exceptions/workspace-schema-migration.exception';
|
|
||||||
import { generateColumnDefinitions } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/workspace-schema-migration-runner/utils/generate-column-definitions.util';
|
|
||||||
|
|
||||||
import {
|
|
||||||
prepareFieldWorkspaceSchemaContext,
|
|
||||||
prepareWorkspaceSchemaContext,
|
|
||||||
} from './utils/workspace-schema-context.util';
|
|
||||||
import {
|
import {
|
||||||
collectEnumOperationsForField,
|
collectEnumOperationsForField,
|
||||||
EnumOperation,
|
EnumOperation,
|
||||||
executeBatchEnumOperations,
|
executeBatchEnumOperations,
|
||||||
} from './utils/workspace-schema-enum-operations.util';
|
} from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/utils/workspace-schema-enum-operations.util';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class WorkspaceSchemaFieldActionRunnerService
|
export class UpdateFieldActionHandlerService extends WorkspaceMigrationRunnerActionHandler(
|
||||||
implements
|
'update_field',
|
||||||
RunnerMethodForActionType<WorkspaceMigrationFieldActionTypeV2, 'schema'>
|
) {
|
||||||
{
|
|
||||||
constructor(
|
constructor(
|
||||||
private readonly workspaceSchemaManagerService: WorkspaceSchemaManagerService,
|
private readonly workspaceSchemaManagerService: WorkspaceSchemaManagerService,
|
||||||
) {}
|
) {
|
||||||
|
super();
|
||||||
|
}
|
||||||
|
|
||||||
runDeleteFieldSchemaMigration = async ({
|
async executeForMetadata(
|
||||||
action,
|
context: WorkspaceMigrationActionRunnerArgs<UpdateFieldAction>,
|
||||||
queryRunner,
|
): Promise<void> {
|
||||||
flatObjectMetadataMaps,
|
const { action, queryRunner } = context;
|
||||||
}: WorkspaceMigrationActionRunnerArgs<DeleteFieldAction>) => {
|
const fieldMetadataRepository =
|
||||||
const { objectMetadataId, fieldMetadataId } = action;
|
queryRunner.manager.getRepository<FieldMetadataEntity>(
|
||||||
const { schemaName, tableName, fieldMetadata } =
|
FieldMetadataEntity,
|
||||||
prepareFieldWorkspaceSchemaContext({
|
);
|
||||||
|
|
||||||
|
const { fieldMetadataId } = action;
|
||||||
|
|
||||||
|
await fieldMetadataRepository.update(
|
||||||
|
fieldMetadataId,
|
||||||
|
fromWorkspaceMigrationUpdateActionToPartialEntity(action),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
async executeForWorkspaceSchema(
|
||||||
|
context: WorkspaceMigrationActionRunnerArgs<UpdateFieldAction>,
|
||||||
|
): Promise<void> {
|
||||||
|
const { action, queryRunner, flatObjectMetadataMaps, workspaceId } =
|
||||||
|
context;
|
||||||
|
const { objectMetadataId, fieldMetadataId, updates } = action;
|
||||||
|
|
||||||
|
const flatObjectMetadataWithFlatFieldMaps =
|
||||||
|
findFlatObjectMetadataWithFlatFieldMapsInFlatObjectMetadataMapsOrThrow({
|
||||||
|
flatObjectMetadataMaps,
|
||||||
|
objectMetadataId,
|
||||||
|
});
|
||||||
|
|
||||||
|
const { schemaName, tableName } = getWorkspaceSchemaContextForMigration({
|
||||||
|
workspaceId,
|
||||||
|
flatObjectMetadata: flatObjectMetadataWithFlatFieldMaps,
|
||||||
|
});
|
||||||
|
|
||||||
|
const flatObjectMetadata =
|
||||||
|
findFlatObjectMetadataWithFlatFieldMapsInFlatObjectMetadataMapsOrThrow({
|
||||||
|
flatObjectMetadataMaps,
|
||||||
|
objectMetadataId,
|
||||||
|
});
|
||||||
|
|
||||||
|
const currentFlatFieldMetadata =
|
||||||
|
findFlatFieldMetadataInFlatObjectMetadataMapsOrThrow({
|
||||||
flatObjectMetadataMaps,
|
flatObjectMetadataMaps,
|
||||||
objectMetadataId,
|
objectMetadataId,
|
||||||
fieldMetadataId,
|
fieldMetadataId,
|
||||||
});
|
});
|
||||||
|
|
||||||
const flatObjectMetadata =
|
|
||||||
findFlatObjectMetadataInFlatObjectMetadataMapsOrThrow({
|
|
||||||
flatObjectMetadataMaps,
|
|
||||||
objectMetadataId,
|
|
||||||
});
|
|
||||||
|
|
||||||
const columnDefinitions = generateColumnDefinitions({
|
|
||||||
flatFieldMetadata: fieldMetadata,
|
|
||||||
flatObjectMetadataWithoutFields: flatObjectMetadata,
|
|
||||||
});
|
|
||||||
const columnNamesToDrop = columnDefinitions.map((def) => def.name);
|
|
||||||
|
|
||||||
await this.workspaceSchemaManagerService.columnManager.dropColumns({
|
|
||||||
queryRunner,
|
|
||||||
schemaName,
|
|
||||||
tableName,
|
|
||||||
columnNames: columnNamesToDrop,
|
|
||||||
});
|
|
||||||
|
|
||||||
const enumOperations = collectEnumOperationsForField({
|
|
||||||
flatFieldMetadata: fieldMetadata,
|
|
||||||
tableName,
|
|
||||||
operation: EnumOperation.DROP,
|
|
||||||
});
|
|
||||||
|
|
||||||
await executeBatchEnumOperations({
|
|
||||||
enumOperations,
|
|
||||||
queryRunner,
|
|
||||||
schemaName,
|
|
||||||
workspaceSchemaManagerService: this.workspaceSchemaManagerService,
|
|
||||||
});
|
|
||||||
|
|
||||||
return;
|
|
||||||
};
|
|
||||||
|
|
||||||
runCreateFieldSchemaMigration = async ({
|
|
||||||
action,
|
|
||||||
queryRunner,
|
|
||||||
flatObjectMetadataMaps,
|
|
||||||
}: WorkspaceMigrationActionRunnerArgs<CreateFieldAction>) => {
|
|
||||||
const { flatFieldMetadata } = action;
|
|
||||||
const {
|
|
||||||
schemaName,
|
|
||||||
tableName,
|
|
||||||
flatObjectMetadataWithFlatFieldMaps: flatObjectMetadata,
|
|
||||||
} = prepareWorkspaceSchemaContext({
|
|
||||||
flatObjectMetadataMaps,
|
|
||||||
objectMetadataId: flatFieldMetadata.objectMetadataId,
|
|
||||||
});
|
|
||||||
|
|
||||||
const enumOperations = collectEnumOperationsForField({
|
|
||||||
flatFieldMetadata: flatFieldMetadata,
|
|
||||||
tableName,
|
|
||||||
operation: EnumOperation.CREATE,
|
|
||||||
});
|
|
||||||
|
|
||||||
await executeBatchEnumOperations({
|
|
||||||
enumOperations,
|
|
||||||
queryRunner,
|
|
||||||
schemaName,
|
|
||||||
workspaceSchemaManagerService: this.workspaceSchemaManagerService,
|
|
||||||
});
|
|
||||||
|
|
||||||
const columnDefinitions = generateColumnDefinitions({
|
|
||||||
flatFieldMetadata: flatFieldMetadata,
|
|
||||||
flatObjectMetadataWithoutFields: flatObjectMetadata,
|
|
||||||
});
|
|
||||||
|
|
||||||
await this.workspaceSchemaManagerService.columnManager.addColumns({
|
|
||||||
queryRunner,
|
|
||||||
schemaName,
|
|
||||||
tableName,
|
|
||||||
columnDefinitions,
|
|
||||||
});
|
|
||||||
|
|
||||||
return;
|
|
||||||
};
|
|
||||||
|
|
||||||
runUpdateFieldSchemaMigration = async ({
|
|
||||||
action,
|
|
||||||
queryRunner,
|
|
||||||
flatObjectMetadataMaps,
|
|
||||||
}: WorkspaceMigrationActionRunnerArgs<UpdateFieldAction>) => {
|
|
||||||
const { objectMetadataId, fieldMetadataId, updates } = action;
|
|
||||||
const {
|
|
||||||
schemaName,
|
|
||||||
tableName,
|
|
||||||
flatObjectMetadataWithFlatFieldMaps: flatObjectMetadata,
|
|
||||||
fieldMetadata: currentFlatFieldMetadata,
|
|
||||||
} = prepareFieldWorkspaceSchemaContext({
|
|
||||||
flatObjectMetadataMaps,
|
|
||||||
objectMetadataId,
|
|
||||||
fieldMetadataId,
|
|
||||||
});
|
|
||||||
|
|
||||||
let optimisticFlatFieldMetadata = structuredClone(currentFlatFieldMetadata);
|
let optimisticFlatFieldMetadata = structuredClone(currentFlatFieldMetadata);
|
||||||
|
|
||||||
for (const update of updates) {
|
for (const update of updates) {
|
||||||
@@ -202,9 +136,7 @@ export class WorkspaceSchemaFieldActionRunnerService
|
|||||||
optimisticFlatFieldMetadata.options = update.to ?? [];
|
optimisticFlatFieldMetadata.options = update.to ?? [];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
return;
|
|
||||||
};
|
|
||||||
|
|
||||||
private async handleFieldNameUpdate(
|
private async handleFieldNameUpdate(
|
||||||
queryRunner: QueryRunner,
|
queryRunner: QueryRunner,
|
||||||
@@ -220,9 +152,9 @@ export class WorkspaceSchemaFieldActionRunnerService
|
|||||||
|
|
||||||
for (const property of compositeType.properties) {
|
for (const property of compositeType.properties) {
|
||||||
if (isRelationFieldMetadataType(property.type)) {
|
if (isRelationFieldMetadataType(property.type)) {
|
||||||
throw new WorkspaceSchemaMigrationException(
|
throw new WorkspaceMigrationRunnerException(
|
||||||
'Relation field metadata in composite type is not supported yet',
|
'Relation field metadata in composite type is not supported yet',
|
||||||
WorkspaceSchemaMigrationExceptionCode.NOT_SUPPORTED,
|
WorkspaceMigrationRunnerExceptionCode.NOT_SUPPORTED,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -245,9 +177,9 @@ export class WorkspaceSchemaFieldActionRunnerService
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (isRelationFlatFieldMetadata(flatFieldMetadata)) {
|
if (isRelationFlatFieldMetadata(flatFieldMetadata)) {
|
||||||
throw new WorkspaceSchemaMigrationException(
|
throw new WorkspaceMigrationRunnerException(
|
||||||
'Relation field metadata name update is not supported yet',
|
'Relation field metadata name update is not supported yet',
|
||||||
WorkspaceSchemaMigrationExceptionCode.NOT_SUPPORTED,
|
WorkspaceMigrationRunnerExceptionCode.NOT_SUPPORTED,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
await this.workspaceSchemaManagerService.columnManager.renameColumn({
|
await this.workspaceSchemaManagerService.columnManager.renameColumn({
|
||||||
@@ -290,9 +222,9 @@ export class WorkspaceSchemaFieldActionRunnerService
|
|||||||
|
|
||||||
for (const property of compositeType.properties) {
|
for (const property of compositeType.properties) {
|
||||||
if (isRelationFieldMetadataType(property.type)) {
|
if (isRelationFieldMetadataType(property.type)) {
|
||||||
throw new WorkspaceSchemaMigrationException(
|
throw new WorkspaceMigrationRunnerException(
|
||||||
'Relation field metadata in composite type is not supported yet',
|
'Relation field metadata in composite type is not supported yet',
|
||||||
WorkspaceSchemaMigrationExceptionCode.NOT_SUPPORTED,
|
WorkspaceMigrationRunnerExceptionCode.NOT_SUPPORTED,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
+23
@@ -0,0 +1,23 @@
|
|||||||
|
import { Injectable } from '@nestjs/common';
|
||||||
|
|
||||||
|
import { WorkspaceMigrationRunnerActionHandler } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/interfaces/workspace-migration-runner-action-handler-service.interface';
|
||||||
|
|
||||||
|
import { type CreateIndexAction } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/types/workspace-migration-index-action-v2';
|
||||||
|
import { type WorkspaceMigrationActionRunnerArgs } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/types/workspace-migration-action-runner-args.type';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class CreateIndexActionHandlerService extends WorkspaceMigrationRunnerActionHandler(
|
||||||
|
'create_index',
|
||||||
|
) {
|
||||||
|
async executeForMetadata(
|
||||||
|
_context: WorkspaceMigrationActionRunnerArgs<CreateIndexAction>,
|
||||||
|
): Promise<void> {
|
||||||
|
throw new Error('Not implemented');
|
||||||
|
}
|
||||||
|
|
||||||
|
async executeForWorkspaceSchema(
|
||||||
|
_context: WorkspaceMigrationActionRunnerArgs<CreateIndexAction>,
|
||||||
|
): Promise<void> {
|
||||||
|
throw new Error('Not implemented');
|
||||||
|
}
|
||||||
|
}
|
||||||
+23
@@ -0,0 +1,23 @@
|
|||||||
|
import { Injectable } from '@nestjs/common';
|
||||||
|
|
||||||
|
import { WorkspaceMigrationRunnerActionHandler } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/interfaces/workspace-migration-runner-action-handler-service.interface';
|
||||||
|
|
||||||
|
import { type DeleteIndexAction } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/types/workspace-migration-index-action-v2';
|
||||||
|
import { type WorkspaceMigrationActionRunnerArgs } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/types/workspace-migration-action-runner-args.type';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class DeleteIndexActionHandlerService extends WorkspaceMigrationRunnerActionHandler(
|
||||||
|
'delete_index',
|
||||||
|
) {
|
||||||
|
async executeForMetadata(
|
||||||
|
_context: WorkspaceMigrationActionRunnerArgs<DeleteIndexAction>,
|
||||||
|
): Promise<void> {
|
||||||
|
throw new Error('Not implemented');
|
||||||
|
}
|
||||||
|
|
||||||
|
async executeForWorkspaceSchema(
|
||||||
|
_context: WorkspaceMigrationActionRunnerArgs<DeleteIndexAction>,
|
||||||
|
): Promise<void> {
|
||||||
|
throw new Error('Not implemented');
|
||||||
|
}
|
||||||
|
}
|
||||||
+119
@@ -0,0 +1,119 @@
|
|||||||
|
import { Injectable } from '@nestjs/common';
|
||||||
|
|
||||||
|
import { WorkspaceMigrationRunnerActionHandler } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/interfaces/workspace-migration-runner-action-handler-service.interface';
|
||||||
|
|
||||||
|
import { DataSourceEntity } from 'src/engine/metadata-modules/data-source/data-source.entity';
|
||||||
|
import { FieldMetadataEntity } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity';
|
||||||
|
import { type FlatFieldMetadata } from 'src/engine/metadata-modules/flat-field-metadata/types/flat-field-metadata.type';
|
||||||
|
import { isCompositeFlatFieldMetadata } from 'src/engine/metadata-modules/flat-field-metadata/utils/is-composite-flat-field-metadata.util';
|
||||||
|
import { isEnumFlatFieldMetadata } from 'src/engine/metadata-modules/flat-field-metadata/utils/is-enum-flat-field-metadata.util';
|
||||||
|
import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
|
||||||
|
import { WorkspaceSchemaManagerService } from 'src/engine/twenty-orm/workspace-schema-manager/workspace-schema-manager.service';
|
||||||
|
import { type CreateObjectAction } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/types/workspace-migration-object-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';
|
||||||
|
import { getWorkspaceSchemaContextForMigration } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/utils/get-workspace-schema-context-for-migration.util';
|
||||||
|
import {
|
||||||
|
collectEnumOperationsForObject,
|
||||||
|
EnumOperation,
|
||||||
|
executeBatchEnumOperations,
|
||||||
|
} from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/utils/workspace-schema-enum-operations.util';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class CreateObjectActionHandlerService extends WorkspaceMigrationRunnerActionHandler(
|
||||||
|
'create_object',
|
||||||
|
) {
|
||||||
|
constructor(
|
||||||
|
private readonly workspaceSchemaManagerService: WorkspaceSchemaManagerService,
|
||||||
|
) {
|
||||||
|
super();
|
||||||
|
}
|
||||||
|
|
||||||
|
async executeForMetadata(
|
||||||
|
context: WorkspaceMigrationActionRunnerArgs<CreateObjectAction>,
|
||||||
|
): Promise<void> {
|
||||||
|
const { action, queryRunner } = context;
|
||||||
|
const { flatObjectMetadataWithoutFields, createFieldActions } = action;
|
||||||
|
|
||||||
|
const objectMetadataRepository =
|
||||||
|
queryRunner.manager.getRepository<ObjectMetadataEntity>(
|
||||||
|
ObjectMetadataEntity,
|
||||||
|
);
|
||||||
|
|
||||||
|
const dataSourceRepository =
|
||||||
|
queryRunner.manager.getRepository<DataSourceEntity>(DataSourceEntity);
|
||||||
|
|
||||||
|
const lastDataSourceMetadata = await dataSourceRepository.findOneOrFail({
|
||||||
|
where: {
|
||||||
|
workspaceId: flatObjectMetadataWithoutFields.workspaceId,
|
||||||
|
},
|
||||||
|
order: { createdAt: 'DESC' },
|
||||||
|
});
|
||||||
|
|
||||||
|
await objectMetadataRepository.save({
|
||||||
|
...flatObjectMetadataWithoutFields,
|
||||||
|
dataSourceId: lastDataSourceMetadata.id,
|
||||||
|
targetTableName: 'DEPRECATED',
|
||||||
|
});
|
||||||
|
|
||||||
|
const fieldMetadataRepository =
|
||||||
|
queryRunner.manager.getRepository<FieldMetadataEntity>(
|
||||||
|
FieldMetadataEntity,
|
||||||
|
);
|
||||||
|
|
||||||
|
for (const createFieldAction of createFieldActions) {
|
||||||
|
const { flatFieldMetadata } = createFieldAction;
|
||||||
|
|
||||||
|
await fieldMetadataRepository.save(flatFieldMetadata);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async executeForWorkspaceSchema(
|
||||||
|
context: WorkspaceMigrationActionRunnerArgs<CreateObjectAction>,
|
||||||
|
): Promise<void> {
|
||||||
|
const { action, queryRunner, workspaceId } = context;
|
||||||
|
const { flatObjectMetadataWithoutFields, createFieldActions } = action;
|
||||||
|
|
||||||
|
const flatObjectMetadata = flatObjectMetadataWithoutFields;
|
||||||
|
|
||||||
|
const { schemaName, tableName } = getWorkspaceSchemaContextForMigration({
|
||||||
|
workspaceId,
|
||||||
|
flatObjectMetadata: flatObjectMetadata,
|
||||||
|
});
|
||||||
|
|
||||||
|
const columnDefinitions = createFieldActions.flatMap((createFieldAction) =>
|
||||||
|
generateColumnDefinitions({
|
||||||
|
flatFieldMetadata: createFieldAction.flatFieldMetadata,
|
||||||
|
flatObjectMetadataWithoutFields: flatObjectMetadataWithoutFields,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
const enumOrCompositeFlatFieldMetadatas = createFieldActions
|
||||||
|
.map((createFieldAction) => createFieldAction.flatFieldMetadata)
|
||||||
|
.filter((field): field is FlatFieldMetadata => field != null)
|
||||||
|
.filter(
|
||||||
|
(field) =>
|
||||||
|
isEnumFlatFieldMetadata(field) || isCompositeFlatFieldMetadata(field),
|
||||||
|
);
|
||||||
|
|
||||||
|
const enumOperations = collectEnumOperationsForObject({
|
||||||
|
flatFieldMetadatas: enumOrCompositeFlatFieldMetadatas,
|
||||||
|
tableName,
|
||||||
|
operation: EnumOperation.CREATE,
|
||||||
|
});
|
||||||
|
|
||||||
|
await executeBatchEnumOperations({
|
||||||
|
enumOperations,
|
||||||
|
queryRunner,
|
||||||
|
schemaName,
|
||||||
|
workspaceSchemaManagerService: this.workspaceSchemaManagerService,
|
||||||
|
});
|
||||||
|
|
||||||
|
await this.workspaceSchemaManagerService.tableManager.createTable({
|
||||||
|
queryRunner,
|
||||||
|
schemaName,
|
||||||
|
tableName,
|
||||||
|
columnDefinitions,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
+90
@@ -0,0 +1,90 @@
|
|||||||
|
import { Injectable } from '@nestjs/common';
|
||||||
|
|
||||||
|
import { WorkspaceMigrationRunnerActionHandler } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/interfaces/workspace-migration-runner-action-handler-service.interface';
|
||||||
|
|
||||||
|
import { type FlatFieldMetadata } from 'src/engine/metadata-modules/flat-field-metadata/types/flat-field-metadata.type';
|
||||||
|
import { isCompositeFlatFieldMetadata } from 'src/engine/metadata-modules/flat-field-metadata/utils/is-composite-flat-field-metadata.util';
|
||||||
|
import { isEnumFlatFieldMetadata } from 'src/engine/metadata-modules/flat-field-metadata/utils/is-enum-flat-field-metadata.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 { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
|
||||||
|
import { WorkspaceSchemaManagerService } from 'src/engine/twenty-orm/workspace-schema-manager/workspace-schema-manager.service';
|
||||||
|
import { type DeleteObjectAction } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/types/workspace-migration-object-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 { getWorkspaceSchemaContextForMigration } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/utils/get-workspace-schema-context-for-migration.util';
|
||||||
|
import {
|
||||||
|
collectEnumOperationsForObject,
|
||||||
|
EnumOperation,
|
||||||
|
executeBatchEnumOperations,
|
||||||
|
} from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/utils/workspace-schema-enum-operations.util';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class DeleteObjectActionHandlerService extends WorkspaceMigrationRunnerActionHandler(
|
||||||
|
'delete_object',
|
||||||
|
) {
|
||||||
|
constructor(
|
||||||
|
private readonly workspaceSchemaManagerService: WorkspaceSchemaManagerService,
|
||||||
|
) {
|
||||||
|
super();
|
||||||
|
}
|
||||||
|
|
||||||
|
async executeForMetadata(
|
||||||
|
context: WorkspaceMigrationActionRunnerArgs<DeleteObjectAction>,
|
||||||
|
): Promise<void> {
|
||||||
|
const { action, queryRunner } = context;
|
||||||
|
const { objectMetadataId } = action;
|
||||||
|
|
||||||
|
const objectMetadataRepository =
|
||||||
|
queryRunner.manager.getRepository<ObjectMetadataEntity>(
|
||||||
|
ObjectMetadataEntity,
|
||||||
|
);
|
||||||
|
|
||||||
|
await objectMetadataRepository.delete(objectMetadataId);
|
||||||
|
}
|
||||||
|
|
||||||
|
async executeForWorkspaceSchema(
|
||||||
|
context: WorkspaceMigrationActionRunnerArgs<DeleteObjectAction>,
|
||||||
|
): Promise<void> {
|
||||||
|
const { action, queryRunner, flatObjectMetadataMaps, workspaceId } =
|
||||||
|
context;
|
||||||
|
const { objectMetadataId } = action;
|
||||||
|
|
||||||
|
const flatObjectMetadataWithFlatFieldMaps =
|
||||||
|
findFlatObjectMetadataWithFlatFieldMapsInFlatObjectMetadataMapsOrThrow({
|
||||||
|
flatObjectMetadataMaps,
|
||||||
|
objectMetadataId,
|
||||||
|
});
|
||||||
|
|
||||||
|
const { schemaName, tableName } = getWorkspaceSchemaContextForMigration({
|
||||||
|
workspaceId,
|
||||||
|
flatObjectMetadata: flatObjectMetadataWithFlatFieldMaps,
|
||||||
|
});
|
||||||
|
|
||||||
|
await this.workspaceSchemaManagerService.tableManager.dropTable({
|
||||||
|
queryRunner,
|
||||||
|
schemaName,
|
||||||
|
tableName,
|
||||||
|
});
|
||||||
|
|
||||||
|
const enumOrCompositeFlatFieldMetadatas = Object.values(
|
||||||
|
flatObjectMetadataWithFlatFieldMaps.fieldsById,
|
||||||
|
)
|
||||||
|
.filter((field): field is FlatFieldMetadata => field != null)
|
||||||
|
.filter(
|
||||||
|
(field) =>
|
||||||
|
isEnumFlatFieldMetadata(field) || isCompositeFlatFieldMetadata(field),
|
||||||
|
);
|
||||||
|
|
||||||
|
const enumOperations = collectEnumOperationsForObject({
|
||||||
|
flatFieldMetadatas: enumOrCompositeFlatFieldMetadatas,
|
||||||
|
tableName,
|
||||||
|
operation: EnumOperation.DROP,
|
||||||
|
});
|
||||||
|
|
||||||
|
await executeBatchEnumOperations({
|
||||||
|
enumOperations,
|
||||||
|
queryRunner,
|
||||||
|
schemaName,
|
||||||
|
workspaceSchemaManagerService: this.workspaceSchemaManagerService,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
+115
@@ -0,0 +1,115 @@
|
|||||||
|
import { Injectable } from '@nestjs/common';
|
||||||
|
|
||||||
|
import { WorkspaceMigrationRunnerActionHandler } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/interfaces/workspace-migration-runner-action-handler-service.interface';
|
||||||
|
|
||||||
|
import { type FlatFieldMetadata } from 'src/engine/metadata-modules/flat-field-metadata/types/flat-field-metadata.type';
|
||||||
|
import { isCompositeFlatFieldMetadata } from 'src/engine/metadata-modules/flat-field-metadata/utils/is-composite-flat-field-metadata.util';
|
||||||
|
import { isEnumFlatFieldMetadata } from 'src/engine/metadata-modules/flat-field-metadata/utils/is-enum-flat-field-metadata.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 { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
|
||||||
|
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 { type UpdateObjectAction } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/types/workspace-migration-object-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 { fromWorkspaceMigrationUpdateActionToPartialEntity } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/utils/from-workspace-migration-update-action-to-partial-field-or-object-entity.util';
|
||||||
|
import { getWorkspaceSchemaContextForMigration } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/utils/get-workspace-schema-context-for-migration.util';
|
||||||
|
import {
|
||||||
|
collectEnumOperationsForObject,
|
||||||
|
EnumOperation,
|
||||||
|
executeBatchEnumOperations,
|
||||||
|
} from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/utils/workspace-schema-enum-operations.util';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class UpdateObjectActionHandlerService extends WorkspaceMigrationRunnerActionHandler(
|
||||||
|
'update_object',
|
||||||
|
) {
|
||||||
|
constructor(
|
||||||
|
private readonly workspaceSchemaManagerService: WorkspaceSchemaManagerService,
|
||||||
|
) {
|
||||||
|
super();
|
||||||
|
}
|
||||||
|
|
||||||
|
async executeForMetadata(
|
||||||
|
context: WorkspaceMigrationActionRunnerArgs<UpdateObjectAction>,
|
||||||
|
): Promise<void> {
|
||||||
|
const { action, queryRunner } = context;
|
||||||
|
|
||||||
|
const objectMetadataRepository =
|
||||||
|
queryRunner.manager.getRepository<ObjectMetadataEntity>(
|
||||||
|
ObjectMetadataEntity,
|
||||||
|
);
|
||||||
|
|
||||||
|
await objectMetadataRepository.update(
|
||||||
|
action.objectMetadataId,
|
||||||
|
fromWorkspaceMigrationUpdateActionToPartialEntity(action),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
async executeForWorkspaceSchema(
|
||||||
|
context: WorkspaceMigrationActionRunnerArgs<UpdateObjectAction>,
|
||||||
|
): Promise<void> {
|
||||||
|
const { action, queryRunner, flatObjectMetadataMaps, workspaceId } =
|
||||||
|
context;
|
||||||
|
const { objectMetadataId, updates } = action;
|
||||||
|
|
||||||
|
const flatObjectMetadataWithFlatFieldMaps =
|
||||||
|
findFlatObjectMetadataWithFlatFieldMapsInFlatObjectMetadataMapsOrThrow({
|
||||||
|
flatObjectMetadataMaps,
|
||||||
|
objectMetadataId,
|
||||||
|
});
|
||||||
|
|
||||||
|
const { schemaName, tableName: currentTableName } =
|
||||||
|
getWorkspaceSchemaContextForMigration({
|
||||||
|
workspaceId,
|
||||||
|
flatObjectMetadata: flatObjectMetadataWithFlatFieldMaps,
|
||||||
|
});
|
||||||
|
|
||||||
|
for (const update of updates) {
|
||||||
|
if (update.property !== 'nameSingular') {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const updatedObjectMetadata = {
|
||||||
|
...flatObjectMetadataWithFlatFieldMaps,
|
||||||
|
[update.property]: update.to,
|
||||||
|
};
|
||||||
|
|
||||||
|
const newTableName = computeObjectTargetTable(updatedObjectMetadata);
|
||||||
|
|
||||||
|
if (currentTableName !== newTableName) {
|
||||||
|
await this.workspaceSchemaManagerService.tableManager.renameTable({
|
||||||
|
queryRunner,
|
||||||
|
schemaName,
|
||||||
|
oldTableName: currentTableName,
|
||||||
|
newTableName,
|
||||||
|
});
|
||||||
|
|
||||||
|
const enumOrCompositeFlatFieldMetadatas = Object.values(
|
||||||
|
flatObjectMetadataWithFlatFieldMaps.fieldsById,
|
||||||
|
)
|
||||||
|
.filter((field): field is FlatFieldMetadata => field != null)
|
||||||
|
.filter(
|
||||||
|
(field) =>
|
||||||
|
isEnumFlatFieldMetadata(field) ||
|
||||||
|
isCompositeFlatFieldMetadata(field),
|
||||||
|
);
|
||||||
|
|
||||||
|
const enumOperations = collectEnumOperationsForObject({
|
||||||
|
flatFieldMetadatas: enumOrCompositeFlatFieldMetadatas,
|
||||||
|
tableName: currentTableName,
|
||||||
|
operation: EnumOperation.RENAME,
|
||||||
|
options: {
|
||||||
|
newTableName,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
await executeBatchEnumOperations({
|
||||||
|
enumOperations,
|
||||||
|
queryRunner,
|
||||||
|
schemaName,
|
||||||
|
workspaceSchemaManagerService: this.workspaceSchemaManagerService,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+28
@@ -0,0 +1,28 @@
|
|||||||
|
import { Module } from '@nestjs/common';
|
||||||
|
|
||||||
|
import { WorkspaceSchemaManagerModule } from 'src/engine/twenty-orm/workspace-schema-manager/workspace-schema-manager.module';
|
||||||
|
import { CreateFieldActionHandlerService } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/action-handlers/field/services/create-field-action-handler.service';
|
||||||
|
import { DeleteFieldActionHandlerService } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/action-handlers/field/services/delete-field-action-handler.service';
|
||||||
|
import { UpdateFieldActionHandlerService } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/action-handlers/field/services/update-field-action-handler.service';
|
||||||
|
import { CreateIndexActionHandlerService } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/action-handlers/index/services/create-index-action-handler.service';
|
||||||
|
import { DeleteIndexActionHandlerService } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/action-handlers/index/services/delete-index-action-handler.service';
|
||||||
|
import { CreateObjectActionHandlerService } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/action-handlers/object/services/create-object-action-handler.service';
|
||||||
|
import { DeleteObjectActionHandlerService } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/action-handlers/object/services/delete-object-action-handler.service';
|
||||||
|
import { UpdateObjectActionHandlerService } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/action-handlers/object/services/update-object-action-handler.service';
|
||||||
|
|
||||||
|
@Module({
|
||||||
|
imports: [WorkspaceSchemaManagerModule],
|
||||||
|
providers: [
|
||||||
|
CreateFieldActionHandlerService,
|
||||||
|
UpdateFieldActionHandlerService,
|
||||||
|
DeleteFieldActionHandlerService,
|
||||||
|
|
||||||
|
CreateObjectActionHandlerService,
|
||||||
|
UpdateObjectActionHandlerService,
|
||||||
|
DeleteObjectActionHandlerService,
|
||||||
|
|
||||||
|
CreateIndexActionHandlerService,
|
||||||
|
DeleteIndexActionHandlerService,
|
||||||
|
],
|
||||||
|
})
|
||||||
|
export class WorkspaceSchemaMigrationRunnerActionHandlersModule {}
|
||||||
+2
@@ -0,0 +1,2 @@
|
|||||||
|
export const WORKSPACE_MIGRATION_ACTION_HANDLER_METADATA_KEY =
|
||||||
|
'workspace_migration_action_handler_metadata_key';
|
||||||
+4
@@ -10,4 +10,8 @@ export class WorkspaceMigrationRunnerException extends CustomException<
|
|||||||
export const WorkspaceMigrationRunnerExceptionCode = appendCommonExceptionCode({
|
export const WorkspaceMigrationRunnerExceptionCode = appendCommonExceptionCode({
|
||||||
FIELD_METADATA_NOT_FOUND: 'FIELD_METADATA_NOT_FOUND',
|
FIELD_METADATA_NOT_FOUND: 'FIELD_METADATA_NOT_FOUND',
|
||||||
OBJECT_METADATA_NOT_FOUND: 'OBJECT_METADATA_NOT_FOUND',
|
OBJECT_METADATA_NOT_FOUND: 'OBJECT_METADATA_NOT_FOUND',
|
||||||
|
ENUM_OPERATION_FAILED: 'ENUM_OPERATION_FAILED',
|
||||||
|
UNSUPPORTED_COMPOSITE_COLUMN_TYPE: 'UNSUPPORTED_COMPOSITE_COLUMN_TYPE',
|
||||||
|
NOT_SUPPORTED: 'NOT_SUPPORTED',
|
||||||
|
INVALID_ACTION_TYPE: 'INVALID_ACTION_TYPE',
|
||||||
} as const);
|
} as const);
|
||||||
+51
@@ -0,0 +1,51 @@
|
|||||||
|
import { SetMetadata } from '@nestjs/common';
|
||||||
|
|
||||||
|
import {
|
||||||
|
type ExtractAction,
|
||||||
|
type WorkspaceMigrationActionTypeV2,
|
||||||
|
} from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/types/workspace-migration-action-common-v2';
|
||||||
|
import { WORKSPACE_MIGRATION_ACTION_HANDLER_METADATA_KEY } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/constants/workspace-migration-action-handler-metadata-key.constant';
|
||||||
|
import { type WorkspaceMigrationActionRunnerArgs } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/types/workspace-migration-action-runner-args.type';
|
||||||
|
|
||||||
|
export interface WorkspaceMigrationRunnerActionHandlerService<
|
||||||
|
T extends WorkspaceMigrationActionTypeV2,
|
||||||
|
> {
|
||||||
|
execute(
|
||||||
|
context: WorkspaceMigrationActionRunnerArgs<ExtractAction<T>>,
|
||||||
|
): Promise<void>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export abstract class BaseWorkspaceMigrationRunnerActionHandlerService<
|
||||||
|
T extends WorkspaceMigrationActionTypeV2,
|
||||||
|
> implements WorkspaceMigrationRunnerActionHandlerService<T>
|
||||||
|
{
|
||||||
|
abstract executeForMetadata(
|
||||||
|
context: WorkspaceMigrationActionRunnerArgs<ExtractAction<T>>,
|
||||||
|
): Promise<void>;
|
||||||
|
|
||||||
|
abstract executeForWorkspaceSchema(
|
||||||
|
context: WorkspaceMigrationActionRunnerArgs<ExtractAction<T>>,
|
||||||
|
): Promise<void>;
|
||||||
|
|
||||||
|
async execute(
|
||||||
|
context: WorkspaceMigrationActionRunnerArgs<ExtractAction<T>>,
|
||||||
|
): Promise<void> {
|
||||||
|
await Promise.all([
|
||||||
|
this.executeForMetadata(context),
|
||||||
|
this.executeForWorkspaceSchema(context),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function WorkspaceMigrationRunnerActionHandler<
|
||||||
|
T extends WorkspaceMigrationActionTypeV2,
|
||||||
|
>(actionType: T): typeof BaseWorkspaceMigrationRunnerActionHandlerService<T> {
|
||||||
|
abstract class ActionHandlerService extends BaseWorkspaceMigrationRunnerActionHandlerService<T> {}
|
||||||
|
|
||||||
|
SetMetadata(
|
||||||
|
WORKSPACE_MIGRATION_ACTION_HANDLER_METADATA_KEY,
|
||||||
|
actionType,
|
||||||
|
)(ActionHandlerService);
|
||||||
|
|
||||||
|
return ActionHandlerService;
|
||||||
|
}
|
||||||
+69
@@ -0,0 +1,69 @@
|
|||||||
|
import { Injectable, OnModuleInit } from '@nestjs/common';
|
||||||
|
import { DiscoveryService } from '@nestjs/core';
|
||||||
|
|
||||||
|
import { type WorkspaceMigrationRunnerActionHandlerService } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/interfaces/workspace-migration-runner-action-handler-service.interface';
|
||||||
|
|
||||||
|
import {
|
||||||
|
type WorkspaceMigrationActionTypeV2,
|
||||||
|
type WorkspaceMigrationActionV2,
|
||||||
|
} from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/types/workspace-migration-action-common-v2';
|
||||||
|
import { WorkspaceSchemaMigrationRunnerActionHandlersModule } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/action-handlers/workspace-schema-migration-runner-action-handlers.module';
|
||||||
|
import { WORKSPACE_MIGRATION_ACTION_HANDLER_METADATA_KEY } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/constants/workspace-migration-action-handler-metadata-key.constant';
|
||||||
|
import {
|
||||||
|
WorkspaceMigrationRunnerException,
|
||||||
|
WorkspaceMigrationRunnerExceptionCode,
|
||||||
|
} from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/exceptions/workspace-migration-runner.exception';
|
||||||
|
import { WorkspaceMigrationActionRunnerArgs } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/types/workspace-migration-action-runner-args.type';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class WorkspaceMigrationRunnerActionHandlerRegistryService
|
||||||
|
implements OnModuleInit
|
||||||
|
{
|
||||||
|
private readonly actionHandlers = new Map<
|
||||||
|
WorkspaceMigrationActionTypeV2,
|
||||||
|
WorkspaceMigrationRunnerActionHandlerService<WorkspaceMigrationActionTypeV2>
|
||||||
|
>();
|
||||||
|
|
||||||
|
constructor(private readonly discoveryService: DiscoveryService) {}
|
||||||
|
|
||||||
|
async onModuleInit() {
|
||||||
|
this.discoverAndRegisterActionHandlers();
|
||||||
|
}
|
||||||
|
|
||||||
|
private discoverAndRegisterActionHandlers(): void {
|
||||||
|
const providers = this.discoveryService.getProviders({
|
||||||
|
include: [WorkspaceSchemaMigrationRunnerActionHandlersModule],
|
||||||
|
});
|
||||||
|
|
||||||
|
providers.forEach((wrapper) => {
|
||||||
|
const { instance, metatype } = wrapper;
|
||||||
|
|
||||||
|
if (!instance || !metatype) return;
|
||||||
|
|
||||||
|
const actionType = Reflect.getMetadata(
|
||||||
|
WORKSPACE_MIGRATION_ACTION_HANDLER_METADATA_KEY,
|
||||||
|
metatype,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (actionType && instance.execute) {
|
||||||
|
this.actionHandlers.set(actionType, instance);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async executeActionHandler<T extends WorkspaceMigrationActionV2>(
|
||||||
|
actionType: WorkspaceMigrationActionTypeV2,
|
||||||
|
context: WorkspaceMigrationActionRunnerArgs<T>,
|
||||||
|
): Promise<void> {
|
||||||
|
const handler = this.actionHandlers.get(actionType);
|
||||||
|
|
||||||
|
if (!handler) {
|
||||||
|
throw new WorkspaceMigrationRunnerException(
|
||||||
|
`No migration runner action handler found for action: ${actionType}`,
|
||||||
|
WorkspaceMigrationRunnerExceptionCode.INVALID_ACTION_TYPE,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
await handler.execute(context);
|
||||||
|
}
|
||||||
|
}
|
||||||
+11
-15
@@ -8,20 +8,18 @@ import { WorkspaceMetadataCacheService } from 'src/engine/metadata-modules/works
|
|||||||
import { WorkspaceMetadataVersionService } from 'src/engine/metadata-modules/workspace-metadata-version/services/workspace-metadata-version.service';
|
import { WorkspaceMetadataVersionService } from 'src/engine/metadata-modules/workspace-metadata-version/services/workspace-metadata-version.service';
|
||||||
import { WorkspacePermissionsCacheService } from 'src/engine/metadata-modules/workspace-permissions-cache/workspace-permissions-cache.service';
|
import { WorkspacePermissionsCacheService } from 'src/engine/metadata-modules/workspace-permissions-cache/workspace-permissions-cache.service';
|
||||||
import { type WorkspaceMigrationV2 } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/types/workspace-migration-v2';
|
import { type WorkspaceMigrationV2 } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/types/workspace-migration-v2';
|
||||||
import { applyWorkspaceMigrationActionOnFlatObjectMetadataMaps } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/apply-workspace-migration-action-on-flat-object-metadata-maps';
|
import { WorkspaceMigrationRunnerActionHandlerRegistryService } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/registry/workspace-migration-runner-action-handler-registry.service';
|
||||||
import { WorkspaceMetadataMigrationRunnerService } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/workspace-metadata-migration-runner/workspace-metadata-migration-runner-service';
|
import { applyWorkspaceMigrationActionOnFlatObjectMetadataMaps } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/utils/apply-workspace-migration-action-on-flat-object-metadata-maps';
|
||||||
import { WorkspaceSchemaMigrationRunnerService } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/workspace-schema-migration-runner/workspace-schema-migration-runner.service';
|
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class WorkspaceMigrationRunnerV2Service {
|
export class WorkspaceMigrationRunnerV2Service {
|
||||||
constructor(
|
constructor(
|
||||||
private readonly workspaceMetadataMigrationRunner: WorkspaceMetadataMigrationRunnerService,
|
|
||||||
private readonly workspaceSchemaMigrationRunner: WorkspaceSchemaMigrationRunnerService,
|
|
||||||
private readonly workspaceMetadataVersionService: WorkspaceMetadataVersionService,
|
private readonly workspaceMetadataVersionService: WorkspaceMetadataVersionService,
|
||||||
private readonly workspacePermissionsCacheService: WorkspacePermissionsCacheService,
|
private readonly workspacePermissionsCacheService: WorkspacePermissionsCacheService,
|
||||||
private readonly workspaceMetadataCacheService: WorkspaceMetadataCacheService,
|
private readonly workspaceMetadataCacheService: WorkspaceMetadataCacheService,
|
||||||
@InjectDataSource('core')
|
@InjectDataSource('core')
|
||||||
private readonly coreDataSource: DataSource,
|
private readonly coreDataSource: DataSource,
|
||||||
|
private readonly workspaceMigrationRunnerActionHandlerRegistry: WorkspaceMigrationRunnerActionHandlerRegistryService,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
run = async (
|
run = async (
|
||||||
@@ -44,23 +42,21 @@ export class WorkspaceMigrationRunnerV2Service {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
for (const action of workspaceMigration.actions) {
|
for (const action of workspaceMigration.actions) {
|
||||||
await Promise.all([
|
await this.workspaceMigrationRunnerActionHandlerRegistry.executeActionHandler(
|
||||||
this.workspaceMetadataMigrationRunner.runWorkspaceMetadataMigration({
|
action.type,
|
||||||
|
{
|
||||||
|
action,
|
||||||
flatObjectMetadataMaps: optimisticFlatObjectMetadataMaps,
|
flatObjectMetadataMaps: optimisticFlatObjectMetadataMaps,
|
||||||
queryRunner,
|
queryRunner,
|
||||||
action,
|
workspaceId: workspaceMigration.workspaceId,
|
||||||
}),
|
},
|
||||||
this.workspaceSchemaMigrationRunner.runWorkspaceSchemaMigration({
|
);
|
||||||
queryRunner,
|
|
||||||
flatObjectMetadataMaps: optimisticFlatObjectMetadataMaps,
|
|
||||||
action,
|
|
||||||
}),
|
|
||||||
]);
|
|
||||||
|
|
||||||
optimisticFlatObjectMetadataMaps =
|
optimisticFlatObjectMetadataMaps =
|
||||||
applyWorkspaceMigrationActionOnFlatObjectMetadataMaps({
|
applyWorkspaceMigrationActionOnFlatObjectMetadataMaps({
|
||||||
action,
|
action,
|
||||||
flatObjectMetadataMaps: optimisticFlatObjectMetadataMaps,
|
flatObjectMetadataMaps: optimisticFlatObjectMetadataMaps,
|
||||||
|
workspaceId: workspaceMigration.workspaceId,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
-17
@@ -1,17 +0,0 @@
|
|||||||
import { type ConvertActionTypeToCamelCase } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/types/convert-action-type-to-camel-case.type';
|
|
||||||
import {
|
|
||||||
type WorkspaceMigrationActionTypeV2,
|
|
||||||
type WorkspaceMigrationActionV2,
|
|
||||||
} from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/types/workspace-migration-action-common-v2';
|
|
||||||
import { type WorkspaceMigrationActionRunnerArgs } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/types/workspace-migration-action-runner-args.type';
|
|
||||||
|
|
||||||
export type RunnerMethodForActionType<
|
|
||||||
TAction extends WorkspaceMigrationActionTypeV2,
|
|
||||||
TRunner extends 'metadata' | 'schema',
|
|
||||||
> = {
|
|
||||||
[P in TAction as `run${Capitalize<ConvertActionTypeToCamelCase<P>>}${Capitalize<TRunner>}Migration`]: (
|
|
||||||
arg: WorkspaceMigrationActionRunnerArgs<
|
|
||||||
Extract<WorkspaceMigrationActionV2, { type: P }>
|
|
||||||
>,
|
|
||||||
) => Promise<void>;
|
|
||||||
};
|
|
||||||
+1
@@ -9,4 +9,5 @@ export type WorkspaceMigrationActionRunnerArgs<
|
|||||||
queryRunner: QueryRunner;
|
queryRunner: QueryRunner;
|
||||||
action: T;
|
action: T;
|
||||||
flatObjectMetadataMaps: FlatObjectMetadataMaps;
|
flatObjectMetadataMaps: FlatObjectMetadataMaps;
|
||||||
|
workspaceId: string;
|
||||||
};
|
};
|
||||||
|
|||||||
-10
@@ -1,10 +0,0 @@
|
|||||||
import { type QueryRunner } from 'typeorm';
|
|
||||||
|
|
||||||
import { type FlatObjectMetadataMaps } from 'src/engine/metadata-modules/flat-object-metadata-maps/types/flat-object-metadata-maps.type';
|
|
||||||
import { type WorkspaceMigrationActionV2 } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/types/workspace-migration-action-common-v2';
|
|
||||||
|
|
||||||
export type WorkspaceMigrationRunnerArgs = {
|
|
||||||
action: WorkspaceMigrationActionV2;
|
|
||||||
queryRunner: QueryRunner;
|
|
||||||
flatObjectMetadataMaps: FlatObjectMetadataMaps;
|
|
||||||
};
|
|
||||||
+1
-1
@@ -3,7 +3,7 @@ import { FieldMetadataType } from 'twenty-shared/types';
|
|||||||
import { getFlatFieldMetadataMock } from 'src/engine/metadata-modules/flat-field-metadata/__mocks__/get-flat-field-metadata.mock';
|
import { getFlatFieldMetadataMock } from 'src/engine/metadata-modules/flat-field-metadata/__mocks__/get-flat-field-metadata.mock';
|
||||||
import { getFlatObjectMetadataMock } from 'src/engine/metadata-modules/flat-object-metadata/__mocks__/get-flat-object-metadata.mock';
|
import { getFlatObjectMetadataMock } from 'src/engine/metadata-modules/flat-object-metadata/__mocks__/get-flat-object-metadata.mock';
|
||||||
import { getWorkspaceSchemaName } from 'src/engine/workspace-datasource/utils/get-workspace-schema-name.util';
|
import { getWorkspaceSchemaName } from 'src/engine/workspace-datasource/utils/get-workspace-schema-name.util';
|
||||||
import { generateColumnDefinitions } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/workspace-schema-migration-runner/utils/generate-column-definitions.util';
|
import { generateColumnDefinitions } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/utils/generate-column-definitions.util';
|
||||||
|
|
||||||
describe('Generate Column Definitions', () => {
|
describe('Generate Column Definitions', () => {
|
||||||
const mockObjectMetadata = getFlatObjectMetadataMock({
|
const mockObjectMetadata = getFlatObjectMetadataMock({
|
||||||
+3
-3
@@ -3,13 +3,13 @@ import { type QueryRunner } from 'typeorm';
|
|||||||
|
|
||||||
import { getFlatFieldMetadataMock } from 'src/engine/metadata-modules/flat-field-metadata/__mocks__/get-flat-field-metadata.mock';
|
import { getFlatFieldMetadataMock } from 'src/engine/metadata-modules/flat-field-metadata/__mocks__/get-flat-field-metadata.mock';
|
||||||
import { type WorkspaceSchemaManagerService } from 'src/engine/twenty-orm/workspace-schema-manager/workspace-schema-manager.service';
|
import { type WorkspaceSchemaManagerService } from 'src/engine/twenty-orm/workspace-schema-manager/workspace-schema-manager.service';
|
||||||
import { WorkspaceSchemaMigrationException } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/workspace-schema-migration-runner/exceptions/workspace-schema-migration.exception';
|
import { WorkspaceMigrationRunnerException } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/exceptions/workspace-migration-runner.exception';
|
||||||
import {
|
import {
|
||||||
collectEnumOperationsForField,
|
collectEnumOperationsForField,
|
||||||
EnumOperation,
|
EnumOperation,
|
||||||
type EnumOperationSpec,
|
type EnumOperationSpec,
|
||||||
executeBatchEnumOperations,
|
executeBatchEnumOperations,
|
||||||
} from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/workspace-schema-migration-runner/utils/workspace-schema-enum-operations.util';
|
} from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/utils/workspace-schema-enum-operations.util';
|
||||||
|
|
||||||
describe('WorkspaceSchemaEnumOperations', () => {
|
describe('WorkspaceSchemaEnumOperations', () => {
|
||||||
let mockSchemaManagerService: jest.Mocked<WorkspaceSchemaManagerService>;
|
let mockSchemaManagerService: jest.Mocked<WorkspaceSchemaManagerService>;
|
||||||
@@ -61,7 +61,7 @@ describe('WorkspaceSchemaEnumOperations', () => {
|
|||||||
schemaName: 'test_schema',
|
schemaName: 'test_schema',
|
||||||
workspaceSchemaManagerService: mockSchemaManagerService,
|
workspaceSchemaManagerService: mockSchemaManagerService,
|
||||||
}),
|
}),
|
||||||
).rejects.toThrow(WorkspaceSchemaMigrationException);
|
).rejects.toThrow(WorkspaceMigrationRunnerException);
|
||||||
|
|
||||||
// All operations should be attempted in parallel despite failure
|
// All operations should be attempted in parallel despite failure
|
||||||
expect(
|
expect(
|
||||||
+8
-5
@@ -9,18 +9,21 @@ import { findFlatFieldMetadataInFlatObjectMetadataMaps } from 'src/engine/metada
|
|||||||
import { findFlatObjectMetadataInFlatObjectMetadataMaps } from 'src/engine/metadata-modules/flat-object-metadata-maps/utils/find-flat-object-metadata-in-flat-object-metadata-maps.util';
|
import { findFlatObjectMetadataInFlatObjectMetadataMaps } from 'src/engine/metadata-modules/flat-object-metadata-maps/utils/find-flat-object-metadata-in-flat-object-metadata-maps.util';
|
||||||
import { replaceFlatFieldMetadataInFlatObjectMetadataMapsOrThrow } from 'src/engine/metadata-modules/flat-object-metadata-maps/utils/replace-flat-field-metadata-in-flat-object-metadata-maps-or-throw.util';
|
import { replaceFlatFieldMetadataInFlatObjectMetadataMapsOrThrow } from 'src/engine/metadata-modules/flat-object-metadata-maps/utils/replace-flat-field-metadata-in-flat-object-metadata-maps-or-throw.util';
|
||||||
import { replaceFlatObjectMetadataInFlatObjectMetadataMapsOrThrow } from 'src/engine/metadata-modules/flat-object-metadata-maps/utils/replace-flat-object-metadata-in-flat-object-metadata-maps-or-throw.util';
|
import { replaceFlatObjectMetadataInFlatObjectMetadataMapsOrThrow } from 'src/engine/metadata-modules/flat-object-metadata-maps/utils/replace-flat-object-metadata-in-flat-object-metadata-maps-or-throw.util';
|
||||||
import { type WorkspaceMigrationRunnerArgs } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/types/workspace-migration-runner-args.type';
|
import { type WorkspaceMigrationActionV2 } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/types/workspace-migration-action-common-v2';
|
||||||
import { fromWorkspaceMigrationUpdateActionToPartialEntity } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/utils/from-workspace-migration-update-action-to-partial-field-or-object-entity.util';
|
|
||||||
import {
|
import {
|
||||||
WorkspaceMigrationRunnerException,
|
WorkspaceMigrationRunnerException,
|
||||||
WorkspaceMigrationRunnerExceptionCode,
|
WorkspaceMigrationRunnerExceptionCode,
|
||||||
} from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/workspace-migration-runner.exception';
|
} from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/exceptions/workspace-migration-runner.exception';
|
||||||
|
import { type WorkspaceMigrationActionRunnerArgs } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/types/workspace-migration-action-runner-args.type';
|
||||||
|
import { fromWorkspaceMigrationUpdateActionToPartialEntity } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/utils/from-workspace-migration-update-action-to-partial-field-or-object-entity.util';
|
||||||
|
|
||||||
export const applyWorkspaceMigrationActionOnFlatObjectMetadataMaps = ({
|
export const applyWorkspaceMigrationActionOnFlatObjectMetadataMaps = <
|
||||||
|
T extends WorkspaceMigrationActionV2,
|
||||||
|
>({
|
||||||
action,
|
action,
|
||||||
flatObjectMetadataMaps,
|
flatObjectMetadataMaps,
|
||||||
}: Omit<
|
}: Omit<
|
||||||
WorkspaceMigrationRunnerArgs,
|
WorkspaceMigrationActionRunnerArgs<T>,
|
||||||
'queryRunner'
|
'queryRunner'
|
||||||
>): FlatObjectMetadataMaps => {
|
>): FlatObjectMetadataMaps => {
|
||||||
switch (action.type) {
|
switch (action.type) {
|
||||||
+8
-9
@@ -18,11 +18,10 @@ import { fieldMetadataTypeToColumnType } from 'src/engine/metadata-modules/works
|
|||||||
import { type WorkspaceSchemaColumnDefinition } from 'src/engine/twenty-orm/workspace-schema-manager/types/workspace-schema-column-definition.type';
|
import { type WorkspaceSchemaColumnDefinition } from 'src/engine/twenty-orm/workspace-schema-manager/types/workspace-schema-column-definition.type';
|
||||||
import { computePostgresEnumName } from 'src/engine/workspace-manager/workspace-migration-runner/utils/compute-postgres-enum-name.util';
|
import { computePostgresEnumName } from 'src/engine/workspace-manager/workspace-migration-runner/utils/compute-postgres-enum-name.util';
|
||||||
import {
|
import {
|
||||||
WorkspaceSchemaMigrationException,
|
WorkspaceMigrationRunnerException,
|
||||||
WorkspaceSchemaMigrationExceptionCode,
|
WorkspaceMigrationRunnerExceptionCode,
|
||||||
} from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/workspace-schema-migration-runner/exceptions/workspace-schema-migration.exception';
|
} from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/exceptions/workspace-migration-runner.exception';
|
||||||
|
import { getWorkspaceSchemaContextForMigration } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/utils/get-workspace-schema-context-for-migration.util';
|
||||||
import { getWorkspaceSchemaContextForMigration } from './get-workspace-schema-context-for-migration.util';
|
|
||||||
|
|
||||||
export const generateCompositeColumnDefinition = ({
|
export const generateCompositeColumnDefinition = ({
|
||||||
compositeProperty,
|
compositeProperty,
|
||||||
@@ -35,16 +34,16 @@ export const generateCompositeColumnDefinition = ({
|
|||||||
}): WorkspaceSchemaColumnDefinition => {
|
}): WorkspaceSchemaColumnDefinition => {
|
||||||
const { tableName, schemaName } = getWorkspaceSchemaContextForMigration({
|
const { tableName, schemaName } = getWorkspaceSchemaContextForMigration({
|
||||||
workspaceId: flatObjectMetadataWithoutFields.workspaceId,
|
workspaceId: flatObjectMetadataWithoutFields.workspaceId,
|
||||||
flatObjectMetadataWithoutFields,
|
flatObjectMetadata: flatObjectMetadataWithoutFields,
|
||||||
});
|
});
|
||||||
|
|
||||||
if (
|
if (
|
||||||
compositeProperty.type === FieldMetadataType.RELATION ||
|
compositeProperty.type === FieldMetadataType.RELATION ||
|
||||||
compositeProperty.type === FieldMetadataType.MORPH_RELATION
|
compositeProperty.type === FieldMetadataType.MORPH_RELATION
|
||||||
) {
|
) {
|
||||||
throw new WorkspaceSchemaMigrationException(
|
throw new WorkspaceMigrationRunnerException(
|
||||||
`Relation type not supported for composite columns`,
|
`Relation type not supported for composite columns`,
|
||||||
WorkspaceSchemaMigrationExceptionCode.UNSUPPORTED_COMPOSITE_COLUMN_TYPE,
|
WorkspaceMigrationRunnerExceptionCode.UNSUPPORTED_COMPOSITE_COLUMN_TYPE,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -156,7 +155,7 @@ export const generateColumnDefinitions = ({
|
|||||||
}): WorkspaceSchemaColumnDefinition[] => {
|
}): WorkspaceSchemaColumnDefinition[] => {
|
||||||
const { tableName, schemaName } = getWorkspaceSchemaContextForMigration({
|
const { tableName, schemaName } = getWorkspaceSchemaContextForMigration({
|
||||||
workspaceId: flatObjectMetadataWithoutFields.workspaceId,
|
workspaceId: flatObjectMetadataWithoutFields.workspaceId,
|
||||||
flatObjectMetadataWithoutFields: flatObjectMetadataWithoutFields,
|
flatObjectMetadata: flatObjectMetadataWithoutFields,
|
||||||
});
|
});
|
||||||
|
|
||||||
if (isCompositeFlatFieldMetadata(flatFieldMetadata)) {
|
if (isCompositeFlatFieldMetadata(flatFieldMetadata)) {
|
||||||
+7
-4
@@ -1,4 +1,7 @@
|
|||||||
import { type FlatObjectMetadataWithoutFields } from 'src/engine/metadata-modules/flat-object-metadata/types/flat-object-metadata.type';
|
import {
|
||||||
|
type FlatObjectMetadata,
|
||||||
|
type FlatObjectMetadataWithoutFields,
|
||||||
|
} from 'src/engine/metadata-modules/flat-object-metadata/types/flat-object-metadata.type';
|
||||||
import { computeObjectTargetTable } from 'src/engine/utils/compute-object-target-table.util';
|
import { computeObjectTargetTable } from 'src/engine/utils/compute-object-target-table.util';
|
||||||
import { getWorkspaceSchemaName } from 'src/engine/workspace-datasource/utils/get-workspace-schema-name.util';
|
import { getWorkspaceSchemaName } from 'src/engine/workspace-datasource/utils/get-workspace-schema-name.util';
|
||||||
|
|
||||||
@@ -9,13 +12,13 @@ export type WorkspaceSchemaContextForMigration = {
|
|||||||
|
|
||||||
export const getWorkspaceSchemaContextForMigration = ({
|
export const getWorkspaceSchemaContextForMigration = ({
|
||||||
workspaceId,
|
workspaceId,
|
||||||
flatObjectMetadataWithoutFields,
|
flatObjectMetadata,
|
||||||
}: {
|
}: {
|
||||||
workspaceId: string;
|
workspaceId: string;
|
||||||
flatObjectMetadataWithoutFields: FlatObjectMetadataWithoutFields;
|
flatObjectMetadata: FlatObjectMetadata | FlatObjectMetadataWithoutFields;
|
||||||
}): WorkspaceSchemaContextForMigration => {
|
}): WorkspaceSchemaContextForMigration => {
|
||||||
return {
|
return {
|
||||||
schemaName: getWorkspaceSchemaName(workspaceId),
|
schemaName: getWorkspaceSchemaName(workspaceId),
|
||||||
tableName: computeObjectTargetTable(flatObjectMetadataWithoutFields),
|
tableName: computeObjectTargetTable(flatObjectMetadata),
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
+5
-5
@@ -12,9 +12,9 @@ import { isEnumFlatFieldMetadata } from 'src/engine/metadata-modules/flat-field-
|
|||||||
import { type WorkspaceSchemaManagerService } from 'src/engine/twenty-orm/workspace-schema-manager/workspace-schema-manager.service';
|
import { type WorkspaceSchemaManagerService } from 'src/engine/twenty-orm/workspace-schema-manager/workspace-schema-manager.service';
|
||||||
import { computePostgresEnumName } from 'src/engine/workspace-manager/workspace-migration-runner/utils/compute-postgres-enum-name.util';
|
import { computePostgresEnumName } from 'src/engine/workspace-manager/workspace-migration-runner/utils/compute-postgres-enum-name.util';
|
||||||
import {
|
import {
|
||||||
WorkspaceSchemaMigrationException,
|
WorkspaceMigrationRunnerException,
|
||||||
WorkspaceSchemaMigrationExceptionCode,
|
WorkspaceMigrationRunnerExceptionCode,
|
||||||
} from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/workspace-schema-migration-runner/exceptions/workspace-schema-migration.exception';
|
} from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/exceptions/workspace-migration-runner.exception';
|
||||||
|
|
||||||
export interface CreateEnumOperationSpec {
|
export interface CreateEnumOperationSpec {
|
||||||
operation: EnumOperation.CREATE;
|
operation: EnumOperation.CREATE;
|
||||||
@@ -246,9 +246,9 @@ export const executeBatchEnumOperations = async ({
|
|||||||
|
|
||||||
await Promise.all(enumPromises);
|
await Promise.all(enumPromises);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
throw new WorkspaceSchemaMigrationException(
|
throw new WorkspaceMigrationRunnerException(
|
||||||
`Failed to execute batch enum operations: ${error instanceof Error ? error.message : 'Unknown error'}`,
|
`Failed to execute batch enum operations: ${error instanceof Error ? error.message : 'Unknown error'}`,
|
||||||
WorkspaceSchemaMigrationExceptionCode.ENUM_OPERATION_FAILED,
|
WorkspaceMigrationRunnerExceptionCode.ENUM_OPERATION_FAILED,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
-63
@@ -1,63 +0,0 @@
|
|||||||
import { In } from 'typeorm';
|
|
||||||
|
|
||||||
import { FieldMetadataEntity } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity';
|
|
||||||
import {
|
|
||||||
type CreateFieldAction,
|
|
||||||
type DeleteFieldAction,
|
|
||||||
type UpdateFieldAction,
|
|
||||||
type WorkspaceMigrationFieldActionTypeV2,
|
|
||||||
} from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/types/workspace-migration-field-action-v2';
|
|
||||||
import { type RunnerMethodForActionType } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/types/runner-method-for-action-type';
|
|
||||||
import { type WorkspaceMigrationActionRunnerArgs } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/types/workspace-migration-action-runner-args.type';
|
|
||||||
import { fromWorkspaceMigrationUpdateActionToPartialEntity } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/utils/from-workspace-migration-update-action-to-partial-field-or-object-entity.util';
|
|
||||||
|
|
||||||
export class WorkspaceMetadataFieldActionRunnerService
|
|
||||||
implements
|
|
||||||
RunnerMethodForActionType<WorkspaceMigrationFieldActionTypeV2, 'metadata'>
|
|
||||||
{
|
|
||||||
runDeleteFieldMetadataMigration = async ({
|
|
||||||
action,
|
|
||||||
queryRunner,
|
|
||||||
}: WorkspaceMigrationActionRunnerArgs<DeleteFieldAction>) => {
|
|
||||||
const fieldMetadataRepository =
|
|
||||||
queryRunner.manager.getRepository<FieldMetadataEntity>(
|
|
||||||
FieldMetadataEntity,
|
|
||||||
);
|
|
||||||
|
|
||||||
const { fieldMetadataId } = action;
|
|
||||||
|
|
||||||
await fieldMetadataRepository.delete({
|
|
||||||
id: In([fieldMetadataId]),
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
runCreateFieldMetadataMigration = async ({
|
|
||||||
action,
|
|
||||||
queryRunner,
|
|
||||||
}: WorkspaceMigrationActionRunnerArgs<CreateFieldAction>) => {
|
|
||||||
const fieldMetadataRepository =
|
|
||||||
queryRunner.manager.getRepository<FieldMetadataEntity>(
|
|
||||||
FieldMetadataEntity,
|
|
||||||
);
|
|
||||||
|
|
||||||
const { flatFieldMetadata } = action;
|
|
||||||
|
|
||||||
await fieldMetadataRepository.save(flatFieldMetadata);
|
|
||||||
};
|
|
||||||
runUpdateFieldMetadataMigration = async ({
|
|
||||||
action,
|
|
||||||
queryRunner,
|
|
||||||
}: WorkspaceMigrationActionRunnerArgs<UpdateFieldAction>) => {
|
|
||||||
const fieldMetadataRepository =
|
|
||||||
queryRunner.manager.getRepository<FieldMetadataEntity>(
|
|
||||||
FieldMetadataEntity,
|
|
||||||
);
|
|
||||||
|
|
||||||
const { fieldMetadataId } = action;
|
|
||||||
|
|
||||||
await fieldMetadataRepository.update(
|
|
||||||
fieldMetadataId,
|
|
||||||
fromWorkspaceMigrationUpdateActionToPartialEntity(action),
|
|
||||||
);
|
|
||||||
};
|
|
||||||
}
|
|
||||||
-26
@@ -1,26 +0,0 @@
|
|||||||
import { Injectable } from '@nestjs/common';
|
|
||||||
|
|
||||||
import {
|
|
||||||
type CreateIndexAction,
|
|
||||||
type DeleteIndexAction,
|
|
||||||
type WorkspaceMigrationIndexActionTypeV2,
|
|
||||||
} from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/types/workspace-migration-index-action-v2';
|
|
||||||
import { type RunnerMethodForActionType } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/types/runner-method-for-action-type';
|
|
||||||
import { type WorkspaceMigrationActionRunnerArgs } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/types/workspace-migration-action-runner-args.type';
|
|
||||||
|
|
||||||
@Injectable()
|
|
||||||
export class WorkspaceMetadataIndexActionRunnerService
|
|
||||||
implements
|
|
||||||
RunnerMethodForActionType<WorkspaceMigrationIndexActionTypeV2, 'metadata'>
|
|
||||||
{
|
|
||||||
runDeleteIndexMetadataMigration = async (
|
|
||||||
_action: WorkspaceMigrationActionRunnerArgs<DeleteIndexAction>,
|
|
||||||
) => {
|
|
||||||
return;
|
|
||||||
};
|
|
||||||
runCreateIndexMetadataMigration = async (
|
|
||||||
_action: WorkspaceMigrationActionRunnerArgs<CreateIndexAction>,
|
|
||||||
) => {
|
|
||||||
return;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
-80
@@ -1,80 +0,0 @@
|
|||||||
import { Injectable } from '@nestjs/common';
|
|
||||||
|
|
||||||
import { assertUnreachable } from 'twenty-shared/utils';
|
|
||||||
|
|
||||||
import { type WorkspaceMigrationRunnerArgs } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/types/workspace-migration-runner-args.type';
|
|
||||||
import { WorkspaceMetadataFieldActionRunnerService } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/workspace-metadata-migration-runner/workspace-metadata-field-action-runner.service';
|
|
||||||
import { WorkspaceMetadataIndexActionRunnerService } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/workspace-metadata-migration-runner/workspace-metadata-index-action-runner.service';
|
|
||||||
import { WorkspaceMetadataObjectActionRunnerService } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/workspace-metadata-migration-runner/workspace-metadata-object-action-runner.service';
|
|
||||||
|
|
||||||
@Injectable()
|
|
||||||
export class WorkspaceMetadataMigrationRunnerService {
|
|
||||||
constructor(
|
|
||||||
private readonly workspaceMetadataObjectMigrationRunnerService: WorkspaceMetadataObjectActionRunnerService,
|
|
||||||
private readonly workspaceMetadataIndexMigrationRunnerService: WorkspaceMetadataIndexActionRunnerService,
|
|
||||||
private readonly workspaceMetadataFieldMigrationRunnerService: WorkspaceMetadataFieldActionRunnerService,
|
|
||||||
) {}
|
|
||||||
|
|
||||||
runWorkspaceMetadataMigration = async ({
|
|
||||||
action,
|
|
||||||
queryRunner,
|
|
||||||
flatObjectMetadataMaps,
|
|
||||||
}: WorkspaceMigrationRunnerArgs) => {
|
|
||||||
switch (action.type) {
|
|
||||||
case 'delete_object': {
|
|
||||||
await this.workspaceMetadataObjectMigrationRunnerService.runDeleteObjectMetadataMigration(
|
|
||||||
{ action, queryRunner, flatObjectMetadataMaps },
|
|
||||||
);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case 'create_object': {
|
|
||||||
await this.workspaceMetadataObjectMigrationRunnerService.runCreateObjectMetadataMigration(
|
|
||||||
{ action, queryRunner, flatObjectMetadataMaps },
|
|
||||||
);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case 'update_object': {
|
|
||||||
await this.workspaceMetadataObjectMigrationRunnerService.runUpdateObjectMetadataMigration(
|
|
||||||
{ action, queryRunner, flatObjectMetadataMaps },
|
|
||||||
);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case 'create_field': {
|
|
||||||
await this.workspaceMetadataFieldMigrationRunnerService.runCreateFieldMetadataMigration(
|
|
||||||
{ action, queryRunner, flatObjectMetadataMaps },
|
|
||||||
);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case 'update_field': {
|
|
||||||
await this.workspaceMetadataFieldMigrationRunnerService.runUpdateFieldMetadataMigration(
|
|
||||||
{ action, queryRunner, flatObjectMetadataMaps },
|
|
||||||
);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case 'delete_field': {
|
|
||||||
await this.workspaceMetadataFieldMigrationRunnerService.runDeleteFieldMetadataMigration(
|
|
||||||
{ action, queryRunner, flatObjectMetadataMaps },
|
|
||||||
);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case 'create_index': {
|
|
||||||
await this.workspaceMetadataIndexMigrationRunnerService.runCreateIndexMetadataMigration(
|
|
||||||
{ action, queryRunner, flatObjectMetadataMaps },
|
|
||||||
);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case 'delete_index': {
|
|
||||||
await this.workspaceMetadataIndexMigrationRunnerService.runDeleteIndexMetadataMigration(
|
|
||||||
{ action, queryRunner, flatObjectMetadataMaps },
|
|
||||||
);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
default: {
|
|
||||||
assertUnreachable(
|
|
||||||
action,
|
|
||||||
'Should never occur, encountered an unsupported workspace migration action type',
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
-83
@@ -1,83 +0,0 @@
|
|||||||
import { Injectable } from '@nestjs/common';
|
|
||||||
|
|
||||||
import { DataSourceService } from 'src/engine/metadata-modules/data-source/data-source.service';
|
|
||||||
import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
|
|
||||||
import {
|
|
||||||
type CreateObjectAction,
|
|
||||||
type DeleteObjectAction,
|
|
||||||
type UpdateObjectAction,
|
|
||||||
type WorkspaceMigrationObjectActionTypeV2,
|
|
||||||
} from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/types/workspace-migration-object-action-v2';
|
|
||||||
import { type RunnerMethodForActionType } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/types/runner-method-for-action-type';
|
|
||||||
import { type WorkspaceMigrationActionRunnerArgs } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/types/workspace-migration-action-runner-args.type';
|
|
||||||
import { fromWorkspaceMigrationUpdateActionToPartialEntity } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/utils/from-workspace-migration-update-action-to-partial-field-or-object-entity.util';
|
|
||||||
import { WorkspaceMetadataFieldActionRunnerService } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/workspace-metadata-migration-runner/workspace-metadata-field-action-runner.service';
|
|
||||||
|
|
||||||
@Injectable()
|
|
||||||
export class WorkspaceMetadataObjectActionRunnerService
|
|
||||||
implements
|
|
||||||
RunnerMethodForActionType<WorkspaceMigrationObjectActionTypeV2, 'metadata'>
|
|
||||||
{
|
|
||||||
constructor(
|
|
||||||
private readonly dataSourceService: DataSourceService,
|
|
||||||
private readonly workspaceMetadataFieldActionRunnerService: WorkspaceMetadataFieldActionRunnerService,
|
|
||||||
) {}
|
|
||||||
|
|
||||||
runDeleteObjectMetadataMigration = async ({
|
|
||||||
action: { objectMetadataId },
|
|
||||||
queryRunner,
|
|
||||||
}: WorkspaceMigrationActionRunnerArgs<DeleteObjectAction>) => {
|
|
||||||
const objectMetadataRepository =
|
|
||||||
queryRunner.manager.getRepository<ObjectMetadataEntity>(
|
|
||||||
ObjectMetadataEntity,
|
|
||||||
);
|
|
||||||
|
|
||||||
await objectMetadataRepository.delete(objectMetadataId);
|
|
||||||
};
|
|
||||||
|
|
||||||
runCreateObjectMetadataMigration = async ({
|
|
||||||
action: { flatObjectMetadataWithoutFields, createFieldActions },
|
|
||||||
queryRunner,
|
|
||||||
flatObjectMetadataMaps,
|
|
||||||
}: WorkspaceMigrationActionRunnerArgs<CreateObjectAction>) => {
|
|
||||||
const objectMetadataRepository =
|
|
||||||
queryRunner.manager.getRepository<ObjectMetadataEntity>(
|
|
||||||
ObjectMetadataEntity,
|
|
||||||
);
|
|
||||||
const lastDataSourceMetadata =
|
|
||||||
await this.dataSourceService.getLastDataSourceMetadataFromWorkspaceIdOrFail(
|
|
||||||
flatObjectMetadataWithoutFields.workspaceId,
|
|
||||||
);
|
|
||||||
|
|
||||||
await objectMetadataRepository.save({
|
|
||||||
...flatObjectMetadataWithoutFields,
|
|
||||||
dataSourceId: lastDataSourceMetadata.id,
|
|
||||||
targetTableName: 'DEPRECATED',
|
|
||||||
});
|
|
||||||
|
|
||||||
for (const createFieldAction of createFieldActions) {
|
|
||||||
await this.workspaceMetadataFieldActionRunnerService.runCreateFieldMetadataMigration(
|
|
||||||
{
|
|
||||||
action: createFieldAction,
|
|
||||||
queryRunner,
|
|
||||||
flatObjectMetadataMaps,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
runUpdateObjectMetadataMigration = async ({
|
|
||||||
action,
|
|
||||||
queryRunner,
|
|
||||||
}: WorkspaceMigrationActionRunnerArgs<UpdateObjectAction>) => {
|
|
||||||
const objectMetadataRepository =
|
|
||||||
queryRunner.manager.getRepository<ObjectMetadataEntity>(
|
|
||||||
ObjectMetadataEntity,
|
|
||||||
);
|
|
||||||
|
|
||||||
await objectMetadataRepository.update(
|
|
||||||
action.objectMetadataId,
|
|
||||||
fromWorkspaceMigrationUpdateActionToPartialEntity(action),
|
|
||||||
);
|
|
||||||
};
|
|
||||||
}
|
|
||||||
+7
-19
@@ -1,4 +1,5 @@
|
|||||||
import { Module } from '@nestjs/common';
|
import { Module } from '@nestjs/common';
|
||||||
|
import { DiscoveryModule } from '@nestjs/core';
|
||||||
|
|
||||||
import { TypeORMModule } from 'src/database/typeorm/typeorm.module';
|
import { TypeORMModule } from 'src/database/typeorm/typeorm.module';
|
||||||
import { FeatureFlagModule } from 'src/engine/core-modules/feature-flag/feature-flag.module';
|
import { FeatureFlagModule } from 'src/engine/core-modules/feature-flag/feature-flag.module';
|
||||||
@@ -6,16 +7,9 @@ import { DataSourceModule } from 'src/engine/metadata-modules/data-source/data-s
|
|||||||
import { WorkspaceMetadataCacheModule } from 'src/engine/metadata-modules/workspace-metadata-cache/workspace-metadata-cache.module';
|
import { WorkspaceMetadataCacheModule } from 'src/engine/metadata-modules/workspace-metadata-cache/workspace-metadata-cache.module';
|
||||||
import { WorkspaceMetadataVersionModule } from 'src/engine/metadata-modules/workspace-metadata-version/workspace-metadata-version.module';
|
import { WorkspaceMetadataVersionModule } from 'src/engine/metadata-modules/workspace-metadata-version/workspace-metadata-version.module';
|
||||||
import { WorkspacePermissionsCacheModule } from 'src/engine/metadata-modules/workspace-permissions-cache/workspace-permissions-cache.module';
|
import { WorkspacePermissionsCacheModule } from 'src/engine/metadata-modules/workspace-permissions-cache/workspace-permissions-cache.module';
|
||||||
import { WorkspaceSchemaManagerModule } from 'src/engine/twenty-orm/workspace-schema-manager/workspace-schema-manager.module';
|
import { WorkspaceSchemaMigrationRunnerActionHandlersModule } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/action-handlers/workspace-schema-migration-runner-action-handlers.module';
|
||||||
import { WorkspaceMetadataFieldActionRunnerService } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/workspace-metadata-migration-runner/workspace-metadata-field-action-runner.service';
|
import { WorkspaceMigrationRunnerActionHandlerRegistryService } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/registry/workspace-migration-runner-action-handler-registry.service';
|
||||||
import { WorkspaceMetadataIndexActionRunnerService } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/workspace-metadata-migration-runner/workspace-metadata-index-action-runner.service';
|
import { WorkspaceMigrationRunnerV2Service } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/services/workspace-migration-runner-v2.service';
|
||||||
import { WorkspaceMetadataMigrationRunnerService } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/workspace-metadata-migration-runner/workspace-metadata-migration-runner-service';
|
|
||||||
import { WorkspaceMetadataObjectActionRunnerService } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/workspace-metadata-migration-runner/workspace-metadata-object-action-runner.service';
|
|
||||||
import { WorkspaceMigrationRunnerV2Service } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/workspace-migration-runner-v2.service';
|
|
||||||
import { WorkspaceSchemaFieldActionRunnerService } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/workspace-schema-migration-runner/workspace-schema-field-action-runner.service';
|
|
||||||
import { WorkspaceSchemaIndexActionRunnerService } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/workspace-schema-migration-runner/workspace-schema-index-action-runner.service';
|
|
||||||
import { WorkspaceSchemaMigrationRunnerService } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/workspace-schema-migration-runner/workspace-schema-migration-runner.service';
|
|
||||||
import { WorkspaceSchemaObjectActionRunnerService } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/workspace-schema-migration-runner/workspace-schema-object-action-runner.service';
|
|
||||||
|
|
||||||
@Module({
|
@Module({
|
||||||
imports: [
|
imports: [
|
||||||
@@ -25,18 +19,12 @@ import { WorkspaceSchemaObjectActionRunnerService } from 'src/engine/workspace-m
|
|||||||
WorkspaceMetadataVersionModule,
|
WorkspaceMetadataVersionModule,
|
||||||
WorkspacePermissionsCacheModule,
|
WorkspacePermissionsCacheModule,
|
||||||
WorkspaceMetadataCacheModule,
|
WorkspaceMetadataCacheModule,
|
||||||
WorkspaceSchemaManagerModule,
|
WorkspaceSchemaMigrationRunnerActionHandlersModule,
|
||||||
|
DiscoveryModule,
|
||||||
],
|
],
|
||||||
providers: [
|
providers: [
|
||||||
WorkspaceMetadataObjectActionRunnerService,
|
|
||||||
WorkspaceMetadataIndexActionRunnerService,
|
|
||||||
WorkspaceMetadataFieldActionRunnerService,
|
|
||||||
WorkspaceSchemaIndexActionRunnerService,
|
|
||||||
WorkspaceMetadataMigrationRunnerService,
|
|
||||||
WorkspaceSchemaMigrationRunnerService,
|
|
||||||
WorkspaceMigrationRunnerV2Service,
|
WorkspaceMigrationRunnerV2Service,
|
||||||
WorkspaceSchemaObjectActionRunnerService,
|
WorkspaceMigrationRunnerActionHandlerRegistryService,
|
||||||
WorkspaceSchemaFieldActionRunnerService,
|
|
||||||
],
|
],
|
||||||
exports: [WorkspaceMigrationRunnerV2Service],
|
exports: [WorkspaceMigrationRunnerV2Service],
|
||||||
})
|
})
|
||||||
|
|||||||
-730
@@ -1,730 +0,0 @@
|
|||||||
import { FieldMetadataType } from 'twenty-shared/types';
|
|
||||||
import { type QueryRunner } from 'typeorm';
|
|
||||||
|
|
||||||
import { RelationType } from 'src/engine/metadata-modules/field-metadata/interfaces/relation-type.interface';
|
|
||||||
|
|
||||||
import { getFlatFieldMetadataMock } from 'src/engine/metadata-modules/flat-field-metadata/__mocks__/get-flat-field-metadata.mock';
|
|
||||||
import { getFlatObjectMetadataMock } from 'src/engine/metadata-modules/flat-object-metadata/__mocks__/get-flat-object-metadata.mock';
|
|
||||||
import { type WorkspaceSchemaManagerService } from 'src/engine/twenty-orm/workspace-schema-manager/workspace-schema-manager.service';
|
|
||||||
import { getWorkspaceSchemaName } from 'src/engine/workspace-datasource/utils/get-workspace-schema-name.util';
|
|
||||||
import { WorkspaceSchemaFieldActionRunnerService } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/workspace-schema-migration-runner/workspace-schema-field-action-runner.service';
|
|
||||||
|
|
||||||
describe('WorkspaceSchemaFieldActionRunner', () => {
|
|
||||||
let service: WorkspaceSchemaFieldActionRunnerService;
|
|
||||||
let mockSchemaManagerService: jest.Mocked<WorkspaceSchemaManagerService>;
|
|
||||||
let mockQueryRunner: jest.Mocked<QueryRunner>;
|
|
||||||
|
|
||||||
const mockWorkspaceId = '20202020-1c25-4d02-bf25-6aeccf7ea419';
|
|
||||||
const mockSchemaName = getWorkspaceSchemaName(mockWorkspaceId);
|
|
||||||
const mockObjectMetadataId = '20202020-1c25-4d02-bf25-6aeccf7ea418';
|
|
||||||
const mockFieldMetadataId = '20202020-1c25-4d02-bf25-6aeccf7ea417';
|
|
||||||
|
|
||||||
const createMockFlatObjectMetadataMaps = (
|
|
||||||
objectMetadata: any,
|
|
||||||
fieldMetadata: any,
|
|
||||||
) => ({
|
|
||||||
byId: {
|
|
||||||
[mockObjectMetadataId]: {
|
|
||||||
...objectMetadata,
|
|
||||||
fieldsById: {
|
|
||||||
[mockFieldMetadataId]: fieldMetadata,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
idByNameSingular: {
|
|
||||||
[objectMetadata.nameSingular]: mockObjectMetadataId,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
beforeEach(() => {
|
|
||||||
mockSchemaManagerService = {
|
|
||||||
columnManager: {
|
|
||||||
dropColumns: jest.fn(),
|
|
||||||
addColumns: jest.fn(),
|
|
||||||
renameColumn: jest.fn(),
|
|
||||||
alterColumnDefault: jest.fn(),
|
|
||||||
},
|
|
||||||
enumManager: {
|
|
||||||
createEnum: jest.fn(),
|
|
||||||
dropEnum: jest.fn(),
|
|
||||||
renameEnum: jest.fn(),
|
|
||||||
alterEnumValues: jest.fn(),
|
|
||||||
},
|
|
||||||
tableManager: {
|
|
||||||
createTable: jest.fn(),
|
|
||||||
dropTable: jest.fn(),
|
|
||||||
renameTable: jest.fn(),
|
|
||||||
},
|
|
||||||
} as any;
|
|
||||||
|
|
||||||
mockQueryRunner = {} as any;
|
|
||||||
|
|
||||||
service = new WorkspaceSchemaFieldActionRunnerService(
|
|
||||||
mockSchemaManagerService,
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('DELETE Field Migration', () => {
|
|
||||||
it('should properly delete composite ADDRESS fields with all sub-columns', async () => {
|
|
||||||
const addressField = getFlatFieldMetadataMock({
|
|
||||||
id: mockFieldMetadataId,
|
|
||||||
objectMetadataId: mockObjectMetadataId,
|
|
||||||
type: FieldMetadataType.ADDRESS,
|
|
||||||
name: 'homeAddress',
|
|
||||||
uniqueIdentifier: 'homeAddress',
|
|
||||||
});
|
|
||||||
|
|
||||||
const objectMetadata = getFlatObjectMetadataMock({
|
|
||||||
id: mockObjectMetadataId,
|
|
||||||
workspaceId: mockWorkspaceId,
|
|
||||||
nameSingular: 'person',
|
|
||||||
uniqueIdentifier: 'person',
|
|
||||||
});
|
|
||||||
|
|
||||||
const flatObjectMetadataMaps = createMockFlatObjectMetadataMaps(
|
|
||||||
objectMetadata,
|
|
||||||
addressField,
|
|
||||||
);
|
|
||||||
|
|
||||||
await service.runDeleteFieldSchemaMigration({
|
|
||||||
action: {
|
|
||||||
type: 'delete_field',
|
|
||||||
fieldMetadataId: mockFieldMetadataId,
|
|
||||||
objectMetadataId: mockObjectMetadataId,
|
|
||||||
},
|
|
||||||
queryRunner: mockQueryRunner,
|
|
||||||
flatObjectMetadataMaps,
|
|
||||||
});
|
|
||||||
|
|
||||||
// All 8 ADDRESS composite columns must be dropped
|
|
||||||
expect(
|
|
||||||
mockSchemaManagerService.columnManager.dropColumns,
|
|
||||||
).toHaveBeenCalledWith({
|
|
||||||
queryRunner: mockQueryRunner,
|
|
||||||
schemaName: mockSchemaName,
|
|
||||||
tableName: '_person',
|
|
||||||
columnNames: [
|
|
||||||
'homeAddressAddressStreet1',
|
|
||||||
'homeAddressAddressStreet2',
|
|
||||||
'homeAddressAddressCity',
|
|
||||||
'homeAddressAddressPostcode',
|
|
||||||
'homeAddressAddressState',
|
|
||||||
'homeAddressAddressCountry',
|
|
||||||
'homeAddressAddressLat',
|
|
||||||
'homeAddressAddressLng',
|
|
||||||
],
|
|
||||||
});
|
|
||||||
|
|
||||||
// No enum operations should be performed for ADDRESS fields
|
|
||||||
expect(
|
|
||||||
mockSchemaManagerService.enumManager.dropEnum,
|
|
||||||
).not.toHaveBeenCalled();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should properly delete SELECT fields with enum cleanup', async () => {
|
|
||||||
const selectField = getFlatFieldMetadataMock({
|
|
||||||
id: mockFieldMetadataId,
|
|
||||||
objectMetadataId: mockObjectMetadataId,
|
|
||||||
type: FieldMetadataType.SELECT,
|
|
||||||
name: 'status',
|
|
||||||
options: [
|
|
||||||
{
|
|
||||||
id: '1',
|
|
||||||
value: 'ACTIVE',
|
|
||||||
label: 'Active',
|
|
||||||
color: 'green',
|
|
||||||
position: 0,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: '2',
|
|
||||||
value: 'INACTIVE',
|
|
||||||
label: 'Inactive',
|
|
||||||
color: 'red',
|
|
||||||
position: 1,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
uniqueIdentifier: 'status',
|
|
||||||
});
|
|
||||||
|
|
||||||
const objectMetadata = getFlatObjectMetadataMock({
|
|
||||||
id: mockObjectMetadataId,
|
|
||||||
workspaceId: mockWorkspaceId,
|
|
||||||
nameSingular: 'person',
|
|
||||||
uniqueIdentifier: 'person',
|
|
||||||
});
|
|
||||||
|
|
||||||
const flatObjectMetadataMaps = createMockFlatObjectMetadataMaps(
|
|
||||||
objectMetadata,
|
|
||||||
selectField,
|
|
||||||
);
|
|
||||||
|
|
||||||
await service.runDeleteFieldSchemaMigration({
|
|
||||||
action: {
|
|
||||||
type: 'delete_field',
|
|
||||||
fieldMetadataId: mockFieldMetadataId,
|
|
||||||
objectMetadataId: mockObjectMetadataId,
|
|
||||||
},
|
|
||||||
queryRunner: mockQueryRunner,
|
|
||||||
flatObjectMetadataMaps,
|
|
||||||
});
|
|
||||||
|
|
||||||
// Column must be dropped first
|
|
||||||
expect(
|
|
||||||
mockSchemaManagerService.columnManager.dropColumns,
|
|
||||||
).toHaveBeenCalledWith({
|
|
||||||
queryRunner: mockQueryRunner,
|
|
||||||
schemaName: mockSchemaName,
|
|
||||||
tableName: '_person',
|
|
||||||
columnNames: ['status'],
|
|
||||||
});
|
|
||||||
|
|
||||||
// Enum type must be properly cleaned up
|
|
||||||
expect(
|
|
||||||
mockSchemaManagerService.enumManager.dropEnum,
|
|
||||||
).toHaveBeenCalledWith({
|
|
||||||
queryRunner: mockQueryRunner,
|
|
||||||
schemaName: mockSchemaName,
|
|
||||||
enumName: '_person_status_enum',
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should properly delete RELATION fields with foreign key cleanup', async () => {
|
|
||||||
const relationField = getFlatFieldMetadataMock({
|
|
||||||
id: mockFieldMetadataId,
|
|
||||||
objectMetadataId: mockObjectMetadataId,
|
|
||||||
type: FieldMetadataType.RELATION,
|
|
||||||
name: 'company',
|
|
||||||
settings: {
|
|
||||||
relationType: RelationType.MANY_TO_ONE,
|
|
||||||
joinColumnName: 'companyId',
|
|
||||||
},
|
|
||||||
uniqueIdentifier: 'company',
|
|
||||||
});
|
|
||||||
|
|
||||||
const objectMetadata = getFlatObjectMetadataMock({
|
|
||||||
id: mockObjectMetadataId,
|
|
||||||
workspaceId: mockWorkspaceId,
|
|
||||||
nameSingular: 'person',
|
|
||||||
uniqueIdentifier: 'person',
|
|
||||||
});
|
|
||||||
|
|
||||||
const flatObjectMetadataMaps = createMockFlatObjectMetadataMaps(
|
|
||||||
objectMetadata,
|
|
||||||
relationField,
|
|
||||||
);
|
|
||||||
|
|
||||||
await service.runDeleteFieldSchemaMigration({
|
|
||||||
action: {
|
|
||||||
type: 'delete_field',
|
|
||||||
fieldMetadataId: mockFieldMetadataId,
|
|
||||||
objectMetadataId: mockObjectMetadataId,
|
|
||||||
},
|
|
||||||
queryRunner: mockQueryRunner,
|
|
||||||
flatObjectMetadataMaps,
|
|
||||||
});
|
|
||||||
|
|
||||||
// Foreign key column must be dropped
|
|
||||||
expect(
|
|
||||||
mockSchemaManagerService.columnManager.dropColumns,
|
|
||||||
).toHaveBeenCalledWith({
|
|
||||||
queryRunner: mockQueryRunner,
|
|
||||||
schemaName: mockSchemaName,
|
|
||||||
tableName: '_person',
|
|
||||||
columnNames: ['companyId'],
|
|
||||||
});
|
|
||||||
|
|
||||||
// No enum operations for relation fields
|
|
||||||
expect(
|
|
||||||
mockSchemaManagerService.enumManager.dropEnum,
|
|
||||||
).not.toHaveBeenCalled();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('CREATE Field Migration', () => {
|
|
||||||
it('should create SELECT fields with proper enum-first ordering', async () => {
|
|
||||||
const selectField = getFlatFieldMetadataMock({
|
|
||||||
objectMetadataId: mockObjectMetadataId,
|
|
||||||
type: FieldMetadataType.SELECT,
|
|
||||||
name: 'priority',
|
|
||||||
options: [
|
|
||||||
{ id: '1', value: 'HIGH', label: 'High', color: 'red', position: 0 },
|
|
||||||
{ id: '2', value: 'LOW', label: 'Low', color: 'blue', position: 1 },
|
|
||||||
],
|
|
||||||
uniqueIdentifier: 'priority',
|
|
||||||
});
|
|
||||||
|
|
||||||
const objectMetadata = getFlatObjectMetadataMock({
|
|
||||||
id: mockObjectMetadataId,
|
|
||||||
workspaceId: mockWorkspaceId,
|
|
||||||
nameSingular: 'task',
|
|
||||||
uniqueIdentifier: 'task',
|
|
||||||
});
|
|
||||||
|
|
||||||
const flatObjectMetadataMaps = createMockFlatObjectMetadataMaps(
|
|
||||||
objectMetadata,
|
|
||||||
selectField,
|
|
||||||
);
|
|
||||||
|
|
||||||
await service.runCreateFieldSchemaMigration({
|
|
||||||
action: {
|
|
||||||
type: 'create_field',
|
|
||||||
flatFieldMetadata: selectField,
|
|
||||||
},
|
|
||||||
queryRunner: mockQueryRunner,
|
|
||||||
flatObjectMetadataMaps,
|
|
||||||
});
|
|
||||||
|
|
||||||
// Enum must be created BEFORE column that depends on it
|
|
||||||
const enumCreateMock = mockSchemaManagerService.enumManager
|
|
||||||
.createEnum as unknown as jest.Mock;
|
|
||||||
const addColumnsMock = mockSchemaManagerService.columnManager
|
|
||||||
.addColumns as unknown as jest.Mock;
|
|
||||||
|
|
||||||
const allCalls = [
|
|
||||||
...enumCreateMock.mock.calls.map((call: unknown[]) => ({
|
|
||||||
type: 'enum',
|
|
||||||
call,
|
|
||||||
})),
|
|
||||||
...addColumnsMock.mock.calls.map((call: unknown[]) => ({
|
|
||||||
type: 'column',
|
|
||||||
call,
|
|
||||||
})),
|
|
||||||
];
|
|
||||||
|
|
||||||
expect(allCalls).toHaveLength(2);
|
|
||||||
expect(allCalls[0].type).toBe('enum');
|
|
||||||
expect(allCalls[1].type).toBe('column');
|
|
||||||
|
|
||||||
// Enum creation with correct parameters
|
|
||||||
expect(
|
|
||||||
mockSchemaManagerService.enumManager.createEnum,
|
|
||||||
).toHaveBeenCalledWith({
|
|
||||||
queryRunner: mockQueryRunner,
|
|
||||||
schemaName: mockSchemaName,
|
|
||||||
enumName: '_task_priority_enum',
|
|
||||||
values: ['HIGH', 'LOW'],
|
|
||||||
});
|
|
||||||
|
|
||||||
// Column creation with enum reference
|
|
||||||
expect(
|
|
||||||
mockSchemaManagerService.columnManager.addColumns,
|
|
||||||
).toHaveBeenCalledWith({
|
|
||||||
queryRunner: mockQueryRunner,
|
|
||||||
schemaName: mockSchemaName,
|
|
||||||
tableName: '_task',
|
|
||||||
columnDefinitions: [
|
|
||||||
{
|
|
||||||
name: 'priority',
|
|
||||||
type: `"${mockSchemaName}"."_task_priority_enum"`,
|
|
||||||
isNullable: true,
|
|
||||||
isArray: false,
|
|
||||||
isUnique: false,
|
|
||||||
default: null,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should create CURRENCY composite fields with proper column generation', async () => {
|
|
||||||
const currencyField = getFlatFieldMetadataMock({
|
|
||||||
objectMetadataId: mockObjectMetadataId,
|
|
||||||
type: FieldMetadataType.CURRENCY,
|
|
||||||
name: 'salary',
|
|
||||||
defaultValue: {
|
|
||||||
amountMicros: '5000000000',
|
|
||||||
currencyCode: 'USD',
|
|
||||||
},
|
|
||||||
uniqueIdentifier: 'salary',
|
|
||||||
});
|
|
||||||
|
|
||||||
const objectMetadata = getFlatObjectMetadataMock({
|
|
||||||
id: mockObjectMetadataId,
|
|
||||||
workspaceId: mockWorkspaceId,
|
|
||||||
nameSingular: 'employee',
|
|
||||||
uniqueIdentifier: 'employee',
|
|
||||||
});
|
|
||||||
|
|
||||||
const flatObjectMetadataMaps = createMockFlatObjectMetadataMaps(
|
|
||||||
objectMetadata,
|
|
||||||
currencyField,
|
|
||||||
);
|
|
||||||
|
|
||||||
await service.runCreateFieldSchemaMigration({
|
|
||||||
action: {
|
|
||||||
type: 'create_field',
|
|
||||||
flatFieldMetadata: currencyField,
|
|
||||||
},
|
|
||||||
queryRunner: mockQueryRunner,
|
|
||||||
flatObjectMetadataMaps,
|
|
||||||
});
|
|
||||||
|
|
||||||
// No enum operations for composite fields
|
|
||||||
expect(
|
|
||||||
mockSchemaManagerService.enumManager.createEnum,
|
|
||||||
).not.toHaveBeenCalled();
|
|
||||||
|
|
||||||
// Both composite columns must be created
|
|
||||||
expect(
|
|
||||||
mockSchemaManagerService.columnManager.addColumns,
|
|
||||||
).toHaveBeenCalledWith({
|
|
||||||
queryRunner: mockQueryRunner,
|
|
||||||
schemaName: mockSchemaName,
|
|
||||||
tableName: '_employee',
|
|
||||||
columnDefinitions: [
|
|
||||||
{
|
|
||||||
name: 'salaryAmountMicros',
|
|
||||||
type: 'numeric',
|
|
||||||
isNullable: true,
|
|
||||||
isUnique: false,
|
|
||||||
default: '5000000000',
|
|
||||||
isArray: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'salaryCurrencyCode',
|
|
||||||
type: 'text',
|
|
||||||
isNullable: true,
|
|
||||||
isUnique: false,
|
|
||||||
default: 'USD',
|
|
||||||
isArray: false,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('UPDATE Field Migration', () => {
|
|
||||||
it('should handle field name updates with composite field column renaming', async () => {
|
|
||||||
const addressField = getFlatFieldMetadataMock({
|
|
||||||
id: mockFieldMetadataId,
|
|
||||||
objectMetadataId: mockObjectMetadataId,
|
|
||||||
type: FieldMetadataType.ADDRESS,
|
|
||||||
name: 'oldAddress',
|
|
||||||
uniqueIdentifier: 'oldAddress',
|
|
||||||
});
|
|
||||||
|
|
||||||
const objectMetadata = getFlatObjectMetadataMock({
|
|
||||||
id: mockObjectMetadataId,
|
|
||||||
workspaceId: mockWorkspaceId,
|
|
||||||
nameSingular: 'company',
|
|
||||||
uniqueIdentifier: 'company',
|
|
||||||
});
|
|
||||||
|
|
||||||
const flatObjectMetadataMaps = createMockFlatObjectMetadataMaps(
|
|
||||||
objectMetadata,
|
|
||||||
addressField,
|
|
||||||
);
|
|
||||||
|
|
||||||
await service.runUpdateFieldSchemaMigration({
|
|
||||||
action: {
|
|
||||||
type: 'update_field',
|
|
||||||
workspaceId: mockWorkspaceId,
|
|
||||||
fieldMetadataId: mockFieldMetadataId,
|
|
||||||
objectMetadataId: mockObjectMetadataId,
|
|
||||||
updates: [
|
|
||||||
{
|
|
||||||
property: 'name',
|
|
||||||
from: 'oldAddress',
|
|
||||||
to: 'newAddress',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
queryRunner: mockQueryRunner,
|
|
||||||
flatObjectMetadataMaps,
|
|
||||||
});
|
|
||||||
|
|
||||||
// All 8 ADDRESS composite columns must be renamed
|
|
||||||
const expectedRenameCalls = [
|
|
||||||
['oldAddressAddressStreet1', 'newAddressAddressStreet1'],
|
|
||||||
['oldAddressAddressStreet2', 'newAddressAddressStreet2'],
|
|
||||||
['oldAddressAddressCity', 'newAddressAddressCity'],
|
|
||||||
['oldAddressAddressPostcode', 'newAddressAddressPostcode'],
|
|
||||||
['oldAddressAddressState', 'newAddressAddressState'],
|
|
||||||
['oldAddressAddressCountry', 'newAddressAddressCountry'],
|
|
||||||
['oldAddressAddressLat', 'newAddressAddressLat'],
|
|
||||||
['oldAddressAddressLng', 'newAddressAddressLng'],
|
|
||||||
];
|
|
||||||
|
|
||||||
expect(
|
|
||||||
mockSchemaManagerService.columnManager.renameColumn,
|
|
||||||
).toHaveBeenCalledTimes(8);
|
|
||||||
|
|
||||||
expectedRenameCalls.forEach(([fromName, toName]) => {
|
|
||||||
expect(
|
|
||||||
mockSchemaManagerService.columnManager.renameColumn,
|
|
||||||
).toHaveBeenCalledWith({
|
|
||||||
queryRunner: mockQueryRunner,
|
|
||||||
schemaName: mockSchemaName,
|
|
||||||
tableName: '_company',
|
|
||||||
oldColumnName: fromName,
|
|
||||||
newColumnName: toName,
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should handle enum field option updates with proper value mapping', async () => {
|
|
||||||
const selectField = getFlatFieldMetadataMock({
|
|
||||||
id: mockFieldMetadataId,
|
|
||||||
objectMetadataId: mockObjectMetadataId,
|
|
||||||
type: FieldMetadataType.SELECT,
|
|
||||||
name: 'status',
|
|
||||||
options: [
|
|
||||||
{
|
|
||||||
id: '1',
|
|
||||||
value: 'UPDATED_ACTIVE',
|
|
||||||
label: 'Updated Active',
|
|
||||||
color: 'green',
|
|
||||||
position: 0,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: '2',
|
|
||||||
value: 'UPDATED_INACTIVE',
|
|
||||||
label: 'Updated Inactive',
|
|
||||||
color: 'red',
|
|
||||||
position: 1,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
uniqueIdentifier: 'status',
|
|
||||||
});
|
|
||||||
|
|
||||||
const objectMetadata = getFlatObjectMetadataMock({
|
|
||||||
id: mockObjectMetadataId,
|
|
||||||
workspaceId: mockWorkspaceId,
|
|
||||||
nameSingular: 'person',
|
|
||||||
uniqueIdentifier: 'person',
|
|
||||||
});
|
|
||||||
|
|
||||||
const flatObjectMetadataMaps = createMockFlatObjectMetadataMaps(
|
|
||||||
objectMetadata,
|
|
||||||
selectField,
|
|
||||||
);
|
|
||||||
|
|
||||||
await service.runUpdateFieldSchemaMigration({
|
|
||||||
action: {
|
|
||||||
type: 'update_field',
|
|
||||||
workspaceId: mockWorkspaceId,
|
|
||||||
fieldMetadataId: mockFieldMetadataId,
|
|
||||||
objectMetadataId: mockObjectMetadataId,
|
|
||||||
updates: [
|
|
||||||
{
|
|
||||||
property: 'options',
|
|
||||||
from: [
|
|
||||||
{
|
|
||||||
id: '1',
|
|
||||||
value: 'ACTIVE',
|
|
||||||
label: 'Active',
|
|
||||||
color: 'green',
|
|
||||||
position: 0,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: '2',
|
|
||||||
value: 'INACTIVE',
|
|
||||||
label: 'Inactive',
|
|
||||||
color: 'red',
|
|
||||||
position: 1,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
to: [
|
|
||||||
{
|
|
||||||
id: '1',
|
|
||||||
value: 'UPDATED_ACTIVE',
|
|
||||||
label: 'Updated Active',
|
|
||||||
color: 'green',
|
|
||||||
position: 0,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: '2',
|
|
||||||
value: 'UPDATED_INACTIVE',
|
|
||||||
label: 'Updated Inactive',
|
|
||||||
color: 'red',
|
|
||||||
position: 1,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
queryRunner: mockQueryRunner,
|
|
||||||
flatObjectMetadataMaps,
|
|
||||||
});
|
|
||||||
|
|
||||||
// Enum values must be updated with proper mapping
|
|
||||||
expect(
|
|
||||||
mockSchemaManagerService.enumManager.alterEnumValues,
|
|
||||||
).toHaveBeenCalledWith({
|
|
||||||
queryRunner: mockQueryRunner,
|
|
||||||
schemaName: mockSchemaName,
|
|
||||||
tableName: '_person',
|
|
||||||
enumValues: ['UPDATED_ACTIVE', 'UPDATED_INACTIVE'],
|
|
||||||
columnDefinition: {
|
|
||||||
name: 'status',
|
|
||||||
type: `"${mockSchemaName}"."_person_status_enum"`,
|
|
||||||
isNullable: true,
|
|
||||||
isArray: false,
|
|
||||||
isUnique: false,
|
|
||||||
default: null,
|
|
||||||
},
|
|
||||||
oldToNewEnumOptionMap: {
|
|
||||||
ACTIVE: 'UPDATED_ACTIVE', // Keep original values
|
|
||||||
INACTIVE: 'UPDATED_INACTIVE',
|
|
||||||
},
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should handle default value updates for composite fields', async () => {
|
|
||||||
const currencyField = getFlatFieldMetadataMock({
|
|
||||||
id: mockFieldMetadataId,
|
|
||||||
objectMetadataId: mockObjectMetadataId,
|
|
||||||
type: FieldMetadataType.CURRENCY,
|
|
||||||
name: 'price',
|
|
||||||
defaultValue: {
|
|
||||||
amountMicros: "'0'",
|
|
||||||
currencyCode: "'USD'",
|
|
||||||
},
|
|
||||||
uniqueIdentifier: 'price',
|
|
||||||
});
|
|
||||||
|
|
||||||
const objectMetadata = getFlatObjectMetadataMock({
|
|
||||||
id: mockObjectMetadataId,
|
|
||||||
workspaceId: mockWorkspaceId,
|
|
||||||
nameSingular: 'product',
|
|
||||||
uniqueIdentifier: 'product',
|
|
||||||
});
|
|
||||||
|
|
||||||
const flatObjectMetadataMaps = createMockFlatObjectMetadataMaps(
|
|
||||||
objectMetadata,
|
|
||||||
currencyField,
|
|
||||||
);
|
|
||||||
|
|
||||||
await service.runUpdateFieldSchemaMigration({
|
|
||||||
action: {
|
|
||||||
type: 'update_field',
|
|
||||||
workspaceId: mockWorkspaceId,
|
|
||||||
fieldMetadataId: mockFieldMetadataId,
|
|
||||||
objectMetadataId: mockObjectMetadataId,
|
|
||||||
updates: [
|
|
||||||
{
|
|
||||||
property: 'defaultValue',
|
|
||||||
from: {
|
|
||||||
amountMicros: "'0'",
|
|
||||||
currencyCode: "'USD'",
|
|
||||||
},
|
|
||||||
to: {
|
|
||||||
amountMicros: "'100000000'",
|
|
||||||
currencyCode: "'EUR'",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
queryRunner: mockQueryRunner,
|
|
||||||
flatObjectMetadataMaps,
|
|
||||||
});
|
|
||||||
|
|
||||||
// Both composite column defaults must be updated
|
|
||||||
expect(
|
|
||||||
mockSchemaManagerService.columnManager.alterColumnDefault,
|
|
||||||
).toHaveBeenCalledWith({
|
|
||||||
queryRunner: mockQueryRunner,
|
|
||||||
schemaName: mockSchemaName,
|
|
||||||
tableName: '_product',
|
|
||||||
columnName: 'priceAmountMicros',
|
|
||||||
defaultValue: '100000000',
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(
|
|
||||||
mockSchemaManagerService.columnManager.alterColumnDefault,
|
|
||||||
).toHaveBeenCalledWith({
|
|
||||||
queryRunner: mockQueryRunner,
|
|
||||||
schemaName: mockSchemaName,
|
|
||||||
tableName: '_product',
|
|
||||||
columnName: 'priceCurrencyCode',
|
|
||||||
defaultValue: 'EUR',
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('Error Handling and Edge Cases', () => {
|
|
||||||
it('should handle fields with no join column settings gracefully', async () => {
|
|
||||||
const relationFieldWithoutSettings = getFlatFieldMetadataMock({
|
|
||||||
id: mockFieldMetadataId,
|
|
||||||
objectMetadataId: mockObjectMetadataId,
|
|
||||||
type: FieldMetadataType.RELATION,
|
|
||||||
name: 'company',
|
|
||||||
settings: undefined,
|
|
||||||
uniqueIdentifier: 'company',
|
|
||||||
});
|
|
||||||
|
|
||||||
const objectMetadata = getFlatObjectMetadataMock({
|
|
||||||
id: mockObjectMetadataId,
|
|
||||||
workspaceId: mockWorkspaceId,
|
|
||||||
nameSingular: 'person',
|
|
||||||
uniqueIdentifier: 'person',
|
|
||||||
});
|
|
||||||
|
|
||||||
const flatObjectMetadataMaps = createMockFlatObjectMetadataMaps(
|
|
||||||
objectMetadata,
|
|
||||||
relationFieldWithoutSettings,
|
|
||||||
);
|
|
||||||
|
|
||||||
await service.runDeleteFieldSchemaMigration({
|
|
||||||
action: {
|
|
||||||
type: 'delete_field',
|
|
||||||
fieldMetadataId: mockFieldMetadataId,
|
|
||||||
objectMetadataId: mockObjectMetadataId,
|
|
||||||
},
|
|
||||||
queryRunner: mockQueryRunner,
|
|
||||||
flatObjectMetadataMaps,
|
|
||||||
});
|
|
||||||
|
|
||||||
// Should not attempt to drop columns for relations without join columns
|
|
||||||
expect(
|
|
||||||
mockSchemaManagerService.columnManager.dropColumns,
|
|
||||||
).toHaveBeenCalledWith({
|
|
||||||
queryRunner: mockQueryRunner,
|
|
||||||
schemaName: mockSchemaName,
|
|
||||||
tableName: '_person',
|
|
||||||
columnNames: [], // Empty array - no columns to drop
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should handle enum fields without options safely', async () => {
|
|
||||||
const selectFieldWithoutOptions = getFlatFieldMetadataMock({
|
|
||||||
id: mockFieldMetadataId,
|
|
||||||
objectMetadataId: mockObjectMetadataId,
|
|
||||||
type: FieldMetadataType.SELECT,
|
|
||||||
name: 'emptyStatus',
|
|
||||||
options: [],
|
|
||||||
uniqueIdentifier: 'emptyStatus',
|
|
||||||
});
|
|
||||||
|
|
||||||
const objectMetadata = getFlatObjectMetadataMock({
|
|
||||||
id: mockObjectMetadataId,
|
|
||||||
workspaceId: mockWorkspaceId,
|
|
||||||
nameSingular: 'test',
|
|
||||||
uniqueIdentifier: 'test',
|
|
||||||
});
|
|
||||||
|
|
||||||
const flatObjectMetadataMaps = createMockFlatObjectMetadataMaps(
|
|
||||||
objectMetadata,
|
|
||||||
selectFieldWithoutOptions,
|
|
||||||
);
|
|
||||||
|
|
||||||
await service.runCreateFieldSchemaMigration({
|
|
||||||
action: {
|
|
||||||
type: 'create_field',
|
|
||||||
flatFieldMetadata: selectFieldWithoutOptions,
|
|
||||||
},
|
|
||||||
queryRunner: mockQueryRunner,
|
|
||||||
flatObjectMetadataMaps,
|
|
||||||
});
|
|
||||||
|
|
||||||
// Should still create enum even with empty values
|
|
||||||
expect(
|
|
||||||
mockSchemaManagerService.enumManager.createEnum,
|
|
||||||
).toHaveBeenCalledWith({
|
|
||||||
queryRunner: mockQueryRunner,
|
|
||||||
schemaName: mockSchemaName,
|
|
||||||
enumName: '_test_emptyStatus_enum',
|
|
||||||
values: [], // Empty enum values array
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
-873
@@ -1,873 +0,0 @@
|
|||||||
import { FieldMetadataType } from 'twenty-shared/types';
|
|
||||||
import { type QueryRunner } from 'typeorm';
|
|
||||||
|
|
||||||
import { getFlatFieldMetadataMock } from 'src/engine/metadata-modules/flat-field-metadata/__mocks__/get-flat-field-metadata.mock';
|
|
||||||
import { getFlatObjectMetadataMock } from 'src/engine/metadata-modules/flat-object-metadata/__mocks__/get-flat-object-metadata.mock';
|
|
||||||
import { type WorkspaceSchemaManagerService } from 'src/engine/twenty-orm/workspace-schema-manager/workspace-schema-manager.service';
|
|
||||||
import { getWorkspaceSchemaName } from 'src/engine/workspace-datasource/utils/get-workspace-schema-name.util';
|
|
||||||
import { WorkspaceSchemaObjectActionRunnerService } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/workspace-schema-migration-runner/workspace-schema-object-action-runner.service';
|
|
||||||
|
|
||||||
describe('WorkspaceSchemaObjectActionRunner', () => {
|
|
||||||
let service: WorkspaceSchemaObjectActionRunnerService;
|
|
||||||
let mockSchemaManagerService: jest.Mocked<WorkspaceSchemaManagerService>;
|
|
||||||
let mockQueryRunner: jest.Mocked<QueryRunner>;
|
|
||||||
|
|
||||||
const mockWorkspaceId = '20202020-1c25-4d02-bf25-6aeccf7ea419';
|
|
||||||
const mockObjectMetadataId = '20202020-1c25-4d02-bf25-6aeccf7ea418';
|
|
||||||
const mockSchemaName = getWorkspaceSchemaName(mockWorkspaceId);
|
|
||||||
|
|
||||||
const createMockFlatObjectMetadataMaps = (
|
|
||||||
objectMetadata: any,
|
|
||||||
fields: any[] = [],
|
|
||||||
) => {
|
|
||||||
const fieldsById = fields.reduce((acc, field, index) => {
|
|
||||||
const fieldId = `field-${index}`;
|
|
||||||
|
|
||||||
acc[fieldId] = { ...field, id: fieldId };
|
|
||||||
|
|
||||||
return acc;
|
|
||||||
}, {});
|
|
||||||
|
|
||||||
return {
|
|
||||||
byId: {
|
|
||||||
[mockObjectMetadataId]: {
|
|
||||||
...objectMetadata,
|
|
||||||
fieldsById,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
idByNameSingular: {
|
|
||||||
[objectMetadata.nameSingular]: mockObjectMetadataId,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
beforeEach(() => {
|
|
||||||
mockSchemaManagerService = {
|
|
||||||
columnManager: {
|
|
||||||
dropColumns: jest.fn(),
|
|
||||||
addColumns: jest.fn(),
|
|
||||||
renameColumn: jest.fn(),
|
|
||||||
alterColumnDefault: jest.fn(),
|
|
||||||
},
|
|
||||||
enumManager: {
|
|
||||||
createEnum: jest.fn(),
|
|
||||||
dropEnum: jest.fn(),
|
|
||||||
renameEnum: jest.fn(),
|
|
||||||
alterEnumValues: jest.fn(),
|
|
||||||
},
|
|
||||||
tableManager: {
|
|
||||||
createTable: jest.fn(),
|
|
||||||
dropTable: jest.fn(),
|
|
||||||
renameTable: jest.fn(),
|
|
||||||
},
|
|
||||||
} as any;
|
|
||||||
|
|
||||||
mockQueryRunner = {} as any;
|
|
||||||
|
|
||||||
service = new WorkspaceSchemaObjectActionRunnerService(
|
|
||||||
mockSchemaManagerService,
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('DELETE Object Migration', () => {
|
|
||||||
it('should properly delete objects with SELECT field enum cleanup', async () => {
|
|
||||||
const selectField1 = getFlatFieldMetadataMock({
|
|
||||||
objectMetadataId: mockObjectMetadataId,
|
|
||||||
type: FieldMetadataType.SELECT,
|
|
||||||
name: 'status',
|
|
||||||
uniqueIdentifier: 'status',
|
|
||||||
options: [
|
|
||||||
{
|
|
||||||
id: '1',
|
|
||||||
value: 'ACTIVE',
|
|
||||||
label: 'Active',
|
|
||||||
color: 'green',
|
|
||||||
position: 0,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
});
|
|
||||||
|
|
||||||
const selectField2 = getFlatFieldMetadataMock({
|
|
||||||
objectMetadataId: mockObjectMetadataId,
|
|
||||||
type: FieldMetadataType.SELECT,
|
|
||||||
name: 'priority',
|
|
||||||
uniqueIdentifier: 'priority',
|
|
||||||
options: [
|
|
||||||
{ id: '1', value: 'HIGH', label: 'High', color: 'red', position: 0 },
|
|
||||||
],
|
|
||||||
});
|
|
||||||
|
|
||||||
const objectMetadata = getFlatObjectMetadataMock({
|
|
||||||
id: mockObjectMetadataId,
|
|
||||||
workspaceId: mockWorkspaceId,
|
|
||||||
nameSingular: 'task',
|
|
||||||
uniqueIdentifier: 'task',
|
|
||||||
});
|
|
||||||
|
|
||||||
const flatObjectMetadataMaps = createMockFlatObjectMetadataMaps(
|
|
||||||
objectMetadata,
|
|
||||||
[selectField1, selectField2],
|
|
||||||
);
|
|
||||||
|
|
||||||
await service.runDeleteObjectSchemaMigration({
|
|
||||||
action: {
|
|
||||||
type: 'delete_object',
|
|
||||||
objectMetadataId: mockObjectMetadataId,
|
|
||||||
},
|
|
||||||
queryRunner: mockQueryRunner,
|
|
||||||
flatObjectMetadataMaps,
|
|
||||||
});
|
|
||||||
|
|
||||||
// Table must be dropped first
|
|
||||||
expect(
|
|
||||||
mockSchemaManagerService.tableManager.dropTable,
|
|
||||||
).toHaveBeenCalledWith({
|
|
||||||
queryRunner: mockQueryRunner,
|
|
||||||
schemaName: mockSchemaName,
|
|
||||||
tableName: '_task',
|
|
||||||
});
|
|
||||||
|
|
||||||
// All enum types associated with the object must be cleaned up
|
|
||||||
expect(
|
|
||||||
mockSchemaManagerService.enumManager.dropEnum,
|
|
||||||
).toHaveBeenCalledTimes(2);
|
|
||||||
expect(
|
|
||||||
mockSchemaManagerService.enumManager.dropEnum,
|
|
||||||
).toHaveBeenCalledWith({
|
|
||||||
queryRunner: mockQueryRunner,
|
|
||||||
schemaName: mockSchemaName,
|
|
||||||
enumName: '_task_status_enum',
|
|
||||||
});
|
|
||||||
expect(
|
|
||||||
mockSchemaManagerService.enumManager.dropEnum,
|
|
||||||
).toHaveBeenCalledWith({
|
|
||||||
queryRunner: mockQueryRunner,
|
|
||||||
schemaName: mockSchemaName,
|
|
||||||
enumName: '_task_priority_enum',
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should handle object deletion with mixed field types', async () => {
|
|
||||||
const textField = getFlatFieldMetadataMock({
|
|
||||||
objectMetadataId: mockObjectMetadataId,
|
|
||||||
type: FieldMetadataType.TEXT,
|
|
||||||
name: 'description',
|
|
||||||
uniqueIdentifier: 'description',
|
|
||||||
});
|
|
||||||
|
|
||||||
const addressField = getFlatFieldMetadataMock({
|
|
||||||
objectMetadataId: mockObjectMetadataId,
|
|
||||||
type: FieldMetadataType.ADDRESS,
|
|
||||||
name: 'location',
|
|
||||||
uniqueIdentifier: 'location',
|
|
||||||
});
|
|
||||||
|
|
||||||
const multiSelectField = getFlatFieldMetadataMock({
|
|
||||||
objectMetadataId: mockObjectMetadataId,
|
|
||||||
type: FieldMetadataType.MULTI_SELECT,
|
|
||||||
name: 'tags',
|
|
||||||
uniqueIdentifier: 'tags',
|
|
||||||
options: [
|
|
||||||
{
|
|
||||||
id: '1',
|
|
||||||
value: 'URGENT',
|
|
||||||
label: 'Urgent',
|
|
||||||
color: 'red',
|
|
||||||
position: 0,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
});
|
|
||||||
|
|
||||||
const objectMetadata = getFlatObjectMetadataMock({
|
|
||||||
id: mockObjectMetadataId,
|
|
||||||
workspaceId: mockWorkspaceId,
|
|
||||||
nameSingular: 'project',
|
|
||||||
uniqueIdentifier: 'project',
|
|
||||||
});
|
|
||||||
|
|
||||||
const flatObjectMetadataMaps = createMockFlatObjectMetadataMaps(
|
|
||||||
objectMetadata,
|
|
||||||
[textField, addressField, multiSelectField],
|
|
||||||
);
|
|
||||||
|
|
||||||
await service.runDeleteObjectSchemaMigration({
|
|
||||||
action: {
|
|
||||||
type: 'delete_object',
|
|
||||||
objectMetadataId: mockObjectMetadataId,
|
|
||||||
},
|
|
||||||
queryRunner: mockQueryRunner,
|
|
||||||
flatObjectMetadataMaps,
|
|
||||||
});
|
|
||||||
|
|
||||||
// Table must be dropped
|
|
||||||
expect(
|
|
||||||
mockSchemaManagerService.tableManager.dropTable,
|
|
||||||
).toHaveBeenCalledWith({
|
|
||||||
queryRunner: mockQueryRunner,
|
|
||||||
schemaName: mockSchemaName,
|
|
||||||
tableName: '_project',
|
|
||||||
});
|
|
||||||
|
|
||||||
// Only enum fields should trigger enum cleanup
|
|
||||||
expect(
|
|
||||||
mockSchemaManagerService.enumManager.dropEnum,
|
|
||||||
).toHaveBeenCalledTimes(1);
|
|
||||||
expect(
|
|
||||||
mockSchemaManagerService.enumManager.dropEnum,
|
|
||||||
).toHaveBeenCalledWith({
|
|
||||||
queryRunner: mockQueryRunner,
|
|
||||||
schemaName: mockSchemaName,
|
|
||||||
enumName: '_project_tags_enum',
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should handle object deletion with no enum fields', async () => {
|
|
||||||
const textField = getFlatFieldMetadataMock({
|
|
||||||
objectMetadataId: mockObjectMetadataId,
|
|
||||||
type: FieldMetadataType.TEXT,
|
|
||||||
name: 'name',
|
|
||||||
uniqueIdentifier: 'name',
|
|
||||||
});
|
|
||||||
|
|
||||||
const objectMetadata = getFlatObjectMetadataMock({
|
|
||||||
id: mockObjectMetadataId,
|
|
||||||
workspaceId: mockWorkspaceId,
|
|
||||||
nameSingular: 'simpleObject',
|
|
||||||
uniqueIdentifier: 'simpleObject',
|
|
||||||
});
|
|
||||||
|
|
||||||
const flatObjectMetadataMaps = createMockFlatObjectMetadataMaps(
|
|
||||||
objectMetadata,
|
|
||||||
[textField],
|
|
||||||
);
|
|
||||||
|
|
||||||
await service.runDeleteObjectSchemaMigration({
|
|
||||||
action: {
|
|
||||||
type: 'delete_object',
|
|
||||||
objectMetadataId: mockObjectMetadataId,
|
|
||||||
},
|
|
||||||
queryRunner: mockQueryRunner,
|
|
||||||
flatObjectMetadataMaps,
|
|
||||||
});
|
|
||||||
|
|
||||||
// Table must be dropped
|
|
||||||
expect(
|
|
||||||
mockSchemaManagerService.tableManager.dropTable,
|
|
||||||
).toHaveBeenCalledWith({
|
|
||||||
queryRunner: mockQueryRunner,
|
|
||||||
schemaName: mockSchemaName,
|
|
||||||
tableName: '_simpleObject',
|
|
||||||
});
|
|
||||||
|
|
||||||
// No enum cleanup should occur
|
|
||||||
expect(
|
|
||||||
mockSchemaManagerService.enumManager.dropEnum,
|
|
||||||
).not.toHaveBeenCalled();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('CREATE Object Migration', () => {
|
|
||||||
it('should create objects with mixed field types and proper column generation', async () => {
|
|
||||||
const textFieldAction = {
|
|
||||||
type: 'create_field' as const,
|
|
||||||
flatFieldMetadata: getFlatFieldMetadataMock({
|
|
||||||
objectMetadataId: mockObjectMetadataId,
|
|
||||||
type: FieldMetadataType.TEXT,
|
|
||||||
name: 'title',
|
|
||||||
uniqueIdentifier: 'title',
|
|
||||||
}),
|
|
||||||
};
|
|
||||||
|
|
||||||
const selectFieldAction = {
|
|
||||||
type: 'create_field' as const,
|
|
||||||
flatFieldMetadata: getFlatFieldMetadataMock({
|
|
||||||
objectMetadataId: mockObjectMetadataId,
|
|
||||||
type: FieldMetadataType.SELECT,
|
|
||||||
name: 'status',
|
|
||||||
uniqueIdentifier: 'status',
|
|
||||||
options: [
|
|
||||||
{
|
|
||||||
id: '1',
|
|
||||||
value: 'DRAFT',
|
|
||||||
label: 'Draft',
|
|
||||||
color: 'gray',
|
|
||||||
position: 0,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: '2',
|
|
||||||
value: 'PUBLISHED',
|
|
||||||
label: 'Published',
|
|
||||||
color: 'green',
|
|
||||||
position: 1,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
}),
|
|
||||||
};
|
|
||||||
|
|
||||||
const currencyFieldAction = {
|
|
||||||
type: 'create_field' as const,
|
|
||||||
flatFieldMetadata: getFlatFieldMetadataMock({
|
|
||||||
objectMetadataId: mockObjectMetadataId,
|
|
||||||
type: FieldMetadataType.CURRENCY,
|
|
||||||
name: 'price',
|
|
||||||
uniqueIdentifier: 'price',
|
|
||||||
}),
|
|
||||||
};
|
|
||||||
|
|
||||||
const objectMetadata = getFlatObjectMetadataMock({
|
|
||||||
id: mockObjectMetadataId,
|
|
||||||
workspaceId: mockWorkspaceId,
|
|
||||||
nameSingular: 'article',
|
|
||||||
uniqueIdentifier: 'article',
|
|
||||||
});
|
|
||||||
|
|
||||||
await service.runCreateObjectSchemaMigration({
|
|
||||||
action: {
|
|
||||||
type: 'create_object',
|
|
||||||
flatObjectMetadataWithoutFields: objectMetadata,
|
|
||||||
createFieldActions: [
|
|
||||||
textFieldAction,
|
|
||||||
selectFieldAction,
|
|
||||||
currencyFieldAction,
|
|
||||||
],
|
|
||||||
},
|
|
||||||
queryRunner: mockQueryRunner,
|
|
||||||
flatObjectMetadataMaps: {
|
|
||||||
byId: {
|
|
||||||
[mockObjectMetadataId]: {
|
|
||||||
...objectMetadata,
|
|
||||||
fieldsById: {},
|
|
||||||
fieldIdByJoinColumnName: {},
|
|
||||||
fieldIdByName: {},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
idByNameSingular: {
|
|
||||||
[objectMetadata.nameSingular]: mockObjectMetadataId,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
// Table must be created with all field columns and enums
|
|
||||||
expect(
|
|
||||||
mockSchemaManagerService.tableManager.createTable,
|
|
||||||
).toHaveBeenCalledWith({
|
|
||||||
queryRunner: mockQueryRunner,
|
|
||||||
schemaName: mockSchemaName,
|
|
||||||
tableName: '_article',
|
|
||||||
columnDefinitions: [
|
|
||||||
// TEXT field column
|
|
||||||
{
|
|
||||||
name: 'title',
|
|
||||||
type: 'text',
|
|
||||||
isNullable: true,
|
|
||||||
isArray: false,
|
|
||||||
isUnique: false,
|
|
||||||
default: null,
|
|
||||||
},
|
|
||||||
// SELECT field column
|
|
||||||
{
|
|
||||||
name: 'status',
|
|
||||||
type: `"${mockSchemaName}"."_article_status_enum"`,
|
|
||||||
isNullable: true,
|
|
||||||
isArray: false,
|
|
||||||
isUnique: false,
|
|
||||||
default: null,
|
|
||||||
},
|
|
||||||
// CURRENCY field columns (2 composite columns)
|
|
||||||
{
|
|
||||||
name: 'priceAmountMicros',
|
|
||||||
type: 'numeric',
|
|
||||||
isNullable: true,
|
|
||||||
isArray: false,
|
|
||||||
isUnique: false,
|
|
||||||
default: null,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'priceCurrencyCode',
|
|
||||||
type: 'text',
|
|
||||||
isNullable: true,
|
|
||||||
isArray: false,
|
|
||||||
isUnique: false,
|
|
||||||
default: null,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(
|
|
||||||
mockSchemaManagerService.enumManager.createEnum,
|
|
||||||
).toHaveBeenCalledWith({
|
|
||||||
queryRunner: mockQueryRunner,
|
|
||||||
schemaName: mockSchemaName,
|
|
||||||
enumName: '_article_status_enum',
|
|
||||||
values: ['DRAFT', 'PUBLISHED'],
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should handle object creation with ADDRESS composite fields', async () => {
|
|
||||||
const addressFieldAction = {
|
|
||||||
type: 'create_field' as const,
|
|
||||||
flatFieldMetadata: getFlatFieldMetadataMock({
|
|
||||||
objectMetadataId: mockObjectMetadataId,
|
|
||||||
type: FieldMetadataType.ADDRESS,
|
|
||||||
name: 'headquarters',
|
|
||||||
isNullable: false, // Test non-nullable composite field
|
|
||||||
uniqueIdentifier: 'headquarters',
|
|
||||||
}),
|
|
||||||
};
|
|
||||||
|
|
||||||
const objectMetadata = getFlatObjectMetadataMock({
|
|
||||||
id: mockObjectMetadataId,
|
|
||||||
workspaceId: mockWorkspaceId,
|
|
||||||
nameSingular: 'company',
|
|
||||||
uniqueIdentifier: 'company',
|
|
||||||
});
|
|
||||||
|
|
||||||
await service.runCreateObjectSchemaMigration({
|
|
||||||
action: {
|
|
||||||
type: 'create_object',
|
|
||||||
flatObjectMetadataWithoutFields: objectMetadata,
|
|
||||||
createFieldActions: [addressFieldAction],
|
|
||||||
},
|
|
||||||
queryRunner: mockQueryRunner,
|
|
||||||
flatObjectMetadataMaps: {
|
|
||||||
byId: {
|
|
||||||
[mockObjectMetadataId]: {
|
|
||||||
...objectMetadata,
|
|
||||||
fieldsById: {},
|
|
||||||
fieldIdByJoinColumnName: {},
|
|
||||||
fieldIdByName: {},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
idByNameSingular: {
|
|
||||||
[objectMetadata.nameSingular]: mockObjectMetadataId,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
// Table must be created with all 8 ADDRESS composite columns
|
|
||||||
expect(
|
|
||||||
mockSchemaManagerService.tableManager.createTable,
|
|
||||||
).toHaveBeenCalledWith({
|
|
||||||
queryRunner: mockQueryRunner,
|
|
||||||
schemaName: mockSchemaName,
|
|
||||||
tableName: '_company',
|
|
||||||
columnDefinitions: [
|
|
||||||
{
|
|
||||||
name: 'headquartersAddressStreet1',
|
|
||||||
type: 'text',
|
|
||||||
isNullable: true,
|
|
||||||
isUnique: false,
|
|
||||||
default: null,
|
|
||||||
isArray: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'headquartersAddressStreet2',
|
|
||||||
type: 'text',
|
|
||||||
isNullable: true,
|
|
||||||
isUnique: false,
|
|
||||||
default: null,
|
|
||||||
isArray: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'headquartersAddressCity',
|
|
||||||
type: 'text',
|
|
||||||
isNullable: true,
|
|
||||||
isUnique: false,
|
|
||||||
default: null,
|
|
||||||
isArray: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'headquartersAddressPostcode',
|
|
||||||
type: 'text',
|
|
||||||
isNullable: true,
|
|
||||||
isUnique: false,
|
|
||||||
default: null,
|
|
||||||
isArray: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'headquartersAddressState',
|
|
||||||
type: 'text',
|
|
||||||
isNullable: true,
|
|
||||||
isUnique: false,
|
|
||||||
default: null,
|
|
||||||
isArray: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'headquartersAddressCountry',
|
|
||||||
type: 'text',
|
|
||||||
isNullable: true,
|
|
||||||
isUnique: false,
|
|
||||||
default: null,
|
|
||||||
isArray: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'headquartersAddressLat',
|
|
||||||
type: 'numeric',
|
|
||||||
isNullable: true,
|
|
||||||
isUnique: false,
|
|
||||||
default: null,
|
|
||||||
isArray: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'headquartersAddressLng',
|
|
||||||
type: 'numeric',
|
|
||||||
isNullable: true,
|
|
||||||
isUnique: false,
|
|
||||||
default: null,
|
|
||||||
isArray: false,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should handle object creation with no field actions', async () => {
|
|
||||||
const objectMetadata = getFlatObjectMetadataMock({
|
|
||||||
id: mockObjectMetadataId,
|
|
||||||
workspaceId: mockWorkspaceId,
|
|
||||||
nameSingular: 'emptyObject',
|
|
||||||
uniqueIdentifier: 'emptyObject',
|
|
||||||
});
|
|
||||||
|
|
||||||
await service.runCreateObjectSchemaMigration({
|
|
||||||
action: {
|
|
||||||
type: 'create_object',
|
|
||||||
flatObjectMetadataWithoutFields: objectMetadata,
|
|
||||||
createFieldActions: [],
|
|
||||||
},
|
|
||||||
queryRunner: mockQueryRunner,
|
|
||||||
flatObjectMetadataMaps: {
|
|
||||||
byId: {
|
|
||||||
[mockObjectMetadataId]: {
|
|
||||||
...objectMetadata,
|
|
||||||
fieldsById: {},
|
|
||||||
fieldIdByJoinColumnName: {},
|
|
||||||
fieldIdByName: {},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
idByNameSingular: {
|
|
||||||
[objectMetadata.nameSingular]: mockObjectMetadataId,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
// Table must be created even with no columns
|
|
||||||
expect(
|
|
||||||
mockSchemaManagerService.tableManager.createTable,
|
|
||||||
).toHaveBeenCalledWith({
|
|
||||||
queryRunner: mockQueryRunner,
|
|
||||||
schemaName: mockSchemaName,
|
|
||||||
tableName: '_emptyObject',
|
|
||||||
columnDefinitions: [], // Empty columns array
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('UPDATE Object Migration', () => {
|
|
||||||
it('should handle object name changes with enum type updates', async () => {
|
|
||||||
const selectField = getFlatFieldMetadataMock({
|
|
||||||
objectMetadataId: mockObjectMetadataId,
|
|
||||||
type: FieldMetadataType.SELECT,
|
|
||||||
name: 'category',
|
|
||||||
uniqueIdentifier: 'category',
|
|
||||||
options: [
|
|
||||||
{
|
|
||||||
id: '1',
|
|
||||||
value: 'TECH',
|
|
||||||
label: 'Technology',
|
|
||||||
color: 'blue',
|
|
||||||
position: 0,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
});
|
|
||||||
|
|
||||||
const multiSelectField = getFlatFieldMetadataMock({
|
|
||||||
objectMetadataId: mockObjectMetadataId,
|
|
||||||
type: FieldMetadataType.MULTI_SELECT,
|
|
||||||
name: 'tags',
|
|
||||||
uniqueIdentifier: 'tags',
|
|
||||||
options: [
|
|
||||||
{
|
|
||||||
id: '1',
|
|
||||||
value: 'FEATURED',
|
|
||||||
label: 'Featured',
|
|
||||||
color: 'yellow',
|
|
||||||
position: 0,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
});
|
|
||||||
|
|
||||||
const objectMetadata = getFlatObjectMetadataMock({
|
|
||||||
id: mockObjectMetadataId,
|
|
||||||
workspaceId: mockWorkspaceId,
|
|
||||||
nameSingular: 'blogPost',
|
|
||||||
uniqueIdentifier: 'blogPost',
|
|
||||||
});
|
|
||||||
|
|
||||||
const flatObjectMetadataMaps = createMockFlatObjectMetadataMaps(
|
|
||||||
objectMetadata,
|
|
||||||
[selectField, multiSelectField],
|
|
||||||
);
|
|
||||||
|
|
||||||
await service.runUpdateObjectSchemaMigration({
|
|
||||||
action: {
|
|
||||||
type: 'update_object',
|
|
||||||
objectMetadataId: mockObjectMetadataId,
|
|
||||||
updates: [
|
|
||||||
{
|
|
||||||
property: 'nameSingular',
|
|
||||||
from: 'blogPost',
|
|
||||||
to: 'article',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
queryRunner: mockQueryRunner,
|
|
||||||
flatObjectMetadataMaps,
|
|
||||||
});
|
|
||||||
|
|
||||||
// Table must be renamed
|
|
||||||
expect(
|
|
||||||
mockSchemaManagerService.tableManager.renameTable,
|
|
||||||
).toHaveBeenCalledWith({
|
|
||||||
queryRunner: mockQueryRunner,
|
|
||||||
schemaName: mockSchemaName,
|
|
||||||
oldTableName: '_blogPost',
|
|
||||||
newTableName: '_article',
|
|
||||||
});
|
|
||||||
|
|
||||||
// All enum types must be renamed to match new table name
|
|
||||||
expect(
|
|
||||||
mockSchemaManagerService.enumManager.renameEnum,
|
|
||||||
).toHaveBeenCalledTimes(2);
|
|
||||||
expect(
|
|
||||||
mockSchemaManagerService.enumManager.renameEnum,
|
|
||||||
).toHaveBeenCalledWith({
|
|
||||||
queryRunner: mockQueryRunner,
|
|
||||||
schemaName: mockSchemaName,
|
|
||||||
oldEnumName: '_blogPost_category_enum',
|
|
||||||
newEnumName: '_article_category_enum',
|
|
||||||
});
|
|
||||||
expect(
|
|
||||||
mockSchemaManagerService.enumManager.renameEnum,
|
|
||||||
).toHaveBeenCalledWith({
|
|
||||||
queryRunner: mockQueryRunner,
|
|
||||||
schemaName: mockSchemaName,
|
|
||||||
oldEnumName: '_blogPost_tags_enum',
|
|
||||||
newEnumName: '_article_tags_enum',
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should handle object name changes with no actual table rename needed', async () => {
|
|
||||||
const objectMetadata = getFlatObjectMetadataMock({
|
|
||||||
id: mockObjectMetadataId,
|
|
||||||
workspaceId: mockWorkspaceId,
|
|
||||||
nameSingular: 'person',
|
|
||||||
uniqueIdentifier: 'person',
|
|
||||||
});
|
|
||||||
|
|
||||||
const flatObjectMetadataMaps = createMockFlatObjectMetadataMaps(
|
|
||||||
objectMetadata,
|
|
||||||
[],
|
|
||||||
);
|
|
||||||
|
|
||||||
await service.runUpdateObjectSchemaMigration({
|
|
||||||
action: {
|
|
||||||
type: 'update_object',
|
|
||||||
objectMetadataId: mockObjectMetadataId,
|
|
||||||
updates: [
|
|
||||||
{
|
|
||||||
property: 'nameSingular',
|
|
||||||
from: 'person',
|
|
||||||
to: 'person', // Same name - no change needed
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
queryRunner: mockQueryRunner,
|
|
||||||
flatObjectMetadataMaps,
|
|
||||||
});
|
|
||||||
|
|
||||||
// No table operations should occur when names are the same
|
|
||||||
expect(
|
|
||||||
mockSchemaManagerService.tableManager.renameTable,
|
|
||||||
).not.toHaveBeenCalled();
|
|
||||||
expect(
|
|
||||||
mockSchemaManagerService.enumManager.renameEnum,
|
|
||||||
).not.toHaveBeenCalled();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should handle object updates with complex field combinations', async () => {
|
|
||||||
const textField = getFlatFieldMetadataMock({
|
|
||||||
objectMetadataId: mockObjectMetadataId,
|
|
||||||
type: FieldMetadataType.TEXT,
|
|
||||||
name: 'description',
|
|
||||||
uniqueIdentifier: 'description',
|
|
||||||
});
|
|
||||||
|
|
||||||
const selectField = getFlatFieldMetadataMock({
|
|
||||||
objectMetadataId: mockObjectMetadataId,
|
|
||||||
type: FieldMetadataType.SELECT,
|
|
||||||
name: 'status',
|
|
||||||
uniqueIdentifier: 'status',
|
|
||||||
options: [
|
|
||||||
{
|
|
||||||
id: '1',
|
|
||||||
value: 'ACTIVE',
|
|
||||||
label: 'Active',
|
|
||||||
color: 'green',
|
|
||||||
position: 0,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
});
|
|
||||||
|
|
||||||
const objectMetadata = getFlatObjectMetadataMock({
|
|
||||||
id: mockObjectMetadataId,
|
|
||||||
workspaceId: mockWorkspaceId,
|
|
||||||
nameSingular: 'oldEntity',
|
|
||||||
uniqueIdentifier: 'oldEntity',
|
|
||||||
});
|
|
||||||
|
|
||||||
const flatObjectMetadataMaps = createMockFlatObjectMetadataMaps(
|
|
||||||
objectMetadata,
|
|
||||||
[textField, selectField],
|
|
||||||
);
|
|
||||||
|
|
||||||
await service.runUpdateObjectSchemaMigration({
|
|
||||||
action: {
|
|
||||||
type: 'update_object',
|
|
||||||
objectMetadataId: mockObjectMetadataId,
|
|
||||||
updates: [
|
|
||||||
{
|
|
||||||
property: 'nameSingular',
|
|
||||||
from: 'oldEntity',
|
|
||||||
to: 'newEntity',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
queryRunner: mockQueryRunner,
|
|
||||||
flatObjectMetadataMaps,
|
|
||||||
});
|
|
||||||
|
|
||||||
// Table rename
|
|
||||||
expect(
|
|
||||||
mockSchemaManagerService.tableManager.renameTable,
|
|
||||||
).toHaveBeenCalledWith({
|
|
||||||
queryRunner: mockQueryRunner,
|
|
||||||
schemaName: mockSchemaName,
|
|
||||||
oldTableName: '_oldEntity',
|
|
||||||
newTableName: '_newEntity',
|
|
||||||
});
|
|
||||||
|
|
||||||
// Only SELECT field should trigger enum rename
|
|
||||||
expect(
|
|
||||||
mockSchemaManagerService.enumManager.renameEnum,
|
|
||||||
).toHaveBeenCalledTimes(1);
|
|
||||||
expect(
|
|
||||||
mockSchemaManagerService.enumManager.renameEnum,
|
|
||||||
).toHaveBeenCalledWith({
|
|
||||||
queryRunner: mockQueryRunner,
|
|
||||||
schemaName: mockSchemaName,
|
|
||||||
oldEnumName: '_oldEntity_status_enum',
|
|
||||||
newEnumName: '_newEntity_status_enum',
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('Edge Cases and Error Conditions', () => {
|
|
||||||
it('should handle objects with undefined fieldsById gracefully', async () => {
|
|
||||||
const objectMetadata = getFlatObjectMetadataMock({
|
|
||||||
id: mockObjectMetadataId,
|
|
||||||
workspaceId: mockWorkspaceId,
|
|
||||||
nameSingular: 'testObject',
|
|
||||||
uniqueIdentifier: 'testObject',
|
|
||||||
});
|
|
||||||
|
|
||||||
// Create maps with undefined fieldsById
|
|
||||||
const flatObjectMetadataMaps = {
|
|
||||||
byId: {
|
|
||||||
[mockObjectMetadataId]: {
|
|
||||||
...objectMetadata,
|
|
||||||
fieldsById: undefined as unknown as Record<string, any>,
|
|
||||||
fieldIdByJoinColumnName: {},
|
|
||||||
fieldIdByName: {},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
idByNameSingular: {
|
|
||||||
[objectMetadata.nameSingular]: mockObjectMetadataId,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
await service.runCreateObjectSchemaMigration({
|
|
||||||
action: {
|
|
||||||
type: 'create_object',
|
|
||||||
flatObjectMetadataWithoutFields: objectMetadata,
|
|
||||||
createFieldActions: [],
|
|
||||||
},
|
|
||||||
queryRunner: mockQueryRunner,
|
|
||||||
flatObjectMetadataMaps,
|
|
||||||
});
|
|
||||||
|
|
||||||
// Should still drop table
|
|
||||||
expect(
|
|
||||||
mockSchemaManagerService.tableManager.createTable,
|
|
||||||
).toHaveBeenCalledWith({
|
|
||||||
queryRunner: mockQueryRunner,
|
|
||||||
schemaName: mockSchemaName,
|
|
||||||
tableName: '_testObject',
|
|
||||||
columnDefinitions: [],
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should handle empty fieldsById object', async () => {
|
|
||||||
const objectMetadata = getFlatObjectMetadataMock({
|
|
||||||
id: mockObjectMetadataId,
|
|
||||||
workspaceId: mockWorkspaceId,
|
|
||||||
nameSingular: 'emptyFieldsObject',
|
|
||||||
uniqueIdentifier: 'emptyFieldsObject',
|
|
||||||
});
|
|
||||||
|
|
||||||
const flatObjectMetadataMaps = {
|
|
||||||
byId: {
|
|
||||||
[mockObjectMetadataId]: {
|
|
||||||
...objectMetadata,
|
|
||||||
fieldsById: {},
|
|
||||||
fieldIdByJoinColumnName: {},
|
|
||||||
fieldIdByName: {},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
idByNameSingular: {
|
|
||||||
[objectMetadata.nameSingular]: mockObjectMetadataId,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
await service.runUpdateObjectSchemaMigration({
|
|
||||||
action: {
|
|
||||||
type: 'update_object',
|
|
||||||
objectMetadataId: mockObjectMetadataId,
|
|
||||||
updates: [
|
|
||||||
{
|
|
||||||
property: 'nameSingular',
|
|
||||||
from: 'emptyFieldsObject',
|
|
||||||
to: 'renamedEmptyFieldsObject',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
queryRunner: mockQueryRunner,
|
|
||||||
flatObjectMetadataMaps,
|
|
||||||
});
|
|
||||||
|
|
||||||
// Should rename table
|
|
||||||
expect(
|
|
||||||
mockSchemaManagerService.tableManager.renameTable,
|
|
||||||
).toHaveBeenCalledWith({
|
|
||||||
queryRunner: mockQueryRunner,
|
|
||||||
schemaName: mockSchemaName,
|
|
||||||
oldTableName: '_emptyFieldsObject',
|
|
||||||
newTableName: '_renamedEmptyFieldsObject',
|
|
||||||
});
|
|
||||||
|
|
||||||
// Should not perform enum operations
|
|
||||||
expect(
|
|
||||||
mockSchemaManagerService.enumManager.renameEnum,
|
|
||||||
).not.toHaveBeenCalled();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
-13
@@ -1,13 +0,0 @@
|
|||||||
import { CustomException } from 'src/utils/custom-exception';
|
|
||||||
|
|
||||||
export class WorkspaceSchemaMigrationException extends CustomException<WorkspaceSchemaMigrationExceptionCode> {}
|
|
||||||
|
|
||||||
export enum WorkspaceSchemaMigrationExceptionCode {
|
|
||||||
COMPOSITE_TYPE_NOT_FOUND = 'COMPOSITE_TYPE_NOT_FOUND',
|
|
||||||
INVALID_FIELD_TYPE = 'INVALID_FIELD_TYPE',
|
|
||||||
ENUM_OPERATION_FAILED = 'ENUM_OPERATION_FAILED',
|
|
||||||
COLUMN_OPERATION_FAILED = 'COLUMN_OPERATION_FAILED',
|
|
||||||
TABLE_OPERATION_FAILED = 'TABLE_OPERATION_FAILED',
|
|
||||||
UNSUPPORTED_COMPOSITE_COLUMN_TYPE = 'UNSUPPORTED_COMPOSITE_COLUMN_TYPE',
|
|
||||||
NOT_SUPPORTED = 'NOT_SUPPORTED',
|
|
||||||
}
|
|
||||||
-63
@@ -1,63 +0,0 @@
|
|||||||
import { type FlatFieldMetadata } from 'src/engine/metadata-modules/flat-field-metadata/types/flat-field-metadata.type';
|
|
||||||
import { type FlatObjectMetadataMaps } from 'src/engine/metadata-modules/flat-object-metadata-maps/types/flat-object-metadata-maps.type';
|
|
||||||
import { type FlatObjectMetadataWithFlatFieldMaps } from 'src/engine/metadata-modules/flat-object-metadata-maps/types/flat-object-metadata-with-flat-field-metadata-maps.type';
|
|
||||||
import { findFlatFieldMetadataInFlatObjectMetadataMapsOrThrow } from 'src/engine/metadata-modules/flat-object-metadata-maps/utils/find-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 { getWorkspaceSchemaContextForMigration } from './get-workspace-schema-context-for-migration.util';
|
|
||||||
|
|
||||||
export interface WorkspaceSchemaContext {
|
|
||||||
flatObjectMetadataWithFlatFieldMaps: FlatObjectMetadataWithFlatFieldMaps;
|
|
||||||
schemaName: string;
|
|
||||||
tableName: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const prepareWorkspaceSchemaContext = ({
|
|
||||||
flatObjectMetadataMaps,
|
|
||||||
objectMetadataId,
|
|
||||||
}: {
|
|
||||||
flatObjectMetadataMaps: FlatObjectMetadataMaps;
|
|
||||||
objectMetadataId: string;
|
|
||||||
}): WorkspaceSchemaContext => {
|
|
||||||
const flatObjectMetadataWithFlatFieldMaps =
|
|
||||||
findFlatObjectMetadataWithFlatFieldMapsInFlatObjectMetadataMapsOrThrow({
|
|
||||||
flatObjectMetadataMaps,
|
|
||||||
objectMetadataId,
|
|
||||||
});
|
|
||||||
|
|
||||||
const { schemaName, tableName } = getWorkspaceSchemaContextForMigration({
|
|
||||||
workspaceId: flatObjectMetadataWithFlatFieldMaps.workspaceId,
|
|
||||||
flatObjectMetadataWithoutFields: flatObjectMetadataWithFlatFieldMaps,
|
|
||||||
});
|
|
||||||
|
|
||||||
return {
|
|
||||||
flatObjectMetadataWithFlatFieldMaps,
|
|
||||||
schemaName,
|
|
||||||
tableName,
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
export const prepareFieldWorkspaceSchemaContext = ({
|
|
||||||
flatObjectMetadataMaps,
|
|
||||||
objectMetadataId,
|
|
||||||
fieldMetadataId,
|
|
||||||
}: {
|
|
||||||
flatObjectMetadataMaps: FlatObjectMetadataMaps;
|
|
||||||
objectMetadataId: string;
|
|
||||||
fieldMetadataId: string;
|
|
||||||
}): WorkspaceSchemaContext & { fieldMetadata: FlatFieldMetadata } => {
|
|
||||||
const context = prepareWorkspaceSchemaContext({
|
|
||||||
flatObjectMetadataMaps,
|
|
||||||
objectMetadataId,
|
|
||||||
});
|
|
||||||
const fieldMetadata = findFlatFieldMetadataInFlatObjectMetadataMapsOrThrow({
|
|
||||||
flatObjectMetadataMaps,
|
|
||||||
objectMetadataId,
|
|
||||||
fieldMetadataId,
|
|
||||||
});
|
|
||||||
|
|
||||||
return {
|
|
||||||
...context,
|
|
||||||
fieldMetadata,
|
|
||||||
};
|
|
||||||
};
|
|
||||||
-28
@@ -1,28 +0,0 @@
|
|||||||
import { Injectable } from '@nestjs/common';
|
|
||||||
|
|
||||||
import {
|
|
||||||
type CreateIndexAction,
|
|
||||||
type DeleteIndexAction,
|
|
||||||
type WorkspaceMigrationIndexActionTypeV2,
|
|
||||||
} from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/types/workspace-migration-index-action-v2';
|
|
||||||
import { type RunnerMethodForActionType } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/types/runner-method-for-action-type';
|
|
||||||
import { type WorkspaceMigrationActionRunnerArgs } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/types/workspace-migration-action-runner-args.type';
|
|
||||||
|
|
||||||
@Injectable()
|
|
||||||
export class WorkspaceSchemaIndexActionRunnerService
|
|
||||||
implements
|
|
||||||
RunnerMethodForActionType<WorkspaceMigrationIndexActionTypeV2, 'schema'>
|
|
||||||
{
|
|
||||||
// TODO: Implement index schema operations
|
|
||||||
runDeleteIndexSchemaMigration = async (
|
|
||||||
_action: WorkspaceMigrationActionRunnerArgs<DeleteIndexAction>,
|
|
||||||
) => {
|
|
||||||
return;
|
|
||||||
};
|
|
||||||
|
|
||||||
runCreateIndexSchemaMigration = async (
|
|
||||||
_action: WorkspaceMigrationActionRunnerArgs<CreateIndexAction>,
|
|
||||||
) => {
|
|
||||||
return;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
-80
@@ -1,80 +0,0 @@
|
|||||||
import { Injectable } from '@nestjs/common';
|
|
||||||
|
|
||||||
import { assertUnreachable } from 'twenty-shared/utils';
|
|
||||||
|
|
||||||
import { type WorkspaceMigrationRunnerArgs } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/types/workspace-migration-runner-args.type';
|
|
||||||
import { WorkspaceSchemaFieldActionRunnerService } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/workspace-schema-migration-runner/workspace-schema-field-action-runner.service';
|
|
||||||
import { WorkspaceSchemaIndexActionRunnerService } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/workspace-schema-migration-runner/workspace-schema-index-action-runner.service';
|
|
||||||
import { WorkspaceSchemaObjectActionRunnerService } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/workspace-schema-migration-runner/workspace-schema-object-action-runner.service';
|
|
||||||
|
|
||||||
@Injectable()
|
|
||||||
export class WorkspaceSchemaMigrationRunnerService {
|
|
||||||
constructor(
|
|
||||||
private readonly workspaceSchemaObjectMigrationRunnerService: WorkspaceSchemaObjectActionRunnerService,
|
|
||||||
private readonly workspaceSchemaIndexMigrationRunnerService: WorkspaceSchemaIndexActionRunnerService,
|
|
||||||
private readonly workspaceSchemaFieldMigrationRunnerService: WorkspaceSchemaFieldActionRunnerService,
|
|
||||||
) {}
|
|
||||||
|
|
||||||
runWorkspaceSchemaMigration = async ({
|
|
||||||
action,
|
|
||||||
queryRunner,
|
|
||||||
flatObjectMetadataMaps,
|
|
||||||
}: WorkspaceMigrationRunnerArgs) => {
|
|
||||||
switch (action.type) {
|
|
||||||
case 'delete_object': {
|
|
||||||
await this.workspaceSchemaObjectMigrationRunnerService.runDeleteObjectSchemaMigration(
|
|
||||||
{ action, queryRunner, flatObjectMetadataMaps },
|
|
||||||
);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case 'create_object': {
|
|
||||||
await this.workspaceSchemaObjectMigrationRunnerService.runCreateObjectSchemaMigration(
|
|
||||||
{ action, queryRunner, flatObjectMetadataMaps },
|
|
||||||
);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case 'update_object': {
|
|
||||||
await this.workspaceSchemaObjectMigrationRunnerService.runUpdateObjectSchemaMigration(
|
|
||||||
{ action, queryRunner, flatObjectMetadataMaps },
|
|
||||||
);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case 'create_field': {
|
|
||||||
await this.workspaceSchemaFieldMigrationRunnerService.runCreateFieldSchemaMigration(
|
|
||||||
{ action, queryRunner, flatObjectMetadataMaps },
|
|
||||||
);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case 'update_field': {
|
|
||||||
await this.workspaceSchemaFieldMigrationRunnerService.runUpdateFieldSchemaMigration(
|
|
||||||
{ action, queryRunner, flatObjectMetadataMaps },
|
|
||||||
);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case 'delete_field': {
|
|
||||||
await this.workspaceSchemaFieldMigrationRunnerService.runDeleteFieldSchemaMigration(
|
|
||||||
{ action, queryRunner, flatObjectMetadataMaps },
|
|
||||||
);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case 'create_index': {
|
|
||||||
await this.workspaceSchemaIndexMigrationRunnerService.runCreateIndexSchemaMigration(
|
|
||||||
{ action, queryRunner, flatObjectMetadataMaps },
|
|
||||||
);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case 'delete_index': {
|
|
||||||
await this.workspaceSchemaIndexMigrationRunnerService.runDeleteIndexSchemaMigration(
|
|
||||||
{ action, queryRunner, flatObjectMetadataMaps },
|
|
||||||
);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
default: {
|
|
||||||
assertUnreachable(
|
|
||||||
action,
|
|
||||||
'Should never occur, encountered an unsupported workspace migration action type',
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
-181
@@ -1,181 +0,0 @@
|
|||||||
import { Injectable } from '@nestjs/common';
|
|
||||||
|
|
||||||
import { type FlatFieldMetadata } from 'src/engine/metadata-modules/flat-field-metadata/types/flat-field-metadata.type';
|
|
||||||
import { isCompositeFlatFieldMetadata } from 'src/engine/metadata-modules/flat-field-metadata/utils/is-composite-flat-field-metadata.util';
|
|
||||||
import { isEnumFlatFieldMetadata } from 'src/engine/metadata-modules/flat-field-metadata/utils/is-enum-flat-field-metadata.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 { getWorkspaceSchemaName } from 'src/engine/workspace-datasource/utils/get-workspace-schema-name.util';
|
|
||||||
import {
|
|
||||||
type CreateObjectAction,
|
|
||||||
type DeleteObjectAction,
|
|
||||||
type UpdateObjectAction,
|
|
||||||
type WorkspaceMigrationObjectActionTypeV2,
|
|
||||||
} from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/types/workspace-migration-object-action-v2';
|
|
||||||
import { type RunnerMethodForActionType } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/types/runner-method-for-action-type';
|
|
||||||
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/workspace-schema-migration-runner/utils/generate-column-definitions.util';
|
|
||||||
|
|
||||||
import { prepareWorkspaceSchemaContext } from './utils/workspace-schema-context.util';
|
|
||||||
import {
|
|
||||||
collectEnumOperationsForObject,
|
|
||||||
EnumOperation,
|
|
||||||
executeBatchEnumOperations,
|
|
||||||
} from './utils/workspace-schema-enum-operations.util';
|
|
||||||
|
|
||||||
@Injectable()
|
|
||||||
export class WorkspaceSchemaObjectActionRunnerService
|
|
||||||
implements
|
|
||||||
RunnerMethodForActionType<WorkspaceMigrationObjectActionTypeV2, 'schema'>
|
|
||||||
{
|
|
||||||
constructor(
|
|
||||||
private readonly workspaceSchemaManagerService: WorkspaceSchemaManagerService,
|
|
||||||
) {}
|
|
||||||
|
|
||||||
runDeleteObjectSchemaMigration = async ({
|
|
||||||
action: { objectMetadataId },
|
|
||||||
queryRunner,
|
|
||||||
flatObjectMetadataMaps,
|
|
||||||
}: WorkspaceMigrationActionRunnerArgs<DeleteObjectAction>) => {
|
|
||||||
const { schemaName, tableName, flatObjectMetadataWithFlatFieldMaps } =
|
|
||||||
prepareWorkspaceSchemaContext({
|
|
||||||
flatObjectMetadataMaps,
|
|
||||||
objectMetadataId,
|
|
||||||
});
|
|
||||||
|
|
||||||
await this.workspaceSchemaManagerService.tableManager.dropTable({
|
|
||||||
queryRunner,
|
|
||||||
schemaName,
|
|
||||||
tableName,
|
|
||||||
});
|
|
||||||
|
|
||||||
const enumOrCompositeFlatFieldMetadatas = Object.values(
|
|
||||||
flatObjectMetadataWithFlatFieldMaps.fieldsById,
|
|
||||||
)
|
|
||||||
.filter((field): field is FlatFieldMetadata => field != null)
|
|
||||||
.filter(
|
|
||||||
(field) =>
|
|
||||||
isEnumFlatFieldMetadata(field) || isCompositeFlatFieldMetadata(field),
|
|
||||||
);
|
|
||||||
|
|
||||||
const enumOperations = collectEnumOperationsForObject({
|
|
||||||
flatFieldMetadatas: enumOrCompositeFlatFieldMetadatas,
|
|
||||||
tableName,
|
|
||||||
operation: EnumOperation.DROP,
|
|
||||||
});
|
|
||||||
|
|
||||||
await executeBatchEnumOperations({
|
|
||||||
enumOperations,
|
|
||||||
queryRunner,
|
|
||||||
schemaName,
|
|
||||||
workspaceSchemaManagerService: this.workspaceSchemaManagerService,
|
|
||||||
});
|
|
||||||
};
|
|
||||||
runCreateObjectSchemaMigration = async ({
|
|
||||||
action: { flatObjectMetadataWithoutFields, createFieldActions },
|
|
||||||
queryRunner,
|
|
||||||
}: WorkspaceMigrationActionRunnerArgs<CreateObjectAction>) => {
|
|
||||||
const flatObjectMetadata = flatObjectMetadataWithoutFields;
|
|
||||||
const schemaName = getWorkspaceSchemaName(flatObjectMetadata.workspaceId);
|
|
||||||
const tableName = computeObjectTargetTable(flatObjectMetadata);
|
|
||||||
|
|
||||||
const columnDefinitions = createFieldActions.flatMap((createFieldAction) =>
|
|
||||||
generateColumnDefinitions({
|
|
||||||
flatFieldMetadata: createFieldAction.flatFieldMetadata,
|
|
||||||
flatObjectMetadataWithoutFields: flatObjectMetadataWithoutFields,
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
|
|
||||||
const enumOrCompositeFlatFieldMetadatas = createFieldActions
|
|
||||||
.map((createFieldAction) => createFieldAction.flatFieldMetadata)
|
|
||||||
.filter((field): field is FlatFieldMetadata => field != null)
|
|
||||||
.filter(
|
|
||||||
(field) =>
|
|
||||||
isEnumFlatFieldMetadata(field) || isCompositeFlatFieldMetadata(field),
|
|
||||||
);
|
|
||||||
|
|
||||||
const enumOperations = collectEnumOperationsForObject({
|
|
||||||
flatFieldMetadatas: enumOrCompositeFlatFieldMetadatas,
|
|
||||||
tableName,
|
|
||||||
operation: EnumOperation.CREATE,
|
|
||||||
});
|
|
||||||
|
|
||||||
await executeBatchEnumOperations({
|
|
||||||
enumOperations,
|
|
||||||
queryRunner,
|
|
||||||
schemaName,
|
|
||||||
workspaceSchemaManagerService: this.workspaceSchemaManagerService,
|
|
||||||
});
|
|
||||||
|
|
||||||
await this.workspaceSchemaManagerService.tableManager.createTable({
|
|
||||||
queryRunner,
|
|
||||||
schemaName,
|
|
||||||
tableName,
|
|
||||||
columnDefinitions,
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
runUpdateObjectSchemaMigration = async ({
|
|
||||||
action,
|
|
||||||
queryRunner,
|
|
||||||
flatObjectMetadataMaps,
|
|
||||||
}: WorkspaceMigrationActionRunnerArgs<UpdateObjectAction>) => {
|
|
||||||
const { objectMetadataId, updates } = action;
|
|
||||||
const {
|
|
||||||
schemaName,
|
|
||||||
tableName: currentTableName,
|
|
||||||
flatObjectMetadataWithFlatFieldMaps,
|
|
||||||
} = prepareWorkspaceSchemaContext({
|
|
||||||
flatObjectMetadataMaps,
|
|
||||||
objectMetadataId,
|
|
||||||
});
|
|
||||||
|
|
||||||
for (const update of updates) {
|
|
||||||
if (update.property !== 'nameSingular') {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
const updatedObjectMetadata = {
|
|
||||||
...flatObjectMetadataWithFlatFieldMaps,
|
|
||||||
[update.property]: update.to,
|
|
||||||
};
|
|
||||||
|
|
||||||
const newTableName = computeObjectTargetTable(updatedObjectMetadata);
|
|
||||||
|
|
||||||
if (currentTableName !== newTableName) {
|
|
||||||
await this.workspaceSchemaManagerService.tableManager.renameTable({
|
|
||||||
queryRunner,
|
|
||||||
schemaName,
|
|
||||||
oldTableName: currentTableName,
|
|
||||||
newTableName,
|
|
||||||
});
|
|
||||||
|
|
||||||
const enumOrCompositeFlatFieldMetadatas = Object.values(
|
|
||||||
flatObjectMetadataWithFlatFieldMaps.fieldsById,
|
|
||||||
)
|
|
||||||
.filter((field): field is FlatFieldMetadata => field != null)
|
|
||||||
.filter(
|
|
||||||
(field) =>
|
|
||||||
isEnumFlatFieldMetadata(field) ||
|
|
||||||
isCompositeFlatFieldMetadata(field),
|
|
||||||
);
|
|
||||||
|
|
||||||
const enumOperations = collectEnumOperationsForObject({
|
|
||||||
flatFieldMetadatas: enumOrCompositeFlatFieldMetadatas,
|
|
||||||
tableName: currentTableName,
|
|
||||||
operation: EnumOperation.RENAME,
|
|
||||||
options: {
|
|
||||||
newTableName,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
await executeBatchEnumOperations({
|
|
||||||
enumOperations,
|
|
||||||
queryRunner,
|
|
||||||
schemaName,
|
|
||||||
workspaceSchemaManagerService: this.workspaceSchemaManagerService,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
+3
-7
@@ -1,8 +1,8 @@
|
|||||||
import { Module } from '@nestjs/common';
|
import { Module } from '@nestjs/common';
|
||||||
|
|
||||||
import { WorkspaceMigrationBuilderV2Module } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/services/workspace-migration-builder-v2.module';
|
import { WorkspaceMigrationValidateBuildAndRunService } from 'src/engine/workspace-manager/workspace-migration-v2/services/workspace-migration-validate-build-and-run-service';
|
||||||
|
import { WorkspaceMigrationBuilderV2Module } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/workspace-migration-builder-v2.module';
|
||||||
import { WorkspaceMigrationRunnerV2Module } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/workspace-migration-runner-v2.module';
|
import { WorkspaceMigrationRunnerV2Module } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/workspace-migration-runner-v2.module';
|
||||||
import { WorkspaceMigrationValidateBuildAndRunService } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-validate-build-and-run-service';
|
|
||||||
|
|
||||||
@Module({
|
@Module({
|
||||||
imports: [
|
imports: [
|
||||||
@@ -10,10 +10,6 @@ import { WorkspaceMigrationValidateBuildAndRunService } from 'src/engine/workspa
|
|||||||
WorkspaceMigrationRunnerV2Module,
|
WorkspaceMigrationRunnerV2Module,
|
||||||
],
|
],
|
||||||
providers: [WorkspaceMigrationValidateBuildAndRunService],
|
providers: [WorkspaceMigrationValidateBuildAndRunService],
|
||||||
exports: [
|
exports: [WorkspaceMigrationValidateBuildAndRunService],
|
||||||
WorkspaceMigrationRunnerV2Module,
|
|
||||||
WorkspaceMigrationBuilderV2Module,
|
|
||||||
WorkspaceMigrationValidateBuildAndRunService,
|
|
||||||
],
|
|
||||||
})
|
})
|
||||||
export class WorkspaceMigrationV2Module {}
|
export class WorkspaceMigrationV2Module {}
|
||||||
|
|||||||
Reference in New Issue
Block a user