mirror of
https://github.com/hcengineering/platform.git
synced 2026-09-30 21:45:01 +02:00
fix(chunter): skip legacy comment migration when domain is absent (#10891)
Allow old Chunter migrations to complete cleanly for workspaces that no longer have the legacy `comment` storage domain. The migration still runs normally when the domain exists, but workspaces created or restored after the comment-to-activity migration can now skip the obsolete table operations and record the migration state. Changes: - Add a migration-client `domainExists` helper backed by the active adapter. - Make the Postgres domain helper check actual public tables instead of assuming every domain exists. - Keep Postgres domain listing compatible with model domain names while including translated table names. - Guard legacy `comment` update/move/delete operations in the Chunter migration. Validation: - git diff --cached --check - node common/scripts/install-run-rush.js build -t @hcengineering/model -t @hcengineering/server-tool -t @hcengineering/postgres -t @hcengineering/model-chunter Signed-off-by: Ignat Remizov <ignat@ignatremizov.com>
This commit is contained in:
@@ -108,6 +108,8 @@ export interface MigrationClient {
|
||||
create: <T extends Doc>(domain: Domain, doc: T | T[]) => Promise<void>
|
||||
delete: <T extends Doc>(domain: Domain, _id: Ref<T>) => Promise<void>
|
||||
deleteMany: <T extends Doc>(domain: Domain, query: DocumentQuery<T>) => Promise<void>
|
||||
// Check the physical storage domain, including legacy domains no longer present in the active model.
|
||||
domainExists: (domain: Domain) => Promise<boolean>
|
||||
|
||||
hierarchy: Hierarchy
|
||||
model: ModelDb
|
||||
|
||||
@@ -325,12 +325,39 @@ export class DBCollectionHelper implements DomainHelperOperations {
|
||||
async create (domain: Domain): Promise<void> {}
|
||||
|
||||
async exists (domain: Domain): Promise<boolean> {
|
||||
// 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<Set<Domain>> {
|
||||
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<Doc>, options?: { name: string }): Promise<void> {}
|
||||
|
||||
@@ -156,16 +156,20 @@ export async function createRandom (client: MigrationUpgradeClient, tx: TxOperat
|
||||
}
|
||||
|
||||
async function convertCommentsToChatMessages (client: MigrationClient): Promise<void> {
|
||||
await client.update(
|
||||
DOMAIN_COMMENT,
|
||||
{ _class: 'chunter:class:Comment' as Ref<Class<Doc>> },
|
||||
{ _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<Doc>> },
|
||||
{ _class: chunter.class.ChatMessage }
|
||||
)
|
||||
await client.move(DOMAIN_COMMENT, { _class: chunter.class.ChatMessage }, DOMAIN_ACTIVITY)
|
||||
}
|
||||
}
|
||||
|
||||
async function removeBacklinks (client: MigrationClient): Promise<void> {
|
||||
await client.deleteMany(DOMAIN_COMMENT, { _class: 'chunter:class:Backlink' as Ref<Class<Doc>> })
|
||||
if (await client.domainExists(DOMAIN_COMMENT)) {
|
||||
await client.deleteMany(DOMAIN_COMMENT, { _class: 'chunter:class:Backlink' as Ref<Class<Doc>> })
|
||||
}
|
||||
await client.deleteMany(DOMAIN_ACTIVITY, {
|
||||
_class: activity.class.DocUpdateMessage,
|
||||
objectClass: 'chunter:class:Backlink' as Ref<Class<Doc>>
|
||||
|
||||
@@ -127,6 +127,12 @@ export class MigrateClientImpl implements MigrationClient {
|
||||
await this.lowLevel.rawDeleteMany(domain, query)
|
||||
}
|
||||
|
||||
async domainExists (domain: Domain): Promise<boolean> {
|
||||
const adapter = this.pipeline.context.adapterManager?.getAdapter(domain, false)
|
||||
const helper = adapter?.helper?.()
|
||||
return helper !== undefined ? await helper.exists(domain) : true
|
||||
}
|
||||
|
||||
async fullReindex (): Promise<void> {
|
||||
await this.queue.send(this.ctx, this.wsIds.uuid, [workspaceEvents.fullReindex()])
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user