Compare commits

..

1 Commits

Author SHA1 Message Date
lancedb automation a7b29dfabb chore: update lance dependency to v2.0.0-beta.9 2026-01-15 17:21:57 +00:00
50 changed files with 727 additions and 1113 deletions
+1 -1
View File
@@ -1,5 +1,5 @@
[tool.bumpversion] [tool.bumpversion]
current_version = "0.24.0" current_version = "0.23.1"
parse = """(?x) parse = """(?x)
(?P<major>0|[1-9]\\d*)\\. (?P<major>0|[1-9]\\d*)\\.
(?P<minor>0|[1-9]\\d*)\\. (?P<minor>0|[1-9]\\d*)\\.
@@ -75,13 +75,6 @@ jobs:
VERSION="${VERSION#v}" VERSION="${VERSION#v}"
BRANCH_NAME="codex/update-lance-${VERSION//[^a-zA-Z0-9]/-}" BRANCH_NAME="codex/update-lance-${VERSION//[^a-zA-Z0-9]/-}"
# Use "chore" for beta/rc versions, "feat" for stable releases
if [[ "${VERSION}" == *beta* ]] || [[ "${VERSION}" == *rc* ]]; then
COMMIT_TYPE="chore"
else
COMMIT_TYPE="feat"
fi
cat <<EOF >/tmp/codex-prompt.txt cat <<EOF >/tmp/codex-prompt.txt
You are running inside the lancedb repository on a GitHub Actions runner. Update the Lance dependency to version ${VERSION} and prepare a pull request for maintainers to review. You are running inside the lancedb repository on a GitHub Actions runner. Update the Lance dependency to version ${VERSION} and prepare a pull request for maintainers to review.
@@ -91,10 +84,10 @@ jobs:
3. After clippy succeeds, run "cargo fmt --all" to format the workspace. 3. After clippy succeeds, run "cargo fmt --all" to format the workspace.
4. Ensure the repository is clean except for intentional changes. Inspect "git status --short" and "git diff" to confirm the dependency update and any required fixes. 4. Ensure the repository is clean except for intentional changes. Inspect "git status --short" and "git diff" to confirm the dependency update and any required fixes.
5. Create and switch to a new branch named "${BRANCH_NAME}" (replace any duplicated hyphens if necessary). 5. Create and switch to a new branch named "${BRANCH_NAME}" (replace any duplicated hyphens if necessary).
6. Stage all relevant files with "git add -A". Commit using the message "${COMMIT_TYPE}: update lance dependency to v${VERSION}". 6. Stage all relevant files with "git add -A". Commit using the message "chore: update lance dependency to v${VERSION}".
7. Push the branch to origin. If the branch already exists, force-push your changes. 7. Push the branch to origin. If the branch already exists, force-push your changes.
8. env "GH_TOKEN" is available, use "gh" tools for github related operations like creating pull request. 8. env "GH_TOKEN" is available, use "gh" tools for github related operations like creating pull request.
9. Create a pull request targeting "main" with title "${COMMIT_TYPE}: update lance dependency to v${VERSION}". First, write the PR body to /tmp/pr-body.md using a heredoc (cat <<'EOF' > /tmp/pr-body.md). The body should summarize the dependency bump, clippy/fmt verification, and link the triggering tag (${TAG}). Then run "gh pr create --body-file /tmp/pr-body.md". 9. Create a pull request targeting "main" with title "chore: update lance dependency to v${VERSION}". In the body, summarize the dependency bump, clippy/fmt verification, and link the triggering tag (${TAG}).
10. After creating the PR, display the PR URL, "git status --short", and a concise summary of the commands run and their results. 10. After creating the PR, display the PR URL, "git status --short", and a concise summary of the commands run and their results.
Constraints: Constraints:
+1 -5
View File
@@ -48,8 +48,6 @@ jobs:
run: cargo fmt --all -- --check run: cargo fmt --all -- --check
- name: Run clippy - name: Run clippy
run: cargo clippy --profile ci --workspace --tests --all-features -- -D warnings run: cargo clippy --profile ci --workspace --tests --all-features -- -D warnings
- name: Run clippy (without remote feature)
run: cargo clippy --profile ci --workspace --tests -- -D warnings
build-no-lock: build-no-lock:
runs-on: ubuntu-24.04 runs-on: ubuntu-24.04
@@ -183,7 +181,7 @@ jobs:
runs-on: ubuntu-24.04 runs-on: ubuntu-24.04
strategy: strategy:
matrix: matrix:
msrv: ["1.88.0"] # This should match up with rust-version in Cargo.toml msrv: ["1.78.0"] # This should match up with rust-version in Cargo.toml
env: env:
# Need up-to-date compilers for kernels # Need up-to-date compilers for kernels
CC: clang-18 CC: clang-18
@@ -214,6 +212,4 @@ jobs:
cargo update -p aws-sdk-sts --precise 1.51.0 cargo update -p aws-sdk-sts --precise 1.51.0
cargo update -p home --precise 0.5.9 cargo update -p home --precise 0.5.9
- name: cargo +${{ matrix.msrv }} check - name: cargo +${{ matrix.msrv }} check
env:
RUSTUP_TOOLCHAIN: ${{ matrix.msrv }}
run: cargo check --profile ci --workspace --tests --benches --all-features run: cargo check --profile ci --workspace --tests --benches --all-features
Generated
+352 -485
View File
File diff suppressed because it is too large Load Diff
+29 -29
View File
@@ -12,40 +12,40 @@ repository = "https://github.com/lancedb/lancedb"
description = "Serverless, low-latency vector database for AI applications" description = "Serverless, low-latency vector database for AI applications"
keywords = ["lancedb", "lance", "database", "vector", "search"] keywords = ["lancedb", "lance", "database", "vector", "search"]
categories = ["database-implementations"] categories = ["database-implementations"]
rust-version = "1.88.0" rust-version = "1.78.0"
[workspace.dependencies] [workspace.dependencies]
lance = { "version" = "=1.0.4", default-features = false, "tag" = "v1.0.4", "git" = "https://github.com/lance-format/lance.git" } lance = { "version" = "=2.0.0-beta.9", default-features = false, "tag" = "v2.0.0-beta.9", "git" = "https://github.com/lance-format/lance.git" }
lance-core = { "version" = "=1.0.4", "tag" = "v1.0.4", "git" = "https://github.com/lance-format/lance.git" } lance-core = { "version" = "=2.0.0-beta.9", "tag" = "v2.0.0-beta.9", "git" = "https://github.com/lance-format/lance.git" }
lance-datagen = { "version" = "=1.0.4", "tag" = "v1.0.4", "git" = "https://github.com/lance-format/lance.git" } lance-datagen = { "version" = "=2.0.0-beta.9", "tag" = "v2.0.0-beta.9", "git" = "https://github.com/lance-format/lance.git" }
lance-file = { "version" = "=1.0.4", "tag" = "v1.0.4", "git" = "https://github.com/lance-format/lance.git" } lance-file = { "version" = "=2.0.0-beta.9", "tag" = "v2.0.0-beta.9", "git" = "https://github.com/lance-format/lance.git" }
lance-io = { "version" = "=1.0.4", default-features = false, "tag" = "v1.0.4", "git" = "https://github.com/lance-format/lance.git" } lance-io = { "version" = "=2.0.0-beta.9", default-features = false, "tag" = "v2.0.0-beta.9", "git" = "https://github.com/lance-format/lance.git" }
lance-index = { "version" = "=1.0.4", "tag" = "v1.0.4", "git" = "https://github.com/lance-format/lance.git" } lance-index = { "version" = "=2.0.0-beta.9", "tag" = "v2.0.0-beta.9", "git" = "https://github.com/lance-format/lance.git" }
lance-linalg = { "version" = "=1.0.4", "tag" = "v1.0.4", "git" = "https://github.com/lance-format/lance.git" } lance-linalg = { "version" = "=2.0.0-beta.9", "tag" = "v2.0.0-beta.9", "git" = "https://github.com/lance-format/lance.git" }
lance-namespace = { "version" = "=1.0.4", "tag" = "v1.0.4", "git" = "https://github.com/lance-format/lance.git" } lance-namespace = { "version" = "=2.0.0-beta.9", "tag" = "v2.0.0-beta.9", "git" = "https://github.com/lance-format/lance.git" }
lance-namespace-impls = { "version" = "=1.0.4", default-features = false, "tag" = "v1.0.4", "git" = "https://github.com/lance-format/lance.git" } lance-namespace-impls = { "version" = "=2.0.0-beta.9", default-features = false, "tag" = "v2.0.0-beta.9", "git" = "https://github.com/lance-format/lance.git" }
lance-table = { "version" = "=1.0.4", "tag" = "v1.0.4", "git" = "https://github.com/lance-format/lance.git" } lance-table = { "version" = "=2.0.0-beta.9", "tag" = "v2.0.0-beta.9", "git" = "https://github.com/lance-format/lance.git" }
lance-testing = { "version" = "=1.0.4", "tag" = "v1.0.4", "git" = "https://github.com/lance-format/lance.git" } lance-testing = { "version" = "=2.0.0-beta.9", "tag" = "v2.0.0-beta.9", "git" = "https://github.com/lance-format/lance.git" }
lance-datafusion = { "version" = "=1.0.4", "tag" = "v1.0.4", "git" = "https://github.com/lance-format/lance.git" } lance-datafusion = { "version" = "=2.0.0-beta.9", "tag" = "v2.0.0-beta.9", "git" = "https://github.com/lance-format/lance.git" }
lance-encoding = { "version" = "=1.0.4", "tag" = "v1.0.4", "git" = "https://github.com/lance-format/lance.git" } lance-encoding = { "version" = "=2.0.0-beta.9", "tag" = "v2.0.0-beta.9", "git" = "https://github.com/lance-format/lance.git" }
lance-arrow = { "version" = "=1.0.4", "tag" = "v1.0.4", "git" = "https://github.com/lance-format/lance.git" } lance-arrow = { "version" = "=2.0.0-beta.9", "tag" = "v2.0.0-beta.9", "git" = "https://github.com/lance-format/lance.git" }
ahash = "0.8" ahash = "0.8"
# Note that this one does not include pyarrow # Note that this one does not include pyarrow
arrow = { version = "56.2", optional = false } arrow = { version = "57.2", optional = false }
arrow-array = "56.2" arrow-array = "57.2"
arrow-data = "56.2" arrow-data = "57.2"
arrow-ipc = "56.2" arrow-ipc = "57.2"
arrow-ord = "56.2" arrow-ord = "57.2"
arrow-schema = "56.2" arrow-schema = "57.2"
arrow-select = "56.2" arrow-select = "57.2"
arrow-cast = "56.2" arrow-cast = "57.2"
async-trait = "0" async-trait = "0"
datafusion = { version = "50.1", default-features = false } datafusion = { version = "51.0", default-features = false }
datafusion-catalog = "50.1" datafusion-catalog = "51.0"
datafusion-common = { version = "50.1", default-features = false } datafusion-common = { version = "51.0", default-features = false }
datafusion-execution = "50.1" datafusion-execution = "51.0"
datafusion-expr = "50.1" datafusion-expr = "51.0"
datafusion-physical-plan = "50.1" datafusion-physical-plan = "51.0"
env_logger = "0.11" env_logger = "0.11"
half = { "version" = "2.6.0", default-features = false, features = [ half = { "version" = "2.6.0", default-features = false, features = [
"num-traits", "num-traits",
+1 -1
View File
@@ -14,7 +14,7 @@ Add the following dependency to your `pom.xml`:
<dependency> <dependency>
<groupId>com.lancedb</groupId> <groupId>com.lancedb</groupId>
<artifactId>lancedb-core</artifactId> <artifactId>lancedb-core</artifactId>
<version>0.24.0</version> <version>0.23.1</version>
</dependency> </dependency>
``` ```
+1 -1
View File
@@ -8,7 +8,7 @@
<parent> <parent>
<groupId>com.lancedb</groupId> <groupId>com.lancedb</groupId>
<artifactId>lancedb-parent</artifactId> <artifactId>lancedb-parent</artifactId>
<version>0.24.0-final.0</version> <version>0.23.1-final.0</version>
<relativePath>../pom.xml</relativePath> <relativePath>../pom.xml</relativePath>
</parent> </parent>
+1 -1
View File
@@ -6,7 +6,7 @@
<groupId>com.lancedb</groupId> <groupId>com.lancedb</groupId>
<artifactId>lancedb-parent</artifactId> <artifactId>lancedb-parent</artifactId>
<version>0.24.0-final.0</version> <version>0.23.1-final.0</version>
<packaging>pom</packaging> <packaging>pom</packaging>
<name>${project.artifactId}</name> <name>${project.artifactId}</name>
<description>LanceDB Java SDK Parent POM</description> <description>LanceDB Java SDK Parent POM</description>
+1 -1
View File
@@ -1,7 +1,7 @@
[package] [package]
name = "lancedb-nodejs" name = "lancedb-nodejs"
edition.workspace = true edition.workspace = true
version = "0.24.0" version = "0.23.1"
license.workspace = true license.workspace = true
description.workspace = true description.workspace = true
repository.workspace = true repository.workspace = true
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "@lancedb/lancedb-darwin-arm64", "name": "@lancedb/lancedb-darwin-arm64",
"version": "0.24.0", "version": "0.23.1",
"os": ["darwin"], "os": ["darwin"],
"cpu": ["arm64"], "cpu": ["arm64"],
"main": "lancedb.darwin-arm64.node", "main": "lancedb.darwin-arm64.node",
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "@lancedb/lancedb-darwin-x64", "name": "@lancedb/lancedb-darwin-x64",
"version": "0.24.0", "version": "0.23.1",
"os": ["darwin"], "os": ["darwin"],
"cpu": ["x64"], "cpu": ["x64"],
"main": "lancedb.darwin-x64.node", "main": "lancedb.darwin-x64.node",
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "@lancedb/lancedb-linux-arm64-gnu", "name": "@lancedb/lancedb-linux-arm64-gnu",
"version": "0.24.0", "version": "0.23.1",
"os": ["linux"], "os": ["linux"],
"cpu": ["arm64"], "cpu": ["arm64"],
"main": "lancedb.linux-arm64-gnu.node", "main": "lancedb.linux-arm64-gnu.node",
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "@lancedb/lancedb-linux-arm64-musl", "name": "@lancedb/lancedb-linux-arm64-musl",
"version": "0.24.0", "version": "0.23.1",
"os": ["linux"], "os": ["linux"],
"cpu": ["arm64"], "cpu": ["arm64"],
"main": "lancedb.linux-arm64-musl.node", "main": "lancedb.linux-arm64-musl.node",
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "@lancedb/lancedb-linux-x64-gnu", "name": "@lancedb/lancedb-linux-x64-gnu",
"version": "0.24.0", "version": "0.23.1",
"os": ["linux"], "os": ["linux"],
"cpu": ["x64"], "cpu": ["x64"],
"main": "lancedb.linux-x64-gnu.node", "main": "lancedb.linux-x64-gnu.node",
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "@lancedb/lancedb-linux-x64-musl", "name": "@lancedb/lancedb-linux-x64-musl",
"version": "0.24.0", "version": "0.23.1",
"os": ["linux"], "os": ["linux"],
"cpu": ["x64"], "cpu": ["x64"],
"main": "lancedb.linux-x64-musl.node", "main": "lancedb.linux-x64-musl.node",
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "@lancedb/lancedb-win32-arm64-msvc", "name": "@lancedb/lancedb-win32-arm64-msvc",
"version": "0.24.0", "version": "0.23.1",
"os": [ "os": [
"win32" "win32"
], ],
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "@lancedb/lancedb-win32-x64-msvc", "name": "@lancedb/lancedb-win32-x64-msvc",
"version": "0.24.0", "version": "0.23.1",
"os": ["win32"], "os": ["win32"],
"cpu": ["x64"], "cpu": ["x64"],
"main": "lancedb.win32-x64-msvc.node", "main": "lancedb.win32-x64-msvc.node",
+2 -2
View File
@@ -1,12 +1,12 @@
{ {
"name": "@lancedb/lancedb", "name": "@lancedb/lancedb",
"version": "0.24.0", "version": "0.23.1",
"lockfileVersion": 3, "lockfileVersion": 3,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "@lancedb/lancedb", "name": "@lancedb/lancedb",
"version": "0.24.0", "version": "0.23.1",
"cpu": [ "cpu": [
"x64", "x64",
"arm64" "arm64"
+1 -1
View File
@@ -11,7 +11,7 @@
"ann" "ann"
], ],
"private": false, "private": false,
"version": "0.24.0", "version": "0.23.1",
"main": "dist/index.js", "main": "dist/index.js",
"exports": { "exports": {
".": "./dist/index.js", ".": "./dist/index.js",
+1 -1
View File
@@ -1,5 +1,5 @@
[tool.bumpversion] [tool.bumpversion]
current_version = "0.27.1" current_version = "0.26.1"
parse = """(?x) parse = """(?x)
(?P<major>0|[1-9]\\d*)\\. (?P<major>0|[1-9]\\d*)\\.
(?P<minor>0|[1-9]\\d*)\\. (?P<minor>0|[1-9]\\d*)\\.
+6 -6
View File
@@ -1,28 +1,28 @@
[package] [package]
name = "lancedb-python" name = "lancedb-python"
version = "0.27.1" version = "0.26.1"
edition.workspace = true edition.workspace = true
description = "Python bindings for LanceDB" description = "Python bindings for LanceDB"
license.workspace = true license.workspace = true
repository.workspace = true repository.workspace = true
keywords.workspace = true keywords.workspace = true
categories.workspace = true categories.workspace = true
rust-version = "1.88.0" rust-version = "1.75.0"
[lib] [lib]
name = "_lancedb" name = "_lancedb"
crate-type = ["cdylib"] crate-type = ["cdylib"]
[dependencies] [dependencies]
arrow = { version = "56.2", features = ["pyarrow"] } arrow = { version = "57.2", features = ["pyarrow"] }
async-trait = "0.1" async-trait = "0.1"
lancedb = { path = "../rust/lancedb", default-features = false } lancedb = { path = "../rust/lancedb", default-features = false }
lance-core.workspace = true lance-core.workspace = true
lance-namespace.workspace = true lance-namespace.workspace = true
lance-io.workspace = true lance-io.workspace = true
env_logger.workspace = true env_logger.workspace = true
pyo3 = { version = "0.25", features = ["extension-module", "abi3-py39"] } pyo3 = { version = "0.26", features = ["extension-module", "abi3-py39"] }
pyo3-async-runtimes = { version = "0.25", features = [ pyo3-async-runtimes = { version = "0.26", features = [
"attributes", "attributes",
"tokio-runtime", "tokio-runtime",
] } ] }
@@ -32,7 +32,7 @@ snafu.workspace = true
tokio = { version = "1.40", features = ["sync"] } tokio = { version = "1.40", features = ["sync"] }
[build-dependencies] [build-dependencies]
pyo3-build-config = { version = "0.25", features = [ pyo3-build-config = { version = "0.26", features = [
"extension-module", "extension-module",
"abi3-py39", "abi3-py39",
] } ] }
-1
View File
@@ -179,7 +179,6 @@ class Table:
cleanup_since_ms: Optional[int] = None, cleanup_since_ms: Optional[int] = None,
delete_unverified: Optional[bool] = None, delete_unverified: Optional[bool] = None,
) -> OptimizeStats: ... ) -> OptimizeStats: ...
async def uri(self) -> str: ...
@property @property
def tags(self) -> Tags: ... def tags(self) -> Tags: ...
def query(self) -> Query: ... def query(self) -> Query: ...
-8
View File
@@ -655,14 +655,6 @@ class RemoteTable(Table):
def stats(self): def stats(self):
return LOOP.run(self._table.stats()) return LOOP.run(self._table.stats())
@property
def uri(self) -> str:
"""The table URI (storage location).
For remote tables, this fetches the location from the server via describe.
"""
return LOOP.run(self._table.uri())
def take_offsets(self, offsets: list[int]) -> LanceTakeQueryBuilder: def take_offsets(self, offsets: list[int]) -> LanceTakeQueryBuilder:
return LanceTakeQueryBuilder(self._table.take_offsets(offsets)) return LanceTakeQueryBuilder(self._table.take_offsets(offsets))
-18
View File
@@ -2218,10 +2218,6 @@ class LanceTable(Table):
def stats(self) -> TableStatistics: def stats(self) -> TableStatistics:
return LOOP.run(self._table.stats()) return LOOP.run(self._table.stats())
@property
def uri(self) -> str:
return LOOP.run(self._table.uri())
def create_scalar_index( def create_scalar_index(
self, self,
column: str, column: str,
@@ -3610,20 +3606,6 @@ class AsyncTable:
""" """
return await self._inner.stats() return await self._inner.stats()
async def uri(self) -> str:
"""
Get the table URI (storage location).
For remote tables, this fetches the location from the server via describe.
For local tables, this returns the dataset URI.
Returns
-------
str
The full storage location of the table (e.g., S3/GCS path).
"""
return await self._inner.uri()
async def add( async def add(
self, self,
data: DATA, data: DATA,
-15
View File
@@ -2,27 +2,12 @@
# SPDX-FileCopyrightText: Copyright The LanceDB Authors # SPDX-FileCopyrightText: Copyright The LanceDB Authors
from datetime import timedelta from datetime import timedelta
from lancedb.db import AsyncConnection, DBConnection from lancedb.db import AsyncConnection, DBConnection
import lancedb import lancedb
import pytest import pytest
import pytest_asyncio import pytest_asyncio
def pandas_string_type():
"""Return the PyArrow string type that pandas uses for string columns.
pandas 3.0+ uses large_string for string columns, pandas 2.x uses string.
"""
import pandas as pd
import pyarrow as pa
version = tuple(int(x) for x in pd.__version__.split(".")[:2])
if version >= (3, 0):
return pa.large_utf8()
return pa.utf8()
# Use an in-memory database for most tests. # Use an in-memory database for most tests.
@pytest.fixture @pytest.fixture
def mem_db() -> DBConnection: def mem_db() -> DBConnection:
+4 -13
View File
@@ -268,8 +268,6 @@ async def test_create_table_from_iterator_async(mem_db_async: lancedb.AsyncConne
def test_create_exist_ok(tmp_db: lancedb.DBConnection): def test_create_exist_ok(tmp_db: lancedb.DBConnection):
from conftest import pandas_string_type
data = pd.DataFrame( data = pd.DataFrame(
{ {
"vector": [[3.1, 4.1], [5.9, 26.5]], "vector": [[3.1, 4.1], [5.9, 26.5]],
@@ -288,11 +286,10 @@ def test_create_exist_ok(tmp_db: lancedb.DBConnection):
assert tbl.schema == tbl2.schema assert tbl.schema == tbl2.schema
assert len(tbl) == len(tbl2) assert len(tbl) == len(tbl2)
# pandas 3.0+ uses large_string, pandas 2.x uses string
schema = pa.schema( schema = pa.schema(
[ [
pa.field("vector", pa.list_(pa.float32(), list_size=2)), pa.field("vector", pa.list_(pa.float32(), list_size=2)),
pa.field("item", pandas_string_type()), pa.field("item", pa.utf8()),
pa.field("price", pa.float64()), pa.field("price", pa.float64()),
] ]
) )
@@ -302,7 +299,7 @@ def test_create_exist_ok(tmp_db: lancedb.DBConnection):
bad_schema = pa.schema( bad_schema = pa.schema(
[ [
pa.field("vector", pa.list_(pa.float32(), list_size=2)), pa.field("vector", pa.list_(pa.float32(), list_size=2)),
pa.field("item", pandas_string_type()), pa.field("item", pa.utf8()),
pa.field("price", pa.float64()), pa.field("price", pa.float64()),
pa.field("extra", pa.float32()), pa.field("extra", pa.float32()),
] ]
@@ -368,8 +365,6 @@ async def test_create_mode_async(tmp_db_async: lancedb.AsyncConnection):
@pytest.mark.asyncio @pytest.mark.asyncio
async def test_create_exist_ok_async(tmp_db_async: lancedb.AsyncConnection): async def test_create_exist_ok_async(tmp_db_async: lancedb.AsyncConnection):
from conftest import pandas_string_type
data = pd.DataFrame( data = pd.DataFrame(
{ {
"vector": [[3.1, 4.1], [5.9, 26.5]], "vector": [[3.1, 4.1], [5.9, 26.5]],
@@ -387,11 +382,10 @@ async def test_create_exist_ok_async(tmp_db_async: lancedb.AsyncConnection):
assert tbl.name == tbl2.name assert tbl.name == tbl2.name
assert await tbl.schema() == await tbl2.schema() assert await tbl.schema() == await tbl2.schema()
# pandas 3.0+ uses large_string, pandas 2.x uses string
schema = pa.schema( schema = pa.schema(
[ [
pa.field("vector", pa.list_(pa.float32(), list_size=2)), pa.field("vector", pa.list_(pa.float32(), list_size=2)),
pa.field("item", pandas_string_type()), pa.field("item", pa.utf8()),
pa.field("price", pa.float64()), pa.field("price", pa.float64()),
] ]
) )
@@ -601,8 +595,6 @@ def test_open_table_sync(tmp_db: lancedb.DBConnection):
@pytest.mark.asyncio @pytest.mark.asyncio
async def test_open_table(tmp_path): async def test_open_table(tmp_path):
from conftest import pandas_string_type
db = await lancedb.connect_async(tmp_path) db = await lancedb.connect_async(tmp_path)
data = pd.DataFrame( data = pd.DataFrame(
{ {
@@ -622,11 +614,10 @@ async def test_open_table(tmp_path):
) )
is not None is not None
) )
# pandas 3.0+ uses large_string, pandas 2.x uses string
assert await tbl.schema() == pa.schema( assert await tbl.schema() == pa.schema(
{ {
"vector": pa.list_(pa.float32(), list_size=2), "vector": pa.list_(pa.float32(), list_size=2),
"item": pandas_string_type(), "item": pa.utf8(),
"price": pa.float64(), "price": pa.float64(),
} }
) )
@@ -26,8 +26,6 @@ import pytest
from lance_namespace import ( from lance_namespace import (
CreateEmptyTableRequest, CreateEmptyTableRequest,
CreateEmptyTableResponse, CreateEmptyTableResponse,
DeclareTableRequest,
DeclareTableResponse,
DescribeTableRequest, DescribeTableRequest,
DescribeTableResponse, DescribeTableResponse,
LanceNamespace, LanceNamespace,
@@ -162,19 +160,6 @@ class TrackingNamespace(LanceNamespace):
return modified return modified
def declare_table(self, request: DeclareTableRequest) -> DeclareTableResponse:
"""Track declare_table calls and inject rotating credentials."""
with self.lock:
self.create_call_count += 1
count = self.create_call_count
response = self.inner.declare_table(request)
response.storage_options = self._modify_storage_options(
response.storage_options, count
)
return response
def create_empty_table( def create_empty_table(
self, request: CreateEmptyTableRequest self, request: CreateEmptyTableRequest
) -> CreateEmptyTableResponse: ) -> CreateEmptyTableResponse:
-6
View File
@@ -1967,9 +1967,3 @@ def test_add_table_with_empty_embeddings(tmp_path):
on_bad_vectors="drop", on_bad_vectors="drop",
) )
assert table.count_rows() == 1 assert table.count_rows() == 1
def test_table_uri(tmp_path):
db = lancedb.connect(tmp_path)
table = db.create_table("my_table", data=[{"x": 0}])
assert table.uri == str(tmp_path / "my_table.lance")
+3 -10
View File
@@ -528,19 +528,12 @@ def test_sanitize_data(
else: else:
expected_schema = schema expected_schema = schema
else: else:
from conftest import pandas_string_type
# polars uses large_string, pandas 3.0+ uses large_string, others use string
if isinstance(data, pl.DataFrame):
text_type = pa.large_utf8()
elif isinstance(data, pd.DataFrame):
text_type = pandas_string_type()
else:
text_type = pa.string()
expected_schema = pa.schema( expected_schema = pa.schema(
{ {
"id": pa.int64(), "id": pa.int64(),
"text": text_type, "text": pa.large_utf8()
if isinstance(data, pl.DataFrame)
else pa.string(),
"vector": pa.list_(pa.float32(), 10), "vector": pa.list_(pa.float32(), 10),
} }
) )
+12 -5
View File
@@ -10,8 +10,7 @@ use arrow::{
use futures::stream::StreamExt; use futures::stream::StreamExt;
use lancedb::arrow::SendableRecordBatchStream; use lancedb::arrow::SendableRecordBatchStream;
use pyo3::{ use pyo3::{
exceptions::PyStopAsyncIteration, pyclass, pymethods, Bound, PyAny, PyObject, PyRef, PyResult, exceptions::PyStopAsyncIteration, pyclass, pymethods, Bound, Py, PyAny, PyRef, PyResult, Python,
Python,
}; };
use pyo3_async_runtimes::tokio::future_into_py; use pyo3_async_runtimes::tokio::future_into_py;
@@ -36,8 +35,11 @@ impl RecordBatchStream {
#[pymethods] #[pymethods]
impl RecordBatchStream { impl RecordBatchStream {
#[getter] #[getter]
pub fn schema(&self, py: Python) -> PyResult<PyObject> { pub fn schema(&self, py: Python) -> PyResult<Py<PyAny>> {
(*self.schema).clone().into_pyarrow(py) (*self.schema)
.clone()
.into_pyarrow(py)
.map(|obj| obj.unbind())
} }
pub fn __aiter__(self_: PyRef<'_, Self>) -> PyRef<'_, Self> { pub fn __aiter__(self_: PyRef<'_, Self>) -> PyRef<'_, Self> {
@@ -53,7 +55,12 @@ impl RecordBatchStream {
.next() .next()
.await .await
.ok_or_else(|| PyStopAsyncIteration::new_err(""))?; .ok_or_else(|| PyStopAsyncIteration::new_err(""))?;
Python::with_gil(|py| inner_next.infer_error()?.to_pyarrow(py)) Python::attach(|py| {
inner_next
.infer_error()?
.to_pyarrow(py)
.map(|obj| obj.unbind())
})
}) })
} }
} }
+25 -22
View File
@@ -12,7 +12,7 @@ use pyo3::{
exceptions::{PyRuntimeError, PyValueError}, exceptions::{PyRuntimeError, PyValueError},
pyclass, pyfunction, pymethods, pyclass, pyfunction, pymethods,
types::{PyDict, PyDictMethods}, types::{PyDict, PyDictMethods},
Bound, FromPyObject, Py, PyAny, PyObject, PyRef, PyResult, Python, Bound, FromPyObject, Py, PyAny, PyRef, PyResult, Python,
}; };
use pyo3_async_runtimes::tokio::future_into_py; use pyo3_async_runtimes::tokio::future_into_py;
@@ -114,7 +114,7 @@ impl Connection {
data: Bound<'_, PyAny>, data: Bound<'_, PyAny>,
namespace: Vec<String>, namespace: Vec<String>,
storage_options: Option<HashMap<String, String>>, storage_options: Option<HashMap<String, String>>,
storage_options_provider: Option<PyObject>, storage_options_provider: Option<Py<PyAny>>,
location: Option<String>, location: Option<String>,
) -> PyResult<Bound<'a, PyAny>> { ) -> PyResult<Bound<'a, PyAny>> {
let inner = self_.get_inner()?.clone(); let inner = self_.get_inner()?.clone();
@@ -152,7 +152,7 @@ impl Connection {
schema: Bound<'_, PyAny>, schema: Bound<'_, PyAny>,
namespace: Vec<String>, namespace: Vec<String>,
storage_options: Option<HashMap<String, String>>, storage_options: Option<HashMap<String, String>>,
storage_options_provider: Option<PyObject>, storage_options_provider: Option<Py<PyAny>>,
location: Option<String>, location: Option<String>,
) -> PyResult<Bound<'a, PyAny>> { ) -> PyResult<Bound<'a, PyAny>> {
let inner = self_.get_inner()?.clone(); let inner = self_.get_inner()?.clone();
@@ -187,7 +187,7 @@ impl Connection {
name: String, name: String,
namespace: Vec<String>, namespace: Vec<String>,
storage_options: Option<HashMap<String, String>>, storage_options: Option<HashMap<String, String>>,
storage_options_provider: Option<PyObject>, storage_options_provider: Option<Py<PyAny>>,
index_cache_size: Option<u32>, index_cache_size: Option<u32>,
location: Option<String>, location: Option<String>,
) -> PyResult<Bound<'_, PyAny>> { ) -> PyResult<Bound<'_, PyAny>> {
@@ -297,6 +297,8 @@ impl Connection {
future_into_py(py, async move { future_into_py(py, async move {
use lance_namespace::models::ListNamespacesRequest; use lance_namespace::models::ListNamespacesRequest;
let request = ListNamespacesRequest { let request = ListNamespacesRequest {
identity: None,
context: None,
id: if namespace.is_empty() { id: if namespace.is_empty() {
None None
} else { } else {
@@ -304,10 +306,9 @@ impl Connection {
}, },
page_token, page_token,
limit: limit.map(|l| l as i32), limit: limit.map(|l| l as i32),
..Default::default()
}; };
let response = inner.list_namespaces(request).await.infer_error()?; let response = inner.list_namespaces(request).await.infer_error()?;
Python::with_gil(|py| -> PyResult<Py<PyDict>> { Python::attach(|py| -> PyResult<Py<PyDict>> {
let dict = PyDict::new(py); let dict = PyDict::new(py);
dict.set_item("namespaces", response.namespaces)?; dict.set_item("namespaces", response.namespaces)?;
dict.set_item("page_token", response.page_token)?; dict.set_item("page_token", response.page_token)?;
@@ -327,25 +328,25 @@ impl Connection {
let py = self_.py(); let py = self_.py();
future_into_py(py, async move { future_into_py(py, async move {
use lance_namespace::models::CreateNamespaceRequest; use lance_namespace::models::CreateNamespaceRequest;
// Mode is now a string field let mode_value = mode.and_then(|m| match m.to_lowercase().as_str() {
let mode_str = mode.and_then(|m| match m.to_lowercase().as_str() {
"create" => Some("Create".to_string()), "create" => Some("Create".to_string()),
"exist_ok" => Some("ExistOk".to_string()), "exist_ok" => Some("ExistOk".to_string()),
"overwrite" => Some("Overwrite".to_string()), "overwrite" => Some("Overwrite".to_string()),
_ => None, _ => None,
}); });
let request = CreateNamespaceRequest { let request = CreateNamespaceRequest {
identity: None,
context: None,
id: if namespace.is_empty() { id: if namespace.is_empty() {
None None
} else { } else {
Some(namespace) Some(namespace)
}, },
mode: mode_str, mode: mode_value,
properties, properties,
..Default::default()
}; };
let response = inner.create_namespace(request).await.infer_error()?; let response = inner.create_namespace(request).await.infer_error()?;
Python::with_gil(|py| -> PyResult<Py<PyDict>> { Python::attach(|py| -> PyResult<Py<PyDict>> {
let dict = PyDict::new(py); let dict = PyDict::new(py);
dict.set_item("properties", response.properties)?; dict.set_item("properties", response.properties)?;
Ok(dict.unbind()) Ok(dict.unbind())
@@ -364,29 +365,29 @@ impl Connection {
let py = self_.py(); let py = self_.py();
future_into_py(py, async move { future_into_py(py, async move {
use lance_namespace::models::DropNamespaceRequest; use lance_namespace::models::DropNamespaceRequest;
// Mode and Behavior are now string fields let mode_value = mode.and_then(|m| match m.to_uppercase().as_str() {
let mode_str = mode.and_then(|m| match m.to_uppercase().as_str() {
"SKIP" => Some("Skip".to_string()), "SKIP" => Some("Skip".to_string()),
"FAIL" => Some("Fail".to_string()), "FAIL" => Some("Fail".to_string()),
_ => None, _ => None,
}); });
let behavior_str = behavior.and_then(|b| match b.to_uppercase().as_str() { let behavior_value = behavior.and_then(|b| match b.to_uppercase().as_str() {
"RESTRICT" => Some("Restrict".to_string()), "RESTRICT" => Some("Restrict".to_string()),
"CASCADE" => Some("Cascade".to_string()), "CASCADE" => Some("Cascade".to_string()),
_ => None, _ => None,
}); });
let request = DropNamespaceRequest { let request = DropNamespaceRequest {
identity: None,
context: None,
id: if namespace.is_empty() { id: if namespace.is_empty() {
None None
} else { } else {
Some(namespace) Some(namespace)
}, },
mode: mode_str, mode: mode_value,
behavior: behavior_str, behavior: behavior_value,
..Default::default()
}; };
let response = inner.drop_namespace(request).await.infer_error()?; let response = inner.drop_namespace(request).await.infer_error()?;
Python::with_gil(|py| -> PyResult<Py<PyDict>> { Python::attach(|py| -> PyResult<Py<PyDict>> {
let dict = PyDict::new(py); let dict = PyDict::new(py);
dict.set_item("properties", response.properties)?; dict.set_item("properties", response.properties)?;
dict.set_item("transaction_id", response.transaction_id)?; dict.set_item("transaction_id", response.transaction_id)?;
@@ -405,15 +406,16 @@ impl Connection {
future_into_py(py, async move { future_into_py(py, async move {
use lance_namespace::models::DescribeNamespaceRequest; use lance_namespace::models::DescribeNamespaceRequest;
let request = DescribeNamespaceRequest { let request = DescribeNamespaceRequest {
identity: None,
context: None,
id: if namespace.is_empty() { id: if namespace.is_empty() {
None None
} else { } else {
Some(namespace) Some(namespace)
}, },
..Default::default()
}; };
let response = inner.describe_namespace(request).await.infer_error()?; let response = inner.describe_namespace(request).await.infer_error()?;
Python::with_gil(|py| -> PyResult<Py<PyDict>> { Python::attach(|py| -> PyResult<Py<PyDict>> {
let dict = PyDict::new(py); let dict = PyDict::new(py);
dict.set_item("properties", response.properties)?; dict.set_item("properties", response.properties)?;
Ok(dict.unbind()) Ok(dict.unbind())
@@ -433,6 +435,8 @@ impl Connection {
future_into_py(py, async move { future_into_py(py, async move {
use lance_namespace::models::ListTablesRequest; use lance_namespace::models::ListTablesRequest;
let request = ListTablesRequest { let request = ListTablesRequest {
identity: None,
context: None,
id: if namespace.is_empty() { id: if namespace.is_empty() {
None None
} else { } else {
@@ -440,10 +444,9 @@ impl Connection {
}, },
page_token, page_token,
limit: limit.map(|l| l as i32), limit: limit.map(|l| l as i32),
..Default::default()
}; };
let response = inner.list_tables(request).await.infer_error()?; let response = inner.list_tables(request).await.infer_error()?;
Python::with_gil(|py| -> PyResult<Py<PyDict>> { Python::attach(|py| -> PyResult<Py<PyDict>> {
let dict = PyDict::new(py); let dict = PyDict::new(py);
dict.set_item("tables", response.tables)?; dict.set_item("tables", response.tables)?;
dict.set_item("page_token", response.page_token)?; dict.set_item("page_token", response.page_token)?;
+2 -2
View File
@@ -40,7 +40,7 @@ impl<T> PythonErrorExt<T> for std::result::Result<T, LanceError> {
request_id, request_id,
source, source,
status_code, status_code,
} => Python::with_gil(|py| { } => Python::attach(|py| {
let message = err.to_string(); let message = err.to_string();
let http_err_cls = py let http_err_cls = py
.import(intern!(py, "lancedb.remote.errors"))? .import(intern!(py, "lancedb.remote.errors"))?
@@ -75,7 +75,7 @@ impl<T> PythonErrorExt<T> for std::result::Result<T, LanceError> {
max_read_failures, max_read_failures,
source, source,
status_code, status_code,
} => Python::with_gil(|py| { } => Python::attach(|py| {
let cause_err = http_from_rust_error( let cause_err = http_from_rust_error(
py, py,
source.as_ref(), source.as_ref(),
+2 -2
View File
@@ -12,7 +12,7 @@ pub struct PyHeaderProvider {
impl Clone for PyHeaderProvider { impl Clone for PyHeaderProvider {
fn clone(&self) -> Self { fn clone(&self) -> Self {
Python::with_gil(|py| Self { Python::attach(|py| Self {
provider: self.provider.clone_ref(py), provider: self.provider.clone_ref(py),
}) })
} }
@@ -25,7 +25,7 @@ impl PyHeaderProvider {
/// Get headers from the Python provider (internal implementation) /// Get headers from the Python provider (internal implementation)
fn get_headers_internal(&self) -> Result<HashMap<String, String>, String> { fn get_headers_internal(&self) -> Result<HashMap<String, String>, String> {
Python::with_gil(|py| { Python::attach(|py| {
// Call the get_headers method // Call the get_headers method
let result = self.provider.call_method0(py, "get_headers"); let result = self.provider.call_method0(py, "get_headers");
+1 -1
View File
@@ -281,7 +281,7 @@ impl PyPermutationReader {
let reader = slf.reader.clone(); let reader = slf.reader.clone();
future_into_py(slf.py(), async move { future_into_py(slf.py(), async move {
let schema = reader.output_schema(selection).await.infer_error()?; let schema = reader.output_schema(selection).await.infer_error()?;
Python::with_gil(|py| schema.to_pyarrow(py)) Python::attach(|py| schema.as_ref().to_pyarrow(py).map(|obj| obj.unbind()))
}) })
} }
+4 -4
View File
@@ -453,7 +453,7 @@ impl Query {
let inner = self_.inner.clone(); let inner = self_.inner.clone();
future_into_py(self_.py(), async move { future_into_py(self_.py(), async move {
let schema = inner.output_schema().await.infer_error()?; let schema = inner.output_schema().await.infer_error()?;
Python::with_gil(|py| schema.to_pyarrow(py)) Python::attach(|py| schema.to_pyarrow(py).map(|obj| obj.unbind()))
}) })
} }
@@ -532,7 +532,7 @@ impl TakeQuery {
let inner = self_.inner.clone(); let inner = self_.inner.clone();
future_into_py(self_.py(), async move { future_into_py(self_.py(), async move {
let schema = inner.output_schema().await.infer_error()?; let schema = inner.output_schema().await.infer_error()?;
Python::with_gil(|py| schema.to_pyarrow(py)) Python::attach(|py| schema.to_pyarrow(py).map(|obj| obj.unbind()))
}) })
} }
@@ -627,7 +627,7 @@ impl FTSQuery {
let inner = self_.inner.clone(); let inner = self_.inner.clone();
future_into_py(self_.py(), async move { future_into_py(self_.py(), async move {
let schema = inner.output_schema().await.infer_error()?; let schema = inner.output_schema().await.infer_error()?;
Python::with_gil(|py| schema.to_pyarrow(py)) Python::attach(|py| schema.to_pyarrow(py).map(|obj| obj.unbind()))
}) })
} }
@@ -806,7 +806,7 @@ impl VectorQuery {
let inner = self_.inner.clone(); let inner = self_.inner.clone();
future_into_py(self_.py(), async move { future_into_py(self_.py(), async move {
let schema = inner.output_schema().await.infer_error()?; let schema = inner.output_schema().await.infer_error()?;
Python::with_gil(|py| schema.to_pyarrow(py)) Python::attach(|py| schema.to_pyarrow(py).map(|obj| obj.unbind()))
}) })
} }
+7 -7
View File
@@ -17,20 +17,20 @@ use pyo3::types::PyDict;
/// Internal wrapper around a Python object implementing StorageOptionsProvider /// Internal wrapper around a Python object implementing StorageOptionsProvider
pub struct PyStorageOptionsProvider { pub struct PyStorageOptionsProvider {
/// The Python object implementing fetch_storage_options() /// The Python object implementing fetch_storage_options()
inner: PyObject, inner: Py<PyAny>,
} }
impl Clone for PyStorageOptionsProvider { impl Clone for PyStorageOptionsProvider {
fn clone(&self) -> Self { fn clone(&self) -> Self {
Python::with_gil(|py| Self { Python::attach(|py| Self {
inner: self.inner.clone_ref(py), inner: self.inner.clone_ref(py),
}) })
} }
} }
impl PyStorageOptionsProvider { impl PyStorageOptionsProvider {
pub fn new(obj: PyObject) -> PyResult<Self> { pub fn new(obj: Py<PyAny>) -> PyResult<Self> {
Python::with_gil(|py| { Python::attach(|py| {
// Verify the object has a fetch_storage_options method // Verify the object has a fetch_storage_options method
if !obj.bind(py).hasattr("fetch_storage_options")? { if !obj.bind(py).hasattr("fetch_storage_options")? {
return Err(pyo3::exceptions::PyTypeError::new_err( return Err(pyo3::exceptions::PyTypeError::new_err(
@@ -60,7 +60,7 @@ impl StorageOptionsProvider for PyStorageOptionsProviderWrapper {
let py_provider = self.py_provider.clone(); let py_provider = self.py_provider.clone();
tokio::task::spawn_blocking(move || { tokio::task::spawn_blocking(move || {
Python::with_gil(|py| { Python::attach(|py| {
// Call the Python fetch_storage_options method // Call the Python fetch_storage_options method
let result = py_provider let result = py_provider
.inner .inner
@@ -119,7 +119,7 @@ impl StorageOptionsProvider for PyStorageOptionsProviderWrapper {
} }
fn provider_id(&self) -> String { fn provider_id(&self) -> String {
Python::with_gil(|py| { Python::attach(|py| {
// Call provider_id() method on the Python object // Call provider_id() method on the Python object
let obj = self.py_provider.inner.bind(py); let obj = self.py_provider.inner.bind(py);
obj.call_method0("provider_id") obj.call_method0("provider_id")
@@ -143,7 +143,7 @@ impl std::fmt::Debug for PyStorageOptionsProviderWrapper {
/// This is the main entry point for converting Python StorageOptionsProvider objects /// This is the main entry point for converting Python StorageOptionsProvider objects
/// to Rust trait objects that can be used by the Lance ecosystem. /// to Rust trait objects that can be used by the Lance ecosystem.
pub fn py_object_to_storage_options_provider( pub fn py_object_to_storage_options_provider(
py_obj: PyObject, py_obj: Py<PyAny>,
) -> PyResult<Arc<dyn StorageOptionsProvider>> { ) -> PyResult<Arc<dyn StorageOptionsProvider>> {
let py_provider = PyStorageOptionsProvider::new(py_obj)?; let py_provider = PyStorageOptionsProvider::new(py_obj)?;
Ok(Arc::new(PyStorageOptionsProviderWrapper::new(py_provider))) Ok(Arc::new(PyStorageOptionsProviderWrapper::new(py_provider)))
+5 -10
View File
@@ -287,7 +287,7 @@ impl Table {
let inner = self_.inner_ref()?.clone(); let inner = self_.inner_ref()?.clone();
future_into_py(self_.py(), async move { future_into_py(self_.py(), async move {
let schema = inner.schema().await.infer_error()?; let schema = inner.schema().await.infer_error()?;
Python::with_gil(|py| schema.to_pyarrow(py)) Python::attach(|py| schema.to_pyarrow(py).map(|obj| obj.unbind()))
}) })
} }
@@ -437,7 +437,7 @@ impl Table {
future_into_py(self_.py(), async move { future_into_py(self_.py(), async move {
let stats = inner.index_stats(&index_name).await.infer_error()?; let stats = inner.index_stats(&index_name).await.infer_error()?;
if let Some(stats) = stats { if let Some(stats) = stats {
Python::with_gil(|py| { Python::attach(|py| {
let dict = PyDict::new(py); let dict = PyDict::new(py);
dict.set_item("num_indexed_rows", stats.num_indexed_rows)?; dict.set_item("num_indexed_rows", stats.num_indexed_rows)?;
dict.set_item("num_unindexed_rows", stats.num_unindexed_rows)?; dict.set_item("num_unindexed_rows", stats.num_unindexed_rows)?;
@@ -467,7 +467,7 @@ impl Table {
let inner = self_.inner_ref()?.clone(); let inner = self_.inner_ref()?.clone();
future_into_py(self_.py(), async move { future_into_py(self_.py(), async move {
let stats = inner.stats().await.infer_error()?; let stats = inner.stats().await.infer_error()?;
Python::with_gil(|py| { Python::attach(|py| {
let dict = PyDict::new(py); let dict = PyDict::new(py);
dict.set_item("total_bytes", stats.total_bytes)?; dict.set_item("total_bytes", stats.total_bytes)?;
dict.set_item("num_rows", stats.num_rows)?; dict.set_item("num_rows", stats.num_rows)?;
@@ -497,11 +497,6 @@ impl Table {
}) })
} }
pub fn uri(self_: PyRef<'_, Self>) -> PyResult<Bound<'_, PyAny>> {
let inner = self_.inner_ref()?.clone();
future_into_py(self_.py(), async move { inner.uri().await.infer_error() })
}
pub fn __repr__(&self) -> String { pub fn __repr__(&self) -> String {
match &self.inner { match &self.inner {
None => format!("ClosedTable({})", self.name), None => format!("ClosedTable({})", self.name),
@@ -521,7 +516,7 @@ impl Table {
let inner = self_.inner_ref()?.clone(); let inner = self_.inner_ref()?.clone();
future_into_py(self_.py(), async move { future_into_py(self_.py(), async move {
let versions = inner.list_versions().await.infer_error()?; let versions = inner.list_versions().await.infer_error()?;
let versions_as_dict = Python::with_gil(|py| { let versions_as_dict = Python::attach(|py| {
versions versions
.iter() .iter()
.map(|v| { .map(|v| {
@@ -872,7 +867,7 @@ impl Tags {
let tags = inner.tags().await.infer_error()?; let tags = inner.tags().await.infer_error()?;
let res = tags.list().await.infer_error()?; let res = tags.list().await.infer_error()?;
Python::with_gil(|py| { Python::attach(|py| {
let py_dict = PyDict::new(py); let py_dict = PyDict::new(py);
for (key, contents) in res { for (key, contents) in res {
let value_dict = PyDict::new(py); let value_dict = PyDict::new(py);
+1 -1
View File
@@ -1,6 +1,6 @@
[package] [package]
name = "lancedb" name = "lancedb"
version = "0.24.0" version = "0.23.1"
edition.workspace = true edition.workspace = true
description = "LanceDB: A serverless, low-latency vector database for AI applications" description = "LanceDB: A serverless, low-latency vector database for AI applications"
license.workspace = true license.workspace = true
+60 -128
View File
@@ -36,42 +36,10 @@ use crate::remote::{
}; };
use crate::table::{TableDefinition, WriteOptions}; use crate::table::{TableDefinition, WriteOptions};
use crate::Table; use crate::Table;
use lance::io::ObjectStoreParams;
pub use lance_encoding::version::LanceFileVersion; pub use lance_encoding::version::LanceFileVersion;
#[cfg(feature = "remote")] #[cfg(feature = "remote")]
use lance_io::object_store::StorageOptions; use lance_io::object_store::StorageOptions;
use lance_io::object_store::{StorageOptionsAccessor, StorageOptionsProvider}; use lance_io::object_store::StorageOptionsProvider;
fn merge_storage_options(
store_params: &mut ObjectStoreParams,
pairs: impl IntoIterator<Item = (String, String)>,
) {
let mut options = store_params.storage_options().cloned().unwrap_or_default();
for (key, value) in pairs {
options.insert(key, value);
}
let provider = store_params
.storage_options_accessor
.as_ref()
.and_then(|accessor| accessor.provider().cloned());
let accessor = if let Some(provider) = provider {
StorageOptionsAccessor::with_initial_and_provider(options, provider)
} else {
StorageOptionsAccessor::with_static_options(options)
};
store_params.storage_options_accessor = Some(Arc::new(accessor));
}
fn set_storage_options_provider(
store_params: &mut ObjectStoreParams,
provider: Arc<dyn StorageOptionsProvider>,
) {
let accessor = match store_params.storage_options().cloned() {
Some(options) => StorageOptionsAccessor::with_initial_and_provider(options, provider),
None => StorageOptionsAccessor::with_provider(provider),
};
store_params.storage_options_accessor = Some(Arc::new(accessor));
}
/// A builder for configuring a [`Connection::table_names`] operation /// A builder for configuring a [`Connection::table_names`] operation
pub struct TableNamesBuilder { pub struct TableNamesBuilder {
@@ -278,14 +246,16 @@ impl<const HAS_DATA: bool> CreateTableBuilder<HAS_DATA> {
/// ///
/// See available options at <https://lancedb.com/docs/storage/> /// See available options at <https://lancedb.com/docs/storage/>
pub fn storage_option(mut self, key: impl Into<String>, value: impl Into<String>) -> Self { pub fn storage_option(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
let store_params = self let store_options = self
.request .request
.write_options .write_options
.lance_write_params .lance_write_params
.get_or_insert(Default::default()) .get_or_insert(Default::default())
.store_params .store_params
.get_or_insert(Default::default())
.storage_options
.get_or_insert(Default::default()); .get_or_insert(Default::default());
merge_storage_options(store_params, [(key.into(), value.into())]); store_options.insert(key.into(), value.into());
self self
} }
@@ -299,17 +269,19 @@ impl<const HAS_DATA: bool> CreateTableBuilder<HAS_DATA> {
mut self, mut self,
pairs: impl IntoIterator<Item = (impl Into<String>, impl Into<String>)>, pairs: impl IntoIterator<Item = (impl Into<String>, impl Into<String>)>,
) -> Self { ) -> Self {
let store_params = self let store_options = self
.request .request
.write_options .write_options
.lance_write_params .lance_write_params
.get_or_insert(Default::default()) .get_or_insert(Default::default())
.store_params .store_params
.get_or_insert(Default::default())
.storage_options
.get_or_insert(Default::default()); .get_or_insert(Default::default());
let updates = pairs
.into_iter() for (key, value) in pairs {
.map(|(key, value)| (key.into(), value.into())); store_options.insert(key.into(), value.into());
merge_storage_options(store_params, updates); }
self self
} }
@@ -346,21 +318,23 @@ impl<const HAS_DATA: bool> CreateTableBuilder<HAS_DATA> {
/// This has no effect in LanceDB Cloud. /// This has no effect in LanceDB Cloud.
#[deprecated(since = "0.15.1", note = "Use `database_options` instead")] #[deprecated(since = "0.15.1", note = "Use `database_options` instead")]
pub fn enable_v2_manifest_paths(mut self, use_v2_manifest_paths: bool) -> Self { pub fn enable_v2_manifest_paths(mut self, use_v2_manifest_paths: bool) -> Self {
let store_params = self let storage_options = self
.request .request
.write_options .write_options
.lance_write_params .lance_write_params
.get_or_insert_with(Default::default) .get_or_insert_with(Default::default)
.store_params .store_params
.get_or_insert_with(Default::default)
.storage_options
.get_or_insert_with(Default::default); .get_or_insert_with(Default::default);
let value = if use_v2_manifest_paths {
"true".to_string() storage_options.insert(
} else { OPT_NEW_TABLE_V2_MANIFEST_PATHS.to_string(),
"false".to_string() if use_v2_manifest_paths {
}; "true".to_string()
merge_storage_options( } else {
store_params, "false".to_string()
[(OPT_NEW_TABLE_V2_MANIFEST_PATHS.to_string(), value)], },
); );
self self
} }
@@ -370,19 +344,19 @@ impl<const HAS_DATA: bool> CreateTableBuilder<HAS_DATA> {
/// The default is `LanceFileVersion::Stable`. /// The default is `LanceFileVersion::Stable`.
#[deprecated(since = "0.15.1", note = "Use `database_options` instead")] #[deprecated(since = "0.15.1", note = "Use `database_options` instead")]
pub fn data_storage_version(mut self, data_storage_version: LanceFileVersion) -> Self { pub fn data_storage_version(mut self, data_storage_version: LanceFileVersion) -> Self {
let store_params = self let storage_options = self
.request .request
.write_options .write_options
.lance_write_params .lance_write_params
.get_or_insert_with(Default::default) .get_or_insert_with(Default::default)
.store_params .store_params
.get_or_insert_with(Default::default)
.storage_options
.get_or_insert_with(Default::default); .get_or_insert_with(Default::default);
merge_storage_options(
store_params, storage_options.insert(
[( OPT_NEW_TABLE_STORAGE_VERSION.to_string(),
OPT_NEW_TABLE_STORAGE_VERSION.to_string(), data_storage_version.to_string(),
data_storage_version.to_string(),
)],
); );
self self
} }
@@ -407,14 +381,13 @@ impl<const HAS_DATA: bool> CreateTableBuilder<HAS_DATA> {
/// This allows tables to automatically refresh cloud storage credentials /// This allows tables to automatically refresh cloud storage credentials
/// when they expire, enabling long-running operations on remote storage. /// when they expire, enabling long-running operations on remote storage.
pub fn storage_options_provider(mut self, provider: Arc<dyn StorageOptionsProvider>) -> Self { pub fn storage_options_provider(mut self, provider: Arc<dyn StorageOptionsProvider>) -> Self {
let store_params = self self.request
.request
.write_options .write_options
.lance_write_params .lance_write_params
.get_or_insert(Default::default()) .get_or_insert(Default::default())
.store_params .store_params
.get_or_insert(Default::default()); .get_or_insert(Default::default())
set_storage_options_provider(store_params, provider); .storage_options_provider = Some(provider);
self self
} }
} }
@@ -477,13 +450,15 @@ impl OpenTableBuilder {
/// ///
/// See available options at <https://lancedb.com/docs/storage/> /// See available options at <https://lancedb.com/docs/storage/>
pub fn storage_option(mut self, key: impl Into<String>, value: impl Into<String>) -> Self { pub fn storage_option(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
let store_params = self let storage_options = self
.request .request
.lance_read_params .lance_read_params
.get_or_insert(Default::default()) .get_or_insert(Default::default())
.store_options .store_options
.get_or_insert(Default::default())
.storage_options
.get_or_insert(Default::default()); .get_or_insert(Default::default());
merge_storage_options(store_params, [(key.into(), value.into())]); storage_options.insert(key.into(), value.into());
self self
} }
@@ -497,16 +472,18 @@ impl OpenTableBuilder {
mut self, mut self,
pairs: impl IntoIterator<Item = (impl Into<String>, impl Into<String>)>, pairs: impl IntoIterator<Item = (impl Into<String>, impl Into<String>)>,
) -> Self { ) -> Self {
let store_params = self let storage_options = self
.request .request
.lance_read_params .lance_read_params
.get_or_insert(Default::default()) .get_or_insert(Default::default())
.store_options .store_options
.get_or_insert(Default::default())
.storage_options
.get_or_insert(Default::default()); .get_or_insert(Default::default());
let updates = pairs
.into_iter() for (key, value) in pairs {
.map(|(key, value)| (key.into(), value.into())); storage_options.insert(key.into(), value.into());
merge_storage_options(store_params, updates); }
self self
} }
@@ -530,13 +507,12 @@ impl OpenTableBuilder {
/// This allows tables to automatically refresh cloud storage credentials /// This allows tables to automatically refresh cloud storage credentials
/// when they expire, enabling long-running operations on remote storage. /// when they expire, enabling long-running operations on remote storage.
pub fn storage_options_provider(mut self, provider: Arc<dyn StorageOptionsProvider>) -> Self { pub fn storage_options_provider(mut self, provider: Arc<dyn StorageOptionsProvider>) -> Self {
let store_params = self self.request
.request
.lance_read_params .lance_read_params
.get_or_insert(Default::default()) .get_or_insert(Default::default())
.store_options .store_options
.get_or_insert(Default::default()); .get_or_insert(Default::default())
set_storage_options_provider(store_params, provider); .storage_options_provider = Some(provider);
self self
} }
@@ -892,10 +868,6 @@ pub struct ConnectBuilder {
embedding_registry: Option<Arc<dyn EmbeddingRegistry>>, embedding_registry: Option<Arc<dyn EmbeddingRegistry>>,
} }
#[cfg(feature = "remote")]
const ENV_VARS_TO_STORAGE_OPTS: [(&str, &str); 1] =
[("AZURE_STORAGE_ACCOUNT_NAME", "azure_storage_account_name")];
impl ConnectBuilder { impl ConnectBuilder {
/// Create a new [`ConnectOptions`] with the given database URI. /// Create a new [`ConnectOptions`] with the given database URI.
pub fn new(uri: &str) -> Self { pub fn new(uri: &str) -> Self {
@@ -1079,27 +1051,11 @@ impl ConnectBuilder {
self self
} }
#[cfg(feature = "remote")]
fn apply_env_defaults(
env_var_to_remote_storage_option: &[(&str, &str)],
options: &mut HashMap<String, String>,
) {
for (env_key, opt_key) in env_var_to_remote_storage_option {
if let Ok(env_value) = std::env::var(env_key) {
if !options.contains_key(*opt_key) {
options.insert((*opt_key).to_string(), env_value);
}
}
}
}
#[cfg(feature = "remote")] #[cfg(feature = "remote")]
fn execute_remote(self) -> Result<Connection> { fn execute_remote(self) -> Result<Connection> {
use crate::remote::db::RemoteDatabaseOptions; use crate::remote::db::RemoteDatabaseOptions;
let mut merged_options = self.request.options.clone(); let options = RemoteDatabaseOptions::parse_from_map(&self.request.options)?;
Self::apply_env_defaults(&ENV_VARS_TO_STORAGE_OPTS, &mut merged_options);
let options = RemoteDatabaseOptions::parse_from_map(&merged_options)?;
let region = options.region.ok_or_else(|| Error::InvalidInput { let region = options.region.ok_or_else(|| Error::InvalidInput {
message: "A region is required when connecting to LanceDb Cloud".to_string(), message: "A region is required when connecting to LanceDb Cloud".to_string(),
@@ -1321,6 +1277,8 @@ mod test_utils {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use std::fs::create_dir_all;
use crate::database::listing::{ListingDatabaseOptions, NewTableConfig}; use crate::database::listing::{ListingDatabaseOptions, NewTableConfig};
use crate::query::QueryBase; use crate::query::QueryBase;
use crate::query::{ExecutableQuery, QueryExecutionOptions}; use crate::query::{ExecutableQuery, QueryExecutionOptions};
@@ -1344,23 +1302,6 @@ mod tests {
assert_eq!(tc.connection.uri(), tc.uri); assert_eq!(tc.connection.uri(), tc.uri);
} }
#[cfg(feature = "remote")]
#[test]
fn test_apply_env_defaults() {
let env_key = "TEST_APPLY_ENV_DEFAULTS_ENVIRONMENT_VARIABLE_ENV_KEY";
let env_val = "TEST_APPLY_ENV_DEFAULTS_ENVIRONMENT_VARIABLE_ENV_VAL";
let opts_key = "test_apply_env_defaults_environment_variable_opts_key";
std::env::set_var(env_key, env_val);
let mut options = HashMap::new();
ConnectBuilder::apply_env_defaults(&[(env_key, opts_key)], &mut options);
assert_eq!(Some(&env_val.to_string()), options.get(opts_key));
options.insert(opts_key.to_string(), "EXPLICIT-VALUE".to_string());
ConnectBuilder::apply_env_defaults(&[(env_key, opts_key)], &mut options);
assert_eq!(Some(&"EXPLICIT-VALUE".to_string()), options.get(opts_key));
}
#[cfg(not(windows))] #[cfg(not(windows))]
#[tokio::test] #[tokio::test]
async fn test_connect_relative() { async fn test_connect_relative() {
@@ -1585,27 +1526,18 @@ mod tests {
#[tokio::test] #[tokio::test]
async fn drop_table() { async fn drop_table() {
let tc = new_test_connection().await.unwrap(); let tmp_dir = tempdir().unwrap();
let db = tc.connection;
if tc.is_remote { let uri = tmp_dir.path().to_str().unwrap();
// All the typical endpoints such as s3:///, file-object-store:///, etc. treat drop_table let db = connect(uri).execute().await.unwrap();
// as idempotent.
assert!(db.drop_table("invalid_table", &[]).await.is_ok());
} else {
// The behavior of drop_table when using a file:/// endpoint differs from all other
// object providers, in that it returns an error when deleting a non-existent table.
assert!(matches!(
db.drop_table("invalid_table", &[]).await,
Err(crate::Error::TableNotFound { .. }),
));
}
let schema = Arc::new(Schema::new(vec![Field::new("x", DataType::Int32, false)])); // drop non-exist table
db.create_empty_table("table1", schema.clone()) assert!(matches!(
.execute() db.drop_table("invalid_table", &[]).await,
.await Err(crate::Error::TableNotFound { .. }),
.unwrap(); ));
create_dir_all(tmp_dir.path().join("table1.lance")).unwrap();
db.drop_table("table1", &[]).await.unwrap(); db.drop_table("table1", &[]).await.unwrap();
let tables = db.table_names().execute().await.unwrap(); let tables = db.table_names().execute().await.unwrap();
+38 -87
View File
@@ -12,7 +12,7 @@ use lance::dataset::{builder::DatasetBuilder, ReadParams, WriteMode};
use lance::io::{ObjectStore, ObjectStoreParams, WrappingObjectStore}; use lance::io::{ObjectStore, ObjectStoreParams, WrappingObjectStore};
use lance_datafusion::utils::StreamingWriteSource; use lance_datafusion::utils::StreamingWriteSource;
use lance_encoding::version::LanceFileVersion; use lance_encoding::version::LanceFileVersion;
use lance_io::object_store::{StorageOptionsAccessor, StorageOptionsProvider}; use lance_io::object_store::StorageOptionsProvider;
use lance_table::io::commit::commit_handler_from_url; use lance_table::io::commit::commit_handler_from_url;
use object_store::local::LocalFileSystem; use object_store::local::LocalFileSystem;
use snafu::ResultExt; use snafu::ResultExt;
@@ -356,13 +356,7 @@ impl ListingDatabase {
.clone() .clone()
.unwrap_or_else(|| Arc::new(lance::session::Session::default())); .unwrap_or_else(|| Arc::new(lance::session::Session::default()));
let os_params = ObjectStoreParams { let os_params = ObjectStoreParams {
storage_options_accessor: if options.storage_options.is_empty() { storage_options: Some(options.storage_options.clone()),
None
} else {
Some(Arc::new(StorageOptionsAccessor::with_static_options(
options.storage_options.clone(),
)))
},
..Default::default() ..Default::default()
}; };
let (object_store, base_path) = ObjectStore::from_uri_and_params( let (object_store, base_path) = ObjectStore::from_uri_and_params(
@@ -469,20 +463,9 @@ impl ListingDatabase {
validate_table_name(name)?; validate_table_name(name)?;
let mut uri = self.uri.clone(); let mut uri = self.uri.clone();
// If the URI does not end with a path separator, add one // If the URI does not end with a slash, add one
// Use forward slash for URIs (http://, s3://, gs://, file://, etc.) if !uri.ends_with('/') {
// Use platform-specific separator for local paths without scheme uri.push('/');
let has_scheme = uri.contains("://");
let ends_with_separator = uri.ends_with('/') || uri.ends_with('\\');
if !ends_with_separator {
if has_scheme {
// URIs always use forward slash
uri.push('/');
} else {
// Local path without scheme - use platform separator
uri.push(std::path::MAIN_SEPARATOR);
}
} }
// Append the table name with the lance file extension // Append the table name with the lance file extension
uri.push_str(&format!("{}.{}", name, LANCE_FILE_EXTENSION)); uri.push_str(&format!("{}.{}", name, LANCE_FILE_EXTENSION));
@@ -498,13 +481,7 @@ impl ListingDatabase {
async fn drop_tables(&self, names: Vec<String>) -> Result<()> { async fn drop_tables(&self, names: Vec<String>) -> Result<()> {
let object_store_params = ObjectStoreParams { let object_store_params = ObjectStoreParams {
storage_options_accessor: if self.storage_options.is_empty() { storage_options: Some(self.storage_options.clone()),
None
} else {
Some(Arc::new(StorageOptionsAccessor::with_static_options(
self.storage_options.clone(),
)))
},
..Default::default() ..Default::default()
}; };
let mut uri = self.uri.clone(); let mut uri = self.uri.clone();
@@ -553,7 +530,7 @@ impl ListingDatabase {
.lance_write_params .lance_write_params
.as_ref() .as_ref()
.and_then(|p| p.store_params.as_ref()) .and_then(|p| p.store_params.as_ref())
.and_then(|sp| sp.storage_options()); .and_then(|sp| sp.storage_options.as_ref());
let storage_version_override = storage_options let storage_version_override = storage_options
.and_then(|opts| opts.get(OPT_NEW_TABLE_STORAGE_VERSION)) .and_then(|opts| opts.get(OPT_NEW_TABLE_STORAGE_VERSION))
@@ -604,20 +581,21 @@ impl ListingDatabase {
// will cause a new connection to be created, and that connection will // will cause a new connection to be created, and that connection will
// be dropped from the cache when python GCs the table object, which // be dropped from the cache when python GCs the table object, which
// confounds reuse across tables. // confounds reuse across tables.
if !self.storage_options.is_empty() || self.storage_options_provider.is_some() { if !self.storage_options.is_empty() {
let store_params = write_params let storage_options = write_params
.store_params .store_params
.get_or_insert_with(Default::default)
.storage_options
.get_or_insert_with(Default::default); .get_or_insert_with(Default::default);
let mut storage_options = store_params.storage_options().cloned().unwrap_or_default(); self.inherit_storage_options(storage_options);
if !self.storage_options.is_empty() { }
self.inherit_storage_options(&mut storage_options);
} // Set storage options provider if available
let accessor = if let Some(ref provider) = self.storage_options_provider { if self.storage_options_provider.is_some() {
StorageOptionsAccessor::with_initial_and_provider(storage_options, provider.clone()) write_params
} else { .store_params
StorageOptionsAccessor::with_static_options(storage_options) .get_or_insert_with(Default::default)
}; .storage_options_provider = self.storage_options_provider.clone();
store_params.storage_options_accessor = Some(Arc::new(accessor));
} }
write_params.data_storage_version = self write_params.data_storage_version = self
@@ -903,13 +881,7 @@ impl Database for ListingDatabase {
validate_table_name(&request.target_table_name)?; validate_table_name(&request.target_table_name)?;
let storage_params = ObjectStoreParams { let storage_params = ObjectStoreParams {
storage_options_accessor: if self.storage_options.is_empty() { storage_options: Some(self.storage_options.clone()),
None
} else {
Some(Arc::new(StorageOptionsAccessor::with_static_options(
self.storage_options.clone(),
)))
},
..Default::default() ..Default::default()
}; };
let read_params = ReadParams { let read_params = ReadParams {
@@ -973,28 +945,25 @@ impl Database for ListingDatabase {
// will cause a new connection to be created, and that connection will // will cause a new connection to be created, and that connection will
// be dropped from the cache when python GCs the table object, which // be dropped from the cache when python GCs the table object, which
// confounds reuse across tables. // confounds reuse across tables.
if !self.storage_options.is_empty() || self.storage_options_provider.is_some() { if !self.storage_options.is_empty() {
let store_params = request let storage_options = request
.lance_read_params .lance_read_params
.get_or_insert_with(Default::default) .get_or_insert_with(Default::default)
.store_options .store_options
.get_or_insert_with(Default::default)
.storage_options
.get_or_insert_with(Default::default); .get_or_insert_with(Default::default);
let mut storage_options = store_params.storage_options().cloned().unwrap_or_default(); self.inherit_storage_options(storage_options);
if !self.storage_options.is_empty() { }
self.inherit_storage_options(&mut storage_options);
} // Set storage options provider if available
// Preserve request-level provider if no connection-level provider exists if self.storage_options_provider.is_some() {
let request_provider = store_params request
.storage_options_accessor .lance_read_params
.as_ref() .get_or_insert_with(Default::default)
.and_then(|a| a.provider().cloned()); .store_options
let provider = self.storage_options_provider.clone().or(request_provider); .get_or_insert_with(Default::default)
let accessor = if let Some(provider) = provider { .storage_options_provider = self.storage_options_provider.clone();
StorageOptionsAccessor::with_initial_and_provider(storage_options, provider)
} else {
StorageOptionsAccessor::with_static_options(storage_options)
};
store_params.storage_options_accessor = Some(Arc::new(accessor));
} }
// Some ReadParams are exposed in the OpenTableBuilder, but we also // Some ReadParams are exposed in the OpenTableBuilder, but we also
@@ -1102,7 +1071,6 @@ mod tests {
use crate::table::{Table, TableDefinition}; use crate::table::{Table, TableDefinition};
use arrow_array::{Int32Array, RecordBatch, StringArray}; use arrow_array::{Int32Array, RecordBatch, StringArray};
use arrow_schema::{DataType, Field, Schema}; use arrow_schema::{DataType, Field, Schema};
use std::path::PathBuf;
use tempfile::tempdir; use tempfile::tempdir;
async fn setup_database() -> (tempfile::TempDir, ListingDatabase) { async fn setup_database() -> (tempfile::TempDir, ListingDatabase) {
@@ -1901,9 +1869,7 @@ mod tests {
let write_options = WriteOptions { let write_options = WriteOptions {
lance_write_params: Some(lance::dataset::WriteParams { lance_write_params: Some(lance::dataset::WriteParams {
store_params: Some(lance::io::ObjectStoreParams { store_params: Some(lance::io::ObjectStoreParams {
storage_options_accessor: Some(Arc::new( storage_options: Some(storage_options),
StorageOptionsAccessor::with_static_options(storage_options),
)),
..Default::default() ..Default::default()
}), }),
..Default::default() ..Default::default()
@@ -1977,9 +1943,7 @@ mod tests {
let write_options = WriteOptions { let write_options = WriteOptions {
lance_write_params: Some(lance::dataset::WriteParams { lance_write_params: Some(lance::dataset::WriteParams {
store_params: Some(lance::io::ObjectStoreParams { store_params: Some(lance::io::ObjectStoreParams {
storage_options_accessor: Some(Arc::new( storage_options: Some(storage_options),
StorageOptionsAccessor::with_static_options(storage_options),
)),
..Default::default() ..Default::default()
}), }),
..Default::default() ..Default::default()
@@ -2082,19 +2046,6 @@ mod tests {
assert_eq!(db_options.new_table_config.enable_stable_row_ids, None); assert_eq!(db_options.new_table_config.enable_stable_row_ids, None);
} }
#[tokio::test]
async fn test_table_uri() {
let (_tempdir, db) = setup_database().await;
let mut pb = PathBuf::new();
pb.push(db.uri.clone());
pb.push("test.lance");
let expected = pb.to_str().unwrap();
let uri = db.table_uri("test").ok().unwrap();
assert_eq!(uri, expected);
}
#[tokio::test] #[tokio::test]
async fn test_namespace_client() { async fn test_namespace_client() {
let (_tempdir, db) = setup_database().await; let (_tempdir, db) = setup_database().await;
+65 -59
View File
@@ -9,15 +9,14 @@ use std::sync::Arc;
use async_trait::async_trait; use async_trait::async_trait;
use lance_namespace::{ use lance_namespace::{
models::{ models::{
CreateEmptyTableRequest, CreateNamespaceRequest, CreateNamespaceResponse, CreateNamespaceRequest, CreateNamespaceResponse, DeclareTableRequest,
DeclareTableRequest, DescribeNamespaceRequest, DescribeNamespaceResponse, DescribeNamespaceRequest, DescribeNamespaceResponse, DescribeTableRequest,
DescribeTableRequest, DropNamespaceRequest, DropNamespaceResponse, DropTableRequest, DropNamespaceRequest, DropNamespaceResponse, DropTableRequest, ListNamespacesRequest,
ListNamespacesRequest, ListNamespacesResponse, ListTablesRequest, ListTablesResponse, ListNamespacesResponse, ListTablesRequest, ListTablesResponse,
}, },
LanceNamespace, LanceNamespace,
}; };
use lance_namespace_impls::ConnectBuilder; use lance_namespace_impls::ConnectBuilder;
use log::warn;
use crate::database::ReadConsistency; use crate::database::ReadConsistency;
use crate::error::{Error, Result}; use crate::error::{Error, Result};
@@ -135,10 +134,11 @@ impl Database for LanceNamespaceDatabase {
async fn table_names(&self, request: TableNamesRequest) -> Result<Vec<String>> { async fn table_names(&self, request: TableNamesRequest) -> Result<Vec<String>> {
let ns_request = ListTablesRequest { let ns_request = ListTablesRequest {
identity: None,
context: None,
id: Some(request.namespace), id: Some(request.namespace),
page_token: request.start_after, page_token: request.start_after,
limit: request.limit.map(|l| l as i32), limit: request.limit.map(|l| l as i32),
..Default::default()
}; };
let response = self.namespace.list_tables(ns_request).await?; let response = self.namespace.list_tables(ns_request).await?;
@@ -154,8 +154,13 @@ impl Database for LanceNamespaceDatabase {
let mut table_id = request.namespace.clone(); let mut table_id = request.namespace.clone();
table_id.push(request.name.clone()); table_id.push(request.name.clone());
let describe_request = DescribeTableRequest { let describe_request = DescribeTableRequest {
identity: None,
context: None,
id: Some(table_id.clone()), id: Some(table_id.clone()),
..Default::default() version: None,
with_table_uri: None,
load_detailed_metadata: None,
vend_credentials: None,
}; };
let describe_result = self.namespace.describe_table(describe_request).await; let describe_result = self.namespace.describe_table(describe_request).await;
@@ -172,8 +177,9 @@ impl Database for LanceNamespaceDatabase {
if describe_result.is_ok() { if describe_result.is_ok() {
// Drop the existing table - must succeed // Drop the existing table - must succeed
let drop_request = DropTableRequest { let drop_request = DropTableRequest {
identity: None,
context: None,
id: Some(table_id.clone()), id: Some(table_id.clone()),
..Default::default()
}; };
self.namespace self.namespace
.drop_table(drop_request) .drop_table(drop_request)
@@ -205,53 +211,31 @@ impl Database for LanceNamespaceDatabase {
let mut table_id = request.namespace.clone(); let mut table_id = request.namespace.clone();
table_id.push(request.name.clone()); table_id.push(request.name.clone());
// Try declare_table first, falling back to create_empty_table for backwards let create_empty_request = DeclareTableRequest {
// compatibility with older namespace clients that don't support declare_table identity: None,
let declare_request = DeclareTableRequest { context: None,
id: Some(table_id.clone()), id: Some(table_id.clone()),
..Default::default() location: None,
vend_credentials: if self.storage_options.is_empty() {
None
} else {
Some(true)
},
}; };
let location = match self.namespace.declare_table(declare_request).await { let create_empty_response = self
Ok(response) => response.location.ok_or_else(|| Error::Runtime { .namespace
message: "Table location is missing from declare_table response".to_string(), .declare_table(create_empty_request)
})?, .await
Err(e) => { .map_err(|e| Error::Runtime {
// Check if the error is "not supported" and try create_empty_table as fallback message: format!("Failed to declare table: {}", e),
let err_str = e.to_string().to_lowercase(); })?;
if err_str.contains("not supported") || err_str.contains("not implemented") {
warn!(
"declare_table is not supported by the namespace client, \
falling back to deprecated create_empty_table. \
create_empty_table is deprecated and will be removed in Lance 3.0.0. \
Please upgrade your namespace client to support declare_table."
);
#[allow(deprecated)]
let create_empty_request = CreateEmptyTableRequest {
id: Some(table_id.clone()),
..Default::default()
};
#[allow(deprecated)] let location = create_empty_response
let create_response = self .location
.namespace .ok_or_else(|| Error::Runtime {
.create_empty_table(create_empty_request) message: "Table location is missing from create_empty_table response".to_string(),
.await })?;
.map_err(|e| Error::Runtime {
message: format!("Failed to create empty table: {}", e),
})?;
create_response.location.ok_or_else(|| Error::Runtime {
message: "Table location is missing from create_empty_table response"
.to_string(),
})?
} else {
return Err(Error::Runtime {
message: format!("Failed to declare table: {}", e),
});
}
}
};
let native_table = NativeTable::create_from_namespace( let native_table = NativeTable::create_from_namespace(
self.namespace.clone(), self.namespace.clone(),
@@ -309,8 +293,9 @@ impl Database for LanceNamespaceDatabase {
table_id.push(name.to_string()); table_id.push(name.to_string());
let drop_request = DropTableRequest { let drop_request = DropTableRequest {
identity: None,
context: None,
id: Some(table_id), id: Some(table_id),
..Default::default()
}; };
self.namespace self.namespace
.drop_table(drop_request) .drop_table(drop_request)
@@ -465,8 +450,11 @@ mod tests {
// Create a child namespace first // Create a child namespace first
conn.create_namespace(CreateNamespaceRequest { conn.create_namespace(CreateNamespaceRequest {
identity: None,
context: None,
id: Some(vec!["test_ns".into()]), id: Some(vec!["test_ns".into()]),
..Default::default() mode: None,
properties: None,
}) })
.await .await
.expect("Failed to create namespace"); .expect("Failed to create namespace");
@@ -525,8 +513,11 @@ mod tests {
// Create a child namespace first // Create a child namespace first
conn.create_namespace(CreateNamespaceRequest { conn.create_namespace(CreateNamespaceRequest {
identity: None,
context: None,
id: Some(vec!["test_ns".into()]), id: Some(vec!["test_ns".into()]),
..Default::default() mode: None,
properties: None,
}) })
.await .await
.expect("Failed to create namespace"); .expect("Failed to create namespace");
@@ -588,8 +579,11 @@ mod tests {
// Create a child namespace first // Create a child namespace first
conn.create_namespace(CreateNamespaceRequest { conn.create_namespace(CreateNamespaceRequest {
identity: None,
context: None,
id: Some(vec!["test_ns".into()]), id: Some(vec!["test_ns".into()]),
..Default::default() mode: None,
properties: None,
}) })
.await .await
.expect("Failed to create namespace"); .expect("Failed to create namespace");
@@ -671,8 +665,11 @@ mod tests {
// Create a child namespace first // Create a child namespace first
conn.create_namespace(CreateNamespaceRequest { conn.create_namespace(CreateNamespaceRequest {
identity: None,
context: None,
id: Some(vec!["test_ns".into()]), id: Some(vec!["test_ns".into()]),
..Default::default() mode: None,
properties: None,
}) })
.await .await
.expect("Failed to create namespace"); .expect("Failed to create namespace");
@@ -726,8 +723,11 @@ mod tests {
// Create a child namespace first // Create a child namespace first
conn.create_namespace(CreateNamespaceRequest { conn.create_namespace(CreateNamespaceRequest {
identity: None,
context: None,
id: Some(vec!["test_ns".into()]), id: Some(vec!["test_ns".into()]),
..Default::default() mode: None,
properties: None,
}) })
.await .await
.expect("Failed to create namespace"); .expect("Failed to create namespace");
@@ -806,8 +806,11 @@ mod tests {
// Create a child namespace first // Create a child namespace first
conn.create_namespace(CreateNamespaceRequest { conn.create_namespace(CreateNamespaceRequest {
identity: None,
context: None,
id: Some(vec!["test_ns".into()]), id: Some(vec!["test_ns".into()]),
..Default::default() mode: None,
properties: None,
}) })
.await .await
.expect("Failed to create namespace"); .expect("Failed to create namespace");
@@ -839,8 +842,11 @@ mod tests {
// Create a child namespace first // Create a child namespace first
conn.create_namespace(CreateNamespaceRequest { conn.create_namespace(CreateNamespaceRequest {
identity: None,
context: None,
id: Some(vec!["test_ns".into()]), id: Some(vec!["test_ns".into()]),
..Default::default() mode: None,
properties: None,
}) })
.await .await
.expect("Failed to create namespace"); .expect("Failed to create namespace");
@@ -171,7 +171,7 @@ impl Shuffler {
// This is kind of an annoying limitation but if we allow runt clumps from batches then // This is kind of an annoying limitation but if we allow runt clumps from batches then
// clumps will get unaligned and we will mess up the clumps when we do the in-memory // clumps will get unaligned and we will mess up the clumps when we do the in-memory
// shuffle step. If this is a problem we can probably figure out a better way to do this. // shuffle step. If this is a problem we can probably figure out a better way to do this.
if !is_last && !(batch.num_rows() as u64).is_multiple_of(clump_size) { if !is_last && batch.num_rows() as u64 % clump_size != 0 {
return Err(Error::Runtime { return Err(Error::Runtime {
message: format!( message: format!(
"Expected batch size ({}) to be divisible by clump size ({})", "Expected batch size ({}) to be divisible by clump size ({})",
@@ -1,9 +1,12 @@
// SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: Apache-2.0
// SPDX-FileCopyrightText: Copyright The LanceDB Authors // SPDX-FileCopyrightText: Copyright The LanceDB Authors
use std::sync::{ use std::{
atomic::{AtomicBool, AtomicU64, AtomicUsize, Ordering}, iter,
Arc, sync::{
atomic::{AtomicBool, AtomicU64, AtomicUsize, Ordering},
Arc,
},
}; };
use arrow_array::{Array, BooleanArray, RecordBatch, UInt64Array}; use arrow_array::{Array, BooleanArray, RecordBatch, UInt64Array};
@@ -155,7 +158,7 @@ impl Splitter {
remaining_in_split remaining_in_split
}; };
split_ids.extend(std::iter::repeat_n(split_id as u64, rows_to_add as usize)); split_ids.extend(iter::repeat(split_id as u64).take(rows_to_add as usize));
if done { if done {
// Quit early if we've run out of splits // Quit early if we've run out of splits
break; break;
@@ -659,7 +662,7 @@ mod tests {
assert_eq!(split_batch.num_rows(), total_split_sizes as usize); assert_eq!(split_batch.num_rows(), total_split_sizes as usize);
let mut expected = Vec::with_capacity(total_split_sizes as usize); let mut expected = Vec::with_capacity(total_split_sizes as usize);
for (i, size) in expected_split_sizes.iter().enumerate() { for (i, size) in expected_split_sizes.iter().enumerate() {
expected.extend(std::iter::repeat_n(i as u64, *size as usize)); expected.extend(iter::repeat(i as u64).take(*size as usize));
} }
let expected = Arc::new(UInt64Array::from(expected)) as Arc<dyn Array>; let expected = Arc::new(UInt64Array::from(expected)) as Arc<dyn Array>;
+2 -2
View File
@@ -297,10 +297,10 @@ impl IvfPqIndexBuilder {
} }
pub(crate) fn suggested_num_sub_vectors(dim: u32) -> u32 { pub(crate) fn suggested_num_sub_vectors(dim: u32) -> u32 {
if dim.is_multiple_of(16) { if dim % 16 == 0 {
// Should be more aggressive than this default. // Should be more aggressive than this default.
dim / 16 dim / 16
} else if dim.is_multiple_of(8) { } else if dim % 8 == 0 {
dim / 8 dim / 8
} else { } else {
log::warn!( log::warn!(
+7 -5
View File
@@ -51,15 +51,17 @@
//! - `s3://bucket/path/to/database` or `gs://bucket/path/to/database` - database on cloud object store //! - `s3://bucket/path/to/database` or `gs://bucket/path/to/database` - database on cloud object store
//! - `db://dbname` - Lance Cloud //! - `db://dbname` - Lance Cloud
//! //!
//! You can also use [`ConnectBuilder`] to configure the connection to the database. //! You can also use [`ConnectOptions`] to configure the connection to the database.
//! //!
//! ```rust //! ```rust
//! use object_store::aws::AwsCredential;
//! # tokio::runtime::Runtime::new().unwrap().block_on(async { //! # tokio::runtime::Runtime::new().unwrap().block_on(async {
//! let db = lancedb::connect("data/sample-lancedb") //! let db = lancedb::connect("data/sample-lancedb")
//! .storage_options([ //! .aws_creds(AwsCredential {
//! ("aws_access_key_id", "some_key"), //! key_id: "some_key".to_string(),
//! ("aws_secret_access_key", "some_secret"), //! secret_key: "some_secret".to_string(),
//! ]) //! token: None,
//! })
//! .execute() //! .execute()
//! .await //! .await
//! .unwrap(); //! .unwrap();
+20 -5
View File
@@ -1717,8 +1717,11 @@ mod tests {
// Create a child namespace first // Create a child namespace first
let namespace = vec!["test_ns".to_string()]; let namespace = vec!["test_ns".to_string()];
conn.create_namespace(CreateNamespaceRequest { conn.create_namespace(CreateNamespaceRequest {
identity: None,
context: None,
id: Some(namespace.clone()), id: Some(namespace.clone()),
..Default::default() mode: None,
properties: None,
}) })
.await .await
.expect("Failed to create namespace"); .expect("Failed to create namespace");
@@ -1742,8 +1745,11 @@ mod tests {
// List tables in the child namespace // List tables in the child namespace
let list_response = conn let list_response = conn
.list_tables(ListTablesRequest { .list_tables(ListTablesRequest {
identity: None,
context: None,
id: Some(namespace.clone()), id: Some(namespace.clone()),
..Default::default() page_token: None,
limit: None,
}) })
.await .await
.expect("Failed to list tables"); .expect("Failed to list tables");
@@ -1753,8 +1759,11 @@ mod tests {
let namespace_client = conn.namespace_client().await.unwrap(); let namespace_client = conn.namespace_client().await.unwrap();
let list_response = namespace_client let list_response = namespace_client
.list_tables(ListTablesRequest { .list_tables(ListTablesRequest {
identity: None,
context: None,
id: Some(namespace.clone()), id: Some(namespace.clone()),
..Default::default() page_token: None,
limit: None,
}) })
.await .await
.unwrap(); .unwrap();
@@ -1793,8 +1802,11 @@ mod tests {
// Create a child namespace first // Create a child namespace first
let namespace = vec!["multi_table_ns".to_string()]; let namespace = vec!["multi_table_ns".to_string()];
conn.create_namespace(CreateNamespaceRequest { conn.create_namespace(CreateNamespaceRequest {
identity: None,
context: None,
id: Some(namespace.clone()), id: Some(namespace.clone()),
..Default::default() mode: None,
properties: None,
}) })
.await .await
.expect("Failed to create namespace"); .expect("Failed to create namespace");
@@ -1818,8 +1830,11 @@ mod tests {
// List tables in the child namespace // List tables in the child namespace
let list_response = conn let list_response = conn
.list_tables(ListTablesRequest { .list_tables(ListTablesRequest {
identity: None,
context: None,
id: Some(namespace.clone()), id: Some(namespace.clone()),
..Default::default() page_token: None,
limit: None,
}) })
.await .await
.unwrap(); .unwrap();
+2 -91
View File
@@ -204,7 +204,6 @@ pub struct RemoteTable<S: HttpSend = Sender> {
server_version: ServerVersion, server_version: ServerVersion,
version: RwLock<Option<u64>>, version: RwLock<Option<u64>>,
location: RwLock<Option<String>>,
} }
impl<S: HttpSend> RemoteTable<S> { impl<S: HttpSend> RemoteTable<S> {
@@ -222,7 +221,6 @@ impl<S: HttpSend> RemoteTable<S> {
identifier, identifier,
server_version, server_version,
version: RwLock::new(None), version: RwLock::new(None),
location: RwLock::new(None),
} }
} }
@@ -641,7 +639,6 @@ impl<S: HttpSend> RemoteTable<S> {
struct TableDescription { struct TableDescription {
version: u64, version: u64,
schema: JsonSchema, schema: JsonSchema,
location: Option<String>,
} }
impl<S: HttpSend> std::fmt::Display for RemoteTable<S> { impl<S: HttpSend> std::fmt::Display for RemoteTable<S> {
@@ -670,7 +667,6 @@ mod test_utils {
identifier: name, identifier: name,
server_version: version.map(ServerVersion).unwrap_or_default(), server_version: version.map(ServerVersion).unwrap_or_default(),
version: RwLock::new(None), version: RwLock::new(None),
location: RwLock::new(None),
} }
} }
} }
@@ -1465,28 +1461,8 @@ impl<S: HttpSend> BaseTable for RemoteTable<S> {
message: "table_definition is not supported on LanceDB cloud.".into(), message: "table_definition is not supported on LanceDB cloud.".into(),
}) })
} }
async fn uri(&self) -> Result<String> { fn dataset_uri(&self) -> &str {
// Check if we already have the location cached "NOT_SUPPORTED"
{
let location = self.location.read().await;
if let Some(ref loc) = *location {
return Ok(loc.clone());
}
}
// Fetch from server via describe
let description = self.describe().await?;
let location = description.location.ok_or_else(|| Error::NotSupported {
message: "Table URI not supported by the server".into(),
})?;
// Cache the location for future use
{
let mut cached_location = self.location.write().await;
*cached_location = Some(location.clone());
}
Ok(location)
} }
async fn storage_options(&self) -> Option<HashMap<String, String>> { async fn storage_options(&self) -> Option<HashMap<String, String>> {
@@ -3356,69 +3332,4 @@ mod tests {
let result = table.drop_columns(&["old_col1", "old_col2"]).await.unwrap(); let result = table.drop_columns(&["old_col1", "old_col2"]).await.unwrap();
assert_eq!(result.version, 5); assert_eq!(result.version, 5);
} }
#[tokio::test]
async fn test_uri() {
let table = Table::new_with_handler("my_table", |request| {
assert_eq!(request.method(), "POST");
assert_eq!(request.url().path(), "/v1/table/my_table/describe/");
http::Response::builder()
.status(200)
.body(r#"{"version": 1, "schema": {"fields": []}, "location": "s3://bucket/path/to/table"}"#)
.unwrap()
});
let uri = table.uri().await.unwrap();
assert_eq!(uri, "s3://bucket/path/to/table");
}
#[tokio::test]
async fn test_uri_missing_location() {
let table = Table::new_with_handler("my_table", |request| {
assert_eq!(request.method(), "POST");
assert_eq!(request.url().path(), "/v1/table/my_table/describe/");
// Server returns response without location field
http::Response::builder()
.status(200)
.body(r#"{"version": 1, "schema": {"fields": []}}"#)
.unwrap()
});
let result = table.uri().await;
assert!(result.is_err());
assert!(matches!(&result, Err(Error::NotSupported { .. })));
}
#[tokio::test]
async fn test_uri_caching() {
use std::sync::atomic::{AtomicUsize, Ordering};
use std::sync::Arc;
let call_count = Arc::new(AtomicUsize::new(0));
let call_count_clone = call_count.clone();
let table = Table::new_with_handler("my_table", move |request| {
assert_eq!(request.url().path(), "/v1/table/my_table/describe/");
call_count_clone.fetch_add(1, Ordering::SeqCst);
http::Response::builder()
.status(200)
.body(
r#"{"version": 1, "schema": {"fields": []}, "location": "gs://bucket/table"}"#,
)
.unwrap()
});
// First call should fetch from server
let uri1 = table.uri().await.unwrap();
assert_eq!(uri1, "gs://bucket/table");
assert_eq!(call_count.load(Ordering::SeqCst), 1);
// Second call should use cached value
let uri2 = table.uri().await.unwrap();
assert_eq!(uri2, "gs://bucket/table");
assert_eq!(call_count.load(Ordering::SeqCst), 1); // Still 1, no new call
}
} }
+46 -35
View File
@@ -40,7 +40,7 @@ use lance_index::vector::pq::PQBuildParams;
use lance_index::vector::sq::builder::SQBuildParams; use lance_index::vector::sq::builder::SQBuildParams;
use lance_index::DatasetIndexExt; use lance_index::DatasetIndexExt;
use lance_index::IndexType; use lance_index::IndexType;
use lance_io::object_store::{LanceNamespaceStorageOptionsProvider, StorageOptionsAccessor}; use lance_io::object_store::LanceNamespaceStorageOptionsProvider;
use lance_namespace::models::{ use lance_namespace::models::{
QueryTableRequest as NsQueryTableRequest, QueryTableRequestColumns, QueryTableRequest as NsQueryTableRequest, QueryTableRequestColumns,
QueryTableRequestFullTextQuery, QueryTableRequestVector, StringFtsQuery, QueryTableRequestFullTextQuery, QueryTableRequestVector, StringFtsQuery,
@@ -608,8 +608,8 @@ pub trait BaseTable: std::fmt::Display + std::fmt::Debug + Send + Sync {
async fn list_versions(&self) -> Result<Vec<Version>>; async fn list_versions(&self) -> Result<Vec<Version>>;
/// Get the table definition. /// Get the table definition.
async fn table_definition(&self) -> Result<TableDefinition>; async fn table_definition(&self) -> Result<TableDefinition>;
/// Get the table URI (storage location) /// Get the table URI
async fn uri(&self) -> Result<String>; fn dataset_uri(&self) -> &str;
/// Get the storage options used when opening this table, if any. /// Get the storage options used when opening this table, if any.
async fn storage_options(&self) -> Option<HashMap<String, String>>; async fn storage_options(&self) -> Option<HashMap<String, String>>;
/// Poll until the columns are fully indexed. Will return Error::Timeout if the columns /// Poll until the columns are fully indexed. Will return Error::Timeout if the columns
@@ -1317,12 +1317,11 @@ impl Table {
self.inner.list_indices().await self.inner.list_indices().await
} }
/// Get the table URI (storage location) /// Get the underlying dataset URI
/// ///
/// Returns the full storage location of the table (e.g., S3/GCS path). /// Warning: This is an internal API and the return value is subject to change.
/// For remote tables, this fetches the location from the server via describe. pub fn dataset_uri(&self) -> &str {
pub async fn uri(&self) -> Result<String> { self.inner.dataset_uri()
self.inner.uri().await
} }
/// Get the storage options used when opening this table, if any. /// Get the storage options used when opening this table, if any.
@@ -1425,7 +1424,9 @@ impl Table {
}) })
.collect::<Vec<_>>(); .collect::<Vec<_>>();
let unioned = Arc::new(UnionExec::new(projected_plans)); let unioned = UnionExec::try_new(projected_plans).map_err(|e| Error::Runtime {
message: format!("Failed to union query plans: {}", e),
})?;
// We require 1 partition in the final output // We require 1 partition in the final output
let repartitioned = RepartitionExec::try_new( let repartitioned = RepartitionExec::try_new(
unioned, unioned,
@@ -1666,14 +1667,18 @@ impl NativeTable {
// Use DatasetBuilder::from_namespace which automatically fetches location // Use DatasetBuilder::from_namespace which automatically fetches location
// and storage options from the namespace // and storage options from the namespace
let builder = DatasetBuilder::from_namespace(namespace_client.clone(), table_id) let builder = DatasetBuilder::from_namespace(
.await namespace_client.clone(),
.map_err(|e| match e { table_id,
lance::Error::Namespace { source, .. } => Error::Runtime { false, // Don't ignore namespace storage options
message: format!("Failed to get table info from namespace: {:?}", source), )
}, .await
source => Error::Lance { source }, .map_err(|e| match e {
})?; lance::Error::Namespace { source, .. } => Error::Runtime {
message: format!("Failed to get table info from namespace: {:?}", source),
},
source => Error::Lance { source },
})?;
let dataset = builder let dataset = builder
.with_read_params(params) .with_read_params(params)
@@ -1877,13 +1882,7 @@ impl NativeTable {
let store_params = params let store_params = params
.store_params .store_params
.get_or_insert_with(ObjectStoreParams::default); .get_or_insert_with(ObjectStoreParams::default);
let accessor = match store_params.storage_options().cloned() { store_params.storage_options_provider = Some(storage_options_provider);
Some(options) => {
StorageOptionsAccessor::with_initial_and_provider(options, storage_options_provider)
}
None => StorageOptionsAccessor::with_provider(storage_options_provider),
};
store_params.storage_options_accessor = Some(Arc::new(accessor));
// Patch the params if we have a write store wrapper // Patch the params if we have a write store wrapper
let params = match write_store_wrapper.clone() { let params = match write_store_wrapper.clone() {
@@ -2059,7 +2058,7 @@ impl NativeTable {
return provided; return provided;
} }
let suggested = suggested_num_sub_vectors(dim); let suggested = suggested_num_sub_vectors(dim);
if num_bits.is_some_and(|num_bits| num_bits == 4) && !suggested.is_multiple_of(2) { if num_bits.is_some_and(|num_bits| num_bits == 4) && suggested % 2 != 0 {
// num_sub_vectors must be even when 4 bits are used // num_sub_vectors must be even when 4 bits are used
suggested + 1 suggested + 1
} else { } else {
@@ -2389,6 +2388,8 @@ impl NativeTable {
}); });
Ok(NsQueryTableRequest { Ok(NsQueryTableRequest {
identity: None,
context: None,
id: None, // Will be set in namespace_query id: None, // Will be set in namespace_query
k: vq.base.limit.unwrap_or(10) as i32, k: vq.base.limit.unwrap_or(10) as i32,
vector: Box::new(vector), vector: Box::new(vector),
@@ -2407,7 +2408,7 @@ impl NativeTable {
with_row_id: Some(vq.base.with_row_id), with_row_id: Some(vq.base.with_row_id),
bypass_vector_index: Some(!vq.use_index), bypass_vector_index: Some(!vq.use_index),
full_text_query, full_text_query,
..Default::default() version: None,
}) })
} }
AnyQuery::Query(q) => { AnyQuery::Query(q) => {
@@ -2462,6 +2463,8 @@ impl NativeTable {
}); });
Ok(NsQueryTableRequest { Ok(NsQueryTableRequest {
identity: None,
context: None,
id: None, // Will be set by caller id: None, // Will be set by caller
vector, vector,
k: q.limit.unwrap_or(10) as i32, k: q.limit.unwrap_or(10) as i32,
@@ -2469,11 +2472,18 @@ impl NativeTable {
columns, columns,
prefilter: Some(q.prefilter), prefilter: Some(q.prefilter),
offset: q.offset.map(|o| o as i32), offset: q.offset.map(|o| o as i32),
ef: None,
refine_factor: None,
distance_type: None,
nprobes: None,
vector_column: None, // No vector column for plain queries vector_column: None, // No vector column for plain queries
with_row_id: Some(q.with_row_id), with_row_id: Some(q.with_row_id),
bypass_vector_index: Some(true), // No vector index for plain queries bypass_vector_index: Some(true), // No vector index for plain queries
full_text_query, full_text_query,
..Default::default() version: None,
fast_search: None,
lower_bound: None,
upper_bound: None,
}) })
} }
} }
@@ -3226,8 +3236,8 @@ impl BaseTable for NativeTable {
Ok(results.into_iter().flatten().collect()) Ok(results.into_iter().flatten().collect())
} }
async fn uri(&self) -> Result<String> { fn dataset_uri(&self) -> &str {
Ok(self.uri.clone()) self.uri.as_str()
} }
async fn storage_options(&self) -> Option<HashMap<String, String>> { async fn storage_options(&self) -> Option<HashMap<String, String>> {
@@ -3235,7 +3245,7 @@ impl BaseTable for NativeTable {
.get() .get()
.await .await
.ok() .ok()
.and_then(|dataset| dataset.initial_storage_options().cloned()) .and_then(|dataset| dataset.storage_options().cloned())
} }
async fn index_stats(&self, index_name: &str) -> Result<Option<IndexStatistics>> { async fn index_stats(&self, index_name: &str) -> Result<Option<IndexStatistics>> {
@@ -3400,6 +3410,7 @@ pub struct FragmentSummaryStats {
#[cfg(test)] #[cfg(test)]
#[allow(deprecated)] #[allow(deprecated)]
mod tests { mod tests {
use std::iter;
use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::Arc; use std::sync::Arc;
use std::time::Duration; use std::time::Duration;
@@ -4016,7 +4027,7 @@ mod tests {
schema.clone(), schema.clone(),
vec![ vec![
Arc::new(Int32Array::from_iter_values(offset..(offset + 10))), Arc::new(Int32Array::from_iter_values(offset..(offset + 10))),
Arc::new(Int32Array::from_iter_values(std::iter::repeat_n(age, 10))), Arc::new(Int32Array::from_iter_values(iter::repeat(age).take(10))),
], ],
)], )],
schema, schema,
@@ -5151,8 +5162,8 @@ mod tests {
ns_request ns_request
.columns .columns
.as_ref() .as_ref()
.and_then(|c| c.column_names.as_ref()), .and_then(|columns| columns.column_names.clone()),
Some(&vec!["id".to_string()]) Some(vec!["id".to_string()])
); );
assert_eq!(ns_request.vector_column, Some("vector".to_string())); assert_eq!(ns_request.vector_column, Some("vector".to_string()));
assert_eq!(ns_request.distance_type, Some("l2".to_string())); assert_eq!(ns_request.distance_type, Some("l2".to_string()));
@@ -5198,8 +5209,8 @@ mod tests {
ns_request ns_request
.columns .columns
.as_ref() .as_ref()
.and_then(|c| c.column_names.as_ref()), .and_then(|columns| columns.column_names.clone()),
Some(&vec!["id".to_string()]) Some(vec!["id".to_string()])
); );
assert_eq!(ns_request.with_row_id, Some(true)); assert_eq!(ns_request.with_row_id, Some(true));
assert_eq!(ns_request.bypass_vector_index, Some(true)); assert_eq!(ns_request.bypass_vector_index, Some(true));
+1
View File
@@ -101,6 +101,7 @@ impl DatasetRef {
refs::Ref::Version(_, Some(target_ver)) => version != target_ver, refs::Ref::Version(_, Some(target_ver)) => version != target_ver,
refs::Ref::Version(_, None) => true, // No specific version, always checkout refs::Ref::Version(_, None) => true, // No specific version, always checkout
refs::Ref::Tag(_) => true, // Always checkout for tags refs::Ref::Tag(_) => true, // Always checkout for tags
refs::Ref::VersionNumber(target_ver) => version != target_ver,
}; };
if should_checkout { if should_checkout {
@@ -4,6 +4,7 @@
use std::{ use std::{
borrow::Cow, borrow::Cow,
collections::{HashMap, HashSet}, collections::{HashMap, HashSet},
iter::repeat,
sync::Arc, sync::Arc,
}; };
@@ -267,10 +268,9 @@ fn create_some_records() -> Result<impl IntoArrow> {
schema.clone(), schema.clone(),
vec![ vec![
Arc::new(Int32Array::from_iter_values(0..TOTAL as i32)), Arc::new(Int32Array::from_iter_values(0..TOTAL as i32)),
Arc::new(StringArray::from_iter(std::iter::repeat_n( Arc::new(StringArray::from_iter(
Some("hello world".to_string()), repeat(Some("hello world".to_string())).take(TOTAL),
TOTAL, )),
))),
], ],
) )
.unwrap()] .unwrap()]