Add onDeleteAction to RelationMetadata (#4100)
* Add onDeleteAction to relationMetadata * rename to SET NULL * fix migration * fix migration * fix after review
This commit is contained in:
@ -0,0 +1,25 @@
|
|||||||
|
import { MigrationInterface, QueryRunner } from 'typeorm';
|
||||||
|
|
||||||
|
export class AddOnDeleteActionToRelationMetadata1708449210922
|
||||||
|
implements MigrationInterface
|
||||||
|
{
|
||||||
|
name = 'AddOnDeleteActionToRelationMetadata1708449210922';
|
||||||
|
|
||||||
|
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||||
|
await queryRunner.query(
|
||||||
|
`CREATE TYPE "metadata"."relationMetadata_ondeleteaction_enum" AS ENUM('CASCADE', 'RESTRICT', 'SET_NULL')`,
|
||||||
|
);
|
||||||
|
await queryRunner.query(
|
||||||
|
`ALTER TABLE "metadata"."relationMetadata" ADD "onDeleteAction" "metadata"."relationMetadata_ondeleteaction_enum" NOT NULL DEFAULT 'SET_NULL'`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||||
|
await queryRunner.query(
|
||||||
|
`ALTER TABLE "metadata"."relationMetadata" DROP COLUMN "onDeleteAction"`,
|
||||||
|
);
|
||||||
|
await queryRunner.query(
|
||||||
|
`DROP TYPE "metadata"."relationMetadata_ondeleteaction_enum"`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -347,7 +347,7 @@ export class ObjectMetadataService extends TypeOrmQueryService<ObjectMetadataEnt
|
|||||||
action: 'alter',
|
action: 'alter',
|
||||||
columns: [
|
columns: [
|
||||||
{
|
{
|
||||||
action: WorkspaceMigrationColumnActionType.RELATION,
|
action: WorkspaceMigrationColumnActionType.CREATE_FOREIGN_KEY,
|
||||||
columnName: `${computeObjectTargetTable(
|
columnName: `${computeObjectTargetTable(
|
||||||
createdObjectMetadata,
|
createdObjectMetadata,
|
||||||
)}Id`,
|
)}Id`,
|
||||||
@ -378,7 +378,7 @@ export class ObjectMetadataService extends TypeOrmQueryService<ObjectMetadataEnt
|
|||||||
action: 'alter',
|
action: 'alter',
|
||||||
columns: [
|
columns: [
|
||||||
{
|
{
|
||||||
action: WorkspaceMigrationColumnActionType.RELATION,
|
action: WorkspaceMigrationColumnActionType.CREATE_FOREIGN_KEY,
|
||||||
columnName: `${computeObjectTargetTable(
|
columnName: `${computeObjectTargetTable(
|
||||||
createdObjectMetadata,
|
createdObjectMetadata,
|
||||||
)}Id`,
|
)}Id`,
|
||||||
|
|||||||
@ -20,6 +20,12 @@ export enum RelationMetadataType {
|
|||||||
MANY_TO_MANY = 'MANY_TO_MANY',
|
MANY_TO_MANY = 'MANY_TO_MANY',
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export enum RelationOnDeleteAction {
|
||||||
|
CASCADE = 'CASCADE',
|
||||||
|
RESTRICT = 'RESTRICT',
|
||||||
|
SET_NULL = 'SET_NULL',
|
||||||
|
}
|
||||||
|
|
||||||
@Entity('relationMetadata')
|
@Entity('relationMetadata')
|
||||||
export class RelationMetadataEntity implements RelationMetadataInterface {
|
export class RelationMetadataEntity implements RelationMetadataInterface {
|
||||||
@PrimaryGeneratedColumn('uuid')
|
@PrimaryGeneratedColumn('uuid')
|
||||||
@ -28,6 +34,14 @@ export class RelationMetadataEntity implements RelationMetadataInterface {
|
|||||||
@Column({ nullable: false })
|
@Column({ nullable: false })
|
||||||
relationType: RelationMetadataType;
|
relationType: RelationMetadataType;
|
||||||
|
|
||||||
|
@Column({
|
||||||
|
nullable: false,
|
||||||
|
default: RelationOnDeleteAction.SET_NULL,
|
||||||
|
type: 'enum',
|
||||||
|
enum: RelationOnDeleteAction,
|
||||||
|
})
|
||||||
|
onDeleteAction: RelationOnDeleteAction;
|
||||||
|
|
||||||
@Column({ nullable: false, type: 'uuid' })
|
@Column({ nullable: false, type: 'uuid' })
|
||||||
fromObjectMetadataId: string;
|
fromObjectMetadataId: string;
|
||||||
|
|
||||||
|
|||||||
@ -200,7 +200,7 @@ export class RelationMetadataService extends TypeOrmQueryService<RelationMetadat
|
|||||||
action: 'alter',
|
action: 'alter',
|
||||||
columns: [
|
columns: [
|
||||||
{
|
{
|
||||||
action: WorkspaceMigrationColumnActionType.RELATION,
|
action: WorkspaceMigrationColumnActionType.CREATE_FOREIGN_KEY,
|
||||||
columnName: foreignKeyColumnName,
|
columnName: foreignKeyColumnName,
|
||||||
referencedTableName: computeObjectTargetTable(
|
referencedTableName: computeObjectTargetTable(
|
||||||
objectMetadataMap[relationMetadataInput.fromObjectMetadataId],
|
objectMetadataMap[relationMetadataInput.fromObjectMetadataId],
|
||||||
|
|||||||
@ -5,10 +5,13 @@ import {
|
|||||||
PrimaryGeneratedColumn,
|
PrimaryGeneratedColumn,
|
||||||
} from 'typeorm';
|
} from 'typeorm';
|
||||||
|
|
||||||
|
import { RelationOnDeleteAction } from 'src/metadata/relation-metadata/relation-metadata.entity';
|
||||||
|
|
||||||
export enum WorkspaceMigrationColumnActionType {
|
export enum WorkspaceMigrationColumnActionType {
|
||||||
CREATE = 'CREATE',
|
CREATE = 'CREATE',
|
||||||
ALTER = 'ALTER',
|
ALTER = 'ALTER',
|
||||||
RELATION = 'RELATION',
|
CREATE_FOREIGN_KEY = 'CREATE_FOREIGN_KEY',
|
||||||
|
DROP_FOREIGN_KEY = 'DROP_FOREIGN_KEY',
|
||||||
DROP = 'DROP',
|
DROP = 'DROP',
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -34,12 +37,18 @@ export type WorkspaceMigrationColumnAlter = {
|
|||||||
alteredColumnDefinition: WorkspaceMigrationColumnDefinition;
|
alteredColumnDefinition: WorkspaceMigrationColumnDefinition;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type WorkspaceMigrationColumnRelation = {
|
export type WorkspaceMigrationColumnCreateRelation = {
|
||||||
action: WorkspaceMigrationColumnActionType.RELATION;
|
action: WorkspaceMigrationColumnActionType.CREATE_FOREIGN_KEY;
|
||||||
columnName: string;
|
columnName: string;
|
||||||
referencedTableName: string;
|
referencedTableName: string;
|
||||||
referencedTableColumnName: string;
|
referencedTableColumnName: string;
|
||||||
isUnique?: boolean;
|
isUnique?: boolean;
|
||||||
|
onDelete?: RelationOnDeleteAction;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type WorkspaceMigrationColumnDropRelation = {
|
||||||
|
action: WorkspaceMigrationColumnActionType.DROP_FOREIGN_KEY;
|
||||||
|
columnName: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type WorkspaceMigrationColumnDrop = {
|
export type WorkspaceMigrationColumnDrop = {
|
||||||
@ -52,7 +61,8 @@ export type WorkspaceMigrationColumnAction = {
|
|||||||
} & (
|
} & (
|
||||||
| WorkspaceMigrationColumnCreate
|
| WorkspaceMigrationColumnCreate
|
||||||
| WorkspaceMigrationColumnAlter
|
| WorkspaceMigrationColumnAlter
|
||||||
| WorkspaceMigrationColumnRelation
|
| WorkspaceMigrationColumnCreateRelation
|
||||||
|
| WorkspaceMigrationColumnDropRelation
|
||||||
| WorkspaceMigrationColumnDrop
|
| WorkspaceMigrationColumnDrop
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@ -29,6 +29,7 @@ export enum WorkspaceHealthIssueType {
|
|||||||
RELATION_FROM_OR_TO_FIELD_METADATA_NOT_VALID = 'RELATION_FROM_OR_TO_FIELD_METADATA_NOT_VALID',
|
RELATION_FROM_OR_TO_FIELD_METADATA_NOT_VALID = 'RELATION_FROM_OR_TO_FIELD_METADATA_NOT_VALID',
|
||||||
RELATION_FOREIGN_KEY_NOT_VALID = 'RELATION_FOREIGN_KEY_NOT_VALID',
|
RELATION_FOREIGN_KEY_NOT_VALID = 'RELATION_FOREIGN_KEY_NOT_VALID',
|
||||||
RELATION_FOREIGN_KEY_CONFLICT = 'RELATION_FOREIGN_KEY_CONFLICT',
|
RELATION_FOREIGN_KEY_CONFLICT = 'RELATION_FOREIGN_KEY_CONFLICT',
|
||||||
|
RELATION_FOREIGN_KEY_ON_DELETE_ACTION_CONFLICT = 'RELATION_FOREIGN_KEY_ON_DELETE_ACTION_CONFLICT',
|
||||||
RELATION_TYPE_NOT_VALID = 'RELATION_TYPE_NOT_VALID',
|
RELATION_TYPE_NOT_VALID = 'RELATION_TYPE_NOT_VALID',
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -85,6 +86,7 @@ export type WorkspaceRelationIssueTypes =
|
|||||||
| WorkspaceHealthIssueType.RELATION_FROM_OR_TO_FIELD_METADATA_NOT_VALID
|
| WorkspaceHealthIssueType.RELATION_FROM_OR_TO_FIELD_METADATA_NOT_VALID
|
||||||
| WorkspaceHealthIssueType.RELATION_FOREIGN_KEY_NOT_VALID
|
| WorkspaceHealthIssueType.RELATION_FOREIGN_KEY_NOT_VALID
|
||||||
| WorkspaceHealthIssueType.RELATION_FOREIGN_KEY_CONFLICT
|
| WorkspaceHealthIssueType.RELATION_FOREIGN_KEY_CONFLICT
|
||||||
|
| WorkspaceHealthIssueType.RELATION_FOREIGN_KEY_ON_DELETE_ACTION_CONFLICT
|
||||||
| WorkspaceHealthIssueType.RELATION_TYPE_NOT_VALID;
|
| WorkspaceHealthIssueType.RELATION_TYPE_NOT_VALID;
|
||||||
|
|
||||||
export interface WorkspaceHealthRelationIssue<
|
export interface WorkspaceHealthRelationIssue<
|
||||||
|
|||||||
@ -8,6 +8,8 @@ export interface WorkspaceTableStructure {
|
|||||||
isPrimaryKey: boolean;
|
isPrimaryKey: boolean;
|
||||||
isForeignKey: boolean;
|
isForeignKey: boolean;
|
||||||
isUnique: boolean;
|
isUnique: boolean;
|
||||||
|
onUpdateAction: string;
|
||||||
|
onDeleteAction: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type WorkspaceTableStructureResult = {
|
export type WorkspaceTableStructureResult = {
|
||||||
|
|||||||
@ -85,7 +85,9 @@ export class DatabaseStructureService {
|
|||||||
CASE
|
CASE
|
||||||
WHEN uc.column_name IS NOT NULL THEN 'TRUE'
|
WHEN uc.column_name IS NOT NULL THEN 'TRUE'
|
||||||
ELSE 'FALSE'
|
ELSE 'FALSE'
|
||||||
END AS "isUnique"
|
END AS "isUnique",
|
||||||
|
rc.update_rule AS "onUpdateAction",
|
||||||
|
rc.delete_rule AS "onDeleteAction"
|
||||||
FROM
|
FROM
|
||||||
information_schema.columns AS c
|
information_schema.columns AS c
|
||||||
LEFT JOIN
|
LEFT JOIN
|
||||||
@ -109,6 +111,9 @@ export class DatabaseStructureService {
|
|||||||
ON c.table_schema = uc.schema_name
|
ON c.table_schema = uc.schema_name
|
||||||
AND c.table_name = uc.table_name
|
AND c.table_name = uc.table_name
|
||||||
AND c.column_name = uc.column_name
|
AND c.column_name = uc.column_name
|
||||||
|
LEFT JOIN
|
||||||
|
information_schema.referential_constraints AS rc
|
||||||
|
ON rc.constraint_name = fk.constraint_name
|
||||||
WHERE
|
WHERE
|
||||||
c.table_schema = '${schemaName}'
|
c.table_schema = '${schemaName}'
|
||||||
AND c.table_name = '${tableName}';
|
AND c.table_name = '${tableName}';
|
||||||
|
|||||||
@ -209,6 +209,16 @@ export class RelationMetadataHealthService {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (relationMetadata.onDeleteAction !== relationColumn.onDeleteAction) {
|
||||||
|
issues.push({
|
||||||
|
type: WorkspaceHealthIssueType.RELATION_FOREIGN_KEY_ON_DELETE_ACTION_CONFLICT,
|
||||||
|
fromFieldMetadata,
|
||||||
|
toFieldMetadata,
|
||||||
|
relationMetadata,
|
||||||
|
message: `Relation ${relationMetadata.id} foreign key onDeleteAction is not properly set`,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
return issues;
|
return issues;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -43,11 +43,92 @@ export class WorkspaceMigrationRelationFactory {
|
|||||||
originalObjectMetadataMap,
|
originalObjectMetadataMap,
|
||||||
relationMetadataCollection,
|
relationMetadataCollection,
|
||||||
);
|
);
|
||||||
|
case WorkspaceMigrationBuilderAction.UPDATE:
|
||||||
|
return this.updateRelationMigration(
|
||||||
|
originalObjectMetadataMap,
|
||||||
|
relationMetadataCollection,
|
||||||
|
);
|
||||||
default:
|
default:
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async updateRelationMigration(
|
||||||
|
originalObjectMetadataMap: Record<string, ObjectMetadataEntity>,
|
||||||
|
relationMetadataCollection: RelationMetadataEntity[],
|
||||||
|
): Promise<Partial<WorkspaceMigrationEntity>[]> {
|
||||||
|
const workspaceMigrations: Partial<WorkspaceMigrationEntity>[] = [];
|
||||||
|
|
||||||
|
for (const relationMetadata of relationMetadataCollection) {
|
||||||
|
const toObjectMetadata =
|
||||||
|
originalObjectMetadataMap[relationMetadata.toObjectMetadataId];
|
||||||
|
const fromObjectMetadata =
|
||||||
|
originalObjectMetadataMap[relationMetadata.fromObjectMetadataId];
|
||||||
|
|
||||||
|
if (!toObjectMetadata) {
|
||||||
|
throw new Error(
|
||||||
|
`ObjectMetadata with id ${relationMetadata.toObjectMetadataId} not found`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!fromObjectMetadata) {
|
||||||
|
throw new Error(
|
||||||
|
`ObjectMetadata with id ${relationMetadata.fromObjectMetadataId} not found`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const toFieldMetadata = toObjectMetadata.fields.find(
|
||||||
|
(field) => field.id === relationMetadata.toFieldMetadataId,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!toFieldMetadata) {
|
||||||
|
throw new Error(
|
||||||
|
`FieldMetadata with id ${relationMetadata.toFieldMetadataId} not found`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const migrations: WorkspaceMigrationTableAction[] = [
|
||||||
|
{
|
||||||
|
name: computeObjectTargetTable(toObjectMetadata),
|
||||||
|
action: 'alter',
|
||||||
|
columns: [
|
||||||
|
{
|
||||||
|
action: WorkspaceMigrationColumnActionType.DROP_FOREIGN_KEY,
|
||||||
|
columnName: `${camelCase(toFieldMetadata.name)}Id`,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: computeObjectTargetTable(toObjectMetadata),
|
||||||
|
action: 'alter',
|
||||||
|
columns: [
|
||||||
|
{
|
||||||
|
action: WorkspaceMigrationColumnActionType.CREATE_FOREIGN_KEY,
|
||||||
|
columnName: `${camelCase(toFieldMetadata.name)}Id`,
|
||||||
|
referencedTableName: computeObjectTargetTable(fromObjectMetadata),
|
||||||
|
referencedTableColumnName: 'id',
|
||||||
|
isUnique:
|
||||||
|
relationMetadata.relationType ===
|
||||||
|
RelationMetadataType.ONE_TO_ONE,
|
||||||
|
onDelete: relationMetadata.onDeleteAction,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
workspaceMigrations.push({
|
||||||
|
workspaceId: relationMetadata.workspaceId,
|
||||||
|
name: generateMigrationName(
|
||||||
|
`update-relation-from-${fromObjectMetadata.nameSingular}-to-${toObjectMetadata.nameSingular}`,
|
||||||
|
),
|
||||||
|
isCustom: false,
|
||||||
|
migrations,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return workspaceMigrations;
|
||||||
|
}
|
||||||
|
|
||||||
private async createRelationMigration(
|
private async createRelationMigration(
|
||||||
originalObjectMetadataMap: Record<string, ObjectMetadataEntity>,
|
originalObjectMetadataMap: Record<string, ObjectMetadataEntity>,
|
||||||
relationMetadataCollection: RelationMetadataEntity[],
|
relationMetadataCollection: RelationMetadataEntity[],
|
||||||
@ -88,13 +169,14 @@ export class WorkspaceMigrationRelationFactory {
|
|||||||
action: 'alter',
|
action: 'alter',
|
||||||
columns: [
|
columns: [
|
||||||
{
|
{
|
||||||
action: WorkspaceMigrationColumnActionType.RELATION,
|
action: WorkspaceMigrationColumnActionType.CREATE_FOREIGN_KEY,
|
||||||
columnName: `${camelCase(toFieldMetadata.name)}Id`,
|
columnName: `${camelCase(toFieldMetadata.name)}Id`,
|
||||||
referencedTableName: computeObjectTargetTable(fromObjectMetadata),
|
referencedTableName: computeObjectTargetTable(fromObjectMetadata),
|
||||||
referencedTableColumnName: 'id',
|
referencedTableColumnName: 'id',
|
||||||
isUnique:
|
isUnique:
|
||||||
relationMetadata.relationType ===
|
relationMetadata.relationType ===
|
||||||
RelationMetadataType.ONE_TO_ONE,
|
RelationMetadataType.ONE_TO_ONE,
|
||||||
|
onDelete: relationMetadata.onDeleteAction,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
|||||||
@ -15,8 +15,9 @@ import {
|
|||||||
WorkspaceMigrationColumnAction,
|
WorkspaceMigrationColumnAction,
|
||||||
WorkspaceMigrationColumnActionType,
|
WorkspaceMigrationColumnActionType,
|
||||||
WorkspaceMigrationColumnCreate,
|
WorkspaceMigrationColumnCreate,
|
||||||
WorkspaceMigrationColumnRelation,
|
WorkspaceMigrationColumnCreateRelation,
|
||||||
WorkspaceMigrationColumnAlter,
|
WorkspaceMigrationColumnAlter,
|
||||||
|
WorkspaceMigrationColumnDropRelation,
|
||||||
} from 'src/metadata/workspace-migration/workspace-migration.entity';
|
} from 'src/metadata/workspace-migration/workspace-migration.entity';
|
||||||
import { WorkspaceCacheVersionService } from 'src/metadata/workspace-cache-version/workspace-cache-version.service';
|
import { WorkspaceCacheVersionService } from 'src/metadata/workspace-cache-version/workspace-cache-version.service';
|
||||||
import { WorkspaceMigrationEnumService } from 'src/workspace/workspace-migration-runner/services/workspace-migration-enum.service';
|
import { WorkspaceMigrationEnumService } from 'src/workspace/workspace-migration-runner/services/workspace-migration-enum.service';
|
||||||
@ -200,7 +201,7 @@ export class WorkspaceMigrationRunnerService {
|
|||||||
columnMigration,
|
columnMigration,
|
||||||
);
|
);
|
||||||
break;
|
break;
|
||||||
case WorkspaceMigrationColumnActionType.RELATION:
|
case WorkspaceMigrationColumnActionType.CREATE_FOREIGN_KEY:
|
||||||
await this.createRelation(
|
await this.createRelation(
|
||||||
queryRunner,
|
queryRunner,
|
||||||
schemaName,
|
schemaName,
|
||||||
@ -208,6 +209,14 @@ export class WorkspaceMigrationRunnerService {
|
|||||||
columnMigration,
|
columnMigration,
|
||||||
);
|
);
|
||||||
break;
|
break;
|
||||||
|
case WorkspaceMigrationColumnActionType.DROP_FOREIGN_KEY:
|
||||||
|
await this.dropRelation(
|
||||||
|
queryRunner,
|
||||||
|
schemaName,
|
||||||
|
tableName,
|
||||||
|
columnMigration,
|
||||||
|
);
|
||||||
|
break;
|
||||||
case WorkspaceMigrationColumnActionType.DROP:
|
case WorkspaceMigrationColumnActionType.DROP:
|
||||||
await queryRunner.dropColumn(
|
await queryRunner.dropColumn(
|
||||||
`${schemaName}.${tableName}`,
|
`${schemaName}.${tableName}`,
|
||||||
@ -325,7 +334,7 @@ export class WorkspaceMigrationRunnerService {
|
|||||||
queryRunner: QueryRunner,
|
queryRunner: QueryRunner,
|
||||||
schemaName: string,
|
schemaName: string,
|
||||||
tableName: string,
|
tableName: string,
|
||||||
migrationColumn: WorkspaceMigrationColumnRelation,
|
migrationColumn: WorkspaceMigrationColumnCreateRelation,
|
||||||
) {
|
) {
|
||||||
await queryRunner.createForeignKey(
|
await queryRunner.createForeignKey(
|
||||||
`${schemaName}.${tableName}`,
|
`${schemaName}.${tableName}`,
|
||||||
@ -334,7 +343,7 @@ export class WorkspaceMigrationRunnerService {
|
|||||||
referencedColumnNames: [migrationColumn.referencedTableColumnName],
|
referencedColumnNames: [migrationColumn.referencedTableColumnName],
|
||||||
referencedTableName: migrationColumn.referencedTableName,
|
referencedTableName: migrationColumn.referencedTableName,
|
||||||
referencedSchema: schemaName,
|
referencedSchema: schemaName,
|
||||||
onDelete: 'CASCADE',
|
onDelete: migrationColumn.onDelete?.replace(/_/g, ' '),
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -349,4 +358,57 @@ export class WorkspaceMigrationRunnerService {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async dropRelation(
|
||||||
|
queryRunner: QueryRunner,
|
||||||
|
schemaName: string,
|
||||||
|
tableName: string,
|
||||||
|
migrationColumn: WorkspaceMigrationColumnDropRelation,
|
||||||
|
) {
|
||||||
|
const foreignKeyName = await this.getForeignKeyName(
|
||||||
|
queryRunner,
|
||||||
|
schemaName,
|
||||||
|
tableName,
|
||||||
|
migrationColumn.columnName,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!foreignKeyName) {
|
||||||
|
throw new Error(
|
||||||
|
`Foreign key not found for column ${migrationColumn.columnName}`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
await queryRunner.dropForeignKey(
|
||||||
|
`${schemaName}.${tableName}`,
|
||||||
|
foreignKeyName,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async getForeignKeyName(
|
||||||
|
queryRunner: QueryRunner,
|
||||||
|
schemaName: string,
|
||||||
|
tableName: string,
|
||||||
|
columnName: string,
|
||||||
|
): Promise<string | undefined> {
|
||||||
|
const foreignKeys = await queryRunner.query(
|
||||||
|
`
|
||||||
|
SELECT
|
||||||
|
tc.constraint_name AS constraint_name
|
||||||
|
FROM
|
||||||
|
information_schema.table_constraints AS tc
|
||||||
|
JOIN
|
||||||
|
information_schema.key_column_usage AS kcu
|
||||||
|
ON tc.constraint_name = kcu.constraint_name
|
||||||
|
AND tc.table_schema = kcu.table_schema
|
||||||
|
WHERE
|
||||||
|
tc.constraint_type = 'FOREIGN KEY'
|
||||||
|
AND tc.table_schema = $1
|
||||||
|
AND tc.table_name = $2
|
||||||
|
AND kcu.column_name = $3
|
||||||
|
`,
|
||||||
|
[schemaName, tableName, columnName],
|
||||||
|
);
|
||||||
|
|
||||||
|
return foreignKeys[0]?.constraint_name;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -48,6 +48,28 @@ describe('WorkspaceRelationComparator', () => {
|
|||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should generate UPDATE action for changed relations', () => {
|
||||||
|
const original = [
|
||||||
|
createMockRelationMetadata({ onDeleteAction: 'CASCADE' }),
|
||||||
|
];
|
||||||
|
const standard = [
|
||||||
|
createMockRelationMetadata({ onDeleteAction: 'SET_NULL' }),
|
||||||
|
];
|
||||||
|
|
||||||
|
const result = comparator.compare(original, standard);
|
||||||
|
|
||||||
|
expect(result).toEqual([
|
||||||
|
{
|
||||||
|
action: ComparatorAction.UPDATE,
|
||||||
|
object: expect.objectContaining({
|
||||||
|
fromObjectMetadataId: 'object-1',
|
||||||
|
fromFieldMetadataId: 'field-1',
|
||||||
|
onDeleteAction: 'SET_NULL',
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
it('should not generate any action for identical relations', () => {
|
it('should not generate any action for identical relations', () => {
|
||||||
const relation = createMockRelationMetadata({});
|
const relation = createMockRelationMetadata({});
|
||||||
const original = [{ id: '1', ...relation }];
|
const original = [{ id: '1', ...relation }];
|
||||||
|
|||||||
@ -11,6 +11,7 @@ import { RelationMetadataEntity } from 'src/metadata/relation-metadata/relation-
|
|||||||
import { transformMetadataForComparison } from 'src/workspace/workspace-sync-metadata/comparators/utils/transform-metadata-for-comparison.util';
|
import { transformMetadataForComparison } from 'src/workspace/workspace-sync-metadata/comparators/utils/transform-metadata-for-comparison.util';
|
||||||
|
|
||||||
const relationPropertiesToIgnore = ['createdAt', 'updatedAt'] as const;
|
const relationPropertiesToIgnore = ['createdAt', 'updatedAt'] as const;
|
||||||
|
const relationPropertiesToUpdate = ['onDeleteAction'];
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class WorkspaceRelationComparator {
|
export class WorkspaceRelationComparator {
|
||||||
@ -51,19 +52,54 @@ export class WorkspaceRelationComparator {
|
|||||||
);
|
);
|
||||||
|
|
||||||
for (const difference of relationMetadataDifference) {
|
for (const difference of relationMetadataDifference) {
|
||||||
if (difference.type === 'CREATE') {
|
switch (difference.type) {
|
||||||
results.push({
|
case 'CREATE':
|
||||||
action: ComparatorAction.CREATE,
|
results.push({
|
||||||
object: difference.value,
|
action: ComparatorAction.CREATE,
|
||||||
});
|
object: difference.value,
|
||||||
} else if (
|
});
|
||||||
difference.type === 'REMOVE' &&
|
break;
|
||||||
difference.path[difference.path.length - 1] !== 'id'
|
case 'REMOVE':
|
||||||
) {
|
if (difference.path[difference.path.length - 1] !== 'id') {
|
||||||
results.push({
|
results.push({
|
||||||
action: ComparatorAction.DELETE,
|
action: ComparatorAction.DELETE,
|
||||||
object: difference.oldValue,
|
object: difference.oldValue,
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 'CHANGE':
|
||||||
|
const fieldName = difference.path[0];
|
||||||
|
const property = difference.path[difference.path.length - 1];
|
||||||
|
|
||||||
|
if (!relationPropertiesToUpdate.includes(property as string)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const originalRelationMetadata =
|
||||||
|
originalRelationMetadataMap[fieldName];
|
||||||
|
|
||||||
|
if (!originalRelationMetadata) {
|
||||||
|
throw new Error(
|
||||||
|
`Relation ${fieldName} not found in originalRelationMetadataMap`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
results.push({
|
||||||
|
action: ComparatorAction.UPDATE,
|
||||||
|
object: {
|
||||||
|
id: originalRelationMetadata.id,
|
||||||
|
fromObjectMetadataId:
|
||||||
|
originalRelationMetadata.fromObjectMetadataId,
|
||||||
|
fromFieldMetadataId: originalRelationMetadata.fromFieldMetadataId,
|
||||||
|
toObjectMetadataId: originalRelationMetadata.toObjectMetadataId,
|
||||||
|
toFieldMetadataId: originalRelationMetadata.toFieldMetadataId,
|
||||||
|
workspaceId: originalRelationMetadata.workspaceId,
|
||||||
|
...{
|
||||||
|
[property]: difference.value,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,9 +1,13 @@
|
|||||||
import 'reflect-metadata';
|
import 'reflect-metadata';
|
||||||
|
|
||||||
import { RelationMetadataDecoratorParams } from 'src/workspace/workspace-sync-metadata/interfaces/reflect-relation-metadata.interface';
|
import {
|
||||||
|
ReflectRelationMetadata,
|
||||||
|
RelationMetadataDecoratorParams,
|
||||||
|
} from 'src/workspace/workspace-sync-metadata/interfaces/reflect-relation-metadata.interface';
|
||||||
|
|
||||||
import { TypedReflect } from 'src/utils/typed-reflect';
|
import { TypedReflect } from 'src/utils/typed-reflect';
|
||||||
import { convertClassNameToObjectMetadataName } from 'src/workspace/workspace-sync-metadata/utils/convert-class-to-object-metadata-name.util';
|
import { convertClassNameToObjectMetadataName } from 'src/workspace/workspace-sync-metadata/utils/convert-class-to-object-metadata-name.util';
|
||||||
|
import { RelationOnDeleteAction } from 'src/metadata/relation-metadata/relation-metadata.entity';
|
||||||
|
|
||||||
export function RelationMetadata(
|
export function RelationMetadata(
|
||||||
params: RelationMetadataDecoratorParams,
|
params: RelationMetadataDecoratorParams,
|
||||||
@ -29,8 +33,9 @@ export function RelationMetadata(
|
|||||||
toObjectNameSingular: params.objectName,
|
toObjectNameSingular: params.objectName,
|
||||||
fromFieldMetadataName: fieldKey,
|
fromFieldMetadataName: fieldKey,
|
||||||
toFieldMetadataName: params.inverseSideFieldName ?? objectName,
|
toFieldMetadataName: params.inverseSideFieldName ?? objectName,
|
||||||
|
onDelete: params.onDelete ?? RelationOnDeleteAction.SET_NULL,
|
||||||
gate,
|
gate,
|
||||||
},
|
} satisfies ReflectRelationMetadata,
|
||||||
],
|
],
|
||||||
target.constructor,
|
target.constructor,
|
||||||
);
|
);
|
||||||
|
|||||||
@ -110,6 +110,7 @@ export class StandardRelationFactory {
|
|||||||
fromFieldMetadataId: fromFieldMetadata?.id,
|
fromFieldMetadataId: fromFieldMetadata?.id,
|
||||||
toFieldMetadataId: toFieldMetadata?.id,
|
toFieldMetadataId: toFieldMetadata?.id,
|
||||||
workspaceId: context.workspaceId,
|
workspaceId: context.workspaceId,
|
||||||
|
onDeleteAction: relationMetadata.onDelete,
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@ -43,4 +43,5 @@ export type FieldComparatorResult =
|
|||||||
|
|
||||||
export type RelationComparatorResult =
|
export type RelationComparatorResult =
|
||||||
| ComparatorCreateResult<Partial<RelationMetadataEntity>>
|
| ComparatorCreateResult<Partial<RelationMetadataEntity>>
|
||||||
| ComparatorDeleteResult<RelationMetadataEntity>;
|
| ComparatorDeleteResult<RelationMetadataEntity>
|
||||||
|
| ComparatorUpdateResult<Partial<RelationMetadataEntity>>;
|
||||||
|
|||||||
@ -0,0 +1,10 @@
|
|||||||
|
import { ReflectRelationMetadata } from 'src/workspace/workspace-sync-metadata/interfaces/reflect-Relation-metadata.interface';
|
||||||
|
|
||||||
|
export type PartialRelationMetadata = ReflectRelationMetadata & {
|
||||||
|
id: string;
|
||||||
|
workspaceId: string;
|
||||||
|
fromObjectMetadataId: string;
|
||||||
|
toObjectMetadataId: string;
|
||||||
|
fromFieldMetadataId: string;
|
||||||
|
toFieldMetadataId: string;
|
||||||
|
};
|
||||||
@ -1,11 +1,15 @@
|
|||||||
import { GateDecoratorParams } from 'src/workspace/workspace-sync-metadata/interfaces/gate-decorator.interface';
|
import { GateDecoratorParams } from 'src/workspace/workspace-sync-metadata/interfaces/gate-decorator.interface';
|
||||||
|
|
||||||
import { RelationMetadataType } from 'src/metadata/relation-metadata/relation-metadata.entity';
|
import {
|
||||||
|
RelationOnDeleteAction,
|
||||||
|
RelationMetadataType,
|
||||||
|
} from 'src/metadata/relation-metadata/relation-metadata.entity';
|
||||||
|
|
||||||
export interface RelationMetadataDecoratorParams {
|
export interface RelationMetadataDecoratorParams {
|
||||||
type: RelationMetadataType;
|
type: RelationMetadataType;
|
||||||
objectName: string;
|
objectName: string;
|
||||||
inverseSideFieldName?: string;
|
inverseSideFieldName?: string;
|
||||||
|
onDelete?: RelationOnDeleteAction;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ReflectRelationMetadata {
|
export interface ReflectRelationMetadata {
|
||||||
@ -15,4 +19,5 @@ export interface ReflectRelationMetadata {
|
|||||||
fromFieldMetadataName: string;
|
fromFieldMetadataName: string;
|
||||||
toFieldMetadataName: string;
|
toFieldMetadataName: string;
|
||||||
gate?: GateDecoratorParams;
|
gate?: GateDecoratorParams;
|
||||||
|
onDelete: RelationOnDeleteAction;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -209,6 +209,7 @@ export class WorkspaceMetadataUpdaterService {
|
|||||||
storage: WorkspaceSyncStorage,
|
storage: WorkspaceSyncStorage,
|
||||||
): Promise<{
|
): Promise<{
|
||||||
createdRelationMetadataCollection: RelationMetadataEntity[];
|
createdRelationMetadataCollection: RelationMetadataEntity[];
|
||||||
|
updatedRelationMetadataCollection: RelationMetadataEntity[];
|
||||||
}> {
|
}> {
|
||||||
const relationMetadataRepository = manager.getRepository(
|
const relationMetadataRepository = manager.getRepository(
|
||||||
RelationMetadataEntity,
|
RelationMetadataEntity,
|
||||||
@ -223,6 +224,15 @@ export class WorkspaceMetadataUpdaterService {
|
|||||||
storage.relationMetadataCreateCollection,
|
storage.relationMetadataCreateCollection,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update relation metadata
|
||||||
|
*/
|
||||||
|
|
||||||
|
const updatedRelationMetadataCollection =
|
||||||
|
await relationMetadataRepository.save(
|
||||||
|
storage.relationMetadataUpdateCollection,
|
||||||
|
);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Delete relation metadata
|
* Delete relation metadata
|
||||||
*/
|
*/
|
||||||
@ -250,6 +260,7 @@ export class WorkspaceMetadataUpdaterService {
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
createdRelationMetadataCollection,
|
createdRelationMetadataCollection,
|
||||||
|
updatedRelationMetadataCollection,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -84,6 +84,8 @@ export class WorkspaceSyncRelationMetadataService {
|
|||||||
for (const relationComparatorResult of relationComparatorResults) {
|
for (const relationComparatorResult of relationComparatorResults) {
|
||||||
if (relationComparatorResult.action === ComparatorAction.CREATE) {
|
if (relationComparatorResult.action === ComparatorAction.CREATE) {
|
||||||
storage.addCreateRelationMetadata(relationComparatorResult.object);
|
storage.addCreateRelationMetadata(relationComparatorResult.object);
|
||||||
|
} else if (relationComparatorResult.action === ComparatorAction.UPDATE) {
|
||||||
|
storage.addUpdateRelationMetadata(relationComparatorResult.object);
|
||||||
} else if (relationComparatorResult.action === ComparatorAction.DELETE) {
|
} else if (relationComparatorResult.action === ComparatorAction.DELETE) {
|
||||||
storage.addDeleteRelationMetadata(relationComparatorResult.object);
|
storage.addDeleteRelationMetadata(relationComparatorResult.object);
|
||||||
}
|
}
|
||||||
@ -103,6 +105,16 @@ export class WorkspaceSyncRelationMetadataService {
|
|||||||
WorkspaceMigrationBuilderAction.CREATE,
|
WorkspaceMigrationBuilderAction.CREATE,
|
||||||
);
|
);
|
||||||
|
|
||||||
return createRelationWorkspaceMigrations;
|
const updateRelationWorkspaceMigrations =
|
||||||
|
await this.workspaceMigrationRelationFactory.create(
|
||||||
|
originalObjectMetadataCollection,
|
||||||
|
metadataRelationUpdaterResult.updatedRelationMetadataCollection,
|
||||||
|
WorkspaceMigrationBuilderAction.UPDATE,
|
||||||
|
);
|
||||||
|
|
||||||
|
return [
|
||||||
|
...createRelationWorkspaceMigrations,
|
||||||
|
...updateRelationWorkspaceMigrations,
|
||||||
|
];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,5 +1,6 @@
|
|||||||
import { PartialObjectMetadata } from 'src/workspace/workspace-sync-metadata/interfaces/partial-object-metadata.interface';
|
import { PartialObjectMetadata } from 'src/workspace/workspace-sync-metadata/interfaces/partial-object-metadata.interface';
|
||||||
import { PartialFieldMetadata } from 'src/workspace/workspace-sync-metadata/interfaces/partial-field-metadata.interface';
|
import { PartialFieldMetadata } from 'src/workspace/workspace-sync-metadata/interfaces/partial-field-metadata.interface';
|
||||||
|
import { PartialRelationMetadata } from 'src/workspace/workspace-sync-metadata/interfaces/partial-relation-metadata.interface';
|
||||||
|
|
||||||
import { ObjectMetadataEntity } from 'src/metadata/object-metadata/object-metadata.entity';
|
import { ObjectMetadataEntity } from 'src/metadata/object-metadata/object-metadata.entity';
|
||||||
import { FieldMetadataEntity } from 'src/metadata/field-metadata/field-metadata.entity';
|
import { FieldMetadataEntity } from 'src/metadata/field-metadata/field-metadata.entity';
|
||||||
@ -25,6 +26,8 @@ export class WorkspaceSyncStorage {
|
|||||||
[];
|
[];
|
||||||
private readonly _relationMetadataDeleteCollection: RelationMetadataEntity[] =
|
private readonly _relationMetadataDeleteCollection: RelationMetadataEntity[] =
|
||||||
[];
|
[];
|
||||||
|
private readonly _relationMetadataUpdateCollection: Partial<PartialRelationMetadata>[] =
|
||||||
|
[];
|
||||||
|
|
||||||
constructor() {}
|
constructor() {}
|
||||||
|
|
||||||
@ -56,6 +59,10 @@ export class WorkspaceSyncStorage {
|
|||||||
return this._relationMetadataCreateCollection;
|
return this._relationMetadataCreateCollection;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get relationMetadataUpdateCollection() {
|
||||||
|
return this._relationMetadataUpdateCollection;
|
||||||
|
}
|
||||||
|
|
||||||
get relationMetadataDeleteCollection() {
|
get relationMetadataDeleteCollection() {
|
||||||
return this._relationMetadataDeleteCollection;
|
return this._relationMetadataDeleteCollection;
|
||||||
}
|
}
|
||||||
@ -90,6 +97,10 @@ export class WorkspaceSyncStorage {
|
|||||||
this._relationMetadataCreateCollection.push(relation);
|
this._relationMetadataCreateCollection.push(relation);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
addUpdateRelationMetadata(relation: Partial<PartialRelationMetadata>) {
|
||||||
|
this._relationMetadataUpdateCollection.push(relation);
|
||||||
|
}
|
||||||
|
|
||||||
addDeleteRelationMetadata(relation: RelationMetadataEntity) {
|
addDeleteRelationMetadata(relation: RelationMetadataEntity) {
|
||||||
this._relationMetadataDeleteCollection.push(relation);
|
this._relationMetadataDeleteCollection.push(relation);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user