Skip to content

Commit

Permalink
Fix custom index creation missing indexFieldMetadatas (twentyhq#7832)
Browse files Browse the repository at this point in the history
## Context
Regression on custom index creation where indexFieldMetadatas were not
saved properly in the DB. This is because we recently changed save() to
upsert() in the indexMetadataService and upsert does not handle nesting
insert properly.
I'm suggesting another fix where we separate indexMetadata creation and
index migration creation in 2 different functions. Since the goal was to
be able to recreate the index after being deleted when we changed the
tsvector expression and indexMetadata was actually not deleted, we
didn't need to recreate that part (hence the upsert) and only needed to
run a migration to create the actual index in the workspace schema.
I've updated the different services and now only call
createIndexMigration when we update a search vector expression.

Note: this is also fixing the sync-metadata command when running on a
workspace with a custom object (including the seeded workspace which has
the 'rocket' custom object), failing due to the missing 'searchVector'
indexFieldMetadata
  • Loading branch information
Weiko authored Oct 18, 2024
1 parent 17b934e commit d4457d7
Show file tree
Hide file tree
Showing 3 changed files with 62 additions and 28 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';

import { isDefined } from 'class-validator';
import { InsertResult, Repository } from 'typeorm';
import { Repository } from 'typeorm';

import { FieldMetadataEntity } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity';
import {
Expand All @@ -28,7 +28,7 @@ export class IndexMetadataService {
private readonly workspaceMigrationService: WorkspaceMigrationService,
) {}

async createIndex(
async createIndexMetadata(
workspaceId: string,
objectMetadata: ObjectMetadataEntity,
fieldMetadataToIndex: Partial<FieldMetadataEntity>[],
Expand All @@ -45,42 +45,76 @@ export class IndexMetadataService {

const indexName = `IDX_${generateDeterministicIndexName([tableName, ...columnNames])}`;

let result: InsertResult;
let result: IndexMetadataEntity;

try {
result = await this.indexMetadataRepository.upsert(
{
name: indexName,
indexFieldMetadatas: fieldMetadataToIndex.map(
(fieldMetadata, index) => {
return {
fieldMetadataId: fieldMetadata.id,
order: index,
};
},
),
workspaceId,
objectMetadataId: objectMetadata.id,
...(isDefined(indexType) ? { indexType: indexType } : {}),
isCustom: isCustom,
},
{
conflictPaths: ['workspaceId', 'name', 'objectMetadataId'],
skipUpdateIfNoValuesChanged: true,
},
const existingIndex = await this.indexMetadataRepository.findOne({
where: {
name: indexName,
workspaceId,
objectMetadataId: objectMetadata.id,
},
});

if (existingIndex) {
throw new Error(
`Index ${indexName} on object metadata ${objectMetadata.nameSingular} already exists`,
);
}

try {
result = await this.indexMetadataRepository.save({
name: indexName,
indexFieldMetadatas: fieldMetadataToIndex.map(
(fieldMetadata, index) => ({
fieldMetadataId: fieldMetadata.id,
order: index,
}),
),
workspaceId,
objectMetadataId: objectMetadata.id,
...(isDefined(indexType) ? { indexType } : {}),
isCustom,
});
} catch (error) {
throw new Error(
`Failed to create index ${indexName} on object metadata ${objectMetadata.nameSingular}`,
);
}

if (!result.identifiers.length) {
if (!result) {
throw new Error(
`Failed to return saved index ${indexName} on object metadata ${objectMetadata.nameSingular}`,
);
}

await this.createIndexCreationMigration(
workspaceId,
objectMetadata,
fieldMetadataToIndex,
isUnique,
isCustom,
indexType,
indexWhereClause,
);
}

async createIndexCreationMigration(
workspaceId: string,
objectMetadata: ObjectMetadataEntity,
fieldMetadataToIndex: Partial<FieldMetadataEntity>[],
isUnique: boolean,
isCustom: boolean,
indexType?: IndexType,
indexWhereClause?: string,
) {
const tableName = computeObjectTargetTable(objectMetadata);

const columnNames: string[] = fieldMetadataToIndex.map(
(fieldMetadata) => fieldMetadata.name as string,
);

const indexName = `IDX_${generateDeterministicIndexName([tableName, ...columnNames])}`;

const migration = {
name: tableName,
action: WorkspaceMigrationTableActionType.ALTER_INDEXES,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -149,7 +149,7 @@ export class RelationMetadataService extends TypeOrmQueryService<RelationMetadat
);
}

await this.indexMetadataService.createIndex(
await this.indexMetadataService.createIndexMetadata(
relationMetadataInput.workspaceId,
toObjectMetadata,
[foreignKeyFieldMetadata, deletedFieldMetadata],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,7 @@ export class SearchService {
],
);

await this.indexMetadataService.createIndex(
await this.indexMetadataService.createIndexMetadata(
objectMetadataInput.workspaceId,
createdObjectMetadata,
[searchVectorFieldMetadata],
Expand Down Expand Up @@ -157,7 +157,7 @@ export class SearchService {
);

// index needs to be recreated as typeorm deletes then recreates searchVector column at alter
await this.indexMetadataService.createIndex(
await this.indexMetadataService.createIndexCreationMigration(
workspaceId,
objectMetadata,
[existingSearchVectorFieldMetadata],
Expand Down

0 comments on commit d4457d7

Please sign in to comment.