105 lines
4.0 KiB
TypeScript
105 lines
4.0 KiB
TypeScript
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/);
|
|
});
|
|
});
|