Skip to content

Commit 4e7ad67

Browse files
committed
more join utils tests
1 parent 830cae7 commit 4e7ad67

File tree

2 files changed

+101
-1
lines changed

2 files changed

+101
-1
lines changed

packages/zql/src/ivm/cap.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ interface CapStorage {
3838
*
3939
* - No comparator needed (no ordering requirement)
4040
* - No `start` or `reverse` fetch support
41-
* - No `#rowHiddenFromFetch` complexity
41+
* - No `#rowHiddenFromFetch` complexity (we can defer when adding to the pk set)
4242
*
4343
* Cap is used in EXISTS child pipelines where only the count of matching
4444
* rows matters, not their order. This allows SQLite to skip ORDER BY

packages/zql/src/ivm/join-utils.test.ts

Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,9 @@ import {describe, expect, test} from 'vitest';
22
import {
33
generateWithOverlayUnordered,
44
generateWithOverlayNoYieldUnordered,
5+
rowEqualsForCompoundKey,
6+
isJoinMatch,
7+
buildJoinConstraint,
58
} from './join-utils.ts';
69
import type {Node} from './data.ts';
710
import type {SourceSchema} from './schema.ts';
@@ -280,3 +283,100 @@ describe('generateWithOverlayNoYieldUnordered', () => {
280283
expect(result.map(n => n.row)).toEqual([{id: 1}, {id: 3}]);
281284
});
282285
});
286+
287+
describe('rowEqualsForCompoundKey', () => {
288+
test('single key match', () => {
289+
expect(rowEqualsForCompoundKey({id: 1}, {id: 1}, ['id'])).toBe(true);
290+
});
291+
292+
test('single key mismatch', () => {
293+
expect(rowEqualsForCompoundKey({id: 1}, {id: 2}, ['id'])).toBe(false);
294+
});
295+
296+
test('compound key all match', () => {
297+
expect(
298+
rowEqualsForCompoundKey({a: 1, b: 'x'}, {a: 1, b: 'x'}, ['a', 'b']),
299+
).toBe(true);
300+
});
301+
302+
test('compound key partial mismatch', () => {
303+
expect(
304+
rowEqualsForCompoundKey({a: 1, b: 'x'}, {a: 1, b: 'y'}, ['a', 'b']),
305+
).toBe(false);
306+
});
307+
308+
test('null equals null (compareValues treats null as a real value)', () => {
309+
expect(rowEqualsForCompoundKey({id: null}, {id: null}, ['id'])).toBe(true);
310+
});
311+
312+
test('extra columns ignored', () => {
313+
expect(
314+
rowEqualsForCompoundKey(
315+
{id: 1, val: 'a'},
316+
{id: 1, val: 'b'},
317+
['id'],
318+
),
319+
).toBe(true);
320+
});
321+
});
322+
323+
describe('isJoinMatch', () => {
324+
test('single key match', () => {
325+
expect(isJoinMatch({id: 1}, ['id'], {id: 1}, ['id'])).toBe(true);
326+
});
327+
328+
test('single key mismatch', () => {
329+
expect(isJoinMatch({id: 1}, ['id'], {id: 2}, ['id'])).toBe(false);
330+
});
331+
332+
test('compound key match with different column names', () => {
333+
expect(
334+
isJoinMatch(
335+
{a: 1, b: 'x'},
336+
['a', 'b'],
337+
{x: 1, y: 'x'},
338+
['x', 'y'],
339+
),
340+
).toBe(true);
341+
});
342+
343+
test('null parent value returns false (SQL NULL semantics)', () => {
344+
expect(isJoinMatch({id: null}, ['id'], {id: 1}, ['id'])).toBe(false);
345+
});
346+
347+
test('null child value returns false', () => {
348+
expect(isJoinMatch({id: 1}, ['id'], {id: null}, ['id'])).toBe(false);
349+
});
350+
351+
test('both null returns false (unlike rowEqualsForCompoundKey)', () => {
352+
expect(isJoinMatch({id: null}, ['id'], {id: null}, ['id'])).toBe(false);
353+
});
354+
});
355+
356+
describe('buildJoinConstraint', () => {
357+
test('single key maps value correctly', () => {
358+
expect(buildJoinConstraint({id: 1}, ['id'], ['id'])).toEqual({id: 1});
359+
});
360+
361+
test('compound key maps all values', () => {
362+
expect(
363+
buildJoinConstraint({a: 1, b: 'x'}, ['a', 'b'], ['a', 'b']),
364+
).toEqual({a: 1, b: 'x'});
365+
});
366+
367+
test('null value returns undefined', () => {
368+
expect(buildJoinConstraint({id: null}, ['id'], ['id'])).toBeUndefined();
369+
});
370+
371+
test('null in second position returns undefined', () => {
372+
expect(
373+
buildJoinConstraint({a: 1, b: null}, ['a', 'b'], ['x', 'y']),
374+
).toBeUndefined();
375+
});
376+
377+
test('different source/target key names', () => {
378+
expect(
379+
buildJoinConstraint({userId: 5, orgId: 10}, ['userId', 'orgId'], ['id', 'org']),
380+
).toEqual({id: 5, org: 10});
381+
});
382+
});

0 commit comments

Comments
 (0)