Commit Graph

2814 Commits

Author SHA1 Message Date
Wyatt Alt 60f5f6fef2 test: differential refresh harness for materialized views
Example tests pin behaviors; the refresh contract is a property: after any
sequence of source mutations, a view maintained by default refreshes equals
the definition evaluated against the source directly, and so does a forced
rebuild. This drives every mutation sequence up to length three -- appends,
deletes, updates crossing the filter, compactions, unrelated column adds --
over an identity and a filtered view shape, checking against an oracle that
shares nothing with the refresh path: a plain column scan with the filter
applied in Rust. The oracle runs after every step because a later
rebuild-forcing mutation silently heals an incremental error; end-state
checks miss exactly the transient bugs that matter. A length-four sweep
(1,560 cases) runs behind ignore.

Named regressions additionally assert the refresh mode, which value
comparison cannot: a wrongly rebuilding classifier still matches the
oracle, so the append, unrelated-column and compaction cases pin that the
incremental path actually ran.
2026-08-17 10:42:35 -07:00
Wyatt Alt 4cd8e29114 feat: refresh materialized views
A declared view holds no rows; refresh computes them. It pins one source
version and brings the view to exactly the definition's result at that
version, recording the version as a watermark in the view's schema
metadata. The refresh is incremental when the source changed by nothing but
appends and compactions since the watermark: a transaction-log walk
separates the two, so only genuinely appended fragments are computed --
compaction outputs are already-materialized rows rearranged and cost
nothing, which is what keeps routine background compaction from rebuilding
the view. One subtlety shapes the walk: transaction files record an
Append's fragments with placeholder ids (real ids are assigned at commit),
while a Rewrite's ids are reserved beforehand and real, so appends are
derived as new-at-head minus rewrite outputs rather than read from the log.
A fragment-signature check scoped to the columns the view reads is the
fallback for deltas the walk cannot classify, and passes changes to
unrelated columns.

Anything else rebuilds: deletes, updates, a vacuumed watermark version, or
an append a later compaction swallowed (its rows cannot be told apart from
already-materialized ones without a provenance scan). A rebuild of an
indexed view stages the new fragments uncommitted and commits one Update
swapping out every old fragment, so index definitions are never absent and
readers never observe an empty view; unindexed rebuilds overwrite, with the
watermark riding the same commit. Refresh also accepts a pinned source
version, and a row limit counts already-held rows.

Two bounds, stated in the module docs: on the append path the watermark
moves in a follow-up commit, so a crash between the two re-appends those
rows on the next refresh; and concurrent refreshes of one view can
double-append, since lance appends do not conflict -- run one at a time.
2026-08-17 10:42:35 -07:00
Wyatt Alt 8b4b294c89 feat: materialized view declarations on local tables
A materialized view is a table whose contents are defined by a query over one source table and maintained by refresh rather than by writes. This adds the declaration half: create_materialized_view(name, source) resolves a projected, filtered and limited definition against the source schema -- output types come from the DataFusion planner, never the caller -- and commits an empty table carrying it as kind-tagged JSON in schema metadata. The tag lets a kind added later read back as a view this version cannot refresh rather than as a plain table. Views open and list as ordinary tables.

Sources must have stable row ids, checked here because the property cannot be enabled later: each view row records its source row in __source_row_id, and that provenance survives compactions, updates and deletes only when row ids are stable.

A view inherits the metadata describing its columns and none governing how a table is written, so blob markers carry through at every depth while declarations its always-nullable fields would contradict are stripped. Embedding configuration and column definitions are rewritten to the names the view gives those columns, and dropped where it does not project both ends of a function.

Refresh lands in the next layer of this stack; a declared view is inert but introspectable.
2026-08-17 10:42:35 -07:00
Wyatt Alt 928c3dde2d feat: computed columns on remote tables (#3941)
LanceDB Cloud and Enterprise support computed columns through the REST
API,
so declaration dispatches per backend: local tables plan the expression
themselves, remote ones send {name, computed} entries for the server to
plan. A remote refresh is the server's backfill job --
refresh_column_async
submits it and returns a handle whose successful wait establishes a
read-freshness baseline on the submitting handle, unless a checkout has
pinned the handle by the time the job completes; the blocking form
refuses
rather than invent a fill count the server does not report.

Declaration entries are built from the namespace client's
AddColumnsEntry
model (lance-namespace 0.11.0, via the lance beta.13 pin), so the
payload
shape is compile-checked against the published contract.

---

<sub>Stack created with <a
href="https://github.com/github/gh-stack">GitHub Stacks CLI</a> • <a
href="https://gh.io/stacks-feedback">Give Feedback 💬</a></sub>
2026-08-14 17:21:55 -07:00
LanceDB Robot 980818df26 chore: update lance dependency to v11.0.0-beta.13 (#3947)
Updates the Lance Rust workspace dependencies and Java lance-core
dependency to
[v11.0.0-beta.13](https://github.com/lance-format/lance/releases/tag/v11.0.0-beta.13).
Adds the required `ListTablesResponse.context` compatibility field and
validates the workspace with Clippy warnings denied.
2026-08-14 16:20:39 -07:00
Wyatt Alt c429863122 feat: refresh_column_async returns a job handle (#3939)
Mirrors create_index's dual surface: the blocking refresh_column keeps
returning {rows_filled, version}, and refresh_column_async returns the
same
Job handle create_index uses, running the refresh as an in-process task.
Invalid input is reported by the submitting call rather than by the job.

---

<sub>Stack created with <a
href="https://github.com/github/gh-stack">GitHub Stacks CLI</a> • <a
href="https://gh.io/stacks-feedback">Give Feedback 💬</a></sub>
2026-08-14 16:05:04 -07:00
Wyatt Alt fc0d917d32 feat: refresh computed columns (#3938)
table.refresh_column("doubled") fills the rows of a declared column that
hold no value, in two passes per fragment: the first scans only the
unfilled
live rows to count exact gains and decide staging, the second streams
the
fragment's physical rows into a standalone column file published in one
DataReplacement -- committed under the dataset's own session -- so peak
memory is bounded by a scan batch. A row that holds a value keeps it;
deleted and already-filled rows never reach the expression, so a poison
value in them cannot fail the refresh. Refresh refuses under an LSM
write
spec, including the mem-wal catch-up flag that outlives unset and marks
retained SSTable rows.

---

<sub>Stack created with <a
href="https://github.com/github/gh-stack">GitHub Stacks CLI</a> • <a
href="https://gh.io/stacks-feedback">Give Feedback 💬</a></sub>
2026-08-14 14:43:41 -07:00
Wyatt Alt def869bb78 feat: declare computed columns by SQL expression (#3937)
add_columns().computed("doubled", "x * 2") stores the expression in
field
metadata and commits the column empty; a later refresh fills it. Type
and
inputs are derived from the expression.

The declaration stays authoritative for its lifetime: writes that would
give
the column a value (append, update, merge, SQL insert), schema changes
that
would break the stored expression or reshape its output, metadata edits,
volatile expressions, declaration metadata arriving through any path but
the
validated declare call, and LSM write specs in either order against
latest
committed state are all refused. The LSM check also refuses on the
mem-wal
catch-up feature flag, which outlives unset and marks retained SSTable
rows.
Simultaneous declare/install interleavings conflict at commit via
lance's
mem-wal rule (lance#8539). Local tables only.

---

<sub>Stack created with <a
href="https://github.com/github/gh-stack">GitHub Stacks CLI</a> • <a
href="https://gh.io/stacks-feedback">Give Feedback 💬</a></sub>
2026-08-14 14:17:41 -07:00
LanceDB Robot 9e4d8bd1c7 chore: update lance dependency to v11.0.0-beta.11 (#3946)
Updates the Rust workspace Lance crates and Java lance-core dependency
to v11.0.0-beta.11. No compatibility fixes were required; formatting and
full-workspace clippy validation pass. Lance tag:
https://github.com/lance-format/lance/releases/tag/v11.0.0-beta.11
2026-08-14 08:31:58 -07:00
XY Zhan 4148dfef72 feat(lsm): require recorded index catch-up, as an explicit activation (#3911)
> Stacked on #3780. Blocked only on #3922 (`lance` → `v11.0.0-beta.6`),
so CI
> stays red until that lands.

## Missing coverage must mean "not known to be covered"

#3780 caps the SSTable exclusion watermark at an index's recorded
catch-up when
there is one, and silently ignores the case where there is none. On a
table that
requires catch-up, an absent entry means the index is *not* known to
hold the
compacted rows — and the LSM base arm reads base through the index
(`fast_search`, no brute-force tail), so dropping that SSTable loses
those rows
for that query.

```rust
Some(caught_up) => watermark = watermark.min(caught_up),
None if catchup_required => watermark = 0,   // retain everything
None => {}
```

`catchup_required` reads the manifest feature bit directly, and requires
both
words: a half-set manifest is treated as legacy, which is the
conservative side.
Without the bit the field is not maintained at all, so absence carries
no
information and behaviour is unchanged.

## Activation, as a table-level entry point

`Table::require_mem_wal_index_catchup()` performs the one-way switch,
separate
from `set_lsm_write_spec`: a table carrying the bit retains every
generation
until something records catch-up, so it has to follow the deployment of
whatever
repairs coverage, not the creation of the table.

This is a convenience, not the only path — a writer holding the dataset
calls
the equivalent on `DatasetMemWalExt`, which is what the WAL pod does.
Lance
enforces the preconditions either way: the MemWAL index must exist, and
the
table must not already carry `compacted_sstables` from before this
protocol,
since those numbers cannot be validated.

## Still correct after the Lance rework

lance-format/lance#8481 replaced the transmitted `IndexCatchupAdvance`
with a
position derived at commit time from the version a transaction read.
That
changed how a writer earns coverage; it did not change what a reader may
conclude from its absence. The rule here, and the field it reads, are
unchanged.

## Tests

Existing `exclusion_watermarks` unit tests carry the new argument.
Coverage
against a real dataset follows once #3922 lands and this can build.
2026-08-14 09:32:02 -04:00
LanceDB Robot 0ac70a8b9f chore: update lance dependency to v11.0.0-beta.10 (#3944)
Updates the Rust workspace Lance dependencies and Java lance-core
dependency to v11.0.0-beta.10. No compatibility fixes were required;
workspace clippy with all features and Rust formatting pass.

Lance tag:
https://github.com/lance-format/lance/releases/tag/v11.0.0-beta.10
2026-08-14 18:46:46 +08:00
Lance Release 91c5f344d2 Bump version: 0.37.1-beta.1 → 0.38.0-beta.0 2026-08-14 01:09:50 +00:00
Jack Ye ffd35c1a8f feat: add asynchronous drop table API (#3936)
## Summary

- add `drop_table_async` and return a job handle while preserving
`drop_table`
- consume remote 202 responses with cleanup job IDs and retain
older-server compatibility
- expose the API through Python and TypeScript connection wrappers
2026-08-13 18:05:44 -07:00
Wyatt Alt 790d0c684c docs(ci): clarify tag input on codex-update-lance-dependency (#3924)
Say what resolving "latest" actually does: pick the newest release,
preferring stable over pre-release, and skip the run if it is not newer
than the version pinned in Cargo.toml.

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-13 11:26:58 -07:00
XY Zhan 251f194696 refactor(lsm): gate SSTable exclusion on every index a query relies on (#3780)
`exclusion_watermarks` resolved a single index and capped SSTable
exclusion at that index's catch-up watermark. It now takes every index
the query relies on and retains to the **lowest** of them, and the
resolver collects arms together rather than returning at the first
match.

This is groundwork, not a fix for a reachable bug: `reject_unsupported`
refuses hybrid search, so the vector and full-text arms are mutually
exclusive and the list never holds more than one entry today. The
generalisation is what the remaining work below plugs into.

Unchanged: a plain scan uses the compaction watermark alone, an index
with no catch-up entry contributes no cap, and a caught-up index falls
back to the compaction watermark. Taking a minimum over more indexes can
only lower a watermark, so the failure direction is "read an SSTable
unnecessarily", never "miss rows".

## Tests

Three in `lsm`: the existing lagging-index test updated for the new
signature;
`exclusion_watermark_takes_the_minimum_across_every_index_used` (two
indexes at 7 and 4 against compaction at 9 — each alone stops at its own
watermark, together the lower governs, order-independent); and
`an_untracked_index_does_not_widen_a_lagging_sibling`.

`cargo test -p lancedb --lib` — 45 lsm tests, 484 in the crate. `cargo
fmt --check` clean.

## Follow-ups

This crate pins lance to a released tag, so anything needing unreleased
Lance symbols waits for a bump.

1. **Select legacy versus strict semantics from the feature bit.** On a
table with `FLAG_MEM_WAL_INDEX_CATCHUP` set, a *missing* entry must mean
"not caught up" and retain the SSTables, instead of leaving the
compaction watermark unchanged. Needs the bit from
lance-format/lance#8263. **This must land before any table is
activated** — otherwise the bit is set while queries still read
permissively.
2. **Collect scalar and bitmap-family prefilter indexes.** The genuinely
multi-index query is a vector search with a scalar prefilter, and it is
gated on the vector index alone today. Identifying the others needs the
planner's chosen indexes, not the columns the filter names, so it needs
a Lance-side helper.
3. **Verify a retained SSTable can actually answer.** Both base and
SSTable arms use `fast_search`; a source without a compatible index
contributes nothing, so retention alone does not guarantee its rows are
returned. Needs a flat-search fallback or an explicit error in Lance's
`LsmScanner`.
4. **Planner-level integration tests.** Current tests exercise the
watermark arithmetic directly. End-to-end coverage over real queries —
prefilter forms, legacy versus activated, missing index and missing
shard entries — depends on 1–3.
2026-08-13 13:23:37 -04:00
LanceDB Robot 4b7325bd74 chore: update lance dependency to v11.0.0-beta.8 (#3928)
Updates the Rust workspace and Java lance-core dependency to Lance
v11.0.0-beta.8, with refreshed Cargo lockfile metadata. No compatibility
fixes were required. Lance tag:
https://github.com/lance-format/lance/releases/tag/v11.0.0-beta.8
2026-08-14 00:00:18 +08:00
Yang Cen 1d75638dea fix: make table existence manifest-authoritative (#3919)
## What is the bug?

#3731 tries to distinguish a missing table from a corrupt table after
Lance returns `DatasetNotFound`. It does that by listing the database
parent and treating a physical `<name>.lance` entry as evidence that the
table exists.

That premise is not sound for a listing database. Table creation writes
data before atomically committing the first manifest, so the same
physical prefix can represent a live concurrent create, abandoned
uncommitted data, or an old empty directory. It is not evidence of a
committed table. The parent listing also makes every missing-table open,
including the create-on-miss path, perform work proportional to the
number of sibling tables. Cloud `list_with_delimiter` exhausts all pages
before returning.

## How does this PR fix the problem?

This PR makes the committed Lance manifest the sole table-existence
authority for listing-database opens:

- `DatasetNotFound` maps directly to `TableNotFound`; no parent or
target storage probe runs.
- Other Lance load errors continue to propagate unchanged.
- A physical directory, object prefix, or uncommitted data file alone
does not block `Create`.
- Concurrent `Create` requests are arbitrated by the conditional
version-1 manifest commit: one succeeds and the loser receives
`TableAlreadyExists`.
- `table_names` is documented as physical discovery, not an atomic
table-existence check. Its snapshot can contain an entry that is still
being created, has only uncommitted storage, or is concurrently dropped.

This removes the need for a new Lance object-store capability. LanceDB
remains on the official Lance `v11.0.0-beta.6` dependency from `main`;
the merge commit for lance-format/lance#7722 is an ancestor of that tag,
so the ambiguous-GCS-500 corruption-prevention fix is retained.

## Performance evidence

Lower is better. The benchmark uses real `.lance` directories with
marker objects on the local filesystem; fixture creation and teardown
are outside the timed region. Baseline is `origin/main` at `6fb976cf`,
candidate is `e1240751`. Both were built from the same lockfile on the
same macOS arm64 machine with the repository's `release` profile (fat
LTO), then executed in alternating baseline/candidate order for three
pairs. Each run used 10 warmups and 100 distinct missing-table opens per
scale. The table reports the median of the three run-level percentiles.

| Scenario / metric | Baseline | This PR | Benefit |
| --- | ---: | ---: | ---: |
| 1,000 real sibling directories, p50 | 11.905 ms | 21.042 us | 566x
speedup |
| 10,000 real sibling directories, p50 | 143.630 ms | 18.375 us | 7,817x
speedup |
| 100,000 real sibling directories, p50 | 1.991 s | 19.917 us | 99,984x
speedup |
| 100,000 real sibling directories, p95 | 2.346 s | 25.792 us | 90,965x
speedup |

These results validate removal of the sibling-cardinality dependency in
this local-filesystem workload; they are not an extrapolation to
production GCS latency. A structural object-store regression test
separately asserts that opening one missing table performs zero
parent-scoped `list`, `list_with_offset`, or `list_with_delimiter`
calls.

Run with:

```bash
BENCH_SIBLINGS=1000,10000,100000 BENCH_WARMUPS=10 BENCH_TRIALS=100 \
  cargo run --locked --release --quiet -p lancedb --example bench_open_missing_table
```

## Correctness and compatibility boundaries

- An empty `.lance` directory or orphan data without a committed
manifest now opens as `TableNotFound` and may be replaced by a
successful `Create`.
- Two synchronized creators sharing one object store deterministically
produce one success and one conditional-manifest conflict mapped to
`TableAlreadyExists`.
- A readable manifest remains authoritative; non-`DatasetNotFound`
corruption, external-manifest, authorization, and object-store errors
are not folded into `TableNotFound`.
- `TableCorrupted` remains in the public error enum for compatibility,
but this listing-database fallback no longer synthesizes it from an
ambiguous physical footprint.
- Reliably distinguishing `Missing`, `Creating`, and `Corrupt` would
require explicit authoritative lifecycle/catalog metadata (for example a
leased creation record). It cannot be inferred from a directory or
prefix, and is outside this incident fix.

## Validation

- `cargo fmt --all -- --check`
- `cargo check --quiet --locked -p lancedb --features remote --tests
--examples`
- `cargo clippy --quiet --locked -p lancedb --features remote --tests
--examples -- -D warnings`
- `cargo test --quiet --locked -p lancedb --features remote --tests`
  - library: 843 passed, 1 ignored
  - integration groups: 39 passed, 6 passed, 5 passed
- focused coverage for empty directories, orphan data, physical listing
snapshots, zero parent listings, and concurrent manifest arbitration
2026-08-13 21:22:42 +08:00
LanceDB Robot 031c3585a8 chore: update lance dependency to v11.0.0-beta.7 (#3925)
Updates the Rust workspace Lance dependencies and Java lance-core
dependency to v11.0.0-beta.7. No compatibility fixes were required;
full-workspace Clippy passes with warnings denied. Lance tag:
https://github.com/lance-format/lance/releases/tag/v11.0.0-beta.7

---------

Co-authored-by: Yang Cen <159225399+BubbleCal@users.noreply.github.com>
2026-08-13 20:37:19 +08:00
LanceDB Robot 6fb976cf89 chore: update lance dependency to v11.0.0-beta.6 (#3922)
Updates the Rust workspace Lance dependencies and Java lance-core
dependency to v11.0.0-beta.6. Includes compatibility updates for the new
concrete Lance file-version API. Trigger:
https://github.com/lance-format/lance/releases/tag/v11.0.0-beta.6

---------

Co-authored-by: XYZhan <zhaner08@hotmail.com>
2026-08-12 02:43:44 -04:00
Sravan Avvaru a615306f39 feat(python): add on_transform_error fault tolerance to StreamingDataset (#3763)
Closes #3704

## Problem

Transforms can fail on bad data (e.g. nulls/NaNs from incomplete user
surveys). Today any transform exception aborts iteration, and there is
no way to skip invalid rows during loading.

## Solution

New `on_transform_error` parameter on `StreamingDataset`:

- `"raise"` (default, matches current behavior and the convention in
tf.data / WebDataset / Ray Data)
- `"skip"` — drop the failing rows and continue
- `"warn"` — like skip, plus a logged warning per failing batch
- a WebDataset-style callable `handler(exc) -> bool`, so users can skip
only expected error types

Key design points:

- **Row-granular skipping**: when a batch fails, the transform is re-run
on single-row slices so only the rows that actually fail are dropped
(avoids Ray-style whole-block loss). Skips are counted in a new
`rows_skipped` property.
- **No crash on uneven skips**: the round-robin loop now ends the epoch
at the last cycle where every split still has a row, instead of hitting
`IndexError` when a split runs dry early.
- **Exact resumability under skips**: checkpoints are now
position-based. `state_dict` gains `positions_consumed_per_split` (exact
for owned splits), and a new `merge_state_dicts` static method combines
per-rank states via elementwise max for elastic resume across topology
changes. Old checkpoints without the new key still load. Positions equal
sample counts when nothing is skipped, so existing behavior is
unchanged.
- **Guardrail**: transforms returning the wrong number of rows now raise
a clear `ValueError` instead of silently corrupting split accounting.

### Answers to the issue's open questions

- *Can we do this?* Yes — all transforms funnel through one guarded call
in the Stage 2 pipeline.
- *What do other libraries do?* tf.data `ignore_errors()`, WebDataset
`handler=`, Ray `max_errored_blocks`; MosaicML StreamingDataset offers
nothing (skipping conflicts with its determinism model). This design
follows the common conventions: raise by default, opt-in skipping,
count/log drops.
- *Error handling or pre-filtering?* Both: the existing `filter=`
remains the recommended tool for predictable bad data (splits are built
post-filter, so all guarantees hold — now documented);
`on_transform_error` covers failures not expressible as a predicate.
- *Impact on splits / elastic determinism?* Per-split sample sequences
stay deterministic (skips are data-dependent, not topology-dependent).
With unequal bad-row counts across splits the last few global steps of
an epoch can differ across topologies (bounded by the skew), which is
documented on the parameter. With equal counts per split, full
determinism is preserved — covered by a test.

## Testing

15 new tests in `test_elastic_dataloader.py` covering: default raise,
invalid values, uniform and uneven skips (including epoch-end
truncation), warn logging, selective callable handlers, wrong-row-count
guardrail, determinism across runs and across world sizes (1/2/3/4) with
skips, exact mid-epoch resume with skips on the same topology, elastic
resume via `merge_state_dicts` (ws=2 → ws=1), merge validation, and
backward-compat loading of old checkpoints.

Note: relying on CI for the test run — my local machine OOMs during the
final link of the native extension. The change itself is pure Python.

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-08-10 09:22:06 -07:00
Xuanwo 920fc0e455 fix(python): set native module metadata (#3913)
PyO3 defaults native extension classes to `builtins`, so
mkdocstrings/Griffe could not resolve the newly documented
`lancedb.Session` alias and `Deploy docs to Pages` failed on `main`.
Declare the extension module for the public native types referenced by
the Python API docs so Griffe resolves them through `lancedb._lancedb`
and Pages can build again.

Validated with the docs toolchain used by CI (`griffe==0.49.0`,
`mkdocstrings==0.25.2`, and `mkdocs==1.6.1`); `PYTHONPATH=. mkdocs
build` succeeds.
2026-08-10 21:40:31 +08:00
Xuanwo 5acce6782e ci(docs): report link checker failures through issues (#3909) 2026-08-10 15:08:36 +08:00
ForwardXu 12405a4077 chore: drop explicit goosefs-sdk pin in favor of opendal 0.58.1 transitive dep (#3910)
## Summary

`opendal 0.58.1` (the version pulled in transitively via Lance) already
ships
`goosefs-sdk 0.1.9`, which includes the upstream fix for the 0.1.6
compile
break. The explicit version pin that lancedb has been carrying since the
GooseFS feature was introduced is therefore no longer necessary and is
now
redundant work to maintain.

## Changes

- Remove the direct `goosefs-sdk` dependency from
`rust/lancedb/Cargo.toml`
(it was pinned to `=0.1.9` with a comment referencing the 0.1.6 compile
  break).
- Remove the `dep:goosefs-sdk` entry from the `goosefs` cargo feature,
since
  no source file in lancedb imports the crate directly.
- Refresh `Cargo.lock`; `goosefs-sdk 0.1.9` now resolves transitively
through
  `lance` → `opendal 0.58.1`.

## Verification

- `cargo fmt --all` — clean
- `cargo check --features remote,goosefs --tests --examples` — passes
- `Cargo.lock` confirms `goosefs-sdk 0.1.9` is still resolved (now
transitively), so the `goosefs` feature continues to enable the same set
of
  Lance/IOPaths as before.

## Backwards compatibility

No public API changes. The `goosefs` cargo feature still activates
`lance/goosefs`, `lance-io/goosefs`, and
`lance-namespace-impls/dir-goosefs`,
and the same `goosefs-sdk 0.1.9` version is selected by the resolver.
2026-08-10 12:16:21 +08:00
lancedb-gatefixer[bot] 36054be576 fix(node): preserve nested Arrow data across versions (#3900)
<!-- lance-gatekeeper-fix:v1 agent=613a074d606e626c5169d601373a32d8
generation=1 -->

## Root cause

When LanceDB accepted an Arrow table created by a different installed
Arrow package, its compatibility sanitizer rebuilt each Data node
without converting the foreign type or preserving nested children. It
also dropped the separate dictionary vector payload and did not preserve
identity shared by dictionary schema types, vector wrappers, or growing
dictionary chunks.

## Fix

Recursively sanitize nested Arrow data types and child data. Use one
table-scoped sanitization context to rebuild and memoize source type
objects, dictionary vectors, and Data nodes in the local Arrow realm,
preserving all identities required by Arrow IPC.

Add Arrow 15 through 18 regressions for list serialization, ordinary
dictionaries, dictionaries shared across fields and batches, growing
dictionaries, and IPC round trips.

## Validation

- pnpm test __test__/arrow.test.ts --runInBand (188 passed)
- pnpm lint
- pnpm build
- pnpm test --runInBand (706 passed, 5 skipped)
- pnpm run docs

Fixes #2256

---------

Co-authored-by: Gatefixer <313497061+lancedb-gatefixer[bot]@users.noreply.github.com>
2026-08-09 03:34:39 +08:00
Dan Tasse 77a93fee76 fix: get table size from metadata, not files (#3790)
Some issues:
- file_size_bytes is optional in the manifest, so if it's not there (old
writer I guess) it'll under-report the table size.
- it changes results a little bit from the old way by including per-file
footers and metadata (probably not a big difference at real scale)

---------

Co-authored-by: Will Jones <willjones127@gmail.com>
2026-08-07 17:41:41 -04:00
Lance Release 7bb501839a Bump version: 0.37.1-beta.0 → 0.37.1-beta.1 2026-08-07 21:16:07 +00:00
Andrew Chen 5b347afd99 fix: avoid AttributeError in JinaEmbeddings image input for str/Path (#3670)
## What

`JinaEmbeddings._generate_image_input_dict()` crashes with
`AttributeError: 'function' object has no attribute 'urlparse'` on any
image given as a URL string, local path string, or `pathlib.Path` — i.e.
every documented `jina-clip-v1` image-embedding use case except raw
`bytes`.

## Why

```python
from urllib.parse import urlparse
...
parsed = urlparse.urlparse(image)
```

`urlparse` is imported as a function, then called as if it were the
`urllib.parse` module (`urlparse.urlparse(...)`). The module-level
`is_valid_url()` a few lines above does it correctly (`urlparse(text)`),
which is why this reads as a typo rather than intentional. Fixed to
`urlparse(str(image))` — `str()` is needed because `urlparse()` only
accepts `str`/`bytes` and raises a different `AttributeError` on a raw
`Path`.

## Testing

Added `test_jina_generate_image_input_dict_local_path`, which fails with
the original `AttributeError` before the fix and passes after, covering
both a `str` path and a `pathlib.Path`. Verified locally (built the Rust
extension, ran red→green, then the full `test_embeddings.py` file: 15
passed / 8 skipped, no regressions) and with `ruff check`/`ruff format`.

---
Disclosure: this PR was drafted with AI assistance (Claude); I reviewed,
tested, and take responsibility for the change.

---------

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-08-07 14:05:45 -07:00
Dan Rammer 706a9c327f feat: infer maintained indexes when an LsmWriteSpec omits them (#3748)
## What

`LsmWriteSpec::maintained_indexes` becomes `Option<Vec<String>>`:

| value | meaning |
|---|---|
| `None` (new default) | every index the MemWAL supports, resolved when
the spec is installed |
| `Some([])` | maintain nothing — a scan/filter-only WAL table |
| `Some([..])` | exactly these, taken verbatim |

`with_maintained_indexes` keeps its signature;
`with_no_maintained_indexes()` is new. Surfaced through the remote path
(null on the wire), Python, and Node.

## Why

Callers had to state the maintained set by hand every time, which is
both tedious and easy to get wrong — the common case is "maintain what I
already built."

Resolution filters on `IndexConfig::is_memwal_maintainable`, delegating
to lance's `is_maintainable_index_type`. This is load-bearing rather
than cosmetic: lance does **not** skip an index type its memtable cannot
build, it errors when the shard writer opens, so sweeping up a bitmap
index would fail every memtable claim and leave the table unwritable.
The inferred set excludes those, and an explicit list naming one is now
rejected at spec time instead of at claim time.

## Behavior change

A freshly constructed spec used to maintain **nothing**; it now
maintains **everything supported**. This flipped because napi collapses
`undefined` and `null` to `None`, so TypeScript cannot express "absent
means nothing, null means all" — any other choice makes the bindings
disagree with the wire. The error direction also favors it: an unwanted
maintained index costs memory, while a silently unmaintained one
degrades FTS to an unscored scan.

Three existing tests encoded the old default and are updated rather than
worked around.

## Caveat

The resolved set is a snapshot, not a subscription. An index created
after the spec is installed is not maintained until the spec is unset
and set again. `get_lsm_write_spec` therefore always reports a concrete
list — `None` never round-trips.

## Dependency

Needs a lance release carrying `is_maintainable_index_type`
(lance-format/lance#8095) before this builds against the pinned tag.
Draft until then.

## Testing

38 Rust LSM tests and 10 Python tests pass against a local lance build,
including new coverage that a bitmap index is excluded from inference
and rejected when named, and that `[]` stays distinguishable from null
on the wire.

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

---------

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-07 14:50:22 -05:00
LanceDB Robot be290447d9 chore: update lance dependency to v11.0.0-beta.3 (#3896)
Updates the Rust workspace Lance dependencies and Java lance-core
dependency to v11.0.0-beta.3. No compatibility fixes were required;
all-features clippy and Rust formatting pass. Triggering tag:
https://github.com/lance-format/lance/releases/tag/v11.0.0-beta.3
2026-08-07 13:54:32 -05:00
Dan Rammer 79ba076429 feat(table): checkpoint_lsm, flush_lsm, compact_lsm, get_lsm_stats (#3736)
Converge a table's LSM write path into its base table, and inspect it.

`checkpoint_lsm` is `flush` then `compact`, repeated until the fresh
tier is empty — and the loop runs **client-side**. Putting it on the
server would mean a background task, which means a single-flight intent,
an intent that leaks on panic, a bounded-iteration policy, an "is it
done" observable, and a story for every way a client can vanish
mid-operation. None of that exists in this shape: each request does a
bounded unit of work and reports what is left, so completion is *carried
in the responses* rather than inferred from a shared counter that cannot
distinguish "converged" from "hasn't started yet".

Best-effort by construction. Nothing is frozen, so `converged` means L0
was empty as of the last pass. It is idempotent, abandonable at any
point with zero consequence, and safe to run on a cadence — an
already-converged table costs one round trip and zero compaction passes,
because `flush` reports `generations_remaining` and the loop is never
entered.

## The failure taxonomy is the load-bearing part

Five distinct conditions used to arrive at a client as one 503.
`Error::LsmRoute` carries a classification read from the response body's
namespace error code **at the point of receipt** — before any generic
helper folds the body into a string and keeps only the status.

| condition | wire | client action |
|---|---|---|
| contention (latch held / pool saturated) | 429, code 21 | retry with
backoff |
| owning node draining | 503, code 19 `InvalidTableState` | **stop** |
| fenced / no slot / transport | 503, code 17 | retry with backoff |
| registry entry vanished | 404 | re-issue from `flush` (capped) |
| table being dropped / not WAL-backed | 409 / 400 | stop |

Draining is terminal because the drain gate is a one-way latch —
retrying spins until the deadline to report a failure that was knowable
on the first response. Transport retry is disabled on these routes for
the same reason: it treats every 503 alike and would burn its budget
before the classifier ever saw the body.

`get_lsm_stats` returns `Option<LsmStats>`, matching
`get_lsm_write_spec` — `None` only when the table has no LSM write path,
since a struct of zeros would read as measurements.

Python bindings mirror all four, preserving per-bucket detail rather
than flattening to a table-level summary.

## Testing

Six new unit tests against the mocked endpoint, plus the taxonomy
round-trip:
- flush into an empty L0 issues **zero** compact calls (asserts the call
count — `generations_consumed: 0` is also true of a loop that ran a
pointless pass)
- the loop drives compact until the server reports zero remaining
- **contention is not draining**: a 429 retries and converges; asserts
the retry count
- a draining node stops after **exactly one** request, no retries
- stats round-trips fully populated; `include_generation_rows` off by
default
- every `(status, code)` pair classifies correctly, including
unparseable 503 bodies falling back to *retryable* rather than terminal

`cargo test -p lancedb --features remote --lib`: 723 passed.

## Notes for review

- Depends on the sibling lance change returning `SealedGeneration` from
`force_seal_active` only at the *server* level — no lance API is used
here.
- The branch is based on `codex/update-lance-10-0-0-beta-5`, so it
carries one extra commit (`chore: update lance dependency to
v10.0.0-beta.5`) that is not part of this change.

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

---------

Co-authored-by: lancedb automation <robot@lancedb.com>
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-07 13:44:49 -05:00
lancedb-gatefixer[bot] ec21e37040 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

<!-- lance-gatekeeper-fix:v1 agent=4aadcf04e9ac93b97d499d7448b67e19
generation=1 -->

Co-authored-by: Gatefixer <313497061+lancedb-gatefixer[bot]@users.noreply.github.com>
2026-08-07 17:37:37 +08:00
lancedb-gatefixer[bot] 6ba80a960c 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

<!-- lance-gatekeeper-fix:v1 agent=8ba8b18a18260a68a3e605d1bbfa518e
generation=1 -->

Co-authored-by: Gatefixer <313497061+lancedb-gatefixer[bot]@users.noreply.github.com>
2026-08-07 17:33:13 +08:00
lancedb-gatefixer[bot] 11f24b1df4 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

<!-- lance-gatekeeper-fix:v1 agent=d53283c18fdb00a3a1b69448b1f40529
generation=1 -->

---------

Co-authored-by: Gatefixer <313497061+lancedb-gatefixer[bot]@users.noreply.github.com>
2026-08-07 17:33:02 +08:00
lancedb-gatefixer[bot] 2ba7407dc3 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

<!-- lance-gatekeeper-fix:v1 agent=6b7270aeb92e6b6c6f5b45022fa83f6a
generation=1 -->

---------

Co-authored-by: Gatefixer <313497061+lancedb-gatefixer[bot]@users.noreply.github.com>
2026-08-07 17:32:39 +08:00
lancedb-gatefixer[bot] 607e556927 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

<!-- lance-gatekeeper-fix:v1 agent=4e17331e0542c132eae31e86da508629
generation=1 -->

---------

Co-authored-by: Gatefixer <313497061+lancedb-gatefixer[bot]@users.noreply.github.com>
2026-08-07 17:32:28 +08:00
lancedb-gatefixer[bot] 564e5d0d56 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

<!-- lance-gatekeeper-fix:v1 agent=0d42bcda944ac42765b25f2c19ff729f
generation=1 -->

---------

Co-authored-by: Gatefixer <313497061+lancedb-gatefixer[bot]@users.noreply.github.com>
2026-08-07 17:32:17 +08:00
lancedb-gatefixer[bot] dd5cb4d805 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<float16> 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

<!-- lance-gatekeeper-fix:v1 agent=dd0a32a959f691f49de958d4333fb29d
generation=1 -->

---------

Co-authored-by: Gatefixer <313497061+lancedb-gatefixer[bot]@users.noreply.github.com>
2026-08-07 17:32:05 +08:00
lancedb-gatefixer[bot] dbc3687c7b 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

<!-- lance-gatekeeper-fix:v1 agent=7a2b68f3daad20bed9e46cb8892d6e6c
generation=1 -->

Co-authored-by: Gatefixer <313497061+lancedb-gatefixer[bot]@users.noreply.github.com>
2026-08-07 17:31:53 +08:00
lancedb-gatefixer[bot] ec80acb668 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

<!-- lance-gatekeeper-fix:v1 agent=b07901451487187fc03f61890d3aa6bb
generation=1 -->

Co-authored-by: lancedb-gatefixer[bot] <313497061+lancedb-gatefixer[bot]@users.noreply.github.com>
2026-08-07 17:31:42 +08:00
lancedb-gatefixer[bot] fc44535cee 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

<!-- lance-gatekeeper-fix:v1 agent=71e7473e18c91db5137a3c0d3bb73640
generation=1 -->

Co-authored-by: Gatefixer <313497061+lancedb-gatefixer[bot]@users.noreply.github.com>
2026-08-07 17:31:30 +08:00
lancedb-gatefixer[bot] 4048150fdd 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

<!-- lance-gatekeeper-fix:v1 agent=cb0475e85e764f79bd03b35eb8955ec4
generation=1 -->

Co-authored-by: Gatefixer <313497061+lancedb-gatefixer[bot]@users.noreply.github.com>
2026-08-07 17:31:19 +08:00
lancedb-gatefixer[bot] 2922c171f7 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

<!-- lance-gatekeeper-fix:v1 agent=7d385255a072ed89ddc3ff4d08f82218
generation=1 -->

Co-authored-by: Gatefixer <313497061+lancedb-gatefixer[bot]@users.noreply.github.com>
2026-08-07 17:31:08 +08:00
lancedb-gatefixer[bot] c5f9efefe9 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

<!-- lance-gatekeeper-fix:v1 agent=6b25bc529d76813c3db7627c8be947ef
generation=1 -->

Co-authored-by: Gatefixer <313497061+lancedb-gatefixer[bot]@users.noreply.github.com>
2026-08-07 17:30:55 +08:00
David Tolnay f4c668e244 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<Option<Self::Item>> {
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<Option<Self::Item>> {
...   |
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<Option<Self::Item>> {
...   |
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::<Result<Vec<u8>, 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: I) -> JoinAll<I>
  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: I) -> JoinAll<I>
  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: I) -> JoinAll<I>
  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: I) -> JoinAll<I>
  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<Box<dyn RecordBatchStream + Send>>;
   |                                                  ^^^^^^^^^^^^^^^^^
   |
help: specify the associated type
   |
70 | pub type SendableRecordBatchStream = Pin<Box<dyn RecordBatchStream<Error = /* Type */> + 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<V, E> = Shared<BoxFuture<'static, Result<V, Arc<E>>>>;
    |                               ^^^^^^^^^ ------- 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<T, E> = ::std::boxed::Box<Future<Item = T, Error = E> + 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<V, E> = Shared<BoxFuture<'static, Result<V, Arc<E>>>>;
    |                               ^^^^^^^^^          ----------------- 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<T, E> = ::std::boxed::Box<Future<Item = T, Error = E> + Send>;
    |              ^^^^^^^^^ -  -
help: add missing generic argument
    |
 15 | type SharedFut<V, E> = Shared<BoxFuture<'static, Result<V, Arc<E>>, E>>;
    |                                                                   +++

error[E0046]: not all trait items implemented, missing: `Error`, `poll`
   --> rust/lancedb/src/arrow.rs:105:1
    |
105 | impl<S: Stream<Item = Result<arrow_array::RecordBatch>>> Stream for SimpleRecordBatchStream<S> {
    | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 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<Async<std::option::Option<<Self as futures::Stream>::Item>>, <Self as futures::Stream>::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<ObjectMeta>> {
    |                                              ^^^^^^^^^ ------- 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<T, E> = ::std::boxed::Box<Stream<Item = T, Error = E> + 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<ObjectMeta>> {
    |                                              ^^^^^^^^^          ------------------ 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<T, E> = ::std::boxed::Box<Stream<Item = T, Error = E> + Send>;
    |              ^^^^^^^^^ -  -
help: add missing generic argument
    |
 97 |     fn list(&self, prefix: Option<&Path>) -> BoxStream<'static, Result<ObjectMeta>, 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<Path>>,
    |                    ^^^^^^^^^ ------- 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<T, E> = ::std::boxed::Box<Stream<Item = T, Error = E> + 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<Path>>,
    |                    ^^^^^^^^^          ------------ 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<T, E> = ::std::boxed::Box<Stream<Item = T, Error = E> + Send>;
    |              ^^^^^^^^^ -  -
help: add missing generic argument
    |
107 |         locations: BoxStream<'static, Result<Path>, 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<Path>> {
    |          ^^^^^^^^^ ------- 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<T, E> = ::std::boxed::Box<Stream<Item = T, Error = E> + 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<Path>> {
    |          ^^^^^^^^^          ------------ 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<T, E> = ::std::boxed::Box<Stream<Item = T, Error = E> + Send>;
    |              ^^^^^^^^^ -  -
help: add missing generic argument
    |
108 |     ) -> BoxStream<'static, Result<Path>, E> {
    |                                         +++

error[E0599]: no method named `map_err` found for struct `Pin<Box<dyn datafusion_physical_plan::RecordBatchStream + std::marker::Send>>` 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<Box<dyn datafusion_physical_plan::RecordBatchStream + std::marker::Send>>`
    |
   ::: $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<E, F>(self, f: F) -> MapErr<Self, F>
    |        ------- the method is available for `Pin<Box<dyn datafusion_physical_plan::RecordBatchStream + std::marker::Send>>` 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::<Vec<_>>().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<T, E>(item: Result<T, E>) -> Once<T, E> {
    |        ^^^^
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 `<Range<u64> 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<J, T, E>(i: J) -> Iter<J::IntoIter>
    |        ---- required by a bound in this function
 31 |     where J: IntoIterator<Item=Result<T, E>>,
    |                           ^^^^^^^^^^^^^^^^^ required by this bound in `iter`

error[E0599]: no method named `then` found for struct `IterStream<I>` 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<std::ops::Range<u64>>`
    | |_____________|
    |

error[E0599]: no method named `try_collect` found for struct `Pin<Box<dyn lance::io::RecordBatchStream>>` 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::<Vec<_>>()
     | |_________________________-^^^^^^^^^^^

error[E0599]: no method named `and_then` found for associated type `impl Future<Output = Result<Arc<...>, ...>> + 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<Fut, F>(self, f: F) -> AndThen<Self, Fut, F>
    |          -------- the method is available for `impl std::future::Future<Output = std::result::Result<Arc<(dyn ExecutionPlan + 'static)>, 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<Output = Result<Bytes, Error>>`
   --> 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<I>` 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<Map<Cloned<Iter<'_, Range<u64>>>, {closure@...}>>`
    | |_____________|

error[E0599]: no method named `try_next` found for struct `Pin<Box<dyn datafusion_physical_plan::RecordBatchStream + std::marker::Send>>` 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<Box<dyn datafusion_physical_plan::RecordBatchStream + std::marker::Send>>`

error[E0271]: type mismatch resolving `<Vec<...> 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<I>` 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<IntoIter<impl Future<Output = Result<..., ...>>>>`
    | |_________|

error[E0599]: no method named `next` found for struct `Pin<Box<dyn datafusion_physical_plan::RecordBatchStream + std::marker::Send>>` 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<Box<dyn datafusion_physical_plan::RecordBatchStream + std::marker::Send>>`

error[E0599]: no method named `next` found for struct `Pin<Box<dyn datafusion_physical_plan::RecordBatchStream + std::marker::Send>>` in the current scope
   --> rust/lancedb/src/remote/table/insert.rs:345:33
    |
345 |             first = match input.next().await {
    |                                 ^^^^ method not found in `Pin<Box<dyn datafusion_physical_plan::RecordBatchStream + std::marker::Send>>`

error[E0599]: the method `next` exists for mutable reference `&mut Pin<Box<dyn RecordBatchStream + Send>>`, 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<Box<dyn RecordBatchStream + Send>>` due to unsatisfied trait bounds
    |
    = note: the following trait bounds were not satisfied:
            `Pin<Box<(dyn datafusion_physical_plan::RecordBatchStream + std::marker::Send + 'static)>>: Iterator`
            which is required by `&mut Pin<Box<(dyn datafusion_physical_plan::RecordBatchStream + std::marker::Send + 'static)>>: Iterator`

error[E0599]: no method named `map_err` found for struct `IterStream<I>` 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<Box<dyn Iterator<Item = Result<..., ...>> + Send>>`

error[E0599]: no method named `try_collect` found for struct `Pin<Box<dyn datafusion_physical_plan::RecordBatchStream + std::marker::Send>>` in the current scope
    --> rust/lancedb/src/remote/table.rs:1378:49
     |
1378 |             let result: Result<Vec<_>> = stream.try_collect().await.map_err(Error::from);
     |                                                 ^^^^^^^^^^^

error[E0599]: no method named `next` found for struct `Pin<Box<dyn datafusion_physical_plan::RecordBatchStream + std::marker::Send>>` 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<Box<dyn datafusion_physical_plan::RecordBatchStream + std::marker::Send>>`

error[E0599]: no method named `boxed` found for opaque type `impl Future<Output = Result<DeleteResult, Error>>` 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<Output = Result<DeleteResult, Error>>`

error[E0599]: no variant, associated function, or constant named `Left` found for enum `Either<A, B>` 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<impl Future<Output = Result<(Arc<...>, ...), ...>>>` 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<impl Future<Output = Result<(Arc<...>, ...), ...>>>` 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<impl std::future::Future<Output = std::result::Result<(Arc<lance::Dataset>, MergeStats), lance::Error>>>: Iterator`
            which is required by `&mut tokio::time::Timeout<impl std::future::Future<Output = std::result::Result<(Arc<lance::Dataset>, MergeStats), lance::Error>>>: Iterator`

error[E0599]: no variant, associated function, or constant named `Right` found for enum `Either<A, B>` 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<Output = Result<(Arc<Dataset>, ...), ...>>` 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<Output = Result<(Arc<Dataset>, ...), ...>>`

error[E0277]: the trait bound `Iter<Map<IntoIter<RecordBatch>, ...>>: 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<Map<IntoIter<RecordBatch>, ...>>`

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<Async<std::option::Option<<Self as futures::Stream>::Item>>, <Self as futures::Stream>::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<Async<std::option::Option<<Self as futures::Stream>::Item>>, <Self as futures::Stream>::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<I: lance::io::RecordBatchStream + 'static> From<I> 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<Self>
564 | |         where Self: Sized
    | |_________________________^

error[E0599]: the method `map_err` exists for struct `Pin<Box<dyn Stream<Item = Result<..., ...>> + 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<T, E>(item: Result<T, E>) -> Once<T, E> {
   |        ^^^^
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<RecordBatch>".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<T, E>(item: Result<T, E>) -> Once<T, E> {
    |        ^^^^
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<RecordBatch>".to_string(),
110 |                     })
111 ~                 })),
    |
107 ~                 stream: once(Err(async {
108 |                     Err(Error::InvalidInput {
109 |                         message: "Cannot scan an empty Vec<RecordBatch>".to_string(),
110 |                     })
111 ~                 })),
    |

error[E0271]: expected `Ok` to return `Result<Result<RecordBatch, Error>, _>`, but it returns `Result<RecordBatch, _>`
   --> rust/lancedb/src/data/scannable.rs:117:52
    |
117 |         Box::pin(SimpleRecordBatchStream { schema, stream })
    |                                                    ^^^^^^ expected `Result<Result<RecordBatch, Error>, _>`, found `Result<RecordBatch, _>`

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<Receiver<Result<RecordBatch, Error>>, ..., _>`, 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<T, E>(item: Result<T, E>) -> Once<T, E> {
    |        ^^^^
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<Result<RecordBatch, _>>`
    |                               |
    |                               arguments to this function are incorrect
    |
    = note: expected enum `std::result::Result<_, _>`
             found struct `std::future::Ready<std::result::Result<arrow_array::RecordBatch, _>>`
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<T, E>(item: Result<T, E>) -> Once<T, E> {
    |        ^^^^
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<Result<RecordBatch, _>>`
    |                         |
    |                         arguments to this function are incorrect
    |
    = note: expected enum `std::result::Result<_, _>`
             found struct `std::future::Ready<std::result::Result<arrow_array::RecordBatch, _>>`
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<T, E>(item: Result<T, E>) -> Once<T, E> {
    |        ^^^^
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<Result<_, Error>>`
    |                                  |
    |                                  arguments to this function are incorrect
    |
    = note: expected enum `std::result::Result<_, _>`
             found struct `std::future::Ready<std::result::Result<_, error::Error>>`
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<T, E>(item: Result<T, E>) -> Once<T, E> {
    |        ^^^^
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<Box<dyn Future<Output = Result<(), Error>> + 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<Result<RecordBatch, _>, 1>` to be an iterator that yields `Result<Result<RecordBatch, Error>, _>`, but it yields `Result<RecordBatch, _>`
    --> rust/lancedb/src/query.rs:1465:25
     |
1465 |         return Box::pin(SimpleRecordBatchStream::new(
     |                         ^^^^^^^^^^^^^^^^^^^^^^^ expected `Result<Result<RecordBatch, Error>, _>`, found `Result<RecordBatch, _>`

error[E0271]: expected `IntoIter<Result<RecordBatch, _>>` to be an iterator that yields `Result<Result<RecordBatch, Error>, _>`, but it yields `Result<RecordBatch, _>`
    --> rust/lancedb/src/query.rs:1478:14
     |
1478 |     Box::pin(SimpleRecordBatchStream::new(stream::iter(batches), schema))
     |              ^^^^^^^^^^^^^^^^^^^^^^^ expected `Result<Result<RecordBatch, Error>, _>`, found `Result<RecordBatch, _>`

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<T, E>(item: Result<T, E>) -> Once<T, E> {
    |        ^^^^
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<Box<dyn datafusion_physical_plan::RecordBatchStream + std::marker::Send>>` in the current scope
    --> rust/lancedb/src/remote/table.rs:2442:49
     |
2442 |             let result: Result<Vec<_>> = stream.try_collect().await.map_err(Error::from);
     |                                                 ^^^^^^^^^^^

error[E0277]: the trait bound `impl Stream<Item = Result<Bytes, Error>>: 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<Box<dyn datafusion_physical_plan::RecordBatchStream + std::marker::Send>>` 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<Box<dyn datafusion_physical_plan::RecordBatchStream + std::marker::Send>>`

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<T, E>(item: Result<T, E>) -> Once<T, E> {
    |        ^^^^
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<Box<dyn datafusion_physical_plan::RecordBatchStream + std::marker::Send>>` 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<Box<dyn datafusion_physical_plan::RecordBatchStream + std::marker::Send>>`

error[E0599]: no method named `map_err` found for struct `Pin<Box<dyn Future<Output = Result<Arc<...>, ...>> + 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<Box<dyn Future<Output = Result<Arc<...>, ...>> + Send>>`
    | |_____________|

error[E0599]: no method named `next` found for struct `Pin<Box<dyn datafusion_physical_plan::RecordBatchStream + std::marker::Send>>` in the current scope
    --> rust/lancedb/src/table.rs:3048:48
     |
3048 |                 while let Some(batch) = stream.next().await {
     |                                                ^^^^ method not found in `Pin<Box<dyn datafusion_physical_plan::RecordBatchStream + std::marker::Send>>`

error[E0277]: the trait bound `JoinHandle<Result<(), Error>>: 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<std::result::Result<(), error::Error>>`

error[E0277]: `FuturesUnordered<JoinHandle<Result<(), Error>>>` is not an iterator
    --> rust/lancedb/src/table.rs:3054:23
     |
3054 |         for handle in handles {
     |                       ^^^^^^^ `FuturesUnordered<JoinHandle<Result<(), Error>>>` 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<F>
               BiLockAcquire<T>
               Box<F>
               Concat2<S>
               Either<A, B>
               Finished<T, E>
               Fold<S, F, Fut, T>
             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: I) -> JoinAll<I>
     |        -------- 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<F>
               BiLockAcquire<T>
               Box<F>
               Concat2<S>
               Either<A, B>
               Finished<T, E>
               Fold<S, F, Fut, T>
             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<I>
     |            ------- required by a bound in this struct
  23 |     where I: IntoIterator,
  24 |           I::Item: IntoFuture,
     |                    ^^^^^^^^^^ required by this bound in `JoinAll`

error[E0277]: `JoinAll<Map<Iter<'_, FileFragment>, {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<std::iter::Map<std::slice::Iter<'_, FileFragment>, {closure@rust/lancedb/src/table.rs:3452:22: 3452:28}>>`
3454 |           .await;
     |            ^^^^^ `JoinAll<Map<Iter<'_, FileFragment>, {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<F>
               BiLockAcquire<T>
               Box<F>
               Concat2<S>
               Either<A, B>
               Finished<T, E>
               Fold<S, F, Fut, T>
             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<I>
     |            ------- 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::<V, E> {
    |                                                  ++++++++

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::<V, E>::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::<V, E>::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<Self>
564 | |         where Self: Sized
    | |_________________________^

error[E0599]: no method named `poll_unpin` found for mutable reference `&mut Pin<Box<Sleep>>` 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<Box<Sleep>>`

error[E0599]: no method named `poll_next` found for struct `Pin<&mut Pin<Box<dyn RecordBatchStream + Send>>>` 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<Box<dyn RecordBatchStream + Send>>>` 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<Box<dyn RecordBatchStream + Send>>>` 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
```
2026-08-07 17:24:38 +08:00
Xuanwo b1cfe6edb1 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.
2026-08-07 16:31:40 +08:00
LanceDB Robot 001237c7a4 chore: update lance dependency to v11.0.0-beta.2 (#3886)
Updates the Lance dependencies and Java lance-core to v11.0.0-beta.2.
Includes required compatibility fixes for the LanceFileVersion module
move and the updated GooseFS/OpenDAL dependency. Lance tag:
https://github.com/lance-format/lance/releases/tag/v11.0.0-beta.2

---------

Co-authored-by: Daniel Rammer <hamersaw@protonmail.com>
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-06 14:00:22 -05:00
lancedb-gatefixer[bot] 369b10a377 test(rust): cover object store reuse on table open (#3831)
## Summary

- add regression coverage for repeated table opens through one database
connection
- assert that each open reuses the connection object-store client
without another registry miss
- exercise the table after every open so the test covers the complete
dataset-loading path

## Root cause

At the commit reported in #1600, opening a table constructed a separate
object-store client rather than reusing the client that had already
connected to the database. On S3 this repeated credential discovery,
which could fail intermittently in AWS Lambda and surface as
TableNotFound. The connection-owned Session reuse added later fixed the
runtime path, but no focused test protected the open-table invariant.

## Fix

Add a regression test backed by ObjectStoreRegistry statistics. Three
successive opens must add cache hits while leaving the miss count
unchanged, proving that open_table uses the connection Session and its
authenticated object-store client.

## Validation

- cargo fmt --all
- cargo test --quiet --features remote -p lancedb
database::listing::tests::test_open_table_reuses_connection_object_store
- cargo check --quiet --features remote --tests --examples
- cargo clippy --quiet --features remote --tests --examples
- cargo test --quiet --features remote --tests

Fixes #1600

<!-- lance-gatekeeper-fix:v1 agent=974491978c3e42840f32dbc35492d856
generation=1 -->

Co-authored-by: Gatefixer <313497061+lancedb-gatefixer[bot]@users.noreply.github.com>
2026-08-06 16:54:59 +08:00
lancedb-gatefixer[bot] 1c3cd1d918 fix(python): accept Arrow scalars in table updates (#3838)
## Summary
- convert PyArrow scalar values through their Python representation
before SQL literal rendering
- add an end-to-end regression for updating a fixed-size-list vector
from a queried FixedSizeListScalar

## Root cause
Python update literal conversion used single dispatch for native Python
and NumPy values but had no PyArrow Scalar registration. A
FixedSizeListScalar returned by a query therefore reached the
unsupported generic conversion instead of the existing recursive list
converter.

## Validation
- uv run --extra tests pytest python/tests/test_table.py::test_update
python/tests/test_table.py::test_update_with_arrow_scalar
python/tests/test_table.py::test_update_types -q
- uv run --extra tests pytest python/tests/test_util.py -q
- uv run --project python --extra tests --extra dev ruff format --check
python/python/lancedb/util.py python/python/tests/test_table.py
- uv run --project python --extra tests --extra dev ruff check .

Fixes #1228

<!-- lance-gatekeeper-fix:v1 agent=950dd892194e53b61c203d5e3715cac7
generation=1 -->

Co-authored-by: Gatefixer <313497061+lancedb-gatefixer[bot]@users.noreply.github.com>
2026-08-06 16:54:04 +08:00
lancedb-gatefixer[bot] 9707966943 test(rust): cover named memory databases on Windows (#3839)
## Summary\n\n- add a create-table regression for a named database\n-
assert that the derived table URI uses URL separators\n- restore the
four query tests that were moved to temporary files for #1051\n\n## Root
cause\n\n historically joined table names with . On Windows this
inserted a backslash into , so Lance interpreted the URI as an invalid
local filename. The production URI builder now preserves forward slashes
for URI schemes; this change restores the issue-specific tests and adds
direct regression coverage for table creation and the derived URI.\n\n##
Validation\n\n- \n- \n- (passes with four pre-existing warnings in
unrelated remote-table code)\n-
running 814 tests

.......................................................................................
87/814

.....................................i.................................................
174/814

.......................................................................................
261/814

.......................................................................................
348/814

.......................................................................................
435/814

.......................................................................................
522/814

.......................................................................................
609/814

.......................................................................................
696/814

.......................................................................................
783/814
...............................
test result: ok. 813 passed; 0 failed; 1 ignored; 0 measured; 0 filtered
out; finished in 7.76s


running 39 tests
.......................................
test result: ok. 39 passed; 0 failed; 0 ignored; 0 measured; 0 filtered
out; finished in 0.23s


running 6 tests
......
test result: ok. 6 passed; 0 failed; 0 ignored; 0 measured; 0 filtered
out; finished in 0.03s


running 5 tests
.....
test result: ok. 5 passed; 0 failed; 0 ignored; 0 measured; 0 filtered
out; finished in 0.10s


running 0 tests

test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered
out; finished in 0.00s


running 2 tests
..
test result: ok. 2 passed; 0 failed; 0 ignored; 0 measured; 0 filtered
out; finished in 0.00s


running 2 tests
..
test result: ok. 2 passed; 0 failed; 0 ignored; 0 measured; 0 filtered
out; finished in 0.00s (867 passed, 1 ignored)\n- focused named-memory
create and restored query tests\n\nFixes #1051\n\n<!--
lance-gatekeeper-fix:v1 agent=5ddf7a9520292b4cbaa58b9ea5a1fe76
generation=1 -->

Co-authored-by: Gatefixer <313497061+lancedb-gatefixer[bot]@users.noreply.github.com>
2026-08-06 16:53:32 +08:00
Wyatt Alt 62fe413a52 fix: percent-encode index names in per-index remote REST paths (#3840)
Nothing validates index names, so a `/` in one is reachable, and the
remote client interpolates it straight into the URL, splitting the path
so the router 404s. The index then reads back as missing and cannot be
dropped, while `create_index` keeps succeeding because it sends the name
in the body.

Encode at the three affected sites, mirroring `fetch_blob_files`. The
shared Rust client covers all bindings.
2026-08-06 16:53:09 +08:00