Roughly in order of risk.
None of the three new memory-source.test.ts cases exercise req.filter
with an in-progress push (overlay set). The code path does feed the
merged predicate into generateWithOverlay
(memory-source.ts:374) and overlaysForFilterPredicate, but nothing
locks it in. Same gap on table-source.ts:295-298. This is the most
intricate IVM interaction and the most likely to silently regress.
Worth adding:
- Fetch-with-
req.filterwhile an ADD overlay is present (matching and non-matching). - REMOVE overlay, EDIT overlay (old-row matches filter, new-row doesn't, and vice versa).
- Same trio in unordered mode (
generateWithOverlayUnordered).
packages/zqlite/src/table-source.test.ts has no test that passes a
req.filter. The memory-source tests don't catch SQLite-specific issues
(NULL semantics, column-type coercion in filtersToSQL, the
fetchFilters AND-merge in buildSelectQuery). At minimum:
- A
table-source.test.tsmirror of the three newmemory-source.test.tscases. - A
query-builder.test.tscase asserting the emitted SQL when bothfiltersandfetchFiltersare present.
The new tests use only the basic path. Untested combinations:
req.filter+req.reverse: true(different code path throughindexComparator).req.filter+req.start(thepkConstraintshort-circuit may interact with start).req.filter+req.multiConstraints—#fetchMultispreadsreqsofilterpropagates into the recursive#fetchcall, but nothing verifies it.
Take and Join get reasoned about in the contract comment on
operator.ts:81-110, but no test asserts they preserve req.filter.
They're the kind of operator someone could easily refactor and silently
drop the filter. Quick RecordingInput-style tests for both would be
cheap insurance.
Cap (cap.ts:115) intentionally builds a fresh {constraint} request
and drops req.filter. That's safe today because Cap is only used as a
non-flipped EXISTS child whose only consumer is a Join with no filter —
but it's an undocumented restriction. Either add an assert that
req.filter === undefined, or document the carve-out next to the new
contract block in operator.ts.
Direct unit tests would catch regressions earlier:
propagateConstraintswithfrom.kind === 'fan-in' && from.type === 'FI'does not callsetPerBranchFilter(the "FI mode is skipped" rule).propagateConstraintswithfrom.kind === 'fan-in' && from.type === 'UFI'does call it, with the right branchPattern.findParentConnectionbails on nestedfan-in(the "deeply nested OR loses the optimization" carve-out).findParentConnectionwalks throughjoin.parentcorrectly.
The planner-controlled tests verify plan shape;
predicate-pushdown.test.ts verifies forwarding shape. There's no test
that builds a real pipeline for or(simple, exists) with a flipped CSQ
and asserts the result rows are correct when the simple branch's
filter actually rides req.filter to the source. If the planner picks
UFI but a downstream operator silently drops the filter, plan tests pass
and results are wrong. One integration test in zql-integration-tests
for this shape would catch a whole category of regressions.
If only two are done, do #1 (overlay × filter — highest correctness risk and intricate) and #6 (end-to-end runtime — catches integration drift across the stack). #2 is also worth it given SQLite is the production path.