mirror of
https://github.com/lancedb/lancedb.git
synced 2026-08-29 01:18:23 +00:00
Compare commits
4 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| a3e3c1ad5c | |||
| c6e43b545d | |||
| 5807ad464b | |||
| 513c2215c5 |
+1
-1
@@ -1,5 +1,5 @@
|
|||||||
[tool.bumpversion]
|
[tool.bumpversion]
|
||||||
current_version = "0.28.0-beta.10"
|
current_version = "0.28.0-beta.5"
|
||||||
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*)\\.
|
||||||
|
|||||||
@@ -1,18 +0,0 @@
|
|||||||
version: 2
|
|
||||||
|
|
||||||
# Scope: the root Cargo workspace, which produces the Rust binaries we
|
|
||||||
# ship to users (the Node.js and Python native extensions). The
|
|
||||||
# `rust/lancedb` library crate shares the same lockfile; its consumers
|
|
||||||
# pick their own dependency versions, but bumping transitive deps here
|
|
||||||
# keeps the binaries we ship current.
|
|
||||||
updates:
|
|
||||||
- package-ecosystem: cargo
|
|
||||||
directory: /
|
|
||||||
schedule:
|
|
||||||
interval: weekly
|
|
||||||
open-pull-requests-limit: 10
|
|
||||||
groups:
|
|
||||||
rust-minor-patch:
|
|
||||||
update-types:
|
|
||||||
- minor
|
|
||||||
- patch
|
|
||||||
@@ -8,9 +8,6 @@ concurrency:
|
|||||||
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
|
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
|
||||||
cancel-in-progress: true
|
cancel-in-progress: true
|
||||||
|
|
||||||
permissions:
|
|
||||||
contents: read
|
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
labeler:
|
labeler:
|
||||||
permissions:
|
permissions:
|
||||||
|
|||||||
@@ -19,9 +19,6 @@ on:
|
|||||||
paths:
|
paths:
|
||||||
- .github/workflows/java-publish.yml
|
- .github/workflows/java-publish.yml
|
||||||
|
|
||||||
permissions:
|
|
||||||
contents: read
|
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
publish:
|
publish:
|
||||||
name: Build and Publish
|
name: Build and Publish
|
||||||
|
|||||||
@@ -24,9 +24,6 @@ on:
|
|||||||
- java/**
|
- java/**
|
||||||
- .github/workflows/java.yml
|
- .github/workflows/java.yml
|
||||||
|
|
||||||
permissions:
|
|
||||||
contents: read
|
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build-java:
|
build-java:
|
||||||
runs-on: ubuntu-24.04
|
runs-on: ubuntu-24.04
|
||||||
|
|||||||
@@ -10,10 +10,6 @@ on:
|
|||||||
- nodejs/**
|
- nodejs/**
|
||||||
- java/**
|
- java/**
|
||||||
- .github/workflows/license-header-check.yml
|
- .github/workflows/license-header-check.yml
|
||||||
|
|
||||||
permissions:
|
|
||||||
contents: read
|
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
check-licenses:
|
check-licenses:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
|||||||
@@ -15,9 +15,6 @@ on:
|
|||||||
- .github/workflows/nodejs.yml
|
- .github/workflows/nodejs.yml
|
||||||
- docker-compose.yml
|
- docker-compose.yml
|
||||||
|
|
||||||
permissions:
|
|
||||||
contents: read
|
|
||||||
|
|
||||||
concurrency:
|
concurrency:
|
||||||
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
|
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
|
||||||
cancel-in-progress: true
|
cancel-in-progress: true
|
||||||
|
|||||||
@@ -14,16 +14,10 @@ on:
|
|||||||
env:
|
env:
|
||||||
PIP_EXTRA_INDEX_URL: "https://pypi.fury.io/lance-format/ https://pypi.fury.io/lancedb/"
|
PIP_EXTRA_INDEX_URL: "https://pypi.fury.io/lance-format/ https://pypi.fury.io/lancedb/"
|
||||||
|
|
||||||
permissions:
|
|
||||||
contents: read
|
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
linux:
|
linux:
|
||||||
name: Python ${{ matrix.config.platform }} manylinux${{ matrix.config.manylinux }}
|
name: Python ${{ matrix.config.platform }} manylinux${{ matrix.config.manylinux }}
|
||||||
timeout-minutes: 60
|
timeout-minutes: 60
|
||||||
permissions:
|
|
||||||
id-token: write
|
|
||||||
contents: read
|
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
config:
|
config:
|
||||||
@@ -63,12 +57,10 @@ jobs:
|
|||||||
- uses: ./.github/workflows/upload_wheel
|
- uses: ./.github/workflows/upload_wheel
|
||||||
if: startsWith(github.ref, 'refs/tags/python-v')
|
if: startsWith(github.ref, 'refs/tags/python-v')
|
||||||
with:
|
with:
|
||||||
|
pypi_token: ${{ secrets.LANCEDB_PYPI_API_TOKEN }}
|
||||||
fury_token: ${{ secrets.FURY_TOKEN }}
|
fury_token: ${{ secrets.FURY_TOKEN }}
|
||||||
mac:
|
mac:
|
||||||
timeout-minutes: 90
|
timeout-minutes: 90
|
||||||
permissions:
|
|
||||||
id-token: write
|
|
||||||
contents: read
|
|
||||||
runs-on: ${{ matrix.config.runner }}
|
runs-on: ${{ matrix.config.runner }}
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
@@ -93,12 +85,10 @@ jobs:
|
|||||||
- uses: ./.github/workflows/upload_wheel
|
- uses: ./.github/workflows/upload_wheel
|
||||||
if: startsWith(github.ref, 'refs/tags/python-v')
|
if: startsWith(github.ref, 'refs/tags/python-v')
|
||||||
with:
|
with:
|
||||||
|
pypi_token: ${{ secrets.LANCEDB_PYPI_API_TOKEN }}
|
||||||
fury_token: ${{ secrets.FURY_TOKEN }}
|
fury_token: ${{ secrets.FURY_TOKEN }}
|
||||||
windows:
|
windows:
|
||||||
timeout-minutes: 60
|
timeout-minutes: 60
|
||||||
permissions:
|
|
||||||
id-token: write
|
|
||||||
contents: read
|
|
||||||
runs-on: windows-latest
|
runs-on: windows-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
@@ -117,6 +107,7 @@ jobs:
|
|||||||
- uses: ./.github/workflows/upload_wheel
|
- uses: ./.github/workflows/upload_wheel
|
||||||
if: startsWith(github.ref, 'refs/tags/python-v')
|
if: startsWith(github.ref, 'refs/tags/python-v')
|
||||||
with:
|
with:
|
||||||
|
pypi_token: ${{ secrets.LANCEDB_PYPI_API_TOKEN }}
|
||||||
fury_token: ${{ secrets.FURY_TOKEN }}
|
fury_token: ${{ secrets.FURY_TOKEN }}
|
||||||
gh-release:
|
gh-release:
|
||||||
if: startsWith(github.ref, 'refs/tags/python-v')
|
if: startsWith(github.ref, 'refs/tags/python-v')
|
||||||
|
|||||||
@@ -17,9 +17,6 @@ on:
|
|||||||
- .github/workflows/build_windows_wheel/**
|
- .github/workflows/build_windows_wheel/**
|
||||||
- .github/workflows/run_tests/**
|
- .github/workflows/run_tests/**
|
||||||
|
|
||||||
permissions:
|
|
||||||
contents: read
|
|
||||||
|
|
||||||
concurrency:
|
concurrency:
|
||||||
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
|
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
|
||||||
cancel-in-progress: true
|
cancel-in-progress: true
|
||||||
@@ -111,6 +108,7 @@ jobs:
|
|||||||
- name: Install
|
- name: Install
|
||||||
run: |
|
run: |
|
||||||
pip install --extra-index-url https://pypi.fury.io/lance-format/ --extra-index-url https://pypi.fury.io/lancedb/ -e .[tests,dev,embeddings]
|
pip install --extra-index-url https://pypi.fury.io/lance-format/ --extra-index-url https://pypi.fury.io/lancedb/ -e .[tests,dev,embeddings]
|
||||||
|
pip install tantivy
|
||||||
pip install mlx
|
pip install mlx
|
||||||
- name: Doctest
|
- name: Doctest
|
||||||
run: pytest --doctest-modules python/lancedb
|
run: pytest --doctest-modules python/lancedb
|
||||||
@@ -229,5 +227,6 @@ jobs:
|
|||||||
pip install "pydantic<2"
|
pip install "pydantic<2"
|
||||||
pip install pyarrow==16
|
pip install pyarrow==16
|
||||||
pip install --extra-index-url https://pypi.fury.io/lance-format/ --extra-index-url https://pypi.fury.io/lancedb/ -e .[tests]
|
pip install --extra-index-url https://pypi.fury.io/lance-format/ --extra-index-url https://pypi.fury.io/lancedb/ -e .[tests]
|
||||||
|
pip install tantivy
|
||||||
- name: Run tests
|
- name: Run tests
|
||||||
run: pytest -m "not slow and not s3_test" -x -v --durations=30 python/tests
|
run: pytest -m "not slow and not s3_test" -x -v --durations=30 python/tests
|
||||||
|
|||||||
@@ -9,15 +9,9 @@ on:
|
|||||||
- Cargo.toml
|
- Cargo.toml
|
||||||
- Cargo.lock
|
- Cargo.lock
|
||||||
- rust-toolchain.toml
|
- rust-toolchain.toml
|
||||||
- deny.toml
|
|
||||||
- rust/**
|
- rust/**
|
||||||
- nodejs/Cargo.toml
|
|
||||||
- python/Cargo.toml
|
|
||||||
- .github/workflows/rust.yml
|
- .github/workflows/rust.yml
|
||||||
|
|
||||||
permissions:
|
|
||||||
contents: read
|
|
||||||
|
|
||||||
concurrency:
|
concurrency:
|
||||||
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
|
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
|
||||||
cancel-in-progress: true
|
cancel-in-progress: true
|
||||||
@@ -59,17 +53,6 @@ jobs:
|
|||||||
- name: Run clippy (without remote feature)
|
- name: Run clippy (without remote feature)
|
||||||
run: cargo clippy --profile ci --workspace --tests -- -D warnings
|
run: cargo clippy --profile ci --workspace --tests -- -D warnings
|
||||||
|
|
||||||
deny:
|
|
||||||
# Supply-chain checks: advisories, licenses, banned crates, and source
|
|
||||||
# restrictions. Configuration lives in `deny.toml` at the workspace root.
|
|
||||||
timeout-minutes: 10
|
|
||||||
runs-on: ubuntu-24.04
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v4
|
|
||||||
- uses: EmbarkStudios/cargo-deny-action@v2
|
|
||||||
with:
|
|
||||||
command: check advisories bans licenses sources
|
|
||||||
|
|
||||||
build-no-lock:
|
build-no-lock:
|
||||||
runs-on: ubuntu-24.04
|
runs-on: ubuntu-24.04
|
||||||
timeout-minutes: 30
|
timeout-minutes: 30
|
||||||
|
|||||||
@@ -3,9 +3,6 @@ name: Update package-lock.json
|
|||||||
on:
|
on:
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
|
|
||||||
permissions:
|
|
||||||
contents: read
|
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
publish:
|
publish:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
|||||||
@@ -3,9 +3,6 @@ name: Update NodeJs package-lock.json
|
|||||||
on:
|
on:
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
|
|
||||||
permissions:
|
|
||||||
contents: read
|
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
publish:
|
publish:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
|||||||
@@ -2,6 +2,9 @@ name: upload-wheel
|
|||||||
|
|
||||||
description: "Upload wheels to Pypi"
|
description: "Upload wheels to Pypi"
|
||||||
inputs:
|
inputs:
|
||||||
|
pypi_token:
|
||||||
|
required: true
|
||||||
|
description: "release token for the repo"
|
||||||
fury_token:
|
fury_token:
|
||||||
required: true
|
required: true
|
||||||
description: "release token for the fury repo"
|
description: "release token for the fury repo"
|
||||||
@@ -9,6 +12,12 @@ inputs:
|
|||||||
runs:
|
runs:
|
||||||
using: "composite"
|
using: "composite"
|
||||||
steps:
|
steps:
|
||||||
|
- name: Install dependencies
|
||||||
|
shell: bash
|
||||||
|
run: |
|
||||||
|
python -m pip install --upgrade pip
|
||||||
|
pip install twine
|
||||||
|
python3 -m pip install --upgrade pkginfo
|
||||||
- name: Choose repo
|
- name: Choose repo
|
||||||
shell: bash
|
shell: bash
|
||||||
id: choose_repo
|
id: choose_repo
|
||||||
@@ -18,17 +27,19 @@ runs:
|
|||||||
else
|
else
|
||||||
echo "repo=pypi" >> $GITHUB_OUTPUT
|
echo "repo=pypi" >> $GITHUB_OUTPUT
|
||||||
fi
|
fi
|
||||||
- name: Publish to Fury
|
- name: Publish to PyPI
|
||||||
if: steps.choose_repo.outputs.repo == 'fury'
|
|
||||||
shell: bash
|
shell: bash
|
||||||
env:
|
env:
|
||||||
FURY_TOKEN: ${{ inputs.fury_token }}
|
FURY_TOKEN: ${{ inputs.fury_token }}
|
||||||
|
PYPI_TOKEN: ${{ inputs.pypi_token }}
|
||||||
run: |
|
run: |
|
||||||
WHEEL=$(ls target/wheels/lancedb-*.whl 2> /dev/null | head -n 1)
|
if [[ ${{ steps.choose_repo.outputs.repo }} == fury ]]; then
|
||||||
echo "Uploading $WHEEL to Fury"
|
WHEEL=$(ls target/wheels/lancedb-*.whl 2> /dev/null | head -n 1)
|
||||||
curl -f -F package=@$WHEEL https://$FURY_TOKEN@push.fury.io/lancedb/
|
echo "Uploading $WHEEL to Fury"
|
||||||
- name: Publish to PyPI
|
curl -f -F package=@$WHEEL https://$FURY_TOKEN@push.fury.io/lancedb/
|
||||||
if: steps.choose_repo.outputs.repo == 'pypi'
|
else
|
||||||
uses: pypa/gh-action-pypi-publish@release/v1
|
twine upload --repository ${{ steps.choose_repo.outputs.repo }} \
|
||||||
with:
|
--username __token__ \
|
||||||
packages-dir: target/wheels/
|
--password $PYPI_TOKEN \
|
||||||
|
target/wheels/lancedb-*.whl
|
||||||
|
fi
|
||||||
|
|||||||
Generated
+536
-288
File diff suppressed because it is too large
Load Diff
+33
-31
@@ -1,5 +1,7 @@
|
|||||||
[workspace]
|
[workspace]
|
||||||
members = ["rust/lancedb", "nodejs", "python"]
|
members = ["rust/lancedb", "nodejs", "python"]
|
||||||
|
# Python package needs to be built by maturin.
|
||||||
|
exclude = ["python"]
|
||||||
resolver = "2"
|
resolver = "2"
|
||||||
|
|
||||||
[workspace.package]
|
[workspace.package]
|
||||||
@@ -13,40 +15,40 @@ categories = ["database-implementations"]
|
|||||||
rust-version = "1.91.0"
|
rust-version = "1.91.0"
|
||||||
|
|
||||||
[workspace.dependencies]
|
[workspace.dependencies]
|
||||||
lance = { "version" = "=6.0.0-beta.7", default-features = false, "tag" = "v6.0.0-beta.7", "git" = "https://github.com/lance-format/lance.git" }
|
lance = { "version" = "=5.1.0-beta.3", default-features = false, "tag" = "v5.1.0-beta.3", "git" = "https://github.com/lance-format/lance.git" }
|
||||||
lance-core = { "version" = "=6.0.0-beta.7", "tag" = "v6.0.0-beta.7", "git" = "https://github.com/lance-format/lance.git" }
|
lance-core = { "version" = "=5.1.0-beta.3", "tag" = "v5.1.0-beta.3", "git" = "https://github.com/lance-format/lance.git" }
|
||||||
lance-datagen = { "version" = "=6.0.0-beta.7", "tag" = "v6.0.0-beta.7", "git" = "https://github.com/lance-format/lance.git" }
|
lance-datagen = { "version" = "=5.1.0-beta.3", "tag" = "v5.1.0-beta.3", "git" = "https://github.com/lance-format/lance.git" }
|
||||||
lance-file = { "version" = "=6.0.0-beta.7", "tag" = "v6.0.0-beta.7", "git" = "https://github.com/lance-format/lance.git" }
|
lance-file = { "version" = "=5.1.0-beta.3", "tag" = "v5.1.0-beta.3", "git" = "https://github.com/lance-format/lance.git" }
|
||||||
lance-io = { "version" = "=6.0.0-beta.7", default-features = false, "tag" = "v6.0.0-beta.7", "git" = "https://github.com/lance-format/lance.git" }
|
lance-io = { "version" = "=5.1.0-beta.3", default-features = false, "tag" = "v5.1.0-beta.3", "git" = "https://github.com/lance-format/lance.git" }
|
||||||
lance-index = { "version" = "=6.0.0-beta.7", "tag" = "v6.0.0-beta.7", "git" = "https://github.com/lance-format/lance.git" }
|
lance-index = { "version" = "=5.1.0-beta.3", "tag" = "v5.1.0-beta.3", "git" = "https://github.com/lance-format/lance.git" }
|
||||||
lance-linalg = { "version" = "=6.0.0-beta.7", "tag" = "v6.0.0-beta.7", "git" = "https://github.com/lance-format/lance.git" }
|
lance-linalg = { "version" = "=5.1.0-beta.3", "tag" = "v5.1.0-beta.3", "git" = "https://github.com/lance-format/lance.git" }
|
||||||
lance-namespace = { "version" = "=6.0.0-beta.7", "tag" = "v6.0.0-beta.7", "git" = "https://github.com/lance-format/lance.git" }
|
lance-namespace = { "version" = "=5.1.0-beta.3", "tag" = "v5.1.0-beta.3", "git" = "https://github.com/lance-format/lance.git" }
|
||||||
lance-namespace-impls = { "version" = "=6.0.0-beta.7", default-features = false, "tag" = "v6.0.0-beta.7", "git" = "https://github.com/lance-format/lance.git" }
|
lance-namespace-impls = { "version" = "=5.1.0-beta.3", default-features = false, "tag" = "v5.1.0-beta.3", "git" = "https://github.com/lance-format/lance.git" }
|
||||||
lance-table = { "version" = "=6.0.0-beta.7", "tag" = "v6.0.0-beta.7", "git" = "https://github.com/lance-format/lance.git" }
|
lance-table = { "version" = "=5.1.0-beta.3", "tag" = "v5.1.0-beta.3", "git" = "https://github.com/lance-format/lance.git" }
|
||||||
lance-testing = { "version" = "=6.0.0-beta.7", "tag" = "v6.0.0-beta.7", "git" = "https://github.com/lance-format/lance.git" }
|
lance-testing = { "version" = "=5.1.0-beta.3", "tag" = "v5.1.0-beta.3", "git" = "https://github.com/lance-format/lance.git" }
|
||||||
lance-datafusion = { "version" = "=6.0.0-beta.7", "tag" = "v6.0.0-beta.7", "git" = "https://github.com/lance-format/lance.git" }
|
lance-datafusion = { "version" = "=5.1.0-beta.3", "tag" = "v5.1.0-beta.3", "git" = "https://github.com/lance-format/lance.git" }
|
||||||
lance-encoding = { "version" = "=6.0.0-beta.7", "tag" = "v6.0.0-beta.7", "git" = "https://github.com/lance-format/lance.git" }
|
lance-encoding = { "version" = "=5.1.0-beta.3", "tag" = "v5.1.0-beta.3", "git" = "https://github.com/lance-format/lance.git" }
|
||||||
lance-arrow = { "version" = "=6.0.0-beta.7", "tag" = "v6.0.0-beta.7", "git" = "https://github.com/lance-format/lance.git" }
|
lance-arrow = { "version" = "=5.1.0-beta.3", "tag" = "v5.1.0-beta.3", "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 = "58.0.0", optional = false }
|
arrow = { version = "57.2", optional = false }
|
||||||
arrow-array = "58.0.0"
|
arrow-array = "57.2"
|
||||||
arrow-data = "58.0.0"
|
arrow-data = "57.2"
|
||||||
arrow-ipc = "58.0.0"
|
arrow-ipc = "57.2"
|
||||||
arrow-ord = "58.0.0"
|
arrow-ord = "57.2"
|
||||||
arrow-schema = "58.0.0"
|
arrow-schema = "57.2"
|
||||||
arrow-select = "58.0.0"
|
arrow-select = "57.2"
|
||||||
arrow-cast = "58.0.0"
|
arrow-cast = "57.2"
|
||||||
async-trait = "0"
|
async-trait = "0"
|
||||||
datafusion = { version = "53.0.0", default-features = false }
|
datafusion = { version = "52.1", default-features = false }
|
||||||
datafusion-catalog = "53.0.0"
|
datafusion-catalog = "52.1"
|
||||||
datafusion-common = { version = "53.0.0", default-features = false }
|
datafusion-common = { version = "52.1", default-features = false }
|
||||||
datafusion-execution = "53.0.0"
|
datafusion-execution = "52.1"
|
||||||
datafusion-expr = "53.0.0"
|
datafusion-expr = "52.1"
|
||||||
datafusion-functions = "53.0.0"
|
datafusion-functions = "52.1"
|
||||||
datafusion-physical-plan = "53.0.0"
|
datafusion-physical-plan = "52.1"
|
||||||
datafusion-physical-expr = "53.0.0"
|
datafusion-physical-expr = "52.1"
|
||||||
datafusion-sql = "53.0.0"
|
datafusion-sql = "52.1"
|
||||||
env_logger = "0.11"
|
env_logger = "0.11"
|
||||||
half = { "version" = "2.7.1", default-features = false, features = [
|
half = { "version" = "2.7.1", default-features = false, features = [
|
||||||
"num-traits",
|
"num-traits",
|
||||||
|
|||||||
@@ -1,172 +0,0 @@
|
|||||||
# cargo-deny configuration for LanceDB.
|
|
||||||
#
|
|
||||||
# Run locally with `cargo deny check`. See
|
|
||||||
# https://embarkstudios.github.io/cargo-deny/ for the full reference.
|
|
||||||
|
|
||||||
# The set of target triples we care about. cargo-deny will only consider
|
|
||||||
# dependencies that are used on at least one of these targets. Keeping this
|
|
||||||
# explicit avoids noise from platform-specific crates (e.g. wasm, android,
|
|
||||||
# ios) that we never actually ship.
|
|
||||||
[graph]
|
|
||||||
targets = [
|
|
||||||
"x86_64-unknown-linux-gnu",
|
|
||||||
"aarch64-unknown-linux-gnu",
|
|
||||||
"x86_64-apple-darwin",
|
|
||||||
"aarch64-apple-darwin",
|
|
||||||
"x86_64-pc-windows-msvc",
|
|
||||||
"aarch64-pc-windows-msvc",
|
|
||||||
]
|
|
||||||
all-features = true
|
|
||||||
|
|
||||||
[output]
|
|
||||||
feature-depth = 1
|
|
||||||
|
|
||||||
# ---------------------------------------------------------------------------
|
|
||||||
# Advisories: security vulnerabilities and yanked crates.
|
|
||||||
# ---------------------------------------------------------------------------
|
|
||||||
[advisories]
|
|
||||||
version = 2
|
|
||||||
# Fail the check if any crate in the lockfile has been yanked from crates.io.
|
|
||||||
# Yanked crates are a signal the author retracted the release (often due to
|
|
||||||
# bugs or security issues) and should not be depended on.
|
|
||||||
yanked = "deny"
|
|
||||||
# Advisory IDs we have explicitly reviewed and chosen to accept. Every
|
|
||||||
# entry must include a rationale and, where possible, an upstream issue
|
|
||||||
# pointing to a fix. Revisit this list whenever dependencies are updated.
|
|
||||||
ignore = [
|
|
||||||
# rsa: Marvin Attack timing side-channel in PKCS#1 v1.5 decryption.
|
|
||||||
# Reached only through opendal → reqsign → rsa. We do not use RSA
|
|
||||||
# decryption in LanceDB ourselves; this is dormant in the signing path.
|
|
||||||
# No fixed release exists upstream as of this writing.
|
|
||||||
# https://rustsec.org/advisories/RUSTSEC-2023-0071
|
|
||||||
{ id = "RUSTSEC-2023-0071", reason = "rsa crate via opendal/reqsign; no fixed upstream release" },
|
|
||||||
|
|
||||||
# instant: unmaintained. Pulled in via backoff → instant. Upstream
|
|
||||||
# recommends switching to `web-time`; fix has to come from backoff.
|
|
||||||
# https://rustsec.org/advisories/RUSTSEC-2024-0384
|
|
||||||
{ id = "RUSTSEC-2024-0384", reason = "transitive via backoff; waiting on backoff replacement" },
|
|
||||||
|
|
||||||
# paste: unmaintained (author archived the repo). Used transitively by
|
|
||||||
# datafusion and the arrow ecosystem; widespread, no drop-in replacement.
|
|
||||||
# https://rustsec.org/advisories/RUSTSEC-2024-0436
|
|
||||||
{ id = "RUSTSEC-2024-0436", reason = "transitive via datafusion; awaiting ecosystem migration" },
|
|
||||||
|
|
||||||
# tantivy: segfault on malformed input due to missing bounds check.
|
|
||||||
# Pulled in via lance for full-text search. We only feed tantivy
|
|
||||||
# documents we construct ourselves, not attacker-controlled bytes.
|
|
||||||
# Tracked for a lance dependency bump.
|
|
||||||
# https://rustsec.org/advisories/RUSTSEC-2025-0003
|
|
||||||
{ id = "RUSTSEC-2025-0003", reason = "tantivy via lance; inputs are internally produced, not user-supplied bytes" },
|
|
||||||
|
|
||||||
# backoff: unmaintained. Reached only via async-openai. Replacement
|
|
||||||
# requires async-openai to migrate (or us to drop async-openai).
|
|
||||||
# https://rustsec.org/advisories/RUSTSEC-2025-0012
|
|
||||||
{ id = "RUSTSEC-2025-0012", reason = "transitive via async-openai; waiting on upstream migration" },
|
|
||||||
|
|
||||||
# number_prefix: unmaintained. Transitive via indicatif → hf-hub.
|
|
||||||
# No security impact, just maintenance status.
|
|
||||||
# https://rustsec.org/advisories/RUSTSEC-2025-0119
|
|
||||||
{ id = "RUSTSEC-2025-0119", reason = "transitive via hf-hub/indicatif; cosmetic formatting crate" },
|
|
||||||
|
|
||||||
# rustls-pemfile: unmaintained. Reached from two separate chains:
|
|
||||||
# rustls-native-certs 0.6 (via hyper-rustls 0.24) and object_store 0.12.
|
|
||||||
# Both upstream dependencies need to move before we can drop it.
|
|
||||||
# https://rustsec.org/advisories/RUSTSEC-2025-0134
|
|
||||||
{ id = "RUSTSEC-2025-0134", reason = "transitive via rustls-native-certs/object_store; waiting on upstream migration" },
|
|
||||||
|
|
||||||
# rustls-webpki 0.101.7 (old major line): name-constraint checks for
|
|
||||||
# URI / wildcard names. Pulled in only via the legacy rustls 0.21 chain
|
|
||||||
# from aws-smithy-http-client. The 0.103 line we actively use is patched.
|
|
||||||
# Clearing the 0.101 copy requires the aws-sdk chain to migrate off
|
|
||||||
# rustls 0.21.
|
|
||||||
# https://rustsec.org/advisories/RUSTSEC-2026-0098
|
|
||||||
# https://rustsec.org/advisories/RUSTSEC-2026-0099
|
|
||||||
{ id = "RUSTSEC-2026-0098", reason = "only affects rustls-webpki 0.101 from legacy aws-smithy/rustls 0.21 chain" },
|
|
||||||
{ id = "RUSTSEC-2026-0099", reason = "only affects rustls-webpki 0.101 from legacy aws-smithy/rustls 0.21 chain" },
|
|
||||||
|
|
||||||
# rustls-webpki 0.101.7: reachable panic in CRL parsing. Same legacy
|
|
||||||
# rustls 0.21 chain from aws-smithy-http-client as above. The 0.103 line
|
|
||||||
# we actively use is upgraded to 0.103.13 which contains the fix.
|
|
||||||
# https://rustsec.org/advisories/RUSTSEC-2026-0104
|
|
||||||
{ id = "RUSTSEC-2026-0104", reason = "only affects rustls-webpki 0.101 from legacy aws-smithy/rustls 0.21 chain" },
|
|
||||||
]
|
|
||||||
|
|
||||||
# ---------------------------------------------------------------------------
|
|
||||||
# Licenses: only allow licenses we've reviewed as compatible with Apache-2.0.
|
|
||||||
# ---------------------------------------------------------------------------
|
|
||||||
[licenses]
|
|
||||||
version = 2
|
|
||||||
# SPDX identifiers for licenses that are compatible with our Apache-2.0
|
|
||||||
# distribution. Additions require legal review.
|
|
||||||
allow = [
|
|
||||||
"Apache-2.0",
|
|
||||||
"Apache-2.0 WITH LLVM-exception",
|
|
||||||
"MIT",
|
|
||||||
"BSD-2-Clause",
|
|
||||||
"BSD-3-Clause",
|
|
||||||
"ISC",
|
|
||||||
"Unicode-3.0",
|
|
||||||
"Unicode-DFS-2016",
|
|
||||||
"Zlib",
|
|
||||||
"CC0-1.0",
|
|
||||||
"MPL-2.0",
|
|
||||||
"BSL-1.0",
|
|
||||||
"OpenSSL",
|
|
||||||
# 0BSD ("BSD Zero Clause") is effectively public domain — no attribution
|
|
||||||
# required. Pulled in by `mock_instant`.
|
|
||||||
"0BSD",
|
|
||||||
# bzip2-1.0.6 is the permissive upstream bzip2 license (BSD-like). Pulled
|
|
||||||
# in by `libbz2-rs-sys`, the pure-Rust bzip2 implementation.
|
|
||||||
"bzip2-1.0.6",
|
|
||||||
# CDLA-Permissive-2.0 is a permissive data license used by `webpki-roots`
|
|
||||||
# for the Mozilla CA root bundle. Data-only, distribution-compatible.
|
|
||||||
"CDLA-Permissive-2.0",
|
|
||||||
]
|
|
||||||
confidence-threshold = 0.8
|
|
||||||
# Crates whose license cannot be determined from Cargo metadata but whose
|
|
||||||
# license we've manually confirmed from upstream. Keep this list minimal.
|
|
||||||
[[licenses.clarify]]
|
|
||||||
# polars-arrow-format omits the `license` field in its Cargo.toml, but the
|
|
||||||
# upstream repo (pola-rs/polars-arrow-format) is dual-licensed Apache-2.0 OR
|
|
||||||
# MIT. See https://github.com/pola-rs/polars-arrow-format/blob/main/LICENSE
|
|
||||||
crate = "polars-arrow-format"
|
|
||||||
expression = "Apache-2.0 OR MIT"
|
|
||||||
license-files = []
|
|
||||||
|
|
||||||
# ---------------------------------------------------------------------------
|
|
||||||
# Bans: disallow specific crates and flag dependency hygiene issues.
|
|
||||||
# ---------------------------------------------------------------------------
|
|
||||||
[bans]
|
|
||||||
# Warn (not deny) on duplicate versions of the same crate. In a large
|
|
||||||
# workspace like this one, duplicates are common and often unavoidable
|
|
||||||
# transitively. We surface them to discourage growth, but don't fail CI.
|
|
||||||
multiple-versions = "warn"
|
|
||||||
# Wildcard version requirements (`foo = "*"`) are a footgun — they let any
|
|
||||||
# future release in without review. Ban them outright.
|
|
||||||
wildcards = "deny"
|
|
||||||
# Internal workspace crates reference each other via `path = "..."`, which
|
|
||||||
# cargo-deny sees as a wildcard version. That's fine for private workspace
|
|
||||||
# members (not published to crates.io), so allow it specifically for paths.
|
|
||||||
allow-wildcard-paths = true
|
|
||||||
# Features that, if enabled, should cause the check to fail.
|
|
||||||
deny = []
|
|
||||||
# Crates to skip when checking for duplicate versions.
|
|
||||||
skip = []
|
|
||||||
# Similar to `skip`, but also skips the entire transitive subtree.
|
|
||||||
skip-tree = []
|
|
||||||
|
|
||||||
# ---------------------------------------------------------------------------
|
|
||||||
# Sources: restrict where crates can come from.
|
|
||||||
# ---------------------------------------------------------------------------
|
|
||||||
[sources]
|
|
||||||
# Deny any registry other than the ones explicitly listed below.
|
|
||||||
unknown-registry = "deny"
|
|
||||||
# Deny any git dependency whose host isn't in the allow-list below. This
|
|
||||||
# prevents accidental pulls from arbitrary forks.
|
|
||||||
unknown-git = "deny"
|
|
||||||
allow-registry = ["https://github.com/rust-lang/crates.io-index"]
|
|
||||||
# Lance is developed in a sibling repo and pulled as a git dependency until
|
|
||||||
# releases are cut to crates.io. Allow that specific host.
|
|
||||||
allow-git = [
|
|
||||||
"https://github.com/lance-format/lance",
|
|
||||||
]
|
|
||||||
@@ -24,4 +24,4 @@ RUN python --version && \
|
|||||||
rustc --version && \
|
rustc --version && \
|
||||||
protoc --version
|
protoc --version
|
||||||
|
|
||||||
RUN pip install --no-cache-dir lancedb
|
RUN pip install --no-cache-dir tantivy lancedb
|
||||||
|
|||||||
@@ -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.28.0-beta.10</version>
|
<version>0.28.0-beta.5</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|||||||
@@ -41,29 +41,6 @@ for testing purposes.
|
|||||||
|
|
||||||
***
|
***
|
||||||
|
|
||||||
### manifestEnabled?
|
|
||||||
|
|
||||||
```ts
|
|
||||||
optional manifestEnabled: boolean;
|
|
||||||
```
|
|
||||||
|
|
||||||
(For LanceDB OSS only): use directory namespace manifests as the source
|
|
||||||
of truth for table metadata. Existing directory-listed root tables are
|
|
||||||
migrated into the manifest on access.
|
|
||||||
|
|
||||||
***
|
|
||||||
|
|
||||||
### namespaceClientProperties?
|
|
||||||
|
|
||||||
```ts
|
|
||||||
optional namespaceClientProperties: Record<string, string>;
|
|
||||||
```
|
|
||||||
|
|
||||||
(For LanceDB OSS only): extra properties for the backing namespace
|
|
||||||
client used by manifest-enabled native connections.
|
|
||||||
|
|
||||||
***
|
|
||||||
|
|
||||||
### readConsistencyInterval?
|
### readConsistencyInterval?
|
||||||
|
|
||||||
```ts
|
```ts
|
||||||
|
|||||||
@@ -8,7 +8,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<groupId>com.lancedb</groupId>
|
<groupId>com.lancedb</groupId>
|
||||||
<artifactId>lancedb-parent</artifactId>
|
<artifactId>lancedb-parent</artifactId>
|
||||||
<version>0.28.0-beta.10</version>
|
<version>0.28.0-beta.5</version>
|
||||||
<relativePath>../pom.xml</relativePath>
|
<relativePath>../pom.xml</relativePath>
|
||||||
</parent>
|
</parent>
|
||||||
|
|
||||||
|
|||||||
+2
-2
@@ -6,7 +6,7 @@
|
|||||||
|
|
||||||
<groupId>com.lancedb</groupId>
|
<groupId>com.lancedb</groupId>
|
||||||
<artifactId>lancedb-parent</artifactId>
|
<artifactId>lancedb-parent</artifactId>
|
||||||
<version>0.28.0-beta.10</version>
|
<version>0.28.0-beta.5</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>
|
||||||
@@ -28,7 +28,7 @@
|
|||||||
<properties>
|
<properties>
|
||||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||||
<arrow.version>15.0.0</arrow.version>
|
<arrow.version>15.0.0</arrow.version>
|
||||||
<lance-core.version>6.0.0-beta.7</lance-core.version>
|
<lance-core.version>5.1.0-beta.3</lance-core.version>
|
||||||
<spotless.skip>false</spotless.skip>
|
<spotless.skip>false</spotless.skip>
|
||||||
<spotless.version>2.30.0</spotless.version>
|
<spotless.version>2.30.0</spotless.version>
|
||||||
<spotless.java.googlejavaformat.version>1.7</spotless.java.googlejavaformat.version>
|
<spotless.java.googlejavaformat.version>1.7</spotless.java.googlejavaformat.version>
|
||||||
|
|||||||
+4
-5
@@ -1,8 +1,7 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "lancedb-nodejs"
|
name = "lancedb-nodejs"
|
||||||
edition.workspace = true
|
edition.workspace = true
|
||||||
version = "0.28.0-beta.10"
|
version = "0.28.0-beta.5"
|
||||||
publish = false
|
|
||||||
license.workspace = true
|
license.workspace = true
|
||||||
description.workspace = true
|
description.workspace = true
|
||||||
repository.workspace = true
|
repository.workspace = true
|
||||||
@@ -16,7 +15,7 @@ crate-type = ["cdylib"]
|
|||||||
async-trait.workspace = true
|
async-trait.workspace = true
|
||||||
arrow-ipc.workspace = true
|
arrow-ipc.workspace = true
|
||||||
arrow-array.workspace = true
|
arrow-array.workspace = true
|
||||||
arrow-buffer = "58.0.0"
|
arrow-buffer = "57.2"
|
||||||
half.workspace = true
|
half.workspace = true
|
||||||
arrow-schema.workspace = true
|
arrow-schema.workspace = true
|
||||||
env_logger.workspace = true
|
env_logger.workspace = true
|
||||||
@@ -32,8 +31,8 @@ lzma-sys = { version = "0.1", features = ["static"] }
|
|||||||
log.workspace = true
|
log.workspace = true
|
||||||
|
|
||||||
# Pin to resolve build failures; update periodically for security patches.
|
# Pin to resolve build failures; update periodically for security patches.
|
||||||
aws-lc-sys = "=0.40.0"
|
aws-lc-sys = "=0.38.0"
|
||||||
aws-lc-rs = "=1.16.3"
|
aws-lc-rs = "=1.16.1"
|
||||||
|
|
||||||
[build-dependencies]
|
[build-dependencies]
|
||||||
napi-build = "2.3.1"
|
napi-build = "2.3.1"
|
||||||
|
|||||||
@@ -1,8 +1,6 @@
|
|||||||
// SPDX-License-Identifier: Apache-2.0
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
// SPDX-FileCopyrightText: Copyright The LanceDB Authors
|
// SPDX-FileCopyrightText: Copyright The LanceDB Authors
|
||||||
|
|
||||||
import { spawn } from "node:child_process";
|
|
||||||
import * as path from "node:path";
|
|
||||||
import { RecordBatch } from "apache-arrow";
|
import { RecordBatch } from "apache-arrow";
|
||||||
import * as tmp from "tmp";
|
import * as tmp from "tmp";
|
||||||
import { Connection, Index, Table, connect, makeArrowTable } from "../lancedb";
|
import { Connection, Index, Table, connect, makeArrowTable } from "../lancedb";
|
||||||
@@ -78,91 +76,4 @@ describe("rerankers", function () {
|
|||||||
|
|
||||||
expect(result).toHaveLength(2);
|
expect(result).toHaveLength(2);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("does not keep process alive after rerank query", async function () {
|
|
||||||
const script = `
|
|
||||||
import * as lancedb from "./dist/index.js";
|
|
||||||
import * as os from "node:os";
|
|
||||||
import * as path from "node:path";
|
|
||||||
import * as fs from "node:fs/promises";
|
|
||||||
|
|
||||||
const dir = await fs.mkdtemp(path.join(os.tmpdir(), "lancedb-rerank-exit-"));
|
|
||||||
const db = await lancedb.connect(dir);
|
|
||||||
const table = await db.createTable("test", [{ text: "hello", vector: [1, 2, 3] }], {
|
|
||||||
mode: "overwrite",
|
|
||||||
});
|
|
||||||
await table.createIndex("text", { config: lancedb.Index.fts() });
|
|
||||||
await table.waitForIndex(["text_idx"], 30);
|
|
||||||
|
|
||||||
const reranker = await lancedb.rerankers.RRFReranker.create();
|
|
||||||
await table
|
|
||||||
.query()
|
|
||||||
.nearestTo([1, 2, 3])
|
|
||||||
.fullTextSearch("hello")
|
|
||||||
.rerank(reranker)
|
|
||||||
.toArray();
|
|
||||||
|
|
||||||
table.close();
|
|
||||||
db.close();
|
|
||||||
`;
|
|
||||||
|
|
||||||
await new Promise<void>((resolve, reject) => {
|
|
||||||
const child = spawn(
|
|
||||||
process.execPath,
|
|
||||||
["--input-type=module", "-e", script],
|
|
||||||
{
|
|
||||||
cwd: path.resolve(__dirname, ".."),
|
|
||||||
stdio: ["ignore", "pipe", "pipe"],
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
let stdout = "";
|
|
||||||
let stderr = "";
|
|
||||||
|
|
||||||
child.stdout.on("data", (chunk) => {
|
|
||||||
stdout += chunk.toString();
|
|
||||||
});
|
|
||||||
|
|
||||||
child.stderr.on("data", (chunk) => {
|
|
||||||
stderr += chunk.toString();
|
|
||||||
});
|
|
||||||
|
|
||||||
const timeout = setTimeout(() => {
|
|
||||||
child.kill();
|
|
||||||
reject(
|
|
||||||
new Error(
|
|
||||||
`child process did not exit in time\nstdout:\n${stdout}\nstderr:\n${stderr}`,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}, 20_000);
|
|
||||||
|
|
||||||
child.on("error", (err) => {
|
|
||||||
clearTimeout(timeout);
|
|
||||||
reject(err);
|
|
||||||
});
|
|
||||||
|
|
||||||
child.on("exit", (code, signal) => {
|
|
||||||
clearTimeout(timeout);
|
|
||||||
if (signal !== null) {
|
|
||||||
reject(
|
|
||||||
new Error(
|
|
||||||
`child process exited with signal ${signal}\nstdout:\n${stdout}\nstderr:\n${stderr}`,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (code !== 0) {
|
|
||||||
reject(
|
|
||||||
new Error(
|
|
||||||
`child process exited with code ${code}\nstdout:\n${stdout}\nstderr:\n${stderr}`,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
resolve();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@lancedb/lancedb-darwin-arm64",
|
"name": "@lancedb/lancedb-darwin-arm64",
|
||||||
"version": "0.28.0-beta.10",
|
"version": "0.28.0-beta.5",
|
||||||
"os": ["darwin"],
|
"os": ["darwin"],
|
||||||
"cpu": ["arm64"],
|
"cpu": ["arm64"],
|
||||||
"main": "lancedb.darwin-arm64.node",
|
"main": "lancedb.darwin-arm64.node",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@lancedb/lancedb-linux-arm64-gnu",
|
"name": "@lancedb/lancedb-linux-arm64-gnu",
|
||||||
"version": "0.28.0-beta.10",
|
"version": "0.28.0-beta.5",
|
||||||
"os": ["linux"],
|
"os": ["linux"],
|
||||||
"cpu": ["arm64"],
|
"cpu": ["arm64"],
|
||||||
"main": "lancedb.linux-arm64-gnu.node",
|
"main": "lancedb.linux-arm64-gnu.node",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@lancedb/lancedb-linux-arm64-musl",
|
"name": "@lancedb/lancedb-linux-arm64-musl",
|
||||||
"version": "0.28.0-beta.10",
|
"version": "0.28.0-beta.5",
|
||||||
"os": ["linux"],
|
"os": ["linux"],
|
||||||
"cpu": ["arm64"],
|
"cpu": ["arm64"],
|
||||||
"main": "lancedb.linux-arm64-musl.node",
|
"main": "lancedb.linux-arm64-musl.node",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@lancedb/lancedb-linux-x64-gnu",
|
"name": "@lancedb/lancedb-linux-x64-gnu",
|
||||||
"version": "0.28.0-beta.10",
|
"version": "0.28.0-beta.5",
|
||||||
"os": ["linux"],
|
"os": ["linux"],
|
||||||
"cpu": ["x64"],
|
"cpu": ["x64"],
|
||||||
"main": "lancedb.linux-x64-gnu.node",
|
"main": "lancedb.linux-x64-gnu.node",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@lancedb/lancedb-linux-x64-musl",
|
"name": "@lancedb/lancedb-linux-x64-musl",
|
||||||
"version": "0.28.0-beta.10",
|
"version": "0.28.0-beta.5",
|
||||||
"os": ["linux"],
|
"os": ["linux"],
|
||||||
"cpu": ["x64"],
|
"cpu": ["x64"],
|
||||||
"main": "lancedb.linux-x64-musl.node",
|
"main": "lancedb.linux-x64-musl.node",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@lancedb/lancedb-win32-arm64-msvc",
|
"name": "@lancedb/lancedb-win32-arm64-msvc",
|
||||||
"version": "0.28.0-beta.10",
|
"version": "0.28.0-beta.5",
|
||||||
"os": [
|
"os": [
|
||||||
"win32"
|
"win32"
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@lancedb/lancedb-win32-x64-msvc",
|
"name": "@lancedb/lancedb-win32-x64-msvc",
|
||||||
"version": "0.28.0-beta.10",
|
"version": "0.28.0-beta.5",
|
||||||
"os": ["win32"],
|
"os": ["win32"],
|
||||||
"cpu": ["x64"],
|
"cpu": ["x64"],
|
||||||
"main": "lancedb.win32-x64-msvc.node",
|
"main": "lancedb.win32-x64-msvc.node",
|
||||||
|
|||||||
Generated
+2
-2
@@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "@lancedb/lancedb",
|
"name": "@lancedb/lancedb",
|
||||||
"version": "0.28.0-beta.10",
|
"version": "0.28.0-beta.5",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "@lancedb/lancedb",
|
"name": "@lancedb/lancedb",
|
||||||
"version": "0.28.0-beta.10",
|
"version": "0.28.0-beta.5",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"x64",
|
"x64",
|
||||||
"arm64"
|
"arm64"
|
||||||
|
|||||||
+1
-1
@@ -11,7 +11,7 @@
|
|||||||
"ann"
|
"ann"
|
||||||
],
|
],
|
||||||
"private": false,
|
"private": false,
|
||||||
"version": "0.28.0-beta.10",
|
"version": "0.28.0-beta.5",
|
||||||
"main": "dist/index.js",
|
"main": "dist/index.js",
|
||||||
"exports": {
|
"exports": {
|
||||||
".": "./dist/index.js",
|
".": "./dist/index.js",
|
||||||
|
|||||||
@@ -67,12 +67,6 @@ impl Connection {
|
|||||||
builder = builder.storage_option(key, value);
|
builder = builder.storage_option(key, value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if let Some(manifest_enabled) = options.manifest_enabled {
|
|
||||||
builder = builder.manifest_enabled(manifest_enabled);
|
|
||||||
}
|
|
||||||
if let Some(namespace_client_properties) = options.namespace_client_properties {
|
|
||||||
builder = builder.namespace_client_properties(namespace_client_properties);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create client config, optionally with header provider
|
// Create client config, optionally with header provider
|
||||||
let client_config = options.client_config.unwrap_or_default();
|
let client_config = options.client_config.unwrap_or_default();
|
||||||
|
|||||||
@@ -37,13 +37,6 @@ pub struct ConnectionOptions {
|
|||||||
///
|
///
|
||||||
/// The available options are described at https://docs.lancedb.com/storage/
|
/// The available options are described at https://docs.lancedb.com/storage/
|
||||||
pub storage_options: Option<HashMap<String, String>>,
|
pub storage_options: Option<HashMap<String, String>>,
|
||||||
/// (For LanceDB OSS only): use directory namespace manifests as the source
|
|
||||||
/// of truth for table metadata. Existing directory-listed root tables are
|
|
||||||
/// migrated into the manifest on access.
|
|
||||||
pub manifest_enabled: Option<bool>,
|
|
||||||
/// (For LanceDB OSS only): extra properties for the backing namespace
|
|
||||||
/// client used by manifest-enabled native connections.
|
|
||||||
pub namespace_client_properties: Option<HashMap<String, String>>,
|
|
||||||
/// (For LanceDB OSS only): the session to use for this connection. Holds
|
/// (For LanceDB OSS only): the session to use for this connection. Holds
|
||||||
/// shared caches and other session-specific state.
|
/// shared caches and other session-specific state.
|
||||||
pub session: Option<session::Session>,
|
pub session: Option<session::Session>,
|
||||||
|
|||||||
@@ -18,7 +18,6 @@ type RerankHybridFn = ThreadsafeFunction<
|
|||||||
RerankHybridCallbackArgs,
|
RerankHybridCallbackArgs,
|
||||||
Status,
|
Status,
|
||||||
false,
|
false,
|
||||||
true,
|
|
||||||
>;
|
>;
|
||||||
|
|
||||||
/// Reranker implementation that "wraps" a NodeJS Reranker implementation.
|
/// Reranker implementation that "wraps" a NodeJS Reranker implementation.
|
||||||
@@ -33,10 +32,7 @@ impl Reranker {
|
|||||||
pub fn new(
|
pub fn new(
|
||||||
rerank_hybrid: Function<RerankHybridCallbackArgs, Promise<Buffer>>,
|
rerank_hybrid: Function<RerankHybridCallbackArgs, Promise<Buffer>>,
|
||||||
) -> napi::Result<Self> {
|
) -> napi::Result<Self> {
|
||||||
let rerank_hybrid = rerank_hybrid
|
let rerank_hybrid = rerank_hybrid.build_threadsafe_function().build()?;
|
||||||
.build_threadsafe_function()
|
|
||||||
.weak::<true>()
|
|
||||||
.build()?;
|
|
||||||
Ok(Self { rerank_hybrid })
|
Ok(Self { rerank_hybrid })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
[tool.bumpversion]
|
[tool.bumpversion]
|
||||||
current_version = "0.31.0-beta.11"
|
current_version = "0.31.0-beta.5"
|
||||||
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*)\\.
|
||||||
|
|||||||
+5
-6
@@ -1,7 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "lancedb-python"
|
name = "lancedb-python"
|
||||||
version = "0.31.0-beta.11"
|
version = "0.31.0-beta.5"
|
||||||
publish = false
|
|
||||||
edition.workspace = true
|
edition.workspace = true
|
||||||
description = "Python bindings for LanceDB"
|
description = "Python bindings for LanceDB"
|
||||||
license.workspace = true
|
license.workspace = true
|
||||||
@@ -15,7 +14,7 @@ name = "_lancedb"
|
|||||||
crate-type = ["cdylib"]
|
crate-type = ["cdylib"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
arrow = { version = "58.0.0", features = ["pyarrow"] }
|
arrow = { version = "57.2", features = ["pyarrow"] }
|
||||||
async-trait = "0.1"
|
async-trait = "0.1"
|
||||||
bytes = "1"
|
bytes = "1"
|
||||||
lancedb = { path = "../rust/lancedb", default-features = false }
|
lancedb = { path = "../rust/lancedb", default-features = false }
|
||||||
@@ -25,8 +24,8 @@ lance-namespace-impls.workspace = true
|
|||||||
lance-io.workspace = true
|
lance-io.workspace = true
|
||||||
env_logger.workspace = true
|
env_logger.workspace = true
|
||||||
log.workspace = true
|
log.workspace = true
|
||||||
pyo3 = { version = "0.28", features = ["extension-module", "abi3-py39"] }
|
pyo3 = { version = "0.26", features = ["extension-module", "abi3-py39"] }
|
||||||
pyo3-async-runtimes = { version = "0.28", features = [
|
pyo3-async-runtimes = { version = "0.26", features = [
|
||||||
"attributes",
|
"attributes",
|
||||||
"tokio-runtime",
|
"tokio-runtime",
|
||||||
] }
|
] }
|
||||||
@@ -38,7 +37,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.28", features = [
|
pyo3-build-config = { version = "0.26", features = [
|
||||||
"extension-module",
|
"extension-module",
|
||||||
"abi3-py39",
|
"abi3-py39",
|
||||||
] }
|
] }
|
||||||
|
|||||||
@@ -183,6 +183,7 @@
|
|||||||
| stack-data | 0.6.3 | MIT License | http://github.com/alexmojaki/stack_data |
|
| stack-data | 0.6.3 | MIT License | http://github.com/alexmojaki/stack_data |
|
||||||
| sympy | 1.14.0 | BSD License | https://sympy.org |
|
| sympy | 1.14.0 | BSD License | https://sympy.org |
|
||||||
| tabulate | 0.9.0 | MIT License | https://github.com/astanin/python-tabulate |
|
| tabulate | 0.9.0 | MIT License | https://github.com/astanin/python-tabulate |
|
||||||
|
| tantivy | 0.25.1 | UNKNOWN | UNKNOWN |
|
||||||
| threadpoolctl | 3.6.0 | BSD License | https://github.com/joblib/threadpoolctl |
|
| threadpoolctl | 3.6.0 | BSD License | https://github.com/joblib/threadpoolctl |
|
||||||
| timm | 1.0.24 | Apache Software License | https://github.com/huggingface/pytorch-image-models |
|
| timm | 1.0.24 | Apache Software License | https://github.com/huggingface/pytorch-image-models |
|
||||||
| tinycss2 | 1.4.0 | BSD License | https://www.courtbouillon.org/tinycss2 |
|
| tinycss2 | 1.4.0 | BSD License | https://www.courtbouillon.org/tinycss2 |
|
||||||
|
|||||||
@@ -57,6 +57,7 @@ tests = [
|
|||||||
"duckdb>=0.9.0",
|
"duckdb>=0.9.0",
|
||||||
"pytz>=2023.3",
|
"pytz>=2023.3",
|
||||||
"polars>=0.19, <=1.3.0",
|
"polars>=0.19, <=1.3.0",
|
||||||
|
"tantivy>=0.20.0",
|
||||||
"pyarrow-stubs>=16.0",
|
"pyarrow-stubs>=16.0",
|
||||||
"pylance>=5.0.0b5",
|
"pylance>=5.0.0b5",
|
||||||
"requests>=2.31.0",
|
"requests>=2.31.0",
|
||||||
|
|||||||
@@ -73,7 +73,6 @@ def connect(
|
|||||||
client_config: Union[ClientConfig, Dict[str, Any], None] = None,
|
client_config: Union[ClientConfig, Dict[str, Any], None] = None,
|
||||||
storage_options: Optional[Dict[str, str]] = None,
|
storage_options: Optional[Dict[str, str]] = None,
|
||||||
session: Optional[Session] = None,
|
session: Optional[Session] = None,
|
||||||
manifest_enabled: bool = False,
|
|
||||||
namespace_client_impl: Optional[str] = None,
|
namespace_client_impl: Optional[str] = None,
|
||||||
namespace_client_properties: Optional[Dict[str, str]] = None,
|
namespace_client_properties: Optional[Dict[str, str]] = None,
|
||||||
namespace_client_pushdown_operations: Optional[List[str]] = None,
|
namespace_client_pushdown_operations: Optional[List[str]] = None,
|
||||||
@@ -112,10 +111,6 @@ def connect(
|
|||||||
storage_options: dict, optional
|
storage_options: dict, optional
|
||||||
Additional options for the storage backend. See available options at
|
Additional options for the storage backend. See available options at
|
||||||
<https://docs.lancedb.com/storage/>
|
<https://docs.lancedb.com/storage/>
|
||||||
manifest_enabled : bool, default False
|
|
||||||
When true for local/native connections, use directory namespace
|
|
||||||
manifests as the source of truth for table metadata. Existing
|
|
||||||
directory-listed root tables are migrated into the manifest on access.
|
|
||||||
session: Session, optional
|
session: Session, optional
|
||||||
(For LanceDB OSS only)
|
(For LanceDB OSS only)
|
||||||
A session to use for this connection. Sessions allow you to configure
|
A session to use for this connection. Sessions allow you to configure
|
||||||
@@ -163,11 +158,11 @@ def connect(
|
|||||||
conn : DBConnection
|
conn : DBConnection
|
||||||
A connection to a LanceDB database.
|
A connection to a LanceDB database.
|
||||||
"""
|
"""
|
||||||
if namespace_client_impl is not None:
|
if namespace_client_impl is not None or namespace_client_properties is not None:
|
||||||
if namespace_client_properties is None:
|
if namespace_client_impl is None or namespace_client_properties is None:
|
||||||
raise ValueError(
|
raise ValueError(
|
||||||
"namespace_client_properties must be provided when "
|
"Both namespace_client_impl and "
|
||||||
"namespace_client_impl is set"
|
"namespace_client_properties must be provided"
|
||||||
)
|
)
|
||||||
if kwargs:
|
if kwargs:
|
||||||
raise ValueError(f"Unknown keyword arguments: {kwargs}")
|
raise ValueError(f"Unknown keyword arguments: {kwargs}")
|
||||||
@@ -180,12 +175,6 @@ def connect(
|
|||||||
namespace_client_pushdown_operations=namespace_client_pushdown_operations,
|
namespace_client_pushdown_operations=namespace_client_pushdown_operations,
|
||||||
)
|
)
|
||||||
|
|
||||||
if namespace_client_properties is not None and not manifest_enabled:
|
|
||||||
raise ValueError(
|
|
||||||
"namespace_client_impl must be provided when using "
|
|
||||||
"namespace_client_properties unless manifest_enabled=True"
|
|
||||||
)
|
|
||||||
|
|
||||||
if namespace_client_pushdown_operations is not None:
|
if namespace_client_pushdown_operations is not None:
|
||||||
raise ValueError(
|
raise ValueError(
|
||||||
"namespace_client_pushdown_operations is only valid when "
|
"namespace_client_pushdown_operations is only valid when "
|
||||||
@@ -223,8 +212,6 @@ def connect(
|
|||||||
read_consistency_interval=read_consistency_interval,
|
read_consistency_interval=read_consistency_interval,
|
||||||
storage_options=storage_options,
|
storage_options=storage_options,
|
||||||
session=session,
|
session=session,
|
||||||
manifest_enabled=manifest_enabled,
|
|
||||||
namespace_client_properties=namespace_client_properties,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@@ -302,8 +289,6 @@ def deserialize_conn(
|
|||||||
parsed["uri"],
|
parsed["uri"],
|
||||||
read_consistency_interval=rci,
|
read_consistency_interval=rci,
|
||||||
storage_options=storage_options,
|
storage_options=storage_options,
|
||||||
manifest_enabled=parsed.get("manifest_enabled", False),
|
|
||||||
namespace_client_properties=parsed.get("namespace_client_properties"),
|
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
raise ValueError(f"Unknown connection_type: {connection_type}")
|
raise ValueError(f"Unknown connection_type: {connection_type}")
|
||||||
@@ -319,8 +304,6 @@ async def connect_async(
|
|||||||
client_config: Optional[Union[ClientConfig, Dict[str, Any]]] = None,
|
client_config: Optional[Union[ClientConfig, Dict[str, Any]]] = None,
|
||||||
storage_options: Optional[Dict[str, str]] = None,
|
storage_options: Optional[Dict[str, str]] = None,
|
||||||
session: Optional[Session] = None,
|
session: Optional[Session] = None,
|
||||||
manifest_enabled: bool = False,
|
|
||||||
namespace_client_properties: Optional[Dict[str, str]] = None,
|
|
||||||
) -> AsyncConnection:
|
) -> AsyncConnection:
|
||||||
"""Connect to a LanceDB database.
|
"""Connect to a LanceDB database.
|
||||||
|
|
||||||
@@ -360,13 +343,6 @@ async def connect_async(
|
|||||||
cache sizes for index and metadata caches, which can significantly
|
cache sizes for index and metadata caches, which can significantly
|
||||||
impact memory use and performance. They can also be re-used across
|
impact memory use and performance. They can also be re-used across
|
||||||
multiple connections to share the same cache state.
|
multiple connections to share the same cache state.
|
||||||
manifest_enabled : bool, default False
|
|
||||||
When true for local/native connections, use directory namespace
|
|
||||||
manifests as the source of truth for table metadata. Existing
|
|
||||||
directory-listed root tables are migrated into the manifest on access.
|
|
||||||
namespace_client_properties : dict, optional
|
|
||||||
Additional directory namespace client properties to use with
|
|
||||||
``manifest_enabled=True``.
|
|
||||||
|
|
||||||
Examples
|
Examples
|
||||||
--------
|
--------
|
||||||
@@ -409,8 +385,6 @@ async def connect_async(
|
|||||||
client_config,
|
client_config,
|
||||||
storage_options,
|
storage_options,
|
||||||
session,
|
session,
|
||||||
manifest_enabled,
|
|
||||||
namespace_client_properties,
|
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -242,8 +242,6 @@ async def connect(
|
|||||||
client_config: Optional[Union[ClientConfig, Dict[str, Any]]],
|
client_config: Optional[Union[ClientConfig, Dict[str, Any]]],
|
||||||
storage_options: Optional[Dict[str, str]],
|
storage_options: Optional[Dict[str, str]],
|
||||||
session: Optional[Session],
|
session: Optional[Session],
|
||||||
manifest_enabled: bool = False,
|
|
||||||
namespace_client_properties: Optional[Dict[str, str]] = None,
|
|
||||||
) -> Connection: ...
|
) -> Connection: ...
|
||||||
|
|
||||||
class RecordBatchStream:
|
class RecordBatchStream:
|
||||||
|
|||||||
@@ -590,13 +590,8 @@ class LanceDBConnection(DBConnection):
|
|||||||
read_consistency_interval: Optional[timedelta] = None,
|
read_consistency_interval: Optional[timedelta] = None,
|
||||||
storage_options: Optional[Dict[str, str]] = None,
|
storage_options: Optional[Dict[str, str]] = None,
|
||||||
session: Optional[Session] = None,
|
session: Optional[Session] = None,
|
||||||
manifest_enabled: bool = False,
|
|
||||||
namespace_client_properties: Optional[Dict[str, str]] = None,
|
|
||||||
_inner: Optional[LanceDbConnection] = None,
|
_inner: Optional[LanceDbConnection] = None,
|
||||||
):
|
):
|
||||||
self.storage_options = storage_options
|
|
||||||
self._manifest_enabled = manifest_enabled
|
|
||||||
self._namespace_client_properties = namespace_client_properties
|
|
||||||
if _inner is not None:
|
if _inner is not None:
|
||||||
self._conn = _inner
|
self._conn = _inner
|
||||||
self._cached_namespace_client = None
|
self._cached_namespace_client = None
|
||||||
@@ -638,8 +633,6 @@ class LanceDBConnection(DBConnection):
|
|||||||
None,
|
None,
|
||||||
storage_options,
|
storage_options,
|
||||||
session,
|
session,
|
||||||
manifest_enabled,
|
|
||||||
namespace_client_properties,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
# TODO: It would be nice if we didn't store self.storage_options but it is
|
# TODO: It would be nice if we didn't store self.storage_options but it is
|
||||||
@@ -647,6 +640,7 @@ class LanceDBConnection(DBConnection):
|
|||||||
# work because some paths like LanceDBConnection.from_inner will lose the
|
# work because some paths like LanceDBConnection.from_inner will lose the
|
||||||
# storage_options. Also, this class really shouldn't be holding any state
|
# storage_options. Also, this class really shouldn't be holding any state
|
||||||
# beyond _conn.
|
# beyond _conn.
|
||||||
|
self.storage_options = storage_options
|
||||||
self._conn = AsyncConnection(LOOP.run(do_connect()))
|
self._conn = AsyncConnection(LOOP.run(do_connect()))
|
||||||
self._cached_namespace_client: Optional[LanceNamespace] = None
|
self._cached_namespace_client: Optional[LanceNamespace] = None
|
||||||
|
|
||||||
@@ -683,8 +677,6 @@ class LanceDBConnection(DBConnection):
|
|||||||
"connection_type": "local",
|
"connection_type": "local",
|
||||||
"uri": self.uri,
|
"uri": self.uri,
|
||||||
"storage_options": self.storage_options,
|
"storage_options": self.storage_options,
|
||||||
"manifest_enabled": self._manifest_enabled,
|
|
||||||
"namespace_client_properties": self._namespace_client_properties,
|
|
||||||
"read_consistency_interval_seconds": (
|
"read_consistency_interval_seconds": (
|
||||||
rci.total_seconds() if rci else None
|
rci.total_seconds() if rci else None
|
||||||
),
|
),
|
||||||
@@ -1398,7 +1390,6 @@ class AsyncConnection(object):
|
|||||||
namespace_path: Optional[List[str]] = None,
|
namespace_path: Optional[List[str]] = None,
|
||||||
embedding_functions: Optional[List[EmbeddingFunctionConfig]] = None,
|
embedding_functions: Optional[List[EmbeddingFunctionConfig]] = None,
|
||||||
location: Optional[str] = None,
|
location: Optional[str] = None,
|
||||||
namespace_client: Optional[Any] = None,
|
|
||||||
) -> AsyncTable:
|
) -> AsyncTable:
|
||||||
"""Create an [AsyncTable][lancedb.table.AsyncTable] in the database.
|
"""Create an [AsyncTable][lancedb.table.AsyncTable] in the database.
|
||||||
|
|
||||||
@@ -1596,7 +1587,6 @@ class AsyncConnection(object):
|
|||||||
namespace_path=namespace_path,
|
namespace_path=namespace_path,
|
||||||
storage_options=storage_options,
|
storage_options=storage_options,
|
||||||
location=location,
|
location=location,
|
||||||
namespace_client=namespace_client,
|
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
data = data_to_reader(data, schema)
|
data = data_to_reader(data, schema)
|
||||||
@@ -1607,7 +1597,6 @@ class AsyncConnection(object):
|
|||||||
namespace_path=namespace_path,
|
namespace_path=namespace_path,
|
||||||
storage_options=storage_options,
|
storage_options=storage_options,
|
||||||
location=location,
|
location=location,
|
||||||
namespace_client=namespace_client,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
return AsyncTable(new_table)
|
return AsyncTable(new_table)
|
||||||
|
|||||||
@@ -0,0 +1,201 @@
|
|||||||
|
# SPDX-License-Identifier: Apache-2.0
|
||||||
|
# SPDX-FileCopyrightText: Copyright The LanceDB Authors
|
||||||
|
|
||||||
|
"""Full text search index using tantivy-py"""
|
||||||
|
|
||||||
|
import os
|
||||||
|
from typing import List, Tuple, Optional
|
||||||
|
|
||||||
|
import pyarrow as pa
|
||||||
|
|
||||||
|
try:
|
||||||
|
import tantivy
|
||||||
|
except ImportError:
|
||||||
|
raise ImportError(
|
||||||
|
"Please install tantivy-py `pip install tantivy` to use the full text search feature." # noqa: E501
|
||||||
|
)
|
||||||
|
|
||||||
|
from .table import LanceTable
|
||||||
|
|
||||||
|
|
||||||
|
def create_index(
|
||||||
|
index_path: str,
|
||||||
|
text_fields: List[str],
|
||||||
|
ordering_fields: Optional[List[str]] = None,
|
||||||
|
tokenizer_name: str = "default",
|
||||||
|
) -> tantivy.Index:
|
||||||
|
"""
|
||||||
|
Create a new Index (not populated)
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
index_path : str
|
||||||
|
Path to the index directory
|
||||||
|
text_fields : List[str]
|
||||||
|
List of text fields to index
|
||||||
|
ordering_fields: List[str]
|
||||||
|
List of unsigned type fields to order by at search time
|
||||||
|
tokenizer_name : str, default "default"
|
||||||
|
The tokenizer to use
|
||||||
|
|
||||||
|
Returns
|
||||||
|
-------
|
||||||
|
index : tantivy.Index
|
||||||
|
The index object (not yet populated)
|
||||||
|
"""
|
||||||
|
if ordering_fields is None:
|
||||||
|
ordering_fields = []
|
||||||
|
# Declaring our schema.
|
||||||
|
schema_builder = tantivy.SchemaBuilder()
|
||||||
|
# special field that we'll populate with row_id
|
||||||
|
schema_builder.add_integer_field("doc_id", stored=True)
|
||||||
|
# data fields
|
||||||
|
for name in text_fields:
|
||||||
|
schema_builder.add_text_field(name, stored=True, tokenizer_name=tokenizer_name)
|
||||||
|
if ordering_fields:
|
||||||
|
for name in ordering_fields:
|
||||||
|
schema_builder.add_unsigned_field(name, fast=True)
|
||||||
|
schema = schema_builder.build()
|
||||||
|
os.makedirs(index_path, exist_ok=True)
|
||||||
|
index = tantivy.Index(schema, path=index_path)
|
||||||
|
return index
|
||||||
|
|
||||||
|
|
||||||
|
def populate_index(
|
||||||
|
index: tantivy.Index,
|
||||||
|
table: LanceTable,
|
||||||
|
fields: List[str],
|
||||||
|
writer_heap_size: Optional[int] = None,
|
||||||
|
ordering_fields: Optional[List[str]] = None,
|
||||||
|
) -> int:
|
||||||
|
"""
|
||||||
|
Populate an index with data from a LanceTable
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
index : tantivy.Index
|
||||||
|
The index object
|
||||||
|
table : LanceTable
|
||||||
|
The table to index
|
||||||
|
fields : List[str]
|
||||||
|
List of fields to index
|
||||||
|
writer_heap_size : int
|
||||||
|
The writer heap size in bytes, defaults to 1GB
|
||||||
|
|
||||||
|
Returns
|
||||||
|
-------
|
||||||
|
int
|
||||||
|
The number of rows indexed
|
||||||
|
"""
|
||||||
|
if ordering_fields is None:
|
||||||
|
ordering_fields = []
|
||||||
|
writer_heap_size = writer_heap_size or 1024 * 1024 * 1024
|
||||||
|
# first check the fields exist and are string or large string type
|
||||||
|
nested = []
|
||||||
|
|
||||||
|
for name in fields:
|
||||||
|
try:
|
||||||
|
f = table.schema.field(name) # raises KeyError if not found
|
||||||
|
except KeyError:
|
||||||
|
f = resolve_path(table.schema, name)
|
||||||
|
nested.append(name)
|
||||||
|
|
||||||
|
if not pa.types.is_string(f.type) and not pa.types.is_large_string(f.type):
|
||||||
|
raise TypeError(f"Field {name} is not a string type")
|
||||||
|
|
||||||
|
# create a tantivy writer
|
||||||
|
writer = index.writer(heap_size=writer_heap_size)
|
||||||
|
# write data into index
|
||||||
|
dataset = table.to_lance()
|
||||||
|
row_id = 0
|
||||||
|
|
||||||
|
max_nested_level = 0
|
||||||
|
if len(nested) > 0:
|
||||||
|
max_nested_level = max([len(name.split(".")) for name in nested])
|
||||||
|
|
||||||
|
for b in dataset.to_batches(columns=fields + ordering_fields):
|
||||||
|
if max_nested_level > 0:
|
||||||
|
b = pa.Table.from_batches([b])
|
||||||
|
for _ in range(max_nested_level - 1):
|
||||||
|
b = b.flatten()
|
||||||
|
for i in range(b.num_rows):
|
||||||
|
doc = tantivy.Document()
|
||||||
|
for name in fields:
|
||||||
|
value = b[name][i].as_py()
|
||||||
|
if value is not None:
|
||||||
|
doc.add_text(name, value)
|
||||||
|
for name in ordering_fields:
|
||||||
|
value = b[name][i].as_py()
|
||||||
|
if value is not None:
|
||||||
|
doc.add_unsigned(name, value)
|
||||||
|
if not doc.is_empty:
|
||||||
|
doc.add_integer("doc_id", row_id)
|
||||||
|
writer.add_document(doc)
|
||||||
|
row_id += 1
|
||||||
|
# commit changes
|
||||||
|
writer.commit()
|
||||||
|
return row_id
|
||||||
|
|
||||||
|
|
||||||
|
def resolve_path(schema, field_name: str) -> pa.Field:
|
||||||
|
"""
|
||||||
|
Resolve a nested field path to a list of field names
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
field_name : str
|
||||||
|
The field name to resolve
|
||||||
|
|
||||||
|
Returns
|
||||||
|
-------
|
||||||
|
List[str]
|
||||||
|
The resolved path
|
||||||
|
"""
|
||||||
|
path = field_name.split(".")
|
||||||
|
field = schema.field(path.pop(0))
|
||||||
|
for segment in path:
|
||||||
|
if pa.types.is_struct(field.type):
|
||||||
|
field = field.type.field(segment)
|
||||||
|
else:
|
||||||
|
raise KeyError(f"field {field_name} not found in schema {schema}")
|
||||||
|
return field
|
||||||
|
|
||||||
|
|
||||||
|
def search_index(
|
||||||
|
index: tantivy.Index, query: str, limit: int = 10, ordering_field=None
|
||||||
|
) -> Tuple[Tuple[int], Tuple[float]]:
|
||||||
|
"""
|
||||||
|
Search an index for a query
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
index : tantivy.Index
|
||||||
|
The index object
|
||||||
|
query : str
|
||||||
|
The query string
|
||||||
|
limit : int
|
||||||
|
The maximum number of results to return
|
||||||
|
|
||||||
|
Returns
|
||||||
|
-------
|
||||||
|
ids_and_score: list[tuple[int], tuple[float]]
|
||||||
|
A tuple of two tuples, the first containing the document ids
|
||||||
|
and the second containing the scores
|
||||||
|
"""
|
||||||
|
searcher = index.searcher()
|
||||||
|
query = index.parse_query(query)
|
||||||
|
# get top results
|
||||||
|
if ordering_field:
|
||||||
|
results = searcher.search(query, limit, order_by_field=ordering_field)
|
||||||
|
else:
|
||||||
|
results = searcher.search(query, limit)
|
||||||
|
if results.count == 0:
|
||||||
|
return tuple(), tuple()
|
||||||
|
return tuple(
|
||||||
|
zip(
|
||||||
|
*[
|
||||||
|
(searcher.doc(doc_address)["doc_id"][0], score)
|
||||||
|
for score, doc_address in results.hits
|
||||||
|
]
|
||||||
|
)
|
||||||
|
)
|
||||||
+357
-111
@@ -10,6 +10,7 @@ through a namespace abstraction.
|
|||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import asyncio
|
||||||
import sys
|
import sys
|
||||||
from typing import TYPE_CHECKING, Any, Dict, Iterable, List, Optional, Union
|
from typing import TYPE_CHECKING, Any, Dict, Iterable, List, Optional, Union
|
||||||
|
|
||||||
@@ -24,24 +25,7 @@ if TYPE_CHECKING:
|
|||||||
from datetime import timedelta
|
from datetime import timedelta
|
||||||
import pyarrow as pa
|
import pyarrow as pa
|
||||||
|
|
||||||
from lance_namespace_urllib3_client.models.json_arrow_data_type import JsonArrowDataType
|
from lancedb.db import DBConnection, LanceDBConnection
|
||||||
from lance_namespace_urllib3_client.models.json_arrow_field import JsonArrowField
|
|
||||||
from lance_namespace_urllib3_client.models.json_arrow_schema import JsonArrowSchema
|
|
||||||
from lance_namespace_urllib3_client.models.query_table_request import QueryTableRequest
|
|
||||||
from lance_namespace_urllib3_client.models.query_table_request_columns import (
|
|
||||||
QueryTableRequestColumns,
|
|
||||||
)
|
|
||||||
from lance_namespace_urllib3_client.models.query_table_request_full_text_query import (
|
|
||||||
QueryTableRequestFullTextQuery,
|
|
||||||
)
|
|
||||||
from lance_namespace_urllib3_client.models.query_table_request_vector import (
|
|
||||||
QueryTableRequestVector,
|
|
||||||
)
|
|
||||||
from lance_namespace_urllib3_client.models.string_fts_query import StringFtsQuery
|
|
||||||
from lance_namespace.errors import TableNotFoundError
|
|
||||||
from lancedb._lancedb import connect_namespace_client as _connect_namespace_client
|
|
||||||
from lancedb.background_loop import LOOP
|
|
||||||
from lancedb.db import AsyncConnection, DBConnection
|
|
||||||
from lancedb.namespace_utils import (
|
from lancedb.namespace_utils import (
|
||||||
_normalize_create_namespace_mode,
|
_normalize_create_namespace_mode,
|
||||||
_normalize_drop_namespace_mode,
|
_normalize_drop_namespace_mode,
|
||||||
@@ -56,11 +40,14 @@ from lance_namespace import (
|
|||||||
ListNamespacesResponse,
|
ListNamespacesResponse,
|
||||||
ListTablesResponse,
|
ListTablesResponse,
|
||||||
ListTablesRequest,
|
ListTablesRequest,
|
||||||
|
DescribeTableRequest,
|
||||||
DescribeNamespaceRequest,
|
DescribeNamespaceRequest,
|
||||||
DropTableRequest,
|
DropTableRequest,
|
||||||
ListNamespacesRequest,
|
ListNamespacesRequest,
|
||||||
CreateNamespaceRequest,
|
CreateNamespaceRequest,
|
||||||
DropNamespaceRequest,
|
DropNamespaceRequest,
|
||||||
|
DeclareTableRequest,
|
||||||
|
CreateTableRequest,
|
||||||
)
|
)
|
||||||
from lancedb.table import AsyncTable, LanceTable, Table
|
from lancedb.table import AsyncTable, LanceTable, Table
|
||||||
from lancedb.util import validate_table_name
|
from lancedb.util import validate_table_name
|
||||||
@@ -69,6 +56,21 @@ from lancedb.pydantic import LanceModel
|
|||||||
from lancedb.embeddings import EmbeddingFunctionConfig
|
from lancedb.embeddings import EmbeddingFunctionConfig
|
||||||
from ._lancedb import Session
|
from ._lancedb import Session
|
||||||
|
|
||||||
|
from lance_namespace_urllib3_client.models.json_arrow_schema import JsonArrowSchema
|
||||||
|
from lance_namespace_urllib3_client.models.json_arrow_field import JsonArrowField
|
||||||
|
from lance_namespace_urllib3_client.models.json_arrow_data_type import JsonArrowDataType
|
||||||
|
from lance_namespace_urllib3_client.models.query_table_request import QueryTableRequest
|
||||||
|
from lance_namespace_urllib3_client.models.query_table_request_vector import (
|
||||||
|
QueryTableRequestVector,
|
||||||
|
)
|
||||||
|
from lance_namespace_urllib3_client.models.query_table_request_columns import (
|
||||||
|
QueryTableRequestColumns,
|
||||||
|
)
|
||||||
|
from lance_namespace_urllib3_client.models.query_table_request_full_text_query import (
|
||||||
|
QueryTableRequestFullTextQuery,
|
||||||
|
)
|
||||||
|
from lance_namespace_urllib3_client.models.string_fts_query import StringFtsQuery
|
||||||
|
|
||||||
|
|
||||||
def _query_to_namespace_request(
|
def _query_to_namespace_request(
|
||||||
table_id: List[str],
|
table_id: List[str],
|
||||||
@@ -422,23 +424,6 @@ class LanceNamespaceDBConnection(DBConnection):
|
|||||||
)
|
)
|
||||||
self._namespace_client_impl = namespace_client_impl
|
self._namespace_client_impl = namespace_client_impl
|
||||||
self._namespace_client_properties = namespace_client_properties
|
self._namespace_client_properties = namespace_client_properties
|
||||||
self._inner = AsyncConnection(
|
|
||||||
_connect_namespace_client(
|
|
||||||
namespace_client,
|
|
||||||
read_consistency_interval=(
|
|
||||||
read_consistency_interval.total_seconds()
|
|
||||||
if read_consistency_interval is not None
|
|
||||||
else None
|
|
||||||
),
|
|
||||||
storage_options=self.storage_options or None,
|
|
||||||
session=session,
|
|
||||||
namespace_client_pushdown_operations=(
|
|
||||||
list(self._namespace_client_pushdown_operations)
|
|
||||||
),
|
|
||||||
namespace_client_impl=namespace_client_impl,
|
|
||||||
namespace_client_properties=namespace_client_properties,
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
def serialize(self) -> str:
|
def serialize(self) -> str:
|
||||||
@@ -512,10 +497,13 @@ class LanceNamespaceDBConnection(DBConnection):
|
|||||||
if mode.lower() not in ["create", "overwrite"]:
|
if mode.lower() not in ["create", "overwrite"]:
|
||||||
raise ValueError("mode must be either 'create' or 'overwrite'")
|
raise ValueError("mode must be either 'create' or 'overwrite'")
|
||||||
validate_table_name(name)
|
validate_table_name(name)
|
||||||
async_table = LOOP.run(
|
|
||||||
self._inner.create_table(
|
table_id = namespace_path + [name]
|
||||||
name,
|
|
||||||
data,
|
if "CreateTable" in self._namespace_client_pushdown_operations:
|
||||||
|
return self._create_table_server_side(
|
||||||
|
name=name,
|
||||||
|
data=data,
|
||||||
schema=schema,
|
schema=schema,
|
||||||
mode=mode,
|
mode=mode,
|
||||||
exist_ok=exist_ok,
|
exist_ok=exist_ok,
|
||||||
@@ -525,15 +513,130 @@ class LanceNamespaceDBConnection(DBConnection):
|
|||||||
namespace_path=namespace_path,
|
namespace_path=namespace_path,
|
||||||
storage_options=storage_options,
|
storage_options=storage_options,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# Local create path: declare_table + local write
|
||||||
|
# Step 1: Get the table location and storage options from namespace
|
||||||
|
# In overwrite mode, if table exists, use describe_table to get
|
||||||
|
# existing location. Otherwise, call create_empty_table to reserve
|
||||||
|
# a new location
|
||||||
|
location = None
|
||||||
|
namespace_storage_options = None
|
||||||
|
if mode.lower() == "overwrite":
|
||||||
|
# Try to describe the table first to see if it exists
|
||||||
|
try:
|
||||||
|
describe_request = DescribeTableRequest(id=table_id)
|
||||||
|
describe_response = self._namespace_client.describe_table(
|
||||||
|
describe_request
|
||||||
|
)
|
||||||
|
location = describe_response.location
|
||||||
|
namespace_storage_options = describe_response.storage_options
|
||||||
|
except Exception:
|
||||||
|
# Table doesn't exist, will create a new one below
|
||||||
|
pass
|
||||||
|
|
||||||
|
if location is None:
|
||||||
|
# Table doesn't exist or mode is "create", reserve a new location
|
||||||
|
declare_request = DeclareTableRequest(
|
||||||
|
id=table_id,
|
||||||
|
location=None,
|
||||||
|
properties=self.storage_options if self.storage_options else None,
|
||||||
|
)
|
||||||
|
declare_response = self._namespace_client.declare_table(declare_request)
|
||||||
|
|
||||||
|
if not declare_response.location:
|
||||||
|
raise ValueError(
|
||||||
|
"Table location is missing from declare_table response"
|
||||||
|
)
|
||||||
|
|
||||||
|
location = declare_response.location
|
||||||
|
namespace_storage_options = declare_response.storage_options
|
||||||
|
|
||||||
|
# Merge storage options: self.storage_options < user options < namespace options
|
||||||
|
merged_storage_options = dict(self.storage_options)
|
||||||
|
if storage_options:
|
||||||
|
merged_storage_options.update(storage_options)
|
||||||
|
if namespace_storage_options:
|
||||||
|
merged_storage_options.update(namespace_storage_options)
|
||||||
|
|
||||||
|
# Step 2: Create table using LanceTable.create with the location
|
||||||
|
# We need a temporary connection for the LanceTable.create method
|
||||||
|
temp_conn = LanceDBConnection(
|
||||||
|
location, # Use the actual location as the connection URI
|
||||||
|
read_consistency_interval=self.read_consistency_interval,
|
||||||
|
storage_options=merged_storage_options,
|
||||||
|
session=self.session,
|
||||||
)
|
)
|
||||||
|
|
||||||
return LanceTable(
|
# Note: storage_options_provider is auto-created in Rust from namespace_client
|
||||||
self,
|
tbl = LanceTable.create(
|
||||||
|
temp_conn,
|
||||||
name,
|
name,
|
||||||
|
data,
|
||||||
|
schema,
|
||||||
|
mode=mode,
|
||||||
|
exist_ok=exist_ok,
|
||||||
|
on_bad_vectors=on_bad_vectors,
|
||||||
|
fill_value=fill_value,
|
||||||
|
embedding_functions=embedding_functions,
|
||||||
namespace_path=namespace_path,
|
namespace_path=namespace_path,
|
||||||
|
storage_options=merged_storage_options,
|
||||||
|
location=location,
|
||||||
namespace_client=self._namespace_client,
|
namespace_client=self._namespace_client,
|
||||||
pushdown_operations=self._namespace_client_pushdown_operations,
|
pushdown_operations=self._namespace_client_pushdown_operations,
|
||||||
_async=async_table,
|
)
|
||||||
|
|
||||||
|
return tbl
|
||||||
|
|
||||||
|
def _create_table_server_side(
|
||||||
|
self,
|
||||||
|
name: str,
|
||||||
|
data: Optional[DATA],
|
||||||
|
schema: Optional[Union[pa.Schema, LanceModel]],
|
||||||
|
mode: str,
|
||||||
|
exist_ok: bool,
|
||||||
|
on_bad_vectors: str,
|
||||||
|
fill_value: float,
|
||||||
|
embedding_functions: Optional[List[EmbeddingFunctionConfig]],
|
||||||
|
namespace_path: Optional[List[str]],
|
||||||
|
storage_options: Optional[Dict[str, str]],
|
||||||
|
) -> Table:
|
||||||
|
"""Create a table using server-side namespace.create_table()."""
|
||||||
|
if namespace_path is None:
|
||||||
|
namespace_path = []
|
||||||
|
table_id = namespace_path + [name]
|
||||||
|
|
||||||
|
arrow_ipc_bytes = _data_to_arrow_ipc(
|
||||||
|
data=data,
|
||||||
|
schema=schema,
|
||||||
|
embedding_functions=embedding_functions,
|
||||||
|
on_bad_vectors=on_bad_vectors,
|
||||||
|
fill_value=fill_value,
|
||||||
|
)
|
||||||
|
|
||||||
|
merged = dict(self.storage_options or {})
|
||||||
|
if storage_options:
|
||||||
|
merged.update(storage_options)
|
||||||
|
request = CreateTableRequest(
|
||||||
|
id=table_id,
|
||||||
|
mode=_normalize_create_table_mode(mode),
|
||||||
|
properties=merged or None,
|
||||||
|
)
|
||||||
|
|
||||||
|
try:
|
||||||
|
self._namespace_client.create_table(request, arrow_ipc_bytes)
|
||||||
|
except Exception as e:
|
||||||
|
if exist_ok and "already exists" in str(e).lower():
|
||||||
|
return self.open_table(
|
||||||
|
name,
|
||||||
|
namespace_path=namespace_path,
|
||||||
|
storage_options=storage_options,
|
||||||
|
)
|
||||||
|
raise
|
||||||
|
|
||||||
|
return self.open_table(
|
||||||
|
name,
|
||||||
|
namespace_path=namespace_path,
|
||||||
|
storage_options=storage_options,
|
||||||
)
|
)
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@@ -547,28 +650,30 @@ class LanceNamespaceDBConnection(DBConnection):
|
|||||||
) -> Table:
|
) -> Table:
|
||||||
if namespace_path is None:
|
if namespace_path is None:
|
||||||
namespace_path = []
|
namespace_path = []
|
||||||
try:
|
table_id = namespace_path + [name]
|
||||||
async_table = LOOP.run(
|
request = DescribeTableRequest(id=table_id)
|
||||||
self._inner.open_table(
|
response = self._namespace_client.describe_table(request)
|
||||||
name,
|
|
||||||
namespace_path=namespace_path,
|
|
||||||
storage_options=storage_options,
|
|
||||||
index_cache_size=index_cache_size,
|
|
||||||
)
|
|
||||||
)
|
|
||||||
except RuntimeError as e:
|
|
||||||
if "Table not found" in str(e):
|
|
||||||
table_id = namespace_path + [name]
|
|
||||||
raise TableNotFoundError(f"Table not found: {'$'.join(table_id)}")
|
|
||||||
raise
|
|
||||||
|
|
||||||
return LanceTable(
|
# Merge storage options: self.storage_options < user options < namespace options
|
||||||
self,
|
merged_storage_options = dict(self.storage_options)
|
||||||
|
if storage_options:
|
||||||
|
merged_storage_options.update(storage_options)
|
||||||
|
if response.storage_options:
|
||||||
|
merged_storage_options.update(response.storage_options)
|
||||||
|
|
||||||
|
# Pass managed_versioning to avoid redundant describe_table call in Rust.
|
||||||
|
# Convert None to False since we already have the answer from describe_table.
|
||||||
|
managed_versioning = response.managed_versioning is True
|
||||||
|
|
||||||
|
# Note: storage_options_provider is auto-created in Rust from namespace_client
|
||||||
|
return self._lance_table_from_uri(
|
||||||
name,
|
name,
|
||||||
|
response.location,
|
||||||
namespace_path=namespace_path,
|
namespace_path=namespace_path,
|
||||||
|
storage_options=merged_storage_options,
|
||||||
|
index_cache_size=index_cache_size,
|
||||||
namespace_client=self._namespace_client,
|
namespace_client=self._namespace_client,
|
||||||
pushdown_operations=self._namespace_client_pushdown_operations,
|
managed_versioning=managed_versioning,
|
||||||
_async=async_table,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@@ -792,34 +897,33 @@ class LanceNamespaceDBConnection(DBConnection):
|
|||||||
namespace_client: Optional[Any] = None,
|
namespace_client: Optional[Any] = None,
|
||||||
managed_versioning: Optional[bool] = None,
|
managed_versioning: Optional[bool] = None,
|
||||||
) -> LanceTable:
|
) -> LanceTable:
|
||||||
# Open a table directly from the namespace-resolved physical location.
|
# Open a table directly from a URI using the location parameter
|
||||||
#
|
# Note: storage_options should already be merged by the caller
|
||||||
# Open the table through the Rust namespace-backed connection. The Rust
|
# Note: storage_options_provider is auto-created in Rust from namespace_client
|
||||||
# layer keeps the logical namespace path and namespace client intact.
|
|
||||||
if namespace_path is None:
|
if namespace_path is None:
|
||||||
namespace_path = []
|
namespace_path = []
|
||||||
|
temp_conn = LanceDBConnection(
|
||||||
async_table = LOOP.run(
|
table_uri, # Use the table location as the connection URI
|
||||||
self._inner.open_table(
|
read_consistency_interval=self.read_consistency_interval,
|
||||||
name,
|
storage_options=storage_options if storage_options is not None else {},
|
||||||
namespace_path=namespace_path,
|
session=self.session,
|
||||||
storage_options=storage_options,
|
|
||||||
index_cache_size=index_cache_size,
|
|
||||||
location=None,
|
|
||||||
namespace_client=namespace_client,
|
|
||||||
managed_versioning=managed_versioning,
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
|
|
||||||
return LanceTable(
|
# Open the table using the temporary connection with the location parameter
|
||||||
self,
|
# Pass namespace_client to enable managed versioning support and auto-create
|
||||||
|
# storage options provider
|
||||||
|
# Pass managed_versioning to avoid redundant describe_table call
|
||||||
|
# Pass pushdown_operations if configured on this connection
|
||||||
|
return LanceTable.open(
|
||||||
|
temp_conn,
|
||||||
name,
|
name,
|
||||||
namespace_path=namespace_path,
|
namespace_path=namespace_path,
|
||||||
|
storage_options=storage_options,
|
||||||
|
index_cache_size=index_cache_size,
|
||||||
location=table_uri,
|
location=table_uri,
|
||||||
namespace_client=namespace_client,
|
namespace_client=namespace_client,
|
||||||
managed_versioning=managed_versioning,
|
managed_versioning=managed_versioning,
|
||||||
pushdown_operations=self._namespace_client_pushdown_operations,
|
pushdown_operations=self._namespace_client_pushdown_operations,
|
||||||
_async=async_table,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@@ -886,23 +990,6 @@ class AsyncLanceNamespaceDBConnection:
|
|||||||
self._namespace_client_pushdown_operations = set(
|
self._namespace_client_pushdown_operations = set(
|
||||||
namespace_client_pushdown_operations or []
|
namespace_client_pushdown_operations or []
|
||||||
)
|
)
|
||||||
self._inner = AsyncConnection(
|
|
||||||
_connect_namespace_client(
|
|
||||||
namespace_client,
|
|
||||||
read_consistency_interval=(
|
|
||||||
read_consistency_interval.total_seconds()
|
|
||||||
if read_consistency_interval is not None
|
|
||||||
else None
|
|
||||||
),
|
|
||||||
storage_options=self.storage_options or None,
|
|
||||||
session=session,
|
|
||||||
namespace_client_pushdown_operations=(
|
|
||||||
list(self._namespace_client_pushdown_operations)
|
|
||||||
),
|
|
||||||
namespace_client_impl=None,
|
|
||||||
namespace_client_properties=None,
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
async def table_names(
|
async def table_names(
|
||||||
self,
|
self,
|
||||||
@@ -954,16 +1041,148 @@ class AsyncLanceNamespaceDBConnection:
|
|||||||
if mode.lower() not in ["create", "overwrite"]:
|
if mode.lower() not in ["create", "overwrite"]:
|
||||||
raise ValueError("mode must be either 'create' or 'overwrite'")
|
raise ValueError("mode must be either 'create' or 'overwrite'")
|
||||||
validate_table_name(name)
|
validate_table_name(name)
|
||||||
return await self._inner.create_table(
|
|
||||||
|
table_id = namespace_path + [name]
|
||||||
|
|
||||||
|
if "CreateTable" in self._namespace_client_pushdown_operations:
|
||||||
|
return await self._create_table_server_side(
|
||||||
|
name=name,
|
||||||
|
data=data,
|
||||||
|
schema=schema,
|
||||||
|
mode=mode,
|
||||||
|
exist_ok=exist_ok,
|
||||||
|
on_bad_vectors=on_bad_vectors,
|
||||||
|
fill_value=fill_value,
|
||||||
|
embedding_functions=embedding_functions,
|
||||||
|
namespace_path=namespace_path,
|
||||||
|
storage_options=storage_options,
|
||||||
|
)
|
||||||
|
|
||||||
|
# Local create path: declare_table + local write
|
||||||
|
# Step 1: Get the table location and storage options from namespace
|
||||||
|
location = None
|
||||||
|
namespace_storage_options = None
|
||||||
|
if mode.lower() == "overwrite":
|
||||||
|
# Try to describe the table first to see if it exists
|
||||||
|
try:
|
||||||
|
describe_request = DescribeTableRequest(id=table_id)
|
||||||
|
describe_response = self._namespace_client.describe_table(
|
||||||
|
describe_request
|
||||||
|
)
|
||||||
|
location = describe_response.location
|
||||||
|
namespace_storage_options = describe_response.storage_options
|
||||||
|
except Exception:
|
||||||
|
# Table doesn't exist, will create a new one below
|
||||||
|
pass
|
||||||
|
|
||||||
|
if location is None:
|
||||||
|
# Table doesn't exist or mode is "create", reserve a new location
|
||||||
|
declare_request = DeclareTableRequest(
|
||||||
|
id=table_id,
|
||||||
|
location=None,
|
||||||
|
properties=self.storage_options if self.storage_options else None,
|
||||||
|
)
|
||||||
|
declare_response = self._namespace_client.declare_table(declare_request)
|
||||||
|
|
||||||
|
if not declare_response.location:
|
||||||
|
raise ValueError(
|
||||||
|
"Table location is missing from declare_table response"
|
||||||
|
)
|
||||||
|
|
||||||
|
location = declare_response.location
|
||||||
|
namespace_storage_options = declare_response.storage_options
|
||||||
|
|
||||||
|
# Merge storage options: self.storage_options < user options < namespace options
|
||||||
|
merged_storage_options = dict(self.storage_options)
|
||||||
|
if storage_options:
|
||||||
|
merged_storage_options.update(storage_options)
|
||||||
|
if namespace_storage_options:
|
||||||
|
merged_storage_options.update(namespace_storage_options)
|
||||||
|
|
||||||
|
# Step 2: Create table using LanceTable.create with the location
|
||||||
|
# Run the sync operation in a thread
|
||||||
|
def _create_table():
|
||||||
|
temp_conn = LanceDBConnection(
|
||||||
|
location,
|
||||||
|
read_consistency_interval=self.read_consistency_interval,
|
||||||
|
storage_options=merged_storage_options,
|
||||||
|
session=self.session,
|
||||||
|
)
|
||||||
|
|
||||||
|
# storage_options_provider is auto-created in Rust from namespace_client
|
||||||
|
return LanceTable.create(
|
||||||
|
temp_conn,
|
||||||
|
name,
|
||||||
|
data,
|
||||||
|
schema,
|
||||||
|
mode=mode,
|
||||||
|
exist_ok=exist_ok,
|
||||||
|
on_bad_vectors=on_bad_vectors,
|
||||||
|
fill_value=fill_value,
|
||||||
|
embedding_functions=embedding_functions,
|
||||||
|
namespace_path=namespace_path,
|
||||||
|
storage_options=merged_storage_options,
|
||||||
|
location=location,
|
||||||
|
namespace_client=self._namespace_client,
|
||||||
|
pushdown_operations=self._namespace_client_pushdown_operations,
|
||||||
|
)
|
||||||
|
|
||||||
|
lance_table = await asyncio.to_thread(_create_table)
|
||||||
|
# Get the underlying async table from LanceTable
|
||||||
|
return lance_table._table
|
||||||
|
|
||||||
|
async def _create_table_server_side(
|
||||||
|
self,
|
||||||
|
name: str,
|
||||||
|
data: Optional[DATA],
|
||||||
|
schema: Optional[Union[pa.Schema, LanceModel]],
|
||||||
|
mode: str,
|
||||||
|
exist_ok: bool,
|
||||||
|
on_bad_vectors: str,
|
||||||
|
fill_value: float,
|
||||||
|
embedding_functions: Optional[List[EmbeddingFunctionConfig]],
|
||||||
|
namespace_path: Optional[List[str]],
|
||||||
|
storage_options: Optional[Dict[str, str]],
|
||||||
|
) -> AsyncTable:
|
||||||
|
"""Create a table using server-side namespace.create_table()."""
|
||||||
|
if namespace_path is None:
|
||||||
|
namespace_path = []
|
||||||
|
table_id = namespace_path + [name]
|
||||||
|
|
||||||
|
def _prepare_and_create():
|
||||||
|
arrow_ipc_bytes = _data_to_arrow_ipc(
|
||||||
|
data=data,
|
||||||
|
schema=schema,
|
||||||
|
embedding_functions=embedding_functions,
|
||||||
|
on_bad_vectors=on_bad_vectors,
|
||||||
|
fill_value=fill_value,
|
||||||
|
)
|
||||||
|
|
||||||
|
merged = dict(self.storage_options or {})
|
||||||
|
if storage_options:
|
||||||
|
merged.update(storage_options)
|
||||||
|
request = CreateTableRequest(
|
||||||
|
id=table_id,
|
||||||
|
mode=_normalize_create_table_mode(mode),
|
||||||
|
properties=merged or None,
|
||||||
|
)
|
||||||
|
|
||||||
|
self._namespace_client.create_table(request, arrow_ipc_bytes)
|
||||||
|
|
||||||
|
try:
|
||||||
|
await asyncio.to_thread(_prepare_and_create)
|
||||||
|
except Exception as e:
|
||||||
|
if exist_ok and "already exists" in str(e).lower():
|
||||||
|
return await self.open_table(
|
||||||
|
name,
|
||||||
|
namespace_path=namespace_path,
|
||||||
|
storage_options=storage_options,
|
||||||
|
)
|
||||||
|
raise
|
||||||
|
|
||||||
|
return await self.open_table(
|
||||||
name,
|
name,
|
||||||
data,
|
|
||||||
schema=schema,
|
|
||||||
mode=mode,
|
|
||||||
exist_ok=exist_ok,
|
|
||||||
on_bad_vectors=on_bad_vectors,
|
|
||||||
fill_value=fill_value,
|
|
||||||
namespace_path=namespace_path,
|
namespace_path=namespace_path,
|
||||||
embedding_functions=embedding_functions,
|
|
||||||
storage_options=storage_options,
|
storage_options=storage_options,
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -978,18 +1197,45 @@ class AsyncLanceNamespaceDBConnection:
|
|||||||
"""Open an existing table from the namespace."""
|
"""Open an existing table from the namespace."""
|
||||||
if namespace_path is None:
|
if namespace_path is None:
|
||||||
namespace_path = []
|
namespace_path = []
|
||||||
try:
|
table_id = namespace_path + [name]
|
||||||
return await self._inner.open_table(
|
request = DescribeTableRequest(id=table_id)
|
||||||
|
response = self._namespace_client.describe_table(request)
|
||||||
|
|
||||||
|
# Merge storage options: self.storage_options < user options < namespace options
|
||||||
|
merged_storage_options = dict(self.storage_options)
|
||||||
|
if storage_options:
|
||||||
|
merged_storage_options.update(storage_options)
|
||||||
|
if response.storage_options:
|
||||||
|
merged_storage_options.update(response.storage_options)
|
||||||
|
|
||||||
|
# Capture managed_versioning from describe response.
|
||||||
|
# Convert None to False since we already have the answer from describe_table.
|
||||||
|
managed_versioning = response.managed_versioning is True
|
||||||
|
|
||||||
|
# Open table in a thread
|
||||||
|
# Note: storage_options_provider is auto-created in Rust from namespace_client
|
||||||
|
def _open_table():
|
||||||
|
temp_conn = LanceDBConnection(
|
||||||
|
response.location,
|
||||||
|
read_consistency_interval=self.read_consistency_interval,
|
||||||
|
storage_options=merged_storage_options,
|
||||||
|
session=self.session,
|
||||||
|
)
|
||||||
|
|
||||||
|
return LanceTable.open(
|
||||||
|
temp_conn,
|
||||||
name,
|
name,
|
||||||
namespace_path=namespace_path,
|
namespace_path=namespace_path,
|
||||||
storage_options=storage_options,
|
storage_options=merged_storage_options,
|
||||||
index_cache_size=index_cache_size,
|
index_cache_size=index_cache_size,
|
||||||
|
location=response.location,
|
||||||
|
namespace_client=self._namespace_client,
|
||||||
|
managed_versioning=managed_versioning,
|
||||||
|
pushdown_operations=self._namespace_client_pushdown_operations,
|
||||||
)
|
)
|
||||||
except RuntimeError as e:
|
|
||||||
if "Table not found" in str(e):
|
lance_table = await asyncio.to_thread(_open_table)
|
||||||
table_id = namespace_path + [name]
|
return lance_table._table
|
||||||
raise TableNotFoundError(f"Table not found: {'$'.join(table_id)}")
|
|
||||||
raise
|
|
||||||
|
|
||||||
async def drop_table(self, name: str, namespace_path: Optional[List[str]] = None):
|
async def drop_table(self, name: str, namespace_path: Optional[List[str]] = None):
|
||||||
"""Drop a table from the namespace."""
|
"""Drop a table from the namespace."""
|
||||||
|
|||||||
@@ -25,6 +25,7 @@ import deprecation
|
|||||||
import numpy as np
|
import numpy as np
|
||||||
import pyarrow as pa
|
import pyarrow as pa
|
||||||
import pyarrow.compute as pc
|
import pyarrow.compute as pc
|
||||||
|
import pyarrow.fs as pa_fs
|
||||||
import pydantic
|
import pydantic
|
||||||
|
|
||||||
from lancedb.pydantic import PYDANTIC_VERSION
|
from lancedb.pydantic import PYDANTIC_VERSION
|
||||||
@@ -1525,7 +1526,9 @@ class LanceFtsQueryBuilder(LanceQueryBuilder):
|
|||||||
return self._table._output_schema(self.to_query_object())
|
return self._table._output_schema(self.to_query_object())
|
||||||
|
|
||||||
def to_arrow(self, *, timeout: Optional[timedelta] = None) -> pa.Table:
|
def to_arrow(self, *, timeout: Optional[timedelta] = None) -> pa.Table:
|
||||||
self._table._ensure_no_legacy_fts_index()
|
path, fs, exist = self._table._get_fts_index_path()
|
||||||
|
if exist:
|
||||||
|
return self.tantivy_to_arrow()
|
||||||
|
|
||||||
query = self._query
|
query = self._query
|
||||||
if self._phrase_query:
|
if self._phrase_query:
|
||||||
@@ -1549,6 +1552,90 @@ class LanceFtsQueryBuilder(LanceQueryBuilder):
|
|||||||
):
|
):
|
||||||
raise NotImplementedError("to_batches on an FTS query")
|
raise NotImplementedError("to_batches on an FTS query")
|
||||||
|
|
||||||
|
def tantivy_to_arrow(self) -> pa.Table:
|
||||||
|
try:
|
||||||
|
import tantivy
|
||||||
|
except ImportError:
|
||||||
|
raise ImportError(
|
||||||
|
"Please install tantivy-py `pip install tantivy` to use the full text search feature." # noqa: E501
|
||||||
|
)
|
||||||
|
|
||||||
|
from .fts import search_index
|
||||||
|
|
||||||
|
# get the index path
|
||||||
|
path, fs, exist = self._table._get_fts_index_path()
|
||||||
|
|
||||||
|
# check if the index exist
|
||||||
|
if not exist:
|
||||||
|
raise FileNotFoundError(
|
||||||
|
"Fts index does not exist. "
|
||||||
|
"Please first call table.create_fts_index(['<field_names>']) to "
|
||||||
|
"create the fts index."
|
||||||
|
)
|
||||||
|
|
||||||
|
# Check that we are on local filesystem
|
||||||
|
if not isinstance(fs, pa_fs.LocalFileSystem):
|
||||||
|
raise NotImplementedError(
|
||||||
|
"Tantivy-based full text search "
|
||||||
|
"is only supported on the local filesystem"
|
||||||
|
)
|
||||||
|
# open the index
|
||||||
|
index = tantivy.Index.open(path)
|
||||||
|
# get the scores and doc ids
|
||||||
|
query = self._query
|
||||||
|
if self._phrase_query:
|
||||||
|
query = query.replace('"', "'")
|
||||||
|
query = f'"{query}"'
|
||||||
|
limit = self._limit if self._limit is not None else 10
|
||||||
|
row_ids, scores = search_index(
|
||||||
|
index, query, limit, ordering_field=self.ordering_field_name
|
||||||
|
)
|
||||||
|
if len(row_ids) == 0:
|
||||||
|
empty_schema = pa.schema([pa.field("_score", pa.float32())])
|
||||||
|
return pa.Table.from_batches([], schema=empty_schema)
|
||||||
|
scores = pa.array(scores)
|
||||||
|
output_tbl = self._table.to_lance().take(row_ids, columns=self._columns)
|
||||||
|
output_tbl = output_tbl.append_column("_score", scores)
|
||||||
|
# this needs to match vector search results which are uint64
|
||||||
|
row_ids = pa.array(row_ids, type=pa.uint64())
|
||||||
|
|
||||||
|
if self._where is not None:
|
||||||
|
tmp_name = "__lancedb__duckdb__indexer__"
|
||||||
|
output_tbl = output_tbl.append_column(
|
||||||
|
tmp_name, pa.array(range(len(output_tbl)))
|
||||||
|
)
|
||||||
|
try:
|
||||||
|
# TODO would be great to have Substrait generate pyarrow compute
|
||||||
|
# expressions or conversely have pyarrow support SQL expressions
|
||||||
|
# using Substrait
|
||||||
|
import duckdb
|
||||||
|
|
||||||
|
indexer = duckdb.sql(
|
||||||
|
f"SELECT {tmp_name} FROM output_tbl WHERE {self._where}"
|
||||||
|
).to_arrow_table()[tmp_name]
|
||||||
|
output_tbl = output_tbl.take(indexer).drop([tmp_name])
|
||||||
|
row_ids = row_ids.take(indexer)
|
||||||
|
|
||||||
|
except ImportError:
|
||||||
|
import tempfile
|
||||||
|
|
||||||
|
import lance
|
||||||
|
|
||||||
|
# TODO Use "memory://" instead once that's supported
|
||||||
|
with tempfile.TemporaryDirectory() as tmp:
|
||||||
|
ds = lance.write_dataset(output_tbl, tmp)
|
||||||
|
output_tbl = ds.to_table(filter=self._where)
|
||||||
|
indexer = output_tbl[tmp_name]
|
||||||
|
row_ids = row_ids.take(indexer)
|
||||||
|
output_tbl = output_tbl.drop([tmp_name])
|
||||||
|
|
||||||
|
if self._with_row_id:
|
||||||
|
output_tbl = output_tbl.append_column("_rowid", row_ids)
|
||||||
|
|
||||||
|
if self._reranker is not None:
|
||||||
|
output_tbl = self._reranker.rerank_fts(self._query, output_tbl)
|
||||||
|
return output_tbl
|
||||||
|
|
||||||
def rerank(self, reranker: Reranker) -> LanceFtsQueryBuilder:
|
def rerank(self, reranker: Reranker) -> LanceFtsQueryBuilder:
|
||||||
"""Rerank the results using the specified reranker.
|
"""Rerank the results using the specified reranker.
|
||||||
|
|
||||||
|
|||||||
@@ -943,26 +943,29 @@ class Table(ABC):
|
|||||||
Parameters
|
Parameters
|
||||||
----------
|
----------
|
||||||
field_names: str or list of str
|
field_names: str or list of str
|
||||||
The name of the field to index. Native FTS indexes can only be
|
The name(s) of the field to index.
|
||||||
created on a single field at a time. To search over multiple text
|
If ``use_tantivy`` is False (default), only a single field name
|
||||||
fields, create a separate FTS index for each field.
|
(str) is supported. To index multiple fields, create a separate
|
||||||
|
FTS index for each field.
|
||||||
replace: bool, default False
|
replace: bool, default False
|
||||||
If True, replace the existing index if it exists. Note that this is
|
If True, replace the existing index if it exists. Note that this is
|
||||||
not yet an atomic operation; the index will be temporarily
|
not yet an atomic operation; the index will be temporarily
|
||||||
unavailable while the new index is being created.
|
unavailable while the new index is being created.
|
||||||
writer_heap_size: int, default 1GB
|
writer_heap_size: int, default 1GB
|
||||||
Deprecated legacy Tantivy parameter. Any value other than the
|
Only available with use_tantivy=True
|
||||||
default raises an error.
|
|
||||||
ordering_field_names:
|
ordering_field_names:
|
||||||
Deprecated legacy Tantivy parameter. Setting this raises an error.
|
A list of unsigned type fields to index to optionally order
|
||||||
|
results on at search time.
|
||||||
|
only available with use_tantivy=True
|
||||||
tokenizer_name: str, default "default"
|
tokenizer_name: str, default "default"
|
||||||
A compatibility alias for native tokenizer configs. Can be "raw",
|
The tokenizer to use for the index. Can be "raw", "default" or the 2 letter
|
||||||
"default" or the 2 letter language code followed by "_stem". So
|
language code followed by "_stem". So for english it would be "en_stem".
|
||||||
for english it would be "en_stem".
|
For available languages see: https://docs.rs/tantivy/latest/tantivy/tokenizer/enum.Language.html
|
||||||
use_tantivy: bool, default False
|
use_tantivy: bool, default False
|
||||||
Deprecated legacy Tantivy parameter. Setting this to True raises an
|
If True, use the legacy full-text search implementation based on tantivy.
|
||||||
error.
|
If False, use the new full-text search implementation based on lance-index.
|
||||||
with_position: bool, default False
|
with_position: bool, default False
|
||||||
|
Only available with use_tantivy=False
|
||||||
If False, do not store the positions of the terms in the text.
|
If False, do not store the positions of the terms in the text.
|
||||||
This can reduce the size of the index and improve indexing speed.
|
This can reduce the size of the index and improve indexing speed.
|
||||||
But it will raise an exception for phrase queries.
|
But it will raise an exception for phrase queries.
|
||||||
@@ -1743,16 +1746,6 @@ class Table(ABC):
|
|||||||
index_exists = fs.get_file_info(path).type != pa_fs.FileType.NotFound
|
index_exists = fs.get_file_info(path).type != pa_fs.FileType.NotFound
|
||||||
return (path, fs, index_exists)
|
return (path, fs, index_exists)
|
||||||
|
|
||||||
def _ensure_no_legacy_fts_index(self):
|
|
||||||
path, _, exists = self._get_fts_index_path()
|
|
||||||
if exists:
|
|
||||||
raise ValueError(
|
|
||||||
"Legacy Tantivy FTS index detected at "
|
|
||||||
f"{path}. Tantivy-based FTS has been removed. "
|
|
||||||
"Delete the legacy index and recreate it with "
|
|
||||||
"table.create_fts_index(...)."
|
|
||||||
)
|
|
||||||
|
|
||||||
@abstractmethod
|
@abstractmethod
|
||||||
def uses_v2_manifest_paths(self) -> bool:
|
def uses_v2_manifest_paths(self) -> bool:
|
||||||
"""
|
"""
|
||||||
@@ -2412,63 +2405,84 @@ class LanceTable(Table):
|
|||||||
prefix_only: bool = False,
|
prefix_only: bool = False,
|
||||||
name: Optional[str] = None,
|
name: Optional[str] = None,
|
||||||
):
|
):
|
||||||
self._ensure_no_legacy_fts_index()
|
if not use_tantivy:
|
||||||
|
if not isinstance(field_names, str):
|
||||||
|
raise ValueError(
|
||||||
|
"Native FTS indexes can only be created on a single field "
|
||||||
|
"at a time. To search over multiple text fields, create a "
|
||||||
|
"separate FTS index for each field."
|
||||||
|
)
|
||||||
|
|
||||||
if use_tantivy:
|
if tokenizer_name is None:
|
||||||
raise ValueError(
|
tokenizer_configs = {
|
||||||
"Tantivy-based FTS has been removed. "
|
"base_tokenizer": base_tokenizer,
|
||||||
"Remove use_tantivy and recreate the index with native FTS."
|
"language": language,
|
||||||
|
"with_position": with_position,
|
||||||
|
"max_token_length": max_token_length,
|
||||||
|
"lower_case": lower_case,
|
||||||
|
"stem": stem,
|
||||||
|
"remove_stop_words": remove_stop_words,
|
||||||
|
"ascii_folding": ascii_folding,
|
||||||
|
"ngram_min_length": ngram_min_length,
|
||||||
|
"ngram_max_length": ngram_max_length,
|
||||||
|
"prefix_only": prefix_only,
|
||||||
|
}
|
||||||
|
else:
|
||||||
|
tokenizer_configs = self.infer_tokenizer_configs(tokenizer_name)
|
||||||
|
|
||||||
|
config = FTS(
|
||||||
|
**tokenizer_configs,
|
||||||
)
|
)
|
||||||
if ordering_field_names is not None:
|
|
||||||
raise ValueError(
|
# delete the existing legacy index if it exists
|
||||||
"ordering_field_names was only supported by the removed "
|
if replace:
|
||||||
"Tantivy-based FTS implementation."
|
path, fs, exist = self._get_fts_index_path()
|
||||||
|
if exist:
|
||||||
|
fs.delete_dir(path)
|
||||||
|
|
||||||
|
LOOP.run(
|
||||||
|
self._table.create_index(
|
||||||
|
field_names,
|
||||||
|
replace=replace,
|
||||||
|
config=config,
|
||||||
|
name=name,
|
||||||
|
)
|
||||||
)
|
)
|
||||||
if writer_heap_size != 1024 * 1024 * 1024:
|
return
|
||||||
raise ValueError(
|
|
||||||
"writer_heap_size was only supported by the removed "
|
from .fts import create_index, populate_index
|
||||||
"Tantivy-based FTS implementation."
|
|
||||||
)
|
if isinstance(field_names, str):
|
||||||
if not isinstance(field_names, str):
|
field_names = [field_names]
|
||||||
raise ValueError(
|
|
||||||
"Native FTS indexes can only be created on a single field "
|
if isinstance(ordering_field_names, str):
|
||||||
"at a time. To search over multiple text fields, create a "
|
ordering_field_names = [ordering_field_names]
|
||||||
"separate FTS index for each field."
|
|
||||||
)
|
path, fs, exist = self._get_fts_index_path()
|
||||||
if "." in field_names:
|
if exist:
|
||||||
raise ValueError(
|
if not replace:
|
||||||
"Native FTS indexes can only be created on top-level fields. "
|
raise ValueError("Index already exists. Use replace=True to overwrite.")
|
||||||
f"Received nested field path: {field_names!r}."
|
fs.delete_dir(path)
|
||||||
|
|
||||||
|
if not isinstance(fs, pa_fs.LocalFileSystem):
|
||||||
|
raise NotImplementedError(
|
||||||
|
"Full-text search is only supported on the local filesystem"
|
||||||
)
|
)
|
||||||
|
|
||||||
if tokenizer_name is None:
|
if tokenizer_name is None:
|
||||||
tokenizer_configs = {
|
tokenizer_name = "default"
|
||||||
"base_tokenizer": base_tokenizer,
|
index = create_index(
|
||||||
"language": language,
|
path,
|
||||||
"with_position": with_position,
|
field_names,
|
||||||
"max_token_length": max_token_length,
|
ordering_fields=ordering_field_names,
|
||||||
"lower_case": lower_case,
|
tokenizer_name=tokenizer_name,
|
||||||
"stem": stem,
|
|
||||||
"remove_stop_words": remove_stop_words,
|
|
||||||
"ascii_folding": ascii_folding,
|
|
||||||
"ngram_min_length": ngram_min_length,
|
|
||||||
"ngram_max_length": ngram_max_length,
|
|
||||||
"prefix_only": prefix_only,
|
|
||||||
}
|
|
||||||
else:
|
|
||||||
tokenizer_configs = self.infer_tokenizer_configs(tokenizer_name)
|
|
||||||
|
|
||||||
config = FTS(
|
|
||||||
**tokenizer_configs,
|
|
||||||
)
|
)
|
||||||
|
populate_index(
|
||||||
LOOP.run(
|
index,
|
||||||
self._table.create_index(
|
self,
|
||||||
field_names,
|
field_names,
|
||||||
replace=replace,
|
ordering_fields=ordering_field_names,
|
||||||
config=config,
|
writer_heap_size=writer_heap_size,
|
||||||
name=name,
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
@@ -2915,7 +2929,6 @@ class LanceTable(Table):
|
|||||||
namespace_path=namespace_path,
|
namespace_path=namespace_path,
|
||||||
storage_options=storage_options,
|
storage_options=storage_options,
|
||||||
location=location,
|
location=location,
|
||||||
namespace_client=namespace_client,
|
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
return self
|
return self
|
||||||
|
|||||||
@@ -180,7 +180,7 @@ def test_fts_fuzzy_query():
|
|||||||
),
|
),
|
||||||
mode="overwrite",
|
mode="overwrite",
|
||||||
)
|
)
|
||||||
table.create_fts_index("text", replace=True)
|
table.create_fts_index("text", use_tantivy=False, replace=True)
|
||||||
|
|
||||||
results = table.search(MatchQuery("foo", "text", fuzziness=1)).to_pandas()
|
results = table.search(MatchQuery("foo", "text", fuzziness=1)).to_pandas()
|
||||||
assert len(results) == 4
|
assert len(results) == 4
|
||||||
@@ -230,7 +230,7 @@ def test_fts_boost_query():
|
|||||||
),
|
),
|
||||||
mode="overwrite",
|
mode="overwrite",
|
||||||
)
|
)
|
||||||
table.create_fts_index("desc", replace=True)
|
table.create_fts_index("desc", use_tantivy=False, replace=True)
|
||||||
|
|
||||||
results = table.search(
|
results = table.search(
|
||||||
BoostQuery(
|
BoostQuery(
|
||||||
@@ -265,7 +265,7 @@ def test_fts_boolean_query(tmp_path):
|
|||||||
],
|
],
|
||||||
mode="overwrite",
|
mode="overwrite",
|
||||||
)
|
)
|
||||||
table.create_fts_index("text", replace=True)
|
table.create_fts_index("text", use_tantivy=False, replace=True)
|
||||||
|
|
||||||
# SHOULD
|
# SHOULD
|
||||||
results = table.search(
|
results = table.search(
|
||||||
@@ -319,7 +319,9 @@ def test_fts_native():
|
|||||||
],
|
],
|
||||||
)
|
)
|
||||||
|
|
||||||
table.create_fts_index("text")
|
# passing `use_tantivy=False` to use lance FTS index
|
||||||
|
# `use_tantivy=True` by default
|
||||||
|
table.create_fts_index("text", use_tantivy=False)
|
||||||
table.search("puppy").limit(10).select(["text"]).to_list()
|
table.search("puppy").limit(10).select(["text"]).to_list()
|
||||||
# [{'text': 'Frodo was a happy puppy', '_score': 0.6931471824645996}]
|
# [{'text': 'Frodo was a happy puppy', '_score': 0.6931471824645996}]
|
||||||
# ...
|
# ...
|
||||||
@@ -330,6 +332,7 @@ def test_fts_native():
|
|||||||
# --8<-- [start:fts_config_folding]
|
# --8<-- [start:fts_config_folding]
|
||||||
table.create_fts_index(
|
table.create_fts_index(
|
||||||
"text",
|
"text",
|
||||||
|
use_tantivy=False,
|
||||||
language="French",
|
language="French",
|
||||||
stem=True,
|
stem=True,
|
||||||
ascii_folding=True,
|
ascii_folding=True,
|
||||||
@@ -343,7 +346,7 @@ def test_fts_native():
|
|||||||
table.search("puppy").limit(10).where("text='foo'", prefilter=False).to_list()
|
table.search("puppy").limit(10).where("text='foo'", prefilter=False).to_list()
|
||||||
# --8<-- [end:fts_postfiltering]
|
# --8<-- [end:fts_postfiltering]
|
||||||
# --8<-- [start:fts_with_position]
|
# --8<-- [start:fts_with_position]
|
||||||
table.create_fts_index("text", with_position=True, replace=True)
|
table.create_fts_index("text", use_tantivy=False, with_position=True, replace=True)
|
||||||
# --8<-- [end:fts_with_position]
|
# --8<-- [end:fts_with_position]
|
||||||
# --8<-- [start:fts_incremental_index]
|
# --8<-- [start:fts_incremental_index]
|
||||||
table.add([{"vector": [3.1, 4.1], "text": "Frodo was a happy puppy"}])
|
table.add([{"vector": [3.1, 4.1], "text": "Frodo was a happy puppy"}])
|
||||||
|
|||||||
@@ -15,7 +15,8 @@ import pytest
|
|||||||
from lancedb.pydantic import LanceModel, Vector
|
from lancedb.pydantic import LanceModel, Vector
|
||||||
|
|
||||||
|
|
||||||
def test_basic(tmp_path):
|
@pytest.mark.parametrize("use_tantivy", [True, False])
|
||||||
|
def test_basic(tmp_path, use_tantivy):
|
||||||
db = lancedb.connect(tmp_path)
|
db = lancedb.connect(tmp_path)
|
||||||
|
|
||||||
assert db.uri == str(tmp_path)
|
assert db.uri == str(tmp_path)
|
||||||
@@ -48,7 +49,7 @@ def test_basic(tmp_path):
|
|||||||
assert len(rs) == 1
|
assert len(rs) == 1
|
||||||
assert rs["item"].iloc[0] == "foo"
|
assert rs["item"].iloc[0] == "foo"
|
||||||
|
|
||||||
table.create_fts_index("item")
|
table.create_fts_index("item", use_tantivy=use_tantivy)
|
||||||
rs = table.search("bar", query_type="fts").to_pandas()
|
rs = table.search("bar", query_type="fts").to_pandas()
|
||||||
assert len(rs) == 1
|
assert len(rs) == 1
|
||||||
assert rs["item"].iloc[0] == "bar"
|
assert rs["item"].iloc[0] == "bar"
|
||||||
|
|||||||
+158
-100
@@ -36,6 +36,9 @@ import pytest
|
|||||||
import pytest_asyncio
|
import pytest_asyncio
|
||||||
from utils import exception_output
|
from utils import exception_output
|
||||||
|
|
||||||
|
pytest.importorskip("lancedb.fts")
|
||||||
|
tantivy = pytest.importorskip("tantivy")
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def table(tmp_path) -> ldb.table.LanceTable:
|
def table(tmp_path) -> ldb.table.LanceTable:
|
||||||
@@ -141,53 +144,58 @@ async def async_table(tmp_path) -> ldb.table.AsyncTable:
|
|||||||
return table
|
return table
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize(
|
def test_create_index(tmp_path):
|
||||||
("kwargs", "match"),
|
index = ldb.fts.create_index(str(tmp_path / "index"), ["text"])
|
||||||
[
|
assert isinstance(index, tantivy.Index)
|
||||||
(
|
assert os.path.exists(str(tmp_path / "index"))
|
||||||
{"use_tantivy": True},
|
|
||||||
"Tantivy-based FTS has been removed",
|
|
||||||
),
|
|
||||||
(
|
|
||||||
{"ordering_field_names": ["count"]},
|
|
||||||
"ordering_field_names was only supported",
|
|
||||||
),
|
|
||||||
(
|
|
||||||
{"writer_heap_size": 128},
|
|
||||||
"writer_heap_size was only supported",
|
|
||||||
),
|
|
||||||
],
|
|
||||||
)
|
|
||||||
def test_reject_removed_tantivy_parameters(table, kwargs, match):
|
|
||||||
with pytest.raises(ValueError, match=match):
|
|
||||||
table.create_fts_index("text", **kwargs)
|
|
||||||
|
|
||||||
|
|
||||||
def test_reject_legacy_tantivy_index(table):
|
def test_create_index_with_stemming(tmp_path, table):
|
||||||
path, _, _ = table._get_fts_index_path()
|
index = ldb.fts.create_index(
|
||||||
os.makedirs(path, exist_ok=True)
|
str(tmp_path / "index"), ["text"], tokenizer_name="en_stem"
|
||||||
|
)
|
||||||
|
assert isinstance(index, tantivy.Index)
|
||||||
|
assert os.path.exists(str(tmp_path / "index"))
|
||||||
|
|
||||||
with pytest.raises(ValueError, match="Legacy Tantivy FTS index detected"):
|
# Check stemming by running tokenizer on non empty table
|
||||||
table.search("puppy").limit(5).to_list()
|
table.create_fts_index("text", tokenizer_name="en_stem", use_tantivy=True)
|
||||||
|
|
||||||
with pytest.raises(ValueError, match="Legacy Tantivy FTS index detected"):
|
|
||||||
table.create_fts_index("text")
|
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize("use_tantivy", [True, False])
|
||||||
@pytest.mark.parametrize("with_position", [True, False])
|
@pytest.mark.parametrize("with_position", [True, False])
|
||||||
def test_create_inverted_index(table, with_position):
|
def test_create_inverted_index(table, use_tantivy, with_position):
|
||||||
|
if use_tantivy and not with_position:
|
||||||
|
pytest.skip("we don't support building a tantivy index without position")
|
||||||
table.create_fts_index(
|
table.create_fts_index(
|
||||||
"text",
|
"text",
|
||||||
|
use_tantivy=use_tantivy,
|
||||||
with_position=with_position,
|
with_position=with_position,
|
||||||
name="custom_fts_index",
|
name="custom_fts_index",
|
||||||
)
|
)
|
||||||
indices = table.list_indices()
|
if not use_tantivy:
|
||||||
fts_indices = [i for i in indices if i.index_type == "FTS"]
|
indices = table.list_indices()
|
||||||
assert any(i.name == "custom_fts_index" for i in fts_indices)
|
fts_indices = [i for i in indices if i.index_type == "FTS"]
|
||||||
|
assert any(i.name == "custom_fts_index" for i in fts_indices)
|
||||||
|
|
||||||
|
|
||||||
def test_search_fts(table):
|
def test_populate_index(tmp_path, table):
|
||||||
table.create_fts_index("text")
|
index = ldb.fts.create_index(str(tmp_path / "index"), ["text"])
|
||||||
|
assert ldb.fts.populate_index(index, table, ["text"]) == len(table)
|
||||||
|
|
||||||
|
|
||||||
|
def test_search_index(tmp_path, table):
|
||||||
|
index = ldb.fts.create_index(str(tmp_path / "index"), ["text"])
|
||||||
|
ldb.fts.populate_index(index, table, ["text"])
|
||||||
|
index.reload()
|
||||||
|
results = ldb.fts.search_index(index, query="puppy", limit=5)
|
||||||
|
assert len(results) == 2
|
||||||
|
assert len(results[0]) == 5 # row_ids
|
||||||
|
assert len(results[1]) == 5 # _score
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize("use_tantivy", [True, False])
|
||||||
|
def test_search_fts(table, use_tantivy):
|
||||||
|
table.create_fts_index("text", use_tantivy=use_tantivy)
|
||||||
results = table.search("puppy").select(["id", "text"]).limit(5).to_list()
|
results = table.search("puppy").select(["id", "text"]).limit(5).to_list()
|
||||||
assert len(results) == 5
|
assert len(results) == 5
|
||||||
assert len(results[0]) == 3 # id, text, _score
|
assert len(results[0]) == 3 # id, text, _score
|
||||||
@@ -196,52 +204,53 @@ def test_search_fts(table):
|
|||||||
results = table.search("puppy").select(["id", "text"]).to_list()
|
results = table.search("puppy").select(["id", "text"]).to_list()
|
||||||
assert len(results) == 10
|
assert len(results) == 10
|
||||||
|
|
||||||
# Test with a query
|
if not use_tantivy:
|
||||||
results = (
|
# Test with a query
|
||||||
table.search(MatchQuery("puppy", "text"))
|
results = (
|
||||||
.select(["id", "text"])
|
table.search(MatchQuery("puppy", "text"))
|
||||||
.limit(5)
|
.select(["id", "text"])
|
||||||
.to_list()
|
.limit(5)
|
||||||
)
|
.to_list()
|
||||||
assert len(results) == 5
|
|
||||||
|
|
||||||
# Test boost query
|
|
||||||
results = (
|
|
||||||
table.search(
|
|
||||||
BoostQuery(
|
|
||||||
MatchQuery("puppy", "text"),
|
|
||||||
MatchQuery("runs", "text"),
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
.select(["id", "text"])
|
assert len(results) == 5
|
||||||
.limit(5)
|
|
||||||
.to_list()
|
|
||||||
)
|
|
||||||
assert len(results) == 5
|
|
||||||
|
|
||||||
# Test multi match query
|
# Test boost query
|
||||||
table.create_fts_index("text2")
|
results = (
|
||||||
results = (
|
table.search(
|
||||||
table.search(MultiMatchQuery("puppy", ["text", "text2"]))
|
BoostQuery(
|
||||||
.select(["id", "text"])
|
MatchQuery("puppy", "text"),
|
||||||
.limit(5)
|
MatchQuery("runs", "text"),
|
||||||
.to_list()
|
)
|
||||||
)
|
)
|
||||||
assert len(results) == 5
|
.select(["id", "text"])
|
||||||
assert len(results[0]) == 3 # id, text, _score
|
.limit(5)
|
||||||
|
.to_list()
|
||||||
|
)
|
||||||
|
assert len(results) == 5
|
||||||
|
|
||||||
# Test boolean query
|
# Test multi match query
|
||||||
results = (
|
table.create_fts_index("text2", use_tantivy=use_tantivy)
|
||||||
table.search(MatchQuery("puppy", "text") & MatchQuery("runs", "text"))
|
results = (
|
||||||
.select(["id", "text"])
|
table.search(MultiMatchQuery("puppy", ["text", "text2"]))
|
||||||
.limit(5)
|
.select(["id", "text"])
|
||||||
.to_list()
|
.limit(5)
|
||||||
)
|
.to_list()
|
||||||
assert len(results) == 5
|
)
|
||||||
assert len(results[0]) == 3 # id, text, _score
|
assert len(results) == 5
|
||||||
for r in results:
|
assert len(results[0]) == 3 # id, text, _score
|
||||||
assert "puppy" in r["text"]
|
|
||||||
assert "runs" in r["text"]
|
# Test boolean query
|
||||||
|
results = (
|
||||||
|
table.search(MatchQuery("puppy", "text") & MatchQuery("runs", "text"))
|
||||||
|
.select(["id", "text"])
|
||||||
|
.limit(5)
|
||||||
|
.to_list()
|
||||||
|
)
|
||||||
|
assert len(results) == 5
|
||||||
|
assert len(results[0]) == 3 # id, text, _score
|
||||||
|
for r in results:
|
||||||
|
assert "puppy" in r["text"]
|
||||||
|
assert "runs" in r["text"]
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
@pytest.mark.asyncio
|
||||||
@@ -309,13 +318,13 @@ async def test_fts_select_async(async_table):
|
|||||||
|
|
||||||
|
|
||||||
def test_search_fts_phrase_query(table):
|
def test_search_fts_phrase_query(table):
|
||||||
table.create_fts_index("text", with_position=False)
|
table.create_fts_index("text", use_tantivy=False, with_position=False)
|
||||||
try:
|
try:
|
||||||
phrase_results = table.search('"puppy runs"').limit(100).to_list()
|
phrase_results = table.search('"puppy runs"').limit(100).to_list()
|
||||||
assert False
|
assert False
|
||||||
except Exception:
|
except Exception:
|
||||||
pass
|
pass
|
||||||
table.create_fts_index("text", with_position=True, replace=True)
|
table.create_fts_index("text", use_tantivy=False, with_position=True, replace=True)
|
||||||
results = table.search("puppy").limit(100).to_list()
|
results = table.search("puppy").limit(100).to_list()
|
||||||
|
|
||||||
# Test with quotation marks
|
# Test with quotation marks
|
||||||
@@ -366,8 +375,8 @@ async def test_search_fts_phrase_query_async(async_table):
|
|||||||
|
|
||||||
|
|
||||||
def test_search_fts_specify_column(table):
|
def test_search_fts_specify_column(table):
|
||||||
table.create_fts_index("text")
|
table.create_fts_index("text", use_tantivy=False)
|
||||||
table.create_fts_index("text2")
|
table.create_fts_index("text2", use_tantivy=False)
|
||||||
|
|
||||||
results = table.search("puppy", fts_columns="text").limit(5).to_list()
|
results = table.search("puppy", fts_columns="text").limit(5).to_list()
|
||||||
assert len(results) == 5
|
assert len(results) == 5
|
||||||
@@ -461,8 +470,42 @@ async def test_search_fts_specify_column_async(async_table):
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
def test_create_index_from_table(tmp_path, table):
|
def test_search_ordering_field_index_table(tmp_path, table):
|
||||||
table.create_fts_index("text")
|
table.create_fts_index("text", ordering_field_names=["count"], use_tantivy=True)
|
||||||
|
rows = (
|
||||||
|
table.search("puppy", ordering_field_name="count")
|
||||||
|
.limit(20)
|
||||||
|
.select(["text", "count"])
|
||||||
|
.to_list()
|
||||||
|
)
|
||||||
|
for r in rows:
|
||||||
|
assert "puppy" in r["text"]
|
||||||
|
assert sorted(rows, key=lambda x: x["count"], reverse=True) == rows
|
||||||
|
|
||||||
|
|
||||||
|
def test_search_ordering_field_index(tmp_path, table):
|
||||||
|
index = ldb.fts.create_index(
|
||||||
|
str(tmp_path / "index"), ["text"], ordering_fields=["count"]
|
||||||
|
)
|
||||||
|
|
||||||
|
ldb.fts.populate_index(index, table, ["text"], ordering_fields=["count"])
|
||||||
|
index.reload()
|
||||||
|
results = ldb.fts.search_index(
|
||||||
|
index, query="puppy", limit=5, ordering_field="count"
|
||||||
|
)
|
||||||
|
assert len(results) == 2
|
||||||
|
assert len(results[0]) == 5 # row_ids
|
||||||
|
assert len(results[1]) == 5 # _distance
|
||||||
|
rows = table.to_lance().take(results[0]).to_pylist()
|
||||||
|
|
||||||
|
for r in rows:
|
||||||
|
assert "puppy" in r["text"]
|
||||||
|
assert sorted(rows, key=lambda x: x["count"], reverse=True) == rows
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize("use_tantivy", [True, False])
|
||||||
|
def test_create_index_from_table(tmp_path, table, use_tantivy):
|
||||||
|
table.create_fts_index("text", use_tantivy=use_tantivy)
|
||||||
df = table.search("puppy").limit(5).select(["text"]).to_pandas()
|
df = table.search("puppy").limit(5).select(["text"]).to_pandas()
|
||||||
assert len(df) <= 5
|
assert len(df) <= 5
|
||||||
assert "text" in df.columns
|
assert "text" in df.columns
|
||||||
@@ -482,24 +525,36 @@ def test_create_index_from_table(tmp_path, table):
|
|||||||
)
|
)
|
||||||
|
|
||||||
with pytest.raises(Exception, match="already exists"):
|
with pytest.raises(Exception, match="already exists"):
|
||||||
table.create_fts_index("text")
|
table.create_fts_index("text", use_tantivy=use_tantivy)
|
||||||
|
|
||||||
table.create_fts_index("text", replace=True)
|
table.create_fts_index("text", replace=True, use_tantivy=use_tantivy)
|
||||||
assert len(table.search("gorilla").limit(1).to_pandas()) == 1
|
assert len(table.search("gorilla").limit(1).to_pandas()) == 1
|
||||||
|
|
||||||
|
|
||||||
def test_create_index_multiple_columns(tmp_path, table):
|
def test_create_index_multiple_columns(tmp_path, table):
|
||||||
with pytest.raises(ValueError, match="Native FTS indexes can only be created"):
|
table.create_fts_index(["text", "text2"], use_tantivy=True)
|
||||||
table.create_fts_index(["text", "text2"])
|
df = table.search("puppy").limit(5).to_pandas()
|
||||||
|
assert len(df) == 5
|
||||||
|
assert "text" in df.columns
|
||||||
|
assert "text2" in df.columns
|
||||||
|
|
||||||
|
|
||||||
|
def test_empty_rs(tmp_path, table, mocker):
|
||||||
|
table.create_fts_index(["text", "text2"], use_tantivy=True)
|
||||||
|
mocker.patch("lancedb.fts.search_index", return_value=([], []))
|
||||||
|
df = table.search("puppy").limit(5).to_pandas()
|
||||||
|
assert len(df) == 0
|
||||||
|
|
||||||
|
|
||||||
def test_nested_schema(tmp_path, table):
|
def test_nested_schema(tmp_path, table):
|
||||||
with pytest.raises(ValueError, match="top-level fields"):
|
table.create_fts_index("nested.text", use_tantivy=True)
|
||||||
table.create_fts_index("nested.text")
|
rs = table.search("puppy").limit(5).to_list()
|
||||||
|
assert len(rs) == 5
|
||||||
|
|
||||||
|
|
||||||
def test_search_index_with_filter(table):
|
@pytest.mark.parametrize("use_tantivy", [True, False])
|
||||||
table.create_fts_index("text")
|
def test_search_index_with_filter(table, use_tantivy):
|
||||||
|
table.create_fts_index("text", use_tantivy=use_tantivy)
|
||||||
orig_import = __import__
|
orig_import = __import__
|
||||||
|
|
||||||
def import_mock(name, *args):
|
def import_mock(name, *args):
|
||||||
@@ -529,7 +584,8 @@ def test_search_index_with_filter(table):
|
|||||||
assert r["_rowid"] is not None
|
assert r["_rowid"] is not None
|
||||||
|
|
||||||
|
|
||||||
def test_null_input(table):
|
@pytest.mark.parametrize("use_tantivy", [True, False])
|
||||||
|
def test_null_input(table, use_tantivy):
|
||||||
table.add(
|
table.add(
|
||||||
[
|
[
|
||||||
{
|
{
|
||||||
@@ -542,13 +598,14 @@ def test_null_input(table):
|
|||||||
}
|
}
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
table.create_fts_index("text")
|
table.create_fts_index("text", use_tantivy=use_tantivy)
|
||||||
|
|
||||||
|
|
||||||
def test_syntax(table):
|
def test_syntax(table):
|
||||||
# https://github.com/lancedb/lancedb/issues/769
|
# https://github.com/lancedb/lancedb/issues/769
|
||||||
table.create_fts_index("text")
|
table.create_fts_index("text", use_tantivy=True)
|
||||||
table.search("they could have been dogs OR").limit(10).to_list()
|
with pytest.raises(ValueError, match="Syntax Error"):
|
||||||
|
table.search("they could have been dogs OR").limit(10).to_list()
|
||||||
|
|
||||||
# these should work
|
# these should work
|
||||||
|
|
||||||
@@ -559,7 +616,6 @@ def test_syntax(table):
|
|||||||
).to_list()
|
).to_list()
|
||||||
|
|
||||||
# phrase queries
|
# phrase queries
|
||||||
table.create_fts_index("text", with_position=True, replace=True)
|
|
||||||
table.search("they could have been dogs OR cats").phrase_query().limit(10).to_list()
|
table.search("they could have been dogs OR cats").phrase_query().limit(10).to_list()
|
||||||
table.search('"they could have been dogs OR cats"').limit(10).to_list()
|
table.search('"they could have been dogs OR cats"').limit(10).to_list()
|
||||||
table.search('''"the cats OR dogs were not really 'pets' at all"''').limit(
|
table.search('''"the cats OR dogs were not really 'pets' at all"''').limit(
|
||||||
@@ -583,7 +639,7 @@ def test_language(mem_db: DBConnection):
|
|||||||
table = mem_db.create_table("test", data=data)
|
table = mem_db.create_table("test", data=data)
|
||||||
|
|
||||||
with pytest.raises(ValueError) as e:
|
with pytest.raises(ValueError) as e:
|
||||||
table.create_fts_index("text", language="klingon")
|
table.create_fts_index("text", use_tantivy=False, language="klingon")
|
||||||
|
|
||||||
assert exception_output(e) == (
|
assert exception_output(e) == (
|
||||||
"ValueError: LanceDB does not support the requested language: 'klingon'\n"
|
"ValueError: LanceDB does not support the requested language: 'klingon'\n"
|
||||||
@@ -594,6 +650,7 @@ def test_language(mem_db: DBConnection):
|
|||||||
|
|
||||||
table.create_fts_index(
|
table.create_fts_index(
|
||||||
"text",
|
"text",
|
||||||
|
use_tantivy=False,
|
||||||
language="French",
|
language="French",
|
||||||
stem=True,
|
stem=True,
|
||||||
ascii_folding=True,
|
ascii_folding=True,
|
||||||
@@ -633,7 +690,7 @@ def test_fts_on_list(mem_db: DBConnection):
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
table = mem_db.create_table("test", data=data)
|
table = mem_db.create_table("test", data=data)
|
||||||
table.create_fts_index("text", with_position=True)
|
table.create_fts_index("text", use_tantivy=False, with_position=True)
|
||||||
|
|
||||||
res = table.search("lance").limit(5).to_list()
|
res = table.search("lance").limit(5).to_list()
|
||||||
assert len(res) == 3
|
assert len(res) == 3
|
||||||
@@ -645,7 +702,7 @@ def test_fts_on_list(mem_db: DBConnection):
|
|||||||
def test_fts_ngram(mem_db: DBConnection):
|
def test_fts_ngram(mem_db: DBConnection):
|
||||||
data = pa.table({"text": ["hello world", "lance database", "lance is cool"]})
|
data = pa.table({"text": ["hello world", "lance database", "lance is cool"]})
|
||||||
table = mem_db.create_table("test", data=data)
|
table = mem_db.create_table("test", data=data)
|
||||||
table.create_fts_index("text", base_tokenizer="ngram")
|
table.create_fts_index("text", use_tantivy=False, base_tokenizer="ngram")
|
||||||
|
|
||||||
results = table.search("lan", query_type="fts").limit(10).to_list()
|
results = table.search("lan", query_type="fts").limit(10).to_list()
|
||||||
assert len(results) == 2
|
assert len(results) == 2
|
||||||
@@ -664,6 +721,7 @@ def test_fts_ngram(mem_db: DBConnection):
|
|||||||
# test setting min_ngram_length and prefix_only
|
# test setting min_ngram_length and prefix_only
|
||||||
table.create_fts_index(
|
table.create_fts_index(
|
||||||
"text",
|
"text",
|
||||||
|
use_tantivy=False,
|
||||||
base_tokenizer="ngram",
|
base_tokenizer="ngram",
|
||||||
replace=True,
|
replace=True,
|
||||||
ngram_min_length=2,
|
ngram_min_length=2,
|
||||||
@@ -828,7 +886,7 @@ def test_fts_query_to_json():
|
|||||||
|
|
||||||
|
|
||||||
def test_fts_fast_search(table):
|
def test_fts_fast_search(table):
|
||||||
table.create_fts_index("text")
|
table.create_fts_index("text", use_tantivy=False)
|
||||||
|
|
||||||
# Insert some unindexed data
|
# Insert some unindexed data
|
||||||
table.add(
|
table.add(
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ def sync_table(tmpdir_factory) -> Table:
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
table = db.create_table("test", data)
|
table = db.create_table("test", data)
|
||||||
table.create_fts_index("text", with_position=False)
|
table.create_fts_index("text", with_position=False, use_tantivy=False)
|
||||||
return table
|
return table
|
||||||
|
|
||||||
|
|
||||||
@@ -192,7 +192,7 @@ def table_with_id(tmpdir_factory) -> Table:
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
table = db.create_table("test_with_id", data)
|
table = db.create_table("test_with_id", data)
|
||||||
table.create_fts_index("text", with_position=False)
|
table.create_fts_index("text", with_position=False, use_tantivy=False)
|
||||||
return table
|
return table
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -18,9 +18,6 @@ Tests verify:
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
import copy
|
import copy
|
||||||
import shutil
|
|
||||||
import sys
|
|
||||||
import tempfile
|
|
||||||
import time
|
import time
|
||||||
import uuid
|
import uuid
|
||||||
from typing import Dict, Optional
|
from typing import Dict, Optional
|
||||||
@@ -390,66 +387,6 @@ def test_namespace_open_table_with_provider(s3_bucket: str, use_custom: bool):
|
|||||||
assert get_describe_call_count(inner_ns_client) == describe_count_after_open
|
assert get_describe_call_count(inner_ns_client) == describe_count_after_open
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.skipif(
|
|
||||||
sys.platform == "win32",
|
|
||||||
reason="TODO: fix schema-only namespace metrics test on Windows",
|
|
||||||
)
|
|
||||||
@pytest.mark.parametrize("use_custom", [False, True], ids=["DirectoryNS", "CustomNS"])
|
|
||||||
def test_namespace_create_schema_only_with_provider(use_custom: bool):
|
|
||||||
"""
|
|
||||||
Test creating a schema-only table through namespace with storage options provider.
|
|
||||||
|
|
||||||
Verifies:
|
|
||||||
- declare_table is called once to reserve the location
|
|
||||||
- describe_table is not needed during create in create mode
|
|
||||||
- the table can be reopened successfully afterward
|
|
||||||
- opening the table triggers exactly one describe_table call
|
|
||||||
"""
|
|
||||||
temp_dir = tempfile.mkdtemp()
|
|
||||||
try:
|
|
||||||
ns_client, inner_ns_client = create_tracking_namespace(
|
|
||||||
bucket_name=temp_dir,
|
|
||||||
storage_options={},
|
|
||||||
credential_expires_in_seconds=3600,
|
|
||||||
use_custom=use_custom,
|
|
||||||
)
|
|
||||||
|
|
||||||
db = LanceNamespaceDBConnection(ns_client)
|
|
||||||
|
|
||||||
namespace_name = f"test_ns_{uuid.uuid4().hex[:8]}"
|
|
||||||
db.create_namespace([namespace_name])
|
|
||||||
|
|
||||||
table_name = f"test_table_{uuid.uuid4().hex}"
|
|
||||||
namespace_path = [namespace_name]
|
|
||||||
schema = pa.schema(
|
|
||||||
[
|
|
||||||
pa.field("id", pa.int64()),
|
|
||||||
pa.field("vector", pa.list_(pa.float32(), 2)),
|
|
||||||
pa.field("text", pa.string()),
|
|
||||||
]
|
|
||||||
)
|
|
||||||
|
|
||||||
assert get_declare_call_count(inner_ns_client) == 0
|
|
||||||
assert get_describe_call_count(inner_ns_client) == 0
|
|
||||||
|
|
||||||
table = db.create_table(
|
|
||||||
table_name, schema=schema, namespace_path=namespace_path
|
|
||||||
)
|
|
||||||
|
|
||||||
assert table.name == table_name
|
|
||||||
assert table.namespace == namespace_path
|
|
||||||
assert get_declare_call_count(inner_ns_client) == 1
|
|
||||||
assert get_describe_call_count(inner_ns_client) == 0
|
|
||||||
|
|
||||||
reopened_table = db.open_table(table_name, namespace_path=namespace_path)
|
|
||||||
|
|
||||||
assert reopened_table.schema == schema
|
|
||||||
assert get_declare_call_count(inner_ns_client) == 1
|
|
||||||
assert get_describe_call_count(inner_ns_client) == 1
|
|
||||||
finally:
|
|
||||||
shutil.rmtree(temp_dir, ignore_errors=True)
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.s3_test
|
@pytest.mark.s3_test
|
||||||
@pytest.mark.parametrize("use_custom", [False, True], ids=["DirectoryNS", "CustomNS"])
|
@pytest.mark.parametrize("use_custom", [False, True], ids=["DirectoryNS", "CustomNS"])
|
||||||
def test_namespace_credential_refresh_on_read(s3_bucket: str, use_custom: bool):
|
def test_namespace_credential_refresh_on_read(s3_bucket: str, use_custom: bool):
|
||||||
|
|||||||
@@ -1385,7 +1385,7 @@ def test_query_timeout(tmp_path):
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
table = db.create_table("test", data)
|
table = db.create_table("test", data)
|
||||||
table.create_fts_index("text")
|
table.create_fts_index("text", use_tantivy=False)
|
||||||
|
|
||||||
with pytest.raises(Exception, match="Query timeout"):
|
with pytest.raises(Exception, match="Query timeout"):
|
||||||
table.search().where("text = 'a'").to_list(timeout=timedelta(0))
|
table.search().where("text = 'a'").to_list(timeout=timedelta(0))
|
||||||
|
|||||||
@@ -26,8 +26,11 @@ from lancedb.rerankers import (
|
|||||||
)
|
)
|
||||||
from lancedb.table import LanceTable
|
from lancedb.table import LanceTable
|
||||||
|
|
||||||
|
# Tests rely on FTS index
|
||||||
|
pytest.importorskip("lancedb.fts")
|
||||||
|
|
||||||
def get_test_table(tmp_path):
|
|
||||||
|
def get_test_table(tmp_path, use_tantivy):
|
||||||
db = lancedb.connect(tmp_path)
|
db = lancedb.connect(tmp_path)
|
||||||
# Create a LanceDB table schema with a vector and a text column
|
# Create a LanceDB table schema with a vector and a text column
|
||||||
emb = EmbeddingFunctionRegistry.get_instance().get("test").create()
|
emb = EmbeddingFunctionRegistry.get_instance().get("test").create()
|
||||||
@@ -95,7 +98,7 @@ def get_test_table(tmp_path):
|
|||||||
)
|
)
|
||||||
|
|
||||||
# Create a fts index
|
# Create a fts index
|
||||||
table.create_fts_index("text", replace=True)
|
table.create_fts_index("text", use_tantivy=use_tantivy, replace=True)
|
||||||
|
|
||||||
return table, MyTable
|
return table, MyTable
|
||||||
|
|
||||||
@@ -205,8 +208,8 @@ def _run_test_reranker(reranker, table, query, query_vector, schema):
|
|||||||
assert len(result) == 20 and result == result_arrow
|
assert len(result) == 20 and result == result_arrow
|
||||||
|
|
||||||
|
|
||||||
def _run_test_hybrid_reranker(reranker, tmp_path):
|
def _run_test_hybrid_reranker(reranker, tmp_path, use_tantivy):
|
||||||
table, schema = get_test_table(tmp_path)
|
table, schema = get_test_table(tmp_path, use_tantivy)
|
||||||
# The default reranker
|
# The default reranker
|
||||||
result1 = (
|
result1 = (
|
||||||
table.search(
|
table.search(
|
||||||
@@ -282,7 +285,8 @@ def _run_test_hybrid_reranker(reranker, tmp_path):
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def test_linear_combination(tmp_path):
|
@pytest.mark.parametrize("use_tantivy", [True, False])
|
||||||
|
def test_linear_combination(tmp_path, use_tantivy):
|
||||||
reranker = LinearCombinationReranker()
|
reranker = LinearCombinationReranker()
|
||||||
|
|
||||||
vector_results = pa.Table.from_pydict(
|
vector_results = pa.Table.from_pydict(
|
||||||
@@ -309,20 +313,22 @@ def test_linear_combination(tmp_path):
|
|||||||
assert "_score" not in combined_results.column_names
|
assert "_score" not in combined_results.column_names
|
||||||
assert "_relevance_score" in combined_results.column_names
|
assert "_relevance_score" in combined_results.column_names
|
||||||
|
|
||||||
_run_test_hybrid_reranker(reranker, tmp_path)
|
_run_test_hybrid_reranker(reranker, tmp_path, use_tantivy)
|
||||||
|
|
||||||
|
|
||||||
def test_rrf_reranker(tmp_path):
|
@pytest.mark.parametrize("use_tantivy", [True, False])
|
||||||
|
def test_rrf_reranker(tmp_path, use_tantivy):
|
||||||
reranker = RRFReranker()
|
reranker = RRFReranker()
|
||||||
_run_test_hybrid_reranker(reranker, tmp_path)
|
_run_test_hybrid_reranker(reranker, tmp_path, use_tantivy)
|
||||||
|
|
||||||
|
|
||||||
def test_mrr_reranker(tmp_path):
|
@pytest.mark.parametrize("use_tantivy", [True, False])
|
||||||
|
def test_mrr_reranker(tmp_path, use_tantivy):
|
||||||
reranker = MRRReranker()
|
reranker = MRRReranker()
|
||||||
_run_test_hybrid_reranker(reranker, tmp_path)
|
_run_test_hybrid_reranker(reranker, tmp_path, use_tantivy)
|
||||||
|
|
||||||
# Test multi-vector part
|
# Test multi-vector part
|
||||||
table, schema = get_test_table(tmp_path)
|
table, schema = get_test_table(tmp_path, use_tantivy)
|
||||||
query = "single player experience"
|
query = "single player experience"
|
||||||
rs1 = table.search(query, vector_column_name="vector").limit(10).with_row_id(True)
|
rs1 = table.search(query, vector_column_name="vector").limit(10).with_row_id(True)
|
||||||
rs2 = (
|
rs2 = (
|
||||||
@@ -357,7 +363,7 @@ def test_rrf_reranker_distance():
|
|||||||
table = db.create_table("test", data)
|
table = db.create_table("test", data)
|
||||||
|
|
||||||
table.create_index(num_partitions=1, num_sub_vectors=2)
|
table.create_index(num_partitions=1, num_sub_vectors=2)
|
||||||
table.create_fts_index("text")
|
table.create_fts_index("text", use_tantivy=False)
|
||||||
|
|
||||||
reranker = RRFReranker(return_score="all")
|
reranker = RRFReranker(return_score="all")
|
||||||
|
|
||||||
@@ -416,31 +422,35 @@ def test_rrf_reranker_distance():
|
|||||||
@pytest.mark.skipif(
|
@pytest.mark.skipif(
|
||||||
os.environ.get("COHERE_API_KEY") is None, reason="COHERE_API_KEY not set"
|
os.environ.get("COHERE_API_KEY") is None, reason="COHERE_API_KEY not set"
|
||||||
)
|
)
|
||||||
def test_cohere_reranker(tmp_path):
|
@pytest.mark.parametrize("use_tantivy", [True, False])
|
||||||
|
def test_cohere_reranker(tmp_path, use_tantivy):
|
||||||
pytest.importorskip("cohere")
|
pytest.importorskip("cohere")
|
||||||
reranker = CohereReranker()
|
reranker = CohereReranker()
|
||||||
table, schema = get_test_table(tmp_path)
|
table, schema = get_test_table(tmp_path, use_tantivy)
|
||||||
_run_test_reranker(reranker, table, "single player experience", None, schema)
|
_run_test_reranker(reranker, table, "single player experience", None, schema)
|
||||||
|
|
||||||
|
|
||||||
def test_cross_encoder_reranker(tmp_path):
|
@pytest.mark.parametrize("use_tantivy", [True, False])
|
||||||
|
def test_cross_encoder_reranker(tmp_path, use_tantivy):
|
||||||
pytest.importorskip("sentence_transformers")
|
pytest.importorskip("sentence_transformers")
|
||||||
reranker = CrossEncoderReranker()
|
reranker = CrossEncoderReranker()
|
||||||
table, schema = get_test_table(tmp_path)
|
table, schema = get_test_table(tmp_path, use_tantivy)
|
||||||
_run_test_reranker(reranker, table, "single player experience", None, schema)
|
_run_test_reranker(reranker, table, "single player experience", None, schema)
|
||||||
|
|
||||||
|
|
||||||
def test_colbert_reranker(tmp_path):
|
@pytest.mark.parametrize("use_tantivy", [True, False])
|
||||||
|
def test_colbert_reranker(tmp_path, use_tantivy):
|
||||||
pytest.importorskip("rerankers")
|
pytest.importorskip("rerankers")
|
||||||
reranker = ColbertReranker()
|
reranker = ColbertReranker()
|
||||||
table, schema = get_test_table(tmp_path)
|
table, schema = get_test_table(tmp_path, use_tantivy)
|
||||||
_run_test_reranker(reranker, table, "single player experience", None, schema)
|
_run_test_reranker(reranker, table, "single player experience", None, schema)
|
||||||
|
|
||||||
|
|
||||||
def test_answerdotai_reranker(tmp_path):
|
@pytest.mark.parametrize("use_tantivy", [True, False])
|
||||||
|
def test_answerdotai_reranker(tmp_path, use_tantivy):
|
||||||
pytest.importorskip("rerankers")
|
pytest.importorskip("rerankers")
|
||||||
reranker = AnswerdotaiRerankers()
|
reranker = AnswerdotaiRerankers()
|
||||||
table, schema = get_test_table(tmp_path)
|
table, schema = get_test_table(tmp_path, use_tantivy)
|
||||||
_run_test_reranker(reranker, table, "single player experience", None, schema)
|
_run_test_reranker(reranker, table, "single player experience", None, schema)
|
||||||
|
|
||||||
|
|
||||||
@@ -449,9 +459,10 @@ def test_answerdotai_reranker(tmp_path):
|
|||||||
or os.environ.get("OPENAI_BASE_URL") is not None,
|
or os.environ.get("OPENAI_BASE_URL") is not None,
|
||||||
reason="OPENAI_API_KEY not set",
|
reason="OPENAI_API_KEY not set",
|
||||||
)
|
)
|
||||||
def test_openai_reranker(tmp_path):
|
@pytest.mark.parametrize("use_tantivy", [True, False])
|
||||||
|
def test_openai_reranker(tmp_path, use_tantivy):
|
||||||
pytest.importorskip("openai")
|
pytest.importorskip("openai")
|
||||||
table, schema = get_test_table(tmp_path)
|
table, schema = get_test_table(tmp_path, use_tantivy)
|
||||||
reranker = OpenaiReranker()
|
reranker = OpenaiReranker()
|
||||||
_run_test_reranker(reranker, table, "single player experience", None, schema)
|
_run_test_reranker(reranker, table, "single player experience", None, schema)
|
||||||
|
|
||||||
@@ -459,9 +470,10 @@ def test_openai_reranker(tmp_path):
|
|||||||
@pytest.mark.skipif(
|
@pytest.mark.skipif(
|
||||||
os.environ.get("JINA_API_KEY") is None, reason="JINA_API_KEY not set"
|
os.environ.get("JINA_API_KEY") is None, reason="JINA_API_KEY not set"
|
||||||
)
|
)
|
||||||
def test_jina_reranker(tmp_path):
|
@pytest.mark.parametrize("use_tantivy", [True, False])
|
||||||
|
def test_jina_reranker(tmp_path, use_tantivy):
|
||||||
pytest.importorskip("jina")
|
pytest.importorskip("jina")
|
||||||
table, schema = get_test_table(tmp_path)
|
table, schema = get_test_table(tmp_path, use_tantivy)
|
||||||
reranker = JinaReranker()
|
reranker = JinaReranker()
|
||||||
_run_test_reranker(reranker, table, "single player experience", None, schema)
|
_run_test_reranker(reranker, table, "single player experience", None, schema)
|
||||||
|
|
||||||
@@ -469,10 +481,11 @@ def test_jina_reranker(tmp_path):
|
|||||||
@pytest.mark.skipif(
|
@pytest.mark.skipif(
|
||||||
os.environ.get("VOYAGE_API_KEY") is None, reason="VOYAGE_API_KEY not set"
|
os.environ.get("VOYAGE_API_KEY") is None, reason="VOYAGE_API_KEY not set"
|
||||||
)
|
)
|
||||||
def test_voyageai_reranker(tmp_path):
|
@pytest.mark.parametrize("use_tantivy", [True, False])
|
||||||
|
def test_voyageai_reranker(tmp_path, use_tantivy):
|
||||||
pytest.importorskip("voyageai")
|
pytest.importorskip("voyageai")
|
||||||
reranker = VoyageAIReranker(model_name="rerank-2.5")
|
reranker = VoyageAIReranker(model_name="rerank-2.5")
|
||||||
table, schema = get_test_table(tmp_path)
|
table, schema = get_test_table(tmp_path, use_tantivy)
|
||||||
_run_test_reranker(reranker, table, "single player experience", None, schema)
|
_run_test_reranker(reranker, table, "single player experience", None, schema)
|
||||||
|
|
||||||
|
|
||||||
@@ -491,7 +504,7 @@ def test_empty_result_reranker():
|
|||||||
|
|
||||||
# Create empty table with schema
|
# Create empty table with schema
|
||||||
empty_table = db.create_table("empty_table", schema=schema, mode="overwrite")
|
empty_table = db.create_table("empty_table", schema=schema, mode="overwrite")
|
||||||
empty_table.create_fts_index("text", replace=True)
|
empty_table.create_fts_index("text", use_tantivy=False, replace=True)
|
||||||
for reranker in [
|
for reranker in [
|
||||||
CrossEncoderReranker(),
|
CrossEncoderReranker(),
|
||||||
# ColbertReranker(),
|
# ColbertReranker(),
|
||||||
@@ -590,10 +603,11 @@ def test_empty_hybrid_result_reranker():
|
|||||||
assert "_rowid" in result.column_names
|
assert "_rowid" in result.column_names
|
||||||
|
|
||||||
|
|
||||||
def test_cross_encoder_reranker_return_all(tmp_path):
|
@pytest.mark.parametrize("use_tantivy", [True, False])
|
||||||
|
def test_cross_encoder_reranker_return_all(tmp_path, use_tantivy):
|
||||||
pytest.importorskip("sentence_transformers")
|
pytest.importorskip("sentence_transformers")
|
||||||
reranker = CrossEncoderReranker(return_score="all")
|
reranker = CrossEncoderReranker(return_score="all")
|
||||||
table, schema = get_test_table(tmp_path)
|
table, schema = get_test_table(tmp_path, use_tantivy)
|
||||||
query = "single player experience"
|
query = "single player experience"
|
||||||
result = (
|
result = (
|
||||||
table.search(query, query_type="hybrid", vector_column_name="vector")
|
table.search(query, query_type="hybrid", vector_column_name="vector")
|
||||||
|
|||||||
@@ -242,8 +242,8 @@ def test_s3_dynamodb_sync(s3_bucket: str, commit_table: str, monkeypatch):
|
|||||||
|
|
||||||
# FTS indices should error since they are not supported yet.
|
# FTS indices should error since they are not supported yet.
|
||||||
with pytest.raises(
|
with pytest.raises(
|
||||||
ValueError,
|
NotImplementedError,
|
||||||
match="Tantivy-based FTS has been removed",
|
match="Full-text search is only supported on the local filesystem",
|
||||||
):
|
):
|
||||||
table.create_fts_index("x", use_tantivy=True)
|
table.create_fts_index("x", use_tantivy=True)
|
||||||
|
|
||||||
|
|||||||
@@ -3,7 +3,6 @@
|
|||||||
|
|
||||||
|
|
||||||
import os
|
import os
|
||||||
import sys
|
|
||||||
from datetime import date, datetime, timedelta
|
from datetime import date, datetime, timedelta
|
||||||
from time import sleep
|
from time import sleep
|
||||||
from typing import List
|
from typing import List
|
||||||
@@ -1948,6 +1947,7 @@ def setup_hybrid_search_table(db: DBConnection, embedding_func):
|
|||||||
|
|
||||||
def test_hybrid_search(tmp_db: DBConnection):
|
def test_hybrid_search(tmp_db: DBConnection):
|
||||||
# This test uses an FTS index
|
# This test uses an FTS index
|
||||||
|
pytest.importorskip("lancedb.fts")
|
||||||
pytest.importorskip("lance")
|
pytest.importorskip("lance")
|
||||||
|
|
||||||
table, MyTable, emb = setup_hybrid_search_table(tmp_db, "test")
|
table, MyTable, emb = setup_hybrid_search_table(tmp_db, "test")
|
||||||
@@ -2018,6 +2018,7 @@ def test_hybrid_search(tmp_db: DBConnection):
|
|||||||
|
|
||||||
def test_hybrid_search_metric_type(tmp_db: DBConnection):
|
def test_hybrid_search_metric_type(tmp_db: DBConnection):
|
||||||
# This test uses an FTS index
|
# This test uses an FTS index
|
||||||
|
pytest.importorskip("lancedb.fts")
|
||||||
pytest.importorskip("lance")
|
pytest.importorskip("lance")
|
||||||
|
|
||||||
# Need to use nonnorm as the embedding function so l2 and dot results
|
# Need to use nonnorm as the embedding function so l2 and dot results
|
||||||
@@ -2039,13 +2040,6 @@ def test_hybrid_search_metric_type(tmp_db: DBConnection):
|
|||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
"consistency_interval", [None, timedelta(seconds=0), timedelta(seconds=0.1)]
|
"consistency_interval", [None, timedelta(seconds=0), timedelta(seconds=0.1)]
|
||||||
)
|
)
|
||||||
@pytest.mark.skipif(
|
|
||||||
sys.platform == "win32",
|
|
||||||
reason=(
|
|
||||||
"TODO: directory namespace is not supported on Windows yet; "
|
|
||||||
"re-enable after that is fixed."
|
|
||||||
),
|
|
||||||
)
|
|
||||||
def test_consistency(tmp_path, consistency_interval):
|
def test_consistency(tmp_path, consistency_interval):
|
||||||
db = lancedb.connect(tmp_path)
|
db = lancedb.connect(tmp_path)
|
||||||
table = db.create_table("my_table", data=[{"id": 0}])
|
table = db.create_table("my_table", data=[{"id": 0}])
|
||||||
@@ -2066,6 +2060,7 @@ def test_consistency(tmp_path, consistency_interval):
|
|||||||
elif consistency_interval == timedelta(seconds=0):
|
elif consistency_interval == timedelta(seconds=0):
|
||||||
assert table2.version == table.version
|
assert table2.version == table.version
|
||||||
else:
|
else:
|
||||||
|
# (consistency_interval == timedelta(seconds=0.1)
|
||||||
assert table2.version == table.version - 1
|
assert table2.version == table.version - 1
|
||||||
sleep(0.1)
|
sleep(0.1)
|
||||||
assert table2.version == table.version
|
assert table2.version == table.version
|
||||||
|
|||||||
@@ -1,17 +1,11 @@
|
|||||||
// 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::{
|
use std::{collections::HashMap, sync::Arc, time::Duration};
|
||||||
collections::{HashMap, HashSet},
|
|
||||||
sync::Arc,
|
|
||||||
time::Duration,
|
|
||||||
};
|
|
||||||
|
|
||||||
use arrow::{datatypes::Schema, ffi_stream::ArrowArrayStreamReader, pyarrow::FromPyArrow};
|
use arrow::{datatypes::Schema, ffi_stream::ArrowArrayStreamReader, pyarrow::FromPyArrow};
|
||||||
use lancedb::{
|
use lancedb::{
|
||||||
connection::Connection as LanceConnection,
|
connection::Connection as LanceConnection,
|
||||||
connection::NamespaceClientPushdownOperation,
|
|
||||||
database::namespace::LanceNamespaceDatabase,
|
|
||||||
database::{CreateTableMode, Database, ReadConsistency},
|
database::{CreateTableMode, Database, ReadConsistency},
|
||||||
};
|
};
|
||||||
use pyo3::{
|
use pyo3::{
|
||||||
@@ -45,29 +39,6 @@ impl Connection {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn parse_namespace_client_pushdown_operations(
|
|
||||||
operations: Option<Vec<String>>,
|
|
||||||
) -> PyResult<HashSet<NamespaceClientPushdownOperation>> {
|
|
||||||
let mut parsed = HashSet::new();
|
|
||||||
for operation in operations.unwrap_or_default() {
|
|
||||||
match operation.as_str() {
|
|
||||||
"QueryTable" => {
|
|
||||||
parsed.insert(NamespaceClientPushdownOperation::QueryTable);
|
|
||||||
}
|
|
||||||
"CreateTable" => {
|
|
||||||
parsed.insert(NamespaceClientPushdownOperation::CreateTable);
|
|
||||||
}
|
|
||||||
_ => {
|
|
||||||
return Err(PyValueError::new_err(format!(
|
|
||||||
"Invalid pushdown operation: {}",
|
|
||||||
operation
|
|
||||||
)));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Ok(parsed)
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Connection {
|
impl Connection {
|
||||||
fn parse_create_mode_str(mode: &str) -> PyResult<CreateTableMode> {
|
fn parse_create_mode_str(mode: &str) -> PyResult<CreateTableMode> {
|
||||||
match mode {
|
match mode {
|
||||||
@@ -525,7 +496,7 @@ impl Connection {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[pyfunction]
|
#[pyfunction]
|
||||||
#[pyo3(signature = (uri, api_key=None, region=None, host_override=None, read_consistency_interval=None, client_config=None, storage_options=None, session=None, manifest_enabled=false, namespace_client_properties=None))]
|
#[pyo3(signature = (uri, api_key=None, region=None, host_override=None, read_consistency_interval=None, client_config=None, storage_options=None, session=None))]
|
||||||
#[allow(clippy::too_many_arguments)]
|
#[allow(clippy::too_many_arguments)]
|
||||||
pub fn connect(
|
pub fn connect(
|
||||||
py: Python<'_>,
|
py: Python<'_>,
|
||||||
@@ -537,8 +508,6 @@ pub fn connect(
|
|||||||
client_config: Option<PyClientConfig>,
|
client_config: Option<PyClientConfig>,
|
||||||
storage_options: Option<HashMap<String, String>>,
|
storage_options: Option<HashMap<String, String>>,
|
||||||
session: Option<crate::session::Session>,
|
session: Option<crate::session::Session>,
|
||||||
manifest_enabled: bool,
|
|
||||||
namespace_client_properties: Option<HashMap<String, String>>,
|
|
||||||
) -> PyResult<Bound<'_, PyAny>> {
|
) -> PyResult<Bound<'_, PyAny>> {
|
||||||
future_into_py(py, async move {
|
future_into_py(py, async move {
|
||||||
let mut builder = lancedb::connect(&uri);
|
let mut builder = lancedb::connect(&uri);
|
||||||
@@ -558,12 +527,6 @@ pub fn connect(
|
|||||||
if let Some(storage_options) = storage_options {
|
if let Some(storage_options) = storage_options {
|
||||||
builder = builder.storage_options(storage_options);
|
builder = builder.storage_options(storage_options);
|
||||||
}
|
}
|
||||||
if manifest_enabled {
|
|
||||||
builder = builder.manifest_enabled(true);
|
|
||||||
}
|
|
||||||
if let Some(namespace_client_properties) = namespace_client_properties {
|
|
||||||
builder = builder.namespace_client_properties(namespace_client_properties);
|
|
||||||
}
|
|
||||||
#[cfg(feature = "remote")]
|
#[cfg(feature = "remote")]
|
||||||
if let Some(client_config) = client_config {
|
if let Some(client_config) = client_config {
|
||||||
builder = builder.client_config(client_config.into());
|
builder = builder.client_config(client_config.into());
|
||||||
@@ -575,52 +538,6 @@ pub fn connect(
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
#[pyfunction]
|
|
||||||
#[pyo3(signature = (
|
|
||||||
namespace_client,
|
|
||||||
read_consistency_interval=None,
|
|
||||||
storage_options=None,
|
|
||||||
session=None,
|
|
||||||
namespace_client_pushdown_operations=None,
|
|
||||||
namespace_client_impl=None,
|
|
||||||
namespace_client_properties=None,
|
|
||||||
))]
|
|
||||||
#[allow(clippy::too_many_arguments)]
|
|
||||||
pub fn connect_namespace_client(
|
|
||||||
py: Python<'_>,
|
|
||||||
namespace_client: Py<PyAny>,
|
|
||||||
read_consistency_interval: Option<f64>,
|
|
||||||
storage_options: Option<HashMap<String, String>>,
|
|
||||||
session: Option<crate::session::Session>,
|
|
||||||
namespace_client_pushdown_operations: Option<Vec<String>>,
|
|
||||||
namespace_client_impl: Option<String>,
|
|
||||||
namespace_client_properties: Option<HashMap<String, String>>,
|
|
||||||
) -> PyResult<Connection> {
|
|
||||||
let namespace_client = extract_namespace_arc(py, namespace_client)?;
|
|
||||||
let read_consistency_interval = read_consistency_interval.map(Duration::from_secs_f64);
|
|
||||||
let namespace_client_pushdown_operations =
|
|
||||||
parse_namespace_client_pushdown_operations(namespace_client_pushdown_operations)?;
|
|
||||||
let ns_impl = namespace_client_impl.unwrap_or_else(|| "python".to_string());
|
|
||||||
let ns_properties = namespace_client_properties.unwrap_or_default();
|
|
||||||
let storage_options = storage_options.unwrap_or_default();
|
|
||||||
let session = session.map(|s| s.inner.clone());
|
|
||||||
|
|
||||||
let database = LanceNamespaceDatabase::from_namespace_client(
|
|
||||||
namespace_client,
|
|
||||||
ns_impl,
|
|
||||||
ns_properties,
|
|
||||||
storage_options,
|
|
||||||
read_consistency_interval,
|
|
||||||
session,
|
|
||||||
namespace_client_pushdown_operations,
|
|
||||||
);
|
|
||||||
|
|
||||||
Ok(Connection::new(LanceConnection::new(
|
|
||||||
Arc::new(database),
|
|
||||||
Arc::new(lancedb::embeddings::MemoryRegistry::new()),
|
|
||||||
)))
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(FromPyObject)]
|
#[derive(FromPyObject)]
|
||||||
pub struct PyClientConfig {
|
pub struct PyClientConfig {
|
||||||
user_agent: String,
|
user_agent: String,
|
||||||
|
|||||||
+1
-1
@@ -17,7 +17,7 @@ use pyo3::{Bound, PyAny, PyResult, exceptions::PyValueError, prelude::*, pyfunct
|
|||||||
/// [`expr_lit`] and combined with the methods on this struct. On the Python
|
/// [`expr_lit`] and combined with the methods on this struct. On the Python
|
||||||
/// side a thin wrapper class (`lancedb.expr.Expr`) delegates to these methods
|
/// side a thin wrapper class (`lancedb.expr.Expr`) delegates to these methods
|
||||||
/// and adds Python operator overloads.
|
/// and adds Python operator overloads.
|
||||||
#[pyclass(name = "PyExpr", from_py_object)]
|
#[pyclass(name = "PyExpr")]
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct PyExpr(pub DfExpr);
|
pub struct PyExpr(pub DfExpr);
|
||||||
|
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ impl PyHeaderProvider {
|
|||||||
Ok(headers_py) => {
|
Ok(headers_py) => {
|
||||||
// Convert Python dict to Rust HashMap
|
// Convert Python dict to Rust HashMap
|
||||||
let bound_headers = headers_py.bind(py);
|
let bound_headers = headers_py.bind(py);
|
||||||
let dict: &Bound<PyDict> = bound_headers.cast().map_err(|e| {
|
let dict: &Bound<PyDict> = bound_headers.downcast().map_err(|e| {
|
||||||
format!("HeaderProvider.get_headers must return a dict: {}", e)
|
format!("HeaderProvider.get_headers must return a dict: {}", e)
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
|
|||||||
+2
-2
@@ -13,7 +13,7 @@ use pyo3::{
|
|||||||
Bound, FromPyObject, PyAny, PyResult, Python,
|
Bound, FromPyObject, PyAny, PyResult, Python,
|
||||||
exceptions::{PyKeyError, PyValueError},
|
exceptions::{PyKeyError, PyValueError},
|
||||||
intern, pyclass, pymethods,
|
intern, pyclass, pymethods,
|
||||||
types::{PyAnyMethods, PyString},
|
types::PyAnyMethods,
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::util::parse_distance_type;
|
use crate::util::parse_distance_type;
|
||||||
@@ -22,7 +22,7 @@ pub fn class_name(ob: &'_ Bound<'_, PyAny>) -> PyResult<String> {
|
|||||||
let full_name = ob
|
let full_name = ob
|
||||||
.getattr(intern!(ob.py(), "__class__"))?
|
.getattr(intern!(ob.py(), "__class__"))?
|
||||||
.getattr(intern!(ob.py(), "__name__"))?;
|
.getattr(intern!(ob.py(), "__name__"))?;
|
||||||
let full_name = full_name.cast::<PyString>()?.to_string_lossy();
|
let full_name = full_name.downcast()?.to_string_lossy();
|
||||||
|
|
||||||
match full_name.rsplit_once('.') {
|
match full_name.rsplit_once('.') {
|
||||||
Some((_, name)) => Ok(name.to_string()),
|
Some((_, name)) => Ok(name.to_string()),
|
||||||
|
|||||||
+1
-2
@@ -2,7 +2,7 @@
|
|||||||
// SPDX-FileCopyrightText: Copyright The LanceDB Authors
|
// SPDX-FileCopyrightText: Copyright The LanceDB Authors
|
||||||
|
|
||||||
use arrow::RecordBatchStream;
|
use arrow::RecordBatchStream;
|
||||||
use connection::{Connection, connect, connect_namespace_client};
|
use connection::{Connection, connect};
|
||||||
use env_logger::Env;
|
use env_logger::Env;
|
||||||
use expr::{PyExpr, expr_col, expr_func, expr_lit};
|
use expr::{PyExpr, expr_col, expr_func, expr_lit};
|
||||||
use index::IndexConfig;
|
use index::IndexConfig;
|
||||||
@@ -58,7 +58,6 @@ pub fn _lancedb(_py: Python, m: &Bound<'_, PyModule>) -> PyResult<()> {
|
|||||||
m.add_class::<PyPermutationReader>()?;
|
m.add_class::<PyPermutationReader>()?;
|
||||||
m.add_class::<PyExpr>()?;
|
m.add_class::<PyExpr>()?;
|
||||||
m.add_function(wrap_pyfunction!(connect, m)?)?;
|
m.add_function(wrap_pyfunction!(connect, m)?)?;
|
||||||
m.add_function(wrap_pyfunction!(connect_namespace_client, m)?)?;
|
|
||||||
m.add_function(wrap_pyfunction!(permutation::async_permutation_builder, m)?)?;
|
m.add_function(wrap_pyfunction!(permutation::async_permutation_builder, m)?)?;
|
||||||
m.add_function(wrap_pyfunction!(util::validate_table_name, m)?)?;
|
m.add_function(wrap_pyfunction!(util::validate_table_name, m)?)?;
|
||||||
m.add_function(wrap_pyfunction!(query::fts_query_to_json, m)?)?;
|
m.add_function(wrap_pyfunction!(query::fts_query_to_json, m)?)?;
|
||||||
|
|||||||
@@ -183,7 +183,7 @@ async fn call_py_method_primitive<Req, Resp>(
|
|||||||
) -> lance_core::Result<Resp>
|
) -> lance_core::Result<Resp>
|
||||||
where
|
where
|
||||||
Req: serde::Serialize + Send + 'static,
|
Req: serde::Serialize + Send + 'static,
|
||||||
Resp: for<'a, 'py> pyo3::FromPyObject<'a, 'py> + Send + 'static,
|
Resp: for<'py> pyo3::FromPyObject<'py> + Send + 'static,
|
||||||
{
|
{
|
||||||
let request_json = serde_json::to_string(&request).map_err(|e| {
|
let request_json = serde_json::to_string(&request).map_err(|e| {
|
||||||
lance_core::Error::io(format!(
|
lance_core::Error::io(format!(
|
||||||
@@ -203,7 +203,7 @@ where
|
|||||||
|
|
||||||
// Call the Python method
|
// Call the Python method
|
||||||
let result = py_namespace.call_method1(py, method_name, (request_arg,))?;
|
let result = py_namespace.call_method1(py, method_name, (request_arg,))?;
|
||||||
let value: Resp = result.extract(py).map_err(Into::into)?;
|
let value: Resp = result.extract(py)?;
|
||||||
Ok::<_, PyErr>(value)
|
Ok::<_, PyErr>(value)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -25,12 +25,12 @@ use pyo3_async_runtimes::tokio::future_into_py;
|
|||||||
|
|
||||||
fn table_from_py<'a>(table: Bound<'a, PyAny>) -> PyResult<Bound<'a, Table>> {
|
fn table_from_py<'a>(table: Bound<'a, PyAny>) -> PyResult<Bound<'a, Table>> {
|
||||||
if table.hasattr("_inner")? {
|
if table.hasattr("_inner")? {
|
||||||
Ok(table.getattr("_inner")?.cast_into::<Table>()?)
|
Ok(table.getattr("_inner")?.downcast_into::<Table>()?)
|
||||||
} else if table.hasattr("_table")? {
|
} else if table.hasattr("_table")? {
|
||||||
Ok(table
|
Ok(table
|
||||||
.getattr("_table")?
|
.getattr("_table")?
|
||||||
.getattr("_inner")?
|
.getattr("_inner")?
|
||||||
.cast_into::<Table>()?)
|
.downcast_into::<Table>()?)
|
||||||
} else {
|
} else {
|
||||||
Err(PyRuntimeError::new_err(
|
Err(PyRuntimeError::new_err(
|
||||||
"Provided table does not appear to be a Table or RemoteTable instance",
|
"Provided table does not appear to be a Table or RemoteTable instance",
|
||||||
@@ -90,9 +90,9 @@ impl PyAsyncPermutationBuilder {
|
|||||||
database
|
database
|
||||||
.getattr("_conn")?
|
.getattr("_conn")?
|
||||||
.getattr("_inner")?
|
.getattr("_inner")?
|
||||||
.cast_into::<Connection>()?
|
.downcast_into::<Connection>()?
|
||||||
} else {
|
} else {
|
||||||
database.getattr("_inner")?.cast_into::<Connection>()?
|
database.getattr("_inner")?.downcast_into::<Connection>()?
|
||||||
};
|
};
|
||||||
let database = conn.borrow().database()?;
|
let database = conn.borrow().database()?;
|
||||||
slf.modify(|builder| builder.persist(database, table_name))
|
slf.modify(|builder| builder.persist(database, table_name))
|
||||||
@@ -243,7 +243,7 @@ impl PyPermutationReader {
|
|||||||
let Some(selection) = selection else {
|
let Some(selection) = selection else {
|
||||||
return Ok(Select::All);
|
return Ok(Select::All);
|
||||||
};
|
};
|
||||||
let selection = selection.cast_into::<PyDict>()?;
|
let selection = selection.downcast_into::<PyDict>()?;
|
||||||
let selection = selection
|
let selection = selection
|
||||||
.iter()
|
.iter()
|
||||||
.map(|(key, value)| {
|
.map(|(key, value)| {
|
||||||
|
|||||||
+7
-10
@@ -33,7 +33,7 @@ use pyo3::pyfunction;
|
|||||||
use pyo3::pymethods;
|
use pyo3::pymethods;
|
||||||
use pyo3::types::PyList;
|
use pyo3::types::PyList;
|
||||||
use pyo3::types::{PyDict, PyString};
|
use pyo3::types::{PyDict, PyString};
|
||||||
use pyo3::{Borrowed, FromPyObject, exceptions::PyRuntimeError};
|
use pyo3::{FromPyObject, exceptions::PyRuntimeError};
|
||||||
use pyo3::{PyErr, pyclass};
|
use pyo3::{PyErr, pyclass};
|
||||||
use pyo3::{exceptions::PyValueError, intern};
|
use pyo3::{exceptions::PyValueError, intern};
|
||||||
use pyo3_async_runtimes::tokio::future_into_py;
|
use pyo3_async_runtimes::tokio::future_into_py;
|
||||||
@@ -43,12 +43,9 @@ use crate::util::parse_distance_type;
|
|||||||
use crate::{arrow::RecordBatchStream, util::PyLanceDB};
|
use crate::{arrow::RecordBatchStream, util::PyLanceDB};
|
||||||
use crate::{error::PythonErrorExt, index::class_name};
|
use crate::{error::PythonErrorExt, index::class_name};
|
||||||
|
|
||||||
impl<'a, 'py> FromPyObject<'a, 'py> for PyLanceDB<FtsQuery> {
|
impl FromPyObject<'_> for PyLanceDB<FtsQuery> {
|
||||||
type Error = PyErr;
|
fn extract_bound(ob: &Bound<'_, PyAny>) -> PyResult<Self> {
|
||||||
|
match class_name(ob)?.as_str() {
|
||||||
fn extract(ob: Borrowed<'a, 'py, PyAny>) -> PyResult<Self> {
|
|
||||||
let ob = ob.to_owned();
|
|
||||||
match class_name(&ob)?.as_str() {
|
|
||||||
"MatchQuery" => {
|
"MatchQuery" => {
|
||||||
let query = ob.getattr("query")?.extract()?;
|
let query = ob.getattr("query")?.extract()?;
|
||||||
let column = ob.getattr("column")?.extract()?;
|
let column = ob.getattr("column")?.extract()?;
|
||||||
@@ -427,7 +424,7 @@ impl Query {
|
|||||||
"Query text is required for nearest_to_text",
|
"Query text is required for nearest_to_text",
|
||||||
))?;
|
))?;
|
||||||
|
|
||||||
let query = if let Ok(query_text) = fts_query.cast::<PyString>() {
|
let query = if let Ok(query_text) = fts_query.downcast::<PyString>() {
|
||||||
let mut query_text = query_text.to_string();
|
let mut query_text = query_text.to_string();
|
||||||
let columns = query
|
let columns = query
|
||||||
.get_item("columns")?
|
.get_item("columns")?
|
||||||
@@ -609,7 +606,7 @@ impl TakeQuery {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[pyclass(from_py_object)]
|
#[pyclass]
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct FTSQuery {
|
pub struct FTSQuery {
|
||||||
inner: LanceDbQuery,
|
inner: LanceDbQuery,
|
||||||
@@ -738,7 +735,7 @@ impl FTSQuery {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[pyclass(from_py_object)]
|
#[pyclass]
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct VectorQuery {
|
pub struct VectorQuery {
|
||||||
inner: LanceDbVectorQuery,
|
inner: LanceDbVectorQuery,
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ use pyo3::{PyResult, pyclass, pymethods};
|
|||||||
/// Sessions allow you to configure cache sizes for index and metadata caches,
|
/// Sessions allow you to configure cache sizes for index and metadata caches,
|
||||||
/// which can significantly impact memory use and performance. They can
|
/// which can significantly impact memory use and performance. They can
|
||||||
/// also be re-used across multiple connections to share the same cache state.
|
/// also be re-used across multiple connections to share the same cache state.
|
||||||
#[pyclass(from_py_object)]
|
#[pyclass]
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct Session {
|
pub struct Session {
|
||||||
pub(crate) inner: Arc<LanceSession>,
|
pub(crate) inner: Arc<LanceSession>,
|
||||||
|
|||||||
+10
-10
@@ -29,7 +29,7 @@ use pyo3_async_runtimes::tokio::future_into_py;
|
|||||||
mod scannable;
|
mod scannable;
|
||||||
|
|
||||||
/// Statistics about a compaction operation.
|
/// Statistics about a compaction operation.
|
||||||
#[pyclass(get_all, from_py_object)]
|
#[pyclass(get_all)]
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub struct CompactionStats {
|
pub struct CompactionStats {
|
||||||
/// The number of fragments removed
|
/// The number of fragments removed
|
||||||
@@ -43,7 +43,7 @@ pub struct CompactionStats {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Statistics about a cleanup operation
|
/// Statistics about a cleanup operation
|
||||||
#[pyclass(get_all, from_py_object)]
|
#[pyclass(get_all)]
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub struct RemovalStats {
|
pub struct RemovalStats {
|
||||||
/// The number of bytes removed
|
/// The number of bytes removed
|
||||||
@@ -53,7 +53,7 @@ pub struct RemovalStats {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Statistics about an optimize operation
|
/// Statistics about an optimize operation
|
||||||
#[pyclass(get_all, from_py_object)]
|
#[pyclass(get_all)]
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub struct OptimizeStats {
|
pub struct OptimizeStats {
|
||||||
/// Statistics about the compaction operation
|
/// Statistics about the compaction operation
|
||||||
@@ -62,7 +62,7 @@ pub struct OptimizeStats {
|
|||||||
pub prune: RemovalStats,
|
pub prune: RemovalStats,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[pyclass(get_all, from_py_object)]
|
#[pyclass(get_all)]
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub struct UpdateResult {
|
pub struct UpdateResult {
|
||||||
pub rows_updated: u64,
|
pub rows_updated: u64,
|
||||||
@@ -88,7 +88,7 @@ impl From<lancedb::table::UpdateResult> for UpdateResult {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[pyclass(get_all, from_py_object)]
|
#[pyclass(get_all)]
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub struct AddResult {
|
pub struct AddResult {
|
||||||
pub version: u64,
|
pub version: u64,
|
||||||
@@ -109,7 +109,7 @@ impl From<lancedb::table::AddResult> for AddResult {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[pyclass(get_all, from_py_object)]
|
#[pyclass(get_all)]
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub struct DeleteResult {
|
pub struct DeleteResult {
|
||||||
pub num_deleted_rows: u64,
|
pub num_deleted_rows: u64,
|
||||||
@@ -135,7 +135,7 @@ impl From<lancedb::table::DeleteResult> for DeleteResult {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[pyclass(get_all, from_py_object)]
|
#[pyclass(get_all)]
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub struct MergeResult {
|
pub struct MergeResult {
|
||||||
pub version: u64,
|
pub version: u64,
|
||||||
@@ -171,7 +171,7 @@ impl From<lancedb::table::MergeResult> for MergeResult {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[pyclass(get_all, from_py_object)]
|
#[pyclass(get_all)]
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub struct AddColumnsResult {
|
pub struct AddColumnsResult {
|
||||||
pub version: u64,
|
pub version: u64,
|
||||||
@@ -192,7 +192,7 @@ impl From<lancedb::table::AddColumnsResult> for AddColumnsResult {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[pyclass(get_all, from_py_object)]
|
#[pyclass(get_all)]
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub struct AlterColumnsResult {
|
pub struct AlterColumnsResult {
|
||||||
pub version: u64,
|
pub version: u64,
|
||||||
@@ -213,7 +213,7 @@ impl From<lancedb::table::AlterColumnsResult> for AlterColumnsResult {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[pyclass(get_all, from_py_object)]
|
#[pyclass(get_all)]
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub struct DropColumnsResult {
|
pub struct DropColumnsResult {
|
||||||
pub version: u64,
|
pub version: u64,
|
||||||
|
|||||||
@@ -126,11 +126,8 @@ impl Scannable for PyScannable {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a, 'py> FromPyObject<'a, 'py> for PyScannable {
|
impl<'py> FromPyObject<'py> for PyScannable {
|
||||||
type Error = pyo3::PyErr;
|
fn extract_bound(ob: &pyo3::Bound<'py, PyAny>) -> pyo3::PyResult<Self> {
|
||||||
|
|
||||||
fn extract(ob: pyo3::Borrowed<'a, 'py, PyAny>) -> pyo3::PyResult<Self> {
|
|
||||||
let ob = ob.to_owned();
|
|
||||||
// Convert from Scannable dataclass.
|
// Convert from Scannable dataclass.
|
||||||
let schema: PyArrowType<Schema> = ob.getattr("schema")?.extract()?;
|
let schema: PyArrowType<Schema> = ob.getattr("schema")?.extract()?;
|
||||||
let schema = Arc::new(schema.0);
|
let schema = Arc::new(schema.0);
|
||||||
|
|||||||
Generated
+40
@@ -1996,6 +1996,7 @@ tests = [
|
|||||||
{ name = "pytest-mock" },
|
{ name = "pytest-mock" },
|
||||||
{ name = "pytz" },
|
{ name = "pytz" },
|
||||||
{ name = "requests" },
|
{ name = "requests" },
|
||||||
|
{ name = "tantivy" },
|
||||||
]
|
]
|
||||||
|
|
||||||
[package.metadata]
|
[package.metadata]
|
||||||
@@ -2049,6 +2050,7 @@ requires-dist = [
|
|||||||
{ name = "sentence-transformers", marker = "extra == 'embeddings'", specifier = ">=2.2.0" },
|
{ name = "sentence-transformers", marker = "extra == 'embeddings'", specifier = ">=2.2.0" },
|
||||||
{ name = "sentencepiece", marker = "extra == 'embeddings'", specifier = ">=0.1.99" },
|
{ name = "sentencepiece", marker = "extra == 'embeddings'", specifier = ">=0.1.99" },
|
||||||
{ name = "sentencepiece", marker = "extra == 'siglip'" },
|
{ name = "sentencepiece", marker = "extra == 'siglip'" },
|
||||||
|
{ name = "tantivy", marker = "extra == 'tests'", specifier = ">=0.20.0" },
|
||||||
{ name = "torch", marker = "extra == 'clip'" },
|
{ name = "torch", marker = "extra == 'clip'" },
|
||||||
{ name = "torch", marker = "extra == 'embeddings'", specifier = ">=2.0.0" },
|
{ name = "torch", marker = "extra == 'embeddings'", specifier = ">=2.0.0" },
|
||||||
{ name = "torch", marker = "extra == 'siglip'" },
|
{ name = "torch", marker = "extra == 'siglip'" },
|
||||||
@@ -4777,6 +4779,44 @@ wheels = [
|
|||||||
{ url = "https://files.pythonhosted.org/packages/40/44/4a5f08c96eb108af5cb50b41f76142f0afa346dfa99d5296fe7202a11854/tabulate-0.9.0-py3-none-any.whl", hash = "sha256:024ca478df22e9340661486f85298cff5f6dcdba14f3813e8830015b9ed1948f", size = 35252, upload-time = "2022-10-06T17:21:44.262Z" },
|
{ url = "https://files.pythonhosted.org/packages/40/44/4a5f08c96eb108af5cb50b41f76142f0afa346dfa99d5296fe7202a11854/tabulate-0.9.0-py3-none-any.whl", hash = "sha256:024ca478df22e9340661486f85298cff5f6dcdba14f3813e8830015b9ed1948f", size = 35252, upload-time = "2022-10-06T17:21:44.262Z" },
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "tantivy"
|
||||||
|
version = "0.25.1"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/1b/f9/0cd3955d155d3e3ef74b864769514dd191e5dacba9f0beb7af2d914942ce/tantivy-0.25.1.tar.gz", hash = "sha256:68a3314699a7d18fcf338b52bae8ce46a97dde1128a3e47e33fa4db7f71f265e", size = 75120, upload-time = "2025-12-02T11:57:12.997Z" }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/80/f7/2276bed3bed983ce2970dc70e3571f372587fe4f5f2bac1d6d617df08fa3/tantivy-0.25.1-cp310-cp310-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:7aa587a3dc9470584cacf5e3640fee93d12ec5f10109669c1f47c4e90820b958", size = 7638510, upload-time = "2025-12-02T11:56:08.754Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/20/8c/078dc50570e243414356b05633f52fe544b85179281ffa9f1fe05d76bbd8/tantivy-0.25.1-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:56d77fe667595693d9fa5f0b4545776d84da9526bab0273b3fc6c7536dc0d8a2", size = 3932659, upload-time = "2025-12-02T11:56:10.621Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/bd/dc/281c48436a1e3178b58fe463af314434fe0f3a4ec0c7588a362900e0c69e/tantivy-0.25.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5ba8c347cd48595fcaeabb28a909ebce92cf9c5e5c84ab5ba1136a280a307b5c", size = 4197430, upload-time = "2025-12-02T11:56:12.65Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/7b/6c/61e6e0b0a350007d10a9b66a35703361d3345e14e7a7cc83494776b2a054/tantivy-0.25.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:aa7c4932e8fde1f09f2d46226060e827e197c2749abdc6129d73a752773adc38", size = 4184055, upload-time = "2025-12-02T11:56:14.647Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/5f/fd/0eb059b12f0b6f91623a54a46448a83b7f716d08f3bca68c095d697b85da/tantivy-0.25.1-cp310-cp310-win_amd64.whl", hash = "sha256:afcfc5dbb0bcd5d24531f4471737ae0896f33528426ab0b1dad3e427c19120f6", size = 3424134, upload-time = "2025-12-02T11:56:16.242Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/4e/7a/8a277f377e8a151fc0e71d4ffc1114aefb6e5e1c7dd609fed0955cf34ed8/tantivy-0.25.1-cp311-cp311-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:d363d7b4207d3a5aa7f0d212420df35bed18bdb6bae26a2a8bd57428388b7c29", size = 7637033, upload-time = "2025-12-02T11:56:18.104Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/71/31/8b4acdedfc9f9a2d04b1340d07eef5213d6f151d1e18da0cb423e5f090d2/tantivy-0.25.1-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:8f4389cf1d889a1df7c5a3195806b4b56c37cee10d8a26faaa0dea35a867b5ff", size = 3932180, upload-time = "2025-12-02T11:56:19.833Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/2f/dc/3e8499c21b4b9795e8f2fc54c68ce5b92905aaeadadaa56ecfa9180b11b1/tantivy-0.25.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:99864c09fc54652c3c2486cdf13f86cdc8200f4b481569cb291e095ca5d496e5", size = 4197620, upload-time = "2025-12-02T11:56:21.496Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/f8/8e/f2ce62fffc811eb62bead92c7b23c2e218f817cbd54c4f3b802e03ba1438/tantivy-0.25.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:05abf37ddbc5063c575548be0d62931629c086bff7a5a1b67cf5a8f5ebf4cd8c", size = 4183794, upload-time = "2025-12-02T11:56:23.215Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/de/64/24e2891b0ba3fd9853e10c296095a33b89bf3efd65e29da1ee5dae736040/tantivy-0.25.1-cp311-cp311-win_amd64.whl", hash = "sha256:f307ee8ad21597b0be23af83008fd66cfd5f958cdfa24ec0aaa08a38e86bbef4", size = 3424235, upload-time = "2025-12-02T11:56:25.172Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/41/e7/6849c713ed0996c7628324c60512c4882006f0a62145e56c624a93407f90/tantivy-0.25.1-cp312-cp312-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:90fd919e5f611809f746560ecf36eb9be824dec62e21ae17a27243759edb9aa1", size = 7621494, upload-time = "2025-12-02T11:56:27.069Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/c5/22/c3d8294600dc6e7fa350daef9ff337d3c06e132b81df727de9f7a50c692a/tantivy-0.25.1-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:4613c7cf6c23f3a97989819690a0f956d799354957de7a204abcc60083cebe02", size = 3925219, upload-time = "2025-12-02T11:56:29.403Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/41/fc/cbb1df71dd44c9110eff4eaaeda9d44f2d06182fe0452193be20ddfba93f/tantivy-0.25.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c477bd20b4df804d57dfc5033431bef27cde605695ae141b03abbf6ebc069129", size = 4198699, upload-time = "2025-12-02T11:56:31.359Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/47/4d/71abb78b774073c3ce12a4faa4351a9d910a71ffa3659526affba163873d/tantivy-0.25.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f9b1a1ba1113c523c7ff7b10f282d6c4074006f7ef8d71e1d973d51bf7291ddb", size = 4183585, upload-time = "2025-12-02T11:56:33.317Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/be/16/3f00cd7ec458b92a0e977960af9ddfbeb762127d9acc68da9094a1fda556/tantivy-0.25.1-cp312-cp312-win_amd64.whl", hash = "sha256:9de0bafd3bd7ac9f8f82d53e17562e9db11a5af308fe5185c4bd86feaddbe4a6", size = 3424622, upload-time = "2025-12-02T11:56:34.788Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/3d/25/73cfbcf1a8ea49be6c42817431cac46b70a119fe64da903fcc2d92b5b511/tantivy-0.25.1-cp313-cp313-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:f51ff7196c6f31719202080ed8372d5e3d51e92c749c032fb8234f012e99744c", size = 7622530, upload-time = "2025-12-02T11:56:36.839Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/12/c8/c0d7591cdf4f7e7a9fc4da786d1ca8cd1aacffaa2be16ea6d401a8e4a566/tantivy-0.25.1-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:550e63321bfcacc003859f2fa29c1e8e56450807b3c9a501c1add27cfb9236d9", size = 3925637, upload-time = "2025-12-02T11:56:38.425Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/3a/09/bedfc223bffec7641b417dd7ab071134b2ef8f8550e9b1fb6014657ef52e/tantivy-0.25.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fde31cc8d6e122faf7902aeea32bc008a429a6e8904e34d3468126a3ec01b016", size = 4197322, upload-time = "2025-12-02T11:56:40.411Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/f5/f1/1fa5183500c8042200c9f2b840d34f5bbcfb434a1ee750e7132262d2a5c9/tantivy-0.25.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b11bd5a518b0be645320b47af8493f6a40c4f3234313e37adcf4534a564d27dd", size = 4183143, upload-time = "2025-12-02T11:56:42.048Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/d5/74/a4c4f4eb95888ccb784da3b017aa0625ab1ac411bf5d022a9a797d9a2334/tantivy-0.25.1-cp313-cp313-win_amd64.whl", hash = "sha256:cc7fe88853e06b3251ee4fa42b7a2038727f850c8765bcc8167cfc73585dd24e", size = 3423491, upload-time = "2025-12-02T11:56:43.858Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/8b/2f/581519492226f97d23bd0adc95dad991ebeaa73ea6abc8bff389a3096d9a/tantivy-0.25.1-cp313-cp313t-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:dae99e75b7eaa9bf5bd16ab106b416370f08c135aed0e117d62a3201cd1ffe36", size = 7610316, upload-time = "2025-12-02T11:56:45.927Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/91/40/5d7bc315ab9e6a22c5572656e8ada1c836cfa96dccf533377504fbc3c9d9/tantivy-0.25.1-cp313-cp313t-macosx_10_12_x86_64.whl", hash = "sha256:506e9533c5ef4d3df43bad64ffecc0aa97c76e361ea610815dc3a20a9d6b30b3", size = 3919882, upload-time = "2025-12-02T11:56:48.469Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/02/b9/e0ef2f57a6a72444cb66c2ffbc310ab33ffaace275f1c4b0319d84ea3f18/tantivy-0.25.1-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5dbd4f8f264dacbcc9dee542832da2173fd53deaaea03f082d95214f8b5ed6bc", size = 4196031, upload-time = "2025-12-02T11:56:50.151Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/1e/02/bf3f8cacfd08642e14a73f7956a3fb95d58119132c98c121b9065a1f8615/tantivy-0.25.1-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:824c643ccb640dd9e35e00c5d5054ddf3323f56fe4219d57d428a9eeea13d22c", size = 4183437, upload-time = "2025-12-02T11:56:51.818Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/9c/83/afa90e570198e2d1139dd567bec3c9cf44d8c54f63a649f16d711ede02f5/tantivy-0.25.1-cp313-cp313t-win_amd64.whl", hash = "sha256:09c987b840afcebac817836ac08407eff17272d8aa60ce6e291f89c81830221d", size = 3419409, upload-time = "2025-12-02T11:56:53.451Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/ff/44/9f1d67aa5030f7eebc966c863d1316a510a971dd8bb45651df4acdfae9ed/tantivy-0.25.1-cp314-cp314-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:7f5d29ae85dd0f23df8d15b3e7b341d4f9eb5a446bbb9640df48ac1f6d9e0c6c", size = 7623723, upload-time = "2025-12-02T11:56:55.066Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/db/30/6e085bd3ed9d12da3c91c185854abd70f9dfd35fb36a75ea98428d42c30b/tantivy-0.25.1-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:f2d2938fb69a74fc1bb36edfaf7f0d1596fa1264db0f377bda2195c58bcb6245", size = 3926243, upload-time = "2025-12-02T11:56:57.058Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/32/f5/a00d65433430f51718e5cc6938df571765d7c4e03aedec5aef4ab567aa9b/tantivy-0.25.1-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4f5ff124c4802558e627091e780b362ca944169736caba5a372eef39a79d0ae0", size = 4207186, upload-time = "2025-12-02T11:56:58.803Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/19/63/61bdb12fc95f2a7f77bd419a5149bfa9f28caa76cb569bf2b6b06e1d033e/tantivy-0.25.1-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:43b80ef62a340416139c93d19264e5f808da48e04f9305f1092b8ed22be0a5be", size = 4187312, upload-time = "2025-12-02T11:57:00.595Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/b7/de/e39c0b01d59019bf5c38face8b81defbc4a68cebf5e0c53bcb2cd715a449/tantivy-0.25.1-cp314-cp314-win_amd64.whl", hash = "sha256:286b654f40c70c1e6b64b9bc7031ed0bf5c440f5bffeaeeee21a0ee6cc39f0e2", size = 3436535, upload-time = "2025-12-02T11:57:02.267Z" },
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "threadpoolctl"
|
name = "threadpoolctl"
|
||||||
version = "3.6.0"
|
version = "3.6.0"
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "lancedb"
|
name = "lancedb"
|
||||||
version = "0.28.0-beta.10"
|
version = "0.28.0-beta.5"
|
||||||
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
|
||||||
@@ -111,12 +111,7 @@ default = []
|
|||||||
aws = ["lance/aws", "lance-io/aws", "lance-namespace-impls/dir-aws"]
|
aws = ["lance/aws", "lance-io/aws", "lance-namespace-impls/dir-aws"]
|
||||||
oss = ["lance/oss", "lance-io/oss", "lance-namespace-impls/dir-oss"]
|
oss = ["lance/oss", "lance-io/oss", "lance-namespace-impls/dir-oss"]
|
||||||
gcs = ["lance/gcp", "lance-io/gcp", "lance-namespace-impls/dir-gcp"]
|
gcs = ["lance/gcp", "lance-io/gcp", "lance-namespace-impls/dir-gcp"]
|
||||||
azure = [
|
azure = ["lance/azure", "lance-io/azure", "lance-namespace-impls/dir-azure"]
|
||||||
"lance/azure",
|
|
||||||
"lance-io/azure",
|
|
||||||
"lance-namespace-impls/dir-azure",
|
|
||||||
"lance-namespace-impls/credential-vendor-azure",
|
|
||||||
]
|
|
||||||
huggingface = [
|
huggingface = [
|
||||||
"lance/huggingface",
|
"lance/huggingface",
|
||||||
"lance-io/huggingface",
|
"lance-io/huggingface",
|
||||||
|
|||||||
@@ -590,15 +590,6 @@ pub struct ConnectRequest {
|
|||||||
/// storage options.
|
/// storage options.
|
||||||
pub namespace_client_properties: HashMap<String, String>,
|
pub namespace_client_properties: HashMap<String, String>,
|
||||||
|
|
||||||
/// Use directory namespace manifests as the source of truth for native
|
|
||||||
/// LanceDB table metadata.
|
|
||||||
///
|
|
||||||
/// When enabled for a local/native connection, LanceDB returns a
|
|
||||||
/// namespace-backed database directly. Directory listing fallback remains
|
|
||||||
/// enabled for migration, and directory-listing-to-manifest migration is
|
|
||||||
/// forced on.
|
|
||||||
pub manifest_enabled: bool,
|
|
||||||
|
|
||||||
/// The interval at which to check for updates from other processes.
|
/// The interval at which to check for updates from other processes.
|
||||||
///
|
///
|
||||||
/// If None, then consistency is not checked. For performance
|
/// If None, then consistency is not checked. For performance
|
||||||
@@ -639,7 +630,6 @@ impl ConnectBuilder {
|
|||||||
read_consistency_interval: None,
|
read_consistency_interval: None,
|
||||||
options: HashMap::new(),
|
options: HashMap::new(),
|
||||||
namespace_client_properties: HashMap::new(),
|
namespace_client_properties: HashMap::new(),
|
||||||
manifest_enabled: false,
|
|
||||||
session: None,
|
session: None,
|
||||||
},
|
},
|
||||||
embedding_registry: None,
|
embedding_registry: None,
|
||||||
@@ -801,17 +791,6 @@ impl ConnectBuilder {
|
|||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Enable or disable manifest-backed directory namespace mode for local
|
|
||||||
/// native connections.
|
|
||||||
///
|
|
||||||
/// When enabled, the connection uses the directory namespace database
|
|
||||||
/// directly for all table operations and forces
|
|
||||||
/// `dir_listing_to_manifest_migration_enabled=true`.
|
|
||||||
pub fn manifest_enabled(mut self, enabled: bool) -> Self {
|
|
||||||
self.request.manifest_enabled = enabled;
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
/// The interval at which to check for updates from other processes. This
|
/// The interval at which to check for updates from other processes. This
|
||||||
/// only affects LanceDB OSS.
|
/// only affects LanceDB OSS.
|
||||||
///
|
///
|
||||||
@@ -907,16 +886,6 @@ impl ConnectBuilder {
|
|||||||
pub async fn execute(self) -> Result<Connection> {
|
pub async fn execute(self) -> Result<Connection> {
|
||||||
if self.request.uri.starts_with("db") {
|
if self.request.uri.starts_with("db") {
|
||||||
self.execute_remote()
|
self.execute_remote()
|
||||||
} else if self.request.manifest_enabled {
|
|
||||||
let internal = Arc::new(
|
|
||||||
ListingDatabase::connect_manifest_enabled_namespace_database(&self.request).await?,
|
|
||||||
);
|
|
||||||
Ok(Connection {
|
|
||||||
internal,
|
|
||||||
embedding_registry: self
|
|
||||||
.embedding_registry
|
|
||||||
.unwrap_or_else(|| Arc::new(MemoryRegistry::new())),
|
|
||||||
})
|
|
||||||
} else {
|
} else {
|
||||||
let internal = Arc::new(ListingDatabase::connect_with_options(&self.request).await?);
|
let internal = Arc::new(ListingDatabase::connect_with_options(&self.request).await?);
|
||||||
Ok(Connection {
|
Ok(Connection {
|
||||||
@@ -946,7 +915,7 @@ use std::collections::HashSet;
|
|||||||
/// These operations will be executed on the namespace server instead of locally
|
/// These operations will be executed on the namespace server instead of locally
|
||||||
/// when enabled via [`ConnectNamespaceBuilder::pushdown_operations`].
|
/// when enabled via [`ConnectNamespaceBuilder::pushdown_operations`].
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||||
pub enum NamespaceClientPushdownOperation {
|
pub enum PushdownOperation {
|
||||||
/// Execute queries on the namespace server via `query_table()` instead of locally.
|
/// Execute queries on the namespace server via `query_table()` instead of locally.
|
||||||
QueryTable,
|
QueryTable,
|
||||||
/// Execute table creation on the namespace server via `create_table()`
|
/// Execute table creation on the namespace server via `create_table()`
|
||||||
@@ -962,7 +931,7 @@ pub struct ConnectNamespaceBuilder {
|
|||||||
read_consistency_interval: Option<std::time::Duration>,
|
read_consistency_interval: Option<std::time::Duration>,
|
||||||
embedding_registry: Option<Arc<dyn EmbeddingRegistry>>,
|
embedding_registry: Option<Arc<dyn EmbeddingRegistry>>,
|
||||||
session: Option<Arc<lance::session::Session>>,
|
session: Option<Arc<lance::session::Session>>,
|
||||||
pushdown_operations: HashSet<NamespaceClientPushdownOperation>,
|
pushdown_operations: HashSet<PushdownOperation>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ConnectNamespaceBuilder {
|
impl ConnectNamespaceBuilder {
|
||||||
@@ -1060,11 +1029,11 @@ impl ConnectNamespaceBuilder {
|
|||||||
/// and leveraging server-side compute resources.
|
/// and leveraging server-side compute resources.
|
||||||
///
|
///
|
||||||
/// Available operations:
|
/// Available operations:
|
||||||
/// - [`NamespaceClientPushdownOperation::QueryTable`]: Execute queries via `namespace.query_table()`
|
/// - [`PushdownOperation::QueryTable`]: Execute queries via `namespace.query_table()`
|
||||||
/// - [`NamespaceClientPushdownOperation::CreateTable`]: Execute table creation via `namespace.create_table()`
|
/// - [`PushdownOperation::CreateTable`]: Execute table creation via `namespace.create_table()`
|
||||||
///
|
///
|
||||||
/// By default, no operations are pushed down (all executed locally).
|
/// By default, no operations are pushed down (all executed locally).
|
||||||
pub fn pushdown_operation(mut self, operation: NamespaceClientPushdownOperation) -> Self {
|
pub fn pushdown_operation(mut self, operation: PushdownOperation) -> Self {
|
||||||
self.pushdown_operations.insert(operation);
|
self.pushdown_operations.insert(operation);
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
@@ -1074,7 +1043,7 @@ impl ConnectNamespaceBuilder {
|
|||||||
/// See [`Self::pushdown_operation`] for details.
|
/// See [`Self::pushdown_operation`] for details.
|
||||||
pub fn pushdown_operations(
|
pub fn pushdown_operations(
|
||||||
mut self,
|
mut self,
|
||||||
operations: impl IntoIterator<Item = NamespaceClientPushdownOperation>,
|
operations: impl IntoIterator<Item = PushdownOperation>,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
self.pushdown_operations.extend(operations);
|
self.pushdown_operations.extend(operations);
|
||||||
self
|
self
|
||||||
@@ -1163,9 +1132,6 @@ mod tests {
|
|||||||
use lance_testing::datagen::{BatchGenerator, IncrementingInt32};
|
use lance_testing::datagen::{BatchGenerator, IncrementingInt32};
|
||||||
use tempfile::tempdir;
|
use tempfile::tempdir;
|
||||||
|
|
||||||
use crate::database::listing::{ListingDatabaseOptions, OPT_NEW_TABLE_V2_MANIFEST_PATHS};
|
|
||||||
use crate::database::namespace::LanceNamespaceDatabase;
|
|
||||||
use crate::table::NativeTable;
|
|
||||||
use crate::test_utils::connection::new_test_connection;
|
use crate::test_utils::connection::new_test_connection;
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
@@ -1238,147 +1204,6 @@ mod tests {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
|
||||||
async fn test_connect_with_manifest_enabled_uses_directory_namespace() {
|
|
||||||
let tmp_dir = tempdir().unwrap();
|
|
||||||
let uri = tmp_dir.path().to_str().unwrap();
|
|
||||||
|
|
||||||
let db = connect(uri)
|
|
||||||
.manifest_enabled(true)
|
|
||||||
.storage_option("timeout", "30s")
|
|
||||||
.namespace_client_property("manifest_enabled", "false")
|
|
||||||
.namespace_client_property("dir_listing_to_manifest_migration_enabled", "false")
|
|
||||||
.execute()
|
|
||||||
.await
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
assert!(
|
|
||||||
db.database()
|
|
||||||
.as_any()
|
|
||||||
.downcast_ref::<LanceNamespaceDatabase>()
|
|
||||||
.is_some()
|
|
||||||
);
|
|
||||||
assert_eq!(db.uri(), uri);
|
|
||||||
|
|
||||||
let (ns_impl, properties) = db.namespace_client_config().await.unwrap();
|
|
||||||
assert_eq!(ns_impl, "dir");
|
|
||||||
assert_eq!(properties.get("root"), Some(&uri.to_string()));
|
|
||||||
assert_eq!(
|
|
||||||
properties.get("manifest_enabled"),
|
|
||||||
Some(&"true".to_string())
|
|
||||||
);
|
|
||||||
assert_eq!(
|
|
||||||
properties.get("dir_listing_to_manifest_migration_enabled"),
|
|
||||||
Some(&"true".to_string())
|
|
||||||
);
|
|
||||||
assert_eq!(properties.get("storage.timeout"), Some(&"30s".to_string()));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[tokio::test]
|
|
||||||
async fn test_manifest_enabled_rejects_commit_engine_uri() {
|
|
||||||
let Err(err) = connect("s3+ddb://bucket/db?ddbTableName=manifest")
|
|
||||||
.manifest_enabled(true)
|
|
||||||
.execute()
|
|
||||||
.await
|
|
||||||
else {
|
|
||||||
panic!("expected manifest-enabled s3+ddb connection to fail");
|
|
||||||
};
|
|
||||||
assert!(
|
|
||||||
matches!(err, Error::NotSupported { message } if message.contains("commit engine URI schemes"))
|
|
||||||
);
|
|
||||||
|
|
||||||
let Err(err) = connect("s3://bucket/db?engine=ddb&ddbTableName=manifest")
|
|
||||||
.manifest_enabled(true)
|
|
||||||
.execute()
|
|
||||||
.await
|
|
||||||
else {
|
|
||||||
panic!("expected manifest-enabled engine query connection to fail");
|
|
||||||
};
|
|
||||||
assert!(
|
|
||||||
matches!(err, Error::NotSupported { message } if message.contains("commit engine"))
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[tokio::test]
|
|
||||||
async fn test_manifest_enabled_connection_migrates_root_listing_table() {
|
|
||||||
let tmp_dir = tempdir().unwrap();
|
|
||||||
let uri = tmp_dir.path().to_str().unwrap();
|
|
||||||
let schema = Arc::new(Schema::new(vec![Field::new("x", DataType::Int32, false)]));
|
|
||||||
|
|
||||||
connect(uri)
|
|
||||||
.execute()
|
|
||||||
.await
|
|
||||||
.unwrap()
|
|
||||||
.create_empty_table("legacy", schema)
|
|
||||||
.execute()
|
|
||||||
.await
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
let db = connect(uri).manifest_enabled(true).execute().await.unwrap();
|
|
||||||
let tables = db.table_names().execute().await.unwrap();
|
|
||||||
assert_eq!(tables, vec!["legacy".to_string()]);
|
|
||||||
db.open_table("legacy").execute().await.unwrap();
|
|
||||||
}
|
|
||||||
|
|
||||||
#[tokio::test]
|
|
||||||
async fn test_manifest_enabled_preserves_new_table_options() {
|
|
||||||
let tmp_dir = tempdir().unwrap();
|
|
||||||
let uri = tmp_dir.path().to_str().unwrap();
|
|
||||||
let options = ListingDatabaseOptions::builder()
|
|
||||||
.enable_v2_manifest_paths(true)
|
|
||||||
.build();
|
|
||||||
let schema = Arc::new(Schema::new(vec![Field::new("x", DataType::Int32, false)]));
|
|
||||||
|
|
||||||
let table = connect(uri)
|
|
||||||
.manifest_enabled(true)
|
|
||||||
.database_options(&options)
|
|
||||||
.execute()
|
|
||||||
.await
|
|
||||||
.unwrap()
|
|
||||||
.create_empty_table("v1_manifest", schema)
|
|
||||||
.storage_option(OPT_NEW_TABLE_V2_MANIFEST_PATHS, "false")
|
|
||||||
.execute()
|
|
||||||
.await
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
let native_table = table
|
|
||||||
.base_table()
|
|
||||||
.as_any()
|
|
||||||
.downcast_ref::<NativeTable>()
|
|
||||||
.unwrap();
|
|
||||||
assert!(!native_table.uses_v2_manifest_paths().await.unwrap());
|
|
||||||
}
|
|
||||||
|
|
||||||
#[tokio::test]
|
|
||||||
async fn test_manifest_enabled_vend_input_storage_options() {
|
|
||||||
let tmp_dir = tempdir().unwrap();
|
|
||||||
let uri = tmp_dir.path().to_str().unwrap();
|
|
||||||
let schema = Arc::new(Schema::new(vec![Field::new("x", DataType::Int32, false)]));
|
|
||||||
|
|
||||||
let table = connect(uri)
|
|
||||||
.manifest_enabled(true)
|
|
||||||
.storage_option("test_storage_option", "test_value")
|
|
||||||
.namespace_client_property("vend_input_storage_options", "true")
|
|
||||||
.namespace_client_property(
|
|
||||||
"vend_input_storage_options_refresh_interval_millis",
|
|
||||||
"60000",
|
|
||||||
)
|
|
||||||
.execute()
|
|
||||||
.await
|
|
||||||
.unwrap()
|
|
||||||
.create_empty_table("vended", schema)
|
|
||||||
.execute()
|
|
||||||
.await
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
let storage_options = table.latest_storage_options().await.unwrap().unwrap();
|
|
||||||
assert_eq!(
|
|
||||||
storage_options.get("test_storage_option"),
|
|
||||||
Some(&"test_value".to_string())
|
|
||||||
);
|
|
||||||
assert!(storage_options.contains_key("expires_at_millis"));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn test_table_names() {
|
async fn test_table_names() {
|
||||||
let tc = new_test_connection().await.unwrap();
|
let tc = new_test_connection().await.unwrap();
|
||||||
|
|||||||
@@ -285,7 +285,7 @@ const MIRRORED_STORE: &str = "mirroredStore";
|
|||||||
|
|
||||||
/// A connection to LanceDB
|
/// A connection to LanceDB
|
||||||
impl ListingDatabase {
|
impl ListingDatabase {
|
||||||
pub(crate) fn build_namespace_client_properties(
|
fn build_namespace_client_properties(
|
||||||
uri: &str,
|
uri: &str,
|
||||||
storage_options: &HashMap<String, String>,
|
storage_options: &HashMap<String, String>,
|
||||||
namespace_client_properties: HashMap<String, String>,
|
namespace_client_properties: HashMap<String, String>,
|
||||||
@@ -298,24 +298,6 @@ impl ListingDatabase {
|
|||||||
properties
|
properties
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn build_manifest_enabled_namespace_client_properties(
|
|
||||||
uri: &str,
|
|
||||||
storage_options: &HashMap<String, String>,
|
|
||||||
namespace_client_properties: HashMap<String, String>,
|
|
||||||
) -> HashMap<String, String> {
|
|
||||||
let mut properties = Self::build_namespace_client_properties(
|
|
||||||
uri,
|
|
||||||
storage_options,
|
|
||||||
namespace_client_properties,
|
|
||||||
);
|
|
||||||
properties.insert("manifest_enabled".to_string(), "true".to_string());
|
|
||||||
properties.insert(
|
|
||||||
"dir_listing_to_manifest_migration_enabled".to_string(),
|
|
||||||
"true".to_string(),
|
|
||||||
);
|
|
||||||
properties
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn connect_namespace_database(
|
async fn connect_namespace_database(
|
||||||
uri: &str,
|
uri: &str,
|
||||||
storage_options: HashMap<String, String>,
|
storage_options: HashMap<String, String>,
|
||||||
@@ -341,119 +323,6 @@ impl ListingDatabase {
|
|||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn prepare_namespace_root(
|
|
||||||
uri: &str,
|
|
||||||
storage_options: &HashMap<String, String>,
|
|
||||||
session: Arc<lance::session::Session>,
|
|
||||||
) -> Result<String> {
|
|
||||||
match url::Url::parse(uri) {
|
|
||||||
Ok(url) if url.scheme().len() == 1 && cfg!(windows) => {
|
|
||||||
let (object_store, _) = ObjectStore::from_uri_and_params(
|
|
||||||
session.store_registry(),
|
|
||||||
uri,
|
|
||||||
&ObjectStoreParams::default(),
|
|
||||||
)
|
|
||||||
.await?;
|
|
||||||
if object_store.is_local() {
|
|
||||||
Self::try_create_dir(uri).context(CreateDirSnafu { path: uri })?;
|
|
||||||
}
|
|
||||||
Ok(uri.to_string())
|
|
||||||
}
|
|
||||||
Ok(mut url) => {
|
|
||||||
if url.scheme().contains('+') {
|
|
||||||
return Err(Error::NotSupported {
|
|
||||||
message: "commit engine URI schemes are not supported for manifest-enabled namespace connections".to_string(),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
for (key, value) in url.query_pairs() {
|
|
||||||
if key == ENGINE {
|
|
||||||
return Err(Error::NotSupported {
|
|
||||||
message: format!(
|
|
||||||
"commit engine '{}' is not supported for manifest-enabled namespace connections",
|
|
||||||
value
|
|
||||||
),
|
|
||||||
});
|
|
||||||
} else if key == MIRRORED_STORE {
|
|
||||||
return Err(Error::NotSupported {
|
|
||||||
message: "mirrored store is not supported for manifest-enabled namespace connections"
|
|
||||||
.to_string(),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
url.set_query(None);
|
|
||||||
let plain_uri = url.to_string();
|
|
||||||
|
|
||||||
let os_params = ObjectStoreParams {
|
|
||||||
storage_options_accessor: if storage_options.is_empty() {
|
|
||||||
None
|
|
||||||
} else {
|
|
||||||
Some(Arc::new(StorageOptionsAccessor::with_static_options(
|
|
||||||
storage_options.clone(),
|
|
||||||
)))
|
|
||||||
},
|
|
||||||
..Default::default()
|
|
||||||
};
|
|
||||||
let (object_store, _) = ObjectStore::from_uri_and_params(
|
|
||||||
session.store_registry(),
|
|
||||||
&plain_uri,
|
|
||||||
&os_params,
|
|
||||||
)
|
|
||||||
.await?;
|
|
||||||
if object_store.is_local() {
|
|
||||||
Self::try_create_dir(&plain_uri).context(CreateDirSnafu {
|
|
||||||
path: plain_uri.clone(),
|
|
||||||
})?;
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(plain_uri)
|
|
||||||
}
|
|
||||||
Err(_) => {
|
|
||||||
let (object_store, _) = ObjectStore::from_uri_and_params(
|
|
||||||
session.store_registry(),
|
|
||||||
uri,
|
|
||||||
&ObjectStoreParams::default(),
|
|
||||||
)
|
|
||||||
.await?;
|
|
||||||
if object_store.is_local() {
|
|
||||||
Self::try_create_dir(uri).context(CreateDirSnafu { path: uri })?;
|
|
||||||
}
|
|
||||||
Ok(uri.to_string())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) async fn connect_manifest_enabled_namespace_database(
|
|
||||||
request: &ConnectRequest,
|
|
||||||
) -> Result<LanceNamespaceDatabase> {
|
|
||||||
let options = ListingDatabaseOptions::parse_from_map(&request.options)?;
|
|
||||||
let session = request
|
|
||||||
.session
|
|
||||||
.clone()
|
|
||||||
.unwrap_or_else(|| Arc::new(lance::session::Session::default()));
|
|
||||||
let namespace_root =
|
|
||||||
Self::prepare_namespace_root(&request.uri, &options.storage_options, session.clone())
|
|
||||||
.await?;
|
|
||||||
let ns_properties = Self::build_manifest_enabled_namespace_client_properties(
|
|
||||||
&namespace_root,
|
|
||||||
&options.storage_options,
|
|
||||||
request.namespace_client_properties.clone(),
|
|
||||||
);
|
|
||||||
|
|
||||||
LanceNamespaceDatabase::connect_with_new_table_config(
|
|
||||||
"dir",
|
|
||||||
ns_properties,
|
|
||||||
options.storage_options,
|
|
||||||
request.read_consistency_interval,
|
|
||||||
Some(session),
|
|
||||||
HashSet::new(),
|
|
||||||
options.new_table_config,
|
|
||||||
)
|
|
||||||
.await
|
|
||||||
.map(|db| db.with_uri(request.uri.clone()))
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Connect to a listing database
|
/// Connect to a listing database
|
||||||
///
|
///
|
||||||
/// The URI should be a path to a directory where the tables are stored.
|
/// The URI should be a path to a directory where the tables are stored.
|
||||||
@@ -821,12 +690,15 @@ impl ListingDatabase {
|
|||||||
store_params.storage_options_accessor = Some(Arc::new(accessor));
|
store_params.storage_options_accessor = Some(Arc::new(accessor));
|
||||||
}
|
}
|
||||||
|
|
||||||
write_params.data_storage_version = storage_version_override
|
write_params.data_storage_version = self
|
||||||
.or(write_params.data_storage_version)
|
.new_table_config
|
||||||
.or(self.new_table_config.data_storage_version);
|
.data_storage_version
|
||||||
|
.or(storage_version_override);
|
||||||
|
|
||||||
if let Some(enable_v2_manifest_paths) =
|
if let Some(enable_v2_manifest_paths) = self
|
||||||
v2_manifest_override.or(self.new_table_config.enable_v2_manifest_paths)
|
.new_table_config
|
||||||
|
.enable_v2_manifest_paths
|
||||||
|
.or(v2_manifest_override)
|
||||||
{
|
{
|
||||||
write_params.enable_v2_manifest_paths = enable_v2_manifest_paths;
|
write_params.enable_v2_manifest_paths = enable_v2_manifest_paths;
|
||||||
}
|
}
|
||||||
@@ -1286,7 +1158,6 @@ mod tests {
|
|||||||
client_config: Default::default(),
|
client_config: Default::default(),
|
||||||
options: Default::default(),
|
options: Default::default(),
|
||||||
namespace_client_properties: Default::default(),
|
namespace_client_properties: Default::default(),
|
||||||
manifest_enabled: false,
|
|
||||||
read_consistency_interval: None,
|
read_consistency_interval: None,
|
||||||
session: None,
|
session: None,
|
||||||
};
|
};
|
||||||
@@ -1421,7 +1292,6 @@ mod tests {
|
|||||||
client_config: Default::default(),
|
client_config: Default::default(),
|
||||||
options: options.clone(),
|
options: options.clone(),
|
||||||
namespace_client_properties: Default::default(),
|
namespace_client_properties: Default::default(),
|
||||||
manifest_enabled: false,
|
|
||||||
read_consistency_interval: None,
|
read_consistency_interval: None,
|
||||||
session: None,
|
session: None,
|
||||||
};
|
};
|
||||||
@@ -1957,7 +1827,6 @@ mod tests {
|
|||||||
client_config: Default::default(),
|
client_config: Default::default(),
|
||||||
options,
|
options,
|
||||||
namespace_client_properties: Default::default(),
|
namespace_client_properties: Default::default(),
|
||||||
manifest_enabled: false,
|
|
||||||
read_consistency_interval: None,
|
read_consistency_interval: None,
|
||||||
session: None,
|
session: None,
|
||||||
};
|
};
|
||||||
@@ -2064,7 +1933,6 @@ mod tests {
|
|||||||
client_config: Default::default(),
|
client_config: Default::default(),
|
||||||
options,
|
options,
|
||||||
namespace_client_properties: Default::default(),
|
namespace_client_properties: Default::default(),
|
||||||
manifest_enabled: false,
|
|
||||||
read_consistency_interval: None,
|
read_consistency_interval: None,
|
||||||
session: None,
|
session: None,
|
||||||
};
|
};
|
||||||
@@ -2137,7 +2005,6 @@ mod tests {
|
|||||||
client_config: Default::default(),
|
client_config: Default::default(),
|
||||||
options,
|
options,
|
||||||
namespace_client_properties: Default::default(),
|
namespace_client_properties: Default::default(),
|
||||||
manifest_enabled: false,
|
|
||||||
read_consistency_interval: None,
|
read_consistency_interval: None,
|
||||||
session: None,
|
session: None,
|
||||||
};
|
};
|
||||||
@@ -2317,7 +2184,6 @@ mod tests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
#[cfg(not(windows))] // TODO: support Windows once directory namespace-backed listing DB tests are supported.
|
|
||||||
async fn test_listing_database_with_namespace_client_properties() {
|
async fn test_listing_database_with_namespace_client_properties() {
|
||||||
let tempdir = tempdir().unwrap();
|
let tempdir = tempdir().unwrap();
|
||||||
let uri = tempdir.path().to_str().unwrap();
|
let uri = tempdir.path().to_str().unwrap();
|
||||||
@@ -2335,7 +2201,6 @@ mod tests {
|
|||||||
client_config: Default::default(),
|
client_config: Default::default(),
|
||||||
options: Default::default(),
|
options: Default::default(),
|
||||||
namespace_client_properties,
|
namespace_client_properties,
|
||||||
manifest_enabled: false,
|
|
||||||
read_consistency_interval: None,
|
read_consistency_interval: None,
|
||||||
session: None,
|
session: None,
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -22,15 +22,10 @@ use lance_namespace_impls::ConnectBuilder;
|
|||||||
use lance_table::io::commit::CommitHandler;
|
use lance_table::io::commit::CommitHandler;
|
||||||
use lance_table::io::commit::external_manifest::ExternalManifestCommitHandler;
|
use lance_table::io::commit::external_manifest::ExternalManifestCommitHandler;
|
||||||
|
|
||||||
use crate::connection::NamespaceClientPushdownOperation;
|
use crate::connection::PushdownOperation;
|
||||||
use crate::database::ReadConsistency;
|
use crate::database::ReadConsistency;
|
||||||
use crate::database::listing::{
|
|
||||||
NewTableConfig, OPT_NEW_TABLE_ENABLE_STABLE_ROW_IDS, OPT_NEW_TABLE_STORAGE_VERSION,
|
|
||||||
OPT_NEW_TABLE_V2_MANIFEST_PATHS,
|
|
||||||
};
|
|
||||||
use crate::error::{Error, Result};
|
use crate::error::{Error, Result};
|
||||||
use crate::table::NativeTable;
|
use crate::table::NativeTable;
|
||||||
use lance::dataset::WriteMode;
|
|
||||||
|
|
||||||
use super::{
|
use super::{
|
||||||
BaseTable, CloneTableRequest, CreateTableMode, CreateTableRequest as DbCreateTableRequest,
|
BaseTable, CloneTableRequest, CreateTableMode, CreateTableRequest as DbCreateTableRequest,
|
||||||
@@ -49,71 +44,21 @@ pub struct LanceNamespaceDatabase {
|
|||||||
// database URI
|
// database URI
|
||||||
uri: String,
|
uri: String,
|
||||||
// Operations to push down to the namespace server
|
// Operations to push down to the namespace server
|
||||||
pushdown_operations: HashSet<NamespaceClientPushdownOperation>,
|
pushdown_operations: HashSet<PushdownOperation>,
|
||||||
// Namespace implementation type (e.g., "dir", "rest")
|
// Namespace implementation type (e.g., "dir", "rest")
|
||||||
ns_impl: String,
|
ns_impl: String,
|
||||||
// Namespace properties used to construct the namespace client
|
// Namespace properties used to construct the namespace client
|
||||||
ns_properties: HashMap<String, String>,
|
ns_properties: HashMap<String, String>,
|
||||||
// Options for tables created by this connection
|
|
||||||
new_table_config: NewTableConfig,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl LanceNamespaceDatabase {
|
impl LanceNamespaceDatabase {
|
||||||
pub fn from_namespace_client(
|
|
||||||
namespace_client: Arc<dyn LanceNamespace>,
|
|
||||||
namespace_client_impl: String,
|
|
||||||
namespace_client_properties: HashMap<String, String>,
|
|
||||||
storage_options: HashMap<String, String>,
|
|
||||||
read_consistency_interval: Option<std::time::Duration>,
|
|
||||||
session: Option<Arc<lance::session::Session>>,
|
|
||||||
namespace_client_pushdown_operations: HashSet<NamespaceClientPushdownOperation>,
|
|
||||||
) -> Self {
|
|
||||||
Self {
|
|
||||||
namespace: namespace_client,
|
|
||||||
storage_options,
|
|
||||||
read_consistency_interval,
|
|
||||||
session,
|
|
||||||
uri: format!("namespace://{}", namespace_client_impl),
|
|
||||||
pushdown_operations: namespace_client_pushdown_operations,
|
|
||||||
ns_impl: namespace_client_impl,
|
|
||||||
ns_properties: namespace_client_properties,
|
|
||||||
new_table_config: NewTableConfig::default(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn with_uri(mut self, uri: impl Into<String>) -> Self {
|
|
||||||
self.uri = uri.into();
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn connect(
|
pub async fn connect(
|
||||||
ns_impl: &str,
|
ns_impl: &str,
|
||||||
ns_properties: HashMap<String, String>,
|
ns_properties: HashMap<String, String>,
|
||||||
storage_options: HashMap<String, String>,
|
storage_options: HashMap<String, String>,
|
||||||
read_consistency_interval: Option<std::time::Duration>,
|
read_consistency_interval: Option<std::time::Duration>,
|
||||||
session: Option<Arc<lance::session::Session>>,
|
session: Option<Arc<lance::session::Session>>,
|
||||||
pushdown_operations: HashSet<NamespaceClientPushdownOperation>,
|
pushdown_operations: HashSet<PushdownOperation>,
|
||||||
) -> Result<Self> {
|
|
||||||
Self::connect_with_new_table_config(
|
|
||||||
ns_impl,
|
|
||||||
ns_properties,
|
|
||||||
storage_options,
|
|
||||||
read_consistency_interval,
|
|
||||||
session,
|
|
||||||
pushdown_operations,
|
|
||||||
NewTableConfig::default(),
|
|
||||||
)
|
|
||||||
.await
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) async fn connect_with_new_table_config(
|
|
||||||
ns_impl: &str,
|
|
||||||
ns_properties: HashMap<String, String>,
|
|
||||||
storage_options: HashMap<String, String>,
|
|
||||||
read_consistency_interval: Option<std::time::Duration>,
|
|
||||||
session: Option<Arc<lance::session::Session>>,
|
|
||||||
pushdown_operations: HashSet<NamespaceClientPushdownOperation>,
|
|
||||||
new_table_config: NewTableConfig,
|
|
||||||
) -> Result<Self> {
|
) -> Result<Self> {
|
||||||
let mut builder = ConnectBuilder::new(ns_impl);
|
let mut builder = ConnectBuilder::new(ns_impl);
|
||||||
for (key, value) in ns_properties.clone() {
|
for (key, value) in ns_properties.clone() {
|
||||||
@@ -135,79 +80,8 @@ impl LanceNamespaceDatabase {
|
|||||||
pushdown_operations,
|
pushdown_operations,
|
||||||
ns_impl: ns_impl.to_string(),
|
ns_impl: ns_impl.to_string(),
|
||||||
ns_properties,
|
ns_properties,
|
||||||
new_table_config,
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fn extract_storage_overrides(
|
|
||||||
&self,
|
|
||||||
request: &DbCreateTableRequest,
|
|
||||||
) -> Result<(
|
|
||||||
Option<lance_encoding::version::LanceFileVersion>,
|
|
||||||
Option<bool>,
|
|
||||||
Option<bool>,
|
|
||||||
)> {
|
|
||||||
let storage_options = request
|
|
||||||
.write_options
|
|
||||||
.lance_write_params
|
|
||||||
.as_ref()
|
|
||||||
.and_then(|p| p.store_params.as_ref())
|
|
||||||
.and_then(|sp| sp.storage_options());
|
|
||||||
|
|
||||||
let storage_version_override = storage_options
|
|
||||||
.and_then(|opts| opts.get(OPT_NEW_TABLE_STORAGE_VERSION))
|
|
||||||
.map(|s| s.parse::<lance_encoding::version::LanceFileVersion>())
|
|
||||||
.transpose()?;
|
|
||||||
|
|
||||||
let v2_manifest_override = storage_options
|
|
||||||
.and_then(|opts| opts.get(OPT_NEW_TABLE_V2_MANIFEST_PATHS))
|
|
||||||
.map(|s| s.parse::<bool>())
|
|
||||||
.transpose()
|
|
||||||
.map_err(|_| Error::InvalidInput {
|
|
||||||
message: "enable_v2_manifest_paths must be a boolean".to_string(),
|
|
||||||
})?;
|
|
||||||
|
|
||||||
let stable_row_ids_override = storage_options
|
|
||||||
.and_then(|opts| opts.get(OPT_NEW_TABLE_ENABLE_STABLE_ROW_IDS))
|
|
||||||
.map(|s| s.parse::<bool>())
|
|
||||||
.transpose()
|
|
||||||
.map_err(|_| Error::InvalidInput {
|
|
||||||
message: "enable_stable_row_ids must be a boolean".to_string(),
|
|
||||||
})?;
|
|
||||||
|
|
||||||
Ok((
|
|
||||||
storage_version_override,
|
|
||||||
v2_manifest_override,
|
|
||||||
stable_row_ids_override,
|
|
||||||
))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn apply_new_table_config(
|
|
||||||
&self,
|
|
||||||
params: &mut lance::dataset::WriteParams,
|
|
||||||
request: &DbCreateTableRequest,
|
|
||||||
) -> Result<()> {
|
|
||||||
let (storage_version_override, v2_manifest_override, stable_row_ids_override) =
|
|
||||||
self.extract_storage_overrides(request)?;
|
|
||||||
|
|
||||||
params.data_storage_version = storage_version_override
|
|
||||||
.or(params.data_storage_version)
|
|
||||||
.or(self.new_table_config.data_storage_version);
|
|
||||||
|
|
||||||
if let Some(enable_v2_manifest_paths) =
|
|
||||||
v2_manifest_override.or(self.new_table_config.enable_v2_manifest_paths)
|
|
||||||
{
|
|
||||||
params.enable_v2_manifest_paths = enable_v2_manifest_paths;
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(enable_stable_row_ids) =
|
|
||||||
stable_row_ids_override.or(self.new_table_config.enable_stable_row_ids)
|
|
||||||
{
|
|
||||||
params.enable_stable_row_ids = enable_stable_row_ids;
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl std::fmt::Debug for LanceNamespaceDatabase {
|
impl std::fmt::Debug for LanceNamespaceDatabase {
|
||||||
@@ -289,23 +163,37 @@ impl Database for LanceNamespaceDatabase {
|
|||||||
async fn create_table(&self, request: DbCreateTableRequest) -> Result<Arc<dyn BaseTable>> {
|
async fn create_table(&self, request: DbCreateTableRequest) -> Result<Arc<dyn BaseTable>> {
|
||||||
let mut table_id = request.namespace_path.clone();
|
let mut table_id = request.namespace_path.clone();
|
||||||
table_id.push(request.name.clone());
|
table_id.push(request.name.clone());
|
||||||
let mut existing_table = None;
|
let describe_request = DescribeTableRequest {
|
||||||
|
id: Some(table_id.clone()),
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
|
||||||
|
let describe_result = self.namespace.describe_table(describe_request).await;
|
||||||
|
|
||||||
match request.mode {
|
match request.mode {
|
||||||
CreateTableMode::Create => {}
|
CreateTableMode::Create => {
|
||||||
|
if describe_result.is_ok() {
|
||||||
|
return Err(Error::TableAlreadyExists {
|
||||||
|
name: request.name.clone(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
CreateTableMode::Overwrite => {
|
CreateTableMode::Overwrite => {
|
||||||
let describe_request = DescribeTableRequest {
|
if describe_result.is_ok() {
|
||||||
id: Some(table_id.clone()),
|
// Drop the existing table - must succeed
|
||||||
..Default::default()
|
let drop_request = DropTableRequest {
|
||||||
};
|
id: Some(table_id.clone()),
|
||||||
existing_table = self.namespace.describe_table(describe_request).await.ok();
|
..Default::default()
|
||||||
|
};
|
||||||
|
self.namespace
|
||||||
|
.drop_table(drop_request)
|
||||||
|
.await
|
||||||
|
.map_err(|e| Error::Runtime {
|
||||||
|
message: format!("Failed to drop existing table for overwrite: {}", e),
|
||||||
|
})?;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
CreateTableMode::ExistOk(_) => {
|
CreateTableMode::ExistOk(_) => {
|
||||||
let describe_request = DescribeTableRequest {
|
|
||||||
id: Some(table_id.clone()),
|
|
||||||
..Default::default()
|
|
||||||
};
|
|
||||||
let describe_result = self.namespace.describe_table(describe_request).await;
|
|
||||||
if describe_result.is_ok() {
|
if describe_result.is_ok() {
|
||||||
let native_table = NativeTable::open_from_namespace(
|
let native_table = NativeTable::open_from_namespace(
|
||||||
self.namespace.clone(),
|
self.namespace.clone(),
|
||||||
@@ -333,86 +221,20 @@ impl Database for LanceNamespaceDatabase {
|
|||||||
};
|
};
|
||||||
|
|
||||||
let (location, initial_storage_options, managed_versioning) = {
|
let (location, initial_storage_options, managed_versioning) = {
|
||||||
if let Some(response) = existing_table {
|
let response = self.namespace.declare_table(declare_request).await?;
|
||||||
let loc = response.location.ok_or_else(|| Error::Runtime {
|
let loc = response.location.ok_or_else(|| Error::Runtime {
|
||||||
message: "Table location is missing from describe_table response".to_string(),
|
message: "Table location is missing from declare_table response".to_string(),
|
||||||
})?;
|
})?;
|
||||||
let opts = response
|
// Use storage options from response, fall back to self.storage_options
|
||||||
.storage_options
|
let opts = response
|
||||||
.or_else(|| Some(self.storage_options.clone()))
|
.storage_options
|
||||||
.filter(|o| !o.is_empty());
|
.or_else(|| Some(self.storage_options.clone()))
|
||||||
(loc, opts, response.managed_versioning)
|
.filter(|o| !o.is_empty());
|
||||||
} else {
|
(loc, opts, response.managed_versioning)
|
||||||
match self.namespace.declare_table(declare_request).await {
|
|
||||||
Ok(response) => {
|
|
||||||
let loc = response.location.ok_or_else(|| Error::Runtime {
|
|
||||||
message: "Table location is missing from declare_table response"
|
|
||||||
.to_string(),
|
|
||||||
})?;
|
|
||||||
let opts = response
|
|
||||||
.storage_options
|
|
||||||
.or_else(|| Some(self.storage_options.clone()))
|
|
||||||
.filter(|o: &HashMap<String, String>| !o.is_empty());
|
|
||||||
(loc, opts, response.managed_versioning)
|
|
||||||
}
|
|
||||||
Err(e)
|
|
||||||
if matches!(request.mode, CreateTableMode::Create) && {
|
|
||||||
let err_str = e.to_string();
|
|
||||||
err_str.contains("already exists")
|
|
||||||
|| err_str.contains("TableAlreadyExists")
|
|
||||||
|| err_str.contains("table already exists")
|
|
||||||
} =>
|
|
||||||
{
|
|
||||||
let response = self
|
|
||||||
.namespace
|
|
||||||
.describe_table(DescribeTableRequest {
|
|
||||||
id: Some(table_id.clone()),
|
|
||||||
..Default::default()
|
|
||||||
})
|
|
||||||
.await
|
|
||||||
.map_err(|describe_err| Error::Runtime {
|
|
||||||
message: format!(
|
|
||||||
"Failed to describe existing declared table after declare conflict: {}",
|
|
||||||
describe_err
|
|
||||||
),
|
|
||||||
})?;
|
|
||||||
|
|
||||||
if response.version.is_some() && response.schema.is_some() {
|
|
||||||
return Err(Error::TableAlreadyExists {
|
|
||||||
name: request.name.clone(),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
let loc = response.location.ok_or_else(|| Error::Runtime {
|
|
||||||
message: "Table location is missing from describe_table response"
|
|
||||||
.to_string(),
|
|
||||||
})?;
|
|
||||||
let opts = response
|
|
||||||
.storage_options
|
|
||||||
.or_else(|| Some(self.storage_options.clone()))
|
|
||||||
.filter(|o: &HashMap<String, String>| !o.is_empty());
|
|
||||||
(loc, opts, response.managed_versioning)
|
|
||||||
}
|
|
||||||
Err(e) => {
|
|
||||||
return Err(Error::Runtime {
|
|
||||||
message: format!("Failed to declare table: {}", e),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// Build write params with storage options and commit handler
|
// Build write params with storage options and commit handler
|
||||||
let mut params = request
|
let mut params = request.write_options.lance_write_params.unwrap_or_default();
|
||||||
.write_options
|
|
||||||
.lance_write_params
|
|
||||||
.clone()
|
|
||||||
.unwrap_or_default();
|
|
||||||
self.apply_new_table_config(&mut params, &request)?;
|
|
||||||
|
|
||||||
if matches!(request.mode, CreateTableMode::Overwrite) {
|
|
||||||
params.mode = WriteMode::Overwrite;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set up storage options if provided
|
// Set up storage options if provided
|
||||||
if let Some(storage_opts) = initial_storage_options {
|
if let Some(storage_opts) = initial_storage_options {
|
||||||
@@ -870,58 +692,6 @@ mod tests {
|
|||||||
assert_eq!(id_col.value(2), 30);
|
assert_eq!(id_col.value(2), 30);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
|
||||||
async fn test_namespace_create_table_after_declare_conflict() {
|
|
||||||
let tmp_dir = tempdir().unwrap();
|
|
||||||
let root_path = tmp_dir.path().to_str().unwrap().to_string();
|
|
||||||
|
|
||||||
let mut properties = HashMap::new();
|
|
||||||
properties.insert("root".to_string(), root_path);
|
|
||||||
|
|
||||||
let conn = connect_namespace("dir", properties)
|
|
||||||
.execute()
|
|
||||||
.await
|
|
||||||
.expect("Failed to connect to namespace");
|
|
||||||
|
|
||||||
conn.create_namespace(CreateNamespaceRequest {
|
|
||||||
id: Some(vec!["test_ns".into()]),
|
|
||||||
..Default::default()
|
|
||||||
})
|
|
||||||
.await
|
|
||||||
.expect("Failed to create namespace");
|
|
||||||
|
|
||||||
let namespace_client = conn.namespace_client().await.unwrap();
|
|
||||||
namespace_client
|
|
||||||
.declare_table(DeclareTableRequest {
|
|
||||||
id: Some(vec!["test_ns".into(), "declared_test".into()]),
|
|
||||||
..Default::default()
|
|
||||||
})
|
|
||||||
.await
|
|
||||||
.expect("Failed to declare table");
|
|
||||||
|
|
||||||
let test_data = create_test_data();
|
|
||||||
let table = conn
|
|
||||||
.create_table("declared_test", test_data)
|
|
||||||
.namespace(vec!["test_ns".into()])
|
|
||||||
.execute()
|
|
||||||
.await
|
|
||||||
.expect("Failed to create table after declare conflict");
|
|
||||||
|
|
||||||
let results = table
|
|
||||||
.query()
|
|
||||||
.execute()
|
|
||||||
.await
|
|
||||||
.expect("Failed to query table")
|
|
||||||
.try_collect::<Vec<_>>()
|
|
||||||
.await
|
|
||||||
.expect("Failed to collect results");
|
|
||||||
|
|
||||||
assert_eq!(results.len(), 1);
|
|
||||||
assert_eq!(results[0].num_rows(), 5);
|
|
||||||
assert_eq!(table.namespace(), &["test_ns"]);
|
|
||||||
assert_eq!(table.id(), "test_ns$declared_test");
|
|
||||||
}
|
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn test_namespace_create_table_exist_ok_mode() {
|
async fn test_namespace_create_table_exist_ok_mode() {
|
||||||
// Setup: Create a temporary directory for the namespace
|
// Setup: Create a temporary directory for the namespace
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ use crate::remote::retry::{ResolvedRetryConfig, RetryCounter};
|
|||||||
const REQUEST_ID_HEADER: HeaderName = HeaderName::from_static("x-request-id");
|
const REQUEST_ID_HEADER: HeaderName = HeaderName::from_static("x-request-id");
|
||||||
|
|
||||||
/// Configuration for TLS/mTLS settings.
|
/// Configuration for TLS/mTLS settings.
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug, Default)]
|
||||||
pub struct TlsConfig {
|
pub struct TlsConfig {
|
||||||
/// Path to the client certificate file (PEM format)
|
/// Path to the client certificate file (PEM format)
|
||||||
pub cert_file: Option<String>,
|
pub cert_file: Option<String>,
|
||||||
@@ -24,22 +24,10 @@ pub struct TlsConfig {
|
|||||||
pub key_file: Option<String>,
|
pub key_file: Option<String>,
|
||||||
/// Path to the CA certificate file for server verification (PEM format)
|
/// Path to the CA certificate file for server verification (PEM format)
|
||||||
pub ssl_ca_cert: Option<String>,
|
pub ssl_ca_cert: Option<String>,
|
||||||
/// Whether to verify the hostname in the server's certificate.
|
/// Whether to verify the hostname in the server's certificate
|
||||||
/// Defaults to `true`.
|
|
||||||
pub assert_hostname: bool,
|
pub assert_hostname: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for TlsConfig {
|
|
||||||
fn default() -> Self {
|
|
||||||
Self {
|
|
||||||
cert_file: None,
|
|
||||||
key_file: None,
|
|
||||||
ssl_ca_cert: None,
|
|
||||||
assert_hostname: true,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Trait for providing custom headers for each request
|
/// Trait for providing custom headers for each request
|
||||||
#[async_trait::async_trait]
|
#[async_trait::async_trait]
|
||||||
pub trait HeaderProvider: Send + Sync + std::fmt::Debug {
|
pub trait HeaderProvider: Send + Sync + std::fmt::Debug {
|
||||||
@@ -938,7 +926,7 @@ mod tests {
|
|||||||
assert!(config.cert_file.is_none());
|
assert!(config.cert_file.is_none());
|
||||||
assert!(config.key_file.is_none());
|
assert!(config.key_file.is_none());
|
||||||
assert!(config.ssl_ca_cert.is_none());
|
assert!(config.ssl_ca_cert.is_none());
|
||||||
assert!(config.assert_hostname);
|
assert!(!config.assert_hostname);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|||||||
@@ -43,7 +43,7 @@ pub struct RemoteInsertExec<S: HttpSend = Sender> {
|
|||||||
client: RestfulLanceDbClient<S>,
|
client: RestfulLanceDbClient<S>,
|
||||||
input: Arc<dyn ExecutionPlan>,
|
input: Arc<dyn ExecutionPlan>,
|
||||||
overwrite: bool,
|
overwrite: bool,
|
||||||
properties: Arc<PlanProperties>,
|
properties: PlanProperties,
|
||||||
add_result: Arc<Mutex<Option<AddResult>>>,
|
add_result: Arc<Mutex<Option<AddResult>>>,
|
||||||
metrics: ExecutionPlanMetricsSet,
|
metrics: ExecutionPlanMetricsSet,
|
||||||
upload_id: Option<String>,
|
upload_id: Option<String>,
|
||||||
@@ -118,7 +118,7 @@ impl<S: HttpSend + 'static> RemoteInsertExec<S> {
|
|||||||
client,
|
client,
|
||||||
input,
|
input,
|
||||||
overwrite,
|
overwrite,
|
||||||
properties: Arc::new(properties),
|
properties,
|
||||||
add_result: Arc::new(Mutex::new(None)),
|
add_result: Arc::new(Mutex::new(None)),
|
||||||
metrics: ExecutionPlanMetricsSet::new(),
|
metrics: ExecutionPlanMetricsSet::new(),
|
||||||
upload_id,
|
upload_id,
|
||||||
@@ -232,7 +232,7 @@ impl<S: HttpSend + 'static> ExecutionPlan for RemoteInsertExec<S> {
|
|||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
fn properties(&self) -> &Arc<PlanProperties> {
|
fn properties(&self) -> &PlanProperties {
|
||||||
&self.properties
|
&self.properties
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -47,7 +47,7 @@ use std::format;
|
|||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
use crate::connection::NamespaceClientPushdownOperation;
|
use crate::connection::PushdownOperation;
|
||||||
|
|
||||||
use crate::data::scannable::{PeekedScannable, Scannable, estimate_write_partitions};
|
use crate::data::scannable::{PeekedScannable, Scannable, estimate_write_partitions};
|
||||||
use crate::database::Database;
|
use crate::database::Database;
|
||||||
@@ -1272,7 +1272,7 @@ pub struct NativeTable {
|
|||||||
pub(crate) namespace_client: Option<Arc<dyn LanceNamespace>>,
|
pub(crate) namespace_client: Option<Arc<dyn LanceNamespace>>,
|
||||||
// Operations to push down to the namespace server.
|
// Operations to push down to the namespace server.
|
||||||
// pub(crate) so query.rs can access the field for server-side query execution.
|
// pub(crate) so query.rs can access the field for server-side query execution.
|
||||||
pub(crate) pushdown_operations: HashSet<NamespaceClientPushdownOperation>,
|
pub(crate) pushdown_operations: HashSet<PushdownOperation>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl std::fmt::Debug for NativeTable {
|
impl std::fmt::Debug for NativeTable {
|
||||||
@@ -1359,7 +1359,7 @@ impl NativeTable {
|
|||||||
params: Option<ReadParams>,
|
params: Option<ReadParams>,
|
||||||
read_consistency_interval: Option<std::time::Duration>,
|
read_consistency_interval: Option<std::time::Duration>,
|
||||||
namespace_client: Option<Arc<dyn LanceNamespace>>,
|
namespace_client: Option<Arc<dyn LanceNamespace>>,
|
||||||
pushdown_operations: HashSet<NamespaceClientPushdownOperation>,
|
pushdown_operations: HashSet<PushdownOperation>,
|
||||||
managed_versioning: Option<bool>,
|
managed_versioning: Option<bool>,
|
||||||
) -> Result<Self> {
|
) -> Result<Self> {
|
||||||
let params = params.unwrap_or_default();
|
let params = params.unwrap_or_default();
|
||||||
@@ -1470,7 +1470,7 @@ impl NativeTable {
|
|||||||
write_store_wrapper: Option<Arc<dyn WrappingObjectStore>>,
|
write_store_wrapper: Option<Arc<dyn WrappingObjectStore>>,
|
||||||
params: Option<ReadParams>,
|
params: Option<ReadParams>,
|
||||||
read_consistency_interval: Option<std::time::Duration>,
|
read_consistency_interval: Option<std::time::Duration>,
|
||||||
pushdown_operations: HashSet<NamespaceClientPushdownOperation>,
|
pushdown_operations: HashSet<PushdownOperation>,
|
||||||
session: Option<Arc<lance::session::Session>>,
|
session: Option<Arc<lance::session::Session>>,
|
||||||
) -> Result<Self> {
|
) -> Result<Self> {
|
||||||
let mut params = params.unwrap_or_default();
|
let mut params = params.unwrap_or_default();
|
||||||
@@ -1518,7 +1518,7 @@ impl NativeTable {
|
|||||||
let id = Self::build_id(&namespace, name);
|
let id = Self::build_id(&namespace, name);
|
||||||
|
|
||||||
let stored_namespace_client =
|
let stored_namespace_client =
|
||||||
if pushdown_operations.contains(&NamespaceClientPushdownOperation::QueryTable) {
|
if pushdown_operations.contains(&PushdownOperation::QueryTable) {
|
||||||
Some(namespace_client)
|
Some(namespace_client)
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
@@ -1588,7 +1588,7 @@ impl NativeTable {
|
|||||||
params: Option<WriteParams>,
|
params: Option<WriteParams>,
|
||||||
read_consistency_interval: Option<std::time::Duration>,
|
read_consistency_interval: Option<std::time::Duration>,
|
||||||
namespace_client: Option<Arc<dyn LanceNamespace>>,
|
namespace_client: Option<Arc<dyn LanceNamespace>>,
|
||||||
pushdown_operations: HashSet<NamespaceClientPushdownOperation>,
|
pushdown_operations: HashSet<PushdownOperation>,
|
||||||
) -> Result<Self> {
|
) -> Result<Self> {
|
||||||
// Default params uses format v1.
|
// Default params uses format v1.
|
||||||
let params = params.unwrap_or(WriteParams {
|
let params = params.unwrap_or(WriteParams {
|
||||||
@@ -1635,7 +1635,7 @@ impl NativeTable {
|
|||||||
params: Option<WriteParams>,
|
params: Option<WriteParams>,
|
||||||
read_consistency_interval: Option<std::time::Duration>,
|
read_consistency_interval: Option<std::time::Duration>,
|
||||||
namespace_client: Option<Arc<dyn LanceNamespace>>,
|
namespace_client: Option<Arc<dyn LanceNamespace>>,
|
||||||
pushdown_operations: HashSet<NamespaceClientPushdownOperation>,
|
pushdown_operations: HashSet<PushdownOperation>,
|
||||||
) -> Result<Self> {
|
) -> Result<Self> {
|
||||||
let data: Box<dyn Scannable> = Box::new(RecordBatch::new_empty(schema));
|
let data: Box<dyn Scannable> = Box::new(RecordBatch::new_empty(schema));
|
||||||
Self::create(
|
Self::create(
|
||||||
@@ -1685,7 +1685,7 @@ impl NativeTable {
|
|||||||
write_store_wrapper: Option<Arc<dyn WrappingObjectStore>>,
|
write_store_wrapper: Option<Arc<dyn WrappingObjectStore>>,
|
||||||
params: Option<WriteParams>,
|
params: Option<WriteParams>,
|
||||||
read_consistency_interval: Option<std::time::Duration>,
|
read_consistency_interval: Option<std::time::Duration>,
|
||||||
pushdown_operations: HashSet<NamespaceClientPushdownOperation>,
|
pushdown_operations: HashSet<PushdownOperation>,
|
||||||
session: Option<Arc<lance::session::Session>>,
|
session: Option<Arc<lance::session::Session>>,
|
||||||
) -> Result<Self> {
|
) -> Result<Self> {
|
||||||
// Build table_id from namespace + name for the storage options provider
|
// Build table_id from namespace + name for the storage options provider
|
||||||
@@ -1738,7 +1738,7 @@ impl NativeTable {
|
|||||||
let id = Self::build_id(&namespace, name);
|
let id = Self::build_id(&namespace, name);
|
||||||
|
|
||||||
let stored_namespace_client =
|
let stored_namespace_client =
|
||||||
if pushdown_operations.contains(&NamespaceClientPushdownOperation::QueryTable) {
|
if pushdown_operations.contains(&PushdownOperation::QueryTable) {
|
||||||
Some(namespace_client)
|
Some(namespace_client)
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
|
|||||||
@@ -39,26 +39,21 @@ use lance_index::scalar::FullTextSearchQuery;
|
|||||||
struct MetadataEraserExec {
|
struct MetadataEraserExec {
|
||||||
input: Arc<dyn ExecutionPlan>,
|
input: Arc<dyn ExecutionPlan>,
|
||||||
schema: Arc<ArrowSchema>,
|
schema: Arc<ArrowSchema>,
|
||||||
properties: Arc<PlanProperties>,
|
properties: PlanProperties,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl MetadataEraserExec {
|
impl MetadataEraserExec {
|
||||||
fn compute_properties_from_input(
|
fn compute_properties_from_input(
|
||||||
input: &Arc<dyn ExecutionPlan>,
|
input: &Arc<dyn ExecutionPlan>,
|
||||||
schema: &Arc<ArrowSchema>,
|
schema: &Arc<ArrowSchema>,
|
||||||
) -> Arc<PlanProperties> {
|
) -> PlanProperties {
|
||||||
let input_properties = input.properties();
|
let input_properties = input.properties();
|
||||||
let eq_properties = input_properties
|
let eq_properties = input_properties
|
||||||
.eq_properties
|
.eq_properties
|
||||||
.clone()
|
.clone()
|
||||||
.with_new_schema(schema.clone())
|
.with_new_schema(schema.clone())
|
||||||
.unwrap();
|
.unwrap();
|
||||||
Arc::new(
|
input_properties.clone().with_eq_properties(eq_properties)
|
||||||
input_properties
|
|
||||||
.as_ref()
|
|
||||||
.clone()
|
|
||||||
.with_eq_properties(eq_properties),
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn new(input: Arc<dyn ExecutionPlan>) -> Self {
|
fn new(input: Arc<dyn ExecutionPlan>) -> Self {
|
||||||
@@ -92,7 +87,7 @@ impl ExecutionPlan for MetadataEraserExec {
|
|||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
fn properties(&self) -> &Arc<PlanProperties> {
|
fn properties(&self) -> &PlanProperties {
|
||||||
&self.properties
|
&self.properties
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -81,7 +81,7 @@ pub struct InsertExec {
|
|||||||
dataset: Arc<Dataset>,
|
dataset: Arc<Dataset>,
|
||||||
input: Arc<dyn ExecutionPlan>,
|
input: Arc<dyn ExecutionPlan>,
|
||||||
write_params: WriteParams,
|
write_params: WriteParams,
|
||||||
properties: Arc<PlanProperties>,
|
properties: PlanProperties,
|
||||||
partial_transactions: Arc<Mutex<Vec<Transaction>>>,
|
partial_transactions: Arc<Mutex<Vec<Transaction>>>,
|
||||||
metrics: ExecutionPlanMetricsSet,
|
metrics: ExecutionPlanMetricsSet,
|
||||||
}
|
}
|
||||||
@@ -107,7 +107,7 @@ impl InsertExec {
|
|||||||
dataset,
|
dataset,
|
||||||
input,
|
input,
|
||||||
write_params,
|
write_params,
|
||||||
properties: Arc::new(properties),
|
properties,
|
||||||
partial_transactions: Arc::new(Mutex::new(Vec::with_capacity(num_partitions))),
|
partial_transactions: Arc::new(Mutex::new(Vec::with_capacity(num_partitions))),
|
||||||
metrics: ExecutionPlanMetricsSet::new(),
|
metrics: ExecutionPlanMetricsSet::new(),
|
||||||
}
|
}
|
||||||
@@ -136,7 +136,7 @@ impl ExecutionPlan for InsertExec {
|
|||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
fn properties(&self) -> &Arc<PlanProperties> {
|
fn properties(&self) -> &PlanProperties {
|
||||||
&self.properties
|
&self.properties
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ pub(crate) struct ScannableExec {
|
|||||||
// We don't require Scannable to be Sync, so we wrap it in a Mutex to allow safe concurrent access.
|
// We don't require Scannable to be Sync, so we wrap it in a Mutex to allow safe concurrent access.
|
||||||
source: Mutex<Box<dyn Scannable>>,
|
source: Mutex<Box<dyn Scannable>>,
|
||||||
num_rows: Option<usize>,
|
num_rows: Option<usize>,
|
||||||
properties: Arc<PlanProperties>,
|
properties: PlanProperties,
|
||||||
tracker: Option<Arc<WriteProgressTracker>>,
|
tracker: Option<Arc<WriteProgressTracker>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -49,7 +49,7 @@ impl ScannableExec {
|
|||||||
Self {
|
Self {
|
||||||
source,
|
source,
|
||||||
num_rows,
|
num_rows,
|
||||||
properties: Arc::new(properties),
|
properties,
|
||||||
tracker,
|
tracker,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -70,7 +70,7 @@ impl ExecutionPlan for ScannableExec {
|
|||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
fn properties(&self) -> &Arc<PlanProperties> {
|
fn properties(&self) -> &PlanProperties {
|
||||||
&self.properties
|
&self.properties
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -285,10 +285,7 @@ mod tests {
|
|||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
use crate::{
|
use crate::{connect, io::object_store::io_tracking::IoStatsHolder, table::WriteOptions};
|
||||||
connect, io::object_store::io_tracking::IoStatsHolder, table::WriteOptions,
|
|
||||||
utils::background_cache::clock,
|
|
||||||
};
|
|
||||||
|
|
||||||
async fn create_test_dataset(uri: &str) -> Dataset {
|
async fn create_test_dataset(uri: &str) -> Dataset {
|
||||||
let schema = Arc::new(Schema::new(vec![Field::new("id", DataType::Int32, false)]));
|
let schema = Arc::new(Schema::new(vec![Field::new("id", DataType::Int32, false)]));
|
||||||
@@ -466,10 +463,6 @@ mod tests {
|
|||||||
let uri = dir.path().to_str().unwrap();
|
let uri = dir.path().to_str().unwrap();
|
||||||
let ds = create_test_dataset(uri).await;
|
let ds = create_test_dataset(uri).await;
|
||||||
|
|
||||||
// Other tests use a thread-local mock clock. Simulate leaked state from a
|
|
||||||
// previous test to ensure this wrapper starts from real time.
|
|
||||||
clock::advance_by(Duration::from_secs(60));
|
|
||||||
|
|
||||||
let wrapper = DatasetConsistencyWrapper::new_latest(ds, Some(Duration::from_millis(200)));
|
let wrapper = DatasetConsistencyWrapper::new_latest(ds, Some(Duration::from_millis(200)));
|
||||||
|
|
||||||
// Populate the cache
|
// Populate the cache
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
use super::NativeTable;
|
use super::NativeTable;
|
||||||
use crate::connection::NamespaceClientPushdownOperation;
|
use crate::connection::PushdownOperation;
|
||||||
use crate::error::{Error, Result};
|
use crate::error::{Error, Result};
|
||||||
use crate::expr::expr_to_sql_string;
|
use crate::expr::expr_to_sql_string;
|
||||||
use crate::query::{
|
use crate::query::{
|
||||||
@@ -44,7 +44,7 @@ pub async fn execute_query(
|
|||||||
// If QueryTable pushdown is enabled and namespace client is configured, use server-side query execution
|
// If QueryTable pushdown is enabled and namespace client is configured, use server-side query execution
|
||||||
if table
|
if table
|
||||||
.pushdown_operations
|
.pushdown_operations
|
||||||
.contains(&NamespaceClientPushdownOperation::QueryTable)
|
.contains(&PushdownOperation::QueryTable)
|
||||||
&& let Some(ref namespace_client) = table.namespace_client
|
&& let Some(ref namespace_client) = table.namespace_client
|
||||||
{
|
{
|
||||||
return execute_namespace_query(table, namespace_client.clone(), query, options).await;
|
return execute_namespace_query(table, namespace_client.clone(), query, options).await;
|
||||||
|
|||||||
@@ -107,14 +107,6 @@ where
|
|||||||
refresh_window < ttl,
|
refresh_window < ttl,
|
||||||
"refresh_window ({refresh_window:?}) must be less than ttl ({ttl:?})"
|
"refresh_window ({refresh_window:?}) must be less than ttl ({ttl:?})"
|
||||||
);
|
);
|
||||||
#[cfg(test)]
|
|
||||||
{
|
|
||||||
// Tests may advance the thread-local mock clock and leave it behind for
|
|
||||||
// the next test that happens to run on the same worker thread. Each new
|
|
||||||
// cache should start from a clean clock state instead of inheriting
|
|
||||||
// unrelated mock time from a previous test.
|
|
||||||
clock::clear_mock();
|
|
||||||
}
|
|
||||||
Self {
|
Self {
|
||||||
inner: Arc::new(Mutex::new(CacheInner {
|
inner: Arc::new(Mutex::new(CacheInner {
|
||||||
state: State::Empty,
|
state: State::Empty,
|
||||||
|
|||||||
Reference in New Issue
Block a user