close
Skip to content

Add TopN plan node for O(limit) ORDER BY + LIMIT#24

Open
philcunliffe wants to merge 11 commits intomasterfrom
perf/topn-heap
Open

Add TopN plan node for O(limit) ORDER BY + LIMIT#24
philcunliffe wants to merge 11 commits intomasterfrom
perf/topn-heap

Conversation

@philcunliffe
Copy link
Copy Markdown
Contributor

Summary

  • Fuses Sort + Limit into a TopN node using a bounded binary max-heap
  • ORDER BY x LIMIT N now buffers only N rows instead of the entire dataset
  • Planner detects Limit(Sort(...)) and Limit(Project(Sort(...))) patterns
  • Depends on Eagerly materialize row cells during sort buffering #23 (eager sort materialization) for materializeRow

Test plan

  • All 1322 existing tests pass (plan expectations updated for new TopN shape)
  • TopN results identical to Sort+Limit for all sort directions and types

philcunliffe and others added 6 commits April 9, 2026 16:45
Add multi-level caching and reduce per-row overhead:

- parseSql: LRU cache (64 entries) avoids re-tokenizing/parsing same SQL strings
- planSql: WeakMap cache on parsed ASTs avoids re-planning identical queries
- asyncRow: attach _data field for zero-copy collection
- collect: sync fast-path skips Promise.all when all rows have pre-materialized _data
- executeProject: pre-compute static column names, fast-path for simple identifier
  projections with direct cell passthrough and _data propagation
- executeSql: skip table normalization when no array tables are present
- compareForTerm: use module-level Set instead of per-call array allocation
- memorySource: hoist column computation outside scan loop, use Set for validation
- Add _data to AsyncRow type definition
- Cast to DerivedColumn/IdentifierNode where type narrowing is needed
- Type _data as Record<string, SqlPrimitive>
- Fix JSDoc placement for compareForTerm
Adapt optimizations to the new QueryResults return type:
- executeSql: keep table normalization skip, use new inline plan+execute
- executeProject: move pre-computation outside rows(), keep identifier
  fast-path and static column names inside the rows() generator
- Add _data to AsyncRow type definition
- Fix JSDoc placement and type casts for tsc
Drop the parseSql/planSql memoization caches added in 881a031. Also
rename the pre-materialized row payload from `_data` to `resolved` for
clarity, and delete stale scratch files (query-parquet.mjs, repro-525.mjs).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Resolves all cell values when rows are buffered for ORDER BY, replacing
AsyncRow closures (which capture decompressed parquet row group data)
with plain value-returning functions. The original closures become
GC-eligible immediately.

For tables with large text columns (~10KB/row), this reduces per-row
buffer cost from ~10KB (closure over parquet data) to ~100B (plain value).
Fuses Sort + Limit into a TopN node that uses a bounded binary
max-heap. ORDER BY x LIMIT N now buffers only N rows instead of
the entire dataset.

The planner detects two patterns:
  - Limit(Sort(child)) → TopN(child)
  - Limit(Project(Sort(child))) → Project(TopN(child))
philcunliffe and others added 2 commits April 13, 2026 12:12
# Conflicts:
#	src/execute/execute.js
#	src/execute/sort.js
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@philcunliffe philcunliffe marked this pull request as ready for review April 13, 2026 19:39
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants