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:
@ -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,
|
||||
|
||||
@ -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,
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -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(
|
||||
|
||||
@ -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;
|
||||
};
|
||||
|
||||
@ -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],
|
||||
|
||||
@ -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};
|
||||
`);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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 {}
|
||||
|
||||
@ -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({
|
||||
|
||||
Reference in New Issue
Block a user