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
@@ -14,6 +14,9 @@ import { WorkspaceRelatedEntity } from 'src/engine/workspace-manager/types/works
export type DataSourceType = DataSourceOptions['type'];
// @deprecated - This entity is being deprecated in favor of storing
// databaseSchema directly on WorkspaceEntity.
// During the transition, writes go to both tables (dual-write).
@Entity('dataSource')
@Index('IDX_DATA_SOURCE_WORKSPACE_ID_CREATED_AT', ['workspaceId', 'createdAt'])
export class DataSourceEntity extends WorkspaceRelatedEntity {
@@ -1,11 +1,13 @@
import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { WorkspaceEntity } from 'src/engine/core-modules/workspace/workspace.entity';
import { DataSourceEntity } from './data-source.entity';
import { DataSourceService } from './data-source.service';
@Module({
imports: [TypeOrmModule.forFeature([DataSourceEntity])],
imports: [TypeOrmModule.forFeature([DataSourceEntity, WorkspaceEntity])],
providers: [DataSourceService],
exports: [DataSourceService],
})
@@ -3,6 +3,7 @@ import { InjectRepository } from '@nestjs/typeorm';
import { type FindManyOptions, Repository } from 'typeorm';
import { WorkspaceEntity } from 'src/engine/core-modules/workspace/workspace.entity';
import {
DataSourceException,
DataSourceExceptionCode,
@@ -10,22 +11,32 @@ import {
import { DataSourceEntity } from './data-source.entity';
// @deprecated - This service is being deprecated. During the transition,
// writes go to both the dataSource table and workspace table (dual-write).
// Reads should progressively migrate to use workspace.databaseSchema
// or the deterministic getWorkspaceSchemaName(workspaceId) utility.
@Injectable()
export class DataSourceService {
constructor(
@InjectRepository(DataSourceEntity)
private readonly dataSourceMetadataRepository: Repository<DataSourceEntity>,
@InjectRepository(WorkspaceEntity)
private readonly workspaceRepository: Repository<WorkspaceEntity>,
) {}
async createDataSourceMetadata(
workspaceId: string,
workspaceSchema: string,
): Promise<DataSourceEntity> {
// TODO: Double check if this is the correct way to do this
const dataSource = await this.dataSourceMetadataRepository.findOne({
where: { workspaceId },
});
// Dual-write: always keep workspace.databaseSchema in sync
await this.workspaceRepository.update(workspaceId, {
databaseSchema: workspaceSchema,
});
if (dataSource) {
return dataSource;
}
@@ -36,12 +47,16 @@ export class DataSourceService {
});
}
// @deprecated - Use workspace.activationStatus or workspace.databaseSchema
// to check if a workspace has been initialized instead.
async getManyDataSourceMetadata(
options: FindManyOptions<DataSourceEntity> = {},
): Promise<DataSourceEntity[]> {
return this.dataSourceMetadataRepository.find(options);
}
// @deprecated - Use workspace.databaseSchema or
// getWorkspaceSchemaName(workspaceId) instead.
async getDataSourcesMetadataFromWorkspaceId(
workspaceId: string,
): Promise<DataSourceEntity[]> {
@@ -51,6 +66,8 @@ export class DataSourceService {
});
}
// @deprecated - Use workspace.databaseSchema or
// getWorkspaceSchemaName(workspaceId) instead.
async getLastDataSourceMetadataFromWorkspaceId(
workspaceId: string,
): Promise<DataSourceEntity | null> {
@@ -60,6 +77,8 @@ export class DataSourceService {
});
}
// @deprecated - Use workspace.databaseSchema or
// getWorkspaceSchemaName(workspaceId) instead.
async getLastDataSourceMetadataFromWorkspaceIdOrFail(
workspaceId: string,
): Promise<DataSourceEntity> {
@@ -78,5 +97,10 @@ export class DataSourceService {
async delete(workspaceId: string): Promise<void> {
await this.dataSourceMetadataRepository.delete({ workspaceId });
// Dual-write: clear workspace.databaseSchema on delete
await this.workspaceRepository.update(workspaceId, {
databaseSchema: null,
});
}
}