Add 1-21 upgrade command to backfill datasource to workspace table (#19180)

## Summary
- Adds a new `upgrade:1-21:backfill-datasource-to-workspace` command
that copies `dataSource.schema` into `workspace.databaseSchema` for all
active/suspended workspaces that haven't been migrated yet
- Registers the command in the 1-21 upgrade module and wires it into the
upgrade runner (runs before other 1-21 commands)
- Part of the ongoing deprecation of the `dataSource` table in favor of
storing `databaseSchema` directly on `WorkspaceEntity`
This commit is contained in:
Charles Bochet
2026-03-31 20:34:46 +02:00
committed by GitHub
parent 887e0283c5
commit 5bbfce7789
3 changed files with 103 additions and 3 deletions
@@ -0,0 +1,89 @@
import { InjectRepository } from '@nestjs/typeorm';
import { isNonEmptyString } from '@sniptt/guards';
import { Command } from 'nest-commander';
import { isDefined } from 'twenty-shared/utils';
import { Repository } from 'typeorm';
import { ActiveOrSuspendedWorkspacesMigrationCommandRunner } from 'src/database/commands/command-runners/active-or-suspended-workspaces-migration.command-runner';
import { RunOnWorkspaceArgs } from 'src/database/commands/command-runners/workspaces-migration.command-runner';
import { WorkspaceEntity } from 'src/engine/core-modules/workspace/workspace.entity';
import { DataSourceEntity } from 'src/engine/metadata-modules/data-source/data-source.entity';
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';
@Command({
name: 'upgrade:1-21:backfill-datasource-to-workspace',
description:
'Backfill workspace.databaseSchema from the dataSource entity for workspaces that have not been migrated yet',
})
export class BackfillDatasourceToWorkspaceCommand extends ActiveOrSuspendedWorkspacesMigrationCommandRunner {
constructor(
@InjectRepository(WorkspaceEntity)
protected readonly workspaceRepository: Repository<WorkspaceEntity>,
@InjectRepository(DataSourceEntity)
private readonly dataSourceRepository: Repository<DataSourceEntity>,
protected readonly twentyORMGlobalManager: GlobalWorkspaceOrmManager,
protected readonly dataSourceService: DataSourceService,
) {
super(workspaceRepository, twentyORMGlobalManager, dataSourceService);
}
override async runOnWorkspace({
workspaceId,
options,
}: RunOnWorkspaceArgs): Promise<void> {
const isDryRun = options.dryRun ?? false;
const workspace = await this.workspaceRepository.findOne({
where: { id: workspaceId },
});
if (!isDefined(workspace)) {
this.logger.warn(`Workspace ${workspaceId} not found, skipping`);
return;
}
if (isNonEmptyString(workspace.databaseSchema)) {
this.logger.log(
`Workspace ${workspaceId} already has databaseSchema="${workspace.databaseSchema}", skipping`,
);
return;
}
const dataSource = await this.dataSourceRepository.findOne({
where: { workspaceId },
order: { createdAt: 'DESC' },
});
if (!isDefined(dataSource)) {
throw new Error(
`No dataSource row found for workspace ${workspaceId}. Cannot backfill databaseSchema.`,
);
}
if (!isNonEmptyString(dataSource.schema)) {
throw new Error(
`DataSource for workspace ${workspaceId} has an empty schema. Cannot backfill databaseSchema.`,
);
}
if (isDryRun) {
this.logger.log(
`[DRY RUN] Would set workspace ${workspaceId} databaseSchema to "${dataSource.schema}"`,
);
return;
}
await this.workspaceRepository.update(workspaceId, {
databaseSchema: dataSource.schema,
});
this.logger.log(
`Backfilled workspace ${workspaceId} databaseSchema to "${dataSource.schema}"`,
);
}
}
@@ -1,24 +1,32 @@
import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { BackfillDatasourceToWorkspaceCommand } from 'src/database/commands/upgrade-version-command/1-21/1-21-backfill-datasource-to-workspace.command';
import { BackfillPageLayoutsAndFieldsWidgetViewFieldsCommand } from 'src/database/commands/upgrade-version-command/1-21/1-21-backfill-page-layouts-and-fields-widget-view-fields.command';
import { ApplicationModule } from 'src/engine/core-modules/application/application.module';
import { FeatureFlagModule } from 'src/engine/core-modules/feature-flag/feature-flag.module';
import { WorkspaceEntity } from 'src/engine/core-modules/workspace/workspace.entity';
import { DataSourceEntity } from 'src/engine/metadata-modules/data-source/data-source.entity';
import { DataSourceModule } from 'src/engine/metadata-modules/data-source/data-source.module';
import { WorkspaceCacheModule } from 'src/engine/workspace-cache/workspace-cache.module';
import { WorkspaceMigrationModule } from 'src/engine/workspace-manager/workspace-migration/workspace-migration.module';
@Module({
imports: [
TypeOrmModule.forFeature([WorkspaceEntity]),
TypeOrmModule.forFeature([WorkspaceEntity, DataSourceEntity]),
DataSourceModule,
WorkspaceCacheModule,
ApplicationModule,
WorkspaceMigrationModule,
FeatureFlagModule,
],
providers: [BackfillPageLayoutsAndFieldsWidgetViewFieldsCommand],
exports: [BackfillPageLayoutsAndFieldsWidgetViewFieldsCommand],
providers: [
BackfillDatasourceToWorkspaceCommand,
BackfillPageLayoutsAndFieldsWidgetViewFieldsCommand,
],
exports: [
BackfillDatasourceToWorkspaceCommand,
BackfillPageLayoutsAndFieldsWidgetViewFieldsCommand,
],
})
export class V1_21_UpgradeVersionCommandModule {}
@@ -12,6 +12,7 @@ import {
import { CoreMigrationRunnerService } from 'src/database/commands/core-migration-runner/services/core-migration-runner.service';
import { BackfillCommandMenuItemsCommand } from 'src/database/commands/upgrade-version-command/1-20/1-20-backfill-command-menu-items.command';
import { BackfillNavigationMenuItemTypeCommand } from 'src/database/commands/upgrade-version-command/1-20/1-20-backfill-navigation-menu-item-type.command';
import { BackfillDatasourceToWorkspaceCommand } from 'src/database/commands/upgrade-version-command/1-21/1-21-backfill-datasource-to-workspace.command';
import { BackfillPageLayoutsAndFieldsWidgetViewFieldsCommand } from 'src/database/commands/upgrade-version-command/1-21/1-21-backfill-page-layouts-and-fields-widget-view-fields.command';
import { IdentifyFieldPermissionMetadataCommand } from 'src/database/commands/upgrade-version-command/1-20/1-20-identify-field-permission-metadata.command';
import { BackfillSelectFieldOptionIdsCommand } from 'src/database/commands/upgrade-version-command/1-20/1-20-backfill-select-field-option-ids.command';
@@ -68,6 +69,7 @@ export class UpgradeCommand extends UpgradeCommandRunner {
protected readonly makeWorkflowSearchableCommand: MakeWorkflowSearchableCommand,
// 1.21 Commands
protected readonly backfillDatasourceToWorkspaceCommand: BackfillDatasourceToWorkspaceCommand,
protected readonly backfillPageLayoutsAndFieldsWidgetViewFieldsCommand: BackfillPageLayoutsAndFieldsWidgetViewFieldsCommand,
) {
super(
@@ -102,6 +104,7 @@ export class UpgradeCommand extends UpgradeCommandRunner {
];
const commands_1210: VersionCommands = [
this.backfillDatasourceToWorkspaceCommand,
this.backfillPageLayoutsAndFieldsWidgetViewFieldsCommand,
];