From b1cfe6edb15c3904001a99e98560b80970fd711e Mon Sep 17 00:00:00 2001 From: Xuanwo Date: Fri, 7 Aug 2026 16:31:40 +0800 Subject: [PATCH 01/15] ci(docs): add scheduled doc link check (#3888) The docs have no link checking at all, so external links rot silently: a trial run already found `docs/src/python/python.md` pointing at `lancedb.github.io/lance-namespace`, which returns 404 since the repository moved to the lance-format org. Checking external links on the blocking path would be the wrong trade: third-party hosts rate-limit automated clients, reject non-browser user agents, and go down temporarily, so any of them having a bad minute would turn unrelated PRs red. Following lance-format/lance#8315, this adds a daily `lychee` run that reports broken links into a single tracking issue, rewritten in place on each run and closed automatically once every link resolves. The scan job runs the downloaded lychee binary with a read-only token; everything that writes lives in a separate report job, and a non-verdict lychee exit fails the run instead of publishing a bogus report. The check is restricted to http(s) links because much of `docs/src` is generated API reference (the `js/` tree comes from `npm run docs`) and the hand-written pages use mkdocstrings cross-references and nav-relative paths that only resolve in the site mkdocs builds, so relative links would be reported as broken on every run. The one broken link the trial run surfaced is fixed here; after the fix, a local run over all 154 files reports 0 errors across 216 unique links. --- .github/workflows/docs-link-check.yml | 222 ++++++++++++++++++++++++++ docs/src/python/python.md | 2 +- 2 files changed, 223 insertions(+), 1 deletion(-) create mode 100644 .github/workflows/docs-link-check.yml diff --git a/.github/workflows/docs-link-check.yml b/.github/workflows/docs-link-check.yml new file mode 100644 index 000000000..0e22100eb --- /dev/null +++ b/.github/workflows/docs-link-check.yml @@ -0,0 +1,222 @@ +name: Check doc links + +# Checking external links is inherently noisy: third-party sites rate-limit +# automated clients, reject non-browser user agents, and go down temporarily. +# Blocking pull requests on that trades a lot of false failures for very little +# signal, so this runs on a schedule and reports findings in a single tracking +# issue instead of failing anyone's build. +on: + schedule: + - cron: "0 7 * * *" + workflow_dispatch: + +# The report lives in one repository-global issue, so runs must not overlap: a +# lookup racing a create produces duplicate issues, and a healthy run closing +# the issue while a failing run only rewrites its body would leave a broken +# report closed. The group is deliberately ref-independent so that a manual +# dispatch serializes against the scheduled run. +concurrency: + group: docs-link-check + cancel-in-progress: false + +permissions: {} + +env: + REPORT_TITLE: "Docs link checker report" + +jobs: + scan: + name: Scan links + runs-on: ubuntu-24.04 + # lychee-action is pinned by SHA, but its wrapper downloads the lychee + # release tarball at run time without verifying a digest, and hands the + # resulting binary a GitHub token. Release assets remain replaceable, so + # that binary is confined to a job whose token can only read public + # content; everything that writes runs in the report job below. + permissions: + contents: read + outputs: + exit_code: ${{ steps.lychee.outputs.exit_code }} + steps: + - name: Checkout + uses: actions/checkout@v6 + with: + # workflow_dispatch can run from any ref, but the report is + # repository-global. Always measure the default branch so a manual + # run from a topic branch cannot close a report that main warrants, + # or overwrite it with branch-only findings. + ref: ${{ github.event.repository.default_branch }} + persist-credentials: false + + - name: Check links + id: lychee + uses: lycheeverse/lychee-action@e7477775783ea5526144ba13e8db5eec57747ce8 # v2.9.0 + with: + # Restricted to http(s) on purpose. Much of docs/src is generated + # API reference (the js/ tree comes from `npm run docs` in nodejs) + # and the hand-written pages use mkdocstrings cross-references and + # nav-relative paths that only resolve in the site mkdocs builds, + # not in this checkout, so relative links would be reported as + # broken on every run. + args: >- + --scheme https + --scheme http + --no-progress + --max-retries 3 + --timeout 20 + 'docs/src/**/*.md' + format: json + output: ./lychee/out.json + jobSummary: false + # The report, not a red build, is the signal for broken links. The + # validation step below still fails the run if the check itself + # breaks. + fail: false + + - name: Validate report + # lychee does not reserve exit code 2 for broken links: its CLI + # parser also exits 2 on an invalid option, before any link was + # checked or any report written. Only a parseable report whose + # counts agree with the exit code counts as a link verdict; anything + # else fails here, and the report job below is skipped entirely, so + # the tracking issue is never touched. Exit 2 covers timeouts as + # well as errors, and a timed-out host is exactly the transient + # unavailability this report exists to surface, so both count as + # findings. Requiring total > 0 also catches a glob that silently + # stopped matching any file. + if: steps.lychee.outputs.exit_code == 0 || steps.lychee.outputs.exit_code == 2 + env: + EXIT_CODE: ${{ steps.lychee.outputs.exit_code }} + run: | + jq -e --argjson code "$EXIT_CODE" ' + (.total > 0) and + (if $code == 0 + then .errors == 0 and .timeouts == 0 + and (.error_map | length == 0) and (.timeout_map | length == 0) + else (.errors + .timeouts) > 0 + and ((.error_map | length) + (.timeout_map | length)) > 0 + end) + ' ./lychee/out.json + + - name: Upload report + if: steps.lychee.outputs.exit_code == 2 + uses: actions/upload-artifact@v7 + with: + name: link-report + path: ./lychee/out.json + retention-days: 7 + + report: + name: Update report issue + needs: scan + runs-on: ubuntu-24.04 + # Deliberately no checkout: this job needs the report artifact and the + # issues API, not the repository contents. + permissions: + issues: write + env: + EXIT_CODE: ${{ needs.scan.outputs.exit_code }} + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + steps: + - name: Classify checker result + # lychee exits 0 when every link resolves and 2 when links fail, + # both already cross-checked against the report by the scan job's + # validation step. Anything else (1 runtime, 3 bad config) means the + # check never produced a link verdict, which must surface as a failed + # run rather than be published as "broken documentation links". + run: | + case "$EXIT_CODE" in + 0|2) + echo "lychee exit code $EXIT_CODE" + ;; + *) + echo "::error::lychee exited with '$EXIT_CODE': the link check did not complete. Leaving the report issue untouched." + exit 1 + ;; + esac + + - name: Find existing report issue + id: report + # Matched on title alone, and through search rather than a listing: + # the issue action applies labels in a separate call after creating the + # issue, so a label filter misses a half-created report, and this + # repository has far more open issues than one listing page holds. + # Closed issues are included because a healthy run closes the report: + # an open-only lookup would forget that identity and the next failing + # run would open a duplicate. The oldest match stays the canonical + # report and is reopened below when links break again. + run: | + match=$(gh issue list --repo "$GITHUB_REPOSITORY" --state all \ + --search "in:title \"$REPORT_TITLE\" author:app/github-actions" \ + --limit 50 --json number,title,state \ + --jq "[.[] | select(.title == \"$REPORT_TITLE\")] | sort_by(.number) | first // empty") + echo "number=$(jq -r '.number // empty' <<<"$match")" >> "$GITHUB_OUTPUT" + echo "state=$(jq -r '.state // empty' <<<"$match")" >> "$GITHUB_OUTPUT" + + - name: Download report + if: env.EXIT_CODE == 2 + uses: actions/download-artifact@v8 + with: + name: link-report + path: ./lychee + + - name: Compose report + if: env.EXIT_CODE == 2 + run: | + run_url="$GITHUB_SERVER_URL/$GITHUB_REPOSITORY/actions/runs/$GITHUB_RUN_ID" + { + echo "Broken documentation links found by [\`$GITHUB_WORKFLOW\`]($run_url)." + echo + echo "This issue is rewritten by every scheduled run and closed automatically once all links resolve." + echo + echo "Entries can be false positives: some sites rate-limit or block automated clients while working fine in a browser. Confirm before editing the docs, and add persistent offenders to \`--exclude\` in \`.github/workflows/docs-link-check.yml\`." + echo + # Timeouts are reported alongside errors: entries land in + # timeout_map with a status text instead of an HTTP code. + jq -r ' + "\(.errors) of \(.total) links failed, \(.timeouts) timed out.", + "", + ([(.error_map | to_entries[]), (.timeout_map | to_entries[])] + | group_by(.key)[] | + "### Errors in \(.[0].key)", + "", + (map(.value[])[] | "* [\(.status.code // .status.text // "ERR")] <\(.url)> — \(.status.details // .status.text // "unknown error")"), + "") + ' ./lychee/out.json + } > ./lychee/issue.md + + - name: Reopen report issue + # A healthy run closes the report, and the issue action below only + # rewrites the body of whatever number it is given. Without an + # explicit reopen, the 2 -> 0 -> 2 sequence would keep rewriting a + # closed issue while links are broken. A CLOSED state implies the + # lookup found a canonical issue, so no separate emptiness check. + if: env.EXIT_CODE == 2 && steps.report.outputs.state == 'CLOSED' + env: + ISSUE_NUMBER: ${{ steps.report.outputs.number }} + run: | + run_url="$GITHUB_SERVER_URL/$GITHUB_REPOSITORY/actions/runs/$GITHUB_RUN_ID" + gh issue reopen "$ISSUE_NUMBER" --repo "$GITHUB_REPOSITORY" \ + --comment "Broken documentation links found again in [the latest run]($run_url)." + + - name: Report broken links + if: env.EXIT_CODE == 2 + uses: peter-evans/create-issue-from-file@fca9117c27cdc29c6c4db3b86c48e4115a786710 # v6.0.0 + with: + # Empty on the first failing run, which creates the issue; afterwards + # the same issue is updated in place. + issue-number: ${{ steps.report.outputs.number }} + title: ${{ env.REPORT_TITLE }} + content-filepath: ./lychee/issue.md + labels: documentation + + - name: Close report issue once links are healthy + # An OPEN state implies the lookup found a canonical issue; a report + # that is already closed needs nothing. + if: env.EXIT_CODE == 0 && steps.report.outputs.state == 'OPEN' + env: + ISSUE_NUMBER: ${{ steps.report.outputs.number }} + run: | + run_url="$GITHUB_SERVER_URL/$GITHUB_REPOSITORY/actions/runs/$GITHUB_RUN_ID" + gh issue close "$ISSUE_NUMBER" --repo "$GITHUB_REPOSITORY" \ + --comment "All documentation links resolved in [the latest run]($run_url)." diff --git a/docs/src/python/python.md b/docs/src/python/python.md index 36044d35d..3dd6f59f4 100644 --- a/docs/src/python/python.md +++ b/docs/src/python/python.md @@ -31,7 +31,7 @@ is also an [asynchronous API client](#connections-asynchronous). ## Namespaces (Synchronous) A namespace-backed connection resolves tables through a -[Lance namespace](https://lancedb.github.io/lance-namespace/) service instead of +[Lance namespace](https://lance-format.github.io/lance-namespace/) service instead of listing a storage directory. ::: lancedb.connect_namespace From f4c668e2441a3c5ab31a024eb8e1ec6a68920f17 Mon Sep 17 00:00:00 2001 From: David Tolnay Date: Fri, 7 Aug 2026 02:24:38 -0700 Subject: [PATCH 02/15] chore(deps): declare more specific futures dependency (#3800) Lancedb does not work with any other version of `futures`. With futures 0.1 it fails like this: ```console error[E0432]: unresolved imports `futures::StreamExt`, `futures::TryStreamExt` --> rust/lancedb/src/arrow.rs:21:23 | 21 | use futures::{Stream, StreamExt, TryStreamExt}; | ^^^^^^^^^ ^^^^^^^^^^^^ no `TryStreamExt` in the root | | | no `StreamExt` in the root | error[E0432]: unresolved import `futures::StreamExt` --> rust/lancedb/src/data/scannable.rs:24:5 | 24 | use futures::StreamExt; | ^^^^^^^^^^^^^^^^^^ no `StreamExt` in the root | error[E0432]: unresolved import `futures::TryStreamExt` --> rust/lancedb/src/dataloader/permutation/builder.rs:9:5 | 9 | use futures::TryStreamExt; | ^^^^^^^^^^^^^^^^^^^^^ no `TryStreamExt` in the root error[E0432]: unresolved imports `futures::StreamExt`, `futures::TryStreamExt` --> rust/lancedb/src/dataloader/permutation/reader.rs:25:15 | 25 | use futures::{StreamExt, TryStreamExt}; | ^^^^^^^^^ ^^^^^^^^^^^^ no `TryStreamExt` in the root | | | no `StreamExt` in the root | error[E0432]: unresolved imports `futures::StreamExt`, `futures::TryStreamExt` --> rust/lancedb/src/dataloader/permutation/shuffle.rs:8:15 | 8 | use futures::{StreamExt, TryStreamExt}; | ^^^^^^^^^ ^^^^^^^^^^^^ no `TryStreamExt` in the root | | | no `StreamExt` in the root | error[E0432]: unresolved imports `futures::StreamExt`, `futures::TryStreamExt` --> rust/lancedb/src/dataloader/permutation/split.rs:12:15 | 12 | use futures::{StreamExt, TryStreamExt}; | ^^^^^^^^^ ^^^^^^^^^^^^ no `TryStreamExt` in the root | | | no `StreamExt` in the root | error[E0432]: unresolved import `futures::TryStreamExt` --> rust/lancedb/src/dataloader/permutation/util.rs:9:5 | 9 | use futures::TryStreamExt; | ^^^^^^^^^^^^^^^^^^^^^ no `TryStreamExt` in the root error[E0432]: unresolved imports `futures::StreamExt`, `futures::TryFutureExt` --> rust/lancedb/src/io/object_store.rs:8:15 | 8 | use futures::{StreamExt, TryFutureExt, stream::BoxStream}; | ^^^^^^^^^ ^^^^^^^^^^^^ no `TryFutureExt` in the root | | | no `StreamExt` in the root | error[E0432]: unresolved imports `futures::FutureExt`, `futures::TryFutureExt`, `futures::TryStreamExt`, `futures::try_join` --> rust/lancedb/src/query.rs:12:15 | 12 | use futures::{FutureExt, TryFutureExt, TryStreamExt, stream, try_join}; | ^^^^^^^^^ ^^^^^^^^^^^^ ^^^^^^^^^^^^ ^^^^^^^^ no `try_join` in the root | | | | | | | no `TryStreamExt` in the root | | no `TryFutureExt` in the root | no `FutureExt` in the root | error[E0432]: unresolved imports `futures::StreamExt`, `futures::TryStreamExt` --> rust/lancedb/src/remote/table/blobs.rs:13:15 | 13 | use futures::{StreamExt, TryStreamExt}; | ^^^^^^^^^ ^^^^^^^^^^^^ no `TryStreamExt` in the root | | | no `StreamExt` in the root | error[E0432]: unresolved imports `futures::SinkExt`, `futures::StreamExt` --> rust/lancedb/src/remote/table/insert.rs:20:15 | 20 | use futures::{SinkExt, StreamExt}; | ^^^^^^^ ^^^^^^^^^ no `StreamExt` in the root | | | no `SinkExt` in the root | error[E0432]: unresolved imports `futures::StreamExt`, `futures::TryStreamExt` --> rust/lancedb/src/remote/table.rs:58:15 | 58 | use futures::{StreamExt, TryStreamExt}; | ^^^^^^^^^ ^^^^^^^^^^^^ no `TryStreamExt` in the root | | | no `StreamExt` in the root | error[E0432]: unresolved import `futures::StreamExt` --> rust/lancedb/src/remote/util.rs:5:23 | 5 | use futures::{Stream, StreamExt}; | ^^^^^^^^^ no `StreamExt` in the root | error[E0432]: unresolved import `futures::StreamExt` --> rust/lancedb/src/table.rs:14:5 | 14 | use futures::StreamExt; | ^^^^^^^^^^^^^^^^^^ no `StreamExt` in the root | error[E0432]: unresolved import `futures::TryStreamExt` --> rust/lancedb/src/table/datafusion/insert.rs:20:5 | 20 | use futures::TryStreamExt; | ^^^^^^^^^^^^^^^^^^^^^ no `TryStreamExt` in the root error[E0432]: unresolved import `futures::TryStreamExt` --> rust/lancedb/src/table/datafusion/scannable_exec.rs:14:5 | 14 | use futures::TryStreamExt; | ^^^^^^^^^^^^^^^^^^^^^ no `TryStreamExt` in the root error[E0432]: unresolved imports `futures::TryFutureExt`, `futures::TryStreamExt` --> rust/lancedb/src/table/datafusion.rs:25:15 | 25 | use futures::{TryFutureExt, TryStreamExt}; | ^^^^^^^^^^^^ ^^^^^^^^^^^^ no `TryStreamExt` in the root | | | no `TryFutureExt` in the root error[E0432]: unresolved import `futures::FutureExt` --> rust/lancedb/src/table/delete.rs:3:5 | 3 | use futures::FutureExt; | ^^^^^^^^^^^^^^^^^^ no `FutureExt` in the root | error[E0432]: unresolved imports `futures::FutureExt`, `futures::TryFutureExt` --> rust/lancedb/src/table/merge.rs:9:15 | 9 | use futures::{FutureExt, TryFutureExt}; | ^^^^^^^^^ ^^^^^^^^^^^^ no `TryFutureExt` in the root | | | no `FutureExt` in the root | error[E0432]: unresolved import `futures::future::try_join_all` --> rust/lancedb/src/table/query.rs:24:5 | 24 | use futures::future::try_join_all; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ no `try_join_all` in `future` | error[E0432]: unresolved import `futures::FutureExt` --> rust/lancedb/src/utils/background_cache.rs:12:5 | 12 | use futures::FutureExt; | ^^^^^^^^^^^^^^^^^^ no `FutureExt` in the root | error[E0432]: unresolved import `futures::FutureExt` --> rust/lancedb/src/utils/mod.rs:12:15 | 12 | use futures::{FutureExt, Stream}; | ^^^^^^^^^ no `FutureExt` in the root | error[E0433]: cannot find `join` in `futures` --> rust/lancedb/src/remote/table/insert.rs:504:55 | 504 | let (producer_result, send_result) = futures::join!(producer, send); | ^^^^ could not find `join` in `futures` error[E0407]: method `poll_next` is not a member of trait `Stream` --> rust/lancedb/src/arrow.rs:108:5 | 108 | / fn poll_next( 109 | | self: Pin<&mut Self>, 110 | | cx: &mut std::task::Context<'_>, 111 | | ) -> std::task::Poll> { 112 | | let this = self.project(); 113 | | this.stream.poll_next(cx) 114 | | } | |_____^ not a member of trait `Stream` error[E0407]: method `poll_next` is not a member of trait `Stream` --> rust/lancedb/src/utils/mod.rs:362:5 | 362 | / fn poll_next( 363 | | mut self: std::pin::Pin<&mut Self>, 364 | | cx: &mut std::task::Context<'_>, 365 | | ) -> std::task::Poll> { ... | 391 | | } | |_____^ not a member of trait `Stream` error[E0407]: method `poll_next` is not a member of trait `Stream` --> rust/lancedb/src/utils/mod.rs:433:5 | 433 | / fn poll_next( 434 | | mut self: Pin<&mut Self>, 435 | | cx: &mut std::task::Context<'_>, 436 | | ) -> std::task::Poll> { ... | 470 | | } | |_____^ not a member of trait `Stream` error[E0425]: cannot find function `try_unfold` in module `futures::stream` --> rust/lancedb/src/remote/table/insert.rs:230:39 | 230 | let stream = futures::stream::try_unfold( | ^^^^^^^^^^ not found in `futures::stream` error[E0433]: cannot find `channel` in `futures` --> rust/lancedb/src/remote/table/insert.rs:418:22 | 418 | futures::channel::mpsc::channel::, std::io::Error>>(2); | ^^^^^^^ could not find `channel` in `futures` | error[E0425]: cannot find function `try_join_all` in module `futures::future` --> rust/lancedb/src/remote/table.rs:1062:40 | 1062 | let streams = futures::future::try_join_all(futures); | ^^^^^^^^^^^^ | ::: $CARGO_HOME/registry/src/index.crates.io-1949cf8c6b5b557f/futures-0.1.31/src/future/join_all.rs:76:1 | 76 | / pub fn join_all(i: I) -> JoinAll 77 | | where I: IntoIterator, 78 | | I::Item: IntoFuture, | |______________________________- similarly named function `join_all` defined here | error[E0425]: cannot find function `try_join_all` in module `futures::future` --> rust/lancedb/src/remote/table.rs:1660:40 | 1660 | let results = futures::future::try_join_all(futures).await?; | ^^^^^^^^^^^^ | ::: $CARGO_HOME/registry/src/index.crates.io-1949cf8c6b5b557f/futures-0.1.31/src/future/join_all.rs:76:1 | 76 | / pub fn join_all(i: I) -> JoinAll 77 | | where I: IntoIterator, 78 | | I::Item: IntoFuture, | |______________________________- similarly named function `join_all` defined here | error[E0425]: cannot find function `try_join_all` in module `futures::future` --> rust/lancedb/src/remote/table.rs:2243:43 | 2243 | let plan_texts = futures::future::try_join_all(futures).await?; | ^^^^^^^^^^^^ | ::: $CARGO_HOME/registry/src/index.crates.io-1949cf8c6b5b557f/futures-0.1.31/src/future/join_all.rs:76:1 | 76 | / pub fn join_all(i: I) -> JoinAll 77 | | where I: IntoIterator, 78 | | I::Item: IntoFuture, | |______________________________- similarly named function `join_all` defined here | error[E0425]: cannot find function `try_join_all` in module `futures::future` --> rust/lancedb/src/remote/table.rs:2290:53 | 2290 | let analyze_result_texts = futures::future::try_join_all(futures).await?; | ^^^^^^^^^^^^ | ::: $CARGO_HOME/registry/src/index.crates.io-1949cf8c6b5b557f/futures-0.1.31/src/future/join_all.rs:76:1 | 76 | / pub fn join_all(i: I) -> JoinAll 77 | | where I: IntoIterator, 78 | | I::Item: IntoFuture, | |______________________________- similarly named function `join_all` defined here | error[E0425]: cannot find function `try_unfold` in module `futures::stream` --> rust/lancedb/src/remote/util.rs:21:35 | 21 | let stream = futures::stream::try_unfold( | ^^^^^^^^^^ not found in `futures::stream` error[E0191]: the value of the associated type `Error` in `futures::Stream` must be specified --> rust/lancedb/src/arrow.rs:70:50 | 70 | pub type SendableRecordBatchStream = Pin>; | ^^^^^^^^^^^^^^^^^ | help: specify the associated type | 70 | pub type SendableRecordBatchStream = Pin + Send>>; | ++++++++++++++++++++ error[E0107]: type alias takes 0 lifetime arguments but 1 lifetime argument was supplied --> rust/lancedb/src/utils/background_cache.rs:15:31 | 15 | type SharedFut = Shared>>>; | ^^^^^^^^^ ------- help: remove the lifetime argument | | | expected 0 lifetime arguments | note: type alias defined here, with 0 lifetime parameters --> $CARGO_HOME/registry/src/index.crates.io-1949cf8c6b5b557f/futures-0.1.31/src/future/mod.rs:106:14 | 106 | pub type BoxFuture = ::std::boxed::Box + Send>; | ^^^^^^^^^ error[E0107]: type alias takes 2 generic arguments but 1 generic argument was supplied --> rust/lancedb/src/utils/background_cache.rs:15:31 | 15 | type SharedFut = Shared>>>; | ^^^^^^^^^ ----------------- supplied 1 generic argument | | | expected 2 generic arguments | note: type alias defined here, with 2 generic parameters: `T`, `E` --> $CARGO_HOME/registry/src/index.crates.io-1949cf8c6b5b557f/futures-0.1.31/src/future/mod.rs:106:14 | 106 | pub type BoxFuture = ::std::boxed::Box + Send>; | ^^^^^^^^^ - - help: add missing generic argument | 15 | type SharedFut = Shared>, E>>; | +++ error[E0046]: not all trait items implemented, missing: `Error`, `poll` --> rust/lancedb/src/arrow.rs:105:1 | 105 | impl>> Stream for SimpleRecordBatchStream { | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ missing `Error`, `poll` in implementation | = help: implement the missing item: `type Error = /* Type */;` = help: implement the missing item: `fn poll(&mut self) -> std::result::Result::Item>>, ::Error> { todo!() }` error[E0107]: type alias takes 0 lifetime arguments but 1 lifetime argument was supplied --> rust/lancedb/src/io/object_store.rs:97:46 | 97 | fn list(&self, prefix: Option<&Path>) -> BoxStream<'static, Result> { | ^^^^^^^^^ ------- help: remove the lifetime argument | | | expected 0 lifetime arguments | note: type alias defined here, with 0 lifetime parameters --> $CARGO_HOME/registry/src/index.crates.io-1949cf8c6b5b557f/futures-0.1.31/src/stream/mod.rs:132:14 | 132 | pub type BoxStream = ::std::boxed::Box + Send>; | ^^^^^^^^^ error[E0107]: type alias takes 2 generic arguments but 1 generic argument was supplied --> rust/lancedb/src/io/object_store.rs:97:46 | 97 | fn list(&self, prefix: Option<&Path>) -> BoxStream<'static, Result> { | ^^^^^^^^^ ------------------ supplied 1 generic argument | | | expected 2 generic arguments | note: type alias defined here, with 2 generic parameters: `T`, `E` --> $CARGO_HOME/registry/src/index.crates.io-1949cf8c6b5b557f/futures-0.1.31/src/stream/mod.rs:132:14 | 132 | pub type BoxStream = ::std::boxed::Box + Send>; | ^^^^^^^^^ - - help: add missing generic argument | 97 | fn list(&self, prefix: Option<&Path>) -> BoxStream<'static, Result, E> { | +++ error[E0107]: type alias takes 0 lifetime arguments but 1 lifetime argument was supplied --> rust/lancedb/src/io/object_store.rs:107:20 | 107 | locations: BoxStream<'static, Result>, | ^^^^^^^^^ ------- help: remove the lifetime argument | | | expected 0 lifetime arguments | note: type alias defined here, with 0 lifetime parameters --> $CARGO_HOME/registry/src/index.crates.io-1949cf8c6b5b557f/futures-0.1.31/src/stream/mod.rs:132:14 | 132 | pub type BoxStream = ::std::boxed::Box + Send>; | ^^^^^^^^^ error[E0107]: type alias takes 2 generic arguments but 1 generic argument was supplied --> rust/lancedb/src/io/object_store.rs:107:20 | 107 | locations: BoxStream<'static, Result>, | ^^^^^^^^^ ------------ supplied 1 generic argument | | | expected 2 generic arguments | note: type alias defined here, with 2 generic parameters: `T`, `E` --> $CARGO_HOME/registry/src/index.crates.io-1949cf8c6b5b557f/futures-0.1.31/src/stream/mod.rs:132:14 | 132 | pub type BoxStream = ::std::boxed::Box + Send>; | ^^^^^^^^^ - - help: add missing generic argument | 107 | locations: BoxStream<'static, Result, E>, | +++ error[E0107]: type alias takes 0 lifetime arguments but 1 lifetime argument was supplied --> rust/lancedb/src/io/object_store.rs:108:10 | 108 | ) -> BoxStream<'static, Result> { | ^^^^^^^^^ ------- help: remove the lifetime argument | | | expected 0 lifetime arguments | note: type alias defined here, with 0 lifetime parameters --> $CARGO_HOME/registry/src/index.crates.io-1949cf8c6b5b557f/futures-0.1.31/src/stream/mod.rs:132:14 | 132 | pub type BoxStream = ::std::boxed::Box + Send>; | ^^^^^^^^^ error[E0107]: type alias takes 2 generic arguments but 1 generic argument was supplied --> rust/lancedb/src/io/object_store.rs:108:10 | 108 | ) -> BoxStream<'static, Result> { | ^^^^^^^^^ ------------ supplied 1 generic argument | | | expected 2 generic arguments | note: type alias defined here, with 2 generic parameters: `T`, `E` --> $CARGO_HOME/registry/src/index.crates.io-1949cf8c6b5b557f/futures-0.1.31/src/stream/mod.rs:132:14 | 132 | pub type BoxStream = ::std::boxed::Box + Send>; | ^^^^^^^^^ - - help: add missing generic argument | 108 | ) -> BoxStream<'static, Result, E> { | +++ error[E0599]: no method named `map_err` found for struct `Pin>` in the current scope --> rust/lancedb/src/dataloader/permutation/builder.rs:208:32 | 208 | let stream = df_stream.map_err(|e| Error::Other { | ----------^^^^^^^ method not found in `Pin>` | ::: $CARGO_HOME/registry/src/index.crates.io-1949cf8c6b5b557f/futures-util-0.3.32/src/stream/try_stream/mod.rs:248:8 | 248 | fn map_err(self, f: F) -> MapErr | ------- the method is available for `Pin>` here | error[E0599]: no method named `try_collect` found for struct `DatasetRecordBatchStream` in the current scope --> rust/lancedb/src/dataloader/permutation/reader.rs:220:28 | 220 | let batches = data.try_collect::>().await?; | ^^^^^^^^^^^ | error[E0599]: no method named `map_err` found for struct `DatasetRecordBatchStream` in the current scope --> rust/lancedb/src/dataloader/permutation/reader.rs:287:14 | 286 | let mut stream = row_ids | __________________________- 287 | | .map_err(Error::from) | | -^^^^^^^ method not found in `DatasetRecordBatchStream` | |_____________| | error[E0599]: the method `chain` exists for struct `futures::stream::Once<_, _>`, but its trait bounds were not satisfied --> rust/lancedb/src/dataloader/permutation/reader.rs:307:81 | 307 | let stream = futures::stream::once(std::future::ready(Ok(first_batch))).chain(stream); | ^^^^^ method cannot be called on `futures::stream::Once<_, _>` due to unsatisfied trait bounds error[E0308]: mismatched types --> rust/lancedb/src/dataloader/permutation/shuffle.rs:120:35 | 120 | futures::stream::once(async move { Ok(shuffled) }), | --------------------- ^^^^^^^^^^^^^^^^^^^^^^^^^^^ expected `Result<_, _>`, found `async` block | | | arguments to this function are incorrect | = note: expected enum `std::result::Result<_, _>` found `async` block `{async block@rust/lancedb/src/dataloader/permutation/shuffle.rs:120:35: 120:45}` note: function defined here --> $CARGO_HOME/registry/src/index.crates.io-1949cf8c6b5b557f/futures-0.1.31/src/stream/once.rs:20:8 | 20 | pub fn once(item: Result) -> Once { | ^^^^ help: try wrapping the expression in a variant of `std::result::Result` | 120 | futures::stream::once(Ok(async move { Ok(shuffled) })), | +++ + 120 | futures::stream::once(Err(async move { Ok(shuffled) })), | ++++ + error[E0271]: type mismatch resolving ` as IntoIterator>::Item == Result<_, _>` --> rust/lancedb/src/dataloader/permutation/shuffle.rs:228:44 | 228 | let stream = futures::stream::iter(0..num_files) | --------------------- ^^^^^^^^^^^^ expected `Result<_, _>`, found `u64` | | | required by a bound introduced by this call | = note: expected enum `std::result::Result<_, _>` found type `u64` note: required by a bound in `futures::stream::iter` --> $CARGO_HOME/registry/src/index.crates.io-1949cf8c6b5b557f/futures-0.1.31/src/stream/iter.rs:31:27 | 30 | pub fn iter(i: J) -> Iter | ---- required by a bound in this function 31 | where J: IntoIterator>, | ^^^^^^^^^^^^^^^^^ required by this bound in `iter` error[E0599]: no method named `then` found for struct `IterStream` in the current scope --> rust/lancedb/src/dataloader/permutation/shuffle.rs:229:14 | 228 | let stream = futures::stream::iter(0..num_files) | ______________________- 229 | | .then(move |file_index| { | | -^^^^ method not found in `IterStream>` | |_____________| | error[E0599]: no method named `try_collect` found for struct `Pin>` in the current scope --> rust/lancedb/src/dataloader/permutation/shuffle.rs:258:26 | 250 | let batches = reader | ___________________________________- 251 | | .read_stream( 252 | | ReadBatchParams::RangeFull, 253 | | reader.num_rows() as u32, ... | 257 | | .await? 258 | | .try_collect::>() | |_________________________-^^^^^^^^^^^ error[E0599]: no method named `and_then` found for associated type `impl Future, ...>> + Send` in the current scope --> rust/lancedb/src/query.rs:766:14 | 765 | / self.create_plan(QueryExecutionOptions::default()) 766 | | .and_then(|plan| std::future::ready(Ok(plan.schema()))) | |_____________-^^^^^^^^ | ::: $CARGO_HOME/registry/src/index.crates.io-1949cf8c6b5b557f/futures-util-0.3.32/src/future/try_future/mod.rs:395:8 | 395 | fn and_then(self, f: F) -> AndThen | -------- the method is available for `impl std::future::Future, error::Error>> + std::marker::Send` here error[E0599]: no method named `boxed` found for `async` block `{async block@rust/lancedb/src/query.rs:1492:33: 1492:43}` in the current scope --> rust/lancedb/src/query.rs:1493:18 | 1492 | let hybrid_result = async move { self.execute_hybrid(options).await } | _________________________________- 1493 | | .boxed() | | -^^^^^ method not found in `{async block@rust/lancedb/src/query.rs:1492:33: 1492:43}` | |_________________| error[E0271]: expected `{closure@blobs.rs:181:58}` to return `Result<_, _>`, but it returns `impl Future>` --> rust/lancedb/src/remote/table/blobs.rs:181:66 | 181 | futures::stream::iter(ranges.iter().cloned().map(|range| self.read_range(range))) | --------------------- ------- ^^^^^^^^^^^^^^^^^^^^^^ expected `Result<_, _>`, found future | | | | | this closure | required by a bound introduced by this call error[E0599]: no method named `buffered` found for struct `IterStream` in the current scope --> rust/lancedb/src/remote/table/blobs.rs:182:14 | 181 | / futures::stream::iter(ranges.iter().cloned().map(|range| self.read_range(range))) 182 | | .buffered(BLOB_REQUEST_CONCURRENCY) | | -^^^^^^^^ method not found in `Iter>>, {closure@...}>>` | |_____________| error[E0599]: no method named `try_next` found for struct `Pin>` in the current scope --> rust/lancedb/src/remote/table/blobs.rs:379:40 | 379 | while let Some(batch) = stream.try_next().await? { | ^^^^^^^^ method not found in `Pin>` error[E0271]: type mismatch resolving ` as IntoIterator>::Item == Result<_, _>` --> rust/lancedb/src/remote/table/blobs.rs:481:27 | 481 | futures::stream::iter(probe_futures) | --------------------- ^^^^^^^^^^^^^ expected `Result<_, _>`, found future | | | required by a bound introduced by this call error[E0599]: no method named `buffered` found for struct `IterStream` in the current scope --> rust/lancedb/src/remote/table/blobs.rs:482:10 | 481 | / futures::stream::iter(probe_futures) 482 | | .buffered(BLOB_REQUEST_CONCURRENCY) | | -^^^^^^^^ method not found in `Iter>>>` | |_________| error[E0599]: no method named `next` found for struct `Pin>` in the current scope --> rust/lancedb/src/remote/table/insert.rs:324:37 | 324 | let mut first = match input.next().await { | ^^^^ method not found in `Pin>` error[E0599]: no method named `next` found for struct `Pin>` in the current scope --> rust/lancedb/src/remote/table/insert.rs:345:33 | 345 | first = match input.next().await { | ^^^^ method not found in `Pin>` error[E0599]: the method `next` exists for mutable reference `&mut Pin>`, but its trait bounds were not satisfied --> rust/lancedb/src/remote/table/insert.rs:446:41 | 446 | None => match input.next().await { | ^^^^ method cannot be called on `&mut Pin>` due to unsatisfied trait bounds | = note: the following trait bounds were not satisfied: `Pin>: Iterator` which is required by `&mut Pin>: Iterator` error[E0599]: no method named `map_err` found for struct `IterStream` in the current scope --> rust/lancedb/src/remote/table.rs:688:53 | 688 | let stream = futures::stream::iter(batches).map_err(DataFusionError::from); | ^^^^^^^ method not found in `Iter> + Send>>` error[E0599]: no method named `try_collect` found for struct `Pin>` in the current scope --> rust/lancedb/src/remote/table.rs:1378:49 | 1378 | let result: Result> = stream.try_collect().await.map_err(Error::from); | ^^^^^^^^^^^ error[E0599]: no method named `next` found for struct `Pin>` in the current scope --> rust/lancedb/src/remote/table.rs:1509:48 | 1509 | while let Some(batch) = stream.next().await { | ^^^^ method not found in `Pin>` error[E0599]: no method named `boxed` found for opaque type `impl Future>` in the current scope --> rust/lancedb/src/table/delete.rs:35:51 | 35 | let delete_result = dataset.delete(s).boxed().await?; | ^^^^^ method not found in `impl Future>` error[E0599]: no variant, associated function, or constant named `Left` found for enum `Either` in the current scope --> rust/lancedb/src/table/merge.rs:292:17 | 292 | Either::Left(tokio::time::timeout(timeout, future).map(|res| match res { | ^^^^ variant, associated function, or constant not found in `Either<_, _>` error[E0599]: `Timeout, ...), ...>>>` is not an iterator --> rust/lancedb/src/table/merge.rs:292:60 | 292 | Either::Left(tokio::time::timeout(timeout, future).map(|res| match res { | --------------------------------------^^^ `Timeout, ...), ...>>>` is not an iterator | ::: $CARGO_HOME/registry/src/index.crates.io-1949cf8c6b5b557f/pin-project-lite-0.2.17/src/lib.rs:745:9 | 745 | / $vis struct $ident $($def_generics)* 746 | | $(where 747 | | $($where_clause)*)? ... | 751 | | ),+ 752 | | } | |_________- doesn't satisfy `_: Iterator` | = note: the following trait bounds were not satisfied: `tokio::time::Timeout, MergeStats), lance::Error>>>: Iterator` which is required by `&mut tokio::time::Timeout, MergeStats), lance::Error>>>: Iterator` error[E0599]: no variant, associated function, or constant named `Right` found for enum `Either` in the current scope --> rust/lancedb/src/table/merge.rs:301:17 | 301 | Either::Right(job.execute_reader(new_data).map_err(|e| e.into())) | ^^^^^ variant, associated function, or constant not found in `Either<_, _>` error[E0599]: no method named `map_err` found for opaque type `impl Future, ...), ...>>` in the current scope --> rust/lancedb/src/table/merge.rs:301:52 | 301 | Either::Right(job.execute_reader(new_data).map_err(|e| e.into())) | ^^^^^^^ method not found in `impl Future, ...), ...>>` error[E0277]: the trait bound `Iter, ...>>: Stream` is not satisfied --> rust/lancedb/src/table/query.rs:681:38 | 681 | Ok(DatasetRecordBatchStream::new(record_batch_stream)) | ^^^^^^^^^^^^^^^^^^^ the trait `futures_core::stream::Stream` is not implemented for `Iter, ...>>` error[E0277]: the trait bound `TimeoutStream: futures_core::stream::Stream` is not satisfied --> rust/lancedb/src/utils/mod.rs:353:28 | 353 | impl RecordBatchStream for TimeoutStream { | ^^^^^^^^^^^^^ unsatisfied trait bound error[E0046]: not all trait items implemented, missing: `Error`, `poll` --> rust/lancedb/src/utils/mod.rs:359:1 | 359 | impl Stream for TimeoutStream { | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ missing `Error`, `poll` in implementation | = help: implement the missing item: `type Error = /* Type */;` = help: implement the missing item: `fn poll(&mut self) -> std::result::Result::Item>>, ::Error> { todo!() }` error[E0277]: the trait bound `MaxBatchLengthStream: futures_core::stream::Stream` is not satisfied --> rust/lancedb/src/utils/mod.rs:424:28 | 424 | impl RecordBatchStream for MaxBatchLengthStream { | ^^^^^^^^^^^^^^^^^^^^ unsatisfied trait bound error[E0046]: not all trait items implemented, missing: `Error`, `poll` --> rust/lancedb/src/utils/mod.rs:430:1 | 430 | impl Stream for MaxBatchLengthStream { | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ missing `Error`, `poll` in implementation | = help: implement the missing item: `type Error = /* Type */;` = help: implement the missing item: `fn poll(&mut self) -> std::result::Result::Item>>, ::Error> { todo!() }` error[E0599]: no method named `map` found for type parameter `I` in the current scope --> rust/lancedb/src/arrow.rs:75:45 | 72 | impl From for SendableRecordBatchStream { | - method `map` not found for this type parameter ... 75 | let mapped_stream = Box::pin(stream.map(|r| r.map_err(Into::into))); | ^^^ error[E0599]: no method named `poll_next` found for struct `Pin<&mut S>` in the current scope --> rust/lancedb/src/arrow.rs:113:21 | 113 | this.stream.poll_next(cx) | ^^^^^^^^^ | = help: items from traits can only be used if the trait is implemented and in scope = note: the following traits define an item `poll_next`, perhaps you need to implement one of them: candidate #1: `futures_core::stream::Stream` candidate #2: `sorts::stream::PartitionedStream` help: there is a method `collect` with a similar name, but with different arguments --> $CARGO_HOME/registry/src/index.crates.io-1949cf8c6b5b557f/futures-0.1.31/src/stream/mod.rs:563:5 | 563 | / fn collect(self) -> Collect 564 | | where Self: Sized | |_________________________^ error[E0599]: the method `map_err` exists for struct `Pin> + Send>>`, but its trait bounds were not satisfied --> rust/lancedb/src/arrow.rs:150:29 | 150 | let stream = stream.map_err(|err| Error::Arrow { source: err }); | ^^^^^^^ method cannot be called due to unsatisfied trait bounds error[E0308]: mismatched types --> rust/lancedb/src/data/scannable.rs:80:26 | 80 | stream: once(async move { Ok(batch) }), | ---- ^^^^^^^^^^^^^^^^^^^^^^^^ expected `Result<_, _>`, found `async` block | | | arguments to this function are incorrect | = note: expected enum `std::result::Result<_, _>` found `async` block `{async block@rust/lancedb/src/data/scannable.rs:80:26: 80:36}` note: function defined here --> $CARGO_HOME/registry/src/index.crates.io-1949cf8c6b5b557f/futures-0.1.31/src/stream/once.rs:20:8 | 20 | pub fn once(item: Result) -> Once { | ^^^^ help: try wrapping the expression in a variant of `std::result::Result` | 80 | stream: once(Ok(async move { Ok(batch) })), | +++ + 80 | stream: once(Err(async move { Ok(batch) })), | ++++ + error[E0308]: mismatched types --> rust/lancedb/src/data/scannable.rs:107:30 | 107 | stream: once(async { | _________________________----_^ | | | | | arguments to this function are incorrect 108 | | Err(Error::InvalidInput { 109 | | message: "Cannot scan an empty Vec".to_string(), 110 | | }) 111 | | }), | |_________________^ expected `Result<_, _>`, found `async` block | = note: expected enum `std::result::Result<_, _>` found `async` block `{async block@rust/lancedb/src/data/scannable.rs:107:30: 107:35}` note: function defined here --> $CARGO_HOME/registry/src/index.crates.io-1949cf8c6b5b557f/futures-0.1.31/src/stream/once.rs:20:8 | 20 | pub fn once(item: Result) -> Once { | ^^^^ help: try wrapping the expression in a variant of `std::result::Result` | 107 ~ stream: once(Ok(async { 108 | Err(Error::InvalidInput { 109 | message: "Cannot scan an empty Vec".to_string(), 110 | }) 111 ~ })), | 107 ~ stream: once(Err(async { 108 | Err(Error::InvalidInput { 109 | message: "Cannot scan an empty Vec".to_string(), 110 | }) 111 ~ })), | error[E0271]: expected `Ok` to return `Result, _>`, but it returns `Result` --> rust/lancedb/src/data/scannable.rs:117:52 | 117 | Box::pin(SimpleRecordBatchStream { schema, stream }) | ^^^^^^ expected `Result, _>`, found `Result` error[E0308]: mismatched types --> rust/lancedb/src/data/scannable.rs:158:59 | 158 | let stream = futures::stream::unfold(rx, |mut rx| async move { | ___________________________________________________________^ 159 | | rx.recv().await.map(|batch| (batch, rx)) 160 | | }) | |_________^ expected `Option<_>`, found `async` block | = note: expected enum `std::option::Option<_>` found `async` block `{async block@rust/lancedb/src/data/scannable.rs:158:59: 158:69}` help: try wrapping the expression in `Some` | 158 ~ let stream = futures::stream::unfold(rx, |mut rx| Some(async move { 159 | rx.recv().await.map(|batch| (batch, rx)) 160 ~ })) | error[E0599]: the method `fuse` exists for struct `Unfold>, ..., _>`, but its trait bounds were not satisfied --> rust/lancedb/src/data/scannable.rs:161:10 | 158 | let stream = futures::stream::unfold(rx, |mut rx| async move { | ______________________- 159 | | rx.recv().await.map(|batch| (batch, rx)) 160 | | }) 161 | | .fuse(); | | -^^^^ method cannot be called due to unsatisfied trait bounds | |_________| error[E0308]: mismatched types --> rust/lancedb/src/data/scannable.rs:178:26 | 178 | stream: once(async { | _____________________----_^ | | | | | arguments to this function are incorrect 179 | | Err(Error::InvalidInput { 180 | | message: "Stream has already been consumed".to_string(), 181 | | }) 182 | | }), | |_____________^ expected `Result<_, _>`, found `async` block | = note: expected enum `std::result::Result<_, _>` found `async` block `{async block@rust/lancedb/src/data/scannable.rs:178:26: 178:31}` note: function defined here --> $CARGO_HOME/registry/src/index.crates.io-1949cf8c6b5b557f/futures-0.1.31/src/stream/once.rs:20:8 | 20 | pub fn once(item: Result) -> Once { | ^^^^ help: try wrapping the expression in a variant of `std::result::Result` | 178 ~ stream: once(Ok(async { 179 | Err(Error::InvalidInput { 180 | message: "Stream has already been consumed".to_string(), 181 | }) 182 ~ })), | 178 ~ stream: once(Err(async { 179 | Err(Error::InvalidInput { 180 | message: "Stream has already been consumed".to_string(), 181 | }) 182 ~ })), | error[E0308]: mismatched types --> rust/lancedb/src/data/scannable.rs:474:53 | 474 | let prepend = futures::stream::once(std::future::ready(Ok(batch))); | --------------------- ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ expected `Result<_, _>`, found `Ready>` | | | arguments to this function are incorrect | = note: expected enum `std::result::Result<_, _>` found struct `std::future::Ready>` note: function defined here --> $CARGO_HOME/registry/src/index.crates.io-1949cf8c6b5b557f/futures-0.1.31/src/stream/once.rs:20:8 | 20 | pub fn once(item: Result) -> Once { | ^^^^ help: try wrapping the expression in a variant of `std::result::Result` | 474 | let prepend = futures::stream::once(Ok(std::future::ready(Ok(batch)))); | +++ + 474 | let prepend = futures::stream::once(Err(std::future::ready(Ok(batch)))); | ++++ + error[E0599]: the method `chain` exists for struct `futures::stream::Once<_, _>`, but its trait bounds were not satisfied --> rust/lancedb/src/data/scannable.rs:477:37 | 477 | stream: prepend.chain(rest), | ^^^^^ method cannot be called on `futures::stream::Once<_, _>` due to unsatisfied trait bounds error[E0308]: mismatched types --> rust/lancedb/src/data/scannable.rs:482:47 | 482 | stream: futures::stream::once(std::future::ready(Ok(batch))), | --------------------- ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ expected `Result<_, _>`, found `Ready>` | | | arguments to this function are incorrect | = note: expected enum `std::result::Result<_, _>` found struct `std::future::Ready>` note: function defined here --> $CARGO_HOME/registry/src/index.crates.io-1949cf8c6b5b557f/futures-0.1.31/src/stream/once.rs:20:8 | 20 | pub fn once(item: Result) -> Once { | ^^^^ help: try wrapping the expression in a variant of `std::result::Result` | 482 | stream: futures::stream::once(Ok(std::future::ready(Ok(batch)))), | +++ + 482 | stream: futures::stream::once(Err(std::future::ready(Ok(batch)))), | ++++ + error[E0308]: mismatched types --> rust/lancedb/src/data/scannable.rs:486:56 | 486 | let stream = futures::stream::once(std::future::ready(err)); | --------------------- ^^^^^^^^^^^^^^^^^^^^^^^ expected `Result<_, _>`, found `Ready>` | | | arguments to this function are incorrect | = note: expected enum `std::result::Result<_, _>` found struct `std::future::Ready>` note: function defined here --> $CARGO_HOME/registry/src/index.crates.io-1949cf8c6b5b557f/futures-0.1.31/src/stream/once.rs:20:8 | 20 | pub fn once(item: Result) -> Once { | ^^^^ help: try wrapping the expression in a variant of `std::result::Result` | 486 | let stream = futures::stream::once(Ok(std::future::ready(err))); | +++ + 486 | let stream = futures::stream::once(Err(std::future::ready(err))); | ++++ + error[E0599]: no method named `and_then` found for struct `Pin> + Send>>` in the current scope --> rust/lancedb/src/io/object_store.rs:153:32 | 153 | Box::pin(put_secondary.and_then(|_| put_primary)) | ^^^^^^^^ error[E0271]: expected `IntoIter, 1>` to be an iterator that yields `Result, _>`, but it yields `Result` --> rust/lancedb/src/query.rs:1465:25 | 1465 | return Box::pin(SimpleRecordBatchStream::new( | ^^^^^^^^^^^^^^^^^^^^^^^ expected `Result, _>`, found `Result` error[E0271]: expected `IntoIter>` to be an iterator that yields `Result, _>`, but it yields `Result` --> rust/lancedb/src/query.rs:1478:14 | 1478 | Box::pin(SimpleRecordBatchStream::new(stream::iter(batches), schema)) | ^^^^^^^^^^^^^^^^^^^^^^^ expected `Result, _>`, found `Result` error[E0308]: mismatched types --> rust/lancedb/src/remote/table/insert.rs:626:44 | 626 | let stream = futures::stream::once(async move { | ______________________---------------------_^ | | | | | arguments to this function are incorrect ... | 791 | | Ok::<_, DataFusionError>(batch) 792 | | }); | |_________^ expected `Result<_, _>`, found `async` block | = note: expected enum `std::result::Result<_, _>` found `async` block `{async block@rust/lancedb/src/remote/table/insert.rs:626:44: 626:54}` note: function defined here --> $CARGO_HOME/registry/src/index.crates.io-1949cf8c6b5b557f/futures-0.1.31/src/stream/once.rs:20:8 | 20 | pub fn once(item: Result) -> Once { | ^^^^ help: try wrapping the expression in a variant of `std::result::Result` | 626 ~ let stream = futures::stream::once(Ok(async move { 627 | // Multipart writes with a byte budget split the partition into ... 791 | Ok::<_, DataFusionError>(batch) 792 ~ })); | 626 ~ let stream = futures::stream::once(Err(async move { 627 | // Multipart writes with a byte budget split the partition into ... 791 | Ok::<_, DataFusionError>(batch) 792 ~ })); | error[E0277]: the trait bound `futures::stream::Once<_, _>: futures_core::stream::Stream` is not satisfied --> rust/lancedb/src/remote/table/insert.rs:794:12 | 794 | Ok(Box::pin(RecordBatchStreamAdapter::new( | ____________^ 795 | | COUNT_SCHEMA.clone(), 796 | | stream, 797 | | ))) | |__________^ the trait `futures_core::stream::Stream` is not implemented for `futures::stream::Once<_, _>` error[E0599]: no method named `try_collect` found for struct `Pin>` in the current scope --> rust/lancedb/src/remote/table.rs:2442:49 | 2442 | let result: Result> = stream.try_collect().await.map_err(Error::from); | ^^^^^^^^^^^ error[E0277]: the trait bound `impl Stream>: TryStream` is not satisfied --> rust/lancedb/src/remote/util.rs:47:35 | 47 | Ok(reqwest::Body::wrap_stream(stream)) | -------------------------- ^^^^^^ unsatisfied trait bound | | | required by a bound introduced by this call error[E0599]: no method named `map_ok` found for struct `Pin>` in the current scope --> rust/lancedb/src/table/datafusion/insert.rs:200:30 | 200 | input_stream.map_ok(move |batch| { | -------------^^^^^^ method not found in `Pin>` error[E0308]: mismatched types --> rust/lancedb/src/table/datafusion/insert.rs:208:44 | 208 | let stream = futures::stream::once(async move { | ______________________---------------------_^ | | | | | arguments to this function are incorrect 209 | | if let Some(tracker) = tracker 210 | | && write_params.write_progress.is_none() ... | 255 | | )?) 256 | | }); | |_________^ expected `Result<_, _>`, found `async` block | = note: expected enum `std::result::Result<_, _>` found `async` block `{async block@rust/lancedb/src/table/datafusion/insert.rs:208:44: 208:54}` note: function defined here --> $CARGO_HOME/registry/src/index.crates.io-1949cf8c6b5b557f/futures-0.1.31/src/stream/once.rs:20:8 | 20 | pub fn once(item: Result) -> Once { | ^^^^ help: try wrapping the expression in a variant of `std::result::Result` | 208 ~ let stream = futures::stream::once(Ok(async move { 209 | if let Some(tracker) = tracker ... 255 | )?) 256 ~ })); | 208 ~ let stream = futures::stream::once(Err(async move { 209 | if let Some(tracker) = tracker ... 255 | )?) 256 ~ })); | error[E0277]: the trait bound `futures::stream::Once<_, _>: futures_core::stream::Stream` is not satisfied --> rust/lancedb/src/table/datafusion/insert.rs:258:12 | 258 | Ok(Box::pin(RecordBatchStreamAdapter::new( | ____________^ 259 | | COUNT_SCHEMA.clone(), 260 | | stream, 261 | | ))) | |__________^ the trait `futures_core::stream::Stream` is not implemented for `futures::stream::Once<_, _>` error[E0599]: no method named `map_ok` found for struct `Pin>` in the current scope --> rust/lancedb/src/table/datafusion.rs:128:29 | 128 | let stream = stream.map_ok(move |batch| { | -------^^^^^^ method not found in `Pin>` error[E0599]: no method named `map_err` found for struct `Pin, ...>> + Send>>` in the current scope --> rust/lancedb/src/table/datafusion.rs:245:14 | 242 | let plan = self | ____________________- 243 | | .table 244 | | .create_plan(&AnyQuery::Query(query), options) 245 | | .map_err(|err| DataFusionError::External(err.into())) | | -^^^^^^^ method not found in `Pin, ...>> + Send>>` | |_____________| error[E0599]: no method named `next` found for struct `Pin>` in the current scope --> rust/lancedb/src/table.rs:3048:48 | 3048 | while let Some(batch) = stream.next().await { | ^^^^ method not found in `Pin>` error[E0277]: the trait bound `JoinHandle>: Future` is not satisfied --> rust/lancedb/src/table.rs:3038:23 | 3038 | let handles = FuturesUnordered::new(); | ^^^^^^^^^^^^^^^^^^^^^^^ the trait `futures::Future` is not implemented for `tokio::task::JoinHandle>` error[E0277]: `FuturesUnordered>>` is not an iterator --> rust/lancedb/src/table.rs:3054:23 | 3054 | for handle in handles { | ^^^^^^^ `FuturesUnordered>>` is not an iterator error[E0277]: the trait bound `{async block@rust/lancedb/src/table.rs:3452:29: 3452:39}: futures::IntoFuture` is not satisfied --> rust/lancedb/src/table.rs:3450:13 | 3449 | let mut sorted_sizes = join_all( | -------- required by a bound introduced by this call 3450 | / frags 3451 | | .iter() 3452 | | .map(|frag| async move { frag.physical_rows().await.unwrap_or(0) }), | |___________________________________________________________________________________^ the trait `futures::Future` is not implemented for `{async block@rust/lancedb/src/table.rs:3452:29: 3452:39}` | = note: `{async block@rust/lancedb/src/table.rs:3452:29: 3452:39}` implements similarly named trait `std::future::Future`, but not `futures::Future` = help: the following other types implement trait `futures::Future`: &'a mut F AssertUnwindSafe BiLockAcquire Box Concat2 Either Finished Fold and 43 others = note: required for `{async block@rust/lancedb/src/table.rs:3452:29: 3452:39}` to implement `futures::IntoFuture` note: required by a bound in `join_all` --> $CARGO_HOME/registry/src/index.crates.io-1949cf8c6b5b557f/futures-0.1.31/src/future/join_all.rs:78:20 | 76 | pub fn join_all(i: I) -> JoinAll | -------- required by a bound in this function 77 | where I: IntoIterator, 78 | I::Item: IntoFuture, | ^^^^^^^^^^ required by this bound in `join_all` error[E0277]: the trait bound `{async block@rust/lancedb/src/table.rs:3452:29: 3452:39}: futures::Future` is not satisfied --> rust/lancedb/src/table.rs:3449:32 | 3449 | let mut sorted_sizes = join_all( | ________________________________^ 3450 | | frags 3451 | | .iter() 3452 | | .map(|frag| async move { frag.physical_rows().await.unwrap_or(0) }), 3453 | | ) | |_________^ the trait `futures::Future` is not implemented for `{async block@rust/lancedb/src/table.rs:3452:29: 3452:39}` | = note: `{async block@rust/lancedb/src/table.rs:3452:29: 3452:39}` implements similarly named trait `std::future::Future`, but not `futures::Future` = help: the following other types implement trait `futures::Future`: &'a mut F AssertUnwindSafe BiLockAcquire Box Concat2 Either Finished Fold and 43 others = note: required for `{async block@rust/lancedb/src/table.rs:3452:29: 3452:39}` to implement `futures::IntoFuture` note: required by a bound in `JoinAll` --> $CARGO_HOME/registry/src/index.crates.io-1949cf8c6b5b557f/futures-0.1.31/src/future/join_all.rs:24:20 | 22 | pub struct JoinAll | ------- required by a bound in this struct 23 | where I: IntoIterator, 24 | I::Item: IntoFuture, | ^^^^^^^^^^ required by this bound in `JoinAll` error[E0277]: `JoinAll, {closure@...}>>` is not a future --> rust/lancedb/src/table.rs:3454:10 | 3449 | let mut sorted_sizes = join_all( | ________________________________- 3450 | | frags 3451 | | .iter() 3452 | | .map(|frag| async move { frag.physical_rows().await.unwrap_or(0) }), 3453 | | ) | |_________- this call returns `JoinAll, {closure@rust/lancedb/src/table.rs:3452:22: 3452:28}>>` 3454 | .await; | ^^^^^ `JoinAll, {closure@...}>>` is not a future error[E0277]: the trait bound `{async block@rust/lancedb/src/table.rs:3452:29: 3452:39}: futures::Future` is not satisfied --> rust/lancedb/src/table.rs:3454:10 | 3454 | .await; | ^^^^^ the trait `futures::Future` is not implemented for `{async block@rust/lancedb/src/table.rs:3452:29: 3452:39}` | = note: `{async block@rust/lancedb/src/table.rs:3452:29: 3452:39}` implements similarly named trait `std::future::Future`, but not `futures::Future` = help: the following other types implement trait `futures::Future`: &'a mut F AssertUnwindSafe BiLockAcquire Box Concat2 Either Finished Fold and 43 others = note: required for `{async block@rust/lancedb/src/table.rs:3452:29: 3452:39}` to implement `futures::IntoFuture` note: required by a bound in `JoinAll` --> $CARGO_HOME/registry/src/index.crates.io-1949cf8c6b5b557f/futures-0.1.31/src/future/join_all.rs:24:20 | 22 | pub struct JoinAll | ------- required by a bound in this struct 23 | where I: IntoIterator, 24 | I::Item: IntoFuture, | ^^^^^^^^^^ required by this bound in `JoinAll` error[E0282]: type annotations needed --> rust/lancedb/src/utils/background_cache.rs:119:40 | 119 | inner: Arc::new(Mutex::new(CacheInner { | ________________________________________^ 120 | | state: State::Empty, 121 | | generation: 0, 122 | | })), | |_____________^ cannot infer type of the type parameter `E` declared on the struct `CacheInner` | help: consider specifying the generic arguments | 119 | inner: Arc::new(Mutex::new(CacheInner:: { | ++++++++ error[E0282]: type annotations needed --> rust/lancedb/src/utils/background_cache.rs:134:9 | 134 | cache.state.fresh_value(self.ttl, self.refresh_window) | ^^^^^^^^^^^ cannot infer type for type parameter `E` error[E0282]: type annotations needed --> rust/lancedb/src/utils/background_cache.rs:173:23 | 173 | cache.state = State::Current(value, clock::now()); | ^^^^^^^^^^^^^^ cannot infer type of the type parameter `E` declared on the enum `State` | help: consider specifying the generic arguments | 173 | cache.state = State::::Current(value, clock::now()); | ++++++++ error[E0282]: type annotations needed --> rust/lancedb/src/utils/background_cache.rs:182:23 | 182 | cache.state = State::Empty; | ^^^^^^^^^^^^ cannot infer type of the type parameter `E` declared on the enum `State` | help: consider specifying the generic arguments | 182 | cache.state = State::::Empty; | ++++++++ error[E0599]: no method named `boxed` found for `async` block `{async block@rust/lancedb/src/utils/background_cache.rs:269:22: 269:32}` in the current scope --> rust/lancedb/src/utils/background_cache.rs:270:14 | 269 | let shared = async move { (fetch)().await.map_err(Arc::new) } | ______________________- 270 | | .boxed() | | -^^^^^ method not found in `{async block@rust/lancedb/src/utils/background_cache.rs:269:22: 269:32}` | |_____________| error[E0277]: the trait bound `TimeoutStream: futures_core::stream::Stream` is not satisfied --> rust/lancedb/src/utils/mod.rs:345:9 | 345 | Box::pin(Self::new(inner, timeout)) | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ unsatisfied trait bound error[E0599]: no method named `poll_next` found for struct `Pin<&mut TimeoutStream>` in the current scope --> rust/lancedb/src/utils/mod.rs:376:22 | 376 | self.poll_next(cx) | ^^^^^^^^^ | = help: items from traits can only be used if the trait is implemented and in scope = note: the following traits define an item `poll_next`, perhaps you need to implement one of them: candidate #1: `futures_core::stream::Stream` candidate #2: `sorts::stream::PartitionedStream` help: there is a method `collect` with a similar name, but with different arguments --> $CARGO_HOME/registry/src/index.crates.io-1949cf8c6b5b557f/futures-0.1.31/src/stream/mod.rs:563:5 | 563 | / fn collect(self) -> Collect 564 | | where Self: Sized | |_________________________^ error[E0599]: no method named `poll_unpin` found for mutable reference `&mut Pin>` in the current scope --> rust/lancedb/src/utils/mod.rs:378:75 | 378 | TimeoutState::Started { deadline, timeout } => match deadline.poll_unpin(cx) { | ^^^^^^^^^^ method not found in `&mut Pin>` error[E0599]: no method named `poll_next` found for struct `Pin<&mut Pin>>` in the current scope --> rust/lancedb/src/utils/mod.rs:386:27 | 386 | inner.poll_next(cx) | ^^^^^^^^^ error[E0277]: the trait bound `MaxBatchLengthStream: futures_core::stream::Stream` is not satisfied --> rust/lancedb/src/utils/mod.rs:419:13 | 419 | Box::pin(Self::new(inner, max_batch_length)) | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ unsatisfied trait bound error[E0599]: no method named `poll_next` found for struct `Pin<&mut Pin>>` in the current scope --> rust/lancedb/src/utils/mod.rs:439:50 | 439 | return Pin::new(&mut self.inner).poll_next(cx); | ^^^^^^^^^ error[E0599]: no method named `poll_next` found for struct `Pin<&mut Pin>>` in the current scope --> rust/lancedb/src/utils/mod.rs:459:45 | 459 | match Pin::new(&mut self.inner).poll_next(cx) { | ^^^^^^^^^ Some errors have detailed explanations: E0046, E0107, E0191, E0271, E0277, E0282, E0308, E0407, E0425... For more information about an error, try `rustc --explain E0046`. error: could not compile `lancedb` (lib) due to 118 previous errors ``` --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index a879d1f8b..78474cd16 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -52,7 +52,7 @@ env_logger = "0.11" half = { "version" = "2.7.1", default-features = false, features = [ "num-traits", ] } -futures = "0" +futures = "0.3" log = "0.4" metrics = "0.24" metrics-util = "0.19" From c5f9efefe9396c058b3ce6aa2298c0ef635477fb Mon Sep 17 00:00:00 2001 From: "lancedb-gatefixer[bot]" <313497061+lancedb-gatefixer[bot]@users.noreply.github.com> Date: Fri, 7 Aug 2026 17:30:55 +0800 Subject: [PATCH 03/15] test(python): cover local sync multiple-vector search (#3830) ## Summary - add regression coverage for multiple query vectors in the local synchronous Python API - verify that each query vector receives its own limited nearest-neighbor result and `query_index` ## Root cause In LanceDB v0.16, the local synchronous scanner passed a nested vector array as one query, unlike the async and remote implementations. The subsequent sync-to-async table migration supplied the correct shared runtime path, but this local sync behavior was never regression-tested and issue #1857 remained open. ## Validation - `uv run --extra tests pytest python/tests/test_query.py::test_query_multiple_vectors -q` - `uv run --project python --extra tests --extra dev ruff format --check python/python/tests/test_query.py` - `uv run --project python --extra tests --extra dev ruff check .` Fixes #1857 Co-authored-by: Gatefixer <313497061+lancedb-gatefixer[bot]@users.noreply.github.com> --- python/python/tests/test_query.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/python/python/tests/test_query.py b/python/python/tests/test_query.py index 6840be052..d2629d1a8 100644 --- a/python/python/tests/test_query.py +++ b/python/python/tests/test_query.py @@ -570,6 +570,15 @@ def test_query_builder(table): assert all(np.array(rs[0]["vector"]) == [1, 2]) +def test_query_multiple_vectors(table): + results = table.search([np.array([1, 2]), np.array([4, 5])]).limit(1).to_list() + + assert len(results) == 2 + results_by_query = {result["query_index"]: result for result in results} + assert results_by_query[0]["id"] == 1 + assert results_by_query[1]["id"] == 2 + + def test_with_row_id(table: lancedb.table.Table): rs = table.search().with_row_id(True).to_arrow() assert "_rowid" in rs.column_names From 2922c171f7feecc6ce6ab63621fb4e3791e36420 Mon Sep 17 00:00:00 2001 From: "lancedb-gatefixer[bot]" <313497061+lancedb-gatefixer[bot]@users.noreply.github.com> Date: Fri, 7 Aug 2026 17:31:08 +0800 Subject: [PATCH 04/15] test(rust): cover Azure table URI separators (#3837) ## Root cause The former listing-database table URI builder used OS-native `Path::join` for object-store URIs. On Windows this inserted backslashes into `az://` table paths, so `table_names` found slash-delimited objects while `open_table` addressed a different key. The production path now builds URI paths with forward slashes after the equivalent S3 report was fixed in #2575, but #1072 remained open without Azure-specific regression coverage. ## Fix - Add Azure URI regression assertions at the Rust table URI construction boundary. - Cover connection bases both with and without a trailing slash, matching the behavior reported in #1072. - Verify the resulting table URI always uses forward slashes on every platform. ## Validation - `cargo fmt --all -- --check` - `cargo test --quiet -p lancedb --lib database::listing::tests::test_table_uri` - `cargo check --quiet --features remote --tests --examples` - `cargo clippy --quiet --features remote --tests --examples` - `cargo test --quiet --features remote --tests` (866 passed, 1 ignored) Fixes #1072 Co-authored-by: Gatefixer <313497061+lancedb-gatefixer[bot]@users.noreply.github.com> --- rust/lancedb/src/database/listing.rs | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/rust/lancedb/src/database/listing.rs b/rust/lancedb/src/database/listing.rs index fea34bb48..a4624112e 100644 --- a/rust/lancedb/src/database/listing.rs +++ b/rust/lancedb/src/database/listing.rs @@ -2342,7 +2342,7 @@ mod tests { #[tokio::test] async fn test_table_uri() { - let (_tempdir, db) = setup_database().await; + let (_tempdir, mut db) = setup_database().await; let mut pb = PathBuf::new(); pb.push(db.uri.clone()); @@ -2351,6 +2351,18 @@ mod tests { let expected = pb.to_str().unwrap(); let uri = db.table_uri("test").ok().unwrap(); assert_eq!(uri, expected); + + // URI paths always use forward slashes, even on Windows. Using + // `Path::join` here used to produce `az://container/prefix\\test.lance`, + // which Azure treated as a different object from the table returned by + // `table_names` (https://github.com/lancedb/lancedb/issues/1072). + for base_uri in ["az://container/prefix", "az://container/prefix/"] { + db.uri = base_uri.to_string(); + assert_eq!( + db.table_uri("test").unwrap(), + "az://container/prefix/test.lance" + ); + } } /// Regression: connecting via a URL-style URI (which goes through From 4048150fdd3bc19d7c651f1e4e7e16c82b6b5888 Mon Sep 17 00:00:00 2001 From: "lancedb-gatefixer[bot]" <313497061+lancedb-gatefixer[bot]@users.noreply.github.com> Date: Fri, 7 Aug 2026 17:31:19 +0800 Subject: [PATCH 05/15] test(python): cover nullable fixed-size-list ingestion (#3812) ## Summary - add regression coverage for adding dictionary rows with a nullable fixed-size-list column - verify ordinary list columns remain aligned alongside the null fixed-size-list value ## Root cause PyArrow infers an all-`None` dictionary column as the generic `null` type. The original schema-alignment path treated the target fixed-size-list type as proof that the inferred source was also list-like and unconditionally accessed `value_field`, which raised `AttributeError`. Current alignment logic correctly falls back to the target type when the source is not list-like; this test locks in that repair for the reported ingestion path. ## Validation - `uv run --extra tests pytest python/tests/test_table.py::test_add_with_empty_fixed_size_list_drops_bad_rows python/tests/test_table.py::test_add_nullable_fixed_size_list_with_none python/tests/test_table.py::test_add_nullable_struct_with_none -q` - `uv run --with pyarrow==19.0.1 --extra tests pytest python/tests/test_table.py::test_add_nullable_fixed_size_list_with_none -q` - `uv run --project python --extra dev ruff format --check python/python/tests/test_table.py` - `uv run --project python --extra dev ruff check .` Fixes #2340 Co-authored-by: Gatefixer <313497061+lancedb-gatefixer[bot]@users.noreply.github.com> --- python/python/tests/test_table.py | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/python/python/tests/test_table.py b/python/python/tests/test_table.py index b2bfa2a68..e5c4ad801 100644 --- a/python/python/tests/test_table.py +++ b/python/python/tests/test_table.py @@ -1845,6 +1845,27 @@ def test_add_with_empty_fixed_size_list_drops_bad_rows(mem_db: DBConnection): assert np.allclose(data["embedding"].to_pylist()[0], np.array([0.1] * 16)) +def test_add_nullable_fixed_size_list_with_none(mem_db: DBConnection): + """Regression test for issue #2340.""" + table = mem_db.create_table( + "test_nullable_fixed_size_list", + schema=pa.schema( + [ + pa.field("id", pa.string()), + pa.field("feature", pa.list_(pa.float32(), 256)), + pa.field("tags", pa.list_(pa.string())), + ] + ), + ) + + table.add([{"id": "1", "feature": None, "tags": ["tag1", "tag2"]}]) + + result = table.to_arrow() + assert result.to_pylist() == [ + {"id": "1", "feature": None, "tags": ["tag1", "tag2"]} + ] + + def test_add_nullable_struct_with_none(mem_db: DBConnection): """Regression test for issue #2654: a nullable struct column whose first batch contains only None values must not crash in From fc44535ceeac0fd73ae94d0a0151c35753cb3e40 Mon Sep 17 00:00:00 2001 From: "lancedb-gatefixer[bot]" <313497061+lancedb-gatefixer[bot]@users.noreply.github.com> Date: Fri, 7 Aug 2026 17:31:30 +0800 Subject: [PATCH 06/15] fix(python): clarify bare Vector annotations (#3809) ## Summary - raise a clear `TypeError` when `Vector` is used without a dimension - preserve normal `Vector(dim)` behavior across Pydantic v1 and v2 - add a regression test that defines a model without importing PyArrow ## Root cause Pydantic interpreted the bare `Vector` factory as a callable field type and inspected its postponed annotations in the user model's namespace. Because that namespace did not define LanceDB's internal `pa` alias, model construction failed with the misleading `NameError: name 'pa' is not defined` instead of explaining that `Vector` must be parameterized. The factory now exposes Pydantic's v1 and v2 schema hooks and rejects bare use before signature introspection with guidance to use `Vector(dim)`. ## Validation - `uvx --from 'ruff==0.15.20' ruff check .` - `uvx --from 'ruff==0.15.20' ruff format --check python/python/lancedb/pydantic.py python/python/tests/test_pydantic.py` - `cd python && uv run --extra tests pytest python/tests/test_pydantic.py::test_bare_vector_raises_clear_error -q` - `cd python && uv run --extra tests pytest python/tests/test_pydantic.py -q` - compatibility checks with Pydantic 1.10.22, 2.11.4, and 2.13.4 Fixes #2384 Co-authored-by: Gatefixer <313497061+lancedb-gatefixer[bot]@users.noreply.github.com> --- python/python/lancedb/pydantic.py | 10 ++++++++++ python/python/tests/test_pydantic.py | 11 +++++++++++ 2 files changed, 21 insertions(+) diff --git a/python/python/lancedb/pydantic.py b/python/python/lancedb/pydantic.py index c4dedc0e6..1ab6e6fcc 100644 --- a/python/python/lancedb/pydantic.py +++ b/python/python/lancedb/pydantic.py @@ -153,6 +153,16 @@ def Vector( return FixedSizeList +def _raise_bare_vector_error(*_args): + raise TypeError("Vector must be parameterized with a dimension, e.g. Vector(128).") + + +# Pydantic v1 and v2 otherwise treat the bare Vector factory as a field validator +# and inspect its signature, which produces misleading errors about internal types. +setattr(Vector, "__get_validators__", _raise_bare_vector_error) +setattr(Vector, "__get_pydantic_core_schema__", _raise_bare_vector_error) + + def MultiVector( dim: int, value_type: pa.DataType = pa.float32(), nullable: bool = True ) -> Type: diff --git a/python/python/tests/test_pydantic.py b/python/python/tests/test_pydantic.py index e1d533784..db93d7c64 100644 --- a/python/python/tests/test_pydantic.py +++ b/python/python/tests/test_pydantic.py @@ -415,6 +415,17 @@ def test_nullable_vector(): assert schema == pa.schema([pa.field("vec", pa.list_(pa.float32(), 16), True)]) +def test_bare_vector_raises_clear_error(): + namespace = { + "__name__": "test_model_without_pyarrow", + "LanceModel": LanceModel, + "Vector": Vector, + } + + with pytest.raises(TypeError, match=r"Vector must be parameterized.*Vector\(128\)"): + exec("class TestModel(LanceModel):\n vector: Vector", namespace) + + def test_fixed_size_list_field(): class TestModel(pydantic.BaseModel): vec: Vector(16) From ec80acb668b361e395ecfa5ee381391bd3b3b859 Mon Sep 17 00:00:00 2001 From: "lancedb-gatefixer[bot]" <313497061+lancedb-gatefixer[bot]@users.noreply.github.com> Date: Fri, 7 Aug 2026 17:31:42 +0800 Subject: [PATCH 07/15] fix(python): expose inline types to downstream checkers (#3817) ## Summary - publish the PEP 561 `py.typed` marker so downstream type checkers consume the inline public annotations - add a Pyright contract test that distinguishes synchronous `connect` from awaited `connect_async` - verify the marker is present in the installed package ## Root cause The public Python module already annotated `lancedb.connect` as synchronous and `lancedb.connect_async` as asynchronous. The private native `_lancedb.connect` stub is intentionally awaitable because it backs `connect_async`. However, the distribution did not include a PEP 561 marker, so downstream tools such as mypy could ignore the public inline annotations and expose misleading or incomplete type information. ## Validation - `python/.venv/bin/ruff format --check python/python/tests/test_db.py python/python/type_tests/connect.py` - `python/.venv/bin/ruff check .` - `cd python && .venv/bin/pytest python/tests/test_db.py::test_package_includes_pep_561_marker -q` - `cd python && .venv/bin/pyright --pythonpath .venv/bin/python` - downstream mypy contract check for both public connection functions Fixes #2159 Co-authored-by: lancedb-gatefixer[bot] <313497061+lancedb-gatefixer[bot]@users.noreply.github.com> --- python/pyproject.toml | 1 + python/python/lancedb/py.typed | 1 + python/python/tests/test_db.py | 5 +++++ python/python/type_tests/connect.py | 15 +++++++++++++++ 4 files changed, 22 insertions(+) create mode 100644 python/python/lancedb/py.typed create mode 100644 python/python/type_tests/connect.py diff --git a/python/pyproject.toml b/python/pyproject.toml index cb175bd6d..348058957 100644 --- a/python/pyproject.toml +++ b/python/pyproject.toml @@ -140,6 +140,7 @@ include = [ "python/lancedb/remote/errors.py", "python/lancedb/embeddings/__init__.py", "python/lancedb/_lancedb.pyi", + "python/type_tests/connect.py", ] exclude = ["python/tests/"] pythonVersion = "3.13" diff --git a/python/python/lancedb/py.typed b/python/python/lancedb/py.typed new file mode 100644 index 000000000..8b1378917 --- /dev/null +++ b/python/python/lancedb/py.typed @@ -0,0 +1 @@ + diff --git a/python/python/tests/test_db.py b/python/python/tests/test_db.py index 8f4a8850c..84e78fd8f 100644 --- a/python/python/tests/test_db.py +++ b/python/python/tests/test_db.py @@ -6,6 +6,7 @@ import inspect import re import sys from datetime import timedelta +from importlib import resources import os from types import SimpleNamespace @@ -18,6 +19,10 @@ from lance_namespace.errors import NamespaceNotEmptyError, TableNotFoundError from lancedb.pydantic import LanceModel, Vector +def test_package_includes_pep_561_marker(): + assert resources.files(lancedb).joinpath("py.typed").is_file() + + def test_basic(tmp_path): db = lancedb.connect(tmp_path) diff --git a/python/python/type_tests/connect.py b/python/python/type_tests/connect.py new file mode 100644 index 000000000..eb2cba37c --- /dev/null +++ b/python/python/type_tests/connect.py @@ -0,0 +1,15 @@ +# SPDX-License-Identifier: Apache-2.0 +# SPDX-FileCopyrightText: Copyright The LanceDB Authors + +from typing import assert_type + +import lancedb +from lancedb import AsyncConnection, DBConnection + + +def check_connect_type() -> None: + assert_type(lancedb.connect("memory://"), DBConnection) + + +async def check_connect_async_type() -> None: + assert_type(await lancedb.connect_async("memory://"), AsyncConnection) From dbc3687c7b9cfc2e5923d2c8a28a94cb6b9e56da Mon Sep 17 00:00:00 2001 From: "lancedb-gatefixer[bot]" <313497061+lancedb-gatefixer[bot]@users.noreply.github.com> Date: Fri, 7 Aug 2026 17:31:53 +0800 Subject: [PATCH 08/15] fix(node): require compatible Node.js types (#3829) ## Summary - require Node.js 18-compatible type declarations when TypeScript consumers install them - keep the type peer optional for JavaScript-only consumers - add a regression test tying the Node type peer range to the supported runtime ## Root cause LanceDB requires Node.js 18 or newer, and its public types expose Apache Arrow declarations that import built-ins through the node: scheme. The package did not declare a matching @types/node peer requirement, so npm accepted projects pinned to Node 12 declarations and TypeScript then reported that node:stream and node:fs/promises did not exist. ## Validation - pnpm lint - pnpm build - pnpm run docs - pnpm test --runInBand (678 passed, 5 skipped) - packed-package consumer probe rejects @types/node 12.20.55 and installs with @types/node 18.19.130 Fixes #1713 Co-authored-by: Gatefixer <313497061+lancedb-gatefixer[bot]@users.noreply.github.com> --- nodejs/__test__/package.test.ts | 14 ++++++++++++++ nodejs/package-lock.json | 6 ++++++ nodejs/package.json | 6 ++++++ 3 files changed, 26 insertions(+) create mode 100644 nodejs/__test__/package.test.ts diff --git a/nodejs/__test__/package.test.ts b/nodejs/__test__/package.test.ts new file mode 100644 index 000000000..7743d73d6 --- /dev/null +++ b/nodejs/__test__/package.test.ts @@ -0,0 +1,14 @@ +// SPDX-License-Identifier: Apache-2.0 +// SPDX-FileCopyrightText: Copyright The LanceDB Authors + +import packageJson = require("../package.json"); + +describe("package metadata", () => { + it("requires Node.js type declarations compatible with the runtime", () => { + expect(packageJson.engines.node).toBe(">= 18"); + expect(packageJson.peerDependencies["@types/node"]).toBe(">=18"); + expect(packageJson.peerDependenciesMeta["@types/node"]).toEqual({ + optional: true, + }); + }); +}); diff --git a/nodejs/package-lock.json b/nodejs/package-lock.json index 8e30b0fab..bdbd3cf79 100644 --- a/nodejs/package-lock.json +++ b/nodejs/package-lock.json @@ -55,7 +55,13 @@ "openai": "4.29.2" }, "peerDependencies": { + "@types/node": ">=18", "apache-arrow": ">=15.0.0 <=18.1.0" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } } }, "node_modules/@aws-crypto/crc32": { diff --git a/nodejs/package.json b/nodejs/package.json index f3f719af2..671f3f94d 100644 --- a/nodejs/package.json +++ b/nodejs/package.json @@ -101,6 +101,12 @@ "openai": "4.29.2" }, "peerDependencies": { + "@types/node": ">=18", "apache-arrow": ">=15.0.0 <=18.1.0" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } } } From dd5cb4d805b6cd79c7ecf9fb25754285d5d631bd Mon Sep 17 00:00:00 2001 From: "lancedb-gatefixer[bot]" <313497061+lancedb-gatefixer[bot]@users.noreply.github.com> Date: Fri, 7 Aug 2026 17:32:05 +0800 Subject: [PATCH 09/15] test(python): cover float16 table creation from Arrow data (#3785) ## Summary - exercise float16 sanitization through the reported direct Arrow-data table creation path - assert that the inferred fixed-size vector schema remains float16 - retain end-to-end index creation and vector search coverage ## Root cause and fix PyArrow 16 does not provide an is_nan kernel for half-float arrays, so passing float16 vector values directly to that kernel raises ArrowNotImplementedError. LanceDB's sanitizer already carries the compatibility fix from #837: it casts float16 values to float32 only for NaN detection while preserving the stored vector type. The existing end-to-end regression created an empty schema-defined table and added data afterward. This change aligns that regression with the issue reproduction by creating a table directly from a FixedSizeList Arrow table and verifying the persisted schema. ## Validation - uv run --extra tests pytest python/tests/test_table.py::test_create_f16_table_from_arrow_data -q - direct 1,000-row by 128-dimension float16 Arrow-table reproduction - PyArrow 16.1 half-float is_nan kernel reproduction - uvx ruff@0.15.20 format --check python/python/tests/test_table.py - uvx ruff@0.15.20 check . Fixes #835 --------- Co-authored-by: Gatefixer <313497061+lancedb-gatefixer[bot]@users.noreply.github.com> --- python/python/tests/test_table.py | 27 ++++++++++++++++++++++++++- 1 file changed, 26 insertions(+), 1 deletion(-) diff --git a/python/python/tests/test_table.py b/python/python/tests/test_table.py index e5c4ad801..8fc06ea69 100644 --- a/python/python/tests/test_table.py +++ b/python/python/tests/test_table.py @@ -2759,15 +2759,40 @@ def test_create_with_embedding_function(mem_db: DBConnection): assert actual == expected +def test_create_f16_table_from_arrow_data(mem_db: DBConnection): + dimension = 32 + num_rows = 512 + values = pa.array( + np.random.default_rng(42) + .standard_normal(num_rows * dimension) + .astype(np.float16) + ) + df = pa.table( + { + "text": [f"s-{i}" for i in range(num_rows)], + "vector": pa.FixedSizeListArray.from_arrays(values, dimension), + } + ) + table = mem_db.create_table("f16_tbl", data=df) + assert table.schema.field("vector").type == pa.list_(pa.float16(), dimension) + table.create_index(num_partitions=2, num_sub_vectors=2) + + query = df["vector"][2].as_py() + expected = table.search(query).limit(2).to_arrow() + + assert "s-2" in expected["text"].to_pylist() + + def test_create_f16_table(mem_db: DBConnection): class MyTable(LanceModel): text: str vector: Vector(32, value_type=pa.float16()) + rng = np.random.default_rng(42) df = pa.table( { "text": [f"s-{i}" for i in range(512)], - "vector": [np.random.randn(32).astype(np.float16) for _ in range(512)], + "vector": [rng.standard_normal(32).astype(np.float16) for _ in range(512)], } ) table = mem_db.create_table( From 564e5d0d56802bfd2c025e9337b8dd810d42759a Mon Sep 17 00:00:00 2001 From: "lancedb-gatefixer[bot]" <313497061+lancedb-gatefixer[bot]@users.noreply.github.com> Date: Fri, 7 Aug 2026 17:32:17 +0800 Subject: [PATCH 10/15] fix(python): support Polars 1.32 table scans (#3801) ## Root cause `Table.to_polars()` disabled PyArrow predicate pushdown by selecting the non-PyArrow Polars scan callback. Polars 1.32.3 invokes that callback with `batch_size` both positionally and through its partial, so collecting the returned lazy frame raises `TypeError: _scan_pyarrow_dataset_impl() got multiple values for argument batch_size`. ## Fix - Keep the compatible PyArrow callback path. - Add an identity `map_batches` barrier so predicates stay in Polars instead of reaching the LanceDB adapter as unsupported PyArrow expressions. - Extend the tested Polars range through 1.32.3 and retain lazy-frame regression coverage. ## Validation - `python/tests/test_table.py::test_polars` with Polars 1.32.3 - `python/tests/test_table.py::test_polars` with the locked Polars 1.3.0 baseline - `ruff format --check` on the changed Python files - `ruff check .` - `uv lock --check` Fixes #2619 --------- Co-authored-by: Gatefixer <313497061+lancedb-gatefixer[bot]@users.noreply.github.com> --- python/pyproject.toml | 2 +- python/python/lancedb/table.py | 28 +++++++++++++++++++++++----- python/python/tests/test_table.py | 1 + python/uv.lock | 2 +- 4 files changed, 26 insertions(+), 7 deletions(-) diff --git a/python/pyproject.toml b/python/pyproject.toml index 348058957..ce71484de 100644 --- a/python/pyproject.toml +++ b/python/pyproject.toml @@ -60,7 +60,7 @@ tests = [ "pytest-asyncio>=0.21", "duckdb>=0.9.0", "pytz>=2023.3", - "polars>=0.19, <=1.3.0", + "polars>=0.19, <=1.32.3", "pyarrow<25", "pyarrow-stubs>=16.0", "pylance==9.0.0rc1", diff --git a/python/python/lancedb/table.py b/python/python/lancedb/table.py index ae36bac7a..59e2650eb 100644 --- a/python/python/lancedb/table.py +++ b/python/python/lancedb/table.py @@ -108,6 +108,11 @@ def _should_push_down_query_table( return namespace_client is not None and "QueryTable" in pushdown_operations +def _polars_predicate_pushdown_barrier(frame: Any) -> Any: + """Return a Polars frame unchanged while blocking predicate pushdown.""" + return frame + + _MODEL_BACKED_TOKENIZER_PREFIXES = ("jieba", "lindera") _MODEL_BACKED_TOKENIZER_ERRORS = ( "unknown base tokenizer", @@ -864,12 +869,18 @@ class Table(ABC): """ raise NotImplementedError - def to_polars(self, **kwargs) -> "pl.DataFrame": - """Return the table as a polars.DataFrame. + def to_polars(self, **kwargs) -> "pl.LazyFrame": + """Return the table as a Polars LazyFrame. + + Note + ---- + The Polars streaming engine is not supported because it does not currently + implement Python PyArrow dataset scans. Use the default engine when collecting + this LazyFrame. Returns ------- - polars.DataFrame + polars.LazyFrame """ raise NotImplementedError @@ -2569,6 +2580,9 @@ class LanceTable(Table): 2. Currently we've disabled push-down of the filters from polars because polars pushdown into pyarrow uses pyarrow compute expressions rather than SQl strings (which LanceDB supports) + 3. The Polars streaming engine is not supported because it does not + currently implement Python PyArrow dataset scans. Use the default + engine when collecting this LazyFrame. Returns ------- @@ -2577,8 +2591,12 @@ class LanceTable(Table): from lancedb.integrations.pyarrow import PyarrowDatasetAdapter dataset = PyarrowDatasetAdapter(self) - return pl.scan_pyarrow_dataset( - dataset, allow_pyarrow_filter=False, batch_size=batch_size + # Polars 1.32's non-PyArrow callback path passes batch_size twice. Keep + # the compatible PyArrow path, but block predicates because this adapter + # cannot translate PyArrow expressions into LanceDB filters. + return pl.scan_pyarrow_dataset(dataset, batch_size=batch_size).map_batches( + _polars_predicate_pushdown_barrier, + predicate_pushdown=False, ) # New unified API overload diff --git a/python/python/tests/test_table.py b/python/python/tests/test_table.py index 8fc06ea69..4ad5d7c3d 100644 --- a/python/python/tests/test_table.py +++ b/python/python/tests/test_table.py @@ -929,6 +929,7 @@ def test_polars(mem_db: DBConnection): # enter table to polars dataframe result = table.to_polars() + assert isinstance(result, pl.LazyFrame) assert np.allclose(result.collect()["vector"].to_list(), data["vector"]) # make sure filtering isn't broken diff --git a/python/uv.lock b/python/uv.lock index 551dc3f68..2cdcb182e 100644 --- a/python/uv.lock +++ b/python/uv.lock @@ -1998,7 +1998,7 @@ requires-dist = [ { name = "pillow", marker = "extra == 'clip'", specifier = ">=12.1.1" }, { name = "pillow", marker = "extra == 'embeddings'", specifier = ">=12.1.1" }, { name = "pillow", marker = "extra == 'siglip'", specifier = ">=12.1.1" }, - { name = "polars", marker = "extra == 'tests'", specifier = ">=0.19,<=1.3.0" }, + { name = "polars", marker = "extra == 'tests'", specifier = ">=0.19,<=1.32.3" }, { name = "pre-commit", marker = "extra == 'dev'", specifier = ">=3.5.0" }, { name = "pyarrow", specifier = ">=16" }, { name = "pyarrow", marker = "extra == 'tests'", specifier = "<25" }, From 607e5569276e68fc9b7bd6803b2748e6028007e1 Mon Sep 17 00:00:00 2001 From: "lancedb-gatefixer[bot]" <313497061+lancedb-gatefixer[bot]@users.noreply.github.com> Date: Fri, 7 Aug 2026 17:32:28 +0800 Subject: [PATCH 11/15] test(python): cover search after schema merge (#3784) ## Summary - add an end-to-end regression for indexed vector search after merging a pandas column - verify unmatched rows retain a null merged value instead of failing Arrow batch assembly ## Root cause Historical Lance readers could assemble schema-evolved columns in physical data-file order. Indexed row-ID reads after a merge could therefore omit or misorder the newly merged column for unmatched rows. The currently pinned Lance release contains the reader correction, but LanceDB did not cover the reported merge-then-search path. ## Validation - uv run --extra tests pytest python/tests/test_table.py::test_merge python/tests/test_table.py::test_search_after_merge -q - uv run --project python --extra dev ruff check . - uv run --project python --extra dev ruff format --check python/python/tests/test_table.py Fixes #599 --------- Co-authored-by: Gatefixer <313497061+lancedb-gatefixer[bot]@users.noreply.github.com> --- python/python/tests/test_table.py | 39 +++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/python/python/tests/test_table.py b/python/python/tests/test_table.py index 4ad5d7c3d..eb6eaefaa 100644 --- a/python/python/tests/test_table.py +++ b/python/python/tests/test_table.py @@ -2218,6 +2218,45 @@ def test_merge(tmp_db: DBConnection, tmp_path): table.merge(other_dataset, left_on="id") +@pytest.mark.parametrize("storage_version", ["legacy", "stable"]) +def test_search_after_merge(tmp_path, storage_version): + pytest.importorskip("lance") + pd = pytest.importorskip("pandas") + + db = lancedb.connect( + tmp_path, + storage_options={"new_table_data_storage_version": storage_version}, + ) + rng = np.random.default_rng(42) + row_count = 512 + vectors = rng.standard_normal((row_count, 8)).astype(np.float32) + table = db.create_table( + "search_after_merge", + data=pd.DataFrame( + { + "id": [str(i) for i in range(row_count)], + "vector": list(vectors), + } + ), + ) + table.create_index("vector", config=IvfPq(num_partitions=1, num_sub_vectors=2)) + + links = pd.DataFrame( + { + "id": [str(i) for i in range(row_count // 2)], + "link": [f"https://example.com/{i}" for i in range(row_count // 2)], + } + ) + table.merge(links, left_on="id") + + query = table.search(vectors[-1]).refine_factor(50).limit(10) + assert "ANN" in query.explain_plan(verbose=True) + + result = query.to_arrow() + links_by_id = dict(zip(result["id"].to_pylist(), result["link"].to_pylist())) + assert links_by_id[str(row_count - 1)] is None + + def test_delete(mem_db: DBConnection): table = mem_db.create_table( "my_table", From 2ba7407dc36f4989dc720d96bd765601b94566ba Mon Sep 17 00:00:00 2001 From: "lancedb-gatefixer[bot]" <313497061+lancedb-gatefixer[bot]@users.noreply.github.com> Date: Fri, 7 Aug 2026 17:32:39 +0800 Subject: [PATCH 12/15] fix(node): cover non-nullable embedding schema append (#3835) ## Summary - Add an issue-specific regression for appending generated embeddings to an empty table with a non-nullable vector field. - Verify the custom embedding function produces the declared Float64 vectors and both appended rows are readable. ## Root cause In v0.4.19, records without a vector value were materialized against the explicit schema before embeddings were inserted. Apache Arrow inferred the generated batch vector field as nullable while the table retained the user-provided non-nullable field, then rejected the mismatched schemas. The current conversion path excludes the generated field from the initial record conversion and realigns the completed batch to the stored schema after embedding, but the reported empty-table append sequence lacked permanent regression coverage. ## Validation - `pnpm exec biome format --write __test__/embedding.test.ts` - `pnpm lint-ci` - `pnpm test -- --runInBand __test__/embedding.test.ts` (12 passed, 1 skipped integration test) - `pnpm build` - `pnpm run docs` Fixes #1281 --------- Co-authored-by: Gatefixer <313497061+lancedb-gatefixer[bot]@users.noreply.github.com> --- nodejs/__test__/embedding.test.ts | 60 +++++++++++++++++++++++++++++++ 1 file changed, 60 insertions(+) diff --git a/nodejs/__test__/embedding.test.ts b/nodejs/__test__/embedding.test.ts index e56e80631..06184751e 100644 --- a/nodejs/__test__/embedding.test.ts +++ b/nodejs/__test__/embedding.test.ts @@ -11,8 +11,11 @@ import { Float16, Float32, Float64, + Int32, Schema, Utf8, + fromDataToBuffer, + tableFromIPC, } from "../lancedb/arrow"; import { EmbeddingFunction, LanceSchema } from "../lancedb/embedding"; import { getRegistry, register } from "../lancedb/embedding/registry"; @@ -184,6 +187,63 @@ describe("embedding functions", () => { const vector0 = JSON.parse(JSON.stringify(arr[0].vector)); expect(vector0).toEqual([1, 2, 3]); }); + + it("should append generated vectors to a non-nullable schema", async () => { + @register("non_nullable_schema_test") + class MockEmbeddingFunction extends EmbeddingFunction { + ndims() { + return 3; + } + embeddingDataType(): Float { + return new Float64(); + } + async computeSourceEmbeddings(data: string[]) { + return data.map(() => [1, 2, 3]); + } + } + + const schema = new Schema([ + new Field("id", new Int32()), + new Field("text", new Utf8()), + new Field("type", new Utf8()), + new Field( + "vector", + new FixedSizeList(3, new Field("item", new Float64())), + ), + ]); + const func = new MockEmbeddingFunction(); + const db = await connect(tmpDir.name); + const table = await db.createEmptyTable("test_non_nullable", schema, { + embeddingFunction: { + function: func, + sourceColumn: "text", + }, + }); + + const data = [ + { id: 1, text: "Carrot", type: "vegetable" }, + { id: 2, text: "Apple", type: "fruit" }, + ]; + const buffer = await fromDataToBuffer( + data, + undefined, + await table.schema(), + ); + const generatedTable = tableFromIPC(buffer); + const vectorField = generatedTable.schema.fields.find( + (field) => field.name === "vector", + ); + expect(vectorField?.nullable).toBe(false); + + await table.add(data); + + const rows = await table.query().toArray(); + expect(rows).toHaveLength(2); + for (const row of rows) { + expect([...row.vector]).toEqual([1, 2, 3]); + } + }); + it("should error when appending to a table with an unregistered embedding function", async () => { @register("mock") class MockEmbeddingFunction extends EmbeddingFunction { From 11f24b1df408306d9a7801f83fe7fbed20b99e46 Mon Sep 17 00:00:00 2001 From: "lancedb-gatefixer[bot]" <313497061+lancedb-gatefixer[bot]@users.noreply.github.com> Date: Fri, 7 Aug 2026 17:33:02 +0800 Subject: [PATCH 13/15] fix: explain unsupported object storage mounts (#3823) ## Summary - classify unsupported local-filesystem operations from Lance as a NotSupported error - explain that object-storage mounts cannot provide the safe commit operations Lance requires and direct users to native object-store URIs - preserve existing error behavior for other local I/O failures and non-local backends ## Root cause Mountpoint for Amazon S3 exposes an S3 bucket as a local path but does not implement atomic rename. Lance uses atomic rename for safe local commits, and the resulting unsupported I/O error was previously passed through as a generic Lance error, leaving Python users with an opaque low-level failure. Transparent support for such mounts is not safe; direct s3:// access remains the supported path. ## Validation - cargo test --quiet --features remote -p lancedb error::tests - cargo test --quiet --features remote -p lancedb --lib (807 passed, 1 ignored) - cargo check --quiet --features remote --tests --examples - cargo clippy --quiet --features remote --tests --examples - cargo fmt --all -- --check Fixes #2016 --------- Co-authored-by: Gatefixer <313497061+lancedb-gatefixer[bot]@users.noreply.github.com> --- rust/lancedb/src/error.rs | 70 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 70 insertions(+) diff --git a/rust/lancedb/src/error.rs b/rust/lancedb/src/error.rs index f6f596f3d..4a6e6d8d9 100644 --- a/rust/lancedb/src/error.rs +++ b/rust/lancedb/src/error.rs @@ -169,6 +169,12 @@ impl From for Error { impl From for Error { fn from(source: lance::Error) -> Self { + if has_unsupported_local_filesystem_source(&source) { + return Self::NotSupported { + message: "the filesystem does not support an operation required for safe Lance commits (such as atomic rename). Object-storage mounts such as Mountpoint for Amazon S3 are not supported; use the native object-store URI (for example, s3://bucket/path) instead".to_string(), + }; + } + // Try to unwrap external errors that were wrapped by lance match source { lance::Error::Wrapped { error, .. } => Self::from_box_error(error), @@ -181,6 +187,27 @@ impl From for Error { } } +fn has_unsupported_local_filesystem_source(error: &(dyn std::error::Error + 'static)) -> bool { + let mut current = Some(error); + let mut is_local_filesystem = false; + let mut is_unsupported = false; + while let Some(error) = current { + is_local_filesystem |= error + .downcast_ref::() + .is_some_and(|error| { + matches!(error, object_store::Error::Generic { store, .. } if *store == "LocalFileSystem") + }); + is_unsupported |= error + .downcast_ref::() + .is_some_and(|error| error.kind() == std::io::ErrorKind::Unsupported); + if is_local_filesystem && is_unsupported { + return true; + } + current = error.source(); + } + false +} + impl Error { fn from_box_error(mut source: Box) -> Self { source = match source.downcast::() { @@ -270,3 +297,46 @@ impl From for Error { } } } + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn unsupported_filesystem_operations_have_actionable_error() { + let object_store_error = object_store::Error::Generic { + store: "LocalFileSystem", + source: Box::new(std::io::Error::from(std::io::ErrorKind::Unsupported)), + }; + let lance_error = lance::Error::io_source(Box::new(object_store_error)); + + let error = Error::from(lance_error); + + assert!(matches!( + error, + Error::NotSupported { message } + if message.contains("Mountpoint for Amazon S3") + && message.contains("s3://bucket/path") + )); + } + + #[test] + fn other_io_errors_remain_lance_errors() { + let object_store_error = object_store::Error::Generic { + store: "LocalFileSystem", + source: Box::new(std::io::Error::from(std::io::ErrorKind::PermissionDenied)), + }; + let lance_error = lance::Error::io_source(Box::new(object_store_error)); + + assert!(matches!(Error::from(lance_error), Error::Lance { .. })); + } + + #[test] + fn unsupported_non_filesystem_errors_remain_lance_errors() { + let lance_error = lance::Error::io_source(Box::new(std::io::Error::from( + std::io::ErrorKind::Unsupported, + ))); + + assert!(matches!(Error::from(lance_error), Error::Lance { .. })); + } +} From 6ba80a960cd1a54f6d8b625124743301beaf4173 Mon Sep 17 00:00:00 2001 From: "lancedb-gatefixer[bot]" <313497061+lancedb-gatefixer[bot]@users.noreply.github.com> Date: Fri, 7 Aug 2026 17:33:13 +0800 Subject: [PATCH 14/15] fix(node): cover offset pagination in search (#3814) ## Summary - add Node regression coverage for vector-search offset pagination - add equivalent coverage for full-text search - compare later pages with the corresponding complete-result slice and assert page sizes ## Root cause The historical query path requested only the user limit from nearest-neighbor or full-text search before applying the offset, so a page became empty when its offset reached that limit. The production query path on current main already incorporates the later fix from #2592; this change adds the missing Node binding coverage for the still-open report and protects both affected APIs from regression. ## Validation - corepack pnpm build - corepack pnpm test -- query.test.ts --runInBand --testNamePattern="Search pagination" - corepack pnpm lint-ci - corepack pnpm tsc - corepack pnpm run docs Fixes #2229 Co-authored-by: Gatefixer <313497061+lancedb-gatefixer[bot]@users.noreply.github.com> --- nodejs/__test__/query.test.ts | 75 +++++++++++++++++++++++++++++++++++ 1 file changed, 75 insertions(+) diff --git a/nodejs/__test__/query.test.ts b/nodejs/__test__/query.test.ts index da001b1eb..5f3e68b16 100644 --- a/nodejs/__test__/query.test.ts +++ b/nodejs/__test__/query.test.ts @@ -110,6 +110,81 @@ describe("Query outputSchema", () => { }); }); +describe("Search pagination", () => { + let tmpDir: tmp.DirResult; + let table: Table; + + beforeEach(async () => { + tmpDir = tmp.dirSync({ unsafeCleanup: true }); + const db = await connect(tmpDir.name); + const schema = new Schema([ + new Field("id", new Int64(), false), + new Field("text", new Utf8(), false), + new Field( + "vector", + new FixedSizeList(2, new Field("item", new Float32())), + false, + ), + ]); + const data = makeArrowTable( + [ + { id: 1n, text: "common", vector: [0, 0] }, + { id: 2n, text: "common common", vector: [1, 1] }, + { id: 3n, text: "common common common", vector: [2, 2] }, + { id: 4n, text: "common common common common", vector: [3, 3] }, + ], + { schema }, + ); + table = await db.createTable("test", data); + }); + + afterEach(() => { + tmpDir.removeCallback(); + }); + + it("applies offset after the vector search limit", async () => { + const allResults = await table + .vectorSearch([0, 0]) + .select(["id"]) + .limit(4) + .toArray(); + const secondPage = await table + .vectorSearch([0, 0]) + .select(["id"]) + .limit(2) + .offset(2) + .toArray(); + + expect(allResults).toHaveLength(4); + expect(secondPage).toHaveLength(2); + expect(secondPage.map((row) => row.id)).toEqual( + allResults.slice(2, 4).map((row) => row.id), + ); + }); + + it("applies offset after the full-text search limit", async () => { + await table.createIndex("text", { config: Index.fts() }); + + const allResults = await table + .search("common", "fts") + .select(["id"]) + .limit(4) + .toArray(); + const secondPage = await table + .search("common", "fts") + .select(["id"]) + .limit(2) + .offset(2) + .toArray(); + + expect(allResults).toHaveLength(4); + expect(secondPage).toHaveLength(2); + expect(secondPage.map((row) => row.id)).toEqual( + allResults.slice(2, 4).map((row) => row.id), + ); + }); +}); + describe("Query orderBy", () => { let tmpDir: tmp.DirResult; let table: Table; From ec21e370401a2d74b43ca3efa187238905bf5fa8 Mon Sep 17 00:00:00 2001 From: "lancedb-gatefixer[bot]" <313497061+lancedb-gatefixer[bot]@users.noreply.github.com> Date: Fri, 7 Aug 2026 17:37:37 +0800 Subject: [PATCH 15/15] test(rust): cover Hugging Face table symlinks (#3887) ## Summary - cover Hugging Face cache layouts where both manifests and Lance data files are relative symlinks into a blob directory - reconnect with a fresh session before opening so the test exercises filesystem discovery instead of cached manifest metadata - scan the reopened table to verify both manifest recovery and data-file reads ## Root cause Lance 3.0.1 recorded Unix symlink metadata as the known manifest size, so the short link length caused a file size is too small error. The current Lance v11.0.0-beta.2 dependency repairs this by detecting an invalid footer from a stale known size and retrying with the target file metadata. This regression test locks that behavior into the LanceDB open-table path used by Node. ## Validation - cargo fmt --all - cargo test --quiet --features remote -p lancedb --lib test_open_table_follows_hugging_face_symlinks -- --nocapture - cargo test --quiet --features remote -p lancedb --lib database::listing::tests - cargo clippy --quiet --features remote -p lancedb --lib --tests -- -D warnings - cargo check --quiet --features remote --tests --examples Fixes #3197 Co-authored-by: Gatefixer <313497061+lancedb-gatefixer[bot]@users.noreply.github.com> --- rust/lancedb/src/database/listing.rs | 92 +++++++++++++++++++++++++++- 1 file changed, 91 insertions(+), 1 deletion(-) diff --git a/rust/lancedb/src/database/listing.rs b/rust/lancedb/src/database/listing.rs index a4624112e..0ab3614e7 100644 --- a/rust/lancedb/src/database/listing.rs +++ b/rust/lancedb/src/database/listing.rs @@ -1294,9 +1294,11 @@ mod tests { use crate::connection::ConnectRequest; use crate::data::scannable::Scannable; use crate::database::{CreateTableMode, CreateTableRequest}; - use crate::table::WriteOptions; + use crate::query::QueryRequest; + use crate::table::{AnyQuery, WriteOptions}; use arrow_array::{Int32Array, RecordBatch, StringArray}; use arrow_schema::{DataType, Field, Schema}; + use futures::TryStreamExt; use std::path::PathBuf; use tempfile::tempdir; @@ -1438,6 +1440,94 @@ mod tests { assert!(after_open.hits >= before_open.hits + 3); } + /// Regression test for https://github.com/lancedb/lancedb/issues/3197. + #[cfg(unix)] + #[tokio::test] + async fn test_open_table_follows_hugging_face_symlinks() { + let (tempdir, db) = setup_database().await; + let schema = Arc::new(Schema::new(vec![Field::new("id", DataType::Int32, false)])); + db.create_table(CreateTableRequest { + name: "test".to_string(), + namespace_path: vec![], + data: Box::new( + RecordBatch::try_new(schema, vec![Arc::new(Int32Array::from(vec![1, 2, 3]))]) + .unwrap(), + ) as Box, + mode: CreateTableMode::Create, + write_options: Default::default(), + location: None, + namespace_client: None, + }) + .await + .unwrap(); + + let table_dir = tempdir.path().join("test.lance"); + let versions_dir = table_dir.join("_versions"); + let manifest_path = std::fs::read_dir(&versions_dir) + .unwrap() + .map(|entry| entry.unwrap().path()) + .find(|path| path.extension().is_some_and(|ext| ext == "manifest")) + .unwrap(); + let data_path = std::fs::read_dir(table_dir.join("data")) + .unwrap() + .map(|entry| entry.unwrap().path()) + .find(|path| path.extension().is_some_and(|ext| ext == "lance")) + .unwrap(); + + // Hugging Face snapshots keep dataset objects in a separate blob directory and + // expose them through relative symlinks. + let blobs_dir = tempdir.path().join("blobs"); + std::fs::create_dir(&blobs_dir).unwrap(); + let manifest_blob = "9b603c63d0e692e05d58be25605f2f2064cc781e5ff94fe983a405059547b816"; + let data_blob = "be64f20e5723bd0a27cfdbdb41cf7d6fad94cd572a71973b717fb8340f4310c5"; + std::fs::rename(&manifest_path, blobs_dir.join(manifest_blob)).unwrap(); + std::fs::rename(&data_path, blobs_dir.join(data_blob)).unwrap(); + std::os::unix::fs::symlink(Path::new("../../blobs").join(manifest_blob), &manifest_path) + .unwrap(); + std::os::unix::fs::symlink(Path::new("../../blobs").join(data_blob), &data_path).unwrap(); + let symlink_len = std::fs::symlink_metadata(&manifest_path).unwrap().len(); + let target_len = std::fs::metadata(&manifest_path).unwrap().len(); + assert_ne!(symlink_len, target_len); + + drop(db); + let db = ListingDatabase::connect_with_options(&ConnectRequest { + uri: tempdir.path().to_str().unwrap().to_string(), + #[cfg(feature = "remote")] + client_config: Default::default(), + options: Default::default(), + namespace_client_properties: Default::default(), + manifest_enabled: false, + read_consistency_interval: None, + session: None, + }) + .await + .unwrap(); + + let table = db + .open_table(OpenTableRequest { + name: "test".to_string(), + namespace_path: vec![], + index_cache_size: None, + lance_read_params: None, + location: None, + namespace_client: None, + managed_versioning: None, + }) + .await + .unwrap(); + let batches = table + .query( + &AnyQuery::Query(QueryRequest::default()), + Default::default(), + ) + .await + .unwrap() + .try_collect::>() + .await + .unwrap(); + assert_eq!(batches.iter().map(RecordBatch::num_rows).sum::(), 3); + } + #[tokio::test] async fn test_clone_table_basic() { let (_tempdir, db) = setup_database().await;