mirror of
https://github.com/lancedb/lancedb.git
synced 2026-08-30 18:08:24 +00:00
Compare commits
18 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| cb6a1fafa6 | |||
| a3339b7bdd | |||
| b20cdc4f93 | |||
| e77a62e35a | |||
| a9f49c8150 | |||
| a7d9f2e99d | |||
| 7dba793629 | |||
| 87bd6694b6 | |||
| 15e75804c4 | |||
| df2b6d3dd4 | |||
| ccec91d957 | |||
| ec82e36317 | |||
| da2a1c4a2c | |||
| 8463a10ebe | |||
| 7168d64af1 | |||
| 403c33dff0 | |||
| a0001043b6 | |||
| 1bb7acb74f |
+1
-1
@@ -1,5 +1,5 @@
|
|||||||
[tool.bumpversion]
|
[tool.bumpversion]
|
||||||
current_version = "0.29.1-beta.0"
|
current_version = "0.30.0-beta.1"
|
||||||
parse = """(?x)
|
parse = """(?x)
|
||||||
(?P<major>0|[1-9]\\d*)\\.
|
(?P<major>0|[1-9]\\d*)\\.
|
||||||
(?P<minor>0|[1-9]\\d*)\\.
|
(?P<minor>0|[1-9]\\d*)\\.
|
||||||
|
|||||||
@@ -29,7 +29,3 @@ runs:
|
|||||||
args: ${{ inputs.args }}
|
args: ${{ inputs.args }}
|
||||||
docker-options: "-e PIP_EXTRA_INDEX_URL='https://pypi.fury.io/lance-format/ https://pypi.fury.io/lancedb/'"
|
docker-options: "-e PIP_EXTRA_INDEX_URL='https://pypi.fury.io/lance-format/ https://pypi.fury.io/lancedb/'"
|
||||||
working-directory: python
|
working-directory: python
|
||||||
- uses: actions/upload-artifact@v4
|
|
||||||
with:
|
|
||||||
name: windows-wheels
|
|
||||||
path: python\target\wheels
|
|
||||||
|
|||||||
@@ -8,6 +8,9 @@ on:
|
|||||||
# This should trigger a dry run (we skip the final publish step)
|
# This should trigger a dry run (we skip the final publish step)
|
||||||
paths:
|
paths:
|
||||||
- .github/workflows/pypi-publish.yml
|
- .github/workflows/pypi-publish.yml
|
||||||
|
- .github/workflows/build_linux_wheel/action.yml
|
||||||
|
- .github/workflows/build_mac_wheel/action.yml
|
||||||
|
- .github/workflows/build_windows_wheel/action.yml
|
||||||
- Cargo.toml # Change in dependency frequently breaks builds
|
- Cargo.toml # Change in dependency frequently breaks builds
|
||||||
- Cargo.lock
|
- Cargo.lock
|
||||||
|
|
||||||
@@ -21,32 +24,21 @@ jobs:
|
|||||||
linux:
|
linux:
|
||||||
name: Python ${{ matrix.config.platform }} manylinux${{ matrix.config.manylinux }}
|
name: Python ${{ matrix.config.platform }} manylinux${{ matrix.config.manylinux }}
|
||||||
timeout-minutes: 60
|
timeout-minutes: 60
|
||||||
permissions:
|
|
||||||
id-token: write
|
|
||||||
contents: read
|
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
config:
|
config:
|
||||||
- platform: x86_64
|
|
||||||
manylinux: "2_17"
|
|
||||||
extra_args: ""
|
|
||||||
runner: ubuntu-22.04
|
|
||||||
- platform: x86_64
|
- platform: x86_64
|
||||||
manylinux: "2_28"
|
manylinux: "2_28"
|
||||||
extra_args: "--features fp16kernels"
|
extra_args: "--features fp16kernels"
|
||||||
runner: ubuntu-22.04
|
runner: ubuntu-22.04
|
||||||
- platform: aarch64
|
# For successful fat LTO builds, we need a large runner to avoid OOM errors.
|
||||||
manylinux: "2_17"
|
|
||||||
extra_args: ""
|
|
||||||
# For successful fat LTO builds, we need a large runner to avoid OOM errors.
|
|
||||||
runner: ubuntu-2404-8x-arm64
|
|
||||||
- platform: aarch64
|
- platform: aarch64
|
||||||
manylinux: "2_28"
|
manylinux: "2_28"
|
||||||
extra_args: "--features fp16kernels"
|
extra_args: "--features fp16kernels"
|
||||||
runner: ubuntu-2404-8x-arm64
|
runner: ubuntu-2404-8x-arm64
|
||||||
runs-on: ${{ matrix.config.runner }}
|
runs-on: ${{ matrix.config.runner }}
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v6
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
lfs: true
|
lfs: true
|
||||||
@@ -60,15 +52,14 @@ jobs:
|
|||||||
args: "--release --strip ${{ matrix.config.extra_args }}"
|
args: "--release --strip ${{ matrix.config.extra_args }}"
|
||||||
arm-build: ${{ matrix.config.platform == 'aarch64' }}
|
arm-build: ${{ matrix.config.platform == 'aarch64' }}
|
||||||
manylinux: ${{ matrix.config.manylinux }}
|
manylinux: ${{ matrix.config.manylinux }}
|
||||||
- uses: ./.github/workflows/upload_wheel
|
- uses: actions/upload-artifact@v7
|
||||||
if: startsWith(github.ref, 'refs/tags/python-v')
|
if: startsWith(github.ref, 'refs/tags/python-v')
|
||||||
with:
|
with:
|
||||||
fury_token: ${{ secrets.FURY_TOKEN }}
|
name: wheels-linux-${{ matrix.config.platform }}-${{ matrix.config.manylinux }}
|
||||||
|
path: target/wheels/lancedb-*.whl
|
||||||
|
if-no-files-found: error
|
||||||
mac:
|
mac:
|
||||||
timeout-minutes: 90
|
timeout-minutes: 90
|
||||||
permissions:
|
|
||||||
id-token: write
|
|
||||||
contents: read
|
|
||||||
runs-on: ${{ matrix.config.runner }}
|
runs-on: ${{ matrix.config.runner }}
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
@@ -78,7 +69,7 @@ jobs:
|
|||||||
env:
|
env:
|
||||||
MACOSX_DEPLOYMENT_TARGET: 10.15
|
MACOSX_DEPLOYMENT_TARGET: 10.15
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v6
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
lfs: true
|
lfs: true
|
||||||
@@ -90,18 +81,21 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
python-minor-version: 10
|
python-minor-version: 10
|
||||||
args: "--release --strip --target ${{ matrix.config.target }} --features fp16kernels"
|
args: "--release --strip --target ${{ matrix.config.target }} --features fp16kernels"
|
||||||
- uses: ./.github/workflows/upload_wheel
|
- uses: actions/upload-artifact@v7
|
||||||
if: startsWith(github.ref, 'refs/tags/python-v')
|
if: startsWith(github.ref, 'refs/tags/python-v')
|
||||||
with:
|
with:
|
||||||
fury_token: ${{ secrets.FURY_TOKEN }}
|
name: wheels-mac-${{ matrix.config.target }}
|
||||||
|
path: target/wheels/lancedb-*.whl
|
||||||
|
if-no-files-found: error
|
||||||
windows:
|
windows:
|
||||||
timeout-minutes: 60
|
timeout-minutes: 90
|
||||||
permissions:
|
|
||||||
id-token: write
|
|
||||||
contents: read
|
|
||||||
runs-on: windows-latest
|
runs-on: windows-latest
|
||||||
|
env:
|
||||||
|
# link.exe is single-threaded and the long pole on Windows builds. Use
|
||||||
|
# rustc's bundled lld-link instead.
|
||||||
|
CARGO_TARGET_X86_64_PC_WINDOWS_MSVC_LINKER: rust-lld
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v6
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
lfs: true
|
lfs: true
|
||||||
@@ -113,18 +107,70 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
python-minor-version: 10
|
python-minor-version: 10
|
||||||
args: "--release --strip"
|
args: "--release --strip"
|
||||||
vcpkg_token: ${{ secrets.VCPKG_GITHUB_PACKAGES }}
|
- uses: actions/upload-artifact@v7
|
||||||
- uses: ./.github/workflows/upload_wheel
|
|
||||||
if: startsWith(github.ref, 'refs/tags/python-v')
|
if: startsWith(github.ref, 'refs/tags/python-v')
|
||||||
with:
|
with:
|
||||||
fury_token: ${{ secrets.FURY_TOKEN }}
|
name: wheels-windows
|
||||||
|
path: target/wheels/lancedb-*.whl
|
||||||
|
if-no-files-found: error
|
||||||
|
publish:
|
||||||
|
name: Publish wheels
|
||||||
|
if: startsWith(github.ref, 'refs/tags/python-v')
|
||||||
|
needs: [linux, mac, windows]
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
permissions:
|
||||||
|
id-token: write
|
||||||
|
contents: read
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v6
|
||||||
|
- name: Download wheel artifacts
|
||||||
|
uses: actions/download-artifact@v8
|
||||||
|
with:
|
||||||
|
pattern: wheels-*
|
||||||
|
path: target/wheels
|
||||||
|
merge-multiple: true
|
||||||
|
- name: List wheels
|
||||||
|
run: ls -la target/wheels
|
||||||
|
- name: Choose repo
|
||||||
|
id: choose_repo
|
||||||
|
run: |
|
||||||
|
if [[ ${{ github.ref }} == *beta* ]]; then
|
||||||
|
echo "repo=fury" >> $GITHUB_OUTPUT
|
||||||
|
else
|
||||||
|
echo "repo=pypi" >> $GITHUB_OUTPUT
|
||||||
|
fi
|
||||||
|
- name: Publish to Fury
|
||||||
|
if: steps.choose_repo.outputs.repo == 'fury'
|
||||||
|
env:
|
||||||
|
FURY_TOKEN: ${{ secrets.FURY_TOKEN }}
|
||||||
|
run: |
|
||||||
|
shopt -s nullglob
|
||||||
|
WHEELS=(target/wheels/lancedb-*.whl)
|
||||||
|
if [[ ${#WHEELS[@]} -eq 0 ]]; then
|
||||||
|
echo "No wheels found in target/wheels/" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
for WHEEL in "${WHEELS[@]}"; do
|
||||||
|
echo "Uploading $WHEEL to Fury"
|
||||||
|
curl -f -F package=@"$WHEEL" "https://$FURY_TOKEN@push.fury.io/lancedb/"
|
||||||
|
done
|
||||||
|
# NOTE: pypa/gh-action-pypi-publish must be invoked directly from a
|
||||||
|
# workflow file, not from inside a composite action. When called from a
|
||||||
|
# composite, `github.action_repository` is empty (actions/runner#2473)
|
||||||
|
# and the action falls back to `github.repository`, producing a bogus
|
||||||
|
# `docker://ghcr.io/<repo>:<ref>` image reference that GHA tries to pull.
|
||||||
|
- name: Publish to PyPI
|
||||||
|
if: steps.choose_repo.outputs.repo == 'pypi'
|
||||||
|
uses: pypa/gh-action-pypi-publish@release/v1
|
||||||
|
with:
|
||||||
|
packages-dir: target/wheels/
|
||||||
gh-release:
|
gh-release:
|
||||||
if: startsWith(github.ref, 'refs/tags/python-v')
|
if: startsWith(github.ref, 'refs/tags/python-v')
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
permissions:
|
permissions:
|
||||||
contents: write
|
contents: write
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v6
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
lfs: true
|
lfs: true
|
||||||
@@ -187,13 +233,13 @@ jobs:
|
|||||||
report-failure:
|
report-failure:
|
||||||
name: Report Workflow Failure
|
name: Report Workflow Failure
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
needs: [linux, mac, windows]
|
needs: [linux, mac, windows, publish]
|
||||||
permissions:
|
permissions:
|
||||||
contents: read
|
contents: read
|
||||||
issues: write
|
issues: write
|
||||||
if: always() && failure() && startsWith(github.ref, 'refs/tags/python-v')
|
if: always() && failure() && startsWith(github.ref, 'refs/tags/python-v')
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v6
|
||||||
- uses: ./.github/actions/create-failure-issue
|
- uses: ./.github/actions/create-failure-issue
|
||||||
with:
|
with:
|
||||||
job-results: ${{ toJSON(needs) }}
|
job-results: ${{ toJSON(needs) }}
|
||||||
|
|||||||
@@ -1,34 +0,0 @@
|
|||||||
name: upload-wheel
|
|
||||||
|
|
||||||
description: "Upload wheels to Pypi"
|
|
||||||
inputs:
|
|
||||||
fury_token:
|
|
||||||
required: true
|
|
||||||
description: "release token for the fury repo"
|
|
||||||
|
|
||||||
runs:
|
|
||||||
using: "composite"
|
|
||||||
steps:
|
|
||||||
- name: Choose repo
|
|
||||||
shell: bash
|
|
||||||
id: choose_repo
|
|
||||||
run: |
|
|
||||||
if [[ ${{ github.ref }} == *beta* ]]; then
|
|
||||||
echo "repo=fury" >> $GITHUB_OUTPUT
|
|
||||||
else
|
|
||||||
echo "repo=pypi" >> $GITHUB_OUTPUT
|
|
||||||
fi
|
|
||||||
- name: Publish to Fury
|
|
||||||
if: steps.choose_repo.outputs.repo == 'fury'
|
|
||||||
shell: bash
|
|
||||||
env:
|
|
||||||
FURY_TOKEN: ${{ inputs.fury_token }}
|
|
||||||
run: |
|
|
||||||
WHEEL=$(ls target/wheels/lancedb-*.whl 2> /dev/null | head -n 1)
|
|
||||||
echo "Uploading $WHEEL to Fury"
|
|
||||||
curl -f -F package=@$WHEEL https://$FURY_TOKEN@push.fury.io/lancedb/
|
|
||||||
- name: Publish to PyPI
|
|
||||||
if: steps.choose_repo.outputs.repo == 'pypi'
|
|
||||||
uses: pypa/gh-action-pypi-publish@release/v1
|
|
||||||
with:
|
|
||||||
packages-dir: target/wheels/
|
|
||||||
@@ -37,10 +37,13 @@ Before committing changes, run formatting for every language you touched. At min
|
|||||||
and run targeted tests through `cd python && uv run ...`.
|
and run targeted tests through `cd python && uv run ...`.
|
||||||
* TypeScript changes: run the relevant `npm`/`pnpm` lint, format, build, and docs commands in `nodejs`.
|
* TypeScript changes: run the relevant `npm`/`pnpm` lint, format, build, and docs commands in `nodejs`.
|
||||||
|
|
||||||
Before creating a PR, make sure the PR title follows Conventional Commits, such as
|
Before creating a PR, the exact value passed to `gh pr create --title` must follow
|
||||||
`fix: support nested field paths in native index creation` or
|
Conventional Commits, such as `fix: support nested field paths in native index creation`
|
||||||
`feat(python): add dataset multiprocessing support`. The semantic-release check uses the
|
or `feat(python): add dataset multiprocessing support`. Do not use a plain natural
|
||||||
PR title and body as the merge commit message, so a non-conventional PR title will fail CI.
|
language summary like `Support nested field paths in native index creation` as the PR
|
||||||
|
title. The semantic-release check uses the PR title and body as the merge commit message,
|
||||||
|
so a non-conventional PR title will fail CI. After creating a PR, read the remote PR title
|
||||||
|
back and fix it immediately if it is not conventional.
|
||||||
|
|
||||||
## Coding tips
|
## Coding tips
|
||||||
|
|
||||||
|
|||||||
Generated
+101
-39
@@ -3284,8 +3284,8 @@ checksum = "42703706b716c37f96a77aea830392ad231f44c9e9a67872fa5548707e11b11c"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "fsst"
|
name = "fsst"
|
||||||
version = "7.0.0-beta.13"
|
version = "7.1.0-beta.4"
|
||||||
source = "git+https://github.com/lance-format/lance.git?tag=v7.0.0-beta.13#929166e3ff51ed61b1fa42de2c63feaf51967ea1"
|
source = "git+https://github.com/lance-format/lance.git?tag=v7.1.0-beta.4#0c0b3e18c0a4c75bda1dd6ec9d6247ef75bd29d9"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"arrow-array",
|
"arrow-array",
|
||||||
"rand 0.9.4",
|
"rand 0.9.4",
|
||||||
@@ -4506,8 +4506,8 @@ checksum = "e037a2e1d8d5fdbd49b16a4ea09d5d6401c1f29eca5ff29d03d3824dba16256a"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "lance"
|
name = "lance"
|
||||||
version = "7.0.0-beta.13"
|
version = "7.1.0-beta.4"
|
||||||
source = "git+https://github.com/lance-format/lance.git?tag=v7.0.0-beta.13#929166e3ff51ed61b1fa42de2c63feaf51967ea1"
|
source = "git+https://github.com/lance-format/lance.git?tag=v7.1.0-beta.4#0c0b3e18c0a4c75bda1dd6ec9d6247ef75bd29d9"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"arc-swap",
|
"arc-swap",
|
||||||
"arrow",
|
"arrow",
|
||||||
@@ -4525,6 +4525,7 @@ dependencies = [
|
|||||||
"async_cell",
|
"async_cell",
|
||||||
"aws-credential-types",
|
"aws-credential-types",
|
||||||
"aws-sdk-dynamodb",
|
"aws-sdk-dynamodb",
|
||||||
|
"bitpacking",
|
||||||
"byteorder",
|
"byteorder",
|
||||||
"bytes",
|
"bytes",
|
||||||
"chrono",
|
"chrono",
|
||||||
@@ -4551,9 +4552,11 @@ dependencies = [
|
|||||||
"lance-io",
|
"lance-io",
|
||||||
"lance-linalg",
|
"lance-linalg",
|
||||||
"lance-namespace",
|
"lance-namespace",
|
||||||
|
"lance-select",
|
||||||
"lance-table",
|
"lance-table",
|
||||||
"lance-tokenizer",
|
"lance-tokenizer",
|
||||||
"log",
|
"log",
|
||||||
|
"moka",
|
||||||
"object_store",
|
"object_store",
|
||||||
"permutation",
|
"permutation",
|
||||||
"pin-project",
|
"pin-project",
|
||||||
@@ -4577,8 +4580,8 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "lance-arrow"
|
name = "lance-arrow"
|
||||||
version = "7.0.0-beta.13"
|
version = "7.1.0-beta.4"
|
||||||
source = "git+https://github.com/lance-format/lance.git?tag=v7.0.0-beta.13#929166e3ff51ed61b1fa42de2c63feaf51967ea1"
|
source = "git+https://github.com/lance-format/lance.git?tag=v7.1.0-beta.4#0c0b3e18c0a4c75bda1dd6ec9d6247ef75bd29d9"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"arrow-array",
|
"arrow-array",
|
||||||
"arrow-buffer",
|
"arrow-buffer",
|
||||||
@@ -4598,8 +4601,8 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "lance-bitpacking"
|
name = "lance-bitpacking"
|
||||||
version = "7.0.0-beta.13"
|
version = "7.1.0-beta.4"
|
||||||
source = "git+https://github.com/lance-format/lance.git?tag=v7.0.0-beta.13#929166e3ff51ed61b1fa42de2c63feaf51967ea1"
|
source = "git+https://github.com/lance-format/lance.git?tag=v7.1.0-beta.4#0c0b3e18c0a4c75bda1dd6ec9d6247ef75bd29d9"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"arrayref",
|
"arrayref",
|
||||||
"paste",
|
"paste",
|
||||||
@@ -4608,8 +4611,8 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "lance-core"
|
name = "lance-core"
|
||||||
version = "7.0.0-beta.13"
|
version = "7.1.0-beta.4"
|
||||||
source = "git+https://github.com/lance-format/lance.git?tag=v7.0.0-beta.13#929166e3ff51ed61b1fa42de2c63feaf51967ea1"
|
source = "git+https://github.com/lance-format/lance.git?tag=v7.1.0-beta.4#0c0b3e18c0a4c75bda1dd6ec9d6247ef75bd29d9"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"arrow-array",
|
"arrow-array",
|
||||||
"arrow-buffer",
|
"arrow-buffer",
|
||||||
@@ -4644,8 +4647,8 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "lance-datafusion"
|
name = "lance-datafusion"
|
||||||
version = "7.0.0-beta.13"
|
version = "7.1.0-beta.4"
|
||||||
source = "git+https://github.com/lance-format/lance.git?tag=v7.0.0-beta.13#929166e3ff51ed61b1fa42de2c63feaf51967ea1"
|
source = "git+https://github.com/lance-format/lance.git?tag=v7.1.0-beta.4#0c0b3e18c0a4c75bda1dd6ec9d6247ef75bd29d9"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"arrow",
|
"arrow",
|
||||||
"arrow-array",
|
"arrow-array",
|
||||||
@@ -4675,8 +4678,8 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "lance-datagen"
|
name = "lance-datagen"
|
||||||
version = "7.0.0-beta.13"
|
version = "7.1.0-beta.4"
|
||||||
source = "git+https://github.com/lance-format/lance.git?tag=v7.0.0-beta.13#929166e3ff51ed61b1fa42de2c63feaf51967ea1"
|
source = "git+https://github.com/lance-format/lance.git?tag=v7.1.0-beta.4#0c0b3e18c0a4c75bda1dd6ec9d6247ef75bd29d9"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"arrow",
|
"arrow",
|
||||||
"arrow-array",
|
"arrow-array",
|
||||||
@@ -4694,8 +4697,8 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "lance-encoding"
|
name = "lance-encoding"
|
||||||
version = "7.0.0-beta.13"
|
version = "7.1.0-beta.4"
|
||||||
source = "git+https://github.com/lance-format/lance.git?tag=v7.0.0-beta.13#929166e3ff51ed61b1fa42de2c63feaf51967ea1"
|
source = "git+https://github.com/lance-format/lance.git?tag=v7.1.0-beta.4#0c0b3e18c0a4c75bda1dd6ec9d6247ef75bd29d9"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"arrow-arith",
|
"arrow-arith",
|
||||||
"arrow-array",
|
"arrow-array",
|
||||||
@@ -4730,8 +4733,8 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "lance-file"
|
name = "lance-file"
|
||||||
version = "7.0.0-beta.13"
|
version = "7.1.0-beta.4"
|
||||||
source = "git+https://github.com/lance-format/lance.git?tag=v7.0.0-beta.13#929166e3ff51ed61b1fa42de2c63feaf51967ea1"
|
source = "git+https://github.com/lance-format/lance.git?tag=v7.1.0-beta.4#0c0b3e18c0a4c75bda1dd6ec9d6247ef75bd29d9"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"arrow-arith",
|
"arrow-arith",
|
||||||
"arrow-array",
|
"arrow-array",
|
||||||
@@ -4762,8 +4765,8 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "lance-index"
|
name = "lance-index"
|
||||||
version = "7.0.0-beta.13"
|
version = "7.1.0-beta.4"
|
||||||
source = "git+https://github.com/lance-format/lance.git?tag=v7.0.0-beta.13#929166e3ff51ed61b1fa42de2c63feaf51967ea1"
|
source = "git+https://github.com/lance-format/lance.git?tag=v7.1.0-beta.4#0c0b3e18c0a4c75bda1dd6ec9d6247ef75bd29d9"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"arc-swap",
|
"arc-swap",
|
||||||
"arrow",
|
"arrow",
|
||||||
@@ -4800,6 +4803,7 @@ dependencies = [
|
|||||||
"lance-file",
|
"lance-file",
|
||||||
"lance-io",
|
"lance-io",
|
||||||
"lance-linalg",
|
"lance-linalg",
|
||||||
|
"lance-select",
|
||||||
"lance-table",
|
"lance-table",
|
||||||
"lance-tokenizer",
|
"lance-tokenizer",
|
||||||
"libm",
|
"libm",
|
||||||
@@ -4827,8 +4831,8 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "lance-io"
|
name = "lance-io"
|
||||||
version = "7.0.0-beta.13"
|
version = "7.1.0-beta.4"
|
||||||
source = "git+https://github.com/lance-format/lance.git?tag=v7.0.0-beta.13#929166e3ff51ed61b1fa42de2c63feaf51967ea1"
|
source = "git+https://github.com/lance-format/lance.git?tag=v7.1.0-beta.4#0c0b3e18c0a4c75bda1dd6ec9d6247ef75bd29d9"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"arrow",
|
"arrow",
|
||||||
"arrow-arith",
|
"arrow-arith",
|
||||||
@@ -4870,8 +4874,8 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "lance-linalg"
|
name = "lance-linalg"
|
||||||
version = "7.0.0-beta.13"
|
version = "7.1.0-beta.4"
|
||||||
source = "git+https://github.com/lance-format/lance.git?tag=v7.0.0-beta.13#929166e3ff51ed61b1fa42de2c63feaf51967ea1"
|
source = "git+https://github.com/lance-format/lance.git?tag=v7.1.0-beta.4#0c0b3e18c0a4c75bda1dd6ec9d6247ef75bd29d9"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"arrow-array",
|
"arrow-array",
|
||||||
"arrow-buffer",
|
"arrow-buffer",
|
||||||
@@ -4887,8 +4891,8 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "lance-namespace"
|
name = "lance-namespace"
|
||||||
version = "7.0.0-beta.13"
|
version = "7.1.0-beta.4"
|
||||||
source = "git+https://github.com/lance-format/lance.git?tag=v7.0.0-beta.13#929166e3ff51ed61b1fa42de2c63feaf51967ea1"
|
source = "git+https://github.com/lance-format/lance.git?tag=v7.1.0-beta.4#0c0b3e18c0a4c75bda1dd6ec9d6247ef75bd29d9"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"arrow",
|
"arrow",
|
||||||
"async-trait",
|
"async-trait",
|
||||||
@@ -4900,8 +4904,8 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "lance-namespace-impls"
|
name = "lance-namespace-impls"
|
||||||
version = "7.0.0-beta.13"
|
version = "7.1.0-beta.4"
|
||||||
source = "git+https://github.com/lance-format/lance.git?tag=v7.0.0-beta.13#929166e3ff51ed61b1fa42de2c63feaf51967ea1"
|
source = "git+https://github.com/lance-format/lance.git?tag=v7.1.0-beta.4#0c0b3e18c0a4c75bda1dd6ec9d6247ef75bd29d9"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"arrow",
|
"arrow",
|
||||||
"arrow-ipc",
|
"arrow-ipc",
|
||||||
@@ -4936,9 +4940,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "lance-namespace-reqwest-client"
|
name = "lance-namespace-reqwest-client"
|
||||||
version = "0.7.6"
|
version = "0.7.7"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "f65e31bdaa13e01dab6e7cf566da31df243c34a542f0d915d3601ec0e01e61d2"
|
checksum = "6369eee4682fb11edf538388b43c61ce288b8302fe89bb40944d7daa7faaae99"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"reqwest 0.12.28",
|
"reqwest 0.12.28",
|
||||||
"serde",
|
"serde",
|
||||||
@@ -4948,10 +4952,25 @@ dependencies = [
|
|||||||
"url",
|
"url",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "lance-select"
|
||||||
|
version = "7.1.0-beta.4"
|
||||||
|
source = "git+https://github.com/lance-format/lance.git?tag=v7.1.0-beta.4#0c0b3e18c0a4c75bda1dd6ec9d6247ef75bd29d9"
|
||||||
|
dependencies = [
|
||||||
|
"arrow-array",
|
||||||
|
"arrow-buffer",
|
||||||
|
"byteorder",
|
||||||
|
"bytes",
|
||||||
|
"deepsize",
|
||||||
|
"itertools 0.13.0",
|
||||||
|
"lance-core",
|
||||||
|
"roaring",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "lance-table"
|
name = "lance-table"
|
||||||
version = "7.0.0-beta.13"
|
version = "7.1.0-beta.4"
|
||||||
source = "git+https://github.com/lance-format/lance.git?tag=v7.0.0-beta.13#929166e3ff51ed61b1fa42de2c63feaf51967ea1"
|
source = "git+https://github.com/lance-format/lance.git?tag=v7.1.0-beta.4#0c0b3e18c0a4c75bda1dd6ec9d6247ef75bd29d9"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"arrow",
|
"arrow",
|
||||||
"arrow-array",
|
"arrow-array",
|
||||||
@@ -4970,6 +4989,7 @@ dependencies = [
|
|||||||
"lance-core",
|
"lance-core",
|
||||||
"lance-file",
|
"lance-file",
|
||||||
"lance-io",
|
"lance-io",
|
||||||
|
"lance-select",
|
||||||
"log",
|
"log",
|
||||||
"object_store",
|
"object_store",
|
||||||
"prost",
|
"prost",
|
||||||
@@ -4990,8 +5010,8 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "lance-testing"
|
name = "lance-testing"
|
||||||
version = "7.0.0-beta.13"
|
version = "7.1.0-beta.4"
|
||||||
source = "git+https://github.com/lance-format/lance.git?tag=v7.0.0-beta.13#929166e3ff51ed61b1fa42de2c63feaf51967ea1"
|
source = "git+https://github.com/lance-format/lance.git?tag=v7.1.0-beta.4#0c0b3e18c0a4c75bda1dd6ec9d6247ef75bd29d9"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"arrow-array",
|
"arrow-array",
|
||||||
"arrow-schema",
|
"arrow-schema",
|
||||||
@@ -5002,8 +5022,8 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "lance-tokenizer"
|
name = "lance-tokenizer"
|
||||||
version = "7.0.0-beta.13"
|
version = "7.1.0-beta.4"
|
||||||
source = "git+https://github.com/lance-format/lance.git?tag=v7.0.0-beta.13#929166e3ff51ed61b1fa42de2c63feaf51967ea1"
|
source = "git+https://github.com/lance-format/lance.git?tag=v7.1.0-beta.4#0c0b3e18c0a4c75bda1dd6ec9d6247ef75bd29d9"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"jieba-rs",
|
"jieba-rs",
|
||||||
"lindera",
|
"lindera",
|
||||||
@@ -5014,7 +5034,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "lancedb"
|
name = "lancedb"
|
||||||
version = "0.29.1-beta.0"
|
version = "0.30.0-beta.1"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"ahash",
|
"ahash",
|
||||||
"anyhow",
|
"anyhow",
|
||||||
@@ -5084,6 +5104,7 @@ dependencies = [
|
|||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
"serde_with",
|
"serde_with",
|
||||||
|
"serial_test",
|
||||||
"snafu 0.8.9",
|
"snafu 0.8.9",
|
||||||
"tempfile",
|
"tempfile",
|
||||||
"test-log",
|
"test-log",
|
||||||
@@ -5096,7 +5117,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "lancedb-nodejs"
|
name = "lancedb-nodejs"
|
||||||
version = "0.29.1-beta.0"
|
version = "0.30.0-beta.1"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"arrow-array",
|
"arrow-array",
|
||||||
"arrow-buffer",
|
"arrow-buffer",
|
||||||
@@ -5119,7 +5140,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "lancedb-python"
|
name = "lancedb-python"
|
||||||
version = "0.32.1-beta.0"
|
version = "0.33.0-beta.1"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"arrow",
|
"arrow",
|
||||||
"async-trait",
|
"async-trait",
|
||||||
@@ -8128,6 +8149,15 @@ dependencies = [
|
|||||||
"winapi-util",
|
"winapi-util",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "scc"
|
||||||
|
version = "2.4.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "46e6f046b7fef48e2660c57ed794263155d713de679057f2d0c169bfc6e756cc"
|
||||||
|
dependencies = [
|
||||||
|
"sdd",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "schannel"
|
name = "schannel"
|
||||||
version = "0.1.29"
|
version = "0.1.29"
|
||||||
@@ -8194,6 +8224,12 @@ dependencies = [
|
|||||||
"untrusted 0.9.0",
|
"untrusted 0.9.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "sdd"
|
||||||
|
version = "3.0.10"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "490dcfcbfef26be6800d11870ff2df8774fa6e86d047e3e8c8a76b25655e41ca"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "sec1"
|
name = "sec1"
|
||||||
version = "0.3.0"
|
version = "0.3.0"
|
||||||
@@ -8384,6 +8420,32 @@ dependencies = [
|
|||||||
"unsafe-libyaml",
|
"unsafe-libyaml",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "serial_test"
|
||||||
|
version = "3.4.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "911bd979bf1070a3f3aa7b691a3b3e9968f339ceeec89e08c280a8a22207a32f"
|
||||||
|
dependencies = [
|
||||||
|
"futures-executor",
|
||||||
|
"futures-util",
|
||||||
|
"log",
|
||||||
|
"once_cell",
|
||||||
|
"parking_lot",
|
||||||
|
"scc",
|
||||||
|
"serial_test_derive",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "serial_test_derive"
|
||||||
|
version = "3.4.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "0a7d91949b85b0d2fb687445e448b40d322b6b3e4af6b44a29b21d9a5f33e6d9"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn 2.0.117",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "sha1"
|
name = "sha1"
|
||||||
version = "0.10.6"
|
version = "0.10.6"
|
||||||
|
|||||||
+14
-14
@@ -13,20 +13,20 @@ categories = ["database-implementations"]
|
|||||||
rust-version = "1.91.0"
|
rust-version = "1.91.0"
|
||||||
|
|
||||||
[workspace.dependencies]
|
[workspace.dependencies]
|
||||||
lance = { "version" = "=7.0.0-beta.13", default-features = false, "tag" = "v7.0.0-beta.13", "git" = "https://github.com/lance-format/lance.git" }
|
lance = { "version" = "=7.1.0-beta.4", default-features = false, "tag" = "v7.1.0-beta.4", "git" = "https://github.com/lance-format/lance.git" }
|
||||||
lance-core = { "version" = "=7.0.0-beta.13", "tag" = "v7.0.0-beta.13", "git" = "https://github.com/lance-format/lance.git" }
|
lance-core = { "version" = "=7.1.0-beta.4", "tag" = "v7.1.0-beta.4", "git" = "https://github.com/lance-format/lance.git" }
|
||||||
lance-datagen = { "version" = "=7.0.0-beta.13", "tag" = "v7.0.0-beta.13", "git" = "https://github.com/lance-format/lance.git" }
|
lance-datagen = { "version" = "=7.1.0-beta.4", "tag" = "v7.1.0-beta.4", "git" = "https://github.com/lance-format/lance.git" }
|
||||||
lance-file = { "version" = "=7.0.0-beta.13", "tag" = "v7.0.0-beta.13", "git" = "https://github.com/lance-format/lance.git" }
|
lance-file = { "version" = "=7.1.0-beta.4", "tag" = "v7.1.0-beta.4", "git" = "https://github.com/lance-format/lance.git" }
|
||||||
lance-io = { "version" = "=7.0.0-beta.13", default-features = false, "tag" = "v7.0.0-beta.13", "git" = "https://github.com/lance-format/lance.git" }
|
lance-io = { "version" = "=7.1.0-beta.4", default-features = false, "tag" = "v7.1.0-beta.4", "git" = "https://github.com/lance-format/lance.git" }
|
||||||
lance-index = { "version" = "=7.0.0-beta.13", "tag" = "v7.0.0-beta.13", "git" = "https://github.com/lance-format/lance.git" }
|
lance-index = { "version" = "=7.1.0-beta.4", "tag" = "v7.1.0-beta.4", "git" = "https://github.com/lance-format/lance.git" }
|
||||||
lance-linalg = { "version" = "=7.0.0-beta.13", "tag" = "v7.0.0-beta.13", "git" = "https://github.com/lance-format/lance.git" }
|
lance-linalg = { "version" = "=7.1.0-beta.4", "tag" = "v7.1.0-beta.4", "git" = "https://github.com/lance-format/lance.git" }
|
||||||
lance-namespace = { "version" = "=7.0.0-beta.13", "tag" = "v7.0.0-beta.13", "git" = "https://github.com/lance-format/lance.git" }
|
lance-namespace = { "version" = "=7.1.0-beta.4", "tag" = "v7.1.0-beta.4", "git" = "https://github.com/lance-format/lance.git" }
|
||||||
lance-namespace-impls = { "version" = "=7.0.0-beta.13", default-features = false, "tag" = "v7.0.0-beta.13", "git" = "https://github.com/lance-format/lance.git" }
|
lance-namespace-impls = { "version" = "=7.1.0-beta.4", default-features = false, "tag" = "v7.1.0-beta.4", "git" = "https://github.com/lance-format/lance.git" }
|
||||||
lance-table = { "version" = "=7.0.0-beta.13", "tag" = "v7.0.0-beta.13", "git" = "https://github.com/lance-format/lance.git" }
|
lance-table = { "version" = "=7.1.0-beta.4", "tag" = "v7.1.0-beta.4", "git" = "https://github.com/lance-format/lance.git" }
|
||||||
lance-testing = { "version" = "=7.0.0-beta.13", "tag" = "v7.0.0-beta.13", "git" = "https://github.com/lance-format/lance.git" }
|
lance-testing = { "version" = "=7.1.0-beta.4", "tag" = "v7.1.0-beta.4", "git" = "https://github.com/lance-format/lance.git" }
|
||||||
lance-datafusion = { "version" = "=7.0.0-beta.13", "tag" = "v7.0.0-beta.13", "git" = "https://github.com/lance-format/lance.git" }
|
lance-datafusion = { "version" = "=7.1.0-beta.4", "tag" = "v7.1.0-beta.4", "git" = "https://github.com/lance-format/lance.git" }
|
||||||
lance-encoding = { "version" = "=7.0.0-beta.13", "tag" = "v7.0.0-beta.13", "git" = "https://github.com/lance-format/lance.git" }
|
lance-encoding = { "version" = "=7.1.0-beta.4", "tag" = "v7.1.0-beta.4", "git" = "https://github.com/lance-format/lance.git" }
|
||||||
lance-arrow = { "version" = "=7.0.0-beta.13", "tag" = "v7.0.0-beta.13", "git" = "https://github.com/lance-format/lance.git" }
|
lance-arrow = { "version" = "=7.1.0-beta.4", "tag" = "v7.1.0-beta.4", "git" = "https://github.com/lance-format/lance.git" }
|
||||||
ahash = "0.8"
|
ahash = "0.8"
|
||||||
# Note that this one does not include pyarrow
|
# Note that this one does not include pyarrow
|
||||||
arrow = { version = "58.0.0", optional = false }
|
arrow = { version = "58.0.0", optional = false }
|
||||||
|
|||||||
@@ -112,25 +112,25 @@ def fetch_remote_tags() -> List[TagInfo]:
|
|||||||
"api",
|
"api",
|
||||||
"-X",
|
"-X",
|
||||||
"GET",
|
"GET",
|
||||||
f"repos/{LANCE_REPO}/git/refs/tags",
|
f"repos/{LANCE_REPO}/releases",
|
||||||
"--paginate",
|
|
||||||
"--jq",
|
"--jq",
|
||||||
".[].ref",
|
".[].tag_name",
|
||||||
|
"-F",
|
||||||
|
"per_page=20",
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
tags: List[TagInfo] = []
|
tags: List[TagInfo] = []
|
||||||
for line in output.splitlines():
|
for line in output.splitlines():
|
||||||
ref = line.strip()
|
tag = line.strip()
|
||||||
if not ref.startswith("refs/tags/v"):
|
if not tag.startswith("v"):
|
||||||
continue
|
continue
|
||||||
tag = ref.split("refs/tags/")[-1]
|
|
||||||
version = tag.lstrip("v")
|
version = tag.lstrip("v")
|
||||||
try:
|
try:
|
||||||
tags.append(TagInfo(tag=tag, version=version, semver=parse_semver(version)))
|
tags.append(TagInfo(tag=tag, version=version, semver=parse_semver(version)))
|
||||||
except ValueError:
|
except ValueError:
|
||||||
continue
|
continue
|
||||||
if not tags:
|
if not tags:
|
||||||
raise RuntimeError("No Lance tags could be parsed from GitHub API output")
|
raise RuntimeError("No Lance releases could be parsed from GitHub API output")
|
||||||
return tags
|
return tags
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ Add the following dependency to your `pom.xml`:
|
|||||||
<dependency>
|
<dependency>
|
||||||
<groupId>com.lancedb</groupId>
|
<groupId>com.lancedb</groupId>
|
||||||
<artifactId>lancedb-core</artifactId>
|
<artifactId>lancedb-core</artifactId>
|
||||||
<version>0.29.1-beta.0</version>
|
<version>0.30.0-beta.1</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|||||||
@@ -70,16 +70,20 @@ client used by manifest-enabled native connections.
|
|||||||
optional readConsistencyInterval: number;
|
optional readConsistencyInterval: number;
|
||||||
```
|
```
|
||||||
|
|
||||||
(For LanceDB OSS only): The interval, in seconds, at which to check for
|
The interval, in seconds, at which to check for updates to the table
|
||||||
updates to the table from other processes. If None, then consistency is not
|
from other processes. If None, then consistency is not checked. For
|
||||||
checked. For performance reasons, this is the default. For strong
|
performance reasons, this is the default. For strong consistency, set
|
||||||
consistency, set this to zero seconds. Then every read will check for
|
this to zero seconds. Then every read will check for updates from other
|
||||||
updates from other processes. As a compromise, you can set this to a
|
processes. As a compromise, you can set this to a non-zero value for
|
||||||
non-zero value for eventual consistency. If more than that interval
|
eventual consistency. If more than that interval has passed since the
|
||||||
has passed since the last check, then the table will be checked for updates.
|
last check, then the table will be checked for updates. Note: this
|
||||||
Note: this consistency only applies to read operations. Write operations are
|
consistency only applies to read operations. Write operations are
|
||||||
always consistent.
|
always consistent.
|
||||||
|
|
||||||
|
Stronger consistency is not free. The smaller the interval, the more
|
||||||
|
often each read pays the cost of checking for updates against object
|
||||||
|
storage, raising per-read latency and cost.
|
||||||
|
|
||||||
***
|
***
|
||||||
|
|
||||||
### region?
|
### region?
|
||||||
|
|||||||
@@ -8,7 +8,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<groupId>com.lancedb</groupId>
|
<groupId>com.lancedb</groupId>
|
||||||
<artifactId>lancedb-parent</artifactId>
|
<artifactId>lancedb-parent</artifactId>
|
||||||
<version>0.29.1-beta.0</version>
|
<version>0.30.0-beta.1</version>
|
||||||
<relativePath>../pom.xml</relativePath>
|
<relativePath>../pom.xml</relativePath>
|
||||||
</parent>
|
</parent>
|
||||||
|
|
||||||
|
|||||||
+2
-2
@@ -6,7 +6,7 @@
|
|||||||
|
|
||||||
<groupId>com.lancedb</groupId>
|
<groupId>com.lancedb</groupId>
|
||||||
<artifactId>lancedb-parent</artifactId>
|
<artifactId>lancedb-parent</artifactId>
|
||||||
<version>0.29.1-beta.0</version>
|
<version>0.30.0-beta.1</version>
|
||||||
<packaging>pom</packaging>
|
<packaging>pom</packaging>
|
||||||
<name>${project.artifactId}</name>
|
<name>${project.artifactId}</name>
|
||||||
<description>LanceDB Java SDK Parent POM</description>
|
<description>LanceDB Java SDK Parent POM</description>
|
||||||
@@ -28,7 +28,7 @@
|
|||||||
<properties>
|
<properties>
|
||||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||||
<arrow.version>15.0.0</arrow.version>
|
<arrow.version>15.0.0</arrow.version>
|
||||||
<lance-core.version>7.0.0-beta.13</lance-core.version>
|
<lance-core.version>7.1.0-beta.4</lance-core.version>
|
||||||
<spotless.skip>false</spotless.skip>
|
<spotless.skip>false</spotless.skip>
|
||||||
<spotless.version>2.30.0</spotless.version>
|
<spotless.version>2.30.0</spotless.version>
|
||||||
<spotless.java.googlejavaformat.version>1.7</spotless.java.googlejavaformat.version>
|
<spotless.java.googlejavaformat.version>1.7</spotless.java.googlejavaformat.version>
|
||||||
|
|||||||
+1
-1
@@ -1,7 +1,7 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "lancedb-nodejs"
|
name = "lancedb-nodejs"
|
||||||
edition.workspace = true
|
edition.workspace = true
|
||||||
version = "0.29.1-beta.0"
|
version = "0.30.0-beta.1"
|
||||||
publish = false
|
publish = false
|
||||||
license.workspace = true
|
license.workspace = true
|
||||||
description.workspace = true
|
description.workspace = true
|
||||||
|
|||||||
@@ -171,18 +171,22 @@ describe("given a connection", () => {
|
|||||||
|
|
||||||
let manifestDir =
|
let manifestDir =
|
||||||
tmpDir.name + "/test_manifest_paths_v2_empty.lance/_versions";
|
tmpDir.name + "/test_manifest_paths_v2_empty.lance/_versions";
|
||||||
readdirSync(manifestDir).forEach((file) => {
|
readdirSync(manifestDir)
|
||||||
expect(file).toMatch(/^\d{20}\.manifest$/);
|
.filter((f) => f.endsWith(".manifest"))
|
||||||
});
|
.forEach((file) => {
|
||||||
|
expect(file).toMatch(/^\d{20}\.manifest$/);
|
||||||
|
});
|
||||||
|
|
||||||
table = (await db.createTable("test_manifest_paths_v2", [{ id: 1 }], {
|
table = (await db.createTable("test_manifest_paths_v2", [{ id: 1 }], {
|
||||||
enableV2ManifestPaths: true,
|
enableV2ManifestPaths: true,
|
||||||
})) as LocalTable;
|
})) as LocalTable;
|
||||||
expect(await table.usesV2ManifestPaths()).toBe(true);
|
expect(await table.usesV2ManifestPaths()).toBe(true);
|
||||||
manifestDir = tmpDir.name + "/test_manifest_paths_v2.lance/_versions";
|
manifestDir = tmpDir.name + "/test_manifest_paths_v2.lance/_versions";
|
||||||
readdirSync(manifestDir).forEach((file) => {
|
readdirSync(manifestDir)
|
||||||
expect(file).toMatch(/^\d{20}\.manifest$/);
|
.filter((f) => f.endsWith(".manifest"))
|
||||||
});
|
.forEach((file) => {
|
||||||
|
expect(file).toMatch(/^\d{20}\.manifest$/);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should be able to migrate tables to the V2 manifest paths", async () => {
|
it("should be able to migrate tables to the V2 manifest paths", async () => {
|
||||||
@@ -199,16 +203,20 @@ describe("given a connection", () => {
|
|||||||
|
|
||||||
const manifestDir =
|
const manifestDir =
|
||||||
tmpDir.name + "/test_manifest_path_migration.lance/_versions";
|
tmpDir.name + "/test_manifest_path_migration.lance/_versions";
|
||||||
readdirSync(manifestDir).forEach((file) => {
|
readdirSync(manifestDir)
|
||||||
expect(file).toMatch(/^\d\.manifest$/);
|
.filter((f) => f.endsWith(".manifest"))
|
||||||
});
|
.forEach((file) => {
|
||||||
|
expect(file).toMatch(/^\d\.manifest$/);
|
||||||
|
});
|
||||||
|
|
||||||
await table.migrateManifestPathsV2();
|
await table.migrateManifestPathsV2();
|
||||||
expect(await table.usesV2ManifestPaths()).toBe(true);
|
expect(await table.usesV2ManifestPaths()).toBe(true);
|
||||||
|
|
||||||
readdirSync(manifestDir).forEach((file) => {
|
readdirSync(manifestDir)
|
||||||
expect(file).toMatch(/^\d{20}\.manifest$/);
|
.filter((f) => f.endsWith(".manifest"))
|
||||||
});
|
.forEach((file) => {
|
||||||
|
expect(file).toMatch(/^\d{20}\.manifest$/);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@lancedb/lancedb-darwin-arm64",
|
"name": "@lancedb/lancedb-darwin-arm64",
|
||||||
"version": "0.29.1-beta.0",
|
"version": "0.30.0-beta.1",
|
||||||
"os": ["darwin"],
|
"os": ["darwin"],
|
||||||
"cpu": ["arm64"],
|
"cpu": ["arm64"],
|
||||||
"main": "lancedb.darwin-arm64.node",
|
"main": "lancedb.darwin-arm64.node",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@lancedb/lancedb-linux-arm64-gnu",
|
"name": "@lancedb/lancedb-linux-arm64-gnu",
|
||||||
"version": "0.29.1-beta.0",
|
"version": "0.30.0-beta.1",
|
||||||
"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.29.1-beta.0",
|
"version": "0.30.0-beta.1",
|
||||||
"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.29.1-beta.0",
|
"version": "0.30.0-beta.1",
|
||||||
"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.29.1-beta.0",
|
"version": "0.30.0-beta.1",
|
||||||
"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.29.1-beta.0",
|
"version": "0.30.0-beta.1",
|
||||||
"os": [
|
"os": [
|
||||||
"win32"
|
"win32"
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@lancedb/lancedb-win32-x64-msvc",
|
"name": "@lancedb/lancedb-win32-x64-msvc",
|
||||||
"version": "0.29.1-beta.0",
|
"version": "0.30.0-beta.1",
|
||||||
"os": ["win32"],
|
"os": ["win32"],
|
||||||
"cpu": ["x64"],
|
"cpu": ["x64"],
|
||||||
"main": "lancedb.win32-x64-msvc.node",
|
"main": "lancedb.win32-x64-msvc.node",
|
||||||
|
|||||||
Generated
+2
-2
@@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "@lancedb/lancedb",
|
"name": "@lancedb/lancedb",
|
||||||
"version": "0.29.1-beta.0",
|
"version": "0.30.0-beta.1",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "@lancedb/lancedb",
|
"name": "@lancedb/lancedb",
|
||||||
"version": "0.29.1-beta.0",
|
"version": "0.30.0-beta.1",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"x64",
|
"x64",
|
||||||
"arm64"
|
"arm64"
|
||||||
|
|||||||
+1
-1
@@ -11,7 +11,7 @@
|
|||||||
"ann"
|
"ann"
|
||||||
],
|
],
|
||||||
"private": false,
|
"private": false,
|
||||||
"version": "0.29.1-beta.0",
|
"version": "0.30.0-beta.1",
|
||||||
"main": "dist/index.js",
|
"main": "dist/index.js",
|
||||||
"exports": {
|
"exports": {
|
||||||
".": "./dist/index.js",
|
".": "./dist/index.js",
|
||||||
|
|||||||
+12
-8
@@ -24,15 +24,19 @@ mod util;
|
|||||||
#[napi(object)]
|
#[napi(object)]
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct ConnectionOptions {
|
pub struct ConnectionOptions {
|
||||||
/// (For LanceDB OSS only): The interval, in seconds, at which to check for
|
/// The interval, in seconds, at which to check for updates to the table
|
||||||
/// updates to the table from other processes. If None, then consistency is not
|
/// from other processes. If None, then consistency is not checked. For
|
||||||
/// checked. For performance reasons, this is the default. For strong
|
/// performance reasons, this is the default. For strong consistency, set
|
||||||
/// consistency, set this to zero seconds. Then every read will check for
|
/// this to zero seconds. Then every read will check for updates from other
|
||||||
/// updates from other processes. As a compromise, you can set this to a
|
/// processes. As a compromise, you can set this to a non-zero value for
|
||||||
/// non-zero value for eventual consistency. If more than that interval
|
/// eventual consistency. If more than that interval has passed since the
|
||||||
/// has passed since the last check, then the table will be checked for updates.
|
/// last check, then the table will be checked for updates. Note: this
|
||||||
/// Note: this consistency only applies to read operations. Write operations are
|
/// consistency only applies to read operations. Write operations are
|
||||||
/// always consistent.
|
/// always consistent.
|
||||||
|
///
|
||||||
|
/// Stronger consistency is not free. The smaller the interval, the more
|
||||||
|
/// often each read pays the cost of checking for updates against object
|
||||||
|
/// storage, raising per-read latency and cost.
|
||||||
pub read_consistency_interval: Option<f64>,
|
pub read_consistency_interval: Option<f64>,
|
||||||
/// (For LanceDB OSS only): configuration for object storage.
|
/// (For LanceDB OSS only): configuration for object storage.
|
||||||
///
|
///
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
[tool.bumpversion]
|
[tool.bumpversion]
|
||||||
current_version = "0.33.0-beta.0"
|
current_version = "0.33.0-beta.1"
|
||||||
parse = """(?x)
|
parse = """(?x)
|
||||||
(?P<major>0|[1-9]\\d*)\\.
|
(?P<major>0|[1-9]\\d*)\\.
|
||||||
(?P<minor>0|[1-9]\\d*)\\.
|
(?P<minor>0|[1-9]\\d*)\\.
|
||||||
|
|||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "lancedb-python"
|
name = "lancedb-python"
|
||||||
version = "0.33.0-beta.0"
|
version = "0.33.0-beta.1"
|
||||||
publish = false
|
publish = false
|
||||||
edition.workspace = true
|
edition.workspace = true
|
||||||
description = "Python bindings for LanceDB"
|
description = "Python bindings for LanceDB"
|
||||||
|
|||||||
@@ -94,7 +94,6 @@ def connect(
|
|||||||
host_override: str, optional
|
host_override: str, optional
|
||||||
The override url for LanceDB Cloud.
|
The override url for LanceDB Cloud.
|
||||||
read_consistency_interval: timedelta, default None
|
read_consistency_interval: timedelta, default None
|
||||||
(For LanceDB OSS only)
|
|
||||||
The interval at which to check for updates to the table from other
|
The interval at which to check for updates to the table from other
|
||||||
processes. If None, then consistency is not checked. For performance
|
processes. If None, then consistency is not checked. For performance
|
||||||
reasons, this is the default. For strong consistency, set this to
|
reasons, this is the default. For strong consistency, set this to
|
||||||
@@ -104,6 +103,10 @@ def connect(
|
|||||||
the last check, then the table will be checked for updates. Note: this
|
the last check, then the table will be checked for updates. Note: this
|
||||||
consistency only applies to read operations. Write operations are
|
consistency only applies to read operations. Write operations are
|
||||||
always consistent.
|
always consistent.
|
||||||
|
|
||||||
|
Stronger consistency is not free. The smaller the interval, the more
|
||||||
|
often each read pays the cost of checking for updates against object
|
||||||
|
storage, raising per-read latency and cost.
|
||||||
client_config: ClientConfig or dict, optional
|
client_config: ClientConfig or dict, optional
|
||||||
Configuration options for the LanceDB Cloud HTTP client. If a dict, then
|
Configuration options for the LanceDB Cloud HTTP client. If a dict, then
|
||||||
the keys are the attributes of the ClientConfig class. If None, then the
|
the keys are the attributes of the ClientConfig class. If None, then the
|
||||||
@@ -147,6 +150,13 @@ def connect(
|
|||||||
>>> db = lancedb.connect("s3://my-bucket/lancedb",
|
>>> db = lancedb.connect("s3://my-bucket/lancedb",
|
||||||
... storage_options={"aws_access_key_id": "***"})
|
... storage_options={"aws_access_key_id": "***"})
|
||||||
|
|
||||||
|
For tests and temporary data, use an in-memory database:
|
||||||
|
|
||||||
|
>>> db = lancedb.connect("memory://")
|
||||||
|
|
||||||
|
In-memory databases are not persisted. Tables are dropped when the last
|
||||||
|
connection or table handle referencing them is closed.
|
||||||
|
|
||||||
Connect to LanceDB cloud:
|
Connect to LanceDB cloud:
|
||||||
|
|
||||||
>>> db = lancedb.connect("db://my_database", api_key="ldb_...",
|
>>> db = lancedb.connect("db://my_database", api_key="ldb_...",
|
||||||
@@ -210,6 +220,7 @@ def connect(
|
|||||||
request_thread_pool=request_thread_pool,
|
request_thread_pool=request_thread_pool,
|
||||||
client_config=client_config,
|
client_config=client_config,
|
||||||
storage_options=storage_options,
|
storage_options=storage_options,
|
||||||
|
read_consistency_interval=read_consistency_interval,
|
||||||
**kwargs,
|
**kwargs,
|
||||||
)
|
)
|
||||||
_check_s3_bucket_with_dots(str(uri), storage_options)
|
_check_s3_bucket_with_dots(str(uri), storage_options)
|
||||||
@@ -336,7 +347,6 @@ async def connect_async(
|
|||||||
host_override: str, optional
|
host_override: str, optional
|
||||||
The override url for LanceDB Cloud.
|
The override url for LanceDB Cloud.
|
||||||
read_consistency_interval: timedelta, default None
|
read_consistency_interval: timedelta, default None
|
||||||
(For LanceDB OSS only)
|
|
||||||
The interval at which to check for updates to the table from other
|
The interval at which to check for updates to the table from other
|
||||||
processes. If None, then consistency is not checked. For performance
|
processes. If None, then consistency is not checked. For performance
|
||||||
reasons, this is the default. For strong consistency, set this to
|
reasons, this is the default. For strong consistency, set this to
|
||||||
@@ -346,6 +356,10 @@ async def connect_async(
|
|||||||
the last check, then the table will be checked for updates. Note: this
|
the last check, then the table will be checked for updates. Note: this
|
||||||
consistency only applies to read operations. Write operations are
|
consistency only applies to read operations. Write operations are
|
||||||
always consistent.
|
always consistent.
|
||||||
|
|
||||||
|
Stronger consistency is not free. The smaller the interval, the more
|
||||||
|
often each read pays the cost of checking for updates against object
|
||||||
|
storage, raising per-read latency and cost.
|
||||||
client_config: ClientConfig or dict, optional
|
client_config: ClientConfig or dict, optional
|
||||||
Configuration options for the LanceDB Cloud HTTP client. If a dict, then
|
Configuration options for the LanceDB Cloud HTTP client. If a dict, then
|
||||||
the keys are the attributes of the ClientConfig class. If None, then the
|
the keys are the attributes of the ClientConfig class. If None, then the
|
||||||
@@ -378,6 +392,8 @@ async def connect_async(
|
|||||||
... db = await lancedb.connect_async("s3://my-bucket/lancedb",
|
... db = await lancedb.connect_async("s3://my-bucket/lancedb",
|
||||||
... storage_options={
|
... storage_options={
|
||||||
... "aws_access_key_id": "***"})
|
... "aws_access_key_id": "***"})
|
||||||
|
... # For tests and temporary data, use an in-memory database
|
||||||
|
... db = await lancedb.connect_async("memory://")
|
||||||
... # Connect to LanceDB cloud
|
... # Connect to LanceDB cloud
|
||||||
... db = await lancedb.connect_async("db://my_database", api_key="ldb_...",
|
... db = await lancedb.connect_async("db://my_database", api_key="ldb_...",
|
||||||
... client_config={
|
... client_config={
|
||||||
|
|||||||
@@ -50,6 +50,7 @@ class RemoteDBConnection(DBConnection):
|
|||||||
connection_timeout: Optional[float] = None,
|
connection_timeout: Optional[float] = None,
|
||||||
read_timeout: Optional[float] = None,
|
read_timeout: Optional[float] = None,
|
||||||
storage_options: Optional[Dict[str, str]] = None,
|
storage_options: Optional[Dict[str, str]] = None,
|
||||||
|
read_consistency_interval: Optional[timedelta] = None,
|
||||||
):
|
):
|
||||||
"""Connect to a remote LanceDB database."""
|
"""Connect to a remote LanceDB database."""
|
||||||
if isinstance(client_config, dict):
|
if isinstance(client_config, dict):
|
||||||
@@ -103,6 +104,7 @@ class RemoteDBConnection(DBConnection):
|
|||||||
host_override=host_override,
|
host_override=host_override,
|
||||||
client_config=client_config,
|
client_config=client_config,
|
||||||
storage_options=storage_options,
|
storage_options=storage_options,
|
||||||
|
read_consistency_interval=read_consistency_interval,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -102,8 +102,15 @@ class LinearCombinationReranker(Reranker):
|
|||||||
|
|
||||||
combined_list = []
|
combined_list = []
|
||||||
for row_id, result in results.items():
|
for row_id, result in results.items():
|
||||||
|
# Convert vector distance to a relevance score in [0, 1] where
|
||||||
|
# higher is better. Missing vector entries are penalised with
|
||||||
|
# `_invert_score(fill)` = 1 - fill (= 0.0 for the default fill=1).
|
||||||
vector_score = self._invert_score(result.get("_distance", fill))
|
vector_score = self._invert_score(result.get("_distance", fill))
|
||||||
fts_score = result.get("_score", fill)
|
# FTS scores (BM25) are already in a "higher = more relevant" space.
|
||||||
|
# Missing FTS entries are penalised symmetrically: we use
|
||||||
|
# `1 - fill` so that the same `fill` value drives both missing-vector
|
||||||
|
# and missing-FTS penalties in the same direction.
|
||||||
|
fts_score = result.get("_score", 1 - fill)
|
||||||
result["_relevance_score"] = self._combine_score(vector_score, fts_score)
|
result["_relevance_score"] = self._combine_score(vector_score, fts_score)
|
||||||
combined_list.append(result)
|
combined_list.append(result)
|
||||||
|
|
||||||
@@ -123,8 +130,12 @@ class LinearCombinationReranker(Reranker):
|
|||||||
return tbl
|
return tbl
|
||||||
|
|
||||||
def _combine_score(self, vector_score, fts_score):
|
def _combine_score(self, vector_score, fts_score):
|
||||||
# these scores represent distance
|
# Both vector_score (inverted distance) and fts_score are in a
|
||||||
return 1 - (self.weight * vector_score + (1 - self.weight) * fts_score)
|
# "higher = more relevant" space. A straight weighted average gives
|
||||||
|
# higher _relevance_score to better matches, as expected.
|
||||||
|
# Previously this returned `1 - (...)` which inverted the final
|
||||||
|
# ranking so that the *least* relevant document ranked first.
|
||||||
|
return self.weight * vector_score + (1 - self.weight) * fts_score
|
||||||
|
|
||||||
def _invert_score(self, dist: float):
|
def _invert_score(self, dist: float):
|
||||||
# Invert the score between relevance and distance
|
# Invert the score between relevance and distance
|
||||||
|
|||||||
@@ -466,7 +466,8 @@ async def test_create_table_v2_manifest_paths_async(tmp_path):
|
|||||||
assert await tbl.uses_v2_manifest_paths()
|
assert await tbl.uses_v2_manifest_paths()
|
||||||
manifests_dir = tmp_path / "test_v2_manifest_paths.lance" / "_versions"
|
manifests_dir = tmp_path / "test_v2_manifest_paths.lance" / "_versions"
|
||||||
for manifest in os.listdir(manifests_dir):
|
for manifest in os.listdir(manifests_dir):
|
||||||
assert re.match(r"\d{20}\.manifest", manifest)
|
if manifest.endswith(".manifest"):
|
||||||
|
assert re.match(r"\d{20}\.manifest", manifest)
|
||||||
|
|
||||||
# Start a table in V1 mode then migrate
|
# Start a table in V1 mode then migrate
|
||||||
tbl = await db_no_v2_paths.create_table(
|
tbl = await db_no_v2_paths.create_table(
|
||||||
@@ -476,13 +477,15 @@ async def test_create_table_v2_manifest_paths_async(tmp_path):
|
|||||||
assert not await tbl.uses_v2_manifest_paths()
|
assert not await tbl.uses_v2_manifest_paths()
|
||||||
manifests_dir = tmp_path / "test_v2_migration.lance" / "_versions"
|
manifests_dir = tmp_path / "test_v2_migration.lance" / "_versions"
|
||||||
for manifest in os.listdir(manifests_dir):
|
for manifest in os.listdir(manifests_dir):
|
||||||
assert re.match(r"\d\.manifest", manifest)
|
if manifest.endswith(".manifest"):
|
||||||
|
assert re.match(r"\d\.manifest", manifest)
|
||||||
|
|
||||||
await tbl.migrate_manifest_paths_v2()
|
await tbl.migrate_manifest_paths_v2()
|
||||||
assert await tbl.uses_v2_manifest_paths()
|
assert await tbl.uses_v2_manifest_paths()
|
||||||
|
|
||||||
for manifest in os.listdir(manifests_dir):
|
for manifest in os.listdir(manifests_dir):
|
||||||
assert re.match(r"\d{20}\.manifest", manifest)
|
if manifest.endswith(".manifest"):
|
||||||
|
assert re.match(r"\d{20}\.manifest", manifest)
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
@pytest.mark.asyncio
|
||||||
|
|||||||
@@ -40,16 +40,6 @@ def _make_table(tmp_path):
|
|||||||
def test_set_lsm_write_spec_validates(tmp_path):
|
def test_set_lsm_write_spec_validates(tmp_path):
|
||||||
_db, table = _make_table(tmp_path)
|
_db, table = _make_table(tmp_path)
|
||||||
|
|
||||||
# No PK set yet.
|
|
||||||
with pytest.raises(Exception, match="primary key"):
|
|
||||||
table.set_lsm_write_spec(LsmWriteSpec.bucket("id", 4))
|
|
||||||
|
|
||||||
table.set_unenforced_primary_key("id")
|
|
||||||
|
|
||||||
# Column mismatch.
|
|
||||||
with pytest.raises(Exception, match="match"):
|
|
||||||
table.set_lsm_write_spec(LsmWriteSpec.bucket("v", 4))
|
|
||||||
|
|
||||||
# Out-of-range num_buckets.
|
# Out-of-range num_buckets.
|
||||||
with pytest.raises(Exception, match="num_buckets"):
|
with pytest.raises(Exception, match="num_buckets"):
|
||||||
table.set_lsm_write_spec(LsmWriteSpec.bucket("id", 0))
|
table.set_lsm_write_spec(LsmWriteSpec.bucket("id", 0))
|
||||||
@@ -70,7 +60,6 @@ def test_unset_lsm_write_spec(tmp_path):
|
|||||||
table.unset_lsm_write_spec()
|
table.unset_lsm_write_spec()
|
||||||
|
|
||||||
# Install a spec, then remove it; afterwards a fresh spec can be set.
|
# Install a spec, then remove it; afterwards a fresh spec can be set.
|
||||||
table.set_unenforced_primary_key("id")
|
|
||||||
table.set_lsm_write_spec(LsmWriteSpec.bucket("id", 4))
|
table.set_lsm_write_spec(LsmWriteSpec.bucket("id", 4))
|
||||||
table.unset_lsm_write_spec()
|
table.unset_lsm_write_spec()
|
||||||
# A second unset errors — there is no spec left to remove.
|
# A second unset errors — there is no spec left to remove.
|
||||||
|
|||||||
@@ -362,6 +362,22 @@ def test_table_create_indices():
|
|||||||
schema=dict(
|
schema=dict(
|
||||||
fields=[
|
fields=[
|
||||||
dict(name="id", type={"type": "int64"}, nullable=False),
|
dict(name="id", type={"type": "int64"}, nullable=False),
|
||||||
|
dict(name="text", type={"type": "string"}, nullable=False),
|
||||||
|
dict(
|
||||||
|
name="vector",
|
||||||
|
type={
|
||||||
|
"type": "fixed_size_list",
|
||||||
|
"fields": [
|
||||||
|
dict(
|
||||||
|
name="item",
|
||||||
|
type={"type": "float"},
|
||||||
|
nullable=True,
|
||||||
|
)
|
||||||
|
],
|
||||||
|
"length": 2,
|
||||||
|
},
|
||||||
|
nullable=False,
|
||||||
|
),
|
||||||
]
|
]
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -603,3 +603,89 @@ def test_cross_encoder_reranker_return_all(tmp_path):
|
|||||||
assert "_relevance_score" in result.column_names
|
assert "_relevance_score" in result.column_names
|
||||||
assert "_score" in result.column_names
|
assert "_score" in result.column_names
|
||||||
assert "_distance" in result.column_names
|
assert "_distance" in result.column_names
|
||||||
|
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# Regression tests for LinearCombinationReranker scoring bugs (issue #3154)
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
|
||||||
|
def test_linear_combination_best_match_ranks_first():
|
||||||
|
"""
|
||||||
|
The document that is BOTH the closest vector match AND the only FTS match
|
||||||
|
must rank first. Previously _combine_score subtracted from 1, inverting
|
||||||
|
the ranking so the worst document ranked highest.
|
||||||
|
"""
|
||||||
|
reranker = LinearCombinationReranker(weight=0.7, return_score="all")
|
||||||
|
|
||||||
|
# rowid 0: perfect vector match, sole FTS match → should rank 1st
|
||||||
|
# rowid 1: mediocre vector, no FTS match
|
||||||
|
# rowid 2: bad vector, no FTS match
|
||||||
|
vector_results = pa.Table.from_pydict(
|
||||||
|
{
|
||||||
|
"_rowid": [0, 1, 2],
|
||||||
|
"_distance": [0.0, 0.5, 0.9],
|
||||||
|
}
|
||||||
|
)
|
||||||
|
fts_results = pa.Table.from_pydict(
|
||||||
|
{
|
||||||
|
"_rowid": [0],
|
||||||
|
"_score": [1.0],
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
combined = reranker.merge_results(vector_results, fts_results, fill=1.0)
|
||||||
|
scores = dict(
|
||||||
|
zip(
|
||||||
|
combined["_rowid"].to_pylist(),
|
||||||
|
combined["_relevance_score"].to_pylist(),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
# rowid 0 must have the highest relevance score
|
||||||
|
assert scores[0] > scores[1], (
|
||||||
|
f"Best match (rowid 0, score={scores[0]:.4f}) should beat "
|
||||||
|
f"mid match (rowid 1, score={scores[1]:.4f})"
|
||||||
|
)
|
||||||
|
assert scores[1] > scores[2], (
|
||||||
|
f"Mid match (rowid 1, score={scores[1]:.4f}) should beat "
|
||||||
|
f"bad match (rowid 2, score={scores[2]:.4f})"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def test_linear_combination_missing_fts_is_penalised():
|
||||||
|
"""
|
||||||
|
A document with no FTS match must score *lower* than a document that
|
||||||
|
has a mediocre FTS match, everything else being equal. Previously
|
||||||
|
missing-FTS entries used fill=1.0 directly, which gave them a reward
|
||||||
|
(via the 1-(...) inversion) instead of a penalty.
|
||||||
|
"""
|
||||||
|
reranker = LinearCombinationReranker(weight=0.5, return_score="all")
|
||||||
|
|
||||||
|
vector_results = pa.Table.from_pydict(
|
||||||
|
{
|
||||||
|
"_rowid": [0, 1],
|
||||||
|
"_distance": [0.2, 0.2], # identical vector scores
|
||||||
|
}
|
||||||
|
)
|
||||||
|
fts_results = pa.Table.from_pydict(
|
||||||
|
{
|
||||||
|
"_rowid": [0], # rowid 1 has no FTS match
|
||||||
|
"_score": [0.3], # small FTS score
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
combined = reranker.merge_results(vector_results, fts_results, fill=1.0)
|
||||||
|
scores = dict(
|
||||||
|
zip(
|
||||||
|
combined["_rowid"].to_pylist(),
|
||||||
|
combined["_relevance_score"].to_pylist(),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
# rowid 0 has a small FTS score; rowid 1 has none.
|
||||||
|
# Even a small FTS contribution should beat having none at all.
|
||||||
|
assert scores[0] > scores[1], (
|
||||||
|
f"Document with FTS score (rowid 0, {scores[0]:.4f}) should beat "
|
||||||
|
f"document with no FTS match (rowid 1, {scores[1]:.4f})"
|
||||||
|
)
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "lancedb"
|
name = "lancedb"
|
||||||
version = "0.29.1-beta.0"
|
version = "0.30.0-beta.1"
|
||||||
edition.workspace = true
|
edition.workspace = true
|
||||||
description = "LanceDB: A serverless, low-latency vector database for AI applications"
|
description = "LanceDB: A serverless, low-latency vector database for AI applications"
|
||||||
license.workspace = true
|
license.workspace = true
|
||||||
@@ -104,6 +104,7 @@ datafusion.workspace = true
|
|||||||
http-body = "1" # Matching reqwest
|
http-body = "1" # Matching reqwest
|
||||||
rstest = "0.23.0"
|
rstest = "0.23.0"
|
||||||
test-log = "0.2"
|
test-log = "0.2"
|
||||||
|
serial_test = "3"
|
||||||
|
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
|
|||||||
@@ -812,8 +812,7 @@ impl ConnectBuilder {
|
|||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The interval at which to check for updates from other processes. This
|
/// The interval at which to check for updates from other processes.
|
||||||
/// only affects LanceDB OSS.
|
|
||||||
///
|
///
|
||||||
/// If left unset, consistency is not checked. For maximum read
|
/// If left unset, consistency is not checked. For maximum read
|
||||||
/// performance, this is the default. For strong consistency, set this to
|
/// performance, this is the default. For strong consistency, set this to
|
||||||
@@ -825,8 +824,11 @@ impl ConnectBuilder {
|
|||||||
/// This only affects read operations. Write operations are always
|
/// This only affects read operations. Write operations are always
|
||||||
/// consistent.
|
/// consistent.
|
||||||
///
|
///
|
||||||
/// LanceDB Cloud uses eventual consistency under the hood, and is not
|
/// # Cost
|
||||||
/// currently configurable.
|
///
|
||||||
|
/// Stronger consistency is not free. The smaller the interval, the more
|
||||||
|
/// often each read pays the cost of checking for updates against object
|
||||||
|
/// storage, raising per-read latency and cost.
|
||||||
pub fn read_consistency_interval(
|
pub fn read_consistency_interval(
|
||||||
mut self,
|
mut self,
|
||||||
read_consistency_interval: std::time::Duration,
|
read_consistency_interval: std::time::Duration,
|
||||||
@@ -886,6 +888,7 @@ impl ConnectBuilder {
|
|||||||
options.host_override,
|
options.host_override,
|
||||||
self.request.client_config,
|
self.request.client_config,
|
||||||
storage_options.into(),
|
storage_options.into(),
|
||||||
|
self.request.read_consistency_interval,
|
||||||
)?);
|
)?);
|
||||||
Ok(Connection {
|
Ok(Connection {
|
||||||
internal,
|
internal,
|
||||||
|
|||||||
@@ -245,6 +245,9 @@ pub struct RestfulLanceDbClient<S: HttpSend = Sender> {
|
|||||||
pub(crate) sender: S,
|
pub(crate) sender: S,
|
||||||
pub(crate) id_delimiter: String,
|
pub(crate) id_delimiter: String,
|
||||||
pub(crate) header_provider: Option<Arc<dyn HeaderProvider>>,
|
pub(crate) header_provider: Option<Arc<dyn HeaderProvider>>,
|
||||||
|
/// Connection-level read consistency interval. Drives the
|
||||||
|
/// `x-lancedb-min-timestamp` freshness header sent on read requests.
|
||||||
|
pub(crate) read_consistency_interval: Option<Duration>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<S: HttpSend> std::fmt::Debug for RestfulLanceDbClient<S> {
|
impl<S: HttpSend> std::fmt::Debug for RestfulLanceDbClient<S> {
|
||||||
@@ -338,6 +341,7 @@ impl RestfulLanceDbClient<Sender> {
|
|||||||
host_override: Option<String>,
|
host_override: Option<String>,
|
||||||
default_headers: HeaderMap,
|
default_headers: HeaderMap,
|
||||||
client_config: ClientConfig,
|
client_config: ClientConfig,
|
||||||
|
read_consistency_interval: Option<Duration>,
|
||||||
) -> Result<Self> {
|
) -> Result<Self> {
|
||||||
// Get the timeouts
|
// Get the timeouts
|
||||||
let timeout =
|
let timeout =
|
||||||
@@ -435,6 +439,7 @@ impl RestfulLanceDbClient<Sender> {
|
|||||||
.clone()
|
.clone()
|
||||||
.unwrap_or("$".to_string()),
|
.unwrap_or("$".to_string()),
|
||||||
header_provider: client_config.header_provider,
|
header_provider: client_config.header_provider,
|
||||||
|
read_consistency_interval,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -840,6 +845,16 @@ pub mod test_utils {
|
|||||||
pub fn client_with_handler<T>(
|
pub fn client_with_handler<T>(
|
||||||
handler: impl Fn(reqwest::Request) -> http::response::Response<T> + Send + Sync + 'static,
|
handler: impl Fn(reqwest::Request) -> http::response::Response<T> + Send + Sync + 'static,
|
||||||
) -> RestfulLanceDbClient<MockSender>
|
) -> RestfulLanceDbClient<MockSender>
|
||||||
|
where
|
||||||
|
T: Into<reqwest::Body>,
|
||||||
|
{
|
||||||
|
client_with_handler_and_interval(handler, None)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn client_with_handler_and_interval<T>(
|
||||||
|
handler: impl Fn(reqwest::Request) -> http::response::Response<T> + Send + Sync + 'static,
|
||||||
|
read_consistency_interval: Option<Duration>,
|
||||||
|
) -> RestfulLanceDbClient<MockSender>
|
||||||
where
|
where
|
||||||
T: Into<reqwest::Body>,
|
T: Into<reqwest::Body>,
|
||||||
{
|
{
|
||||||
@@ -857,6 +872,7 @@ pub mod test_utils {
|
|||||||
},
|
},
|
||||||
id_delimiter: "$".to_string(),
|
id_delimiter: "$".to_string(),
|
||||||
header_provider: None,
|
header_provider: None,
|
||||||
|
read_consistency_interval,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -881,6 +897,7 @@ pub mod test_utils {
|
|||||||
},
|
},
|
||||||
id_delimiter: config.id_delimiter.unwrap_or_else(|| "$".to_string()),
|
id_delimiter: config.id_delimiter.unwrap_or_else(|| "$".to_string()),
|
||||||
header_provider: config.header_provider,
|
header_provider: config.header_provider,
|
||||||
|
read_consistency_interval: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -888,6 +905,7 @@ pub mod test_utils {
|
|||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
use serial_test::serial;
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@@ -1046,6 +1064,7 @@ mod tests {
|
|||||||
sender: Sender,
|
sender: Sender,
|
||||||
id_delimiter: "+".to_string(),
|
id_delimiter: "+".to_string(),
|
||||||
header_provider: Some(Arc::new(provider) as Arc<dyn HeaderProvider>),
|
header_provider: Some(Arc::new(provider) as Arc<dyn HeaderProvider>),
|
||||||
|
read_consistency_interval: None,
|
||||||
};
|
};
|
||||||
|
|
||||||
// Apply dynamic headers
|
// Apply dynamic headers
|
||||||
@@ -1081,6 +1100,7 @@ mod tests {
|
|||||||
sender: Sender,
|
sender: Sender,
|
||||||
id_delimiter: "+".to_string(),
|
id_delimiter: "+".to_string(),
|
||||||
header_provider: Some(Arc::new(provider) as Arc<dyn HeaderProvider>),
|
header_provider: Some(Arc::new(provider) as Arc<dyn HeaderProvider>),
|
||||||
|
read_consistency_interval: None,
|
||||||
};
|
};
|
||||||
|
|
||||||
// Apply dynamic headers
|
// Apply dynamic headers
|
||||||
@@ -1118,6 +1138,7 @@ mod tests {
|
|||||||
sender: Sender,
|
sender: Sender,
|
||||||
id_delimiter: "+".to_string(),
|
id_delimiter: "+".to_string(),
|
||||||
header_provider: Some(Arc::new(provider) as Arc<dyn HeaderProvider>),
|
header_provider: Some(Arc::new(provider) as Arc<dyn HeaderProvider>),
|
||||||
|
read_consistency_interval: None,
|
||||||
};
|
};
|
||||||
|
|
||||||
// Header provider errors should fail the request
|
// Header provider errors should fail the request
|
||||||
@@ -1143,6 +1164,7 @@ mod tests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
#[serial(user_id_env)]
|
||||||
fn test_resolve_user_id_none() {
|
fn test_resolve_user_id_none() {
|
||||||
let config = ClientConfig::default();
|
let config = ClientConfig::default();
|
||||||
// Clear env vars that might be set from other tests
|
// Clear env vars that might be set from other tests
|
||||||
@@ -1155,6 +1177,7 @@ mod tests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
#[serial(user_id_env)]
|
||||||
fn test_resolve_user_id_from_env() {
|
fn test_resolve_user_id_from_env() {
|
||||||
// SAFETY: This is only called in tests
|
// SAFETY: This is only called in tests
|
||||||
unsafe {
|
unsafe {
|
||||||
@@ -1169,6 +1192,7 @@ mod tests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
#[serial(user_id_env)]
|
||||||
fn test_resolve_user_id_from_env_key() {
|
fn test_resolve_user_id_from_env_key() {
|
||||||
// SAFETY: This is only called in tests
|
// SAFETY: This is only called in tests
|
||||||
unsafe {
|
unsafe {
|
||||||
@@ -1189,6 +1213,7 @@ mod tests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
#[serial(user_id_env)]
|
||||||
fn test_resolve_user_id_direct_takes_precedence() {
|
fn test_resolve_user_id_direct_takes_precedence() {
|
||||||
// SAFETY: This is only called in tests
|
// SAFETY: This is only called in tests
|
||||||
unsafe {
|
unsafe {
|
||||||
@@ -1206,6 +1231,7 @@ mod tests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
#[serial(user_id_env)]
|
||||||
fn test_resolve_user_id_empty_env_ignored() {
|
fn test_resolve_user_id_empty_env_ignored() {
|
||||||
// SAFETY: This is only called in tests
|
// SAFETY: This is only called in tests
|
||||||
unsafe {
|
unsafe {
|
||||||
|
|||||||
@@ -206,6 +206,7 @@ impl RemoteDatabase {
|
|||||||
host_override: Option<String>,
|
host_override: Option<String>,
|
||||||
client_config: ClientConfig,
|
client_config: ClientConfig,
|
||||||
options: RemoteOptions,
|
options: RemoteOptions,
|
||||||
|
read_consistency_interval: Option<std::time::Duration>,
|
||||||
) -> Result<Self> {
|
) -> Result<Self> {
|
||||||
let parsed = super::client::parse_db_url(uri)?;
|
let parsed = super::client::parse_db_url(uri)?;
|
||||||
let header_map = RestfulLanceDbClient::<Sender>::default_headers(
|
let header_map = RestfulLanceDbClient::<Sender>::default_headers(
|
||||||
@@ -233,6 +234,7 @@ impl RemoteDatabase {
|
|||||||
host_override,
|
host_override,
|
||||||
header_map,
|
header_map,
|
||||||
client_config.clone(),
|
client_config.clone(),
|
||||||
|
read_consistency_interval,
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
let table_cache = Cache::builder()
|
let table_cache = Cache::builder()
|
||||||
|
|||||||
+828
-106
File diff suppressed because it is too large
Load Diff
+72
-39
@@ -253,6 +253,36 @@ pub enum Filter {
|
|||||||
Datafusion(Expr),
|
Datafusion(Expr),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// A predicate for filtering rows in delete operations.
|
||||||
|
///
|
||||||
|
/// Accepts either a SQL string or a DataFusion [`Expr`]. Use the [`From`]
|
||||||
|
/// implementations to convert from `&str` or `&Expr` automatically.
|
||||||
|
/// See [`Table::delete`] for usage examples.
|
||||||
|
pub enum Predicate<'a> {
|
||||||
|
/// A SQL predicate string
|
||||||
|
String(&'a str),
|
||||||
|
/// A DataFusion logical expression
|
||||||
|
Expr(&'a Expr),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> From<&'a str> for Predicate<'a> {
|
||||||
|
fn from(s: &'a str) -> Self {
|
||||||
|
Predicate::String(s)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> From<&'a String> for Predicate<'a> {
|
||||||
|
fn from(s: &'a String) -> Self {
|
||||||
|
Predicate::String(s.as_str())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> From<&'a Expr> for Predicate<'a> {
|
||||||
|
fn from(e: &'a Expr) -> Self {
|
||||||
|
Predicate::Expr(e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[async_trait]
|
#[async_trait]
|
||||||
pub trait Tags: Send + Sync {
|
pub trait Tags: Send + Sync {
|
||||||
/// List the tags of the table.
|
/// List the tags of the table.
|
||||||
@@ -282,17 +312,15 @@ pub use self::merge::MergeResult;
|
|||||||
/// date) and [`LsmWriteSpec::with_writer_config_defaults`] (default
|
/// date) and [`LsmWriteSpec::with_writer_config_defaults`] (default
|
||||||
/// `ShardWriter` configuration recorded in the MemWAL index).
|
/// `ShardWriter` configuration recorded in the MemWAL index).
|
||||||
///
|
///
|
||||||
/// All variants require the table to have an unenforced primary key.
|
|
||||||
///
|
|
||||||
/// Install a spec with [`Table::set_lsm_write_spec`] and remove it with
|
/// Install a spec with [`Table::set_lsm_write_spec`] and remove it with
|
||||||
/// [`Table::unset_lsm_write_spec`]. The actual `merge_insert` dispatch
|
/// [`Table::unset_lsm_write_spec`]. The actual `merge_insert` dispatch
|
||||||
/// onto the MemWAL writer is a follow-up.
|
/// onto the MemWAL writer is a follow-up.
|
||||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||||
pub enum LsmWriteSpec {
|
pub enum LsmWriteSpec {
|
||||||
/// Hash-bucket sharding by the unenforced primary key column.
|
/// Hash-bucket sharding by a scalar column.
|
||||||
///
|
///
|
||||||
/// `column` must equal the table's currently-set single-column
|
/// `column` must be a non-nested column with a supported scalar type.
|
||||||
/// unenforced primary key. `num_buckets` must be in `[1, 1024]`.
|
/// `num_buckets` must be in `[1, 1024]`.
|
||||||
/// Iceberg-compatible Murmur3-x86-32 (seed 0) is used so each row's
|
/// Iceberg-compatible Murmur3-x86-32 (seed 0) is used so each row's
|
||||||
/// `bucket(column, num_buckets)` value is stable across processes.
|
/// `bucket(column, num_buckets)` value is stable across processes.
|
||||||
Bucket {
|
Bucket {
|
||||||
@@ -491,8 +519,8 @@ pub trait BaseTable: std::fmt::Display + std::fmt::Debug + Send + Sync {
|
|||||||
|
|
||||||
/// Add new records to the table.
|
/// Add new records to the table.
|
||||||
async fn add(&self, add: AddDataBuilder) -> Result<AddResult>;
|
async fn add(&self, add: AddDataBuilder) -> Result<AddResult>;
|
||||||
/// Delete rows from the table.
|
/// Delete rows from the table matching the given [`Predicate`].
|
||||||
async fn delete(&self, predicate: &str) -> Result<DeleteResult>;
|
async fn delete(&self, predicate: Predicate<'_>) -> Result<DeleteResult>;
|
||||||
/// Update rows in the table.
|
/// Update rows in the table.
|
||||||
async fn update(&self, update: UpdateBuilder) -> Result<UpdateResult>;
|
async fn update(&self, update: UpdateBuilder) -> Result<UpdateResult>;
|
||||||
/// Create an index on the provided column(s).
|
/// Create an index on the provided column(s).
|
||||||
@@ -656,6 +684,30 @@ mod test_utils {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn new_with_handler_and_interval<T>(
|
||||||
|
name: impl Into<String>,
|
||||||
|
handler: impl Fn(reqwest::Request) -> http::Response<T> + Clone + Send + Sync + 'static,
|
||||||
|
read_consistency_interval: Option<std::time::Duration>,
|
||||||
|
) -> Self
|
||||||
|
where
|
||||||
|
T: Into<reqwest::Body>,
|
||||||
|
{
|
||||||
|
let inner = Arc::new(
|
||||||
|
crate::remote::table::RemoteTable::new_mock_with_consistency_interval(
|
||||||
|
name.into(),
|
||||||
|
handler.clone(),
|
||||||
|
read_consistency_interval,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
let database = Arc::new(crate::remote::db::RemoteDatabase::new_mock(handler));
|
||||||
|
Self {
|
||||||
|
inner,
|
||||||
|
database: Some(database),
|
||||||
|
// Registry is unused.
|
||||||
|
embedding_registry: Arc::new(MemoryRegistry::new()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn new_with_handler_version<T>(
|
pub fn new_with_handler_version<T>(
|
||||||
name: impl Into<String>,
|
name: impl Into<String>,
|
||||||
version: semver::Version,
|
version: semver::Version,
|
||||||
@@ -860,7 +912,8 @@ impl Table {
|
|||||||
/// Delete the rows from table that match the predicate.
|
/// Delete the rows from table that match the predicate.
|
||||||
///
|
///
|
||||||
/// # Arguments
|
/// # Arguments
|
||||||
/// - `predicate` - The SQL predicate string to filter the rows to be deleted.
|
/// - `predicate` - A SQL string (`&str`) or DataFusion expression (`&Expr`)
|
||||||
|
/// that selects the rows to delete.
|
||||||
///
|
///
|
||||||
/// # Example
|
/// # Example
|
||||||
///
|
///
|
||||||
@@ -869,6 +922,7 @@ impl Table {
|
|||||||
/// # use arrow_array::{FixedSizeListArray, types::Float32Type, RecordBatch,
|
/// # use arrow_array::{FixedSizeListArray, types::Float32Type, RecordBatch,
|
||||||
/// # RecordBatchIterator, Int32Array};
|
/// # RecordBatchIterator, Int32Array};
|
||||||
/// # use arrow_schema::{Schema, Field, DataType};
|
/// # use arrow_schema::{Schema, Field, DataType};
|
||||||
|
/// use datafusion_expr::{col, lit};
|
||||||
/// # tokio::runtime::Runtime::new().unwrap().block_on(async {
|
/// # tokio::runtime::Runtime::new().unwrap().block_on(async {
|
||||||
/// let tmpdir = tempfile::tempdir().unwrap();
|
/// let tmpdir = tempfile::tempdir().unwrap();
|
||||||
/// let db = lancedb::connect(tmpdir.path().to_str().unwrap())
|
/// let db = lancedb::connect(tmpdir.path().to_str().unwrap())
|
||||||
@@ -898,11 +952,17 @@ impl Table {
|
|||||||
/// .execute()
|
/// .execute()
|
||||||
/// .await
|
/// .await
|
||||||
/// .unwrap();
|
/// .unwrap();
|
||||||
|
///
|
||||||
|
/// // Using a SQL string:
|
||||||
/// tbl.delete("id > 5").await.unwrap();
|
/// tbl.delete("id > 5").await.unwrap();
|
||||||
|
///
|
||||||
|
/// // Using a DataFusion expression:
|
||||||
|
/// let expr = col("id").lt(lit(4));
|
||||||
|
/// tbl.delete(&expr).await.unwrap();
|
||||||
/// # });
|
/// # });
|
||||||
/// ```
|
/// ```
|
||||||
pub async fn delete(&self, predicate: &str) -> Result<DeleteResult> {
|
pub async fn delete(&self, predicate: impl Into<Predicate<'_>>) -> Result<DeleteResult> {
|
||||||
self.inner.delete(predicate).await
|
self.inner.delete(predicate.into()).await
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Create an index on the provided column(s).
|
/// Create an index on the provided column(s).
|
||||||
@@ -1298,21 +1358,15 @@ impl Table {
|
|||||||
///
|
///
|
||||||
/// [`LsmWriteSpec`] chooses one of three sharding strategies:
|
/// [`LsmWriteSpec`] chooses one of three sharding strategies:
|
||||||
///
|
///
|
||||||
/// - [`LsmWriteSpec::bucket`] — hash-bucket writes by the single-column
|
/// - [`LsmWriteSpec::bucket`] — hash-bucket writes by a scalar column.
|
||||||
/// unenforced primary key.
|
|
||||||
/// - [`LsmWriteSpec::identity`] — shard by the raw value of a scalar column.
|
/// - [`LsmWriteSpec::identity`] — shard by the raw value of a scalar column.
|
||||||
/// - [`LsmWriteSpec::unsharded`] — route every write to a single shard.
|
/// - [`LsmWriteSpec::unsharded`] — route every write to a single shard.
|
||||||
///
|
///
|
||||||
/// All variants require the table to have an unenforced primary key
|
|
||||||
/// ([`Table::set_unenforced_primary_key`]); bucket sharding additionally
|
|
||||||
/// requires it to be the single column being bucketed.
|
|
||||||
///
|
|
||||||
/// # Example
|
/// # Example
|
||||||
///
|
///
|
||||||
/// ```
|
/// ```
|
||||||
/// # use lancedb::table::{LsmWriteSpec, Table};
|
/// # use lancedb::table::{LsmWriteSpec, Table};
|
||||||
/// # async fn example(table: &Table) -> Result<(), Box<dyn std::error::Error>> {
|
/// # async fn example(table: &Table) -> Result<(), Box<dyn std::error::Error>> {
|
||||||
/// table.set_unenforced_primary_key(["id"]).await?;
|
|
||||||
/// table
|
/// table
|
||||||
/// .set_lsm_write_spec(
|
/// .set_lsm_write_spec(
|
||||||
/// LsmWriteSpec::bucket("id", 16).with_maintained_indexes(["id_idx"]),
|
/// LsmWriteSpec::bucket("id", 16).with_maintained_indexes(["id_idx"]),
|
||||||
@@ -2777,8 +2831,7 @@ impl BaseTable for NativeTable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Delete rows from the table
|
/// Delete rows from the table
|
||||||
async fn delete(&self, predicate: &str) -> Result<DeleteResult> {
|
async fn delete(&self, predicate: Predicate<'_>) -> Result<DeleteResult> {
|
||||||
// Delegate to the submodule implementation
|
|
||||||
delete::execute_delete(self, predicate).await
|
delete::execute_delete(self, predicate).await
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -4600,21 +4653,6 @@ mod tests {
|
|||||||
.unwrap();
|
.unwrap();
|
||||||
let table = conn.create_table("t", reader).execute().await.unwrap();
|
let table = conn.create_table("t", reader).execute().await.unwrap();
|
||||||
|
|
||||||
// Reject when no PK is set.
|
|
||||||
let err = table
|
|
||||||
.set_lsm_write_spec(LsmWriteSpec::bucket("id", 4))
|
|
||||||
.await
|
|
||||||
.expect_err("should reject without PK");
|
|
||||||
assert!(matches!(err, Error::Lance { .. }), "got {:?}", err);
|
|
||||||
|
|
||||||
// Set PK, then a mismatched column on the spec must be rejected.
|
|
||||||
table.set_unenforced_primary_key(["id"]).await.unwrap();
|
|
||||||
let err = table
|
|
||||||
.set_lsm_write_spec(LsmWriteSpec::bucket("name", 4))
|
|
||||||
.await
|
|
||||||
.expect_err("should reject column != PK");
|
|
||||||
assert!(matches!(err, Error::Lance { .. }), "got {:?}", err);
|
|
||||||
|
|
||||||
// Reject num_buckets out of range.
|
// Reject num_buckets out of range.
|
||||||
for bad in [0u32, 1025] {
|
for bad in [0u32, 1025] {
|
||||||
let err = table
|
let err = table
|
||||||
@@ -4680,9 +4718,6 @@ mod tests {
|
|||||||
.unwrap();
|
.unwrap();
|
||||||
let table = conn.create_table("t", reader).execute().await.unwrap();
|
let table = conn.create_table("t", reader).execute().await.unwrap();
|
||||||
|
|
||||||
// Lance's MemWAL still requires *some* unenforced primary key on
|
|
||||||
// the dataset; Unsharded just skips the per-row hashing step.
|
|
||||||
table.set_unenforced_primary_key(["id"]).await.unwrap();
|
|
||||||
table
|
table
|
||||||
.set_lsm_write_spec(LsmWriteSpec::unsharded())
|
.set_lsm_write_spec(LsmWriteSpec::unsharded())
|
||||||
.await
|
.await
|
||||||
@@ -4729,7 +4764,6 @@ mod tests {
|
|||||||
.unwrap();
|
.unwrap();
|
||||||
let table = conn.create_table("t", reader).execute().await.unwrap();
|
let table = conn.create_table("t", reader).execute().await.unwrap();
|
||||||
|
|
||||||
table.set_unenforced_primary_key(["id"]).await.unwrap();
|
|
||||||
table
|
table
|
||||||
.set_lsm_write_spec(
|
.set_lsm_write_spec(
|
||||||
LsmWriteSpec::identity("region")
|
LsmWriteSpec::identity("region")
|
||||||
@@ -4785,7 +4819,6 @@ mod tests {
|
|||||||
table.unset_lsm_write_spec().await.unwrap_err();
|
table.unset_lsm_write_spec().await.unwrap_err();
|
||||||
|
|
||||||
// Install a spec, then unset it.
|
// Install a spec, then unset it.
|
||||||
table.set_unenforced_primary_key(["id"]).await.unwrap();
|
|
||||||
table
|
table
|
||||||
.set_lsm_write_spec(LsmWriteSpec::bucket("id", 4))
|
.set_lsm_write_spec(LsmWriteSpec::bucket("id", 4))
|
||||||
.await
|
.await
|
||||||
|
|||||||
@@ -982,4 +982,105 @@ mod tests {
|
|||||||
table2.add(struct_batch).execute().await.unwrap();
|
table2.add(struct_batch).execute().await.unwrap();
|
||||||
assert_eq!(table2.count_rows(None).await.unwrap(), 2);
|
assert_eq!(table2.count_rows(None).await.unwrap(), 2);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Regression test: appending `arrow.json` (PyArrow `pa.json_()`) data into a table
|
||||||
|
/// whose schema was created with `pa.json_()` (internally stored as `lance.json`, backed
|
||||||
|
/// by `LargeBinary`) must succeed without a schema-mismatch error.
|
||||||
|
///
|
||||||
|
/// Previously `build_field_exprs` would attempt a `Utf8 → LargeBinary` DataFusion cast,
|
||||||
|
/// which produced a field whose Arrow extension metadata still read `arrow.json` instead
|
||||||
|
/// of `lance.json`. Lance-core then rejected the append with
|
||||||
|
/// `"json vs large_binary" schema mismatch`.
|
||||||
|
///
|
||||||
|
/// PyArrow's `pa.json_()` may be backed by either `Utf8` or `LargeUtf8` depending on the
|
||||||
|
/// constructor used, so the test is parameterized over the input backing type.
|
||||||
|
#[rstest::rstest]
|
||||||
|
#[case::utf8(DataType::Utf8)]
|
||||||
|
#[case::large_utf8(DataType::LargeUtf8)]
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_add_arrow_json_into_lance_json_table(#[case] input_type: DataType) {
|
||||||
|
use arrow_array::{Array, cast::AsArray};
|
||||||
|
use lance_arrow::ARROW_EXT_NAME_KEY;
|
||||||
|
use lance_arrow::json::{ARROW_JSON_EXT_NAME, JSON_EXT_NAME};
|
||||||
|
|
||||||
|
// Build a table whose "data" column is lance.json (LargeBinary +
|
||||||
|
// ARROW:extension:name = "lance.json").
|
||||||
|
let lance_json_field = lance_arrow::json::json_field("data", true);
|
||||||
|
let table_schema = Arc::new(Schema::new(vec![lance_json_field]));
|
||||||
|
|
||||||
|
let db = connect("memory://").execute().await.unwrap();
|
||||||
|
let table = db
|
||||||
|
.create_empty_table("json_test", table_schema)
|
||||||
|
.execute()
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
// Sanity-check the stored schema.
|
||||||
|
let stored_field = table.schema().await.unwrap();
|
||||||
|
let data_field = stored_field.field_with_name("data").unwrap();
|
||||||
|
assert_eq!(data_field.data_type(), &DataType::LargeBinary);
|
||||||
|
assert_eq!(
|
||||||
|
data_field
|
||||||
|
.metadata()
|
||||||
|
.get(ARROW_EXT_NAME_KEY)
|
||||||
|
.map(|s| s.as_str()),
|
||||||
|
Some(JSON_EXT_NAME),
|
||||||
|
);
|
||||||
|
|
||||||
|
// Build an arrow.json input field (Utf8/LargeUtf8 + arrow.json extension).
|
||||||
|
// This is what PyArrow produces for pa.json_() arrays.
|
||||||
|
let arrow_json_metadata = std::collections::HashMap::from([(
|
||||||
|
ARROW_EXT_NAME_KEY.to_string(),
|
||||||
|
ARROW_JSON_EXT_NAME.to_string(),
|
||||||
|
)]);
|
||||||
|
let arrow_json_field =
|
||||||
|
Field::new("data", input_type.clone(), true).with_metadata(arrow_json_metadata);
|
||||||
|
let arrow_json_schema = Arc::new(Schema::new(vec![arrow_json_field]));
|
||||||
|
|
||||||
|
let rows: Vec<Option<&str>> = vec![None, Some(r#"{"a": 1}"#), Some(r#"{"b": 2}"#)];
|
||||||
|
let string_array: Arc<dyn arrow_array::Array> = match input_type {
|
||||||
|
DataType::Utf8 => Arc::new(arrow_array::StringArray::from(rows.clone())),
|
||||||
|
DataType::LargeUtf8 => Arc::new(arrow_array::LargeStringArray::from(rows.clone())),
|
||||||
|
other => panic!("unsupported arrow.json backing type for this test: {other:?}"),
|
||||||
|
};
|
||||||
|
let batch = RecordBatch::try_new(arrow_json_schema, vec![string_array]).unwrap();
|
||||||
|
|
||||||
|
// This must not fail with a schema-mismatch error.
|
||||||
|
table.add(batch).execute().await.unwrap();
|
||||||
|
|
||||||
|
assert_eq!(table.count_rows(None).await.unwrap(), rows.len());
|
||||||
|
|
||||||
|
// A lance.json column is read back as Utf8 carrying arrow.json extension metadata.
|
||||||
|
let results: Vec<RecordBatch> = table
|
||||||
|
.query()
|
||||||
|
.select(Select::columns(&["data"]))
|
||||||
|
.execute()
|
||||||
|
.await
|
||||||
|
.unwrap()
|
||||||
|
.try_collect()
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
assert_eq!(results.len(), 1);
|
||||||
|
let batch = &results[0];
|
||||||
|
assert_eq!(batch.num_rows(), rows.len());
|
||||||
|
|
||||||
|
let json_col = batch.column(0);
|
||||||
|
assert_eq!(json_col.data_type(), &DataType::Utf8);
|
||||||
|
let json_strs = json_col.as_string::<i32>();
|
||||||
|
|
||||||
|
for (i, expected) in rows.iter().enumerate() {
|
||||||
|
match expected {
|
||||||
|
None => assert!(json_strs.is_null(i), "row {i} expected null"),
|
||||||
|
Some(raw) => {
|
||||||
|
assert!(!json_strs.is_null(i), "row {i} expected non-null");
|
||||||
|
let actual: serde_json::Value = serde_json::from_str(json_strs.value(i))
|
||||||
|
.expect("read-back JSON should be valid");
|
||||||
|
let expected: serde_json::Value =
|
||||||
|
serde_json::from_str(raw).expect("expected JSON should be valid");
|
||||||
|
assert_eq!(actual, expected, "row {i} JSON mismatch");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ use datafusion_physical_expr::expressions::{CastExpr, Literal};
|
|||||||
use datafusion_physical_plan::expressions::Column;
|
use datafusion_physical_plan::expressions::Column;
|
||||||
use datafusion_physical_plan::projection::ProjectionExec;
|
use datafusion_physical_plan::projection::ProjectionExec;
|
||||||
use datafusion_physical_plan::{ExecutionPlan, PhysicalExpr};
|
use datafusion_physical_plan::{ExecutionPlan, PhysicalExpr};
|
||||||
|
use lance_arrow::json::{is_arrow_json_field, is_json_field};
|
||||||
|
|
||||||
use crate::{Error, Result};
|
use crate::{Error, Result};
|
||||||
|
|
||||||
@@ -64,6 +65,18 @@ fn build_field_exprs(
|
|||||||
let input_field = &input_fields[input_idx];
|
let input_field = &input_fields[input_idx];
|
||||||
let input_expr = get_input_expr(input_idx);
|
let input_expr = get_input_expr(input_idx);
|
||||||
|
|
||||||
|
// Special case: input is arrow.json (PyArrow pa.json_() extension type backed by
|
||||||
|
// Utf8/LargeUtf8) and the table field is lance.json (backed by LargeBinary).
|
||||||
|
// Lance-core's write path already handles the arrow.json → lance.json conversion
|
||||||
|
// (including JSONB encoding), so we pass the expression through unchanged and let
|
||||||
|
// lance-core deal with it. Attempting to cast Utf8 → LargeBinary here would
|
||||||
|
// produce a field whose metadata still identifies it as arrow.json, which then
|
||||||
|
// causes a schema-mismatch error inside lance-core.
|
||||||
|
if is_arrow_json_field(input_field) && is_json_field(table_field) {
|
||||||
|
result.push((input_expr, Arc::clone(input_field) as FieldRef));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
let expr = match (input_field.data_type(), table_field.data_type()) {
|
let expr = match (input_field.data_type(), table_field.data_type()) {
|
||||||
// Both are structs: recurse into sub-fields to handle subschemas and casts.
|
// Both are structs: recurse into sub-fields to handle subschemas and casts.
|
||||||
(DataType::Struct(in_children), DataType::Struct(tbl_children))
|
(DataType::Struct(in_children), DataType::Struct(tbl_children))
|
||||||
@@ -618,4 +631,75 @@ mod tests {
|
|||||||
.unwrap();
|
.unwrap();
|
||||||
assert_eq!(a.values(), &[1, 3]);
|
assert_eq!(a.values(), &[1, 3]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// `arrow.json` input (PyArrow `pa.json_()`, Utf8/LargeUtf8 + extension metadata) against a
|
||||||
|
/// `lance.json` table field (LargeBinary + extension metadata) must be passed through
|
||||||
|
/// without a cast so that lance-core can perform its own arrow.json → JSONB conversion.
|
||||||
|
///
|
||||||
|
/// Before the fix, `cast_to_table_schema` attempted a `Utf8 → LargeBinary` DataFusion
|
||||||
|
/// cast that preserved the wrong extension metadata, causing lance-core to reject the
|
||||||
|
/// batch with a "json vs large_binary" schema-mismatch error.
|
||||||
|
#[rstest::rstest]
|
||||||
|
#[case::utf8(DataType::Utf8)]
|
||||||
|
#[case::large_utf8(DataType::LargeUtf8)]
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_arrow_json_passthrough_to_lance_json(#[case] input_type: DataType) {
|
||||||
|
use lance_arrow::ARROW_EXT_NAME_KEY;
|
||||||
|
use lance_arrow::json::{ARROW_JSON_EXT_NAME, json_field};
|
||||||
|
|
||||||
|
// Build a table schema with a lance.json field (LargeBinary + lance.json metadata).
|
||||||
|
let lance_field = json_field("data", true);
|
||||||
|
let table_schema = Schema::new(vec![lance_field]);
|
||||||
|
|
||||||
|
// Build an input batch with an arrow.json field (Utf8/LargeUtf8 + arrow.json metadata).
|
||||||
|
let arrow_meta = std::collections::HashMap::from([(
|
||||||
|
ARROW_EXT_NAME_KEY.to_string(),
|
||||||
|
ARROW_JSON_EXT_NAME.to_string(),
|
||||||
|
)]);
|
||||||
|
let arrow_field = Field::new("data", input_type.clone(), true).with_metadata(arrow_meta);
|
||||||
|
let input_schema = Arc::new(Schema::new(vec![arrow_field]));
|
||||||
|
|
||||||
|
let values = vec![Some(r#"{"x": 1}"#), None, Some(r#"{"y": 2}"#)];
|
||||||
|
let input_array: Arc<dyn arrow_array::Array> = match input_type {
|
||||||
|
DataType::Utf8 => Arc::new(StringArray::from(values)),
|
||||||
|
DataType::LargeUtf8 => Arc::new(arrow_array::LargeStringArray::from(values)),
|
||||||
|
other => panic!("unsupported arrow.json backing type for this test: {other:?}"),
|
||||||
|
};
|
||||||
|
let input_batch = RecordBatch::try_new(input_schema, vec![input_array]).unwrap();
|
||||||
|
|
||||||
|
let plan = plan_from_batch(input_batch).await;
|
||||||
|
let projected = cast_to_table_schema(plan, &table_schema).unwrap();
|
||||||
|
|
||||||
|
// The projected schema's "data" field must carry arrow.json metadata
|
||||||
|
// (the input field), not be silently dropped or miscast.
|
||||||
|
let out_field = projected.schema().field_with_name("data").unwrap().clone();
|
||||||
|
assert_eq!(out_field.data_type(), &input_type);
|
||||||
|
assert_eq!(
|
||||||
|
out_field
|
||||||
|
.metadata()
|
||||||
|
.get(ARROW_EXT_NAME_KEY)
|
||||||
|
.map(|s| s.as_str()),
|
||||||
|
Some(ARROW_JSON_EXT_NAME),
|
||||||
|
"output field must still carry arrow.json metadata so lance-core can handle it"
|
||||||
|
);
|
||||||
|
|
||||||
|
// The data must flow through correctly (3 rows, no panic).
|
||||||
|
let result = collect(projected).await;
|
||||||
|
assert_eq!(result.num_rows(), 3);
|
||||||
|
let (v0, v2) = match input_type {
|
||||||
|
DataType::Utf8 => {
|
||||||
|
let col: &StringArray = result.column(0).as_any().downcast_ref().unwrap();
|
||||||
|
(col.value(0).to_string(), col.value(2).to_string())
|
||||||
|
}
|
||||||
|
DataType::LargeUtf8 => {
|
||||||
|
let col: &arrow_array::LargeStringArray =
|
||||||
|
result.column(0).as_any().downcast_ref().unwrap();
|
||||||
|
(col.value(0).to_string(), col.value(2).to_string())
|
||||||
|
}
|
||||||
|
_ => unreachable!(),
|
||||||
|
};
|
||||||
|
assert_eq!(v0, r#"{"x": 1}"#);
|
||||||
|
assert!(result.column(0).is_null(1));
|
||||||
|
assert_eq!(v2, r#"{"y": 2}"#);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,9 +1,12 @@
|
|||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
use futures::FutureExt;
|
use futures::FutureExt;
|
||||||
|
use lance::dataset::DeleteBuilder;
|
||||||
// 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 serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
use super::NativeTable;
|
use super::{NativeTable, Predicate};
|
||||||
use crate::Result;
|
use crate::Result;
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Default)]
|
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Default)]
|
||||||
@@ -21,17 +24,39 @@ pub struct DeleteResult {
|
|||||||
/// Internal implementation of the delete logic
|
/// Internal implementation of the delete logic
|
||||||
///
|
///
|
||||||
/// This logic was moved from NativeTable::delete to keep table.rs clean.
|
/// This logic was moved from NativeTable::delete to keep table.rs clean.
|
||||||
pub(crate) async fn execute_delete(table: &NativeTable, predicate: &str) -> Result<DeleteResult> {
|
pub(crate) async fn execute_delete(
|
||||||
|
table: &NativeTable,
|
||||||
|
predicate: Predicate<'_>,
|
||||||
|
) -> Result<DeleteResult> {
|
||||||
table.dataset.ensure_mutable()?;
|
table.dataset.ensure_mutable()?;
|
||||||
let mut dataset = (*table.dataset.get().await?).clone();
|
match predicate {
|
||||||
let delete_result = dataset.delete(predicate).boxed().await?;
|
Predicate::String(s) => {
|
||||||
let num_deleted_rows = delete_result.num_deleted_rows;
|
let mut dataset = (*table.dataset.get().await?).clone();
|
||||||
let version = dataset.version().version;
|
let delete_result = dataset.delete(s).boxed().await?;
|
||||||
table.dataset.update(dataset);
|
let num_deleted_rows = delete_result.num_deleted_rows;
|
||||||
Ok(DeleteResult {
|
let version = dataset.version().version;
|
||||||
num_deleted_rows,
|
table.dataset.update(dataset);
|
||||||
version,
|
Ok(DeleteResult {
|
||||||
})
|
num_deleted_rows,
|
||||||
|
version,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
Predicate::Expr(expr) => {
|
||||||
|
let dataset = table.dataset.get().await?;
|
||||||
|
let delete_result = DeleteBuilder::from_expr(Arc::clone(&dataset), expr.clone())
|
||||||
|
.execute()
|
||||||
|
.await?;
|
||||||
|
let num_deleted_rows = delete_result.num_deleted_rows;
|
||||||
|
let version = delete_result.new_dataset.version().version;
|
||||||
|
table.dataset.update(
|
||||||
|
Arc::try_unwrap(delete_result.new_dataset).unwrap_or_else(|arc| (*arc).clone()),
|
||||||
|
);
|
||||||
|
Ok(DeleteResult {
|
||||||
|
num_deleted_rows,
|
||||||
|
version,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
@@ -176,4 +201,100 @@ mod tests {
|
|||||||
"Table version must increment after delete operation"
|
"Table version must increment after delete operation"
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_delete_expr() {
|
||||||
|
use datafusion_expr::{col, lit};
|
||||||
|
|
||||||
|
let conn = connect("memory://").execute().await.unwrap();
|
||||||
|
|
||||||
|
// 1. Create a table with values 0 to 9
|
||||||
|
let schema = Arc::new(Schema::new(vec![Field::new("i", DataType::Int32, false)]));
|
||||||
|
let batch = RecordBatch::try_new(
|
||||||
|
schema.clone(),
|
||||||
|
vec![Arc::new(Int32Array::from_iter_values(0..10))],
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let table = conn
|
||||||
|
.create_table("test_delete_expr", batch)
|
||||||
|
.execute()
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
// 2. Verify initial state
|
||||||
|
assert_eq!(table.count_rows(None).await.unwrap(), 10);
|
||||||
|
let initial_version = table.version().await.unwrap();
|
||||||
|
|
||||||
|
// 3. Execute Delete with Expr (removes values > 5)
|
||||||
|
let expr = col("i").gt(lit(5));
|
||||||
|
table.delete(&expr).await.unwrap();
|
||||||
|
|
||||||
|
// 4. Verify results
|
||||||
|
assert_eq!(table.count_rows(None).await.unwrap(), 6); // 0, 1, 2, 3, 4, 5 remain
|
||||||
|
let current_version = table.version().await.unwrap();
|
||||||
|
assert!(
|
||||||
|
current_version > initial_version,
|
||||||
|
"Table version must increment after delete_expr operation"
|
||||||
|
);
|
||||||
|
|
||||||
|
// 5. Verify specific data consistency
|
||||||
|
let batches = table
|
||||||
|
.query()
|
||||||
|
.execute()
|
||||||
|
.await
|
||||||
|
.unwrap()
|
||||||
|
.try_collect::<Vec<_>>()
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
let batch = &batches[0];
|
||||||
|
let array = batch
|
||||||
|
.column(0)
|
||||||
|
.as_any()
|
||||||
|
.downcast_ref::<Int32Array>()
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
// Ensure no value > 5 exists
|
||||||
|
for val in array.iter() {
|
||||||
|
assert!(val.unwrap() <= 5);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_delete_expr_increments_version() {
|
||||||
|
use datafusion_expr::lit;
|
||||||
|
|
||||||
|
let conn = connect("memory://").execute().await.unwrap();
|
||||||
|
|
||||||
|
// Create a table with 5 rows
|
||||||
|
let batch = record_batch!(("id", Int32, [1, 2, 3, 4, 5])).unwrap();
|
||||||
|
|
||||||
|
let table = conn
|
||||||
|
.create_table("test_delete_expr_noop", batch)
|
||||||
|
.execute()
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
// Capture the initial state (Rows = 5, Version = 1)
|
||||||
|
let initial_rows = table.count_rows(None).await.unwrap();
|
||||||
|
let initial_version = table.version().await.unwrap();
|
||||||
|
|
||||||
|
assert_eq!(initial_rows, 5);
|
||||||
|
let expr = lit(false);
|
||||||
|
table.delete(&expr).await.unwrap();
|
||||||
|
|
||||||
|
// Rows should still be 5
|
||||||
|
let current_rows = table.count_rows(None).await.unwrap();
|
||||||
|
assert_eq!(
|
||||||
|
current_rows, initial_rows,
|
||||||
|
"Data should not change when predicate is false"
|
||||||
|
);
|
||||||
|
|
||||||
|
// version check
|
||||||
|
let current_version = table.version().await.unwrap();
|
||||||
|
assert!(
|
||||||
|
current_version > initial_version,
|
||||||
|
"Table version must increment after delete_expr operation"
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ pub(crate) mod background_cache;
|
|||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
use arrow_array::RecordBatch;
|
use arrow_array::RecordBatch;
|
||||||
use arrow_schema::{DataType, Field, Fields, Schema, SchemaRef};
|
use arrow_schema::{DataType, Field, Schema, SchemaRef};
|
||||||
use datafusion_common::{DataFusionError, Result as DataFusionResult};
|
use datafusion_common::{DataFusionError, Result as DataFusionResult};
|
||||||
use datafusion_execution::RecordBatchStream;
|
use datafusion_execution::RecordBatchStream;
|
||||||
use futures::{FutureExt, Stream};
|
use futures::{FutureExt, Stream};
|
||||||
@@ -199,38 +199,32 @@ fn collect_vector_columns(
|
|||||||
path.pop();
|
path.pop();
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn resolve_arrow_field_path(schema: &Schema, column: &str) -> Result<Field> {
|
pub(crate) fn resolve_arrow_field_path(schema: &Schema, column: &str) -> Result<(String, Field)> {
|
||||||
let segments =
|
lance_core::datatypes::parse_field_path(column).map_err(|e| Error::InvalidInput {
|
||||||
lance_core::datatypes::parse_field_path(column).map_err(|e| Error::InvalidInput {
|
message: format!("Invalid field path `{}`: {}", column, e),
|
||||||
message: format!("Invalid field path `{}`: {}", column, e),
|
})?;
|
||||||
|
|
||||||
|
let lance_schema =
|
||||||
|
lance_core::datatypes::Schema::try_from(schema).map_err(|e| Error::Schema {
|
||||||
|
message: format!("Invalid schema: {}", e),
|
||||||
})?;
|
})?;
|
||||||
let mut fields = schema.fields();
|
let field_path = lance_schema
|
||||||
|
.resolve_case_insensitive(column)
|
||||||
for (idx, segment) in segments.iter().enumerate() {
|
.ok_or_else(|| Error::Schema {
|
||||||
let field = find_field(fields, segment).ok_or_else(|| Error::Schema {
|
message: format!(
|
||||||
message: format!("Field path `{}` not found in schema", column),
|
"Field path `{}` not found in schema. Available field paths: {}",
|
||||||
|
column,
|
||||||
|
lance_schema.field_paths().join(", ")
|
||||||
|
),
|
||||||
})?;
|
})?;
|
||||||
if idx + 1 == segments.len() {
|
let field = field_path.last().expect("field path should be non-empty");
|
||||||
return Ok(field.clone());
|
let path_segments = field_path
|
||||||
}
|
|
||||||
fields = match field.data_type() {
|
|
||||||
DataType::Struct(fields) => fields,
|
|
||||||
_ => {
|
|
||||||
return Err(Error::Schema {
|
|
||||||
message: format!("Field path `{}` not found in schema", column),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
unreachable!("parse_field_path returns at least one segment")
|
|
||||||
}
|
|
||||||
|
|
||||||
fn find_field<'a>(fields: &'a Fields, name: &str) -> Option<&'a Field> {
|
|
||||||
fields
|
|
||||||
.iter()
|
.iter()
|
||||||
.find(|field| field.name() == name)
|
.map(|field| field.name.as_str())
|
||||||
.map(|field| field.as_ref())
|
.collect::<Vec<_>>();
|
||||||
|
let canonical_path = lance_core::datatypes::format_field_path(&path_segments);
|
||||||
|
|
||||||
|
Ok((canonical_path, Field::from(*field)))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn supported_btree_data_type(dtype: &DataType) -> bool {
|
pub fn supported_btree_data_type(dtype: &DataType) -> bool {
|
||||||
|
|||||||
Reference in New Issue
Block a user