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:
@ -443,14 +443,17 @@ export class ObjectMetadataService extends TypeOrmQueryService<ObjectMetadataEnt
|
|||||||
objectMetadataForUpdate.workspaceId,
|
objectMetadataForUpdate.workspaceId,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
await this.objectMetadataMigrationService.recomputeEnumNames(
|
||||||
|
objectMetadataForUpdate,
|
||||||
|
objectMetadataForUpdate.workspaceId,
|
||||||
|
);
|
||||||
|
|
||||||
const recomputedIndexes =
|
const recomputedIndexes =
|
||||||
await this.indexMetadataService.recomputeIndexMetadataForObject(
|
await this.indexMetadataService.recomputeIndexMetadataForObject(
|
||||||
objectMetadataForUpdate.workspaceId,
|
objectMetadataForUpdate.workspaceId,
|
||||||
objectMetadataForUpdate,
|
objectMetadataForUpdate,
|
||||||
);
|
);
|
||||||
|
|
||||||
// TODO: recompute foreign keys indexes as well (in the related object and not objectMetadataForUpdate)
|
|
||||||
|
|
||||||
await this.indexMetadataService.createIndexRecomputeMigrations(
|
await this.indexMetadataService.createIndexRecomputeMigrations(
|
||||||
objectMetadataForUpdate.workspaceId,
|
objectMetadataForUpdate.workspaceId,
|
||||||
objectMetadataForUpdate,
|
objectMetadataForUpdate,
|
||||||
|
|||||||
@ -1,9 +1,12 @@
|
|||||||
import { Injectable } from '@nestjs/common';
|
import { Injectable } from '@nestjs/common';
|
||||||
import { InjectRepository } from '@nestjs/typeorm';
|
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 { 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 { 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';
|
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,
|
||||||
|
),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -12,4 +12,5 @@ export enum WorkspaceMigrationExceptionCode {
|
|||||||
INVALID_ACTION = 'INVALID_ACTION',
|
INVALID_ACTION = 'INVALID_ACTION',
|
||||||
INVALID_FIELD_METADATA = 'INVALID_FIELD_METADATA',
|
INVALID_FIELD_METADATA = 'INVALID_FIELD_METADATA',
|
||||||
INVALID_COMPOSITE_TYPE = 'INVALID_COMPOSITE_TYPE',
|
INVALID_COMPOSITE_TYPE = 'INVALID_COMPOSITE_TYPE',
|
||||||
|
ENUM_TYPE_NAME_NOT_FOUND = 'ENUM_TYPE_NAME_NOT_FOUND',
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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',
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
@ -10,6 +10,10 @@ import {
|
|||||||
WorkspaceMigrationColumnAlter,
|
WorkspaceMigrationColumnAlter,
|
||||||
WorkspaceMigrationRenamedEnum,
|
WorkspaceMigrationRenamedEnum,
|
||||||
} from 'src/engine/metadata-modules/workspace-migration/workspace-migration.entity';
|
} from 'src/engine/metadata-modules/workspace-migration/workspace-migration.entity';
|
||||||
|
import {
|
||||||
|
WorkspaceMigrationException,
|
||||||
|
WorkspaceMigrationExceptionCode,
|
||||||
|
} from 'src/engine/metadata-modules/workspace-migration/workspace-migration.exception';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class WorkspaceMigrationEnumService {
|
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 columnDefinition = migrationColumn.alteredColumnDefinition;
|
||||||
const oldEnumTypeName = `${tableName}_${migrationColumn.currentColumnDefinition.columnName}_enum`;
|
|
||||||
const tempEnumTypeName = `${oldEnumTypeName}_temp`;
|
const tempEnumTypeName = `${oldEnumTypeName}_temp`;
|
||||||
const newEnumTypeName = `${tableName}_${columnDefinition.columnName}_enum`;
|
const newEnumTypeName = `${tableName}_${columnDefinition.columnName}_enum`;
|
||||||
const enumValues =
|
const enumValues =
|
||||||
@ -214,4 +224,27 @@ export class WorkspaceMigrationEnumService {
|
|||||||
RENAME TO "${newEnumTypeName}"
|
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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user