mirror of
https://github.com/langgenius/dify.git
synced 2026-09-08 11:04:27 +08:00
fix(knowledge-fs): allow zero-revision logical document deletion
This commit is contained in:
parent
fd8e4a80cd
commit
b88a8933ed
@ -0,0 +1,37 @@
|
||||
# Logical Document Zero-Revision Deletion
|
||||
|
||||
## What Changed
|
||||
|
||||
- Relaxed the durable deletion job and tombstone constraints so only `logical_document` targets may use revision `0`.
|
||||
- Added PostgreSQL and TiDB migration `0037_logical_document_zero_revision_deletion` and refreshed the checked-in migration artifacts.
|
||||
- Updated database, adapter migration-runner, and API migration-command tests to include migration `0037`.
|
||||
|
||||
## Why
|
||||
|
||||
A logical document begins at row version `0` and may fail before its first revision becomes active. The deletion repository already accepts that compare-and-swap value, but the persisted deletion job and tombstone constraints previously required every target revision to be at least `1`.
|
||||
|
||||
## TDD Notes
|
||||
|
||||
- RED: The new artifact exposed stale adapter and API migration-runner expectations, producing six adapter test failures and one API app test failure.
|
||||
- GREEN: Added `0037` to both runner test baselines and updated the expected migration count and SQL sequence.
|
||||
- REFACTOR: Kept the shared migration-id fixture structure and changed no runtime runner behavior.
|
||||
|
||||
## Performance Notes
|
||||
|
||||
- The change only replaces two existing check constraints and adds no queries, round trips, indexes, or unbounded data paths.
|
||||
|
||||
## Verification
|
||||
|
||||
- `pnpm --filter @knowledge/database test`: passed (110 tests).
|
||||
- `pnpm --filter @knowledge/adapters test -- src/migration-runner.test.ts`: passed (105 tests).
|
||||
- `pnpm --filter @knowledge/api-app test -- src/migrate.test.ts`: passed (210 tests).
|
||||
- `pnpm typecheck`: passed.
|
||||
- `pnpm build`: passed.
|
||||
- `pnpm exec biome check apps/api/src/migrate.test.ts packages/adapters/src/migration-runner.test.ts packages/database/src/schema.ts packages/database/src/migration-file.test.ts packages/database/src/migration-artifacts.generated.ts`: passed.
|
||||
- `pnpm db:migrations:check`: passed.
|
||||
- `git diff --check`: passed.
|
||||
|
||||
## Known Risks / Follow-Up
|
||||
|
||||
- The migration was verified through deterministic artifact and runner tests, not against live PostgreSQL or TiDB instances.
|
||||
- Full-repository `pnpm lint` remains blocked by ten pre-existing formatting/size findings in unrelated admin, OpenAPI, contract, and test-support files; all files changed in this slice pass targeted Biome checks.
|
||||
@ -76,8 +76,9 @@ describe("runApiDatabaseMigrations", () => {
|
||||
"0034_knowledge_space_emoji_icons",
|
||||
"0035_research_task_answer_streaming",
|
||||
"0036_page_index_findability",
|
||||
"0037_logical_document_zero_revision_deletion",
|
||||
],
|
||||
pendingBeforeRun: 36,
|
||||
pendingBeforeRun: 37,
|
||||
});
|
||||
expect(operations).toEqual([
|
||||
"schema",
|
||||
@ -155,8 +156,10 @@ describe("runApiDatabaseMigrations", () => {
|
||||
"insert",
|
||||
"schema",
|
||||
"insert",
|
||||
"schema",
|
||||
"insert",
|
||||
]);
|
||||
expect(migrationSql).toHaveLength(36);
|
||||
expect(migrationSql).toHaveLength(37);
|
||||
expect(migrationSql[2]).toContain("-- Migration id: 0003_projection_set_publications\n");
|
||||
expect(migrationSql[2]).toContain("-- Dialect: postgres\n");
|
||||
expect(migrationSql[2]).toContain('CREATE TABLE IF NOT EXISTS "projection_set_publications"');
|
||||
@ -204,6 +207,9 @@ describe("runApiDatabaseMigrations", () => {
|
||||
expect(migrationSql[33]).toContain("-- Migration id: 0034_knowledge_space_emoji_icons\n");
|
||||
expect(migrationSql[34]).toContain("-- Migration id: 0035_research_task_answer_streaming\n");
|
||||
expect(migrationSql[35]).toContain("-- Migration id: 0036_page_index_findability\n");
|
||||
expect(migrationSql[36]).toContain(
|
||||
"-- Migration id: 0037_logical_document_zero_revision_deletion\n",
|
||||
);
|
||||
expect(closed).toBe(true);
|
||||
});
|
||||
|
||||
|
||||
@ -43,6 +43,8 @@ const researchTaskFinalAnswersMigrationId = "0033_research_task_final_answers";
|
||||
const knowledgeSpaceEmojiIconsMigrationId = "0034_knowledge_space_emoji_icons";
|
||||
const researchTaskAnswerStreamingMigrationId = "0035_research_task_answer_streaming";
|
||||
const pageIndexFindabilityMigrationId = "0036_page_index_findability";
|
||||
const logicalDocumentZeroRevisionDeletionMigrationId =
|
||||
"0037_logical_document_zero_revision_deletion";
|
||||
const migrationsAfterDurableDeletion = [
|
||||
versionedSpaceProfilesMigrationId,
|
||||
profilePublicationBindingsMigrationId,
|
||||
@ -63,6 +65,7 @@ const migrationsAfterDurableDeletion = [
|
||||
knowledgeSpaceEmojiIconsMigrationId,
|
||||
researchTaskAnswerStreamingMigrationId,
|
||||
pageIndexFindabilityMigrationId,
|
||||
logicalDocumentZeroRevisionDeletionMigrationId,
|
||||
] as const;
|
||||
const migrationsAfterTidbBaselineRepair = [
|
||||
spaceAccessControlMigrationId,
|
||||
|
||||
@ -0,0 +1,32 @@
|
||||
-- Knowledge Platform schema migration
|
||||
-- Migration id: 0037_logical_document_zero_revision_deletion
|
||||
-- Dialect: postgres
|
||||
|
||||
-- A logical document starts at row_version 0 and can fail before its first revision is activated.
|
||||
-- Durable deletion already accepts that CAS value; keep other target revisions strictly positive.
|
||||
ALTER TABLE "deletion_jobs"
|
||||
DROP CONSTRAINT IF EXISTS "deletion_jobs_positive_ck";
|
||||
ALTER TABLE "deletion_jobs"
|
||||
ADD CONSTRAINT "deletion_jobs_positive_ck" CHECK (
|
||||
(
|
||||
("target_type" = 'logical_document' AND "target_revision" >= 0)
|
||||
OR ("target_type" <> 'logical_document' AND "target_revision" >= 1)
|
||||
)
|
||||
AND ("capability_grant_id" IS NOT NULL OR "permission_snapshot_revision" >= 1)
|
||||
AND "row_version" >= 1
|
||||
AND "execution_attempts" >= 0
|
||||
AND "max_execution_attempts" >= 1
|
||||
AND "execution_attempts" <= "max_execution_attempts"
|
||||
AND ("active_slot" IS NULL OR "active_slot" = 1)
|
||||
);
|
||||
|
||||
ALTER TABLE "deletion_tombstones"
|
||||
DROP CONSTRAINT IF EXISTS "deletion_tombstones_positive_ck";
|
||||
ALTER TABLE "deletion_tombstones"
|
||||
ADD CONSTRAINT "deletion_tombstones_positive_ck" CHECK (
|
||||
(
|
||||
("target_type" = 'logical_document' AND "target_revision" >= 0)
|
||||
OR ("target_type" <> 'logical_document' AND "target_revision" >= 1)
|
||||
)
|
||||
AND "row_version" >= 1
|
||||
);
|
||||
@ -0,0 +1,71 @@
|
||||
-- Knowledge Platform schema migration
|
||||
-- Migration id: 0037_logical_document_zero_revision_deletion
|
||||
-- Dialect: tidb
|
||||
|
||||
-- A logical document starts at row_version 0 and can fail before its first revision is activated.
|
||||
-- Durable deletion already accepts that CAS value; keep other target revisions strictly positive.
|
||||
-- TiDB DDL may commit before the migration marker. Guard each independent schema change so a
|
||||
-- crash after either DROP can resume with the ADD, and marker loss after both ADDs can replay.
|
||||
SET @kfs_0037_deletion_jobs_positive_sql = IF(
|
||||
EXISTS(
|
||||
SELECT 1
|
||||
FROM information_schema.tidb_check_constraints
|
||||
WHERE constraint_schema = DATABASE()
|
||||
AND table_name = 'deletion_jobs'
|
||||
AND constraint_name = 'deletion_jobs_positive_ck'
|
||||
),
|
||||
'ALTER TABLE `deletion_jobs` DROP CHECK `deletion_jobs_positive_ck`',
|
||||
'DO 0'
|
||||
);
|
||||
PREPARE kfs_0037_deletion_jobs_positive_stmt
|
||||
FROM @kfs_0037_deletion_jobs_positive_sql;
|
||||
EXECUTE kfs_0037_deletion_jobs_positive_stmt;
|
||||
DEALLOCATE PREPARE kfs_0037_deletion_jobs_positive_stmt;
|
||||
|
||||
SET @kfs_0037_deletion_jobs_positive_sql = IF(
|
||||
EXISTS(
|
||||
SELECT 1
|
||||
FROM information_schema.tidb_check_constraints
|
||||
WHERE constraint_schema = DATABASE()
|
||||
AND table_name = 'deletion_jobs'
|
||||
AND constraint_name = 'deletion_jobs_positive_ck'
|
||||
),
|
||||
'DO 0',
|
||||
'ALTER TABLE `deletion_jobs` ADD CONSTRAINT `deletion_jobs_positive_ck` CHECK (((`target_type` = ''logical_document'' AND `target_revision` >= 0) OR (`target_type` <> ''logical_document'' AND `target_revision` >= 1)) AND (`capability_grant_id` IS NOT NULL OR `permission_snapshot_revision` >= 1) AND `row_version` >= 1 AND `execution_attempts` >= 0 AND `max_execution_attempts` >= 1 AND `execution_attempts` <= `max_execution_attempts` AND (`active_slot` IS NULL OR `active_slot` = 1))'
|
||||
);
|
||||
PREPARE kfs_0037_deletion_jobs_positive_stmt
|
||||
FROM @kfs_0037_deletion_jobs_positive_sql;
|
||||
EXECUTE kfs_0037_deletion_jobs_positive_stmt;
|
||||
DEALLOCATE PREPARE kfs_0037_deletion_jobs_positive_stmt;
|
||||
|
||||
SET @kfs_0037_deletion_tombstones_positive_sql = IF(
|
||||
EXISTS(
|
||||
SELECT 1
|
||||
FROM information_schema.tidb_check_constraints
|
||||
WHERE constraint_schema = DATABASE()
|
||||
AND table_name = 'deletion_tombstones'
|
||||
AND constraint_name = 'deletion_tombstones_positive_ck'
|
||||
),
|
||||
'ALTER TABLE `deletion_tombstones` DROP CHECK `deletion_tombstones_positive_ck`',
|
||||
'DO 0'
|
||||
);
|
||||
PREPARE kfs_0037_deletion_tombstones_positive_stmt
|
||||
FROM @kfs_0037_deletion_tombstones_positive_sql;
|
||||
EXECUTE kfs_0037_deletion_tombstones_positive_stmt;
|
||||
DEALLOCATE PREPARE kfs_0037_deletion_tombstones_positive_stmt;
|
||||
|
||||
SET @kfs_0037_deletion_tombstones_positive_sql = IF(
|
||||
EXISTS(
|
||||
SELECT 1
|
||||
FROM information_schema.tidb_check_constraints
|
||||
WHERE constraint_schema = DATABASE()
|
||||
AND table_name = 'deletion_tombstones'
|
||||
AND constraint_name = 'deletion_tombstones_positive_ck'
|
||||
),
|
||||
'DO 0',
|
||||
'ALTER TABLE `deletion_tombstones` ADD CONSTRAINT `deletion_tombstones_positive_ck` CHECK (((`target_type` = ''logical_document'' AND `target_revision` >= 0) OR (`target_type` <> ''logical_document'' AND `target_revision` >= 1)) AND `row_version` >= 1)'
|
||||
);
|
||||
PREPARE kfs_0037_deletion_tombstones_positive_stmt
|
||||
FROM @kfs_0037_deletion_tombstones_positive_sql;
|
||||
EXECUTE kfs_0037_deletion_tombstones_positive_stmt;
|
||||
DEALLOCATE PREPARE kfs_0037_deletion_tombstones_positive_stmt;
|
||||
@ -75,4 +75,6 @@ export const migrationArtifacts = [
|
||||
{ content: "-- Knowledge Platform schema migration\n-- Migration id: 0035_research_task_answer_streaming\n-- Dialect: tidb\n-- Allows durable Research progress streams to carry bounded, batched answer deltas.\n-- TiDB DDL may commit before the migration marker, so both replacement steps are replay safe.\n\nSET @kfs_0035_progress_event_constraint_sql = IF(\n EXISTS(\n SELECT 1\n FROM information_schema.tidb_check_constraints\n WHERE constraint_schema = DATABASE()\n AND table_name = 'research_task_progress_events'\n AND constraint_name = 'research_task_progress_event_ck'\n ),\n 'ALTER TABLE `research_task_progress_events` DROP CHECK `research_task_progress_event_ck`',\n 'DO 0'\n);\nPREPARE kfs_0035_progress_event_constraint_stmt\n FROM @kfs_0035_progress_event_constraint_sql;\nEXECUTE kfs_0035_progress_event_constraint_stmt;\nDEALLOCATE PREPARE kfs_0035_progress_event_constraint_stmt;\n\nSET @kfs_0035_progress_event_constraint_sql = IF(\n EXISTS(\n SELECT 1\n FROM information_schema.tidb_check_constraints\n WHERE constraint_schema = DATABASE()\n AND table_name = 'research_task_progress_events'\n AND constraint_name = 'research_task_progress_event_ck'\n ),\n 'DO 0',\n 'ALTER TABLE `research_task_progress_events` ADD CONSTRAINT `research_task_progress_event_ck` CHECK (`event_type` IN (''research_task.answer_delta'', ''research_task.canceled'', ''research_task.failed'', ''research_task.paused'', ''research_task.resumed'', ''research_task.stage_changed'', ''research_task.started''))'\n);\nPREPARE kfs_0035_progress_event_constraint_stmt\n FROM @kfs_0035_progress_event_constraint_sql;\nEXECUTE kfs_0035_progress_event_constraint_stmt;\nDEALLOCATE PREPARE kfs_0035_progress_event_constraint_stmt;\n", path: "packages/database/migrations/0035_research_task_answer_streaming.tidb.sql" },
|
||||
{ content: "-- Knowledge Platform schema migration\n-- Migration id: 0036_page_index_findability\n-- Dialect: postgres\n-- Persists generation-scoped PageIndex navigation quality and a bounded summary-repair request.\n\nCREATE TABLE IF NOT EXISTS \"page_index_findability_evaluations\" (\n \"id\" UUID PRIMARY KEY NOT NULL,\n \"tenant_id\" VARCHAR(255) NOT NULL,\n \"knowledge_space_id\" UUID NOT NULL,\n \"document_asset_id\" UUID NOT NULL,\n \"document_version\" BIGINT NOT NULL,\n \"outline_id\" UUID NOT NULL,\n \"publication_generation_id\" UUID NOT NULL,\n \"publication_fingerprint\" VARCHAR(96) NOT NULL,\n \"compilation_attempt_id\" UUID NOT NULL,\n \"evaluator_version\" VARCHAR(128) NOT NULL,\n \"status\" VARCHAR(32) NOT NULL,\n \"recommended_route\" VARCHAR(16) NOT NULL,\n \"evaluation\" JSONB NOT NULL,\n \"summary_repair_state\" VARCHAR(24) NOT NULL,\n \"summary_repair_attempts\" INTEGER NOT NULL DEFAULT 0,\n \"summary_repair_error\" VARCHAR(2000),\n \"available_at\" TIMESTAMPTZ,\n \"lock_token\" UUID,\n \"locked_by\" VARCHAR(255),\n \"lease_expires_at\" TIMESTAMPTZ,\n \"evaluated_at\" TIMESTAMPTZ NOT NULL,\n \"updated_at\" TIMESTAMPTZ NOT NULL,\n CONSTRAINT \"page_index_findability_scope_fk\"\n FOREIGN KEY (\"tenant_id\", \"knowledge_space_id\")\n REFERENCES \"knowledge_spaces\" (\"tenant_id\", \"id\") ON DELETE CASCADE,\n CONSTRAINT \"page_index_findability_status_ck\"\n CHECK (\"status\" IN ('failed', 'not-evaluated', 'passed')),\n CONSTRAINT \"page_index_findability_route_ck\"\n CHECK (\"recommended_route\" IN ('hybrid', 'layered', 'unchanged')),\n CONSTRAINT \"page_index_findability_repair_ck\"\n CHECK (\n \"summary_repair_state\" IN ('not-requested', 'queued', 'leased', 'dispatched', 'failed')\n AND \"summary_repair_attempts\" >= 0\n AND (\n (\"summary_repair_state\" = 'leased'\n AND \"lock_token\" IS NOT NULL\n AND \"locked_by\" IS NOT NULL\n AND \"lease_expires_at\" IS NOT NULL)\n OR\n (\"summary_repair_state\" <> 'leased'\n AND \"lock_token\" IS NULL\n AND \"locked_by\" IS NULL\n AND \"lease_expires_at\" IS NULL)\n )\n ),\n CONSTRAINT \"page_index_findability_evaluation_ck\"\n CHECK (jsonb_typeof(\"evaluation\") = 'object'),\n CONSTRAINT \"page_index_findability_document_version_ck\"\n CHECK (\"document_version\" >= 1)\n);\n\nCREATE UNIQUE INDEX IF NOT EXISTS \"page_index_findability_generation_evaluator_uq\"\n ON \"page_index_findability_evaluations\" (\n \"tenant_id\", \"knowledge_space_id\", \"publication_generation_id\", \"evaluator_version\"\n );\nCREATE INDEX IF NOT EXISTS \"page_index_findability_route_idx\"\n ON \"page_index_findability_evaluations\" (\n \"tenant_id\", \"knowledge_space_id\", \"document_asset_id\", \"publication_generation_id\"\n );\nCREATE INDEX IF NOT EXISTS \"page_index_findability_repair_queue_idx\"\n ON \"page_index_findability_evaluations\" (\n \"summary_repair_state\", \"available_at\", \"lease_expires_at\", \"updated_at\", \"id\"\n );\n", path: "packages/database/migrations/0036_page_index_findability.postgres.sql" },
|
||||
{ content: "-- Knowledge Platform schema migration\n-- Migration id: 0036_page_index_findability\n-- Dialect: tidb\n-- Persists generation-scoped PageIndex navigation quality and a bounded summary-repair request.\n\nCREATE TABLE IF NOT EXISTS `page_index_findability_evaluations` (\n `id` CHAR(36) PRIMARY KEY NOT NULL,\n `tenant_id` VARCHAR(255) NOT NULL,\n `knowledge_space_id` CHAR(36) NOT NULL,\n `document_asset_id` CHAR(36) NOT NULL,\n `document_version` BIGINT NOT NULL,\n `outline_id` CHAR(36) NOT NULL,\n `publication_generation_id` CHAR(36) NOT NULL,\n `publication_fingerprint` VARCHAR(96) NOT NULL,\n `compilation_attempt_id` CHAR(36) NOT NULL,\n `evaluator_version` VARCHAR(128) NOT NULL,\n `status` VARCHAR(32) NOT NULL,\n `recommended_route` VARCHAR(16) NOT NULL,\n `evaluation` JSON NOT NULL,\n `summary_repair_state` VARCHAR(24) NOT NULL,\n `summary_repair_attempts` INT NOT NULL DEFAULT 0,\n `summary_repair_error` VARCHAR(2000),\n `available_at` DATETIME(3),\n `lock_token` CHAR(36),\n `locked_by` VARCHAR(255),\n `lease_expires_at` DATETIME(3),\n `evaluated_at` DATETIME(3) NOT NULL,\n `updated_at` DATETIME(3) NOT NULL,\n CONSTRAINT `page_index_findability_scope_fk`\n FOREIGN KEY (`tenant_id`, `knowledge_space_id`)\n REFERENCES `knowledge_spaces` (`tenant_id`, `id`) ON DELETE CASCADE,\n CONSTRAINT `page_index_findability_status_ck`\n CHECK (`status` IN ('failed', 'not-evaluated', 'passed')),\n CONSTRAINT `page_index_findability_route_ck`\n CHECK (`recommended_route` IN ('hybrid', 'layered', 'unchanged')),\n CONSTRAINT `page_index_findability_repair_ck`\n CHECK (\n `summary_repair_state` IN ('not-requested', 'queued', 'leased', 'dispatched', 'failed')\n AND `summary_repair_attempts` >= 0\n AND (\n (`summary_repair_state` = 'leased'\n AND `lock_token` IS NOT NULL\n AND `locked_by` IS NOT NULL\n AND `lease_expires_at` IS NOT NULL)\n OR\n (`summary_repair_state` <> 'leased'\n AND `lock_token` IS NULL\n AND `locked_by` IS NULL\n AND `lease_expires_at` IS NULL)\n )\n ),\n CONSTRAINT `page_index_findability_evaluation_ck`\n CHECK (JSON_TYPE(`evaluation`) = 'OBJECT'),\n CONSTRAINT `page_index_findability_document_version_ck`\n CHECK (`document_version` >= 1)\n);\n\nCREATE UNIQUE INDEX IF NOT EXISTS `page_index_findability_generation_evaluator_uq`\n ON `page_index_findability_evaluations` (\n `tenant_id`, `knowledge_space_id`, `publication_generation_id`, `evaluator_version`\n );\nCREATE INDEX IF NOT EXISTS `page_index_findability_route_idx`\n ON `page_index_findability_evaluations` (\n `tenant_id`, `knowledge_space_id`, `document_asset_id`, `publication_generation_id`\n );\nCREATE INDEX IF NOT EXISTS `page_index_findability_repair_queue_idx`\n ON `page_index_findability_evaluations` (\n `summary_repair_state`, `available_at`, `lease_expires_at`, `updated_at`, `id`\n );\n", path: "packages/database/migrations/0036_page_index_findability.tidb.sql" },
|
||||
{ content: "-- Knowledge Platform schema migration\n-- Migration id: 0037_logical_document_zero_revision_deletion\n-- Dialect: postgres\n\n-- A logical document starts at row_version 0 and can fail before its first revision is activated.\n-- Durable deletion already accepts that CAS value; keep other target revisions strictly positive.\nALTER TABLE \"deletion_jobs\"\n DROP CONSTRAINT IF EXISTS \"deletion_jobs_positive_ck\";\nALTER TABLE \"deletion_jobs\"\n ADD CONSTRAINT \"deletion_jobs_positive_ck\" CHECK (\n (\n (\"target_type\" = 'logical_document' AND \"target_revision\" >= 0)\n OR (\"target_type\" <> 'logical_document' AND \"target_revision\" >= 1)\n )\n AND (\"capability_grant_id\" IS NOT NULL OR \"permission_snapshot_revision\" >= 1)\n AND \"row_version\" >= 1\n AND \"execution_attempts\" >= 0\n AND \"max_execution_attempts\" >= 1\n AND \"execution_attempts\" <= \"max_execution_attempts\"\n AND (\"active_slot\" IS NULL OR \"active_slot\" = 1)\n );\n\nALTER TABLE \"deletion_tombstones\"\n DROP CONSTRAINT IF EXISTS \"deletion_tombstones_positive_ck\";\nALTER TABLE \"deletion_tombstones\"\n ADD CONSTRAINT \"deletion_tombstones_positive_ck\" CHECK (\n (\n (\"target_type\" = 'logical_document' AND \"target_revision\" >= 0)\n OR (\"target_type\" <> 'logical_document' AND \"target_revision\" >= 1)\n )\n AND \"row_version\" >= 1\n );\n", path: "packages/database/migrations/0037_logical_document_zero_revision_deletion.postgres.sql" },
|
||||
{ content: "-- Knowledge Platform schema migration\n-- Migration id: 0037_logical_document_zero_revision_deletion\n-- Dialect: tidb\n\n-- A logical document starts at row_version 0 and can fail before its first revision is activated.\n-- Durable deletion already accepts that CAS value; keep other target revisions strictly positive.\n-- TiDB DDL may commit before the migration marker. Guard each independent schema change so a\n-- crash after either DROP can resume with the ADD, and marker loss after both ADDs can replay.\nSET @kfs_0037_deletion_jobs_positive_sql = IF(\n EXISTS(\n SELECT 1\n FROM information_schema.tidb_check_constraints\n WHERE constraint_schema = DATABASE()\n AND table_name = 'deletion_jobs'\n AND constraint_name = 'deletion_jobs_positive_ck'\n ),\n 'ALTER TABLE `deletion_jobs` DROP CHECK `deletion_jobs_positive_ck`',\n 'DO 0'\n);\nPREPARE kfs_0037_deletion_jobs_positive_stmt\n FROM @kfs_0037_deletion_jobs_positive_sql;\nEXECUTE kfs_0037_deletion_jobs_positive_stmt;\nDEALLOCATE PREPARE kfs_0037_deletion_jobs_positive_stmt;\n\nSET @kfs_0037_deletion_jobs_positive_sql = IF(\n EXISTS(\n SELECT 1\n FROM information_schema.tidb_check_constraints\n WHERE constraint_schema = DATABASE()\n AND table_name = 'deletion_jobs'\n AND constraint_name = 'deletion_jobs_positive_ck'\n ),\n 'DO 0',\n 'ALTER TABLE `deletion_jobs` ADD CONSTRAINT `deletion_jobs_positive_ck` CHECK (((`target_type` = ''logical_document'' AND `target_revision` >= 0) OR (`target_type` <> ''logical_document'' AND `target_revision` >= 1)) AND (`capability_grant_id` IS NOT NULL OR `permission_snapshot_revision` >= 1) AND `row_version` >= 1 AND `execution_attempts` >= 0 AND `max_execution_attempts` >= 1 AND `execution_attempts` <= `max_execution_attempts` AND (`active_slot` IS NULL OR `active_slot` = 1))'\n);\nPREPARE kfs_0037_deletion_jobs_positive_stmt\n FROM @kfs_0037_deletion_jobs_positive_sql;\nEXECUTE kfs_0037_deletion_jobs_positive_stmt;\nDEALLOCATE PREPARE kfs_0037_deletion_jobs_positive_stmt;\n\nSET @kfs_0037_deletion_tombstones_positive_sql = IF(\n EXISTS(\n SELECT 1\n FROM information_schema.tidb_check_constraints\n WHERE constraint_schema = DATABASE()\n AND table_name = 'deletion_tombstones'\n AND constraint_name = 'deletion_tombstones_positive_ck'\n ),\n 'ALTER TABLE `deletion_tombstones` DROP CHECK `deletion_tombstones_positive_ck`',\n 'DO 0'\n);\nPREPARE kfs_0037_deletion_tombstones_positive_stmt\n FROM @kfs_0037_deletion_tombstones_positive_sql;\nEXECUTE kfs_0037_deletion_tombstones_positive_stmt;\nDEALLOCATE PREPARE kfs_0037_deletion_tombstones_positive_stmt;\n\nSET @kfs_0037_deletion_tombstones_positive_sql = IF(\n EXISTS(\n SELECT 1\n FROM information_schema.tidb_check_constraints\n WHERE constraint_schema = DATABASE()\n AND table_name = 'deletion_tombstones'\n AND constraint_name = 'deletion_tombstones_positive_ck'\n ),\n 'DO 0',\n 'ALTER TABLE `deletion_tombstones` ADD CONSTRAINT `deletion_tombstones_positive_ck` CHECK (((`target_type` = ''logical_document'' AND `target_revision` >= 0) OR (`target_type` <> ''logical_document'' AND `target_revision` >= 1)) AND `row_version` >= 1)'\n);\nPREPARE kfs_0037_deletion_tombstones_positive_stmt\n FROM @kfs_0037_deletion_tombstones_positive_sql;\nEXECUTE kfs_0037_deletion_tombstones_positive_stmt;\nDEALLOCATE PREPARE kfs_0037_deletion_tombstones_positive_stmt;\n", path: "packages/database/migrations/0037_logical_document_zero_revision_deletion.tidb.sql" },
|
||||
] as const satisfies readonly MigrationArtifact[];
|
||||
|
||||
@ -132,6 +132,8 @@ describe("migration file rendering", () => {
|
||||
"packages/database/migrations/0035_research_task_answer_streaming.tidb.sql",
|
||||
"packages/database/migrations/0036_page_index_findability.postgres.sql",
|
||||
"packages/database/migrations/0036_page_index_findability.tidb.sql",
|
||||
"packages/database/migrations/0037_logical_document_zero_revision_deletion.postgres.sql",
|
||||
"packages/database/migrations/0037_logical_document_zero_revision_deletion.tidb.sql",
|
||||
]);
|
||||
expect(artifacts[2]?.content).toContain('ALTER COLUMN "dense_vector" TYPE vector');
|
||||
expect(artifacts[2]?.content).not.toContain("vector(1536)");
|
||||
@ -627,6 +629,39 @@ describe("migration file rendering", () => {
|
||||
);
|
||||
});
|
||||
|
||||
it("allows zero revisions only for logical-document deletion records", () => {
|
||||
const artifacts = getDatabaseMigrationArtifacts();
|
||||
|
||||
for (const dialect of ["postgres", "tidb"] as const) {
|
||||
const migration = artifacts.find((artifact) =>
|
||||
artifact.path.endsWith(`0037_logical_document_zero_revision_deletion.${dialect}.sql`),
|
||||
)?.content;
|
||||
|
||||
expect(migration).toContain("deletion_jobs_positive_ck");
|
||||
expect(migration).toContain("deletion_tombstones_positive_ck");
|
||||
expect(migration).toContain("target_type");
|
||||
expect(migration).toContain("logical_document");
|
||||
expect(migration).toContain("target_revision");
|
||||
expect(migration).toContain(">= 0");
|
||||
expect(migration).toContain(
|
||||
dialect === "tidb" ? "<> ''logical_document''" : "<> 'logical_document'",
|
||||
);
|
||||
expect(migration).toContain(">= 1");
|
||||
}
|
||||
|
||||
const tidb = artifacts.find((artifact) =>
|
||||
artifact.path.endsWith("0037_logical_document_zero_revision_deletion.tidb.sql"),
|
||||
)?.content;
|
||||
expect(tidb?.match(/FROM information_schema\.tidb_check_constraints/gu)).toHaveLength(4);
|
||||
expect(tidb?.match(/'ALTER TABLE `deletion_(?:jobs|tombstones)` DROP CHECK/gu)).toHaveLength(2);
|
||||
expect(
|
||||
tidb?.match(/'ALTER TABLE `deletion_(?:jobs|tombstones)` ADD CONSTRAINT/gu),
|
||||
).toHaveLength(2);
|
||||
expect(tidb).not.toMatch(
|
||||
/ALTER TABLE `deletion_(?:jobs|tombstones)`\s+DROP CONSTRAINT[^;]+ADD CONSTRAINT/gu,
|
||||
);
|
||||
});
|
||||
|
||||
it("keeps migration 0015 safe to replay after DDL commits before its marker", () => {
|
||||
const artifacts = getDatabaseMigrationArtifacts();
|
||||
const postgres = artifacts.find(
|
||||
@ -817,6 +852,7 @@ describe("migration file rendering", () => {
|
||||
"packages/database/migrations/0034_knowledge_space_emoji_icons.postgres.sql",
|
||||
"packages/database/migrations/0035_research_task_answer_streaming.postgres.sql",
|
||||
"packages/database/migrations/0036_page_index_findability.postgres.sql",
|
||||
"packages/database/migrations/0037_logical_document_zero_revision_deletion.postgres.sql",
|
||||
]);
|
||||
expect(
|
||||
getPendingMigrationArtifacts({
|
||||
@ -857,6 +893,7 @@ describe("migration file rendering", () => {
|
||||
"0034_knowledge_space_emoji_icons",
|
||||
"0035_research_task_answer_streaming",
|
||||
"0036_page_index_findability",
|
||||
"0037_logical_document_zero_revision_deletion",
|
||||
],
|
||||
dialect: "postgres",
|
||||
}),
|
||||
|
||||
@ -3395,8 +3395,8 @@ const tables = [
|
||||
{
|
||||
expression: {
|
||||
postgres:
|
||||
'"target_revision" >= 1 AND ("capability_grant_id" IS NOT NULL OR "permission_snapshot_revision" >= 1) AND "row_version" >= 1 AND "execution_attempts" >= 0 AND "max_execution_attempts" >= 1 AND "execution_attempts" <= "max_execution_attempts" AND ("active_slot" IS NULL OR "active_slot" = 1)',
|
||||
tidb: "`target_revision` >= 1 AND (`capability_grant_id` IS NOT NULL OR `permission_snapshot_revision` >= 1) AND `row_version` >= 1 AND `execution_attempts` >= 0 AND `max_execution_attempts` >= 1 AND `execution_attempts` <= `max_execution_attempts` AND (`active_slot` IS NULL OR `active_slot` = 1)",
|
||||
'(("target_type" = \'logical_document\' AND "target_revision" >= 0) OR ("target_type" <> \'logical_document\' AND "target_revision" >= 1)) AND ("capability_grant_id" IS NOT NULL OR "permission_snapshot_revision" >= 1) AND "row_version" >= 1 AND "execution_attempts" >= 0 AND "max_execution_attempts" >= 1 AND "execution_attempts" <= "max_execution_attempts" AND ("active_slot" IS NULL OR "active_slot" = 1)',
|
||||
tidb: "((`target_type` = 'logical_document' AND `target_revision` >= 0) OR (`target_type` <> 'logical_document' AND `target_revision` >= 1)) AND (`capability_grant_id` IS NOT NULL OR `permission_snapshot_revision` >= 1) AND `row_version` >= 1 AND `execution_attempts` >= 0 AND `max_execution_attempts` >= 1 AND `execution_attempts` <= `max_execution_attempts` AND (`active_slot` IS NULL OR `active_slot` = 1)",
|
||||
},
|
||||
name: "deletion_jobs_positive_ck",
|
||||
},
|
||||
@ -3512,8 +3512,9 @@ const tables = [
|
||||
},
|
||||
{
|
||||
expression: {
|
||||
postgres: '"target_revision" >= 1 AND "row_version" >= 1',
|
||||
tidb: "`target_revision` >= 1 AND `row_version` >= 1",
|
||||
postgres:
|
||||
'(("target_type" = \'logical_document\' AND "target_revision" >= 0) OR ("target_type" <> \'logical_document\' AND "target_revision" >= 1)) AND "row_version" >= 1',
|
||||
tidb: "((`target_type` = 'logical_document' AND `target_revision` >= 0) OR (`target_type` <> 'logical_document' AND `target_revision` >= 1)) AND `row_version` >= 1",
|
||||
},
|
||||
name: "deletion_tombstones_positive_ck",
|
||||
},
|
||||
|
||||
Loading…
Reference in New Issue
Block a user