Skip to content

Commit c6c0689

Browse files
committed
test unordered join overlay
1 parent b072057 commit c6c0689

File tree

2 files changed

+68
-0
lines changed

2 files changed

+68
-0
lines changed

packages/zql-integration-tests/src/cap.pg.test.ts

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -244,3 +244,66 @@ test('re-add comment → issue reappears', () => {
244244

245245
expect(view.data).toEqual(queryDelegate.materialize(q).data);
246246
});
247+
248+
test('join-level unordered overlay — remove comment triggers overlay for multiple parent issues', () => {
249+
// Uses ownerComments: issue.ownerId = comment.authorId
250+
// All 4 issues have ownerId='user1', all comments have authorId='user1'
251+
// So a single comment change matches ALL 4 issues as parents.
252+
// With flip: false, the planner builds a regular Join + Cap(limit=3, unordered).
253+
// When Cap pushes a remove+refill to Join, Join iterates all 4 parent issues,
254+
// and for issues 2-4, generateWithOverlayUnordered (join-utils.ts) is called.
255+
const q = issueQuery.whereExists('ownerComments', {flip: false});
256+
const view = queryDelegate.materialize(q);
257+
258+
// All 4 issues should be present (all have ownerComments via ownerId='user1')
259+
const initialData = view.data as ReadonlyArray<{readonly id: string}>;
260+
const initialIds = initialData.map(r => r.id);
261+
expect(initialIds).toContain('issue1');
262+
expect(initialIds).toContain('issue2');
263+
expect(initialIds).toContain('issue3');
264+
expect(initialIds).toContain('issue4');
265+
266+
expect(view.data).toEqual(queryDelegate.materialize(q).data);
267+
268+
// Remove comments to ensure we hit a tracked one.
269+
// Cap tracks the first 3 it encounters (unordered). Removing multiple
270+
// guarantees at least one hits a tracked comment, triggering Cap refill → Join overlay.
271+
// After prior tests, the remaining comments are:
272+
// c1b (issue1), c2a (issue2), c3a/c3b/c3c/c3d (issue3), c4b/c4c/c4d (issue4)
273+
const commentsToRemove = [
274+
{
275+
id: 'c2a',
276+
authorId: 'user1',
277+
issue_id: 'issue2',
278+
text: 'Comment 2a',
279+
createdAt: 1009843200000,
280+
},
281+
{
282+
id: 'c3a',
283+
authorId: 'user1',
284+
issue_id: 'issue3',
285+
text: 'Comment 3a',
286+
createdAt: 1012521600000,
287+
},
288+
{
289+
id: 'c3b',
290+
authorId: 'user1',
291+
issue_id: 'issue3',
292+
text: 'Comment 3b',
293+
createdAt: 1012608000000,
294+
},
295+
{
296+
id: 'c4b',
297+
authorId: 'user1',
298+
issue_id: 'issue4',
299+
text: 'Comment 4b',
300+
createdAt: 1015113600000,
301+
},
302+
];
303+
304+
const source = must(queryDelegate.getSource('comments'));
305+
for (const row of commentsToRemove) {
306+
consume(source.push({type: 'remove', row}));
307+
expect(view.data).toEqual(queryDelegate.materialize(q).data);
308+
}
309+
});

packages/zql/src/query/test/test-schemas.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,11 @@ const issueRelationships = relationships(issue, ({one, many}) => ({
8484
destField: ['issueId'],
8585
destSchema: comment,
8686
}),
87+
ownerComments: many({
88+
sourceField: ['ownerId'],
89+
destField: ['authorId'],
90+
destSchema: comment,
91+
}),
8792
labels: many(
8893
{
8994
sourceField: ['id'],

0 commit comments

Comments
 (0)