Deprecate dataSource table with dual-write to workspace.databaseSchema (#19059)

## Summary
- Starts deprecation of the `core.dataSource` table by introducing a
dual-write system: `DataSourceService.createDataSourceMetadata` now
writes to both `core.dataSource` and `core.workspace.databaseSchema`
- Migrates read sites (`WorkspaceDataSourceService.checkSchemaExists`,
`WorkspaceSchemaFactory`, `MiddlewareService`,
`WorkspacesMigrationCommandRunner`) to read from
`workspace.databaseSchema` instead of querying the `dataSource` table
- Removes the unused `databaseUrl` field from `WorkspaceEntity` and
drops the column via migration
- Adds a 1.20 upgrade command to backfill `workspace.databaseSchema`
from `dataSource.schema` for existing workspaces
This commit is contained in:
Charles Bochet
2026-03-28 11:29:19 +01:00
committed by GitHub
parent cb44b22e15
commit 81fc960712
18 changed files with 161 additions and 42 deletions
@@ -1,12 +1,20 @@
import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { TypeORMModule } from 'src/database/typeorm/typeorm.module';
import { FeatureFlagModule } from 'src/engine/core-modules/feature-flag/feature-flag.module';
import { WorkspaceEntity } from 'src/engine/core-modules/workspace/workspace.entity';
import { DataSourceModule } from 'src/engine/metadata-modules/data-source/data-source.module';
import { WorkspaceDataSourceService } from './workspace-datasource.service';
@Module({
imports: [DataSourceModule, TypeORMModule],
imports: [
TypeOrmModule.forFeature([WorkspaceEntity]),
TypeORMModule,
FeatureFlagModule,
DataSourceModule,
],
exports: [WorkspaceDataSourceService],
providers: [WorkspaceDataSourceService],
})
@@ -1,9 +1,13 @@
import { Injectable } from '@nestjs/common';
import { InjectDataSource } from '@nestjs/typeorm';
import { InjectDataSource, InjectRepository } from '@nestjs/typeorm';
import { msg } from '@lingui/core/macro';
import { type DataSource, type EntityManager } from 'typeorm';
import { isNonEmptyString } from '@sniptt/guards';
import { FeatureFlagKey } from 'twenty-shared/types';
import { type DataSource, type EntityManager, Repository } from 'typeorm';
import { FeatureFlagService } from 'src/engine/core-modules/feature-flag/services/feature-flag.service';
import { WorkspaceEntity } from 'src/engine/core-modules/workspace/workspace.entity';
import { DataSourceService } from 'src/engine/metadata-modules/data-source/data-source.service';
import {
PermissionsException,
@@ -14,18 +18,35 @@ import { getWorkspaceSchemaName } from 'src/engine/workspace-datasource/utils/ge
@Injectable()
export class WorkspaceDataSourceService {
constructor(
private readonly dataSourceService: DataSourceService,
@InjectRepository(WorkspaceEntity)
private readonly workspaceRepository: Repository<WorkspaceEntity>,
@InjectDataSource()
private readonly coreDataSource: DataSource,
private readonly featureFlagService: FeatureFlagService,
private readonly dataSourceService: DataSourceService,
) {}
public async checkSchemaExists(workspaceId: string) {
const dataSource =
const isDataSourceMigrated = await this.featureFlagService.isFeatureEnabled(
FeatureFlagKey.IS_DATASOURCE_MIGRATED,
workspaceId,
);
if (isDataSourceMigrated) {
const workspace = await this.workspaceRepository.findOne({
select: ['databaseSchema'],
where: { id: workspaceId },
});
return isNonEmptyString(workspace?.databaseSchema);
}
const dataSources =
await this.dataSourceService.getDataSourcesMetadataFromWorkspaceId(
workspaceId,
);
return dataSource.length > 0;
return dataSources.length > 0;
}
/**