Rename enum types when tables are renamed (#8794)

## Context
Enum are named after this pattern
`${schema}_${tableName}_${columnName}_enum` however now that we allowed
table name update, we need to make sure their enums are renamed as well.
This commit is contained in:
Weiko 2024-11-28 15:39:20 +01:00 committed by GitHub
parent fbe042db3b
commit 75f5afb968
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 185 additions and 5 deletions

View File

@ -443,14 +443,17 @@ export class ObjectMetadataService extends TypeOrmQueryService<ObjectMetadataEnt
objectMetadataForUpdate.workspaceId,
);
await this.objectMetadataMigrationService.recomputeEnumNames(
objectMetadataForUpdate,
objectMetadataForUpdate.workspaceId,
);
const recomputedIndexes =
await this.indexMetadataService.recomputeIndexMetadataForObject(
objectMetadataForUpdate.workspaceId,
objectMetadataForUpdate,
);
// TODO: recompute foreign keys indexes as well (in the related object and not objectMetadataForUpdate)
await this.indexMetadataService.createIndexRecomputeMigrations(
objectMetadataForUpdate.workspaceId,
objectMetadataForUpdate,

View File

@ -1,9 +1,12 @@
import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { In, Repository } from 'typeorm';
import { FieldMetadataEntity } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity';
import {
FieldMetadataEntity,
FieldMetadataType,
} from 'src/engine/metadata-modules/field-metadata/field-metadata.entity';
import { computeColumnName } from 'src/engine/metadata-modules/field-metadata/utils/compute-column-name.util';
import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
import { buildMigrationsForCustomObjectRelations } from 'src/engine/metadata-modules/object-metadata/utils/build-migrations-for-custom-object-relations.util';
@ -264,4 +267,43 @@ export class ObjectMetadataMigrationService {
],
);
}
public async recomputeEnumNames(
updatedObjectMetadata: ObjectMetadataEntity,
workspaceId: string,
) {
const fieldMetadataToUpdate = await this.fieldMetadataRepository.find({
where: {
objectMetadataId: updatedObjectMetadata.id,
workspaceId,
type: In([
FieldMetadataType.SELECT,
FieldMetadataType.MULTI_SELECT,
FieldMetadataType.RATING,
FieldMetadataType.ACTOR,
]),
},
});
for (const fieldMetadata of fieldMetadataToUpdate) {
await this.workspaceMigrationService.createCustomMigration(
generateMigrationName(`update-${fieldMetadata.name}-enum-name`),
workspaceId,
[
{
name: computeTableName(
updatedObjectMetadata.nameSingular,
updatedObjectMetadata.isCustom,
),
action: WorkspaceMigrationTableActionType.ALTER,
columns: this.workspaceMigrationFactory.createColumnActions(
WorkspaceMigrationColumnActionType.ALTER,
fieldMetadata,
fieldMetadata,
),
},
],
);
}
}
}

View File

@ -12,4 +12,5 @@ export enum WorkspaceMigrationExceptionCode {
INVALID_ACTION = 'INVALID_ACTION',
INVALID_FIELD_METADATA = 'INVALID_FIELD_METADATA',
INVALID_COMPOSITE_TYPE = 'INVALID_COMPOSITE_TYPE',
ENUM_TYPE_NAME_NOT_FOUND = 'ENUM_TYPE_NAME_NOT_FOUND',
}

View File

@ -0,0 +1,101 @@
import { Test, TestingModule } from '@nestjs/testing';
import { DataSource, QueryRunner, TableColumn } from 'typeorm';
import {
WorkspaceMigrationColumnActionType,
WorkspaceMigrationColumnAlter,
} from 'src/engine/metadata-modules/workspace-migration/workspace-migration.entity';
import { WorkspaceMigrationEnumService } from 'src/engine/workspace-manager/workspace-migration-runner/services/workspace-migration-enum.service';
describe('WorkspaceMigrationEnumService', () => {
let service: WorkspaceMigrationEnumService;
let queryRunner: QueryRunner;
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
providers: [
WorkspaceMigrationEnumService,
{
provide: DataSource,
useValue: {
createQueryRunner: () => ({
query: jest.fn(),
addColumn: jest.fn(),
}),
},
},
],
}).compile();
service = module.get<WorkspaceMigrationEnumService>(
WorkspaceMigrationEnumService,
);
queryRunner = module.get(DataSource).createQueryRunner();
});
describe('alterEnum', () => {
it('should handle enum column alteration with renamed values', async () => {
const mockMigrationColumn: WorkspaceMigrationColumnAlter = {
action: WorkspaceMigrationColumnActionType.ALTER,
currentColumnDefinition: {
columnName: 'status',
columnType: 'enum',
enum: ['ACTIVE', 'INACTIVE'],
isNullable: false,
defaultValue: 'ACTIVE',
},
alteredColumnDefinition: {
columnName: 'status',
columnType: 'enum',
enum: [
{ from: 'ACTIVE', to: 'ENABLED' },
{ from: 'INACTIVE', to: 'DISABLED' },
],
isNullable: false,
defaultValue: 'ENABLED',
},
};
jest.spyOn(queryRunner, 'query').mockImplementation((query: string) => {
if (query.includes('information_schema.columns')) {
return Promise.resolve([{ udt_name: 'test_status_enum' }]);
}
if (query.includes('SELECT id')) {
return Promise.resolve([
{ id: '1', status: 'ACTIVE' },
{ id: '2', status: 'INACTIVE' },
]);
}
return Promise.resolve();
});
await service.alterEnum(
queryRunner,
'test_schema',
'test_table',
mockMigrationColumn,
);
expect(queryRunner.query).toHaveBeenCalledWith(
expect.stringContaining('information_schema.columns'),
['test_schema', 'test_table', 'status'],
);
expect(queryRunner.query).toHaveBeenCalledWith(
expect.stringContaining('ALTER TYPE'),
);
expect(queryRunner.addColumn).toHaveBeenCalledWith(
'test_schema.test_table',
new TableColumn({
name: 'status',
type: 'enum',
enum: ['ENABLED', 'DISABLED'],
isNullable: false,
default: 'ENABLED',
enumName: 'test_table_status_enum',
}),
);
});
});
});

View File

@ -10,6 +10,10 @@ import {
WorkspaceMigrationColumnAlter,
WorkspaceMigrationRenamedEnum,
} from 'src/engine/metadata-modules/workspace-migration/workspace-migration.entity';
import {
WorkspaceMigrationException,
WorkspaceMigrationExceptionCode,
} from 'src/engine/metadata-modules/workspace-migration/workspace-migration.exception';
@Injectable()
export class WorkspaceMigrationEnumService {
@ -33,8 +37,14 @@ export class WorkspaceMigrationEnumService {
);
}
const oldEnumTypeName = await this.getEnumTypeName(
queryRunner,
schemaName,
tableName,
migrationColumn.currentColumnDefinition.columnName,
);
const columnDefinition = migrationColumn.alteredColumnDefinition;
const oldEnumTypeName = `${tableName}_${migrationColumn.currentColumnDefinition.columnName}_enum`;
const tempEnumTypeName = `${oldEnumTypeName}_temp`;
const newEnumTypeName = `${tableName}_${columnDefinition.columnName}_enum`;
const enumValues =
@ -214,4 +224,27 @@ export class WorkspaceMigrationEnumService {
RENAME TO "${newEnumTypeName}"
`);
}
private async getEnumTypeName(
queryRunner: QueryRunner,
schemaName: string,
tableName: string,
columnName: string,
): Promise<string> {
const result = await queryRunner.query(
`SELECT udt_name FROM information_schema.columns WHERE table_schema = $1 AND table_name = $2 AND column_name = $3`,
[schemaName, tableName, columnName],
);
const enumTypeName = result[0].udt_name;
if (!enumTypeName) {
throw new WorkspaceMigrationException(
`Enum type name not found for column ${columnName} in table ${tableName} while trying to alter enum`,
WorkspaceMigrationExceptionCode.ENUM_TYPE_NAME_NOT_FOUND,
);
}
return enumTypeName;
}
}