diff --git a/packages/twenty-server/src/database/commands/upgrade-version-command/1-3/1-3-update-timestamp-column-type-in-workspace-schema.command.ts b/packages/twenty-server/src/database/commands/upgrade-version-command/1-3/1-3-update-timestamp-column-type-in-workspace-schema.command.ts new file mode 100644 index 00000000000..1fc1adec401 --- /dev/null +++ b/packages/twenty-server/src/database/commands/upgrade-version-command/1-3/1-3-update-timestamp-column-type-in-workspace-schema.command.ts @@ -0,0 +1,61 @@ +import { InjectRepository } from '@nestjs/typeorm'; + +import { Command } from 'nest-commander'; +import { FieldMetadataType } from 'twenty-shared/types'; +import { Repository } from 'typeorm'; + +import { + ActiveOrSuspendedWorkspacesMigrationCommandRunner, + RunOnWorkspaceArgs, +} from 'src/database/commands/command-runners/active-or-suspended-workspaces-migration.command-runner'; +import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity'; +import { FieldMetadataEntity } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity'; +import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager'; +import { computeObjectTargetTable } from 'src/engine/utils/compute-object-target-table.util'; +import { getWorkspaceSchemaName } from 'src/engine/workspace-datasource/utils/get-workspace-schema-name.util'; +import { WorkspaceDataSourceService } from 'src/engine/workspace-datasource/workspace-datasource.service'; + +@Command({ + name: 'upgrade:1-3:update-timestamp-column-type-in-workspace-schema', + description: + 'Update the timestamp column type in all the workspace schema tables', +}) +export class UpdateTimestampColumnTypeInWorkspaceSchemaCommand extends ActiveOrSuspendedWorkspacesMigrationCommandRunner { + constructor( + @InjectRepository(Workspace, 'core') + protected readonly workspaceRepository: Repository, + private readonly workspaceDataSourceService: WorkspaceDataSourceService, + protected readonly twentyORMGlobalManager: TwentyORMGlobalManager, + @InjectRepository(FieldMetadataEntity, 'core') + private readonly fieldMetadataRepository: Repository, + ) { + super(workspaceRepository, twentyORMGlobalManager); + } + + override async runOnWorkspace({ + workspaceId, + }: RunOnWorkspaceArgs): Promise { + const dateTimeFieldMetadataItems = await this.fieldMetadataRepository.find({ + where: { + workspaceId, + type: FieldMetadataType.DATE_TIME, + }, + relations: ['object'], + }); + + const mainDataSource = + await this.workspaceDataSourceService.connectToMainDataSource(); + + const schemaName = getWorkspaceSchemaName(workspaceId); + + for (const fieldMetadataItem of dateTimeFieldMetadataItems) { + this.logger.log( + `Updating column type for ${fieldMetadataItem.name} in ${schemaName}."${computeObjectTargetTable(fieldMetadataItem.object)}"`, + ); + await mainDataSource.query( + `ALTER TABLE ${schemaName}."${computeObjectTargetTable(fieldMetadataItem.object)}" + ALTER COLUMN "${fieldMetadataItem.name}" TYPE timestamptz(3);`, + ); + } + } +} diff --git a/packages/twenty-server/src/database/commands/upgrade-version-command/1-3/1-3-upgrade-version-command.module.ts b/packages/twenty-server/src/database/commands/upgrade-version-command/1-3/1-3-upgrade-version-command.module.ts index 0d7e409b1ac..d6013b25609 100644 --- a/packages/twenty-server/src/database/commands/upgrade-version-command/1-3/1-3-upgrade-version-command.module.ts +++ b/packages/twenty-server/src/database/commands/upgrade-version-command/1-3/1-3-upgrade-version-command.module.ts @@ -3,6 +3,7 @@ import { TypeOrmModule } from '@nestjs/typeorm'; import { AddNextStepIdsToWorkflowRunsTrigger } from 'src/database/commands/upgrade-version-command/1-3/1-3-add-next-step-ids-to-workflow-runs-trigger.command'; import { AssignRolesToExistingApiKeysCommand } from 'src/database/commands/upgrade-version-command/1-3/1-3-assign-roles-to-existing-api-keys.command'; +import { UpdateTimestampColumnTypeInWorkspaceSchemaCommand } from 'src/database/commands/upgrade-version-command/1-3/1-3-update-timestamp-column-type-in-workspace-schema.command'; import { ApiKey } from 'src/engine/core-modules/api-key/api-key.entity'; import { ApiKeyModule } from 'src/engine/core-modules/api-key/api-key.module'; import { FeatureFlagModule } from 'src/engine/core-modules/feature-flag/feature-flag.module'; @@ -41,10 +42,12 @@ import { WorkspaceDataSourceModule } from 'src/engine/workspace-datasource/works providers: [ AssignRolesToExistingApiKeysCommand, AddNextStepIdsToWorkflowRunsTrigger, + UpdateTimestampColumnTypeInWorkspaceSchemaCommand, ], exports: [ AssignRolesToExistingApiKeysCommand, AddNextStepIdsToWorkflowRunsTrigger, + UpdateTimestampColumnTypeInWorkspaceSchemaCommand, ], }) export class V1_3_UpgradeVersionCommandModule {} diff --git a/packages/twenty-server/src/database/commands/upgrade-version-command/upgrade.command.ts b/packages/twenty-server/src/database/commands/upgrade-version-command/upgrade.command.ts index 1108734229c..94dffac9581 100644 --- a/packages/twenty-server/src/database/commands/upgrade-version-command/upgrade.command.ts +++ b/packages/twenty-server/src/database/commands/upgrade-version-command/upgrade.command.ts @@ -30,6 +30,7 @@ import { AddNextStepIdsToWorkflowVersionTriggers } from 'src/database/commands/u import { RemoveWorkflowRunsWithoutState } from 'src/database/commands/upgrade-version-command/1-2/1-2-remove-workflow-runs-without-state.command'; import { AddNextStepIdsToWorkflowRunsTrigger } from 'src/database/commands/upgrade-version-command/1-3/1-3-add-next-step-ids-to-workflow-runs-trigger.command'; import { AssignRolesToExistingApiKeysCommand } from 'src/database/commands/upgrade-version-command/1-3/1-3-assign-roles-to-existing-api-keys.command'; +import { UpdateTimestampColumnTypeInWorkspaceSchemaCommand } from 'src/database/commands/upgrade-version-command/1-3/1-3-update-timestamp-column-type-in-workspace-schema.command'; import { TwentyConfigService } from 'src/engine/core-modules/twenty-config/twenty-config.service'; import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity'; import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager'; @@ -158,8 +159,8 @@ export class UpgradeCommand extends UpgradeCommandRunner { // 1.3 Commands protected readonly assignRolesToExistingApiKeysCommand: AssignRolesToExistingApiKeysCommand, - // protected readonly addNextStepIdsToWorkflowVersionTriggers: AddNextStepIdsToWorkflowVersionTriggers, protected readonly addNextStepIdsToWorkflowRunsTrigger: AddNextStepIdsToWorkflowRunsTrigger, + protected readonly updateTimestampColumnTypeInWorkspaceSchemaCommand: UpdateTimestampColumnTypeInWorkspaceSchemaCommand, ) { super( workspaceRepository, @@ -223,6 +224,7 @@ export class UpgradeCommand extends UpgradeCommandRunner { this.addNextStepIdsToWorkflowVersionTriggers, // We add that command again because nextStepIds where not added on freshly created triggers. It will be done in 1.3 this.addNextStepIdsToWorkflowRunsTrigger, this.assignRolesToExistingApiKeysCommand, + this.updateTimestampColumnTypeInWorkspaceSchemaCommand, ], afterSyncMetadata: [], }; diff --git a/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/utils/__tests__/get-field-metadata-type.spec.ts b/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/utils/__tests__/get-field-metadata-type.spec.ts deleted file mode 100644 index 388bcc60998..00000000000 --- a/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/utils/__tests__/get-field-metadata-type.spec.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { FieldMetadataType } from 'twenty-shared/types'; - -import { getFieldMetadataType } from 'src/engine/api/graphql/workspace-schema-builder/utils/get-field-metadata-type.util'; - -describe('getFieldMetadataType', () => { - it.each([ - ['uuid', FieldMetadataType.UUID], - ['timestamptz', FieldMetadataType.DATE_TIME], - ])( - 'should return correct FieldMetadataType for type %s', - (type, expectedMetadataType) => { - expect(getFieldMetadataType(type)).toBe(expectedMetadataType); - }, - ); - - it('should throw an error for an unknown type', () => { - const unknownType = 'unknownType'; - - expect(() => getFieldMetadataType(unknownType)).toThrow( - `Unknown type ${unknownType}`, - ); - }); -}); diff --git a/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/utils/get-field-metadata-type.util.ts b/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/utils/get-field-metadata-type.util.ts deleted file mode 100644 index d101433433f..00000000000 --- a/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/utils/get-field-metadata-type.util.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { FieldMetadataType } from 'twenty-shared/types'; -const typeOrmTypeMapping = new Map([ - ['uuid', FieldMetadataType.UUID], - ['timestamptz', FieldMetadataType.DATE_TIME], - // Add more types here if we need to support more than id, and createdAt/updatedAt/deletedAt -]); - -export const getFieldMetadataType = (type: string) => { - const fieldType = typeOrmTypeMapping.get(type); - - if (fieldType === undefined || fieldType === null) { - throw new Error(`Unknown type ${type}`); - } - - return fieldType; -}; diff --git a/packages/twenty-server/src/engine/twenty-orm/factories/entity-schema-column.factory.ts b/packages/twenty-server/src/engine/twenty-orm/factories/entity-schema-column.factory.ts index 29da44c20ee..ed52bb09a50 100644 --- a/packages/twenty-server/src/engine/twenty-orm/factories/entity-schema-column.factory.ts +++ b/packages/twenty-server/src/engine/twenty-orm/factories/entity-schema-column.factory.ts @@ -90,6 +90,8 @@ export class EntitySchemaColumnFactory { entitySchemaColumnMap[key] = { name: key, type: columnType as ColumnType, + precision: + fieldMetadata.type === FieldMetadataType.DATE_TIME ? 3 : undefined, // TODO: We should double check that primary: key === 'id', nullable: fieldMetadata.isNullable ?? false, diff --git a/packages/twenty-server/src/engine/workspace-manager/workspace-migration-runner/services/workspace-migration-column.service.ts b/packages/twenty-server/src/engine/workspace-manager/workspace-migration-runner/services/workspace-migration-column.service.ts index 7986beb05ec..2e2dfcdeea0 100644 --- a/packages/twenty-server/src/engine/workspace-manager/workspace-migration-runner/services/workspace-migration-column.service.ts +++ b/packages/twenty-server/src/engine/workspace-manager/workspace-migration-runner/services/workspace-migration-column.service.ts @@ -179,6 +179,10 @@ export class WorkspaceMigrationColumnService { column: { name: createColumnMigration.columnName, type: createColumnMigration.columnType, + precision: + createColumnMigration.columnType === 'timestamptz' + ? 3 + : undefined, isArray: createColumnMigration.isArray ?? false, isNullable: createColumnMigration.isNullable, default: createColumnMigration.defaultValue, diff --git a/packages/twenty-server/src/engine/workspace-manager/workspace-migration-runner/services/workspace-migration-type.service.ts b/packages/twenty-server/src/engine/workspace-manager/workspace-migration-runner/services/workspace-migration-type.service.ts index 08c6ecbdf08..56895392d13 100644 --- a/packages/twenty-server/src/engine/workspace-manager/workspace-migration-runner/services/workspace-migration-type.service.ts +++ b/packages/twenty-server/src/engine/workspace-manager/workspace-migration-runner/services/workspace-migration-type.service.ts @@ -15,13 +15,16 @@ export class WorkspaceMigrationTypeService { migrationColumn: WorkspaceMigrationColumnAlter, ) { const columnDefinition = migrationColumn.alteredColumnDefinition; + const computedColumnType = ` ${columnDefinition.columnType}${ + columnDefinition.columnType === 'timestamptz' ? `(3)` : '' + }`; // Update the column type // If casting is not possible, the query will fail await queryRunner.query(` ALTER TABLE "${schemaName}"."${tableName}" - ALTER COLUMN "${columnDefinition.columnName}" TYPE ${columnDefinition.columnType} - USING "${columnDefinition.columnName}"::${columnDefinition.columnType} + ALTER COLUMN "${columnDefinition.columnName}" TYPE ${computedColumnType} + USING "${columnDefinition.columnName}"::${computedColumnType} `); // Update the column default value diff --git a/packages/twenty-server/src/engine/workspace-manager/workspace-migration-runner/utils/internal/typeorm-build-create-column-sql.util.ts b/packages/twenty-server/src/engine/workspace-manager/workspace-migration-runner/utils/internal/typeorm-build-create-column-sql.util.ts index 7d52bf2e5b3..2237c73920d 100644 --- a/packages/twenty-server/src/engine/workspace-manager/workspace-migration-runner/utils/internal/typeorm-build-create-column-sql.util.ts +++ b/packages/twenty-server/src/engine/workspace-manager/workspace-migration-runner/utils/internal/typeorm-build-create-column-sql.util.ts @@ -19,6 +19,7 @@ export const typeormBuildCreateColumnSql = ({ | 'default' | 'generatedType' | 'asExpression' + | 'precision' >; }): string => { let columnSql = '"' + column.name + '"'; @@ -30,6 +31,7 @@ export const typeormBuildCreateColumnSql = ({ })}"`; } else { columnSql += ' ' + column.type; + if (column.precision) columnSql += `(${column.precision})`; } if (column.isArray) columnSql += '[]';