Index v2 side effects (#14567)

# Introduction
Honestly this implem is a mess, discussing a potential side effect
handler with @weiko before the build and run that would handle each side
effect per entity and operation

Handling:
- [ ] unique index is generated when a field is updated with the
`isUnique`
- [x] an index is generated when a relation is created
- [x] search vector index creation on custom object creation
- [x] renaming a field metadata or an object should re-create all
related indexes which are composed by their namings
- [x] delete object should remove any related indexes
- [x] delete field should update related indexes ( if index ends up
empty it should be removed )
- [ ] on object renaming that contains morph fields -> triggers update
field -> trigger index recompute
- [x] on update name renaming should recompute all related indexes

## TODO
- [x] Integration testing
- [ ] Refactor the index maps cache to be storing a
`idsByObjectMetadataId`
- [x] Refactor deterministic name to use order sorting
- [x] Remove flat index from flat object

## What's next

Will handle morph indexes in a new dedicated PR for the moment will
stick to this
Same for the cache improvement and uniqueness
This commit is contained in:
Paul Rastoin
2025-09-23 10:57:11 +02:00
committed by GitHub
parent d0b9c7f076
commit fbd3286792
63 changed files with 2591 additions and 1323 deletions
@@ -0,0 +1,33 @@
import { createHash } from 'crypto';
import { type FlatFieldMetadata } from 'src/engine/metadata-modules/flat-field-metadata/types/flat-field-metadata.type';
import { type FlatObjectMetadata } from 'src/engine/metadata-modules/flat-object-metadata/types/flat-object-metadata.type';
import { computeTableName } from 'src/engine/utils/compute-table-name.util';
type GenerateDeterministicIndexNameArgs = {
flatObjectMetadata: Pick<FlatObjectMetadata, 'nameSingular' | 'isCustom'>;
isUnique?: boolean;
relatedFieldNames: Pick<FlatFieldMetadata, 'name'>[];
};
export const generateDeterministicIndexNameV2 = ({
relatedFieldNames,
flatObjectMetadata,
isUnique = false,
}: GenerateDeterministicIndexNameArgs): string => {
const hash = createHash('sha256');
const tableName = computeTableName(
flatObjectMetadata.nameSingular,
flatObjectMetadata.isCustom,
);
const columnsNames = relatedFieldNames.map(
(flatFieldMetadata) => flatFieldMetadata.name,
);
[tableName, ...columnsNames].forEach((column) => {
hash.update(column);
});
return `IDX_${isUnique ? 'UNIQUE_' : ''}${hash.digest('hex').slice(0, 27)}`;
};
@@ -0,0 +1,56 @@
import { isDefined } from 'twenty-shared/utils';
import {
FlatEntityMapsException,
FlatEntityMapsExceptionCode,
} from 'src/engine/core-modules/common/exceptions/flat-entity-maps.exception';
import { isMorphOrRelationFlatFieldMetadata } from 'src/engine/metadata-modules/flat-field-metadata/utils/is-morph-or-relation-flat-field-metadata.util';
import { type FlatIndexMetadata } from 'src/engine/metadata-modules/flat-index-metadata/types/flat-index-metadata.type';
import { type FlatObjectMetadata } from 'src/engine/metadata-modules/flat-object-metadata/types/flat-object-metadata.type';
import { generateDeterministicIndexNameV2 } from 'src/engine/metadata-modules/index-metadata/utils/generate-deterministic-index-name-v2';
type GenerateFlatIndexArgs = {
flatObjectMetadata: FlatObjectMetadata;
flatIndex: Omit<FlatIndexMetadata, 'name'>;
};
export const generateFlatIndexMetadataWithNameOrThrow = ({
flatObjectMetadata,
flatIndex,
}: GenerateFlatIndexArgs): FlatIndexMetadata => {
const orderedFlatFieldNames = flatIndex.flatIndexFieldMetadatas
.sort((a, b) => a.order - b.order)
.map((flatIndexField) => {
const relatedFlatFieldMetadata =
flatObjectMetadata.flatFieldMetadatas.find(
(flatFieldMetadata) =>
flatFieldMetadata.id === flatIndexField.fieldMetadataId,
);
if (!isDefined(relatedFlatFieldMetadata)) {
throw new FlatEntityMapsException(
'Could not find flat index field related field in cache',
FlatEntityMapsExceptionCode.ENTITY_NOT_FOUND,
);
}
const name = isMorphOrRelationFlatFieldMetadata(relatedFlatFieldMetadata)
? (relatedFlatFieldMetadata.settings.joinColumnName ??
relatedFlatFieldMetadata.name)
: relatedFlatFieldMetadata.name;
return {
name,
};
});
const name = generateDeterministicIndexNameV2({
flatObjectMetadata,
isUnique: flatIndex.isUnique,
relatedFieldNames: orderedFlatFieldNames,
});
return {
...flatIndex,
name,
};
};