diff --git a/packages/twenty-server/src/database/commands/upgrade-version-command/1-19/1-19-add-missing-system-fields-to-standard-objects.command.ts b/packages/twenty-server/src/database/commands/upgrade-version-command/1-19/1-19-add-missing-system-fields-to-standard-objects.command.ts index 1a57a39300..9606b4794a 100644 --- a/packages/twenty-server/src/database/commands/upgrade-version-command/1-19/1-19-add-missing-system-fields-to-standard-objects.command.ts +++ b/packages/twenty-server/src/database/commands/upgrade-version-command/1-19/1-19-add-missing-system-fields-to-standard-objects.command.ts @@ -1,6 +1,7 @@ import { InjectRepository } from '@nestjs/typeorm'; import { Command } from 'nest-commander'; +import { FieldMetadataType } from 'twenty-shared/types'; import { isDefined } from 'twenty-shared/utils'; import { Repository } from 'typeorm'; @@ -11,11 +12,33 @@ import { WorkspaceEntity } from 'src/engine/core-modules/workspace/workspace.ent import { DataSourceService } from 'src/engine/metadata-modules/data-source/data-source.service'; import { GlobalWorkspaceOrmManager } from 'src/engine/twenty-orm/global-workspace-datasource/global-workspace-orm.manager'; import { WorkspaceCacheService } from 'src/engine/workspace-cache/services/workspace-cache.service'; +import { type WorkspaceMigration } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-builder/types/workspace-migration.type'; import { WorkspaceMigrationRunnerService } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-runner/services/workspace-migration-runner.service'; -const FIRST_FIELD_UNIVERSAL_IDENTIFIER = - ADD_MISSING_SYSTEM_FIELDS_TO_STANDARD_OBJECTS_1771420702241.actions[0] - .flatEntity.universalIdentifier; +const { applicationUniversalIdentifier, actions: allActions } = + ADD_MISSING_SYSTEM_FIELDS_TO_STANDARD_OBJECTS_1771420702241; + +const NON_TS_VECTOR_MIGRATION: WorkspaceMigration = { + applicationUniversalIdentifier, + actions: allActions.filter( + (action) => action.flatEntity.type !== FieldMetadataType.TS_VECTOR, + ), +}; + +const TS_VECTOR_INDIVIDUAL_MIGRATIONS = allActions + .filter((action) => action.flatEntity.type === FieldMetadataType.TS_VECTOR) + .map((action) => ({ + universalIdentifier: action.flatEntity.universalIdentifier, + fieldName: action.flatEntity.name, + objectIdentifier: action.flatEntity.objectMetadataUniversalIdentifier, + migration: { + applicationUniversalIdentifier, + actions: [action], + } satisfies WorkspaceMigration, + })); + +const FIRST_NON_TS_VECTOR_UNIVERSAL_IDENTIFIER = + allActions[0].flatEntity.universalIdentifier; @Command({ name: 'upgrade:1-19:add-missing-system-fields-to-standard-objects', @@ -34,19 +57,17 @@ export class AddMissingSystemFieldsToStandardObjectsCommand extends ActiveOrSusp super(workspaceRepository, twentyORMGlobalManager, dataSourceService); } - // The entire migration runs in a single transaction, so checking the first - // field is enough to know whether the migration has already been applied. - // In the future we will maintain a list of passed migrations - private async hasAlreadyRun(workspaceId: string): Promise { + private async hasFieldBeenCreated( + workspaceId: string, + universalIdentifier: string, + ): Promise { const { flatFieldMetadataMaps } = await this.workspaceCacheService.getOrRecompute(workspaceId, [ 'flatFieldMetadataMaps', ]); return isDefined( - flatFieldMetadataMaps.byUniversalIdentifier[ - FIRST_FIELD_UNIVERSAL_IDENTIFIER - ], + flatFieldMetadataMaps.byUniversalIdentifier[universalIdentifier], ); } @@ -62,25 +83,51 @@ export class AddMissingSystemFieldsToStandardObjectsCommand extends ActiveOrSusp if (dryRun) { this.logger.log( - `[DRY RUN] Would add ${ADD_MISSING_SYSTEM_FIELDS_TO_STANDARD_OBJECTS_1771420702241.actions.length} missing system fields to standard objects in workspace ${workspaceId}. Skipping.`, + `[DRY RUN] Would add ${NON_TS_VECTOR_MIGRATION.actions.length} non-tsVector fields and ${TS_VECTOR_INDIVIDUAL_MIGRATIONS.length} tsVector fields to standard objects in workspace ${workspaceId}. Skipping.`, ); return; } - if (await this.hasAlreadyRun(workspaceId)) { - this.logger.log( - `Migration already applied for workspace ${workspaceId}, skipping.`, - ); - - return; - } - - await this.workspaceMigrationRunnerService.run({ + const nonTsVectorAlreadyRan = await this.hasFieldBeenCreated( workspaceId, - workspaceMigration: - ADD_MISSING_SYSTEM_FIELDS_TO_STANDARD_OBJECTS_1771420702241, - }); + FIRST_NON_TS_VECTOR_UNIVERSAL_IDENTIFIER, + ); + + if (!nonTsVectorAlreadyRan) { + this.logger.log( + `Adding ${NON_TS_VECTOR_MIGRATION.actions.length} non-tsVector fields (position, createdBy, updatedBy) in workspace ${workspaceId}`, + ); + + await this.workspaceMigrationRunnerService.run({ + workspaceId, + workspaceMigration: NON_TS_VECTOR_MIGRATION, + }); + } else { + this.logger.log( + `Non-tsVector fields already exist in workspace ${workspaceId}, skipping.`, + ); + } + + for (const tsVectorEntry of TS_VECTOR_INDIVIDUAL_MIGRATIONS) { + const alreadyCreated = await this.hasFieldBeenCreated( + workspaceId, + tsVectorEntry.universalIdentifier, + ); + + if (alreadyCreated) { + continue; + } + + this.logger.log( + `Adding tsVector field ${tsVectorEntry.fieldName} on object ${tsVectorEntry.objectIdentifier} in workspace ${workspaceId}`, + ); + + await this.workspaceMigrationRunnerService.run({ + workspaceId, + workspaceMigration: tsVectorEntry.migration, + }); + } this.logger.log( `Successfully added missing system fields to standard objects in workspace ${workspaceId}`, 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 2243aacd12..d94ab6383e 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 @@ -124,11 +124,11 @@ export class UpgradeCommand extends UpgradeCommandRunner { ]; const commands_1190: VersionCommands = [ + this.fixRoleAndAgentUniversalIdentifiersCommand, this.backfillSystemFieldsIsSystemCommand, this.addMissingSystemFieldsToStandardObjectsCommand, this.backfillMessageChannelMessageAssociationMessageFolderCommand, this.backfillMissingStandardViewsCommand, - this.fixRoleAndAgentUniversalIdentifiersCommand, this.seedServerIdCommand, ]; diff --git a/packages/twenty-server/src/database/typeorm/core/core.datasource.ts b/packages/twenty-server/src/database/typeorm/core/core.datasource.ts index 3c621a32e9..597a786dda 100644 --- a/packages/twenty-server/src/database/typeorm/core/core.datasource.ts +++ b/packages/twenty-server/src/database/typeorm/core/core.datasource.ts @@ -72,7 +72,7 @@ export const typeORMCoreModuleOptions: TypeOrmModuleOptions = { } : undefined, extra: { - query_timeout: 15000, + query_timeout: Number(process.env.DATABASE_STATEMENT_TIMEOUT_MS ?? 15000), }, }; diff --git a/packages/twenty-server/src/engine/core-modules/twenty-config/config-variables.ts b/packages/twenty-server/src/engine/core-modules/twenty-config/config-variables.ts index e7ce5afd02..1210d0f874 100644 --- a/packages/twenty-server/src/engine/core-modules/twenty-config/config-variables.ts +++ b/packages/twenty-server/src/engine/core-modules/twenty-config/config-variables.ts @@ -1682,6 +1682,17 @@ export class ConfigVariables { @IsOptional() PG_DATABASE_REPLICA_TIMEOUT_MS: number = 10000; + @ConfigVariablesMetadata({ + group: ConfigVariablesGroup.SERVER_CONFIG, + description: + 'Client-side query timeout in milliseconds for the core database connection pool. Controls how long any single query can run before the driver aborts it.', + type: ConfigVariableType.NUMBER, + isEnvOnly: true, + }) + @CastToPositiveNumber() + @IsOptional() + DATABASE_STATEMENT_TIMEOUT_MS: number = 15000; + @ConfigVariablesMetadata({ group: ConfigVariablesGroup.SERVER_CONFIG, description: