Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: manually implement joinColumn #6022

Merged
merged 10 commits into from
Jun 27, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -31,9 +31,7 @@ export class TimelineCalendarEventService {
const calendarEventIds = await this.calendarEventRepository.find({
where: {
calendarEventParticipants: {
person: {
id: Any(personIds),
},
personId: Any(personIds),
},
},
select: {
Expand Down Expand Up @@ -81,8 +79,8 @@ export class TimelineCalendarEventService {
const participants = event.calendarEventParticipants.map(
(participant) => ({
calendarEventId: event.id,
personId: participant.person?.id ?? null,
workspaceMemberId: participant.workspaceMember?.id ?? null,
personId: participant.personId ?? null,
workspaceMemberId: participant.workspaceMemberId ?? null,
firstName:
participant.person?.name?.firstName ||
participant.workspaceMember?.name.firstName ||
Expand Down Expand Up @@ -135,9 +133,7 @@ export class TimelineCalendarEventService {
): Promise<TimelineCalendarEventsWithTotal> {
const personIds = await this.personRepository.find({
where: {
company: {
id: companyId,
},
companyId,
},
select: {
id: true,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ import { TypeORMService } from 'src/database/typeorm/typeorm.service';
import { DataSourceEntity } from 'src/engine/metadata-modules/data-source/data-source.entity';
import { WorkspaceMemberWorkspaceEntity } from 'src/modules/workspace-member/standard-objects/workspace-member.workspace-entity';
import { ObjectRecordDeleteEvent } from 'src/engine/integrations/event-emitter/types/object-record-delete.event';
import { ObjectRecord } from 'src/engine/workspace-manager/workspace-sync-metadata/types/object-record';
import { WorkspaceService } from 'src/engine/core-modules/workspace/services/workspace.service';

export class UserService extends TypeOrmQueryService<User> {
Expand Down Expand Up @@ -113,8 +112,7 @@ export class UserService extends TypeOrmQueryService<User> {
`SELECT * FROM ${dataSourceMetadata.schema}."workspaceMember"`,
);
const workspaceMember = workspaceMembers.filter(
(member: ObjectRecord<WorkspaceMemberWorkspaceEntity>) =>
member.userId === userId,
(member: WorkspaceMemberWorkspaceEntity) => member.userId === userId,
)?.[0];

assert(workspaceMember, 'WorkspaceMember not found');
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { metadataArgsStorage } from 'src/engine/twenty-orm/storage/metadata-args.storage';

export function WorkspaceJoinColumn(
relationPropertyKey: string,
): PropertyDecorator {
return (object, propertyKey) => {
metadataArgsStorage.addJoinColumns({
target: object.constructor,
relationName: relationPropertyKey,
joinColumn: propertyKey.toString(),
});
};
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,35 +8,19 @@ import { metadataArgsStorage } from 'src/engine/twenty-orm/storage/metadata-args
import { TypedReflect } from 'src/utils/typed-reflect';
import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';

interface WorkspaceBaseRelationOptions<TType, TClass> {
interface WorkspaceRelationOptions<TClass> {
standardId: string;
label: string | ((objectMetadata: ObjectMetadataEntity) => string);
description?: string | ((objectMetadata: ObjectMetadataEntity) => string);
icon?: string;
type: TType;
type: RelationMetadataType;
inverseSideTarget: () => ObjectType<TClass>;
inverseSideFieldKey?: keyof TClass;
onDelete?: RelationOnDeleteAction;
}

export interface WorkspaceManyToOneRelationOptions<TClass>
extends WorkspaceBaseRelationOptions<
RelationMetadataType.MANY_TO_ONE | RelationMetadataType.ONE_TO_ONE,
TClass
> {
joinColumn?: string;
}

export interface WorkspaceOtherRelationOptions<TClass>
extends WorkspaceBaseRelationOptions<
RelationMetadataType.ONE_TO_MANY | RelationMetadataType.MANY_TO_MANY,
TClass
> {}

export function WorkspaceRelation<TClass extends object>(
options:
| WorkspaceManyToOneRelationOptions<TClass>
| WorkspaceOtherRelationOptions<TClass>,
options: WorkspaceRelationOptions<TClass>,
): PropertyDecorator {
return (object, propertyKey) => {
const isPrimary =
Expand All @@ -63,14 +47,6 @@ export function WorkspaceRelation<TClass extends object>(
propertyKey.toString(),
);

let joinColumn: string | undefined;

if ('joinColumn' in options) {
joinColumn = options.joinColumn
? options.joinColumn
: `${propertyKey.toString()}Id`;
}

metadataArgsStorage.addRelations({
target: object.constructor,
standardId: options.standardId,
Expand All @@ -82,7 +58,6 @@ export function WorkspaceRelation<TClass extends object>(
inverseSideTarget: options.inverseSideTarget,
inverseSideFieldKey: options.inverseSideFieldKey as string | undefined,
onDelete: options.onDelete,
joinColumn,
isPrimary,
isNullable,
isSystem,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { ColumnType, EntitySchemaColumnOptions } from 'typeorm';

import { WorkspaceFieldMetadataArgs } from 'src/engine/twenty-orm/interfaces/workspace-field-metadata-args.interface';
import { WorkspaceRelationMetadataArgs } from 'src/engine/twenty-orm/interfaces/workspace-relation-metadata-args.interface';
import { WorkspaceJoinColumnsMetadataArgs } from 'src/engine/twenty-orm/interfaces/workspace-join-columns-metadata-args.interface';

import { fieldMetadataTypeToColumnType } from 'src/engine/metadata-modules/workspace-migration/utils/field-metadata-type-to-column-type.util';
import { isEnumFieldMetadataType } from 'src/engine/metadata-modules/field-metadata/utils/is-enum-field-metadata-type.util';
Expand All @@ -12,6 +13,7 @@ import { computeCompositeColumnName } from 'src/engine/metadata-modules/field-me
import { isCompositeFieldMetadataType } from 'src/engine/metadata-modules/field-metadata/utils/is-composite-field-metadata-type.util';
import { compositeTypeDefintions } from 'src/engine/metadata-modules/field-metadata/composite-types';
import { FieldMetadataType } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity';
import { getJoinColumn } from 'src/engine/twenty-orm/utils/get-join-column.util';

type EntitySchemaColumnMap = {
[key: string]: EntitySchemaColumnOptions;
Expand All @@ -22,6 +24,7 @@ export class EntitySchemaColumnFactory {
create(
fieldMetadataArgsCollection: WorkspaceFieldMetadataArgs[],
relationMetadataArgsCollection: WorkspaceRelationMetadataArgs[],
joinColumnsMetadataArgsCollection: WorkspaceJoinColumnsMetadataArgs[],
): EntitySchemaColumnMap {
let entitySchemaColumnMap: EntitySchemaColumnMap = {};

Expand Down Expand Up @@ -56,9 +59,14 @@ export class EntitySchemaColumnFactory {
};

for (const relationMetadataArgs of relationMetadataArgsCollection) {
if (relationMetadataArgs.joinColumn) {
entitySchemaColumnMap[relationMetadataArgs.joinColumn] = {
name: relationMetadataArgs.joinColumn,
const joinColumn = getJoinColumn(
joinColumnsMetadataArgsCollection,
relationMetadataArgs,
);

if (joinColumn) {
entitySchemaColumnMap[joinColumn] = {
name: joinColumn,
type: 'uuid',
nullable: relationMetadataArgs.isNullable,
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,11 @@ import { EntitySchemaRelationOptions } from 'typeorm';
import { RelationType } from 'typeorm/metadata/types/RelationTypes';

import { WorkspaceRelationMetadataArgs } from 'src/engine/twenty-orm/interfaces/workspace-relation-metadata-args.interface';
import { WorkspaceJoinColumnsMetadataArgs } from 'src/engine/twenty-orm/interfaces/workspace-join-columns-metadata-args.interface';

import { convertClassNameToObjectMetadataName } from 'src/engine/workspace-manager/workspace-sync-metadata/utils/convert-class-to-object-metadata-name.util';
import { RelationMetadataType } from 'src/engine/metadata-modules/relation-metadata/relation-metadata.entity';
import { getJoinColumn } from 'src/engine/twenty-orm/utils/get-join-column.util';

type EntitySchemaRelationMap = {
[key: string]: EntitySchemaRelationOptions;
Expand All @@ -18,6 +20,7 @@ export class EntitySchemaRelationFactory {
// eslint-disable-next-line @typescript-eslint/ban-types
target: Function,
relationMetadataArgsCollection: WorkspaceRelationMetadataArgs[],
joinColumnsMetadataArgsCollection: WorkspaceJoinColumnsMetadataArgs[],
): EntitySchemaRelationMap {
const entitySchemaRelationMap: EntitySchemaRelationMap = {};

Expand All @@ -27,16 +30,19 @@ export class EntitySchemaRelationFactory {
const oppositeObjectName = convertClassNameToObjectMetadataName(
oppositeTarget.name,
);

const relationType = this.getRelationType(relationMetadataArgs);
const joinColumn = getJoinColumn(
joinColumnsMetadataArgsCollection,
relationMetadataArgs,
);

entitySchemaRelationMap[relationMetadataArgs.name] = {
type: relationType,
target: oppositeObjectName,
inverseSide: relationMetadataArgs.inverseSideFieldKey ?? objectName,
joinColumn: relationMetadataArgs.joinColumn
joinColumn: joinColumn
? {
name: relationMetadataArgs.joinColumn,
name: joinColumn,
}
: undefined,
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,17 +23,21 @@ export class EntitySchemaFactory {

const fieldMetadataArgsCollection =
metadataArgsStorage.filterFields(target);
const joinColumnsMetadataArgsCollection =
metadataArgsStorage.filterJoinColumns(target);
const relationMetadataArgsCollection =
metadataArgsStorage.filterRelations(target);

const columns = this.entitySchemaColumnFactory.create(
fieldMetadataArgsCollection,
relationMetadataArgsCollection,
joinColumnsMetadataArgsCollection,
);

const relations = this.entitySchemaRelationFactory.create(
target,
relationMetadataArgsCollection,
joinColumnsMetadataArgsCollection,
);

const entitySchema = new EntitySchema({
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
export interface WorkspaceJoinColumnsMetadataArgs {
/**
* Class to which relation is applied.
*/
// eslint-disable-next-line @typescript-eslint/ban-types
readonly target: Function;

/**
* Relation name.
*/
readonly relationName: string;

/**
* Relation label.
*/
readonly joinColumn: string;
}
Original file line number Diff line number Diff line change
Expand Up @@ -62,11 +62,6 @@ export interface WorkspaceRelationMetadataArgs {
*/
readonly onDelete?: RelationOnDeleteAction;

/**
* Relation join column.
*/
readonly joinColumn?: string;

/**
* Is primary field.
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { WorkspaceEntityMetadataArgs } from 'src/engine/twenty-orm/interfaces/wo
import { WorkspaceRelationMetadataArgs } from 'src/engine/twenty-orm/interfaces/workspace-relation-metadata-args.interface';
import { WorkspaceExtendedEntityMetadataArgs } from 'src/engine/twenty-orm/interfaces/workspace-extended-entity-metadata-args.interface';
import { WorkspaceIndexMetadataArgs } from 'src/engine/twenty-orm/interfaces/workspace-index-metadata-args.interface';
import { WorkspaceJoinColumnsMetadataArgs } from 'src/engine/twenty-orm/interfaces/workspace-join-columns-metadata-args.interface';

export class MetadataArgsStorage {
private readonly entities: WorkspaceEntityMetadataArgs[] = [];
Expand All @@ -15,6 +16,7 @@ export class MetadataArgsStorage {
private readonly dynamicRelations: WorkspaceDynamicRelationMetadataArgs[] =
[];
private readonly indexes: WorkspaceIndexMetadataArgs[] = [];
private readonly joinColumns: WorkspaceJoinColumnsMetadataArgs[] = [];

addEntities(...entities: WorkspaceEntityMetadataArgs[]): void {
this.entities.push(...entities);
Expand Down Expand Up @@ -44,6 +46,10 @@ export class MetadataArgsStorage {
this.dynamicRelations.push(...dynamicRelations);
}

addJoinColumns(...joinColumns: WorkspaceJoinColumnsMetadataArgs[]): void {
this.joinColumns.push(...joinColumns);
}

filterEntities(
target: Function | string,
): WorkspaceEntityMetadataArgs | undefined;
Expand Down Expand Up @@ -123,6 +129,20 @@ export class MetadataArgsStorage {
return this.filterByTarget(this.dynamicRelations, target);
}

filterJoinColumns(
target: Function | string,
): WorkspaceJoinColumnsMetadataArgs[];

filterJoinColumns(
target: (Function | string)[],
): WorkspaceJoinColumnsMetadataArgs[];

filterJoinColumns(
target: (Function | string) | (Function | string)[],
): WorkspaceJoinColumnsMetadataArgs[] {
return this.filterByTarget(this.joinColumns, target);
}

protected filterByTarget<T extends { target: Function | string }>(
array: T[],
target: (Function | string) | (Function | string)[],
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
import { WorkspaceJoinColumnsMetadataArgs } from 'src/engine/twenty-orm/interfaces/workspace-join-columns-metadata-args.interface';
import { WorkspaceRelationMetadataArgs } from 'src/engine/twenty-orm/interfaces/workspace-relation-metadata-args.interface';

import { RelationMetadataType } from 'src/engine/metadata-modules/relation-metadata/relation-metadata.entity';
import { metadataArgsStorage } from 'src/engine/twenty-orm/storage/metadata-args.storage';

export const getJoinColumn = (
joinColumnsMetadataArgsCollection: WorkspaceJoinColumnsMetadataArgs[],
relationMetadataArgs: WorkspaceRelationMetadataArgs,
opposite = false,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🪶 style: Consider renaming opposite to something more descriptive like isInverseSide.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🪶 style: Consider renaming opposite to something more descriptive like isInverseSide.

): string | null => {
if (
relationMetadataArgs.type === RelationMetadataType.ONE_TO_MANY ||
relationMetadataArgs.type === RelationMetadataType.MANY_TO_MANY
) {
return null;
}

const inverseSideTarget = relationMetadataArgs.inverseSideTarget();
const inverseSideJoinColumnsMetadataArgsCollection =
metadataArgsStorage.filterJoinColumns(inverseSideTarget);
const filteredJoinColumnsMetadataArgsCollection =
joinColumnsMetadataArgsCollection.filter(
(joinColumnsMetadataArgs) =>
joinColumnsMetadataArgs.relationName === relationMetadataArgs.name,
);
const oppositeFilteredJoinColumnsMetadataArgsCollection =
inverseSideJoinColumnsMetadataArgsCollection.filter(
(joinColumnsMetadataArgs) =>
joinColumnsMetadataArgs.relationName === relationMetadataArgs.name,
);

if (
filteredJoinColumnsMetadataArgsCollection.length > 0 &&
oppositeFilteredJoinColumnsMetadataArgsCollection.length > 0
) {
throw new Error(
`Join column for ${relationMetadataArgs.name} relation is present on both sides`,
);
Comment on lines +34 to +39
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧠 logic: Avoid potential infinite recursion by adding a maximum recursion depth check.

}

// If we're in a ONE_TO_ONE relation and there are no join columns, we need to find the join column on the inverse side
if (
relationMetadataArgs.type === RelationMetadataType.ONE_TO_ONE &&
filteredJoinColumnsMetadataArgsCollection.length === 0 &&
!opposite
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧠 logic: Avoid potential infinite recursion by adding a maximum recursion depth check.

) {
const inverseSideRelationMetadataArgsCollection =
metadataArgsStorage.filterRelations(inverseSideTarget);
const inverseSideRelationMetadataArgs =
inverseSideRelationMetadataArgsCollection.find(
(inverseSideRelationMetadataArgs) =>
inverseSideRelationMetadataArgs.inverseSideFieldKey ===
relationMetadataArgs.name,
);

if (!inverseSideRelationMetadataArgs) {
throw new Error(
`Inverse side join column of relation ${relationMetadataArgs.name} is missing`,
);
}

return getJoinColumn(
inverseSideJoinColumnsMetadataArgsCollection,
inverseSideRelationMetadataArgs,
// Avoid infinite recursion
true,
);
}

// Check if there are multiple join columns for the relation
if (filteredJoinColumnsMetadataArgsCollection.length > 1) {
throw new Error(
`Multiple join columns found for relation ${relationMetadataArgs.name}`,
);
}

const joinColumnsMetadataArgs = filteredJoinColumnsMetadataArgsCollection[0];

if (!joinColumnsMetadataArgs) {
throw new Error(
`Join column is missing for relation ${relationMetadataArgs.name}`,
);
}

return joinColumnsMetadataArgs.joinColumn;
};
Loading
Loading