Skip to content

Commit

Permalink
Alter comment on foreign key deletion (#5406)
Browse files Browse the repository at this point in the history
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>
  • Loading branch information
thomtrp and Thomas Trompette committed May 15, 2024
1 parent f0383e3 commit e1eead5
Show file tree
Hide file tree
Showing 5 changed files with 191 additions and 6 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -54,14 +54,15 @@ import {
createForeignKeyDeterministicUuid,
createRelationDeterministicUuid,
} from 'src/engine/workspace-manager/workspace-sync-metadata/utils/create-deterministic-uuid.util';
import { createWorkspaceMigrationsForCustomObject } from 'src/engine/metadata-modules/object-metadata/utils/create-workspace-migrations-for-custom-object.util';
import { createWorkspaceMigrationsForRemoteObject } from 'src/engine/metadata-modules/object-metadata/utils/create-workspace-migrations-for-remote-object.util';
import { createWorkspaceMigrationsForCustomObjectRelations } from 'src/engine/metadata-modules/object-metadata/utils/create-migrations-for-custom-object-relations.util';
import { createWorkspaceMigrationsForRemoteObjectRelations } from 'src/engine/metadata-modules/object-metadata/utils/create-workspace-migrations-for-remote-object-relations.util';
import { computeColumnName } from 'src/engine/metadata-modules/field-metadata/utils/compute-column-name.util';
import { DataSourceEntity } from 'src/engine/metadata-modules/data-source/data-source.entity';
import { validateObjectMetadataInput } from 'src/engine/metadata-modules/object-metadata/utils/validate-object-metadata-input.util';
import { mapUdtNameToFieldType } from 'src/engine/metadata-modules/remote-server/remote-table/utils/udt-name-mapper.util';
import { WorkspaceCacheVersionService } from 'src/engine/metadata-modules/workspace-cache-version/workspace-cache-version.service';
import { UpdateOneObjectInput } from 'src/engine/metadata-modules/object-metadata/dtos/update-object.input';
import { createMigrationToAlterCommentOnForeignKeyDeletion } from 'src/engine/metadata-modules/object-metadata/utils/create-migration-for-foreign-key-comment-alteration.util';

import { ObjectMetadataEntity } from './object-metadata.entity';

Expand Down Expand Up @@ -205,6 +206,17 @@ export class ObjectMetadataService extends TypeOrmQueryService<ObjectMetadataEnt
},
],
);

// for remote objects, we need to update the comment of the foreign key column
if (objectMetadata.isRemote) {
await createMigrationToAlterCommentOnForeignKeyDeletion(
this.dataSourceService,
this.typeORMService,
this.workspaceMigrationService,
workspaceId,
relationToDelete,
);
}
}
}

Expand Down Expand Up @@ -543,7 +555,7 @@ export class ObjectMetadataService extends TypeOrmQueryService<ObjectMetadataEnt
generateMigrationName(`create-${createdObjectMetadata.nameSingular}`),
createdObjectMetadata.workspaceId,
isRemoteObject
? await createWorkspaceMigrationsForRemoteObject(
? await createWorkspaceMigrationsForRemoteObjectRelations(
createdObjectMetadata,
activityTargetObjectMetadata,
attachmentObjectMetadata,
Expand All @@ -553,7 +565,7 @@ export class ObjectMetadataService extends TypeOrmQueryService<ObjectMetadataEnt
objectMetadataInput.primaryKeyColumnType ?? 'uuid',
workspaceDataSource,
)
: createWorkspaceMigrationsForCustomObject(
: createWorkspaceMigrationsForCustomObjectRelations(
createdObjectMetadata,
activityTargetObjectMetadata,
attachmentObjectMetadata,
Expand Down
Original file line number Diff line number Diff line change
@@ -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"]}]})',
);
});
});
Original file line number Diff line number Diff line change
@@ -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,
],
},
],
);
}
};
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down

0 comments on commit e1eead5

Please sign in to comment.