Skip to content

feat: add lazy Arrow and database viewing v2 - #1876

Open
Fred-Wu wants to merge 11 commits into
REditorSupport:mainfrom
Fred-Wu:lazy-dataview
Open

Fred-Wu wants to merge 11 commits into
REditorSupport:mainfrom
Fred-Wu:lazy-dataview

Conversation

@Fred-Wu

@Fred-Wu Fred-Wu commented Oct 15, 2026 •

Copy link
Copy Markdown
Contributor

Closes #1785

This PR is a clean replacement for #1804. It includes the implementation and subsequent fixes, incorporates the latest master, and reorganises the changes into two commits: DBI support with shared viewer infrastructure, followed by Arrow support.

I am submitting both implementations for review and discussion; they do not necessarily need to be adopted together.

Summary

Add on-demand Data Viewer support for Arrow datasets, arrow_dplyr_query objects, and DBI-backed tbl_sql objects, currently targeting SQL Server using dbplyr for database dialect handling.

Opening and scrolling do not require collecting the complete result into an R data frame. Paging, filtering, and sorting use different strategies for datasets, bare database tables, and transformed lazy queries.

Arrow datasets

  • Reuse a persistent reader for sequential scrolling and cached rows for backward access.
  • Use fragment-aware access for multi-file datasets and Parquet row groups for suitable distant or sparse requests.
  • Select requested rows and columns before converting Arrow batches into R data.
  • Preserve partition columns and support nested columns, with scanner fallback for nested Parquet schemas.
  • Compute filtering and sorting across the full dataset using the required key columns and source-row indices.
  • Preserve supported types, including signed integer64, dates, datetimes, and durations. Disable filtering and sorting for nested columns.

SQL Server Database tables

  • Support bare database tables represented by tbl_sql.
  • Execute viewer filtering and sorting in SQL Server.
  • Disable viewer filtering and sorting for database tables and queries.
  • Discover primary keys where available to provide stable paging order.
  • Use dbplyr to generate dialect-appropriate metadata and query SQL.
  • Reuse an open DBI result for sequential reads and use OFFSET when repositioning outside the cache, fetching 5,000-row batches and using cached results for backward access.
  • Preserve temporal and signed 64-bit values during paging.
  • Require the optional arrow package for disk caching, including bare tables.

Transformed lazy queries

For both arrow_dplyr_query and transformed database queries:

  • Execute the original query progressively, retaining fetched batches and spilling older batches to temporary Arrow IPC files.
  • Execute database queries using their original top-level SQL, preserving their query semantics and ordering.
  • On the first viewer filter or sort, finish caching the active query execution, then apply the viewer operation to that result.
  • For Arrow queries only, on the first viewer filter or sort, finish caching the active query execution, then apply the viewer operation to that result.
  • Apply subsequent control changes to the original cached result. Clearing the controls restores its original execution order. This applies to Arrow queries only.
  • Reuse cached results for backward access and column projection changes.
  • Reset failed or interrupted reads so retries cannot combine results from different executions.
  • Support unknown row counts until the result is exhausted. Show the actual fetched count with +, then the exact total at completion, including empty results.

Viewer filtering and sorting therefore act as additional operations over the original Arrow query result. They do not rewrite the user's query. Transformed database queries also require All supported database tables and queries require the optional arrow package for disk caching.

Shared viewer behaviour

  • Support column projection and unknown row counts for lazy queries and database tables.
  • Serialise page requests and discard queued requests when the viewer closes.
  • Allow long-running page requests without the normal request timeout through the native session transport; existing Interactive timeouts remain unchanged.
  • Interrupt active reads on close where the owning terminal is available.
  • Clean up readers, database results, and temporary cache files, including before clearing the viewer registry.
  • Keep the session responsive to subsequent requests after an interrupted fetch.

Memory and current limitations

Block caches use a 20,000-row retention threshold, but this is not a strict total-memory limit. Individual Arrow batches can be larger, and Arrow filtering or sorting can require complete key columns and row indices in memory. Cached lazy-query results also consume temporary disk space.

Stable bare-table ordering depends on an available unique key. Database rows retain the order returned by the original query execution; the viewer does not add an ordering clause. Source changes require reopening the viewer.

Large unsigned 64-bit integers and high-precision decimals remain a known precision limitation when converted to R numeric values, affecting display, filtering, and sorting.

Validation

Regression coverage includes sequential and random paging, fragments and row groups, projection, Arrow filtering and sorting, cache spilling, ordering, failure recovery, interruption, disposal, supported column types, and fetched-count transitions to exact totals. Arrow query tests account for platform-dependent batch arrival order.

Local Windows validation passed:

  • 2,570 R checks.
  • R and TypeScript linting.
  • TypeScript type checking.
  • Production build.

Latest local macOS validation passed:

  • 2,662 R checks.
  • 24 viewer tests.
  • R and TypeScript linting.
  • TypeScript compilation.

DBI tests use simulated connections and SQLite, and verify SQL generation and cursor behaviour; they do not replace live SQL Server/ODBC integration testing. macOS and Ubuntu CI results are not claimed by this local validation.

Fred Wu added 2 commits October 15, 2026 23:49
Add SQL Server table paging, primary-key discovery, and cached query execution with viewer filtering and sorting. Preserve temporal and integer64 values through Arrow IPC spill storage.

Add shared column projection, unknown row counts, cancellation, cleanup, recovery, and required CI dependencies. Include DBI and primary-key regression tests.
Add progressive paging, projected random access, and cached query filtering and sorting. Preserve nested and temporal values, recover failed reads, and handle unknown row counts.

Cover fragment and row-group access, spill caches, recovery, and platform-independent Arrow stream ordering in the regression tests.
@Fred-Wu
Fred-Wu marked this pull request as ready for review October 20, 2026 11:45
@Fred-Wu
Fred-Wu requested review from eitsupi and renkun-ken and removed request for renkun-ken October 20, 2026 11:45

@renkun-ken renkun-ken left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed 48bd0e6. I reproduced two DBI cursor/recovery defects, detailed inline: sharing one connection between viewers invalidates their retained results, and an interrupt between fetching and caching can silently omit a batch on retry.

Validation against this head:

  • Built the extension, compiled TypeScript, and passed TypeScript lint.
  • Installed the exact generated dist/resources/sess into an isolated R library; all 1,614 checks across the eight Data Viewer R test files passed with Arrow, DBI, dbplyr, and RSQLite available.
  • All 24 focused Data Viewer / viewer-session extension tests passed in VS Code.
  • Reproduced both findings separately with real SQLite; the second uses deterministic interrupt injection immediately after a successful fetch.
  • All five current CI jobs are green.

The existing SQLite coverage exhausts each small query before opening the next viewer, and the interruption coverage injects inside dbFetch(), so neither scenario is covered by those passing tests.

Comment thread sess/R/dataview-dbi.R Outdated
Comment on lines +167 to +170
if (is.null(cache_state$result)) {
cache_state$result <- DBI::dbSendQuery(
state$dbi$con, state$dbi$query_sql
)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[P2] Coordinate retained results across viewers sharing a connection

This retains a result on the user's original connection between page requests, but supported drivers such as RSQLite allow only one active result per connection. With a SQLite table containing id = 1:12000, open View(items, title = "A"), fetch its first page, then open View(items, title = "B"). B's metadata query in dataview_dbi_source() closes A's result (Closing open result set, pending rows), and scrolling A past its cached 5,000 rows fails with Invalid result set. Retrying A opens another cursor and invalidates B in turn. An ordinary DBI::dbGetQuery() on that connection has the same effect. Please coordinate cursor lifetimes per connection or isolate viewer executions, and add a real SQLite test with two partially fetched viewers; the current 12-row SQLite fixtures complete before this conflict can occur.

Comment thread sess/R/dataview-dbi.R Outdated
Comment on lines +185 to +187
)
dataview_dbi_result_cache_add(cache_state, block)
if (!nrow(block) || DBI::dbHasCompleted(cache_state$result)) {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[P2] Include cache publication in interrupt recovery

The recovery handlers cover only dbFetch(), although that call has already advanced the cursor when dataview_dbi_result_cache_add() starts. If an interrupt arrives before the fetched block is published, the cursor remains open and result_cache_rows still describes the previous position. I reproduced this with real SQLite (id = 1:12000) by injecting an interrupt condition at entry to dataview_dbi_result_cache_add() after the first successful fetch: the cache still reports zero rows, and retrying startRow = 0, endRow = 2 returns values 5001 and 5002, labeled as rows 1 and 2. This silently drops a batch after cancellation. Please protect the entire fetch/cache/completion transition with cleanup that resets the execution on an error or interrupt, and test interruption after the fetch has succeeded.

 - A second viewer on the same connection is rejected before affecting the first.
 - Separate connections keep independent cursors and caches.
 - Reopening and disposal release resources correctly.
 - External cursor invalidation produces a clear error without silently restarting.
- Clear the cursor and all spill files so retries restart without skipping or mixing rows.
- Add SQLite regression tests for failures after fetching and during cache updates.
# Conflicts:
#	src/session.ts
#	src/test/suite/dataViewerSessions.test.ts
@eitsupi

eitsupi commented Oct 20, 2026

Copy link
Copy Markdown
Member

Shouldn't we prioritize #1879 and close this instead?

@Fred-Wu

Fred-Wu commented Oct 20, 2026

Copy link
Copy Markdown
Contributor Author

Shouldn't we prioritize #1879 and close this instead?

Oh, I thought jdv and sess data viewers would be treated separately for now.

@eitsupi

eitsupi commented Oct 20, 2026

Copy link
Copy Markdown
Member

Oh, sorry for not being clearer.
My intention was to make jdv the sole data viewer backend and remove that functionality from sess.
jdv should be installed in a managed library and is not affected by other R packages.

@Fred-Wu

Fred-Wu commented Oct 20, 2026

Copy link
Copy Markdown
Contributor Author

Oh, sorry for not being clearer. My intention was to make jdv the sole data viewer backend and remove that functionality from sess. jdv should be installed in a managed library and is not affected by other R packages.

I don't disagree. Once the current issues are resolved and the vscode-R integration is working well, it might be worth having a code level review and optimisation on the jdv implementation. It feels like some parts are quite complicated.

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.

(feat) Additional Arrow support

3 participants