Clear orphan search field metadata backfill tsVectorFieldMetadataId (#22353)
Instead of invariant throw in instance slow, auto recover by deleting orphan search field metadata as in the end they would just end up as dead metadata <!-- This is an auto-generated description by cubic. --> <a href="https://cubic.dev/pr/twentyhq/twenty/pull/22353?utm_source=github" target="_blank" rel="noopener noreferrer" data-no-image-dialog="true"><picture><source media="(prefers-color-scheme: dark)" srcset="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"><source media="(prefers-color-scheme: light)" srcset="https://www.cubic.dev/buttons/review-in-cubic-light.svg"><img alt="Review in cubic" src="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"></picture></a> <!-- End of auto-generated description by cubic. -->
This commit is contained in:
+21
-9
@@ -1,3 +1,5 @@
|
||||
import { Logger } from '@nestjs/common';
|
||||
|
||||
import { DataSource, QueryRunner } from 'typeorm';
|
||||
|
||||
import { RegisteredInstanceCommand } from 'src/engine/core-modules/upgrade/decorators/registered-instance-command.decorator';
|
||||
@@ -6,12 +8,18 @@ import { SlowInstanceCommand } from 'src/engine/core-modules/upgrade/interfaces/
|
||||
// Backfills the tsVectorFieldMetadataId FK by pointing every existing
|
||||
// searchFieldMetadata row at its object's system searchVector (TS_VECTOR) field,
|
||||
// then enforces NOT NULL. Runs after the fast command that adds the column (and makes
|
||||
// the FKs deferrable). Any row left without a resolvable searchVector
|
||||
// aborts the upgrade rather than silently relaxing the NOT NULL guarantee.
|
||||
// the FKs deferrable). Rows left without a resolvable searchVector are orphans: they
|
||||
// index into a TS_VECTOR column that no longer exists (e.g. the object's searchVector
|
||||
// field was deleted while these rows survived, since their FK only cascades on the
|
||||
// indexed field). They are dead data, so we delete them rather than abort the upgrade.
|
||||
@RegisteredInstanceCommand('2.18.0', 1810000003000, { type: 'slow' })
|
||||
export class BackfillTsVectorFieldMetadataIdOnSearchFieldMetadataSlowInstanceCommand
|
||||
implements SlowInstanceCommand
|
||||
{
|
||||
private readonly logger = new Logger(
|
||||
BackfillTsVectorFieldMetadataIdOnSearchFieldMetadataSlowInstanceCommand.name,
|
||||
);
|
||||
|
||||
async runDataMigration(dataSource: DataSource): Promise<void> {
|
||||
await dataSource.query(
|
||||
`UPDATE "core"."searchFieldMetadata" "searchFieldMetadata"
|
||||
@@ -23,16 +31,20 @@ export class BackfillTsVectorFieldMetadataIdOnSearchFieldMetadataSlowInstanceCom
|
||||
AND "searchFieldMetadata"."tsVectorFieldMetadataId" IS NULL`,
|
||||
);
|
||||
|
||||
const unresolvedRows: { count: string }[] = await dataSource.query(
|
||||
`SELECT COUNT(*) AS "count" FROM "core"."searchFieldMetadata"
|
||||
WHERE "tsVectorFieldMetadataId" IS NULL`,
|
||||
const deletedRows: { count: string }[] = await dataSource.query(
|
||||
`WITH "deleted" AS (
|
||||
DELETE FROM "core"."searchFieldMetadata"
|
||||
WHERE "tsVectorFieldMetadataId" IS NULL
|
||||
RETURNING "id"
|
||||
)
|
||||
SELECT COUNT(*) AS "count" FROM "deleted"`,
|
||||
);
|
||||
|
||||
const unresolvedCount = Number(unresolvedRows[0]?.count ?? 0);
|
||||
const deletedCount = Number(deletedRows[0]?.count ?? 0);
|
||||
|
||||
if (unresolvedCount > 0) {
|
||||
throw new Error(
|
||||
`Cannot enforce searchFieldMetadata.tsVectorFieldMetadataId NOT NULL: ${unresolvedCount} row(s) reference an object without a searchVector (TS_VECTOR) field`,
|
||||
if (deletedCount > 0) {
|
||||
this.logger.warn(
|
||||
`Deleted ${deletedCount} orphaned searchFieldMetadata row(s) referencing an object without a searchVector (TS_VECTOR) field`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user