Skip to content

feat: add plan/execute separation to FilteredReadExec#5843

Merged
LuQQiu merged 4 commits intolance-format:mainfrom
LuQQiu:lu/planexec
Jan 30, 2026
Merged

feat: add plan/execute separation to FilteredReadExec#5843
LuQQiu merged 4 commits intolance-format:mainfrom
LuQQiu:lu/planexec

Conversation

@LuQQiu
Copy link
Copy Markdown
Contributor

@LuQQiu LuQQiu commented Jan 29, 2026

Split the FilteredReadExec into plan phrase and execute phrase.
The planned result ( RowAddrTreeMap) can be further split with distributed execution engine's distribute logics.
FilteredReadExec can take the planned result, skip the planning phrase and directly read (+ optional predicate)

@github-actions github-actions Bot added the enhancement New feature or request label Jan 29, 2026
@LuQQiu
Copy link
Copy Markdown
Contributor Author

LuQQiu commented Jan 29, 2026

@hamersaw FYI

@github-actions
Copy link
Copy Markdown
Contributor

PR Review: feat: add plan/execute separation to FilteredReadExec

Summary

This PR introduces a clean separation between planning and execution phases in FilteredReadExec, enabling distributed execution scenarios where the plan can be computed once and split across workers.

Critical Issues (P0/P1)

P1: Missing test coverage for ExecuteOnly mode with filters and indexes

The new test test_plan_execute_equivalence only tests a basic full read without filters. Given the complexity of the plan/execute separation, there should be additional tests covering:

  • ExecuteOnly mode with filters (FilteredReadPlan.filters field)
  • ExecuteOnly mode with indexed queries
  • ExecuteOnly mode with scan_range_after_filter

The existing complex test cases (e.g., test_exact_match_filter_scan_range_after_filter, test_at_least_match_filter_scan_range_after_filter) should have equivalence tests for the ExecuteOnly path.

P1: plan() method always uses partition 0

In FilteredReadExec::plan() (line ~1815-1840 in the diff), the partition is hardcoded to 0:

let (internal_plan, _loaded_fragments) = plan_scan(
    self.dataset.clone(),
    self.options.clone(),
    index_input.clone(),
    0,  // hardcoded partition
    ctx,
)
.await?;

This may be intentional since planning should be partition-independent, but it warrants a doc comment explaining this design decision, especially since the index execution uses the partition parameter.

Minor Observations (not blocking)

  • The FilteredReadPlan struct uses HashMap<u32, Arc<Expr>> for filters, which is not serializable out of the box. If this needs to be sent to distributed workers, serialization support will be needed. This is likely a follow-up concern.

  • Good job adding the equivalence test that verifies metrics match between modes.

Verdict

The core implementation is sound. Addressing the P1 test coverage gap before merge would strengthen confidence in the new ExecuteOnly path.

@codecov
Copy link
Copy Markdown

codecov Bot commented Jan 29, 2026

Codecov Report

❌ Patch coverage is 85.71429% with 29 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
rust/lance/src/io/exec/filtered_read.rs 88.70% 14 Missing and 6 partials ⚠️
rust/lance-core/src/utils/mask.rs 65.38% 8 Missing and 1 partial ⚠️

📢 Thoughts on this report? Let us know!

@LuQQiu LuQQiu force-pushed the lu/planexec branch 2 times, most recently from 2244a3b to a4fb82e Compare January 29, 2026 04:14
@LuQQiu LuQQiu marked this pull request as ready for review January 29, 2026 04:34
@LuQQiu
Copy link
Copy Markdown
Contributor Author

LuQQiu commented Jan 29, 2026

Will have FilteredReadExec serd/deserd in another follow up PRs.

Comment thread rust/lance/src/io/exec/filtered_read.rs Outdated

/// Filter to apply per fragment
/// fragments not here don't need filtering
pub filters: HashMap<u32, Arc<Expr>>,
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Currently for Expr, we serd and deserialize with subtrait which requires ArrowSchema for serd/deser. This prevents the FilteredReadPlan from being serd indepdently.
Another choice is to have lance depends on datafusion-proto (may be big ... ) and have new way to serialize Expr using proto approach.

Copy link
Copy Markdown
Contributor Author

@LuQQiu LuQQiu Jan 29, 2026

Choose a reason for hiding this comment

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

 datafusion-proto:
  use datafusion_proto::bytes::Serializeable;

  // Encode - NO schema
  let bytes = expr.to_bytes()?;

  // Decode - NO schema (just needs function
  registry)
  let expr = Expr::from_bytes_with_registry(&bytes,
   registry)?;

  datafusion-substrait (what Lance uses):
  // Encode - NEEDS schema
  let bytes = encode_substrait(expr, schema,
  state)?;

  // Decode - NEEDS schema
  let expr = parse_substrait(&bytes, schema,
  state).await?;

Substrait is a cross-system standard (works with PyArrow, DuckDB, etc.). But datafusion-proto is simpler for Rust-to-Rust communication.

We will maintain Expr subsrait here. The problem is FilteredReadPlan cannot be serd independently. it needs schema info. we can include schema info in the plan but then it's way too much info.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Serialization and deserialization will be in following up PR because it's not very straightforward

Copy link
Copy Markdown
Contributor

@hamersaw hamersaw left a comment

Choose a reason for hiding this comment

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

Thanks! I'll start building on top of this and merge in any changes (if necessary).

Comment thread rust/lance/src/io/exec/filtered_read.rs Outdated
Comment on lines +62 to +70
#[derive(Clone, Debug)]
pub enum FilteredReadExecMode {
PlanAndExecute {
index_input: Option<Arc<dyn ExecutionPlan>>,
},
ExecuteOnly {
plan: FilteredReadPlan,
},
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

If I understand correctly the reason to have this enum is to modify how the FilteredReadExpr executes to either (1) generate a plan and use that or (2) use an existing FilteredReadPlan (that's provide on creation). I'm finding this makes it a little difficult to parse the call stack. Could we get the same functionality by having a publicly exposed function (ex. get_or_create_plan(index_input: Option<Arc<dyn ExecutionPlan>>)) that either returns the existing plan (stored as Option<FilteredReadPlan> within the FilteredReadExec) or generates a new one and populates that field before returning? We could call this inline in the execute function just to make sure a plan is compiled before execution -- if one was provided on creation (ie. with_plan(...) or deserialization) then it would just be a noop.

Copy link
Copy Markdown
Contributor Author

@LuQQiu LuQQiu Jan 29, 2026

Choose a reason for hiding this comment

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

Good call.
I will remodel to this approach (store as parameter in Exec, and get_or_create_plan)
And the detailed plan ranges can also convert from ranges to roaring bitmap calculation, it may be even easier this way.

- Add FilteredReadPlan struct using RowAddrTreeMap for row selection
- Add get_or_create_plan API for lazy plan computation via OnceCell
- Support providing pre-computed plan to FilteredReadExec::try_new
- Centralize plan creation in get_or_create_plan_impl
- Make RowAddrSelection public in lance-core
@LuQQiu
Copy link
Copy Markdown
Contributor Author

LuQQiu commented Jan 29, 2026

@hamersaw @wjones127 @westonpace PTAL, thanks

Copy link
Copy Markdown
Member

@westonpace westonpace left a comment

Choose a reason for hiding this comment

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

I think my only concern is that the conversion to/from ranges and bitmap might be expensive in some cases (e.g. filters that match most rows) but we can see how it does in benchmarks.

pub filters: HashMap<u32, Arc<Expr>>,
/// Scan range after filter may be applied during planning phase based on index result
/// This is leftover range to apply during execution phase
pub scan_range_after_filter: Option<Range<u64>>,
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.

Is this a logical range / does it include deletions (I should probably know)? It would be good to document that.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

This is a logical range after filter, similar to offset limit, yep, i will modify the document
in the plan_scan, we deal with scan_range_before_filter & deletion vector at the beginning.

Comment thread rust/lance/src/io/exec/filtered_read.rs Outdated
if let Some(to_read) = fragments_to_read.get(&fragment_id) {
if !to_read.is_empty() {
// Convert ranges to bitmap
let bitmap = Self::ranges_to_bitmap(to_read);
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.

Why do we have to go from ranges to bitmap here and then back to ranges later (I guess this is the same question as the above TODO)?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

For external distributed execution, i think returning RowAddrTreeMap (bitmap-based) is more useful because it's easier to split/merge/serialize, and support arbitrary row selection patterns.

For internal local execution, the underlying reader uses read_range which expects Vec<Range>, so ranges is better internally.

The current implementation prioritizes external API flexibility. For the internal path, we could potentially change planning to use bitmap since we're dealing with index results anyway.

Copy link
Copy Markdown
Contributor

@hamersaw hamersaw Jan 30, 2026

Choose a reason for hiding this comment

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

For external distributed execution

If this is the only case I would say lets keep things as lightweight as possible internally (work only in ranges) and we can do the conversion to bitmap in plan_splits if we decide it's useful. Thoughts?

}

/// A fragment with all of its metadata loaded
#[derive(Debug, Clone)]
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.

Maybe we should change fragment to Arc<FileFragment> if we are going to clone this? It has the protobuf metadata which might not be trivial to clone.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Converted, thanks!

@westonpace
Copy link
Copy Markdown
Member

@bench-bot benchmark

@westonpace
Copy link
Copy Markdown
Member

westonpace commented Jan 29, 2026

Benchmark Results for PR #5843

Commit: 3090d4e
Baseline: Up to 20 most recent historical results per benchmark

Summary

  • Total benchmarks: 131
  • 🚀 Improvements: 0
  • ⚠️ Regressions: 2
  • Stable: 129
  • Insufficient data: 0

Flagged Benchmarks (|z-score| > 2.0)

Benchmark PR Result Baseline Mean Baseline Std Dev Z-Score Status
Cosine(f64, auto-vectorized) 407.23 ms 312.55 ms 27.92 ms +3.39 ⚠️ Likely Regressed
Cosine(half::bfloat::bf16, scalar) 7.45 s 7.17 s 104.95 ms +2.69 ⚠️ Likely Regressed

All Results

View all 131 benchmark results
Benchmark PR Result Baseline Mean Baseline N Z-Score Status
Cosine(f32, auto-vectorized) 116.32 ms 118.81 ms 20 -0.14
Cosine(f32, scalar) 683.93 ms 665.02 ms 20 +1.25
Cosine(f64, auto-vectorized) 407.23 ms 312.55 ms 20 +3.39 ⚠️
Cosine(f64, scalar) 697.01 ms 682.39 ms 20 +0.76
Cosine(half::bfloat::bf16, auto-vectorized) 271.48 ms 266.08 ms 20 +1.10
Cosine(half::bfloat::bf16, scalar) 7.45 s 7.17 s 20 +2.69 ⚠️
Cosine(half::binary16::f16, auto-vectorized) 124.11 ms 124.74 ms 20 -0.13
Cosine(half::binary16::f16, scalar) 6.31 s 6.09 s 20 +1.62
Cosine(simd,f32x8) rng seed 2.85 ms 2.87 ms 20 -0.30
Dot(bf16, auto-vectorization) 413.39 ms 400.58 ms 20 +1.14
Dot(f16, SIMD) 75.26 ms 72.16 ms 20 +0.49
Dot(f32, SIMD) 102.59 ms 110.02 ms 20 -0.49
Dot(f32, arrow_artiy) 633.30 ms 633.03 ms 20 +0.02
Dot(f32, auto-vectorization) 102.36 ms 109.92 ms 20 -0.61
Dot(f64, arrow_artiy) 643.92 ms 642.37 ms 20 +0.14
Dot(f64, auto-vectorization) 203.92 ms 209.56 ms 20 -0.23
Dot(half::binary16::f16, arrow_artiy) 740.38 ms 720.10 ms 20 +0.95
Dot(half::binary16::f16, auto-vectorization) 75.11 ms 73.02 ms 20 +0.41
L2(f32, auto-vectorization) 106.29 ms 106.95 ms 20 -0.05
L2(f32, scalar) 626.25 ms 642.22 ms 20 -1.34
L2(f32, simd) 102.99 ms 111.08 ms 20 -0.49
L2(f64, auto-vectorization) 205.38 ms 219.66 ms 20 -0.39
L2(f64, scalar) 661.58 ms 655.35 ms 20 +0.54
L2(half::binary16::f16, auto-vectorization) 73.84 ms 74.87 ms 20 -0.16
L2(half::binary16::f16, scalar) 2.83 s 2.79 s 20 +0.61
L2(simd,f32x8) 4.59 ms 4.55 ms 20 +0.30
L2(uint8, auto-vectorization) 58.89 ms 55.23 ms 20 +0.96
L2(uint8, scalar) 52.51 ms 50.88 ms 20 +0.50
NormL2(f32, SIMD) 96.49 ms 106.27 ms 20 -0.58
NormL2(f32, auto-vectorization) 103.04 ms 107.66 ms 20 -0.26
NormL2(f32, scalar) 628.45 ms 637.69 ms 20 -0.67
NormL2(f64, auto-vectorization) 209.76 ms 220.80 ms 20 -0.27
NormL2(f64, scalar) 622.69 ms 645.28 ms 20 -1.45
NormL2(half::bfloat::bf16, auto-vectorization) 256.99 ms 252.66 ms 20 +0.76
NormL2(half::bfloat::bf16, scalar) 2.66 s 2.72 s 20 -0.92
NormL2(half::binary16::f16, SIMD) 60.33 ms 62.05 ms 20 -0.32
NormL2(half::binary16::f16, auto-vectorization) 66.02 ms 63.11 ms 20 +0.36
NormL2(half::binary16::f16, scalar) 658.50 ms 657.50 ms 20 +0.06
argmin(arrow) 4.94 ms 4.75 ms 20 +1.75
decode_compressed/lz4_strings_10cols 977.52 ms 968.97 ms 20 +0.29
decode_compressed/zstd_strings_10cols 2.56 s 2.53 s 20 +0.50
decode_compressed_parallel/lz4_10cols_parallel_1 212.13 ms 204.65 ms 20 +0.70
decode_compressed_parallel/lz4_10cols_parallel_8 63.00 ms 65.12 ms 20 -0.75
decode_compressed_parallel/zstd_10cols_parallel_1 514.49 ms 512.70 ms 20 +0.11
decode_compressed_parallel/zstd_10cols_parallel_8 141.51 ms 144.25 ms 20 -0.55
decode_fsl/float32_128_v2.0_nullfalse 28.51 ms 30.67 ms 20 -1.02
decode_fsl/float32_128_v2.1_nullfalse 11.34 ms 13.23 ms 20 -0.96
decode_fsl/float32_128_v2.1_nulltrue 24.25 ms 25.89 ms 20 -0.59
decode_fsl/float32_16_v2.0_nullfalse 27.82 ms 30.65 ms 20 -1.41
decode_fsl/float32_16_v2.1_nullfalse 18.39 ms 19.89 ms 20 -0.62
decode_fsl/float32_16_v2.1_nulltrue 28.61 ms 33.08 ms 20 -1.30
decode_fsl/float32_32_v2.0_nullfalse 27.87 ms 30.64 ms 20 -1.37
decode_fsl/float32_32_v2.1_nullfalse 18.51 ms 21.35 ms 20 -0.95
decode_fsl/float32_32_v2.1_nulltrue 26.50 ms 28.93 ms 20 -0.65
decode_fsl/float32_4_v2.0_nullfalse 27.93 ms 30.74 ms 20 -1.65
decode_fsl/float32_4_v2.1_nullfalse 18.59 ms 20.93 ms 20 -0.84
decode_fsl/float32_4_v2.1_nulltrue 50.45 ms 52.65 ms 20 -0.65
decode_fsl/float32_64_v2.0_nullfalse 28.14 ms 30.70 ms 20 -1.14
decode_fsl/float32_64_v2.1_nullfalse 11.48 ms 13.29 ms 20 -1.05
decode_fsl/float32_64_v2.1_nulltrue 23.13 ms 27.87 ms 20 -1.52
decode_fsl/int8_128_v2.0_nullfalse 28.25 ms 30.58 ms 20 -1.45
decode_fsl/int8_128_v2.1_nullfalse 18.65 ms 19.82 ms 20 -0.55
decode_fsl/int8_128_v2.1_nulltrue 27.34 ms 28.62 ms 20 -0.36
decode_fsl/int8_16_v2.0_nullfalse 28.66 ms 30.68 ms 20 -1.21
decode_fsl/int8_16_v2.1_nullfalse 18.39 ms 20.40 ms 20 -0.78
decode_fsl/int8_16_v2.1_nulltrue 48.36 ms 51.04 ms 20 -0.86
decode_fsl/int8_32_v2.0_nullfalse 28.33 ms 30.82 ms 20 -1.58
decode_fsl/int8_32_v2.1_nullfalse 18.92 ms 20.31 ms 20 -0.54
decode_fsl/int8_32_v2.1_nulltrue 36.38 ms 37.37 ms 20 -0.50
decode_fsl/int8_4_v2.0_nullfalse 28.46 ms 30.89 ms 20 -1.13
decode_fsl/int8_4_v2.1_nullfalse 19.55 ms 22.24 ms 20 -1.02
decode_fsl/int8_4_v2.1_nulltrue 135.39 ms 138.18 ms 20 -0.55
decode_fsl/int8_64_v2.0_nullfalse 28.39 ms 30.74 ms 20 -1.40
decode_fsl/int8_64_v2.1_nullfalse 18.20 ms 20.54 ms 20 -0.81
decode_fsl/int8_64_v2.1_nulltrue 28.96 ms 31.25 ms 20 -0.75
decode_primitive/date32 11.96 ms 13.85 ms 20 -0.90
decode_primitive/date64 11.41 ms 13.96 ms 20 -1.09
decode_primitive/decimal128(10, 10) 12.76 ms 15.10 ms 20 -1.05
decode_primitive/decimal256(10, 10) 27.26 ms 32.08 ms 20 -1.05
decode_primitive/duration(second) 11.84 ms 14.49 ms 20 -1.66
decode_primitive/fixed-utf8 64.26 µs 76.60 µs 20 -1.49
decode_primitive/float16 13.24 ms 14.44 ms 20 -0.94
decode_primitive/float32 14.84 ms 15.04 ms 20 -0.11
decode_primitive/float64 12.71 ms 15.15 ms 20 -1.04
decode_primitive/int16 11.43 ms 14.17 ms 20 -1.40
decode_primitive/int32 11.44 ms 14.64 ms 20 -1.55
decode_primitive/int64 11.51 ms 14.93 ms 20 -1.20
decode_primitive/int8 11.51 ms 14.38 ms 20 -1.26
decode_primitive/struct 141.58 µs 148.67 µs 20 -0.57
decode_primitive/time32(second) 13.35 ms 14.67 ms 20 -0.86
decode_primitive/time64(nanosecond) 11.98 ms 14.50 ms 20 -1.35
decode_primitive/timestamp(nanosecond, none) 12.48 ms 14.85 ms 20 -0.98
decode_primitive/uint16 11.55 ms 14.58 ms 20 -1.34
decode_primitive/uint32 17.33 ms 15.65 ms 20 +0.68
decode_primitive/uint64 12.04 ms 14.83 ms 20 -1.21
decode_primitive/uint8 11.52 ms 14.54 ms 20 -1.49
decode_primitive/utf8 759.47 µs 761.83 µs 20 -0.07
encode_compressed/lz4_strings_10cols 4.35 s 4.26 s 20 +0.62
encode_compressed/zstd_strings_10cols 6.42 s 6.34 s 20 +0.38
from_elem/full_read,parallel=1,read_size=1048576 36.45 ms 37.96 ms 20 -0.40
from_elem/full_read,parallel=1,read_size=16384 237.55 ms 276.56 ms 20 -0.82
from_elem/full_read,parallel=1,read_size=4096 838.14 ms 904.25 ms 20 -0.55
from_elem/full_read,parallel=16,read_size=1048576 39.18 ms 37.73 ms 20 +0.36
from_elem/full_read,parallel=16,read_size=16384 234.19 ms 277.35 ms 20 -0.94
from_elem/full_read,parallel=16,read_size=4096 828.96 ms 903.59 ms 20 -0.57
from_elem/full_read,parallel=32,read_size=1048576 37.57 ms 38.29 ms 20 -0.16
from_elem/full_read,parallel=32,read_size=16384 235.27 ms 282.72 ms 20 -0.89
from_elem/full_read,parallel=32,read_size=4096 827.19 ms 913.53 ms 20 -0.60
from_elem/full_read,parallel=64,read_size=1048576 45.04 ms 38.09 ms 20 +1.67
from_elem/full_read,parallel=64,read_size=16384 236.39 ms 279.21 ms 20 -0.87
from_elem/full_read,parallel=64,read_size=4096 851.83 ms 910.22 ms 20 -0.45
from_elem/random_read,parallel=1,item_size=1024 3.76 ms 3.95 ms 20 -0.29
from_elem/random_read,parallel=1,item_size=4096 5.51 ms 5.23 ms 20 +0.26
from_elem/random_read,parallel=1,item_size=8 1.47 ms 1.68 ms 20 -1.08
from_elem/random_read,parallel=16,item_size=1024 3.67 ms 3.93 ms 20 -0.38
from_elem/random_read,parallel=16,item_size=4096 4.33 ms 5.26 ms 20 -0.83
from_elem/random_read,parallel=16,item_size=8 1.48 ms 1.65 ms 20 -0.88
from_elem/random_read,parallel=32,item_size=1024 3.63 ms 3.94 ms 20 -0.50
from_elem/random_read,parallel=32,item_size=4096 4.15 ms 5.18 ms 20 -1.01
from_elem/random_read,parallel=32,item_size=8 1.53 ms 1.67 ms 20 -0.62
from_elem/random_read,parallel=64,item_size=1024 3.70 ms 3.86 ms 20 -0.28
from_elem/random_read,parallel=64,item_size=4096 4.27 ms 5.13 ms 20 -0.87
from_elem/random_read,parallel=64,item_size=8 1.44 ms 1.67 ms 20 -1.10
hamming,auto_vec 75.73 ms 75.72 ms 20 +0.00
hamming,scalar 117.90 ms 117.60 ms 20 +0.09
zip_1024Ki/2_2_2_zip_into_6 6.10 ms 5.96 ms 20 +0.44
zip_1024Ki/2_4_zip_into_6 4.52 ms 4.62 ms 20 -0.54
zip_32Ki/2_2_2_zip_into_6 184.22 µs 195.03 µs 20 -0.40
zip_32Ki/2_4_zip_into_6 158.48 µs 142.46 µs 20 +1.63
zip_8Ki/2_2_2_zip_into_6 41.44 µs 47.94 µs 20 -1.01
zip_8Ki/2_4_zip_into_6 39.34 µs 35.98 µs 20 +1.12

Generated by bench-bot 🤖

LuQQiu and others added 3 commits January 29, 2026 17:27
- Add FilteredReadInternalPlan (private) using BTreeMap<u32, Vec<Range<u64>>>
  for efficient local execution without bitmap conversion
- Keep FilteredReadPlan (public) using RowAddrTreeMap for distributed execution
- Local path: plan_scan() → internal plan → ScopedFragmentRead (zero conversions)
- External API: get_or_create_plan() converts internal → external once
- with_plan() converts external → internal for distributed workers
- Add bitmap_to_ranges() utility in lance-core for efficient bitmap conversion
- Use BTreeMap for rows to maintain deterministic fragment order

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
@LuQQiu
Copy link
Copy Markdown
Contributor Author

LuQQiu commented Jan 30, 2026

For local execution, no conversion of bitmap & vec, for remote execution, when fetch plan and when provide plan we will each do a conversion. Compare to other overhead of remote execution, this extra bitmap - vec conversion is neglectable

@LuQQiu LuQQiu merged commit 305983c into lance-format:main Jan 30, 2026
28 checks passed
vivek-bharathan pushed a commit to vivek-bharathan/lance that referenced this pull request Feb 2, 2026
)

Split the FilteredReadExec into plan phrase and execute phrase.
The planned result ( RowAddrTreeMap) can be further split with
distributed execution engine's distribute logics.
FilteredReadExec can take the planned result, skip the planning phrase
and directly read (+ optional predicate)

---------

Co-authored-by: Claude <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request python

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants