Commit Graph
653 Commits
Author SHA1 Message Date
Jack YeandXuanwo 45dbf5eff5 fix: use SQL for all remote materialized view APIs (#4673)
Remote materialized-view APIs now use SQL for the full lifecycle. Create
and refresh already submit SQL; this change moves describe, paginated
listing, and drop onto `DESCRIBE MATERIALIZED VIEW`, `SHOW MATERIALIZED
VIEWS`, and `DROP MATERIALIZED VIEW`.

Async create, refresh, and drop return the existing Job handle with
`job_type = "query"`. The handle has one `id`: the connection-scoped SQL
query ID, also accepted by `describe_query`. Submission starts the
query, the job drains its result once, and repeated waits reuse the
completed result. Synchronous drop waits for catalog removal; physical
cleanup proceeds in the background.

Listing uses namespace-scoped keyset pagination with opaque resume
tokens. Streamed and deferred SQL responses share the same path. Adds
transport coverage and updates Python/TypeScript documentation for
query-backed drop jobs.

---------

Co-authored-by: Xuanwo <github@xuanwo.io>
2026-10-28 04:37:00 +08:00
Dan Tasse 6e4f523e14 docs: remove logical-column from docs (#4693)
We've removed this feature from LanceDB Enterprise and no longer
privilege the "lancedb:logical-column" key. So, removing that from these
docs.
2026-10-27 13:59:51 -04:00
lancedb-gatefixer[bot]andXuanwo 2cd635d41c fix: correct remote materialized view refresh results and options (#4672)
Remote materialized-view refreshes drained the SQL result and then
fabricated `mode=rebuild`, `rows_written=<total view rows>`, and
`source_version=0`. This made incremental refreshes report incorrect
statistics.

Make these three result fields optional across Rust, Python, and
TypeScript, and leave them unset for remote SQL refreshes. Remove the
post-refresh row count request. Local refreshes continue to populate all
metrics, and `version` remains the view version observed after
completion. Remote per-operation statistics remain unavailable until the
SQL job adapter can retrieve them.

Expose the forced-rebuild option through the Rust builder,
synchronous/asynchronous Python refresh and job methods, and TypeScript
refresh. Remote `full=true` sends `WITH (full = true)`; the default lets
the server choose its refresh mode. Local source-version pinning and
remote job cancellation are preserved.

Local refresh regression tests respect the `experimental-functions`
build gate, while remote refresh coverage also runs in default builds.

Validation:

- Rust: 20 materialized-view tests and 21 remote SQL tests passed with
`remote,experimental-functions`; `cargo fmt --all` passed.
- Python: rebuilt the extension in the uv-managed environment. The
default build passed 23 tests with 27 feature-dependent skips. The
opt-in build passed 61 tests with 3 expected default-build-only skips,
covering the materialized-view suite and 14 async query-serialization
cases from current main. Python Ruff lint and formatting checks passed.
- TypeScript: default and opt-in native builds and TypeScript
compilation passed. The default build passed 5 tests with 10 expected
feature-dependent skips; the opt-in build passed all 14
materialized-view tests with the default-build-only test skipped.
Lint/format checks and documentation generation passed.
- Repository-wide `ruff check .` reports 48 pre-existing violations in
unchanged `docs/web-tests` files; `ruff check python` passes.

Fixes #4670

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

Co-authored-by: Gatefixer <313497061+lancedb-gatefixer[bot]@users.noreply.github.com>
Co-authored-by: Xuanwo <github@xuanwo.io>
2026-10-27 21:34:01 +08:00
lancedb-gatefixer[bot]andXuanwo c167b99fa0 fix: read legacy blob columns through blob APIs (#4649)
Legacy blob v1 columns were rejected by LanceDB's blob validation and
omitted from `blob_columns()`, even though the pinned Lance reader
already supports them. Accept both blob versions in read validation and
discovery so `fetch_blobs`, `fetch_blob_files`, and `fetch_blob_ranges`
can read legacy payloads through the existing native reader.

Keep list-nested blobs excluded from row-based reads and document how to
fetch legacy blobs using explicit row IDs. Regression coverage checks
sync and async APIs, independently written Lance datasets, ordering and
duplicates across fragments, null and empty blobs, lazy seeking, ranges,
and deleted rows.

Fixes #4644

Validation:

- Both new Python regressions failed on the original implementation with
the reported legacy-column rejection and empty discovery result, then
passed with the fix.
- Python blob suite: 202 passed, 1 skipped.
- Remote blob tests: 29 passed.
- Focused Rust blob unit tests with `remote`: 13 passed.
- Rust blob integration suite with `remote`: 59 passed, including legacy
storage 2.0 and 2.1.
- Rust compile and Clippy checks with `remote`, tests, and examples
passed.
- Python package Ruff lint and format checks passed; Rust formatting
passed.
- Repository-wide `ruff check .` reports 49 existing errors in unchanged
`docs/web-tests` examples; the Python package is clean.

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

---------

Co-authored-by: Gatefixer <313497061+lancedb-gatefixer[bot]@users.noreply.github.com>
Co-authored-by: Xuanwo <github@xuanwo.io>
2026-10-27 19:56:50 +08:00
lancedb-gatefixer[bot]andXuanwo 83b7275137 fix(remote): make HTTP timeout errors actionable (#4650)
Remote `create_table` reader uploads can exceed the read deadline while
waiting for response headers, but reqwest's main error message only says
"error sending request". Make HTTP and retry errors explicitly identify
timeouts and name the relevant `client_config.timeout_config` settings
and environment variables, preserving typed sources, request metadata,
and retry classification.

Document that the read timeout covers waiting for headers during upload.
This implements the issue's diagnostic option: creation still uses one
streaming request, so large or slow readers may require a longer
`client_config.timeout_config.read_timeout`.

Validation:
- 48 focused Rust client, error, and retry tests passed. Regression
coverage holds an upload open until its read timeout and also exercises
TLS connection timeouts, with and without retries.
- 8 Python create-table and HTTP/retry error tests passed in the
uv-managed environment after rebuilding the extension, including reader
creation with read and overall timeouts.
- `cargo check --quiet --features remote --tests --examples` passed.
- `cargo clippy --quiet --features remote --tests --examples` passed.
- Rust formatting and Python package Ruff lint/format checks passed.
- Repository-wide `ruff check .` reports 49 existing findings under
`docs/web-tests`; those files are unchanged.

Fixes #4646

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

---------

Co-authored-by: Gatefixer <313497061+lancedb-gatefixer[bot]@users.noreply.github.com>
Co-authored-by: Xuanwo <github@xuanwo.io>
2026-10-27 18:51:06 +08:00
Xuanwo 8a4effa06a fix: gate experimental local functions behind an opt-in feature (#4688)
Published SDK builds currently expose local SQL computed columns and
materialized-view execution that are not ready for release. Add the
opt-in `experimental-functions` Cargo feature to the Rust core and
forward it through the Python and Node bindings; published packages
leave it disabled.

Local declarations and refreshes now return `NotSupported` before
mutating tables or spawning jobs, including prepared materialized-view
creation. Remote Cloud/Enterprise client operations, Python UDF
packaging, ordinary eager `add_columns`, and access to existing data
remain available. Stored metadata and write protections stay active, and
shared server commit helpers remain usable.

CI covers both feature configurations. Existing local execution tests
run with the feature enabled; default-build regressions cover early
rejection, unchanged table versions, existing metadata protections, and
normal eager transforms. API and source-build documentation explain the
opt-in.

Validation: Rust library tests pass with the feature disabled (1,869
tests) and enabled (1,947 tests), plus three default-build integration
regressions. Python and TypeScript bindings were rebuilt and tested in
both configurations, including mocked remote requests; Rust formatting,
Python package lint, TypeScript lint/build, and generated docs were
checked. Repository-wide Ruff still reports 48 pre-existing errors in
unchanged `docs/web-tests` files.
2026-10-27 14:32:09 +08:00
Lance Release 37b46f6b4f Bump version: 0.41.0-beta.14 → 0.41.0-beta.15 2026-10-27 01:43:00 +00:00
lancedb-gatefixer[bot]andXuanwo 3352bf49df fix(python): expose optimize compaction options (#3870)
## Summary
- expose typed compaction options on synchronous and asynchronous table
optimization
- translate validated Python options into native Lance compaction
settings
- document the row-based steady-state limit and first-pass/source-budget
caveats
- carry the Lance beta.15 fragment-exclusion option through the typed
Python API

## Root cause
The native Python optimize binding always constructed default compaction
settings, and the public AsyncTable API offered no way to lower the
fragment row target. Wide-row fragments therefore remained compaction
candidates on every later optimize call, repeatedly rewriting the
accumulated table.

## Limitations
This is an explicit row-based mitigation, not byte-native task sizing or
a hard memory cap. Callers must derive target_rows_per_fragment from
expected row size. Existing oversized fragments are rewritten whole on
the first pass; max_bytes_per_file limits output file size rather than
compaction memory; and source budgets admit whole planned tasks, so a
budget below the first task performs no compaction. Splitting tasks by a
byte budget or returning a planner error requires a Lance-side change.

## Validation
- cargo check --release -p lancedb-python
- cargo clippy --quiet -p lancedb-python
- cargo fmt --all --check
- ruff format --check python tests
- ruff check python tests
- uv run --no-sync pytest python/tests/test_table.py -k
optimize_compaction -q (16 passed)

Fixes #2325

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

---------

Co-authored-by: Gatefixer <313497061+lancedb-gatefixer[bot]@users.noreply.github.com>
Co-authored-by: Xuanwo <github@xuanwo.io>
2026-10-27 03:59:52 +08:00
KarthikSandXuanwo 180eaaf68e fix!: reject unsupported remote connection and table options (#4486)
Fixes #4399.

Remote connections currently discard local-only settings, including
table write options that determine storage format. The synchronous
Python client also binds the fourth positional `create_table` argument
to `on_bad_vectors`, so an intended overwrite is sent as a create.

This change aligns the remote Python signature with
`DBConnection.create_table`, preserves the legacy remote timeout
keywords, and raises `ValueError` for other unknown connection keywords.
The shared Rust client rejects unsupported connection, create, and open
options before sending HTTP requests; open validation also runs before a
table-cache hit. Python reports these as `NotImplementedError`,
including explicit `enable_v2_manifest_paths=False`. Supported
Enterprise connection-level storage options remain available.

This implements option 2 from the issue: explicit rejection. It
deliberately does not add unverified server write parameters. Calls that
previously supplied ignored options must omit them to use server-managed
storage and caching. Database URI prefixes are already preserved on
current main and are unchanged here.

Regression tests cover Python sync/async, TypeScript create/open calls,
and the Rust remote client, including empty-table creation, overwrite
mode, and cached table handles. Python API documentation describes the
behavior and corrected positional order.

Reconciled with main at `0ea932da`, preserving upstream blob and
error-handling tests and updating the Python test that previously
expected open options to be ignored.

Validation on the merged source:

- Rebuilt the Python native extension; 304 tests passed across remote
options, database, remote database, and session suites.
- Rust remote database suite: 135 passed. Rebuilt the Node native
extension; TypeScript remote suite: 62 passed. TypeScript compilation
and documentation generation passed.
- Full Python SDK lint and formatting pass (159 files); full TypeScript
lint and formatting pass (66 files). Rust formatting and `git diff
--check` pass.
- Repository-wide Python lint/format reports existing issues in
unchanged `docs/web-tests` files (49 lint findings and 11 formatting
files).

Environment: Ubuntu under WSL2, Linux x86-64, CPython 3.12.13, Rust
1.97.0, and Node.js 22.16.0. Locked dependencies and the repository `ci`
build profile. Remote tests use HTTP fixtures; this does not claim
validation against the original ARM64 single-node deployment.

Documentation review follow-up: added a draft Enterprise migration note
in `docs/web/basics/sdk.mdx`. It explains that `db://` connections
reject local session/cache and per-table storage/write options, while
supported connection-level Enterprise storage options remain available.
Following the repository's release conventions, the note stays in an MDX
comment below the migration list until the behavior is released. Vale
3.24.0 with the pinned LanceDB style package reports no findings; `git
diff --check` passes. This follow-up changes documentation only.

BREAKING CHANGE: Remote connections now reject unsupported local
session/cache and per-table storage/write options instead of silently
ignoring them. Callers must omit these options and use server-managed
storage and caching; supported Enterprise connection-level storage
options remain available.

Integration with current main preserves its synchronous API signatures,
including the remote `create_table(mode=None)` default, and the table
compatibility defaults. The positional-order repair is already on main
via #4608; this PR retains the distinct unsupported-option rejection
behavior.

CI follow-up: the inherited IVF search-effort test occasionally measured
recall 0.375 against its unchanged 0.5 threshold after random index
training. A matching local reproducer observed this once in 30 builds.
The fixture now places the eight exact neighbors together, preserving
the 16-partition setup, two writes, all probe/precedence assertions, and
the recall threshold. The adjusted fixture returned recall 1.0 in 100
builds; the full query and remote suites passed (102 tests). This
stabilizes test data and does not change search behavior or promise
production recall.

---------

Co-authored-by: Xuanwo <github@xuanwo.io>
2026-10-26 21:31:13 +08:00
KarthikSandXuanwo 3dd579785e fix(python)!: align synchronous table method signatures (#4498)
Closes #4396.

Calls written against `Table` can fail or bind different arguments when
used with `RemoteTable`. Align the affected sync signatures, positional
order, and defaults; preserve renamed remote keywords as deprecated
aliases. Share legacy vector configuration and tokenizer inference,
forward index tuning and timeout options, and accept `fast_search`
consistently.

Preserve current upstream conversion behavior: local
`to_polars(batch_size=...)` returns a LazyFrame; remote tables accept
the same argument signature but raise the documented
unsupported-operation error and direct callers to
`table.search().to_polars()`. The abstract `Table` method retains its
unsupported-operation error, with the implementation confined to
`LanceTable`.

Regression coverage checks argument binding, deprecated aliases, option
forwarding, native behavior through both wrappers, HTTP index requests,
and remote conversion rejection. The merge preserves current tokenizer
defaults, remote query defaults, HNSW-PQ support, and
unsupported-operation handling.

Runtime validation at `c4fb01f2` (Linux x86_64, uv-managed Python
3.12.15):
- Focused Ruff lint and formatting passed for all four changed Python
files.
- Rebuilt and installed the current native extension successfully with
the repository's `ci` Cargo profile.
- Seven regression suites passed: `test_errors.py`,
`test_table_signatures.py`, `test_remote_db.py`, `test_table.py`,
`test_index.py`, `test_fts.py`, and `test_query.py`: **874 passed, 1
deselected**, with slow and S3 tests excluded.
- CI caught an abstract-base conversion regression in the initial merge.
The existing error-contract test reproduced it locally (1 failed, 7
passed); restoring the implementation to `LanceTable` fixes it while
preserving signature parity.
- Full-root Ruff reports 49 lint errors and 11 files needing formatting,
all in unchanged upstream `docs/web-tests` files. The four changed
Python files pass both checks.
- Validated against upstream `197403e8`, using Lance 15.0.0-beta.1 and
LanceDB 0.41.0-beta.14 bindings.

Historical validation before this merge: 618 tests passed and one was
deselected. That result does not establish correctness of the current
merge. The original client-side reproduction produced nine
argument-binding errors without contacting a server; the initial
regression suite failed 32 cases before the fix and passed 39 after it.

Documentation review follow-up: restored the `batch_size` parameter
description on the abstract `Table.to_polars` method so it appears in
the public API reference. This adds six docstring lines and changes no
runtime behavior. After this edit, all 47 focused error-contract and
signature tests pass; changed-file Ruff lint/format and `git diff
--check` pass. Repository-wide Ruff still reports the same 49 findings
and 11 formatting files in unchanged `docs/web-tests`. The full
documentation site and seven-suite runtime run were not repeated for
this documentation-only follow-up.

The reported ARM64 production-server deployment has not been recreated.
HTTP integration tests use the repository's local mock server.

BREAKING CHANGE: Remote synchronous table methods now use the positional
argument order and defaults documented by `Table`. Migrate calls using
the former remote-only positional order to named arguments, particularly
`create_index` and `search`; pass `index_type` by keyword to
`create_scalar_index`. Remote scalar and vector index creation now
default to `replace=True`; specify `replace=False` to retain rejection
of an existing index. Renamed remote keywords remain supported as
deprecated aliases, as documented above.

---------

Co-authored-by: Xuanwo <github@xuanwo.io>
2026-10-26 19:28:44 +08:00
lancedb-gatefixer[bot]andXuanwo 66357275bb fix: inherit Lance defaults for IVF RQ quantization (#4585)
`IvfRq()` forced `num_bits=1` through Python's Rust binding, overriding
Lance's five-bit default. The shared native index creation path also
substituted one bit when the Rust or Node builder left the value unset.

Make Python's bit count optional and forward only explicit values. Use
`RQBuildParams::default()` for an unset native bit count, and omit unset
values when dispatching to pylance for accelerator builds. Update the
Python, Rust, Node, and vector-index documentation.

New indexes with an unset bit count now inherit Lance's default,
currently five bits, across Python, Rust, and Node. Five-bit vector
codes use roughly five times the storage of one-bit codes, plus other
index overhead. This also changes reader compatibility: indexes created
with `num_bits >= 2` use a newer on-disk layout that some older LanceDB
versions cannot read. Upgrade readers to versions that support multi-bit
IVF_RQ, or explicitly set `num_bits=1` (for example, `IvfRq(num_bits=1)`
in Python) when sharing indexes with readers limited to one-bit IVF_RQ.

Validation:

- Reproduced six regression failures before the fix: one-bit default
metadata, rejected `None`, and incorrect remote/accelerator forwarding.
- After merging `main` through `b762ec22`, rebuilt both native bindings
against Lance 15.0.0-beta.1. Python validation passed with 106 tests and
15 optional Ollama skips. Coverage includes the full index suite,
omitted and explicit bit counts, reading Lance-created IVF_RQ indexes,
accelerator forwarding, CUDA-unavailability handling, timeout
validation, remote requests, and table/namespace listing behavior.
- Rust formatting and Python package Ruff lint/format checks pass. Node
`pnpm lint`, `pnpm build` (including TypeScript checks), `pnpm run
docs`, the full connection suite, and the existing IVF_RQ regression
pass. Generated documentation adds no further changes.

The merge preserves upstream's explicit RaBitQ heading anchor alongside
the `IvfRq()` example and five-bit default/reader-compatibility
guidance.

Repository-wide `ruff check .` still reports 49 existing errors in
unchanged documentation examples under `docs/web-tests`; root formatting
also finds 11 unchanged documentation example files. The Python package
checks pass.

Fixes #4582

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

---------

Co-authored-by: Gatefixer <313497061+lancedb-gatefixer[bot]@users.noreply.github.com>
Co-authored-by: Xuanwo <github@xuanwo.io>
2026-10-26 18:57:25 +08:00
Lance Release de0c5edc7d Bump version: 0.41.0-beta.13 → 0.41.0-beta.14 2026-10-25 11:22:56 +00:00
Jack Ye dcfef7c5ab feat(python): open lazy blob files from SQL descriptors (#4668)
SQL blob descriptors need pinned source identity to reopen a value after
the query finishes. Adds `open_blob_file(descriptor)` and
`open_blob_files(descriptors)` so Python can read the original blob
incrementally without materializing it in the SQL result.

### Python experience

```python
rows = db.execute_query("""
    SELECT f.timestamp, blob_descriptor(v.video) AS video
    FROM videos v JOIN frames f ON v.id = f.video_id
""").read_all()

videos = db.open_table("videos")
for row in rows.to_pylist():
    if row["video"] is not None:
        with videos.open_blob_file(row["video"]) as video:
            frame = extract_frame(video, row["timestamp"])
```

The singular API accepts the descriptor dictionary or an Arrow struct
scalar directly and returns a seekable `BlobFile`.
`videos.open_blob_files(rows["video"])` accepts an Arrow array/chunked
array or another iterable, opens handles as iteration advances, and
preserves order, duplicates, and nulls as `None`. Async tables provide
`await videos.open_blob_file(...)` and `async for video in
videos.open_blob_files(...)`.

The iterable API can be used directly with the Arrow result column. Zip
it with the rows to keep each frame timestamp:

```python
for row, video in zip(rows.to_pylist(), videos.open_blob_files(rows["video"])):
    if video is not None:
        with video:
            frame = extract_frame(video, row["timestamp"])
```

Each iteration opens one handle. Null descriptors yield `None`; handles
preserve input order and duplicates, and the `with` block closes each
non-null file.

For an async table:

```python
async for video in async_videos.open_blob_files(rows["video"]):
    if video is not None:
        with video:
            payload = await video.aread()
```

### Execution

The descriptor retains the canonical `kind`, `position`, `size`,
`blob_id`, and `blob_uri` fields and carries a `source` struct with its
dataset URI, version, commit timestamp, branch, column, row reference,
and whether that reference is a physical address. Native opening
resolves that exact snapshot without changing the table handle's
checkout and rejects a descriptor belonging to another table. Remote
handles send authenticated byte-range requests with the captured
version/branch, commit timestamp, and source URI, so an old physical
address is never interpreted against the latest table.

Known-size remote descriptors open without a payload request. External
blobs with an unspecified size use a metadata-only HEAD request. Payload
bytes load only when the file is read; full reads still allocate the
full value. Existing row-ID-based `fetch_blob_files` remains available
when a descriptor is not already present.

Coverage includes native snapshot preservation after overwrite, both row
IDs and addresses, dictionary/Arrow inputs, null/empty values, reordered
and duplicate descriptors, lazy iterable behavior, async iteration, and
remote range pinning for all six storage kinds.

Descriptor reads isolate their pinned snapshot from the live table
handle's freshness state. Both native and remote reads verify the source
commit timestamp to reject a replaced snapshot after table recreation.
2026-10-25 02:31:21 -07:00
Jack Ye 9af7757053 fix: execute remote materialized view creation through SQL (#4665)
Remote materialized-view creation currently expects a REST response
containing a registry job ID, while refresh already submits SQL. Route
both through the SQL query backend and preserve Python's synchronous and
asynchronous return types.

### Query to Job mapping

`SqlClient::submit_as_job()` submits an `ExecuteQueryRequest` through
the same backend as `execute_query_async()`. Once submission succeeds,
it retains the returned `Query` and starts one client-side task that
consumes its result reader to completion. `Job::from_query_task()` wraps
that task, its cached outcome, a single ID and a cancellation callback.
Python exposes this as `Job[T]` or `AsyncJob[T]`.

The existing `job_type` property maps SQL-backed handles to the new
`"query"` kind. The shared `id` contains that query's UUID string, so
Python callers can inspect it with `db.describe_query(job.id)` on the
submitting connection. No separate `query_id`, backend enum or `type()`
API is added.

| Handle | `job_type` | `id` | Available lookup |
| --- | --- | --- | --- |
| SQL-backed | `"query"`, known at submission | Connection-scoped query
UUID string | `describe_query(job.id)` |
| Registry-backed | Existing server-reported kind, after `status()` or
`refresh()` | Registry job ID string | Registry job APIs, descriptions
and events |
| Local | `None` | `None` | The current in-process handle |

| Job concept | SQL-backed behavior |
| --- | --- |
| Submission | Await initial SQL submission before returning the handle.
Submission errors propagate immediately. |
| `status()` | Tracks the adapter task: running during result
consumption/finalization, finished after both succeed, failed if either
fails, or cancelled after cancellation. |
| `wait()` | Returns the operation's typed terminal result. One task
consumes the query reader once; repeated waits reuse its cached outcome
without resubmitting SQL or reopening the reader. |
| `cancel()` | Delegates to `Query.cancel()`. Rejected or unsupported
cancellation leaves the task running; an accepted request stops the
task. Cancelling a finished operation is a no-op. |
| Registry details | Query-backed handles expose no registry
description, event history, pause/resume or reattachment after a client
restart. `refresh()` updates cached lifecycle state. |

The Job tracks command completion rather than exposing SQL batches. A
wait timeout does not cancel the query. Query IDs remain scoped to the
submitting connection and cannot be used with registry APIs or as
durable resume tokens. Python `describe_query()` accepts both UUID
objects and the string `id` exposed by query-backed jobs. Rust, Python
and TypeScript expose the same classification through their existing
job-type accessors.

### Materialized-view operations

- **CREATE:** Render `CREATE MATERIALIZED VIEW` with the view namespace
and optional auto-refresh settings. After command completion, resolve
the job to `None`. Synchronous creation waits on the job, then opens and
returns the materialized view.
- **REFRESH:** Submit `REFRESH MATERIALIZED VIEW` through the same
adapter. After command completion, read the refreshed table's version
and row count to construct the existing typed refresh result.
Synchronous refresh waits on the same path.

Remote creation requires the SQL service. Execution placement remains a
server decision. REST endpoints are unchanged. The job guide documents
the ID/job-type contract and query-backed limitations.

Regression coverage exercises SQL submission, namespaces, refresh
options, job-type/ID preservation through typed result mapping, repeated
waits, failures and cancellation rejection. No local builds or tests
were run.
2026-10-25 01:20:13 -07:00
Jack Ye 24c2669a50 feat(sql): follow adaptive streaming and polling responses (#4664)
SQL execution now starts with one Flight call. The server can stream
rows immediately or return a polling continuation, which the SDK follows
through the existing query handle and reader APIs. The same behavior
applies with and without parameters.

Both initial transports share direct-stream lifecycle handling. Handoffs
are explicitly negotiated; existing servers can continue returning
ordinary streams. Add coverage for streaming, deferred results,
cancellation, malformed decisions, and legacy responses.

```mermaid
flowchart TD
    A[execute_query or execute_query_async] --> B{Parameters supplied?}
    B -->|No| C[DoGet: SQL command ticket]
    B -->|Yes| D[DoExchange: SQL and Arrow parameter row]
    C --> E[Server chooses execution path]
    D --> E
    E -->|Stream| F[Initial response: streaming decision and Arrow batches]
    E -->|Defer| G[Initial response: PollInfo continuation]
    G --> H[SDK follows PollFlightInfo]
    H -->|Still running| H
    H -->|Result endpoints available| I[SDK fetches tickets with DoGet]
    F --> J[Query reader yields Arrow batches]
    I --> J
```

`execute_query` opens the reader; `execute_query_async` returns its
query handle. The caller does not select the transport or execution
path.
2026-10-25 15:47:29 +08:00
Jack Ye 6b6778e684 feat: infer lazy blob reader Function inputs (#4663)
Infer Blob V2 input access from the Python Function signature. `video:
BlobReader` requests a lazy, seekable reader; `video: bytes` requests
the complete payload. Users do not need a separate `blob_inputs`
registration option.

## Python experience

For a video table joined to a table of frame timestamps, the Function
can pass its reader directly to a media decoder:

```python
import io

import av
from lancedb import BlobReader, udf


@udf(pip=["av", "Pillow"])
def extract_frame(video: BlobReader, timestamp_s: float) -> bytes:
    with av.open(video) as container:
        stream = container.streams.video[0]
        container.seek(int(timestamp_s / stream.time_base), stream=stream)
        for frame in container.decode(stream):
            if frame.time is not None and frame.time >= timestamp_s:
                output = io.BytesIO()
                frame.to_image().save(output, format="JPEG")
                return output.getvalue()
    raise ValueError("No frame at or after the requested timestamp")
```

After registering `extract_frame`, the query keeps the usual Function
call:

```sql
SELECT f.video_id, f.timestamp_s,
       extract_frame(v.video, f.timestamp_s) AS image
FROM videos AS v
JOIN frames AS f ON v.id = f.video_id
```

The returned JPEG can feed a subsequent image-model step. The reader
provides `read`, `readinto`, `seek`, `tell`, `size()`, and
`read_range(offset, length)`; range reads leave the sequential cursor
unchanged. Each row invocation owns its reader, which closes after the
call. Decoder seeks can request the parts of the MP4 needed to locate
and decode the requested frame; the amount read depends on the file
layout and codec.

Functions that need the whole value can keep a bytes signature:

```python
@udf
def video_length(video: bytes) -> int:
    return len(video)
```

## Execution contract

- Signature inference maps `BlobReader` to a Blob V2 field and records
`blob_access="reader"`. `bytes` keeps its binary Arrow type and records
`blob_access="bytes"`; it can also bind to a Blob V2 source. Explicit
schemas support the same choice, including a Blob V2 field annotated as
`bytes`.
- Reader execution carries a compact blob reference into the Function
worker. Payload reads happen when Python calls the reader, with requests
split into at most 1 MiB ranges. The full video does not need to enter
the Arrow input batch just to invoke the Function.
- Bytes execution materializes the full source before the Function runs
and remains subject to eager-input memory admission. Calling
`reader.read()` without a size also materializes the remaining contents
in Python; lazy access depends on how the Function consumes it.
- Input access metadata survives Python/Rust Function serialization and
registration. Packaged Functions import the worker-provided reader
implementation. This PR defines the SDK contract; execution requires
corresponding server support.
- This changes blob inputs. The example still returns ordinary image
bytes; it does not add streamed outputs or run an image model.

Adds coverage for signature inference, explicit schemas, nullable
readers, signature round trips, seeking, bounded range reads, buffering,
and invalid responses.
2026-10-25 09:50:27 +08:00
Colin Patrick McCabe 631ea41a63 feat!: return lazy iterators for table and namespace listings (#4599)
Make table and namespace listings consistent with other resource
listings across Rust, Python, and TypeScript. Callers can iterate over
individual names across all pages, with configurable page sizes and
continuation tokens.

In order to avoid breaking existing Python users, this change adds some
compatibility methods to the returned Python iterators. Code such as:
```
for t in db.list_tables().tables:
    print(t)
```
will continue to work as expected because the iterator has a `tables`
field and a `page_token` field, which function as expected to reveal the
first page of results. In other words, it is never necessary to use the
iterator __as an iterator__. It may be used as if it were an old-style
response object. (As the iterator progresses, the `tables` list may
switch to the second, third, etc. page of results.) Similar
compatibility code allows the async python API to keep working as well.
2026-10-22 15:41:30 -07:00
lancedb-gatefixer[bot]andXuanwo f5f4463b15 fix(node): materialize nested struct blobs in bytes mode (#4611)
`blobMode: "bytes"` left blobs inside structs as descriptors because
projection matching ignored parent structs and byte replacement only
rebuilt top-level fields.

Expand direct struct projections and aliases to their blob leaves, then
recursively replace the corresponding Arrow fields and data with
`LargeBinary`. Track literal output names separately from descendant
paths so scalar aliases stay intact and every overlapping blob
projection is converted. Preserve null structs, null and empty blobs,
sibling fields, schema metadata, and the existing row ID and snapshot
behavior. Blobs inside lists retain their descriptors. Update the API
docs to describe struct support.

Regression coverage includes full and parent-struct projections, leaf
and struct aliases, dotted output names, overlapping projections in both
orders, nested structs, nulls, empty blobs and results, multiple
batches, list siblings, query reuse, row IDs, and concurrent writes.
Regression tests reproduce the nested-blob and projection-collision
failures before the corresponding changes.

Validation:

- `pnpm build`
- `pnpm lint`
- `pnpm run docs`
- `pnpm test __test__/nested_blob.test.ts __test__/blob.test.ts
__test__/query.test.ts --runInBand --verbose=false` — 142 passed
- `pnpm test __test__/table.test.ts --runInBand --verbose=false -t 'when
dealing with blob columns'` — 60 passed

Fixes #4609

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

---------

Co-authored-by: Gatefixer <313497061+lancedb-gatefixer[bot]@users.noreply.github.com>
Co-authored-by: Xuanwo <github@xuanwo.io>
2026-10-23 03:42:22 +08:00
Ishita Singh e5873d9763 fix(node): support nested blobs with blobMode 'bytes' (#4610)
Closes #4609.

`blobMode: "bytes"` only resolved top-level blob columns, so a blob
nested in a struct came back as its descriptor. Now struct projections
that contain blob columns are resolved too: the bytes are fetched per
blob column and the struct is rebuilt with `LargeBinary` leaves,
preserving struct validity so a null struct stays null. Sibling fields
pass through untouched, aliases work for both `info.image AS image` and
`info AS details`, and blobs nested in lists still keep their
descriptors.

Tested with `npx tsc`, `biome check`, and the full `table.test.ts` suite
(580 tests, including new regressions for select-all, struct selection,
aliases, null structs, null and empty blobs, sibling fields, empty
results, and single-row batches). Not run against remote tables.
2026-10-23 03:24:56 +08:00
Aviral Saxena d826b6115a fix(rust): explain client-side model files when remote tokenize fails (#4519)
Addresses option 2 of #4440.

tokenize() builds the FTS analyzer on the client. For jieba/* and
lindera/* that needs model files under LANCE_LANGUAGE_MODEL_HOME, and
when they’re missing you only got lance’s raw Invalid directory path:
... error, even though server-side search works.

Now, if a jieba/* or lindera/* tokenizer can’t be built, the error keeps
the original cause and adds that tokenize() runs on the client, which
directory the files need to be in, and that for remote tables they must
match the server’s. I also updated the tokenize docs (Rust, Python, TS,
generated JS docs) to say a mismatch can’t be detected, so tokens may
differ from the server’s index.

Behavior when the files exist is unchanged. Detecting a mismatch needs a
server-side tokenize endpoint (option 1), so I left it out. The hint
isn’t limited to remote tables, since Table::as_native is slated for
removal and the wording fits local tables too.

Tests: Rust test with mocked jieba/default index_details and an empty
model dir, one checking other tokenizer errors don’t get the hint, and
Python sync/async tests.
2026-10-23 03:15:01 +08:00
Preston ZhaoandPreston Zhao cc9f01ae26 fix(node): accept exist_ok in createTable (#4432)
TypeScript CreateTableOptions.mode omitted exist_ok even though
createTable already forwards that mode to the native client.

closes: #2900

---------

Co-authored-by: Preston Zhao <195942578+canyang25@users.noreply.github.com>
2026-10-23 02:43:21 +08:00
Lance Release 9f0f6400bc Bump version: 0.41.0-beta.12 → 0.41.0-beta.13 2026-10-22 11:53:47 +00:00
Jack Ye f9680806c9 feat(python): expose materialized view auto-refresh creation options (#4621)
Expose optional `auto_refresh_enabled` and
`auto_refresh_interval_seconds` when creating materialized views from
Python and Rust. Omitted intervals remain unset: the server determines
when to refresh.

```python
view = db.create_materialized_view(
    "recent_orders", "orders",
    auto_refresh_enabled=True,
    auto_refresh_interval_seconds=300,
)
```

## Architecture and workflow

- Python synchronous, asynchronous, job-returning and namespace wrappers
forward both optional values through PyO3 to the Rust creation builder.
Both default to `None`; no client interval is selected.
- Remote creation calls `POST /v1/materialized_view/{id}/create` with
the defining query and only the supplied refresh properties. A `202
Accepted` response must contain a job ID. Existing job tracking waits
for the server's ordinary SQL CREATE job before synchronous creation
opens the view.
- The server validates and authorizes the CREATE, then uses the existing
SQL worker to populate the view and persist its refresh settings. The
Lance agent subsequently uses those settings to submit REFRESH SQL jobs.
- Explicit positive finite fractional seconds are preserved. An interval
alone does not enable refresh. Embedded connections retain manual
refresh, accept explicit disabled state, and reject requests for
automatic scheduling rather than ignore them.

Includes API documentation and coverage for omission, false,
fractional/invalid intervals, namespace forwarding, server job IDs and
invalid acceptance responses.

Requires the server REST-create endpoint to be deployed before SDK
release. Existing remote `with_no_data=True` remains unsupported.
2026-10-21 23:47:09 -07:00
Lance Release 1480a9ff57 Bump version: 0.41.0-beta.11 → 0.41.0-beta.12 2026-10-21 10:48:42 +00:00
Lance Release 46ccbe4e2c Bump version: 0.41.0-beta.10 → 0.41.0-beta.11 2026-10-21 08:21:13 +00:00
Lance Release 9e54c8cf27 Bump version: 0.41.0-beta.9 → 0.41.0-beta.10 2026-10-20 17:06:59 +00:00
Dan RammerandClaude Opus 5.5 92825f6f13 refactor!: rename MemWAL APIs to RTE (realtime engine) (#4598)
## What

Renames the user-facing MemWAL surface to **RTE** (realtime engine)
across Rust, Python, TypeScript, and Java, including the REST wire
format. This follows the LSM → MemWAL rename in #4075.

| Before | After |
|---|---|
| `MemWalWriteSpec` / `MemWalStats` | `RTEWriteSpec` / `RTEStats` |
| `enable_memwal` / `disable_memwal` / `get_memwal_spec` | `enable_rte`
/ `disable_rte` / `get_rte_spec` |
| `flush_memwal` / `compact_memwal` / `checkpoint_memwal` | `flush_rte`
/ `compact_rte` / `checkpoint_rte` |
| `get_memwal_stats` / `close_memwal_writers` | `get_rte_stats` /
`close_rte_writers` |
| `use_memwal` (query, merge insert) | `use_rte` |
| TS/Java `enableMemWal`, `useMemWal`, ... | `enableRTE`, `useRTE`, ...
(napi `js_name` set explicitly) |
| Java `LanceDbTableMemWal` | `LanceDbTableRTE` |
| `table/memwal_stats.rs` | `table/rte_stats.rs` |

Docs and comments now say "realtime engine (RTE)". Text about Lance's
underlying mechanism keeps "MemWAL" ("Lance's MemWAL", "MemWAL index").

**Left alone:** Lance-owned names (`MemWalIndexDetails`,
`DatasetMemWalExt`, `InitializeMemWal*`, `mem_wal` paths), internal
`Lsm*` names and modules, and the `does not support with_row_id` refusal
text that the hybrid fallback matches on.

## Compatibility

Breaking, for an experimental API. The wire format changes, so this must
roll out together with the matching sophon change (lancedb/sophon#8870):

- Routes: `/enable_memwal/` → `/enable_rte/`, `/disable_memwal/` →
`/disable_rte/`, `/get_memwal_spec/` → `/get_rte_spec/`,
`/get_memwal_stats/` → `/get_rte_stats/`, `/flush_memwal/` →
`/flush_rte/`, `/compact_memwal/` → `/compact_rte/`
- Response keys: `memwal_spec` → `rte_spec`, `memwal_stats` →
`rte_stats`
- Request field: `use_memwal` → `use_rte` on query and merge-insert
bodies

The SQL keyword becomes `ALTER TABLE ... SET RTE` server-side. The
guides under `docs/web` will be updated separately.

## Validation

- Rust: `cargo clippy --features remote --tests --examples` clean;
`cargo test --features remote -p lancedb --lib`: 1533 passed
- Java: `mvn test`: 37 passed (Spotless applied with JDK 11)
- Python: `ruff format` / `ruff check` clean; targeted tests
(`test_rte_spec`, `test_merge_insert_lsm`, `test_remote_db`,
`test_wal_hybrid`, `test_query`, `test_permutation`, `test_table`): 708
passed
- TypeScript: `pnpm build`; `table`, `query` tests: 606 passed; `biome
check` clean; `docs/src/js` regenerated with `pnpm run docs`

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

---------

Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-20 11:58:57 -05:00
lancedb-gatefixer[bot] 4a03859b2a fix!: normalize blob layouts and support external byte ranges (#4589) 2026-10-19 16:44:00 -07:00
Lance Release 6507615a6f Bump version: 0.41.0-beta.8 → 0.41.0-beta.9 2026-10-19 21:23:24 +00:00
Lance Release c54b4027ef Bump version: 0.41.0-beta.7 → 0.41.0-beta.8 2026-10-18 18:15:51 +00:00
Bilal Atique b301a47aa8 feat(node): add blobMode to return blob bytes from queries (#4353) 2026-10-16 12:50:38 -07:00
Dan RammerandClaude Opus 5 fe34eb3549 refactor!: rename LSM APIs to MemWAL and align SSTable/table shard naming (#4075)
## What

Renames the MemWAL surface in two passes, across Rust, Python,
TypeScript, and Java, including the wire format.

### 1. MemWAL APIs drop "LSM"

The write path these APIs drive is Lance's MemWAL; "LSM" described its
shape rather than naming it. Turning it on and off now reads as enabling
a feature rather than installing a spec.

| Before | After |
|---|---|
| `set_lsm_write_spec` | `enable_memwal` |
| `unset_lsm_write_spec` | `disable_memwal` |
| `get_lsm_write_spec` | `get_memwal_spec` |
| `flush_lsm` / `compact_lsm` / `checkpoint_lsm` | `flush_memwal` /
`compact_memwal` / `checkpoint_memwal` |
| `get_lsm_stats` | `get_memwal_stats` |
| `close_lsm_writers` | `close_memwal_writers` |
| `use_lsm` (query, merge insert) | `use_memwal` |
| `LsmWriteSpec` / `LsmStats` | `MemWalWriteSpec` / `MemWalStats` |
| Java `LanceDbTableLsm` | `LanceDbTableMemWal` |

TypeScript and Java use camelCase (`enableMemWal`, `getMemWalStats`,
...); the napi methods set `js_name` so the generated names keep the
`MemWal` casing. flush/compact/checkpoint keep a `_memwal` suffix so
`compact_memwal` is not confused with `compact_files` / `optimize`,
which compact the base table.

Internal names (`LsmMode`, `LsmScanner`, the `merge::lsm` / `query::lsm`
modules) are unchanged.

### 2. Stats use SSTable and table shard

Aligns the stats surface with the MemWAL naming settled upstream in
lance-format/lance#7943 and #7957, where the persisted unit became an
**SSTable**.

**Generation → SSTable.** A `GenerationStats` describes one flushed
MemTable, which is an SSTable. The generation *number* is kept — an
SSTable is identified by its generation — so only the noun moved.

**Bucket → table shard.** Each entry is one MemWAL shard. "Bucket" names
only the hash sharding *transform*, so it was already wrong for
`identity` and `year` sharding, which produce shards and no buckets at
all.

| Before | After |
|---|---|
| `GenerationStats` | `SsTableStats` |
| `BucketStats` | `TableShardStats` |
| `stats.buckets` | `stats.table_shards` |
| `BucketStats.generations` | `TableShardStats.sstables` |
| `include_generation_rows` | `include_sstable_rows` |
| `newest_generation` | `newest_sstable_generation` |
| `outstanding_generations` | `outstanding_sstables` |

Docs and comments follow suit: "seal" → "freeze", "L0" / "flushed
generations" → SSTables, "bucket" (meaning a shard) → table shard.

`MemWalWriteSpec::bucket`, `num_buckets`, and the `"Sealed"` shard
status value are **unchanged** — those name the sharding transform or a
server value, not the shard.

## Compatibility

Breaking, for an experimental API. The wire format changes with the
SDKs, so this must roll out together with the matching server change:

- Routes: `/set_lsm_write_spec/` → `/enable_memwal/`,
`/unset_lsm_write_spec/` → `/disable_memwal/`, `/get_lsm_write_spec/` →
`/get_memwal_spec/`, `/flush_lsm/` → `/flush_memwal/`, `/compact_lsm/` →
`/compact_memwal/`, `/get_lsm_stats/` → `/get_memwal_stats/`
- Response keys: `lsm_write_spec` → `memwal_spec`, `lsm_stats` →
`memwal_stats`, `buckets` → `table_shards`, `generations` → `sstables`
- Request fields: `include_generation_rows` → `include_sstable_rows`;
`use_lsm` → `use_memwal` on query and merge-insert bodies

## Validation

- Rust: `cargo test --features remote -p lancedb --lib --tests` — 1467
lib tests and all integration test targets pass; `cargo clippy` clean
- Python: rebuilt the extension; `test_remote_db`, `test_memwal_spec`,
`test_merge_insert_lsm`, `test_query`, `test_permutation`, `test_table`
— 655 passed; `ruff format` / `ruff check` clean
- TypeScript: `pnpm build`; `table`, `merge`, `query` tests — 593
passed; `biome check` clean; `docs/src/js` regenerated with `npm run
docs`
- Java: `mvn test` — 37 passed (Spotless applied with JDK 11; the pinned
google-java-format does not run on JDK 21)

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

---------

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-10-16 13:33:57 -05:00
Lance Release 7b3ba4c437 Bump version: 0.41.0-beta.6 → 0.41.0-beta.7 2026-10-15 22:35:51 +00:00
lancedb-gatefixer[bot] 3336bdfbec fix(remote): bound concurrent blob reads across handles (#4311) 2026-10-14 10:27:25 -07:00
lancedb-gatefixer[bot] 6487503cff fix: omit unfetchable list blobs from column discovery (#4312) 2026-10-14 09:50:31 -07:00
Colin Patrick McCabe 6cc4919c3c feat: add an authorization API (#4363)
Add an authorization API to the LanceDB catalog. This is an API that can
be used to bind access control lists (ACLs) to individual catalog
objects. It can also be used to manage server-side roles, and introspect
principals and groups. The new API is implemented in both the Rust,
Python, and async Python styles and includes list_acls, add_acl,
delete_acl, list_role_bindings, add_role_binding, delete_role_binding,
get_principal, and get_group. This API implements the design principle
of forwards-compatibility. So unknown privilege, object, and subject
types do not cause errors, but are be supported as best as possible in
the library.
2026-10-13 19:59:25 -07:00
lancedb-gatefixer[bot] 0212cfd3d6 fix(python): normalize blob row ID inputs and errors (#4293) 2026-10-13 14:49:51 -07:00
Lance Release 0a988a40ac Bump version: 0.41.0-beta.5 → 0.41.0-beta.6 2026-10-12 11:38:01 +00:00
Yang Cen 0bceaaf3f0 feat(search): expose search effort (#4520)
Lance supports `search_effort`, but LanceDB cannot pass it through its
query APIs. Expose the parameter in Rust, Python sync/async and
TypeScript, including remote requests and hybrid query conversion, using
the [Lance
v14.0.0-beta.13](https://github.com/lance-format/lance/releases/tag/v14.0.0-beta.13)
dependency already on main.

The default is `None` (Auto, internally 0.5). Any explicit effort,
including 0.5, overrides fixed `nprobes` in either setter order;
explicit minimum/maximum bounds remain effective. Invalid values are
rejected in the Rust core. Hybrid conversion now retains the vector
probe parameters and handles an inferred FTS column without panicking.

Namespace queries with explicit effort execute locally because the
namespace wire model cannot carry it. MemWAL LSM queries reject it
explicitly because Lance's LSM scanner does not yet expose this option.
Maturin is included in the dev extra so the documented build command
works in the project environment.

BREAKING CHANGE: The public Rust `VectorQueryRequest` struct now
includes `search_effort: Option<f64>`. Downstream code that initializes
every field explicitly must add `search_effort: None` or use
`..Default::default()`. Exhaustive destructuring patterns must include
the new field or `..`. Query-builder calls and struct literals that
already use `..Default::default()` require no source changes. The new
effort setters in Python and TypeScript are additive. This PR does not
change the persisted file format.

Using explicit effort with remote tables requires server support for
`search_effort` and its precedence over `nprobes`; the client wire tests
do not establish that a deployed server supports it. Server-side support
is tracked in lancedb/sophon#8607.

Validation on AWS after merging current main (Lance beta.13 / SDK
beta.5):

- 14 Rust regressions and the API doctest pass.
- 60 parameter regressions, the full 47-test namespace suite, and three
API doctests pass. The parameter selection includes some namespace
cases.
- 40 TypeScript query tests and 54 remote tests pass; build, lint and
generated docs pass.
- Rust Clippy, Python package lint/format, Pyright and lockfile checks
pass. Repository-wide Ruff reports 49 lint errors and 11 formatting
failures in unchanged `docs/web-tests` files; those files are
byte-identical to main and outside this change.

All 1,103 Git-tracked files match the AWS validation checkout. No
benchmark scripts or result artifacts are included, and no performance
improvement is claimed.

Uses lance-format/lance#9798. Server-side support is in
lancedb/sophon#8607.
2026-10-12 19:32:38 +08:00
Lance Release f32e9ab26a Bump version: 0.41.0-beta.4 → 0.41.0-beta.5 2026-10-12 06:04:07 +00:00
Lance Release 142d032cf1 Bump version: 0.41.0-beta.3 → 0.41.0-beta.4 2026-10-11 06:35:18 +00:00
Lance Release 55ca56d4e2 Bump version: 0.41.0-beta.2 → 0.41.0-beta.3 2026-10-09 21:13:42 +00:00
Jack Ye f305a05fd4 fix: preserve adaptive IVF probe defaults (#4168)
Keep IVF probe fields unset by default so Lance supplies adaptive
behavior. Preserve `nprobes`, `minimum_nprobes`, and `maximum_nprobes`
independently through Rust, Python, namespace, and remote request
construction, with compatibility fields serialized only when explicitly
configured.

The final Rust namespace and LSM propagation update depends on the Lance
release containing lance-format/lance#9184.
2026-10-09 12:45:02 -07:00
Lance Release f85c235d48 Bump version: 0.41.0-beta.1 → 0.41.0-beta.2 2026-10-09 18:21:31 +00:00
lancedb-gatefixer[bot] 3348087d09 fix(node): accept code as a base tokenizer (#4368)
TypeScript rejected `Index.fts({ baseTokenizer: "code" })` because the
public `BaseTokenizer` union omitted `"code"`, although the native
tokenizer already supports it.

Add `"code"` to the shared union, document its identifier behavior, and
regenerate the API reference. The regression test creates and searches a
code-tokenized FTS index with a typed configuration across Arrow
versions 15–18.

Validation:

- The regression failed before the fix with TS2322: `"code"` was not
assignable to `BaseTokenizer`.
- After the fix, all four cases pass: `pnpm test __test__/table.test.ts
--runInBand -t 'full text search code tokenizer' --verbose=false`.
- `pnpm build`, `pnpm lint`, `pnpm run docs`, and formatting of the
changed TypeScript files pass.

Fixes #4366

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

Co-authored-by: Gatefixer <313497061+lancedb-gatefixer[bot]@users.noreply.github.com>
2026-10-08 19:46:02 -07:00
lancedb-gatefixer[bot] 920b1e7bbb fix: align table listing pagination across backends (#4412)
Fixes #4379

Remote `table_names` discarded the continuation token after the first
root-listing request, silently returning the server's default page of 10
names. Remote `list_tables` also inherited that server default, while
local listings had no SDK page limit. Python's synchronous and namespace
wrappers still imposed a default limit of 10 names. The namespace
adapter also discarded continuation tokens, so an unlimited REST
namespace listing could stop at the server's page cap.

Follow server-provided root listing tokens until the requested name
limit or the end of the listing, preserving `start_after`, cache
population, and guards against empty or repeated tokens. The namespace
adapter collects every server page, then sorts names and applies the
name cursor and total limit: namespace tokens are opaque, and page order
need not be name order. Empty tokens end the listing; token cycles
return an error. Python sync and namespace `table_names` wrappers now
default to `None`, matching the asynchronous and TypeScript APIs.
Namespace overrides accept the base connection's optional limit,
avoiding import-time signature errors on Python 3.10 and 3.11. Set the
`list_tables` default to 100 at the shared connection boundary,
including namespaces, and return empty results for a zero limit without
sending an invalid server request. Update the SDK documentation to
describe these defaults.

`list_tables()` is now a paginated call in both modes: callers with more
than 100 tables must follow the returned token or supply an explicit
larger limit. `table_names()` without a limit returns every name.
Namespace table-name calls enumerate the namespace before applying name
cursors and limits; use `list_tables()` for paginated access.

Validation:

- Regression cases reproduce the original root-listing truncation and
cover 130 tables, opaque tokens, short pages, explicit limits,
`start_after`, and zero limits.
- Relevant Rust remote database, connection, and local listing test
groups: 175 passed. Namespace adapter tests: 23 passed, including opaque
tokens, unordered and empty pages, name cursors, limits, zero requests,
and token cycles.
- Rebuilt the Python extension; namespace and local/mocked remote
listing tests using `uv.lock`: 46 passed on Python 3.10 and 46 passed on
Python 3.13.
- REST namespace regressions reproduced the truncation before the fix
and now return all 12 names from a server capped at 10 per response, for
sync/async connections at root and child paths. They also cover
unordered pages, name cursors, explicit limits, and zero requests. The
exact Python 3.10 import error also reproduced before the
optional-signature fix.
- TypeScript connection test file: 38 passed; `pnpm build`, `pnpm lint`,
and `pnpm run docs` passed.
- `cargo check --quiet --features remote --tests --examples` and `cargo
clippy --quiet --features remote --tests --examples` passed.
- Rust formatting, Python Ruff formatting/lint, and diff whitespace
checks passed.

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

---------

Co-authored-by: Gatefixer <313497061+lancedb-gatefixer[bot]@users.noreply.github.com>
2026-10-08 19:43:18 -07:00
Jack Ye 4487b1108c feat(sql): bind query parameters over Flight DoExchange (#4345)
`execute_query` / `execute_query_async` can now bind values to `$1` /
`$name` placeholders instead of splicing them into the SQL text. The
values travel as Arrow, so a float32 keeps its exact bits and type and a
vector stays a compact `FixedSizeList` (3 KB for 768 dims rather than ~9
KB of decimal literals parsed back as Float64).

```rust
db.execute_query_async("SELECT id FROM docs ORDER BY distance(vector, $vector) LIMIT $k")
    .parameters(batch) // one-row RecordBatch
    .execute()
    .await?;
```

```python
db.execute_query(sql, parameters={"vector": np.float32_vector, "k": 10})
```

- A statement with parameters is sent on one `DoExchange`: the first
message carries the `CommandStatementQuery` descriptor and the parameter
schema, the second the one-row batch, and the result rows stream back on
the same call. Statements without parameters keep `PollFlightInfo` +
`DoGet` unchanged.
- Column `i` binds `$<i+1>`; a column whose name is not a number also
binds `$<name>`. Python accepts a list (positional), a dict (named), or
a one-row `RecordBatch` / `Table`; 1-D numpy / pyarrow arrays become a
`FixedSizeList` of their element type.
- A parameterized query runs on its call: `cancel()`, dropping the
handle before reading, or dropping the reader ends the call and stops
the server-side statement. It is still registered for `describe_query`.
- `Database::execute_query_async` now takes an `ExecuteQueryRequest`
options struct.

Server side: lancedb/sophon#8256.
2026-10-08 18:23:46 -07:00
Lance Release ae50d84e34 Bump version: 0.41.0-beta.0 → 0.41.0-beta.1 2026-10-09 00:49:43 +00:00
Colin Patrick McCabe de005f1d61 refactor!: implement lazy iterators for several APIs (#4459)
Implement lazy iterators for:
- secrets
- views
- jobs
- functions
- materialized views
2026-10-07 10:24:20 -07:00
Yuval LifshitzandClaude Opus 5.0 069b2f7d31 feat: add delete rows to table stats (#3783)
add to table stats the number of rows that has
been marked for deletion, but not yet deleted.
note that when a table fragment is removed due to
deletion of all of its rows, these rows are not counted. since the main
use of this feature is to determine when table compaction is needed, the
fact that these rows are not reported should not impact that decision
(deleted table fragments don't need compaction).

Fixes: https://github.com/lancedb/lancedb/issues/3761

Co-authored-by: Claude Opus 5.0 <noreply@anthropic.com>
2026-10-07 08:38:31 -07:00