[Flexible Schema] Create indexes for join columns (#6165)

## Context
We want to add an index on our foreign keys since PG does not do it for
us. An index can sometimes be expensive and not always meaningful
depending on different usages but in our case we decided to apply an
index for every foreign keys.

```typescript
  @WorkspaceIndex()
  @WorkspaceJoinColumn('author')
  authorId: string;
```
This syntax is valid but since we want to apply it to every join column
I've decided to update the code of WorkspaceJoinColumn so it properly
registers a new index at the same time which is less error-prone.

Note: We had a bug on index name generation since postgres index names
are unique per schema and not table, the object metadata id (hashed) has
been added to the formula that generates the name of the index

## Test
Sync metadata. We have 45 join columns as of today per workspace, we
should see 45 rows inside IndexMetadata table
This commit is contained in:
Weiko
2024-07-09 14:51:24 +02:00
committed by GitHub
parent 37fb88c533
commit 6af1bcd55c
7 changed files with 77 additions and 14 deletions

View File

@ -0,0 +1,37 @@
import { MigrationInterface, QueryRunner } from 'typeorm';
export class AddDateColumnsToIndexMetadata1720524654925
implements MigrationInterface
{
name = 'AddDateColumnsToIndexMetadata1720524654925';
public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(
`ALTER TABLE "metadata"."indexMetadata" ADD "createdAt" TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now()`,
);
await queryRunner.query(
`ALTER TABLE "metadata"."indexMetadata" ADD "updatedAt" TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now()`,
);
await queryRunner.query(
`ALTER TABLE "metadata"."indexFieldMetadata" ADD "createdAt" TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now()`,
);
await queryRunner.query(
`ALTER TABLE "metadata"."indexFieldMetadata" ADD "updatedAt" TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now()`,
);
}
public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(
`ALTER TABLE "metadata"."indexFieldMetadata" DROP COLUMN "updatedAt"`,
);
await queryRunner.query(
`ALTER TABLE "metadata"."indexFieldMetadata" DROP COLUMN "createdAt"`,
);
await queryRunner.query(
`ALTER TABLE "metadata"."indexMetadata" DROP COLUMN "updatedAt"`,
);
await queryRunner.query(
`ALTER TABLE "metadata"."indexMetadata" DROP COLUMN "createdAt"`,
);
}
}

View File

@ -1,14 +1,16 @@
import {
Entity,
PrimaryGeneratedColumn,
Column,
CreateDateColumn,
Entity,
JoinColumn,
ManyToOne,
PrimaryGeneratedColumn,
Relation,
UpdateDateColumn,
} from 'typeorm';
import { IndexMetadataEntity } from 'src/engine/metadata-modules/index-metadata/index-metadata.entity';
import { FieldMetadataEntity } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity';
import { IndexMetadataEntity } from 'src/engine/metadata-modules/index-metadata/index-metadata.entity';
@Entity('indexFieldMetadata')
export class IndexFieldMetadataEntity {
@ -43,4 +45,10 @@ export class IndexFieldMetadataEntity {
@Column({ nullable: false })
order: number;
@CreateDateColumn({ type: 'timestamptz' })
createdAt: Date;
@UpdateDateColumn({ type: 'timestamptz' })
updatedAt: Date;
}

View File

@ -1,15 +1,17 @@
import {
Entity,
PrimaryGeneratedColumn,
Column,
CreateDateColumn,
Entity,
JoinColumn,
ManyToOne,
Relation,
OneToMany,
PrimaryGeneratedColumn,
Relation,
UpdateDateColumn,
} from 'typeorm';
import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
import { IndexFieldMetadataEntity } from 'src/engine/metadata-modules/index-field-metadata/index-field-metadata.entity';
import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
@Entity('indexMetadata')
export class IndexMetadataEntity {
@ -40,4 +42,10 @@ export class IndexMetadataEntity {
},
)
indexFieldMetadatas: Relation<IndexFieldMetadataEntity[]>;
@CreateDateColumn({ type: 'timestamptz' })
createdAt: Date;
@UpdateDateColumn({ type: 'timestamptz' })
updatedAt: Date;
}

View File

@ -1,5 +1,6 @@
import { generateDeterministicIndexName } from 'src/engine/metadata-modules/index-metadata/utils/generate-deterministic-index-name';
import { metadataArgsStorage } from 'src/engine/twenty-orm/storage/metadata-args.storage';
import { convertClassNameToObjectMetadataName } from 'src/engine/workspace-manager/workspace-sync-metadata/utils/convert-class-to-object-metadata-name.util';
export interface WorkspaceIndexOptions {
columns?: string[];
@ -16,11 +17,13 @@ export function WorkspaceIndex(
}
// TODO: handle composite field metadata types
// TODO: handle relation field metadata types
if (Array.isArray(columns) && columns.length > 0) {
metadataArgsStorage.addIndexes({
name: `IDX_${generateDeterministicIndexName(columns)}`,
name: `IDX_${generateDeterministicIndexName([
convertClassNameToObjectMetadataName(target.name),
...columns,
])}`,
columns,
target: target,
});
@ -29,7 +32,10 @@ export function WorkspaceIndex(
}
metadataArgsStorage.addIndexes({
name: `IDX_${generateDeterministicIndexName([propertyKey.toString()])}`,
name: `IDX_${generateDeterministicIndexName([
convertClassNameToObjectMetadataName(target.constructor.name),
propertyKey.toString(),
])}`,
columns: [propertyKey.toString()],
target: target.constructor,
});

View File

@ -1,3 +1,4 @@
import { WorkspaceIndex } from 'src/engine/twenty-orm/decorators/workspace-index.decorator';
import { metadataArgsStorage } from 'src/engine/twenty-orm/storage/metadata-args.storage';
export function WorkspaceJoinColumn(
@ -9,5 +10,8 @@ export function WorkspaceJoinColumn(
relationName: relationPropertyKey,
joinColumn: propertyKey.toString(),
});
// Register index for join column
WorkspaceIndex()(object, propertyKey);
};
}

View File

@ -1,12 +1,12 @@
import { Injectable } from '@nestjs/common';
import { FeatureFlagMap } from 'src/engine/core-modules/feature-flag/interfaces/feature-flag-map.interface';
import { WorkspaceSyncContext } from 'src/engine/workspace-manager/workspace-sync-metadata/interfaces/workspace-sync-context.interface';
import { PartialIndexMetadata } from 'src/engine/workspace-manager/workspace-sync-metadata/interfaces/partial-index-metadata.interface';
import { WorkspaceSyncContext } from 'src/engine/workspace-manager/workspace-sync-metadata/interfaces/workspace-sync-context.interface';
import { BaseWorkspaceEntity } from 'src/engine/twenty-orm/base.workspace-entity';
import { IndexMetadataEntity } from 'src/engine/metadata-modules/index-metadata/index-metadata.entity';
import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
import { BaseWorkspaceEntity } from 'src/engine/twenty-orm/base.workspace-entity';
import { metadataArgsStorage } from 'src/engine/twenty-orm/storage/metadata-args.storage';
@Injectable()
@ -31,7 +31,7 @@ export class StandardIndexFactory {
target: typeof BaseWorkspaceEntity,
context: WorkspaceSyncContext,
originalObjectMetadataMap: Record<string, ObjectMetadataEntity>,
workspaceFeatureFlagsMap: FeatureFlagMap,
_workspaceFeatureFlagsMap: FeatureFlagMap,
): Partial<IndexMetadataEntity>[] {
const workspaceEntity = metadataArgsStorage.filterEntities(target);

View File

@ -2,7 +2,7 @@ import { IndexMetadataEntity } from 'src/engine/metadata-modules/index-metadata/
export type PartialIndexMetadata = Omit<
IndexMetadataEntity,
'id' | 'objectMetadata' | 'indexFieldMetadatas'
'id' | 'objectMetadata' | 'indexFieldMetadatas' | 'createdAt' | 'updatedAt'
> & {
columns: string[];
};