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:
+6
@@ -48,6 +48,11 @@ export type FieldMetadataAddressSettings = {
|
||||
subFields?: AllowedAddressSubField[];
|
||||
};
|
||||
|
||||
export type FieldMetadataTsVectorSettings = {
|
||||
asExpression?: string;
|
||||
generatedType?: 'STORED' | 'VIRTUAL';
|
||||
};
|
||||
|
||||
type FieldMetadataSettingsMapping = {
|
||||
[FieldMetadataType.NUMBER]: FieldMetadataNumberSettings | null;
|
||||
[FieldMetadataType.DATE]: FieldMetadataDateSettings | null;
|
||||
@@ -56,6 +61,7 @@ type FieldMetadataSettingsMapping = {
|
||||
[FieldMetadataType.RELATION]: FieldMetadataRelationSettings;
|
||||
[FieldMetadataType.ADDRESS]: FieldMetadataAddressSettings | null;
|
||||
[FieldMetadataType.MORPH_RELATION]: FieldMetadataRelationSettings | null; // TODO Should not be null
|
||||
[FieldMetadataType.TS_VECTOR]: FieldMetadataTsVectorSettings | null;
|
||||
};
|
||||
|
||||
export type AllFieldMetadataSettings =
|
||||
|
||||
+10
-2
@@ -125,6 +125,15 @@ export const fromCreateFieldInputToFlatFieldMetadatasToCreate = async ({
|
||||
],
|
||||
};
|
||||
}
|
||||
case FieldMetadataType.TS_VECTOR: {
|
||||
return {
|
||||
status: 'fail',
|
||||
error: new FieldMetadataException(
|
||||
'TS Vector is not supported for field creation',
|
||||
FieldMetadataExceptionCode.INVALID_FIELD_INPUT,
|
||||
),
|
||||
};
|
||||
}
|
||||
case FieldMetadataType.UUID:
|
||||
case FieldMetadataType.TEXT:
|
||||
case FieldMetadataType.PHONES:
|
||||
@@ -143,8 +152,7 @@ export const fromCreateFieldInputToFlatFieldMetadatasToCreate = async ({
|
||||
case FieldMetadataType.RICH_TEXT:
|
||||
case FieldMetadataType.RICH_TEXT_V2:
|
||||
case FieldMetadataType.ACTOR:
|
||||
case FieldMetadataType.ARRAY:
|
||||
case FieldMetadataType.TS_VECTOR: {
|
||||
case FieldMetadataType.ARRAY: {
|
||||
return {
|
||||
status: 'success',
|
||||
result: [
|
||||
|
||||
+1
@@ -10,4 +10,5 @@ export const FLAT_OBJECT_METADATA_PROPERTIES_TO_COMPARE = [
|
||||
'namePlural',
|
||||
'nameSingular',
|
||||
'standardOverrides', // Only if standard
|
||||
'labelIdentifierFieldMetadataId',
|
||||
] as const satisfies (keyof FlatObjectMetadata)[];
|
||||
|
||||
+35
@@ -7,6 +7,7 @@ import {
|
||||
BASE_OBJECT_STANDARD_FIELD_IDS,
|
||||
CUSTOM_OBJECT_STANDARD_FIELD_IDS,
|
||||
} from 'src/engine/workspace-manager/workspace-sync-metadata/constants/standard-field-ids';
|
||||
import { getTsVectorColumnExpressionFromFields } from 'src/engine/workspace-manager/workspace-sync-metadata/utils/get-ts-vector-column-expression.util';
|
||||
|
||||
export const buildDefaultFieldsForCustomObject = (
|
||||
workspaceId: string,
|
||||
@@ -336,6 +337,39 @@ export const buildDefaultFlatFieldMetadataForCustomObject = ({
|
||||
settings: null,
|
||||
};
|
||||
|
||||
const searchVectorField: FlatFieldMetadata<FieldMetadataType.TS_VECTOR> = {
|
||||
type: FieldMetadataType.TS_VECTOR,
|
||||
id: v4(),
|
||||
isLabelSyncedWithName: false,
|
||||
isUnique: false,
|
||||
objectMetadataId,
|
||||
uniqueIdentifier: CUSTOM_OBJECT_STANDARD_FIELD_IDS.searchVector,
|
||||
workspaceId,
|
||||
standardId: CUSTOM_OBJECT_STANDARD_FIELD_IDS.searchVector,
|
||||
name: 'searchVector',
|
||||
label: 'Search vector',
|
||||
icon: 'IconSearch',
|
||||
description: 'Search vector',
|
||||
isNullable: true,
|
||||
isActive: true,
|
||||
isCustom: false,
|
||||
isSystem: true,
|
||||
defaultValue: null,
|
||||
|
||||
createdAt,
|
||||
updatedAt: createdAt,
|
||||
flatRelationTargetFieldMetadata: null,
|
||||
flatRelationTargetObjectMetadata: null,
|
||||
options: null,
|
||||
standardOverrides: null,
|
||||
relationTargetFieldMetadataId: null,
|
||||
relationTargetObjectMetadataId: null,
|
||||
settings: {
|
||||
asExpression: getTsVectorColumnExpressionFromFields([nameField]),
|
||||
generatedType: 'STORED',
|
||||
},
|
||||
};
|
||||
|
||||
return {
|
||||
idField,
|
||||
nameField,
|
||||
@@ -344,5 +378,6 @@ export const buildDefaultFlatFieldMetadataForCustomObject = ({
|
||||
deletedAtField,
|
||||
createdByField,
|
||||
positionField,
|
||||
searchVectorField,
|
||||
} as const;
|
||||
};
|
||||
|
||||
+36
-21
@@ -137,13 +137,15 @@ export class WorkspaceSchemaEnumManagerService {
|
||||
schemaName,
|
||||
tableName,
|
||||
columnDefinition,
|
||||
enumValues,
|
||||
oldToNewEnumOptionMap,
|
||||
}: {
|
||||
queryRunner: QueryRunner;
|
||||
schemaName: string;
|
||||
tableName: string;
|
||||
columnDefinition: WorkspaceSchemaColumnDefinition;
|
||||
oldToNewEnumOptionMap?: Record<string, string>;
|
||||
enumValues: string[];
|
||||
oldToNewEnumOptionMap: Record<string, string>;
|
||||
}): Promise<void> {
|
||||
const isTransactionAlreadyActive = queryRunner.isTransactionActive;
|
||||
|
||||
@@ -151,10 +153,7 @@ export class WorkspaceSchemaEnumManagerService {
|
||||
await queryRunner.startTransaction();
|
||||
}
|
||||
|
||||
if (
|
||||
!columnDefinition.enumValues ||
|
||||
columnDefinition.enumValues.length === 0
|
||||
) {
|
||||
if (!enumValues || enumValues.length === 0) {
|
||||
throw new WorkspaceSchemaManagerException(
|
||||
`Cannot alter enum values for column ${columnDefinition.name} because it has no enum values`,
|
||||
WorkspaceSchemaManagerExceptionCode.ENUM_OPERATION_FAILED,
|
||||
@@ -182,7 +181,7 @@ export class WorkspaceSchemaEnumManagerService {
|
||||
queryRunner,
|
||||
schemaName,
|
||||
enumName,
|
||||
values: columnDefinition.enumValues,
|
||||
values: enumValues,
|
||||
});
|
||||
|
||||
const oldColumnName = `${columnName}_old`;
|
||||
@@ -217,13 +216,13 @@ export class WorkspaceSchemaEnumManagerService {
|
||||
});
|
||||
}
|
||||
|
||||
await this.dropEnum({ queryRunner, schemaName, enumName: oldEnumName });
|
||||
await this.dropColumn({
|
||||
queryRunner,
|
||||
schemaName,
|
||||
tableName,
|
||||
columnName: oldColumnName,
|
||||
});
|
||||
await this.dropEnum({ queryRunner, schemaName, enumName: oldEnumName });
|
||||
|
||||
if (!isTransactionAlreadyActive) {
|
||||
await queryRunner.commitTransaction();
|
||||
@@ -271,12 +270,14 @@ export class WorkspaceSchemaEnumManagerService {
|
||||
columnDefinition: WorkspaceSchemaColumnDefinition;
|
||||
enumTypeName: string;
|
||||
}): Promise<void> {
|
||||
const columnDef = buildSqlColumnDefinition({
|
||||
...columnDefinition,
|
||||
type: enumTypeName,
|
||||
});
|
||||
const safeSchemaName = removeSqlDDLInjection(schemaName);
|
||||
const safeTableName = removeSqlDDLInjection(tableName);
|
||||
|
||||
const columnDef = buildSqlColumnDefinition({
|
||||
...columnDefinition,
|
||||
type: `"${safeSchemaName}"."${enumTypeName}"`,
|
||||
});
|
||||
|
||||
const sql = `ALTER TABLE "${safeSchemaName}"."${safeTableName}" ADD COLUMN ${columnDef}`;
|
||||
|
||||
await queryRunner.query(sql);
|
||||
@@ -322,22 +323,36 @@ export class WorkspaceSchemaEnumManagerService {
|
||||
const safeOldColumnName = removeSqlDDLInjection(oldColumnName);
|
||||
const safeNewColumnName = removeSqlDDLInjection(newColumnName);
|
||||
|
||||
const newEnumTypeName = computePostgresEnumName({
|
||||
tableName,
|
||||
columnName: newColumnName,
|
||||
});
|
||||
|
||||
if (Object.keys(oldToNewEnumOptionMap).length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
const caseStatements = Object.entries(oldToNewEnumOptionMap)
|
||||
.map(
|
||||
([oldEnumOption, newEnumOption]) =>
|
||||
`WHEN '${removeSqlDDLInjection(oldEnumOption)}' THEN '${removeSqlDDLInjection(newEnumOption)}'`,
|
||||
`WHEN '${removeSqlDDLInjection(oldEnumOption)}' THEN '${removeSqlDDLInjection(newEnumOption)}'::"${safeSchemaName}"."${newEnumTypeName}"`,
|
||||
)
|
||||
.join(' ');
|
||||
|
||||
const updateSql = `
|
||||
UPDATE "${safeSchemaName}"."${safeTableName}"
|
||||
SET "${safeNewColumnName}" =
|
||||
CASE "${safeOldColumnName}"
|
||||
${caseStatements}
|
||||
ELSE "${safeOldColumnName}"
|
||||
END
|
||||
WHERE "${safeOldColumnName}" IS NOT NULL`;
|
||||
const mappedValuesCondition = Object.keys(oldToNewEnumOptionMap)
|
||||
.map((oldValue) => `'${removeSqlDDLInjection(oldValue)}'`)
|
||||
.join(', ');
|
||||
|
||||
await queryRunner.query(updateSql);
|
||||
// Update rows with mapped enum values
|
||||
const updateMappedSql = `
|
||||
UPDATE "${safeSchemaName}"."${safeTableName}"
|
||||
SET "${safeNewColumnName}" =
|
||||
CASE "${safeOldColumnName}"::text
|
||||
${caseStatements}
|
||||
END
|
||||
WHERE "${safeOldColumnName}" IS NOT NULL
|
||||
AND "${safeOldColumnName}"::text IN (${mappedValuesCondition})`;
|
||||
|
||||
await queryRunner.query(updateMappedSql);
|
||||
}
|
||||
}
|
||||
|
||||
-1
@@ -8,5 +8,4 @@ export type WorkspaceSchemaColumnDefinition = {
|
||||
isArray?: boolean;
|
||||
asExpression?: string;
|
||||
generatedType?: 'STORED' | 'VIRTUAL';
|
||||
enumValues?: string[];
|
||||
};
|
||||
|
||||
+29
-103
@@ -2,26 +2,15 @@ import { sanitizeDefaultValue } from 'src/engine/twenty-orm/workspace-schema-man
|
||||
|
||||
describe('sanitizeDefaultValue', () => {
|
||||
describe('allowed functions', () => {
|
||||
it('should allow gen_random_uuid() function', () => {
|
||||
// Prepare
|
||||
const input = 'gen_random_uuid()';
|
||||
|
||||
// Act
|
||||
const result = sanitizeDefaultValue(input);
|
||||
|
||||
// Assert
|
||||
expect(result).toBe('gen_random_uuid()');
|
||||
});
|
||||
|
||||
it('should allow uuid_generate_v4() function', () => {
|
||||
// Prepare
|
||||
const input = 'uuid_generate_v4()';
|
||||
const input = 'public.uuid_generate_v4()';
|
||||
|
||||
// Act
|
||||
const result = sanitizeDefaultValue(input);
|
||||
|
||||
// Assert
|
||||
expect(result).toBe('uuid_generate_v4()');
|
||||
expect(result).toBe('public.uuid_generate_v4()');
|
||||
});
|
||||
|
||||
it('should allow now() function', () => {
|
||||
@@ -35,71 +24,10 @@ describe('sanitizeDefaultValue', () => {
|
||||
expect(result).toBe('now()');
|
||||
});
|
||||
|
||||
it('should allow current_timestamp function', () => {
|
||||
// Prepare
|
||||
const input = 'current_timestamp';
|
||||
|
||||
// Act
|
||||
const result = sanitizeDefaultValue(input);
|
||||
|
||||
// Assert
|
||||
expect(result).toBe('current_timestamp');
|
||||
});
|
||||
|
||||
it('should allow current_date function', () => {
|
||||
// Prepare
|
||||
const input = 'current_date';
|
||||
|
||||
// Act
|
||||
const result = sanitizeDefaultValue(input);
|
||||
|
||||
// Assert
|
||||
expect(result).toBe('current_date');
|
||||
});
|
||||
|
||||
it('should allow current_time function', () => {
|
||||
// Prepare
|
||||
const input = 'current_time';
|
||||
|
||||
// Act
|
||||
const result = sanitizeDefaultValue(input);
|
||||
|
||||
// Assert
|
||||
expect(result).toBe('current_time');
|
||||
});
|
||||
|
||||
it('should allow localtime function', () => {
|
||||
// Prepare
|
||||
const input = 'localtime';
|
||||
|
||||
// Act
|
||||
const result = sanitizeDefaultValue(input);
|
||||
|
||||
// Assert
|
||||
expect(result).toBe('localtime');
|
||||
});
|
||||
|
||||
it('should allow localtimestamp function', () => {
|
||||
// Prepare
|
||||
const input = 'localtimestamp';
|
||||
|
||||
// Act
|
||||
const result = sanitizeDefaultValue(input);
|
||||
|
||||
// Assert
|
||||
expect(result).toBe('localtimestamp');
|
||||
});
|
||||
|
||||
it('should be case insensitive for allowed functions', () => {
|
||||
// Act & Assert
|
||||
expect(sanitizeDefaultValue('GEN_RANDOM_UUID()')).toBe(
|
||||
'GEN_RANDOM_UUID()',
|
||||
);
|
||||
|
||||
expect(sanitizeDefaultValue('NOW()')).toBe('NOW()');
|
||||
expect(sanitizeDefaultValue('CURRENT_TIMESTAMP')).toBe(
|
||||
'CURRENT_TIMESTAMP',
|
||||
);
|
||||
expect(sanitizeDefaultValue('Current_Date')).toBe('Current_Date');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -115,7 +43,7 @@ describe('sanitizeDefaultValue', () => {
|
||||
expect(result).not.toContain('DROP TABLE');
|
||||
expect(result).not.toContain(';');
|
||||
expect(result).not.toContain('--');
|
||||
expect(result).toBe('DROPTABLEusers');
|
||||
expect(result).toBe("'DROPTABLEusers'");
|
||||
});
|
||||
|
||||
it('should sanitize quotes in string values', () => {
|
||||
@@ -127,7 +55,7 @@ describe('sanitizeDefaultValue', () => {
|
||||
|
||||
// Assert
|
||||
expect(result).not.toContain('"');
|
||||
expect(result).toBe('testvalue');
|
||||
expect(result).toBe("'testvalue'");
|
||||
});
|
||||
|
||||
it('should sanitize parentheses in non-function values', () => {
|
||||
@@ -140,7 +68,7 @@ describe('sanitizeDefaultValue', () => {
|
||||
// Assert
|
||||
expect(result).not.toContain('(');
|
||||
expect(result).not.toContain(')');
|
||||
expect(result).toBe('testvalue');
|
||||
expect(result).toBe("'testvalue'");
|
||||
});
|
||||
|
||||
it('should sanitize backslashes', () => {
|
||||
@@ -152,7 +80,7 @@ describe('sanitizeDefaultValue', () => {
|
||||
|
||||
// Assert
|
||||
expect(result).not.toContain('\\');
|
||||
expect(result).toBe('testvalue');
|
||||
expect(result).toBe("'testvalue'");
|
||||
});
|
||||
|
||||
it('should sanitize SQL comment patterns', () => {
|
||||
@@ -165,7 +93,7 @@ describe('sanitizeDefaultValue', () => {
|
||||
// Assert
|
||||
expect(result).not.toContain('/*');
|
||||
expect(result).not.toContain('*/');
|
||||
expect(result).toBe('valuecommenttest');
|
||||
expect(result).toBe("'valuecommenttest'");
|
||||
});
|
||||
|
||||
it('should remove non-alphanumeric characters but preserve SQL keywords in alphanumeric form', () => {
|
||||
@@ -176,7 +104,7 @@ describe('sanitizeDefaultValue', () => {
|
||||
const result = sanitizeDefaultValue(inputWithKeywords);
|
||||
|
||||
// Assert
|
||||
expect(result).toBe('SELECTFROMusers');
|
||||
expect(result).toBe("'SELECTFROMusers'");
|
||||
expect(result).not.toContain('*');
|
||||
expect(result).not.toContain(' ');
|
||||
});
|
||||
@@ -191,24 +119,24 @@ describe('sanitizeDefaultValue', () => {
|
||||
const result = sanitizeDefaultValue(input);
|
||||
|
||||
// Assert
|
||||
expect(result).toBe('simple_value');
|
||||
expect(result).toBe("'simple_value'");
|
||||
});
|
||||
|
||||
it('should preserve numeric string values', () => {
|
||||
it('should preserve numeric values', () => {
|
||||
// Prepare
|
||||
const input = '12345';
|
||||
const input = 12345;
|
||||
|
||||
// Act
|
||||
const result = sanitizeDefaultValue(input);
|
||||
|
||||
// Assert
|
||||
expect(result).toBe('12345');
|
||||
expect(result).toBe(12345);
|
||||
});
|
||||
|
||||
it('should preserve boolean string values', () => {
|
||||
it('should preserve boolean values', () => {
|
||||
// Act & Assert
|
||||
expect(sanitizeDefaultValue('true')).toBe('true');
|
||||
expect(sanitizeDefaultValue('false')).toBe('false');
|
||||
expect(sanitizeDefaultValue(true)).toBe(true);
|
||||
expect(sanitizeDefaultValue(false)).toBe(false);
|
||||
});
|
||||
|
||||
it('should handle empty string', () => {
|
||||
@@ -219,7 +147,7 @@ describe('sanitizeDefaultValue', () => {
|
||||
const result = sanitizeDefaultValue(input);
|
||||
|
||||
// Assert
|
||||
expect(result).toBe('');
|
||||
expect(result).toBe("''");
|
||||
});
|
||||
|
||||
it('should remove whitespace but preserve alphanumeric and underscores', () => {
|
||||
@@ -230,7 +158,7 @@ describe('sanitizeDefaultValue', () => {
|
||||
const result = sanitizeDefaultValue(input);
|
||||
|
||||
// Assert
|
||||
expect(result).toBe('test');
|
||||
expect(result).toBe("'test'");
|
||||
});
|
||||
|
||||
it('should preserve alphanumeric values with underscores', () => {
|
||||
@@ -241,7 +169,7 @@ describe('sanitizeDefaultValue', () => {
|
||||
const result = sanitizeDefaultValue(input);
|
||||
|
||||
// Assert
|
||||
expect(result).toBe('test_value_123');
|
||||
expect(result).toBe("'test_value_123'");
|
||||
});
|
||||
});
|
||||
|
||||
@@ -249,14 +177,14 @@ describe('sanitizeDefaultValue', () => {
|
||||
it('should distinguish between allowed functions and similar strings', () => {
|
||||
// Act & Assert
|
||||
expect(sanitizeDefaultValue('now()')).toBe('now()');
|
||||
expect(sanitizeDefaultValue('now_test')).toBe('now_test');
|
||||
expect(sanitizeDefaultValue('not_now()')).toBe('not_now');
|
||||
expect(sanitizeDefaultValue('now_test')).toBe("'now_test'");
|
||||
expect(sanitizeDefaultValue('not_now()')).toBe("'not_now'");
|
||||
});
|
||||
|
||||
it('should handle functions with different casing but sanitize non-functions normally', () => {
|
||||
// Act & Assert
|
||||
expect(sanitizeDefaultValue('NOW()')).toBe('NOW()');
|
||||
expect(sanitizeDefaultValue('now_function')).toBe('now_function');
|
||||
expect(sanitizeDefaultValue('now_function')).toBe("'now_function'");
|
||||
});
|
||||
|
||||
it('should handle complex mixed input', () => {
|
||||
@@ -267,7 +195,7 @@ describe('sanitizeDefaultValue', () => {
|
||||
const result = sanitizeDefaultValue(complexInput);
|
||||
|
||||
// Assert
|
||||
expect(result).toBe('testvalueDROPTABLEuserscommentnow');
|
||||
expect(result).toBe("'testvalueDROPTABLEuserscommentnow'");
|
||||
expect(result).not.toContain(';');
|
||||
expect(result).not.toContain('"');
|
||||
expect(result).not.toContain('/*');
|
||||
@@ -277,18 +205,16 @@ describe('sanitizeDefaultValue', () => {
|
||||
});
|
||||
|
||||
describe('edge cases', () => {
|
||||
it('should handle null-like strings', () => {
|
||||
it('should handle null', () => {
|
||||
// Act & Assert
|
||||
expect(sanitizeDefaultValue('null')).toBe('null');
|
||||
expect(sanitizeDefaultValue('NULL')).toBe('NULL');
|
||||
expect(sanitizeDefaultValue('undefined')).toBe('undefined');
|
||||
expect(sanitizeDefaultValue(null)).toBe('NULL');
|
||||
});
|
||||
|
||||
it('should handle strings that start with allowed function names', () => {
|
||||
// Act & Assert
|
||||
expect(sanitizeDefaultValue('now_extended')).toBe('now_extended');
|
||||
expect(sanitizeDefaultValue('now_extended')).toBe("'now_extended'");
|
||||
expect(sanitizeDefaultValue('gen_random_uuid_custom')).toBe(
|
||||
'gen_random_uuid_custom',
|
||||
"'gen_random_uuid_custom'",
|
||||
);
|
||||
});
|
||||
|
||||
@@ -300,7 +226,7 @@ describe('sanitizeDefaultValue', () => {
|
||||
const result = sanitizeDefaultValue(specialChars);
|
||||
|
||||
// Assert
|
||||
expect(result).toBe('');
|
||||
expect(result).toBe("''");
|
||||
});
|
||||
|
||||
it('should handle very long strings', () => {
|
||||
@@ -311,7 +237,7 @@ describe('sanitizeDefaultValue', () => {
|
||||
const result = sanitizeDefaultValue(longString);
|
||||
|
||||
// Assert
|
||||
expect(result).toBe('a'.repeat(1000) + 'DROPTABLEusers');
|
||||
expect(result).toBe(`'${'a'.repeat(1000)}DROPTABLEusers'`);
|
||||
expect(result).not.toContain(';');
|
||||
expect(result).not.toContain(' ');
|
||||
});
|
||||
|
||||
+23
-19
@@ -1,3 +1,5 @@
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
|
||||
import { type WorkspaceSchemaColumnDefinition } from 'src/engine/twenty-orm/workspace-schema-manager/types/workspace-schema-column-definition.type';
|
||||
import { sanitizeDefaultValue } from 'src/engine/twenty-orm/workspace-schema-manager/utils/sanitize-default-value.util';
|
||||
import { removeSqlDDLInjection } from 'src/engine/workspace-manager/workspace-migration-runner/utils/remove-sql-injection.util';
|
||||
@@ -8,33 +10,35 @@ export const buildSqlColumnDefinition = (
|
||||
const safeName = removeSqlDDLInjection(column.name);
|
||||
const parts = [`"${safeName}"`];
|
||||
|
||||
if (column.asExpression) {
|
||||
parts.push(`AS (${column.asExpression})`); // TODO: to sanitize
|
||||
parts.push(column.isArray ? `${column.type}[]` : column.type);
|
||||
|
||||
if (column.asExpression && column.type === 'tsvector') {
|
||||
parts.push(`GENERATED ALWAYS AS (${column.asExpression})`); // TODO: to sanitize
|
||||
if (column.generatedType) {
|
||||
parts.push(column.generatedType);
|
||||
}
|
||||
} else {
|
||||
const safeType = removeSqlDDLInjection(column.type);
|
||||
}
|
||||
|
||||
parts.push(column.isArray ? `${safeType}[]` : safeType);
|
||||
if (column.isPrimary) {
|
||||
parts.push('PRIMARY KEY');
|
||||
}
|
||||
|
||||
if (column.isPrimary) {
|
||||
parts.push('PRIMARY KEY');
|
||||
}
|
||||
if (column.isNullable === false) {
|
||||
parts.push('NOT NULL');
|
||||
}
|
||||
|
||||
if (column.isNullable === false) {
|
||||
parts.push('NOT NULL');
|
||||
}
|
||||
if (column.isUnique) {
|
||||
parts.push('UNIQUE');
|
||||
}
|
||||
|
||||
if (column.isUnique) {
|
||||
parts.push('UNIQUE');
|
||||
}
|
||||
if (
|
||||
isDefined(column.default) &&
|
||||
column.default !== '' &&
|
||||
column.type !== 'tsvector'
|
||||
) {
|
||||
const safeDefault = sanitizeDefaultValue(column.default);
|
||||
|
||||
if (column.default !== undefined) {
|
||||
const safeDefault = sanitizeDefaultValue(column.default);
|
||||
|
||||
parts.push(`DEFAULT ${safeDefault}`);
|
||||
}
|
||||
parts.push(`DEFAULT ${safeDefault}`);
|
||||
}
|
||||
|
||||
return parts.join(' ');
|
||||
|
||||
+2
-11
@@ -7,23 +7,14 @@ export const sanitizeDefaultValue = (
|
||||
return 'NULL';
|
||||
}
|
||||
|
||||
const allowedFunctions = [
|
||||
'gen_random_uuid()',
|
||||
'uuid_generate_v4()',
|
||||
'now()',
|
||||
'current_timestamp',
|
||||
'current_date',
|
||||
'current_time',
|
||||
'localtime',
|
||||
'localtimestamp',
|
||||
];
|
||||
const allowedFunctions = ['public.uuid_generate_v4()', 'now()'];
|
||||
|
||||
if (typeof defaultValue === 'string') {
|
||||
if (allowedFunctions.includes(defaultValue.toLowerCase())) {
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
return removeSqlDDLInjection(defaultValue);
|
||||
return `'${removeSqlDDLInjection(defaultValue)}'`;
|
||||
}
|
||||
|
||||
return defaultValue;
|
||||
|
||||
+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