feat(v2.2): 建立分区关系表和AppData迁移预演
This commit is contained in:
104
apps/server/prisma/v22-partitioned-schema.spec.ts
Normal file
104
apps/server/prisma/v22-partitioned-schema.spec.ts
Normal file
@@ -0,0 +1,104 @@
|
||||
import { existsSync, readFileSync } from 'node:fs';
|
||||
import { join } from 'node:path';
|
||||
|
||||
const migrationPath = join(
|
||||
process.cwd(),
|
||||
'prisma',
|
||||
'migrations',
|
||||
'20260703000000_v22_partitioned_domain_schema',
|
||||
'migration.sql',
|
||||
);
|
||||
|
||||
function readMigrationSql() {
|
||||
if (!existsSync(migrationPath)) {
|
||||
throw new Error(`Missing V2.2 migration: ${migrationPath}`);
|
||||
}
|
||||
return readFileSync(migrationPath, 'utf8');
|
||||
}
|
||||
|
||||
function expectPartitionedTable(
|
||||
sql: string,
|
||||
table: string,
|
||||
method: 'HASH' | 'RANGE',
|
||||
key: string,
|
||||
primaryKey: string[],
|
||||
) {
|
||||
const quotedPrimaryKey = primaryKey.map((column) => `"${column}"`).join(', ');
|
||||
expect(sql).toMatch(
|
||||
new RegExp(
|
||||
`CREATE TABLE "${table}"[\\s\\S]*CONSTRAINT "${table}_pkey" PRIMARY KEY \\(${quotedPrimaryKey}\\)[\\s\\S]*PARTITION BY ${method} \\("${key}"\\);`,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
function expectHashPartitions(sql: string, table: string, count: number) {
|
||||
const matches = sql.match(
|
||||
new RegExp(`PARTITION OF "${table}" FOR VALUES WITH \\(MODULUS ${count}, REMAINDER`, 'g'),
|
||||
);
|
||||
expect(matches).toHaveLength(count);
|
||||
}
|
||||
|
||||
describe('V2.2 partitioned domain schema migration', () => {
|
||||
it('keeps every hash partitioned business table primary key anchored by the partition key', () => {
|
||||
const sql = readMigrationSql();
|
||||
|
||||
expectPartitionedTable(sql, 'requirements', 'HASH', 'product_id', ['id', 'product_id']);
|
||||
expectPartitionedTable(sql, 'dev_tasks', 'HASH', 'version_id', ['id', 'version_id']);
|
||||
expectPartitionedTable(sql, 'test_cases', 'HASH', 'version_id', ['id', 'version_id']);
|
||||
expectPartitionedTable(sql, 'bugs', 'HASH', 'version_id', ['id', 'version_id']);
|
||||
|
||||
expectHashPartitions(sql, 'requirements', 16);
|
||||
expectHashPartitions(sql, 'dev_tasks', 16);
|
||||
expectHashPartitions(sql, 'test_cases', 16);
|
||||
expectHashPartitions(sql, 'bugs', 16);
|
||||
});
|
||||
|
||||
it('includes partition keys in business unique constraints', () => {
|
||||
const sql = readMigrationSql();
|
||||
|
||||
expect(sql).toMatch(/CONSTRAINT "requirements_product_id_code_key" UNIQUE \("product_id", "code"\)/);
|
||||
expect(sql).toMatch(/CONSTRAINT "dev_tasks_version_id_code_key" UNIQUE \("version_id", "code"\)/);
|
||||
expect(sql).toMatch(/CONSTRAINT "test_cases_version_id_code_key" UNIQUE \("version_id", "code"\)/);
|
||||
expect(sql).toMatch(/CONSTRAINT "bugs_version_id_code_key" UNIQUE \("version_id", "code"\)/);
|
||||
});
|
||||
|
||||
it('uses composite foreign keys when a referenced table is partitioned', () => {
|
||||
const sql = readMigrationSql();
|
||||
|
||||
expect(sql).toMatch(
|
||||
/FOREIGN KEY \("requirement_id", "requirement_product_id"\) REFERENCES "requirements"\("id", "product_id"\)/,
|
||||
);
|
||||
expect(sql).toMatch(
|
||||
/FOREIGN KEY \("test_case_id", "test_case_version_id"\) REFERENCES "test_cases"\("id", "version_id"\)/,
|
||||
);
|
||||
});
|
||||
|
||||
it('range partitions append-only evidence and snapshot tables by created_at', () => {
|
||||
const sql = readMigrationSql();
|
||||
|
||||
expectPartitionedTable(sql, 'work_activities', 'RANGE', 'created_at', ['id', 'created_at']);
|
||||
expectPartitionedTable(sql, 'task_worklogs', 'RANGE', 'created_at', ['id', 'created_at']);
|
||||
expectPartitionedTable(sql, 'overtime_records', 'RANGE', 'created_at', ['id', 'created_at']);
|
||||
expectPartitionedTable(sql, 'xiaobao_risk_snapshots', 'RANGE', 'created_at', ['id', 'created_at']);
|
||||
expectPartitionedTable(sql, 'ai_logs', 'RANGE', 'created_at', ['id', 'created_at']);
|
||||
|
||||
for (const table of [
|
||||
'work_activities',
|
||||
'task_worklogs',
|
||||
'overtime_records',
|
||||
'xiaobao_risk_snapshots',
|
||||
'ai_logs',
|
||||
]) {
|
||||
expect(sql).toMatch(new RegExp(`CREATE TABLE "${table}_default" PARTITION OF "${table}" DEFAULT;`));
|
||||
}
|
||||
});
|
||||
|
||||
it('keeps Xiaobao current summaries as one row per version instead of partitioned history', () => {
|
||||
const sql = readMigrationSql();
|
||||
|
||||
expect(sql).toMatch(
|
||||
/CREATE TABLE "xiaobao_risk_summaries"[\s\S]*CONSTRAINT "xiaobao_risk_summaries_pkey" PRIMARY KEY \("version_id"\)/,
|
||||
);
|
||||
expect(sql).not.toMatch(/CREATE TABLE "xiaobao_risk_summaries"[\s\S]*PARTITION BY/);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user