feat: workspace health type fix (#3890)

* feat: workspace health type fix

* Fix

---------

Co-authored-by: Charles Bochet <charles@twenty.com>
This commit is contained in:
Jérémy M
2024-02-08 18:59:17 +01:00
committed by GitHub
parent 90b58518bb
commit 7ec968d5a2
8 changed files with 191 additions and 2 deletions

View File

@ -140,6 +140,27 @@ export class DatabaseStructureService {
});
}
getFieldMetadataTypeFromPostgresDataType(
postgresDataType: string,
): FieldMetadataType | null {
const mainDataSource = this.typeORMService.getMainDataSource();
for (const type in FieldMetadataType) {
const typeORMType = fieldMetadataTypeToColumnType(
FieldMetadataType[type],
) as ColumnType;
const dataType = mainDataSource.driver.normalizeType({
type: typeORMType,
});
if (postgresDataType === dataType) {
return FieldMetadataType[type];
}
}
return null;
}
getPostgresDefault(
fieldMetadataType: FieldMetadataType,
defaultValue: FieldMetadataDefaultValue | null,

View File

@ -0,0 +1,89 @@
import { Injectable } from '@nestjs/common';
import { EntityManager } from 'typeorm';
import {
WorkspaceHealthColumnIssue,
WorkspaceHealthIssueType,
} from 'src/workspace/workspace-health/interfaces/workspace-health-issue.interface';
import { WorkspaceMigrationBuilderAction } from 'src/workspace/workspace-migration-builder/interfaces/workspace-migration-builder-action.interface';
import { ObjectMetadataEntity } from 'src/metadata/object-metadata/object-metadata.entity';
import { WorkspaceMigrationEntity } from 'src/metadata/workspace-migration/workspace-migration.entity';
import { WorkspaceMigrationFieldFactory } from 'src/workspace/workspace-migration-builder/factories/workspace-migration-field.factory';
import { DatabaseStructureService } from './database-structure.service';
type WorkspaceHealthTypeIssue =
WorkspaceHealthColumnIssue<WorkspaceHealthIssueType.COLUMN_DATA_TYPE_CONFLICT>;
@Injectable()
export class WorkspaceFixTypeService {
constructor(
private readonly workspaceMigrationFieldFactory: WorkspaceMigrationFieldFactory,
private readonly databaseStructureService: DatabaseStructureService,
) {}
async fix(
manager: EntityManager,
objectMetadataCollection: ObjectMetadataEntity[],
issues: WorkspaceHealthTypeIssue[],
): Promise<Partial<WorkspaceMigrationEntity>[]> {
const workspaceMigrations: Partial<WorkspaceMigrationEntity>[] = [];
for (const issue of issues) {
switch (issue.type) {
case WorkspaceHealthIssueType.COLUMN_DATA_TYPE_CONFLICT: {
const columnNullabilityWorkspaceMigrations =
await this.fixColumnTypeIssues(
objectMetadataCollection,
issues.filter(
(issue) =>
issue.type ===
WorkspaceHealthIssueType.COLUMN_DATA_TYPE_CONFLICT,
) as WorkspaceHealthColumnIssue<WorkspaceHealthIssueType.COLUMN_DATA_TYPE_CONFLICT>[],
);
workspaceMigrations.push(...columnNullabilityWorkspaceMigrations);
break;
}
}
}
return workspaceMigrations;
}
private async fixColumnTypeIssues(
objectMetadataCollection: ObjectMetadataEntity[],
issues: WorkspaceHealthColumnIssue<WorkspaceHealthIssueType.COLUMN_DATA_TYPE_CONFLICT>[],
): Promise<Partial<WorkspaceMigrationEntity>[]> {
const fieldMetadataUpdateCollection = issues.map((issue) => {
if (!issue.columnStructure?.dataType) {
throw new Error('Column structure data type is missing');
}
const type =
this.databaseStructureService.getFieldMetadataTypeFromPostgresDataType(
issue.columnStructure?.dataType,
);
if (!type) {
throw new Error("Can't find field metadata type from column structure");
}
return {
current: {
...issue.fieldMetadata,
type,
},
altered: issue.fieldMetadata,
};
});
return this.workspaceMigrationFieldFactory.create(
objectMetadataCollection,
fieldMetadataUpdateCollection,
WorkspaceMigrationBuilderAction.UPDATE,
);
}
}

View File

@ -7,14 +7,19 @@ import { WorkspaceHealthIssue } from 'src/workspace/workspace-health/interfaces/
import { WorkspaceMigrationEntity } from 'src/metadata/workspace-migration/workspace-migration.entity';
import { ObjectMetadataEntity } from 'src/metadata/object-metadata/object-metadata.entity';
import { isWorkspaceHealthNullableIssue } from 'src/workspace/workspace-health/utils/is-workspace-health-issue-type.util';
import {
isWorkspaceHealthNullableIssue,
isWorkspaceHealthTypeIssue,
} from 'src/workspace/workspace-health/utils/is-workspace-health-issue-type.util';
import { WorkspaceFixNullableService } from './workspace-fix-nullable.service';
import { WorkspaceFixTypeService } from './workspace-fix-type.service';
@Injectable()
export class WorkspaceFixService {
constructor(
private readonly workspaceFixNullableService: WorkspaceFixNullableService,
private readonly workspaceFixTypeService: WorkspaceFixTypeService,
) {}
async fix(
@ -30,6 +35,12 @@ export class WorkspaceFixService {
isWorkspaceHealthNullableIssue(issue.type),
),
},
[WorkspaceHealthFixKind.Type]: {
service: this.workspaceFixTypeService,
issues: issues.filter((issue) =>
isWorkspaceHealthTypeIssue(issue.type),
),
},
};
return services[type].service.fix(

View File

@ -5,3 +5,9 @@ export const isWorkspaceHealthNullableIssue = (
): type is WorkspaceHealthIssueType.COLUMN_NULLABILITY_CONFLICT => {
return type === WorkspaceHealthIssueType.COLUMN_NULLABILITY_CONFLICT;
};
export const isWorkspaceHealthTypeIssue = (
type: WorkspaceHealthIssueType,
): type is WorkspaceHealthIssueType.COLUMN_DATA_TYPE_CONFLICT => {
return type === WorkspaceHealthIssueType.COLUMN_DATA_TYPE_CONFLICT;
};

View File

@ -14,6 +14,7 @@ import { WorkspaceMigrationRunnerModule } from 'src/workspace/workspace-migratio
import { WorkspaceFixService } from './services/workspace-fix.service';
import { WorkspaceFixNullableService } from './services/workspace-fix-nullable.service';
import { WorkspaceFixTypeService } from './services/workspace-fix-type.service';
@Module({
imports: [
@ -31,6 +32,7 @@ import { WorkspaceFixNullableService } from './services/workspace-fix-nullable.s
FieldMetadataHealthService,
RelationMetadataHealthService,
WorkspaceFixNullableService,
WorkspaceFixTypeService,
WorkspaceFixService,
],
exports: [WorkspaceHealthService],

View File

@ -0,0 +1,35 @@
import { Injectable } from '@nestjs/common';
import { QueryRunner } from 'typeorm';
import { WorkspaceMigrationColumnAlter } from 'src/metadata/workspace-migration/workspace-migration.entity';
@Injectable()
export class WorkspaceMigrationTypeService {
constructor() {}
async alterType(
queryRunner: QueryRunner,
schemaName: string,
tableName: string,
migrationColumn: WorkspaceMigrationColumnAlter,
) {
const columnDefinition = migrationColumn.alteredColumnDefinition;
// Update the column type
// If casting is not possible, the query will fail
await queryRunner.query(`
ALTER TABLE "${schemaName}"."${tableName}"
ALTER COLUMN "${columnDefinition.columnName}" TYPE ${columnDefinition.columnType}
USING "${columnDefinition.columnName}"::${columnDefinition.columnType}
`);
// Update the column default value
if (columnDefinition.defaultValue) {
await queryRunner.query(`
ALTER TABLE "${schemaName}"."${tableName}"
ALTER COLUMN "${columnDefinition.columnName}" SET DEFAULT ${columnDefinition.defaultValue}::${columnDefinition.columnType};
`);
}
}
}

View File

@ -7,13 +7,19 @@ import { WorkspaceMigrationEnumService } from 'src/workspace/workspace-migration
import { WorkspaceMigrationRunnerService } from './workspace-migration-runner.service';
import { WorkspaceMigrationTypeService } from './services/workspace-migration-type.service';
@Module({
imports: [
WorkspaceDataSourceModule,
WorkspaceMigrationModule,
WorkspaceCacheVersionModule,
],
providers: [WorkspaceMigrationRunnerService, WorkspaceMigrationEnumService],
providers: [
WorkspaceMigrationRunnerService,
WorkspaceMigrationEnumService,
WorkspaceMigrationTypeService,
],
exports: [WorkspaceMigrationRunnerService],
})
export class WorkspaceMigrationRunnerModule {}

View File

@ -22,6 +22,7 @@ import { WorkspaceCacheVersionService } from 'src/metadata/workspace-cache-versi
import { WorkspaceMigrationEnumService } from 'src/workspace/workspace-migration-runner/services/workspace-migration-enum.service';
import { customTableDefaultColumns } from './utils/custom-table-default-column.util';
import { WorkspaceMigrationTypeService } from './services/workspace-migration-type.service';
@Injectable()
export class WorkspaceMigrationRunnerService {
@ -30,6 +31,7 @@ export class WorkspaceMigrationRunnerService {
private readonly workspaceMigrationService: WorkspaceMigrationService,
private readonly workspaceCacheVersionService: WorkspaceCacheVersionService,
private readonly workspaceMigrationEnumService: WorkspaceMigrationEnumService,
private readonly workspaceMigrationTypeService: WorkspaceMigrationTypeService,
) {}
/**
@ -275,6 +277,23 @@ export class WorkspaceMigrationRunnerService {
);
}
if (
migrationColumn.currentColumnDefinition.columnType !==
migrationColumn.alteredColumnDefinition.columnType
) {
await this.workspaceMigrationTypeService.alterType(
queryRunner,
schemaName,
tableName,
migrationColumn,
);
migrationColumn.currentColumnDefinition.columnType =
migrationColumn.alteredColumnDefinition.columnType;
return;
}
await queryRunner.changeColumn(
`${schemaName}.${tableName}`,
new TableColumn({