diff --git a/foundations/core/packages/model/src/migration.ts b/foundations/core/packages/model/src/migration.ts index c82b8c2eaf5..9c19a97a60b 100644 --- a/foundations/core/packages/model/src/migration.ts +++ b/foundations/core/packages/model/src/migration.ts @@ -108,6 +108,8 @@ export interface MigrationClient { create: (domain: Domain, doc: T | T[]) => Promise delete: (domain: Domain, _id: Ref) => Promise deleteMany: (domain: Domain, query: DocumentQuery) => Promise + // Check the physical storage domain, including legacy domains no longer present in the active model. + domainExists: (domain: Domain) => Promise hierarchy: Hierarchy model: ModelDb diff --git a/foundations/server/packages/postgres/src/utils.ts b/foundations/server/packages/postgres/src/utils.ts index 469904e3a93..3c0df2258a9 100644 --- a/foundations/server/packages/postgres/src/utils.ts +++ b/foundations/server/packages/postgres/src/utils.ts @@ -325,12 +325,39 @@ export class DBCollectionHelper implements DomainHelperOperations { async create (domain: Domain): Promise {} async exists (domain: Domain): Promise { - // Always exists. We don't need to check for index existence - return true + // Migrations can reference legacy domains that were removed from the current model, + // so this must check the physical table instead of the initialized model-domain set. + const tableName = translateDomain(domain) + const res = await this.client.execute( + ` + SELECT 1 + FROM information_schema.tables + WHERE table_schema = 'public' + AND table_name = $1 + LIMIT 1 + `, + [tableName] + ) + return res.length > 0 } async listDomains (): Promise> { - return this.domains + const rows = await this.client.execute(` + SELECT table_name + FROM information_schema.tables + WHERE table_schema = 'public' + AND table_name NOT LIKE 'pg_%' + AND table_name NOT LIKE 'cluster_%' + AND table_name NOT LIKE 'kv_%' + AND table_name NOT LIKE 'node_%' + `) + const tableNames = new Set(rows.map((it) => it.table_name as Domain)) + for (const domain of this.domains) { + if (tableNames.has(translateDomain(domain) as Domain)) { + tableNames.add(domain) + } + } + return tableNames } async createIndex (domain: Domain, value: string | FieldIndexConfig, options?: { name: string }): Promise {} diff --git a/models/chunter/src/migration.ts b/models/chunter/src/migration.ts index abfa2b41395..87ed38f030c 100644 --- a/models/chunter/src/migration.ts +++ b/models/chunter/src/migration.ts @@ -156,16 +156,20 @@ export async function createRandom (client: MigrationUpgradeClient, tx: TxOperat } async function convertCommentsToChatMessages (client: MigrationClient): Promise { - await client.update( - DOMAIN_COMMENT, - { _class: 'chunter:class:Comment' as Ref> }, - { _class: chunter.class.ChatMessage } - ) - await client.move(DOMAIN_COMMENT, { _class: chunter.class.ChatMessage }, DOMAIN_ACTIVITY) + if (await client.domainExists(DOMAIN_COMMENT)) { + await client.update( + DOMAIN_COMMENT, + { _class: 'chunter:class:Comment' as Ref> }, + { _class: chunter.class.ChatMessage } + ) + await client.move(DOMAIN_COMMENT, { _class: chunter.class.ChatMessage }, DOMAIN_ACTIVITY) + } } async function removeBacklinks (client: MigrationClient): Promise { - await client.deleteMany(DOMAIN_COMMENT, { _class: 'chunter:class:Backlink' as Ref> }) + if (await client.domainExists(DOMAIN_COMMENT)) { + await client.deleteMany(DOMAIN_COMMENT, { _class: 'chunter:class:Backlink' as Ref> }) + } await client.deleteMany(DOMAIN_ACTIVITY, { _class: activity.class.DocUpdateMessage, objectClass: 'chunter:class:Backlink' as Ref> diff --git a/server/tool/src/upgrade.ts b/server/tool/src/upgrade.ts index 4792ef47cb0..21a8942e820 100644 --- a/server/tool/src/upgrade.ts +++ b/server/tool/src/upgrade.ts @@ -127,6 +127,12 @@ export class MigrateClientImpl implements MigrationClient { await this.lowLevel.rawDeleteMany(domain, query) } + async domainExists (domain: Domain): Promise { + const adapter = this.pipeline.context.adapterManager?.getAdapter(domain, false) + const helper = adapter?.helper?.() + return helper !== undefined ? await helper.exists(domain) : true + } + async fullReindex (): Promise { await this.queue.send(this.ctx, this.wsIds.uuid, [workspaceEvents.fullReindex()]) }