fix(data-compare): avoid unsafe key inference

This commit is contained in:
jearhe authored and GitHub committed 2026-08-18 10:51:26 +08:00
1 parent 665e5920c9
commit fab5c75fd2
4 files changed
+109 -6

No files matched your search

@@ -0,0 +1,36 @@
import { describe, expect, it } from "vitest";
import { inferCompareKeyColumns } from "../dataCompare";
describe("inferCompareKeyColumns", () => {
it("returns all primary-key columns when a primary key exists", () => {
expect(
inferCompareKeyColumns([
{ name: "id", is_primary_key: true },
{ name: "name", is_primary_key: false },
]),
).toEqual(["id"]);
});
it("returns a composite primary key as-is", () => {
expect(
inferCompareKeyColumns([
{ name: "tenant_id", is_primary_key: true },
{ name: "user_id", is_primary_key: true },
{ name: "name", is_primary_key: false },
]),
).toEqual(["tenant_id", "user_id"]);
});
it("returns an empty array instead of falling back to the first column when there is no primary key", () => {
expect(
inferCompareKeyColumns([
{ name: "category", is_primary_key: false },
{ name: "name", is_primary_key: false },
]),
).toEqual([]);
});
it("returns an empty array for an empty column list", () => {
expect(inferCompareKeyColumns([])).toEqual([]);
});
});
@@ -118,3 +118,24 @@ export interface DataCompareSyncPlan {
syncStatements: string[];
syncSql: string;
}
/**
* Safely infers the columns to use as comparison keys for a table.
*
* Only primary-key columns are trusted: a primary key is unique by
* definition, so comparing on it can never produce duplicate-key failures.
* Composite primary keys are returned as-is.
*
* When no primary key exists an empty array is returned so the caller can ask
* the user to pick matching columns explicitly. Falling back to the first
* column would silently select an arbitrary, possibly non-unique field
* (category, status, date, ...) and make the whole table comparison fail with
* an unhelpful duplicate-key error.
*/
export function inferCompareKeyColumns(columns: Pick<ColumnInfo, "name" | "is_primary_key">[]): string[] {
const primaryKeys: string[] = [];
for (const column of columns) {
if (column.is_primary_key) primaryKeys.push(column.name);
}
return primaryKeys;
}