From ad1212fb3269d1b02f9218deeae506f58f18df07 Mon Sep 17 00:00:00 2001 From: Xuanwo Date: Tue, 27 Oct 2026 17:35:43 +0800 Subject: [PATCH] chore: update Lance to v15.0.0-beta.2 (#4690) **Blocked on the corrected Lance release:** https://github.com/lance-format/lance/pull/10370 restores the `Send` contract required by downstream Function workers. Refresh this upgrade with that release before merging. Upgrade the Rust, Java, and Python Lance dependencies to https://github.com/lance-format/lance/releases/tag/v15.0.0-beta.2. This brings in bounded vector-index consolidation without retraining (https://github.com/lance-format/lance/pull/10335) and deletion filtering for index entries whose fragment coverage was pruned (https://github.com/lance-format/lance/pull/10325). Synchronize the vendored `lance-arrow` with the release and retain its offset-safe validity patch. The existing `nullable_struct_merge` regression still panics with the unpatched release, so removing that patch would regress nullable nested queries. Refresh both lockfiles, including the `zstd-sys` version required by the new Lance encoding dependency. Update the custom memtable bitmap test adapter to train on row addresses, matching the new bitmap contract; remove its obsolete row-ID conversion. Preserve the Blob APIs' existing missing-row error messages when newer Lance rejects a missing fragment earlier in the read path. Retain upstream attribution in vendored sources while satisfying the repository license-header check. Validation of the compatibility fixes: all 58 Rust Blob integration tests and 219 Python Blob tests pass (one Python test skipped). Strict workspace Clippy, formatting, and diff checks pass. --- Cargo.lock | 163 +++-- Cargo.toml | 30 +- java/pom.xml | 2 +- python/pyproject.toml | 4 +- python/uv.lock | 202 +++--- python/vendor/lance-arrow/Cargo.toml | 5 +- python/vendor/lance-arrow/README.lancedb.md | 4 +- python/vendor/lance-arrow/src/lib.rs | 737 ++++++++++++++++++-- python/vendor/lance-arrow/src/stream.rs | 93 +-- rust/lancedb/Cargo.toml | 2 +- rust/lancedb/src/blob.rs | 2 + rust/lancedb/src/table/merge.rs | 20 +- 12 files changed, 957 insertions(+), 307 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 2844ae735..f189ba871 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -373,7 +373,7 @@ dependencies = [ "arrow-select", "flatbuffers", "lz4_flex", - "zstd", + "zstd 0.13.3", ] [[package]] @@ -3584,8 +3584,8 @@ checksum = "42703706b716c37f96a77aea830392ad231f44c9e9a67872fa5548707e11b11c" [[package]] name = "fsst" -version = "15.0.0-beta.1" -source = "git+https://github.com/lance-format/lance.git?tag=v15.0.0-beta.1#46a7cfe1fba5efa1f90deea4c7b14d414b991409" +version = "15.0.0-beta.2" +source = "git+https://github.com/lance-format/lance.git?tag=v15.0.0-beta.2#f50a157269d925ad5708c7b0f7ec6c87b0fd08dd" dependencies = [ "arrow-array", "rand 0.9.5", @@ -5059,8 +5059,8 @@ checksum = "e037a2e1d8d5fdbd49b16a4ea09d5d6401c1f29eca5ff29d03d3824dba16256a" [[package]] name = "lance" -version = "15.0.0-beta.1" -source = "git+https://github.com/lance-format/lance.git?tag=v15.0.0-beta.1#46a7cfe1fba5efa1f90deea4c7b14d414b991409" +version = "15.0.0-beta.2" +source = "git+https://github.com/lance-format/lance.git?tag=v15.0.0-beta.2#f50a157269d925ad5708c7b0f7ec6c87b0fd08dd" dependencies = [ "arc-swap", "arrow", @@ -5135,7 +5135,7 @@ dependencies = [ [[package]] name = "lance-arrow" -version = "15.0.0-beta.1" +version = "15.0.0-beta.2" dependencies = [ "arrow-array", "arrow-buffer", @@ -5157,7 +5157,7 @@ dependencies = [ [[package]] name = "lance-arrow-scalar" version = "58.0.0" -source = "git+https://github.com/lance-format/lance.git?tag=v15.0.0-beta.1#46a7cfe1fba5efa1f90deea4c7b14d414b991409" +source = "git+https://github.com/lance-format/lance.git?tag=v15.0.0-beta.2#f50a157269d925ad5708c7b0f7ec6c87b0fd08dd" dependencies = [ "arrow-array", "arrow-buffer", @@ -5171,7 +5171,7 @@ dependencies = [ [[package]] name = "lance-arrow-stats" version = "58.0.0" -source = "git+https://github.com/lance-format/lance.git?tag=v15.0.0-beta.1#46a7cfe1fba5efa1f90deea4c7b14d414b991409" +source = "git+https://github.com/lance-format/lance.git?tag=v15.0.0-beta.2#f50a157269d925ad5708c7b0f7ec6c87b0fd08dd" dependencies = [ "arrow-array", "arrow-schema", @@ -5180,8 +5180,8 @@ dependencies = [ [[package]] name = "lance-bitpacking" -version = "15.0.0-beta.1" -source = "git+https://github.com/lance-format/lance.git?tag=v15.0.0-beta.1#46a7cfe1fba5efa1f90deea4c7b14d414b991409" +version = "15.0.0-beta.2" +source = "git+https://github.com/lance-format/lance.git?tag=v15.0.0-beta.2#f50a157269d925ad5708c7b0f7ec6c87b0fd08dd" dependencies = [ "arrayref", "crunchy", @@ -5191,8 +5191,8 @@ dependencies = [ [[package]] name = "lance-core" -version = "15.0.0-beta.1" -source = "git+https://github.com/lance-format/lance.git?tag=v15.0.0-beta.1#46a7cfe1fba5efa1f90deea4c7b14d414b991409" +version = "15.0.0-beta.2" +source = "git+https://github.com/lance-format/lance.git?tag=v15.0.0-beta.2#f50a157269d925ad5708c7b0f7ec6c87b0fd08dd" dependencies = [ "arrow-array", "arrow-buffer", @@ -5229,8 +5229,8 @@ dependencies = [ [[package]] name = "lance-datafusion" -version = "15.0.0-beta.1" -source = "git+https://github.com/lance-format/lance.git?tag=v15.0.0-beta.1#46a7cfe1fba5efa1f90deea4c7b14d414b991409" +version = "15.0.0-beta.2" +source = "git+https://github.com/lance-format/lance.git?tag=v15.0.0-beta.2#f50a157269d925ad5708c7b0f7ec6c87b0fd08dd" dependencies = [ "arrow", "arrow-array", @@ -5246,10 +5246,11 @@ dependencies = [ "datafusion-functions", "datafusion-physical-expr", "futures", + "geodatafusion", "half", "lance-arrow", "lance-core", - "lance-geo", + "lance-datafusion-exec", "lance-jsonb", "log", "pin-project", @@ -5259,10 +5260,36 @@ dependencies = [ "tracing", ] +[[package]] +name = "lance-datafusion-exec" +version = "15.0.0-beta.2" +source = "git+https://github.com/lance-format/lance.git?tag=v15.0.0-beta.2#f50a157269d925ad5708c7b0f7ec6c87b0fd08dd" +dependencies = [ + "arrow", + "arrow-array", + "arrow-schema", + "async-trait", + "datafusion-common", + "datafusion-execution", + "datafusion-expr", + "datafusion-functions", + "datafusion-physical-expr", + "datafusion-physical-plan", + "futures", + "half", + "lance-arrow", + "lance-core", + "lance-jsonb", + "log", + "pin-project", + "tokio", + "tracing", +] + [[package]] name = "lance-datagen" -version = "15.0.0-beta.1" -source = "git+https://github.com/lance-format/lance.git?tag=v15.0.0-beta.1#46a7cfe1fba5efa1f90deea4c7b14d414b991409" +version = "15.0.0-beta.2" +source = "git+https://github.com/lance-format/lance.git?tag=v15.0.0-beta.2#f50a157269d925ad5708c7b0f7ec6c87b0fd08dd" dependencies = [ "arrow", "arrow-array", @@ -5279,8 +5306,8 @@ dependencies = [ [[package]] name = "lance-derive" -version = "15.0.0-beta.1" -source = "git+https://github.com/lance-format/lance.git?tag=v15.0.0-beta.1#46a7cfe1fba5efa1f90deea4c7b14d414b991409" +version = "15.0.0-beta.2" +source = "git+https://github.com/lance-format/lance.git?tag=v15.0.0-beta.2#f50a157269d925ad5708c7b0f7ec6c87b0fd08dd" dependencies = [ "proc-macro2", "quote", @@ -5289,8 +5316,8 @@ dependencies = [ [[package]] name = "lance-encoding" -version = "15.0.0-beta.1" -source = "git+https://github.com/lance-format/lance.git?tag=v15.0.0-beta.1#46a7cfe1fba5efa1f90deea4c7b14d414b991409" +version = "15.0.0-beta.2" +source = "git+https://github.com/lance-format/lance.git?tag=v15.0.0-beta.2#f50a157269d925ad5708c7b0f7ec6c87b0fd08dd" dependencies = [ "arrow-arith", "arrow-array", @@ -5315,16 +5342,17 @@ dependencies = [ "num-traits", "prost", "prost-build", + "serde_json", "tokio", "tracing", "xxhash-rust", - "zstd", + "zstd 0.14.3", ] [[package]] name = "lance-file" -version = "15.0.0-beta.1" -source = "git+https://github.com/lance-format/lance.git?tag=v15.0.0-beta.1#46a7cfe1fba5efa1f90deea4c7b14d414b991409" +version = "15.0.0-beta.2" +source = "git+https://github.com/lance-format/lance.git?tag=v15.0.0-beta.2#f50a157269d925ad5708c7b0f7ec6c87b0fd08dd" dependencies = [ "arrow-arith", "arrow-array", @@ -5356,23 +5384,21 @@ dependencies = [ [[package]] name = "lance-geo" -version = "15.0.0-beta.1" -source = "git+https://github.com/lance-format/lance.git?tag=v15.0.0-beta.1#46a7cfe1fba5efa1f90deea4c7b14d414b991409" +version = "15.0.0-beta.2" +source = "git+https://github.com/lance-format/lance.git?tag=v15.0.0-beta.2#f50a157269d925ad5708c7b0f7ec6c87b0fd08dd" dependencies = [ - "datafusion", "geo-traits", "geo-types", "geoarrow-array", "geoarrow-schema", - "geodatafusion", "lance-core", "serde", ] [[package]] name = "lance-index" -version = "15.0.0-beta.1" -source = "git+https://github.com/lance-format/lance.git?tag=v15.0.0-beta.1#46a7cfe1fba5efa1f90deea4c7b14d414b991409" +version = "15.0.0-beta.2" +source = "git+https://github.com/lance-format/lance.git?tag=v15.0.0-beta.2#f50a157269d925ad5708c7b0f7ec6c87b0fd08dd" dependencies = [ "arc-swap", "arrow", @@ -5389,10 +5415,13 @@ dependencies = [ "blake3", "bytes", "crossbeam-queue", - "datafusion", "datafusion-common", + "datafusion-execution", "datafusion-expr", + "datafusion-functions", + "datafusion-functions-nested", "datafusion-physical-expr", + "datafusion-physical-plan", "dirs", "fst", "futures", @@ -5408,7 +5437,7 @@ dependencies = [ "lance-arrow-stats", "lance-bitpacking", "lance-core", - "lance-datafusion", + "lance-datafusion-exec", "lance-encoding", "lance-file", "lance-geo", @@ -5445,16 +5474,16 @@ dependencies = [ [[package]] name = "lance-index-core" -version = "15.0.0-beta.1" -source = "git+https://github.com/lance-format/lance.git?tag=v15.0.0-beta.1#46a7cfe1fba5efa1f90deea4c7b14d414b991409" +version = "15.0.0-beta.2" +source = "git+https://github.com/lance-format/lance.git?tag=v15.0.0-beta.2#f50a157269d925ad5708c7b0f7ec6c87b0fd08dd" dependencies = [ "arrow-array", "arrow-schema", "arrow-select", "async-trait", "bytes", - "datafusion", "datafusion-common", + "datafusion-execution", "datafusion-expr", "futures", "lance-core", @@ -5469,8 +5498,8 @@ dependencies = [ [[package]] name = "lance-io" -version = "15.0.0-beta.1" -source = "git+https://github.com/lance-format/lance.git?tag=v15.0.0-beta.1#46a7cfe1fba5efa1f90deea4c7b14d414b991409" +version = "15.0.0-beta.2" +source = "git+https://github.com/lance-format/lance.git?tag=v15.0.0-beta.2#f50a157269d925ad5708c7b0f7ec6c87b0fd08dd" dependencies = [ "arrow", "arrow-array", @@ -5513,8 +5542,8 @@ dependencies = [ [[package]] name = "lance-jsonb" -version = "15.0.0-beta.1" -source = "git+https://github.com/lance-format/lance.git?tag=v15.0.0-beta.1#46a7cfe1fba5efa1f90deea4c7b14d414b991409" +version = "15.0.0-beta.2" +source = "git+https://github.com/lance-format/lance.git?tag=v15.0.0-beta.2#f50a157269d925ad5708c7b0f7ec6c87b0fd08dd" dependencies = [ "byteorder", "ethnum", @@ -5531,8 +5560,8 @@ dependencies = [ [[package]] name = "lance-linalg" -version = "15.0.0-beta.1" -source = "git+https://github.com/lance-format/lance.git?tag=v15.0.0-beta.1#46a7cfe1fba5efa1f90deea4c7b14d414b991409" +version = "15.0.0-beta.2" +source = "git+https://github.com/lance-format/lance.git?tag=v15.0.0-beta.2#f50a157269d925ad5708c7b0f7ec6c87b0fd08dd" dependencies = [ "arrow-array", "arrow-schema", @@ -5546,8 +5575,8 @@ dependencies = [ [[package]] name = "lance-namespace" -version = "15.0.0-beta.1" -source = "git+https://github.com/lance-format/lance.git?tag=v15.0.0-beta.1#46a7cfe1fba5efa1f90deea4c7b14d414b991409" +version = "15.0.0-beta.2" +source = "git+https://github.com/lance-format/lance.git?tag=v15.0.0-beta.2#f50a157269d925ad5708c7b0f7ec6c87b0fd08dd" dependencies = [ "arrow", "async-trait", @@ -5561,8 +5590,8 @@ dependencies = [ [[package]] name = "lance-namespace-impls" -version = "15.0.0-beta.1" -source = "git+https://github.com/lance-format/lance.git?tag=v15.0.0-beta.1#46a7cfe1fba5efa1f90deea4c7b14d414b991409" +version = "15.0.0-beta.2" +source = "git+https://github.com/lance-format/lance.git?tag=v15.0.0-beta.2#f50a157269d925ad5708c7b0f7ec6c87b0fd08dd" dependencies = [ "arc-swap", "arrow", @@ -5616,8 +5645,8 @@ dependencies = [ [[package]] name = "lance-select" -version = "15.0.0-beta.1" -source = "git+https://github.com/lance-format/lance.git?tag=v15.0.0-beta.1#46a7cfe1fba5efa1f90deea4c7b14d414b991409" +version = "15.0.0-beta.2" +source = "git+https://github.com/lance-format/lance.git?tag=v15.0.0-beta.2#f50a157269d925ad5708c7b0f7ec6c87b0fd08dd" dependencies = [ "arrow-array", "arrow-buffer", @@ -5631,8 +5660,8 @@ dependencies = [ [[package]] name = "lance-table" -version = "15.0.0-beta.1" -source = "git+https://github.com/lance-format/lance.git?tag=v15.0.0-beta.1#46a7cfe1fba5efa1f90deea4c7b14d414b991409" +version = "15.0.0-beta.2" +source = "git+https://github.com/lance-format/lance.git?tag=v15.0.0-beta.2#f50a157269d925ad5708c7b0f7ec6c87b0fd08dd" dependencies = [ "arrow", "arrow-array", @@ -5672,8 +5701,8 @@ dependencies = [ [[package]] name = "lance-testing" -version = "15.0.0-beta.1" -source = "git+https://github.com/lance-format/lance.git?tag=v15.0.0-beta.1#46a7cfe1fba5efa1f90deea4c7b14d414b991409" +version = "15.0.0-beta.2" +source = "git+https://github.com/lance-format/lance.git?tag=v15.0.0-beta.2#f50a157269d925ad5708c7b0f7ec6c87b0fd08dd" dependencies = [ "arrow-array", "arrow-schema", @@ -5686,8 +5715,8 @@ dependencies = [ [[package]] name = "lance-tokenizer" -version = "15.0.0-beta.1" -source = "git+https://github.com/lance-format/lance.git?tag=v15.0.0-beta.1#46a7cfe1fba5efa1f90deea4c7b14d414b991409" +version = "15.0.0-beta.2" +source = "git+https://github.com/lance-format/lance.git?tag=v15.0.0-beta.2#f50a157269d925ad5708c7b0f7ec6c87b0fd08dd" dependencies = [ "frostem", "icu_segmenter", @@ -7692,7 +7721,7 @@ dependencies = [ "streaming-iterator", "strength_reduce", "version_check", - "zstd", + "zstd 0.13.3", ] [[package]] @@ -12016,7 +12045,16 @@ version = "0.13.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e91ee311a569c327171651566e07972200e76fcfe2242a4fa446149a3881c08a" dependencies = [ - "zstd-safe", + "zstd-safe 7.2.4", +] + +[[package]] +name = "zstd" +version = "0.14.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db3ae9bf90af6e40197bef767eeefc46ad6397420fe873830bd85e5fbbfb3c70" +dependencies = [ + "zstd-safe 8.2.0", ] [[package]] @@ -12029,10 +12067,19 @@ dependencies = [ ] [[package]] -name = "zstd-sys" -version = "2.0.16+zstd.1.5.7" +name = "zstd-safe" +version = "8.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91e19ebc2adc8f83e43039e79776e3fda8ca919132d68a1fed6a5faca2683748" +checksum = "ded520f16b29e1a9b771c722221b2b0a0398c2316d9bb3c42b458a5414d7434b" +dependencies = [ + "zstd-sys", +] + +[[package]] +name = "zstd-sys" +version = "2.1.2+zstd.1.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "481ada9c543b2323cbb7bbdbb5249177fbf0188ed959ed2c15e4b22421c84ed3" dependencies = [ "cc", "pkg-config", diff --git a/Cargo.toml b/Cargo.toml index 22dec8c0c..40040c329 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -18,21 +18,21 @@ categories = ["database-implementations"] rust-version = "1.94.0" [workspace.dependencies] -lance = { "version" = "=15.0.0-beta.1", default-features = false, "tag" = "v15.0.0-beta.1", "git" = "https://github.com/lance-format/lance.git" } -lance-core = { "version" = "=15.0.0-beta.1", "tag" = "v15.0.0-beta.1", "git" = "https://github.com/lance-format/lance.git" } -lance-datagen = { "version" = "=15.0.0-beta.1", "tag" = "v15.0.0-beta.1", "git" = "https://github.com/lance-format/lance.git" } -lance-file = { "version" = "=15.0.0-beta.1", "tag" = "v15.0.0-beta.1", "git" = "https://github.com/lance-format/lance.git" } -lance-io = { "version" = "=15.0.0-beta.1", default-features = false, "tag" = "v15.0.0-beta.1", "git" = "https://github.com/lance-format/lance.git" } -lance-index = { "version" = "=15.0.0-beta.1", "tag" = "v15.0.0-beta.1", "git" = "https://github.com/lance-format/lance.git" } -lance-jsonb = { version = "=15.0.0-beta.1", tag = "v15.0.0-beta.1", git = "https://github.com/lance-format/lance.git" } -lance-linalg = { "version" = "=15.0.0-beta.1", "tag" = "v15.0.0-beta.1", "git" = "https://github.com/lance-format/lance.git" } -lance-namespace = { "version" = "=15.0.0-beta.1", "tag" = "v15.0.0-beta.1", "git" = "https://github.com/lance-format/lance.git" } -lance-namespace-impls = { "version" = "=15.0.0-beta.1", default-features = false, "tag" = "v15.0.0-beta.1", "git" = "https://github.com/lance-format/lance.git" } -lance-table = { "version" = "=15.0.0-beta.1", "tag" = "v15.0.0-beta.1", "git" = "https://github.com/lance-format/lance.git" } -lance-testing = { "version" = "=15.0.0-beta.1", "tag" = "v15.0.0-beta.1", "git" = "https://github.com/lance-format/lance.git" } -lance-datafusion = { "version" = "=15.0.0-beta.1", "tag" = "v15.0.0-beta.1", "git" = "https://github.com/lance-format/lance.git" } -lance-encoding = { "version" = "=15.0.0-beta.1", "tag" = "v15.0.0-beta.1", "git" = "https://github.com/lance-format/lance.git" } -lance-arrow = { version = "=15.0.0-beta.1", path = "python/vendor/lance-arrow" } +lance = { "version" = "=15.0.0-beta.2", default-features = false, "tag" = "v15.0.0-beta.2", "git" = "https://github.com/lance-format/lance.git" } +lance-core = { "version" = "=15.0.0-beta.2", "tag" = "v15.0.0-beta.2", "git" = "https://github.com/lance-format/lance.git" } +lance-datagen = { "version" = "=15.0.0-beta.2", "tag" = "v15.0.0-beta.2", "git" = "https://github.com/lance-format/lance.git" } +lance-file = { "version" = "=15.0.0-beta.2", "tag" = "v15.0.0-beta.2", "git" = "https://github.com/lance-format/lance.git" } +lance-io = { "version" = "=15.0.0-beta.2", default-features = false, "tag" = "v15.0.0-beta.2", "git" = "https://github.com/lance-format/lance.git" } +lance-index = { "version" = "=15.0.0-beta.2", "tag" = "v15.0.0-beta.2", "git" = "https://github.com/lance-format/lance.git" } +lance-jsonb = { "version" = "=15.0.0-beta.2", "tag" = "v15.0.0-beta.2", "git" = "https://github.com/lance-format/lance.git" } +lance-linalg = { "version" = "=15.0.0-beta.2", "tag" = "v15.0.0-beta.2", "git" = "https://github.com/lance-format/lance.git" } +lance-namespace = { "version" = "=15.0.0-beta.2", "tag" = "v15.0.0-beta.2", "git" = "https://github.com/lance-format/lance.git" } +lance-namespace-impls = { "version" = "=15.0.0-beta.2", default-features = false, "tag" = "v15.0.0-beta.2", "git" = "https://github.com/lance-format/lance.git" } +lance-table = { "version" = "=15.0.0-beta.2", "tag" = "v15.0.0-beta.2", "git" = "https://github.com/lance-format/lance.git" } +lance-testing = { "version" = "=15.0.0-beta.2", "tag" = "v15.0.0-beta.2", "git" = "https://github.com/lance-format/lance.git" } +lance-datafusion = { "version" = "=15.0.0-beta.2", "tag" = "v15.0.0-beta.2", "git" = "https://github.com/lance-format/lance.git" } +lance-encoding = { "version" = "=15.0.0-beta.2", "tag" = "v15.0.0-beta.2", "git" = "https://github.com/lance-format/lance.git" } +lance-arrow = { version = "=15.0.0-beta.2", path = "python/vendor/lance-arrow" } lancedb = { path = "rust/lancedb", default-features = false } ahash = "0.8" # Note that this one does not include pyarrow diff --git a/java/pom.xml b/java/pom.xml index e3cfb2781..96262a8e9 100644 --- a/java/pom.xml +++ b/java/pom.xml @@ -28,7 +28,7 @@ UTF-8 15.0.0 - 15.0.0-beta.1 + 15.0.0-beta.2 2.18.11 false 2.30.0 diff --git a/python/pyproject.toml b/python/pyproject.toml index a0de6509c..ac9281248 100644 --- a/python/pyproject.toml +++ b/python/pyproject.toml @@ -46,7 +46,7 @@ repository = "https://github.com/lancedb/lancedb" [project.optional-dependencies] pylance = [ # Keep the minimum in the bundled Lance major and compatible with tests below. - "pylance>=15.0.0b1", + "pylance>=15.0.0b2", ] # A library only needs the OpenTelemetry API; the application supplies and # configures the SDK (the actual exporter/reader). See @@ -64,7 +64,7 @@ tests = [ "polars>=0.19, <=1.32.3", "pyarrow<25", "pyarrow-stubs>=16.0", - "pylance==15.0.0b1", + "pylance==15.0.0b2", "requests>=2.31.0", "datafusion>=54,<55", "opentelemetry-sdk>=1.30.0", diff --git a/python/uv.lock b/python/uv.lock index 99bcb439c..329a9ef24 100644 --- a/python/uv.lock +++ b/python/uv.lock @@ -1,5 +1,5 @@ version = 1 -revision = 3 +revision = 5 requires-python = ">=3.10" resolution-markers = [ "python_full_version >= '3.14' and sys_platform == 'win32'", @@ -799,7 +799,7 @@ name = "cuda-bindings" version = "13.3.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "cuda-pathfinder", marker = "(python_full_version < '3.14' and sys_platform == 'emscripten') or (python_full_version < '3.14' and sys_platform == 'win32') or (sys_platform != 'emscripten' and sys_platform != 'win32')" }, + { name = "cuda-pathfinder" }, ] wheels = [ { url = "https://files.pythonhosted.org/packages/a9/21/8464d133752951c154feafb3b65c297e7d80f301183d220bec4c830f1441/cuda_bindings-13.3.1-cp310-cp310-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:120fcc53d57903df529c3486962c56528cba5b7d6c57c99537320ed9922c8b86", size = 6073403, upload-time = "2026-05-29T23:11:36.22Z" }, @@ -834,37 +834,37 @@ wheels = [ [package.optional-dependencies] cublas = [ - { name = "nvidia-cublas", marker = "(python_full_version < '3.14' and sys_platform == 'win32') or sys_platform == 'linux'" }, + { name = "nvidia-cublas" }, ] cudart = [ - { name = "nvidia-cuda-runtime", marker = "(python_full_version < '3.14' and sys_platform == 'win32') or sys_platform == 'linux'" }, + { name = "nvidia-cuda-runtime" }, ] cufft = [ - { name = "nvidia-cufft", marker = "(python_full_version < '3.14' and sys_platform == 'win32') or sys_platform == 'linux'" }, + { name = "nvidia-cufft" }, ] cufile = [ - { name = "nvidia-cufile", marker = "sys_platform == 'linux'" }, + { name = "nvidia-cufile" }, ] cupti = [ - { name = "nvidia-cuda-cupti", marker = "(python_full_version < '3.14' and sys_platform == 'win32') or sys_platform == 'linux'" }, + { name = "nvidia-cuda-cupti" }, ] curand = [ - { name = "nvidia-curand", marker = "(python_full_version < '3.14' and sys_platform == 'win32') or sys_platform == 'linux'" }, + { name = "nvidia-curand" }, ] cusolver = [ - { name = "nvidia-cusolver", marker = "(python_full_version < '3.14' and sys_platform == 'win32') or sys_platform == 'linux'" }, + { name = "nvidia-cusolver" }, ] cusparse = [ - { name = "nvidia-cusparse", marker = "(python_full_version < '3.14' and sys_platform == 'win32') or sys_platform == 'linux'" }, + { name = "nvidia-cusparse" }, ] nvjitlink = [ - { name = "nvidia-nvjitlink", marker = "(python_full_version < '3.14' and sys_platform == 'win32') or sys_platform == 'linux'" }, + { name = "nvidia-nvjitlink" }, ] nvrtc = [ - { name = "nvidia-cuda-nvrtc", marker = "(python_full_version < '3.14' and sys_platform == 'win32') or sys_platform == 'linux'" }, + { name = "nvidia-cuda-nvrtc" }, ] nvtx = [ - { name = "nvidia-nvtx", marker = "(python_full_version < '3.14' and sys_platform == 'win32') or sys_platform == 'linux'" }, + { name = "nvidia-nvtx" }, ] [[package]] @@ -1023,7 +1023,7 @@ name = "exceptiongroup" version = "1.3.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "typing-extensions", marker = "python_full_version < '3.11'" }, + { name = "typing-extensions" }, ] sdist = { url = "https://files.pythonhosted.org/packages/50/79/66800aadf48771f6b62f7eb014e352e5d06856655206165d775e675a02c9/exceptiongroup-1.3.1.tar.gz", hash = "sha256:8b412432c6055b0b7d14c310000ae93352ed6754f70fa8f7c34141f91c4e3219", size = 30371, upload-time = "2025-11-21T23:01:54.787Z" } wheels = [ @@ -1440,16 +1440,16 @@ resolution-markers = [ "python_full_version < '3.11'", ] dependencies = [ - { name = "cachetools", marker = "python_full_version < '3.11'" }, - { name = "certifi", marker = "python_full_version < '3.11'" }, - { name = "httpx", marker = "python_full_version < '3.11'" }, - { name = "ibm-cos-sdk", marker = "python_full_version < '3.11'" }, - { name = "lomond", marker = "python_full_version < '3.11'" }, - { name = "packaging", marker = "python_full_version < '3.11'" }, - { name = "pandas", version = "2.2.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, - { name = "requests", marker = "python_full_version < '3.11'" }, - { name = "tabulate", marker = "python_full_version < '3.11'" }, - { name = "urllib3", marker = "python_full_version < '3.11'" }, + { name = "cachetools" }, + { name = "certifi" }, + { name = "httpx" }, + { name = "ibm-cos-sdk" }, + { name = "lomond" }, + { name = "packaging" }, + { name = "pandas", version = "2.2.3", source = { registry = "https://pypi.org/simple" } }, + { name = "requests" }, + { name = "tabulate" }, + { name = "urllib3" }, ] sdist = { url = "https://files.pythonhosted.org/packages/c7/56/2e3df38a1f13062095d7bde23c87a92f3898982993a15186b1bfecbd206f/ibm_watsonx_ai-1.3.42.tar.gz", hash = "sha256:ee5be59009004245d957ce97d1227355516df95a2640189749487614fef674ff", size = 688651, upload-time = "2025-10-01T13:35:41.527Z" } wheels = [ @@ -1468,17 +1468,17 @@ resolution-markers = [ "python_full_version == '3.11.*'", ] dependencies = [ - { name = "cachetools", marker = "python_full_version >= '3.11'" }, - { name = "certifi", marker = "python_full_version >= '3.11'" }, - { name = "httpx", marker = "python_full_version >= '3.11'" }, - { name = "ibm-cos-sdk", marker = "python_full_version >= '3.11'" }, - { name = "lomond", marker = "python_full_version >= '3.11'" }, - { name = "packaging", marker = "python_full_version >= '3.11'" }, - { name = "pandas", version = "2.3.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11' and python_full_version < '3.14'" }, + { name = "cachetools" }, + { name = "certifi" }, + { name = "httpx" }, + { name = "ibm-cos-sdk" }, + { name = "lomond" }, + { name = "packaging" }, + { name = "pandas", version = "2.3.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.14'" }, { name = "pandas", version = "3.0.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.14'" }, - { name = "requests", marker = "python_full_version >= '3.11'" }, - { name = "tabulate", marker = "python_full_version >= '3.11'" }, - { name = "urllib3", marker = "python_full_version >= '3.11'" }, + { name = "requests" }, + { name = "tabulate" }, + { name = "urllib3" }, ] sdist = { url = "https://files.pythonhosted.org/packages/29/a3/c756b534696ab2f3f29882fdb7ca7198b7a5c94e10c0a3a327853d6d6b79/ibm_watsonx_ai-1.5.14.tar.gz", hash = "sha256:a756488bd57e87c0fc51be42dcba871143cfe0ac1e805c497c5047e1e4f13e9d", size = 735804, upload-time = "2026-06-22T12:32:43.85Z" } wheels = [ @@ -1554,17 +1554,17 @@ resolution-markers = [ "python_full_version < '3.11'", ] dependencies = [ - { name = "colorama", marker = "python_full_version < '3.11' and sys_platform == 'win32'" }, - { name = "decorator", marker = "python_full_version < '3.11'" }, - { name = "exceptiongroup", marker = "python_full_version < '3.11'" }, - { name = "jedi", marker = "python_full_version < '3.11'" }, - { name = "matplotlib-inline", marker = "python_full_version < '3.11'" }, - { name = "pexpect", marker = "python_full_version < '3.11' and sys_platform != 'emscripten' and sys_platform != 'win32'" }, - { name = "prompt-toolkit", marker = "python_full_version < '3.11'" }, - { name = "pygments", marker = "python_full_version < '3.11'" }, - { name = "stack-data", marker = "python_full_version < '3.11'" }, - { name = "traitlets", marker = "python_full_version < '3.11'" }, - { name = "typing-extensions", marker = "python_full_version < '3.11'" }, + { name = "colorama", marker = "sys_platform == 'win32'" }, + { name = "decorator" }, + { name = "exceptiongroup" }, + { name = "jedi" }, + { name = "matplotlib-inline" }, + { name = "pexpect", marker = "sys_platform != 'emscripten' and sys_platform != 'win32'" }, + { name = "prompt-toolkit" }, + { name = "pygments" }, + { name = "stack-data" }, + { name = "traitlets" }, + { name = "typing-extensions" }, ] sdist = { url = "https://files.pythonhosted.org/packages/40/18/f8598d287006885e7136451fdea0755af4ebcbfe342836f24deefaed1164/ipython-8.39.0.tar.gz", hash = "sha256:4110ae96012c379b8b6db898a07e186c40a2a1ef5d57a7fa83166047d9da7624", size = 5513971, upload-time = "2026-03-27T10:02:13.94Z" } wheels = [ @@ -1583,18 +1583,18 @@ resolution-markers = [ "python_full_version == '3.11.*'", ] dependencies = [ - { name = "colorama", marker = "python_full_version >= '3.11' and sys_platform == 'win32'" }, - { name = "decorator", marker = "python_full_version >= '3.11'" }, - { name = "ipython-pygments-lexers", marker = "python_full_version >= '3.11'" }, - { name = "jedi", marker = "python_full_version >= '3.11'" }, - { name = "matplotlib-inline", marker = "python_full_version >= '3.11'" }, - { name = "pexpect", marker = "python_full_version >= '3.11' and sys_platform != 'emscripten' and sys_platform != 'win32'" }, - { name = "prompt-toolkit", marker = "python_full_version >= '3.11'" }, - { name = "psutil", marker = "python_full_version >= '3.11' and sys_platform != 'cygwin' and sys_platform != 'emscripten'" }, - { name = "pygments", marker = "python_full_version >= '3.11'" }, - { name = "stack-data", marker = "python_full_version >= '3.11'" }, - { name = "traitlets", marker = "python_full_version >= '3.11'" }, - { name = "typing-extensions", marker = "python_full_version == '3.11.*'" }, + { name = "colorama", marker = "sys_platform == 'win32'" }, + { name = "decorator" }, + { name = "ipython-pygments-lexers" }, + { name = "jedi" }, + { name = "matplotlib-inline" }, + { name = "pexpect", marker = "sys_platform != 'emscripten' and sys_platform != 'win32'" }, + { name = "prompt-toolkit" }, + { name = "psutil", marker = "sys_platform != 'cygwin' and sys_platform != 'emscripten'" }, + { name = "pygments" }, + { name = "stack-data" }, + { name = "traitlets" }, + { name = "typing-extensions", marker = "python_full_version < '3.12'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/53/59/165d3b4d75cc34add3122c4417ecb229085140ac573103c223cd01dde96f/ipython-9.15.0.tar.gz", hash = "sha256:da2819ce2aa83135257df830660b1176d986c3d2876db24df01974fa955b2756", size = 4442580, upload-time = "2026-06-26T11:03:35.913Z" } wheels = [ @@ -1606,7 +1606,7 @@ name = "ipython-pygments-lexers" version = "1.1.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pygments", marker = "python_full_version >= '3.11'" }, + { name = "pygments" }, ] sdist = { url = "https://files.pythonhosted.org/packages/ef/4c/5dd1d8af08107f88c7f741ead7a40854b8ac24ddf9ae850afbcf698aa552/ipython_pygments_lexers-1.1.1.tar.gz", hash = "sha256:09c0138009e56b6854f9535736f4171d855c8c08a563a0dcd8022f78355c7e81", size = 8393, upload-time = "2025-01-17T11:24:34.505Z" } wheels = [ @@ -1854,9 +1854,9 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "lance-namespace-urllib3-client" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/c0/e7/d5d46594678ee479c0eda830c47b2f5c46133bd100e84e9aa6e01306eca7/lance_namespace-0.13.0.tar.gz", hash = "sha256:24554a0997bdb39595c6e4cb3ac6722069f6cd3bd1a74e7acbba7bfaf774a40d", size = 11636, upload-time = "2026-09-14T02:47:03.871523Z" } +sdist = { url = "https://files.pythonhosted.org/packages/c0/e7/d5d46594678ee479c0eda830c47b2f5c46133bd100e84e9aa6e01306eca7/lance_namespace-0.13.0.tar.gz", hash = "sha256:24554a0997bdb39595c6e4cb3ac6722069f6cd3bd1a74e7acbba7bfaf774a40d", size = 11636, upload-time = "2026-09-14T02:47:03.871Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/e7/47/cfa33cca1ba7c749fd2918cfc5c8ded788f378cc6424d23e4fead5a14125/lance_namespace-0.13.0-py3-none-any.whl", hash = "sha256:438c7b17aef421c21c138196e715f2510d62f07e865372047f87c1e75e618c7a", size = 13507, upload-time = "2026-09-14T02:47:01.765655Z" }, + { url = "https://files.pythonhosted.org/packages/e7/47/cfa33cca1ba7c749fd2918cfc5c8ded788f378cc6424d23e4fead5a14125/lance_namespace-0.13.0-py3-none-any.whl", hash = "sha256:438c7b17aef421c21c138196e715f2510d62f07e865372047f87c1e75e618c7a", size = 13507, upload-time = "2026-09-14T02:47:01.765Z" }, ] [[package]] @@ -1869,9 +1869,9 @@ dependencies = [ { name = "typing-extensions" }, { name = "urllib3" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/69/25/9aaa4a5e8999693fb0f227c2c0c4b97bd8f0539066408cdff0b89d41b2d5/lance_namespace_urllib3_client-0.13.0.tar.gz", hash = "sha256:1e8a79c6e4e6277033597fd76aa0e2f33d909ca1436c935936e9e569221f43ef", size = 238348, upload-time = "2026-09-14T02:47:04.521878Z" } +sdist = { url = "https://files.pythonhosted.org/packages/69/25/9aaa4a5e8999693fb0f227c2c0c4b97bd8f0539066408cdff0b89d41b2d5/lance_namespace_urllib3_client-0.13.0.tar.gz", hash = "sha256:1e8a79c6e4e6277033597fd76aa0e2f33d909ca1436c935936e9e569221f43ef", size = 238348, upload-time = "2026-09-14T02:47:04.521Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/60/4c/7b8f0712a7fe1b8655b711342bc4989696635a79e7027ee56ba3cae23e99/lance_namespace_urllib3_client-0.13.0-py3-none-any.whl", hash = "sha256:fb361eb4f6c7f2d1f9e92809609657b73e38241393e9c4516e12d6b6ee643a0a", size = 406848, upload-time = "2026-09-14T02:47:02.715954Z" }, + { url = "https://files.pythonhosted.org/packages/60/4c/7b8f0712a7fe1b8655b711342bc4989696635a79e7027ee56ba3cae23e99/lance_namespace_urllib3_client-0.13.0-py3-none-any.whl", hash = "sha256:fb361eb4f6c7f2d1f9e92809609657b73e38241393e9c4516e12d6b6ee643a0a", size = 406848, upload-time = "2026-09-14T02:47:02.715Z" }, ] [[package]] @@ -2006,8 +2006,8 @@ requires-dist = [ { name = "pyarrow", marker = "extra == 'tests'", specifier = "<25" }, { name = "pyarrow-stubs", marker = "extra == 'tests'", specifier = ">=16.0" }, { name = "pydantic", specifier = ">=2.7.4,<3" }, - { name = "pylance", marker = "extra == 'pylance'", specifier = ">=15.0.0b1" }, - { name = "pylance", marker = "extra == 'tests'", specifier = "==15.0.0b1" }, + { name = "pylance", marker = "extra == 'pylance'", specifier = ">=15.0.0b2" }, + { name = "pylance", marker = "extra == 'tests'", specifier = "==15.0.0b2" }, { name = "pyright", marker = "extra == 'dev'", specifier = ">=1.1.350" }, { name = "pytest", marker = "extra == 'tests'", specifier = ">=7.0" }, { name = "pytest-asyncio", marker = "extra == 'tests'", specifier = ">=0.21" }, @@ -2884,7 +2884,7 @@ name = "nvidia-cudnn-cu13" version = "9.19.0.56" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "nvidia-cublas", marker = "(python_full_version < '3.14' and sys_platform == 'emscripten') or (python_full_version < '3.14' and sys_platform == 'win32') or (sys_platform != 'emscripten' and sys_platform != 'win32')" }, + { name = "nvidia-cublas" }, ] wheels = [ { url = "https://files.pythonhosted.org/packages/f1/84/26025437c1e6b61a707442184fa0c03d083b661adf3a3eecfd6d21677740/nvidia_cudnn_cu13-9.19.0.56-py3-none-manylinux_2_27_aarch64.whl", hash = "sha256:6ed29ffaee1176c612daf442e4dd6cfeb6a0caa43ddcbeb59da94953030b1be4", size = 433781201, upload-time = "2026-02-03T20:40:53.805Z" }, @@ -2896,7 +2896,7 @@ name = "nvidia-cufft" version = "12.0.0.61" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "nvidia-nvjitlink", marker = "(python_full_version < '3.14' and sys_platform == 'emscripten') or (python_full_version < '3.14' and sys_platform == 'win32') or (sys_platform != 'emscripten' and sys_platform != 'win32')" }, + { name = "nvidia-nvjitlink" }, ] wheels = [ { url = "https://files.pythonhosted.org/packages/8b/ae/f417a75c0259e85c1d2f83ca4e960289a5f814ed0cea74d18c353d3e989d/nvidia_cufft-12.0.0.61-py3-none-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:2708c852ef8cd89d1d2068bdbece0aa188813a0c934db3779b9b1faa8442e5f5", size = 214053554, upload-time = "2025-09-04T08:31:38.196Z" }, @@ -2926,9 +2926,9 @@ name = "nvidia-cusolver" version = "12.0.4.66" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "nvidia-cublas", marker = "(python_full_version < '3.14' and sys_platform == 'emscripten') or (python_full_version < '3.14' and sys_platform == 'win32') or (sys_platform != 'emscripten' and sys_platform != 'win32')" }, - { name = "nvidia-cusparse", marker = "(python_full_version < '3.14' and sys_platform == 'emscripten') or (python_full_version < '3.14' and sys_platform == 'win32') or (sys_platform != 'emscripten' and sys_platform != 'win32')" }, - { name = "nvidia-nvjitlink", marker = "(python_full_version < '3.14' and sys_platform == 'emscripten') or (python_full_version < '3.14' and sys_platform == 'win32') or (sys_platform != 'emscripten' and sys_platform != 'win32')" }, + { name = "nvidia-cublas" }, + { name = "nvidia-cusparse" }, + { name = "nvidia-nvjitlink" }, ] wheels = [ { url = "https://files.pythonhosted.org/packages/c8/c3/b30c9e935fc01e3da443ec0116ed1b2a009bb867f5324d3f2d7e533e776b/nvidia_cusolver-12.0.4.66-py3-none-manylinux_2_27_aarch64.whl", hash = "sha256:02c2457eaa9e39de20f880f4bd8820e6a1cfb9f9a34f820eb12a155aa5bc92d2", size = 223467760, upload-time = "2025-09-04T08:33:04.222Z" }, @@ -2940,7 +2940,7 @@ name = "nvidia-cusparse" version = "12.6.3.3" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "nvidia-nvjitlink", marker = "(python_full_version < '3.14' and sys_platform == 'emscripten') or (python_full_version < '3.14' and sys_platform == 'win32') or (sys_platform != 'emscripten' and sys_platform != 'win32')" }, + { name = "nvidia-nvjitlink" }, ] wheels = [ { url = "https://files.pythonhosted.org/packages/f8/94/5c26f33738ae35276672f12615a64bd008ed5be6d1ebcb23579285d960a9/nvidia_cusparse-12.6.3.3-py3-none-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:80bcc4662f23f1054ee334a15c72b8940402975e0eab63178fc7e670aa59472c", size = 162155568, upload-time = "2025-09-04T08:33:42.864Z" }, @@ -3117,10 +3117,10 @@ resolution-markers = [ "python_full_version < '3.11'", ] dependencies = [ - { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, - { name = "python-dateutil", marker = "python_full_version < '3.11'" }, - { name = "pytz", marker = "python_full_version < '3.11'" }, - { name = "tzdata", marker = "python_full_version < '3.11'" }, + { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" } }, + { name = "python-dateutil" }, + { name = "pytz" }, + { name = "tzdata" }, ] sdist = { url = "https://files.pythonhosted.org/packages/9c/d6/9f8431bacc2e19dca897724cd097b1bb224a6ad5433784a44b587c7c13af/pandas-2.2.3.tar.gz", hash = "sha256:4f18ba62b61d7e192368b84517265a99b4d7ee8912f8708660fb4a366cc82667", size = 4399213, upload-time = "2024-09-20T13:10:04.827Z" } wheels = [ @@ -3169,11 +3169,11 @@ resolution-markers = [ "python_full_version == '3.11.*'", ] dependencies = [ - { name = "numpy", version = "2.4.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.11.*'" }, + { name = "numpy", version = "2.4.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.12' or python_full_version >= '3.14'" }, { name = "numpy", version = "2.5.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12' and python_full_version < '3.14'" }, - { name = "python-dateutil", marker = "python_full_version >= '3.11' and python_full_version < '3.14'" }, - { name = "pytz", marker = "python_full_version >= '3.11' and python_full_version < '3.14'" }, - { name = "tzdata", marker = "python_full_version >= '3.11' and python_full_version < '3.14'" }, + { name = "python-dateutil" }, + { name = "pytz" }, + { name = "tzdata" }, ] sdist = { url = "https://files.pythonhosted.org/packages/33/01/d40b85317f86cf08d853a4f495195c73815fdf205eef3993821720274518/pandas-2.3.3.tar.gz", hash = "sha256:e05e1af93b977f7eafa636d043f9f94c7ee3ac81af99c13508215942e64c993b", size = 4495223, upload-time = "2025-09-29T23:34:51.853Z" } wheels = [ @@ -3236,9 +3236,9 @@ resolution-markers = [ "python_full_version >= '3.14' and sys_platform != 'emscripten' and sys_platform != 'win32'", ] dependencies = [ - { name = "numpy", version = "2.5.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.14'" }, - { name = "python-dateutil", marker = "python_full_version >= '3.14'" }, - { name = "tzdata", marker = "(python_full_version >= '3.14' and sys_platform == 'emscripten') or (python_full_version >= '3.14' and sys_platform == 'win32')" }, + { name = "numpy", version = "2.5.1", source = { registry = "https://pypi.org/simple" } }, + { name = "python-dateutil" }, + { name = "tzdata", marker = "sys_platform == 'emscripten' or sys_platform == 'win32'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/f8/87/4341c6252d1c47b08768c3d25ac487362bf403f0313ddae4a2a26c9b1b4c/pandas-3.0.3.tar.gz", hash = "sha256:696a4a00a2a2a35d4e5deb3fc946641b96c944f02230e4f76137fe35d806c4fc", size = 4651414, upload-time = "2026-05-11T18:54:29.21Z" } wheels = [ @@ -3346,7 +3346,7 @@ name = "pexpect" version = "4.9.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "ptyprocess", marker = "(python_full_version < '3.14' and sys_platform == 'emscripten') or (python_full_version < '3.14' and sys_platform == 'win32') or (sys_platform != 'emscripten' and sys_platform != 'win32')" }, + { name = "ptyprocess" }, ] sdist = { url = "https://files.pythonhosted.org/packages/42/92/cc564bf6381ff43ce1f4d06852fc19a2f11d180f23dc32d9588bee2f149d/pexpect-4.9.0.tar.gz", hash = "sha256:ee7d41123f3c9911050ea2c2dac107568dc43b2d3b0c7557a33212c398ead30f", size = 166450, upload-time = "2023-11-25T09:07:26.339Z" } wheels = [ @@ -3938,7 +3938,7 @@ crypto = [ [[package]] name = "pylance" -version = "15.0.0b1" +version = "15.0.0b2" source = { registry = "https://pypi.fury.io/lance-format/" } dependencies = [ { name = "lance-namespace" }, @@ -3948,12 +3948,12 @@ dependencies = [ { name = "pyarrow" }, ] wheels = [ - { url = "https://pypi.fury.io/lance-format/-/ver_23L1Nd/pylance-15.0.0b1-cp310-abi3-macosx_11_0_arm64.whl", hash = "sha256:19605d3daca1224cc0db079054be5d4eec403dcf286bf4617430bfc259ab0a52" }, - { url = "https://pypi.fury.io/lance-format/-/ver_1Ho3RK/pylance-15.0.0b1-cp310-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:be22b0b6f1e1d250ffaa82b449e982c817db33103c483d1005020285310623cf" }, - { url = "https://pypi.fury.io/lance-format/-/ver_1QmyF0/pylance-15.0.0b1-cp310-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a5b2761d60b1b9db112058213f1f7b2de82b08cbe65888fab08b0a56e702482e" }, - { url = "https://pypi.fury.io/lance-format/-/ver_1in2Ph/pylance-15.0.0b1-cp310-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:294a0fedae312ed32d1b66cddbf7e69770ec9b4c82a873af60c619ed88450718" }, - { url = "https://pypi.fury.io/lance-format/-/ver_21xpLH/pylance-15.0.0b1-cp310-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:ff20a16703d4d3f3bc675b25ce1871ba1d54137929dc46305bffdd07f7b35a6b" }, - { url = "https://pypi.fury.io/lance-format/-/ver_AAGzF/pylance-15.0.0b1-cp310-abi3-win_amd64.whl", hash = "sha256:3b134e24a5becfc940ca134cf78aa3bae8b80ec40f8927a1b856273ab76213f2" }, + { url = "https://pypi.fury.io/lance-format/-/ver_5aLfR/pylance-15.0.0b2-cp310-abi3-macosx_11_0_arm64.whl", hash = "sha256:fd6b872e348d804c3188d014f2ff469f8cfaff5bbe846617e665c1e2a2965683" }, + { url = "https://pypi.fury.io/lance-format/-/ver_1W21Yj/pylance-15.0.0b2-cp310-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b0ed217ee71bea57ef97303bd1318ae212ee005c885c16390d7efbaf1f7398e3" }, + { url = "https://pypi.fury.io/lance-format/-/ver_12xJCp/pylance-15.0.0b2-cp310-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:72580d6359db4d471c578daab46a35fd87e74e57a1cb64e333188fea1e1c2f1b" }, + { url = "https://pypi.fury.io/lance-format/-/ver_m4ypB/pylance-15.0.0b2-cp310-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:c194f95e2d2b1c5a9e4f57ed2681a9295effe5aaa165bf924f2b2090094c4645" }, + { url = "https://pypi.fury.io/lance-format/-/ver_1zYkf2/pylance-15.0.0b2-cp310-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:c14532517ed1452d9cb215f33dc4bc3166500e14145c8c6f2f3538b82ac5147b" }, + { url = "https://pypi.fury.io/lance-format/-/ver_xp2do/pylance-15.0.0b2-cp310-abi3-win_amd64.whl", hash = "sha256:ce13e573a1842eda18f9b74eec4c8d1dc60db0895581db3226c6a6b1a8e79a75" }, ] [[package]] @@ -4709,10 +4709,10 @@ resolution-markers = [ "python_full_version < '3.11'", ] dependencies = [ - { name = "joblib", marker = "python_full_version < '3.11'" }, - { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, - { name = "scipy", version = "1.15.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, - { name = "threadpoolctl", marker = "python_full_version < '3.11'" }, + { name = "joblib" }, + { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" } }, + { name = "scipy", version = "1.15.3", source = { registry = "https://pypi.org/simple" } }, + { name = "threadpoolctl" }, ] sdist = { url = "https://files.pythonhosted.org/packages/98/c2/a7855e41c9d285dfe86dc50b250978105dce513d6e459ea66a6aeb0e1e0c/scikit_learn-1.7.2.tar.gz", hash = "sha256:20e9e49ecd130598f1ca38a1d85090e1a600147b9c02fa6f15d69cb53d968fda", size = 7193136, upload-time = "2025-09-09T08:21:29.075Z" } wheels = [ @@ -4760,13 +4760,13 @@ resolution-markers = [ "python_full_version == '3.11.*'", ] dependencies = [ - { name = "joblib", marker = "python_full_version >= '3.11'" }, - { name = "narwhals", marker = "python_full_version >= '3.11'" }, - { name = "numpy", version = "2.4.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.11.*'" }, + { name = "joblib" }, + { name = "narwhals" }, + { name = "numpy", version = "2.4.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.12'" }, { name = "numpy", version = "2.5.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12'" }, - { name = "scipy", version = "1.17.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.11.*'" }, + { name = "scipy", version = "1.17.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.12'" }, { name = "scipy", version = "1.18.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12'" }, - { name = "threadpoolctl", marker = "python_full_version >= '3.11'" }, + { name = "threadpoolctl" }, ] sdist = { url = "https://files.pythonhosted.org/packages/fa/6f/37092bdb25f712817231799fc5674d8e704066a8a70c1d2d40517e18b4ab/scikit_learn-1.9.0.tar.gz", hash = "sha256:8833266989d3a5110178a9fae30783675460724d0e1efb13b14901d2c660c557", size = 7750767, upload-time = "2026-06-02T11:54:32.706Z" } wheels = [ @@ -4810,7 +4810,7 @@ resolution-markers = [ "python_full_version < '3.11'", ] dependencies = [ - { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, + { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" } }, ] sdist = { url = "https://files.pythonhosted.org/packages/0f/37/6964b830433e654ec7485e45a00fc9a27cf868d622838f6b6d9c5ec0d532/scipy-1.15.3.tar.gz", hash = "sha256:eae3cf522bc7df64b42cad3925c876e1b0b6c35c1337c93e12c0f366f55b0eaf", size = 59419214, upload-time = "2025-05-08T16:13:05.955Z" } wheels = [ @@ -4869,7 +4869,7 @@ resolution-markers = [ "python_full_version == '3.11.*'", ] dependencies = [ - { name = "numpy", version = "2.4.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.11.*'" }, + { name = "numpy", version = "2.4.6", source = { registry = "https://pypi.org/simple" } }, ] sdist = { url = "https://files.pythonhosted.org/packages/7a/97/5a3609c4f8d58b039179648e62dd220f89864f56f7357f5d4f45c29eb2cc/scipy-1.17.1.tar.gz", hash = "sha256:95d8e012d8cb8816c226aef832200b1d45109ed4464303e997c5b13122b297c0", size = 30573822, upload-time = "2026-02-23T00:26:24.851Z" } wheels = [ @@ -4946,7 +4946,7 @@ resolution-markers = [ "python_full_version >= '3.12' and python_full_version < '3.14'", ] dependencies = [ - { name = "numpy", version = "2.5.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12'" }, + { name = "numpy", version = "2.5.1", source = { registry = "https://pypi.org/simple" } }, ] sdist = { url = "https://files.pythonhosted.org/packages/a7/25/c2700dfaf6442b4effaa91af24ebce5dc9d31bb4a69706313aae70d72cd0/scipy-1.18.0.tar.gz", hash = "sha256:67b2ad2ad54c72ca6d04975a9b2df8c3638c34ddd5b28738e94fc2b57929d378", size = 30774447, upload-time = "2026-06-19T15:01:43.456Z" } wheels = [ diff --git a/python/vendor/lance-arrow/Cargo.toml b/python/vendor/lance-arrow/Cargo.toml index 9cb156f2e..e9adc478c 100644 --- a/python/vendor/lance-arrow/Cargo.toml +++ b/python/vendor/lance-arrow/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "lance-arrow" -version = "15.0.0-beta.1" +version = "15.0.0-beta.2" edition = "2024" authors = ["Lance Devs "] license = "Apache-2.0" @@ -10,9 +10,6 @@ description = "Arrow Extension for Lance" keywords = ["data-format", "data-science", "machine-learning", "apache-arrow", "data-analytics"] categories = ["database-implementations"] -[lib] -crate-type = ["cdylib", "rlib"] - [dependencies] arrow-array.workspace = true arrow-buffer.workspace = true diff --git a/python/vendor/lance-arrow/README.lancedb.md b/python/vendor/lance-arrow/README.lancedb.md index 5ed714e08..4a9f6176f 100644 --- a/python/vendor/lance-arrow/README.lancedb.md +++ b/python/vendor/lance-arrow/README.lancedb.md @@ -1,5 +1,5 @@ -This directory contains `rust/lance-arrow` from Lance tag `v15.0.0-beta.1` -(commit `46a7cfe1fba5efa1f90deea4c7b14d414b991409`), licensed under Apache-2.0. +This directory contains `rust/lance-arrow` from Lance tag `v15.0.0-beta.2` +(commit `f50a157269d925ad5708c7b0f7ec6c87b0fd08dd`), licensed under Apache-2.0. LanceDB carries a local fix in `src/lib.rs` for sliced child validity bitmaps until a Lance release includes the fix. The patch is selected in the root diff --git a/python/vendor/lance-arrow/src/lib.rs b/python/vendor/lance-arrow/src/lib.rs index f26e1f3e3..6c31cf0c5 100644 --- a/python/vendor/lance-arrow/src/lib.rs +++ b/python/vendor/lance-arrow/src/lib.rs @@ -27,7 +27,7 @@ use arrow_array::{ use arrow_array::{ Float32Array, Float64Array, Int8Array, Int16Array, Int32Array, Int64Array, new_null_array, }; -use arrow_buffer::MutableBuffer; +use arrow_buffer::{BooleanBufferBuilder, MutableBuffer, NullBuffer}; use arrow_data::ArrayDataBuilder; use arrow_schema::{ArrowError, DataType, Field, Fields, IntervalUnit, Schema, SortOptions}; use arrow_select::{interleave::interleave, take::take}; @@ -670,7 +670,7 @@ impl RecordBatchExt for RecordBatch { } let left_struct_array: StructArray = self.clone().into(); let right_struct_array: StructArray = other.clone().into(); - self.try_new_from_struct_array(merge(&left_struct_array, &right_struct_array)) + self.try_new_from_struct_array(merge(&left_struct_array, &right_struct_array)?) } fn merge_with_schema(&self, other: &RecordBatch, schema: &Schema) -> Result { @@ -921,12 +921,44 @@ fn holds_struct(data_type: &DataType) -> bool { } } +/// The values of `list`, with the items of every entry that is null in `list` +/// but valid in `other` made null. Those entries become valid when the two +/// lists are merged, and their items here were never meant to be read. Merging +/// the items then pushes these nulls into their children. +fn hide_items_revealed_by( + list: &GenericListArray, + other: Option<&NullBuffer>, +) -> ArrayRef { + let Some(nulls) = list.nulls() else { + return list.values().clone(); + }; + let revealed = match other { + Some(other) => other.inner() & &!nulls.inner(), + None => !nulls.inner(), + }; + if revealed.count_set_bits() == 0 { + return list.values().clone(); + } + let offsets = list.value_offsets(); + let mut items_validity = BooleanBufferBuilder::new(list.values().len()); + items_validity.append_n(list.values().len(), true); + for row in revealed.set_indices() { + for item in offsets[row].as_usize()..offsets[row + 1].as_usize() { + items_validity.set_bit(item, false); + } + } + adjust_child_validity( + list.values(), + Some(&NullBuffer::new(items_validity.finish())), + ) +} + /// Merge the values of two lists: structs field by field, nested lists /// recursively, anything else keeps the left side. -fn merge_list_values(left: &ArrayRef, right: &ArrayRef) -> ArrayRef { - match (left.data_type(), right.data_type()) { +fn merge_list_values(left: &ArrayRef, right: &ArrayRef) -> Result { + Ok(match (left.data_type(), right.data_type()) { (DataType::Struct(_), DataType::Struct(_)) => { - Arc::new(merge(left.as_struct(), right.as_struct())) + Arc::new(merge(left.as_struct(), right.as_struct())?) } (DataType::List(_), DataType::List(_)) | (DataType::LargeList(_), DataType::LargeList(_)) @@ -934,27 +966,29 @@ fn merge_list_values(left: &ArrayRef, right: &ArrayRef) -> ArrayRef { && holds_struct(left.data_type()) && holds_struct(right.data_type()) => { - merge_list_struct(left, right) + merge_list_struct(left, right, None, None)? } _ => left.clone(), - } + }) } fn merge_list_struct_helper( left: &dyn Array, right: &dyn Array, -) -> Arc { + null_left: bool, + null_right: bool, +) -> Result { let left_list: &GenericListArray = left.as_list(); let right_list: &GenericListArray = right.as_list(); - let item_name = match left.data_type() { - DataType::List(item) | DataType::LargeList(item) => item.name().clone(), - _ => unreachable!(), + let ((DataType::List(left_item), DataType::List(right_item)) + | (DataType::LargeList(left_item), DataType::LargeList(right_item))) = + (left.data_type(), right.data_type()) + else { + unreachable!() }; - // One side all-null (a placeholder for columns this fragment lacks) has - // no values, so stand in nulls shaped like the other side's. - let null_left = left_list.null_count() == left_list.len(); - let null_right = !null_left && right_list.null_count() == right_list.len(); - let (merged_items, list, items_nullable) = if null_left || null_right { + let (merged_items, list, nulls, items_nullable) = if null_left || null_right { + // One side all-null (a placeholder for columns this fragment lacks) has + // no values, so stand in nulls shaped like the other side's. let not_null = if null_left { right_list } else { left_list }; let values_len = not_null.values().len(); let values = |list: &GenericListArray, is_null: bool| { @@ -967,39 +1001,68 @@ fn merge_list_struct_helper( let merged = merge_list_values( &values(left_list, null_left), &values(right_list, null_right), - ); - (merged, not_null, true) + )?; + ( + merged, + not_null, + not_null.nulls().cloned(), + left_item.is_nullable() || right_item.is_nullable(), + ) } else { if left_list.offsets().inner() != right_list.offsets().inner() { panic!("Attempt to merge list struct arrays which do not have same offsets"); } - let merged = merge_list_values(left_list.values(), right_list.values()); - let nullable = match left.data_type() { - DataType::List(item) | DataType::LargeList(item) => item.is_nullable(), - _ => unreachable!(), - }; - (merged, left_list, nullable) + // The lists share offsets, and an entry is valid if either side has it, + // as for struct rows. Each side's items under its own null entries are + // hidden first, so the other side's validity cannot expose them. + let left_items = hide_items_revealed_by(left_list, right_list.nulls()); + let right_items = hide_items_revealed_by(right_list, left_list.nulls()); + let merged = merge_list_values(&left_items, &right_items)?; + ( + merged, + left_list, + merge_struct_validity(left_list.nulls(), right_list.nulls()), + left_item.is_nullable() || right_item.is_nullable(), + ) }; let items_field = Arc::new(Field::new( - item_name, + left_item.name(), merged_items.data_type().clone(), items_nullable, )); - Arc::new(GenericListArray::::new( + Ok(Arc::new(GenericListArray::::new( items_field, list.offsets().clone(), merged_items, - list.nulls().cloned(), - )) + nulls, + ))) } /// Merge two lists whose items hold structs. Only succeeds if both lists have /// the same offsets or one of them is all-null; panics otherwise. -fn merge_list_struct(left: &dyn Array, right: &dyn Array) -> Arc { +/// +/// `left_parent` and `right_parent`, when given, null each side's entries under +/// its null parent rows before the merge. +fn merge_list_struct( + left: &ArrayRef, + right: &ArrayRef, + left_parent: Option<&NullBuffer>, + right_parent: Option<&NullBuffer>, +) -> Result { + // The parents are applied before the all-null side is picked, so a side + // whose rows all sit under null parents counts as the placeholder and the + // other side's offsets are kept. + let left = adjust_child_validity(left, left_parent); + let right = adjust_child_validity(right, right_parent); + let null_left = left.null_count() == left.len(); + let null_right = !null_left && right.null_count() == right.len(); + let (left, right) = (left.as_ref(), right.as_ref()); match (left.data_type(), right.data_type()) { - (DataType::List(_), DataType::List(_)) => merge_list_struct_helper::(left, right), + (DataType::List(_), DataType::List(_)) => { + merge_list_struct_helper::(left, right, null_left, null_right) + } (DataType::LargeList(_), DataType::LargeList(_)) => { - merge_list_struct_helper::(left, right) + merge_list_struct_helper::(left, right, null_left, null_right) } _ => unreachable!(), } @@ -1187,7 +1250,46 @@ fn adjust_child_validity( ) } -fn merge(left_struct_array: &StructArray, right_struct_array: &StructArray) -> StructArray { +/// Fails if `field`, a non-nullable child of one side's struct, sits under a +/// row that is null on that side but valid in `merged`. The merged row is valid +/// because the other side has it, and this side has no value to show there. +fn check_hideable( + field: &Field, + parent: Option<&NullBuffer>, + merged: Option<&NullBuffer>, +) -> Result<()> { + let Some(parent) = parent.filter(|_| !field.is_nullable()) else { + return Ok(()); + }; + let revealed = match merged { + Some(merged) => merged.inner() & &!parent.inner(), + None => !parent.inner(), + }; + match revealed.set_indices().next() { + None => Ok(()), + Some(row) => Err(ArrowError::InvalidArgumentError(format!( + "Cannot merge non-nullable field '{}': its parent is null on one side but valid \ + on the other at row {row}, where the field has no value", + field.name() + ))), + } +} + +/// `column`, a child of one side's struct, with that struct's null rows pushed +/// into it. The merged struct is valid where either side is, so the column +/// would otherwise show, at a row only the other side has, what its own null +/// parent hides. +fn hide_under_parent( + field: &Field, + column: &ArrayRef, + parent: Option<&NullBuffer>, + merged: Option<&NullBuffer>, +) -> Result { + check_hideable(field, parent, merged)?; + Ok(adjust_child_validity(column, parent)) +} + +fn merge(left_struct_array: &StructArray, right_struct_array: &StructArray) -> Result { let mut fields: Vec = vec![]; let mut columns: Vec = vec![]; let right_fields = right_struct_array.fields(); @@ -1199,6 +1301,7 @@ fn merge(left_struct_array: &StructArray, right_struct_array: &StructArray) -> S // Compute merged validity let merged_validity = merge_struct_validity(left_validity, right_validity); + let merged = merged_validity.as_ref(); // iterate through the fields on the left hand side for (left_field, left_column) in left_struct_array @@ -1217,9 +1320,13 @@ fn merge(left_struct_array: &StructArray, right_struct_array: &StructArray) -> S // if both fields are struct, merge them recursively match (left_field.data_type(), right_field.data_type()) { (DataType::Struct(_), DataType::Struct(_)) => { - let left_sub_array = left_column.as_struct(); - let right_sub_array = right_column.as_struct(); - let merged_sub_array = merge(left_sub_array, right_sub_array); + // Both sides feed this column, so a row one side's parent + // hides can still take its value from the other side, and + // the field's nullability is checked on the merged result. + let left_sub_array = adjust_child_validity(left_column, left_validity); + let right_sub_array = adjust_child_validity(right_column, right_validity); + let merged_sub_array = + merge(left_sub_array.as_struct(), right_sub_array.as_struct())?; fields.push(Field::new( left_field.name(), merged_sub_array.data_type().clone(), @@ -1235,12 +1342,22 @@ fn merge(left_struct_array: &StructArray, right_struct_array: &StructArray) -> S // Identical inner types: nothing to merge, keep the left column. if left_list.data_type() == right_list.data_type() { fields.push(left_field.as_ref().clone()); - columns.push(left_column.clone()); + columns.push(hide_under_parent( + left_field, + left_column, + left_validity, + merged, + )?); } else { // The struct fields differ, so merge them structurally. merge_list_struct // only succeeds when both lists share offsets or one side is all-null; // it panics otherwise. - let merged_sub_array = merge_list_struct(&left_column, &right_column); + let merged_sub_array = merge_list_struct( + left_column, + right_column, + left_validity, + right_validity, + )?; fields.push(Field::new( left_field.name(), @@ -1254,41 +1371,41 @@ fn merge(left_struct_array: &StructArray, right_struct_array: &StructArray) -> S _ => { // TODO handle list-of-struct and other types fields.push(left_field.as_ref().clone()); - // Adjust the column validity: if left struct was null, propagate to child - let adjusted_column = adjust_child_validity(left_column, left_validity); - columns.push(adjusted_column); + columns.push(hide_under_parent( + left_field, + left_column, + left_validity, + merged, + )?); } } } None => { fields.push(left_field.as_ref().clone()); - // Adjust the column validity: if left struct was null, propagate to child - let adjusted_column = adjust_child_validity(left_column, left_validity); - columns.push(adjusted_column); + columns.push(hide_under_parent( + left_field, + left_column, + left_validity, + merged, + )?); } } } // now iterate through the fields on the right hand side - right_fields - .iter() - .zip(right_columns.iter()) - .for_each(|(field, column)| { - // add new columns on the right - if !left_struct_array - .fields() - .iter() - .any(|f| f.name() == field.name()) - { - fields.push(field.as_ref().clone()); - // This field doesn't exist on the left - // We use the right's column but need to adjust for struct validity - let adjusted_column = adjust_child_validity(column, right_validity); - columns.push(adjusted_column); - } - }); + for (field, column) in right_fields.iter().zip(right_columns.iter()) { + // add new columns on the right + if !left_struct_array + .fields() + .iter() + .any(|f| f.name() == field.name()) + { + fields.push(field.as_ref().clone()); + columns.push(hide_under_parent(field, column, right_validity, merged)?); + } + } - StructArray::try_new(Fields::from(fields), columns, merged_validity).unwrap() + StructArray::try_new(Fields::from(fields), columns, merged_validity) } fn merge_with_schema( @@ -1563,6 +1680,7 @@ impl BufferExt for arrow_buffer::Buffer { #[cfg(test)] mod tests { use super::*; + use arrow_array::types::Int32Type; use arrow_array::{ BinaryArray, BinaryViewArray, BooleanArray, FixedSizeBinaryArray, Float32Array, Int32Array, LargeBinaryArray, ListArray, NullArray, StringArray, StructArray, new_empty_array, @@ -2083,6 +2201,493 @@ mod tests { assert_eq!(merged, left); } + /// A list of `items`, one item per entry. + fn one_item_per_entry(large: bool, items: ArrayRef) -> ArrayRef { + let item_field = Arc::new(Field::new("item", items.data_type().clone(), true)); + let lengths = vec![1; items.len()]; + if large { + Arc::new(LargeListArray::new( + item_field, + OffsetBuffer::::from_lengths(lengths), + items, + None, + )) + } else { + Arc::new(ListArray::new( + item_field, + OffsetBuffer::::from_lengths(lengths), + items, + None, + )) + } + } + + /// A batch with the single column `s`, valid per `parent_validity`. + fn batch_of_parent(parent: StructArray) -> RecordBatch { + RecordBatch::try_new( + Arc::new(Schema::new(vec![Field::new( + "s", + parent.data_type().clone(), + true, + )])), + vec![Arc::new(parent)], + ) + .unwrap() + } + + /// `{s: struct>>}`, one row per value, each + /// list holding one item, and `s` valid per `parent_validity`. + fn list_struct_under_parent( + large: bool, + child: &str, + parent_validity: &[bool], + values: &[i32], + ) -> RecordBatch { + let items = Arc::new(StructArray::new( + vec![Field::new(child, DataType::Int32, true)].into(), + vec![Arc::new(Int32Array::from(values.to_vec()))], + None, + )); + let list = one_item_per_entry(large, items); + batch_of_parent(StructArray::new( + vec![Field::new("l", list.data_type().clone(), true)].into(), + vec![list], + Some(parent_validity.to_vec().into()), + )) + } + + /// The child `name` of the single item in row `row` of the merged `s.l`. + fn merged_item_child(merged: &RecordBatch, large: bool, row: usize, name: &str) -> ArrayRef { + let list = merged.column(0).as_struct().column(0); + let item = if large { + list.as_list::().value(row) + } else { + list.as_list::().value(row) + }; + item.as_struct().column_by_name(name).unwrap().clone() + } + + #[test] + fn test_merge_list_struct_identical_schema_hides_a_null_parent_row() { + for large in [false, true] { + let left = list_struct_under_parent(large, "x", &[false, true], &[11, 12]); + let right = list_struct_under_parent(large, "x", &[true, true], &[21, 22]); + let merged = left.merge(&right).unwrap(); + let parent = merged.column(0).as_struct(); + assert!(parent.is_valid(0), "large={large}"); + assert!( + parent.column(0).is_null(0), + "large={large}: the list under the null left parent became visible" + ); + assert!(parent.column(0).is_valid(1), "large={large}"); + } + } + + #[test] + fn test_merge_list_struct_hides_the_items_of_a_null_parent_row() { + for large in [false, true] { + // Null parent on the left: row 0 keeps the right item, not x = 11. + let left = list_struct_under_parent(large, "x", &[false, true], &[11, 12]); + let right = list_struct_under_parent(large, "y", &[true, true], &[21, 22]); + let merged = left.merge(&right).unwrap(); + assert!(merged.column(0).as_struct().column(0).is_valid(0)); + assert!( + merged_item_child(&merged, large, 0, "x").is_null(0), + "large={large}: x under the null left parent became visible" + ); + let y = merged_item_child(&merged, large, 0, "y"); + assert_eq!(y.as_primitive::().value(0), 21, "large={large}"); + + // Null parent on the right: row 0 keeps x = 11, not y = 21. + let left = list_struct_under_parent(large, "x", &[true, true], &[11, 12]); + let right = list_struct_under_parent(large, "y", &[false, true], &[21, 22]); + let merged = left.merge(&right).unwrap(); + let x = merged_item_child(&merged, large, 0, "x"); + assert_eq!(x.as_primitive::().value(0), 11, "large={large}"); + assert!( + merged_item_child(&merged, large, 0, "y").is_null(0), + "large={large}: y under the null right parent became visible" + ); + + // Row 1 is valid on both sides and keeps both items. + let x = merged_item_child(&merged, large, 1, "x"); + let y = merged_item_child(&merged, large, 1, "y"); + assert_eq!(x.as_primitive::().value(0), 12, "large={large}"); + assert_eq!(y.as_primitive::().value(0), 22, "large={large}"); + } + } + + #[test] + fn test_merge_list_struct_follows_a_sliced_parent() { + for large in [false, true] { + // After slicing, row 0 is the null parent row and row 1 is valid. + let left = list_struct_under_parent(large, "x", &[true, false, true], &[10, 11, 12]); + let left = left.slice(1, 2); + + let same = list_struct_under_parent(large, "x", &[true, true, true], &[20, 21, 22]); + let merged = left.merge(&same.slice(1, 2)).unwrap(); + let list = merged.column(0).as_struct().column(0); + assert!(list.is_null(0), "large={large}"); + assert!(list.is_valid(1), "large={large}"); + + let other = list_struct_under_parent(large, "y", &[true, true, true], &[20, 21, 22]); + let merged = left.merge(&other.slice(1, 2)).unwrap(); + assert!( + merged_item_child(&merged, large, 0, "x").is_null(0), + "large={large}" + ); + let x = merged_item_child(&merged, large, 1, "x"); + assert_eq!(x.as_primitive::().value(0), 12, "large={large}"); + } + } + + /// A non-nullable `l` or item child cannot be nulled under a parent row that + /// only the other side has, so the merge fails there rather than show the + /// hidden value. Where both sides agree on the parent, nothing is revealed + /// and the same fields merge. + #[test] + fn test_merge_list_struct_with_non_nullable_fields_under_a_null_parent() { + #[derive(Clone, Copy)] + struct Shape { + child: &'static str, + l_nullable: bool, + item_nullable: bool, + child_nullable: bool, + null_first_entry: bool, + null_first_item: bool, + } + let shape = |child| Shape { + child, + l_nullable: true, + item_nullable: true, + child_nullable: true, + null_first_entry: false, + null_first_item: false, + }; + let batch = |shape: Shape, parent_validity: [bool; 2]| { + let items = Arc::new(StructArray::new( + vec![Field::new( + shape.child, + DataType::Int32, + shape.child_nullable, + )] + .into(), + vec![Arc::new(Int32Array::from(vec![1, 2]))], + shape.null_first_item.then(|| vec![false, true].into()), + )); + let list: ArrayRef = Arc::new(ListArray::new( + Arc::new(Field::new( + "item", + items.data_type().clone(), + shape.item_nullable, + )), + OffsetBuffer::::from_lengths([1, 1]), + items, + shape.null_first_entry.then(|| vec![false, true].into()), + )); + batch_of_parent(StructArray::new( + vec![Field::new("l", list.data_type().clone(), shape.l_nullable)].into(), + vec![list], + Some(parent_validity.to_vec().into()), + )) + }; + let null_first = [false, true]; + let all_valid = [true, true]; + let cases = [ + // Non-nullable `l`, identical item types. + ( + Shape { + l_nullable: false, + ..shape("x") + }, + Shape { + l_nullable: false, + ..shape("x") + }, + Some("Cannot merge non-nullable field 'l'"), + ), + // Non-nullable `l` on the left, a null entry on the right: neither + // side has an entry at the merged row. + ( + Shape { + l_nullable: false, + ..shape("x") + }, + Shape { + null_first_entry: true, + ..shape("y") + }, + Some("non-nullable StructArray field \"l\""), + ), + // A non-nullable child of the item struct. + ( + Shape { + child_nullable: false, + ..shape("x") + }, + shape("y"), + Some("Cannot merge non-nullable field 'x'"), + ), + // A non-nullable item can be hidden where the right item is null. + ( + Shape { + item_nullable: false, + ..shape("x") + }, + Shape { + null_first_item: true, + ..shape("y") + }, + None, + ), + ]; + for (case, (left, right, rejected)) in cases.into_iter().enumerate() { + let merged = batch(left, null_first).merge(&batch(right, all_valid)); + match rejected { + Some(expected) => { + let message = merged.unwrap_err().to_string(); + assert!(message.contains(expected), "case {case}: {message}"); + } + None => assert!(merged.is_ok(), "case {case}: {merged:?}"), + } + let agreed = batch(left, null_first).merge(&batch(right, null_first)); + assert!( + agreed.is_ok(), + "case {case} with agreeing parents: {agreed:?}" + ); + } + } + + /// The fragment reader merges a data batch with an all-null placeholder for + /// a nested child the file lacks. Only the placeholder's top-level field is + /// nullable, so a non-nullable field below it must still merge, in either + /// order, keeping the data and filling the new child with nulls. + #[test] + fn test_merge_with_a_placeholder_under_a_non_nullable_field() { + let inner = + |child: &str| -> Fields { vec![Field::new(child, DataType::Int32, true)].into() }; + for wrap in ["struct", "list", "large_list"] { + let wrapped = |fields: Fields| match wrap { + "struct" => DataType::Struct(fields), + "list" => { + DataType::List(Arc::new(Field::new("item", DataType::Struct(fields), true))) + } + _ => DataType::LargeList(Arc::new(Field::new( + "item", + DataType::Struct(fields), + true, + ))), + }; + let outer = |child: &str| { + DataType::Struct(vec![Field::new("inner", wrapped(inner(child)), false)].into()) + }; + let items: ArrayRef = Arc::new(StructArray::new( + inner("a"), + vec![Arc::new(Int32Array::from(vec![1, 2]))], + None, + )); + let data_inner = match wrap { + "struct" => items, + "list" => one_item_per_entry(false, items), + _ => one_item_per_entry(true, items), + }; + let data = batch_of_parent(StructArray::new( + vec![Field::new("inner", data_inner.data_type().clone(), false)].into(), + vec![data_inner], + None, + )); + let placeholder = batch_of_parent(new_null_array(&outer("b"), 2).as_struct().clone()); + + for (left, right) in [(&data, &placeholder), (&placeholder, &data)] { + let merged = left.merge(right).unwrap(); + let inner = merged + .column(0) + .as_struct() + .column_by_name("inner") + .unwrap(); + let items = match wrap { + "struct" => inner.clone(), + "list" => inner.as_list::().values().clone(), + _ => inner.as_list::().values().clone(), + }; + let items = items.as_struct(); + let a = items.column_by_name("a").unwrap(); + assert_eq!(a.as_primitive::().values(), &[1, 2], "{wrap}"); + assert_eq!(a.null_count(), 0, "{wrap}"); + assert_eq!(items.column_by_name("b").unwrap().null_count(), 2, "{wrap}"); + } + } + } + + /// The null parent row's values stay hidden below a struct nested in the + /// list item, and below a plain struct field. + #[test] + fn test_merge_hides_nested_structs_of_a_null_parent_row() { + let nested = |child: &str, values: Vec| { + let leaf = StructArray::new( + vec![Field::new(child, DataType::Int32, true)].into(), + vec![Arc::new(Int32Array::from(values))], + None, + ); + StructArray::new( + vec![Field::new("payload", leaf.data_type().clone(), true)].into(), + vec![Arc::new(leaf)], + None, + ) + }; + let x_of = |payload: &dyn Array, row: usize| { + let x = payload.as_struct().column_by_name("x").unwrap().clone(); + x.is_null(row) + }; + + for large in [false, true] { + let in_list = |child: &str, parent: [bool; 2]| { + let list = one_item_per_entry(large, Arc::new(nested(child, vec![11, 12]))); + batch_of_parent(StructArray::new( + vec![Field::new("l", list.data_type().clone(), true)].into(), + vec![list], + Some(parent.to_vec().into()), + )) + }; + let merged = in_list("x", [false, true]) + .merge(&in_list("y", [true, true])) + .unwrap(); + let list = merged.column(0).as_struct().column(0); + let item = if large { + list.as_list::().value(0) + } else { + list.as_list::().value(0) + }; + let payload = item.as_struct().column_by_name("payload").unwrap(); + assert!(x_of(payload.as_ref(), 0), "large={large}"); + } + + let in_struct = |child: &str, parent: [bool; 2]| { + let inner = nested(child, vec![11, 12]); + batch_of_parent(StructArray::new( + vec![Field::new("c", inner.data_type().clone(), true)].into(), + vec![Arc::new(inner)], + Some(parent.to_vec().into()), + )) + }; + let merged = in_struct("x", [false, true]) + .merge(&in_struct("y", [true, true])) + .unwrap(); + let c = merged.column(0).as_struct().column(0); + let payload = c.as_struct().column_by_name("payload").unwrap(); + assert!(x_of(payload.as_ref(), 0)); + assert!(!x_of(payload.as_ref(), 1)); + } + + /// A batch whose parent rows are all null merges with an all-null + /// placeholder whose item struct holds more fields. + #[test] + fn test_merge_list_struct_under_null_parents_with_a_placeholder() { + for large in [false, true] { + let a = Field::new("a", DataType::Int32, true); + let inner = StructArray::new( + vec![a.clone()].into(), + vec![Arc::new(Int32Array::from(vec![1, 2]))], + None, + ); + let items = Arc::new(StructArray::new( + vec![Field::new("x", inner.data_type().clone(), true)].into(), + vec![Arc::new(inner)], + None, + )); + let list = one_item_per_entry(large, items); + let left = batch_of_parent(StructArray::new( + vec![Field::new("l", list.data_type().clone(), true)].into(), + vec![list], + Some(vec![false, false].into()), + )); + + let wider_x = + DataType::Struct(vec![a.clone(), Field::new("b", DataType::Int32, true)].into()); + let wider_items = new_null_array( + &DataType::Struct(vec![Field::new("x", wider_x, true)].into()), + 2, + ); + let wider_list = one_item_per_entry(large, wider_items); + let placeholder = new_null_array( + &DataType::Struct( + vec![Field::new("l", wider_list.data_type().clone(), true)].into(), + ), + 2, + ); + let right = batch_of_parent(placeholder.as_struct().clone()); + + let merged = left.merge(&right).unwrap(); + assert_eq!(merged.column(0).null_count(), 2, "large={large}"); + } + } + + /// A side whose list entries are all hidden by null parents merges like a + /// placeholder, so the other side's lists survive even with other offsets. + #[test] + fn test_merge_list_struct_hidden_side_keeps_the_other_offsets() { + for large in [false, true] { + let list_of = |child: &str, lengths: Vec, values: Vec| -> ArrayRef { + let items: ArrayRef = Arc::new(StructArray::new( + vec![Field::new(child, DataType::Int32, true)].into(), + vec![Arc::new(Int32Array::from(values))], + None, + )); + let field = Arc::new(Field::new("item", items.data_type().clone(), true)); + if large { + Arc::new(LargeListArray::new( + field, + OffsetBuffer::::from_lengths(lengths), + items, + None, + )) + } else { + Arc::new(ListArray::new( + field, + OffsetBuffer::::from_lengths(lengths), + items, + None, + )) + } + }; + let parent = |list: ArrayRef, validity: Vec| { + batch_of_parent(StructArray::new( + vec![Field::new("l", list.data_type().clone(), true)].into(), + vec![list], + Some(validity.into()), + )) + }; + let left = parent(list_of("x", vec![1, 1], vec![1, 2]), vec![false, false]); + let right = parent(list_of("y", vec![2, 0], vec![7, 8]), vec![true, true]); + + let merged = left.merge(&right).unwrap(); + let list = merged.column(0).as_struct().column(0); + let (first, second) = if large { + let list = list.as_list::(); + (list.value(0), list.value(1)) + } else { + let list = list.as_list::(); + (list.value(0), list.value(1)) + }; + let first = first.as_struct(); + assert_eq!( + first + .column_by_name("y") + .unwrap() + .as_primitive::() + .values(), + &[7, 8], + "large={large}" + ); + assert_eq!( + first.column_by_name("x").unwrap().null_count(), + 2, + "large={large}" + ); + assert_eq!(second.len(), 0, "large={large}"); + } + } + #[test] fn test_byte_width_opt() { assert_eq!(DataType::Int32.byte_width_opt(), Some(4)); @@ -2315,7 +2920,7 @@ mod tests { ); // Merge the two structs - let merged = merge(&left_struct, &right_struct); + let merged = merge(&left_struct, &right_struct).unwrap(); // Expected: // Row 1: both non-null -> {width: 300, height: 500} @@ -2355,7 +2960,7 @@ mod tests { Some(vec![false, false].into()), ); - let merged = merge(&all_null_left, &all_null_right); + let merged = merge(&all_null_left, &all_null_right).unwrap(); assert_eq!(merged.null_count(), 2); // An all-null side is the identity of the merge, so the other side decides each row. @@ -2364,7 +2969,7 @@ mod tests { vec![Arc::new(Int32Array::from(vec![Some(1), None])) as ArrayRef], Some(vec![true, false].into()), ); - let merged = merge(&partial_left, &all_null_right); + let merged = merge(&partial_left, &all_null_right).unwrap(); assert!(!merged.is_null(0)); assert!(merged.is_null(1)); @@ -2374,7 +2979,7 @@ mod tests { vec![Arc::new(Int32Array::from(vec![1, 2])) as ArrayRef], None, ); - let merged = merge(&all_valid_left, &all_null_right); + let merged = merge(&all_valid_left, &all_null_right).unwrap(); assert_eq!(merged.null_count(), 0); // An explicit all-valid buffer has the same semantics as a missing buffer. @@ -2401,7 +3006,7 @@ mod tests { ); // Previously panicked: "Arrays of type Null cannot contain a null bitmask" - let merged = merge(&left_struct, &right_struct); + let merged = merge(&left_struct, &right_struct).unwrap(); assert_eq!(merged.len(), 2); let b_col = merged.column_by_name("b").unwrap(); // DataType::Null implies all-null by definition; no null bitmap is stored. diff --git a/python/vendor/lance-arrow/src/stream.rs b/python/vendor/lance-arrow/src/stream.rs index c5e55f093..5aee834fd 100644 --- a/python/vendor/lance-arrow/src/stream.rs +++ b/python/vendor/lance-arrow/src/stream.rs @@ -8,16 +8,21 @@ use arrow_array::RecordBatch; use arrow_schema::{ArrowError, SchemaRef}; use futures::stream::{self, Stream, StreamExt}; use std::pin::Pin; +use std::vec::IntoIter; use crate::deepcopy::deep_copy_batch_sliced; /// Rechunks a stream of [`RecordBatch`] so that each output batch has -/// approximately `target_bytes` of array data. +/// approximately `max_bytes` of array data. /// /// Small input batches are accumulated (by concatenation) until at least /// `min_bytes` of data has been collected. If the resulting batch exceeds /// `max_bytes`, it is sliced into roughly equal pieces of ~`max_bytes` /// (assuming uniform row sizes). +/// +/// Slices share the input's buffers, so their array memory sizes may still +/// report the full parent allocation. Once split, all slices are delivered +/// directly, including a final slice smaller than `min_bytes`. pub fn rechunk_stream_by_size( input: S, input_schema: SchemaRef, @@ -74,6 +79,7 @@ where RechunkState { input: Box::pin(input), accumulated: Vec::new(), + slices: Vec::new().into_iter(), acc_bytes: 0, done: false, input_schema, @@ -82,6 +88,12 @@ where deep_copy, }, |mut state| async move { + // These slices are already sized. Measuring their shared buffers + // again would repeatedly split the tail using the parent size. + if let Some(batch) = state.slices.next() { + return Ok(Some((batch, state))); + } + if state.done && state.accumulated.is_empty() { return Ok(None); } @@ -106,17 +118,6 @@ where return Ok(None); } - // Fast path: if the first accumulated batch already meets the - // byte threshold, deliver it directly instead of concatenating - // everything together (which would just get sliced back apart). - if state.accumulated.len() > 1 - && state.accumulated[0].get_array_memory_size() >= state.min_bytes - { - let b = state.accumulated.remove(0); - state.acc_bytes -= b.get_array_memory_size(); - return Ok(Some((b, state))); - } - let batch = if state.accumulated.len() == 1 { state.accumulated.pop().unwrap() } else { @@ -132,19 +133,9 @@ where let mut slices = slice_batch(batch, state.max_bytes, state.deep_copy).map_err(E::from)?; - if slices.len() == 1 { - Ok(Some((slices.pop().unwrap(), state))) - } else { - let first = slices.remove(0); - - // Stash leftover slices for subsequent iterations. - for a in &slices { - state.acc_bytes += a.get_array_memory_size(); - } - state.accumulated = slices; - - Ok(Some((first, state))) - } + let first = slices.remove(0); + state.slices = slices.into_iter(); + Ok(Some((first, state))) }, ) } @@ -206,12 +197,13 @@ fn slice_batch( Ok(result) } -/// Internal state for [`rechunk_stream`]. +/// Internal state for [`rechunk_stream_by_size`]. /// /// Kept as a named struct so the `try_unfold` closure stays readable. struct RechunkState { input: Pin>, accumulated: Vec, + slices: IntoIter, acc_bytes: usize, done: bool, input_schema: SchemaRef, @@ -339,9 +331,9 @@ mod tests { #[test] fn test_sliced_leftovers_are_not_recombined() { - // Key test for the fast-path optimisation. When a large batch is - // sliced, leftover slices should be delivered one-at-a-time without - // being concatenated back together. We verify this by checking that + // When a large batch is sliced, leftover slices should be delivered + // one at a time without being split again or concatenated back + // together. We verify zero-copy behavior by checking that // every output buffer pointer falls inside the original batch's // allocation (i.e. they are all zero-copy slices, not fresh copies). let batch = make_batch(1000); @@ -354,7 +346,11 @@ mod tests { let result = collect_rechunked(vec![batch], bytes / 8, bytes / 4); assert_eq!(total_rows(&result), 1000); - assert!(result.len() >= 4); + assert_eq!( + result.iter().map(RecordBatch::num_rows).collect::>(), + vec![250, 250, 250, 250], + "leftover slices must not be split again using the parent buffer size" + ); for (i, b) in result.iter().enumerate() { let ptr = b.column(0).to_data().buffers()[0].as_ptr() as usize; @@ -381,6 +377,7 @@ mod tests { // After a large batch is fully drained, subsequent small batches // should be accumulated normally. let large = make_batch(1000); + let large_bytes = large.get_array_memory_size(); let small_bytes = make_batch(10).get_array_memory_size(); let batches = vec![ large, @@ -390,12 +387,12 @@ mod tests { make_batch(10), make_batch(10), ]; - let result = collect_rechunked(batches, small_bytes * 3, small_bytes * 100); + let result = collect_rechunked(batches, small_bytes * 3, large_bytes / 4); assert_eq!(total_rows(&result), 1050); - // The large batch should appear (possibly sliced) followed by - // concatenated small batches, so we should have fewer output batches - // than the 6 inputs. - assert!(result.len() < 6); + assert_eq!( + result.iter().map(RecordBatch::num_rows).collect::>(), + vec![250, 250, 250, 250, 30, 20] + ); } #[test] @@ -441,10 +438,9 @@ mod tests { let bytes = batch.get_array_memory_size(); let result = collect_rechunked(vec![batch], 0, bytes / 4); assert_eq!(total_rows(&result), 1000); - assert!( - result.len() >= 4, - "expected at least 4 slices, got {}", - result.len() + assert_eq!( + result.iter().map(RecordBatch::num_rows).collect::>(), + vec![250, 250, 250, 250] ); } @@ -475,6 +471,25 @@ mod tests { Arc::new(Schema::new(vec![Field::new("s", DataType::Utf8, false)])) } + #[test] + fn test_variable_length_tail_is_not_resliced() { + let batch = make_variable_batch(100, 256, usize::MAX, 256); + let bytes = batch.get_array_memory_size(); + let max_bytes = bytes - bytes / 4; + let input = stream::iter(vec![Ok::<_, ArrowError>(batch)]); + let rechunked = rechunk_stream_by_size(input, variable_schema(), max_bytes, max_bytes); + let result: Vec<_> = block_on(rechunked.collect::>()) + .into_iter() + .map(Result::unwrap) + .collect(); + + assert_eq!( + result.iter().map(RecordBatch::num_rows).collect::>(), + vec![75, 25] + ); + assert_eq!(total_rows(&result), 100); + } + fn collect_rechunked_variable( batches: Vec, min_bytes: usize, diff --git a/rust/lancedb/Cargo.toml b/rust/lancedb/Cargo.toml index 62029bb92..7f3764b02 100644 --- a/rust/lancedb/Cargo.toml +++ b/rust/lancedb/Cargo.toml @@ -46,7 +46,7 @@ lance-index = { workspace = true, features = ["tokenizer-jieba", "tokenizer-lind lance-table = { workspace = true } lance-linalg = { workspace = true } lance-encoding = { workspace = true } -lance-arrow = { version = "=15.0.0-beta.1", tag = "v15.0.0-beta.1", git = "https://github.com/lance-format/lance.git" } +lance-arrow = { "version" = "=15.0.0-beta.2", "tag" = "v15.0.0-beta.2", "git" = "https://github.com/lance-format/lance.git" } lance-namespace = { workspace = true } lance-namespace-impls = { workspace = true } metrics = { workspace = true, optional = true } diff --git a/rust/lancedb/src/blob.rs b/rust/lancedb/src/blob.rs index 142b2264e..07808d56c 100644 --- a/rust/lancedb/src/blob.rs +++ b/rust/lancedb/src/blob.rs @@ -687,6 +687,8 @@ async fn map_blob_take_error( let message = err.to_string(); let missing_row_id = message.contains("must not target deleted rows") || message.contains("belongs to non-existent fragment") + || (message.contains("Unknown fragment id ") + && message.contains("in fragment filter; not part of the current dataset version")) || (message.contains("Invalid read params ") && message.contains("addressable rows")) || message.contains("Could not resolve all requested row IDs"); diff --git a/rust/lancedb/src/table/merge.rs b/rust/lancedb/src/table/merge.rs index 91a0ed6ea..a066a4d6d 100644 --- a/rust/lancedb/src/table/merge.rs +++ b/rust/lancedb/src/table/merge.rs @@ -2137,7 +2137,7 @@ mod lsm_tests { } fn training_criteria(&self) -> lance_index::scalar::registry::TrainingCriteria { use lance_index::scalar::registry::{TrainingCriteria, TrainingOrdering}; - TrainingCriteria::new(TrainingOrdering::Values).with_row_id() + TrainingCriteria::new(TrainingOrdering::Values).with_row_addr() } fn validate( &self, @@ -2181,24 +2181,8 @@ mod lsm_tests { &self, ctx: &lance::dataset::mem_wal::index::FlushContext<'_>, ) -> lance_core::Result { - // BTree now trains on row addresses, while Bitmap still takes row IDs. - // These coincide in the flushed generation, which uses physical IDs. - assert!(!ctx.generation()?.dataset.manifest().uses_stable_row_ids()); - let batches = self - .0 - .to_training_batches(ctx.batch_size)? - .into_iter() - .map(|batch| { - assert_eq!(batch.schema().field(1).name(), lance_core::ROW_ADDR); - let schema = Arc::new(Schema::new(vec![ - batch.schema().field(0).clone(), - Field::new(lance_core::ROW_ID, DataType::UInt64, false), - ])); - RecordBatch::try_new(schema, batch.columns().to_vec()) - }) - .collect::, _>>()?; Ok(lance::dataset::mem_wal::index::FlushOutcome::from_batches( - batches, + self.0.to_training_batches(ctx.batch_size)?, )) } }