Skip to content

Push Down Offset to TableScan - #25404

Draft
martin-g wants to merge 8 commits into
apache:mainfrom
martin-g:TableProvider-recommend-scan_with_args
Draft

martin-g wants to merge 8 commits into
apache:mainfrom
martin-g:TableProvider-recommend-scan_with_args

Conversation

@martin-g

Copy link
Copy Markdown
Member

Which issue does this PR close?

Rationale for this change

Currently TableProvider provides the limit argument to the scan() method to request a maximum number of rows. There is no way to tell the implementation to skip some of the rows, for example to fetch the second/third/Nth page of rows (i.e. SQL ... LIMIT 20 OFFSET 40).

Adding an additional field to ScanArgs (named offset or skip) will make it possible for implementations to override the scan_with_args() method and optimize their scan to read and return only the requested rows.

What changes are included in this PR?

  • A new field named offset is added to ScanArgs, with a setter and a getter.
  • A new method is added to the TableProvider trait - supports_offset_pushdown() -> bool. By default it returns false but any implementation that can support skipping of rows could override it to return true and combined with a custom implementation of scan_with_args() to optimise its data scan/read.
  • Update some callers of TableProvider::scan() to use ::scan_with_args() where they could support offset push down
  • Update the migration guide for 56.0.0 with a section about the offset pushdown support

Note: datafusion-ffi is not updated because it does not expose scan_with_args() yet.

What is the testing strategy for this PR?

New unit tests are added for the implementations which support offset pushdown.

Are there any user-facing changes?

The new functionality is opt-in! All currently existing custom implementations of TableProvider trait will continue to compile and run without any modifications.
Any custom implementation that wants to make use of the new functionality will need to override TableProvider::supports_offset_pushdown() to return true and make use of ScanArgs::offset in its scan_with_args() implementation.

@github-actions github-actions Bot added documentation Improvements or additions to documentation logical-expr Logical plan and expressions optimizer Optimizer rules core Core DataFusion crate catalog Related to the catalog crate labels Sep 17, 2026
@github-actions

github-actions Bot commented Sep 17, 2026

Copy link
Copy Markdown

Thank you for opening this pull request!

Reviewer note: cargo-semver-checks reported the current version number is not SemVer-compatible with the changes in this pull request (compared against the base branch).

Details
     Cloning apache/main
    Building datafusion v55.1.0 (current)
       Built [  62.905s] (current)
     Parsing datafusion v55.1.0 (current)
      Parsed [   0.035s] (current)
    Building datafusion v55.1.0 (baseline)
       Built [  62.610s] (baseline)
     Parsing datafusion v55.1.0 (baseline)
      Parsed [   0.037s] (baseline)
    Checking datafusion v55.1.0 -> v55.1.0 (no change; assume patch)
     Checked [   0.564s] 223 checks: 223 pass, 31 skip
     Summary no semver update required
    Finished [ 128.011s] datafusion
    Building datafusion-catalog v55.1.0 (current)
       Built [  43.382s] (current)
     Parsing datafusion-catalog v55.1.0 (current)
      Parsed [   0.024s] (current)
    Building datafusion-catalog v55.1.0 (baseline)
       Built [  43.062s] (baseline)
     Parsing datafusion-catalog v55.1.0 (baseline)
      Parsed [   0.026s] (baseline)
    Checking datafusion-catalog v55.1.0 -> v55.1.0 (no change; assume patch)
     Checked [   0.118s] 223 checks: 223 pass, 31 skip
     Summary no semver update required
    Finished [  87.718s] datafusion-catalog
    Building datafusion-catalog-listing v55.1.0 (current)
       Built [  43.341s] (current)
     Parsing datafusion-catalog-listing v55.1.0 (current)
      Parsed [   0.011s] (current)
    Building datafusion-catalog-listing v55.1.0 (baseline)
       Built [  42.977s] (baseline)
     Parsing datafusion-catalog-listing v55.1.0 (baseline)
      Parsed [   0.012s] (baseline)
    Checking datafusion-catalog-listing v55.1.0 -> v55.1.0 (no change; assume patch)
     Checked [   0.087s] 223 checks: 223 pass, 31 skip
     Summary no semver update required
    Finished [  87.521s] datafusion-catalog-listing
    Building datafusion-expr v55.1.0 (current)
       Built [  30.513s] (current)
     Parsing datafusion-expr v55.1.0 (current)
      Parsed [   0.080s] (current)
    Building datafusion-expr v55.1.0 (baseline)
       Built [  30.626s] (baseline)
     Parsing datafusion-expr v55.1.0 (baseline)
      Parsed [   0.081s] (baseline)
    Checking datafusion-expr v55.1.0 -> v55.1.0 (no change; assume patch)
     Checked [   1.193s] 223 checks: 222 pass, 1 fail, 0 warn, 31 skip

--- failure constructible_struct_adds_field: struct exhaustively constructible through public API adds field ---

Description:
A pub struct that could be exhaustively constructed with a literal using only public API has a new pub field, breaking existing exhaustive literals.
        ref: https://doc.rust-lang.org/reference/expressions/struct-expr.html
       impl: https://github.com/obi1kenobi/cargo-semver-checks/tree/v0.50.0/src/lints/constructible_struct_adds_field.ron

Failed in:
  field TableScan.offset in /home/runner/work/datafusion/datafusion/datafusion/expr/src/logical_plan/plan.rs:3138
  field TableScan.offset in /home/runner/work/datafusion/datafusion/datafusion/expr/src/logical_plan/plan.rs:3138

     Summary semver requires new major version: 1 major and 0 minor checks failed
    Finished [  63.840s] datafusion-expr
    Building datafusion-optimizer v55.1.0 (current)
       Built [  29.754s] (current)
     Parsing datafusion-optimizer v55.1.0 (current)
      Parsed [   0.032s] (current)
    Building datafusion-optimizer v55.1.0 (baseline)
       Built [  29.592s] (baseline)
     Parsing datafusion-optimizer v55.1.0 (baseline)
      Parsed [   0.033s] (baseline)
    Checking datafusion-optimizer v55.1.0 -> v55.1.0 (no change; assume patch)
     Checked [   0.194s] 223 checks: 223 pass, 31 skip
     Summary no semver update required
    Finished [  60.596s] datafusion-optimizer
    Building datafusion-proto v55.1.0 (current)
       Built [  59.671s] (current)
     Parsing datafusion-proto v55.1.0 (current)
      Parsed [   0.019s] (current)
    Building datafusion-proto v55.1.0 (baseline)
       Built [  60.439s] (baseline)
     Parsing datafusion-proto v55.1.0 (baseline)
      Parsed [   0.019s] (baseline)
    Checking datafusion-proto v55.1.0 -> v55.1.0 (no change; assume patch)
     Checked [   0.119s] 223 checks: 223 pass, 31 skip
     Summary no semver update required
    Finished [ 121.538s] datafusion-proto
    Building datafusion-proto-models v55.1.0 (current)
       Built [  27.078s] (current)
     Parsing datafusion-proto-models v55.1.0 (current)
      Parsed [   0.137s] (current)
    Building datafusion-proto-models v55.1.0 (baseline)
       Built [  27.849s] (baseline)
     Parsing datafusion-proto-models v55.1.0 (baseline)
      Parsed [   0.143s] (baseline)
    Checking datafusion-proto-models v55.1.0 -> v55.1.0 (no change; assume patch)
     Checked [   1.956s] 223 checks: 222 pass, 1 fail, 0 warn, 31 skip

--- failure constructible_struct_adds_field: struct exhaustively constructible through public API adds field ---

Description:
A pub struct that could be exhaustively constructed with a literal using only public API has a new pub field, breaking existing exhaustive literals.
        ref: https://doc.rust-lang.org/reference/expressions/struct-expr.html
       impl: https://github.com/obi1kenobi/cargo-semver-checks/tree/v0.50.0/src/lints/constructible_struct_adds_field.ron

Failed in:
  field ViewTableScanNode.fetch in /home/runner/work/datafusion/datafusion/datafusion/proto-models/src/generated/prost.rs:170
  field ViewTableScanNode.offset in /home/runner/work/datafusion/datafusion/datafusion/proto-models/src/generated/prost.rs:173
  field ViewTableScanNode.fetch in /home/runner/work/datafusion/datafusion/datafusion/proto-models/src/generated/prost.rs:170
  field ViewTableScanNode.offset in /home/runner/work/datafusion/datafusion/datafusion/proto-models/src/generated/prost.rs:173
  field CustomTableScanNode.fetch in /home/runner/work/datafusion/datafusion/datafusion/proto-models/src/generated/prost.rs:190
  field CustomTableScanNode.offset in /home/runner/work/datafusion/datafusion/datafusion/proto-models/src/generated/prost.rs:193
  field CustomTableScanNode.fetch in /home/runner/work/datafusion/datafusion/datafusion/proto-models/src/generated/prost.rs:190
  field CustomTableScanNode.offset in /home/runner/work/datafusion/datafusion/datafusion/proto-models/src/generated/prost.rs:193
  field ListingTableScanNode.fetch in /home/runner/work/datafusion/datafusion/datafusion/proto-models/src/generated/prost.rs:128
  field ListingTableScanNode.offset in /home/runner/work/datafusion/datafusion/datafusion/proto-models/src/generated/prost.rs:131
  field ListingTableScanNode.fetch in /home/runner/work/datafusion/datafusion/datafusion/proto-models/src/generated/prost.rs:128
  field ListingTableScanNode.offset in /home/runner/work/datafusion/datafusion/datafusion/proto-models/src/generated/prost.rs:131

     Summary semver requires new major version: 1 major and 0 minor checks failed
    Finished [  58.338s] datafusion-proto-models
    Building datafusion-session v55.1.0 (current)
       Built [  42.168s] (current)
     Parsing datafusion-session v55.1.0 (current)
      Parsed [   0.012s] (current)
    Building datafusion-session v55.1.0 (baseline)
       Built [  42.998s] (baseline)
     Parsing datafusion-session v55.1.0 (baseline)
      Parsed [   0.012s] (baseline)
    Checking datafusion-session v55.1.0 -> v55.1.0 (no change; assume patch)
     Checked [   0.182s] 223 checks: 223 pass, 31 skip
     Summary no semver update required
    Finished [  86.764s] datafusion-session
    Building datafusion-sql v55.1.0 (current)
       Built [  48.133s] (current)
     Parsing datafusion-sql v55.1.0 (current)
      Parsed [   0.034s] (current)
    Building datafusion-sql v55.1.0 (baseline)
       Built [  46.818s] (baseline)
     Parsing datafusion-sql v55.1.0 (baseline)
      Parsed [   0.037s] (baseline)
    Checking datafusion-sql v55.1.0 -> v55.1.0 (no change; assume patch)
     Checked [   0.240s] 223 checks: 223 pass, 31 skip
     Summary no semver update required
    Finished [  96.516s] datafusion-sql
    Building datafusion-substrait v55.1.0 (current)
       Built [ 336.155s] (current)
     Parsing datafusion-substrait v55.1.0 (current)
      Parsed [   0.017s] (current)
    Building datafusion-substrait v55.1.0 (baseline)
       Built [ 341.410s] (baseline)
     Parsing datafusion-substrait v55.1.0 (baseline)
      Parsed [   0.017s] (baseline)
    Checking datafusion-substrait v55.1.0 -> v55.1.0 (no change; assume patch)
     Checked [   0.231s] 223 checks: 223 pass, 31 skip
     Summary no semver update required
    Finished [ 679.881s] datafusion-substrait

@github-actions github-actions Bot added the auto detected api change Auto detected API change label Sep 17, 2026
@codecov-commenter

codecov-commenter commented Sep 17, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 58.60806% with 113 lines in your changes missing coverage. Please review.
✅ Project coverage is 82.30%. Comparing base (e5469e1) to head (861a55f).
⚠️ Report is 7 commits behind head on main.

Files with missing lines Patch % Lines
datafusion/proto-models/src/generated/pbjson.rs 0.00% 78 Missing ⚠️
datafusion/proto/src/logical_plan/mod.rs 44.44% 8 Missing and 2 partials ⚠️
datafusion/core/src/datasource/listing/table.rs 75.86% 0 Missing and 7 partials ⚠️
datafusion/optimizer/src/push_down_limit.rs 81.81% 1 Missing and 5 partials ⚠️
datafusion/sql/src/unparser/plan.rs 33.33% 3 Missing and 1 partial ⚠️
datafusion/expr/src/logical_plan/display.rs 25.00% 1 Missing and 2 partials ⚠️
datafusion/expr/src/logical_plan/builder.rs 95.45% 2 Missing ⚠️
datafusion/expr/src/logical_plan/plan.rs 89.47% 1 Missing and 1 partial ⚠️
...bstrait/src/logical_plan/producer/rel/fetch_rel.rs 90.00% 1 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main   #25404      +/-   ##
==========================================
+ Coverage   82.28%   82.30%   +0.01%     
==========================================
  Files        1137     1137              
  Lines      430211   432078    +1867     
  Branches   430211   432078    +1867     
==========================================
+ Hits       354003   355617    +1614     
- Misses      54784    54953     +169     
- Partials    21424    21508      +84     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@github-actions github-actions Bot added substrait Changes to the substrait crate proto Related to proto crate labels Sep 17, 2026
@github-actions github-actions Bot added the sql SQL Planner label Sep 17, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

auto detected api change Auto detected API change catalog Related to the catalog crate core Core DataFusion crate documentation Improvements or additions to documentation logical-expr Logical plan and expressions optimizer Optimizer rules proto Related to proto crate sql SQL Planner substrait Changes to the substrait crate

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Push Down Offset to TableScan

2 participants