mirror of
https://github.com/lancedb/lancedb.git
synced 2025-12-23 13:29:57 +00:00
Compare commits
102 Commits
docs/quick
...
v0.21.0
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6b5130cccb | ||
|
|
9c8314b4fd | ||
|
|
c625b6f2b2 | ||
|
|
bec8fe6547 | ||
|
|
dc1150c011 | ||
|
|
afaefc6264 | ||
|
|
cb70ff8cee | ||
|
|
cbb5a841b1 | ||
|
|
c72f6770fd | ||
|
|
e5a80a5e86 | ||
|
|
8d0a7fad1f | ||
|
|
b80d4d0134 | ||
|
|
9645fe52c2 | ||
|
|
b77314168d | ||
|
|
e08d45e090 | ||
|
|
2e3ddb8382 | ||
|
|
627ca4c810 | ||
|
|
f8dae4ffe9 | ||
|
|
9eb6119468 | ||
|
|
59b57e30ed | ||
|
|
fec8d58f06 | ||
|
|
84ded9d678 | ||
|
|
65696d9713 | ||
|
|
e2f2ea32e4 | ||
|
|
d5f2eca754 | ||
|
|
7fa455a8a5 | ||
|
|
8f42b5874e | ||
|
|
274f19f560 | ||
|
|
fbcbc75b5b | ||
|
|
008f389bd0 | ||
|
|
91af6518d9 | ||
|
|
af6819762c | ||
|
|
7acece493d | ||
|
|
20e017fedc | ||
|
|
74e578b3c8 | ||
|
|
d92d9eb3d2 | ||
|
|
b6cdce7bc9 | ||
|
|
316b406265 | ||
|
|
8825c7c1dd | ||
|
|
81c85ff702 | ||
|
|
570f2154d5 | ||
|
|
0525c055fc | ||
|
|
38d11291da | ||
|
|
258e682574 | ||
|
|
d7afa600b8 | ||
|
|
5c7303ab2e | ||
|
|
5895ef4039 | ||
|
|
0528cd858a | ||
|
|
6582f43422 | ||
|
|
5c7f63388d | ||
|
|
d0bc671cac | ||
|
|
d37e17593d | ||
|
|
cb726d370e | ||
|
|
23ee132546 | ||
|
|
7fa090d330 | ||
|
|
07bc1c5397 | ||
|
|
d7a9dbb9fc | ||
|
|
00487afc7d | ||
|
|
1902d65aad | ||
|
|
c4fbb65b8e | ||
|
|
875ed7ae6f | ||
|
|
95a46a57ba | ||
|
|
51561e31a0 | ||
|
|
7b19120578 | ||
|
|
745c34a6a9 | ||
|
|
db8fa2454d | ||
|
|
a67a7b4b42 | ||
|
|
496846e532 | ||
|
|
dadcfebf8e | ||
|
|
67033dbd7f | ||
|
|
05a85cfc2a | ||
|
|
40c5d3d72b | ||
|
|
198f0f80c6 | ||
|
|
e3f2fd3892 | ||
|
|
f401ccc599 | ||
|
|
81b59139f8 | ||
|
|
1026781ab6 | ||
|
|
9c699b8cd9 | ||
|
|
34bec59bc3 | ||
|
|
a5fbbf0d66 | ||
|
|
b42721167b | ||
|
|
543dec9ff0 | ||
|
|
04f962f6b0 | ||
|
|
19e896ff69 | ||
|
|
272e4103b2 | ||
|
|
75c257ebb6 | ||
|
|
9ee152eb42 | ||
|
|
c9ae1b1737 | ||
|
|
89dc80c42a | ||
|
|
7b020ac799 | ||
|
|
529e774bbb | ||
|
|
7c12239305 | ||
|
|
d83424d6b4 | ||
|
|
8bf89f887c | ||
|
|
b2160b2304 | ||
|
|
1bb82597be | ||
|
|
e4eee38b3c | ||
|
|
64fc2be503 | ||
|
|
dc8054e90d | ||
|
|
1684940946 | ||
|
|
695813463c | ||
|
|
ed594b0f76 |
@@ -1,5 +1,5 @@
|
|||||||
[tool.bumpversion]
|
[tool.bumpversion]
|
||||||
current_version = "0.19.1-beta.1"
|
current_version = "0.21.0"
|
||||||
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*)\\.
|
||||||
|
|||||||
7
.github/workflows/java.yml
vendored
7
.github/workflows/java.yml
vendored
@@ -35,6 +35,9 @@ jobs:
|
|||||||
- uses: Swatinem/rust-cache@v2
|
- uses: Swatinem/rust-cache@v2
|
||||||
with:
|
with:
|
||||||
workspaces: java/core/lancedb-jni
|
workspaces: java/core/lancedb-jni
|
||||||
|
- uses: actions-rust-lang/setup-rust-toolchain@v1
|
||||||
|
with:
|
||||||
|
components: rustfmt
|
||||||
- name: Run cargo fmt
|
- name: Run cargo fmt
|
||||||
run: cargo fmt --check
|
run: cargo fmt --check
|
||||||
working-directory: ./java/core/lancedb-jni
|
working-directory: ./java/core/lancedb-jni
|
||||||
@@ -68,6 +71,9 @@ jobs:
|
|||||||
- uses: Swatinem/rust-cache@v2
|
- uses: Swatinem/rust-cache@v2
|
||||||
with:
|
with:
|
||||||
workspaces: java/core/lancedb-jni
|
workspaces: java/core/lancedb-jni
|
||||||
|
- uses: actions-rust-lang/setup-rust-toolchain@v1
|
||||||
|
with:
|
||||||
|
components: rustfmt
|
||||||
- name: Run cargo fmt
|
- name: Run cargo fmt
|
||||||
run: cargo fmt --check
|
run: cargo fmt --check
|
||||||
working-directory: ./java/core/lancedb-jni
|
working-directory: ./java/core/lancedb-jni
|
||||||
@@ -110,4 +116,3 @@ jobs:
|
|||||||
-Djdk.reflect.useDirectMethodHandle=false \
|
-Djdk.reflect.useDirectMethodHandle=false \
|
||||||
-Dio.netty.tryReflectionSetAccessible=true"
|
-Dio.netty.tryReflectionSetAccessible=true"
|
||||||
JAVA_HOME=$JAVA_17 mvn clean test
|
JAVA_HOME=$JAVA_17 mvn clean test
|
||||||
|
|
||||||
|
|||||||
9
.github/workflows/make-release-commit.yml
vendored
9
.github/workflows/make-release-commit.yml
vendored
@@ -84,6 +84,7 @@ jobs:
|
|||||||
run: |
|
run: |
|
||||||
pip install bump-my-version PyGithub packaging
|
pip install bump-my-version PyGithub packaging
|
||||||
bash ci/bump_version.sh ${{ inputs.type }} ${{ inputs.bump-minor }} v $COMMIT_BEFORE_BUMP
|
bash ci/bump_version.sh ${{ inputs.type }} ${{ inputs.bump-minor }} v $COMMIT_BEFORE_BUMP
|
||||||
|
bash ci/update_lockfiles.sh --amend
|
||||||
- name: Push new version tag
|
- name: Push new version tag
|
||||||
if: ${{ !inputs.dry_run }}
|
if: ${{ !inputs.dry_run }}
|
||||||
uses: ad-m/github-push-action@master
|
uses: ad-m/github-push-action@master
|
||||||
@@ -92,11 +93,3 @@ jobs:
|
|||||||
github_token: ${{ secrets.LANCEDB_RELEASE_TOKEN }}
|
github_token: ${{ secrets.LANCEDB_RELEASE_TOKEN }}
|
||||||
branch: ${{ github.ref }}
|
branch: ${{ github.ref }}
|
||||||
tags: true
|
tags: true
|
||||||
- uses: ./.github/workflows/update_package_lock
|
|
||||||
if: ${{ !inputs.dry_run && inputs.other }}
|
|
||||||
with:
|
|
||||||
github_token: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
- uses: ./.github/workflows/update_package_lock_nodejs
|
|
||||||
if: ${{ !inputs.dry_run && inputs.other }}
|
|
||||||
with:
|
|
||||||
github_token: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
|
|||||||
5
.github/workflows/nodejs.yml
vendored
5
.github/workflows/nodejs.yml
vendored
@@ -47,6 +47,9 @@ jobs:
|
|||||||
run: |
|
run: |
|
||||||
sudo apt update
|
sudo apt update
|
||||||
sudo apt install -y protobuf-compiler libssl-dev
|
sudo apt install -y protobuf-compiler libssl-dev
|
||||||
|
- uses: actions-rust-lang/setup-rust-toolchain@v1
|
||||||
|
with:
|
||||||
|
components: rustfmt, clippy
|
||||||
- name: Lint
|
- name: Lint
|
||||||
run: |
|
run: |
|
||||||
cargo fmt --all -- --check
|
cargo fmt --all -- --check
|
||||||
@@ -113,7 +116,7 @@ jobs:
|
|||||||
set -e
|
set -e
|
||||||
npm ci
|
npm ci
|
||||||
npm run docs
|
npm run docs
|
||||||
if ! git diff --exit-code; then
|
if ! git diff --exit-code -- . ':(exclude)Cargo.lock'; then
|
||||||
echo "Docs need to be updated"
|
echo "Docs need to be updated"
|
||||||
echo "Run 'npm run docs', fix any warnings, and commit the changes."
|
echo "Run 'npm run docs', fix any warnings, and commit the changes."
|
||||||
exit 1
|
exit 1
|
||||||
|
|||||||
34
.github/workflows/npm-publish.yml
vendored
34
.github/workflows/npm-publish.yml
vendored
@@ -505,6 +505,8 @@ jobs:
|
|||||||
name: vectordb NPM Publish
|
name: vectordb NPM Publish
|
||||||
needs: [node, node-macos, node-linux-gnu, node-windows]
|
needs: [node, node-macos, node-linux-gnu, node-windows]
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
permissions:
|
||||||
|
contents: write
|
||||||
# Only runs on tags that matches the make-release action
|
# Only runs on tags that matches the make-release action
|
||||||
if: startsWith(github.ref, 'refs/tags/v')
|
if: startsWith(github.ref, 'refs/tags/v')
|
||||||
steps:
|
steps:
|
||||||
@@ -537,6 +539,20 @@ jobs:
|
|||||||
# We need to deprecate the old package to avoid confusion.
|
# We need to deprecate the old package to avoid confusion.
|
||||||
# Each time we publish a new version, it gets undeprecated.
|
# Each time we publish a new version, it gets undeprecated.
|
||||||
run: npm deprecate vectordb "Use @lancedb/lancedb instead."
|
run: npm deprecate vectordb "Use @lancedb/lancedb instead."
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
ref: main
|
||||||
|
- name: Update package-lock.json
|
||||||
|
run: |
|
||||||
|
git config user.name 'Lance Release'
|
||||||
|
git config user.email 'lance-dev@lancedb.com'
|
||||||
|
bash ci/update_lockfiles.sh
|
||||||
|
- name: Push new commit
|
||||||
|
uses: ad-m/github-push-action@master
|
||||||
|
with:
|
||||||
|
github_token: ${{ secrets.LANCEDB_RELEASE_TOKEN }}
|
||||||
|
branch: main
|
||||||
- name: Notify Slack Action
|
- name: Notify Slack Action
|
||||||
uses: ravsamhq/notify-slack-action@2.3.0
|
uses: ravsamhq/notify-slack-action@2.3.0
|
||||||
if: ${{ always() }}
|
if: ${{ always() }}
|
||||||
@@ -546,21 +562,3 @@ jobs:
|
|||||||
notification_title: "{workflow} is failing"
|
notification_title: "{workflow} is failing"
|
||||||
env:
|
env:
|
||||||
SLACK_WEBHOOK_URL: ${{ secrets.ACTION_MONITORING_SLACK }}
|
SLACK_WEBHOOK_URL: ${{ secrets.ACTION_MONITORING_SLACK }}
|
||||||
|
|
||||||
update-package-lock:
|
|
||||||
if: startsWith(github.ref, 'refs/tags/v')
|
|
||||||
needs: [release]
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
permissions:
|
|
||||||
contents: write
|
|
||||||
steps:
|
|
||||||
- name: Checkout
|
|
||||||
uses: actions/checkout@v4
|
|
||||||
with:
|
|
||||||
ref: main
|
|
||||||
token: ${{ secrets.LANCEDB_RELEASE_TOKEN }}
|
|
||||||
fetch-depth: 0
|
|
||||||
lfs: true
|
|
||||||
- uses: ./.github/workflows/update_package_lock
|
|
||||||
with:
|
|
||||||
github_token: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
|
|||||||
4
.github/workflows/run_tests/action.yml
vendored
4
.github/workflows/run_tests/action.yml
vendored
@@ -24,8 +24,8 @@ runs:
|
|||||||
- name: pytest (with integration)
|
- name: pytest (with integration)
|
||||||
shell: bash
|
shell: bash
|
||||||
if: ${{ inputs.integration == 'true' }}
|
if: ${{ inputs.integration == 'true' }}
|
||||||
run: pytest -m "not slow" -x -v --durations=30 python/python/tests
|
run: pytest -m "not slow" -vv --durations=30 python/python/tests
|
||||||
- name: pytest (no integration tests)
|
- name: pytest (no integration tests)
|
||||||
shell: bash
|
shell: bash
|
||||||
if: ${{ inputs.integration != 'true' }}
|
if: ${{ inputs.integration != 'true' }}
|
||||||
run: pytest -m "not slow and not s3_test" -x -v --durations=30 python/python/tests
|
run: pytest -m "not slow and not s3_test" -vv --durations=30 python/python/tests
|
||||||
|
|||||||
7
.github/workflows/rust.yml
vendored
7
.github/workflows/rust.yml
vendored
@@ -40,6 +40,9 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
lfs: true
|
lfs: true
|
||||||
|
- uses: actions-rust-lang/setup-rust-toolchain@v1
|
||||||
|
with:
|
||||||
|
components: rustfmt, clippy
|
||||||
- uses: Swatinem/rust-cache@v2
|
- uses: Swatinem/rust-cache@v2
|
||||||
with:
|
with:
|
||||||
workspaces: rust
|
workspaces: rust
|
||||||
@@ -160,8 +163,8 @@ jobs:
|
|||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
target:
|
target:
|
||||||
- x86_64-pc-windows-msvc
|
- x86_64-pc-windows-msvc
|
||||||
- aarch64-pc-windows-msvc
|
- aarch64-pc-windows-msvc
|
||||||
defaults:
|
defaults:
|
||||||
run:
|
run:
|
||||||
working-directory: rust/lancedb
|
working-directory: rust/lancedb
|
||||||
|
|||||||
33
.github/workflows/update_package_lock/action.yml
vendored
33
.github/workflows/update_package_lock/action.yml
vendored
@@ -1,33 +0,0 @@
|
|||||||
name: update_package_lock
|
|
||||||
description: "Update node's package.lock"
|
|
||||||
|
|
||||||
inputs:
|
|
||||||
github_token:
|
|
||||||
required: true
|
|
||||||
description: "github token for the repo"
|
|
||||||
|
|
||||||
runs:
|
|
||||||
using: "composite"
|
|
||||||
steps:
|
|
||||||
- uses: actions/setup-node@v3
|
|
||||||
with:
|
|
||||||
node-version: 20
|
|
||||||
- name: Set git configs
|
|
||||||
shell: bash
|
|
||||||
run: |
|
|
||||||
git config user.name 'Lance Release'
|
|
||||||
git config user.email 'lance-dev@lancedb.com'
|
|
||||||
- name: Update package-lock.json file
|
|
||||||
working-directory: ./node
|
|
||||||
run: |
|
|
||||||
npm install
|
|
||||||
git add package-lock.json
|
|
||||||
git commit -m "Updating package-lock.json"
|
|
||||||
shell: bash
|
|
||||||
- name: Push changes
|
|
||||||
if: ${{ inputs.dry_run }} == "false"
|
|
||||||
uses: ad-m/github-push-action@master
|
|
||||||
with:
|
|
||||||
github_token: ${{ inputs.github_token }}
|
|
||||||
branch: main
|
|
||||||
tags: true
|
|
||||||
@@ -1,33 +0,0 @@
|
|||||||
name: update_package_lock_nodejs
|
|
||||||
description: "Update nodejs's package.lock"
|
|
||||||
|
|
||||||
inputs:
|
|
||||||
github_token:
|
|
||||||
required: true
|
|
||||||
description: "github token for the repo"
|
|
||||||
|
|
||||||
runs:
|
|
||||||
using: "composite"
|
|
||||||
steps:
|
|
||||||
- uses: actions/setup-node@v3
|
|
||||||
with:
|
|
||||||
node-version: 20
|
|
||||||
- name: Set git configs
|
|
||||||
shell: bash
|
|
||||||
run: |
|
|
||||||
git config user.name 'Lance Release'
|
|
||||||
git config user.email 'lance-dev@lancedb.com'
|
|
||||||
- name: Update package-lock.json file
|
|
||||||
working-directory: ./nodejs
|
|
||||||
run: |
|
|
||||||
npm install
|
|
||||||
git add package-lock.json
|
|
||||||
git commit -m "Updating package-lock.json"
|
|
||||||
shell: bash
|
|
||||||
- name: Push changes
|
|
||||||
if: ${{ inputs.dry_run }} == "false"
|
|
||||||
uses: ad-m/github-push-action@master
|
|
||||||
with:
|
|
||||||
github_token: ${{ inputs.github_token }}
|
|
||||||
branch: main
|
|
||||||
tags: true
|
|
||||||
1945
Cargo.lock
generated
1945
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
53
Cargo.toml
53
Cargo.toml
@@ -21,32 +21,32 @@ categories = ["database-implementations"]
|
|||||||
rust-version = "1.78.0"
|
rust-version = "1.78.0"
|
||||||
|
|
||||||
[workspace.dependencies]
|
[workspace.dependencies]
|
||||||
lance = { "version" = "=0.27.0", "features" = ["dynamodb"], tag = "v0.27.0-beta.2", git="https://github.com/lancedb/lance.git" }
|
lance = { "version" = "=0.30.0", "features" = ["dynamodb"] }
|
||||||
lance-io = { version = "=0.27.0", tag = "v0.27.0-beta.2", git="https://github.com/lancedb/lance.git" }
|
lance-io = "=0.30.0"
|
||||||
lance-index = { version = "=0.27.0", tag = "v0.27.0-beta.2", git="https://github.com/lancedb/lance.git" }
|
lance-index = "=0.30.0"
|
||||||
lance-linalg = { version = "=0.27.0", tag = "v0.27.0-beta.2", git="https://github.com/lancedb/lance.git" }
|
lance-linalg = "=0.30.0"
|
||||||
lance-table = { version = "=0.27.0", tag = "v0.27.0-beta.2", git="https://github.com/lancedb/lance.git" }
|
lance-table = "=0.30.0"
|
||||||
lance-testing = { version = "=0.27.0", tag = "v0.27.0-beta.2", git="https://github.com/lancedb/lance.git" }
|
lance-testing = "=0.30.0"
|
||||||
lance-datafusion = { version = "=0.27.0", tag = "v0.27.0-beta.2", git="https://github.com/lancedb/lance.git" }
|
lance-datafusion = "=0.30.0"
|
||||||
lance-encoding = { version = "=0.27.0", tag = "v0.27.0-beta.2", git="https://github.com/lancedb/lance.git" }
|
lance-encoding = "=0.30.0"
|
||||||
# Note that this one does not include pyarrow
|
# Note that this one does not include pyarrow
|
||||||
arrow = { version = "54.1", optional = false }
|
arrow = { version = "55.1", optional = false }
|
||||||
arrow-array = "54.1"
|
arrow-array = "55.1"
|
||||||
arrow-data = "54.1"
|
arrow-data = "55.1"
|
||||||
arrow-ipc = "54.1"
|
arrow-ipc = "55.1"
|
||||||
arrow-ord = "54.1"
|
arrow-ord = "55.1"
|
||||||
arrow-schema = "54.1"
|
arrow-schema = "55.1"
|
||||||
arrow-arith = "54.1"
|
arrow-arith = "55.1"
|
||||||
arrow-cast = "54.1"
|
arrow-cast = "55.1"
|
||||||
async-trait = "0"
|
async-trait = "0"
|
||||||
datafusion = { version = "46.0", default-features = false }
|
datafusion = { version = "47.0", default-features = false }
|
||||||
datafusion-catalog = "46.0"
|
datafusion-catalog = "47.0"
|
||||||
datafusion-common = { version = "46.0", default-features = false }
|
datafusion-common = { version = "47.0", default-features = false }
|
||||||
datafusion-execution = "46.0"
|
datafusion-execution = "47.0"
|
||||||
datafusion-expr = "46.0"
|
datafusion-expr = "47.0"
|
||||||
datafusion-physical-plan = "46.0"
|
datafusion-physical-plan = "47.0"
|
||||||
env_logger = "0.11"
|
env_logger = "0.11"
|
||||||
half = { "version" = "=2.4.1", default-features = false, features = [
|
half = { "version" = "=2.5.0", default-features = false, features = [
|
||||||
"num-traits",
|
"num-traits",
|
||||||
] }
|
] }
|
||||||
futures = "0"
|
futures = "0"
|
||||||
@@ -57,19 +57,16 @@ pin-project = "1.0.7"
|
|||||||
snafu = "0.8"
|
snafu = "0.8"
|
||||||
url = "2"
|
url = "2"
|
||||||
num-traits = "0.2"
|
num-traits = "0.2"
|
||||||
rand = "0.8"
|
rand = "0.9"
|
||||||
regex = "1.10"
|
regex = "1.10"
|
||||||
lazy_static = "1"
|
lazy_static = "1"
|
||||||
semver = "1.0.25"
|
semver = "1.0.25"
|
||||||
|
|
||||||
# Temporary pins to work around downstream issues
|
# Temporary pins to work around downstream issues
|
||||||
# https://github.com/apache/arrow-rs/commit/2fddf85afcd20110ce783ed5b4cdeb82293da30b
|
# https://github.com/apache/arrow-rs/commit/2fddf85afcd20110ce783ed5b4cdeb82293da30b
|
||||||
chrono = "=0.4.39"
|
chrono = "=0.4.41"
|
||||||
# https://github.com/RustCrypto/formats/issues/1684
|
# https://github.com/RustCrypto/formats/issues/1684
|
||||||
base64ct = "=1.6.0"
|
base64ct = "=1.6.0"
|
||||||
|
|
||||||
# Workaround for: https://github.com/eira-fransham/crunchy/issues/13
|
# Workaround for: https://github.com/eira-fransham/crunchy/issues/13
|
||||||
crunchy = "=0.2.2"
|
crunchy = "=0.2.2"
|
||||||
|
|
||||||
# Workaround for: https://github.com/Lokathor/bytemuck/issues/306
|
# Workaround for: https://github.com/Lokathor/bytemuck/issues/306
|
||||||
bytemuck_derive = ">=1.8.1, <1.9.0"
|
bytemuck_derive = ">=1.8.1, <1.9.0"
|
||||||
|
|||||||
129
README.md
129
README.md
@@ -1,94 +1,97 @@
|
|||||||
<a href="https://cloud.lancedb.com" target="_blank">
|
<a href="https://cloud.lancedb.com" target="_blank">
|
||||||
<img src="https://github.com/user-attachments/assets/92dad0a2-2a37-4ce1-b783-0d1b4f30a00c" alt="LanceDB Cloud Public Beta" width="100%" style="max-width: 100%;">
|
<img src="https://github.com/user-attachments/assets/92dad0a2-2a37-4ce1-b783-0d1b4f30a00c" alt="LanceDB Cloud Public Beta" width="100%" style="max-width: 100%;">
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
<div align="center">
|
<div align="center">
|
||||||
<p align="center">
|
|
||||||
|
|
||||||
<picture>
|
[](https://lancedb.com)
|
||||||
<source media="(prefers-color-scheme: dark)" srcset="https://github.com/user-attachments/assets/ac270358-333e-4bea-a132-acefaa94040e">
|
[](https://lancedb.com/)
|
||||||
<source media="(prefers-color-scheme: light)" srcset="https://github.com/user-attachments/assets/b864d814-0d29-4784-8fd9-807297c758c0">
|
[](https://blog.lancedb.com/)
|
||||||
<img alt="LanceDB Logo" src="https://github.com/user-attachments/assets/b864d814-0d29-4784-8fd9-807297c758c0" width=300>
|
[](https://discord.gg/zMM32dvNtd)
|
||||||
</picture>
|
[](https://twitter.com/lancedb)
|
||||||
|
[](https://www.linkedin.com/company/lancedb/)
|
||||||
|
|
||||||
**Search More, Manage Less**
|
|
||||||
|
|
||||||
<a href='https://github.com/lancedb/vectordb-recipes/tree/main' target="_blank"><img alt='LanceDB' src='https://img.shields.io/badge/VectorDB_Recipes-100000?style=for-the-badge&logo=LanceDB&logoColor=white&labelColor=645cfb&color=645cfb'/></a>
|
<img src="docs/src/assets/lancedb.png" alt="LanceDB" width="50%">
|
||||||
<a href='https://lancedb.github.io/lancedb/' target="_blank"><img alt='lancdb' src='https://img.shields.io/badge/DOCS-100000?style=for-the-badge&logo=lancdb&logoColor=white&labelColor=645cfb&color=645cfb'/></a>
|
|
||||||
[](https://blog.lancedb.com/)
|
|
||||||
[](https://discord.gg/zMM32dvNtd)
|
|
||||||
[](https://twitter.com/lancedb)
|
|
||||||
[](https://gurubase.io/g/lancedb)
|
|
||||||
|
|
||||||
</p>
|
# **The Multimodal AI Lakehouse**
|
||||||
|
|
||||||
<img max-width="750px" alt="LanceDB Multimodal Search" src="https://github.com/lancedb/lancedb/assets/917119/09c5afc5-7816-4687-bae4-f2ca194426ec">
|
[**How to Install** ](#how-to-install) ✦ [**Detailed Documentation**](https://lancedb.github.io/lancedb/) ✦ [**Tutorials and Recipes**](https://github.com/lancedb/vectordb-recipes/tree/main) ✦ [**Contributors**](#contributors)
|
||||||
|
|
||||||
|
**The ultimate multimodal data platform for AI/ML applications.**
|
||||||
|
|
||||||
|
LanceDB is designed for fast, scalable, and production-ready vector search. It is built on top of the Lance columnar format. You can store, index, and search over petabytes of multimodal data and vectors with ease.
|
||||||
|
LanceDB is a central location where developers can build, train and analyze their AI workloads.
|
||||||
|
|
||||||
</p>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<hr />
|
<br>
|
||||||
|
|
||||||
LanceDB is an open-source database for vector-search built with persistent storage, which greatly simplifies retrieval, filtering and management of embeddings.
|
## **Demo: Multimodal Search by Keyword, Vector or with SQL**
|
||||||
|
<img max-width="750px" alt="LanceDB Multimodal Search" src="https://github.com/lancedb/lancedb/assets/917119/09c5afc5-7816-4687-bae4-f2ca194426ec">
|
||||||
|
|
||||||
The key features of LanceDB include:
|
## **Star LanceDB to get updates!**
|
||||||
|
|
||||||
* Production-scale vector search with no servers to manage.
|
<details>
|
||||||
|
<summary>⭐ Click here ⭐ to see how fast we're growing!</summary>
|
||||||
|
<picture>
|
||||||
|
<source media="(prefers-color-scheme: dark)" srcset="https://api.star-history.com/svg?repos=lancedb/lancedb&theme=dark&type=Date">
|
||||||
|
<img width="100%" src="https://api.star-history.com/svg?repos=lancedb/lancedb&theme=dark&type=Date">
|
||||||
|
</picture>
|
||||||
|
</details>
|
||||||
|
|
||||||
* Store, query and filter vectors, metadata and multi-modal data (text, images, videos, point clouds, and more).
|
## **Key Features**:
|
||||||
|
|
||||||
* Support for vector similarity search, full-text search and SQL.
|
- **Fast Vector Search**: Search billions of vectors in milliseconds with state-of-the-art indexing.
|
||||||
|
- **Comprehensive Search**: Support for vector similarity search, full-text search and SQL.
|
||||||
|
- **Multimodal Support**: Store, query and filter vectors, metadata and multimodal data (text, images, videos, point clouds, and more).
|
||||||
|
- **Advanced Features**: Zero-copy, automatic versioning, manage versions of your data without needing extra infrastructure. GPU support in building vector index.
|
||||||
|
|
||||||
* Native Python and Javascript/Typescript support.
|
### **Products**:
|
||||||
|
- **Open Source & Local**: 100% open source, runs locally or in your cloud. No vendor lock-in.
|
||||||
|
- **Cloud and Enterprise**: Production-scale vector search with no servers to manage. Complete data sovereignty and security.
|
||||||
|
|
||||||
* Zero-copy, automatic versioning, manage versions of your data without needing extra infrastructure.
|
### **Ecosystem**:
|
||||||
|
- **Columnar Storage**: Built on the Lance columnar format for efficient storage and analytics.
|
||||||
|
- **Seamless Integration**: Python, Node.js, Rust, and REST APIs for easy integration. Native Python and Javascript/Typescript support.
|
||||||
|
- **Rich Ecosystem**: Integrations with [**LangChain** 🦜️🔗](https://python.langchain.com/docs/integrations/vectorstores/lancedb/), [**LlamaIndex** 🦙](https://gpt-index.readthedocs.io/en/latest/examples/vector_stores/LanceDBIndexDemo.html), Apache-Arrow, Pandas, Polars, DuckDB and more on the way.
|
||||||
|
|
||||||
* GPU support in building vector index(*).
|
## **How to Install**:
|
||||||
|
|
||||||
* Ecosystem integrations with [LangChain 🦜️🔗](https://python.langchain.com/docs/integrations/vectorstores/lancedb/), [LlamaIndex 🦙](https://gpt-index.readthedocs.io/en/latest/examples/vector_stores/LanceDBIndexDemo.html), Apache-Arrow, Pandas, Polars, DuckDB and more on the way.
|
Follow the [Quickstart](https://lancedb.github.io/lancedb/basic/) doc to set up LanceDB locally.
|
||||||
|
|
||||||
LanceDB's core is written in Rust 🦀 and is built using <a href="https://github.com/lancedb/lance">Lance</a>, an open-source columnar format designed for performant ML workloads.
|
**API & SDK:** We also support Python, Typescript and Rust SDKs
|
||||||
|
|
||||||
## Quick Start
|
| Interface | Documentation |
|
||||||
|
|-----------|---------------|
|
||||||
|
| Python SDK | https://lancedb.github.io/lancedb/python/python/ |
|
||||||
|
| Typescript SDK | https://lancedb.github.io/lancedb/js/globals/ |
|
||||||
|
| Rust SDK | https://docs.rs/lancedb/latest/lancedb/index.html |
|
||||||
|
| REST API | https://docs.lancedb.com/api-reference/introduction |
|
||||||
|
|
||||||
**Javascript**
|
## **Join Us and Contribute**
|
||||||
```shell
|
|
||||||
npm install @lancedb/lancedb
|
|
||||||
```
|
|
||||||
|
|
||||||
```javascript
|
We welcome contributions from everyone! Whether you're a developer, researcher, or just someone who wants to help out.
|
||||||
import * as lancedb from "@lancedb/lancedb";
|
|
||||||
|
|
||||||
const db = await lancedb.connect("data/sample-lancedb");
|
If you have any suggestions or feature requests, please feel free to open an issue on GitHub or discuss it on our [**Discord**](https://discord.gg/G5DcmnZWKB) server.
|
||||||
const table = await db.createTable("vectors", [
|
|
||||||
{ id: 1, vector: [0.1, 0.2], item: "foo", price: 10 },
|
[**Check out the GitHub Issues**](https://github.com/lancedb/lancedb/issues) if you would like to work on the features that are planned for the future. If you have any suggestions or feature requests, please feel free to open an issue on GitHub.
|
||||||
{ id: 2, vector: [1.1, 1.2], item: "bar", price: 50 },
|
|
||||||
], {mode: 'overwrite'});
|
## **Contributors**
|
||||||
|
|
||||||
|
<a href="https://github.com/lancedb/lancedb/graphs/contributors">
|
||||||
|
<img src="https://contrib.rocks/image?repo=lancedb/lancedb" />
|
||||||
|
</a>
|
||||||
|
|
||||||
|
|
||||||
const query = table.vectorSearch([0.1, 0.3]).limit(2);
|
## **Stay in Touch With Us**
|
||||||
const results = await query.toArray();
|
<div align="center">
|
||||||
|
|
||||||
// You can also search for rows by specific criteria without involving a vector search.
|
</br>
|
||||||
const rowsByCriteria = await table.query().where("price >= 10").toArray();
|
|
||||||
```
|
|
||||||
|
|
||||||
**Python**
|
[](https://lancedb.com/)
|
||||||
```shell
|
[](https://blog.lancedb.com/)
|
||||||
pip install lancedb
|
[](https://discord.gg/zMM32dvNtd)
|
||||||
```
|
[](https://twitter.com/lancedb)
|
||||||
|
[](https://www.linkedin.com/company/lancedb/)
|
||||||
|
|
||||||
```python
|
</div>
|
||||||
import lancedb
|
|
||||||
|
|
||||||
uri = "data/sample-lancedb"
|
|
||||||
db = lancedb.connect(uri)
|
|
||||||
table = db.create_table("my_table",
|
|
||||||
data=[{"vector": [3.1, 4.1], "item": "foo", "price": 10.0},
|
|
||||||
{"vector": [5.9, 26.5], "item": "bar", "price": 20.0}])
|
|
||||||
result = table.search([100, 100]).limit(2).to_pandas()
|
|
||||||
```
|
|
||||||
|
|
||||||
## Blogs, Tutorials & Videos
|
|
||||||
* 📈 <a href="https://blog.lancedb.com/benchmarking-random-access-in-lance/">2000x better performance with Lance over Parquet</a>
|
|
||||||
* 🤖 <a href="https://github.com/lancedb/vectordb-recipes/tree/main/examples/Youtube-Search-QA-Bot">Build a question and answer bot with LanceDB</a>
|
|
||||||
|
|||||||
174
ci/set_lance_version.py
Normal file
174
ci/set_lance_version.py
Normal file
@@ -0,0 +1,174 @@
|
|||||||
|
import argparse
|
||||||
|
import sys
|
||||||
|
import json
|
||||||
|
|
||||||
|
|
||||||
|
def run_command(command: str) -> str:
|
||||||
|
"""
|
||||||
|
Run a shell command and return stdout as a string.
|
||||||
|
If exit code is not 0, raise an exception with the stderr output.
|
||||||
|
"""
|
||||||
|
import subprocess
|
||||||
|
|
||||||
|
result = subprocess.run(command, shell=True, capture_output=True, text=True)
|
||||||
|
if result.returncode != 0:
|
||||||
|
raise Exception(f"Command failed with error: {result.stderr.strip()}")
|
||||||
|
return result.stdout.strip()
|
||||||
|
|
||||||
|
|
||||||
|
def get_latest_stable_version() -> str:
|
||||||
|
version_line = run_command("cargo info lance | grep '^version:'")
|
||||||
|
version = version_line.split(" ")[1].strip()
|
||||||
|
return version
|
||||||
|
|
||||||
|
|
||||||
|
def get_latest_preview_version() -> str:
|
||||||
|
lance_tags = run_command(
|
||||||
|
"git ls-remote --tags https://github.com/lancedb/lance.git | grep 'refs/tags/v[0-9beta.-]\\+$'"
|
||||||
|
).splitlines()
|
||||||
|
lance_tags = (
|
||||||
|
tag.split("refs/tags/")[1]
|
||||||
|
for tag in lance_tags
|
||||||
|
if "refs/tags/" in tag and "beta" in tag
|
||||||
|
)
|
||||||
|
from packaging.version import Version
|
||||||
|
|
||||||
|
latest = max(
|
||||||
|
(tag[1:] for tag in lance_tags if tag.startswith("v")), key=lambda t: Version(t)
|
||||||
|
)
|
||||||
|
return str(latest)
|
||||||
|
|
||||||
|
|
||||||
|
def extract_features(line: str) -> list:
|
||||||
|
"""
|
||||||
|
Extracts the features from a line in Cargo.toml.
|
||||||
|
Example: 'lance = { "version" = "=0.29.0", "features" = ["dynamodb"] }'
|
||||||
|
Returns: ['dynamodb']
|
||||||
|
"""
|
||||||
|
import re
|
||||||
|
|
||||||
|
match = re.search(r'"features"\s*=\s*\[(.*?)\]', line)
|
||||||
|
if match:
|
||||||
|
features_str = match.group(1)
|
||||||
|
return [f.strip('"') for f in features_str.split(",")]
|
||||||
|
return []
|
||||||
|
|
||||||
|
|
||||||
|
def update_cargo_toml(line_updater):
|
||||||
|
"""
|
||||||
|
Updates the Cargo.toml file by applying the line_updater function to each line.
|
||||||
|
The line_updater function should take a line as input and return the updated line.
|
||||||
|
"""
|
||||||
|
with open("Cargo.toml", "r") as f:
|
||||||
|
lines = f.readlines()
|
||||||
|
|
||||||
|
new_lines = []
|
||||||
|
for line in lines:
|
||||||
|
if line.startswith("lance"):
|
||||||
|
# Update the line using the provided function
|
||||||
|
new_lines.append(line_updater(line))
|
||||||
|
else:
|
||||||
|
# Keep the line unchanged
|
||||||
|
new_lines.append(line)
|
||||||
|
|
||||||
|
with open("Cargo.toml", "w") as f:
|
||||||
|
f.writelines(new_lines)
|
||||||
|
|
||||||
|
|
||||||
|
def set_stable_version(version: str):
|
||||||
|
"""
|
||||||
|
Sets lines to
|
||||||
|
lance = { "version" = "=0.29.0", "features" = ["dynamodb"] }
|
||||||
|
lance-io = "=0.29.0"
|
||||||
|
...
|
||||||
|
"""
|
||||||
|
|
||||||
|
def line_updater(line: str) -> str:
|
||||||
|
package_name = line.split("=", maxsplit=1)[0].strip()
|
||||||
|
features = extract_features(line)
|
||||||
|
if features:
|
||||||
|
return f'{package_name} = {{ "version" = "={version}", "features" = {json.dumps(features)} }}\n'
|
||||||
|
else:
|
||||||
|
return f'{package_name} = "={version}"\n'
|
||||||
|
|
||||||
|
update_cargo_toml(line_updater)
|
||||||
|
|
||||||
|
|
||||||
|
def set_preview_version(version: str):
|
||||||
|
"""
|
||||||
|
Sets lines to
|
||||||
|
lance = { "version" = "=0.29.0", "features" = ["dynamodb"], tag = "v0.29.0-beta.2", git="https://github.com/lancedb/lance.git" }
|
||||||
|
lance-io = { version = "=0.29.0", tag = "v0.29.0-beta.2", git="https://github.com/lancedb/lance.git" }
|
||||||
|
...
|
||||||
|
"""
|
||||||
|
|
||||||
|
def line_updater(line: str) -> str:
|
||||||
|
package_name = line.split("=", maxsplit=1)[0].strip()
|
||||||
|
features = extract_features(line)
|
||||||
|
base_version = version.split("-")[0] # Get the base version without beta suffix
|
||||||
|
if features:
|
||||||
|
return f'{package_name} = {{ "version" = "={base_version}", "features" = {json.dumps(features)}, "tag" = "v{version}", "git" = "https://github.com/lancedb/lance.git" }}\n'
|
||||||
|
else:
|
||||||
|
return f'{package_name} = {{ "version" = "={base_version}", "tag" = "v{version}", "git" = "https://github.com/lancedb/lance.git" }}\n'
|
||||||
|
|
||||||
|
update_cargo_toml(line_updater)
|
||||||
|
|
||||||
|
|
||||||
|
def set_local_version():
|
||||||
|
"""
|
||||||
|
Sets lines to
|
||||||
|
lance = { path = "../lance/rust/lance", features = ["dynamodb"] }
|
||||||
|
lance-io = { path = "../lance/rust/lance-io" }
|
||||||
|
...
|
||||||
|
"""
|
||||||
|
|
||||||
|
def line_updater(line: str) -> str:
|
||||||
|
package_name = line.split("=", maxsplit=1)[0].strip()
|
||||||
|
features = extract_features(line)
|
||||||
|
if features:
|
||||||
|
return f'{package_name} = {{ "path" = "../lance/rust/{package_name}", "features" = {json.dumps(features)} }}\n'
|
||||||
|
else:
|
||||||
|
return f'{package_name} = {{ "path" = "../lance/rust/{package_name}" }}\n'
|
||||||
|
|
||||||
|
update_cargo_toml(line_updater)
|
||||||
|
|
||||||
|
|
||||||
|
parser = argparse.ArgumentParser(description="Set the version of the Lance package.")
|
||||||
|
parser.add_argument(
|
||||||
|
"version",
|
||||||
|
type=str,
|
||||||
|
help="The version to set for the Lance package. Use 'stable' for the latest stable version, 'preview' for latest preview version, or a specific version number (e.g., '0.1.0'). You can also specify 'local' to use a local path.",
|
||||||
|
)
|
||||||
|
args = parser.parse_args()
|
||||||
|
|
||||||
|
if args.version == "stable":
|
||||||
|
latest_stable_version = get_latest_stable_version()
|
||||||
|
print(
|
||||||
|
f"Found latest stable version: \033[1mv{latest_stable_version}\033[0m",
|
||||||
|
file=sys.stderr,
|
||||||
|
)
|
||||||
|
set_stable_version(latest_stable_version)
|
||||||
|
elif args.version == "preview":
|
||||||
|
latest_preview_version = get_latest_preview_version()
|
||||||
|
print(
|
||||||
|
f"Found latest preview version: \033[1mv{latest_preview_version}\033[0m",
|
||||||
|
file=sys.stderr,
|
||||||
|
)
|
||||||
|
set_preview_version(latest_preview_version)
|
||||||
|
elif args.version == "local":
|
||||||
|
set_local_version()
|
||||||
|
else:
|
||||||
|
# Parse the version number.
|
||||||
|
version = args.version
|
||||||
|
# Ignore initial v if present.
|
||||||
|
if version.startswith("v"):
|
||||||
|
version = version[1:]
|
||||||
|
|
||||||
|
if "beta" in version:
|
||||||
|
set_preview_version(version)
|
||||||
|
else:
|
||||||
|
set_stable_version(version)
|
||||||
|
|
||||||
|
print("Updating lockfiles...", file=sys.stderr, end="")
|
||||||
|
run_command("cargo metadata > /dev/null")
|
||||||
|
print(" done.", file=sys.stderr)
|
||||||
30
ci/update_lockfiles.sh
Executable file
30
ci/update_lockfiles.sh
Executable file
@@ -0,0 +1,30 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
AMEND=false
|
||||||
|
|
||||||
|
for arg in "$@"; do
|
||||||
|
if [[ "$arg" == "--amend" ]]; then
|
||||||
|
AMEND=true
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
# This updates the lockfile without building
|
||||||
|
cargo metadata --quiet > /dev/null
|
||||||
|
|
||||||
|
pushd nodejs || exit 1
|
||||||
|
npm install --package-lock-only --silent
|
||||||
|
popd
|
||||||
|
pushd node || exit 1
|
||||||
|
npm install --package-lock-only --silent
|
||||||
|
popd
|
||||||
|
|
||||||
|
if git diff --quiet --exit-code; then
|
||||||
|
echo "No lockfile changes to commit; skipping amend."
|
||||||
|
elif $AMEND; then
|
||||||
|
git add Cargo.lock nodejs/package-lock.json node/package-lock.json
|
||||||
|
git commit --amend --no-edit
|
||||||
|
else
|
||||||
|
git add Cargo.lock nodejs/package-lock.json node/package-lock.json
|
||||||
|
git commit -m "Update lockfiles"
|
||||||
|
fi
|
||||||
@@ -193,6 +193,7 @@ nav:
|
|||||||
- Pandas and PyArrow: python/pandas_and_pyarrow.md
|
- Pandas and PyArrow: python/pandas_and_pyarrow.md
|
||||||
- Polars: python/polars_arrow.md
|
- Polars: python/polars_arrow.md
|
||||||
- DuckDB: python/duckdb.md
|
- DuckDB: python/duckdb.md
|
||||||
|
- Datafusion: python/datafusion.md
|
||||||
- LangChain:
|
- LangChain:
|
||||||
- LangChain 🔗: integrations/langchain.md
|
- LangChain 🔗: integrations/langchain.md
|
||||||
- LangChain demo: notebooks/langchain_demo.ipynb
|
- LangChain demo: notebooks/langchain_demo.ipynb
|
||||||
@@ -205,6 +206,7 @@ nav:
|
|||||||
- PromptTools: integrations/prompttools.md
|
- PromptTools: integrations/prompttools.md
|
||||||
- dlt: integrations/dlt.md
|
- dlt: integrations/dlt.md
|
||||||
- phidata: integrations/phidata.md
|
- phidata: integrations/phidata.md
|
||||||
|
- Genkit: integrations/genkit.md
|
||||||
- 🎯 Examples:
|
- 🎯 Examples:
|
||||||
- Overview: examples/index.md
|
- Overview: examples/index.md
|
||||||
- 🐍 Python:
|
- 🐍 Python:
|
||||||
@@ -247,6 +249,7 @@ nav:
|
|||||||
- Data management: concepts/data_management.md
|
- Data management: concepts/data_management.md
|
||||||
- Guides:
|
- Guides:
|
||||||
- Working with tables: guides/tables.md
|
- Working with tables: guides/tables.md
|
||||||
|
- Working with SQL: guides/sql_querying.md
|
||||||
- Building an ANN index: ann_indexes.md
|
- Building an ANN index: ann_indexes.md
|
||||||
- Vector Search: search.md
|
- Vector Search: search.md
|
||||||
- Full-text search (native): fts.md
|
- Full-text search (native): fts.md
|
||||||
@@ -323,6 +326,7 @@ nav:
|
|||||||
- Pandas and PyArrow: python/pandas_and_pyarrow.md
|
- Pandas and PyArrow: python/pandas_and_pyarrow.md
|
||||||
- Polars: python/polars_arrow.md
|
- Polars: python/polars_arrow.md
|
||||||
- DuckDB: python/duckdb.md
|
- DuckDB: python/duckdb.md
|
||||||
|
- Datafusion: python/datafusion.md
|
||||||
- LangChain 🦜️🔗↗: integrations/langchain.md
|
- LangChain 🦜️🔗↗: integrations/langchain.md
|
||||||
- LangChain.js 🦜️🔗↗: https://js.langchain.com/docs/integrations/vectorstores/lancedb
|
- LangChain.js 🦜️🔗↗: https://js.langchain.com/docs/integrations/vectorstores/lancedb
|
||||||
- LlamaIndex 🦙↗: integrations/llamaIndex.md
|
- LlamaIndex 🦙↗: integrations/llamaIndex.md
|
||||||
@@ -331,6 +335,7 @@ nav:
|
|||||||
- PromptTools: integrations/prompttools.md
|
- PromptTools: integrations/prompttools.md
|
||||||
- dlt: integrations/dlt.md
|
- dlt: integrations/dlt.md
|
||||||
- phidata: integrations/phidata.md
|
- phidata: integrations/phidata.md
|
||||||
|
- Genkit: integrations/genkit.md
|
||||||
- Examples:
|
- Examples:
|
||||||
- examples/index.md
|
- examples/index.md
|
||||||
- 🐍 Python:
|
- 🐍 Python:
|
||||||
|
|||||||
5
docs/overrides/partials/main.html
Normal file
5
docs/overrides/partials/main.html
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
{% extends "base.html" %}
|
||||||
|
|
||||||
|
{% block announce %}
|
||||||
|
📚 Starting June 1st, 2025, please use <a href="https://lancedb.github.io/documentation" target="_blank" rel="noopener noreferrer">lancedb.github.io/documentation</a> for the latest docs.
|
||||||
|
{% endblock %}
|
||||||
@@ -291,7 +291,7 @@ Product quantization can lead to approximately `16 * sizeof(float32) / 1 = 64` t
|
|||||||
|
|
||||||
`num_partitions` is used to decide how many partitions the first level `IVF` index uses.
|
`num_partitions` is used to decide how many partitions the first level `IVF` index uses.
|
||||||
Higher number of partitions could lead to more efficient I/O during queries and better accuracy, but it takes much more time to train.
|
Higher number of partitions could lead to more efficient I/O during queries and better accuracy, but it takes much more time to train.
|
||||||
On `SIFT-1M` dataset, our benchmark shows that keeping each partition 1K-4K rows lead to a good latency / recall.
|
On `SIFT-1M` dataset, our benchmark shows that keeping each partition 4K-8K rows lead to a good latency / recall.
|
||||||
|
|
||||||
`num_sub_vectors` specifies how many Product Quantization (PQ) short codes to generate on each vector. The number should be a factor of the vector dimension. Because
|
`num_sub_vectors` specifies how many Product Quantization (PQ) short codes to generate on each vector. The number should be a factor of the vector dimension. Because
|
||||||
PQ is a lossy compression of the original vector, a higher `num_sub_vectors` usually results in
|
PQ is a lossy compression of the original vector, a higher `num_sub_vectors` usually results in
|
||||||
|
|||||||
BIN
docs/src/assets/hero-header.png
Normal file
BIN
docs/src/assets/hero-header.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.7 MiB |
BIN
docs/src/assets/lancedb.png
Normal file
BIN
docs/src/assets/lancedb.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 40 KiB |
68
docs/src/guides/sql_querying.md
Normal file
68
docs/src/guides/sql_querying.md
Normal file
@@ -0,0 +1,68 @@
|
|||||||
|
You can use DuckDB and Apache Datafusion to query your LanceDB tables using SQL.
|
||||||
|
This guide will show how to query Lance tables them using both.
|
||||||
|
|
||||||
|
We will re-use the dataset [created previously](./pandas_and_pyarrow.md):
|
||||||
|
|
||||||
|
```python
|
||||||
|
import lancedb
|
||||||
|
|
||||||
|
db = lancedb.connect("data/sample-lancedb")
|
||||||
|
data = [
|
||||||
|
{"vector": [3.1, 4.1], "item": "foo", "price": 10.0},
|
||||||
|
{"vector": [5.9, 26.5], "item": "bar", "price": 20.0}
|
||||||
|
]
|
||||||
|
table = db.create_table("pd_table", data=data)
|
||||||
|
```
|
||||||
|
|
||||||
|
## Querying a LanceDB Table with DuckDb
|
||||||
|
|
||||||
|
The `to_lance` method converts the LanceDB table to a `LanceDataset`, which is accessible to DuckDB through the Arrow compatibility layer.
|
||||||
|
To query the resulting Lance dataset in DuckDB, all you need to do is reference the dataset by the same name in your SQL query.
|
||||||
|
|
||||||
|
```python
|
||||||
|
import duckdb
|
||||||
|
|
||||||
|
arrow_table = table.to_lance()
|
||||||
|
|
||||||
|
duckdb.query("SELECT * FROM arrow_table")
|
||||||
|
```
|
||||||
|
|
||||||
|
```
|
||||||
|
┌─────────────┬─────────┬────────┐
|
||||||
|
│ vector │ item │ price │
|
||||||
|
│ float[] │ varchar │ double │
|
||||||
|
├─────────────┼─────────┼────────┤
|
||||||
|
│ [3.1, 4.1] │ foo │ 10.0 │
|
||||||
|
│ [5.9, 26.5] │ bar │ 20.0 │
|
||||||
|
└─────────────┴─────────┴────────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
## Querying a LanceDB Table with Apache Datafusion
|
||||||
|
|
||||||
|
Have the required imports before doing any querying.
|
||||||
|
|
||||||
|
=== "Python"
|
||||||
|
|
||||||
|
```python
|
||||||
|
--8<-- "python/python/tests/docs/test_guide_tables.py:import-lancedb"
|
||||||
|
--8<-- "python/python/tests/docs/test_guide_tables.py:import-session-context"
|
||||||
|
--8<-- "python/python/tests/docs/test_guide_tables.py:import-ffi-dataset"
|
||||||
|
```
|
||||||
|
|
||||||
|
Register the table created with the Datafusion session context.
|
||||||
|
|
||||||
|
=== "Python"
|
||||||
|
|
||||||
|
```python
|
||||||
|
--8<-- "python/python/tests/docs/test_guide_tables.py:lance_sql_basic"
|
||||||
|
```
|
||||||
|
|
||||||
|
```
|
||||||
|
┌─────────────┬─────────┬────────┐
|
||||||
|
│ vector │ item │ price │
|
||||||
|
│ float[] │ varchar │ double │
|
||||||
|
├─────────────┼─────────┼────────┤
|
||||||
|
│ [3.1, 4.1] │ foo │ 10.0 │
|
||||||
|
│ [5.9, 26.5] │ bar │ 20.0 │
|
||||||
|
└─────────────┴─────────┴────────┘
|
||||||
|
```
|
||||||
183
docs/src/integrations/genkit.md
Normal file
183
docs/src/integrations/genkit.md
Normal file
@@ -0,0 +1,183 @@
|
|||||||
|
### genkitx-lancedb
|
||||||
|
This is a lancedb plugin for genkit framework. It allows you to use LanceDB for ingesting and rereiving data using genkit framework.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
### Installation
|
||||||
|
```bash
|
||||||
|
pnpm install genkitx-lancedb
|
||||||
|
```
|
||||||
|
|
||||||
|
### Usage
|
||||||
|
|
||||||
|
Adding LanceDB plugin to your genkit instance.
|
||||||
|
|
||||||
|
```ts
|
||||||
|
import { lancedbIndexerRef, lancedb, lancedbRetrieverRef, WriteMode } from 'genkitx-lancedb';
|
||||||
|
import { textEmbedding004, vertexAI } from '@genkit-ai/vertexai';
|
||||||
|
import { gemini } from '@genkit-ai/vertexai';
|
||||||
|
import { z, genkit } from 'genkit';
|
||||||
|
import { Document } from 'genkit/retriever';
|
||||||
|
import { chunk } from 'llm-chunk';
|
||||||
|
import { readFile } from 'fs/promises';
|
||||||
|
import path from 'path';
|
||||||
|
import pdf from 'pdf-parse/lib/pdf-parse';
|
||||||
|
|
||||||
|
const ai = genkit({
|
||||||
|
plugins: [
|
||||||
|
// vertexAI provides the textEmbedding004 embedder
|
||||||
|
vertexAI(),
|
||||||
|
|
||||||
|
// the local vector store requires an embedder to translate from text to vector
|
||||||
|
lancedb([
|
||||||
|
{
|
||||||
|
dbUri: '.db', // optional lancedb uri, default to .db
|
||||||
|
tableName: 'table', // optional table name, default to table
|
||||||
|
embedder: textEmbedding004,
|
||||||
|
},
|
||||||
|
]),
|
||||||
|
],
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
You can run this app with the following command:
|
||||||
|
```bash
|
||||||
|
genkit start -- tsx --watch src/index.ts
|
||||||
|
```
|
||||||
|
|
||||||
|
This'll add LanceDB as a retriever and indexer to the genkit instance. You can see it in the GUI view
|
||||||
|
<img width="1710" alt="Screenshot 2025-05-11 at 7 21 05 PM" src="https://github.com/user-attachments/assets/e752f7f4-785b-4797-a11e-72ab06a531b7" />
|
||||||
|
|
||||||
|
**Testing retrieval on a sample table**
|
||||||
|
Let's see the raw retrieval results
|
||||||
|
|
||||||
|
<img width="1710" alt="Screenshot 2025-05-11 at 7 21 05 PM" src="https://github.com/user-attachments/assets/b8d356ed-8421-4790-8fc0-d6af563b9657" />
|
||||||
|
On running this query, you'll 5 results fetched from the lancedb table, where each result looks something like this:
|
||||||
|
<img width="1417" alt="Screenshot 2025-05-11 at 7 21 18 PM" src="https://github.com/user-attachments/assets/77429525-36e2-4da6-a694-e58c1cf9eb83" />
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## Creating a custom RAG flow
|
||||||
|
|
||||||
|
Now that we've seen how you can use LanceDB for in a genkit pipeline, let's refine the flow and create a RAG. A RAG flow will consist of an index and a retreiver with its outputs postprocessed an fed into an LLM for final response
|
||||||
|
|
||||||
|
### Creating custom indexer flows
|
||||||
|
You can also create custom indexer flows, utilizing more options and features provided by LanceDB.
|
||||||
|
|
||||||
|
```ts
|
||||||
|
export const menuPdfIndexer = lancedbIndexerRef({
|
||||||
|
// Using all defaults, for dbUri, tableName, and embedder, etc
|
||||||
|
});
|
||||||
|
|
||||||
|
const chunkingConfig = {
|
||||||
|
minLength: 1000,
|
||||||
|
maxLength: 2000,
|
||||||
|
splitter: 'sentence',
|
||||||
|
overlap: 100,
|
||||||
|
delimiters: '',
|
||||||
|
} as any;
|
||||||
|
|
||||||
|
|
||||||
|
async function extractTextFromPdf(filePath: string) {
|
||||||
|
const pdfFile = path.resolve(filePath);
|
||||||
|
const dataBuffer = await readFile(pdfFile);
|
||||||
|
const data = await pdf(dataBuffer);
|
||||||
|
return data.text;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const indexMenu = ai.defineFlow(
|
||||||
|
{
|
||||||
|
name: 'indexMenu',
|
||||||
|
inputSchema: z.string().describe('PDF file path'),
|
||||||
|
outputSchema: z.void(),
|
||||||
|
},
|
||||||
|
async (filePath: string) => {
|
||||||
|
filePath = path.resolve(filePath);
|
||||||
|
|
||||||
|
// Read the pdf.
|
||||||
|
const pdfTxt = await ai.run('extract-text', () =>
|
||||||
|
extractTextFromPdf(filePath)
|
||||||
|
);
|
||||||
|
|
||||||
|
// Divide the pdf text into segments.
|
||||||
|
const chunks = await ai.run('chunk-it', async () =>
|
||||||
|
chunk(pdfTxt, chunkingConfig)
|
||||||
|
);
|
||||||
|
|
||||||
|
// Convert chunks of text into documents to store in the index.
|
||||||
|
const documents = chunks.map((text) => {
|
||||||
|
return Document.fromText(text, { filePath });
|
||||||
|
});
|
||||||
|
|
||||||
|
// Add documents to the index.
|
||||||
|
await ai.index({
|
||||||
|
indexer: menuPdfIndexer,
|
||||||
|
documents,
|
||||||
|
options: {
|
||||||
|
writeMode: WriteMode.Overwrite,
|
||||||
|
} as any
|
||||||
|
});
|
||||||
|
}
|
||||||
|
);
|
||||||
|
```
|
||||||
|
|
||||||
|
<img width="1316" alt="Screenshot 2025-05-11 at 8 35 56 PM" src="https://github.com/user-attachments/assets/e2a20ce4-d1d0-4fa2-9a84-f2cc26e3a29f" />
|
||||||
|
|
||||||
|
In your console, you can see the logs
|
||||||
|
|
||||||
|
<img width="511" alt="Screenshot 2025-05-11 at 7 19 14 PM" src="https://github.com/user-attachments/assets/243f26c5-ed38-40b6-b661-002f40f0423a" />
|
||||||
|
|
||||||
|
### Creating custom retriever flows
|
||||||
|
You can also create custom retriever flows, utilizing more options and features provided by LanceDB.
|
||||||
|
```ts
|
||||||
|
export const menuRetriever = lancedbRetrieverRef({
|
||||||
|
tableName: "table", // Use the same table name as the indexer.
|
||||||
|
displayName: "Menu", // Use a custom display name.
|
||||||
|
|
||||||
|
export const menuQAFlow = ai.defineFlow(
|
||||||
|
{ name: "Menu", inputSchema: z.string(), outputSchema: z.string() },
|
||||||
|
async (input: string) => {
|
||||||
|
// retrieve relevant documents
|
||||||
|
const docs = await ai.retrieve({
|
||||||
|
retriever: menuRetriever,
|
||||||
|
query: input,
|
||||||
|
options: {
|
||||||
|
k: 3,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const extractedContent = docs.map(doc => {
|
||||||
|
if (doc.content && Array.isArray(doc.content) && doc.content.length > 0) {
|
||||||
|
if (doc.content[0].media && doc.content[0].media.url) {
|
||||||
|
return doc.content[0].media.url;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return "No content found";
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log("Extracted content:", extractedContent);
|
||||||
|
|
||||||
|
const { text } = await ai.generate({
|
||||||
|
model: gemini('gemini-2.0-flash'),
|
||||||
|
prompt: `
|
||||||
|
You are acting as a helpful AI assistant that can answer
|
||||||
|
questions about the food available on the menu at Genkit Grub Pub.
|
||||||
|
|
||||||
|
Use only the context provided to answer the question.
|
||||||
|
If you don't know, do not make up an answer.
|
||||||
|
Do not add or change items on the menu.
|
||||||
|
|
||||||
|
Context:
|
||||||
|
${extractedContent.join('\n\n')}
|
||||||
|
|
||||||
|
Question: ${input}`,
|
||||||
|
docs,
|
||||||
|
});
|
||||||
|
|
||||||
|
return text;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
```
|
||||||
|
Now using our retrieval flow, we can ask question about the ingsted PDF
|
||||||
|
<img width="1306" alt="Screenshot 2025-05-11 at 7 18 45 PM" src="https://github.com/user-attachments/assets/86c66b13-7c12-4d5f-9d81-ae36bfb1c346" />
|
||||||
|
|
||||||
53
docs/src/js/classes/BooleanQuery.md
Normal file
53
docs/src/js/classes/BooleanQuery.md
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
[**@lancedb/lancedb**](../README.md) • **Docs**
|
||||||
|
|
||||||
|
***
|
||||||
|
|
||||||
|
[@lancedb/lancedb](../globals.md) / BooleanQuery
|
||||||
|
|
||||||
|
# Class: BooleanQuery
|
||||||
|
|
||||||
|
Represents a full-text query interface.
|
||||||
|
This interface defines the structure and behavior for full-text queries,
|
||||||
|
including methods to retrieve the query type and convert the query to a dictionary format.
|
||||||
|
|
||||||
|
## Implements
|
||||||
|
|
||||||
|
- [`FullTextQuery`](../interfaces/FullTextQuery.md)
|
||||||
|
|
||||||
|
## Constructors
|
||||||
|
|
||||||
|
### new BooleanQuery()
|
||||||
|
|
||||||
|
```ts
|
||||||
|
new BooleanQuery(queries): BooleanQuery
|
||||||
|
```
|
||||||
|
|
||||||
|
Creates an instance of BooleanQuery.
|
||||||
|
|
||||||
|
#### Parameters
|
||||||
|
|
||||||
|
* **queries**: [[`Occur`](../enumerations/Occur.md), [`FullTextQuery`](../interfaces/FullTextQuery.md)][]
|
||||||
|
An array of (Occur, FullTextQuery objects) to combine.
|
||||||
|
Occur specifies whether the query must match, or should match.
|
||||||
|
|
||||||
|
#### Returns
|
||||||
|
|
||||||
|
[`BooleanQuery`](BooleanQuery.md)
|
||||||
|
|
||||||
|
## Methods
|
||||||
|
|
||||||
|
### queryType()
|
||||||
|
|
||||||
|
```ts
|
||||||
|
queryType(): FullTextQueryType
|
||||||
|
```
|
||||||
|
|
||||||
|
The type of the full-text query.
|
||||||
|
|
||||||
|
#### Returns
|
||||||
|
|
||||||
|
[`FullTextQueryType`](../enumerations/FullTextQueryType.md)
|
||||||
|
|
||||||
|
#### Implementation of
|
||||||
|
|
||||||
|
[`FullTextQuery`](../interfaces/FullTextQuery.md).[`queryType`](../interfaces/FullTextQuery.md#querytype)
|
||||||
@@ -40,6 +40,7 @@ Creates an instance of MatchQuery.
|
|||||||
- `boost`: The boost factor for the query (default is 1.0).
|
- `boost`: The boost factor for the query (default is 1.0).
|
||||||
- `fuzziness`: The fuzziness level for the query (default is 0).
|
- `fuzziness`: The fuzziness level for the query (default is 0).
|
||||||
- `maxExpansions`: The maximum number of terms to consider for fuzzy matching (default is 50).
|
- `maxExpansions`: The maximum number of terms to consider for fuzzy matching (default is 50).
|
||||||
|
- `operator`: The logical operator to use for combining terms in the query (default is "OR").
|
||||||
|
|
||||||
* **options.boost?**: `number`
|
* **options.boost?**: `number`
|
||||||
|
|
||||||
@@ -47,6 +48,8 @@ Creates an instance of MatchQuery.
|
|||||||
|
|
||||||
* **options.maxExpansions?**: `number`
|
* **options.maxExpansions?**: `number`
|
||||||
|
|
||||||
|
* **options.operator?**: [`Operator`](../enumerations/Operator.md)
|
||||||
|
|
||||||
#### Returns
|
#### Returns
|
||||||
|
|
||||||
[`MatchQuery`](MatchQuery.md)
|
[`MatchQuery`](MatchQuery.md)
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ Construct a MergeInsertBuilder. __Internal use only.__
|
|||||||
### execute()
|
### execute()
|
||||||
|
|
||||||
```ts
|
```ts
|
||||||
execute(data): Promise<MergeStats>
|
execute(data, execOptions?): Promise<MergeResult>
|
||||||
```
|
```
|
||||||
|
|
||||||
Executes the merge insert operation
|
Executes the merge insert operation
|
||||||
@@ -42,11 +42,13 @@ Executes the merge insert operation
|
|||||||
|
|
||||||
* **data**: [`Data`](../type-aliases/Data.md)
|
* **data**: [`Data`](../type-aliases/Data.md)
|
||||||
|
|
||||||
|
* **execOptions?**: `Partial`<[`WriteExecutionOptions`](../interfaces/WriteExecutionOptions.md)>
|
||||||
|
|
||||||
#### Returns
|
#### Returns
|
||||||
|
|
||||||
`Promise`<[`MergeStats`](../interfaces/MergeStats.md)>
|
`Promise`<[`MergeResult`](../interfaces/MergeResult.md)>
|
||||||
|
|
||||||
Statistics about the merge operation: counts of inserted, updated, and deleted rows
|
the merge result
|
||||||
|
|
||||||
***
|
***
|
||||||
|
|
||||||
|
|||||||
@@ -38,9 +38,12 @@ Creates an instance of MultiMatchQuery.
|
|||||||
* **options?**
|
* **options?**
|
||||||
Optional parameters for the multi-match query.
|
Optional parameters for the multi-match query.
|
||||||
- `boosts`: An array of boost factors for each column (default is 1.0 for all).
|
- `boosts`: An array of boost factors for each column (default is 1.0 for all).
|
||||||
|
- `operator`: The logical operator to use for combining terms in the query (default is "OR").
|
||||||
|
|
||||||
* **options.boosts?**: `number`[]
|
* **options.boosts?**: `number`[]
|
||||||
|
|
||||||
|
* **options.operator?**: [`Operator`](../enumerations/Operator.md)
|
||||||
|
|
||||||
#### Returns
|
#### Returns
|
||||||
|
|
||||||
[`MultiMatchQuery`](MultiMatchQuery.md)
|
[`MultiMatchQuery`](MultiMatchQuery.md)
|
||||||
|
|||||||
@@ -19,7 +19,10 @@ including methods to retrieve the query type and convert the query to a dictiona
|
|||||||
### new PhraseQuery()
|
### new PhraseQuery()
|
||||||
|
|
||||||
```ts
|
```ts
|
||||||
new PhraseQuery(query, column): PhraseQuery
|
new PhraseQuery(
|
||||||
|
query,
|
||||||
|
column,
|
||||||
|
options?): PhraseQuery
|
||||||
```
|
```
|
||||||
|
|
||||||
Creates an instance of `PhraseQuery`.
|
Creates an instance of `PhraseQuery`.
|
||||||
@@ -32,6 +35,12 @@ Creates an instance of `PhraseQuery`.
|
|||||||
* **column**: `string`
|
* **column**: `string`
|
||||||
The name of the column to search within.
|
The name of the column to search within.
|
||||||
|
|
||||||
|
* **options?**
|
||||||
|
Optional parameters for the phrase query.
|
||||||
|
- `slop`: The maximum number of intervening unmatched positions allowed between words in the phrase (default is 0).
|
||||||
|
|
||||||
|
* **options.slop?**: `number`
|
||||||
|
|
||||||
#### Returns
|
#### Returns
|
||||||
|
|
||||||
[`PhraseQuery`](PhraseQuery.md)
|
[`PhraseQuery`](PhraseQuery.md)
|
||||||
|
|||||||
@@ -40,7 +40,7 @@ Returns the name of the table
|
|||||||
### add()
|
### add()
|
||||||
|
|
||||||
```ts
|
```ts
|
||||||
abstract add(data, options?): Promise<void>
|
abstract add(data, options?): Promise<AddResult>
|
||||||
```
|
```
|
||||||
|
|
||||||
Insert records into this Table.
|
Insert records into this Table.
|
||||||
@@ -54,14 +54,17 @@ Insert records into this Table.
|
|||||||
|
|
||||||
#### Returns
|
#### Returns
|
||||||
|
|
||||||
`Promise`<`void`>
|
`Promise`<[`AddResult`](../interfaces/AddResult.md)>
|
||||||
|
|
||||||
|
A promise that resolves to an object
|
||||||
|
containing the new version number of the table
|
||||||
|
|
||||||
***
|
***
|
||||||
|
|
||||||
### addColumns()
|
### addColumns()
|
||||||
|
|
||||||
```ts
|
```ts
|
||||||
abstract addColumns(newColumnTransforms): Promise<void>
|
abstract addColumns(newColumnTransforms): Promise<AddColumnsResult>
|
||||||
```
|
```
|
||||||
|
|
||||||
Add new columns with defined values.
|
Add new columns with defined values.
|
||||||
@@ -76,14 +79,17 @@ Add new columns with defined values.
|
|||||||
|
|
||||||
#### Returns
|
#### Returns
|
||||||
|
|
||||||
`Promise`<`void`>
|
`Promise`<[`AddColumnsResult`](../interfaces/AddColumnsResult.md)>
|
||||||
|
|
||||||
|
A promise that resolves to an object
|
||||||
|
containing the new version number of the table after adding the columns.
|
||||||
|
|
||||||
***
|
***
|
||||||
|
|
||||||
### alterColumns()
|
### alterColumns()
|
||||||
|
|
||||||
```ts
|
```ts
|
||||||
abstract alterColumns(columnAlterations): Promise<void>
|
abstract alterColumns(columnAlterations): Promise<AlterColumnsResult>
|
||||||
```
|
```
|
||||||
|
|
||||||
Alter the name or nullability of columns.
|
Alter the name or nullability of columns.
|
||||||
@@ -96,7 +102,10 @@ Alter the name or nullability of columns.
|
|||||||
|
|
||||||
#### Returns
|
#### Returns
|
||||||
|
|
||||||
`Promise`<`void`>
|
`Promise`<[`AlterColumnsResult`](../interfaces/AlterColumnsResult.md)>
|
||||||
|
|
||||||
|
A promise that resolves to an object
|
||||||
|
containing the new version number of the table after altering the columns.
|
||||||
|
|
||||||
***
|
***
|
||||||
|
|
||||||
@@ -252,7 +261,7 @@ await table.createIndex("my_float_col");
|
|||||||
### delete()
|
### delete()
|
||||||
|
|
||||||
```ts
|
```ts
|
||||||
abstract delete(predicate): Promise<void>
|
abstract delete(predicate): Promise<DeleteResult>
|
||||||
```
|
```
|
||||||
|
|
||||||
Delete the rows that satisfy the predicate.
|
Delete the rows that satisfy the predicate.
|
||||||
@@ -263,7 +272,10 @@ Delete the rows that satisfy the predicate.
|
|||||||
|
|
||||||
#### Returns
|
#### Returns
|
||||||
|
|
||||||
`Promise`<`void`>
|
`Promise`<[`DeleteResult`](../interfaces/DeleteResult.md)>
|
||||||
|
|
||||||
|
A promise that resolves to an object
|
||||||
|
containing the new version number of the table
|
||||||
|
|
||||||
***
|
***
|
||||||
|
|
||||||
@@ -284,7 +296,7 @@ Return a brief description of the table
|
|||||||
### dropColumns()
|
### dropColumns()
|
||||||
|
|
||||||
```ts
|
```ts
|
||||||
abstract dropColumns(columnNames): Promise<void>
|
abstract dropColumns(columnNames): Promise<DropColumnsResult>
|
||||||
```
|
```
|
||||||
|
|
||||||
Drop one or more columns from the dataset
|
Drop one or more columns from the dataset
|
||||||
@@ -303,7 +315,10 @@ then call ``cleanup_files`` to remove the old files.
|
|||||||
|
|
||||||
#### Returns
|
#### Returns
|
||||||
|
|
||||||
`Promise`<`void`>
|
`Promise`<[`DropColumnsResult`](../interfaces/DropColumnsResult.md)>
|
||||||
|
|
||||||
|
A promise that resolves to an object
|
||||||
|
containing the new version number of the table after dropping the columns.
|
||||||
|
|
||||||
***
|
***
|
||||||
|
|
||||||
@@ -678,7 +693,7 @@ Return the table as an arrow table
|
|||||||
#### update(opts)
|
#### update(opts)
|
||||||
|
|
||||||
```ts
|
```ts
|
||||||
abstract update(opts): Promise<void>
|
abstract update(opts): Promise<UpdateResult>
|
||||||
```
|
```
|
||||||
|
|
||||||
Update existing records in the Table
|
Update existing records in the Table
|
||||||
@@ -689,7 +704,10 @@ Update existing records in the Table
|
|||||||
|
|
||||||
##### Returns
|
##### Returns
|
||||||
|
|
||||||
`Promise`<`void`>
|
`Promise`<[`UpdateResult`](../interfaces/UpdateResult.md)>
|
||||||
|
|
||||||
|
A promise that resolves to an object containing
|
||||||
|
the number of rows updated and the new version number
|
||||||
|
|
||||||
##### Example
|
##### Example
|
||||||
|
|
||||||
@@ -700,7 +718,7 @@ table.update({where:"x = 2", values:{"vector": [10, 10]}})
|
|||||||
#### update(opts)
|
#### update(opts)
|
||||||
|
|
||||||
```ts
|
```ts
|
||||||
abstract update(opts): Promise<void>
|
abstract update(opts): Promise<UpdateResult>
|
||||||
```
|
```
|
||||||
|
|
||||||
Update existing records in the Table
|
Update existing records in the Table
|
||||||
@@ -711,7 +729,10 @@ Update existing records in the Table
|
|||||||
|
|
||||||
##### Returns
|
##### Returns
|
||||||
|
|
||||||
`Promise`<`void`>
|
`Promise`<[`UpdateResult`](../interfaces/UpdateResult.md)>
|
||||||
|
|
||||||
|
A promise that resolves to an object containing
|
||||||
|
the number of rows updated and the new version number
|
||||||
|
|
||||||
##### Example
|
##### Example
|
||||||
|
|
||||||
@@ -722,7 +743,7 @@ table.update({where:"x = 2", valuesSql:{"x": "x + 1"}})
|
|||||||
#### update(updates, options)
|
#### update(updates, options)
|
||||||
|
|
||||||
```ts
|
```ts
|
||||||
abstract update(updates, options?): Promise<void>
|
abstract update(updates, options?): Promise<UpdateResult>
|
||||||
```
|
```
|
||||||
|
|
||||||
Update existing records in the Table
|
Update existing records in the Table
|
||||||
@@ -745,10 +766,6 @@ repeatedly calilng this method.
|
|||||||
* **updates**: `Record`<`string`, `string`> \| `Map`<`string`, `string`>
|
* **updates**: `Record`<`string`, `string`> \| `Map`<`string`, `string`>
|
||||||
the
|
the
|
||||||
columns to update
|
columns to update
|
||||||
Keys in the map should specify the name of the column to update.
|
|
||||||
Values in the map provide the new value of the column. These can
|
|
||||||
be SQL literal strings (e.g. "7" or "'foo'") or they can be expressions
|
|
||||||
based on the row being updated (e.g. "my_col + 1")
|
|
||||||
|
|
||||||
* **options?**: `Partial`<[`UpdateOptions`](../interfaces/UpdateOptions.md)>
|
* **options?**: `Partial`<[`UpdateOptions`](../interfaces/UpdateOptions.md)>
|
||||||
additional options to control
|
additional options to control
|
||||||
@@ -756,7 +773,15 @@ repeatedly calilng this method.
|
|||||||
|
|
||||||
##### Returns
|
##### Returns
|
||||||
|
|
||||||
`Promise`<`void`>
|
`Promise`<[`UpdateResult`](../interfaces/UpdateResult.md)>
|
||||||
|
|
||||||
|
A promise that resolves to an object
|
||||||
|
containing the number of rows updated and the new version number
|
||||||
|
|
||||||
|
Keys in the map should specify the name of the column to update.
|
||||||
|
Values in the map provide the new value of the column. These can
|
||||||
|
be SQL literal strings (e.g. "7" or "'foo'") or they can be expressions
|
||||||
|
based on the row being updated (e.g. "my_col + 1")
|
||||||
|
|
||||||
***
|
***
|
||||||
|
|
||||||
|
|||||||
@@ -15,6 +15,14 @@ Enum representing the types of full-text queries supported.
|
|||||||
|
|
||||||
## Enumeration Members
|
## Enumeration Members
|
||||||
|
|
||||||
|
### Boolean
|
||||||
|
|
||||||
|
```ts
|
||||||
|
Boolean: "boolean";
|
||||||
|
```
|
||||||
|
|
||||||
|
***
|
||||||
|
|
||||||
### Boost
|
### Boost
|
||||||
|
|
||||||
```ts
|
```ts
|
||||||
|
|||||||
28
docs/src/js/enumerations/Occur.md
Normal file
28
docs/src/js/enumerations/Occur.md
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
[**@lancedb/lancedb**](../README.md) • **Docs**
|
||||||
|
|
||||||
|
***
|
||||||
|
|
||||||
|
[@lancedb/lancedb](../globals.md) / Occur
|
||||||
|
|
||||||
|
# Enumeration: Occur
|
||||||
|
|
||||||
|
Enum representing the occurrence of terms in full-text queries.
|
||||||
|
|
||||||
|
- `Must`: The term must be present in the document.
|
||||||
|
- `Should`: The term should contribute to the document score, but is not required.
|
||||||
|
|
||||||
|
## Enumeration Members
|
||||||
|
|
||||||
|
### Must
|
||||||
|
|
||||||
|
```ts
|
||||||
|
Must: "MUST";
|
||||||
|
```
|
||||||
|
|
||||||
|
***
|
||||||
|
|
||||||
|
### Should
|
||||||
|
|
||||||
|
```ts
|
||||||
|
Should: "SHOULD";
|
||||||
|
```
|
||||||
28
docs/src/js/enumerations/Operator.md
Normal file
28
docs/src/js/enumerations/Operator.md
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
[**@lancedb/lancedb**](../README.md) • **Docs**
|
||||||
|
|
||||||
|
***
|
||||||
|
|
||||||
|
[@lancedb/lancedb](../globals.md) / Operator
|
||||||
|
|
||||||
|
# Enumeration: Operator
|
||||||
|
|
||||||
|
Enum representing the logical operators used in full-text queries.
|
||||||
|
|
||||||
|
- `And`: All terms must match.
|
||||||
|
- `Or`: At least one term must match.
|
||||||
|
|
||||||
|
## Enumeration Members
|
||||||
|
|
||||||
|
### And
|
||||||
|
|
||||||
|
```ts
|
||||||
|
And: "AND";
|
||||||
|
```
|
||||||
|
|
||||||
|
***
|
||||||
|
|
||||||
|
### Or
|
||||||
|
|
||||||
|
```ts
|
||||||
|
Or: "OR";
|
||||||
|
```
|
||||||
@@ -12,9 +12,12 @@
|
|||||||
## Enumerations
|
## Enumerations
|
||||||
|
|
||||||
- [FullTextQueryType](enumerations/FullTextQueryType.md)
|
- [FullTextQueryType](enumerations/FullTextQueryType.md)
|
||||||
|
- [Occur](enumerations/Occur.md)
|
||||||
|
- [Operator](enumerations/Operator.md)
|
||||||
|
|
||||||
## Classes
|
## Classes
|
||||||
|
|
||||||
|
- [BooleanQuery](classes/BooleanQuery.md)
|
||||||
- [BoostQuery](classes/BoostQuery.md)
|
- [BoostQuery](classes/BoostQuery.md)
|
||||||
- [Connection](classes/Connection.md)
|
- [Connection](classes/Connection.md)
|
||||||
- [Index](classes/Index.md)
|
- [Index](classes/Index.md)
|
||||||
@@ -34,13 +37,18 @@
|
|||||||
|
|
||||||
## Interfaces
|
## Interfaces
|
||||||
|
|
||||||
|
- [AddColumnsResult](interfaces/AddColumnsResult.md)
|
||||||
- [AddColumnsSql](interfaces/AddColumnsSql.md)
|
- [AddColumnsSql](interfaces/AddColumnsSql.md)
|
||||||
- [AddDataOptions](interfaces/AddDataOptions.md)
|
- [AddDataOptions](interfaces/AddDataOptions.md)
|
||||||
|
- [AddResult](interfaces/AddResult.md)
|
||||||
|
- [AlterColumnsResult](interfaces/AlterColumnsResult.md)
|
||||||
- [ClientConfig](interfaces/ClientConfig.md)
|
- [ClientConfig](interfaces/ClientConfig.md)
|
||||||
- [ColumnAlteration](interfaces/ColumnAlteration.md)
|
- [ColumnAlteration](interfaces/ColumnAlteration.md)
|
||||||
- [CompactionStats](interfaces/CompactionStats.md)
|
- [CompactionStats](interfaces/CompactionStats.md)
|
||||||
- [ConnectionOptions](interfaces/ConnectionOptions.md)
|
- [ConnectionOptions](interfaces/ConnectionOptions.md)
|
||||||
- [CreateTableOptions](interfaces/CreateTableOptions.md)
|
- [CreateTableOptions](interfaces/CreateTableOptions.md)
|
||||||
|
- [DeleteResult](interfaces/DeleteResult.md)
|
||||||
|
- [DropColumnsResult](interfaces/DropColumnsResult.md)
|
||||||
- [ExecutableQuery](interfaces/ExecutableQuery.md)
|
- [ExecutableQuery](interfaces/ExecutableQuery.md)
|
||||||
- [FragmentStatistics](interfaces/FragmentStatistics.md)
|
- [FragmentStatistics](interfaces/FragmentStatistics.md)
|
||||||
- [FragmentSummaryStats](interfaces/FragmentSummaryStats.md)
|
- [FragmentSummaryStats](interfaces/FragmentSummaryStats.md)
|
||||||
@@ -54,7 +62,7 @@
|
|||||||
- [IndexStatistics](interfaces/IndexStatistics.md)
|
- [IndexStatistics](interfaces/IndexStatistics.md)
|
||||||
- [IvfFlatOptions](interfaces/IvfFlatOptions.md)
|
- [IvfFlatOptions](interfaces/IvfFlatOptions.md)
|
||||||
- [IvfPqOptions](interfaces/IvfPqOptions.md)
|
- [IvfPqOptions](interfaces/IvfPqOptions.md)
|
||||||
- [MergeStats](interfaces/MergeStats.md)
|
- [MergeResult](interfaces/MergeResult.md)
|
||||||
- [OpenTableOptions](interfaces/OpenTableOptions.md)
|
- [OpenTableOptions](interfaces/OpenTableOptions.md)
|
||||||
- [OptimizeOptions](interfaces/OptimizeOptions.md)
|
- [OptimizeOptions](interfaces/OptimizeOptions.md)
|
||||||
- [OptimizeStats](interfaces/OptimizeStats.md)
|
- [OptimizeStats](interfaces/OptimizeStats.md)
|
||||||
@@ -65,7 +73,9 @@
|
|||||||
- [TableStatistics](interfaces/TableStatistics.md)
|
- [TableStatistics](interfaces/TableStatistics.md)
|
||||||
- [TimeoutConfig](interfaces/TimeoutConfig.md)
|
- [TimeoutConfig](interfaces/TimeoutConfig.md)
|
||||||
- [UpdateOptions](interfaces/UpdateOptions.md)
|
- [UpdateOptions](interfaces/UpdateOptions.md)
|
||||||
|
- [UpdateResult](interfaces/UpdateResult.md)
|
||||||
- [Version](interfaces/Version.md)
|
- [Version](interfaces/Version.md)
|
||||||
|
- [WriteExecutionOptions](interfaces/WriteExecutionOptions.md)
|
||||||
|
|
||||||
## Type Aliases
|
## Type Aliases
|
||||||
|
|
||||||
|
|||||||
15
docs/src/js/interfaces/AddColumnsResult.md
Normal file
15
docs/src/js/interfaces/AddColumnsResult.md
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
[**@lancedb/lancedb**](../README.md) • **Docs**
|
||||||
|
|
||||||
|
***
|
||||||
|
|
||||||
|
[@lancedb/lancedb](../globals.md) / AddColumnsResult
|
||||||
|
|
||||||
|
# Interface: AddColumnsResult
|
||||||
|
|
||||||
|
## Properties
|
||||||
|
|
||||||
|
### version
|
||||||
|
|
||||||
|
```ts
|
||||||
|
version: number;
|
||||||
|
```
|
||||||
15
docs/src/js/interfaces/AddResult.md
Normal file
15
docs/src/js/interfaces/AddResult.md
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
[**@lancedb/lancedb**](../README.md) • **Docs**
|
||||||
|
|
||||||
|
***
|
||||||
|
|
||||||
|
[@lancedb/lancedb](../globals.md) / AddResult
|
||||||
|
|
||||||
|
# Interface: AddResult
|
||||||
|
|
||||||
|
## Properties
|
||||||
|
|
||||||
|
### version
|
||||||
|
|
||||||
|
```ts
|
||||||
|
version: number;
|
||||||
|
```
|
||||||
15
docs/src/js/interfaces/AlterColumnsResult.md
Normal file
15
docs/src/js/interfaces/AlterColumnsResult.md
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
[**@lancedb/lancedb**](../README.md) • **Docs**
|
||||||
|
|
||||||
|
***
|
||||||
|
|
||||||
|
[@lancedb/lancedb](../globals.md) / AlterColumnsResult
|
||||||
|
|
||||||
|
# Interface: AlterColumnsResult
|
||||||
|
|
||||||
|
## Properties
|
||||||
|
|
||||||
|
### version
|
||||||
|
|
||||||
|
```ts
|
||||||
|
version: number;
|
||||||
|
```
|
||||||
15
docs/src/js/interfaces/DeleteResult.md
Normal file
15
docs/src/js/interfaces/DeleteResult.md
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
[**@lancedb/lancedb**](../README.md) • **Docs**
|
||||||
|
|
||||||
|
***
|
||||||
|
|
||||||
|
[@lancedb/lancedb](../globals.md) / DeleteResult
|
||||||
|
|
||||||
|
# Interface: DeleteResult
|
||||||
|
|
||||||
|
## Properties
|
||||||
|
|
||||||
|
### version
|
||||||
|
|
||||||
|
```ts
|
||||||
|
version: number;
|
||||||
|
```
|
||||||
15
docs/src/js/interfaces/DropColumnsResult.md
Normal file
15
docs/src/js/interfaces/DropColumnsResult.md
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
[**@lancedb/lancedb**](../README.md) • **Docs**
|
||||||
|
|
||||||
|
***
|
||||||
|
|
||||||
|
[@lancedb/lancedb](../globals.md) / DropColumnsResult
|
||||||
|
|
||||||
|
# Interface: DropColumnsResult
|
||||||
|
|
||||||
|
## Properties
|
||||||
|
|
||||||
|
### version
|
||||||
|
|
||||||
|
```ts
|
||||||
|
version: number;
|
||||||
|
```
|
||||||
39
docs/src/js/interfaces/MergeResult.md
Normal file
39
docs/src/js/interfaces/MergeResult.md
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
[**@lancedb/lancedb**](../README.md) • **Docs**
|
||||||
|
|
||||||
|
***
|
||||||
|
|
||||||
|
[@lancedb/lancedb](../globals.md) / MergeResult
|
||||||
|
|
||||||
|
# Interface: MergeResult
|
||||||
|
|
||||||
|
## Properties
|
||||||
|
|
||||||
|
### numDeletedRows
|
||||||
|
|
||||||
|
```ts
|
||||||
|
numDeletedRows: number;
|
||||||
|
```
|
||||||
|
|
||||||
|
***
|
||||||
|
|
||||||
|
### numInsertedRows
|
||||||
|
|
||||||
|
```ts
|
||||||
|
numInsertedRows: number;
|
||||||
|
```
|
||||||
|
|
||||||
|
***
|
||||||
|
|
||||||
|
### numUpdatedRows
|
||||||
|
|
||||||
|
```ts
|
||||||
|
numUpdatedRows: number;
|
||||||
|
```
|
||||||
|
|
||||||
|
***
|
||||||
|
|
||||||
|
### version
|
||||||
|
|
||||||
|
```ts
|
||||||
|
version: number;
|
||||||
|
```
|
||||||
@@ -1,31 +0,0 @@
|
|||||||
[**@lancedb/lancedb**](../README.md) • **Docs**
|
|
||||||
|
|
||||||
***
|
|
||||||
|
|
||||||
[@lancedb/lancedb](../globals.md) / MergeStats
|
|
||||||
|
|
||||||
# Interface: MergeStats
|
|
||||||
|
|
||||||
## Properties
|
|
||||||
|
|
||||||
### numDeletedRows
|
|
||||||
|
|
||||||
```ts
|
|
||||||
numDeletedRows: bigint;
|
|
||||||
```
|
|
||||||
|
|
||||||
***
|
|
||||||
|
|
||||||
### numInsertedRows
|
|
||||||
|
|
||||||
```ts
|
|
||||||
numInsertedRows: bigint;
|
|
||||||
```
|
|
||||||
|
|
||||||
***
|
|
||||||
|
|
||||||
### numUpdatedRows
|
|
||||||
|
|
||||||
```ts
|
|
||||||
numUpdatedRows: bigint;
|
|
||||||
```
|
|
||||||
23
docs/src/js/interfaces/UpdateResult.md
Normal file
23
docs/src/js/interfaces/UpdateResult.md
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
[**@lancedb/lancedb**](../README.md) • **Docs**
|
||||||
|
|
||||||
|
***
|
||||||
|
|
||||||
|
[@lancedb/lancedb](../globals.md) / UpdateResult
|
||||||
|
|
||||||
|
# Interface: UpdateResult
|
||||||
|
|
||||||
|
## Properties
|
||||||
|
|
||||||
|
### rowsUpdated
|
||||||
|
|
||||||
|
```ts
|
||||||
|
rowsUpdated: number;
|
||||||
|
```
|
||||||
|
|
||||||
|
***
|
||||||
|
|
||||||
|
### version
|
||||||
|
|
||||||
|
```ts
|
||||||
|
version: number;
|
||||||
|
```
|
||||||
26
docs/src/js/interfaces/WriteExecutionOptions.md
Normal file
26
docs/src/js/interfaces/WriteExecutionOptions.md
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
[**@lancedb/lancedb**](../README.md) • **Docs**
|
||||||
|
|
||||||
|
***
|
||||||
|
|
||||||
|
[@lancedb/lancedb](../globals.md) / WriteExecutionOptions
|
||||||
|
|
||||||
|
# Interface: WriteExecutionOptions
|
||||||
|
|
||||||
|
## Properties
|
||||||
|
|
||||||
|
### timeoutMs?
|
||||||
|
|
||||||
|
```ts
|
||||||
|
optional timeoutMs: number;
|
||||||
|
```
|
||||||
|
|
||||||
|
Maximum time to run the operation before cancelling it.
|
||||||
|
|
||||||
|
By default, there is a 30-second timeout that is only enforced after the
|
||||||
|
first attempt. This is to prevent spending too long retrying to resolve
|
||||||
|
conflicts. For example, if a write attempt takes 20 seconds and fails,
|
||||||
|
the second attempt will be cancelled after 10 seconds, hitting the
|
||||||
|
30-second timeout. However, a write that takes one hour and succeeds on the
|
||||||
|
first attempt will not be cancelled.
|
||||||
|
|
||||||
|
When this is set, the timeout is enforced on all attempts, including the first.
|
||||||
53
docs/src/python/datafusion.md
Normal file
53
docs/src/python/datafusion.md
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
# Apache Datafusion
|
||||||
|
|
||||||
|
In Python, LanceDB tables can also be queried with [Apache Datafusion](https://datafusion.apache.org/), an extensible query engine written in Rust that uses Apache Arrow as its in-memory format. This means you can write complex SQL queries to analyze your data in LanceDB.
|
||||||
|
|
||||||
|
This integration is done via [Datafusion FFI](https://docs.rs/datafusion-ffi/latest/datafusion_ffi/), which provides a native integration between LanceDB and Datafusion.
|
||||||
|
The Datafusion FFI allows to pass down column selections and basic filters to LanceDB, reducing the amount of scanned data when executing your query. Additionally, the integration allows streaming data from LanceDB tables which allows to do aggregation larger-than-memory.
|
||||||
|
|
||||||
|
We can demonstrate this by first installing `datafusion` and `lancedb`.
|
||||||
|
|
||||||
|
```shell
|
||||||
|
pip install datafusion lancedb
|
||||||
|
```
|
||||||
|
|
||||||
|
We will re-use the dataset [created previously](./pandas_and_pyarrow.md):
|
||||||
|
|
||||||
|
```python
|
||||||
|
import lancedb
|
||||||
|
|
||||||
|
from datafusion import SessionContext
|
||||||
|
from lance import FFILanceTableProvider
|
||||||
|
|
||||||
|
db = lancedb.connect("data/sample-lancedb")
|
||||||
|
data = [
|
||||||
|
{"vector": [3.1, 4.1], "item": "foo", "price": 10.0},
|
||||||
|
{"vector": [5.9, 26.5], "item": "bar", "price": 20.0}
|
||||||
|
]
|
||||||
|
lance_table = db.create_table("lance_table", data)
|
||||||
|
|
||||||
|
ctx = SessionContext()
|
||||||
|
|
||||||
|
ffi_lance_table = FFILanceTableProvider(
|
||||||
|
lance_table.to_lance(), with_row_id=True, with_row_addr=True
|
||||||
|
)
|
||||||
|
ctx.register_table_provider("ffi_lance_table", ffi_lance_table)
|
||||||
|
```
|
||||||
|
|
||||||
|
The `to_lance` method converts the LanceDB table to a `LanceDataset`, which is accessible to Datafusion through the Datafusion FFI integration layer.
|
||||||
|
To query the resulting Lance dataset in Datafusion, you first need to register the dataset with Datafusion and then just reference it by the same name in your SQL query.
|
||||||
|
|
||||||
|
```python
|
||||||
|
ctx.table("ffi_lance_table")
|
||||||
|
ctx.sql("SELECT * FROM ffi_lance_table")
|
||||||
|
```
|
||||||
|
|
||||||
|
```
|
||||||
|
┌─────────────┬─────────┬────────┬─────────────────┬─────────────────┐
|
||||||
|
│ vector │ item │ price │ _rowid │ _rowaddr │
|
||||||
|
│ float[] │ varchar │ double │ bigint unsigned │ bigint unsigned │
|
||||||
|
├─────────────┼─────────┼────────┼─────────────────┼─────────────────┤
|
||||||
|
│ [3.1, 4.1] │ foo │ 10.0 │ 0 │ 0 │
|
||||||
|
│ [5.9, 26.5] │ bar │ 20.0 │ 1 │ 1 │
|
||||||
|
└─────────────┴─────────┴────────┴─────────────────┴─────────────────┘
|
||||||
|
```
|
||||||
@@ -7,3 +7,4 @@ tantivy==0.20.1
|
|||||||
--extra-index-url https://download.pytorch.org/whl/cpu
|
--extra-index-url https://download.pytorch.org/whl/cpu
|
||||||
torch
|
torch
|
||||||
polars>=0.19, <=1.3.0
|
polars>=0.19, <=1.3.0
|
||||||
|
datafusion
|
||||||
|
|||||||
@@ -8,7 +8,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<groupId>com.lancedb</groupId>
|
<groupId>com.lancedb</groupId>
|
||||||
<artifactId>lancedb-parent</artifactId>
|
<artifactId>lancedb-parent</artifactId>
|
||||||
<version>0.19.1-beta.1</version>
|
<version>0.21.0-final.0</version>
|
||||||
<relativePath>../pom.xml</relativePath>
|
<relativePath>../pom.xml</relativePath>
|
||||||
</parent>
|
</parent>
|
||||||
|
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
|
|
||||||
<groupId>com.lancedb</groupId>
|
<groupId>com.lancedb</groupId>
|
||||||
<artifactId>lancedb-parent</artifactId>
|
<artifactId>lancedb-parent</artifactId>
|
||||||
<version>0.19.1-beta.1</version>
|
<version>0.21.0-final.0</version>
|
||||||
<packaging>pom</packaging>
|
<packaging>pom</packaging>
|
||||||
|
|
||||||
<name>LanceDB Parent</name>
|
<name>LanceDB Parent</name>
|
||||||
|
|||||||
49
node/package-lock.json
generated
49
node/package-lock.json
generated
@@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "vectordb",
|
"name": "vectordb",
|
||||||
"version": "0.19.1-beta.1",
|
"version": "0.20.1-beta.2",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "vectordb",
|
"name": "vectordb",
|
||||||
"version": "0.19.1-beta.1",
|
"version": "0.20.1-beta.2",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"x64",
|
"x64",
|
||||||
"arm64"
|
"arm64"
|
||||||
@@ -52,11 +52,11 @@
|
|||||||
"uuid": "^9.0.0"
|
"uuid": "^9.0.0"
|
||||||
},
|
},
|
||||||
"optionalDependencies": {
|
"optionalDependencies": {
|
||||||
"@lancedb/vectordb-darwin-arm64": "0.19.1-beta.1",
|
"@lancedb/vectordb-darwin-arm64": "0.20.1-beta.2",
|
||||||
"@lancedb/vectordb-darwin-x64": "0.19.1-beta.1",
|
"@lancedb/vectordb-darwin-x64": "0.20.1-beta.2",
|
||||||
"@lancedb/vectordb-linux-arm64-gnu": "0.19.1-beta.1",
|
"@lancedb/vectordb-linux-arm64-gnu": "0.20.1-beta.2",
|
||||||
"@lancedb/vectordb-linux-x64-gnu": "0.19.1-beta.1",
|
"@lancedb/vectordb-linux-x64-gnu": "0.20.1-beta.2",
|
||||||
"@lancedb/vectordb-win32-x64-msvc": "0.19.1-beta.1"
|
"@lancedb/vectordb-win32-x64-msvc": "0.20.1-beta.2"
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"@apache-arrow/ts": "^14.0.2",
|
"@apache-arrow/ts": "^14.0.2",
|
||||||
@@ -327,65 +327,60 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@lancedb/vectordb-darwin-arm64": {
|
"node_modules/@lancedb/vectordb-darwin-arm64": {
|
||||||
"version": "0.19.1-beta.1",
|
"version": "0.20.1-beta.2",
|
||||||
"resolved": "https://registry.npmjs.org/@lancedb/vectordb-darwin-arm64/-/vectordb-darwin-arm64-0.19.1-beta.1.tgz",
|
"resolved": "https://registry.npmjs.org/@lancedb/vectordb-darwin-arm64/-/vectordb-darwin-arm64-0.20.1-beta.2.tgz",
|
||||||
"integrity": "sha512-Epvel0pF5TM6MtIWQ2KhqezqSSHTL3Wr7a2rGAwz6X/XY23i6DbMPpPs0HyeIDzDrhxNfE3cz3S+SiCA6xpR0g==",
|
"integrity": "sha512-mqi0yI+ZwBTydaDy1FRHAUZwrWS28u6tbHTe1s4uSrmERbVI6PfmoPR+NZWWAp6ZhlseSdl/+yeI4imk11rQSw==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
"license": "Apache-2.0",
|
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
"darwin"
|
"darwin"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"node_modules/@lancedb/vectordb-darwin-x64": {
|
"node_modules/@lancedb/vectordb-darwin-x64": {
|
||||||
"version": "0.19.1-beta.1",
|
"version": "0.20.1-beta.2",
|
||||||
"resolved": "https://registry.npmjs.org/@lancedb/vectordb-darwin-x64/-/vectordb-darwin-x64-0.19.1-beta.1.tgz",
|
"resolved": "https://registry.npmjs.org/@lancedb/vectordb-darwin-x64/-/vectordb-darwin-x64-0.20.1-beta.2.tgz",
|
||||||
"integrity": "sha512-hOiUSlIoISbiXytp46hToi/r6sF5pImAsfbzCsIq8ExDV4TPa8fjbhcIT80vxxOwc2mpSSK4HsVJYod95RSbEQ==",
|
"integrity": "sha512-m8EYYA8JZIeNsJqQsBDUMu6r31/u7FzpjonJ4Y+CjapVl6UdvI65KUkeL2dYrFao++RuIoaiqcm3e7gRgFZpXQ==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
"license": "Apache-2.0",
|
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
"darwin"
|
"darwin"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"node_modules/@lancedb/vectordb-linux-arm64-gnu": {
|
"node_modules/@lancedb/vectordb-linux-arm64-gnu": {
|
||||||
"version": "0.19.1-beta.1",
|
"version": "0.20.1-beta.2",
|
||||||
"resolved": "https://registry.npmjs.org/@lancedb/vectordb-linux-arm64-gnu/-/vectordb-linux-arm64-gnu-0.19.1-beta.1.tgz",
|
"resolved": "https://registry.npmjs.org/@lancedb/vectordb-linux-arm64-gnu/-/vectordb-linux-arm64-gnu-0.20.1-beta.2.tgz",
|
||||||
"integrity": "sha512-/1JhGVDEngwrlM8o2TNW8G6nJ9U/VgHKAORmj/cTA7O30helJIoo9jfvUAUy+vZ4VoEwRXQbMI+gaYTg0l3MTg==",
|
"integrity": "sha512-3Og2+bk4GlWmMO1Yg2HBfeb5zrOMLaIHD7bEqQ4+6yw4IckAaV+ke05H0tyyqmOVrOQ0LpvtXgD7pPztjm9r9A==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
"license": "Apache-2.0",
|
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
"linux"
|
"linux"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"node_modules/@lancedb/vectordb-linux-x64-gnu": {
|
"node_modules/@lancedb/vectordb-linux-x64-gnu": {
|
||||||
"version": "0.19.1-beta.1",
|
"version": "0.20.1-beta.2",
|
||||||
"resolved": "https://registry.npmjs.org/@lancedb/vectordb-linux-x64-gnu/-/vectordb-linux-x64-gnu-0.19.1-beta.1.tgz",
|
"resolved": "https://registry.npmjs.org/@lancedb/vectordb-linux-x64-gnu/-/vectordb-linux-x64-gnu-0.20.1-beta.2.tgz",
|
||||||
"integrity": "sha512-zNRGSSUt8nTJMmll4NdxhQjwxR8Rezq3T4dsRoiDts5ienMam5HFjYiZ3FkDZQo16rgq2BcbFuH1G8u1chywlg==",
|
"integrity": "sha512-mwTQyA/FBoU/FkPuvCNBZG3y83gBN+iYoejehBH2HBkLUIcmlsDgSRZ1OQ+f9ijj12EMBCA11tBUPA9zhHzyrw==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
"license": "Apache-2.0",
|
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
"linux"
|
"linux"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"node_modules/@lancedb/vectordb-win32-x64-msvc": {
|
"node_modules/@lancedb/vectordb-win32-x64-msvc": {
|
||||||
"version": "0.19.1-beta.1",
|
"version": "0.20.1-beta.2",
|
||||||
"resolved": "https://registry.npmjs.org/@lancedb/vectordb-win32-x64-msvc/-/vectordb-win32-x64-msvc-0.19.1-beta.1.tgz",
|
"resolved": "https://registry.npmjs.org/@lancedb/vectordb-win32-x64-msvc/-/vectordb-win32-x64-msvc-0.20.1-beta.2.tgz",
|
||||||
"integrity": "sha512-yV550AJGlsIFdm1KoHQPJ1TZx121ZXCIdebBtBZj3wOObIhyB/i0kZAtGvwjkmr7EYyfzt1EHZzbjSGVdehIAA==",
|
"integrity": "sha512-VkjNpqhK3l3uHLLPmox+HrmKPMaZgV+qsGQWx0nfseGnSOEmXAWZWQFe0APVCQ9y0xTypQB0oH7eSOPZv2t4WQ==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
"license": "Apache-2.0",
|
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
"win32"
|
"win32"
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "vectordb",
|
"name": "vectordb",
|
||||||
"version": "0.19.1-beta.1",
|
"version": "0.21.0",
|
||||||
"description": " Serverless, low-latency vector database for AI applications",
|
"description": " Serverless, low-latency vector database for AI applications",
|
||||||
"private": false,
|
"private": false,
|
||||||
"main": "dist/index.js",
|
"main": "dist/index.js",
|
||||||
@@ -89,10 +89,10 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"optionalDependencies": {
|
"optionalDependencies": {
|
||||||
"@lancedb/vectordb-darwin-x64": "0.19.1-beta.1",
|
"@lancedb/vectordb-darwin-x64": "0.21.0",
|
||||||
"@lancedb/vectordb-darwin-arm64": "0.19.1-beta.1",
|
"@lancedb/vectordb-darwin-arm64": "0.21.0",
|
||||||
"@lancedb/vectordb-linux-x64-gnu": "0.19.1-beta.1",
|
"@lancedb/vectordb-linux-x64-gnu": "0.21.0",
|
||||||
"@lancedb/vectordb-linux-arm64-gnu": "0.19.1-beta.1",
|
"@lancedb/vectordb-linux-arm64-gnu": "0.21.0",
|
||||||
"@lancedb/vectordb-win32-x64-msvc": "0.19.1-beta.1"
|
"@lancedb/vectordb-win32-x64-msvc": "0.21.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "lancedb-nodejs"
|
name = "lancedb-nodejs"
|
||||||
edition.workspace = true
|
edition.workspace = true
|
||||||
version = "0.19.1-beta.1"
|
version = "0.21.0"
|
||||||
license.workspace = true
|
license.workspace = true
|
||||||
description.workspace = true
|
description.workspace = true
|
||||||
repository.workspace = true
|
repository.workspace = true
|
||||||
@@ -30,6 +30,7 @@ log.workspace = true
|
|||||||
|
|
||||||
# Workaround for build failure until we can fix it.
|
# Workaround for build failure until we can fix it.
|
||||||
aws-lc-sys = "=0.28.0"
|
aws-lc-sys = "=0.28.0"
|
||||||
|
aws-lc-rs = "=1.13.0"
|
||||||
|
|
||||||
[build-dependencies]
|
[build-dependencies]
|
||||||
napi-build = "2.1"
|
napi-build = "2.1"
|
||||||
|
|||||||
@@ -592,14 +592,14 @@ describe.each([arrow15, arrow16, arrow17, arrow18])(
|
|||||||
).rejects.toThrow("column vector was missing");
|
).rejects.toThrow("column vector was missing");
|
||||||
});
|
});
|
||||||
|
|
||||||
it("will provide a nice error if run twice", async function () {
|
it("will skip embedding application if already applied", async function () {
|
||||||
const records = sampleRecords();
|
const records = sampleRecords();
|
||||||
const table = await convertToTable(records, dummyEmbeddingConfig);
|
const table = await convertToTable(records, dummyEmbeddingConfig);
|
||||||
|
|
||||||
// fromTableToBuffer will try and apply the embeddings again
|
// fromTableToBuffer will try and apply the embeddings again
|
||||||
await expect(
|
// but should skip since the column already has non-null values
|
||||||
fromTableToBuffer(table, dummyEmbeddingConfig),
|
const result = await fromTableToBuffer(table, dummyEmbeddingConfig);
|
||||||
).rejects.toThrow("already existed");
|
expect(result.byteLength).toBeGreaterThan(0);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -33,7 +33,13 @@ import {
|
|||||||
register,
|
register,
|
||||||
} from "../lancedb/embedding";
|
} from "../lancedb/embedding";
|
||||||
import { Index } from "../lancedb/indices";
|
import { Index } from "../lancedb/indices";
|
||||||
import { instanceOfFullTextQuery } from "../lancedb/query";
|
import {
|
||||||
|
BooleanQuery,
|
||||||
|
Occur,
|
||||||
|
Operator,
|
||||||
|
instanceOfFullTextQuery,
|
||||||
|
} from "../lancedb/query";
|
||||||
|
import exp = require("constants");
|
||||||
|
|
||||||
describe.each([arrow15, arrow16, arrow17, arrow18])(
|
describe.each([arrow15, arrow16, arrow17, arrow18])(
|
||||||
"Given a table",
|
"Given a table",
|
||||||
@@ -95,7 +101,9 @@ describe.each([arrow15, arrow16, arrow17, arrow18])(
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("should overwrite data if asked", async () => {
|
it("should overwrite data if asked", async () => {
|
||||||
await table.add([{ id: 1 }, { id: 2 }]);
|
const addRes = await table.add([{ id: 1 }, { id: 2 }]);
|
||||||
|
expect(addRes).toHaveProperty("version");
|
||||||
|
expect(addRes.version).toBe(2);
|
||||||
await table.add([{ id: 1 }], { mode: "overwrite" });
|
await table.add([{ id: 1 }], { mode: "overwrite" });
|
||||||
await expect(table.countRows()).resolves.toBe(1);
|
await expect(table.countRows()).resolves.toBe(1);
|
||||||
});
|
});
|
||||||
@@ -111,7 +119,11 @@ describe.each([arrow15, arrow16, arrow17, arrow18])(
|
|||||||
await table.add([{ id: 1 }]);
|
await table.add([{ id: 1 }]);
|
||||||
expect(await table.countRows("id == 1")).toBe(1);
|
expect(await table.countRows("id == 1")).toBe(1);
|
||||||
expect(await table.countRows("id == 7")).toBe(0);
|
expect(await table.countRows("id == 7")).toBe(0);
|
||||||
await table.update({ id: "7" });
|
const updateRes = await table.update({ id: "7" });
|
||||||
|
expect(updateRes).toHaveProperty("version");
|
||||||
|
expect(updateRes.version).toBe(3);
|
||||||
|
expect(updateRes).toHaveProperty("rowsUpdated");
|
||||||
|
expect(updateRes.rowsUpdated).toBe(1);
|
||||||
expect(await table.countRows("id == 1")).toBe(0);
|
expect(await table.countRows("id == 1")).toBe(0);
|
||||||
expect(await table.countRows("id == 7")).toBe(1);
|
expect(await table.countRows("id == 7")).toBe(1);
|
||||||
await table.add([{ id: 2 }]);
|
await table.add([{ id: 2 }]);
|
||||||
@@ -338,15 +350,16 @@ describe("merge insert", () => {
|
|||||||
{ a: 3, b: "y" },
|
{ a: 3, b: "y" },
|
||||||
{ a: 4, b: "z" },
|
{ a: 4, b: "z" },
|
||||||
];
|
];
|
||||||
const stats = await table
|
const mergeInsertRes = await table
|
||||||
.mergeInsert("a")
|
.mergeInsert("a")
|
||||||
.whenMatchedUpdateAll()
|
.whenMatchedUpdateAll()
|
||||||
.whenNotMatchedInsertAll()
|
.whenNotMatchedInsertAll()
|
||||||
.execute(newData);
|
.execute(newData, { timeoutMs: 10_000 });
|
||||||
|
expect(mergeInsertRes).toHaveProperty("version");
|
||||||
expect(stats.numInsertedRows).toBe(1n);
|
expect(mergeInsertRes.version).toBe(2);
|
||||||
expect(stats.numUpdatedRows).toBe(2n);
|
expect(mergeInsertRes.numInsertedRows).toBe(1);
|
||||||
expect(stats.numDeletedRows).toBe(0n);
|
expect(mergeInsertRes.numUpdatedRows).toBe(2);
|
||||||
|
expect(mergeInsertRes.numDeletedRows).toBe(0);
|
||||||
|
|
||||||
const expected = [
|
const expected = [
|
||||||
{ a: 1, b: "a" },
|
{ a: 1, b: "a" },
|
||||||
@@ -365,10 +378,12 @@ describe("merge insert", () => {
|
|||||||
{ a: 3, b: "y" },
|
{ a: 3, b: "y" },
|
||||||
{ a: 4, b: "z" },
|
{ a: 4, b: "z" },
|
||||||
];
|
];
|
||||||
await table
|
const mergeInsertRes = await table
|
||||||
.mergeInsert("a")
|
.mergeInsert("a")
|
||||||
.whenMatchedUpdateAll({ where: "target.b = 'b'" })
|
.whenMatchedUpdateAll({ where: "target.b = 'b'" })
|
||||||
.execute(newData);
|
.execute(newData);
|
||||||
|
expect(mergeInsertRes).toHaveProperty("version");
|
||||||
|
expect(mergeInsertRes.version).toBe(2);
|
||||||
|
|
||||||
const expected = [
|
const expected = [
|
||||||
{ a: 1, b: "a" },
|
{ a: 1, b: "a" },
|
||||||
@@ -453,6 +468,20 @@ describe("merge insert", () => {
|
|||||||
res = res.sort((a, b) => a.a - b.a);
|
res = res.sort((a, b) => a.a - b.a);
|
||||||
expect(res).toEqual(expected);
|
expect(res).toEqual(expected);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test("timeout", async () => {
|
||||||
|
const newData = [
|
||||||
|
{ a: 2, b: "x" },
|
||||||
|
{ a: 4, b: "z" },
|
||||||
|
];
|
||||||
|
await expect(
|
||||||
|
table
|
||||||
|
.mergeInsert("a")
|
||||||
|
.whenMatchedUpdateAll()
|
||||||
|
.whenNotMatchedInsertAll()
|
||||||
|
.execute(newData, { timeoutMs: 0 }),
|
||||||
|
).rejects.toThrow("merge insert timed out");
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("When creating an index", () => {
|
describe("When creating an index", () => {
|
||||||
@@ -530,6 +559,32 @@ describe("When creating an index", () => {
|
|||||||
rst = await tbl.query().limit(2).offset(1).nearestTo(queryVec).toArrow();
|
rst = await tbl.query().limit(2).offset(1).nearestTo(queryVec).toArrow();
|
||||||
expect(rst.numRows).toBe(1);
|
expect(rst.numRows).toBe(1);
|
||||||
|
|
||||||
|
// test nprobes
|
||||||
|
rst = await tbl.query().nearestTo(queryVec).limit(2).nprobes(50).toArrow();
|
||||||
|
expect(rst.numRows).toBe(2);
|
||||||
|
rst = await tbl
|
||||||
|
.query()
|
||||||
|
.nearestTo(queryVec)
|
||||||
|
.limit(2)
|
||||||
|
.minimumNprobes(15)
|
||||||
|
.toArrow();
|
||||||
|
expect(rst.numRows).toBe(2);
|
||||||
|
rst = await tbl
|
||||||
|
.query()
|
||||||
|
.nearestTo(queryVec)
|
||||||
|
.limit(2)
|
||||||
|
.minimumNprobes(10)
|
||||||
|
.maximumNprobes(20)
|
||||||
|
.toArrow();
|
||||||
|
expect(rst.numRows).toBe(2);
|
||||||
|
|
||||||
|
expect(() => tbl.query().nearestTo(queryVec).minimumNprobes(0)).toThrow(
|
||||||
|
"Invalid input, minimum_nprobes must be greater than 0",
|
||||||
|
);
|
||||||
|
expect(() => tbl.query().nearestTo(queryVec).maximumNprobes(5)).toThrow(
|
||||||
|
"Invalid input, maximum_nprobes must be greater than minimum_nprobes",
|
||||||
|
);
|
||||||
|
|
||||||
await tbl.dropIndex("vec_idx");
|
await tbl.dropIndex("vec_idx");
|
||||||
const indices2 = await tbl.listIndices();
|
const indices2 = await tbl.listIndices();
|
||||||
expect(indices2.length).toBe(0);
|
expect(indices2.length).toBe(0);
|
||||||
@@ -1028,15 +1083,19 @@ describe("schema evolution", function () {
|
|||||||
{ id: 1n, vector: [0.1, 0.2] },
|
{ id: 1n, vector: [0.1, 0.2] },
|
||||||
]);
|
]);
|
||||||
// Can create a non-nullable column only through addColumns at the moment.
|
// Can create a non-nullable column only through addColumns at the moment.
|
||||||
await table.addColumns([
|
const addColumnsRes = await table.addColumns([
|
||||||
{ name: "price", valueSql: "cast(10.0 as double)" },
|
{ name: "price", valueSql: "cast(10.0 as double)" },
|
||||||
]);
|
]);
|
||||||
|
expect(addColumnsRes).toHaveProperty("version");
|
||||||
|
expect(addColumnsRes.version).toBe(2);
|
||||||
expect(await table.schema()).toEqual(schema);
|
expect(await table.schema()).toEqual(schema);
|
||||||
|
|
||||||
await table.alterColumns([
|
const alterColumnsRes = await table.alterColumns([
|
||||||
{ path: "id", rename: "new_id" },
|
{ path: "id", rename: "new_id" },
|
||||||
{ path: "price", nullable: true },
|
{ path: "price", nullable: true },
|
||||||
]);
|
]);
|
||||||
|
expect(alterColumnsRes).toHaveProperty("version");
|
||||||
|
expect(alterColumnsRes.version).toBe(3);
|
||||||
|
|
||||||
const expectedSchema = new Schema([
|
const expectedSchema = new Schema([
|
||||||
new Field("new_id", new Int64(), true),
|
new Field("new_id", new Int64(), true),
|
||||||
@@ -1154,7 +1213,9 @@ describe("schema evolution", function () {
|
|||||||
const table = await con.createTable("vectors", [
|
const table = await con.createTable("vectors", [
|
||||||
{ id: 1n, vector: [0.1, 0.2] },
|
{ id: 1n, vector: [0.1, 0.2] },
|
||||||
]);
|
]);
|
||||||
await table.dropColumns(["vector"]);
|
const dropColumnsRes = await table.dropColumns(["vector"]);
|
||||||
|
expect(dropColumnsRes).toHaveProperty("version");
|
||||||
|
expect(dropColumnsRes.version).toBe(2);
|
||||||
|
|
||||||
const expectedSchema = new Schema([new Field("id", new Int64(), true)]);
|
const expectedSchema = new Schema([new Field("id", new Int64(), true)]);
|
||||||
expect(await table.schema()).toEqual(expectedSchema);
|
expect(await table.schema()).toEqual(expectedSchema);
|
||||||
@@ -1271,6 +1332,32 @@ describe("when dealing with tags", () => {
|
|||||||
await table.checkoutLatest();
|
await table.checkoutLatest();
|
||||||
expect(await table.version()).toBe(4);
|
expect(await table.version()).toBe(4);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("can checkout and restore tags", async () => {
|
||||||
|
const conn = await connect(tmpDir.name, {
|
||||||
|
readConsistencyInterval: 0,
|
||||||
|
});
|
||||||
|
|
||||||
|
const table = await conn.createTable("my_table", [
|
||||||
|
{ id: 1n, vector: [0.1, 0.2] },
|
||||||
|
]);
|
||||||
|
expect(await table.version()).toBe(1);
|
||||||
|
expect(await table.countRows()).toBe(1);
|
||||||
|
const tagsManager = await table.tags();
|
||||||
|
const tag1 = "tag1";
|
||||||
|
await tagsManager.create(tag1, 1);
|
||||||
|
await table.add([{ id: 2n, vector: [0.3, 0.4] }]);
|
||||||
|
const tag2 = "tag2";
|
||||||
|
await tagsManager.create(tag2, 2);
|
||||||
|
expect(await table.version()).toBe(2);
|
||||||
|
await table.checkout(tag1);
|
||||||
|
expect(await table.version()).toBe(1);
|
||||||
|
await table.restore();
|
||||||
|
expect(await table.version()).toBe(3);
|
||||||
|
expect(await table.countRows()).toBe(1);
|
||||||
|
await table.add([{ id: 3n, vector: [0.5, 0.6] }]);
|
||||||
|
expect(await table.countRows()).toBe(2);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("when optimizing a dataset", () => {
|
describe("when optimizing a dataset", () => {
|
||||||
@@ -1450,7 +1537,9 @@ describe.each([arrow15, arrow16, arrow17, arrow18])(
|
|||||||
];
|
];
|
||||||
const table = await db.createTable("test", data);
|
const table = await db.createTable("test", data);
|
||||||
await table.createIndex("text", {
|
await table.createIndex("text", {
|
||||||
config: Index.fts(),
|
config: Index.fts({
|
||||||
|
withPosition: true,
|
||||||
|
}),
|
||||||
});
|
});
|
||||||
|
|
||||||
const results = await table.search("lance").toArray();
|
const results = await table.search("lance").toArray();
|
||||||
@@ -1473,6 +1562,18 @@ describe.each([arrow15, arrow16, arrow17, arrow18])(
|
|||||||
|
|
||||||
const results = await table.search("hello").toArray();
|
const results = await table.search("hello").toArray();
|
||||||
expect(results[0].text).toBe(data[0].text);
|
expect(results[0].text).toBe(data[0].text);
|
||||||
|
|
||||||
|
const results2 = await table
|
||||||
|
.search(new MatchQuery("hello world", "text"))
|
||||||
|
.toArray();
|
||||||
|
expect(results2.length).toBe(2);
|
||||||
|
|
||||||
|
const results3 = await table
|
||||||
|
.search(
|
||||||
|
new MatchQuery("hello world", "text", { operator: Operator.And }),
|
||||||
|
)
|
||||||
|
.toArray();
|
||||||
|
expect(results3.length).toBe(1);
|
||||||
});
|
});
|
||||||
|
|
||||||
test("full text search without lowercase", async () => {
|
test("full text search without lowercase", async () => {
|
||||||
@@ -1503,7 +1604,9 @@ describe.each([arrow15, arrow16, arrow17, arrow18])(
|
|||||||
];
|
];
|
||||||
const table = await db.createTable("test", data);
|
const table = await db.createTable("test", data);
|
||||||
await table.createIndex("text", {
|
await table.createIndex("text", {
|
||||||
config: Index.fts(),
|
config: Index.fts({
|
||||||
|
withPosition: true,
|
||||||
|
}),
|
||||||
});
|
});
|
||||||
|
|
||||||
const results = await table.search("world").toArray();
|
const results = await table.search("world").toArray();
|
||||||
@@ -1547,6 +1650,60 @@ describe.each([arrow15, arrow16, arrow17, arrow18])(
|
|||||||
expect(resultSet.has("fob")).toBe(true);
|
expect(resultSet.has("fob")).toBe(true);
|
||||||
expect(resultSet.has("fo")).toBe(true);
|
expect(resultSet.has("fo")).toBe(true);
|
||||||
expect(resultSet.has("food")).toBe(true);
|
expect(resultSet.has("food")).toBe(true);
|
||||||
|
|
||||||
|
const prefixResults = await table
|
||||||
|
.search(
|
||||||
|
new MatchQuery("foo", "text", { fuzziness: 3, prefixLength: 3 }),
|
||||||
|
)
|
||||||
|
.toArray();
|
||||||
|
expect(prefixResults.length).toBe(2);
|
||||||
|
const resultSet2 = new Set(prefixResults.map((r) => r.text));
|
||||||
|
expect(resultSet2.has("foo")).toBe(true);
|
||||||
|
expect(resultSet2.has("food")).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("full text search boolean query", async () => {
|
||||||
|
const db = await connect(tmpDir.name);
|
||||||
|
const data = [
|
||||||
|
{ text: "The cat and dog are playing" },
|
||||||
|
{ text: "The cat is sleeping" },
|
||||||
|
{ text: "The dog is barking" },
|
||||||
|
{ text: "The dog chases the cat" },
|
||||||
|
];
|
||||||
|
const table = await db.createTable("test", data);
|
||||||
|
await table.createIndex("text", {
|
||||||
|
config: Index.fts({ withPosition: false }),
|
||||||
|
});
|
||||||
|
|
||||||
|
const shouldResults = await table
|
||||||
|
.search(
|
||||||
|
new BooleanQuery([
|
||||||
|
[Occur.Should, new MatchQuery("cat", "text")],
|
||||||
|
[Occur.Should, new MatchQuery("dog", "text")],
|
||||||
|
]),
|
||||||
|
)
|
||||||
|
.toArray();
|
||||||
|
expect(shouldResults.length).toBe(4);
|
||||||
|
|
||||||
|
const mustResults = await table
|
||||||
|
.search(
|
||||||
|
new BooleanQuery([
|
||||||
|
[Occur.Must, new MatchQuery("cat", "text")],
|
||||||
|
[Occur.Must, new MatchQuery("dog", "text")],
|
||||||
|
]),
|
||||||
|
)
|
||||||
|
.toArray();
|
||||||
|
expect(mustResults.length).toBe(2);
|
||||||
|
|
||||||
|
const mustNotResults = await table
|
||||||
|
.search(
|
||||||
|
new BooleanQuery([
|
||||||
|
[Occur.Must, new MatchQuery("cat", "text")],
|
||||||
|
[Occur.MustNot, new MatchQuery("dog", "text")],
|
||||||
|
]),
|
||||||
|
)
|
||||||
|
.toArray();
|
||||||
|
expect(mustNotResults.length).toBe(1);
|
||||||
});
|
});
|
||||||
|
|
||||||
test.each([
|
test.each([
|
||||||
|
|||||||
@@ -417,7 +417,9 @@ function inferSchema(
|
|||||||
} else {
|
} else {
|
||||||
const inferredType = inferType(value, path, opts);
|
const inferredType = inferType(value, path, opts);
|
||||||
if (inferredType === undefined) {
|
if (inferredType === undefined) {
|
||||||
throw new Error(`Failed to infer data type for field ${path.join(".")} at row ${rowI}. \
|
throw new Error(`Failed to infer data type for field ${path.join(
|
||||||
|
".",
|
||||||
|
)} at row ${rowI}. \
|
||||||
Consider providing an explicit schema.`);
|
Consider providing an explicit schema.`);
|
||||||
}
|
}
|
||||||
pathTree.set(path, inferredType);
|
pathTree.set(path, inferredType);
|
||||||
@@ -799,11 +801,17 @@ async function applyEmbeddingsFromMetadata(
|
|||||||
`Cannot apply embedding function because the source column '${functionEntry.sourceColumn}' was not present in the data`,
|
`Cannot apply embedding function because the source column '${functionEntry.sourceColumn}' was not present in the data`,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Check if destination column exists and handle accordingly
|
||||||
if (columns[destColumn] !== undefined) {
|
if (columns[destColumn] !== undefined) {
|
||||||
throw new Error(
|
const existingColumn = columns[destColumn];
|
||||||
`Attempt to apply embeddings to table failed because column ${destColumn} already existed`,
|
// If the column exists but is all null, we can fill it with embeddings
|
||||||
);
|
if (existingColumn.nullCount !== existingColumn.length) {
|
||||||
|
// Column has non-null values, skip embedding application
|
||||||
|
continue;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (table.batches.length > 1) {
|
if (table.batches.length > 1) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
"Internal error: `makeArrowTable` unexpectedly created a table with more than one batch",
|
"Internal error: `makeArrowTable` unexpectedly created a table with more than one batch",
|
||||||
@@ -903,11 +911,23 @@ async function applyEmbeddings<T>(
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
// Check if destination column exists and handle accordingly
|
||||||
if (Object.prototype.hasOwnProperty.call(newColumns, destColumn)) {
|
if (Object.prototype.hasOwnProperty.call(newColumns, destColumn)) {
|
||||||
throw new Error(
|
const existingColumn = newColumns[destColumn];
|
||||||
`Attempt to apply embeddings to table failed because column ${destColumn} already existed`,
|
// If the column exists but is all null, we can fill it with embeddings
|
||||||
);
|
if (existingColumn.nullCount !== existingColumn.length) {
|
||||||
|
// Column has non-null values, skip embedding application and return table as-is
|
||||||
|
let newTable = new ArrowTable(newColumns);
|
||||||
|
if (schema != null) {
|
||||||
|
newTable = alignTable(newTable, schema as Schema);
|
||||||
|
}
|
||||||
|
return new ArrowTable(
|
||||||
|
new Schema(newTable.schema.fields, schemaMetadata),
|
||||||
|
newTable.batches,
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (table.batches.length > 1) {
|
if (table.batches.length > 1) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
"Internal error: `makeArrowTable` unexpectedly created a table with more than one batch",
|
"Internal error: `makeArrowTable` unexpectedly created a table with more than one batch",
|
||||||
|
|||||||
@@ -28,7 +28,13 @@ export {
|
|||||||
FragmentSummaryStats,
|
FragmentSummaryStats,
|
||||||
Tags,
|
Tags,
|
||||||
TagContents,
|
TagContents,
|
||||||
MergeStats,
|
MergeResult,
|
||||||
|
AddResult,
|
||||||
|
AddColumnsResult,
|
||||||
|
AlterColumnsResult,
|
||||||
|
DeleteResult,
|
||||||
|
DropColumnsResult,
|
||||||
|
UpdateResult,
|
||||||
} from "./native.js";
|
} from "./native.js";
|
||||||
|
|
||||||
export {
|
export {
|
||||||
@@ -58,7 +64,10 @@ export {
|
|||||||
PhraseQuery,
|
PhraseQuery,
|
||||||
BoostQuery,
|
BoostQuery,
|
||||||
MultiMatchQuery,
|
MultiMatchQuery,
|
||||||
|
BooleanQuery,
|
||||||
FullTextQueryType,
|
FullTextQueryType,
|
||||||
|
Operator,
|
||||||
|
Occur,
|
||||||
} from "./query";
|
} from "./query";
|
||||||
|
|
||||||
export {
|
export {
|
||||||
@@ -80,7 +89,7 @@ export {
|
|||||||
ColumnAlteration,
|
ColumnAlteration,
|
||||||
} from "./table";
|
} from "./table";
|
||||||
|
|
||||||
export { MergeInsertBuilder } from "./merge";
|
export { MergeInsertBuilder, WriteExecutionOptions } from "./merge";
|
||||||
|
|
||||||
export * as embedding from "./embedding";
|
export * as embedding from "./embedding";
|
||||||
export * as rerankers from "./rerankers";
|
export * as rerankers from "./rerankers";
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
// 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 { Data, Schema, fromDataToBuffer } from "./arrow";
|
import { Data, Schema, fromDataToBuffer } from "./arrow";
|
||||||
import { MergeStats, NativeMergeInsertBuilder } from "./native";
|
import { MergeResult, NativeMergeInsertBuilder } from "./native";
|
||||||
|
|
||||||
/** A builder used to create and run a merge insert operation */
|
/** A builder used to create and run a merge insert operation */
|
||||||
export class MergeInsertBuilder {
|
export class MergeInsertBuilder {
|
||||||
@@ -73,9 +73,12 @@ export class MergeInsertBuilder {
|
|||||||
/**
|
/**
|
||||||
* Executes the merge insert operation
|
* Executes the merge insert operation
|
||||||
*
|
*
|
||||||
* @returns Statistics about the merge operation: counts of inserted, updated, and deleted rows
|
* @returns {Promise<MergeResult>} the merge result
|
||||||
*/
|
*/
|
||||||
async execute(data: Data): Promise<MergeStats> {
|
async execute(
|
||||||
|
data: Data,
|
||||||
|
execOptions?: Partial<WriteExecutionOptions>,
|
||||||
|
): Promise<MergeResult> {
|
||||||
let schema: Schema;
|
let schema: Schema;
|
||||||
if (this.#schema instanceof Promise) {
|
if (this.#schema instanceof Promise) {
|
||||||
schema = await this.#schema;
|
schema = await this.#schema;
|
||||||
@@ -83,7 +86,28 @@ export class MergeInsertBuilder {
|
|||||||
} else {
|
} else {
|
||||||
schema = this.#schema;
|
schema = this.#schema;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (execOptions?.timeoutMs !== undefined) {
|
||||||
|
this.#native.setTimeout(execOptions.timeoutMs);
|
||||||
|
}
|
||||||
|
|
||||||
const buffer = await fromDataToBuffer(data, undefined, schema);
|
const buffer = await fromDataToBuffer(data, undefined, schema);
|
||||||
return await this.#native.execute(buffer);
|
return await this.#native.execute(buffer);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface WriteExecutionOptions {
|
||||||
|
/**
|
||||||
|
* Maximum time to run the operation before cancelling it.
|
||||||
|
*
|
||||||
|
* By default, there is a 30-second timeout that is only enforced after the
|
||||||
|
* first attempt. This is to prevent spending too long retrying to resolve
|
||||||
|
* conflicts. For example, if a write attempt takes 20 seconds and fails,
|
||||||
|
* the second attempt will be cancelled after 10 seconds, hitting the
|
||||||
|
* 30-second timeout. However, a write that takes one hour and succeeds on the
|
||||||
|
* first attempt will not be cancelled.
|
||||||
|
*
|
||||||
|
* When this is set, the timeout is enforced on all attempts, including the first.
|
||||||
|
*/
|
||||||
|
timeoutMs?: number;
|
||||||
|
}
|
||||||
|
|||||||
@@ -448,6 +448,10 @@ export class VectorQuery extends QueryBase<NativeVectorQuery> {
|
|||||||
* For best results we recommend tuning this parameter with a benchmark against
|
* For best results we recommend tuning this parameter with a benchmark against
|
||||||
* your actual data to find the smallest possible value that will still give
|
* your actual data to find the smallest possible value that will still give
|
||||||
* you the desired recall.
|
* you the desired recall.
|
||||||
|
*
|
||||||
|
* For more fine grained control over behavior when you have a very narrow filter
|
||||||
|
* you can use `minimumNprobes` and `maximumNprobes`. This method sets both
|
||||||
|
* the minimum and maximum to the same value.
|
||||||
*/
|
*/
|
||||||
nprobes(nprobes: number): VectorQuery {
|
nprobes(nprobes: number): VectorQuery {
|
||||||
super.doCall((inner) => inner.nprobes(nprobes));
|
super.doCall((inner) => inner.nprobes(nprobes));
|
||||||
@@ -455,6 +459,33 @@ export class VectorQuery extends QueryBase<NativeVectorQuery> {
|
|||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the minimum number of probes used.
|
||||||
|
*
|
||||||
|
* This controls the minimum number of partitions that will be searched. This
|
||||||
|
* parameter will impact every query against a vector index, regardless of the
|
||||||
|
* filter. See `nprobes` for more details. Higher values will increase recall
|
||||||
|
* but will also increase latency.
|
||||||
|
*/
|
||||||
|
minimumNprobes(minimumNprobes: number): VectorQuery {
|
||||||
|
super.doCall((inner) => inner.minimumNprobes(minimumNprobes));
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the maximum number of probes used.
|
||||||
|
*
|
||||||
|
* This controls the maximum number of partitions that will be searched. If this
|
||||||
|
* number is greater than minimumNprobes then the excess partitions will _only_ be
|
||||||
|
* searched if we have not found enough results. This can be useful when there is
|
||||||
|
* a narrow filter to allow these queries to spend more time searching and avoid
|
||||||
|
* potential false negatives.
|
||||||
|
*/
|
||||||
|
maximumNprobes(maximumNprobes: number): VectorQuery {
|
||||||
|
super.doCall((inner) => inner.maximumNprobes(maximumNprobes));
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Set the distance range to use
|
* Set the distance range to use
|
||||||
*
|
*
|
||||||
@@ -762,6 +793,31 @@ export enum FullTextQueryType {
|
|||||||
MatchPhrase = "match_phrase",
|
MatchPhrase = "match_phrase",
|
||||||
Boost = "boost",
|
Boost = "boost",
|
||||||
MultiMatch = "multi_match",
|
MultiMatch = "multi_match",
|
||||||
|
Boolean = "boolean",
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Enum representing the logical operators used in full-text queries.
|
||||||
|
*
|
||||||
|
* - `And`: All terms must match.
|
||||||
|
* - `Or`: At least one term must match.
|
||||||
|
*/
|
||||||
|
export enum Operator {
|
||||||
|
And = "AND",
|
||||||
|
Or = "OR",
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Enum representing the occurrence of terms in full-text queries.
|
||||||
|
*
|
||||||
|
* - `Must`: The term must be present in the document.
|
||||||
|
* - `Should`: The term should contribute to the document score, but is not required.
|
||||||
|
* - `MustNot`: The term must not be present in the document.
|
||||||
|
*/
|
||||||
|
export enum Occur {
|
||||||
|
Should = "SHOULD",
|
||||||
|
Must = "MUST",
|
||||||
|
MustNot = "MUST_NOT",
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -791,6 +847,7 @@ export function instanceOfFullTextQuery(obj: any): obj is FullTextQuery {
|
|||||||
export class MatchQuery implements FullTextQuery {
|
export class MatchQuery implements FullTextQuery {
|
||||||
/** @ignore */
|
/** @ignore */
|
||||||
public readonly inner: JsFullTextQuery;
|
public readonly inner: JsFullTextQuery;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates an instance of MatchQuery.
|
* Creates an instance of MatchQuery.
|
||||||
*
|
*
|
||||||
@@ -800,6 +857,8 @@ export class MatchQuery implements FullTextQuery {
|
|||||||
* - `boost`: The boost factor for the query (default is 1.0).
|
* - `boost`: The boost factor for the query (default is 1.0).
|
||||||
* - `fuzziness`: The fuzziness level for the query (default is 0).
|
* - `fuzziness`: The fuzziness level for the query (default is 0).
|
||||||
* - `maxExpansions`: The maximum number of terms to consider for fuzzy matching (default is 50).
|
* - `maxExpansions`: The maximum number of terms to consider for fuzzy matching (default is 50).
|
||||||
|
* - `operator`: The logical operator to use for combining terms in the query (default is "OR").
|
||||||
|
* - `prefixLength`: The number of beginning characters being unchanged for fuzzy matching.
|
||||||
*/
|
*/
|
||||||
constructor(
|
constructor(
|
||||||
query: string,
|
query: string,
|
||||||
@@ -808,6 +867,8 @@ export class MatchQuery implements FullTextQuery {
|
|||||||
boost?: number;
|
boost?: number;
|
||||||
fuzziness?: number;
|
fuzziness?: number;
|
||||||
maxExpansions?: number;
|
maxExpansions?: number;
|
||||||
|
operator?: Operator;
|
||||||
|
prefixLength?: number;
|
||||||
},
|
},
|
||||||
) {
|
) {
|
||||||
let fuzziness = options?.fuzziness;
|
let fuzziness = options?.fuzziness;
|
||||||
@@ -820,6 +881,8 @@ export class MatchQuery implements FullTextQuery {
|
|||||||
options?.boost ?? 1.0,
|
options?.boost ?? 1.0,
|
||||||
fuzziness,
|
fuzziness,
|
||||||
options?.maxExpansions ?? 50,
|
options?.maxExpansions ?? 50,
|
||||||
|
options?.operator ?? Operator.Or,
|
||||||
|
options?.prefixLength ?? 0,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -836,9 +899,11 @@ export class PhraseQuery implements FullTextQuery {
|
|||||||
*
|
*
|
||||||
* @param query - The phrase to search for in the specified column.
|
* @param query - The phrase to search for in the specified column.
|
||||||
* @param column - The name of the column to search within.
|
* @param column - The name of the column to search within.
|
||||||
|
* @param options - Optional parameters for the phrase query.
|
||||||
|
* - `slop`: The maximum number of intervening unmatched positions allowed between words in the phrase (default is 0).
|
||||||
*/
|
*/
|
||||||
constructor(query: string, column: string) {
|
constructor(query: string, column: string, options?: { slop?: number }) {
|
||||||
this.inner = JsFullTextQuery.phraseQuery(query, column);
|
this.inner = JsFullTextQuery.phraseQuery(query, column, options?.slop ?? 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
queryType(): FullTextQueryType {
|
queryType(): FullTextQueryType {
|
||||||
@@ -889,18 +954,21 @@ export class MultiMatchQuery implements FullTextQuery {
|
|||||||
* @param columns - An array of column names to search within.
|
* @param columns - An array of column names to search within.
|
||||||
* @param options - Optional parameters for the multi-match query.
|
* @param options - Optional parameters for the multi-match query.
|
||||||
* - `boosts`: An array of boost factors for each column (default is 1.0 for all).
|
* - `boosts`: An array of boost factors for each column (default is 1.0 for all).
|
||||||
|
* - `operator`: The logical operator to use for combining terms in the query (default is "OR").
|
||||||
*/
|
*/
|
||||||
constructor(
|
constructor(
|
||||||
query: string,
|
query: string,
|
||||||
columns: string[],
|
columns: string[],
|
||||||
options?: {
|
options?: {
|
||||||
boosts?: number[];
|
boosts?: number[];
|
||||||
|
operator?: Operator;
|
||||||
},
|
},
|
||||||
) {
|
) {
|
||||||
this.inner = JsFullTextQuery.multiMatchQuery(
|
this.inner = JsFullTextQuery.multiMatchQuery(
|
||||||
query,
|
query,
|
||||||
columns,
|
columns,
|
||||||
options?.boosts,
|
options?.boosts,
|
||||||
|
options?.operator ?? Operator.Or,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -908,3 +976,23 @@ export class MultiMatchQuery implements FullTextQuery {
|
|||||||
return FullTextQueryType.MultiMatch;
|
return FullTextQueryType.MultiMatch;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export class BooleanQuery implements FullTextQuery {
|
||||||
|
/** @ignore */
|
||||||
|
public readonly inner: JsFullTextQuery;
|
||||||
|
/**
|
||||||
|
* Creates an instance of BooleanQuery.
|
||||||
|
*
|
||||||
|
* @param queries - An array of (Occur, FullTextQuery objects) to combine.
|
||||||
|
* Occur specifies whether the query must match, or should match.
|
||||||
|
*/
|
||||||
|
constructor(queries: [Occur, FullTextQuery][]) {
|
||||||
|
this.inner = JsFullTextQuery.booleanQuery(
|
||||||
|
queries.map(([occur, query]) => [occur, query.inner]),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
queryType(): FullTextQueryType {
|
||||||
|
return FullTextQueryType.Boolean;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -16,12 +16,18 @@ import { EmbeddingFunctionConfig, getRegistry } from "./embedding/registry";
|
|||||||
import { IndexOptions } from "./indices";
|
import { IndexOptions } from "./indices";
|
||||||
import { MergeInsertBuilder } from "./merge";
|
import { MergeInsertBuilder } from "./merge";
|
||||||
import {
|
import {
|
||||||
|
AddColumnsResult,
|
||||||
AddColumnsSql,
|
AddColumnsSql,
|
||||||
|
AddResult,
|
||||||
|
AlterColumnsResult,
|
||||||
|
DeleteResult,
|
||||||
|
DropColumnsResult,
|
||||||
IndexConfig,
|
IndexConfig,
|
||||||
IndexStatistics,
|
IndexStatistics,
|
||||||
OptimizeStats,
|
OptimizeStats,
|
||||||
TableStatistics,
|
TableStatistics,
|
||||||
Tags,
|
Tags,
|
||||||
|
UpdateResult,
|
||||||
Table as _NativeTable,
|
Table as _NativeTable,
|
||||||
} from "./native";
|
} from "./native";
|
||||||
import {
|
import {
|
||||||
@@ -126,12 +132,19 @@ export abstract class Table {
|
|||||||
/**
|
/**
|
||||||
* Insert records into this Table.
|
* Insert records into this Table.
|
||||||
* @param {Data} data Records to be inserted into the Table
|
* @param {Data} data Records to be inserted into the Table
|
||||||
|
* @returns {Promise<AddResult>} A promise that resolves to an object
|
||||||
|
* containing the new version number of the table
|
||||||
*/
|
*/
|
||||||
abstract add(data: Data, options?: Partial<AddDataOptions>): Promise<void>;
|
abstract add(
|
||||||
|
data: Data,
|
||||||
|
options?: Partial<AddDataOptions>,
|
||||||
|
): Promise<AddResult>;
|
||||||
/**
|
/**
|
||||||
* Update existing records in the Table
|
* Update existing records in the Table
|
||||||
* @param opts.values The values to update. The keys are the column names and the values
|
* @param opts.values The values to update. The keys are the column names and the values
|
||||||
* are the values to set.
|
* are the values to set.
|
||||||
|
* @returns {Promise<UpdateResult>} A promise that resolves to an object containing
|
||||||
|
* the number of rows updated and the new version number
|
||||||
* @example
|
* @example
|
||||||
* ```ts
|
* ```ts
|
||||||
* table.update({where:"x = 2", values:{"vector": [10, 10]}})
|
* table.update({where:"x = 2", values:{"vector": [10, 10]}})
|
||||||
@@ -141,11 +154,13 @@ export abstract class Table {
|
|||||||
opts: {
|
opts: {
|
||||||
values: Map<string, IntoSql> | Record<string, IntoSql>;
|
values: Map<string, IntoSql> | Record<string, IntoSql>;
|
||||||
} & Partial<UpdateOptions>,
|
} & Partial<UpdateOptions>,
|
||||||
): Promise<void>;
|
): Promise<UpdateResult>;
|
||||||
/**
|
/**
|
||||||
* Update existing records in the Table
|
* Update existing records in the Table
|
||||||
* @param opts.valuesSql The values to update. The keys are the column names and the values
|
* @param opts.valuesSql The values to update. The keys are the column names and the values
|
||||||
* are the values to set. The values are SQL expressions.
|
* are the values to set. The values are SQL expressions.
|
||||||
|
* @returns {Promise<UpdateResult>} A promise that resolves to an object containing
|
||||||
|
* the number of rows updated and the new version number
|
||||||
* @example
|
* @example
|
||||||
* ```ts
|
* ```ts
|
||||||
* table.update({where:"x = 2", valuesSql:{"x": "x + 1"}})
|
* table.update({where:"x = 2", valuesSql:{"x": "x + 1"}})
|
||||||
@@ -155,7 +170,7 @@ export abstract class Table {
|
|||||||
opts: {
|
opts: {
|
||||||
valuesSql: Map<string, string> | Record<string, string>;
|
valuesSql: Map<string, string> | Record<string, string>;
|
||||||
} & Partial<UpdateOptions>,
|
} & Partial<UpdateOptions>,
|
||||||
): Promise<void>;
|
): Promise<UpdateResult>;
|
||||||
/**
|
/**
|
||||||
* Update existing records in the Table
|
* Update existing records in the Table
|
||||||
*
|
*
|
||||||
@@ -173,6 +188,8 @@ export abstract class Table {
|
|||||||
* repeatedly calilng this method.
|
* repeatedly calilng this method.
|
||||||
* @param {Map<string, string> | Record<string, string>} updates - the
|
* @param {Map<string, string> | Record<string, string>} updates - the
|
||||||
* columns to update
|
* columns to update
|
||||||
|
* @returns {Promise<UpdateResult>} A promise that resolves to an object
|
||||||
|
* containing the number of rows updated and the new version number
|
||||||
*
|
*
|
||||||
* Keys in the map should specify the name of the column to update.
|
* Keys in the map should specify the name of the column to update.
|
||||||
* Values in the map provide the new value of the column. These can
|
* Values in the map provide the new value of the column. These can
|
||||||
@@ -184,12 +201,16 @@ export abstract class Table {
|
|||||||
abstract update(
|
abstract update(
|
||||||
updates: Map<string, string> | Record<string, string>,
|
updates: Map<string, string> | Record<string, string>,
|
||||||
options?: Partial<UpdateOptions>,
|
options?: Partial<UpdateOptions>,
|
||||||
): Promise<void>;
|
): Promise<UpdateResult>;
|
||||||
|
|
||||||
/** Count the total number of rows in the dataset. */
|
/** Count the total number of rows in the dataset. */
|
||||||
abstract countRows(filter?: string): Promise<number>;
|
abstract countRows(filter?: string): Promise<number>;
|
||||||
/** Delete the rows that satisfy the predicate. */
|
/**
|
||||||
abstract delete(predicate: string): Promise<void>;
|
* Delete the rows that satisfy the predicate.
|
||||||
|
* @returns {Promise<DeleteResult>} A promise that resolves to an object
|
||||||
|
* containing the new version number of the table
|
||||||
|
*/
|
||||||
|
abstract delete(predicate: string): Promise<DeleteResult>;
|
||||||
/**
|
/**
|
||||||
* Create an index to speed up queries.
|
* Create an index to speed up queries.
|
||||||
*
|
*
|
||||||
@@ -343,15 +364,23 @@ export abstract class Table {
|
|||||||
* the SQL expression to use to calculate the value of the new column. These
|
* the SQL expression to use to calculate the value of the new column. These
|
||||||
* expressions will be evaluated for each row in the table, and can
|
* expressions will be evaluated for each row in the table, and can
|
||||||
* reference existing columns in the table.
|
* reference existing columns in the table.
|
||||||
|
* @returns {Promise<AddColumnsResult>} A promise that resolves to an object
|
||||||
|
* containing the new version number of the table after adding the columns.
|
||||||
*/
|
*/
|
||||||
abstract addColumns(newColumnTransforms: AddColumnsSql[]): Promise<void>;
|
abstract addColumns(
|
||||||
|
newColumnTransforms: AddColumnsSql[],
|
||||||
|
): Promise<AddColumnsResult>;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Alter the name or nullability of columns.
|
* Alter the name or nullability of columns.
|
||||||
* @param {ColumnAlteration[]} columnAlterations One or more alterations to
|
* @param {ColumnAlteration[]} columnAlterations One or more alterations to
|
||||||
* apply to columns.
|
* apply to columns.
|
||||||
|
* @returns {Promise<AlterColumnsResult>} A promise that resolves to an object
|
||||||
|
* containing the new version number of the table after altering the columns.
|
||||||
*/
|
*/
|
||||||
abstract alterColumns(columnAlterations: ColumnAlteration[]): Promise<void>;
|
abstract alterColumns(
|
||||||
|
columnAlterations: ColumnAlteration[],
|
||||||
|
): Promise<AlterColumnsResult>;
|
||||||
/**
|
/**
|
||||||
* Drop one or more columns from the dataset
|
* Drop one or more columns from the dataset
|
||||||
*
|
*
|
||||||
@@ -362,8 +391,10 @@ export abstract class Table {
|
|||||||
* @param {string[]} columnNames The names of the columns to drop. These can
|
* @param {string[]} columnNames The names of the columns to drop. These can
|
||||||
* be nested column references (e.g. "a.b.c") or top-level column names
|
* be nested column references (e.g. "a.b.c") or top-level column names
|
||||||
* (e.g. "a").
|
* (e.g. "a").
|
||||||
|
* @returns {Promise<DropColumnsResult>} A promise that resolves to an object
|
||||||
|
* containing the new version number of the table after dropping the columns.
|
||||||
*/
|
*/
|
||||||
abstract dropColumns(columnNames: string[]): Promise<void>;
|
abstract dropColumns(columnNames: string[]): Promise<DropColumnsResult>;
|
||||||
/** Retrieve the version of the table */
|
/** Retrieve the version of the table */
|
||||||
|
|
||||||
abstract version(): Promise<number>;
|
abstract version(): Promise<number>;
|
||||||
@@ -529,12 +560,12 @@ export class LocalTable extends Table {
|
|||||||
return tbl.schema;
|
return tbl.schema;
|
||||||
}
|
}
|
||||||
|
|
||||||
async add(data: Data, options?: Partial<AddDataOptions>): Promise<void> {
|
async add(data: Data, options?: Partial<AddDataOptions>): Promise<AddResult> {
|
||||||
const mode = options?.mode ?? "append";
|
const mode = options?.mode ?? "append";
|
||||||
const schema = await this.schema();
|
const schema = await this.schema();
|
||||||
|
|
||||||
const buffer = await fromDataToBuffer(data, undefined, schema);
|
const buffer = await fromDataToBuffer(data, undefined, schema);
|
||||||
await this.inner.add(buffer, mode);
|
return await this.inner.add(buffer, mode);
|
||||||
}
|
}
|
||||||
|
|
||||||
async update(
|
async update(
|
||||||
@@ -547,7 +578,7 @@ export class LocalTable extends Table {
|
|||||||
valuesSql: Map<string, string> | Record<string, string>;
|
valuesSql: Map<string, string> | Record<string, string>;
|
||||||
} & Partial<UpdateOptions>),
|
} & Partial<UpdateOptions>),
|
||||||
options?: Partial<UpdateOptions>,
|
options?: Partial<UpdateOptions>,
|
||||||
) {
|
): Promise<UpdateResult> {
|
||||||
const isValues =
|
const isValues =
|
||||||
"values" in optsOrUpdates && typeof optsOrUpdates.values !== "string";
|
"values" in optsOrUpdates && typeof optsOrUpdates.values !== "string";
|
||||||
const isValuesSql =
|
const isValuesSql =
|
||||||
@@ -594,15 +625,15 @@ export class LocalTable extends Table {
|
|||||||
columns = Object.entries(optsOrUpdates as Record<string, string>);
|
columns = Object.entries(optsOrUpdates as Record<string, string>);
|
||||||
predicate = options?.where;
|
predicate = options?.where;
|
||||||
}
|
}
|
||||||
await this.inner.update(predicate, columns);
|
return await this.inner.update(predicate, columns);
|
||||||
}
|
}
|
||||||
|
|
||||||
async countRows(filter?: string): Promise<number> {
|
async countRows(filter?: string): Promise<number> {
|
||||||
return await this.inner.countRows(filter);
|
return await this.inner.countRows(filter);
|
||||||
}
|
}
|
||||||
|
|
||||||
async delete(predicate: string): Promise<void> {
|
async delete(predicate: string): Promise<DeleteResult> {
|
||||||
await this.inner.delete(predicate);
|
return await this.inner.delete(predicate);
|
||||||
}
|
}
|
||||||
|
|
||||||
async createIndex(column: string, options?: Partial<IndexOptions>) {
|
async createIndex(column: string, options?: Partial<IndexOptions>) {
|
||||||
@@ -690,11 +721,15 @@ export class LocalTable extends Table {
|
|||||||
|
|
||||||
// TODO: Support BatchUDF
|
// TODO: Support BatchUDF
|
||||||
|
|
||||||
async addColumns(newColumnTransforms: AddColumnsSql[]): Promise<void> {
|
async addColumns(
|
||||||
await this.inner.addColumns(newColumnTransforms);
|
newColumnTransforms: AddColumnsSql[],
|
||||||
|
): Promise<AddColumnsResult> {
|
||||||
|
return await this.inner.addColumns(newColumnTransforms);
|
||||||
}
|
}
|
||||||
|
|
||||||
async alterColumns(columnAlterations: ColumnAlteration[]): Promise<void> {
|
async alterColumns(
|
||||||
|
columnAlterations: ColumnAlteration[],
|
||||||
|
): Promise<AlterColumnsResult> {
|
||||||
const processedAlterations = columnAlterations.map((alteration) => {
|
const processedAlterations = columnAlterations.map((alteration) => {
|
||||||
if (typeof alteration.dataType === "string") {
|
if (typeof alteration.dataType === "string") {
|
||||||
return {
|
return {
|
||||||
@@ -715,11 +750,11 @@ export class LocalTable extends Table {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
await this.inner.alterColumns(processedAlterations);
|
return await this.inner.alterColumns(processedAlterations);
|
||||||
}
|
}
|
||||||
|
|
||||||
async dropColumns(columnNames: string[]): Promise<void> {
|
async dropColumns(columnNames: string[]): Promise<DropColumnsResult> {
|
||||||
await this.inner.dropColumns(columnNames);
|
return await this.inner.dropColumns(columnNames);
|
||||||
}
|
}
|
||||||
|
|
||||||
async version(): Promise<number> {
|
async version(): Promise<number> {
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@lancedb/lancedb-darwin-arm64",
|
"name": "@lancedb/lancedb-darwin-arm64",
|
||||||
"version": "0.19.1-beta.1",
|
"version": "0.21.0",
|
||||||
"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-darwin-x64",
|
"name": "@lancedb/lancedb-darwin-x64",
|
||||||
"version": "0.19.1-beta.1",
|
"version": "0.21.0",
|
||||||
"os": ["darwin"],
|
"os": ["darwin"],
|
||||||
"cpu": ["x64"],
|
"cpu": ["x64"],
|
||||||
"main": "lancedb.darwin-x64.node",
|
"main": "lancedb.darwin-x64.node",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@lancedb/lancedb-linux-arm64-gnu",
|
"name": "@lancedb/lancedb-linux-arm64-gnu",
|
||||||
"version": "0.19.1-beta.1",
|
"version": "0.21.0",
|
||||||
"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.19.1-beta.1",
|
"version": "0.21.0",
|
||||||
"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.19.1-beta.1",
|
"version": "0.21.0",
|
||||||
"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.19.1-beta.1",
|
"version": "0.21.0",
|
||||||
"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.19.1-beta.1",
|
"version": "0.21.0",
|
||||||
"os": [
|
"os": [
|
||||||
"win32"
|
"win32"
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@lancedb/lancedb-win32-x64-msvc",
|
"name": "@lancedb/lancedb-win32-x64-msvc",
|
||||||
"version": "0.19.1-beta.1",
|
"version": "0.21.0",
|
||||||
"os": ["win32"],
|
"os": ["win32"],
|
||||||
"cpu": ["x64"],
|
"cpu": ["x64"],
|
||||||
"main": "lancedb.win32-x64-msvc.node",
|
"main": "lancedb.win32-x64-msvc.node",
|
||||||
|
|||||||
4
nodejs/package-lock.json
generated
4
nodejs/package-lock.json
generated
@@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "@lancedb/lancedb",
|
"name": "@lancedb/lancedb",
|
||||||
"version": "0.19.1-beta.1",
|
"version": "0.20.1-beta.2",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "@lancedb/lancedb",
|
"name": "@lancedb/lancedb",
|
||||||
"version": "0.19.1-beta.1",
|
"version": "0.20.1-beta.2",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"x64",
|
"x64",
|
||||||
"arm64"
|
"arm64"
|
||||||
|
|||||||
@@ -11,7 +11,7 @@
|
|||||||
"ann"
|
"ann"
|
||||||
],
|
],
|
||||||
"private": false,
|
"private": false,
|
||||||
"version": "0.19.1-beta.1",
|
"version": "0.21.0",
|
||||||
"main": "dist/index.js",
|
"main": "dist/index.js",
|
||||||
"exports": {
|
"exports": {
|
||||||
".": "./dist/index.js",
|
".": "./dist/index.js",
|
||||||
|
|||||||
@@ -125,32 +125,30 @@ impl Index {
|
|||||||
ascii_folding: Option<bool>,
|
ascii_folding: Option<bool>,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
let mut opts = FtsIndexBuilder::default();
|
let mut opts = FtsIndexBuilder::default();
|
||||||
let mut tokenizer_configs = opts.tokenizer_configs.clone();
|
|
||||||
if let Some(with_position) = with_position {
|
if let Some(with_position) = with_position {
|
||||||
opts = opts.with_position(with_position);
|
opts = opts.with_position(with_position);
|
||||||
}
|
}
|
||||||
if let Some(base_tokenizer) = base_tokenizer {
|
if let Some(base_tokenizer) = base_tokenizer {
|
||||||
tokenizer_configs = tokenizer_configs.base_tokenizer(base_tokenizer);
|
opts = opts.base_tokenizer(base_tokenizer);
|
||||||
}
|
}
|
||||||
if let Some(language) = language {
|
if let Some(language) = language {
|
||||||
tokenizer_configs = tokenizer_configs.language(&language).unwrap();
|
opts = opts.language(&language).unwrap();
|
||||||
}
|
}
|
||||||
if let Some(max_token_length) = max_token_length {
|
if let Some(max_token_length) = max_token_length {
|
||||||
tokenizer_configs = tokenizer_configs.max_token_length(Some(max_token_length as usize));
|
opts = opts.max_token_length(Some(max_token_length as usize));
|
||||||
}
|
}
|
||||||
if let Some(lower_case) = lower_case {
|
if let Some(lower_case) = lower_case {
|
||||||
tokenizer_configs = tokenizer_configs.lower_case(lower_case);
|
opts = opts.lower_case(lower_case);
|
||||||
}
|
}
|
||||||
if let Some(stem) = stem {
|
if let Some(stem) = stem {
|
||||||
tokenizer_configs = tokenizer_configs.stem(stem);
|
opts = opts.stem(stem);
|
||||||
}
|
}
|
||||||
if let Some(remove_stop_words) = remove_stop_words {
|
if let Some(remove_stop_words) = remove_stop_words {
|
||||||
tokenizer_configs = tokenizer_configs.remove_stop_words(remove_stop_words);
|
opts = opts.remove_stop_words(remove_stop_words);
|
||||||
}
|
}
|
||||||
if let Some(ascii_folding) = ascii_folding {
|
if let Some(ascii_folding) = ascii_folding {
|
||||||
tokenizer_configs = tokenizer_configs.ascii_folding(ascii_folding);
|
opts = opts.ascii_folding(ascii_folding);
|
||||||
}
|
}
|
||||||
opts.tokenizer_configs = tokenizer_configs;
|
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
inner: Mutex::new(Some(LanceDbIndex::FTS(opts))),
|
inner: Mutex::new(Some(LanceDbIndex::FTS(opts))),
|
||||||
|
|||||||
@@ -1,11 +1,13 @@
|
|||||||
// 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::time::Duration;
|
||||||
|
|
||||||
use lancedb::{arrow::IntoArrow, ipc::ipc_file_to_batches, table::merge::MergeInsertBuilder};
|
use lancedb::{arrow::IntoArrow, ipc::ipc_file_to_batches, table::merge::MergeInsertBuilder};
|
||||||
use napi::bindgen_prelude::*;
|
use napi::bindgen_prelude::*;
|
||||||
use napi_derive::napi;
|
use napi_derive::napi;
|
||||||
|
|
||||||
use crate::error::convert_error;
|
use crate::{error::convert_error, table::MergeResult};
|
||||||
|
|
||||||
#[napi]
|
#[napi]
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
@@ -36,8 +38,13 @@ impl NativeMergeInsertBuilder {
|
|||||||
this
|
this
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[napi]
|
||||||
|
pub fn set_timeout(&mut self, timeout: u32) {
|
||||||
|
self.inner.timeout(Duration::from_millis(timeout as u64));
|
||||||
|
}
|
||||||
|
|
||||||
#[napi(catch_unwind)]
|
#[napi(catch_unwind)]
|
||||||
pub async fn execute(&self, buf: Buffer) -> napi::Result<MergeStats> {
|
pub async fn execute(&self, buf: Buffer) -> napi::Result<MergeResult> {
|
||||||
let data = ipc_file_to_batches(buf.to_vec())
|
let data = ipc_file_to_batches(buf.to_vec())
|
||||||
.and_then(IntoArrow::into_arrow)
|
.and_then(IntoArrow::into_arrow)
|
||||||
.map_err(|e| {
|
.map_err(|e| {
|
||||||
@@ -46,14 +53,13 @@ impl NativeMergeInsertBuilder {
|
|||||||
|
|
||||||
let this = self.clone();
|
let this = self.clone();
|
||||||
|
|
||||||
let stats = this.inner.execute(data).await.map_err(|e| {
|
let res = this.inner.execute(data).await.map_err(|e| {
|
||||||
napi::Error::from_reason(format!(
|
napi::Error::from_reason(format!(
|
||||||
"Failed to execute merge insert: {}",
|
"Failed to execute merge insert: {}",
|
||||||
convert_error(&e)
|
convert_error(&e)
|
||||||
))
|
))
|
||||||
})?;
|
})?;
|
||||||
|
Ok(res.into())
|
||||||
Ok(stats.into())
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -62,20 +68,3 @@ impl From<MergeInsertBuilder> for NativeMergeInsertBuilder {
|
|||||||
Self { inner }
|
Self { inner }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[napi(object)]
|
|
||||||
pub struct MergeStats {
|
|
||||||
pub num_inserted_rows: BigInt,
|
|
||||||
pub num_updated_rows: BigInt,
|
|
||||||
pub num_deleted_rows: BigInt,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<lancedb::table::MergeStats> for MergeStats {
|
|
||||||
fn from(stats: lancedb::table::MergeStats) -> Self {
|
|
||||||
Self {
|
|
||||||
num_inserted_rows: stats.num_inserted_rows.into(),
|
|
||||||
num_updated_rows: stats.num_updated_rows.into(),
|
|
||||||
num_deleted_rows: stats.num_deleted_rows.into(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -4,7 +4,8 @@
|
|||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
use lancedb::index::scalar::{
|
use lancedb::index::scalar::{
|
||||||
BoostQuery, FtsQuery, FullTextSearchQuery, MatchQuery, MultiMatchQuery, PhraseQuery,
|
BooleanQuery, BoostQuery, FtsQuery, FullTextSearchQuery, MatchQuery, MultiMatchQuery, Occur,
|
||||||
|
Operator, PhraseQuery,
|
||||||
};
|
};
|
||||||
use lancedb::query::ExecutableQuery;
|
use lancedb::query::ExecutableQuery;
|
||||||
use lancedb::query::Query as LanceDbQuery;
|
use lancedb::query::Query as LanceDbQuery;
|
||||||
@@ -177,6 +178,31 @@ impl VectorQuery {
|
|||||||
self.inner = self.inner.clone().nprobes(nprobe as usize);
|
self.inner = self.inner.clone().nprobes(nprobe as usize);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[napi]
|
||||||
|
pub fn minimum_nprobes(&mut self, minimum_nprobe: u32) -> napi::Result<()> {
|
||||||
|
self.inner = self
|
||||||
|
.inner
|
||||||
|
.clone()
|
||||||
|
.minimum_nprobes(minimum_nprobe as usize)
|
||||||
|
.default_error()?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[napi]
|
||||||
|
pub fn maximum_nprobes(&mut self, maximum_nprobes: u32) -> napi::Result<()> {
|
||||||
|
let maximum_nprobes = if maximum_nprobes == 0 {
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
Some(maximum_nprobes as usize)
|
||||||
|
};
|
||||||
|
self.inner = self
|
||||||
|
.inner
|
||||||
|
.clone()
|
||||||
|
.maximum_nprobes(maximum_nprobes)
|
||||||
|
.default_error()?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
#[napi]
|
#[napi]
|
||||||
pub fn distance_range(&mut self, lower_bound: Option<f64>, upper_bound: Option<f64>) {
|
pub fn distance_range(&mut self, lower_bound: Option<f64>, upper_bound: Option<f64>) {
|
||||||
// napi doesn't support f32, so we have to convert to f32
|
// napi doesn't support f32, so we have to convert to f32
|
||||||
@@ -308,6 +334,8 @@ impl JsFullTextQuery {
|
|||||||
boost: f64,
|
boost: f64,
|
||||||
fuzziness: Option<u32>,
|
fuzziness: Option<u32>,
|
||||||
max_expansions: u32,
|
max_expansions: u32,
|
||||||
|
operator: String,
|
||||||
|
prefix_length: u32,
|
||||||
) -> napi::Result<Self> {
|
) -> napi::Result<Self> {
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
inner: MatchQuery::new(query)
|
inner: MatchQuery::new(query)
|
||||||
@@ -315,14 +343,23 @@ impl JsFullTextQuery {
|
|||||||
.with_boost(boost as f32)
|
.with_boost(boost as f32)
|
||||||
.with_fuzziness(fuzziness)
|
.with_fuzziness(fuzziness)
|
||||||
.with_max_expansions(max_expansions as usize)
|
.with_max_expansions(max_expansions as usize)
|
||||||
|
.with_operator(
|
||||||
|
Operator::try_from(operator.as_str()).map_err(|e| {
|
||||||
|
napi::Error::from_reason(format!("Invalid operator: {}", e))
|
||||||
|
})?,
|
||||||
|
)
|
||||||
|
.with_prefix_length(prefix_length)
|
||||||
.into(),
|
.into(),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
#[napi(factory)]
|
#[napi(factory)]
|
||||||
pub fn phrase_query(query: String, column: String) -> napi::Result<Self> {
|
pub fn phrase_query(query: String, column: String, slop: u32) -> napi::Result<Self> {
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
inner: PhraseQuery::new(query).with_column(Some(column)).into(),
|
inner: PhraseQuery::new(query)
|
||||||
|
.with_column(Some(column))
|
||||||
|
.with_slop(slop)
|
||||||
|
.into(),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -348,6 +385,7 @@ impl JsFullTextQuery {
|
|||||||
query: String,
|
query: String,
|
||||||
columns: Vec<String>,
|
columns: Vec<String>,
|
||||||
boosts: Option<Vec<f64>>,
|
boosts: Option<Vec<f64>>,
|
||||||
|
operator: String,
|
||||||
) -> napi::Result<Self> {
|
) -> napi::Result<Self> {
|
||||||
let q = match boosts {
|
let q = match boosts {
|
||||||
Some(boosts) => MultiMatchQuery::try_new(query, columns)
|
Some(boosts) => MultiMatchQuery::try_new(query, columns)
|
||||||
@@ -358,7 +396,37 @@ impl JsFullTextQuery {
|
|||||||
napi::Error::from_reason(format!("Failed to create multi match query: {}", e))
|
napi::Error::from_reason(format!("Failed to create multi match query: {}", e))
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
Ok(Self { inner: q.into() })
|
let operator = Operator::try_from(operator.as_str()).map_err(|e| {
|
||||||
|
napi::Error::from_reason(format!("Invalid operator for multi match query: {}", e))
|
||||||
|
})?;
|
||||||
|
|
||||||
|
Ok(Self {
|
||||||
|
inner: q.with_operator(operator).into(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
#[napi(factory)]
|
||||||
|
pub fn boolean_query(queries: Vec<(String, &JsFullTextQuery)>) -> napi::Result<Self> {
|
||||||
|
let mut sub_queries = Vec::with_capacity(queries.len());
|
||||||
|
for (occur, q) in queries {
|
||||||
|
let occur = Occur::try_from(occur.as_str())
|
||||||
|
.map_err(|e| napi::Error::from_reason(e.to_string()))?;
|
||||||
|
sub_queries.push((occur, q.inner.clone()));
|
||||||
|
}
|
||||||
|
Ok(Self {
|
||||||
|
inner: BooleanQuery::new(sub_queries).into(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
#[napi(getter)]
|
||||||
|
pub fn query_type(&self) -> String {
|
||||||
|
match self.inner {
|
||||||
|
FtsQuery::Match(_) => "match".to_string(),
|
||||||
|
FtsQuery::Phrase(_) => "phrase".to_string(),
|
||||||
|
FtsQuery::Boost(_) => "boost".to_string(),
|
||||||
|
FtsQuery::MultiMatch(_) => "multi_match".to_string(),
|
||||||
|
FtsQuery::Boolean(_) => "boolean".to_string(),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -75,7 +75,7 @@ impl Table {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[napi(catch_unwind)]
|
#[napi(catch_unwind)]
|
||||||
pub async fn add(&self, buf: Buffer, mode: String) -> napi::Result<()> {
|
pub async fn add(&self, buf: Buffer, mode: String) -> napi::Result<AddResult> {
|
||||||
let batches = ipc_file_to_batches(buf.to_vec())
|
let batches = ipc_file_to_batches(buf.to_vec())
|
||||||
.map_err(|e| napi::Error::from_reason(format!("Failed to read IPC file: {}", e)))?;
|
.map_err(|e| napi::Error::from_reason(format!("Failed to read IPC file: {}", e)))?;
|
||||||
let mut op = self.inner_ref()?.add(batches);
|
let mut op = self.inner_ref()?.add(batches);
|
||||||
@@ -88,7 +88,8 @@ impl Table {
|
|||||||
return Err(napi::Error::from_reason(format!("Invalid mode: {}", mode)));
|
return Err(napi::Error::from_reason(format!("Invalid mode: {}", mode)));
|
||||||
};
|
};
|
||||||
|
|
||||||
op.execute().await.default_error()
|
let res = op.execute().await.default_error()?;
|
||||||
|
Ok(res.into())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[napi(catch_unwind)]
|
#[napi(catch_unwind)]
|
||||||
@@ -101,8 +102,9 @@ impl Table {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[napi(catch_unwind)]
|
#[napi(catch_unwind)]
|
||||||
pub async fn delete(&self, predicate: String) -> napi::Result<()> {
|
pub async fn delete(&self, predicate: String) -> napi::Result<DeleteResult> {
|
||||||
self.inner_ref()?.delete(&predicate).await.default_error()
|
let res = self.inner_ref()?.delete(&predicate).await.default_error()?;
|
||||||
|
Ok(res.into())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[napi(catch_unwind)]
|
#[napi(catch_unwind)]
|
||||||
@@ -168,7 +170,7 @@ impl Table {
|
|||||||
&self,
|
&self,
|
||||||
only_if: Option<String>,
|
only_if: Option<String>,
|
||||||
columns: Vec<(String, String)>,
|
columns: Vec<(String, String)>,
|
||||||
) -> napi::Result<u64> {
|
) -> napi::Result<UpdateResult> {
|
||||||
let mut op = self.inner_ref()?.update();
|
let mut op = self.inner_ref()?.update();
|
||||||
if let Some(only_if) = only_if {
|
if let Some(only_if) = only_if {
|
||||||
op = op.only_if(only_if);
|
op = op.only_if(only_if);
|
||||||
@@ -176,7 +178,8 @@ impl Table {
|
|||||||
for (column_name, value) in columns {
|
for (column_name, value) in columns {
|
||||||
op = op.column(column_name, value);
|
op = op.column(column_name, value);
|
||||||
}
|
}
|
||||||
op.execute().await.default_error()
|
let res = op.execute().await.default_error()?;
|
||||||
|
Ok(res.into())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[napi(catch_unwind)]
|
#[napi(catch_unwind)]
|
||||||
@@ -190,21 +193,28 @@ impl Table {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[napi(catch_unwind)]
|
#[napi(catch_unwind)]
|
||||||
pub async fn add_columns(&self, transforms: Vec<AddColumnsSql>) -> napi::Result<()> {
|
pub async fn add_columns(
|
||||||
|
&self,
|
||||||
|
transforms: Vec<AddColumnsSql>,
|
||||||
|
) -> napi::Result<AddColumnsResult> {
|
||||||
let transforms = transforms
|
let transforms = transforms
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|sql| (sql.name, sql.value_sql))
|
.map(|sql| (sql.name, sql.value_sql))
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
let transforms = NewColumnTransform::SqlExpressions(transforms);
|
let transforms = NewColumnTransform::SqlExpressions(transforms);
|
||||||
self.inner_ref()?
|
let res = self
|
||||||
|
.inner_ref()?
|
||||||
.add_columns(transforms, None)
|
.add_columns(transforms, None)
|
||||||
.await
|
.await
|
||||||
.default_error()?;
|
.default_error()?;
|
||||||
Ok(())
|
Ok(res.into())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[napi(catch_unwind)]
|
#[napi(catch_unwind)]
|
||||||
pub async fn alter_columns(&self, alterations: Vec<ColumnAlteration>) -> napi::Result<()> {
|
pub async fn alter_columns(
|
||||||
|
&self,
|
||||||
|
alterations: Vec<ColumnAlteration>,
|
||||||
|
) -> napi::Result<AlterColumnsResult> {
|
||||||
for alteration in &alterations {
|
for alteration in &alterations {
|
||||||
if alteration.rename.is_none()
|
if alteration.rename.is_none()
|
||||||
&& alteration.nullable.is_none()
|
&& alteration.nullable.is_none()
|
||||||
@@ -221,21 +231,23 @@ impl Table {
|
|||||||
.collect::<std::result::Result<Vec<_>, String>>()
|
.collect::<std::result::Result<Vec<_>, String>>()
|
||||||
.map_err(napi::Error::from_reason)?;
|
.map_err(napi::Error::from_reason)?;
|
||||||
|
|
||||||
self.inner_ref()?
|
let res = self
|
||||||
|
.inner_ref()?
|
||||||
.alter_columns(&alterations)
|
.alter_columns(&alterations)
|
||||||
.await
|
.await
|
||||||
.default_error()?;
|
.default_error()?;
|
||||||
Ok(())
|
Ok(res.into())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[napi(catch_unwind)]
|
#[napi(catch_unwind)]
|
||||||
pub async fn drop_columns(&self, columns: Vec<String>) -> napi::Result<()> {
|
pub async fn drop_columns(&self, columns: Vec<String>) -> napi::Result<DropColumnsResult> {
|
||||||
let col_refs = columns.iter().map(String::as_str).collect::<Vec<_>>();
|
let col_refs = columns.iter().map(String::as_str).collect::<Vec<_>>();
|
||||||
self.inner_ref()?
|
let res = self
|
||||||
|
.inner_ref()?
|
||||||
.drop_columns(&col_refs)
|
.drop_columns(&col_refs)
|
||||||
.await
|
.await
|
||||||
.default_error()?;
|
.default_error()?;
|
||||||
Ok(())
|
Ok(res.into())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[napi(catch_unwind)]
|
#[napi(catch_unwind)]
|
||||||
@@ -642,6 +654,105 @@ pub struct Version {
|
|||||||
pub metadata: HashMap<String, String>,
|
pub metadata: HashMap<String, String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[napi(object)]
|
||||||
|
pub struct UpdateResult {
|
||||||
|
pub rows_updated: i64,
|
||||||
|
pub version: i64,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<lancedb::table::UpdateResult> for UpdateResult {
|
||||||
|
fn from(value: lancedb::table::UpdateResult) -> Self {
|
||||||
|
Self {
|
||||||
|
rows_updated: value.rows_updated as i64,
|
||||||
|
version: value.version as i64,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[napi(object)]
|
||||||
|
pub struct AddResult {
|
||||||
|
pub version: i64,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<lancedb::table::AddResult> for AddResult {
|
||||||
|
fn from(value: lancedb::table::AddResult) -> Self {
|
||||||
|
Self {
|
||||||
|
version: value.version as i64,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[napi(object)]
|
||||||
|
pub struct DeleteResult {
|
||||||
|
pub version: i64,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<lancedb::table::DeleteResult> for DeleteResult {
|
||||||
|
fn from(value: lancedb::table::DeleteResult) -> Self {
|
||||||
|
Self {
|
||||||
|
version: value.version as i64,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[napi(object)]
|
||||||
|
pub struct MergeResult {
|
||||||
|
pub version: i64,
|
||||||
|
pub num_inserted_rows: i64,
|
||||||
|
pub num_updated_rows: i64,
|
||||||
|
pub num_deleted_rows: i64,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<lancedb::table::MergeResult> for MergeResult {
|
||||||
|
fn from(value: lancedb::table::MergeResult) -> Self {
|
||||||
|
Self {
|
||||||
|
version: value.version as i64,
|
||||||
|
num_inserted_rows: value.num_inserted_rows as i64,
|
||||||
|
num_updated_rows: value.num_updated_rows as i64,
|
||||||
|
num_deleted_rows: value.num_deleted_rows as i64,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[napi(object)]
|
||||||
|
pub struct AddColumnsResult {
|
||||||
|
pub version: i64,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<lancedb::table::AddColumnsResult> for AddColumnsResult {
|
||||||
|
fn from(value: lancedb::table::AddColumnsResult) -> Self {
|
||||||
|
Self {
|
||||||
|
version: value.version as i64,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[napi(object)]
|
||||||
|
pub struct AlterColumnsResult {
|
||||||
|
pub version: i64,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<lancedb::table::AlterColumnsResult> for AlterColumnsResult {
|
||||||
|
fn from(value: lancedb::table::AlterColumnsResult) -> Self {
|
||||||
|
Self {
|
||||||
|
version: value.version as i64,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[napi(object)]
|
||||||
|
pub struct DropColumnsResult {
|
||||||
|
pub version: i64,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<lancedb::table::DropColumnsResult> for DropColumnsResult {
|
||||||
|
fn from(value: lancedb::table::DropColumnsResult) -> Self {
|
||||||
|
Self {
|
||||||
|
version: value.version as i64,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[napi]
|
#[napi]
|
||||||
pub struct TagContents {
|
pub struct TagContents {
|
||||||
pub version: i64,
|
pub version: i64,
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
[tool.bumpversion]
|
[tool.bumpversion]
|
||||||
current_version = "0.22.1-beta.1"
|
current_version = "0.24.0"
|
||||||
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,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "lancedb-python"
|
name = "lancedb-python"
|
||||||
version = "0.22.1-beta.1"
|
version = "0.24.0"
|
||||||
edition.workspace = true
|
edition.workspace = true
|
||||||
description = "Python bindings for LanceDB"
|
description = "Python bindings for LanceDB"
|
||||||
license.workspace = true
|
license.workspace = true
|
||||||
@@ -14,11 +14,11 @@ name = "_lancedb"
|
|||||||
crate-type = ["cdylib"]
|
crate-type = ["cdylib"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
arrow = { version = "54.1", features = ["pyarrow"] }
|
arrow = { version = "55.1", features = ["pyarrow"] }
|
||||||
lancedb = { path = "../rust/lancedb", default-features = false }
|
lancedb = { path = "../rust/lancedb", default-features = false }
|
||||||
env_logger.workspace = true
|
env_logger.workspace = true
|
||||||
pyo3 = { version = "0.23", features = ["extension-module", "abi3-py39"] }
|
pyo3 = { version = "0.24", features = ["extension-module", "abi3-py39"] }
|
||||||
pyo3-async-runtimes = { version = "0.23", features = [
|
pyo3-async-runtimes = { version = "0.24", features = [
|
||||||
"attributes",
|
"attributes",
|
||||||
"tokio-runtime",
|
"tokio-runtime",
|
||||||
] }
|
] }
|
||||||
@@ -27,7 +27,7 @@ futures.workspace = true
|
|||||||
tokio = { version = "1.40", features = ["sync"] }
|
tokio = { version = "1.40", features = ["sync"] }
|
||||||
|
|
||||||
[build-dependencies]
|
[build-dependencies]
|
||||||
pyo3-build-config = { version = "0.23", features = [
|
pyo3-build-config = { version = "0.24", features = [
|
||||||
"extension-module",
|
"extension-module",
|
||||||
"abi3-py39",
|
"abi3-py39",
|
||||||
] }
|
] }
|
||||||
|
|||||||
@@ -60,6 +60,7 @@ tests = [
|
|||||||
"pyarrow-stubs",
|
"pyarrow-stubs",
|
||||||
"pylance>=0.25",
|
"pylance>=0.25",
|
||||||
"requests",
|
"requests",
|
||||||
|
"datafusion",
|
||||||
]
|
]
|
||||||
dev = [
|
dev = [
|
||||||
"ruff",
|
"ruff",
|
||||||
|
|||||||
@@ -36,8 +36,10 @@ class Table:
|
|||||||
async def schema(self) -> pa.Schema: ...
|
async def schema(self) -> pa.Schema: ...
|
||||||
async def add(
|
async def add(
|
||||||
self, data: pa.RecordBatchReader, mode: Literal["append", "overwrite"]
|
self, data: pa.RecordBatchReader, mode: Literal["append", "overwrite"]
|
||||||
) -> None: ...
|
) -> AddResult: ...
|
||||||
async def update(self, updates: Dict[str, str], where: Optional[str]) -> None: ...
|
async def update(
|
||||||
|
self, updates: Dict[str, str], where: Optional[str]
|
||||||
|
) -> UpdateResult: ...
|
||||||
async def count_rows(self, filter: Optional[str]) -> int: ...
|
async def count_rows(self, filter: Optional[str]) -> int: ...
|
||||||
async def create_index(
|
async def create_index(
|
||||||
self,
|
self,
|
||||||
@@ -49,12 +51,14 @@ class Table:
|
|||||||
async def version(self) -> int: ...
|
async def version(self) -> int: ...
|
||||||
async def checkout(self, version: Union[int, str]): ...
|
async def checkout(self, version: Union[int, str]): ...
|
||||||
async def checkout_latest(self): ...
|
async def checkout_latest(self): ...
|
||||||
async def restore(self, version: Optional[int] = None): ...
|
async def restore(self, version: Optional[Union[int, str]] = None): ...
|
||||||
async def list_indices(self) -> list[IndexConfig]: ...
|
async def list_indices(self) -> list[IndexConfig]: ...
|
||||||
async def delete(self, filter: str): ...
|
async def delete(self, filter: str) -> DeleteResult: ...
|
||||||
async def add_columns(self, columns: list[tuple[str, str]]) -> None: ...
|
async def add_columns(self, columns: list[tuple[str, str]]) -> AddColumnsResult: ...
|
||||||
async def add_columns_with_schema(self, schema: pa.Schema) -> None: ...
|
async def add_columns_with_schema(self, schema: pa.Schema) -> AddColumnsResult: ...
|
||||||
async def alter_columns(self, columns: list[dict[str, Any]]) -> None: ...
|
async def alter_columns(
|
||||||
|
self, columns: list[dict[str, Any]]
|
||||||
|
) -> AlterColumnsResult: ...
|
||||||
async def optimize(
|
async def optimize(
|
||||||
self,
|
self,
|
||||||
*,
|
*,
|
||||||
@@ -139,6 +143,8 @@ class VectorQuery:
|
|||||||
def postfilter(self): ...
|
def postfilter(self): ...
|
||||||
def refine_factor(self, refine_factor: int): ...
|
def refine_factor(self, refine_factor: int): ...
|
||||||
def nprobes(self, nprobes: int): ...
|
def nprobes(self, nprobes: int): ...
|
||||||
|
def minimum_nprobes(self, minimum_nprobes: int): ...
|
||||||
|
def maximum_nprobes(self, maximum_nprobes: int): ...
|
||||||
def bypass_vector_index(self): ...
|
def bypass_vector_index(self): ...
|
||||||
def nearest_to_text(self, query: dict) -> HybridQuery: ...
|
def nearest_to_text(self, query: dict) -> HybridQuery: ...
|
||||||
def to_query_request(self) -> PyQueryRequest: ...
|
def to_query_request(self) -> PyQueryRequest: ...
|
||||||
@@ -154,6 +160,8 @@ class HybridQuery:
|
|||||||
def distance_type(self, distance_type: str): ...
|
def distance_type(self, distance_type: str): ...
|
||||||
def refine_factor(self, refine_factor: int): ...
|
def refine_factor(self, refine_factor: int): ...
|
||||||
def nprobes(self, nprobes: int): ...
|
def nprobes(self, nprobes: int): ...
|
||||||
|
def minimum_nprobes(self, minimum_nprobes: int): ...
|
||||||
|
def maximum_nprobes(self, maximum_nprobes: int): ...
|
||||||
def bypass_vector_index(self): ...
|
def bypass_vector_index(self): ...
|
||||||
def to_vector_query(self) -> VectorQuery: ...
|
def to_vector_query(self) -> VectorQuery: ...
|
||||||
def to_fts_query(self) -> FTSQuery: ...
|
def to_fts_query(self) -> FTSQuery: ...
|
||||||
@@ -161,23 +169,21 @@ class HybridQuery:
|
|||||||
def get_with_row_id(self) -> bool: ...
|
def get_with_row_id(self) -> bool: ...
|
||||||
def to_query_request(self) -> PyQueryRequest: ...
|
def to_query_request(self) -> PyQueryRequest: ...
|
||||||
|
|
||||||
class PyFullTextSearchQuery:
|
class FullTextQuery:
|
||||||
columns: Optional[List[str]]
|
pass
|
||||||
query: str
|
|
||||||
limit: Optional[int]
|
|
||||||
wand_factor: Optional[float]
|
|
||||||
|
|
||||||
class PyQueryRequest:
|
class PyQueryRequest:
|
||||||
limit: Optional[int]
|
limit: Optional[int]
|
||||||
offset: Optional[int]
|
offset: Optional[int]
|
||||||
filter: Optional[Union[str, bytes]]
|
filter: Optional[Union[str, bytes]]
|
||||||
full_text_search: Optional[PyFullTextSearchQuery]
|
full_text_search: Optional[FullTextQuery]
|
||||||
select: Optional[Union[str, List[str]]]
|
select: Optional[Union[str, List[str]]]
|
||||||
fast_search: Optional[bool]
|
fast_search: Optional[bool]
|
||||||
with_row_id: Optional[bool]
|
with_row_id: Optional[bool]
|
||||||
column: Optional[str]
|
column: Optional[str]
|
||||||
query_vector: Optional[List[pa.Array]]
|
query_vector: Optional[List[pa.Array]]
|
||||||
nprobes: Optional[int]
|
minimum_nprobes: Optional[int]
|
||||||
|
maximum_nprobes: Optional[int]
|
||||||
lower_bound: Optional[float]
|
lower_bound: Optional[float]
|
||||||
upper_bound: Optional[float]
|
upper_bound: Optional[float]
|
||||||
ef: Optional[int]
|
ef: Optional[int]
|
||||||
@@ -208,3 +214,28 @@ class OptimizeStats:
|
|||||||
class Tag(TypedDict):
|
class Tag(TypedDict):
|
||||||
version: int
|
version: int
|
||||||
manifest_size: int
|
manifest_size: int
|
||||||
|
|
||||||
|
class AddResult:
|
||||||
|
version: int
|
||||||
|
|
||||||
|
class DeleteResult:
|
||||||
|
version: int
|
||||||
|
|
||||||
|
class UpdateResult:
|
||||||
|
rows_updated: int
|
||||||
|
version: int
|
||||||
|
|
||||||
|
class MergeResult:
|
||||||
|
version: int
|
||||||
|
num_updated_rows: int
|
||||||
|
num_inserted_rows: int
|
||||||
|
num_deleted_rows: int
|
||||||
|
|
||||||
|
class AddColumnsResult:
|
||||||
|
version: int
|
||||||
|
|
||||||
|
class AlterColumnsResult:
|
||||||
|
version: int
|
||||||
|
|
||||||
|
class DropColumnsResult:
|
||||||
|
version: int
|
||||||
|
|||||||
@@ -102,7 +102,7 @@ class FTS:
|
|||||||
|
|
||||||
Attributes
|
Attributes
|
||||||
----------
|
----------
|
||||||
with_position : bool, default True
|
with_position : bool, default False
|
||||||
Whether to store the position of the token in the document. Setting this
|
Whether to store the position of the token in the document. Setting this
|
||||||
to False can reduce the size of the index and improve indexing speed,
|
to False can reduce the size of the index and improve indexing speed,
|
||||||
but it will disable support for phrase queries.
|
but it will disable support for phrase queries.
|
||||||
@@ -118,25 +118,25 @@ class FTS:
|
|||||||
ignored.
|
ignored.
|
||||||
lower_case : bool, default True
|
lower_case : bool, default True
|
||||||
Whether to convert the token to lower case. This makes queries case-insensitive.
|
Whether to convert the token to lower case. This makes queries case-insensitive.
|
||||||
stem : bool, default False
|
stem : bool, default True
|
||||||
Whether to stem the token. Stemming reduces words to their root form.
|
Whether to stem the token. Stemming reduces words to their root form.
|
||||||
For example, in English "running" and "runs" would both be reduced to "run".
|
For example, in English "running" and "runs" would both be reduced to "run".
|
||||||
remove_stop_words : bool, default False
|
remove_stop_words : bool, default True
|
||||||
Whether to remove stop words. Stop words are common words that are often
|
Whether to remove stop words. Stop words are common words that are often
|
||||||
removed from text before indexing. For example, in English "the" and "and".
|
removed from text before indexing. For example, in English "the" and "and".
|
||||||
ascii_folding : bool, default False
|
ascii_folding : bool, default True
|
||||||
Whether to fold ASCII characters. This converts accented characters to
|
Whether to fold ASCII characters. This converts accented characters to
|
||||||
their ASCII equivalent. For example, "café" would be converted to "cafe".
|
their ASCII equivalent. For example, "café" would be converted to "cafe".
|
||||||
"""
|
"""
|
||||||
|
|
||||||
with_position: bool = True
|
with_position: bool = False
|
||||||
base_tokenizer: Literal["simple", "raw", "whitespace"] = "simple"
|
base_tokenizer: Literal["simple", "raw", "whitespace"] = "simple"
|
||||||
language: str = "English"
|
language: str = "English"
|
||||||
max_token_length: Optional[int] = 40
|
max_token_length: Optional[int] = 40
|
||||||
lower_case: bool = True
|
lower_case: bool = True
|
||||||
stem: bool = False
|
stem: bool = True
|
||||||
remove_stop_words: bool = False
|
remove_stop_words: bool = True
|
||||||
ascii_folding: bool = False
|
ascii_folding: bool = True
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
|
|||||||
@@ -4,10 +4,14 @@
|
|||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from datetime import timedelta
|
||||||
from typing import TYPE_CHECKING, List, Optional
|
from typing import TYPE_CHECKING, List, Optional
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from .common import DATA
|
from .common import DATA
|
||||||
|
from ._lancedb import (
|
||||||
|
MergeInsertResult,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class LanceMergeInsertBuilder(object):
|
class LanceMergeInsertBuilder(object):
|
||||||
@@ -28,6 +32,7 @@ class LanceMergeInsertBuilder(object):
|
|||||||
self._when_not_matched_insert_all = False
|
self._when_not_matched_insert_all = False
|
||||||
self._when_not_matched_by_source_delete = False
|
self._when_not_matched_by_source_delete = False
|
||||||
self._when_not_matched_by_source_condition = None
|
self._when_not_matched_by_source_condition = None
|
||||||
|
self._timeout = None
|
||||||
|
|
||||||
def when_matched_update_all(
|
def when_matched_update_all(
|
||||||
self, *, where: Optional[str] = None
|
self, *, where: Optional[str] = None
|
||||||
@@ -78,7 +83,8 @@ class LanceMergeInsertBuilder(object):
|
|||||||
new_data: DATA,
|
new_data: DATA,
|
||||||
on_bad_vectors: str = "error",
|
on_bad_vectors: str = "error",
|
||||||
fill_value: float = 0.0,
|
fill_value: float = 0.0,
|
||||||
):
|
timeout: Optional[timedelta] = None,
|
||||||
|
) -> MergeInsertResult:
|
||||||
"""
|
"""
|
||||||
Executes the merge insert operation
|
Executes the merge insert operation
|
||||||
|
|
||||||
@@ -95,5 +101,24 @@ class LanceMergeInsertBuilder(object):
|
|||||||
One of "error", "drop", "fill".
|
One of "error", "drop", "fill".
|
||||||
fill_value: float, default 0.
|
fill_value: float, default 0.
|
||||||
The value to use when filling vectors. Only used if on_bad_vectors="fill".
|
The value to use when filling vectors. Only used if on_bad_vectors="fill".
|
||||||
|
timeout: Optional[timedelta], default None
|
||||||
|
Maximum time to run the operation before cancelling it.
|
||||||
|
|
||||||
|
By default, there is a 30-second timeout that is only enforced after the
|
||||||
|
first attempt. This is to prevent spending too long retrying to resolve
|
||||||
|
conflicts. For example, if a write attempt takes 20 seconds and fails,
|
||||||
|
the second attempt will be cancelled after 10 seconds, hitting the
|
||||||
|
30-second timeout. However, a write that takes one hour and succeeds on the
|
||||||
|
first attempt will not be cancelled.
|
||||||
|
|
||||||
|
When this is set, the timeout is enforced on all attempts, including
|
||||||
|
the first.
|
||||||
|
|
||||||
|
Returns
|
||||||
|
-------
|
||||||
|
MergeInsertResult
|
||||||
|
version: the new version number of the table after doing merge insert.
|
||||||
"""
|
"""
|
||||||
|
if timeout is not None:
|
||||||
|
self._timeout = timeout
|
||||||
return self._table._do_merge(self, new_data, on_bad_vectors, fill_value)
|
return self._table._do_merge(self, new_data, on_bad_vectors, fill_value)
|
||||||
|
|||||||
@@ -415,6 +415,7 @@ class LanceModel(pydantic.BaseModel):
|
|||||||
>>> table.add([
|
>>> table.add([
|
||||||
... TestModel(name="test", vector=[1.0, 2.0])
|
... TestModel(name="test", vector=[1.0, 2.0])
|
||||||
... ])
|
... ])
|
||||||
|
AddResult(version=2)
|
||||||
>>> table.search([0., 0.]).limit(1).to_pydantic(TestModel)
|
>>> table.search([0., 0.]).limit(1).to_pydantic(TestModel)
|
||||||
[TestModel(name='test', vector=FixedSizeList(dim=2))]
|
[TestModel(name='test', vector=FixedSizeList(dim=2))]
|
||||||
"""
|
"""
|
||||||
|
|||||||
@@ -4,7 +4,6 @@
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from abc import ABC, abstractmethod
|
from abc import ABC, abstractmethod
|
||||||
import abc
|
|
||||||
from concurrent.futures import ThreadPoolExecutor
|
from concurrent.futures import ThreadPoolExecutor
|
||||||
from enum import Enum
|
from enum import Enum
|
||||||
from datetime import timedelta
|
from datetime import timedelta
|
||||||
@@ -88,15 +87,28 @@ def ensure_vector_query(
|
|||||||
return val
|
return val
|
||||||
|
|
||||||
|
|
||||||
class FullTextQueryType(Enum):
|
class FullTextQueryType(str, Enum):
|
||||||
MATCH = "match"
|
MATCH = "match"
|
||||||
MATCH_PHRASE = "match_phrase"
|
MATCH_PHRASE = "match_phrase"
|
||||||
BOOST = "boost"
|
BOOST = "boost"
|
||||||
MULTI_MATCH = "multi_match"
|
MULTI_MATCH = "multi_match"
|
||||||
|
BOOLEAN = "boolean"
|
||||||
|
|
||||||
|
|
||||||
class FullTextQuery(abc.ABC, pydantic.BaseModel):
|
class FullTextOperator(str, Enum):
|
||||||
@abc.abstractmethod
|
AND = "AND"
|
||||||
|
OR = "OR"
|
||||||
|
|
||||||
|
|
||||||
|
class Occur(str, Enum):
|
||||||
|
SHOULD = "SHOULD"
|
||||||
|
MUST = "MUST"
|
||||||
|
MUST_NOT = "MUST_NOT"
|
||||||
|
|
||||||
|
|
||||||
|
@pydantic.dataclasses.dataclass
|
||||||
|
class FullTextQuery(ABC):
|
||||||
|
@abstractmethod
|
||||||
def query_type(self) -> FullTextQueryType:
|
def query_type(self) -> FullTextQueryType:
|
||||||
"""
|
"""
|
||||||
Get the query type of the query.
|
Get the query type of the query.
|
||||||
@@ -106,193 +118,178 @@ class FullTextQuery(abc.ABC, pydantic.BaseModel):
|
|||||||
str
|
str
|
||||||
The type of the query.
|
The type of the query.
|
||||||
"""
|
"""
|
||||||
|
pass
|
||||||
|
|
||||||
@abc.abstractmethod
|
def __and__(self, other: "FullTextQuery") -> "FullTextQuery":
|
||||||
def to_dict(self) -> dict:
|
|
||||||
"""
|
"""
|
||||||
Convert the query to a dictionary.
|
Combine two queries with a logical AND operation.
|
||||||
|
|
||||||
Returns
|
|
||||||
-------
|
|
||||||
dict
|
|
||||||
The query as a dictionary.
|
|
||||||
"""
|
|
||||||
|
|
||||||
|
|
||||||
class MatchQuery(FullTextQuery):
|
|
||||||
query: str
|
|
||||||
column: str
|
|
||||||
boost: float = 1.0
|
|
||||||
fuzziness: int = 0
|
|
||||||
max_expansions: int = 50
|
|
||||||
|
|
||||||
def __init__(
|
|
||||||
self,
|
|
||||||
query: str,
|
|
||||||
column: str,
|
|
||||||
*,
|
|
||||||
boost: float = 1.0,
|
|
||||||
fuzziness: int = 0,
|
|
||||||
max_expansions: int = 50,
|
|
||||||
):
|
|
||||||
"""
|
|
||||||
Match query for full-text search.
|
|
||||||
|
|
||||||
Parameters
|
Parameters
|
||||||
----------
|
----------
|
||||||
query : str
|
other : FullTextQuery
|
||||||
The query string to match against.
|
The other query to combine with.
|
||||||
column : str
|
|
||||||
The name of the column to match against.
|
Returns
|
||||||
boost : float, default 1.0
|
-------
|
||||||
The boost factor for the query.
|
FullTextQuery
|
||||||
The score of each matching document is multiplied by this value.
|
A new query that combines both queries with AND.
|
||||||
fuzziness : int, optional
|
|
||||||
The maximum edit distance for each term in the match query.
|
|
||||||
Defaults to 0 (exact match).
|
|
||||||
If None, fuzziness is applied automatically by the rules:
|
|
||||||
- 0 for terms with length <= 2
|
|
||||||
- 1 for terms with length <= 5
|
|
||||||
- 2 for terms with length > 5
|
|
||||||
max_expansions : int, optional
|
|
||||||
The maximum number of terms to consider for fuzzy matching.
|
|
||||||
Defaults to 50.
|
|
||||||
"""
|
"""
|
||||||
super().__init__(
|
return BooleanQuery([(Occur.MUST, self), (Occur.MUST, other)])
|
||||||
query=query,
|
|
||||||
column=column,
|
def __or__(self, other: "FullTextQuery") -> "FullTextQuery":
|
||||||
boost=boost,
|
"""
|
||||||
fuzziness=fuzziness,
|
Combine two queries with a logical OR operation.
|
||||||
max_expansions=max_expansions,
|
|
||||||
)
|
Parameters
|
||||||
|
----------
|
||||||
|
other : FullTextQuery
|
||||||
|
The other query to combine with.
|
||||||
|
|
||||||
|
Returns
|
||||||
|
-------
|
||||||
|
FullTextQuery
|
||||||
|
A new query that combines both queries with OR.
|
||||||
|
"""
|
||||||
|
return BooleanQuery([(Occur.SHOULD, self), (Occur.SHOULD, other)])
|
||||||
|
|
||||||
|
|
||||||
|
@pydantic.dataclasses.dataclass
|
||||||
|
class MatchQuery(FullTextQuery):
|
||||||
|
"""
|
||||||
|
Match query for full-text search.
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
query : str
|
||||||
|
The query string to match against.
|
||||||
|
column : str
|
||||||
|
The name of the column to match against.
|
||||||
|
boost : float, default 1.0
|
||||||
|
The boost factor for the query.
|
||||||
|
The score of each matching document is multiplied by this value.
|
||||||
|
fuzziness : int, optional
|
||||||
|
The maximum edit distance for each term in the match query.
|
||||||
|
Defaults to 0 (exact match).
|
||||||
|
If None, fuzziness is applied automatically by the rules:
|
||||||
|
- 0 for terms with length <= 2
|
||||||
|
- 1 for terms with length <= 5
|
||||||
|
- 2 for terms with length > 5
|
||||||
|
max_expansions : int, optional
|
||||||
|
The maximum number of terms to consider for fuzzy matching.
|
||||||
|
Defaults to 50.
|
||||||
|
operator : FullTextOperator, default OR
|
||||||
|
The operator to use for combining the query results.
|
||||||
|
Can be either `AND` or `OR`.
|
||||||
|
If `AND`, all terms in the query must match.
|
||||||
|
If `OR`, at least one term in the query must match.
|
||||||
|
prefix_length : int, optional
|
||||||
|
The number of beginning characters being unchanged for fuzzy matching.
|
||||||
|
This is useful to achieve prefix matching.
|
||||||
|
"""
|
||||||
|
|
||||||
|
query: str
|
||||||
|
column: str
|
||||||
|
boost: float = pydantic.Field(1.0, kw_only=True)
|
||||||
|
fuzziness: int = pydantic.Field(0, kw_only=True)
|
||||||
|
max_expansions: int = pydantic.Field(50, kw_only=True)
|
||||||
|
operator: FullTextOperator = pydantic.Field(FullTextOperator.OR, kw_only=True)
|
||||||
|
prefix_length: int = pydantic.Field(0, kw_only=True)
|
||||||
|
|
||||||
def query_type(self) -> FullTextQueryType:
|
def query_type(self) -> FullTextQueryType:
|
||||||
return FullTextQueryType.MATCH
|
return FullTextQueryType.MATCH
|
||||||
|
|
||||||
def to_dict(self) -> dict:
|
|
||||||
return {
|
|
||||||
"match": {
|
|
||||||
self.column: {
|
|
||||||
"query": self.query,
|
|
||||||
"boost": self.boost,
|
|
||||||
"fuzziness": self.fuzziness,
|
|
||||||
"max_expansions": self.max_expansions,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
@pydantic.dataclasses.dataclass
|
||||||
class PhraseQuery(FullTextQuery):
|
class PhraseQuery(FullTextQuery):
|
||||||
|
"""
|
||||||
|
Phrase query for full-text search.
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
query : str
|
||||||
|
The query string to match against.
|
||||||
|
column : str
|
||||||
|
The name of the column to match against.
|
||||||
|
"""
|
||||||
|
|
||||||
query: str
|
query: str
|
||||||
column: str
|
column: str
|
||||||
|
slop: int = pydantic.Field(0, kw_only=True)
|
||||||
def __init__(self, query: str, column: str):
|
|
||||||
"""
|
|
||||||
Phrase query for full-text search.
|
|
||||||
|
|
||||||
Parameters
|
|
||||||
----------
|
|
||||||
query : str
|
|
||||||
The query string to match against.
|
|
||||||
column : str
|
|
||||||
The name of the column to match against.
|
|
||||||
"""
|
|
||||||
super().__init__(query=query, column=column)
|
|
||||||
|
|
||||||
def query_type(self) -> FullTextQueryType:
|
def query_type(self) -> FullTextQueryType:
|
||||||
return FullTextQueryType.MATCH_PHRASE
|
return FullTextQueryType.MATCH_PHRASE
|
||||||
|
|
||||||
def to_dict(self) -> dict:
|
|
||||||
return {
|
|
||||||
"match_phrase": {
|
|
||||||
self.column: self.query,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
@pydantic.dataclasses.dataclass
|
||||||
class BoostQuery(FullTextQuery):
|
class BoostQuery(FullTextQuery):
|
||||||
|
"""
|
||||||
|
Boost query for full-text search.
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
positive : dict
|
||||||
|
The positive query object.
|
||||||
|
negative : dict
|
||||||
|
The negative query object.
|
||||||
|
negative_boost : float, default 0.5
|
||||||
|
The boost factor for the negative query.
|
||||||
|
"""
|
||||||
|
|
||||||
positive: FullTextQuery
|
positive: FullTextQuery
|
||||||
negative: FullTextQuery
|
negative: FullTextQuery
|
||||||
negative_boost: float = 0.5
|
negative_boost: float = pydantic.Field(0.5, kw_only=True)
|
||||||
|
|
||||||
def __init__(
|
|
||||||
self,
|
|
||||||
positive: FullTextQuery,
|
|
||||||
negative: FullTextQuery,
|
|
||||||
*,
|
|
||||||
negative_boost: float = 0.5,
|
|
||||||
):
|
|
||||||
"""
|
|
||||||
Boost query for full-text search.
|
|
||||||
|
|
||||||
Parameters
|
|
||||||
----------
|
|
||||||
positive : dict
|
|
||||||
The positive query object.
|
|
||||||
negative : dict
|
|
||||||
The negative query object.
|
|
||||||
negative_boost : float
|
|
||||||
The boost factor for the negative query.
|
|
||||||
"""
|
|
||||||
super().__init__(
|
|
||||||
positive=positive, negative=negative, negative_boost=negative_boost
|
|
||||||
)
|
|
||||||
|
|
||||||
def query_type(self) -> FullTextQueryType:
|
def query_type(self) -> FullTextQueryType:
|
||||||
return FullTextQueryType.BOOST
|
return FullTextQueryType.BOOST
|
||||||
|
|
||||||
def to_dict(self) -> dict:
|
|
||||||
return {
|
|
||||||
"boost": {
|
|
||||||
"positive": self.positive.to_dict(),
|
|
||||||
"negative": self.negative.to_dict(),
|
|
||||||
"negative_boost": self.negative_boost,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
@pydantic.dataclasses.dataclass
|
||||||
class MultiMatchQuery(FullTextQuery):
|
class MultiMatchQuery(FullTextQuery):
|
||||||
|
"""
|
||||||
|
Multi-match query for full-text search.
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
query : str | list[Query]
|
||||||
|
If a string, the query string to match against.
|
||||||
|
columns : list[str]
|
||||||
|
The list of columns to match against.
|
||||||
|
boosts : list[float], optional
|
||||||
|
The list of boost factors for each column. If not provided,
|
||||||
|
all columns will have the same boost factor.
|
||||||
|
operator : FullTextOperator, default OR
|
||||||
|
The operator to use for combining the query results.
|
||||||
|
Can be either `AND` or `OR`.
|
||||||
|
It would be applied to all columns individually.
|
||||||
|
For example, if the operator is `AND`,
|
||||||
|
then the query "hello world" is equal to
|
||||||
|
`match("hello AND world", column1) OR match("hello AND world", column2)`.
|
||||||
|
"""
|
||||||
|
|
||||||
query: str
|
query: str
|
||||||
columns: list[str]
|
columns: list[str]
|
||||||
boosts: list[float]
|
boosts: Optional[list[float]] = pydantic.Field(None, kw_only=True)
|
||||||
|
operator: FullTextOperator = pydantic.Field(FullTextOperator.OR, kw_only=True)
|
||||||
def __init__(
|
|
||||||
self,
|
|
||||||
query: str,
|
|
||||||
columns: list[str],
|
|
||||||
*,
|
|
||||||
boosts: Optional[list[float]] = None,
|
|
||||||
):
|
|
||||||
"""
|
|
||||||
Multi-match query for full-text search.
|
|
||||||
|
|
||||||
Parameters
|
|
||||||
----------
|
|
||||||
query : str
|
|
||||||
The query string to match against.
|
|
||||||
|
|
||||||
columns : list[str]
|
|
||||||
The list of columns to match against.
|
|
||||||
|
|
||||||
boosts : list[float], optional
|
|
||||||
The list of boost factors for each column. If not provided,
|
|
||||||
all columns will have the same boost factor.
|
|
||||||
"""
|
|
||||||
if boosts is None:
|
|
||||||
boosts = [1.0] * len(columns)
|
|
||||||
super().__init__(query=query, columns=columns, boosts=boosts)
|
|
||||||
|
|
||||||
def query_type(self) -> FullTextQueryType:
|
def query_type(self) -> FullTextQueryType:
|
||||||
return FullTextQueryType.MULTI_MATCH
|
return FullTextQueryType.MULTI_MATCH
|
||||||
|
|
||||||
def to_dict(self) -> dict:
|
|
||||||
return {
|
@pydantic.dataclasses.dataclass
|
||||||
"multi_match": {
|
class BooleanQuery(FullTextQuery):
|
||||||
"query": self.query,
|
"""
|
||||||
"columns": self.columns,
|
Boolean query for full-text search.
|
||||||
"boost": self.boosts,
|
|
||||||
}
|
Parameters
|
||||||
}
|
----------
|
||||||
|
queries : list[tuple(Occur, FullTextQuery)]
|
||||||
|
The list of queries with their occurrence requirements.
|
||||||
|
"""
|
||||||
|
|
||||||
|
queries: list[tuple[Occur, FullTextQuery]]
|
||||||
|
|
||||||
|
def query_type(self) -> FullTextQueryType:
|
||||||
|
return FullTextQueryType.BOOLEAN
|
||||||
|
|
||||||
|
|
||||||
class FullTextSearchQuery(pydantic.BaseModel):
|
class FullTextSearchQuery(pydantic.BaseModel):
|
||||||
@@ -445,8 +442,18 @@ class Query(pydantic.BaseModel):
|
|||||||
# which columns to return in the results
|
# which columns to return in the results
|
||||||
columns: Optional[Union[List[str], Dict[str, str]]] = None
|
columns: Optional[Union[List[str], Dict[str, str]]] = None
|
||||||
|
|
||||||
# number of IVF partitions to search
|
# minimum number of IVF partitions to search
|
||||||
nprobes: Optional[int] = None
|
#
|
||||||
|
# If None then a default value (20) will be used.
|
||||||
|
minimum_nprobes: Optional[int] = None
|
||||||
|
|
||||||
|
# maximum number of IVF partitions to search
|
||||||
|
#
|
||||||
|
# If None then a default value (20) will be used.
|
||||||
|
#
|
||||||
|
# If 0 then no limit will be applied and all partitions could be searched
|
||||||
|
# if needed to satisfy the limit.
|
||||||
|
maximum_nprobes: Optional[int] = None
|
||||||
|
|
||||||
# lower bound for distance search
|
# lower bound for distance search
|
||||||
lower_bound: Optional[float] = None
|
lower_bound: Optional[float] = None
|
||||||
@@ -484,7 +491,8 @@ class Query(pydantic.BaseModel):
|
|||||||
query.vector_column = req.column
|
query.vector_column = req.column
|
||||||
query.vector = req.query_vector
|
query.vector = req.query_vector
|
||||||
query.distance_type = req.distance_type
|
query.distance_type = req.distance_type
|
||||||
query.nprobes = req.nprobes
|
query.minimum_nprobes = req.minimum_nprobes
|
||||||
|
query.maximum_nprobes = req.maximum_nprobes
|
||||||
query.lower_bound = req.lower_bound
|
query.lower_bound = req.lower_bound
|
||||||
query.upper_bound = req.upper_bound
|
query.upper_bound = req.upper_bound
|
||||||
query.ef = req.ef
|
query.ef = req.ef
|
||||||
@@ -493,10 +501,8 @@ class Query(pydantic.BaseModel):
|
|||||||
query.postfilter = req.postfilter
|
query.postfilter = req.postfilter
|
||||||
if req.full_text_search is not None:
|
if req.full_text_search is not None:
|
||||||
query.full_text_query = FullTextSearchQuery(
|
query.full_text_query = FullTextSearchQuery(
|
||||||
columns=req.full_text_search.columns,
|
columns=None,
|
||||||
query=req.full_text_search.query,
|
query=req.full_text_search,
|
||||||
limit=req.full_text_search.limit,
|
|
||||||
wand_factor=req.full_text_search.wand_factor,
|
|
||||||
)
|
)
|
||||||
return query
|
return query
|
||||||
|
|
||||||
@@ -1047,7 +1053,8 @@ class LanceVectorQueryBuilder(LanceQueryBuilder):
|
|||||||
super().__init__(table)
|
super().__init__(table)
|
||||||
self._query = query
|
self._query = query
|
||||||
self._distance_type = None
|
self._distance_type = None
|
||||||
self._nprobes = None
|
self._minimum_nprobes = None
|
||||||
|
self._maximum_nprobes = None
|
||||||
self._lower_bound = None
|
self._lower_bound = None
|
||||||
self._upper_bound = None
|
self._upper_bound = None
|
||||||
self._refine_factor = None
|
self._refine_factor = None
|
||||||
@@ -1110,6 +1117,10 @@ class LanceVectorQueryBuilder(LanceQueryBuilder):
|
|||||||
See discussion in [Querying an ANN Index][querying-an-ann-index] for
|
See discussion in [Querying an ANN Index][querying-an-ann-index] for
|
||||||
tuning advice.
|
tuning advice.
|
||||||
|
|
||||||
|
This method sets both the minimum and maximum number of probes to the same
|
||||||
|
value. See `minimum_nprobes` and `maximum_nprobes` for more fine-grained
|
||||||
|
control.
|
||||||
|
|
||||||
Parameters
|
Parameters
|
||||||
----------
|
----------
|
||||||
nprobes: int
|
nprobes: int
|
||||||
@@ -1120,7 +1131,36 @@ class LanceVectorQueryBuilder(LanceQueryBuilder):
|
|||||||
LanceVectorQueryBuilder
|
LanceVectorQueryBuilder
|
||||||
The LanceQueryBuilder object.
|
The LanceQueryBuilder object.
|
||||||
"""
|
"""
|
||||||
self._nprobes = nprobes
|
self._minimum_nprobes = nprobes
|
||||||
|
self._maximum_nprobes = nprobes
|
||||||
|
return self
|
||||||
|
|
||||||
|
def minimum_nprobes(self, minimum_nprobes: int) -> LanceVectorQueryBuilder:
|
||||||
|
"""Set the minimum number of probes to use.
|
||||||
|
|
||||||
|
See `nprobes` for more details.
|
||||||
|
|
||||||
|
These partitions will be searched on every vector query and will increase recall
|
||||||
|
at the expense of latency.
|
||||||
|
"""
|
||||||
|
self._minimum_nprobes = minimum_nprobes
|
||||||
|
return self
|
||||||
|
|
||||||
|
def maximum_nprobes(self, maximum_nprobes: int) -> LanceVectorQueryBuilder:
|
||||||
|
"""Set the maximum number of probes to use.
|
||||||
|
|
||||||
|
See `nprobes` for more details.
|
||||||
|
|
||||||
|
If this value is greater than `minimum_nprobes` then the excess partitions
|
||||||
|
will be searched only if we have not found enough results.
|
||||||
|
|
||||||
|
This can be useful when there is a narrow filter to allow these queries to
|
||||||
|
spend more time searching and avoid potential false negatives.
|
||||||
|
|
||||||
|
If this value is 0 then no limit will be applied and all partitions could be
|
||||||
|
searched if needed to satisfy the limit.
|
||||||
|
"""
|
||||||
|
self._maximum_nprobes = maximum_nprobes
|
||||||
return self
|
return self
|
||||||
|
|
||||||
def distance_range(
|
def distance_range(
|
||||||
@@ -1224,7 +1264,8 @@ class LanceVectorQueryBuilder(LanceQueryBuilder):
|
|||||||
limit=self._limit,
|
limit=self._limit,
|
||||||
distance_type=self._distance_type,
|
distance_type=self._distance_type,
|
||||||
columns=self._columns,
|
columns=self._columns,
|
||||||
nprobes=self._nprobes,
|
minimum_nprobes=self._minimum_nprobes,
|
||||||
|
maximum_nprobes=self._maximum_nprobes,
|
||||||
lower_bound=self._lower_bound,
|
lower_bound=self._lower_bound,
|
||||||
upper_bound=self._upper_bound,
|
upper_bound=self._upper_bound,
|
||||||
refine_factor=self._refine_factor,
|
refine_factor=self._refine_factor,
|
||||||
@@ -1410,10 +1451,13 @@ class LanceFtsQueryBuilder(LanceQueryBuilder):
|
|||||||
|
|
||||||
query = self._query
|
query = self._query
|
||||||
if self._phrase_query:
|
if self._phrase_query:
|
||||||
raise NotImplementedError(
|
if isinstance(query, str):
|
||||||
"Phrase query is not yet supported in Lance FTS. "
|
if not query.startswith('"') or not query.endswith('"'):
|
||||||
"Use tantivy-based index instead for now."
|
query = f'"{query}"'
|
||||||
)
|
elif isinstance(query, FullTextQuery) and not isinstance(
|
||||||
|
query, PhraseQuery
|
||||||
|
):
|
||||||
|
raise TypeError("Please use PhraseQuery for phrase queries.")
|
||||||
query = self.to_query_object()
|
query = self.to_query_object()
|
||||||
results = self._table._execute_query(query, timeout=timeout)
|
results = self._table._execute_query(query, timeout=timeout)
|
||||||
results = results.read_all()
|
results = results.read_all()
|
||||||
@@ -1588,7 +1632,8 @@ class LanceHybridQueryBuilder(LanceQueryBuilder):
|
|||||||
self._fts_columns = fts_columns
|
self._fts_columns = fts_columns
|
||||||
self._norm = None
|
self._norm = None
|
||||||
self._reranker = None
|
self._reranker = None
|
||||||
self._nprobes = None
|
self._minimum_nprobes = None
|
||||||
|
self._maximum_nprobes = None
|
||||||
self._refine_factor = None
|
self._refine_factor = None
|
||||||
self._distance_type = None
|
self._distance_type = None
|
||||||
self._phrase_query = None
|
self._phrase_query = None
|
||||||
@@ -1820,7 +1865,24 @@ class LanceHybridQueryBuilder(LanceQueryBuilder):
|
|||||||
LanceHybridQueryBuilder
|
LanceHybridQueryBuilder
|
||||||
The LanceHybridQueryBuilder object.
|
The LanceHybridQueryBuilder object.
|
||||||
"""
|
"""
|
||||||
self._nprobes = nprobes
|
self._minimum_nprobes = nprobes
|
||||||
|
self._maximum_nprobes = nprobes
|
||||||
|
return self
|
||||||
|
|
||||||
|
def minimum_nprobes(self, minimum_nprobes: int) -> LanceHybridQueryBuilder:
|
||||||
|
"""Set the minimum number of probes to use.
|
||||||
|
|
||||||
|
See `nprobes` for more details.
|
||||||
|
"""
|
||||||
|
self._minimum_nprobes = minimum_nprobes
|
||||||
|
return self
|
||||||
|
|
||||||
|
def maximum_nprobes(self, maximum_nprobes: int) -> LanceHybridQueryBuilder:
|
||||||
|
"""Set the maximum number of probes to use.
|
||||||
|
|
||||||
|
See `nprobes` for more details.
|
||||||
|
"""
|
||||||
|
self._maximum_nprobes = maximum_nprobes
|
||||||
return self
|
return self
|
||||||
|
|
||||||
def distance_range(
|
def distance_range(
|
||||||
@@ -2049,8 +2111,10 @@ class LanceHybridQueryBuilder(LanceQueryBuilder):
|
|||||||
self._fts_query.phrase_query(True)
|
self._fts_query.phrase_query(True)
|
||||||
if self._distance_type:
|
if self._distance_type:
|
||||||
self._vector_query.metric(self._distance_type)
|
self._vector_query.metric(self._distance_type)
|
||||||
if self._nprobes:
|
if self._minimum_nprobes:
|
||||||
self._vector_query.nprobes(self._nprobes)
|
self._vector_query.minimum_nprobes(self._minimum_nprobes)
|
||||||
|
if self._maximum_nprobes is not None:
|
||||||
|
self._vector_query.maximum_nprobes(self._maximum_nprobes)
|
||||||
if self._refine_factor:
|
if self._refine_factor:
|
||||||
self._vector_query.refine_factor(self._refine_factor)
|
self._vector_query.refine_factor(self._refine_factor)
|
||||||
if self._ef:
|
if self._ef:
|
||||||
@@ -2513,7 +2577,7 @@ class AsyncQuery(AsyncQueryBase):
|
|||||||
self._inner.nearest_to_text({"query": query, "columns": columns})
|
self._inner.nearest_to_text({"query": query, "columns": columns})
|
||||||
)
|
)
|
||||||
# FullTextQuery object
|
# FullTextQuery object
|
||||||
return AsyncFTSQuery(self._inner.nearest_to_text({"query": query.to_dict()}))
|
return AsyncFTSQuery(self._inner.nearest_to_text({"query": query}))
|
||||||
|
|
||||||
|
|
||||||
class AsyncFTSQuery(AsyncQueryBase):
|
class AsyncFTSQuery(AsyncQueryBase):
|
||||||
@@ -2661,6 +2725,34 @@ class AsyncVectorQueryBase:
|
|||||||
self._inner.nprobes(nprobes)
|
self._inner.nprobes(nprobes)
|
||||||
return self
|
return self
|
||||||
|
|
||||||
|
def minimum_nprobes(self, minimum_nprobes: int) -> Self:
|
||||||
|
"""Set the minimum number of probes to use.
|
||||||
|
|
||||||
|
See `nprobes` for more details.
|
||||||
|
|
||||||
|
These partitions will be searched on every indexed vector query and will
|
||||||
|
increase recall at the expense of latency.
|
||||||
|
"""
|
||||||
|
self._inner.minimum_nprobes(minimum_nprobes)
|
||||||
|
return self
|
||||||
|
|
||||||
|
def maximum_nprobes(self, maximum_nprobes: int) -> Self:
|
||||||
|
"""Set the maximum number of probes to use.
|
||||||
|
|
||||||
|
See `nprobes` for more details.
|
||||||
|
|
||||||
|
If this value is greater than `minimum_nprobes` then the excess partitions
|
||||||
|
will be searched only if we have not found enough results.
|
||||||
|
|
||||||
|
This can be useful when there is a narrow filter to allow these queries to
|
||||||
|
spend more time searching and avoid potential false negatives.
|
||||||
|
|
||||||
|
If this value is 0 then no limit will be applied and all partitions could be
|
||||||
|
searched if needed to satisfy the limit.
|
||||||
|
"""
|
||||||
|
self._inner.maximum_nprobes(maximum_nprobes)
|
||||||
|
return self
|
||||||
|
|
||||||
def distance_range(
|
def distance_range(
|
||||||
self, lower_bound: Optional[float] = None, upper_bound: Optional[float] = None
|
self, lower_bound: Optional[float] = None, upper_bound: Optional[float] = None
|
||||||
) -> Self:
|
) -> Self:
|
||||||
@@ -2835,7 +2927,7 @@ class AsyncVectorQuery(AsyncQueryBase, AsyncVectorQueryBase):
|
|||||||
self._inner.nearest_to_text({"query": query, "columns": columns})
|
self._inner.nearest_to_text({"query": query, "columns": columns})
|
||||||
)
|
)
|
||||||
# FullTextQuery object
|
# FullTextQuery object
|
||||||
return AsyncHybridQuery(self._inner.nearest_to_text({"query": query.to_dict()}))
|
return AsyncHybridQuery(self._inner.nearest_to_text({"query": query}))
|
||||||
|
|
||||||
async def to_batches(
|
async def to_batches(
|
||||||
self,
|
self,
|
||||||
|
|||||||
@@ -7,7 +7,16 @@ from functools import cached_property
|
|||||||
from typing import Dict, Iterable, List, Optional, Union, Literal
|
from typing import Dict, Iterable, List, Optional, Union, Literal
|
||||||
import warnings
|
import warnings
|
||||||
|
|
||||||
from lancedb._lancedb import IndexConfig
|
from lancedb._lancedb import (
|
||||||
|
AddColumnsResult,
|
||||||
|
AddResult,
|
||||||
|
AlterColumnsResult,
|
||||||
|
DeleteResult,
|
||||||
|
DropColumnsResult,
|
||||||
|
IndexConfig,
|
||||||
|
MergeResult,
|
||||||
|
UpdateResult,
|
||||||
|
)
|
||||||
from lancedb.embeddings.base import EmbeddingFunctionConfig
|
from lancedb.embeddings.base import EmbeddingFunctionConfig
|
||||||
from lancedb.index import FTS, BTree, Bitmap, HnswPq, HnswSq, IvfFlat, IvfPq, LabelList
|
from lancedb.index import FTS, BTree, Bitmap, HnswPq, HnswSq, IvfFlat, IvfPq, LabelList
|
||||||
from lancedb.remote.db import LOOP
|
from lancedb.remote.db import LOOP
|
||||||
@@ -38,9 +47,6 @@ class RemoteTable(Table):
|
|||||||
def __repr__(self) -> str:
|
def __repr__(self) -> str:
|
||||||
return f"RemoteTable({self.db_name}.{self.name})"
|
return f"RemoteTable({self.db_name}.{self.name})"
|
||||||
|
|
||||||
def __len__(self) -> int:
|
|
||||||
self.count_rows(None)
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def schema(self) -> pa.Schema:
|
def schema(self) -> pa.Schema:
|
||||||
"""The [Arrow Schema](https://arrow.apache.org/docs/python/api/datatypes.html#)
|
"""The [Arrow Schema](https://arrow.apache.org/docs/python/api/datatypes.html#)
|
||||||
@@ -91,7 +97,7 @@ class RemoteTable(Table):
|
|||||||
def checkout_latest(self):
|
def checkout_latest(self):
|
||||||
return LOOP.run(self._table.checkout_latest())
|
return LOOP.run(self._table.checkout_latest())
|
||||||
|
|
||||||
def restore(self, version: Optional[int] = None):
|
def restore(self, version: Optional[Union[int, str]] = None):
|
||||||
return LOOP.run(self._table.restore(version))
|
return LOOP.run(self._table.restore(version))
|
||||||
|
|
||||||
def list_indices(self) -> Iterable[IndexConfig]:
|
def list_indices(self) -> Iterable[IndexConfig]:
|
||||||
@@ -143,15 +149,15 @@ class RemoteTable(Table):
|
|||||||
*,
|
*,
|
||||||
replace: bool = False,
|
replace: bool = False,
|
||||||
wait_timeout: timedelta = None,
|
wait_timeout: timedelta = None,
|
||||||
with_position: bool = True,
|
with_position: bool = False,
|
||||||
# tokenizer configs:
|
# tokenizer configs:
|
||||||
base_tokenizer: str = "simple",
|
base_tokenizer: str = "simple",
|
||||||
language: str = "English",
|
language: str = "English",
|
||||||
max_token_length: Optional[int] = 40,
|
max_token_length: Optional[int] = 40,
|
||||||
lower_case: bool = True,
|
lower_case: bool = True,
|
||||||
stem: bool = False,
|
stem: bool = True,
|
||||||
remove_stop_words: bool = False,
|
remove_stop_words: bool = True,
|
||||||
ascii_folding: bool = False,
|
ascii_folding: bool = True,
|
||||||
):
|
):
|
||||||
config = FTS(
|
config = FTS(
|
||||||
with_position=with_position,
|
with_position=with_position,
|
||||||
@@ -263,7 +269,7 @@ class RemoteTable(Table):
|
|||||||
mode: str = "append",
|
mode: str = "append",
|
||||||
on_bad_vectors: str = "error",
|
on_bad_vectors: str = "error",
|
||||||
fill_value: float = 0.0,
|
fill_value: float = 0.0,
|
||||||
) -> int:
|
) -> AddResult:
|
||||||
"""Add more data to the [Table](Table). It has the same API signature as
|
"""Add more data to the [Table](Table). It has the same API signature as
|
||||||
the OSS version.
|
the OSS version.
|
||||||
|
|
||||||
@@ -286,8 +292,12 @@ class RemoteTable(Table):
|
|||||||
fill_value: float, default 0.
|
fill_value: float, default 0.
|
||||||
The value to use when filling vectors. Only used if on_bad_vectors="fill".
|
The value to use when filling vectors. Only used if on_bad_vectors="fill".
|
||||||
|
|
||||||
|
Returns
|
||||||
|
-------
|
||||||
|
AddResult
|
||||||
|
An object containing the new version number of the table after adding data.
|
||||||
"""
|
"""
|
||||||
LOOP.run(
|
return LOOP.run(
|
||||||
self._table.add(
|
self._table.add(
|
||||||
data, mode=mode, on_bad_vectors=on_bad_vectors, fill_value=fill_value
|
data, mode=mode, on_bad_vectors=on_bad_vectors, fill_value=fill_value
|
||||||
)
|
)
|
||||||
@@ -413,10 +423,12 @@ class RemoteTable(Table):
|
|||||||
new_data: DATA,
|
new_data: DATA,
|
||||||
on_bad_vectors: str,
|
on_bad_vectors: str,
|
||||||
fill_value: float,
|
fill_value: float,
|
||||||
):
|
) -> MergeResult:
|
||||||
LOOP.run(self._table._do_merge(merge, new_data, on_bad_vectors, fill_value))
|
return LOOP.run(
|
||||||
|
self._table._do_merge(merge, new_data, on_bad_vectors, fill_value)
|
||||||
|
)
|
||||||
|
|
||||||
def delete(self, predicate: str):
|
def delete(self, predicate: str) -> DeleteResult:
|
||||||
"""Delete rows from the table.
|
"""Delete rows from the table.
|
||||||
|
|
||||||
This can be used to delete a single row, many rows, all rows, or
|
This can be used to delete a single row, many rows, all rows, or
|
||||||
@@ -431,6 +443,11 @@ class RemoteTable(Table):
|
|||||||
|
|
||||||
The filter must not be empty, or it will error.
|
The filter must not be empty, or it will error.
|
||||||
|
|
||||||
|
Returns
|
||||||
|
-------
|
||||||
|
DeleteResult
|
||||||
|
An object containing the new version number of the table after deletion.
|
||||||
|
|
||||||
Examples
|
Examples
|
||||||
--------
|
--------
|
||||||
>>> import lancedb
|
>>> import lancedb
|
||||||
@@ -463,7 +480,7 @@ class RemoteTable(Table):
|
|||||||
x vector _distance # doctest: +SKIP
|
x vector _distance # doctest: +SKIP
|
||||||
0 2 [3.0, 4.0] 85.0 # doctest: +SKIP
|
0 2 [3.0, 4.0] 85.0 # doctest: +SKIP
|
||||||
"""
|
"""
|
||||||
LOOP.run(self._table.delete(predicate))
|
return LOOP.run(self._table.delete(predicate))
|
||||||
|
|
||||||
def update(
|
def update(
|
||||||
self,
|
self,
|
||||||
@@ -471,7 +488,7 @@ class RemoteTable(Table):
|
|||||||
values: Optional[dict] = None,
|
values: Optional[dict] = None,
|
||||||
*,
|
*,
|
||||||
values_sql: Optional[Dict[str, str]] = None,
|
values_sql: Optional[Dict[str, str]] = None,
|
||||||
):
|
) -> UpdateResult:
|
||||||
"""
|
"""
|
||||||
This can be used to update zero to all rows depending on how many
|
This can be used to update zero to all rows depending on how many
|
||||||
rows match the where clause.
|
rows match the where clause.
|
||||||
@@ -489,6 +506,12 @@ class RemoteTable(Table):
|
|||||||
reference existing columns. For example, {"x": "x + 1"} will increment
|
reference existing columns. For example, {"x": "x + 1"} will increment
|
||||||
the x column by 1.
|
the x column by 1.
|
||||||
|
|
||||||
|
Returns
|
||||||
|
-------
|
||||||
|
UpdateResult
|
||||||
|
- rows_updated: The number of rows that were updated
|
||||||
|
- version: The new version number of the table after the update
|
||||||
|
|
||||||
Examples
|
Examples
|
||||||
--------
|
--------
|
||||||
>>> import lancedb
|
>>> import lancedb
|
||||||
@@ -513,7 +536,7 @@ class RemoteTable(Table):
|
|||||||
2 2 [10.0, 10.0] # doctest: +SKIP
|
2 2 [10.0, 10.0] # doctest: +SKIP
|
||||||
|
|
||||||
"""
|
"""
|
||||||
LOOP.run(
|
return LOOP.run(
|
||||||
self._table.update(where=where, updates=values, updates_sql=values_sql)
|
self._table.update(where=where, updates=values, updates_sql=values_sql)
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -561,13 +584,15 @@ class RemoteTable(Table):
|
|||||||
def count_rows(self, filter: Optional[str] = None) -> int:
|
def count_rows(self, filter: Optional[str] = None) -> int:
|
||||||
return LOOP.run(self._table.count_rows(filter))
|
return LOOP.run(self._table.count_rows(filter))
|
||||||
|
|
||||||
def add_columns(self, transforms: Dict[str, str]):
|
def add_columns(self, transforms: Dict[str, str]) -> AddColumnsResult:
|
||||||
return LOOP.run(self._table.add_columns(transforms))
|
return LOOP.run(self._table.add_columns(transforms))
|
||||||
|
|
||||||
def alter_columns(self, *alterations: Iterable[Dict[str, str]]):
|
def alter_columns(
|
||||||
|
self, *alterations: Iterable[Dict[str, str]]
|
||||||
|
) -> AlterColumnsResult:
|
||||||
return LOOP.run(self._table.alter_columns(*alterations))
|
return LOOP.run(self._table.alter_columns(*alterations))
|
||||||
|
|
||||||
def drop_columns(self, columns: Iterable[str]):
|
def drop_columns(self, columns: Iterable[str]) -> DropColumnsResult:
|
||||||
return LOOP.run(self._table.drop_columns(columns))
|
return LOOP.run(self._table.drop_columns(columns))
|
||||||
|
|
||||||
def drop_index(self, index_name: str):
|
def drop_index(self, index_name: str):
|
||||||
|
|||||||
@@ -78,6 +78,13 @@ if TYPE_CHECKING:
|
|||||||
CleanupStats,
|
CleanupStats,
|
||||||
CompactionStats,
|
CompactionStats,
|
||||||
Tag,
|
Tag,
|
||||||
|
AddColumnsResult,
|
||||||
|
AddResult,
|
||||||
|
AlterColumnsResult,
|
||||||
|
DeleteResult,
|
||||||
|
DropColumnsResult,
|
||||||
|
MergeResult,
|
||||||
|
UpdateResult,
|
||||||
)
|
)
|
||||||
from .db import LanceDBConnection
|
from .db import LanceDBConnection
|
||||||
from .index import IndexConfig
|
from .index import IndexConfig
|
||||||
@@ -550,6 +557,7 @@ class Table(ABC):
|
|||||||
Can append new data with [Table.add()][lancedb.table.Table.add].
|
Can append new data with [Table.add()][lancedb.table.Table.add].
|
||||||
|
|
||||||
>>> table.add([{"vector": [0.5, 1.3], "b": 4}])
|
>>> table.add([{"vector": [0.5, 1.3], "b": 4}])
|
||||||
|
AddResult(version=2)
|
||||||
|
|
||||||
Can query the table with [Table.search][lancedb.table.Table.search].
|
Can query the table with [Table.search][lancedb.table.Table.search].
|
||||||
|
|
||||||
@@ -612,6 +620,10 @@ class Table(ABC):
|
|||||||
"""
|
"""
|
||||||
raise NotImplementedError
|
raise NotImplementedError
|
||||||
|
|
||||||
|
def __len__(self) -> int:
|
||||||
|
"""The number of rows in this Table"""
|
||||||
|
return self.count_rows(None)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
@abstractmethod
|
@abstractmethod
|
||||||
def embedding_functions(self) -> Dict[str, EmbeddingFunctionConfig]:
|
def embedding_functions(self) -> Dict[str, EmbeddingFunctionConfig]:
|
||||||
@@ -815,17 +827,17 @@ class Table(ABC):
|
|||||||
ordering_field_names: Optional[Union[str, List[str]]] = None,
|
ordering_field_names: Optional[Union[str, List[str]]] = None,
|
||||||
replace: bool = False,
|
replace: bool = False,
|
||||||
writer_heap_size: Optional[int] = 1024 * 1024 * 1024,
|
writer_heap_size: Optional[int] = 1024 * 1024 * 1024,
|
||||||
use_tantivy: bool = True,
|
use_tantivy: bool = False,
|
||||||
tokenizer_name: Optional[str] = None,
|
tokenizer_name: Optional[str] = None,
|
||||||
with_position: bool = True,
|
with_position: bool = False,
|
||||||
# tokenizer configs:
|
# tokenizer configs:
|
||||||
base_tokenizer: BaseTokenizerType = "simple",
|
base_tokenizer: BaseTokenizerType = "simple",
|
||||||
language: str = "English",
|
language: str = "English",
|
||||||
max_token_length: Optional[int] = 40,
|
max_token_length: Optional[int] = 40,
|
||||||
lower_case: bool = True,
|
lower_case: bool = True,
|
||||||
stem: bool = False,
|
stem: bool = True,
|
||||||
remove_stop_words: bool = False,
|
remove_stop_words: bool = True,
|
||||||
ascii_folding: bool = False,
|
ascii_folding: bool = True,
|
||||||
wait_timeout: Optional[timedelta] = None,
|
wait_timeout: Optional[timedelta] = None,
|
||||||
):
|
):
|
||||||
"""Create a full-text search index on the table.
|
"""Create a full-text search index on the table.
|
||||||
@@ -852,10 +864,10 @@ class Table(ABC):
|
|||||||
The tokenizer to use for the index. Can be "raw", "default" or the 2 letter
|
The tokenizer to use for the index. Can be "raw", "default" or the 2 letter
|
||||||
language code followed by "_stem". So for english it would be "en_stem".
|
language code followed by "_stem". So for english it would be "en_stem".
|
||||||
For available languages see: https://docs.rs/tantivy/latest/tantivy/tokenizer/enum.Language.html
|
For available languages see: https://docs.rs/tantivy/latest/tantivy/tokenizer/enum.Language.html
|
||||||
use_tantivy: bool, default True
|
use_tantivy: bool, default False
|
||||||
If True, use the legacy full-text search implementation based on tantivy.
|
If True, use the legacy full-text search implementation based on tantivy.
|
||||||
If False, use the new full-text search implementation based on lance-index.
|
If False, use the new full-text search implementation based on lance-index.
|
||||||
with_position: bool, default True
|
with_position: bool, default False
|
||||||
Only available with use_tantivy=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.
|
||||||
@@ -873,13 +885,13 @@ class Table(ABC):
|
|||||||
lower_case : bool, default True
|
lower_case : bool, default True
|
||||||
Whether to convert the token to lower case. This makes queries
|
Whether to convert the token to lower case. This makes queries
|
||||||
case-insensitive.
|
case-insensitive.
|
||||||
stem : bool, default False
|
stem : bool, default True
|
||||||
Whether to stem the token. Stemming reduces words to their root form.
|
Whether to stem the token. Stemming reduces words to their root form.
|
||||||
For example, in English "running" and "runs" would both be reduced to "run".
|
For example, in English "running" and "runs" would both be reduced to "run".
|
||||||
remove_stop_words : bool, default False
|
remove_stop_words : bool, default True
|
||||||
Whether to remove stop words. Stop words are common words that are often
|
Whether to remove stop words. Stop words are common words that are often
|
||||||
removed from text before indexing. For example, in English "the" and "and".
|
removed from text before indexing. For example, in English "the" and "and".
|
||||||
ascii_folding : bool, default False
|
ascii_folding : bool, default True
|
||||||
Whether to fold ASCII characters. This converts accented characters to
|
Whether to fold ASCII characters. This converts accented characters to
|
||||||
their ASCII equivalent. For example, "café" would be converted to "cafe".
|
their ASCII equivalent. For example, "café" would be converted to "cafe".
|
||||||
wait_timeout: timedelta, optional
|
wait_timeout: timedelta, optional
|
||||||
@@ -894,7 +906,7 @@ class Table(ABC):
|
|||||||
mode: AddMode = "append",
|
mode: AddMode = "append",
|
||||||
on_bad_vectors: OnBadVectorsType = "error",
|
on_bad_vectors: OnBadVectorsType = "error",
|
||||||
fill_value: float = 0.0,
|
fill_value: float = 0.0,
|
||||||
):
|
) -> AddResult:
|
||||||
"""Add more data to the [Table](Table).
|
"""Add more data to the [Table](Table).
|
||||||
|
|
||||||
Parameters
|
Parameters
|
||||||
@@ -916,6 +928,10 @@ class Table(ABC):
|
|||||||
fill_value: float, default 0.
|
fill_value: float, default 0.
|
||||||
The value to use when filling vectors. Only used if on_bad_vectors="fill".
|
The value to use when filling vectors. Only used if on_bad_vectors="fill".
|
||||||
|
|
||||||
|
Returns
|
||||||
|
-------
|
||||||
|
AddResult
|
||||||
|
An object containing the new version number of the table after adding data.
|
||||||
"""
|
"""
|
||||||
raise NotImplementedError
|
raise NotImplementedError
|
||||||
|
|
||||||
@@ -962,12 +978,12 @@ class Table(ABC):
|
|||||||
>>> table = db.create_table("my_table", data)
|
>>> table = db.create_table("my_table", data)
|
||||||
>>> new_data = pa.table({"a": [2, 3, 4], "b": ["x", "y", "z"]})
|
>>> new_data = pa.table({"a": [2, 3, 4], "b": ["x", "y", "z"]})
|
||||||
>>> # Perform a "upsert" operation
|
>>> # Perform a "upsert" operation
|
||||||
>>> stats = table.merge_insert("a") \\
|
>>> res = table.merge_insert("a") \\
|
||||||
... .when_matched_update_all() \\
|
... .when_matched_update_all() \\
|
||||||
... .when_not_matched_insert_all() \\
|
... .when_not_matched_insert_all() \\
|
||||||
... .execute(new_data)
|
... .execute(new_data)
|
||||||
>>> stats
|
>>> res
|
||||||
{'num_inserted_rows': 1, 'num_updated_rows': 2, 'num_deleted_rows': 0}
|
MergeResult(version=2, num_updated_rows=2, num_inserted_rows=1, num_deleted_rows=0)
|
||||||
>>> # The order of new rows is non-deterministic since we use
|
>>> # The order of new rows is non-deterministic since we use
|
||||||
>>> # a hash-join as part of this operation and so we sort here
|
>>> # a hash-join as part of this operation and so we sort here
|
||||||
>>> table.to_arrow().sort_by("a").to_pandas()
|
>>> table.to_arrow().sort_by("a").to_pandas()
|
||||||
@@ -976,7 +992,7 @@ class Table(ABC):
|
|||||||
1 2 x
|
1 2 x
|
||||||
2 3 y
|
2 3 y
|
||||||
3 4 z
|
3 4 z
|
||||||
"""
|
""" # noqa: E501
|
||||||
on = [on] if isinstance(on, str) else list(iter(on))
|
on = [on] if isinstance(on, str) else list(iter(on))
|
||||||
|
|
||||||
return LanceMergeInsertBuilder(self, on)
|
return LanceMergeInsertBuilder(self, on)
|
||||||
@@ -1091,10 +1107,10 @@ class Table(ABC):
|
|||||||
new_data: DATA,
|
new_data: DATA,
|
||||||
on_bad_vectors: OnBadVectorsType,
|
on_bad_vectors: OnBadVectorsType,
|
||||||
fill_value: float,
|
fill_value: float,
|
||||||
): ...
|
) -> MergeResult: ...
|
||||||
|
|
||||||
@abstractmethod
|
@abstractmethod
|
||||||
def delete(self, where: str):
|
def delete(self, where: str) -> DeleteResult:
|
||||||
"""Delete rows from the table.
|
"""Delete rows from the table.
|
||||||
|
|
||||||
This can be used to delete a single row, many rows, all rows, or
|
This can be used to delete a single row, many rows, all rows, or
|
||||||
@@ -1109,6 +1125,11 @@ class Table(ABC):
|
|||||||
|
|
||||||
The filter must not be empty, or it will error.
|
The filter must not be empty, or it will error.
|
||||||
|
|
||||||
|
Returns
|
||||||
|
-------
|
||||||
|
DeleteResult
|
||||||
|
An object containing the new version number of the table after deletion.
|
||||||
|
|
||||||
Examples
|
Examples
|
||||||
--------
|
--------
|
||||||
>>> import lancedb
|
>>> import lancedb
|
||||||
@@ -1125,6 +1146,7 @@ class Table(ABC):
|
|||||||
1 2 [3.0, 4.0]
|
1 2 [3.0, 4.0]
|
||||||
2 3 [5.0, 6.0]
|
2 3 [5.0, 6.0]
|
||||||
>>> table.delete("x = 2")
|
>>> table.delete("x = 2")
|
||||||
|
DeleteResult(version=2)
|
||||||
>>> table.to_pandas()
|
>>> table.to_pandas()
|
||||||
x vector
|
x vector
|
||||||
0 1 [1.0, 2.0]
|
0 1 [1.0, 2.0]
|
||||||
@@ -1138,6 +1160,7 @@ class Table(ABC):
|
|||||||
>>> to_remove
|
>>> to_remove
|
||||||
'1, 5'
|
'1, 5'
|
||||||
>>> table.delete(f"x IN ({to_remove})")
|
>>> table.delete(f"x IN ({to_remove})")
|
||||||
|
DeleteResult(version=3)
|
||||||
>>> table.to_pandas()
|
>>> table.to_pandas()
|
||||||
x vector
|
x vector
|
||||||
0 3 [5.0, 6.0]
|
0 3 [5.0, 6.0]
|
||||||
@@ -1151,7 +1174,7 @@ class Table(ABC):
|
|||||||
values: Optional[dict] = None,
|
values: Optional[dict] = None,
|
||||||
*,
|
*,
|
||||||
values_sql: Optional[Dict[str, str]] = None,
|
values_sql: Optional[Dict[str, str]] = None,
|
||||||
):
|
) -> UpdateResult:
|
||||||
"""
|
"""
|
||||||
This can be used to update zero to all rows depending on how many
|
This can be used to update zero to all rows depending on how many
|
||||||
rows match the where clause. If no where clause is provided, then
|
rows match the where clause. If no where clause is provided, then
|
||||||
@@ -1173,6 +1196,12 @@ class Table(ABC):
|
|||||||
reference existing columns. For example, {"x": "x + 1"} will increment
|
reference existing columns. For example, {"x": "x + 1"} will increment
|
||||||
the x column by 1.
|
the x column by 1.
|
||||||
|
|
||||||
|
Returns
|
||||||
|
-------
|
||||||
|
UpdateResult
|
||||||
|
- rows_updated: The number of rows that were updated
|
||||||
|
- version: The new version number of the table after the update
|
||||||
|
|
||||||
Examples
|
Examples
|
||||||
--------
|
--------
|
||||||
>>> import lancedb
|
>>> import lancedb
|
||||||
@@ -1186,12 +1215,14 @@ class Table(ABC):
|
|||||||
1 2 [3.0, 4.0]
|
1 2 [3.0, 4.0]
|
||||||
2 3 [5.0, 6.0]
|
2 3 [5.0, 6.0]
|
||||||
>>> table.update(where="x = 2", values={"vector": [10.0, 10]})
|
>>> table.update(where="x = 2", values={"vector": [10.0, 10]})
|
||||||
|
UpdateResult(rows_updated=1, version=2)
|
||||||
>>> table.to_pandas()
|
>>> table.to_pandas()
|
||||||
x vector
|
x vector
|
||||||
0 1 [1.0, 2.0]
|
0 1 [1.0, 2.0]
|
||||||
1 3 [5.0, 6.0]
|
1 3 [5.0, 6.0]
|
||||||
2 2 [10.0, 10.0]
|
2 2 [10.0, 10.0]
|
||||||
>>> table.update(values_sql={"x": "x + 1"})
|
>>> table.update(values_sql={"x": "x + 1"})
|
||||||
|
UpdateResult(rows_updated=3, version=3)
|
||||||
>>> table.to_pandas()
|
>>> table.to_pandas()
|
||||||
x vector
|
x vector
|
||||||
0 2 [1.0, 2.0]
|
0 2 [1.0, 2.0]
|
||||||
@@ -1354,6 +1385,11 @@ class Table(ABC):
|
|||||||
Alternatively, a pyarrow Field or Schema can be provided to add
|
Alternatively, a pyarrow Field or Schema can be provided to add
|
||||||
new columns with the specified data types. The new columns will
|
new columns with the specified data types. The new columns will
|
||||||
be initialized with null values.
|
be initialized with null values.
|
||||||
|
|
||||||
|
Returns
|
||||||
|
-------
|
||||||
|
AddColumnsResult
|
||||||
|
version: the new version number of the table after adding columns.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
@abstractmethod
|
@abstractmethod
|
||||||
@@ -1379,10 +1415,15 @@ class Table(ABC):
|
|||||||
nullability is not changed. Only non-nullable columns can be changed
|
nullability is not changed. Only non-nullable columns can be changed
|
||||||
to nullable. Currently, you cannot change a nullable column to
|
to nullable. Currently, you cannot change a nullable column to
|
||||||
non-nullable.
|
non-nullable.
|
||||||
|
|
||||||
|
Returns
|
||||||
|
-------
|
||||||
|
AlterColumnsResult
|
||||||
|
version: the new version number of the table after the alteration.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
@abstractmethod
|
@abstractmethod
|
||||||
def drop_columns(self, columns: Iterable[str]):
|
def drop_columns(self, columns: Iterable[str]) -> DropColumnsResult:
|
||||||
"""
|
"""
|
||||||
Drop columns from the table.
|
Drop columns from the table.
|
||||||
|
|
||||||
@@ -1390,6 +1431,11 @@ class Table(ABC):
|
|||||||
----------
|
----------
|
||||||
columns : Iterable[str]
|
columns : Iterable[str]
|
||||||
The names of the columns to drop.
|
The names of the columns to drop.
|
||||||
|
|
||||||
|
Returns
|
||||||
|
-------
|
||||||
|
DropColumnsResult
|
||||||
|
version: the new version number of the table dropping the columns.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
@abstractmethod
|
@abstractmethod
|
||||||
@@ -1428,7 +1474,7 @@ class Table(ABC):
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
@abstractmethod
|
@abstractmethod
|
||||||
def restore(self, version: Optional[int] = None):
|
def restore(self, version: Optional[Union[int, str]] = None):
|
||||||
"""Restore a version of the table. This is an in-place operation.
|
"""Restore a version of the table. This is an in-place operation.
|
||||||
|
|
||||||
This creates a new version where the data is equivalent to the
|
This creates a new version where the data is equivalent to the
|
||||||
@@ -1436,9 +1482,10 @@ class Table(ABC):
|
|||||||
|
|
||||||
Parameters
|
Parameters
|
||||||
----------
|
----------
|
||||||
version : int, default None
|
version : int or str, default None
|
||||||
The version to restore. If unspecified then restores the currently
|
The version number or version tag to restore.
|
||||||
checked out version. If the currently checked out version is the
|
If unspecified then restores the currently checked out version.
|
||||||
|
If the currently checked out version is the
|
||||||
latest version then this is a no-op.
|
latest version then this is a no-op.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
@@ -1611,6 +1658,7 @@ class LanceTable(Table):
|
|||||||
... [{"vector": [1.1, 0.9], "type": "vector"}])
|
... [{"vector": [1.1, 0.9], "type": "vector"}])
|
||||||
>>> table.tags.create("v1", table.version)
|
>>> table.tags.create("v1", table.version)
|
||||||
>>> table.add([{"vector": [0.5, 0.2], "type": "vector"}])
|
>>> table.add([{"vector": [0.5, 0.2], "type": "vector"}])
|
||||||
|
AddResult(version=2)
|
||||||
>>> tags = table.tags.list()
|
>>> tags = table.tags.list()
|
||||||
>>> print(tags["v1"]["version"])
|
>>> print(tags["v1"]["version"])
|
||||||
1
|
1
|
||||||
@@ -1649,6 +1697,7 @@ class LanceTable(Table):
|
|||||||
vector type
|
vector type
|
||||||
0 [1.1, 0.9] vector
|
0 [1.1, 0.9] vector
|
||||||
>>> table.add([{"vector": [0.5, 0.2], "type": "vector"}])
|
>>> table.add([{"vector": [0.5, 0.2], "type": "vector"}])
|
||||||
|
AddResult(version=2)
|
||||||
>>> table.version
|
>>> table.version
|
||||||
2
|
2
|
||||||
>>> table.checkout(1)
|
>>> table.checkout(1)
|
||||||
@@ -1666,7 +1715,7 @@ class LanceTable(Table):
|
|||||||
"""
|
"""
|
||||||
LOOP.run(self._table.checkout_latest())
|
LOOP.run(self._table.checkout_latest())
|
||||||
|
|
||||||
def restore(self, version: Optional[int] = None):
|
def restore(self, version: Optional[Union[int, str]] = None):
|
||||||
"""Restore a version of the table. This is an in-place operation.
|
"""Restore a version of the table. This is an in-place operation.
|
||||||
|
|
||||||
This creates a new version where the data is equivalent to the
|
This creates a new version where the data is equivalent to the
|
||||||
@@ -1674,9 +1723,10 @@ class LanceTable(Table):
|
|||||||
|
|
||||||
Parameters
|
Parameters
|
||||||
----------
|
----------
|
||||||
version : int, default None
|
version : int or str, default None
|
||||||
The version to restore. If unspecified then restores the currently
|
The version number or version tag to restore.
|
||||||
checked out version. If the currently checked out version is the
|
If unspecified then restores the currently checked out version.
|
||||||
|
If the currently checked out version is the
|
||||||
latest version then this is a no-op.
|
latest version then this is a no-op.
|
||||||
|
|
||||||
Examples
|
Examples
|
||||||
@@ -1691,14 +1741,23 @@ class LanceTable(Table):
|
|||||||
vector type
|
vector type
|
||||||
0 [1.1, 0.9] vector
|
0 [1.1, 0.9] vector
|
||||||
>>> table.add([{"vector": [0.5, 0.2], "type": "vector"}])
|
>>> table.add([{"vector": [0.5, 0.2], "type": "vector"}])
|
||||||
|
AddResult(version=2)
|
||||||
>>> table.version
|
>>> table.version
|
||||||
2
|
2
|
||||||
|
>>> table.tags.create("v2", 2)
|
||||||
>>> table.restore(1)
|
>>> table.restore(1)
|
||||||
>>> table.to_pandas()
|
>>> table.to_pandas()
|
||||||
vector type
|
vector type
|
||||||
0 [1.1, 0.9] vector
|
0 [1.1, 0.9] vector
|
||||||
>>> len(table.list_versions())
|
>>> len(table.list_versions())
|
||||||
3
|
3
|
||||||
|
>>> table.restore("v2")
|
||||||
|
>>> table.to_pandas()
|
||||||
|
vector type
|
||||||
|
0 [1.1, 0.9] vector
|
||||||
|
1 [0.5, 0.2] vector
|
||||||
|
>>> len(table.list_versions())
|
||||||
|
4
|
||||||
"""
|
"""
|
||||||
if version is not None:
|
if version is not None:
|
||||||
LOOP.run(self._table.checkout(version))
|
LOOP.run(self._table.checkout(version))
|
||||||
@@ -1707,9 +1766,6 @@ class LanceTable(Table):
|
|||||||
def count_rows(self, filter: Optional[str] = None) -> int:
|
def count_rows(self, filter: Optional[str] = None) -> int:
|
||||||
return LOOP.run(self._table.count_rows(filter))
|
return LOOP.run(self._table.count_rows(filter))
|
||||||
|
|
||||||
def __len__(self) -> int:
|
|
||||||
return self.count_rows()
|
|
||||||
|
|
||||||
def __repr__(self) -> str:
|
def __repr__(self) -> str:
|
||||||
val = f"{self.__class__.__name__}(name={self.name!r}, version={self.version}"
|
val = f"{self.__class__.__name__}(name={self.name!r}, version={self.version}"
|
||||||
if self._conn.read_consistency_interval is not None:
|
if self._conn.read_consistency_interval is not None:
|
||||||
@@ -1914,17 +1970,17 @@ class LanceTable(Table):
|
|||||||
ordering_field_names: Optional[Union[str, List[str]]] = None,
|
ordering_field_names: Optional[Union[str, List[str]]] = None,
|
||||||
replace: bool = False,
|
replace: bool = False,
|
||||||
writer_heap_size: Optional[int] = 1024 * 1024 * 1024,
|
writer_heap_size: Optional[int] = 1024 * 1024 * 1024,
|
||||||
use_tantivy: bool = True,
|
use_tantivy: bool = False,
|
||||||
tokenizer_name: Optional[str] = None,
|
tokenizer_name: Optional[str] = None,
|
||||||
with_position: bool = True,
|
with_position: bool = False,
|
||||||
# tokenizer configs:
|
# tokenizer configs:
|
||||||
base_tokenizer: BaseTokenizerType = "simple",
|
base_tokenizer: BaseTokenizerType = "simple",
|
||||||
language: str = "English",
|
language: str = "English",
|
||||||
max_token_length: Optional[int] = 40,
|
max_token_length: Optional[int] = 40,
|
||||||
lower_case: bool = True,
|
lower_case: bool = True,
|
||||||
stem: bool = False,
|
stem: bool = True,
|
||||||
remove_stop_words: bool = False,
|
remove_stop_words: bool = True,
|
||||||
ascii_folding: bool = False,
|
ascii_folding: bool = True,
|
||||||
):
|
):
|
||||||
if not use_tantivy:
|
if not use_tantivy:
|
||||||
if not isinstance(field_names, str):
|
if not isinstance(field_names, str):
|
||||||
@@ -1934,6 +1990,7 @@ class LanceTable(Table):
|
|||||||
tokenizer_configs = {
|
tokenizer_configs = {
|
||||||
"base_tokenizer": base_tokenizer,
|
"base_tokenizer": base_tokenizer,
|
||||||
"language": language,
|
"language": language,
|
||||||
|
"with_position": with_position,
|
||||||
"max_token_length": max_token_length,
|
"max_token_length": max_token_length,
|
||||||
"lower_case": lower_case,
|
"lower_case": lower_case,
|
||||||
"stem": stem,
|
"stem": stem,
|
||||||
@@ -1944,7 +2001,6 @@ class LanceTable(Table):
|
|||||||
tokenizer_configs = self.infer_tokenizer_configs(tokenizer_name)
|
tokenizer_configs = self.infer_tokenizer_configs(tokenizer_name)
|
||||||
|
|
||||||
config = FTS(
|
config = FTS(
|
||||||
with_position=with_position,
|
|
||||||
**tokenizer_configs,
|
**tokenizer_configs,
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -2055,7 +2111,7 @@ class LanceTable(Table):
|
|||||||
mode: AddMode = "append",
|
mode: AddMode = "append",
|
||||||
on_bad_vectors: OnBadVectorsType = "error",
|
on_bad_vectors: OnBadVectorsType = "error",
|
||||||
fill_value: float = 0.0,
|
fill_value: float = 0.0,
|
||||||
):
|
) -> AddResult:
|
||||||
"""Add data to the table.
|
"""Add data to the table.
|
||||||
If vector columns are missing and the table
|
If vector columns are missing and the table
|
||||||
has embedding functions, then the vector columns
|
has embedding functions, then the vector columns
|
||||||
@@ -2079,7 +2135,7 @@ class LanceTable(Table):
|
|||||||
int
|
int
|
||||||
The number of vectors in the table.
|
The number of vectors in the table.
|
||||||
"""
|
"""
|
||||||
LOOP.run(
|
return LOOP.run(
|
||||||
self._table.add(
|
self._table.add(
|
||||||
data, mode=mode, on_bad_vectors=on_bad_vectors, fill_value=fill_value
|
data, mode=mode, on_bad_vectors=on_bad_vectors, fill_value=fill_value
|
||||||
)
|
)
|
||||||
@@ -2409,8 +2465,8 @@ class LanceTable(Table):
|
|||||||
)
|
)
|
||||||
return self
|
return self
|
||||||
|
|
||||||
def delete(self, where: str):
|
def delete(self, where: str) -> DeleteResult:
|
||||||
LOOP.run(self._table.delete(where))
|
return LOOP.run(self._table.delete(where))
|
||||||
|
|
||||||
def update(
|
def update(
|
||||||
self,
|
self,
|
||||||
@@ -2418,7 +2474,7 @@ class LanceTable(Table):
|
|||||||
values: Optional[dict] = None,
|
values: Optional[dict] = None,
|
||||||
*,
|
*,
|
||||||
values_sql: Optional[Dict[str, str]] = None,
|
values_sql: Optional[Dict[str, str]] = None,
|
||||||
):
|
) -> UpdateResult:
|
||||||
"""
|
"""
|
||||||
This can be used to update zero to all rows depending on how many
|
This can be used to update zero to all rows depending on how many
|
||||||
rows match the where clause.
|
rows match the where clause.
|
||||||
@@ -2436,6 +2492,12 @@ class LanceTable(Table):
|
|||||||
reference existing columns. For example, {"x": "x + 1"} will increment
|
reference existing columns. For example, {"x": "x + 1"} will increment
|
||||||
the x column by 1.
|
the x column by 1.
|
||||||
|
|
||||||
|
Returns
|
||||||
|
-------
|
||||||
|
UpdateResult
|
||||||
|
- rows_updated: The number of rows that were updated
|
||||||
|
- version: The new version number of the table after the update
|
||||||
|
|
||||||
Examples
|
Examples
|
||||||
--------
|
--------
|
||||||
>>> import lancedb
|
>>> import lancedb
|
||||||
@@ -2449,6 +2511,7 @@ class LanceTable(Table):
|
|||||||
1 2 [3.0, 4.0]
|
1 2 [3.0, 4.0]
|
||||||
2 3 [5.0, 6.0]
|
2 3 [5.0, 6.0]
|
||||||
>>> table.update(where="x = 2", values={"vector": [10.0, 10]})
|
>>> table.update(where="x = 2", values={"vector": [10.0, 10]})
|
||||||
|
UpdateResult(rows_updated=1, version=2)
|
||||||
>>> table.to_pandas()
|
>>> table.to_pandas()
|
||||||
x vector
|
x vector
|
||||||
0 1 [1.0, 2.0]
|
0 1 [1.0, 2.0]
|
||||||
@@ -2456,7 +2519,7 @@ class LanceTable(Table):
|
|||||||
2 2 [10.0, 10.0]
|
2 2 [10.0, 10.0]
|
||||||
|
|
||||||
"""
|
"""
|
||||||
LOOP.run(self._table.update(values, where=where, updates_sql=values_sql))
|
return LOOP.run(self._table.update(values, where=where, updates_sql=values_sql))
|
||||||
|
|
||||||
def _execute_query(
|
def _execute_query(
|
||||||
self,
|
self,
|
||||||
@@ -2490,7 +2553,7 @@ class LanceTable(Table):
|
|||||||
new_data: DATA,
|
new_data: DATA,
|
||||||
on_bad_vectors: OnBadVectorsType,
|
on_bad_vectors: OnBadVectorsType,
|
||||||
fill_value: float,
|
fill_value: float,
|
||||||
):
|
) -> MergeResult:
|
||||||
return LOOP.run(
|
return LOOP.run(
|
||||||
self._table._do_merge(merge, new_data, on_bad_vectors, fill_value)
|
self._table._do_merge(merge, new_data, on_bad_vectors, fill_value)
|
||||||
)
|
)
|
||||||
@@ -2635,14 +2698,16 @@ class LanceTable(Table):
|
|||||||
|
|
||||||
def add_columns(
|
def add_columns(
|
||||||
self, transforms: Dict[str, str] | pa.field | List[pa.field] | pa.Schema
|
self, transforms: Dict[str, str] | pa.field | List[pa.field] | pa.Schema
|
||||||
):
|
) -> AddColumnsResult:
|
||||||
LOOP.run(self._table.add_columns(transforms))
|
return LOOP.run(self._table.add_columns(transforms))
|
||||||
|
|
||||||
def alter_columns(self, *alterations: Iterable[Dict[str, str]]):
|
def alter_columns(
|
||||||
LOOP.run(self._table.alter_columns(*alterations))
|
self, *alterations: Iterable[Dict[str, str]]
|
||||||
|
) -> AlterColumnsResult:
|
||||||
|
return LOOP.run(self._table.alter_columns(*alterations))
|
||||||
|
|
||||||
def drop_columns(self, columns: Iterable[str]):
|
def drop_columns(self, columns: Iterable[str]) -> DropColumnsResult:
|
||||||
LOOP.run(self._table.drop_columns(columns))
|
return LOOP.run(self._table.drop_columns(columns))
|
||||||
|
|
||||||
def uses_v2_manifest_paths(self) -> bool:
|
def uses_v2_manifest_paths(self) -> bool:
|
||||||
"""
|
"""
|
||||||
@@ -3197,7 +3262,7 @@ class AsyncTable:
|
|||||||
mode: Optional[Literal["append", "overwrite"]] = "append",
|
mode: Optional[Literal["append", "overwrite"]] = "append",
|
||||||
on_bad_vectors: Optional[OnBadVectorsType] = None,
|
on_bad_vectors: Optional[OnBadVectorsType] = None,
|
||||||
fill_value: Optional[float] = None,
|
fill_value: Optional[float] = None,
|
||||||
):
|
) -> AddResult:
|
||||||
"""Add more data to the [Table](Table).
|
"""Add more data to the [Table](Table).
|
||||||
|
|
||||||
Parameters
|
Parameters
|
||||||
@@ -3236,7 +3301,7 @@ class AsyncTable:
|
|||||||
if isinstance(data, pa.Table):
|
if isinstance(data, pa.Table):
|
||||||
data = data.to_reader()
|
data = data.to_reader()
|
||||||
|
|
||||||
await self._inner.add(data, mode or "append")
|
return await self._inner.add(data, mode or "append")
|
||||||
|
|
||||||
def merge_insert(self, on: Union[str, Iterable[str]]) -> LanceMergeInsertBuilder:
|
def merge_insert(self, on: Union[str, Iterable[str]]) -> LanceMergeInsertBuilder:
|
||||||
"""
|
"""
|
||||||
@@ -3281,12 +3346,12 @@ class AsyncTable:
|
|||||||
>>> table = db.create_table("my_table", data)
|
>>> table = db.create_table("my_table", data)
|
||||||
>>> new_data = pa.table({"a": [2, 3, 4], "b": ["x", "y", "z"]})
|
>>> new_data = pa.table({"a": [2, 3, 4], "b": ["x", "y", "z"]})
|
||||||
>>> # Perform a "upsert" operation
|
>>> # Perform a "upsert" operation
|
||||||
>>> stats = table.merge_insert("a") \\
|
>>> res = table.merge_insert("a") \\
|
||||||
... .when_matched_update_all() \\
|
... .when_matched_update_all() \\
|
||||||
... .when_not_matched_insert_all() \\
|
... .when_not_matched_insert_all() \\
|
||||||
... .execute(new_data)
|
... .execute(new_data)
|
||||||
>>> stats
|
>>> res
|
||||||
{'num_inserted_rows': 1, 'num_updated_rows': 2, 'num_deleted_rows': 0}
|
MergeResult(version=2, num_updated_rows=2, num_inserted_rows=1, num_deleted_rows=0)
|
||||||
>>> # The order of new rows is non-deterministic since we use
|
>>> # The order of new rows is non-deterministic since we use
|
||||||
>>> # a hash-join as part of this operation and so we sort here
|
>>> # a hash-join as part of this operation and so we sort here
|
||||||
>>> table.to_arrow().sort_by("a").to_pandas()
|
>>> table.to_arrow().sort_by("a").to_pandas()
|
||||||
@@ -3295,7 +3360,7 @@ class AsyncTable:
|
|||||||
1 2 x
|
1 2 x
|
||||||
2 3 y
|
2 3 y
|
||||||
3 4 z
|
3 4 z
|
||||||
"""
|
""" # noqa: E501
|
||||||
on = [on] if isinstance(on, str) else list(iter(on))
|
on = [on] if isinstance(on, str) else list(iter(on))
|
||||||
|
|
||||||
return LanceMergeInsertBuilder(self, on)
|
return LanceMergeInsertBuilder(self, on)
|
||||||
@@ -3572,8 +3637,10 @@ class AsyncTable:
|
|||||||
)
|
)
|
||||||
if query.distance_type is not None:
|
if query.distance_type is not None:
|
||||||
async_query = async_query.distance_type(query.distance_type)
|
async_query = async_query.distance_type(query.distance_type)
|
||||||
if query.nprobes is not None:
|
if query.minimum_nprobes is not None:
|
||||||
async_query = async_query.nprobes(query.nprobes)
|
async_query = async_query.minimum_nprobes(query.minimum_nprobes)
|
||||||
|
if query.maximum_nprobes is not None:
|
||||||
|
async_query = async_query.maximum_nprobes(query.maximum_nprobes)
|
||||||
if query.refine_factor is not None:
|
if query.refine_factor is not None:
|
||||||
async_query = async_query.refine_factor(query.refine_factor)
|
async_query = async_query.refine_factor(query.refine_factor)
|
||||||
if query.vector_column:
|
if query.vector_column:
|
||||||
@@ -3626,7 +3693,7 @@ class AsyncTable:
|
|||||||
new_data: DATA,
|
new_data: DATA,
|
||||||
on_bad_vectors: OnBadVectorsType,
|
on_bad_vectors: OnBadVectorsType,
|
||||||
fill_value: float,
|
fill_value: float,
|
||||||
):
|
) -> MergeResult:
|
||||||
schema = await self.schema()
|
schema = await self.schema()
|
||||||
if on_bad_vectors is None:
|
if on_bad_vectors is None:
|
||||||
on_bad_vectors = "error"
|
on_bad_vectors = "error"
|
||||||
@@ -3651,10 +3718,11 @@ class AsyncTable:
|
|||||||
when_not_matched_insert_all=merge._when_not_matched_insert_all,
|
when_not_matched_insert_all=merge._when_not_matched_insert_all,
|
||||||
when_not_matched_by_source_delete=merge._when_not_matched_by_source_delete,
|
when_not_matched_by_source_delete=merge._when_not_matched_by_source_delete,
|
||||||
when_not_matched_by_source_condition=merge._when_not_matched_by_source_condition,
|
when_not_matched_by_source_condition=merge._when_not_matched_by_source_condition,
|
||||||
|
timeout=merge._timeout,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
async def delete(self, where: str):
|
async def delete(self, where: str) -> DeleteResult:
|
||||||
"""Delete rows from the table.
|
"""Delete rows from the table.
|
||||||
|
|
||||||
This can be used to delete a single row, many rows, all rows, or
|
This can be used to delete a single row, many rows, all rows, or
|
||||||
@@ -3685,6 +3753,7 @@ class AsyncTable:
|
|||||||
1 2 [3.0, 4.0]
|
1 2 [3.0, 4.0]
|
||||||
2 3 [5.0, 6.0]
|
2 3 [5.0, 6.0]
|
||||||
>>> table.delete("x = 2")
|
>>> table.delete("x = 2")
|
||||||
|
DeleteResult(version=2)
|
||||||
>>> table.to_pandas()
|
>>> table.to_pandas()
|
||||||
x vector
|
x vector
|
||||||
0 1 [1.0, 2.0]
|
0 1 [1.0, 2.0]
|
||||||
@@ -3698,6 +3767,7 @@ class AsyncTable:
|
|||||||
>>> to_remove
|
>>> to_remove
|
||||||
'1, 5'
|
'1, 5'
|
||||||
>>> table.delete(f"x IN ({to_remove})")
|
>>> table.delete(f"x IN ({to_remove})")
|
||||||
|
DeleteResult(version=3)
|
||||||
>>> table.to_pandas()
|
>>> table.to_pandas()
|
||||||
x vector
|
x vector
|
||||||
0 3 [5.0, 6.0]
|
0 3 [5.0, 6.0]
|
||||||
@@ -3710,7 +3780,7 @@ class AsyncTable:
|
|||||||
*,
|
*,
|
||||||
where: Optional[str] = None,
|
where: Optional[str] = None,
|
||||||
updates_sql: Optional[Dict[str, str]] = None,
|
updates_sql: Optional[Dict[str, str]] = None,
|
||||||
):
|
) -> UpdateResult:
|
||||||
"""
|
"""
|
||||||
This can be used to update zero to all rows in the table.
|
This can be used to update zero to all rows in the table.
|
||||||
|
|
||||||
@@ -3732,6 +3802,13 @@ class AsyncTable:
|
|||||||
literals (e.g. "7" or "'foo'") or they can be expressions based on the
|
literals (e.g. "7" or "'foo'") or they can be expressions based on the
|
||||||
previous value of the row (e.g. "x + 1" to increment the x column by 1)
|
previous value of the row (e.g. "x + 1" to increment the x column by 1)
|
||||||
|
|
||||||
|
Returns
|
||||||
|
-------
|
||||||
|
UpdateResult
|
||||||
|
An object containing:
|
||||||
|
- rows_updated: The number of rows that were updated
|
||||||
|
- version: The new version number of the table after the update
|
||||||
|
|
||||||
Examples
|
Examples
|
||||||
--------
|
--------
|
||||||
>>> import asyncio
|
>>> import asyncio
|
||||||
@@ -3760,7 +3837,7 @@ class AsyncTable:
|
|||||||
|
|
||||||
async def add_columns(
|
async def add_columns(
|
||||||
self, transforms: dict[str, str] | pa.field | List[pa.field] | pa.Schema
|
self, transforms: dict[str, str] | pa.field | List[pa.field] | pa.Schema
|
||||||
):
|
) -> AddColumnsResult:
|
||||||
"""
|
"""
|
||||||
Add new columns with defined values.
|
Add new columns with defined values.
|
||||||
|
|
||||||
@@ -3772,6 +3849,12 @@ class AsyncTable:
|
|||||||
each row in the table, and can reference existing columns.
|
each row in the table, and can reference existing columns.
|
||||||
Alternatively, you can pass a pyarrow field or schema to add
|
Alternatively, you can pass a pyarrow field or schema to add
|
||||||
new columns with NULLs.
|
new columns with NULLs.
|
||||||
|
|
||||||
|
Returns
|
||||||
|
-------
|
||||||
|
AddColumnsResult
|
||||||
|
version: the new version number of the table after adding columns.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
if isinstance(transforms, pa.Field):
|
if isinstance(transforms, pa.Field):
|
||||||
transforms = [transforms]
|
transforms = [transforms]
|
||||||
@@ -3780,11 +3863,13 @@ class AsyncTable:
|
|||||||
):
|
):
|
||||||
transforms = pa.schema(transforms)
|
transforms = pa.schema(transforms)
|
||||||
if isinstance(transforms, pa.Schema):
|
if isinstance(transforms, pa.Schema):
|
||||||
await self._inner.add_columns_with_schema(transforms)
|
return await self._inner.add_columns_with_schema(transforms)
|
||||||
else:
|
else:
|
||||||
await self._inner.add_columns(list(transforms.items()))
|
return await self._inner.add_columns(list(transforms.items()))
|
||||||
|
|
||||||
async def alter_columns(self, *alterations: Iterable[dict[str, Any]]):
|
async def alter_columns(
|
||||||
|
self, *alterations: Iterable[dict[str, Any]]
|
||||||
|
) -> AlterColumnsResult:
|
||||||
"""
|
"""
|
||||||
Alter column names and nullability.
|
Alter column names and nullability.
|
||||||
|
|
||||||
@@ -3804,8 +3889,13 @@ class AsyncTable:
|
|||||||
nullability is not changed. Only non-nullable columns can be changed
|
nullability is not changed. Only non-nullable columns can be changed
|
||||||
to nullable. Currently, you cannot change a nullable column to
|
to nullable. Currently, you cannot change a nullable column to
|
||||||
non-nullable.
|
non-nullable.
|
||||||
|
|
||||||
|
Returns
|
||||||
|
-------
|
||||||
|
AlterColumnsResult
|
||||||
|
version: the new version number of the table after the alteration.
|
||||||
"""
|
"""
|
||||||
await self._inner.alter_columns(alterations)
|
return await self._inner.alter_columns(alterations)
|
||||||
|
|
||||||
async def drop_columns(self, columns: Iterable[str]):
|
async def drop_columns(self, columns: Iterable[str]):
|
||||||
"""
|
"""
|
||||||
@@ -3816,7 +3906,7 @@ class AsyncTable:
|
|||||||
columns : Iterable[str]
|
columns : Iterable[str]
|
||||||
The names of the columns to drop.
|
The names of the columns to drop.
|
||||||
"""
|
"""
|
||||||
await self._inner.drop_columns(columns)
|
return await self._inner.drop_columns(columns)
|
||||||
|
|
||||||
async def version(self) -> int:
|
async def version(self) -> int:
|
||||||
"""
|
"""
|
||||||
@@ -3886,7 +3976,7 @@ class AsyncTable:
|
|||||||
"""
|
"""
|
||||||
await self._inner.checkout_latest()
|
await self._inner.checkout_latest()
|
||||||
|
|
||||||
async def restore(self, version: Optional[int] = None):
|
async def restore(self, version: Optional[int | str] = None):
|
||||||
"""
|
"""
|
||||||
Restore the table to the currently checked out version
|
Restore the table to the currently checked out version
|
||||||
|
|
||||||
|
|||||||
@@ -25,6 +25,10 @@ import numpy as np
|
|||||||
from lancedb.pydantic import Vector, LanceModel
|
from lancedb.pydantic import Vector, LanceModel
|
||||||
|
|
||||||
# --8<-- [end:import-lancedb-pydantic]
|
# --8<-- [end:import-lancedb-pydantic]
|
||||||
|
# --8<-- [start:import-session-context]
|
||||||
|
from datafusion import SessionContext
|
||||||
|
|
||||||
|
# --8<-- [end:import-session-context]
|
||||||
# --8<-- [start:import-datetime]
|
# --8<-- [start:import-datetime]
|
||||||
from datetime import timedelta
|
from datetime import timedelta
|
||||||
|
|
||||||
@@ -33,6 +37,10 @@ from datetime import timedelta
|
|||||||
from lancedb.embeddings import get_registry
|
from lancedb.embeddings import get_registry
|
||||||
|
|
||||||
# --8<-- [end:import-embeddings]
|
# --8<-- [end:import-embeddings]
|
||||||
|
# --8<-- [start:import-ffi-dataset]
|
||||||
|
from lance import FFILanceTableProvider
|
||||||
|
|
||||||
|
# --8<-- [end:import-ffi-dataset]
|
||||||
# --8<-- [start:import-pydantic-basemodel]
|
# --8<-- [start:import-pydantic-basemodel]
|
||||||
from pydantic import BaseModel
|
from pydantic import BaseModel
|
||||||
|
|
||||||
@@ -341,6 +349,27 @@ def test_table_with_embedding():
|
|||||||
# --8<-- [end:create_table_with_embedding]
|
# --8<-- [end:create_table_with_embedding]
|
||||||
|
|
||||||
|
|
||||||
|
def test_sql_query():
|
||||||
|
db = lancedb.connect("data/sample-lancedb")
|
||||||
|
data = [
|
||||||
|
{"vector": [1.1, 1.2], "lat": 45.5, "long": -122.7},
|
||||||
|
{"vector": [0.2, 1.8], "lat": 40.1, "long": -74.1},
|
||||||
|
]
|
||||||
|
table = db.create_table("lance_table", data)
|
||||||
|
|
||||||
|
# --8<-- [start:lance_sql_basic]
|
||||||
|
ctx = SessionContext()
|
||||||
|
ffi_lance_table = FFILanceTableProvider(
|
||||||
|
table.to_lance(), with_row_id=False, with_row_addr=False
|
||||||
|
)
|
||||||
|
|
||||||
|
ctx.register_table_provider("ffi_lance_table", ffi_lance_table)
|
||||||
|
ctx.table("ffi_lance_table")
|
||||||
|
|
||||||
|
ctx.sql("SELECT vector FROM ffi_lance_table")
|
||||||
|
# --8<-- [end:lance_sql_basic]
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.skip
|
@pytest.mark.skip
|
||||||
async def test_table_with_embedding_async():
|
async def test_table_with_embedding_async():
|
||||||
async_db = await lancedb.connect_async("data/sample-lancedb")
|
async_db = await lancedb.connect_async("data/sample-lancedb")
|
||||||
|
|||||||
@@ -18,19 +18,19 @@ def test_upsert(mem_db):
|
|||||||
{"id": 1, "name": "Bobby"},
|
{"id": 1, "name": "Bobby"},
|
||||||
{"id": 2, "name": "Charlie"},
|
{"id": 2, "name": "Charlie"},
|
||||||
]
|
]
|
||||||
stats = (
|
res = (
|
||||||
table.merge_insert("id")
|
table.merge_insert("id")
|
||||||
.when_matched_update_all()
|
.when_matched_update_all()
|
||||||
.when_not_matched_insert_all()
|
.when_not_matched_insert_all()
|
||||||
.execute(new_users)
|
.execute(new_users)
|
||||||
)
|
)
|
||||||
table.count_rows() # 3
|
table.count_rows() # 3
|
||||||
stats # {'num_inserted_rows': 1, 'num_updated_rows': 1, 'num_deleted_rows': 0}
|
res # {'num_inserted_rows': 1, 'num_updated_rows': 1, 'num_deleted_rows': 0}
|
||||||
# --8<-- [end:upsert_basic]
|
# --8<-- [end:upsert_basic]
|
||||||
assert table.count_rows() == 3
|
assert table.count_rows() == 3
|
||||||
assert stats["num_inserted_rows"] == 1
|
assert res.num_inserted_rows == 1
|
||||||
assert stats["num_updated_rows"] == 1
|
assert res.num_deleted_rows == 0
|
||||||
assert stats["num_deleted_rows"] == 0
|
assert res.num_updated_rows == 1
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
@pytest.mark.asyncio
|
||||||
@@ -48,19 +48,22 @@ async def test_upsert_async(mem_db_async):
|
|||||||
{"id": 1, "name": "Bobby"},
|
{"id": 1, "name": "Bobby"},
|
||||||
{"id": 2, "name": "Charlie"},
|
{"id": 2, "name": "Charlie"},
|
||||||
]
|
]
|
||||||
stats = await (
|
res = await (
|
||||||
table.merge_insert("id")
|
table.merge_insert("id")
|
||||||
.when_matched_update_all()
|
.when_matched_update_all()
|
||||||
.when_not_matched_insert_all()
|
.when_not_matched_insert_all()
|
||||||
.execute(new_users)
|
.execute(new_users)
|
||||||
)
|
)
|
||||||
await table.count_rows() # 3
|
await table.count_rows() # 3
|
||||||
stats # {'num_inserted_rows': 1, 'num_updated_rows': 1, 'num_deleted_rows': 0}
|
res
|
||||||
|
# MergeResult(version=2, num_updated_rows=1,
|
||||||
|
# num_inserted_rows=1, num_deleted_rows=0)
|
||||||
# --8<-- [end:upsert_basic_async]
|
# --8<-- [end:upsert_basic_async]
|
||||||
assert await table.count_rows() == 3
|
assert await table.count_rows() == 3
|
||||||
assert stats["num_inserted_rows"] == 1
|
assert res.version == 2
|
||||||
assert stats["num_updated_rows"] == 1
|
assert res.num_inserted_rows == 1
|
||||||
assert stats["num_deleted_rows"] == 0
|
assert res.num_deleted_rows == 0
|
||||||
|
assert res.num_updated_rows == 1
|
||||||
|
|
||||||
|
|
||||||
def test_insert_if_not_exists(mem_db):
|
def test_insert_if_not_exists(mem_db):
|
||||||
@@ -77,16 +80,19 @@ def test_insert_if_not_exists(mem_db):
|
|||||||
{"domain": "google.com", "name": "Google"},
|
{"domain": "google.com", "name": "Google"},
|
||||||
{"domain": "facebook.com", "name": "Facebook"},
|
{"domain": "facebook.com", "name": "Facebook"},
|
||||||
]
|
]
|
||||||
stats = (
|
res = (
|
||||||
table.merge_insert("domain").when_not_matched_insert_all().execute(new_domains)
|
table.merge_insert("domain").when_not_matched_insert_all().execute(new_domains)
|
||||||
)
|
)
|
||||||
table.count_rows() # 3
|
table.count_rows() # 3
|
||||||
stats # {'num_inserted_rows': 1, 'num_updated_rows': 0, 'num_deleted_rows': 0}
|
res
|
||||||
|
# MergeResult(version=2, num_updated_rows=0,
|
||||||
|
# num_inserted_rows=1, num_deleted_rows=0)
|
||||||
# --8<-- [end:insert_if_not_exists]
|
# --8<-- [end:insert_if_not_exists]
|
||||||
assert table.count_rows() == 3
|
assert table.count_rows() == 3
|
||||||
assert stats["num_inserted_rows"] == 1
|
assert res.version == 2
|
||||||
assert stats["num_updated_rows"] == 0
|
assert res.num_inserted_rows == 1
|
||||||
assert stats["num_deleted_rows"] == 0
|
assert res.num_deleted_rows == 0
|
||||||
|
assert res.num_updated_rows == 0
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
@pytest.mark.asyncio
|
||||||
@@ -104,16 +110,19 @@ async def test_insert_if_not_exists_async(mem_db_async):
|
|||||||
{"domain": "google.com", "name": "Google"},
|
{"domain": "google.com", "name": "Google"},
|
||||||
{"domain": "facebook.com", "name": "Facebook"},
|
{"domain": "facebook.com", "name": "Facebook"},
|
||||||
]
|
]
|
||||||
stats = await (
|
res = await (
|
||||||
table.merge_insert("domain").when_not_matched_insert_all().execute(new_domains)
|
table.merge_insert("domain").when_not_matched_insert_all().execute(new_domains)
|
||||||
)
|
)
|
||||||
await table.count_rows() # 3
|
await table.count_rows() # 3
|
||||||
stats # {'num_inserted_rows': 1, 'num_updated_rows': 0, 'num_deleted_rows': 0}
|
res
|
||||||
# --8<-- [end:insert_if_not_exists_async]
|
# MergeResult(version=2, num_updated_rows=0,
|
||||||
|
# num_inserted_rows=1, num_deleted_rows=0)
|
||||||
|
# --8<-- [end:insert_if_not_exists]
|
||||||
assert await table.count_rows() == 3
|
assert await table.count_rows() == 3
|
||||||
assert stats["num_inserted_rows"] == 1
|
assert res.version == 2
|
||||||
assert stats["num_updated_rows"] == 0
|
assert res.num_inserted_rows == 1
|
||||||
assert stats["num_deleted_rows"] == 0
|
assert res.num_deleted_rows == 0
|
||||||
|
assert res.num_updated_rows == 0
|
||||||
|
|
||||||
|
|
||||||
def test_replace_range(mem_db):
|
def test_replace_range(mem_db):
|
||||||
@@ -131,7 +140,7 @@ def test_replace_range(mem_db):
|
|||||||
new_chunks = [
|
new_chunks = [
|
||||||
{"doc_id": 1, "chunk_id": 0, "text": "Baz"},
|
{"doc_id": 1, "chunk_id": 0, "text": "Baz"},
|
||||||
]
|
]
|
||||||
stats = (
|
res = (
|
||||||
table.merge_insert(["doc_id", "chunk_id"])
|
table.merge_insert(["doc_id", "chunk_id"])
|
||||||
.when_matched_update_all()
|
.when_matched_update_all()
|
||||||
.when_not_matched_insert_all()
|
.when_not_matched_insert_all()
|
||||||
@@ -139,12 +148,15 @@ def test_replace_range(mem_db):
|
|||||||
.execute(new_chunks)
|
.execute(new_chunks)
|
||||||
)
|
)
|
||||||
table.count_rows("doc_id = 1") # 1
|
table.count_rows("doc_id = 1") # 1
|
||||||
stats # {'num_inserted_rows': 0, 'num_updated_rows': 1, 'num_deleted_rows': 1}
|
res
|
||||||
# --8<-- [end:replace_range]
|
# MergeResult(version=2, num_updated_rows=1,
|
||||||
|
# num_inserted_rows=0, num_deleted_rows=1)
|
||||||
|
# --8<-- [end:insert_if_not_exists]
|
||||||
assert table.count_rows("doc_id = 1") == 1
|
assert table.count_rows("doc_id = 1") == 1
|
||||||
assert stats["num_inserted_rows"] == 0
|
assert res.version == 2
|
||||||
assert stats["num_updated_rows"] == 1
|
assert res.num_inserted_rows == 0
|
||||||
assert stats["num_deleted_rows"] == 1
|
assert res.num_deleted_rows == 1
|
||||||
|
assert res.num_updated_rows == 1
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
@pytest.mark.asyncio
|
||||||
@@ -163,7 +175,7 @@ async def test_replace_range_async(mem_db_async):
|
|||||||
new_chunks = [
|
new_chunks = [
|
||||||
{"doc_id": 1, "chunk_id": 0, "text": "Baz"},
|
{"doc_id": 1, "chunk_id": 0, "text": "Baz"},
|
||||||
]
|
]
|
||||||
stats = await (
|
res = await (
|
||||||
table.merge_insert(["doc_id", "chunk_id"])
|
table.merge_insert(["doc_id", "chunk_id"])
|
||||||
.when_matched_update_all()
|
.when_matched_update_all()
|
||||||
.when_not_matched_insert_all()
|
.when_not_matched_insert_all()
|
||||||
@@ -171,9 +183,12 @@ async def test_replace_range_async(mem_db_async):
|
|||||||
.execute(new_chunks)
|
.execute(new_chunks)
|
||||||
)
|
)
|
||||||
await table.count_rows("doc_id = 1") # 1
|
await table.count_rows("doc_id = 1") # 1
|
||||||
stats # {'num_inserted_rows': 0, 'num_updated_rows': 1, 'num_deleted_rows': 1}
|
res
|
||||||
# --8<-- [end:replace_range_async]
|
# MergeResult(version=2, num_updated_rows=1,
|
||||||
|
# num_inserted_rows=0, num_deleted_rows=1)
|
||||||
|
# --8<-- [end:insert_if_not_exists]
|
||||||
assert await table.count_rows("doc_id = 1") == 1
|
assert await table.count_rows("doc_id = 1") == 1
|
||||||
assert stats["num_inserted_rows"] == 0
|
assert res.version == 2
|
||||||
assert stats["num_updated_rows"] == 1
|
assert res.num_inserted_rows == 0
|
||||||
assert stats["num_deleted_rows"] == 1
|
assert res.num_deleted_rows == 1
|
||||||
|
assert res.num_updated_rows == 1
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import lancedb
|
|||||||
|
|
||||||
# --8<-- [end:import-lancedb]
|
# --8<-- [end:import-lancedb]
|
||||||
# --8<-- [start:import-numpy]
|
# --8<-- [start:import-numpy]
|
||||||
from lancedb.query import BoostQuery, MatchQuery
|
from lancedb.query import BooleanQuery, BoostQuery, MatchQuery, Occur
|
||||||
import numpy as np
|
import numpy as np
|
||||||
import pyarrow as pa
|
import pyarrow as pa
|
||||||
|
|
||||||
@@ -156,6 +156,9 @@ async def test_vector_search_async():
|
|||||||
# --8<-- [end:search_result_async_as_list]
|
# --8<-- [end:search_result_async_as_list]
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.skipif(
|
||||||
|
os.name == "nt", reason="Need to fix https://github.com/lancedb/lance/issues/3905"
|
||||||
|
)
|
||||||
def test_fts_fuzzy_query():
|
def test_fts_fuzzy_query():
|
||||||
uri = "data/fuzzy-example"
|
uri = "data/fuzzy-example"
|
||||||
db = lancedb.connect(uri)
|
db = lancedb.connect(uri)
|
||||||
@@ -188,7 +191,19 @@ def test_fts_fuzzy_query():
|
|||||||
"food", # 1 insertion
|
"food", # 1 insertion
|
||||||
}
|
}
|
||||||
|
|
||||||
|
results = table.search(
|
||||||
|
MatchQuery("foo", "text", fuzziness=1, prefix_length=3)
|
||||||
|
).to_pandas()
|
||||||
|
assert len(results) == 2
|
||||||
|
assert set(results["text"].to_list()) == {
|
||||||
|
"foo",
|
||||||
|
"food",
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.skipif(
|
||||||
|
os.name == "nt", reason="Need to fix https://github.com/lancedb/lance/issues/3905"
|
||||||
|
)
|
||||||
def test_fts_boost_query():
|
def test_fts_boost_query():
|
||||||
uri = "data/boost-example"
|
uri = "data/boost-example"
|
||||||
db = lancedb.connect(uri)
|
db = lancedb.connect(uri)
|
||||||
@@ -234,6 +249,63 @@ def test_fts_boost_query():
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.skipif(
|
||||||
|
os.name == "nt", reason="Need to fix https://github.com/lancedb/lance/issues/3905"
|
||||||
|
)
|
||||||
|
def test_fts_boolean_query(tmp_path):
|
||||||
|
uri = tmp_path / "boolean-example"
|
||||||
|
db = lancedb.connect(uri)
|
||||||
|
table = db.create_table(
|
||||||
|
"my_table_fts_boolean",
|
||||||
|
data=[
|
||||||
|
{"text": "The cat and dog are playing"},
|
||||||
|
{"text": "The cat is sleeping"},
|
||||||
|
{"text": "The dog is barking"},
|
||||||
|
{"text": "The dog chases the cat"},
|
||||||
|
],
|
||||||
|
mode="overwrite",
|
||||||
|
)
|
||||||
|
table.create_fts_index("text", use_tantivy=False, replace=True)
|
||||||
|
|
||||||
|
# SHOULD
|
||||||
|
results = table.search(
|
||||||
|
MatchQuery("cat", "text") | MatchQuery("dog", "text")
|
||||||
|
).to_pandas()
|
||||||
|
assert len(results) == 4
|
||||||
|
assert set(results["text"].to_list()) == {
|
||||||
|
"The cat and dog are playing",
|
||||||
|
"The cat is sleeping",
|
||||||
|
"The dog is barking",
|
||||||
|
"The dog chases the cat",
|
||||||
|
}
|
||||||
|
# MUST
|
||||||
|
results = table.search(
|
||||||
|
MatchQuery("cat", "text") & MatchQuery("dog", "text")
|
||||||
|
).to_pandas()
|
||||||
|
assert len(results) == 2
|
||||||
|
assert set(results["text"].to_list()) == {
|
||||||
|
"The cat and dog are playing",
|
||||||
|
"The dog chases the cat",
|
||||||
|
}
|
||||||
|
|
||||||
|
# MUST NOT
|
||||||
|
results = table.search(
|
||||||
|
BooleanQuery(
|
||||||
|
[
|
||||||
|
(Occur.MUST, MatchQuery("cat", "text")),
|
||||||
|
(Occur.MUST_NOT, MatchQuery("dog", "text")),
|
||||||
|
]
|
||||||
|
)
|
||||||
|
).to_pandas()
|
||||||
|
assert len(results) == 1
|
||||||
|
assert set(results["text"].to_list()) == {
|
||||||
|
"The cat is sleeping",
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.skipif(
|
||||||
|
os.name == "nt", reason="Need to fix https://github.com/lancedb/lance/issues/3905"
|
||||||
|
)
|
||||||
def test_fts_native():
|
def test_fts_native():
|
||||||
# --8<-- [start:basic_fts]
|
# --8<-- [start:basic_fts]
|
||||||
uri = "data/sample-lancedb"
|
uri = "data/sample-lancedb"
|
||||||
@@ -282,6 +354,9 @@ def test_fts_native():
|
|||||||
# --8<-- [end:fts_incremental_index]
|
# --8<-- [end:fts_incremental_index]
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.skipif(
|
||||||
|
os.name == "nt", reason="Need to fix https://github.com/lancedb/lance/issues/3905"
|
||||||
|
)
|
||||||
@pytest.mark.asyncio
|
@pytest.mark.asyncio
|
||||||
async def test_fts_native_async():
|
async def test_fts_native_async():
|
||||||
# --8<-- [start:basic_fts_async]
|
# --8<-- [start:basic_fts_async]
|
||||||
|
|||||||
@@ -215,6 +215,19 @@ def test_search_fts(table, use_tantivy):
|
|||||||
assert len(results) == 5
|
assert len(results) == 5
|
||||||
assert len(results[0]) == 3 # id, text, _score
|
assert len(results[0]) == 3 # id, text, _score
|
||||||
|
|
||||||
|
# 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
|
||||||
async def test_fts_select_async(async_table):
|
async def test_fts_select_async(async_table):
|
||||||
@@ -287,7 +300,7 @@ def test_search_fts_phrase_query(table):
|
|||||||
assert False
|
assert False
|
||||||
except Exception:
|
except Exception:
|
||||||
pass
|
pass
|
||||||
table.create_fts_index("text", use_tantivy=False, 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()
|
||||||
phrase_results = table.search('"puppy runs"').limit(100).to_list()
|
phrase_results = table.search('"puppy runs"').limit(100).to_list()
|
||||||
assert len(results) > len(phrase_results)
|
assert len(results) > len(phrase_results)
|
||||||
@@ -312,7 +325,7 @@ async def test_search_fts_phrase_query_async(async_table):
|
|||||||
assert False
|
assert False
|
||||||
except Exception:
|
except Exception:
|
||||||
pass
|
pass
|
||||||
await async_table.create_index("text", config=FTS())
|
await async_table.create_index("text", config=FTS(with_position=True))
|
||||||
results = await async_table.query().nearest_to_text("puppy").limit(100).to_list()
|
results = await async_table.query().nearest_to_text("puppy").limit(100).to_list()
|
||||||
phrase_results = (
|
phrase_results = (
|
||||||
await async_table.query().nearest_to_text('"puppy runs"').limit(100).to_list()
|
await async_table.query().nearest_to_text('"puppy runs"').limit(100).to_list()
|
||||||
@@ -649,7 +662,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", use_tantivy=False)
|
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
|
||||||
|
|||||||
@@ -25,6 +25,8 @@ from lancedb.query import (
|
|||||||
AsyncQueryBase,
|
AsyncQueryBase,
|
||||||
AsyncVectorQuery,
|
AsyncVectorQuery,
|
||||||
LanceVectorQueryBuilder,
|
LanceVectorQueryBuilder,
|
||||||
|
MatchQuery,
|
||||||
|
PhraseQuery,
|
||||||
Query,
|
Query,
|
||||||
FullTextSearchQuery,
|
FullTextSearchQuery,
|
||||||
)
|
)
|
||||||
@@ -437,6 +439,33 @@ def test_query_builder_with_filter(table):
|
|||||||
assert all(np.array(rs[0]["vector"]) == [3, 4])
|
assert all(np.array(rs[0]["vector"]) == [3, 4])
|
||||||
|
|
||||||
|
|
||||||
|
def test_invalid_nprobes_sync(table):
|
||||||
|
with pytest.raises(ValueError, match="minimum_nprobes must be greater than 0"):
|
||||||
|
LanceVectorQueryBuilder(table, [0, 0], "vector").minimum_nprobes(0).to_list()
|
||||||
|
with pytest.raises(
|
||||||
|
ValueError, match="maximum_nprobes must be greater than minimum_nprobes"
|
||||||
|
):
|
||||||
|
LanceVectorQueryBuilder(table, [0, 0], "vector").maximum_nprobes(5).to_list()
|
||||||
|
with pytest.raises(
|
||||||
|
ValueError, match="minimum_nprobes must be less or equal to maximum_nprobes"
|
||||||
|
):
|
||||||
|
LanceVectorQueryBuilder(table, [0, 0], "vector").minimum_nprobes(100).to_list()
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_invalid_nprobes_async(table_async: AsyncTable):
|
||||||
|
with pytest.raises(ValueError, match="minimum_nprobes must be greater than 0"):
|
||||||
|
await table_async.vector_search([0, 0]).minimum_nprobes(0).to_list()
|
||||||
|
with pytest.raises(
|
||||||
|
ValueError, match="maximum_nprobes must be greater than minimum_nprobes"
|
||||||
|
):
|
||||||
|
await table_async.vector_search([0, 0]).maximum_nprobes(5).to_list()
|
||||||
|
with pytest.raises(
|
||||||
|
ValueError, match="minimum_nprobes must be less or equal to maximum_nprobes"
|
||||||
|
):
|
||||||
|
await table_async.vector_search([0, 0]).minimum_nprobes(100).to_list()
|
||||||
|
|
||||||
|
|
||||||
def test_query_builder_with_prefilter(table):
|
def test_query_builder_with_prefilter(table):
|
||||||
df = (
|
df = (
|
||||||
LanceVectorQueryBuilder(table, [0, 0], "vector")
|
LanceVectorQueryBuilder(table, [0, 0], "vector")
|
||||||
@@ -583,6 +612,21 @@ async def test_query_async(table_async: AsyncTable):
|
|||||||
table_async.query().nearest_to(pa.array([1, 2])).nprobes(10),
|
table_async.query().nearest_to(pa.array([1, 2])).nprobes(10),
|
||||||
expected_num_rows=2,
|
expected_num_rows=2,
|
||||||
)
|
)
|
||||||
|
await check_query(
|
||||||
|
table_async.query().nearest_to(pa.array([1, 2])).minimum_nprobes(10),
|
||||||
|
expected_num_rows=2,
|
||||||
|
)
|
||||||
|
await check_query(
|
||||||
|
table_async.query().nearest_to(pa.array([1, 2])).maximum_nprobes(30),
|
||||||
|
expected_num_rows=2,
|
||||||
|
)
|
||||||
|
await check_query(
|
||||||
|
table_async.query()
|
||||||
|
.nearest_to(pa.array([1, 2]))
|
||||||
|
.minimum_nprobes(10)
|
||||||
|
.maximum_nprobes(20),
|
||||||
|
expected_num_rows=2,
|
||||||
|
)
|
||||||
await check_query(
|
await check_query(
|
||||||
table_async.query().nearest_to(pa.array([1, 2])).bypass_vector_index(),
|
table_async.query().nearest_to(pa.array([1, 2])).bypass_vector_index(),
|
||||||
expected_num_rows=2,
|
expected_num_rows=2,
|
||||||
@@ -909,7 +953,39 @@ def test_query_serialization_sync(table: lancedb.table.Table):
|
|||||||
|
|
||||||
q = table.search([5.0, 6.0]).nprobes(10).refine_factor(5).to_query_object()
|
q = table.search([5.0, 6.0]).nprobes(10).refine_factor(5).to_query_object()
|
||||||
check_set_props(
|
check_set_props(
|
||||||
q, vector_column="vector", vector=[5.0, 6.0], nprobes=10, refine_factor=5
|
q,
|
||||||
|
vector_column="vector",
|
||||||
|
vector=[5.0, 6.0],
|
||||||
|
minimum_nprobes=10,
|
||||||
|
maximum_nprobes=10,
|
||||||
|
refine_factor=5,
|
||||||
|
)
|
||||||
|
|
||||||
|
q = table.search([5.0, 6.0]).minimum_nprobes(10).to_query_object()
|
||||||
|
check_set_props(
|
||||||
|
q,
|
||||||
|
vector_column="vector",
|
||||||
|
vector=[5.0, 6.0],
|
||||||
|
minimum_nprobes=10,
|
||||||
|
maximum_nprobes=None,
|
||||||
|
)
|
||||||
|
|
||||||
|
q = table.search([5.0, 6.0]).nprobes(50).to_query_object()
|
||||||
|
check_set_props(
|
||||||
|
q,
|
||||||
|
vector_column="vector",
|
||||||
|
vector=[5.0, 6.0],
|
||||||
|
minimum_nprobes=50,
|
||||||
|
maximum_nprobes=50,
|
||||||
|
)
|
||||||
|
|
||||||
|
q = table.search([5.0, 6.0]).maximum_nprobes(10).to_query_object()
|
||||||
|
check_set_props(
|
||||||
|
q,
|
||||||
|
vector_column="vector",
|
||||||
|
vector=[5.0, 6.0],
|
||||||
|
maximum_nprobes=10,
|
||||||
|
minimum_nprobes=None,
|
||||||
)
|
)
|
||||||
|
|
||||||
q = table.search([5.0, 6.0]).distance_range(0.0, 1.0).to_query_object()
|
q = table.search([5.0, 6.0]).distance_range(0.0, 1.0).to_query_object()
|
||||||
@@ -961,7 +1037,8 @@ async def test_query_serialization_async(table_async: AsyncTable):
|
|||||||
limit=10,
|
limit=10,
|
||||||
vector=sample_vector,
|
vector=sample_vector,
|
||||||
postfilter=False,
|
postfilter=False,
|
||||||
nprobes=20,
|
minimum_nprobes=20,
|
||||||
|
maximum_nprobes=20,
|
||||||
with_row_id=False,
|
with_row_id=False,
|
||||||
bypass_vector_index=False,
|
bypass_vector_index=False,
|
||||||
)
|
)
|
||||||
@@ -971,7 +1048,20 @@ async def test_query_serialization_async(table_async: AsyncTable):
|
|||||||
q,
|
q,
|
||||||
vector=sample_vector,
|
vector=sample_vector,
|
||||||
postfilter=False,
|
postfilter=False,
|
||||||
nprobes=20,
|
minimum_nprobes=20,
|
||||||
|
maximum_nprobes=20,
|
||||||
|
with_row_id=False,
|
||||||
|
bypass_vector_index=False,
|
||||||
|
limit=10,
|
||||||
|
)
|
||||||
|
|
||||||
|
q = (await table_async.search([5.0, 6.0])).nprobes(50).to_query_object()
|
||||||
|
check_set_props(
|
||||||
|
q,
|
||||||
|
vector=sample_vector,
|
||||||
|
postfilter=False,
|
||||||
|
minimum_nprobes=50,
|
||||||
|
maximum_nprobes=50,
|
||||||
with_row_id=False,
|
with_row_id=False,
|
||||||
bypass_vector_index=False,
|
bypass_vector_index=False,
|
||||||
limit=10,
|
limit=10,
|
||||||
@@ -990,7 +1080,8 @@ async def test_query_serialization_async(table_async: AsyncTable):
|
|||||||
filter="id = 1",
|
filter="id = 1",
|
||||||
postfilter=True,
|
postfilter=True,
|
||||||
vector=sample_vector,
|
vector=sample_vector,
|
||||||
nprobes=20,
|
minimum_nprobes=20,
|
||||||
|
maximum_nprobes=20,
|
||||||
with_row_id=False,
|
with_row_id=False,
|
||||||
bypass_vector_index=False,
|
bypass_vector_index=False,
|
||||||
)
|
)
|
||||||
@@ -1004,7 +1095,8 @@ async def test_query_serialization_async(table_async: AsyncTable):
|
|||||||
check_set_props(
|
check_set_props(
|
||||||
q,
|
q,
|
||||||
vector=sample_vector,
|
vector=sample_vector,
|
||||||
nprobes=10,
|
minimum_nprobes=10,
|
||||||
|
maximum_nprobes=10,
|
||||||
refine_factor=5,
|
refine_factor=5,
|
||||||
postfilter=False,
|
postfilter=False,
|
||||||
with_row_id=False,
|
with_row_id=False,
|
||||||
@@ -1012,6 +1104,18 @@ async def test_query_serialization_async(table_async: AsyncTable):
|
|||||||
limit=10,
|
limit=10,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
q = (await table_async.search([5.0, 6.0])).minimum_nprobes(5).to_query_object()
|
||||||
|
check_set_props(
|
||||||
|
q,
|
||||||
|
vector=sample_vector,
|
||||||
|
minimum_nprobes=5,
|
||||||
|
maximum_nprobes=20,
|
||||||
|
postfilter=False,
|
||||||
|
with_row_id=False,
|
||||||
|
bypass_vector_index=False,
|
||||||
|
limit=10,
|
||||||
|
)
|
||||||
|
|
||||||
q = (
|
q = (
|
||||||
(await table_async.search([5.0, 6.0]))
|
(await table_async.search([5.0, 6.0]))
|
||||||
.distance_range(0.0, 1.0)
|
.distance_range(0.0, 1.0)
|
||||||
@@ -1023,7 +1127,8 @@ async def test_query_serialization_async(table_async: AsyncTable):
|
|||||||
lower_bound=0.0,
|
lower_bound=0.0,
|
||||||
upper_bound=1.0,
|
upper_bound=1.0,
|
||||||
postfilter=False,
|
postfilter=False,
|
||||||
nprobes=20,
|
minimum_nprobes=20,
|
||||||
|
maximum_nprobes=20,
|
||||||
with_row_id=False,
|
with_row_id=False,
|
||||||
bypass_vector_index=False,
|
bypass_vector_index=False,
|
||||||
limit=10,
|
limit=10,
|
||||||
@@ -1035,7 +1140,8 @@ async def test_query_serialization_async(table_async: AsyncTable):
|
|||||||
distance_type="cosine",
|
distance_type="cosine",
|
||||||
vector=sample_vector,
|
vector=sample_vector,
|
||||||
postfilter=False,
|
postfilter=False,
|
||||||
nprobes=20,
|
minimum_nprobes=20,
|
||||||
|
maximum_nprobes=20,
|
||||||
with_row_id=False,
|
with_row_id=False,
|
||||||
bypass_vector_index=False,
|
bypass_vector_index=False,
|
||||||
limit=10,
|
limit=10,
|
||||||
@@ -1047,7 +1153,8 @@ async def test_query_serialization_async(table_async: AsyncTable):
|
|||||||
ef=7,
|
ef=7,
|
||||||
vector=sample_vector,
|
vector=sample_vector,
|
||||||
postfilter=False,
|
postfilter=False,
|
||||||
nprobes=20,
|
minimum_nprobes=20,
|
||||||
|
maximum_nprobes=20,
|
||||||
with_row_id=False,
|
with_row_id=False,
|
||||||
bypass_vector_index=False,
|
bypass_vector_index=False,
|
||||||
limit=10,
|
limit=10,
|
||||||
@@ -1059,24 +1166,34 @@ async def test_query_serialization_async(table_async: AsyncTable):
|
|||||||
bypass_vector_index=True,
|
bypass_vector_index=True,
|
||||||
vector=sample_vector,
|
vector=sample_vector,
|
||||||
postfilter=False,
|
postfilter=False,
|
||||||
nprobes=20,
|
minimum_nprobes=20,
|
||||||
|
maximum_nprobes=20,
|
||||||
with_row_id=False,
|
with_row_id=False,
|
||||||
limit=10,
|
limit=10,
|
||||||
)
|
)
|
||||||
|
|
||||||
# FTS queries
|
# FTS queries
|
||||||
q = (await table_async.search("foo")).limit(10).to_query_object()
|
match_query = MatchQuery("foo", "text")
|
||||||
|
q = (await table_async.search(match_query)).limit(10).to_query_object()
|
||||||
check_set_props(
|
check_set_props(
|
||||||
q,
|
q,
|
||||||
limit=10,
|
limit=10,
|
||||||
full_text_query=FullTextSearchQuery(columns=[], query="foo"),
|
full_text_query=FullTextSearchQuery(columns=None, query=match_query),
|
||||||
with_row_id=False,
|
with_row_id=False,
|
||||||
)
|
)
|
||||||
|
|
||||||
q = (await table_async.search("foo", query_type="fts")).to_query_object()
|
q = (await table_async.search(match_query)).to_query_object()
|
||||||
check_set_props(
|
check_set_props(
|
||||||
q,
|
q,
|
||||||
full_text_query=FullTextSearchQuery(columns=[], query="foo"),
|
full_text_query=FullTextSearchQuery(columns=None, query=match_query),
|
||||||
|
with_row_id=False,
|
||||||
|
)
|
||||||
|
|
||||||
|
phrase_query = PhraseQuery("foo", "text", slop=1)
|
||||||
|
q = (await table_async.search(phrase_query)).to_query_object()
|
||||||
|
check_set_props(
|
||||||
|
q,
|
||||||
|
full_text_query=FullTextSearchQuery(columns=None, query=phrase_query),
|
||||||
with_row_id=False,
|
with_row_id=False,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -149,6 +149,24 @@ async def test_async_checkout():
|
|||||||
assert await table.count_rows() == 300
|
assert await table.count_rows() == 300
|
||||||
|
|
||||||
|
|
||||||
|
def test_table_len_sync():
|
||||||
|
def handler(request):
|
||||||
|
if request.path == "/v1/table/test/create/?mode=create":
|
||||||
|
request.send_response(200)
|
||||||
|
request.send_header("Content-Type", "application/json")
|
||||||
|
request.end_headers()
|
||||||
|
request.wfile.write(b"{}")
|
||||||
|
|
||||||
|
request.send_response(200)
|
||||||
|
request.send_header("Content-Type", "application/json")
|
||||||
|
request.end_headers()
|
||||||
|
request.wfile.write(json.dumps(1).encode())
|
||||||
|
|
||||||
|
with mock_lancedb_connection(handler) as db:
|
||||||
|
table = db.create_table("test", [{"id": 1}])
|
||||||
|
assert len(table) == 1
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
@pytest.mark.asyncio
|
||||||
async def test_http_error():
|
async def test_http_error():
|
||||||
request_id_holder = {"request_id": None}
|
request_id_holder = {"request_id": None}
|
||||||
@@ -478,6 +496,8 @@ def test_query_sync_minimal():
|
|||||||
"ef": None,
|
"ef": None,
|
||||||
"vector": [1.0, 2.0, 3.0],
|
"vector": [1.0, 2.0, 3.0],
|
||||||
"nprobes": 20,
|
"nprobes": 20,
|
||||||
|
"minimum_nprobes": 20,
|
||||||
|
"maximum_nprobes": 20,
|
||||||
"version": None,
|
"version": None,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -518,6 +538,8 @@ def test_query_sync_maximal():
|
|||||||
"refine_factor": 10,
|
"refine_factor": 10,
|
||||||
"vector": [1.0, 2.0, 3.0],
|
"vector": [1.0, 2.0, 3.0],
|
||||||
"nprobes": 5,
|
"nprobes": 5,
|
||||||
|
"minimum_nprobes": 5,
|
||||||
|
"maximum_nprobes": 5,
|
||||||
"lower_bound": None,
|
"lower_bound": None,
|
||||||
"upper_bound": None,
|
"upper_bound": None,
|
||||||
"ef": None,
|
"ef": None,
|
||||||
@@ -546,6 +568,66 @@ def test_query_sync_maximal():
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def test_query_sync_nprobes():
|
||||||
|
def handler(body):
|
||||||
|
assert body == {
|
||||||
|
"distance_type": "l2",
|
||||||
|
"k": 10,
|
||||||
|
"prefilter": True,
|
||||||
|
"fast_search": True,
|
||||||
|
"vector_column": "vector2",
|
||||||
|
"refine_factor": None,
|
||||||
|
"lower_bound": None,
|
||||||
|
"upper_bound": None,
|
||||||
|
"ef": None,
|
||||||
|
"vector": [1.0, 2.0, 3.0],
|
||||||
|
"nprobes": 5,
|
||||||
|
"minimum_nprobes": 5,
|
||||||
|
"maximum_nprobes": 15,
|
||||||
|
"version": None,
|
||||||
|
}
|
||||||
|
|
||||||
|
return pa.table({"id": [1, 2, 3], "name": ["a", "b", "c"]})
|
||||||
|
|
||||||
|
with query_test_table(handler) as table:
|
||||||
|
(
|
||||||
|
table.search([1, 2, 3], vector_column_name="vector2", fast_search=True)
|
||||||
|
.minimum_nprobes(5)
|
||||||
|
.maximum_nprobes(15)
|
||||||
|
.to_list()
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def test_query_sync_no_max_nprobes():
|
||||||
|
def handler(body):
|
||||||
|
assert body == {
|
||||||
|
"distance_type": "l2",
|
||||||
|
"k": 10,
|
||||||
|
"prefilter": True,
|
||||||
|
"fast_search": True,
|
||||||
|
"vector_column": "vector2",
|
||||||
|
"refine_factor": None,
|
||||||
|
"lower_bound": None,
|
||||||
|
"upper_bound": None,
|
||||||
|
"ef": None,
|
||||||
|
"vector": [1.0, 2.0, 3.0],
|
||||||
|
"nprobes": 5,
|
||||||
|
"minimum_nprobes": 5,
|
||||||
|
"maximum_nprobes": 0,
|
||||||
|
"version": None,
|
||||||
|
}
|
||||||
|
|
||||||
|
return pa.table({"id": [1, 2, 3], "name": ["a", "b", "c"]})
|
||||||
|
|
||||||
|
with query_test_table(handler) as table:
|
||||||
|
(
|
||||||
|
table.search([1, 2, 3], vector_column_name="vector2", fast_search=True)
|
||||||
|
.minimum_nprobes(5)
|
||||||
|
.maximum_nprobes(0)
|
||||||
|
.to_list()
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize("server_version", [Version("0.1.0"), Version("0.2.0")])
|
@pytest.mark.parametrize("server_version", [Version("0.1.0"), Version("0.2.0")])
|
||||||
def test_query_sync_batch_queries(server_version):
|
def test_query_sync_batch_queries(server_version):
|
||||||
def handler(body):
|
def handler(body):
|
||||||
@@ -648,6 +730,8 @@ def test_query_sync_hybrid():
|
|||||||
"refine_factor": None,
|
"refine_factor": None,
|
||||||
"vector": [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0],
|
"vector": [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0],
|
||||||
"nprobes": 20,
|
"nprobes": 20,
|
||||||
|
"minimum_nprobes": 20,
|
||||||
|
"maximum_nprobes": 20,
|
||||||
"lower_bound": None,
|
"lower_bound": None,
|
||||||
"upper_bound": None,
|
"upper_bound": None,
|
||||||
"ef": None,
|
"ef": None,
|
||||||
|
|||||||
@@ -245,7 +245,7 @@ def test_s3_dynamodb_sync(s3_bucket: str, commit_table: str, monkeypatch):
|
|||||||
NotImplementedError,
|
NotImplementedError,
|
||||||
match="Full-text search is only supported on the local filesystem",
|
match="Full-text search is only supported on the local filesystem",
|
||||||
):
|
):
|
||||||
table.create_fts_index("x")
|
table.create_fts_index("x", use_tantivy=True)
|
||||||
|
|
||||||
# make sure list tables still works
|
# make sure list tables still works
|
||||||
assert db.table_names() == ["test_ddb_sync"]
|
assert db.table_names() == ["test_ddb_sync"]
|
||||||
|
|||||||
@@ -106,15 +106,22 @@ async def test_update_async(mem_db_async: AsyncConnection):
|
|||||||
table = await mem_db_async.create_table("some_table", data=[{"id": 0}])
|
table = await mem_db_async.create_table("some_table", data=[{"id": 0}])
|
||||||
assert await table.count_rows("id == 0") == 1
|
assert await table.count_rows("id == 0") == 1
|
||||||
assert await table.count_rows("id == 7") == 0
|
assert await table.count_rows("id == 7") == 0
|
||||||
await table.update({"id": 7})
|
update_res = await table.update({"id": 7})
|
||||||
|
assert update_res.rows_updated == 1
|
||||||
|
assert update_res.version == 2
|
||||||
assert await table.count_rows("id == 7") == 1
|
assert await table.count_rows("id == 7") == 1
|
||||||
assert await table.count_rows("id == 0") == 0
|
assert await table.count_rows("id == 0") == 0
|
||||||
await table.add([{"id": 2}])
|
add_res = await table.add([{"id": 2}])
|
||||||
await table.update(where="id % 2 == 0", updates_sql={"id": "5"})
|
assert add_res.version == 3
|
||||||
|
update_res = await table.update(where="id % 2 == 0", updates_sql={"id": "5"})
|
||||||
|
assert update_res.rows_updated == 1
|
||||||
|
assert update_res.version == 4
|
||||||
assert await table.count_rows("id == 7") == 1
|
assert await table.count_rows("id == 7") == 1
|
||||||
assert await table.count_rows("id == 2") == 0
|
assert await table.count_rows("id == 2") == 0
|
||||||
assert await table.count_rows("id == 5") == 1
|
assert await table.count_rows("id == 5") == 1
|
||||||
await table.update({"id": 10}, where="id == 5")
|
update_res = await table.update({"id": 10}, where="id == 5")
|
||||||
|
assert update_res.rows_updated == 1
|
||||||
|
assert update_res.version == 5
|
||||||
assert await table.count_rows("id == 10") == 1
|
assert await table.count_rows("id == 10") == 1
|
||||||
|
|
||||||
|
|
||||||
@@ -437,7 +444,8 @@ def test_add_pydantic_model(mem_db: DBConnection):
|
|||||||
content="foo", meta=Metadata(source="bar", timestamp=datetime.now())
|
content="foo", meta=Metadata(source="bar", timestamp=datetime.now())
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
tbl.add([expected])
|
add_res = tbl.add([expected])
|
||||||
|
assert add_res.version == 2
|
||||||
|
|
||||||
result = tbl.search([0.0, 0.0]).limit(1).to_pydantic(LanceSchema)[0]
|
result = tbl.search([0.0, 0.0]).limit(1).to_pydantic(LanceSchema)[0]
|
||||||
assert result == expected
|
assert result == expected
|
||||||
@@ -459,11 +467,12 @@ async def test_add_async(mem_db_async: AsyncConnection):
|
|||||||
],
|
],
|
||||||
)
|
)
|
||||||
assert await table.count_rows() == 2
|
assert await table.count_rows() == 2
|
||||||
await table.add(
|
add_res = await table.add(
|
||||||
data=[
|
data=[
|
||||||
{"vector": [10.0, 11.0], "item": "baz", "price": 30.0},
|
{"vector": [10.0, 11.0], "item": "baz", "price": 30.0},
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
|
assert add_res.version == 2
|
||||||
assert await table.count_rows() == 3
|
assert await table.count_rows() == 3
|
||||||
|
|
||||||
|
|
||||||
@@ -760,6 +769,29 @@ def test_restore(mem_db: DBConnection):
|
|||||||
table.restore(0)
|
table.restore(0)
|
||||||
|
|
||||||
|
|
||||||
|
def test_restore_with_tags(mem_db: DBConnection):
|
||||||
|
table = mem_db.create_table(
|
||||||
|
"my_table",
|
||||||
|
data=[{"vector": [1.1, 0.9], "type": "vector"}],
|
||||||
|
)
|
||||||
|
tag = "tag1"
|
||||||
|
table.tags.create(tag, 1)
|
||||||
|
table.add([{"vector": [0.5, 0.2], "type": "vector"}])
|
||||||
|
table.restore(tag)
|
||||||
|
assert len(table.list_versions()) == 3
|
||||||
|
assert len(table) == 1
|
||||||
|
expected = table.to_arrow()
|
||||||
|
|
||||||
|
table.add([{"vector": [0.3, 0.3], "type": "vector"}])
|
||||||
|
table.checkout("tag1")
|
||||||
|
table.restore()
|
||||||
|
assert len(table.list_versions()) == 5
|
||||||
|
assert table.to_arrow() == expected
|
||||||
|
|
||||||
|
with pytest.raises(ValueError):
|
||||||
|
table.restore("tag_unknown")
|
||||||
|
|
||||||
|
|
||||||
def test_merge(tmp_db: DBConnection, tmp_path):
|
def test_merge(tmp_db: DBConnection, tmp_path):
|
||||||
pytest.importorskip("lance")
|
pytest.importorskip("lance")
|
||||||
import lance
|
import lance
|
||||||
@@ -795,7 +827,8 @@ def test_delete(mem_db: DBConnection):
|
|||||||
)
|
)
|
||||||
assert len(table) == 2
|
assert len(table) == 2
|
||||||
assert len(table.list_versions()) == 1
|
assert len(table.list_versions()) == 1
|
||||||
table.delete("id=0")
|
delete_res = table.delete("id=0")
|
||||||
|
assert delete_res.version == 2
|
||||||
assert len(table.list_versions()) == 2
|
assert len(table.list_versions()) == 2
|
||||||
assert table.version == 2
|
assert table.version == 2
|
||||||
assert len(table) == 1
|
assert len(table) == 1
|
||||||
@@ -809,7 +842,9 @@ def test_update(mem_db: DBConnection):
|
|||||||
)
|
)
|
||||||
assert len(table) == 2
|
assert len(table) == 2
|
||||||
assert len(table.list_versions()) == 1
|
assert len(table.list_versions()) == 1
|
||||||
table.update(where="id=0", values={"vector": [1.1, 1.1]})
|
update_res = table.update(where="id=0", values={"vector": [1.1, 1.1]})
|
||||||
|
assert update_res.version == 2
|
||||||
|
assert update_res.rows_updated == 1
|
||||||
assert len(table.list_versions()) == 2
|
assert len(table.list_versions()) == 2
|
||||||
assert table.version == 2
|
assert table.version == 2
|
||||||
assert len(table) == 2
|
assert len(table) == 2
|
||||||
@@ -898,9 +933,16 @@ def test_merge_insert(mem_db: DBConnection):
|
|||||||
new_data = pa.table({"a": [2, 3, 4], "b": ["x", "y", "z"]})
|
new_data = pa.table({"a": [2, 3, 4], "b": ["x", "y", "z"]})
|
||||||
|
|
||||||
# upsert
|
# upsert
|
||||||
table.merge_insert(
|
merge_insert_res = (
|
||||||
"a"
|
table.merge_insert("a")
|
||||||
).when_matched_update_all().when_not_matched_insert_all().execute(new_data)
|
.when_matched_update_all()
|
||||||
|
.when_not_matched_insert_all()
|
||||||
|
.execute(new_data, timeout=timedelta(seconds=10))
|
||||||
|
)
|
||||||
|
assert merge_insert_res.version == 2
|
||||||
|
assert merge_insert_res.num_inserted_rows == 1
|
||||||
|
assert merge_insert_res.num_updated_rows == 2
|
||||||
|
assert merge_insert_res.num_deleted_rows == 0
|
||||||
|
|
||||||
expected = pa.table({"a": [1, 2, 3, 4], "b": ["a", "x", "y", "z"]})
|
expected = pa.table({"a": [1, 2, 3, 4], "b": ["a", "x", "y", "z"]})
|
||||||
assert table.to_arrow().sort_by("a") == expected
|
assert table.to_arrow().sort_by("a") == expected
|
||||||
@@ -908,17 +950,28 @@ def test_merge_insert(mem_db: DBConnection):
|
|||||||
table.restore(version)
|
table.restore(version)
|
||||||
|
|
||||||
# conditional update
|
# conditional update
|
||||||
table.merge_insert("a").when_matched_update_all(where="target.b = 'b'").execute(
|
merge_insert_res = (
|
||||||
new_data
|
table.merge_insert("a")
|
||||||
|
.when_matched_update_all(where="target.b = 'b'")
|
||||||
|
.execute(new_data)
|
||||||
)
|
)
|
||||||
|
assert merge_insert_res.version == 4
|
||||||
|
assert merge_insert_res.num_inserted_rows == 0
|
||||||
|
assert merge_insert_res.num_updated_rows == 1
|
||||||
|
assert merge_insert_res.num_deleted_rows == 0
|
||||||
expected = pa.table({"a": [1, 2, 3], "b": ["a", "x", "c"]})
|
expected = pa.table({"a": [1, 2, 3], "b": ["a", "x", "c"]})
|
||||||
assert table.to_arrow().sort_by("a") == expected
|
assert table.to_arrow().sort_by("a") == expected
|
||||||
|
|
||||||
table.restore(version)
|
table.restore(version)
|
||||||
|
|
||||||
# insert-if-not-exists
|
# insert-if-not-exists
|
||||||
table.merge_insert("a").when_not_matched_insert_all().execute(new_data)
|
merge_insert_res = (
|
||||||
|
table.merge_insert("a").when_not_matched_insert_all().execute(new_data)
|
||||||
|
)
|
||||||
|
assert merge_insert_res.version == 6
|
||||||
|
assert merge_insert_res.num_inserted_rows == 1
|
||||||
|
assert merge_insert_res.num_updated_rows == 0
|
||||||
|
assert merge_insert_res.num_deleted_rows == 0
|
||||||
expected = pa.table({"a": [1, 2, 3, 4], "b": ["a", "b", "c", "z"]})
|
expected = pa.table({"a": [1, 2, 3, 4], "b": ["a", "b", "c", "z"]})
|
||||||
assert table.to_arrow().sort_by("a") == expected
|
assert table.to_arrow().sort_by("a") == expected
|
||||||
|
|
||||||
@@ -927,13 +980,17 @@ def test_merge_insert(mem_db: DBConnection):
|
|||||||
new_data = pa.table({"a": [2, 4], "b": ["x", "z"]})
|
new_data = pa.table({"a": [2, 4], "b": ["x", "z"]})
|
||||||
|
|
||||||
# replace-range
|
# replace-range
|
||||||
(
|
merge_insert_res = (
|
||||||
table.merge_insert("a")
|
table.merge_insert("a")
|
||||||
.when_matched_update_all()
|
.when_matched_update_all()
|
||||||
.when_not_matched_insert_all()
|
.when_not_matched_insert_all()
|
||||||
.when_not_matched_by_source_delete("a > 2")
|
.when_not_matched_by_source_delete("a > 2")
|
||||||
.execute(new_data)
|
.execute(new_data)
|
||||||
)
|
)
|
||||||
|
assert merge_insert_res.version == 8
|
||||||
|
assert merge_insert_res.num_inserted_rows == 1
|
||||||
|
assert merge_insert_res.num_updated_rows == 1
|
||||||
|
assert merge_insert_res.num_deleted_rows == 1
|
||||||
|
|
||||||
expected = pa.table({"a": [1, 2, 4], "b": ["a", "x", "z"]})
|
expected = pa.table({"a": [1, 2, 4], "b": ["a", "x", "z"]})
|
||||||
assert table.to_arrow().sort_by("a") == expected
|
assert table.to_arrow().sort_by("a") == expected
|
||||||
@@ -941,15 +998,27 @@ def test_merge_insert(mem_db: DBConnection):
|
|||||||
table.restore(version)
|
table.restore(version)
|
||||||
|
|
||||||
# replace-range no condition
|
# replace-range no condition
|
||||||
table.merge_insert(
|
merge_insert_res = (
|
||||||
"a"
|
table.merge_insert("a")
|
||||||
).when_matched_update_all().when_not_matched_insert_all().when_not_matched_by_source_delete().execute(
|
.when_matched_update_all()
|
||||||
new_data
|
.when_not_matched_insert_all()
|
||||||
|
.when_not_matched_by_source_delete()
|
||||||
|
.execute(new_data)
|
||||||
)
|
)
|
||||||
|
assert merge_insert_res.version == 10
|
||||||
|
assert merge_insert_res.num_inserted_rows == 1
|
||||||
|
assert merge_insert_res.num_updated_rows == 1
|
||||||
|
assert merge_insert_res.num_deleted_rows == 2
|
||||||
|
|
||||||
expected = pa.table({"a": [2, 4], "b": ["x", "z"]})
|
expected = pa.table({"a": [2, 4], "b": ["x", "z"]})
|
||||||
assert table.to_arrow().sort_by("a") == expected
|
assert table.to_arrow().sort_by("a") == expected
|
||||||
|
|
||||||
|
# timeout
|
||||||
|
with pytest.raises(Exception, match="merge insert timed out"):
|
||||||
|
table.merge_insert("a").when_matched_update_all().execute(
|
||||||
|
new_data, timeout=timedelta(0)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
# We vary the data format because there are slight differences in how
|
# We vary the data format because there are slight differences in how
|
||||||
# subschemas are handled in different formats
|
# subschemas are handled in different formats
|
||||||
@@ -1478,11 +1547,13 @@ def test_restore_consistency(tmp_path):
|
|||||||
def test_add_columns(mem_db: DBConnection):
|
def test_add_columns(mem_db: DBConnection):
|
||||||
data = pa.table({"id": [0, 1]})
|
data = pa.table({"id": [0, 1]})
|
||||||
table = LanceTable.create(mem_db, "my_table", data=data)
|
table = LanceTable.create(mem_db, "my_table", data=data)
|
||||||
table.add_columns({"new_col": "id + 2"})
|
add_columns_res = table.add_columns({"new_col": "id + 2"})
|
||||||
|
assert add_columns_res.version == 2
|
||||||
assert table.to_arrow().column_names == ["id", "new_col"]
|
assert table.to_arrow().column_names == ["id", "new_col"]
|
||||||
assert table.to_arrow()["new_col"].to_pylist() == [2, 3]
|
assert table.to_arrow()["new_col"].to_pylist() == [2, 3]
|
||||||
|
|
||||||
table.add_columns({"null_int": "cast(null as bigint)"})
|
add_columns_res = table.add_columns({"null_int": "cast(null as bigint)"})
|
||||||
|
assert add_columns_res.version == 3
|
||||||
assert table.schema.field("null_int").type == pa.int64()
|
assert table.schema.field("null_int").type == pa.int64()
|
||||||
|
|
||||||
|
|
||||||
@@ -1490,7 +1561,8 @@ def test_add_columns(mem_db: DBConnection):
|
|||||||
async def test_add_columns_async(mem_db_async: AsyncConnection):
|
async def test_add_columns_async(mem_db_async: AsyncConnection):
|
||||||
data = pa.table({"id": [0, 1]})
|
data = pa.table({"id": [0, 1]})
|
||||||
table = await mem_db_async.create_table("my_table", data=data)
|
table = await mem_db_async.create_table("my_table", data=data)
|
||||||
await table.add_columns({"new_col": "id + 2"})
|
add_columns_res = await table.add_columns({"new_col": "id + 2"})
|
||||||
|
assert add_columns_res.version == 2
|
||||||
data = await table.to_arrow()
|
data = await table.to_arrow()
|
||||||
assert data.column_names == ["id", "new_col"]
|
assert data.column_names == ["id", "new_col"]
|
||||||
assert data["new_col"].to_pylist() == [2, 3]
|
assert data["new_col"].to_pylist() == [2, 3]
|
||||||
@@ -1500,9 +1572,10 @@ async def test_add_columns_async(mem_db_async: AsyncConnection):
|
|||||||
async def test_add_columns_with_schema(mem_db_async: AsyncConnection):
|
async def test_add_columns_with_schema(mem_db_async: AsyncConnection):
|
||||||
data = pa.table({"id": [0, 1]})
|
data = pa.table({"id": [0, 1]})
|
||||||
table = await mem_db_async.create_table("my_table", data=data)
|
table = await mem_db_async.create_table("my_table", data=data)
|
||||||
await table.add_columns(
|
add_columns_res = await table.add_columns(
|
||||||
[pa.field("x", pa.int64()), pa.field("vector", pa.list_(pa.float32(), 8))]
|
[pa.field("x", pa.int64()), pa.field("vector", pa.list_(pa.float32(), 8))]
|
||||||
)
|
)
|
||||||
|
assert add_columns_res.version == 2
|
||||||
|
|
||||||
assert await table.schema() == pa.schema(
|
assert await table.schema() == pa.schema(
|
||||||
[
|
[
|
||||||
@@ -1513,11 +1586,12 @@ async def test_add_columns_with_schema(mem_db_async: AsyncConnection):
|
|||||||
)
|
)
|
||||||
|
|
||||||
table = await mem_db_async.create_table("table2", data=data)
|
table = await mem_db_async.create_table("table2", data=data)
|
||||||
await table.add_columns(
|
add_columns_res = await table.add_columns(
|
||||||
pa.schema(
|
pa.schema(
|
||||||
[pa.field("y", pa.int64()), pa.field("emb", pa.list_(pa.float32(), 8))]
|
[pa.field("y", pa.int64()), pa.field("emb", pa.list_(pa.float32(), 8))]
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
assert add_columns_res.version == 2
|
||||||
assert await table.schema() == pa.schema(
|
assert await table.schema() == pa.schema(
|
||||||
[
|
[
|
||||||
pa.field("id", pa.int64()),
|
pa.field("id", pa.int64()),
|
||||||
@@ -1530,7 +1604,8 @@ async def test_add_columns_with_schema(mem_db_async: AsyncConnection):
|
|||||||
def test_alter_columns(mem_db: DBConnection):
|
def test_alter_columns(mem_db: DBConnection):
|
||||||
data = pa.table({"id": [0, 1]})
|
data = pa.table({"id": [0, 1]})
|
||||||
table = mem_db.create_table("my_table", data=data)
|
table = mem_db.create_table("my_table", data=data)
|
||||||
table.alter_columns({"path": "id", "rename": "new_id"})
|
alter_columns_res = table.alter_columns({"path": "id", "rename": "new_id"})
|
||||||
|
assert alter_columns_res.version == 2
|
||||||
assert table.to_arrow().column_names == ["new_id"]
|
assert table.to_arrow().column_names == ["new_id"]
|
||||||
|
|
||||||
|
|
||||||
@@ -1538,9 +1613,13 @@ def test_alter_columns(mem_db: DBConnection):
|
|||||||
async def test_alter_columns_async(mem_db_async: AsyncConnection):
|
async def test_alter_columns_async(mem_db_async: AsyncConnection):
|
||||||
data = pa.table({"id": [0, 1]})
|
data = pa.table({"id": [0, 1]})
|
||||||
table = await mem_db_async.create_table("my_table", data=data)
|
table = await mem_db_async.create_table("my_table", data=data)
|
||||||
await table.alter_columns({"path": "id", "rename": "new_id"})
|
alter_columns_res = await table.alter_columns({"path": "id", "rename": "new_id"})
|
||||||
|
assert alter_columns_res.version == 2
|
||||||
assert (await table.to_arrow()).column_names == ["new_id"]
|
assert (await table.to_arrow()).column_names == ["new_id"]
|
||||||
await table.alter_columns(dict(path="new_id", data_type=pa.int16(), nullable=True))
|
alter_columns_res = await table.alter_columns(
|
||||||
|
dict(path="new_id", data_type=pa.int16(), nullable=True)
|
||||||
|
)
|
||||||
|
assert alter_columns_res.version == 3
|
||||||
data = await table.to_arrow()
|
data = await table.to_arrow()
|
||||||
assert data.column(0).type == pa.int16()
|
assert data.column(0).type == pa.int16()
|
||||||
assert data.schema.field(0).nullable
|
assert data.schema.field(0).nullable
|
||||||
@@ -1549,7 +1628,8 @@ async def test_alter_columns_async(mem_db_async: AsyncConnection):
|
|||||||
def test_drop_columns(mem_db: DBConnection):
|
def test_drop_columns(mem_db: DBConnection):
|
||||||
data = pa.table({"id": [0, 1], "category": ["a", "b"]})
|
data = pa.table({"id": [0, 1], "category": ["a", "b"]})
|
||||||
table = mem_db.create_table("my_table", data=data)
|
table = mem_db.create_table("my_table", data=data)
|
||||||
table.drop_columns(["category"])
|
drop_columns_res = table.drop_columns(["category"])
|
||||||
|
assert drop_columns_res.version == 2
|
||||||
assert table.to_arrow().column_names == ["id"]
|
assert table.to_arrow().column_names == ["id"]
|
||||||
|
|
||||||
|
|
||||||
@@ -1557,7 +1637,8 @@ def test_drop_columns(mem_db: DBConnection):
|
|||||||
async def test_drop_columns_async(mem_db_async: AsyncConnection):
|
async def test_drop_columns_async(mem_db_async: AsyncConnection):
|
||||||
data = pa.table({"id": [0, 1], "category": ["a", "b"]})
|
data = pa.table({"id": [0, 1], "category": ["a", "b"]})
|
||||||
table = await mem_db_async.create_table("my_table", data=data)
|
table = await mem_db_async.create_table("my_table", data=data)
|
||||||
await table.drop_columns(["category"])
|
drop_columns_res = await table.drop_columns(["category"])
|
||||||
|
assert drop_columns_res.version == 2
|
||||||
assert (await table.to_arrow()).column_names == ["id"]
|
assert (await table.to_arrow()).column_names == ["id"]
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
|
|
||||||
use lancedb::index::vector::IvfFlatIndexBuilder;
|
use lancedb::index::vector::IvfFlatIndexBuilder;
|
||||||
use lancedb::index::{
|
use lancedb::index::{
|
||||||
scalar::{BTreeIndexBuilder, FtsIndexBuilder, TokenizerConfig},
|
scalar::{BTreeIndexBuilder, FtsIndexBuilder},
|
||||||
vector::{IvfHnswPqIndexBuilder, IvfHnswSqIndexBuilder, IvfPqIndexBuilder},
|
vector::{IvfHnswPqIndexBuilder, IvfHnswSqIndexBuilder, IvfPqIndexBuilder},
|
||||||
Index as LanceDbIndex,
|
Index as LanceDbIndex,
|
||||||
};
|
};
|
||||||
@@ -38,19 +38,17 @@ pub fn extract_index_params(source: &Option<Bound<'_, PyAny>>) -> PyResult<Lance
|
|||||||
"LabelList" => Ok(LanceDbIndex::LabelList(Default::default())),
|
"LabelList" => Ok(LanceDbIndex::LabelList(Default::default())),
|
||||||
"FTS" => {
|
"FTS" => {
|
||||||
let params = source.extract::<FtsParams>()?;
|
let params = source.extract::<FtsParams>()?;
|
||||||
let inner_opts = TokenizerConfig::default()
|
let inner_opts = FtsIndexBuilder::default()
|
||||||
.base_tokenizer(params.base_tokenizer)
|
.base_tokenizer(params.base_tokenizer)
|
||||||
.language(¶ms.language)
|
.language(¶ms.language)
|
||||||
.map_err(|_| PyValueError::new_err(format!("LanceDB does not support the requested language: '{}'", params.language)))?
|
.map_err(|_| PyValueError::new_err(format!("LanceDB does not support the requested language: '{}'", params.language)))?
|
||||||
|
.with_position(params.with_position)
|
||||||
.lower_case(params.lower_case)
|
.lower_case(params.lower_case)
|
||||||
.max_token_length(params.max_token_length)
|
.max_token_length(params.max_token_length)
|
||||||
.remove_stop_words(params.remove_stop_words)
|
.remove_stop_words(params.remove_stop_words)
|
||||||
.stem(params.stem)
|
.stem(params.stem)
|
||||||
.ascii_folding(params.ascii_folding);
|
.ascii_folding(params.ascii_folding);
|
||||||
let mut opts = FtsIndexBuilder::default()
|
Ok(LanceDbIndex::FTS(inner_opts))
|
||||||
.with_position(params.with_position);
|
|
||||||
opts.tokenizer_configs = inner_opts;
|
|
||||||
Ok(LanceDbIndex::FTS(opts))
|
|
||||||
},
|
},
|
||||||
"IvfFlat" => {
|
"IvfFlat" => {
|
||||||
let params = source.extract::<IvfFlatParams>()?;
|
let params = source.extract::<IvfFlatParams>()?;
|
||||||
|
|||||||
@@ -11,7 +11,10 @@ use pyo3::{
|
|||||||
wrap_pyfunction, Bound, PyResult, Python,
|
wrap_pyfunction, Bound, PyResult, Python,
|
||||||
};
|
};
|
||||||
use query::{FTSQuery, HybridQuery, Query, VectorQuery};
|
use query::{FTSQuery, HybridQuery, Query, VectorQuery};
|
||||||
use table::Table;
|
use table::{
|
||||||
|
AddColumnsResult, AddResult, AlterColumnsResult, DeleteResult, DropColumnsResult, MergeResult,
|
||||||
|
Table, UpdateResult,
|
||||||
|
};
|
||||||
|
|
||||||
pub mod arrow;
|
pub mod arrow;
|
||||||
pub mod connection;
|
pub mod connection;
|
||||||
@@ -35,6 +38,13 @@ pub fn _lancedb(_py: Python, m: &Bound<'_, PyModule>) -> PyResult<()> {
|
|||||||
m.add_class::<HybridQuery>()?;
|
m.add_class::<HybridQuery>()?;
|
||||||
m.add_class::<VectorQuery>()?;
|
m.add_class::<VectorQuery>()?;
|
||||||
m.add_class::<RecordBatchStream>()?;
|
m.add_class::<RecordBatchStream>()?;
|
||||||
|
m.add_class::<AddColumnsResult>()?;
|
||||||
|
m.add_class::<AlterColumnsResult>()?;
|
||||||
|
m.add_class::<AddResult>()?;
|
||||||
|
m.add_class::<MergeResult>()?;
|
||||||
|
m.add_class::<DeleteResult>()?;
|
||||||
|
m.add_class::<DropColumnsResult>()?;
|
||||||
|
m.add_class::<UpdateResult>()?;
|
||||||
m.add_function(wrap_pyfunction!(connect, m)?)?;
|
m.add_function(wrap_pyfunction!(connect, m)?)?;
|
||||||
m.add_function(wrap_pyfunction!(util::validate_table_name, m)?)?;
|
m.add_function(wrap_pyfunction!(util::validate_table_name, m)?)?;
|
||||||
m.add("__version__", env!("CARGO_PKG_VERSION"))?;
|
m.add("__version__", env!("CARGO_PKG_VERSION"))?;
|
||||||
|
|||||||
@@ -9,15 +9,16 @@ use arrow::array::Array;
|
|||||||
use arrow::array::ArrayData;
|
use arrow::array::ArrayData;
|
||||||
use arrow::pyarrow::FromPyArrow;
|
use arrow::pyarrow::FromPyArrow;
|
||||||
use arrow::pyarrow::IntoPyArrow;
|
use arrow::pyarrow::IntoPyArrow;
|
||||||
use lancedb::index::scalar::{FtsQuery, FullTextSearchQuery, MatchQuery, PhraseQuery};
|
use lancedb::index::scalar::{
|
||||||
|
BooleanQuery, BoostQuery, FtsQuery, FullTextSearchQuery, MatchQuery, MultiMatchQuery, Occur,
|
||||||
|
Operator, PhraseQuery,
|
||||||
|
};
|
||||||
use lancedb::query::QueryExecutionOptions;
|
use lancedb::query::QueryExecutionOptions;
|
||||||
use lancedb::query::QueryFilter;
|
use lancedb::query::QueryFilter;
|
||||||
use lancedb::query::{
|
use lancedb::query::{
|
||||||
ExecutableQuery, Query as LanceDbQuery, QueryBase, Select, VectorQuery as LanceDbVectorQuery,
|
ExecutableQuery, Query as LanceDbQuery, QueryBase, Select, VectorQuery as LanceDbVectorQuery,
|
||||||
};
|
};
|
||||||
use lancedb::table::AnyQuery;
|
use lancedb::table::AnyQuery;
|
||||||
use pyo3::exceptions::PyRuntimeError;
|
|
||||||
use pyo3::exceptions::{PyNotImplementedError, PyValueError};
|
|
||||||
use pyo3::prelude::{PyAnyMethods, PyDictMethods};
|
use pyo3::prelude::{PyAnyMethods, PyDictMethods};
|
||||||
use pyo3::pymethods;
|
use pyo3::pymethods;
|
||||||
use pyo3::types::PyList;
|
use pyo3::types::PyList;
|
||||||
@@ -27,30 +28,173 @@ use pyo3::IntoPyObject;
|
|||||||
use pyo3::PyAny;
|
use pyo3::PyAny;
|
||||||
use pyo3::PyRef;
|
use pyo3::PyRef;
|
||||||
use pyo3::PyResult;
|
use pyo3::PyResult;
|
||||||
|
use pyo3::{exceptions::PyRuntimeError, FromPyObject};
|
||||||
|
use pyo3::{
|
||||||
|
exceptions::{PyNotImplementedError, PyValueError},
|
||||||
|
intern,
|
||||||
|
};
|
||||||
use pyo3::{pyclass, PyErr};
|
use pyo3::{pyclass, PyErr};
|
||||||
use pyo3_async_runtimes::tokio::future_into_py;
|
use pyo3_async_runtimes::tokio::future_into_py;
|
||||||
|
|
||||||
use crate::arrow::RecordBatchStream;
|
use crate::util::parse_distance_type;
|
||||||
use crate::error::PythonErrorExt;
|
use crate::{arrow::RecordBatchStream, util::PyLanceDB};
|
||||||
use crate::util::{parse_distance_type, parse_fts_query};
|
use crate::{error::PythonErrorExt, index::class_name};
|
||||||
|
|
||||||
// Python representation of full text search parameters
|
impl FromPyObject<'_> for PyLanceDB<FtsQuery> {
|
||||||
#[derive(Clone)]
|
fn extract_bound(ob: &Bound<'_, PyAny>) -> PyResult<Self> {
|
||||||
#[pyclass(get_all)]
|
match class_name(ob)?.as_str() {
|
||||||
pub struct PyFullTextSearchQuery {
|
"MatchQuery" => {
|
||||||
pub columns: Vec<String>,
|
let query = ob.getattr("query")?.extract()?;
|
||||||
pub query: String,
|
let column = ob.getattr("column")?.extract()?;
|
||||||
pub limit: Option<i64>,
|
let boost = ob.getattr("boost")?.extract()?;
|
||||||
pub wand_factor: Option<f32>,
|
let fuzziness = ob.getattr("fuzziness")?.extract()?;
|
||||||
|
let max_expansions = ob.getattr("max_expansions")?.extract()?;
|
||||||
|
let operator = ob.getattr("operator")?.extract::<String>()?;
|
||||||
|
let prefix_length = ob.getattr("prefix_length")?.extract()?;
|
||||||
|
|
||||||
|
Ok(PyLanceDB(
|
||||||
|
MatchQuery::new(query)
|
||||||
|
.with_column(Some(column))
|
||||||
|
.with_boost(boost)
|
||||||
|
.with_fuzziness(fuzziness)
|
||||||
|
.with_max_expansions(max_expansions)
|
||||||
|
.with_operator(Operator::try_from(operator.as_str()).map_err(|e| {
|
||||||
|
PyValueError::new_err(format!("Invalid operator: {}", e))
|
||||||
|
})?)
|
||||||
|
.with_prefix_length(prefix_length)
|
||||||
|
.into(),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
"PhraseQuery" => {
|
||||||
|
let query = ob.getattr("query")?.extract()?;
|
||||||
|
let column = ob.getattr("column")?.extract()?;
|
||||||
|
let slop = ob.getattr("slop")?.extract()?;
|
||||||
|
|
||||||
|
Ok(PyLanceDB(
|
||||||
|
PhraseQuery::new(query)
|
||||||
|
.with_column(Some(column))
|
||||||
|
.with_slop(slop)
|
||||||
|
.into(),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
"BoostQuery" => {
|
||||||
|
let positive: PyLanceDB<FtsQuery> = ob.getattr("positive")?.extract()?;
|
||||||
|
let negative: PyLanceDB<FtsQuery> = ob.getattr("negative")?.extract()?;
|
||||||
|
let negative_boost = ob.getattr("negative_boost")?.extract()?;
|
||||||
|
Ok(PyLanceDB(
|
||||||
|
BoostQuery::new(positive.0, negative.0, negative_boost).into(),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
"MultiMatchQuery" => {
|
||||||
|
let query = ob.getattr("query")?.extract()?;
|
||||||
|
let columns = ob.getattr("columns")?.extract()?;
|
||||||
|
let boosts: Option<Vec<f32>> = ob.getattr("boosts")?.extract()?;
|
||||||
|
let operator: String = ob.getattr("operator")?.extract()?;
|
||||||
|
|
||||||
|
let q = MultiMatchQuery::try_new(query, columns)
|
||||||
|
.map_err(|e| PyValueError::new_err(format!("Invalid query: {}", e)))?;
|
||||||
|
let q = if let Some(boosts) = boosts {
|
||||||
|
q.try_with_boosts(boosts)
|
||||||
|
.map_err(|e| PyValueError::new_err(format!("Invalid boosts: {}", e)))?
|
||||||
|
} else {
|
||||||
|
q
|
||||||
|
};
|
||||||
|
|
||||||
|
let op = Operator::try_from(operator.as_str())
|
||||||
|
.map_err(|e| PyValueError::new_err(format!("Invalid operator: {}", e)))?;
|
||||||
|
|
||||||
|
Ok(PyLanceDB(q.with_operator(op).into()))
|
||||||
|
}
|
||||||
|
"BooleanQuery" => {
|
||||||
|
let queries: Vec<(String, PyLanceDB<FtsQuery>)> =
|
||||||
|
ob.getattr("queries")?.extract()?;
|
||||||
|
let mut sub_queries = Vec::with_capacity(queries.len());
|
||||||
|
for (occur, q) in queries {
|
||||||
|
let occur = Occur::try_from(occur.as_str())
|
||||||
|
.map_err(|e| PyValueError::new_err(e.to_string()))?;
|
||||||
|
sub_queries.push((occur, q.0));
|
||||||
|
}
|
||||||
|
Ok(PyLanceDB(BooleanQuery::new(sub_queries).into()))
|
||||||
|
}
|
||||||
|
name => Err(PyValueError::new_err(format!(
|
||||||
|
"Unsupported FTS query type: {}",
|
||||||
|
name
|
||||||
|
))),
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<FullTextSearchQuery> for PyFullTextSearchQuery {
|
impl<'py> IntoPyObject<'py> for PyLanceDB<FtsQuery> {
|
||||||
fn from(query: FullTextSearchQuery) -> Self {
|
type Target = PyAny;
|
||||||
Self {
|
type Output = Bound<'py, Self::Target>;
|
||||||
columns: query.columns().into_iter().collect(),
|
type Error = PyErr;
|
||||||
query: query.query.query().to_owned(),
|
|
||||||
limit: query.limit,
|
fn into_pyobject(self, py: pyo3::Python<'py>) -> PyResult<Self::Output> {
|
||||||
wand_factor: query.wand_factor,
|
let namespace = py
|
||||||
|
.import(intern!(py, "lancedb"))
|
||||||
|
.and_then(|m| m.getattr(intern!(py, "query")))
|
||||||
|
.expect("Failed to import namespace");
|
||||||
|
|
||||||
|
match self.0 {
|
||||||
|
FtsQuery::Match(query) => {
|
||||||
|
let kwargs = PyDict::new(py);
|
||||||
|
kwargs.set_item("boost", query.boost)?;
|
||||||
|
kwargs.set_item("fuzziness", query.fuzziness)?;
|
||||||
|
kwargs.set_item("max_expansions", query.max_expansions)?;
|
||||||
|
kwargs.set_item::<_, &str>("operator", query.operator.into())?;
|
||||||
|
kwargs.set_item("prefix_length", query.prefix_length)?;
|
||||||
|
namespace
|
||||||
|
.getattr(intern!(py, "MatchQuery"))?
|
||||||
|
.call((query.terms, query.column.unwrap()), Some(&kwargs))
|
||||||
|
}
|
||||||
|
FtsQuery::Phrase(query) => {
|
||||||
|
let kwargs = PyDict::new(py);
|
||||||
|
kwargs.set_item("slop", query.slop)?;
|
||||||
|
namespace
|
||||||
|
.getattr(intern!(py, "PhraseQuery"))?
|
||||||
|
.call((query.terms, query.column.unwrap()), Some(&kwargs))
|
||||||
|
}
|
||||||
|
FtsQuery::Boost(query) => {
|
||||||
|
let positive = PyLanceDB(query.positive.as_ref().clone()).into_pyobject(py)?;
|
||||||
|
let negative = PyLanceDB(query.negative.as_ref().clone()).into_pyobject(py)?;
|
||||||
|
let kwargs = PyDict::new(py);
|
||||||
|
kwargs.set_item("negative_boost", query.negative_boost)?;
|
||||||
|
namespace
|
||||||
|
.getattr(intern!(py, "BoostQuery"))?
|
||||||
|
.call((positive, negative), Some(&kwargs))
|
||||||
|
}
|
||||||
|
FtsQuery::MultiMatch(query) => {
|
||||||
|
let first = &query.match_queries[0];
|
||||||
|
let (columns, boosts): (Vec<_>, Vec<_>) = query
|
||||||
|
.match_queries
|
||||||
|
.iter()
|
||||||
|
.map(|q| (q.column.as_ref().unwrap().clone(), q.boost))
|
||||||
|
.unzip();
|
||||||
|
let kwargs = PyDict::new(py);
|
||||||
|
kwargs.set_item("boosts", boosts)?;
|
||||||
|
kwargs.set_item::<_, &str>("operator", first.operator.into())?;
|
||||||
|
namespace
|
||||||
|
.getattr(intern!(py, "MultiMatchQuery"))?
|
||||||
|
.call((first.terms.clone(), columns), Some(&kwargs))
|
||||||
|
}
|
||||||
|
FtsQuery::Boolean(query) => {
|
||||||
|
let mut queries: Vec<(&str, Bound<'py, PyAny>)> = Vec::with_capacity(
|
||||||
|
query.should.len() + query.must.len() + query.must_not.len(),
|
||||||
|
);
|
||||||
|
for q in query.should {
|
||||||
|
queries.push((Occur::Should.into(), PyLanceDB(q).into_pyobject(py)?));
|
||||||
|
}
|
||||||
|
for q in query.must {
|
||||||
|
queries.push((Occur::Must.into(), PyLanceDB(q).into_pyobject(py)?));
|
||||||
|
}
|
||||||
|
for q in query.must_not {
|
||||||
|
queries.push((Occur::MustNot.into(), PyLanceDB(q).into_pyobject(py)?));
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace
|
||||||
|
.getattr(intern!(py, "BooleanQuery"))?
|
||||||
|
.call1((queries,))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -80,13 +224,16 @@ pub struct PyQueryRequest {
|
|||||||
pub limit: Option<usize>,
|
pub limit: Option<usize>,
|
||||||
pub offset: Option<usize>,
|
pub offset: Option<usize>,
|
||||||
pub filter: Option<PyQueryFilter>,
|
pub filter: Option<PyQueryFilter>,
|
||||||
pub full_text_search: Option<PyFullTextSearchQuery>,
|
pub full_text_search: Option<PyLanceDB<FtsQuery>>,
|
||||||
pub select: PySelect,
|
pub select: PySelect,
|
||||||
pub fast_search: Option<bool>,
|
pub fast_search: Option<bool>,
|
||||||
pub with_row_id: Option<bool>,
|
pub with_row_id: Option<bool>,
|
||||||
pub column: Option<String>,
|
pub column: Option<String>,
|
||||||
pub query_vector: Option<PyQueryVectors>,
|
pub query_vector: Option<PyQueryVectors>,
|
||||||
pub nprobes: Option<usize>,
|
pub minimum_nprobes: Option<usize>,
|
||||||
|
// None means user did not set it and default shoud be used (currenty 20)
|
||||||
|
// Some(0) means user set it to None and there is no limit
|
||||||
|
pub maximum_nprobes: Option<usize>,
|
||||||
pub lower_bound: Option<f32>,
|
pub lower_bound: Option<f32>,
|
||||||
pub upper_bound: Option<f32>,
|
pub upper_bound: Option<f32>,
|
||||||
pub ef: Option<usize>,
|
pub ef: Option<usize>,
|
||||||
@@ -106,13 +253,14 @@ impl From<AnyQuery> for PyQueryRequest {
|
|||||||
filter: query_request.filter.map(PyQueryFilter),
|
filter: query_request.filter.map(PyQueryFilter),
|
||||||
full_text_search: query_request
|
full_text_search: query_request
|
||||||
.full_text_search
|
.full_text_search
|
||||||
.map(PyFullTextSearchQuery::from),
|
.map(|fts| PyLanceDB(fts.query)),
|
||||||
select: PySelect(query_request.select),
|
select: PySelect(query_request.select),
|
||||||
fast_search: Some(query_request.fast_search),
|
fast_search: Some(query_request.fast_search),
|
||||||
with_row_id: Some(query_request.with_row_id),
|
with_row_id: Some(query_request.with_row_id),
|
||||||
column: None,
|
column: None,
|
||||||
query_vector: None,
|
query_vector: None,
|
||||||
nprobes: None,
|
minimum_nprobes: None,
|
||||||
|
maximum_nprobes: None,
|
||||||
lower_bound: None,
|
lower_bound: None,
|
||||||
upper_bound: None,
|
upper_bound: None,
|
||||||
ef: None,
|
ef: None,
|
||||||
@@ -132,7 +280,11 @@ impl From<AnyQuery> for PyQueryRequest {
|
|||||||
with_row_id: Some(vector_query.base.with_row_id),
|
with_row_id: Some(vector_query.base.with_row_id),
|
||||||
column: vector_query.column,
|
column: vector_query.column,
|
||||||
query_vector: Some(PyQueryVectors(vector_query.query_vector)),
|
query_vector: Some(PyQueryVectors(vector_query.query_vector)),
|
||||||
nprobes: Some(vector_query.nprobes),
|
minimum_nprobes: Some(vector_query.minimum_nprobes),
|
||||||
|
maximum_nprobes: match vector_query.maximum_nprobes {
|
||||||
|
None => Some(0),
|
||||||
|
Some(value) => Some(value),
|
||||||
|
},
|
||||||
lower_bound: vector_query.lower_bound,
|
lower_bound: vector_query.lower_bound,
|
||||||
upper_bound: vector_query.upper_bound,
|
upper_bound: vector_query.upper_bound,
|
||||||
ef: vector_query.ef,
|
ef: vector_query.ef,
|
||||||
@@ -269,8 +421,8 @@ impl Query {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
let mut query = FullTextSearchQuery::new_query(query);
|
let mut query = FullTextSearchQuery::new_query(query);
|
||||||
if let Some(cols) = columns {
|
match columns {
|
||||||
if !cols.is_empty() {
|
Some(cols) if !cols.is_empty() => {
|
||||||
query = query.with_columns(&cols).map_err(|e| {
|
query = query.with_columns(&cols).map_err(|e| {
|
||||||
PyValueError::new_err(format!(
|
PyValueError::new_err(format!(
|
||||||
"Failed to set full text search columns: {}",
|
"Failed to set full text search columns: {}",
|
||||||
@@ -278,15 +430,12 @@ impl Query {
|
|||||||
))
|
))
|
||||||
})?;
|
})?;
|
||||||
}
|
}
|
||||||
|
_ => {}
|
||||||
}
|
}
|
||||||
query
|
query
|
||||||
} else if let Ok(query) = fts_query.downcast::<PyDict>() {
|
|
||||||
let query = parse_fts_query(query)?;
|
|
||||||
FullTextSearchQuery::new_query(query)
|
|
||||||
} else {
|
} else {
|
||||||
return Err(PyValueError::new_err(
|
let query = fts_query.extract::<PyLanceDB<FtsQuery>>()?;
|
||||||
"query must be a string or a Query object",
|
FullTextSearchQuery::new_query(query.0)
|
||||||
));
|
|
||||||
};
|
};
|
||||||
|
|
||||||
Ok(FTSQuery {
|
Ok(FTSQuery {
|
||||||
@@ -509,6 +658,29 @@ impl VectorQuery {
|
|||||||
self.inner = self.inner.clone().nprobes(nprobe as usize);
|
self.inner = self.inner.clone().nprobes(nprobe as usize);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn minimum_nprobes(&mut self, minimum_nprobes: u32) -> PyResult<()> {
|
||||||
|
self.inner = self
|
||||||
|
.inner
|
||||||
|
.clone()
|
||||||
|
.minimum_nprobes(minimum_nprobes as usize)
|
||||||
|
.infer_error()?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn maximum_nprobes(&mut self, maximum_nprobes: u32) -> PyResult<()> {
|
||||||
|
let maximum_nprobes = if maximum_nprobes == 0 {
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
Some(maximum_nprobes as usize)
|
||||||
|
};
|
||||||
|
self.inner = self
|
||||||
|
.inner
|
||||||
|
.clone()
|
||||||
|
.maximum_nprobes(maximum_nprobes)
|
||||||
|
.infer_error()?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
#[pyo3(signature = (lower_bound=None, upper_bound=None))]
|
#[pyo3(signature = (lower_bound=None, upper_bound=None))]
|
||||||
pub fn distance_range(&mut self, lower_bound: Option<f32>, upper_bound: Option<f32>) {
|
pub fn distance_range(&mut self, lower_bound: Option<f32>, upper_bound: Option<f32>) {
|
||||||
self.inner = self.inner.clone().distance_range(lower_bound, upper_bound);
|
self.inner = self.inner.clone().distance_range(lower_bound, upper_bound);
|
||||||
|
|||||||
@@ -17,10 +17,10 @@ use lancedb::table::{
|
|||||||
Table as LanceDbTable,
|
Table as LanceDbTable,
|
||||||
};
|
};
|
||||||
use pyo3::{
|
use pyo3::{
|
||||||
exceptions::{PyIOError, PyKeyError, PyRuntimeError, PyValueError},
|
exceptions::{PyKeyError, PyRuntimeError, PyValueError},
|
||||||
pyclass, pymethods,
|
pyclass, pymethods,
|
||||||
types::{IntoPyDict, PyAnyMethods, PyDict, PyDictMethods, PyInt, PyString},
|
types::{IntoPyDict, PyAnyMethods, PyDict, PyDictMethods},
|
||||||
Bound, FromPyObject, PyAny, PyObject, PyRef, PyResult, Python,
|
Bound, FromPyObject, PyAny, PyRef, PyResult, Python,
|
||||||
};
|
};
|
||||||
use pyo3_async_runtimes::tokio::future_into_py;
|
use pyo3_async_runtimes::tokio::future_into_py;
|
||||||
|
|
||||||
@@ -58,6 +58,170 @@ pub struct OptimizeStats {
|
|||||||
pub prune: RemovalStats,
|
pub prune: RemovalStats,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[pyclass(get_all)]
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
pub struct UpdateResult {
|
||||||
|
pub rows_updated: u64,
|
||||||
|
pub version: u64,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[pymethods]
|
||||||
|
impl UpdateResult {
|
||||||
|
pub fn __repr__(&self) -> String {
|
||||||
|
format!(
|
||||||
|
"UpdateResult(rows_updated={}, version={})",
|
||||||
|
self.rows_updated, self.version
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<lancedb::table::UpdateResult> for UpdateResult {
|
||||||
|
fn from(result: lancedb::table::UpdateResult) -> Self {
|
||||||
|
Self {
|
||||||
|
rows_updated: result.rows_updated,
|
||||||
|
version: result.version,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[pyclass(get_all)]
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
pub struct AddResult {
|
||||||
|
pub version: u64,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[pymethods]
|
||||||
|
impl AddResult {
|
||||||
|
pub fn __repr__(&self) -> String {
|
||||||
|
format!("AddResult(version={})", self.version)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<lancedb::table::AddResult> for AddResult {
|
||||||
|
fn from(result: lancedb::table::AddResult) -> Self {
|
||||||
|
Self {
|
||||||
|
version: result.version,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[pyclass(get_all)]
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
pub struct DeleteResult {
|
||||||
|
pub version: u64,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[pymethods]
|
||||||
|
impl DeleteResult {
|
||||||
|
pub fn __repr__(&self) -> String {
|
||||||
|
format!("DeleteResult(version={})", self.version)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<lancedb::table::DeleteResult> for DeleteResult {
|
||||||
|
fn from(result: lancedb::table::DeleteResult) -> Self {
|
||||||
|
Self {
|
||||||
|
version: result.version,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[pyclass(get_all)]
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
pub struct MergeResult {
|
||||||
|
pub version: u64,
|
||||||
|
pub num_updated_rows: u64,
|
||||||
|
pub num_inserted_rows: u64,
|
||||||
|
pub num_deleted_rows: u64,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[pymethods]
|
||||||
|
impl MergeResult {
|
||||||
|
pub fn __repr__(&self) -> String {
|
||||||
|
format!(
|
||||||
|
"MergeResult(version={}, num_updated_rows={}, num_inserted_rows={}, num_deleted_rows={})",
|
||||||
|
self.version,
|
||||||
|
self.num_updated_rows,
|
||||||
|
self.num_inserted_rows,
|
||||||
|
self.num_deleted_rows
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<lancedb::table::MergeResult> for MergeResult {
|
||||||
|
fn from(result: lancedb::table::MergeResult) -> Self {
|
||||||
|
Self {
|
||||||
|
version: result.version,
|
||||||
|
num_updated_rows: result.num_updated_rows,
|
||||||
|
num_inserted_rows: result.num_inserted_rows,
|
||||||
|
num_deleted_rows: result.num_deleted_rows,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[pyclass(get_all)]
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
pub struct AddColumnsResult {
|
||||||
|
pub version: u64,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[pymethods]
|
||||||
|
impl AddColumnsResult {
|
||||||
|
pub fn __repr__(&self) -> String {
|
||||||
|
format!("AddColumnsResult(version={})", self.version)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<lancedb::table::AddColumnsResult> for AddColumnsResult {
|
||||||
|
fn from(result: lancedb::table::AddColumnsResult) -> Self {
|
||||||
|
Self {
|
||||||
|
version: result.version,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[pyclass(get_all)]
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
pub struct AlterColumnsResult {
|
||||||
|
pub version: u64,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[pymethods]
|
||||||
|
impl AlterColumnsResult {
|
||||||
|
pub fn __repr__(&self) -> String {
|
||||||
|
format!("AlterColumnsResult(version={})", self.version)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<lancedb::table::AlterColumnsResult> for AlterColumnsResult {
|
||||||
|
fn from(result: lancedb::table::AlterColumnsResult) -> Self {
|
||||||
|
Self {
|
||||||
|
version: result.version,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[pyclass(get_all)]
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
pub struct DropColumnsResult {
|
||||||
|
pub version: u64,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[pymethods]
|
||||||
|
impl DropColumnsResult {
|
||||||
|
pub fn __repr__(&self) -> String {
|
||||||
|
format!("DropColumnsResult(version={})", self.version)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<lancedb::table::DropColumnsResult> for DropColumnsResult {
|
||||||
|
fn from(result: lancedb::table::DropColumnsResult) -> Self {
|
||||||
|
Self {
|
||||||
|
version: result.version,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[pyclass]
|
#[pyclass]
|
||||||
pub struct Table {
|
pub struct Table {
|
||||||
// We keep a copy of the name to use if the inner table is dropped
|
// We keep a copy of the name to use if the inner table is dropped
|
||||||
@@ -132,15 +296,16 @@ impl Table {
|
|||||||
}
|
}
|
||||||
|
|
||||||
future_into_py(self_.py(), async move {
|
future_into_py(self_.py(), async move {
|
||||||
op.execute().await.infer_error()?;
|
let result = op.execute().await.infer_error()?;
|
||||||
Ok(())
|
Ok(AddResult::from(result))
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn delete(self_: PyRef<'_, Self>, condition: String) -> PyResult<Bound<'_, PyAny>> {
|
pub fn delete(self_: PyRef<'_, Self>, condition: String) -> PyResult<Bound<'_, PyAny>> {
|
||||||
let inner = self_.inner_ref()?.clone();
|
let inner = self_.inner_ref()?.clone();
|
||||||
future_into_py(self_.py(), async move {
|
future_into_py(self_.py(), async move {
|
||||||
inner.delete(&condition).await.infer_error()
|
let result = inner.delete(&condition).await.infer_error()?;
|
||||||
|
Ok(DeleteResult::from(result))
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -160,8 +325,8 @@ impl Table {
|
|||||||
op = op.column(column_name, value);
|
op = op.column(column_name, value);
|
||||||
}
|
}
|
||||||
future_into_py(self_.py(), async move {
|
future_into_py(self_.py(), async move {
|
||||||
op.execute().await.infer_error()?;
|
let result = op.execute().await.infer_error()?;
|
||||||
Ok(())
|
Ok(UpdateResult::from(result))
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -355,25 +520,15 @@ impl Table {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn checkout(self_: PyRef<'_, Self>, version: PyObject) -> PyResult<Bound<'_, PyAny>> {
|
pub fn checkout(self_: PyRef<'_, Self>, version: LanceVersion) -> PyResult<Bound<'_, PyAny>> {
|
||||||
let inner = self_.inner_ref()?.clone();
|
let inner = self_.inner_ref()?.clone();
|
||||||
let py = self_.py();
|
let py = self_.py();
|
||||||
let (is_int, int_value, string_value) = if let Ok(i) = version.downcast_bound::<PyInt>(py) {
|
|
||||||
let num: u64 = i.extract()?;
|
|
||||||
(true, num, String::new())
|
|
||||||
} else if let Ok(s) = version.downcast_bound::<PyString>(py) {
|
|
||||||
let str_value = s.to_string();
|
|
||||||
(false, 0, str_value)
|
|
||||||
} else {
|
|
||||||
return Err(PyIOError::new_err(
|
|
||||||
"version must be an integer or a string.",
|
|
||||||
));
|
|
||||||
};
|
|
||||||
future_into_py(py, async move {
|
future_into_py(py, async move {
|
||||||
if is_int {
|
match version {
|
||||||
inner.checkout(int_value).await.infer_error()
|
LanceVersion::Version(version_num) => {
|
||||||
} else {
|
inner.checkout(version_num).await.infer_error()
|
||||||
inner.checkout_tag(&string_value).await.infer_error()
|
}
|
||||||
|
LanceVersion::Tag(tag) => inner.checkout_tag(&tag).await.infer_error(),
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -386,12 +541,19 @@ impl Table {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[pyo3(signature = (version=None))]
|
#[pyo3(signature = (version=None))]
|
||||||
pub fn restore(self_: PyRef<'_, Self>, version: Option<u64>) -> PyResult<Bound<'_, PyAny>> {
|
pub fn restore(
|
||||||
|
self_: PyRef<'_, Self>,
|
||||||
|
version: Option<LanceVersion>,
|
||||||
|
) -> PyResult<Bound<'_, PyAny>> {
|
||||||
let inner = self_.inner_ref()?.clone();
|
let inner = self_.inner_ref()?.clone();
|
||||||
|
let py = self_.py();
|
||||||
|
|
||||||
future_into_py(self_.py(), async move {
|
future_into_py(py, async move {
|
||||||
if let Some(version) = version {
|
if let Some(version) = version {
|
||||||
inner.checkout(version).await.infer_error()?;
|
match version {
|
||||||
|
LanceVersion::Version(num) => inner.checkout(num).await.infer_error()?,
|
||||||
|
LanceVersion::Tag(tag) => inner.checkout_tag(&tag).await.infer_error()?,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
inner.restore().await.infer_error()
|
inner.restore().await.infer_error()
|
||||||
})
|
})
|
||||||
@@ -487,16 +649,13 @@ impl Table {
|
|||||||
builder
|
builder
|
||||||
.when_not_matched_by_source_delete(parameters.when_not_matched_by_source_condition);
|
.when_not_matched_by_source_delete(parameters.when_not_matched_by_source_condition);
|
||||||
}
|
}
|
||||||
|
if let Some(timeout) = parameters.timeout {
|
||||||
|
builder.timeout(timeout);
|
||||||
|
}
|
||||||
|
|
||||||
future_into_py(self_.py(), async move {
|
future_into_py(self_.py(), async move {
|
||||||
let stats = builder.execute(Box::new(batches)).await.infer_error()?;
|
let res = builder.execute(Box::new(batches)).await.infer_error()?;
|
||||||
Python::with_gil(|py| {
|
Ok(MergeResult::from(res))
|
||||||
let dict = PyDict::new(py);
|
|
||||||
dict.set_item("num_inserted_rows", stats.num_inserted_rows)?;
|
|
||||||
dict.set_item("num_updated_rows", stats.num_updated_rows)?;
|
|
||||||
dict.set_item("num_deleted_rows", stats.num_deleted_rows)?;
|
|
||||||
Ok(dict.unbind())
|
|
||||||
})
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -532,8 +691,8 @@ impl Table {
|
|||||||
|
|
||||||
let inner = self_.inner_ref()?.clone();
|
let inner = self_.inner_ref()?.clone();
|
||||||
future_into_py(self_.py(), async move {
|
future_into_py(self_.py(), async move {
|
||||||
inner.add_columns(definitions, None).await.infer_error()?;
|
let result = inner.add_columns(definitions, None).await.infer_error()?;
|
||||||
Ok(())
|
Ok(AddColumnsResult::from(result))
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -546,8 +705,8 @@ impl Table {
|
|||||||
|
|
||||||
let inner = self_.inner_ref()?.clone();
|
let inner = self_.inner_ref()?.clone();
|
||||||
future_into_py(self_.py(), async move {
|
future_into_py(self_.py(), async move {
|
||||||
inner.add_columns(transform, None).await.infer_error()?;
|
let result = inner.add_columns(transform, None).await.infer_error()?;
|
||||||
Ok(())
|
Ok(AddColumnsResult::from(result))
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -590,8 +749,8 @@ impl Table {
|
|||||||
|
|
||||||
let inner = self_.inner_ref()?.clone();
|
let inner = self_.inner_ref()?.clone();
|
||||||
future_into_py(self_.py(), async move {
|
future_into_py(self_.py(), async move {
|
||||||
inner.alter_columns(&alterations).await.infer_error()?;
|
let result = inner.alter_columns(&alterations).await.infer_error()?;
|
||||||
Ok(())
|
Ok(AlterColumnsResult::from(result))
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -599,8 +758,8 @@ impl Table {
|
|||||||
let inner = self_.inner_ref()?.clone();
|
let inner = self_.inner_ref()?.clone();
|
||||||
future_into_py(self_.py(), async move {
|
future_into_py(self_.py(), async move {
|
||||||
let column_refs = columns.iter().map(String::as_str).collect::<Vec<&str>>();
|
let column_refs = columns.iter().map(String::as_str).collect::<Vec<&str>>();
|
||||||
inner.drop_columns(&column_refs).await.infer_error()?;
|
let result = inner.drop_columns(&column_refs).await.infer_error()?;
|
||||||
Ok(())
|
Ok(DropColumnsResult::from(result))
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -636,6 +795,12 @@ impl Table {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(FromPyObject)]
|
||||||
|
pub enum LanceVersion {
|
||||||
|
Version(u64),
|
||||||
|
Tag(String),
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(FromPyObject)]
|
#[derive(FromPyObject)]
|
||||||
#[pyo3(from_item_all)]
|
#[pyo3(from_item_all)]
|
||||||
pub struct MergeInsertParams {
|
pub struct MergeInsertParams {
|
||||||
@@ -645,6 +810,7 @@ pub struct MergeInsertParams {
|
|||||||
when_not_matched_insert_all: bool,
|
when_not_matched_insert_all: bool,
|
||||||
when_not_matched_by_source_delete: bool,
|
when_not_matched_by_source_delete: bool,
|
||||||
when_not_matched_by_source_condition: Option<String>,
|
when_not_matched_by_source_condition: Option<String>,
|
||||||
|
timeout: Option<std::time::Duration>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[pyclass]
|
#[pyclass]
|
||||||
|
|||||||
@@ -3,15 +3,11 @@
|
|||||||
|
|
||||||
use std::sync::Mutex;
|
use std::sync::Mutex;
|
||||||
|
|
||||||
use lancedb::index::scalar::{BoostQuery, FtsQuery, MatchQuery, MultiMatchQuery, PhraseQuery};
|
|
||||||
use lancedb::DistanceType;
|
use lancedb::DistanceType;
|
||||||
use pyo3::prelude::{PyAnyMethods, PyDictMethods, PyListMethods};
|
|
||||||
use pyo3::types::PyDict;
|
|
||||||
use pyo3::{
|
use pyo3::{
|
||||||
exceptions::{PyRuntimeError, PyValueError},
|
exceptions::{PyRuntimeError, PyValueError},
|
||||||
pyfunction, PyResult,
|
pyfunction, PyResult,
|
||||||
};
|
};
|
||||||
use pyo3::{Bound, PyAny};
|
|
||||||
|
|
||||||
/// A wrapper around a rust builder
|
/// A wrapper around a rust builder
|
||||||
///
|
///
|
||||||
@@ -64,116 +60,6 @@ pub fn validate_table_name(table_name: &str) -> PyResult<()> {
|
|||||||
.map_err(|e| PyValueError::new_err(e.to_string()))
|
.map_err(|e| PyValueError::new_err(e.to_string()))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn parse_fts_query(query: &Bound<'_, PyDict>) -> PyResult<FtsQuery> {
|
/// A wrapper around a LanceDB type to allow it to be used in Python
|
||||||
let query_type = query.keys().get_item(0)?.extract::<String>()?;
|
#[derive(Debug, Clone)]
|
||||||
let query_value = query
|
pub struct PyLanceDB<T>(pub T);
|
||||||
.get_item(&query_type)?
|
|
||||||
.ok_or(PyValueError::new_err(format!(
|
|
||||||
"Query type {} not found",
|
|
||||||
query_type
|
|
||||||
)))?;
|
|
||||||
let query_value = query_value.downcast::<PyDict>()?;
|
|
||||||
|
|
||||||
match query_type.as_str() {
|
|
||||||
"match" => {
|
|
||||||
let column = query_value.keys().get_item(0)?.extract::<String>()?;
|
|
||||||
let params = query_value
|
|
||||||
.get_item(&column)?
|
|
||||||
.ok_or(PyValueError::new_err(format!(
|
|
||||||
"column {} not found",
|
|
||||||
column
|
|
||||||
)))?;
|
|
||||||
let params = params.downcast::<PyDict>()?;
|
|
||||||
|
|
||||||
let query = params
|
|
||||||
.get_item("query")?
|
|
||||||
.ok_or(PyValueError::new_err("query not found"))?
|
|
||||||
.extract::<String>()?;
|
|
||||||
let boost = params
|
|
||||||
.get_item("boost")?
|
|
||||||
.ok_or(PyValueError::new_err("boost not found"))?
|
|
||||||
.extract::<f32>()?;
|
|
||||||
let fuzziness = params
|
|
||||||
.get_item("fuzziness")?
|
|
||||||
.ok_or(PyValueError::new_err("fuzziness not found"))?
|
|
||||||
.extract::<Option<u32>>()?;
|
|
||||||
let max_expansions = params
|
|
||||||
.get_item("max_expansions")?
|
|
||||||
.ok_or(PyValueError::new_err("max_expansions not found"))?
|
|
||||||
.extract::<usize>()?;
|
|
||||||
|
|
||||||
let query = MatchQuery::new(query)
|
|
||||||
.with_column(Some(column))
|
|
||||||
.with_boost(boost)
|
|
||||||
.with_fuzziness(fuzziness)
|
|
||||||
.with_max_expansions(max_expansions);
|
|
||||||
Ok(query.into())
|
|
||||||
}
|
|
||||||
|
|
||||||
"match_phrase" => {
|
|
||||||
let column = query_value.keys().get_item(0)?.extract::<String>()?;
|
|
||||||
let query = query_value
|
|
||||||
.get_item(&column)?
|
|
||||||
.ok_or(PyValueError::new_err(format!(
|
|
||||||
"column {} not found",
|
|
||||||
column
|
|
||||||
)))?
|
|
||||||
.extract::<String>()?;
|
|
||||||
|
|
||||||
let query = PhraseQuery::new(query).with_column(Some(column));
|
|
||||||
Ok(query.into())
|
|
||||||
}
|
|
||||||
|
|
||||||
"boost" => {
|
|
||||||
let positive: Bound<'_, PyAny> = query_value
|
|
||||||
.get_item("positive")?
|
|
||||||
.ok_or(PyValueError::new_err("positive not found"))?;
|
|
||||||
let positive = positive.downcast::<PyDict>()?;
|
|
||||||
|
|
||||||
let negative = query_value
|
|
||||||
.get_item("negative")?
|
|
||||||
.ok_or(PyValueError::new_err("negative not found"))?;
|
|
||||||
let negative = negative.downcast::<PyDict>()?;
|
|
||||||
|
|
||||||
let negative_boost = query_value
|
|
||||||
.get_item("negative_boost")?
|
|
||||||
.ok_or(PyValueError::new_err("negative_boost not found"))?
|
|
||||||
.extract::<f32>()?;
|
|
||||||
|
|
||||||
let positive_query = parse_fts_query(positive)?;
|
|
||||||
let negative_query = parse_fts_query(negative)?;
|
|
||||||
let query = BoostQuery::new(positive_query, negative_query, Some(negative_boost));
|
|
||||||
|
|
||||||
Ok(query.into())
|
|
||||||
}
|
|
||||||
|
|
||||||
"multi_match" => {
|
|
||||||
let query = query_value
|
|
||||||
.get_item("query")?
|
|
||||||
.ok_or(PyValueError::new_err("query not found"))?
|
|
||||||
.extract::<String>()?;
|
|
||||||
|
|
||||||
let columns = query_value
|
|
||||||
.get_item("columns")?
|
|
||||||
.ok_or(PyValueError::new_err("columns not found"))?
|
|
||||||
.extract::<Vec<String>>()?;
|
|
||||||
|
|
||||||
let boost = query_value
|
|
||||||
.get_item("boost")?
|
|
||||||
.ok_or(PyValueError::new_err("boost not found"))?
|
|
||||||
.extract::<Vec<f32>>()?;
|
|
||||||
|
|
||||||
let query = MultiMatchQuery::try_new(query, columns)
|
|
||||||
.and_then(|q| q.try_with_boosts(boost))
|
|
||||||
.map_err(|e| {
|
|
||||||
PyValueError::new_err(format!("Error creating MultiMatchQuery: {}", e))
|
|
||||||
})?;
|
|
||||||
Ok(query.into())
|
|
||||||
}
|
|
||||||
|
|
||||||
_ => Err(PyValueError::new_err(format!(
|
|
||||||
"Unsupported query type: {}",
|
|
||||||
query_type
|
|
||||||
))),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,2 +1,2 @@
|
|||||||
[toolchain]
|
[toolchain]
|
||||||
channel = "1.83.0"
|
channel = "1.86.0"
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "lancedb-node"
|
name = "lancedb-node"
|
||||||
version = "0.19.1-beta.1"
|
version = "0.21.0"
|
||||||
description = "Serverless, low-latency vector database for AI applications"
|
description = "Serverless, low-latency vector database for AI applications"
|
||||||
license.workspace = true
|
license.workspace = true
|
||||||
edition.workspace = true
|
edition.workspace = true
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "lancedb"
|
name = "lancedb"
|
||||||
version = "0.19.1-beta.1"
|
version = "0.21.0"
|
||||||
edition.workspace = true
|
edition.workspace = true
|
||||||
description = "LanceDB: A serverless, low-latency vector database for AI applications"
|
description = "LanceDB: A serverless, low-latency vector database for AI applications"
|
||||||
license.workspace = true
|
license.workspace = true
|
||||||
@@ -60,15 +60,15 @@ reqwest = { version = "0.12.0", default-features = false, features = [
|
|||||||
"macos-system-configuration",
|
"macos-system-configuration",
|
||||||
"stream",
|
"stream",
|
||||||
], optional = true }
|
], optional = true }
|
||||||
rand = { version = "0.8.3", features = ["small_rng"], optional = true }
|
rand = { version = "0.9", features = ["small_rng"], optional = true }
|
||||||
http = { version = "1", optional = true } # Matching what is in reqwest
|
http = { version = "1", optional = true } # Matching what is in reqwest
|
||||||
uuid = { version = "1.7.0", features = ["v4"], optional = true }
|
uuid = { version = "1.7.0", features = ["v4"], optional = true }
|
||||||
polars-arrow = { version = ">=0.37,<0.40.0", optional = true }
|
polars-arrow = { version = ">=0.37,<0.40.0", optional = true }
|
||||||
polars = { version = ">=0.37,<0.40.0", optional = true }
|
polars = { version = ">=0.37,<0.40.0", optional = true }
|
||||||
hf-hub = { version = "0.4.1", optional = true, default-features = false, features = ["rustls-tls", "tokio", "ureq"]}
|
hf-hub = { version = "0.4.1", optional = true, default-features = false, features = ["rustls-tls", "tokio", "ureq"]}
|
||||||
candle-core = { version = "0.6.0", optional = true }
|
candle-core = { version = "0.9.1", optional = true }
|
||||||
candle-transformers = { version = "0.6.0", optional = true }
|
candle-transformers = { version = "0.9.1", optional = true }
|
||||||
candle-nn = { version = "0.6.0", optional = true }
|
candle-nn = { version = "0.9.1", optional = true }
|
||||||
tokenizers = { version = "0.19.1", optional = true }
|
tokenizers = { version = "0.19.1", optional = true }
|
||||||
semver = { workspace = true }
|
semver = { workspace = true }
|
||||||
|
|
||||||
@@ -78,7 +78,7 @@ bytemuck_derive.workspace = true
|
|||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
tempfile = "3.5.0"
|
tempfile = "3.5.0"
|
||||||
rand = { version = "0.8.3", features = ["small_rng"] }
|
rand = { version = "0.9", features = ["small_rng"] }
|
||||||
random_word = { version = "0.4.3", features = ["en"] }
|
random_word = { version = "0.4.3", features = ["en"] }
|
||||||
uuid = { version = "1.7.0", features = ["v4"] }
|
uuid = { version = "1.7.0", features = ["v4"] }
|
||||||
walkdir = "2"
|
walkdir = "2"
|
||||||
|
|||||||
@@ -51,7 +51,7 @@ fn create_some_records() -> Result<Box<dyn RecordBatchReader + Send>> {
|
|||||||
Arc::new(Int32Array::from_iter_values(0..TOTAL as i32)),
|
Arc::new(Int32Array::from_iter_values(0..TOTAL as i32)),
|
||||||
Arc::new(StringArray::from_iter_values((0..TOTAL).map(|_| {
|
Arc::new(StringArray::from_iter_values((0..TOTAL).map(|_| {
|
||||||
(0..n_terms)
|
(0..n_terms)
|
||||||
.map(|_| words[random::<usize>() % words.len()])
|
.map(|_| words[random::<u32>() as usize % words.len()])
|
||||||
.collect::<Vec<_>>()
|
.collect::<Vec<_>>()
|
||||||
.join(" ")
|
.join(" ")
|
||||||
}))),
|
}))),
|
||||||
|
|||||||
@@ -214,7 +214,7 @@ impl SentenceTransformersEmbeddings {
|
|||||||
|
|
||||||
let embeddings = self
|
let embeddings = self
|
||||||
.model
|
.model
|
||||||
.forward(&input_ids, &token_type_ids)
|
.forward(&input_ids, &token_type_ids, None)
|
||||||
// TODO: it'd be nice to support other devices
|
// TODO: it'd be nice to support other devices
|
||||||
.and_then(|output| output.to_device(&Device::Cpu))?;
|
.and_then(|output| output.to_device(&Device::Cpu))?;
|
||||||
|
|
||||||
@@ -310,7 +310,7 @@ impl SentenceTransformersEmbeddings {
|
|||||||
let embeddings = Tensor::stack(&tokens, 0)
|
let embeddings = Tensor::stack(&tokens, 0)
|
||||||
.and_then(|tokens| {
|
.and_then(|tokens| {
|
||||||
let token_type_ids = tokens.zeros_like()?;
|
let token_type_ids = tokens.zeros_like()?;
|
||||||
self.model.forward(&tokens, &token_type_ids)
|
self.model.forward(&tokens, &token_type_ids, None)
|
||||||
})
|
})
|
||||||
// TODO: it'd be nice to support other devices
|
// TODO: it'd be nice to support other devices
|
||||||
.and_then(|tokens| tokens.to_device(&Device::Cpu))
|
.and_then(|tokens| tokens.to_device(&Device::Cpu))
|
||||||
|
|||||||
@@ -51,35 +51,7 @@ pub struct BitmapIndexBuilder {}
|
|||||||
#[derive(Debug, Clone, Default)]
|
#[derive(Debug, Clone, Default)]
|
||||||
pub struct LabelListIndexBuilder {}
|
pub struct LabelListIndexBuilder {}
|
||||||
|
|
||||||
/// Builder for a full text search index
|
|
||||||
///
|
|
||||||
/// A full text search index is an index on a string column that allows for full text search
|
|
||||||
#[derive(Debug, Clone)]
|
|
||||||
pub struct FtsIndexBuilder {
|
|
||||||
/// Whether to store the position of the tokens
|
|
||||||
/// This is used for phrase queries
|
|
||||||
pub with_position: bool,
|
|
||||||
|
|
||||||
pub tokenizer_configs: TokenizerConfig,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Default for FtsIndexBuilder {
|
|
||||||
fn default() -> Self {
|
|
||||||
Self {
|
|
||||||
with_position: true,
|
|
||||||
tokenizer_configs: TokenizerConfig::default(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl FtsIndexBuilder {
|
|
||||||
/// Set the with_position flag
|
|
||||||
pub fn with_position(mut self, with_position: bool) -> Self {
|
|
||||||
self.with_position = with_position;
|
|
||||||
self
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub use lance_index::scalar::inverted::query::*;
|
pub use lance_index::scalar::inverted::query::*;
|
||||||
pub use lance_index::scalar::inverted::TokenizerConfig;
|
|
||||||
pub use lance_index::scalar::FullTextSearchQuery;
|
pub use lance_index::scalar::FullTextSearchQuery;
|
||||||
|
pub use lance_index::scalar::InvertedIndexParams as FtsIndexBuilder;
|
||||||
|
pub use lance_index::scalar::InvertedIndexParams;
|
||||||
|
|||||||
@@ -197,16 +197,8 @@ mod test {
|
|||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn test_e2e() {
|
async fn test_e2e() {
|
||||||
let dir1 = tempfile::tempdir()
|
let dir1 = tempfile::tempdir().unwrap().keep().canonicalize().unwrap();
|
||||||
.unwrap()
|
let dir2 = tempfile::tempdir().unwrap().keep().canonicalize().unwrap();
|
||||||
.into_path()
|
|
||||||
.canonicalize()
|
|
||||||
.unwrap();
|
|
||||||
let dir2 = tempfile::tempdir()
|
|
||||||
.unwrap()
|
|
||||||
.into_path()
|
|
||||||
.canonicalize()
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
let secondary_store = LocalFileSystem::new_with_prefix(dir2.to_str().unwrap()).unwrap();
|
let secondary_store = LocalFileSystem::new_with_prefix(dir2.to_str().unwrap()).unwrap();
|
||||||
let object_store_wrapper = Arc::new(MirroringObjectStoreWrapper {
|
let object_store_wrapper = Arc::new(MirroringObjectStoreWrapper {
|
||||||
|
|||||||
@@ -796,8 +796,10 @@ pub struct VectorQueryRequest {
|
|||||||
pub column: Option<String>,
|
pub column: Option<String>,
|
||||||
/// The vector(s) to search for
|
/// The vector(s) to search for
|
||||||
pub query_vector: Vec<Arc<dyn Array>>,
|
pub query_vector: Vec<Arc<dyn Array>>,
|
||||||
/// The number of partitions to search
|
/// The minimum number of partitions to search
|
||||||
pub nprobes: usize,
|
pub minimum_nprobes: usize,
|
||||||
|
/// The maximum number of partitions to search
|
||||||
|
pub maximum_nprobes: Option<usize>,
|
||||||
/// The lower bound (inclusive) of the distance to search for.
|
/// The lower bound (inclusive) of the distance to search for.
|
||||||
pub lower_bound: Option<f32>,
|
pub lower_bound: Option<f32>,
|
||||||
/// The upper bound (exclusive) of the distance to search for.
|
/// The upper bound (exclusive) of the distance to search for.
|
||||||
@@ -819,7 +821,8 @@ impl Default for VectorQueryRequest {
|
|||||||
base: QueryRequest::default(),
|
base: QueryRequest::default(),
|
||||||
column: None,
|
column: None,
|
||||||
query_vector: Vec::new(),
|
query_vector: Vec::new(),
|
||||||
nprobes: 20,
|
minimum_nprobes: 20,
|
||||||
|
maximum_nprobes: Some(20),
|
||||||
lower_bound: None,
|
lower_bound: None,
|
||||||
upper_bound: None,
|
upper_bound: None,
|
||||||
ef: None,
|
ef: None,
|
||||||
@@ -925,11 +928,75 @@ impl VectorQuery {
|
|||||||
/// For best results we recommend tuning this parameter with a benchmark against
|
/// For best results we recommend tuning this parameter with a benchmark against
|
||||||
/// your actual data to find the smallest possible value that will still give
|
/// your actual data to find the smallest possible value that will still give
|
||||||
/// you the desired recall.
|
/// you the desired recall.
|
||||||
|
///
|
||||||
|
/// This method sets both the minimum and maximum number of partitions to search.
|
||||||
|
/// For more fine-grained control see [`VectorQuery::minimum_nprobes`] and
|
||||||
|
/// [`VectorQuery::maximum_nprobes`].
|
||||||
pub fn nprobes(mut self, nprobes: usize) -> Self {
|
pub fn nprobes(mut self, nprobes: usize) -> Self {
|
||||||
self.request.nprobes = nprobes;
|
self.request.minimum_nprobes = nprobes;
|
||||||
|
self.request.maximum_nprobes = Some(nprobes);
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Set the minimum number of partitions to search
|
||||||
|
///
|
||||||
|
/// This argument is only used when the vector column has an IVF PQ index.
|
||||||
|
/// If there is no index then this value is ignored.
|
||||||
|
///
|
||||||
|
/// See [`VectorQuery::nprobes`] for more details.
|
||||||
|
///
|
||||||
|
/// These partitions will be searched on every indexed vector query.
|
||||||
|
///
|
||||||
|
/// Will return an error if the value is not greater than 0 or if maximum_nprobes
|
||||||
|
/// has been set and is less than the minimum_nprobes.
|
||||||
|
pub fn minimum_nprobes(mut self, minimum_nprobes: usize) -> Result<Self> {
|
||||||
|
if minimum_nprobes == 0 {
|
||||||
|
return Err(Error::InvalidInput {
|
||||||
|
message: "minimum_nprobes must be greater than 0".to_string(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if let Some(maximum_nprobes) = self.request.maximum_nprobes {
|
||||||
|
if minimum_nprobes > maximum_nprobes {
|
||||||
|
return Err(Error::InvalidInput {
|
||||||
|
message: "minimum_nprobes must be less or equal to maximum_nprobes".to_string(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
self.request.minimum_nprobes = minimum_nprobes;
|
||||||
|
Ok(self)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Set the maximum number of partitions to search
|
||||||
|
///
|
||||||
|
/// This argument is only used when the vector column has an IVF PQ index.
|
||||||
|
/// If there is no index then this value is ignored.
|
||||||
|
///
|
||||||
|
/// See [`VectorQuery::nprobes`] for more details.
|
||||||
|
///
|
||||||
|
/// If this value is greater than minimum_nprobes then the excess partitions will
|
||||||
|
/// only be searched if the initial search does not return enough results.
|
||||||
|
///
|
||||||
|
/// This can be useful when there is a narrow filter to allow these queries to
|
||||||
|
/// spend more time searching and avoid potential false negatives.
|
||||||
|
///
|
||||||
|
/// Set to None to search all partitions, if needed, to satsify the limit
|
||||||
|
pub fn maximum_nprobes(mut self, maximum_nprobes: Option<usize>) -> Result<Self> {
|
||||||
|
if let Some(maximum_nprobes) = maximum_nprobes {
|
||||||
|
if maximum_nprobes == 0 {
|
||||||
|
return Err(Error::InvalidInput {
|
||||||
|
message: "maximum_nprobes must be greater than 0".to_string(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if maximum_nprobes < self.request.minimum_nprobes {
|
||||||
|
return Err(Error::InvalidInput {
|
||||||
|
message: "maximum_nprobes must be greater than minimum_nprobes".to_string(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
self.request.maximum_nprobes = maximum_nprobes;
|
||||||
|
Ok(self)
|
||||||
|
}
|
||||||
|
|
||||||
/// Set the distance range for vector search,
|
/// Set the distance range for vector search,
|
||||||
/// only rows with distances in the range [lower_bound, upper_bound) will be returned
|
/// only rows with distances in the range [lower_bound, upper_bound) will be returned
|
||||||
pub fn distance_range(mut self, lower_bound: Option<f32>, upper_bound: Option<f32>) -> Self {
|
pub fn distance_range(mut self, lower_bound: Option<f32>, upper_bound: Option<f32>) -> Self {
|
||||||
@@ -1208,7 +1275,8 @@ mod tests {
|
|||||||
);
|
);
|
||||||
assert_eq!(query.request.base.limit.unwrap(), 100);
|
assert_eq!(query.request.base.limit.unwrap(), 100);
|
||||||
assert_eq!(query.request.base.offset.unwrap(), 1);
|
assert_eq!(query.request.base.offset.unwrap(), 1);
|
||||||
assert_eq!(query.request.nprobes, 1000);
|
assert_eq!(query.request.minimum_nprobes, 1000);
|
||||||
|
assert_eq!(query.request.maximum_nprobes, Some(1000));
|
||||||
assert!(query.request.use_index);
|
assert!(query.request.use_index);
|
||||||
assert_eq!(query.request.distance_type, Some(DistanceType::Cosine));
|
assert_eq!(query.request.distance_type, Some(DistanceType::Cosine));
|
||||||
assert_eq!(query.request.refine_factor, Some(999));
|
assert_eq!(query.request.refine_factor, Some(999));
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user