mirror of
https://github.com/lancedb/lancedb.git
synced 2026-06-03 04:10:41 +00:00
Compare commits
4 Commits
yang/fix-q
...
python-v0.
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3fbfbfb702 | ||
|
|
64978c8419 | ||
|
|
241420239b | ||
|
|
9d67ea2bb0 |
@@ -1,7 +0,0 @@
|
|||||||
# Agent Skills
|
|
||||||
|
|
||||||
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.
|
|
||||||
|
|
||||||
Codex discovers skills from `.agents/skills` in the current working directory and parent directories.
|
|
||||||
@@ -1,98 +0,0 @@
|
|||||||
---
|
|
||||||
name: lancedb-update-lance-dependency
|
|
||||||
description: Update LanceDB to a specific Lance release or tag. Use when bumping Lance dependencies in the lancedb repository, including Rust workspace Lance crates, Java lance-core, validation, branch creation, commit, push, and PR creation when requested.
|
|
||||||
---
|
|
||||||
|
|
||||||
# LanceDB Update Lance Dependency
|
|
||||||
|
|
||||||
## Scope
|
|
||||||
|
|
||||||
Use this skill in the `lancedb/lancedb` repository when updating the Lance dependency to a specific Lance version or tag.
|
|
||||||
|
|
||||||
Inputs can be a version (`7.2.0-beta.1`), a tag (`v7.2.0-beta.1`), a tag ref (`refs/tags/v7.2.0-beta.1`), or `latest`.
|
|
||||||
|
|
||||||
## Workflow
|
|
||||||
|
|
||||||
1. Confirm the worktree status with `git status --short`.
|
|
||||||
2. Resolve the target Lance version:
|
|
||||||
|
|
||||||
- If the input is `latest`, empty, or omitted, run:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
python3 ci/check_lance_release.py
|
|
||||||
```
|
|
||||||
|
|
||||||
Parse the JSON output. If `needs_update` is not `true`, stop without creating a PR. Otherwise use `latest_tag`.
|
|
||||||
|
|
||||||
- If the input is explicit, use it directly.
|
|
||||||
|
|
||||||
3. Compute update metadata without changing files:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
python3 ci/update_lance_dependency.py "$TAG_OR_VERSION" --metadata-only
|
|
||||||
```
|
|
||||||
|
|
||||||
Before making changes, check for an existing open PR with the emitted `pr_title`:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
gh pr list --search "\"$PR_TITLE\" in:title" --state open --limit 1 --json number,url,title
|
|
||||||
```
|
|
||||||
|
|
||||||
If a matching open PR exists, stop and report it instead of creating a duplicate.
|
|
||||||
|
|
||||||
4. Run the deterministic update entrypoint:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
python3 ci/update_lance_dependency.py "$TAG_OR_VERSION"
|
|
||||||
```
|
|
||||||
|
|
||||||
This updates the Rust workspace Lance dependencies through `ci/set_lance_version.py`, updates `java/pom.xml`, refreshes Cargo metadata, and prints JSON metadata containing `branch_name`, `commit_message`, and `pr_title`.
|
|
||||||
|
|
||||||
5. Run validation:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
cargo clippy --quiet --workspace --tests --all-features -- -D warnings
|
|
||||||
cargo fmt --all --quiet
|
|
||||||
```
|
|
||||||
|
|
||||||
Fix real diagnostics and rerun clippy until it succeeds. Do not skip warnings.
|
|
||||||
|
|
||||||
6. Inspect `git status --short` and `git diff` to ensure only the Lance dependency update and required compatibility fixes are present.
|
|
||||||
|
|
||||||
7. If the task only asks to prepare local changes, stop here and report the changed files and validation result.
|
|
||||||
|
|
||||||
8. If the task asks to publish the update, create a branch using the printed `branch_name`, stage all relevant files, and commit using the printed `commit_message`. Do not amend or rewrite existing commits.
|
|
||||||
|
|
||||||
9. Push to `origin`. Before creating the PR, check that the current token has push permission:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
gh api repos/lancedb/lancedb --jq .permissions.push
|
|
||||||
```
|
|
||||||
|
|
||||||
If the remote branch already exists for the same generated branch name, delete the remote ref with `gh api -X DELETE repos/lancedb/lancedb/git/refs/heads/$BRANCH_NAME`, then push. Do not force-push.
|
|
||||||
|
|
||||||
10. Create a PR targeting `main` with the printed `pr_title`. If there is no PR template, keep the body to two or three concise sentences: state the Lance dependency bump, note any required compatibility fixes, and link the triggering Lance tag or release.
|
|
||||||
|
|
||||||
11. Read back the remote PR title after creation. If it is not a Conventional Commit title, fix it immediately.
|
|
||||||
|
|
||||||
12. When running in GitHub Actions after creating the LanceDB PR, trigger the Sophon dependency update:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
gh workflow run codex-bump-lancedb-lance.yml \
|
|
||||||
--repo lancedb/sophon \
|
|
||||||
-f lance_ref="$LANCE_TAG" \
|
|
||||||
-f lancedb_ref="$BRANCH_NAME"
|
|
||||||
gh run list --repo lancedb/sophon --workflow codex-bump-lancedb-lance.yml --limit 1 --json databaseId,url,displayTitle
|
|
||||||
```
|
|
||||||
|
|
||||||
Use the emitted metadata `tag` value as `LANCE_TAG`. Do this only after a new LanceDB PR has been created. If the update was skipped because no update is needed or an open PR already exists, do not trigger Sophon.
|
|
||||||
|
|
||||||
## GitHub Actions
|
|
||||||
|
|
||||||
When this skill is used from GitHub Actions, `TAG`, `GH_TOKEN`, and `GITHUB_TOKEN` may already be set. Resolve `latest` first when `TAG` is empty. Once an explicit tag or version is known, use:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
python3 ci/update_lance_dependency.py "$TAG" --github-output "$GITHUB_OUTPUT"
|
|
||||||
```
|
|
||||||
|
|
||||||
Then use the emitted `branch_name`, `commit_message`, and `pr_title` values for branch, commit, and PR creation.
|
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
[tool.bumpversion]
|
[tool.bumpversion]
|
||||||
current_version = "0.30.1-beta.0"
|
current_version = "0.28.0-beta.11"
|
||||||
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*)\\.
|
||||||
|
|||||||
5
.github/dependabot.yml
vendored
5
.github/dependabot.yml
vendored
@@ -11,11 +11,6 @@ updates:
|
|||||||
schedule:
|
schedule:
|
||||||
interval: weekly
|
interval: weekly
|
||||||
open-pull-requests-limit: 10
|
open-pull-requests-limit: 10
|
||||||
# Only update Cargo.lock, never widen/raise the version requirements in
|
|
||||||
# Cargo.toml. The goal is keeping the lockfile (and the binaries we ship)
|
|
||||||
# current on security fixes, not forcing our library's consumers onto
|
|
||||||
# newer minimum versions.
|
|
||||||
versioning-strategy: lockfile-only
|
|
||||||
groups:
|
groups:
|
||||||
rust-minor-patch:
|
rust-minor-patch:
|
||||||
update-types:
|
update-types:
|
||||||
|
|||||||
12
.github/workflows/codex-fix-ci.yml
vendored
12
.github/workflows/codex-fix-ci.yml
vendored
@@ -45,9 +45,7 @@ jobs:
|
|||||||
- name: Set up Node.js
|
- name: Set up Node.js
|
||||||
uses: actions/setup-node@v4
|
uses: actions/setup-node@v4
|
||||||
with:
|
with:
|
||||||
# pnpm 11 (used by the nodejs install step below) requires
|
node-version: 20
|
||||||
# Node >= 22.13; use 24 since 22 hits EOL in October.
|
|
||||||
node-version: 24
|
|
||||||
|
|
||||||
- name: Install Codex CLI
|
- name: Install Codex CLI
|
||||||
run: npm install -g @openai/codex
|
run: npm install -g @openai/codex
|
||||||
@@ -81,14 +79,10 @@ jobs:
|
|||||||
java-version: '11'
|
java-version: '11'
|
||||||
cache: maven
|
cache: maven
|
||||||
|
|
||||||
- name: Setup pnpm
|
|
||||||
uses: pnpm/action-setup@v4
|
|
||||||
with:
|
|
||||||
version: 11.1.1
|
|
||||||
- name: Install Node.js dependencies for TypeScript bindings
|
- name: Install Node.js dependencies for TypeScript bindings
|
||||||
run: |
|
run: |
|
||||||
cd nodejs
|
cd nodejs
|
||||||
pnpm install --frozen-lockfile
|
npm ci
|
||||||
|
|
||||||
- name: Configure git user
|
- name: Configure git user
|
||||||
run: |
|
run: |
|
||||||
@@ -143,7 +137,7 @@ jobs:
|
|||||||
- For Rust test failures: Run the specific test with "cargo test -p <crate> <test_name>"
|
- For Rust test failures: Run the specific test with "cargo test -p <crate> <test_name>"
|
||||||
- For Python test failures: Build with "cd python && maturin develop" then run "pytest <specific_test_file>::<test_name>"
|
- For Python test failures: Build with "cd python && maturin develop" then run "pytest <specific_test_file>::<test_name>"
|
||||||
- For Java test failures: Run "cd java && mvn test -Dtest=<TestClass>#<testMethod>"
|
- For Java test failures: Run "cd java && mvn test -Dtest=<TestClass>#<testMethod>"
|
||||||
- For TypeScript test failures: Run "cd nodejs && pnpm build && pnpm test -- --testNamePattern='<test_name>'"
|
- For TypeScript test failures: Run "cd nodejs && npm run build && npm test -- --testNamePattern='<test_name>'"
|
||||||
- Do NOT run the full test suite - only run the tests that were failing
|
- Do NOT run the full test suite - only run the tests that were failing
|
||||||
|
|
||||||
7. If the additional guidelines are provided, follow them as well.
|
7. If the additional guidelines are provided, follow them as well.
|
||||||
|
|||||||
@@ -4,16 +4,14 @@ on:
|
|||||||
workflow_call:
|
workflow_call:
|
||||||
inputs:
|
inputs:
|
||||||
tag:
|
tag:
|
||||||
description: "Tag name from Lance. If omitted, the skill will use the latest Lance release that needs an update."
|
description: "Tag name from Lance"
|
||||||
required: false
|
required: true
|
||||||
default: ""
|
|
||||||
type: string
|
type: string
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
inputs:
|
inputs:
|
||||||
tag:
|
tag:
|
||||||
description: "Tag name from Lance. Leave empty to use the latest Lance release that needs an update."
|
description: "Tag name from Lance"
|
||||||
required: false
|
required: true
|
||||||
default: ""
|
|
||||||
type: string
|
type: string
|
||||||
|
|
||||||
permissions:
|
permissions:
|
||||||
@@ -27,7 +25,7 @@ jobs:
|
|||||||
steps:
|
steps:
|
||||||
- name: Show inputs
|
- name: Show inputs
|
||||||
run: |
|
run: |
|
||||||
echo "tag = ${{ inputs.tag || 'latest' }}"
|
echo "tag = ${{ inputs.tag }}"
|
||||||
|
|
||||||
- name: Checkout Repo LanceDB
|
- name: Checkout Repo LanceDB
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
@@ -73,21 +71,65 @@ jobs:
|
|||||||
OPENAI_API_KEY: ${{ secrets.CODEX_TOKEN }}
|
OPENAI_API_KEY: ${{ secrets.CODEX_TOKEN }}
|
||||||
run: |
|
run: |
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
TARGET_TAG="${TAG:-latest}"
|
VERSION="${TAG#refs/tags/}"
|
||||||
|
VERSION="${VERSION#v}"
|
||||||
|
BRANCH_NAME="codex/update-lance-${VERSION//[^a-zA-Z0-9]/-}"
|
||||||
|
|
||||||
|
# Use "chore" for beta/rc versions, "feat" for stable releases
|
||||||
|
if [[ "${VERSION}" == *beta* ]] || [[ "${VERSION}" == *rc* ]]; then
|
||||||
|
COMMIT_TYPE="chore"
|
||||||
|
else
|
||||||
|
COMMIT_TYPE="feat"
|
||||||
|
fi
|
||||||
|
|
||||||
cat <<EOF >/tmp/codex-prompt.txt
|
cat <<EOF >/tmp/codex-prompt.txt
|
||||||
You are running inside the lancedb repository on a GitHub Actions runner.
|
You are running inside the lancedb repository on a GitHub Actions runner. Update the Lance dependency to version ${VERSION} and prepare a pull request for maintainers to review.
|
||||||
|
|
||||||
Use \$lancedb-update-lance-dependency with target "${TARGET_TAG}".
|
Follow these steps exactly:
|
||||||
|
1. Use script "ci/set_lance_version.py" to update Lance Rust dependencies. The script already refreshes Cargo metadata, so allow it to finish even if it takes time.
|
||||||
|
2. Update the Java lance-core dependency version in "java/pom.xml": change the "<lance-core.version>...</lance-core.version>" property to "${VERSION}".
|
||||||
|
3. Run "cargo clippy --workspace --tests --all-features -- -D warnings". If diagnostics appear, fix them yourself and rerun clippy until it exits cleanly. Do not skip any warnings.
|
||||||
|
4. After clippy succeeds, run "cargo fmt --all" to format the workspace.
|
||||||
|
5. Ensure the repository is clean except for intentional changes. Inspect "git status --short" and "git diff" to confirm the dependency update and any required fixes.
|
||||||
|
6. Create and switch to a new branch named "${BRANCH_NAME}" (replace any duplicated hyphens if necessary).
|
||||||
|
7. Stage all relevant files with "git add -A". Commit using the message "${COMMIT_TYPE}: update lance dependency to v${VERSION}".
|
||||||
|
8. Push the branch to origin. If the remote branch already exists, delete it first with "gh api -X DELETE repos/lancedb/lancedb/git/refs/heads/${BRANCH_NAME}" then push with "git push origin ${BRANCH_NAME}". Do NOT use "git push --force" or "git push -f".
|
||||||
|
9. env "GH_TOKEN" is available, use "gh" tools for github related operations like creating pull request.
|
||||||
|
10. Create a pull request targeting "main" with title "${COMMIT_TYPE}: update lance dependency to v${VERSION}". First, write the PR body to /tmp/pr-body.md using a heredoc (cat <<'EOF' > /tmp/pr-body.md). The body should summarize the dependency bump, clippy/fmt verification, and link the triggering tag (${TAG}). Then run "gh pr create --body-file /tmp/pr-body.md".
|
||||||
|
11. After creating the PR, display the PR URL, "git status --short", and a concise summary of the commands run and their results.
|
||||||
|
|
||||||
Constraints:
|
Constraints:
|
||||||
- Use env "GH_TOKEN" for GitHub operations.
|
- Use bash commands; avoid modifying GitHub workflow files other than through the scripted task above.
|
||||||
- Do not merge the pull request.
|
- Do not merge the PR.
|
||||||
- Do not force-push.
|
- If any command fails, diagnose and fix the issue instead of aborting.
|
||||||
- Do not create a duplicate pull request if an open PR already exists for the target Lance version.
|
|
||||||
- If any command fails, diagnose and fix the root cause instead of aborting.
|
|
||||||
- After creating the PR, display the PR URL, "git status --short", and a concise summary of the commands run and their results.
|
|
||||||
EOF
|
EOF
|
||||||
|
|
||||||
printenv OPENAI_API_KEY | codex login --with-api-key
|
printenv OPENAI_API_KEY | codex login --with-api-key
|
||||||
codex --config shell_environment_policy.ignore_default_excludes=true exec --dangerously-bypass-approvals-and-sandbox "$(cat /tmp/codex-prompt.txt)"
|
codex --config shell_environment_policy.ignore_default_excludes=true exec --dangerously-bypass-approvals-and-sandbox "$(cat /tmp/codex-prompt.txt)"
|
||||||
|
|
||||||
|
- name: Trigger sophon dependency update
|
||||||
|
env:
|
||||||
|
TAG: ${{ inputs.tag }}
|
||||||
|
GH_TOKEN: ${{ secrets.ROBOT_TOKEN }}
|
||||||
|
run: |
|
||||||
|
set -euo pipefail
|
||||||
|
VERSION="${TAG#refs/tags/}"
|
||||||
|
VERSION="${VERSION#v}"
|
||||||
|
LANCEDB_BRANCH="codex/update-lance-${VERSION//[^a-zA-Z0-9]/-}"
|
||||||
|
|
||||||
|
echo "Triggering sophon workflow with:"
|
||||||
|
echo " lance_ref: ${TAG#refs/tags/}"
|
||||||
|
echo " lancedb_ref: ${LANCEDB_BRANCH}"
|
||||||
|
|
||||||
|
gh workflow run codex-bump-lancedb-lance.yml \
|
||||||
|
--repo lancedb/sophon \
|
||||||
|
-f lance_ref="${TAG#refs/tags/}" \
|
||||||
|
-f lancedb_ref="${LANCEDB_BRANCH}"
|
||||||
|
|
||||||
|
- name: Show latest sophon workflow run
|
||||||
|
env:
|
||||||
|
GH_TOKEN: ${{ secrets.ROBOT_TOKEN }}
|
||||||
|
run: |
|
||||||
|
set -euo pipefail
|
||||||
|
echo "Latest sophon workflow run:"
|
||||||
|
gh run list --repo lancedb/sophon --workflow codex-bump-lancedb-lance.yml --limit 1 --json databaseId,url,displayTitle
|
||||||
|
|||||||
1
.github/workflows/java.yml
vendored
1
.github/workflows/java.yml
vendored
@@ -16,6 +16,7 @@ on:
|
|||||||
push:
|
push:
|
||||||
branches:
|
branches:
|
||||||
- main
|
- main
|
||||||
|
- release/**
|
||||||
paths:
|
paths:
|
||||||
- java/**
|
- java/**
|
||||||
- .github/workflows/java.yml
|
- .github/workflows/java.yml
|
||||||
|
|||||||
62
.github/workflows/lance-release-timer.yml
vendored
Normal file
62
.github/workflows/lance-release-timer.yml
vendored
Normal file
@@ -0,0 +1,62 @@
|
|||||||
|
name: Lance Release Timer
|
||||||
|
|
||||||
|
on:
|
||||||
|
schedule:
|
||||||
|
- cron: "*/10 * * * *"
|
||||||
|
workflow_dispatch:
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
actions: write
|
||||||
|
|
||||||
|
concurrency:
|
||||||
|
group: lance-release-timer
|
||||||
|
cancel-in-progress: false
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
trigger-update:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Checkout repository
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Check for new Lance tag
|
||||||
|
id: check
|
||||||
|
env:
|
||||||
|
GH_TOKEN: ${{ secrets.ROBOT_TOKEN }}
|
||||||
|
run: |
|
||||||
|
python3 ci/check_lance_release.py --github-output "$GITHUB_OUTPUT"
|
||||||
|
|
||||||
|
- name: Look for existing PR
|
||||||
|
if: steps.check.outputs.needs_update == 'true'
|
||||||
|
id: pr
|
||||||
|
env:
|
||||||
|
GH_TOKEN: ${{ secrets.ROBOT_TOKEN }}
|
||||||
|
run: |
|
||||||
|
set -euo pipefail
|
||||||
|
TITLE="chore: update lance dependency to v${{ steps.check.outputs.latest_version }}"
|
||||||
|
COUNT=$(gh pr list --search "\"$TITLE\" in:title" --state open --limit 1 --json number --jq 'length')
|
||||||
|
if [ "$COUNT" -gt 0 ]; then
|
||||||
|
echo "Open PR already exists for $TITLE"
|
||||||
|
echo "pr_exists=true" >> "$GITHUB_OUTPUT"
|
||||||
|
else
|
||||||
|
echo "No existing PR for $TITLE"
|
||||||
|
echo "pr_exists=false" >> "$GITHUB_OUTPUT"
|
||||||
|
fi
|
||||||
|
|
||||||
|
- name: Trigger codex update workflow
|
||||||
|
if: steps.check.outputs.needs_update == 'true' && steps.pr.outputs.pr_exists != 'true'
|
||||||
|
env:
|
||||||
|
GH_TOKEN: ${{ secrets.ROBOT_TOKEN }}
|
||||||
|
run: |
|
||||||
|
set -euo pipefail
|
||||||
|
TAG=${{ steps.check.outputs.latest_tag }}
|
||||||
|
gh workflow run codex-update-lance-dependency.yml -f tag=refs/tags/$TAG
|
||||||
|
|
||||||
|
- name: Show latest codex workflow run
|
||||||
|
if: steps.check.outputs.needs_update == 'true' && steps.pr.outputs.pr_exists != 'true'
|
||||||
|
env:
|
||||||
|
GH_TOKEN: ${{ secrets.ROBOT_TOKEN }}
|
||||||
|
run: |
|
||||||
|
set -euo pipefail
|
||||||
|
gh run list --workflow codex-update-lance-dependency.yml --limit 1 --json databaseId,url,displayTitle
|
||||||
1
.github/workflows/license-header-check.yml
vendored
1
.github/workflows/license-header-check.yml
vendored
@@ -3,6 +3,7 @@ on:
|
|||||||
push:
|
push:
|
||||||
branches:
|
branches:
|
||||||
- main
|
- main
|
||||||
|
- release/**
|
||||||
pull_request:
|
pull_request:
|
||||||
paths:
|
paths:
|
||||||
- rust/**
|
- rust/**
|
||||||
|
|||||||
113
.github/workflows/nodejs.yml
vendored
113
.github/workflows/nodejs.yml
vendored
@@ -4,6 +4,7 @@ on:
|
|||||||
push:
|
push:
|
||||||
branches:
|
branches:
|
||||||
- main
|
- main
|
||||||
|
- release/**
|
||||||
pull_request:
|
pull_request:
|
||||||
paths:
|
paths:
|
||||||
- Cargo.toml
|
- Cargo.toml
|
||||||
@@ -42,17 +43,11 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
lfs: true
|
lfs: true
|
||||||
- uses: pnpm/action-setup@v4
|
|
||||||
with:
|
|
||||||
version: 11.1.1
|
|
||||||
- uses: actions/setup-node@v4
|
- uses: actions/setup-node@v4
|
||||||
with:
|
with:
|
||||||
# pnpm 11 requires Node >= 22.13; use 24 since 22 hits EOL
|
node-version: 20
|
||||||
# in October. The library itself still supports Node >= 18
|
cache: 'npm'
|
||||||
# (see test matrix below).
|
cache-dependency-path: nodejs/package-lock.json
|
||||||
node-version: 24
|
|
||||||
cache: 'pnpm'
|
|
||||||
cache-dependency-path: nodejs/pnpm-lock.yaml
|
|
||||||
- uses: actions-rust-lang/setup-rust-toolchain@v1
|
- uses: actions-rust-lang/setup-rust-toolchain@v1
|
||||||
with:
|
with:
|
||||||
components: rustfmt, clippy
|
components: rustfmt, clippy
|
||||||
@@ -67,13 +62,11 @@ jobs:
|
|||||||
run: cargo clippy --profile ci --all --all-features -- -D warnings
|
run: cargo clippy --profile ci --all --all-features -- -D warnings
|
||||||
- name: Lint Typescript
|
- name: Lint Typescript
|
||||||
run: |
|
run: |
|
||||||
pnpm install --frozen-lockfile
|
npm ci
|
||||||
pnpm lint-ci
|
npm run lint-ci
|
||||||
- name: Lint examples
|
- name: Lint examples
|
||||||
working-directory: nodejs/examples
|
working-directory: nodejs/examples
|
||||||
# The `@lancedb/lancedb` dep points at file:../dist; pnpm errors if
|
run: npm ci && npm run lint-ci
|
||||||
# that dir is missing, so create an empty one for lint-only runs.
|
|
||||||
run: mkdir -p ../dist && pnpm install --frozen-lockfile && pnpm lint-ci
|
|
||||||
linux:
|
linux:
|
||||||
name: Linux (NodeJS ${{ matrix.node-version }})
|
name: Linux (NodeJS ${{ matrix.node-version }})
|
||||||
timeout-minutes: 30
|
timeout-minutes: 30
|
||||||
@@ -90,18 +83,14 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
lfs: true
|
lfs: true
|
||||||
- uses: pnpm/action-setup@v4
|
|
||||||
with:
|
|
||||||
version: 11.1.1
|
|
||||||
- uses: actions/setup-node@v4
|
- uses: actions/setup-node@v4
|
||||||
name: Setup Node.js 24 for build
|
name: Setup Node.js 20 for build
|
||||||
with:
|
with:
|
||||||
# pnpm 11 requires Node >= 22.13; use 24 since 22 hits EOL
|
# @napi-rs/cli v3 requires Node >= 20.12 (via @inquirer/prompts@8).
|
||||||
# in October. Build/install runs on Node 24; tests run on the
|
# Build always on Node 20; tests run on the matrix version below.
|
||||||
# matrix version below using direct jest invocation.
|
node-version: 20
|
||||||
node-version: 24
|
cache: 'npm'
|
||||||
cache: 'pnpm'
|
cache-dependency-path: nodejs/package-lock.json
|
||||||
cache-dependency-path: nodejs/pnpm-lock.yaml
|
|
||||||
- uses: Swatinem/rust-cache@v2
|
- uses: Swatinem/rust-cache@v2
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
run: |
|
run: |
|
||||||
@@ -109,58 +98,48 @@ jobs:
|
|||||||
sudo apt install -y protobuf-compiler libssl-dev
|
sudo apt install -y protobuf-compiler libssl-dev
|
||||||
- name: Build
|
- name: Build
|
||||||
run: |
|
run: |
|
||||||
pnpm install --frozen-lockfile
|
npm ci --include=optional
|
||||||
# No `--` separator: pnpm forwards it literally, which would
|
npm run build:debug -- --profile ci
|
||||||
# make napi-rs treat `--profile ci` as a cargo passthrough arg.
|
|
||||||
pnpm build:debug --profile ci
|
|
||||||
pnpm tsc
|
|
||||||
- name: Setup examples
|
|
||||||
working-directory: nodejs/examples
|
|
||||||
run: pnpm install --frozen-lockfile
|
|
||||||
- name: Check docs
|
|
||||||
run: |
|
|
||||||
# We run this as part of the job because the binary needs to be built
|
|
||||||
# first to export the types of the native code.
|
|
||||||
set -e
|
|
||||||
# `pnpm docs` would invoke pnpm's built-in `docs` command, not
|
|
||||||
# the script — use `pnpm run docs`.
|
|
||||||
pnpm run docs
|
|
||||||
if ! git diff --exit-code -- ../ ':(exclude)Cargo.lock'; then
|
|
||||||
echo "Docs need to be updated"
|
|
||||||
echo "Run 'pnpm run docs', fix any warnings, and commit the changes."
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
- uses: actions/setup-node@v4
|
- uses: actions/setup-node@v4
|
||||||
name: Setup Node.js ${{ matrix.node-version }} for test
|
name: Setup Node.js ${{ matrix.node-version }} for test
|
||||||
with:
|
with:
|
||||||
node-version: ${{ matrix.node-version }}
|
node-version: ${{ matrix.node-version }}
|
||||||
|
- name: Compile TypeScript
|
||||||
|
run: npm run tsc
|
||||||
- name: Setup localstack
|
- name: Setup localstack
|
||||||
working-directory: .
|
working-directory: .
|
||||||
run: docker compose up --detach --wait
|
run: docker compose up --detach --wait
|
||||||
- name: Test
|
- name: Test
|
||||||
env:
|
env:
|
||||||
S3_TEST: "1"
|
S3_TEST: "1"
|
||||||
# Newer @smithy/core uses dynamic ESM imports.
|
run: npm run test
|
||||||
NODE_OPTIONS: "--experimental-vm-modules"
|
- name: Setup examples
|
||||||
# Invoke jest directly because pnpm 11 itself requires Node 22+
|
working-directory: nodejs/examples
|
||||||
# while the matrix tests on older Node versions.
|
run: npm ci
|
||||||
run: npx jest --verbose
|
|
||||||
- name: Test examples
|
- name: Test examples
|
||||||
working-directory: ./
|
working-directory: ./
|
||||||
env:
|
env:
|
||||||
OPENAI_API_KEY: test
|
OPENAI_API_KEY: test
|
||||||
OPENAI_BASE_URL: http://0.0.0.0:8000
|
OPENAI_BASE_URL: http://0.0.0.0:8000
|
||||||
NODE_OPTIONS: "--experimental-vm-modules"
|
|
||||||
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
|
npm test
|
||||||
|
- name: Check docs
|
||||||
|
run: |
|
||||||
|
# We run this as part of the job because the binary needs to be built
|
||||||
|
# first to export the types of the native code.
|
||||||
|
set -e
|
||||||
|
npm ci
|
||||||
|
npm run docs
|
||||||
|
if ! git diff --exit-code -- ../ ':(exclude)Cargo.lock'; then
|
||||||
|
echo "Docs need to be updated"
|
||||||
|
echo "Run 'npm run docs', fix any warnings, and commit the changes."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
macos:
|
macos:
|
||||||
timeout-minutes: 30
|
timeout-minutes: 30
|
||||||
# macos-15 ships a newer linker; the older macos-14 linker fails to insert
|
runs-on: "macos-14"
|
||||||
# branch islands when the debug cdylib's __text section exceeds the 128 MB
|
|
||||||
# AArch64 B/BL branch range.
|
|
||||||
runs-on: "macos-15"
|
|
||||||
defaults:
|
defaults:
|
||||||
run:
|
run:
|
||||||
shell: bash
|
shell: bash
|
||||||
@@ -170,28 +149,20 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
lfs: true
|
lfs: true
|
||||||
- uses: pnpm/action-setup@v4
|
|
||||||
with:
|
|
||||||
version: 11.1.1
|
|
||||||
- uses: actions/setup-node@v4
|
- uses: actions/setup-node@v4
|
||||||
with:
|
with:
|
||||||
# pnpm 11 requires Node >= 22.13; use 24 since 22 hits EOL
|
node-version: 20
|
||||||
# in October.
|
cache: 'npm'
|
||||||
node-version: 24
|
cache-dependency-path: nodejs/package-lock.json
|
||||||
cache: 'pnpm'
|
|
||||||
cache-dependency-path: nodejs/pnpm-lock.yaml
|
|
||||||
- uses: dtolnay/rust-toolchain@stable
|
|
||||||
- uses: Swatinem/rust-cache@v2
|
- uses: Swatinem/rust-cache@v2
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
run: |
|
run: |
|
||||||
brew install protobuf
|
brew install protobuf
|
||||||
- name: Build
|
- name: Build
|
||||||
run: |
|
run: |
|
||||||
pnpm install --frozen-lockfile
|
npm ci --include=optional
|
||||||
# No `--` separator: pnpm forwards it literally, which would
|
npm run build:debug -- --profile ci
|
||||||
# make napi-rs treat `--profile ci` as a cargo passthrough arg.
|
npm run tsc
|
||||||
pnpm build:debug --profile ci
|
|
||||||
pnpm tsc
|
|
||||||
- name: Test
|
- name: Test
|
||||||
run: |
|
run: |
|
||||||
pnpm test
|
npm run test
|
||||||
|
|||||||
53
.github/workflows/npm-publish.yml
vendored
53
.github/workflows/npm-publish.yml
vendored
@@ -171,18 +171,13 @@ jobs:
|
|||||||
working-directory: nodejs
|
working-directory: nodejs
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
- name: Setup pnpm
|
|
||||||
uses: pnpm/action-setup@v4
|
|
||||||
with:
|
|
||||||
version: 11.1.1
|
|
||||||
- name: Setup node
|
- name: Setup node
|
||||||
uses: actions/setup-node@v4
|
uses: actions/setup-node@v4
|
||||||
|
if: ${{ !matrix.settings.docker }}
|
||||||
with:
|
with:
|
||||||
# pnpm 11 requires Node >= 22.13; use 24 since 22 hits EOL
|
node-version: 20
|
||||||
# in October.
|
cache: npm
|
||||||
node-version: 24
|
cache-dependency-path: nodejs/package-lock.json
|
||||||
cache: pnpm
|
|
||||||
cache-dependency-path: nodejs/pnpm-lock.yaml
|
|
||||||
- name: Install
|
- name: Install
|
||||||
uses: dtolnay/rust-toolchain@stable
|
uses: dtolnay/rust-toolchain@stable
|
||||||
if: ${{ !matrix.settings.docker }}
|
if: ${{ !matrix.settings.docker }}
|
||||||
@@ -200,7 +195,7 @@ jobs:
|
|||||||
target/
|
target/
|
||||||
key: nodejs-${{ matrix.settings.target }}-cargo-${{ matrix.settings.host }}
|
key: nodejs-${{ matrix.settings.target }}-cargo-${{ matrix.settings.host }}
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
run: pnpm install --frozen-lockfile
|
run: npm ci
|
||||||
- name: Install Zig
|
- name: Install Zig
|
||||||
uses: mlugg/setup-zig@v2
|
uses: mlugg/setup-zig@v2
|
||||||
if: ${{ contains(matrix.settings.target, 'musl') }}
|
if: ${{ contains(matrix.settings.target, 'musl') }}
|
||||||
@@ -253,7 +248,7 @@ jobs:
|
|||||||
# one to do the upload.
|
# one to do the upload.
|
||||||
- name: Make generic artifacts
|
- name: Make generic artifacts
|
||||||
if: ${{ matrix.settings.target == 'aarch64-apple-darwin' }}
|
if: ${{ matrix.settings.target == 'aarch64-apple-darwin' }}
|
||||||
run: pnpm tsc
|
run: npm run tsc
|
||||||
- name: Upload Generic Artifacts
|
- name: Upload Generic Artifacts
|
||||||
if: ${{ matrix.settings.target == 'aarch64-apple-darwin' }}
|
if: ${{ matrix.settings.target == 'aarch64-apple-darwin' }}
|
||||||
uses: actions/upload-artifact@v4
|
uses: actions/upload-artifact@v4
|
||||||
@@ -288,24 +283,14 @@ jobs:
|
|||||||
working-directory: nodejs
|
working-directory: nodejs
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
- name: Setup pnpm
|
- name: Setup node
|
||||||
uses: pnpm/action-setup@v4
|
|
||||||
with:
|
|
||||||
version: 11.1.1
|
|
||||||
- name: Setup Node.js 24 for install
|
|
||||||
uses: actions/setup-node@v4
|
|
||||||
with:
|
|
||||||
# pnpm 11 requires Node >= 22.13; use 24 since 22 hits EOL
|
|
||||||
# in October.
|
|
||||||
node-version: 24
|
|
||||||
cache: pnpm
|
|
||||||
cache-dependency-path: nodejs/pnpm-lock.yaml
|
|
||||||
- name: Install dependencies
|
|
||||||
run: pnpm install --frozen-lockfile
|
|
||||||
- name: Setup Node.js ${{ matrix.node }} for test
|
|
||||||
uses: actions/setup-node@v4
|
uses: actions/setup-node@v4
|
||||||
with:
|
with:
|
||||||
node-version: ${{ matrix.node }}
|
node-version: ${{ matrix.node }}
|
||||||
|
cache: npm
|
||||||
|
cache-dependency-path: nodejs/package-lock.json
|
||||||
|
- name: Install dependencies
|
||||||
|
run: npm ci
|
||||||
- name: Download artifacts
|
- name: Download artifacts
|
||||||
uses: actions/download-artifact@v4
|
uses: actions/download-artifact@v4
|
||||||
with:
|
with:
|
||||||
@@ -326,9 +311,7 @@ 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+
|
run: npm test
|
||||||
# while the matrix tests on older Node versions.
|
|
||||||
run: npx jest --verbose
|
|
||||||
publish:
|
publish:
|
||||||
name: Publish
|
name: Publish
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
@@ -340,19 +323,15 @@ jobs:
|
|||||||
- test-lancedb
|
- test-lancedb
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
- name: Setup pnpm
|
|
||||||
uses: pnpm/action-setup@v4
|
|
||||||
with:
|
|
||||||
version: 11.1.1
|
|
||||||
- name: Setup node
|
- name: Setup node
|
||||||
uses: actions/setup-node@v4
|
uses: actions/setup-node@v4
|
||||||
with:
|
with:
|
||||||
node-version: 24
|
node-version: 24
|
||||||
cache: pnpm
|
cache: npm
|
||||||
cache-dependency-path: nodejs/pnpm-lock.yaml
|
cache-dependency-path: nodejs/package-lock.json
|
||||||
registry-url: "https://registry.npmjs.org"
|
registry-url: "https://registry.npmjs.org"
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
run: pnpm install --frozen-lockfile
|
run: npm ci
|
||||||
- uses: actions/download-artifact@v4
|
- uses: actions/download-artifact@v4
|
||||||
with:
|
with:
|
||||||
name: nodejs-dist
|
name: nodejs-dist
|
||||||
@@ -372,7 +351,7 @@ jobs:
|
|||||||
- name: Display structure of downloaded files
|
- name: Display structure of downloaded files
|
||||||
run: find dist && find nodejs-artifacts
|
run: find dist && find nodejs-artifacts
|
||||||
- name: Move artifacts
|
- name: Move artifacts
|
||||||
run: pnpm exec napi artifacts -d nodejs-artifacts
|
run: npx napi artifacts -d nodejs-artifacts
|
||||||
- name: List packages
|
- name: List packages
|
||||||
run: find npm
|
run: find npm
|
||||||
- name: Publish
|
- name: Publish
|
||||||
|
|||||||
10
.github/workflows/pypi-publish.yml
vendored
10
.github/workflows/pypi-publish.yml
vendored
@@ -27,11 +27,19 @@ jobs:
|
|||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
config:
|
config:
|
||||||
|
- platform: x86_64
|
||||||
|
manylinux: "2_17"
|
||||||
|
extra_args: ""
|
||||||
|
runner: ubuntu-22.04
|
||||||
- platform: x86_64
|
- platform: x86_64
|
||||||
manylinux: "2_28"
|
manylinux: "2_28"
|
||||||
extra_args: "--features fp16kernels"
|
extra_args: "--features fp16kernels"
|
||||||
runner: ubuntu-22.04
|
runner: ubuntu-22.04
|
||||||
# For successful fat LTO builds, we need a large runner to avoid OOM errors.
|
- platform: aarch64
|
||||||
|
manylinux: "2_17"
|
||||||
|
extra_args: ""
|
||||||
|
# For successful fat LTO builds, we need a large runner to avoid OOM errors.
|
||||||
|
runner: ubuntu-2404-8x-arm64
|
||||||
- platform: aarch64
|
- platform: aarch64
|
||||||
manylinux: "2_28"
|
manylinux: "2_28"
|
||||||
extra_args: "--features fp16kernels"
|
extra_args: "--features fp16kernels"
|
||||||
|
|||||||
3
.github/workflows/python.yml
vendored
3
.github/workflows/python.yml
vendored
@@ -4,6 +4,7 @@ on:
|
|||||||
push:
|
push:
|
||||||
branches:
|
branches:
|
||||||
- main
|
- main
|
||||||
|
- release/**
|
||||||
pull_request:
|
pull_request:
|
||||||
paths:
|
paths:
|
||||||
- Cargo.toml
|
- Cargo.toml
|
||||||
@@ -205,7 +206,7 @@ jobs:
|
|||||||
- name: Delete wheels
|
- name: Delete wheels
|
||||||
run: rm -rf target/wheels
|
run: rm -rf target/wheels
|
||||||
pydantic1x:
|
pydantic1x:
|
||||||
timeout-minutes: 60
|
timeout-minutes: 30
|
||||||
runs-on: "ubuntu-24.04"
|
runs-on: "ubuntu-24.04"
|
||||||
defaults:
|
defaults:
|
||||||
run:
|
run:
|
||||||
|
|||||||
21
.github/workflows/rust.yml
vendored
21
.github/workflows/rust.yml
vendored
@@ -4,6 +4,7 @@ on:
|
|||||||
push:
|
push:
|
||||||
branches:
|
branches:
|
||||||
- main
|
- main
|
||||||
|
- release/**
|
||||||
pull_request:
|
pull_request:
|
||||||
paths:
|
paths:
|
||||||
- Cargo.toml
|
- Cargo.toml
|
||||||
@@ -233,26 +234,6 @@ jobs:
|
|||||||
cargo update -p aws-sdk-sso --precise 1.62.0
|
cargo update -p aws-sdk-sso --precise 1.62.0
|
||||||
cargo update -p aws-sdk-ssooidc --precise 1.63.0
|
cargo update -p aws-sdk-ssooidc --precise 1.63.0
|
||||||
cargo update -p aws-sdk-sts --precise 1.63.0
|
cargo update -p aws-sdk-sts --precise 1.63.0
|
||||||
# aws-runtime/sigv4/credential-types/types and the aws-smithy-*
|
|
||||||
# crates bumped their MSRV to 1.91.1 in late 2026; pin to the last
|
|
||||||
# 1.91.0-compatible versions. The order matters — each downgrade
|
|
||||||
# only succeeds once everything that still pins it at a higher
|
|
||||||
# version has itself been downgraded.
|
|
||||||
cargo update -p aws-runtime --precise 1.5.12
|
|
||||||
cargo update -p aws-types --precise 1.3.9
|
|
||||||
cargo update -p aws-sigv4 --precise 1.3.5
|
|
||||||
cargo update -p aws-credential-types --precise 1.2.8
|
|
||||||
cargo update -p aws-smithy-checksums --precise 0.63.9
|
|
||||||
cargo update -p aws-smithy-runtime --precise 1.9.3
|
|
||||||
cargo update -p aws-smithy-http --precise 0.62.4
|
|
||||||
cargo update -p aws-smithy-eventstream --precise 0.60.12
|
|
||||||
cargo update -p aws-smithy-http-client --precise 1.1.3
|
|
||||||
cargo update -p aws-smithy-observability --precise 0.1.4
|
|
||||||
cargo update -p aws-smithy-query --precise 0.60.8
|
|
||||||
cargo update -p aws-smithy-runtime-api --precise 1.9.1
|
|
||||||
cargo update -p aws-smithy-async --precise 1.2.6
|
|
||||||
cargo update -p aws-smithy-types --precise 1.3.5
|
|
||||||
cargo update -p aws-smithy-xml --precise 0.60.11
|
|
||||||
cargo update -p home --precise 0.5.9
|
cargo update -p home --precise 0.5.9
|
||||||
- name: cargo +${{ matrix.msrv }} check
|
- name: cargo +${{ matrix.msrv }} check
|
||||||
env:
|
env:
|
||||||
|
|||||||
28
AGENTS.md
28
AGENTS.md
@@ -17,33 +17,9 @@ Common commands:
|
|||||||
* Run tests: `cargo test --quiet --features remote --tests`
|
* Run tests: `cargo test --quiet --features remote --tests`
|
||||||
* 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: `cargo fmt --all`
|
||||||
* Format Python: `ruff format .`
|
|
||||||
* Lint Python: `ruff check .`
|
|
||||||
* Bootstrap Python dev env: `cd python && uv run --extra tests --extra dev maturin develop --extras tests,dev`
|
|
||||||
* Run Python tests: `cd python && uv run --extra tests pytest python/tests -vv --durations=10 -m "not slow and not s3_test"`
|
|
||||||
* Run specific Python test: `cd python && uv run --extra tests pytest python/tests/<test_file>.py::<test_name> -q`
|
|
||||||
|
|
||||||
For Python validation, prefer the uv-managed environment declared by `python/uv.lock`.
|
Before committing changes, run formatting.
|
||||||
Do not treat system `python`, global `pytest`, or missing editable-install errors as
|
|
||||||
final blockers; bootstrap or enter the uv environment instead. If `lancedb._lancedb`
|
|
||||||
is missing or stale, or if Rust/PyO3 binding code changed, rebuild the Python
|
|
||||||
extension with the bootstrap command above before running tests.
|
|
||||||
|
|
||||||
Before committing changes, run formatting for every language you touched. At minimum:
|
|
||||||
|
|
||||||
* Rust changes: run `cargo fmt --all`.
|
|
||||||
* Python changes: run `ruff format .` and `ruff check .` from the repository root,
|
|
||||||
and run targeted tests through `cd python && uv run ...`.
|
|
||||||
* TypeScript changes: run the relevant `npm`/`pnpm` lint, format, build, and docs commands in `nodejs`.
|
|
||||||
|
|
||||||
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`
|
|
||||||
or `feat(python): add dataset multiprocessing support`. Do not use a plain natural
|
|
||||||
language summary like `Support nested field paths in native index creation` as the PR
|
|
||||||
title. The semantic-release check uses the PR title and body as the merge commit message,
|
|
||||||
so a non-conventional PR title will fail CI. After creating a PR, read the remote PR title
|
|
||||||
back and fix it immediately if it is not conventional.
|
|
||||||
|
|
||||||
## Coding tips
|
## Coding tips
|
||||||
|
|
||||||
|
|||||||
3094
Cargo.lock
generated
3094
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
30
Cargo.toml
30
Cargo.toml
@@ -13,20 +13,20 @@ categories = ["database-implementations"]
|
|||||||
rust-version = "1.91.0"
|
rust-version = "1.91.0"
|
||||||
|
|
||||||
[workspace.dependencies]
|
[workspace.dependencies]
|
||||||
lance = { "version" = "=7.2.0-beta.3", default-features = false, "tag" = "v7.2.0-beta.3", "git" = "https://github.com/lance-format/lance.git" }
|
lance = { "version" = "=6.0.0", default-features = false }
|
||||||
lance-core = { "version" = "=7.2.0-beta.3", "tag" = "v7.2.0-beta.3", "git" = "https://github.com/lance-format/lance.git" }
|
lance-core = "=6.0.0"
|
||||||
lance-datagen = { "version" = "=7.2.0-beta.3", "tag" = "v7.2.0-beta.3", "git" = "https://github.com/lance-format/lance.git" }
|
lance-datagen = "=6.0.0"
|
||||||
lance-file = { "version" = "=7.2.0-beta.3", "tag" = "v7.2.0-beta.3", "git" = "https://github.com/lance-format/lance.git" }
|
lance-file = "=6.0.0"
|
||||||
lance-io = { "version" = "=7.2.0-beta.3", default-features = false, "tag" = "v7.2.0-beta.3", "git" = "https://github.com/lance-format/lance.git" }
|
lance-io = { "version" = "=6.0.0", default-features = false }
|
||||||
lance-index = { "version" = "=7.2.0-beta.3", "tag" = "v7.2.0-beta.3", "git" = "https://github.com/lance-format/lance.git" }
|
lance-index = "=6.0.0"
|
||||||
lance-linalg = { "version" = "=7.2.0-beta.3", "tag" = "v7.2.0-beta.3", "git" = "https://github.com/lance-format/lance.git" }
|
lance-linalg = "=6.0.0"
|
||||||
lance-namespace = { "version" = "=7.2.0-beta.3", "tag" = "v7.2.0-beta.3", "git" = "https://github.com/lance-format/lance.git" }
|
lance-namespace = "=6.0.0"
|
||||||
lance-namespace-impls = { "version" = "=7.2.0-beta.3", default-features = false, "tag" = "v7.2.0-beta.3", "git" = "https://github.com/lance-format/lance.git" }
|
lance-namespace-impls = { "version" = "=6.0.0", default-features = false }
|
||||||
lance-table = { "version" = "=7.2.0-beta.3", "tag" = "v7.2.0-beta.3", "git" = "https://github.com/lance-format/lance.git" }
|
lance-table = "=6.0.0"
|
||||||
lance-testing = { "version" = "=7.2.0-beta.3", "tag" = "v7.2.0-beta.3", "git" = "https://github.com/lance-format/lance.git" }
|
lance-testing = "=6.0.0"
|
||||||
lance-datafusion = { "version" = "=7.2.0-beta.3", "tag" = "v7.2.0-beta.3", "git" = "https://github.com/lance-format/lance.git" }
|
lance-datafusion = "=6.0.0"
|
||||||
lance-encoding = { "version" = "=7.2.0-beta.3", "tag" = "v7.2.0-beta.3", "git" = "https://github.com/lance-format/lance.git" }
|
lance-encoding = "=6.0.0"
|
||||||
lance-arrow = { "version" = "=7.2.0-beta.3", "tag" = "v7.2.0-beta.3", "git" = "https://github.com/lance-format/lance.git" }
|
lance-arrow = "=6.0.0"
|
||||||
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 }
|
||||||
@@ -54,7 +54,7 @@ half = { "version" = "2.7.1", default-features = false, features = [
|
|||||||
futures = "0"
|
futures = "0"
|
||||||
log = "0.4"
|
log = "0.4"
|
||||||
moka = { version = "0.12", features = ["future"] }
|
moka = { version = "0.12", features = ["future"] }
|
||||||
object_store = "0.13.2"
|
object_store = "0.12.0"
|
||||||
pin-project = "1.0.7"
|
pin-project = "1.0.7"
|
||||||
rand = "0.9"
|
rand = "0.9"
|
||||||
snafu = "0.8"
|
snafu = "0.8"
|
||||||
|
|||||||
@@ -112,25 +112,25 @@ def fetch_remote_tags() -> List[TagInfo]:
|
|||||||
"api",
|
"api",
|
||||||
"-X",
|
"-X",
|
||||||
"GET",
|
"GET",
|
||||||
f"repos/{LANCE_REPO}/releases",
|
f"repos/{LANCE_REPO}/git/refs/tags",
|
||||||
|
"--paginate",
|
||||||
"--jq",
|
"--jq",
|
||||||
".[].tag_name",
|
".[].ref",
|
||||||
"-F",
|
|
||||||
"per_page=20",
|
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
tags: List[TagInfo] = []
|
tags: List[TagInfo] = []
|
||||||
for line in output.splitlines():
|
for line in output.splitlines():
|
||||||
tag = line.strip()
|
ref = line.strip()
|
||||||
if not tag.startswith("v"):
|
if not ref.startswith("refs/tags/v"):
|
||||||
continue
|
continue
|
||||||
|
tag = ref.split("refs/tags/")[-1]
|
||||||
version = tag.lstrip("v")
|
version = tag.lstrip("v")
|
||||||
try:
|
try:
|
||||||
tags.append(TagInfo(tag=tag, version=version, semver=parse_semver(version)))
|
tags.append(TagInfo(tag=tag, version=version, semver=parse_semver(version)))
|
||||||
except ValueError:
|
except ValueError:
|
||||||
continue
|
continue
|
||||||
if not tags:
|
if not tags:
|
||||||
raise RuntimeError("No Lance releases could be parsed from GitHub API output")
|
raise RuntimeError("No Lance tags could be parsed from GitHub API output")
|
||||||
return tags
|
return tags
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,126 +0,0 @@
|
|||||||
#!/usr/bin/env python3
|
|
||||||
"""Prepare a Lance dependency update for LanceDB."""
|
|
||||||
|
|
||||||
from __future__ import annotations
|
|
||||||
|
|
||||||
import argparse
|
|
||||||
import json
|
|
||||||
import re
|
|
||||||
import subprocess
|
|
||||||
import sys
|
|
||||||
from pathlib import Path
|
|
||||||
from typing import Sequence
|
|
||||||
|
|
||||||
try:
|
|
||||||
from check_lance_release import parse_semver
|
|
||||||
except ModuleNotFoundError:
|
|
||||||
# Supports importing as ci.update_lance_dependency from tests or ad hoc checks.
|
|
||||||
from ci.check_lance_release import parse_semver # type: ignore
|
|
||||||
|
|
||||||
|
|
||||||
def normalize_version(raw: str) -> str:
|
|
||||||
value = raw.strip()
|
|
||||||
value = value.removeprefix("refs/tags/")
|
|
||||||
value = value.removeprefix("v")
|
|
||||||
try:
|
|
||||||
parse_semver(value)
|
|
||||||
except ValueError:
|
|
||||||
raise ValueError(f"Unsupported Lance version or tag: {raw}")
|
|
||||||
return value
|
|
||||||
|
|
||||||
|
|
||||||
def normalized_tag(version: str) -> str:
|
|
||||||
return f"v{version}"
|
|
||||||
|
|
||||||
|
|
||||||
def branch_name(version: str) -> str:
|
|
||||||
suffix = re.sub(r"[^a-zA-Z0-9]+", "-", version).strip("-")
|
|
||||||
suffix = re.sub(r"-+", "-", suffix)
|
|
||||||
return f"codex/update-lance-{suffix}"
|
|
||||||
|
|
||||||
|
|
||||||
def commit_type(version: str) -> str:
|
|
||||||
prerelease = version.split("-", maxsplit=1)[1] if "-" in version else ""
|
|
||||||
return "chore" if "beta" in prerelease or "rc" in prerelease else "feat"
|
|
||||||
|
|
||||||
|
|
||||||
def metadata_for(version: str) -> dict[str, str]:
|
|
||||||
kind = commit_type(version)
|
|
||||||
message = f"{kind}: update lance dependency to v{version}"
|
|
||||||
return {
|
|
||||||
"version": version,
|
|
||||||
"tag": normalized_tag(version),
|
|
||||||
"branch_name": branch_name(version),
|
|
||||||
"commit_type": kind,
|
|
||||||
"commit_message": message,
|
|
||||||
"pr_title": message,
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
def run_command(cmd: Sequence[str], *, cwd: Path) -> None:
|
|
||||||
subprocess.run(cmd, cwd=cwd, check=True)
|
|
||||||
|
|
||||||
|
|
||||||
def update_java_lance_core_version(repo_root: Path, version: str) -> None:
|
|
||||||
pom_path = repo_root / "java" / "pom.xml"
|
|
||||||
contents = pom_path.read_text(encoding="utf-8")
|
|
||||||
updated, count = re.subn(
|
|
||||||
r"(<lance-core\.version>)[^<]+(</lance-core\.version>)",
|
|
||||||
rf"\g<1>{version}\g<2>",
|
|
||||||
contents,
|
|
||||||
count=1,
|
|
||||||
)
|
|
||||||
if count != 1:
|
|
||||||
raise RuntimeError(
|
|
||||||
"Expected exactly one <lance-core.version> entry in java/pom.xml"
|
|
||||||
)
|
|
||||||
pom_path.write_text(updated, encoding="utf-8")
|
|
||||||
|
|
||||||
|
|
||||||
def write_github_outputs(path: str | None, payload: dict[str, str]) -> None:
|
|
||||||
if not path:
|
|
||||||
return
|
|
||||||
with open(path, "a", encoding="utf-8") as output:
|
|
||||||
for key, value in payload.items():
|
|
||||||
output.write(f"{key}={value}\n")
|
|
||||||
|
|
||||||
|
|
||||||
def main(argv: Sequence[str] | None = None) -> int:
|
|
||||||
parser = argparse.ArgumentParser(description=__doc__)
|
|
||||||
parser.add_argument(
|
|
||||||
"tag_or_version",
|
|
||||||
help="Lance tag or version, for example refs/tags/v7.2.0-beta.1 or 7.2.0",
|
|
||||||
)
|
|
||||||
parser.add_argument(
|
|
||||||
"--repo-root",
|
|
||||||
type=Path,
|
|
||||||
default=Path(__file__).resolve().parents[1],
|
|
||||||
help="Path to the lancedb repository root",
|
|
||||||
)
|
|
||||||
parser.add_argument(
|
|
||||||
"--github-output",
|
|
||||||
default=None,
|
|
||||||
help="Optional GitHub Actions output file to receive metadata fields",
|
|
||||||
)
|
|
||||||
parser.add_argument(
|
|
||||||
"--metadata-only",
|
|
||||||
action="store_true",
|
|
||||||
help="Only print derived metadata; do not modify dependency files",
|
|
||||||
)
|
|
||||||
args = parser.parse_args(argv)
|
|
||||||
|
|
||||||
repo_root = args.repo_root.resolve()
|
|
||||||
version = normalize_version(args.tag_or_version)
|
|
||||||
payload = metadata_for(version)
|
|
||||||
|
|
||||||
if not args.metadata_only:
|
|
||||||
run_command([sys.executable, "ci/set_lance_version.py", version], cwd=repo_root)
|
|
||||||
update_java_lance_core_version(repo_root, version)
|
|
||||||
|
|
||||||
write_github_outputs(args.github_output, payload)
|
|
||||||
print(json.dumps(payload, sort_keys=True))
|
|
||||||
return 0
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
sys.exit(main())
|
|
||||||
@@ -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.30.1-beta.0</version>
|
<version>0.28.0-beta.11</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|||||||
@@ -12,22 +12,20 @@ Typescript.
|
|||||||
* `src/`: Rust bindings source code
|
* `src/`: Rust bindings source code
|
||||||
* `lancedb/`: Typescript package source code
|
* `lancedb/`: Typescript package source code
|
||||||
* `__test__/`: Unit tests
|
* `__test__/`: Unit tests
|
||||||
* `examples/`: A pnpm package with the examples shown in the documentation
|
* `examples/`: An npm package with the examples shown in the documentation
|
||||||
|
|
||||||
## Development environment
|
## Development environment
|
||||||
|
|
||||||
To set up your development environment, you will need to install the following:
|
To set up your development environment, you will need to install the following:
|
||||||
|
|
||||||
1. Node.js 22 or later (required by pnpm 11)
|
1. Node.js 14 or later
|
||||||
2. [pnpm](https://pnpm.io/installation) 11 or later (or run via `corepack enable`,
|
2. Rust's package manager, Cargo. Use [rustup](https://rustup.rs/) to install.
|
||||||
which uses the `packageManager` field in `package.json`)
|
3. [protoc](https://grpc.io/docs/protoc-installation/) (Protocol Buffers compiler)
|
||||||
3. Rust's package manager, Cargo. Use [rustup](https://rustup.rs/) to install.
|
|
||||||
4. [protoc](https://grpc.io/docs/protoc-installation/) (Protocol Buffers compiler)
|
|
||||||
|
|
||||||
Initial setup:
|
Initial setup:
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
pnpm install
|
npm install
|
||||||
```
|
```
|
||||||
|
|
||||||
### Commit Hooks
|
### Commit Hooks
|
||||||
@@ -41,38 +39,38 @@ pre-commit install
|
|||||||
|
|
||||||
## Development
|
## Development
|
||||||
|
|
||||||
Most common development commands can be run using the pnpm scripts.
|
Most common development commands can be run using the npm scripts.
|
||||||
|
|
||||||
Build the package
|
Build the package
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
pnpm install
|
npm install
|
||||||
pnpm build
|
npm run build
|
||||||
```
|
```
|
||||||
|
|
||||||
Lint:
|
Lint:
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
pnpm lint
|
npm run lint
|
||||||
```
|
```
|
||||||
|
|
||||||
Format and fix lints:
|
Format and fix lints:
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
pnpm lint-fix
|
npm run lint-fix
|
||||||
```
|
```
|
||||||
|
|
||||||
Run tests:
|
Run tests:
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
pnpm test
|
npm test
|
||||||
```
|
```
|
||||||
|
|
||||||
To run a single test:
|
To run a single test:
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
# Single file: table.test.ts
|
# Single file: table.test.ts
|
||||||
pnpm test -- table.test.ts
|
npm test -- table.test.ts
|
||||||
# Single test: 'merge insert' in table.test.ts
|
# Single test: 'merge insert' in table.test.ts
|
||||||
pnpm test -- table.test.ts --testNamePattern=merge\ insert
|
npm test -- table.test.ts --testNamePattern=merge\ insert
|
||||||
```
|
```
|
||||||
|
|||||||
@@ -148,33 +148,6 @@ Creates a new empty Table
|
|||||||
|
|
||||||
***
|
***
|
||||||
|
|
||||||
### createNamespace()
|
|
||||||
|
|
||||||
```ts
|
|
||||||
abstract createNamespace(namespacePath, options?): Promise<CreateNamespaceResponse>
|
|
||||||
```
|
|
||||||
|
|
||||||
Create a new namespace at the given path.
|
|
||||||
|
|
||||||
#### Parameters
|
|
||||||
|
|
||||||
* **namespacePath**: `string`[]
|
|
||||||
The namespace path to create.
|
|
||||||
|
|
||||||
* **options?**: `Partial`<[`CreateNamespaceOptions`](../interfaces/CreateNamespaceOptions.md)>
|
|
||||||
Creation `mode`
|
|
||||||
("create" | "exist_ok" | "overwrite") and optional `properties`
|
|
||||||
to attach to the namespace.
|
|
||||||
|
|
||||||
#### Returns
|
|
||||||
|
|
||||||
`Promise`<[`CreateNamespaceResponse`](../interfaces/CreateNamespaceResponse.md)>
|
|
||||||
|
|
||||||
The properties of the
|
|
||||||
created namespace and an optional transaction id.
|
|
||||||
|
|
||||||
***
|
|
||||||
|
|
||||||
### createTable()
|
### createTable()
|
||||||
|
|
||||||
#### createTable(options, namespacePath)
|
#### createTable(options, namespacePath)
|
||||||
@@ -257,29 +230,6 @@ Creates a new Table and initialize it with new data.
|
|||||||
|
|
||||||
***
|
***
|
||||||
|
|
||||||
### describeNamespace()
|
|
||||||
|
|
||||||
```ts
|
|
||||||
abstract describeNamespace(namespacePath): Promise<DescribeNamespaceResponse>
|
|
||||||
```
|
|
||||||
|
|
||||||
Describe a namespace, returning its properties.
|
|
||||||
|
|
||||||
#### Parameters
|
|
||||||
|
|
||||||
* **namespacePath**: `string`[]
|
|
||||||
The namespace path to describe, in
|
|
||||||
parent → child order, e.g. `["analytics", "sales"]`.
|
|
||||||
|
|
||||||
#### Returns
|
|
||||||
|
|
||||||
`Promise`<[`DescribeNamespaceResponse`](../interfaces/DescribeNamespaceResponse.md)>
|
|
||||||
|
|
||||||
The namespace's properties
|
|
||||||
(may be undefined if the namespace has none).
|
|
||||||
|
|
||||||
***
|
|
||||||
|
|
||||||
### display()
|
### display()
|
||||||
|
|
||||||
```ts
|
```ts
|
||||||
@@ -313,36 +263,6 @@ Drop all tables in the database.
|
|||||||
|
|
||||||
***
|
***
|
||||||
|
|
||||||
### dropNamespace()
|
|
||||||
|
|
||||||
```ts
|
|
||||||
abstract dropNamespace(namespacePath, options?): Promise<DropNamespaceResponse>
|
|
||||||
```
|
|
||||||
|
|
||||||
Drop a namespace.
|
|
||||||
|
|
||||||
Use `behavior: "cascade"` to also drop everything contained in the
|
|
||||||
namespace (sub-namespaces and tables). The default `"restrict"`
|
|
||||||
behavior refuses to drop a non-empty namespace.
|
|
||||||
|
|
||||||
#### Parameters
|
|
||||||
|
|
||||||
* **namespacePath**: `string`[]
|
|
||||||
The namespace path to drop.
|
|
||||||
|
|
||||||
* **options?**: `Partial`<[`DropNamespaceOptions`](../interfaces/DropNamespaceOptions.md)>
|
|
||||||
`mode` ("skip" | "fail"
|
|
||||||
for missing-namespace handling) and `behavior` ("restrict" | "cascade").
|
|
||||||
|
|
||||||
#### Returns
|
|
||||||
|
|
||||||
`Promise`<[`DropNamespaceResponse`](../interfaces/DropNamespaceResponse.md)>
|
|
||||||
|
|
||||||
Any properties returned by
|
|
||||||
the server and an optional transaction id.
|
|
||||||
|
|
||||||
***
|
|
||||||
|
|
||||||
### dropTable()
|
### dropTable()
|
||||||
|
|
||||||
```ts
|
```ts
|
||||||
@@ -379,36 +299,6 @@ Return true if the connection has not been closed
|
|||||||
|
|
||||||
***
|
***
|
||||||
|
|
||||||
### listNamespaces()
|
|
||||||
|
|
||||||
```ts
|
|
||||||
abstract listNamespaces(namespacePath?, options?): Promise<ListNamespacesResponse>
|
|
||||||
```
|
|
||||||
|
|
||||||
List the immediate child namespaces under the given parent.
|
|
||||||
|
|
||||||
Results may be paginated. To retrieve subsequent pages, pass the
|
|
||||||
`pageToken` returned by a previous call.
|
|
||||||
|
|
||||||
#### Parameters
|
|
||||||
|
|
||||||
* **namespacePath?**: `string`[]
|
|
||||||
The parent namespace path. Defaults
|
|
||||||
to the root namespace if omitted.
|
|
||||||
|
|
||||||
* **options?**: `Partial`<[`ListNamespacesOptions`](../interfaces/ListNamespacesOptions.md)>
|
|
||||||
Pagination options
|
|
||||||
(`pageToken`, `limit`).
|
|
||||||
|
|
||||||
#### Returns
|
|
||||||
|
|
||||||
`Promise`<[`ListNamespacesResponse`](../interfaces/ListNamespacesResponse.md)>
|
|
||||||
|
|
||||||
Child namespace names and
|
|
||||||
an optional token for fetching the next page.
|
|
||||||
|
|
||||||
***
|
|
||||||
|
|
||||||
### openTable()
|
### openTable()
|
||||||
|
|
||||||
```ts
|
```ts
|
||||||
@@ -437,39 +327,6 @@ Open a table in the database.
|
|||||||
|
|
||||||
***
|
***
|
||||||
|
|
||||||
### renameTable()
|
|
||||||
|
|
||||||
```ts
|
|
||||||
abstract renameTable(
|
|
||||||
currentName,
|
|
||||||
newName,
|
|
||||||
options?): Promise<void>
|
|
||||||
```
|
|
||||||
|
|
||||||
Rename a table.
|
|
||||||
|
|
||||||
Currently only supported by LanceDB Cloud. Local OSS connections and
|
|
||||||
namespace-backed connections (via [connectNamespace](../functions/connectNamespace.md)) reject with
|
|
||||||
a "not supported" error.
|
|
||||||
|
|
||||||
#### Parameters
|
|
||||||
|
|
||||||
* **currentName**: `string`
|
|
||||||
The current name of the table.
|
|
||||||
|
|
||||||
* **newName**: `string`
|
|
||||||
The new name for the table.
|
|
||||||
|
|
||||||
* **options?**: [`RenameTableOptions`](../interfaces/RenameTableOptions.md)
|
|
||||||
Optional namespace paths. When
|
|
||||||
`newNamespacePath` is omitted the table stays in `namespacePath`.
|
|
||||||
|
|
||||||
#### Returns
|
|
||||||
|
|
||||||
`Promise`<`void`>
|
|
||||||
|
|
||||||
***
|
|
||||||
|
|
||||||
### tableNames()
|
### tableNames()
|
||||||
|
|
||||||
#### tableNames(options)
|
#### tableNames(options)
|
||||||
|
|||||||
@@ -76,57 +76,6 @@ the query optimizer chooses a suboptimal path.
|
|||||||
|
|
||||||
***
|
***
|
||||||
|
|
||||||
### useLsmWrite()
|
|
||||||
|
|
||||||
```ts
|
|
||||||
useLsmWrite(useLsmWrite): MergeInsertBuilder
|
|
||||||
```
|
|
||||||
|
|
||||||
Controls whether the merge uses the MemWAL LSM write path.
|
|
||||||
|
|
||||||
By default (unset), a `mergeInsert` on a table with an LSM write spec is
|
|
||||||
routed through Lance's MemWAL shard writer, and a table without one uses
|
|
||||||
the standard path. Pass `false` to force the standard path even when a
|
|
||||||
spec is set. Pass `true` to require a spec — `mergeInsert` rejects if none
|
|
||||||
is installed.
|
|
||||||
|
|
||||||
#### Parameters
|
|
||||||
|
|
||||||
* **useLsmWrite**: `boolean`
|
|
||||||
Whether to use the LSM write path.
|
|
||||||
|
|
||||||
#### Returns
|
|
||||||
|
|
||||||
[`MergeInsertBuilder`](MergeInsertBuilder.md)
|
|
||||||
|
|
||||||
***
|
|
||||||
|
|
||||||
### validateSingleShard()
|
|
||||||
|
|
||||||
```ts
|
|
||||||
validateSingleShard(validateSingleShard): MergeInsertBuilder
|
|
||||||
```
|
|
||||||
|
|
||||||
Controls how an LSM merge checks that its input targets a single shard.
|
|
||||||
|
|
||||||
When a table has an LSM write spec, every row in a `mergeInsert` call must
|
|
||||||
route to the same shard. When `true` (the default), every row is inspected
|
|
||||||
to verify this. When `false`, only the first row is inspected and the
|
|
||||||
shard it routes to is used for the whole input — a faster path for callers
|
|
||||||
that have already pre-sharded their input. Has no effect on tables without
|
|
||||||
an LSM write spec.
|
|
||||||
|
|
||||||
#### Parameters
|
|
||||||
|
|
||||||
* **validateSingleShard**: `boolean`
|
|
||||||
Whether to check every row routes to one shard. Defaults to `true`.
|
|
||||||
|
|
||||||
#### Returns
|
|
||||||
|
|
||||||
[`MergeInsertBuilder`](MergeInsertBuilder.md)
|
|
||||||
|
|
||||||
***
|
|
||||||
|
|
||||||
### whenMatchedUpdateAll()
|
### whenMatchedUpdateAll()
|
||||||
|
|
||||||
```ts
|
```ts
|
||||||
|
|||||||
@@ -343,30 +343,6 @@ This is useful for pagination.
|
|||||||
|
|
||||||
***
|
***
|
||||||
|
|
||||||
### 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()
|
### outputSchema()
|
||||||
|
|
||||||
```ts
|
```ts
|
||||||
|
|||||||
@@ -1,173 +0,0 @@
|
|||||||
[**@lancedb/lancedb**](../README.md) • **Docs**
|
|
||||||
|
|
||||||
***
|
|
||||||
|
|
||||||
[@lancedb/lancedb](../globals.md) / Scannable
|
|
||||||
|
|
||||||
# Class: Scannable
|
|
||||||
|
|
||||||
A data source that can be scanned as a stream of Arrow `RecordBatch`es.
|
|
||||||
|
|
||||||
`Scannable` wraps the schema + optional row count + rescannable flag and
|
|
||||||
a callback that yields batches one at a time. It is passed to consumers
|
|
||||||
(e.g. `Table.add`, `createTable`, `mergeInsert` — follow-up work) that
|
|
||||||
need to pull data without materializing the full dataset in JS memory.
|
|
||||||
|
|
||||||
Batches cross the JS↔Rust boundary as Arrow IPC Stream messages; a fresh
|
|
||||||
writer serializes each batch, and the Rust side decodes it with
|
|
||||||
`arrow_ipc::reader::StreamReader`. One batch is in flight at a time.
|
|
||||||
|
|
||||||
## Properties
|
|
||||||
|
|
||||||
### numRows
|
|
||||||
|
|
||||||
```ts
|
|
||||||
readonly numRows: null | number;
|
|
||||||
```
|
|
||||||
|
|
||||||
***
|
|
||||||
|
|
||||||
### rescannable
|
|
||||||
|
|
||||||
```ts
|
|
||||||
readonly rescannable: boolean;
|
|
||||||
```
|
|
||||||
|
|
||||||
***
|
|
||||||
|
|
||||||
### schema
|
|
||||||
|
|
||||||
```ts
|
|
||||||
readonly schema: Schema<any>;
|
|
||||||
```
|
|
||||||
|
|
||||||
## Methods
|
|
||||||
|
|
||||||
### fromFactory()
|
|
||||||
|
|
||||||
```ts
|
|
||||||
static fromFactory(
|
|
||||||
schema,
|
|
||||||
factory,
|
|
||||||
opts): Promise<Scannable>
|
|
||||||
```
|
|
||||||
|
|
||||||
Build a Scannable from an explicit schema and a factory that returns a
|
|
||||||
fresh batch iterator on each call.
|
|
||||||
|
|
||||||
The factory is invoked once per scan. Each iterator yields
|
|
||||||
`RecordBatch`es matching the declared schema. Use this when you need
|
|
||||||
direct control over the pull loop — for example, to wrap a streaming
|
|
||||||
source whose batches are produced lazily.
|
|
||||||
|
|
||||||
#### Parameters
|
|
||||||
|
|
||||||
* **schema**: `Schema`<`any`>
|
|
||||||
The Arrow schema of the produced batches.
|
|
||||||
|
|
||||||
* **factory**
|
|
||||||
Called at the start of each scan to produce a batch
|
|
||||||
iterator. Must be idempotent when `rescannable` is true.
|
|
||||||
|
|
||||||
* **opts**: [`ScannableOptions`](../interfaces/ScannableOptions.md) = `{}`
|
|
||||||
Optional hints. `rescannable` defaults to `true`; set to
|
|
||||||
`false` if calling `factory()` twice would not reproduce the same data.
|
|
||||||
|
|
||||||
#### Returns
|
|
||||||
|
|
||||||
`Promise`<[`Scannable`](Scannable.md)>
|
|
||||||
|
|
||||||
***
|
|
||||||
|
|
||||||
### fromIterable()
|
|
||||||
|
|
||||||
```ts
|
|
||||||
static fromIterable(
|
|
||||||
schema,
|
|
||||||
iter,
|
|
||||||
opts): Promise<Scannable>
|
|
||||||
```
|
|
||||||
|
|
||||||
Build a Scannable from an iterable of `RecordBatch`es. `rescannable`
|
|
||||||
defaults to `false`. Pass an explicit schema so the consumer can
|
|
||||||
validate before any batch is pulled.
|
|
||||||
|
|
||||||
`opts.rescannable: true` is honest for replayable iterables (Arrays,
|
|
||||||
Sets, or custom iterables whose `[Symbol.iterator]()` returns a fresh
|
|
||||||
iterator each call). It is rejected for one-shot iterables (generators,
|
|
||||||
async generators, or already-an-iterator inputs) because their
|
|
||||||
`[Symbol.iterator]()` returns the same exhausted object on the second
|
|
||||||
scan. For replayable sources outside this shape, use
|
|
||||||
`fromFactory(schema, () => createIter(), { rescannable: true })`.
|
|
||||||
|
|
||||||
Note: when `opts.rescannable` is `true`, the constructor calls
|
|
||||||
`[Symbol.iterator]()` once on the input to perform the structural check.
|
|
||||||
|
|
||||||
#### Parameters
|
|
||||||
|
|
||||||
* **schema**: `Schema`<`any`>
|
|
||||||
|
|
||||||
* **iter**: `Iterable`<`RecordBatch`<`any`>> \| `AsyncIterable`<`RecordBatch`<`any`>>
|
|
||||||
|
|
||||||
* **opts**: [`ScannableOptions`](../interfaces/ScannableOptions.md) = `{}`
|
|
||||||
|
|
||||||
#### Returns
|
|
||||||
|
|
||||||
`Promise`<[`Scannable`](Scannable.md)>
|
|
||||||
|
|
||||||
***
|
|
||||||
|
|
||||||
### fromRecordBatchReader()
|
|
||||||
|
|
||||||
```ts
|
|
||||||
static fromRecordBatchReader(reader, opts): Promise<Scannable>
|
|
||||||
```
|
|
||||||
|
|
||||||
Build a Scannable from an Arrow `RecordBatchReader`. A reader can only
|
|
||||||
be consumed once; `rescannable` defaults to `false`.
|
|
||||||
|
|
||||||
The reader must already be opened (via `.open()`) so its `.schema` is
|
|
||||||
populated. `RecordBatchReader.from(...)` returns an unopened reader.
|
|
||||||
|
|
||||||
`opts.rescannable: true` is rejected because `RecordBatchReader` is a
|
|
||||||
self-iterator (its `[Symbol.iterator]()` returns itself), and this
|
|
||||||
constructor does not call `reader.reset()` between scans, so a second
|
|
||||||
scan would always see an exhausted reader. For genuinely replayable
|
|
||||||
sources, use
|
|
||||||
`fromFactory(schema, () => openReader(), { rescannable: true })`,
|
|
||||||
which mints a fresh reader on each scan.
|
|
||||||
|
|
||||||
#### Parameters
|
|
||||||
|
|
||||||
* **reader**: `RecordBatchReader`<`any`>
|
|
||||||
|
|
||||||
* **opts**: [`ScannableOptions`](../interfaces/ScannableOptions.md) = `{}`
|
|
||||||
|
|
||||||
#### Returns
|
|
||||||
|
|
||||||
`Promise`<[`Scannable`](Scannable.md)>
|
|
||||||
|
|
||||||
***
|
|
||||||
|
|
||||||
### fromTable()
|
|
||||||
|
|
||||||
```ts
|
|
||||||
static fromTable(table, opts): Promise<Scannable>
|
|
||||||
```
|
|
||||||
|
|
||||||
Build a Scannable from an in-memory Arrow `Table`. Always rescannable;
|
|
||||||
the table's batches are replayed on each scan.
|
|
||||||
|
|
||||||
The table's row count is authoritative: `opts.numRows` must either be
|
|
||||||
omitted or equal to `table.numRows`. `opts.rescannable` of `false` is
|
|
||||||
rejected because in-memory Tables are always rescannable.
|
|
||||||
|
|
||||||
#### Parameters
|
|
||||||
|
|
||||||
* **table**: `Table`<`any`>
|
|
||||||
|
|
||||||
* **opts**: [`ScannableOptions`](../interfaces/ScannableOptions.md) = `{}`
|
|
||||||
|
|
||||||
#### Returns
|
|
||||||
|
|
||||||
`Promise`<[`Scannable`](Scannable.md)>
|
|
||||||
@@ -187,25 +187,6 @@ Any attempt to use the table after it is closed will result in an error.
|
|||||||
|
|
||||||
***
|
***
|
||||||
|
|
||||||
### closeLsmWriters()
|
|
||||||
|
|
||||||
```ts
|
|
||||||
abstract closeLsmWriters(): Promise<void>
|
|
||||||
```
|
|
||||||
|
|
||||||
Drain and close any cached MemWAL shard writers held for this table.
|
|
||||||
|
|
||||||
When an [LsmWriteSpec](../interfaces/LsmWriteSpec.md) is installed, `mergeInsert` opens MemWAL
|
|
||||||
shard writers and caches them for reuse across calls. This closes them,
|
|
||||||
flushing pending data; writers reopen lazily on the next `mergeInsert`.
|
|
||||||
It is a no-op when no writers are cached.
|
|
||||||
|
|
||||||
#### Returns
|
|
||||||
|
|
||||||
`Promise`<`void`>
|
|
||||||
|
|
||||||
***
|
|
||||||
|
|
||||||
### countRows()
|
### countRows()
|
||||||
|
|
||||||
```ts
|
```ts
|
||||||
@@ -709,74 +690,6 @@ of the given query
|
|||||||
|
|
||||||
***
|
***
|
||||||
|
|
||||||
### setLsmWriteSpec()
|
|
||||||
|
|
||||||
```ts
|
|
||||||
abstract setLsmWriteSpec(spec): Promise<void>
|
|
||||||
```
|
|
||||||
|
|
||||||
Install an [LsmWriteSpec](../interfaces/LsmWriteSpec.md) on this table, selecting Lance's MemWAL
|
|
||||||
LSM-style write path for future `mergeInsert` calls.
|
|
||||||
|
|
||||||
`LsmWriteSpec` chooses one of three sharding strategies via `specType`:
|
|
||||||
|
|
||||||
- `"bucket"` — hash-bucket writes by the single-column unenforced primary
|
|
||||||
key (`column` and `numBuckets` required).
|
|
||||||
- `"identity"` — shard by the raw value of a scalar `column`.
|
|
||||||
- `"unsharded"` — route every write to a single shard.
|
|
||||||
|
|
||||||
All variants require the table to have an unenforced primary key
|
|
||||||
([Table#setUnenforcedPrimaryKey](Table.md#setunenforcedprimarykey)); bucket sharding additionally
|
|
||||||
requires it to be the single column being bucketed.
|
|
||||||
|
|
||||||
#### Parameters
|
|
||||||
|
|
||||||
* **spec**: [`LsmWriteSpec`](../interfaces/LsmWriteSpec.md)
|
|
||||||
The sharding spec to install.
|
|
||||||
|
|
||||||
#### Returns
|
|
||||||
|
|
||||||
`Promise`<`void`>
|
|
||||||
|
|
||||||
#### Example
|
|
||||||
|
|
||||||
```ts
|
|
||||||
await table.setUnenforcedPrimaryKey("id");
|
|
||||||
await table.setLsmWriteSpec({
|
|
||||||
specType: "bucket",
|
|
||||||
column: "id",
|
|
||||||
numBuckets: 16,
|
|
||||||
maintainedIndexes: ["id_idx"],
|
|
||||||
});
|
|
||||||
```
|
|
||||||
|
|
||||||
***
|
|
||||||
|
|
||||||
### setUnenforcedPrimaryKey()
|
|
||||||
|
|
||||||
```ts
|
|
||||||
abstract setUnenforcedPrimaryKey(columns): Promise<void>
|
|
||||||
```
|
|
||||||
|
|
||||||
Set the unenforced primary key for this table to a single column.
|
|
||||||
|
|
||||||
"Unenforced" means LanceDB does not check uniqueness on writes; the
|
|
||||||
column is recorded in the schema as the primary key for use by features
|
|
||||||
such as `merge_insert`. Only single-column primary keys are supported,
|
|
||||||
and the key cannot be changed once set.
|
|
||||||
|
|
||||||
#### Parameters
|
|
||||||
|
|
||||||
* **columns**: `string` \| `string`[]
|
|
||||||
The primary key column. A one-element
|
|
||||||
array is also accepted; passing more than one column is rejected.
|
|
||||||
|
|
||||||
#### Returns
|
|
||||||
|
|
||||||
`Promise`<`void`>
|
|
||||||
|
|
||||||
***
|
|
||||||
|
|
||||||
### stats()
|
### stats()
|
||||||
|
|
||||||
```ts
|
```ts
|
||||||
@@ -880,23 +793,6 @@ Return the table as an arrow table
|
|||||||
|
|
||||||
***
|
***
|
||||||
|
|
||||||
### unsetLsmWriteSpec()
|
|
||||||
|
|
||||||
```ts
|
|
||||||
abstract unsetLsmWriteSpec(): Promise<void>
|
|
||||||
```
|
|
||||||
|
|
||||||
Remove the [LsmWriteSpec](../interfaces/LsmWriteSpec.md) from this table, reverting to the standard
|
|
||||||
`mergeInsert` write path.
|
|
||||||
|
|
||||||
Errors if no spec is currently set.
|
|
||||||
|
|
||||||
#### Returns
|
|
||||||
|
|
||||||
`Promise`<`void`>
|
|
||||||
|
|
||||||
***
|
|
||||||
|
|
||||||
### update()
|
### update()
|
||||||
|
|
||||||
#### update(opts)
|
#### update(opts)
|
||||||
|
|||||||
@@ -498,30 +498,6 @@ This is useful for pagination.
|
|||||||
|
|
||||||
***
|
***
|
||||||
|
|
||||||
### 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()
|
### outputSchema()
|
||||||
|
|
||||||
```ts
|
```ts
|
||||||
|
|||||||
@@ -1,131 +0,0 @@
|
|||||||
[**@lancedb/lancedb**](../README.md) • **Docs**
|
|
||||||
|
|
||||||
***
|
|
||||||
|
|
||||||
[@lancedb/lancedb](../globals.md) / connectNamespace
|
|
||||||
|
|
||||||
# Function: connectNamespace()
|
|
||||||
|
|
||||||
## connectNamespace(implName, config, options)
|
|
||||||
|
|
||||||
```ts
|
|
||||||
function connectNamespace(
|
|
||||||
implName,
|
|
||||||
config,
|
|
||||||
options?): Promise<Connection>
|
|
||||||
```
|
|
||||||
|
|
||||||
Connect to a LanceDB database through a namespace.
|
|
||||||
|
|
||||||
Unlike [connect](connect.md), which routes by URI scheme (local path vs.
|
|
||||||
`db://` cloud), `connectNamespace` always returns a namespace-backed
|
|
||||||
connection. The `implName` selects the namespace implementation:
|
|
||||||
|
|
||||||
- `"dir"` — directory namespace, configured with [DirNamespaceConfig](../interfaces/DirNamespaceConfig.md).
|
|
||||||
- `"rest"` — remote REST catalog, configured with [RestNamespaceConfig](../interfaces/RestNamespaceConfig.md).
|
|
||||||
- Any other string — full module path for a custom implementation,
|
|
||||||
configured with a free-form string-keyed `properties` map.
|
|
||||||
|
|
||||||
### Parameters
|
|
||||||
|
|
||||||
* **implName**: `"dir"`
|
|
||||||
|
|
||||||
* **config**: [`DirNamespaceConfig`](../interfaces/DirNamespaceConfig.md)
|
|
||||||
|
|
||||||
* **options?**: `Partial`<[`ConnectNamespaceOptions`](../interfaces/ConnectNamespaceOptions.md)>
|
|
||||||
|
|
||||||
### Returns
|
|
||||||
|
|
||||||
`Promise`<[`Connection`](../classes/Connection.md)>
|
|
||||||
|
|
||||||
### Examples
|
|
||||||
|
|
||||||
```ts
|
|
||||||
const db = await connectNamespace("dir", { root: "/path/to/db" });
|
|
||||||
await db.createTable("users", [{ id: 1 }]);
|
|
||||||
```
|
|
||||||
|
|
||||||
```ts
|
|
||||||
const db = await connectNamespace("rest", {
|
|
||||||
uri: "https://catalog.example.com",
|
|
||||||
headers: { "x-api-key": process.env.CATALOG_KEY ?? "" },
|
|
||||||
});
|
|
||||||
```
|
|
||||||
|
|
||||||
```ts
|
|
||||||
const db = await connectNamespace("my.custom.Namespace", {
|
|
||||||
endpoint: "...",
|
|
||||||
});
|
|
||||||
```
|
|
||||||
|
|
||||||
## connectNamespace(implName, config, options)
|
|
||||||
|
|
||||||
```ts
|
|
||||||
function connectNamespace(
|
|
||||||
implName,
|
|
||||||
config,
|
|
||||||
options?): Promise<Connection>
|
|
||||||
```
|
|
||||||
|
|
||||||
Connect through the built-in REST namespace.
|
|
||||||
|
|
||||||
Configured with [RestNamespaceConfig](../interfaces/RestNamespaceConfig.md). See the function-level
|
|
||||||
documentation above for the full surface, examples, and how this
|
|
||||||
relates to [connect](connect.md).
|
|
||||||
|
|
||||||
### Parameters
|
|
||||||
|
|
||||||
* **implName**: `"rest"`
|
|
||||||
|
|
||||||
* **config**: [`RestNamespaceConfig`](../interfaces/RestNamespaceConfig.md)
|
|
||||||
|
|
||||||
* **options?**: `Partial`<[`ConnectNamespaceOptions`](../interfaces/ConnectNamespaceOptions.md)>
|
|
||||||
|
|
||||||
### Returns
|
|
||||||
|
|
||||||
`Promise`<[`Connection`](../classes/Connection.md)>
|
|
||||||
|
|
||||||
### Example
|
|
||||||
|
|
||||||
```ts
|
|
||||||
const db = await connectNamespace("rest", {
|
|
||||||
uri: "https://catalog.example.com",
|
|
||||||
headers: { "x-api-key": process.env.CATALOG_KEY ?? "" },
|
|
||||||
});
|
|
||||||
```
|
|
||||||
|
|
||||||
## connectNamespace(implName, properties, options)
|
|
||||||
|
|
||||||
```ts
|
|
||||||
function connectNamespace(
|
|
||||||
implName,
|
|
||||||
properties,
|
|
||||||
options?): Promise<Connection>
|
|
||||||
```
|
|
||||||
|
|
||||||
Connect through a custom namespace implementation by full module path,
|
|
||||||
configured with a free-form string-keyed `properties` map. Use the
|
|
||||||
typed overloads above for the built-in `"dir"` and `"rest"` impls.
|
|
||||||
|
|
||||||
See the function-level documentation above for examples and how this
|
|
||||||
relates to [connect](connect.md).
|
|
||||||
|
|
||||||
### Parameters
|
|
||||||
|
|
||||||
* **implName**: `string`
|
|
||||||
|
|
||||||
* **properties**: `Record`<`string`, `string`>
|
|
||||||
|
|
||||||
* **options?**: `Partial`<[`ConnectNamespaceOptions`](../interfaces/ConnectNamespaceOptions.md)>
|
|
||||||
|
|
||||||
### Returns
|
|
||||||
|
|
||||||
`Promise`<[`Connection`](../classes/Connection.md)>
|
|
||||||
|
|
||||||
### Example
|
|
||||||
|
|
||||||
```ts
|
|
||||||
const db = await connectNamespace("my.custom.Namespace", {
|
|
||||||
endpoint: "...",
|
|
||||||
});
|
|
||||||
```
|
|
||||||
@@ -32,7 +32,6 @@
|
|||||||
- [PhraseQuery](classes/PhraseQuery.md)
|
- [PhraseQuery](classes/PhraseQuery.md)
|
||||||
- [Query](classes/Query.md)
|
- [Query](classes/Query.md)
|
||||||
- [QueryBase](classes/QueryBase.md)
|
- [QueryBase](classes/QueryBase.md)
|
||||||
- [Scannable](classes/Scannable.md)
|
|
||||||
- [Session](classes/Session.md)
|
- [Session](classes/Session.md)
|
||||||
- [StaticHeaderProvider](classes/StaticHeaderProvider.md)
|
- [StaticHeaderProvider](classes/StaticHeaderProvider.md)
|
||||||
- [Table](classes/Table.md)
|
- [Table](classes/Table.md)
|
||||||
@@ -51,19 +50,11 @@
|
|||||||
- [AlterColumnsResult](interfaces/AlterColumnsResult.md)
|
- [AlterColumnsResult](interfaces/AlterColumnsResult.md)
|
||||||
- [ClientConfig](interfaces/ClientConfig.md)
|
- [ClientConfig](interfaces/ClientConfig.md)
|
||||||
- [ColumnAlteration](interfaces/ColumnAlteration.md)
|
- [ColumnAlteration](interfaces/ColumnAlteration.md)
|
||||||
- [ColumnOrdering](interfaces/ColumnOrdering.md)
|
|
||||||
- [CompactionStats](interfaces/CompactionStats.md)
|
- [CompactionStats](interfaces/CompactionStats.md)
|
||||||
- [ConnectNamespaceOptions](interfaces/ConnectNamespaceOptions.md)
|
|
||||||
- [ConnectionOptions](interfaces/ConnectionOptions.md)
|
- [ConnectionOptions](interfaces/ConnectionOptions.md)
|
||||||
- [CreateNamespaceOptions](interfaces/CreateNamespaceOptions.md)
|
|
||||||
- [CreateNamespaceResponse](interfaces/CreateNamespaceResponse.md)
|
|
||||||
- [CreateTableOptions](interfaces/CreateTableOptions.md)
|
- [CreateTableOptions](interfaces/CreateTableOptions.md)
|
||||||
- [DeleteResult](interfaces/DeleteResult.md)
|
- [DeleteResult](interfaces/DeleteResult.md)
|
||||||
- [DescribeNamespaceResponse](interfaces/DescribeNamespaceResponse.md)
|
|
||||||
- [DirNamespaceConfig](interfaces/DirNamespaceConfig.md)
|
|
||||||
- [DropColumnsResult](interfaces/DropColumnsResult.md)
|
- [DropColumnsResult](interfaces/DropColumnsResult.md)
|
||||||
- [DropNamespaceOptions](interfaces/DropNamespaceOptions.md)
|
|
||||||
- [DropNamespaceResponse](interfaces/DropNamespaceResponse.md)
|
|
||||||
- [ExecutableQuery](interfaces/ExecutableQuery.md)
|
- [ExecutableQuery](interfaces/ExecutableQuery.md)
|
||||||
- [FragmentStatistics](interfaces/FragmentStatistics.md)
|
- [FragmentStatistics](interfaces/FragmentStatistics.md)
|
||||||
- [FragmentSummaryStats](interfaces/FragmentSummaryStats.md)
|
- [FragmentSummaryStats](interfaces/FragmentSummaryStats.md)
|
||||||
@@ -78,19 +69,13 @@
|
|||||||
- [IvfFlatOptions](interfaces/IvfFlatOptions.md)
|
- [IvfFlatOptions](interfaces/IvfFlatOptions.md)
|
||||||
- [IvfPqOptions](interfaces/IvfPqOptions.md)
|
- [IvfPqOptions](interfaces/IvfPqOptions.md)
|
||||||
- [IvfRqOptions](interfaces/IvfRqOptions.md)
|
- [IvfRqOptions](interfaces/IvfRqOptions.md)
|
||||||
- [ListNamespacesOptions](interfaces/ListNamespacesOptions.md)
|
|
||||||
- [ListNamespacesResponse](interfaces/ListNamespacesResponse.md)
|
|
||||||
- [LsmWriteSpec](interfaces/LsmWriteSpec.md)
|
|
||||||
- [MergeResult](interfaces/MergeResult.md)
|
- [MergeResult](interfaces/MergeResult.md)
|
||||||
- [OpenTableOptions](interfaces/OpenTableOptions.md)
|
- [OpenTableOptions](interfaces/OpenTableOptions.md)
|
||||||
- [OptimizeOptions](interfaces/OptimizeOptions.md)
|
- [OptimizeOptions](interfaces/OptimizeOptions.md)
|
||||||
- [OptimizeStats](interfaces/OptimizeStats.md)
|
- [OptimizeStats](interfaces/OptimizeStats.md)
|
||||||
- [QueryExecutionOptions](interfaces/QueryExecutionOptions.md)
|
- [QueryExecutionOptions](interfaces/QueryExecutionOptions.md)
|
||||||
- [RemovalStats](interfaces/RemovalStats.md)
|
- [RemovalStats](interfaces/RemovalStats.md)
|
||||||
- [RenameTableOptions](interfaces/RenameTableOptions.md)
|
|
||||||
- [RestNamespaceConfig](interfaces/RestNamespaceConfig.md)
|
|
||||||
- [RetryConfig](interfaces/RetryConfig.md)
|
- [RetryConfig](interfaces/RetryConfig.md)
|
||||||
- [ScannableOptions](interfaces/ScannableOptions.md)
|
|
||||||
- [ShuffleOptions](interfaces/ShuffleOptions.md)
|
- [ShuffleOptions](interfaces/ShuffleOptions.md)
|
||||||
- [SplitCalculatedOptions](interfaces/SplitCalculatedOptions.md)
|
- [SplitCalculatedOptions](interfaces/SplitCalculatedOptions.md)
|
||||||
- [SplitHashOptions](interfaces/SplitHashOptions.md)
|
- [SplitHashOptions](interfaces/SplitHashOptions.md)
|
||||||
@@ -105,7 +90,6 @@
|
|||||||
- [UpdateResult](interfaces/UpdateResult.md)
|
- [UpdateResult](interfaces/UpdateResult.md)
|
||||||
- [Version](interfaces/Version.md)
|
- [Version](interfaces/Version.md)
|
||||||
- [WriteExecutionOptions](interfaces/WriteExecutionOptions.md)
|
- [WriteExecutionOptions](interfaces/WriteExecutionOptions.md)
|
||||||
- [WriteProgress](interfaces/WriteProgress.md)
|
|
||||||
|
|
||||||
## Type Aliases
|
## Type Aliases
|
||||||
|
|
||||||
@@ -123,7 +107,6 @@
|
|||||||
|
|
||||||
- [RecordBatchIterator](functions/RecordBatchIterator.md)
|
- [RecordBatchIterator](functions/RecordBatchIterator.md)
|
||||||
- [connect](functions/connect.md)
|
- [connect](functions/connect.md)
|
||||||
- [connectNamespace](functions/connectNamespace.md)
|
|
||||||
- [makeArrowTable](functions/makeArrowTable.md)
|
- [makeArrowTable](functions/makeArrowTable.md)
|
||||||
- [packBits](functions/packBits.md)
|
- [packBits](functions/packBits.md)
|
||||||
- [permutationBuilder](functions/permutationBuilder.md)
|
- [permutationBuilder](functions/permutationBuilder.md)
|
||||||
|
|||||||
@@ -19,39 +19,3 @@ mode: "append" | "overwrite";
|
|||||||
If "append" (the default) then the new data will be added to the table
|
If "append" (the default) then the new data will be added to the table
|
||||||
|
|
||||||
If "overwrite" then the new data will replace the existing data in the table.
|
If "overwrite" then the new data will replace the existing data in the table.
|
||||||
|
|
||||||
***
|
|
||||||
|
|
||||||
### progress()
|
|
||||||
|
|
||||||
```ts
|
|
||||||
progress: (progress) => void;
|
|
||||||
```
|
|
||||||
|
|
||||||
Optional callback invoked periodically with write progress.
|
|
||||||
|
|
||||||
The callback is fired once per batch written and once more with
|
|
||||||
`done: true` when the write completes. Calls are dispatched
|
|
||||||
asynchronously to the JS event loop and never block the write — a slow
|
|
||||||
callback will queue events rather than back-pressure the writer.
|
|
||||||
|
|
||||||
Errors thrown from the callback are logged with `console.warn` and
|
|
||||||
swallowed — they do not abort the write.
|
|
||||||
|
|
||||||
#### Parameters
|
|
||||||
|
|
||||||
* **progress**: [`WriteProgress`](WriteProgress.md)
|
|
||||||
|
|
||||||
#### Returns
|
|
||||||
|
|
||||||
`void`
|
|
||||||
|
|
||||||
#### Example
|
|
||||||
|
|
||||||
```ts
|
|
||||||
await table.add(data, {
|
|
||||||
progress: (p) => {
|
|
||||||
console.log(`${p.outputRows}/${p.totalRows ?? "?"} rows`);
|
|
||||||
},
|
|
||||||
});
|
|
||||||
```
|
|
||||||
|
|||||||
@@ -1,31 +0,0 @@
|
|||||||
[**@lancedb/lancedb**](../README.md) • **Docs**
|
|
||||||
|
|
||||||
***
|
|
||||||
|
|
||||||
[@lancedb/lancedb](../globals.md) / ColumnOrdering
|
|
||||||
|
|
||||||
# Interface: ColumnOrdering
|
|
||||||
|
|
||||||
## Properties
|
|
||||||
|
|
||||||
### ascending?
|
|
||||||
|
|
||||||
```ts
|
|
||||||
optional ascending: boolean;
|
|
||||||
```
|
|
||||||
|
|
||||||
***
|
|
||||||
|
|
||||||
### columnName
|
|
||||||
|
|
||||||
```ts
|
|
||||||
columnName: string;
|
|
||||||
```
|
|
||||||
|
|
||||||
***
|
|
||||||
|
|
||||||
### nullsFirst?
|
|
||||||
|
|
||||||
```ts
|
|
||||||
optional nullsFirst: boolean;
|
|
||||||
```
|
|
||||||
@@ -1,54 +0,0 @@
|
|||||||
[**@lancedb/lancedb**](../README.md) • **Docs**
|
|
||||||
|
|
||||||
***
|
|
||||||
|
|
||||||
[@lancedb/lancedb](../globals.md) / ConnectNamespaceOptions
|
|
||||||
|
|
||||||
# Interface: ConnectNamespaceOptions
|
|
||||||
|
|
||||||
## Properties
|
|
||||||
|
|
||||||
### namespaceClientProperties?
|
|
||||||
|
|
||||||
```ts
|
|
||||||
optional namespaceClientProperties: Record<string, string>;
|
|
||||||
```
|
|
||||||
|
|
||||||
Extra properties for the backing namespace client.
|
|
||||||
|
|
||||||
***
|
|
||||||
|
|
||||||
### readConsistencyInterval?
|
|
||||||
|
|
||||||
```ts
|
|
||||||
optional readConsistencyInterval: number;
|
|
||||||
```
|
|
||||||
|
|
||||||
The interval, in seconds, at which to check for updates to the table
|
|
||||||
from other processes. If None, then consistency is not checked. For
|
|
||||||
performance reasons, this is the default. For strong consistency, set
|
|
||||||
this to zero seconds. Then every read will check for updates from other
|
|
||||||
processes. As a compromise, you can set this to a non-zero value for
|
|
||||||
eventual consistency.
|
|
||||||
|
|
||||||
***
|
|
||||||
|
|
||||||
### session?
|
|
||||||
|
|
||||||
```ts
|
|
||||||
optional session: Session;
|
|
||||||
```
|
|
||||||
|
|
||||||
The session to use for this connection. Holds shared caches and other
|
|
||||||
session-specific state.
|
|
||||||
|
|
||||||
***
|
|
||||||
|
|
||||||
### storageOptions?
|
|
||||||
|
|
||||||
```ts
|
|
||||||
optional storageOptions: Record<string, string>;
|
|
||||||
```
|
|
||||||
|
|
||||||
Configuration for object storage. The available options are described
|
|
||||||
at https://docs.lancedb.com/storage/
|
|
||||||
@@ -70,20 +70,16 @@ client used by manifest-enabled native connections.
|
|||||||
optional readConsistencyInterval: number;
|
optional readConsistencyInterval: number;
|
||||||
```
|
```
|
||||||
|
|
||||||
The interval, in seconds, at which to check for updates to the table
|
(For LanceDB OSS only): The interval, in seconds, at which to check for
|
||||||
from other processes. If None, then consistency is not checked. For
|
updates to the table from other processes. If None, then consistency is not
|
||||||
performance reasons, this is the default. For strong consistency, set
|
checked. For performance reasons, this is the default. For strong
|
||||||
this to zero seconds. Then every read will check for updates from other
|
consistency, set this to zero seconds. Then every read will check for
|
||||||
processes. As a compromise, you can set this to a non-zero value for
|
updates from other processes. As a compromise, you can set this to a
|
||||||
eventual consistency. If more than that interval has passed since the
|
non-zero value for eventual consistency. If more than that interval
|
||||||
last check, then the table will be checked for updates. Note: this
|
has passed since the last check, then the table will be checked for updates.
|
||||||
consistency only applies to read operations. Write operations are
|
Note: this consistency only applies to read operations. Write operations are
|
||||||
always consistent.
|
always consistent.
|
||||||
|
|
||||||
Stronger consistency is not free. The smaller the interval, the more
|
|
||||||
often each read pays the cost of checking for updates against object
|
|
||||||
storage, raising per-read latency and cost.
|
|
||||||
|
|
||||||
***
|
***
|
||||||
|
|
||||||
### region?
|
### region?
|
||||||
|
|||||||
@@ -1,27 +0,0 @@
|
|||||||
[**@lancedb/lancedb**](../README.md) • **Docs**
|
|
||||||
|
|
||||||
***
|
|
||||||
|
|
||||||
[@lancedb/lancedb](../globals.md) / CreateNamespaceOptions
|
|
||||||
|
|
||||||
# Interface: CreateNamespaceOptions
|
|
||||||
|
|
||||||
## Properties
|
|
||||||
|
|
||||||
### mode?
|
|
||||||
|
|
||||||
```ts
|
|
||||||
optional mode: "overwrite" | "create" | "exist_ok";
|
|
||||||
```
|
|
||||||
|
|
||||||
Creation mode.
|
|
||||||
|
|
||||||
***
|
|
||||||
|
|
||||||
### properties?
|
|
||||||
|
|
||||||
```ts
|
|
||||||
optional properties: Record<string, string>;
|
|
||||||
```
|
|
||||||
|
|
||||||
Properties to set on the new namespace.
|
|
||||||
@@ -1,23 +0,0 @@
|
|||||||
[**@lancedb/lancedb**](../README.md) • **Docs**
|
|
||||||
|
|
||||||
***
|
|
||||||
|
|
||||||
[@lancedb/lancedb](../globals.md) / CreateNamespaceResponse
|
|
||||||
|
|
||||||
# Interface: CreateNamespaceResponse
|
|
||||||
|
|
||||||
## Properties
|
|
||||||
|
|
||||||
### properties?
|
|
||||||
|
|
||||||
```ts
|
|
||||||
optional properties: Record<string, string>;
|
|
||||||
```
|
|
||||||
|
|
||||||
***
|
|
||||||
|
|
||||||
### transactionId?
|
|
||||||
|
|
||||||
```ts
|
|
||||||
optional transactionId: string;
|
|
||||||
```
|
|
||||||
@@ -1,15 +0,0 @@
|
|||||||
[**@lancedb/lancedb**](../README.md) • **Docs**
|
|
||||||
|
|
||||||
***
|
|
||||||
|
|
||||||
[@lancedb/lancedb](../globals.md) / DescribeNamespaceResponse
|
|
||||||
|
|
||||||
# Interface: DescribeNamespaceResponse
|
|
||||||
|
|
||||||
## Properties
|
|
||||||
|
|
||||||
### properties?
|
|
||||||
|
|
||||||
```ts
|
|
||||||
optional properties: Record<string, string>;
|
|
||||||
```
|
|
||||||
@@ -1,47 +0,0 @@
|
|||||||
[**@lancedb/lancedb**](../README.md) • **Docs**
|
|
||||||
|
|
||||||
***
|
|
||||||
|
|
||||||
[@lancedb/lancedb](../globals.md) / DirNamespaceConfig
|
|
||||||
|
|
||||||
# Interface: DirNamespaceConfig
|
|
||||||
|
|
||||||
Configuration for the built-in directory namespace (`"dir"`).
|
|
||||||
|
|
||||||
The directory namespace stores tables under a single root path (local
|
|
||||||
filesystem or object storage URI). See
|
|
||||||
[https://docs.lancedb.com/namespaces](https://docs.lancedb.com/namespaces) for the documented surface;
|
|
||||||
less-common knobs live under [DirNamespaceConfig.extraProperties](DirNamespaceConfig.md#extraproperties).
|
|
||||||
|
|
||||||
## Properties
|
|
||||||
|
|
||||||
### extraProperties?
|
|
||||||
|
|
||||||
```ts
|
|
||||||
optional extraProperties: Record<string, string>;
|
|
||||||
```
|
|
||||||
|
|
||||||
Additional raw properties passed verbatim to the namespace
|
|
||||||
implementation (e.g. `storage.*`, `credential_vendor.*`). Typed
|
|
||||||
fields above take precedence on key collision.
|
|
||||||
|
|
||||||
***
|
|
||||||
|
|
||||||
### manifestEnabled?
|
|
||||||
|
|
||||||
```ts
|
|
||||||
optional manifestEnabled: boolean;
|
|
||||||
```
|
|
||||||
|
|
||||||
Whether to maintain a namespace manifest at the root. Required for
|
|
||||||
child namespaces. Defaults to true on the impl side.
|
|
||||||
|
|
||||||
***
|
|
||||||
|
|
||||||
### root
|
|
||||||
|
|
||||||
```ts
|
|
||||||
root: string;
|
|
||||||
```
|
|
||||||
|
|
||||||
Root path or URI containing the LanceDB tables.
|
|
||||||
@@ -1,27 +0,0 @@
|
|||||||
[**@lancedb/lancedb**](../README.md) • **Docs**
|
|
||||||
|
|
||||||
***
|
|
||||||
|
|
||||||
[@lancedb/lancedb](../globals.md) / DropNamespaceOptions
|
|
||||||
|
|
||||||
# Interface: DropNamespaceOptions
|
|
||||||
|
|
||||||
## Properties
|
|
||||||
|
|
||||||
### behavior?
|
|
||||||
|
|
||||||
```ts
|
|
||||||
optional behavior: "restrict" | "cascade";
|
|
||||||
```
|
|
||||||
|
|
||||||
Refuse to drop if non-empty (restrict) or drop recursively (cascade).
|
|
||||||
|
|
||||||
***
|
|
||||||
|
|
||||||
### mode?
|
|
||||||
|
|
||||||
```ts
|
|
||||||
optional mode: "fail" | "skip";
|
|
||||||
```
|
|
||||||
|
|
||||||
Whether to skip if the namespace doesn't exist, or fail.
|
|
||||||
@@ -1,23 +0,0 @@
|
|||||||
[**@lancedb/lancedb**](../README.md) • **Docs**
|
|
||||||
|
|
||||||
***
|
|
||||||
|
|
||||||
[@lancedb/lancedb](../globals.md) / DropNamespaceResponse
|
|
||||||
|
|
||||||
# Interface: DropNamespaceResponse
|
|
||||||
|
|
||||||
## Properties
|
|
||||||
|
|
||||||
### properties?
|
|
||||||
|
|
||||||
```ts
|
|
||||||
optional properties: Record<string, string>;
|
|
||||||
```
|
|
||||||
|
|
||||||
***
|
|
||||||
|
|
||||||
### transactionId?
|
|
||||||
|
|
||||||
```ts
|
|
||||||
optional transactionId: string[];
|
|
||||||
```
|
|
||||||
@@ -1,27 +0,0 @@
|
|||||||
[**@lancedb/lancedb**](../README.md) • **Docs**
|
|
||||||
|
|
||||||
***
|
|
||||||
|
|
||||||
[@lancedb/lancedb](../globals.md) / ListNamespacesOptions
|
|
||||||
|
|
||||||
# Interface: ListNamespacesOptions
|
|
||||||
|
|
||||||
## Properties
|
|
||||||
|
|
||||||
### limit?
|
|
||||||
|
|
||||||
```ts
|
|
||||||
optional limit: number;
|
|
||||||
```
|
|
||||||
|
|
||||||
An optional limit to the number of results to return.
|
|
||||||
|
|
||||||
***
|
|
||||||
|
|
||||||
### pageToken?
|
|
||||||
|
|
||||||
```ts
|
|
||||||
optional pageToken: string;
|
|
||||||
```
|
|
||||||
|
|
||||||
Token from a previous response for pagination.
|
|
||||||
@@ -1,23 +0,0 @@
|
|||||||
[**@lancedb/lancedb**](../README.md) • **Docs**
|
|
||||||
|
|
||||||
***
|
|
||||||
|
|
||||||
[@lancedb/lancedb](../globals.md) / ListNamespacesResponse
|
|
||||||
|
|
||||||
# Interface: ListNamespacesResponse
|
|
||||||
|
|
||||||
## Properties
|
|
||||||
|
|
||||||
### namespaces
|
|
||||||
|
|
||||||
```ts
|
|
||||||
namespaces: string[];
|
|
||||||
```
|
|
||||||
|
|
||||||
***
|
|
||||||
|
|
||||||
### pageToken?
|
|
||||||
|
|
||||||
```ts
|
|
||||||
optional pageToken: string;
|
|
||||||
```
|
|
||||||
@@ -1,67 +0,0 @@
|
|||||||
[**@lancedb/lancedb**](../README.md) • **Docs**
|
|
||||||
|
|
||||||
***
|
|
||||||
|
|
||||||
[@lancedb/lancedb](../globals.md) / LsmWriteSpec
|
|
||||||
|
|
||||||
# Interface: LsmWriteSpec
|
|
||||||
|
|
||||||
Specification selecting Lance's MemWAL LSM-style write path for
|
|
||||||
`mergeInsert`.
|
|
||||||
|
|
||||||
`specType` is `"bucket"`, `"identity"`, or `"unsharded"`. For `"bucket"`,
|
|
||||||
`column` and `numBuckets` are required; for `"identity"`, `column` is
|
|
||||||
required and must be a deterministic function of the unenforced primary
|
|
||||||
key (every row with a given primary key must always produce the same
|
|
||||||
`column` value, or upserts of that key can land in different shards and a
|
|
||||||
stale version can win).
|
|
||||||
|
|
||||||
## Properties
|
|
||||||
|
|
||||||
### column?
|
|
||||||
|
|
||||||
```ts
|
|
||||||
optional column: string;
|
|
||||||
```
|
|
||||||
|
|
||||||
Bucket and identity variants: the sharding column.
|
|
||||||
|
|
||||||
***
|
|
||||||
|
|
||||||
### maintainedIndexes?
|
|
||||||
|
|
||||||
```ts
|
|
||||||
optional maintainedIndexes: string[];
|
|
||||||
```
|
|
||||||
|
|
||||||
Names of indexes the MemWAL should keep up to date during writes.
|
|
||||||
|
|
||||||
***
|
|
||||||
|
|
||||||
### numBuckets?
|
|
||||||
|
|
||||||
```ts
|
|
||||||
optional numBuckets: number;
|
|
||||||
```
|
|
||||||
|
|
||||||
Bucket variant: the number of buckets, in `[1, 1024]`.
|
|
||||||
|
|
||||||
***
|
|
||||||
|
|
||||||
### specType
|
|
||||||
|
|
||||||
```ts
|
|
||||||
specType: "bucket" | "identity" | "unsharded";
|
|
||||||
```
|
|
||||||
|
|
||||||
One of `"bucket"`, `"identity"`, or `"unsharded"`.
|
|
||||||
|
|
||||||
***
|
|
||||||
|
|
||||||
### writerConfigDefaults?
|
|
||||||
|
|
||||||
```ts
|
|
||||||
optional writerConfigDefaults: Record<string, string>;
|
|
||||||
```
|
|
||||||
|
|
||||||
Default `ShardWriter` configuration recorded in the MemWAL index.
|
|
||||||
@@ -32,14 +32,6 @@ numInsertedRows: number;
|
|||||||
|
|
||||||
***
|
***
|
||||||
|
|
||||||
### numRows
|
|
||||||
|
|
||||||
```ts
|
|
||||||
numRows: number;
|
|
||||||
```
|
|
||||||
|
|
||||||
***
|
|
||||||
|
|
||||||
### numUpdatedRows
|
### numUpdatedRows
|
||||||
|
|
||||||
```ts
|
```ts
|
||||||
|
|||||||
@@ -1,29 +0,0 @@
|
|||||||
[**@lancedb/lancedb**](../README.md) • **Docs**
|
|
||||||
|
|
||||||
***
|
|
||||||
|
|
||||||
[@lancedb/lancedb](../globals.md) / RenameTableOptions
|
|
||||||
|
|
||||||
# Interface: RenameTableOptions
|
|
||||||
|
|
||||||
## Properties
|
|
||||||
|
|
||||||
### namespacePath?
|
|
||||||
|
|
||||||
```ts
|
|
||||||
optional namespacePath: string[];
|
|
||||||
```
|
|
||||||
|
|
||||||
The namespace path of the table being renamed. Defaults to the root
|
|
||||||
namespace (`[]`) when omitted.
|
|
||||||
|
|
||||||
***
|
|
||||||
|
|
||||||
### newNamespacePath?
|
|
||||||
|
|
||||||
```ts
|
|
||||||
optional newNamespacePath: string[];
|
|
||||||
```
|
|
||||||
|
|
||||||
The namespace path to move the table to as part of the rename. When
|
|
||||||
omitted the table stays in `namespacePath`.
|
|
||||||
@@ -1,47 +0,0 @@
|
|||||||
[**@lancedb/lancedb**](../README.md) • **Docs**
|
|
||||||
|
|
||||||
***
|
|
||||||
|
|
||||||
[@lancedb/lancedb](../globals.md) / RestNamespaceConfig
|
|
||||||
|
|
||||||
# Interface: RestNamespaceConfig
|
|
||||||
|
|
||||||
Configuration for the built-in REST namespace (`"rest"`).
|
|
||||||
|
|
||||||
The REST namespace talks to a remote catalog server over HTTP. See
|
|
||||||
[https://docs.lancedb.com/namespaces](https://docs.lancedb.com/namespaces) for the documented surface;
|
|
||||||
less-common knobs (TLS, metrics) live under
|
|
||||||
[RestNamespaceConfig.extraProperties](RestNamespaceConfig.md#extraproperties).
|
|
||||||
|
|
||||||
## Properties
|
|
||||||
|
|
||||||
### extraProperties?
|
|
||||||
|
|
||||||
```ts
|
|
||||||
optional extraProperties: Record<string, string>;
|
|
||||||
```
|
|
||||||
|
|
||||||
Additional raw properties passed verbatim to the namespace
|
|
||||||
implementation (e.g. `tls.*`, `ops_metrics_enabled`, `delimiter`).
|
|
||||||
Typed fields above take precedence on key collision.
|
|
||||||
|
|
||||||
***
|
|
||||||
|
|
||||||
### headers?
|
|
||||||
|
|
||||||
```ts
|
|
||||||
optional headers: Record<string, string>;
|
|
||||||
```
|
|
||||||
|
|
||||||
HTTP headers forwarded with each request. Keys are passed through
|
|
||||||
as-is (e.g. `"x-api-key"`, `"Authorization"`).
|
|
||||||
|
|
||||||
***
|
|
||||||
|
|
||||||
### uri
|
|
||||||
|
|
||||||
```ts
|
|
||||||
uri: string;
|
|
||||||
```
|
|
||||||
|
|
||||||
Catalog endpoint URL.
|
|
||||||
@@ -1,29 +0,0 @@
|
|||||||
[**@lancedb/lancedb**](../README.md) • **Docs**
|
|
||||||
|
|
||||||
***
|
|
||||||
|
|
||||||
[@lancedb/lancedb](../globals.md) / ScannableOptions
|
|
||||||
|
|
||||||
# Interface: ScannableOptions
|
|
||||||
|
|
||||||
## Properties
|
|
||||||
|
|
||||||
### numRows?
|
|
||||||
|
|
||||||
```ts
|
|
||||||
optional numRows: number;
|
|
||||||
```
|
|
||||||
|
|
||||||
Hint about the number of rows. Not validated against the stream.
|
|
||||||
|
|
||||||
***
|
|
||||||
|
|
||||||
### rescannable?
|
|
||||||
|
|
||||||
```ts
|
|
||||||
optional rescannable: boolean;
|
|
||||||
```
|
|
||||||
|
|
||||||
Whether the source can be scanned more than once. Defaults to `true` for
|
|
||||||
`fromTable` / `fromFactory` and `false` for `fromIterable` /
|
|
||||||
`fromRecordBatchReader`.
|
|
||||||
@@ -1,84 +0,0 @@
|
|||||||
[**@lancedb/lancedb**](../README.md) • **Docs**
|
|
||||||
|
|
||||||
***
|
|
||||||
|
|
||||||
[@lancedb/lancedb](../globals.md) / WriteProgress
|
|
||||||
|
|
||||||
# Interface: WriteProgress
|
|
||||||
|
|
||||||
Progress snapshot for a write operation, delivered to the `progress`
|
|
||||||
callback passed to [Table.add](../classes/Table.md#add).
|
|
||||||
|
|
||||||
## Properties
|
|
||||||
|
|
||||||
### activeTasks
|
|
||||||
|
|
||||||
```ts
|
|
||||||
activeTasks: number;
|
|
||||||
```
|
|
||||||
|
|
||||||
Number of parallel write tasks currently in flight.
|
|
||||||
|
|
||||||
***
|
|
||||||
|
|
||||||
### done
|
|
||||||
|
|
||||||
```ts
|
|
||||||
done: boolean;
|
|
||||||
```
|
|
||||||
|
|
||||||
`true` for the final callback; `false` otherwise.
|
|
||||||
|
|
||||||
***
|
|
||||||
|
|
||||||
### elapsedSeconds
|
|
||||||
|
|
||||||
```ts
|
|
||||||
elapsedSeconds: number;
|
|
||||||
```
|
|
||||||
|
|
||||||
Wall-clock seconds since the write started.
|
|
||||||
|
|
||||||
***
|
|
||||||
|
|
||||||
### outputBytes
|
|
||||||
|
|
||||||
```ts
|
|
||||||
outputBytes: number;
|
|
||||||
```
|
|
||||||
|
|
||||||
Number of bytes written so far.
|
|
||||||
|
|
||||||
***
|
|
||||||
|
|
||||||
### outputRows
|
|
||||||
|
|
||||||
```ts
|
|
||||||
outputRows: number;
|
|
||||||
```
|
|
||||||
|
|
||||||
Number of rows written so far.
|
|
||||||
|
|
||||||
***
|
|
||||||
|
|
||||||
### totalRows?
|
|
||||||
|
|
||||||
```ts
|
|
||||||
optional totalRows: number;
|
|
||||||
```
|
|
||||||
|
|
||||||
Total rows expected, when the input source reports it.
|
|
||||||
|
|
||||||
Always set on the final callback (the one with `done: true`), falling
|
|
||||||
back to the actual number of rows written when the source could not
|
|
||||||
report a row count up front.
|
|
||||||
|
|
||||||
***
|
|
||||||
|
|
||||||
### totalTasks
|
|
||||||
|
|
||||||
```ts
|
|
||||||
totalTasks: number;
|
|
||||||
```
|
|
||||||
|
|
||||||
Total number of parallel write tasks (the write parallelism).
|
|
||||||
@@ -166,12 +166,6 @@ lists the indices that LanceDb supports.
|
|||||||
|
|
||||||
::: lancedb.index.IvfFlat
|
::: lancedb.index.IvfFlat
|
||||||
|
|
||||||
::: lancedb.index.IvfSq
|
|
||||||
|
|
||||||
::: lancedb.index.IvfRq
|
|
||||||
|
|
||||||
::: lancedb.index.HnswFlat
|
|
||||||
|
|
||||||
::: lancedb.table.IndexStatistics
|
::: lancedb.table.IndexStatistics
|
||||||
|
|
||||||
## Querying (Asynchronous)
|
## Querying (Asynchronous)
|
||||||
|
|||||||
@@ -8,7 +8,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<groupId>com.lancedb</groupId>
|
<groupId>com.lancedb</groupId>
|
||||||
<artifactId>lancedb-parent</artifactId>
|
<artifactId>lancedb-parent</artifactId>
|
||||||
<version>0.30.1-beta.0</version>
|
<version>0.28.0-beta.11</version>
|
||||||
<relativePath>../pom.xml</relativePath>
|
<relativePath>../pom.xml</relativePath>
|
||||||
</parent>
|
</parent>
|
||||||
|
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
|
|
||||||
<groupId>com.lancedb</groupId>
|
<groupId>com.lancedb</groupId>
|
||||||
<artifactId>lancedb-parent</artifactId>
|
<artifactId>lancedb-parent</artifactId>
|
||||||
<version>0.30.1-beta.0</version>
|
<version>0.28.0-beta.11</version>
|
||||||
<packaging>pom</packaging>
|
<packaging>pom</packaging>
|
||||||
<name>${project.artifactId}</name>
|
<name>${project.artifactId}</name>
|
||||||
<description>LanceDB Java SDK Parent POM</description>
|
<description>LanceDB Java SDK Parent POM</description>
|
||||||
@@ -28,7 +28,7 @@
|
|||||||
<properties>
|
<properties>
|
||||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||||
<arrow.version>15.0.0</arrow.version>
|
<arrow.version>15.0.0</arrow.version>
|
||||||
<lance-core.version>7.2.0-beta.1</lance-core.version>
|
<lance-core.version>6.0.0</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>
|
||||||
|
|||||||
@@ -3,11 +3,11 @@ The core Rust library is in the `../rust/lancedb` directory, the rust binding
|
|||||||
code is in the `src/` directory and the typescript bindings are in
|
code is in the `src/` directory and the typescript bindings are in
|
||||||
the `lancedb/` directory.
|
the `lancedb/` directory.
|
||||||
|
|
||||||
Whenever you change the Rust code, you will need to recompile: `pnpm build`.
|
Whenever you change the Rust code, you will need to recompile: `npm run build`.
|
||||||
|
|
||||||
Common commands:
|
Common commands:
|
||||||
* Build: `pnpm build`
|
* Build: `npm run build`
|
||||||
* Lint: `pnpm lint`
|
* Lint: `npm run lint`
|
||||||
* Fix lints: `pnpm lint-fix`
|
* Fix lints: `npm run lint-fix`
|
||||||
* Test: `pnpm test`
|
* Test: `npm test`
|
||||||
* Run single test file: `pnpm test __test__/arrow.test.ts`
|
* Run single test file: `npm test __test__/arrow.test.ts`
|
||||||
|
|||||||
@@ -12,22 +12,20 @@ Typescript.
|
|||||||
* `src/`: Rust bindings source code
|
* `src/`: Rust bindings source code
|
||||||
* `lancedb/`: Typescript package source code
|
* `lancedb/`: Typescript package source code
|
||||||
* `__test__/`: Unit tests
|
* `__test__/`: Unit tests
|
||||||
* `examples/`: A pnpm package with the examples shown in the documentation
|
* `examples/`: An npm package with the examples shown in the documentation
|
||||||
|
|
||||||
## Development environment
|
## Development environment
|
||||||
|
|
||||||
To set up your development environment, you will need to install the following:
|
To set up your development environment, you will need to install the following:
|
||||||
|
|
||||||
1. Node.js 22 or later (required by pnpm 11)
|
1. Node.js 14 or later
|
||||||
2. [pnpm](https://pnpm.io/installation) 11 or later (or run via `corepack enable`,
|
2. Rust's package manager, Cargo. Use [rustup](https://rustup.rs/) to install.
|
||||||
which uses the `packageManager` field in `package.json`)
|
3. [protoc](https://grpc.io/docs/protoc-installation/) (Protocol Buffers compiler)
|
||||||
3. Rust's package manager, Cargo. Use [rustup](https://rustup.rs/) to install.
|
|
||||||
4. [protoc](https://grpc.io/docs/protoc-installation/) (Protocol Buffers compiler)
|
|
||||||
|
|
||||||
Initial setup:
|
Initial setup:
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
pnpm install
|
npm install
|
||||||
```
|
```
|
||||||
|
|
||||||
### Commit Hooks
|
### Commit Hooks
|
||||||
@@ -41,38 +39,38 @@ pre-commit install
|
|||||||
|
|
||||||
## Development
|
## Development
|
||||||
|
|
||||||
Most common development commands can be run using the pnpm scripts.
|
Most common development commands can be run using the npm scripts.
|
||||||
|
|
||||||
Build the package
|
Build the package
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
pnpm install
|
npm install
|
||||||
pnpm build
|
npm run build
|
||||||
```
|
```
|
||||||
|
|
||||||
Lint:
|
Lint:
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
pnpm lint
|
npm run lint
|
||||||
```
|
```
|
||||||
|
|
||||||
Format and fix lints:
|
Format and fix lints:
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
pnpm lint-fix
|
npm run lint-fix
|
||||||
```
|
```
|
||||||
|
|
||||||
Run tests:
|
Run tests:
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
pnpm test
|
npm test
|
||||||
```
|
```
|
||||||
|
|
||||||
To run a single test:
|
To run a single test:
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
# Single file: table.test.ts
|
# Single file: table.test.ts
|
||||||
pnpm test -- table.test.ts
|
npm test -- table.test.ts
|
||||||
# Single test: 'merge insert' in table.test.ts
|
# Single test: 'merge insert' in table.test.ts
|
||||||
pnpm test -- table.test.ts --testNamePattern=merge\ insert
|
npm test -- table.test.ts --testNamePattern=merge\ insert
|
||||||
```
|
```
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "lancedb-nodejs"
|
name = "lancedb-nodejs"
|
||||||
edition.workspace = true
|
edition.workspace = true
|
||||||
version = "0.30.1-beta.0"
|
version = "0.28.0-beta.11"
|
||||||
publish = false
|
publish = false
|
||||||
license.workspace = true
|
license.workspace = true
|
||||||
description.workspace = true
|
description.workspace = true
|
||||||
@@ -22,7 +22,6 @@ 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 = { path = "../rust/lancedb", default-features = false }
|
||||||
lance-namespace.workspace = true
|
|
||||||
napi = { version = "3.8.3", default-features = false, features = [
|
napi = { version = "3.8.3", default-features = false, features = [
|
||||||
"napi9",
|
"napi9",
|
||||||
"async"
|
"async"
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
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, Table, connect } from "../lancedb";
|
||||||
import { LocalTable } from "../lancedb/table";
|
import { LocalTable } from "../lancedb/table";
|
||||||
|
|
||||||
describe("when connecting", () => {
|
describe("when connecting", () => {
|
||||||
@@ -47,14 +47,6 @@ 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.renameTable("a", "b")).rejects.toThrow(
|
|
||||||
"Connection is closed",
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should report renameTable as unsupported on an OSS connection", async () => {
|
|
||||||
await db.createTable("a", [{ id: 1 }]);
|
|
||||||
await expect(db.renameTable("a", "b")).rejects.toThrow(/not supported/);
|
|
||||||
});
|
});
|
||||||
it("should be able to create a table from an object arg `createTable(options)`, or args `createTable(name, data, options)`", async () => {
|
it("should be able to create a table from an object arg `createTable(options)`, or args `createTable(name, data, options)`", async () => {
|
||||||
let tbl = await db.createTable("test", [{ id: 1 }, { id: 2 }]);
|
let tbl = await db.createTable("test", [{ id: 1 }, { id: 2 }]);
|
||||||
@@ -171,22 +163,18 @@ describe("given a connection", () => {
|
|||||||
|
|
||||||
let manifestDir =
|
let manifestDir =
|
||||||
tmpDir.name + "/test_manifest_paths_v2_empty.lance/_versions";
|
tmpDir.name + "/test_manifest_paths_v2_empty.lance/_versions";
|
||||||
readdirSync(manifestDir)
|
readdirSync(manifestDir).forEach((file) => {
|
||||||
.filter((f) => f.endsWith(".manifest"))
|
expect(file).toMatch(/^\d{20}\.manifest$/);
|
||||||
.forEach((file) => {
|
});
|
||||||
expect(file).toMatch(/^\d{20}\.manifest$/);
|
|
||||||
});
|
|
||||||
|
|
||||||
table = (await db.createTable("test_manifest_paths_v2", [{ id: 1 }], {
|
table = (await db.createTable("test_manifest_paths_v2", [{ id: 1 }], {
|
||||||
enableV2ManifestPaths: true,
|
enableV2ManifestPaths: true,
|
||||||
})) as LocalTable;
|
})) as LocalTable;
|
||||||
expect(await table.usesV2ManifestPaths()).toBe(true);
|
expect(await table.usesV2ManifestPaths()).toBe(true);
|
||||||
manifestDir = tmpDir.name + "/test_manifest_paths_v2.lance/_versions";
|
manifestDir = tmpDir.name + "/test_manifest_paths_v2.lance/_versions";
|
||||||
readdirSync(manifestDir)
|
readdirSync(manifestDir).forEach((file) => {
|
||||||
.filter((f) => f.endsWith(".manifest"))
|
expect(file).toMatch(/^\d{20}\.manifest$/);
|
||||||
.forEach((file) => {
|
});
|
||||||
expect(file).toMatch(/^\d{20}\.manifest$/);
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should be able to migrate tables to the V2 manifest paths", async () => {
|
it("should be able to migrate tables to the V2 manifest paths", async () => {
|
||||||
@@ -203,20 +191,16 @@ describe("given a connection", () => {
|
|||||||
|
|
||||||
const manifestDir =
|
const manifestDir =
|
||||||
tmpDir.name + "/test_manifest_path_migration.lance/_versions";
|
tmpDir.name + "/test_manifest_path_migration.lance/_versions";
|
||||||
readdirSync(manifestDir)
|
readdirSync(manifestDir).forEach((file) => {
|
||||||
.filter((f) => f.endsWith(".manifest"))
|
expect(file).toMatch(/^\d\.manifest$/);
|
||||||
.forEach((file) => {
|
});
|
||||||
expect(file).toMatch(/^\d\.manifest$/);
|
|
||||||
});
|
|
||||||
|
|
||||||
await table.migrateManifestPathsV2();
|
await table.migrateManifestPathsV2();
|
||||||
expect(await table.usesV2ManifestPaths()).toBe(true);
|
expect(await table.usesV2ManifestPaths()).toBe(true);
|
||||||
|
|
||||||
readdirSync(manifestDir)
|
readdirSync(manifestDir).forEach((file) => {
|
||||||
.filter((f) => f.endsWith(".manifest"))
|
expect(file).toMatch(/^\d{20}\.manifest$/);
|
||||||
.forEach((file) => {
|
});
|
||||||
expect(file).toMatch(/^\d{20}\.manifest$/);
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -322,186 +306,3 @@ describe("clone table functionality", () => {
|
|||||||
).rejects.toThrow("Deep clone is not yet implemented");
|
).rejects.toThrow("Deep clone is not yet implemented");
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("namespaces", () => {
|
|
||||||
let tmpDir: tmp.DirResult;
|
|
||||||
let db: Connection;
|
|
||||||
|
|
||||||
beforeEach(async () => {
|
|
||||||
tmpDir = tmp.dirSync({ unsafeCleanup: true });
|
|
||||||
// The local DirectoryNamespace backend only supports child namespaces
|
|
||||||
// when manifest mode is enabled (see lance-namespace-impls/src/dir.rs).
|
|
||||||
db = await connect(tmpDir.name, {
|
|
||||||
// biome-ignore lint/style/useNamingConvention: opaque backend property key, must match Rust
|
|
||||||
namespaceClientProperties: { manifest_enabled: "true" },
|
|
||||||
});
|
|
||||||
});
|
|
||||||
afterEach(() => tmpDir.removeCallback());
|
|
||||||
|
|
||||||
it("should create and describe a namespace", async () => {
|
|
||||||
await db.createNamespace(["myns"]);
|
|
||||||
const desc = await db.describeNamespace(["myns"]);
|
|
||||||
expect(desc).toBeDefined();
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should list namespaces created at the root", async () => {
|
|
||||||
await db.createNamespace(["alpha"]);
|
|
||||||
await db.createNamespace(["beta"]);
|
|
||||||
const list = await db.listNamespaces();
|
|
||||||
expect(list.namespaces).toEqual(expect.arrayContaining(["alpha", "beta"]));
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should list child namespaces under a parent", async () => {
|
|
||||||
await db.createNamespace(["parent"]);
|
|
||||||
await db.createNamespace(["parent", "child"]);
|
|
||||||
const list = await db.listNamespaces(["parent"]);
|
|
||||||
expect(list.namespaces).toContain("child");
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should drop a namespace", async () => {
|
|
||||||
await db.createNamespace(["ephemeral"]);
|
|
||||||
await db.dropNamespace(["ephemeral"]);
|
|
||||||
const list = await db.listNamespaces();
|
|
||||||
expect(list.namespaces).not.toContain("ephemeral");
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should raise an error on any namespace op after close", async () => {
|
|
||||||
await db.close();
|
|
||||||
await expect(db.describeNamespace(["foo"])).rejects.toThrow(
|
|
||||||
"Connection is closed",
|
|
||||||
);
|
|
||||||
await expect(db.listNamespaces()).rejects.toThrow("Connection is closed");
|
|
||||||
await expect(db.createNamespace(["foo"])).rejects.toThrow(
|
|
||||||
"Connection is closed",
|
|
||||||
);
|
|
||||||
await expect(db.dropNamespace(["foo"])).rejects.toThrow(
|
|
||||||
"Connection is closed",
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should raise an understandable error when describing a non-existent namespace", async () => {
|
|
||||||
await expect(db.describeNamespace(["does-not-exist"])).rejects.toThrow(
|
|
||||||
/not found/i,
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should raise an error when creating a namespace that already exists", async () => {
|
|
||||||
await db.createNamespace(["dup"]);
|
|
||||||
await expect(db.createNamespace(["dup"])).rejects.toThrow();
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should reject an unrecognized createNamespace mode with a clear error", async () => {
|
|
||||||
await expect(
|
|
||||||
// biome-ignore lint/suspicious/noExplicitAny: deliberately bypass TS to test runtime validation
|
|
||||||
db.createNamespace(["x"], { mode: "frobnicate" as any }),
|
|
||||||
).rejects.toThrow(/Invalid mode 'frobnicate'/);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should reject an unrecognized dropNamespace mode with a clear error", async () => {
|
|
||||||
await db.createNamespace(["x"]);
|
|
||||||
await expect(
|
|
||||||
// biome-ignore lint/suspicious/noExplicitAny: deliberately bypass TS to test runtime validation
|
|
||||||
db.dropNamespace(["x"], { mode: "frobnicate" as any }),
|
|
||||||
).rejects.toThrow(/Invalid mode 'frobnicate'/);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should reject an unrecognized dropNamespace behavior with a clear error", async () => {
|
|
||||||
await db.createNamespace(["x"]);
|
|
||||||
await expect(
|
|
||||||
// biome-ignore lint/suspicious/noExplicitAny: deliberately bypass TS to test runtime validation
|
|
||||||
db.dropNamespace(["x"], { behavior: "frobnicate" as any }),
|
|
||||||
).rejects.toThrow(/Invalid behavior 'frobnicate'/);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("connectNamespace", () => {
|
|
||||||
let tmpDir: tmp.DirResult;
|
|
||||||
beforeEach(() => {
|
|
||||||
tmpDir = tmp.dirSync({ unsafeCleanup: true });
|
|
||||||
});
|
|
||||||
afterEach(() => tmpDir.removeCallback());
|
|
||||||
|
|
||||||
it("connects via the dir implementation and supports table ops", async () => {
|
|
||||||
const db = await connectNamespace("dir", { root: tmpDir.name });
|
|
||||||
await db.createTable("users", [{ id: 1 }, { id: 2 }]);
|
|
||||||
await expect(db.tableNames()).resolves.toContain("users");
|
|
||||||
});
|
|
||||||
|
|
||||||
it("throws a clear error when implName is empty", async () => {
|
|
||||||
await expect(connectNamespace("", {})).rejects.toThrow(
|
|
||||||
"implName must be a non-empty string",
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("throws when the namespace implementation is unknown", async () => {
|
|
||||||
await expect(connectNamespace("not-a-real-impl", {})).rejects.toThrow();
|
|
||||||
});
|
|
||||||
|
|
||||||
it("passes storage options through to the namespace", async () => {
|
|
||||||
const db = await connectNamespace(
|
|
||||||
"dir",
|
|
||||||
{ root: tmpDir.name },
|
|
||||||
{ storageOptions: { newTableDataStorageVersion: "stable" } },
|
|
||||||
);
|
|
||||||
await db.createTable("plumbing", [{ id: 1 }]);
|
|
||||||
await expect(db.tableNames()).resolves.toContain("plumbing");
|
|
||||||
});
|
|
||||||
|
|
||||||
it("supports child namespaces when manifestEnabled is true on the dir config", async () => {
|
|
||||||
const writer = await connectNamespace("dir", {
|
|
||||||
root: tmpDir.name,
|
|
||||||
manifestEnabled: true,
|
|
||||||
});
|
|
||||||
await writer.createNamespace(["analytics"]);
|
|
||||||
await writer.createTable("orders", [{ id: 1 }, { id: 2 }], ["analytics"]);
|
|
||||||
await writer.close();
|
|
||||||
|
|
||||||
const reader = await connectNamespace("dir", {
|
|
||||||
root: tmpDir.name,
|
|
||||||
manifestEnabled: true,
|
|
||||||
});
|
|
||||||
await expect(reader.tableNames(["analytics"])).resolves.toContain("orders");
|
|
||||||
const orders = await reader.openTable("orders", ["analytics"]);
|
|
||||||
await expect(orders.countRows()).resolves.toBe(2);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("merges extraProperties into the dir config and is overridden by typed fields", async () => {
|
|
||||||
// Two observable assertions:
|
|
||||||
// - Typed `root` overrides extraProperties.root: createTable would fail
|
|
||||||
// under the bogus path if the override didn't happen.
|
|
||||||
// - extraProperties.manifest_enabled="false" is honored end-to-end. Child
|
|
||||||
// namespaces require manifest mode (default true), so explicitly
|
|
||||||
// disabling it via extraProperties must make createNamespace reject. If
|
|
||||||
// extraProperties pass-through were silently broken, the default would
|
|
||||||
// let createNamespace succeed.
|
|
||||||
const db = await connectNamespace("dir", {
|
|
||||||
root: tmpDir.name,
|
|
||||||
extraProperties: {
|
|
||||||
root: "/should/be/overridden",
|
|
||||||
// biome-ignore lint/style/useNamingConvention: backend property key
|
|
||||||
manifest_enabled: "false",
|
|
||||||
},
|
|
||||||
});
|
|
||||||
await db.createTable("base", [{ id: 1 }]);
|
|
||||||
await expect(db.tableNames()).resolves.toContain("base");
|
|
||||||
await expect(db.createNamespace(["analytics"])).rejects.toThrow();
|
|
||||||
});
|
|
||||||
|
|
||||||
it("flows unknown top-level keys through when implName is dynamic (no silent drop)", async () => {
|
|
||||||
// Routes via the third overload because `impl` is `string`, not the
|
|
||||||
// literal `"dir"`. The dispatcher still notices the runtime value is
|
|
||||||
// "dir", but unknown keys like `manifest_enabled` must not be silently
|
|
||||||
// dropped during the conversion.
|
|
||||||
//
|
|
||||||
// Asserting a *negative* outcome (manifest disabled -> createNamespace
|
|
||||||
// rejects) is required for observability, since the backend default for
|
|
||||||
// `manifest_enabled` is true.
|
|
||||||
const impl: string = "dir";
|
|
||||||
const db = await connectNamespace(impl, {
|
|
||||||
root: tmpDir.name,
|
|
||||||
// biome-ignore lint/style/useNamingConvention: backend property key
|
|
||||||
manifest_enabled: "false",
|
|
||||||
});
|
|
||||||
await expect(db.createNamespace(["mixed"])).rejects.toThrow();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|||||||
@@ -109,209 +109,3 @@ describe("Query outputSchema", () => {
|
|||||||
expect(schema.fields.length).toBe(3);
|
expect(schema.fields.length).toBe(3);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("Query orderBy", () => {
|
|
||||||
let tmpDir: tmp.DirResult;
|
|
||||||
let table: Table;
|
|
||||||
|
|
||||||
beforeEach(async () => {
|
|
||||||
tmpDir = tmp.dirSync({ unsafeCleanup: true });
|
|
||||||
const db = await connect(tmpDir.name);
|
|
||||||
|
|
||||||
// Create table with numeric data for sorting
|
|
||||||
const schema = new Schema([
|
|
||||||
new Field("id", new Int64(), true),
|
|
||||||
new Field("score", new Float32(), true),
|
|
||||||
new Field("name", new Utf8(), true),
|
|
||||||
]);
|
|
||||||
|
|
||||||
const data = makeArrowTable(
|
|
||||||
[
|
|
||||||
{ id: 1n, score: 3.5, name: "charlie" },
|
|
||||||
{ id: 2n, score: 1.2, name: "alice" },
|
|
||||||
{ id: 3n, score: 2.8, name: "bob" },
|
|
||||||
{ id: 4n, score: 0.5, name: "david" },
|
|
||||||
{ id: 5n, score: 4.1, name: "eve" },
|
|
||||||
],
|
|
||||||
{ schema },
|
|
||||||
);
|
|
||||||
table = await db.createTable("test", data);
|
|
||||||
});
|
|
||||||
|
|
||||||
afterEach(() => {
|
|
||||||
tmpDir.removeCallback();
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should sort by single column ascending", async () => {
|
|
||||||
const results = await table
|
|
||||||
.query()
|
|
||||||
.orderBy({ columnName: "score", ascending: true, nullsFirst: false })
|
|
||||||
.toArray();
|
|
||||||
|
|
||||||
expect(results.length).toBe(5);
|
|
||||||
// Verify ascending order
|
|
||||||
expect(results[0].score).toBeCloseTo(0.5, 0.001);
|
|
||||||
expect(results[1].score).toBeCloseTo(1.2, 0.001);
|
|
||||||
expect(results[2].score).toBeCloseTo(2.8, 0.001);
|
|
||||||
expect(results[3].score).toBeCloseTo(3.5, 0.001);
|
|
||||||
expect(results[4].score).toBeCloseTo(4.1, 0.001);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should sort by single column descending", async () => {
|
|
||||||
const results = await table
|
|
||||||
.query()
|
|
||||||
.orderBy({ columnName: "score", ascending: false, nullsFirst: false })
|
|
||||||
.toArray();
|
|
||||||
|
|
||||||
expect(results.length).toBe(5);
|
|
||||||
// Verify descending order
|
|
||||||
expect(results[0].score).toBeCloseTo(4.1, 0.001);
|
|
||||||
expect(results[1].score).toBeCloseTo(3.5, 0.001);
|
|
||||||
expect(results[2].score).toBeCloseTo(2.8, 0.001);
|
|
||||||
expect(results[3].score).toBeCloseTo(1.2, 0.001);
|
|
||||||
expect(results[4].score).toBeCloseTo(0.5, 0.001);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should use ascending as default direction", async () => {
|
|
||||||
const results = await table
|
|
||||||
.query()
|
|
||||||
.orderBy({ columnName: "score" })
|
|
||||||
.toArray();
|
|
||||||
|
|
||||||
expect(results.length).toBe(5);
|
|
||||||
// Verify ascending order (default)
|
|
||||||
expect(results[0].score).toBeCloseTo(0.5, 0.001);
|
|
||||||
expect(results[1].score).toBeCloseTo(1.2, 0.001);
|
|
||||||
expect(results[2].score).toBeCloseTo(2.8, 0.001);
|
|
||||||
expect(results[3].score).toBeCloseTo(3.5, 0.001);
|
|
||||||
expect(results[4].score).toBeCloseTo(4.1, 0.001);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should sort by string column", async () => {
|
|
||||||
const results = await table
|
|
||||||
.query()
|
|
||||||
.orderBy({ columnName: "name" })
|
|
||||||
.toArray();
|
|
||||||
|
|
||||||
expect(results.length).toBe(5);
|
|
||||||
// Verify alphabetical order
|
|
||||||
expect(results[0].name).toBe("alice");
|
|
||||||
expect(results[1].name).toBe("bob");
|
|
||||||
expect(results[2].name).toBe("charlie");
|
|
||||||
expect(results[3].name).toBe("david");
|
|
||||||
expect(results[4].name).toBe("eve");
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should support method chaining with where", async () => {
|
|
||||||
const results = await table
|
|
||||||
.query()
|
|
||||||
.where("score > 2.0")
|
|
||||||
.orderBy({ columnName: "score" })
|
|
||||||
.toArray();
|
|
||||||
expect(results.length).toBe(3);
|
|
||||||
// Verify filtered and sorted
|
|
||||||
expect(results[0].score).toBeCloseTo(2.8, 0.001);
|
|
||||||
expect(results[1].score).toBeCloseTo(3.5, 0.001);
|
|
||||||
expect(results[2].score).toBeCloseTo(4.1, 0.001);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should support method chaining with limit", async () => {
|
|
||||||
const results = await table
|
|
||||||
.query()
|
|
||||||
.orderBy({ columnName: "score", ascending: false })
|
|
||||||
.limit(3)
|
|
||||||
.toArray();
|
|
||||||
|
|
||||||
expect(results.length).toBe(3);
|
|
||||||
// Verify top 3 in descending order
|
|
||||||
expect(results[0].score).toBeCloseTo(4.1, 0.001);
|
|
||||||
expect(results[1].score).toBeCloseTo(3.5, 0.001);
|
|
||||||
expect(results[2].score).toBeCloseTo(2.8, 0.001);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should support method chaining with offset", async () => {
|
|
||||||
const results = await table
|
|
||||||
.query()
|
|
||||||
.orderBy({ columnName: "score" })
|
|
||||||
.offset(2)
|
|
||||||
.limit(2)
|
|
||||||
.toArray();
|
|
||||||
|
|
||||||
expect(results.length).toBe(2);
|
|
||||||
// Verify results skip first 2 and take next 2
|
|
||||||
expect(results[0].score).toBeCloseTo(2.8, 0.001);
|
|
||||||
expect(results[1].score).toBeCloseTo(3.5, 0.001);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should support method chaining with select", async () => {
|
|
||||||
const results = await table
|
|
||||||
.query()
|
|
||||||
.orderBy({ columnName: "name" })
|
|
||||||
.select(["name", "score"])
|
|
||||||
.toArray();
|
|
||||||
|
|
||||||
expect(results.length).toBe(5);
|
|
||||||
// Verify only selected columns are present
|
|
||||||
expect(Object.keys(results[0])).toEqual(["name", "score"]);
|
|
||||||
expect(Object.keys(results[4])).toEqual(["name", "score"]);
|
|
||||||
// Verify sorted by name
|
|
||||||
expect(results[0].name).toBe("alice");
|
|
||||||
expect(results[4].name).toBe("eve");
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should support complex method chaining", async () => {
|
|
||||||
const results = await table
|
|
||||||
.query()
|
|
||||||
.where("score > 1.0")
|
|
||||||
.orderBy({ columnName: "score", ascending: false })
|
|
||||||
.limit(3)
|
|
||||||
.select(["id", "score", "name"])
|
|
||||||
.toArray();
|
|
||||||
|
|
||||||
expect(results.length).toBe(3);
|
|
||||||
// Verify filtered, sorted, limited, and projected
|
|
||||||
expect(results[0].score).toBeCloseTo(4.1, 0.001);
|
|
||||||
expect(results[1].score).toBeCloseTo(3.5, 0.001);
|
|
||||||
expect(results[2].score).toBeCloseTo(2.8, 0.001);
|
|
||||||
expect(Object.keys(results[0])).toEqual(["id", "score", "name"]);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should support multi-column ordering and null placement", async () => {
|
|
||||||
const schema = new Schema([
|
|
||||||
new Field("group", new Int64(), true),
|
|
||||||
new Field("score", new Float32(), true),
|
|
||||||
new Field("name", new Utf8(), true),
|
|
||||||
]);
|
|
||||||
|
|
||||||
const data = makeArrowTable(
|
|
||||||
[
|
|
||||||
{ group: 1n, score: null, name: "z" },
|
|
||||||
{ group: 1n, score: 1.0, name: "b" },
|
|
||||||
{ group: 1n, score: 1.0, name: "a" },
|
|
||||||
{ group: 2n, score: 0.5, name: "c" },
|
|
||||||
],
|
|
||||||
{ schema },
|
|
||||||
);
|
|
||||||
const nullTable = await (await connect(tmpDir.name)).createTable(
|
|
||||||
"test_multi_order",
|
|
||||||
data,
|
|
||||||
{ mode: "overwrite" },
|
|
||||||
);
|
|
||||||
|
|
||||||
const results = await nullTable
|
|
||||||
.query()
|
|
||||||
.orderBy([
|
|
||||||
{ columnName: "group", ascending: true, nullsFirst: false },
|
|
||||||
{ columnName: "score", ascending: true, nullsFirst: true },
|
|
||||||
{ columnName: "name", ascending: true, nullsFirst: false },
|
|
||||||
])
|
|
||||||
.toArray();
|
|
||||||
|
|
||||||
expect(results.map((r) => [r.group, r.score, r.name])).toEqual([
|
|
||||||
[1n, null, "z"],
|
|
||||||
[1n, 1.0, "a"],
|
|
||||||
[1n, 1.0, "b"],
|
|
||||||
[2n, 0.5, "c"],
|
|
||||||
]);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|||||||
@@ -617,68 +617,4 @@ describe("remote connection", () => {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("renameTable", () => {
|
|
||||||
async function captureRenameRequest(
|
|
||||||
call: (db: Connection) => Promise<void>,
|
|
||||||
): Promise<{ url: string; body: Record<string, unknown> }> {
|
|
||||||
let captured: { url: string; body: Record<string, unknown> } | undefined;
|
|
||||||
await withMockDatabase((req, res) => {
|
|
||||||
let raw = "";
|
|
||||||
req.on("data", (chunk) => {
|
|
||||||
raw += chunk;
|
|
||||||
});
|
|
||||||
req.on("end", () => {
|
|
||||||
captured = {
|
|
||||||
url: req.url ?? "",
|
|
||||||
body: raw ? JSON.parse(raw) : {},
|
|
||||||
};
|
|
||||||
res.writeHead(200, { "Content-Type": "application/json" }).end("");
|
|
||||||
});
|
|
||||||
}, call);
|
|
||||||
if (!captured) {
|
|
||||||
throw new Error("mock server never saw a request");
|
|
||||||
}
|
|
||||||
return captured;
|
|
||||||
}
|
|
||||||
|
|
||||||
it("sends rename request for a table in the root namespace", async () => {
|
|
||||||
const { url, body } = await captureRenameRequest(async (db) => {
|
|
||||||
await db.renameTable("table1", "table2");
|
|
||||||
});
|
|
||||||
expect(url).toBe("/v1/table/table1/rename/");
|
|
||||||
// biome-ignore lint/style/useNamingConvention: snake_case mandated by the server wire format
|
|
||||||
expect(body).toEqual({ new_table_name: "table2" });
|
|
||||||
});
|
|
||||||
|
|
||||||
it("omits new_namespace when only the current namespace is supplied", async () => {
|
|
||||||
// Safe-default check: passing namespacePath alone must not send
|
|
||||||
// `new_namespace`, so the server keeps the table in its current
|
|
||||||
// namespace instead of silently moving it to root.
|
|
||||||
const { url, body } = await captureRenameRequest(async (db) => {
|
|
||||||
await db.renameTable("table1", "table2", {
|
|
||||||
namespacePath: ["ns1"],
|
|
||||||
});
|
|
||||||
});
|
|
||||||
expect(url).toBe("/v1/table/ns1$table1/rename/");
|
|
||||||
// biome-ignore lint/style/useNamingConvention: snake_case mandated by the server wire format
|
|
||||||
expect(body).toEqual({ new_table_name: "table2" });
|
|
||||||
});
|
|
||||||
|
|
||||||
it("includes new_namespace in the body for a cross-namespace rename", async () => {
|
|
||||||
const { url, body } = await captureRenameRequest(async (db) => {
|
|
||||||
await db.renameTable("table1", "table2", {
|
|
||||||
namespacePath: ["ns1"],
|
|
||||||
newNamespacePath: ["ns2"],
|
|
||||||
});
|
|
||||||
});
|
|
||||||
expect(url).toBe("/v1/table/ns1$table1/rename/");
|
|
||||||
expect(body).toEqual({
|
|
||||||
// biome-ignore lint/style/useNamingConvention: snake_case mandated by the server wire format
|
|
||||||
new_table_name: "table2",
|
|
||||||
// biome-ignore lint/style/useNamingConvention: snake_case mandated by the server wire format
|
|
||||||
new_namespace: ["ns2"],
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,438 +0,0 @@
|
|||||||
// SPDX-License-Identifier: Apache-2.0
|
|
||||||
// SPDX-FileCopyrightText: Copyright The LanceDB Authors
|
|
||||||
|
|
||||||
import {
|
|
||||||
Field,
|
|
||||||
Float16,
|
|
||||||
Int32,
|
|
||||||
type RecordBatch,
|
|
||||||
RecordBatchReader,
|
|
||||||
Schema,
|
|
||||||
tableToIPC,
|
|
||||||
} from "apache-arrow";
|
|
||||||
import { makeArrowTable, makeEmptyTable } from "../lancedb/arrow";
|
|
||||||
import { Scannable } from "../lancedb/scannable";
|
|
||||||
|
|
||||||
function makeTable() {
|
|
||||||
return makeArrowTable(
|
|
||||||
[
|
|
||||||
{ id: 1, name: "a" },
|
|
||||||
{ id: 2, name: "b" },
|
|
||||||
{ id: 3, name: "c" },
|
|
||||||
],
|
|
||||||
{ vectorColumns: {} },
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
async function makeReader(): Promise<RecordBatchReader> {
|
|
||||||
// `RecordBatchReader.from()` returns an unopened reader; `.schema` is only
|
|
||||||
// populated after `.open()`. Opening sync readers is synchronous.
|
|
||||||
const reader = RecordBatchReader.from(tableToIPC(makeTable()));
|
|
||||||
return reader.open() as RecordBatchReader;
|
|
||||||
}
|
|
||||||
|
|
||||||
describe("Scannable", () => {
|
|
||||||
describe("fromTable", () => {
|
|
||||||
test("reflects schema, numRows, and defaults rescannable=true", async () => {
|
|
||||||
const table = makeTable();
|
|
||||||
const scannable = await Scannable.fromTable(table);
|
|
||||||
|
|
||||||
expect(scannable.schema).toBe(table.schema);
|
|
||||||
expect(scannable.numRows).toBe(table.numRows);
|
|
||||||
expect(scannable.rescannable).toBe(true);
|
|
||||||
});
|
|
||||||
|
|
||||||
test("throws when opts.numRows does not match table.numRows", async () => {
|
|
||||||
await expect(
|
|
||||||
Scannable.fromTable(makeTable(), { numRows: 42 }),
|
|
||||||
).rejects.toThrow(/does not match table\.numRows/);
|
|
||||||
});
|
|
||||||
|
|
||||||
test("throws when opts.rescannable is false", async () => {
|
|
||||||
await expect(
|
|
||||||
Scannable.fromTable(makeTable(), { rescannable: false }),
|
|
||||||
).rejects.toThrow(/always rescannable/);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("fromRecordBatchReader", () => {
|
|
||||||
test("reflects schema and defaults numRows=null, rescannable=false", async () => {
|
|
||||||
const reader = await makeReader();
|
|
||||||
const scannable = await Scannable.fromRecordBatchReader(reader);
|
|
||||||
|
|
||||||
expect(scannable.schema).toBe(reader.schema);
|
|
||||||
expect(scannable.numRows).toBeNull();
|
|
||||||
expect(scannable.rescannable).toBe(false);
|
|
||||||
});
|
|
||||||
|
|
||||||
test("honors numRows override", async () => {
|
|
||||||
const scannable = await Scannable.fromRecordBatchReader(
|
|
||||||
await makeReader(),
|
|
||||||
{ numRows: 3 },
|
|
||||||
);
|
|
||||||
|
|
||||||
expect(scannable.numRows).toBe(3);
|
|
||||||
expect(scannable.rescannable).toBe(false);
|
|
||||||
});
|
|
||||||
|
|
||||||
test("rescannable: false explicit does not throw", async () => {
|
|
||||||
const reader = await makeReader();
|
|
||||||
const scannable = await Scannable.fromRecordBatchReader(reader, {
|
|
||||||
rescannable: false,
|
|
||||||
});
|
|
||||||
expect(scannable.rescannable).toBe(false);
|
|
||||||
});
|
|
||||||
|
|
||||||
test("throws when opts.rescannable is true", async () => {
|
|
||||||
const reader = await makeReader();
|
|
||||||
await expect(
|
|
||||||
Scannable.fromRecordBatchReader(reader, { rescannable: true }),
|
|
||||||
).rejects.toThrow(/does not accept rescannable/);
|
|
||||||
});
|
|
||||||
|
|
||||||
test("throws when opts.rescannable is true even alongside numRows", async () => {
|
|
||||||
const reader = await makeReader();
|
|
||||||
await expect(
|
|
||||||
Scannable.fromRecordBatchReader(reader, {
|
|
||||||
numRows: 3,
|
|
||||||
rescannable: true,
|
|
||||||
}),
|
|
||||||
).rejects.toThrow(/does not accept rescannable/);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("fromIterable", () => {
|
|
||||||
test("accepts a sync iterable of batches", async () => {
|
|
||||||
const table = makeTable();
|
|
||||||
const scannable = await Scannable.fromIterable(
|
|
||||||
table.schema,
|
|
||||||
table.batches,
|
|
||||||
);
|
|
||||||
|
|
||||||
expect(scannable.schema).toBe(table.schema);
|
|
||||||
expect(scannable.numRows).toBeNull();
|
|
||||||
expect(scannable.rescannable).toBe(false);
|
|
||||||
});
|
|
||||||
|
|
||||||
test("accepts an async iterable of batches", async () => {
|
|
||||||
const table = makeTable();
|
|
||||||
async function* generator(): AsyncGenerator<RecordBatch> {
|
|
||||||
for (const batch of table.batches) {
|
|
||||||
yield batch;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const scannable = await Scannable.fromIterable(table.schema, generator());
|
|
||||||
expect(scannable.schema).toBe(table.schema);
|
|
||||||
expect(scannable.rescannable).toBe(false);
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("rescannable: true detection", () => {
|
|
||||||
// Replayable inputs: [Symbol.iterator]() / [Symbol.asyncIterator]()
|
|
||||||
// returns a fresh iterator each call. Must NOT throw.
|
|
||||||
|
|
||||||
test("Array passes (fresh ArrayIterator each call)", async () => {
|
|
||||||
const table = makeTable();
|
|
||||||
const scannable = await Scannable.fromIterable(
|
|
||||||
table.schema,
|
|
||||||
table.batches,
|
|
||||||
{ rescannable: true },
|
|
||||||
);
|
|
||||||
expect(scannable.rescannable).toBe(true);
|
|
||||||
});
|
|
||||||
|
|
||||||
test("Set passes (fresh SetIterator each call)", async () => {
|
|
||||||
const table = makeTable();
|
|
||||||
const set = new Set<RecordBatch>(table.batches);
|
|
||||||
const scannable = await Scannable.fromIterable(table.schema, set, {
|
|
||||||
rescannable: true,
|
|
||||||
});
|
|
||||||
expect(scannable.rescannable).toBe(true);
|
|
||||||
});
|
|
||||||
|
|
||||||
test("custom Iterable returning a fresh iterator passes", async () => {
|
|
||||||
const table = makeTable();
|
|
||||||
const replayable: Iterable<RecordBatch> = {
|
|
||||||
[Symbol.iterator]() {
|
|
||||||
return table.batches[Symbol.iterator]();
|
|
||||||
},
|
|
||||||
};
|
|
||||||
const scannable = await Scannable.fromIterable(
|
|
||||||
table.schema,
|
|
||||||
replayable,
|
|
||||||
{ rescannable: true },
|
|
||||||
);
|
|
||||||
expect(scannable.rescannable).toBe(true);
|
|
||||||
});
|
|
||||||
|
|
||||||
test("object with generator method passes (fresh generator each call)", async () => {
|
|
||||||
const table = makeTable();
|
|
||||||
const replayable: Iterable<RecordBatch> = {
|
|
||||||
*[Symbol.iterator]() {
|
|
||||||
for (const batch of table.batches) yield batch;
|
|
||||||
},
|
|
||||||
};
|
|
||||||
const scannable = await Scannable.fromIterable(
|
|
||||||
table.schema,
|
|
||||||
replayable,
|
|
||||||
{ rescannable: true },
|
|
||||||
);
|
|
||||||
expect(scannable.rescannable).toBe(true);
|
|
||||||
});
|
|
||||||
|
|
||||||
test("empty Array passes (replayable degenerate case)", async () => {
|
|
||||||
const schema = makeTable().schema;
|
|
||||||
const scannable = await Scannable.fromIterable(
|
|
||||||
schema,
|
|
||||||
[] as RecordBatch[],
|
|
||||||
{ rescannable: true },
|
|
||||||
);
|
|
||||||
expect(scannable.rescannable).toBe(true);
|
|
||||||
});
|
|
||||||
|
|
||||||
// One-shot inputs: [Symbol.iterator]() / [Symbol.asyncIterator]()
|
|
||||||
// returns the same object, or the input is already-an-iterator.
|
|
||||||
// Must throw with a /one-shot/ message.
|
|
||||||
|
|
||||||
test("sync generator throws", async () => {
|
|
||||||
const table = makeTable();
|
|
||||||
function* generator(): Generator<RecordBatch> {
|
|
||||||
for (const batch of table.batches) yield batch;
|
|
||||||
}
|
|
||||||
await expect(
|
|
||||||
Scannable.fromIterable(table.schema, generator(), {
|
|
||||||
rescannable: true,
|
|
||||||
}),
|
|
||||||
).rejects.toThrow(/one-shot/);
|
|
||||||
});
|
|
||||||
|
|
||||||
test("async generator throws", async () => {
|
|
||||||
const table = makeTable();
|
|
||||||
async function* generator(): AsyncGenerator<RecordBatch> {
|
|
||||||
for (const batch of table.batches) yield batch;
|
|
||||||
}
|
|
||||||
await expect(
|
|
||||||
Scannable.fromIterable(table.schema, generator(), {
|
|
||||||
rescannable: true,
|
|
||||||
}),
|
|
||||||
).rejects.toThrow(/one-shot/);
|
|
||||||
});
|
|
||||||
|
|
||||||
test("empty generator throws (one-shot degenerate case)", async () => {
|
|
||||||
const schema = makeTable().schema;
|
|
||||||
function* generator(): Generator<RecordBatch> {
|
|
||||||
// intentionally empty; yields nothing.
|
|
||||||
}
|
|
||||||
await expect(
|
|
||||||
Scannable.fromIterable(schema, generator(), { rescannable: true }),
|
|
||||||
).rejects.toThrow(/one-shot/);
|
|
||||||
});
|
|
||||||
|
|
||||||
test("custom self-iterator throws", async () => {
|
|
||||||
const table = makeTable();
|
|
||||||
const batches = table.batches;
|
|
||||||
let i = 0;
|
|
||||||
const oneShot: Iterable<RecordBatch> & Iterator<RecordBatch> = {
|
|
||||||
[Symbol.iterator]() {
|
|
||||||
return this;
|
|
||||||
},
|
|
||||||
next() {
|
|
||||||
if (i >= batches.length) {
|
|
||||||
return { done: true, value: undefined };
|
|
||||||
}
|
|
||||||
return { done: false, value: batches[i++] };
|
|
||||||
},
|
|
||||||
};
|
|
||||||
await expect(
|
|
||||||
Scannable.fromIterable(table.schema, oneShot, { rescannable: true }),
|
|
||||||
).rejects.toThrow(/one-shot/);
|
|
||||||
});
|
|
||||||
|
|
||||||
test("Array.values() (IterableIterator) throws", async () => {
|
|
||||||
const table = makeTable();
|
|
||||||
const iter = table.batches.values();
|
|
||||||
await expect(
|
|
||||||
Scannable.fromIterable(table.schema, iter, { rescannable: true }),
|
|
||||||
).rejects.toThrow(/one-shot/);
|
|
||||||
});
|
|
||||||
|
|
||||||
test("raw iterator (only `.next`) throws", async () => {
|
|
||||||
const table = makeTable();
|
|
||||||
const batches = table.batches;
|
|
||||||
let i = 0;
|
|
||||||
const rawIter = {
|
|
||||||
next(): IteratorResult<RecordBatch> {
|
|
||||||
if (i >= batches.length) {
|
|
||||||
return { done: true, value: undefined };
|
|
||||||
}
|
|
||||||
return { done: false, value: batches[i++] };
|
|
||||||
},
|
|
||||||
};
|
|
||||||
await expect(
|
|
||||||
Scannable.fromIterable(
|
|
||||||
table.schema,
|
|
||||||
rawIter as unknown as Iterable<RecordBatch>,
|
|
||||||
{ rescannable: true },
|
|
||||||
),
|
|
||||||
).rejects.toThrow(/one-shot/);
|
|
||||||
});
|
|
||||||
|
|
||||||
// Edge: null/undefined must not crash the detection helper. The
|
|
||||||
// null check belongs to `normalizeIterator` and only fires when a
|
|
||||||
// scan starts.
|
|
||||||
|
|
||||||
test("null input does not crash detection at construction", async () => {
|
|
||||||
const schema = makeTable().schema;
|
|
||||||
await expect(
|
|
||||||
Scannable.fromIterable(
|
|
||||||
schema,
|
|
||||||
null as unknown as Iterable<RecordBatch>,
|
|
||||||
{
|
|
||||||
rescannable: true,
|
|
||||||
},
|
|
||||||
),
|
|
||||||
).resolves.toBeDefined();
|
|
||||||
});
|
|
||||||
|
|
||||||
test("undefined input does not crash detection at construction", async () => {
|
|
||||||
const schema = makeTable().schema;
|
|
||||||
await expect(
|
|
||||||
Scannable.fromIterable(
|
|
||||||
schema,
|
|
||||||
undefined as unknown as Iterable<RecordBatch>,
|
|
||||||
{ rescannable: true },
|
|
||||||
),
|
|
||||||
).resolves.toBeDefined();
|
|
||||||
});
|
|
||||||
|
|
||||||
// Default (rescannable omitted) skips the check entirely, so even
|
|
||||||
// pathological inputs construct without throwing here.
|
|
||||||
|
|
||||||
test("rescannable omitted skips detection entirely (generator passes)", async () => {
|
|
||||||
const table = makeTable();
|
|
||||||
function* generator(): Generator<RecordBatch> {
|
|
||||||
for (const batch of table.batches) yield batch;
|
|
||||||
}
|
|
||||||
const scannable = await Scannable.fromIterable(
|
|
||||||
table.schema,
|
|
||||||
generator(),
|
|
||||||
);
|
|
||||||
expect(scannable.rescannable).toBe(false);
|
|
||||||
});
|
|
||||||
|
|
||||||
test("rescannable: false explicit skips detection entirely (generator passes)", async () => {
|
|
||||||
const table = makeTable();
|
|
||||||
function* generator(): Generator<RecordBatch> {
|
|
||||||
for (const batch of table.batches) yield batch;
|
|
||||||
}
|
|
||||||
const scannable = await Scannable.fromIterable(
|
|
||||||
table.schema,
|
|
||||||
generator(),
|
|
||||||
{ rescannable: false },
|
|
||||||
);
|
|
||||||
expect(scannable.rescannable).toBe(false);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("fromFactory", () => {
|
|
||||||
test("defaults rescannable=true and does not invoke the factory eagerly", async () => {
|
|
||||||
const table = makeTable();
|
|
||||||
const factory = jest.fn(() => table.batches);
|
|
||||||
|
|
||||||
const scannable = await Scannable.fromFactory(table.schema, factory);
|
|
||||||
|
|
||||||
expect(scannable.schema).toBe(table.schema);
|
|
||||||
expect(scannable.rescannable).toBe(true);
|
|
||||||
expect(factory).not.toHaveBeenCalled();
|
|
||||||
});
|
|
||||||
|
|
||||||
test("honors rescannable and numRows overrides", async () => {
|
|
||||||
const table = makeTable();
|
|
||||||
const scannable = await Scannable.fromFactory(
|
|
||||||
table.schema,
|
|
||||||
() => table.batches,
|
|
||||||
{ numRows: 7, rescannable: false },
|
|
||||||
);
|
|
||||||
|
|
||||||
expect(scannable.numRows).toBe(7);
|
|
||||||
expect(scannable.rescannable).toBe(false);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("validation", () => {
|
|
||||||
test("throws when numRows is negative", async () => {
|
|
||||||
await expect(
|
|
||||||
Scannable.fromFactory(makeTable().schema, () => [], { numRows: -1 }),
|
|
||||||
).rejects.toThrow(/non-negative/);
|
|
||||||
});
|
|
||||||
|
|
||||||
test("throws when numRows is not an integer", async () => {
|
|
||||||
await expect(
|
|
||||||
Scannable.fromFactory(makeTable().schema, () => [], { numRows: 3.5 }),
|
|
||||||
).rejects.toThrow(/integer/);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("native handle", () => {
|
|
||||||
test("exposes a native handle via inner", async () => {
|
|
||||||
const scannable = await Scannable.fromTable(makeTable());
|
|
||||||
expect(scannable.inner).toBeDefined();
|
|
||||||
expect(typeof scannable.inner).toBe("object");
|
|
||||||
expect(scannable.inner).not.toBeNull();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
// Schema-variety construction tests. Each asserts that construction
|
|
||||||
// succeeds against a richer Arrow schema, which transitively exercises
|
|
||||||
// schema serialization and the Rust-side `ipc_file_to_schema` for types
|
|
||||||
// beyond flat primitives.
|
|
||||||
describe("schema variety", () => {
|
|
||||||
test("accepts an empty table", async () => {
|
|
||||||
const schema = new Schema([new Field("id", new Int32(), true)]);
|
|
||||||
const table = makeEmptyTable(schema);
|
|
||||||
const scannable = await Scannable.fromTable(table);
|
|
||||||
|
|
||||||
expect(scannable.numRows).toBe(0);
|
|
||||||
expect(scannable.schema).toBe(table.schema);
|
|
||||||
});
|
|
||||||
|
|
||||||
test("accepts nested struct and list columns", async () => {
|
|
||||||
const table = makeArrowTable(
|
|
||||||
[
|
|
||||||
{ id: 1, point: { x: 0, y: 0 }, tags: ["a", "b"] },
|
|
||||||
{ id: 2, point: { x: 1, y: 2 }, tags: ["c"] },
|
|
||||||
],
|
|
||||||
{ vectorColumns: {} },
|
|
||||||
);
|
|
||||||
const scannable = await Scannable.fromTable(table);
|
|
||||||
|
|
||||||
expect(scannable.schema).toBe(table.schema);
|
|
||||||
expect(scannable.numRows).toBe(2);
|
|
||||||
});
|
|
||||||
|
|
||||||
test("accepts a FixedSizeList (vector) column", async () => {
|
|
||||||
const table = makeArrowTable(
|
|
||||||
[
|
|
||||||
{ id: 1, vec: [1, 2, 3] },
|
|
||||||
{ id: 2, vec: [4, 5, 6] },
|
|
||||||
],
|
|
||||||
{ vectorColumns: { vec: { type: new Float16() } } },
|
|
||||||
);
|
|
||||||
const scannable = await Scannable.fromTable(table);
|
|
||||||
|
|
||||||
expect(scannable.schema).toBe(table.schema);
|
|
||||||
expect(scannable.numRows).toBe(2);
|
|
||||||
});
|
|
||||||
|
|
||||||
test("accepts a table with many columns", async () => {
|
|
||||||
const row: Record<string, number> = {};
|
|
||||||
for (let i = 0; i < 50; i++) row[`c${i}`] = i;
|
|
||||||
const table = makeArrowTable([row, row], { vectorColumns: {} });
|
|
||||||
const scannable = await Scannable.fromTable(table);
|
|
||||||
|
|
||||||
expect(scannable.schema.fields.length).toBe(50);
|
|
||||||
expect(scannable.numRows).toBe(2);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -28,7 +28,6 @@ import {
|
|||||||
List,
|
List,
|
||||||
Schema,
|
Schema,
|
||||||
SchemaLike,
|
SchemaLike,
|
||||||
Struct,
|
|
||||||
Type,
|
Type,
|
||||||
Uint8,
|
Uint8,
|
||||||
Utf8,
|
Utf8,
|
||||||
@@ -116,48 +115,6 @@ describe.each([arrow15, arrow16, arrow17, arrow18])(
|
|||||||
await expect(table.countRows()).resolves.toBe(1);
|
await expect(table.countRows()).resolves.toBe(1);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should invoke the progress callback", async () => {
|
|
||||||
const events: import("../lancedb").WriteProgress[] = [];
|
|
||||||
await table.add([{ id: 1 }, { id: 2 }, { id: 3 }], {
|
|
||||||
progress: (p) => events.push(p),
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(events.length).toBeGreaterThan(0);
|
|
||||||
const last = events[events.length - 1];
|
|
||||||
expect(last.done).toBe(true);
|
|
||||||
// Earlier callbacks must have done=false.
|
|
||||||
for (const ev of events.slice(0, -1)) {
|
|
||||||
expect(ev.done).toBe(false);
|
|
||||||
}
|
|
||||||
// outputRows reflects the rows added in this call, not table size.
|
|
||||||
expect(last.outputRows).toBe(3);
|
|
||||||
// The input source (an array) reports a row count, so totalRows is set.
|
|
||||||
expect(last.totalRows).toBe(3);
|
|
||||||
// outputRows is monotonic.
|
|
||||||
for (let i = 1; i < events.length; i++) {
|
|
||||||
expect(events[i].outputRows).toBeGreaterThanOrEqual(
|
|
||||||
events[i - 1].outputRows,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should swallow errors thrown from the progress callback", async () => {
|
|
||||||
const warn = jest
|
|
||||||
.spyOn(console, "warn")
|
|
||||||
.mockImplementation(() => undefined);
|
|
||||||
try {
|
|
||||||
const res = await table.add([{ id: 1 }, { id: 2 }], {
|
|
||||||
progress: () => {
|
|
||||||
throw new Error("callback bomb");
|
|
||||||
},
|
|
||||||
});
|
|
||||||
expect(res.version).toBeGreaterThan(0);
|
|
||||||
expect(warn).toHaveBeenCalled();
|
|
||||||
} finally {
|
|
||||||
warn.mockRestore();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should let me close the table", async () => {
|
it("should let me close the table", async () => {
|
||||||
expect(table.isOpen()).toBe(true);
|
expect(table.isOpen()).toBe(true);
|
||||||
table.close();
|
table.close();
|
||||||
@@ -781,113 +738,6 @@ describe("When creating an index", () => {
|
|||||||
expect(indices2.length).toBe(0);
|
expect(indices2.length).toBe(0);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should create and search a nested vector index", async () => {
|
|
||||||
const db = await connect(tmpDir.name);
|
|
||||||
const nestedSchema = new Schema([
|
|
||||||
new Field("id", new Int32(), true),
|
|
||||||
new Field(
|
|
||||||
"image",
|
|
||||||
new Struct([
|
|
||||||
new Field(
|
|
||||||
"embedding",
|
|
||||||
new FixedSizeList(2, new Field("item", new Float32(), true)),
|
|
||||||
true,
|
|
||||||
),
|
|
||||||
]),
|
|
||||||
true,
|
|
||||||
),
|
|
||||||
]);
|
|
||||||
const nestedTable = await db.createTable(
|
|
||||||
"nested_vector",
|
|
||||||
makeArrowTable(
|
|
||||||
Array.from({ length: 300 }, (_, id) => ({
|
|
||||||
id,
|
|
||||||
image: { embedding: [id, id + 1] },
|
|
||||||
})),
|
|
||||||
{ schema: nestedSchema },
|
|
||||||
),
|
|
||||||
);
|
|
||||||
|
|
||||||
await nestedTable.createIndex("image.embedding", {
|
|
||||||
name: "image_embedding_idx",
|
|
||||||
});
|
|
||||||
const indices = await nestedTable.listIndices();
|
|
||||||
expect(indices).toContainEqual({
|
|
||||||
name: "image_embedding_idx",
|
|
||||||
indexType: "IvfPq",
|
|
||||||
columns: ["image.embedding"],
|
|
||||||
});
|
|
||||||
|
|
||||||
const explicit = await nestedTable
|
|
||||||
.query()
|
|
||||||
.nearestTo([0.0, 1.0])
|
|
||||||
.column("image.embedding")
|
|
||||||
.limit(1)
|
|
||||||
.toArray();
|
|
||||||
const inferred = await nestedTable
|
|
||||||
.query()
|
|
||||||
.nearestTo([0.0, 1.0])
|
|
||||||
.limit(1)
|
|
||||||
.toArray();
|
|
||||||
expect(inferred[0].id).toEqual(explicit[0].id);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should report multiple nested vector candidates", async () => {
|
|
||||||
const db = await connect(tmpDir.name);
|
|
||||||
const nestedSchema = new Schema([
|
|
||||||
new Field(
|
|
||||||
"image",
|
|
||||||
new Struct([
|
|
||||||
new Field(
|
|
||||||
"embedding",
|
|
||||||
new FixedSizeList(2, new Field("item", new Float32(), true)),
|
|
||||||
true,
|
|
||||||
),
|
|
||||||
]),
|
|
||||||
true,
|
|
||||||
),
|
|
||||||
new Field(
|
|
||||||
"text",
|
|
||||||
new Struct([
|
|
||||||
new Field(
|
|
||||||
"embedding",
|
|
||||||
new FixedSizeList(2, new Field("item", new Float32(), true)),
|
|
||||||
true,
|
|
||||||
),
|
|
||||||
]),
|
|
||||||
true,
|
|
||||||
),
|
|
||||||
]);
|
|
||||||
const nestedTable = await db.createTable(
|
|
||||||
"multiple_nested_vectors",
|
|
||||||
makeArrowTable(
|
|
||||||
[
|
|
||||||
{
|
|
||||||
image: { embedding: [0.0, 1.0] },
|
|
||||||
text: { embedding: [2.0, 3.0] },
|
|
||||||
},
|
|
||||||
],
|
|
||||||
{ schema: nestedSchema },
|
|
||||||
),
|
|
||||||
);
|
|
||||||
|
|
||||||
await expect(
|
|
||||||
nestedTable.query().nearestTo([0.0, 1.0]).limit(1).toArray(),
|
|
||||||
).rejects.toThrow(/image\.embedding.*text\.embedding/);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should report when no default vector column exists", async () => {
|
|
||||||
const db = await connect(tmpDir.name);
|
|
||||||
const noVectorTable = await db.createTable(
|
|
||||||
"no_vector",
|
|
||||||
makeArrowTable([{ id: 0, label: "cat" }]),
|
|
||||||
);
|
|
||||||
|
|
||||||
await expect(
|
|
||||||
noVectorTable.query().nearestTo([0.0, 1.0]).limit(1).toArray(),
|
|
||||||
).rejects.toThrow(/No vector column/);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should wait for index readiness", async () => {
|
it("should wait for index readiness", async () => {
|
||||||
// Create an index and then wait for it to be ready
|
// Create an index and then wait for it to be ready
|
||||||
await tbl.createIndex("vec");
|
await tbl.createIndex("vec");
|
||||||
@@ -2498,224 +2348,3 @@ describe("when creating a table with Float32Array vectors", () => {
|
|||||||
expect((fsl.children[0].type as Float32).precision).toBe(1);
|
expect((fsl.children[0].type as Float32).precision).toBe(1);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("setUnenforcedPrimaryKey", () => {
|
|
||||||
let tmpDir: tmp.DirResult;
|
|
||||||
|
|
||||||
beforeEach(() => {
|
|
||||||
tmpDir = tmp.dirSync({ unsafeCleanup: true });
|
|
||||||
});
|
|
||||||
afterEach(() => tmpDir.removeCallback());
|
|
||||||
|
|
||||||
it("sets a single-column primary key (string or one-element array)", async () => {
|
|
||||||
const conn = await connect(tmpDir.name);
|
|
||||||
const schema = new arrow.Schema([
|
|
||||||
new arrow.Field("id", new arrow.Int64(), false),
|
|
||||||
]);
|
|
||||||
const t1 = await conn.createEmptyTable("t1", schema);
|
|
||||||
await t1.setUnenforcedPrimaryKey("id");
|
|
||||||
|
|
||||||
const t2 = await conn.createEmptyTable("t2", schema);
|
|
||||||
await t2.setUnenforcedPrimaryKey(["id"]);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("rejects a compound primary key", async () => {
|
|
||||||
const conn = await connect(tmpDir.name);
|
|
||||||
const table = await conn.createEmptyTable(
|
|
||||||
"t",
|
|
||||||
new arrow.Schema([
|
|
||||||
new arrow.Field("id", new arrow.Int64(), false),
|
|
||||||
new arrow.Field("name", new arrow.Utf8(), false),
|
|
||||||
]),
|
|
||||||
);
|
|
||||||
await expect(
|
|
||||||
table.setUnenforcedPrimaryKey(["id", "name"]),
|
|
||||||
).rejects.toThrow();
|
|
||||||
});
|
|
||||||
|
|
||||||
it("rejects changing the primary key once set", async () => {
|
|
||||||
const conn = await connect(tmpDir.name);
|
|
||||||
const table = await conn.createEmptyTable(
|
|
||||||
"t",
|
|
||||||
new arrow.Schema([
|
|
||||||
new arrow.Field("id", new arrow.Int64(), false),
|
|
||||||
new arrow.Field("name", new arrow.Utf8(), false),
|
|
||||||
]),
|
|
||||||
);
|
|
||||||
await table.setUnenforcedPrimaryKey("id");
|
|
||||||
await expect(table.setUnenforcedPrimaryKey("name")).rejects.toThrow();
|
|
||||||
await expect(table.setUnenforcedPrimaryKey("id")).rejects.toThrow();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("setLsmWriteSpec / unsetLsmWriteSpec", () => {
|
|
||||||
let tmpDir: tmp.DirResult;
|
|
||||||
|
|
||||||
beforeEach(() => {
|
|
||||||
tmpDir = tmp.dirSync({ unsafeCleanup: true });
|
|
||||||
});
|
|
||||||
afterEach(() => tmpDir.removeCallback());
|
|
||||||
|
|
||||||
async function makeTable(conn: Connection): Promise<Table> {
|
|
||||||
return await conn.createEmptyTable(
|
|
||||||
"t",
|
|
||||||
new arrow.Schema([new arrow.Field("id", new arrow.Int64(), false)]),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
it("installs and removes a bucket spec", async () => {
|
|
||||||
const conn = await connect(tmpDir.name);
|
|
||||||
const table = await makeTable(conn);
|
|
||||||
|
|
||||||
await table.setUnenforcedPrimaryKey("id");
|
|
||||||
await table.setLsmWriteSpec({
|
|
||||||
specType: "bucket",
|
|
||||||
column: "id",
|
|
||||||
numBuckets: 4,
|
|
||||||
});
|
|
||||||
await table.unsetLsmWriteSpec();
|
|
||||||
// A second unset errors — there is no spec left to remove.
|
|
||||||
await expect(table.unsetLsmWriteSpec()).rejects.toThrow();
|
|
||||||
// A fresh spec can be installed after unset.
|
|
||||||
await table.setLsmWriteSpec({
|
|
||||||
specType: "bucket",
|
|
||||||
column: "id",
|
|
||||||
numBuckets: 8,
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it("installs an unsharded spec", async () => {
|
|
||||||
const conn = await connect(tmpDir.name);
|
|
||||||
const table = await makeTable(conn);
|
|
||||||
|
|
||||||
await table.setUnenforcedPrimaryKey("id");
|
|
||||||
await table.setLsmWriteSpec({ specType: "unsharded" });
|
|
||||||
await table.unsetLsmWriteSpec();
|
|
||||||
});
|
|
||||||
|
|
||||||
it("installs an identity spec", async () => {
|
|
||||||
const conn = await connect(tmpDir.name);
|
|
||||||
const table = await makeTable(conn);
|
|
||||||
|
|
||||||
await table.setUnenforcedPrimaryKey("id");
|
|
||||||
await table.setLsmWriteSpec({ specType: "identity", column: "id" });
|
|
||||||
await table.unsetLsmWriteSpec();
|
|
||||||
});
|
|
||||||
|
|
||||||
it("rejects an invalid spec", async () => {
|
|
||||||
const conn = await connect(tmpDir.name);
|
|
||||||
const table = await makeTable(conn);
|
|
||||||
|
|
||||||
await table.setUnenforcedPrimaryKey("id");
|
|
||||||
// num_buckets out of range.
|
|
||||||
await expect(
|
|
||||||
table.setLsmWriteSpec({
|
|
||||||
specType: "bucket",
|
|
||||||
column: "id",
|
|
||||||
numBuckets: 0,
|
|
||||||
}),
|
|
||||||
).rejects.toThrow();
|
|
||||||
// Column mismatch.
|
|
||||||
await expect(
|
|
||||||
table.setLsmWriteSpec({
|
|
||||||
specType: "bucket",
|
|
||||||
column: "missing",
|
|
||||||
numBuckets: 4,
|
|
||||||
}),
|
|
||||||
).rejects.toThrow();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("LSM merge insert", () => {
|
|
||||||
let tmpDir: tmp.DirResult;
|
|
||||||
|
|
||||||
beforeEach(() => {
|
|
||||||
tmpDir = tmp.dirSync({ unsafeCleanup: true });
|
|
||||||
});
|
|
||||||
afterEach(() => tmpDir.removeCallback());
|
|
||||||
|
|
||||||
async function bucketTable(conn: Connection): Promise<Table> {
|
|
||||||
// The primary key column must be non-nullable.
|
|
||||||
const table = await conn.createEmptyTable(
|
|
||||||
"t",
|
|
||||||
new arrow.Schema([
|
|
||||||
new arrow.Field("id", new arrow.Utf8(), false),
|
|
||||||
new arrow.Field("value", new arrow.Float64(), true),
|
|
||||||
]),
|
|
||||||
);
|
|
||||||
await table.add([
|
|
||||||
{ id: "a", value: 1 },
|
|
||||||
{ id: "b", value: 2 },
|
|
||||||
]);
|
|
||||||
await table.setUnenforcedPrimaryKey("id");
|
|
||||||
// numBuckets = 1: every row routes to the single bucket.
|
|
||||||
await table.setLsmWriteSpec({
|
|
||||||
specType: "bucket",
|
|
||||||
column: "id",
|
|
||||||
numBuckets: 1,
|
|
||||||
});
|
|
||||||
return table;
|
|
||||||
}
|
|
||||||
|
|
||||||
it("routes merge_insert through the shard writer", async () => {
|
|
||||||
const conn = await connect(tmpDir.name);
|
|
||||||
const table = await bucketTable(conn);
|
|
||||||
|
|
||||||
const res = await table
|
|
||||||
.mergeInsert("id")
|
|
||||||
.whenMatchedUpdateAll()
|
|
||||||
.whenNotMatchedInsertAll()
|
|
||||||
.execute([
|
|
||||||
{ id: "c", value: 3 },
|
|
||||||
{ id: "d", value: 4 },
|
|
||||||
]);
|
|
||||||
// LSM path: rows go to the MemWAL, so only numRows is populated.
|
|
||||||
expect(res.numRows).toBe(2);
|
|
||||||
expect(res.version).toBe(0);
|
|
||||||
expect(res.numInsertedRows).toBe(0);
|
|
||||||
|
|
||||||
await table.closeLsmWriters();
|
|
||||||
});
|
|
||||||
|
|
||||||
it("falls back to the standard path with useLsmWrite(false)", async () => {
|
|
||||||
const conn = await connect(tmpDir.name);
|
|
||||||
const table = await bucketTable(conn);
|
|
||||||
|
|
||||||
const res = await table
|
|
||||||
.mergeInsert("id")
|
|
||||||
.whenNotMatchedInsertAll()
|
|
||||||
.useLsmWrite(false)
|
|
||||||
.execute([
|
|
||||||
{ id: "b", value: 9 },
|
|
||||||
{ id: "e", value: 5 },
|
|
||||||
]);
|
|
||||||
// Standard path commits: id="e" inserted ("b" already exists).
|
|
||||||
expect(res.numInsertedRows).toBe(1);
|
|
||||||
expect(await table.countRows()).toBe(3);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("supports validateSingleShard(false)", async () => {
|
|
||||||
const conn = await connect(tmpDir.name);
|
|
||||||
const table = await bucketTable(conn);
|
|
||||||
|
|
||||||
const res = await table
|
|
||||||
.mergeInsert("id")
|
|
||||||
.whenMatchedUpdateAll()
|
|
||||||
.whenNotMatchedInsertAll()
|
|
||||||
.validateSingleShard(false)
|
|
||||||
.execute([{ id: "f", value: 6 }]);
|
|
||||||
expect(res.numRows).toBe(1);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("rejects a non-upsert merge under an LSM spec", async () => {
|
|
||||||
const conn = await connect(tmpDir.name);
|
|
||||||
const table = await bucketTable(conn);
|
|
||||||
|
|
||||||
await expect(
|
|
||||||
table
|
|
||||||
.mergeInsert("id")
|
|
||||||
.whenNotMatchedInsertAll()
|
|
||||||
.execute([{ id: "g", value: 7 }]),
|
|
||||||
).rejects.toThrow();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|||||||
@@ -38,14 +38,5 @@ test("filtering examples", async () => {
|
|||||||
// --8<-- [start:sql_search]
|
// --8<-- [start:sql_search]
|
||||||
await tbl.query().where("id = 10").limit(10).toArray();
|
await tbl.query().where("id = 10").limit(10).toArray();
|
||||||
// --8<-- [end:sql_search]
|
// --8<-- [end:sql_search]
|
||||||
|
|
||||||
// --8<-- [start:orderby_search]
|
|
||||||
await tbl
|
|
||||||
.query()
|
|
||||||
.where("id > 10")
|
|
||||||
.orderBy({ columnName: "id", ascending: false })
|
|
||||||
.limit(5)
|
|
||||||
.toArray();
|
|
||||||
// --8<-- [end:orderby_search]
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
4810
nodejs/examples/package-lock.json
generated
Normal file
4810
nodejs/examples/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
@@ -11,17 +11,16 @@
|
|||||||
"test": "node --experimental-vm-modules node_modules/.bin/jest --testEnvironment jest-environment-node-single-context --verbose",
|
"test": "node --experimental-vm-modules node_modules/.bin/jest --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 && npm run format",
|
||||||
"format": "biome format --write *.ts"
|
"format": "biome format --write *.ts"
|
||||||
},
|
},
|
||||||
"author": "Lance Devs",
|
"author": "Lance Devs",
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"packageManager": "pnpm@11.1.1",
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@huggingface/transformers": "3.0.2",
|
"@huggingface/transformers": "^3.0.2",
|
||||||
"@lancedb/lancedb": "file:../dist",
|
"@lancedb/lancedb": "file:../dist",
|
||||||
"openai": "4.29.2",
|
"openai": "^4.29.2",
|
||||||
"sharp": "0.33.5"
|
"sharp": "^0.33.5"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@biomejs/biome": "^1.7.3",
|
"@biomejs/biome": "^1.7.3",
|
||||||
|
|||||||
3466
nodejs/examples/pnpm-lock.yaml
generated
3466
nodejs/examples/pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
@@ -1,13 +0,0 @@
|
|||||||
# Block resolution of versions less than 24h old (Shai-Hulud window).
|
|
||||||
# This is the pnpm 11 default but pinned here so it's visible to
|
|
||||||
# reviewers and survives a future pnpm major flipping the default.
|
|
||||||
minimumReleaseAge: 1440
|
|
||||||
|
|
||||||
# Fail install if a transitive dep tries to run an unapproved script.
|
|
||||||
strictDepBuilds: true
|
|
||||||
|
|
||||||
allowBuilds:
|
|
||||||
'@biomejs/biome': true
|
|
||||||
onnxruntime-node: true
|
|
||||||
protobufjs: true
|
|
||||||
sharp: true
|
|
||||||
@@ -1291,18 +1291,6 @@ export async function fromRecordBatchToBuffer(
|
|||||||
return Buffer.from(await writer.toUint8Array());
|
return Buffer.from(await writer.toUint8Array());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Create a buffer containing a single record batch using the Arrow IPC Stream
|
|
||||||
* serialization. Each call produces a self-contained Stream message (schema +
|
|
||||||
* batch + EOS) suitable for incremental decode by `arrow_ipc::reader::StreamReader`.
|
|
||||||
*/
|
|
||||||
export async function fromRecordBatchToStreamBuffer(
|
|
||||||
batch: RecordBatch,
|
|
||||||
): Promise<Buffer> {
|
|
||||||
const writer = RecordBatchStreamWriter.writeAll([batch]);
|
|
||||||
return Buffer.from(await writer.toUint8Array());
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Serialize an Arrow Table into a buffer using the Arrow IPC Stream serialization
|
* Serialize an Arrow Table into a buffer using the Arrow IPC Stream serialization
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -16,18 +16,6 @@ import {
|
|||||||
} from "./arrow";
|
} from "./arrow";
|
||||||
import { EmbeddingFunctionConfig, getRegistry } from "./embedding/registry";
|
import { EmbeddingFunctionConfig, getRegistry } from "./embedding/registry";
|
||||||
import { Connection as LanceDbConnection } from "./native";
|
import { Connection as LanceDbConnection } from "./native";
|
||||||
import type {
|
|
||||||
CreateNamespaceResponse,
|
|
||||||
DescribeNamespaceResponse,
|
|
||||||
DropNamespaceResponse,
|
|
||||||
ListNamespacesResponse,
|
|
||||||
} from "./native";
|
|
||||||
export type {
|
|
||||||
CreateNamespaceResponse,
|
|
||||||
DescribeNamespaceResponse,
|
|
||||||
DropNamespaceResponse,
|
|
||||||
ListNamespacesResponse,
|
|
||||||
};
|
|
||||||
import { sanitizeTable } from "./sanitize";
|
import { sanitizeTable } from "./sanitize";
|
||||||
import { LocalTable, Table } from "./table";
|
import { LocalTable, Table } from "./table";
|
||||||
|
|
||||||
@@ -122,41 +110,6 @@ export interface TableNamesOptions {
|
|||||||
/** An optional limit to the number of results to return. */
|
/** An optional limit to the number of results to return. */
|
||||||
limit?: number;
|
limit?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ListNamespacesOptions {
|
|
||||||
/** Token from a previous response for pagination. */
|
|
||||||
pageToken?: string;
|
|
||||||
/** An optional limit to the number of results to return. */
|
|
||||||
limit?: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface CreateNamespaceOptions {
|
|
||||||
/** Creation mode. */
|
|
||||||
mode?: "create" | "exist_ok" | "overwrite";
|
|
||||||
/** Properties to set on the new namespace. */
|
|
||||||
properties?: Record<string, string>;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface DropNamespaceOptions {
|
|
||||||
/** Whether to skip if the namespace doesn't exist, or fail. */
|
|
||||||
mode?: "skip" | "fail";
|
|
||||||
/** Refuse to drop if non-empty (restrict) or drop recursively (cascade). */
|
|
||||||
behavior?: "restrict" | "cascade";
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface RenameTableOptions {
|
|
||||||
/**
|
|
||||||
* The namespace path of the table being renamed. Defaults to the root
|
|
||||||
* namespace (`[]`) when omitted.
|
|
||||||
*/
|
|
||||||
namespacePath?: string[];
|
|
||||||
/**
|
|
||||||
* The namespace path to move the table to as part of the rename. When
|
|
||||||
* omitted the table stays in `namespacePath`.
|
|
||||||
*/
|
|
||||||
newNamespacePath?: string[];
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A LanceDB Connection that allows you to open tables and create new ones.
|
* A LanceDB Connection that allows you to open tables and create new ones.
|
||||||
*
|
*
|
||||||
@@ -315,69 +268,6 @@ export abstract class Connection {
|
|||||||
*/
|
*/
|
||||||
abstract dropAllTables(namespacePath?: string[]): Promise<void>;
|
abstract dropAllTables(namespacePath?: string[]): Promise<void>;
|
||||||
|
|
||||||
/**
|
|
||||||
* Describe a namespace, returning its properties.
|
|
||||||
*
|
|
||||||
* @param {string[]} namespacePath - The namespace path to describe, in
|
|
||||||
* parent → child order, e.g. `["analytics", "sales"]`.
|
|
||||||
* @returns {Promise<DescribeNamespaceResponse>} The namespace's properties
|
|
||||||
* (may be undefined if the namespace has none).
|
|
||||||
*/
|
|
||||||
abstract describeNamespace(
|
|
||||||
namespacePath: string[],
|
|
||||||
): Promise<DescribeNamespaceResponse>;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* List the immediate child namespaces under the given parent.
|
|
||||||
*
|
|
||||||
* Results may be paginated. To retrieve subsequent pages, pass the
|
|
||||||
* `pageToken` returned by a previous call.
|
|
||||||
*
|
|
||||||
* @param {string[]} namespacePath - The parent namespace path. Defaults
|
|
||||||
* to the root namespace if omitted.
|
|
||||||
* @param {Partial<ListNamespacesOptions>} options - Pagination options
|
|
||||||
* (`pageToken`, `limit`).
|
|
||||||
* @returns {Promise<ListNamespacesResponse>} Child namespace names and
|
|
||||||
* an optional token for fetching the next page.
|
|
||||||
*/
|
|
||||||
abstract listNamespaces(
|
|
||||||
namespacePath?: string[],
|
|
||||||
options?: Partial<ListNamespacesOptions>,
|
|
||||||
): Promise<ListNamespacesResponse>;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Create a new namespace at the given path.
|
|
||||||
*
|
|
||||||
* @param {string[]} namespacePath - The namespace path to create.
|
|
||||||
* @param {Partial<CreateNamespaceOptions>} options - Creation `mode`
|
|
||||||
* ("create" | "exist_ok" | "overwrite") and optional `properties`
|
|
||||||
* to attach to the namespace.
|
|
||||||
* @returns {Promise<CreateNamespaceResponse>} The properties of the
|
|
||||||
* created namespace and an optional transaction id.
|
|
||||||
*/
|
|
||||||
abstract createNamespace(
|
|
||||||
namespacePath: string[],
|
|
||||||
options?: Partial<CreateNamespaceOptions>,
|
|
||||||
): Promise<CreateNamespaceResponse>;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Drop a namespace.
|
|
||||||
*
|
|
||||||
* Use `behavior: "cascade"` to also drop everything contained in the
|
|
||||||
* namespace (sub-namespaces and tables). The default `"restrict"`
|
|
||||||
* behavior refuses to drop a non-empty namespace.
|
|
||||||
*
|
|
||||||
* @param {string[]} namespacePath - The namespace path to drop.
|
|
||||||
* @param {Partial<DropNamespaceOptions>} options - `mode` ("skip" | "fail"
|
|
||||||
* for missing-namespace handling) and `behavior` ("restrict" | "cascade").
|
|
||||||
* @returns {Promise<DropNamespaceResponse>} Any properties returned by
|
|
||||||
* the server and an optional transaction id.
|
|
||||||
*/
|
|
||||||
abstract dropNamespace(
|
|
||||||
namespacePath: string[],
|
|
||||||
options?: Partial<DropNamespaceOptions>,
|
|
||||||
): Promise<DropNamespaceResponse>;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Clone a table from a source table.
|
* Clone a table from a source table.
|
||||||
*
|
*
|
||||||
@@ -404,24 +294,6 @@ export abstract class Connection {
|
|||||||
isShallow?: boolean;
|
isShallow?: boolean;
|
||||||
},
|
},
|
||||||
): Promise<Table>;
|
): Promise<Table>;
|
||||||
|
|
||||||
/**
|
|
||||||
* Rename a table.
|
|
||||||
*
|
|
||||||
* Currently only supported by LanceDB Cloud. Local OSS connections and
|
|
||||||
* namespace-backed connections (via {@link connectNamespace}) reject with
|
|
||||||
* a "not supported" error.
|
|
||||||
*
|
|
||||||
* @param {string} currentName - The current name of the table.
|
|
||||||
* @param {string} newName - The new name for the table.
|
|
||||||
* @param {RenameTableOptions} options - Optional namespace paths. When
|
|
||||||
* `newNamespacePath` is omitted the table stays in `namespacePath`.
|
|
||||||
*/
|
|
||||||
abstract renameTable(
|
|
||||||
currentName: string,
|
|
||||||
newName: string,
|
|
||||||
options?: RenameTableOptions,
|
|
||||||
): Promise<void>;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @hideconstructor */
|
/** @hideconstructor */
|
||||||
@@ -643,58 +515,6 @@ export class LocalConnection extends Connection {
|
|||||||
async dropAllTables(namespacePath?: string[]): Promise<void> {
|
async dropAllTables(namespacePath?: string[]): Promise<void> {
|
||||||
return this.inner.dropAllTables(namespacePath ?? []);
|
return this.inner.dropAllTables(namespacePath ?? []);
|
||||||
}
|
}
|
||||||
|
|
||||||
describeNamespace(
|
|
||||||
namespacePath: string[],
|
|
||||||
): Promise<DescribeNamespaceResponse> {
|
|
||||||
return this.inner.describeNamespace(namespacePath);
|
|
||||||
}
|
|
||||||
|
|
||||||
listNamespaces(
|
|
||||||
namespacePath?: string[],
|
|
||||||
options?: Partial<ListNamespacesOptions>,
|
|
||||||
): Promise<ListNamespacesResponse> {
|
|
||||||
return this.inner.listNamespaces(
|
|
||||||
namespacePath ?? [],
|
|
||||||
options?.pageToken,
|
|
||||||
options?.limit,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
createNamespace(
|
|
||||||
namespacePath: string[],
|
|
||||||
options?: Partial<CreateNamespaceOptions>,
|
|
||||||
): Promise<CreateNamespaceResponse> {
|
|
||||||
return this.inner.createNamespace(
|
|
||||||
namespacePath,
|
|
||||||
options?.mode,
|
|
||||||
options?.properties,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
dropNamespace(
|
|
||||||
namespacePath: string[],
|
|
||||||
options?: Partial<DropNamespaceOptions>,
|
|
||||||
): Promise<DropNamespaceResponse> {
|
|
||||||
return this.inner.dropNamespace(
|
|
||||||
namespacePath,
|
|
||||||
options?.mode,
|
|
||||||
options?.behavior,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
async renameTable(
|
|
||||||
currentName: string,
|
|
||||||
newName: string,
|
|
||||||
options?: RenameTableOptions,
|
|
||||||
): Promise<void> {
|
|
||||||
return this.inner.renameTable(
|
|
||||||
currentName,
|
|
||||||
newName,
|
|
||||||
options?.namespacePath ?? [],
|
|
||||||
options?.newNamespacePath,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -8,7 +8,6 @@ import {
|
|||||||
} from "./connection";
|
} from "./connection";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
ConnectNamespaceOptions,
|
|
||||||
ConnectionOptions,
|
ConnectionOptions,
|
||||||
Connection as LanceDbConnection,
|
Connection as LanceDbConnection,
|
||||||
JsHeaderProvider as NativeJsHeaderProvider,
|
JsHeaderProvider as NativeJsHeaderProvider,
|
||||||
@@ -23,7 +22,6 @@ export { JsHeaderProvider as NativeJsHeaderProvider } from "./native.js";
|
|||||||
export {
|
export {
|
||||||
AddColumnsSql,
|
AddColumnsSql,
|
||||||
ConnectionOptions,
|
ConnectionOptions,
|
||||||
ConnectNamespaceOptions,
|
|
||||||
IndexStatistics,
|
IndexStatistics,
|
||||||
IndexConfig,
|
IndexConfig,
|
||||||
ClientConfig,
|
ClientConfig,
|
||||||
@@ -64,14 +62,6 @@ export {
|
|||||||
CreateTableOptions,
|
CreateTableOptions,
|
||||||
TableNamesOptions,
|
TableNamesOptions,
|
||||||
OpenTableOptions,
|
OpenTableOptions,
|
||||||
ListNamespacesOptions,
|
|
||||||
CreateNamespaceOptions,
|
|
||||||
DropNamespaceOptions,
|
|
||||||
ListNamespacesResponse,
|
|
||||||
CreateNamespaceResponse,
|
|
||||||
DropNamespaceResponse,
|
|
||||||
DescribeNamespaceResponse,
|
|
||||||
RenameTableOptions,
|
|
||||||
} from "./connection";
|
} from "./connection";
|
||||||
|
|
||||||
export { Session } from "./native.js";
|
export { Session } from "./native.js";
|
||||||
@@ -83,7 +73,6 @@ export {
|
|||||||
VectorQuery,
|
VectorQuery,
|
||||||
TakeQuery,
|
TakeQuery,
|
||||||
QueryExecutionOptions,
|
QueryExecutionOptions,
|
||||||
ColumnOrdering,
|
|
||||||
FullTextSearchOptions,
|
FullTextSearchOptions,
|
||||||
RecordBatchIterator,
|
RecordBatchIterator,
|
||||||
FullTextQuery,
|
FullTextQuery,
|
||||||
@@ -114,8 +103,6 @@ export {
|
|||||||
UpdateOptions,
|
UpdateOptions,
|
||||||
OptimizeOptions,
|
OptimizeOptions,
|
||||||
Version,
|
Version,
|
||||||
WriteProgress,
|
|
||||||
LsmWriteSpec,
|
|
||||||
ColumnAlteration,
|
ColumnAlteration,
|
||||||
} from "./table";
|
} from "./table";
|
||||||
|
|
||||||
@@ -130,7 +117,6 @@ export { MergeInsertBuilder, WriteExecutionOptions } from "./merge";
|
|||||||
|
|
||||||
export * as embedding from "./embedding";
|
export * as embedding from "./embedding";
|
||||||
export { permutationBuilder, PermutationBuilder } from "./permutation";
|
export { permutationBuilder, PermutationBuilder } from "./permutation";
|
||||||
export { Scannable, ScannableOptions } from "./scannable";
|
|
||||||
export * as rerankers from "./rerankers";
|
export * as rerankers from "./rerankers";
|
||||||
export {
|
export {
|
||||||
SchemaLike,
|
SchemaLike,
|
||||||
@@ -307,197 +293,3 @@ export async function connect(
|
|||||||
);
|
);
|
||||||
return new LocalConnection(nativeConn);
|
return new LocalConnection(nativeConn);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Configuration for the built-in directory namespace (`"dir"`).
|
|
||||||
*
|
|
||||||
* The directory namespace stores tables under a single root path (local
|
|
||||||
* filesystem or object storage URI). See
|
|
||||||
* {@link https://docs.lancedb.com/namespaces} for the documented surface;
|
|
||||||
* less-common knobs live under {@link DirNamespaceConfig.extraProperties}.
|
|
||||||
*/
|
|
||||||
export interface DirNamespaceConfig {
|
|
||||||
/** Root path or URI containing the LanceDB tables. */
|
|
||||||
root: string;
|
|
||||||
/**
|
|
||||||
* Whether to maintain a namespace manifest at the root. Required for
|
|
||||||
* child namespaces. Defaults to true on the impl side.
|
|
||||||
*/
|
|
||||||
manifestEnabled?: boolean;
|
|
||||||
/**
|
|
||||||
* Additional raw properties passed verbatim to the namespace
|
|
||||||
* implementation (e.g. `storage.*`, `credential_vendor.*`). Typed
|
|
||||||
* fields above take precedence on key collision.
|
|
||||||
*/
|
|
||||||
extraProperties?: Record<string, string>;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Configuration for the built-in REST namespace (`"rest"`).
|
|
||||||
*
|
|
||||||
* The REST namespace talks to a remote catalog server over HTTP. See
|
|
||||||
* {@link https://docs.lancedb.com/namespaces} for the documented surface;
|
|
||||||
* less-common knobs (TLS, metrics) live under
|
|
||||||
* {@link RestNamespaceConfig.extraProperties}.
|
|
||||||
*/
|
|
||||||
export interface RestNamespaceConfig {
|
|
||||||
/** Catalog endpoint URL. */
|
|
||||||
uri: string;
|
|
||||||
/**
|
|
||||||
* HTTP headers forwarded with each request. Keys are passed through
|
|
||||||
* as-is (e.g. `"x-api-key"`, `"Authorization"`).
|
|
||||||
*/
|
|
||||||
headers?: Record<string, string>;
|
|
||||||
/**
|
|
||||||
* Additional raw properties passed verbatim to the namespace
|
|
||||||
* implementation (e.g. `tls.*`, `ops_metrics_enabled`, `delimiter`).
|
|
||||||
* Typed fields above take precedence on key collision.
|
|
||||||
*/
|
|
||||||
extraProperties?: Record<string, string>;
|
|
||||||
}
|
|
||||||
|
|
||||||
function dirConfigToProperties(
|
|
||||||
config: DirNamespaceConfig,
|
|
||||||
): Record<string, string> {
|
|
||||||
// Spread the whole input so that unknown keys (e.g. a raw `manifest_enabled`
|
|
||||||
// passed via the dynamic-impl path) flow through instead of being dropped.
|
|
||||||
// Typed transformations layer on top.
|
|
||||||
const { manifestEnabled, extraProperties, ...rest } = config;
|
|
||||||
const properties: Record<string, string> = {
|
|
||||||
...(extraProperties ?? {}),
|
|
||||||
...(rest as Record<string, string>),
|
|
||||||
};
|
|
||||||
if (manifestEnabled !== undefined) {
|
|
||||||
properties.manifest_enabled = String(manifestEnabled);
|
|
||||||
}
|
|
||||||
return properties;
|
|
||||||
}
|
|
||||||
|
|
||||||
function restConfigToProperties(
|
|
||||||
config: RestNamespaceConfig,
|
|
||||||
): Record<string, string> {
|
|
||||||
const { headers, extraProperties, ...rest } = config;
|
|
||||||
const properties: Record<string, string> = {
|
|
||||||
...(extraProperties ?? {}),
|
|
||||||
...(rest as Record<string, string>),
|
|
||||||
};
|
|
||||||
if (headers) {
|
|
||||||
for (const [name, value] of Object.entries(headers)) {
|
|
||||||
properties[`headers.${name}`] = value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return properties;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Connect to a LanceDB database through a namespace.
|
|
||||||
*
|
|
||||||
* Unlike {@link connect}, which routes by URI scheme (local path vs.
|
|
||||||
* `db://` cloud), `connectNamespace` always returns a namespace-backed
|
|
||||||
* connection. The `implName` selects the namespace implementation:
|
|
||||||
*
|
|
||||||
* - `"dir"` — directory namespace, configured with {@link DirNamespaceConfig}.
|
|
||||||
* - `"rest"` — remote REST catalog, configured with {@link RestNamespaceConfig}.
|
|
||||||
* - Any other string — full module path for a custom implementation,
|
|
||||||
* configured with a free-form string-keyed `properties` map.
|
|
||||||
*
|
|
||||||
* @example Typed dir namespace
|
|
||||||
* ```ts
|
|
||||||
* const db = await connectNamespace("dir", { root: "/path/to/db" });
|
|
||||||
* await db.createTable("users", [{ id: 1 }]);
|
|
||||||
* ```
|
|
||||||
*
|
|
||||||
* @example Typed REST namespace with auth headers
|
|
||||||
* ```ts
|
|
||||||
* const db = await connectNamespace("rest", {
|
|
||||||
* uri: "https://catalog.example.com",
|
|
||||||
* headers: { "x-api-key": process.env.CATALOG_KEY ?? "" },
|
|
||||||
* });
|
|
||||||
* ```
|
|
||||||
*
|
|
||||||
* @example Custom implementation with raw properties
|
|
||||||
* ```ts
|
|
||||||
* const db = await connectNamespace("my.custom.Namespace", {
|
|
||||||
* endpoint: "...",
|
|
||||||
* });
|
|
||||||
* ```
|
|
||||||
*/
|
|
||||||
export function connectNamespace(
|
|
||||||
implName: "dir",
|
|
||||||
config: DirNamespaceConfig,
|
|
||||||
options?: Partial<ConnectNamespaceOptions>,
|
|
||||||
): Promise<Connection>;
|
|
||||||
/**
|
|
||||||
* Connect through the built-in REST namespace.
|
|
||||||
*
|
|
||||||
* Configured with {@link RestNamespaceConfig}. See the function-level
|
|
||||||
* documentation above for the full surface, examples, and how this
|
|
||||||
* relates to {@link connect}.
|
|
||||||
*
|
|
||||||
* @example
|
|
||||||
* ```ts
|
|
||||||
* const db = await connectNamespace("rest", {
|
|
||||||
* uri: "https://catalog.example.com",
|
|
||||||
* headers: { "x-api-key": process.env.CATALOG_KEY ?? "" },
|
|
||||||
* });
|
|
||||||
* ```
|
|
||||||
*/
|
|
||||||
export function connectNamespace(
|
|
||||||
implName: "rest",
|
|
||||||
config: RestNamespaceConfig,
|
|
||||||
options?: Partial<ConnectNamespaceOptions>,
|
|
||||||
): Promise<Connection>;
|
|
||||||
/**
|
|
||||||
* Connect through a custom namespace implementation by full module path,
|
|
||||||
* configured with a free-form string-keyed `properties` map. Use the
|
|
||||||
* typed overloads above for the built-in `"dir"` and `"rest"` impls.
|
|
||||||
*
|
|
||||||
* See the function-level documentation above for examples and how this
|
|
||||||
* relates to {@link connect}.
|
|
||||||
*
|
|
||||||
* @example
|
|
||||||
* ```ts
|
|
||||||
* const db = await connectNamespace("my.custom.Namespace", {
|
|
||||||
* endpoint: "...",
|
|
||||||
* });
|
|
||||||
* ```
|
|
||||||
*/
|
|
||||||
export function connectNamespace(
|
|
||||||
implName: string,
|
|
||||||
properties: Record<string, string>,
|
|
||||||
options?: Partial<ConnectNamespaceOptions>,
|
|
||||||
): Promise<Connection>;
|
|
||||||
export async function connectNamespace(
|
|
||||||
implName: string,
|
|
||||||
configOrProperties:
|
|
||||||
| DirNamespaceConfig
|
|
||||||
| RestNamespaceConfig
|
|
||||||
| Record<string, string>,
|
|
||||||
options?: Partial<ConnectNamespaceOptions>,
|
|
||||||
): Promise<Connection> {
|
|
||||||
let properties: Record<string, string>;
|
|
||||||
if (implName === "dir") {
|
|
||||||
properties = dirConfigToProperties(
|
|
||||||
configOrProperties as DirNamespaceConfig,
|
|
||||||
);
|
|
||||||
} else if (implName === "rest") {
|
|
||||||
properties = restConfigToProperties(
|
|
||||||
configOrProperties as RestNamespaceConfig,
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
properties = configOrProperties as Record<string, string>;
|
|
||||||
}
|
|
||||||
|
|
||||||
const finalOptions: ConnectNamespaceOptions = (options ??
|
|
||||||
{}) as ConnectNamespaceOptions;
|
|
||||||
finalOptions.storageOptions = cleanseStorageOptions(
|
|
||||||
finalOptions.storageOptions,
|
|
||||||
);
|
|
||||||
|
|
||||||
const nativeConn = await LanceDbConnection.newWithNamespace(
|
|
||||||
implName,
|
|
||||||
properties,
|
|
||||||
finalOptions,
|
|
||||||
);
|
|
||||||
return new LocalConnection(nativeConn);
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -87,41 +87,6 @@ export class MergeInsertBuilder {
|
|||||||
this.#schema,
|
this.#schema,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
/**
|
|
||||||
* Controls whether the merge uses the MemWAL LSM write path.
|
|
||||||
*
|
|
||||||
* By default (unset), a `mergeInsert` on a table with an LSM write spec is
|
|
||||||
* routed through Lance's MemWAL shard writer, and a table without one uses
|
|
||||||
* the standard path. Pass `false` to force the standard path even when a
|
|
||||||
* spec is set. Pass `true` to require a spec — `mergeInsert` rejects if none
|
|
||||||
* is installed.
|
|
||||||
*
|
|
||||||
* @param useLsmWrite - Whether to use the LSM write path.
|
|
||||||
*/
|
|
||||||
useLsmWrite(useLsmWrite: boolean): MergeInsertBuilder {
|
|
||||||
return new MergeInsertBuilder(
|
|
||||||
this.#native.useLsmWrite(useLsmWrite),
|
|
||||||
this.#schema,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* Controls how an LSM merge checks that its input targets a single shard.
|
|
||||||
*
|
|
||||||
* When a table has an LSM write spec, every row in a `mergeInsert` call must
|
|
||||||
* route to the same shard. When `true` (the default), every row is inspected
|
|
||||||
* to verify this. When `false`, only the first row is inspected and the
|
|
||||||
* shard it routes to is used for the whole input — a faster path for callers
|
|
||||||
* that have already pre-sharded their input. Has no effect on tables without
|
|
||||||
* an LSM write spec.
|
|
||||||
*
|
|
||||||
* @param validateSingleShard - Whether to check every row routes to one shard. Defaults to `true`.
|
|
||||||
*/
|
|
||||||
validateSingleShard(validateSingleShard: boolean): MergeInsertBuilder {
|
|
||||||
return new MergeInsertBuilder(
|
|
||||||
this.#native.validateSingleShard(validateSingleShard),
|
|
||||||
this.#schema,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
/**
|
/**
|
||||||
* Executes the merge insert operation
|
* Executes the merge insert operation
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -79,12 +79,6 @@ export interface QueryExecutionOptions {
|
|||||||
timeoutMs?: number;
|
timeoutMs?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ColumnOrdering {
|
|
||||||
columnName: string;
|
|
||||||
ascending?: boolean;
|
|
||||||
nullsFirst?: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Options that control the behavior of a full text search
|
* Options that control the behavior of a full text search
|
||||||
*/
|
*/
|
||||||
@@ -423,21 +417,6 @@ export class StandardQueryBase<
|
|||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Sort the results by the specified column(s).
|
|
||||||
* @returns This query builder.
|
|
||||||
*/
|
|
||||||
orderBy(ordering: ColumnOrdering | ColumnOrdering[]): this {
|
|
||||||
const orderings = Array.isArray(ordering) ? ordering : [ordering];
|
|
||||||
const normalized = orderings.map((o) => ({
|
|
||||||
columnName: o.columnName,
|
|
||||||
ascending: o.ascending ?? true,
|
|
||||||
nullsFirst: o.nullsFirst ?? false,
|
|
||||||
}));
|
|
||||||
this.doCall((inner) => inner.orderBy(normalized));
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Skip searching un-indexed data. This can make search faster, but will miss
|
* Skip searching un-indexed data. This can make search faster, but will miss
|
||||||
* any data that is not yet indexed.
|
* any data that is not yet indexed.
|
||||||
|
|||||||
@@ -1,274 +0,0 @@
|
|||||||
// SPDX-License-Identifier: Apache-2.0
|
|
||||||
// SPDX-FileCopyrightText: Copyright The LanceDB Authors
|
|
||||||
|
|
||||||
import {
|
|
||||||
Table as ArrowTable,
|
|
||||||
RecordBatch,
|
|
||||||
RecordBatchReader,
|
|
||||||
Schema,
|
|
||||||
} from "apache-arrow";
|
|
||||||
import {
|
|
||||||
fromRecordBatchToStreamBuffer,
|
|
||||||
fromTableToBuffer,
|
|
||||||
makeEmptyTable,
|
|
||||||
} from "./arrow";
|
|
||||||
import { NapiScannable } from "./native.js";
|
|
||||||
|
|
||||||
export interface ScannableOptions {
|
|
||||||
/** Hint about the number of rows. Not validated against the stream. */
|
|
||||||
numRows?: number;
|
|
||||||
/**
|
|
||||||
* Whether the source can be scanned more than once. Defaults to `true` for
|
|
||||||
* `fromTable` / `fromFactory` and `false` for `fromIterable` /
|
|
||||||
* `fromRecordBatchReader`.
|
|
||||||
*/
|
|
||||||
rescannable?: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A data source that can be scanned as a stream of Arrow `RecordBatch`es.
|
|
||||||
*
|
|
||||||
* `Scannable` wraps the schema + optional row count + rescannable flag and
|
|
||||||
* a callback that yields batches one at a time. It is passed to consumers
|
|
||||||
* (e.g. `Table.add`, `createTable`, `mergeInsert` — follow-up work) that
|
|
||||||
* need to pull data without materializing the full dataset in JS memory.
|
|
||||||
*
|
|
||||||
* Batches cross the JS↔Rust boundary as Arrow IPC Stream messages; a fresh
|
|
||||||
* writer serializes each batch, and the Rust side decodes it with
|
|
||||||
* `arrow_ipc::reader::StreamReader`. One batch is in flight at a time.
|
|
||||||
*/
|
|
||||||
export class Scannable {
|
|
||||||
readonly schema: Schema;
|
|
||||||
readonly numRows: number | null;
|
|
||||||
readonly rescannable: boolean;
|
|
||||||
|
|
||||||
/** @hidden */
|
|
||||||
private readonly native: NapiScannable;
|
|
||||||
|
|
||||||
private constructor(
|
|
||||||
native: NapiScannable,
|
|
||||||
schema: Schema,
|
|
||||||
numRows: number | null,
|
|
||||||
rescannable: boolean,
|
|
||||||
) {
|
|
||||||
this.native = native;
|
|
||||||
this.schema = schema;
|
|
||||||
this.numRows = numRows;
|
|
||||||
this.rescannable = rescannable;
|
|
||||||
}
|
|
||||||
|
|
||||||
/** @hidden Access the native handle for passing through to Rust consumers. */
|
|
||||||
get inner(): NapiScannable {
|
|
||||||
return this.native;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Build a Scannable from an explicit schema and a factory that returns a
|
|
||||||
* fresh batch iterator on each call.
|
|
||||||
*
|
|
||||||
* The factory is invoked once per scan. Each iterator yields
|
|
||||||
* `RecordBatch`es matching the declared schema. Use this when you need
|
|
||||||
* direct control over the pull loop — for example, to wrap a streaming
|
|
||||||
* source whose batches are produced lazily.
|
|
||||||
*
|
|
||||||
* @param schema - The Arrow schema of the produced batches.
|
|
||||||
* @param factory - Called at the start of each scan to produce a batch
|
|
||||||
* iterator. Must be idempotent when `rescannable` is true.
|
|
||||||
* @param opts - Optional hints. `rescannable` defaults to `true`; set to
|
|
||||||
* `false` if calling `factory()` twice would not reproduce the same data.
|
|
||||||
*/
|
|
||||||
static async fromFactory(
|
|
||||||
schema: Schema,
|
|
||||||
factory: () =>
|
|
||||||
| AsyncIterable<RecordBatch>
|
|
||||||
| Iterable<RecordBatch>
|
|
||||||
| AsyncIterator<RecordBatch>
|
|
||||||
| Iterator<RecordBatch>,
|
|
||||||
opts: ScannableOptions = {},
|
|
||||||
): Promise<Scannable> {
|
|
||||||
const numRows = opts.numRows ?? null;
|
|
||||||
if (numRows != null && !Number.isInteger(numRows)) {
|
|
||||||
throw new TypeError("numRows must be an integer");
|
|
||||||
}
|
|
||||||
const rescannable = opts.rescannable ?? true;
|
|
||||||
|
|
||||||
let iter: AsyncIterator<RecordBatch> | Iterator<RecordBatch> | null = null;
|
|
||||||
const getNextBatch = async (isStart: boolean): Promise<Buffer | null> => {
|
|
||||||
// `isStart` is true on the first pull of every new scan_as_stream.
|
|
||||||
// Drop any cached iterator so factory() is re-invoked for the next scan
|
|
||||||
if (isStart) {
|
|
||||||
iter = null;
|
|
||||||
}
|
|
||||||
if (iter === null) {
|
|
||||||
iter = normalizeIterator(factory());
|
|
||||||
}
|
|
||||||
const result = await iter.next();
|
|
||||||
if (result.done) {
|
|
||||||
iter = null;
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
return fromRecordBatchToStreamBuffer(result.value);
|
|
||||||
};
|
|
||||||
|
|
||||||
const schemaBuf = await fromTableToBuffer(makeEmptyTable(schema));
|
|
||||||
const native = new NapiScannable(
|
|
||||||
schemaBuf,
|
|
||||||
numRows,
|
|
||||||
rescannable,
|
|
||||||
getNextBatch,
|
|
||||||
);
|
|
||||||
return new Scannable(native, schema, numRows, rescannable);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Build a Scannable from an in-memory Arrow `Table`. Always rescannable;
|
|
||||||
* the table's batches are replayed on each scan.
|
|
||||||
*
|
|
||||||
* The table's row count is authoritative: `opts.numRows` must either be
|
|
||||||
* omitted or equal to `table.numRows`. `opts.rescannable` of `false` is
|
|
||||||
* rejected because in-memory Tables are always rescannable.
|
|
||||||
*/
|
|
||||||
static async fromTable(
|
|
||||||
table: ArrowTable,
|
|
||||||
opts: ScannableOptions = {},
|
|
||||||
): Promise<Scannable> {
|
|
||||||
if (opts.numRows != null && opts.numRows !== table.numRows) {
|
|
||||||
throw new TypeError(
|
|
||||||
`opts.numRows (${opts.numRows}) does not match table.numRows (${table.numRows}). ` +
|
|
||||||
`The table's row count is authoritative; omit numRows or pass the matching value.`,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
if (opts.rescannable === false) {
|
|
||||||
throw new TypeError(
|
|
||||||
`fromTable does not accept rescannable: false. ` +
|
|
||||||
`In-memory Arrow Tables are always rescannable; omit the option or pass true.`,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return Scannable.fromFactory(table.schema, () => table.batches, {
|
|
||||||
numRows: table.numRows,
|
|
||||||
rescannable: true,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Build a Scannable from an iterable of `RecordBatch`es. `rescannable`
|
|
||||||
* defaults to `false`. Pass an explicit schema so the consumer can
|
|
||||||
* validate before any batch is pulled.
|
|
||||||
*
|
|
||||||
* `opts.rescannable: true` is honest for replayable iterables (Arrays,
|
|
||||||
* Sets, or custom iterables whose `[Symbol.iterator]()` returns a fresh
|
|
||||||
* iterator each call). It is rejected for one-shot iterables (generators,
|
|
||||||
* async generators, or already-an-iterator inputs) because their
|
|
||||||
* `[Symbol.iterator]()` returns the same exhausted object on the second
|
|
||||||
* scan. For replayable sources outside this shape, use
|
|
||||||
* `fromFactory(schema, () => createIter(), { rescannable: true })`.
|
|
||||||
*
|
|
||||||
* Note: when `opts.rescannable` is `true`, the constructor calls
|
|
||||||
* `[Symbol.iterator]()` once on the input to perform the structural check.
|
|
||||||
*/
|
|
||||||
static async fromIterable(
|
|
||||||
schema: Schema,
|
|
||||||
iter: AsyncIterable<RecordBatch> | Iterable<RecordBatch>,
|
|
||||||
opts: ScannableOptions = {},
|
|
||||||
): Promise<Scannable> {
|
|
||||||
if (opts.rescannable === true && isOneShotIterable(iter)) {
|
|
||||||
throw new TypeError(
|
|
||||||
`fromIterable: rescannable: true is not honest for one-shot iterables ` +
|
|
||||||
`(generators, async generators, or iterators where [Symbol.iterator]() ` +
|
|
||||||
`returns the same object). The source would be exhausted after the first scan. ` +
|
|
||||||
`Use fromFactory(schema, () => createIter(), { rescannable: true }) for sources ` +
|
|
||||||
`where each call mints a fresh iterator.`,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return Scannable.fromFactory(schema, () => iter, {
|
|
||||||
numRows: opts.numRows,
|
|
||||||
rescannable: opts.rescannable ?? false,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Build a Scannable from an Arrow `RecordBatchReader`. A reader can only
|
|
||||||
* be consumed once; `rescannable` defaults to `false`.
|
|
||||||
*
|
|
||||||
* The reader must already be opened (via `.open()`) so its `.schema` is
|
|
||||||
* populated. `RecordBatchReader.from(...)` returns an unopened reader.
|
|
||||||
*
|
|
||||||
* `opts.rescannable: true` is rejected because `RecordBatchReader` is a
|
|
||||||
* self-iterator (its `[Symbol.iterator]()` returns itself), and this
|
|
||||||
* constructor does not call `reader.reset()` between scans, so a second
|
|
||||||
* scan would always see an exhausted reader. For genuinely replayable
|
|
||||||
* sources, use
|
|
||||||
* `fromFactory(schema, () => openReader(), { rescannable: true })`,
|
|
||||||
* which mints a fresh reader on each scan.
|
|
||||||
*/
|
|
||||||
static async fromRecordBatchReader(
|
|
||||||
reader: RecordBatchReader,
|
|
||||||
opts: ScannableOptions = {},
|
|
||||||
): Promise<Scannable> {
|
|
||||||
if (opts.rescannable === true) {
|
|
||||||
throw new TypeError(
|
|
||||||
`fromRecordBatchReader does not accept rescannable: true. ` +
|
|
||||||
`RecordBatchReader is a self-iterator (its [Symbol.iterator]() ` +
|
|
||||||
`returns itself) and would be exhausted after the first scan. ` +
|
|
||||||
`Use fromFactory(schema, () => openReader(), { rescannable: true }) ` +
|
|
||||||
`for sources where each call mints a fresh reader.`,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return Scannable.fromFactory(reader.schema, () => reader, {
|
|
||||||
numRows: opts.numRows,
|
|
||||||
rescannable: false,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function normalizeIterator<T>(
|
|
||||||
source: AsyncIterable<T> | Iterable<T> | AsyncIterator<T> | Iterator<T>,
|
|
||||||
): AsyncIterator<T> | Iterator<T> {
|
|
||||||
if (source == null) {
|
|
||||||
throw new TypeError("Scannable factory returned null/undefined");
|
|
||||||
}
|
|
||||||
if (
|
|
||||||
typeof (source as AsyncIterable<T>)[Symbol.asyncIterator] === "function"
|
|
||||||
) {
|
|
||||||
return (source as AsyncIterable<T>)[Symbol.asyncIterator]();
|
|
||||||
}
|
|
||||||
if (typeof (source as Iterable<T>)[Symbol.iterator] === "function") {
|
|
||||||
return (source as Iterable<T>)[Symbol.iterator]();
|
|
||||||
}
|
|
||||||
// Already an iterator (has `.next`).
|
|
||||||
if (typeof (source as Iterator<T>).next === "function") {
|
|
||||||
return source as Iterator<T>;
|
|
||||||
}
|
|
||||||
throw new TypeError("Scannable factory returned a non-iterable value");
|
|
||||||
}
|
|
||||||
|
|
||||||
// A "self-iterator" returns the same object from `[Symbol.iterator]()` /
|
|
||||||
// `[Symbol.asyncIterator]()`. Generators behave this way, so they exhaust
|
|
||||||
// after one pass. Replayable iterables (Array, Set, custom) return a fresh
|
|
||||||
// iterator each call. Detection mirrors `normalizeIterator`'s ordering so
|
|
||||||
// classification matches scan-time behavior.
|
|
||||||
function isOneShotIterable(
|
|
||||||
source: AsyncIterable<unknown> | Iterable<unknown>,
|
|
||||||
): boolean {
|
|
||||||
// null/undefined are not one-shot in any meaningful sense; let
|
|
||||||
// `normalizeIterator` raise the actual error at scan time.
|
|
||||||
if (source == null) return false;
|
|
||||||
const ref = source as unknown;
|
|
||||||
if (
|
|
||||||
typeof (source as AsyncIterable<unknown>)[Symbol.asyncIterator] ===
|
|
||||||
"function"
|
|
||||||
) {
|
|
||||||
const it = (source as AsyncIterable<unknown>)[
|
|
||||||
Symbol.asyncIterator
|
|
||||||
]() as unknown;
|
|
||||||
return it === ref;
|
|
||||||
}
|
|
||||||
if (typeof (source as Iterable<unknown>)[Symbol.iterator] === "function") {
|
|
||||||
const it = (source as Iterable<unknown>)[Symbol.iterator]() as unknown;
|
|
||||||
return it === ref;
|
|
||||||
}
|
|
||||||
// Already-an-iterator (has `.next` but no `Symbol.iterator`) is by
|
|
||||||
// definition one-shot.
|
|
||||||
if (typeof (source as { next?: unknown }).next === "function") return true;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
@@ -46,33 +46,6 @@ import { sanitizeType } from "./sanitize";
|
|||||||
import { IntoSql, toSQL } from "./util";
|
import { IntoSql, toSQL } from "./util";
|
||||||
export { IndexConfig } from "./native";
|
export { IndexConfig } from "./native";
|
||||||
|
|
||||||
/**
|
|
||||||
* Progress snapshot for a write operation, delivered to the `progress`
|
|
||||||
* callback passed to {@link Table.add}.
|
|
||||||
*/
|
|
||||||
export interface WriteProgress {
|
|
||||||
/** Number of rows written so far. */
|
|
||||||
outputRows: number;
|
|
||||||
/** Number of bytes written so far. */
|
|
||||||
outputBytes: number;
|
|
||||||
/**
|
|
||||||
* Total rows expected, when the input source reports it.
|
|
||||||
*
|
|
||||||
* Always set on the final callback (the one with `done: true`), falling
|
|
||||||
* back to the actual number of rows written when the source could not
|
|
||||||
* report a row count up front.
|
|
||||||
*/
|
|
||||||
totalRows?: number;
|
|
||||||
/** Wall-clock seconds since the write started. */
|
|
||||||
elapsedSeconds: number;
|
|
||||||
/** Number of parallel write tasks currently in flight. */
|
|
||||||
activeTasks: number;
|
|
||||||
/** Total number of parallel write tasks (the write parallelism). */
|
|
||||||
totalTasks: number;
|
|
||||||
/** `true` for the final callback; `false` otherwise. */
|
|
||||||
done: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Options for adding data to a table.
|
* Options for adding data to a table.
|
||||||
*/
|
*/
|
||||||
@@ -83,28 +56,6 @@ export interface AddDataOptions {
|
|||||||
* If "overwrite" then the new data will replace the existing data in the table.
|
* If "overwrite" then the new data will replace the existing data in the table.
|
||||||
*/
|
*/
|
||||||
mode: "append" | "overwrite";
|
mode: "append" | "overwrite";
|
||||||
|
|
||||||
/**
|
|
||||||
* Optional callback invoked periodically with write progress.
|
|
||||||
*
|
|
||||||
* The callback is fired once per batch written and once more with
|
|
||||||
* `done: true` when the write completes. Calls are dispatched
|
|
||||||
* asynchronously to the JS event loop and never block the write — a slow
|
|
||||||
* callback will queue events rather than back-pressure the writer.
|
|
||||||
*
|
|
||||||
* Errors thrown from the callback are logged with `console.warn` and
|
|
||||||
* swallowed — they do not abort the write.
|
|
||||||
*
|
|
||||||
* @example
|
|
||||||
* ```ts
|
|
||||||
* await table.add(data, {
|
|
||||||
* progress: (p) => {
|
|
||||||
* console.log(`${p.outputRows}/${p.totalRows ?? "?"} rows`);
|
|
||||||
* },
|
|
||||||
* });
|
|
||||||
* ```
|
|
||||||
*/
|
|
||||||
progress: (progress: WriteProgress) => void;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface UpdateOptions {
|
export interface UpdateOptions {
|
||||||
@@ -155,30 +106,6 @@ export interface Version {
|
|||||||
metadata: Record<string, string>;
|
metadata: Record<string, string>;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Specification selecting Lance's MemWAL LSM-style write path for
|
|
||||||
* `mergeInsert`.
|
|
||||||
*
|
|
||||||
* `specType` is `"bucket"`, `"identity"`, or `"unsharded"`. For `"bucket"`,
|
|
||||||
* `column` and `numBuckets` are required; for `"identity"`, `column` is
|
|
||||||
* required and must be a deterministic function of the unenforced primary
|
|
||||||
* key (every row with a given primary key must always produce the same
|
|
||||||
* `column` value, or upserts of that key can land in different shards and a
|
|
||||||
* stale version can win).
|
|
||||||
*/
|
|
||||||
export interface LsmWriteSpec {
|
|
||||||
/** One of `"bucket"`, `"identity"`, or `"unsharded"`. */
|
|
||||||
specType: "bucket" | "identity" | "unsharded";
|
|
||||||
/** Bucket and identity variants: the sharding column. */
|
|
||||||
column?: string;
|
|
||||||
/** Bucket variant: the number of buckets, in `[1, 1024]`. */
|
|
||||||
numBuckets?: number;
|
|
||||||
/** Names of indexes the MemWAL should keep up to date during writes. */
|
|
||||||
maintainedIndexes?: string[];
|
|
||||||
/** Default `ShardWriter` configuration recorded in the MemWAL index. */
|
|
||||||
writerConfigDefaults?: Record<string, string>;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A Table is a collection of Records in a LanceDB Database.
|
* A Table is a collection of Records in a LanceDB Database.
|
||||||
*
|
*
|
||||||
@@ -522,64 +449,6 @@ export abstract class Table {
|
|||||||
* containing the new version number of the table after dropping the columns.
|
* containing the new version number of the table after dropping the columns.
|
||||||
*/
|
*/
|
||||||
abstract dropColumns(columnNames: string[]): Promise<DropColumnsResult>;
|
abstract dropColumns(columnNames: string[]): Promise<DropColumnsResult>;
|
||||||
/**
|
|
||||||
* Set the unenforced primary key for this table to a single column.
|
|
||||||
*
|
|
||||||
* "Unenforced" means LanceDB does not check uniqueness on writes; the
|
|
||||||
* column is recorded in the schema as the primary key for use by features
|
|
||||||
* such as `merge_insert`. Only single-column primary keys are supported,
|
|
||||||
* and the key cannot be changed once set.
|
|
||||||
* @param {string | string[]} columns The primary key column. A one-element
|
|
||||||
* array is also accepted; passing more than one column is rejected.
|
|
||||||
* @returns {Promise<void>}
|
|
||||||
*/
|
|
||||||
abstract setUnenforcedPrimaryKey(columns: string | string[]): Promise<void>;
|
|
||||||
/**
|
|
||||||
* Install an {@link LsmWriteSpec} on this table, selecting Lance's MemWAL
|
|
||||||
* LSM-style write path for future `mergeInsert` calls.
|
|
||||||
*
|
|
||||||
* `LsmWriteSpec` chooses one of three sharding strategies via `specType`:
|
|
||||||
*
|
|
||||||
* - `"bucket"` — hash-bucket writes by the single-column unenforced primary
|
|
||||||
* key (`column` and `numBuckets` required).
|
|
||||||
* - `"identity"` — shard by the raw value of a scalar `column`.
|
|
||||||
* - `"unsharded"` — route every write to a single shard.
|
|
||||||
*
|
|
||||||
* All variants require the table to have an unenforced primary key
|
|
||||||
* ({@link Table#setUnenforcedPrimaryKey}); bucket sharding additionally
|
|
||||||
* requires it to be the single column being bucketed.
|
|
||||||
* @param {LsmWriteSpec} spec The sharding spec to install.
|
|
||||||
* @returns {Promise<void>}
|
|
||||||
* @example
|
|
||||||
* ```ts
|
|
||||||
* await table.setUnenforcedPrimaryKey("id");
|
|
||||||
* await table.setLsmWriteSpec({
|
|
||||||
* specType: "bucket",
|
|
||||||
* column: "id",
|
|
||||||
* numBuckets: 16,
|
|
||||||
* maintainedIndexes: ["id_idx"],
|
|
||||||
* });
|
|
||||||
* ```
|
|
||||||
*/
|
|
||||||
abstract setLsmWriteSpec(spec: LsmWriteSpec): Promise<void>;
|
|
||||||
/**
|
|
||||||
* Remove the {@link LsmWriteSpec} from this table, reverting to the standard
|
|
||||||
* `mergeInsert` write path.
|
|
||||||
*
|
|
||||||
* Errors if no spec is currently set.
|
|
||||||
* @returns {Promise<void>}
|
|
||||||
*/
|
|
||||||
abstract unsetLsmWriteSpec(): Promise<void>;
|
|
||||||
/**
|
|
||||||
* Drain and close any cached MemWAL shard writers held for this table.
|
|
||||||
*
|
|
||||||
* When an {@link LsmWriteSpec} is installed, `mergeInsert` opens MemWAL
|
|
||||||
* shard writers and caches them for reuse across calls. This closes them,
|
|
||||||
* flushing pending data; writers reopen lazily on the next `mergeInsert`.
|
|
||||||
* It is a no-op when no writers are cached.
|
|
||||||
* @returns {Promise<void>}
|
|
||||||
*/
|
|
||||||
abstract closeLsmWriters(): Promise<void>;
|
|
||||||
/** Retrieve the version of the table */
|
/** Retrieve the version of the table */
|
||||||
|
|
||||||
abstract version(): Promise<number>;
|
abstract version(): Promise<number>;
|
||||||
@@ -767,20 +636,7 @@ export class LocalTable extends Table {
|
|||||||
const schema = await this.schema();
|
const schema = await this.schema();
|
||||||
|
|
||||||
const buffer = await fromDataToBuffer(data, undefined, schema);
|
const buffer = await fromDataToBuffer(data, undefined, schema);
|
||||||
// Wrap the user callback so a thrown error doesn't surface as an
|
return await this.inner.add(buffer, mode);
|
||||||
// unhandled exception (the callback fires from a napi threadsafe
|
|
||||||
// function — exceptions there crash the process).
|
|
||||||
const userProgress = options?.progress;
|
|
||||||
const progress = userProgress
|
|
||||||
? (p: WriteProgress) => {
|
|
||||||
try {
|
|
||||||
userProgress(p);
|
|
||||||
} catch (e) {
|
|
||||||
console.warn("Table.add progress callback threw:", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
: undefined;
|
|
||||||
return await this.inner.add(buffer, mode, progress);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async update(
|
async update(
|
||||||
@@ -1041,23 +897,6 @@ export class LocalTable extends Table {
|
|||||||
return await this.inner.dropColumns(columnNames);
|
return await this.inner.dropColumns(columnNames);
|
||||||
}
|
}
|
||||||
|
|
||||||
async setUnenforcedPrimaryKey(columns: string | string[]): Promise<void> {
|
|
||||||
const cols = typeof columns === "string" ? [columns] : columns;
|
|
||||||
return await this.inner.setUnenforcedPrimaryKey(cols);
|
|
||||||
}
|
|
||||||
|
|
||||||
async setLsmWriteSpec(spec: LsmWriteSpec): Promise<void> {
|
|
||||||
return await this.inner.setLsmWriteSpec(spec);
|
|
||||||
}
|
|
||||||
|
|
||||||
async unsetLsmWriteSpec(): Promise<void> {
|
|
||||||
return await this.inner.unsetLsmWriteSpec();
|
|
||||||
}
|
|
||||||
|
|
||||||
async closeLsmWriters(): Promise<void> {
|
|
||||||
return await this.inner.closeLsmWriters();
|
|
||||||
}
|
|
||||||
|
|
||||||
async version(): Promise<number> {
|
async version(): Promise<number> {
|
||||||
return await this.inner.version();
|
return await this.inner.version();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@lancedb/lancedb-darwin-arm64",
|
"name": "@lancedb/lancedb-darwin-arm64",
|
||||||
"version": "0.30.1-beta.0",
|
"version": "0.28.0-beta.11",
|
||||||
"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.30.1-beta.0",
|
"version": "0.28.0-beta.11",
|
||||||
"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.30.1-beta.0",
|
"version": "0.28.0-beta.11",
|
||||||
"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.30.1-beta.0",
|
"version": "0.28.0-beta.11",
|
||||||
"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.30.1-beta.0",
|
"version": "0.28.0-beta.11",
|
||||||
"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.30.1-beta.0",
|
"version": "0.28.0-beta.11",
|
||||||
"os": [
|
"os": [
|
||||||
"win32"
|
"win32"
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@lancedb/lancedb-win32-x64-msvc",
|
"name": "@lancedb/lancedb-win32-x64-msvc",
|
||||||
"version": "0.30.1-beta.0",
|
"version": "0.28.0-beta.11",
|
||||||
"os": ["win32"],
|
"os": ["win32"],
|
||||||
"cpu": ["x64"],
|
"cpu": ["x64"],
|
||||||
"main": "lancedb.win32-x64-msvc.node",
|
"main": "lancedb.win32-x64-msvc.node",
|
||||||
|
|||||||
4703
nodejs/package-lock.json
generated
4703
nodejs/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -11,7 +11,7 @@
|
|||||||
"ann"
|
"ann"
|
||||||
],
|
],
|
||||||
"private": false,
|
"private": false,
|
||||||
"version": "0.30.1-beta.0",
|
"version": "0.28.0-beta.11",
|
||||||
"main": "dist/index.js",
|
"main": "dist/index.js",
|
||||||
"exports": {
|
"exports": {
|
||||||
".": "./dist/index.js",
|
".": "./dist/index.js",
|
||||||
@@ -38,15 +38,15 @@
|
|||||||
"url": "https://github.com/lancedb/lancedb"
|
"url": "https://github.com/lancedb/lancedb"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@aws-sdk/client-dynamodb": "3.1003.0",
|
"@aws-sdk/client-dynamodb": "^3.33.0",
|
||||||
"@aws-sdk/client-kms": "3.1003.0",
|
"@aws-sdk/client-kms": "^3.33.0",
|
||||||
"@aws-sdk/client-s3": "3.1003.0",
|
"@aws-sdk/client-s3": "^3.33.0",
|
||||||
"@biomejs/biome": "^1.7.3",
|
"@biomejs/biome": "^1.7.3",
|
||||||
"@jest/globals": "^29.7.0",
|
"@jest/globals": "^29.7.0",
|
||||||
"@napi-rs/cli": "3.5.1",
|
"@napi-rs/cli": "^3.5.1",
|
||||||
"@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",
|
||||||
"@types/tmp": "^0.2.6",
|
"@types/tmp": "^0.2.6",
|
||||||
"apache-arrow-15": "npm:apache-arrow@15.0.0",
|
"apache-arrow-15": "npm:apache-arrow@15.0.0",
|
||||||
"apache-arrow-16": "npm:apache-arrow@16.0.0",
|
"apache-arrow-16": "npm:apache-arrow@16.0.0",
|
||||||
@@ -57,9 +57,9 @@
|
|||||||
"shx": "^0.3.4",
|
"shx": "^0.3.4",
|
||||||
"tmp": "^0.2.3",
|
"tmp": "^0.2.3",
|
||||||
"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",
|
||||||
"typescript": "5.5.4",
|
"typescript": "^5.5.4",
|
||||||
"typescript-eslint": "^7.1.0"
|
"typescript-eslint": "^7.1.0"
|
||||||
},
|
},
|
||||||
"ava": {
|
"ava": {
|
||||||
@@ -68,16 +68,15 @@
|
|||||||
"engines": {
|
"engines": {
|
||||||
"node": ">= 18"
|
"node": ">= 18"
|
||||||
},
|
},
|
||||||
"packageManager": "pnpm@11.1.1",
|
|
||||||
"cpu": ["x64", "arm64"],
|
"cpu": ["x64", "arm64"],
|
||||||
"os": ["darwin", "linux", "win32"],
|
"os": ["darwin", "linux", "win32"],
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"artifacts": "napi artifacts",
|
"artifacts": "napi artifacts",
|
||||||
"build:debug": "napi build --platform --dts ../lancedb/native.d.ts --js ../lancedb/native.js --output-dir lancedb",
|
"build:debug": "napi build --platform --dts ../lancedb/native.d.ts --js ../lancedb/native.js --output-dir lancedb",
|
||||||
"postbuild:debug": "shx mkdir -p dist && shx cp lancedb/*.node dist/ && node -e \"require('fs').writeFileSync('dist/package.json', JSON.stringify({name:'@lancedb/lancedb',type:'commonjs'}))\"",
|
"postbuild:debug": "shx mkdir -p dist && shx cp lancedb/*.node dist/",
|
||||||
"build:release": "napi build --platform --release --dts ../lancedb/native.d.ts --js ../lancedb/native.js --output-dir dist",
|
"build:release": "napi build --platform --release --dts ../lancedb/native.d.ts --js ../lancedb/native.js --output-dir dist",
|
||||||
"build": "pnpm build:debug && pnpm tsc",
|
"build": "npm run build:debug && npm run tsc",
|
||||||
"build-release": "pnpm build:release && pnpm tsc",
|
"build-release": "npm run build:release && npm run tsc",
|
||||||
"tsc": "tsc -b",
|
"tsc": "tsc -b",
|
||||||
"posttsc": "shx cp lancedb/native.d.ts dist/native.d.ts",
|
"posttsc": "shx cp lancedb/native.d.ts dist/native.d.ts",
|
||||||
"lint-ci": "biome ci .",
|
"lint-ci": "biome ci .",
|
||||||
@@ -87,7 +86,7 @@
|
|||||||
"lint-fix": "biome check --write . && biome format --write .",
|
"lint-fix": "biome check --write . && biome format --write .",
|
||||||
"prepublishOnly": "napi prepublish -t npm",
|
"prepublishOnly": "napi prepublish -t npm",
|
||||||
"test": "jest --verbose",
|
"test": "jest --verbose",
|
||||||
"integration": "S3_TEST=1 pnpm test",
|
"integration": "S3_TEST=1 npm run test",
|
||||||
"universal": "napi universalize",
|
"universal": "napi universalize",
|
||||||
"version": "napi version"
|
"version": "napi version"
|
||||||
},
|
},
|
||||||
@@ -95,8 +94,8 @@
|
|||||||
"reflect-metadata": "^0.2.2"
|
"reflect-metadata": "^0.2.2"
|
||||||
},
|
},
|
||||||
"optionalDependencies": {
|
"optionalDependencies": {
|
||||||
"@huggingface/transformers": "3.0.2",
|
"@huggingface/transformers": "^3.0.2",
|
||||||
"openai": "4.29.2"
|
"openai": "^4.29.2"
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"apache-arrow": ">=15.0.0 <=18.1.0"
|
"apache-arrow": ">=15.0.0 <=18.1.0"
|
||||||
|
|||||||
7317
nodejs/pnpm-lock.yaml
generated
7317
nodejs/pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
@@ -1,18 +0,0 @@
|
|||||||
# Flat node_modules layout. The @napi-rs/cli build step fails to locate
|
|
||||||
# the cdylib artifact under pnpm's isolated layout; the hoisted linker
|
|
||||||
# mirrors npm's structure and unblocks the native build.
|
|
||||||
nodeLinker: hoisted
|
|
||||||
|
|
||||||
# Block resolution of versions less than 24h old (Shai-Hulud window).
|
|
||||||
# This is the pnpm 11 default but pinned here so it's visible to
|
|
||||||
# reviewers and survives a future pnpm major flipping the default.
|
|
||||||
minimumReleaseAge: 1440
|
|
||||||
|
|
||||||
# Fail install if a transitive dep tries to run an unapproved script.
|
|
||||||
strictDepBuilds: true
|
|
||||||
|
|
||||||
allowBuilds:
|
|
||||||
'@biomejs/biome': true
|
|
||||||
onnxruntime-node: true
|
|
||||||
protobufjs: true
|
|
||||||
sharp: true
|
|
||||||
@@ -8,16 +8,12 @@ use lancedb::database::{CreateTableMode, Database};
|
|||||||
use napi::bindgen_prelude::*;
|
use napi::bindgen_prelude::*;
|
||||||
use napi_derive::*;
|
use napi_derive::*;
|
||||||
|
|
||||||
use crate::ConnectNamespaceOptions;
|
|
||||||
use crate::ConnectionOptions;
|
use crate::ConnectionOptions;
|
||||||
use crate::error::NapiErrorExt;
|
use crate::error::NapiErrorExt;
|
||||||
use crate::header::JsHeaderProvider;
|
use crate::header::JsHeaderProvider;
|
||||||
use crate::table::Table;
|
use crate::table::Table;
|
||||||
use lancedb::connection::{ConnectBuilder, Connection as LanceDBConnection, connect_namespace};
|
use lancedb::connection::{ConnectBuilder, Connection as LanceDBConnection};
|
||||||
|
|
||||||
use lance_namespace::models::{
|
|
||||||
CreateNamespaceRequest, DescribeNamespaceRequest, DropNamespaceRequest, ListNamespacesRequest,
|
|
||||||
};
|
|
||||||
use lancedb::ipc::{ipc_file_to_batches, ipc_file_to_schema};
|
use lancedb::ipc::{ipc_file_to_batches, ipc_file_to_schema};
|
||||||
|
|
||||||
#[napi]
|
#[napi]
|
||||||
@@ -25,29 +21,6 @@ pub struct Connection {
|
|||||||
inner: Option<LanceDBConnection>,
|
inner: Option<LanceDBConnection>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[napi(object)]
|
|
||||||
pub struct DescribeNamespaceResponse {
|
|
||||||
pub properties: Option<HashMap<String, String>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[napi(object)]
|
|
||||||
pub struct ListNamespacesResponse {
|
|
||||||
pub namespaces: Vec<String>,
|
|
||||||
pub page_token: Option<String>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[napi(object)]
|
|
||||||
pub struct CreateNamespaceResponse {
|
|
||||||
pub properties: Option<HashMap<String, String>>,
|
|
||||||
pub transaction_id: Option<String>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[napi(object)]
|
|
||||||
pub struct DropNamespaceResponse {
|
|
||||||
pub properties: Option<HashMap<String, String>>,
|
|
||||||
pub transaction_id: Option<Vec<String>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Connection {
|
impl Connection {
|
||||||
pub(crate) fn inner_new(inner: LanceDBConnection) -> Self {
|
pub(crate) fn inner_new(inner: LanceDBConnection) -> Self {
|
||||||
Self { inner: Some(inner) }
|
Self { inner: Some(inner) }
|
||||||
@@ -133,39 +106,6 @@ impl Connection {
|
|||||||
Ok(Self::inner_new(builder.execute().await.default_error()?))
|
Ok(Self::inner_new(builder.execute().await.default_error()?))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Create a new Connection instance backed by a namespace implementation.
|
|
||||||
#[napi(factory)]
|
|
||||||
pub async fn new_with_namespace(
|
|
||||||
impl_name: String,
|
|
||||||
properties: HashMap<String, String>,
|
|
||||||
options: ConnectNamespaceOptions,
|
|
||||||
) -> napi::Result<Self> {
|
|
||||||
if impl_name.is_empty() {
|
|
||||||
return Err(napi::Error::from_reason(
|
|
||||||
"implName must be a non-empty string",
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut builder = connect_namespace(&impl_name, properties);
|
|
||||||
if let Some(interval) = options.read_consistency_interval {
|
|
||||||
builder =
|
|
||||||
builder.read_consistency_interval(std::time::Duration::from_secs_f64(interval));
|
|
||||||
}
|
|
||||||
if let Some(storage_options) = options.storage_options {
|
|
||||||
for (key, value) in storage_options {
|
|
||||||
builder = builder.storage_option(key, value);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if let Some(namespace_client_properties) = options.namespace_client_properties {
|
|
||||||
builder = builder.namespace_client_properties(namespace_client_properties);
|
|
||||||
}
|
|
||||||
if let Some(session) = options.session {
|
|
||||||
builder = builder.session(session.inner.clone());
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(Self::inner_new(builder.execute().await.default_error()?))
|
|
||||||
}
|
|
||||||
|
|
||||||
#[napi]
|
#[napi]
|
||||||
pub fn display(&self) -> napi::Result<String> {
|
pub fn display(&self) -> napi::Result<String> {
|
||||||
Ok(self.get_inner()?.to_string())
|
Ok(self.get_inner()?.to_string())
|
||||||
@@ -333,149 +273,4 @@ impl Connection {
|
|||||||
let ns = namespace_path.unwrap_or_default();
|
let ns = namespace_path.unwrap_or_default();
|
||||||
self.get_inner()?.drop_all_tables(&ns).await.default_error()
|
self.get_inner()?.drop_all_tables(&ns).await.default_error()
|
||||||
}
|
}
|
||||||
|
|
||||||
#[napi(catch_unwind)]
|
|
||||||
/// Describe a namespace and return its properties.
|
|
||||||
pub async fn describe_namespace(
|
|
||||||
&self,
|
|
||||||
namespace_path: Vec<String>,
|
|
||||||
) -> napi::Result<DescribeNamespaceResponse> {
|
|
||||||
let req = DescribeNamespaceRequest {
|
|
||||||
id: Some(namespace_path),
|
|
||||||
..Default::default()
|
|
||||||
};
|
|
||||||
let resp = self
|
|
||||||
.get_inner()?
|
|
||||||
.describe_namespace(req)
|
|
||||||
.await
|
|
||||||
.default_error()?;
|
|
||||||
Ok(DescribeNamespaceResponse {
|
|
||||||
properties: resp.properties,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
#[napi(catch_unwind)]
|
|
||||||
/// List child namespaces under the given namespace path
|
|
||||||
pub async fn list_namespaces(
|
|
||||||
&self,
|
|
||||||
namespace_path: Option<Vec<String>>,
|
|
||||||
page_token: Option<String>,
|
|
||||||
limit: Option<u32>,
|
|
||||||
) -> napi::Result<ListNamespacesResponse> {
|
|
||||||
let req = ListNamespacesRequest {
|
|
||||||
id: namespace_path,
|
|
||||||
page_token,
|
|
||||||
limit: limit.map(|l| l as i32),
|
|
||||||
..Default::default()
|
|
||||||
};
|
|
||||||
let resp = self
|
|
||||||
.get_inner()?
|
|
||||||
.list_namespaces(req)
|
|
||||||
.await
|
|
||||||
.default_error()?;
|
|
||||||
Ok(ListNamespacesResponse {
|
|
||||||
namespaces: resp.namespaces,
|
|
||||||
page_token: resp.page_token,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
#[napi(catch_unwind)]
|
|
||||||
/// Create a new namespace with optional properties.
|
|
||||||
pub async fn create_namespace(
|
|
||||||
&self,
|
|
||||||
namespace_path: Vec<String>,
|
|
||||||
mode: Option<String>,
|
|
||||||
properties: Option<HashMap<String, String>>,
|
|
||||||
) -> napi::Result<CreateNamespaceResponse> {
|
|
||||||
let mode_str = mode
|
|
||||||
.map(|m| match m.to_lowercase().as_str() {
|
|
||||||
"create" => Ok("Create".to_string()),
|
|
||||||
"exist_ok" => Ok("ExistOk".to_string()),
|
|
||||||
"overwrite" => Ok("Overwrite".to_string()),
|
|
||||||
_ => Err(napi::Error::from_reason(format!(
|
|
||||||
"Invalid mode '{}': expected one of 'create', 'exist_ok', 'overwrite'",
|
|
||||||
m
|
|
||||||
))),
|
|
||||||
})
|
|
||||||
.transpose()?;
|
|
||||||
let req = CreateNamespaceRequest {
|
|
||||||
id: Some(namespace_path),
|
|
||||||
mode: mode_str,
|
|
||||||
properties,
|
|
||||||
..Default::default()
|
|
||||||
};
|
|
||||||
let resp = self
|
|
||||||
.get_inner()?
|
|
||||||
.create_namespace(req)
|
|
||||||
.await
|
|
||||||
.default_error()?;
|
|
||||||
Ok(CreateNamespaceResponse {
|
|
||||||
properties: resp.properties,
|
|
||||||
transaction_id: resp.transaction_id,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
#[napi(catch_unwind)]
|
|
||||||
/// Drop a namespace.
|
|
||||||
pub async fn drop_namespace(
|
|
||||||
&self,
|
|
||||||
namespace_path: Vec<String>,
|
|
||||||
mode: Option<String>,
|
|
||||||
behavior: Option<String>,
|
|
||||||
) -> napi::Result<DropNamespaceResponse> {
|
|
||||||
let mode_str = mode
|
|
||||||
.map(|m| match m.to_lowercase().as_str() {
|
|
||||||
"skip" => Ok("Skip".to_string()),
|
|
||||||
"fail" => Ok("Fail".to_string()),
|
|
||||||
_ => Err(napi::Error::from_reason(format!(
|
|
||||||
"Invalid mode '{}': expected one of 'skip', 'fail'",
|
|
||||||
m
|
|
||||||
))),
|
|
||||||
})
|
|
||||||
.transpose()?;
|
|
||||||
let behavior_str = behavior
|
|
||||||
.map(|b| match b.to_lowercase().as_str() {
|
|
||||||
"restrict" => Ok("Restrict".to_string()),
|
|
||||||
"cascade" => Ok("Cascade".to_string()),
|
|
||||||
_ => Err(napi::Error::from_reason(format!(
|
|
||||||
"Invalid behavior '{}': expected one of 'restrict', 'cascade'",
|
|
||||||
b
|
|
||||||
))),
|
|
||||||
})
|
|
||||||
.transpose()?;
|
|
||||||
let req = DropNamespaceRequest {
|
|
||||||
id: Some(namespace_path),
|
|
||||||
mode: mode_str,
|
|
||||||
behavior: behavior_str,
|
|
||||||
..Default::default()
|
|
||||||
};
|
|
||||||
let resp = self
|
|
||||||
.get_inner()?
|
|
||||||
.drop_namespace(req)
|
|
||||||
.await
|
|
||||||
.default_error()?;
|
|
||||||
Ok(DropNamespaceResponse {
|
|
||||||
properties: resp.properties,
|
|
||||||
transaction_id: resp.transaction_id,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Rename a table. `current_namespace_path` and `new_namespace_path` default to
|
|
||||||
/// the root namespace when omitted; the caller is expected to either pass both
|
|
||||||
/// or pass neither.
|
|
||||||
#[napi(catch_unwind)]
|
|
||||||
pub async fn rename_table(
|
|
||||||
&self,
|
|
||||||
current_name: String,
|
|
||||||
new_name: String,
|
|
||||||
current_namespace_path: Option<Vec<String>>,
|
|
||||||
new_namespace_path: Option<Vec<String>>,
|
|
||||||
) -> napi::Result<()> {
|
|
||||||
let cur_ns = current_namespace_path.unwrap_or_default();
|
|
||||||
let new_ns = new_namespace_path.unwrap_or_default();
|
|
||||||
self.get_inner()?
|
|
||||||
.rename_table(¤t_name, &new_name, &cur_ns, &new_ns)
|
|
||||||
.await
|
|
||||||
.default_error()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,7 +16,6 @@ pub mod permutation;
|
|||||||
mod query;
|
mod query;
|
||||||
pub mod remote;
|
pub mod remote;
|
||||||
mod rerankers;
|
mod rerankers;
|
||||||
mod scannable;
|
|
||||||
mod session;
|
mod session;
|
||||||
mod table;
|
mod table;
|
||||||
mod util;
|
mod util;
|
||||||
@@ -24,19 +23,15 @@ mod util;
|
|||||||
#[napi(object)]
|
#[napi(object)]
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct ConnectionOptions {
|
pub struct ConnectionOptions {
|
||||||
/// The interval, in seconds, at which to check for updates to the table
|
/// (For LanceDB OSS only): The interval, in seconds, at which to check for
|
||||||
/// from other processes. If None, then consistency is not checked. For
|
/// updates to the table from other processes. If None, then consistency is not
|
||||||
/// performance reasons, this is the default. For strong consistency, set
|
/// checked. For performance reasons, this is the default. For strong
|
||||||
/// this to zero seconds. Then every read will check for updates from other
|
/// consistency, set this to zero seconds. Then every read will check for
|
||||||
/// processes. As a compromise, you can set this to a non-zero value for
|
/// updates from other processes. As a compromise, you can set this to a
|
||||||
/// eventual consistency. If more than that interval has passed since the
|
/// non-zero value for eventual consistency. If more than that interval
|
||||||
/// last check, then the table will be checked for updates. Note: this
|
/// has passed since the last check, then the table will be checked for updates.
|
||||||
/// consistency only applies to read operations. Write operations are
|
/// Note: this consistency only applies to read operations. Write operations are
|
||||||
/// always consistent.
|
/// always consistent.
|
||||||
///
|
|
||||||
/// Stronger consistency is not free. The smaller the interval, the more
|
|
||||||
/// often each read pays the cost of checking for updates against object
|
|
||||||
/// storage, raising per-read latency and cost.
|
|
||||||
pub read_consistency_interval: Option<f64>,
|
pub read_consistency_interval: Option<f64>,
|
||||||
/// (For LanceDB OSS only): configuration for object storage.
|
/// (For LanceDB OSS only): configuration for object storage.
|
||||||
///
|
///
|
||||||
@@ -72,26 +67,6 @@ pub struct OpenTableOptions {
|
|||||||
pub storage_options: Option<HashMap<String, String>>,
|
pub storage_options: Option<HashMap<String, String>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[napi(object)]
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub struct ConnectNamespaceOptions {
|
|
||||||
/// The interval, in seconds, at which to check for updates to the table
|
|
||||||
/// from other processes. If None, then consistency is not checked. For
|
|
||||||
/// performance reasons, this is the default. For strong consistency, set
|
|
||||||
/// this to zero seconds. Then every read will check for updates from other
|
|
||||||
/// processes. As a compromise, you can set this to a non-zero value for
|
|
||||||
/// eventual consistency.
|
|
||||||
pub read_consistency_interval: Option<f64>,
|
|
||||||
/// Configuration for object storage. The available options are described
|
|
||||||
/// at https://docs.lancedb.com/storage/
|
|
||||||
pub storage_options: Option<HashMap<String, String>>,
|
|
||||||
/// Extra properties for the backing namespace client.
|
|
||||||
pub namespace_client_properties: Option<HashMap<String, String>>,
|
|
||||||
/// The session to use for this connection. Holds shared caches and other
|
|
||||||
/// session-specific state.
|
|
||||||
pub session: Option<session::Session>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[napi_derive::module_init]
|
#[napi_derive::module_init]
|
||||||
fn init() {
|
fn init() {
|
||||||
let env = Env::new()
|
let env = Env::new()
|
||||||
|
|||||||
@@ -50,20 +50,6 @@ impl NativeMergeInsertBuilder {
|
|||||||
this
|
this
|
||||||
}
|
}
|
||||||
|
|
||||||
#[napi]
|
|
||||||
pub fn use_lsm_write(&self, use_lsm_write: bool) -> Self {
|
|
||||||
let mut this = self.clone();
|
|
||||||
this.inner.use_lsm_write(use_lsm_write);
|
|
||||||
this
|
|
||||||
}
|
|
||||||
|
|
||||||
#[napi]
|
|
||||||
pub fn validate_single_shard(&self, validate_single_shard: bool) -> Self {
|
|
||||||
let mut this = self.clone();
|
|
||||||
this.inner.validate_single_shard(validate_single_shard);
|
|
||||||
this
|
|
||||||
}
|
|
||||||
|
|
||||||
#[napi(catch_unwind)]
|
#[napi(catch_unwind)]
|
||||||
pub async fn execute(&self, buf: Buffer) -> napi::Result<MergeResult> {
|
pub async fn execute(&self, buf: Buffer) -> napi::Result<MergeResult> {
|
||||||
let data = ipc_file_to_batches(buf.to_vec())
|
let data = ipc_file_to_batches(buf.to_vec())
|
||||||
|
|||||||
@@ -3,12 +3,6 @@
|
|||||||
|
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
use crate::error::NapiErrorExt;
|
|
||||||
use crate::error::convert_error;
|
|
||||||
use crate::iterator::RecordBatchIterator;
|
|
||||||
use crate::rerankers::RerankHybridCallbackArgs;
|
|
||||||
use crate::rerankers::Reranker;
|
|
||||||
use crate::util::{parse_distance_type, schema_to_buffer};
|
|
||||||
use arrow_array::{
|
use arrow_array::{
|
||||||
Array, Float16Array as ArrowFloat16Array, Float32Array as ArrowFloat32Array,
|
Array, Float16Array as ArrowFloat16Array, Float32Array as ArrowFloat32Array,
|
||||||
Float64Array as ArrowFloat64Array, UInt8Array as ArrowUInt8Array,
|
Float64Array as ArrowFloat64Array, UInt8Array as ArrowUInt8Array,
|
||||||
@@ -25,27 +19,16 @@ use lancedb::query::QueryBase;
|
|||||||
use lancedb::query::QueryExecutionOptions;
|
use lancedb::query::QueryExecutionOptions;
|
||||||
use lancedb::query::Select;
|
use lancedb::query::Select;
|
||||||
use lancedb::query::TakeQuery as LanceDbTakeQuery;
|
use lancedb::query::TakeQuery as LanceDbTakeQuery;
|
||||||
use lancedb::query::{ColumnOrdering as LanceDbColumnOrdering, VectorQuery as LanceDbVectorQuery};
|
use lancedb::query::VectorQuery as LanceDbVectorQuery;
|
||||||
use napi::bindgen_prelude::*;
|
use napi::bindgen_prelude::*;
|
||||||
use napi_derive::napi;
|
use napi_derive::napi;
|
||||||
|
|
||||||
#[napi(object)]
|
use crate::error::NapiErrorExt;
|
||||||
pub struct ColumnOrdering {
|
use crate::error::convert_error;
|
||||||
pub ascending: bool,
|
use crate::iterator::RecordBatchIterator;
|
||||||
pub nulls_first: bool,
|
use crate::rerankers::RerankHybridCallbackArgs;
|
||||||
pub column_name: String,
|
use crate::rerankers::Reranker;
|
||||||
}
|
use crate::util::{parse_distance_type, schema_to_buffer};
|
||||||
|
|
||||||
impl From<ColumnOrdering> for LanceDbColumnOrdering {
|
|
||||||
fn from(value: ColumnOrdering) -> Self {
|
|
||||||
match (value.ascending, value.nulls_first) {
|
|
||||||
(true, true) => Self::asc_nulls_first(value.column_name),
|
|
||||||
(true, false) => Self::asc_nulls_last(value.column_name),
|
|
||||||
(false, true) => Self::desc_nulls_first(value.column_name),
|
|
||||||
(false, false) => Self::desc_nulls_last(value.column_name),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn bytes_to_arrow_array(data: Uint8Array, dtype: String) -> napi::Result<Arc<dyn Array>> {
|
fn bytes_to_arrow_array(data: Uint8Array, dtype: String) -> napi::Result<Arc<dyn Array>> {
|
||||||
let buf = arrow_buffer::Buffer::from(data.to_vec());
|
let buf = arrow_buffer::Buffer::from(data.to_vec());
|
||||||
@@ -145,18 +128,6 @@ impl Query {
|
|||||||
self.inner = self.inner.clone().with_row_id();
|
self.inner = self.inner.clone().with_row_id();
|
||||||
}
|
}
|
||||||
|
|
||||||
#[napi]
|
|
||||||
pub fn order_by(&mut self, ordering: Option<Vec<ColumnOrdering>>) -> napi::Result<()> {
|
|
||||||
let ordering = ordering.map(|ordering| {
|
|
||||||
ordering
|
|
||||||
.into_iter()
|
|
||||||
.map(LanceDbColumnOrdering::from)
|
|
||||||
.collect()
|
|
||||||
});
|
|
||||||
self.inner = self.inner.clone().order_by(ordering);
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[napi(catch_unwind)]
|
#[napi(catch_unwind)]
|
||||||
pub async fn output_schema(&self) -> napi::Result<Buffer> {
|
pub async fn output_schema(&self) -> napi::Result<Buffer> {
|
||||||
let schema = self.inner.output_schema().await.default_error()?;
|
let schema = self.inner.output_schema().await.default_error()?;
|
||||||
@@ -357,18 +328,6 @@ impl VectorQuery {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[napi]
|
|
||||||
pub fn order_by(&mut self, ordering: Option<Vec<ColumnOrdering>>) -> napi::Result<()> {
|
|
||||||
let ordering = ordering.map(|ordering| {
|
|
||||||
ordering
|
|
||||||
.into_iter()
|
|
||||||
.map(LanceDbColumnOrdering::from)
|
|
||||||
.collect()
|
|
||||||
});
|
|
||||||
self.inner = self.inner.clone().order_by(ordering);
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[napi(catch_unwind)]
|
#[napi(catch_unwind)]
|
||||||
pub async fn output_schema(&self) -> napi::Result<Buffer> {
|
pub async fn output_schema(&self) -> napi::Result<Buffer> {
|
||||||
let schema = self.inner.output_schema().await.default_error()?;
|
let schema = self.inner.output_schema().await.default_error()?;
|
||||||
|
|||||||
@@ -1,253 +0,0 @@
|
|||||||
// SPDX-License-Identifier: Apache-2.0
|
|
||||||
// SPDX-FileCopyrightText: Copyright The LanceDB Authors
|
|
||||||
|
|
||||||
//! NodeJS binding for the [`lancedb::data::scannable::Scannable`] trait.
|
|
||||||
//!
|
|
||||||
//! The JS side supplies a `getNextBatch(isStart)` callback that returns the
|
|
||||||
//! next Arrow `RecordBatch` encoded as a self-contained Arrow IPC Stream
|
|
||||||
//! message (schema message + record batch message + EOS marker) wrapped in a
|
|
||||||
//! `Buffer`, or `null` when the stream is exhausted. The Rust side parses
|
|
||||||
//! each buffer with `arrow_ipc::reader::StreamReader`, validates every
|
|
||||||
//! standalone batch stream against the declared schema, and yields decoded
|
|
||||||
//! `RecordBatch`es as a [`SendableRecordBatchStream`].
|
|
||||||
//!
|
|
||||||
//! `isStart` is `true` on the first `getNextBatch` call of each new
|
|
||||||
//! `scan_as_stream` and `false` thereafter. JS uses it to drop any cached
|
|
||||||
//! iterator and re-invoke its factory at scan boundaries, so retries
|
|
||||||
//! triggered by mid-stream failures restart at batch 0.
|
|
||||||
|
|
||||||
use std::io::Cursor;
|
|
||||||
use std::sync::Arc;
|
|
||||||
|
|
||||||
use arrow_array::RecordBatch;
|
|
||||||
use arrow_ipc::reader::StreamReader;
|
|
||||||
use arrow_schema::SchemaRef;
|
|
||||||
use futures::stream::once;
|
|
||||||
use lancedb::arrow::{SendableRecordBatchStream, SimpleRecordBatchStream};
|
|
||||||
use lancedb::data::scannable::Scannable as LanceScannable;
|
|
||||||
use lancedb::ipc::ipc_file_to_schema;
|
|
||||||
use lancedb::{Error, Result as LanceResult};
|
|
||||||
use napi::bindgen_prelude::*;
|
|
||||||
use napi::threadsafe_function::ThreadsafeFunction;
|
|
||||||
use napi_derive::napi;
|
|
||||||
|
|
||||||
/// Threadsafe handle to the JS `getNextBatch` callback. The callback takes a
|
|
||||||
/// single boolean `isStart` (`true` on the first call of each new scan) and
|
|
||||||
/// returns a Promise that resolves to a `Buffer` containing one IPC Stream
|
|
||||||
/// message, or `null` at end-of-stream.
|
|
||||||
type GetNextBatchFn = ThreadsafeFunction<bool, Promise<Option<Buffer>>, bool, Status, false>;
|
|
||||||
|
|
||||||
/// A Rust-side view of a JS-constructed `Scannable`.
|
|
||||||
///
|
|
||||||
/// Held in JS as the return value of the `Scannable` class constructor. When
|
|
||||||
/// passed to a consumer that accepts `impl lancedb::data::scannable::Scannable`,
|
|
||||||
/// the consumer invokes `scan_as_stream()` to pull batches through the JS
|
|
||||||
/// callback.
|
|
||||||
#[napi]
|
|
||||||
pub struct NapiScannable {
|
|
||||||
schema: SchemaRef,
|
|
||||||
num_rows: Option<usize>,
|
|
||||||
rescannable: bool,
|
|
||||||
// `ThreadsafeFunction` is not `Clone`; wrap in `Arc` so the stream
|
|
||||||
// returned by `scan_as_stream` can own a handle independent of `self`.
|
|
||||||
get_next_batch: Arc<GetNextBatchFn>,
|
|
||||||
// Tracks whether a scan has already started; used to enforce one-shot
|
|
||||||
// semantics on non-rescannable sources.
|
|
||||||
scanned: bool,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[napi]
|
|
||||||
impl NapiScannable {
|
|
||||||
/// Construct a new `NapiScannable`.
|
|
||||||
///
|
|
||||||
/// - `schema_buf` — Arrow IPC File buffer carrying only the schema (no batches).
|
|
||||||
/// - `num_rows` — optional row count hint; not validated against the stream.
|
|
||||||
/// - `rescannable` — whether `get_next_batch` may be re-driven after the
|
|
||||||
/// scan completes.
|
|
||||||
/// - `get_next_batch` -- JS callback that yields the next batch as an Arrow
|
|
||||||
/// IPC Stream message wrapped in a `Buffer`, or `null` at EOF. The
|
|
||||||
/// `isStart` argument is `true` on the first call of each new scan;
|
|
||||||
/// JS uses it to discard any cached iterator before pulling.
|
|
||||||
#[napi(constructor)]
|
|
||||||
pub fn new(
|
|
||||||
schema_buf: Buffer,
|
|
||||||
num_rows: Option<i64>,
|
|
||||||
rescannable: bool,
|
|
||||||
get_next_batch: Function<bool, Promise<Option<Buffer>>>,
|
|
||||||
) -> napi::Result<Self> {
|
|
||||||
let schema = ipc_file_to_schema(schema_buf.to_vec())
|
|
||||||
.map_err(|e| napi::Error::from_reason(format!("Invalid schema buffer: {}", e)))?;
|
|
||||||
let num_rows = num_rows
|
|
||||||
.map(|n| {
|
|
||||||
usize::try_from(n)
|
|
||||||
.map_err(|_| napi::Error::from_reason("num_rows must be non-negative"))
|
|
||||||
})
|
|
||||||
.transpose()?;
|
|
||||||
let get_next_batch = Arc::new(get_next_batch.build_threadsafe_function().build()?);
|
|
||||||
Ok(Self {
|
|
||||||
schema,
|
|
||||||
num_rows,
|
|
||||||
rescannable,
|
|
||||||
get_next_batch,
|
|
||||||
scanned: false,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl std::fmt::Debug for NapiScannable {
|
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
||||||
f.debug_struct("NapiScannable")
|
|
||||||
.field("schema", &self.schema)
|
|
||||||
.field("num_rows", &self.num_rows)
|
|
||||||
.field("rescannable", &self.rescannable)
|
|
||||||
.finish()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl LanceScannable for NapiScannable {
|
|
||||||
fn schema(&self) -> SchemaRef {
|
|
||||||
self.schema.clone()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn scan_as_stream(&mut self) -> SendableRecordBatchStream {
|
|
||||||
let schema = self.schema.clone();
|
|
||||||
|
|
||||||
// One-shot enforcement for non-rescannable sources: return a stream
|
|
||||||
// whose first item is an error.
|
|
||||||
if self.scanned && !self.rescannable {
|
|
||||||
let err_stream = once(async {
|
|
||||||
Err(Error::InvalidInput {
|
|
||||||
message: "Scannable has already been consumed (non-rescannable source)"
|
|
||||||
.to_string(),
|
|
||||||
})
|
|
||||||
});
|
|
||||||
return Box::pin(SimpleRecordBatchStream::new(err_stream, schema));
|
|
||||||
}
|
|
||||||
self.scanned = true;
|
|
||||||
|
|
||||||
let tsfn = Arc::clone(&self.get_next_batch);
|
|
||||||
let declared_schema = schema.clone();
|
|
||||||
|
|
||||||
// State threaded through the unfold. `is_first_pull` starts true so
|
|
||||||
// the first call into JS signals a new-scan boundary; JS uses it to
|
|
||||||
// reset any cached iterator before factory()-ing a fresh one.
|
|
||||||
let initial = State {
|
|
||||||
tsfn,
|
|
||||||
batch_index: 0,
|
|
||||||
declared_schema,
|
|
||||||
errored: false,
|
|
||||||
is_first_pull: true,
|
|
||||||
};
|
|
||||||
|
|
||||||
let stream = futures::stream::unfold(initial, |mut state| async move {
|
|
||||||
if state.errored {
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Pull the next IPC Stream buffer from JS. `is_first_pull` is
|
|
||||||
// consumed here and cleared so subsequent pulls continue the
|
|
||||||
// same scan rather than restarting it.
|
|
||||||
let is_start = state.is_first_pull;
|
|
||||||
state.is_first_pull = false;
|
|
||||||
let buf = match pull_next(&state.tsfn, is_start).await {
|
|
||||||
Ok(Some(buf)) => buf,
|
|
||||||
Ok(None) => return None,
|
|
||||||
Err(e) => {
|
|
||||||
state.errored = true;
|
|
||||||
return Some((Err(e), state));
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
match decode_one_batch(buf.as_ref(), &state.declared_schema) {
|
|
||||||
Ok(batch) => {
|
|
||||||
state.batch_index += 1;
|
|
||||||
Some((Ok(batch), state))
|
|
||||||
}
|
|
||||||
Err(e) => {
|
|
||||||
let tagged = Error::Runtime {
|
|
||||||
message: format!(
|
|
||||||
"[scannable/rust-bridge] failure at batch index {}: {}",
|
|
||||||
state.batch_index, e
|
|
||||||
),
|
|
||||||
};
|
|
||||||
state.errored = true;
|
|
||||||
Some((Err(tagged), state))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
Box::pin(SimpleRecordBatchStream::new(stream, schema))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn num_rows(&self) -> Option<usize> {
|
|
||||||
self.num_rows
|
|
||||||
}
|
|
||||||
|
|
||||||
fn rescannable(&self) -> bool {
|
|
||||||
self.rescannable
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
struct State {
|
|
||||||
tsfn: Arc<GetNextBatchFn>,
|
|
||||||
batch_index: usize,
|
|
||||||
declared_schema: SchemaRef,
|
|
||||||
errored: bool,
|
|
||||||
/// True for the very first pull of a new scan. Forwarded to JS so the
|
|
||||||
/// callback can drop any cached iterator and call its factory fresh,
|
|
||||||
/// which makes rescannable sources restart at batch 0 even when the
|
|
||||||
/// previous scan ended mid-stream.
|
|
||||||
is_first_pull: bool,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Invoke the JS callback and await its Promise. `is_start` is forwarded to
|
|
||||||
/// the JS side as the `isStart` argument so it can reset its iterator at the
|
|
||||||
/// scan boundary. Errors on the JS side surface here as rejected promises
|
|
||||||
/// and are tunneled back as `lancedb::Error::Runtime`.
|
|
||||||
async fn pull_next(tsfn: &GetNextBatchFn, is_start: bool) -> LanceResult<Option<Buffer>> {
|
|
||||||
let promise = tsfn
|
|
||||||
.call_async(is_start)
|
|
||||||
.await
|
|
||||||
.map_err(|e| Error::Runtime {
|
|
||||||
message: format!(
|
|
||||||
"[scannable/js-factory] napi error status={}, reason={}",
|
|
||||||
e.status, e.reason
|
|
||||||
),
|
|
||||||
})?;
|
|
||||||
promise.await.map_err(|e| Error::Runtime {
|
|
||||||
message: format!(
|
|
||||||
"[scannable/js-iterator] napi error status={}, reason={}",
|
|
||||||
e.status, e.reason
|
|
||||||
),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Decode one IPC Stream buffer (schema + batch + EOS) into a `RecordBatch`.
|
|
||||||
/// Each buffer is a standalone IPC stream, so every decoded stream schema must
|
|
||||||
/// match the one declared at construction.
|
|
||||||
fn decode_one_batch(buf: &[u8], declared: &SchemaRef) -> LanceResult<RecordBatch> {
|
|
||||||
let reader = StreamReader::try_new(Cursor::new(buf), None).map_err(|e| Error::Runtime {
|
|
||||||
message: format!("failed to open IPC stream reader: {}", e),
|
|
||||||
})?;
|
|
||||||
|
|
||||||
let actual = reader.schema();
|
|
||||||
if actual.as_ref() != declared.as_ref() {
|
|
||||||
return Err(Error::InvalidInput {
|
|
||||||
message: format!(
|
|
||||||
"declared schema does not match stream schema: declared={:?} actual={:?}",
|
|
||||||
declared, actual
|
|
||||||
),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut iter = reader;
|
|
||||||
let batch = iter
|
|
||||||
.next()
|
|
||||||
.ok_or_else(|| Error::Runtime {
|
|
||||||
message: "IPC stream contained schema but no record batch".to_string(),
|
|
||||||
})?
|
|
||||||
.map_err(|e| Error::Runtime {
|
|
||||||
message: format!("failed to decode record batch: {}", e),
|
|
||||||
})?;
|
|
||||||
Ok(batch)
|
|
||||||
}
|
|
||||||
@@ -9,7 +9,6 @@ use lancedb::table::{
|
|||||||
OptimizeAction, OptimizeOptions, Table as LanceDbTable,
|
OptimizeAction, OptimizeOptions, Table as LanceDbTable,
|
||||||
};
|
};
|
||||||
use napi::bindgen_prelude::*;
|
use napi::bindgen_prelude::*;
|
||||||
use napi::threadsafe_function::{ThreadsafeFunction, ThreadsafeFunctionCallMode};
|
|
||||||
use napi_derive::napi;
|
use napi_derive::napi;
|
||||||
|
|
||||||
use crate::error::NapiErrorExt;
|
use crate::error::NapiErrorExt;
|
||||||
@@ -68,16 +67,8 @@ impl Table {
|
|||||||
schema_to_buffer(&schema)
|
schema_to_buffer(&schema)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[napi(
|
#[napi(catch_unwind)]
|
||||||
catch_unwind,
|
pub async fn add(&self, buf: Buffer, mode: String) -> napi::Result<AddResult> {
|
||||||
ts_args_type = "buf: Buffer, mode: string, progressCallback?: (progress: WriteProgressInfo) => void"
|
|
||||||
)]
|
|
||||||
pub async fn add(
|
|
||||||
&self,
|
|
||||||
buf: Buffer,
|
|
||||||
mode: String,
|
|
||||||
progress_callback: Option<ProgressFn>,
|
|
||||||
) -> napi::Result<AddResult> {
|
|
||||||
let batches = ipc_file_to_batches(buf.to_vec())
|
let batches = ipc_file_to_batches(buf.to_vec())
|
||||||
.map_err(|e| napi::Error::from_reason(format!("Failed to read IPC file: {}", e)))?;
|
.map_err(|e| napi::Error::from_reason(format!("Failed to read IPC file: {}", e)))?;
|
||||||
let batches = batches
|
let batches = batches
|
||||||
@@ -101,19 +92,6 @@ impl Table {
|
|||||||
return Err(napi::Error::from_reason(format!("Invalid mode: {}", mode)));
|
return Err(napi::Error::from_reason(format!("Invalid mode: {}", mode)));
|
||||||
};
|
};
|
||||||
|
|
||||||
if let Some(tsfn) = progress_callback {
|
|
||||||
op = op.progress(move |p| {
|
|
||||||
// NonBlocking: dispatch onto the JS event loop without
|
|
||||||
// blocking the writer thread. With napi-rs's default
|
|
||||||
// unbounded queue, events are not dropped — a slow JS
|
|
||||||
// callback will just queue them.
|
|
||||||
tsfn.call(
|
|
||||||
WriteProgressInfo::from(p),
|
|
||||||
ThreadsafeFunctionCallMode::NonBlocking,
|
|
||||||
);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
let res = op.execute().await.default_error()?;
|
let res = op.execute().await.default_error()?;
|
||||||
Ok(res.into())
|
Ok(res.into())
|
||||||
}
|
}
|
||||||
@@ -366,36 +344,6 @@ impl Table {
|
|||||||
Ok(res.into())
|
Ok(res.into())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[napi(catch_unwind)]
|
|
||||||
pub async fn set_unenforced_primary_key(&self, columns: Vec<String>) -> napi::Result<()> {
|
|
||||||
self.inner_ref()?
|
|
||||||
.set_unenforced_primary_key(columns)
|
|
||||||
.await
|
|
||||||
.default_error()
|
|
||||||
}
|
|
||||||
|
|
||||||
#[napi(catch_unwind)]
|
|
||||||
pub async fn set_lsm_write_spec(&self, spec: LsmWriteSpec) -> napi::Result<()> {
|
|
||||||
let native_spec = lancedb::table::LsmWriteSpec::try_from(spec)?;
|
|
||||||
self.inner_ref()?
|
|
||||||
.set_lsm_write_spec(native_spec)
|
|
||||||
.await
|
|
||||||
.default_error()
|
|
||||||
}
|
|
||||||
|
|
||||||
#[napi(catch_unwind)]
|
|
||||||
pub async fn unset_lsm_write_spec(&self) -> napi::Result<()> {
|
|
||||||
self.inner_ref()?
|
|
||||||
.unset_lsm_write_spec()
|
|
||||||
.await
|
|
||||||
.default_error()
|
|
||||||
}
|
|
||||||
|
|
||||||
#[napi(catch_unwind)]
|
|
||||||
pub async fn close_lsm_writers(&self) -> napi::Result<()> {
|
|
||||||
self.inner_ref()?.close_lsm_writers().await.default_error()
|
|
||||||
}
|
|
||||||
|
|
||||||
#[napi(catch_unwind)]
|
#[napi(catch_unwind)]
|
||||||
pub async fn version(&self) -> napi::Result<i64> {
|
pub async fn version(&self) -> napi::Result<i64> {
|
||||||
self.inner_ref()?
|
self.inner_ref()?
|
||||||
@@ -590,63 +538,6 @@ impl From<lancedb::index::IndexConfig> for IndexConfig {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Specification selecting Lance's MemWAL LSM-style write path for
|
|
||||||
/// `mergeInsert`.
|
|
||||||
///
|
|
||||||
/// `specType` must be `"bucket"`, `"identity"`, or `"unsharded"`. For
|
|
||||||
/// `"bucket"`, `column` and `numBuckets` are required; for `"identity"`,
|
|
||||||
/// `column` is required.
|
|
||||||
#[napi(object)]
|
|
||||||
#[derive(Clone, Debug)]
|
|
||||||
pub struct LsmWriteSpec {
|
|
||||||
/// One of `"bucket"`, `"identity"`, or `"unsharded"`.
|
|
||||||
pub spec_type: String,
|
|
||||||
/// Bucket and identity variants: the sharding column.
|
|
||||||
pub column: Option<String>,
|
|
||||||
/// Bucket variant: the number of buckets, in `[1, 1024]`.
|
|
||||||
pub num_buckets: Option<u32>,
|
|
||||||
/// Names of indexes the MemWAL should keep up to date during writes.
|
|
||||||
pub maintained_indexes: Option<Vec<String>>,
|
|
||||||
/// Default `ShardWriter` configuration recorded in the MemWAL index.
|
|
||||||
pub writer_config_defaults: Option<HashMap<String, String>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl TryFrom<LsmWriteSpec> for lancedb::table::LsmWriteSpec {
|
|
||||||
type Error = napi::Error;
|
|
||||||
|
|
||||||
fn try_from(value: LsmWriteSpec) -> napi::Result<Self> {
|
|
||||||
let maintained = value.maintained_indexes.unwrap_or_default();
|
|
||||||
let writer_config_defaults = value.writer_config_defaults.unwrap_or_default();
|
|
||||||
let spec = match value.spec_type.as_str() {
|
|
||||||
"bucket" => {
|
|
||||||
let column = value.column.ok_or_else(|| {
|
|
||||||
napi::Error::from_reason("LsmWriteSpec bucket requires `column`")
|
|
||||||
})?;
|
|
||||||
let num_buckets = value.num_buckets.ok_or_else(|| {
|
|
||||||
napi::Error::from_reason("LsmWriteSpec bucket requires `numBuckets`")
|
|
||||||
})?;
|
|
||||||
Self::bucket(column, num_buckets)
|
|
||||||
}
|
|
||||||
"identity" => {
|
|
||||||
let column = value.column.ok_or_else(|| {
|
|
||||||
napi::Error::from_reason("LsmWriteSpec identity requires `column`")
|
|
||||||
})?;
|
|
||||||
Self::identity(column)
|
|
||||||
}
|
|
||||||
"unsharded" => Self::unsharded(),
|
|
||||||
other => {
|
|
||||||
return Err(napi::Error::from_reason(format!(
|
|
||||||
"LsmWriteSpec `specType` must be 'bucket', 'identity', or 'unsharded', got '{}'",
|
|
||||||
other
|
|
||||||
)));
|
|
||||||
}
|
|
||||||
};
|
|
||||||
Ok(spec
|
|
||||||
.with_maintained_indexes(maintained)
|
|
||||||
.with_writer_config_defaults(writer_config_defaults))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Statistics about a compaction operation.
|
/// Statistics about a compaction operation.
|
||||||
#[napi(object)]
|
#[napi(object)]
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
@@ -681,44 +572,6 @@ pub struct OptimizeStats {
|
|||||||
pub prune: RemovalStats,
|
pub prune: RemovalStats,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Progress snapshot for a write operation, delivered to the JS callback
|
|
||||||
/// passed to `Table.add`.
|
|
||||||
#[napi(object)]
|
|
||||||
#[derive(Clone, Debug)]
|
|
||||||
pub struct WriteProgressInfo {
|
|
||||||
/// Number of rows written so far.
|
|
||||||
pub output_rows: i64,
|
|
||||||
/// Number of bytes written so far.
|
|
||||||
pub output_bytes: i64,
|
|
||||||
/// Total rows expected, if the input source reports it.
|
|
||||||
/// Always set on the final callback (where `done` is `true`).
|
|
||||||
pub total_rows: Option<i64>,
|
|
||||||
/// Wall-clock seconds since monitoring started.
|
|
||||||
pub elapsed_seconds: f64,
|
|
||||||
/// Number of parallel write tasks currently in flight.
|
|
||||||
pub active_tasks: i64,
|
|
||||||
/// Total number of parallel write tasks (the write parallelism).
|
|
||||||
pub total_tasks: i64,
|
|
||||||
/// `true` for the final callback; `false` otherwise.
|
|
||||||
pub done: bool,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<&lancedb::table::write_progress::WriteProgress> for WriteProgressInfo {
|
|
||||||
fn from(p: &lancedb::table::write_progress::WriteProgress) -> Self {
|
|
||||||
Self {
|
|
||||||
output_rows: p.output_rows() as i64,
|
|
||||||
output_bytes: p.output_bytes() as i64,
|
|
||||||
total_rows: p.total_rows().map(|n| n as i64),
|
|
||||||
elapsed_seconds: p.elapsed().as_secs_f64(),
|
|
||||||
active_tasks: p.active_tasks() as i64,
|
|
||||||
total_tasks: p.total_tasks() as i64,
|
|
||||||
done: p.done(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type ProgressFn = ThreadsafeFunction<WriteProgressInfo, (), WriteProgressInfo, Status, false>;
|
|
||||||
|
|
||||||
/// A definition of a column alteration. The alteration changes the column at
|
/// A definition of a column alteration. The alteration changes the column at
|
||||||
/// `path` to have the new name `name`, to be nullable if `nullable` is true,
|
/// `path` to have the new name `name`, to be nullable if `nullable` is true,
|
||||||
/// and to have the data type `data_type`. At least one of `rename` or `nullable`
|
/// and to have the data type `data_type`. At least one of `rename` or `nullable`
|
||||||
@@ -945,7 +798,6 @@ pub struct MergeResult {
|
|||||||
pub num_updated_rows: i64,
|
pub num_updated_rows: i64,
|
||||||
pub num_deleted_rows: i64,
|
pub num_deleted_rows: i64,
|
||||||
pub num_attempts: i64,
|
pub num_attempts: i64,
|
||||||
pub num_rows: i64,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<lancedb::table::MergeResult> for MergeResult {
|
impl From<lancedb::table::MergeResult> for MergeResult {
|
||||||
@@ -956,7 +808,6 @@ impl From<lancedb::table::MergeResult> for MergeResult {
|
|||||||
num_updated_rows: value.num_updated_rows as i64,
|
num_updated_rows: value.num_updated_rows as i64,
|
||||||
num_deleted_rows: value.num_deleted_rows as i64,
|
num_deleted_rows: value.num_deleted_rows as i64,
|
||||||
num_attempts: value.num_attempts as i64,
|
num_attempts: value.num_attempts as i64,
|
||||||
num_rows: value.num_rows as i64,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
[tool.bumpversion]
|
[tool.bumpversion]
|
||||||
current_version = "0.33.1-beta.0"
|
current_version = "0.32.0"
|
||||||
parse = """(?x)
|
parse = """(?x)
|
||||||
(?P<major>0|[1-9]\\d*)\\.
|
(?P<major>0|[1-9]\\d*)\\.
|
||||||
(?P<minor>0|[1-9]\\d*)\\.
|
(?P<minor>0|[1-9]\\d*)\\.
|
||||||
|
|||||||
@@ -4,26 +4,16 @@ code is in the `src/` directory and the Python bindings are in the `lancedb/` di
|
|||||||
|
|
||||||
Common commands:
|
Common commands:
|
||||||
|
|
||||||
* Bootstrap dev env: `uv run --extra tests --extra dev maturin develop --extras tests,dev`
|
|
||||||
* Build: `make develop`
|
* Build: `make develop`
|
||||||
* Format: `make format`
|
* Format: `make format`
|
||||||
* Lint: `make check`
|
* Lint: `make check`
|
||||||
* Fix lints: `make fix`
|
* Fix lints: `make fix`
|
||||||
* Test: `uv run --extra tests pytest python/tests -vv --durations=10 -m "not slow and not s3_test"`
|
* Test: `make test`
|
||||||
* Run specific test: `uv run --extra tests pytest python/tests/<test_file>.py::<test_name> -q`
|
* Doc test: `make doctest`
|
||||||
* Doc test: `uv run --extra tests pytest --doctest-modules python/lancedb`
|
|
||||||
|
|
||||||
Use the uv-managed environment declared by `uv.lock` for Python validation. Do
|
|
||||||
not treat system `python`, global `pytest`, or missing editable-install errors
|
|
||||||
as final blockers; bootstrap or enter the uv environment instead. `make test`
|
|
||||||
and `make doctest` assume the development environment is already prepared.
|
|
||||||
|
|
||||||
Before committing changes, run lints and then formatting.
|
Before committing changes, run lints and then formatting.
|
||||||
|
|
||||||
When you change the Rust code, PyO3 binding code, or see a missing/stale
|
When you change the Rust code, you will need to recompile the Python bindings: `make develop`.
|
||||||
`lancedb._lancedb`, recompile the Python bindings with
|
|
||||||
`uv run --extra tests --extra dev maturin develop --extras tests,dev` before
|
|
||||||
running tests.
|
|
||||||
|
|
||||||
When you export new types from Rust to Python, you must manually update `python/lancedb/_lancedb.pyi`
|
When you export new types from Rust to Python, you must manually update `python/lancedb/_lancedb.pyi`
|
||||||
with the corresponding type hints. You can run `pyright` to check for type errors in the Python code.
|
with the corresponding type hints. You can run `pyright` to check for type errors in the Python code.
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "lancedb-python"
|
name = "lancedb-python"
|
||||||
version = "0.33.1-beta.0"
|
version = "0.32.0"
|
||||||
publish = false
|
publish = false
|
||||||
edition.workspace = true
|
edition.workspace = true
|
||||||
description = "Python bindings for LanceDB"
|
description = "Python bindings for LanceDB"
|
||||||
@@ -19,7 +19,6 @@ arrow = { version = "58.0.0", features = ["pyarrow"] }
|
|||||||
async-trait = "0.1"
|
async-trait = "0.1"
|
||||||
bytes = "1"
|
bytes = "1"
|
||||||
lancedb = { path = "../rust/lancedb", default-features = false }
|
lancedb = { path = "../rust/lancedb", default-features = false }
|
||||||
datafusion-common.workspace = true
|
|
||||||
lance-core.workspace = true
|
lance-core.workspace = true
|
||||||
lance-namespace.workspace = true
|
lance-namespace.workspace = true
|
||||||
lance-namespace-impls.workspace = true
|
lance-namespace-impls.workspace = true
|
||||||
|
|||||||
@@ -45,7 +45,7 @@ repository = "https://github.com/lancedb/lancedb"
|
|||||||
|
|
||||||
[project.optional-dependencies]
|
[project.optional-dependencies]
|
||||||
pylance = [
|
pylance = [
|
||||||
"pylance>=5.0.0b5",
|
"pylance>=6.0.0",
|
||||||
]
|
]
|
||||||
tests = [
|
tests = [
|
||||||
"aiohttp>=3.9.0",
|
"aiohttp>=3.9.0",
|
||||||
@@ -58,7 +58,7 @@ tests = [
|
|||||||
"pytz>=2023.3",
|
"pytz>=2023.3",
|
||||||
"polars>=0.19, <=1.3.0",
|
"polars>=0.19, <=1.3.0",
|
||||||
"pyarrow-stubs>=16.0",
|
"pyarrow-stubs>=16.0",
|
||||||
"pylance>=5.0.0b5",
|
"pylance>=6.0.0",
|
||||||
"requests>=2.31.0",
|
"requests>=2.31.0",
|
||||||
"datafusion>=52,<53",
|
"datafusion>=52,<53",
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -94,6 +94,7 @@ def connect(
|
|||||||
host_override: str, optional
|
host_override: str, optional
|
||||||
The override url for LanceDB Cloud.
|
The override url for LanceDB Cloud.
|
||||||
read_consistency_interval: timedelta, default None
|
read_consistency_interval: timedelta, default None
|
||||||
|
(For LanceDB OSS only)
|
||||||
The interval at which to check for updates to the table from other
|
The interval at which to check for updates to the table from other
|
||||||
processes. If None, then consistency is not checked. For performance
|
processes. If None, then consistency is not checked. For performance
|
||||||
reasons, this is the default. For strong consistency, set this to
|
reasons, this is the default. For strong consistency, set this to
|
||||||
@@ -103,10 +104,6 @@ def connect(
|
|||||||
the last check, then the table will be checked for updates. Note: this
|
the last check, then the table will be checked for updates. Note: this
|
||||||
consistency only applies to read operations. Write operations are
|
consistency only applies to read operations. Write operations are
|
||||||
always consistent.
|
always consistent.
|
||||||
|
|
||||||
Stronger consistency is not free. The smaller the interval, the more
|
|
||||||
often each read pays the cost of checking for updates against object
|
|
||||||
storage, raising per-read latency and cost.
|
|
||||||
client_config: ClientConfig or dict, optional
|
client_config: ClientConfig or dict, optional
|
||||||
Configuration options for the LanceDB Cloud HTTP client. If a dict, then
|
Configuration options for the LanceDB Cloud HTTP client. If a dict, then
|
||||||
the keys are the attributes of the ClientConfig class. If None, then the
|
the keys are the attributes of the ClientConfig class. If None, then the
|
||||||
@@ -150,13 +147,6 @@ def connect(
|
|||||||
>>> db = lancedb.connect("s3://my-bucket/lancedb",
|
>>> db = lancedb.connect("s3://my-bucket/lancedb",
|
||||||
... storage_options={"aws_access_key_id": "***"})
|
... storage_options={"aws_access_key_id": "***"})
|
||||||
|
|
||||||
For tests and temporary data, use an in-memory database:
|
|
||||||
|
|
||||||
>>> db = lancedb.connect("memory://")
|
|
||||||
|
|
||||||
In-memory databases are not persisted. Tables are dropped when the last
|
|
||||||
connection or table handle referencing them is closed.
|
|
||||||
|
|
||||||
Connect to LanceDB cloud:
|
Connect to LanceDB cloud:
|
||||||
|
|
||||||
>>> db = lancedb.connect("db://my_database", api_key="ldb_...",
|
>>> db = lancedb.connect("db://my_database", api_key="ldb_...",
|
||||||
@@ -220,7 +210,6 @@ def connect(
|
|||||||
request_thread_pool=request_thread_pool,
|
request_thread_pool=request_thread_pool,
|
||||||
client_config=client_config,
|
client_config=client_config,
|
||||||
storage_options=storage_options,
|
storage_options=storage_options,
|
||||||
read_consistency_interval=read_consistency_interval,
|
|
||||||
**kwargs,
|
**kwargs,
|
||||||
)
|
)
|
||||||
_check_s3_bucket_with_dots(str(uri), storage_options)
|
_check_s3_bucket_with_dots(str(uri), storage_options)
|
||||||
@@ -315,15 +304,6 @@ def deserialize_conn(
|
|||||||
manifest_enabled=parsed.get("manifest_enabled", False),
|
manifest_enabled=parsed.get("manifest_enabled", False),
|
||||||
namespace_client_properties=parsed.get("namespace_client_properties"),
|
namespace_client_properties=parsed.get("namespace_client_properties"),
|
||||||
)
|
)
|
||||||
elif connection_type == "remote":
|
|
||||||
return RemoteDBConnection(
|
|
||||||
parsed["db_url"],
|
|
||||||
parsed["api_key"],
|
|
||||||
parsed.get("region", "us-east-1"),
|
|
||||||
host_override=parsed.get("host_override"),
|
|
||||||
client_config=parsed.get("client_config"),
|
|
||||||
storage_options=storage_options,
|
|
||||||
)
|
|
||||||
else:
|
else:
|
||||||
raise ValueError(f"Unknown connection_type: {connection_type}")
|
raise ValueError(f"Unknown connection_type: {connection_type}")
|
||||||
|
|
||||||
@@ -356,6 +336,7 @@ async def connect_async(
|
|||||||
host_override: str, optional
|
host_override: str, optional
|
||||||
The override url for LanceDB Cloud.
|
The override url for LanceDB Cloud.
|
||||||
read_consistency_interval: timedelta, default None
|
read_consistency_interval: timedelta, default None
|
||||||
|
(For LanceDB OSS only)
|
||||||
The interval at which to check for updates to the table from other
|
The interval at which to check for updates to the table from other
|
||||||
processes. If None, then consistency is not checked. For performance
|
processes. If None, then consistency is not checked. For performance
|
||||||
reasons, this is the default. For strong consistency, set this to
|
reasons, this is the default. For strong consistency, set this to
|
||||||
@@ -365,10 +346,6 @@ async def connect_async(
|
|||||||
the last check, then the table will be checked for updates. Note: this
|
the last check, then the table will be checked for updates. Note: this
|
||||||
consistency only applies to read operations. Write operations are
|
consistency only applies to read operations. Write operations are
|
||||||
always consistent.
|
always consistent.
|
||||||
|
|
||||||
Stronger consistency is not free. The smaller the interval, the more
|
|
||||||
often each read pays the cost of checking for updates against object
|
|
||||||
storage, raising per-read latency and cost.
|
|
||||||
client_config: ClientConfig or dict, optional
|
client_config: ClientConfig or dict, optional
|
||||||
Configuration options for the LanceDB Cloud HTTP client. If a dict, then
|
Configuration options for the LanceDB Cloud HTTP client. If a dict, then
|
||||||
the keys are the attributes of the ClientConfig class. If None, then the
|
the keys are the attributes of the ClientConfig class. If None, then the
|
||||||
@@ -401,8 +378,6 @@ async def connect_async(
|
|||||||
... db = await lancedb.connect_async("s3://my-bucket/lancedb",
|
... db = await lancedb.connect_async("s3://my-bucket/lancedb",
|
||||||
... storage_options={
|
... storage_options={
|
||||||
... "aws_access_key_id": "***"})
|
... "aws_access_key_id": "***"})
|
||||||
... # For tests and temporary data, use an in-memory database
|
|
||||||
... db = await lancedb.connect_async("memory://")
|
|
||||||
... # Connect to LanceDB cloud
|
... # Connect to LanceDB cloud
|
||||||
... db = await lancedb.connect_async("db://my_database", api_key="ldb_...",
|
... db = await lancedb.connect_async("db://my_database", api_key="ldb_...",
|
||||||
... client_config={
|
... client_config={
|
||||||
|
|||||||
@@ -51,7 +51,7 @@ class PyExpr:
|
|||||||
def to_sql(self) -> str: ...
|
def to_sql(self) -> str: ...
|
||||||
|
|
||||||
def expr_col(name: str) -> PyExpr: ...
|
def expr_col(name: str) -> PyExpr: ...
|
||||||
def expr_lit(value: Union[bool, int, float, str, bytes]) -> PyExpr: ...
|
def expr_lit(value: Union[bool, int, float, str]) -> PyExpr: ...
|
||||||
def expr_func(name: str, args: List[PyExpr]) -> PyExpr: ...
|
def expr_func(name: str, args: List[PyExpr]) -> PyExpr: ...
|
||||||
|
|
||||||
class Session:
|
class Session:
|
||||||
@@ -217,10 +217,6 @@ class Table:
|
|||||||
async def uri(self) -> str: ...
|
async def uri(self) -> str: ...
|
||||||
async def initial_storage_options(self) -> Optional[Dict[str, str]]: ...
|
async def initial_storage_options(self) -> Optional[Dict[str, str]]: ...
|
||||||
async def latest_storage_options(self) -> Optional[Dict[str, str]]: ...
|
async def latest_storage_options(self) -> Optional[Dict[str, str]]: ...
|
||||||
async def set_unenforced_primary_key(self, columns: List[str]) -> None: ...
|
|
||||||
async def set_lsm_write_spec(self, spec: LsmWriteSpec) -> None: ...
|
|
||||||
async def unset_lsm_write_spec(self) -> None: ...
|
|
||||||
async def close_lsm_writers(self) -> None: ...
|
|
||||||
@property
|
@property
|
||||||
def tags(self) -> Tags: ...
|
def tags(self) -> Tags: ...
|
||||||
def query(self) -> Query: ...
|
def query(self) -> Query: ...
|
||||||
@@ -259,11 +255,6 @@ class RecordBatchStream:
|
|||||||
def __aiter__(self) -> "RecordBatchStream": ...
|
def __aiter__(self) -> "RecordBatchStream": ...
|
||||||
async def __anext__(self) -> pa.RecordBatch: ...
|
async def __anext__(self) -> pa.RecordBatch: ...
|
||||||
|
|
||||||
class ColumnOrdering(TypedDict):
|
|
||||||
column_name: str
|
|
||||||
ascending: bool
|
|
||||||
nulls_first: bool
|
|
||||||
|
|
||||||
class Query:
|
class Query:
|
||||||
def where(self, filter: str): ...
|
def where(self, filter: str): ...
|
||||||
def where_expr(self, expr: PyExpr): ...
|
def where_expr(self, expr: PyExpr): ...
|
||||||
@@ -277,7 +268,6 @@ class Query:
|
|||||||
def postfilter(self): ...
|
def postfilter(self): ...
|
||||||
def nearest_to(self, query_vec: pa.Array) -> VectorQuery: ...
|
def nearest_to(self, query_vec: pa.Array) -> VectorQuery: ...
|
||||||
def nearest_to_text(self, query: dict) -> FTSQuery: ...
|
def nearest_to_text(self, query: dict) -> FTSQuery: ...
|
||||||
def order_by(self, ordering: Optional[List[ColumnOrdering]]): ...
|
|
||||||
async def output_schema(self) -> pa.Schema: ...
|
async def output_schema(self) -> pa.Schema: ...
|
||||||
async def execute(
|
async def execute(
|
||||||
self, max_batch_length: Optional[int], timeout: Optional[timedelta]
|
self, max_batch_length: Optional[int], timeout: Optional[timedelta]
|
||||||
@@ -306,7 +296,6 @@ class FTSQuery:
|
|||||||
def get_query(self) -> str: ...
|
def get_query(self) -> str: ...
|
||||||
def add_query_vector(self, query_vec: pa.Array) -> None: ...
|
def add_query_vector(self, query_vec: pa.Array) -> None: ...
|
||||||
def nearest_to(self, query_vec: pa.Array) -> HybridQuery: ...
|
def nearest_to(self, query_vec: pa.Array) -> HybridQuery: ...
|
||||||
def order_by(self, ordering: Optional[List[ColumnOrdering]]): ...
|
|
||||||
async def output_schema(self) -> pa.Schema: ...
|
async def output_schema(self) -> pa.Schema: ...
|
||||||
async def execute(
|
async def execute(
|
||||||
self, max_batch_length: Optional[int], timeout: Optional[timedelta]
|
self, max_batch_length: Optional[int], timeout: Optional[timedelta]
|
||||||
@@ -332,7 +321,6 @@ class VectorQuery:
|
|||||||
def maximum_nprobes(self, maximum_nprobes: int): ...
|
def maximum_nprobes(self, maximum_nprobes: int): ...
|
||||||
def bypass_vector_index(self): ...
|
def bypass_vector_index(self): ...
|
||||||
def nearest_to_text(self, query: dict) -> HybridQuery: ...
|
def nearest_to_text(self, query: dict) -> HybridQuery: ...
|
||||||
def order_by(self, ordering: Optional[List[ColumnOrdering]]): ...
|
|
||||||
def to_query_request(self) -> PyQueryRequest: ...
|
def to_query_request(self) -> PyQueryRequest: ...
|
||||||
|
|
||||||
class HybridQuery:
|
class HybridQuery:
|
||||||
@@ -351,7 +339,6 @@ class HybridQuery:
|
|||||||
def minimum_nprobes(self, minimum_nprobes: int): ...
|
def minimum_nprobes(self, minimum_nprobes: int): ...
|
||||||
def maximum_nprobes(self, maximum_nprobes: int): ...
|
def maximum_nprobes(self, maximum_nprobes: int): ...
|
||||||
def bypass_vector_index(self): ...
|
def bypass_vector_index(self): ...
|
||||||
def order_by(self, ordering: Optional[List[ColumnOrdering]]): ...
|
|
||||||
def to_vector_query(self) -> VectorQuery: ...
|
def to_vector_query(self) -> VectorQuery: ...
|
||||||
def to_fts_query(self) -> FTSQuery: ...
|
def to_fts_query(self) -> FTSQuery: ...
|
||||||
def get_limit(self) -> int: ...
|
def get_limit(self) -> int: ...
|
||||||
@@ -381,7 +368,6 @@ class PyQueryRequest:
|
|||||||
bypass_vector_index: Optional[bool]
|
bypass_vector_index: Optional[bool]
|
||||||
postfilter: Optional[bool]
|
postfilter: Optional[bool]
|
||||||
norm: Optional[str]
|
norm: Optional[str]
|
||||||
order_by: Optional[List[ColumnOrdering]]
|
|
||||||
|
|
||||||
class CompactionStats:
|
class CompactionStats:
|
||||||
fragments_removed: int
|
fragments_removed: int
|
||||||
@@ -421,38 +407,6 @@ class MergeResult:
|
|||||||
num_inserted_rows: int
|
num_inserted_rows: int
|
||||||
num_deleted_rows: int
|
num_deleted_rows: int
|
||||||
num_attempts: int
|
num_attempts: int
|
||||||
num_rows: int
|
|
||||||
|
|
||||||
class LsmWriteSpec:
|
|
||||||
"""Specification selecting Lance's MemWAL LSM-style write path for
|
|
||||||
`merge_insert`."""
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def bucket(column: str, num_buckets: int) -> "LsmWriteSpec": ...
|
|
||||||
@staticmethod
|
|
||||||
def identity(column: str) -> "LsmWriteSpec": ...
|
|
||||||
@staticmethod
|
|
||||||
def unsharded() -> "LsmWriteSpec": ...
|
|
||||||
def with_maintained_indexes(self, indexes: List[str]) -> "LsmWriteSpec":
|
|
||||||
"""Return a copy of this spec asking the MemWAL to keep the named
|
|
||||||
indexes up to date as rows are appended."""
|
|
||||||
...
|
|
||||||
def with_writer_config_defaults(self, defaults: Dict[str, str]) -> "LsmWriteSpec":
|
|
||||||
"""Return a copy of this spec recording the given default
|
|
||||||
`ShardWriter` configuration in the MemWAL index."""
|
|
||||||
...
|
|
||||||
@property
|
|
||||||
def spec_type(self) -> str:
|
|
||||||
"""One of 'bucket', 'identity', or 'unsharded'."""
|
|
||||||
...
|
|
||||||
@property
|
|
||||||
def column(self) -> Optional[str]: ...
|
|
||||||
@property
|
|
||||||
def num_buckets(self) -> Optional[int]: ...
|
|
||||||
@property
|
|
||||||
def maintained_indexes(self) -> List[str]: ...
|
|
||||||
@property
|
|
||||||
def writer_config_defaults(self) -> Dict[str, str]: ...
|
|
||||||
|
|
||||||
class AddColumnsResult:
|
class AddColumnsResult:
|
||||||
version: int
|
version: int
|
||||||
|
|||||||
@@ -8,17 +8,7 @@ from abc import abstractmethod
|
|||||||
from datetime import timedelta
|
from datetime import timedelta
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
import sys
|
import sys
|
||||||
from typing import (
|
from typing import TYPE_CHECKING, Any, Dict, Iterable, List, Literal, Optional, Union
|
||||||
TYPE_CHECKING,
|
|
||||||
Any,
|
|
||||||
Dict,
|
|
||||||
Generator,
|
|
||||||
Iterable,
|
|
||||||
List,
|
|
||||||
Literal,
|
|
||||||
Optional,
|
|
||||||
Union,
|
|
||||||
)
|
|
||||||
|
|
||||||
if sys.version_info >= (3, 12):
|
if sys.version_info >= (3, 12):
|
||||||
from typing import override
|
from typing import override
|
||||||
@@ -323,7 +313,7 @@ class DBConnection(EnforceOverrides):
|
|||||||
>>> data = [{"vector": [1.1, 1.2], "lat": 45.5, "long": -122.7},
|
>>> data = [{"vector": [1.1, 1.2], "lat": 45.5, "long": -122.7},
|
||||||
... {"vector": [0.2, 1.8], "lat": 40.1, "long": -74.1}]
|
... {"vector": [0.2, 1.8], "lat": 40.1, "long": -74.1}]
|
||||||
>>> db.create_table("my_table", data)
|
>>> db.create_table("my_table", data)
|
||||||
LanceTable(name='my_table', ...)
|
LanceTable(name='my_table', version=1, ...)
|
||||||
>>> db["my_table"].head()
|
>>> db["my_table"].head()
|
||||||
pyarrow.Table
|
pyarrow.Table
|
||||||
vector: fixed_size_list<item: float>[2]
|
vector: fixed_size_list<item: float>[2]
|
||||||
@@ -344,7 +334,7 @@ class DBConnection(EnforceOverrides):
|
|||||||
... "long": [-122.7, -74.1]
|
... "long": [-122.7, -74.1]
|
||||||
... })
|
... })
|
||||||
>>> db.create_table("table2", data)
|
>>> db.create_table("table2", data)
|
||||||
LanceTable(name='table2', ...)
|
LanceTable(name='table2', version=1, ...)
|
||||||
>>> db["table2"].head()
|
>>> db["table2"].head()
|
||||||
pyarrow.Table
|
pyarrow.Table
|
||||||
vector: fixed_size_list<item: float>[2]
|
vector: fixed_size_list<item: float>[2]
|
||||||
@@ -367,7 +357,7 @@ class DBConnection(EnforceOverrides):
|
|||||||
... pa.field("long", pa.float32())
|
... pa.field("long", pa.float32())
|
||||||
... ])
|
... ])
|
||||||
>>> db.create_table("table3", data, schema = custom_schema)
|
>>> db.create_table("table3", data, schema = custom_schema)
|
||||||
LanceTable(name='table3', ...)
|
LanceTable(name='table3', version=1, ...)
|
||||||
>>> db["table3"].head()
|
>>> db["table3"].head()
|
||||||
pyarrow.Table
|
pyarrow.Table
|
||||||
vector: fixed_size_list<item: float>[2]
|
vector: fixed_size_list<item: float>[2]
|
||||||
@@ -401,7 +391,7 @@ class DBConnection(EnforceOverrides):
|
|||||||
... pa.field("price", pa.float32()),
|
... pa.field("price", pa.float32()),
|
||||||
... ])
|
... ])
|
||||||
>>> db.create_table("table4", make_batches(), schema=schema)
|
>>> db.create_table("table4", make_batches(), schema=schema)
|
||||||
LanceTable(name='table4', ...)
|
LanceTable(name='table4', version=1, ...)
|
||||||
|
|
||||||
"""
|
"""
|
||||||
raise NotImplementedError
|
raise NotImplementedError
|
||||||
@@ -578,15 +568,15 @@ class LanceDBConnection(DBConnection):
|
|||||||
>>> db = lancedb.connect("./.lancedb")
|
>>> db = lancedb.connect("./.lancedb")
|
||||||
>>> db.create_table("my_table", data=[{"vector": [1.1, 1.2], "b": 2},
|
>>> db.create_table("my_table", data=[{"vector": [1.1, 1.2], "b": 2},
|
||||||
... {"vector": [0.5, 1.3], "b": 4}])
|
... {"vector": [0.5, 1.3], "b": 4}])
|
||||||
LanceTable(name='my_table', ...)
|
LanceTable(name='my_table', version=1, ...)
|
||||||
>>> db.create_table("another_table", data=[{"vector": [0.4, 0.4], "b": 6}])
|
>>> db.create_table("another_table", data=[{"vector": [0.4, 0.4], "b": 6}])
|
||||||
LanceTable(name='another_table', ...)
|
LanceTable(name='another_table', version=1, ...)
|
||||||
>>> sorted(db.table_names())
|
>>> sorted(db.table_names())
|
||||||
['another_table', 'my_table']
|
['another_table', 'my_table']
|
||||||
>>> len(db)
|
>>> len(db)
|
||||||
2
|
2
|
||||||
>>> db["my_table"]
|
>>> db["my_table"]
|
||||||
LanceTable(name='my_table', ...)
|
LanceTable(name='my_table', version=1, ...)
|
||||||
>>> "my_table" in db
|
>>> "my_table" in db
|
||||||
True
|
True
|
||||||
>>> db.drop_table("my_table")
|
>>> db.drop_table("my_table")
|
||||||
@@ -857,20 +847,11 @@ class LanceDBConnection(DBConnection):
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
def _all_table_names(self) -> Generator[str, None, None]:
|
|
||||||
page_token = None
|
|
||||||
while True:
|
|
||||||
response = self.list_tables(page_token=page_token)
|
|
||||||
yield from response.tables
|
|
||||||
page_token = response.page_token
|
|
||||||
if not page_token:
|
|
||||||
return
|
|
||||||
|
|
||||||
def __len__(self) -> int:
|
def __len__(self) -> int:
|
||||||
return sum(1 for _ in self._all_table_names())
|
return len(self.table_names())
|
||||||
|
|
||||||
def __contains__(self, name: str) -> bool:
|
def __contains__(self, name: str) -> bool:
|
||||||
return name in self._all_table_names()
|
return name in self.table_names()
|
||||||
|
|
||||||
@override
|
@override
|
||||||
def create_table(
|
def create_table(
|
||||||
|
|||||||
@@ -63,7 +63,7 @@ def _coerce(value: "ExprLike") -> "Expr":
|
|||||||
|
|
||||||
|
|
||||||
# Type alias used in annotations.
|
# Type alias used in annotations.
|
||||||
ExprLike = Union["Expr", bool, int, float, str, bytes]
|
ExprLike = Union["Expr", bool, int, float, str]
|
||||||
|
|
||||||
|
|
||||||
class Expr:
|
class Expr:
|
||||||
@@ -261,13 +261,13 @@ def col(name: str) -> Expr:
|
|||||||
return Expr(expr_col(name))
|
return Expr(expr_col(name))
|
||||||
|
|
||||||
|
|
||||||
def lit(value: Union[bool, int, float, str, bytes]) -> Expr:
|
def lit(value: Union[bool, int, float, str]) -> Expr:
|
||||||
"""Create a literal (constant) value expression.
|
"""Create a literal (constant) value expression.
|
||||||
|
|
||||||
Parameters
|
Parameters
|
||||||
----------
|
----------
|
||||||
value:
|
value:
|
||||||
A Python ``bool``, ``int``, ``float``, ``str``, or ``bytes``.
|
A Python ``bool``, ``int``, ``float``, or ``str``.
|
||||||
|
|
||||||
Examples
|
Examples
|
||||||
--------
|
--------
|
||||||
|
|||||||
@@ -281,9 +281,6 @@ class HnswPq:
|
|||||||
m: int = 20
|
m: int = 20
|
||||||
ef_construction: int = 300
|
ef_construction: int = 300
|
||||||
target_partition_size: Optional[int] = None
|
target_partition_size: Optional[int] = None
|
||||||
# Name of the accelerator (e.g. "cuda") to use for IVF training. When set,
|
|
||||||
# create_index() dispatches to pylance to build the index on the accelerator.
|
|
||||||
accelerator: Optional[str] = None
|
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
@@ -389,9 +386,6 @@ class HnswSq:
|
|||||||
m: int = 20
|
m: int = 20
|
||||||
ef_construction: int = 300
|
ef_construction: int = 300
|
||||||
target_partition_size: Optional[int] = None
|
target_partition_size: Optional[int] = None
|
||||||
# Name of the accelerator (e.g. "cuda") to use for IVF training. When set,
|
|
||||||
# create_index() dispatches to pylance to build the index on the accelerator.
|
|
||||||
accelerator: Optional[str] = None
|
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
@@ -585,9 +579,6 @@ class IvfFlat:
|
|||||||
max_iterations: int = 50
|
max_iterations: int = 50
|
||||||
sample_rate: int = 256
|
sample_rate: int = 256
|
||||||
target_partition_size: Optional[int] = None
|
target_partition_size: Optional[int] = None
|
||||||
# Name of the accelerator (e.g. "cuda") to use for IVF training. When set,
|
|
||||||
# create_index() dispatches to pylance to build the index on the accelerator.
|
|
||||||
accelerator: Optional[str] = None
|
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
@@ -618,9 +609,6 @@ class IvfSq:
|
|||||||
max_iterations: int = 50
|
max_iterations: int = 50
|
||||||
sample_rate: int = 256
|
sample_rate: int = 256
|
||||||
target_partition_size: Optional[int] = None
|
target_partition_size: Optional[int] = None
|
||||||
# Name of the accelerator (e.g. "cuda") to use for IVF training. When set,
|
|
||||||
# create_index() dispatches to pylance to build the index on the accelerator.
|
|
||||||
accelerator: Optional[str] = None
|
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
@@ -751,9 +739,6 @@ class IvfPq:
|
|||||||
max_iterations: int = 50
|
max_iterations: int = 50
|
||||||
sample_rate: int = 256
|
sample_rate: int = 256
|
||||||
target_partition_size: Optional[int] = None
|
target_partition_size: Optional[int] = None
|
||||||
# Name of the accelerator (e.g. "cuda") to use for IVF training. When set,
|
|
||||||
# create_index() dispatches to pylance to build the index on the accelerator.
|
|
||||||
accelerator: Optional[str] = None
|
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
@@ -807,9 +792,6 @@ class IvfRq:
|
|||||||
max_iterations: int = 50
|
max_iterations: int = 50
|
||||||
sample_rate: int = 256
|
sample_rate: int = 256
|
||||||
target_partition_size: Optional[int] = None
|
target_partition_size: Optional[int] = None
|
||||||
# Name of the accelerator (e.g. "cuda") to use for IVF training. When set,
|
|
||||||
# create_index() dispatches to pylance to build the index on the accelerator.
|
|
||||||
accelerator: Optional[str] = None
|
|
||||||
|
|
||||||
|
|
||||||
__all__ = [
|
__all__ = [
|
||||||
|
|||||||
@@ -34,8 +34,6 @@ class LanceMergeInsertBuilder(object):
|
|||||||
self._when_not_matched_by_source_condition = None
|
self._when_not_matched_by_source_condition = None
|
||||||
self._timeout = None
|
self._timeout = None
|
||||||
self._use_index = True
|
self._use_index = True
|
||||||
self._use_lsm_write = None
|
|
||||||
self._validate_single_shard = None
|
|
||||||
|
|
||||||
def when_matched_update_all(
|
def when_matched_update_all(
|
||||||
self, *, where: Optional[str] = None
|
self, *, where: Optional[str] = None
|
||||||
@@ -98,46 +96,6 @@ class LanceMergeInsertBuilder(object):
|
|||||||
self._use_index = use_index
|
self._use_index = use_index
|
||||||
return self
|
return self
|
||||||
|
|
||||||
def use_lsm_write(self, use_lsm_write: bool) -> LanceMergeInsertBuilder:
|
|
||||||
"""
|
|
||||||
Controls whether the merge uses the MemWAL LSM write path.
|
|
||||||
|
|
||||||
By default (unset), a `merge_insert` on a table with an LSM write spec
|
|
||||||
is routed through Lance's MemWAL shard writer, and a table without one
|
|
||||||
uses the standard path. Pass `False` to force the standard path even
|
|
||||||
when a spec is set. Pass `True` to require a spec — `merge_insert`
|
|
||||||
raises an error if none is installed.
|
|
||||||
|
|
||||||
Parameters
|
|
||||||
----------
|
|
||||||
use_lsm_write: bool
|
|
||||||
Whether to use the LSM write path.
|
|
||||||
"""
|
|
||||||
self._use_lsm_write = use_lsm_write
|
|
||||||
return self
|
|
||||||
|
|
||||||
def validate_single_shard(
|
|
||||||
self, validate_single_shard: bool
|
|
||||||
) -> LanceMergeInsertBuilder:
|
|
||||||
"""
|
|
||||||
Controls how an LSM merge checks that its input targets a single shard.
|
|
||||||
|
|
||||||
When a table has an LSM write spec, every row in a `merge_insert` call
|
|
||||||
must route to the same shard. When `True` (the default), every row is
|
|
||||||
inspected to verify this. When `False`, only the first row is inspected
|
|
||||||
and the shard it routes to is used for the whole input — a faster path
|
|
||||||
for callers that have already pre-sharded their input.
|
|
||||||
|
|
||||||
Has no effect on tables without an LSM write spec.
|
|
||||||
|
|
||||||
Parameters
|
|
||||||
----------
|
|
||||||
validate_single_shard: bool
|
|
||||||
Whether to check every row routes to one shard. Defaults to `True`.
|
|
||||||
"""
|
|
||||||
self._validate_single_shard = validate_single_shard
|
|
||||||
return self
|
|
||||||
|
|
||||||
def execute(
|
def execute(
|
||||||
self,
|
self,
|
||||||
new_data: DATA,
|
new_data: DATA,
|
||||||
|
|||||||
@@ -6,44 +6,22 @@
|
|||||||
from typing import Optional
|
from typing import Optional
|
||||||
|
|
||||||
|
|
||||||
_CREATE_NAMESPACE_MODES = frozenset({"create", "exist_ok", "overwrite"})
|
|
||||||
_DROP_NAMESPACE_MODES = frozenset({"SKIP", "FAIL"})
|
|
||||||
_DROP_NAMESPACE_BEHAVIORS = frozenset({"RESTRICT", "CASCADE"})
|
|
||||||
|
|
||||||
|
|
||||||
def _normalize_create_namespace_mode(mode: Optional[str]) -> Optional[str]:
|
def _normalize_create_namespace_mode(mode: Optional[str]) -> Optional[str]:
|
||||||
"""Normalize create namespace mode to lowercase (API expects lowercase)."""
|
"""Normalize create namespace mode to lowercase (API expects lowercase)."""
|
||||||
if mode is None:
|
if mode is None:
|
||||||
return None
|
return None
|
||||||
normalized = mode.lower()
|
return mode.lower()
|
||||||
if normalized not in _CREATE_NAMESPACE_MODES:
|
|
||||||
raise ValueError(
|
|
||||||
f"Invalid create namespace mode {mode!r}: "
|
|
||||||
f"expected one of 'create', 'exist_ok', 'overwrite'"
|
|
||||||
)
|
|
||||||
return normalized
|
|
||||||
|
|
||||||
|
|
||||||
def _normalize_drop_namespace_mode(mode: Optional[str]) -> Optional[str]:
|
def _normalize_drop_namespace_mode(mode: Optional[str]) -> Optional[str]:
|
||||||
"""Normalize drop namespace mode to uppercase (API expects uppercase)."""
|
"""Normalize drop namespace mode to uppercase (API expects uppercase)."""
|
||||||
if mode is None:
|
if mode is None:
|
||||||
return None
|
return None
|
||||||
normalized = mode.upper()
|
return mode.upper()
|
||||||
if normalized not in _DROP_NAMESPACE_MODES:
|
|
||||||
raise ValueError(
|
|
||||||
f"Invalid drop namespace mode {mode!r}: expected one of 'skip', 'fail'"
|
|
||||||
)
|
|
||||||
return normalized
|
|
||||||
|
|
||||||
|
|
||||||
def _normalize_drop_namespace_behavior(behavior: Optional[str]) -> Optional[str]:
|
def _normalize_drop_namespace_behavior(behavior: Optional[str]) -> Optional[str]:
|
||||||
"""Normalize drop namespace behavior to uppercase (API expects uppercase)."""
|
"""Normalize drop namespace behavior to uppercase (API expects uppercase)."""
|
||||||
if behavior is None:
|
if behavior is None:
|
||||||
return None
|
return None
|
||||||
normalized = behavior.upper()
|
return behavior.upper()
|
||||||
if normalized not in _DROP_NAMESPACE_BEHAVIORS:
|
|
||||||
raise ValueError(
|
|
||||||
f"Invalid drop namespace behavior {behavior!r}: "
|
|
||||||
f"expected one of 'restrict', 'cascade'"
|
|
||||||
)
|
|
||||||
return normalized
|
|
||||||
|
|||||||
@@ -3,13 +3,12 @@
|
|||||||
|
|
||||||
import copy
|
import copy
|
||||||
import json
|
import json
|
||||||
import os
|
|
||||||
|
|
||||||
from deprecation import deprecated
|
from deprecation import deprecated
|
||||||
import pyarrow as pa
|
import pyarrow as pa
|
||||||
|
|
||||||
from ._lancedb import async_permutation_builder, PermutationReader
|
from ._lancedb import async_permutation_builder, PermutationReader
|
||||||
from .table import LanceTable, Table
|
from .table import LanceTable
|
||||||
from .background_loop import LOOP
|
from .background_loop import LOOP
|
||||||
from .util import batch_to_tensor, batch_to_tensor_rows
|
from .util import batch_to_tensor, batch_to_tensor_rows
|
||||||
from typing import Any, Callable, Iterator, Literal, Optional, TYPE_CHECKING, Union
|
from typing import Any, Callable, Iterator, Literal, Optional, TYPE_CHECKING, Union
|
||||||
@@ -355,49 +354,6 @@ class Transforms:
|
|||||||
DEFAULT_BATCH_SIZE = 100
|
DEFAULT_BATCH_SIZE = 100
|
||||||
|
|
||||||
|
|
||||||
def _table_to_pickle_state(table: Table) -> dict[str, Any]:
|
|
||||||
from .remote.table import RemoteTable
|
|
||||||
|
|
||||||
if isinstance(table, RemoteTable):
|
|
||||||
return {
|
|
||||||
"kind": "remote",
|
|
||||||
"table": table,
|
|
||||||
}
|
|
||||||
|
|
||||||
if not isinstance(table, LanceTable):
|
|
||||||
raise ValueError(f"Cannot pickle table of type {type(table)!r}")
|
|
||||||
|
|
||||||
base_uri = table._conn.uri
|
|
||||||
if base_uri.startswith("memory://"):
|
|
||||||
return {
|
|
||||||
"kind": "memory",
|
|
||||||
"name": table.name,
|
|
||||||
"data": table.to_arrow(),
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
"kind": "local",
|
|
||||||
"name": table.name,
|
|
||||||
"uri": base_uri,
|
|
||||||
"namespace": table._namespace_path,
|
|
||||||
"storage_options": table._conn.storage_options,
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
def _table_from_pickle_state(state: dict[str, Any]) -> Table:
|
|
||||||
from . import connect
|
|
||||||
|
|
||||||
kind = state["kind"]
|
|
||||||
if kind == "remote":
|
|
||||||
return state["table"]
|
|
||||||
if kind == "memory":
|
|
||||||
return connect("memory://").create_table(state["name"], state["data"])
|
|
||||||
if kind == "local":
|
|
||||||
db = connect(state["uri"], storage_options=state["storage_options"])
|
|
||||||
return db.open_table(state["name"], namespace_path=state["namespace"] or None)
|
|
||||||
raise ValueError(f"Unknown table pickle state kind: {kind}")
|
|
||||||
|
|
||||||
|
|
||||||
class Permutation:
|
class Permutation:
|
||||||
"""
|
"""
|
||||||
A Permutation is a view of a dataset that can be used as input to model training
|
A Permutation is a view of a dataset that can be used as input to model training
|
||||||
@@ -413,15 +369,15 @@ class Permutation:
|
|||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
base_table: Table,
|
base_table: LanceTable,
|
||||||
permutation_table: Optional[Table],
|
permutation_table: Optional[LanceTable],
|
||||||
split: int,
|
split: int,
|
||||||
selection: dict[str, str],
|
selection: dict[str, str],
|
||||||
batch_size: int,
|
batch_size: int,
|
||||||
transform_fn: Callable[pa.RecordBatch, Any],
|
transform_fn: Callable[pa.RecordBatch, Any],
|
||||||
offset: Optional[int] = None,
|
offset: Optional[int] = None,
|
||||||
limit: Optional[int] = None,
|
limit: Optional[int] = None,
|
||||||
connection_factory: Optional[Callable[[str], Table]] = None,
|
connection_factory: Optional[Callable[[str], LanceTable]] = None,
|
||||||
_reader: Optional[PermutationReader] = None,
|
_reader: Optional[PermutationReader] = None,
|
||||||
):
|
):
|
||||||
"""
|
"""
|
||||||
@@ -441,7 +397,6 @@ class Permutation:
|
|||||||
if _reader is None:
|
if _reader is None:
|
||||||
_reader = LOOP.run(self._build_reader())
|
_reader = LOOP.run(self._build_reader())
|
||||||
self.reader: PermutationReader = _reader
|
self.reader: PermutationReader = _reader
|
||||||
self._pid = os.getpid()
|
|
||||||
|
|
||||||
async def _build_reader(self) -> PermutationReader:
|
async def _build_reader(self) -> PermutationReader:
|
||||||
reader = await PermutationReader.from_tables(
|
reader = await PermutationReader.from_tables(
|
||||||
@@ -473,25 +428,29 @@ class Permutation:
|
|||||||
return new
|
return new
|
||||||
|
|
||||||
def with_connection_factory(
|
def with_connection_factory(
|
||||||
self, connection_factory: Callable[[str], Table]
|
self, connection_factory: Callable[[str], LanceTable]
|
||||||
) -> "Permutation":
|
) -> "Permutation":
|
||||||
"""
|
"""
|
||||||
Creates a new permutation that will use ``connection_factory`` to reopen
|
Creates a new permutation that will use ``connection_factory`` to reopen
|
||||||
the base table when this permutation is unpickled in a worker process.
|
the base table when this permutation is unpickled in a worker process.
|
||||||
|
|
||||||
The factory is a callable that takes a single argument, the base table
|
The factory is a callable that takes a single argument — the base table
|
||||||
name, and returns a LanceDB table. It must be picklable; the worker
|
name — and returns a [LanceTable]. It must be picklable; the worker
|
||||||
will pickle it via standard ``pickle`` and call it to recover the base
|
will pickle it via standard ``pickle`` and call it to recover the base
|
||||||
table. Picklable callables in practice means top-level (module-level)
|
table. Picklable callables in practice means top-level (module-level)
|
||||||
functions, ``functools.partial`` of such functions, or instances of
|
functions, ``functools.partial`` of such functions, or instances of
|
||||||
picklable classes implementing ``__call__``. Lambdas and closures over
|
picklable classes implementing ``__call__``. Lambdas and closures over
|
||||||
local variables don't pickle with the default protocol.
|
local variables don't pickle with the default protocol.
|
||||||
|
|
||||||
A factory is optional for normal local and remote LanceDB connections:
|
Setting a factory is necessary when the URI alone is not enough to
|
||||||
if not set, ``__getstate__`` captures the table's own picklable reopen
|
re-open the connection — most importantly for LanceDB Cloud (``db://``)
|
||||||
state. Use a factory when that default state is not enough, for example
|
connections, where ``api_key`` and ``region`` aren't recoverable from
|
||||||
when credentials should be loaded from the worker environment instead
|
the connection object after construction.
|
||||||
of being embedded in the pickle.
|
|
||||||
|
For local file or cloud-storage paths the factory is optional: if not
|
||||||
|
set, ``__getstate__`` falls back to capturing
|
||||||
|
``(uri, storage_options, namespace_path)`` and re-opening via
|
||||||
|
``lancedb.connect(uri, storage_options=...)``.
|
||||||
|
|
||||||
Examples
|
Examples
|
||||||
--------
|
--------
|
||||||
@@ -549,7 +508,7 @@ class Permutation:
|
|||||||
return new
|
return new
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def identity(cls, table: Table) -> "Permutation":
|
def identity(cls, table: LanceTable) -> "Permutation":
|
||||||
"""
|
"""
|
||||||
Creates an identity permutation for the given table.
|
Creates an identity permutation for the given table.
|
||||||
"""
|
"""
|
||||||
@@ -558,8 +517,8 @@ class Permutation:
|
|||||||
@classmethod
|
@classmethod
|
||||||
def from_tables(
|
def from_tables(
|
||||||
cls,
|
cls,
|
||||||
base_table: Table,
|
base_table: LanceTable,
|
||||||
permutation_table: Optional[Table] = None,
|
permutation_table: Optional[LanceTable] = None,
|
||||||
split: Optional[Union[str, int]] = None,
|
split: Optional[Union[str, int]] = None,
|
||||||
) -> "Permutation":
|
) -> "Permutation":
|
||||||
"""
|
"""
|
||||||
@@ -635,10 +594,11 @@ class Permutation:
|
|||||||
|
|
||||||
The base table is captured either via a user-supplied
|
The base table is captured either via a user-supplied
|
||||||
``connection_factory`` (see [with_connection_factory]) or, as a
|
``connection_factory`` (see [with_connection_factory]) or, as a
|
||||||
fallback, by the table's own picklable reopen state. The permutation
|
fallback, by introspecting ``(uri, storage_options, namespace_path)``
|
||||||
table is captured as a pyarrow Table (which pickles via Arrow IPC
|
on the connection. The permutation table — always an in-memory
|
||||||
natively). The reader is dropped from the wire format and rebuilt
|
LanceDB table — is captured as a pyarrow Table (which pickles via
|
||||||
lazily on first use.
|
Arrow IPC natively). The reader is dropped from the wire format;
|
||||||
|
``__setstate__`` rebuilds it from the restored tables.
|
||||||
"""
|
"""
|
||||||
permutation_data: Optional[pa.Table] = None
|
permutation_data: Optional[pa.Table] = None
|
||||||
if self.permutation_table is not None:
|
if self.permutation_table is not None:
|
||||||
@@ -662,9 +622,39 @@ class Permutation:
|
|||||||
# namespace from the existing connection.
|
# namespace from the existing connection.
|
||||||
return common
|
return common
|
||||||
|
|
||||||
|
# URI-introspection fallback: only viable for native (OSS) connections
|
||||||
|
# where (uri, storage_options) is enough to reopen. Remote / cloud
|
||||||
|
# connections don't expose recoverable api_key / region — those users
|
||||||
|
# must call with_connection_factory().
|
||||||
|
try:
|
||||||
|
base_uri = self.base_table._conn.uri
|
||||||
|
storage_options = self.base_table._conn.storage_options
|
||||||
|
except AttributeError as e:
|
||||||
|
raise ValueError(
|
||||||
|
"Cannot pickle this Permutation: the base table's connection "
|
||||||
|
"does not expose a uri/storage_options, which usually means it "
|
||||||
|
"is a remote (LanceDB Cloud) connection. Call "
|
||||||
|
"Permutation.with_connection_factory(...) first to provide a "
|
||||||
|
"picklable callable that re-opens the base table from a worker "
|
||||||
|
"process."
|
||||||
|
) from e
|
||||||
|
|
||||||
|
if base_uri.startswith("memory://"):
|
||||||
|
# In-memory base tables don't exist in any worker process by
|
||||||
|
# default, so dump the entire base table into the pickle. This
|
||||||
|
# can be expensive for large datasets — users with large
|
||||||
|
# in-memory base tables should either persist them or set a
|
||||||
|
# connection_factory.
|
||||||
|
return {
|
||||||
|
**common,
|
||||||
|
"base_table_data": self.base_table.to_arrow(),
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
**common,
|
**common,
|
||||||
"base_table_state": _table_to_pickle_state(self.base_table),
|
"base_table_uri": base_uri,
|
||||||
|
"base_table_namespace": self.base_table._namespace_path,
|
||||||
|
"base_table_storage_options": storage_options,
|
||||||
}
|
}
|
||||||
|
|
||||||
def __setstate__(self, state: dict[str, Any]) -> None:
|
def __setstate__(self, state: dict[str, Any]) -> None:
|
||||||
@@ -673,8 +663,6 @@ class Permutation:
|
|||||||
connection_factory = state["connection_factory"]
|
connection_factory = state["connection_factory"]
|
||||||
if connection_factory is not None:
|
if connection_factory is not None:
|
||||||
base_table = connection_factory(state["base_table_name"])
|
base_table = connection_factory(state["base_table_name"])
|
||||||
elif "base_table_state" in state:
|
|
||||||
base_table = _table_from_pickle_state(state["base_table_state"])
|
|
||||||
elif "base_table_data" in state:
|
elif "base_table_data" in state:
|
||||||
# In-memory base table inlined into the pickle; rebuild the same
|
# In-memory base table inlined into the pickle; rebuild the same
|
||||||
# way we rebuild the in-memory permutation table.
|
# way we rebuild the in-memory permutation table.
|
||||||
@@ -692,7 +680,7 @@ class Permutation:
|
|||||||
namespace_path=state["base_table_namespace"] or None,
|
namespace_path=state["base_table_namespace"] or None,
|
||||||
)
|
)
|
||||||
|
|
||||||
permutation_table: Optional[Table] = None
|
permutation_table: Optional[LanceTable] = None
|
||||||
if state["permutation_data"] is not None:
|
if state["permutation_data"] is not None:
|
||||||
mem_db = connect("memory://")
|
mem_db = connect("memory://")
|
||||||
permutation_table = mem_db.create_table(
|
permutation_table = mem_db.create_table(
|
||||||
@@ -708,28 +696,10 @@ class Permutation:
|
|||||||
self.offset = state["offset"]
|
self.offset = state["offset"]
|
||||||
self.limit = state["limit"]
|
self.limit = state["limit"]
|
||||||
self.connection_factory = connection_factory
|
self.connection_factory = connection_factory
|
||||||
self.reader = None
|
|
||||||
self._pid = None
|
|
||||||
|
|
||||||
def _ensure_open(self) -> None:
|
|
||||||
pid = os.getpid()
|
|
||||||
if self.reader is not None and getattr(self, "_pid", None) == pid:
|
|
||||||
return
|
|
||||||
# The reader owns Rust-side table handles. Rebuild it after unpickle or
|
|
||||||
# fork even though the Python table wrappers reopen themselves.
|
|
||||||
if hasattr(self.base_table, "_ensure_open"):
|
|
||||||
self.base_table._ensure_open()
|
|
||||||
if self.permutation_table is not None and hasattr(
|
|
||||||
self.permutation_table, "_ensure_open"
|
|
||||||
):
|
|
||||||
self.permutation_table._ensure_open()
|
|
||||||
self.reader = LOOP.run(self._build_reader())
|
self.reader = LOOP.run(self._build_reader())
|
||||||
self._pid = pid
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def schema(self) -> pa.Schema:
|
def schema(self) -> pa.Schema:
|
||||||
self._ensure_open()
|
|
||||||
|
|
||||||
async def do_output_schema():
|
async def do_output_schema():
|
||||||
return await self.reader.output_schema(self.selection)
|
return await self.reader.output_schema(self.selection)
|
||||||
|
|
||||||
@@ -747,7 +717,6 @@ class Permutation:
|
|||||||
"""
|
"""
|
||||||
The number of rows in the permutation
|
The number of rows in the permutation
|
||||||
"""
|
"""
|
||||||
self._ensure_open()
|
|
||||||
return self.reader.count_rows()
|
return self.reader.count_rows()
|
||||||
|
|
||||||
@property
|
@property
|
||||||
@@ -906,7 +875,6 @@ class Permutation:
|
|||||||
If skip_last_batch is True, the last batch will be skipped if it is not a
|
If skip_last_batch is True, the last batch will be skipped if it is not a
|
||||||
multiple of batch_size.
|
multiple of batch_size.
|
||||||
"""
|
"""
|
||||||
self._ensure_open()
|
|
||||||
|
|
||||||
async def get_iter():
|
async def get_iter():
|
||||||
return await self.reader.read(self.selection, batch_size=batch_size)
|
return await self.reader.read(self.selection, batch_size=batch_size)
|
||||||
@@ -1000,33 +968,22 @@ class Permutation:
|
|||||||
new.transform_fn = transform
|
new.transform_fn = transform
|
||||||
return new
|
return new
|
||||||
|
|
||||||
def take_offsets(self, offsets: list[int]) -> Any:
|
|
||||||
"""
|
|
||||||
Take rows from the permutation by offset
|
|
||||||
|
|
||||||
The returned value is passed through the permutation's current transform,
|
|
||||||
so `with_format` and `with_transform` affect this method in the same way
|
|
||||||
they affect iteration.
|
|
||||||
"""
|
|
||||||
self._ensure_open()
|
|
||||||
|
|
||||||
async def do_take_offsets():
|
|
||||||
return await self.reader.take_offsets(offsets, selection=self.selection)
|
|
||||||
|
|
||||||
batch = LOOP.run(do_take_offsets())
|
|
||||||
return self.transform_fn(batch)
|
|
||||||
|
|
||||||
def __getitem__(self, index: int) -> Any:
|
def __getitem__(self, index: int) -> Any:
|
||||||
"""
|
"""
|
||||||
Returns a single row from the permutation by offset
|
Returns a single row from the permutation by offset
|
||||||
"""
|
"""
|
||||||
return self.take_offsets([index])
|
return self.__getitems__([index])
|
||||||
|
|
||||||
def __getitems__(self, indices: list[int]) -> Any:
|
def __getitems__(self, indices: list[int]) -> Any:
|
||||||
"""
|
"""
|
||||||
Returns rows from the permutation by offset
|
Returns rows from the permutation by offset
|
||||||
"""
|
"""
|
||||||
return self.take_offsets(indices)
|
|
||||||
|
async def do_getitems():
|
||||||
|
return await self.reader.take_offsets(indices, selection=self.selection)
|
||||||
|
|
||||||
|
batch = LOOP.run(do_getitems())
|
||||||
|
return self.transform_fn(batch)
|
||||||
|
|
||||||
@deprecated(details="Use with_skip instead")
|
@deprecated(details="Use with_skip instead")
|
||||||
def skip(self, skip: int) -> "Permutation":
|
def skip(self, skip: int) -> "Permutation":
|
||||||
@@ -1044,11 +1001,9 @@ class Permutation:
|
|||||||
"""
|
"""
|
||||||
Skip the first `skip` rows of the permutation
|
Skip the first `skip` rows of the permutation
|
||||||
"""
|
"""
|
||||||
self._ensure_open()
|
|
||||||
new = copy.copy(self)
|
new = copy.copy(self)
|
||||||
new.offset = skip
|
new.offset = skip
|
||||||
new.reader = LOOP.run(new._build_reader())
|
new.reader = LOOP.run(new._build_reader())
|
||||||
new._pid = os.getpid()
|
|
||||||
return new
|
return new
|
||||||
|
|
||||||
@deprecated(details="Use with_take instead")
|
@deprecated(details="Use with_take instead")
|
||||||
@@ -1067,11 +1022,9 @@ class Permutation:
|
|||||||
"""
|
"""
|
||||||
Limit the permutation to `limit` rows (following any `skip`)
|
Limit the permutation to `limit` rows (following any `skip`)
|
||||||
"""
|
"""
|
||||||
self._ensure_open()
|
|
||||||
new = copy.copy(self)
|
new = copy.copy(self)
|
||||||
new.limit = limit
|
new.limit = limit
|
||||||
new.reader = LOOP.run(new._build_reader())
|
new.reader = LOOP.run(new._build_reader())
|
||||||
new._pid = os.getpid()
|
|
||||||
return new
|
return new
|
||||||
|
|
||||||
@deprecated(details="Use with_repeat instead")
|
@deprecated(details="Use with_repeat instead")
|
||||||
|
|||||||
@@ -3,14 +3,12 @@
|
|||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import asyncio
|
|
||||||
from abc import ABC, abstractmethod
|
from abc import ABC, abstractmethod
|
||||||
from concurrent.futures import ThreadPoolExecutor
|
from concurrent.futures import ThreadPoolExecutor
|
||||||
from datetime import timedelta
|
|
||||||
from enum import Enum
|
from enum import Enum
|
||||||
|
from datetime import timedelta
|
||||||
from typing import (
|
from typing import (
|
||||||
TYPE_CHECKING,
|
TYPE_CHECKING,
|
||||||
Any,
|
|
||||||
Dict,
|
Dict,
|
||||||
List,
|
List,
|
||||||
Literal,
|
Literal,
|
||||||
@@ -19,51 +17,44 @@ from typing import (
|
|||||||
Type,
|
Type,
|
||||||
TypeVar,
|
TypeVar,
|
||||||
Union,
|
Union,
|
||||||
|
Any,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
import asyncio
|
||||||
import deprecation
|
import deprecation
|
||||||
import numpy as np
|
import numpy as np
|
||||||
import pyarrow as pa
|
import pyarrow as pa
|
||||||
import pyarrow.compute as pc
|
import pyarrow.compute as pc
|
||||||
import pydantic
|
import pydantic
|
||||||
from typing_extensions import Annotated
|
|
||||||
|
|
||||||
from lancedb._lancedb import fts_query_to_json
|
|
||||||
from lancedb.background_loop import LOOP
|
|
||||||
from lancedb.pydantic import PYDANTIC_VERSION
|
from lancedb.pydantic import PYDANTIC_VERSION
|
||||||
|
from lancedb.background_loop import LOOP
|
||||||
|
|
||||||
from . import __version__
|
from . import __version__
|
||||||
from .arrow import AsyncRecordBatchReader
|
from .arrow import AsyncRecordBatchReader
|
||||||
from .dependencies import pandas as pd
|
from .dependencies import pandas as pd
|
||||||
from .expr import Expr
|
|
||||||
from .rerankers.base import Reranker
|
from .rerankers.base import Reranker
|
||||||
from .rerankers.rrf import RRFReranker
|
from .rerankers.rrf import RRFReranker
|
||||||
from .rerankers.util import check_reranker_result
|
from .rerankers.util import check_reranker_result
|
||||||
from .util import flatten_columns
|
from .util import flatten_columns
|
||||||
|
from .expr import Expr
|
||||||
BlobMode = Literal["lazy", "bytes", "descriptions"]
|
from lancedb._lancedb import fts_query_to_json
|
||||||
|
from typing_extensions import Annotated
|
||||||
_BLOB_MODE_TO_HANDLING = {
|
|
||||||
"lazy": "blobs_descriptions",
|
|
||||||
"bytes": "all_binary",
|
|
||||||
"descriptions": "blobs_descriptions",
|
|
||||||
}
|
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
import PIL
|
import PIL
|
||||||
import polars as pl
|
import polars as pl
|
||||||
|
|
||||||
|
from ._lancedb import Query as LanceQuery
|
||||||
from ._lancedb import FTSQuery as LanceFTSQuery
|
from ._lancedb import FTSQuery as LanceFTSQuery
|
||||||
from ._lancedb import HybridQuery as LanceHybridQuery
|
from ._lancedb import HybridQuery as LanceHybridQuery
|
||||||
from ._lancedb import PyQueryRequest
|
|
||||||
from ._lancedb import Query as LanceQuery
|
|
||||||
from ._lancedb import TakeQuery as LanceTakeQuery
|
|
||||||
from ._lancedb import VectorQuery as LanceVectorQuery
|
from ._lancedb import VectorQuery as LanceVectorQuery
|
||||||
|
from ._lancedb import TakeQuery as LanceTakeQuery
|
||||||
|
from ._lancedb import PyQueryRequest
|
||||||
from .common import VEC
|
from .common import VEC
|
||||||
from .pydantic import LanceModel
|
from .pydantic import LanceModel
|
||||||
from .table import AsyncTable, Table
|
from .table import Table
|
||||||
|
|
||||||
if sys.version_info >= (3, 11):
|
if sys.version_info >= (3, 11):
|
||||||
from typing import Self
|
from typing import Self
|
||||||
@@ -73,147 +64,6 @@ if TYPE_CHECKING:
|
|||||||
T = TypeVar("T", bound="LanceModel")
|
T = TypeVar("T", bound="LanceModel")
|
||||||
|
|
||||||
|
|
||||||
def _validate_blob_mode(blob_mode: BlobMode) -> None:
|
|
||||||
if blob_mode not in _BLOB_MODE_TO_HANDLING:
|
|
||||||
modes = ", ".join(repr(mode) for mode in _BLOB_MODE_TO_HANDLING)
|
|
||||||
raise ValueError(f"blob_mode must be one of {modes}, got {blob_mode!r}")
|
|
||||||
|
|
||||||
|
|
||||||
def _field_is_blob(field: pa.Field) -> bool:
|
|
||||||
metadata = field.metadata or {}
|
|
||||||
return metadata.get(b"lance-encoding:blob") == b"true" or (
|
|
||||||
metadata.get("lance-encoding:blob") == "true"
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def _schema_has_blob_field(schema: pa.Schema) -> bool:
|
|
||||||
return any(_field_is_blob(field) for field in schema)
|
|
||||||
|
|
||||||
|
|
||||||
def _blob_mode_requires_native_pandas(blob_mode: BlobMode, schema: pa.Schema) -> bool:
|
|
||||||
return blob_mode in ("lazy", "bytes") and _schema_has_blob_field(schema)
|
|
||||||
|
|
||||||
|
|
||||||
def _unsupported_blob_pandas_error(reason: str) -> RuntimeError:
|
|
||||||
return RuntimeError(
|
|
||||||
"blob_mode='lazy' and blob_mode='bytes' require Lance native pandas "
|
|
||||||
f"conversion for queries that return blob columns, but {reason}. "
|
|
||||||
"Use blob_mode='descriptions' or remove blob columns from the projection."
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def _query_is_plain_scan(query: Query) -> bool:
|
|
||||||
return (
|
|
||||||
query.vector is None
|
|
||||||
and query.full_text_query is None
|
|
||||||
and not query.postfilter
|
|
||||||
and not query.order_by
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def _filter_to_sql(filter: Optional[Union[str, Expr]]) -> Optional[str]:
|
|
||||||
if filter is None:
|
|
||||||
return None
|
|
||||||
if isinstance(filter, Expr):
|
|
||||||
return filter.to_sql()
|
|
||||||
return filter
|
|
||||||
|
|
||||||
|
|
||||||
def _projection_to_scanner_kwargs(
|
|
||||||
columns: Optional[
|
|
||||||
Union[
|
|
||||||
List[str], List[Tuple[str, Union[str, Expr]]], Dict[str, Union[str, Expr]]
|
|
||||||
]
|
|
||||||
],
|
|
||||||
) -> Dict[str, Any]:
|
|
||||||
if columns is None:
|
|
||||||
return {}
|
|
||||||
if isinstance(columns, list):
|
|
||||||
if all(isinstance(column, str) for column in columns):
|
|
||||||
return {"columns": columns}
|
|
||||||
if all(isinstance(column, tuple) and len(column) == 2 for column in columns):
|
|
||||||
return {
|
|
||||||
"columns": {
|
|
||||||
name: expr.to_sql() if isinstance(expr, Expr) else expr
|
|
||||||
for name, expr in columns
|
|
||||||
}
|
|
||||||
}
|
|
||||||
# Let Lance raise the detailed projection validation error.
|
|
||||||
return {"columns": columns}
|
|
||||||
|
|
||||||
projection = {}
|
|
||||||
for name, expr in columns.items():
|
|
||||||
if isinstance(expr, Expr):
|
|
||||||
expr = expr.to_sql()
|
|
||||||
projection[name] = expr
|
|
||||||
return {"columns": projection}
|
|
||||||
|
|
||||||
|
|
||||||
def _scanner_kwargs_for_query(query: Query, blob_mode: BlobMode) -> Dict[str, Any]:
|
|
||||||
kwargs = {
|
|
||||||
**_projection_to_scanner_kwargs(query.columns),
|
|
||||||
"filter": _filter_to_sql(query.filter),
|
|
||||||
"limit": query.limit,
|
|
||||||
"offset": query.offset,
|
|
||||||
"with_row_id": query.with_row_id,
|
|
||||||
"fast_search": query.fast_search,
|
|
||||||
"blob_handling": _BLOB_MODE_TO_HANDLING[blob_mode],
|
|
||||||
}
|
|
||||||
return {key: value for key, value in kwargs.items() if value is not None}
|
|
||||||
|
|
||||||
|
|
||||||
def _ensure_lazy_blob_frame(
|
|
||||||
df: "pd.DataFrame", schema: pa.Schema, blob_mode: BlobMode
|
|
||||||
) -> "pd.DataFrame":
|
|
||||||
if blob_mode != "lazy" or not _schema_has_blob_field(schema) or len(df) == 0:
|
|
||||||
return df
|
|
||||||
|
|
||||||
for field in schema:
|
|
||||||
if not _field_is_blob(field) or field.name not in df.columns:
|
|
||||||
continue
|
|
||||||
value = df[field.name].iloc[0]
|
|
||||||
if value is not None and not hasattr(value, "readall"):
|
|
||||||
raise _unsupported_blob_pandas_error(
|
|
||||||
"the Lance scanner did not return lazy blob files"
|
|
||||||
)
|
|
||||||
return df
|
|
||||||
|
|
||||||
|
|
||||||
def _scanner_to_pandas(scanner: Any, blob_mode: BlobMode, **kwargs) -> "pd.DataFrame":
|
|
||||||
schema = getattr(scanner, "projected_schema", None)
|
|
||||||
if schema is None:
|
|
||||||
schema = getattr(scanner, "schema", None)
|
|
||||||
if schema is None:
|
|
||||||
schema = getattr(scanner, "dataset_schema", None)
|
|
||||||
if callable(schema):
|
|
||||||
schema = schema()
|
|
||||||
if hasattr(scanner, "to_pandas"):
|
|
||||||
try:
|
|
||||||
df = scanner.to_pandas(blob_mode=blob_mode, **kwargs)
|
|
||||||
except TypeError as err:
|
|
||||||
message = str(err)
|
|
||||||
if "blob_mode" not in message and "unexpected keyword" not in message:
|
|
||||||
raise
|
|
||||||
df = scanner.to_pandas(**kwargs)
|
|
||||||
if schema is not None:
|
|
||||||
return _ensure_lazy_blob_frame(df, schema, blob_mode)
|
|
||||||
return df
|
|
||||||
|
|
||||||
if hasattr(scanner, "to_pyarrow"):
|
|
||||||
reader = scanner.to_pyarrow()
|
|
||||||
tbl = reader.read_all()
|
|
||||||
elif hasattr(scanner, "to_table"):
|
|
||||||
tbl = scanner.to_table()
|
|
||||||
else:
|
|
||||||
reader = scanner.to_reader()
|
|
||||||
tbl = reader.read_all()
|
|
||||||
if blob_mode == "lazy" and _schema_has_blob_field(tbl.schema):
|
|
||||||
raise _unsupported_blob_pandas_error(
|
|
||||||
"the Lance scanner does not expose to_pandas"
|
|
||||||
)
|
|
||||||
return tbl.to_pandas(**kwargs)
|
|
||||||
|
|
||||||
|
|
||||||
# Pydantic validation function for vector queries
|
# Pydantic validation function for vector queries
|
||||||
def ensure_vector_query(
|
def ensure_vector_query(
|
||||||
val: Any,
|
val: Any,
|
||||||
@@ -242,12 +92,6 @@ def ensure_vector_query(
|
|||||||
return val
|
return val
|
||||||
|
|
||||||
|
|
||||||
class ColumnOrdering(pydantic.BaseModel):
|
|
||||||
column_name: str
|
|
||||||
ascending: bool = True
|
|
||||||
nulls_first: bool = False
|
|
||||||
|
|
||||||
|
|
||||||
class FullTextQueryType(str, Enum):
|
class FullTextQueryType(str, Enum):
|
||||||
MATCH = "match"
|
MATCH = "match"
|
||||||
MATCH_PHRASE = "match_phrase"
|
MATCH_PHRASE = "match_phrase"
|
||||||
@@ -660,8 +504,6 @@ class Query(pydantic.BaseModel):
|
|||||||
# Bypass the vector index and use a brute force search
|
# Bypass the vector index and use a brute force search
|
||||||
bypass_vector_index: Optional[bool] = None
|
bypass_vector_index: Optional[bool] = None
|
||||||
|
|
||||||
order_by: Optional[List[ColumnOrdering]] = None
|
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def from_inner(cls, req: PyQueryRequest) -> Self:
|
def from_inner(cls, req: PyQueryRequest) -> Self:
|
||||||
query = cls()
|
query = cls()
|
||||||
@@ -682,8 +524,6 @@ class Query(pydantic.BaseModel):
|
|||||||
query.refine_factor = req.refine_factor
|
query.refine_factor = req.refine_factor
|
||||||
query.bypass_vector_index = req.bypass_vector_index
|
query.bypass_vector_index = req.bypass_vector_index
|
||||||
query.postfilter = req.postfilter
|
query.postfilter = req.postfilter
|
||||||
if req.order_by is not None:
|
|
||||||
query.order_by = [ColumnOrdering(**o) for o in req.order_by]
|
|
||||||
if req.full_text_search is not None:
|
if req.full_text_search is not None:
|
||||||
query.full_text_query = FullTextSearchQuery(
|
query.full_text_query = FullTextSearchQuery(
|
||||||
columns=None,
|
columns=None,
|
||||||
@@ -732,22 +572,9 @@ class LanceQueryBuilder(ABC):
|
|||||||
If "auto", the query type is inferred based on the query.
|
If "auto", the query type is inferred based on the query.
|
||||||
vector_column_name: str
|
vector_column_name: str
|
||||||
The name of the vector column to use for vector search.
|
The name of the vector column to use for vector search.
|
||||||
ordering_field_name: Optional[str]
|
|
||||||
.. deprecated:: 0.27.0
|
|
||||||
Use ``order_by()`` method instead.
|
|
||||||
fts_columns: Optional[Union[str, List[str]]]
|
|
||||||
The columns to search in for full text search.
|
|
||||||
fast_search: bool
|
fast_search: bool
|
||||||
Skip flat search of unindexed data.
|
Skip flat search of unindexed data.
|
||||||
"""
|
"""
|
||||||
if ordering_field_name is not None:
|
|
||||||
import warnings
|
|
||||||
|
|
||||||
warnings.warn(
|
|
||||||
"ordering_field_name is deprecated, use .order_by() method instead.",
|
|
||||||
DeprecationWarning,
|
|
||||||
stacklevel=2,
|
|
||||||
)
|
|
||||||
# Check hybrid search first as it supports empty query pattern
|
# Check hybrid search first as it supports empty query pattern
|
||||||
if query_type == "hybrid":
|
if query_type == "hybrid":
|
||||||
# hybrid fts and vector query
|
# hybrid fts and vector query
|
||||||
@@ -844,7 +671,6 @@ class LanceQueryBuilder(ABC):
|
|||||||
self._text = None
|
self._text = None
|
||||||
self._ef = None
|
self._ef = None
|
||||||
self._bypass_vector_index = None
|
self._bypass_vector_index = None
|
||||||
self._order_by = None
|
|
||||||
|
|
||||||
@deprecation.deprecated(
|
@deprecation.deprecated(
|
||||||
deprecated_in="0.3.1",
|
deprecated_in="0.3.1",
|
||||||
@@ -867,9 +693,7 @@ class LanceQueryBuilder(ABC):
|
|||||||
self,
|
self,
|
||||||
flatten: Optional[Union[int, bool]] = None,
|
flatten: Optional[Union[int, bool]] = None,
|
||||||
*,
|
*,
|
||||||
blob_mode: BlobMode = "lazy",
|
|
||||||
timeout: Optional[timedelta] = None,
|
timeout: Optional[timedelta] = None,
|
||||||
**kwargs,
|
|
||||||
) -> "pd.DataFrame":
|
) -> "pd.DataFrame":
|
||||||
"""
|
"""
|
||||||
Execute the query and return the results as a pandas DataFrame.
|
Execute the query and return the results as a pandas DataFrame.
|
||||||
@@ -887,32 +711,9 @@ class LanceQueryBuilder(ABC):
|
|||||||
timeout: Optional[timedelta]
|
timeout: Optional[timedelta]
|
||||||
The maximum time to wait for the query to complete.
|
The maximum time to wait for the query to complete.
|
||||||
If None, wait indefinitely.
|
If None, wait indefinitely.
|
||||||
blob_mode: str, default "lazy"
|
|
||||||
Controls how blob columns are returned for plain scan queries.
|
|
||||||
Vector, FTS, hybrid, and other non-native query shapes keep the
|
|
||||||
existing Arrow conversion path and only support blob descriptions.
|
|
||||||
**kwargs
|
|
||||||
Forwarded to pyarrow.Table.to_pandas after query execution and
|
|
||||||
optional flattening.
|
|
||||||
"""
|
"""
|
||||||
_validate_blob_mode(blob_mode)
|
|
||||||
native_error = None
|
|
||||||
tbl = flatten_columns(self.to_arrow(timeout=timeout), flatten)
|
tbl = flatten_columns(self.to_arrow(timeout=timeout), flatten)
|
||||||
if _blob_mode_requires_native_pandas(blob_mode, tbl.schema):
|
return tbl.to_pandas()
|
||||||
if flatten is None and timeout is None:
|
|
||||||
try:
|
|
||||||
df = self._plain_scan_to_pandas(blob_mode, **kwargs)
|
|
||||||
if df is not None:
|
|
||||||
return df
|
|
||||||
except Exception as err:
|
|
||||||
native_error = err
|
|
||||||
reason = (
|
|
||||||
"this query shape cannot use Lance native pandas conversion"
|
|
||||||
if native_error is None
|
|
||||||
else str(native_error)
|
|
||||||
)
|
|
||||||
raise _unsupported_blob_pandas_error(reason) from native_error
|
|
||||||
return tbl.to_pandas(**kwargs)
|
|
||||||
|
|
||||||
@abstractmethod
|
@abstractmethod
|
||||||
def to_arrow(self, *, timeout: Optional[timedelta] = None) -> pa.Table:
|
def to_arrow(self, *, timeout: Optional[timedelta] = None) -> pa.Table:
|
||||||
@@ -1146,24 +947,6 @@ class LanceQueryBuilder(ABC):
|
|||||||
""" # noqa: E501
|
""" # noqa: E501
|
||||||
return self._table._explain_plan(self.to_query_object(), verbose=verbose)
|
return self._table._explain_plan(self.to_query_object(), verbose=verbose)
|
||||||
|
|
||||||
def order_by(self, ordering: Optional[List[ColumnOrdering]]) -> Self:
|
|
||||||
"""
|
|
||||||
Set the ordering for the results.
|
|
||||||
|
|
||||||
Parameters
|
|
||||||
----------
|
|
||||||
ordering: Optional[List[ColumnOrdering]]
|
|
||||||
The ordering to use for the results. If None, then the default ordering
|
|
||||||
will be used.
|
|
||||||
|
|
||||||
Returns
|
|
||||||
-------
|
|
||||||
LanceQueryBuilder
|
|
||||||
The LanceQueryBuilder object.
|
|
||||||
"""
|
|
||||||
self._order_by = ordering
|
|
||||||
return self
|
|
||||||
|
|
||||||
def analyze_plan(self) -> str:
|
def analyze_plan(self) -> str:
|
||||||
"""
|
"""
|
||||||
Run the query and return its execution plan with runtime metrics.
|
Run the query and return its execution plan with runtime metrics.
|
||||||
@@ -1256,19 +1039,6 @@ class LanceQueryBuilder(ABC):
|
|||||||
"""
|
"""
|
||||||
raise NotImplementedError
|
raise NotImplementedError
|
||||||
|
|
||||||
def _plain_scan_to_pandas(
|
|
||||||
self,
|
|
||||||
blob_mode: BlobMode,
|
|
||||||
**kwargs,
|
|
||||||
) -> Optional["pd.DataFrame"]:
|
|
||||||
query = self.to_query_object()
|
|
||||||
if not _query_is_plain_scan(query):
|
|
||||||
return None
|
|
||||||
|
|
||||||
dataset = self._table.to_lance()
|
|
||||||
scanner = dataset.scanner(**_scanner_kwargs_for_query(query, blob_mode))
|
|
||||||
return _scanner_to_pandas(scanner, blob_mode, **kwargs)
|
|
||||||
|
|
||||||
@abstractmethod
|
@abstractmethod
|
||||||
def to_query_object(self) -> Query:
|
def to_query_object(self) -> Query:
|
||||||
"""Return a serializable representation of the query
|
"""Return a serializable representation of the query
|
||||||
@@ -1544,7 +1314,6 @@ class LanceVectorQueryBuilder(LanceQueryBuilder):
|
|||||||
fast_search=self._fast_search,
|
fast_search=self._fast_search,
|
||||||
ef=self._ef,
|
ef=self._ef,
|
||||||
bypass_vector_index=self._bypass_vector_index,
|
bypass_vector_index=self._bypass_vector_index,
|
||||||
order_by=self._order_by,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
def to_batches(
|
def to_batches(
|
||||||
@@ -1696,9 +1465,7 @@ class LanceFtsQueryBuilder(LanceQueryBuilder):
|
|||||||
super().__init__(table)
|
super().__init__(table)
|
||||||
self._query = query
|
self._query = query
|
||||||
self._phrase_query = False
|
self._phrase_query = False
|
||||||
# Deprecated compatibility parameter. Native FTS ordering is now
|
self.ordering_field_name = ordering_field_name
|
||||||
# configured through order_by(); LanceQueryBuilder.create emits the warning.
|
|
||||||
_ = ordering_field_name
|
|
||||||
self._reranker = None
|
self._reranker = None
|
||||||
self._fast_search = fast_search
|
self._fast_search = fast_search
|
||||||
if isinstance(fts_columns, str):
|
if isinstance(fts_columns, str):
|
||||||
@@ -1747,7 +1514,6 @@ class LanceFtsQueryBuilder(LanceQueryBuilder):
|
|||||||
),
|
),
|
||||||
offset=self._offset,
|
offset=self._offset,
|
||||||
fast_search=self._fast_search,
|
fast_search=self._fast_search,
|
||||||
order_by=self._order_by,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
def output_schema(self) -> pa.Schema:
|
def output_schema(self) -> pa.Schema:
|
||||||
@@ -1813,7 +1579,6 @@ class LanceEmptyQueryBuilder(LanceQueryBuilder):
|
|||||||
limit=self._limit,
|
limit=self._limit,
|
||||||
with_row_id=self._with_row_id,
|
with_row_id=self._with_row_id,
|
||||||
offset=self._offset,
|
offset=self._offset,
|
||||||
order_by=self._order_by,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
def output_schema(self) -> pa.Schema:
|
def output_schema(self) -> pa.Schema:
|
||||||
@@ -2390,11 +2155,7 @@ class AsyncQueryBase(object):
|
|||||||
Base class for all async queries (take, scan, vector, fts, hybrid)
|
Base class for all async queries (take, scan, vector, fts, hybrid)
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(
|
def __init__(self, inner: Union[LanceQuery, LanceVectorQuery, LanceTakeQuery]):
|
||||||
self,
|
|
||||||
inner: Union[LanceQuery, LanceVectorQuery, LanceTakeQuery],
|
|
||||||
table: Optional["AsyncTable"] = None,
|
|
||||||
):
|
|
||||||
"""
|
"""
|
||||||
Construct an AsyncQueryBase
|
Construct an AsyncQueryBase
|
||||||
|
|
||||||
@@ -2402,7 +2163,6 @@ class AsyncQueryBase(object):
|
|||||||
[AsyncTable.query][lancedb.table.AsyncTable.query] method to create a query.
|
[AsyncTable.query][lancedb.table.AsyncTable.query] method to create a query.
|
||||||
"""
|
"""
|
||||||
self._inner = inner
|
self._inner = inner
|
||||||
self._table = table
|
|
||||||
|
|
||||||
def to_query_object(self) -> Query:
|
def to_query_object(self) -> Query:
|
||||||
"""
|
"""
|
||||||
@@ -2545,9 +2305,6 @@ class AsyncQueryBase(object):
|
|||||||
self,
|
self,
|
||||||
flatten: Optional[Union[int, bool]] = None,
|
flatten: Optional[Union[int, bool]] = None,
|
||||||
timeout: Optional[timedelta] = None,
|
timeout: Optional[timedelta] = None,
|
||||||
*,
|
|
||||||
blob_mode: BlobMode = "lazy",
|
|
||||||
**kwargs,
|
|
||||||
) -> "pd.DataFrame":
|
) -> "pd.DataFrame":
|
||||||
"""
|
"""
|
||||||
Execute the query and collect the results into a pandas DataFrame.
|
Execute the query and collect the results into a pandas DataFrame.
|
||||||
@@ -2580,48 +2337,10 @@ class AsyncQueryBase(object):
|
|||||||
The maximum time to wait for the query to complete.
|
The maximum time to wait for the query to complete.
|
||||||
If not specified, no timeout is applied. If the query does not
|
If not specified, no timeout is applied. If the query does not
|
||||||
complete within the specified time, an error will be raised.
|
complete within the specified time, an error will be raised.
|
||||||
blob_mode: str, default "lazy"
|
|
||||||
Controls how blob columns are returned for plain scan queries.
|
|
||||||
Vector, FTS, hybrid, and other non-native query shapes keep the
|
|
||||||
existing Arrow conversion path and only support blob descriptions.
|
|
||||||
**kwargs
|
|
||||||
Forwarded to pyarrow.Table.to_pandas after query execution and
|
|
||||||
optional flattening.
|
|
||||||
"""
|
"""
|
||||||
_validate_blob_mode(blob_mode)
|
return (
|
||||||
native_error = None
|
flatten_columns(await self.to_arrow(timeout=timeout), flatten)
|
||||||
tbl = flatten_columns(await self.to_arrow(timeout=timeout), flatten)
|
).to_pandas()
|
||||||
if _blob_mode_requires_native_pandas(blob_mode, tbl.schema):
|
|
||||||
if flatten is None and timeout is None:
|
|
||||||
try:
|
|
||||||
df = await self._plain_scan_to_pandas(blob_mode, **kwargs)
|
|
||||||
if df is not None:
|
|
||||||
return df
|
|
||||||
except Exception as err:
|
|
||||||
native_error = err
|
|
||||||
reason = (
|
|
||||||
"this query shape cannot use Lance native pandas conversion"
|
|
||||||
if native_error is None
|
|
||||||
else str(native_error)
|
|
||||||
)
|
|
||||||
raise _unsupported_blob_pandas_error(reason) from native_error
|
|
||||||
return tbl.to_pandas(**kwargs)
|
|
||||||
|
|
||||||
async def _plain_scan_to_pandas(
|
|
||||||
self,
|
|
||||||
blob_mode: BlobMode,
|
|
||||||
**kwargs,
|
|
||||||
) -> Optional["pd.DataFrame"]:
|
|
||||||
if self._table is None:
|
|
||||||
return None
|
|
||||||
|
|
||||||
query = self.to_query_object()
|
|
||||||
if not _query_is_plain_scan(query):
|
|
||||||
return None
|
|
||||||
|
|
||||||
dataset = await self._table._to_lance()
|
|
||||||
scanner = dataset.scanner(**_scanner_kwargs_for_query(query, blob_mode))
|
|
||||||
return _scanner_to_pandas(scanner, blob_mode, **kwargs)
|
|
||||||
|
|
||||||
async def to_polars(
|
async def to_polars(
|
||||||
self,
|
self,
|
||||||
@@ -2728,18 +2447,14 @@ class AsyncStandardQuery(AsyncQueryBase):
|
|||||||
Base class for "standard" async queries (all but take currently)
|
Base class for "standard" async queries (all but take currently)
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(
|
def __init__(self, inner: Union[LanceQuery, LanceVectorQuery]):
|
||||||
self,
|
|
||||||
inner: Union[LanceQuery, LanceVectorQuery],
|
|
||||||
table: Optional["AsyncTable"] = None,
|
|
||||||
):
|
|
||||||
"""
|
"""
|
||||||
Construct an AsyncStandardQuery
|
Construct an AsyncStandardQuery
|
||||||
|
|
||||||
This method is not intended to be called directly. Instead, use the
|
This method is not intended to be called directly. Instead, use the
|
||||||
[AsyncTable.query][lancedb.table.AsyncTable.query] method to create a query.
|
[AsyncTable.query][lancedb.table.AsyncTable.query] method to create a query.
|
||||||
"""
|
"""
|
||||||
super().__init__(inner, table)
|
super().__init__(inner)
|
||||||
|
|
||||||
def where(self, predicate: Union[str, Expr]) -> Self:
|
def where(self, predicate: Union[str, Expr]) -> Self:
|
||||||
"""
|
"""
|
||||||
@@ -2787,27 +2502,6 @@ class AsyncStandardQuery(AsyncQueryBase):
|
|||||||
self._inner.offset(offset)
|
self._inner.offset(offset)
|
||||||
return self
|
return self
|
||||||
|
|
||||||
def order_by(self, ordering: Optional[List[ColumnOrdering]]) -> Self:
|
|
||||||
"""
|
|
||||||
Set the ordering for the results.
|
|
||||||
|
|
||||||
Parameters
|
|
||||||
----------
|
|
||||||
ordering: Optional[List[ColumnOrdering]]
|
|
||||||
The ordering to use for the results. If None, then the default ordering
|
|
||||||
will be used.
|
|
||||||
"""
|
|
||||||
if ordering is None:
|
|
||||||
self._inner.order_by(None)
|
|
||||||
else:
|
|
||||||
self._inner.order_by(
|
|
||||||
[
|
|
||||||
o.model_dump() if hasattr(o, "model_dump") else o.dict()
|
|
||||||
for o in ordering
|
|
||||||
]
|
|
||||||
)
|
|
||||||
return self
|
|
||||||
|
|
||||||
def fast_search(self) -> Self:
|
def fast_search(self) -> Self:
|
||||||
"""
|
"""
|
||||||
Skip searching un-indexed data.
|
Skip searching un-indexed data.
|
||||||
@@ -2845,14 +2539,14 @@ class AsyncStandardQuery(AsyncQueryBase):
|
|||||||
|
|
||||||
|
|
||||||
class AsyncQuery(AsyncStandardQuery):
|
class AsyncQuery(AsyncStandardQuery):
|
||||||
def __init__(self, inner: LanceQuery, table: Optional["AsyncTable"] = None):
|
def __init__(self, inner: LanceQuery):
|
||||||
"""
|
"""
|
||||||
Construct an AsyncQuery
|
Construct an AsyncQuery
|
||||||
|
|
||||||
This method is not intended to be called directly. Instead, use the
|
This method is not intended to be called directly. Instead, use the
|
||||||
[AsyncTable.query][lancedb.table.AsyncTable.query] method to create a query.
|
[AsyncTable.query][lancedb.table.AsyncTable.query] method to create a query.
|
||||||
"""
|
"""
|
||||||
super().__init__(inner, table)
|
super().__init__(inner)
|
||||||
self._inner = inner
|
self._inner = inner
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
@@ -2936,11 +2630,10 @@ class AsyncQuery(AsyncStandardQuery):
|
|||||||
new_self = self._inner.nearest_to(query_vectors[0])
|
new_self = self._inner.nearest_to(query_vectors[0])
|
||||||
for v in query_vectors[1:]:
|
for v in query_vectors[1:]:
|
||||||
new_self.add_query_vector(v)
|
new_self.add_query_vector(v)
|
||||||
return AsyncVectorQuery(new_self, self._table)
|
return AsyncVectorQuery(new_self)
|
||||||
else:
|
else:
|
||||||
return AsyncVectorQuery(
|
return AsyncVectorQuery(
|
||||||
self._inner.nearest_to(AsyncQuery._query_vec_to_array(query_vector)),
|
self._inner.nearest_to(AsyncQuery._query_vec_to_array(query_vector))
|
||||||
self._table,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
def nearest_to_text(
|
def nearest_to_text(
|
||||||
@@ -2973,18 +2666,17 @@ class AsyncQuery(AsyncStandardQuery):
|
|||||||
|
|
||||||
if isinstance(query, str):
|
if isinstance(query, str):
|
||||||
return AsyncFTSQuery(
|
return AsyncFTSQuery(
|
||||||
self._inner.nearest_to_text({"query": query, "columns": columns}),
|
self._inner.nearest_to_text({"query": query, "columns": columns})
|
||||||
self._table,
|
|
||||||
)
|
)
|
||||||
# FullTextQuery object
|
# FullTextQuery object
|
||||||
return AsyncFTSQuery(self._inner.nearest_to_text({"query": query}), self._table)
|
return AsyncFTSQuery(self._inner.nearest_to_text({"query": query}))
|
||||||
|
|
||||||
|
|
||||||
class AsyncFTSQuery(AsyncStandardQuery):
|
class AsyncFTSQuery(AsyncStandardQuery):
|
||||||
"""A query for full text search for LanceDB."""
|
"""A query for full text search for LanceDB."""
|
||||||
|
|
||||||
def __init__(self, inner: LanceFTSQuery, table: Optional["AsyncTable"] = None):
|
def __init__(self, inner: LanceFTSQuery):
|
||||||
super().__init__(inner, table)
|
super().__init__(inner)
|
||||||
self._inner = inner
|
self._inner = inner
|
||||||
self._reranker = None
|
self._reranker = None
|
||||||
|
|
||||||
@@ -3066,11 +2758,10 @@ class AsyncFTSQuery(AsyncStandardQuery):
|
|||||||
new_self = self._inner.nearest_to(query_vectors[0])
|
new_self = self._inner.nearest_to(query_vectors[0])
|
||||||
for v in query_vectors[1:]:
|
for v in query_vectors[1:]:
|
||||||
new_self.add_query_vector(v)
|
new_self.add_query_vector(v)
|
||||||
return AsyncHybridQuery(new_self, self._table)
|
return AsyncHybridQuery(new_self)
|
||||||
else:
|
else:
|
||||||
return AsyncHybridQuery(
|
return AsyncHybridQuery(
|
||||||
self._inner.nearest_to(AsyncQuery._query_vec_to_array(query_vector)),
|
self._inner.nearest_to(AsyncQuery._query_vec_to_array(query_vector))
|
||||||
self._table,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
async def to_batches(
|
async def to_batches(
|
||||||
@@ -3261,7 +2952,7 @@ class AsyncVectorQueryBase:
|
|||||||
|
|
||||||
|
|
||||||
class AsyncVectorQuery(AsyncStandardQuery, AsyncVectorQueryBase):
|
class AsyncVectorQuery(AsyncStandardQuery, AsyncVectorQueryBase):
|
||||||
def __init__(self, inner: LanceVectorQuery, table: Optional["AsyncTable"] = None):
|
def __init__(self, inner: LanceVectorQuery):
|
||||||
"""
|
"""
|
||||||
Construct an AsyncVectorQuery
|
Construct an AsyncVectorQuery
|
||||||
|
|
||||||
@@ -3271,7 +2962,7 @@ class AsyncVectorQuery(AsyncStandardQuery, AsyncVectorQueryBase):
|
|||||||
a vector query. Or you can use
|
a vector query. Or you can use
|
||||||
[AsyncTable.vector_search][lancedb.table.AsyncTable.vector_search]
|
[AsyncTable.vector_search][lancedb.table.AsyncTable.vector_search]
|
||||||
"""
|
"""
|
||||||
super().__init__(inner, table)
|
super().__init__(inner)
|
||||||
self._inner = inner
|
self._inner = inner
|
||||||
self._reranker = None
|
self._reranker = None
|
||||||
self._query_string = None
|
self._query_string = None
|
||||||
@@ -3325,13 +3016,10 @@ class AsyncVectorQuery(AsyncStandardQuery, AsyncVectorQueryBase):
|
|||||||
|
|
||||||
if isinstance(query, str):
|
if isinstance(query, str):
|
||||||
return AsyncHybridQuery(
|
return AsyncHybridQuery(
|
||||||
self._inner.nearest_to_text({"query": query, "columns": columns}),
|
self._inner.nearest_to_text({"query": query, "columns": columns})
|
||||||
self._table,
|
|
||||||
)
|
)
|
||||||
# FullTextQuery object
|
# FullTextQuery object
|
||||||
return AsyncHybridQuery(
|
return AsyncHybridQuery(self._inner.nearest_to_text({"query": query}))
|
||||||
self._inner.nearest_to_text({"query": query}), self._table
|
|
||||||
)
|
|
||||||
|
|
||||||
async def to_batches(
|
async def to_batches(
|
||||||
self,
|
self,
|
||||||
@@ -3358,8 +3046,8 @@ class AsyncHybridQuery(AsyncStandardQuery, AsyncVectorQueryBase):
|
|||||||
in the `rerank` method to convert the scores to ranks and then normalize them.
|
in the `rerank` method to convert the scores to ranks and then normalize them.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, inner: LanceHybridQuery, table: Optional["AsyncTable"] = None):
|
def __init__(self, inner: LanceHybridQuery):
|
||||||
super().__init__(inner, table)
|
super().__init__(inner)
|
||||||
self._inner = inner
|
self._inner = inner
|
||||||
self._norm = "score"
|
self._norm = "score"
|
||||||
self._reranker = RRFReranker()
|
self._reranker = RRFReranker()
|
||||||
@@ -3400,8 +3088,8 @@ class AsyncHybridQuery(AsyncStandardQuery, AsyncVectorQueryBase):
|
|||||||
max_batch_length: Optional[int] = None,
|
max_batch_length: Optional[int] = None,
|
||||||
timeout: Optional[timedelta] = None,
|
timeout: Optional[timedelta] = None,
|
||||||
) -> AsyncRecordBatchReader:
|
) -> AsyncRecordBatchReader:
|
||||||
fts_query = AsyncFTSQuery(self._inner.to_fts_query(), self._table)
|
fts_query = AsyncFTSQuery(self._inner.to_fts_query())
|
||||||
vec_query = AsyncVectorQuery(self._inner.to_vector_query(), self._table)
|
vec_query = AsyncVectorQuery(self._inner.to_vector_query())
|
||||||
|
|
||||||
# save the row ID choice that was made on the query builder and force it
|
# save the row ID choice that was made on the query builder and force it
|
||||||
# to actually fetch the row ids because we need this for reranking
|
# to actually fetch the row ids because we need this for reranking
|
||||||
@@ -3501,15 +3189,8 @@ class AsyncTakeQuery(AsyncQueryBase):
|
|||||||
Builder for parameterizing and executing take queries.
|
Builder for parameterizing and executing take queries.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, inner: LanceTakeQuery, table: Optional["AsyncTable"] = None):
|
def __init__(self, inner: LanceTakeQuery):
|
||||||
super().__init__(inner, table)
|
super().__init__(inner)
|
||||||
|
|
||||||
async def _plain_scan_to_pandas(
|
|
||||||
self,
|
|
||||||
blob_mode: BlobMode,
|
|
||||||
**kwargs,
|
|
||||||
) -> Optional["pd.DataFrame"]:
|
|
||||||
return None
|
|
||||||
|
|
||||||
|
|
||||||
class BaseQueryBuilder(object):
|
class BaseQueryBuilder(object):
|
||||||
@@ -3591,18 +3272,16 @@ class BaseQueryBuilder(object):
|
|||||||
If not specified, no timeout is applied. If the query does not
|
If not specified, no timeout is applied. If the query does not
|
||||||
complete within the specified time, an error will be raised.
|
complete within the specified time, an error will be raised.
|
||||||
"""
|
"""
|
||||||
async_reader = LOOP.run(
|
async_iter = LOOP.run(self._inner.execute(max_batch_length, timeout))
|
||||||
self._inner.to_batches(max_batch_length=max_batch_length, timeout=timeout)
|
|
||||||
)
|
|
||||||
|
|
||||||
def iter_sync():
|
def iter_sync():
|
||||||
try:
|
try:
|
||||||
while True:
|
while True:
|
||||||
yield LOOP.run(async_reader.__anext__())
|
yield LOOP.run(async_iter.__anext__())
|
||||||
except StopAsyncIteration:
|
except StopAsyncIteration:
|
||||||
return
|
return
|
||||||
|
|
||||||
return pa.RecordBatchReader.from_batches(async_reader.schema, iter_sync())
|
return pa.RecordBatchReader.from_batches(async_iter.schema, iter_sync())
|
||||||
|
|
||||||
def to_arrow(self, timeout: Optional[timedelta] = None) -> pa.Table:
|
def to_arrow(self, timeout: Optional[timedelta] = None) -> pa.Table:
|
||||||
"""
|
"""
|
||||||
@@ -3642,9 +3321,6 @@ class BaseQueryBuilder(object):
|
|||||||
self,
|
self,
|
||||||
flatten: Optional[Union[int, bool]] = None,
|
flatten: Optional[Union[int, bool]] = None,
|
||||||
timeout: Optional[timedelta] = None,
|
timeout: Optional[timedelta] = None,
|
||||||
*,
|
|
||||||
blob_mode: BlobMode = "lazy",
|
|
||||||
**kwargs,
|
|
||||||
) -> "pd.DataFrame":
|
) -> "pd.DataFrame":
|
||||||
"""
|
"""
|
||||||
Execute the query and collect the results into a pandas DataFrame.
|
Execute the query and collect the results into a pandas DataFrame.
|
||||||
@@ -3677,15 +3353,8 @@ class BaseQueryBuilder(object):
|
|||||||
The maximum time to wait for the query to complete.
|
The maximum time to wait for the query to complete.
|
||||||
If not specified, no timeout is applied. If the query does not
|
If not specified, no timeout is applied. If the query does not
|
||||||
complete within the specified time, an error will be raised.
|
complete within the specified time, an error will be raised.
|
||||||
blob_mode: str, default "lazy"
|
|
||||||
Controls how blob columns are returned for plain scan queries.
|
|
||||||
**kwargs
|
|
||||||
Forwarded to pyarrow.Table.to_pandas after query execution and
|
|
||||||
optional flattening.
|
|
||||||
"""
|
"""
|
||||||
return LOOP.run(
|
return LOOP.run(self._inner.to_pandas(flatten, timeout))
|
||||||
self._inner.to_pandas(flatten, timeout, blob_mode=blob_mode, **kwargs)
|
|
||||||
)
|
|
||||||
|
|
||||||
def to_polars(
|
def to_polars(
|
||||||
self,
|
self,
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user