feat: drop target column map (#4670)

This PR is dropping the column `targetColumnMap` of fieldMetadata
entities.
The goal of this column was to properly map field to their respecting
column in the table.
We decide to drop it and instead compute the column name on the fly when
we need it, as it's more easier to support.
Some parts of the code has been refactored to try making implementation
of composite type more easier to understand and maintain.

Fix #3760

---------

Co-authored-by: Charles Bochet <charles@twenty.com>
This commit is contained in:
Jérémy M
2024-04-08 16:00:28 +02:00
committed by GitHub
parent 84f8c14e52
commit 5019b5febc
72 changed files with 1432 additions and 1853 deletions

View File

@ -12,6 +12,7 @@ import {
import { serializeDefaultValue } from 'src/engine/metadata-modules/field-metadata/utils/serialize-default-value';
import { fieldMetadataTypeToColumnType } from 'src/engine/metadata-modules/workspace-migration/utils/field-metadata-type-to-column-type.util';
import { ColumnActionAbstractFactory } from 'src/engine/metadata-modules/workspace-migration/factories/column-action-abstract.factory';
import { computeColumnName } from 'src/engine/metadata-modules/field-metadata/utils/compute-column-name.util';
export type BasicFieldMetadataType =
| FieldMetadataType.UUID
@ -33,29 +34,32 @@ export class BasicColumnActionFactory extends ColumnActionAbstractFactory<BasicF
protected handleCreateAction(
fieldMetadata: FieldMetadataInterface<BasicFieldMetadataType>,
options?: WorkspaceColumnActionOptions,
): WorkspaceMigrationColumnCreate {
): WorkspaceMigrationColumnCreate[] {
const columnName = computeColumnName(fieldMetadata);
const defaultValue = fieldMetadata.defaultValue ?? options?.defaultValue;
const serializedDefaultValue = serializeDefaultValue(defaultValue);
return {
action: WorkspaceMigrationColumnActionType.CREATE,
columnName: fieldMetadata.targetColumnMap.value,
columnType: fieldMetadataTypeToColumnType(fieldMetadata.type),
isNullable: fieldMetadata.isNullable,
defaultValue: serializedDefaultValue,
};
return [
{
action: WorkspaceMigrationColumnActionType.CREATE,
columnName,
columnType: fieldMetadataTypeToColumnType(fieldMetadata.type),
isNullable: fieldMetadata.isNullable,
defaultValue: serializedDefaultValue,
},
];
}
protected handleAlterAction(
currentFieldMetadata: FieldMetadataInterface<BasicFieldMetadataType>,
alteredFieldMetadata: FieldMetadataInterface<BasicFieldMetadataType>,
options?: WorkspaceColumnActionOptions,
): WorkspaceMigrationColumnAlter {
): WorkspaceMigrationColumnAlter[] {
const currentColumnName = computeColumnName(currentFieldMetadata);
const alteredColumnName = computeColumnName(alteredFieldMetadata);
const defaultValue =
alteredFieldMetadata.defaultValue ?? options?.defaultValue;
const serializedDefaultValue = serializeDefaultValue(defaultValue);
const currentColumnName = currentFieldMetadata.targetColumnMap.value;
const alteredColumnName = alteredFieldMetadata.targetColumnMap.value;
if (!currentColumnName || !alteredColumnName) {
this.logger.error(
@ -66,20 +70,24 @@ export class BasicColumnActionFactory extends ColumnActionAbstractFactory<BasicF
);
}
return {
action: WorkspaceMigrationColumnActionType.ALTER,
currentColumnDefinition: {
columnName: currentColumnName,
columnType: fieldMetadataTypeToColumnType(currentFieldMetadata.type),
isNullable: currentFieldMetadata.isNullable,
defaultValue: serializeDefaultValue(currentFieldMetadata.defaultValue),
return [
{
action: WorkspaceMigrationColumnActionType.ALTER,
currentColumnDefinition: {
columnName: currentColumnName,
columnType: fieldMetadataTypeToColumnType(currentFieldMetadata.type),
isNullable: currentFieldMetadata.isNullable,
defaultValue: serializeDefaultValue(
currentFieldMetadata.defaultValue,
),
},
alteredColumnDefinition: {
columnName: alteredColumnName,
columnType: fieldMetadataTypeToColumnType(alteredFieldMetadata.type),
isNullable: alteredFieldMetadata.isNullable,
defaultValue: serializedDefaultValue,
},
},
alteredColumnDefinition: {
columnName: alteredColumnName,
columnType: fieldMetadataTypeToColumnType(alteredFieldMetadata.type),
isNullable: alteredFieldMetadata.isNullable,
defaultValue: serializedDefaultValue,
},
};
];
}
}

View File

@ -26,7 +26,7 @@ export class ColumnActionAbstractFactory<
currentFieldMetadata: FieldMetadataInterface<T> | undefined,
alteredFieldMetadata: FieldMetadataInterface<T>,
options?: WorkspaceColumnActionOptions,
): WorkspaceMigrationColumnAction {
): WorkspaceMigrationColumnAction[] {
switch (action) {
case WorkspaceMigrationColumnActionType.CREATE:
return this.handleCreateAction(alteredFieldMetadata, options);
@ -52,7 +52,7 @@ export class ColumnActionAbstractFactory<
protected handleCreateAction(
_fieldMetadata: FieldMetadataInterface<T>,
_options?: WorkspaceColumnActionOptions,
): WorkspaceMigrationColumnCreate {
): WorkspaceMigrationColumnCreate[] {
throw new Error('handleCreateAction method not implemented.');
}
@ -60,7 +60,7 @@ export class ColumnActionAbstractFactory<
_currentFieldMetadata: FieldMetadataInterface<T>,
_alteredFieldMetadata: FieldMetadataInterface<T>,
_options?: WorkspaceColumnActionOptions,
): WorkspaceMigrationColumnAlter {
): WorkspaceMigrationColumnAlter[] {
throw new Error('handleAlterAction method not implemented.');
}
}

View File

@ -0,0 +1,132 @@
import { Injectable, Logger } from '@nestjs/common';
import { FieldMetadataInterface } from 'src/engine/metadata-modules/field-metadata/interfaces/field-metadata.interface';
import { FieldMetadataType } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity';
import {
WorkspaceMigrationColumnActionType,
WorkspaceMigrationColumnAlter,
WorkspaceMigrationColumnCreate,
} from 'src/engine/metadata-modules/workspace-migration/workspace-migration.entity';
import { serializeDefaultValue } from 'src/engine/metadata-modules/field-metadata/utils/serialize-default-value';
import { fieldMetadataTypeToColumnType } from 'src/engine/metadata-modules/workspace-migration/utils/field-metadata-type-to-column-type.util';
import { ColumnActionAbstractFactory } from 'src/engine/metadata-modules/workspace-migration/factories/column-action-abstract.factory';
import { computeCompositeColumnName } from 'src/engine/metadata-modules/field-metadata/utils/compute-column-name.util';
import { compositeTypeDefintions } from 'src/engine/metadata-modules/field-metadata/composite-types';
export type CompositeFieldMetadataType =
| FieldMetadataType.ADDRESS
| FieldMetadataType.CURRENCY
| FieldMetadataType.FULL_NAME
| FieldMetadataType.LINK;
@Injectable()
export class CompositeColumnActionFactory extends ColumnActionAbstractFactory<CompositeFieldMetadataType> {
protected readonly logger = new Logger(CompositeColumnActionFactory.name);
protected handleCreateAction(
fieldMetadata: FieldMetadataInterface<CompositeFieldMetadataType>,
): WorkspaceMigrationColumnCreate[] {
const compositeType = compositeTypeDefintions.get(fieldMetadata.type);
if (!compositeType) {
this.logger.error(
`Composite type not found for field metadata type: ${fieldMetadata.type}`,
);
throw new Error(
`Composite type not found for field metadata type: ${fieldMetadata.type}`,
);
}
const columnActions: WorkspaceMigrationColumnCreate[] = [];
for (const property of compositeType.properties) {
const columnName = computeCompositeColumnName(fieldMetadata, property);
const defaultValue = fieldMetadata.defaultValue?.[property.name];
const serializedDefaultValue = serializeDefaultValue(defaultValue);
columnActions.push({
action: WorkspaceMigrationColumnActionType.CREATE,
columnName,
columnType: fieldMetadataTypeToColumnType(property.type),
isNullable: fieldMetadata.isNullable || !property.isRequired,
defaultValue: serializedDefaultValue,
});
}
return columnActions;
}
protected handleAlterAction(
currentFieldMetadata: FieldMetadataInterface<CompositeFieldMetadataType>,
alteredFieldMetadata: FieldMetadataInterface<CompositeFieldMetadataType>,
): WorkspaceMigrationColumnAlter[] {
const currentCompositeType = compositeTypeDefintions.get(
currentFieldMetadata.type,
);
const alteredCompositeType = compositeTypeDefintions.get(
alteredFieldMetadata.type,
);
if (!currentCompositeType || !alteredCompositeType) {
this.logger.error(
`Composite type not found for field metadata type: ${currentFieldMetadata.type} or ${alteredFieldMetadata.type}`,
);
throw new Error(
`Composite type not found for field metadata type: ${currentFieldMetadata.type} or ${alteredFieldMetadata.type}`,
);
}
const columnActions: WorkspaceMigrationColumnAlter[] = [];
for (const alteredProperty of alteredCompositeType.properties) {
// TODO: Based on the name for now, we can add a more robust check in the future
const currentProperty = currentCompositeType.properties.find(
(p) => p.name === alteredProperty.name,
);
if (!currentProperty) {
this.logger.error(
`Current property not found for altered property: ${alteredProperty.name}`,
);
throw new Error(
`Current property not found for altered property: ${alteredProperty.name}`,
);
}
const currentColumnName = computeCompositeColumnName(
currentFieldMetadata,
currentProperty,
);
const alteredColumnName = computeCompositeColumnName(
alteredFieldMetadata,
alteredProperty,
);
const defaultValue =
alteredFieldMetadata.defaultValue?.[alteredProperty.name];
const serializedDefaultValue = serializeDefaultValue(defaultValue);
columnActions.push({
action: WorkspaceMigrationColumnActionType.ALTER,
currentColumnDefinition: {
columnName: currentColumnName,
columnType: fieldMetadataTypeToColumnType(currentProperty.type),
isNullable:
currentFieldMetadata.isNullable || !currentProperty.isRequired,
defaultValue: serializeDefaultValue(
currentFieldMetadata.defaultValue?.[currentProperty.name],
),
},
alteredColumnDefinition: {
columnName: alteredColumnName,
columnType: fieldMetadataTypeToColumnType(alteredProperty.type),
isNullable:
alteredFieldMetadata.isNullable || !alteredProperty.isRequired,
defaultValue: serializedDefaultValue,
},
});
}
return columnActions;
}
}

View File

@ -12,6 +12,7 @@ import {
import { serializeDefaultValue } from 'src/engine/metadata-modules/field-metadata/utils/serialize-default-value';
import { fieldMetadataTypeToColumnType } from 'src/engine/metadata-modules/workspace-migration/utils/field-metadata-type-to-column-type.util';
import { ColumnActionAbstractFactory } from 'src/engine/metadata-modules/workspace-migration/factories/column-action-abstract.factory';
import { computeColumnName } from 'src/engine/metadata-modules/field-metadata/utils/compute-column-name.util';
export type EnumFieldMetadataType =
| FieldMetadataType.RATING
@ -25,29 +26,34 @@ export class EnumColumnActionFactory extends ColumnActionAbstractFactory<EnumFie
protected handleCreateAction(
fieldMetadata: FieldMetadataInterface<EnumFieldMetadataType>,
options: WorkspaceColumnActionOptions,
): WorkspaceMigrationColumnCreate {
): WorkspaceMigrationColumnCreate[] {
const columnName = computeColumnName(fieldMetadata);
const defaultValue = fieldMetadata.defaultValue ?? options?.defaultValue;
const serializedDefaultValue = serializeDefaultValue(defaultValue);
const enumOptions = fieldMetadata.options
? [...fieldMetadata.options.map((option) => option.value)]
: undefined;
return {
action: WorkspaceMigrationColumnActionType.CREATE,
columnName: fieldMetadata.targetColumnMap.value,
columnType: fieldMetadataTypeToColumnType(fieldMetadata.type),
enum: enumOptions,
isArray: fieldMetadata.type === FieldMetadataType.MULTI_SELECT,
isNullable: fieldMetadata.isNullable,
defaultValue: serializedDefaultValue,
};
return [
{
action: WorkspaceMigrationColumnActionType.CREATE,
columnName,
columnType: fieldMetadataTypeToColumnType(fieldMetadata.type),
enum: enumOptions,
isArray: fieldMetadata.type === FieldMetadataType.MULTI_SELECT,
isNullable: fieldMetadata.isNullable,
defaultValue: serializedDefaultValue,
},
];
}
protected handleAlterAction(
currentFieldMetadata: FieldMetadataInterface<EnumFieldMetadataType>,
alteredFieldMetadata: FieldMetadataInterface<EnumFieldMetadataType>,
options: WorkspaceColumnActionOptions,
): WorkspaceMigrationColumnAlter {
): WorkspaceMigrationColumnAlter[] {
const currentColumnName = computeColumnName(currentFieldMetadata);
const alteredColumnName = computeColumnName(alteredFieldMetadata);
const defaultValue =
alteredFieldMetadata.defaultValue ?? options?.defaultValue;
const serializedDefaultValue = serializeDefaultValue(defaultValue);
@ -71,8 +77,6 @@ export class EnumColumnActionFactory extends ColumnActionAbstractFactory<EnumFie
}),
]
: undefined;
const currentColumnName = currentFieldMetadata.targetColumnMap.value;
const alteredColumnName = alteredFieldMetadata.targetColumnMap.value;
if (!currentColumnName || !alteredColumnName) {
this.logger.error(
@ -83,26 +87,30 @@ export class EnumColumnActionFactory extends ColumnActionAbstractFactory<EnumFie
);
}
return {
action: WorkspaceMigrationColumnActionType.ALTER,
currentColumnDefinition: {
columnName: currentColumnName,
columnType: fieldMetadataTypeToColumnType(currentFieldMetadata.type),
enum: currentFieldMetadata.options
? [...currentFieldMetadata.options.map((option) => option.value)]
: undefined,
isArray: currentFieldMetadata.type === FieldMetadataType.MULTI_SELECT,
isNullable: currentFieldMetadata.isNullable,
defaultValue: serializeDefaultValue(currentFieldMetadata.defaultValue),
return [
{
action: WorkspaceMigrationColumnActionType.ALTER,
currentColumnDefinition: {
columnName: currentColumnName,
columnType: fieldMetadataTypeToColumnType(currentFieldMetadata.type),
enum: currentFieldMetadata.options
? [...currentFieldMetadata.options.map((option) => option.value)]
: undefined,
isArray: currentFieldMetadata.type === FieldMetadataType.MULTI_SELECT,
isNullable: currentFieldMetadata.isNullable,
defaultValue: serializeDefaultValue(
currentFieldMetadata.defaultValue,
),
},
alteredColumnDefinition: {
columnName: alteredColumnName,
columnType: fieldMetadataTypeToColumnType(alteredFieldMetadata.type),
enum: enumOptions,
isArray: alteredFieldMetadata.type === FieldMetadataType.MULTI_SELECT,
isNullable: alteredFieldMetadata.isNullable,
defaultValue: serializedDefaultValue,
},
},
alteredColumnDefinition: {
columnName: alteredColumnName,
columnType: fieldMetadataTypeToColumnType(alteredFieldMetadata.type),
enum: enumOptions,
isArray: alteredFieldMetadata.type === FieldMetadataType.MULTI_SELECT,
isNullable: alteredFieldMetadata.isNullable,
defaultValue: serializedDefaultValue,
},
};
];
}
}

View File

@ -1,7 +1,9 @@
import { BasicColumnActionFactory } from 'src/engine/metadata-modules/workspace-migration/factories/basic-column-action.factory';
import { CompositeColumnActionFactory } from 'src/engine/metadata-modules/workspace-migration/factories/composite-column-action.factory';
import { EnumColumnActionFactory } from 'src/engine/metadata-modules/workspace-migration/factories/enum-column-action.factory';
export const workspaceColumnActionFactories = [
BasicColumnActionFactory,
EnumColumnActionFactory,
CompositeColumnActionFactory,
];

View File

@ -17,5 +17,5 @@ export interface WorkspaceColumnActionFactory<
currentFieldMetadata: FieldMetadataInterface<T> | undefined,
alteredFieldMetadata: FieldMetadataInterface<T>,
options?: WorkspaceColumnActionOptions,
): WorkspaceMigrationColumnAction;
): WorkspaceMigrationColumnAction[];
}

View File

@ -5,14 +5,13 @@ import { FieldMetadataInterface } from 'src/engine/metadata-modules/field-metada
import { WorkspaceColumnActionOptions } from 'src/engine/metadata-modules/workspace-migration/interfaces/workspace-column-action-options.interface';
import { FieldMetadataType } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity';
import { BasicColumnActionFactory } from 'src/engine/metadata-modules/workspace-migration/factories/basic-column-action.factory';
import { EnumColumnActionFactory } from 'src/engine/metadata-modules/workspace-migration/factories/enum-column-action.factory';
import {
WorkspaceMigrationColumnAction,
WorkspaceMigrationColumnActionType,
} from 'src/engine/metadata-modules/workspace-migration/workspace-migration.entity';
import { isCompositeFieldMetadataType } from 'src/engine/metadata-modules/field-metadata/utils/is-composite-field-metadata-type.util';
import { compositeDefinitions } from 'src/engine/metadata-modules/field-metadata/composite-types';
import { BasicColumnActionFactory } from 'src/engine/metadata-modules/workspace-migration/factories/basic-column-action.factory';
import { EnumColumnActionFactory } from 'src/engine/metadata-modules/workspace-migration/factories/enum-column-action.factory';
import { CompositeColumnActionFactory } from 'src/engine/metadata-modules/workspace-migration/factories/composite-column-action.factory';
@Injectable()
export class WorkspaceMigrationFactory {
@ -28,6 +27,7 @@ export class WorkspaceMigrationFactory {
constructor(
private readonly basicColumnActionFactory: BasicColumnActionFactory,
private readonly enumColumnActionFactory: EnumColumnActionFactory,
private readonly compositeColumnActionFactory: CompositeColumnActionFactory,
) {
this.factoriesMap = new Map<
FieldMetadataType,
@ -80,6 +80,19 @@ export class WorkspaceMigrationFactory {
FieldMetadataType.MULTI_SELECT,
{ factory: this.enumColumnActionFactory },
],
[FieldMetadataType.LINK, { factory: this.compositeColumnActionFactory }],
[
FieldMetadataType.CURRENCY,
{ factory: this.compositeColumnActionFactory },
],
[
FieldMetadataType.ADDRESS,
{ factory: this.compositeColumnActionFactory },
],
[
FieldMetadataType.FULL_NAME,
{ factory: this.compositeColumnActionFactory },
],
]);
}
@ -119,41 +132,13 @@ export class WorkspaceMigrationFactory {
throw new Error(`No field metadata provided for action ${action}`);
}
// If it's a composite field type, we need to create a column action for each of the fields
if (isCompositeFieldMetadataType(alteredFieldMetadata.type)) {
const fieldMetadataSplitterFunction = compositeDefinitions.get(
alteredFieldMetadata.type,
);
if (!fieldMetadataSplitterFunction) {
this.logger.error(
`No composite definition found for type ${alteredFieldMetadata.type}`,
{
alteredFieldMetadata,
},
);
throw new Error(
`No composite definition found for type ${alteredFieldMetadata.type}`,
);
}
const fieldMetadataCollection =
fieldMetadataSplitterFunction(alteredFieldMetadata);
return fieldMetadataCollection.map((fieldMetadata) =>
this.createColumnAction(action, fieldMetadata, fieldMetadata),
);
}
// Otherwise, we create a single column action
const columnAction = this.createColumnAction(
const columnActions = this.createColumnAction(
action,
currentFieldMetadata,
alteredFieldMetadata,
);
return [columnAction];
return columnActions;
}
private createColumnAction(
@ -162,7 +147,7 @@ export class WorkspaceMigrationFactory {
| WorkspaceMigrationColumnActionType.ALTER,
currentFieldMetadata: FieldMetadataInterface | undefined,
alteredFieldMetadata: FieldMetadataInterface,
): WorkspaceMigrationColumnAction {
): WorkspaceMigrationColumnAction[] {
const { factory, options } =
this.factoriesMap.get(alteredFieldMetadata.type) ?? {};