Skip to content

Commit 15f290a

Browse files
authored
chore: update compare DB script data sorting logics (#7428)
1 parent 6a8ddae commit 15f290a

File tree

3 files changed

+307
-21
lines changed

3 files changed

+307
-21
lines changed

.scripts/compare-database.js

Lines changed: 35 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -204,18 +204,8 @@ const queryDatabaseManifest = async (database) => {
204204
};
205205
};
206206

207-
const [, , database1, database2] = process.argv;
208-
209-
console.log('Compare database manifest between', database1, 'and', database2);
210-
211-
const manifests = [
212-
await queryDatabaseManifest(database1),
213-
await queryDatabaseManifest(database2),
214-
];
215-
216-
tryCompare(...manifests);
217-
218-
const autoCompare = (a, b) => {
207+
// Export utility functions first
208+
export const autoCompare = (a, b) => {
219209
if (typeof a !== typeof b) {
220210
return (typeof a).localeCompare(typeof b);
221211
}
@@ -240,17 +230,26 @@ const autoCompare = (a, b) => {
240230
return String(a).localeCompare(String(b));
241231
};
242232

243-
const buildSortByKeys = (keys) => (a, b) => {
244-
const found = keys.find((key) => a[key] !== b[key]);
233+
export const buildSortByKeys = (keys) => (a, b) => {
234+
// Filter out keys where either value is boolean, then use original strategy
235+
const filteredKeys = keys.filter((key) =>
236+
typeof a[key] !== 'boolean' && typeof b[key] !== 'boolean'
237+
);
238+
239+
const found = filteredKeys.find((key) => {
240+
const comparison = autoCompare(a[key], b[key]);
241+
return comparison !== 0;
242+
});
245243
return found ? autoCompare(a[found], b[found]) : 0;
246244
};
247245

248-
const queryDatabaseData = async (database) => {
246+
const queryDatabaseData = async (database, manifests) => {
249247
const pool = new pg.Pool({
250248
database,
251249
user: 'postgres',
252250
password: 'postgres',
253251
});
252+
254253
const result = await Promise.all(
255254
manifests[0].tables.map(async ({ table_schema, table_name }) => {
256255
const { rows } = await pool.query(
@@ -288,9 +287,25 @@ const queryDatabaseData = async (database) => {
288287
return Object.fromEntries(result);
289288
};
290289

291-
console.log('Compare database data between', database1, 'and', database2);
290+
// Main execution logic - only runs when file is executed directly
291+
const isMainModule = import.meta.url === new URL(process.argv[1], 'file://').href;
292292

293-
tryCompare(
294-
await queryDatabaseData(database1),
295-
await queryDatabaseData(database2)
296-
);
293+
if (isMainModule) {
294+
const [, , database1, database2] = process.argv;
295+
296+
console.log('Compare database manifest between', database1, 'and', database2);
297+
298+
const manifests = [
299+
await queryDatabaseManifest(database1),
300+
await queryDatabaseManifest(database2),
301+
];
302+
303+
tryCompare(...manifests);
304+
305+
console.log('Compare database data between', database1, 'and', database2);
306+
307+
tryCompare(
308+
await queryDatabaseData(database1, manifests),
309+
await queryDatabaseData(database2, manifests)
310+
);
311+
}

.scripts/compare-database.test.js

Lines changed: 270 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,270 @@
1+
/**
2+
* Test file for deepSort and autoCompare functions from compare-database.js
3+
*
4+
* Usage: node .scripts/compare-database.test.js
5+
*
6+
* This file tests the deep sorting functionality that ensures consistent
7+
* ordering of arrays and objects in database comparison operations.
8+
*/
9+
10+
import assert from 'node:assert';
11+
import { autoCompare, buildSortByKeys } from './compare-database.js';
12+
13+
// Test helper function
14+
const runTest = (testName, testFn) => {
15+
try {
16+
testFn();
17+
console.log(`${testName}`);
18+
} catch (error) {
19+
console.error(`${testName}: ${error.message}`);
20+
process.exit(1);
21+
}
22+
};
23+
24+
// Test cases for autoCompare function
25+
runTest('autoCompare - different types', () => {
26+
assert.strictEqual(autoCompare('string', 123) > 0, true); // string > number
27+
assert.strictEqual(autoCompare(123, 'string') < 0, true); // number < string
28+
});
29+
30+
runTest('autoCompare - same primitive types', () => {
31+
assert.strictEqual(autoCompare('apple', 'banana') < 0, true);
32+
assert.strictEqual(autoCompare('banana', 'apple') > 0, true);
33+
assert.strictEqual(autoCompare('apple', 'apple'), 0);
34+
assert.strictEqual(autoCompare(1, 2) < 0, true);
35+
assert.strictEqual(autoCompare(2, 1) > 0, true);
36+
assert.strictEqual(autoCompare(1, 1), 0);
37+
});
38+
39+
runTest('autoCompare - objects', () => {
40+
const obj1 = { a: 1, b: 2 };
41+
const obj2 = { a: 1, b: 2 };
42+
const obj3 = { a: 1, b: 3 };
43+
44+
assert.strictEqual(autoCompare(obj1, obj2), 0);
45+
assert.strictEqual(autoCompare(obj1, obj3) < 0, true);
46+
assert.strictEqual(autoCompare(obj3, obj1) > 0, true);
47+
});
48+
49+
// Test cases for autoCompare sorting stability - converted from original deepSort tests
50+
runTest('autoCompare - simple arrays sorting stability', () => {
51+
const input = [3, 1, 2];
52+
const sorted = input.slice().sort(autoCompare);
53+
const expected = [1, 2, 3];
54+
55+
assert.deepStrictEqual(sorted, expected);
56+
57+
// Test multiple sorts produce same result (stability)
58+
const sorted2 = input.slice().sort(autoCompare);
59+
assert.deepStrictEqual(sorted, sorted2);
60+
});
61+
62+
runTest('autoCompare - string arrays sorting stability', () => {
63+
const input = ['banana', 'apple', 'cherry'];
64+
const sorted = input.slice().sort(autoCompare);
65+
const expected = ['apple', 'banana', 'cherry'];
66+
67+
assert.deepStrictEqual(sorted, expected);
68+
69+
// Test multiple sorts produce same result
70+
const sorted2 = input.slice().sort(autoCompare);
71+
assert.deepStrictEqual(sorted, sorted2);
72+
});
73+
74+
runTest('autoCompare - nested arrays sorting consistency', () => {
75+
const input = [[3, 1], [2, 4], [1, 2]];
76+
const sorted = input.slice().sort(autoCompare);
77+
78+
// Arrays are compared element by element
79+
// [1, 2] < [2, 4] < [3, 1]
80+
const expected = [[1, 2], [2, 4], [3, 1]];
81+
82+
assert.deepStrictEqual(sorted, expected);
83+
84+
// Test sorting stability
85+
const sorted2 = input.slice().sort(autoCompare);
86+
assert.deepStrictEqual(sorted, sorted2);
87+
});
88+
89+
runTest('autoCompare - objects sorting by keys and values', () => {
90+
const obj1 = { c: 3, a: 1, b: 2 };
91+
const obj2 = { a: 1, b: 2, c: 3 };
92+
const obj3 = { b: 2, c: 3, a: 1 };
93+
94+
// All objects have same content, just different key order
95+
assert.strictEqual(autoCompare(obj1, obj2), 0);
96+
assert.strictEqual(autoCompare(obj2, obj3), 0);
97+
assert.strictEqual(autoCompare(obj1, obj3), 0);
98+
99+
// Objects with different values
100+
const obj4 = { a: 1, b: 2, c: 4 };
101+
assert.strictEqual(autoCompare(obj1, obj4) < 0, true); // 3 < 4
102+
});
103+
104+
runTest('autoCompare - arrays with objects sorting stability', () => {
105+
const input = [
106+
{ name: 'Bob', age: 25 },
107+
{ name: 'Alice', age: 30 },
108+
{ name: 'Charlie', age: 20 }
109+
];
110+
111+
const sorted = input.slice().sort(autoCompare);
112+
113+
// Objects are sorted by their keys and values in lexicographic order
114+
// First by 'age' key, then by 'name' key
115+
const expected = [
116+
{ age: 20, name: 'Charlie' },
117+
{ age: 25, name: 'Bob' },
118+
{ age: 30, name: 'Alice' }
119+
];
120+
121+
assert.deepStrictEqual(sorted, expected);
122+
123+
// Test sorting stability
124+
const sorted2 = input.slice().sort(autoCompare);
125+
assert.deepStrictEqual(sorted, sorted2);
126+
});
127+
128+
runTest('autoCompare - complex nested objects sorting consistency', () => {
129+
const obj1 = {
130+
logto_skus: [
131+
{ type: 'AddOn', quota: { tokenLimit: 10_000 }, is_default: false },
132+
{ type: 'AddOn', quota: { tokenLimit: 100 }, is_default: true },
133+
{ type: 'AddOn', quota: { enterpriseSsoLimit: null }, is_default: true },
134+
],
135+
};
136+
137+
const obj2 = {
138+
logto_skus: [
139+
{ type: 'AddOn', quota: { enterpriseSsoLimit: null }, is_default: true },
140+
{ quota: { tokenLimit: 10_000 }, is_default: false, type: 'AddOn' },
141+
{ type: 'AddOn', quota: { tokenLimit: 100 }, is_default: true },
142+
],
143+
};
144+
145+
// Sort both arrays using buildSortByKeys for consistent comparison
146+
const keys1 = obj1.logto_skus.length > 0 ? Object.keys(obj1.logto_skus[0]) : [];
147+
const keys2 = obj2.logto_skus.length > 0 ? Object.keys(obj2.logto_skus[0]) : [];
148+
149+
const sortedObj1 = {
150+
logto_skus: obj1.logto_skus.slice().sort(buildSortByKeys(keys1))
151+
};
152+
153+
const sortedObj2 = {
154+
logto_skus: obj2.logto_skus.slice().sort(buildSortByKeys(keys2))
155+
};
156+
157+
// After sorting, they should be comparable and produce consistent results
158+
const comparison1 = autoCompare(sortedObj1, sortedObj2);
159+
const comparison2 = autoCompare(sortedObj1, sortedObj2);
160+
161+
assert.strictEqual(comparison1, comparison2); // Consistency
162+
});
163+
164+
runTest('autoCompare - mixed types array sorting order', () => {
165+
const input = [{ b: 2 }, 'string', 1, { a: 1 }];
166+
const sorted = input.slice().sort(autoCompare);
167+
168+
// Type order in autoCompare: number < object < string
169+
// Objects are sorted by their content
170+
const expected = [1, { a: 1 }, { b: 2 }, 'string'];
171+
172+
assert.deepStrictEqual(sorted, expected);
173+
174+
// Test sorting stability
175+
const sorted2 = input.slice().sort(autoCompare);
176+
assert.deepStrictEqual(sorted, sorted2);
177+
});
178+
179+
runTest('autoCompare - buildSortByKeys integration for database data', () => {
180+
// This simulates database rows that might have different ordering
181+
const data1 = [
182+
{ id: 1, name: 'Alice', metadata: { created: '2023-01-01' }, active: true },
183+
{ id: 2, name: 'Bob', metadata: { created: '2023-01-02' }, active: false }
184+
];
185+
186+
const data2 = [
187+
{ id: 2, name: 'Bob', metadata: { created: '2023-01-02' }, active: false },
188+
{ id: 1, name: 'Alice', metadata: { created: '2023-01-01' }, active: true }
189+
];
190+
191+
// Use buildSortByKeys to ensure consistent ordering
192+
const keys = ['id', 'name', 'metadata', 'active'];
193+
const sorted1 = data1.slice().sort(buildSortByKeys(keys));
194+
const sorted2 = data2.slice().sort(buildSortByKeys(keys));
195+
196+
// After sorting with complexity-aware buildSortByKeys, arrays should be identical
197+
assert.deepStrictEqual(sorted1, sorted2);
198+
199+
// Verify that the sorting is based on complexity (metadata object should be compared first)
200+
const comparison = autoCompare(sorted1, sorted2);
201+
assert.strictEqual(comparison, 0); // Should be identical
202+
});
203+
204+
// Test cases for buildSortByKeys with complexity sorting
205+
runTest('buildSortByKeys - prioritizes complex values', () => {
206+
const obj1 = {
207+
simpleBoolean: true,
208+
complexObject: { nested: 'value' },
209+
stringValue: 'test'
210+
};
211+
212+
const obj2 = {
213+
simpleBoolean: false,
214+
complexObject: { nested: 'different' },
215+
stringValue: 'test'
216+
};
217+
218+
const keys = ['simpleBoolean', 'complexObject', 'stringValue'];
219+
const sortFn = buildSortByKeys(keys);
220+
221+
// Should compare complexObject first (highest complexity), not simpleBoolean
222+
const result = sortFn(obj1, obj2);
223+
224+
// Since complexObject values are different, the comparison should be based on that
225+
// 'different' < 'value', so obj2 should come before obj1
226+
assert.strictEqual(result > 0, true);
227+
});
228+
229+
runTest('buildSortByKeys - falls back to less complex when complex values are equal', () => {
230+
const obj1 = {
231+
simpleBoolean: true,
232+
complexObject: { nested: 'value' },
233+
stringValue: 'apple'
234+
};
235+
236+
const obj2 = {
237+
simpleBoolean: false,
238+
complexObject: { nested: 'value' }, // Same as obj1
239+
stringValue: 'banana'
240+
};
241+
242+
const keys = ['simpleBoolean', 'complexObject', 'stringValue'];
243+
const sortFn = buildSortByKeys(keys);
244+
245+
// Should compare complexObject first (equal), then stringValue (next most complex)
246+
const result = sortFn(obj1, obj2);
247+
248+
// 'apple' < 'banana', so obj1 should come before obj2
249+
assert.strictEqual(result < 0, true);
250+
});
251+
252+
runTest('buildSortByKeys - returns 0 when all values are equal', () => {
253+
const obj1 = {
254+
simpleBoolean: true,
255+
complexObject: { nested: 'value' },
256+
stringValue: 'test'
257+
};
258+
259+
const obj2 = {
260+
simpleBoolean: true,
261+
complexObject: { nested: 'value' },
262+
stringValue: 'test'
263+
};
264+
265+
const keys = ['simpleBoolean', 'complexObject', 'stringValue'];
266+
const sortFn = buildSortByKeys(keys);
267+
268+
const result = sortFn(obj1, obj2);
269+
assert.strictEqual(result, 0);
270+
});

package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,8 @@
2222
"ci:build": "pnpm -r build",
2323
"ci:lint": "pnpm -r --parallel --workspace-concurrency=0 lint",
2424
"ci:stylelint": "pnpm -r --parallel --workspace-concurrency=0 stylelint",
25-
"ci:test": "pnpm -r --parallel --workspace-concurrency=0 test:ci"
25+
"test:scripts": "node .scripts/compare-database.test.js",
26+
"ci:test": "pnpm -r --parallel --workspace-concurrency=0 test:ci && pnpm test:scripts"
2627
},
2728
"devDependencies": {
2829
"@changesets/cli": "^2.26.2",

0 commit comments

Comments
 (0)