Workspace schema migration runner v2 - Fix Enums and Create TsVector (#13955)
## Context - Adding ts-vector generatedType/asExpression as TS_VECTOR settings - Using those settings to setup properly tsVector searchVector column through the new migration runner - Fix enum creation/suppression Note: regarding the new tsVector, we should implement a command to update existing fields TODO: - TS_VECTOR search vector column update (note: should be properly updated whenever the object labelIdentifier is updated or a new TEXT field is added to the object to follow the current logic) - relation type fields and columns are not implemented yet - index migrations
This commit is contained in:
+18
-17
@@ -6,6 +6,7 @@ import { RelationType } from 'src/engine/metadata-modules/field-metadata/interfa
|
||||
import { getFlatFieldMetadataMock } from 'src/engine/metadata-modules/flat-field-metadata/__mocks__/get-flat-field-metadata.mock';
|
||||
import { getFlatObjectMetadataMock } from 'src/engine/metadata-modules/flat-object-metadata/__mocks__/get-flat-object-metadata.mock';
|
||||
import { type WorkspaceSchemaManagerService } from 'src/engine/twenty-orm/workspace-schema-manager/workspace-schema-manager.service';
|
||||
import { getWorkspaceSchemaName } from 'src/engine/workspace-datasource/utils/get-workspace-schema-name.util';
|
||||
import { WorkspaceSchemaFieldActionRunnerService } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/workspace-schema-migration-runner/workspace-schema-field-action-runner.service';
|
||||
|
||||
describe('WorkspaceSchemaFieldActionRunner', () => {
|
||||
@@ -14,6 +15,7 @@ describe('WorkspaceSchemaFieldActionRunner', () => {
|
||||
let mockQueryRunner: jest.Mocked<QueryRunner>;
|
||||
|
||||
const mockWorkspaceId = '20202020-1c25-4d02-bf25-6aeccf7ea419';
|
||||
const mockSchemaName = getWorkspaceSchemaName(mockWorkspaceId);
|
||||
const mockObjectMetadataId = '20202020-1c25-4d02-bf25-6aeccf7ea418';
|
||||
const mockFieldMetadataId = '20202020-1c25-4d02-bf25-6aeccf7ea417';
|
||||
|
||||
@@ -99,7 +101,7 @@ describe('WorkspaceSchemaFieldActionRunner', () => {
|
||||
mockSchemaManagerService.columnManager.dropColumns,
|
||||
).toHaveBeenCalledWith({
|
||||
queryRunner: mockQueryRunner,
|
||||
schemaName: 'workspace_1wgvd1injqtife6y4rvfbu3h5',
|
||||
schemaName: mockSchemaName,
|
||||
tableName: '_person',
|
||||
columnNames: [
|
||||
'homeAddressAddressStreet1',
|
||||
@@ -171,7 +173,7 @@ describe('WorkspaceSchemaFieldActionRunner', () => {
|
||||
mockSchemaManagerService.columnManager.dropColumns,
|
||||
).toHaveBeenCalledWith({
|
||||
queryRunner: mockQueryRunner,
|
||||
schemaName: 'workspace_1wgvd1injqtife6y4rvfbu3h5',
|
||||
schemaName: mockSchemaName,
|
||||
tableName: '_person',
|
||||
columnNames: ['status'],
|
||||
});
|
||||
@@ -181,7 +183,7 @@ describe('WorkspaceSchemaFieldActionRunner', () => {
|
||||
mockSchemaManagerService.enumManager.dropEnum,
|
||||
).toHaveBeenCalledWith({
|
||||
queryRunner: mockQueryRunner,
|
||||
schemaName: 'workspace_1wgvd1injqtife6y4rvfbu3h5',
|
||||
schemaName: mockSchemaName,
|
||||
enumName: '_person_status_enum',
|
||||
});
|
||||
});
|
||||
@@ -226,7 +228,7 @@ describe('WorkspaceSchemaFieldActionRunner', () => {
|
||||
mockSchemaManagerService.columnManager.dropColumns,
|
||||
).toHaveBeenCalledWith({
|
||||
queryRunner: mockQueryRunner,
|
||||
schemaName: 'workspace_1wgvd1injqtife6y4rvfbu3h5',
|
||||
schemaName: mockSchemaName,
|
||||
tableName: '_person',
|
||||
columnNames: ['companyId'],
|
||||
});
|
||||
@@ -298,7 +300,7 @@ describe('WorkspaceSchemaFieldActionRunner', () => {
|
||||
mockSchemaManagerService.enumManager.createEnum,
|
||||
).toHaveBeenCalledWith({
|
||||
queryRunner: mockQueryRunner,
|
||||
schemaName: 'workspace_1wgvd1injqtife6y4rvfbu3h5',
|
||||
schemaName: mockSchemaName,
|
||||
enumName: '_task_priority_enum',
|
||||
values: ['HIGH', 'LOW'],
|
||||
});
|
||||
@@ -308,17 +310,16 @@ describe('WorkspaceSchemaFieldActionRunner', () => {
|
||||
mockSchemaManagerService.columnManager.addColumns,
|
||||
).toHaveBeenCalledWith({
|
||||
queryRunner: mockQueryRunner,
|
||||
schemaName: 'workspace_1wgvd1injqtife6y4rvfbu3h5',
|
||||
schemaName: mockSchemaName,
|
||||
tableName: '_task',
|
||||
columnDefinitions: [
|
||||
{
|
||||
name: 'priority',
|
||||
type: '_task_priority_enum',
|
||||
type: `"${mockSchemaName}"."_task_priority_enum"`,
|
||||
isNullable: true,
|
||||
isArray: false,
|
||||
isUnique: false,
|
||||
default: null,
|
||||
enumValues: ['HIGH', 'LOW'],
|
||||
},
|
||||
],
|
||||
});
|
||||
@@ -367,7 +368,7 @@ describe('WorkspaceSchemaFieldActionRunner', () => {
|
||||
mockSchemaManagerService.columnManager.addColumns,
|
||||
).toHaveBeenCalledWith({
|
||||
queryRunner: mockQueryRunner,
|
||||
schemaName: 'workspace_1wgvd1injqtife6y4rvfbu3h5',
|
||||
schemaName: mockSchemaName,
|
||||
tableName: '_employee',
|
||||
columnDefinitions: [
|
||||
{
|
||||
@@ -452,7 +453,7 @@ describe('WorkspaceSchemaFieldActionRunner', () => {
|
||||
mockSchemaManagerService.columnManager.renameColumn,
|
||||
).toHaveBeenCalledWith({
|
||||
queryRunner: mockQueryRunner,
|
||||
schemaName: 'workspace_1wgvd1injqtife6y4rvfbu3h5',
|
||||
schemaName: mockSchemaName,
|
||||
tableName: '_company',
|
||||
oldColumnName: fromName,
|
||||
newColumnName: toName,
|
||||
@@ -550,16 +551,16 @@ describe('WorkspaceSchemaFieldActionRunner', () => {
|
||||
mockSchemaManagerService.enumManager.alterEnumValues,
|
||||
).toHaveBeenCalledWith({
|
||||
queryRunner: mockQueryRunner,
|
||||
schemaName: 'workspace_1wgvd1injqtife6y4rvfbu3h5',
|
||||
schemaName: mockSchemaName,
|
||||
tableName: '_person',
|
||||
enumValues: ['UPDATED_ACTIVE', 'UPDATED_INACTIVE'],
|
||||
columnDefinition: {
|
||||
name: 'status',
|
||||
type: '_person_status_enum',
|
||||
type: `"${mockSchemaName}"."_person_status_enum"`,
|
||||
isNullable: true,
|
||||
isArray: false,
|
||||
isUnique: false,
|
||||
default: null,
|
||||
enumValues: ['UPDATED_ACTIVE', 'UPDATED_INACTIVE'],
|
||||
},
|
||||
oldToNewEnumOptionMap: {
|
||||
ACTIVE: 'UPDATED_ACTIVE', // Keep original values
|
||||
@@ -622,7 +623,7 @@ describe('WorkspaceSchemaFieldActionRunner', () => {
|
||||
mockSchemaManagerService.columnManager.alterColumnDefault,
|
||||
).toHaveBeenCalledWith({
|
||||
queryRunner: mockQueryRunner,
|
||||
schemaName: 'workspace_1wgvd1injqtife6y4rvfbu3h5',
|
||||
schemaName: mockSchemaName,
|
||||
tableName: '_product',
|
||||
columnName: 'priceAmountMicros',
|
||||
defaultValue: '100000000',
|
||||
@@ -632,7 +633,7 @@ describe('WorkspaceSchemaFieldActionRunner', () => {
|
||||
mockSchemaManagerService.columnManager.alterColumnDefault,
|
||||
).toHaveBeenCalledWith({
|
||||
queryRunner: mockQueryRunner,
|
||||
schemaName: 'workspace_1wgvd1injqtife6y4rvfbu3h5',
|
||||
schemaName: mockSchemaName,
|
||||
tableName: '_product',
|
||||
columnName: 'priceCurrencyCode',
|
||||
defaultValue: 'EUR',
|
||||
@@ -678,7 +679,7 @@ describe('WorkspaceSchemaFieldActionRunner', () => {
|
||||
mockSchemaManagerService.columnManager.dropColumns,
|
||||
).toHaveBeenCalledWith({
|
||||
queryRunner: mockQueryRunner,
|
||||
schemaName: 'workspace_1wgvd1injqtife6y4rvfbu3h5',
|
||||
schemaName: mockSchemaName,
|
||||
tableName: '_person',
|
||||
columnNames: [], // Empty array - no columns to drop
|
||||
});
|
||||
@@ -720,7 +721,7 @@ describe('WorkspaceSchemaFieldActionRunner', () => {
|
||||
mockSchemaManagerService.enumManager.createEnum,
|
||||
).toHaveBeenCalledWith({
|
||||
queryRunner: mockQueryRunner,
|
||||
schemaName: 'workspace_1wgvd1injqtife6y4rvfbu3h5',
|
||||
schemaName: mockSchemaName,
|
||||
enumName: '_test_emptyStatus_enum',
|
||||
values: [], // Empty enum values array
|
||||
});
|
||||
|
||||
+28
-28
@@ -4,6 +4,7 @@ import { type QueryRunner } from 'typeorm';
|
||||
import { getFlatFieldMetadataMock } from 'src/engine/metadata-modules/flat-field-metadata/__mocks__/get-flat-field-metadata.mock';
|
||||
import { getFlatObjectMetadataMock } from 'src/engine/metadata-modules/flat-object-metadata/__mocks__/get-flat-object-metadata.mock';
|
||||
import { type WorkspaceSchemaManagerService } from 'src/engine/twenty-orm/workspace-schema-manager/workspace-schema-manager.service';
|
||||
import { getWorkspaceSchemaName } from 'src/engine/workspace-datasource/utils/get-workspace-schema-name.util';
|
||||
import { WorkspaceSchemaObjectActionRunnerService } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/workspace-schema-migration-runner/workspace-schema-object-action-runner.service';
|
||||
|
||||
describe('WorkspaceSchemaObjectActionRunner', () => {
|
||||
@@ -13,6 +14,7 @@ describe('WorkspaceSchemaObjectActionRunner', () => {
|
||||
|
||||
const mockWorkspaceId = '20202020-1c25-4d02-bf25-6aeccf7ea419';
|
||||
const mockObjectMetadataId = '20202020-1c25-4d02-bf25-6aeccf7ea418';
|
||||
const mockSchemaName = getWorkspaceSchemaName(mockWorkspaceId);
|
||||
|
||||
const createMockFlatObjectMetadataMaps = (
|
||||
objectMetadata: any,
|
||||
@@ -121,7 +123,7 @@ describe('WorkspaceSchemaObjectActionRunner', () => {
|
||||
mockSchemaManagerService.tableManager.dropTable,
|
||||
).toHaveBeenCalledWith({
|
||||
queryRunner: mockQueryRunner,
|
||||
schemaName: 'workspace_1wgvd1injqtife6y4rvfbu3h5',
|
||||
schemaName: mockSchemaName,
|
||||
tableName: '_task',
|
||||
});
|
||||
|
||||
@@ -133,14 +135,14 @@ describe('WorkspaceSchemaObjectActionRunner', () => {
|
||||
mockSchemaManagerService.enumManager.dropEnum,
|
||||
).toHaveBeenCalledWith({
|
||||
queryRunner: mockQueryRunner,
|
||||
schemaName: 'workspace_1wgvd1injqtife6y4rvfbu3h5',
|
||||
schemaName: mockSchemaName,
|
||||
enumName: '_task_status_enum',
|
||||
});
|
||||
expect(
|
||||
mockSchemaManagerService.enumManager.dropEnum,
|
||||
).toHaveBeenCalledWith({
|
||||
queryRunner: mockQueryRunner,
|
||||
schemaName: 'workspace_1wgvd1injqtife6y4rvfbu3h5',
|
||||
schemaName: mockSchemaName,
|
||||
enumName: '_task_priority_enum',
|
||||
});
|
||||
});
|
||||
@@ -202,7 +204,7 @@ describe('WorkspaceSchemaObjectActionRunner', () => {
|
||||
mockSchemaManagerService.tableManager.dropTable,
|
||||
).toHaveBeenCalledWith({
|
||||
queryRunner: mockQueryRunner,
|
||||
schemaName: 'workspace_1wgvd1injqtife6y4rvfbu3h5',
|
||||
schemaName: mockSchemaName,
|
||||
tableName: '_project',
|
||||
});
|
||||
|
||||
@@ -214,7 +216,7 @@ describe('WorkspaceSchemaObjectActionRunner', () => {
|
||||
mockSchemaManagerService.enumManager.dropEnum,
|
||||
).toHaveBeenCalledWith({
|
||||
queryRunner: mockQueryRunner,
|
||||
schemaName: 'workspace_1wgvd1injqtife6y4rvfbu3h5',
|
||||
schemaName: mockSchemaName,
|
||||
enumName: '_project_tags_enum',
|
||||
});
|
||||
});
|
||||
@@ -253,7 +255,7 @@ describe('WorkspaceSchemaObjectActionRunner', () => {
|
||||
mockSchemaManagerService.tableManager.dropTable,
|
||||
).toHaveBeenCalledWith({
|
||||
queryRunner: mockQueryRunner,
|
||||
schemaName: 'workspace_1wgvd1injqtife6y4rvfbu3h5',
|
||||
schemaName: mockSchemaName,
|
||||
tableName: '_simpleObject',
|
||||
});
|
||||
|
||||
@@ -350,7 +352,7 @@ describe('WorkspaceSchemaObjectActionRunner', () => {
|
||||
mockSchemaManagerService.tableManager.createTable,
|
||||
).toHaveBeenCalledWith({
|
||||
queryRunner: mockQueryRunner,
|
||||
schemaName: 'workspace_1wgvd1injqtife6y4rvfbu3h5',
|
||||
schemaName: mockSchemaName,
|
||||
tableName: '_article',
|
||||
columnDefinitions: [
|
||||
// TEXT field column
|
||||
@@ -361,17 +363,15 @@ describe('WorkspaceSchemaObjectActionRunner', () => {
|
||||
isArray: false,
|
||||
isUnique: false,
|
||||
default: null,
|
||||
enumValues: undefined,
|
||||
},
|
||||
// SELECT field column
|
||||
{
|
||||
name: 'status',
|
||||
type: '_article_status_enum',
|
||||
type: `"${mockSchemaName}"."_article_status_enum"`,
|
||||
isNullable: true,
|
||||
isArray: false,
|
||||
isUnique: false,
|
||||
default: null,
|
||||
enumValues: ['DRAFT', 'PUBLISHED'],
|
||||
},
|
||||
// CURRENCY field columns (2 composite columns)
|
||||
{
|
||||
@@ -397,7 +397,7 @@ describe('WorkspaceSchemaObjectActionRunner', () => {
|
||||
mockSchemaManagerService.enumManager.createEnum,
|
||||
).toHaveBeenCalledWith({
|
||||
queryRunner: mockQueryRunner,
|
||||
schemaName: 'workspace_1wgvd1injqtife6y4rvfbu3h5',
|
||||
schemaName: mockSchemaName,
|
||||
enumName: '_article_status_enum',
|
||||
values: ['DRAFT', 'PUBLISHED'],
|
||||
});
|
||||
@@ -449,13 +449,13 @@ describe('WorkspaceSchemaObjectActionRunner', () => {
|
||||
mockSchemaManagerService.tableManager.createTable,
|
||||
).toHaveBeenCalledWith({
|
||||
queryRunner: mockQueryRunner,
|
||||
schemaName: 'workspace_1wgvd1injqtife6y4rvfbu3h5',
|
||||
schemaName: mockSchemaName,
|
||||
tableName: '_company',
|
||||
columnDefinitions: [
|
||||
{
|
||||
name: 'headquartersAddressStreet1',
|
||||
type: 'text',
|
||||
isNullable: false,
|
||||
isNullable: true,
|
||||
isUnique: false,
|
||||
default: null,
|
||||
isArray: false,
|
||||
@@ -463,7 +463,7 @@ describe('WorkspaceSchemaObjectActionRunner', () => {
|
||||
{
|
||||
name: 'headquartersAddressStreet2',
|
||||
type: 'text',
|
||||
isNullable: false,
|
||||
isNullable: true,
|
||||
isUnique: false,
|
||||
default: null,
|
||||
isArray: false,
|
||||
@@ -471,7 +471,7 @@ describe('WorkspaceSchemaObjectActionRunner', () => {
|
||||
{
|
||||
name: 'headquartersAddressCity',
|
||||
type: 'text',
|
||||
isNullable: false,
|
||||
isNullable: true,
|
||||
isUnique: false,
|
||||
default: null,
|
||||
isArray: false,
|
||||
@@ -479,7 +479,7 @@ describe('WorkspaceSchemaObjectActionRunner', () => {
|
||||
{
|
||||
name: 'headquartersAddressPostcode',
|
||||
type: 'text',
|
||||
isNullable: false,
|
||||
isNullable: true,
|
||||
isUnique: false,
|
||||
default: null,
|
||||
isArray: false,
|
||||
@@ -487,7 +487,7 @@ describe('WorkspaceSchemaObjectActionRunner', () => {
|
||||
{
|
||||
name: 'headquartersAddressState',
|
||||
type: 'text',
|
||||
isNullable: false,
|
||||
isNullable: true,
|
||||
isUnique: false,
|
||||
default: null,
|
||||
isArray: false,
|
||||
@@ -495,7 +495,7 @@ describe('WorkspaceSchemaObjectActionRunner', () => {
|
||||
{
|
||||
name: 'headquartersAddressCountry',
|
||||
type: 'text',
|
||||
isNullable: false,
|
||||
isNullable: true,
|
||||
isUnique: false,
|
||||
default: null,
|
||||
isArray: false,
|
||||
@@ -503,7 +503,7 @@ describe('WorkspaceSchemaObjectActionRunner', () => {
|
||||
{
|
||||
name: 'headquartersAddressLat',
|
||||
type: 'numeric',
|
||||
isNullable: false,
|
||||
isNullable: true,
|
||||
isUnique: false,
|
||||
default: null,
|
||||
isArray: false,
|
||||
@@ -511,7 +511,7 @@ describe('WorkspaceSchemaObjectActionRunner', () => {
|
||||
{
|
||||
name: 'headquartersAddressLng',
|
||||
type: 'numeric',
|
||||
isNullable: false,
|
||||
isNullable: true,
|
||||
isUnique: false,
|
||||
default: null,
|
||||
isArray: false,
|
||||
@@ -555,7 +555,7 @@ describe('WorkspaceSchemaObjectActionRunner', () => {
|
||||
mockSchemaManagerService.tableManager.createTable,
|
||||
).toHaveBeenCalledWith({
|
||||
queryRunner: mockQueryRunner,
|
||||
schemaName: 'workspace_1wgvd1injqtife6y4rvfbu3h5',
|
||||
schemaName: mockSchemaName,
|
||||
tableName: '_emptyObject',
|
||||
columnDefinitions: [], // Empty columns array
|
||||
});
|
||||
@@ -629,7 +629,7 @@ describe('WorkspaceSchemaObjectActionRunner', () => {
|
||||
mockSchemaManagerService.tableManager.renameTable,
|
||||
).toHaveBeenCalledWith({
|
||||
queryRunner: mockQueryRunner,
|
||||
schemaName: 'workspace_1wgvd1injqtife6y4rvfbu3h5',
|
||||
schemaName: mockSchemaName,
|
||||
oldTableName: '_blogPost',
|
||||
newTableName: '_article',
|
||||
});
|
||||
@@ -642,7 +642,7 @@ describe('WorkspaceSchemaObjectActionRunner', () => {
|
||||
mockSchemaManagerService.enumManager.renameEnum,
|
||||
).toHaveBeenCalledWith({
|
||||
queryRunner: mockQueryRunner,
|
||||
schemaName: 'workspace_1wgvd1injqtife6y4rvfbu3h5',
|
||||
schemaName: mockSchemaName,
|
||||
oldEnumName: '_blogPost_category_enum',
|
||||
newEnumName: '_article_category_enum',
|
||||
});
|
||||
@@ -650,7 +650,7 @@ describe('WorkspaceSchemaObjectActionRunner', () => {
|
||||
mockSchemaManagerService.enumManager.renameEnum,
|
||||
).toHaveBeenCalledWith({
|
||||
queryRunner: mockQueryRunner,
|
||||
schemaName: 'workspace_1wgvd1injqtife6y4rvfbu3h5',
|
||||
schemaName: mockSchemaName,
|
||||
oldEnumName: '_blogPost_tags_enum',
|
||||
newEnumName: '_article_tags_enum',
|
||||
});
|
||||
@@ -751,7 +751,7 @@ describe('WorkspaceSchemaObjectActionRunner', () => {
|
||||
mockSchemaManagerService.tableManager.renameTable,
|
||||
).toHaveBeenCalledWith({
|
||||
queryRunner: mockQueryRunner,
|
||||
schemaName: 'workspace_1wgvd1injqtife6y4rvfbu3h5',
|
||||
schemaName: mockSchemaName,
|
||||
oldTableName: '_oldEntity',
|
||||
newTableName: '_newEntity',
|
||||
});
|
||||
@@ -764,7 +764,7 @@ describe('WorkspaceSchemaObjectActionRunner', () => {
|
||||
mockSchemaManagerService.enumManager.renameEnum,
|
||||
).toHaveBeenCalledWith({
|
||||
queryRunner: mockQueryRunner,
|
||||
schemaName: 'workspace_1wgvd1injqtife6y4rvfbu3h5',
|
||||
schemaName: mockSchemaName,
|
||||
oldEnumName: '_oldEntity_status_enum',
|
||||
newEnumName: '_newEntity_status_enum',
|
||||
});
|
||||
@@ -810,7 +810,7 @@ describe('WorkspaceSchemaObjectActionRunner', () => {
|
||||
mockSchemaManagerService.tableManager.createTable,
|
||||
).toHaveBeenCalledWith({
|
||||
queryRunner: mockQueryRunner,
|
||||
schemaName: 'workspace_1wgvd1injqtife6y4rvfbu3h5',
|
||||
schemaName: mockSchemaName,
|
||||
tableName: '_testObject',
|
||||
columnDefinitions: [],
|
||||
});
|
||||
@@ -859,7 +859,7 @@ describe('WorkspaceSchemaObjectActionRunner', () => {
|
||||
mockSchemaManagerService.tableManager.renameTable,
|
||||
).toHaveBeenCalledWith({
|
||||
queryRunner: mockQueryRunner,
|
||||
schemaName: 'workspace_1wgvd1injqtife6y4rvfbu3h5',
|
||||
schemaName: mockSchemaName,
|
||||
oldTableName: '_emptyFieldsObject',
|
||||
newTableName: '_renamedEmptyFieldsObject',
|
||||
});
|
||||
|
||||
+15
-18
@@ -2,6 +2,7 @@ import { FieldMetadataType } from 'twenty-shared/types';
|
||||
|
||||
import { getFlatFieldMetadataMock } from 'src/engine/metadata-modules/flat-field-metadata/__mocks__/get-flat-field-metadata.mock';
|
||||
import { getFlatObjectMetadataMock } from 'src/engine/metadata-modules/flat-object-metadata/__mocks__/get-flat-object-metadata.mock';
|
||||
import { getWorkspaceSchemaName } from 'src/engine/workspace-datasource/utils/get-workspace-schema-name.util';
|
||||
import { generateColumnDefinitions } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/workspace-schema-migration-runner/utils/generate-column-definitions.util';
|
||||
|
||||
describe('Generate Column Definitions', () => {
|
||||
@@ -12,6 +13,8 @@ describe('Generate Column Definitions', () => {
|
||||
nameSingular: 'person',
|
||||
});
|
||||
|
||||
const mockSchemaName = getWorkspaceSchemaName(mockObjectMetadata.workspaceId);
|
||||
|
||||
describe('Enum Field Schema Generation', () => {
|
||||
it('should generate deterministic enum names to prevent schema conflicts', () => {
|
||||
const enumField = getFlatFieldMetadataMock({
|
||||
@@ -38,7 +41,7 @@ describe('Generate Column Definitions', () => {
|
||||
});
|
||||
|
||||
const columns = generateColumnDefinitions({
|
||||
fieldMetadata: enumField,
|
||||
flatFieldMetadata: enumField,
|
||||
flatObjectMetadataWithoutFields: mockObjectMetadata,
|
||||
});
|
||||
|
||||
@@ -48,8 +51,7 @@ describe('Generate Column Definitions', () => {
|
||||
|
||||
expect(column).toEqual({
|
||||
name: 'status',
|
||||
type: '_person_status_enum',
|
||||
enumValues: ['ACTIVE', 'INACTIVE'],
|
||||
type: `"${mockSchemaName}"."_person_status_enum"`,
|
||||
isArray: false,
|
||||
isNullable: true,
|
||||
isUnique: false,
|
||||
@@ -82,7 +84,7 @@ describe('Generate Column Definitions', () => {
|
||||
});
|
||||
|
||||
const columns = generateColumnDefinitions({
|
||||
fieldMetadata: multiSelectField,
|
||||
flatFieldMetadata: multiSelectField,
|
||||
flatObjectMetadataWithoutFields: mockObjectMetadata,
|
||||
});
|
||||
|
||||
@@ -92,8 +94,7 @@ describe('Generate Column Definitions', () => {
|
||||
|
||||
expect(column).toEqual({
|
||||
name: 'tags',
|
||||
type: '_person_tags_enum',
|
||||
enumValues: ['URGENT', 'LOW_PRIORITY'],
|
||||
type: `"${mockSchemaName}"."_person_tags_enum"`,
|
||||
isArray: true,
|
||||
isNullable: true,
|
||||
isUnique: false,
|
||||
@@ -113,7 +114,7 @@ describe('Generate Column Definitions', () => {
|
||||
});
|
||||
|
||||
const columns = generateColumnDefinitions({
|
||||
fieldMetadata: relationField,
|
||||
flatFieldMetadata: relationField,
|
||||
flatObjectMetadataWithoutFields: mockObjectMetadata,
|
||||
});
|
||||
|
||||
@@ -134,7 +135,7 @@ describe('Generate Column Definitions', () => {
|
||||
});
|
||||
|
||||
const columns = generateColumnDefinitions({
|
||||
fieldMetadata: relationField,
|
||||
flatFieldMetadata: relationField,
|
||||
flatObjectMetadataWithoutFields: mockObjectMetadata,
|
||||
});
|
||||
|
||||
@@ -164,7 +165,7 @@ describe('Generate Column Definitions', () => {
|
||||
});
|
||||
|
||||
const columns = generateColumnDefinitions({
|
||||
fieldMetadata: addressField,
|
||||
flatFieldMetadata: addressField,
|
||||
flatObjectMetadataWithoutFields: mockObjectMetadata,
|
||||
});
|
||||
|
||||
@@ -204,7 +205,7 @@ describe('Generate Column Definitions', () => {
|
||||
});
|
||||
|
||||
const columns = generateColumnDefinitions({
|
||||
fieldMetadata: currencyField,
|
||||
flatFieldMetadata: currencyField,
|
||||
flatObjectMetadataWithoutFields: mockObjectMetadata,
|
||||
});
|
||||
|
||||
@@ -243,7 +244,7 @@ describe('Generate Column Definitions', () => {
|
||||
});
|
||||
|
||||
const columns = generateColumnDefinitions({
|
||||
fieldMetadata: textField,
|
||||
flatFieldMetadata: textField,
|
||||
flatObjectMetadataWithoutFields: mockObjectMetadata,
|
||||
});
|
||||
|
||||
@@ -254,7 +255,6 @@ describe('Generate Column Definitions', () => {
|
||||
isNullable: true,
|
||||
isUnique: false,
|
||||
default: null,
|
||||
enumValues: undefined,
|
||||
isArray: false,
|
||||
},
|
||||
]);
|
||||
@@ -270,7 +270,7 @@ describe('Generate Column Definitions', () => {
|
||||
});
|
||||
|
||||
const columns = generateColumnDefinitions({
|
||||
fieldMetadata: booleanField,
|
||||
flatFieldMetadata: booleanField,
|
||||
flatObjectMetadataWithoutFields: mockObjectMetadata,
|
||||
});
|
||||
|
||||
@@ -281,7 +281,6 @@ describe('Generate Column Definitions', () => {
|
||||
isNullable: true,
|
||||
isUnique: false,
|
||||
default: true,
|
||||
enumValues: undefined,
|
||||
isArray: false,
|
||||
},
|
||||
]);
|
||||
@@ -298,7 +297,7 @@ describe('Generate Column Definitions', () => {
|
||||
});
|
||||
|
||||
const columns = generateColumnDefinitions({
|
||||
fieldMetadata: textField,
|
||||
flatFieldMetadata: textField,
|
||||
flatObjectMetadataWithoutFields: mockObjectMetadata,
|
||||
});
|
||||
|
||||
@@ -309,7 +308,6 @@ describe('Generate Column Definitions', () => {
|
||||
isNullable: true,
|
||||
isUnique: false,
|
||||
default: null,
|
||||
enumValues: undefined,
|
||||
isArray: false,
|
||||
},
|
||||
]);
|
||||
@@ -324,7 +322,7 @@ describe('Generate Column Definitions', () => {
|
||||
});
|
||||
|
||||
const columns = generateColumnDefinitions({
|
||||
fieldMetadata: uuidField,
|
||||
flatFieldMetadata: uuidField,
|
||||
flatObjectMetadataWithoutFields: mockObjectMetadata,
|
||||
});
|
||||
|
||||
@@ -335,7 +333,6 @@ describe('Generate Column Definitions', () => {
|
||||
isNullable: true,
|
||||
isUnique: false,
|
||||
default: null,
|
||||
enumValues: undefined,
|
||||
isArray: false,
|
||||
},
|
||||
]);
|
||||
|
||||
+41
-44
@@ -21,7 +21,6 @@ import {
|
||||
WorkspaceSchemaMigrationException,
|
||||
WorkspaceSchemaMigrationExceptionCode,
|
||||
} from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/workspace-schema-migration-runner/exceptions/workspace-schema-migration.exception';
|
||||
import { getTsVectorColumnExpressionFromFields } from 'src/engine/workspace-manager/workspace-sync-metadata/utils/get-ts-vector-column-expression.util';
|
||||
|
||||
import { getWorkspaceSchemaContextForMigration } from './get-workspace-schema-context-for-migration.util';
|
||||
|
||||
@@ -34,7 +33,7 @@ export const generateCompositeColumnDefinition = ({
|
||||
parentFieldMetadata: FlatFieldMetadata<CompositeFieldMetadataType>;
|
||||
flatObjectMetadataWithoutFields: FlatObjectMetadataWithoutFields;
|
||||
}): WorkspaceSchemaColumnDefinition => {
|
||||
const { tableName } = getWorkspaceSchemaContextForMigration({
|
||||
const { tableName, schemaName } = getWorkspaceSchemaContextForMigration({
|
||||
workspaceId: flatObjectMetadataWithoutFields.workspaceId,
|
||||
flatObjectMetadataWithoutFields,
|
||||
});
|
||||
@@ -70,51 +69,47 @@ export const generateCompositeColumnDefinition = ({
|
||||
name: columnName,
|
||||
type:
|
||||
columnType === 'enum'
|
||||
? computePostgresEnumName({ tableName, columnName })
|
||||
? `"${schemaName}"."${computePostgresEnumName({ tableName, columnName })}"`
|
||||
: columnType,
|
||||
// Align composite column nullability with parent field nullability by default
|
||||
isNullable: parentFieldMetadata.isNullable ?? true,
|
||||
isNullable: parentFieldMetadata.isNullable || !compositeProperty.isRequired,
|
||||
isUnique: parentFieldMetadata.isUnique ?? false,
|
||||
default: unserializedDefaultValue,
|
||||
isArray: isArrayFlag,
|
||||
};
|
||||
|
||||
if (columnType === 'enum') {
|
||||
definition.enumValues = compositeProperty.options?.map(
|
||||
(option) => option.value,
|
||||
);
|
||||
}
|
||||
|
||||
return definition;
|
||||
};
|
||||
|
||||
const generateTsVectorColumnDefinition = (
|
||||
fieldMetadata: FlatFieldMetadata<FieldMetadataType.TS_VECTOR>,
|
||||
flatFieldMetadata: FlatFieldMetadata<FieldMetadataType.TS_VECTOR>,
|
||||
): WorkspaceSchemaColumnDefinition => {
|
||||
const columnName = computeColumnName(fieldMetadata.name);
|
||||
const columnName = computeColumnName(flatFieldMetadata.name);
|
||||
|
||||
return {
|
||||
name: columnName,
|
||||
type: fieldMetadataTypeToColumnType(fieldMetadata.type),
|
||||
type: fieldMetadataTypeToColumnType(flatFieldMetadata.type),
|
||||
isNullable: true,
|
||||
isArray: false,
|
||||
isUnique: false,
|
||||
default: null,
|
||||
generatedType: 'STORED',
|
||||
asExpression: getTsVectorColumnExpressionFromFields([]), // TODO: setup asExpression in flatFieldMetadata transpilation
|
||||
asExpression: flatFieldMetadata.settings?.asExpression ?? undefined,
|
||||
generatedType: flatFieldMetadata.settings?.generatedType ?? undefined,
|
||||
};
|
||||
};
|
||||
|
||||
const generateRelationColumnDefinition = (
|
||||
fieldMetadata: FlatFieldMetadata<
|
||||
flatFieldMetadata: FlatFieldMetadata<
|
||||
FieldMetadataType.RELATION | FieldMetadataType.MORPH_RELATION
|
||||
>,
|
||||
): WorkspaceSchemaColumnDefinition | null => {
|
||||
if (!fieldMetadata.settings || !fieldMetadata.settings.joinColumnName) {
|
||||
if (
|
||||
!flatFieldMetadata.settings ||
|
||||
!flatFieldMetadata.settings.joinColumnName
|
||||
) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const joinColumnName = fieldMetadata.settings.joinColumnName;
|
||||
const joinColumnName = flatFieldMetadata.settings.joinColumnName;
|
||||
|
||||
return {
|
||||
name: joinColumnName,
|
||||
@@ -127,78 +122,80 @@ const generateRelationColumnDefinition = (
|
||||
};
|
||||
|
||||
const generateStandardColumnDefinition = (
|
||||
fieldMetadata: FlatFieldMetadata,
|
||||
flatFieldMetadata: FlatFieldMetadata,
|
||||
tableName: string,
|
||||
schemaName: string,
|
||||
): WorkspaceSchemaColumnDefinition => {
|
||||
const columnName = computeColumnName(fieldMetadata.name);
|
||||
const columnName = computeColumnName(flatFieldMetadata.name);
|
||||
const serializedDefaultValue = serializeDefaultValue(
|
||||
fieldMetadata.defaultValue,
|
||||
flatFieldMetadata.defaultValue,
|
||||
);
|
||||
const columnType = fieldMetadataTypeToColumnType(fieldMetadata.type);
|
||||
const columnType = fieldMetadataTypeToColumnType(flatFieldMetadata.type);
|
||||
|
||||
return {
|
||||
name: columnName,
|
||||
type:
|
||||
columnType === 'enum'
|
||||
? computePostgresEnumName({ tableName, columnName })
|
||||
? `"${schemaName}"."${computePostgresEnumName({ tableName, columnName })}"`
|
||||
: columnType,
|
||||
isNullable: fieldMetadata.isNullable ?? true,
|
||||
isNullable: flatFieldMetadata.isNullable ?? true,
|
||||
isArray:
|
||||
fieldMetadata.type === FieldMetadataType.ARRAY ||
|
||||
fieldMetadata.type === FieldMetadataType.MULTI_SELECT,
|
||||
isUnique: fieldMetadata.isUnique ?? false,
|
||||
flatFieldMetadata.type === FieldMetadataType.ARRAY ||
|
||||
flatFieldMetadata.type === FieldMetadataType.MULTI_SELECT,
|
||||
isUnique: flatFieldMetadata.isUnique ?? false,
|
||||
default: serializedDefaultValue,
|
||||
enumValues:
|
||||
columnType === 'enum'
|
||||
? fieldMetadata.options?.map((option) => option.value)
|
||||
: undefined,
|
||||
};
|
||||
};
|
||||
|
||||
export const generateColumnDefinitions = ({
|
||||
fieldMetadata,
|
||||
flatFieldMetadata,
|
||||
flatObjectMetadataWithoutFields,
|
||||
}: {
|
||||
fieldMetadata: FlatFieldMetadata;
|
||||
flatFieldMetadata: FlatFieldMetadata;
|
||||
flatObjectMetadataWithoutFields: FlatObjectMetadataWithoutFields;
|
||||
}): WorkspaceSchemaColumnDefinition[] => {
|
||||
const { tableName } = getWorkspaceSchemaContextForMigration({
|
||||
const { tableName, schemaName } = getWorkspaceSchemaContextForMigration({
|
||||
workspaceId: flatObjectMetadataWithoutFields.workspaceId,
|
||||
flatObjectMetadataWithoutFields: flatObjectMetadataWithoutFields,
|
||||
});
|
||||
|
||||
if (isCompositeFlatFieldMetadata(fieldMetadata)) {
|
||||
const compositeType = getCompositeTypeOrThrow(fieldMetadata.type);
|
||||
if (isCompositeFlatFieldMetadata(flatFieldMetadata)) {
|
||||
const compositeType = getCompositeTypeOrThrow(flatFieldMetadata.type);
|
||||
|
||||
return compositeType.properties.map((property) =>
|
||||
generateCompositeColumnDefinition({
|
||||
compositeProperty: property,
|
||||
parentFieldMetadata: fieldMetadata,
|
||||
parentFieldMetadata: flatFieldMetadata,
|
||||
flatObjectMetadataWithoutFields: flatObjectMetadataWithoutFields,
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
if (
|
||||
isFlatFieldMetadataEntityOfType(fieldMetadata, FieldMetadataType.TS_VECTOR)
|
||||
isFlatFieldMetadataEntityOfType(
|
||||
flatFieldMetadata,
|
||||
FieldMetadataType.TS_VECTOR,
|
||||
)
|
||||
) {
|
||||
return [generateTsVectorColumnDefinition(fieldMetadata)];
|
||||
return [generateTsVectorColumnDefinition(flatFieldMetadata)];
|
||||
}
|
||||
|
||||
if (
|
||||
isFlatFieldMetadataEntityOfType(
|
||||
fieldMetadata,
|
||||
flatFieldMetadata,
|
||||
FieldMetadataType.RELATION,
|
||||
) ||
|
||||
isFlatFieldMetadataEntityOfType(
|
||||
fieldMetadata,
|
||||
flatFieldMetadata,
|
||||
FieldMetadataType.MORPH_RELATION,
|
||||
)
|
||||
) {
|
||||
const relationColumn = generateRelationColumnDefinition(fieldMetadata);
|
||||
const relationColumn = generateRelationColumnDefinition(flatFieldMetadata);
|
||||
|
||||
return relationColumn ? [relationColumn] : [];
|
||||
}
|
||||
|
||||
return [generateStandardColumnDefinition(fieldMetadata, tableName)];
|
||||
return [
|
||||
generateStandardColumnDefinition(flatFieldMetadata, tableName, schemaName),
|
||||
];
|
||||
};
|
||||
|
||||
+5
-8
@@ -1,7 +1,4 @@
|
||||
import {
|
||||
type EnumFieldMetadataType,
|
||||
type FieldMetadataType,
|
||||
} from 'twenty-shared/types';
|
||||
import { type FieldMetadataType } from 'twenty-shared/types';
|
||||
import { assertUnreachable } from 'twenty-shared/utils';
|
||||
import { type QueryRunner } from 'typeorm';
|
||||
|
||||
@@ -186,17 +183,17 @@ export const collectEnumOperationsForField = ({
|
||||
export const collectEnumOperationsForObject = ({
|
||||
tableName,
|
||||
operation,
|
||||
enumFlatFieldMetadatas,
|
||||
flatFieldMetadatas,
|
||||
options,
|
||||
}: {
|
||||
tableName: string;
|
||||
operation: EnumOperation;
|
||||
enumFlatFieldMetadatas: FlatFieldMetadata<EnumFieldMetadataType>[];
|
||||
flatFieldMetadatas: FlatFieldMetadata[];
|
||||
options?: { newTableName?: string; newFieldName?: string };
|
||||
}): EnumOperationSpec[] => {
|
||||
return enumFlatFieldMetadatas.flatMap((enumFlatFieldMetadata) =>
|
||||
return flatFieldMetadatas.flatMap((flatFieldMetadata) =>
|
||||
collectEnumOperationsForField({
|
||||
flatFieldMetadata: enumFlatFieldMetadata,
|
||||
flatFieldMetadata,
|
||||
tableName,
|
||||
operation,
|
||||
options,
|
||||
|
||||
+15
-17
@@ -1,6 +1,7 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
|
||||
import { FieldMetadataType, type FromTo } from 'twenty-shared/types';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
import { type QueryRunner } from 'typeorm';
|
||||
|
||||
import { type FieldMetadataDefaultValueForAnyType } from 'src/engine/metadata-modules/field-metadata/interfaces/field-metadata-default-value.interface';
|
||||
@@ -69,7 +70,7 @@ export class WorkspaceSchemaFieldActionRunnerService
|
||||
});
|
||||
|
||||
const columnDefinitions = generateColumnDefinitions({
|
||||
fieldMetadata,
|
||||
flatFieldMetadata: fieldMetadata,
|
||||
flatObjectMetadataWithoutFields: flatObjectMetadata,
|
||||
});
|
||||
const columnNamesToDrop = columnDefinitions.map((def) => def.name);
|
||||
@@ -126,7 +127,7 @@ export class WorkspaceSchemaFieldActionRunnerService
|
||||
});
|
||||
|
||||
const columnDefinitions = generateColumnDefinitions({
|
||||
fieldMetadata: flatFieldMetadata,
|
||||
flatFieldMetadata: flatFieldMetadata,
|
||||
flatObjectMetadataWithoutFields: flatObjectMetadata,
|
||||
});
|
||||
|
||||
@@ -333,32 +334,28 @@ export class WorkspaceSchemaFieldActionRunnerService
|
||||
) {
|
||||
const fromOptionsById = new Map(
|
||||
(update.from ?? [])
|
||||
.filter(
|
||||
(opt: FieldMetadataDefaultOption | FieldMetadataComplexOption) =>
|
||||
Boolean(opt.id),
|
||||
)
|
||||
.map((opt: FieldMetadataDefaultOption | FieldMetadataComplexOption) => [
|
||||
opt.id,
|
||||
opt,
|
||||
]),
|
||||
.filter((opt) => isDefined(opt.id))
|
||||
.map((opt) => [opt.id, opt]),
|
||||
);
|
||||
|
||||
const toOptionsById = new Map(
|
||||
(update.to ?? [])
|
||||
.filter((opt) => isDefined(opt.id))
|
||||
.map((opt) => [opt.id, opt]),
|
||||
);
|
||||
|
||||
const valueMapping: Record<string, string> = {};
|
||||
|
||||
for (const toOption of flatFieldMetadata.options ?? []) {
|
||||
if (!toOption.id) {
|
||||
continue;
|
||||
}
|
||||
|
||||
for (const toOption of toOptionsById.values()) {
|
||||
const fromOption = fromOptionsById.get(toOption.id);
|
||||
|
||||
if (fromOption && fromOption.value !== toOption.value) {
|
||||
if (fromOption) {
|
||||
valueMapping[fromOption.value] = toOption.value;
|
||||
}
|
||||
}
|
||||
|
||||
const enumColumnDefinitions = generateColumnDefinitions({
|
||||
fieldMetadata: flatFieldMetadata,
|
||||
flatFieldMetadata: flatFieldMetadata,
|
||||
flatObjectMetadataWithoutFields: flatObjectMetadata,
|
||||
});
|
||||
|
||||
@@ -368,6 +365,7 @@ export class WorkspaceSchemaFieldActionRunnerService
|
||||
schemaName,
|
||||
tableName,
|
||||
columnDefinition: enumColumnDefinition,
|
||||
enumValues: update.to?.map((opt) => opt.value) ?? [],
|
||||
oldToNewEnumOptionMap: valueMapping,
|
||||
});
|
||||
}
|
||||
|
||||
+16
-8
@@ -1,6 +1,7 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
|
||||
import { type FlatFieldMetadata } from 'src/engine/metadata-modules/flat-field-metadata/types/flat-field-metadata.type';
|
||||
import { isCompositeFlatFieldMetadata } from 'src/engine/metadata-modules/flat-field-metadata/utils/is-composite-flat-field-metadata.util';
|
||||
import { isEnumFlatFieldMetadata } from 'src/engine/metadata-modules/flat-field-metadata/utils/is-enum-flat-field-metadata.util';
|
||||
import { WorkspaceSchemaManagerService } from 'src/engine/twenty-orm/workspace-schema-manager/workspace-schema-manager.service';
|
||||
import { computeObjectTargetTable } from 'src/engine/utils/compute-object-target-table.util';
|
||||
@@ -55,7 +56,7 @@ export class WorkspaceSchemaObjectActionRunnerService
|
||||
.filter((field) => isEnumFlatFieldMetadata(field));
|
||||
|
||||
const enumOperations = collectEnumOperationsForObject({
|
||||
enumFlatFieldMetadatas,
|
||||
flatFieldMetadatas: enumFlatFieldMetadatas,
|
||||
tableName,
|
||||
operation: EnumOperation.DROP,
|
||||
});
|
||||
@@ -77,18 +78,21 @@ export class WorkspaceSchemaObjectActionRunnerService
|
||||
|
||||
const columnDefinitions = createFieldActions.flatMap((createFieldAction) =>
|
||||
generateColumnDefinitions({
|
||||
fieldMetadata: createFieldAction.flatFieldMetadata,
|
||||
flatFieldMetadata: createFieldAction.flatFieldMetadata,
|
||||
flatObjectMetadataWithoutFields: flatObjectMetadataWithoutFields,
|
||||
}),
|
||||
);
|
||||
|
||||
const enumFlatFieldMetadatas = createFieldActions
|
||||
const enumOrCompositeFlatFieldMetadatas = createFieldActions
|
||||
.map((createFieldAction) => createFieldAction.flatFieldMetadata)
|
||||
.filter((field): field is FlatFieldMetadata => field != null)
|
||||
.filter((field) => isEnumFlatFieldMetadata(field));
|
||||
.filter(
|
||||
(field) =>
|
||||
isEnumFlatFieldMetadata(field) || isCompositeFlatFieldMetadata(field),
|
||||
);
|
||||
|
||||
const enumOperations = collectEnumOperationsForObject({
|
||||
enumFlatFieldMetadatas,
|
||||
flatFieldMetadatas: enumOrCompositeFlatFieldMetadatas,
|
||||
tableName,
|
||||
operation: EnumOperation.CREATE,
|
||||
});
|
||||
@@ -143,14 +147,18 @@ export class WorkspaceSchemaObjectActionRunnerService
|
||||
newTableName,
|
||||
});
|
||||
|
||||
const enumFlatFieldMetadatas = Object.values(
|
||||
const enumOrCompositeFlatFieldMetadatas = Object.values(
|
||||
flatObjectMetadataWithFlatFieldMaps.fieldsById,
|
||||
)
|
||||
.filter((field): field is FlatFieldMetadata => field != null)
|
||||
.filter((field) => isEnumFlatFieldMetadata(field));
|
||||
.filter(
|
||||
(field) =>
|
||||
isEnumFlatFieldMetadata(field) ||
|
||||
isCompositeFlatFieldMetadata(field),
|
||||
);
|
||||
|
||||
const enumOperations = collectEnumOperationsForObject({
|
||||
enumFlatFieldMetadatas,
|
||||
flatFieldMetadatas: enumOrCompositeFlatFieldMetadatas,
|
||||
tableName: currentTableName,
|
||||
operation: EnumOperation.RENAME,
|
||||
options: {
|
||||
|
||||
Reference in New Issue
Block a user