mirror of
https://github.com/lancedb/lancedb.git
synced 2026-09-11 15:52:17 +00:00
Compare commits
124
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
477a49cecb | ||
|
|
bd86cc5aa6 | ||
|
|
2779b75d0d | ||
|
|
0d19a6c546 | ||
|
|
c0f33f8627 | ||
|
|
904bd975e5 | ||
|
|
d2ca0ce0ab | ||
|
|
9a1ffb9e02 | ||
|
|
f2eb4a245d | ||
|
|
e6867f7d04 | ||
|
|
193c5e3458 | ||
|
|
7ebd3c222d | ||
|
|
d118ef168b | ||
|
|
19232f9c50 | ||
|
|
5cbd979455 | ||
|
|
e773d1e093 | ||
|
|
c8fd3e97d1 | ||
|
|
c196d033e9 | ||
|
|
16753b805a | ||
|
|
840e1d7313 | ||
|
|
1a9414c47c | ||
|
|
c4ee8ae670 | ||
|
|
57b8d3bf05 | ||
|
|
c6dfe830d9 | ||
|
|
d5dac65a21 | ||
|
|
1b0fc2c465 | ||
|
|
a417e46bfa | ||
|
|
fcdc3f949e | ||
|
|
0c4e0667bc | ||
|
|
101f524e47 | ||
|
|
36c142fa2e | ||
|
|
a87cada90e | ||
|
|
0559108fa9 | ||
|
|
6ab3b9eb30 | ||
|
|
c94d9a2a16 | ||
|
|
6c8aa22704 | ||
|
|
84f46df876 | ||
|
|
83cff3ab93 | ||
|
|
b85776c22a | ||
|
|
9d3962686e | ||
|
|
25645d82d4 | ||
|
|
0dd9dfdfc7 | ||
|
|
d24b2dcacc | ||
|
|
2deccf21cf | ||
|
|
ead4d27bfc | ||
|
|
5153e5a023 | ||
|
|
79f626b09e | ||
|
|
ae81d73563 | ||
|
|
8b7e13b0c6 | ||
|
|
b78f2a5044 | ||
|
|
06872463cf | ||
|
|
2fbf6d6211 | ||
|
|
391cac9034 | ||
|
|
21530432a0 | ||
|
|
9b825c5f29 | ||
|
|
8083232dd5 | ||
|
|
302b21aa94 | ||
|
|
35b5d015ac | ||
|
|
a57fb68891 | ||
|
|
a614400755 | ||
|
|
1d880f11ff | ||
|
|
ec4ad54ba2 | ||
|
|
d0bcc6c6fe | ||
|
|
81c3f108ce | ||
|
|
c988e4848d | ||
|
|
2fea7cd48d | ||
|
|
0e65123bd8 | ||
|
|
6ed3074d4c | ||
|
|
c1a8c3f089 | ||
|
|
fce45ba9fc | ||
|
|
5013c176dd | ||
|
|
71f85a8d9f | ||
|
|
c72f5b2960 | ||
|
|
93f47b8aab | ||
|
|
105fd73bc6 | ||
|
|
94d484f539 | ||
|
|
b0dae5eb0b | ||
|
|
242ade8017 | ||
|
|
40d4d012e7 | ||
|
|
000e3b506b | ||
|
|
1b950188c3 | ||
|
|
6cc77b573c | ||
|
|
1f1d03f306 | ||
|
|
45cd053478 | ||
|
|
68749ecfa3 | ||
|
|
e98d8ac685 | ||
|
|
851fa16b47 | ||
|
|
d04ac7ed20 | ||
|
|
a578e9ff7f | ||
|
|
7801e2746a | ||
|
|
5468f3d490 | ||
|
|
c0df2c63b6 | ||
|
|
9e8f1c1a6d | ||
|
|
01679e37fd | ||
|
|
c7cb0b9afa | ||
|
|
a35f7044ee | ||
|
|
29822306d2 | ||
|
|
f39a7a4dd9 | ||
|
|
1baada89ef | ||
|
|
ecf4555cfd | ||
|
|
fa3d9b2ce2 | ||
|
|
217ea1a799 | ||
|
|
bacd0e4c3c | ||
|
|
7fd881bbe3 | ||
|
|
5c3bc7f643 | ||
|
|
fe992bf4ee | ||
|
|
944398d807 | ||
|
|
fd2a202a46 | ||
|
|
6a0df4de47 | ||
|
|
fbfb53e30f | ||
|
|
593ef1c471 | ||
|
|
cf27f6902e | ||
|
|
f76ee304b8 | ||
|
|
a588208de6 | ||
|
|
685cb01d6d | ||
|
|
4ba2421254 | ||
|
|
09843410ec | ||
|
|
7adcffc2b4 | ||
|
|
c1331e5083 | ||
|
|
426684cf1b | ||
|
|
e517ba5205 | ||
|
|
5c1b44020a | ||
|
|
061a3da8b9 | ||
|
|
4e042af12f |
@@ -5,7 +5,3 @@ This directory contains repo-scoped code agent skills for the LanceDB project.
|
|||||||
Each skill is a folder that contains a required `SKILL.md` and optional bundled resources.
|
Each skill is a folder that contains a required `SKILL.md` and optional bundled resources.
|
||||||
|
|
||||||
Codex discovers skills from `.agents/skills` in the current working directory and parent directories.
|
Codex discovers skills from `.agents/skills` in the current working directory and parent directories.
|
||||||
|
|
||||||
The `lancedb` skill lives in the `plugins/lancedb` plugin (see `plugins/lancedb/skills/lancedb`)
|
|
||||||
so it can be installed via the plugin marketplaces (`.claude-plugin/marketplace.json` and
|
|
||||||
`.agents/plugins/marketplace.json`); the `lancedb` entry here is a symlink into that plugin.
|
|
||||||
|
|||||||
@@ -1 +0,0 @@
|
|||||||
../../plugins/lancedb/skills/lancedb
|
|
||||||
+1
-1
@@ -1,5 +1,5 @@
|
|||||||
[tool.bumpversion]
|
[tool.bumpversion]
|
||||||
current_version = "0.38.0-beta.2"
|
current_version = "0.39.0-beta.1"
|
||||||
parse = """(?x)
|
parse = """(?x)
|
||||||
(?P<major>0|[1-9]\\d*)\\.
|
(?P<major>0|[1-9]\\d*)\\.
|
||||||
(?P<minor>0|[1-9]\\d*)\\.
|
(?P<minor>0|[1-9]\\d*)\\.
|
||||||
|
|||||||
@@ -9,6 +9,18 @@ debug = true
|
|||||||
codegen-units = 16
|
codegen-units = 16
|
||||||
lto = "thin"
|
lto = "thin"
|
||||||
|
|
||||||
|
[profile.release-no-lto]
|
||||||
|
inherits = "release"
|
||||||
|
debug = true
|
||||||
|
lto = false
|
||||||
|
# Prioritize compile time when LTO is not relevant to the measurement.
|
||||||
|
codegen-units = 16
|
||||||
|
|
||||||
|
[profile.bench]
|
||||||
|
inherits = "release"
|
||||||
|
lto = "thin"
|
||||||
|
codegen-units = 16
|
||||||
|
|
||||||
[target.'cfg(all())']
|
[target.'cfg(all())']
|
||||||
rustflags = [
|
rustflags = [
|
||||||
"-Wclippy::all",
|
"-Wclippy::all",
|
||||||
|
|||||||
@@ -17,6 +17,18 @@ updates:
|
|||||||
# newer minimum versions.
|
# newer minimum versions.
|
||||||
versioning-strategy: lockfile-only
|
versioning-strategy: lockfile-only
|
||||||
groups:
|
groups:
|
||||||
|
# The arrow-rs and datafusion crates are released in lockstep and have to
|
||||||
|
# move together, so keep them in one PR instead of one per sub-crate.
|
||||||
|
# Listed first: a dependency joins the first group it matches.
|
||||||
|
arrow-datafusion:
|
||||||
|
patterns:
|
||||||
|
- arrow
|
||||||
|
- arrow-*
|
||||||
|
- parquet
|
||||||
|
- parquet-*
|
||||||
|
- datafusion
|
||||||
|
- datafusion-*
|
||||||
|
- object_store
|
||||||
rust-minor-patch:
|
rust-minor-patch:
|
||||||
update-types:
|
update-types:
|
||||||
- minor
|
- minor
|
||||||
@@ -32,3 +44,27 @@ updates:
|
|||||||
python-deps:
|
python-deps:
|
||||||
patterns:
|
patterns:
|
||||||
- "*"
|
- "*"
|
||||||
|
|
||||||
|
# The npm ecosystem covers pnpm lockfiles. There are two separate installs:
|
||||||
|
# the bindings themselves and the examples, which have their own lockfile.
|
||||||
|
# As with cargo and pip above, only bump the lockfile — the version ranges
|
||||||
|
# in package.json are our consumers' constraints, not ours.
|
||||||
|
- package-ecosystem: npm
|
||||||
|
directory: /nodejs
|
||||||
|
schedule:
|
||||||
|
interval: weekly
|
||||||
|
versioning-strategy: lockfile-only
|
||||||
|
groups:
|
||||||
|
nodejs-deps:
|
||||||
|
patterns:
|
||||||
|
- "*"
|
||||||
|
|
||||||
|
- package-ecosystem: npm
|
||||||
|
directory: /nodejs/examples
|
||||||
|
schedule:
|
||||||
|
interval: weekly
|
||||||
|
versioning-strategy: lockfile-only
|
||||||
|
groups:
|
||||||
|
nodejs-examples-deps:
|
||||||
|
patterns:
|
||||||
|
- "*"
|
||||||
|
|||||||
@@ -0,0 +1,30 @@
|
|||||||
|
name: CI scripts
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- main
|
||||||
|
paths:
|
||||||
|
- ci/set_lance_version.py
|
||||||
|
- ci/tests/**
|
||||||
|
- .github/workflows/ci-scripts.yml
|
||||||
|
pull_request:
|
||||||
|
paths:
|
||||||
|
- ci/set_lance_version.py
|
||||||
|
- ci/tests/**
|
||||||
|
- .github/workflows/ci-scripts.yml
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
test:
|
||||||
|
name: Test CI scripts
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v6
|
||||||
|
- uses: actions/setup-python@v6
|
||||||
|
with:
|
||||||
|
python-version: "3.13"
|
||||||
|
- name: Run tests
|
||||||
|
run: python -m unittest discover -s ci/tests -v
|
||||||
@@ -29,12 +29,14 @@ jobs:
|
|||||||
steps:
|
steps:
|
||||||
- uses: actions/setup-node@v6
|
- uses: actions/setup-node@v6
|
||||||
with:
|
with:
|
||||||
node-version: "18"
|
node-version: "24"
|
||||||
|
- uses: pnpm/action-setup@v6
|
||||||
|
with:
|
||||||
|
version: 11.1.1
|
||||||
# These rules are disabled because Github will always ensure there
|
# These rules are disabled because Github will always ensure there
|
||||||
# is a blank line between the title and the body and Github will
|
# is a blank line between the title and the body and Github will
|
||||||
# word wrap the description field to ensure a reasonable max line
|
# word wrap the description field to ensure a reasonable max line
|
||||||
# length.
|
# length.
|
||||||
- run: npm install @commitlint/config-conventional
|
|
||||||
- run: >
|
- run: >
|
||||||
echo 'module.exports = {
|
echo 'module.exports = {
|
||||||
"rules": {
|
"rules": {
|
||||||
@@ -43,7 +45,11 @@ jobs:
|
|||||||
"body-leading-blank": [0, "always"]
|
"body-leading-blank": [0, "always"]
|
||||||
}
|
}
|
||||||
}' > .commitlintrc.js
|
}' > .commitlintrc.js
|
||||||
- run: npx commitlint --extends @commitlint/config-conventional --verbose <<< $COMMIT_MSG
|
- run: >
|
||||||
|
pnpm dlx
|
||||||
|
--package @commitlint/cli@21.2.2
|
||||||
|
--package @commitlint/config-conventional@21.2.2
|
||||||
|
commitlint --extends @commitlint/config-conventional --verbose <<< $COMMIT_MSG
|
||||||
env:
|
env:
|
||||||
COMMIT_MSG: >
|
COMMIT_MSG: >
|
||||||
${{ github.event.pull_request.title }}
|
${{ github.event.pull_request.title }}
|
||||||
@@ -54,7 +60,7 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
script: |
|
script: |
|
||||||
const message = `**ACTION NEEDED**
|
const message = `**ACTION NEEDED**
|
||||||
|
|
||||||
Lance follows the [Conventional Commits specification](https://www.conventionalcommits.org/en/v1.0.0/) for release automation.
|
Lance follows the [Conventional Commits specification](https://www.conventionalcommits.org/en/v1.0.0/) for release automation.
|
||||||
|
|
||||||
The PR title and description are used as the merge commit message.\
|
The PR title and description are used as the merge commit message.\
|
||||||
|
|||||||
@@ -56,7 +56,7 @@ jobs:
|
|||||||
uses: lycheeverse/lychee-action@e7477775783ea5526144ba13e8db5eec57747ce8 # v2.9.0
|
uses: lycheeverse/lychee-action@e7477775783ea5526144ba13e8db5eec57747ce8 # v2.9.0
|
||||||
with:
|
with:
|
||||||
# Restricted to http(s) on purpose. Much of docs/src is generated
|
# Restricted to http(s) on purpose. Much of docs/src is generated
|
||||||
# API reference (the js/ tree comes from `npm run docs` in nodejs)
|
# API reference (the js/ tree comes from `pnpm run docs` in nodejs)
|
||||||
# and the hand-written pages use mkdocstrings cross-references and
|
# and the hand-written pages use mkdocstrings cross-references and
|
||||||
# nav-relative paths that only resolve in the site mkdocs builds,
|
# nav-relative paths that only resolve in the site mkdocs builds,
|
||||||
# not in this checkout, so relative links would be reported as
|
# not in this checkout, so relative links would be reported as
|
||||||
|
|||||||
@@ -55,9 +55,7 @@ jobs:
|
|||||||
- name: Set up node
|
- name: Set up node
|
||||||
uses: actions/setup-node@v6
|
uses: actions/setup-node@v6
|
||||||
with:
|
with:
|
||||||
node-version: 20
|
node-version: 24
|
||||||
cache: 'npm'
|
|
||||||
cache-dependency-path: docs/package-lock.json
|
|
||||||
- name: Install node dependencies
|
- name: Install node dependencies
|
||||||
working-directory: nodejs
|
working-directory: nodejs
|
||||||
run: |
|
run: |
|
||||||
|
|||||||
@@ -47,9 +47,8 @@ jobs:
|
|||||||
version: 11.1.1
|
version: 11.1.1
|
||||||
- uses: actions/setup-node@v6
|
- uses: actions/setup-node@v6
|
||||||
with:
|
with:
|
||||||
# pnpm 11 requires Node >= 22.13; use 24 since 22 hits EOL
|
# Build on a supported LTS; the matrix job below covers every
|
||||||
# in October. The library itself still supports Node >= 18
|
# Node version the library claims to support.
|
||||||
# (see test matrix below).
|
|
||||||
node-version: 24
|
node-version: 24
|
||||||
cache: 'pnpm'
|
cache: 'pnpm'
|
||||||
cache-dependency-path: nodejs/pnpm-lock.yaml
|
cache-dependency-path: nodejs/pnpm-lock.yaml
|
||||||
@@ -84,7 +83,7 @@ jobs:
|
|||||||
timeout-minutes: 30
|
timeout-minutes: 30
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
node-version: [ "18", "20" ]
|
node-version: [ "22", "24", "26" ]
|
||||||
runs-on: "ubuntu-22.04"
|
runs-on: "ubuntu-22.04"
|
||||||
defaults:
|
defaults:
|
||||||
run:
|
run:
|
||||||
@@ -101,9 +100,9 @@ jobs:
|
|||||||
- uses: actions/setup-node@v6
|
- uses: actions/setup-node@v6
|
||||||
name: Setup Node.js 24 for build
|
name: Setup Node.js 24 for build
|
||||||
with:
|
with:
|
||||||
# pnpm 11 requires Node >= 22.13; use 24 since 22 hits EOL
|
# Build and install once on a fixed version so the generated docs
|
||||||
# in October. Build/install runs on Node 24; tests run on the
|
# are identical across matrix legs; the tests below then run on each
|
||||||
# matrix version below using direct jest invocation.
|
# supported Node version.
|
||||||
node-version: 24
|
node-version: 24
|
||||||
cache: 'pnpm'
|
cache: 'pnpm'
|
||||||
cache-dependency-path: nodejs/pnpm-lock.yaml
|
cache-dependency-path: nodejs/pnpm-lock.yaml
|
||||||
@@ -152,9 +151,9 @@ jobs:
|
|||||||
S3_TEST: "1"
|
S3_TEST: "1"
|
||||||
# Newer @smithy/core uses dynamic ESM imports.
|
# Newer @smithy/core uses dynamic ESM imports.
|
||||||
NODE_OPTIONS: "--experimental-vm-modules"
|
NODE_OPTIONS: "--experimental-vm-modules"
|
||||||
# Invoke jest directly because pnpm 11 itself requires Node 22+
|
# Invoke the installed jest binary directly; the pnpm shim is set up
|
||||||
# while the matrix tests on older Node versions.
|
# against the build-phase Node, not the version selected above.
|
||||||
run: npx jest --verbose
|
run: node_modules/.bin/jest --verbose
|
||||||
- name: Test examples
|
- name: Test examples
|
||||||
working-directory: ./
|
working-directory: ./
|
||||||
env:
|
env:
|
||||||
@@ -164,7 +163,7 @@ jobs:
|
|||||||
run: |
|
run: |
|
||||||
python ci/mock_openai.py &
|
python ci/mock_openai.py &
|
||||||
cd nodejs/examples
|
cd nodejs/examples
|
||||||
npx jest --testEnvironment jest-environment-node-single-context --verbose
|
node_modules/.bin/jest --testEnvironment jest-environment-node-single-context --verbose
|
||||||
macos:
|
macos:
|
||||||
timeout-minutes: 30
|
timeout-minutes: 30
|
||||||
# macos-15 ships a newer linker; the older macos-14 linker fails to insert
|
# macos-15 ships a newer linker; the older macos-14 linker fails to insert
|
||||||
@@ -185,8 +184,7 @@ jobs:
|
|||||||
version: 11.1.1
|
version: 11.1.1
|
||||||
- uses: actions/setup-node@v6
|
- uses: actions/setup-node@v6
|
||||||
with:
|
with:
|
||||||
# pnpm 11 requires Node >= 22.13; use 24 since 22 hits EOL
|
# pnpm 11 requires Node >= 22.13.
|
||||||
# in October.
|
|
||||||
node-version: 24
|
node-version: 24
|
||||||
cache: 'pnpm'
|
cache: 'pnpm'
|
||||||
cache-dependency-path: nodejs/pnpm-lock.yaml
|
cache-dependency-path: nodejs/pnpm-lock.yaml
|
||||||
|
|||||||
@@ -40,40 +40,31 @@ jobs:
|
|||||||
- target: aarch64-apple-darwin
|
- target: aarch64-apple-darwin
|
||||||
host: macos-latest
|
host: macos-latest
|
||||||
features: fp16kernels
|
features: fp16kernels
|
||||||
|
# Fat LTO was ~111 of this job's ~113 minutes.
|
||||||
|
lto: thin
|
||||||
|
codegen_units: 16
|
||||||
pre_build: |-
|
pre_build: |-
|
||||||
brew install protobuf
|
brew install protobuf
|
||||||
# Fat LTO (the workspace default in .cargo/config.toml) is
|
|
||||||
# single-threaded and is the peak-memory step of the build. On
|
|
||||||
# this runner it accounted for ~111 of the job's ~113 minutes,
|
|
||||||
# making it the critical path of the entire publish pipeline.
|
|
||||||
# ThinLTO parallelizes it across the runner's cores, for a few
|
|
||||||
# percent of runtime performance.
|
|
||||||
export CARGO_PROFILE_RELEASE_LTO=thin
|
|
||||||
export CARGO_PROFILE_RELEASE_CODEGEN_UNITS=16
|
|
||||||
- target: x86_64-pc-windows-msvc
|
- target: x86_64-pc-windows-msvc
|
||||||
host: windows-2025
|
host: windows-2025
|
||||||
features: ","
|
features: ","
|
||||||
|
# The lower peak also keeps this on the standard 4-core runner.
|
||||||
|
lto: thin
|
||||||
|
codegen_units: 16
|
||||||
pre_build: |-
|
pre_build: |-
|
||||||
choco install --no-progress protoc ninja nasm
|
choco install --no-progress protoc ninja nasm
|
||||||
tail -n 1000 /c/ProgramData/chocolatey/logs/chocolatey.log
|
tail -n 1000 /c/ProgramData/chocolatey/logs/chocolatey.log
|
||||||
# There is an issue where choco doesn't add nasm to the path
|
# There is an issue where choco doesn't add nasm to the path
|
||||||
export PATH="$PATH:/c/Program Files/NASM"
|
export PATH="$PATH:/c/Program Files/NASM"
|
||||||
nasm -v
|
nasm -v
|
||||||
# See the ThinLTO note on aarch64-apple-darwin above. Keeping
|
|
||||||
# peak memory down is also what lets this run on the standard
|
|
||||||
# 4-core runner: the 8-core larger runner was only needed to
|
|
||||||
# stop fat LTO from OOMing rustc-LLVM.
|
|
||||||
export CARGO_PROFILE_RELEASE_LTO=thin
|
|
||||||
export CARGO_PROFILE_RELEASE_CODEGEN_UNITS=16
|
|
||||||
- target: aarch64-pc-windows-msvc
|
- target: aarch64-pc-windows-msvc
|
||||||
host: windows-2025
|
host: windows-2025
|
||||||
features: ","
|
features: ","
|
||||||
|
lto: thin
|
||||||
|
codegen_units: 16
|
||||||
pre_build: |-
|
pre_build: |-
|
||||||
choco install --no-progress protoc
|
choco install --no-progress protoc
|
||||||
rustup target add aarch64-pc-windows-msvc
|
rustup target add aarch64-pc-windows-msvc
|
||||||
# See the ThinLTO note on aarch64-apple-darwin above.
|
|
||||||
export CARGO_PROFILE_RELEASE_LTO=thin
|
|
||||||
export CARGO_PROFILE_RELEASE_CODEGEN_UNITS=16
|
|
||||||
- target: x86_64-unknown-linux-gnu
|
- target: x86_64-unknown-linux-gnu
|
||||||
host: ubuntu-latest
|
host: ubuntu-latest
|
||||||
features: fp16kernels
|
features: fp16kernels
|
||||||
@@ -103,6 +94,14 @@ jobs:
|
|||||||
# https://github.com/napi-rs/napi-rs/blob/main/debian-aarch64.Dockerfile
|
# https://github.com/napi-rs/napi-rs/blob/main/debian-aarch64.Dockerfile
|
||||||
docker: ghcr.io/napi-rs/napi-rs/nodejs-rust:lts-debian-aarch64
|
docker: ghcr.io/napi-rs/napi-rs/nodejs-rust:lts-debian-aarch64
|
||||||
features: "fp16kernels"
|
features: "fp16kernels"
|
||||||
|
# Fat LTO OOM-killed rustc every nightly; even with lld it peaked
|
||||||
|
# at 31391 MiB of the runner's 32 GiB.
|
||||||
|
lto: thin
|
||||||
|
codegen_units: 16
|
||||||
|
# arm64 Linux links through GNU `ld` where x86_64 defaults to
|
||||||
|
# `rust-lld`, which is why only arm64 OOM'd. lld cut the largest
|
||||||
|
# linker process 7.0 -> 4.0 GiB (lancedb/sophon#7313).
|
||||||
|
linker: /tmp/aarch64-lld-clang
|
||||||
pre_build: |-
|
pre_build: |-
|
||||||
set -e &&
|
set -e &&
|
||||||
apt-get update &&
|
apt-get update &&
|
||||||
@@ -112,9 +111,30 @@ jobs:
|
|||||||
# AT_HWCAP2 (added in Linux 3.17). Define it for aws-lc-sys.
|
# AT_HWCAP2 (added in Linux 3.17). Define it for aws-lc-sys.
|
||||||
export CFLAGS="$CFLAGS -DAT_HWCAP2=26" &&
|
export CFLAGS="$CFLAGS -DAT_HWCAP2=26" &&
|
||||||
rustup target add aarch64-unknown-linux-gnu
|
rustup target add aarch64-unknown-linux-gnu
|
||||||
|
# Not `&&`-chained: in dash, errexit does not fire for a
|
||||||
|
# non-final command in an `&&` list, so failures were ignored.
|
||||||
|
#
|
||||||
|
# A wrapper rather than `-C link-arg` because the per-target
|
||||||
|
# rustflags variable does not reach every unit that links, while
|
||||||
|
# the linker variable does. `clang` because GCC silently ignores
|
||||||
|
# `-fuse-ld=lld` unless built with lld support. Two echoes
|
||||||
|
# because printf's newline escape gets rewritten to `;` between
|
||||||
|
# here and the container.
|
||||||
|
echo '#!/bin/sh' > /tmp/aarch64-lld-clang
|
||||||
|
echo 'exec clang --target=aarch64-unknown-linux-gnu --sysroot=/usr/aarch64-unknown-linux-gnu/aarch64-unknown-linux-gnu/sysroot --gcc-toolchain=/usr/aarch64-unknown-linux-gnu -fuse-ld=lld "$@"' >> /tmp/aarch64-lld-clang
|
||||||
|
chmod 0755 /tmp/aarch64-lld-clang
|
||||||
|
# Fail now, not at the cdylib link ~30 minutes later. Linking at
|
||||||
|
# all also proves lld resolved; clang errors out when it cannot.
|
||||||
|
echo 'int main(void){return 0;}' > /tmp/probe.c
|
||||||
|
/tmp/aarch64-lld-clang /tmp/probe.c -o /tmp/probe
|
||||||
|
readelf -h /tmp/probe | grep AArch64
|
||||||
- target: aarch64-unknown-linux-musl
|
- target: aarch64-unknown-linux-musl
|
||||||
host: ubuntu-2404-8x-x64
|
host: ubuntu-2404-8x-x64
|
||||||
features: ","
|
features: ","
|
||||||
|
# Fat LTO took the whole runner down. lld cannot help: it died
|
||||||
|
# inside rustc's LLVM, before any linker was spawned.
|
||||||
|
lto: thin
|
||||||
|
codegen_units: 16
|
||||||
pre_build: |-
|
pre_build: |-
|
||||||
set -e &&
|
set -e &&
|
||||||
sudo apt-get update &&
|
sudo apt-get update &&
|
||||||
@@ -123,6 +143,19 @@ jobs:
|
|||||||
export EXTRA_ARGS="-x"
|
export EXTRA_ARGS="-x"
|
||||||
name: build - ${{ matrix.settings.target }}
|
name: build - ${{ matrix.settings.target }}
|
||||||
runs-on: ${{ matrix.settings.host }}
|
runs-on: ${{ matrix.settings.host }}
|
||||||
|
# On the job, not exported from `pre_build`: `Swatinem/rust-cache` hashes
|
||||||
|
# `CARGO_*` into its cache key before any step runs, so a step-local export
|
||||||
|
# leaves the key unchanged while cargo still rebuilds cold. The ThinLTO
|
||||||
|
# legs had been doing that every run.
|
||||||
|
#
|
||||||
|
# Not `RUSTFLAGS`: setting it, even to "", discards every config-file
|
||||||
|
# rustflag, silently dropping .cargo/config.toml's `target-cpu` and
|
||||||
|
# `target-feature` from the published binaries.
|
||||||
|
env:
|
||||||
|
CARGO_PROFILE_RELEASE_LTO: ${{ matrix.settings.lto || 'fat' }}
|
||||||
|
CARGO_PROFILE_RELEASE_CODEGEN_UNITS: ${{ matrix.settings.codegen_units || '1' }}
|
||||||
|
# Empty elsewhere: a per-target variable is only read for that triple.
|
||||||
|
CARGO_TARGET_AARCH64_UNKNOWN_LINUX_GNU_LINKER: ${{ matrix.settings.linker }}
|
||||||
defaults:
|
defaults:
|
||||||
run:
|
run:
|
||||||
working-directory: nodejs
|
working-directory: nodejs
|
||||||
@@ -135,8 +168,7 @@ jobs:
|
|||||||
- name: Setup node
|
- name: Setup node
|
||||||
uses: actions/setup-node@v6
|
uses: actions/setup-node@v6
|
||||||
with:
|
with:
|
||||||
# pnpm 11 requires Node >= 22.13; use 24 since 22 hits EOL
|
# pnpm 11 requires Node >= 22.13.
|
||||||
# in October.
|
|
||||||
node-version: 24
|
node-version: 24
|
||||||
cache: pnpm
|
cache: pnpm
|
||||||
cache-dependency-path: nodejs/pnpm-lock.yaml
|
cache-dependency-path: nodejs/pnpm-lock.yaml
|
||||||
@@ -169,19 +201,15 @@ jobs:
|
|||||||
# creating ref). The nightly cadence also keeps entries inside
|
# creating ref). The nightly cadence also keeps entries inside
|
||||||
# GitHub's 7-day eviction window, which a tag-only trigger would not.
|
# GitHub's 7-day eviction window, which a tag-only trigger would not.
|
||||||
save-if: ${{ github.ref == 'refs/heads/main' }}
|
save-if: ${{ github.ref == 'refs/heads/main' }}
|
||||||
# Docker builds can use rust-cache too. `target/` already lives on the
|
# Docker builds can use rust-cache too: the workspace is bind-mounted, so
|
||||||
# host because the whole workspace is bind-mounted into the container, and
|
# `target/` lives on the host and rust-cache's prune keeps the entry
|
||||||
# rust-cache's prune and save run host-side, so they can manage it -- which
|
# small.
|
||||||
# is what keeps the entry to dependency artifacts rather than a multi-GB
|
|
||||||
# copy of everything.
|
|
||||||
#
|
#
|
||||||
# Two differences from the native builds. The container's CARGO_HOME is
|
# Two differences from the native builds. The container's CARGO_HOME is
|
||||||
# bind-mounted from `.cargo-cache` rather than the host's ~/.cargo, so that
|
# bind-mounted from `.cargo-cache` rather than ~/.cargo, so that is cached
|
||||||
# has to be cached explicitly. And the key is derived from the *host* rustc
|
# explicitly. And the key uses the *host* rustc version, not the compiler
|
||||||
# version, which is not the compiler that produced these artifacts; that is
|
# that built these artifacts -- safe, since cargo fingerprints the real
|
||||||
# safe because cargo fingerprints the real compiler and rebuilds on a
|
# one; a base-image bump just costs one cold build.
|
||||||
# mismatch, it just means a base-image toolchain bump costs one cold build
|
|
||||||
# instead of invalidating the key.
|
|
||||||
- name: Cache cargo (docker builds)
|
- name: Cache cargo (docker builds)
|
||||||
uses: Swatinem/rust-cache@v2
|
uses: Swatinem/rust-cache@v2
|
||||||
if: ${{ matrix.settings.docker }}
|
if: ${{ matrix.settings.docker }}
|
||||||
@@ -210,14 +238,19 @@ jobs:
|
|||||||
# cache step above saves. Previously the registry mounts pointed at
|
# cache step above saves. Previously the registry mounts pointed at
|
||||||
# `.cargo/...`, a path nothing cached, so the container re-downloaded
|
# `.cargo/...`, a path nothing cached, so the container re-downloaded
|
||||||
# the whole crate registry on every run.
|
# the whole crate registry on every run.
|
||||||
|
#
|
||||||
|
# `docker run` inherits nothing; `-e NAME` carries the job's `env:` in.
|
||||||
options: "--user 0:0 -v ${{ github.workspace }}/.cargo-cache/git/db:/usr/local/cargo/git/db \
|
options: "--user 0:0 -v ${{ github.workspace }}/.cargo-cache/git/db:/usr/local/cargo/git/db \
|
||||||
-v ${{ github.workspace }}/.cargo-cache/registry/cache:/usr/local/cargo/registry/cache \
|
-v ${{ github.workspace }}/.cargo-cache/registry/cache:/usr/local/cargo/registry/cache \
|
||||||
-v ${{ github.workspace }}/.cargo-cache/registry/index:/usr/local/cargo/registry/index \
|
-v ${{ github.workspace }}/.cargo-cache/registry/index:/usr/local/cargo/registry/index \
|
||||||
|
-e CARGO_PROFILE_RELEASE_LTO \
|
||||||
|
-e CARGO_PROFILE_RELEASE_CODEGEN_UNITS \
|
||||||
|
-e CARGO_TARGET_AARCH64_UNKNOWN_LINUX_GNU_LINKER \
|
||||||
-v ${{ github.workspace }}:/build -w /build/nodejs"
|
-v ${{ github.workspace }}:/build -w /build/nodejs"
|
||||||
run: |
|
run: |
|
||||||
set -e
|
set -e
|
||||||
${{ matrix.settings.pre_build }}
|
${{ matrix.settings.pre_build }}
|
||||||
npx napi build --platform --release \
|
node_modules/.bin/napi build --platform --release \
|
||||||
--features ${{ matrix.settings.features }} \
|
--features ${{ matrix.settings.features }} \
|
||||||
--target ${{ matrix.settings.target }} \
|
--target ${{ matrix.settings.target }} \
|
||||||
--dts ../lancedb/native.d.ts \
|
--dts ../lancedb/native.d.ts \
|
||||||
@@ -237,7 +270,7 @@ jobs:
|
|||||||
- name: Build
|
- name: Build
|
||||||
run: |
|
run: |
|
||||||
${{ matrix.settings.pre_build }}
|
${{ matrix.settings.pre_build }}
|
||||||
npx napi build --platform --release \
|
node_modules/.bin/napi build --platform --release \
|
||||||
--features ${{ matrix.settings.features }} \
|
--features ${{ matrix.settings.features }} \
|
||||||
--target ${{ matrix.settings.target }} \
|
--target ${{ matrix.settings.target }} \
|
||||||
--dts ../lancedb/native.d.ts \
|
--dts ../lancedb/native.d.ts \
|
||||||
@@ -256,6 +289,18 @@ jobs:
|
|||||||
if: always()
|
if: always()
|
||||||
run: df -h
|
run: df -h
|
||||||
shell: bash
|
shell: bash
|
||||||
|
- name: Report peak memory
|
||||||
|
if: always() && runner.os == 'Linux'
|
||||||
|
shell: bash
|
||||||
|
run: |
|
||||||
|
peak=$(find /sys/fs/cgroup -name memory.peak -readable \
|
||||||
|
-exec cat {} + 2>/dev/null | sort -n | tail -1)
|
||||||
|
if [ -n "$peak" ]; then
|
||||||
|
echo "peak memory: $((peak / 1024 / 1024)) MiB"
|
||||||
|
else
|
||||||
|
echo "peak memory: unavailable (no readable cgroup v2 memory.peak)"
|
||||||
|
fi
|
||||||
|
free -g || true
|
||||||
- name: Upload artifact
|
- name: Upload artifact
|
||||||
uses: actions/upload-artifact@v7
|
uses: actions/upload-artifact@v7
|
||||||
with:
|
with:
|
||||||
@@ -293,7 +338,7 @@ jobs:
|
|||||||
- target: aarch64-unknown-linux-gnu
|
- target: aarch64-unknown-linux-gnu
|
||||||
host: ubuntu-2404-8x-arm64
|
host: ubuntu-2404-8x-arm64
|
||||||
node:
|
node:
|
||||||
- '20'
|
- '22'
|
||||||
runs-on: ${{ matrix.settings.host }}
|
runs-on: ${{ matrix.settings.host }}
|
||||||
defaults:
|
defaults:
|
||||||
run:
|
run:
|
||||||
@@ -339,9 +384,9 @@ jobs:
|
|||||||
- name: Move built files
|
- name: Move built files
|
||||||
run: cp dist/native.d.ts dist/native.js dist/*.node lancedb/
|
run: cp dist/native.d.ts dist/native.js dist/*.node lancedb/
|
||||||
- name: Test bindings
|
- name: Test bindings
|
||||||
# Invoke jest directly because pnpm 11 itself requires Node 22+
|
# Invoke the installed jest binary directly; the pnpm shim is set up
|
||||||
# while the matrix tests on older Node versions.
|
# against the install-phase Node, not the version selected above.
|
||||||
run: npx jest --verbose
|
run: node_modules/.bin/jest --verbose
|
||||||
publish:
|
publish:
|
||||||
name: Publish
|
name: Publish
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
|||||||
@@ -129,6 +129,12 @@ jobs:
|
|||||||
# link.exe is single-threaded and the long pole on Windows builds. Use
|
# link.exe is single-threaded and the long pole on Windows builds. Use
|
||||||
# rustc's bundled lld-link instead.
|
# rustc's bundled lld-link instead.
|
||||||
CARGO_TARGET_X86_64_PC_WINDOWS_MSVC_LINKER: rust-lld
|
CARGO_TARGET_X86_64_PC_WINDOWS_MSVC_LINKER: rust-lld
|
||||||
|
# Fat LTO of the cdylib is single-threaded and the peak-memory step of the
|
||||||
|
# build. ThinLTO parallelizes it across the runner's cores, at some cost
|
||||||
|
# to runtime performance on our least performance-sensitive platform.
|
||||||
|
# Matches what the nodejs Windows builds already do in npm-publish.yml.
|
||||||
|
CARGO_PROFILE_RELEASE_LTO: thin
|
||||||
|
CARGO_PROFILE_RELEASE_CODEGEN_UNITS: 16
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v6
|
- uses: actions/checkout@v6
|
||||||
with:
|
with:
|
||||||
|
|||||||
@@ -229,7 +229,8 @@ jobs:
|
|||||||
# Make sure wheels are not included in the Rust cache
|
# Make sure wheels are not included in the Rust cache
|
||||||
- name: Delete wheels
|
- name: Delete wheels
|
||||||
run: rm -rf target/wheels
|
run: rm -rf target/wheels
|
||||||
pydantic1x:
|
min-deps:
|
||||||
|
name: "Minimum dependencies"
|
||||||
timeout-minutes: 60
|
timeout-minutes: 60
|
||||||
runs-on: "ubuntu-24.04"
|
runs-on: "ubuntu-24.04"
|
||||||
defaults:
|
defaults:
|
||||||
@@ -259,8 +260,7 @@ jobs:
|
|||||||
save-if: ${{ github.ref == 'refs/heads/main' }}
|
save-if: ${{ github.ref == 'refs/heads/main' }}
|
||||||
- name: Install lancedb
|
- name: Install lancedb
|
||||||
run: |
|
run: |
|
||||||
pip install "pydantic<2"
|
pip install "pydantic==2.7.4" "pyarrow==16"
|
||||||
pip install pyarrow==16
|
|
||||||
pip install --extra-index-url https://pypi.fury.io/lance-format/ --extra-index-url https://pypi.fury.io/lancedb/ -e .[tests]
|
pip install --extra-index-url https://pypi.fury.io/lance-format/ --extra-index-url https://pypi.fury.io/lancedb/ -e .[tests]
|
||||||
- name: Run tests
|
- name: Run tests
|
||||||
run: pytest -m "not slow and not s3_test" -x -v --durations=30 python/tests
|
run: pytest -m "not slow and not s3_test" -x -v --durations=30 python/tests
|
||||||
|
|||||||
@@ -121,7 +121,6 @@ jobs:
|
|||||||
# Need up-to-date compilers for kernels
|
# Need up-to-date compilers for kernels
|
||||||
CC: clang-18
|
CC: clang-18
|
||||||
CXX: clang++-18
|
CXX: clang++-18
|
||||||
GH_TOKEN: ${{ secrets.SOPHON_READ_TOKEN }}
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v6
|
- uses: actions/checkout@v6
|
||||||
with:
|
with:
|
||||||
@@ -165,11 +164,40 @@ jobs:
|
|||||||
- name: Run feature tests
|
- name: Run feature tests
|
||||||
run: CARGO_ARGS="--profile ci" make -C ./lancedb feature-tests
|
run: CARGO_ARGS="--profile ci" make -C ./lancedb feature-tests
|
||||||
- name: Run examples
|
- name: Run examples
|
||||||
run: cargo run --profile ci --example simple --locked
|
run: cargo run --profile ci --all-features --example simple --locked
|
||||||
|
|
||||||
|
remote:
|
||||||
|
timeout-minutes: 30
|
||||||
|
# Running this requires access to secrets, so skip if this is a PR from a
|
||||||
|
# fork. Keep it separate from the all-features build so Cargo does not
|
||||||
|
# retain both dependency graphs in one target directory.
|
||||||
|
if: github.event_name != 'pull_request' || !github.event.pull_request.head.repo.fork
|
||||||
|
runs-on: ubuntu-2404-4x-x64
|
||||||
|
defaults:
|
||||||
|
run:
|
||||||
|
shell: bash
|
||||||
|
working-directory: rust
|
||||||
|
env:
|
||||||
|
CC: clang-18
|
||||||
|
CXX: clang++-18
|
||||||
|
GH_TOKEN: ${{ secrets.SOPHON_READ_TOKEN }}
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v6
|
||||||
|
with:
|
||||||
|
fetch-depth: 0
|
||||||
|
lfs: true
|
||||||
|
- uses: Swatinem/rust-cache@v2
|
||||||
|
with:
|
||||||
|
# Remote tests use a different feature graph from the main Linux
|
||||||
|
# job. Cache downloads, but build into a fresh target directory.
|
||||||
|
cache-targets: false
|
||||||
|
save-if: ${{ github.ref == 'refs/heads/main' }}
|
||||||
|
- name: Install dependencies
|
||||||
|
run: |
|
||||||
|
sudo apt update
|
||||||
|
sudo apt install -y protobuf-compiler libssl-dev
|
||||||
|
- uses: rui314/setup-mold@v1
|
||||||
- name: Run remote tests
|
- name: Run remote tests
|
||||||
# Running this requires access to secrets, so skip if this is
|
|
||||||
# a PR from a fork.
|
|
||||||
if: github.event_name != 'pull_request' || !github.event.pull_request.head.repo.fork
|
|
||||||
run: CARGO_ARGS="--profile ci" make -C ./lancedb remote-tests
|
run: CARGO_ARGS="--profile ci" make -C ./lancedb remote-tests
|
||||||
|
|
||||||
macos:
|
macos:
|
||||||
|
|||||||
@@ -1,22 +0,0 @@
|
|||||||
name: Update package-lock.json
|
|
||||||
|
|
||||||
on:
|
|
||||||
workflow_dispatch:
|
|
||||||
|
|
||||||
permissions:
|
|
||||||
contents: read
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
publish:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- name: Checkout
|
|
||||||
uses: actions/checkout@v6
|
|
||||||
with:
|
|
||||||
ref: main
|
|
||||||
persist-credentials: false
|
|
||||||
fetch-depth: 0
|
|
||||||
lfs: true
|
|
||||||
- uses: ./.github/workflows/update_package_lock
|
|
||||||
with:
|
|
||||||
github_token: ${{ secrets.LANCEDB_RELEASE_TOKEN }}
|
|
||||||
@@ -1,22 +0,0 @@
|
|||||||
name: Update NodeJs package-lock.json
|
|
||||||
|
|
||||||
on:
|
|
||||||
workflow_dispatch:
|
|
||||||
|
|
||||||
permissions:
|
|
||||||
contents: read
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
publish:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- name: Checkout
|
|
||||||
uses: actions/checkout@v6
|
|
||||||
with:
|
|
||||||
ref: main
|
|
||||||
persist-credentials: false
|
|
||||||
fetch-depth: 0
|
|
||||||
lfs: true
|
|
||||||
- uses: ./.github/workflows/update_package_lock_nodejs
|
|
||||||
with:
|
|
||||||
github_token: ${{ secrets.LANCEDB_RELEASE_TOKEN }}
|
|
||||||
@@ -20,7 +20,10 @@ repos:
|
|||||||
hooks:
|
hooks:
|
||||||
- id: local-biome-check
|
- id: local-biome-check
|
||||||
name: biome check
|
name: biome check
|
||||||
entry: npx @biomejs/biome@1.8.3 check --config-path nodejs/biome.json nodejs/
|
# Use the biome from nodejs/package.json rather than a separately
|
||||||
|
# pinned one: the two drifted apart and disagreed on formatting, so
|
||||||
|
# this hook rejected code that `pnpm lint` accepted.
|
||||||
|
entry: nodejs/node_modules/.bin/biome check --config-path nodejs/biome.json nodejs/
|
||||||
language: system
|
language: system
|
||||||
types: [text]
|
types: [text]
|
||||||
files: "nodejs/.*"
|
files: "nodejs/.*"
|
||||||
|
|||||||
@@ -18,6 +18,9 @@ Common commands:
|
|||||||
* Run specific test: `cargo test --quiet --features remote -p <package_name> --test <test_name>`
|
* Run specific test: `cargo test --quiet --features remote -p <package_name> --test <test_name>`
|
||||||
* Lint: `cargo clippy --quiet --features remote --tests --examples`
|
* Lint: `cargo clippy --quiet --features remote --tests --examples`
|
||||||
* Format Rust: `cargo fmt --all`
|
* Format Rust: `cargo fmt --all`
|
||||||
|
* Use repository-defined Cargo profiles instead of ad hoc LTO overrides.
|
||||||
|
* Use `release-with-debug` for benchmarks and profiling so optimized builds keep debug symbols without a rebuild.
|
||||||
|
* Use `release-no-lto` only for local debugging, IO-bound benchmarks, or compile-time-sensitive performance investigation where LTO would not affect the measured bottleneck.
|
||||||
* Format Python: `ruff format .`
|
* Format Python: `ruff format .`
|
||||||
* Lint Python: `ruff check .`
|
* Lint Python: `ruff check .`
|
||||||
* Bootstrap Python dev env: `cd python && uv run --extra tests --extra dev maturin develop --extras tests,dev`
|
* Bootstrap Python dev env: `cd python && uv run --extra tests --extra dev maturin develop --extras tests,dev`
|
||||||
@@ -35,7 +38,7 @@ Before committing changes, run formatting for every language you touched. At min
|
|||||||
* Rust changes: run `cargo fmt --all`.
|
* Rust changes: run `cargo fmt --all`.
|
||||||
* Python changes: run `ruff format .` and `ruff check .` from the repository root,
|
* Python changes: run `ruff format .` and `ruff check .` from the repository root,
|
||||||
and run targeted tests through `cd python && uv run ...`.
|
and run targeted tests through `cd python && uv run ...`.
|
||||||
* TypeScript changes: run the relevant `npm`/`pnpm` lint, format, build, and docs commands in `nodejs`.
|
* TypeScript changes: run the relevant `pnpm` lint, format, build, and docs commands in `nodejs`.
|
||||||
|
|
||||||
Before creating a PR, the exact value passed to `gh pr create --title` must follow
|
Before creating a PR, the exact value passed to `gh pr create --title` must follow
|
||||||
Conventional Commits, such as `fix: support nested field paths in native index creation`
|
Conventional Commits, such as `fix: support nested field paths in native index creation`
|
||||||
@@ -98,12 +101,12 @@ Python bindings changes:
|
|||||||
TypeScript bindings changes:
|
TypeScript bindings changes:
|
||||||
|
|
||||||
1. Add napi-rs method binding on `Table` in `nodejs/src/table.rs`.
|
1. Add napi-rs method binding on `Table` in `nodejs/src/table.rs`.
|
||||||
2. Run `npm run build` to generate TypeScript definitions.
|
2. Run `pnpm build` to generate TypeScript definitions.
|
||||||
3. Add typescript method on abstract class `Table` in `nodejs/src/table.ts`.
|
3. Add typescript method on abstract class `Table` in `nodejs/src/table.ts`.
|
||||||
4. Add concrete method on `LocalTable` class in `nodejs/src/native_table.ts`.
|
4. Add concrete method on `LocalTable` class in `nodejs/src/native_table.ts`.
|
||||||
* Note: despite the name, this class is also used for remote tables.
|
* Note: despite the name, this class is also used for remote tables.
|
||||||
5. Add test in `nodejs/__test__/table.test.ts`.
|
5. Add test in `nodejs/__test__/table.test.ts`.
|
||||||
6. Run `npm run docs` to generate TypeScript documentation.
|
6. Run `pnpm run docs` to generate TypeScript documentation.
|
||||||
|
|
||||||
## Python API reference
|
## Python API reference
|
||||||
|
|
||||||
|
|||||||
Generated
+82
-75
@@ -535,9 +535,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "async-trait"
|
name = "async-trait"
|
||||||
version = "0.1.91"
|
version = "0.1.92"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "ae36dc4177970ef04fde5178d3e2429882def40e57a451f919c098f72baa6cec"
|
checksum = "82f6aeea286b8eb4dd3431a1be1b59d290ace00f5bfd8e2a159bc2a05e2c1667"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
@@ -1443,9 +1443,9 @@ checksum = "175812e0be2bccb6abe50bb8d566126198344f707e304f45c648fd8f2cc0365e"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "bytemuck"
|
name = "bytemuck"
|
||||||
version = "1.25.0"
|
version = "1.25.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "c8efb64bd706a16a1bdde310ae86b351e4d21550d98d056f22f8a7f7a2183fec"
|
checksum = "95832e849adfb21180ccb6826a99da14e5d266ae5c2e668e1602cf234f153797"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bytemuck_derive",
|
"bytemuck_derive",
|
||||||
]
|
]
|
||||||
@@ -1597,9 +1597,9 @@ checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "chacha20"
|
name = "chacha20"
|
||||||
version = "0.10.0"
|
version = "0.10.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "6f8d983286843e49675a4b7a2d174efe136dc93a18d69130dd18198a6c167601"
|
checksum = "65c35e4b699c7e15ccbe7ee35c005e4fc0a278d22238a2857e6ce2dadeda1b06"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"cfg-if 1.0.4",
|
"cfg-if 1.0.4",
|
||||||
"cpufeatures 0.3.0",
|
"cpufeatures 0.3.0",
|
||||||
@@ -1740,9 +1740,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "cmov"
|
name = "cmov"
|
||||||
version = "0.5.3"
|
version = "0.5.4"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "3f88a43d011fc4a6876cb7344703e297c71dda42494fee094d5f7c76bf13f746"
|
checksum = "0c9ea0ac24bc397ab3c98583a3c9ba74fa56b09a4449bbe172b9b1ddb016027a"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "colorchoice"
|
name = "colorchoice"
|
||||||
@@ -3455,8 +3455,8 @@ checksum = "42703706b716c37f96a77aea830392ad231f44c9e9a67872fa5548707e11b11c"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "fsst"
|
name = "fsst"
|
||||||
version = "11.0.0-beta.15"
|
version = "12.0.0-beta.11"
|
||||||
source = "git+https://github.com/lance-format/lance.git?tag=v11.0.0-beta.15#8064b3a27dc4e05a6ab6ceb439fa1be9950e00eb"
|
source = "git+https://github.com/lance-format/lance.git?tag=v12.0.0-beta.11#4a0e26895729feb86d0cb9c09d551bfd619c6472"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"arrow-array",
|
"arrow-array",
|
||||||
"rand 0.9.5",
|
"rand 0.9.5",
|
||||||
@@ -4815,8 +4815,8 @@ checksum = "e037a2e1d8d5fdbd49b16a4ea09d5d6401c1f29eca5ff29d03d3824dba16256a"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "lance"
|
name = "lance"
|
||||||
version = "11.0.0-beta.15"
|
version = "12.0.0-beta.11"
|
||||||
source = "git+https://github.com/lance-format/lance.git?tag=v11.0.0-beta.15#8064b3a27dc4e05a6ab6ceb439fa1be9950e00eb"
|
source = "git+https://github.com/lance-format/lance.git?tag=v12.0.0-beta.11#4a0e26895729feb86d0cb9c09d551bfd619c6472"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"arc-swap",
|
"arc-swap",
|
||||||
"arrow",
|
"arrow",
|
||||||
@@ -4888,8 +4888,8 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "lance-arrow"
|
name = "lance-arrow"
|
||||||
version = "11.0.0-beta.15"
|
version = "12.0.0-beta.11"
|
||||||
source = "git+https://github.com/lance-format/lance.git?tag=v11.0.0-beta.15#8064b3a27dc4e05a6ab6ceb439fa1be9950e00eb"
|
source = "git+https://github.com/lance-format/lance.git?tag=v12.0.0-beta.11#4a0e26895729feb86d0cb9c09d551bfd619c6472"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"arrow-array",
|
"arrow-array",
|
||||||
"arrow-buffer",
|
"arrow-buffer",
|
||||||
@@ -4911,7 +4911,7 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "lance-arrow-scalar"
|
name = "lance-arrow-scalar"
|
||||||
version = "58.0.0"
|
version = "58.0.0"
|
||||||
source = "git+https://github.com/lance-format/lance.git?tag=v11.0.0-beta.15#8064b3a27dc4e05a6ab6ceb439fa1be9950e00eb"
|
source = "git+https://github.com/lance-format/lance.git?tag=v12.0.0-beta.11#4a0e26895729feb86d0cb9c09d551bfd619c6472"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"arrow-array",
|
"arrow-array",
|
||||||
"arrow-buffer",
|
"arrow-buffer",
|
||||||
@@ -4925,7 +4925,7 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "lance-arrow-stats"
|
name = "lance-arrow-stats"
|
||||||
version = "58.0.0"
|
version = "58.0.0"
|
||||||
source = "git+https://github.com/lance-format/lance.git?tag=v11.0.0-beta.15#8064b3a27dc4e05a6ab6ceb439fa1be9950e00eb"
|
source = "git+https://github.com/lance-format/lance.git?tag=v12.0.0-beta.11#4a0e26895729feb86d0cb9c09d551bfd619c6472"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"arrow-array",
|
"arrow-array",
|
||||||
"arrow-schema",
|
"arrow-schema",
|
||||||
@@ -4934,8 +4934,8 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "lance-bitpacking"
|
name = "lance-bitpacking"
|
||||||
version = "11.0.0-beta.15"
|
version = "12.0.0-beta.11"
|
||||||
source = "git+https://github.com/lance-format/lance.git?tag=v11.0.0-beta.15#8064b3a27dc4e05a6ab6ceb439fa1be9950e00eb"
|
source = "git+https://github.com/lance-format/lance.git?tag=v12.0.0-beta.11#4a0e26895729feb86d0cb9c09d551bfd619c6472"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"arrayref",
|
"arrayref",
|
||||||
"crunchy",
|
"crunchy",
|
||||||
@@ -4945,8 +4945,8 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "lance-core"
|
name = "lance-core"
|
||||||
version = "11.0.0-beta.15"
|
version = "12.0.0-beta.11"
|
||||||
source = "git+https://github.com/lance-format/lance.git?tag=v11.0.0-beta.15#8064b3a27dc4e05a6ab6ceb439fa1be9950e00eb"
|
source = "git+https://github.com/lance-format/lance.git?tag=v12.0.0-beta.11#4a0e26895729feb86d0cb9c09d551bfd619c6472"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"arrow-array",
|
"arrow-array",
|
||||||
"arrow-buffer",
|
"arrow-buffer",
|
||||||
@@ -4983,8 +4983,8 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "lance-datafusion"
|
name = "lance-datafusion"
|
||||||
version = "11.0.0-beta.15"
|
version = "12.0.0-beta.11"
|
||||||
source = "git+https://github.com/lance-format/lance.git?tag=v11.0.0-beta.15#8064b3a27dc4e05a6ab6ceb439fa1be9950e00eb"
|
source = "git+https://github.com/lance-format/lance.git?tag=v12.0.0-beta.11#4a0e26895729feb86d0cb9c09d551bfd619c6472"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"arrow",
|
"arrow",
|
||||||
"arrow-array",
|
"arrow-array",
|
||||||
@@ -5000,6 +5000,7 @@ dependencies = [
|
|||||||
"datafusion-functions",
|
"datafusion-functions",
|
||||||
"datafusion-physical-expr",
|
"datafusion-physical-expr",
|
||||||
"futures",
|
"futures",
|
||||||
|
"half",
|
||||||
"jsonb",
|
"jsonb",
|
||||||
"lance-arrow",
|
"lance-arrow",
|
||||||
"lance-core",
|
"lance-core",
|
||||||
@@ -5013,8 +5014,8 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "lance-datagen"
|
name = "lance-datagen"
|
||||||
version = "11.0.0-beta.15"
|
version = "12.0.0-beta.11"
|
||||||
source = "git+https://github.com/lance-format/lance.git?tag=v11.0.0-beta.15#8064b3a27dc4e05a6ab6ceb439fa1be9950e00eb"
|
source = "git+https://github.com/lance-format/lance.git?tag=v12.0.0-beta.11#4a0e26895729feb86d0cb9c09d551bfd619c6472"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"arrow",
|
"arrow",
|
||||||
"arrow-array",
|
"arrow-array",
|
||||||
@@ -5031,8 +5032,8 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "lance-derive"
|
name = "lance-derive"
|
||||||
version = "11.0.0-beta.15"
|
version = "12.0.0-beta.11"
|
||||||
source = "git+https://github.com/lance-format/lance.git?tag=v11.0.0-beta.15#8064b3a27dc4e05a6ab6ceb439fa1be9950e00eb"
|
source = "git+https://github.com/lance-format/lance.git?tag=v12.0.0-beta.11#4a0e26895729feb86d0cb9c09d551bfd619c6472"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
@@ -5041,8 +5042,8 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "lance-encoding"
|
name = "lance-encoding"
|
||||||
version = "11.0.0-beta.15"
|
version = "12.0.0-beta.11"
|
||||||
source = "git+https://github.com/lance-format/lance.git?tag=v11.0.0-beta.15#8064b3a27dc4e05a6ab6ceb439fa1be9950e00eb"
|
source = "git+https://github.com/lance-format/lance.git?tag=v12.0.0-beta.11#4a0e26895729feb86d0cb9c09d551bfd619c6472"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"arrow-arith",
|
"arrow-arith",
|
||||||
"arrow-array",
|
"arrow-array",
|
||||||
@@ -5075,8 +5076,8 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "lance-file"
|
name = "lance-file"
|
||||||
version = "11.0.0-beta.15"
|
version = "12.0.0-beta.11"
|
||||||
source = "git+https://github.com/lance-format/lance.git?tag=v11.0.0-beta.15#8064b3a27dc4e05a6ab6ceb439fa1be9950e00eb"
|
source = "git+https://github.com/lance-format/lance.git?tag=v12.0.0-beta.11#4a0e26895729feb86d0cb9c09d551bfd619c6472"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"arrow-arith",
|
"arrow-arith",
|
||||||
"arrow-array",
|
"arrow-array",
|
||||||
@@ -5107,8 +5108,8 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "lance-index"
|
name = "lance-index"
|
||||||
version = "11.0.0-beta.15"
|
version = "12.0.0-beta.11"
|
||||||
source = "git+https://github.com/lance-format/lance.git?tag=v11.0.0-beta.15#8064b3a27dc4e05a6ab6ceb439fa1be9950e00eb"
|
source = "git+https://github.com/lance-format/lance.git?tag=v12.0.0-beta.11#4a0e26895729feb86d0cb9c09d551bfd619c6472"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"arc-swap",
|
"arc-swap",
|
||||||
"arrow",
|
"arrow",
|
||||||
@@ -5172,8 +5173,8 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "lance-index-core"
|
name = "lance-index-core"
|
||||||
version = "11.0.0-beta.15"
|
version = "12.0.0-beta.11"
|
||||||
source = "git+https://github.com/lance-format/lance.git?tag=v11.0.0-beta.15#8064b3a27dc4e05a6ab6ceb439fa1be9950e00eb"
|
source = "git+https://github.com/lance-format/lance.git?tag=v12.0.0-beta.11#4a0e26895729feb86d0cb9c09d551bfd619c6472"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"arrow-array",
|
"arrow-array",
|
||||||
"arrow-schema",
|
"arrow-schema",
|
||||||
@@ -5195,8 +5196,8 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "lance-io"
|
name = "lance-io"
|
||||||
version = "11.0.0-beta.15"
|
version = "12.0.0-beta.11"
|
||||||
source = "git+https://github.com/lance-format/lance.git?tag=v11.0.0-beta.15#8064b3a27dc4e05a6ab6ceb439fa1be9950e00eb"
|
source = "git+https://github.com/lance-format/lance.git?tag=v12.0.0-beta.11#4a0e26895729feb86d0cb9c09d551bfd619c6472"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"arrow",
|
"arrow",
|
||||||
"arrow-array",
|
"arrow-array",
|
||||||
@@ -5222,7 +5223,11 @@ dependencies = [
|
|||||||
"pin-project",
|
"pin-project",
|
||||||
"prost",
|
"prost",
|
||||||
"rand 0.9.5",
|
"rand 0.9.5",
|
||||||
|
"reqsign-core",
|
||||||
|
"reqsign-file-read-tokio",
|
||||||
|
"reqsign-google",
|
||||||
"serde",
|
"serde",
|
||||||
|
"serde_json",
|
||||||
"tempfile",
|
"tempfile",
|
||||||
"tokio",
|
"tokio",
|
||||||
"tracing",
|
"tracing",
|
||||||
@@ -5232,8 +5237,8 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "lance-linalg"
|
name = "lance-linalg"
|
||||||
version = "11.0.0-beta.15"
|
version = "12.0.0-beta.11"
|
||||||
source = "git+https://github.com/lance-format/lance.git?tag=v11.0.0-beta.15#8064b3a27dc4e05a6ab6ceb439fa1be9950e00eb"
|
source = "git+https://github.com/lance-format/lance.git?tag=v12.0.0-beta.11#4a0e26895729feb86d0cb9c09d551bfd619c6472"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"arrow-array",
|
"arrow-array",
|
||||||
"arrow-schema",
|
"arrow-schema",
|
||||||
@@ -5247,8 +5252,8 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "lance-namespace"
|
name = "lance-namespace"
|
||||||
version = "11.0.0-beta.15"
|
version = "12.0.0-beta.11"
|
||||||
source = "git+https://github.com/lance-format/lance.git?tag=v11.0.0-beta.15#8064b3a27dc4e05a6ab6ceb439fa1be9950e00eb"
|
source = "git+https://github.com/lance-format/lance.git?tag=v12.0.0-beta.11#4a0e26895729feb86d0cb9c09d551bfd619c6472"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"arrow",
|
"arrow",
|
||||||
"async-trait",
|
"async-trait",
|
||||||
@@ -5260,8 +5265,8 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "lance-namespace-impls"
|
name = "lance-namespace-impls"
|
||||||
version = "11.0.0-beta.15"
|
version = "12.0.0-beta.11"
|
||||||
source = "git+https://github.com/lance-format/lance.git?tag=v11.0.0-beta.15#8064b3a27dc4e05a6ab6ceb439fa1be9950e00eb"
|
source = "git+https://github.com/lance-format/lance.git?tag=v12.0.0-beta.11#4a0e26895729feb86d0cb9c09d551bfd619c6472"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"arrow",
|
"arrow",
|
||||||
"arrow-ipc",
|
"arrow-ipc",
|
||||||
@@ -5300,9 +5305,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "lance-namespace-reqwest-client"
|
name = "lance-namespace-reqwest-client"
|
||||||
version = "0.11.0"
|
version = "0.11.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "0a030196da1c994b63a96a4f0bf5b0cfa459fe6dadc9e962320246ca328da22a"
|
checksum = "1d06b1fbb5d41f93bc652b61e2872af92e8a6c5f6b4ce8839a8ecfa05365d359"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"reqwest 0.12.28",
|
"reqwest 0.12.28",
|
||||||
"serde",
|
"serde",
|
||||||
@@ -5314,8 +5319,8 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "lance-select"
|
name = "lance-select"
|
||||||
version = "11.0.0-beta.15"
|
version = "12.0.0-beta.11"
|
||||||
source = "git+https://github.com/lance-format/lance.git?tag=v11.0.0-beta.15#8064b3a27dc4e05a6ab6ceb439fa1be9950e00eb"
|
source = "git+https://github.com/lance-format/lance.git?tag=v12.0.0-beta.11#4a0e26895729feb86d0cb9c09d551bfd619c6472"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"arrow-array",
|
"arrow-array",
|
||||||
"arrow-buffer",
|
"arrow-buffer",
|
||||||
@@ -5329,8 +5334,8 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "lance-table"
|
name = "lance-table"
|
||||||
version = "11.0.0-beta.15"
|
version = "12.0.0-beta.11"
|
||||||
source = "git+https://github.com/lance-format/lance.git?tag=v11.0.0-beta.15#8064b3a27dc4e05a6ab6ceb439fa1be9950e00eb"
|
source = "git+https://github.com/lance-format/lance.git?tag=v12.0.0-beta.11#4a0e26895729feb86d0cb9c09d551bfd619c6472"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"arrow",
|
"arrow",
|
||||||
"arrow-array",
|
"arrow-array",
|
||||||
@@ -5370,8 +5375,8 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "lance-testing"
|
name = "lance-testing"
|
||||||
version = "11.0.0-beta.15"
|
version = "12.0.0-beta.11"
|
||||||
source = "git+https://github.com/lance-format/lance.git?tag=v11.0.0-beta.15#8064b3a27dc4e05a6ab6ceb439fa1be9950e00eb"
|
source = "git+https://github.com/lance-format/lance.git?tag=v12.0.0-beta.11#4a0e26895729feb86d0cb9c09d551bfd619c6472"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"arrow-array",
|
"arrow-array",
|
||||||
"arrow-schema",
|
"arrow-schema",
|
||||||
@@ -5384,8 +5389,8 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "lance-tokenizer"
|
name = "lance-tokenizer"
|
||||||
version = "11.0.0-beta.15"
|
version = "12.0.0-beta.11"
|
||||||
source = "git+https://github.com/lance-format/lance.git?tag=v11.0.0-beta.15#8064b3a27dc4e05a6ab6ceb439fa1be9950e00eb"
|
source = "git+https://github.com/lance-format/lance.git?tag=v12.0.0-beta.11#4a0e26895729feb86d0cb9c09d551bfd619c6472"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"frostem",
|
"frostem",
|
||||||
"icu_segmenter",
|
"icu_segmenter",
|
||||||
@@ -5398,7 +5403,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "lancedb"
|
name = "lancedb"
|
||||||
version = "0.38.0-beta.2"
|
version = "0.39.0-beta.1"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"ahash",
|
"ahash",
|
||||||
"anyhow",
|
"anyhow",
|
||||||
@@ -5486,7 +5491,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "lancedb-nodejs"
|
name = "lancedb-nodejs"
|
||||||
version = "0.38.0-beta.2"
|
version = "0.39.0-beta.1"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"arrow-array",
|
"arrow-array",
|
||||||
"arrow-buffer",
|
"arrow-buffer",
|
||||||
@@ -5511,7 +5516,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "lancedb-python"
|
name = "lancedb-python"
|
||||||
version = "0.38.0-beta.2"
|
version = "0.39.0-beta.1"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"arrow",
|
"arrow",
|
||||||
"async-trait",
|
"async-trait",
|
||||||
@@ -5744,9 +5749,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "log"
|
name = "log"
|
||||||
version = "0.4.33"
|
version = "0.4.34"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "0ceec5bc11778974d1bcb055b18002eba7f4b3518b6a0081b3af5f21666da9ad"
|
checksum = "f9f8bd3e56ce4dfc153cf470fffbfa98c7620958b312ca5c3a4b8d5181fd13c6"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "loom"
|
name = "loom"
|
||||||
@@ -5997,9 +6002,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "moka"
|
name = "moka"
|
||||||
version = "0.12.15"
|
version = "0.12.16"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "957228ad12042ee839f93c8f257b62b4c0ab5eaae1d4fa60de53b27c9d7c5046"
|
checksum = "4293f18e7567a1caf3c584855554377025c65e0aa445344d04171f5ad63d19b9"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"async-lock",
|
"async-lock",
|
||||||
"crossbeam-channel",
|
"crossbeam-channel",
|
||||||
@@ -6093,14 +6098,15 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "napi"
|
name = "napi"
|
||||||
version = "3.11.0"
|
version = "3.12.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "de33522036981030a75c231829566bc63414e08101a6f5ff4ac6cef19c8e0941"
|
checksum = "58c5f4d5375213fdb7be2655e152386e82f026f9a5ba36a75556e11359aafe09"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bitflags 2.11.1",
|
"bitflags 2.11.1",
|
||||||
"chrono",
|
"chrono",
|
||||||
"ctor 1.0.12",
|
"ctor 1.0.12",
|
||||||
"futures",
|
"futures",
|
||||||
|
"libc",
|
||||||
"napi-build",
|
"napi-build",
|
||||||
"napi-sys",
|
"napi-sys",
|
||||||
"nohash-hasher",
|
"nohash-hasher",
|
||||||
@@ -6112,15 +6118,15 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "napi-build"
|
name = "napi-build"
|
||||||
version = "2.4.0"
|
version = "2.4.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "5282704fbe8d49b0cf8b08e3f33233416a528658f205c7e5ace63b582de0b11c"
|
checksum = "60fdf9b392c50e7c4170fa633bd909490ed7835cea4c046776d1a4dd8d2ae0ab"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "napi-derive"
|
name = "napi-derive"
|
||||||
version = "3.6.1"
|
version = "3.6.3"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "4d5c9c02556ea6dc99dffd36c1ce60141411657438501a125b675776d011ce92"
|
checksum = "0fa55ea69990c90b888e9e77044410e304ce7f35de599dc6d0b5c1923d2e59af"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"convert_case",
|
"convert_case",
|
||||||
"ctor 1.0.12",
|
"ctor 1.0.12",
|
||||||
@@ -6132,9 +6138,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "napi-derive-backend"
|
name = "napi-derive-backend"
|
||||||
version = "6.1.1"
|
version = "6.1.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "d60b5d773ad46c698c8cc2cd9fde0b283d39cbb7f71c04bee633c7bdba4423bd"
|
checksum = "df4056ac7c18e4438ccf0edaed4340ca0d269278c8ec19284f7b23cb039fd0ae"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"convert_case",
|
"convert_case",
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
@@ -8597,9 +8603,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "roaring"
|
name = "roaring"
|
||||||
version = "0.11.4"
|
version = "0.11.5"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "1dedc5658c6ecb3bdb5ef5f3295bb9253f42dcf3fd1402c03f6b1f7659c3c4a9"
|
checksum = "18bd8a37d17a58532776dcdf6041ce64929adca78e8489d5cacbafe99229d3e1"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bytemuck",
|
"bytemuck",
|
||||||
"byteorder",
|
"byteorder",
|
||||||
@@ -9059,9 +9065,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "serde_with"
|
name = "serde_with"
|
||||||
version = "3.21.0"
|
version = "3.22.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "76a5c54c7310e7b8b9577c286d7e399ddd876c3e12b3ed917a8aabc4b96e9e8c"
|
checksum = "ee78f1fbe43ac4a0e47aadb3dbd357b69eb0d3793e948624cd03dd2750ab1c0a"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"base64 0.22.1",
|
"base64 0.22.1",
|
||||||
"bs58",
|
"bs58",
|
||||||
@@ -9069,6 +9075,7 @@ dependencies = [
|
|||||||
"hex",
|
"hex",
|
||||||
"indexmap 1.9.3",
|
"indexmap 1.9.3",
|
||||||
"indexmap 2.14.0",
|
"indexmap 2.14.0",
|
||||||
|
"jiff",
|
||||||
"schemars 0.9.0",
|
"schemars 0.9.0",
|
||||||
"schemars 1.2.1",
|
"schemars 1.2.1",
|
||||||
"serde_core",
|
"serde_core",
|
||||||
@@ -9079,9 +9086,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "serde_with_macros"
|
name = "serde_with_macros"
|
||||||
version = "3.21.0"
|
version = "3.22.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "84d57bc0c8b9a17920c178daa6bb924850d54a9c97ab45194bb8c17ad66bb660"
|
checksum = "8705578779c2b6bd90d84d66eb2e206b708b1a4d7b9f17641b293545bf1c7e46"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"darling 0.23.0",
|
"darling 0.23.0",
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
@@ -10448,9 +10455,9 @@ checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "uuid"
|
name = "uuid"
|
||||||
version = "1.24.0"
|
version = "1.26.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "bf3923a6f5c4c6382e0b653c4117f48d631ea17f38ed86e2a828e6f7412f5239"
|
checksum = "b5772d71c9be8a8a6ac2117d949c5b224c1b72241bb611d9a3012edcf8af7812"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"getrandom 0.4.2",
|
"getrandom 0.4.2",
|
||||||
"js-sys",
|
"js-sys",
|
||||||
|
|||||||
+22
-15
@@ -13,20 +13,21 @@ categories = ["database-implementations"]
|
|||||||
rust-version = "1.91.0"
|
rust-version = "1.91.0"
|
||||||
|
|
||||||
[workspace.dependencies]
|
[workspace.dependencies]
|
||||||
lance = { "version" = "=11.0.0-beta.15", default-features = false, "tag" = "v11.0.0-beta.15", "git" = "https://github.com/lance-format/lance.git" }
|
lance = { "version" = "=12.0.0-beta.11", default-features = false, "tag" = "v12.0.0-beta.11", "git" = "https://github.com/lance-format/lance.git" }
|
||||||
lance-core = { "version" = "=11.0.0-beta.15", "tag" = "v11.0.0-beta.15", "git" = "https://github.com/lance-format/lance.git" }
|
lance-core = { "version" = "=12.0.0-beta.11", "tag" = "v12.0.0-beta.11", "git" = "https://github.com/lance-format/lance.git" }
|
||||||
lance-datagen = { "version" = "=11.0.0-beta.15", "tag" = "v11.0.0-beta.15", "git" = "https://github.com/lance-format/lance.git" }
|
lance-datagen = { "version" = "=12.0.0-beta.11", "tag" = "v12.0.0-beta.11", "git" = "https://github.com/lance-format/lance.git" }
|
||||||
lance-file = { "version" = "=11.0.0-beta.15", "tag" = "v11.0.0-beta.15", "git" = "https://github.com/lance-format/lance.git" }
|
lance-file = { "version" = "=12.0.0-beta.11", "tag" = "v12.0.0-beta.11", "git" = "https://github.com/lance-format/lance.git" }
|
||||||
lance-io = { "version" = "=11.0.0-beta.15", default-features = false, "tag" = "v11.0.0-beta.15", "git" = "https://github.com/lance-format/lance.git" }
|
lance-io = { "version" = "=12.0.0-beta.11", default-features = false, "tag" = "v12.0.0-beta.11", "git" = "https://github.com/lance-format/lance.git" }
|
||||||
lance-index = { "version" = "=11.0.0-beta.15", "tag" = "v11.0.0-beta.15", "git" = "https://github.com/lance-format/lance.git" }
|
lance-index = { "version" = "=12.0.0-beta.11", "tag" = "v12.0.0-beta.11", "git" = "https://github.com/lance-format/lance.git" }
|
||||||
lance-linalg = { "version" = "=11.0.0-beta.15", "tag" = "v11.0.0-beta.15", "git" = "https://github.com/lance-format/lance.git" }
|
lance-linalg = { "version" = "=12.0.0-beta.11", "tag" = "v12.0.0-beta.11", "git" = "https://github.com/lance-format/lance.git" }
|
||||||
lance-namespace = { "version" = "=11.0.0-beta.15", "tag" = "v11.0.0-beta.15", "git" = "https://github.com/lance-format/lance.git" }
|
lance-namespace = { "version" = "=12.0.0-beta.11", "tag" = "v12.0.0-beta.11", "git" = "https://github.com/lance-format/lance.git" }
|
||||||
lance-namespace-impls = { "version" = "=11.0.0-beta.15", default-features = false, "tag" = "v11.0.0-beta.15", "git" = "https://github.com/lance-format/lance.git" }
|
lance-namespace-impls = { "version" = "=12.0.0-beta.11", default-features = false, "tag" = "v12.0.0-beta.11", "git" = "https://github.com/lance-format/lance.git" }
|
||||||
lance-table = { "version" = "=11.0.0-beta.15", "tag" = "v11.0.0-beta.15", "git" = "https://github.com/lance-format/lance.git" }
|
lance-table = { "version" = "=12.0.0-beta.11", "tag" = "v12.0.0-beta.11", "git" = "https://github.com/lance-format/lance.git" }
|
||||||
lance-testing = { "version" = "=11.0.0-beta.15", "tag" = "v11.0.0-beta.15", "git" = "https://github.com/lance-format/lance.git" }
|
lance-testing = { "version" = "=12.0.0-beta.11", "tag" = "v12.0.0-beta.11", "git" = "https://github.com/lance-format/lance.git" }
|
||||||
lance-datafusion = { "version" = "=11.0.0-beta.15", "tag" = "v11.0.0-beta.15", "git" = "https://github.com/lance-format/lance.git" }
|
lance-datafusion = { "version" = "=12.0.0-beta.11", "tag" = "v12.0.0-beta.11", "git" = "https://github.com/lance-format/lance.git" }
|
||||||
lance-encoding = { "version" = "=11.0.0-beta.15", "tag" = "v11.0.0-beta.15", "git" = "https://github.com/lance-format/lance.git" }
|
lance-encoding = { "version" = "=12.0.0-beta.11", "tag" = "v12.0.0-beta.11", "git" = "https://github.com/lance-format/lance.git" }
|
||||||
lance-arrow = { "version" = "=11.0.0-beta.15", "tag" = "v11.0.0-beta.15", "git" = "https://github.com/lance-format/lance.git" }
|
lance-arrow = { "version" = "=12.0.0-beta.11", "tag" = "v12.0.0-beta.11", "git" = "https://github.com/lance-format/lance.git" }
|
||||||
|
lancedb = { path = "rust/lancedb", default-features = false }
|
||||||
ahash = "0.8"
|
ahash = "0.8"
|
||||||
# Note that this one does not include pyarrow
|
# Note that this one does not include pyarrow
|
||||||
arrow = { version = "58.0.0", optional = false }
|
arrow = { version = "58.0.0", optional = false }
|
||||||
@@ -39,6 +40,7 @@ arrow-schema = "58.0.0"
|
|||||||
arrow-select = "58.0.0"
|
arrow-select = "58.0.0"
|
||||||
arrow-cast = "58.0.0"
|
arrow-cast = "58.0.0"
|
||||||
async-trait = "0"
|
async-trait = "0"
|
||||||
|
bytes = "1"
|
||||||
datafusion = { version = "54.0.0", default-features = false }
|
datafusion = { version = "54.0.0", default-features = false }
|
||||||
datafusion-catalog = "54.0.0"
|
datafusion-catalog = "54.0.0"
|
||||||
datafusion-common = { version = "54.0.0", default-features = false }
|
datafusion-common = { version = "54.0.0", default-features = false }
|
||||||
@@ -65,7 +67,12 @@ url = "2"
|
|||||||
num-traits = "0.2"
|
num-traits = "0.2"
|
||||||
regex = "1.10"
|
regex = "1.10"
|
||||||
semver = "1.0.25"
|
semver = "1.0.25"
|
||||||
chrono = "0.4"
|
serde = "1"
|
||||||
|
serde_json = "1"
|
||||||
|
tempfile = "3.5.0"
|
||||||
|
tokio = { version = "1.23", features = ["rt-multi-thread", "sync"] }
|
||||||
|
uuid = { version = "1.7.0", features = ["v4"] }
|
||||||
|
chrono = { version = "0.4", default-features = false, features = ["clock"] }
|
||||||
|
|
||||||
[profile.ci]
|
[profile.ci]
|
||||||
debug = "line-tables-only"
|
debug = "line-tables-only"
|
||||||
|
|||||||
@@ -5,5 +5,5 @@ licenses:
|
|||||||
cd python && cargo about generate ../about.hbs -o RUST_THIRD_PARTY_LICENSES.html -c ../about.toml
|
cd python && cargo about generate ../about.hbs -o RUST_THIRD_PARTY_LICENSES.html -c ../about.toml
|
||||||
cd python && uv sync --all-extras && uv tool run pip-licenses --python .venv/bin/python --format=markdown --with-urls --output-file=PYTHON_THIRD_PARTY_LICENSES.md
|
cd python && uv sync --all-extras && uv tool run pip-licenses --python .venv/bin/python --format=markdown --with-urls --output-file=PYTHON_THIRD_PARTY_LICENSES.md
|
||||||
cd nodejs && cargo about generate ../about.hbs -o RUST_THIRD_PARTY_LICENSES.html -c ../about.toml
|
cd nodejs && cargo about generate ../about.hbs -o RUST_THIRD_PARTY_LICENSES.html -c ../about.toml
|
||||||
cd nodejs && npx license-checker --markdown --out NODEJS_THIRD_PARTY_LICENSES.md
|
cd nodejs && pnpm dlx license-checker@25 --markdown --out NODEJS_THIRD_PARTY_LICENSES.md
|
||||||
cd java && ./mvnw license:aggregate-add-third-party -q
|
cd java && ./mvnw license:aggregate-add-third-party -q
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
Check whether there are any breaking changes in the PRs between the base and head commits.
|
Check whether there are any breaking changes in the PRs between the base and head commits.
|
||||||
If there are, assert that we have incremented the minor version.
|
If there are, assert that we have incremented the minor version.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import argparse
|
import argparse
|
||||||
import os
|
import os
|
||||||
from packaging.version import parse
|
from packaging.version import parse
|
||||||
@@ -27,7 +28,7 @@ if __name__ == "__main__":
|
|||||||
else:
|
else:
|
||||||
print("No breaking changes found.")
|
print("No breaking changes found.")
|
||||||
exit(0)
|
exit(0)
|
||||||
|
|
||||||
last_stable_version = parse(args.last_stable_version)
|
last_stable_version = parse(args.last_stable_version)
|
||||||
current_version = parse(args.current_version)
|
current_version = parse(args.current_version)
|
||||||
if current_version.minor <= last_stable_version.minor:
|
if current_version.minor <= last_stable_version.minor:
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
"""Determine whether a newer Lance tag exists and expose results for CI."""
|
"""Determine whether a newer Lance tag exists and expose results for CI."""
|
||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import argparse
|
import argparse
|
||||||
@@ -36,8 +37,16 @@ class SemVer:
|
|||||||
prerelease: Tuple[Union[int, str], ...]
|
prerelease: Tuple[Union[int, str], ...]
|
||||||
|
|
||||||
def __lt__(self, other: "SemVer") -> bool: # pragma: no cover - simple comparison
|
def __lt__(self, other: "SemVer") -> bool: # pragma: no cover - simple comparison
|
||||||
if (self.major, self.minor, self.patch) != (other.major, other.minor, other.patch):
|
if (self.major, self.minor, self.patch) != (
|
||||||
return (self.major, self.minor, self.patch) < (other.major, other.minor, other.patch)
|
other.major,
|
||||||
|
other.minor,
|
||||||
|
other.patch,
|
||||||
|
):
|
||||||
|
return (self.major, self.minor, self.patch) < (
|
||||||
|
other.major,
|
||||||
|
other.minor,
|
||||||
|
other.patch,
|
||||||
|
)
|
||||||
if self.prerelease == other.prerelease:
|
if self.prerelease == other.prerelease:
|
||||||
return False
|
return False
|
||||||
if not self.prerelease:
|
if not self.prerelease:
|
||||||
@@ -142,7 +151,9 @@ def read_current_version(repo_root: Path) -> str:
|
|||||||
deps = data["workspace"]["dependencies"]
|
deps = data["workspace"]["dependencies"]
|
||||||
entry = deps["lance"]
|
entry = deps["lance"]
|
||||||
except KeyError as exc: # pragma: no cover - configuration guard
|
except KeyError as exc: # pragma: no cover - configuration guard
|
||||||
raise RuntimeError("Failed to locate workspace.dependencies.lance in Cargo.toml") from exc
|
raise RuntimeError(
|
||||||
|
"Failed to locate workspace.dependencies.lance in Cargo.toml"
|
||||||
|
) from exc
|
||||||
|
|
||||||
if isinstance(entry, str):
|
if isinstance(entry, str):
|
||||||
raw_version = entry
|
raw_version = entry
|
||||||
|
|||||||
+9
-6
@@ -1,6 +1,7 @@
|
|||||||
# SPDX-License-Identifier: Apache-2.0
|
# SPDX-License-Identifier: Apache-2.0
|
||||||
# SPDX-FileCopyrightText: Copyright The LanceDB Authors
|
# SPDX-FileCopyrightText: Copyright The LanceDB Authors
|
||||||
"""A zero-dependency mock OpenAI embeddings API endpoint for testing purposes."""
|
"""A zero-dependency mock OpenAI embeddings API endpoint for testing purposes."""
|
||||||
|
|
||||||
import argparse
|
import argparse
|
||||||
import json
|
import json
|
||||||
import http.server
|
import http.server
|
||||||
@@ -22,11 +23,13 @@ class MockOpenAIRequestHandler(http.server.BaseHTTPRequestHandler):
|
|||||||
|
|
||||||
data = []
|
data = []
|
||||||
for i in range(num_inputs):
|
for i in range(num_inputs):
|
||||||
data.append({
|
data.append(
|
||||||
"object": "embedding",
|
{
|
||||||
"embedding": [0.1] * 1536,
|
"object": "embedding",
|
||||||
"index": i,
|
"embedding": [0.1] * 1536,
|
||||||
})
|
"index": i,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
response = {
|
response = {
|
||||||
"object": "list",
|
"object": "list",
|
||||||
@@ -35,7 +38,7 @@ class MockOpenAIRequestHandler(http.server.BaseHTTPRequestHandler):
|
|||||||
"usage": {
|
"usage": {
|
||||||
"prompt_tokens": 0,
|
"prompt_tokens": 0,
|
||||||
"total_tokens": 0,
|
"total_tokens": 0,
|
||||||
}
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
self.send_response(200)
|
self.send_response(200)
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ from packaging.version import parse, InvalidVersion
|
|||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
import argparse
|
import argparse
|
||||||
|
|
||||||
parser = argparse.ArgumentParser()
|
parser = argparse.ArgumentParser()
|
||||||
parser.add_argument("prefix", default="v")
|
parser.add_argument("prefix", default="v")
|
||||||
args = parser.parse_args()
|
args = parser.parse_args()
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ def run_command(command: str) -> str:
|
|||||||
def get_latest_stable_version() -> str:
|
def get_latest_stable_version() -> str:
|
||||||
version_line = run_command("cargo info lance | grep '^version:'")
|
version_line = run_command("cargo info lance | grep '^version:'")
|
||||||
# Example output: "version: 0.35.0 (latest 0.37.0)"
|
# Example output: "version: 0.35.0 (latest 0.37.0)"
|
||||||
match = re.search(r'\(latest ([0-9.]+)\)', version_line)
|
match = re.search(r"\(latest ([0-9.]+)\)", version_line)
|
||||||
if match:
|
if match:
|
||||||
return match.group(1)
|
return match.group(1)
|
||||||
# Fallback: use the first version after 'version:'
|
# Fallback: use the first version after 'version:'
|
||||||
@@ -69,7 +69,7 @@ def extract_default_features(line: str) -> bool:
|
|||||||
"""
|
"""
|
||||||
import re
|
import re
|
||||||
|
|
||||||
match = re.search(r'default-features\s*=\s*false', line)
|
match = re.search(r"default-features\s*=\s*false", line)
|
||||||
return match is not None
|
return match is not None
|
||||||
|
|
||||||
|
|
||||||
@@ -104,7 +104,7 @@ def dict_to_toml_line(package_name: str, config: dict) -> str:
|
|||||||
# This shouldn't happen with our current usage
|
# This shouldn't happen with our current usage
|
||||||
parts.append(f'"{key}" = {json.dumps(value)}')
|
parts.append(f'"{key}" = {json.dumps(value)}')
|
||||||
|
|
||||||
return f'{package_name} = {{ {", ".join(parts)} }}\n'
|
return f"{package_name} = {{ {', '.join(parts)} }}\n"
|
||||||
|
|
||||||
|
|
||||||
def update_cargo_toml(line_updater):
|
def update_cargo_toml(line_updater):
|
||||||
@@ -119,7 +119,7 @@ def update_cargo_toml(line_updater):
|
|||||||
lance_line = ""
|
lance_line = ""
|
||||||
is_parsing_lance_line = False
|
is_parsing_lance_line = False
|
||||||
for line in lines:
|
for line in lines:
|
||||||
if line.startswith("lance"):
|
if re.match(r"^lance(?:\s|[-_])", line):
|
||||||
# Check if this is a single-line or multi-line entry
|
# Check if this is a single-line or multi-line entry
|
||||||
# Single-line entries either:
|
# Single-line entries either:
|
||||||
# 1. End with } (complete inline table)
|
# 1. End with } (complete inline table)
|
||||||
|
|||||||
@@ -0,0 +1,185 @@
|
|||||||
|
import os
|
||||||
|
import stat
|
||||||
|
import subprocess
|
||||||
|
import sys
|
||||||
|
import tempfile
|
||||||
|
import textwrap
|
||||||
|
import unittest
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
|
||||||
|
REPO_ROOT = Path(__file__).resolve().parents[2]
|
||||||
|
SCRIPT = REPO_ROOT / "ci" / "set_lance_version.py"
|
||||||
|
LANCE_GIT_URL = "https://github.com/lance-format/lance.git"
|
||||||
|
|
||||||
|
CARGO_TOML = """\
|
||||||
|
[workspace.dependencies]
|
||||||
|
lance = { "version" = "=1.0.0", default-features = false, "features" = ["dynamodb"] }
|
||||||
|
lance-core = "1.0.0"
|
||||||
|
lance_datafusion = {
|
||||||
|
"version" = "=1.0.0",
|
||||||
|
"features" = ["substrait"]
|
||||||
|
}
|
||||||
|
lancedb = { path = "rust/lancedb", default-features = false }
|
||||||
|
lancedb-common = { path = "rust/lancedb-common" }
|
||||||
|
lancewood = "1.0.0"
|
||||||
|
my-lance = "1.0.0"
|
||||||
|
"""
|
||||||
|
|
||||||
|
UNTOUCHED_DEPENDENCIES = """\
|
||||||
|
lancedb = { path = "rust/lancedb", default-features = false }
|
||||||
|
lancedb-common = { path = "rust/lancedb-common" }
|
||||||
|
lancewood = "1.0.0"
|
||||||
|
my-lance = "1.0.0"
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
class SetLanceVersionTest(unittest.TestCase):
|
||||||
|
def test_supported_update_modes_only_rewrite_lance_dependencies(self):
|
||||||
|
cases = {
|
||||||
|
"stable": (
|
||||||
|
"""\
|
||||||
|
lance = { "version" = "=9.9.9", default-features = false, "features" = ["dynamodb"] }
|
||||||
|
lance-core = "=9.9.9"
|
||||||
|
lance_datafusion = { "version" = "=9.9.9", "features" = ["substrait"] }
|
||||||
|
""",
|
||||||
|
["cargo info lance", "cargo metadata"],
|
||||||
|
),
|
||||||
|
"preview": (
|
||||||
|
f"""\
|
||||||
|
lance = {{ "version" = "=10.0.0-beta.3", default-features = false, "features" = ["dynamodb"], "tag" = "v10.0.0-beta.3", "git" = "{LANCE_GIT_URL}" }}
|
||||||
|
lance-core = {{ "version" = "=10.0.0-beta.3", "tag" = "v10.0.0-beta.3", "git" = "{LANCE_GIT_URL}" }}
|
||||||
|
lance_datafusion = {{ "version" = "=10.0.0-beta.3", "features" = ["substrait"], "tag" = "v10.0.0-beta.3", "git" = "{LANCE_GIT_URL}" }}
|
||||||
|
""",
|
||||||
|
["git ls-remote --tags", "cargo metadata"],
|
||||||
|
),
|
||||||
|
"local": (
|
||||||
|
"""\
|
||||||
|
lance = { "path" = "../lance/rust/lance", default-features = false, "features" = ["dynamodb"] }
|
||||||
|
lance-core = { "path" = "../lance/rust/lance-core" }
|
||||||
|
lance_datafusion = { "path" = "../lance/rust/lance_datafusion", "features" = ["substrait"] }
|
||||||
|
""",
|
||||||
|
["cargo metadata"],
|
||||||
|
),
|
||||||
|
"v8.1.2": (
|
||||||
|
"""\
|
||||||
|
lance = { "version" = "=8.1.2", default-features = false, "features" = ["dynamodb"] }
|
||||||
|
lance-core = "=8.1.2"
|
||||||
|
lance_datafusion = { "version" = "=8.1.2", "features" = ["substrait"] }
|
||||||
|
""",
|
||||||
|
["cargo metadata"],
|
||||||
|
),
|
||||||
|
"v8.2.0-beta.4": (
|
||||||
|
f"""\
|
||||||
|
lance = {{ "version" = "=8.2.0-beta.4", default-features = false, "features" = ["dynamodb"], "tag" = "v8.2.0-beta.4", "git" = "{LANCE_GIT_URL}" }}
|
||||||
|
lance-core = {{ "version" = "=8.2.0-beta.4", "tag" = "v8.2.0-beta.4", "git" = "{LANCE_GIT_URL}" }}
|
||||||
|
lance_datafusion = {{ "version" = "=8.2.0-beta.4", "features" = ["substrait"], "tag" = "v8.2.0-beta.4", "git" = "{LANCE_GIT_URL}" }}
|
||||||
|
""",
|
||||||
|
["cargo metadata"],
|
||||||
|
),
|
||||||
|
}
|
||||||
|
|
||||||
|
for version, (updated_dependencies, expected_commands) in cases.items():
|
||||||
|
with self.subTest(version=version), tempfile.TemporaryDirectory() as tmp:
|
||||||
|
workdir = Path(tmp)
|
||||||
|
(workdir / "Cargo.toml").write_text(CARGO_TOML)
|
||||||
|
command_log = workdir / "commands.log"
|
||||||
|
fake_bin = workdir / "bin"
|
||||||
|
fake_bin.mkdir()
|
||||||
|
self._write_fake_executables(fake_bin)
|
||||||
|
self._write_fake_python_dependencies(workdir)
|
||||||
|
|
||||||
|
env = os.environ.copy()
|
||||||
|
env["PATH"] = os.pathsep.join([str(fake_bin), env["PATH"]])
|
||||||
|
env["FAKE_COMMAND_LOG"] = str(command_log)
|
||||||
|
env["PYTHONPATH"] = os.pathsep.join(
|
||||||
|
filter(None, [str(workdir), env.get("PYTHONPATH")])
|
||||||
|
)
|
||||||
|
result = subprocess.run(
|
||||||
|
[sys.executable, str(SCRIPT), version],
|
||||||
|
cwd=workdir,
|
||||||
|
env=env,
|
||||||
|
capture_output=True,
|
||||||
|
text=True,
|
||||||
|
timeout=10,
|
||||||
|
)
|
||||||
|
|
||||||
|
self.assertEqual(result.returncode, 0, result.stderr)
|
||||||
|
self.assertEqual(
|
||||||
|
(workdir / "Cargo.toml").read_text(),
|
||||||
|
"[workspace.dependencies]\n"
|
||||||
|
+ updated_dependencies
|
||||||
|
+ UNTOUCHED_DEPENDENCIES,
|
||||||
|
)
|
||||||
|
commands = command_log.read_text().splitlines()
|
||||||
|
for command in expected_commands:
|
||||||
|
self.assertTrue(
|
||||||
|
any(line.startswith(command) for line in commands),
|
||||||
|
f"{command!r} not found in {commands!r}",
|
||||||
|
)
|
||||||
|
|
||||||
|
def _write_fake_executables(self, fake_bin: Path) -> None:
|
||||||
|
cargo = fake_bin / "cargo"
|
||||||
|
cargo.write_text(
|
||||||
|
textwrap.dedent(
|
||||||
|
"""\
|
||||||
|
#!/bin/sh
|
||||||
|
printf 'cargo %s\\n' "$*" >> "$FAKE_COMMAND_LOG"
|
||||||
|
case "$1" in
|
||||||
|
info)
|
||||||
|
printf '%s\\n' 'version: 8.8.8 (latest 9.9.9)'
|
||||||
|
;;
|
||||||
|
metadata)
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
exit 2
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
)
|
||||||
|
cargo.chmod(cargo.stat().st_mode | stat.S_IXUSR)
|
||||||
|
|
||||||
|
git = fake_bin / "git"
|
||||||
|
git.write_text(
|
||||||
|
textwrap.dedent(
|
||||||
|
"""\
|
||||||
|
#!/bin/sh
|
||||||
|
printf 'git %s\\n' "$*" >> "$FAKE_COMMAND_LOG"
|
||||||
|
if [ "$1" != "ls-remote" ]; then
|
||||||
|
exit 2
|
||||||
|
fi
|
||||||
|
printf '%s\\n' \\
|
||||||
|
'111111 refs/tags/v9.9.9' \\
|
||||||
|
'222222 refs/tags/v10.0.0-beta.1' \\
|
||||||
|
'333333 refs/tags/v10.0.0-beta.3'
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
)
|
||||||
|
git.chmod(git.stat().st_mode | stat.S_IXUSR)
|
||||||
|
|
||||||
|
def _write_fake_python_dependencies(self, workdir: Path) -> None:
|
||||||
|
packaging = workdir / "packaging"
|
||||||
|
packaging.mkdir()
|
||||||
|
(packaging / "__init__.py").write_text("")
|
||||||
|
(packaging / "version.py").write_text(
|
||||||
|
textwrap.dedent(
|
||||||
|
"""\
|
||||||
|
class Version:
|
||||||
|
def __init__(self, value):
|
||||||
|
release, _, prerelease = value.partition("-beta.")
|
||||||
|
self._key = (
|
||||||
|
tuple(int(part) for part in release.split(".")),
|
||||||
|
not prerelease,
|
||||||
|
int(prerelease or 0),
|
||||||
|
)
|
||||||
|
|
||||||
|
def __lt__(self, other):
|
||||||
|
return self._key < other._key
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
unittest.main()
|
||||||
@@ -12,16 +12,12 @@ done
|
|||||||
# This updates the lockfile without building
|
# This updates the lockfile without building
|
||||||
cargo metadata --quiet > /dev/null
|
cargo metadata --quiet > /dev/null
|
||||||
|
|
||||||
pushd nodejs || exit 1
|
|
||||||
npm install --package-lock-only --silent
|
|
||||||
popd
|
|
||||||
|
|
||||||
if git diff --quiet --exit-code; then
|
if git diff --quiet --exit-code; then
|
||||||
echo "No lockfile changes to commit; skipping amend."
|
echo "No lockfile changes to commit; skipping amend."
|
||||||
elif $AMEND; then
|
elif $AMEND; then
|
||||||
git add Cargo.lock nodejs/package-lock.json
|
git add Cargo.lock
|
||||||
git commit --amend --no-edit
|
git commit --amend --no-edit
|
||||||
else
|
else
|
||||||
git add Cargo.lock nodejs/package-lock.json
|
git add Cargo.lock
|
||||||
git commit -m "Update lockfiles"
|
git commit -m "Update lockfiles"
|
||||||
fi
|
fi
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ with open("Cargo.toml", "rb") as f:
|
|||||||
elif isinstance(dep, dict):
|
elif isinstance(dep, dict):
|
||||||
# Version doesn't have the beta tag in it, so we instead look
|
# Version doesn't have the beta tag in it, so we instead look
|
||||||
# at the git tag.
|
# at the git tag.
|
||||||
version = dep.get('tag', dep.get('version'))
|
version = dep.get("tag", dep.get("version"))
|
||||||
else:
|
else:
|
||||||
raise ValueError("Unexpected type for dependency: " + str(dep))
|
raise ValueError("Unexpected type for dependency: " + str(dep))
|
||||||
|
|
||||||
|
|||||||
@@ -131,18 +131,13 @@ allow = [
|
|||||||
"BSD-3-Clause",
|
"BSD-3-Clause",
|
||||||
"ISC",
|
"ISC",
|
||||||
"Unicode-3.0",
|
"Unicode-3.0",
|
||||||
"Unicode-DFS-2016",
|
|
||||||
"Zlib",
|
"Zlib",
|
||||||
"CC0-1.0",
|
"CC0-1.0",
|
||||||
"MPL-2.0",
|
"MPL-2.0",
|
||||||
"BSL-1.0",
|
"BSL-1.0",
|
||||||
"OpenSSL",
|
|
||||||
# 0BSD ("BSD Zero Clause") is effectively public domain — no attribution
|
# 0BSD ("BSD Zero Clause") is effectively public domain — no attribution
|
||||||
# required. Pulled in by `mock_instant`.
|
# required. Pulled in by `mock_instant`.
|
||||||
"0BSD",
|
"0BSD",
|
||||||
# bzip2-1.0.6 is the permissive upstream bzip2 license (BSD-like). Pulled
|
|
||||||
# in by `libbz2-rs-sys`, the pure-Rust bzip2 implementation.
|
|
||||||
"bzip2-1.0.6",
|
|
||||||
# CDLA-Permissive-2.0 is a permissive data license used by `webpki-roots`
|
# CDLA-Permissive-2.0 is a permissive data license used by `webpki-roots`
|
||||||
# for the Mozilla CA root bundle. Data-only, distribution-compatible.
|
# for the Mozilla CA root bundle. Data-only, distribution-compatible.
|
||||||
"CDLA-Permissive-2.0",
|
"CDLA-Permissive-2.0",
|
||||||
@@ -150,12 +145,7 @@ allow = [
|
|||||||
confidence-threshold = 0.8
|
confidence-threshold = 0.8
|
||||||
# Per-crate license exceptions: allow a license for a specific crate only,
|
# Per-crate license exceptions: allow a license for a specific crate only,
|
||||||
# rather than globally via the `allow` list above.
|
# rather than globally via the `allow` list above.
|
||||||
exceptions = [
|
exceptions = []
|
||||||
# CDDL-1.0 (copyleft) is pulled in only as a dev/profiling dependency via
|
|
||||||
# `inferno` -> `pprof` -> `lance-testing`; it is a test dependency that we
|
|
||||||
# do not distribute, so scope the allowance to `inferno` alone.
|
|
||||||
{ allow = ["CDDL-1.0"], crate = "inferno" },
|
|
||||||
]
|
|
||||||
# Crates whose license cannot be determined from Cargo metadata but whose
|
# Crates whose license cannot be determined from Cargo metadata but whose
|
||||||
# license we've manually confirmed from upstream. Keep this list minimal.
|
# license we've manually confirmed from upstream. Keep this list minimal.
|
||||||
[[licenses.clarify]]
|
[[licenses.clarify]]
|
||||||
@@ -177,6 +167,11 @@ multiple-versions = "warn"
|
|||||||
# Wildcard version requirements (`foo = "*"`) are a footgun — they let any
|
# Wildcard version requirements (`foo = "*"`) are a footgun — they let any
|
||||||
# future release in without review. Ban them outright.
|
# future release in without review. Ban them outright.
|
||||||
wildcards = "deny"
|
wildcards = "deny"
|
||||||
|
# Lint every dependency declared by a workspace member against the shared
|
||||||
|
# `[workspace.dependencies]` table: any crate used by more than one member must
|
||||||
|
# go through `workspace = true`, and entries nothing uses are an error. This
|
||||||
|
# keeps versions from drifting between the core crate and the bindings.
|
||||||
|
workspace-dependencies = { duplicates = "deny", unused = "deny" }
|
||||||
# Internal workspace crates reference each other via `path = "..."`, which
|
# Internal workspace crates reference each other via `path = "..."`, which
|
||||||
# cargo-deny sees as a wildcard version. That's fine for private workspace
|
# cargo-deny sees as a wildcard version. That's fine for private workspace
|
||||||
# members (not published to crates.io), so allow it specifically for paths.
|
# members (not published to crates.io), so allow it specifically for paths.
|
||||||
|
|||||||
+11
-8
@@ -47,22 +47,24 @@ pytest -vv python/tests/docs
|
|||||||
|
|
||||||
### Checking typescript examples
|
### Checking typescript examples
|
||||||
|
|
||||||
The `@lancedb/lancedb` package must be built before running the tests:
|
The examples depend on `@lancedb/lancedb` at `file:../dist`, so the package must be
|
||||||
|
built before running the tests. This uses pnpm; see the
|
||||||
|
[Typescript contributing guide](../nodejs/CONTRIBUTING.md) for the toolchain setup.
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
pushd nodejs
|
pushd nodejs
|
||||||
npm ci
|
pnpm install
|
||||||
npm run build
|
pnpm build
|
||||||
popd
|
popd
|
||||||
```
|
```
|
||||||
|
|
||||||
Then you can run the examples by going to the `nodejs/examples` directory and
|
Then you can run the examples by going to the `nodejs/examples` directory, which is a
|
||||||
running the tests like a normal npm package:
|
separate pnpm package with its own lockfile:
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
pushd nodejs/examples
|
pushd nodejs/examples
|
||||||
npm ci
|
pnpm install
|
||||||
npm test
|
pnpm test
|
||||||
popd
|
popd
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -84,6 +86,7 @@ The new files should be checked into the repository.
|
|||||||
|
|
||||||
```shell
|
```shell
|
||||||
pushd nodejs
|
pushd nodejs
|
||||||
npm run docs
|
# `pnpm docs` would invoke pnpm's built-in `docs` command, not the script.
|
||||||
|
pnpm run docs
|
||||||
popd
|
popd
|
||||||
```
|
```
|
||||||
|
|||||||
@@ -446,6 +446,15 @@ paths:
|
|||||||
properties:
|
properties:
|
||||||
column:
|
column:
|
||||||
type: string
|
type: string
|
||||||
|
name:
|
||||||
|
type: string
|
||||||
|
description: Optional name for the created index.
|
||||||
|
replace:
|
||||||
|
type: boolean
|
||||||
|
default: true
|
||||||
|
description: |
|
||||||
|
Whether to replace an existing index with the same resolved
|
||||||
|
name. Defaults to true.
|
||||||
metric_type:
|
metric_type:
|
||||||
type: string
|
type: string
|
||||||
nullable: false
|
nullable: false
|
||||||
|
|||||||
Generated
-135
@@ -1,135 +0,0 @@
|
|||||||
{
|
|
||||||
"name": "lancedb-docs-test",
|
|
||||||
"version": "1.0.0",
|
|
||||||
"lockfileVersion": 3,
|
|
||||||
"requires": true,
|
|
||||||
"packages": {
|
|
||||||
"": {
|
|
||||||
"name": "lancedb-docs-test",
|
|
||||||
"version": "1.0.0",
|
|
||||||
"license": "Apache 2",
|
|
||||||
"dependencies": {
|
|
||||||
"apache-arrow": "file:../node/node_modules/apache-arrow",
|
|
||||||
"vectordb": "file:../node"
|
|
||||||
},
|
|
||||||
"devDependencies": {
|
|
||||||
"@types/node": "^20.11.8",
|
|
||||||
"typescript": "^5.3.3"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"../node": {
|
|
||||||
"name": "vectordb",
|
|
||||||
"version": "0.21.2-beta.0",
|
|
||||||
"cpu": [
|
|
||||||
"x64",
|
|
||||||
"arm64"
|
|
||||||
],
|
|
||||||
"license": "Apache-2.0",
|
|
||||||
"os": [
|
|
||||||
"darwin",
|
|
||||||
"linux",
|
|
||||||
"win32"
|
|
||||||
],
|
|
||||||
"dependencies": {
|
|
||||||
"@neon-rs/load": "^0.0.74",
|
|
||||||
"axios": "^1.4.0"
|
|
||||||
},
|
|
||||||
"devDependencies": {
|
|
||||||
"@neon-rs/cli": "^0.0.160",
|
|
||||||
"@types/chai": "^4.3.4",
|
|
||||||
"@types/chai-as-promised": "^7.1.5",
|
|
||||||
"@types/mocha": "^10.0.1",
|
|
||||||
"@types/node": "^18.16.2",
|
|
||||||
"@types/sinon": "^10.0.15",
|
|
||||||
"@types/temp": "^0.9.1",
|
|
||||||
"@types/uuid": "^9.0.3",
|
|
||||||
"@typescript-eslint/eslint-plugin": "^5.59.1",
|
|
||||||
"apache-arrow-old": "npm:apache-arrow@13.0.0",
|
|
||||||
"cargo-cp-artifact": "^0.1",
|
|
||||||
"chai": "^4.3.7",
|
|
||||||
"chai-as-promised": "^7.1.1",
|
|
||||||
"eslint": "^8.39.0",
|
|
||||||
"eslint-config-standard-with-typescript": "^34.0.1",
|
|
||||||
"eslint-plugin-import": "^2.26.0",
|
|
||||||
"eslint-plugin-n": "^15.7.0",
|
|
||||||
"eslint-plugin-promise": "^6.1.1",
|
|
||||||
"mocha": "^10.2.0",
|
|
||||||
"openai": "^4.24.1",
|
|
||||||
"sinon": "^15.1.0",
|
|
||||||
"temp": "^0.9.4",
|
|
||||||
"ts-node": "^10.9.1",
|
|
||||||
"ts-node-dev": "^2.0.0",
|
|
||||||
"typedoc": "^0.24.7",
|
|
||||||
"typedoc-plugin-markdown": "^3.15.3",
|
|
||||||
"typescript": "^5.1.0",
|
|
||||||
"uuid": "^9.0.0"
|
|
||||||
},
|
|
||||||
"optionalDependencies": {
|
|
||||||
"@lancedb/vectordb-darwin-arm64": "0.21.2-beta.0",
|
|
||||||
"@lancedb/vectordb-darwin-x64": "0.21.2-beta.0",
|
|
||||||
"@lancedb/vectordb-linux-arm64-gnu": "0.21.2-beta.0",
|
|
||||||
"@lancedb/vectordb-linux-x64-gnu": "0.21.2-beta.0",
|
|
||||||
"@lancedb/vectordb-win32-x64-msvc": "0.21.2-beta.0"
|
|
||||||
},
|
|
||||||
"peerDependencies": {
|
|
||||||
"@apache-arrow/ts": "^14.0.2",
|
|
||||||
"apache-arrow": "^14.0.2"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"../node/node_modules/apache-arrow": {
|
|
||||||
"version": "14.0.2",
|
|
||||||
"license": "Apache-2.0",
|
|
||||||
"dependencies": {
|
|
||||||
"@types/command-line-args": "5.2.0",
|
|
||||||
"@types/command-line-usage": "5.0.2",
|
|
||||||
"@types/node": "20.3.0",
|
|
||||||
"@types/pad-left": "2.1.1",
|
|
||||||
"command-line-args": "5.2.1",
|
|
||||||
"command-line-usage": "7.0.1",
|
|
||||||
"flatbuffers": "23.5.26",
|
|
||||||
"json-bignum": "^0.0.3",
|
|
||||||
"pad-left": "^2.1.0",
|
|
||||||
"tslib": "^2.5.3"
|
|
||||||
},
|
|
||||||
"bin": {
|
|
||||||
"arrow2csv": "bin/arrow2csv.js"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@types/node": {
|
|
||||||
"version": "20.11.8",
|
|
||||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.11.8.tgz",
|
|
||||||
"integrity": "sha512-i7omyekpPTNdv4Jb/Rgqg0RU8YqLcNsI12quKSDkRXNfx7Wxdm6HhK1awT3xTgEkgxPn3bvnSpiEAc7a7Lpyow==",
|
|
||||||
"dev": true,
|
|
||||||
"dependencies": {
|
|
||||||
"undici-types": "~5.26.4"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/apache-arrow": {
|
|
||||||
"resolved": "../node/node_modules/apache-arrow",
|
|
||||||
"link": true
|
|
||||||
},
|
|
||||||
"node_modules/typescript": {
|
|
||||||
"version": "5.3.3",
|
|
||||||
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.3.3.tgz",
|
|
||||||
"integrity": "sha512-pXWcraxM0uxAS+tN0AG/BF2TyqmHO014Z070UsJ+pFvYuRSq8KH8DmWpnbXe0pEPDHXZV3FcAbJkijJ5oNEnWw==",
|
|
||||||
"dev": true,
|
|
||||||
"bin": {
|
|
||||||
"tsc": "bin/tsc",
|
|
||||||
"tsserver": "bin/tsserver"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">=14.17"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/undici-types": {
|
|
||||||
"version": "5.26.5",
|
|
||||||
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz",
|
|
||||||
"integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==",
|
|
||||||
"dev": true
|
|
||||||
},
|
|
||||||
"node_modules/vectordb": {
|
|
||||||
"resolved": "../node",
|
|
||||||
"link": true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,20 +0,0 @@
|
|||||||
{
|
|
||||||
"name": "lancedb-docs-test",
|
|
||||||
"version": "1.0.0",
|
|
||||||
"description": "auto-generated tests from doc",
|
|
||||||
"author": "dev@lancedb.com",
|
|
||||||
"license": "Apache 2",
|
|
||||||
"dependencies": {
|
|
||||||
"apache-arrow": "file:../node/node_modules/apache-arrow",
|
|
||||||
"vectordb": "file:../node"
|
|
||||||
},
|
|
||||||
"scripts": {
|
|
||||||
"build": "tsc -b && cd ../node && npm run build-release",
|
|
||||||
"example": "npm run build && node",
|
|
||||||
"test": "npm run build && ls dist/*.js | xargs -n 1 node"
|
|
||||||
},
|
|
||||||
"devDependencies": {
|
|
||||||
"@types/node": "^20.11.8",
|
|
||||||
"typescript": "^5.3.3"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -5,5 +5,5 @@ mkdocs-autorefs>=0.5,<=1.0
|
|||||||
mkdocstrings[python]>=0.24,<1.0
|
mkdocstrings[python]>=0.24,<1.0
|
||||||
griffe>=0.40,<1.0
|
griffe>=0.40,<1.0
|
||||||
mkdocs-render-swagger-plugin>=0.1.0
|
mkdocs-render-swagger-plugin>=0.1.0
|
||||||
pydantic>=2.0,<3.0
|
pydantic>=2.7.4,<3
|
||||||
mkdocs-redirects>=1.2.0
|
mkdocs-redirects>=1.2.0
|
||||||
|
|||||||
@@ -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.38.0-beta.2</version>
|
<version>0.39.0-beta.1</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,518 @@
|
|||||||
|
[**@lancedb/lancedb**](../README.md) • **Docs**
|
||||||
|
|
||||||
|
***
|
||||||
|
|
||||||
|
[@lancedb/lancedb](../globals.md) / AutoQuery
|
||||||
|
|
||||||
|
# Class: AutoQuery
|
||||||
|
|
||||||
|
A builder for automatic string searches.
|
||||||
|
|
||||||
|
Automatic search determines whether to use full-text or vector search from
|
||||||
|
the table revision selected for each execution. This builder exposes the
|
||||||
|
common operations supported by both query families.
|
||||||
|
|
||||||
|
## Extends
|
||||||
|
|
||||||
|
- `StandardQueryBase`<`NativeQuery` \| `NativeVectorQuery`>
|
||||||
|
|
||||||
|
## Properties
|
||||||
|
|
||||||
|
### inner
|
||||||
|
|
||||||
|
```ts
|
||||||
|
protected inner: Query | VectorQuery | Promise<Query | VectorQuery>;
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Inherited from
|
||||||
|
|
||||||
|
`StandardQueryBase.inner`
|
||||||
|
|
||||||
|
## Methods
|
||||||
|
|
||||||
|
### analyzePlan()
|
||||||
|
|
||||||
|
```ts
|
||||||
|
analyzePlan(distributedMetrics?): Promise<string>
|
||||||
|
```
|
||||||
|
|
||||||
|
Executes the query and returns the physical query plan annotated with runtime metrics.
|
||||||
|
|
||||||
|
This is useful for debugging and performance analysis, as it shows how the query was executed
|
||||||
|
and includes metrics such as elapsed time, rows processed, and I/O statistics.
|
||||||
|
|
||||||
|
#### Parameters
|
||||||
|
|
||||||
|
* **distributedMetrics?**: [`AnalyzePlanDistributedMetrics`](../type-aliases/AnalyzePlanDistributedMetrics.md)
|
||||||
|
How distributed worker metrics are displayed for remote query plans.
|
||||||
|
Defaults to `"aggregate"`.
|
||||||
|
|
||||||
|
#### Returns
|
||||||
|
|
||||||
|
`Promise`<`string`>
|
||||||
|
|
||||||
|
A query execution plan with runtime metrics for each step.
|
||||||
|
|
||||||
|
#### Example
|
||||||
|
|
||||||
|
```ts
|
||||||
|
import * as lancedb from "@lancedb/lancedb"
|
||||||
|
|
||||||
|
const db = await lancedb.connect("./.lancedb");
|
||||||
|
const table = await db.createTable("my_table", [
|
||||||
|
{ vector: [1.1, 0.9], id: "1" },
|
||||||
|
]);
|
||||||
|
|
||||||
|
const plan = await table.query().nearestTo([0.5, 0.2]).analyzePlan();
|
||||||
|
|
||||||
|
Example output (with runtime metrics inlined):
|
||||||
|
AnalyzeExec verbose=true, metrics=[]
|
||||||
|
ProjectionExec: expr=[id@3 as id, vector@0 as vector, _distance@2 as _distance], metrics=[output_rows=1, elapsed_compute=3.292µs]
|
||||||
|
Take: columns="vector, _rowid, _distance, (id)", metrics=[output_rows=1, elapsed_compute=66.001µs, batches_processed=1, bytes_read=8, iops=1, requests=1]
|
||||||
|
CoalesceBatchesExec: target_batch_size=1024, metrics=[output_rows=1, elapsed_compute=3.333µs]
|
||||||
|
GlobalLimitExec: skip=0, fetch=10, metrics=[output_rows=1, elapsed_compute=167ns]
|
||||||
|
FilterExec: _distance@2 IS NOT NULL, metrics=[output_rows=1, elapsed_compute=8.542µs]
|
||||||
|
SortExec: TopK(fetch=10), expr=[_distance@2 ASC NULLS LAST], metrics=[output_rows=1, elapsed_compute=63.25µs, row_replacements=1]
|
||||||
|
KNNVectorDistance: metric=l2, metrics=[output_rows=1, elapsed_compute=114.333µs, output_batches=1]
|
||||||
|
LanceScan: uri=/path/to/data, projection=[vector], row_id=true, row_addr=false, ordered=false, metrics=[output_rows=1, elapsed_compute=103.626µs, bytes_read=549, iops=2, requests=2]
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Inherited from
|
||||||
|
|
||||||
|
`StandardQueryBase.analyzePlan`
|
||||||
|
|
||||||
|
***
|
||||||
|
|
||||||
|
### execute()
|
||||||
|
|
||||||
|
```ts
|
||||||
|
protected execute(options?): AsyncGenerator<RecordBatch<any>, void, unknown>
|
||||||
|
```
|
||||||
|
|
||||||
|
Execute the query and return the results as an
|
||||||
|
|
||||||
|
#### Parameters
|
||||||
|
|
||||||
|
* **options?**: `Partial`<[`QueryExecutionOptions`](../interfaces/QueryExecutionOptions.md)>
|
||||||
|
|
||||||
|
#### Returns
|
||||||
|
|
||||||
|
`AsyncGenerator`<`RecordBatch`<`any`>, `void`, `unknown`>
|
||||||
|
|
||||||
|
#### See
|
||||||
|
|
||||||
|
- AsyncIterator
|
||||||
|
of
|
||||||
|
- RecordBatch.
|
||||||
|
|
||||||
|
By default, LanceDb will use many threads to calculate results and, when
|
||||||
|
the result set is large, multiple batches will be processed at one time.
|
||||||
|
This readahead is limited however and backpressure will be applied if this
|
||||||
|
stream is consumed slowly (this constrains the maximum memory used by a
|
||||||
|
single query)
|
||||||
|
|
||||||
|
#### Inherited from
|
||||||
|
|
||||||
|
`StandardQueryBase.execute`
|
||||||
|
|
||||||
|
***
|
||||||
|
|
||||||
|
### explainPlan()
|
||||||
|
|
||||||
|
```ts
|
||||||
|
explainPlan(verbose): Promise<string>
|
||||||
|
```
|
||||||
|
|
||||||
|
Generates an explanation of the query execution plan.
|
||||||
|
|
||||||
|
#### Parameters
|
||||||
|
|
||||||
|
* **verbose**: `boolean` = `false`
|
||||||
|
If true, provides a more detailed explanation. Defaults to false.
|
||||||
|
|
||||||
|
#### Returns
|
||||||
|
|
||||||
|
`Promise`<`string`>
|
||||||
|
|
||||||
|
A Promise that resolves to a string containing the query execution plan explanation.
|
||||||
|
|
||||||
|
#### Example
|
||||||
|
|
||||||
|
```ts
|
||||||
|
import * as lancedb from "@lancedb/lancedb"
|
||||||
|
const db = await lancedb.connect("./.lancedb");
|
||||||
|
const table = await db.createTable("my_table", [
|
||||||
|
{ vector: [1.1, 0.9], id: "1" },
|
||||||
|
]);
|
||||||
|
const plan = await table.query().nearestTo([0.5, 0.2]).explainPlan();
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Inherited from
|
||||||
|
|
||||||
|
`StandardQueryBase.explainPlan`
|
||||||
|
|
||||||
|
***
|
||||||
|
|
||||||
|
### fastSearch()
|
||||||
|
|
||||||
|
```ts
|
||||||
|
fastSearch(): this
|
||||||
|
```
|
||||||
|
|
||||||
|
Skip searching un-indexed data. This can make search faster, but will miss
|
||||||
|
any data that is not yet indexed.
|
||||||
|
|
||||||
|
Use [Table#optimize](Table.md#optimize) to index all un-indexed data.
|
||||||
|
|
||||||
|
#### Returns
|
||||||
|
|
||||||
|
`this`
|
||||||
|
|
||||||
|
#### Inherited from
|
||||||
|
|
||||||
|
`StandardQueryBase.fastSearch`
|
||||||
|
|
||||||
|
***
|
||||||
|
|
||||||
|
### ~~filter()~~
|
||||||
|
|
||||||
|
```ts
|
||||||
|
filter(predicate): this
|
||||||
|
```
|
||||||
|
|
||||||
|
A filter statement to be applied to this query.
|
||||||
|
|
||||||
|
#### Parameters
|
||||||
|
|
||||||
|
* **predicate**: `string`
|
||||||
|
|
||||||
|
#### Returns
|
||||||
|
|
||||||
|
`this`
|
||||||
|
|
||||||
|
#### See
|
||||||
|
|
||||||
|
where
|
||||||
|
|
||||||
|
#### Deprecated
|
||||||
|
|
||||||
|
Use `where` instead
|
||||||
|
|
||||||
|
#### Inherited from
|
||||||
|
|
||||||
|
`StandardQueryBase.filter`
|
||||||
|
|
||||||
|
***
|
||||||
|
|
||||||
|
### fullTextSearch()
|
||||||
|
|
||||||
|
```ts
|
||||||
|
fullTextSearch(query, options?): this
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Parameters
|
||||||
|
|
||||||
|
* **query**: `string` \| [`FullTextQuery`](../interfaces/FullTextQuery.md)
|
||||||
|
|
||||||
|
* **options?**: `Partial`<[`FullTextSearchOptions`](../interfaces/FullTextSearchOptions.md)>
|
||||||
|
|
||||||
|
#### Returns
|
||||||
|
|
||||||
|
`this`
|
||||||
|
|
||||||
|
#### Inherited from
|
||||||
|
|
||||||
|
`StandardQueryBase.fullTextSearch`
|
||||||
|
|
||||||
|
***
|
||||||
|
|
||||||
|
### limit()
|
||||||
|
|
||||||
|
```ts
|
||||||
|
limit(limit): this
|
||||||
|
```
|
||||||
|
|
||||||
|
Set the maximum number of results to return.
|
||||||
|
|
||||||
|
By default, a plain search has no limit. If this method is not
|
||||||
|
called then every valid row from the table will be returned.
|
||||||
|
|
||||||
|
#### Parameters
|
||||||
|
|
||||||
|
* **limit**: `number`
|
||||||
|
|
||||||
|
#### Returns
|
||||||
|
|
||||||
|
`this`
|
||||||
|
|
||||||
|
#### Inherited from
|
||||||
|
|
||||||
|
`StandardQueryBase.limit`
|
||||||
|
|
||||||
|
***
|
||||||
|
|
||||||
|
### offset()
|
||||||
|
|
||||||
|
```ts
|
||||||
|
offset(offset): this
|
||||||
|
```
|
||||||
|
|
||||||
|
Set the number of rows to skip before returning results.
|
||||||
|
|
||||||
|
This is useful for pagination.
|
||||||
|
|
||||||
|
#### Parameters
|
||||||
|
|
||||||
|
* **offset**: `number`
|
||||||
|
|
||||||
|
#### Returns
|
||||||
|
|
||||||
|
`this`
|
||||||
|
|
||||||
|
#### Inherited from
|
||||||
|
|
||||||
|
`StandardQueryBase.offset`
|
||||||
|
|
||||||
|
***
|
||||||
|
|
||||||
|
### orderBy()
|
||||||
|
|
||||||
|
```ts
|
||||||
|
orderBy(ordering): this
|
||||||
|
```
|
||||||
|
|
||||||
|
Sort the results by the specified column(s).
|
||||||
|
|
||||||
|
#### Parameters
|
||||||
|
|
||||||
|
* **ordering**: [`ColumnOrdering`](../interfaces/ColumnOrdering.md) \| [`ColumnOrdering`](../interfaces/ColumnOrdering.md)[]
|
||||||
|
|
||||||
|
#### Returns
|
||||||
|
|
||||||
|
`this`
|
||||||
|
|
||||||
|
This query builder.
|
||||||
|
|
||||||
|
#### Inherited from
|
||||||
|
|
||||||
|
`StandardQueryBase.orderBy`
|
||||||
|
|
||||||
|
***
|
||||||
|
|
||||||
|
### outputSchema()
|
||||||
|
|
||||||
|
```ts
|
||||||
|
outputSchema(): Promise<Schema<any>>
|
||||||
|
```
|
||||||
|
|
||||||
|
Returns the schema of the output that will be returned by this query.
|
||||||
|
|
||||||
|
This can be used to inspect the types and names of the columns that will be
|
||||||
|
returned by the query before executing it.
|
||||||
|
|
||||||
|
#### Returns
|
||||||
|
|
||||||
|
`Promise`<`Schema`<`any`>>
|
||||||
|
|
||||||
|
An Arrow Schema describing the output columns.
|
||||||
|
|
||||||
|
#### Inherited from
|
||||||
|
|
||||||
|
`StandardQueryBase.outputSchema`
|
||||||
|
|
||||||
|
***
|
||||||
|
|
||||||
|
### select()
|
||||||
|
|
||||||
|
```ts
|
||||||
|
select(columns): this
|
||||||
|
```
|
||||||
|
|
||||||
|
Return only the specified columns.
|
||||||
|
|
||||||
|
By default a query will return all columns from the table. However, this can have
|
||||||
|
a very significant impact on latency. LanceDb stores data in a columnar fashion. This
|
||||||
|
means we can finely tune our I/O to select exactly the columns we need.
|
||||||
|
|
||||||
|
As a best practice you should always limit queries to the columns that you need. If you
|
||||||
|
pass in an array of column names then only those columns will be returned.
|
||||||
|
|
||||||
|
You can also use this method to create new "dynamic" columns based on your existing columns.
|
||||||
|
For example, you may not care about "a" or "b" but instead simply want "a + b". This is often
|
||||||
|
seen in the SELECT clause of an SQL query (e.g. `SELECT a+b FROM my_table`).
|
||||||
|
|
||||||
|
To create dynamic columns you can pass in a Map<string, string>. A column will be returned
|
||||||
|
for each entry in the map. The key provides the name of the column. The value is
|
||||||
|
an SQL string used to specify how the column is calculated.
|
||||||
|
|
||||||
|
For example, an SQL query might state `SELECT a + b AS combined, c`. The equivalent
|
||||||
|
input to this method would be:
|
||||||
|
|
||||||
|
#### Parameters
|
||||||
|
|
||||||
|
* **columns**: `string` \| `string`[] \| `Record`<`string`, `string`> \| `Map`<`string`, `string`>
|
||||||
|
|
||||||
|
#### Returns
|
||||||
|
|
||||||
|
`this`
|
||||||
|
|
||||||
|
#### Example
|
||||||
|
|
||||||
|
```ts
|
||||||
|
new Map([["combined", "a + b"], ["c", "c"]])
|
||||||
|
|
||||||
|
Columns will always be returned in the order given, even if that order is different than
|
||||||
|
the order used when adding the data.
|
||||||
|
|
||||||
|
Note that you can pass in a `Record<string, string>` (e.g. an object literal). This method
|
||||||
|
uses `Object.entries` which should preserve the insertion order of the object. However,
|
||||||
|
object insertion order is easy to get wrong and `Map` is more foolproof.
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Inherited from
|
||||||
|
|
||||||
|
`StandardQueryBase.select`
|
||||||
|
|
||||||
|
***
|
||||||
|
|
||||||
|
### toArray()
|
||||||
|
|
||||||
|
```ts
|
||||||
|
toArray(options?): Promise<any[]>
|
||||||
|
```
|
||||||
|
|
||||||
|
Collect the results as an array of objects.
|
||||||
|
|
||||||
|
#### Parameters
|
||||||
|
|
||||||
|
* **options?**: `Partial`<[`QueryExecutionOptions`](../interfaces/QueryExecutionOptions.md)>
|
||||||
|
|
||||||
|
#### Returns
|
||||||
|
|
||||||
|
`Promise`<`any`[]>
|
||||||
|
|
||||||
|
#### Inherited from
|
||||||
|
|
||||||
|
`StandardQueryBase.toArray`
|
||||||
|
|
||||||
|
***
|
||||||
|
|
||||||
|
### toArrow()
|
||||||
|
|
||||||
|
```ts
|
||||||
|
toArrow(options?): Promise<Table<any>>
|
||||||
|
```
|
||||||
|
|
||||||
|
Collect the results as an Arrow
|
||||||
|
|
||||||
|
#### Parameters
|
||||||
|
|
||||||
|
* **options?**: `Partial`<[`QueryExecutionOptions`](../interfaces/QueryExecutionOptions.md)>
|
||||||
|
|
||||||
|
#### Returns
|
||||||
|
|
||||||
|
`Promise`<`Table`<`any`>>
|
||||||
|
|
||||||
|
#### See
|
||||||
|
|
||||||
|
ArrowTable.
|
||||||
|
|
||||||
|
#### Inherited from
|
||||||
|
|
||||||
|
`StandardQueryBase.toArrow`
|
||||||
|
|
||||||
|
***
|
||||||
|
|
||||||
|
### useLsm()
|
||||||
|
|
||||||
|
```ts
|
||||||
|
useLsm(enable): this
|
||||||
|
```
|
||||||
|
|
||||||
|
Control MemWAL read routing for this query.
|
||||||
|
|
||||||
|
By default (unset), when the table carries a MemWAL write spec (see
|
||||||
|
[Table#setLsmWriteSpec](Table.md#setlsmwritespec)), reads are routed through the LSM scanner so
|
||||||
|
they also return data written via the `mergeInsert` LSM path that has not yet
|
||||||
|
been compacted into the base table (the active/frozen in-memory memtables and
|
||||||
|
the flushed generations), deduplicated by primary key; a table without a spec
|
||||||
|
reads the base table.
|
||||||
|
|
||||||
|
#### Parameters
|
||||||
|
|
||||||
|
* **enable**: `boolean`
|
||||||
|
`true` forces the LSM scanner and errors if the table has no
|
||||||
|
MemWAL write spec. `false` bypasses the MemWAL and reads the base table only,
|
||||||
|
even when a spec is present.
|
||||||
|
Note: the LSM scanner does not support every query shape (e.g. reranking,
|
||||||
|
hybrid search, `orderBy`). On a MemWAL table those shapes error unless
|
||||||
|
`useLsm(false)` is set, because a base-only read would silently exclude
|
||||||
|
un-compacted MemWAL data.
|
||||||
|
|
||||||
|
#### Returns
|
||||||
|
|
||||||
|
`this`
|
||||||
|
|
||||||
|
#### Inherited from
|
||||||
|
|
||||||
|
`StandardQueryBase.useLsm`
|
||||||
|
|
||||||
|
***
|
||||||
|
|
||||||
|
### where()
|
||||||
|
|
||||||
|
```ts
|
||||||
|
where(predicate): this
|
||||||
|
```
|
||||||
|
|
||||||
|
A filter statement to be applied to this query.
|
||||||
|
|
||||||
|
The filter should be supplied as an SQL query string. For example:
|
||||||
|
|
||||||
|
#### Parameters
|
||||||
|
|
||||||
|
* **predicate**: `string`
|
||||||
|
|
||||||
|
#### Returns
|
||||||
|
|
||||||
|
`this`
|
||||||
|
|
||||||
|
#### Example
|
||||||
|
|
||||||
|
```ts
|
||||||
|
x > 10
|
||||||
|
y > 0 AND y < 100
|
||||||
|
x > 5 OR y = 'test'
|
||||||
|
|
||||||
|
Filtering performance can often be improved by creating a scalar index
|
||||||
|
on the filter column(s).
|
||||||
|
|
||||||
|
Calling this multiple times combines the filters with a logical AND rather
|
||||||
|
than replacing the previous filter.
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Inherited from
|
||||||
|
|
||||||
|
`StandardQueryBase.where`
|
||||||
|
|
||||||
|
***
|
||||||
|
|
||||||
|
### withRowId()
|
||||||
|
|
||||||
|
```ts
|
||||||
|
withRowId(): this
|
||||||
|
```
|
||||||
|
|
||||||
|
Whether to return the row id in the results.
|
||||||
|
|
||||||
|
This column can be used to match results between different queries. For
|
||||||
|
example, to match results from a full text search and a vector search in
|
||||||
|
order to perform hybrid search.
|
||||||
|
|
||||||
|
#### Returns
|
||||||
|
|
||||||
|
`this`
|
||||||
|
|
||||||
|
#### Inherited from
|
||||||
|
|
||||||
|
`StandardQueryBase.withRowId`
|
||||||
@@ -37,6 +37,31 @@ latest and stays writable.
|
|||||||
|
|
||||||
***
|
***
|
||||||
|
|
||||||
|
### cherryPick()
|
||||||
|
|
||||||
|
```ts
|
||||||
|
cherryPick(fromBranch, dryRun): Promise<CherryPickResult>
|
||||||
|
```
|
||||||
|
|
||||||
|
Cherry-pick a branch onto main.
|
||||||
|
|
||||||
|
Set `dryRun` to `true` to preview. A failed cherry-pick resolves
|
||||||
|
with `status: "failed"` instead of throwing.
|
||||||
|
|
||||||
|
#### Parameters
|
||||||
|
|
||||||
|
* **fromBranch**: `string`
|
||||||
|
Branch to cherry-pick from.
|
||||||
|
|
||||||
|
* **dryRun**: `boolean` = `false`
|
||||||
|
When true, only preview. Defaults to false.
|
||||||
|
|
||||||
|
#### Returns
|
||||||
|
|
||||||
|
`Promise`<[`CherryPickResult`](../interfaces/CherryPickResult.md)>
|
||||||
|
|
||||||
|
***
|
||||||
|
|
||||||
### create()
|
### create()
|
||||||
|
|
||||||
```ts
|
```ts
|
||||||
@@ -112,28 +137,3 @@ List all branches, mapping name to branch metadata.
|
|||||||
#### Returns
|
#### Returns
|
||||||
|
|
||||||
`Promise`<`Record`<`string`, [`BranchContents`](BranchContents.md)>>
|
`Promise`<`Record`<`string`, [`BranchContents`](BranchContents.md)>>
|
||||||
|
|
||||||
***
|
|
||||||
|
|
||||||
### merge()
|
|
||||||
|
|
||||||
```ts
|
|
||||||
merge(fromBranch, dryRun): Promise<MergeBranchResult>
|
|
||||||
```
|
|
||||||
|
|
||||||
Merge a branch into main.
|
|
||||||
|
|
||||||
Set `dryRun` to `true` to preview the merge. A rejected merge resolves
|
|
||||||
with `status: "rejected"` instead of throwing.
|
|
||||||
|
|
||||||
#### Parameters
|
|
||||||
|
|
||||||
* **fromBranch**: `string`
|
|
||||||
Branch to merge from.
|
|
||||||
|
|
||||||
* **dryRun**: `boolean` = `false`
|
|
||||||
When true, only preview the merge. Defaults to false.
|
|
||||||
|
|
||||||
#### Returns
|
|
||||||
|
|
||||||
`Promise`<[`MergeBranchResult`](../interfaces/MergeBranchResult.md)>
|
|
||||||
|
|||||||
@@ -169,6 +169,45 @@ Creates a new empty Table
|
|||||||
|
|
||||||
***
|
***
|
||||||
|
|
||||||
|
### createMaterializedView()
|
||||||
|
|
||||||
|
```ts
|
||||||
|
abstract createMaterializedView(
|
||||||
|
name,
|
||||||
|
source,
|
||||||
|
options?): Promise<MaterializedView>
|
||||||
|
```
|
||||||
|
|
||||||
|
Define a materialized view named `name` over the table `source`.
|
||||||
|
|
||||||
|
The view is created empty, with the query recorded in its schema
|
||||||
|
metadata; `view.refresh()` computes the rows. The view is a normal
|
||||||
|
table: it can be queried, indexed and searched, and it appears in
|
||||||
|
`tableNames`. The source table must have stable row ids (create it with
|
||||||
|
the `newTableEnableStableRowIds` storage option); they keep the view's
|
||||||
|
provenance valid across source compactions and cannot be enabled after
|
||||||
|
a table exists. Local databases only.
|
||||||
|
|
||||||
|
#### Parameters
|
||||||
|
|
||||||
|
* **name**: `string`
|
||||||
|
|
||||||
|
* **source**: `string`
|
||||||
|
|
||||||
|
* **options?**
|
||||||
|
|
||||||
|
* **options.limit?**: `number`
|
||||||
|
|
||||||
|
* **options.select?**: [`MaterializedViewSelect`](../type-aliases/MaterializedViewSelect.md)
|
||||||
|
|
||||||
|
* **options.where?**: `string`
|
||||||
|
|
||||||
|
#### Returns
|
||||||
|
|
||||||
|
`Promise`<[`MaterializedView`](MaterializedView.md)>
|
||||||
|
|
||||||
|
***
|
||||||
|
|
||||||
### createNamespace()
|
### createNamespace()
|
||||||
|
|
||||||
```ts
|
```ts
|
||||||
@@ -499,6 +538,22 @@ List server-side jobs across the database's tables.
|
|||||||
|
|
||||||
***
|
***
|
||||||
|
|
||||||
|
### listMaterializedViews()
|
||||||
|
|
||||||
|
```ts
|
||||||
|
abstract listMaterializedViews(): Promise<string[]>
|
||||||
|
```
|
||||||
|
|
||||||
|
The names of the materialized views in this database.
|
||||||
|
|
||||||
|
Found by reading every table's schema, so this costs an open per table.
|
||||||
|
|
||||||
|
#### Returns
|
||||||
|
|
||||||
|
`Promise`<`string`[]>
|
||||||
|
|
||||||
|
***
|
||||||
|
|
||||||
### listNamespaces()
|
### listNamespaces()
|
||||||
|
|
||||||
```ts
|
```ts
|
||||||
@@ -529,6 +584,90 @@ Child namespace names and
|
|||||||
|
|
||||||
***
|
***
|
||||||
|
|
||||||
|
### listTables()
|
||||||
|
|
||||||
|
#### listTables(options)
|
||||||
|
|
||||||
|
```ts
|
||||||
|
abstract listTables(options?): Promise<ListTablesResponse>
|
||||||
|
```
|
||||||
|
|
||||||
|
List a page of the tables in this database.
|
||||||
|
|
||||||
|
To retrieve the tables after the page, pass the `pageToken` the response
|
||||||
|
carries back in. A page can be shorter than `limit` without being the last
|
||||||
|
one, so walk until a response carries no page token:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
const names = [];
|
||||||
|
let pageToken = undefined;
|
||||||
|
do {
|
||||||
|
const page = await conn.listTables({ pageToken, limit: 100 });
|
||||||
|
names.push(...page.tables);
|
||||||
|
pageToken = page.pageToken;
|
||||||
|
} while (pageToken);
|
||||||
|
```
|
||||||
|
|
||||||
|
##### Parameters
|
||||||
|
|
||||||
|
* **options?**: `Partial`<[`ListTablesOptions`](../interfaces/ListTablesOptions.md)>
|
||||||
|
Pagination options
|
||||||
|
(`pageToken`, `limit`).
|
||||||
|
|
||||||
|
##### Returns
|
||||||
|
|
||||||
|
`Promise`<[`ListTablesResponse`](../interfaces/ListTablesResponse.md)>
|
||||||
|
|
||||||
|
A page of table names and an
|
||||||
|
optional token for the tables after it.
|
||||||
|
|
||||||
|
#### listTables(namespacePath, options)
|
||||||
|
|
||||||
|
```ts
|
||||||
|
abstract listTables(namespacePath?, options?): Promise<ListTablesResponse>
|
||||||
|
```
|
||||||
|
|
||||||
|
List a page of the tables in this database.
|
||||||
|
|
||||||
|
##### Parameters
|
||||||
|
|
||||||
|
* **namespacePath?**: `string`[]
|
||||||
|
The namespace path to list tables from
|
||||||
|
(defaults to root namespace)
|
||||||
|
|
||||||
|
* **options?**: `Partial`<[`ListTablesOptions`](../interfaces/ListTablesOptions.md)>
|
||||||
|
Pagination options
|
||||||
|
(`pageToken`, `limit`).
|
||||||
|
|
||||||
|
##### Returns
|
||||||
|
|
||||||
|
`Promise`<[`ListTablesResponse`](../interfaces/ListTablesResponse.md)>
|
||||||
|
|
||||||
|
A page of table names and an
|
||||||
|
optional token for the tables after it.
|
||||||
|
|
||||||
|
***
|
||||||
|
|
||||||
|
### openMaterializedView()
|
||||||
|
|
||||||
|
```ts
|
||||||
|
abstract openMaterializedView(name): Promise<MaterializedView>
|
||||||
|
```
|
||||||
|
|
||||||
|
Open the materialized view named `name`.
|
||||||
|
|
||||||
|
Rejects a table that exists but is not a materialized view.
|
||||||
|
|
||||||
|
#### Parameters
|
||||||
|
|
||||||
|
* **name**: `string`
|
||||||
|
|
||||||
|
#### Returns
|
||||||
|
|
||||||
|
`Promise`<[`MaterializedView`](MaterializedView.md)>
|
||||||
|
|
||||||
|
***
|
||||||
|
|
||||||
### openTable()
|
### openTable()
|
||||||
|
|
||||||
```ts
|
```ts
|
||||||
@@ -538,18 +677,13 @@ abstract openTable(
|
|||||||
options?): Promise<Table>
|
options?): Promise<Table>
|
||||||
```
|
```
|
||||||
|
|
||||||
Open a table in the database.
|
|
||||||
|
|
||||||
#### Parameters
|
#### Parameters
|
||||||
|
|
||||||
* **name**: `string`
|
* **name**: `string`
|
||||||
The name of the table
|
|
||||||
|
|
||||||
* **namespacePath?**: `string`[]
|
* **namespacePath?**: `string`[]
|
||||||
The namespace path of the table (defaults to root namespace)
|
|
||||||
|
|
||||||
* **options?**: `Partial`<[`OpenTableOptions`](../interfaces/OpenTableOptions.md)>
|
* **options?**: `Partial`<[`OpenTableOptions`](../interfaces/OpenTableOptions.md)>
|
||||||
Additional options
|
|
||||||
|
|
||||||
#### Returns
|
#### Returns
|
||||||
|
|
||||||
@@ -590,7 +724,7 @@ a "not supported" error.
|
|||||||
|
|
||||||
***
|
***
|
||||||
|
|
||||||
### tableNames()
|
### ~~tableNames()~~
|
||||||
|
|
||||||
#### tableNames(options)
|
#### tableNames(options)
|
||||||
|
|
||||||
@@ -612,6 +746,10 @@ Tables will be returned in lexicographical order.
|
|||||||
|
|
||||||
`Promise`<`string`[]>
|
`Promise`<`string`[]>
|
||||||
|
|
||||||
|
##### Deprecated
|
||||||
|
|
||||||
|
Use [Connection.listTables](Connection.md#listtables) instead.
|
||||||
|
|
||||||
#### tableNames(namespacePath, options)
|
#### tableNames(namespacePath, options)
|
||||||
|
|
||||||
```ts
|
```ts
|
||||||
@@ -634,3 +772,7 @@ Tables will be returned in lexicographical order.
|
|||||||
##### Returns
|
##### Returns
|
||||||
|
|
||||||
`Promise`<`string`[]>
|
`Promise`<`string`[]>
|
||||||
|
|
||||||
|
##### Deprecated
|
||||||
|
|
||||||
|
Use [Connection.listTables](Connection.md#listtables) instead.
|
||||||
|
|||||||
@@ -0,0 +1,101 @@
|
|||||||
|
[**@lancedb/lancedb**](../README.md) • **Docs**
|
||||||
|
|
||||||
|
***
|
||||||
|
|
||||||
|
[@lancedb/lancedb](../globals.md) / MaterializedView
|
||||||
|
|
||||||
|
# Class: MaterializedView
|
||||||
|
|
||||||
|
A handle on a materialized view: its table plus its definition.
|
||||||
|
|
||||||
|
Obtained from [Connection#createMaterializedView](Connection.md#creatematerializedview) or
|
||||||
|
[Connection#openMaterializedView](Connection.md#openmaterializedview). The view is a normal table --
|
||||||
|
queries, indexes and search all apply through [MaterializedView#table](MaterializedView.md#table)
|
||||||
|
-- whose contents are maintained by [MaterializedView#refresh](MaterializedView.md#refresh).
|
||||||
|
|
||||||
|
## Constructors
|
||||||
|
|
||||||
|
### new MaterializedView()
|
||||||
|
|
||||||
|
```ts
|
||||||
|
new MaterializedView(table): MaterializedView
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Parameters
|
||||||
|
|
||||||
|
* **table**: [`Table`](Table.md)
|
||||||
|
|
||||||
|
#### Returns
|
||||||
|
|
||||||
|
[`MaterializedView`](MaterializedView.md)
|
||||||
|
|
||||||
|
## Accessors
|
||||||
|
|
||||||
|
### name
|
||||||
|
|
||||||
|
```ts
|
||||||
|
get name(): string
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Returns
|
||||||
|
|
||||||
|
`string`
|
||||||
|
|
||||||
|
## Methods
|
||||||
|
|
||||||
|
### definition()
|
||||||
|
|
||||||
|
```ts
|
||||||
|
definition(): Promise<MaterializedViewDefinition>
|
||||||
|
```
|
||||||
|
|
||||||
|
The query that defines the view, read from its stored schema.
|
||||||
|
|
||||||
|
#### Returns
|
||||||
|
|
||||||
|
`Promise`<[`MaterializedViewDefinition`](../interfaces/MaterializedViewDefinition.md)>
|
||||||
|
|
||||||
|
***
|
||||||
|
|
||||||
|
### refresh()
|
||||||
|
|
||||||
|
```ts
|
||||||
|
refresh(options?): Promise<RefreshMaterializedViewResult>
|
||||||
|
```
|
||||||
|
|
||||||
|
Recompute the view from its source.
|
||||||
|
|
||||||
|
The refresh is incremental when the source's changes can be reconciled
|
||||||
|
into the view -- rows added, changed or removed since the last one --
|
||||||
|
and otherwise rebuilds. `full` forces a rebuild; `sourceVersion`
|
||||||
|
refreshes to that source version instead of the latest.
|
||||||
|
|
||||||
|
Concurrent refreshes of one view do not duplicate its rows. Two that
|
||||||
|
plan the same source rows conflict on commit, and the loser throws
|
||||||
|
rather than writing them a second time.
|
||||||
|
|
||||||
|
#### Parameters
|
||||||
|
|
||||||
|
* **options?**
|
||||||
|
|
||||||
|
* **options.full?**: `boolean`
|
||||||
|
|
||||||
|
* **options.sourceVersion?**: `number`
|
||||||
|
|
||||||
|
#### Returns
|
||||||
|
|
||||||
|
`Promise`<[`RefreshMaterializedViewResult`](../interfaces/RefreshMaterializedViewResult.md)>
|
||||||
|
|
||||||
|
***
|
||||||
|
|
||||||
|
### table()
|
||||||
|
|
||||||
|
```ts
|
||||||
|
table(): Table
|
||||||
|
```
|
||||||
|
|
||||||
|
The view, as the table it is.
|
||||||
|
|
||||||
|
#### Returns
|
||||||
|
|
||||||
|
[`Table`](Table.md)
|
||||||
@@ -676,9 +676,17 @@ List all the versions of the table
|
|||||||
abstract mergeInsert(on): MergeInsertBuilder
|
abstract mergeInsert(on): MergeInsertBuilder
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Create a [MergeInsertBuilder](MergeInsertBuilder.md), which combines new data with the
|
||||||
|
existing table in a single transaction — inserting, updating and deleting
|
||||||
|
rows depending on how they match.
|
||||||
|
|
||||||
#### Parameters
|
#### Parameters
|
||||||
|
|
||||||
* **on**: `string` \| `string`[]
|
* **on**: `string` \| `string`[]
|
||||||
|
The column, or columns, to match source rows against target
|
||||||
|
rows on. Typically a key or id column. Several columns match on the
|
||||||
|
composite key: a source row updates a target row only when it agrees on
|
||||||
|
every one of them.
|
||||||
|
|
||||||
#### Returns
|
#### Returns
|
||||||
|
|
||||||
@@ -942,7 +950,7 @@ Get the schema of the table.
|
|||||||
abstract search(
|
abstract search(
|
||||||
query,
|
query,
|
||||||
queryType?,
|
queryType?,
|
||||||
ftsColumns?): Query | VectorQuery
|
ftsColumns?): Query | VectorQuery | AutoQuery
|
||||||
```
|
```
|
||||||
|
|
||||||
Create a search query to find the nearest neighbors
|
Create a search query to find the nearest neighbors
|
||||||
@@ -964,7 +972,7 @@ of the given query
|
|||||||
|
|
||||||
#### Returns
|
#### Returns
|
||||||
|
|
||||||
[`Query`](Query.md) \| [`VectorQuery`](VectorQuery.md)
|
[`Query`](Query.md) \| [`VectorQuery`](VectorQuery.md) \| [`AutoQuery`](AutoQuery.md)
|
||||||
|
|
||||||
***
|
***
|
||||||
|
|
||||||
@@ -1292,6 +1300,18 @@ abstract updateFieldMetadata(updates): Promise<UpdateFieldMetadataResult>
|
|||||||
|
|
||||||
Update per-field (column) metadata.
|
Update per-field (column) metadata.
|
||||||
|
|
||||||
|
The following keys are treated specially, by convention, and should be
|
||||||
|
used when appropriate:
|
||||||
|
|
||||||
|
- `lancedb:description`: for a human-readable description of a field.
|
||||||
|
- `lancedb:tag:<name>`: for a user-defined key-value tag, where the suffix
|
||||||
|
names the tag category; e.g. `lancedb:tag:model: "clip"`.
|
||||||
|
- `lancedb:logical-column`: for a column grouping; e.g. `feature_v1` and
|
||||||
|
`feature_v2` might be in the same logical column.
|
||||||
|
- `lancedb:status`: for status options (`production`, `candidate`,
|
||||||
|
`deprecated`, `archived`) to designate the current life cycle state of
|
||||||
|
this column.
|
||||||
|
|
||||||
#### Parameters
|
#### Parameters
|
||||||
|
|
||||||
* **updates**: [`FieldMetadataUpdate`](../interfaces/FieldMetadataUpdate.md)[]
|
* **updates**: [`FieldMetadataUpdate`](../interfaces/FieldMetadataUpdate.md)[]
|
||||||
|
|||||||
+10
-3
@@ -18,6 +18,7 @@
|
|||||||
|
|
||||||
## Classes
|
## Classes
|
||||||
|
|
||||||
|
- [AutoQuery](classes/AutoQuery.md)
|
||||||
- [BooleanQuery](classes/BooleanQuery.md)
|
- [BooleanQuery](classes/BooleanQuery.md)
|
||||||
- [BoostQuery](classes/BoostQuery.md)
|
- [BoostQuery](classes/BoostQuery.md)
|
||||||
- [BranchContents](classes/BranchContents.md)
|
- [BranchContents](classes/BranchContents.md)
|
||||||
@@ -28,6 +29,7 @@
|
|||||||
- [Job](classes/Job.md)
|
- [Job](classes/Job.md)
|
||||||
- [MakeArrowTableOptions](classes/MakeArrowTableOptions.md)
|
- [MakeArrowTableOptions](classes/MakeArrowTableOptions.md)
|
||||||
- [MatchQuery](classes/MatchQuery.md)
|
- [MatchQuery](classes/MatchQuery.md)
|
||||||
|
- [MaterializedView](classes/MaterializedView.md)
|
||||||
- [MergeInsertBuilder](classes/MergeInsertBuilder.md)
|
- [MergeInsertBuilder](classes/MergeInsertBuilder.md)
|
||||||
- [MultiMatchQuery](classes/MultiMatchQuery.md)
|
- [MultiMatchQuery](classes/MultiMatchQuery.md)
|
||||||
- [NativeJsHeaderProvider](classes/NativeJsHeaderProvider.md)
|
- [NativeJsHeaderProvider](classes/NativeJsHeaderProvider.md)
|
||||||
@@ -59,6 +61,9 @@
|
|||||||
- [BranchIndexSummary](interfaces/BranchIndexSummary.md)
|
- [BranchIndexSummary](interfaces/BranchIndexSummary.md)
|
||||||
- [BranchRowCountSummary](interfaces/BranchRowCountSummary.md)
|
- [BranchRowCountSummary](interfaces/BranchRowCountSummary.md)
|
||||||
- [BucketStats](interfaces/BucketStats.md)
|
- [BucketStats](interfaces/BucketStats.md)
|
||||||
|
- [CherryPickError](interfaces/CherryPickError.md)
|
||||||
|
- [CherryPickPreview](interfaces/CherryPickPreview.md)
|
||||||
|
- [CherryPickResult](interfaces/CherryPickResult.md)
|
||||||
- [ClientConfig](interfaces/ClientConfig.md)
|
- [ClientConfig](interfaces/ClientConfig.md)
|
||||||
- [ColumnAlteration](interfaces/ColumnAlteration.md)
|
- [ColumnAlteration](interfaces/ColumnAlteration.md)
|
||||||
- [ColumnOrdering](interfaces/ColumnOrdering.md)
|
- [ColumnOrdering](interfaces/ColumnOrdering.md)
|
||||||
@@ -96,12 +101,12 @@
|
|||||||
- [JobInfo](interfaces/JobInfo.md)
|
- [JobInfo](interfaces/JobInfo.md)
|
||||||
- [ListNamespacesOptions](interfaces/ListNamespacesOptions.md)
|
- [ListNamespacesOptions](interfaces/ListNamespacesOptions.md)
|
||||||
- [ListNamespacesResponse](interfaces/ListNamespacesResponse.md)
|
- [ListNamespacesResponse](interfaces/ListNamespacesResponse.md)
|
||||||
|
- [ListTablesOptions](interfaces/ListTablesOptions.md)
|
||||||
|
- [ListTablesResponse](interfaces/ListTablesResponse.md)
|
||||||
- [LsmStats](interfaces/LsmStats.md)
|
- [LsmStats](interfaces/LsmStats.md)
|
||||||
- [LsmWriteSpec](interfaces/LsmWriteSpec.md)
|
- [LsmWriteSpec](interfaces/LsmWriteSpec.md)
|
||||||
|
- [MaterializedViewDefinition](interfaces/MaterializedViewDefinition.md)
|
||||||
- [MemtableStats](interfaces/MemtableStats.md)
|
- [MemtableStats](interfaces/MemtableStats.md)
|
||||||
- [MergeBlocker](interfaces/MergeBlocker.md)
|
|
||||||
- [MergeBranchResult](interfaces/MergeBranchResult.md)
|
|
||||||
- [MergePreview](interfaces/MergePreview.md)
|
|
||||||
- [MergeResult](interfaces/MergeResult.md)
|
- [MergeResult](interfaces/MergeResult.md)
|
||||||
- [NativeOAuthConfig](interfaces/NativeOAuthConfig.md)
|
- [NativeOAuthConfig](interfaces/NativeOAuthConfig.md)
|
||||||
- [OAuthConfig](interfaces/OAuthConfig.md)
|
- [OAuthConfig](interfaces/OAuthConfig.md)
|
||||||
@@ -110,6 +115,7 @@
|
|||||||
- [OptimizeStats](interfaces/OptimizeStats.md)
|
- [OptimizeStats](interfaces/OptimizeStats.md)
|
||||||
- [QueryExecutionOptions](interfaces/QueryExecutionOptions.md)
|
- [QueryExecutionOptions](interfaces/QueryExecutionOptions.md)
|
||||||
- [RefreshColumnResult](interfaces/RefreshColumnResult.md)
|
- [RefreshColumnResult](interfaces/RefreshColumnResult.md)
|
||||||
|
- [RefreshMaterializedViewResult](interfaces/RefreshMaterializedViewResult.md)
|
||||||
- [RemovalStats](interfaces/RemovalStats.md)
|
- [RemovalStats](interfaces/RemovalStats.md)
|
||||||
- [RenameTableOptions](interfaces/RenameTableOptions.md)
|
- [RenameTableOptions](interfaces/RenameTableOptions.md)
|
||||||
- [RestNamespaceConfig](interfaces/RestNamespaceConfig.md)
|
- [RestNamespaceConfig](interfaces/RestNamespaceConfig.md)
|
||||||
@@ -142,6 +148,7 @@
|
|||||||
- [FieldLike](type-aliases/FieldLike.md)
|
- [FieldLike](type-aliases/FieldLike.md)
|
||||||
- [IntoSql](type-aliases/IntoSql.md)
|
- [IntoSql](type-aliases/IntoSql.md)
|
||||||
- [IntoVector](type-aliases/IntoVector.md)
|
- [IntoVector](type-aliases/IntoVector.md)
|
||||||
|
- [MaterializedViewSelect](type-aliases/MaterializedViewSelect.md)
|
||||||
- [MultiVector](type-aliases/MultiVector.md)
|
- [MultiVector](type-aliases/MultiVector.md)
|
||||||
- [RecordBatchLike](type-aliases/RecordBatchLike.md)
|
- [RecordBatchLike](type-aliases/RecordBatchLike.md)
|
||||||
- [SchemaLike](type-aliases/SchemaLike.md)
|
- [SchemaLike](type-aliases/SchemaLike.md)
|
||||||
|
|||||||
@@ -50,6 +50,14 @@ changedColumns: BranchColumnChange[];
|
|||||||
|
|
||||||
***
|
***
|
||||||
|
|
||||||
|
### errors
|
||||||
|
|
||||||
|
```ts
|
||||||
|
errors: CherryPickError[];
|
||||||
|
```
|
||||||
|
|
||||||
|
***
|
||||||
|
|
||||||
### fromBranch
|
### fromBranch
|
||||||
|
|
||||||
```ts
|
```ts
|
||||||
@@ -66,22 +74,6 @@ mainVersion: number;
|
|||||||
|
|
||||||
***
|
***
|
||||||
|
|
||||||
### mergeBlockers
|
|
||||||
|
|
||||||
```ts
|
|
||||||
mergeBlockers: MergeBlocker[];
|
|
||||||
```
|
|
||||||
|
|
||||||
***
|
|
||||||
|
|
||||||
### mergeable
|
|
||||||
|
|
||||||
```ts
|
|
||||||
mergeable: boolean;
|
|
||||||
```
|
|
||||||
|
|
||||||
***
|
|
||||||
|
|
||||||
### parentVersion
|
### parentVersion
|
||||||
|
|
||||||
```ts
|
```ts
|
||||||
|
|||||||
@@ -2,11 +2,11 @@
|
|||||||
|
|
||||||
***
|
***
|
||||||
|
|
||||||
[@lancedb/lancedb](../globals.md) / MergeBlocker
|
[@lancedb/lancedb](../globals.md) / CherryPickError
|
||||||
|
|
||||||
# Interface: MergeBlocker
|
# Interface: CherryPickError
|
||||||
|
|
||||||
A reason why a branch cannot currently be merged.
|
A reason why a cherry-pick cannot currently land.
|
||||||
|
|
||||||
## Properties
|
## Properties
|
||||||
|
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
[**@lancedb/lancedb**](../README.md) • **Docs**
|
||||||
|
|
||||||
|
***
|
||||||
|
|
||||||
|
[@lancedb/lancedb](../globals.md) / CherryPickPreview
|
||||||
|
|
||||||
|
# Interface: CherryPickPreview
|
||||||
|
|
||||||
|
Changes that would be, or were, promoted by a cherry-pick.
|
||||||
|
|
||||||
|
## Properties
|
||||||
|
|
||||||
|
### promotedColumns
|
||||||
|
|
||||||
|
```ts
|
||||||
|
promotedColumns: string[];
|
||||||
|
```
|
||||||
+6
-6
@@ -2,11 +2,11 @@
|
|||||||
|
|
||||||
***
|
***
|
||||||
|
|
||||||
[@lancedb/lancedb](../globals.md) / MergeBranchResult
|
[@lancedb/lancedb](../globals.md) / CherryPickResult
|
||||||
|
|
||||||
# Interface: MergeBranchResult
|
# Interface: CherryPickResult
|
||||||
|
|
||||||
Result of previewing or attempting a branch merge.
|
Result of previewing or attempting a cherry-pick.
|
||||||
|
|
||||||
## Properties
|
## Properties
|
||||||
|
|
||||||
@@ -29,7 +29,7 @@ optional mainVersionAfter: number;
|
|||||||
### preview
|
### preview
|
||||||
|
|
||||||
```ts
|
```ts
|
||||||
preview: MergePreview;
|
preview: CherryPickPreview;
|
||||||
```
|
```
|
||||||
|
|
||||||
***
|
***
|
||||||
@@ -38,9 +38,9 @@ preview: MergePreview;
|
|||||||
|
|
||||||
```ts
|
```ts
|
||||||
status:
|
status:
|
||||||
|
| "failed"
|
||||||
| "unknown"
|
| "unknown"
|
||||||
| "rejected"
|
|
||||||
| "ready"
|
| "ready"
|
||||||
| "notImplemented"
|
| "notImplemented"
|
||||||
| "merged";
|
| "cherryPicked";
|
||||||
```
|
```
|
||||||
@@ -17,7 +17,8 @@ metadata: Record<string, null | string>;
|
|||||||
```
|
```
|
||||||
|
|
||||||
Metadata key/value pairs. Merged into the field's existing metadata by
|
Metadata key/value pairs. Merged into the field's existing metadata by
|
||||||
default; a value of `null` deletes that key.
|
default; a value of `null` deletes that key. See
|
||||||
|
[Table.updateFieldMetadata](../classes/Table.md#updatefieldmetadata) for the conventional `lancedb:*` keys.
|
||||||
|
|
||||||
***
|
***
|
||||||
|
|
||||||
|
|||||||
@@ -50,7 +50,7 @@ If this is false, and another index already exists on the same columns
|
|||||||
and the same name, then an error will be returned. This is true even if
|
and the same name, then an error will be returned. This is true even if
|
||||||
that index is out of date.
|
that index is out of date.
|
||||||
|
|
||||||
The default is true
|
The default is false
|
||||||
|
|
||||||
***
|
***
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,34 @@
|
|||||||
|
[**@lancedb/lancedb**](../README.md) • **Docs**
|
||||||
|
|
||||||
|
***
|
||||||
|
|
||||||
|
[@lancedb/lancedb](../globals.md) / ListTablesOptions
|
||||||
|
|
||||||
|
# Interface: ListTablesOptions
|
||||||
|
|
||||||
|
## Properties
|
||||||
|
|
||||||
|
### limit?
|
||||||
|
|
||||||
|
```ts
|
||||||
|
optional limit: number;
|
||||||
|
```
|
||||||
|
|
||||||
|
An upper bound on how many tables to return.
|
||||||
|
|
||||||
|
A page may hold fewer than this and still not be the last one, so keep
|
||||||
|
going while the response carries a page token rather than while pages are
|
||||||
|
full.
|
||||||
|
|
||||||
|
***
|
||||||
|
|
||||||
|
### pageToken?
|
||||||
|
|
||||||
|
```ts
|
||||||
|
optional pageToken: string;
|
||||||
|
```
|
||||||
|
|
||||||
|
Token from a previous response, to resume listing where it left off.
|
||||||
|
|
||||||
|
The token is opaque: it carries whatever the database needs to resume, and
|
||||||
|
callers should not construct or interpret one.
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
[**@lancedb/lancedb**](../README.md) • **Docs**
|
||||||
|
|
||||||
|
***
|
||||||
|
|
||||||
|
[@lancedb/lancedb](../globals.md) / ListTablesResponse
|
||||||
|
|
||||||
|
# Interface: ListTablesResponse
|
||||||
|
|
||||||
|
## Properties
|
||||||
|
|
||||||
|
### pageToken?
|
||||||
|
|
||||||
|
```ts
|
||||||
|
optional pageToken: string;
|
||||||
|
```
|
||||||
|
|
||||||
|
***
|
||||||
|
|
||||||
|
### tables
|
||||||
|
|
||||||
|
```ts
|
||||||
|
tables: string[];
|
||||||
|
```
|
||||||
@@ -0,0 +1,69 @@
|
|||||||
|
[**@lancedb/lancedb**](../README.md) • **Docs**
|
||||||
|
|
||||||
|
***
|
||||||
|
|
||||||
|
[@lancedb/lancedb](../globals.md) / MaterializedViewDefinition
|
||||||
|
|
||||||
|
# Interface: MaterializedViewDefinition
|
||||||
|
|
||||||
|
The query that defines a materialized view.
|
||||||
|
|
||||||
|
## Properties
|
||||||
|
|
||||||
|
### filter?
|
||||||
|
|
||||||
|
```ts
|
||||||
|
optional filter: string;
|
||||||
|
```
|
||||||
|
|
||||||
|
SQL predicate selecting the source rows the view holds.
|
||||||
|
|
||||||
|
***
|
||||||
|
|
||||||
|
### inputs
|
||||||
|
|
||||||
|
```ts
|
||||||
|
inputs: string[];
|
||||||
|
```
|
||||||
|
|
||||||
|
Source columns the projections and filter read.
|
||||||
|
|
||||||
|
***
|
||||||
|
|
||||||
|
### limit?
|
||||||
|
|
||||||
|
```ts
|
||||||
|
optional limit: number;
|
||||||
|
```
|
||||||
|
|
||||||
|
Cap on the number of rows the view holds.
|
||||||
|
|
||||||
|
***
|
||||||
|
|
||||||
|
### projections
|
||||||
|
|
||||||
|
```ts
|
||||||
|
projections: [string, string][];
|
||||||
|
```
|
||||||
|
|
||||||
|
`[output column, SQL expression]` pairs, in view schema order.
|
||||||
|
|
||||||
|
***
|
||||||
|
|
||||||
|
### sourceNamespace
|
||||||
|
|
||||||
|
```ts
|
||||||
|
sourceNamespace: string[];
|
||||||
|
```
|
||||||
|
|
||||||
|
Namespace holding the source table; empty is the root namespace.
|
||||||
|
|
||||||
|
***
|
||||||
|
|
||||||
|
### sourceTable
|
||||||
|
|
||||||
|
```ts
|
||||||
|
sourceTable: string;
|
||||||
|
```
|
||||||
|
|
||||||
|
Name of the source table, in the same database as the view.
|
||||||
@@ -1,17 +0,0 @@
|
|||||||
[**@lancedb/lancedb**](../README.md) • **Docs**
|
|
||||||
|
|
||||||
***
|
|
||||||
|
|
||||||
[@lancedb/lancedb](../globals.md) / MergePreview
|
|
||||||
|
|
||||||
# Interface: MergePreview
|
|
||||||
|
|
||||||
Changes that would be, or were, promoted by a branch merge.
|
|
||||||
|
|
||||||
## Properties
|
|
||||||
|
|
||||||
### promotedColumns
|
|
||||||
|
|
||||||
```ts
|
|
||||||
promotedColumns: string[];
|
|
||||||
```
|
|
||||||
@@ -0,0 +1,41 @@
|
|||||||
|
[**@lancedb/lancedb**](../README.md) • **Docs**
|
||||||
|
|
||||||
|
***
|
||||||
|
|
||||||
|
[@lancedb/lancedb](../globals.md) / RefreshMaterializedViewResult
|
||||||
|
|
||||||
|
# Interface: RefreshMaterializedViewResult
|
||||||
|
|
||||||
|
## Properties
|
||||||
|
|
||||||
|
### mode
|
||||||
|
|
||||||
|
```ts
|
||||||
|
mode: string;
|
||||||
|
```
|
||||||
|
|
||||||
|
How the view was brought up to date: "rebuild", "incremental" or "no_op".
|
||||||
|
|
||||||
|
***
|
||||||
|
|
||||||
|
### rowsWritten
|
||||||
|
|
||||||
|
```ts
|
||||||
|
rowsWritten: number;
|
||||||
|
```
|
||||||
|
|
||||||
|
***
|
||||||
|
|
||||||
|
### sourceVersion
|
||||||
|
|
||||||
|
```ts
|
||||||
|
sourceVersion: number;
|
||||||
|
```
|
||||||
|
|
||||||
|
***
|
||||||
|
|
||||||
|
### version
|
||||||
|
|
||||||
|
```ts
|
||||||
|
version: number;
|
||||||
|
```
|
||||||
@@ -4,11 +4,16 @@
|
|||||||
|
|
||||||
[@lancedb/lancedb](../globals.md) / TableNamesOptions
|
[@lancedb/lancedb](../globals.md) / TableNamesOptions
|
||||||
|
|
||||||
# Interface: TableNamesOptions
|
# Interface: ~~TableNamesOptions~~
|
||||||
|
|
||||||
|
## Deprecated
|
||||||
|
|
||||||
|
Use [ListTablesOptions](ListTablesOptions.md) with [Connection.listTables](../classes/Connection.md#listtables)
|
||||||
|
instead.
|
||||||
|
|
||||||
## Properties
|
## Properties
|
||||||
|
|
||||||
### limit?
|
### ~~limit?~~
|
||||||
|
|
||||||
```ts
|
```ts
|
||||||
optional limit: number;
|
optional limit: number;
|
||||||
@@ -18,7 +23,7 @@ An optional limit to the number of results to return.
|
|||||||
|
|
||||||
***
|
***
|
||||||
|
|
||||||
### startAfter?
|
### ~~startAfter?~~
|
||||||
|
|
||||||
```ts
|
```ts
|
||||||
optional startAfter: string;
|
optional startAfter: string;
|
||||||
|
|||||||
@@ -25,9 +25,12 @@
|
|||||||
### Type Aliases
|
### Type Aliases
|
||||||
|
|
||||||
- [CreateReturnType](type-aliases/CreateReturnType.md)
|
- [CreateReturnType](type-aliases/CreateReturnType.md)
|
||||||
|
- [EmbeddingMetadataEntry](type-aliases/EmbeddingMetadataEntry.md)
|
||||||
|
- [ResolvedEmbeddingFunctionConfig](type-aliases/ResolvedEmbeddingFunctionConfig.md)
|
||||||
|
|
||||||
### Functions
|
### Functions
|
||||||
|
|
||||||
- [LanceSchema](functions/LanceSchema.md)
|
- [LanceSchema](functions/LanceSchema.md)
|
||||||
- [getRegistry](functions/getRegistry.md)
|
- [getRegistry](functions/getRegistry.md)
|
||||||
|
- [parseEmbeddingMetadata](functions/parseEmbeddingMetadata.md)
|
||||||
- [register](functions/register.md)
|
- [register](functions/register.md)
|
||||||
|
|||||||
@@ -10,16 +10,12 @@
|
|||||||
function getRegistry(): EmbeddingFunctionRegistry
|
function getRegistry(): EmbeddingFunctionRegistry
|
||||||
```
|
```
|
||||||
|
|
||||||
Utility function to get the global instance of the registry
|
Get the global embedding function registry.
|
||||||
|
|
||||||
|
LanceDB built-in providers are initialized when this public API is first
|
||||||
|
used, so importing the root package does not change automatic search
|
||||||
|
selection for tables without embedding metadata.
|
||||||
|
|
||||||
## Returns
|
## Returns
|
||||||
|
|
||||||
[`EmbeddingFunctionRegistry`](../classes/EmbeddingFunctionRegistry.md)
|
[`EmbeddingFunctionRegistry`](../classes/EmbeddingFunctionRegistry.md)
|
||||||
|
|
||||||
`EmbeddingFunctionRegistry` The global instance of the registry
|
|
||||||
|
|
||||||
## Example
|
|
||||||
|
|
||||||
```ts
|
|
||||||
const registry = getRegistry();
|
|
||||||
const openai = registry.get("openai").create();
|
|
||||||
|
|||||||
@@ -0,0 +1,22 @@
|
|||||||
|
[**@lancedb/lancedb**](../../../README.md) • **Docs**
|
||||||
|
|
||||||
|
***
|
||||||
|
|
||||||
|
[@lancedb/lancedb](../../../globals.md) / [embedding](../README.md) / parseEmbeddingMetadata
|
||||||
|
|
||||||
|
# Function: parseEmbeddingMetadata()
|
||||||
|
|
||||||
|
```ts
|
||||||
|
function parseEmbeddingMetadata(json): EmbeddingMetadataEntry[]
|
||||||
|
```
|
||||||
|
|
||||||
|
The single parser for `embedding_functions` schema metadata: every reader
|
||||||
|
goes through here, so the wire contract cannot fork between them.
|
||||||
|
|
||||||
|
## Parameters
|
||||||
|
|
||||||
|
* **json**: `string`
|
||||||
|
|
||||||
|
## Returns
|
||||||
|
|
||||||
|
[`EmbeddingMetadataEntry`](../type-aliases/EmbeddingMetadataEntry.md)[]
|
||||||
@@ -0,0 +1,40 @@
|
|||||||
|
[**@lancedb/lancedb**](../../../README.md) • **Docs**
|
||||||
|
|
||||||
|
***
|
||||||
|
|
||||||
|
[@lancedb/lancedb](../../../globals.md) / [embedding](../README.md) / EmbeddingMetadataEntry
|
||||||
|
|
||||||
|
# Type Alias: EmbeddingMetadataEntry
|
||||||
|
|
||||||
|
```ts
|
||||||
|
type EmbeddingMetadataEntry: object;
|
||||||
|
```
|
||||||
|
|
||||||
|
One entry of the `embedding_functions` schema metadata, with the column
|
||||||
|
keys normalized across the bindings' spellings.
|
||||||
|
|
||||||
|
## Type declaration
|
||||||
|
|
||||||
|
### model
|
||||||
|
|
||||||
|
```ts
|
||||||
|
model: EmbeddingFunction["TOptions"];
|
||||||
|
```
|
||||||
|
|
||||||
|
### name
|
||||||
|
|
||||||
|
```ts
|
||||||
|
name: string;
|
||||||
|
```
|
||||||
|
|
||||||
|
### sourceColumn
|
||||||
|
|
||||||
|
```ts
|
||||||
|
sourceColumn: string;
|
||||||
|
```
|
||||||
|
|
||||||
|
### vectorColumn
|
||||||
|
|
||||||
|
```ts
|
||||||
|
vectorColumn: string;
|
||||||
|
```
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
[**@lancedb/lancedb**](../../../README.md) • **Docs**
|
||||||
|
|
||||||
|
***
|
||||||
|
|
||||||
|
[@lancedb/lancedb](../../../globals.md) / [embedding](../README.md) / ResolvedEmbeddingFunctionConfig
|
||||||
|
|
||||||
|
# Type Alias: ResolvedEmbeddingFunctionConfig
|
||||||
|
|
||||||
|
```ts
|
||||||
|
type ResolvedEmbeddingFunctionConfig: EmbeddingFunctionConfig & object;
|
||||||
|
```
|
||||||
|
|
||||||
|
An [EmbeddingFunctionConfig] read back from table metadata, where the
|
||||||
|
vector column is always recorded.
|
||||||
|
|
||||||
|
## Type declaration
|
||||||
|
|
||||||
|
### vectorColumn
|
||||||
|
|
||||||
|
```ts
|
||||||
|
vectorColumn: string;
|
||||||
|
```
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
[**@lancedb/lancedb**](../README.md) • **Docs**
|
||||||
|
|
||||||
|
***
|
||||||
|
|
||||||
|
[@lancedb/lancedb](../globals.md) / MaterializedViewSelect
|
||||||
|
|
||||||
|
# Type Alias: MaterializedViewSelect
|
||||||
|
|
||||||
|
```ts
|
||||||
|
type MaterializedViewSelect: (string | [string, string])[] | Record<string, string>;
|
||||||
|
```
|
||||||
|
|
||||||
|
The view's columns: column names, `[alias, SQL expression]` pairs, or a
|
||||||
|
record of the same. A bare name projects itself.
|
||||||
@@ -54,6 +54,60 @@ listing a storage directory.
|
|||||||
|
|
||||||
::: lancedb.LsmWriteSpec
|
::: lancedb.LsmWriteSpec
|
||||||
|
|
||||||
|
## Functions and Jobs
|
||||||
|
|
||||||
|
::: lancedb.functions.FunctionArtifact
|
||||||
|
|
||||||
|
::: lancedb.functions.FunctionParameter
|
||||||
|
|
||||||
|
::: lancedb.functions.FunctionResultField
|
||||||
|
|
||||||
|
::: lancedb.functions.FunctionOutput
|
||||||
|
|
||||||
|
::: lancedb.functions.FunctionSignature
|
||||||
|
|
||||||
|
::: lancedb.functions.PythonEnvironmentSpec
|
||||||
|
|
||||||
|
::: lancedb.functions.udf
|
||||||
|
|
||||||
|
::: lancedb.functions.UdfDefinition
|
||||||
|
|
||||||
|
::: lancedb.functions.FunctionRegistrationRequest
|
||||||
|
|
||||||
|
::: lancedb.functions.FunctionArtifactRequest
|
||||||
|
|
||||||
|
::: lancedb.functions.FunctionArtifactContent
|
||||||
|
|
||||||
|
::: lancedb.functions.PythonAdapterSpec
|
||||||
|
|
||||||
|
::: lancedb.functions.FunctionVersion
|
||||||
|
|
||||||
|
::: lancedb.functions.PythonRuntimeSpec
|
||||||
|
|
||||||
|
::: lancedb.functions.FunctionVersionRef
|
||||||
|
|
||||||
|
::: lancedb.functions.ApplicationInput
|
||||||
|
|
||||||
|
::: lancedb.functions.FunctionApplication
|
||||||
|
|
||||||
|
::: lancedb.functions.InputBinding
|
||||||
|
|
||||||
|
::: lancedb.functions.OutputMapping
|
||||||
|
|
||||||
|
::: lancedb.functions.FunctionBinding
|
||||||
|
|
||||||
|
::: lancedb.functions.RefreshColumnResult
|
||||||
|
|
||||||
|
::: lancedb.job.Job
|
||||||
|
|
||||||
|
::: lancedb.job.AsyncJob
|
||||||
|
|
||||||
|
## Materialized Views (Synchronous)
|
||||||
|
|
||||||
|
::: lancedb.materialized_view.MaterializedView
|
||||||
|
|
||||||
|
::: lancedb.materialized_view.MaterializedViewDefinition
|
||||||
|
|
||||||
## Expressions
|
## Expressions
|
||||||
|
|
||||||
Type-safe expression builder for filters and projections. Use these instead
|
Type-safe expression builder for filters and projections. Use these instead
|
||||||
@@ -105,6 +159,8 @@ and combined with [BooleanQuery][lancedb.query.BooleanQuery].
|
|||||||
|
|
||||||
::: lancedb.query.FullTextOperator
|
::: lancedb.query.FullTextOperator
|
||||||
|
|
||||||
|
::: lancedb.query.DocumentGranularity
|
||||||
|
|
||||||
::: lancedb.query.Occur
|
::: lancedb.query.Occur
|
||||||
|
|
||||||
## Embeddings
|
## Embeddings
|
||||||
@@ -153,8 +209,9 @@ The same option is available on `lancedb.tokenize(...)` and the deprecated
|
|||||||
```python
|
```python
|
||||||
import lancedb
|
import lancedb
|
||||||
|
|
||||||
tokens = list(lancedb.tokenize("acme makes searchable data",
|
tokens = list(
|
||||||
custom_stop_words=["acme"]))
|
lancedb.tokenize("acme makes searchable data", custom_stop_words=["acme"])
|
||||||
|
)
|
||||||
```
|
```
|
||||||
|
|
||||||
::: lancedb.tokenize
|
::: lancedb.tokenize
|
||||||
@@ -166,9 +223,13 @@ tokens = list(lancedb.tokenize("acme makes searchable data",
|
|||||||
Blob columns store large binary values out of line so they can be read lazily
|
Blob columns store large binary values out of line so they can be read lazily
|
||||||
instead of being materialized with the rest of the row.
|
instead of being materialized with the rest of the row.
|
||||||
|
|
||||||
::: lancedb.blob
|
`lancedb.BlobType` is `lance.blob.BlobType` when pylance is installed. Without
|
||||||
|
pylance, LanceDB uses a matching `lance.blob.v2` extension type so blob columns
|
||||||
|
still work. Queries return descriptors. Call
|
||||||
|
[`fetch_blob_files`][lancedb.table.Table.fetch_blob_files] for lazy reads or
|
||||||
|
[`fetch_blobs`][lancedb.table.Table.fetch_blobs] for eager bytes.
|
||||||
|
|
||||||
::: lancedb.BlobType
|
::: lancedb.blob
|
||||||
|
|
||||||
::: lancedb._blob.BlobFile
|
::: lancedb._blob.BlobFile
|
||||||
options:
|
options:
|
||||||
@@ -206,6 +267,8 @@ instead of being materialized with the rest of the row.
|
|||||||
|
|
||||||
::: lancedb.streaming.StreamingDataset
|
::: lancedb.streaming.StreamingDataset
|
||||||
|
|
||||||
|
::: lancedb.streaming.StreamingDataLoader
|
||||||
|
|
||||||
::: lancedb.permutation.permutation_builder
|
::: lancedb.permutation.permutation_builder
|
||||||
|
|
||||||
::: lancedb.permutation.PermutationBuilder
|
::: lancedb.permutation.PermutationBuilder
|
||||||
@@ -246,6 +309,10 @@ Table hold your actual data as a collection of records / rows.
|
|||||||
|
|
||||||
::: lancedb.table.AsyncBranches
|
::: lancedb.table.AsyncBranches
|
||||||
|
|
||||||
|
## Materialized Views (Asynchronous)
|
||||||
|
|
||||||
|
::: lancedb.materialized_view.AsyncMaterializedView
|
||||||
|
|
||||||
## Indices (Asynchronous)
|
## Indices (Asynchronous)
|
||||||
|
|
||||||
Indices can be created on a table to speed up queries. This section
|
Indices can be created on a table to speed up queries. This section
|
||||||
|
|||||||
@@ -1,17 +0,0 @@
|
|||||||
{
|
|
||||||
"include": [
|
|
||||||
"src/*.ts",
|
|
||||||
],
|
|
||||||
"compilerOptions": {
|
|
||||||
"target": "es2022",
|
|
||||||
"module": "nodenext",
|
|
||||||
"declaration": true,
|
|
||||||
"outDir": "./dist",
|
|
||||||
"strict": true,
|
|
||||||
"allowJs": true,
|
|
||||||
"resolveJsonModule": true,
|
|
||||||
},
|
|
||||||
"exclude": [
|
|
||||||
"./dist/*",
|
|
||||||
]
|
|
||||||
}
|
|
||||||
@@ -8,7 +8,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<groupId>com.lancedb</groupId>
|
<groupId>com.lancedb</groupId>
|
||||||
<artifactId>lancedb-parent</artifactId>
|
<artifactId>lancedb-parent</artifactId>
|
||||||
<version>0.38.0-beta.2</version>
|
<version>0.39.0-beta.1</version>
|
||||||
<relativePath>../pom.xml</relativePath>
|
<relativePath>../pom.xml</relativePath>
|
||||||
</parent>
|
</parent>
|
||||||
|
|
||||||
|
|||||||
+2
-2
@@ -6,7 +6,7 @@
|
|||||||
|
|
||||||
<groupId>com.lancedb</groupId>
|
<groupId>com.lancedb</groupId>
|
||||||
<artifactId>lancedb-parent</artifactId>
|
<artifactId>lancedb-parent</artifactId>
|
||||||
<version>0.38.0-beta.2</version>
|
<version>0.39.0-beta.1</version>
|
||||||
<packaging>pom</packaging>
|
<packaging>pom</packaging>
|
||||||
<name>${project.artifactId}</name>
|
<name>${project.artifactId}</name>
|
||||||
<description>LanceDB Java SDK Parent POM</description>
|
<description>LanceDB Java SDK Parent POM</description>
|
||||||
@@ -28,7 +28,7 @@
|
|||||||
<properties>
|
<properties>
|
||||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||||
<arrow.version>15.0.0</arrow.version>
|
<arrow.version>15.0.0</arrow.version>
|
||||||
<lance-core.version>11.0.0-beta.15</lance-core.version>
|
<lance-core.version>12.0.0-beta.11</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>
|
||||||
|
|||||||
+5
-5
@@ -1,7 +1,7 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "lancedb-nodejs"
|
name = "lancedb-nodejs"
|
||||||
edition.workspace = true
|
edition.workspace = true
|
||||||
version = "0.38.0-beta.2"
|
version = "0.39.0-beta.1"
|
||||||
publish = false
|
publish = false
|
||||||
license.workspace = true
|
license.workspace = true
|
||||||
description.workspace = true
|
description.workspace = true
|
||||||
@@ -16,12 +16,12 @@ crate-type = ["cdylib"]
|
|||||||
async-trait.workspace = true
|
async-trait.workspace = true
|
||||||
arrow-ipc.workspace = true
|
arrow-ipc.workspace = true
|
||||||
arrow-array.workspace = true
|
arrow-array.workspace = true
|
||||||
arrow-buffer = "58.0.0"
|
arrow-buffer.workspace = true
|
||||||
half.workspace = true
|
half.workspace = true
|
||||||
arrow-schema.workspace = true
|
arrow-schema.workspace = true
|
||||||
env_logger.workspace = true
|
env_logger.workspace = true
|
||||||
futures.workspace = true
|
futures.workspace = true
|
||||||
lancedb = { path = "../rust/lancedb", default-features = false }
|
lancedb.workspace = true
|
||||||
lance-namespace.workspace = true
|
lance-namespace.workspace = true
|
||||||
napi = { version = "3.8.3", default-features = false, features = [
|
napi = { version = "3.8.3", default-features = false, features = [
|
||||||
"napi9",
|
"napi9",
|
||||||
@@ -29,8 +29,8 @@ napi = { version = "3.8.3", default-features = false, features = [
|
|||||||
"chrono_date",
|
"chrono_date",
|
||||||
"serde-json",
|
"serde-json",
|
||||||
] }
|
] }
|
||||||
chrono = { version = "0.4", default-features = false, features = ["clock"] }
|
chrono.workspace = true
|
||||||
serde_json = "1"
|
serde_json.workspace = true
|
||||||
napi-derive = "3.5.2"
|
napi-derive = "3.5.2"
|
||||||
# Prevent dynamic linking of lzma, which comes from datafusion
|
# Prevent dynamic linking of lzma, which comes from datafusion
|
||||||
lzma-sys = { version = "0.1", features = ["static"] }
|
lzma-sys = { version = "0.1", features = ["static"] }
|
||||||
|
|||||||
@@ -1,11 +1,16 @@
|
|||||||
// SPDX-License-Identifier: Apache-2.0
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
// SPDX-FileCopyrightText: Copyright The LanceDB Authors
|
// SPDX-FileCopyrightText: Copyright The LanceDB Authors
|
||||||
|
import * as fs from "node:fs";
|
||||||
|
import * as vm from "node:vm";
|
||||||
import * as arrow15 from "apache-arrow-15";
|
import * as arrow15 from "apache-arrow-15";
|
||||||
import * as arrow16 from "apache-arrow-16";
|
import * as arrow16 from "apache-arrow-16";
|
||||||
import * as arrow17 from "apache-arrow-17";
|
import * as arrow17 from "apache-arrow-17";
|
||||||
import * as arrow18 from "apache-arrow-18";
|
import * as arrow18 from "apache-arrow-18";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
|
Field as CurrentField,
|
||||||
|
LargeBinary as CurrentLargeBinary,
|
||||||
|
Schema as CurrentSchema,
|
||||||
Vector as CurrentVector,
|
Vector as CurrentVector,
|
||||||
convertToTable,
|
convertToTable,
|
||||||
tableFromIPC as currentTableFromIPC,
|
tableFromIPC as currentTableFromIPC,
|
||||||
@@ -36,6 +41,59 @@ function sampleRecords(): Array<Record<string, any>> {
|
|||||||
},
|
},
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
it("serializes an Arrow Table created in another JavaScript realm", async () => {
|
||||||
|
const context = vm.createContext({
|
||||||
|
TextDecoder,
|
||||||
|
TextEncoder,
|
||||||
|
console,
|
||||||
|
setTimeout,
|
||||||
|
clearTimeout,
|
||||||
|
});
|
||||||
|
vm.runInContext(
|
||||||
|
fs.readFileSync(
|
||||||
|
require.resolve("apache-arrow-15/Arrow.es2015.min"),
|
||||||
|
"utf8",
|
||||||
|
),
|
||||||
|
context,
|
||||||
|
);
|
||||||
|
const foreignTable: unknown = vm.runInContext(
|
||||||
|
"Arrow.tableFromArrays({ id: new Int32Array([1, 2, 3]), text: ['foo', 'bar', 'baz'] })",
|
||||||
|
context,
|
||||||
|
);
|
||||||
|
|
||||||
|
const foreignMetadata = (
|
||||||
|
foreignTable as { schema: { metadata: Map<string, string> } }
|
||||||
|
).schema.metadata;
|
||||||
|
expect(foreignMetadata).not.toBeInstanceOf(Map);
|
||||||
|
|
||||||
|
const buf = await fromDataToBuffer(
|
||||||
|
foreignTable as Parameters<typeof fromDataToBuffer>[0],
|
||||||
|
);
|
||||||
|
const actual = currentTableFromIPC(buf);
|
||||||
|
|
||||||
|
expect(actual.numRows).toBe(3);
|
||||||
|
expect(actual.getChild("id")?.toJSON()).toEqual([1, 2, 3]);
|
||||||
|
expect(actual.getChild("text")?.toJSON()).toEqual(["foo", "bar", "baz"]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("preserves field metadata from a provided schema", async function () {
|
||||||
|
const jsonMetadata = new Map([["ARROW:extension:name", "lance.json"]]);
|
||||||
|
const schema = new CurrentSchema([
|
||||||
|
new CurrentField("meta", new CurrentLargeBinary(), true, jsonMetadata),
|
||||||
|
]);
|
||||||
|
|
||||||
|
const table = makeArrowTable(
|
||||||
|
[{ meta: Buffer.from(JSON.stringify({ source: "test" })) }],
|
||||||
|
{ schema },
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(table.schema.fields[0].metadata).toEqual(jsonMetadata);
|
||||||
|
|
||||||
|
const roundTripped = currentTableFromIPC(await fromTableToBuffer(table));
|
||||||
|
expect(roundTripped.schema.fields[0].metadata).toEqual(jsonMetadata);
|
||||||
|
});
|
||||||
|
|
||||||
describe.each([arrow15, arrow16, arrow17, arrow18])(
|
describe.each([arrow15, arrow16, arrow17, arrow18])(
|
||||||
"Arrow",
|
"Arrow",
|
||||||
(
|
(
|
||||||
@@ -173,6 +231,36 @@ describe.each([arrow15, arrow16, arrow17, arrow18])(
|
|||||||
}
|
}
|
||||||
|
|
||||||
describe("The function makeArrowTable", function () {
|
describe("The function makeArrowTable", function () {
|
||||||
|
it("accepts snake_case embedding metadata like camelCase", function () {
|
||||||
|
const spellings = [
|
||||||
|
// biome-ignore lint/style/useNamingConvention: the Python wire spelling
|
||||||
|
{ source_column: "text", vector_column: "vector" },
|
||||||
|
{ sourceColumn: "text", vectorColumn: "vector" },
|
||||||
|
];
|
||||||
|
for (const columns of spellings) {
|
||||||
|
const schema = new Schema(
|
||||||
|
[
|
||||||
|
new Field("text", new Utf8(), false),
|
||||||
|
new Field(
|
||||||
|
"vector",
|
||||||
|
new FixedSizeList(3, new Field("item", new Float32(), true)),
|
||||||
|
false,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
new Map([
|
||||||
|
[
|
||||||
|
"embedding_functions",
|
||||||
|
JSON.stringify([{ name: "mock", model: {}, ...columns }]),
|
||||||
|
],
|
||||||
|
]),
|
||||||
|
);
|
||||||
|
// The vector field is non-nullable and absent from the data; only a
|
||||||
|
// recognized embedding config makes that acceptable.
|
||||||
|
const table = makeArrowTable([{ text: "hello" }], { schema });
|
||||||
|
expect(table.numRows).toBe(1);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
it("will use data types from a provided schema instead of inference", async function () {
|
it("will use data types from a provided schema instead of inference", async function () {
|
||||||
const schema = new Schema([
|
const schema = new Schema([
|
||||||
new Field("a", new Int32(), false),
|
new Field("a", new Int32(), false),
|
||||||
@@ -485,6 +573,137 @@ describe.each([arrow15, arrow16, arrow17, arrow18])(
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("will allow matching inferred types across records", function () {
|
||||||
|
expect(() =>
|
||||||
|
makeArrowTable([{ value: 1 }, { value: 2 }]),
|
||||||
|
).not.toThrow();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("will reject mismatched inferred types across records", function () {
|
||||||
|
expect(() => makeArrowTable([{ value: 1 }, { value: "two" }])).toThrow(
|
||||||
|
"Failed to infer schema for data. Previously inferred type Float64 but found Utf8 for field value at row 1. Consider providing an explicit schema.",
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("will ignore generated dictionary IDs when comparing inferred types", function () {
|
||||||
|
const table = makeArrowTable([{ str: "a" }, { str: "b" }], {
|
||||||
|
dictionaryEncodeStrings: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(table.getChild("str")?.toJSON()).toEqual(["a", "b"]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("will preserve null values without treating them as type mismatches", function () {
|
||||||
|
for (const records of [
|
||||||
|
[{ vector: [1, 2, 3] }, { vector: null }],
|
||||||
|
[{ vector: null }, { vector: [1, 2, 3] }],
|
||||||
|
]) {
|
||||||
|
const table = makeArrowTable(records);
|
||||||
|
|
||||||
|
expect(table.numRows).toBe(2);
|
||||||
|
expect(table.getChild("vector")?.nullCount).toBe(1);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
it("will preserve empty variable-size lists", function () {
|
||||||
|
for (const records of [
|
||||||
|
[{ items: [1] }, { items: [] }],
|
||||||
|
[{ items: [] }, { items: [1] }],
|
||||||
|
]) {
|
||||||
|
const table = makeArrowTable(records);
|
||||||
|
expect(
|
||||||
|
table
|
||||||
|
.getChild("items")
|
||||||
|
?.toJSON()
|
||||||
|
.map((value) => value.toJSON()),
|
||||||
|
).toEqual(records.map((record) => record.items));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
it("will propagate deferred evidence through nested lists", function () {
|
||||||
|
for (const records of [
|
||||||
|
[{ items: [1] }, { items: [null] }],
|
||||||
|
[{ items: [null] }, { items: [1] }],
|
||||||
|
[{ items: [null, 1] }, { items: [2, null] }],
|
||||||
|
]) {
|
||||||
|
const table = makeArrowTable(records);
|
||||||
|
expect(
|
||||||
|
table
|
||||||
|
.getChild("items")
|
||||||
|
?.toJSON()
|
||||||
|
.map((value) => value.toJSON()),
|
||||||
|
).toEqual(records.map((record) => record.items));
|
||||||
|
}
|
||||||
|
|
||||||
|
const nestedRecords = [{ items: [[1]] }, { items: [[null]] }];
|
||||||
|
const nestedTable = makeArrowTable(nestedRecords);
|
||||||
|
expect(
|
||||||
|
nestedTable
|
||||||
|
.getChild("items")
|
||||||
|
?.toJSON()
|
||||||
|
.map((value) =>
|
||||||
|
value
|
||||||
|
.toJSON()
|
||||||
|
.map((nestedValue: { toJSON: () => unknown[] }) =>
|
||||||
|
nestedValue.toJSON(),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
).toEqual(nestedRecords.map((record) => record.items));
|
||||||
|
});
|
||||||
|
|
||||||
|
it("will reject incompatible deferred evidence within a list", function () {
|
||||||
|
for (const items of [
|
||||||
|
[[], 1],
|
||||||
|
[1, []],
|
||||||
|
[[null], 1],
|
||||||
|
[1, [null]],
|
||||||
|
]) {
|
||||||
|
expect(() => makeArrowTable([{ items }])).toThrow(
|
||||||
|
"Failed to infer data type for field items at row 0.",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
it("will reject empty fixed-size lists", function () {
|
||||||
|
expect(() =>
|
||||||
|
makeArrowTable([{ vector: [1, 2, 3] }, { vector: [] }]),
|
||||||
|
).toThrow(
|
||||||
|
"Failed to infer schema for data. Previously inferred type FixedSizeList[3]<Float32> but found List[0] for field vector at row 1.",
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("will reject inferred leaf and branch shape changes", function () {
|
||||||
|
expect(() =>
|
||||||
|
makeArrowTable([{ value: 1 }, { value: { nested: 2 } }]),
|
||||||
|
).toThrow(
|
||||||
|
"Failed to infer schema for data. Previously inferred type Float64 but found Struct for field value at row 1.",
|
||||||
|
);
|
||||||
|
expect(() =>
|
||||||
|
makeArrowTable([{ value: { nested: 1 } }, { value: 2 }]),
|
||||||
|
).toThrow(
|
||||||
|
"Failed to infer schema for data. Previously inferred type Struct but found Float64 for field value at row 1.",
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("will allow null values around inferred struct values", function () {
|
||||||
|
for (const { records, nullIndex } of [
|
||||||
|
{
|
||||||
|
records: [{ value: null }, { value: { nested: 2 } }],
|
||||||
|
nullIndex: 0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
records: [{ value: { nested: 1 } }, { value: null }],
|
||||||
|
nullIndex: 1,
|
||||||
|
},
|
||||||
|
]) {
|
||||||
|
const table = makeArrowTable(records);
|
||||||
|
const values = table.getChild("value");
|
||||||
|
|
||||||
|
expect(values?.nullCount).toBe(1);
|
||||||
|
expect(values?.get(nullIndex)).toBeNull();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
it("will allow a schema to be provided", async function () {
|
it("will allow a schema to be provided", async function () {
|
||||||
await checkTableCreation(
|
await checkTableCreation(
|
||||||
async (records, _, schema) =>
|
async (records, _, schema) =>
|
||||||
|
|||||||
@@ -4,7 +4,13 @@
|
|||||||
import { readdirSync } from "fs";
|
import { readdirSync } from "fs";
|
||||||
import { Field, Float64, Schema } from "apache-arrow";
|
import { Field, Float64, Schema } from "apache-arrow";
|
||||||
import * as tmp from "tmp";
|
import * as tmp from "tmp";
|
||||||
import { Connection, Table, connect, connectNamespace } from "../lancedb";
|
import {
|
||||||
|
Connection,
|
||||||
|
ListTablesResponse,
|
||||||
|
Table,
|
||||||
|
connect,
|
||||||
|
connectNamespace,
|
||||||
|
} from "../lancedb";
|
||||||
import { LocalTable } from "../lancedb/table";
|
import { LocalTable } from "../lancedb/table";
|
||||||
|
|
||||||
describe("when connecting", () => {
|
describe("when connecting", () => {
|
||||||
@@ -47,6 +53,7 @@ describe("given a connection", () => {
|
|||||||
await db.close();
|
await db.close();
|
||||||
expect(db.isOpen()).toBe(false);
|
expect(db.isOpen()).toBe(false);
|
||||||
await expect(db.tableNames()).rejects.toThrow("Connection is closed");
|
await expect(db.tableNames()).rejects.toThrow("Connection is closed");
|
||||||
|
await expect(db.listTables()).rejects.toThrow("Connection is closed");
|
||||||
await expect(db.renameTable("a", "b")).rejects.toThrow(
|
await expect(db.renameTable("a", "b")).rejects.toThrow(
|
||||||
"Connection is closed",
|
"Connection is closed",
|
||||||
);
|
);
|
||||||
@@ -129,6 +136,66 @@ describe("given a connection", () => {
|
|||||||
expect(tables).toEqual(["b", "c"]);
|
expect(tables).toEqual(["b", "c"]);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("should respect limit and page token when listing tables", async () => {
|
||||||
|
const db = await connect(tmpDir.name);
|
||||||
|
|
||||||
|
await db.createTable("b", [{ id: 1 }]);
|
||||||
|
await db.createTable("a", [{ id: 1 }]);
|
||||||
|
await db.createTable("c", [{ id: 1 }]);
|
||||||
|
|
||||||
|
const all = await db.listTables();
|
||||||
|
expect(all.tables).toEqual(["a", "b", "c"]);
|
||||||
|
expect(all.pageToken).toBeUndefined();
|
||||||
|
|
||||||
|
const first = await db.listTables({ limit: 1 });
|
||||||
|
expect(first.tables).toEqual(["a"]);
|
||||||
|
expect(first.pageToken).toBeDefined();
|
||||||
|
|
||||||
|
const second = await db.listTables({
|
||||||
|
limit: 1,
|
||||||
|
pageToken: first.pageToken,
|
||||||
|
});
|
||||||
|
expect(second.tables).toEqual(["b"]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should visit every table exactly once when walking pages", async () => {
|
||||||
|
const db = await connect(tmpDir.name);
|
||||||
|
|
||||||
|
const created = ["a", "b", "c", "d", "e"];
|
||||||
|
for (const name of created) {
|
||||||
|
await db.createTable(name, [{ id: 1 }]);
|
||||||
|
}
|
||||||
|
|
||||||
|
const seen: string[] = [];
|
||||||
|
let pageToken: string | undefined = undefined;
|
||||||
|
do {
|
||||||
|
const page: ListTablesResponse = await db.listTables({
|
||||||
|
limit: 2,
|
||||||
|
pageToken,
|
||||||
|
});
|
||||||
|
seen.push(...page.tables);
|
||||||
|
pageToken = page.pageToken;
|
||||||
|
} while (pageToken);
|
||||||
|
|
||||||
|
expect(seen).toEqual(created);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should list tables in a namespace", async () => {
|
||||||
|
const db = await connect(tmpDir.name, {
|
||||||
|
// biome-ignore lint/style/useNamingConvention: opaque backend property key, must match Rust
|
||||||
|
namespaceClientProperties: { manifest_enabled: "true" },
|
||||||
|
});
|
||||||
|
await db.createNamespace(["child"]);
|
||||||
|
await db.createTable("nested", [{ id: 1 }], ["child"]);
|
||||||
|
|
||||||
|
await expect(db.listTables(["child"])).resolves.toEqual(
|
||||||
|
expect.objectContaining({ tables: ["nested"] }),
|
||||||
|
);
|
||||||
|
await expect(db.listTables()).resolves.toEqual(
|
||||||
|
expect.objectContaining({ tables: [] }),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
it("should create tables in v2 mode", async () => {
|
it("should create tables in v2 mode", async () => {
|
||||||
const db = await connect(tmpDir.name);
|
const db = await connect(tmpDir.name);
|
||||||
const data = [...Array(10000).keys()].map((i) => ({ id: i }));
|
const data = [...Array(10000).keys()].map((i) => ({ id: i }));
|
||||||
|
|||||||
@@ -187,6 +187,58 @@ describe("embedding functions", () => {
|
|||||||
const vector0 = JSON.parse(JSON.stringify(arr[0].vector));
|
const vector0 = JSON.parse(JSON.stringify(arr[0].vector));
|
||||||
expect(vector0).toEqual([1, 2, 3]);
|
expect(vector0).toEqual([1, 2, 3]);
|
||||||
});
|
});
|
||||||
|
it("should append multiple Python embeddings with the same alias", async () => {
|
||||||
|
@register("python-mock")
|
||||||
|
// biome-ignore lint/correctness/noUnusedVariables: the decorator registers this class
|
||||||
|
class MockEmbeddingFunction extends EmbeddingFunction<string> {
|
||||||
|
ndims() {
|
||||||
|
return 3;
|
||||||
|
}
|
||||||
|
embeddingDataType(): Float {
|
||||||
|
return new Float32();
|
||||||
|
}
|
||||||
|
async computeQueryEmbeddings(_data: string) {
|
||||||
|
return [1, 2, 3];
|
||||||
|
}
|
||||||
|
async computeSourceEmbeddings(data: string[]) {
|
||||||
|
return data.map((value) =>
|
||||||
|
value === "hello world" ? [1, 2, 3] : [4, 5, 6],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const metadata = new Map([
|
||||||
|
[
|
||||||
|
"embedding_functions",
|
||||||
|
'[{"source_column":"text1","vector_column":"vector1","name":"python-mock","model":{}},{"source_column":"text2","vector_column":"vector2","name":"python-mock","model":{}}]',
|
||||||
|
],
|
||||||
|
]);
|
||||||
|
const schema = new Schema(
|
||||||
|
[
|
||||||
|
new Field("text1", new Utf8(), true),
|
||||||
|
new Field("text2", new Utf8(), true),
|
||||||
|
new Field(
|
||||||
|
"vector1",
|
||||||
|
new FixedSizeList(3, new Field("item", new Float32(), true)),
|
||||||
|
true,
|
||||||
|
),
|
||||||
|
new Field(
|
||||||
|
"vector2",
|
||||||
|
new FixedSizeList(3, new Field("item", new Float32(), true)),
|
||||||
|
true,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
metadata,
|
||||||
|
);
|
||||||
|
|
||||||
|
const db = await connect(tmpDir.name);
|
||||||
|
const table = await db.createEmptyTable("test", schema);
|
||||||
|
await table.add([{ text1: "hello world", text2: "goodbye world" }]);
|
||||||
|
|
||||||
|
const rows = await table.query().toArray();
|
||||||
|
expect(JSON.parse(JSON.stringify(rows[0].vector1))).toEqual([1, 2, 3]);
|
||||||
|
expect(JSON.parse(JSON.stringify(rows[0].vector2))).toEqual([4, 5, 6]);
|
||||||
|
});
|
||||||
|
|
||||||
it("should append generated vectors to a non-nullable schema", async () => {
|
it("should append generated vectors to a non-nullable schema", async () => {
|
||||||
@register("non_nullable_schema_test")
|
@register("non_nullable_schema_test")
|
||||||
@@ -487,4 +539,52 @@ describe("embedding functions", () => {
|
|||||||
expect(stringSchema3).toEqual(stringExpectedSchema);
|
expect(stringSchema3).toEqual(stringExpectedSchema);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
test("parses one function writing several vector columns", async () => {
|
||||||
|
class MockEmbeddingFunction extends EmbeddingFunction<string> {
|
||||||
|
ndims() {
|
||||||
|
return 3;
|
||||||
|
}
|
||||||
|
embeddingDataType(): Float {
|
||||||
|
return new Float32();
|
||||||
|
}
|
||||||
|
async computeQueryEmbeddings(_data: string) {
|
||||||
|
return [1, 2, 3];
|
||||||
|
}
|
||||||
|
async computeSourceEmbeddings(data: string[]) {
|
||||||
|
return Array.from({ length: data.length }).fill([
|
||||||
|
1, 2, 3,
|
||||||
|
]) as number[][];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const registry = getRegistry();
|
||||||
|
registry.register("multi_output_mock")(MockEmbeddingFunction);
|
||||||
|
|
||||||
|
// A materialized view can project one source vector column under two
|
||||||
|
// names, so a table's configuration names the same function twice.
|
||||||
|
const parsed = await registry.parseFunctions(
|
||||||
|
new Map([
|
||||||
|
[
|
||||||
|
"embedding_functions",
|
||||||
|
JSON.stringify([
|
||||||
|
{
|
||||||
|
name: "multi_output_mock",
|
||||||
|
sourceColumn: "text",
|
||||||
|
vectorColumn: "vector_a",
|
||||||
|
model: {},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "multi_output_mock",
|
||||||
|
sourceColumn: "text",
|
||||||
|
vectorColumn: "vector_b",
|
||||||
|
model: {},
|
||||||
|
},
|
||||||
|
]),
|
||||||
|
],
|
||||||
|
]),
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(
|
||||||
|
[...parsed.values()].map(({ vectorColumn }) => vectorColumn).sort(),
|
||||||
|
).toEqual(["vector_a", "vector_b"]);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -0,0 +1,95 @@
|
|||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
// SPDX-FileCopyrightText: Copyright The LanceDB Authors
|
||||||
|
|
||||||
|
import { execFileSync } from "node:child_process";
|
||||||
|
import { resolve } from "node:path";
|
||||||
|
|
||||||
|
import type { OpenAIEmbeddingFunction } from "../lancedb/embedding/openai";
|
||||||
|
import type { EmbeddingFunctionRegistry } from "../lancedb/embedding/registry";
|
||||||
|
|
||||||
|
type EmbeddingModule = typeof import("../lancedb/embedding");
|
||||||
|
type OpenAIModule = typeof import("../lancedb/embedding/openai");
|
||||||
|
type RegistryModule = typeof import("../lancedb/embedding/registry");
|
||||||
|
|
||||||
|
describe("embedding function registry", () => {
|
||||||
|
const registries: EmbeddingFunctionRegistry[] = [];
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
for (const registry of registries) {
|
||||||
|
registry.reset();
|
||||||
|
}
|
||||||
|
registries.length = 0;
|
||||||
|
});
|
||||||
|
|
||||||
|
it("defers built-in providers until the public registry API is used", () => {
|
||||||
|
jest.isolateModules(() => {
|
||||||
|
const embedding = require("../lancedb/embedding") as EmbeddingModule;
|
||||||
|
const { getRegistry: getInternalRegistry } =
|
||||||
|
require("../lancedb/embedding/registry") as RegistryModule;
|
||||||
|
const registry = getInternalRegistry();
|
||||||
|
registries.push(registry);
|
||||||
|
|
||||||
|
expect(registry.length()).toBe(0);
|
||||||
|
expect(embedding.getRegistry()).toBe(registry);
|
||||||
|
expect(registry.get("openai")).toBeDefined();
|
||||||
|
expect(registry.get("huggingface")).toBeDefined();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("preserves automatic FTS search in a fresh process", () => {
|
||||||
|
execFileSync(
|
||||||
|
process.execPath,
|
||||||
|
[resolve(__dirname, "fixtures", "auto_fts_search.cjs")],
|
||||||
|
{ stdio: "pipe" },
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("shares registrations across duplicated provider module graphs", () => {
|
||||||
|
let registeringRegistry: EmbeddingFunctionRegistry | undefined;
|
||||||
|
let latestOpenAIConstructor: typeof OpenAIEmbeddingFunction | undefined;
|
||||||
|
|
||||||
|
jest.isolateModules(() => {
|
||||||
|
require("../lancedb/embedding/openai");
|
||||||
|
const { getRegistry } =
|
||||||
|
require("../lancedb/embedding/registry") as RegistryModule;
|
||||||
|
registeringRegistry = getRegistry();
|
||||||
|
registries.push(registeringRegistry);
|
||||||
|
expect(registeringRegistry.get("openai")).toBeDefined();
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(() => {
|
||||||
|
jest.isolateModules(() => {
|
||||||
|
const { OpenAIEmbeddingFunction } =
|
||||||
|
require("../lancedb/embedding/openai") as OpenAIModule;
|
||||||
|
latestOpenAIConstructor = OpenAIEmbeddingFunction;
|
||||||
|
const { getRegistry } =
|
||||||
|
require("../lancedb/embedding/registry") as RegistryModule;
|
||||||
|
registries.push(getRegistry());
|
||||||
|
});
|
||||||
|
}).not.toThrow();
|
||||||
|
|
||||||
|
const previousApiKey = process.env.OPENAI_API_KEY;
|
||||||
|
process.env.OPENAI_API_KEY = "test";
|
||||||
|
try {
|
||||||
|
const latestOpenAI = registeringRegistry!
|
||||||
|
.get<OpenAIEmbeddingFunction>("openai")!
|
||||||
|
.create();
|
||||||
|
expect(latestOpenAI).toBeInstanceOf(latestOpenAIConstructor!);
|
||||||
|
} finally {
|
||||||
|
if (previousApiKey === undefined) {
|
||||||
|
delete process.env.OPENAI_API_KEY;
|
||||||
|
} else {
|
||||||
|
process.env.OPENAI_API_KEY = previousApiKey;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
jest.isolateModules(() => {
|
||||||
|
const { getRegistry } =
|
||||||
|
require("../lancedb/embedding") as EmbeddingModule;
|
||||||
|
const publicRegistry = getRegistry();
|
||||||
|
registries.push(publicRegistry);
|
||||||
|
expect(publicRegistry).toBe(registeringRegistry);
|
||||||
|
expect(publicRegistry.get("openai")).toBeDefined();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
// SPDX-FileCopyrightText: Copyright The LanceDB Authors
|
||||||
|
|
||||||
|
const assert = require("node:assert/strict");
|
||||||
|
const tmp = require("tmp");
|
||||||
|
const { connect, embedding, Index } = require("../../dist");
|
||||||
|
const { getRegistry } = require("../../dist/embedding/registry");
|
||||||
|
|
||||||
|
async function main() {
|
||||||
|
assert.equal(typeof embedding.getRegistry, "function");
|
||||||
|
assert.equal(getRegistry().length(), 0);
|
||||||
|
assert.equal(embedding.getRegistry(), getRegistry());
|
||||||
|
assert.equal(getRegistry().length(), 2);
|
||||||
|
|
||||||
|
const dir = tmp.dirSync({ unsafeCleanup: true });
|
||||||
|
let db;
|
||||||
|
try {
|
||||||
|
db = await connect(dir.name);
|
||||||
|
const table = await db.createTable("docs", [{ text: "hello world" }]);
|
||||||
|
await table.createIndex("text", { config: Index.fts() });
|
||||||
|
|
||||||
|
const rows = await table.search("hello").toArray();
|
||||||
|
assert.equal(rows[0].text, "hello world");
|
||||||
|
} finally {
|
||||||
|
db?.close();
|
||||||
|
dir.removeCallback();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
main().catch((error) => {
|
||||||
|
console.error(error);
|
||||||
|
process.exitCode = 1;
|
||||||
|
});
|
||||||
@@ -0,0 +1,169 @@
|
|||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
// SPDX-FileCopyrightText: Copyright The LanceDB Authors
|
||||||
|
|
||||||
|
import * as tmp from "tmp";
|
||||||
|
|
||||||
|
import { Connection, connect } from "../lancedb";
|
||||||
|
import {
|
||||||
|
DEFINITION_META_KEY,
|
||||||
|
definitionFromMetadata,
|
||||||
|
} from "../lancedb/materialized_view";
|
||||||
|
|
||||||
|
describe("materialized views", () => {
|
||||||
|
let tmpDir: tmp.DirResult;
|
||||||
|
let db: Connection;
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
tmpDir = tmp.dirSync({ unsafeCleanup: true });
|
||||||
|
db = await connect(tmpDir.name);
|
||||||
|
await db.createTable(
|
||||||
|
"people",
|
||||||
|
[
|
||||||
|
{ name: "ada", age: 36 },
|
||||||
|
{ name: "kid", age: 7 },
|
||||||
|
{ name: "grace", age: 85 },
|
||||||
|
],
|
||||||
|
{ storageOptions: { newTableEnableStableRowIds: "true" } },
|
||||||
|
);
|
||||||
|
});
|
||||||
|
afterEach(() => tmpDir.removeCallback());
|
||||||
|
|
||||||
|
it("rejects a stored limit a number cannot carry", () => {
|
||||||
|
const big = new Map([
|
||||||
|
[
|
||||||
|
DEFINITION_META_KEY,
|
||||||
|
'{"kind":"select","source_table":"people","limit":9007199254740993}',
|
||||||
|
],
|
||||||
|
]);
|
||||||
|
expect(() => definitionFromMetadata(big, "v")).toThrow(
|
||||||
|
/too large to represent exactly/,
|
||||||
|
);
|
||||||
|
|
||||||
|
const safe = new Map([
|
||||||
|
[
|
||||||
|
DEFINITION_META_KEY,
|
||||||
|
'{"kind":"select","source_table":"people","limit":42}',
|
||||||
|
],
|
||||||
|
]);
|
||||||
|
expect(definitionFromMetadata(safe, "v").limit).toBe(42);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("reads the namespaced select kind and refuses unknown kinds", () => {
|
||||||
|
// "namespaced_select" is the namespaced form of "select": same shape, a
|
||||||
|
// separate kind so readers that predate it refuse instead of resolving
|
||||||
|
// the source at the root.
|
||||||
|
const namespaced = new Map([
|
||||||
|
[
|
||||||
|
DEFINITION_META_KEY,
|
||||||
|
'{"kind":"namespaced_select","source_table":"people","source_namespace":["ns"]}',
|
||||||
|
],
|
||||||
|
]);
|
||||||
|
const definition = definitionFromMetadata(namespaced, "v");
|
||||||
|
expect(definition.sourceTable).toBe("people");
|
||||||
|
expect(definition.sourceNamespace).toEqual(["ns"]);
|
||||||
|
|
||||||
|
const unknown = new Map([
|
||||||
|
[DEFINITION_META_KEY, '{"kind":"select_v3","source_table":"people"}'],
|
||||||
|
]);
|
||||||
|
expect(() => definitionFromMetadata(unknown, "v")).toThrow(
|
||||||
|
/cannot refresh/,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("creates, refreshes and queries a view", async () => {
|
||||||
|
const view = await db.createMaterializedView("adults", "people", {
|
||||||
|
select: ["name", ["shout", "upper(name)"]],
|
||||||
|
where: "age >= 18",
|
||||||
|
});
|
||||||
|
expect(view.name).toBe("adults");
|
||||||
|
expect(await view.table().countRows()).toBe(0);
|
||||||
|
|
||||||
|
const result = await view.refresh();
|
||||||
|
expect(result.mode).toBe("rebuild");
|
||||||
|
expect(Number(result.rowsWritten)).toBe(2);
|
||||||
|
|
||||||
|
const rows = await view.table().query().toArray();
|
||||||
|
expect(rows.map((r) => r.shout).sort()).toEqual(["ADA", "GRACE"]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("round-trips the definition", async () => {
|
||||||
|
await db.createMaterializedView("adults", "people", {
|
||||||
|
where: "age >= 18",
|
||||||
|
});
|
||||||
|
const view = await db.openMaterializedView("adults");
|
||||||
|
const definition = await view.definition();
|
||||||
|
expect(definition.sourceTable).toBe("people");
|
||||||
|
expect(definition.filter).toBe("age >= 18");
|
||||||
|
expect(definition.projections).toEqual([
|
||||||
|
["name", "`name`"],
|
||||||
|
["age", "`age`"],
|
||||||
|
]);
|
||||||
|
expect(definition.inputs).toEqual(["age", "name"]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("refreshes incrementally after an append", async () => {
|
||||||
|
const view = await db.createMaterializedView("copy", "people");
|
||||||
|
await view.refresh();
|
||||||
|
|
||||||
|
const people = await db.openTable("people");
|
||||||
|
await people.add([{ name: "alan", age: 41 }]);
|
||||||
|
const result = await view.refresh();
|
||||||
|
expect(result.mode).toBe("incremental");
|
||||||
|
expect(Number(result.rowsWritten)).toBe(1);
|
||||||
|
expect(await view.table().countRows()).toBe(4);
|
||||||
|
|
||||||
|
expect((await view.refresh()).mode).toBe("no_op");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("lists views and rejects non-views", async () => {
|
||||||
|
await db.createMaterializedView("adults", "people", {
|
||||||
|
where: "age >= 18",
|
||||||
|
});
|
||||||
|
expect(await db.listMaterializedViews()).toEqual(["adults"]);
|
||||||
|
await expect(db.openMaterializedView("people")).rejects.toThrow(
|
||||||
|
"not a materialized view",
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("rejects an invalid expression at create time", async () => {
|
||||||
|
await expect(
|
||||||
|
db.createMaterializedView("bad", "people", {
|
||||||
|
select: [["x", "missing + 1"]],
|
||||||
|
}),
|
||||||
|
).rejects.toThrow("missing");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("rejects invalid numeric options before creating anything", async () => {
|
||||||
|
for (const limit of [-5, 1.5, Infinity, NaN]) {
|
||||||
|
await expect(
|
||||||
|
db.createMaterializedView("bad", "people", { limit }),
|
||||||
|
).rejects.toThrow("non-negative integer");
|
||||||
|
}
|
||||||
|
expect(await db.listMaterializedViews()).toEqual([]);
|
||||||
|
|
||||||
|
const view = await db.createMaterializedView("copy", "people");
|
||||||
|
for (const sourceVersion of [-1, 1.5, Infinity, NaN]) {
|
||||||
|
await expect(view.refresh({ sourceVersion })).rejects.toThrow(
|
||||||
|
"non-negative integer",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
it("quotes bare select names", async () => {
|
||||||
|
await db.createTable("odd_names", [{ "order item": "widget" }], {
|
||||||
|
storageOptions: { newTableEnableStableRowIds: "true" },
|
||||||
|
});
|
||||||
|
const view = await db.createMaterializedView("quoted", "odd_names", {
|
||||||
|
select: ["order item"],
|
||||||
|
});
|
||||||
|
const result = await view.refresh();
|
||||||
|
expect(Number(result.rowsWritten)).toBe(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("requires stable row ids on the source", async () => {
|
||||||
|
await db.createTable("plain", [{ x: 1 }]);
|
||||||
|
await expect(db.createMaterializedView("v", "plain")).rejects.toThrow(
|
||||||
|
"stable row ids",
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -5,8 +5,8 @@ import packageJson = require("../package.json");
|
|||||||
|
|
||||||
describe("package metadata", () => {
|
describe("package metadata", () => {
|
||||||
it("requires Node.js type declarations compatible with the runtime", () => {
|
it("requires Node.js type declarations compatible with the runtime", () => {
|
||||||
expect(packageJson.engines.node).toBe(">= 18");
|
expect(packageJson.engines.node).toBe(">= 22");
|
||||||
expect(packageJson.peerDependencies["@types/node"]).toBe(">=18");
|
expect(packageJson.peerDependencies["@types/node"]).toBe(">=22");
|
||||||
expect(packageJson.peerDependenciesMeta["@types/node"]).toEqual({
|
expect(packageJson.peerDependenciesMeta["@types/node"]).toEqual({
|
||||||
optional: true,
|
optional: true,
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -106,6 +106,77 @@ describe.each([arrow15, arrow16, arrow17, arrow18])("Registry", (arrow) => {
|
|||||||
'Embedding function with alias "mock-embedding" already exists',
|
'Embedding function with alias "mock-embedding" already exists',
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
test("parseFunctions keeps entries sharing a function name", async () => {
|
||||||
|
class MockEmbeddingFunction extends EmbeddingFunction<string> {
|
||||||
|
ndims() {
|
||||||
|
return 3;
|
||||||
|
}
|
||||||
|
embeddingDataType() {
|
||||||
|
return new arrow.Float32() as apiArrow.Float;
|
||||||
|
}
|
||||||
|
async computeSourceEmbeddings(data: string[]) {
|
||||||
|
return data.map(() => [1, 2, 3]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
register("mock-embedding")(MockEmbeddingFunction);
|
||||||
|
const parsed = await getRegistry().parseFunctions(
|
||||||
|
new Map([
|
||||||
|
[
|
||||||
|
"embedding_functions",
|
||||||
|
JSON.stringify([
|
||||||
|
{
|
||||||
|
name: "mock-embedding",
|
||||||
|
sourceColumn: "text",
|
||||||
|
vectorColumn: "vector_a",
|
||||||
|
model: {},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "mock-embedding",
|
||||||
|
sourceColumn: "text",
|
||||||
|
vectorColumn: "vector_b",
|
||||||
|
model: {},
|
||||||
|
},
|
||||||
|
]),
|
||||||
|
],
|
||||||
|
]),
|
||||||
|
);
|
||||||
|
expect([...parsed.values()].map((f) => f.vectorColumn)).toEqual([
|
||||||
|
"vector_a",
|
||||||
|
"vector_b",
|
||||||
|
]);
|
||||||
|
|
||||||
|
// The Python bindings write snake_case keys.
|
||||||
|
const snake = await getRegistry().parseFunctions(
|
||||||
|
new Map([
|
||||||
|
[
|
||||||
|
"embedding_functions",
|
||||||
|
JSON.stringify([
|
||||||
|
{
|
||||||
|
name: "mock-embedding",
|
||||||
|
// biome-ignore lint/style/useNamingConvention: the Python wire spelling
|
||||||
|
source_column: "text",
|
||||||
|
// biome-ignore lint/style/useNamingConvention: the Python wire spelling
|
||||||
|
vector_column: "vector_a",
|
||||||
|
model: {},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "mock-embedding",
|
||||||
|
// biome-ignore lint/style/useNamingConvention: the Python wire spelling
|
||||||
|
source_column: "text",
|
||||||
|
// biome-ignore lint/style/useNamingConvention: the Python wire spelling
|
||||||
|
vector_column: "vector_b",
|
||||||
|
model: {},
|
||||||
|
},
|
||||||
|
]),
|
||||||
|
],
|
||||||
|
]),
|
||||||
|
);
|
||||||
|
expect([...snake.keys()]).toEqual(["vector_a", "vector_b"]);
|
||||||
|
expect([...snake.values()].map((f) => f.sourceColumn)).toEqual([
|
||||||
|
"text",
|
||||||
|
"text",
|
||||||
|
]);
|
||||||
|
});
|
||||||
test("schema should contain correct metadata", async () => {
|
test("schema should contain correct metadata", async () => {
|
||||||
class MockEmbeddingFunction extends EmbeddingFunction<string> {
|
class MockEmbeddingFunction extends EmbeddingFunction<string> {
|
||||||
constructor(args: FunctionOptions = {}) {
|
constructor(args: FunctionOptions = {}) {
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
|
|
||||||
import * as http from "http";
|
import * as http from "http";
|
||||||
import { RequestListener } from "http";
|
import { RequestListener } from "http";
|
||||||
|
import packageJson = require("../package.json");
|
||||||
import {
|
import {
|
||||||
ClientConfig,
|
ClientConfig,
|
||||||
Connection,
|
Connection,
|
||||||
@@ -70,11 +71,36 @@ async function withMockDatabase(
|
|||||||
try {
|
try {
|
||||||
await callback(db);
|
await callback(db);
|
||||||
} finally {
|
} finally {
|
||||||
server.close();
|
// `close()` alone leaves the port bound until keep-alive sockets drain, so
|
||||||
|
// a single failing test would cascade into EADDRINUSE for every test after
|
||||||
|
// it. Destroy the connections and wait for the port to actually be free.
|
||||||
|
await new Promise<void>((resolve) => {
|
||||||
|
server.closeAllConnections();
|
||||||
|
server.close(() => resolve());
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
describe("remote connection", () => {
|
describe("remote connection", () => {
|
||||||
|
it("refuses materialized views before issuing any request", async () => {
|
||||||
|
const paths: string[] = [];
|
||||||
|
await withMockDatabase(
|
||||||
|
(req, res) => {
|
||||||
|
paths.push(req.url ?? "");
|
||||||
|
res.writeHead(404).end();
|
||||||
|
},
|
||||||
|
async (db) => {
|
||||||
|
await expect(db.openMaterializedView("secret_table")).rejects.toThrow(
|
||||||
|
/only on local databases/,
|
||||||
|
);
|
||||||
|
await expect(db.listMaterializedViews()).rejects.toThrow(
|
||||||
|
/only on local databases/,
|
||||||
|
);
|
||||||
|
expect(paths).toEqual([]);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
it("should accept partial connection options", async () => {
|
it("should accept partial connection options", async () => {
|
||||||
await connect("db://test", {
|
await connect("db://test", {
|
||||||
apiKey: "fake",
|
apiKey: "fake",
|
||||||
@@ -112,7 +138,7 @@ describe("remote connection", () => {
|
|||||||
(req, res) => {
|
(req, res) => {
|
||||||
expect(req.headers["x-api-key"]).toEqual("fake");
|
expect(req.headers["x-api-key"]).toEqual("fake");
|
||||||
expect(req.headers["user-agent"]).toEqual(
|
expect(req.headers["user-agent"]).toEqual(
|
||||||
`LanceDB-Node-Client/${process.env.npm_package_version}`,
|
`LanceDB-Node-Client/${packageJson.version}`,
|
||||||
);
|
);
|
||||||
|
|
||||||
const body = JSON.stringify({ tables: [] });
|
const body = JSON.stringify({ tables: [] });
|
||||||
@@ -311,7 +337,7 @@ describe("remote connection", () => {
|
|||||||
expect(createIndexBody?.["custom_stop_words"]).toEqual(["the"]);
|
expect(createIndexBody?.["custom_stop_words"]).toEqual(["the"]);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("diffs and merges remote branches", async () => {
|
it("diffs and cherry-picks remote branches", async () => {
|
||||||
const sampleDiff = {
|
const sampleDiff = {
|
||||||
fromBranch: "exp",
|
fromBranch: "exp",
|
||||||
parentVersion: 1,
|
parentVersion: 1,
|
||||||
@@ -333,10 +359,9 @@ describe("remote connection", () => {
|
|||||||
changedColumns: [],
|
changedColumns: [],
|
||||||
addedIndexes: [],
|
addedIndexes: [],
|
||||||
removedIndexes: [],
|
removedIndexes: [],
|
||||||
mergeable: true,
|
errors: [],
|
||||||
mergeBlockers: [],
|
|
||||||
};
|
};
|
||||||
const mergeBodies: Record<string, unknown>[] = [];
|
const cherryPickBodies: Record<string, unknown>[] = [];
|
||||||
|
|
||||||
await withMockDatabase(
|
await withMockDatabase(
|
||||||
(req, res) => {
|
(req, res) => {
|
||||||
@@ -366,17 +391,16 @@ describe("remote connection", () => {
|
|||||||
.end(JSON.stringify(sampleDiff));
|
.end(JSON.stringify(sampleDiff));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (path.endsWith("/branches/merge/")) {
|
if (path.endsWith("/branches/cherry_pick/")) {
|
||||||
mergeBodies.push(body);
|
cherryPickBodies.push(body);
|
||||||
const dryRun = body["dry_run"] === true;
|
const dryRun = body["dry_run"] === true;
|
||||||
const response = {
|
const response = {
|
||||||
status: dryRun ? "ready" : "rejected",
|
status: dryRun ? "ready" : "failed",
|
||||||
diff: dryRun
|
diff: dryRun
|
||||||
? sampleDiff
|
? sampleDiff
|
||||||
: {
|
: {
|
||||||
...sampleDiff,
|
...sampleDiff,
|
||||||
mergeable: false,
|
errors: [
|
||||||
mergeBlockers: [
|
|
||||||
{ code: "baseMoved", message: "main has advanced" },
|
{ code: "baseMoved", message: "main has advanced" },
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
@@ -398,19 +422,19 @@ describe("remote connection", () => {
|
|||||||
|
|
||||||
await expect(branches.diff("exp")).resolves.toEqual(sampleDiff);
|
await expect(branches.diff("exp")).resolves.toEqual(sampleDiff);
|
||||||
|
|
||||||
const rejected = await branches.merge("exp");
|
const failed = await branches.cherryPick("exp");
|
||||||
expect(rejected.status).toBe("rejected");
|
expect(failed.status).toBe("failed");
|
||||||
expect(rejected.diff.mergeBlockers).toEqual([
|
expect(failed.diff.errors).toEqual([
|
||||||
{ code: "baseMoved", message: "main has advanced" },
|
{ code: "baseMoved", message: "main has advanced" },
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const preview = await branches.merge("exp", true);
|
const preview = await branches.cherryPick("exp", true);
|
||||||
expect(preview.status).toBe("ready");
|
expect(preview.status).toBe("ready");
|
||||||
expect(preview.preview.promotedColumns).toEqual(["tag"]);
|
expect(preview.preview.promotedColumns).toEqual(["tag"]);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
expect(mergeBodies).toEqual([
|
expect(cherryPickBodies).toEqual([
|
||||||
// biome-ignore lint/style/useNamingConvention: snake_case mandated by the server wire format
|
// biome-ignore lint/style/useNamingConvention: snake_case mandated by the server wire format
|
||||||
{ from_branch: "exp", dry_run: false },
|
{ from_branch: "exp", dry_run: false },
|
||||||
// biome-ignore lint/style/useNamingConvention: snake_case mandated by the server wire format
|
// biome-ignore lint/style/useNamingConvention: snake_case mandated by the server wire format
|
||||||
|
|||||||
@@ -11,10 +11,13 @@ import * as arrow17 from "apache-arrow-17";
|
|||||||
import * as arrow18 from "apache-arrow-18";
|
import * as arrow18 from "apache-arrow-18";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
|
AutoQuery,
|
||||||
Connection,
|
Connection,
|
||||||
MatchQuery,
|
MatchQuery,
|
||||||
PhraseQuery,
|
PhraseQuery,
|
||||||
|
Query,
|
||||||
Table,
|
Table,
|
||||||
|
VectorQuery,
|
||||||
connect,
|
connect,
|
||||||
tokenize,
|
tokenize,
|
||||||
} from "../lancedb";
|
} from "../lancedb";
|
||||||
@@ -682,13 +685,64 @@ describe.each([arrow15, arrow16, arrow17, arrow18])(
|
|||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// https://github.com/lancedb/lancedb/issues/1963
|
||||||
|
it("should query documents with LangChain PDF metadata", async () => {
|
||||||
|
const tmpDir = tmp.dirSync({ unsafeCleanup: true });
|
||||||
|
try {
|
||||||
|
const db = await connect(tmpDir.name);
|
||||||
|
const documents = [
|
||||||
|
{
|
||||||
|
text: "first page",
|
||||||
|
vector: [1, 0],
|
||||||
|
source: "first.pdf",
|
||||||
|
loc: { pageNumber: 1, lines: { from: 1, to: 12 } },
|
||||||
|
pdf: {
|
||||||
|
version: "1.10.100",
|
||||||
|
info: {
|
||||||
|
format: "PDF 1.7",
|
||||||
|
producer: "pdf.js",
|
||||||
|
creator: "Writer",
|
||||||
|
},
|
||||||
|
totalPages: 2,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: "second page",
|
||||||
|
vector: [0, 1],
|
||||||
|
source: "second.pdf",
|
||||||
|
loc: { pageNumber: 2, lines: { from: 13, to: 24 } },
|
||||||
|
pdf: {
|
||||||
|
version: "1.10.100",
|
||||||
|
info: {
|
||||||
|
format: "PDF 1.7",
|
||||||
|
producer: "pdf.js",
|
||||||
|
creator: "Writer",
|
||||||
|
},
|
||||||
|
totalPages: 2,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
];
|
||||||
|
const documentsTable = await db.createTable("documents", documents);
|
||||||
|
|
||||||
|
const results = await documentsTable.query().toArray();
|
||||||
|
|
||||||
|
expect(results).toHaveLength(2);
|
||||||
|
expect(results[0].source).toBe("first.pdf");
|
||||||
|
expect(results[0].pdf.info.producer).toBe("pdf.js");
|
||||||
|
expect(results[1].loc.pageNumber).toBe(2);
|
||||||
|
} finally {
|
||||||
|
tmpDir.removeCallback();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
describe("merge insert", () => {
|
describe("merge insert", () => {
|
||||||
let tmpDir: tmp.DirResult;
|
let tmpDir: tmp.DirResult;
|
||||||
|
let conn: Connection;
|
||||||
let table: Table;
|
let table: Table;
|
||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
tmpDir = tmp.dirSync({ unsafeCleanup: true });
|
tmpDir = tmp.dirSync({ unsafeCleanup: true });
|
||||||
const conn = await connect(tmpDir.name);
|
conn = await connect(tmpDir.name);
|
||||||
|
|
||||||
table = await conn.createTable("some_table", [
|
table = await conn.createTable("some_table", [
|
||||||
{ a: 1, b: "a" },
|
{ a: 1, b: "a" },
|
||||||
@@ -726,6 +780,38 @@ describe("merge insert", () => {
|
|||||||
|
|
||||||
expect(result.map((row) => ({ ...row }))).toEqual(expected);
|
expect(result.map((row) => ({ ...row }))).toEqual(expected);
|
||||||
});
|
});
|
||||||
|
test("upsert on a composite key", async () => {
|
||||||
|
const composite = await conn.createTable("composite", [
|
||||||
|
{ shard: "a", id: 1, val: "x" },
|
||||||
|
{ shard: "a", id: 2, val: "y" },
|
||||||
|
{ shard: "b", id: 1, val: "z" },
|
||||||
|
]);
|
||||||
|
|
||||||
|
// ("a", 1) matches an existing row and updates it. ("b", 2) agrees with an
|
||||||
|
// existing row on each key column separately but on neither pair, so it is
|
||||||
|
// an insert.
|
||||||
|
const mergeInsertRes = await composite
|
||||||
|
.mergeInsert(["shard", "id"])
|
||||||
|
.whenMatchedUpdateAll()
|
||||||
|
.whenNotMatchedInsertAll()
|
||||||
|
.execute([
|
||||||
|
{ shard: "a", id: 1, val: "X" },
|
||||||
|
{ shard: "b", id: 2, val: "W" },
|
||||||
|
]);
|
||||||
|
expect(mergeInsertRes.numUpdatedRows).toBe(1);
|
||||||
|
expect(mergeInsertRes.numInsertedRows).toBe(1);
|
||||||
|
|
||||||
|
const result = (await composite.toArrow())
|
||||||
|
.toArray()
|
||||||
|
.sort((a, b) => a.shard.localeCompare(b.shard) || a.id - b.id);
|
||||||
|
|
||||||
|
expect(result.map((row) => ({ ...row }))).toEqual([
|
||||||
|
{ shard: "a", id: 1, val: "X" },
|
||||||
|
{ shard: "a", id: 2, val: "y" },
|
||||||
|
{ shard: "b", id: 1, val: "z" },
|
||||||
|
{ shard: "b", id: 2, val: "W" },
|
||||||
|
]);
|
||||||
|
});
|
||||||
test("conditional update", async () => {
|
test("conditional update", async () => {
|
||||||
const newData = [
|
const newData = [
|
||||||
{ a: 2, b: "x" },
|
{ a: 2, b: "x" },
|
||||||
@@ -1440,11 +1526,7 @@ describe("When creating an index", () => {
|
|||||||
|
|
||||||
it("should allow me to replace (or not) an existing index", async () => {
|
it("should allow me to replace (or not) an existing index", async () => {
|
||||||
await tbl.createIndex("id");
|
await tbl.createIndex("id");
|
||||||
// Default is replace=true
|
await expect(tbl.createIndex("id")).rejects.toThrow("already exists");
|
||||||
await tbl.createIndex("id");
|
|
||||||
await expect(tbl.createIndex("id", { replace: false })).rejects.toThrow(
|
|
||||||
"already exists",
|
|
||||||
);
|
|
||||||
await tbl.createIndex("id", { replace: true });
|
await tbl.createIndex("id", { replace: true });
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -1777,6 +1859,194 @@ describe("Read consistency interval", () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe("automatic search schema consistency", () => {
|
||||||
|
let tmpDir: tmp.DirResult;
|
||||||
|
|
||||||
|
class SchemaRefreshEmbedding extends EmbeddingFunction<string> {
|
||||||
|
ndims() {
|
||||||
|
return 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
embeddingDataType() {
|
||||||
|
return new Float32();
|
||||||
|
}
|
||||||
|
|
||||||
|
async computeSourceEmbeddings(data: string[]) {
|
||||||
|
return data.map((value) => [value.length, 1]);
|
||||||
|
}
|
||||||
|
|
||||||
|
async computeQueryEmbeddings(value: string) {
|
||||||
|
return [value.length, 1];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function embeddingSchema() {
|
||||||
|
const func = new SchemaRefreshEmbedding();
|
||||||
|
return LanceSchema({
|
||||||
|
text: func.sourceField(new Utf8()),
|
||||||
|
vector: func.vectorField(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
getRegistry().reset();
|
||||||
|
register("schema-refresh")(SchemaRefreshEmbedding);
|
||||||
|
tmpDir = tmp.dirSync({ unsafeCleanup: true });
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
getRegistry().reset();
|
||||||
|
tmpDir.removeCallback();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("uses the schema refreshed from another connection", async () => {
|
||||||
|
const first = await connect(tmpDir.name, { readConsistencyInterval: 0 });
|
||||||
|
const second = await connect(tmpDir.name, { readConsistencyInterval: 0 });
|
||||||
|
|
||||||
|
try {
|
||||||
|
const stale = await first.createTable("docs", [{ text: "before" }], {
|
||||||
|
schema: embeddingSchema(),
|
||||||
|
});
|
||||||
|
const replacement = await second.createTable(
|
||||||
|
"docs",
|
||||||
|
[{ text: "after hello" }],
|
||||||
|
{ mode: "overwrite" },
|
||||||
|
);
|
||||||
|
await replacement.createIndex("text", { config: Index.fts() });
|
||||||
|
|
||||||
|
const search = stale.search("hello");
|
||||||
|
expect(search).toBeInstanceOf(AutoQuery);
|
||||||
|
expect(search).not.toBeInstanceOf(Query);
|
||||||
|
expect(search).not.toBeInstanceOf(VectorQuery);
|
||||||
|
expect("nprobes" in search).toBe(false);
|
||||||
|
|
||||||
|
const rows = await search.toArray();
|
||||||
|
expect(rows[0].text).toBe("after hello");
|
||||||
|
expect((await stale.schema()).metadata.has("embedding_functions")).toBe(
|
||||||
|
false,
|
||||||
|
);
|
||||||
|
} finally {
|
||||||
|
first.close();
|
||||||
|
second.close();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
it("tracks embedding metadata across checkout and restore", async () => {
|
||||||
|
const first = await connect(tmpDir.name, { readConsistencyInterval: 0 });
|
||||||
|
const second = await connect(tmpDir.name, { readConsistencyInterval: 0 });
|
||||||
|
|
||||||
|
try {
|
||||||
|
await first.createTable("docs", [{ text: "before" }], {
|
||||||
|
schema: embeddingSchema(),
|
||||||
|
});
|
||||||
|
const table = await second.createTable(
|
||||||
|
"docs",
|
||||||
|
[{ text: "after hello" }],
|
||||||
|
{ mode: "overwrite" },
|
||||||
|
);
|
||||||
|
await table.createIndex("text", { config: Index.fts() });
|
||||||
|
|
||||||
|
await table.checkout(1);
|
||||||
|
expect((await table.search("before").toArray())[0].text).toBe("before");
|
||||||
|
|
||||||
|
await table.checkoutLatest();
|
||||||
|
expect((await table.search("hello").toArray())[0].text).toBe(
|
||||||
|
"after hello",
|
||||||
|
);
|
||||||
|
|
||||||
|
await table.checkout(1);
|
||||||
|
await table.restore();
|
||||||
|
expect((await table.search("before").toArray())[0].text).toBe("before");
|
||||||
|
} finally {
|
||||||
|
first.close();
|
||||||
|
second.close();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
it("pins automatic search while computing an embedding", async () => {
|
||||||
|
let markStarted!: () => void;
|
||||||
|
let releaseEmbedding!: () => void;
|
||||||
|
const started = new Promise<void>((resolve) => {
|
||||||
|
markStarted = resolve;
|
||||||
|
});
|
||||||
|
const released = new Promise<void>((resolve) => {
|
||||||
|
releaseEmbedding = resolve;
|
||||||
|
});
|
||||||
|
|
||||||
|
class BlockingEmbedding extends SchemaRefreshEmbedding {
|
||||||
|
async computeQueryEmbeddings(value: string) {
|
||||||
|
markStarted();
|
||||||
|
await released;
|
||||||
|
return [value.length, 1];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
register("schema-refresh-blocking")(BlockingEmbedding);
|
||||||
|
const func = new BlockingEmbedding();
|
||||||
|
const schema = LanceSchema({
|
||||||
|
text: func.sourceField(new Utf8()),
|
||||||
|
vector: func.vectorField(),
|
||||||
|
});
|
||||||
|
const first = await connect(tmpDir.name, { readConsistencyInterval: 0 });
|
||||||
|
const second = await connect(tmpDir.name, { readConsistencyInterval: 0 });
|
||||||
|
|
||||||
|
try {
|
||||||
|
const table = await first.createTable(
|
||||||
|
"docs",
|
||||||
|
[{ text: "hello before" }],
|
||||||
|
{ schema },
|
||||||
|
);
|
||||||
|
const pending = table.search("hello").toArray();
|
||||||
|
await started;
|
||||||
|
|
||||||
|
const replacement = await second.createTable(
|
||||||
|
"docs",
|
||||||
|
[{ text: "hello after" }],
|
||||||
|
{ mode: "overwrite" },
|
||||||
|
);
|
||||||
|
await replacement.createIndex("text", { config: Index.fts() });
|
||||||
|
releaseEmbedding();
|
||||||
|
|
||||||
|
expect((await pending)[0].text).toBe("hello before");
|
||||||
|
} finally {
|
||||||
|
releaseEmbedding();
|
||||||
|
first.close();
|
||||||
|
second.close();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
it("refreshes a reused automatic search for every execution", async () => {
|
||||||
|
const first = await connect(tmpDir.name, { readConsistencyInterval: 0 });
|
||||||
|
const second = await connect(tmpDir.name, { readConsistencyInterval: 0 });
|
||||||
|
|
||||||
|
try {
|
||||||
|
const table = await first.createTable("docs", [
|
||||||
|
{ text: "hello before", marker: "before" },
|
||||||
|
]);
|
||||||
|
await table.createIndex("text", { config: Index.fts() });
|
||||||
|
const search = table.search("hello").select(["text"]);
|
||||||
|
|
||||||
|
const before = (await search.toArray())[0];
|
||||||
|
expect(before.text).toBe("hello before");
|
||||||
|
expect(before.marker).toBeUndefined();
|
||||||
|
|
||||||
|
const replacement = await second.createTable(
|
||||||
|
"docs",
|
||||||
|
[{ text: "hello after", marker: "after" }],
|
||||||
|
{ mode: "overwrite" },
|
||||||
|
);
|
||||||
|
await replacement.createIndex("text", { config: Index.fts() });
|
||||||
|
|
||||||
|
const after = (await search.toArray())[0];
|
||||||
|
expect(after.text).toBe("hello after");
|
||||||
|
expect(after.marker).toBeUndefined();
|
||||||
|
} finally {
|
||||||
|
first.close();
|
||||||
|
second.close();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
describe("schema evolution", function () {
|
describe("schema evolution", function () {
|
||||||
let tmpDir: tmp.DirResult;
|
let tmpDir: tmp.DirResult;
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
@@ -2344,7 +2614,24 @@ describe.each([arrow15, arrow16, arrow17, arrow18])(
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
test("full text search if no embedding function provided", async () => {
|
test("full text search if only an unrelated embedding function is registered", async () => {
|
||||||
|
register("unused")(
|
||||||
|
class extends EmbeddingFunction<string> {
|
||||||
|
ndims() {
|
||||||
|
return 3;
|
||||||
|
}
|
||||||
|
embeddingDataType() {
|
||||||
|
return new Float32();
|
||||||
|
}
|
||||||
|
async computeQueryEmbeddings(_data: string) {
|
||||||
|
return [1, 2, 3];
|
||||||
|
}
|
||||||
|
async computeSourceEmbeddings(data: string[]) {
|
||||||
|
return data.map(() => [1, 2, 3]);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
const db = await connect(tmpDir.name);
|
const db = await connect(tmpDir.name);
|
||||||
const data = [
|
const data = [
|
||||||
{ text: "hello world", vector: [0.1, 0.2, 0.3] },
|
{ text: "hello world", vector: [0.1, 0.2, 0.3] },
|
||||||
@@ -2366,6 +2653,306 @@ describe.each([arrow15, arrow16, arrow17, arrow18])(
|
|||||||
expect(results2[0].text).toBe(data[1].text);
|
expect(results2[0].text).toBe(data[1].text);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test("auto search stays consistent with the active revision", async () => {
|
||||||
|
let initCalls = 0;
|
||||||
|
let queryCalls = 0;
|
||||||
|
let markStarted!: () => void;
|
||||||
|
const started = new Promise<void>((resolve) => {
|
||||||
|
markStarted = resolve;
|
||||||
|
});
|
||||||
|
let releaseEmbedding!: () => void;
|
||||||
|
const embeddingReleased = new Promise<void>((resolve) => {
|
||||||
|
releaseEmbedding = resolve;
|
||||||
|
});
|
||||||
|
|
||||||
|
@register("refresh-test")
|
||||||
|
class TestEmbedding extends EmbeddingFunction<string> {
|
||||||
|
async init() {
|
||||||
|
initCalls += 1;
|
||||||
|
}
|
||||||
|
ndims() {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
embeddingDataType() {
|
||||||
|
return new arrow.Float32();
|
||||||
|
}
|
||||||
|
async computeQueryEmbeddings(value: string) {
|
||||||
|
queryCalls += 1;
|
||||||
|
if (value === "blocked") {
|
||||||
|
markStarted();
|
||||||
|
await embeddingReleased;
|
||||||
|
}
|
||||||
|
return value === "greetings" ? [0.1] : [0.2];
|
||||||
|
}
|
||||||
|
async computeSourceEmbeddings(values: string[]) {
|
||||||
|
return values.map((value) =>
|
||||||
|
value === "hello world" ? [0.1] : [0.2],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const writer = await connect(tmpDir.name);
|
||||||
|
await writer.createTable("test", [{ text: "plain", vector: [0.0] }]);
|
||||||
|
const reader = await connect(tmpDir.name, {
|
||||||
|
readConsistencyInterval: 0,
|
||||||
|
});
|
||||||
|
const tracked = await reader.openTable("test");
|
||||||
|
type SnapshotCountingNative = {
|
||||||
|
querySnapshot: () => Promise<unknown>;
|
||||||
|
};
|
||||||
|
const native = (tracked as unknown as { inner: SnapshotCountingNative })
|
||||||
|
.inner;
|
||||||
|
const querySnapshot = native.querySnapshot.bind(native);
|
||||||
|
let snapshotCalls = 0;
|
||||||
|
native.querySnapshot = async () => {
|
||||||
|
snapshotCalls += 1;
|
||||||
|
return await querySnapshot();
|
||||||
|
};
|
||||||
|
const autoQuery = tracked.search("greetings").select(["text"]).limit(1);
|
||||||
|
|
||||||
|
const func = new TestEmbedding();
|
||||||
|
const schema = LanceSchema({
|
||||||
|
text: func.sourceField(new arrow.Utf8()),
|
||||||
|
vector: func.vectorField(),
|
||||||
|
});
|
||||||
|
const data = [{ text: "hello world" }, { text: "goodbye world" }];
|
||||||
|
await writer.createTable("test", data, { mode: "overwrite", schema });
|
||||||
|
const baselineInitCalls = initCalls;
|
||||||
|
|
||||||
|
expect(
|
||||||
|
(await tracked.schema()).metadata.get("embedding_functions"),
|
||||||
|
).toBeDefined();
|
||||||
|
const results = await autoQuery.toArray();
|
||||||
|
expect(results[0].text).toBe(data[0].text);
|
||||||
|
expect(initCalls).toBe(baselineInitCalls + 1);
|
||||||
|
expect(queryCalls).toBe(1);
|
||||||
|
expect(snapshotCalls).toBe(1);
|
||||||
|
|
||||||
|
const repeatedResults = await autoQuery.toArray();
|
||||||
|
expect(repeatedResults[0].text).toBe(data[0].text);
|
||||||
|
expect(initCalls).toBe(baselineInitCalls + 1);
|
||||||
|
expect(queryCalls).toBe(1);
|
||||||
|
expect(snapshotCalls).toBe(2);
|
||||||
|
|
||||||
|
const pending = tracked
|
||||||
|
.search("blocked")
|
||||||
|
.select(["text"])
|
||||||
|
.limit(1)
|
||||||
|
.toArray();
|
||||||
|
await started;
|
||||||
|
|
||||||
|
const ftsData = [
|
||||||
|
{ text: "greetings from full text", vector: [0.0] },
|
||||||
|
{ text: "blocked from full text", vector: [0.0] },
|
||||||
|
];
|
||||||
|
const ftsTable = await writer.createTable("test", ftsData, {
|
||||||
|
mode: "overwrite",
|
||||||
|
});
|
||||||
|
await ftsTable.createIndex("text", { config: Index.fts() });
|
||||||
|
releaseEmbedding();
|
||||||
|
|
||||||
|
const pendingResults = await pending;
|
||||||
|
expect(pendingResults[0].text).toBe(data[1].text);
|
||||||
|
|
||||||
|
expect(
|
||||||
|
(await tracked.schema()).metadata.get("embedding_functions"),
|
||||||
|
).toBeUndefined();
|
||||||
|
const ftsResults = await autoQuery.toArray();
|
||||||
|
expect(ftsResults[0].text).toBe(ftsData[0].text);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("auto search keeps newer preparation during a revision race", async () => {
|
||||||
|
let aCalls = 0;
|
||||||
|
let bCalls = 0;
|
||||||
|
let markAStarted!: () => void;
|
||||||
|
const aStarted = new Promise<void>((resolve) => {
|
||||||
|
markAStarted = resolve;
|
||||||
|
});
|
||||||
|
let releaseA!: () => void;
|
||||||
|
const aReleased = new Promise<void>((resolve) => {
|
||||||
|
releaseA = resolve;
|
||||||
|
});
|
||||||
|
let markBStarted!: () => void;
|
||||||
|
const bStarted = new Promise<void>((resolve) => {
|
||||||
|
markBStarted = resolve;
|
||||||
|
});
|
||||||
|
let releaseB!: () => void;
|
||||||
|
const bReleased = new Promise<void>((resolve) => {
|
||||||
|
releaseB = resolve;
|
||||||
|
});
|
||||||
|
|
||||||
|
@register("race-a")
|
||||||
|
class EmbeddingA extends EmbeddingFunction<string> {
|
||||||
|
ndims() {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
embeddingDataType() {
|
||||||
|
return new arrow.Float32();
|
||||||
|
}
|
||||||
|
async computeQueryEmbeddings() {
|
||||||
|
aCalls += 1;
|
||||||
|
markAStarted();
|
||||||
|
await aReleased;
|
||||||
|
return [0.1];
|
||||||
|
}
|
||||||
|
async computeSourceEmbeddings(values: string[]) {
|
||||||
|
return values.map(() => [0.1]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@register("race-b")
|
||||||
|
class EmbeddingB extends EmbeddingFunction<string> {
|
||||||
|
ndims() {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
embeddingDataType() {
|
||||||
|
return new arrow.Float32();
|
||||||
|
}
|
||||||
|
async computeQueryEmbeddings() {
|
||||||
|
bCalls += 1;
|
||||||
|
markBStarted();
|
||||||
|
await bReleased;
|
||||||
|
return [0.2];
|
||||||
|
}
|
||||||
|
async computeSourceEmbeddings(values: string[]) {
|
||||||
|
return values.map(() => [0.2]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const writer = await connect(tmpDir.name);
|
||||||
|
const embeddingA = new EmbeddingA();
|
||||||
|
const schemaA = LanceSchema({
|
||||||
|
text: embeddingA.sourceField(new arrow.Utf8()),
|
||||||
|
vector: embeddingA.vectorField(),
|
||||||
|
});
|
||||||
|
await writer.createTable("race", [{ text: "revision a" }], {
|
||||||
|
schema: schemaA,
|
||||||
|
});
|
||||||
|
const reader = await connect(tmpDir.name, {
|
||||||
|
readConsistencyInterval: 0,
|
||||||
|
});
|
||||||
|
const tracked = await reader.openTable("race");
|
||||||
|
const query = tracked.search("query");
|
||||||
|
|
||||||
|
const first = query.toArray();
|
||||||
|
await aStarted;
|
||||||
|
|
||||||
|
const embeddingB = new EmbeddingB();
|
||||||
|
const schemaB = LanceSchema({
|
||||||
|
text: embeddingB.sourceField(new arrow.Utf8()),
|
||||||
|
vector: embeddingB.vectorField(),
|
||||||
|
});
|
||||||
|
await writer.createTable("race", [{ text: "revision b" }], {
|
||||||
|
mode: "overwrite",
|
||||||
|
schema: schemaB,
|
||||||
|
});
|
||||||
|
const second = query.toArray();
|
||||||
|
await bStarted;
|
||||||
|
|
||||||
|
releaseA();
|
||||||
|
releaseB();
|
||||||
|
await Promise.all([first, second]);
|
||||||
|
expect(aCalls).toBe(1);
|
||||||
|
expect(bCalls).toBe(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("stale FTS routing keeps newer vector preparation", async () => {
|
||||||
|
let vectorCalls = 0;
|
||||||
|
let markVectorStarted!: () => void;
|
||||||
|
const vectorStarted = new Promise<void>((resolve) => {
|
||||||
|
markVectorStarted = resolve;
|
||||||
|
});
|
||||||
|
let releaseVector!: () => void;
|
||||||
|
const vectorReleased = new Promise<void>((resolve) => {
|
||||||
|
releaseVector = resolve;
|
||||||
|
});
|
||||||
|
|
||||||
|
@register("stale-fts-race")
|
||||||
|
class RaceEmbedding extends EmbeddingFunction<string> {
|
||||||
|
ndims() {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
embeddingDataType() {
|
||||||
|
return new arrow.Float32();
|
||||||
|
}
|
||||||
|
async computeQueryEmbeddings() {
|
||||||
|
vectorCalls += 1;
|
||||||
|
markVectorStarted();
|
||||||
|
await vectorReleased;
|
||||||
|
return [0.1];
|
||||||
|
}
|
||||||
|
async computeSourceEmbeddings(values: string[]) {
|
||||||
|
return values.map(() => [0.1]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const writer = await connect(tmpDir.name);
|
||||||
|
const ftsTable = await writer.createTable("stale_fts", [
|
||||||
|
{ text: "hello", vector: [0.0] },
|
||||||
|
]);
|
||||||
|
await ftsTable.createIndex("text", { config: Index.fts() });
|
||||||
|
|
||||||
|
const reader = await connect(tmpDir.name, {
|
||||||
|
readConsistencyInterval: 0,
|
||||||
|
});
|
||||||
|
const tracked = await reader.openTable("stale_fts");
|
||||||
|
type Snapshot = {
|
||||||
|
schema: () => Promise<Buffer>;
|
||||||
|
};
|
||||||
|
type NativeWithSnapshot = {
|
||||||
|
querySnapshot: () => Promise<Snapshot>;
|
||||||
|
};
|
||||||
|
const native = (tracked as unknown as { inner: NativeWithSnapshot })
|
||||||
|
.inner;
|
||||||
|
const querySnapshot = native.querySnapshot.bind(native);
|
||||||
|
let snapshotCalls = 0;
|
||||||
|
let markStaleSchemaStarted!: () => void;
|
||||||
|
const staleSchemaStarted = new Promise<void>((resolve) => {
|
||||||
|
markStaleSchemaStarted = resolve;
|
||||||
|
});
|
||||||
|
let releaseStaleSchema!: () => void;
|
||||||
|
const staleSchemaReleased = new Promise<void>((resolve) => {
|
||||||
|
releaseStaleSchema = resolve;
|
||||||
|
});
|
||||||
|
native.querySnapshot = async () => {
|
||||||
|
const snapshot = await querySnapshot();
|
||||||
|
snapshotCalls += 1;
|
||||||
|
if (snapshotCalls === 1) {
|
||||||
|
const schema = snapshot.schema.bind(snapshot);
|
||||||
|
snapshot.schema = async () => {
|
||||||
|
markStaleSchemaStarted();
|
||||||
|
await staleSchemaReleased;
|
||||||
|
return await schema();
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return snapshot;
|
||||||
|
};
|
||||||
|
|
||||||
|
const query = tracked.search("hello");
|
||||||
|
const staleFtsExecution = query.toArray();
|
||||||
|
await staleSchemaStarted;
|
||||||
|
|
||||||
|
const embedding = new RaceEmbedding();
|
||||||
|
const vectorSchema = LanceSchema({
|
||||||
|
text: embedding.sourceField(new arrow.Utf8()),
|
||||||
|
vector: embedding.vectorField(),
|
||||||
|
});
|
||||||
|
await writer.createTable("stale_fts", [{ text: "hello" }], {
|
||||||
|
mode: "overwrite",
|
||||||
|
schema: vectorSchema,
|
||||||
|
});
|
||||||
|
|
||||||
|
const vectorExecution = query.toArray();
|
||||||
|
await vectorStarted;
|
||||||
|
releaseStaleSchema();
|
||||||
|
await staleFtsExecution;
|
||||||
|
releaseVector();
|
||||||
|
await vectorExecution;
|
||||||
|
|
||||||
|
await query.toArray();
|
||||||
|
expect(vectorCalls).toBe(1);
|
||||||
|
});
|
||||||
|
|
||||||
test("tokenizes FTS queries by column or index name", async () => {
|
test("tokenizes FTS queries by column or index name", async () => {
|
||||||
const db = await connect(tmpDir.name);
|
const db = await connect(tmpDir.name);
|
||||||
const data = [
|
const data = [
|
||||||
@@ -2916,6 +3503,30 @@ describe("column name options", () => {
|
|||||||
expect(results[1].query_index).toBe(1);
|
expect(results[1].query_index).toBe(1);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test("observes promised additional vectors while the query is pending", async () => {
|
||||||
|
const initialVector = new Promise<number[]>(() => undefined);
|
||||||
|
const query = table.query().nearestTo(initialVector);
|
||||||
|
const unhandled: unknown[] = [];
|
||||||
|
const onUnhandled = (reason: unknown) => unhandled.push(reason);
|
||||||
|
process.on("unhandledRejection", onUnhandled);
|
||||||
|
|
||||||
|
try {
|
||||||
|
query.addQueryVector(Promise.reject(new Error("extra vector failed")));
|
||||||
|
await new Promise<void>((resolve) => setImmediate(resolve));
|
||||||
|
expect(unhandled).toEqual([]);
|
||||||
|
|
||||||
|
const rejectedQuery = table
|
||||||
|
.query()
|
||||||
|
.nearestTo([0.1, 0.2])
|
||||||
|
.addQueryVector(Promise.reject(new Error("consumed vector failed")));
|
||||||
|
await expect(rejectedQuery.toArray()).rejects.toThrow(
|
||||||
|
"consumed vector failed",
|
||||||
|
);
|
||||||
|
} finally {
|
||||||
|
process.off("unhandledRejection", onUnhandled);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
test("index and search multivectors", async () => {
|
test("index and search multivectors", async () => {
|
||||||
const db = await connect(tmpDir.name);
|
const db = await connect(tmpDir.name);
|
||||||
const data = [];
|
const data = [];
|
||||||
@@ -2953,7 +3564,7 @@ describe("column name options", () => {
|
|||||||
.limit(10)
|
.limit(10)
|
||||||
.toArray();
|
.toArray();
|
||||||
expect(results2.length).toBe(10);
|
expect(results2.length).toBe(10);
|
||||||
});
|
}, 30_000);
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("when creating an empty table", () => {
|
describe("when creating an empty table", () => {
|
||||||
@@ -2979,6 +3590,27 @@ describe("when creating an empty table", () => {
|
|||||||
expect((actualSchema.fields[1].type as Float64).precision).toBe(2);
|
expect((actualSchema.fields[1].type as Float64).precision).toBe(2);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("can add and query JSON data", async () => {
|
||||||
|
const schema = new Schema([
|
||||||
|
new Field("id", new Int32(), true),
|
||||||
|
new Field(
|
||||||
|
"meta",
|
||||||
|
new Utf8(),
|
||||||
|
true,
|
||||||
|
new Map([["ARROW:extension:name", "arrow.json"]]),
|
||||||
|
),
|
||||||
|
]);
|
||||||
|
const table = await con.createEmptyTable("json", schema);
|
||||||
|
const meta = JSON.stringify({ x: 1 });
|
||||||
|
|
||||||
|
await table.add([{ id: 1, meta }]);
|
||||||
|
|
||||||
|
const rows = await table.query().toArray();
|
||||||
|
expect(rows).toHaveLength(1);
|
||||||
|
expect(rows[0].id).toBe(1);
|
||||||
|
expect(rows[0].meta).toBe(meta);
|
||||||
|
});
|
||||||
|
|
||||||
it("can create an empty table from schema that specifies field types by name", async () => {
|
it("can create an empty table from schema that specifies field types by name", async () => {
|
||||||
const schemaLike = {
|
const schemaLike = {
|
||||||
fields: [
|
fields: [
|
||||||
|
|||||||
@@ -170,7 +170,7 @@ test("basic table examples", async () => {
|
|||||||
// --8<-- [end:create_index]
|
// --8<-- [end:create_index]
|
||||||
|
|
||||||
// --8<-- [start:delete_rows]
|
// --8<-- [start:delete_rows]
|
||||||
await tbl.delete('item = "fizz"');
|
await tbl.delete("item = 'fizz'");
|
||||||
// --8<-- [end:delete_rows]
|
// --8<-- [end:delete_rows]
|
||||||
|
|
||||||
// --8<-- [start:drop_table]
|
// --8<-- [start:drop_table]
|
||||||
|
|||||||
@@ -8,7 +8,8 @@
|
|||||||
"//1": "--experimental-vm-modules is needed to run jest with sentence-transformers",
|
"//1": "--experimental-vm-modules is needed to run jest with sentence-transformers",
|
||||||
"//2": "--testEnvironment is needed to run jest with sentence-transformers",
|
"//2": "--testEnvironment is needed to run jest with sentence-transformers",
|
||||||
"//3": "See: https://github.com/huggingface/transformers.js/issues/57",
|
"//3": "See: https://github.com/huggingface/transformers.js/issues/57",
|
||||||
"test": "node --experimental-vm-modules node_modules/.bin/jest --testEnvironment jest-environment-node-single-context --verbose",
|
"//4": "jest is invoked by its JS entry, not node_modules/.bin/jest: under pnpm that path is a shell shim, which `node` cannot execute",
|
||||||
|
"test": "node --experimental-vm-modules node_modules/jest/bin/jest.js --testEnvironment jest-environment-node-single-context --verbose",
|
||||||
"lint": "biome check *.ts && biome format *.ts",
|
"lint": "biome check *.ts && biome format *.ts",
|
||||||
"lint-ci": "biome ci .",
|
"lint-ci": "biome ci .",
|
||||||
"lint-fix": "biome check --write *.ts && pnpm format",
|
"lint-fix": "biome check --write *.ts && pnpm format",
|
||||||
|
|||||||
+43
-314
@@ -5,7 +5,6 @@ import {
|
|||||||
Data as ArrowData,
|
Data as ArrowData,
|
||||||
Table as ArrowTable,
|
Table as ArrowTable,
|
||||||
Binary,
|
Binary,
|
||||||
Bool,
|
|
||||||
BufferType,
|
BufferType,
|
||||||
DataType,
|
DataType,
|
||||||
DateUnit,
|
DateUnit,
|
||||||
@@ -18,12 +17,7 @@ import {
|
|||||||
FixedSizeList,
|
FixedSizeList,
|
||||||
Float,
|
Float,
|
||||||
Float32,
|
Float32,
|
||||||
Float64,
|
|
||||||
Int,
|
Int,
|
||||||
Int8,
|
|
||||||
Int16,
|
|
||||||
Int32,
|
|
||||||
Int64,
|
|
||||||
LargeBinary,
|
LargeBinary,
|
||||||
List,
|
List,
|
||||||
Null,
|
Null,
|
||||||
@@ -36,33 +30,29 @@ import {
|
|||||||
Struct,
|
Struct,
|
||||||
Timestamp,
|
Timestamp,
|
||||||
Type,
|
Type,
|
||||||
Uint8,
|
|
||||||
Uint16,
|
|
||||||
Uint32,
|
|
||||||
Utf8,
|
Utf8,
|
||||||
Vector,
|
Vector,
|
||||||
makeVector as arrowMakeVector,
|
makeVector as arrowMakeVector,
|
||||||
|
util as arrowUtil,
|
||||||
vectorFromArray as badVectorFromArray,
|
vectorFromArray as badVectorFromArray,
|
||||||
makeBuilder,
|
makeBuilder,
|
||||||
makeData,
|
makeData,
|
||||||
} from "apache-arrow";
|
} from "apache-arrow";
|
||||||
import { Buffers } from "apache-arrow/data";
|
import { Buffers } from "apache-arrow/data";
|
||||||
|
import { typedArrayToArrowType } from "./arrow_type";
|
||||||
import { type EmbeddingFunction } from "./embedding/embedding_function";
|
import { type EmbeddingFunction } from "./embedding/embedding_function";
|
||||||
import { EmbeddingFunctionConfig, getRegistry } from "./embedding/registry";
|
import {
|
||||||
|
EmbeddingFunctionConfig,
|
||||||
|
getRegistry,
|
||||||
|
parseEmbeddingMetadata,
|
||||||
|
} from "./embedding/registry";
|
||||||
import {
|
import {
|
||||||
sanitizeField,
|
sanitizeField,
|
||||||
sanitizeSchema,
|
sanitizeSchema,
|
||||||
sanitizeTable,
|
sanitizeTable,
|
||||||
sanitizeType,
|
sanitizeType,
|
||||||
} from "./sanitize";
|
} from "./sanitize";
|
||||||
|
import { inferSchema } from "./schema";
|
||||||
/**
|
|
||||||
* Check if a field name indicates a vector column.
|
|
||||||
*/
|
|
||||||
function nameSuggestsVectorColumn(fieldName: string): boolean {
|
|
||||||
const nameLower = fieldName.toLowerCase();
|
|
||||||
return nameLower.includes("vector") || nameLower.includes("embedding");
|
|
||||||
}
|
|
||||||
|
|
||||||
export * from "apache-arrow";
|
export * from "apache-arrow";
|
||||||
export type SchemaLike =
|
export type SchemaLike =
|
||||||
@@ -82,8 +72,7 @@ export type FieldLike =
|
|||||||
};
|
};
|
||||||
|
|
||||||
export type DataLike =
|
export type DataLike =
|
||||||
// biome-ignore lint/suspicious/noExplicitAny: <explanation>
|
| import("apache-arrow").Data
|
||||||
| import("apache-arrow").Data<Struct<any>>
|
|
||||||
| {
|
| {
|
||||||
// biome-ignore lint/suspicious/noExplicitAny: <explanation>
|
// biome-ignore lint/suspicious/noExplicitAny: <explanation>
|
||||||
type: any;
|
type: any;
|
||||||
@@ -92,6 +81,7 @@ export type DataLike =
|
|||||||
stride: number;
|
stride: number;
|
||||||
nullable: boolean;
|
nullable: boolean;
|
||||||
children: DataLike[];
|
children: DataLike[];
|
||||||
|
dictionary?: { data: readonly DataLike[] };
|
||||||
get nullCount(): number;
|
get nullCount(): number;
|
||||||
// biome-ignore lint/suspicious/noExplicitAny: <explanation>
|
// biome-ignore lint/suspicious/noExplicitAny: <explanation>
|
||||||
values: Buffers<any>[BufferType.DATA];
|
values: Buffers<any>[BufferType.DATA];
|
||||||
@@ -455,110 +445,6 @@ export function makeArrowTable(
|
|||||||
return new ArrowTable(inferredSchema, finalColumns);
|
return new ArrowTable(inferredSchema, finalColumns);
|
||||||
}
|
}
|
||||||
|
|
||||||
function inferSchema(
|
|
||||||
data: Array<Record<string, unknown>>,
|
|
||||||
schema: Schema | undefined,
|
|
||||||
opts: MakeArrowTableOptions,
|
|
||||||
): Schema {
|
|
||||||
// We will collect all fields we see in the data.
|
|
||||||
const pathTree = new PathTree<DataType>();
|
|
||||||
|
|
||||||
for (const [rowI, row] of data.entries()) {
|
|
||||||
for (const [path, value] of rowPathsAndValues(row)) {
|
|
||||||
if (!pathTree.has(path)) {
|
|
||||||
// First time seeing this field.
|
|
||||||
if (schema !== undefined) {
|
|
||||||
const field = getFieldForPath(schema, path);
|
|
||||||
if (field === undefined) {
|
|
||||||
throw new Error(
|
|
||||||
`Found field not in schema: ${path.join(".")} at row ${rowI}`,
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
pathTree.set(path, field.type);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
const inferredType = inferType(value, path, opts);
|
|
||||||
if (inferredType === undefined) {
|
|
||||||
throw new Error(`Failed to infer data type for field ${path.join(
|
|
||||||
".",
|
|
||||||
)} at row ${rowI}. \
|
|
||||||
Consider providing an explicit schema.`);
|
|
||||||
}
|
|
||||||
pathTree.set(path, inferredType);
|
|
||||||
}
|
|
||||||
} else if (schema === undefined) {
|
|
||||||
const currentType = pathTree.get(path);
|
|
||||||
const newType = inferType(value, path, opts);
|
|
||||||
if (currentType !== newType) {
|
|
||||||
new Error(`Failed to infer schema for data. Previously inferred type \
|
|
||||||
${currentType} but found ${newType} at row ${rowI}. Consider \
|
|
||||||
providing an explicit schema.`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (schema === undefined) {
|
|
||||||
function fieldsFromPathTree(pathTree: PathTree<DataType>): Field[] {
|
|
||||||
const fields = [];
|
|
||||||
for (const [name, value] of pathTree.map.entries()) {
|
|
||||||
if (value instanceof PathTree) {
|
|
||||||
const children = fieldsFromPathTree(value);
|
|
||||||
fields.push(new Field(name, new Struct(children), true));
|
|
||||||
} else {
|
|
||||||
fields.push(new Field(name, value, true));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return fields;
|
|
||||||
}
|
|
||||||
const fields = fieldsFromPathTree(pathTree);
|
|
||||||
return new Schema(fields);
|
|
||||||
} else {
|
|
||||||
function takeMatchingFields(
|
|
||||||
fields: Field[],
|
|
||||||
pathTree: PathTree<DataType>,
|
|
||||||
): Field[] {
|
|
||||||
const outFields = [];
|
|
||||||
for (const field of fields) {
|
|
||||||
if (pathTree.map.has(field.name)) {
|
|
||||||
const value = pathTree.get([field.name]);
|
|
||||||
if (value instanceof PathTree) {
|
|
||||||
const struct = field.type as Struct;
|
|
||||||
const children = takeMatchingFields(struct.children, value);
|
|
||||||
outFields.push(
|
|
||||||
new Field(field.name, new Struct(children), field.nullable),
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
outFields.push(
|
|
||||||
new Field(field.name, value as DataType, field.nullable),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return outFields;
|
|
||||||
}
|
|
||||||
const fields = takeMatchingFields(schema.fields, pathTree);
|
|
||||||
return new Schema(fields);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function* rowPathsAndValues(
|
|
||||||
row: Record<string, unknown>,
|
|
||||||
basePath: string[] = [],
|
|
||||||
): Generator<[string[], unknown]> {
|
|
||||||
for (const [key, value] of Object.entries(row)) {
|
|
||||||
if (isObject(value)) {
|
|
||||||
yield* rowPathsAndValues(value, [...basePath, key]);
|
|
||||||
} else {
|
|
||||||
// Skip undefined values - they should be treated the same as missing fields
|
|
||||||
// for embedding function purposes
|
|
||||||
if (value !== undefined) {
|
|
||||||
yield [[...basePath, key], value];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function isObject(value: unknown): value is Record<string, unknown> {
|
function isObject(value: unknown): value is Record<string, unknown> {
|
||||||
return (
|
return (
|
||||||
typeof value === "object" &&
|
typeof value === "object" &&
|
||||||
@@ -573,146 +459,19 @@ function isObject(value: unknown): value is Record<string, unknown> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function getFieldForPath(schema: Schema, path: string[]): Field | undefined {
|
function valueAtPath(datum: Record<string, unknown>, path: string[]): unknown {
|
||||||
let current: Field | Schema = schema;
|
let current: unknown = datum;
|
||||||
for (const key of path) {
|
for (const key of path) {
|
||||||
if (current instanceof Schema) {
|
if (current == null) {
|
||||||
const field: Field | undefined = current.fields.find(
|
return null;
|
||||||
(f) => f.name === key,
|
}
|
||||||
);
|
if (isObject(current) && (Object.hasOwn(current, key) || key in current)) {
|
||||||
if (field === undefined) {
|
current = current[key];
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
current = field;
|
|
||||||
} else if (current instanceof Field && DataType.isStruct(current.type)) {
|
|
||||||
const struct: Struct = current.type;
|
|
||||||
const field = struct.children.find((f) => f.name === key);
|
|
||||||
if (field === undefined) {
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
current = field;
|
|
||||||
} else {
|
} else {
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (current instanceof Field) {
|
return current;
|
||||||
return current;
|
|
||||||
} else {
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Try to infer which Arrow type to use for a given value.
|
|
||||||
*
|
|
||||||
* May return undefined if the type cannot be inferred.
|
|
||||||
*/
|
|
||||||
function inferType(
|
|
||||||
value: unknown,
|
|
||||||
path: string[],
|
|
||||||
opts: MakeArrowTableOptions,
|
|
||||||
): DataType | undefined {
|
|
||||||
if (typeof value === "bigint") {
|
|
||||||
return new Int64();
|
|
||||||
} else if (typeof value === "number") {
|
|
||||||
// Even if it's an integer, it's safer to assume Float64. Users can
|
|
||||||
// always provide an explicit schema or use BigInt if they mean integer.
|
|
||||||
return new Float64();
|
|
||||||
} else if (typeof value === "string") {
|
|
||||||
if (opts.dictionaryEncodeStrings) {
|
|
||||||
return new Dictionary(new Utf8(), new Int32());
|
|
||||||
} else {
|
|
||||||
return new Utf8();
|
|
||||||
}
|
|
||||||
} else if (typeof value === "boolean") {
|
|
||||||
return new Bool();
|
|
||||||
} else if (value instanceof Buffer) {
|
|
||||||
return new Binary();
|
|
||||||
} else if (ArrayBuffer.isView(value) && !(value instanceof DataView)) {
|
|
||||||
const info = typedArrayToArrowType(value);
|
|
||||||
if (info !== undefined) {
|
|
||||||
const child = new Field("item", info.elementType, true);
|
|
||||||
return new FixedSizeList(info.length, child);
|
|
||||||
}
|
|
||||||
return undefined;
|
|
||||||
} else if (Array.isArray(value)) {
|
|
||||||
if (value.length === 0) {
|
|
||||||
return undefined; // Without any values we can't infer the type
|
|
||||||
}
|
|
||||||
if (path.length === 1 && Object.hasOwn(opts.vectorColumns, path[0])) {
|
|
||||||
const floatType = sanitizeType(opts.vectorColumns[path[0]].type);
|
|
||||||
return new FixedSizeList(
|
|
||||||
value.length,
|
|
||||||
new Field("item", floatType, true),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
const valueType = inferType(value[0], path, opts);
|
|
||||||
if (valueType === undefined) {
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
// Try to automatically detect embedding columns.
|
|
||||||
if (nameSuggestsVectorColumn(path[path.length - 1])) {
|
|
||||||
// Check if value is a Uint8Array for integer vector type determination
|
|
||||||
if (value instanceof Uint8Array) {
|
|
||||||
// For integer vectors, we default to Uint8 (matching Python implementation)
|
|
||||||
const child = new Field("item", new Uint8(), true);
|
|
||||||
return new FixedSizeList(value.length, child);
|
|
||||||
} else {
|
|
||||||
// For float vectors, we default to Float32
|
|
||||||
const child = new Field("item", new Float32(), true);
|
|
||||||
return new FixedSizeList(value.length, child);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
const child = new Field("item", valueType, true);
|
|
||||||
return new List(child);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// TODO: timestamp
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class PathTree<V> {
|
|
||||||
map: Map<string, V | PathTree<V>>;
|
|
||||||
|
|
||||||
constructor(entries?: [string[], V][]) {
|
|
||||||
this.map = new Map();
|
|
||||||
if (entries !== undefined) {
|
|
||||||
for (const [path, value] of entries) {
|
|
||||||
this.set(path, value);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
has(path: string[]): boolean {
|
|
||||||
let ref: PathTree<V> = this;
|
|
||||||
for (const part of path) {
|
|
||||||
if (!(ref instanceof PathTree) || !ref.map.has(part)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
ref = ref.map.get(part) as PathTree<V>;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
get(path: string[]): V | undefined {
|
|
||||||
let ref: PathTree<V> = this;
|
|
||||||
for (const part of path) {
|
|
||||||
if (!(ref instanceof PathTree) || !ref.map.has(part)) {
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
ref = ref.map.get(part) as PathTree<V>;
|
|
||||||
}
|
|
||||||
return ref as V;
|
|
||||||
}
|
|
||||||
set(path: string[], value: V): void {
|
|
||||||
let ref: PathTree<V> = this;
|
|
||||||
for (const part of path.slice(0, path.length - 1)) {
|
|
||||||
if (!ref.map.has(part)) {
|
|
||||||
ref.map.set(part, new PathTree<V>());
|
|
||||||
}
|
|
||||||
ref = ref.map.get(part) as PathTree<V>;
|
|
||||||
}
|
|
||||||
ref.map.set(path[path.length - 1], value);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function transposeData(
|
function transposeData(
|
||||||
@@ -720,37 +479,26 @@ function transposeData(
|
|||||||
field: Field,
|
field: Field,
|
||||||
path: string[] = [],
|
path: string[] = [],
|
||||||
): Vector {
|
): Vector {
|
||||||
|
const valuesPath = [...path, field.name];
|
||||||
|
const values = data.map((datum) => valueAtPath(datum, valuesPath));
|
||||||
if (field.type instanceof Struct) {
|
if (field.type instanceof Struct) {
|
||||||
const childFields = field.type.children;
|
const childFields = field.type.children;
|
||||||
const fullPath = [...path, field.name];
|
|
||||||
const childVectors = childFields.map((child) => {
|
const childVectors = childFields.map((child) => {
|
||||||
return transposeData(data, child, fullPath);
|
return transposeData(data, child, valuesPath);
|
||||||
});
|
});
|
||||||
|
const nullCount = values.filter((value) => value === null).length;
|
||||||
const structData = makeData({
|
const structData = makeData({
|
||||||
type: field.type,
|
type: field.type,
|
||||||
|
length: values.length,
|
||||||
|
nullCount,
|
||||||
|
nullBitmap:
|
||||||
|
nullCount > 0
|
||||||
|
? arrowUtil.packBools(values.map((value) => value !== null))
|
||||||
|
: undefined,
|
||||||
children: childVectors as unknown as ArrowData<DataType>[],
|
children: childVectors as unknown as ArrowData<DataType>[],
|
||||||
});
|
});
|
||||||
return arrowMakeVector(structData);
|
return arrowMakeVector(structData);
|
||||||
} else {
|
} else {
|
||||||
const valuesPath = [...path, field.name];
|
|
||||||
const values = data.map((datum) => {
|
|
||||||
let current: unknown = datum;
|
|
||||||
for (const key of valuesPath) {
|
|
||||||
if (current == null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (
|
|
||||||
isObject(current) &&
|
|
||||||
(Object.hasOwn(current, key) || key in current)
|
|
||||||
) {
|
|
||||||
current = current[key];
|
|
||||||
} else {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return current;
|
|
||||||
});
|
|
||||||
return makeVector(values, field.type, undefined, field.nullable);
|
return makeVector(values, field.type, undefined, field.nullable);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -793,32 +541,6 @@ function makeListVector(lists: unknown[][]): Vector<unknown> {
|
|||||||
return listBuilder.finish().toVector();
|
return listBuilder.finish().toVector();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Map a JS TypedArray instance to the corresponding Arrow element DataType
|
|
||||||
* and its length. Returns undefined if the value is not a recognized TypedArray.
|
|
||||||
*/
|
|
||||||
function typedArrayToArrowType(
|
|
||||||
value: ArrayBufferView,
|
|
||||||
): { elementType: DataType; length: number } | undefined {
|
|
||||||
if (value instanceof Float32Array)
|
|
||||||
return { elementType: new Float32(), length: value.length };
|
|
||||||
if (value instanceof Float64Array)
|
|
||||||
return { elementType: new Float64(), length: value.length };
|
|
||||||
if (value instanceof Uint8Array)
|
|
||||||
return { elementType: new Uint8(), length: value.length };
|
|
||||||
if (value instanceof Uint16Array)
|
|
||||||
return { elementType: new Uint16(), length: value.length };
|
|
||||||
if (value instanceof Uint32Array)
|
|
||||||
return { elementType: new Uint32(), length: value.length };
|
|
||||||
if (value instanceof Int8Array)
|
|
||||||
return { elementType: new Int8(), length: value.length };
|
|
||||||
if (value instanceof Int16Array)
|
|
||||||
return { elementType: new Int16(), length: value.length };
|
|
||||||
if (value instanceof Int32Array)
|
|
||||||
return { elementType: new Int32(), length: value.length };
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Helper function to convert an Array of JS values to an Arrow Vector */
|
/** Helper function to convert an Array of JS values to an Arrow Vector */
|
||||||
function makeVector(
|
function makeVector(
|
||||||
values: unknown[],
|
values: unknown[],
|
||||||
@@ -933,7 +655,7 @@ async function applyEmbeddingsFromMetadata(
|
|||||||
|
|
||||||
for (const functionEntry of functions.values()) {
|
for (const functionEntry of functions.values()) {
|
||||||
const sourceColumn = columns[functionEntry.sourceColumn];
|
const sourceColumn = columns[functionEntry.sourceColumn];
|
||||||
const destColumn = functionEntry.vectorColumn ?? "vector";
|
const destColumn = functionEntry.vectorColumn;
|
||||||
if (sourceColumn === undefined) {
|
if (sourceColumn === undefined) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
`Cannot apply embedding function because the source column '${functionEntry.sourceColumn}' was not present in the data`,
|
`Cannot apply embedding function because the source column '${functionEntry.sourceColumn}' was not present in the data`,
|
||||||
@@ -1385,11 +1107,10 @@ function validateSchemaEmbeddings(
|
|||||||
|
|
||||||
// Check schema metadata for embedding functions
|
// Check schema metadata for embedding functions
|
||||||
if (schema.metadata.has("embedding_functions")) {
|
if (schema.metadata.has("embedding_functions")) {
|
||||||
const embeddings = JSON.parse(
|
const entries = parseEmbeddingMetadata(
|
||||||
schema.metadata.get("embedding_functions")!,
|
schema.metadata.get("embedding_functions")!,
|
||||||
);
|
);
|
||||||
// biome-ignore lint/suspicious/noExplicitAny: we don't know the type of `f`
|
if (entries.some((f) => f.vectorColumn === field.name)) {
|
||||||
if (embeddings.find((f: any) => f["vectorColumn"] === field.name)) {
|
|
||||||
hasEmbeddingFunction = true;
|
hasEmbeddingFunction = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1459,8 +1180,12 @@ export function ensureNestedFieldsExist(
|
|||||||
completeRow[field.name] = row[field.name];
|
completeRow[field.name] = row[field.name];
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Field is missing from the data - set to null
|
// Keep a missing struct valid while filling each of its children with
|
||||||
completeRow[field.name] = null;
|
// null. This is distinct from an explicitly null struct value.
|
||||||
|
completeRow[field.name] =
|
||||||
|
field.type.constructor.name === "Struct"
|
||||||
|
? ensureStructFieldsExist({}, field.type as Struct)
|
||||||
|
: null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1495,8 +1220,12 @@ function ensureStructFieldsExist(
|
|||||||
completeStruct[childField.name] = data[childField.name];
|
completeStruct[childField.name] = data[childField.name];
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Field is missing - set to null
|
// Keep a missing struct valid while filling each of its children with
|
||||||
completeStruct[childField.name] = null;
|
// null. This is distinct from an explicitly null struct value.
|
||||||
|
completeStruct[childField.name] =
|
||||||
|
childField.type.constructor.name === "Struct"
|
||||||
|
? ensureStructFieldsExist({}, childField.type as Struct)
|
||||||
|
: null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,40 @@
|
|||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
// SPDX-FileCopyrightText: Copyright The LanceDB Authors
|
||||||
|
|
||||||
|
import {
|
||||||
|
type DataType,
|
||||||
|
Float32,
|
||||||
|
Float64,
|
||||||
|
Int8,
|
||||||
|
Int16,
|
||||||
|
Int32,
|
||||||
|
Uint8,
|
||||||
|
Uint16,
|
||||||
|
Uint32,
|
||||||
|
} from "apache-arrow";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Map a JS TypedArray instance to the corresponding Arrow element type and
|
||||||
|
* length. Returns undefined when the view is not a supported TypedArray.
|
||||||
|
*/
|
||||||
|
export function typedArrayToArrowType(
|
||||||
|
value: ArrayBufferView,
|
||||||
|
): { elementType: DataType; length: number } | undefined {
|
||||||
|
if (value instanceof Float32Array)
|
||||||
|
return { elementType: new Float32(), length: value.length };
|
||||||
|
if (value instanceof Float64Array)
|
||||||
|
return { elementType: new Float64(), length: value.length };
|
||||||
|
if (value instanceof Uint8Array)
|
||||||
|
return { elementType: new Uint8(), length: value.length };
|
||||||
|
if (value instanceof Uint16Array)
|
||||||
|
return { elementType: new Uint16(), length: value.length };
|
||||||
|
if (value instanceof Uint32Array)
|
||||||
|
return { elementType: new Uint32(), length: value.length };
|
||||||
|
if (value instanceof Int8Array)
|
||||||
|
return { elementType: new Int8(), length: value.length };
|
||||||
|
if (value instanceof Int16Array)
|
||||||
|
return { elementType: new Int16(), length: value.length };
|
||||||
|
if (value instanceof Int32Array)
|
||||||
|
return { elementType: new Int32(), length: value.length };
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
@@ -16,6 +16,12 @@ import {
|
|||||||
makeEmptyTable,
|
makeEmptyTable,
|
||||||
} from "./arrow";
|
} from "./arrow";
|
||||||
import { EmbeddingFunctionConfig, getRegistry } from "./embedding/registry";
|
import { EmbeddingFunctionConfig, getRegistry } from "./embedding/registry";
|
||||||
|
import {
|
||||||
|
MaterializedView,
|
||||||
|
MaterializedViewSelect,
|
||||||
|
normalizeSelect,
|
||||||
|
validateNonNegativeInteger,
|
||||||
|
} from "./materialized_view";
|
||||||
import { Connection as LanceDbConnection } from "./native";
|
import { Connection as LanceDbConnection } from "./native";
|
||||||
import type {
|
import type {
|
||||||
CreateNamespaceResponse,
|
CreateNamespaceResponse,
|
||||||
@@ -25,12 +31,14 @@ import type {
|
|||||||
JobDescription,
|
JobDescription,
|
||||||
JobInfo,
|
JobInfo,
|
||||||
ListNamespacesResponse,
|
ListNamespacesResponse,
|
||||||
|
ListTablesResponse,
|
||||||
} from "./native";
|
} from "./native";
|
||||||
export type {
|
export type {
|
||||||
CreateNamespaceResponse,
|
CreateNamespaceResponse,
|
||||||
DescribeNamespaceResponse,
|
DescribeNamespaceResponse,
|
||||||
DropNamespaceResponse,
|
DropNamespaceResponse,
|
||||||
ListNamespacesResponse,
|
ListNamespacesResponse,
|
||||||
|
ListTablesResponse,
|
||||||
};
|
};
|
||||||
import { sanitizeTable } from "./sanitize";
|
import { sanitizeTable } from "./sanitize";
|
||||||
import { LocalTable, Table } from "./table";
|
import { LocalTable, Table } from "./table";
|
||||||
@@ -128,6 +136,10 @@ export interface OpenTableOptions {
|
|||||||
indexCacheSize?: number;
|
indexCacheSize?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @deprecated Use {@link ListTablesOptions} with {@link Connection.listTables}
|
||||||
|
* instead.
|
||||||
|
*/
|
||||||
export interface TableNamesOptions {
|
export interface TableNamesOptions {
|
||||||
/**
|
/**
|
||||||
* If present, only return names that come lexicographically after the
|
* If present, only return names that come lexicographically after the
|
||||||
@@ -141,6 +153,24 @@ export interface TableNamesOptions {
|
|||||||
limit?: number;
|
limit?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface ListTablesOptions {
|
||||||
|
/**
|
||||||
|
* Token from a previous response, to resume listing where it left off.
|
||||||
|
*
|
||||||
|
* The token is opaque: it carries whatever the database needs to resume, and
|
||||||
|
* callers should not construct or interpret one.
|
||||||
|
*/
|
||||||
|
pageToken?: string;
|
||||||
|
/**
|
||||||
|
* An upper bound on how many tables to return.
|
||||||
|
*
|
||||||
|
* A page may hold fewer than this and still not be the last one, so keep
|
||||||
|
* going while the response carries a page token rather than while pages are
|
||||||
|
* full.
|
||||||
|
*/
|
||||||
|
limit?: number;
|
||||||
|
}
|
||||||
|
|
||||||
export interface ListNamespacesOptions {
|
export interface ListNamespacesOptions {
|
||||||
/** Token from a previous response for pagination. */
|
/** Token from a previous response for pagination. */
|
||||||
pageToken?: string;
|
pageToken?: string;
|
||||||
@@ -225,6 +255,7 @@ export abstract class Connection {
|
|||||||
* @param {Partial<TableNamesOptions>} options - options to control the
|
* @param {Partial<TableNamesOptions>} options - options to control the
|
||||||
* paging / start point (backwards compatibility)
|
* paging / start point (backwards compatibility)
|
||||||
*
|
*
|
||||||
|
* @deprecated Use {@link Connection.listTables} instead.
|
||||||
*/
|
*/
|
||||||
abstract tableNames(options?: Partial<TableNamesOptions>): Promise<string[]>;
|
abstract tableNames(options?: Partial<TableNamesOptions>): Promise<string[]>;
|
||||||
/**
|
/**
|
||||||
@@ -235,18 +266,94 @@ export abstract class Connection {
|
|||||||
* @param {Partial<TableNamesOptions>} options - options to control the
|
* @param {Partial<TableNamesOptions>} options - options to control the
|
||||||
* paging / start point
|
* paging / start point
|
||||||
*
|
*
|
||||||
|
* @deprecated Use {@link Connection.listTables} instead.
|
||||||
*/
|
*/
|
||||||
abstract tableNames(
|
abstract tableNames(
|
||||||
namespacePath?: string[],
|
namespacePath?: string[],
|
||||||
options?: Partial<TableNamesOptions>,
|
options?: Partial<TableNamesOptions>,
|
||||||
): Promise<string[]>;
|
): Promise<string[]>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* List a page of the tables in this database.
|
||||||
|
*
|
||||||
|
* To retrieve the tables after the page, pass the `pageToken` the response
|
||||||
|
* carries back in. A page can be shorter than `limit` without being the last
|
||||||
|
* one, so walk until a response carries no page token:
|
||||||
|
*
|
||||||
|
* ```ts
|
||||||
|
* const names = [];
|
||||||
|
* let pageToken = undefined;
|
||||||
|
* do {
|
||||||
|
* const page = await conn.listTables({ pageToken, limit: 100 });
|
||||||
|
* names.push(...page.tables);
|
||||||
|
* pageToken = page.pageToken;
|
||||||
|
* } while (pageToken);
|
||||||
|
* ```
|
||||||
|
*
|
||||||
|
* @param {Partial<ListTablesOptions>} options - Pagination options
|
||||||
|
* (`pageToken`, `limit`).
|
||||||
|
* @returns {Promise<ListTablesResponse>} A page of table names and an
|
||||||
|
* optional token for the tables after it.
|
||||||
|
*/
|
||||||
|
abstract listTables(
|
||||||
|
options?: Partial<ListTablesOptions>,
|
||||||
|
): Promise<ListTablesResponse>;
|
||||||
|
/**
|
||||||
|
* List a page of the tables in this database.
|
||||||
|
*
|
||||||
|
* @param {string[]} namespacePath - The namespace path to list tables from
|
||||||
|
* (defaults to root namespace)
|
||||||
|
* @param {Partial<ListTablesOptions>} options - Pagination options
|
||||||
|
* (`pageToken`, `limit`).
|
||||||
|
* @returns {Promise<ListTablesResponse>} A page of table names and an
|
||||||
|
* optional token for the tables after it.
|
||||||
|
*/
|
||||||
|
abstract listTables(
|
||||||
|
namespacePath?: string[],
|
||||||
|
options?: Partial<ListTablesOptions>,
|
||||||
|
): Promise<ListTablesResponse>;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Open a table in the database.
|
* Open a table in the database.
|
||||||
* @param {string} name - The name of the table
|
* @param {string} name - The name of the table
|
||||||
* @param {string[]} namespacePath - The namespace path of the table (defaults to root namespace)
|
* @param {string[]} namespacePath - The namespace path of the table (defaults to root namespace)
|
||||||
* @param {Partial<OpenTableOptions>} options - Additional options
|
* @param {Partial<OpenTableOptions>} options - Additional options
|
||||||
*/
|
*/
|
||||||
|
/**
|
||||||
|
* Define a materialized view named `name` over the table `source`.
|
||||||
|
*
|
||||||
|
* The view is created empty, with the query recorded in its schema
|
||||||
|
* metadata; `view.refresh()` computes the rows. The view is a normal
|
||||||
|
* table: it can be queried, indexed and searched, and it appears in
|
||||||
|
* `tableNames`. The source table must have stable row ids (create it with
|
||||||
|
* the `newTableEnableStableRowIds` storage option); they keep the view's
|
||||||
|
* provenance valid across source compactions and cannot be enabled after
|
||||||
|
* a table exists. Local databases only.
|
||||||
|
*/
|
||||||
|
abstract createMaterializedView(
|
||||||
|
name: string,
|
||||||
|
source: string,
|
||||||
|
options?: {
|
||||||
|
select?: MaterializedViewSelect;
|
||||||
|
where?: string;
|
||||||
|
limit?: number;
|
||||||
|
},
|
||||||
|
): Promise<MaterializedView>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Open the materialized view named `name`.
|
||||||
|
*
|
||||||
|
* Rejects a table that exists but is not a materialized view.
|
||||||
|
*/
|
||||||
|
abstract openMaterializedView(name: string): Promise<MaterializedView>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The names of the materialized views in this database.
|
||||||
|
*
|
||||||
|
* Found by reading every table's schema, so this costs an open per table.
|
||||||
|
*/
|
||||||
|
abstract listMaterializedViews(): Promise<string[]>;
|
||||||
|
|
||||||
abstract openTable(
|
abstract openTable(
|
||||||
name: string,
|
name: string,
|
||||||
namespacePath?: string[],
|
namespacePath?: string[],
|
||||||
@@ -531,6 +638,54 @@ export class LocalConnection extends Connection {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async createMaterializedView(
|
||||||
|
name: string,
|
||||||
|
source: string,
|
||||||
|
options?: {
|
||||||
|
select?: MaterializedViewSelect;
|
||||||
|
where?: string;
|
||||||
|
limit?: number;
|
||||||
|
},
|
||||||
|
): Promise<MaterializedView> {
|
||||||
|
validateNonNegativeInteger(options?.limit, "limit");
|
||||||
|
const innerTable = await this.inner.createMaterializedView(
|
||||||
|
name,
|
||||||
|
source,
|
||||||
|
normalizeSelect(options?.select),
|
||||||
|
options?.where,
|
||||||
|
options?.limit,
|
||||||
|
);
|
||||||
|
return new MaterializedView(new LocalTable(innerTable));
|
||||||
|
}
|
||||||
|
|
||||||
|
async openMaterializedView(name: string): Promise<MaterializedView> {
|
||||||
|
const innerTable = await this.inner.openMaterializedView(name);
|
||||||
|
return new MaterializedView(new LocalTable(innerTable));
|
||||||
|
}
|
||||||
|
|
||||||
|
async listMaterializedViews(): Promise<string[]> {
|
||||||
|
return await this.inner.listMaterializedViews();
|
||||||
|
}
|
||||||
|
|
||||||
|
async listTables(
|
||||||
|
namespacePathOrOptions?: string[] | Partial<ListTablesOptions>,
|
||||||
|
options?: Partial<ListTablesOptions>,
|
||||||
|
): Promise<ListTablesResponse> {
|
||||||
|
// Detect if first argument is namespacePath array or options object
|
||||||
|
const namespacePath = Array.isArray(namespacePathOrOptions)
|
||||||
|
? namespacePathOrOptions
|
||||||
|
: undefined;
|
||||||
|
const listTablesOptions = Array.isArray(namespacePathOrOptions)
|
||||||
|
? options
|
||||||
|
: namespacePathOrOptions;
|
||||||
|
|
||||||
|
return this.inner.listTables(
|
||||||
|
namespacePath ?? [],
|
||||||
|
listTablesOptions?.pageToken,
|
||||||
|
listTablesOptions?.limit,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
async openTable(
|
async openTable(
|
||||||
name: string,
|
name: string,
|
||||||
namespacePath?: string[],
|
namespacePath?: string[],
|
||||||
|
|||||||
@@ -4,7 +4,15 @@
|
|||||||
import { Field, Schema } from "../arrow";
|
import { Field, Schema } from "../arrow";
|
||||||
import { sanitizeType } from "../sanitize";
|
import { sanitizeType } from "../sanitize";
|
||||||
import { EmbeddingFunction } from "./embedding_function";
|
import { EmbeddingFunction } from "./embedding_function";
|
||||||
import { EmbeddingFunctionConfig, getRegistry } from "./registry";
|
import {
|
||||||
|
EmbeddingFunctionConfig,
|
||||||
|
EmbeddingFunctionRegistry,
|
||||||
|
getRegistry as getGlobalRegistry,
|
||||||
|
registerBuiltIn,
|
||||||
|
} from "./registry";
|
||||||
|
|
||||||
|
type OpenAIModule = typeof import("./openai");
|
||||||
|
type TransformersModule = typeof import("./transformers");
|
||||||
|
|
||||||
export {
|
export {
|
||||||
FieldOptions,
|
FieldOptions,
|
||||||
@@ -14,7 +22,39 @@ export {
|
|||||||
EmbeddingFunctionConstructor,
|
EmbeddingFunctionConstructor,
|
||||||
} from "./embedding_function";
|
} from "./embedding_function";
|
||||||
|
|
||||||
export * from "./registry";
|
export {
|
||||||
|
EmbeddingFunctionRegistry,
|
||||||
|
parseEmbeddingMetadata,
|
||||||
|
register,
|
||||||
|
} from "./registry";
|
||||||
|
export type {
|
||||||
|
CreateReturnType,
|
||||||
|
EmbeddingFunctionConfig,
|
||||||
|
EmbeddingFunctionCreate,
|
||||||
|
EmbeddingMetadataEntry,
|
||||||
|
ResolvedEmbeddingFunctionConfig,
|
||||||
|
} from "./registry";
|
||||||
|
|
||||||
|
function initializeBuiltInProviders() {
|
||||||
|
const { OpenAIEmbeddingFunction } = require("./openai") as OpenAIModule;
|
||||||
|
const { TransformersEmbeddingFunction } =
|
||||||
|
require("./transformers") as TransformersModule;
|
||||||
|
|
||||||
|
registerBuiltIn("openai", OpenAIEmbeddingFunction);
|
||||||
|
registerBuiltIn("huggingface", TransformersEmbeddingFunction);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the global embedding function registry.
|
||||||
|
*
|
||||||
|
* LanceDB built-in providers are initialized when this public API is first
|
||||||
|
* used, so importing the root package does not change automatic search
|
||||||
|
* selection for tables without embedding metadata.
|
||||||
|
*/
|
||||||
|
export function getRegistry(): EmbeddingFunctionRegistry {
|
||||||
|
initializeBuiltInProviders();
|
||||||
|
return getGlobalRegistry();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a schema with embedding functions.
|
* Create a schema with embedding functions.
|
||||||
|
|||||||
@@ -5,14 +5,13 @@ import type OpenAI from "openai";
|
|||||||
import type { EmbeddingCreateParams } from "openai/resources/index";
|
import type { EmbeddingCreateParams } from "openai/resources/index";
|
||||||
import { Float, Float32 } from "../arrow";
|
import { Float, Float32 } from "../arrow";
|
||||||
import { EmbeddingFunction } from "./embedding_function";
|
import { EmbeddingFunction } from "./embedding_function";
|
||||||
import { register } from "./registry";
|
import { registerBuiltIn } from "./registry";
|
||||||
|
|
||||||
export type OpenAIOptions = {
|
export type OpenAIOptions = {
|
||||||
apiKey: string;
|
apiKey: string;
|
||||||
model: EmbeddingCreateParams["model"];
|
model: EmbeddingCreateParams["model"];
|
||||||
};
|
};
|
||||||
|
|
||||||
@register("openai")
|
|
||||||
export class OpenAIEmbeddingFunction extends EmbeddingFunction<
|
export class OpenAIEmbeddingFunction extends EmbeddingFunction<
|
||||||
string,
|
string,
|
||||||
Partial<OpenAIOptions>
|
Partial<OpenAIOptions>
|
||||||
@@ -100,3 +99,5 @@ export class OpenAIEmbeddingFunction extends EmbeddingFunction<
|
|||||||
return response.data[0].embedding;
|
return response.data[0].embedding;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
registerBuiltIn("openai", OpenAIEmbeddingFunction);
|
||||||
|
|||||||
@@ -7,6 +7,10 @@ import {
|
|||||||
} from "./embedding_function";
|
} from "./embedding_function";
|
||||||
import "reflect-metadata";
|
import "reflect-metadata";
|
||||||
|
|
||||||
|
const builtInFunctionsKey = Symbol.for(
|
||||||
|
"@lancedb/lancedb::embedding-built-in-functions::v1",
|
||||||
|
);
|
||||||
|
|
||||||
export type CreateReturnType<T> = T extends { init: () => Promise<void> }
|
export type CreateReturnType<T> = T extends { init: () => Promise<void> }
|
||||||
? Promise<T>
|
? Promise<T>
|
||||||
: T;
|
: T;
|
||||||
@@ -59,6 +63,15 @@ export class EmbeddingFunctionRegistry {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** @ignore */
|
||||||
|
setBuiltIn<
|
||||||
|
T extends EmbeddingFunctionConstructor = EmbeddingFunctionConstructor,
|
||||||
|
>(name: string, ctor: T): T {
|
||||||
|
this.#functions.set(name, ctor);
|
||||||
|
Reflect.defineMetadata("lancedb::embedding::name", name, ctor);
|
||||||
|
return ctor;
|
||||||
|
}
|
||||||
|
|
||||||
get<T extends EmbeddingFunction<unknown>>(
|
get<T extends EmbeddingFunction<unknown>>(
|
||||||
name: string,
|
name: string,
|
||||||
): EmbeddingFunctionCreate<T> | undefined;
|
): EmbeddingFunctionCreate<T> | undefined;
|
||||||
@@ -96,6 +109,7 @@ export class EmbeddingFunctionRegistry {
|
|||||||
*/
|
*/
|
||||||
reset(this: EmbeddingFunctionRegistry) {
|
reset(this: EmbeddingFunctionRegistry) {
|
||||||
this.#functions.clear();
|
this.#functions.clear();
|
||||||
|
getBuiltInFunctions(this).clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -104,41 +118,29 @@ export class EmbeddingFunctionRegistry {
|
|||||||
async parseFunctions(
|
async parseFunctions(
|
||||||
this: EmbeddingFunctionRegistry,
|
this: EmbeddingFunctionRegistry,
|
||||||
metadata: Map<string, string>,
|
metadata: Map<string, string>,
|
||||||
): Promise<Map<string, EmbeddingFunctionConfig>> {
|
): Promise<Map<string, ResolvedEmbeddingFunctionConfig>> {
|
||||||
if (!metadata.has("embedding_functions")) {
|
if (!metadata.has("embedding_functions")) {
|
||||||
return new Map();
|
return new Map();
|
||||||
} else {
|
|
||||||
type FunctionConfig = {
|
|
||||||
name: string;
|
|
||||||
sourceColumn: string;
|
|
||||||
vectorColumn: string;
|
|
||||||
model: EmbeddingFunction["TOptions"];
|
|
||||||
};
|
|
||||||
|
|
||||||
const functions = <FunctionConfig[]>(
|
|
||||||
JSON.parse(metadata.get("embedding_functions")!)
|
|
||||||
);
|
|
||||||
|
|
||||||
const items: [string, EmbeddingFunctionConfig][] = await Promise.all(
|
|
||||||
functions.map(async (f) => {
|
|
||||||
const fn = this.get(f.name);
|
|
||||||
if (!fn) {
|
|
||||||
throw new Error(`Function "${f.name}" not found in registry`);
|
|
||||||
}
|
|
||||||
const func = await this.get(f.name)!.create(f.model);
|
|
||||||
return [
|
|
||||||
f.name,
|
|
||||||
{
|
|
||||||
sourceColumn: f.sourceColumn,
|
|
||||||
vectorColumn: f.vectorColumn,
|
|
||||||
function: func,
|
|
||||||
},
|
|
||||||
];
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
|
|
||||||
return new Map(items);
|
|
||||||
}
|
}
|
||||||
|
const entries = parseEmbeddingMetadata(
|
||||||
|
metadata.get("embedding_functions")!,
|
||||||
|
);
|
||||||
|
const items = await Promise.all(
|
||||||
|
entries.map(async (f): Promise<ResolvedEmbeddingFunctionConfig> => {
|
||||||
|
const fn = this.get(f.name);
|
||||||
|
if (!fn) {
|
||||||
|
throw new Error(`Function "${f.name}" not found in registry`);
|
||||||
|
}
|
||||||
|
const func = await fn.create(f.model);
|
||||||
|
return {
|
||||||
|
sourceColumn: f.sourceColumn,
|
||||||
|
vectorColumn: f.vectorColumn,
|
||||||
|
function: func,
|
||||||
|
};
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
// Keyed by output column: one function may serve several columns.
|
||||||
|
return new Map(items.map((config) => [config.vectorColumn, config]));
|
||||||
}
|
}
|
||||||
// biome-ignore lint/suspicious/noExplicitAny: <explanation>
|
// biome-ignore lint/suspicious/noExplicitAny: <explanation>
|
||||||
functionToMetadata(conf: EmbeddingFunctionConfig): Record<string, any> {
|
functionToMetadata(conf: EmbeddingFunctionConfig): Record<string, any> {
|
||||||
@@ -195,12 +197,56 @@ export class EmbeddingFunctionRegistry {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const _REGISTRY = new EmbeddingFunctionRegistry();
|
function getBuiltInFunctions(registry: EmbeddingFunctionRegistry): Set<string> {
|
||||||
|
const registryWithBuiltIns = registry as EmbeddingFunctionRegistry & {
|
||||||
|
[key: symbol]: Set<string> | undefined;
|
||||||
|
};
|
||||||
|
let builtInFunctions = registryWithBuiltIns[builtInFunctionsKey];
|
||||||
|
if (builtInFunctions === undefined) {
|
||||||
|
builtInFunctions = new Set<string>();
|
||||||
|
registryWithBuiltIns[builtInFunctionsKey] = builtInFunctions;
|
||||||
|
}
|
||||||
|
return builtInFunctions;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Server bundlers can load the side-effect embedding entry points and the public
|
||||||
|
// embedding API from separate module graphs. Keep their registry shared.
|
||||||
|
const registryKey = Symbol.for(
|
||||||
|
"@lancedb/lancedb::embedding-function-registry::v1",
|
||||||
|
);
|
||||||
|
const registryGlobal = globalThis as typeof globalThis & {
|
||||||
|
[key: symbol]: EmbeddingFunctionRegistry | undefined;
|
||||||
|
};
|
||||||
|
|
||||||
|
function getGlobalRegistry(): EmbeddingFunctionRegistry {
|
||||||
|
const existingRegistry = registryGlobal[registryKey];
|
||||||
|
if (existingRegistry !== undefined) {
|
||||||
|
return existingRegistry;
|
||||||
|
}
|
||||||
|
const registry = new EmbeddingFunctionRegistry();
|
||||||
|
registryGlobal[registryKey] = registry;
|
||||||
|
return registry;
|
||||||
|
}
|
||||||
|
|
||||||
|
const _REGISTRY = getGlobalRegistry();
|
||||||
|
|
||||||
export function register(name?: string) {
|
export function register(name?: string) {
|
||||||
return _REGISTRY.register(name);
|
return _REGISTRY.register(name);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** @ignore */
|
||||||
|
export function registerBuiltIn<
|
||||||
|
T extends EmbeddingFunctionConstructor = EmbeddingFunctionConstructor,
|
||||||
|
>(name: string, ctor: T): T {
|
||||||
|
const builtInFunctions = getBuiltInFunctions(_REGISTRY);
|
||||||
|
if (builtInFunctions.has(name)) {
|
||||||
|
return _REGISTRY.setBuiltIn(name, ctor);
|
||||||
|
}
|
||||||
|
_REGISTRY.register(name)(ctor);
|
||||||
|
builtInFunctions.add(name);
|
||||||
|
return ctor;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Utility function to get the global instance of the registry
|
* Utility function to get the global instance of the registry
|
||||||
* @returns `EmbeddingFunctionRegistry` The global instance of the registry
|
* @returns `EmbeddingFunctionRegistry` The global instance of the registry
|
||||||
@@ -218,3 +264,52 @@ export interface EmbeddingFunctionConfig {
|
|||||||
vectorColumn?: string;
|
vectorColumn?: string;
|
||||||
function: EmbeddingFunction;
|
function: EmbeddingFunction;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** An [EmbeddingFunctionConfig] read back from table metadata, where the
|
||||||
|
* vector column is always recorded. */
|
||||||
|
export type ResolvedEmbeddingFunctionConfig = EmbeddingFunctionConfig & {
|
||||||
|
vectorColumn: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
/** One entry of the `embedding_functions` schema metadata, with the column
|
||||||
|
* keys normalized across the bindings' spellings. */
|
||||||
|
export type EmbeddingMetadataEntry = {
|
||||||
|
name: string;
|
||||||
|
sourceColumn: string;
|
||||||
|
vectorColumn: string;
|
||||||
|
model: EmbeddingFunction["TOptions"];
|
||||||
|
};
|
||||||
|
|
||||||
|
/** The single parser for `embedding_functions` schema metadata: every reader
|
||||||
|
* goes through here, so the wire contract cannot fork between them. */
|
||||||
|
export function parseEmbeddingMetadata(json: string): EmbeddingMetadataEntry[] {
|
||||||
|
// The wire format, honestly: the Python bindings write snake_case keys.
|
||||||
|
type Raw = {
|
||||||
|
name: string;
|
||||||
|
sourceColumn?: string;
|
||||||
|
// biome-ignore lint/style/useNamingConvention: the Python wire spelling
|
||||||
|
source_column?: string;
|
||||||
|
vectorColumn?: string;
|
||||||
|
// biome-ignore lint/style/useNamingConvention: the Python wire spelling
|
||||||
|
vector_column?: string;
|
||||||
|
model: EmbeddingFunction["TOptions"];
|
||||||
|
};
|
||||||
|
const entries = <Raw[]>JSON.parse(json);
|
||||||
|
const seen = new Set<string>();
|
||||||
|
return entries.map((f) => {
|
||||||
|
const sourceColumn = f.sourceColumn ?? f.source_column;
|
||||||
|
const vectorColumn = f.vectorColumn ?? f.vector_column;
|
||||||
|
if (sourceColumn === undefined || vectorColumn === undefined) {
|
||||||
|
throw new Error(
|
||||||
|
`Embedding function "${f.name}" metadata names no source or vector column`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (seen.has(vectorColumn)) {
|
||||||
|
throw new Error(
|
||||||
|
`Multiple embedding configs claim vector column "${vectorColumn}"`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
seen.add(vectorColumn);
|
||||||
|
return { name: f.name, sourceColumn, vectorColumn, model: f.model };
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
|
|
||||||
import { Float, Float32 } from "../arrow";
|
import { Float, Float32 } from "../arrow";
|
||||||
import { EmbeddingFunction } from "./embedding_function";
|
import { EmbeddingFunction } from "./embedding_function";
|
||||||
import { register } from "./registry";
|
import { registerBuiltIn } from "./registry";
|
||||||
|
|
||||||
export type XenovaTransformerOptions = {
|
export type XenovaTransformerOptions = {
|
||||||
/** The wasm compatible model to use */
|
/** The wasm compatible model to use */
|
||||||
@@ -31,7 +31,6 @@ export type XenovaTransformerOptions = {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
@register("huggingface")
|
|
||||||
export class TransformersEmbeddingFunction extends EmbeddingFunction<
|
export class TransformersEmbeddingFunction extends EmbeddingFunction<
|
||||||
string,
|
string,
|
||||||
Partial<XenovaTransformerOptions>
|
Partial<XenovaTransformerOptions>
|
||||||
@@ -158,6 +157,8 @@ export class TransformersEmbeddingFunction extends EmbeddingFunction<
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
registerBuiltIn("huggingface", TransformersEmbeddingFunction);
|
||||||
|
|
||||||
const tensorDiv = (
|
const tensorDiv = (
|
||||||
src: import("@huggingface/transformers").Tensor,
|
src: import("@huggingface/transformers").Tensor,
|
||||||
divBy: number,
|
divBy: number,
|
||||||
|
|||||||
+12
-3
@@ -21,6 +21,11 @@ import type { BaseTokenizer } from "./indices";
|
|||||||
import type { FtsToken } from "./table";
|
import type { FtsToken } from "./table";
|
||||||
|
|
||||||
// Re-export native header provider for use with connectWithHeaderProvider
|
// Re-export native header provider for use with connectWithHeaderProvider
|
||||||
|
export {
|
||||||
|
MaterializedView,
|
||||||
|
MaterializedViewDefinition,
|
||||||
|
MaterializedViewSelect,
|
||||||
|
} from "./materialized_view";
|
||||||
export { JsHeaderProvider as NativeJsHeaderProvider } from "./native.js";
|
export { JsHeaderProvider as NativeJsHeaderProvider } from "./native.js";
|
||||||
|
|
||||||
// OpenTelemetry metrics bridge. Only the high-level entry point is public; the
|
// OpenTelemetry metrics bridge. Only the high-level entry point is public; the
|
||||||
@@ -51,6 +56,7 @@ export {
|
|||||||
AddResult,
|
AddResult,
|
||||||
AddColumnsResult,
|
AddColumnsResult,
|
||||||
RefreshColumnResult,
|
RefreshColumnResult,
|
||||||
|
RefreshMaterializedViewResult,
|
||||||
AlterColumnsResult,
|
AlterColumnsResult,
|
||||||
UpdateFieldMetadataResult,
|
UpdateFieldMetadataResult,
|
||||||
DeleteResult,
|
DeleteResult,
|
||||||
@@ -75,11 +81,13 @@ export {
|
|||||||
Connection,
|
Connection,
|
||||||
CreateTableOptions,
|
CreateTableOptions,
|
||||||
TableNamesOptions,
|
TableNamesOptions,
|
||||||
|
ListTablesOptions,
|
||||||
OpenTableOptions,
|
OpenTableOptions,
|
||||||
ListNamespacesOptions,
|
ListNamespacesOptions,
|
||||||
CreateNamespaceOptions,
|
CreateNamespaceOptions,
|
||||||
DropNamespaceOptions,
|
DropNamespaceOptions,
|
||||||
ListNamespacesResponse,
|
ListNamespacesResponse,
|
||||||
|
ListTablesResponse,
|
||||||
CreateNamespaceResponse,
|
CreateNamespaceResponse,
|
||||||
DropNamespaceResponse,
|
DropNamespaceResponse,
|
||||||
DescribeNamespaceResponse,
|
DescribeNamespaceResponse,
|
||||||
@@ -95,6 +103,7 @@ export {
|
|||||||
} from "./native.js";
|
} from "./native.js";
|
||||||
|
|
||||||
export {
|
export {
|
||||||
|
AutoQuery,
|
||||||
ExecutableQuery,
|
ExecutableQuery,
|
||||||
Query,
|
Query,
|
||||||
QueryBase,
|
QueryBase,
|
||||||
@@ -135,10 +144,10 @@ export {
|
|||||||
BranchColumnChange,
|
BranchColumnChange,
|
||||||
BranchIndexSummary,
|
BranchIndexSummary,
|
||||||
BranchRowCountSummary,
|
BranchRowCountSummary,
|
||||||
MergeBlocker,
|
CherryPickError,
|
||||||
BranchDiff,
|
BranchDiff,
|
||||||
MergePreview,
|
CherryPickPreview,
|
||||||
MergeBranchResult,
|
CherryPickResult,
|
||||||
AddDataOptions,
|
AddDataOptions,
|
||||||
UpdateOptions,
|
UpdateOptions,
|
||||||
OptimizeOptions,
|
OptimizeOptions,
|
||||||
|
|||||||
@@ -841,7 +841,7 @@ export interface IndexOptions {
|
|||||||
* and the same name, then an error will be returned. This is true even if
|
* and the same name, then an error will be returned. This is true even if
|
||||||
* that index is out of date.
|
* that index is out of date.
|
||||||
*
|
*
|
||||||
* The default is true
|
* The default is false
|
||||||
*/
|
*/
|
||||||
replace?: boolean;
|
replace?: boolean;
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,165 @@
|
|||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
// SPDX-FileCopyrightText: Copyright The LanceDB Authors
|
||||||
|
|
||||||
|
import { RefreshMaterializedViewResult } from "./native";
|
||||||
|
import { Table } from "./table";
|
||||||
|
|
||||||
|
/** Schema metadata key holding a materialized view's definition. */
|
||||||
|
export const DEFINITION_META_KEY = "mv.definition";
|
||||||
|
|
||||||
|
/** The query that defines a materialized view. */
|
||||||
|
export interface MaterializedViewDefinition {
|
||||||
|
/** Name of the source table, in the same database as the view. */
|
||||||
|
sourceTable: string;
|
||||||
|
/** `[output column, SQL expression]` pairs, in view schema order. */
|
||||||
|
projections: [string, string][];
|
||||||
|
/** SQL predicate selecting the source rows the view holds. */
|
||||||
|
filter?: string;
|
||||||
|
/** Cap on the number of rows the view holds. */
|
||||||
|
limit?: number;
|
||||||
|
/** Source columns the projections and filter read. */
|
||||||
|
inputs: string[];
|
||||||
|
/** Namespace holding the source table; empty is the root namespace. */
|
||||||
|
sourceNamespace: string[];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The view's columns: column names, `[alias, SQL expression]` pairs, or a
|
||||||
|
* record of the same. A bare name projects itself.
|
||||||
|
*/
|
||||||
|
export type MaterializedViewSelect =
|
||||||
|
| (string | [string, string])[]
|
||||||
|
| Record<string, string>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @internal Reject a numeric option N-API would otherwise silently coerce:
|
||||||
|
* `Infinity` reaches Rust as 0, `1.5` as 1.
|
||||||
|
*/
|
||||||
|
export function validateNonNegativeInteger(
|
||||||
|
value: number | undefined,
|
||||||
|
name: string,
|
||||||
|
): void {
|
||||||
|
if (value !== undefined && !(Number.isSafeInteger(value) && value >= 0)) {
|
||||||
|
throw new Error(`${name} must be a non-negative integer`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @internal Quote a column name as a Lance SQL identifier (backticks). */
|
||||||
|
function quoteIdentifier(name: string): string {
|
||||||
|
return "`" + name.replace(/`/g, "``") + "`";
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @internal Normalize a select argument into `[alias, expression]` pairs.
|
||||||
|
* A bare name projects itself and is quoted, so any valid column name works;
|
||||||
|
* pair and record entries are kept verbatim because their right side is an
|
||||||
|
* expression.
|
||||||
|
*/
|
||||||
|
export function normalizeSelect(
|
||||||
|
select?: MaterializedViewSelect,
|
||||||
|
): [string, string][] | undefined {
|
||||||
|
if (select === undefined) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
if (Array.isArray(select)) {
|
||||||
|
return select.map((item) =>
|
||||||
|
typeof item === "string" ? [item, quoteIdentifier(item)] : item,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return Object.entries(select);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @internal Parse a definition off a table's stored schema metadata. */
|
||||||
|
export function definitionFromMetadata(
|
||||||
|
metadata: Map<string, string>,
|
||||||
|
name: string,
|
||||||
|
): MaterializedViewDefinition {
|
||||||
|
const raw = metadata.get(DEFINITION_META_KEY);
|
||||||
|
if (raw === undefined) {
|
||||||
|
throw new Error(`Table '${name}' is not a materialized view`);
|
||||||
|
}
|
||||||
|
// biome-ignore lint/suspicious/noExplicitAny: raw JSON
|
||||||
|
const value: any = JSON.parse(raw);
|
||||||
|
// "namespaced_select" keeps older readers from resolving the source at root.
|
||||||
|
if (value.kind !== "select" && value.kind !== "namespaced_select") {
|
||||||
|
throw new Error(
|
||||||
|
`materialized view '${name}' is defined by '${value.kind}', which this ` +
|
||||||
|
"version of lancedb cannot refresh",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
const limit = value.limit ?? undefined;
|
||||||
|
// JSON.parse rounds integers past 2^53; every exact u64 parses to a safe
|
||||||
|
// integer and every rounded one does not, so this rejects precisely the
|
||||||
|
// values a number cannot carry.
|
||||||
|
if (limit !== undefined && !Number.isSafeInteger(limit)) {
|
||||||
|
throw new Error(
|
||||||
|
`materialized view '${name}' has a stored limit too large to represent exactly`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
sourceTable: value.source_table,
|
||||||
|
// biome-ignore lint/suspicious/noExplicitAny: raw JSON
|
||||||
|
projections: (value.projections ?? []).map((p: any) => [
|
||||||
|
p.output,
|
||||||
|
p.expression,
|
||||||
|
]),
|
||||||
|
filter: value.filter ?? undefined,
|
||||||
|
limit,
|
||||||
|
inputs: value.inputs ?? [],
|
||||||
|
sourceNamespace: value.source_namespace ?? [],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A handle on a materialized view: its table plus its definition.
|
||||||
|
*
|
||||||
|
* Obtained from {@link Connection#createMaterializedView} or
|
||||||
|
* {@link Connection#openMaterializedView}. The view is a normal table --
|
||||||
|
* queries, indexes and search all apply through {@link MaterializedView#table}
|
||||||
|
* -- whose contents are maintained by {@link MaterializedView#refresh}.
|
||||||
|
*/
|
||||||
|
export class MaterializedView {
|
||||||
|
private readonly inner: Table;
|
||||||
|
|
||||||
|
constructor(table: Table) {
|
||||||
|
this.inner = table;
|
||||||
|
}
|
||||||
|
|
||||||
|
get name(): string {
|
||||||
|
return this.inner.name;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** The view, as the table it is. */
|
||||||
|
table(): Table {
|
||||||
|
return this.inner;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** The query that defines the view, read from its stored schema. */
|
||||||
|
async definition(): Promise<MaterializedViewDefinition> {
|
||||||
|
const schema = await this.inner.schema();
|
||||||
|
return definitionFromMetadata(schema.metadata, this.name);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Recompute the view from its source.
|
||||||
|
*
|
||||||
|
* The refresh is incremental when the source's changes can be reconciled
|
||||||
|
* into the view -- rows added, changed or removed since the last one --
|
||||||
|
* and otherwise rebuilds. `full` forces a rebuild; `sourceVersion`
|
||||||
|
* refreshes to that source version instead of the latest.
|
||||||
|
*
|
||||||
|
* Concurrent refreshes of one view do not duplicate its rows. Two that
|
||||||
|
* plan the same source rows conflict on commit, and the loser throws
|
||||||
|
* rather than writing them a second time.
|
||||||
|
*/
|
||||||
|
async refresh(options?: {
|
||||||
|
full?: boolean;
|
||||||
|
sourceVersion?: number;
|
||||||
|
}): Promise<RefreshMaterializedViewResult> {
|
||||||
|
validateNonNegativeInteger(options?.sourceVersion, "sourceVersion");
|
||||||
|
return await this.inner.refreshMaterializedView(
|
||||||
|
options?.full,
|
||||||
|
options?.sourceVersion,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
+205
-106
@@ -100,6 +100,29 @@ export interface FullTextSearchOptions {
|
|||||||
columns?: string | string[];
|
columns?: string | string[];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function nearestToNative(
|
||||||
|
inner: NativeQuery,
|
||||||
|
vector: Awaited<IntoVector>,
|
||||||
|
): NativeVectorQuery {
|
||||||
|
const raw = Array.isArray(vector) ? null : extractVectorBuffer(vector);
|
||||||
|
if (raw) {
|
||||||
|
return inner.nearestToRaw(raw.data, raw.dtype);
|
||||||
|
}
|
||||||
|
return inner.nearestTo(Float32Array.from(vector as number[]));
|
||||||
|
}
|
||||||
|
|
||||||
|
function addQueryVectorToNative(
|
||||||
|
inner: NativeVectorQuery,
|
||||||
|
vector: Awaited<IntoVector>,
|
||||||
|
) {
|
||||||
|
const raw = Array.isArray(vector) ? null : extractVectorBuffer(vector);
|
||||||
|
if (raw) {
|
||||||
|
inner.addQueryVectorRaw(raw.data, raw.dtype);
|
||||||
|
} else {
|
||||||
|
inner.addQueryVector(Float32Array.from(vector as number[]));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/** Common methods supported by all query types
|
/** Common methods supported by all query types
|
||||||
*
|
*
|
||||||
* @see {@link Query}
|
* @see {@link Query}
|
||||||
@@ -111,13 +134,15 @@ export class QueryBase<
|
|||||||
NativeQueryType extends NativeQuery | NativeVectorQuery | NativeTakeQuery,
|
NativeQueryType extends NativeQuery | NativeVectorQuery | NativeTakeQuery,
|
||||||
> implements AsyncIterable<RecordBatch>
|
> implements AsyncIterable<RecordBatch>
|
||||||
{
|
{
|
||||||
|
protected inner!: NativeQueryType | Promise<NativeQueryType>;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @hidden
|
* @hidden
|
||||||
*/
|
*/
|
||||||
protected constructor(
|
protected constructor(inner?: NativeQueryType | Promise<NativeQueryType>) {
|
||||||
protected inner: NativeQueryType | Promise<NativeQueryType>,
|
if (inner !== undefined) {
|
||||||
) {
|
this.inner = inner;
|
||||||
// intentionally empty
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// call a function on the inner (either a promise or the actual object)
|
// call a function on the inner (either a promise or the actual object)
|
||||||
@@ -135,6 +160,15 @@ export class QueryBase<
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the native query used by the next terminal operation.
|
||||||
|
*
|
||||||
|
* @hidden
|
||||||
|
*/
|
||||||
|
protected async getInner(): Promise<NativeQueryType> {
|
||||||
|
return this.inner;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return only the specified columns.
|
* Return only the specified columns.
|
||||||
*
|
*
|
||||||
@@ -207,16 +241,11 @@ export class QueryBase<
|
|||||||
/**
|
/**
|
||||||
* @hidden
|
* @hidden
|
||||||
*/
|
*/
|
||||||
protected nativeExecute(
|
protected async nativeExecute(
|
||||||
options?: Partial<QueryExecutionOptions>,
|
options?: Partial<QueryExecutionOptions>,
|
||||||
): Promise<NativeBatchIterator> {
|
): Promise<NativeBatchIterator> {
|
||||||
if (this.inner instanceof Promise) {
|
const inner = await this.getInner();
|
||||||
return this.inner.then((inner) =>
|
return inner.execute(options?.maxBatchLength, options?.timeoutMs);
|
||||||
inner.execute(options?.maxBatchLength, options?.timeoutMs),
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
return this.inner.execute(options?.maxBatchLength, options?.timeoutMs);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -245,12 +274,7 @@ export class QueryBase<
|
|||||||
/** Collect the results as an Arrow @see {@link ArrowTable}. */
|
/** Collect the results as an Arrow @see {@link ArrowTable}. */
|
||||||
async toArrow(options?: Partial<QueryExecutionOptions>): Promise<ArrowTable> {
|
async toArrow(options?: Partial<QueryExecutionOptions>): Promise<ArrowTable> {
|
||||||
const batches = [];
|
const batches = [];
|
||||||
let inner;
|
const inner = await this.getInner();
|
||||||
if (this.inner instanceof Promise) {
|
|
||||||
inner = await this.inner;
|
|
||||||
} else {
|
|
||||||
inner = this.inner;
|
|
||||||
}
|
|
||||||
for await (const batch of new RecordBatchIterable(inner, options)) {
|
for await (const batch of new RecordBatchIterable(inner, options)) {
|
||||||
batches.push(batch);
|
batches.push(batch);
|
||||||
}
|
}
|
||||||
@@ -279,11 +303,8 @@ export class QueryBase<
|
|||||||
* @returns A Promise that resolves to a string containing the query execution plan explanation.
|
* @returns A Promise that resolves to a string containing the query execution plan explanation.
|
||||||
*/
|
*/
|
||||||
async explainPlan(verbose = false): Promise<string> {
|
async explainPlan(verbose = false): Promise<string> {
|
||||||
if (this.inner instanceof Promise) {
|
const inner = await this.getInner();
|
||||||
return this.inner.then((inner) => inner.explainPlan(verbose));
|
return inner.explainPlan(verbose);
|
||||||
} else {
|
|
||||||
return this.inner.explainPlan(verbose);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -321,13 +342,8 @@ export class QueryBase<
|
|||||||
distributedMetrics?: AnalyzePlanDistributedMetrics,
|
distributedMetrics?: AnalyzePlanDistributedMetrics,
|
||||||
): Promise<string> {
|
): Promise<string> {
|
||||||
const distributedMetricsMode = distributedMetrics ?? "aggregate";
|
const distributedMetricsMode = distributedMetrics ?? "aggregate";
|
||||||
if (this.inner instanceof Promise) {
|
const inner = await this.getInner();
|
||||||
return this.inner.then((inner) =>
|
return inner.analyzePlan(distributedMetricsMode);
|
||||||
inner.analyzePlan(distributedMetricsMode),
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
return this.inner.analyzePlan(distributedMetricsMode);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -339,12 +355,8 @@ export class QueryBase<
|
|||||||
* @returns An Arrow Schema describing the output columns.
|
* @returns An Arrow Schema describing the output columns.
|
||||||
*/
|
*/
|
||||||
async outputSchema(): Promise<import("./arrow").Schema> {
|
async outputSchema(): Promise<import("./arrow").Schema> {
|
||||||
let schemaBuffer: Buffer;
|
const inner = await this.getInner();
|
||||||
if (this.inner instanceof Promise) {
|
const schemaBuffer = await inner.outputSchema();
|
||||||
schemaBuffer = await this.inner.then((inner) => inner.outputSchema());
|
|
||||||
} else {
|
|
||||||
schemaBuffer = await this.inner.outputSchema();
|
|
||||||
}
|
|
||||||
const schema = tableFromIPC(schemaBuffer).schema;
|
const schema = tableFromIPC(schemaBuffer).schema;
|
||||||
return schema;
|
return schema;
|
||||||
}
|
}
|
||||||
@@ -356,7 +368,7 @@ export class StandardQueryBase<
|
|||||||
extends QueryBase<NativeQueryType>
|
extends QueryBase<NativeQueryType>
|
||||||
implements ExecutableQuery
|
implements ExecutableQuery
|
||||||
{
|
{
|
||||||
constructor(inner: NativeQueryType | Promise<NativeQueryType>) {
|
constructor(inner?: NativeQueryType | Promise<NativeQueryType>) {
|
||||||
super(inner);
|
super(inner);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -510,6 +522,13 @@ export class VectorQuery extends StandardQueryBase<NativeVectorQuery> {
|
|||||||
super(inner);
|
super(inner);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @hidden
|
||||||
|
*/
|
||||||
|
protected doVectorCall(fn: (inner: NativeVectorQuery) => void) {
|
||||||
|
super.doCall(fn);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set the number of partitions to search (probe)
|
* Set the number of partitions to search (probe)
|
||||||
*
|
*
|
||||||
@@ -537,7 +556,7 @@ export class VectorQuery extends StandardQueryBase<NativeVectorQuery> {
|
|||||||
* the minimum and maximum to the same value.
|
* the minimum and maximum to the same value.
|
||||||
*/
|
*/
|
||||||
nprobes(nprobes: number): VectorQuery {
|
nprobes(nprobes: number): VectorQuery {
|
||||||
super.doCall((inner) => inner.nprobes(nprobes));
|
this.doVectorCall((inner) => inner.nprobes(nprobes));
|
||||||
|
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
@@ -551,7 +570,7 @@ export class VectorQuery extends StandardQueryBase<NativeVectorQuery> {
|
|||||||
* but will also increase latency.
|
* but will also increase latency.
|
||||||
*/
|
*/
|
||||||
minimumNprobes(minimumNprobes: number): VectorQuery {
|
minimumNprobes(minimumNprobes: number): VectorQuery {
|
||||||
super.doCall((inner) => inner.minimumNprobes(minimumNprobes));
|
this.doVectorCall((inner) => inner.minimumNprobes(minimumNprobes));
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -565,7 +584,7 @@ export class VectorQuery extends StandardQueryBase<NativeVectorQuery> {
|
|||||||
* potential false negatives.
|
* potential false negatives.
|
||||||
*/
|
*/
|
||||||
maximumNprobes(maximumNprobes: number): VectorQuery {
|
maximumNprobes(maximumNprobes: number): VectorQuery {
|
||||||
super.doCall((inner) => inner.maximumNprobes(maximumNprobes));
|
this.doVectorCall((inner) => inner.maximumNprobes(maximumNprobes));
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -578,7 +597,7 @@ export class VectorQuery extends StandardQueryBase<NativeVectorQuery> {
|
|||||||
* `undefined` means no lower or upper bound.
|
* `undefined` means no lower or upper bound.
|
||||||
*/
|
*/
|
||||||
distanceRange(lowerBound?: number, upperBound?: number): VectorQuery {
|
distanceRange(lowerBound?: number, upperBound?: number): VectorQuery {
|
||||||
super.doCall((inner) => inner.distanceRange(lowerBound, upperBound));
|
this.doVectorCall((inner) => inner.distanceRange(lowerBound, upperBound));
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -592,7 +611,7 @@ export class VectorQuery extends StandardQueryBase<NativeVectorQuery> {
|
|||||||
* also increase the latency of your query. The default value is 1.5*limit.
|
* also increase the latency of your query. The default value is 1.5*limit.
|
||||||
*/
|
*/
|
||||||
ef(ef: number): VectorQuery {
|
ef(ef: number): VectorQuery {
|
||||||
super.doCall((inner) => inner.ef(ef));
|
this.doVectorCall((inner) => inner.ef(ef));
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -606,7 +625,7 @@ export class VectorQuery extends StandardQueryBase<NativeVectorQuery> {
|
|||||||
* whose data type is a fixed-size-list of floats.
|
* whose data type is a fixed-size-list of floats.
|
||||||
*/
|
*/
|
||||||
column(column: string): VectorQuery {
|
column(column: string): VectorQuery {
|
||||||
super.doCall((inner) => inner.column(column));
|
this.doVectorCall((inner) => inner.column(column));
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -627,7 +646,7 @@ export class VectorQuery extends StandardQueryBase<NativeVectorQuery> {
|
|||||||
distanceType(
|
distanceType(
|
||||||
distanceType: Required<IvfPqOptions>["distanceType"],
|
distanceType: Required<IvfPqOptions>["distanceType"],
|
||||||
): VectorQuery {
|
): VectorQuery {
|
||||||
super.doCall((inner) => inner.distanceType(distanceType));
|
this.doVectorCall((inner) => inner.distanceType(distanceType));
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -661,7 +680,7 @@ export class VectorQuery extends StandardQueryBase<NativeVectorQuery> {
|
|||||||
* distance between the query vector and the actual uncompressed vector.
|
* distance between the query vector and the actual uncompressed vector.
|
||||||
*/
|
*/
|
||||||
refineFactor(refineFactor: number): VectorQuery {
|
refineFactor(refineFactor: number): VectorQuery {
|
||||||
super.doCall((inner) => inner.refineFactor(refineFactor));
|
this.doVectorCall((inner) => inner.refineFactor(refineFactor));
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -686,7 +705,7 @@ export class VectorQuery extends StandardQueryBase<NativeVectorQuery> {
|
|||||||
* factor can often help restore some of the results lost by post filtering.
|
* factor can often help restore some of the results lost by post filtering.
|
||||||
*/
|
*/
|
||||||
postfilter(): VectorQuery {
|
postfilter(): VectorQuery {
|
||||||
super.doCall((inner) => inner.postfilter());
|
this.doVectorCall((inner) => inner.postfilter());
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -700,7 +719,7 @@ export class VectorQuery extends StandardQueryBase<NativeVectorQuery> {
|
|||||||
* calculate your recall to select an appropriate value for nprobes.
|
* calculate your recall to select an appropriate value for nprobes.
|
||||||
*/
|
*/
|
||||||
bypassVectorIndex(): VectorQuery {
|
bypassVectorIndex(): VectorQuery {
|
||||||
super.doCall((inner) => inner.bypassVectorIndex());
|
this.doVectorCall((inner) => inner.bypassVectorIndex());
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -708,43 +727,39 @@ export class VectorQuery extends StandardQueryBase<NativeVectorQuery> {
|
|||||||
* Add a query vector to the search
|
* Add a query vector to the search
|
||||||
*
|
*
|
||||||
* This method can be called multiple times to add multiple query vectors
|
* This method can be called multiple times to add multiple query vectors
|
||||||
* to the search. If multiple query vectors are added, then they will be searched
|
* to the search. A column called `query_index` will be added to indicate the index
|
||||||
* in parallel, and the results will be concatenated. A column called `query_index`
|
* of the query vector that produced the result. Flat searches share one table scan
|
||||||
* will be added to indicate the index of the query vector that produced the result.
|
* across the query vectors, avoiding the scan and memory amplification of running
|
||||||
*
|
* multiple queries concurrently. Indexed searches may still perform per-vector
|
||||||
* Performance wise, this is equivalent to running multiple queries concurrently.
|
* index work.
|
||||||
*/
|
*/
|
||||||
addQueryVector(vector: IntoVector): VectorQuery {
|
addQueryVector(vector: IntoVector): VectorQuery {
|
||||||
if (vector instanceof Promise) {
|
if (vector instanceof Promise) {
|
||||||
|
// Observe the promise as soon as it is accepted. The existing native
|
||||||
|
// query may still be pending, and delaying observation until it resolves
|
||||||
|
// can otherwise surface a fast rejection as unhandled.
|
||||||
|
const settledVector = vector.then(
|
||||||
|
(value) => ({ status: "fulfilled" as const, value }),
|
||||||
|
(reason) => ({ status: "rejected" as const, reason }),
|
||||||
|
);
|
||||||
const res = (async () => {
|
const res = (async () => {
|
||||||
try {
|
const inner = await this.getInner();
|
||||||
const v = await vector;
|
const outcome = await settledVector;
|
||||||
// biome-ignore lint/suspicious/noExplicitAny: we need to get the `inner`, but js has no package scoping
|
if (outcome.status === "rejected") {
|
||||||
const value: any = this.addQueryVector(v);
|
throw outcome.reason;
|
||||||
const inner = value.inner as
|
|
||||||
| NativeVectorQuery
|
|
||||||
| Promise<NativeVectorQuery>;
|
|
||||||
return inner;
|
|
||||||
} catch (e) {
|
|
||||||
return Promise.reject(e);
|
|
||||||
}
|
}
|
||||||
|
addQueryVectorToNative(inner, outcome.value);
|
||||||
|
return inner;
|
||||||
})();
|
})();
|
||||||
return new VectorQuery(res);
|
return new VectorQuery(res);
|
||||||
} else {
|
} else {
|
||||||
super.doCall((inner) => {
|
this.doVectorCall((inner) => addQueryVectorToNative(inner, vector));
|
||||||
const raw = Array.isArray(vector) ? null : extractVectorBuffer(vector);
|
|
||||||
if (raw) {
|
|
||||||
inner.addQueryVectorRaw(raw.data, raw.dtype);
|
|
||||||
} else {
|
|
||||||
inner.addQueryVector(Float32Array.from(vector as number[]));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
rerank(reranker: Reranker): VectorQuery {
|
rerank(reranker: Reranker): VectorQuery {
|
||||||
super.doCall((inner) =>
|
this.doVectorCall((inner) =>
|
||||||
inner.rerank(async (args) => {
|
inner.rerank(async (args) => {
|
||||||
const vecResults = await fromBufferToRecordBatch(args.vecResults);
|
const vecResults = await fromBufferToRecordBatch(args.vecResults);
|
||||||
const ftsResults = await fromBufferToRecordBatch(args.ftsResults);
|
const ftsResults = await fromBufferToRecordBatch(args.ftsResults);
|
||||||
@@ -763,6 +778,71 @@ export class VectorQuery extends StandardQueryBase<NativeVectorQuery> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a string query whose vector/FTS routing is resolved against the active
|
||||||
|
* table schema when the query executes.
|
||||||
|
*
|
||||||
|
* @hidden
|
||||||
|
*/
|
||||||
|
export function createAutoQuery(
|
||||||
|
table: NativeTable,
|
||||||
|
query: string,
|
||||||
|
columns: string[] | null,
|
||||||
|
getVector: (metadata: string) => Promise<Awaited<IntoVector>>,
|
||||||
|
): AutoQuery {
|
||||||
|
type RouteSnapshot = {
|
||||||
|
table: NativeTable;
|
||||||
|
embeddingMetadata: string | undefined;
|
||||||
|
};
|
||||||
|
type CachedPreparation = {
|
||||||
|
metadata: string;
|
||||||
|
vector: Promise<Awaited<IntoVector>>;
|
||||||
|
};
|
||||||
|
|
||||||
|
let cachedPreparation: CachedPreparation | undefined;
|
||||||
|
|
||||||
|
const snapshotRoute = async (): Promise<RouteSnapshot> => {
|
||||||
|
const snapshot = await table.querySnapshot();
|
||||||
|
const schema = tableFromIPC(await snapshot.schema()).schema;
|
||||||
|
return {
|
||||||
|
table: snapshot,
|
||||||
|
embeddingMetadata: schema.metadata.get("embedding_functions"),
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const createInner = async (): Promise<NativeQuery | NativeVectorQuery> => {
|
||||||
|
const route = await snapshotRoute();
|
||||||
|
if (route.embeddingMetadata === undefined) {
|
||||||
|
const inner = route.table.query();
|
||||||
|
inner.fullTextSearch({ query, columns });
|
||||||
|
return inner;
|
||||||
|
}
|
||||||
|
|
||||||
|
const metadata = route.embeddingMetadata;
|
||||||
|
if (cachedPreparation?.metadata !== metadata) {
|
||||||
|
cachedPreparation = {
|
||||||
|
metadata,
|
||||||
|
vector: Promise.resolve().then(() => getVector(metadata)),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const preparation = cachedPreparation;
|
||||||
|
let vector: Awaited<IntoVector>;
|
||||||
|
try {
|
||||||
|
vector = await preparation.vector;
|
||||||
|
} catch (error) {
|
||||||
|
if (cachedPreparation === preparation) {
|
||||||
|
cachedPreparation = undefined;
|
||||||
|
}
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
|
||||||
|
return nearestToNative(route.table.query(), vector);
|
||||||
|
};
|
||||||
|
|
||||||
|
return new AutoQuery(createInner);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A query that returns a subset of the rows in the table.
|
* A query that returns a subset of the rows in the table.
|
||||||
*
|
*
|
||||||
@@ -788,6 +868,51 @@ export class TakeQuery extends QueryBase<NativeTakeQuery> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A builder for automatic string searches.
|
||||||
|
*
|
||||||
|
* Automatic search determines whether to use full-text or vector search from
|
||||||
|
* the table revision selected for each execution. This builder exposes the
|
||||||
|
* common operations supported by both query families.
|
||||||
|
*
|
||||||
|
* @hideconstructor
|
||||||
|
*/
|
||||||
|
export class AutoQuery extends StandardQueryBase<
|
||||||
|
NativeQuery | NativeVectorQuery
|
||||||
|
> {
|
||||||
|
private readonly calls: Array<
|
||||||
|
(inner: NativeQuery | NativeVectorQuery) => void
|
||||||
|
> = [];
|
||||||
|
|
||||||
|
/** @hidden */
|
||||||
|
constructor(
|
||||||
|
private readonly createInner: () => Promise<
|
||||||
|
NativeQuery | NativeVectorQuery
|
||||||
|
>,
|
||||||
|
) {
|
||||||
|
super();
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @hidden */
|
||||||
|
protected override doCall(
|
||||||
|
fn: (inner: NativeQuery | NativeVectorQuery) => void,
|
||||||
|
) {
|
||||||
|
this.calls.push(fn);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @hidden */
|
||||||
|
protected override async getInner(): Promise<
|
||||||
|
NativeQuery | NativeVectorQuery
|
||||||
|
> {
|
||||||
|
const calls = [...this.calls];
|
||||||
|
const inner = await this.createInner();
|
||||||
|
for (const call of calls) {
|
||||||
|
call(inner);
|
||||||
|
}
|
||||||
|
return inner;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/** A builder for LanceDB queries.
|
/** A builder for LanceDB queries.
|
||||||
*
|
*
|
||||||
* @see {@link Table#query}, {@link Table#search}
|
* @see {@link Table#query}, {@link Table#search}
|
||||||
@@ -840,45 +965,19 @@ export class Query extends StandardQueryBase<NativeQuery> {
|
|||||||
* a default `limit` of 10 will be used. @see {@link Query#limit}
|
* a default `limit` of 10 will be used. @see {@link Query#limit}
|
||||||
*/
|
*/
|
||||||
nearestTo(vector: IntoVector): VectorQuery {
|
nearestTo(vector: IntoVector): VectorQuery {
|
||||||
const callNearestTo = (
|
const inner = this.inner;
|
||||||
inner: NativeQuery,
|
if (inner instanceof Promise) {
|
||||||
resolved: Float32Array | Float64Array | Uint8Array | number[],
|
const nativeQuery = inner.then(async (resolvedInner) =>
|
||||||
): NativeVectorQuery => {
|
nearestToNative(resolvedInner, await vector),
|
||||||
const raw = Array.isArray(resolved)
|
);
|
||||||
? null
|
|
||||||
: extractVectorBuffer(resolved);
|
|
||||||
if (raw) {
|
|
||||||
return inner.nearestToRaw(raw.data, raw.dtype);
|
|
||||||
}
|
|
||||||
return inner.nearestTo(Float32Array.from(resolved as number[]));
|
|
||||||
};
|
|
||||||
|
|
||||||
if (this.inner instanceof Promise) {
|
|
||||||
const nativeQuery = this.inner.then(async (inner) => {
|
|
||||||
const resolved = vector instanceof Promise ? await vector : vector;
|
|
||||||
return callNearestTo(inner, resolved);
|
|
||||||
});
|
|
||||||
return new VectorQuery(nativeQuery);
|
return new VectorQuery(nativeQuery);
|
||||||
}
|
}
|
||||||
if (vector instanceof Promise) {
|
if (vector instanceof Promise) {
|
||||||
const res = (async () => {
|
return new VectorQuery(
|
||||||
try {
|
vector.then((resolvedVector) => nearestToNative(inner, resolvedVector)),
|
||||||
const v = await vector;
|
);
|
||||||
// biome-ignore lint/suspicious/noExplicitAny: we need to get the `inner`, but js has no package scoping
|
|
||||||
const value: any = this.nearestTo(v);
|
|
||||||
const inner = value.inner as
|
|
||||||
| NativeVectorQuery
|
|
||||||
| Promise<NativeVectorQuery>;
|
|
||||||
return inner;
|
|
||||||
} catch (e) {
|
|
||||||
return Promise.reject(e);
|
|
||||||
}
|
|
||||||
})();
|
|
||||||
return new VectorQuery(res);
|
|
||||||
} else {
|
|
||||||
const vectorQuery = callNearestTo(this.inner, vector);
|
|
||||||
return new VectorQuery(vectorQuery);
|
|
||||||
}
|
}
|
||||||
|
return new VectorQuery(nearestToNative(inner, vector));
|
||||||
}
|
}
|
||||||
|
|
||||||
nearestToText(query: string | FullTextQuery, columns?: string[]): Query {
|
nearestToText(query: string | FullTextQuery, columns?: string[]): Query {
|
||||||
|
|||||||
@@ -94,17 +94,24 @@ export function sanitizeMetadata(
|
|||||||
if (metadataLike === undefined || metadataLike === null) {
|
if (metadataLike === undefined || metadataLike === null) {
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
if (!(metadataLike instanceof Map)) {
|
|
||||||
|
let entries: IterableIterator<[unknown, unknown]>;
|
||||||
|
try {
|
||||||
|
entries = Map.prototype.entries.call(metadataLike);
|
||||||
|
} catch {
|
||||||
throw Error("Expected metadata, if present, to be a Map<string, string>");
|
throw Error("Expected metadata, if present, to be a Map<string, string>");
|
||||||
}
|
}
|
||||||
for (const item of metadataLike) {
|
|
||||||
if (typeof item[0] !== "string" || typeof item[1] !== "string") {
|
const metadata = new Map<string, string>();
|
||||||
|
for (const [key, value] of entries) {
|
||||||
|
if (typeof key !== "string" || typeof value !== "string") {
|
||||||
throw Error(
|
throw Error(
|
||||||
"Expected metadata, if present, to be a Map<string, string> but it had non-string keys or values",
|
"Expected metadata, if present, to be a Map<string, string> but it had non-string keys or values",
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
metadata.set(key, value);
|
||||||
}
|
}
|
||||||
return metadataLike as Map<string, string>;
|
return metadata;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function sanitizeInt(typeLike: object) {
|
export function sanitizeInt(typeLike: object) {
|
||||||
|
|||||||
@@ -0,0 +1,567 @@
|
|||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
// SPDX-FileCopyrightText: Copyright The LanceDB Authors
|
||||||
|
|
||||||
|
import {
|
||||||
|
Binary,
|
||||||
|
Bool,
|
||||||
|
DataType,
|
||||||
|
Dictionary,
|
||||||
|
Field,
|
||||||
|
FixedSizeList,
|
||||||
|
Float32,
|
||||||
|
Float64,
|
||||||
|
Int32,
|
||||||
|
Int64,
|
||||||
|
List,
|
||||||
|
Schema,
|
||||||
|
Struct,
|
||||||
|
Utf8,
|
||||||
|
util as arrowUtil,
|
||||||
|
} from "apache-arrow";
|
||||||
|
import { typedArrayToArrowType } from "./arrow_type";
|
||||||
|
import { sanitizeType } from "./sanitize";
|
||||||
|
|
||||||
|
type InferenceOptions = {
|
||||||
|
dictionaryEncodeStrings: boolean;
|
||||||
|
vectorColumns: Record<string, { type: unknown }>;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Infer the Arrow schema represented by a set of records.
|
||||||
|
*
|
||||||
|
* This is the intentionally small interface to schema inference. The stateful
|
||||||
|
* details of combining partial type evidence are encapsulated below so callers
|
||||||
|
* only need to provide records, an optional schema, and inference options.
|
||||||
|
*/
|
||||||
|
export function inferSchema(
|
||||||
|
data: Array<Record<string, unknown>>,
|
||||||
|
schema: Schema | undefined,
|
||||||
|
options: InferenceOptions,
|
||||||
|
): Schema {
|
||||||
|
return new SchemaInferrer(schema, options).infer(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
class SchemaInferrer {
|
||||||
|
private readonly fields = new FieldTree();
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
private readonly providedSchema: Schema | undefined,
|
||||||
|
private readonly options: InferenceOptions,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
infer(data: Array<Record<string, unknown>>): Schema {
|
||||||
|
for (const [row, record] of data.entries()) {
|
||||||
|
for (const [path, value] of recordPathsAndValues(record)) {
|
||||||
|
this.observe(path, value, row);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.providedSchema === undefined
|
||||||
|
? new Schema(fieldsFromTree(this.fields))
|
||||||
|
: new Schema(matchingFields(this.providedSchema.fields, this.fields));
|
||||||
|
}
|
||||||
|
|
||||||
|
private observe(path: string[], value: unknown, row: number): void {
|
||||||
|
const current = this.fields.get(path);
|
||||||
|
if (current === undefined) {
|
||||||
|
this.addField(path, value, row);
|
||||||
|
} else if (this.providedSchema === undefined) {
|
||||||
|
this.updateInferredField(path, value, row, current);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private addField(path: string[], value: unknown, row: number): void {
|
||||||
|
if (this.providedSchema !== undefined) {
|
||||||
|
this.addSchemaField(this.providedSchema, path, row);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const evidence =
|
||||||
|
this.inferType(value, path) ?? DeferredTypeEvidence.from(value, row);
|
||||||
|
if (evidence === undefined) {
|
||||||
|
throw typeInferenceError(path, row);
|
||||||
|
}
|
||||||
|
|
||||||
|
const conflict = this.fields.set(
|
||||||
|
path,
|
||||||
|
evidence,
|
||||||
|
(existing) =>
|
||||||
|
existing instanceof DeferredTypeEvidence && existing.isOnlyNulls(),
|
||||||
|
);
|
||||||
|
if (conflict !== undefined) {
|
||||||
|
throw branchConflictError(conflict, row, "Struct");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private addSchemaField(schema: Schema, path: string[], row: number): void {
|
||||||
|
const field = fieldAtPath(schema, path);
|
||||||
|
if (field === undefined) {
|
||||||
|
throw new Error(
|
||||||
|
`Found field not in schema: ${path.join(".")} at row ${row}`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const conflict = this.fields.set(path, field.type);
|
||||||
|
if (conflict !== undefined) {
|
||||||
|
throw branchConflictError(conflict, row, "Struct");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private updateInferredField(
|
||||||
|
path: string[],
|
||||||
|
value: unknown,
|
||||||
|
row: number,
|
||||||
|
current: FieldNode,
|
||||||
|
): void {
|
||||||
|
const newType = this.inferType(value, path);
|
||||||
|
const deferred = DeferredTypeEvidence.from(value, row);
|
||||||
|
|
||||||
|
if (current instanceof FieldTree) {
|
||||||
|
if (deferred?.isOnlyNulls()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
throw schemaInferenceError(
|
||||||
|
path,
|
||||||
|
row,
|
||||||
|
"Struct",
|
||||||
|
describeEvidence(newType ?? deferred),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (current instanceof DeferredTypeEvidence) {
|
||||||
|
this.resolveDeferredField(path, row, current, newType, deferred);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (newType !== undefined) {
|
||||||
|
if (!inferredTypesEqual(current, newType)) {
|
||||||
|
throw schemaInferenceError(
|
||||||
|
path,
|
||||||
|
row,
|
||||||
|
describeEvidence(current),
|
||||||
|
describeEvidence(newType),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (deferred === undefined || !deferred.matches(current)) {
|
||||||
|
throw schemaInferenceError(
|
||||||
|
path,
|
||||||
|
row,
|
||||||
|
describeEvidence(current),
|
||||||
|
describeEvidence(deferred),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private resolveDeferredField(
|
||||||
|
path: string[],
|
||||||
|
row: number,
|
||||||
|
current: DeferredTypeEvidence,
|
||||||
|
newType: DataType | undefined,
|
||||||
|
deferred: DeferredTypeEvidence | undefined,
|
||||||
|
): void {
|
||||||
|
if (newType !== undefined) {
|
||||||
|
if (!current.matches(newType)) {
|
||||||
|
throw schemaInferenceError(
|
||||||
|
path,
|
||||||
|
row,
|
||||||
|
current.describe(),
|
||||||
|
describeEvidence(newType),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
this.fields.set(path, newType);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (deferred !== undefined) {
|
||||||
|
this.fields.set(path, current.merge(deferred));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
throw schemaInferenceError(
|
||||||
|
path,
|
||||||
|
row,
|
||||||
|
current.describe(),
|
||||||
|
describeEvidence(newType),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private inferType(value: unknown, path: string[]): DataType | undefined {
|
||||||
|
if (typeof value === "bigint") {
|
||||||
|
return new Int64();
|
||||||
|
}
|
||||||
|
if (typeof value === "number") {
|
||||||
|
return new Float64();
|
||||||
|
}
|
||||||
|
if (typeof value === "string") {
|
||||||
|
return this.options.dictionaryEncodeStrings
|
||||||
|
? new Dictionary(new Utf8(), new Int32())
|
||||||
|
: new Utf8();
|
||||||
|
}
|
||||||
|
if (typeof value === "boolean") {
|
||||||
|
return new Bool();
|
||||||
|
}
|
||||||
|
if (value instanceof Buffer) {
|
||||||
|
return new Binary();
|
||||||
|
}
|
||||||
|
if (ArrayBuffer.isView(value) && !(value instanceof DataView)) {
|
||||||
|
const typedArray = typedArrayToArrowType(value);
|
||||||
|
return typedArray === undefined
|
||||||
|
? undefined
|
||||||
|
: new FixedSizeList(
|
||||||
|
typedArray.length,
|
||||||
|
new Field("item", typedArray.elementType, true),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (!Array.isArray(value) || value.length === 0) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
const configuredVector =
|
||||||
|
path.length === 1 ? this.options.vectorColumns[path[0]] : undefined;
|
||||||
|
if (configuredVector !== undefined) {
|
||||||
|
return new FixedSizeList(
|
||||||
|
value.length,
|
||||||
|
new Field("item", sanitizeType(configuredVector.type), true),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const itemType = this.inferArrayItemType(value, path);
|
||||||
|
if (itemType === undefined) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
return nameSuggestsVectorColumn(path[path.length - 1])
|
||||||
|
? new FixedSizeList(value.length, new Field("item", new Float32(), true))
|
||||||
|
: new List(new Field("item", itemType, true));
|
||||||
|
}
|
||||||
|
|
||||||
|
private inferArrayItemType(
|
||||||
|
values: unknown[],
|
||||||
|
path: string[],
|
||||||
|
): DataType | undefined {
|
||||||
|
let itemType: DataType | undefined;
|
||||||
|
const deferredItems: unknown[] = [];
|
||||||
|
|
||||||
|
for (const value of values) {
|
||||||
|
const candidate = this.inferType(value, path);
|
||||||
|
if (candidate === undefined) {
|
||||||
|
if (!isDeferredValue(value)) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
deferredItems.push(value);
|
||||||
|
} else if (itemType === undefined) {
|
||||||
|
itemType = candidate;
|
||||||
|
} else if (!inferredTypesEqual(itemType, candidate)) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (itemType === undefined) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
return deferredItems.every((value) =>
|
||||||
|
deferredValueMatchesType(value, itemType),
|
||||||
|
)
|
||||||
|
? itemType
|
||||||
|
: undefined;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Nulls and empty/all-null lists that do not determine a type by themselves. */
|
||||||
|
class DeferredTypeEvidence {
|
||||||
|
private constructor(
|
||||||
|
private readonly values: Array<{ value: unknown; row: number }>,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
static from(value: unknown, row: number): DeferredTypeEvidence | undefined {
|
||||||
|
return isDeferredValue(value)
|
||||||
|
? new DeferredTypeEvidence([{ value, row }])
|
||||||
|
: undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
isOnlyNulls(): boolean {
|
||||||
|
return this.values.every(({ value }) => value == null);
|
||||||
|
}
|
||||||
|
|
||||||
|
matches(type: DataType): boolean {
|
||||||
|
return this.values.every(({ value }) =>
|
||||||
|
deferredValueMatchesType(value, type),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
merge(other: DeferredTypeEvidence): DeferredTypeEvidence {
|
||||||
|
return new DeferredTypeEvidence([...this.values, ...other.values]);
|
||||||
|
}
|
||||||
|
|
||||||
|
describe(): string {
|
||||||
|
const list = this.values.find(({ value }) => Array.isArray(value));
|
||||||
|
return list === undefined
|
||||||
|
? "null"
|
||||||
|
: `List[${(list.value as unknown[]).length}]`;
|
||||||
|
}
|
||||||
|
|
||||||
|
firstRow(): number {
|
||||||
|
return this.values[0].row;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type FieldNode = DataType | DeferredTypeEvidence | FieldTree;
|
||||||
|
type LeafNode = Exclude<FieldNode, FieldTree>;
|
||||||
|
type FieldConflict = { path: string[]; value: FieldNode };
|
||||||
|
|
||||||
|
/** Nested field state, kept separate from Arrow's eventual Struct types. */
|
||||||
|
class FieldTree {
|
||||||
|
private readonly children = new Map<string, FieldNode>();
|
||||||
|
|
||||||
|
get(path: string[]): FieldNode | undefined {
|
||||||
|
let current: FieldNode = this;
|
||||||
|
for (const part of path) {
|
||||||
|
if (!(current instanceof FieldTree)) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
const child = current.children.get(part);
|
||||||
|
if (child === undefined) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
current = child;
|
||||||
|
}
|
||||||
|
return current;
|
||||||
|
}
|
||||||
|
|
||||||
|
set(
|
||||||
|
path: string[],
|
||||||
|
value: LeafNode,
|
||||||
|
canReplaceLeaf: (value: LeafNode) => boolean = () => false,
|
||||||
|
): FieldConflict | undefined {
|
||||||
|
let branch: FieldTree = this;
|
||||||
|
for (const [index, part] of path.slice(0, -1).entries()) {
|
||||||
|
const child = branch.children.get(part);
|
||||||
|
if (child === undefined || (isLeaf(child) && canReplaceLeaf(child))) {
|
||||||
|
const nextBranch = new FieldTree();
|
||||||
|
branch.children.set(part, nextBranch);
|
||||||
|
branch = nextBranch;
|
||||||
|
} else if (child instanceof FieldTree) {
|
||||||
|
branch = child;
|
||||||
|
} else {
|
||||||
|
return { path: path.slice(0, index + 1), value: child };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const name = path[path.length - 1];
|
||||||
|
const current = branch.children.get(name);
|
||||||
|
if (current instanceof FieldTree) {
|
||||||
|
return { path, value: current };
|
||||||
|
}
|
||||||
|
branch.children.set(name, value);
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
entries(): IterableIterator<[string, FieldNode]> {
|
||||||
|
return this.children.entries();
|
||||||
|
}
|
||||||
|
|
||||||
|
has(name: string): boolean {
|
||||||
|
return this.children.has(name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function isLeaf(value: FieldNode): value is LeafNode {
|
||||||
|
return !(value instanceof FieldTree);
|
||||||
|
}
|
||||||
|
|
||||||
|
function fieldsFromTree(tree: FieldTree, path: string[] = []): Field[] {
|
||||||
|
const fields: Field[] = [];
|
||||||
|
for (const [name, value] of tree.entries()) {
|
||||||
|
if (value instanceof FieldTree) {
|
||||||
|
fields.push(
|
||||||
|
new Field(
|
||||||
|
name,
|
||||||
|
new Struct(fieldsFromTree(value, [...path, name])),
|
||||||
|
true,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
} else if (value instanceof DeferredTypeEvidence) {
|
||||||
|
throw typeInferenceError([...path, name], value.firstRow());
|
||||||
|
} else {
|
||||||
|
fields.push(new Field(name, value, true));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return fields;
|
||||||
|
}
|
||||||
|
|
||||||
|
function matchingFields(fields: Field[], tree: FieldTree): Field[] {
|
||||||
|
const matches: Field[] = [];
|
||||||
|
for (const field of fields) {
|
||||||
|
if (!tree.has(field.name)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
const value = tree.get([field.name]);
|
||||||
|
if (value instanceof FieldTree) {
|
||||||
|
const struct = field.type as Struct;
|
||||||
|
matches.push(
|
||||||
|
new Field(
|
||||||
|
field.name,
|
||||||
|
new Struct(matchingFields(struct.children, value)),
|
||||||
|
field.nullable,
|
||||||
|
field.metadata,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
matches.push(field);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return matches;
|
||||||
|
}
|
||||||
|
|
||||||
|
function* recordPathsAndValues(
|
||||||
|
record: Record<string, unknown>,
|
||||||
|
path: string[] = [],
|
||||||
|
): Generator<[string[], unknown]> {
|
||||||
|
for (const [name, value] of Object.entries(record)) {
|
||||||
|
if (isRecord(value)) {
|
||||||
|
yield* recordPathsAndValues(value, [...path, name]);
|
||||||
|
} else if (value !== undefined) {
|
||||||
|
yield [[...path, name], value];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function isRecord(value: unknown): value is Record<string, unknown> {
|
||||||
|
return (
|
||||||
|
typeof value === "object" &&
|
||||||
|
value !== null &&
|
||||||
|
!Array.isArray(value) &&
|
||||||
|
!(value instanceof RegExp) &&
|
||||||
|
!(value instanceof Date) &&
|
||||||
|
!(value instanceof Set) &&
|
||||||
|
!(value instanceof Map) &&
|
||||||
|
!(value instanceof Buffer) &&
|
||||||
|
!ArrayBuffer.isView(value)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function fieldAtPath(schema: Schema, path: string[]): Field | undefined {
|
||||||
|
let fields = schema.fields;
|
||||||
|
let field: Field | undefined;
|
||||||
|
for (const [index, name] of path.entries()) {
|
||||||
|
field = fields.find((candidate) => candidate.name === name);
|
||||||
|
if (field === undefined || index === path.length - 1) {
|
||||||
|
return field;
|
||||||
|
}
|
||||||
|
if (!DataType.isStruct(field.type)) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
fields = field.type.children;
|
||||||
|
}
|
||||||
|
return field;
|
||||||
|
}
|
||||||
|
|
||||||
|
function isDeferredValue(value: unknown): boolean {
|
||||||
|
return (
|
||||||
|
value == null || (Array.isArray(value) && value.every(isDeferredValue))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function deferredValueMatchesType(value: unknown, type: DataType): boolean {
|
||||||
|
if (value == null) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (!Array.isArray(value)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (DataType.isList(type)) {
|
||||||
|
return value.every((item) =>
|
||||||
|
deferredValueMatchesType(item, type.valueType),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (DataType.isFixedSizeList(type)) {
|
||||||
|
return (
|
||||||
|
value.length === type.listSize &&
|
||||||
|
value.every((item) => deferredValueMatchesType(item, type.valueType))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
function inferredTypesEqual(current: DataType, candidate: DataType): boolean {
|
||||||
|
if (DataType.isDictionary(current)) {
|
||||||
|
return (
|
||||||
|
DataType.isDictionary(candidate) &&
|
||||||
|
current.isOrdered === candidate.isOrdered &&
|
||||||
|
inferredTypesEqual(current.indices, candidate.indices) &&
|
||||||
|
inferredTypesEqual(current.dictionary, candidate.dictionary)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (DataType.isList(current)) {
|
||||||
|
return (
|
||||||
|
DataType.isList(candidate) &&
|
||||||
|
current.valueField.name === candidate.valueField.name &&
|
||||||
|
current.valueField.nullable === candidate.valueField.nullable &&
|
||||||
|
inferredTypesEqual(current.valueType, candidate.valueType)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (DataType.isFixedSizeList(current)) {
|
||||||
|
return (
|
||||||
|
DataType.isFixedSizeList(candidate) &&
|
||||||
|
current.listSize === candidate.listSize &&
|
||||||
|
current.valueField.name === candidate.valueField.name &&
|
||||||
|
current.valueField.nullable === candidate.valueField.nullable &&
|
||||||
|
inferredTypesEqual(current.valueType, candidate.valueType)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return arrowUtil.compareTypes(current, candidate);
|
||||||
|
}
|
||||||
|
|
||||||
|
function describeEvidence(
|
||||||
|
evidence: DataType | DeferredTypeEvidence | undefined,
|
||||||
|
): string {
|
||||||
|
if (evidence === undefined) {
|
||||||
|
return "an unsupported value";
|
||||||
|
}
|
||||||
|
return evidence instanceof DeferredTypeEvidence
|
||||||
|
? evidence.describe()
|
||||||
|
: evidence.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
function branchConflictError(
|
||||||
|
conflict: FieldConflict,
|
||||||
|
row: number,
|
||||||
|
candidate: string,
|
||||||
|
): Error {
|
||||||
|
return schemaInferenceError(
|
||||||
|
conflict.path,
|
||||||
|
row,
|
||||||
|
conflict.value instanceof FieldTree
|
||||||
|
? "Struct"
|
||||||
|
: describeEvidence(conflict.value),
|
||||||
|
candidate,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function schemaInferenceError(
|
||||||
|
path: string[],
|
||||||
|
row: number,
|
||||||
|
currentType: string,
|
||||||
|
newType: string,
|
||||||
|
): Error {
|
||||||
|
return new Error(
|
||||||
|
`Failed to infer schema for data. Previously inferred type ${currentType} ` +
|
||||||
|
`but found ${newType} for field ${path.join(".")} at row ${row}. ` +
|
||||||
|
"Consider providing an explicit schema.",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function typeInferenceError(path: string[], row: number): Error {
|
||||||
|
return new Error(
|
||||||
|
`Failed to infer data type for field ${path.join(".")} at row ${row}. ` +
|
||||||
|
"Consider providing an explicit schema.",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function nameSuggestsVectorColumn(name: string): boolean {
|
||||||
|
const normalized = name.toLowerCase();
|
||||||
|
return normalized.includes("vector") || normalized.includes("embedding");
|
||||||
|
}
|
||||||
+93
-34
@@ -35,6 +35,7 @@ import {
|
|||||||
Branches as NativeBranches,
|
Branches as NativeBranches,
|
||||||
OptimizeStats,
|
OptimizeStats,
|
||||||
RefreshColumnResult,
|
RefreshColumnResult,
|
||||||
|
RefreshMaterializedViewResult,
|
||||||
TableStatistics,
|
TableStatistics,
|
||||||
Tags,
|
Tags,
|
||||||
UpdateFieldMetadataResult,
|
UpdateFieldMetadataResult,
|
||||||
@@ -42,10 +43,12 @@ import {
|
|||||||
Table as _NativeTable,
|
Table as _NativeTable,
|
||||||
} from "./native";
|
} from "./native";
|
||||||
import {
|
import {
|
||||||
|
AutoQuery,
|
||||||
FullTextQuery,
|
FullTextQuery,
|
||||||
Query,
|
Query,
|
||||||
TakeQuery,
|
TakeQuery,
|
||||||
VectorQuery,
|
VectorQuery,
|
||||||
|
createAutoQuery,
|
||||||
instanceOfFullTextQuery,
|
instanceOfFullTextQuery,
|
||||||
} from "./query";
|
} from "./query";
|
||||||
import { sanitizeType } from "./sanitize";
|
import { sanitizeType } from "./sanitize";
|
||||||
@@ -522,7 +525,7 @@ export abstract class Table {
|
|||||||
query: string | IntoVector | MultiVector | FullTextQuery,
|
query: string | IntoVector | MultiVector | FullTextQuery,
|
||||||
queryType?: string,
|
queryType?: string,
|
||||||
ftsColumns?: string | string[],
|
ftsColumns?: string | string[],
|
||||||
): VectorQuery | Query;
|
): VectorQuery | Query | AutoQuery;
|
||||||
/**
|
/**
|
||||||
* Search the table with a given query vector.
|
* Search the table with a given query vector.
|
||||||
*
|
*
|
||||||
@@ -602,6 +605,18 @@ export abstract class Table {
|
|||||||
*/
|
*/
|
||||||
abstract refreshColumnAsync(column: string): Promise<Job>;
|
abstract refreshColumnAsync(column: string): Promise<Job>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Recompute this table's contents from its materialized-view definition.
|
||||||
|
*
|
||||||
|
* Plumbing for {@link MaterializedView.refresh}, which is the way to call
|
||||||
|
* it: rejects tables that carry no view definition. Local tables only.
|
||||||
|
* @ignore
|
||||||
|
*/
|
||||||
|
abstract refreshMaterializedView(
|
||||||
|
full?: boolean,
|
||||||
|
sourceVersion?: number,
|
||||||
|
): Promise<RefreshMaterializedViewResult>;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Alter the name or nullability of columns.
|
* Alter the name or nullability of columns.
|
||||||
* @param {ColumnAlteration[]} columnAlterations One or more alterations to
|
* @param {ColumnAlteration[]} columnAlterations One or more alterations to
|
||||||
@@ -615,6 +630,18 @@ export abstract class Table {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Update per-field (column) metadata.
|
* Update per-field (column) metadata.
|
||||||
|
*
|
||||||
|
* The following keys are treated specially, by convention, and should be
|
||||||
|
* used when appropriate:
|
||||||
|
*
|
||||||
|
* - `lancedb:description`: for a human-readable description of a field.
|
||||||
|
* - `lancedb:tag:<name>`: for a user-defined key-value tag, where the suffix
|
||||||
|
* names the tag category; e.g. `lancedb:tag:model: "clip"`.
|
||||||
|
* - `lancedb:logical-column`: for a column grouping; e.g. `feature_v1` and
|
||||||
|
* `feature_v2` might be in the same logical column.
|
||||||
|
* - `lancedb:status`: for status options (`production`, `candidate`,
|
||||||
|
* `deprecated`, `archived`) to designate the current life cycle state of
|
||||||
|
* this column.
|
||||||
* @param {FieldMetadataUpdate[]} updates One or more per-field updates. Each
|
* @param {FieldMetadataUpdate[]} updates One or more per-field updates. Each
|
||||||
* update's metadata is merged into the field's existing metadata by default;
|
* update's metadata is merged into the field's existing metadata by default;
|
||||||
* a value of `null` deletes that key, and `replace: true` swaps the whole map.
|
* a value of `null` deletes that key, and `replace: true` swaps the whole map.
|
||||||
@@ -892,6 +919,16 @@ export abstract class Table {
|
|||||||
/** Return the table as an arrow table */
|
/** Return the table as an arrow table */
|
||||||
abstract toArrow(): Promise<ArrowTable>;
|
abstract toArrow(): Promise<ArrowTable>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a {@link MergeInsertBuilder}, which combines new data with the
|
||||||
|
* existing table in a single transaction — inserting, updating and deleting
|
||||||
|
* rows depending on how they match.
|
||||||
|
*
|
||||||
|
* @param on - The column, or columns, to match source rows against target
|
||||||
|
* rows on. Typically a key or id column. Several columns match on the
|
||||||
|
* composite key: a source row updates a target row only when it agrees on
|
||||||
|
* every one of them.
|
||||||
|
*/
|
||||||
abstract mergeInsert(on: string | string[]): MergeInsertBuilder;
|
abstract mergeInsert(on: string | string[]): MergeInsertBuilder;
|
||||||
|
|
||||||
/** List all the stats of a specified index
|
/** List all the stats of a specified index
|
||||||
@@ -962,10 +999,11 @@ export class LocalTable extends Table {
|
|||||||
return this.inner.display();
|
return this.inner.display();
|
||||||
}
|
}
|
||||||
|
|
||||||
private async getEmbeddingFunctions(): Promise<
|
private async getEmbeddingFunctions(
|
||||||
Map<string, EmbeddingFunctionConfig>
|
inner: _NativeTable = this.inner,
|
||||||
> {
|
): Promise<Map<string, EmbeddingFunctionConfig>> {
|
||||||
const schema = await this.schema();
|
const schemaBuf = await inner.schema();
|
||||||
|
const schema = tableFromIPC(schemaBuf).schema;
|
||||||
const registry = getRegistry();
|
const registry = getRegistry();
|
||||||
return registry.parseFunctions(schema.metadata);
|
return registry.parseFunctions(schema.metadata);
|
||||||
}
|
}
|
||||||
@@ -1147,7 +1185,7 @@ export class LocalTable extends Table {
|
|||||||
query: string | IntoVector | MultiVector | FullTextQuery,
|
query: string | IntoVector | MultiVector | FullTextQuery,
|
||||||
queryType: string = "auto",
|
queryType: string = "auto",
|
||||||
ftsColumns?: string | string[],
|
ftsColumns?: string | string[],
|
||||||
): VectorQuery | Query {
|
): VectorQuery | Query | AutoQuery {
|
||||||
if (typeof query !== "string" && !instanceOfFullTextQuery(query)) {
|
if (typeof query !== "string" && !instanceOfFullTextQuery(query)) {
|
||||||
if (queryType === "fts") {
|
if (queryType === "fts") {
|
||||||
throw new Error("Cannot perform full text search on a vector query");
|
throw new Error("Cannot perform full text search on a vector query");
|
||||||
@@ -1162,14 +1200,28 @@ export class LocalTable extends Table {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// The query type is auto or vector
|
if (queryType === "auto") {
|
||||||
// fall back to full text search if no embedding functions are defined and the query is a string
|
if (instanceOfFullTextQuery(query)) {
|
||||||
if (
|
return this.query().fullTextSearch(query, {
|
||||||
queryType === "auto" &&
|
columns: ftsColumns,
|
||||||
(getRegistry().length() === 0 || instanceOfFullTextQuery(query))
|
});
|
||||||
) {
|
}
|
||||||
return this.query().fullTextSearch(query, {
|
|
||||||
columns: ftsColumns,
|
const columns =
|
||||||
|
typeof ftsColumns === "string" ? [ftsColumns] : (ftsColumns ?? null);
|
||||||
|
return createAutoQuery(this.inner, query, columns, async (metadata) => {
|
||||||
|
const functions = await getRegistry().parseFunctions(
|
||||||
|
new Map([["embedding_functions", metadata]]),
|
||||||
|
);
|
||||||
|
// TODO: Support multiple embedding functions
|
||||||
|
const embeddingFunc: EmbeddingFunctionConfig | undefined = functions
|
||||||
|
.values()
|
||||||
|
.next().value;
|
||||||
|
// The route only calls this callback when embedding metadata exists.
|
||||||
|
// parseFunctions either yields a provider or reports malformed metadata.
|
||||||
|
if (!embeddingFunc)
|
||||||
|
throw new Error("Invalid embedding function metadata");
|
||||||
|
return await embeddingFunc.function.computeQueryEmbeddings(query);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1264,6 +1316,13 @@ export class LocalTable extends Table {
|
|||||||
return await this.inner.refreshColumnAsync(column);
|
return await this.inner.refreshColumnAsync(column);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async refreshMaterializedView(
|
||||||
|
full?: boolean,
|
||||||
|
sourceVersion?: number,
|
||||||
|
): Promise<RefreshMaterializedViewResult> {
|
||||||
|
return await this.inner.refreshMaterializedView(full, sourceVersion);
|
||||||
|
}
|
||||||
|
|
||||||
async alterColumns(
|
async alterColumns(
|
||||||
columnAlterations: ColumnAlteration[],
|
columnAlterations: ColumnAlteration[],
|
||||||
): Promise<AlterColumnsResult> {
|
): Promise<AlterColumnsResult> {
|
||||||
@@ -1518,7 +1577,8 @@ export interface FieldMetadataUpdate {
|
|||||||
path: string;
|
path: string;
|
||||||
/**
|
/**
|
||||||
* Metadata key/value pairs. Merged into the field's existing metadata by
|
* Metadata key/value pairs. Merged into the field's existing metadata by
|
||||||
* default; a value of `null` deletes that key.
|
* default; a value of `null` deletes that key. See
|
||||||
|
* {@link Table.updateFieldMetadata} for the conventional `lancedb:*` keys.
|
||||||
*/
|
*/
|
||||||
metadata: Record<string, string | null>;
|
metadata: Record<string, string | null>;
|
||||||
/** If true, replace the field's entire metadata map instead of merging. */
|
/** If true, replace the field's entire metadata map instead of merging. */
|
||||||
@@ -1557,8 +1617,8 @@ export interface BranchRowCountSummary {
|
|||||||
deltaAvailable: boolean;
|
deltaAvailable: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** A reason why a branch cannot currently be merged. */
|
/** A reason why a cherry-pick cannot currently land. */
|
||||||
export interface MergeBlocker {
|
export interface CherryPickError {
|
||||||
code: string;
|
code: string;
|
||||||
message: string;
|
message: string;
|
||||||
}
|
}
|
||||||
@@ -1578,20 +1638,19 @@ export interface BranchDiff {
|
|||||||
changedColumns: BranchColumnChange[];
|
changedColumns: BranchColumnChange[];
|
||||||
addedIndexes: BranchIndexSummary[];
|
addedIndexes: BranchIndexSummary[];
|
||||||
removedIndexes: BranchIndexSummary[];
|
removedIndexes: BranchIndexSummary[];
|
||||||
mergeable: boolean;
|
errors: CherryPickError[];
|
||||||
mergeBlockers: MergeBlocker[];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Changes that would be, or were, promoted by a branch merge. */
|
/** Changes that would be, or were, promoted by a cherry-pick. */
|
||||||
export interface MergePreview {
|
export interface CherryPickPreview {
|
||||||
promotedColumns: string[];
|
promotedColumns: string[];
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Result of previewing or attempting a branch merge. */
|
/** Result of previewing or attempting a cherry-pick. */
|
||||||
export interface MergeBranchResult {
|
export interface CherryPickResult {
|
||||||
status: "ready" | "rejected" | "notImplemented" | "merged" | "unknown";
|
status: "ready" | "failed" | "notImplemented" | "cherryPicked" | "unknown";
|
||||||
diff: BranchDiff;
|
diff: BranchDiff;
|
||||||
preview: MergePreview;
|
preview: CherryPickPreview;
|
||||||
mainVersionAfter?: number;
|
mainVersionAfter?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1654,21 +1713,21 @@ export class Branches {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Merge a branch into main.
|
* Cherry-pick a branch onto main.
|
||||||
*
|
*
|
||||||
* Set `dryRun` to `true` to preview the merge. A rejected merge resolves
|
* Set `dryRun` to `true` to preview. A failed cherry-pick resolves
|
||||||
* with `status: "rejected"` instead of throwing.
|
* with `status: "failed"` instead of throwing.
|
||||||
*
|
*
|
||||||
* @param fromBranch Branch to merge from.
|
* @param fromBranch Branch to cherry-pick from.
|
||||||
* @param dryRun When true, only preview the merge. Defaults to false.
|
* @param dryRun When true, only preview. Defaults to false.
|
||||||
*/
|
*/
|
||||||
async merge(
|
async cherryPick(
|
||||||
fromBranch: string,
|
fromBranch: string,
|
||||||
dryRun: boolean = false,
|
dryRun: boolean = false,
|
||||||
): Promise<MergeBranchResult> {
|
): Promise<CherryPickResult> {
|
||||||
return (await this.#inner.merge(
|
return (await this.#inner.cherryPick(
|
||||||
fromBranch,
|
fromBranch,
|
||||||
dryRun,
|
dryRun,
|
||||||
)) as unknown as MergeBranchResult;
|
)) as unknown as CherryPickResult;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@lancedb/lancedb-darwin-arm64",
|
"name": "@lancedb/lancedb-darwin-arm64",
|
||||||
"version": "0.38.0-beta.2",
|
"version": "0.39.0-beta.1",
|
||||||
"os": ["darwin"],
|
"os": ["darwin"],
|
||||||
"cpu": ["arm64"],
|
"cpu": ["arm64"],
|
||||||
"main": "lancedb.darwin-arm64.node",
|
"main": "lancedb.darwin-arm64.node",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@lancedb/lancedb-linux-arm64-gnu",
|
"name": "@lancedb/lancedb-linux-arm64-gnu",
|
||||||
"version": "0.38.0-beta.2",
|
"version": "0.39.0-beta.1",
|
||||||
"os": ["linux"],
|
"os": ["linux"],
|
||||||
"cpu": ["arm64"],
|
"cpu": ["arm64"],
|
||||||
"main": "lancedb.linux-arm64-gnu.node",
|
"main": "lancedb.linux-arm64-gnu.node",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@lancedb/lancedb-linux-arm64-musl",
|
"name": "@lancedb/lancedb-linux-arm64-musl",
|
||||||
"version": "0.38.0-beta.2",
|
"version": "0.39.0-beta.1",
|
||||||
"os": ["linux"],
|
"os": ["linux"],
|
||||||
"cpu": ["arm64"],
|
"cpu": ["arm64"],
|
||||||
"main": "lancedb.linux-arm64-musl.node",
|
"main": "lancedb.linux-arm64-musl.node",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@lancedb/lancedb-linux-x64-gnu",
|
"name": "@lancedb/lancedb-linux-x64-gnu",
|
||||||
"version": "0.38.0-beta.2",
|
"version": "0.39.0-beta.1",
|
||||||
"os": ["linux"],
|
"os": ["linux"],
|
||||||
"cpu": ["x64"],
|
"cpu": ["x64"],
|
||||||
"main": "lancedb.linux-x64-gnu.node",
|
"main": "lancedb.linux-x64-gnu.node",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@lancedb/lancedb-linux-x64-musl",
|
"name": "@lancedb/lancedb-linux-x64-musl",
|
||||||
"version": "0.38.0-beta.2",
|
"version": "0.39.0-beta.1",
|
||||||
"os": ["linux"],
|
"os": ["linux"],
|
||||||
"cpu": ["x64"],
|
"cpu": ["x64"],
|
||||||
"main": "lancedb.linux-x64-musl.node",
|
"main": "lancedb.linux-x64-musl.node",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@lancedb/lancedb-win32-arm64-msvc",
|
"name": "@lancedb/lancedb-win32-arm64-msvc",
|
||||||
"version": "0.38.0-beta.2",
|
"version": "0.39.0-beta.1",
|
||||||
"os": [
|
"os": [
|
||||||
"win32"
|
"win32"
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@lancedb/lancedb-win32-x64-msvc",
|
"name": "@lancedb/lancedb-win32-x64-msvc",
|
||||||
"version": "0.38.0-beta.2",
|
"version": "0.39.0-beta.1",
|
||||||
"os": ["win32"],
|
"os": ["win32"],
|
||||||
"cpu": ["x64"],
|
"cpu": ["x64"],
|
||||||
"main": "lancedb.win32-x64-msvc.node",
|
"main": "lancedb.win32-x64-msvc.node",
|
||||||
|
|||||||
Generated
-11106
File diff suppressed because it is too large
Load Diff
+5
-5
@@ -11,7 +11,7 @@
|
|||||||
"ann"
|
"ann"
|
||||||
],
|
],
|
||||||
"private": false,
|
"private": false,
|
||||||
"version": "0.38.0-beta.2",
|
"version": "0.39.0-beta.1",
|
||||||
"main": "dist/index.js",
|
"main": "dist/index.js",
|
||||||
"exports": {
|
"exports": {
|
||||||
".": "./dist/index.js",
|
".": "./dist/index.js",
|
||||||
@@ -44,7 +44,7 @@
|
|||||||
"@biomejs/biome": "^1.7.3",
|
"@biomejs/biome": "^1.7.3",
|
||||||
"@jest/globals": "^29.7.0",
|
"@jest/globals": "^29.7.0",
|
||||||
"@napi-rs/cli": "3.7.0",
|
"@napi-rs/cli": "3.7.0",
|
||||||
"@opentelemetry/sdk-metrics": "^1.30.0",
|
"@opentelemetry/sdk-metrics": "^2.10.0",
|
||||||
"@types/axios": "^0.14.0",
|
"@types/axios": "^0.14.0",
|
||||||
"@types/jest": "^29.1.2",
|
"@types/jest": "^29.1.2",
|
||||||
"@types/node": "22.7.4",
|
"@types/node": "22.7.4",
|
||||||
@@ -56,7 +56,7 @@
|
|||||||
"eslint": "^8.57.0",
|
"eslint": "^8.57.0",
|
||||||
"jest": "^29.7.0",
|
"jest": "^29.7.0",
|
||||||
"shx": "^0.3.4",
|
"shx": "^0.3.4",
|
||||||
"tmp": "^0.2.3",
|
"tmp": "^0.2.7",
|
||||||
"ts-jest": "^29.1.2",
|
"ts-jest": "^29.1.2",
|
||||||
"typedoc": "0.26.4",
|
"typedoc": "0.26.4",
|
||||||
"typedoc-plugin-markdown": "4.2.1",
|
"typedoc-plugin-markdown": "4.2.1",
|
||||||
@@ -67,7 +67,7 @@
|
|||||||
"timeout": "3m"
|
"timeout": "3m"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">= 18"
|
"node": ">= 22"
|
||||||
},
|
},
|
||||||
"packageManager": "pnpm@11.1.1",
|
"packageManager": "pnpm@11.1.1",
|
||||||
"cpu": ["x64", "arm64"],
|
"cpu": ["x64", "arm64"],
|
||||||
@@ -101,7 +101,7 @@
|
|||||||
"openai": "4.29.2"
|
"openai": "4.29.2"
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"@types/node": ">=18",
|
"@types/node": ">=22",
|
||||||
"apache-arrow": ">=15.0.0 <=18.1.0"
|
"apache-arrow": ">=15.0.0 <=18.1.0"
|
||||||
},
|
},
|
||||||
"peerDependenciesMeta": {
|
"peerDependenciesMeta": {
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user