mirror of
https://github.com/lancedb/lancedb.git
synced 2026-08-30 18:08:24 +00:00
Compare commits
22 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| a3e3c1ad5c | |||
| c6e43b545d | |||
| 5807ad464b | |||
| 513c2215c5 | |||
| 10879d99b8 | |||
| 4e6a1d5dce | |||
| 13d2759356 | |||
| 7f52ec8c36 | |||
| c6ae0de3ee | |||
| 231f0655ce | |||
| 8c52977c59 | |||
| 359710a0bf | |||
| 1f1726369d | |||
| df354abae4 | |||
| 11bc674548 | |||
| 5593460823 | |||
| 2807ad6854 | |||
| 4761fa9bcb | |||
| 4c2939d66e | |||
| a813ce2f71 | |||
| a898dc81c2 | |||
| de3f8097e7 |
+1
-1
@@ -1,5 +1,5 @@
|
|||||||
[tool.bumpversion]
|
[tool.bumpversion]
|
||||||
current_version = "0.28.0-beta.0"
|
current_version = "0.28.0-beta.5"
|
||||||
parse = """(?x)
|
parse = """(?x)
|
||||||
(?P<major>0|[1-9]\\d*)\\.
|
(?P<major>0|[1-9]\\d*)\\.
|
||||||
(?P<minor>0|[1-9]\\d*)\\.
|
(?P<minor>0|[1-9]\\d*)\\.
|
||||||
|
|||||||
@@ -18,6 +18,6 @@ body:
|
|||||||
label: Link
|
label: Link
|
||||||
description: >
|
description: >
|
||||||
Provide a link to the existing documentation, if applicable.
|
Provide a link to the existing documentation, if applicable.
|
||||||
placeholder: ex. https://lancedb.com/docs/tables/...
|
placeholder: ex. https://docs.lancedb.com/tables/...
|
||||||
validations:
|
validations:
|
||||||
required: false
|
required: false
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ on:
|
|||||||
paths:
|
paths:
|
||||||
- Cargo.toml
|
- Cargo.toml
|
||||||
- Cargo.lock
|
- Cargo.lock
|
||||||
|
- rust-toolchain.toml
|
||||||
- nodejs/**
|
- nodejs/**
|
||||||
- rust/**
|
- rust/**
|
||||||
- docs/src/js/**
|
- docs/src/js/**
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ on:
|
|||||||
paths:
|
paths:
|
||||||
- Cargo.toml
|
- Cargo.toml
|
||||||
- Cargo.lock
|
- Cargo.lock
|
||||||
|
- rust-toolchain.toml
|
||||||
- python/**
|
- python/**
|
||||||
- rust/**
|
- rust/**
|
||||||
- .github/workflows/python.yml
|
- .github/workflows/python.yml
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ on:
|
|||||||
paths:
|
paths:
|
||||||
- Cargo.toml
|
- Cargo.toml
|
||||||
- Cargo.lock
|
- Cargo.lock
|
||||||
|
- rust-toolchain.toml
|
||||||
- rust/**
|
- rust/**
|
||||||
- .github/workflows/rust.yml
|
- .github/workflows/rust.yml
|
||||||
|
|
||||||
|
|||||||
Generated
+38
-35
@@ -3072,8 +3072,8 @@ checksum = "42703706b716c37f96a77aea830392ad231f44c9e9a67872fa5548707e11b11c"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "fsst"
|
name = "fsst"
|
||||||
version = "5.0.0-beta.5"
|
version = "5.1.0-beta.3"
|
||||||
source = "git+https://github.com/lance-format/lance.git?tag=v5.0.0-beta.5#d630106da5a238b3adfb8c5dea3b3921f3519945"
|
source = "git+https://github.com/lance-format/lance.git?tag=v5.1.0-beta.3#0a0d53b5ef4b0f65bb775ce63bdcabc79d1d14b8"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"arrow-array",
|
"arrow-array",
|
||||||
"rand 0.9.2",
|
"rand 0.9.2",
|
||||||
@@ -4134,13 +4134,14 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "lance"
|
name = "lance"
|
||||||
version = "5.0.0-beta.5"
|
version = "5.1.0-beta.3"
|
||||||
source = "git+https://github.com/lance-format/lance.git?tag=v5.0.0-beta.5#d630106da5a238b3adfb8c5dea3b3921f3519945"
|
source = "git+https://github.com/lance-format/lance.git?tag=v5.1.0-beta.3#0a0d53b5ef4b0f65bb775ce63bdcabc79d1d14b8"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"arrow",
|
"arrow",
|
||||||
"arrow-arith",
|
"arrow-arith",
|
||||||
"arrow-array",
|
"arrow-array",
|
||||||
"arrow-buffer",
|
"arrow-buffer",
|
||||||
|
"arrow-cast",
|
||||||
"arrow-ipc",
|
"arrow-ipc",
|
||||||
"arrow-ord",
|
"arrow-ord",
|
||||||
"arrow-row",
|
"arrow-row",
|
||||||
@@ -4201,13 +4202,14 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "lance-arrow"
|
name = "lance-arrow"
|
||||||
version = "5.0.0-beta.5"
|
version = "5.1.0-beta.3"
|
||||||
source = "git+https://github.com/lance-format/lance.git?tag=v5.0.0-beta.5#d630106da5a238b3adfb8c5dea3b3921f3519945"
|
source = "git+https://github.com/lance-format/lance.git?tag=v5.1.0-beta.3#0a0d53b5ef4b0f65bb775ce63bdcabc79d1d14b8"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"arrow-array",
|
"arrow-array",
|
||||||
"arrow-buffer",
|
"arrow-buffer",
|
||||||
"arrow-cast",
|
"arrow-cast",
|
||||||
"arrow-data",
|
"arrow-data",
|
||||||
|
"arrow-ipc",
|
||||||
"arrow-ord",
|
"arrow-ord",
|
||||||
"arrow-schema",
|
"arrow-schema",
|
||||||
"arrow-select",
|
"arrow-select",
|
||||||
@@ -4222,8 +4224,8 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "lance-bitpacking"
|
name = "lance-bitpacking"
|
||||||
version = "5.0.0-beta.5"
|
version = "5.1.0-beta.3"
|
||||||
source = "git+https://github.com/lance-format/lance.git?tag=v5.0.0-beta.5#d630106da5a238b3adfb8c5dea3b3921f3519945"
|
source = "git+https://github.com/lance-format/lance.git?tag=v5.1.0-beta.3#0a0d53b5ef4b0f65bb775ce63bdcabc79d1d14b8"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"arrayref",
|
"arrayref",
|
||||||
"paste",
|
"paste",
|
||||||
@@ -4232,8 +4234,8 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "lance-core"
|
name = "lance-core"
|
||||||
version = "5.0.0-beta.5"
|
version = "5.1.0-beta.3"
|
||||||
source = "git+https://github.com/lance-format/lance.git?tag=v5.0.0-beta.5#d630106da5a238b3adfb8c5dea3b3921f3519945"
|
source = "git+https://github.com/lance-format/lance.git?tag=v5.1.0-beta.3#0a0d53b5ef4b0f65bb775ce63bdcabc79d1d14b8"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"arrow-array",
|
"arrow-array",
|
||||||
"arrow-buffer",
|
"arrow-buffer",
|
||||||
@@ -4270,12 +4272,13 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "lance-datafusion"
|
name = "lance-datafusion"
|
||||||
version = "5.0.0-beta.5"
|
version = "5.1.0-beta.3"
|
||||||
source = "git+https://github.com/lance-format/lance.git?tag=v5.0.0-beta.5#d630106da5a238b3adfb8c5dea3b3921f3519945"
|
source = "git+https://github.com/lance-format/lance.git?tag=v5.1.0-beta.3#0a0d53b5ef4b0f65bb775ce63bdcabc79d1d14b8"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"arrow",
|
"arrow",
|
||||||
"arrow-array",
|
"arrow-array",
|
||||||
"arrow-buffer",
|
"arrow-buffer",
|
||||||
|
"arrow-cast",
|
||||||
"arrow-ord",
|
"arrow-ord",
|
||||||
"arrow-schema",
|
"arrow-schema",
|
||||||
"arrow-select",
|
"arrow-select",
|
||||||
@@ -4301,8 +4304,8 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "lance-datagen"
|
name = "lance-datagen"
|
||||||
version = "5.0.0-beta.5"
|
version = "5.1.0-beta.3"
|
||||||
source = "git+https://github.com/lance-format/lance.git?tag=v5.0.0-beta.5#d630106da5a238b3adfb8c5dea3b3921f3519945"
|
source = "git+https://github.com/lance-format/lance.git?tag=v5.1.0-beta.3#0a0d53b5ef4b0f65bb775ce63bdcabc79d1d14b8"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"arrow",
|
"arrow",
|
||||||
"arrow-array",
|
"arrow-array",
|
||||||
@@ -4320,8 +4323,8 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "lance-encoding"
|
name = "lance-encoding"
|
||||||
version = "5.0.0-beta.5"
|
version = "5.1.0-beta.3"
|
||||||
source = "git+https://github.com/lance-format/lance.git?tag=v5.0.0-beta.5#d630106da5a238b3adfb8c5dea3b3921f3519945"
|
source = "git+https://github.com/lance-format/lance.git?tag=v5.1.0-beta.3#0a0d53b5ef4b0f65bb775ce63bdcabc79d1d14b8"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"arrow-arith",
|
"arrow-arith",
|
||||||
"arrow-array",
|
"arrow-array",
|
||||||
@@ -4358,8 +4361,8 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "lance-file"
|
name = "lance-file"
|
||||||
version = "5.0.0-beta.5"
|
version = "5.1.0-beta.3"
|
||||||
source = "git+https://github.com/lance-format/lance.git?tag=v5.0.0-beta.5#d630106da5a238b3adfb8c5dea3b3921f3519945"
|
source = "git+https://github.com/lance-format/lance.git?tag=v5.1.0-beta.3#0a0d53b5ef4b0f65bb775ce63bdcabc79d1d14b8"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"arrow-arith",
|
"arrow-arith",
|
||||||
"arrow-array",
|
"arrow-array",
|
||||||
@@ -4391,8 +4394,8 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "lance-index"
|
name = "lance-index"
|
||||||
version = "5.0.0-beta.5"
|
version = "5.1.0-beta.3"
|
||||||
source = "git+https://github.com/lance-format/lance.git?tag=v5.0.0-beta.5#d630106da5a238b3adfb8c5dea3b3921f3519945"
|
source = "git+https://github.com/lance-format/lance.git?tag=v5.1.0-beta.3#0a0d53b5ef4b0f65bb775ce63bdcabc79d1d14b8"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"arrow",
|
"arrow",
|
||||||
"arrow-arith",
|
"arrow-arith",
|
||||||
@@ -4456,8 +4459,8 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "lance-io"
|
name = "lance-io"
|
||||||
version = "5.0.0-beta.5"
|
version = "5.1.0-beta.3"
|
||||||
source = "git+https://github.com/lance-format/lance.git?tag=v5.0.0-beta.5#d630106da5a238b3adfb8c5dea3b3921f3519945"
|
source = "git+https://github.com/lance-format/lance.git?tag=v5.1.0-beta.3#0a0d53b5ef4b0f65bb775ce63bdcabc79d1d14b8"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"arrow",
|
"arrow",
|
||||||
"arrow-arith",
|
"arrow-arith",
|
||||||
@@ -4501,8 +4504,8 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "lance-linalg"
|
name = "lance-linalg"
|
||||||
version = "5.0.0-beta.5"
|
version = "5.1.0-beta.3"
|
||||||
source = "git+https://github.com/lance-format/lance.git?tag=v5.0.0-beta.5#d630106da5a238b3adfb8c5dea3b3921f3519945"
|
source = "git+https://github.com/lance-format/lance.git?tag=v5.1.0-beta.3#0a0d53b5ef4b0f65bb775ce63bdcabc79d1d14b8"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"arrow-array",
|
"arrow-array",
|
||||||
"arrow-buffer",
|
"arrow-buffer",
|
||||||
@@ -4518,8 +4521,8 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "lance-namespace"
|
name = "lance-namespace"
|
||||||
version = "5.0.0-beta.5"
|
version = "5.1.0-beta.3"
|
||||||
source = "git+https://github.com/lance-format/lance.git?tag=v5.0.0-beta.5#d630106da5a238b3adfb8c5dea3b3921f3519945"
|
source = "git+https://github.com/lance-format/lance.git?tag=v5.1.0-beta.3#0a0d53b5ef4b0f65bb775ce63bdcabc79d1d14b8"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"arrow",
|
"arrow",
|
||||||
"async-trait",
|
"async-trait",
|
||||||
@@ -4532,8 +4535,8 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "lance-namespace-impls"
|
name = "lance-namespace-impls"
|
||||||
version = "5.0.0-beta.5"
|
version = "5.1.0-beta.3"
|
||||||
source = "git+https://github.com/lance-format/lance.git?tag=v5.0.0-beta.5#d630106da5a238b3adfb8c5dea3b3921f3519945"
|
source = "git+https://github.com/lance-format/lance.git?tag=v5.1.0-beta.3#0a0d53b5ef4b0f65bb775ce63bdcabc79d1d14b8"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"arrow",
|
"arrow",
|
||||||
"arrow-ipc",
|
"arrow-ipc",
|
||||||
@@ -4578,8 +4581,8 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "lance-table"
|
name = "lance-table"
|
||||||
version = "5.0.0-beta.5"
|
version = "5.1.0-beta.3"
|
||||||
source = "git+https://github.com/lance-format/lance.git?tag=v5.0.0-beta.5#d630106da5a238b3adfb8c5dea3b3921f3519945"
|
source = "git+https://github.com/lance-format/lance.git?tag=v5.1.0-beta.3#0a0d53b5ef4b0f65bb775ce63bdcabc79d1d14b8"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"arrow",
|
"arrow",
|
||||||
"arrow-array",
|
"arrow-array",
|
||||||
@@ -4618,8 +4621,8 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "lance-testing"
|
name = "lance-testing"
|
||||||
version = "5.0.0-beta.5"
|
version = "5.1.0-beta.3"
|
||||||
source = "git+https://github.com/lance-format/lance.git?tag=v5.0.0-beta.5#d630106da5a238b3adfb8c5dea3b3921f3519945"
|
source = "git+https://github.com/lance-format/lance.git?tag=v5.1.0-beta.3#0a0d53b5ef4b0f65bb775ce63bdcabc79d1d14b8"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"arrow-array",
|
"arrow-array",
|
||||||
"arrow-schema",
|
"arrow-schema",
|
||||||
@@ -4630,7 +4633,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "lancedb"
|
name = "lancedb"
|
||||||
version = "0.28.0-beta.0"
|
version = "0.28.0-beta.5"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"ahash",
|
"ahash",
|
||||||
"anyhow",
|
"anyhow",
|
||||||
@@ -4712,7 +4715,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "lancedb-nodejs"
|
name = "lancedb-nodejs"
|
||||||
version = "0.28.0-beta.0"
|
version = "0.28.0-beta.5"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"arrow-array",
|
"arrow-array",
|
||||||
"arrow-buffer",
|
"arrow-buffer",
|
||||||
@@ -4734,7 +4737,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "lancedb-python"
|
name = "lancedb-python"
|
||||||
version = "0.31.0-beta.0"
|
version = "0.31.0-beta.5"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"arrow",
|
"arrow",
|
||||||
"async-trait",
|
"async-trait",
|
||||||
|
|||||||
+14
-14
@@ -15,20 +15,20 @@ categories = ["database-implementations"]
|
|||||||
rust-version = "1.91.0"
|
rust-version = "1.91.0"
|
||||||
|
|
||||||
[workspace.dependencies]
|
[workspace.dependencies]
|
||||||
lance = { "version" = "=5.0.0-beta.5", default-features = false, "tag" = "v5.0.0-beta.5", "git" = "https://github.com/lance-format/lance.git" }
|
lance = { "version" = "=5.1.0-beta.3", default-features = false, "tag" = "v5.1.0-beta.3", "git" = "https://github.com/lance-format/lance.git" }
|
||||||
lance-core = { "version" = "=5.0.0-beta.5", "tag" = "v5.0.0-beta.5", "git" = "https://github.com/lance-format/lance.git" }
|
lance-core = { "version" = "=5.1.0-beta.3", "tag" = "v5.1.0-beta.3", "git" = "https://github.com/lance-format/lance.git" }
|
||||||
lance-datagen = { "version" = "=5.0.0-beta.5", "tag" = "v5.0.0-beta.5", "git" = "https://github.com/lance-format/lance.git" }
|
lance-datagen = { "version" = "=5.1.0-beta.3", "tag" = "v5.1.0-beta.3", "git" = "https://github.com/lance-format/lance.git" }
|
||||||
lance-file = { "version" = "=5.0.0-beta.5", "tag" = "v5.0.0-beta.5", "git" = "https://github.com/lance-format/lance.git" }
|
lance-file = { "version" = "=5.1.0-beta.3", "tag" = "v5.1.0-beta.3", "git" = "https://github.com/lance-format/lance.git" }
|
||||||
lance-io = { "version" = "=5.0.0-beta.5", default-features = false, "tag" = "v5.0.0-beta.5", "git" = "https://github.com/lance-format/lance.git" }
|
lance-io = { "version" = "=5.1.0-beta.3", default-features = false, "tag" = "v5.1.0-beta.3", "git" = "https://github.com/lance-format/lance.git" }
|
||||||
lance-index = { "version" = "=5.0.0-beta.5", "tag" = "v5.0.0-beta.5", "git" = "https://github.com/lance-format/lance.git" }
|
lance-index = { "version" = "=5.1.0-beta.3", "tag" = "v5.1.0-beta.3", "git" = "https://github.com/lance-format/lance.git" }
|
||||||
lance-linalg = { "version" = "=5.0.0-beta.5", "tag" = "v5.0.0-beta.5", "git" = "https://github.com/lance-format/lance.git" }
|
lance-linalg = { "version" = "=5.1.0-beta.3", "tag" = "v5.1.0-beta.3", "git" = "https://github.com/lance-format/lance.git" }
|
||||||
lance-namespace = { "version" = "=5.0.0-beta.5", "tag" = "v5.0.0-beta.5", "git" = "https://github.com/lance-format/lance.git" }
|
lance-namespace = { "version" = "=5.1.0-beta.3", "tag" = "v5.1.0-beta.3", "git" = "https://github.com/lance-format/lance.git" }
|
||||||
lance-namespace-impls = { "version" = "=5.0.0-beta.5", default-features = false, "tag" = "v5.0.0-beta.5", "git" = "https://github.com/lance-format/lance.git" }
|
lance-namespace-impls = { "version" = "=5.1.0-beta.3", default-features = false, "tag" = "v5.1.0-beta.3", "git" = "https://github.com/lance-format/lance.git" }
|
||||||
lance-table = { "version" = "=5.0.0-beta.5", "tag" = "v5.0.0-beta.5", "git" = "https://github.com/lance-format/lance.git" }
|
lance-table = { "version" = "=5.1.0-beta.3", "tag" = "v5.1.0-beta.3", "git" = "https://github.com/lance-format/lance.git" }
|
||||||
lance-testing = { "version" = "=5.0.0-beta.5", "tag" = "v5.0.0-beta.5", "git" = "https://github.com/lance-format/lance.git" }
|
lance-testing = { "version" = "=5.1.0-beta.3", "tag" = "v5.1.0-beta.3", "git" = "https://github.com/lance-format/lance.git" }
|
||||||
lance-datafusion = { "version" = "=5.0.0-beta.5", "tag" = "v5.0.0-beta.5", "git" = "https://github.com/lance-format/lance.git" }
|
lance-datafusion = { "version" = "=5.1.0-beta.3", "tag" = "v5.1.0-beta.3", "git" = "https://github.com/lance-format/lance.git" }
|
||||||
lance-encoding = { "version" = "=5.0.0-beta.5", "tag" = "v5.0.0-beta.5", "git" = "https://github.com/lance-format/lance.git" }
|
lance-encoding = { "version" = "=5.1.0-beta.3", "tag" = "v5.1.0-beta.3", "git" = "https://github.com/lance-format/lance.git" }
|
||||||
lance-arrow = { "version" = "=5.0.0-beta.5", "tag" = "v5.0.0-beta.5", "git" = "https://github.com/lance-format/lance.git" }
|
lance-arrow = { "version" = "=5.1.0-beta.3", "tag" = "v5.1.0-beta.3", "git" = "https://github.com/lance-format/lance.git" }
|
||||||
ahash = "0.8"
|
ahash = "0.8"
|
||||||
# Note that this one does not include pyarrow
|
# Note that this one does not include pyarrow
|
||||||
arrow = { version = "57.2", optional = false }
|
arrow = { version = "57.2", optional = false }
|
||||||
|
|||||||
@@ -15,7 +15,7 @@
|
|||||||
|
|
||||||
# **The Multimodal AI Lakehouse**
|
# **The Multimodal AI Lakehouse**
|
||||||
|
|
||||||
[**How to Install** ](#how-to-install) ✦ [**Detailed Documentation**](https://lancedb.com/docs) ✦ [**Tutorials and Recipes**](https://github.com/lancedb/vectordb-recipes/tree/main) ✦ [**Contributors**](#contributors)
|
[**How to Install** ](#how-to-install) ✦ [**Detailed Documentation**](https://docs.lancedb.com) ✦ [**Tutorials and Recipes**](https://github.com/lancedb/vectordb-recipes/tree/main) ✦ [**Contributors**](#contributors)
|
||||||
|
|
||||||
**The ultimate multimodal data platform for AI/ML applications.**
|
**The ultimate multimodal data platform for AI/ML applications.**
|
||||||
|
|
||||||
@@ -57,7 +57,7 @@ LanceDB is a central location where developers can build, train and analyze thei
|
|||||||
|
|
||||||
## **How to Install**:
|
## **How to Install**:
|
||||||
|
|
||||||
Follow the [Quickstart](https://lancedb.com/docs/quickstart/) doc to set up LanceDB locally.
|
Follow the [Quickstart](https://docs.lancedb.com/quickstart) doc to set up LanceDB locally.
|
||||||
|
|
||||||
**API & SDK:** We also support Python, Typescript and Rust SDKs
|
**API & SDK:** We also support Python, Typescript and Rust SDKs
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
# LanceDB Documentation
|
# LanceDB Documentation
|
||||||
|
|
||||||
LanceDB docs are available at [lancedb.com/docs](https://lancedb.com/docs).
|
LanceDB docs are available at [docs.lancedb.com](https://docs.lancedb.com).
|
||||||
|
|
||||||
The SDK docs are built and deployed automatically by [Github Actions](../.github/workflows/docs.yml)
|
The SDK docs are 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
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ Add the following dependency to your `pom.xml`:
|
|||||||
<dependency>
|
<dependency>
|
||||||
<groupId>com.lancedb</groupId>
|
<groupId>com.lancedb</groupId>
|
||||||
<artifactId>lancedb-core</artifactId>
|
<artifactId>lancedb-core</artifactId>
|
||||||
<version>0.28.0-beta.0</version>
|
<version>0.28.0-beta.5</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|||||||
@@ -34,7 +34,7 @@ const results = await table.vectorSearch([0.1, 0.3]).limit(20).toArray();
|
|||||||
console.log(results);
|
console.log(results);
|
||||||
```
|
```
|
||||||
|
|
||||||
The [quickstart](https://lancedb.com/docs/quickstart/basic-usage/) contains more complete examples.
|
The [quickstart](https://docs.lancedb.com/quickstart/) contains more complete examples.
|
||||||
|
|
||||||
## Development
|
## Development
|
||||||
|
|
||||||
|
|||||||
@@ -53,3 +53,18 @@ optional tlsConfig: TlsConfig;
|
|||||||
```ts
|
```ts
|
||||||
optional userAgent: string;
|
optional userAgent: string;
|
||||||
```
|
```
|
||||||
|
|
||||||
|
***
|
||||||
|
|
||||||
|
### userId?
|
||||||
|
|
||||||
|
```ts
|
||||||
|
optional userId: string;
|
||||||
|
```
|
||||||
|
|
||||||
|
User identifier for tracking purposes.
|
||||||
|
|
||||||
|
This is sent as the `x-lancedb-user-id` header in requests to LanceDB Cloud/Enterprise.
|
||||||
|
It can be set directly, or via the `LANCEDB_USER_ID` environment variable.
|
||||||
|
Alternatively, set `LANCEDB_USER_ID_ENV_KEY` to specify another environment
|
||||||
|
variable that contains the user ID value.
|
||||||
|
|||||||
@@ -89,4 +89,4 @@ optional storageOptions: Record<string, string>;
|
|||||||
|
|
||||||
(For LanceDB OSS only): configuration for object storage.
|
(For LanceDB OSS only): configuration for object storage.
|
||||||
|
|
||||||
The available options are described at https://lancedb.com/docs/storage/
|
The available options are described at https://docs.lancedb.com/storage/
|
||||||
|
|||||||
@@ -97,4 +97,4 @@ Configuration for object storage.
|
|||||||
Options already set on the connection will be inherited by the table,
|
Options already set on the connection will be inherited by the table,
|
||||||
but can be overridden here.
|
but can be overridden here.
|
||||||
|
|
||||||
The available options are described at https://lancedb.com/docs/storage/
|
The available options are described at https://docs.lancedb.com/storage/
|
||||||
|
|||||||
@@ -42,4 +42,4 @@ Configuration for object storage.
|
|||||||
Options already set on the connection will be inherited by the table,
|
Options already set on the connection will be inherited by the table,
|
||||||
but can be overridden here.
|
but can be overridden here.
|
||||||
|
|
||||||
The available options are described at https://lancedb.com/docs/storage/
|
The available options are described at https://docs.lancedb.com/storage/
|
||||||
|
|||||||
@@ -8,7 +8,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<groupId>com.lancedb</groupId>
|
<groupId>com.lancedb</groupId>
|
||||||
<artifactId>lancedb-parent</artifactId>
|
<artifactId>lancedb-parent</artifactId>
|
||||||
<version>0.28.0-beta.0</version>
|
<version>0.28.0-beta.5</version>
|
||||||
<relativePath>../pom.xml</relativePath>
|
<relativePath>../pom.xml</relativePath>
|
||||||
</parent>
|
</parent>
|
||||||
|
|
||||||
|
|||||||
+2
-2
@@ -6,7 +6,7 @@
|
|||||||
|
|
||||||
<groupId>com.lancedb</groupId>
|
<groupId>com.lancedb</groupId>
|
||||||
<artifactId>lancedb-parent</artifactId>
|
<artifactId>lancedb-parent</artifactId>
|
||||||
<version>0.28.0-beta.0</version>
|
<version>0.28.0-beta.5</version>
|
||||||
<packaging>pom</packaging>
|
<packaging>pom</packaging>
|
||||||
<name>${project.artifactId}</name>
|
<name>${project.artifactId}</name>
|
||||||
<description>LanceDB Java SDK Parent POM</description>
|
<description>LanceDB Java SDK Parent POM</description>
|
||||||
@@ -28,7 +28,7 @@
|
|||||||
<properties>
|
<properties>
|
||||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||||
<arrow.version>15.0.0</arrow.version>
|
<arrow.version>15.0.0</arrow.version>
|
||||||
<lance-core.version>5.0.0-beta.5</lance-core.version>
|
<lance-core.version>5.1.0-beta.3</lance-core.version>
|
||||||
<spotless.skip>false</spotless.skip>
|
<spotless.skip>false</spotless.skip>
|
||||||
<spotless.version>2.30.0</spotless.version>
|
<spotless.version>2.30.0</spotless.version>
|
||||||
<spotless.java.googlejavaformat.version>1.7</spotless.java.googlejavaformat.version>
|
<spotless.java.googlejavaformat.version>1.7</spotless.java.googlejavaformat.version>
|
||||||
|
|||||||
+1
-1
@@ -1,7 +1,7 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "lancedb-nodejs"
|
name = "lancedb-nodejs"
|
||||||
edition.workspace = true
|
edition.workspace = true
|
||||||
version = "0.28.0-beta.0"
|
version = "0.28.0-beta.5"
|
||||||
license.workspace = true
|
license.workspace = true
|
||||||
description.workspace = true
|
description.workspace = true
|
||||||
repository.workspace = true
|
repository.workspace = true
|
||||||
|
|||||||
+1
-1
@@ -30,7 +30,7 @@ const results = await table.vectorSearch([0.1, 0.3]).limit(20).toArray();
|
|||||||
console.log(results);
|
console.log(results);
|
||||||
```
|
```
|
||||||
|
|
||||||
The [quickstart](https://lancedb.com/docs/quickstart/basic-usage/) contains more complete examples.
|
The [quickstart](https://docs.lancedb.com/quickstart/) contains more complete examples.
|
||||||
|
|
||||||
## Development
|
## Development
|
||||||
|
|
||||||
|
|||||||
@@ -42,7 +42,7 @@ export interface CreateTableOptions {
|
|||||||
* Options already set on the connection will be inherited by the table,
|
* Options already set on the connection will be inherited by the table,
|
||||||
* but can be overridden here.
|
* but can be overridden here.
|
||||||
*
|
*
|
||||||
* The available options are described at https://lancedb.com/docs/storage/
|
* The available options are described at https://docs.lancedb.com/storage/
|
||||||
*/
|
*/
|
||||||
storageOptions?: Record<string, string>;
|
storageOptions?: Record<string, string>;
|
||||||
|
|
||||||
@@ -78,7 +78,7 @@ export interface OpenTableOptions {
|
|||||||
* Options already set on the connection will be inherited by the table,
|
* Options already set on the connection will be inherited by the table,
|
||||||
* but can be overridden here.
|
* but can be overridden here.
|
||||||
*
|
*
|
||||||
* The available options are described at https://lancedb.com/docs/storage/
|
* The available options are described at https://docs.lancedb.com/storage/
|
||||||
*/
|
*/
|
||||||
storageOptions?: Record<string, string>;
|
storageOptions?: Record<string, string>;
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@lancedb/lancedb-darwin-arm64",
|
"name": "@lancedb/lancedb-darwin-arm64",
|
||||||
"version": "0.28.0-beta.0",
|
"version": "0.28.0-beta.5",
|
||||||
"os": ["darwin"],
|
"os": ["darwin"],
|
||||||
"cpu": ["arm64"],
|
"cpu": ["arm64"],
|
||||||
"main": "lancedb.darwin-arm64.node",
|
"main": "lancedb.darwin-arm64.node",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@lancedb/lancedb-linux-arm64-gnu",
|
"name": "@lancedb/lancedb-linux-arm64-gnu",
|
||||||
"version": "0.28.0-beta.0",
|
"version": "0.28.0-beta.5",
|
||||||
"os": ["linux"],
|
"os": ["linux"],
|
||||||
"cpu": ["arm64"],
|
"cpu": ["arm64"],
|
||||||
"main": "lancedb.linux-arm64-gnu.node",
|
"main": "lancedb.linux-arm64-gnu.node",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@lancedb/lancedb-linux-arm64-musl",
|
"name": "@lancedb/lancedb-linux-arm64-musl",
|
||||||
"version": "0.28.0-beta.0",
|
"version": "0.28.0-beta.5",
|
||||||
"os": ["linux"],
|
"os": ["linux"],
|
||||||
"cpu": ["arm64"],
|
"cpu": ["arm64"],
|
||||||
"main": "lancedb.linux-arm64-musl.node",
|
"main": "lancedb.linux-arm64-musl.node",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@lancedb/lancedb-linux-x64-gnu",
|
"name": "@lancedb/lancedb-linux-x64-gnu",
|
||||||
"version": "0.28.0-beta.0",
|
"version": "0.28.0-beta.5",
|
||||||
"os": ["linux"],
|
"os": ["linux"],
|
||||||
"cpu": ["x64"],
|
"cpu": ["x64"],
|
||||||
"main": "lancedb.linux-x64-gnu.node",
|
"main": "lancedb.linux-x64-gnu.node",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@lancedb/lancedb-linux-x64-musl",
|
"name": "@lancedb/lancedb-linux-x64-musl",
|
||||||
"version": "0.28.0-beta.0",
|
"version": "0.28.0-beta.5",
|
||||||
"os": ["linux"],
|
"os": ["linux"],
|
||||||
"cpu": ["x64"],
|
"cpu": ["x64"],
|
||||||
"main": "lancedb.linux-x64-musl.node",
|
"main": "lancedb.linux-x64-musl.node",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@lancedb/lancedb-win32-arm64-msvc",
|
"name": "@lancedb/lancedb-win32-arm64-msvc",
|
||||||
"version": "0.28.0-beta.0",
|
"version": "0.28.0-beta.5",
|
||||||
"os": [
|
"os": [
|
||||||
"win32"
|
"win32"
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@lancedb/lancedb-win32-x64-msvc",
|
"name": "@lancedb/lancedb-win32-x64-msvc",
|
||||||
"version": "0.28.0-beta.0",
|
"version": "0.28.0-beta.5",
|
||||||
"os": ["win32"],
|
"os": ["win32"],
|
||||||
"cpu": ["x64"],
|
"cpu": ["x64"],
|
||||||
"main": "lancedb.win32-x64-msvc.node",
|
"main": "lancedb.win32-x64-msvc.node",
|
||||||
|
|||||||
Generated
+2
-2
@@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "@lancedb/lancedb",
|
"name": "@lancedb/lancedb",
|
||||||
"version": "0.28.0-beta.0",
|
"version": "0.28.0-beta.5",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "@lancedb/lancedb",
|
"name": "@lancedb/lancedb",
|
||||||
"version": "0.28.0-beta.0",
|
"version": "0.28.0-beta.5",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"x64",
|
"x64",
|
||||||
"arm64"
|
"arm64"
|
||||||
|
|||||||
+1
-1
@@ -11,7 +11,7 @@
|
|||||||
"ann"
|
"ann"
|
||||||
],
|
],
|
||||||
"private": false,
|
"private": false,
|
||||||
"version": "0.28.0-beta.0",
|
"version": "0.28.0-beta.5",
|
||||||
"main": "dist/index.js",
|
"main": "dist/index.js",
|
||||||
"exports": {
|
"exports": {
|
||||||
".": "./dist/index.js",
|
".": "./dist/index.js",
|
||||||
|
|||||||
+1
-1
@@ -35,7 +35,7 @@ pub struct ConnectionOptions {
|
|||||||
pub read_consistency_interval: Option<f64>,
|
pub read_consistency_interval: Option<f64>,
|
||||||
/// (For LanceDB OSS only): configuration for object storage.
|
/// (For LanceDB OSS only): configuration for object storage.
|
||||||
///
|
///
|
||||||
/// The available options are described at https://lancedb.com/docs/storage/
|
/// The available options are described at https://docs.lancedb.com/storage/
|
||||||
pub storage_options: Option<HashMap<String, String>>,
|
pub storage_options: Option<HashMap<String, String>>,
|
||||||
/// (For LanceDB OSS only): the session to use for this connection. Holds
|
/// (For LanceDB OSS only): the session to use for this connection. Holds
|
||||||
/// shared caches and other session-specific state.
|
/// shared caches and other session-specific state.
|
||||||
|
|||||||
@@ -92,6 +92,13 @@ pub struct ClientConfig {
|
|||||||
pub extra_headers: Option<HashMap<String, String>>,
|
pub extra_headers: Option<HashMap<String, String>>,
|
||||||
pub id_delimiter: Option<String>,
|
pub id_delimiter: Option<String>,
|
||||||
pub tls_config: Option<TlsConfig>,
|
pub tls_config: Option<TlsConfig>,
|
||||||
|
/// User identifier for tracking purposes.
|
||||||
|
///
|
||||||
|
/// This is sent as the `x-lancedb-user-id` header in requests to LanceDB Cloud/Enterprise.
|
||||||
|
/// It can be set directly, or via the `LANCEDB_USER_ID` environment variable.
|
||||||
|
/// Alternatively, set `LANCEDB_USER_ID_ENV_KEY` to specify another environment
|
||||||
|
/// variable that contains the user ID value.
|
||||||
|
pub user_id: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<TimeoutConfig> for lancedb::remote::TimeoutConfig {
|
impl From<TimeoutConfig> for lancedb::remote::TimeoutConfig {
|
||||||
@@ -145,6 +152,7 @@ impl From<ClientConfig> for lancedb::remote::ClientConfig {
|
|||||||
id_delimiter: config.id_delimiter,
|
id_delimiter: config.id_delimiter,
|
||||||
tls_config: config.tls_config.map(Into::into),
|
tls_config: config.tls_config.map(Into::into),
|
||||||
header_provider: None, // the header provider is set separately later
|
header_provider: None, // the header provider is set separately later
|
||||||
|
user_id: config.user_id,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
[tool.bumpversion]
|
[tool.bumpversion]
|
||||||
current_version = "0.31.0-beta.1"
|
current_version = "0.31.0-beta.5"
|
||||||
parse = """(?x)
|
parse = """(?x)
|
||||||
(?P<major>0|[1-9]\\d*)\\.
|
(?P<major>0|[1-9]\\d*)\\.
|
||||||
(?P<minor>0|[1-9]\\d*)\\.
|
(?P<minor>0|[1-9]\\d*)\\.
|
||||||
|
|||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "lancedb-python"
|
name = "lancedb-python"
|
||||||
version = "0.31.0-beta.1"
|
version = "0.31.0-beta.5"
|
||||||
edition.workspace = true
|
edition.workspace = true
|
||||||
description = "Python bindings for LanceDB"
|
description = "Python bindings for LanceDB"
|
||||||
license.workspace = true
|
license.workspace = true
|
||||||
|
|||||||
@@ -83,7 +83,7 @@ embeddings = [
|
|||||||
"colpali-engine>=0.3.10",
|
"colpali-engine>=0.3.10",
|
||||||
"huggingface_hub>=0.19.0",
|
"huggingface_hub>=0.19.0",
|
||||||
"InstructorEmbedding>=1.0.1",
|
"InstructorEmbedding>=1.0.1",
|
||||||
"google.generativeai>=0.3.0",
|
"google-genai>=1.0.0",
|
||||||
"boto3>=1.28.57",
|
"boto3>=1.28.57",
|
||||||
"awscli>=1.44.38",
|
"awscli>=1.44.38",
|
||||||
"botocore>=1.31.57",
|
"botocore>=1.31.57",
|
||||||
|
|||||||
@@ -110,7 +110,7 @@ def connect(
|
|||||||
default configuration is used.
|
default configuration is used.
|
||||||
storage_options: dict, optional
|
storage_options: dict, optional
|
||||||
Additional options for the storage backend. See available options at
|
Additional options for the storage backend. See available options at
|
||||||
<https://lancedb.com/docs/storage/>
|
<https://docs.lancedb.com/storage/>
|
||||||
session: Session, optional
|
session: Session, optional
|
||||||
(For LanceDB OSS only)
|
(For LanceDB OSS only)
|
||||||
A session to use for this connection. Sessions allow you to configure
|
A session to use for this connection. Sessions allow you to configure
|
||||||
@@ -215,6 +215,85 @@ def connect(
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
WORKER_PROPERTY_PREFIX = "_lancedb_worker_"
|
||||||
|
|
||||||
|
|
||||||
|
def _apply_worker_overrides(props: dict[str, str]) -> dict[str, str]:
|
||||||
|
"""Apply worker property overrides.
|
||||||
|
|
||||||
|
Any key starting with ``_lancedb_worker_`` is extracted, the prefix
|
||||||
|
is stripped, and the resulting key-value pair is put back into the
|
||||||
|
map (overriding the existing value if present). The original
|
||||||
|
prefixed key is removed.
|
||||||
|
"""
|
||||||
|
worker_keys = [k for k in props if k.startswith(WORKER_PROPERTY_PREFIX)]
|
||||||
|
if not worker_keys:
|
||||||
|
return props
|
||||||
|
result = dict(props)
|
||||||
|
for key in worker_keys:
|
||||||
|
value = result.pop(key)
|
||||||
|
real_key = key[len(WORKER_PROPERTY_PREFIX) :]
|
||||||
|
result[real_key] = value
|
||||||
|
return result
|
||||||
|
|
||||||
|
|
||||||
|
def deserialize_conn(
|
||||||
|
data: str,
|
||||||
|
*,
|
||||||
|
for_worker: bool = False,
|
||||||
|
) -> DBConnection:
|
||||||
|
"""Reconstruct a DBConnection from a serialized string.
|
||||||
|
|
||||||
|
The string must have been produced by
|
||||||
|
:meth:`DBConnection.serialize`.
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
data : str
|
||||||
|
String produced by ``serialize()``.
|
||||||
|
for_worker : bool, default False
|
||||||
|
When ``True``, any namespace client property whose key starts
|
||||||
|
with ``_lancedb_worker_`` has that prefix stripped and the
|
||||||
|
value overrides the corresponding property. For example,
|
||||||
|
``_lancedb_worker_uri`` replaces ``uri``.
|
||||||
|
|
||||||
|
Returns
|
||||||
|
-------
|
||||||
|
DBConnection
|
||||||
|
A new connection matching the serialized state.
|
||||||
|
"""
|
||||||
|
import json
|
||||||
|
|
||||||
|
parsed = json.loads(data)
|
||||||
|
connection_type = parsed.get("connection_type")
|
||||||
|
|
||||||
|
rci_secs = parsed.get("read_consistency_interval_seconds")
|
||||||
|
rci = timedelta(seconds=rci_secs) if rci_secs is not None else None
|
||||||
|
storage_options = parsed.get("storage_options")
|
||||||
|
|
||||||
|
if connection_type == "namespace":
|
||||||
|
props = dict(parsed.get("namespace_client_properties") or {})
|
||||||
|
if for_worker:
|
||||||
|
props = _apply_worker_overrides(props)
|
||||||
|
return connect_namespace(
|
||||||
|
namespace_client_impl=parsed["namespace_client_impl"],
|
||||||
|
namespace_client_properties=props,
|
||||||
|
read_consistency_interval=rci,
|
||||||
|
storage_options=storage_options,
|
||||||
|
namespace_client_pushdown_operations=parsed.get(
|
||||||
|
"namespace_client_pushdown_operations"
|
||||||
|
),
|
||||||
|
)
|
||||||
|
elif connection_type == "local":
|
||||||
|
return LanceDBConnection(
|
||||||
|
parsed["uri"],
|
||||||
|
read_consistency_interval=rci,
|
||||||
|
storage_options=storage_options,
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
raise ValueError(f"Unknown connection_type: {connection_type}")
|
||||||
|
|
||||||
|
|
||||||
async def connect_async(
|
async def connect_async(
|
||||||
uri: URI,
|
uri: URI,
|
||||||
*,
|
*,
|
||||||
@@ -257,7 +336,7 @@ async def connect_async(
|
|||||||
default configuration is used.
|
default configuration is used.
|
||||||
storage_options: dict, optional
|
storage_options: dict, optional
|
||||||
Additional options for the storage backend. See available options at
|
Additional options for the storage backend. See available options at
|
||||||
<https://lancedb.com/docs/storage/>
|
<https://docs.lancedb.com/storage/>
|
||||||
session: Session, optional
|
session: Session, optional
|
||||||
(For LanceDB OSS only)
|
(For LanceDB OSS only)
|
||||||
A session to use for this connection. Sessions allow you to configure
|
A session to use for this connection. Sessions allow you to configure
|
||||||
|
|||||||
@@ -96,7 +96,7 @@ def data_to_reader(
|
|||||||
f"Unknown data type {type(data)}. "
|
f"Unknown data type {type(data)}. "
|
||||||
"Supported types: list of dicts, pandas DataFrame, polars DataFrame, "
|
"Supported types: list of dicts, pandas DataFrame, polars DataFrame, "
|
||||||
"pyarrow Table/RecordBatch, or Pydantic models. "
|
"pyarrow Table/RecordBatch, or Pydantic models. "
|
||||||
"See https://lancedb.com/docs/tables/ for examples."
|
"See https://docs.lancedb.com/tables/ for examples."
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
+103
-66
@@ -282,7 +282,7 @@ class DBConnection(EnforceOverrides):
|
|||||||
Additional options for the storage backend. Options already set on the
|
Additional options for the storage backend. Options already set on the
|
||||||
connection will be inherited by the table, but can be overridden here.
|
connection will be inherited by the table, but can be overridden here.
|
||||||
See available options at
|
See available options at
|
||||||
<https://lancedb.com/docs/storage/>
|
<https://docs.lancedb.com/storage/>
|
||||||
|
|
||||||
To enable stable row IDs (row IDs remain stable after compaction,
|
To enable stable row IDs (row IDs remain stable after compaction,
|
||||||
update, delete, and merges), set `new_table_enable_stable_row_ids`
|
update, delete, and merges), set `new_table_enable_stable_row_ids`
|
||||||
@@ -433,7 +433,7 @@ class DBConnection(EnforceOverrides):
|
|||||||
Additional options for the storage backend. Options already set on the
|
Additional options for the storage backend. Options already set on the
|
||||||
connection will be inherited by the table, but can be overridden here.
|
connection will be inherited by the table, but can be overridden here.
|
||||||
See available options at
|
See available options at
|
||||||
<https://lancedb.com/docs/storage/>
|
<https://docs.lancedb.com/storage/>
|
||||||
|
|
||||||
Returns
|
Returns
|
||||||
-------
|
-------
|
||||||
@@ -529,6 +529,19 @@ class DBConnection(EnforceOverrides):
|
|||||||
"namespace_client is not supported for this connection type"
|
"namespace_client is not supported for this connection type"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def serialize(self) -> str:
|
||||||
|
"""Serialize this connection for reconstruction.
|
||||||
|
|
||||||
|
The returned string can be passed to :func:`lancedb.deserialize_conn`
|
||||||
|
to recreate an equivalent connection, e.g. in a remote worker.
|
||||||
|
|
||||||
|
Returns
|
||||||
|
-------
|
||||||
|
str
|
||||||
|
Serialized representation of this connection.
|
||||||
|
"""
|
||||||
|
raise NotImplementedError("serialize is not supported for this connection type")
|
||||||
|
|
||||||
|
|
||||||
class LanceDBConnection(DBConnection):
|
class LanceDBConnection(DBConnection):
|
||||||
"""
|
"""
|
||||||
@@ -581,6 +594,7 @@ class LanceDBConnection(DBConnection):
|
|||||||
):
|
):
|
||||||
if _inner is not None:
|
if _inner is not None:
|
||||||
self._conn = _inner
|
self._conn = _inner
|
||||||
|
self._cached_namespace_client = None
|
||||||
return
|
return
|
||||||
|
|
||||||
if not isinstance(uri, Path):
|
if not isinstance(uri, Path):
|
||||||
@@ -628,6 +642,7 @@ class LanceDBConnection(DBConnection):
|
|||||||
# beyond _conn.
|
# beyond _conn.
|
||||||
self.storage_options = storage_options
|
self.storage_options = storage_options
|
||||||
self._conn = AsyncConnection(LOOP.run(do_connect()))
|
self._conn = AsyncConnection(LOOP.run(do_connect()))
|
||||||
|
self._cached_namespace_client: Optional[LanceNamespace] = None
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def read_consistency_interval(self) -> Optional[timedelta]:
|
def read_consistency_interval(self) -> Optional[timedelta]:
|
||||||
@@ -652,6 +667,22 @@ class LanceDBConnection(DBConnection):
|
|||||||
val += ")"
|
val += ")"
|
||||||
return val
|
return val
|
||||||
|
|
||||||
|
@override
|
||||||
|
def serialize(self) -> str:
|
||||||
|
import json
|
||||||
|
|
||||||
|
rci = self.read_consistency_interval
|
||||||
|
return json.dumps(
|
||||||
|
{
|
||||||
|
"connection_type": "local",
|
||||||
|
"uri": self.uri,
|
||||||
|
"storage_options": self.storage_options,
|
||||||
|
"read_consistency_interval_seconds": (
|
||||||
|
rci.total_seconds() if rci else None
|
||||||
|
),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
async def _async_get_table_names(self, start_after: Optional[str], limit: int):
|
async def _async_get_table_names(self, start_after: Optional[str], limit: int):
|
||||||
conn = AsyncConnection(await lancedb_connect(self.uri))
|
conn = AsyncConnection(await lancedb_connect(self.uri))
|
||||||
return await conn.table_names(start_after=start_after, limit=limit)
|
return await conn.table_names(start_after=start_after, limit=limit)
|
||||||
@@ -687,10 +718,10 @@ class LanceDBConnection(DBConnection):
|
|||||||
"""
|
"""
|
||||||
if namespace_path is None:
|
if namespace_path is None:
|
||||||
namespace_path = []
|
namespace_path = []
|
||||||
return LOOP.run(
|
return self._namespace_conn().list_namespaces(
|
||||||
self._conn.list_namespaces(
|
namespace_path=namespace_path,
|
||||||
namespace_path=namespace_path, page_token=page_token, limit=limit
|
page_token=page_token,
|
||||||
)
|
limit=limit,
|
||||||
)
|
)
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@@ -700,27 +731,10 @@ class LanceDBConnection(DBConnection):
|
|||||||
mode: Optional[str] = None,
|
mode: Optional[str] = None,
|
||||||
properties: Optional[Dict[str, str]] = None,
|
properties: Optional[Dict[str, str]] = None,
|
||||||
) -> CreateNamespaceResponse:
|
) -> CreateNamespaceResponse:
|
||||||
"""Create a new namespace.
|
return self._namespace_conn().create_namespace(
|
||||||
|
namespace_path=namespace_path,
|
||||||
Parameters
|
mode=mode,
|
||||||
----------
|
properties=properties,
|
||||||
namespace_path: List[str]
|
|
||||||
The namespace identifier to create.
|
|
||||||
mode: str, optional
|
|
||||||
Creation mode - "create" (fail if exists), "exist_ok" (skip if exists),
|
|
||||||
or "overwrite" (replace if exists). Case insensitive.
|
|
||||||
properties: Dict[str, str], optional
|
|
||||||
Properties to set on the namespace.
|
|
||||||
|
|
||||||
Returns
|
|
||||||
-------
|
|
||||||
CreateNamespaceResponse
|
|
||||||
Response containing the properties of the created namespace.
|
|
||||||
"""
|
|
||||||
return LOOP.run(
|
|
||||||
self._conn.create_namespace(
|
|
||||||
namespace_path=namespace_path, mode=mode, properties=properties
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@@ -730,46 +744,19 @@ class LanceDBConnection(DBConnection):
|
|||||||
mode: Optional[str] = None,
|
mode: Optional[str] = None,
|
||||||
behavior: Optional[str] = None,
|
behavior: Optional[str] = None,
|
||||||
) -> DropNamespaceResponse:
|
) -> DropNamespaceResponse:
|
||||||
"""Drop a namespace.
|
return self._namespace_conn().drop_namespace(
|
||||||
|
namespace_path=namespace_path,
|
||||||
Parameters
|
mode=mode,
|
||||||
----------
|
behavior=behavior,
|
||||||
namespace_path: List[str]
|
|
||||||
The namespace identifier to drop.
|
|
||||||
mode: str, optional
|
|
||||||
Whether to skip if not exists ("SKIP") or fail ("FAIL"). Case insensitive.
|
|
||||||
behavior: str, optional
|
|
||||||
Whether to restrict drop if not empty ("RESTRICT") or cascade ("CASCADE").
|
|
||||||
Case insensitive.
|
|
||||||
|
|
||||||
Returns
|
|
||||||
-------
|
|
||||||
DropNamespaceResponse
|
|
||||||
Response containing properties and transaction_id if applicable.
|
|
||||||
"""
|
|
||||||
return LOOP.run(
|
|
||||||
self._conn.drop_namespace(
|
|
||||||
namespace_path=namespace_path, mode=mode, behavior=behavior
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
|
|
||||||
@override
|
@override
|
||||||
def describe_namespace(
|
def describe_namespace(
|
||||||
self, namespace_path: List[str]
|
self, namespace_path: List[str]
|
||||||
) -> DescribeNamespaceResponse:
|
) -> DescribeNamespaceResponse:
|
||||||
"""Describe a namespace.
|
return self._namespace_conn().describe_namespace(
|
||||||
|
namespace_path=namespace_path,
|
||||||
Parameters
|
)
|
||||||
----------
|
|
||||||
namespace_path: List[str]
|
|
||||||
The namespace identifier to describe.
|
|
||||||
|
|
||||||
Returns
|
|
||||||
-------
|
|
||||||
DescribeNamespaceResponse
|
|
||||||
Response containing the namespace properties.
|
|
||||||
"""
|
|
||||||
return LOOP.run(self._conn.describe_namespace(namespace_path=namespace_path))
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
def list_tables(
|
def list_tables(
|
||||||
@@ -798,6 +785,12 @@ class LanceDBConnection(DBConnection):
|
|||||||
"""
|
"""
|
||||||
if namespace_path is None:
|
if namespace_path is None:
|
||||||
namespace_path = []
|
namespace_path = []
|
||||||
|
if namespace_path:
|
||||||
|
return self._namespace_conn().list_tables(
|
||||||
|
namespace_path=namespace_path,
|
||||||
|
page_token=page_token,
|
||||||
|
limit=limit,
|
||||||
|
)
|
||||||
return LOOP.run(
|
return LOOP.run(
|
||||||
self._conn.list_tables(
|
self._conn.list_tables(
|
||||||
namespace_path=namespace_path, page_token=page_token, limit=limit
|
namespace_path=namespace_path, page_token=page_token, limit=limit
|
||||||
@@ -886,6 +879,22 @@ class LanceDBConnection(DBConnection):
|
|||||||
raise ValueError("mode must be either 'create' or 'overwrite'")
|
raise ValueError("mode must be either 'create' or 'overwrite'")
|
||||||
validate_table_name(name)
|
validate_table_name(name)
|
||||||
|
|
||||||
|
if namespace_path:
|
||||||
|
return self._namespace_conn().create_table(
|
||||||
|
name,
|
||||||
|
data=data,
|
||||||
|
schema=schema,
|
||||||
|
mode=mode,
|
||||||
|
exist_ok=exist_ok,
|
||||||
|
on_bad_vectors=on_bad_vectors,
|
||||||
|
fill_value=fill_value,
|
||||||
|
embedding_functions=embedding_functions,
|
||||||
|
namespace_path=namespace_path,
|
||||||
|
storage_options=storage_options,
|
||||||
|
data_storage_version=data_storage_version,
|
||||||
|
enable_v2_manifest_paths=enable_v2_manifest_paths,
|
||||||
|
)
|
||||||
|
|
||||||
tbl = LanceTable.create(
|
tbl = LanceTable.create(
|
||||||
self,
|
self,
|
||||||
name,
|
name,
|
||||||
@@ -901,6 +910,19 @@ class LanceDBConnection(DBConnection):
|
|||||||
)
|
)
|
||||||
return tbl
|
return tbl
|
||||||
|
|
||||||
|
def _namespace_conn(self) -> DBConnection:
|
||||||
|
"""Return a LanceNamespaceDBConnection backed by this connection's
|
||||||
|
directory namespace. Used to delegate child-namespace operations."""
|
||||||
|
from lancedb.namespace import LanceNamespaceDBConnection
|
||||||
|
|
||||||
|
return LanceNamespaceDBConnection(
|
||||||
|
self.namespace_client(),
|
||||||
|
read_consistency_interval=self.read_consistency_interval,
|
||||||
|
storage_options=self.storage_options,
|
||||||
|
namespace_client_impl=None,
|
||||||
|
namespace_client_properties=None,
|
||||||
|
)
|
||||||
|
|
||||||
@override
|
@override
|
||||||
def open_table(
|
def open_table(
|
||||||
self,
|
self,
|
||||||
@@ -917,7 +939,8 @@ class LanceDBConnection(DBConnection):
|
|||||||
name: str
|
name: str
|
||||||
The name of the table.
|
The name of the table.
|
||||||
namespace_path: List[str], optional
|
namespace_path: List[str], optional
|
||||||
The namespace to open the table from.
|
The namespace to open the table from. When non-empty, the
|
||||||
|
table is resolved through the directory namespace client.
|
||||||
|
|
||||||
Returns
|
Returns
|
||||||
-------
|
-------
|
||||||
@@ -936,6 +959,14 @@ class LanceDBConnection(DBConnection):
|
|||||||
stacklevel=2,
|
stacklevel=2,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if namespace_path:
|
||||||
|
return self._namespace_conn().open_table(
|
||||||
|
name,
|
||||||
|
namespace_path=namespace_path,
|
||||||
|
storage_options=storage_options,
|
||||||
|
index_cache_size=index_cache_size,
|
||||||
|
)
|
||||||
|
|
||||||
return LanceTable.open(
|
return LanceTable.open(
|
||||||
self,
|
self,
|
||||||
name,
|
name,
|
||||||
@@ -1020,6 +1051,9 @@ class LanceDBConnection(DBConnection):
|
|||||||
"""
|
"""
|
||||||
if namespace_path is None:
|
if namespace_path is None:
|
||||||
namespace_path = []
|
namespace_path = []
|
||||||
|
if namespace_path:
|
||||||
|
self._namespace_conn().drop_table(name, namespace_path=namespace_path)
|
||||||
|
return
|
||||||
LOOP.run(
|
LOOP.run(
|
||||||
self._conn.drop_table(
|
self._conn.drop_table(
|
||||||
name, namespace_path=namespace_path, ignore_missing=ignore_missing
|
name, namespace_path=namespace_path, ignore_missing=ignore_missing
|
||||||
@@ -1071,14 +1105,17 @@ class LanceDBConnection(DBConnection):
|
|||||||
"""Get the equivalent namespace client for this connection.
|
"""Get the equivalent namespace client for this connection.
|
||||||
|
|
||||||
Returns a DirectoryNamespace pointing to the same root with the
|
Returns a DirectoryNamespace pointing to the same root with the
|
||||||
same storage options.
|
same storage options. The result is cached for the lifetime of
|
||||||
|
this connection.
|
||||||
|
|
||||||
Returns
|
Returns
|
||||||
-------
|
-------
|
||||||
LanceNamespace
|
LanceNamespace
|
||||||
The namespace client for this connection.
|
The namespace client for this connection.
|
||||||
"""
|
"""
|
||||||
return LOOP.run(self._conn.namespace_client())
|
if self._cached_namespace_client is None:
|
||||||
|
self._cached_namespace_client = LOOP.run(self._conn.namespace_client())
|
||||||
|
return self._cached_namespace_client
|
||||||
|
|
||||||
@deprecation.deprecated(
|
@deprecation.deprecated(
|
||||||
deprecated_in="0.15.1",
|
deprecated_in="0.15.1",
|
||||||
@@ -1397,7 +1434,7 @@ class AsyncConnection(object):
|
|||||||
Additional options for the storage backend. Options already set on the
|
Additional options for the storage backend. Options already set on the
|
||||||
connection will be inherited by the table, but can be overridden here.
|
connection will be inherited by the table, but can be overridden here.
|
||||||
See available options at
|
See available options at
|
||||||
<https://lancedb.com/docs/storage/>
|
<https://docs.lancedb.com/storage/>
|
||||||
|
|
||||||
To enable stable row IDs (row IDs remain stable after compaction,
|
To enable stable row IDs (row IDs remain stable after compaction,
|
||||||
update, delete, and merges), set `new_table_enable_stable_row_ids`
|
update, delete, and merges), set `new_table_enable_stable_row_ids`
|
||||||
@@ -1588,7 +1625,7 @@ class AsyncConnection(object):
|
|||||||
Additional options for the storage backend. Options already set on the
|
Additional options for the storage backend. Options already set on the
|
||||||
connection will be inherited by the table, but can be overridden here.
|
connection will be inherited by the table, but can be overridden here.
|
||||||
See available options at
|
See available options at
|
||||||
<https://lancedb.com/docs/storage/>
|
<https://docs.lancedb.com/storage/>
|
||||||
index_cache_size: int, default 256
|
index_cache_size: int, default 256
|
||||||
**Deprecated**: Use session-level cache configuration instead.
|
**Deprecated**: Use session-level cache configuration instead.
|
||||||
Create a Session with custom cache sizes and pass it to lancedb.connect().
|
Create a Session with custom cache sizes and pass it to lancedb.connect().
|
||||||
|
|||||||
@@ -19,10 +19,10 @@ from .utils import TEXT, api_key_not_found_help
|
|||||||
@register("gemini-text")
|
@register("gemini-text")
|
||||||
class GeminiText(TextEmbeddingFunction):
|
class GeminiText(TextEmbeddingFunction):
|
||||||
"""
|
"""
|
||||||
An embedding function that uses the Google's Gemini API. Requires GOOGLE_API_KEY to
|
An embedding function that uses Google's Gemini API. Requires GOOGLE_API_KEY to
|
||||||
be set.
|
be set.
|
||||||
|
|
||||||
https://ai.google.dev/docs/embeddings_guide
|
https://ai.google.dev/gemini-api/docs/embeddings
|
||||||
|
|
||||||
Supports various tasks types:
|
Supports various tasks types:
|
||||||
| Task Type | Description |
|
| Task Type | Description |
|
||||||
@@ -46,9 +46,12 @@ class GeminiText(TextEmbeddingFunction):
|
|||||||
|
|
||||||
Parameters
|
Parameters
|
||||||
----------
|
----------
|
||||||
name: str, default "models/embedding-001"
|
name: str, default "gemini-embedding-001"
|
||||||
The name of the model to use. See the Gemini documentation for a list of
|
The name of the model to use. Supported models include:
|
||||||
available models.
|
- "gemini-embedding-001" (768 dimensions)
|
||||||
|
|
||||||
|
Note: The legacy "models/embedding-001" format is also supported but
|
||||||
|
"gemini-embedding-001" is recommended.
|
||||||
|
|
||||||
query_task_type: str, default "retrieval_query"
|
query_task_type: str, default "retrieval_query"
|
||||||
Sets the task type for the queries.
|
Sets the task type for the queries.
|
||||||
@@ -77,7 +80,7 @@ class GeminiText(TextEmbeddingFunction):
|
|||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
name: str = "models/embedding-001"
|
name: str = "gemini-embedding-001"
|
||||||
query_task_type: str = "retrieval_query"
|
query_task_type: str = "retrieval_query"
|
||||||
source_task_type: str = "retrieval_document"
|
source_task_type: str = "retrieval_document"
|
||||||
|
|
||||||
@@ -114,23 +117,48 @@ class GeminiText(TextEmbeddingFunction):
|
|||||||
texts: list[str] or np.ndarray (of str)
|
texts: list[str] or np.ndarray (of str)
|
||||||
The texts to embed
|
The texts to embed
|
||||||
"""
|
"""
|
||||||
if (
|
from google.genai import types
|
||||||
kwargs.get("task_type") == "retrieval_document"
|
|
||||||
): # Provide a title to use existing API design
|
|
||||||
title = "Embedding of a document"
|
|
||||||
kwargs["title"] = title
|
|
||||||
|
|
||||||
return [
|
task_type = kwargs.get("task_type")
|
||||||
self.client.embed_content(model=self.name, content=text, **kwargs)[
|
|
||||||
"embedding"
|
# Build content objects for embed_content
|
||||||
]
|
contents = []
|
||||||
for text in texts
|
for text in texts:
|
||||||
]
|
if task_type == "retrieval_document":
|
||||||
|
# Provide a title for retrieval_document task
|
||||||
|
contents.append(
|
||||||
|
{"parts": [{"text": "Embedding of a document"}, {"text": text}]}
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
contents.append({"parts": [{"text": text}]})
|
||||||
|
|
||||||
|
# Build config
|
||||||
|
config_kwargs = {}
|
||||||
|
if task_type:
|
||||||
|
config_kwargs["task_type"] = task_type.upper() # API expects uppercase
|
||||||
|
|
||||||
|
# Call embed_content for each content
|
||||||
|
embeddings = []
|
||||||
|
for content in contents:
|
||||||
|
config = (
|
||||||
|
types.EmbedContentConfig(**config_kwargs) if config_kwargs else None
|
||||||
|
)
|
||||||
|
response = self.client.models.embed_content(
|
||||||
|
model=self.name,
|
||||||
|
contents=content,
|
||||||
|
config=config,
|
||||||
|
)
|
||||||
|
embeddings.append(response.embeddings[0].values)
|
||||||
|
|
||||||
|
return embeddings
|
||||||
|
|
||||||
@cached_property
|
@cached_property
|
||||||
def client(self):
|
def client(self):
|
||||||
genai = attempt_import_or_raise("google.generativeai", "google.generativeai")
|
attempt_import_or_raise("google.genai", "google-genai")
|
||||||
|
|
||||||
if not os.environ.get("GOOGLE_API_KEY"):
|
if not os.environ.get("GOOGLE_API_KEY"):
|
||||||
api_key_not_found_help("google")
|
api_key_not_found_help("google")
|
||||||
return genai
|
|
||||||
|
from google import genai as genai_module
|
||||||
|
|
||||||
|
return genai_module.Client(api_key=os.environ.get("GOOGLE_API_KEY"))
|
||||||
|
|||||||
@@ -381,6 +381,8 @@ class LanceNamespaceDBConnection(DBConnection):
|
|||||||
storage_options: Optional[Dict[str, str]] = None,
|
storage_options: Optional[Dict[str, str]] = None,
|
||||||
session: Optional[Session] = None,
|
session: Optional[Session] = None,
|
||||||
namespace_client_pushdown_operations: Optional[List[str]] = None,
|
namespace_client_pushdown_operations: Optional[List[str]] = None,
|
||||||
|
namespace_client_impl: Optional[str] = None,
|
||||||
|
namespace_client_properties: Optional[Dict[str, str]] = None,
|
||||||
):
|
):
|
||||||
"""
|
"""
|
||||||
Initialize a namespace-based LanceDB connection.
|
Initialize a namespace-based LanceDB connection.
|
||||||
@@ -406,12 +408,43 @@ class LanceNamespaceDBConnection(DBConnection):
|
|||||||
namespace.create_table() instead of using declare_table + local write.
|
namespace.create_table() instead of using declare_table + local write.
|
||||||
|
|
||||||
Default is None (no pushdown, all operations run locally).
|
Default is None (no pushdown, all operations run locally).
|
||||||
|
namespace_client_impl : Optional[str]
|
||||||
|
The namespace implementation name used to create this connection.
|
||||||
|
Stored for serialization purposes.
|
||||||
|
namespace_client_properties : Optional[Dict[str, str]]
|
||||||
|
The namespace properties used to create this connection.
|
||||||
|
Stored for serialization purposes.
|
||||||
"""
|
"""
|
||||||
self._namespace_client = namespace_client
|
self._namespace_client = namespace_client
|
||||||
self.read_consistency_interval = read_consistency_interval
|
self.read_consistency_interval = read_consistency_interval
|
||||||
self.storage_options = storage_options or {}
|
self.storage_options = storage_options or {}
|
||||||
self.session = session
|
self.session = session
|
||||||
self._pushdown_operations = set(namespace_client_pushdown_operations or [])
|
self._namespace_client_pushdown_operations = set(
|
||||||
|
namespace_client_pushdown_operations or []
|
||||||
|
)
|
||||||
|
self._namespace_client_impl = namespace_client_impl
|
||||||
|
self._namespace_client_properties = namespace_client_properties
|
||||||
|
|
||||||
|
@override
|
||||||
|
def serialize(self) -> str:
|
||||||
|
import json
|
||||||
|
|
||||||
|
return json.dumps(
|
||||||
|
{
|
||||||
|
"connection_type": "namespace",
|
||||||
|
"namespace_client_impl": self._namespace_client_impl,
|
||||||
|
"namespace_client_properties": self._namespace_client_properties,
|
||||||
|
"namespace_client_pushdown_operations": sorted(
|
||||||
|
self._namespace_client_pushdown_operations
|
||||||
|
),
|
||||||
|
"storage_options": self.storage_options or None,
|
||||||
|
"read_consistency_interval_seconds": (
|
||||||
|
self.read_consistency_interval.total_seconds()
|
||||||
|
if self.read_consistency_interval
|
||||||
|
else None
|
||||||
|
),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
@override
|
@override
|
||||||
def table_names(
|
def table_names(
|
||||||
@@ -467,7 +500,7 @@ class LanceNamespaceDBConnection(DBConnection):
|
|||||||
|
|
||||||
table_id = namespace_path + [name]
|
table_id = namespace_path + [name]
|
||||||
|
|
||||||
if "CreateTable" in self._pushdown_operations:
|
if "CreateTable" in self._namespace_client_pushdown_operations:
|
||||||
return self._create_table_server_side(
|
return self._create_table_server_side(
|
||||||
name=name,
|
name=name,
|
||||||
data=data,
|
data=data,
|
||||||
@@ -549,7 +582,7 @@ class LanceNamespaceDBConnection(DBConnection):
|
|||||||
storage_options=merged_storage_options,
|
storage_options=merged_storage_options,
|
||||||
location=location,
|
location=location,
|
||||||
namespace_client=self._namespace_client,
|
namespace_client=self._namespace_client,
|
||||||
pushdown_operations=self._pushdown_operations,
|
pushdown_operations=self._namespace_client_pushdown_operations,
|
||||||
)
|
)
|
||||||
|
|
||||||
return tbl
|
return tbl
|
||||||
@@ -580,10 +613,13 @@ class LanceNamespaceDBConnection(DBConnection):
|
|||||||
fill_value=fill_value,
|
fill_value=fill_value,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
merged = dict(self.storage_options or {})
|
||||||
|
if storage_options:
|
||||||
|
merged.update(storage_options)
|
||||||
request = CreateTableRequest(
|
request = CreateTableRequest(
|
||||||
id=table_id,
|
id=table_id,
|
||||||
mode=_normalize_create_table_mode(mode),
|
mode=_normalize_create_table_mode(mode),
|
||||||
properties=self.storage_options if self.storage_options else None,
|
properties=merged or None,
|
||||||
)
|
)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
@@ -887,7 +923,7 @@ class LanceNamespaceDBConnection(DBConnection):
|
|||||||
location=table_uri,
|
location=table_uri,
|
||||||
namespace_client=namespace_client,
|
namespace_client=namespace_client,
|
||||||
managed_versioning=managed_versioning,
|
managed_versioning=managed_versioning,
|
||||||
pushdown_operations=self._pushdown_operations,
|
pushdown_operations=self._namespace_client_pushdown_operations,
|
||||||
)
|
)
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@@ -951,7 +987,9 @@ class AsyncLanceNamespaceDBConnection:
|
|||||||
self.read_consistency_interval = read_consistency_interval
|
self.read_consistency_interval = read_consistency_interval
|
||||||
self.storage_options = storage_options or {}
|
self.storage_options = storage_options or {}
|
||||||
self.session = session
|
self.session = session
|
||||||
self._pushdown_operations = set(namespace_client_pushdown_operations or [])
|
self._namespace_client_pushdown_operations = set(
|
||||||
|
namespace_client_pushdown_operations or []
|
||||||
|
)
|
||||||
|
|
||||||
async def table_names(
|
async def table_names(
|
||||||
self,
|
self,
|
||||||
@@ -1006,7 +1044,7 @@ class AsyncLanceNamespaceDBConnection:
|
|||||||
|
|
||||||
table_id = namespace_path + [name]
|
table_id = namespace_path + [name]
|
||||||
|
|
||||||
if "CreateTable" in self._pushdown_operations:
|
if "CreateTable" in self._namespace_client_pushdown_operations:
|
||||||
return await self._create_table_server_side(
|
return await self._create_table_server_side(
|
||||||
name=name,
|
name=name,
|
||||||
data=data,
|
data=data,
|
||||||
@@ -1086,7 +1124,7 @@ class AsyncLanceNamespaceDBConnection:
|
|||||||
storage_options=merged_storage_options,
|
storage_options=merged_storage_options,
|
||||||
location=location,
|
location=location,
|
||||||
namespace_client=self._namespace_client,
|
namespace_client=self._namespace_client,
|
||||||
pushdown_operations=self._pushdown_operations,
|
pushdown_operations=self._namespace_client_pushdown_operations,
|
||||||
)
|
)
|
||||||
|
|
||||||
lance_table = await asyncio.to_thread(_create_table)
|
lance_table = await asyncio.to_thread(_create_table)
|
||||||
@@ -1120,10 +1158,13 @@ class AsyncLanceNamespaceDBConnection:
|
|||||||
fill_value=fill_value,
|
fill_value=fill_value,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
merged = dict(self.storage_options or {})
|
||||||
|
if storage_options:
|
||||||
|
merged.update(storage_options)
|
||||||
request = CreateTableRequest(
|
request = CreateTableRequest(
|
||||||
id=table_id,
|
id=table_id,
|
||||||
mode=_normalize_create_table_mode(mode),
|
mode=_normalize_create_table_mode(mode),
|
||||||
properties=self.storage_options if self.storage_options else None,
|
properties=merged or None,
|
||||||
)
|
)
|
||||||
|
|
||||||
self._namespace_client.create_table(request, arrow_ipc_bytes)
|
self._namespace_client.create_table(request, arrow_ipc_bytes)
|
||||||
@@ -1190,7 +1231,7 @@ class AsyncLanceNamespaceDBConnection:
|
|||||||
location=response.location,
|
location=response.location,
|
||||||
namespace_client=self._namespace_client,
|
namespace_client=self._namespace_client,
|
||||||
managed_versioning=managed_versioning,
|
managed_versioning=managed_versioning,
|
||||||
pushdown_operations=self._pushdown_operations,
|
pushdown_operations=self._namespace_client_pushdown_operations,
|
||||||
)
|
)
|
||||||
|
|
||||||
lance_table = await asyncio.to_thread(_open_table)
|
lance_table = await asyncio.to_thread(_open_table)
|
||||||
@@ -1472,6 +1513,8 @@ def connect_namespace(
|
|||||||
storage_options=storage_options,
|
storage_options=storage_options,
|
||||||
session=session,
|
session=session,
|
||||||
namespace_client_pushdown_operations=namespace_client_pushdown_operations,
|
namespace_client_pushdown_operations=namespace_client_pushdown_operations,
|
||||||
|
namespace_client_impl=namespace_client_impl,
|
||||||
|
namespace_client_properties=namespace_client_properties,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -284,9 +284,8 @@ class Permutations:
|
|||||||
self.permutation_table = permutation_table
|
self.permutation_table = permutation_table
|
||||||
|
|
||||||
if permutation_table.schema.metadata is not None:
|
if permutation_table.schema.metadata is not None:
|
||||||
split_names = permutation_table.schema.metadata.get(
|
raw = permutation_table.schema.metadata.get(b"split_names")
|
||||||
b"split_names", None
|
split_names = raw.decode("utf-8") if raw is not None else None
|
||||||
).decode("utf-8")
|
|
||||||
if split_names is not None:
|
if split_names is not None:
|
||||||
self.split_names = json.loads(split_names)
|
self.split_names = json.loads(split_names)
|
||||||
self.split_dict = {
|
self.split_dict = {
|
||||||
@@ -460,9 +459,8 @@ class Permutation:
|
|||||||
f"Cannot create a permutation on split `{split}`"
|
f"Cannot create a permutation on split `{split}`"
|
||||||
" because no split names are defined in the permutation table"
|
" because no split names are defined in the permutation table"
|
||||||
)
|
)
|
||||||
split_names = permutation_table.schema.metadata.get(
|
raw = permutation_table.schema.metadata.get(b"split_names")
|
||||||
b"split_names", None
|
split_names = raw.decode("utf-8") if raw is not None else None
|
||||||
).decode("utf-8")
|
|
||||||
if split_names is None:
|
if split_names is None:
|
||||||
raise ValueError(
|
raise ValueError(
|
||||||
f"Cannot create a permutation on split `{split}`"
|
f"Cannot create a permutation on split `{split}`"
|
||||||
|
|||||||
@@ -145,6 +145,33 @@ class TlsConfig:
|
|||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class ClientConfig:
|
class ClientConfig:
|
||||||
|
"""Configuration for the LanceDB Cloud HTTP client.
|
||||||
|
|
||||||
|
Attributes
|
||||||
|
----------
|
||||||
|
user_agent: str
|
||||||
|
User agent string sent with requests.
|
||||||
|
retry_config: RetryConfig
|
||||||
|
Configuration for retrying failed requests.
|
||||||
|
timeout_config: Optional[TimeoutConfig]
|
||||||
|
Configuration for request timeouts.
|
||||||
|
extra_headers: Optional[dict]
|
||||||
|
Additional headers to include in requests.
|
||||||
|
id_delimiter: Optional[str]
|
||||||
|
The delimiter to use when constructing object identifiers.
|
||||||
|
tls_config: Optional[TlsConfig]
|
||||||
|
TLS/mTLS configuration for secure connections.
|
||||||
|
header_provider: Optional[HeaderProvider]
|
||||||
|
Provider for dynamic headers to be added to each request.
|
||||||
|
user_id: Optional[str]
|
||||||
|
User identifier for tracking purposes. This is sent as the
|
||||||
|
`x-lancedb-user-id` header in requests to LanceDB Cloud/Enterprise.
|
||||||
|
|
||||||
|
This can also be set via the `LANCEDB_USER_ID` environment variable.
|
||||||
|
Alternatively, set `LANCEDB_USER_ID_ENV_KEY` to specify another
|
||||||
|
environment variable that contains the user ID value.
|
||||||
|
"""
|
||||||
|
|
||||||
user_agent: str = f"LanceDB-Python-Client/{__version__}"
|
user_agent: str = f"LanceDB-Python-Client/{__version__}"
|
||||||
retry_config: RetryConfig = field(default_factory=RetryConfig)
|
retry_config: RetryConfig = field(default_factory=RetryConfig)
|
||||||
timeout_config: Optional[TimeoutConfig] = field(default_factory=TimeoutConfig)
|
timeout_config: Optional[TimeoutConfig] = field(default_factory=TimeoutConfig)
|
||||||
@@ -152,6 +179,7 @@ class ClientConfig:
|
|||||||
id_delimiter: Optional[str] = None
|
id_delimiter: Optional[str] = None
|
||||||
tls_config: Optional[TlsConfig] = None
|
tls_config: Optional[TlsConfig] = None
|
||||||
header_provider: Optional["HeaderProvider"] = None
|
header_provider: Optional["HeaderProvider"] = None
|
||||||
|
user_id: Optional[str] = None
|
||||||
|
|
||||||
def __post_init__(self):
|
def __post_init__(self):
|
||||||
if isinstance(self.retry_config, dict):
|
if isinstance(self.retry_config, dict):
|
||||||
|
|||||||
+231
-36
@@ -191,7 +191,7 @@ def _into_pyarrow_reader(
|
|||||||
f"Unknown data type {type(data)}. "
|
f"Unknown data type {type(data)}. "
|
||||||
"Supported types: list of dicts, pandas DataFrame, polars DataFrame, "
|
"Supported types: list of dicts, pandas DataFrame, polars DataFrame, "
|
||||||
"pyarrow Table/RecordBatch, or Pydantic models. "
|
"pyarrow Table/RecordBatch, or Pydantic models. "
|
||||||
"See https://lancedb.com/docs/tables/ for examples."
|
"See https://docs.lancedb.com/tables/ for examples."
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@@ -270,15 +270,17 @@ def _sanitize_data(
|
|||||||
reader,
|
reader,
|
||||||
on_bad_vectors=on_bad_vectors,
|
on_bad_vectors=on_bad_vectors,
|
||||||
fill_value=fill_value,
|
fill_value=fill_value,
|
||||||
|
target_schema=target_schema,
|
||||||
|
metadata=metadata,
|
||||||
)
|
)
|
||||||
|
|
||||||
if target_schema is None:
|
if target_schema is None:
|
||||||
target_schema, reader = _infer_target_schema(reader)
|
target_schema, reader = _infer_target_schema(reader)
|
||||||
|
|
||||||
if metadata:
|
if metadata:
|
||||||
new_metadata = target_schema.metadata or {}
|
target_schema = target_schema.with_metadata(
|
||||||
new_metadata.update(metadata)
|
_merge_metadata(target_schema.metadata, metadata)
|
||||||
target_schema = target_schema.with_metadata(new_metadata)
|
)
|
||||||
|
|
||||||
_validate_schema(target_schema)
|
_validate_schema(target_schema)
|
||||||
reader = _cast_to_target_schema(reader, target_schema, allow_subschema)
|
reader = _cast_to_target_schema(reader, target_schema, allow_subschema)
|
||||||
@@ -294,7 +296,7 @@ def _cast_to_target_schema(
|
|||||||
# pa.Table.cast expects field order not to be changed.
|
# pa.Table.cast expects field order not to be changed.
|
||||||
# Lance doesn't care about field order, so we don't need to rearrange fields
|
# Lance doesn't care about field order, so we don't need to rearrange fields
|
||||||
# to match the target schema. We just need to correctly cast the fields.
|
# to match the target schema. We just need to correctly cast the fields.
|
||||||
if reader.schema == target_schema:
|
if reader.schema.equals(target_schema, check_metadata=True):
|
||||||
# Fast path when the schemas are already the same
|
# Fast path when the schemas are already the same
|
||||||
return reader
|
return reader
|
||||||
|
|
||||||
@@ -314,7 +316,13 @@ def _cast_to_target_schema(
|
|||||||
def gen():
|
def gen():
|
||||||
for batch in reader:
|
for batch in reader:
|
||||||
# Table but not RecordBatch has cast.
|
# Table but not RecordBatch has cast.
|
||||||
yield pa.Table.from_batches([batch]).cast(reordered_schema).to_batches()[0]
|
cast_batches = (
|
||||||
|
pa.Table.from_batches([batch]).cast(reordered_schema).to_batches()
|
||||||
|
)
|
||||||
|
if cast_batches:
|
||||||
|
yield pa.RecordBatch.from_arrays(
|
||||||
|
cast_batches[0].columns, schema=reordered_schema
|
||||||
|
)
|
||||||
|
|
||||||
return pa.RecordBatchReader.from_batches(reordered_schema, gen())
|
return pa.RecordBatchReader.from_batches(reordered_schema, gen())
|
||||||
|
|
||||||
@@ -332,27 +340,37 @@ def _align_field_types(
|
|||||||
if target_field is None:
|
if target_field is None:
|
||||||
raise ValueError(f"Field '{field.name}' not found in target schema")
|
raise ValueError(f"Field '{field.name}' not found in target schema")
|
||||||
if pa.types.is_struct(target_field.type):
|
if pa.types.is_struct(target_field.type):
|
||||||
|
if pa.types.is_struct(field.type):
|
||||||
new_type = pa.struct(
|
new_type = pa.struct(
|
||||||
_align_field_types(
|
_align_field_types(
|
||||||
field.type.fields,
|
field.type.fields,
|
||||||
target_field.type.fields,
|
target_field.type.fields,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
else:
|
||||||
|
new_type = target_field.type
|
||||||
elif pa.types.is_list(target_field.type):
|
elif pa.types.is_list(target_field.type):
|
||||||
|
if _is_list_like(field.type):
|
||||||
new_type = pa.list_(
|
new_type = pa.list_(
|
||||||
_align_field_types(
|
_align_field_types(
|
||||||
[field.type.value_field],
|
[field.type.value_field],
|
||||||
[target_field.type.value_field],
|
[target_field.type.value_field],
|
||||||
)[0]
|
)[0]
|
||||||
)
|
)
|
||||||
|
else:
|
||||||
|
new_type = target_field.type
|
||||||
elif pa.types.is_large_list(target_field.type):
|
elif pa.types.is_large_list(target_field.type):
|
||||||
|
if _is_list_like(field.type):
|
||||||
new_type = pa.large_list(
|
new_type = pa.large_list(
|
||||||
_align_field_types(
|
_align_field_types(
|
||||||
[field.type.value_field],
|
[field.type.value_field],
|
||||||
[target_field.type.value_field],
|
[target_field.type.value_field],
|
||||||
)[0]
|
)[0]
|
||||||
)
|
)
|
||||||
|
else:
|
||||||
|
new_type = target_field.type
|
||||||
elif pa.types.is_fixed_size_list(target_field.type):
|
elif pa.types.is_fixed_size_list(target_field.type):
|
||||||
|
if _is_list_like(field.type):
|
||||||
new_type = pa.list_(
|
new_type = pa.list_(
|
||||||
_align_field_types(
|
_align_field_types(
|
||||||
[field.type.value_field],
|
[field.type.value_field],
|
||||||
@@ -362,7 +380,11 @@ def _align_field_types(
|
|||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
new_type = target_field.type
|
new_type = target_field.type
|
||||||
new_fields.append(pa.field(field.name, new_type, field.nullable))
|
else:
|
||||||
|
new_type = target_field.type
|
||||||
|
new_fields.append(
|
||||||
|
pa.field(field.name, new_type, field.nullable, target_field.metadata)
|
||||||
|
)
|
||||||
return new_fields
|
return new_fields
|
||||||
|
|
||||||
|
|
||||||
@@ -440,6 +462,7 @@ def sanitize_create_table(
|
|||||||
schema = data.schema
|
schema = data.schema
|
||||||
|
|
||||||
if metadata:
|
if metadata:
|
||||||
|
metadata = _merge_metadata(schema.metadata, metadata)
|
||||||
schema = schema.with_metadata(metadata)
|
schema = schema.with_metadata(metadata)
|
||||||
# Need to apply metadata to the data as well
|
# Need to apply metadata to the data as well
|
||||||
if isinstance(data, pa.Table):
|
if isinstance(data, pa.Table):
|
||||||
@@ -492,9 +515,9 @@ def _append_vector_columns(
|
|||||||
vector columns to the table.
|
vector columns to the table.
|
||||||
"""
|
"""
|
||||||
if schema is None:
|
if schema is None:
|
||||||
metadata = metadata or {}
|
metadata = _merge_metadata(metadata)
|
||||||
else:
|
else:
|
||||||
metadata = schema.metadata or metadata or {}
|
metadata = _merge_metadata(schema.metadata, metadata)
|
||||||
functions = EmbeddingFunctionRegistry.get_instance().parse_functions(metadata)
|
functions = EmbeddingFunctionRegistry.get_instance().parse_functions(metadata)
|
||||||
|
|
||||||
if not functions:
|
if not functions:
|
||||||
@@ -3211,43 +3234,157 @@ def _handle_bad_vectors(
|
|||||||
reader: pa.RecordBatchReader,
|
reader: pa.RecordBatchReader,
|
||||||
on_bad_vectors: Literal["error", "drop", "fill", "null"] = "error",
|
on_bad_vectors: Literal["error", "drop", "fill", "null"] = "error",
|
||||||
fill_value: float = 0.0,
|
fill_value: float = 0.0,
|
||||||
|
target_schema: Optional[pa.Schema] = None,
|
||||||
|
metadata: Optional[dict] = None,
|
||||||
) -> pa.RecordBatchReader:
|
) -> pa.RecordBatchReader:
|
||||||
vector_columns = []
|
vector_columns = _find_vector_columns(reader.schema, target_schema, metadata)
|
||||||
|
if not vector_columns:
|
||||||
|
return reader
|
||||||
|
|
||||||
for field in reader.schema:
|
output_schema = _vector_output_schema(reader.schema, vector_columns)
|
||||||
# They can provide a 'vector' column that isn't yet a FSL
|
|
||||||
named_vector_col = (
|
def gen():
|
||||||
(
|
for batch in reader:
|
||||||
pa.types.is_list(field.type)
|
pending_dims = []
|
||||||
or pa.types.is_large_list(field.type)
|
for vector_column in vector_columns:
|
||||||
or pa.types.is_fixed_size_list(field.type)
|
dim = vector_column["expected_dim"]
|
||||||
|
if target_schema is not None and dim is None:
|
||||||
|
dim = _infer_vector_dim(batch[vector_column["name"]])
|
||||||
|
pending_dims.append(vector_column)
|
||||||
|
batch = _handle_bad_vector_column(
|
||||||
|
batch,
|
||||||
|
vector_column_name=vector_column["name"],
|
||||||
|
on_bad_vectors=on_bad_vectors,
|
||||||
|
fill_value=fill_value,
|
||||||
|
expected_dim=dim,
|
||||||
|
expected_value_type=vector_column["expected_value_type"],
|
||||||
)
|
)
|
||||||
|
for vector_column in pending_dims:
|
||||||
|
if vector_column["expected_dim"] is None:
|
||||||
|
vector_column["expected_dim"] = _infer_vector_dim(
|
||||||
|
batch[vector_column["name"]]
|
||||||
|
)
|
||||||
|
if batch.schema.equals(output_schema, check_metadata=True):
|
||||||
|
yield batch
|
||||||
|
continue
|
||||||
|
|
||||||
|
cast_batches = (
|
||||||
|
pa.Table.from_batches([batch]).cast(output_schema).to_batches()
|
||||||
|
)
|
||||||
|
if cast_batches:
|
||||||
|
yield pa.RecordBatch.from_arrays(
|
||||||
|
cast_batches[0].columns,
|
||||||
|
schema=output_schema,
|
||||||
|
)
|
||||||
|
|
||||||
|
return pa.RecordBatchReader.from_batches(output_schema, gen())
|
||||||
|
|
||||||
|
|
||||||
|
def _find_vector_columns(
|
||||||
|
reader_schema: pa.Schema,
|
||||||
|
target_schema: Optional[pa.Schema],
|
||||||
|
metadata: Optional[dict],
|
||||||
|
) -> List[dict]:
|
||||||
|
if target_schema is None:
|
||||||
|
vector_columns = []
|
||||||
|
for field in reader_schema:
|
||||||
|
named_vector_col = (
|
||||||
|
_is_list_like(field.type)
|
||||||
and pa.types.is_floating(field.type.value_type)
|
and pa.types.is_floating(field.type.value_type)
|
||||||
and field.name == VECTOR_COLUMN_NAME
|
and field.name == VECTOR_COLUMN_NAME
|
||||||
)
|
)
|
||||||
# TODO: we're making an assumption that fixed size list of 10 or more
|
|
||||||
# is a vector column. This is definitely a bit hacky.
|
|
||||||
likely_vector_col = (
|
likely_vector_col = (
|
||||||
pa.types.is_fixed_size_list(field.type)
|
pa.types.is_fixed_size_list(field.type)
|
||||||
and pa.types.is_floating(field.type.value_type)
|
and pa.types.is_floating(field.type.value_type)
|
||||||
and (field.type.list_size >= 10)
|
and (field.type.list_size >= 10)
|
||||||
)
|
)
|
||||||
|
|
||||||
if named_vector_col or likely_vector_col:
|
if named_vector_col or likely_vector_col:
|
||||||
vector_columns.append(field.name)
|
vector_columns.append(
|
||||||
|
{
|
||||||
def gen():
|
"name": field.name,
|
||||||
for batch in reader:
|
"expected_dim": None,
|
||||||
for name in vector_columns:
|
"expected_value_type": None,
|
||||||
batch = _handle_bad_vector_column(
|
}
|
||||||
batch,
|
|
||||||
vector_column_name=name,
|
|
||||||
on_bad_vectors=on_bad_vectors,
|
|
||||||
fill_value=fill_value,
|
|
||||||
)
|
)
|
||||||
yield batch
|
return vector_columns
|
||||||
|
|
||||||
return pa.RecordBatchReader.from_batches(reader.schema, gen())
|
reader_column_names = set(reader_schema.names)
|
||||||
|
active_metadata = _merge_metadata(target_schema.metadata, metadata)
|
||||||
|
embedding_function_columns = set(
|
||||||
|
EmbeddingFunctionRegistry.get_instance().parse_functions(active_metadata).keys()
|
||||||
|
)
|
||||||
|
vector_columns = []
|
||||||
|
for field in target_schema:
|
||||||
|
if field.name not in reader_column_names:
|
||||||
|
continue
|
||||||
|
if not _is_list_like(field.type) or not pa.types.is_floating(
|
||||||
|
field.type.value_type
|
||||||
|
):
|
||||||
|
continue
|
||||||
|
|
||||||
|
reader_field = reader_schema.field(field.name)
|
||||||
|
named_vector_col = (
|
||||||
|
field.name in embedding_function_columns
|
||||||
|
or field.name == VECTOR_COLUMN_NAME
|
||||||
|
or (field.name == "embedding" and pa.types.is_fixed_size_list(field.type))
|
||||||
|
)
|
||||||
|
typed_fixed_vector_col = (
|
||||||
|
pa.types.is_fixed_size_list(reader_field.type)
|
||||||
|
and pa.types.is_floating(reader_field.type.value_type)
|
||||||
|
and reader_field.type.list_size >= 10
|
||||||
|
)
|
||||||
|
|
||||||
|
if named_vector_col or typed_fixed_vector_col:
|
||||||
|
vector_columns.append(
|
||||||
|
{
|
||||||
|
"name": field.name,
|
||||||
|
"expected_dim": (
|
||||||
|
field.type.list_size
|
||||||
|
if pa.types.is_fixed_size_list(field.type)
|
||||||
|
else None
|
||||||
|
),
|
||||||
|
"expected_value_type": field.type.value_type,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
return vector_columns
|
||||||
|
|
||||||
|
|
||||||
|
def _vector_output_schema(
|
||||||
|
reader_schema: pa.Schema,
|
||||||
|
vector_columns: List[dict],
|
||||||
|
) -> pa.Schema:
|
||||||
|
columns_by_name = {column["name"]: column for column in vector_columns}
|
||||||
|
fields = []
|
||||||
|
for field in reader_schema:
|
||||||
|
column = columns_by_name.get(field.name)
|
||||||
|
if column is None:
|
||||||
|
output_type = field.type
|
||||||
|
else:
|
||||||
|
output_type = _vector_output_type(field, column)
|
||||||
|
fields.append(pa.field(field.name, output_type, field.nullable, field.metadata))
|
||||||
|
return pa.schema(fields, metadata=reader_schema.metadata)
|
||||||
|
|
||||||
|
|
||||||
|
def _vector_output_type(field: pa.Field, vector_column: dict) -> pa.DataType:
|
||||||
|
if not _is_list_like(field.type):
|
||||||
|
return field.type
|
||||||
|
|
||||||
|
if vector_column["expected_value_type"] is not None and (
|
||||||
|
pa.types.is_null(field.type.value_type)
|
||||||
|
or pa.types.is_integer(field.type.value_type)
|
||||||
|
or pa.types.is_unsigned_integer(field.type.value_type)
|
||||||
|
):
|
||||||
|
return pa.list_(vector_column["expected_value_type"])
|
||||||
|
|
||||||
|
if (
|
||||||
|
vector_column["expected_dim"] is not None
|
||||||
|
and pa.types.is_fixed_size_list(field.type)
|
||||||
|
and field.type.list_size != vector_column["expected_dim"]
|
||||||
|
):
|
||||||
|
return pa.list_(field.type.value_type)
|
||||||
|
|
||||||
|
return field.type
|
||||||
|
|
||||||
|
|
||||||
def _handle_bad_vector_column(
|
def _handle_bad_vector_column(
|
||||||
@@ -3255,6 +3392,8 @@ def _handle_bad_vector_column(
|
|||||||
vector_column_name: str,
|
vector_column_name: str,
|
||||||
on_bad_vectors: str = "error",
|
on_bad_vectors: str = "error",
|
||||||
fill_value: float = 0.0,
|
fill_value: float = 0.0,
|
||||||
|
expected_dim: Optional[int] = None,
|
||||||
|
expected_value_type: Optional[pa.DataType] = None,
|
||||||
) -> pa.RecordBatch:
|
) -> pa.RecordBatch:
|
||||||
"""
|
"""
|
||||||
Ensure that the vector column exists and has type fixed_size_list(float)
|
Ensure that the vector column exists and has type fixed_size_list(float)
|
||||||
@@ -3271,14 +3410,39 @@ def _handle_bad_vector_column(
|
|||||||
fill_value: float, default 0.0
|
fill_value: float, default 0.0
|
||||||
The value to use when filling vectors. Only used if on_bad_vectors="fill".
|
The value to use when filling vectors. Only used if on_bad_vectors="fill".
|
||||||
"""
|
"""
|
||||||
|
position = data.column_names.index(vector_column_name)
|
||||||
vec_arr = data[vector_column_name]
|
vec_arr = data[vector_column_name]
|
||||||
|
if not _is_list_like(vec_arr.type):
|
||||||
|
return data
|
||||||
|
|
||||||
|
if (
|
||||||
|
expected_dim is not None
|
||||||
|
and pa.types.is_fixed_size_list(vec_arr.type)
|
||||||
|
and vec_arr.type.list_size != expected_dim
|
||||||
|
):
|
||||||
|
vec_arr = pa.array(vec_arr.to_pylist(), type=pa.list_(vec_arr.type.value_type))
|
||||||
|
data = data.set_column(position, vector_column_name, vec_arr)
|
||||||
|
|
||||||
|
if expected_value_type is not None and (
|
||||||
|
pa.types.is_integer(vec_arr.type.value_type)
|
||||||
|
or pa.types.is_unsigned_integer(vec_arr.type.value_type)
|
||||||
|
):
|
||||||
|
vec_arr = pa.array(vec_arr.to_pylist(), type=pa.list_(expected_value_type))
|
||||||
|
data = data.set_column(position, vector_column_name, vec_arr)
|
||||||
|
|
||||||
|
if pa.types.is_floating(vec_arr.type.value_type):
|
||||||
has_nan = has_nan_values(vec_arr)
|
has_nan = has_nan_values(vec_arr)
|
||||||
|
else:
|
||||||
|
has_nan = pa.array([False] * len(vec_arr))
|
||||||
|
|
||||||
if pa.types.is_fixed_size_list(vec_arr.type):
|
if expected_dim is not None:
|
||||||
|
dim = expected_dim
|
||||||
|
elif pa.types.is_fixed_size_list(vec_arr.type):
|
||||||
dim = vec_arr.type.list_size
|
dim = vec_arr.type.list_size
|
||||||
else:
|
else:
|
||||||
dim = _modal_list_size(vec_arr)
|
dim = _infer_vector_dim(vec_arr)
|
||||||
|
if dim is None:
|
||||||
|
return data
|
||||||
has_wrong_dim = pc.not_equal(pc.list_value_length(vec_arr), dim)
|
has_wrong_dim = pc.not_equal(pc.list_value_length(vec_arr), dim)
|
||||||
|
|
||||||
has_bad_vectors = pc.any(has_nan).as_py() or pc.any(has_wrong_dim).as_py()
|
has_bad_vectors = pc.any(has_nan).as_py() or pc.any(has_wrong_dim).as_py()
|
||||||
@@ -3316,13 +3480,12 @@ def _handle_bad_vector_column(
|
|||||||
)
|
)
|
||||||
vec_arr = pc.if_else(
|
vec_arr = pc.if_else(
|
||||||
is_bad,
|
is_bad,
|
||||||
pa.scalar([fill_value] * dim),
|
pa.scalar([fill_value] * dim, type=vec_arr.type),
|
||||||
vec_arr,
|
vec_arr,
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
raise ValueError(f"Invalid value for on_bad_vectors: {on_bad_vectors}")
|
raise ValueError(f"Invalid value for on_bad_vectors: {on_bad_vectors}")
|
||||||
|
|
||||||
position = data.column_names.index(vector_column_name)
|
|
||||||
return data.set_column(position, vector_column_name, vec_arr)
|
return data.set_column(position, vector_column_name, vec_arr)
|
||||||
|
|
||||||
|
|
||||||
@@ -3343,6 +3506,28 @@ def has_nan_values(arr: Union[pa.ListArray, pa.ChunkedArray]) -> pa.BooleanArray
|
|||||||
return pc.is_in(indices, has_nan_indices)
|
return pc.is_in(indices, has_nan_indices)
|
||||||
|
|
||||||
|
|
||||||
|
def _is_list_like(data_type: pa.DataType) -> bool:
|
||||||
|
return (
|
||||||
|
pa.types.is_list(data_type)
|
||||||
|
or pa.types.is_large_list(data_type)
|
||||||
|
or pa.types.is_fixed_size_list(data_type)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def _merge_metadata(*metadata_dicts: Optional[dict]) -> dict:
|
||||||
|
merged = {}
|
||||||
|
for metadata in metadata_dicts:
|
||||||
|
if metadata is None:
|
||||||
|
continue
|
||||||
|
for key, value in metadata.items():
|
||||||
|
if isinstance(key, str):
|
||||||
|
key = key.encode("utf-8")
|
||||||
|
if isinstance(value, str):
|
||||||
|
value = value.encode("utf-8")
|
||||||
|
merged[key] = value
|
||||||
|
return merged
|
||||||
|
|
||||||
|
|
||||||
def _name_suggests_vector_column(field_name: str) -> bool:
|
def _name_suggests_vector_column(field_name: str) -> bool:
|
||||||
"""Check if a field name indicates a vector column."""
|
"""Check if a field name indicates a vector column."""
|
||||||
name_lower = field_name.lower()
|
name_lower = field_name.lower()
|
||||||
@@ -3410,6 +3595,16 @@ def _modal_list_size(arr: Union[pa.ListArray, pa.ChunkedArray]) -> int:
|
|||||||
return pc.mode(pc.list_value_length(arr))[0].as_py()["mode"]
|
return pc.mode(pc.list_value_length(arr))[0].as_py()["mode"]
|
||||||
|
|
||||||
|
|
||||||
|
def _infer_vector_dim(arr: Union[pa.Array, pa.ChunkedArray]) -> Optional[int]:
|
||||||
|
if not _is_list_like(arr.type):
|
||||||
|
return None
|
||||||
|
lengths = pc.list_value_length(arr)
|
||||||
|
lengths = pc.filter(lengths, pc.greater(lengths, 0))
|
||||||
|
if len(lengths) == 0:
|
||||||
|
return None
|
||||||
|
return pc.mode(lengths)[0].as_py()["mode"]
|
||||||
|
|
||||||
|
|
||||||
def _validate_schema(schema: pa.Schema):
|
def _validate_schema(schema: pa.Schema):
|
||||||
"""
|
"""
|
||||||
Make sure the metadata is valid utf8
|
Make sure the metadata is valid utf8
|
||||||
|
|||||||
@@ -897,42 +897,22 @@ def test_bypass_vector_index_sync(tmp_db: lancedb.DBConnection):
|
|||||||
|
|
||||||
|
|
||||||
def test_local_namespace_operations(tmp_path):
|
def test_local_namespace_operations(tmp_path):
|
||||||
"""Test that local mode namespace operations behave as expected."""
|
"""Test that local mode namespace operations work via directory namespace."""
|
||||||
# Create a local database connection
|
|
||||||
db = lancedb.connect(tmp_path)
|
db = lancedb.connect(tmp_path)
|
||||||
|
|
||||||
# Test list_namespaces returns empty list for root namespace
|
# Root namespace starts empty
|
||||||
namespaces = db.list_namespaces().namespaces
|
assert db.list_namespaces().namespaces == []
|
||||||
assert namespaces == []
|
|
||||||
|
|
||||||
# Test list_namespaces with non-empty namespace raises NotImplementedError
|
# Create and list child namespace
|
||||||
with pytest.raises(
|
db.create_namespace(["child"])
|
||||||
NotImplementedError,
|
assert "child" in db.list_namespaces().namespaces
|
||||||
match="Namespace operations are not supported for listing database",
|
|
||||||
):
|
|
||||||
db.list_namespaces(namespace_path=["test"])
|
|
||||||
|
|
||||||
|
# List namespaces under child
|
||||||
|
assert db.list_namespaces(namespace_path=["child"]).namespaces == []
|
||||||
|
|
||||||
def test_local_create_namespace_not_supported(tmp_path):
|
# Drop namespace
|
||||||
"""Test that create_namespace is not supported in local mode."""
|
db.drop_namespace(["child"])
|
||||||
db = lancedb.connect(tmp_path)
|
assert db.list_namespaces().namespaces == []
|
||||||
|
|
||||||
with pytest.raises(
|
|
||||||
NotImplementedError,
|
|
||||||
match="Namespace operations are not supported for listing database",
|
|
||||||
):
|
|
||||||
db.create_namespace(["test_namespace"])
|
|
||||||
|
|
||||||
|
|
||||||
def test_local_drop_namespace_not_supported(tmp_path):
|
|
||||||
"""Test that drop_namespace is not supported in local mode."""
|
|
||||||
db = lancedb.connect(tmp_path)
|
|
||||||
|
|
||||||
with pytest.raises(
|
|
||||||
NotImplementedError,
|
|
||||||
match="Namespace operations are not supported for listing database",
|
|
||||||
):
|
|
||||||
db.drop_namespace(["test_namespace"])
|
|
||||||
|
|
||||||
|
|
||||||
def test_clone_table_latest_version(tmp_path):
|
def test_clone_table_latest_version(tmp_path):
|
||||||
|
|||||||
@@ -681,7 +681,7 @@ class TestPushdownOperations:
|
|||||||
{"root": self.temp_dir},
|
{"root": self.temp_dir},
|
||||||
namespace_client_pushdown_operations=["QueryTable"],
|
namespace_client_pushdown_operations=["QueryTable"],
|
||||||
)
|
)
|
||||||
assert "QueryTable" in db._pushdown_operations
|
assert "QueryTable" in db._namespace_client_pushdown_operations
|
||||||
|
|
||||||
def test_create_table_pushdown_stored(self):
|
def test_create_table_pushdown_stored(self):
|
||||||
"""Test that CreateTable pushdown is stored on sync connection."""
|
"""Test that CreateTable pushdown is stored on sync connection."""
|
||||||
@@ -690,7 +690,7 @@ class TestPushdownOperations:
|
|||||||
{"root": self.temp_dir},
|
{"root": self.temp_dir},
|
||||||
namespace_client_pushdown_operations=["CreateTable"],
|
namespace_client_pushdown_operations=["CreateTable"],
|
||||||
)
|
)
|
||||||
assert "CreateTable" in db._pushdown_operations
|
assert "CreateTable" in db._namespace_client_pushdown_operations
|
||||||
|
|
||||||
def test_both_pushdowns_stored(self):
|
def test_both_pushdowns_stored(self):
|
||||||
"""Test that both pushdown operations can be set together."""
|
"""Test that both pushdown operations can be set together."""
|
||||||
@@ -699,13 +699,13 @@ class TestPushdownOperations:
|
|||||||
{"root": self.temp_dir},
|
{"root": self.temp_dir},
|
||||||
namespace_client_pushdown_operations=["QueryTable", "CreateTable"],
|
namespace_client_pushdown_operations=["QueryTable", "CreateTable"],
|
||||||
)
|
)
|
||||||
assert "QueryTable" in db._pushdown_operations
|
assert "QueryTable" in db._namespace_client_pushdown_operations
|
||||||
assert "CreateTable" in db._pushdown_operations
|
assert "CreateTable" in db._namespace_client_pushdown_operations
|
||||||
|
|
||||||
def test_pushdown_defaults_to_empty(self):
|
def test_pushdown_defaults_to_empty(self):
|
||||||
"""Test that pushdown operations default to empty."""
|
"""Test that pushdown operations default to empty."""
|
||||||
db = lancedb.connect_namespace("dir", {"root": self.temp_dir})
|
db = lancedb.connect_namespace("dir", {"root": self.temp_dir})
|
||||||
assert len(db._pushdown_operations) == 0
|
assert len(db._namespace_client_pushdown_operations) == 0
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
@pytest.mark.asyncio
|
||||||
@@ -727,7 +727,7 @@ class TestAsyncPushdownOperations:
|
|||||||
{"root": self.temp_dir},
|
{"root": self.temp_dir},
|
||||||
namespace_client_pushdown_operations=["QueryTable"],
|
namespace_client_pushdown_operations=["QueryTable"],
|
||||||
)
|
)
|
||||||
assert "QueryTable" in db._pushdown_operations
|
assert "QueryTable" in db._namespace_client_pushdown_operations
|
||||||
|
|
||||||
async def test_async_create_table_pushdown_stored(self):
|
async def test_async_create_table_pushdown_stored(self):
|
||||||
"""Test that CreateTable pushdown is stored on async connection."""
|
"""Test that CreateTable pushdown is stored on async connection."""
|
||||||
@@ -736,9 +736,9 @@ class TestAsyncPushdownOperations:
|
|||||||
{"root": self.temp_dir},
|
{"root": self.temp_dir},
|
||||||
namespace_client_pushdown_operations=["CreateTable"],
|
namespace_client_pushdown_operations=["CreateTable"],
|
||||||
)
|
)
|
||||||
assert "CreateTable" in db._pushdown_operations
|
assert "CreateTable" in db._namespace_client_pushdown_operations
|
||||||
|
|
||||||
async def test_async_pushdown_defaults_to_empty(self):
|
async def test_async_pushdown_defaults_to_empty(self):
|
||||||
"""Test that pushdown operations default to empty on async connection."""
|
"""Test that pushdown operations default to empty on async connection."""
|
||||||
db = lancedb.connect_namespace_async("dir", {"root": self.temp_dir})
|
db = lancedb.connect_namespace_async("dir", {"root": self.temp_dir})
|
||||||
assert len(db._pushdown_operations) == 0
|
assert len(db._namespace_client_pushdown_operations) == 0
|
||||||
|
|||||||
@@ -522,6 +522,50 @@ def test_no_split_names(some_table: Table):
|
|||||||
assert permutations[1].num_rows == 500
|
assert permutations[1].num_rows == 500
|
||||||
|
|
||||||
|
|
||||||
|
def test_permutations_metadata_without_split_names_key(mem_db: DBConnection):
|
||||||
|
"""Regression: schema metadata present but missing split_names key must not crash.
|
||||||
|
|
||||||
|
Previously, `.get(b"split_names", None).decode()` was called unconditionally,
|
||||||
|
so any permutation table whose metadata dict had other keys but no split_names
|
||||||
|
raised AttributeError: 'NoneType' has no attribute 'decode'.
|
||||||
|
"""
|
||||||
|
base = mem_db.create_table("base_nosplit", pa.table({"x": range(10)}))
|
||||||
|
|
||||||
|
# Build a permutation-like table that carries some metadata but NOT split_names.
|
||||||
|
raw = pa.table(
|
||||||
|
{
|
||||||
|
"row_id": pa.array(range(10), type=pa.uint64()),
|
||||||
|
"split_id": pa.array([0] * 10, type=pa.uint32()),
|
||||||
|
}
|
||||||
|
).replace_schema_metadata({b"other_key": b"other_value"})
|
||||||
|
perm_tbl = mem_db.create_table("perm_nosplit", raw)
|
||||||
|
|
||||||
|
permutations = Permutations(base, perm_tbl)
|
||||||
|
assert permutations.split_names == []
|
||||||
|
assert permutations.split_dict == {}
|
||||||
|
|
||||||
|
|
||||||
|
def test_from_tables_string_split_missing_names_key(mem_db: DBConnection):
|
||||||
|
"""Regression: from_tables() with a string split must raise ValueError, not
|
||||||
|
AttributeError.
|
||||||
|
|
||||||
|
Previously, `.get(b"split_names", None).decode()` crashed with AttributeError
|
||||||
|
when the metadata dict existed but had no split_names key.
|
||||||
|
"""
|
||||||
|
base = mem_db.create_table("base_strsplit", pa.table({"x": range(10)}))
|
||||||
|
|
||||||
|
raw = pa.table(
|
||||||
|
{
|
||||||
|
"row_id": pa.array(range(10), type=pa.uint64()),
|
||||||
|
"split_id": pa.array([0] * 10, type=pa.uint32()),
|
||||||
|
}
|
||||||
|
).replace_schema_metadata({b"other_key": b"other_value"})
|
||||||
|
perm_tbl = mem_db.create_table("perm_strsplit", raw)
|
||||||
|
|
||||||
|
with pytest.raises(ValueError, match="no split names are defined"):
|
||||||
|
Permutation.from_tables(base, perm_tbl, split="train")
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def some_perm_table(some_table: Table) -> Table:
|
def some_perm_table(some_table: Table) -> Table:
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -1049,6 +1049,231 @@ def test_add_with_nans(mem_db: DBConnection):
|
|||||||
assert np.allclose(v, np.array([0.0, 0.0]))
|
assert np.allclose(v, np.array([0.0, 0.0]))
|
||||||
|
|
||||||
|
|
||||||
|
def test_add_with_empty_fixed_size_list_drops_bad_rows(mem_db: DBConnection):
|
||||||
|
class Schema(LanceModel):
|
||||||
|
text: str
|
||||||
|
embedding: Vector(16)
|
||||||
|
|
||||||
|
table = mem_db.create_table("test_empty_embeddings", schema=Schema)
|
||||||
|
table.add(
|
||||||
|
[
|
||||||
|
{"text": "hello", "embedding": []},
|
||||||
|
{"text": "bar", "embedding": [0.1] * 16},
|
||||||
|
],
|
||||||
|
on_bad_vectors="drop",
|
||||||
|
)
|
||||||
|
|
||||||
|
data = table.to_arrow()
|
||||||
|
assert data["text"].to_pylist() == ["bar"]
|
||||||
|
assert np.allclose(data["embedding"].to_pylist()[0], np.array([0.1] * 16))
|
||||||
|
|
||||||
|
|
||||||
|
def test_add_with_integer_embeddings_preserves_casting(mem_db: DBConnection):
|
||||||
|
class Schema(LanceModel):
|
||||||
|
text: str
|
||||||
|
embedding: Vector(4)
|
||||||
|
|
||||||
|
table = mem_db.create_table("test_integer_embeddings", schema=Schema)
|
||||||
|
table.add(
|
||||||
|
[{"text": "foo", "embedding": [1, 2, 3, 4]}],
|
||||||
|
on_bad_vectors="drop",
|
||||||
|
)
|
||||||
|
|
||||||
|
assert table.to_arrow()["embedding"].to_pylist() == [[1.0, 2.0, 3.0, 4.0]]
|
||||||
|
|
||||||
|
|
||||||
|
def test_on_bad_vectors_does_not_handle_non_vector_fixed_size_lists(
|
||||||
|
mem_db: DBConnection,
|
||||||
|
):
|
||||||
|
schema = pa.schema(
|
||||||
|
[
|
||||||
|
pa.field("vector", pa.list_(pa.float32(), 4)),
|
||||||
|
pa.field("bbox", pa.list_(pa.float32(), 4)),
|
||||||
|
]
|
||||||
|
)
|
||||||
|
table = mem_db.create_table("test_bbox_schema", schema=schema)
|
||||||
|
|
||||||
|
with pytest.raises(RuntimeError, match="FixedSizeListType"):
|
||||||
|
table.add(
|
||||||
|
[{"vector": [1.0, 2.0, 3.0, 4.0], "bbox": [0.0, 1.0]}],
|
||||||
|
on_bad_vectors="drop",
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def test_on_bad_vectors_does_not_handle_custom_named_fixed_size_lists(
|
||||||
|
mem_db: DBConnection,
|
||||||
|
):
|
||||||
|
schema = pa.schema([pa.field("features", pa.list_(pa.float32(), 16))])
|
||||||
|
table = mem_db.create_table("test_custom_named_fixed_size_vector", schema=schema)
|
||||||
|
|
||||||
|
with pytest.raises(RuntimeError, match="FixedSizeListType"):
|
||||||
|
table.add(
|
||||||
|
[
|
||||||
|
{"features": []},
|
||||||
|
{"features": [0.1] * 16},
|
||||||
|
],
|
||||||
|
on_bad_vectors="drop",
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def test_on_bad_vectors_with_schema_list_vector_still_sanitizes(mem_db: DBConnection):
|
||||||
|
schema = pa.schema([pa.field("vector", pa.list_(pa.float32()))])
|
||||||
|
table = mem_db.create_table("test_schema_list_vector", schema=schema)
|
||||||
|
table.add(
|
||||||
|
[
|
||||||
|
{"vector": [1.0, 2.0]},
|
||||||
|
{"vector": [3.0]},
|
||||||
|
{"vector": [4.0, 5.0]},
|
||||||
|
],
|
||||||
|
on_bad_vectors="drop",
|
||||||
|
)
|
||||||
|
|
||||||
|
assert table.to_arrow()["vector"].to_pylist() == [[1.0, 2.0], [4.0, 5.0]]
|
||||||
|
|
||||||
|
|
||||||
|
def test_on_bad_vectors_handles_typed_custom_fixed_vectors_for_list_schema(
|
||||||
|
mem_db: DBConnection,
|
||||||
|
):
|
||||||
|
schema = pa.schema([pa.field("vec", pa.list_(pa.float32()))])
|
||||||
|
table = mem_db.create_table("test_typed_custom_fixed_vector", schema=schema)
|
||||||
|
data = pa.table(
|
||||||
|
{
|
||||||
|
"vec": pa.array(
|
||||||
|
[[float("nan")] * 16, [1.0] * 16],
|
||||||
|
type=pa.list_(pa.float32(), 16),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
table.add(data, on_bad_vectors="drop")
|
||||||
|
|
||||||
|
assert table.to_arrow()["vec"].to_pylist() == [[1.0] * 16]
|
||||||
|
|
||||||
|
|
||||||
|
def test_on_bad_vectors_fill_preserves_arrow_nested_vector_type(mem_db: DBConnection):
|
||||||
|
schema = pa.schema([pa.field("vector", pa.list_(pa.float32()))])
|
||||||
|
table = mem_db.create_table("test_fill_arrow_nested_type", schema=schema)
|
||||||
|
data = pa.table(
|
||||||
|
{
|
||||||
|
"vector": pa.array(
|
||||||
|
[[1.0, 2.0], [float("nan"), 3.0]],
|
||||||
|
type=pa.list_(pa.float32(), 2),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
table.add(
|
||||||
|
data,
|
||||||
|
on_bad_vectors="fill",
|
||||||
|
fill_value=0.0,
|
||||||
|
)
|
||||||
|
|
||||||
|
assert table.to_arrow()["vector"].to_pylist() == [[1.0, 2.0], [0.0, 0.0]]
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
("table_name", "batch1", "expected"),
|
||||||
|
[
|
||||||
|
(
|
||||||
|
"test_schema_list_vector_empty_prefix",
|
||||||
|
pa.record_batch({"vector": [[], []]}),
|
||||||
|
[[], [], [1.0, 2.0], [3.0, 4.0]],
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"test_schema_list_vector_all_bad_prefix",
|
||||||
|
pa.record_batch({"vector": [[float("nan")] * 3, [float("nan")] * 3]}),
|
||||||
|
[[1.0, 2.0], [3.0, 4.0]],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
def test_on_bad_vectors_with_schema_list_vector_ignores_invalid_prefix_batches(
|
||||||
|
mem_db: DBConnection,
|
||||||
|
table_name: str,
|
||||||
|
batch1: pa.RecordBatch,
|
||||||
|
expected: list,
|
||||||
|
):
|
||||||
|
schema = pa.schema([pa.field("vector", pa.list_(pa.float32()))])
|
||||||
|
table = mem_db.create_table(table_name, schema=schema)
|
||||||
|
batch2 = pa.record_batch({"vector": [[1.0, 2.0], [3.0, 4.0]]})
|
||||||
|
reader = pa.RecordBatchReader.from_batches(batch1.schema, [batch1, batch2])
|
||||||
|
|
||||||
|
table.add(reader, on_bad_vectors="drop")
|
||||||
|
|
||||||
|
assert table.to_arrow()["vector"].to_pylist() == expected
|
||||||
|
|
||||||
|
|
||||||
|
def test_on_bad_vectors_with_multiple_vectors_locks_dim_after_final_drop(
|
||||||
|
mem_db: DBConnection,
|
||||||
|
):
|
||||||
|
registry = EmbeddingFunctionRegistry.get_instance()
|
||||||
|
func = MockTextEmbeddingFunction.create()
|
||||||
|
metadata = registry.get_table_metadata(
|
||||||
|
[
|
||||||
|
EmbeddingFunctionConfig(
|
||||||
|
source_column="text1", vector_column="vec1", function=func
|
||||||
|
),
|
||||||
|
EmbeddingFunctionConfig(
|
||||||
|
source_column="text2", vector_column="vec2", function=func
|
||||||
|
),
|
||||||
|
]
|
||||||
|
)
|
||||||
|
schema = pa.schema(
|
||||||
|
[
|
||||||
|
pa.field("vec1", pa.list_(pa.float32())),
|
||||||
|
pa.field("vec2", pa.list_(pa.float32())),
|
||||||
|
],
|
||||||
|
metadata=metadata,
|
||||||
|
)
|
||||||
|
table = mem_db.create_table("test_multi_vector_dim_lock", schema=schema)
|
||||||
|
batch1 = pa.record_batch(
|
||||||
|
{
|
||||||
|
"vec1": [[1.0, 2.0, 3.0], [10.0, 11.0]],
|
||||||
|
"vec2": [[float("nan"), 0.0], [5.0, 6.0]],
|
||||||
|
}
|
||||||
|
)
|
||||||
|
batch2 = pa.record_batch(
|
||||||
|
{
|
||||||
|
"vec1": [[20.0, 21.0], [30.0, 31.0]],
|
||||||
|
"vec2": [[7.0, 8.0], [9.0, 10.0]],
|
||||||
|
}
|
||||||
|
)
|
||||||
|
reader = pa.RecordBatchReader.from_batches(batch1.schema, [batch1, batch2])
|
||||||
|
|
||||||
|
table.add(reader, on_bad_vectors="drop")
|
||||||
|
|
||||||
|
data = table.to_arrow()
|
||||||
|
assert data["vec1"].to_pylist() == [[10.0, 11.0], [20.0, 21.0], [30.0, 31.0]]
|
||||||
|
assert data["vec2"].to_pylist() == [[5.0, 6.0], [7.0, 8.0], [9.0, 10.0]]
|
||||||
|
|
||||||
|
|
||||||
|
def test_on_bad_vectors_does_not_handle_non_vector_list_columns(mem_db: DBConnection):
|
||||||
|
schema = pa.schema([pa.field("embedding_history", pa.list_(pa.float32()))])
|
||||||
|
table = mem_db.create_table("test_non_vector_list_schema", schema=schema)
|
||||||
|
table.add(
|
||||||
|
[
|
||||||
|
{"embedding_history": [1.0, 2.0]},
|
||||||
|
{"embedding_history": [3.0]},
|
||||||
|
],
|
||||||
|
on_bad_vectors="drop",
|
||||||
|
)
|
||||||
|
|
||||||
|
assert table.to_arrow()["embedding_history"].to_pylist() == [
|
||||||
|
[1.0, 2.0],
|
||||||
|
[3.0],
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
def test_on_bad_vectors_all_null_schema_vector_batches_do_not_crash(
|
||||||
|
mem_db: DBConnection,
|
||||||
|
):
|
||||||
|
schema = pa.schema([pa.field("vector", pa.list_(pa.float32(), 2), nullable=True)])
|
||||||
|
table = mem_db.create_table("test_all_null_vector_batch", schema=schema)
|
||||||
|
|
||||||
|
table.add([{"vector": None}], on_bad_vectors="drop")
|
||||||
|
|
||||||
|
assert table.to_arrow()["vector"].to_pylist() == [None]
|
||||||
|
|
||||||
|
|
||||||
def test_restore(mem_db: DBConnection):
|
def test_restore(mem_db: DBConnection):
|
||||||
table = mem_db.create_table(
|
table = mem_db.create_table(
|
||||||
"my_table",
|
"my_table",
|
||||||
|
|||||||
@@ -15,8 +15,10 @@ from lancedb.table import (
|
|||||||
_cast_to_target_schema,
|
_cast_to_target_schema,
|
||||||
_handle_bad_vectors,
|
_handle_bad_vectors,
|
||||||
_into_pyarrow_reader,
|
_into_pyarrow_reader,
|
||||||
_sanitize_data,
|
|
||||||
_infer_target_schema,
|
_infer_target_schema,
|
||||||
|
_merge_metadata,
|
||||||
|
_sanitize_data,
|
||||||
|
sanitize_create_table,
|
||||||
)
|
)
|
||||||
import pyarrow as pa
|
import pyarrow as pa
|
||||||
import pandas as pd
|
import pandas as pd
|
||||||
@@ -304,6 +306,117 @@ def test_handle_bad_vectors_noop():
|
|||||||
assert output["vector"] == vector
|
assert output["vector"] == vector
|
||||||
|
|
||||||
|
|
||||||
|
def test_handle_bad_vectors_updates_reader_schema_for_target_schema():
|
||||||
|
data = pa.table({"vector": [[1, 2, 3, 4]]})
|
||||||
|
target_schema = pa.schema([pa.field("vector", pa.list_(pa.float32(), 4))])
|
||||||
|
|
||||||
|
output = _handle_bad_vectors(
|
||||||
|
data.to_reader(),
|
||||||
|
on_bad_vectors="drop",
|
||||||
|
target_schema=target_schema,
|
||||||
|
)
|
||||||
|
|
||||||
|
assert output.schema == pa.schema([pa.field("vector", pa.list_(pa.float32()))])
|
||||||
|
assert output.read_all()["vector"].to_pylist() == [[1.0, 2.0, 3.0, 4.0]]
|
||||||
|
|
||||||
|
|
||||||
|
def test_sanitize_data_keeps_target_field_metadata():
|
||||||
|
source_field = pa.field(
|
||||||
|
"vector",
|
||||||
|
pa.list_(pa.float32(), 2),
|
||||||
|
metadata={b"source": b"drop-me"},
|
||||||
|
)
|
||||||
|
target_field = pa.field(
|
||||||
|
"vector",
|
||||||
|
pa.list_(pa.float32(), 2),
|
||||||
|
metadata={b"target": b"keep-me"},
|
||||||
|
)
|
||||||
|
data = pa.table(
|
||||||
|
{"vector": pa.array([[1.0, 2.0]], type=pa.list_(pa.float32(), 2))},
|
||||||
|
schema=pa.schema([source_field]),
|
||||||
|
)
|
||||||
|
|
||||||
|
output = _sanitize_data(
|
||||||
|
data,
|
||||||
|
target_schema=pa.schema([target_field]),
|
||||||
|
on_bad_vectors="drop",
|
||||||
|
).read_all()
|
||||||
|
|
||||||
|
assert output.schema.field("vector").metadata == {b"target": b"keep-me"}
|
||||||
|
|
||||||
|
|
||||||
|
def test_sanitize_data_uses_separate_embedding_metadata_for_bad_vectors():
|
||||||
|
registry = EmbeddingFunctionRegistry.get_instance()
|
||||||
|
conf = EmbeddingFunctionConfig(
|
||||||
|
source_column="text",
|
||||||
|
vector_column="custom_vector",
|
||||||
|
function=MockTextEmbeddingFunction.create(),
|
||||||
|
)
|
||||||
|
metadata = registry.get_table_metadata([conf])
|
||||||
|
schema = pa.schema(
|
||||||
|
{
|
||||||
|
"text": pa.string(),
|
||||||
|
"custom_vector": pa.list_(pa.float32(), 10),
|
||||||
|
},
|
||||||
|
metadata={b"note": b"keep-me"},
|
||||||
|
)
|
||||||
|
data = pa.table(
|
||||||
|
{
|
||||||
|
"text": ["bad", "good"],
|
||||||
|
"custom_vector": [[1.0] * 9, [2.0] * 10],
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
output = _sanitize_data(
|
||||||
|
data,
|
||||||
|
target_schema=schema,
|
||||||
|
metadata=metadata,
|
||||||
|
on_bad_vectors="drop",
|
||||||
|
).read_all()
|
||||||
|
|
||||||
|
assert output["text"].to_pylist() == ["good"]
|
||||||
|
assert output.schema.metadata[b"note"] == b"keep-me"
|
||||||
|
assert b"embedding_functions" in output.schema.metadata
|
||||||
|
|
||||||
|
|
||||||
|
def test_sanitize_create_table_merges_and_overrides_embedding_metadata():
|
||||||
|
registry = EmbeddingFunctionRegistry.get_instance()
|
||||||
|
old_conf = EmbeddingFunctionConfig(
|
||||||
|
source_column="text",
|
||||||
|
vector_column="old_vector",
|
||||||
|
function=MockTextEmbeddingFunction.create(),
|
||||||
|
)
|
||||||
|
new_conf = EmbeddingFunctionConfig(
|
||||||
|
source_column="text",
|
||||||
|
vector_column="custom_vector",
|
||||||
|
function=MockTextEmbeddingFunction.create(),
|
||||||
|
)
|
||||||
|
metadata = registry.get_table_metadata([new_conf])
|
||||||
|
schema = pa.schema(
|
||||||
|
{
|
||||||
|
"text": pa.string(),
|
||||||
|
"custom_vector": pa.list_(pa.float32(), 10),
|
||||||
|
},
|
||||||
|
metadata=_merge_metadata(
|
||||||
|
{b"note": b"keep-me"},
|
||||||
|
registry.get_table_metadata([old_conf]),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
data, schema = sanitize_create_table(
|
||||||
|
pa.table({"text": ["good"]}),
|
||||||
|
schema,
|
||||||
|
metadata=metadata,
|
||||||
|
on_bad_vectors="drop",
|
||||||
|
)
|
||||||
|
|
||||||
|
assert schema.metadata[b"note"] == b"keep-me"
|
||||||
|
assert b"embedding_functions" in schema.metadata
|
||||||
|
assert data.schema.metadata[b"note"] == b"keep-me"
|
||||||
|
funcs = EmbeddingFunctionRegistry.get_instance().parse_functions(schema.metadata)
|
||||||
|
assert set(funcs.keys()) == {"custom_vector"}
|
||||||
|
|
||||||
|
|
||||||
class TestModel(lancedb.pydantic.LanceModel):
|
class TestModel(lancedb.pydantic.LanceModel):
|
||||||
a: Optional[int]
|
a: Optional[int]
|
||||||
b: Optional[int]
|
b: Optional[int]
|
||||||
|
|||||||
@@ -547,6 +547,7 @@ pub struct PyClientConfig {
|
|||||||
id_delimiter: Option<String>,
|
id_delimiter: Option<String>,
|
||||||
tls_config: Option<PyClientTlsConfig>,
|
tls_config: Option<PyClientTlsConfig>,
|
||||||
header_provider: Option<Py<PyAny>>,
|
header_provider: Option<Py<PyAny>>,
|
||||||
|
user_id: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(FromPyObject)]
|
#[derive(FromPyObject)]
|
||||||
@@ -631,6 +632,7 @@ impl From<PyClientConfig> for lancedb::remote::ClientConfig {
|
|||||||
id_delimiter: value.id_delimiter,
|
id_delimiter: value.id_delimiter,
|
||||||
tls_config: value.tls_config.map(Into::into),
|
tls_config: value.tls_config.map(Into::into),
|
||||||
header_provider,
|
header_provider,
|
||||||
|
user_id: value.user_id,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-1
@@ -1,2 +1,2 @@
|
|||||||
[toolchain]
|
[toolchain]
|
||||||
channel = "1.91.0"
|
channel = "1.94.0"
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "lancedb"
|
name = "lancedb"
|
||||||
version = "0.28.0-beta.0"
|
version = "0.28.0-beta.5"
|
||||||
edition.workspace = true
|
edition.workspace = true
|
||||||
description = "LanceDB: A serverless, low-latency vector database for AI applications"
|
description = "LanceDB: A serverless, low-latency vector database for AI applications"
|
||||||
license.workspace = true
|
license.workspace = true
|
||||||
|
|||||||
@@ -171,7 +171,7 @@ impl OpenTableBuilder {
|
|||||||
/// Options already set on the connection will be inherited by the table,
|
/// Options already set on the connection will be inherited by the table,
|
||||||
/// but can be overridden here.
|
/// but can be overridden here.
|
||||||
///
|
///
|
||||||
/// See available options at <https://lancedb.com/docs/storage/>
|
/// See available options at <https://docs.lancedb.com/storage/>
|
||||||
pub fn storage_option(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
|
pub fn storage_option(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
|
||||||
let store_params = self
|
let store_params = self
|
||||||
.request
|
.request
|
||||||
@@ -188,7 +188,7 @@ impl OpenTableBuilder {
|
|||||||
/// Options already set on the connection will be inherited by the table,
|
/// Options already set on the connection will be inherited by the table,
|
||||||
/// but can be overridden here.
|
/// but can be overridden here.
|
||||||
///
|
///
|
||||||
/// See available options at <https://lancedb.com/docs/storage/>
|
/// See available options at <https://docs.lancedb.com/storage/>
|
||||||
pub fn storage_options(
|
pub fn storage_options(
|
||||||
mut self,
|
mut self,
|
||||||
pairs: impl IntoIterator<Item = (impl Into<String>, impl Into<String>)>,
|
pairs: impl IntoIterator<Item = (impl Into<String>, impl Into<String>)>,
|
||||||
@@ -582,6 +582,14 @@ pub struct ConnectRequest {
|
|||||||
/// Database specific options
|
/// Database specific options
|
||||||
pub options: HashMap<String, String>,
|
pub options: HashMap<String, String>,
|
||||||
|
|
||||||
|
/// Extra properties for the equivalent namespace client.
|
||||||
|
///
|
||||||
|
/// For a local [`ListingDatabase`], these are merged into the backing
|
||||||
|
/// `DirectoryNamespace` properties. This is useful for namespace-specific
|
||||||
|
/// settings such as `table_version_tracking_enabled` that are distinct from
|
||||||
|
/// storage options.
|
||||||
|
pub namespace_client_properties: HashMap<String, String>,
|
||||||
|
|
||||||
/// The interval at which to check for updates from other processes.
|
/// The interval at which to check for updates from other processes.
|
||||||
///
|
///
|
||||||
/// If None, then consistency is not checked. For performance
|
/// If None, then consistency is not checked. For performance
|
||||||
@@ -621,6 +629,7 @@ impl ConnectBuilder {
|
|||||||
client_config: Default::default(),
|
client_config: Default::default(),
|
||||||
read_consistency_interval: None,
|
read_consistency_interval: None,
|
||||||
options: HashMap::new(),
|
options: HashMap::new(),
|
||||||
|
namespace_client_properties: HashMap::new(),
|
||||||
session: None,
|
session: None,
|
||||||
},
|
},
|
||||||
embedding_registry: None,
|
embedding_registry: None,
|
||||||
@@ -738,7 +747,7 @@ impl ConnectBuilder {
|
|||||||
|
|
||||||
/// Set an option for the storage layer.
|
/// Set an option for the storage layer.
|
||||||
///
|
///
|
||||||
/// See available options at <https://lancedb.com/docs/storage/>
|
/// See available options at <https://docs.lancedb.com/storage/>
|
||||||
pub fn storage_option(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
|
pub fn storage_option(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
|
||||||
self.request.options.insert(key.into(), value.into());
|
self.request.options.insert(key.into(), value.into());
|
||||||
self
|
self
|
||||||
@@ -746,7 +755,7 @@ impl ConnectBuilder {
|
|||||||
|
|
||||||
/// Set multiple options for the storage layer.
|
/// Set multiple options for the storage layer.
|
||||||
///
|
///
|
||||||
/// See available options at <https://lancedb.com/docs/storage/>
|
/// See available options at <https://docs.lancedb.com/storage/>
|
||||||
pub fn storage_options(
|
pub fn storage_options(
|
||||||
mut self,
|
mut self,
|
||||||
pairs: impl IntoIterator<Item = (impl Into<String>, impl Into<String>)>,
|
pairs: impl IntoIterator<Item = (impl Into<String>, impl Into<String>)>,
|
||||||
@@ -757,6 +766,31 @@ impl ConnectBuilder {
|
|||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Set an additional property for the equivalent namespace client.
|
||||||
|
pub fn namespace_client_property(
|
||||||
|
mut self,
|
||||||
|
key: impl Into<String>,
|
||||||
|
value: impl Into<String>,
|
||||||
|
) -> Self {
|
||||||
|
self.request
|
||||||
|
.namespace_client_properties
|
||||||
|
.insert(key.into(), value.into());
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Set multiple additional properties for the equivalent namespace client.
|
||||||
|
pub fn namespace_client_properties(
|
||||||
|
mut self,
|
||||||
|
pairs: impl IntoIterator<Item = (impl Into<String>, impl Into<String>)>,
|
||||||
|
) -> Self {
|
||||||
|
for (key, value) in pairs {
|
||||||
|
self.request
|
||||||
|
.namespace_client_properties
|
||||||
|
.insert(key.into(), value.into());
|
||||||
|
}
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
/// The interval at which to check for updates from other processes. This
|
/// The interval at which to check for updates from other processes. This
|
||||||
/// only affects LanceDB OSS.
|
/// only affects LanceDB OSS.
|
||||||
///
|
///
|
||||||
@@ -893,6 +927,7 @@ pub struct ConnectNamespaceBuilder {
|
|||||||
ns_impl: String,
|
ns_impl: String,
|
||||||
properties: HashMap<String, String>,
|
properties: HashMap<String, String>,
|
||||||
storage_options: HashMap<String, String>,
|
storage_options: HashMap<String, String>,
|
||||||
|
namespace_client_properties: HashMap<String, String>,
|
||||||
read_consistency_interval: Option<std::time::Duration>,
|
read_consistency_interval: Option<std::time::Duration>,
|
||||||
embedding_registry: Option<Arc<dyn EmbeddingRegistry>>,
|
embedding_registry: Option<Arc<dyn EmbeddingRegistry>>,
|
||||||
session: Option<Arc<lance::session::Session>>,
|
session: Option<Arc<lance::session::Session>>,
|
||||||
@@ -905,6 +940,7 @@ impl ConnectNamespaceBuilder {
|
|||||||
ns_impl: ns_impl.to_string(),
|
ns_impl: ns_impl.to_string(),
|
||||||
properties,
|
properties,
|
||||||
storage_options: HashMap::new(),
|
storage_options: HashMap::new(),
|
||||||
|
namespace_client_properties: HashMap::new(),
|
||||||
read_consistency_interval: None,
|
read_consistency_interval: None,
|
||||||
embedding_registry: None,
|
embedding_registry: None,
|
||||||
session: None,
|
session: None,
|
||||||
@@ -914,7 +950,7 @@ impl ConnectNamespaceBuilder {
|
|||||||
|
|
||||||
/// Set an option for the storage layer.
|
/// Set an option for the storage layer.
|
||||||
///
|
///
|
||||||
/// See available options at <https://lancedb.com/docs/storage/>
|
/// See available options at <https://docs.lancedb.com/storage/>
|
||||||
pub fn storage_option(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
|
pub fn storage_option(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
|
||||||
self.storage_options.insert(key.into(), value.into());
|
self.storage_options.insert(key.into(), value.into());
|
||||||
self
|
self
|
||||||
@@ -922,7 +958,7 @@ impl ConnectNamespaceBuilder {
|
|||||||
|
|
||||||
/// Set multiple options for the storage layer.
|
/// Set multiple options for the storage layer.
|
||||||
///
|
///
|
||||||
/// See available options at <https://lancedb.com/docs/storage/>
|
/// See available options at <https://docs.lancedb.com/storage/>
|
||||||
pub fn storage_options(
|
pub fn storage_options(
|
||||||
mut self,
|
mut self,
|
||||||
pairs: impl IntoIterator<Item = (impl Into<String>, impl Into<String>)>,
|
pairs: impl IntoIterator<Item = (impl Into<String>, impl Into<String>)>,
|
||||||
@@ -933,6 +969,29 @@ impl ConnectNamespaceBuilder {
|
|||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Set an additional namespace client property.
|
||||||
|
pub fn namespace_client_property(
|
||||||
|
mut self,
|
||||||
|
key: impl Into<String>,
|
||||||
|
value: impl Into<String>,
|
||||||
|
) -> Self {
|
||||||
|
self.namespace_client_properties
|
||||||
|
.insert(key.into(), value.into());
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Set multiple additional namespace client properties.
|
||||||
|
pub fn namespace_client_properties(
|
||||||
|
mut self,
|
||||||
|
pairs: impl IntoIterator<Item = (impl Into<String>, impl Into<String>)>,
|
||||||
|
) -> Self {
|
||||||
|
for (key, value) in pairs {
|
||||||
|
self.namespace_client_properties
|
||||||
|
.insert(key.into(), value.into());
|
||||||
|
}
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
/// The interval at which to check for updates from other processes.
|
/// The interval at which to check for updates from other processes.
|
||||||
///
|
///
|
||||||
/// If left unset, consistency is not checked. For maximum read
|
/// If left unset, consistency is not checked. For maximum read
|
||||||
@@ -994,10 +1053,13 @@ impl ConnectNamespaceBuilder {
|
|||||||
pub async fn execute(self) -> Result<Connection> {
|
pub async fn execute(self) -> Result<Connection> {
|
||||||
use crate::database::namespace::LanceNamespaceDatabase;
|
use crate::database::namespace::LanceNamespaceDatabase;
|
||||||
|
|
||||||
|
let mut properties = self.properties;
|
||||||
|
properties.extend(self.namespace_client_properties);
|
||||||
|
|
||||||
let internal = Arc::new(
|
let internal = Arc::new(
|
||||||
LanceNamespaceDatabase::connect(
|
LanceNamespaceDatabase::connect(
|
||||||
&self.ns_impl,
|
&self.ns_impl,
|
||||||
self.properties,
|
properties,
|
||||||
self.storage_options,
|
self.storage_options,
|
||||||
self.read_consistency_interval,
|
self.read_consistency_interval,
|
||||||
self.session,
|
self.session,
|
||||||
@@ -1117,6 +1179,31 @@ mod tests {
|
|||||||
assert_eq!(db.uri(), relative_uri.to_str().unwrap().to_string());
|
assert_eq!(db.uri(), relative_uri.to_str().unwrap().to_string());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_connect_with_namespace_client_properties() {
|
||||||
|
let tmp_dir = tempdir().unwrap();
|
||||||
|
let uri = tmp_dir.path().to_str().unwrap();
|
||||||
|
|
||||||
|
let db = connect(uri)
|
||||||
|
.namespace_client_property("table_version_tracking_enabled", "true")
|
||||||
|
.namespace_client_property("manifest_enabled", "true")
|
||||||
|
.execute()
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let (ns_impl, properties) = db.namespace_client_config().await.unwrap();
|
||||||
|
assert_eq!(ns_impl, "dir");
|
||||||
|
assert_eq!(properties.get("root"), Some(&uri.to_string()));
|
||||||
|
assert_eq!(
|
||||||
|
properties.get("table_version_tracking_enabled"),
|
||||||
|
Some(&"true".to_string())
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
properties.get("manifest_enabled"),
|
||||||
|
Some(&"true".to_string())
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn test_table_names() {
|
async fn test_table_names() {
|
||||||
let tc = new_test_connection().await.unwrap();
|
let tc = new_test_connection().await.unwrap();
|
||||||
|
|||||||
@@ -55,7 +55,7 @@ impl CreateTableBuilder {
|
|||||||
/// Options already set on the connection will be inherited by the table,
|
/// Options already set on the connection will be inherited by the table,
|
||||||
/// but can be overridden here.
|
/// but can be overridden here.
|
||||||
///
|
///
|
||||||
/// See available options at <https://lancedb.com/docs/storage/>
|
/// See available options at <https://docs.lancedb.com/storage/>
|
||||||
pub fn storage_option(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
|
pub fn storage_option(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
|
||||||
let store_params = self
|
let store_params = self
|
||||||
.request
|
.request
|
||||||
@@ -73,7 +73,7 @@ impl CreateTableBuilder {
|
|||||||
/// Options already set on the connection will be inherited by the table,
|
/// Options already set on the connection will be inherited by the table,
|
||||||
/// but can be overridden here.
|
/// but can be overridden here.
|
||||||
///
|
///
|
||||||
/// See available options at <https://lancedb.com/docs/storage/>
|
/// See available options at <https://docs.lancedb.com/storage/>
|
||||||
pub fn storage_options(
|
pub fn storage_options(
|
||||||
mut self,
|
mut self,
|
||||||
pairs: impl IntoIterator<Item = (impl Into<String>, impl Into<String>)>,
|
pairs: impl IntoIterator<Item = (impl Into<String>, impl Into<String>)>,
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ use snafu::ResultExt;
|
|||||||
|
|
||||||
use crate::connection::ConnectRequest;
|
use crate::connection::ConnectRequest;
|
||||||
use crate::database::ReadConsistency;
|
use crate::database::ReadConsistency;
|
||||||
|
use crate::database::namespace::LanceNamespaceDatabase;
|
||||||
use crate::error::{CreateDirSnafu, Error, Result};
|
use crate::error::{CreateDirSnafu, Error, Result};
|
||||||
use crate::io::object_store::MirroringObjectStoreWrapper;
|
use crate::io::object_store::MirroringObjectStoreWrapper;
|
||||||
use crate::table::NativeTable;
|
use crate::table::NativeTable;
|
||||||
@@ -73,7 +74,7 @@ pub struct ListingDatabaseOptions {
|
|||||||
/// These are used to create/list tables and they are inherited by all tables
|
/// These are used to create/list tables and they are inherited by all tables
|
||||||
/// opened by this database.
|
/// opened by this database.
|
||||||
///
|
///
|
||||||
/// See available options at <https://lancedb.com/docs/storage/>
|
/// See available options at <https://docs.lancedb.com/storage/>
|
||||||
pub storage_options: HashMap<String, String>,
|
pub storage_options: HashMap<String, String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -185,7 +186,7 @@ impl ListingDatabaseOptionsBuilder {
|
|||||||
|
|
||||||
/// Set an option for the storage layer.
|
/// Set an option for the storage layer.
|
||||||
///
|
///
|
||||||
/// See available options at <https://lancedb.com/docs/storage/>
|
/// See available options at <https://docs.lancedb.com/storage/>
|
||||||
pub fn storage_option(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
|
pub fn storage_option(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
|
||||||
self.options
|
self.options
|
||||||
.storage_options
|
.storage_options
|
||||||
@@ -195,7 +196,7 @@ impl ListingDatabaseOptionsBuilder {
|
|||||||
|
|
||||||
/// Set multiple options for the storage layer.
|
/// Set multiple options for the storage layer.
|
||||||
///
|
///
|
||||||
/// See available options at <https://lancedb.com/docs/storage/>
|
/// See available options at <https://docs.lancedb.com/storage/>
|
||||||
pub fn storage_options(
|
pub fn storage_options(
|
||||||
mut self,
|
mut self,
|
||||||
pairs: impl IntoIterator<Item = (impl Into<String>, impl Into<String>)>,
|
pairs: impl IntoIterator<Item = (impl Into<String>, impl Into<String>)>,
|
||||||
@@ -255,6 +256,9 @@ pub struct ListingDatabase {
|
|||||||
|
|
||||||
// Session for object stores and caching
|
// Session for object stores and caching
|
||||||
session: Arc<lance::session::Session>,
|
session: Arc<lance::session::Session>,
|
||||||
|
|
||||||
|
// Namespace-backed database for child namespace operations
|
||||||
|
namespace_database: Arc<LanceNamespaceDatabase>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl std::fmt::Display for ListingDatabase {
|
impl std::fmt::Display for ListingDatabase {
|
||||||
@@ -281,6 +285,44 @@ const MIRRORED_STORE: &str = "mirroredStore";
|
|||||||
|
|
||||||
/// A connection to LanceDB
|
/// A connection to LanceDB
|
||||||
impl ListingDatabase {
|
impl ListingDatabase {
|
||||||
|
fn build_namespace_client_properties(
|
||||||
|
uri: &str,
|
||||||
|
storage_options: &HashMap<String, String>,
|
||||||
|
namespace_client_properties: HashMap<String, String>,
|
||||||
|
) -> HashMap<String, String> {
|
||||||
|
let mut properties = namespace_client_properties;
|
||||||
|
properties.insert("root".to_string(), uri.to_string());
|
||||||
|
for (key, value) in storage_options {
|
||||||
|
properties.insert(format!("storage.{}", key), value.clone());
|
||||||
|
}
|
||||||
|
properties
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn connect_namespace_database(
|
||||||
|
uri: &str,
|
||||||
|
storage_options: HashMap<String, String>,
|
||||||
|
namespace_client_properties: HashMap<String, String>,
|
||||||
|
read_consistency_interval: Option<std::time::Duration>,
|
||||||
|
session: Arc<lance::session::Session>,
|
||||||
|
) -> Result<Arc<LanceNamespaceDatabase>> {
|
||||||
|
let ns_properties = Self::build_namespace_client_properties(
|
||||||
|
uri,
|
||||||
|
&storage_options,
|
||||||
|
namespace_client_properties,
|
||||||
|
);
|
||||||
|
Ok(Arc::new(
|
||||||
|
LanceNamespaceDatabase::connect(
|
||||||
|
"dir",
|
||||||
|
ns_properties,
|
||||||
|
storage_options,
|
||||||
|
read_consistency_interval,
|
||||||
|
Some(session),
|
||||||
|
HashSet::new(),
|
||||||
|
)
|
||||||
|
.await?,
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
/// Connect to a listing database
|
/// Connect to a listing database
|
||||||
///
|
///
|
||||||
/// The URI should be a path to a directory where the tables are stored.
|
/// The URI should be a path to a directory where the tables are stored.
|
||||||
@@ -300,6 +342,7 @@ impl ListingDatabase {
|
|||||||
uri,
|
uri,
|
||||||
request.read_consistency_interval,
|
request.read_consistency_interval,
|
||||||
options.new_table_config,
|
options.new_table_config,
|
||||||
|
request.namespace_client_properties.clone(),
|
||||||
request.session.clone(),
|
request.session.clone(),
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
@@ -387,6 +430,15 @@ impl ListingDatabase {
|
|||||||
None => None,
|
None => None,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let namespace_database = Self::connect_namespace_database(
|
||||||
|
&table_base_uri,
|
||||||
|
options.storage_options.clone(),
|
||||||
|
request.namespace_client_properties.clone(),
|
||||||
|
request.read_consistency_interval,
|
||||||
|
session.clone(),
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
uri: table_base_uri,
|
uri: table_base_uri,
|
||||||
query_string,
|
query_string,
|
||||||
@@ -398,6 +450,7 @@ impl ListingDatabase {
|
|||||||
storage_options_provider: None,
|
storage_options_provider: None,
|
||||||
new_table_config: options.new_table_config,
|
new_table_config: options.new_table_config,
|
||||||
session,
|
session,
|
||||||
|
namespace_database,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
Err(_) => {
|
Err(_) => {
|
||||||
@@ -405,6 +458,7 @@ impl ListingDatabase {
|
|||||||
uri,
|
uri,
|
||||||
request.read_consistency_interval,
|
request.read_consistency_interval,
|
||||||
options.new_table_config,
|
options.new_table_config,
|
||||||
|
request.namespace_client_properties.clone(),
|
||||||
request.session.clone(),
|
request.session.clone(),
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
@@ -416,6 +470,7 @@ impl ListingDatabase {
|
|||||||
path: &str,
|
path: &str,
|
||||||
read_consistency_interval: Option<std::time::Duration>,
|
read_consistency_interval: Option<std::time::Duration>,
|
||||||
new_table_config: NewTableConfig,
|
new_table_config: NewTableConfig,
|
||||||
|
namespace_client_properties: HashMap<String, String>,
|
||||||
session: Option<Arc<lance::session::Session>>,
|
session: Option<Arc<lance::session::Session>>,
|
||||||
) -> Result<Self> {
|
) -> Result<Self> {
|
||||||
let session = session.unwrap_or_else(|| Arc::new(lance::session::Session::default()));
|
let session = session.unwrap_or_else(|| Arc::new(lance::session::Session::default()));
|
||||||
@@ -429,6 +484,15 @@ impl ListingDatabase {
|
|||||||
Self::try_create_dir(path).context(CreateDirSnafu { path })?;
|
Self::try_create_dir(path).context(CreateDirSnafu { path })?;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let namespace_database = Self::connect_namespace_database(
|
||||||
|
path,
|
||||||
|
HashMap::new(),
|
||||||
|
namespace_client_properties,
|
||||||
|
read_consistency_interval,
|
||||||
|
session.clone(),
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
uri: path.to_string(),
|
uri: path.to_string(),
|
||||||
query_string: None,
|
query_string: None,
|
||||||
@@ -440,6 +504,7 @@ impl ListingDatabase {
|
|||||||
storage_options_provider: None,
|
storage_options_provider: None,
|
||||||
new_table_config,
|
new_table_config,
|
||||||
session,
|
session,
|
||||||
|
namespace_database,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -497,6 +562,10 @@ impl ListingDatabase {
|
|||||||
Ok(uri)
|
Ok(uri)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn namespace_database(&self) -> Arc<LanceNamespaceDatabase> {
|
||||||
|
self.namespace_database.clone()
|
||||||
|
}
|
||||||
|
|
||||||
async fn drop_tables(&self, names: Vec<String>) -> Result<()> {
|
async fn drop_tables(&self, names: Vec<String>) -> Result<()> {
|
||||||
let object_store_params = ObjectStoreParams {
|
let object_store_params = ObjectStoreParams {
|
||||||
storage_options_accessor: if self.storage_options.is_empty() {
|
storage_options_accessor: if self.storage_options.is_empty() {
|
||||||
@@ -696,16 +765,7 @@ impl Database for ListingDatabase {
|
|||||||
&self,
|
&self,
|
||||||
request: ListNamespacesRequest,
|
request: ListNamespacesRequest,
|
||||||
) -> Result<ListNamespacesResponse> {
|
) -> Result<ListNamespacesResponse> {
|
||||||
if request.id.as_ref().map(|v| !v.is_empty()).unwrap_or(false) {
|
self.namespace_database().list_namespaces(request).await
|
||||||
return Err(Error::NotSupported {
|
|
||||||
message: "Namespace operations are not supported for listing database".into(),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(ListNamespacesResponse {
|
|
||||||
namespaces: Vec::new(),
|
|
||||||
page_token: None,
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn uri(&self) -> &str {
|
fn uri(&self) -> &str {
|
||||||
@@ -726,36 +786,26 @@ impl Database for ListingDatabase {
|
|||||||
|
|
||||||
async fn create_namespace(
|
async fn create_namespace(
|
||||||
&self,
|
&self,
|
||||||
_request: CreateNamespaceRequest,
|
request: CreateNamespaceRequest,
|
||||||
) -> Result<CreateNamespaceResponse> {
|
) -> Result<CreateNamespaceResponse> {
|
||||||
Err(Error::NotSupported {
|
self.namespace_database().create_namespace(request).await
|
||||||
message: "Namespace operations are not supported for listing database".into(),
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn drop_namespace(
|
async fn drop_namespace(&self, request: DropNamespaceRequest) -> Result<DropNamespaceResponse> {
|
||||||
&self,
|
self.namespace_database().drop_namespace(request).await
|
||||||
_request: DropNamespaceRequest,
|
|
||||||
) -> Result<DropNamespaceResponse> {
|
|
||||||
Err(Error::NotSupported {
|
|
||||||
message: "Namespace operations are not supported for listing database".into(),
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn describe_namespace(
|
async fn describe_namespace(
|
||||||
&self,
|
&self,
|
||||||
_request: DescribeNamespaceRequest,
|
request: DescribeNamespaceRequest,
|
||||||
) -> Result<DescribeNamespaceResponse> {
|
) -> Result<DescribeNamespaceResponse> {
|
||||||
Err(Error::NotSupported {
|
self.namespace_database().describe_namespace(request).await
|
||||||
message: "Namespace operations are not supported for listing database".into(),
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[allow(deprecated)]
|
||||||
async fn table_names(&self, request: TableNamesRequest) -> Result<Vec<String>> {
|
async fn table_names(&self, request: TableNamesRequest) -> Result<Vec<String>> {
|
||||||
if !request.namespace_path.is_empty() {
|
if !request.namespace_path.is_empty() {
|
||||||
return Err(Error::NotSupported {
|
return self.namespace_database().table_names(request).await;
|
||||||
message: "Namespace parameter is not supported for listing database. Only root namespace is supported.".into(),
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
let mut f = self
|
let mut f = self
|
||||||
.object_store
|
.object_store
|
||||||
@@ -788,9 +838,7 @@ impl Database for ListingDatabase {
|
|||||||
|
|
||||||
async fn list_tables(&self, request: ListTablesRequest) -> Result<ListTablesResponse> {
|
async fn list_tables(&self, request: ListTablesRequest) -> Result<ListTablesResponse> {
|
||||||
if request.id.as_ref().map(|v| !v.is_empty()).unwrap_or(false) {
|
if request.id.as_ref().map(|v| !v.is_empty()).unwrap_or(false) {
|
||||||
return Err(Error::NotSupported {
|
return self.namespace_database().list_tables(request).await;
|
||||||
message: "Namespace parameter is not supported for listing database. Only root namespace is supported.".into(),
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
let mut f = self
|
let mut f = self
|
||||||
.object_store
|
.object_store
|
||||||
@@ -838,11 +886,8 @@ impl Database for ListingDatabase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async fn create_table(&self, request: CreateTableRequest) -> Result<Arc<dyn BaseTable>> {
|
async fn create_table(&self, request: CreateTableRequest) -> Result<Arc<dyn BaseTable>> {
|
||||||
// When namespace is not empty, location must be provided
|
if !request.namespace_path.is_empty() {
|
||||||
if !request.namespace_path.is_empty() && request.location.is_none() {
|
return self.namespace_database().create_table(request).await;
|
||||||
return Err(Error::InvalidInput {
|
|
||||||
message: "Location must be provided when namespace is not empty".into(),
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
// Use provided location if available, otherwise derive from table name
|
// Use provided location if available, otherwise derive from table name
|
||||||
let table_uri = request
|
let table_uri = request
|
||||||
@@ -959,11 +1004,8 @@ impl Database for ListingDatabase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async fn open_table(&self, mut request: OpenTableRequest) -> Result<Arc<dyn BaseTable>> {
|
async fn open_table(&self, mut request: OpenTableRequest) -> Result<Arc<dyn BaseTable>> {
|
||||||
// When namespace is not empty, location must be provided
|
if !request.namespace_path.is_empty() {
|
||||||
if !request.namespace_path.is_empty() && request.location.is_none() {
|
return self.namespace_database().open_table(request).await;
|
||||||
return Err(Error::InvalidInput {
|
|
||||||
message: "Location must be provided when namespace is not empty".into(),
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
// Use provided location if available, otherwise derive from table name
|
// Use provided location if available, otherwise derive from table name
|
||||||
let table_uri = request
|
let table_uri = request
|
||||||
@@ -1059,9 +1101,10 @@ impl Database for ListingDatabase {
|
|||||||
|
|
||||||
async fn drop_table(&self, name: &str, namespace_path: &[String]) -> Result<()> {
|
async fn drop_table(&self, name: &str, namespace_path: &[String]) -> Result<()> {
|
||||||
if !namespace_path.is_empty() {
|
if !namespace_path.is_empty() {
|
||||||
return Err(Error::NotSupported {
|
return self
|
||||||
message: "Namespace parameter is not supported for listing database.".into(),
|
.namespace_database()
|
||||||
});
|
.drop_table(name, namespace_path)
|
||||||
|
.await;
|
||||||
}
|
}
|
||||||
self.drop_tables(vec![name.to_string()]).await
|
self.drop_tables(vec![name.to_string()]).await
|
||||||
}
|
}
|
||||||
@@ -1070,9 +1113,10 @@ impl Database for ListingDatabase {
|
|||||||
async fn drop_all_tables(&self, namespace_path: &[String]) -> Result<()> {
|
async fn drop_all_tables(&self, namespace_path: &[String]) -> Result<()> {
|
||||||
// Check if namespace parameter is provided
|
// Check if namespace parameter is provided
|
||||||
if !namespace_path.is_empty() {
|
if !namespace_path.is_empty() {
|
||||||
return Err(Error::NotSupported {
|
return self
|
||||||
message: "Namespace parameter is not supported for listing database.".into(),
|
.namespace_database()
|
||||||
});
|
.drop_all_tables(namespace_path)
|
||||||
|
.await;
|
||||||
}
|
}
|
||||||
let tables = self.table_names(TableNamesRequest::default()).await?;
|
let tables = self.table_names(TableNamesRequest::default()).await?;
|
||||||
self.drop_tables(tables).await
|
self.drop_tables(tables).await
|
||||||
@@ -1083,30 +1127,11 @@ impl Database for ListingDatabase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async fn namespace_client(&self) -> Result<Arc<dyn lance_namespace::LanceNamespace>> {
|
async fn namespace_client(&self) -> Result<Arc<dyn lance_namespace::LanceNamespace>> {
|
||||||
// Create a DirectoryNamespace pointing to the same root with the same storage options
|
self.namespace_database.namespace_client().await
|
||||||
let mut builder = lance_namespace_impls::DirectoryNamespaceBuilder::new(&self.uri);
|
|
||||||
|
|
||||||
// Add storage options
|
|
||||||
if !self.storage_options.is_empty() {
|
|
||||||
builder = builder.storage_options(self.storage_options.clone());
|
|
||||||
}
|
|
||||||
|
|
||||||
// Use the same session
|
|
||||||
builder = builder.session(self.session.clone());
|
|
||||||
|
|
||||||
let namespace = builder.build().await.map_err(|e| Error::Runtime {
|
|
||||||
message: format!("Failed to create namespace client: {}", e),
|
|
||||||
})?;
|
|
||||||
Ok(Arc::new(namespace) as Arc<dyn lance_namespace::LanceNamespace>)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn namespace_client_config(&self) -> Result<(String, HashMap<String, String>)> {
|
async fn namespace_client_config(&self) -> Result<(String, HashMap<String, String>)> {
|
||||||
let mut properties = HashMap::new();
|
self.namespace_database.namespace_client_config().await
|
||||||
properties.insert("root".to_string(), self.uri.clone());
|
|
||||||
for (key, value) in &self.storage_options {
|
|
||||||
properties.insert(format!("storage.{}", key), value.clone());
|
|
||||||
}
|
|
||||||
Ok(("dir".to_string(), properties))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1132,6 +1157,7 @@ mod tests {
|
|||||||
#[cfg(feature = "remote")]
|
#[cfg(feature = "remote")]
|
||||||
client_config: Default::default(),
|
client_config: Default::default(),
|
||||||
options: Default::default(),
|
options: Default::default(),
|
||||||
|
namespace_client_properties: Default::default(),
|
||||||
read_consistency_interval: None,
|
read_consistency_interval: None,
|
||||||
session: None,
|
session: None,
|
||||||
};
|
};
|
||||||
@@ -1265,6 +1291,7 @@ mod tests {
|
|||||||
#[cfg(feature = "remote")]
|
#[cfg(feature = "remote")]
|
||||||
client_config: Default::default(),
|
client_config: Default::default(),
|
||||||
options: options.clone(),
|
options: options.clone(),
|
||||||
|
namespace_client_properties: Default::default(),
|
||||||
read_consistency_interval: None,
|
read_consistency_interval: None,
|
||||||
session: None,
|
session: None,
|
||||||
};
|
};
|
||||||
@@ -1799,6 +1826,7 @@ mod tests {
|
|||||||
#[cfg(feature = "remote")]
|
#[cfg(feature = "remote")]
|
||||||
client_config: Default::default(),
|
client_config: Default::default(),
|
||||||
options,
|
options,
|
||||||
|
namespace_client_properties: Default::default(),
|
||||||
read_consistency_interval: None,
|
read_consistency_interval: None,
|
||||||
session: None,
|
session: None,
|
||||||
};
|
};
|
||||||
@@ -1904,6 +1932,7 @@ mod tests {
|
|||||||
#[cfg(feature = "remote")]
|
#[cfg(feature = "remote")]
|
||||||
client_config: Default::default(),
|
client_config: Default::default(),
|
||||||
options,
|
options,
|
||||||
|
namespace_client_properties: Default::default(),
|
||||||
read_consistency_interval: None,
|
read_consistency_interval: None,
|
||||||
session: None,
|
session: None,
|
||||||
};
|
};
|
||||||
@@ -1975,6 +2004,7 @@ mod tests {
|
|||||||
#[cfg(feature = "remote")]
|
#[cfg(feature = "remote")]
|
||||||
client_config: Default::default(),
|
client_config: Default::default(),
|
||||||
options,
|
options,
|
||||||
|
namespace_client_properties: Default::default(),
|
||||||
read_consistency_interval: None,
|
read_consistency_interval: None,
|
||||||
session: None,
|
session: None,
|
||||||
};
|
};
|
||||||
@@ -2108,4 +2138,208 @@ mod tests {
|
|||||||
assert!(tables.contains(&"table1".to_string()));
|
assert!(tables.contains(&"table1".to_string()));
|
||||||
assert!(tables.contains(&"table2".to_string()));
|
assert!(tables.contains(&"table2".to_string()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_listing_database_namespace_operations() {
|
||||||
|
let (_tempdir, db) = setup_database().await;
|
||||||
|
|
||||||
|
db.create_namespace(CreateNamespaceRequest {
|
||||||
|
id: Some(vec!["parent".to_string()]),
|
||||||
|
..Default::default()
|
||||||
|
})
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
db.create_namespace(CreateNamespaceRequest {
|
||||||
|
id: Some(vec!["parent".to_string(), "child".to_string()]),
|
||||||
|
..Default::default()
|
||||||
|
})
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let root_namespaces = db
|
||||||
|
.list_namespaces(ListNamespacesRequest {
|
||||||
|
id: Some(vec![]),
|
||||||
|
..Default::default()
|
||||||
|
})
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
assert!(root_namespaces.namespaces.contains(&"parent".to_string()));
|
||||||
|
|
||||||
|
let child_namespaces = db
|
||||||
|
.list_namespaces(ListNamespacesRequest {
|
||||||
|
id: Some(vec!["parent".to_string()]),
|
||||||
|
..Default::default()
|
||||||
|
})
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
assert!(child_namespaces.namespaces.contains(&"child".to_string()));
|
||||||
|
|
||||||
|
db.describe_namespace(DescribeNamespaceRequest {
|
||||||
|
id: Some(vec!["parent".to_string(), "child".to_string()]),
|
||||||
|
..Default::default()
|
||||||
|
})
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_listing_database_with_namespace_client_properties() {
|
||||||
|
let tempdir = tempdir().unwrap();
|
||||||
|
let uri = tempdir.path().to_str().unwrap();
|
||||||
|
|
||||||
|
let mut namespace_client_properties = HashMap::new();
|
||||||
|
namespace_client_properties.insert(
|
||||||
|
"table_version_tracking_enabled".to_string(),
|
||||||
|
"true".to_string(),
|
||||||
|
);
|
||||||
|
namespace_client_properties.insert("manifest_enabled".to_string(), "true".to_string());
|
||||||
|
|
||||||
|
let request = ConnectRequest {
|
||||||
|
uri: uri.to_string(),
|
||||||
|
#[cfg(feature = "remote")]
|
||||||
|
client_config: Default::default(),
|
||||||
|
options: Default::default(),
|
||||||
|
namespace_client_properties,
|
||||||
|
read_consistency_interval: None,
|
||||||
|
session: None,
|
||||||
|
};
|
||||||
|
|
||||||
|
let db = ListingDatabase::connect_with_options(&request)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
let namespace_path = vec!["test_ns".to_string()];
|
||||||
|
|
||||||
|
db.create_namespace(CreateNamespaceRequest {
|
||||||
|
id: Some(namespace_path.clone()),
|
||||||
|
..Default::default()
|
||||||
|
})
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let schema = Arc::new(Schema::new(vec![
|
||||||
|
Field::new("id", DataType::Int32, false),
|
||||||
|
Field::new("name", DataType::Utf8, false),
|
||||||
|
]));
|
||||||
|
|
||||||
|
db.create_table(CreateTableRequest {
|
||||||
|
name: "managed_table".to_string(),
|
||||||
|
namespace_path: namespace_path.clone(),
|
||||||
|
data: Box::new(RecordBatch::new_empty(schema)) as Box<dyn Scannable>,
|
||||||
|
mode: CreateTableMode::Create,
|
||||||
|
write_options: Default::default(),
|
||||||
|
location: None,
|
||||||
|
namespace_client: None,
|
||||||
|
})
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let namespace_client = db.namespace_client().await.unwrap();
|
||||||
|
let describe = namespace_client
|
||||||
|
.describe_table(lance_namespace::models::DescribeTableRequest {
|
||||||
|
id: Some(vec!["test_ns".to_string(), "managed_table".to_string()]),
|
||||||
|
..Default::default()
|
||||||
|
})
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
assert_eq!(describe.managed_versioning, Some(true));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_listing_database_nested_namespace_table_ops() {
|
||||||
|
let (_tempdir, db) = setup_database().await;
|
||||||
|
let namespace_path = vec!["parent".to_string(), "child".to_string()];
|
||||||
|
|
||||||
|
db.create_namespace(CreateNamespaceRequest {
|
||||||
|
id: Some(vec!["parent".to_string()]),
|
||||||
|
..Default::default()
|
||||||
|
})
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
db.create_namespace(CreateNamespaceRequest {
|
||||||
|
id: Some(namespace_path.clone()),
|
||||||
|
..Default::default()
|
||||||
|
})
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let schema = Arc::new(Schema::new(vec![
|
||||||
|
Field::new("id", DataType::Int32, false),
|
||||||
|
Field::new("name", DataType::Utf8, false),
|
||||||
|
]));
|
||||||
|
|
||||||
|
db.create_table(CreateTableRequest {
|
||||||
|
name: "nested_table".to_string(),
|
||||||
|
namespace_path: namespace_path.clone(),
|
||||||
|
data: Box::new(RecordBatch::new_empty(schema)) as Box<dyn Scannable>,
|
||||||
|
mode: CreateTableMode::Create,
|
||||||
|
write_options: Default::default(),
|
||||||
|
location: None,
|
||||||
|
namespace_client: None,
|
||||||
|
})
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let namespace_client = db.namespace_client().await.unwrap();
|
||||||
|
let describe = namespace_client
|
||||||
|
.describe_table(lance_namespace::models::DescribeTableRequest {
|
||||||
|
id: Some(vec![
|
||||||
|
"parent".to_string(),
|
||||||
|
"child".to_string(),
|
||||||
|
"nested_table".to_string(),
|
||||||
|
]),
|
||||||
|
..Default::default()
|
||||||
|
})
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
assert!(describe.location.is_some());
|
||||||
|
|
||||||
|
let table = db
|
||||||
|
.open_table(OpenTableRequest {
|
||||||
|
name: "nested_table".to_string(),
|
||||||
|
namespace_path: namespace_path.clone(),
|
||||||
|
index_cache_size: None,
|
||||||
|
lance_read_params: None,
|
||||||
|
location: None,
|
||||||
|
namespace_client: None,
|
||||||
|
managed_versioning: None,
|
||||||
|
})
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
assert_eq!(table.name(), "nested_table");
|
||||||
|
|
||||||
|
#[allow(deprecated)]
|
||||||
|
let table_names = db
|
||||||
|
.table_names(TableNamesRequest {
|
||||||
|
namespace_path: namespace_path.clone(),
|
||||||
|
start_after: None,
|
||||||
|
limit: None,
|
||||||
|
})
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
assert_eq!(table_names, vec!["nested_table".to_string()]);
|
||||||
|
|
||||||
|
let list_tables = db
|
||||||
|
.list_tables(ListTablesRequest {
|
||||||
|
id: Some(namespace_path.clone()),
|
||||||
|
..Default::default()
|
||||||
|
})
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
assert_eq!(list_tables.tables, vec!["nested_table".to_string()]);
|
||||||
|
|
||||||
|
db.drop_table("nested_table", &namespace_path)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let post_drop = db
|
||||||
|
.list_tables(ListTablesRequest {
|
||||||
|
id: Some(namespace_path),
|
||||||
|
..Default::default()
|
||||||
|
})
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
assert!(post_drop.tables.is_empty());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -450,6 +450,47 @@ mod tests {
|
|||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_namespace_connection_with_namespace_client_properties() {
|
||||||
|
let tmp_dir = tempdir().unwrap();
|
||||||
|
let root_path = tmp_dir.path().to_str().unwrap().to_string();
|
||||||
|
|
||||||
|
let mut properties = HashMap::new();
|
||||||
|
properties.insert("root".to_string(), root_path);
|
||||||
|
|
||||||
|
let conn = connect_namespace("dir", properties)
|
||||||
|
.namespace_client_property("table_version_tracking_enabled", "true")
|
||||||
|
.namespace_client_property("manifest_enabled", "true")
|
||||||
|
.execute()
|
||||||
|
.await
|
||||||
|
.expect("Failed to connect to namespace");
|
||||||
|
|
||||||
|
conn.create_namespace(CreateNamespaceRequest {
|
||||||
|
id: Some(vec!["test_ns".into()]),
|
||||||
|
..Default::default()
|
||||||
|
})
|
||||||
|
.await
|
||||||
|
.expect("Failed to create namespace");
|
||||||
|
|
||||||
|
let test_data = create_test_data();
|
||||||
|
conn.create_table("test_table", test_data)
|
||||||
|
.namespace(vec!["test_ns".into()])
|
||||||
|
.execute()
|
||||||
|
.await
|
||||||
|
.expect("Failed to create table");
|
||||||
|
|
||||||
|
let namespace_client = conn.namespace_client().await.unwrap();
|
||||||
|
let describe = namespace_client
|
||||||
|
.describe_table(DescribeTableRequest {
|
||||||
|
id: Some(vec!["test_ns".into(), "test_table".into()]),
|
||||||
|
..Default::default()
|
||||||
|
})
|
||||||
|
.await
|
||||||
|
.expect("Failed to describe table");
|
||||||
|
|
||||||
|
assert_eq!(describe.managed_versioning, Some(true));
|
||||||
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn test_namespace_create_table_basic() {
|
async fn test_namespace_create_table_basic() {
|
||||||
// Setup: Create a temporary directory for the namespace
|
// Setup: Create a temporary directory for the namespace
|
||||||
|
|||||||
@@ -177,6 +177,7 @@ impl BedrockEmbeddingFunction {
|
|||||||
))
|
))
|
||||||
.send()
|
.send()
|
||||||
.await
|
.await
|
||||||
|
.map_err(Box::new)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|||||||
@@ -69,7 +69,7 @@
|
|||||||
//! It treats [`FixedSizeList<Float16/Float32>`](https://docs.rs/arrow/latest/arrow/array/struct.FixedSizeListArray.html)
|
//! It treats [`FixedSizeList<Float16/Float32>`](https://docs.rs/arrow/latest/arrow/array/struct.FixedSizeListArray.html)
|
||||||
//! columns as vector columns.
|
//! columns as vector columns.
|
||||||
//!
|
//!
|
||||||
//! For more details, please refer to the [LanceDB documentation](https://lancedb.com/docs).
|
//! For more details, please refer to the [LanceDB documentation](https://docs.lancedb.com).
|
||||||
//!
|
//!
|
||||||
//! #### Create a table
|
//! #### Create a table
|
||||||
//!
|
//!
|
||||||
|
|||||||
@@ -52,6 +52,13 @@ pub struct ClientConfig {
|
|||||||
pub tls_config: Option<TlsConfig>,
|
pub tls_config: Option<TlsConfig>,
|
||||||
/// Provider for custom headers to be added to each request
|
/// Provider for custom headers to be added to each request
|
||||||
pub header_provider: Option<Arc<dyn HeaderProvider>>,
|
pub header_provider: Option<Arc<dyn HeaderProvider>>,
|
||||||
|
/// User identifier for tracking purposes.
|
||||||
|
///
|
||||||
|
/// This is sent as the `x-lancedb-user-id` header in requests to LanceDB Cloud/Enterprise.
|
||||||
|
/// It can be set directly, or via the `LANCEDB_USER_ID` environment variable.
|
||||||
|
/// Alternatively, set `LANCEDB_USER_ID_ENV_KEY` to specify another environment
|
||||||
|
/// variable that contains the user ID value.
|
||||||
|
pub user_id: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl std::fmt::Debug for ClientConfig {
|
impl std::fmt::Debug for ClientConfig {
|
||||||
@@ -67,6 +74,7 @@ impl std::fmt::Debug for ClientConfig {
|
|||||||
"header_provider",
|
"header_provider",
|
||||||
&self.header_provider.as_ref().map(|_| "Some(...)"),
|
&self.header_provider.as_ref().map(|_| "Some(...)"),
|
||||||
)
|
)
|
||||||
|
.field("user_id", &self.user_id)
|
||||||
.finish()
|
.finish()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -81,10 +89,41 @@ impl Default for ClientConfig {
|
|||||||
id_delimiter: None,
|
id_delimiter: None,
|
||||||
tls_config: None,
|
tls_config: None,
|
||||||
header_provider: None,
|
header_provider: None,
|
||||||
|
user_id: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl ClientConfig {
|
||||||
|
/// Resolve the user ID from the config or environment variables.
|
||||||
|
///
|
||||||
|
/// Resolution order:
|
||||||
|
/// 1. If `user_id` is set in the config, use that value
|
||||||
|
/// 2. If `LANCEDB_USER_ID` environment variable is set, use that value
|
||||||
|
/// 3. If `LANCEDB_USER_ID_ENV_KEY` is set, read the env var it points to
|
||||||
|
/// 4. Otherwise, return None
|
||||||
|
pub fn resolve_user_id(&self) -> Option<String> {
|
||||||
|
if self.user_id.is_some() {
|
||||||
|
return self.user_id.clone();
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Ok(user_id) = std::env::var("LANCEDB_USER_ID")
|
||||||
|
&& !user_id.is_empty()
|
||||||
|
{
|
||||||
|
return Some(user_id);
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Ok(env_key) = std::env::var("LANCEDB_USER_ID_ENV_KEY")
|
||||||
|
&& let Ok(user_id) = std::env::var(&env_key)
|
||||||
|
&& !user_id.is_empty()
|
||||||
|
{
|
||||||
|
return Some(user_id);
|
||||||
|
}
|
||||||
|
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// How to handle timeouts for HTTP requests.
|
/// How to handle timeouts for HTTP requests.
|
||||||
#[derive(Clone, Default, Debug)]
|
#[derive(Clone, Default, Debug)]
|
||||||
pub struct TimeoutConfig {
|
pub struct TimeoutConfig {
|
||||||
@@ -464,6 +503,15 @@ impl<S: HttpSend> RestfulLanceDbClient<S> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if let Some(user_id) = config.resolve_user_id() {
|
||||||
|
headers.insert(
|
||||||
|
HeaderName::from_static("x-lancedb-user-id"),
|
||||||
|
HeaderValue::from_str(&user_id).map_err(|_| Error::InvalidInput {
|
||||||
|
message: format!("non-ascii user_id '{}' provided", user_id),
|
||||||
|
})?,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
Ok(headers)
|
Ok(headers)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1072,4 +1120,91 @@ mod tests {
|
|||||||
_ => panic!("Expected Runtime error"),
|
_ => panic!("Expected Runtime error"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_resolve_user_id_direct_value() {
|
||||||
|
let config = ClientConfig {
|
||||||
|
user_id: Some("direct-user-id".to_string()),
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
assert_eq!(config.resolve_user_id(), Some("direct-user-id".to_string()));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_resolve_user_id_none() {
|
||||||
|
let config = ClientConfig::default();
|
||||||
|
// Clear env vars that might be set from other tests
|
||||||
|
// SAFETY: This is only called in tests
|
||||||
|
unsafe {
|
||||||
|
std::env::remove_var("LANCEDB_USER_ID");
|
||||||
|
std::env::remove_var("LANCEDB_USER_ID_ENV_KEY");
|
||||||
|
}
|
||||||
|
assert_eq!(config.resolve_user_id(), None);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_resolve_user_id_from_env() {
|
||||||
|
// SAFETY: This is only called in tests
|
||||||
|
unsafe {
|
||||||
|
std::env::set_var("LANCEDB_USER_ID", "env-user-id");
|
||||||
|
}
|
||||||
|
let config = ClientConfig::default();
|
||||||
|
assert_eq!(config.resolve_user_id(), Some("env-user-id".to_string()));
|
||||||
|
// SAFETY: This is only called in tests
|
||||||
|
unsafe {
|
||||||
|
std::env::remove_var("LANCEDB_USER_ID");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_resolve_user_id_from_env_key() {
|
||||||
|
// SAFETY: This is only called in tests
|
||||||
|
unsafe {
|
||||||
|
std::env::remove_var("LANCEDB_USER_ID");
|
||||||
|
std::env::set_var("LANCEDB_USER_ID_ENV_KEY", "MY_CUSTOM_USER_ID");
|
||||||
|
std::env::set_var("MY_CUSTOM_USER_ID", "custom-env-user-id");
|
||||||
|
}
|
||||||
|
let config = ClientConfig::default();
|
||||||
|
assert_eq!(
|
||||||
|
config.resolve_user_id(),
|
||||||
|
Some("custom-env-user-id".to_string())
|
||||||
|
);
|
||||||
|
// SAFETY: This is only called in tests
|
||||||
|
unsafe {
|
||||||
|
std::env::remove_var("LANCEDB_USER_ID_ENV_KEY");
|
||||||
|
std::env::remove_var("MY_CUSTOM_USER_ID");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_resolve_user_id_direct_takes_precedence() {
|
||||||
|
// SAFETY: This is only called in tests
|
||||||
|
unsafe {
|
||||||
|
std::env::set_var("LANCEDB_USER_ID", "env-user-id");
|
||||||
|
}
|
||||||
|
let config = ClientConfig {
|
||||||
|
user_id: Some("direct-user-id".to_string()),
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
assert_eq!(config.resolve_user_id(), Some("direct-user-id".to_string()));
|
||||||
|
// SAFETY: This is only called in tests
|
||||||
|
unsafe {
|
||||||
|
std::env::remove_var("LANCEDB_USER_ID");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_resolve_user_id_empty_env_ignored() {
|
||||||
|
// SAFETY: This is only called in tests
|
||||||
|
unsafe {
|
||||||
|
std::env::set_var("LANCEDB_USER_ID", "");
|
||||||
|
std::env::remove_var("LANCEDB_USER_ID_ENV_KEY");
|
||||||
|
}
|
||||||
|
let config = ClientConfig::default();
|
||||||
|
assert_eq!(config.resolve_user_id(), None);
|
||||||
|
// SAFETY: This is only called in tests
|
||||||
|
unsafe {
|
||||||
|
std::env::remove_var("LANCEDB_USER_ID");
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -97,7 +97,7 @@ pub struct RemoteDatabaseOptions {
|
|||||||
pub host_override: Option<String>,
|
pub host_override: Option<String>,
|
||||||
/// Storage options configure the storage layer (e.g. S3, GCS, Azure, etc.)
|
/// Storage options configure the storage layer (e.g. S3, GCS, Azure, etc.)
|
||||||
///
|
///
|
||||||
/// See available options at <https://lancedb.com/docs/storage/>
|
/// See available options at <https://docs.lancedb.com/storage/>
|
||||||
///
|
///
|
||||||
/// These options are only used for LanceDB Enterprise and only a subset of options
|
/// These options are only used for LanceDB Enterprise and only a subset of options
|
||||||
/// are supported.
|
/// are supported.
|
||||||
|
|||||||
Reference in New Issue
Block a user