mirror of
https://github.com/lancedb/lancedb.git
synced 2025-12-27 15:12:53 +00:00
Compare commits
35 Commits
python-v0.
...
python-v0.
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c6c20cb2bd | ||
|
|
26080ee4c1 | ||
|
|
ef3a2b5357 | ||
|
|
c42a201389 | ||
|
|
24e42ccd4d | ||
|
|
8a50944061 | ||
|
|
40e066bc7c | ||
|
|
b3ad105fa0 | ||
|
|
6e701d3e1b | ||
|
|
2248aa9508 | ||
|
|
a6fa69ab89 | ||
|
|
b3a4efd587 | ||
|
|
4708b60bb1 | ||
|
|
080ea2f9a4 | ||
|
|
32fdde23f8 | ||
|
|
c44e5c046c | ||
|
|
f23aa0a793 | ||
|
|
83fc2b1851 | ||
|
|
56aa133ee6 | ||
|
|
27d9e5c596 | ||
|
|
ec8271931f | ||
|
|
6c6966600c | ||
|
|
2e170c3c7b | ||
|
|
fd92e651d1 | ||
|
|
c298482ee1 | ||
|
|
d59f64b5a3 | ||
|
|
30ed8c4c43 | ||
|
|
4a2cdbf299 | ||
|
|
657843d9e9 | ||
|
|
1cd76b8498 | ||
|
|
a38f784081 | ||
|
|
647dee4e94 | ||
|
|
0844c2dd64 | ||
|
|
fd2692295c | ||
|
|
d4ea50fba1 |
@@ -1,5 +1,5 @@
|
|||||||
[tool.bumpversion]
|
[tool.bumpversion]
|
||||||
current_version = "0.19.0-beta.3"
|
current_version = "0.19.0-beta.7"
|
||||||
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*)\\.
|
||||||
|
|||||||
3
.github/workflows/npm-publish.yml
vendored
3
.github/workflows/npm-publish.yml
vendored
@@ -18,6 +18,7 @@ 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/npm-publish.yml
|
- .github/workflows/npm-publish.yml
|
||||||
|
- Cargo.toml # Change in dependency frequently breaks builds
|
||||||
|
|
||||||
concurrency:
|
concurrency:
|
||||||
group: ${{ github.workflow }}-${{ github.ref }}
|
group: ${{ github.workflow }}-${{ github.ref }}
|
||||||
@@ -531,6 +532,8 @@ jobs:
|
|||||||
npm publish $PUBLISH_ARGS $filename
|
npm publish $PUBLISH_ARGS $filename
|
||||||
done
|
done
|
||||||
- name: Deprecate
|
- name: Deprecate
|
||||||
|
env:
|
||||||
|
NODE_AUTH_TOKEN: ${{ secrets.LANCEDB_NPM_REGISTRY_TOKEN }}
|
||||||
# We need to deprecate the old package to avoid confusion.
|
# We need to deprecate the old package to avoid confusion.
|
||||||
# Each time we publish a new version, it gets undeprecated.
|
# Each time we publish a new version, it gets undeprecated.
|
||||||
run: npm deprecate vectordb "Use @lancedb/lancedb instead."
|
run: npm deprecate vectordb "Use @lancedb/lancedb instead."
|
||||||
|
|||||||
1
.github/workflows/pypi-publish.yml
vendored
1
.github/workflows/pypi-publish.yml
vendored
@@ -8,6 +8,7 @@ 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
|
||||||
|
- Cargo.toml # Change in dependency frequently breaks builds
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
linux:
|
linux:
|
||||||
|
|||||||
323
Cargo.lock
generated
323
Cargo.lock
generated
@@ -158,9 +158,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "array-init-cursor"
|
name = "array-init-cursor"
|
||||||
version = "0.2.0"
|
version = "0.2.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "bf7d0a018de4f6aa429b9d33d69edf69072b1c5b1cb8d3e4a5f7ef898fc3eb76"
|
checksum = "ed51fe0f224d1d4ea768be38c51f9f831dee9d05c163c11fba0b8c44387b1fc3"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "arrayref"
|
name = "arrayref"
|
||||||
@@ -229,9 +229,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "arrow-buffer"
|
name = "arrow-buffer"
|
||||||
version = "54.3.0"
|
version = "54.3.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "bc6ed265c73f134a583d02c3cab5e16afab9446d8048ede8707e31f85fad58a0"
|
checksum = "263f4801ff1839ef53ebd06f99a56cecd1dbaf314ec893d93168e2e860e0291c"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bytes",
|
"bytes",
|
||||||
"half",
|
"half",
|
||||||
@@ -277,9 +277,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "arrow-data"
|
name = "arrow-data"
|
||||||
version = "54.3.0"
|
version = "54.3.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "5f2cebf504bb6a92a134a87fff98f01b14fbb3a93ecf7aef90cd0f888c5fffa4"
|
checksum = "61cfdd7d99b4ff618f167e548b2411e5dd2c98c0ddebedd7df433d34c20a4429"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"arrow-buffer",
|
"arrow-buffer",
|
||||||
"arrow-schema",
|
"arrow-schema",
|
||||||
@@ -315,7 +315,7 @@ dependencies = [
|
|||||||
"arrow-schema",
|
"arrow-schema",
|
||||||
"chrono",
|
"chrono",
|
||||||
"half",
|
"half",
|
||||||
"indexmap 2.8.0",
|
"indexmap 2.9.0",
|
||||||
"lexical-core",
|
"lexical-core",
|
||||||
"num",
|
"num",
|
||||||
"serde",
|
"serde",
|
||||||
@@ -350,9 +350,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "arrow-schema"
|
name = "arrow-schema"
|
||||||
version = "54.3.0"
|
version = "54.3.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "a5c53775bba63f319189f366d2b86e9a8889373eb198f07d8544938fc9f8ed9a"
|
checksum = "39cfaf5e440be44db5413b75b72c2a87c1f8f0627117d110264048f2969b99e9"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bitflags 2.9.0",
|
"bitflags 2.9.0",
|
||||||
]
|
]
|
||||||
@@ -554,9 +554,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "aws-lc-rs"
|
name = "aws-lc-rs"
|
||||||
version = "1.12.6"
|
version = "1.13.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "dabb68eb3a7aa08b46fddfd59a3d55c978243557a90ab804769f7e20e67d2b01"
|
checksum = "19b756939cb2f8dc900aa6dcd505e6e2428e9cae7ff7b028c49e3946efa70878"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"aws-lc-sys",
|
"aws-lc-sys",
|
||||||
"zeroize",
|
"zeroize",
|
||||||
@@ -564,9 +564,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "aws-lc-sys"
|
name = "aws-lc-sys"
|
||||||
version = "0.27.1"
|
version = "0.28.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "77926887776171ced7d662120a75998e444d3750c951abfe07f90da130514b1f"
|
checksum = "b9f7720b74ed28ca77f90769a71fd8c637a0137f6fae4ae947e1050229cff57f"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bindgen",
|
"bindgen",
|
||||||
"cc",
|
"cc",
|
||||||
@@ -603,12 +603,13 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "aws-sdk-bedrockruntime"
|
name = "aws-sdk-bedrockruntime"
|
||||||
version = "1.78.0"
|
version = "1.82.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "6457fd617f20075dd2d5c9f6c3cb09815e6b83ab61c55499055499da9e04478d"
|
checksum = "8cb95f77abd4321348dd2f52a25e1de199732f54d2a35860ad20f5df21c66b44"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"aws-credential-types",
|
"aws-credential-types",
|
||||||
"aws-runtime",
|
"aws-runtime",
|
||||||
|
"aws-sigv4",
|
||||||
"aws-smithy-async",
|
"aws-smithy-async",
|
||||||
"aws-smithy-eventstream",
|
"aws-smithy-eventstream",
|
||||||
"aws-smithy-http",
|
"aws-smithy-http",
|
||||||
@@ -620,6 +621,7 @@ dependencies = [
|
|||||||
"bytes",
|
"bytes",
|
||||||
"fastrand",
|
"fastrand",
|
||||||
"http 0.2.12",
|
"http 0.2.12",
|
||||||
|
"hyper 0.14.32",
|
||||||
"once_cell",
|
"once_cell",
|
||||||
"regex-lite",
|
"regex-lite",
|
||||||
"tracing",
|
"tracing",
|
||||||
@@ -627,9 +629,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "aws-sdk-dynamodb"
|
name = "aws-sdk-dynamodb"
|
||||||
version = "1.70.0"
|
version = "1.71.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "4ac281113af7f8700394bf25eb272b842b7ca088810e96c928f812282f2e6f44"
|
checksum = "2d49d08b1c99ca9a7de728a8975504857f2c24581a177f952e2a10244c305a1c"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"aws-credential-types",
|
"aws-credential-types",
|
||||||
"aws-runtime",
|
"aws-runtime",
|
||||||
@@ -650,9 +652,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "aws-sdk-kms"
|
name = "aws-sdk-kms"
|
||||||
version = "1.64.0"
|
version = "1.65.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "c23289881f4071421bbef3688ca43501c4fede796e0cbca942a54f0eb6906fbc"
|
checksum = "f5325c5e2badf4148e850017cc56cc205888c6e0b52c9e29d3501ec577005230"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"aws-credential-types",
|
"aws-credential-types",
|
||||||
"aws-runtime",
|
"aws-runtime",
|
||||||
@@ -673,9 +675,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "aws-sdk-s3"
|
name = "aws-sdk-s3"
|
||||||
version = "1.80.0"
|
version = "1.82.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "3a36b09e8273d89c4f35ea122b83b30e48f906f3b644460d72a7d3656d1be93d"
|
checksum = "e6eab2900764411ab01c8e91a76fd11a63b4e12bc3da97d9e14a0ce1343d86d3"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"aws-credential-types",
|
"aws-credential-types",
|
||||||
"aws-runtime",
|
"aws-runtime",
|
||||||
@@ -891,7 +893,7 @@ dependencies = [
|
|||||||
"hyper-util",
|
"hyper-util",
|
||||||
"pin-project-lite",
|
"pin-project-lite",
|
||||||
"rustls 0.21.12",
|
"rustls 0.21.12",
|
||||||
"rustls 0.23.25",
|
"rustls 0.23.26",
|
||||||
"rustls-native-certs 0.8.1",
|
"rustls-native-certs 0.8.1",
|
||||||
"rustls-pki-types",
|
"rustls-pki-types",
|
||||||
"tokio",
|
"tokio",
|
||||||
@@ -1090,9 +1092,9 @@ checksum = "89e25b6adfb930f02d1981565a6e5d9c547ac15a96606256d3b59040e5cd4ca3"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "bigdecimal"
|
name = "bigdecimal"
|
||||||
version = "0.4.7"
|
version = "0.4.8"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "7f31f3af01c5c65a07985c804d3366560e6fa7883d640a122819b14ec327482c"
|
checksum = "1a22f228ab7a1b23027ccc6c350b72868017af7ea8356fbdf19f8d991c690013"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"autocfg",
|
"autocfg",
|
||||||
"libm",
|
"libm",
|
||||||
@@ -1183,9 +1185,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "blake3"
|
name = "blake3"
|
||||||
version = "1.7.0"
|
version = "1.8.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "b17679a8d69b6d7fd9cd9801a536cec9fa5e5970b69f9d4747f70b39b031f5e7"
|
checksum = "389a099b34312839e16420d499a9cad9650541715937ffbdd40d36f49e77eeb3"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"arrayref",
|
"arrayref",
|
||||||
"arrayvec",
|
"arrayvec",
|
||||||
@@ -1329,9 +1331,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "cc"
|
name = "cc"
|
||||||
version = "1.2.17"
|
version = "1.2.19"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "1fcb57c740ae1daf453ae85f16e37396f672b039e00d9d866e07ddb24e328e3a"
|
checksum = "8e3a13707ac958681c13b39b458c073d0d9bc8a22cb1b2f4c8e55eb72c13f362"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"jobserver",
|
"jobserver",
|
||||||
"libc",
|
"libc",
|
||||||
@@ -1627,9 +1629,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "crossbeam-channel"
|
name = "crossbeam-channel"
|
||||||
version = "0.5.14"
|
version = "0.5.15"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "06ba6d68e24814cb8de6bb986db8222d3a027d15872cabc0d18817bc3c0e4471"
|
checksum = "82b8f8f868b36967f9606790d1903570de9ceaf870a7bf9fbbd3016d636a2cb2"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"crossbeam-utils",
|
"crossbeam-utils",
|
||||||
]
|
]
|
||||||
@@ -1767,9 +1769,9 @@ checksum = "ef8ae57c4978a2acd8b869ce6b9ca1dfe817bff704c220209fdef2c0b75a01b9"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "darling"
|
name = "darling"
|
||||||
version = "0.20.10"
|
version = "0.20.11"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "6f63b86c8a8826a49b8c21f08a2d07338eec8d900540f8630dc76284be802989"
|
checksum = "fc7f46116c46ff9ab3eb1597a45688b6715c6e628b5c133e288e709a29bcb4ee"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"darling_core",
|
"darling_core",
|
||||||
"darling_macro",
|
"darling_macro",
|
||||||
@@ -1777,9 +1779,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "darling_core"
|
name = "darling_core"
|
||||||
version = "0.20.10"
|
version = "0.20.11"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "95133861a8032aaea082871032f5815eb9e98cef03fa916ab4500513994df9e5"
|
checksum = "0d00b9596d185e565c2207a0b01f8bd1a135483d02d9b7b0a54b11da8d53412e"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"fnv",
|
"fnv",
|
||||||
"ident_case",
|
"ident_case",
|
||||||
@@ -1791,9 +1793,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "darling_macro"
|
name = "darling_macro"
|
||||||
version = "0.20.10"
|
version = "0.20.11"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "d336a2a514f6ccccaa3e09b02d41d35330c07ddf03a62165fcec10bb561c7806"
|
checksum = "fc34b93ccb385b40dc71c6fceac4b2ad23662c7eeb248cf10d529b7e055b6ead"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"darling_core",
|
"darling_core",
|
||||||
"quote",
|
"quote",
|
||||||
@@ -1914,7 +1916,7 @@ dependencies = [
|
|||||||
"base64 0.22.1",
|
"base64 0.22.1",
|
||||||
"half",
|
"half",
|
||||||
"hashbrown 0.14.5",
|
"hashbrown 0.14.5",
|
||||||
"indexmap 2.8.0",
|
"indexmap 2.9.0",
|
||||||
"libc",
|
"libc",
|
||||||
"log",
|
"log",
|
||||||
"object_store",
|
"object_store",
|
||||||
@@ -2001,7 +2003,7 @@ dependencies = [
|
|||||||
"datafusion-functions-aggregate-common",
|
"datafusion-functions-aggregate-common",
|
||||||
"datafusion-functions-window-common",
|
"datafusion-functions-window-common",
|
||||||
"datafusion-physical-expr-common",
|
"datafusion-physical-expr-common",
|
||||||
"indexmap 2.8.0",
|
"indexmap 2.9.0",
|
||||||
"paste",
|
"paste",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
"sqlparser 0.54.0",
|
"sqlparser 0.54.0",
|
||||||
@@ -2015,7 +2017,7 @@ checksum = "18f0a851a436c5a2139189eb4617a54e6a9ccb9edc96c4b3c83b3bb7c58b950e"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"arrow",
|
"arrow",
|
||||||
"datafusion-common",
|
"datafusion-common",
|
||||||
"indexmap 2.8.0",
|
"indexmap 2.9.0",
|
||||||
"itertools 0.14.0",
|
"itertools 0.14.0",
|
||||||
"paste",
|
"paste",
|
||||||
]
|
]
|
||||||
@@ -2169,7 +2171,7 @@ dependencies = [
|
|||||||
"datafusion-common",
|
"datafusion-common",
|
||||||
"datafusion-expr",
|
"datafusion-expr",
|
||||||
"datafusion-physical-expr",
|
"datafusion-physical-expr",
|
||||||
"indexmap 2.8.0",
|
"indexmap 2.9.0",
|
||||||
"itertools 0.14.0",
|
"itertools 0.14.0",
|
||||||
"log",
|
"log",
|
||||||
"regex",
|
"regex",
|
||||||
@@ -2191,7 +2193,7 @@ dependencies = [
|
|||||||
"datafusion-physical-expr-common",
|
"datafusion-physical-expr-common",
|
||||||
"half",
|
"half",
|
||||||
"hashbrown 0.14.5",
|
"hashbrown 0.14.5",
|
||||||
"indexmap 2.8.0",
|
"indexmap 2.9.0",
|
||||||
"itertools 0.14.0",
|
"itertools 0.14.0",
|
||||||
"log",
|
"log",
|
||||||
"paste",
|
"paste",
|
||||||
@@ -2252,7 +2254,7 @@ dependencies = [
|
|||||||
"futures",
|
"futures",
|
||||||
"half",
|
"half",
|
||||||
"hashbrown 0.14.5",
|
"hashbrown 0.14.5",
|
||||||
"indexmap 2.8.0",
|
"indexmap 2.9.0",
|
||||||
"itertools 0.14.0",
|
"itertools 0.14.0",
|
||||||
"log",
|
"log",
|
||||||
"parking_lot",
|
"parking_lot",
|
||||||
@@ -2270,7 +2272,7 @@ dependencies = [
|
|||||||
"bigdecimal",
|
"bigdecimal",
|
||||||
"datafusion-common",
|
"datafusion-common",
|
||||||
"datafusion-expr",
|
"datafusion-expr",
|
||||||
"indexmap 2.8.0",
|
"indexmap 2.9.0",
|
||||||
"log",
|
"log",
|
||||||
"regex",
|
"regex",
|
||||||
"sqlparser 0.54.0",
|
"sqlparser 0.54.0",
|
||||||
@@ -2308,9 +2310,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "deranged"
|
name = "deranged"
|
||||||
version = "0.4.1"
|
version = "0.4.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "28cfac68e08048ae1883171632c2aef3ebc555621ae56fbccce1cbf22dd7f058"
|
checksum = "9c9e6a11ca8224451684bc0d7d5a7adbf8f2fd6887261a1cfc3c0432f9d4068e"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"powerfmt",
|
"powerfmt",
|
||||||
"serde",
|
"serde",
|
||||||
@@ -2518,9 +2520,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "env_logger"
|
name = "env_logger"
|
||||||
version = "0.11.7"
|
version = "0.11.8"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "c3716d7a920fb4fac5d84e9d4bce8ceb321e9414b4409da61b07b75c1e3d0697"
|
checksum = "13c863f0904021b108aa8b2f55046443e6b1ebde8fd4a15c399893aae4fa069f"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anstream",
|
"anstream",
|
||||||
"anstyle",
|
"anstyle",
|
||||||
@@ -2537,9 +2539,9 @@ checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "errno"
|
name = "errno"
|
||||||
version = "0.3.10"
|
version = "0.3.11"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "33d852cb9b869c2a9b3df2f71a3074817f01e1844f839a144f5fcef059a4eb5d"
|
checksum = "976dd42dc7e85965fe702eb8164f21f450704bdde31faefd6471dba214cb594e"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"libc",
|
"libc",
|
||||||
"windows-sys 0.59.0",
|
"windows-sys 0.59.0",
|
||||||
@@ -2666,9 +2668,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "flate2"
|
name = "flate2"
|
||||||
version = "1.1.0"
|
version = "1.1.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "11faaf5a5236997af9848be0bef4db95824b1d534ebc64d0f0c6cf3e67bd38dc"
|
checksum = "7ced92e76e966ca2fd84c8f7aa01a4aea65b0eb6648d72f7c8f3e2764a67fece"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"crc32fast",
|
"crc32fast",
|
||||||
"miniz_oxide",
|
"miniz_oxide",
|
||||||
@@ -2719,8 +2721,8 @@ checksum = "42703706b716c37f96a77aea830392ad231f44c9e9a67872fa5548707e11b11c"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "fsst"
|
name = "fsst"
|
||||||
version = "0.25.3"
|
version = "0.26.0"
|
||||||
source = "git+https://github.com/lancedb/lance?tag=v0.25.3-beta.2#e0d3179bcc6e6ce5f9c5fcfeb4398789a7005467"
|
source = "git+https://github.com/lancedb/lance?tag=v0.26.0-beta.1#8e46047e2dcb171bec28e28b507a9b7858348773"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"rand 0.8.5",
|
"rand 0.8.5",
|
||||||
]
|
]
|
||||||
@@ -3038,7 +3040,7 @@ dependencies = [
|
|||||||
"futures-sink",
|
"futures-sink",
|
||||||
"futures-util",
|
"futures-util",
|
||||||
"http 0.2.12",
|
"http 0.2.12",
|
||||||
"indexmap 2.8.0",
|
"indexmap 2.9.0",
|
||||||
"slab",
|
"slab",
|
||||||
"tokio",
|
"tokio",
|
||||||
"tokio-util",
|
"tokio-util",
|
||||||
@@ -3057,7 +3059,7 @@ dependencies = [
|
|||||||
"futures-core",
|
"futures-core",
|
||||||
"futures-sink",
|
"futures-sink",
|
||||||
"http 1.3.1",
|
"http 1.3.1",
|
||||||
"indexmap 2.8.0",
|
"indexmap 2.9.0",
|
||||||
"slab",
|
"slab",
|
||||||
"tokio",
|
"tokio",
|
||||||
"tokio-util",
|
"tokio-util",
|
||||||
@@ -3321,7 +3323,7 @@ dependencies = [
|
|||||||
"http 1.3.1",
|
"http 1.3.1",
|
||||||
"hyper 1.6.0",
|
"hyper 1.6.0",
|
||||||
"hyper-util",
|
"hyper-util",
|
||||||
"rustls 0.23.25",
|
"rustls 0.23.26",
|
||||||
"rustls-native-certs 0.8.1",
|
"rustls-native-certs 0.8.1",
|
||||||
"rustls-pki-types",
|
"rustls-pki-types",
|
||||||
"tokio",
|
"tokio",
|
||||||
@@ -3332,9 +3334,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "hyper-util"
|
name = "hyper-util"
|
||||||
version = "0.1.10"
|
version = "0.1.11"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "df2dcfbe0677734ab2f3ffa7fa7bfd4706bfdc1ef393f2ee30184aed67e631b4"
|
checksum = "497bbc33a26fdd4af9ed9c70d63f61cf56a938375fbb32df34db9b1cd6d643f2"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bytes",
|
"bytes",
|
||||||
"futures-channel",
|
"futures-channel",
|
||||||
@@ -3342,6 +3344,7 @@ dependencies = [
|
|||||||
"http 1.3.1",
|
"http 1.3.1",
|
||||||
"http-body 1.0.1",
|
"http-body 1.0.1",
|
||||||
"hyper 1.6.0",
|
"hyper 1.6.0",
|
||||||
|
"libc",
|
||||||
"pin-project-lite",
|
"pin-project-lite",
|
||||||
"socket2",
|
"socket2",
|
||||||
"tokio",
|
"tokio",
|
||||||
@@ -3360,9 +3363,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "iana-time-zone"
|
name = "iana-time-zone"
|
||||||
version = "0.1.62"
|
version = "0.1.63"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "b2fd658b06e56721792c5df4475705b6cda790e9298d19d2f8af083457bcd127"
|
checksum = "b0c919e5debc312ad217002b8048a17b7d83f80703865bbfcfebb0458b0b27d8"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"android_system_properties",
|
"android_system_properties",
|
||||||
"core-foundation-sys",
|
"core-foundation-sys",
|
||||||
@@ -3370,7 +3373,7 @@ dependencies = [
|
|||||||
"js-sys",
|
"js-sys",
|
||||||
"log",
|
"log",
|
||||||
"wasm-bindgen",
|
"wasm-bindgen",
|
||||||
"windows-core 0.52.0",
|
"windows-core 0.61.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -3540,9 +3543,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "indexmap"
|
name = "indexmap"
|
||||||
version = "2.8.0"
|
version = "2.9.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "3954d50fe15b02142bf25d3b8bdadb634ec3948f103d04ffe3031bc8fe9d7058"
|
checksum = "cea70ddb795996207ad57735b50c5982d8844f38ba9ee5f1aedcfb708a2aa11e"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"equivalent",
|
"equivalent",
|
||||||
"hashbrown 0.15.2",
|
"hashbrown 0.15.2",
|
||||||
@@ -3642,9 +3645,9 @@ checksum = "9028f49264629065d057f340a86acb84867925865f73bbf8d47b4d149a7e88b8"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "jiff"
|
name = "jiff"
|
||||||
version = "0.2.5"
|
version = "0.2.6"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "c102670231191d07d37a35af3eb77f1f0dbf7a71be51a962dcd57ea607be7260"
|
checksum = "1f33145a5cbea837164362c7bd596106eb7c5198f97d1ba6f6ebb3223952e488"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"jiff-static",
|
"jiff-static",
|
||||||
"log",
|
"log",
|
||||||
@@ -3655,9 +3658,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "jiff-static"
|
name = "jiff-static"
|
||||||
version = "0.2.5"
|
version = "0.2.6"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "4cdde31a9d349f1b1f51a0b3714a5940ac022976f4b49485fc04be052b183b4c"
|
checksum = "43ce13c40ec6956157a3635d97a1ee2df323b263f09ea14165131289cb0f5c19"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
@@ -3688,10 +3691,11 @@ checksum = "8eaf4bc02d17cbdd7ff4c7438cafcdf7fb9a4613313ad11b4f8fefe7d3fa0130"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "jobserver"
|
name = "jobserver"
|
||||||
version = "0.1.32"
|
version = "0.1.33"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "48d1dbcbbeb6a7fec7e059840aa538bd62aaccf972c7346c4d9d2059312853d0"
|
checksum = "38f262f097c174adebe41eb73d66ae9c06b2844fb0da69969647bbddd9b0538a"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"getrandom 0.3.2",
|
||||||
"libc",
|
"libc",
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -3707,8 +3711,8 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "lance"
|
name = "lance"
|
||||||
version = "0.25.3"
|
version = "0.26.0"
|
||||||
source = "git+https://github.com/lancedb/lance?tag=v0.25.3-beta.2#e0d3179bcc6e6ce5f9c5fcfeb4398789a7005467"
|
source = "git+https://github.com/lancedb/lance?tag=v0.26.0-beta.1#8e46047e2dcb171bec28e28b507a9b7858348773"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"arrow",
|
"arrow",
|
||||||
"arrow-arith",
|
"arrow-arith",
|
||||||
@@ -3734,6 +3738,7 @@ dependencies = [
|
|||||||
"deepsize",
|
"deepsize",
|
||||||
"futures",
|
"futures",
|
||||||
"half",
|
"half",
|
||||||
|
"humantime",
|
||||||
"itertools 0.13.0",
|
"itertools 0.13.0",
|
||||||
"lance-arrow",
|
"lance-arrow",
|
||||||
"lance-core",
|
"lance-core",
|
||||||
@@ -3767,8 +3772,8 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "lance-arrow"
|
name = "lance-arrow"
|
||||||
version = "0.25.3"
|
version = "0.26.0"
|
||||||
source = "git+https://github.com/lancedb/lance?tag=v0.25.3-beta.2#e0d3179bcc6e6ce5f9c5fcfeb4398789a7005467"
|
source = "git+https://github.com/lancedb/lance?tag=v0.26.0-beta.1#8e46047e2dcb171bec28e28b507a9b7858348773"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"arrow-array",
|
"arrow-array",
|
||||||
"arrow-buffer",
|
"arrow-buffer",
|
||||||
@@ -3785,8 +3790,8 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "lance-core"
|
name = "lance-core"
|
||||||
version = "0.25.3"
|
version = "0.26.0"
|
||||||
source = "git+https://github.com/lancedb/lance?tag=v0.25.3-beta.2#e0d3179bcc6e6ce5f9c5fcfeb4398789a7005467"
|
source = "git+https://github.com/lancedb/lance?tag=v0.26.0-beta.1#8e46047e2dcb171bec28e28b507a9b7858348773"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"arrow-array",
|
"arrow-array",
|
||||||
"arrow-buffer",
|
"arrow-buffer",
|
||||||
@@ -3822,8 +3827,8 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "lance-datafusion"
|
name = "lance-datafusion"
|
||||||
version = "0.25.3"
|
version = "0.26.0"
|
||||||
source = "git+https://github.com/lancedb/lance?tag=v0.25.3-beta.2#e0d3179bcc6e6ce5f9c5fcfeb4398789a7005467"
|
source = "git+https://github.com/lancedb/lance?tag=v0.26.0-beta.1#8e46047e2dcb171bec28e28b507a9b7858348773"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"arrow",
|
"arrow",
|
||||||
"arrow-array",
|
"arrow-array",
|
||||||
@@ -3850,8 +3855,8 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "lance-datagen"
|
name = "lance-datagen"
|
||||||
version = "0.25.3"
|
version = "0.26.0"
|
||||||
source = "git+https://github.com/lancedb/lance?tag=v0.25.3-beta.2#e0d3179bcc6e6ce5f9c5fcfeb4398789a7005467"
|
source = "git+https://github.com/lancedb/lance?tag=v0.26.0-beta.1#8e46047e2dcb171bec28e28b507a9b7858348773"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"arrow",
|
"arrow",
|
||||||
"arrow-array",
|
"arrow-array",
|
||||||
@@ -3866,8 +3871,8 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "lance-encoding"
|
name = "lance-encoding"
|
||||||
version = "0.25.3"
|
version = "0.26.0"
|
||||||
source = "git+https://github.com/lancedb/lance?tag=v0.25.3-beta.2#e0d3179bcc6e6ce5f9c5fcfeb4398789a7005467"
|
source = "git+https://github.com/lancedb/lance?tag=v0.26.0-beta.1#8e46047e2dcb171bec28e28b507a9b7858348773"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"arrayref",
|
"arrayref",
|
||||||
"arrow",
|
"arrow",
|
||||||
@@ -3906,8 +3911,8 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "lance-file"
|
name = "lance-file"
|
||||||
version = "0.25.3"
|
version = "0.26.0"
|
||||||
source = "git+https://github.com/lancedb/lance?tag=v0.25.3-beta.2#e0d3179bcc6e6ce5f9c5fcfeb4398789a7005467"
|
source = "git+https://github.com/lancedb/lance?tag=v0.26.0-beta.1#8e46047e2dcb171bec28e28b507a9b7858348773"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"arrow-arith",
|
"arrow-arith",
|
||||||
"arrow-array",
|
"arrow-array",
|
||||||
@@ -3941,8 +3946,8 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "lance-index"
|
name = "lance-index"
|
||||||
version = "0.25.3"
|
version = "0.26.0"
|
||||||
source = "git+https://github.com/lancedb/lance?tag=v0.25.3-beta.2#e0d3179bcc6e6ce5f9c5fcfeb4398789a7005467"
|
source = "git+https://github.com/lancedb/lance?tag=v0.26.0-beta.1#8e46047e2dcb171bec28e28b507a9b7858348773"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"arrow",
|
"arrow",
|
||||||
"arrow-array",
|
"arrow-array",
|
||||||
@@ -3995,8 +4000,8 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "lance-io"
|
name = "lance-io"
|
||||||
version = "0.25.3"
|
version = "0.26.0"
|
||||||
source = "git+https://github.com/lancedb/lance?tag=v0.25.3-beta.2#e0d3179bcc6e6ce5f9c5fcfeb4398789a7005467"
|
source = "git+https://github.com/lancedb/lance?tag=v0.26.0-beta.1#8e46047e2dcb171bec28e28b507a9b7858348773"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"arrow",
|
"arrow",
|
||||||
"arrow-arith",
|
"arrow-arith",
|
||||||
@@ -4034,8 +4039,8 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "lance-linalg"
|
name = "lance-linalg"
|
||||||
version = "0.25.3"
|
version = "0.26.0"
|
||||||
source = "git+https://github.com/lancedb/lance?tag=v0.25.3-beta.2#e0d3179bcc6e6ce5f9c5fcfeb4398789a7005467"
|
source = "git+https://github.com/lancedb/lance?tag=v0.26.0-beta.1#8e46047e2dcb171bec28e28b507a9b7858348773"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"arrow-array",
|
"arrow-array",
|
||||||
"arrow-ord",
|
"arrow-ord",
|
||||||
@@ -4058,8 +4063,8 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "lance-table"
|
name = "lance-table"
|
||||||
version = "0.25.3"
|
version = "0.26.0"
|
||||||
source = "git+https://github.com/lancedb/lance?tag=v0.25.3-beta.2#e0d3179bcc6e6ce5f9c5fcfeb4398789a7005467"
|
source = "git+https://github.com/lancedb/lance?tag=v0.26.0-beta.1#8e46047e2dcb171bec28e28b507a9b7858348773"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"arrow",
|
"arrow",
|
||||||
"arrow-array",
|
"arrow-array",
|
||||||
@@ -4098,8 +4103,8 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "lance-testing"
|
name = "lance-testing"
|
||||||
version = "0.25.3"
|
version = "0.26.0"
|
||||||
source = "git+https://github.com/lancedb/lance?tag=v0.25.3-beta.2#e0d3179bcc6e6ce5f9c5fcfeb4398789a7005467"
|
source = "git+https://github.com/lancedb/lance?tag=v0.26.0-beta.1#8e46047e2dcb171bec28e28b507a9b7858348773"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"arrow-array",
|
"arrow-array",
|
||||||
"arrow-schema",
|
"arrow-schema",
|
||||||
@@ -4110,7 +4115,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "lancedb"
|
name = "lancedb"
|
||||||
version = "0.19.0-beta.3"
|
version = "0.19.0-beta.7"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"arrow",
|
"arrow",
|
||||||
"arrow-array",
|
"arrow-array",
|
||||||
@@ -4197,7 +4202,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "lancedb-node"
|
name = "lancedb-node"
|
||||||
version = "0.19.0-beta.3"
|
version = "0.19.0-beta.7"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"arrow-array",
|
"arrow-array",
|
||||||
"arrow-ipc",
|
"arrow-ipc",
|
||||||
@@ -4222,7 +4227,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "lancedb-nodejs"
|
name = "lancedb-nodejs"
|
||||||
version = "0.19.0-beta.3"
|
version = "0.19.0-beta.7"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"arrow-array",
|
"arrow-array",
|
||||||
"arrow-ipc",
|
"arrow-ipc",
|
||||||
@@ -4240,7 +4245,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "lancedb-python"
|
name = "lancedb-python"
|
||||||
version = "0.22.0-beta.3"
|
version = "0.22.0-beta.7"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"arrow",
|
"arrow",
|
||||||
"env_logger",
|
"env_logger",
|
||||||
@@ -4385,9 +4390,9 @@ checksum = "d26c52dbd32dccf2d10cac7725f8eae5296885fb5703b261f7d0a0739ec807ab"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "linux-raw-sys"
|
name = "linux-raw-sys"
|
||||||
version = "0.9.3"
|
version = "0.9.4"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "fe7db12097d22ec582439daf8618b8fdd1a7bef6270e9af3b1ebcd30893cf413"
|
checksum = "cd945864f07fe9f5371a27ad7b52a172b4b499999f1d97574c9fa68373937e12"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "litemap"
|
name = "litemap"
|
||||||
@@ -4575,9 +4580,9 @@ checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "miniz_oxide"
|
name = "miniz_oxide"
|
||||||
version = "0.8.5"
|
version = "0.8.8"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "8e3e04debbb59698c15bacbb6d93584a8c0ca9cc3213cb423d31f760d8843ce5"
|
checksum = "3be647b768db090acb35d5ec5db2b0e1f1de11133ca123b9eacf5137868f892a"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"adler2",
|
"adler2",
|
||||||
]
|
]
|
||||||
@@ -5134,7 +5139,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "3672b37090dbd86368a4145bc067582552b29c27377cad4e0a306c97f9bd7772"
|
checksum = "3672b37090dbd86368a4145bc067582552b29c27377cad4e0a306c97f9bd7772"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"fixedbitset",
|
"fixedbitset",
|
||||||
"indexmap 2.8.0",
|
"indexmap 2.9.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -5327,7 +5332,7 @@ dependencies = [
|
|||||||
"comfy-table",
|
"comfy-table",
|
||||||
"either",
|
"either",
|
||||||
"hashbrown 0.14.5",
|
"hashbrown 0.14.5",
|
||||||
"indexmap 2.8.0",
|
"indexmap 2.9.0",
|
||||||
"num-traits",
|
"num-traits",
|
||||||
"once_cell",
|
"once_cell",
|
||||||
"polars-arrow",
|
"polars-arrow",
|
||||||
@@ -5425,7 +5430,7 @@ dependencies = [
|
|||||||
"either",
|
"either",
|
||||||
"hashbrown 0.14.5",
|
"hashbrown 0.14.5",
|
||||||
"hex",
|
"hex",
|
||||||
"indexmap 2.8.0",
|
"indexmap 2.9.0",
|
||||||
"memchr",
|
"memchr",
|
||||||
"num-traits",
|
"num-traits",
|
||||||
"polars-arrow",
|
"polars-arrow",
|
||||||
@@ -5569,7 +5574,7 @@ dependencies = [
|
|||||||
"ahash",
|
"ahash",
|
||||||
"bytemuck",
|
"bytemuck",
|
||||||
"hashbrown 0.14.5",
|
"hashbrown 0.14.5",
|
||||||
"indexmap 2.8.0",
|
"indexmap 2.9.0",
|
||||||
"num-traits",
|
"num-traits",
|
||||||
"once_cell",
|
"once_cell",
|
||||||
"polars-error",
|
"polars-error",
|
||||||
@@ -5613,9 +5618,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "prettyplease"
|
name = "prettyplease"
|
||||||
version = "0.2.31"
|
version = "0.2.32"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "5316f57387668042f561aae71480de936257848f9c43ce528e311d89a07cadeb"
|
checksum = "664ec5419c51e34154eec046ebcba56312d5a2fc3b09a06da188e1ad21afadf6"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"syn 2.0.100",
|
"syn 2.0.100",
|
||||||
@@ -5802,9 +5807,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "quick-xml"
|
name = "quick-xml"
|
||||||
version = "0.37.3"
|
version = "0.37.4"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "bf763ab1c7a3aa408be466efc86efe35ed1bd3dd74173ed39d6b0d0a6f0ba148"
|
checksum = "a4ce8c88de324ff838700f36fb6ab86c96df0e3c4ab6ef3a9b2044465cce1369"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"memchr",
|
"memchr",
|
||||||
"serde",
|
"serde",
|
||||||
@@ -5822,7 +5827,7 @@ dependencies = [
|
|||||||
"quinn-proto",
|
"quinn-proto",
|
||||||
"quinn-udp",
|
"quinn-udp",
|
||||||
"rustc-hash 2.1.1",
|
"rustc-hash 2.1.1",
|
||||||
"rustls 0.23.25",
|
"rustls 0.23.26",
|
||||||
"socket2",
|
"socket2",
|
||||||
"thiserror 2.0.12",
|
"thiserror 2.0.12",
|
||||||
"tokio",
|
"tokio",
|
||||||
@@ -5841,7 +5846,7 @@ dependencies = [
|
|||||||
"rand 0.9.0",
|
"rand 0.9.0",
|
||||||
"ring",
|
"ring",
|
||||||
"rustc-hash 2.1.1",
|
"rustc-hash 2.1.1",
|
||||||
"rustls 0.23.25",
|
"rustls 0.23.26",
|
||||||
"rustls-pki-types",
|
"rustls-pki-types",
|
||||||
"slab",
|
"slab",
|
||||||
"thiserror 2.0.12",
|
"thiserror 2.0.12",
|
||||||
@@ -6061,9 +6066,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "redox_syscall"
|
name = "redox_syscall"
|
||||||
version = "0.5.10"
|
version = "0.5.11"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "0b8c0c260b63a8219631167be35e6a988e9554dbd323f8bd08439c8ed1302bd1"
|
checksum = "d2f103c6d277498fbceb16e84d317e2a400f160f46904d5f5410848c829511a3"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bitflags 2.9.0",
|
"bitflags 2.9.0",
|
||||||
]
|
]
|
||||||
@@ -6163,7 +6168,7 @@ dependencies = [
|
|||||||
"percent-encoding",
|
"percent-encoding",
|
||||||
"pin-project-lite",
|
"pin-project-lite",
|
||||||
"quinn",
|
"quinn",
|
||||||
"rustls 0.23.25",
|
"rustls 0.23.26",
|
||||||
"rustls-native-certs 0.8.1",
|
"rustls-native-certs 0.8.1",
|
||||||
"rustls-pemfile 2.2.0",
|
"rustls-pemfile 2.2.0",
|
||||||
"rustls-pki-types",
|
"rustls-pki-types",
|
||||||
@@ -6229,9 +6234,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "roaring"
|
name = "roaring"
|
||||||
version = "0.10.10"
|
version = "0.10.12"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "a652edd001c53df0b3f96a36a8dc93fce6866988efc16808235653c6bcac8bf2"
|
checksum = "19e8d2cfa184d94d0726d650a9f4a1be7f9b76ac9fdb954219878dc00c1c1e7b"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bytemuck",
|
"bytemuck",
|
||||||
"byteorder",
|
"byteorder",
|
||||||
@@ -6319,14 +6324,14 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rustix"
|
name = "rustix"
|
||||||
version = "1.0.3"
|
version = "1.0.5"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "e56a18552996ac8d29ecc3b190b4fdbb2d91ca4ec396de7bbffaf43f3d637e96"
|
checksum = "d97817398dd4bb2e6da002002db259209759911da105da92bec29ccb12cf58bf"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bitflags 2.9.0",
|
"bitflags 2.9.0",
|
||||||
"errno",
|
"errno",
|
||||||
"libc",
|
"libc",
|
||||||
"linux-raw-sys 0.9.3",
|
"linux-raw-sys 0.9.4",
|
||||||
"windows-sys 0.59.0",
|
"windows-sys 0.59.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -6344,9 +6349,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rustls"
|
name = "rustls"
|
||||||
version = "0.23.25"
|
version = "0.23.26"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "822ee9188ac4ec04a2f0531e55d035fb2de73f18b41a63c70c2712503b6fb13c"
|
checksum = "df51b5869f3a441595eac5e8ff14d486ff285f7b8c0df8770e49c3b56351f0f0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"aws-lc-rs",
|
"aws-lc-rs",
|
||||||
"log",
|
"log",
|
||||||
@@ -6643,7 +6648,7 @@ dependencies = [
|
|||||||
"chrono",
|
"chrono",
|
||||||
"hex",
|
"hex",
|
||||||
"indexmap 1.9.3",
|
"indexmap 1.9.3",
|
||||||
"indexmap 2.8.0",
|
"indexmap 2.9.0",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_derive",
|
"serde_derive",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
@@ -6760,9 +6765,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "smallvec"
|
name = "smallvec"
|
||||||
version = "1.14.0"
|
version = "1.15.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "7fcf8323ef1faaee30a44a340193b1ac6814fd9b7b4e88e9d4519a3e4abe1cfd"
|
checksum = "8917285742e9f3e1683f0a9c4e6b57960b7314d0b08d30d1ecd426713ee2eee9"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "smartstring"
|
name = "smartstring"
|
||||||
@@ -6798,9 +6803,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "socket2"
|
name = "socket2"
|
||||||
version = "0.5.8"
|
version = "0.5.9"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "c970269d99b64e60ec3bd6ad27270092a5394c4e309314b18ae3fe575695fbe8"
|
checksum = "4f5fd57c80058a56cf5c777ab8a126398ece8e442983605d280a44ce79d0edef"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"libc",
|
"libc",
|
||||||
"windows-sys 0.52.0",
|
"windows-sys 0.52.0",
|
||||||
@@ -7229,7 +7234,7 @@ dependencies = [
|
|||||||
"fastrand",
|
"fastrand",
|
||||||
"getrandom 0.3.2",
|
"getrandom 0.3.2",
|
||||||
"once_cell",
|
"once_cell",
|
||||||
"rustix 1.0.3",
|
"rustix 1.0.5",
|
||||||
"windows-sys 0.59.0",
|
"windows-sys 0.59.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -7382,9 +7387,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tokio"
|
name = "tokio"
|
||||||
version = "1.44.1"
|
version = "1.44.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "f382da615b842244d4b8738c82ed1275e6c5dd90c459a30941cd07080b06c91a"
|
checksum = "e6b88822cbe49de4185e3a4cbf8321dd487cf5fe0c5c65695fef6346371e9c48"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"backtrace",
|
"backtrace",
|
||||||
"bytes",
|
"bytes",
|
||||||
@@ -7424,7 +7429,7 @@ version = "0.26.2"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "8e727b36a1a0e8b74c376ac2211e40c2c8af09fb4013c60d910495810f008e9b"
|
checksum = "8e727b36a1a0e8b74c376ac2211e40c2c8af09fb4013c60d910495810f008e9b"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"rustls 0.23.25",
|
"rustls 0.23.26",
|
||||||
"tokio",
|
"tokio",
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -7464,7 +7469,7 @@ version = "0.22.24"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "17b4795ff5edd201c7cd6dca065ae59972ce77d1b80fa0a84d94950ece7d1474"
|
checksum = "17b4795ff5edd201c7cd6dca065ae59972ce77d1b80fa0a84d94950ece7d1474"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"indexmap 2.8.0",
|
"indexmap 2.9.0",
|
||||||
"toml_datetime",
|
"toml_datetime",
|
||||||
"winnow",
|
"winnow",
|
||||||
]
|
]
|
||||||
@@ -7649,7 +7654,7 @@ dependencies = [
|
|||||||
"flate2",
|
"flate2",
|
||||||
"log",
|
"log",
|
||||||
"once_cell",
|
"once_cell",
|
||||||
"rustls 0.23.25",
|
"rustls 0.23.26",
|
||||||
"rustls-pki-types",
|
"rustls-pki-types",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
@@ -7954,13 +7959,26 @@ version = "0.58.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "6ba6d44ec8c2591c134257ce647b7ea6b20335bf6379a27dac5f1641fcf59f99"
|
checksum = "6ba6d44ec8c2591c134257ce647b7ea6b20335bf6379a27dac5f1641fcf59f99"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"windows-implement",
|
"windows-implement 0.58.0",
|
||||||
"windows-interface",
|
"windows-interface 0.58.0",
|
||||||
"windows-result 0.2.0",
|
"windows-result 0.2.0",
|
||||||
"windows-strings 0.1.0",
|
"windows-strings 0.1.0",
|
||||||
"windows-targets 0.52.6",
|
"windows-targets 0.52.6",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows-core"
|
||||||
|
version = "0.61.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "4763c1de310c86d75a878046489e2e5ba02c649d185f21c67d4cf8a56d098980"
|
||||||
|
dependencies = [
|
||||||
|
"windows-implement 0.60.0",
|
||||||
|
"windows-interface 0.59.1",
|
||||||
|
"windows-link",
|
||||||
|
"windows-result 0.3.2",
|
||||||
|
"windows-strings 0.4.0",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "windows-implement"
|
name = "windows-implement"
|
||||||
version = "0.58.0"
|
version = "0.58.0"
|
||||||
@@ -7972,6 +7990,17 @@ dependencies = [
|
|||||||
"syn 2.0.100",
|
"syn 2.0.100",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows-implement"
|
||||||
|
version = "0.60.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a47fddd13af08290e67f4acabf4b459f647552718f683a7b415d290ac744a836"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn 2.0.100",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "windows-interface"
|
name = "windows-interface"
|
||||||
version = "0.58.0"
|
version = "0.58.0"
|
||||||
@@ -7983,6 +8012,17 @@ dependencies = [
|
|||||||
"syn 2.0.100",
|
"syn 2.0.100",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows-interface"
|
||||||
|
version = "0.59.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "bd9211b69f8dcdfa817bfd14bf1c97c9188afa36f4750130fcdf3f400eca9fa8"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn 2.0.100",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "windows-link"
|
name = "windows-link"
|
||||||
version = "0.1.1"
|
version = "0.1.1"
|
||||||
@@ -8037,6 +8077,15 @@ dependencies = [
|
|||||||
"windows-link",
|
"windows-link",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows-strings"
|
||||||
|
version = "0.4.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "7a2ba9642430ee452d5a7aa78d72907ebe8cfda358e8cb7918a2050581322f97"
|
||||||
|
dependencies = [
|
||||||
|
"windows-link",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "windows-sys"
|
name = "windows-sys"
|
||||||
version = "0.45.0"
|
version = "0.45.0"
|
||||||
@@ -8317,9 +8366,9 @@ checksum = "271414315aff87387382ec3d271b52d7ae78726f5d44ac98b4f4030c91880486"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "winnow"
|
name = "winnow"
|
||||||
version = "0.7.4"
|
version = "0.7.6"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "0e97b544156e9bebe1a0ffbc03484fc1ffe3100cbce3ffb17eac35f7cdd7ab36"
|
checksum = "63d3fcd9bba44b03821e7d699eeee959f3126dcc4aa8e4ae18ec617c2a5cea10"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"memchr",
|
"memchr",
|
||||||
]
|
]
|
||||||
@@ -8489,7 +8538,7 @@ dependencies = [
|
|||||||
"crc32fast",
|
"crc32fast",
|
||||||
"crossbeam-utils",
|
"crossbeam-utils",
|
||||||
"displaydoc",
|
"displaydoc",
|
||||||
"indexmap 2.8.0",
|
"indexmap 2.9.0",
|
||||||
"num_enum",
|
"num_enum",
|
||||||
"thiserror 1.0.69",
|
"thiserror 1.0.69",
|
||||||
]
|
]
|
||||||
|
|||||||
18
Cargo.toml
18
Cargo.toml
@@ -21,16 +21,16 @@ categories = ["database-implementations"]
|
|||||||
rust-version = "1.78.0"
|
rust-version = "1.78.0"
|
||||||
|
|
||||||
[workspace.dependencies]
|
[workspace.dependencies]
|
||||||
lance = { "version" = "=0.25.3", "features" = [
|
lance = { "version" = "=0.26.0", "features" = [
|
||||||
"dynamodb",
|
"dynamodb",
|
||||||
], tag = "v0.25.3-beta.2", git = "https://github.com/lancedb/lance" }
|
], tag = "v0.26.0-beta.1", git = "https://github.com/lancedb/lance" }
|
||||||
lance-io = { version = "=0.25.3", tag = "v0.25.3-beta.2", git = "https://github.com/lancedb/lance" }
|
lance-io = { version = "=0.26.0", tag = "v0.26.0-beta.1", git = "https://github.com/lancedb/lance" }
|
||||||
lance-index = { version = "=0.25.3", tag = "v0.25.3-beta.2", git = "https://github.com/lancedb/lance" }
|
lance-index = { version = "=0.26.0", tag = "v0.26.0-beta.1", git = "https://github.com/lancedb/lance" }
|
||||||
lance-linalg = { version = "=0.25.3", tag = "v0.25.3-beta.2", git = "https://github.com/lancedb/lance" }
|
lance-linalg = { version = "=0.26.0", tag = "v0.26.0-beta.1", git = "https://github.com/lancedb/lance" }
|
||||||
lance-table = { version = "=0.25.3", tag = "v0.25.3-beta.2", git = "https://github.com/lancedb/lance" }
|
lance-table = { version = "=0.26.0", tag = "v0.26.0-beta.1", git = "https://github.com/lancedb/lance" }
|
||||||
lance-testing = { version = "=0.25.3", tag = "v0.25.3-beta.2", git = "https://github.com/lancedb/lance" }
|
lance-testing = { version = "=0.26.0", tag = "v0.26.0-beta.1", git = "https://github.com/lancedb/lance" }
|
||||||
lance-datafusion = { version = "=0.25.3", tag = "v0.25.3-beta.2", git = "https://github.com/lancedb/lance" }
|
lance-datafusion = { version = "=0.26.0", tag = "v0.26.0-beta.1", git = "https://github.com/lancedb/lance" }
|
||||||
lance-encoding = { version = "=0.25.3", tag = "v0.25.3-beta.2", git = "https://github.com/lancedb/lance" }
|
lance-encoding = { version = "=0.26.0", tag = "v0.26.0-beta.1", git = "https://github.com/lancedb/lance" }
|
||||||
# Note that this one does not include pyarrow
|
# Note that this one does not include pyarrow
|
||||||
arrow = { version = "54.1", optional = false }
|
arrow = { version = "54.1", optional = false }
|
||||||
arrow-array = "54.1"
|
arrow-array = "54.1"
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
LanceDB docs are deployed to https://lancedb.github.io/lancedb/.
|
LanceDB docs are deployed to https://lancedb.github.io/lancedb/.
|
||||||
|
|
||||||
Docs is built and deployed automatically by [Github Actions](.github/workflows/docs.yml)
|
Docs is built and deployed automatically by [Github Actions](../.github/workflows/docs.yml)
|
||||||
whenever a commit is pushed to the `main` branch. So it is possible for the docs to show
|
whenever a commit is pushed to the `main` branch. So it is possible for the docs to show
|
||||||
unreleased features.
|
unreleased features.
|
||||||
|
|
||||||
|
|||||||
@@ -342,7 +342,7 @@ For **read and write access**, LanceDB will need a policy such as:
|
|||||||
"Action": [
|
"Action": [
|
||||||
"s3:PutObject",
|
"s3:PutObject",
|
||||||
"s3:GetObject",
|
"s3:GetObject",
|
||||||
"s3:DeleteObject",
|
"s3:DeleteObject"
|
||||||
],
|
],
|
||||||
"Resource": "arn:aws:s3:::<bucket>/<prefix>/*"
|
"Resource": "arn:aws:s3:::<bucket>/<prefix>/*"
|
||||||
},
|
},
|
||||||
@@ -374,7 +374,7 @@ For **read-only access**, LanceDB will need a policy such as:
|
|||||||
{
|
{
|
||||||
"Effect": "Allow",
|
"Effect": "Allow",
|
||||||
"Action": [
|
"Action": [
|
||||||
"s3:GetObject",
|
"s3:GetObject"
|
||||||
],
|
],
|
||||||
"Resource": "arn:aws:s3:::<bucket>/<prefix>/*"
|
"Resource": "arn:aws:s3:::<bucket>/<prefix>/*"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1001,11 +1001,9 @@ In LanceDB OSS, users can set the `read_consistency_interval` parameter on conne
|
|||||||
|
|
||||||
There are three possible settings for `read_consistency_interval`:
|
There are three possible settings for `read_consistency_interval`:
|
||||||
|
|
||||||
1. **Unset**: The database does not check for updates to tables made by other processes. This setting is suitable for applications where the data does not change during the lifetime of the table reference.
|
1. **Unset (default)**: The database does not check for updates to tables made by other processes. This provides the best query performance, but means that clients may not see the most up-to-date data. This setting is suitable for applications where the data does not change during the lifetime of the table reference.
|
||||||
2. **Zero seconds (Strong consistency)**: The database checks for updates on every read. This provides the strongest consistency guarantees, ensuring that all clients see the latest committed data. However, it has the most overhead. This setting is suitable when consistency matters more than having high QPS. For best performance, combine this setting with the storage option `new_table_enable_v2_manifest_paths` set to `true`.
|
2. **Zero seconds (Strong consistency)**: The database checks for updates on every read. This provides the strongest consistency guarantees, ensuring that all clients see the latest committed data. However, it has the most overhead. This setting is suitable when consistency matters more than having high QPS.
|
||||||
3. **Custom interval (Eventual consistency, the default)**: The database checks for updates at a custom interval. By default, this is every 5 seconds. This provides eventual consistency, allowing for some lag between write and read operations. Performance wise, this is a middle ground between strong consistency and no consistency check. This setting is suitable for applications where immediate consistency is not critical, but clients should see updated data eventually.
|
3. **Custom interval (Eventual consistency)**: The database checks for updates at a custom interval, such as every 5 seconds. This provides eventual consistency, allowing for some lag between write and read operations. Performance wise, this is a middle ground between strong consistency and no consistency check. This setting is suitable for applications where immediate consistency is not critical, but clients should see updated data eventually.
|
||||||
|
|
||||||
You can always force a synchronization by calling `checkout_latest()` / `checkoutLatest()` on a table.
|
|
||||||
|
|
||||||
!!! tip "Consistency in LanceDB Cloud"
|
!!! tip "Consistency in LanceDB Cloud"
|
||||||
|
|
||||||
@@ -1043,21 +1041,7 @@ You can always force a synchronization by calling `checkout_latest()` / `checkou
|
|||||||
--8<-- "python/python/tests/docs/test_guide_tables.py:table_async_eventual_consistency"
|
--8<-- "python/python/tests/docs/test_guide_tables.py:table_async_eventual_consistency"
|
||||||
```
|
```
|
||||||
|
|
||||||
For no consistency, use `None`:
|
By default, a `Table` will never check for updates from other writers. To manually check for updates you can use `checkout_latest`:
|
||||||
|
|
||||||
=== "Sync API"
|
|
||||||
|
|
||||||
```python
|
|
||||||
--8<-- "python/python/tests/docs/test_guide_tables.py:table_no_consistency"
|
|
||||||
```
|
|
||||||
|
|
||||||
=== "Async API"
|
|
||||||
|
|
||||||
```python
|
|
||||||
--8<-- "python/python/tests/docs/test_guide_tables.py:table_async_no_consistency"
|
|
||||||
```
|
|
||||||
|
|
||||||
To manually check for updates you can use `checkout_latest`:
|
|
||||||
|
|
||||||
=== "Sync API"
|
=== "Sync API"
|
||||||
|
|
||||||
@@ -1075,25 +1059,15 @@ You can always force a synchronization by calling `checkout_latest()` / `checkou
|
|||||||
To set strong consistency, use `0`:
|
To set strong consistency, use `0`:
|
||||||
|
|
||||||
```ts
|
```ts
|
||||||
--8<-- "nodejs/examples/basic.test.ts:table_strong_consistency"
|
const db = await lancedb.connect({ uri: "./.lancedb", readConsistencyInterval: 0 });
|
||||||
|
const tbl = await db.openTable("my_table");
|
||||||
```
|
```
|
||||||
|
|
||||||
For eventual consistency, specify the update interval as seconds:
|
For eventual consistency, specify the update interval as seconds:
|
||||||
|
|
||||||
```ts
|
```ts
|
||||||
--8<-- "nodejs/examples/basic.test.ts:table_eventual_consistency"
|
const db = await lancedb.connect({ uri: "./.lancedb", readConsistencyInterval: 5 });
|
||||||
```
|
const tbl = await db.openTable("my_table");
|
||||||
|
|
||||||
For no consistency, use `null`:
|
|
||||||
|
|
||||||
```ts
|
|
||||||
--8<-- "nodejs/examples/basic.test.ts:table_no_consistency"
|
|
||||||
```
|
|
||||||
|
|
||||||
To manually check for updates you can use `checkoutLatest`:
|
|
||||||
|
|
||||||
```ts
|
|
||||||
--8<-- "nodejs/examples/basic.test.ts:table_checkout_latest"
|
|
||||||
```
|
```
|
||||||
|
|
||||||
<!-- Node doesn't yet support the version time travel: https://github.com/lancedb/lancedb/issues/1007
|
<!-- Node doesn't yet support the version time travel: https://github.com/lancedb/lancedb/issues/1007
|
||||||
|
|||||||
@@ -22,10 +22,13 @@ including methods to retrieve the query type and convert the query to a dictiona
|
|||||||
new BoostQuery(
|
new BoostQuery(
|
||||||
positive,
|
positive,
|
||||||
negative,
|
negative,
|
||||||
negativeBoost): BoostQuery
|
options?): BoostQuery
|
||||||
```
|
```
|
||||||
|
|
||||||
Creates an instance of BoostQuery.
|
Creates an instance of BoostQuery.
|
||||||
|
The boost returns documents that match the positive query,
|
||||||
|
but penalizes those that match the negative query.
|
||||||
|
the penalty is controlled by the `negativeBoost` parameter.
|
||||||
|
|
||||||
#### Parameters
|
#### Parameters
|
||||||
|
|
||||||
@@ -35,8 +38,11 @@ Creates an instance of BoostQuery.
|
|||||||
* **negative**: [`FullTextQuery`](../interfaces/FullTextQuery.md)
|
* **negative**: [`FullTextQuery`](../interfaces/FullTextQuery.md)
|
||||||
The negative query that reduces the relevance score.
|
The negative query that reduces the relevance score.
|
||||||
|
|
||||||
* **negativeBoost**: `number`
|
* **options?**
|
||||||
The factor by which the negative query reduces the score.
|
Optional parameters for the boost query.
|
||||||
|
- `negativeBoost`: The boost factor for the negative query (default is 0.0).
|
||||||
|
|
||||||
|
* **options.negativeBoost?**: `number`
|
||||||
|
|
||||||
#### Returns
|
#### Returns
|
||||||
|
|
||||||
@@ -50,6 +56,8 @@ Creates an instance of BoostQuery.
|
|||||||
queryType(): FullTextQueryType
|
queryType(): FullTextQueryType
|
||||||
```
|
```
|
||||||
|
|
||||||
|
The type of the full-text query.
|
||||||
|
|
||||||
#### Returns
|
#### Returns
|
||||||
|
|
||||||
[`FullTextQueryType`](../enumerations/FullTextQueryType.md)
|
[`FullTextQueryType`](../enumerations/FullTextQueryType.md)
|
||||||
@@ -57,19 +65,3 @@ queryType(): FullTextQueryType
|
|||||||
#### Implementation of
|
#### Implementation of
|
||||||
|
|
||||||
[`FullTextQuery`](../interfaces/FullTextQuery.md).[`queryType`](../interfaces/FullTextQuery.md#querytype)
|
[`FullTextQuery`](../interfaces/FullTextQuery.md).[`queryType`](../interfaces/FullTextQuery.md#querytype)
|
||||||
|
|
||||||
***
|
|
||||||
|
|
||||||
### toDict()
|
|
||||||
|
|
||||||
```ts
|
|
||||||
toDict(): Record<string, unknown>
|
|
||||||
```
|
|
||||||
|
|
||||||
#### Returns
|
|
||||||
|
|
||||||
`Record`<`string`, `unknown`>
|
|
||||||
|
|
||||||
#### Implementation of
|
|
||||||
|
|
||||||
[`FullTextQuery`](../interfaces/FullTextQuery.md).[`toDict`](../interfaces/FullTextQuery.md#todict)
|
|
||||||
|
|||||||
@@ -22,9 +22,7 @@ including methods to retrieve the query type and convert the query to a dictiona
|
|||||||
new MatchQuery(
|
new MatchQuery(
|
||||||
query,
|
query,
|
||||||
column,
|
column,
|
||||||
boost,
|
options?): MatchQuery
|
||||||
fuzziness,
|
|
||||||
maxExpansions): MatchQuery
|
|
||||||
```
|
```
|
||||||
|
|
||||||
Creates an instance of MatchQuery.
|
Creates an instance of MatchQuery.
|
||||||
@@ -37,14 +35,17 @@ Creates an instance of MatchQuery.
|
|||||||
* **column**: `string`
|
* **column**: `string`
|
||||||
The name of the column to search within.
|
The name of the column to search within.
|
||||||
|
|
||||||
* **boost**: `number` = `1.0`
|
* **options?**
|
||||||
(Optional) The boost factor to influence the relevance score of this query. Default is `1.0`.
|
Optional parameters for the match query.
|
||||||
|
- `boost`: The boost factor for the query (default is 1.0).
|
||||||
|
- `fuzziness`: The fuzziness level for the query (default is 0).
|
||||||
|
- `maxExpansions`: The maximum number of terms to consider for fuzzy matching (default is 50).
|
||||||
|
|
||||||
* **fuzziness**: `number` = `0`
|
* **options.boost?**: `number`
|
||||||
(Optional) The allowed edit distance for fuzzy matching. Default is `0`.
|
|
||||||
|
|
||||||
* **maxExpansions**: `number` = `50`
|
* **options.fuzziness?**: `number`
|
||||||
(Optional) The maximum number of terms to consider for fuzzy matching. Default is `50`.
|
|
||||||
|
* **options.maxExpansions?**: `number`
|
||||||
|
|
||||||
#### Returns
|
#### Returns
|
||||||
|
|
||||||
@@ -58,6 +59,8 @@ Creates an instance of MatchQuery.
|
|||||||
queryType(): FullTextQueryType
|
queryType(): FullTextQueryType
|
||||||
```
|
```
|
||||||
|
|
||||||
|
The type of the full-text query.
|
||||||
|
|
||||||
#### Returns
|
#### Returns
|
||||||
|
|
||||||
[`FullTextQueryType`](../enumerations/FullTextQueryType.md)
|
[`FullTextQueryType`](../enumerations/FullTextQueryType.md)
|
||||||
@@ -65,19 +68,3 @@ queryType(): FullTextQueryType
|
|||||||
#### Implementation of
|
#### Implementation of
|
||||||
|
|
||||||
[`FullTextQuery`](../interfaces/FullTextQuery.md).[`queryType`](../interfaces/FullTextQuery.md#querytype)
|
[`FullTextQuery`](../interfaces/FullTextQuery.md).[`queryType`](../interfaces/FullTextQuery.md#querytype)
|
||||||
|
|
||||||
***
|
|
||||||
|
|
||||||
### toDict()
|
|
||||||
|
|
||||||
```ts
|
|
||||||
toDict(): Record<string, unknown>
|
|
||||||
```
|
|
||||||
|
|
||||||
#### Returns
|
|
||||||
|
|
||||||
`Record`<`string`, `unknown`>
|
|
||||||
|
|
||||||
#### Implementation of
|
|
||||||
|
|
||||||
[`FullTextQuery`](../interfaces/FullTextQuery.md).[`toDict`](../interfaces/FullTextQuery.md#todict)
|
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ including methods to retrieve the query type and convert the query to a dictiona
|
|||||||
new MultiMatchQuery(
|
new MultiMatchQuery(
|
||||||
query,
|
query,
|
||||||
columns,
|
columns,
|
||||||
boosts): MultiMatchQuery
|
options?): MultiMatchQuery
|
||||||
```
|
```
|
||||||
|
|
||||||
Creates an instance of MultiMatchQuery.
|
Creates an instance of MultiMatchQuery.
|
||||||
@@ -35,10 +35,11 @@ Creates an instance of MultiMatchQuery.
|
|||||||
* **columns**: `string`[]
|
* **columns**: `string`[]
|
||||||
An array of column names to search within.
|
An array of column names to search within.
|
||||||
|
|
||||||
* **boosts**: `number`[] = `...`
|
* **options?**
|
||||||
(Optional) An array of boost factors corresponding to each column. Default is an array of 1.0 for each column.
|
Optional parameters for the multi-match query.
|
||||||
The `boosts` array should have the same length as `columns`. If not provided, all columns will have a default boost of 1.0.
|
- `boosts`: An array of boost factors for each column (default is 1.0 for all).
|
||||||
If the length of `boosts` is less than `columns`, it will be padded with 1.0s.
|
|
||||||
|
* **options.boosts?**: `number`[]
|
||||||
|
|
||||||
#### Returns
|
#### Returns
|
||||||
|
|
||||||
@@ -52,6 +53,8 @@ Creates an instance of MultiMatchQuery.
|
|||||||
queryType(): FullTextQueryType
|
queryType(): FullTextQueryType
|
||||||
```
|
```
|
||||||
|
|
||||||
|
The type of the full-text query.
|
||||||
|
|
||||||
#### Returns
|
#### Returns
|
||||||
|
|
||||||
[`FullTextQueryType`](../enumerations/FullTextQueryType.md)
|
[`FullTextQueryType`](../enumerations/FullTextQueryType.md)
|
||||||
@@ -59,19 +62,3 @@ queryType(): FullTextQueryType
|
|||||||
#### Implementation of
|
#### Implementation of
|
||||||
|
|
||||||
[`FullTextQuery`](../interfaces/FullTextQuery.md).[`queryType`](../interfaces/FullTextQuery.md#querytype)
|
[`FullTextQuery`](../interfaces/FullTextQuery.md).[`queryType`](../interfaces/FullTextQuery.md#querytype)
|
||||||
|
|
||||||
***
|
|
||||||
|
|
||||||
### toDict()
|
|
||||||
|
|
||||||
```ts
|
|
||||||
toDict(): Record<string, unknown>
|
|
||||||
```
|
|
||||||
|
|
||||||
#### Returns
|
|
||||||
|
|
||||||
`Record`<`string`, `unknown`>
|
|
||||||
|
|
||||||
#### Implementation of
|
|
||||||
|
|
||||||
[`FullTextQuery`](../interfaces/FullTextQuery.md).[`toDict`](../interfaces/FullTextQuery.md#todict)
|
|
||||||
|
|||||||
@@ -44,6 +44,8 @@ Creates an instance of `PhraseQuery`.
|
|||||||
queryType(): FullTextQueryType
|
queryType(): FullTextQueryType
|
||||||
```
|
```
|
||||||
|
|
||||||
|
The type of the full-text query.
|
||||||
|
|
||||||
#### Returns
|
#### Returns
|
||||||
|
|
||||||
[`FullTextQueryType`](../enumerations/FullTextQueryType.md)
|
[`FullTextQueryType`](../enumerations/FullTextQueryType.md)
|
||||||
@@ -51,19 +53,3 @@ queryType(): FullTextQueryType
|
|||||||
#### Implementation of
|
#### Implementation of
|
||||||
|
|
||||||
[`FullTextQuery`](../interfaces/FullTextQuery.md).[`queryType`](../interfaces/FullTextQuery.md#querytype)
|
[`FullTextQuery`](../interfaces/FullTextQuery.md).[`queryType`](../interfaces/FullTextQuery.md#querytype)
|
||||||
|
|
||||||
***
|
|
||||||
|
|
||||||
### toDict()
|
|
||||||
|
|
||||||
```ts
|
|
||||||
toDict(): Record<string, unknown>
|
|
||||||
```
|
|
||||||
|
|
||||||
#### Returns
|
|
||||||
|
|
||||||
`Record`<`string`, `unknown`>
|
|
||||||
|
|
||||||
#### Implementation of
|
|
||||||
|
|
||||||
[`FullTextQuery`](../interfaces/FullTextQuery.md).[`toDict`](../interfaces/FullTextQuery.md#todict)
|
|
||||||
|
|||||||
@@ -454,6 +454,28 @@ Modeled after ``VACUUM`` in PostgreSQL.
|
|||||||
|
|
||||||
***
|
***
|
||||||
|
|
||||||
|
### prewarmIndex()
|
||||||
|
|
||||||
|
```ts
|
||||||
|
abstract prewarmIndex(name): Promise<void>
|
||||||
|
```
|
||||||
|
|
||||||
|
Prewarm an index in the table.
|
||||||
|
|
||||||
|
#### Parameters
|
||||||
|
|
||||||
|
* **name**: `string`
|
||||||
|
The name of the index.
|
||||||
|
This will load the index into memory. This may reduce the cold-start time for
|
||||||
|
future queries. If the index does not fit in the cache then this call may be
|
||||||
|
wasteful.
|
||||||
|
|
||||||
|
#### Returns
|
||||||
|
|
||||||
|
`Promise`<`void`>
|
||||||
|
|
||||||
|
***
|
||||||
|
|
||||||
### query()
|
### query()
|
||||||
|
|
||||||
```ts
|
```ts
|
||||||
@@ -575,7 +597,7 @@ of the given query
|
|||||||
|
|
||||||
#### Parameters
|
#### Parameters
|
||||||
|
|
||||||
* **query**: `string` \| [`IntoVector`](../type-aliases/IntoVector.md)
|
* **query**: `string` \| [`IntoVector`](../type-aliases/IntoVector.md) \| [`FullTextQuery`](../interfaces/FullTextQuery.md)
|
||||||
the query, a vector or string
|
the query, a vector or string
|
||||||
|
|
||||||
* **queryType?**: `string`
|
* **queryType?**: `string`
|
||||||
|
|||||||
@@ -44,7 +44,7 @@ for testing purposes.
|
|||||||
### readConsistencyInterval?
|
### readConsistencyInterval?
|
||||||
|
|
||||||
```ts
|
```ts
|
||||||
optional readConsistencyInterval: null | number;
|
optional readConsistencyInterval: number;
|
||||||
```
|
```
|
||||||
|
|
||||||
(For LanceDB OSS only): The interval, in seconds, at which to check for
|
(For LanceDB OSS only): The interval, in seconds, at which to check for
|
||||||
|
|||||||
@@ -18,18 +18,8 @@ including methods to retrieve the query type and convert the query to a dictiona
|
|||||||
queryType(): FullTextQueryType
|
queryType(): FullTextQueryType
|
||||||
```
|
```
|
||||||
|
|
||||||
|
The type of the full-text query.
|
||||||
|
|
||||||
#### Returns
|
#### Returns
|
||||||
|
|
||||||
[`FullTextQueryType`](../enumerations/FullTextQueryType.md)
|
[`FullTextQueryType`](../enumerations/FullTextQueryType.md)
|
||||||
|
|
||||||
***
|
|
||||||
|
|
||||||
### toDict()
|
|
||||||
|
|
||||||
```ts
|
|
||||||
toDict(): Record<string, unknown>
|
|
||||||
```
|
|
||||||
|
|
||||||
#### Returns
|
|
||||||
|
|
||||||
`Record`<`string`, `unknown`>
|
|
||||||
|
|||||||
@@ -20,3 +20,13 @@ The maximum number of rows to return in a single batch
|
|||||||
|
|
||||||
Batches may have fewer rows if the underlying data is stored
|
Batches may have fewer rows if the underlying data is stored
|
||||||
in smaller chunks.
|
in smaller chunks.
|
||||||
|
|
||||||
|
***
|
||||||
|
|
||||||
|
### timeoutMs?
|
||||||
|
|
||||||
|
```ts
|
||||||
|
optional timeoutMs: number;
|
||||||
|
```
|
||||||
|
|
||||||
|
Timeout for query execution in milliseconds
|
||||||
|
|||||||
@@ -11,7 +11,6 @@ likely that someone who knows the answer will see your question.
|
|||||||
## Common issues
|
## Common issues
|
||||||
|
|
||||||
* Multiprocessing with `fork` is not supported. You should use `spawn` instead.
|
* Multiprocessing with `fork` is not supported. You should use `spawn` instead.
|
||||||
* Data returned by queries may not reflect the most recent writes, depending on configuration. LanceDB uses eventual consistency by default. See [consistency](/docs/src/guides/tables.md#consistency) for more information.
|
|
||||||
|
|
||||||
## Enabling logging
|
## Enabling logging
|
||||||
|
|
||||||
|
|||||||
@@ -8,7 +8,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<groupId>com.lancedb</groupId>
|
<groupId>com.lancedb</groupId>
|
||||||
<artifactId>lancedb-parent</artifactId>
|
<artifactId>lancedb-parent</artifactId>
|
||||||
<version>0.19.0-beta.3</version>
|
<version>0.19.0-beta.7</version>
|
||||||
<relativePath>../pom.xml</relativePath>
|
<relativePath>../pom.xml</relativePath>
|
||||||
</parent>
|
</parent>
|
||||||
|
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
|
|
||||||
<groupId>com.lancedb</groupId>
|
<groupId>com.lancedb</groupId>
|
||||||
<artifactId>lancedb-parent</artifactId>
|
<artifactId>lancedb-parent</artifactId>
|
||||||
<version>0.19.0-beta.3</version>
|
<version>0.19.0-beta.7</version>
|
||||||
<packaging>pom</packaging>
|
<packaging>pom</packaging>
|
||||||
|
|
||||||
<name>LanceDB Parent</name>
|
<name>LanceDB Parent</name>
|
||||||
|
|||||||
79
node/package-lock.json
generated
79
node/package-lock.json
generated
@@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "vectordb",
|
"name": "vectordb",
|
||||||
"version": "0.19.0-beta.3",
|
"version": "0.19.0-beta.7",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "vectordb",
|
"name": "vectordb",
|
||||||
"version": "0.19.0-beta.3",
|
"version": "0.19.0-beta.7",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"x64",
|
"x64",
|
||||||
"arm64"
|
"arm64"
|
||||||
@@ -52,11 +52,11 @@
|
|||||||
"uuid": "^9.0.0"
|
"uuid": "^9.0.0"
|
||||||
},
|
},
|
||||||
"optionalDependencies": {
|
"optionalDependencies": {
|
||||||
"@lancedb/vectordb-darwin-arm64": "0.19.0-beta.3",
|
"@lancedb/vectordb-darwin-arm64": "0.19.0-beta.7",
|
||||||
"@lancedb/vectordb-darwin-x64": "0.19.0-beta.3",
|
"@lancedb/vectordb-darwin-x64": "0.19.0-beta.7",
|
||||||
"@lancedb/vectordb-linux-arm64-gnu": "0.19.0-beta.3",
|
"@lancedb/vectordb-linux-arm64-gnu": "0.19.0-beta.7",
|
||||||
"@lancedb/vectordb-linux-x64-gnu": "0.19.0-beta.3",
|
"@lancedb/vectordb-linux-x64-gnu": "0.19.0-beta.7",
|
||||||
"@lancedb/vectordb-win32-x64-msvc": "0.19.0-beta.3"
|
"@lancedb/vectordb-win32-x64-msvc": "0.19.0-beta.7"
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"@apache-arrow/ts": "^14.0.2",
|
"@apache-arrow/ts": "^14.0.2",
|
||||||
@@ -326,6 +326,71 @@
|
|||||||
"@jridgewell/sourcemap-codec": "^1.4.10"
|
"@jridgewell/sourcemap-codec": "^1.4.10"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@lancedb/vectordb-darwin-arm64": {
|
||||||
|
"version": "0.19.0-beta.7",
|
||||||
|
"resolved": "https://registry.npmjs.org/@lancedb/vectordb-darwin-arm64/-/vectordb-darwin-arm64-0.19.0-beta.7.tgz",
|
||||||
|
"integrity": "sha512-HpbVKw4Vs+mPv7uPwaK7ilJlGrGdjOrNlC2mSkMCj0OlEwGRVcEcrSyijI7LXQH7ybEgNnDhSds5TuzBV26SGg==",
|
||||||
|
"cpu": [
|
||||||
|
"arm64"
|
||||||
|
],
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"darwin"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"node_modules/@lancedb/vectordb-darwin-x64": {
|
||||||
|
"version": "0.19.0-beta.7",
|
||||||
|
"resolved": "https://registry.npmjs.org/@lancedb/vectordb-darwin-x64/-/vectordb-darwin-x64-0.19.0-beta.7.tgz",
|
||||||
|
"integrity": "sha512-x3X7nqIYVZtxaa0uZUk/M99vKvDinZ5G0+8k2NqZ696YXGWKGyRxR6k8ZzKYCoCTSuYXnBftgKoIlwJGtNt8Bw==",
|
||||||
|
"cpu": [
|
||||||
|
"x64"
|
||||||
|
],
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"darwin"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"node_modules/@lancedb/vectordb-linux-arm64-gnu": {
|
||||||
|
"version": "0.19.0-beta.7",
|
||||||
|
"resolved": "https://registry.npmjs.org/@lancedb/vectordb-linux-arm64-gnu/-/vectordb-linux-arm64-gnu-0.19.0-beta.7.tgz",
|
||||||
|
"integrity": "sha512-Vwj0HI3+b4NgXKf+5+W/GfLBCGoQMBGM47vA/ts1dpe/PxraOQYPDv67I5kbXkCQKwhal7b0iZx/PbMu0JZPyw==",
|
||||||
|
"cpu": [
|
||||||
|
"arm64"
|
||||||
|
],
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"linux"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"node_modules/@lancedb/vectordb-linux-x64-gnu": {
|
||||||
|
"version": "0.19.0-beta.7",
|
||||||
|
"resolved": "https://registry.npmjs.org/@lancedb/vectordb-linux-x64-gnu/-/vectordb-linux-x64-gnu-0.19.0-beta.7.tgz",
|
||||||
|
"integrity": "sha512-Dx2B6UWQei9D7Rt+MgHWqPTYtEK2w3EgsNb5ENEWUTZxH7lD/CV7Sw0JMK5LDG209fFcpXFerveF6J8ZC8uGBQ==",
|
||||||
|
"cpu": [
|
||||||
|
"x64"
|
||||||
|
],
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"linux"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"node_modules/@lancedb/vectordb-win32-x64-msvc": {
|
||||||
|
"version": "0.19.0-beta.7",
|
||||||
|
"resolved": "https://registry.npmjs.org/@lancedb/vectordb-win32-x64-msvc/-/vectordb-win32-x64-msvc-0.19.0-beta.7.tgz",
|
||||||
|
"integrity": "sha512-F5LZGa+gkUH1TgsWZWLLAMejwXFIWdash7+85ip4k2M0ThyqLF/dtlldOvteUEd5+flxihGjHg6TUtnSY8XBFA==",
|
||||||
|
"cpu": [
|
||||||
|
"x64"
|
||||||
|
],
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"win32"
|
||||||
|
]
|
||||||
|
},
|
||||||
"node_modules/@neon-rs/cli": {
|
"node_modules/@neon-rs/cli": {
|
||||||
"version": "0.0.160",
|
"version": "0.0.160",
|
||||||
"resolved": "https://registry.npmjs.org/@neon-rs/cli/-/cli-0.0.160.tgz",
|
"resolved": "https://registry.npmjs.org/@neon-rs/cli/-/cli-0.0.160.tgz",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "vectordb",
|
"name": "vectordb",
|
||||||
"version": "0.19.0-beta.3",
|
"version": "0.19.0-beta.7",
|
||||||
"description": " Serverless, low-latency vector database for AI applications",
|
"description": " Serverless, low-latency vector database for AI applications",
|
||||||
"private": false,
|
"private": false,
|
||||||
"main": "dist/index.js",
|
"main": "dist/index.js",
|
||||||
@@ -89,10 +89,10 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"optionalDependencies": {
|
"optionalDependencies": {
|
||||||
"@lancedb/vectordb-darwin-x64": "0.19.0-beta.3",
|
"@lancedb/vectordb-darwin-x64": "0.19.0-beta.7",
|
||||||
"@lancedb/vectordb-darwin-arm64": "0.19.0-beta.3",
|
"@lancedb/vectordb-darwin-arm64": "0.19.0-beta.7",
|
||||||
"@lancedb/vectordb-linux-x64-gnu": "0.19.0-beta.3",
|
"@lancedb/vectordb-linux-x64-gnu": "0.19.0-beta.7",
|
||||||
"@lancedb/vectordb-linux-arm64-gnu": "0.19.0-beta.3",
|
"@lancedb/vectordb-linux-arm64-gnu": "0.19.0-beta.7",
|
||||||
"@lancedb/vectordb-win32-x64-msvc": "0.19.0-beta.3"
|
"@lancedb/vectordb-win32-x64-msvc": "0.19.0-beta.7"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -110,7 +110,7 @@ describe('LanceDB Mirrored Store Integration test', function () {
|
|||||||
|
|
||||||
fs.readdir(path.join(mirroredPath, 'data'), { withFileTypes: true }, (err, files) => {
|
fs.readdir(path.join(mirroredPath, 'data'), { withFileTypes: true }, (err, files) => {
|
||||||
if (err != null) throw err
|
if (err != null) throw err
|
||||||
assert.equal(files.length, 1, `Found files: ${files.map(f => f.name)}`)
|
assert.equal(files.length, 1)
|
||||||
assert.isTrue(files[0].name.endsWith('.lance'))
|
assert.isTrue(files[0].name.endsWith('.lance'))
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "lancedb-nodejs"
|
name = "lancedb-nodejs"
|
||||||
edition.workspace = true
|
edition.workspace = true
|
||||||
version = "0.19.0-beta.3"
|
version = "0.19.0-beta.7"
|
||||||
license.workspace = true
|
license.workspace = true
|
||||||
description.workspace = true
|
description.workspace = true
|
||||||
repository.workspace = true
|
repository.workspace = true
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ describe("when connecting", () => {
|
|||||||
it("should connect", async () => {
|
it("should connect", async () => {
|
||||||
const db = await connect(tmpDir.name);
|
const db = await connect(tmpDir.name);
|
||||||
expect(db.display()).toBe(
|
expect(db.display()).toBe(
|
||||||
`ListingDatabase(uri=${tmpDir.name}, read_consistency_interval=5s)`,
|
`ListingDatabase(uri=${tmpDir.name}, read_consistency_interval=None)`,
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ import * as arrow16 from "apache-arrow-16";
|
|||||||
import * as arrow17 from "apache-arrow-17";
|
import * as arrow17 from "apache-arrow-17";
|
||||||
import * as arrow18 from "apache-arrow-18";
|
import * as arrow18 from "apache-arrow-18";
|
||||||
|
|
||||||
import { Table, connect } from "../lancedb";
|
import { MatchQuery, PhraseQuery, Table, connect } from "../lancedb";
|
||||||
import {
|
import {
|
||||||
Table as ArrowTable,
|
Table as ArrowTable,
|
||||||
Field,
|
Field,
|
||||||
@@ -33,6 +33,7 @@ import {
|
|||||||
register,
|
register,
|
||||||
} from "../lancedb/embedding";
|
} from "../lancedb/embedding";
|
||||||
import { Index } from "../lancedb/indices";
|
import { Index } from "../lancedb/indices";
|
||||||
|
import { instanceOfFullTextQuery } from "../lancedb/query";
|
||||||
|
|
||||||
describe.each([arrow15, arrow16, arrow17, arrow18])(
|
describe.each([arrow15, arrow16, arrow17, arrow18])(
|
||||||
"Given a table",
|
"Given a table",
|
||||||
@@ -58,7 +59,7 @@ describe.each([arrow15, arrow16, arrow17, arrow18])(
|
|||||||
|
|
||||||
it("be displayable", async () => {
|
it("be displayable", async () => {
|
||||||
expect(table.display()).toMatch(
|
expect(table.display()).toMatch(
|
||||||
/NativeTable\(some_table, uri=.*, read_consistency_interval=5s\)/,
|
/NativeTable\(some_table, uri=.*, read_consistency_interval=None\)/,
|
||||||
);
|
);
|
||||||
table.close();
|
table.close();
|
||||||
expect(table.display()).toBe("ClosedTable(some_table)");
|
expect(table.display()).toBe("ClosedTable(some_table)");
|
||||||
@@ -867,6 +868,44 @@ describe("When creating an index", () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe("When querying a table", () => {
|
||||||
|
let tmpDir: tmp.DirResult;
|
||||||
|
beforeEach(() => {
|
||||||
|
tmpDir = tmp.dirSync({ unsafeCleanup: true });
|
||||||
|
});
|
||||||
|
afterEach(() => tmpDir.removeCallback());
|
||||||
|
|
||||||
|
it("should throw an error when timeout is reached", async () => {
|
||||||
|
const db = await connect(tmpDir.name);
|
||||||
|
const data = makeArrowTable([
|
||||||
|
{ text: "a", vector: [0.1, 0.2] },
|
||||||
|
{ text: "b", vector: [0.3, 0.4] },
|
||||||
|
]);
|
||||||
|
const table = await db.createTable("test", data);
|
||||||
|
await table.createIndex("text", { config: Index.fts() });
|
||||||
|
|
||||||
|
await expect(
|
||||||
|
table.query().where("text != 'a'").toArray({ timeoutMs: 0 }),
|
||||||
|
).rejects.toThrow("Query timeout");
|
||||||
|
|
||||||
|
await expect(
|
||||||
|
table.query().nearestTo([0.0, 0.0]).toArrow({ timeoutMs: 0 }),
|
||||||
|
).rejects.toThrow("Query timeout");
|
||||||
|
|
||||||
|
await expect(
|
||||||
|
table.search("a", "fts").toArray({ timeoutMs: 0 }),
|
||||||
|
).rejects.toThrow("Query timeout");
|
||||||
|
|
||||||
|
await expect(
|
||||||
|
table
|
||||||
|
.query()
|
||||||
|
.nearestToText("a")
|
||||||
|
.nearestTo([0.0, 0.0])
|
||||||
|
.toArrow({ timeoutMs: 0 }),
|
||||||
|
).rejects.toThrow("Query timeout");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
describe("Read consistency interval", () => {
|
describe("Read consistency interval", () => {
|
||||||
let tmpDir: tmp.DirResult;
|
let tmpDir: tmp.DirResult;
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
@@ -1264,6 +1303,56 @@ describe.each([arrow15, arrow16, arrow17, arrow18])(
|
|||||||
|
|
||||||
const results = await table.search("hello").toArray();
|
const results = await table.search("hello").toArray();
|
||||||
expect(results[0].text).toBe(data[0].text);
|
expect(results[0].text).toBe(data[0].text);
|
||||||
|
|
||||||
|
const query = new MatchQuery("goodbye", "text");
|
||||||
|
expect(instanceOfFullTextQuery(query)).toBe(true);
|
||||||
|
const results2 = await table
|
||||||
|
.search(new MatchQuery("goodbye", "text"))
|
||||||
|
.toArray();
|
||||||
|
expect(results2[0].text).toBe(data[1].text);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("prewarm full text search index", async () => {
|
||||||
|
const db = await connect(tmpDir.name);
|
||||||
|
const data = [
|
||||||
|
{ text: ["lance database", "the", "search"], vector: [0.1, 0.2, 0.3] },
|
||||||
|
{ text: ["lance database"], vector: [0.4, 0.5, 0.6] },
|
||||||
|
{ text: ["lance", "search"], vector: [0.7, 0.8, 0.9] },
|
||||||
|
{ text: ["database", "search"], vector: [1.0, 1.1, 1.2] },
|
||||||
|
{ text: ["unrelated", "doc"], vector: [1.3, 1.4, 1.5] },
|
||||||
|
];
|
||||||
|
const table = await db.createTable("test", data);
|
||||||
|
await table.createIndex("text", {
|
||||||
|
config: Index.fts(),
|
||||||
|
});
|
||||||
|
|
||||||
|
// For the moment, we just confirm we can call prewarmIndex without error
|
||||||
|
// and still search it afterwards
|
||||||
|
await table.prewarmIndex("text_idx");
|
||||||
|
|
||||||
|
const results = await table.search("lance").toArray();
|
||||||
|
expect(results.length).toBe(3);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("full text index on list", async () => {
|
||||||
|
const db = await connect(tmpDir.name);
|
||||||
|
const data = [
|
||||||
|
{ text: ["lance database", "the", "search"], vector: [0.1, 0.2, 0.3] },
|
||||||
|
{ text: ["lance database"], vector: [0.4, 0.5, 0.6] },
|
||||||
|
{ text: ["lance", "search"], vector: [0.7, 0.8, 0.9] },
|
||||||
|
{ text: ["database", "search"], vector: [1.0, 1.1, 1.2] },
|
||||||
|
{ text: ["unrelated", "doc"], vector: [1.3, 1.4, 1.5] },
|
||||||
|
];
|
||||||
|
const table = await db.createTable("test", data);
|
||||||
|
await table.createIndex("text", {
|
||||||
|
config: Index.fts(),
|
||||||
|
});
|
||||||
|
|
||||||
|
const results = await table.search("lance").toArray();
|
||||||
|
expect(results.length).toBe(3);
|
||||||
|
|
||||||
|
const results2 = await table.search('"lance database"').toArray();
|
||||||
|
expect(results2.length).toBe(2);
|
||||||
});
|
});
|
||||||
|
|
||||||
test("full text search without positions", async () => {
|
test("full text search without positions", async () => {
|
||||||
@@ -1316,6 +1405,43 @@ describe.each([arrow15, arrow16, arrow17, arrow18])(
|
|||||||
expect(results.length).toBe(2);
|
expect(results.length).toBe(2);
|
||||||
const phraseResults = await table.search('"hello world"').toArray();
|
const phraseResults = await table.search('"hello world"').toArray();
|
||||||
expect(phraseResults.length).toBe(1);
|
expect(phraseResults.length).toBe(1);
|
||||||
|
const phraseResults2 = await table
|
||||||
|
.search(new PhraseQuery("hello world", "text"))
|
||||||
|
.toArray();
|
||||||
|
expect(phraseResults2.length).toBe(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("full text search fuzzy query", async () => {
|
||||||
|
const db = await connect(tmpDir.name);
|
||||||
|
const data = [
|
||||||
|
{ text: "fa", vector: [0.1, 0.2, 0.3] },
|
||||||
|
{ text: "fo", vector: [0.4, 0.5, 0.6] },
|
||||||
|
{ text: "fob", vector: [0.4, 0.5, 0.6] },
|
||||||
|
{ text: "focus", vector: [0.4, 0.5, 0.6] },
|
||||||
|
{ text: "foo", vector: [0.4, 0.5, 0.6] },
|
||||||
|
{ text: "food", vector: [0.4, 0.5, 0.6] },
|
||||||
|
{ text: "foul", vector: [0.4, 0.5, 0.6] },
|
||||||
|
];
|
||||||
|
const table = await db.createTable("test", data);
|
||||||
|
await table.createIndex("text", {
|
||||||
|
config: Index.fts(),
|
||||||
|
});
|
||||||
|
|
||||||
|
const results = await table
|
||||||
|
.search(new MatchQuery("foo", "text"))
|
||||||
|
.toArray();
|
||||||
|
expect(results.length).toBe(1);
|
||||||
|
expect(results[0].text).toBe("foo");
|
||||||
|
|
||||||
|
const fuzzyResults = await table
|
||||||
|
.search(new MatchQuery("foo", "text", { fuzziness: 1 }))
|
||||||
|
.toArray();
|
||||||
|
expect(fuzzyResults.length).toBe(4);
|
||||||
|
const resultSet = new Set(fuzzyResults.map((r) => r.text));
|
||||||
|
expect(resultSet.has("foo")).toBe(true);
|
||||||
|
expect(resultSet.has("fob")).toBe(true);
|
||||||
|
expect(resultSet.has("fo")).toBe(true);
|
||||||
|
expect(resultSet.has("food")).toBe(true);
|
||||||
});
|
});
|
||||||
|
|
||||||
test.each([
|
test.each([
|
||||||
|
|||||||
@@ -202,35 +202,5 @@ test("basic table examples", async () => {
|
|||||||
// --8<-- [end:create_f16_table]
|
// --8<-- [end:create_f16_table]
|
||||||
await db.dropTable("f16_tbl");
|
await db.dropTable("f16_tbl");
|
||||||
}
|
}
|
||||||
const uri = databaseDir;
|
|
||||||
await db.createTable("my_table", [{ id: 1 }, { id: 2 }]);
|
|
||||||
{
|
|
||||||
// --8<-- [start:table_strong_consistency]
|
|
||||||
const db = await lancedb.connect({ uri, readConsistencyInterval: 0 });
|
|
||||||
const tbl = await db.openTable("my_table");
|
|
||||||
// --8<-- [end:table_strong_consistency]
|
|
||||||
}
|
|
||||||
{
|
|
||||||
// --8<-- [start:table_eventual_consistency]
|
|
||||||
const db = await lancedb.connect({ uri, readConsistencyInterval: 5 });
|
|
||||||
const tbl = await db.openTable("my_table");
|
|
||||||
// --8<-- [end:table_eventual_consistency]
|
|
||||||
}
|
|
||||||
{
|
|
||||||
// --8<-- [start:table_no_consistency]
|
|
||||||
const db = await lancedb.connect({ uri, readConsistencyInterval: null });
|
|
||||||
const tbl = await db.openTable("my_table");
|
|
||||||
// --8<-- [end:table_no_consistency]
|
|
||||||
}
|
|
||||||
{
|
|
||||||
// --8<-- [start:table_checkout_latest]
|
|
||||||
const tbl = await db.openTable("my_table");
|
|
||||||
|
|
||||||
// (Other writes happen to test_table_async from another process)
|
|
||||||
|
|
||||||
// Check for updates
|
|
||||||
tbl.checkoutLatest();
|
|
||||||
// --8<-- [end:table_checkout_latest]
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import {
|
|||||||
} from "./arrow";
|
} from "./arrow";
|
||||||
import { type IvfPqOptions } from "./indices";
|
import { type IvfPqOptions } from "./indices";
|
||||||
import {
|
import {
|
||||||
|
JsFullTextQuery,
|
||||||
RecordBatchIterator as NativeBatchIterator,
|
RecordBatchIterator as NativeBatchIterator,
|
||||||
Query as NativeQuery,
|
Query as NativeQuery,
|
||||||
Table as NativeTable,
|
Table as NativeTable,
|
||||||
@@ -63,7 +64,7 @@ class RecordBatchIterable<
|
|||||||
// biome-ignore lint/suspicious/noExplicitAny: skip
|
// biome-ignore lint/suspicious/noExplicitAny: skip
|
||||||
[Symbol.asyncIterator](): AsyncIterator<RecordBatch<any>, any, undefined> {
|
[Symbol.asyncIterator](): AsyncIterator<RecordBatch<any>, any, undefined> {
|
||||||
return new RecordBatchIterator(
|
return new RecordBatchIterator(
|
||||||
this.inner.execute(this.options?.maxBatchLength),
|
this.inner.execute(this.options?.maxBatchLength, this.options?.timeoutMs),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -79,6 +80,11 @@ export interface QueryExecutionOptions {
|
|||||||
* in smaller chunks.
|
* in smaller chunks.
|
||||||
*/
|
*/
|
||||||
maxBatchLength?: number;
|
maxBatchLength?: number;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Timeout for query execution in milliseconds
|
||||||
|
*/
|
||||||
|
timeoutMs?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -172,9 +178,7 @@ export class QueryBase<NativeQueryType extends NativeQuery | NativeVectorQuery>
|
|||||||
columns: columns,
|
columns: columns,
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
// If query is a FullTextQuery object, convert it to a dict
|
inner.fullTextSearch({ query: query.inner });
|
||||||
const queryObj = query.toDict();
|
|
||||||
inner.fullTextSearch(queryObj);
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
return this;
|
return this;
|
||||||
@@ -283,9 +287,11 @@ export class QueryBase<NativeQueryType extends NativeQuery | NativeVectorQuery>
|
|||||||
options?: Partial<QueryExecutionOptions>,
|
options?: Partial<QueryExecutionOptions>,
|
||||||
): Promise<NativeBatchIterator> {
|
): Promise<NativeBatchIterator> {
|
||||||
if (this.inner instanceof Promise) {
|
if (this.inner instanceof Promise) {
|
||||||
return this.inner.then((inner) => inner.execute(options?.maxBatchLength));
|
return this.inner.then((inner) =>
|
||||||
|
inner.execute(options?.maxBatchLength, options?.timeoutMs),
|
||||||
|
);
|
||||||
} else {
|
} else {
|
||||||
return this.inner.execute(options?.maxBatchLength);
|
return this.inner.execute(options?.maxBatchLength, options?.timeoutMs);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -736,8 +742,7 @@ export class Query extends QueryBase<NativeQuery> {
|
|||||||
columns: columns,
|
columns: columns,
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
const queryObj = query.toDict();
|
inner.fullTextSearch({ query: query.inner });
|
||||||
inner.fullTextSearch(queryObj);
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
return this;
|
return this;
|
||||||
@@ -765,130 +770,141 @@ export enum FullTextQueryType {
|
|||||||
* including methods to retrieve the query type and convert the query to a dictionary format.
|
* including methods to retrieve the query type and convert the query to a dictionary format.
|
||||||
*/
|
*/
|
||||||
export interface FullTextQuery {
|
export interface FullTextQuery {
|
||||||
|
/**
|
||||||
|
* Returns the inner query object.
|
||||||
|
* This is the underlying query object used by the database engine.
|
||||||
|
* @ignore
|
||||||
|
*/
|
||||||
|
inner: JsFullTextQuery;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The type of the full-text query.
|
||||||
|
*/
|
||||||
queryType(): FullTextQueryType;
|
queryType(): FullTextQueryType;
|
||||||
toDict(): Record<string, unknown>;
|
}
|
||||||
|
|
||||||
|
// biome-ignore lint/suspicious/noExplicitAny: we want any here
|
||||||
|
export function instanceOfFullTextQuery(obj: any): obj is FullTextQuery {
|
||||||
|
return obj != null && obj.inner instanceof JsFullTextQuery;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class MatchQuery implements FullTextQuery {
|
export class MatchQuery implements FullTextQuery {
|
||||||
|
/** @ignore */
|
||||||
|
public readonly inner: JsFullTextQuery;
|
||||||
/**
|
/**
|
||||||
* Creates an instance of MatchQuery.
|
* Creates an instance of MatchQuery.
|
||||||
*
|
*
|
||||||
* @param query - The text query to search for.
|
* @param query - The text query to search for.
|
||||||
* @param column - The name of the column to search within.
|
* @param column - The name of the column to search within.
|
||||||
* @param boost - (Optional) The boost factor to influence the relevance score of this query. Default is `1.0`.
|
* @param options - Optional parameters for the match query.
|
||||||
* @param fuzziness - (Optional) The allowed edit distance for fuzzy matching. Default is `0`.
|
* - `boost`: The boost factor for the query (default is 1.0).
|
||||||
* @param maxExpansions - (Optional) The maximum number of terms to consider for fuzzy matching. Default is `50`.
|
* - `fuzziness`: The fuzziness level for the query (default is 0).
|
||||||
|
* - `maxExpansions`: The maximum number of terms to consider for fuzzy matching (default is 50).
|
||||||
*/
|
*/
|
||||||
constructor(
|
constructor(
|
||||||
private query: string,
|
query: string,
|
||||||
private column: string,
|
column: string,
|
||||||
private boost: number = 1.0,
|
options?: {
|
||||||
private fuzziness: number = 0,
|
boost?: number;
|
||||||
private maxExpansions: number = 50,
|
fuzziness?: number;
|
||||||
) {}
|
maxExpansions?: number;
|
||||||
|
},
|
||||||
|
) {
|
||||||
|
let fuzziness = options?.fuzziness;
|
||||||
|
if (fuzziness === undefined) {
|
||||||
|
fuzziness = 0;
|
||||||
|
}
|
||||||
|
this.inner = JsFullTextQuery.matchQuery(
|
||||||
|
query,
|
||||||
|
column,
|
||||||
|
options?.boost ?? 1.0,
|
||||||
|
fuzziness,
|
||||||
|
options?.maxExpansions ?? 50,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
queryType(): FullTextQueryType {
|
queryType(): FullTextQueryType {
|
||||||
return FullTextQueryType.Match;
|
return FullTextQueryType.Match;
|
||||||
}
|
}
|
||||||
|
|
||||||
toDict(): Record<string, unknown> {
|
|
||||||
return {
|
|
||||||
[this.queryType()]: {
|
|
||||||
[this.column]: {
|
|
||||||
query: this.query,
|
|
||||||
boost: this.boost,
|
|
||||||
fuzziness: this.fuzziness,
|
|
||||||
// biome-ignore lint/style/useNamingConvention: use underscore for consistency with the other APIs
|
|
||||||
max_expansions: this.maxExpansions,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export class PhraseQuery implements FullTextQuery {
|
export class PhraseQuery implements FullTextQuery {
|
||||||
|
/** @ignore */
|
||||||
|
public readonly inner: JsFullTextQuery;
|
||||||
/**
|
/**
|
||||||
* Creates an instance of `PhraseQuery`.
|
* Creates an instance of `PhraseQuery`.
|
||||||
*
|
*
|
||||||
* @param query - The phrase to search for in the specified column.
|
* @param query - The phrase to search for in the specified column.
|
||||||
* @param column - The name of the column to search within.
|
* @param column - The name of the column to search within.
|
||||||
*/
|
*/
|
||||||
constructor(
|
constructor(query: string, column: string) {
|
||||||
private query: string,
|
this.inner = JsFullTextQuery.phraseQuery(query, column);
|
||||||
private column: string,
|
}
|
||||||
) {}
|
|
||||||
|
|
||||||
queryType(): FullTextQueryType {
|
queryType(): FullTextQueryType {
|
||||||
return FullTextQueryType.MatchPhrase;
|
return FullTextQueryType.MatchPhrase;
|
||||||
}
|
}
|
||||||
|
|
||||||
toDict(): Record<string, unknown> {
|
|
||||||
return {
|
|
||||||
[this.queryType()]: {
|
|
||||||
[this.column]: this.query,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export class BoostQuery implements FullTextQuery {
|
export class BoostQuery implements FullTextQuery {
|
||||||
|
/** @ignore */
|
||||||
|
public readonly inner: JsFullTextQuery;
|
||||||
/**
|
/**
|
||||||
* Creates an instance of BoostQuery.
|
* Creates an instance of BoostQuery.
|
||||||
|
* The boost returns documents that match the positive query,
|
||||||
|
* but penalizes those that match the negative query.
|
||||||
|
* the penalty is controlled by the `negativeBoost` parameter.
|
||||||
*
|
*
|
||||||
* @param positive - The positive query that boosts the relevance score.
|
* @param positive - The positive query that boosts the relevance score.
|
||||||
* @param negative - The negative query that reduces the relevance score.
|
* @param negative - The negative query that reduces the relevance score.
|
||||||
* @param negativeBoost - The factor by which the negative query reduces the score.
|
* @param options - Optional parameters for the boost query.
|
||||||
|
* - `negativeBoost`: The boost factor for the negative query (default is 0.0).
|
||||||
*/
|
*/
|
||||||
constructor(
|
constructor(
|
||||||
private positive: FullTextQuery,
|
positive: FullTextQuery,
|
||||||
private negative: FullTextQuery,
|
negative: FullTextQuery,
|
||||||
private negativeBoost: number,
|
options?: {
|
||||||
) {}
|
negativeBoost?: number;
|
||||||
|
},
|
||||||
|
) {
|
||||||
|
this.inner = JsFullTextQuery.boostQuery(
|
||||||
|
positive.inner,
|
||||||
|
negative.inner,
|
||||||
|
options?.negativeBoost,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
queryType(): FullTextQueryType {
|
queryType(): FullTextQueryType {
|
||||||
return FullTextQueryType.Boost;
|
return FullTextQueryType.Boost;
|
||||||
}
|
}
|
||||||
|
|
||||||
toDict(): Record<string, unknown> {
|
|
||||||
return {
|
|
||||||
[this.queryType()]: {
|
|
||||||
positive: this.positive.toDict(),
|
|
||||||
negative: this.negative.toDict(),
|
|
||||||
// biome-ignore lint/style/useNamingConvention: use underscore for consistency with the other APIs
|
|
||||||
negative_boost: this.negativeBoost,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export class MultiMatchQuery implements FullTextQuery {
|
export class MultiMatchQuery implements FullTextQuery {
|
||||||
|
/** @ignore */
|
||||||
|
public readonly inner: JsFullTextQuery;
|
||||||
/**
|
/**
|
||||||
* Creates an instance of MultiMatchQuery.
|
* Creates an instance of MultiMatchQuery.
|
||||||
*
|
*
|
||||||
* @param query - The text query to search for across multiple columns.
|
* @param query - The text query to search for across multiple columns.
|
||||||
* @param columns - An array of column names to search within.
|
* @param columns - An array of column names to search within.
|
||||||
* @param boosts - (Optional) An array of boost factors corresponding to each column. Default is an array of 1.0 for each column.
|
* @param options - Optional parameters for the multi-match query.
|
||||||
*
|
* - `boosts`: An array of boost factors for each column (default is 1.0 for all).
|
||||||
* The `boosts` array should have the same length as `columns`. If not provided, all columns will have a default boost of 1.0.
|
|
||||||
* If the length of `boosts` is less than `columns`, it will be padded with 1.0s.
|
|
||||||
*/
|
*/
|
||||||
constructor(
|
constructor(
|
||||||
private query: string,
|
query: string,
|
||||||
private columns: string[],
|
columns: string[],
|
||||||
private boosts: number[] = columns.map(() => 1.0),
|
options?: {
|
||||||
) {}
|
boosts?: number[];
|
||||||
|
},
|
||||||
|
) {
|
||||||
|
this.inner = JsFullTextQuery.multiMatchQuery(
|
||||||
|
query,
|
||||||
|
columns,
|
||||||
|
options?.boosts,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
queryType(): FullTextQueryType {
|
queryType(): FullTextQueryType {
|
||||||
return FullTextQueryType.MultiMatch;
|
return FullTextQueryType.MultiMatch;
|
||||||
}
|
}
|
||||||
|
|
||||||
toDict(): Record<string, unknown> {
|
|
||||||
return {
|
|
||||||
[this.queryType()]: {
|
|
||||||
query: this.query,
|
|
||||||
columns: this.columns,
|
|
||||||
boost: this.boosts,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,7 +22,12 @@ import {
|
|||||||
OptimizeStats,
|
OptimizeStats,
|
||||||
Table as _NativeTable,
|
Table as _NativeTable,
|
||||||
} from "./native";
|
} from "./native";
|
||||||
import { Query, VectorQuery } from "./query";
|
import {
|
||||||
|
FullTextQuery,
|
||||||
|
Query,
|
||||||
|
VectorQuery,
|
||||||
|
instanceOfFullTextQuery,
|
||||||
|
} from "./query";
|
||||||
import { sanitizeType } from "./sanitize";
|
import { sanitizeType } from "./sanitize";
|
||||||
import { IntoSql, toSQL } from "./util";
|
import { IntoSql, toSQL } from "./util";
|
||||||
export { IndexConfig } from "./native";
|
export { IndexConfig } from "./native";
|
||||||
@@ -230,6 +235,17 @@ export abstract class Table {
|
|||||||
*/
|
*/
|
||||||
abstract dropIndex(name: string): Promise<void>;
|
abstract dropIndex(name: string): Promise<void>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Prewarm an index in the table.
|
||||||
|
*
|
||||||
|
* @param name The name of the index.
|
||||||
|
*
|
||||||
|
* This will load the index into memory. This may reduce the cold-start time for
|
||||||
|
* future queries. If the index does not fit in the cache then this call may be
|
||||||
|
* wasteful.
|
||||||
|
*/
|
||||||
|
abstract prewarmIndex(name: string): Promise<void>;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a {@link Query} Builder.
|
* Create a {@link Query} Builder.
|
||||||
*
|
*
|
||||||
@@ -294,7 +310,7 @@ export abstract class Table {
|
|||||||
* if the query is a string and no embedding function is defined, it will be treated as a full text search query
|
* if the query is a string and no embedding function is defined, it will be treated as a full text search query
|
||||||
*/
|
*/
|
||||||
abstract search(
|
abstract search(
|
||||||
query: string | IntoVector,
|
query: string | IntoVector | FullTextQuery,
|
||||||
queryType?: string,
|
queryType?: string,
|
||||||
ftsColumns?: string | string[],
|
ftsColumns?: string | string[],
|
||||||
): VectorQuery | Query;
|
): VectorQuery | Query;
|
||||||
@@ -560,16 +576,20 @@ export class LocalTable extends Table {
|
|||||||
await this.inner.dropIndex(name);
|
await this.inner.dropIndex(name);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async prewarmIndex(name: string): Promise<void> {
|
||||||
|
await this.inner.prewarmIndex(name);
|
||||||
|
}
|
||||||
|
|
||||||
query(): Query {
|
query(): Query {
|
||||||
return new Query(this.inner);
|
return new Query(this.inner);
|
||||||
}
|
}
|
||||||
|
|
||||||
search(
|
search(
|
||||||
query: string | IntoVector,
|
query: string | IntoVector | FullTextQuery,
|
||||||
queryType: string = "auto",
|
queryType: string = "auto",
|
||||||
ftsColumns?: string | string[],
|
ftsColumns?: string | string[],
|
||||||
): VectorQuery | Query {
|
): VectorQuery | Query {
|
||||||
if (typeof query !== "string") {
|
if (typeof query !== "string" && !instanceOfFullTextQuery(query)) {
|
||||||
if (queryType === "fts") {
|
if (queryType === "fts") {
|
||||||
throw new Error("Cannot perform full text search on a vector query");
|
throw new Error("Cannot perform full text search on a vector query");
|
||||||
}
|
}
|
||||||
@@ -585,7 +605,10 @@ export class LocalTable extends Table {
|
|||||||
|
|
||||||
// The query type is auto or vector
|
// The query type is auto or vector
|
||||||
// fall back to full text search if no embedding functions are defined and the query is a string
|
// fall back to full text search if no embedding functions are defined and the query is a string
|
||||||
if (queryType === "auto" && getRegistry().length() === 0) {
|
if (
|
||||||
|
queryType === "auto" &&
|
||||||
|
(getRegistry().length() === 0 || instanceOfFullTextQuery(query))
|
||||||
|
) {
|
||||||
return this.query().fullTextSearch(query, {
|
return this.query().fullTextSearch(query, {
|
||||||
columns: ftsColumns,
|
columns: ftsColumns,
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@lancedb/lancedb-darwin-arm64",
|
"name": "@lancedb/lancedb-darwin-arm64",
|
||||||
"version": "0.19.0-beta.3",
|
"version": "0.19.0-beta.7",
|
||||||
"os": ["darwin"],
|
"os": ["darwin"],
|
||||||
"cpu": ["arm64"],
|
"cpu": ["arm64"],
|
||||||
"main": "lancedb.darwin-arm64.node",
|
"main": "lancedb.darwin-arm64.node",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@lancedb/lancedb-darwin-x64",
|
"name": "@lancedb/lancedb-darwin-x64",
|
||||||
"version": "0.19.0-beta.3",
|
"version": "0.19.0-beta.7",
|
||||||
"os": ["darwin"],
|
"os": ["darwin"],
|
||||||
"cpu": ["x64"],
|
"cpu": ["x64"],
|
||||||
"main": "lancedb.darwin-x64.node",
|
"main": "lancedb.darwin-x64.node",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@lancedb/lancedb-linux-arm64-gnu",
|
"name": "@lancedb/lancedb-linux-arm64-gnu",
|
||||||
"version": "0.19.0-beta.3",
|
"version": "0.19.0-beta.7",
|
||||||
"os": ["linux"],
|
"os": ["linux"],
|
||||||
"cpu": ["arm64"],
|
"cpu": ["arm64"],
|
||||||
"main": "lancedb.linux-arm64-gnu.node",
|
"main": "lancedb.linux-arm64-gnu.node",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@lancedb/lancedb-linux-arm64-musl",
|
"name": "@lancedb/lancedb-linux-arm64-musl",
|
||||||
"version": "0.19.0-beta.3",
|
"version": "0.19.0-beta.7",
|
||||||
"os": ["linux"],
|
"os": ["linux"],
|
||||||
"cpu": ["arm64"],
|
"cpu": ["arm64"],
|
||||||
"main": "lancedb.linux-arm64-musl.node",
|
"main": "lancedb.linux-arm64-musl.node",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@lancedb/lancedb-linux-x64-gnu",
|
"name": "@lancedb/lancedb-linux-x64-gnu",
|
||||||
"version": "0.19.0-beta.3",
|
"version": "0.19.0-beta.7",
|
||||||
"os": ["linux"],
|
"os": ["linux"],
|
||||||
"cpu": ["x64"],
|
"cpu": ["x64"],
|
||||||
"main": "lancedb.linux-x64-gnu.node",
|
"main": "lancedb.linux-x64-gnu.node",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@lancedb/lancedb-linux-x64-musl",
|
"name": "@lancedb/lancedb-linux-x64-musl",
|
||||||
"version": "0.19.0-beta.3",
|
"version": "0.19.0-beta.7",
|
||||||
"os": ["linux"],
|
"os": ["linux"],
|
||||||
"cpu": ["x64"],
|
"cpu": ["x64"],
|
||||||
"main": "lancedb.linux-x64-musl.node",
|
"main": "lancedb.linux-x64-musl.node",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@lancedb/lancedb-win32-arm64-msvc",
|
"name": "@lancedb/lancedb-win32-arm64-msvc",
|
||||||
"version": "0.19.0-beta.3",
|
"version": "0.19.0-beta.7",
|
||||||
"os": [
|
"os": [
|
||||||
"win32"
|
"win32"
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@lancedb/lancedb-win32-x64-msvc",
|
"name": "@lancedb/lancedb-win32-x64-msvc",
|
||||||
"version": "0.19.0-beta.3",
|
"version": "0.19.0-beta.7",
|
||||||
"os": ["win32"],
|
"os": ["win32"],
|
||||||
"cpu": ["x64"],
|
"cpu": ["x64"],
|
||||||
"main": "lancedb.win32-x64-msvc.node",
|
"main": "lancedb.win32-x64-msvc.node",
|
||||||
|
|||||||
4
nodejs/package-lock.json
generated
4
nodejs/package-lock.json
generated
@@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "@lancedb/lancedb",
|
"name": "@lancedb/lancedb",
|
||||||
"version": "0.19.0-beta.3",
|
"version": "0.19.0-beta.7",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "@lancedb/lancedb",
|
"name": "@lancedb/lancedb",
|
||||||
"version": "0.19.0-beta.3",
|
"version": "0.19.0-beta.7",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"x64",
|
"x64",
|
||||||
"arm64"
|
"arm64"
|
||||||
|
|||||||
@@ -11,7 +11,7 @@
|
|||||||
"ann"
|
"ann"
|
||||||
],
|
],
|
||||||
"private": false,
|
"private": false,
|
||||||
"version": "0.19.0-beta.3",
|
"version": "0.19.0-beta.7",
|
||||||
"main": "dist/index.js",
|
"main": "dist/index.js",
|
||||||
"exports": {
|
"exports": {
|
||||||
".": "./dist/index.js",
|
".": "./dist/index.js",
|
||||||
|
|||||||
@@ -48,16 +48,8 @@ impl Connection {
|
|||||||
pub async fn new(uri: String, options: ConnectionOptions) -> napi::Result<Self> {
|
pub async fn new(uri: String, options: ConnectionOptions) -> napi::Result<Self> {
|
||||||
let mut builder = ConnectBuilder::new(&uri);
|
let mut builder = ConnectBuilder::new(&uri);
|
||||||
if let Some(interval) = options.read_consistency_interval {
|
if let Some(interval) = options.read_consistency_interval {
|
||||||
match interval {
|
builder =
|
||||||
Either::A(seconds) => {
|
builder.read_consistency_interval(std::time::Duration::from_secs_f64(interval));
|
||||||
builder = builder.read_consistency_interval(Some(
|
|
||||||
std::time::Duration::from_secs_f64(seconds),
|
|
||||||
));
|
|
||||||
}
|
|
||||||
Either::B(_) => {
|
|
||||||
builder = builder.read_consistency_interval(None);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
if let Some(storage_options) = options.storage_options {
|
if let Some(storage_options) = options.storage_options {
|
||||||
for (key, value) in storage_options {
|
for (key, value) in storage_options {
|
||||||
|
|||||||
@@ -4,7 +4,6 @@
|
|||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
|
||||||
use env_logger::Env;
|
use env_logger::Env;
|
||||||
use napi::{bindgen_prelude::Null, Either};
|
|
||||||
use napi_derive::*;
|
use napi_derive::*;
|
||||||
|
|
||||||
mod connection;
|
mod connection;
|
||||||
@@ -19,6 +18,7 @@ mod table;
|
|||||||
mod util;
|
mod util;
|
||||||
|
|
||||||
#[napi(object)]
|
#[napi(object)]
|
||||||
|
#[derive(Debug)]
|
||||||
pub struct ConnectionOptions {
|
pub struct ConnectionOptions {
|
||||||
/// (For LanceDB OSS only): The interval, in seconds, at which to check for
|
/// (For LanceDB OSS only): The interval, in seconds, at which to check for
|
||||||
/// updates to the table from other processes. If None, then consistency is not
|
/// updates to the table from other processes. If None, then consistency is not
|
||||||
@@ -29,7 +29,7 @@ pub struct ConnectionOptions {
|
|||||||
/// has passed since the last check, then the table will be checked for updates.
|
/// has passed since the last check, then the table will be checked for updates.
|
||||||
/// Note: this consistency only applies to read operations. Write operations are
|
/// Note: this consistency only applies to read operations. Write operations are
|
||||||
/// always consistent.
|
/// always consistent.
|
||||||
pub read_consistency_interval: Option<Either<f64, Null>>,
|
pub read_consistency_interval: Option<f64>,
|
||||||
/// (For LanceDB OSS only): configuration for object storage.
|
/// (For LanceDB OSS only): configuration for object storage.
|
||||||
///
|
///
|
||||||
/// The available options are described at https://lancedb.github.io/lancedb/guides/storage/
|
/// The available options are described at https://lancedb.github.io/lancedb/guides/storage/
|
||||||
|
|||||||
@@ -3,7 +3,9 @@
|
|||||||
|
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
use lancedb::index::scalar::{FtsQuery, FullTextSearchQuery, MatchQuery, PhraseQuery};
|
use lancedb::index::scalar::{
|
||||||
|
BoostQuery, FtsQuery, FullTextSearchQuery, MatchQuery, MultiMatchQuery, PhraseQuery,
|
||||||
|
};
|
||||||
use lancedb::query::ExecutableQuery;
|
use lancedb::query::ExecutableQuery;
|
||||||
use lancedb::query::Query as LanceDbQuery;
|
use lancedb::query::Query as LanceDbQuery;
|
||||||
use lancedb::query::QueryBase;
|
use lancedb::query::QueryBase;
|
||||||
@@ -18,7 +20,7 @@ use crate::error::NapiErrorExt;
|
|||||||
use crate::iterator::RecordBatchIterator;
|
use crate::iterator::RecordBatchIterator;
|
||||||
use crate::rerankers::Reranker;
|
use crate::rerankers::Reranker;
|
||||||
use crate::rerankers::RerankerCallbacks;
|
use crate::rerankers::RerankerCallbacks;
|
||||||
use crate::util::{parse_distance_type, parse_fts_query};
|
use crate::util::parse_distance_type;
|
||||||
|
|
||||||
#[napi]
|
#[napi]
|
||||||
pub struct Query {
|
pub struct Query {
|
||||||
@@ -38,51 +40,8 @@ impl Query {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[napi]
|
#[napi]
|
||||||
pub fn full_text_search(&mut self, query: napi::JsUnknown) -> napi::Result<()> {
|
pub fn full_text_search(&mut self, query: napi::JsObject) -> napi::Result<()> {
|
||||||
let query = unsafe { query.cast::<napi::JsObject>() };
|
let query = parse_fts_query(query)?;
|
||||||
let query = if let Some(query_text) = query.get::<_, String>("query").transpose() {
|
|
||||||
let mut query_text = query_text?;
|
|
||||||
let columns = query.get::<_, Option<Vec<String>>>("columns")?.flatten();
|
|
||||||
|
|
||||||
let is_phrase =
|
|
||||||
query_text.len() >= 2 && query_text.starts_with('"') && query_text.ends_with('"');
|
|
||||||
let is_multi_match = columns.as_ref().map(|cols| cols.len() > 1).unwrap_or(false);
|
|
||||||
|
|
||||||
if is_phrase {
|
|
||||||
// Remove the surrounding quotes for phrase queries
|
|
||||||
query_text = query_text[1..query_text.len() - 1].to_string();
|
|
||||||
}
|
|
||||||
|
|
||||||
let query: FtsQuery = match (is_phrase, is_multi_match) {
|
|
||||||
(false, _) => MatchQuery::new(query_text).into(),
|
|
||||||
(true, false) => PhraseQuery::new(query_text).into(),
|
|
||||||
(true, true) => {
|
|
||||||
return Err(napi::Error::from_reason(
|
|
||||||
"Phrase queries cannot be used with multiple columns.",
|
|
||||||
));
|
|
||||||
}
|
|
||||||
};
|
|
||||||
let mut query = FullTextSearchQuery::new_query(query);
|
|
||||||
if let Some(cols) = columns {
|
|
||||||
if !cols.is_empty() {
|
|
||||||
query = query.with_columns(&cols).map_err(|e| {
|
|
||||||
napi::Error::from_reason(format!(
|
|
||||||
"Failed to set full text search columns: {}",
|
|
||||||
e
|
|
||||||
))
|
|
||||||
})?;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
query
|
|
||||||
} else if let Some(query) = query.get::<_, napi::JsObject>("query")? {
|
|
||||||
let query = parse_fts_query(&query)?;
|
|
||||||
FullTextSearchQuery::new_query(query)
|
|
||||||
} else {
|
|
||||||
return Err(napi::Error::from_reason(
|
|
||||||
"Invalid full text search query object".to_string(),
|
|
||||||
));
|
|
||||||
};
|
|
||||||
|
|
||||||
self.inner = self.inner.clone().full_text_search(query);
|
self.inner = self.inner.clone().full_text_search(query);
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@@ -131,11 +90,15 @@ impl Query {
|
|||||||
pub async fn execute(
|
pub async fn execute(
|
||||||
&self,
|
&self,
|
||||||
max_batch_length: Option<u32>,
|
max_batch_length: Option<u32>,
|
||||||
|
timeout_ms: Option<u32>,
|
||||||
) -> napi::Result<RecordBatchIterator> {
|
) -> napi::Result<RecordBatchIterator> {
|
||||||
let mut execution_opts = QueryExecutionOptions::default();
|
let mut execution_opts = QueryExecutionOptions::default();
|
||||||
if let Some(max_batch_length) = max_batch_length {
|
if let Some(max_batch_length) = max_batch_length {
|
||||||
execution_opts.max_batch_length = max_batch_length;
|
execution_opts.max_batch_length = max_batch_length;
|
||||||
}
|
}
|
||||||
|
if let Some(timeout_ms) = timeout_ms {
|
||||||
|
execution_opts.timeout = Some(std::time::Duration::from_millis(timeout_ms as u64))
|
||||||
|
}
|
||||||
let inner_stream = self
|
let inner_stream = self
|
||||||
.inner
|
.inner
|
||||||
.execute_with_options(execution_opts)
|
.execute_with_options(execution_opts)
|
||||||
@@ -239,51 +202,8 @@ impl VectorQuery {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[napi]
|
#[napi]
|
||||||
pub fn full_text_search(&mut self, query: napi::JsUnknown) -> napi::Result<()> {
|
pub fn full_text_search(&mut self, query: napi::JsObject) -> napi::Result<()> {
|
||||||
let query = unsafe { query.cast::<napi::JsObject>() };
|
let query = parse_fts_query(query)?;
|
||||||
let query = if let Some(query_text) = query.get::<_, String>("query").transpose() {
|
|
||||||
let mut query_text = query_text?;
|
|
||||||
let columns = query.get::<_, Option<Vec<String>>>("columns")?.flatten();
|
|
||||||
|
|
||||||
let is_phrase =
|
|
||||||
query_text.len() >= 2 && query_text.starts_with('"') && query_text.ends_with('"');
|
|
||||||
let is_multi_match = columns.as_ref().map(|cols| cols.len() > 1).unwrap_or(false);
|
|
||||||
|
|
||||||
if is_phrase {
|
|
||||||
// Remove the surrounding quotes for phrase queries
|
|
||||||
query_text = query_text[1..query_text.len() - 1].to_string();
|
|
||||||
}
|
|
||||||
|
|
||||||
let query: FtsQuery = match (is_phrase, is_multi_match) {
|
|
||||||
(false, _) => MatchQuery::new(query_text).into(),
|
|
||||||
(true, false) => PhraseQuery::new(query_text).into(),
|
|
||||||
(true, true) => {
|
|
||||||
return Err(napi::Error::from_reason(
|
|
||||||
"Phrase queries cannot be used with multiple columns.",
|
|
||||||
));
|
|
||||||
}
|
|
||||||
};
|
|
||||||
let mut query = FullTextSearchQuery::new_query(query);
|
|
||||||
if let Some(cols) = columns {
|
|
||||||
if !cols.is_empty() {
|
|
||||||
query = query.with_columns(&cols).map_err(|e| {
|
|
||||||
napi::Error::from_reason(format!(
|
|
||||||
"Failed to set full text search columns: {}",
|
|
||||||
e
|
|
||||||
))
|
|
||||||
})?;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
query
|
|
||||||
} else if let Some(query) = query.get::<_, napi::JsObject>("query")? {
|
|
||||||
let query = parse_fts_query(&query)?;
|
|
||||||
FullTextSearchQuery::new_query(query)
|
|
||||||
} else {
|
|
||||||
return Err(napi::Error::from_reason(
|
|
||||||
"Invalid full text search query object".to_string(),
|
|
||||||
));
|
|
||||||
};
|
|
||||||
|
|
||||||
self.inner = self.inner.clone().full_text_search(query);
|
self.inner = self.inner.clone().full_text_search(query);
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@@ -330,11 +250,15 @@ impl VectorQuery {
|
|||||||
pub async fn execute(
|
pub async fn execute(
|
||||||
&self,
|
&self,
|
||||||
max_batch_length: Option<u32>,
|
max_batch_length: Option<u32>,
|
||||||
|
timeout_ms: Option<u32>,
|
||||||
) -> napi::Result<RecordBatchIterator> {
|
) -> napi::Result<RecordBatchIterator> {
|
||||||
let mut execution_opts = QueryExecutionOptions::default();
|
let mut execution_opts = QueryExecutionOptions::default();
|
||||||
if let Some(max_batch_length) = max_batch_length {
|
if let Some(max_batch_length) = max_batch_length {
|
||||||
execution_opts.max_batch_length = max_batch_length;
|
execution_opts.max_batch_length = max_batch_length;
|
||||||
}
|
}
|
||||||
|
if let Some(timeout_ms) = timeout_ms {
|
||||||
|
execution_opts.timeout = Some(std::time::Duration::from_millis(timeout_ms as u64))
|
||||||
|
}
|
||||||
let inner_stream = self
|
let inner_stream = self
|
||||||
.inner
|
.inner
|
||||||
.execute_with_options(execution_opts)
|
.execute_with_options(execution_opts)
|
||||||
@@ -368,3 +292,116 @@ impl VectorQuery {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[napi]
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct JsFullTextQuery {
|
||||||
|
pub(crate) inner: FtsQuery,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[napi]
|
||||||
|
impl JsFullTextQuery {
|
||||||
|
#[napi(factory)]
|
||||||
|
pub fn match_query(
|
||||||
|
query: String,
|
||||||
|
column: String,
|
||||||
|
boost: f64,
|
||||||
|
fuzziness: Option<u32>,
|
||||||
|
max_expansions: u32,
|
||||||
|
) -> napi::Result<Self> {
|
||||||
|
Ok(Self {
|
||||||
|
inner: MatchQuery::new(query)
|
||||||
|
.with_column(Some(column))
|
||||||
|
.with_boost(boost as f32)
|
||||||
|
.with_fuzziness(fuzziness)
|
||||||
|
.with_max_expansions(max_expansions as usize)
|
||||||
|
.into(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
#[napi(factory)]
|
||||||
|
pub fn phrase_query(query: String, column: String) -> napi::Result<Self> {
|
||||||
|
Ok(Self {
|
||||||
|
inner: PhraseQuery::new(query).with_column(Some(column)).into(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
#[napi(factory)]
|
||||||
|
#[allow(clippy::use_self)] // NAPI doesn't allow Self here but clippy reports it
|
||||||
|
pub fn boost_query(
|
||||||
|
positive: &JsFullTextQuery,
|
||||||
|
negative: &JsFullTextQuery,
|
||||||
|
negative_boost: Option<f64>,
|
||||||
|
) -> napi::Result<Self> {
|
||||||
|
Ok(Self {
|
||||||
|
inner: BoostQuery::new(
|
||||||
|
positive.inner.clone(),
|
||||||
|
negative.inner.clone(),
|
||||||
|
negative_boost.map(|v| v as f32),
|
||||||
|
)
|
||||||
|
.into(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
#[napi(factory)]
|
||||||
|
pub fn multi_match_query(
|
||||||
|
query: String,
|
||||||
|
columns: Vec<String>,
|
||||||
|
boosts: Option<Vec<f64>>,
|
||||||
|
) -> napi::Result<Self> {
|
||||||
|
let q = match boosts {
|
||||||
|
Some(boosts) => MultiMatchQuery::try_new(query, columns)
|
||||||
|
.and_then(|q| q.try_with_boosts(boosts.into_iter().map(|v| v as f32).collect())),
|
||||||
|
None => MultiMatchQuery::try_new(query, columns),
|
||||||
|
}
|
||||||
|
.map_err(|e| {
|
||||||
|
napi::Error::from_reason(format!("Failed to create multi match query: {}", e))
|
||||||
|
})?;
|
||||||
|
|
||||||
|
Ok(Self { inner: q.into() })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_fts_query(query: napi::JsObject) -> napi::Result<FullTextSearchQuery> {
|
||||||
|
if let Ok(Some(query)) = query.get::<_, &JsFullTextQuery>("query") {
|
||||||
|
Ok(FullTextSearchQuery::new_query(query.inner.clone()))
|
||||||
|
} else if let Ok(Some(query_text)) = query.get::<_, String>("query") {
|
||||||
|
let mut query_text = query_text;
|
||||||
|
let columns = query.get::<_, Option<Vec<String>>>("columns")?.flatten();
|
||||||
|
|
||||||
|
let is_phrase =
|
||||||
|
query_text.len() >= 2 && query_text.starts_with('"') && query_text.ends_with('"');
|
||||||
|
let is_multi_match = columns.as_ref().map(|cols| cols.len() > 1).unwrap_or(false);
|
||||||
|
|
||||||
|
if is_phrase {
|
||||||
|
// Remove the surrounding quotes for phrase queries
|
||||||
|
query_text = query_text[1..query_text.len() - 1].to_string();
|
||||||
|
}
|
||||||
|
|
||||||
|
let query: FtsQuery = match (is_phrase, is_multi_match) {
|
||||||
|
(false, _) => MatchQuery::new(query_text).into(),
|
||||||
|
(true, false) => PhraseQuery::new(query_text).into(),
|
||||||
|
(true, true) => {
|
||||||
|
return Err(napi::Error::from_reason(
|
||||||
|
"Phrase queries cannot be used with multiple columns.",
|
||||||
|
));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
let mut query = FullTextSearchQuery::new_query(query);
|
||||||
|
if let Some(cols) = columns {
|
||||||
|
if !cols.is_empty() {
|
||||||
|
query = query.with_columns(&cols).map_err(|e| {
|
||||||
|
napi::Error::from_reason(format!(
|
||||||
|
"Failed to set full text search columns: {}",
|
||||||
|
e
|
||||||
|
))
|
||||||
|
})?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(query)
|
||||||
|
} else {
|
||||||
|
Err(napi::Error::from_reason(
|
||||||
|
"Invalid full text search query object".to_string(),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -132,6 +132,14 @@ impl Table {
|
|||||||
.default_error()
|
.default_error()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[napi(catch_unwind)]
|
||||||
|
pub async fn prewarm_index(&self, index_name: String) -> napi::Result<()> {
|
||||||
|
self.inner_ref()?
|
||||||
|
.prewarm_index(&index_name)
|
||||||
|
.await
|
||||||
|
.default_error()
|
||||||
|
}
|
||||||
|
|
||||||
#[napi(catch_unwind)]
|
#[napi(catch_unwind)]
|
||||||
pub async fn update(
|
pub async fn update(
|
||||||
&self,
|
&self,
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
// SPDX-License-Identifier: Apache-2.0
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
// SPDX-FileCopyrightText: Copyright The LanceDB Authors
|
// SPDX-FileCopyrightText: Copyright The LanceDB Authors
|
||||||
|
|
||||||
use lancedb::index::scalar::{BoostQuery, FtsQuery, MatchQuery, MultiMatchQuery, PhraseQuery};
|
|
||||||
use lancedb::DistanceType;
|
use lancedb::DistanceType;
|
||||||
|
|
||||||
pub fn parse_distance_type(distance_type: impl AsRef<str>) -> napi::Result<DistanceType> {
|
pub fn parse_distance_type(distance_type: impl AsRef<str>) -> napi::Result<DistanceType> {
|
||||||
@@ -16,144 +15,3 @@ pub fn parse_distance_type(distance_type: impl AsRef<str>) -> napi::Result<Dista
|
|||||||
))),
|
))),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn parse_fts_query(query: &napi::JsObject) -> napi::Result<FtsQuery> {
|
|
||||||
let query_type = query
|
|
||||||
.get_property_names()?
|
|
||||||
.get_element::<napi::JsString>(0)?;
|
|
||||||
let query_type = query_type.into_utf8()?.into_owned()?;
|
|
||||||
let query_value =
|
|
||||||
query
|
|
||||||
.get::<_, napi::JsObject>(&query_type)?
|
|
||||||
.ok_or(napi::Error::from_reason(format!(
|
|
||||||
"query value {} not found",
|
|
||||||
query_type
|
|
||||||
)))?;
|
|
||||||
|
|
||||||
match query_type.as_str() {
|
|
||||||
"match" => {
|
|
||||||
let column = query_value
|
|
||||||
.get_property_names()?
|
|
||||||
.get_element::<napi::JsString>(0)?
|
|
||||||
.into_utf8()?
|
|
||||||
.into_owned()?;
|
|
||||||
let params =
|
|
||||||
query_value
|
|
||||||
.get::<_, napi::JsObject>(&column)?
|
|
||||||
.ok_or(napi::Error::from_reason(format!(
|
|
||||||
"column {} not found",
|
|
||||||
column
|
|
||||||
)))?;
|
|
||||||
|
|
||||||
let query = params
|
|
||||||
.get::<_, napi::JsString>("query")?
|
|
||||||
.ok_or(napi::Error::from_reason("query not found"))?
|
|
||||||
.into_utf8()?
|
|
||||||
.into_owned()?;
|
|
||||||
let boost = params
|
|
||||||
.get::<_, napi::JsNumber>("boost")?
|
|
||||||
.ok_or(napi::Error::from_reason("boost not found"))?
|
|
||||||
.get_double()? as f32;
|
|
||||||
let fuzziness = params
|
|
||||||
.get::<_, napi::JsNumber>("fuzziness")?
|
|
||||||
.map(|f| f.get_uint32())
|
|
||||||
.transpose()?;
|
|
||||||
let max_expansions = params
|
|
||||||
.get::<_, napi::JsNumber>("max_expansions")?
|
|
||||||
.ok_or(napi::Error::from_reason("max_expansions not found"))?
|
|
||||||
.get_uint32()? as usize;
|
|
||||||
|
|
||||||
let query = MatchQuery::new(query)
|
|
||||||
.with_column(Some(column))
|
|
||||||
.with_boost(boost)
|
|
||||||
.with_fuzziness(fuzziness)
|
|
||||||
.with_max_expansions(max_expansions);
|
|
||||||
Ok(query.into())
|
|
||||||
}
|
|
||||||
|
|
||||||
"match_phrase" => {
|
|
||||||
let column = query_value
|
|
||||||
.get_property_names()?
|
|
||||||
.get_element::<napi::JsString>(0)?
|
|
||||||
.into_utf8()?
|
|
||||||
.into_owned()?;
|
|
||||||
let query = query_value
|
|
||||||
.get::<_, napi::JsString>(&column)?
|
|
||||||
.ok_or(napi::Error::from_reason(format!(
|
|
||||||
"column {} not found",
|
|
||||||
column
|
|
||||||
)))?
|
|
||||||
.into_utf8()?
|
|
||||||
.into_owned()?;
|
|
||||||
|
|
||||||
let query = PhraseQuery::new(query).with_column(Some(column));
|
|
||||||
Ok(query.into())
|
|
||||||
}
|
|
||||||
|
|
||||||
"boost" => {
|
|
||||||
let positive = query_value
|
|
||||||
.get::<_, napi::JsObject>("positive")?
|
|
||||||
.ok_or(napi::Error::from_reason("positive not found"))?;
|
|
||||||
|
|
||||||
let negative = query_value
|
|
||||||
.get::<_, napi::JsObject>("negative")?
|
|
||||||
.ok_or(napi::Error::from_reason("negative not found"))?;
|
|
||||||
let negative_boost = query_value
|
|
||||||
.get::<_, napi::JsNumber>("negative_boost")?
|
|
||||||
.ok_or(napi::Error::from_reason("negative_boost not found"))?
|
|
||||||
.get_double()? as f32;
|
|
||||||
|
|
||||||
let positive = parse_fts_query(&positive)?;
|
|
||||||
let negative = parse_fts_query(&negative)?;
|
|
||||||
let query = BoostQuery::new(positive, negative, Some(negative_boost));
|
|
||||||
Ok(query.into())
|
|
||||||
}
|
|
||||||
|
|
||||||
"multi_match" => {
|
|
||||||
let query = query_value
|
|
||||||
.get::<_, napi::JsString>("query")?
|
|
||||||
.ok_or(napi::Error::from_reason("query not found"))?
|
|
||||||
.into_utf8()?
|
|
||||||
.into_owned()?;
|
|
||||||
let columns_array = query_value
|
|
||||||
.get::<_, napi::JsTypedArray>("columns")?
|
|
||||||
.ok_or(napi::Error::from_reason("columns not found"))?;
|
|
||||||
let columns_num = columns_array.get_array_length()?;
|
|
||||||
let mut columns = Vec::with_capacity(columns_num as usize);
|
|
||||||
for i in 0..columns_num {
|
|
||||||
let column = columns_array
|
|
||||||
.get_element::<napi::JsString>(i)?
|
|
||||||
.into_utf8()?
|
|
||||||
.into_owned()?;
|
|
||||||
columns.push(column);
|
|
||||||
}
|
|
||||||
let boost_array = query_value
|
|
||||||
.get::<_, napi::JsTypedArray>("boost")?
|
|
||||||
.ok_or(napi::Error::from_reason("boost not found"))?;
|
|
||||||
if boost_array.get_array_length()? != columns_num {
|
|
||||||
return Err(napi::Error::from_reason(format!(
|
|
||||||
"boost array length ({}) does not match columns length ({})",
|
|
||||||
boost_array.get_array_length()?,
|
|
||||||
columns_num
|
|
||||||
)));
|
|
||||||
}
|
|
||||||
let mut boost = Vec::with_capacity(columns_num as usize);
|
|
||||||
for i in 0..columns_num {
|
|
||||||
let b = boost_array.get_element::<napi::JsNumber>(i)?.get_double()? as f32;
|
|
||||||
boost.push(b);
|
|
||||||
}
|
|
||||||
|
|
||||||
let query =
|
|
||||||
MultiMatchQuery::try_new_with_boosts(query, columns, boost).map_err(|e| {
|
|
||||||
napi::Error::from_reason(format!("Error creating MultiMatchQuery: {}", e))
|
|
||||||
})?;
|
|
||||||
|
|
||||||
Ok(query.into())
|
|
||||||
}
|
|
||||||
|
|
||||||
_ => Err(napi::Error::from_reason(format!(
|
|
||||||
"Unsupported query type: {}",
|
|
||||||
query_type
|
|
||||||
))),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
[tool.bumpversion]
|
[tool.bumpversion]
|
||||||
current_version = "0.22.0-beta.4"
|
current_version = "0.22.0-beta.8"
|
||||||
parse = """(?x)
|
parse = """(?x)
|
||||||
(?P<major>0|[1-9]\\d*)\\.
|
(?P<major>0|[1-9]\\d*)\\.
|
||||||
(?P<minor>0|[1-9]\\d*)\\.
|
(?P<minor>0|[1-9]\\d*)\\.
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "lancedb-python"
|
name = "lancedb-python"
|
||||||
version = "0.22.0-beta.4"
|
version = "0.22.0-beta.8"
|
||||||
edition.workspace = true
|
edition.workspace = true
|
||||||
description = "Python bindings for LanceDB"
|
description = "Python bindings for LanceDB"
|
||||||
license.workspace = true
|
license.workspace = true
|
||||||
|
|||||||
@@ -4,11 +4,12 @@ name = "lancedb"
|
|||||||
dynamic = ["version"]
|
dynamic = ["version"]
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"deprecation",
|
"deprecation",
|
||||||
"tqdm>=4.27.0",
|
"numpy",
|
||||||
|
"overrides>=0.7",
|
||||||
|
"packaging",
|
||||||
"pyarrow>=14",
|
"pyarrow>=14",
|
||||||
"pydantic>=1.10",
|
"pydantic>=1.10",
|
||||||
"packaging",
|
"tqdm>=4.27.0",
|
||||||
"overrides>=0.7",
|
|
||||||
]
|
]
|
||||||
description = "lancedb"
|
description = "lancedb"
|
||||||
authors = [{ name = "LanceDB Devs", email = "dev@lancedb.com" }]
|
authors = [{ name = "LanceDB Devs", email = "dev@lancedb.com" }]
|
||||||
@@ -42,6 +43,9 @@ classifiers = [
|
|||||||
repository = "https://github.com/lancedb/lancedb"
|
repository = "https://github.com/lancedb/lancedb"
|
||||||
|
|
||||||
[project.optional-dependencies]
|
[project.optional-dependencies]
|
||||||
|
pylance = [
|
||||||
|
"pylance>=0.25",
|
||||||
|
]
|
||||||
tests = [
|
tests = [
|
||||||
"aiohttp",
|
"aiohttp",
|
||||||
"boto3",
|
"boto3",
|
||||||
@@ -54,7 +58,8 @@ tests = [
|
|||||||
"polars>=0.19, <=1.3.0",
|
"polars>=0.19, <=1.3.0",
|
||||||
"tantivy",
|
"tantivy",
|
||||||
"pyarrow-stubs",
|
"pyarrow-stubs",
|
||||||
"pylance>=0.23.2",
|
"pylance>=0.25",
|
||||||
|
"requests",
|
||||||
]
|
]
|
||||||
dev = [
|
dev = [
|
||||||
"ruff",
|
"ruff",
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ def connect(
|
|||||||
api_key: Optional[str] = None,
|
api_key: Optional[str] = None,
|
||||||
region: str = "us-east-1",
|
region: str = "us-east-1",
|
||||||
host_override: Optional[str] = None,
|
host_override: Optional[str] = None,
|
||||||
read_consistency_interval: Optional[timedelta] = timedelta(seconds=5),
|
read_consistency_interval: Optional[timedelta] = None,
|
||||||
request_thread_pool: Optional[Union[int, ThreadPoolExecutor]] = None,
|
request_thread_pool: Optional[Union[int, ThreadPoolExecutor]] = None,
|
||||||
client_config: Union[ClientConfig, Dict[str, Any], None] = None,
|
client_config: Union[ClientConfig, Dict[str, Any], None] = None,
|
||||||
storage_options: Optional[Dict[str, str]] = None,
|
storage_options: Optional[Dict[str, str]] = None,
|
||||||
@@ -49,8 +49,9 @@ def connect(
|
|||||||
read_consistency_interval: timedelta, default None
|
read_consistency_interval: timedelta, default None
|
||||||
(For LanceDB OSS only)
|
(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 strong consistency,
|
processes. If None, then consistency is not checked. For performance
|
||||||
set this to zero seconds. Then every read will check for updates from other
|
reasons, this is the default. For strong consistency, set this to
|
||||||
|
zero seconds. Then every read will check for updates from other
|
||||||
processes. As a compromise, you can set this to a non-zero timedelta
|
processes. As a compromise, you can set this to a non-zero timedelta
|
||||||
for eventual consistency. If more than that interval has passed since
|
for eventual consistency. If more than that interval has passed since
|
||||||
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
|
||||||
@@ -121,7 +122,7 @@ async def connect_async(
|
|||||||
api_key: Optional[str] = None,
|
api_key: Optional[str] = None,
|
||||||
region: str = "us-east-1",
|
region: str = "us-east-1",
|
||||||
host_override: Optional[str] = None,
|
host_override: Optional[str] = None,
|
||||||
read_consistency_interval: Optional[timedelta] = timedelta(seconds=5),
|
read_consistency_interval: Optional[timedelta] = None,
|
||||||
client_config: Optional[Union[ClientConfig, Dict[str, Any]]] = None,
|
client_config: Optional[Union[ClientConfig, Dict[str, Any]]] = None,
|
||||||
storage_options: Optional[Dict[str, str]] = None,
|
storage_options: Optional[Dict[str, str]] = None,
|
||||||
) -> AsyncConnection:
|
) -> AsyncConnection:
|
||||||
@@ -142,8 +143,9 @@ async def connect_async(
|
|||||||
read_consistency_interval: timedelta, default None
|
read_consistency_interval: timedelta, default None
|
||||||
(For LanceDB OSS only)
|
(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 strong consistency,
|
processes. If None, then consistency is not checked. For performance
|
||||||
set this to zero seconds. Then every read will check for updates from other
|
reasons, this is the default. For strong consistency, set this to
|
||||||
|
zero seconds. Then every read will check for updates from other
|
||||||
processes. As a compromise, you can set this to a non-zero timedelta
|
processes. As a compromise, you can set this to a non-zero timedelta
|
||||||
for eventual consistency. If more than that interval has passed since
|
for eventual consistency. If more than that interval has passed since
|
||||||
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
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
from datetime import timedelta
|
||||||
from typing import Dict, List, Optional, Tuple, Any, Union, Literal
|
from typing import Dict, List, Optional, Tuple, Any, Union, Literal
|
||||||
|
|
||||||
import pyarrow as pa
|
import pyarrow as pa
|
||||||
@@ -94,7 +95,9 @@ class Query:
|
|||||||
def postfilter(self): ...
|
def postfilter(self): ...
|
||||||
def nearest_to(self, query_vec: pa.Array) -> VectorQuery: ...
|
def nearest_to(self, query_vec: pa.Array) -> VectorQuery: ...
|
||||||
def nearest_to_text(self, query: dict) -> FTSQuery: ...
|
def nearest_to_text(self, query: dict) -> FTSQuery: ...
|
||||||
async def execute(self, max_batch_length: Optional[int]) -> RecordBatchStream: ...
|
async def execute(
|
||||||
|
self, max_batch_length: Optional[int], timeout: Optional[timedelta]
|
||||||
|
) -> RecordBatchStream: ...
|
||||||
async def explain_plan(self, verbose: Optional[bool]) -> str: ...
|
async def explain_plan(self, verbose: Optional[bool]) -> str: ...
|
||||||
async def analyze_plan(self) -> str: ...
|
async def analyze_plan(self) -> str: ...
|
||||||
def to_query_request(self) -> PyQueryRequest: ...
|
def to_query_request(self) -> PyQueryRequest: ...
|
||||||
@@ -110,7 +113,9 @@ class FTSQuery:
|
|||||||
def get_query(self) -> str: ...
|
def get_query(self) -> str: ...
|
||||||
def add_query_vector(self, query_vec: pa.Array) -> None: ...
|
def add_query_vector(self, query_vec: pa.Array) -> None: ...
|
||||||
def nearest_to(self, query_vec: pa.Array) -> HybridQuery: ...
|
def nearest_to(self, query_vec: pa.Array) -> HybridQuery: ...
|
||||||
async def execute(self, max_batch_length: Optional[int]) -> RecordBatchStream: ...
|
async def execute(
|
||||||
|
self, max_batch_length: Optional[int], timeout: Optional[timedelta]
|
||||||
|
) -> RecordBatchStream: ...
|
||||||
def to_query_request(self) -> PyQueryRequest: ...
|
def to_query_request(self) -> PyQueryRequest: ...
|
||||||
|
|
||||||
class VectorQuery:
|
class VectorQuery:
|
||||||
|
|||||||
@@ -6,7 +6,6 @@ from __future__ import annotations
|
|||||||
|
|
||||||
from abc import abstractmethod
|
from abc import abstractmethod
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from datetime import timedelta
|
|
||||||
from typing import TYPE_CHECKING, Dict, Iterable, List, Literal, Optional, Union
|
from typing import TYPE_CHECKING, Dict, Iterable, List, Literal, Optional, Union
|
||||||
|
|
||||||
from lancedb.embeddings.registry import EmbeddingFunctionRegistry
|
from lancedb.embeddings.registry import EmbeddingFunctionRegistry
|
||||||
@@ -33,6 +32,7 @@ import deprecation
|
|||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
import pyarrow as pa
|
import pyarrow as pa
|
||||||
from .pydantic import LanceModel
|
from .pydantic import LanceModel
|
||||||
|
from datetime import timedelta
|
||||||
|
|
||||||
from ._lancedb import Connection as LanceDbConnection
|
from ._lancedb import Connection as LanceDbConnection
|
||||||
from .common import DATA, URI
|
from .common import DATA, URI
|
||||||
@@ -318,8 +318,9 @@ class LanceDBConnection(DBConnection):
|
|||||||
The root uri of the database.
|
The root uri of the database.
|
||||||
read_consistency_interval: timedelta, default None
|
read_consistency_interval: timedelta, default None
|
||||||
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 strong consistency,
|
processes. If None, then consistency is not checked. For performance
|
||||||
set this to zero seconds. Then every read will check for updates from other
|
reasons, this is the default. For strong consistency, set this to
|
||||||
|
zero seconds. Then every read will check for updates from other
|
||||||
processes. As a compromise, you can set this to a non-zero timedelta
|
processes. As a compromise, you can set this to a non-zero timedelta
|
||||||
for eventual consistency. If more than that interval has passed since
|
for eventual consistency. If more than that interval has passed since
|
||||||
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
|
||||||
@@ -351,7 +352,7 @@ class LanceDBConnection(DBConnection):
|
|||||||
self,
|
self,
|
||||||
uri: URI,
|
uri: URI,
|
||||||
*,
|
*,
|
||||||
read_consistency_interval: Optional[timedelta] = timedelta(seconds=5),
|
read_consistency_interval: Optional[timedelta] = None,
|
||||||
storage_options: Optional[Dict[str, str]] = None,
|
storage_options: Optional[Dict[str, str]] = None,
|
||||||
):
|
):
|
||||||
if not isinstance(uri, Path):
|
if not isinstance(uri, Path):
|
||||||
|
|||||||
@@ -1,9 +1,12 @@
|
|||||||
# SPDX-License-Identifier: Apache-2.0
|
# SPDX-License-Identifier: Apache-2.0
|
||||||
# SPDX-FileCopyrightText: Copyright The LanceDB Authors
|
# SPDX-FileCopyrightText: Copyright The LanceDB Authors
|
||||||
|
import base64
|
||||||
|
|
||||||
import os
|
import os
|
||||||
from typing import ClassVar, TYPE_CHECKING, List, Union
|
from typing import ClassVar, TYPE_CHECKING, List, Union, Any
|
||||||
|
|
||||||
|
from pathlib import Path
|
||||||
|
from urllib.parse import urlparse
|
||||||
|
from io import BytesIO
|
||||||
|
|
||||||
import numpy as np
|
import numpy as np
|
||||||
import pyarrow as pa
|
import pyarrow as pa
|
||||||
@@ -11,12 +14,100 @@ import pyarrow as pa
|
|||||||
from ..util import attempt_import_or_raise
|
from ..util import attempt_import_or_raise
|
||||||
from .base import EmbeddingFunction
|
from .base import EmbeddingFunction
|
||||||
from .registry import register
|
from .registry import register
|
||||||
from .utils import api_key_not_found_help, IMAGES
|
from .utils import api_key_not_found_help, IMAGES, TEXT
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
import PIL
|
import PIL
|
||||||
|
|
||||||
|
|
||||||
|
def is_valid_url(text):
|
||||||
|
try:
|
||||||
|
parsed = urlparse(text)
|
||||||
|
return bool(parsed.scheme) and bool(parsed.netloc)
|
||||||
|
except Exception:
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
def transform_input(input_data: Union[str, bytes, Path]):
|
||||||
|
PIL = attempt_import_or_raise("PIL", "pillow")
|
||||||
|
if isinstance(input_data, str):
|
||||||
|
if is_valid_url(input_data):
|
||||||
|
content = {"type": "image_url", "image_url": input_data}
|
||||||
|
else:
|
||||||
|
content = {"type": "text", "text": input_data}
|
||||||
|
elif isinstance(input_data, PIL.Image.Image):
|
||||||
|
buffered = BytesIO()
|
||||||
|
input_data.save(buffered, format="JPEG")
|
||||||
|
img_str = base64.b64encode(buffered.getvalue()).decode("utf-8")
|
||||||
|
content = {
|
||||||
|
"type": "image_base64",
|
||||||
|
"image_base64": "data:image/jpeg;base64," + img_str,
|
||||||
|
}
|
||||||
|
elif isinstance(input_data, bytes):
|
||||||
|
img = PIL.Image.open(BytesIO(input_data))
|
||||||
|
buffered = BytesIO()
|
||||||
|
img.save(buffered, format="JPEG")
|
||||||
|
img_str = base64.b64encode(buffered.getvalue()).decode("utf-8")
|
||||||
|
content = {
|
||||||
|
"type": "image_base64",
|
||||||
|
"image_base64": "data:image/jpeg;base64," + img_str,
|
||||||
|
}
|
||||||
|
elif isinstance(input_data, Path):
|
||||||
|
img = PIL.Image.open(input_data)
|
||||||
|
buffered = BytesIO()
|
||||||
|
img.save(buffered, format="JPEG")
|
||||||
|
img_str = base64.b64encode(buffered.getvalue()).decode("utf-8")
|
||||||
|
content = {
|
||||||
|
"type": "image_base64",
|
||||||
|
"image_base64": "data:image/jpeg;base64," + img_str,
|
||||||
|
}
|
||||||
|
else:
|
||||||
|
raise ValueError("Each input should be either str, bytes, Path or Image.")
|
||||||
|
|
||||||
|
return {"content": [content]}
|
||||||
|
|
||||||
|
|
||||||
|
def sanitize_multimodal_input(inputs: Union[TEXT, IMAGES]) -> List[Any]:
|
||||||
|
"""
|
||||||
|
Sanitize the input to the embedding function.
|
||||||
|
"""
|
||||||
|
PIL = attempt_import_or_raise("PIL", "pillow")
|
||||||
|
if isinstance(inputs, (str, bytes, Path, PIL.Image.Image)):
|
||||||
|
inputs = [inputs]
|
||||||
|
elif isinstance(inputs, pa.Array):
|
||||||
|
inputs = inputs.to_pylist()
|
||||||
|
elif isinstance(inputs, pa.ChunkedArray):
|
||||||
|
inputs = inputs.combine_chunks().to_pylist()
|
||||||
|
else:
|
||||||
|
raise ValueError(
|
||||||
|
f"Input type {type(inputs)} not allowed with multimodal model."
|
||||||
|
)
|
||||||
|
|
||||||
|
if not all(isinstance(x, (str, bytes, Path, PIL.Image.Image)) for x in inputs):
|
||||||
|
raise ValueError("Each input should be either str, bytes, Path or Image.")
|
||||||
|
|
||||||
|
return [transform_input(i) for i in inputs]
|
||||||
|
|
||||||
|
|
||||||
|
def sanitize_text_input(inputs: TEXT) -> List[str]:
|
||||||
|
"""
|
||||||
|
Sanitize the input to the embedding function.
|
||||||
|
"""
|
||||||
|
if isinstance(inputs, str):
|
||||||
|
inputs = [inputs]
|
||||||
|
elif isinstance(inputs, pa.Array):
|
||||||
|
inputs = inputs.to_pylist()
|
||||||
|
elif isinstance(inputs, pa.ChunkedArray):
|
||||||
|
inputs = inputs.combine_chunks().to_pylist()
|
||||||
|
else:
|
||||||
|
raise ValueError(f"Input type {type(inputs)} not allowed with text model.")
|
||||||
|
|
||||||
|
if not all(isinstance(x, str) for x in inputs):
|
||||||
|
raise ValueError("Each input should be str.")
|
||||||
|
|
||||||
|
return inputs
|
||||||
|
|
||||||
|
|
||||||
@register("voyageai")
|
@register("voyageai")
|
||||||
class VoyageAIEmbeddingFunction(EmbeddingFunction):
|
class VoyageAIEmbeddingFunction(EmbeddingFunction):
|
||||||
"""
|
"""
|
||||||
@@ -74,6 +165,11 @@ class VoyageAIEmbeddingFunction(EmbeddingFunction):
|
|||||||
]
|
]
|
||||||
multimodal_embedding_models: list = ["voyage-multimodal-3"]
|
multimodal_embedding_models: list = ["voyage-multimodal-3"]
|
||||||
|
|
||||||
|
def _is_multimodal_model(self, model_name: str):
|
||||||
|
return (
|
||||||
|
model_name in self.multimodal_embedding_models or "multimodal" in model_name
|
||||||
|
)
|
||||||
|
|
||||||
def ndims(self):
|
def ndims(self):
|
||||||
if self.name == "voyage-3-lite":
|
if self.name == "voyage-3-lite":
|
||||||
return 512
|
return 512
|
||||||
@@ -85,55 +181,12 @@ class VoyageAIEmbeddingFunction(EmbeddingFunction):
|
|||||||
"voyage-finance-2",
|
"voyage-finance-2",
|
||||||
"voyage-multilingual-2",
|
"voyage-multilingual-2",
|
||||||
"voyage-law-2",
|
"voyage-law-2",
|
||||||
|
"voyage-multimodal-3",
|
||||||
]:
|
]:
|
||||||
return 1024
|
return 1024
|
||||||
else:
|
else:
|
||||||
raise ValueError(f"Model {self.name} not supported")
|
raise ValueError(f"Model {self.name} not supported")
|
||||||
|
|
||||||
def sanitize_input(self, images: IMAGES) -> Union[List[bytes], np.ndarray]:
|
|
||||||
"""
|
|
||||||
Sanitize the input to the embedding function.
|
|
||||||
"""
|
|
||||||
if isinstance(images, (str, bytes)):
|
|
||||||
images = [images]
|
|
||||||
elif isinstance(images, pa.Array):
|
|
||||||
images = images.to_pylist()
|
|
||||||
elif isinstance(images, pa.ChunkedArray):
|
|
||||||
images = images.combine_chunks().to_pylist()
|
|
||||||
return images
|
|
||||||
|
|
||||||
def generate_text_embeddings(self, text: str, **kwargs) -> np.ndarray:
|
|
||||||
"""
|
|
||||||
Get the embeddings for the given texts
|
|
||||||
|
|
||||||
Parameters
|
|
||||||
----------
|
|
||||||
texts: list[str] or np.ndarray (of str)
|
|
||||||
The texts to embed
|
|
||||||
input_type: Optional[str]
|
|
||||||
|
|
||||||
truncation: Optional[bool]
|
|
||||||
"""
|
|
||||||
client = VoyageAIEmbeddingFunction._get_client()
|
|
||||||
if self.name in self.text_embedding_models:
|
|
||||||
rs = client.embed(texts=[text], model=self.name, **kwargs)
|
|
||||||
elif self.name in self.multimodal_embedding_models:
|
|
||||||
rs = client.multimodal_embed(inputs=[[text]], model=self.name, **kwargs)
|
|
||||||
else:
|
|
||||||
raise ValueError(
|
|
||||||
f"Model {self.name} not supported to generate text embeddings"
|
|
||||||
)
|
|
||||||
|
|
||||||
return rs.embeddings[0]
|
|
||||||
|
|
||||||
def generate_image_embedding(
|
|
||||||
self, image: "PIL.Image.Image", **kwargs
|
|
||||||
) -> np.ndarray:
|
|
||||||
rs = VoyageAIEmbeddingFunction._get_client().multimodal_embed(
|
|
||||||
inputs=[[image]], model=self.name, **kwargs
|
|
||||||
)
|
|
||||||
return rs.embeddings[0]
|
|
||||||
|
|
||||||
def compute_query_embeddings(
|
def compute_query_embeddings(
|
||||||
self, query: Union[str, "PIL.Image.Image"], *args, **kwargs
|
self, query: Union[str, "PIL.Image.Image"], *args, **kwargs
|
||||||
) -> List[np.ndarray]:
|
) -> List[np.ndarray]:
|
||||||
@@ -144,23 +197,52 @@ class VoyageAIEmbeddingFunction(EmbeddingFunction):
|
|||||||
----------
|
----------
|
||||||
query : Union[str, PIL.Image.Image]
|
query : Union[str, PIL.Image.Image]
|
||||||
The query to embed. A query can be either text or an image.
|
The query to embed. A query can be either text or an image.
|
||||||
|
|
||||||
|
Returns
|
||||||
|
-------
|
||||||
|
List[np.array]: the list of embeddings
|
||||||
"""
|
"""
|
||||||
if isinstance(query, str):
|
client = VoyageAIEmbeddingFunction._get_client()
|
||||||
return [self.generate_text_embeddings(query, input_type="query")]
|
if self._is_multimodal_model(self.name):
|
||||||
|
result = client.multimodal_embed(
|
||||||
|
inputs=[[query]], model=self.name, input_type="query", **kwargs
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
PIL = attempt_import_or_raise("PIL", "pillow")
|
result = client.embed(
|
||||||
if isinstance(query, PIL.Image.Image):
|
texts=[query], model=self.name, input_type="query", **kwargs
|
||||||
return [self.generate_image_embedding(query, input_type="query")]
|
)
|
||||||
else:
|
|
||||||
raise TypeError("Only text PIL images supported as query")
|
return [result.embeddings[0]]
|
||||||
|
|
||||||
def compute_source_embeddings(
|
def compute_source_embeddings(
|
||||||
self, images: IMAGES, *args, **kwargs
|
self, inputs: Union[TEXT, IMAGES], *args, **kwargs
|
||||||
) -> List[np.array]:
|
) -> List[np.array]:
|
||||||
images = self.sanitize_input(images)
|
"""
|
||||||
return [
|
Compute the embeddings for the inputs
|
||||||
self.generate_image_embedding(img, input_type="document") for img in images
|
|
||||||
]
|
Parameters
|
||||||
|
----------
|
||||||
|
inputs : Union[TEXT, IMAGES]
|
||||||
|
The inputs to embed. The input can be either str, bytes, Path (to an image),
|
||||||
|
PIL.Image or list of these.
|
||||||
|
|
||||||
|
Returns
|
||||||
|
-------
|
||||||
|
List[np.array]: the list of embeddings
|
||||||
|
"""
|
||||||
|
client = VoyageAIEmbeddingFunction._get_client()
|
||||||
|
if self._is_multimodal_model(self.name):
|
||||||
|
inputs = sanitize_multimodal_input(inputs)
|
||||||
|
result = client.multimodal_embed(
|
||||||
|
inputs=inputs, model=self.name, input_type="document", **kwargs
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
inputs = sanitize_text_input(inputs)
|
||||||
|
result = client.embed(
|
||||||
|
texts=inputs, model=self.name, input_type="document", **kwargs
|
||||||
|
)
|
||||||
|
|
||||||
|
return result.embeddings
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _get_client():
|
def _get_client():
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ from abc import ABC, abstractmethod
|
|||||||
import abc
|
import abc
|
||||||
from concurrent.futures import ThreadPoolExecutor
|
from concurrent.futures import ThreadPoolExecutor
|
||||||
from enum import Enum
|
from enum import Enum
|
||||||
|
from datetime import timedelta
|
||||||
from typing import (
|
from typing import (
|
||||||
TYPE_CHECKING,
|
TYPE_CHECKING,
|
||||||
Dict,
|
Dict,
|
||||||
@@ -265,8 +266,8 @@ class MultiMatchQuery(FullTextQuery):
|
|||||||
|
|
||||||
Parameters
|
Parameters
|
||||||
----------
|
----------
|
||||||
query : str | list[Query]
|
query : str
|
||||||
If a string, the query string to match against.
|
The query string to match against.
|
||||||
|
|
||||||
columns : list[str]
|
columns : list[str]
|
||||||
The list of columns to match against.
|
The list of columns to match against.
|
||||||
@@ -650,7 +651,12 @@ class LanceQueryBuilder(ABC):
|
|||||||
"""
|
"""
|
||||||
return self.to_pandas()
|
return self.to_pandas()
|
||||||
|
|
||||||
def to_pandas(self, flatten: Optional[Union[int, bool]] = None) -> "pd.DataFrame":
|
def to_pandas(
|
||||||
|
self,
|
||||||
|
flatten: Optional[Union[int, bool]] = None,
|
||||||
|
*,
|
||||||
|
timeout: Optional[timedelta] = None,
|
||||||
|
) -> "pd.DataFrame":
|
||||||
"""
|
"""
|
||||||
Execute the query and return the results as a pandas DataFrame.
|
Execute the query and return the results as a pandas DataFrame.
|
||||||
In addition to the selected columns, LanceDB also returns a vector
|
In addition to the selected columns, LanceDB also returns a vector
|
||||||
@@ -664,12 +670,15 @@ class LanceQueryBuilder(ABC):
|
|||||||
If flatten is an integer, flatten the nested columns up to the
|
If flatten is an integer, flatten the nested columns up to the
|
||||||
specified depth.
|
specified depth.
|
||||||
If unspecified, do not flatten the nested columns.
|
If unspecified, do not flatten the nested columns.
|
||||||
|
timeout: Optional[timedelta]
|
||||||
|
The maximum time to wait for the query to complete.
|
||||||
|
If None, wait indefinitely.
|
||||||
"""
|
"""
|
||||||
tbl = flatten_columns(self.to_arrow(), flatten)
|
tbl = flatten_columns(self.to_arrow(timeout=timeout), flatten)
|
||||||
return tbl.to_pandas()
|
return tbl.to_pandas()
|
||||||
|
|
||||||
@abstractmethod
|
@abstractmethod
|
||||||
def to_arrow(self) -> pa.Table:
|
def to_arrow(self, *, timeout: Optional[timedelta] = None) -> pa.Table:
|
||||||
"""
|
"""
|
||||||
Execute the query and return the results as an
|
Execute the query and return the results as an
|
||||||
[Apache Arrow Table](https://arrow.apache.org/docs/python/generated/pyarrow.Table.html#pyarrow.Table).
|
[Apache Arrow Table](https://arrow.apache.org/docs/python/generated/pyarrow.Table.html#pyarrow.Table).
|
||||||
@@ -677,34 +686,65 @@ class LanceQueryBuilder(ABC):
|
|||||||
In addition to the selected columns, LanceDB also returns a vector
|
In addition to the selected columns, LanceDB also returns a vector
|
||||||
and also the "_distance" column which is the distance between the query
|
and also the "_distance" column which is the distance between the query
|
||||||
vector and the returned vectors.
|
vector and the returned vectors.
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
timeout: Optional[timedelta]
|
||||||
|
The maximum time to wait for the query to complete.
|
||||||
|
If None, wait indefinitely.
|
||||||
"""
|
"""
|
||||||
raise NotImplementedError
|
raise NotImplementedError
|
||||||
|
|
||||||
@abstractmethod
|
@abstractmethod
|
||||||
def to_batches(self, /, batch_size: Optional[int] = None) -> pa.RecordBatchReader:
|
def to_batches(
|
||||||
|
self,
|
||||||
|
/,
|
||||||
|
batch_size: Optional[int] = None,
|
||||||
|
*,
|
||||||
|
timeout: Optional[timedelta] = None,
|
||||||
|
) -> pa.RecordBatchReader:
|
||||||
"""
|
"""
|
||||||
Execute the query and return the results as a pyarrow
|
Execute the query and return the results as a pyarrow
|
||||||
[RecordBatchReader](https://arrow.apache.org/docs/python/generated/pyarrow.RecordBatchReader.html)
|
[RecordBatchReader](https://arrow.apache.org/docs/python/generated/pyarrow.RecordBatchReader.html)
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
batch_size: int
|
||||||
|
The maximum number of selected records in a RecordBatch object.
|
||||||
|
timeout: Optional[timedelta]
|
||||||
|
The maximum time to wait for the query to complete.
|
||||||
|
If None, wait indefinitely.
|
||||||
"""
|
"""
|
||||||
raise NotImplementedError
|
raise NotImplementedError
|
||||||
|
|
||||||
def to_list(self) -> List[dict]:
|
def to_list(self, *, timeout: Optional[timedelta] = None) -> List[dict]:
|
||||||
"""
|
"""
|
||||||
Execute the query and return the results as a list of dictionaries.
|
Execute the query and return the results as a list of dictionaries.
|
||||||
|
|
||||||
Each list entry is a dictionary with the selected column names as keys,
|
Each list entry is a dictionary with the selected column names as keys,
|
||||||
or all table columns if `select` is not called. The vector and the "_distance"
|
or all table columns if `select` is not called. The vector and the "_distance"
|
||||||
fields are returned whether or not they're explicitly selected.
|
fields are returned whether or not they're explicitly selected.
|
||||||
"""
|
|
||||||
return self.to_arrow().to_pylist()
|
|
||||||
|
|
||||||
def to_pydantic(self, model: Type[LanceModel]) -> List[LanceModel]:
|
Parameters
|
||||||
|
----------
|
||||||
|
timeout: Optional[timedelta]
|
||||||
|
The maximum time to wait for the query to complete.
|
||||||
|
If None, wait indefinitely.
|
||||||
|
"""
|
||||||
|
return self.to_arrow(timeout=timeout).to_pylist()
|
||||||
|
|
||||||
|
def to_pydantic(
|
||||||
|
self, model: Type[LanceModel], *, timeout: Optional[timedelta] = None
|
||||||
|
) -> List[LanceModel]:
|
||||||
"""Return the table as a list of pydantic models.
|
"""Return the table as a list of pydantic models.
|
||||||
|
|
||||||
Parameters
|
Parameters
|
||||||
----------
|
----------
|
||||||
model: Type[LanceModel]
|
model: Type[LanceModel]
|
||||||
The pydantic model to use.
|
The pydantic model to use.
|
||||||
|
timeout: Optional[timedelta]
|
||||||
|
The maximum time to wait for the query to complete.
|
||||||
|
If None, wait indefinitely.
|
||||||
|
|
||||||
Returns
|
Returns
|
||||||
-------
|
-------
|
||||||
@@ -712,19 +752,25 @@ class LanceQueryBuilder(ABC):
|
|||||||
"""
|
"""
|
||||||
return [
|
return [
|
||||||
model(**{k: v for k, v in row.items() if k in model.field_names()})
|
model(**{k: v for k, v in row.items() if k in model.field_names()})
|
||||||
for row in self.to_arrow().to_pylist()
|
for row in self.to_arrow(timeout=timeout).to_pylist()
|
||||||
]
|
]
|
||||||
|
|
||||||
def to_polars(self) -> "pl.DataFrame":
|
def to_polars(self, *, timeout: Optional[timedelta] = None) -> "pl.DataFrame":
|
||||||
"""
|
"""
|
||||||
Execute the query and return the results as a Polars DataFrame.
|
Execute the query and return the results as a Polars DataFrame.
|
||||||
In addition to the selected columns, LanceDB also returns a vector
|
In addition to the selected columns, LanceDB also returns a vector
|
||||||
and also the "_distance" column which is the distance between the query
|
and also the "_distance" column which is the distance between the query
|
||||||
vector and the returned vector.
|
vector and the returned vector.
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
timeout: Optional[timedelta]
|
||||||
|
The maximum time to wait for the query to complete.
|
||||||
|
If None, wait indefinitely.
|
||||||
"""
|
"""
|
||||||
import polars as pl
|
import polars as pl
|
||||||
|
|
||||||
return pl.from_arrow(self.to_arrow())
|
return pl.from_arrow(self.to_arrow(timeout=timeout))
|
||||||
|
|
||||||
def limit(self, limit: Union[int, None]) -> Self:
|
def limit(self, limit: Union[int, None]) -> Self:
|
||||||
"""Set the maximum number of results to return.
|
"""Set the maximum number of results to return.
|
||||||
@@ -1139,7 +1185,7 @@ class LanceVectorQueryBuilder(LanceQueryBuilder):
|
|||||||
self._refine_factor = refine_factor
|
self._refine_factor = refine_factor
|
||||||
return self
|
return self
|
||||||
|
|
||||||
def to_arrow(self) -> pa.Table:
|
def to_arrow(self, *, timeout: Optional[timedelta] = None) -> pa.Table:
|
||||||
"""
|
"""
|
||||||
Execute the query and return the results as an
|
Execute the query and return the results as an
|
||||||
[Apache Arrow Table](https://arrow.apache.org/docs/python/generated/pyarrow.Table.html#pyarrow.Table).
|
[Apache Arrow Table](https://arrow.apache.org/docs/python/generated/pyarrow.Table.html#pyarrow.Table).
|
||||||
@@ -1147,8 +1193,14 @@ class LanceVectorQueryBuilder(LanceQueryBuilder):
|
|||||||
In addition to the selected columns, LanceDB also returns a vector
|
In addition to the selected columns, LanceDB also returns a vector
|
||||||
and also the "_distance" column which is the distance between the query
|
and also the "_distance" column which is the distance between the query
|
||||||
vector and the returned vectors.
|
vector and the returned vectors.
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
timeout: Optional[timedelta]
|
||||||
|
The maximum time to wait for the query to complete.
|
||||||
|
If None, wait indefinitely.
|
||||||
"""
|
"""
|
||||||
return self.to_batches().read_all()
|
return self.to_batches(timeout=timeout).read_all()
|
||||||
|
|
||||||
def to_query_object(self) -> Query:
|
def to_query_object(self) -> Query:
|
||||||
"""
|
"""
|
||||||
@@ -1178,7 +1230,13 @@ class LanceVectorQueryBuilder(LanceQueryBuilder):
|
|||||||
bypass_vector_index=self._bypass_vector_index,
|
bypass_vector_index=self._bypass_vector_index,
|
||||||
)
|
)
|
||||||
|
|
||||||
def to_batches(self, /, batch_size: Optional[int] = None) -> pa.RecordBatchReader:
|
def to_batches(
|
||||||
|
self,
|
||||||
|
/,
|
||||||
|
batch_size: Optional[int] = None,
|
||||||
|
*,
|
||||||
|
timeout: Optional[timedelta] = None,
|
||||||
|
) -> pa.RecordBatchReader:
|
||||||
"""
|
"""
|
||||||
Execute the query and return the result as a RecordBatchReader object.
|
Execute the query and return the result as a RecordBatchReader object.
|
||||||
|
|
||||||
@@ -1186,6 +1244,9 @@ class LanceVectorQueryBuilder(LanceQueryBuilder):
|
|||||||
----------
|
----------
|
||||||
batch_size: int
|
batch_size: int
|
||||||
The maximum number of selected records in a RecordBatch object.
|
The maximum number of selected records in a RecordBatch object.
|
||||||
|
timeout: timedelta, default None
|
||||||
|
The maximum time to wait for the query to complete.
|
||||||
|
If None, wait indefinitely.
|
||||||
|
|
||||||
Returns
|
Returns
|
||||||
-------
|
-------
|
||||||
@@ -1195,7 +1256,9 @@ class LanceVectorQueryBuilder(LanceQueryBuilder):
|
|||||||
if isinstance(vector[0], np.ndarray):
|
if isinstance(vector[0], np.ndarray):
|
||||||
vector = [v.tolist() for v in vector]
|
vector = [v.tolist() for v in vector]
|
||||||
query = self.to_query_object()
|
query = self.to_query_object()
|
||||||
result_set = self._table._execute_query(query, batch_size)
|
result_set = self._table._execute_query(
|
||||||
|
query, batch_size=batch_size, timeout=timeout
|
||||||
|
)
|
||||||
if self._reranker is not None:
|
if self._reranker is not None:
|
||||||
rs_table = result_set.read_all()
|
rs_table = result_set.read_all()
|
||||||
result_set = self._reranker.rerank_vector(self._str_query, rs_table)
|
result_set = self._reranker.rerank_vector(self._str_query, rs_table)
|
||||||
@@ -1334,7 +1397,7 @@ class LanceFtsQueryBuilder(LanceQueryBuilder):
|
|||||||
offset=self._offset,
|
offset=self._offset,
|
||||||
)
|
)
|
||||||
|
|
||||||
def to_arrow(self) -> pa.Table:
|
def to_arrow(self, *, timeout: Optional[timedelta] = None) -> pa.Table:
|
||||||
path, fs, exist = self._table._get_fts_index_path()
|
path, fs, exist = self._table._get_fts_index_path()
|
||||||
if exist:
|
if exist:
|
||||||
return self.tantivy_to_arrow()
|
return self.tantivy_to_arrow()
|
||||||
@@ -1346,14 +1409,16 @@ class LanceFtsQueryBuilder(LanceQueryBuilder):
|
|||||||
"Use tantivy-based index instead for now."
|
"Use tantivy-based index instead for now."
|
||||||
)
|
)
|
||||||
query = self.to_query_object()
|
query = self.to_query_object()
|
||||||
results = self._table._execute_query(query)
|
results = self._table._execute_query(query, timeout=timeout)
|
||||||
results = results.read_all()
|
results = results.read_all()
|
||||||
if self._reranker is not None:
|
if self._reranker is not None:
|
||||||
results = self._reranker.rerank_fts(self._query, results)
|
results = self._reranker.rerank_fts(self._query, results)
|
||||||
check_reranker_result(results)
|
check_reranker_result(results)
|
||||||
return results
|
return results
|
||||||
|
|
||||||
def to_batches(self, /, batch_size: Optional[int] = None):
|
def to_batches(
|
||||||
|
self, /, batch_size: Optional[int] = None, timeout: Optional[timedelta] = None
|
||||||
|
):
|
||||||
raise NotImplementedError("to_batches on an FTS query")
|
raise NotImplementedError("to_batches on an FTS query")
|
||||||
|
|
||||||
def tantivy_to_arrow(self) -> pa.Table:
|
def tantivy_to_arrow(self) -> pa.Table:
|
||||||
@@ -1458,8 +1523,8 @@ class LanceFtsQueryBuilder(LanceQueryBuilder):
|
|||||||
|
|
||||||
|
|
||||||
class LanceEmptyQueryBuilder(LanceQueryBuilder):
|
class LanceEmptyQueryBuilder(LanceQueryBuilder):
|
||||||
def to_arrow(self) -> pa.Table:
|
def to_arrow(self, *, timeout: Optional[timedelta] = None) -> pa.Table:
|
||||||
return self.to_batches().read_all()
|
return self.to_batches(timeout=timeout).read_all()
|
||||||
|
|
||||||
def to_query_object(self) -> Query:
|
def to_query_object(self) -> Query:
|
||||||
return Query(
|
return Query(
|
||||||
@@ -1470,9 +1535,11 @@ class LanceEmptyQueryBuilder(LanceQueryBuilder):
|
|||||||
offset=self._offset,
|
offset=self._offset,
|
||||||
)
|
)
|
||||||
|
|
||||||
def to_batches(self, /, batch_size: Optional[int] = None) -> pa.RecordBatchReader:
|
def to_batches(
|
||||||
|
self, /, batch_size: Optional[int] = None, timeout: Optional[timedelta] = None
|
||||||
|
) -> pa.RecordBatchReader:
|
||||||
query = self.to_query_object()
|
query = self.to_query_object()
|
||||||
return self._table._execute_query(query, batch_size)
|
return self._table._execute_query(query, batch_size=batch_size, timeout=timeout)
|
||||||
|
|
||||||
def rerank(self, reranker: Reranker) -> LanceEmptyQueryBuilder:
|
def rerank(self, reranker: Reranker) -> LanceEmptyQueryBuilder:
|
||||||
"""Rerank the results using the specified reranker.
|
"""Rerank the results using the specified reranker.
|
||||||
@@ -1560,7 +1627,7 @@ class LanceHybridQueryBuilder(LanceQueryBuilder):
|
|||||||
def to_query_object(self) -> Query:
|
def to_query_object(self) -> Query:
|
||||||
raise NotImplementedError("to_query_object not yet supported on a hybrid query")
|
raise NotImplementedError("to_query_object not yet supported on a hybrid query")
|
||||||
|
|
||||||
def to_arrow(self) -> pa.Table:
|
def to_arrow(self, *, timeout: Optional[timedelta] = None) -> pa.Table:
|
||||||
vector_query, fts_query = self._validate_query(
|
vector_query, fts_query = self._validate_query(
|
||||||
self._query, self._vector, self._text
|
self._query, self._vector, self._text
|
||||||
)
|
)
|
||||||
@@ -1603,9 +1670,11 @@ class LanceHybridQueryBuilder(LanceQueryBuilder):
|
|||||||
self._reranker = RRFReranker()
|
self._reranker = RRFReranker()
|
||||||
|
|
||||||
with ThreadPoolExecutor() as executor:
|
with ThreadPoolExecutor() as executor:
|
||||||
fts_future = executor.submit(self._fts_query.with_row_id(True).to_arrow)
|
fts_future = executor.submit(
|
||||||
|
self._fts_query.with_row_id(True).to_arrow, timeout=timeout
|
||||||
|
)
|
||||||
vector_future = executor.submit(
|
vector_future = executor.submit(
|
||||||
self._vector_query.with_row_id(True).to_arrow
|
self._vector_query.with_row_id(True).to_arrow, timeout=timeout
|
||||||
)
|
)
|
||||||
fts_results = fts_future.result()
|
fts_results = fts_future.result()
|
||||||
vector_results = vector_future.result()
|
vector_results = vector_future.result()
|
||||||
@@ -1692,7 +1761,9 @@ class LanceHybridQueryBuilder(LanceQueryBuilder):
|
|||||||
|
|
||||||
return results
|
return results
|
||||||
|
|
||||||
def to_batches(self):
|
def to_batches(
|
||||||
|
self, /, batch_size: Optional[int] = None, timeout: Optional[timedelta] = None
|
||||||
|
):
|
||||||
raise NotImplementedError("to_batches not yet supported on a hybrid query")
|
raise NotImplementedError("to_batches not yet supported on a hybrid query")
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
@@ -2056,7 +2127,10 @@ class AsyncQueryBase(object):
|
|||||||
return self
|
return self
|
||||||
|
|
||||||
async def to_batches(
|
async def to_batches(
|
||||||
self, *, max_batch_length: Optional[int] = None
|
self,
|
||||||
|
*,
|
||||||
|
max_batch_length: Optional[int] = None,
|
||||||
|
timeout: Optional[timedelta] = None,
|
||||||
) -> AsyncRecordBatchReader:
|
) -> AsyncRecordBatchReader:
|
||||||
"""
|
"""
|
||||||
Execute the query and return the results as an Apache Arrow RecordBatchReader.
|
Execute the query and return the results as an Apache Arrow RecordBatchReader.
|
||||||
@@ -2069,34 +2143,56 @@ class AsyncQueryBase(object):
|
|||||||
If not specified, a default batch length is used.
|
If not specified, a default batch length is used.
|
||||||
It is possible for batches to be smaller than the provided length if the
|
It is possible for batches to be smaller than the provided length if the
|
||||||
underlying data is stored in smaller chunks.
|
underlying data is stored in smaller chunks.
|
||||||
|
timeout: Optional[timedelta]
|
||||||
|
The maximum time to wait for the query to complete.
|
||||||
|
If not specified, no timeout is applied. If the query does not
|
||||||
|
complete within the specified time, an error will be raised.
|
||||||
"""
|
"""
|
||||||
return AsyncRecordBatchReader(await self._inner.execute(max_batch_length))
|
return AsyncRecordBatchReader(
|
||||||
|
await self._inner.execute(max_batch_length, timeout)
|
||||||
|
)
|
||||||
|
|
||||||
async def to_arrow(self) -> pa.Table:
|
async def to_arrow(self, timeout: Optional[timedelta] = None) -> pa.Table:
|
||||||
"""
|
"""
|
||||||
Execute the query and collect the results into an Apache Arrow Table.
|
Execute the query and collect the results into an Apache Arrow Table.
|
||||||
|
|
||||||
This method will collect all results into memory before returning. If
|
This method will collect all results into memory before returning. If
|
||||||
you expect a large number of results, you may want to use
|
you expect a large number of results, you may want to use
|
||||||
[to_batches][lancedb.query.AsyncQueryBase.to_batches]
|
[to_batches][lancedb.query.AsyncQueryBase.to_batches]
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
timeout: Optional[timedelta]
|
||||||
|
The maximum time to wait for the query to complete.
|
||||||
|
If not specified, no timeout is applied. If the query does not
|
||||||
|
complete within the specified time, an error will be raised.
|
||||||
"""
|
"""
|
||||||
batch_iter = await self.to_batches()
|
batch_iter = await self.to_batches(timeout=timeout)
|
||||||
return pa.Table.from_batches(
|
return pa.Table.from_batches(
|
||||||
await batch_iter.read_all(), schema=batch_iter.schema
|
await batch_iter.read_all(), schema=batch_iter.schema
|
||||||
)
|
)
|
||||||
|
|
||||||
async def to_list(self) -> List[dict]:
|
async def to_list(self, timeout: Optional[timedelta] = None) -> List[dict]:
|
||||||
"""
|
"""
|
||||||
Execute the query and return the results as a list of dictionaries.
|
Execute the query and return the results as a list of dictionaries.
|
||||||
|
|
||||||
Each list entry is a dictionary with the selected column names as keys,
|
Each list entry is a dictionary with the selected column names as keys,
|
||||||
or all table columns if `select` is not called. The vector and the "_distance"
|
or all table columns if `select` is not called. The vector and the "_distance"
|
||||||
fields are returned whether or not they're explicitly selected.
|
fields are returned whether or not they're explicitly selected.
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
timeout: Optional[timedelta]
|
||||||
|
The maximum time to wait for the query to complete.
|
||||||
|
If not specified, no timeout is applied. If the query does not
|
||||||
|
complete within the specified time, an error will be raised.
|
||||||
"""
|
"""
|
||||||
return (await self.to_arrow()).to_pylist()
|
return (await self.to_arrow(timeout=timeout)).to_pylist()
|
||||||
|
|
||||||
async def to_pandas(
|
async def to_pandas(
|
||||||
self, flatten: Optional[Union[int, bool]] = None
|
self,
|
||||||
|
flatten: Optional[Union[int, bool]] = None,
|
||||||
|
timeout: Optional[timedelta] = None,
|
||||||
) -> "pd.DataFrame":
|
) -> "pd.DataFrame":
|
||||||
"""
|
"""
|
||||||
Execute the query and collect the results into a pandas DataFrame.
|
Execute the query and collect the results into a pandas DataFrame.
|
||||||
@@ -2125,10 +2221,19 @@ class AsyncQueryBase(object):
|
|||||||
If flatten is an integer, flatten the nested columns up to the
|
If flatten is an integer, flatten the nested columns up to the
|
||||||
specified depth.
|
specified depth.
|
||||||
If unspecified, do not flatten the nested columns.
|
If unspecified, do not flatten the nested columns.
|
||||||
|
timeout: Optional[timedelta]
|
||||||
|
The maximum time to wait for the query to complete.
|
||||||
|
If not specified, no timeout is applied. If the query does not
|
||||||
|
complete within the specified time, an error will be raised.
|
||||||
"""
|
"""
|
||||||
return (flatten_columns(await self.to_arrow(), flatten)).to_pandas()
|
return (
|
||||||
|
flatten_columns(await self.to_arrow(timeout=timeout), flatten)
|
||||||
|
).to_pandas()
|
||||||
|
|
||||||
async def to_polars(self) -> "pl.DataFrame":
|
async def to_polars(
|
||||||
|
self,
|
||||||
|
timeout: Optional[timedelta] = None,
|
||||||
|
) -> "pl.DataFrame":
|
||||||
"""
|
"""
|
||||||
Execute the query and collect the results into a Polars DataFrame.
|
Execute the query and collect the results into a Polars DataFrame.
|
||||||
|
|
||||||
@@ -2137,6 +2242,13 @@ class AsyncQueryBase(object):
|
|||||||
[to_batches][lancedb.query.AsyncQueryBase.to_batches] and convert each batch to
|
[to_batches][lancedb.query.AsyncQueryBase.to_batches] and convert each batch to
|
||||||
polars separately.
|
polars separately.
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
timeout: Optional[timedelta]
|
||||||
|
The maximum time to wait for the query to complete.
|
||||||
|
If not specified, no timeout is applied. If the query does not
|
||||||
|
complete within the specified time, an error will be raised.
|
||||||
|
|
||||||
Examples
|
Examples
|
||||||
--------
|
--------
|
||||||
|
|
||||||
@@ -2152,7 +2264,7 @@ class AsyncQueryBase(object):
|
|||||||
"""
|
"""
|
||||||
import polars as pl
|
import polars as pl
|
||||||
|
|
||||||
return pl.from_arrow(await self.to_arrow())
|
return pl.from_arrow(await self.to_arrow(timeout=timeout))
|
||||||
|
|
||||||
async def explain_plan(self, verbose: Optional[bool] = False):
|
async def explain_plan(self, verbose: Optional[bool] = False):
|
||||||
"""Return the execution plan for this query.
|
"""Return the execution plan for this query.
|
||||||
@@ -2423,9 +2535,12 @@ class AsyncFTSQuery(AsyncQueryBase):
|
|||||||
)
|
)
|
||||||
|
|
||||||
async def to_batches(
|
async def to_batches(
|
||||||
self, *, max_batch_length: Optional[int] = None
|
self,
|
||||||
|
*,
|
||||||
|
max_batch_length: Optional[int] = None,
|
||||||
|
timeout: Optional[timedelta] = None,
|
||||||
) -> AsyncRecordBatchReader:
|
) -> AsyncRecordBatchReader:
|
||||||
reader = await super().to_batches()
|
reader = await super().to_batches(timeout=timeout)
|
||||||
results = pa.Table.from_batches(await reader.read_all(), reader.schema)
|
results = pa.Table.from_batches(await reader.read_all(), reader.schema)
|
||||||
if self._reranker:
|
if self._reranker:
|
||||||
results = self._reranker.rerank_fts(self.get_query(), results)
|
results = self._reranker.rerank_fts(self.get_query(), results)
|
||||||
@@ -2649,9 +2764,12 @@ class AsyncVectorQuery(AsyncQueryBase, AsyncVectorQueryBase):
|
|||||||
return AsyncHybridQuery(self._inner.nearest_to_text({"query": query.to_dict()}))
|
return AsyncHybridQuery(self._inner.nearest_to_text({"query": query.to_dict()}))
|
||||||
|
|
||||||
async def to_batches(
|
async def to_batches(
|
||||||
self, *, max_batch_length: Optional[int] = None
|
self,
|
||||||
|
*,
|
||||||
|
max_batch_length: Optional[int] = None,
|
||||||
|
timeout: Optional[timedelta] = None,
|
||||||
) -> AsyncRecordBatchReader:
|
) -> AsyncRecordBatchReader:
|
||||||
reader = await super().to_batches()
|
reader = await super().to_batches(timeout=timeout)
|
||||||
results = pa.Table.from_batches(await reader.read_all(), reader.schema)
|
results = pa.Table.from_batches(await reader.read_all(), reader.schema)
|
||||||
if self._reranker:
|
if self._reranker:
|
||||||
results = self._reranker.rerank_vector(self._query_string, results)
|
results = self._reranker.rerank_vector(self._query_string, results)
|
||||||
@@ -2707,7 +2825,10 @@ class AsyncHybridQuery(AsyncQueryBase, AsyncVectorQueryBase):
|
|||||||
return self
|
return self
|
||||||
|
|
||||||
async def to_batches(
|
async def to_batches(
|
||||||
self, *, max_batch_length: Optional[int] = None
|
self,
|
||||||
|
*,
|
||||||
|
max_batch_length: Optional[int] = None,
|
||||||
|
timeout: Optional[timedelta] = None,
|
||||||
) -> AsyncRecordBatchReader:
|
) -> AsyncRecordBatchReader:
|
||||||
fts_query = AsyncFTSQuery(self._inner.to_fts_query())
|
fts_query = AsyncFTSQuery(self._inner.to_fts_query())
|
||||||
vec_query = AsyncVectorQuery(self._inner.to_vector_query())
|
vec_query = AsyncVectorQuery(self._inner.to_vector_query())
|
||||||
@@ -2719,8 +2840,8 @@ class AsyncHybridQuery(AsyncQueryBase, AsyncVectorQueryBase):
|
|||||||
vec_query.with_row_id()
|
vec_query.with_row_id()
|
||||||
|
|
||||||
fts_results, vector_results = await asyncio.gather(
|
fts_results, vector_results = await asyncio.gather(
|
||||||
fts_query.to_arrow(),
|
fts_query.to_arrow(timeout=timeout),
|
||||||
vec_query.to_arrow(),
|
vec_query.to_arrow(timeout=timeout),
|
||||||
)
|
)
|
||||||
|
|
||||||
result = LanceHybridQueryBuilder._combine_hybrid_results(
|
result = LanceHybridQueryBuilder._combine_hybrid_results(
|
||||||
|
|||||||
@@ -355,9 +355,15 @@ class RemoteTable(Table):
|
|||||||
)
|
)
|
||||||
|
|
||||||
def _execute_query(
|
def _execute_query(
|
||||||
self, query: Query, batch_size: Optional[int] = None
|
self,
|
||||||
|
query: Query,
|
||||||
|
*,
|
||||||
|
batch_size: Optional[int] = None,
|
||||||
|
timeout: Optional[timedelta] = None,
|
||||||
) -> pa.RecordBatchReader:
|
) -> pa.RecordBatchReader:
|
||||||
async_iter = LOOP.run(self._table._execute_query(query, batch_size=batch_size))
|
async_iter = LOOP.run(
|
||||||
|
self._table._execute_query(query, batch_size=batch_size, timeout=timeout)
|
||||||
|
)
|
||||||
|
|
||||||
def iter_sync():
|
def iter_sync():
|
||||||
try:
|
try:
|
||||||
|
|||||||
@@ -47,6 +47,9 @@ class AnswerdotaiRerankers(Reranker):
|
|||||||
)
|
)
|
||||||
|
|
||||||
def _rerank(self, result_set: pa.Table, query: str):
|
def _rerank(self, result_set: pa.Table, query: str):
|
||||||
|
result_set = self._handle_empty_results(result_set)
|
||||||
|
if len(result_set) == 0:
|
||||||
|
return result_set
|
||||||
docs = result_set[self.column].to_pylist()
|
docs = result_set[self.column].to_pylist()
|
||||||
doc_ids = list(range(len(docs)))
|
doc_ids = list(range(len(docs)))
|
||||||
result = self.reranker.rank(query, docs, doc_ids=doc_ids)
|
result = self.reranker.rank(query, docs, doc_ids=doc_ids)
|
||||||
@@ -83,7 +86,6 @@ class AnswerdotaiRerankers(Reranker):
|
|||||||
vector_results = self._rerank(vector_results, query)
|
vector_results = self._rerank(vector_results, query)
|
||||||
if self.score == "relevance":
|
if self.score == "relevance":
|
||||||
vector_results = vector_results.drop_columns(["_distance"])
|
vector_results = vector_results.drop_columns(["_distance"])
|
||||||
|
|
||||||
vector_results = vector_results.sort_by([("_relevance_score", "descending")])
|
vector_results = vector_results.sort_by([("_relevance_score", "descending")])
|
||||||
return vector_results
|
return vector_results
|
||||||
|
|
||||||
@@ -91,7 +93,5 @@ class AnswerdotaiRerankers(Reranker):
|
|||||||
fts_results = self._rerank(fts_results, query)
|
fts_results = self._rerank(fts_results, query)
|
||||||
if self.score == "relevance":
|
if self.score == "relevance":
|
||||||
fts_results = fts_results.drop_columns(["_score"])
|
fts_results = fts_results.drop_columns(["_score"])
|
||||||
|
|
||||||
fts_results = fts_results.sort_by([("_relevance_score", "descending")])
|
fts_results = fts_results.sort_by([("_relevance_score", "descending")])
|
||||||
|
|
||||||
return fts_results
|
return fts_results
|
||||||
|
|||||||
@@ -65,6 +65,16 @@ class Reranker(ABC):
|
|||||||
f"{self.__class__.__name__} does not implement rerank_vector"
|
f"{self.__class__.__name__} does not implement rerank_vector"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def _handle_empty_results(self, results: pa.Table):
|
||||||
|
"""
|
||||||
|
Helper method to handle empty FTS results consistently
|
||||||
|
"""
|
||||||
|
if len(results) > 0:
|
||||||
|
return results
|
||||||
|
return results.append_column(
|
||||||
|
"_relevance_score", pa.array([], type=pa.float32())
|
||||||
|
)
|
||||||
|
|
||||||
def rerank_fts(
|
def rerank_fts(
|
||||||
self,
|
self,
|
||||||
query: str,
|
query: str,
|
||||||
|
|||||||
@@ -62,6 +62,9 @@ class CohereReranker(Reranker):
|
|||||||
return cohere.Client(os.environ.get("COHERE_API_KEY") or self.api_key)
|
return cohere.Client(os.environ.get("COHERE_API_KEY") or self.api_key)
|
||||||
|
|
||||||
def _rerank(self, result_set: pa.Table, query: str):
|
def _rerank(self, result_set: pa.Table, query: str):
|
||||||
|
result_set = self._handle_empty_results(result_set)
|
||||||
|
if len(result_set) == 0:
|
||||||
|
return result_set
|
||||||
docs = result_set[self.column].to_pylist()
|
docs = result_set[self.column].to_pylist()
|
||||||
response = self._client.rerank(
|
response = self._client.rerank(
|
||||||
query=query,
|
query=query,
|
||||||
@@ -99,24 +102,14 @@ class CohereReranker(Reranker):
|
|||||||
)
|
)
|
||||||
return combined_results
|
return combined_results
|
||||||
|
|
||||||
def rerank_vector(
|
def rerank_vector(self, query: str, vector_results: pa.Table):
|
||||||
self,
|
vector_results = self._rerank(vector_results, query)
|
||||||
query: str,
|
|
||||||
vector_results: pa.Table,
|
|
||||||
):
|
|
||||||
result_set = self._rerank(vector_results, query)
|
|
||||||
if self.score == "relevance":
|
if self.score == "relevance":
|
||||||
result_set = result_set.drop_columns(["_distance"])
|
vector_results = vector_results.drop_columns(["_distance"])
|
||||||
|
return vector_results
|
||||||
|
|
||||||
return result_set
|
def rerank_fts(self, query: str, fts_results: pa.Table):
|
||||||
|
fts_results = self._rerank(fts_results, query)
|
||||||
def rerank_fts(
|
|
||||||
self,
|
|
||||||
query: str,
|
|
||||||
fts_results: pa.Table,
|
|
||||||
):
|
|
||||||
result_set = self._rerank(fts_results, query)
|
|
||||||
if self.score == "relevance":
|
if self.score == "relevance":
|
||||||
result_set = result_set.drop_columns(["_score"])
|
fts_results = fts_results.drop_columns(["_score"])
|
||||||
|
return fts_results
|
||||||
return result_set
|
|
||||||
|
|||||||
@@ -63,6 +63,9 @@ class CrossEncoderReranker(Reranker):
|
|||||||
return cross_encoder
|
return cross_encoder
|
||||||
|
|
||||||
def _rerank(self, result_set: pa.Table, query: str):
|
def _rerank(self, result_set: pa.Table, query: str):
|
||||||
|
result_set = self._handle_empty_results(result_set)
|
||||||
|
if len(result_set) == 0:
|
||||||
|
return result_set
|
||||||
passages = result_set[self.column].to_pylist()
|
passages = result_set[self.column].to_pylist()
|
||||||
cross_inp = [[query, passage] for passage in passages]
|
cross_inp = [[query, passage] for passage in passages]
|
||||||
cross_scores = self.model.predict(cross_inp)
|
cross_scores = self.model.predict(cross_inp)
|
||||||
@@ -93,11 +96,7 @@ class CrossEncoderReranker(Reranker):
|
|||||||
|
|
||||||
return combined_results
|
return combined_results
|
||||||
|
|
||||||
def rerank_vector(
|
def rerank_vector(self, query: str, vector_results: pa.Table):
|
||||||
self,
|
|
||||||
query: str,
|
|
||||||
vector_results: pa.Table,
|
|
||||||
):
|
|
||||||
vector_results = self._rerank(vector_results, query)
|
vector_results = self._rerank(vector_results, query)
|
||||||
if self.score == "relevance":
|
if self.score == "relevance":
|
||||||
vector_results = vector_results.drop_columns(["_distance"])
|
vector_results = vector_results.drop_columns(["_distance"])
|
||||||
@@ -105,11 +104,7 @@ class CrossEncoderReranker(Reranker):
|
|||||||
vector_results = vector_results.sort_by([("_relevance_score", "descending")])
|
vector_results = vector_results.sort_by([("_relevance_score", "descending")])
|
||||||
return vector_results
|
return vector_results
|
||||||
|
|
||||||
def rerank_fts(
|
def rerank_fts(self, query: str, fts_results: pa.Table):
|
||||||
self,
|
|
||||||
query: str,
|
|
||||||
fts_results: pa.Table,
|
|
||||||
):
|
|
||||||
fts_results = self._rerank(fts_results, query)
|
fts_results = self._rerank(fts_results, query)
|
||||||
if self.score == "relevance":
|
if self.score == "relevance":
|
||||||
fts_results = fts_results.drop_columns(["_score"])
|
fts_results = fts_results.drop_columns(["_score"])
|
||||||
|
|||||||
@@ -62,6 +62,9 @@ class JinaReranker(Reranker):
|
|||||||
return self._session
|
return self._session
|
||||||
|
|
||||||
def _rerank(self, result_set: pa.Table, query: str):
|
def _rerank(self, result_set: pa.Table, query: str):
|
||||||
|
result_set = self._handle_empty_results(result_set)
|
||||||
|
if len(result_set) == 0:
|
||||||
|
return result_set
|
||||||
docs = result_set[self.column].to_pylist()
|
docs = result_set[self.column].to_pylist()
|
||||||
response = self._client.post( # type: ignore
|
response = self._client.post( # type: ignore
|
||||||
API_URL,
|
API_URL,
|
||||||
@@ -104,24 +107,14 @@ class JinaReranker(Reranker):
|
|||||||
)
|
)
|
||||||
return combined_results
|
return combined_results
|
||||||
|
|
||||||
def rerank_vector(
|
def rerank_vector(self, query: str, vector_results: pa.Table):
|
||||||
self,
|
vector_results = self._rerank(vector_results, query)
|
||||||
query: str,
|
|
||||||
vector_results: pa.Table,
|
|
||||||
):
|
|
||||||
result_set = self._rerank(vector_results, query)
|
|
||||||
if self.score == "relevance":
|
if self.score == "relevance":
|
||||||
result_set = result_set.drop_columns(["_distance"])
|
vector_results = vector_results.drop_columns(["_distance"])
|
||||||
|
return vector_results
|
||||||
|
|
||||||
return result_set
|
def rerank_fts(self, query: str, fts_results: pa.Table):
|
||||||
|
fts_results = self._rerank(fts_results, query)
|
||||||
def rerank_fts(
|
|
||||||
self,
|
|
||||||
query: str,
|
|
||||||
fts_results: pa.Table,
|
|
||||||
):
|
|
||||||
result_set = self._rerank(fts_results, query)
|
|
||||||
if self.score == "relevance":
|
if self.score == "relevance":
|
||||||
result_set = result_set.drop_columns(["_score"])
|
fts_results = fts_results.drop_columns(["_score"])
|
||||||
|
return fts_results
|
||||||
return result_set
|
|
||||||
|
|||||||
@@ -44,6 +44,9 @@ class OpenaiReranker(Reranker):
|
|||||||
self.api_key = api_key
|
self.api_key = api_key
|
||||||
|
|
||||||
def _rerank(self, result_set: pa.Table, query: str):
|
def _rerank(self, result_set: pa.Table, query: str):
|
||||||
|
result_set = self._handle_empty_results(result_set)
|
||||||
|
if len(result_set) == 0:
|
||||||
|
return result_set
|
||||||
docs = result_set[self.column].to_pylist()
|
docs = result_set[self.column].to_pylist()
|
||||||
response = self._client.chat.completions.create(
|
response = self._client.chat.completions.create(
|
||||||
model=self.model_name,
|
model=self.model_name,
|
||||||
@@ -104,18 +107,14 @@ class OpenaiReranker(Reranker):
|
|||||||
vector_results = self._rerank(vector_results, query)
|
vector_results = self._rerank(vector_results, query)
|
||||||
if self.score == "relevance":
|
if self.score == "relevance":
|
||||||
vector_results = vector_results.drop_columns(["_distance"])
|
vector_results = vector_results.drop_columns(["_distance"])
|
||||||
|
|
||||||
vector_results = vector_results.sort_by([("_relevance_score", "descending")])
|
vector_results = vector_results.sort_by([("_relevance_score", "descending")])
|
||||||
|
|
||||||
return vector_results
|
return vector_results
|
||||||
|
|
||||||
def rerank_fts(self, query: str, fts_results: pa.Table):
|
def rerank_fts(self, query: str, fts_results: pa.Table):
|
||||||
fts_results = self._rerank(fts_results, query)
|
fts_results = self._rerank(fts_results, query)
|
||||||
if self.score == "relevance":
|
if self.score == "relevance":
|
||||||
fts_results = fts_results.drop_columns(["_score"])
|
fts_results = fts_results.drop_columns(["_score"])
|
||||||
|
|
||||||
fts_results = fts_results.sort_by([("_relevance_score", "descending")])
|
fts_results = fts_results.sort_by([("_relevance_score", "descending")])
|
||||||
|
|
||||||
return fts_results
|
return fts_results
|
||||||
|
|
||||||
@cached_property
|
@cached_property
|
||||||
|
|||||||
@@ -63,6 +63,9 @@ class VoyageAIReranker(Reranker):
|
|||||||
)
|
)
|
||||||
|
|
||||||
def _rerank(self, result_set: pa.Table, query: str):
|
def _rerank(self, result_set: pa.Table, query: str):
|
||||||
|
result_set = self._handle_empty_results(result_set)
|
||||||
|
if len(result_set) == 0:
|
||||||
|
return result_set
|
||||||
docs = result_set[self.column].to_pylist()
|
docs = result_set[self.column].to_pylist()
|
||||||
response = self._client.rerank(
|
response = self._client.rerank(
|
||||||
query=query,
|
query=query,
|
||||||
@@ -101,24 +104,14 @@ class VoyageAIReranker(Reranker):
|
|||||||
)
|
)
|
||||||
return combined_results
|
return combined_results
|
||||||
|
|
||||||
def rerank_vector(
|
def rerank_vector(self, query: str, vector_results: pa.Table):
|
||||||
self,
|
vector_results = self._rerank(vector_results, query)
|
||||||
query: str,
|
|
||||||
vector_results: pa.Table,
|
|
||||||
):
|
|
||||||
result_set = self._rerank(vector_results, query)
|
|
||||||
if self.score == "relevance":
|
if self.score == "relevance":
|
||||||
result_set = result_set.drop_columns(["_distance"])
|
vector_results = vector_results.drop_columns(["_distance"])
|
||||||
|
return vector_results
|
||||||
|
|
||||||
return result_set
|
def rerank_fts(self, query: str, fts_results: pa.Table):
|
||||||
|
fts_results = self._rerank(fts_results, query)
|
||||||
def rerank_fts(
|
|
||||||
self,
|
|
||||||
query: str,
|
|
||||||
fts_results: pa.Table,
|
|
||||||
):
|
|
||||||
result_set = self._rerank(fts_results, query)
|
|
||||||
if self.score == "relevance":
|
if self.score == "relevance":
|
||||||
result_set = result_set.drop_columns(["_score"])
|
fts_results = fts_results.drop_columns(["_score"])
|
||||||
|
return fts_results
|
||||||
return result_set
|
|
||||||
|
|||||||
@@ -1007,7 +1007,11 @@ class Table(ABC):
|
|||||||
|
|
||||||
@abstractmethod
|
@abstractmethod
|
||||||
def _execute_query(
|
def _execute_query(
|
||||||
self, query: Query, batch_size: Optional[int] = None
|
self,
|
||||||
|
query: Query,
|
||||||
|
*,
|
||||||
|
batch_size: Optional[int] = None,
|
||||||
|
timeout: Optional[timedelta] = None,
|
||||||
) -> pa.RecordBatchReader: ...
|
) -> pa.RecordBatchReader: ...
|
||||||
|
|
||||||
@abstractmethod
|
@abstractmethod
|
||||||
@@ -1741,8 +1745,32 @@ class LanceTable(Table):
|
|||||||
)
|
)
|
||||||
|
|
||||||
def drop_index(self, name: str) -> None:
|
def drop_index(self, name: str) -> None:
|
||||||
|
"""
|
||||||
|
Drops an index from the table
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
name: str
|
||||||
|
The name of the index to drop
|
||||||
|
"""
|
||||||
return LOOP.run(self._table.drop_index(name))
|
return LOOP.run(self._table.drop_index(name))
|
||||||
|
|
||||||
|
def prewarm_index(self, name: str) -> None:
|
||||||
|
"""
|
||||||
|
Prewarms an index in the table
|
||||||
|
|
||||||
|
This loads the entire index into memory
|
||||||
|
|
||||||
|
If the index does not fit into the available cache this call
|
||||||
|
may be wasteful
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
name: str
|
||||||
|
The name of the index to prewarm
|
||||||
|
"""
|
||||||
|
return LOOP.run(self._table.prewarm_index(name))
|
||||||
|
|
||||||
def create_scalar_index(
|
def create_scalar_index(
|
||||||
self,
|
self,
|
||||||
column: str,
|
column: str,
|
||||||
@@ -2137,6 +2165,8 @@ class LanceTable(Table):
|
|||||||
and also the "_distance" column which is the distance between the query
|
and also the "_distance" column which is the distance between the query
|
||||||
vector and the returned vector.
|
vector and the returned vector.
|
||||||
"""
|
"""
|
||||||
|
if isinstance(query, FullTextQuery):
|
||||||
|
query_type = "fts"
|
||||||
vector_column_name = infer_vector_column_name(
|
vector_column_name = infer_vector_column_name(
|
||||||
schema=self.schema,
|
schema=self.schema,
|
||||||
query_type=query_type,
|
query_type=query_type,
|
||||||
@@ -2312,9 +2342,15 @@ class LanceTable(Table):
|
|||||||
LOOP.run(self._table.update(values, where=where, updates_sql=values_sql))
|
LOOP.run(self._table.update(values, where=where, updates_sql=values_sql))
|
||||||
|
|
||||||
def _execute_query(
|
def _execute_query(
|
||||||
self, query: Query, batch_size: Optional[int] = None
|
self,
|
||||||
|
query: Query,
|
||||||
|
*,
|
||||||
|
batch_size: Optional[int] = None,
|
||||||
|
timeout: Optional[timedelta] = None,
|
||||||
) -> pa.RecordBatchReader:
|
) -> pa.RecordBatchReader:
|
||||||
async_iter = LOOP.run(self._table._execute_query(query, batch_size))
|
async_iter = LOOP.run(
|
||||||
|
self._table._execute_query(query, batch_size=batch_size, timeout=timeout)
|
||||||
|
)
|
||||||
|
|
||||||
def iter_sync():
|
def iter_sync():
|
||||||
try:
|
try:
|
||||||
@@ -2990,6 +3026,23 @@ class AsyncTable:
|
|||||||
"""
|
"""
|
||||||
await self._inner.drop_index(name)
|
await self._inner.drop_index(name)
|
||||||
|
|
||||||
|
async def prewarm_index(self, name: str) -> None:
|
||||||
|
"""
|
||||||
|
Prewarm an index in the table.
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
name: str
|
||||||
|
The name of the index to prewarm
|
||||||
|
|
||||||
|
Notes
|
||||||
|
-----
|
||||||
|
This will load the index into memory. This may reduce the cold-start time for
|
||||||
|
future queries. If the index does not fit in the cache then this call may be
|
||||||
|
wasteful.
|
||||||
|
"""
|
||||||
|
await self._inner.prewarm_index(name)
|
||||||
|
|
||||||
async def add(
|
async def add(
|
||||||
self,
|
self,
|
||||||
data: DATA,
|
data: DATA,
|
||||||
@@ -3213,8 +3266,10 @@ class AsyncTable:
|
|||||||
async def get_embedding_func(
|
async def get_embedding_func(
|
||||||
vector_column_name: Optional[str],
|
vector_column_name: Optional[str],
|
||||||
query_type: QueryType,
|
query_type: QueryType,
|
||||||
query: Optional[Union[VEC, str, "PIL.Image.Image", Tuple]],
|
query: Optional[Union[VEC, str, "PIL.Image.Image", Tuple, FullTextQuery]],
|
||||||
) -> Tuple[str, EmbeddingFunctionConfig]:
|
) -> Tuple[str, EmbeddingFunctionConfig]:
|
||||||
|
if isinstance(query, FullTextQuery):
|
||||||
|
query_type = "fts"
|
||||||
schema = await self.schema()
|
schema = await self.schema()
|
||||||
vector_column_name = infer_vector_column_name(
|
vector_column_name = infer_vector_column_name(
|
||||||
schema=schema,
|
schema=schema,
|
||||||
@@ -3390,7 +3445,11 @@ class AsyncTable:
|
|||||||
return async_query
|
return async_query
|
||||||
|
|
||||||
async def _execute_query(
|
async def _execute_query(
|
||||||
self, query: Query, batch_size: Optional[int] = None
|
self,
|
||||||
|
query: Query,
|
||||||
|
*,
|
||||||
|
batch_size: Optional[int] = None,
|
||||||
|
timeout: Optional[timedelta] = None,
|
||||||
) -> pa.RecordBatchReader:
|
) -> pa.RecordBatchReader:
|
||||||
# The sync table calls into this method, so we need to map the
|
# The sync table calls into this method, so we need to map the
|
||||||
# query to the async version of the query and run that here. This is only
|
# query to the async version of the query and run that here. This is only
|
||||||
@@ -3398,7 +3457,9 @@ class AsyncTable:
|
|||||||
|
|
||||||
async_query = self._sync_query_to_async(query)
|
async_query = self._sync_query_to_async(query)
|
||||||
|
|
||||||
return await async_query.to_batches(max_batch_length=batch_size)
|
return await async_query.to_batches(
|
||||||
|
max_batch_length=batch_size, timeout=timeout
|
||||||
|
)
|
||||||
|
|
||||||
async def _explain_plan(self, query: Query, verbose: Optional[bool]) -> str:
|
async def _explain_plan(self, query: Query, verbose: Optional[bool]) -> str:
|
||||||
# This method is used by the sync table
|
# This method is used by the sync table
|
||||||
|
|||||||
@@ -253,9 +253,14 @@ def infer_vector_column_name(
|
|||||||
query: Optional[Any], # inferred later in query builder
|
query: Optional[Any], # inferred later in query builder
|
||||||
vector_column_name: Optional[str],
|
vector_column_name: Optional[str],
|
||||||
):
|
):
|
||||||
if (vector_column_name is None and query is not None and query_type != "fts") or (
|
if vector_column_name is not None:
|
||||||
vector_column_name is None and query_type == "hybrid"
|
return vector_column_name
|
||||||
):
|
|
||||||
|
if query_type == "fts":
|
||||||
|
# FTS queries do not require a vector column
|
||||||
|
return None
|
||||||
|
|
||||||
|
if query is not None or query_type == "hybrid":
|
||||||
try:
|
try:
|
||||||
vector_column_name = inf_vector_column_query(schema)
|
vector_column_name = inf_vector_column_query(schema)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
|
|||||||
@@ -315,11 +315,6 @@ def test_table():
|
|||||||
db = lancedb.connect(uri, read_consistency_interval=timedelta(seconds=5))
|
db = lancedb.connect(uri, read_consistency_interval=timedelta(seconds=5))
|
||||||
tbl = db.open_table("test_table")
|
tbl = db.open_table("test_table")
|
||||||
# --8<-- [end:table_eventual_consistency]
|
# --8<-- [end:table_eventual_consistency]
|
||||||
# --8<-- [start:table_no_consistency]
|
|
||||||
uri = "data/sample-lancedb"
|
|
||||||
db = lancedb.connect(uri, read_consistency_interval=None)
|
|
||||||
tbl = db.open_table("test_table")
|
|
||||||
# --8<-- [end:table_no_consistency]
|
|
||||||
# --8<-- [start:table_checkout_latest]
|
# --8<-- [start:table_checkout_latest]
|
||||||
tbl = db.open_table("test_table")
|
tbl = db.open_table("test_table")
|
||||||
|
|
||||||
@@ -574,12 +569,6 @@ async def test_table_async():
|
|||||||
)
|
)
|
||||||
async_tbl = await async_db.open_table("test_table_async")
|
async_tbl = await async_db.open_table("test_table_async")
|
||||||
# --8<-- [end:table_async_eventual_consistency]
|
# --8<-- [end:table_async_eventual_consistency]
|
||||||
# --8<-- [start:table_async_no_consistency]
|
|
||||||
uri = "data/sample-lancedb"
|
|
||||||
async_db = await lancedb.connect_async(uri, read_consistency_interval=None)
|
|
||||||
async_tbl = await async_db.open_table("test_table_async")
|
|
||||||
# --8<-- [end:table_async_no_consistency]
|
|
||||||
|
|
||||||
# --8<-- [start:table_async_checkout_latest]
|
# --8<-- [start:table_async_checkout_latest]
|
||||||
async_tbl = await async_db.open_table("test_table_async")
|
async_tbl = await async_db.open_table("test_table_async")
|
||||||
|
|
||||||
|
|||||||
@@ -6,7 +6,9 @@ import lancedb
|
|||||||
|
|
||||||
# --8<-- [end:import-lancedb]
|
# --8<-- [end:import-lancedb]
|
||||||
# --8<-- [start:import-numpy]
|
# --8<-- [start:import-numpy]
|
||||||
|
from lancedb.query import BoostQuery, MatchQuery
|
||||||
import numpy as np
|
import numpy as np
|
||||||
|
import pyarrow as pa
|
||||||
|
|
||||||
# --8<-- [end:import-numpy]
|
# --8<-- [end:import-numpy]
|
||||||
# --8<-- [start:import-datetime]
|
# --8<-- [start:import-datetime]
|
||||||
@@ -154,6 +156,84 @@ async def test_vector_search_async():
|
|||||||
# --8<-- [end:search_result_async_as_list]
|
# --8<-- [end:search_result_async_as_list]
|
||||||
|
|
||||||
|
|
||||||
|
def test_fts_fuzzy_query():
|
||||||
|
uri = "data/fuzzy-example"
|
||||||
|
db = lancedb.connect(uri)
|
||||||
|
|
||||||
|
table = db.create_table(
|
||||||
|
"my_table_fts_fuzzy",
|
||||||
|
data=pa.table(
|
||||||
|
{
|
||||||
|
"text": [
|
||||||
|
"fa",
|
||||||
|
"fo", # spellchecker:disable-line
|
||||||
|
"fob",
|
||||||
|
"focus",
|
||||||
|
"foo",
|
||||||
|
"food",
|
||||||
|
"foul",
|
||||||
|
]
|
||||||
|
}
|
||||||
|
),
|
||||||
|
mode="overwrite",
|
||||||
|
)
|
||||||
|
table.create_fts_index("text", use_tantivy=False, replace=True)
|
||||||
|
|
||||||
|
results = table.search(MatchQuery("foo", "text", fuzziness=1)).to_pandas()
|
||||||
|
assert len(results) == 4
|
||||||
|
assert set(results["text"].to_list()) == {
|
||||||
|
"foo",
|
||||||
|
"fo", # 1 deletion # spellchecker:disable-line
|
||||||
|
"fob", # 1 substitution
|
||||||
|
"food", # 1 insertion
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def test_fts_boost_query():
|
||||||
|
uri = "data/boost-example"
|
||||||
|
db = lancedb.connect(uri)
|
||||||
|
|
||||||
|
table = db.create_table(
|
||||||
|
"my_table_fts_boost",
|
||||||
|
data=pa.table(
|
||||||
|
{
|
||||||
|
"title": [
|
||||||
|
"The Hidden Gems of Travel",
|
||||||
|
"Exploring Nature's Wonders",
|
||||||
|
"Cultural Treasures Unveiled",
|
||||||
|
"The Nightlife Chronicles",
|
||||||
|
"Scenic Escapes and Challenges",
|
||||||
|
],
|
||||||
|
"desc": [
|
||||||
|
"A vibrant city with occasional traffic jams.",
|
||||||
|
"Beautiful landscapes but overpriced tourist spots.",
|
||||||
|
"Rich cultural heritage but humid summers.",
|
||||||
|
"Bustling nightlife but noisy streets.",
|
||||||
|
"Scenic views but limited public transport options.",
|
||||||
|
],
|
||||||
|
}
|
||||||
|
),
|
||||||
|
mode="overwrite",
|
||||||
|
)
|
||||||
|
table.create_fts_index("desc", use_tantivy=False, replace=True)
|
||||||
|
|
||||||
|
results = table.search(
|
||||||
|
BoostQuery(
|
||||||
|
MatchQuery("beautiful, cultural, nightlife", "desc"),
|
||||||
|
MatchQuery("bad traffic jams, overpriced", "desc"),
|
||||||
|
),
|
||||||
|
).to_pandas()
|
||||||
|
|
||||||
|
# we will hit 3 results because the positive query has 3 hits
|
||||||
|
assert len(results) == 3
|
||||||
|
# the one containing "overpriced" will be negatively boosted,
|
||||||
|
# so it will be the last one
|
||||||
|
assert (
|
||||||
|
results["desc"].to_list()[2]
|
||||||
|
== "Beautiful landscapes but overpriced tourist spots."
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def test_fts_native():
|
def test_fts_native():
|
||||||
# --8<-- [start:basic_fts]
|
# --8<-- [start:basic_fts]
|
||||||
uri = "data/sample-lancedb"
|
uri = "data/sample-lancedb"
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
|
|
||||||
|
|
||||||
import re
|
import re
|
||||||
|
from datetime import timedelta
|
||||||
import os
|
import os
|
||||||
|
|
||||||
import lancedb
|
import lancedb
|
||||||
@@ -298,11 +299,13 @@ def test_create_exist_ok(tmp_db: lancedb.DBConnection):
|
|||||||
@pytest.mark.asyncio
|
@pytest.mark.asyncio
|
||||||
async def test_connect(tmp_path):
|
async def test_connect(tmp_path):
|
||||||
db = await lancedb.connect_async(tmp_path)
|
db = await lancedb.connect_async(tmp_path)
|
||||||
assert str(db) == f"ListingDatabase(uri={tmp_path}, read_consistency_interval=5s)"
|
|
||||||
|
|
||||||
db = await lancedb.connect_async(tmp_path, read_consistency_interval=None)
|
|
||||||
assert str(db) == f"ListingDatabase(uri={tmp_path}, read_consistency_interval=None)"
|
assert str(db) == f"ListingDatabase(uri={tmp_path}, read_consistency_interval=None)"
|
||||||
|
|
||||||
|
db = await lancedb.connect_async(
|
||||||
|
tmp_path, read_consistency_interval=timedelta(seconds=5)
|
||||||
|
)
|
||||||
|
assert str(db) == f"ListingDatabase(uri={tmp_path}, read_consistency_interval=5s)"
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
@pytest.mark.asyncio
|
||||||
async def test_close(mem_db_async: lancedb.AsyncConnection):
|
async def test_close(mem_db_async: lancedb.AsyncConnection):
|
||||||
@@ -450,7 +453,7 @@ async def test_open_table(tmp_path):
|
|||||||
assert tbl.name == "test"
|
assert tbl.name == "test"
|
||||||
assert (
|
assert (
|
||||||
re.search(
|
re.search(
|
||||||
r"NativeTable\(test, uri=.*test\.lance, read_consistency_interval=5s\)",
|
r"NativeTable\(test, uri=.*test\.lance, read_consistency_interval=None\)",
|
||||||
str(tbl),
|
str(tbl),
|
||||||
)
|
)
|
||||||
is not None
|
is not None
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ import pyarrow as pa
|
|||||||
import pytest
|
import pytest
|
||||||
from lancedb.embeddings import get_registry
|
from lancedb.embeddings import get_registry
|
||||||
from lancedb.pydantic import LanceModel, Vector
|
from lancedb.pydantic import LanceModel, Vector
|
||||||
|
import requests
|
||||||
|
|
||||||
# These are integration tests for embedding functions.
|
# These are integration tests for embedding functions.
|
||||||
# They are slow because they require downloading models
|
# They are slow because they require downloading models
|
||||||
@@ -516,3 +517,61 @@ def test_voyageai_embedding_function():
|
|||||||
|
|
||||||
tbl.add(df)
|
tbl.add(df)
|
||||||
assert len(tbl.to_pandas()["vector"][0]) == voyageai.ndims()
|
assert len(tbl.to_pandas()["vector"][0]) == voyageai.ndims()
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.slow
|
||||||
|
@pytest.mark.skipif(
|
||||||
|
os.environ.get("VOYAGE_API_KEY") is None, reason="VOYAGE_API_KEY not set"
|
||||||
|
)
|
||||||
|
def test_voyageai_multimodal_embedding_function():
|
||||||
|
voyageai = (
|
||||||
|
get_registry().get("voyageai").create(name="voyage-multimodal-3", max_retries=0)
|
||||||
|
)
|
||||||
|
|
||||||
|
class Images(LanceModel):
|
||||||
|
label: str
|
||||||
|
image_uri: str = voyageai.SourceField() # image uri as the source
|
||||||
|
image_bytes: bytes = voyageai.SourceField() # image bytes as the source
|
||||||
|
vector: Vector(voyageai.ndims()) = voyageai.VectorField() # vector column
|
||||||
|
vec_from_bytes: Vector(voyageai.ndims()) = (
|
||||||
|
voyageai.VectorField()
|
||||||
|
) # Another vector column
|
||||||
|
|
||||||
|
db = lancedb.connect("~/lancedb")
|
||||||
|
table = db.create_table("test", schema=Images, mode="overwrite")
|
||||||
|
labels = ["cat", "cat", "dog", "dog", "horse", "horse"]
|
||||||
|
uris = [
|
||||||
|
"http://farm1.staticflickr.com/53/167798175_7c7845bbbd_z.jpg",
|
||||||
|
"http://farm1.staticflickr.com/134/332220238_da527d8140_z.jpg",
|
||||||
|
"http://farm9.staticflickr.com/8387/8602747737_2e5c2a45d4_z.jpg",
|
||||||
|
"http://farm5.staticflickr.com/4092/5017326486_1f46057f5f_z.jpg",
|
||||||
|
"http://farm9.staticflickr.com/8216/8434969557_d37882c42d_z.jpg",
|
||||||
|
"http://farm6.staticflickr.com/5142/5835678453_4f3a4edb45_z.jpg",
|
||||||
|
]
|
||||||
|
# get each uri as bytes
|
||||||
|
image_bytes = [requests.get(uri).content for uri in uris]
|
||||||
|
table.add(
|
||||||
|
pd.DataFrame({"label": labels, "image_uri": uris, "image_bytes": image_bytes})
|
||||||
|
)
|
||||||
|
assert len(table.to_pandas()["vector"][0]) == voyageai.ndims()
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.slow
|
||||||
|
@pytest.mark.skipif(
|
||||||
|
os.environ.get("VOYAGE_API_KEY") is None, reason="VOYAGE_API_KEY not set"
|
||||||
|
)
|
||||||
|
def test_voyageai_multimodal_embedding_text_function():
|
||||||
|
voyageai = (
|
||||||
|
get_registry().get("voyageai").create(name="voyage-multimodal-3", max_retries=0)
|
||||||
|
)
|
||||||
|
|
||||||
|
class TextModel(LanceModel):
|
||||||
|
text: str = voyageai.SourceField()
|
||||||
|
vector: Vector(voyageai.ndims()) = voyageai.VectorField()
|
||||||
|
|
||||||
|
df = pd.DataFrame({"text": ["hello world", "goodbye world"]})
|
||||||
|
db = lancedb.connect("~/lancedb")
|
||||||
|
tbl = db.create_table("test", schema=TextModel, mode="overwrite")
|
||||||
|
|
||||||
|
tbl.add(df)
|
||||||
|
assert len(tbl.to_pandas()["vector"][0]) == voyageai.ndims()
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ from lancedb.db import DBConnection
|
|||||||
from lancedb.index import FTS
|
from lancedb.index import FTS
|
||||||
from lancedb.query import BoostQuery, MatchQuery, MultiMatchQuery, PhraseQuery
|
from lancedb.query import BoostQuery, MatchQuery, MultiMatchQuery, PhraseQuery
|
||||||
import numpy as np
|
import numpy as np
|
||||||
|
import pyarrow as pa
|
||||||
import pandas as pd
|
import pandas as pd
|
||||||
import pytest
|
import pytest
|
||||||
from utils import exception_output
|
from utils import exception_output
|
||||||
@@ -626,3 +627,32 @@ def test_language(mem_db: DBConnection):
|
|||||||
# Stop words -> no results
|
# Stop words -> no results
|
||||||
results = table.search("la", query_type="fts").limit(5).to_list()
|
results = table.search("la", query_type="fts").limit(5).to_list()
|
||||||
assert len(results) == 0
|
assert len(results) == 0
|
||||||
|
|
||||||
|
|
||||||
|
def test_fts_on_list(mem_db: DBConnection):
|
||||||
|
data = pa.table(
|
||||||
|
{
|
||||||
|
"text": [
|
||||||
|
["lance database", "the", "search"],
|
||||||
|
["lance database"],
|
||||||
|
["lance", "search"],
|
||||||
|
["database", "search"],
|
||||||
|
["unrelated", "doc"],
|
||||||
|
],
|
||||||
|
"vector": [
|
||||||
|
[1.0, 2.0, 3.0],
|
||||||
|
[4.0, 5.0, 6.0],
|
||||||
|
[7.0, 8.0, 9.0],
|
||||||
|
[10.0, 11.0, 12.0],
|
||||||
|
[13.0, 14.0, 15.0],
|
||||||
|
],
|
||||||
|
}
|
||||||
|
)
|
||||||
|
table = mem_db.create_table("test", data=data)
|
||||||
|
table.create_fts_index("text", use_tantivy=False)
|
||||||
|
|
||||||
|
res = table.search("lance").limit(5).to_list()
|
||||||
|
assert len(res) == 3
|
||||||
|
|
||||||
|
res = table.search(PhraseQuery("lance database", "text")).limit(5).to_list()
|
||||||
|
assert len(res) == 2
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import pyarrow as pa
|
|||||||
import pytest
|
import pytest
|
||||||
import pytest_asyncio
|
import pytest_asyncio
|
||||||
from lancedb import AsyncConnection, AsyncTable, connect_async
|
from lancedb import AsyncConnection, AsyncTable, connect_async
|
||||||
from lancedb.index import BTree, IvfFlat, IvfPq, Bitmap, LabelList, HnswPq, HnswSq
|
from lancedb.index import BTree, IvfFlat, IvfPq, Bitmap, LabelList, HnswPq, HnswSq, FTS
|
||||||
|
|
||||||
|
|
||||||
@pytest_asyncio.fixture
|
@pytest_asyncio.fixture
|
||||||
@@ -119,6 +119,18 @@ async def test_create_label_list_index(some_table: AsyncTable):
|
|||||||
assert str(indices) == '[Index(LabelList, columns=["tags"], name="tags_idx")]'
|
assert str(indices) == '[Index(LabelList, columns=["tags"], name="tags_idx")]'
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_full_text_search_index(some_table: AsyncTable):
|
||||||
|
await some_table.create_index("tags", config=FTS(with_position=False))
|
||||||
|
indices = await some_table.list_indices()
|
||||||
|
assert str(indices) == '[Index(FTS, columns=["tags"], name="tags_idx")]'
|
||||||
|
|
||||||
|
await some_table.prewarm_index("tags_idx")
|
||||||
|
|
||||||
|
res = await (await some_table.search("tag0")).to_arrow()
|
||||||
|
assert res.num_rows > 0
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
@pytest.mark.asyncio
|
||||||
async def test_create_vector_index(some_table: AsyncTable):
|
async def test_create_vector_index(some_table: AsyncTable):
|
||||||
# Can create
|
# Can create
|
||||||
|
|||||||
@@ -511,7 +511,8 @@ def test_query_builder_with_different_vector_column():
|
|||||||
columns=["b"],
|
columns=["b"],
|
||||||
vector_column="foo_vector",
|
vector_column="foo_vector",
|
||||||
),
|
),
|
||||||
None,
|
batch_size=None,
|
||||||
|
timeout=None,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@@ -1076,3 +1077,67 @@ async def test_query_serialization_async(table_async: AsyncTable):
|
|||||||
full_text_query=FullTextSearchQuery(columns=[], query="foo"),
|
full_text_query=FullTextSearchQuery(columns=[], query="foo"),
|
||||||
with_row_id=False,
|
with_row_id=False,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def test_query_timeout(tmp_path):
|
||||||
|
# Use local directory instead of memory:// to add a bit of latency to
|
||||||
|
# operations so a timeout of zero will trigger exceptions.
|
||||||
|
db = lancedb.connect(tmp_path)
|
||||||
|
data = pa.table(
|
||||||
|
{
|
||||||
|
"text": ["a", "b"],
|
||||||
|
"vector": pa.FixedSizeListArray.from_arrays(
|
||||||
|
pc.random(4).cast(pa.float32()), 2
|
||||||
|
),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
table = db.create_table("test", data)
|
||||||
|
table.create_fts_index("text", use_tantivy=False)
|
||||||
|
|
||||||
|
with pytest.raises(Exception, match="Query timeout"):
|
||||||
|
table.search().where("text = 'a'").to_list(timeout=timedelta(0))
|
||||||
|
|
||||||
|
with pytest.raises(Exception, match="Query timeout"):
|
||||||
|
table.search([0.0, 0.0]).to_arrow(timeout=timedelta(0))
|
||||||
|
|
||||||
|
with pytest.raises(Exception, match="Query timeout"):
|
||||||
|
table.search("a", query_type="fts").to_pandas(timeout=timedelta(0))
|
||||||
|
|
||||||
|
with pytest.raises(Exception, match="Query timeout"):
|
||||||
|
table.search(query_type="hybrid").vector([0.0, 0.0]).text("a").to_arrow(
|
||||||
|
timeout=timedelta(0)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_query_timeout_async(tmp_path):
|
||||||
|
db = await lancedb.connect_async(tmp_path)
|
||||||
|
data = pa.table(
|
||||||
|
{
|
||||||
|
"text": ["a", "b"],
|
||||||
|
"vector": pa.FixedSizeListArray.from_arrays(
|
||||||
|
pc.random(4).cast(pa.float32()), 2
|
||||||
|
),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
table = await db.create_table("test", data)
|
||||||
|
await table.create_index("text", config=FTS())
|
||||||
|
|
||||||
|
with pytest.raises(Exception, match="Query timeout"):
|
||||||
|
await table.query().where("text != 'a'").to_list(timeout=timedelta(0))
|
||||||
|
|
||||||
|
with pytest.raises(Exception, match="Query timeout"):
|
||||||
|
await table.vector_search([0.0, 0.0]).to_arrow(timeout=timedelta(0))
|
||||||
|
|
||||||
|
with pytest.raises(Exception, match="Query timeout"):
|
||||||
|
await (await table.search("a", query_type="fts")).to_pandas(
|
||||||
|
timeout=timedelta(0)
|
||||||
|
)
|
||||||
|
|
||||||
|
with pytest.raises(Exception, match="Query timeout"):
|
||||||
|
await (
|
||||||
|
table.query()
|
||||||
|
.nearest_to_text("a")
|
||||||
|
.nearest_to([0.0, 0.0])
|
||||||
|
.to_list(timeout=timedelta(0))
|
||||||
|
)
|
||||||
|
|||||||
@@ -457,3 +457,45 @@ def test_voyageai_reranker(tmp_path, use_tantivy):
|
|||||||
reranker = VoyageAIReranker(model_name="rerank-2")
|
reranker = VoyageAIReranker(model_name="rerank-2")
|
||||||
table, schema = get_test_table(tmp_path, use_tantivy)
|
table, schema = get_test_table(tmp_path, use_tantivy)
|
||||||
_run_test_reranker(reranker, table, "single player experience", None, schema)
|
_run_test_reranker(reranker, table, "single player experience", None, schema)
|
||||||
|
|
||||||
|
|
||||||
|
def test_empty_result_reranker():
|
||||||
|
pytest.importorskip("sentence_transformers")
|
||||||
|
db = lancedb.connect("memory://")
|
||||||
|
|
||||||
|
# Define schema
|
||||||
|
schema = pa.schema(
|
||||||
|
[
|
||||||
|
("id", pa.int64()),
|
||||||
|
("text", pa.string()),
|
||||||
|
("vector", pa.list_(pa.float32(), 128)), # 128-dimensional vector
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
|
# Create empty table with schema
|
||||||
|
empty_table = db.create_table("empty_table", schema=schema, mode="overwrite")
|
||||||
|
empty_table.create_fts_index("text", use_tantivy=False, replace=True)
|
||||||
|
for reranker in [
|
||||||
|
CrossEncoderReranker(),
|
||||||
|
# ColbertReranker(),
|
||||||
|
# AnswerdotaiRerankers(),
|
||||||
|
# OpenaiReranker(),
|
||||||
|
# JinaReranker(),
|
||||||
|
# VoyageAIReranker(model_name="rerank-2"),
|
||||||
|
]:
|
||||||
|
results = (
|
||||||
|
empty_table.search(list(range(128)))
|
||||||
|
.limit(3)
|
||||||
|
.rerank(reranker, "query")
|
||||||
|
.to_arrow()
|
||||||
|
)
|
||||||
|
# check if empty set contains _relevance_score column
|
||||||
|
assert "_relevance_score" in results.column_names
|
||||||
|
assert len(results) == 0
|
||||||
|
|
||||||
|
results = (
|
||||||
|
empty_table.search("query", query_type="fts")
|
||||||
|
.limit(3)
|
||||||
|
.rerank(reranker)
|
||||||
|
.to_arrow()
|
||||||
|
)
|
||||||
|
|||||||
@@ -32,11 +32,7 @@ def test_basic(mem_db: DBConnection):
|
|||||||
table = mem_db.create_table("test", data=data)
|
table = mem_db.create_table("test", data=data)
|
||||||
|
|
||||||
assert table.name == "test"
|
assert table.name == "test"
|
||||||
assert (
|
assert "LanceTable(name='test', version=1, _conn=LanceDBConnection(" in repr(table)
|
||||||
"LanceTable(name='test', version=1, "
|
|
||||||
"read_consistency_interval=datetime.timedelta(seconds=5), "
|
|
||||||
"_conn=LanceDBConnection("
|
|
||||||
) in repr(table)
|
|
||||||
expected_schema = pa.schema(
|
expected_schema = pa.schema(
|
||||||
{
|
{
|
||||||
"vector": pa.list_(pa.float32(), 2),
|
"vector": pa.list_(pa.float32(), 2),
|
||||||
|
|||||||
@@ -204,9 +204,7 @@ pub fn connect(
|
|||||||
}
|
}
|
||||||
if let Some(read_consistency_interval) = read_consistency_interval {
|
if let Some(read_consistency_interval) = read_consistency_interval {
|
||||||
let read_consistency_interval = Duration::from_secs_f64(read_consistency_interval);
|
let read_consistency_interval = Duration::from_secs_f64(read_consistency_interval);
|
||||||
builder = builder.read_consistency_interval(Some(read_consistency_interval));
|
builder = builder.read_consistency_interval(read_consistency_interval);
|
||||||
} else {
|
|
||||||
builder = builder.read_consistency_interval(None);
|
|
||||||
}
|
}
|
||||||
if let Some(storage_options) = storage_options {
|
if let Some(storage_options) = storage_options {
|
||||||
builder = builder.storage_options(storage_options);
|
builder = builder.storage_options(storage_options);
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
// SPDX-FileCopyrightText: Copyright The LanceDB Authors
|
// SPDX-FileCopyrightText: Copyright The LanceDB Authors
|
||||||
|
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
use std::time::Duration;
|
||||||
|
|
||||||
use arrow::array::make_array;
|
use arrow::array::make_array;
|
||||||
use arrow::array::Array;
|
use arrow::array::Array;
|
||||||
@@ -45,7 +46,7 @@ pub struct PyFullTextSearchQuery {
|
|||||||
|
|
||||||
impl From<FullTextSearchQuery> for PyFullTextSearchQuery {
|
impl From<FullTextSearchQuery> for PyFullTextSearchQuery {
|
||||||
fn from(query: FullTextSearchQuery) -> Self {
|
fn from(query: FullTextSearchQuery) -> Self {
|
||||||
PyFullTextSearchQuery {
|
Self {
|
||||||
columns: query.columns().into_iter().collect(),
|
columns: query.columns().into_iter().collect(),
|
||||||
query: query.query.query().to_owned(),
|
query: query.query.query().to_owned(),
|
||||||
limit: query.limit,
|
limit: query.limit,
|
||||||
@@ -99,7 +100,7 @@ pub struct PyQueryRequest {
|
|||||||
impl From<AnyQuery> for PyQueryRequest {
|
impl From<AnyQuery> for PyQueryRequest {
|
||||||
fn from(query: AnyQuery) -> Self {
|
fn from(query: AnyQuery) -> Self {
|
||||||
match query {
|
match query {
|
||||||
AnyQuery::Query(query_request) => PyQueryRequest {
|
AnyQuery::Query(query_request) => Self {
|
||||||
limit: query_request.limit,
|
limit: query_request.limit,
|
||||||
offset: query_request.offset,
|
offset: query_request.offset,
|
||||||
filter: query_request.filter.map(PyQueryFilter),
|
filter: query_request.filter.map(PyQueryFilter),
|
||||||
@@ -121,7 +122,7 @@ impl From<AnyQuery> for PyQueryRequest {
|
|||||||
postfilter: None,
|
postfilter: None,
|
||||||
norm: None,
|
norm: None,
|
||||||
},
|
},
|
||||||
AnyQuery::VectorQuery(vector_query) => PyQueryRequest {
|
AnyQuery::VectorQuery(vector_query) => Self {
|
||||||
limit: vector_query.base.limit,
|
limit: vector_query.base.limit,
|
||||||
offset: vector_query.base.offset,
|
offset: vector_query.base.offset,
|
||||||
filter: vector_query.base.filter.map(PyQueryFilter),
|
filter: vector_query.base.filter.map(PyQueryFilter),
|
||||||
@@ -294,10 +295,11 @@ impl Query {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
#[pyo3(signature = (max_batch_length=None))]
|
#[pyo3(signature = (max_batch_length=None, timeout=None))]
|
||||||
pub fn execute(
|
pub fn execute(
|
||||||
self_: PyRef<'_, Self>,
|
self_: PyRef<'_, Self>,
|
||||||
max_batch_length: Option<u32>,
|
max_batch_length: Option<u32>,
|
||||||
|
timeout: Option<Duration>,
|
||||||
) -> PyResult<Bound<'_, PyAny>> {
|
) -> PyResult<Bound<'_, PyAny>> {
|
||||||
let inner = self_.inner.clone();
|
let inner = self_.inner.clone();
|
||||||
future_into_py(self_.py(), async move {
|
future_into_py(self_.py(), async move {
|
||||||
@@ -305,6 +307,9 @@ impl Query {
|
|||||||
if let Some(max_batch_length) = max_batch_length {
|
if let Some(max_batch_length) = max_batch_length {
|
||||||
opts.max_batch_length = max_batch_length;
|
opts.max_batch_length = max_batch_length;
|
||||||
}
|
}
|
||||||
|
if let Some(timeout) = timeout {
|
||||||
|
opts.timeout = Some(timeout);
|
||||||
|
}
|
||||||
let inner_stream = inner.execute_with_options(opts).await.infer_error()?;
|
let inner_stream = inner.execute_with_options(opts).await.infer_error()?;
|
||||||
Ok(RecordBatchStream::new(inner_stream))
|
Ok(RecordBatchStream::new(inner_stream))
|
||||||
})
|
})
|
||||||
@@ -376,10 +381,11 @@ impl FTSQuery {
|
|||||||
self.inner = self.inner.clone().postfilter();
|
self.inner = self.inner.clone().postfilter();
|
||||||
}
|
}
|
||||||
|
|
||||||
#[pyo3(signature = (max_batch_length=None))]
|
#[pyo3(signature = (max_batch_length=None, timeout=None))]
|
||||||
pub fn execute(
|
pub fn execute(
|
||||||
self_: PyRef<'_, Self>,
|
self_: PyRef<'_, Self>,
|
||||||
max_batch_length: Option<u32>,
|
max_batch_length: Option<u32>,
|
||||||
|
timeout: Option<Duration>,
|
||||||
) -> PyResult<Bound<'_, PyAny>> {
|
) -> PyResult<Bound<'_, PyAny>> {
|
||||||
let inner = self_
|
let inner = self_
|
||||||
.inner
|
.inner
|
||||||
@@ -391,6 +397,9 @@ impl FTSQuery {
|
|||||||
if let Some(max_batch_length) = max_batch_length {
|
if let Some(max_batch_length) = max_batch_length {
|
||||||
opts.max_batch_length = max_batch_length;
|
opts.max_batch_length = max_batch_length;
|
||||||
}
|
}
|
||||||
|
if let Some(timeout) = timeout {
|
||||||
|
opts.timeout = Some(timeout);
|
||||||
|
}
|
||||||
let inner_stream = inner.execute_with_options(opts).await.infer_error()?;
|
let inner_stream = inner.execute_with_options(opts).await.infer_error()?;
|
||||||
Ok(RecordBatchStream::new(inner_stream))
|
Ok(RecordBatchStream::new(inner_stream))
|
||||||
})
|
})
|
||||||
@@ -513,10 +522,11 @@ impl VectorQuery {
|
|||||||
self.inner = self.inner.clone().bypass_vector_index()
|
self.inner = self.inner.clone().bypass_vector_index()
|
||||||
}
|
}
|
||||||
|
|
||||||
#[pyo3(signature = (max_batch_length=None))]
|
#[pyo3(signature = (max_batch_length=None, timeout=None))]
|
||||||
pub fn execute(
|
pub fn execute(
|
||||||
self_: PyRef<'_, Self>,
|
self_: PyRef<'_, Self>,
|
||||||
max_batch_length: Option<u32>,
|
max_batch_length: Option<u32>,
|
||||||
|
timeout: Option<Duration>,
|
||||||
) -> PyResult<Bound<'_, PyAny>> {
|
) -> PyResult<Bound<'_, PyAny>> {
|
||||||
let inner = self_.inner.clone();
|
let inner = self_.inner.clone();
|
||||||
future_into_py(self_.py(), async move {
|
future_into_py(self_.py(), async move {
|
||||||
@@ -524,6 +534,9 @@ impl VectorQuery {
|
|||||||
if let Some(max_batch_length) = max_batch_length {
|
if let Some(max_batch_length) = max_batch_length {
|
||||||
opts.max_batch_length = max_batch_length;
|
opts.max_batch_length = max_batch_length;
|
||||||
}
|
}
|
||||||
|
if let Some(timeout) = timeout {
|
||||||
|
opts.timeout = Some(timeout);
|
||||||
|
}
|
||||||
let inner_stream = inner.execute_with_options(opts).await.infer_error()?;
|
let inner_stream = inner.execute_with_options(opts).await.infer_error()?;
|
||||||
Ok(RecordBatchStream::new(inner_stream))
|
Ok(RecordBatchStream::new(inner_stream))
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -204,6 +204,14 @@ impl Table {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn prewarm_index(self_: PyRef<'_, Self>, index_name: String) -> PyResult<Bound<'_, PyAny>> {
|
||||||
|
let inner = self_.inner_ref()?.clone();
|
||||||
|
future_into_py(self_.py(), async move {
|
||||||
|
inner.prewarm_index(&index_name).await.infer_error()?;
|
||||||
|
Ok(())
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
pub fn list_indices(self_: PyRef<'_, Self>) -> PyResult<Bound<'_, PyAny>> {
|
pub fn list_indices(self_: PyRef<'_, Self>) -> PyResult<Bound<'_, PyAny>> {
|
||||||
let inner = self_.inner_ref()?.clone();
|
let inner = self_.inner_ref()?.clone();
|
||||||
future_into_py(self_.py(), async move {
|
future_into_py(self_.py(), async move {
|
||||||
|
|||||||
@@ -163,8 +163,9 @@ pub fn parse_fts_query(query: &Bound<'_, PyDict>) -> PyResult<FtsQuery> {
|
|||||||
.ok_or(PyValueError::new_err("boost not found"))?
|
.ok_or(PyValueError::new_err("boost not found"))?
|
||||||
.extract::<Vec<f32>>()?;
|
.extract::<Vec<f32>>()?;
|
||||||
|
|
||||||
let query =
|
let query = MultiMatchQuery::try_new(query, columns)
|
||||||
MultiMatchQuery::try_new_with_boosts(query, columns, boost).map_err(|e| {
|
.and_then(|q| q.try_with_boosts(boost))
|
||||||
|
.map_err(|e| {
|
||||||
PyValueError::new_err(format!("Error creating MultiMatchQuery: {}", e))
|
PyValueError::new_err(format!("Error creating MultiMatchQuery: {}", e))
|
||||||
})?;
|
})?;
|
||||||
Ok(query.into())
|
Ok(query.into())
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "lancedb-node"
|
name = "lancedb-node"
|
||||||
version = "0.19.0-beta.3"
|
version = "0.19.0-beta.7"
|
||||||
description = "Serverless, low-latency vector database for AI applications"
|
description = "Serverless, low-latency vector database for AI applications"
|
||||||
license.workspace = true
|
license.workspace = true
|
||||||
edition.workspace = true
|
edition.workspace = true
|
||||||
|
|||||||
@@ -60,7 +60,7 @@ fn database_new(mut cx: FunctionContext) -> JsResult<JsPromise> {
|
|||||||
let mut conn_builder = connect(&path).storage_options(storage_options);
|
let mut conn_builder = connect(&path).storage_options(storage_options);
|
||||||
|
|
||||||
if let Some(interval) = read_consistency_interval {
|
if let Some(interval) = read_consistency_interval {
|
||||||
conn_builder = conn_builder.read_consistency_interval(Some(interval));
|
conn_builder = conn_builder.read_consistency_interval(interval);
|
||||||
}
|
}
|
||||||
rt.spawn(async move {
|
rt.spawn(async move {
|
||||||
let database = conn_builder.execute().await;
|
let database = conn_builder.execute().await;
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "lancedb"
|
name = "lancedb"
|
||||||
version = "0.19.0-beta.3"
|
version = "0.19.0-beta.7"
|
||||||
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
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ use super::{
|
|||||||
Catalog, CatalogOptions, CreateDatabaseMode, CreateDatabaseRequest, DatabaseNamesRequest,
|
Catalog, CatalogOptions, CreateDatabaseMode, CreateDatabaseRequest, DatabaseNamesRequest,
|
||||||
OpenDatabaseRequest,
|
OpenDatabaseRequest,
|
||||||
};
|
};
|
||||||
use crate::connection::{ConnectRequest, DEFAULT_READ_CONSISTENCY_INTERVAL};
|
use crate::connection::ConnectRequest;
|
||||||
use crate::database::listing::{ListingDatabase, ListingDatabaseOptions};
|
use crate::database::listing::{ListingDatabase, ListingDatabaseOptions};
|
||||||
use crate::database::{Database, DatabaseOptions};
|
use crate::database::{Database, DatabaseOptions};
|
||||||
use crate::error::{CreateDirSnafu, Error, Result};
|
use crate::error::{CreateDirSnafu, Error, Result};
|
||||||
@@ -214,7 +214,7 @@ impl Catalog for ListingCatalog {
|
|||||||
uri: db_uri,
|
uri: db_uri,
|
||||||
#[cfg(feature = "remote")]
|
#[cfg(feature = "remote")]
|
||||||
client_config: Default::default(),
|
client_config: Default::default(),
|
||||||
read_consistency_interval: DEFAULT_READ_CONSISTENCY_INTERVAL,
|
read_consistency_interval: None,
|
||||||
options: Default::default(),
|
options: Default::default(),
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -241,7 +241,7 @@ impl Catalog for ListingCatalog {
|
|||||||
uri: db_path.to_string(),
|
uri: db_path.to_string(),
|
||||||
#[cfg(feature = "remote")]
|
#[cfg(feature = "remote")]
|
||||||
client_config: Default::default(),
|
client_config: Default::default(),
|
||||||
read_consistency_interval: DEFAULT_READ_CONSISTENCY_INTERVAL,
|
read_consistency_interval: None,
|
||||||
options: Default::default(),
|
options: Default::default(),
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -311,7 +311,7 @@ mod tests {
|
|||||||
#[cfg(feature = "remote")]
|
#[cfg(feature = "remote")]
|
||||||
client_config: Default::default(),
|
client_config: Default::default(),
|
||||||
options: Default::default(),
|
options: Default::default(),
|
||||||
read_consistency_interval: DEFAULT_READ_CONSISTENCY_INTERVAL,
|
read_consistency_interval: None,
|
||||||
};
|
};
|
||||||
|
|
||||||
let catalog = ListingCatalog::connect(&request).await.unwrap();
|
let catalog = ListingCatalog::connect(&request).await.unwrap();
|
||||||
|
|||||||
@@ -36,9 +36,6 @@ pub use lance_encoding::version::LanceFileVersion;
|
|||||||
#[cfg(feature = "remote")]
|
#[cfg(feature = "remote")]
|
||||||
use lance_io::object_store::StorageOptions;
|
use lance_io::object_store::StorageOptions;
|
||||||
|
|
||||||
pub(crate) const DEFAULT_READ_CONSISTENCY_INTERVAL: Option<std::time::Duration> =
|
|
||||||
Some(std::time::Duration::from_secs(5));
|
|
||||||
|
|
||||||
/// A builder for configuring a [`Connection::table_names`] operation
|
/// A builder for configuring a [`Connection::table_names`] operation
|
||||||
pub struct TableNamesBuilder {
|
pub struct TableNamesBuilder {
|
||||||
parent: Arc<dyn Database>,
|
parent: Arc<dyn Database>,
|
||||||
@@ -142,12 +139,6 @@ impl CreateTableBuilder<true> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Apply the given write options when writing the initial data
|
|
||||||
pub fn write_options(mut self, write_options: WriteOptions) -> Self {
|
|
||||||
self.request.write_options = write_options;
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Execute the create table operation
|
/// Execute the create table operation
|
||||||
pub async fn execute(self) -> Result<Table> {
|
pub async fn execute(self) -> Result<Table> {
|
||||||
let embedding_registry = self.embedding_registry.clone();
|
let embedding_registry = self.embedding_registry.clone();
|
||||||
@@ -229,6 +220,12 @@ impl<const HAS_DATA: bool> CreateTableBuilder<HAS_DATA> {
|
|||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Apply the given write options when writing the initial data
|
||||||
|
pub fn write_options(mut self, write_options: WriteOptions) -> Self {
|
||||||
|
self.request.write_options = write_options;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
/// Set an option for the storage layer.
|
/// Set an option for the storage layer.
|
||||||
///
|
///
|
||||||
/// Options already set on the connection will be inherited by the table,
|
/// Options already set on the connection will be inherited by the table,
|
||||||
@@ -621,15 +618,14 @@ pub struct ConnectRequest {
|
|||||||
|
|
||||||
/// The interval at which to check for updates from other processes.
|
/// The interval at which to check for updates from other processes.
|
||||||
///
|
///
|
||||||
/// If None, then consistency is not checked. For strong consistency, set this to
|
/// If None, then consistency is not checked. For performance
|
||||||
|
/// reasons, this is the default. For strong consistency, set this to
|
||||||
/// zero seconds. Then every read will check for updates from other
|
/// zero seconds. Then every read will check for updates from other
|
||||||
/// processes. As a compromise, you can set this to a non-zero timedelta
|
/// processes. As a compromise, you can set this to a non-zero timedelta
|
||||||
/// for eventual consistency. If more than that interval has passed since
|
/// for eventual consistency. If more than that interval has passed since
|
||||||
/// 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.
|
||||||
///
|
|
||||||
/// The default is 5 seconds.
|
|
||||||
pub read_consistency_interval: Option<std::time::Duration>,
|
pub read_consistency_interval: Option<std::time::Duration>,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -647,7 +643,7 @@ impl ConnectBuilder {
|
|||||||
uri: uri.to_string(),
|
uri: uri.to_string(),
|
||||||
#[cfg(feature = "remote")]
|
#[cfg(feature = "remote")]
|
||||||
client_config: Default::default(),
|
client_config: Default::default(),
|
||||||
read_consistency_interval: DEFAULT_READ_CONSISTENCY_INTERVAL,
|
read_consistency_interval: None,
|
||||||
options: HashMap::new(),
|
options: HashMap::new(),
|
||||||
},
|
},
|
||||||
embedding_registry: None,
|
embedding_registry: None,
|
||||||
@@ -786,7 +782,8 @@ impl ConnectBuilder {
|
|||||||
/// The interval at which to check for updates from other processes. This
|
/// The interval at which to check for updates from other processes. This
|
||||||
/// only affects LanceDB OSS.
|
/// only affects LanceDB OSS.
|
||||||
///
|
///
|
||||||
/// If left unset, consistency is not checked. For strong consistency, set this to
|
/// If left unset, consistency is not checked. For maximum read
|
||||||
|
/// performance, this is the default. For strong consistency, set this to
|
||||||
/// zero seconds. Then every read will check for updates from other processes.
|
/// zero seconds. Then every read will check for updates from other processes.
|
||||||
/// As a compromise, set this to a non-zero duration for eventual consistency.
|
/// As a compromise, set this to a non-zero duration for eventual consistency.
|
||||||
/// If more than that duration has passed since the last read, the read will
|
/// If more than that duration has passed since the last read, the read will
|
||||||
@@ -795,15 +792,13 @@ impl ConnectBuilder {
|
|||||||
/// This only affects read operations. Write operations are always
|
/// This only affects read operations. Write operations are always
|
||||||
/// consistent.
|
/// consistent.
|
||||||
///
|
///
|
||||||
/// The default is 5 seconds.
|
|
||||||
///
|
|
||||||
/// LanceDB Cloud uses eventual consistency under the hood, and is not
|
/// LanceDB Cloud uses eventual consistency under the hood, and is not
|
||||||
/// currently configurable.
|
/// currently configurable.
|
||||||
pub fn read_consistency_interval(
|
pub fn read_consistency_interval(
|
||||||
mut self,
|
mut self,
|
||||||
read_consistency_interval: Option<std::time::Duration>,
|
read_consistency_interval: std::time::Duration,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
self.request.read_consistency_interval = read_consistency_interval;
|
self.request.read_consistency_interval = Some(read_consistency_interval);
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -868,7 +863,7 @@ impl ConnectBuilder {
|
|||||||
/// # Arguments
|
/// # Arguments
|
||||||
///
|
///
|
||||||
/// * `uri` - URI where the database is located, can be a local directory, supported remote cloud storage,
|
/// * `uri` - URI where the database is located, can be a local directory, supported remote cloud storage,
|
||||||
/// or a LanceDB Cloud database. See [ConnectOptions::uri] for a list of accepted formats
|
/// or a LanceDB Cloud database. See [ConnectOptions::uri] for a list of accepted formats
|
||||||
pub fn connect(uri: &str) -> ConnectBuilder {
|
pub fn connect(uri: &str) -> ConnectBuilder {
|
||||||
ConnectBuilder::new(uri)
|
ConnectBuilder::new(uri)
|
||||||
}
|
}
|
||||||
@@ -887,7 +882,7 @@ impl CatalogConnectBuilder {
|
|||||||
uri: uri.to_string(),
|
uri: uri.to_string(),
|
||||||
#[cfg(feature = "remote")]
|
#[cfg(feature = "remote")]
|
||||||
client_config: Default::default(),
|
client_config: Default::default(),
|
||||||
read_consistency_interval: DEFAULT_READ_CONSISTENCY_INTERVAL,
|
read_consistency_interval: None,
|
||||||
options: HashMap::new(),
|
options: HashMap::new(),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -41,7 +41,7 @@ where
|
|||||||
/// ----------
|
/// ----------
|
||||||
/// - reader: RecordBatchReader
|
/// - reader: RecordBatchReader
|
||||||
/// - strict: if set true, only `fixed_size_list<float>` is considered as vector column. If set to false,
|
/// - strict: if set true, only `fixed_size_list<float>` is considered as vector column. If set to false,
|
||||||
/// a `list<float>` column with same length is also considered as vector column.
|
/// a `list<float>` column with same length is also considered as vector column.
|
||||||
pub fn infer_vector_columns(
|
pub fn infer_vector_columns(
|
||||||
reader: impl RecordBatchReader + Send,
|
reader: impl RecordBatchReader + Send,
|
||||||
strict: bool,
|
strict: bool,
|
||||||
|
|||||||
@@ -14,6 +14,9 @@ use object_store::{
|
|||||||
|
|
||||||
use async_trait::async_trait;
|
use async_trait::async_trait;
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
pub mod io_tracking;
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
struct MirroringObjectStore {
|
struct MirroringObjectStore {
|
||||||
primary: Arc<dyn ObjectStore>,
|
primary: Arc<dyn ObjectStore>,
|
||||||
|
|||||||
237
rust/lancedb/src/io/object_store/io_tracking.rs
Normal file
237
rust/lancedb/src/io/object_store/io_tracking.rs
Normal file
@@ -0,0 +1,237 @@
|
|||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
// SPDX-FileCopyrightText: Copyright The LanceDB Authors
|
||||||
|
|
||||||
|
use std::{
|
||||||
|
fmt::{Display, Formatter},
|
||||||
|
sync::{Arc, Mutex},
|
||||||
|
};
|
||||||
|
|
||||||
|
use bytes::Bytes;
|
||||||
|
use futures::stream::BoxStream;
|
||||||
|
use lance::io::WrappingObjectStore;
|
||||||
|
use object_store::{
|
||||||
|
path::Path, GetOptions, GetResult, ListResult, MultipartUpload, ObjectMeta, ObjectStore,
|
||||||
|
PutMultipartOpts, PutOptions, PutPayload, PutResult, Result as OSResult, UploadPart,
|
||||||
|
};
|
||||||
|
|
||||||
|
#[derive(Debug, Default)]
|
||||||
|
pub struct IoStats {
|
||||||
|
pub read_iops: u64,
|
||||||
|
pub read_bytes: u64,
|
||||||
|
pub write_iops: u64,
|
||||||
|
pub write_bytes: u64,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Display for IoStats {
|
||||||
|
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||||
|
write!(f, "{:#?}", self)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct IoTrackingStore {
|
||||||
|
target: Arc<dyn ObjectStore>,
|
||||||
|
stats: Arc<Mutex<IoStats>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Display for IoTrackingStore {
|
||||||
|
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||||
|
write!(f, "{:#?}", self)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Default, Clone)]
|
||||||
|
pub struct IoStatsHolder(Arc<Mutex<IoStats>>);
|
||||||
|
|
||||||
|
impl IoStatsHolder {
|
||||||
|
pub fn incremental_stats(&self) -> IoStats {
|
||||||
|
std::mem::take(&mut self.0.lock().expect("failed to lock IoStats"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl WrappingObjectStore for IoStatsHolder {
|
||||||
|
fn wrap(&self, target: Arc<dyn ObjectStore>) -> Arc<dyn ObjectStore> {
|
||||||
|
Arc::new(IoTrackingStore {
|
||||||
|
target,
|
||||||
|
stats: self.0.clone(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl IoTrackingStore {
|
||||||
|
pub fn new_wrapper() -> (Arc<dyn WrappingObjectStore>, Arc<Mutex<IoStats>>) {
|
||||||
|
let stats = Arc::new(Mutex::new(IoStats::default()));
|
||||||
|
(Arc::new(IoStatsHolder(stats.clone())), stats)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn record_read(&self, num_bytes: u64) {
|
||||||
|
let mut stats = self.stats.lock().unwrap();
|
||||||
|
stats.read_iops += 1;
|
||||||
|
stats.read_bytes += num_bytes;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn record_write(&self, num_bytes: u64) {
|
||||||
|
let mut stats = self.stats.lock().unwrap();
|
||||||
|
stats.write_iops += 1;
|
||||||
|
stats.write_bytes += num_bytes;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[async_trait::async_trait]
|
||||||
|
#[deny(clippy::missing_trait_methods)]
|
||||||
|
impl ObjectStore for IoTrackingStore {
|
||||||
|
async fn put(&self, location: &Path, bytes: PutPayload) -> OSResult<PutResult> {
|
||||||
|
self.record_write(bytes.content_length() as u64);
|
||||||
|
self.target.put(location, bytes).await
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn put_opts(
|
||||||
|
&self,
|
||||||
|
location: &Path,
|
||||||
|
bytes: PutPayload,
|
||||||
|
opts: PutOptions,
|
||||||
|
) -> OSResult<PutResult> {
|
||||||
|
self.record_write(bytes.content_length() as u64);
|
||||||
|
self.target.put_opts(location, bytes, opts).await
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn put_multipart(&self, location: &Path) -> OSResult<Box<dyn MultipartUpload>> {
|
||||||
|
let target = self.target.put_multipart(location).await?;
|
||||||
|
Ok(Box::new(IoTrackingMultipartUpload {
|
||||||
|
target,
|
||||||
|
stats: self.stats.clone(),
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn put_multipart_opts(
|
||||||
|
&self,
|
||||||
|
location: &Path,
|
||||||
|
opts: PutMultipartOpts,
|
||||||
|
) -> OSResult<Box<dyn MultipartUpload>> {
|
||||||
|
let target = self.target.put_multipart_opts(location, opts).await?;
|
||||||
|
Ok(Box::new(IoTrackingMultipartUpload {
|
||||||
|
target,
|
||||||
|
stats: self.stats.clone(),
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn get(&self, location: &Path) -> OSResult<GetResult> {
|
||||||
|
let result = self.target.get(location).await;
|
||||||
|
if let Ok(result) = &result {
|
||||||
|
let num_bytes = result.range.end - result.range.start;
|
||||||
|
self.record_read(num_bytes as u64);
|
||||||
|
}
|
||||||
|
result
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn get_opts(&self, location: &Path, options: GetOptions) -> OSResult<GetResult> {
|
||||||
|
let result = self.target.get_opts(location, options).await;
|
||||||
|
if let Ok(result) = &result {
|
||||||
|
let num_bytes = result.range.end - result.range.start;
|
||||||
|
self.record_read(num_bytes as u64);
|
||||||
|
}
|
||||||
|
result
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn get_range(&self, location: &Path, range: std::ops::Range<usize>) -> OSResult<Bytes> {
|
||||||
|
let result = self.target.get_range(location, range).await;
|
||||||
|
if let Ok(result) = &result {
|
||||||
|
self.record_read(result.len() as u64);
|
||||||
|
}
|
||||||
|
result
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn get_ranges(
|
||||||
|
&self,
|
||||||
|
location: &Path,
|
||||||
|
ranges: &[std::ops::Range<usize>],
|
||||||
|
) -> OSResult<Vec<Bytes>> {
|
||||||
|
let result = self.target.get_ranges(location, ranges).await;
|
||||||
|
if let Ok(result) = &result {
|
||||||
|
self.record_read(result.iter().map(|b| b.len() as u64).sum());
|
||||||
|
}
|
||||||
|
result
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn head(&self, location: &Path) -> OSResult<ObjectMeta> {
|
||||||
|
self.record_read(0);
|
||||||
|
self.target.head(location).await
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn delete(&self, location: &Path) -> OSResult<()> {
|
||||||
|
self.record_write(0);
|
||||||
|
self.target.delete(location).await
|
||||||
|
}
|
||||||
|
|
||||||
|
fn delete_stream<'a>(
|
||||||
|
&'a self,
|
||||||
|
locations: BoxStream<'a, OSResult<Path>>,
|
||||||
|
) -> BoxStream<'a, OSResult<Path>> {
|
||||||
|
self.target.delete_stream(locations)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn list(&self, prefix: Option<&Path>) -> BoxStream<'_, OSResult<ObjectMeta>> {
|
||||||
|
self.record_read(0);
|
||||||
|
self.target.list(prefix)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn list_with_offset(
|
||||||
|
&self,
|
||||||
|
prefix: Option<&Path>,
|
||||||
|
offset: &Path,
|
||||||
|
) -> BoxStream<'_, OSResult<ObjectMeta>> {
|
||||||
|
self.record_read(0);
|
||||||
|
self.target.list_with_offset(prefix, offset)
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn list_with_delimiter(&self, prefix: Option<&Path>) -> OSResult<ListResult> {
|
||||||
|
self.record_read(0);
|
||||||
|
self.target.list_with_delimiter(prefix).await
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn copy(&self, from: &Path, to: &Path) -> OSResult<()> {
|
||||||
|
self.record_write(0);
|
||||||
|
self.target.copy(from, to).await
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn rename(&self, from: &Path, to: &Path) -> OSResult<()> {
|
||||||
|
self.record_write(0);
|
||||||
|
self.target.rename(from, to).await
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn rename_if_not_exists(&self, from: &Path, to: &Path) -> OSResult<()> {
|
||||||
|
self.record_write(0);
|
||||||
|
self.target.rename_if_not_exists(from, to).await
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn copy_if_not_exists(&self, from: &Path, to: &Path) -> OSResult<()> {
|
||||||
|
self.record_write(0);
|
||||||
|
self.target.copy_if_not_exists(from, to).await
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
struct IoTrackingMultipartUpload {
|
||||||
|
target: Box<dyn MultipartUpload>,
|
||||||
|
stats: Arc<Mutex<IoStats>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[async_trait::async_trait]
|
||||||
|
impl MultipartUpload for IoTrackingMultipartUpload {
|
||||||
|
async fn abort(&mut self) -> OSResult<()> {
|
||||||
|
self.target.abort().await
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn complete(&mut self) -> OSResult<PutResult> {
|
||||||
|
self.target.complete().await
|
||||||
|
}
|
||||||
|
|
||||||
|
fn put_part(&mut self, payload: PutPayload) -> UploadPart {
|
||||||
|
{
|
||||||
|
let mut stats = self.stats.lock().unwrap();
|
||||||
|
stats.write_iops += 1;
|
||||||
|
stats.write_bytes += payload.content_length() as u64;
|
||||||
|
}
|
||||||
|
self.target.put_part(payload)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -31,7 +31,7 @@
|
|||||||
//! are not yet ready to be released.
|
//! are not yet ready to be released.
|
||||||
//!
|
//!
|
||||||
//! - `remote` - Enable remote client to connect to LanceDB cloud. This is not yet fully implemented
|
//! - `remote` - Enable remote client to connect to LanceDB cloud. This is not yet fully implemented
|
||||||
//! and should not be enabled.
|
//! and should not be enabled.
|
||||||
//!
|
//!
|
||||||
//! ### Quick Start
|
//! ### Quick Start
|
||||||
//!
|
//!
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
// SPDX-License-Identifier: Apache-2.0
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
// SPDX-FileCopyrightText: Copyright The LanceDB Authors
|
// SPDX-FileCopyrightText: Copyright The LanceDB Authors
|
||||||
|
|
||||||
use std::future::Future;
|
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
use std::{future::Future, time::Duration};
|
||||||
|
|
||||||
use arrow::compute::concat_batches;
|
use arrow::compute::concat_batches;
|
||||||
use arrow_array::{make_array, Array, Float16Array, Float32Array, Float64Array};
|
use arrow_array::{make_array, Array, Float16Array, Float32Array, Float64Array};
|
||||||
@@ -25,6 +25,7 @@ use crate::error::{Error, Result};
|
|||||||
use crate::rerankers::rrf::RRFReranker;
|
use crate::rerankers::rrf::RRFReranker;
|
||||||
use crate::rerankers::{check_reranker_result, NormalizeMethod, Reranker};
|
use crate::rerankers::{check_reranker_result, NormalizeMethod, Reranker};
|
||||||
use crate::table::BaseTable;
|
use crate::table::BaseTable;
|
||||||
|
use crate::utils::TimeoutStream;
|
||||||
use crate::DistanceType;
|
use crate::DistanceType;
|
||||||
use crate::{arrow::SendableRecordBatchStream, table::AnyQuery};
|
use crate::{arrow::SendableRecordBatchStream, table::AnyQuery};
|
||||||
|
|
||||||
@@ -525,12 +526,15 @@ pub struct QueryExecutionOptions {
|
|||||||
///
|
///
|
||||||
/// By default, this is 1024
|
/// By default, this is 1024
|
||||||
pub max_batch_length: u32,
|
pub max_batch_length: u32,
|
||||||
|
/// Max duration to wait for the query to execute before timing out.
|
||||||
|
pub timeout: Option<Duration>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for QueryExecutionOptions {
|
impl Default for QueryExecutionOptions {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self {
|
Self {
|
||||||
max_batch_length: 1024,
|
max_batch_length: 1024,
|
||||||
|
timeout: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1007,7 +1011,10 @@ impl VectorQuery {
|
|||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn execute_hybrid(&self) -> Result<SendableRecordBatchStream> {
|
pub async fn execute_hybrid(
|
||||||
|
&self,
|
||||||
|
options: QueryExecutionOptions,
|
||||||
|
) -> Result<SendableRecordBatchStream> {
|
||||||
// clone query and specify we want to include row IDs, which can be needed for reranking
|
// clone query and specify we want to include row IDs, which can be needed for reranking
|
||||||
let mut fts_query = Query::new(self.parent.clone());
|
let mut fts_query = Query::new(self.parent.clone());
|
||||||
fts_query.request = self.request.base.clone();
|
fts_query.request = self.request.base.clone();
|
||||||
@@ -1016,7 +1023,10 @@ impl VectorQuery {
|
|||||||
let mut vector_query = self.clone().with_row_id();
|
let mut vector_query = self.clone().with_row_id();
|
||||||
|
|
||||||
vector_query.request.base.full_text_search = None;
|
vector_query.request.base.full_text_search = None;
|
||||||
let (fts_results, vec_results) = try_join!(fts_query.execute(), vector_query.execute())?;
|
let (fts_results, vec_results) = try_join!(
|
||||||
|
fts_query.execute_with_options(options.clone()),
|
||||||
|
vector_query.inner_execute_with_options(options)
|
||||||
|
)?;
|
||||||
|
|
||||||
let (fts_results, vec_results) = try_join!(
|
let (fts_results, vec_results) = try_join!(
|
||||||
fts_results.try_collect::<Vec<_>>(),
|
fts_results.try_collect::<Vec<_>>(),
|
||||||
@@ -1074,6 +1084,20 @@ impl VectorQuery {
|
|||||||
RecordBatchStreamAdapter::new(results.schema(), stream::iter([Ok(results)])),
|
RecordBatchStreamAdapter::new(results.schema(), stream::iter([Ok(results)])),
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn inner_execute_with_options(
|
||||||
|
&self,
|
||||||
|
options: QueryExecutionOptions,
|
||||||
|
) -> Result<SendableRecordBatchStream> {
|
||||||
|
let plan = self.create_plan(options.clone()).await?;
|
||||||
|
let inner = execute_plan(plan, Default::default())?;
|
||||||
|
let inner = if let Some(timeout) = options.timeout {
|
||||||
|
TimeoutStream::new_boxed(inner, timeout)
|
||||||
|
} else {
|
||||||
|
inner
|
||||||
|
};
|
||||||
|
Ok(DatasetRecordBatchStream::new(inner).into())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ExecutableQuery for VectorQuery {
|
impl ExecutableQuery for VectorQuery {
|
||||||
@@ -1087,16 +1111,13 @@ impl ExecutableQuery for VectorQuery {
|
|||||||
options: QueryExecutionOptions,
|
options: QueryExecutionOptions,
|
||||||
) -> Result<SendableRecordBatchStream> {
|
) -> Result<SendableRecordBatchStream> {
|
||||||
if self.request.base.full_text_search.is_some() {
|
if self.request.base.full_text_search.is_some() {
|
||||||
let hybrid_result = async move { self.execute_hybrid().await }.boxed().await?;
|
let hybrid_result = async move { self.execute_hybrid(options).await }
|
||||||
|
.boxed()
|
||||||
|
.await?;
|
||||||
return Ok(hybrid_result);
|
return Ok(hybrid_result);
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(SendableRecordBatchStream::from(
|
self.inner_execute_with_options(options).await
|
||||||
DatasetRecordBatchStream::new(execute_plan(
|
|
||||||
self.create_plan(options).await?,
|
|
||||||
Default::default(),
|
|
||||||
)?),
|
|
||||||
))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn explain_plan(&self, verbose: bool) -> Result<String> {
|
async fn explain_plan(&self, verbose: bool) -> Result<String> {
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ use reqwest::{
|
|||||||
use crate::error::{Error, Result};
|
use crate::error::{Error, Result};
|
||||||
use crate::remote::db::RemoteOptions;
|
use crate::remote::db::RemoteOptions;
|
||||||
|
|
||||||
const REQUEST_ID_HEADER: &str = "x-request-id";
|
const REQUEST_ID_HEADER: HeaderName = HeaderName::from_static("x-request-id");
|
||||||
|
|
||||||
/// Configuration for the LanceDB Cloud HTTP client.
|
/// Configuration for the LanceDB Cloud HTTP client.
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
@@ -299,7 +299,7 @@ impl<S: HttpSend> RestfulLanceDbClient<S> {
|
|||||||
) -> Result<HeaderMap> {
|
) -> Result<HeaderMap> {
|
||||||
let mut headers = HeaderMap::new();
|
let mut headers = HeaderMap::new();
|
||||||
headers.insert(
|
headers.insert(
|
||||||
"x-api-key",
|
HeaderName::from_static("x-api-key"),
|
||||||
HeaderValue::from_str(api_key).map_err(|_| Error::InvalidInput {
|
HeaderValue::from_str(api_key).map_err(|_| Error::InvalidInput {
|
||||||
message: "non-ascii api key provided".to_string(),
|
message: "non-ascii api key provided".to_string(),
|
||||||
})?,
|
})?,
|
||||||
@@ -307,7 +307,7 @@ impl<S: HttpSend> RestfulLanceDbClient<S> {
|
|||||||
if region == "local" {
|
if region == "local" {
|
||||||
let host = format!("{}.local.api.lancedb.com", db_name);
|
let host = format!("{}.local.api.lancedb.com", db_name);
|
||||||
headers.insert(
|
headers.insert(
|
||||||
"Host",
|
http::header::HOST,
|
||||||
HeaderValue::from_str(&host).map_err(|_| Error::InvalidInput {
|
HeaderValue::from_str(&host).map_err(|_| Error::InvalidInput {
|
||||||
message: format!("non-ascii database name '{}' provided", db_name),
|
message: format!("non-ascii database name '{}' provided", db_name),
|
||||||
})?,
|
})?,
|
||||||
@@ -315,7 +315,7 @@ impl<S: HttpSend> RestfulLanceDbClient<S> {
|
|||||||
}
|
}
|
||||||
if has_host_override {
|
if has_host_override {
|
||||||
headers.insert(
|
headers.insert(
|
||||||
"x-lancedb-database",
|
HeaderName::from_static("x-lancedb-database"),
|
||||||
HeaderValue::from_str(db_name).map_err(|_| Error::InvalidInput {
|
HeaderValue::from_str(db_name).map_err(|_| Error::InvalidInput {
|
||||||
message: format!("non-ascii database name '{}' provided", db_name),
|
message: format!("non-ascii database name '{}' provided", db_name),
|
||||||
})?,
|
})?,
|
||||||
@@ -323,7 +323,7 @@ impl<S: HttpSend> RestfulLanceDbClient<S> {
|
|||||||
}
|
}
|
||||||
if db_prefix.is_some() {
|
if db_prefix.is_some() {
|
||||||
headers.insert(
|
headers.insert(
|
||||||
"x-lancedb-database-prefix",
|
HeaderName::from_static("x-lancedb-database-prefix"),
|
||||||
HeaderValue::from_str(db_prefix.unwrap()).map_err(|_| Error::InvalidInput {
|
HeaderValue::from_str(db_prefix.unwrap()).map_err(|_| Error::InvalidInput {
|
||||||
message: format!(
|
message: format!(
|
||||||
"non-ascii database prefix '{}' provided",
|
"non-ascii database prefix '{}' provided",
|
||||||
@@ -335,7 +335,7 @@ impl<S: HttpSend> RestfulLanceDbClient<S> {
|
|||||||
|
|
||||||
if let Some(v) = options.0.get("account_name") {
|
if let Some(v) = options.0.get("account_name") {
|
||||||
headers.insert(
|
headers.insert(
|
||||||
"x-azure-storage-account-name",
|
HeaderName::from_static("x-azure-storage-account-name"),
|
||||||
HeaderValue::from_str(v).map_err(|_| Error::InvalidInput {
|
HeaderValue::from_str(v).map_err(|_| Error::InvalidInput {
|
||||||
message: format!("non-ascii storage account name '{}' provided", db_name),
|
message: format!("non-ascii storage account name '{}' provided", db_name),
|
||||||
})?,
|
})?,
|
||||||
@@ -343,7 +343,7 @@ impl<S: HttpSend> RestfulLanceDbClient<S> {
|
|||||||
}
|
}
|
||||||
if let Some(v) = options.0.get("azure_storage_account_name") {
|
if let Some(v) = options.0.get("azure_storage_account_name") {
|
||||||
headers.insert(
|
headers.insert(
|
||||||
"x-azure-storage-account-name",
|
HeaderName::from_static("x-azure-storage-account-name"),
|
||||||
HeaderValue::from_str(v).map_err(|_| Error::InvalidInput {
|
HeaderValue::from_str(v).map_err(|_| Error::InvalidInput {
|
||||||
message: format!("non-ascii storage account name '{}' provided", db_name),
|
message: format!("non-ascii storage account name '{}' provided", db_name),
|
||||||
})?,
|
})?,
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ use datafusion_physical_plan::stream::RecordBatchStreamAdapter;
|
|||||||
use datafusion_physical_plan::{ExecutionPlan, RecordBatchStream, SendableRecordBatchStream};
|
use datafusion_physical_plan::{ExecutionPlan, RecordBatchStream, SendableRecordBatchStream};
|
||||||
use futures::TryStreamExt;
|
use futures::TryStreamExt;
|
||||||
use http::header::CONTENT_TYPE;
|
use http::header::CONTENT_TYPE;
|
||||||
use http::StatusCode;
|
use http::{HeaderName, StatusCode};
|
||||||
use lance::arrow::json::{JsonDataType, JsonSchema};
|
use lance::arrow::json::{JsonDataType, JsonSchema};
|
||||||
use lance::dataset::scanner::DatasetRecordBatchStream;
|
use lance::dataset::scanner::DatasetRecordBatchStream;
|
||||||
use lance::dataset::{ColumnAlteration, NewColumnTransform, Version};
|
use lance::dataset::{ColumnAlteration, NewColumnTransform, Version};
|
||||||
@@ -44,6 +44,8 @@ use super::client::{HttpSend, RestfulLanceDbClient, Sender};
|
|||||||
use super::db::ServerVersion;
|
use super::db::ServerVersion;
|
||||||
use super::ARROW_STREAM_CONTENT_TYPE;
|
use super::ARROW_STREAM_CONTENT_TYPE;
|
||||||
|
|
||||||
|
const REQUEST_TIMEOUT_HEADER: HeaderName = HeaderName::from_static("x-request-timeout-ms");
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct RemoteTable<S: HttpSend = Sender> {
|
pub struct RemoteTable<S: HttpSend = Sender> {
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
@@ -332,9 +334,19 @@ impl<S: HttpSend> RemoteTable<S> {
|
|||||||
async fn execute_query(
|
async fn execute_query(
|
||||||
&self,
|
&self,
|
||||||
query: &AnyQuery,
|
query: &AnyQuery,
|
||||||
_options: QueryExecutionOptions,
|
options: &QueryExecutionOptions,
|
||||||
) -> Result<Vec<Pin<Box<dyn RecordBatchStream + Send>>>> {
|
) -> Result<Vec<Pin<Box<dyn RecordBatchStream + Send>>>> {
|
||||||
let request = self.client.post(&format!("/v1/table/{}/query/", self.name));
|
let mut request = self.client.post(&format!("/v1/table/{}/query/", self.name));
|
||||||
|
|
||||||
|
if let Some(timeout) = options.timeout {
|
||||||
|
// Client side timeout
|
||||||
|
request = request.timeout(timeout);
|
||||||
|
// Also send to server, so it can abort the query if it takes too long.
|
||||||
|
// (If it doesn't fit into u64, it's not worth sending anyways.)
|
||||||
|
if let Ok(timeout_ms) = u64::try_from(timeout.as_millis()) {
|
||||||
|
request = request.header(REQUEST_TIMEOUT_HEADER, timeout_ms);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
let query_bodies = self.prepare_query_bodies(query).await?;
|
let query_bodies = self.prepare_query_bodies(query).await?;
|
||||||
let requests: Vec<reqwest::RequestBuilder> = query_bodies
|
let requests: Vec<reqwest::RequestBuilder> = query_bodies
|
||||||
@@ -543,7 +555,7 @@ impl<S: HttpSend> BaseTable for RemoteTable<S> {
|
|||||||
query: &AnyQuery,
|
query: &AnyQuery,
|
||||||
options: QueryExecutionOptions,
|
options: QueryExecutionOptions,
|
||||||
) -> Result<Arc<dyn ExecutionPlan>> {
|
) -> Result<Arc<dyn ExecutionPlan>> {
|
||||||
let streams = self.execute_query(query, options).await?;
|
let streams = self.execute_query(query, &options).await?;
|
||||||
if streams.len() == 1 {
|
if streams.len() == 1 {
|
||||||
let stream = streams.into_iter().next().unwrap();
|
let stream = streams.into_iter().next().unwrap();
|
||||||
Ok(Arc::new(OneShotExec::new(stream)))
|
Ok(Arc::new(OneShotExec::new(stream)))
|
||||||
@@ -559,9 +571,9 @@ impl<S: HttpSend> BaseTable for RemoteTable<S> {
|
|||||||
async fn query(
|
async fn query(
|
||||||
&self,
|
&self,
|
||||||
query: &AnyQuery,
|
query: &AnyQuery,
|
||||||
_options: QueryExecutionOptions,
|
options: QueryExecutionOptions,
|
||||||
) -> Result<DatasetRecordBatchStream> {
|
) -> Result<DatasetRecordBatchStream> {
|
||||||
let streams = self.execute_query(query, _options).await?;
|
let streams = self.execute_query(query, &options).await?;
|
||||||
|
|
||||||
if streams.len() == 1 {
|
if streams.len() == 1 {
|
||||||
Ok(DatasetRecordBatchStream::new(
|
Ok(DatasetRecordBatchStream::new(
|
||||||
@@ -991,6 +1003,12 @@ impl<S: HttpSend> BaseTable for RemoteTable<S> {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn prewarm_index(&self, _index_name: &str) -> Result<()> {
|
||||||
|
Err(Error::NotSupported {
|
||||||
|
message: "prewarm_index is not yet supported on LanceDB cloud.".into(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
async fn table_definition(&self) -> Result<TableDefinition> {
|
async fn table_definition(&self) -> Result<TableDefinition> {
|
||||||
Err(Error::NotSupported {
|
Err(Error::NotSupported {
|
||||||
message: "table_definition is not supported on LanceDB cloud.".into(),
|
message: "table_definition is not supported on LanceDB cloud.".into(),
|
||||||
@@ -1757,6 +1775,7 @@ mod tests {
|
|||||||
"boost": 1.0,
|
"boost": 1.0,
|
||||||
"fuzziness": 0,
|
"fuzziness": 0,
|
||||||
"max_expansions": 50,
|
"max_expansions": 50,
|
||||||
|
"operator": "Or",
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -29,8 +29,8 @@ impl FromStr for NormalizeMethod {
|
|||||||
|
|
||||||
fn from_str(s: &str) -> Result<Self> {
|
fn from_str(s: &str) -> Result<Self> {
|
||||||
match s.to_lowercase().as_str() {
|
match s.to_lowercase().as_str() {
|
||||||
"score" => Ok(NormalizeMethod::Score),
|
"score" => Ok(Self::Score),
|
||||||
"rank" => Ok(NormalizeMethod::Rank),
|
"rank" => Ok(Self::Rank),
|
||||||
_ => Err(Error::InvalidInput {
|
_ => Err(Error::InvalidInput {
|
||||||
message: format!("invalid normalize method: {}", s),
|
message: format!("invalid normalize method: {}", s),
|
||||||
}),
|
}),
|
||||||
@@ -41,8 +41,8 @@ impl FromStr for NormalizeMethod {
|
|||||||
impl std::fmt::Display for NormalizeMethod {
|
impl std::fmt::Display for NormalizeMethod {
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
match self {
|
match self {
|
||||||
NormalizeMethod::Score => write!(f, "score"),
|
Self::Score => write!(f, "score"),
|
||||||
NormalizeMethod::Rank => write!(f, "rank"),
|
Self::Rank => write!(f, "rank"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -68,7 +68,7 @@ use crate::query::{
|
|||||||
use crate::utils::{
|
use crate::utils::{
|
||||||
default_vector_column, supported_bitmap_data_type, supported_btree_data_type,
|
default_vector_column, supported_bitmap_data_type, supported_btree_data_type,
|
||||||
supported_fts_data_type, supported_label_list_data_type, supported_vector_data_type,
|
supported_fts_data_type, supported_label_list_data_type, supported_vector_data_type,
|
||||||
PatchReadParam, PatchWriteParam,
|
PatchReadParam, PatchWriteParam, TimeoutStream,
|
||||||
};
|
};
|
||||||
|
|
||||||
use self::dataset::DatasetConsistencyWrapper;
|
use self::dataset::DatasetConsistencyWrapper;
|
||||||
@@ -455,6 +455,8 @@ pub trait BaseTable: std::fmt::Display + std::fmt::Debug + Send + Sync {
|
|||||||
async fn list_indices(&self) -> Result<Vec<IndexConfig>>;
|
async fn list_indices(&self) -> Result<Vec<IndexConfig>>;
|
||||||
/// Drop an index from the table.
|
/// Drop an index from the table.
|
||||||
async fn drop_index(&self, name: &str) -> Result<()>;
|
async fn drop_index(&self, name: &str) -> Result<()>;
|
||||||
|
/// Prewarm an index in the table
|
||||||
|
async fn prewarm_index(&self, name: &str) -> Result<()>;
|
||||||
/// Get statistics about the index.
|
/// Get statistics about the index.
|
||||||
async fn index_stats(&self, index_name: &str) -> Result<Option<IndexStatistics>>;
|
async fn index_stats(&self, index_name: &str) -> Result<Option<IndexStatistics>>;
|
||||||
/// Merge insert new records into the table.
|
/// Merge insert new records into the table.
|
||||||
@@ -794,8 +796,8 @@ impl Table {
|
|||||||
/// # Arguments
|
/// # Arguments
|
||||||
///
|
///
|
||||||
/// * `on` One or more columns to join on. This is how records from the
|
/// * `on` One or more columns to join on. This is how records from the
|
||||||
/// source table and target table are matched. Typically this is some
|
/// source table and target table are matched. Typically this is some
|
||||||
/// kind of key or id column.
|
/// kind of key or id column.
|
||||||
///
|
///
|
||||||
/// # Examples
|
/// # Examples
|
||||||
///
|
///
|
||||||
@@ -1086,6 +1088,22 @@ impl Table {
|
|||||||
self.inner.drop_index(name).await
|
self.inner.drop_index(name).await
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Prewarm an index in the table
|
||||||
|
///
|
||||||
|
/// This is a hint to fully load the index into memory. It can be used to
|
||||||
|
/// avoid cold starts
|
||||||
|
///
|
||||||
|
/// It is generally wasteful to call this if the index does not fit into the
|
||||||
|
/// available cache.
|
||||||
|
///
|
||||||
|
/// Note: This function is not yet supported on all indices, in which case it
|
||||||
|
/// may do nothing.
|
||||||
|
///
|
||||||
|
/// Use [`Self::list_indices()`] to find the names of the indices.
|
||||||
|
pub async fn prewarm_index(&self, name: &str) -> Result<()> {
|
||||||
|
self.inner.prewarm_index(name).await
|
||||||
|
}
|
||||||
|
|
||||||
// Take many execution plans and map them into a single plan that adds
|
// Take many execution plans and map them into a single plan that adds
|
||||||
// a query_index column and unions them.
|
// a query_index column and unions them.
|
||||||
pub(crate) fn multi_vector_plan(
|
pub(crate) fn multi_vector_plan(
|
||||||
@@ -1775,11 +1793,14 @@ impl NativeTable {
|
|||||||
query: &AnyQuery,
|
query: &AnyQuery,
|
||||||
options: QueryExecutionOptions,
|
options: QueryExecutionOptions,
|
||||||
) -> Result<DatasetRecordBatchStream> {
|
) -> Result<DatasetRecordBatchStream> {
|
||||||
let plan = self.create_plan(query, options).await?;
|
let plan = self.create_plan(query, options.clone()).await?;
|
||||||
Ok(DatasetRecordBatchStream::new(execute_plan(
|
let inner = execute_plan(plan, Default::default())?;
|
||||||
plan,
|
let inner = if let Some(timeout) = options.timeout {
|
||||||
Default::default(),
|
TimeoutStream::new_boxed(inner, timeout)
|
||||||
)?))
|
} else {
|
||||||
|
inner
|
||||||
|
};
|
||||||
|
Ok(DatasetRecordBatchStream::new(inner))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Check whether the table uses V2 manifest paths.
|
/// Check whether the table uses V2 manifest paths.
|
||||||
@@ -2003,6 +2024,11 @@ impl BaseTable for NativeTable {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn prewarm_index(&self, index_name: &str) -> Result<()> {
|
||||||
|
let dataset = self.dataset.get().await?;
|
||||||
|
Ok(dataset.prewarm_index(index_name).await?)
|
||||||
|
}
|
||||||
|
|
||||||
async fn update(&self, update: UpdateBuilder) -> Result<u64> {
|
async fn update(&self, update: UpdateBuilder) -> Result<u64> {
|
||||||
let dataset = self.dataset.get().await?.clone();
|
let dataset = self.dataset.get().await?.clone();
|
||||||
let mut builder = LanceUpdateBuilder::new(Arc::new(dataset));
|
let mut builder = LanceUpdateBuilder::new(Arc::new(dataset));
|
||||||
@@ -2626,7 +2652,7 @@ mod tests {
|
|||||||
let dataset_path = tmp_dir.path().join("test.lance");
|
let dataset_path = tmp_dir.path().join("test.lance");
|
||||||
let uri = dataset_path.to_str().unwrap();
|
let uri = dataset_path.to_str().unwrap();
|
||||||
let conn = connect(uri)
|
let conn = connect(uri)
|
||||||
.read_consistency_interval(Some(Duration::from_secs(0)))
|
.read_consistency_interval(Duration::from_secs(0))
|
||||||
.execute()
|
.execute()
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
@@ -2709,7 +2735,7 @@ mod tests {
|
|||||||
let dataset_path = tmp_dir.path().join("test.lance");
|
let dataset_path = tmp_dir.path().join("test.lance");
|
||||||
let uri = dataset_path.to_str().unwrap();
|
let uri = dataset_path.to_str().unwrap();
|
||||||
let conn = connect(uri)
|
let conn = connect(uri)
|
||||||
.read_consistency_interval(Some(Duration::from_secs(0)))
|
.read_consistency_interval(Duration::from_secs(0))
|
||||||
.execute()
|
.execute()
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
@@ -2906,7 +2932,7 @@ mod tests {
|
|||||||
let dataset_path = tmp_dir.path().join("test.lance");
|
let dataset_path = tmp_dir.path().join("test.lance");
|
||||||
let uri = dataset_path.to_str().unwrap();
|
let uri = dataset_path.to_str().unwrap();
|
||||||
let conn = connect(uri)
|
let conn = connect(uri)
|
||||||
.read_consistency_interval(Some(Duration::from_secs(0)))
|
.read_consistency_interval(Duration::from_secs(0))
|
||||||
.execute()
|
.execute()
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
@@ -3452,6 +3478,9 @@ mod tests {
|
|||||||
assert_eq!(stats.num_unindexed_rows, 0);
|
assert_eq!(stats.num_unindexed_rows, 0);
|
||||||
assert_eq!(stats.index_type, crate::index::IndexType::FTS);
|
assert_eq!(stats.index_type, crate::index::IndexType::FTS);
|
||||||
assert_eq!(stats.distance_type, None);
|
assert_eq!(stats.distance_type, None);
|
||||||
|
|
||||||
|
// Make sure we can call prewarm without error
|
||||||
|
table.prewarm_index("text_idx").await.unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
@@ -3477,8 +3506,7 @@ mod tests {
|
|||||||
|
|
||||||
let mut conn2 = ConnectBuilder::new(uri);
|
let mut conn2 = ConnectBuilder::new(uri);
|
||||||
if let Some(interval) = interval {
|
if let Some(interval) = interval {
|
||||||
conn2 = conn2
|
conn2 = conn2.read_consistency_interval(std::time::Duration::from_millis(interval));
|
||||||
.read_consistency_interval(Some(std::time::Duration::from_millis(interval)));
|
|
||||||
}
|
}
|
||||||
let conn2 = conn2.execute().await.unwrap();
|
let conn2 = conn2.execute().await.unwrap();
|
||||||
let table2 = conn2.open_table("my_table").execute().await.unwrap();
|
let table2 = conn2.open_table("my_table").execute().await.unwrap();
|
||||||
@@ -3514,7 +3542,7 @@ mod tests {
|
|||||||
let uri = tmp_dir.path().to_str().unwrap();
|
let uri = tmp_dir.path().to_str().unwrap();
|
||||||
|
|
||||||
let conn = ConnectBuilder::new(uri)
|
let conn = ConnectBuilder::new(uri)
|
||||||
.read_consistency_interval(Some(Duration::from_secs(0)))
|
.read_consistency_interval(Duration::from_secs(0))
|
||||||
.execute()
|
.execute()
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
@@ -3535,7 +3563,7 @@ mod tests {
|
|||||||
let uri = tmp_dir.path().to_str().unwrap();
|
let uri = tmp_dir.path().to_str().unwrap();
|
||||||
|
|
||||||
let conn = ConnectBuilder::new(uri)
|
let conn = ConnectBuilder::new(uri)
|
||||||
.read_consistency_interval(Some(Duration::from_secs(0)))
|
.read_consistency_interval(Duration::from_secs(0))
|
||||||
.execute()
|
.execute()
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
@@ -3548,7 +3576,7 @@ mod tests {
|
|||||||
let native_tbl = table.as_native().unwrap();
|
let native_tbl = table.as_native().unwrap();
|
||||||
|
|
||||||
let manifest = native_tbl.manifest().await.unwrap();
|
let manifest = native_tbl.manifest().await.unwrap();
|
||||||
assert_eq!(manifest.config.len(), 0);
|
let base_config_len = manifest.config.len();
|
||||||
|
|
||||||
native_tbl
|
native_tbl
|
||||||
.update_config(vec![("test_key1".to_string(), "test_val1".to_string())])
|
.update_config(vec![("test_key1".to_string(), "test_val1".to_string())])
|
||||||
@@ -3556,7 +3584,7 @@ mod tests {
|
|||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
let manifest = native_tbl.manifest().await.unwrap();
|
let manifest = native_tbl.manifest().await.unwrap();
|
||||||
assert_eq!(manifest.config.len(), 1);
|
assert_eq!(manifest.config.len(), 1 + base_config_len);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
manifest.config.get("test_key1"),
|
manifest.config.get("test_key1"),
|
||||||
Some(&"test_val1".to_string())
|
Some(&"test_val1".to_string())
|
||||||
@@ -3567,7 +3595,7 @@ mod tests {
|
|||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
let manifest = native_tbl.manifest().await.unwrap();
|
let manifest = native_tbl.manifest().await.unwrap();
|
||||||
assert_eq!(manifest.config.len(), 2);
|
assert_eq!(manifest.config.len(), 2 + base_config_len);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
manifest.config.get("test_key1"),
|
manifest.config.get("test_key1"),
|
||||||
Some(&"test_val1".to_string())
|
Some(&"test_val1".to_string())
|
||||||
@@ -3585,7 +3613,7 @@ mod tests {
|
|||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
let manifest = native_tbl.manifest().await.unwrap();
|
let manifest = native_tbl.manifest().await.unwrap();
|
||||||
assert_eq!(manifest.config.len(), 2);
|
assert_eq!(manifest.config.len(), 2 + base_config_len);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
manifest.config.get("test_key1"),
|
manifest.config.get("test_key1"),
|
||||||
Some(&"test_val1".to_string())
|
Some(&"test_val1".to_string())
|
||||||
@@ -3597,7 +3625,7 @@ mod tests {
|
|||||||
|
|
||||||
native_tbl.delete_config_keys(&["test_key1"]).await.unwrap();
|
native_tbl.delete_config_keys(&["test_key1"]).await.unwrap();
|
||||||
let manifest = native_tbl.manifest().await.unwrap();
|
let manifest = native_tbl.manifest().await.unwrap();
|
||||||
assert_eq!(manifest.config.len(), 1);
|
assert_eq!(manifest.config.len(), 1 + base_config_len);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
manifest.config.get("test_key2"),
|
manifest.config.get("test_key2"),
|
||||||
Some(&"test_val2_update".to_string())
|
Some(&"test_val2_update".to_string())
|
||||||
@@ -3610,7 +3638,7 @@ mod tests {
|
|||||||
let uri = tmp_dir.path().to_str().unwrap();
|
let uri = tmp_dir.path().to_str().unwrap();
|
||||||
|
|
||||||
let conn = ConnectBuilder::new(uri)
|
let conn = ConnectBuilder::new(uri)
|
||||||
.read_consistency_interval(Some(Duration::from_secs(0)))
|
.read_consistency_interval(Duration::from_secs(0))
|
||||||
.execute()
|
.execute()
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
@@ -3672,7 +3700,7 @@ mod tests {
|
|||||||
let uri = tmp_dir.path().to_str().unwrap();
|
let uri = tmp_dir.path().to_str().unwrap();
|
||||||
|
|
||||||
let conn = ConnectBuilder::new(uri)
|
let conn = ConnectBuilder::new(uri)
|
||||||
.read_consistency_interval(Some(Duration::from_secs(0)))
|
.read_consistency_interval(Duration::from_secs(0))
|
||||||
.execute()
|
.execute()
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|||||||
@@ -7,7 +7,6 @@ use std::{
|
|||||||
time::{self, Duration, Instant},
|
time::{self, Duration, Instant},
|
||||||
};
|
};
|
||||||
|
|
||||||
use futures::FutureExt;
|
|
||||||
use lance::Dataset;
|
use lance::Dataset;
|
||||||
use tokio::sync::{RwLock, RwLockReadGuard, RwLockWriteGuard};
|
use tokio::sync::{RwLock, RwLockReadGuard, RwLockWriteGuard};
|
||||||
|
|
||||||
@@ -23,16 +22,13 @@ pub struct DatasetConsistencyWrapper(Arc<RwLock<DatasetRef>>);
|
|||||||
///
|
///
|
||||||
/// The dataset is lazily loaded, and starts off as None. On the first access,
|
/// The dataset is lazily loaded, and starts off as None. On the first access,
|
||||||
/// the dataset is loaded.
|
/// the dataset is loaded.
|
||||||
#[derive(Debug)]
|
#[derive(Debug, Clone)]
|
||||||
enum DatasetRef {
|
enum DatasetRef {
|
||||||
/// In this mode, the dataset is always the latest version.
|
/// In this mode, the dataset is always the latest version.
|
||||||
Latest {
|
Latest {
|
||||||
dataset: Dataset,
|
dataset: Dataset,
|
||||||
read_consistency_interval: Option<Duration>,
|
read_consistency_interval: Option<Duration>,
|
||||||
last_consistency_check: Option<time::Instant>,
|
last_consistency_check: Option<time::Instant>,
|
||||||
/// A background task loading the next version of the dataset. This happens
|
|
||||||
/// in the background so as not to block the current thread.
|
|
||||||
refresh_task: Option<tokio::task::JoinHandle<Result<Dataset>>>,
|
|
||||||
},
|
},
|
||||||
/// In this mode, the dataset is a specific version. It cannot be mutated.
|
/// In this mode, the dataset is a specific version. It cannot be mutated.
|
||||||
TimeTravel { dataset: Dataset, version: u64 },
|
TimeTravel { dataset: Dataset, version: u64 },
|
||||||
@@ -45,19 +41,9 @@ impl DatasetRef {
|
|||||||
Self::Latest {
|
Self::Latest {
|
||||||
dataset,
|
dataset,
|
||||||
last_consistency_check,
|
last_consistency_check,
|
||||||
refresh_task,
|
|
||||||
..
|
..
|
||||||
} => {
|
} => {
|
||||||
dataset.checkout_latest().await?;
|
dataset.checkout_latest().await?;
|
||||||
// Replace the refresh task
|
|
||||||
if let Some(refresh_task) = refresh_task {
|
|
||||||
refresh_task.abort();
|
|
||||||
}
|
|
||||||
let mut new_dataset = dataset.clone();
|
|
||||||
refresh_task.replace(tokio::spawn(async move {
|
|
||||||
new_dataset.checkout_latest().await?;
|
|
||||||
Ok(new_dataset)
|
|
||||||
}));
|
|
||||||
last_consistency_check.replace(Instant::now());
|
last_consistency_check.replace(Instant::now());
|
||||||
}
|
}
|
||||||
Self::TimeTravel { dataset, version } => {
|
Self::TimeTravel { dataset, version } => {
|
||||||
@@ -71,24 +57,26 @@ impl DatasetRef {
|
|||||||
matches!(self, Self::Latest { .. })
|
matches!(self, Self::Latest { .. })
|
||||||
}
|
}
|
||||||
|
|
||||||
fn strong_consistency(&self) -> bool {
|
async fn need_reload(&self) -> Result<bool> {
|
||||||
matches!(
|
Ok(match self {
|
||||||
self,
|
Self::Latest { dataset, .. } => {
|
||||||
Self::Latest { read_consistency_interval: Some(interval), .. }
|
dataset.latest_version_id().await? != dataset.version().version
|
||||||
if interval.as_nanos() == 0
|
}
|
||||||
)
|
Self::TimeTravel { dataset, version } => dataset.version().version != *version,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn as_latest(&mut self, read_consistency_interval: Option<Duration>) -> Result<()> {
|
async fn as_latest(&mut self, read_consistency_interval: Option<Duration>) -> Result<()> {
|
||||||
match self {
|
match self {
|
||||||
Self::Latest { .. } => Ok(()),
|
Self::Latest { .. } => Ok(()),
|
||||||
Self::TimeTravel { dataset, .. } => {
|
Self::TimeTravel { dataset, .. } => {
|
||||||
dataset.checkout_latest().await?;
|
dataset
|
||||||
|
.checkout_version(dataset.latest_version_id().await?)
|
||||||
|
.await?;
|
||||||
*self = Self::Latest {
|
*self = Self::Latest {
|
||||||
dataset: dataset.clone(),
|
dataset: dataset.clone(),
|
||||||
read_consistency_interval,
|
read_consistency_interval,
|
||||||
last_consistency_check: Some(Instant::now()),
|
last_consistency_check: Some(Instant::now()),
|
||||||
refresh_task: None,
|
|
||||||
};
|
};
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@@ -126,74 +114,13 @@ impl DatasetRef {
|
|||||||
match self {
|
match self {
|
||||||
Self::Latest {
|
Self::Latest {
|
||||||
dataset: ref mut ds,
|
dataset: ref mut ds,
|
||||||
refresh_task,
|
|
||||||
last_consistency_check,
|
|
||||||
..
|
..
|
||||||
} => {
|
} => {
|
||||||
*ds = dataset;
|
*ds = dataset;
|
||||||
if let Some(refresh_task) = refresh_task {
|
|
||||||
refresh_task.abort();
|
|
||||||
}
|
|
||||||
*refresh_task = None;
|
|
||||||
*last_consistency_check = Some(Instant::now());
|
|
||||||
}
|
}
|
||||||
_ => unreachable!("Dataset should be in latest mode at this point"),
|
_ => unreachable!("Dataset should be in latest mode at this point"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Wait for the background refresh task to complete.
|
|
||||||
async fn await_refresh(&mut self) -> Result<()> {
|
|
||||||
if let Self::Latest {
|
|
||||||
refresh_task: Some(refresh_task),
|
|
||||||
read_consistency_interval,
|
|
||||||
..
|
|
||||||
} = self
|
|
||||||
{
|
|
||||||
let dataset = refresh_task.await.expect("Refresh task panicked")?;
|
|
||||||
*self = Self::Latest {
|
|
||||||
dataset,
|
|
||||||
read_consistency_interval: *read_consistency_interval,
|
|
||||||
last_consistency_check: Some(Instant::now()),
|
|
||||||
refresh_task: None,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Check if background refresh task is done, and if so, update the dataset.
|
|
||||||
fn check_refresh(&mut self) -> Result<()> {
|
|
||||||
if let Self::Latest {
|
|
||||||
refresh_task: Some(refresh_task),
|
|
||||||
read_consistency_interval,
|
|
||||||
..
|
|
||||||
} = self
|
|
||||||
{
|
|
||||||
if refresh_task.is_finished() {
|
|
||||||
let dataset = refresh_task
|
|
||||||
.now_or_never()
|
|
||||||
.unwrap()
|
|
||||||
.expect("Refresh task panicked")?;
|
|
||||||
*self = Self::Latest {
|
|
||||||
dataset,
|
|
||||||
read_consistency_interval: *read_consistency_interval,
|
|
||||||
last_consistency_check: Some(Instant::now()),
|
|
||||||
refresh_task: None,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn refresh_is_ready(&self) -> bool {
|
|
||||||
matches!(
|
|
||||||
self,
|
|
||||||
Self::Latest {
|
|
||||||
refresh_task: Some(refresh_task),
|
|
||||||
..
|
|
||||||
}
|
|
||||||
if refresh_task.is_finished()
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl DatasetConsistencyWrapper {
|
impl DatasetConsistencyWrapper {
|
||||||
@@ -203,7 +130,6 @@ impl DatasetConsistencyWrapper {
|
|||||||
dataset,
|
dataset,
|
||||||
read_consistency_interval,
|
read_consistency_interval,
|
||||||
last_consistency_check: Some(Instant::now()),
|
last_consistency_check: Some(Instant::now()),
|
||||||
refresh_task: None,
|
|
||||||
})))
|
})))
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -262,9 +188,18 @@ impl DatasetConsistencyWrapper {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub async fn reload(&self) -> Result<()> {
|
pub async fn reload(&self) -> Result<()> {
|
||||||
|
if !self.0.read().await.need_reload().await? {
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
let mut write_guard = self.0.write().await;
|
let mut write_guard = self.0.write().await;
|
||||||
write_guard.reload().await?;
|
// on lock escalation -- check if someone else has already reloaded
|
||||||
write_guard.await_refresh().await
|
if !write_guard.need_reload().await? {
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
|
// actually need reloading
|
||||||
|
write_guard.reload().await
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the version, if in time travel mode, or None otherwise
|
/// Returns the version, if in time travel mode, or None otherwise
|
||||||
@@ -310,26 +245,9 @@ impl DatasetConsistencyWrapper {
|
|||||||
/// Ensures that the dataset is loaded and up-to-date with consistency and
|
/// Ensures that the dataset is loaded and up-to-date with consistency and
|
||||||
/// version parameters.
|
/// version parameters.
|
||||||
async fn ensure_up_to_date(&self) -> Result<()> {
|
async fn ensure_up_to_date(&self) -> Result<()> {
|
||||||
// We may have previously created a background task to fetch the new
|
|
||||||
// version of the dataset. If that task is done, we should update the
|
|
||||||
// dataset.
|
|
||||||
{
|
|
||||||
let read_guard = self.0.read().await;
|
|
||||||
if read_guard.refresh_is_ready() {
|
|
||||||
drop(read_guard);
|
|
||||||
self.0.write().await.check_refresh()?;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if !self.is_up_to_date().await? {
|
if !self.is_up_to_date().await? {
|
||||||
self.reload().await?;
|
self.reload().await?;
|
||||||
}
|
}
|
||||||
|
|
||||||
// If we are in strong consistency mode, we should await the refresh task.
|
|
||||||
if self.0.read().await.strong_consistency() {
|
|
||||||
self.0.write().await.await_refresh().await?;
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -372,3 +290,48 @@ impl DerefMut for DatasetWriteGuard<'_> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use arrow_schema::{DataType, Field, Schema};
|
||||||
|
use lance::{dataset::WriteParams, io::ObjectStoreParams};
|
||||||
|
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
use crate::{connect, io::object_store::io_tracking::IoStatsHolder, table::WriteOptions};
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_iops_open_strong_consistency() {
|
||||||
|
let db = connect("memory://")
|
||||||
|
.read_consistency_interval(Duration::ZERO)
|
||||||
|
.execute()
|
||||||
|
.await
|
||||||
|
.expect("Failed to connect to database");
|
||||||
|
let io_stats = IoStatsHolder::default();
|
||||||
|
|
||||||
|
let schema = Arc::new(Schema::new(vec![Field::new("id", DataType::Int32, false)]));
|
||||||
|
|
||||||
|
let table = db
|
||||||
|
.create_empty_table("test", schema)
|
||||||
|
.write_options(WriteOptions {
|
||||||
|
lance_write_params: Some(WriteParams {
|
||||||
|
store_params: Some(ObjectStoreParams {
|
||||||
|
object_store_wrapper: Some(Arc::new(io_stats.clone())),
|
||||||
|
..Default::default()
|
||||||
|
}),
|
||||||
|
..Default::default()
|
||||||
|
}),
|
||||||
|
})
|
||||||
|
.execute()
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
io_stats.incremental_stats();
|
||||||
|
|
||||||
|
// We should only need 1 read IOP to check the schema: looking for the
|
||||||
|
// latest version.
|
||||||
|
table.schema().await.unwrap();
|
||||||
|
let stats = io_stats.incremental_stats();
|
||||||
|
assert_eq!(stats.read_iops, 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -3,14 +3,20 @@
|
|||||||
|
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
use arrow_schema::{DataType, Schema};
|
use arrow_array::RecordBatch;
|
||||||
|
use arrow_schema::{DataType, Schema, SchemaRef};
|
||||||
|
use datafusion_common::{DataFusionError, Result as DataFusionResult};
|
||||||
|
use datafusion_execution::RecordBatchStream;
|
||||||
|
use futures::{FutureExt, Stream};
|
||||||
use lance::arrow::json::JsonDataType;
|
use lance::arrow::json::JsonDataType;
|
||||||
use lance::dataset::{ReadParams, WriteParams};
|
use lance::dataset::{ReadParams, WriteParams};
|
||||||
use lance::index::vector::utils::infer_vector_dim;
|
use lance::index::vector::utils::infer_vector_dim;
|
||||||
use lance::io::{ObjectStoreParams, WrappingObjectStore};
|
use lance::io::{ObjectStoreParams, WrappingObjectStore};
|
||||||
use lazy_static::lazy_static;
|
use lazy_static::lazy_static;
|
||||||
|
use std::pin::Pin;
|
||||||
|
|
||||||
use crate::error::{Error, Result};
|
use crate::error::{Error, Result};
|
||||||
|
use datafusion_physical_plan::SendableRecordBatchStream;
|
||||||
|
|
||||||
lazy_static! {
|
lazy_static! {
|
||||||
static ref TABLE_NAME_REGEX: regex::Regex = regex::Regex::new(r"^[a-zA-Z0-9_\-\.]+$").unwrap();
|
static ref TABLE_NAME_REGEX: regex::Regex = regex::Regex::new(r"^[a-zA-Z0-9_\-\.]+$").unwrap();
|
||||||
@@ -152,7 +158,17 @@ pub fn supported_label_list_data_type(dtype: &DataType) -> bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn supported_fts_data_type(dtype: &DataType) -> bool {
|
pub fn supported_fts_data_type(dtype: &DataType) -> bool {
|
||||||
matches!(dtype, DataType::Utf8 | DataType::LargeUtf8)
|
supported_fts_data_type_impl(dtype, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn supported_fts_data_type_impl(dtype: &DataType, in_list: bool) -> bool {
|
||||||
|
match (dtype, in_list) {
|
||||||
|
(DataType::Utf8 | DataType::LargeUtf8, _) => true,
|
||||||
|
(DataType::List(field) | DataType::LargeList(field), false) => {
|
||||||
|
supported_fts_data_type_impl(field.data_type(), true)
|
||||||
|
}
|
||||||
|
_ => false,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn supported_vector_data_type(dtype: &DataType) -> bool {
|
pub fn supported_vector_data_type(dtype: &DataType) -> bool {
|
||||||
@@ -178,11 +194,97 @@ pub fn string_to_datatype(s: &str) -> Option<DataType> {
|
|||||||
(&json_type).try_into().ok()
|
(&json_type).try_into().ok()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
enum TimeoutState {
|
||||||
|
NotStarted {
|
||||||
|
timeout: std::time::Duration,
|
||||||
|
},
|
||||||
|
Started {
|
||||||
|
deadline: Pin<Box<tokio::time::Sleep>>,
|
||||||
|
timeout: std::time::Duration,
|
||||||
|
},
|
||||||
|
Completed,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A `Stream` wrapper that implements a timeout.
|
||||||
|
///
|
||||||
|
/// The timeout starts when the first `poll_next` is called. As soon as the timeout
|
||||||
|
/// duration has passed, the stream will return an `Err` indicating a timeout error
|
||||||
|
/// for the next poll.
|
||||||
|
pub struct TimeoutStream {
|
||||||
|
inner: SendableRecordBatchStream,
|
||||||
|
state: TimeoutState,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TimeoutStream {
|
||||||
|
pub fn new(inner: SendableRecordBatchStream, timeout: std::time::Duration) -> Self {
|
||||||
|
Self {
|
||||||
|
inner,
|
||||||
|
state: TimeoutState::NotStarted { timeout },
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn new_boxed(
|
||||||
|
inner: SendableRecordBatchStream,
|
||||||
|
timeout: std::time::Duration,
|
||||||
|
) -> SendableRecordBatchStream {
|
||||||
|
Box::pin(Self::new(inner, timeout))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn timeout_error(timeout: &std::time::Duration) -> DataFusionError {
|
||||||
|
DataFusionError::Execution(format!("Query timeout after {} ms", timeout.as_millis()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl RecordBatchStream for TimeoutStream {
|
||||||
|
fn schema(&self) -> SchemaRef {
|
||||||
|
self.inner.schema()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Stream for TimeoutStream {
|
||||||
|
type Item = DataFusionResult<RecordBatch>;
|
||||||
|
|
||||||
|
fn poll_next(
|
||||||
|
mut self: std::pin::Pin<&mut Self>,
|
||||||
|
cx: &mut std::task::Context<'_>,
|
||||||
|
) -> std::task::Poll<Option<Self::Item>> {
|
||||||
|
match &mut self.state {
|
||||||
|
TimeoutState::NotStarted { timeout } => {
|
||||||
|
if timeout.is_zero() {
|
||||||
|
return std::task::Poll::Ready(Some(Err(Self::timeout_error(timeout))));
|
||||||
|
}
|
||||||
|
let deadline = Box::pin(tokio::time::sleep(*timeout));
|
||||||
|
self.state = TimeoutState::Started {
|
||||||
|
deadline,
|
||||||
|
timeout: *timeout,
|
||||||
|
};
|
||||||
|
self.poll_next(cx)
|
||||||
|
}
|
||||||
|
TimeoutState::Started { deadline, timeout } => match deadline.poll_unpin(cx) {
|
||||||
|
std::task::Poll::Ready(_) => {
|
||||||
|
let err = Self::timeout_error(timeout);
|
||||||
|
self.state = TimeoutState::Completed;
|
||||||
|
std::task::Poll::Ready(Some(Err(err)))
|
||||||
|
}
|
||||||
|
std::task::Poll::Pending => {
|
||||||
|
let inner = Pin::new(&mut self.inner);
|
||||||
|
inner.poll_next(cx)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
TimeoutState::Completed => std::task::Poll::Ready(None),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use arrow_array::Int32Array;
|
||||||
|
use arrow_schema::Field;
|
||||||
|
use datafusion_physical_plan::stream::RecordBatchStreamAdapter;
|
||||||
|
use futures::{stream, StreamExt};
|
||||||
|
use tokio::time::sleep;
|
||||||
|
|
||||||
use arrow_schema::{DataType, Field};
|
use super::*;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_guess_default_column() {
|
fn test_guess_default_column() {
|
||||||
@@ -249,4 +351,85 @@ mod tests {
|
|||||||
let expected = DataType::Int32;
|
let expected = DataType::Int32;
|
||||||
assert_eq!(string_to_datatype(string), Some(expected));
|
assert_eq!(string_to_datatype(string), Some(expected));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn sample_batch() -> RecordBatch {
|
||||||
|
let schema = Arc::new(Schema::new(vec![Field::new(
|
||||||
|
"col1",
|
||||||
|
DataType::Int32,
|
||||||
|
false,
|
||||||
|
)]));
|
||||||
|
RecordBatch::try_new(
|
||||||
|
schema.clone(),
|
||||||
|
vec![Arc::new(Int32Array::from(vec![1, 2, 3]))],
|
||||||
|
)
|
||||||
|
.unwrap()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_timeout_stream() {
|
||||||
|
let batch = sample_batch();
|
||||||
|
let schema = batch.schema();
|
||||||
|
let mock_stream = stream::iter(vec![Ok(batch.clone()), Ok(batch.clone())]);
|
||||||
|
|
||||||
|
let sendable_stream: SendableRecordBatchStream =
|
||||||
|
Box::pin(RecordBatchStreamAdapter::new(schema.clone(), mock_stream));
|
||||||
|
let timeout_duration = std::time::Duration::from_millis(10);
|
||||||
|
let mut timeout_stream = TimeoutStream::new(sendable_stream, timeout_duration);
|
||||||
|
|
||||||
|
// Poll the stream to get the first batch
|
||||||
|
let first_result = timeout_stream.next().await;
|
||||||
|
assert!(first_result.is_some());
|
||||||
|
assert!(first_result.unwrap().is_ok());
|
||||||
|
|
||||||
|
// Sleep for the timeout duration
|
||||||
|
sleep(timeout_duration).await;
|
||||||
|
|
||||||
|
// Poll the stream again and ensure it returns a timeout error
|
||||||
|
let second_result = timeout_stream.next().await.unwrap();
|
||||||
|
assert!(second_result.is_err());
|
||||||
|
assert!(second_result
|
||||||
|
.unwrap_err()
|
||||||
|
.to_string()
|
||||||
|
.contains("Query timeout"));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_timeout_stream_zero_duration() {
|
||||||
|
let batch = sample_batch();
|
||||||
|
let schema = batch.schema();
|
||||||
|
let mock_stream = stream::iter(vec![Ok(batch.clone()), Ok(batch.clone())]);
|
||||||
|
|
||||||
|
let sendable_stream: SendableRecordBatchStream =
|
||||||
|
Box::pin(RecordBatchStreamAdapter::new(schema.clone(), mock_stream));
|
||||||
|
|
||||||
|
// Setup similar to test_timeout_stream
|
||||||
|
let timeout_duration = std::time::Duration::from_secs(0);
|
||||||
|
let mut timeout_stream = TimeoutStream::new(sendable_stream, timeout_duration);
|
||||||
|
|
||||||
|
// First poll should immediately return a timeout error
|
||||||
|
let result = timeout_stream.next().await.unwrap();
|
||||||
|
assert!(result.is_err());
|
||||||
|
assert!(result.unwrap_err().to_string().contains("Query timeout"));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_timeout_stream_completes_normally() {
|
||||||
|
let batch = sample_batch();
|
||||||
|
let schema = batch.schema();
|
||||||
|
let mock_stream = stream::iter(vec![Ok(batch.clone()), Ok(batch.clone())]);
|
||||||
|
|
||||||
|
let sendable_stream: SendableRecordBatchStream =
|
||||||
|
Box::pin(RecordBatchStreamAdapter::new(schema.clone(), mock_stream));
|
||||||
|
|
||||||
|
// Setup a stream with 2 batches
|
||||||
|
// Use a longer timeout that won't trigger
|
||||||
|
let timeout_duration = std::time::Duration::from_secs(1);
|
||||||
|
let mut timeout_stream = TimeoutStream::new(sendable_stream, timeout_duration);
|
||||||
|
|
||||||
|
// Both polls should return data normally
|
||||||
|
assert!(timeout_stream.next().await.unwrap().is_ok());
|
||||||
|
assert!(timeout_stream.next().await.unwrap().is_ok());
|
||||||
|
// Stream should be empty now
|
||||||
|
assert!(timeout_stream.next().await.is_none());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user