Alter comment on foreign key deletion (#5406)

We do not update the comment on the local table when a foreign table key
is deleted.
This was not breaking, which is why we did not see it. But comments
should be kept up to date.

---------

Co-authored-by: Thomas Trompette <thomast@twenty.com>
This commit is contained in:
Thomas Trompette
2024-05-15 17:05:30 +02:00
committed by GitHub
parent f0383e3147
commit e1eead56c6
5 changed files with 191 additions and 6 deletions

View File

@ -0,0 +1,76 @@
import { buildAlteredCommentOnForeignKeyDeletion } from 'src/engine/metadata-modules/object-metadata/utils/create-migration-for-foreign-key-comment-alteration.util';
describe('buildAlteredCommentOnForeignKeyDeletion', () => {
const localObjectMetadataName = 'favorite';
const remoteObjectMetadataName = 'blog';
const schema = 'schema';
const workspaceDataSource = {
query: jest.fn(),
};
it('should return null if no comment ', async () => {
workspaceDataSource.query.mockResolvedValueOnce([]);
const result = await buildAlteredCommentOnForeignKeyDeletion(
localObjectMetadataName,
remoteObjectMetadataName,
schema,
workspaceDataSource as any,
);
expect(result).toBeNull();
});
it('should return null if the existing comment does not contain foreign keys', async () => {
workspaceDataSource.query.mockResolvedValueOnce([
{ col_description: '@graphql({"totalCount":{"enabled":true}})' },
]);
const result = await buildAlteredCommentOnForeignKeyDeletion(
localObjectMetadataName,
remoteObjectMetadataName,
schema,
workspaceDataSource as any,
);
expect(result).toBeNull();
});
it('should return altered comment without foreign key', async () => {
const existingComment = {
col_description: `@graphql({"totalCount":{"enabled":true},"foreign_keys":[{"local_name":"favoriteCollection","local_columns":["${remoteObjectMetadataName}Id"],"foreign_name":"${remoteObjectMetadataName}","foreign_schema":"schema","foreign_table":"${remoteObjectMetadataName}","foreign_columns":["id"]}]})`,
};
workspaceDataSource.query.mockResolvedValueOnce([existingComment]);
const result = await buildAlteredCommentOnForeignKeyDeletion(
localObjectMetadataName,
remoteObjectMetadataName,
schema,
workspaceDataSource as any,
);
expect(result).toBe(
'@graphql({"totalCount":{"enabled":true},"foreign_keys":[]})',
);
});
it('should return altered comment without the input foreign key', async () => {
const existingComment = {
col_description: `@graphql({"totalCount":{"enabled":true},"foreign_keys":[{"local_name":"favoriteCollection","local_columns":["${remoteObjectMetadataName}Id"],"foreign_name":"${remoteObjectMetadataName}","foreign_schema":"schema","foreign_table":"${remoteObjectMetadataName}","foreign_columns":["id"]}, {"local_name":"favoriteCollection","local_columns":["testId"],"foreign_name":"test","foreign_schema":"schema","foreign_table":"test","foreign_columns":["id"]}]})`,
};
workspaceDataSource.query.mockResolvedValueOnce([existingComment]);
const result = await buildAlteredCommentOnForeignKeyDeletion(
localObjectMetadataName,
remoteObjectMetadataName,
schema,
workspaceDataSource as any,
);
expect(result).toBe(
'@graphql({"totalCount":{"enabled":true},"foreign_keys":[{"local_name":"favoriteCollection","local_columns":["testId"],"foreign_name":"test","foreign_schema":"schema","foreign_table":"test","foreign_columns":["id"]}]})',
);
});
});

View File

@ -0,0 +1,97 @@
import { DataSource } from 'typeorm';
import { TypeORMService } from 'src/database/typeorm/typeorm.service';
import { DataSourceService } from 'src/engine/metadata-modules/data-source/data-source.service';
import { RelationToDelete } from 'src/engine/metadata-modules/relation-metadata/types/relation-to-delete';
import { generateMigrationName } from 'src/engine/metadata-modules/workspace-migration/utils/generate-migration-name.util';
import {
WorkspaceMigrationTableActionType,
WorkspaceMigrationColumnActionType,
WorkspaceMigrationCreateComment,
} from 'src/engine/metadata-modules/workspace-migration/workspace-migration.entity';
import { WorkspaceMigrationService } from 'src/engine/metadata-modules/workspace-migration/workspace-migration.service';
import { computeTableName } from 'src/engine/utils/compute-table-name.util';
export const buildAlteredCommentOnForeignKeyDeletion = async (
localObjectMetadataName: string,
remoteObjectMetadataName: string,
schema: string,
workspaceDataSource: DataSource | undefined,
): Promise<string | null> => {
const existingComment = await workspaceDataSource?.query(
`SELECT col_description('${schema}."${localObjectMetadataName}"'::regclass, 0)`,
);
if (!existingComment[0]?.col_description) {
return null;
}
const commentWithoutGraphQL = existingComment[0].col_description
.replace('@graphql(', '')
.replace(')', '');
const parsedComment = JSON.parse(commentWithoutGraphQL);
const currentForeignKeys = parsedComment.foreign_keys;
if (!currentForeignKeys) {
return null;
}
const updatedForeignKeys = currentForeignKeys.filter(
(foreignKey: any) =>
foreignKey.foreign_name !== remoteObjectMetadataName &&
foreignKey.foreign_table !== remoteObjectMetadataName,
);
parsedComment.foreign_keys = updatedForeignKeys;
return `@graphql(${JSON.stringify(parsedComment)})`;
};
export const createMigrationToAlterCommentOnForeignKeyDeletion = async (
dataSourceService: DataSourceService,
typeORMService: TypeORMService,
workspaceMigrationService: WorkspaceMigrationService,
workspaceId: string,
relationToDelete: RelationToDelete,
) => {
const dataSourceMetadata =
await dataSourceService.getLastDataSourceMetadataFromWorkspaceIdOrFail(
workspaceId,
);
const workspaceDataSource =
await typeORMService.connectToDataSource(dataSourceMetadata);
const alteredComment = await buildAlteredCommentOnForeignKeyDeletion(
relationToDelete.toObjectName,
relationToDelete.fromObjectName,
dataSourceMetadata.schema,
workspaceDataSource,
);
if (alteredComment) {
await workspaceMigrationService.createCustomMigration(
generateMigrationName(
`alter-comment-${relationToDelete.fromObjectName}-${relationToDelete.toObjectName}`,
),
workspaceId,
[
{
name: computeTableName(
relationToDelete.toObjectName,
relationToDelete.toObjectMetadataIsCustom,
),
action: WorkspaceMigrationTableActionType.ALTER,
columns: [
{
action: WorkspaceMigrationColumnActionType.CREATE_COMMENT,
comment: alteredComment,
} satisfies WorkspaceMigrationCreateComment,
],
},
],
);
}
};

View File

@ -9,7 +9,7 @@ import {
} from 'src/engine/metadata-modules/workspace-migration/workspace-migration.entity';
import { computeObjectTargetTable } from 'src/engine/utils/compute-object-target-table.util';
export const createWorkspaceMigrationsForCustomObject = (
export const createWorkspaceMigrationsForCustomObjectRelations = (
createdObjectMetadata: ObjectMetadataEntity,
activityTargetObjectMetadata: ObjectMetadataEntity,
attachmentObjectMetadata: ObjectMetadataEntity,

View File

@ -47,7 +47,7 @@ const buildCommentForRemoteObjectForeignKey = async (
return `@graphql(${JSON.stringify(parsedComment)})`;
};
export const createWorkspaceMigrationsForRemoteObject = async (
export const createWorkspaceMigrationsForRemoteObjectRelations = async (
createdObjectMetadata: ObjectMetadataEntity,
activityTargetObjectMetadata: ObjectMetadataEntity,
attachmentObjectMetadata: ObjectMetadataEntity,