mirror of
https://github.com/meilisearch/meilisearch.git
synced 2025-07-22 06:11:01 +00:00
Compare commits
2 Commits
stream-dum
...
index-stat
Author | SHA1 | Date | |
---|---|---|---|
a41c0ba755 | |||
ef9875256b |
@ -1,2 +0,0 @@
|
|||||||
[alias]
|
|
||||||
xtask = "run --release --package xtask --"
|
|
32
.github/ISSUE_TEMPLATE/sprint_issue.md
vendored
32
.github/ISSUE_TEMPLATE/sprint_issue.md
vendored
@ -2,43 +2,33 @@
|
|||||||
name: New sprint issue
|
name: New sprint issue
|
||||||
about: ⚠️ Should only be used by the engine team ⚠️
|
about: ⚠️ Should only be used by the engine team ⚠️
|
||||||
title: ''
|
title: ''
|
||||||
labels: 'missing usage in PRD, impacts docs'
|
labels: ''
|
||||||
assignees: ''
|
assignees: ''
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
Related product team resources: [PRD]() (_internal only_)
|
Related product team resources: [roadmap card]() (_internal only_) and [PRD]() (_internal only_)
|
||||||
Related product discussion:
|
Related product discussion:
|
||||||
|
Related spec: WIP
|
||||||
|
|
||||||
## Motivation
|
## Motivation
|
||||||
|
|
||||||
<!---Copy/paste the information in PRD or briefly detail the product motivation. Ask product team if any hesitation.-->
|
<!---Copy/paste the information in the roadmap resources or briefly detail the product motivation. Ask product team if any hesitation.-->
|
||||||
|
|
||||||
## Usage
|
## Usage
|
||||||
|
|
||||||
<!---Link to the public part of the PRD, or to the related product discussion for experimental features-->
|
<!---Write a quick description of the usage if the usage has already been defined-->
|
||||||
|
|
||||||
|
Refer to the final spec to know the details and the final decisions about the usage.
|
||||||
|
|
||||||
## TODO
|
## TODO
|
||||||
|
|
||||||
<!---If necessary, create a list with technical/product steps-->
|
<!---Feel free to adapt this list with more technical/product steps-->
|
||||||
|
|
||||||
### Reminders when modifying the Setting API
|
- [ ] Release a prototype
|
||||||
|
- [ ] If prototype validated, merge changes into `main`
|
||||||
<!--- Special steps to remind when adding a new index setting -->
|
- [ ] Update the spec
|
||||||
|
|
||||||
- [ ] Ensure the new setting route is at least tested by the [`test_setting_routes` macro](https://github.com/meilisearch/meilisearch/blob/5204c0b60b384cbc79621b6b2176fca086069e8e/meilisearch/tests/settings/get_settings.rs#L276)
|
|
||||||
- [ ] Ensure Analytics are fully implemented
|
|
||||||
- [ ] `/settings/my-new-setting` configurated in the [`make_setting_routes` macro](https://github.com/meilisearch/meilisearch/blob/5204c0b60b384cbc79621b6b2176fca086069e8e/meilisearch/src/routes/indexes/settings.rs#L141-L165)
|
|
||||||
- [ ] global `/settings` route configurated in the [`update_all` function](https://github.com/meilisearch/meilisearch/blob/5204c0b60b384cbc79621b6b2176fca086069e8e/meilisearch/src/routes/indexes/settings.rs#L655-L751)
|
|
||||||
- [ ] Ensure the dump serializing is consistent with the `/settings` route serializing, e.g., enums case can be different (`camelCase` in route and `PascalCase` in the dump)
|
|
||||||
|
|
||||||
#### Special cases when adding a setting for an experimental feature
|
|
||||||
|
|
||||||
- [ ] ⚠️ API stability: The setting does not appear on the main settings route when the feature has never been enabled (e.g. mark it `Unset` when returned from the index in this situation. See [an example](https://github.com/meilisearch/meilisearch/blob/7a89abd2a025606a42f8b219e539117eb2eb029f/meilisearch-types/src/settings.rs#L608))
|
|
||||||
- [ ] The setting cannot be set when the feature is disabled, either by the main settings route or the subroute (see [`validate_settings` function](https://github.com/meilisearch/meilisearch/blob/7a89abd2a025606a42f8b219e539117eb2eb029f/meilisearch/src/routes/indexes/settings.rs#L811))
|
|
||||||
- [ ] If possible, the setting is reset when the feature is disabled (hard if it requires reindexing)
|
|
||||||
|
|
||||||
## Impacted teams
|
## Impacted teams
|
||||||
|
|
||||||
<!---Ping the related teams. Ask for the engine manager if any hesitation-->
|
<!---Ping the related teams. Ask for the engine manager if any hesitation-->
|
||||||
<!---@meilisearch/docs-team when there is any API change, e.g. settings addition-->
|
|
||||||
|
30
.github/workflows/bench-manual.yml
vendored
30
.github/workflows/bench-manual.yml
vendored
@ -1,30 +0,0 @@
|
|||||||
name: Bench (manual)
|
|
||||||
|
|
||||||
on:
|
|
||||||
workflow_dispatch:
|
|
||||||
inputs:
|
|
||||||
workload:
|
|
||||||
description: 'The path to the workloads to execute (workloads/...)'
|
|
||||||
required: true
|
|
||||||
default: 'workloads/movies.json'
|
|
||||||
|
|
||||||
env:
|
|
||||||
WORKLOAD_NAME: ${{ github.event.inputs.workload }}
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
benchmarks:
|
|
||||||
name: Run and upload benchmarks
|
|
||||||
runs-on: benchmarks
|
|
||||||
timeout-minutes: 180 # 3h
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v3
|
|
||||||
- uses: actions-rs/toolchain@v1
|
|
||||||
with:
|
|
||||||
profile: minimal
|
|
||||||
toolchain: stable
|
|
||||||
override: true
|
|
||||||
|
|
||||||
- name: Run benchmarks - workload ${WORKLOAD_NAME} - branch ${{ github.ref }} - commit ${{ github.sha }}
|
|
||||||
run: |
|
|
||||||
cargo xtask bench --api-key "${{ secrets.BENCHMARK_API_KEY }}" --dashboard-url "${{ vars.BENCHMARK_DASHBOARD_URL }}" --reason "Manual [Run #${{ github.run_id }}](https://github.com/meilisearch/meilisearch/actions/runs/${{ github.run_id }})" -- ${WORKLOAD_NAME}
|
|
||||||
|
|
46
.github/workflows/bench-pr.yml
vendored
46
.github/workflows/bench-pr.yml
vendored
@ -1,46 +0,0 @@
|
|||||||
name: Bench (PR)
|
|
||||||
on:
|
|
||||||
issue_comment:
|
|
||||||
types: [created]
|
|
||||||
|
|
||||||
permissions:
|
|
||||||
issues: write
|
|
||||||
|
|
||||||
env:
|
|
||||||
GH_TOKEN: ${{ secrets.MEILI_BOT_GH_PAT }}
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
run-benchmarks-on-comment:
|
|
||||||
if: startsWith(github.event.comment.body, '/bench')
|
|
||||||
name: Run and upload benchmarks
|
|
||||||
runs-on: benchmarks
|
|
||||||
timeout-minutes: 180 # 3h
|
|
||||||
steps:
|
|
||||||
- name: Check for Command
|
|
||||||
id: command
|
|
||||||
uses: xt0rted/slash-command-action@v2
|
|
||||||
with:
|
|
||||||
command: bench
|
|
||||||
reaction-type: "rocket"
|
|
||||||
repo-token: ${{ env.GH_TOKEN }}
|
|
||||||
|
|
||||||
- uses: xt0rted/pull-request-comment-branch@v2
|
|
||||||
id: comment-branch
|
|
||||||
with:
|
|
||||||
repo_token: ${{ env.GH_TOKEN }}
|
|
||||||
|
|
||||||
- uses: actions/checkout@v3
|
|
||||||
if: success()
|
|
||||||
with:
|
|
||||||
fetch-depth: 0 # fetch full history to be able to get main commit sha
|
|
||||||
ref: ${{ steps.comment-branch.outputs.head_ref }}
|
|
||||||
|
|
||||||
- uses: actions-rs/toolchain@v1
|
|
||||||
with:
|
|
||||||
profile: minimal
|
|
||||||
toolchain: stable
|
|
||||||
override: true
|
|
||||||
|
|
||||||
- name: Run benchmarks on PR ${{ github.event.issue.id }}
|
|
||||||
run: |
|
|
||||||
cargo xtask bench --api-key "${{ secrets.BENCHMARK_API_KEY }}" --dashboard-url "${{ vars.BENCHMARK_DASHBOARD_URL }}" --reason "[Comment](${{ github.event.comment.html_url }}) on [#${{ github.event.issue.number }}](${{ github.event.issue.html_url }})" -- ${{ steps.command.outputs.command-arguments }}
|
|
25
.github/workflows/bench-push-indexing.yml
vendored
25
.github/workflows/bench-push-indexing.yml
vendored
@ -1,25 +0,0 @@
|
|||||||
name: Indexing bench (push)
|
|
||||||
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
branches:
|
|
||||||
- main
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
benchmarks:
|
|
||||||
name: Run and upload benchmarks
|
|
||||||
runs-on: benchmarks
|
|
||||||
timeout-minutes: 180 # 3h
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v3
|
|
||||||
- uses: actions-rs/toolchain@v1
|
|
||||||
with:
|
|
||||||
profile: minimal
|
|
||||||
toolchain: stable
|
|
||||||
override: true
|
|
||||||
|
|
||||||
# Run benchmarks
|
|
||||||
- name: Run benchmarks - Dataset ${BENCH_NAME} - Branch main - Commit ${{ github.sha }}
|
|
||||||
run: |
|
|
||||||
cargo xtask bench --api-key "${{ secrets.BENCHMARK_API_KEY }}" --dashboard-url "${{ vars.BENCHMARK_DASHBOARD_URL }}" --reason "Push on `main` [Run #${{ github.run_id }}](https://github.com/meilisearch/meilisearch/actions/runs/${{ github.run_id }})" -- workloads/*.json
|
|
||||||
|
|
2
.github/workflows/benchmarks-manual.yml
vendored
2
.github/workflows/benchmarks-manual.yml
vendored
@ -74,4 +74,4 @@ jobs:
|
|||||||
echo "${{ steps.file.outputs.basename }}.json has just been pushed."
|
echo "${{ steps.file.outputs.basename }}.json has just been pushed."
|
||||||
echo 'How to compare this benchmark with another one?'
|
echo 'How to compare this benchmark with another one?'
|
||||||
echo ' - Check the available files with: ./benchmarks/scripts/list.sh'
|
echo ' - Check the available files with: ./benchmarks/scripts/list.sh'
|
||||||
echo " - Run the following command: ./benchmaks/scripts/compare.sh <file-to-compare-with> ${{ steps.file.outputs.basename }}.json"
|
echo " - Run the following command: ./benchmaks/scipts/compare.sh <file-to-compare-with> ${{ steps.file.outputs.basename }}.json"
|
||||||
|
98
.github/workflows/benchmarks-pr.yml
vendored
98
.github/workflows/benchmarks-pr.yml
vendored
@ -1,98 +0,0 @@
|
|||||||
name: Benchmarks (PR)
|
|
||||||
on: issue_comment
|
|
||||||
permissions:
|
|
||||||
issues: write
|
|
||||||
|
|
||||||
env:
|
|
||||||
GH_TOKEN: ${{ secrets.MEILI_BOT_GH_PAT }}
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
run-benchmarks-on-comment:
|
|
||||||
if: startsWith(github.event.comment.body, '/benchmark')
|
|
||||||
name: Run and upload benchmarks
|
|
||||||
runs-on: benchmarks
|
|
||||||
timeout-minutes: 4320 # 72h
|
|
||||||
steps:
|
|
||||||
- uses: actions-rs/toolchain@v1
|
|
||||||
with:
|
|
||||||
profile: minimal
|
|
||||||
toolchain: stable
|
|
||||||
override: true
|
|
||||||
|
|
||||||
- name: Check for Command
|
|
||||||
id: command
|
|
||||||
uses: xt0rted/slash-command-action@v2
|
|
||||||
with:
|
|
||||||
command: benchmark
|
|
||||||
reaction-type: "eyes"
|
|
||||||
repo-token: ${{ env.GH_TOKEN }}
|
|
||||||
|
|
||||||
- uses: xt0rted/pull-request-comment-branch@v2
|
|
||||||
id: comment-branch
|
|
||||||
with:
|
|
||||||
repo_token: ${{ env.GH_TOKEN }}
|
|
||||||
|
|
||||||
- uses: actions/checkout@v3
|
|
||||||
if: success()
|
|
||||||
with:
|
|
||||||
fetch-depth: 0 # fetch full history to be able to get main commit sha
|
|
||||||
ref: ${{ steps.comment-branch.outputs.head_ref }}
|
|
||||||
|
|
||||||
# Set variables
|
|
||||||
- name: Set current branch name
|
|
||||||
shell: bash
|
|
||||||
run: echo "name=$(git rev-parse --abbrev-ref HEAD)" >> $GITHUB_OUTPUT
|
|
||||||
id: current_branch
|
|
||||||
- name: Set normalized current branch name # Replace `/` by `_` in branch name to avoid issues when pushing to S3
|
|
||||||
shell: bash
|
|
||||||
run: echo "name=$(git rev-parse --abbrev-ref HEAD | tr '/' '_')" >> $GITHUB_OUTPUT
|
|
||||||
id: normalized_current_branch
|
|
||||||
- name: Set shorter commit SHA
|
|
||||||
shell: bash
|
|
||||||
run: echo "short=$(echo $GITHUB_SHA | cut -c1-8)" >> $GITHUB_OUTPUT
|
|
||||||
id: commit_sha
|
|
||||||
- name: Set file basename with format "dataset_branch_commitSHA"
|
|
||||||
shell: bash
|
|
||||||
run: echo "basename=$(echo ${{ steps.command.outputs.command-arguments }}_${{ steps.normalized_current_branch.outputs.name }}_${{ steps.commit_sha.outputs.short }})" >> $GITHUB_OUTPUT
|
|
||||||
id: file
|
|
||||||
|
|
||||||
# Run benchmarks
|
|
||||||
- name: Run benchmarks - Dataset ${{ steps.command.outputs.command-arguments }} - Branch ${{ steps.current_branch.outputs.name }} - Commit ${{ steps.commit_sha.outputs.short }}
|
|
||||||
run: |
|
|
||||||
cd benchmarks
|
|
||||||
cargo bench --bench ${{ steps.command.outputs.command-arguments }} -- --save-baseline ${{ steps.file.outputs.basename }}
|
|
||||||
|
|
||||||
# Generate critcmp files
|
|
||||||
- name: Install critcmp
|
|
||||||
uses: taiki-e/install-action@v2
|
|
||||||
with:
|
|
||||||
tool: critcmp
|
|
||||||
- name: Export cripcmp file
|
|
||||||
run: |
|
|
||||||
critcmp --export ${{ steps.file.outputs.basename }} > ${{ steps.file.outputs.basename }}.json
|
|
||||||
|
|
||||||
# Upload benchmarks
|
|
||||||
- name: Upload ${{ steps.file.outputs.basename }}.json to DO Spaces # DigitalOcean Spaces = S3
|
|
||||||
uses: BetaHuhn/do-spaces-action@v2
|
|
||||||
with:
|
|
||||||
access_key: ${{ secrets.DO_SPACES_ACCESS_KEY }}
|
|
||||||
secret_key: ${{ secrets.DO_SPACES_SECRET_KEY }}
|
|
||||||
space_name: ${{ secrets.DO_SPACES_SPACE_NAME }}
|
|
||||||
space_region: ${{ secrets.DO_SPACES_SPACE_REGION }}
|
|
||||||
source: ${{ steps.file.outputs.basename }}.json
|
|
||||||
out_dir: critcmp_results
|
|
||||||
|
|
||||||
# Compute the diff of the benchmarks and send a message on the GitHub PR
|
|
||||||
- name: Compute and send a message in the PR
|
|
||||||
env:
|
|
||||||
GITHUB_TOKEN: ${{ secrets.MEILI_BOT_GH_PAT }}
|
|
||||||
run: |
|
|
||||||
set -x
|
|
||||||
export base_ref=$(git merge-base origin/main ${{ steps.comment-branch.outputs.head_ref }} | head -c8)
|
|
||||||
export base_filename=$(echo ${{ steps.command.outputs.command-arguments }}_main_${base_ref}.json)
|
|
||||||
export bench_name=$(echo ${{ steps.command.outputs.command-arguments }})
|
|
||||||
echo "Here are your $bench_name benchmarks diff 👊" >> body.txt
|
|
||||||
echo '```' >> body.txt
|
|
||||||
./benchmarks/scripts/compare.sh $base_filename ${{ steps.file.outputs.basename }}.json >> body.txt
|
|
||||||
echo '```' >> body.txt
|
|
||||||
gh pr comment ${{ steps.current_branch.outputs.name }} --body-file body.txt
|
|
4
.github/workflows/dependency-issue.yml
vendored
4
.github/workflows/dependency-issue.yml
vendored
@ -2,8 +2,8 @@ name: Create issue to upgrade dependencies
|
|||||||
|
|
||||||
on:
|
on:
|
||||||
schedule:
|
schedule:
|
||||||
# Run the first of the month, every 6 month
|
# Run the first of the month, every 3 month
|
||||||
- cron: '0 0 1 */6 *'
|
- cron: '0 0 1 */3 *'
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
|
38
.github/workflows/milestone-workflow.yml
vendored
38
.github/workflows/milestone-workflow.yml
vendored
@ -110,44 +110,6 @@ jobs:
|
|||||||
--milestone $MILESTONE_VERSION \
|
--milestone $MILESTONE_VERSION \
|
||||||
--assignee curquiza
|
--assignee curquiza
|
||||||
|
|
||||||
create-update-version-issue:
|
|
||||||
needs: get-release-version
|
|
||||||
# Create the update-version issue even if the release is a patch release
|
|
||||||
if: github.event.action == 'created'
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
env:
|
|
||||||
ISSUE_TEMPLATE: issue-template.md
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v3
|
|
||||||
- name: Download the issue template
|
|
||||||
run: curl -s https://raw.githubusercontent.com/meilisearch/engine-team/main/issue-templates/update-version-issue.md > $ISSUE_TEMPLATE
|
|
||||||
- name: Create the issue
|
|
||||||
run: |
|
|
||||||
gh issue create \
|
|
||||||
--title "Update version in Cargo.toml for $MILESTONE_VERSION" \
|
|
||||||
--label 'maintenance' \
|
|
||||||
--body-file $ISSUE_TEMPLATE \
|
|
||||||
--milestone $MILESTONE_VERSION
|
|
||||||
|
|
||||||
create-update-openapi-issue:
|
|
||||||
needs: get-release-version
|
|
||||||
# Create the openAPI issue if the release is not only a patch release
|
|
||||||
if: github.event.action == 'created' && needs.get-release-version.outputs.is-patch == 'false'
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
env:
|
|
||||||
ISSUE_TEMPLATE: issue-template.md
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v3
|
|
||||||
- name: Download the issue template
|
|
||||||
run: curl -s https://raw.githubusercontent.com/meilisearch/engine-team/main/issue-templates/update-openapi-issue.md > $ISSUE_TEMPLATE
|
|
||||||
- name: Create the issue
|
|
||||||
run: |
|
|
||||||
gh issue create \
|
|
||||||
--title "Update Open API file for $MILESTONE_VERSION" \
|
|
||||||
--label 'maintenance' \
|
|
||||||
--body-file $ISSUE_TEMPLATE \
|
|
||||||
--milestone $MILESTONE_VERSION
|
|
||||||
|
|
||||||
# ----------------
|
# ----------------
|
||||||
# MILESTONE CLOSED
|
# MILESTONE CLOSED
|
||||||
# ----------------
|
# ----------------
|
||||||
|
5
.github/workflows/publish-apt-brew-pkg.yml
vendored
5
.github/workflows/publish-apt-brew-pkg.yml
vendored
@ -35,7 +35,7 @@ jobs:
|
|||||||
- name: Build deb package
|
- name: Build deb package
|
||||||
run: cargo deb -p meilisearch -o target/debian/meilisearch.deb
|
run: cargo deb -p meilisearch -o target/debian/meilisearch.deb
|
||||||
- name: Upload debian pkg to release
|
- name: Upload debian pkg to release
|
||||||
uses: svenstaro/upload-release-action@2.7.0
|
uses: svenstaro/upload-release-action@2.6.1
|
||||||
with:
|
with:
|
||||||
repo_token: ${{ secrets.MEILI_BOT_GH_PAT }}
|
repo_token: ${{ secrets.MEILI_BOT_GH_PAT }}
|
||||||
file: target/debian/meilisearch.deb
|
file: target/debian/meilisearch.deb
|
||||||
@ -50,9 +50,8 @@ jobs:
|
|||||||
needs: check-version
|
needs: check-version
|
||||||
steps:
|
steps:
|
||||||
- name: Create PR to Homebrew
|
- name: Create PR to Homebrew
|
||||||
uses: mislav/bump-homebrew-formula-action@v3
|
uses: mislav/bump-homebrew-formula-action@v2
|
||||||
with:
|
with:
|
||||||
formula-name: meilisearch
|
formula-name: meilisearch
|
||||||
formula-path: Formula/m/meilisearch.rb
|
|
||||||
env:
|
env:
|
||||||
COMMITTER_TOKEN: ${{ secrets.HOMEBREW_COMMITTER_TOKEN }}
|
COMMITTER_TOKEN: ${{ secrets.HOMEBREW_COMMITTER_TOKEN }}
|
||||||
|
8
.github/workflows/publish-binaries.yml
vendored
8
.github/workflows/publish-binaries.yml
vendored
@ -54,7 +54,7 @@ jobs:
|
|||||||
# No need to upload binaries for dry run (cron)
|
# No need to upload binaries for dry run (cron)
|
||||||
- name: Upload binaries to release
|
- name: Upload binaries to release
|
||||||
if: github.event_name == 'release'
|
if: github.event_name == 'release'
|
||||||
uses: svenstaro/upload-release-action@2.7.0
|
uses: svenstaro/upload-release-action@2.6.1
|
||||||
with:
|
with:
|
||||||
repo_token: ${{ secrets.MEILI_BOT_GH_PAT }}
|
repo_token: ${{ secrets.MEILI_BOT_GH_PAT }}
|
||||||
file: target/release/meilisearch
|
file: target/release/meilisearch
|
||||||
@ -87,7 +87,7 @@ jobs:
|
|||||||
# No need to upload binaries for dry run (cron)
|
# No need to upload binaries for dry run (cron)
|
||||||
- name: Upload binaries to release
|
- name: Upload binaries to release
|
||||||
if: github.event_name == 'release'
|
if: github.event_name == 'release'
|
||||||
uses: svenstaro/upload-release-action@2.7.0
|
uses: svenstaro/upload-release-action@2.6.1
|
||||||
with:
|
with:
|
||||||
repo_token: ${{ secrets.MEILI_BOT_GH_PAT }}
|
repo_token: ${{ secrets.MEILI_BOT_GH_PAT }}
|
||||||
file: target/release/${{ matrix.artifact_name }}
|
file: target/release/${{ matrix.artifact_name }}
|
||||||
@ -121,7 +121,7 @@ jobs:
|
|||||||
- name: Upload the binary to release
|
- name: Upload the binary to release
|
||||||
# No need to upload binaries for dry run (cron)
|
# No need to upload binaries for dry run (cron)
|
||||||
if: github.event_name == 'release'
|
if: github.event_name == 'release'
|
||||||
uses: svenstaro/upload-release-action@2.7.0
|
uses: svenstaro/upload-release-action@2.6.1
|
||||||
with:
|
with:
|
||||||
repo_token: ${{ secrets.MEILI_BOT_GH_PAT }}
|
repo_token: ${{ secrets.MEILI_BOT_GH_PAT }}
|
||||||
file: target/${{ matrix.target }}/release/meilisearch
|
file: target/${{ matrix.target }}/release/meilisearch
|
||||||
@ -183,7 +183,7 @@ jobs:
|
|||||||
- name: Upload the binary to release
|
- name: Upload the binary to release
|
||||||
# No need to upload binaries for dry run (cron)
|
# No need to upload binaries for dry run (cron)
|
||||||
if: github.event_name == 'release'
|
if: github.event_name == 'release'
|
||||||
uses: svenstaro/upload-release-action@2.7.0
|
uses: svenstaro/upload-release-action@2.6.1
|
||||||
with:
|
with:
|
||||||
repo_token: ${{ secrets.MEILI_BOT_GH_PAT }}
|
repo_token: ${{ secrets.MEILI_BOT_GH_PAT }}
|
||||||
file: target/${{ matrix.target }}/release/meilisearch
|
file: target/${{ matrix.target }}/release/meilisearch
|
||||||
|
12
.github/workflows/publish-docker-images.yml
vendored
12
.github/workflows/publish-docker-images.yml
vendored
@ -57,20 +57,20 @@ jobs:
|
|||||||
echo "date=$commit_date" >> $GITHUB_OUTPUT
|
echo "date=$commit_date" >> $GITHUB_OUTPUT
|
||||||
|
|
||||||
- name: Set up QEMU
|
- name: Set up QEMU
|
||||||
uses: docker/setup-qemu-action@v3
|
uses: docker/setup-qemu-action@v2
|
||||||
|
|
||||||
- name: Set up Docker Buildx
|
- name: Set up Docker Buildx
|
||||||
uses: docker/setup-buildx-action@v3
|
uses: docker/setup-buildx-action@v2
|
||||||
|
|
||||||
- name: Login to Docker Hub
|
- name: Login to Docker Hub
|
||||||
uses: docker/login-action@v3
|
uses: docker/login-action@v2
|
||||||
with:
|
with:
|
||||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||||
|
|
||||||
- name: Docker meta
|
- name: Docker meta
|
||||||
id: meta
|
id: meta
|
||||||
uses: docker/metadata-action@v5
|
uses: docker/metadata-action@v4
|
||||||
with:
|
with:
|
||||||
images: getmeili/meilisearch
|
images: getmeili/meilisearch
|
||||||
# Prevent `latest` to be updated for each new tag pushed.
|
# Prevent `latest` to be updated for each new tag pushed.
|
||||||
@ -83,7 +83,7 @@ jobs:
|
|||||||
type=raw,value=latest,enable=${{ steps.check-tag-format.outputs.stable == 'true' && steps.check-tag-format.outputs.latest == 'true' }}
|
type=raw,value=latest,enable=${{ steps.check-tag-format.outputs.stable == 'true' && steps.check-tag-format.outputs.latest == 'true' }}
|
||||||
|
|
||||||
- name: Build and push
|
- name: Build and push
|
||||||
uses: docker/build-push-action@v5
|
uses: docker/build-push-action@v4
|
||||||
with:
|
with:
|
||||||
push: true
|
push: true
|
||||||
platforms: linux/amd64,linux/arm64
|
platforms: linux/amd64,linux/arm64
|
||||||
@ -97,7 +97,7 @@ jobs:
|
|||||||
- name: Send CI information to Cloud team
|
- name: Send CI information to Cloud team
|
||||||
# Do not send if nightly build (i.e. 'schedule' or 'workflow_dispatch' event)
|
# Do not send if nightly build (i.e. 'schedule' or 'workflow_dispatch' event)
|
||||||
if: github.event_name == 'push'
|
if: github.event_name == 'push'
|
||||||
uses: peter-evans/repository-dispatch@v3
|
uses: peter-evans/repository-dispatch@v2
|
||||||
with:
|
with:
|
||||||
token: ${{ secrets.MEILI_BOT_GH_PAT }}
|
token: ${{ secrets.MEILI_BOT_GH_PAT }}
|
||||||
repository: meilisearch/meilisearch-cloud
|
repository: meilisearch/meilisearch-cloud
|
||||||
|
294
.github/workflows/sdks-tests.yml
vendored
294
.github/workflows/sdks-tests.yml
vendored
@ -14,7 +14,6 @@ on:
|
|||||||
env:
|
env:
|
||||||
MEILI_MASTER_KEY: 'masterKey'
|
MEILI_MASTER_KEY: 'masterKey'
|
||||||
MEILI_NO_ANALYTICS: 'true'
|
MEILI_NO_ANALYTICS: 'true'
|
||||||
DISABLE_COVERAGE: 'true'
|
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
define-docker-image:
|
define-docker-image:
|
||||||
@ -22,7 +21,7 @@ jobs:
|
|||||||
outputs:
|
outputs:
|
||||||
docker-image: ${{ steps.define-image.outputs.docker-image }}
|
docker-image: ${{ steps.define-image.outputs.docker-image }}
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v3
|
||||||
- name: Define the Docker image we need to use
|
- name: Define the Docker image we need to use
|
||||||
id: define-image
|
id: define-image
|
||||||
run: |
|
run: |
|
||||||
@ -31,117 +30,6 @@ jobs:
|
|||||||
if [[ $event == 'workflow_dispatch' ]]; then
|
if [[ $event == 'workflow_dispatch' ]]; then
|
||||||
echo "docker-image=${{ github.event.inputs.docker_image }}" >> $GITHUB_OUTPUT
|
echo "docker-image=${{ github.event.inputs.docker_image }}" >> $GITHUB_OUTPUT
|
||||||
fi
|
fi
|
||||||
- name: Docker image is ${{ steps.define-image.outputs.docker-image }}
|
|
||||||
run: echo "Docker image is ${{ steps.define-image.outputs.docker-image }}"
|
|
||||||
|
|
||||||
##########
|
|
||||||
## SDKs ##
|
|
||||||
##########
|
|
||||||
|
|
||||||
meilisearch-dotnet-tests:
|
|
||||||
needs: define-docker-image
|
|
||||||
name: .NET SDK tests
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
env:
|
|
||||||
MEILISEARCH_VERSION: ${{ needs.define-docker-image.outputs.docker-image }}
|
|
||||||
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v4
|
|
||||||
with:
|
|
||||||
repository: meilisearch/meilisearch-dotnet
|
|
||||||
- name: Setup .NET Core
|
|
||||||
uses: actions/setup-dotnet@v4
|
|
||||||
with:
|
|
||||||
dotnet-version: "6.0.x"
|
|
||||||
- name: Install dependencies
|
|
||||||
run: dotnet restore
|
|
||||||
- name: Build
|
|
||||||
run: dotnet build --configuration Release --no-restore
|
|
||||||
- name: Meilisearch (latest version) setup with Docker
|
|
||||||
run: docker compose up -d
|
|
||||||
- name: Run tests
|
|
||||||
run: dotnet test --no-restore --verbosity normal
|
|
||||||
|
|
||||||
meilisearch-dart-tests:
|
|
||||||
needs: define-docker-image
|
|
||||||
name: Dart SDK tests
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
services:
|
|
||||||
meilisearch:
|
|
||||||
image: getmeili/meilisearch:${{ needs.define-docker-image.outputs.docker-image }}
|
|
||||||
env:
|
|
||||||
MEILI_MASTER_KEY: ${{ env.MEILI_MASTER_KEY }}
|
|
||||||
MEILI_NO_ANALYTICS: ${{ env.MEILI_NO_ANALYTICS }}
|
|
||||||
ports:
|
|
||||||
- '7700:7700'
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v4
|
|
||||||
with:
|
|
||||||
repository: meilisearch/meilisearch-dart
|
|
||||||
- uses: dart-lang/setup-dart@v1
|
|
||||||
with:
|
|
||||||
sdk: 'latest'
|
|
||||||
- name: Install dependencies
|
|
||||||
run: dart pub get
|
|
||||||
- name: Run integration tests
|
|
||||||
run: dart test --concurrency=4
|
|
||||||
|
|
||||||
meilisearch-go-tests:
|
|
||||||
needs: define-docker-image
|
|
||||||
name: Go SDK tests
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
services:
|
|
||||||
meilisearch:
|
|
||||||
image: getmeili/meilisearch:${{ needs.define-docker-image.outputs.docker-image }}
|
|
||||||
env:
|
|
||||||
MEILI_MASTER_KEY: ${{ env.MEILI_MASTER_KEY }}
|
|
||||||
MEILI_NO_ANALYTICS: ${{ env.MEILI_NO_ANALYTICS }}
|
|
||||||
ports:
|
|
||||||
- '7700:7700'
|
|
||||||
steps:
|
|
||||||
- name: Set up Go
|
|
||||||
uses: actions/setup-go@v5
|
|
||||||
with:
|
|
||||||
go-version: stable
|
|
||||||
- uses: actions/checkout@v4
|
|
||||||
with:
|
|
||||||
repository: meilisearch/meilisearch-go
|
|
||||||
- name: Get dependencies
|
|
||||||
run: |
|
|
||||||
go get -v -t -d ./...
|
|
||||||
if [ -f Gopkg.toml ]; then
|
|
||||||
curl https://raw.githubusercontent.com/golang/dep/master/install.sh | sh
|
|
||||||
dep ensure
|
|
||||||
fi
|
|
||||||
- name: Run integration tests
|
|
||||||
run: go test -v ./...
|
|
||||||
|
|
||||||
meilisearch-java-tests:
|
|
||||||
needs: define-docker-image
|
|
||||||
name: Java SDK tests
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
services:
|
|
||||||
meilisearch:
|
|
||||||
image: getmeili/meilisearch:${{ needs.define-docker-image.outputs.docker-image }}
|
|
||||||
env:
|
|
||||||
MEILI_MASTER_KEY: ${{ env.MEILI_MASTER_KEY }}
|
|
||||||
MEILI_NO_ANALYTICS: ${{ env.MEILI_NO_ANALYTICS }}
|
|
||||||
ports:
|
|
||||||
- '7700:7700'
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v4
|
|
||||||
with:
|
|
||||||
repository: meilisearch/meilisearch-java
|
|
||||||
- name: Set up Java
|
|
||||||
uses: actions/setup-java@v4
|
|
||||||
with:
|
|
||||||
java-version: 8
|
|
||||||
distribution: 'zulu'
|
|
||||||
cache: gradle
|
|
||||||
- name: Grant execute permission for gradlew
|
|
||||||
run: chmod +x gradlew
|
|
||||||
- name: Build and run unit and integration tests
|
|
||||||
run: ./gradlew build integrationTest
|
|
||||||
|
|
||||||
meilisearch-js-tests:
|
meilisearch-js-tests:
|
||||||
needs: define-docker-image
|
needs: define-docker-image
|
||||||
@ -156,11 +44,11 @@ jobs:
|
|||||||
ports:
|
ports:
|
||||||
- '7700:7700'
|
- '7700:7700'
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v3
|
||||||
with:
|
with:
|
||||||
repository: meilisearch/meilisearch-js
|
repository: meilisearch/meilisearch-js
|
||||||
- name: Setup node
|
- name: Setup node
|
||||||
uses: actions/setup-node@v4
|
uses: actions/setup-node@v3
|
||||||
with:
|
with:
|
||||||
cache: 'yarn'
|
cache: 'yarn'
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
@ -178,6 +66,33 @@ jobs:
|
|||||||
- name: Run Browser env
|
- name: Run Browser env
|
||||||
run: yarn test:env:browser
|
run: yarn test:env:browser
|
||||||
|
|
||||||
|
instant-meilisearch-tests:
|
||||||
|
needs: define-docker-image
|
||||||
|
name: instant-meilisearch tests
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
services:
|
||||||
|
meilisearch:
|
||||||
|
image: getmeili/meilisearch:${{ needs.define-docker-image.outputs.docker-image }}
|
||||||
|
env:
|
||||||
|
MEILI_MASTER_KEY: ${{ env.MEILI_MASTER_KEY }}
|
||||||
|
MEILI_NO_ANALYTICS: ${{ env.MEILI_NO_ANALYTICS }}
|
||||||
|
ports:
|
||||||
|
- '7700:7700'
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v3
|
||||||
|
with:
|
||||||
|
repository: meilisearch/instant-meilisearch
|
||||||
|
- name: Setup node
|
||||||
|
uses: actions/setup-node@v3
|
||||||
|
with:
|
||||||
|
cache: yarn
|
||||||
|
- name: Install dependencies
|
||||||
|
run: yarn install
|
||||||
|
- name: Run tests
|
||||||
|
run: yarn test
|
||||||
|
- name: Build all the playgrounds and the packages
|
||||||
|
run: yarn build
|
||||||
|
|
||||||
meilisearch-php-tests:
|
meilisearch-php-tests:
|
||||||
needs: define-docker-image
|
needs: define-docker-image
|
||||||
name: PHP SDK tests
|
name: PHP SDK tests
|
||||||
@ -191,11 +106,13 @@ jobs:
|
|||||||
ports:
|
ports:
|
||||||
- '7700:7700'
|
- '7700:7700'
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v3
|
||||||
with:
|
with:
|
||||||
repository: meilisearch/meilisearch-php
|
repository: meilisearch/meilisearch-php
|
||||||
- name: Install PHP
|
- name: Install PHP
|
||||||
uses: shivammathur/setup-php@v2
|
uses: shivammathur/setup-php@v2
|
||||||
|
with:
|
||||||
|
coverage: none
|
||||||
- name: Validate composer.json and composer.lock
|
- name: Validate composer.json and composer.lock
|
||||||
run: composer validate
|
run: composer validate
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
@ -220,11 +137,11 @@ jobs:
|
|||||||
ports:
|
ports:
|
||||||
- '7700:7700'
|
- '7700:7700'
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v3
|
||||||
with:
|
with:
|
||||||
repository: meilisearch/meilisearch-python
|
repository: meilisearch/meilisearch-python
|
||||||
- name: Set up Python
|
- name: Set up Python
|
||||||
uses: actions/setup-python@v5
|
uses: actions/setup-python@v4
|
||||||
- name: Install pipenv
|
- name: Install pipenv
|
||||||
uses: dschep/install-pipenv-action@v1
|
uses: dschep/install-pipenv-action@v1
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
@ -232,6 +149,36 @@ jobs:
|
|||||||
- name: Test with pytest
|
- name: Test with pytest
|
||||||
run: pipenv run pytest
|
run: pipenv run pytest
|
||||||
|
|
||||||
|
meilisearch-go-tests:
|
||||||
|
needs: define-docker-image
|
||||||
|
name: Go SDK tests
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
services:
|
||||||
|
meilisearch:
|
||||||
|
image: getmeili/meilisearch:${{ needs.define-docker-image.outputs.docker-image }}
|
||||||
|
env:
|
||||||
|
MEILI_MASTER_KEY: ${{ env.MEILI_MASTER_KEY }}
|
||||||
|
MEILI_NO_ANALYTICS: ${{ env.MEILI_NO_ANALYTICS }}
|
||||||
|
ports:
|
||||||
|
- '7700:7700'
|
||||||
|
steps:
|
||||||
|
- name: Set up Go
|
||||||
|
uses: actions/setup-go@v4
|
||||||
|
with:
|
||||||
|
go-version: stable
|
||||||
|
- uses: actions/checkout@v3
|
||||||
|
with:
|
||||||
|
repository: meilisearch/meilisearch-go
|
||||||
|
- name: Get dependencies
|
||||||
|
run: |
|
||||||
|
go get -v -t -d ./...
|
||||||
|
if [ -f Gopkg.toml ]; then
|
||||||
|
curl https://raw.githubusercontent.com/golang/dep/master/install.sh | sh
|
||||||
|
dep ensure
|
||||||
|
fi
|
||||||
|
- name: Run integration tests
|
||||||
|
run: go test -v ./...
|
||||||
|
|
||||||
meilisearch-ruby-tests:
|
meilisearch-ruby-tests:
|
||||||
needs: define-docker-image
|
needs: define-docker-image
|
||||||
name: Ruby SDK tests
|
name: Ruby SDK tests
|
||||||
@ -245,7 +192,7 @@ jobs:
|
|||||||
ports:
|
ports:
|
||||||
- '7700:7700'
|
- '7700:7700'
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v3
|
||||||
with:
|
with:
|
||||||
repository: meilisearch/meilisearch-ruby
|
repository: meilisearch/meilisearch-ruby
|
||||||
- name: Set up Ruby 3
|
- name: Set up Ruby 3
|
||||||
@ -270,117 +217,10 @@ jobs:
|
|||||||
ports:
|
ports:
|
||||||
- '7700:7700'
|
- '7700:7700'
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v3
|
||||||
with:
|
with:
|
||||||
repository: meilisearch/meilisearch-rust
|
repository: meilisearch/meilisearch-rust
|
||||||
- name: Build
|
- name: Build
|
||||||
run: cargo build --verbose
|
run: cargo build --verbose
|
||||||
- name: Run tests
|
- name: Run tests
|
||||||
run: cargo test --verbose
|
run: cargo test --verbose
|
||||||
|
|
||||||
meilisearch-swift-tests:
|
|
||||||
needs: define-docker-image
|
|
||||||
name: Swift SDK tests
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
services:
|
|
||||||
meilisearch:
|
|
||||||
image: getmeili/meilisearch:${{ needs.define-docker-image.outputs.docker-image }}
|
|
||||||
env:
|
|
||||||
MEILI_MASTER_KEY: ${{ env.MEILI_MASTER_KEY }}
|
|
||||||
MEILI_NO_ANALYTICS: ${{ env.MEILI_NO_ANALYTICS }}
|
|
||||||
ports:
|
|
||||||
- '7700:7700'
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v4
|
|
||||||
with:
|
|
||||||
repository: meilisearch/meilisearch-swift
|
|
||||||
- name: Run tests
|
|
||||||
run: swift test
|
|
||||||
|
|
||||||
########################
|
|
||||||
## FRONT-END PLUGINS ##
|
|
||||||
########################
|
|
||||||
|
|
||||||
meilisearch-js-plugins-tests:
|
|
||||||
needs: define-docker-image
|
|
||||||
name: meilisearch-js-plugins tests
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
services:
|
|
||||||
meilisearch:
|
|
||||||
image: getmeili/meilisearch:${{ needs.define-docker-image.outputs.docker-image }}
|
|
||||||
env:
|
|
||||||
MEILI_MASTER_KEY: ${{ env.MEILI_MASTER_KEY }}
|
|
||||||
MEILI_NO_ANALYTICS: ${{ env.MEILI_NO_ANALYTICS }}
|
|
||||||
ports:
|
|
||||||
- '7700:7700'
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v4
|
|
||||||
with:
|
|
||||||
repository: meilisearch/meilisearch-js-plugins
|
|
||||||
- name: Setup node
|
|
||||||
uses: actions/setup-node@v4
|
|
||||||
with:
|
|
||||||
cache: yarn
|
|
||||||
- name: Install dependencies
|
|
||||||
run: yarn install
|
|
||||||
- name: Run tests
|
|
||||||
run: yarn test
|
|
||||||
- name: Build all the playgrounds and the packages
|
|
||||||
run: yarn build
|
|
||||||
|
|
||||||
########################
|
|
||||||
## BACK-END PLUGINS ###
|
|
||||||
########################
|
|
||||||
|
|
||||||
meilisearch-rails-tests:
|
|
||||||
needs: define-docker-image
|
|
||||||
name: meilisearch-rails tests
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
services:
|
|
||||||
meilisearch:
|
|
||||||
image: getmeili/meilisearch:${{ needs.define-docker-image.outputs.docker-image }}
|
|
||||||
env:
|
|
||||||
MEILI_MASTER_KEY: ${{ env.MEILI_MASTER_KEY }}
|
|
||||||
MEILI_NO_ANALYTICS: ${{ env.MEILI_NO_ANALYTICS }}
|
|
||||||
ports:
|
|
||||||
- '7700:7700'
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v4
|
|
||||||
with:
|
|
||||||
repository: meilisearch/meilisearch-rails
|
|
||||||
- name: Set up Ruby 3
|
|
||||||
uses: ruby/setup-ruby@v1
|
|
||||||
with:
|
|
||||||
ruby-version: 3
|
|
||||||
bundler-cache: true
|
|
||||||
- name: Run tests
|
|
||||||
run: bundle exec rspec
|
|
||||||
|
|
||||||
meilisearch-symfony-tests:
|
|
||||||
needs: define-docker-image
|
|
||||||
name: meilisearch-symfony tests
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
services:
|
|
||||||
meilisearch:
|
|
||||||
image: getmeili/meilisearch:${{ needs.define-docker-image.outputs.docker-image }}
|
|
||||||
env:
|
|
||||||
MEILI_MASTER_KEY: ${{ env.MEILI_MASTER_KEY }}
|
|
||||||
MEILI_NO_ANALYTICS: ${{ env.MEILI_NO_ANALYTICS }}
|
|
||||||
ports:
|
|
||||||
- '7700:7700'
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v4
|
|
||||||
with:
|
|
||||||
repository: meilisearch/meilisearch-symfony
|
|
||||||
- name: Install PHP
|
|
||||||
uses: shivammathur/setup-php@v2
|
|
||||||
with:
|
|
||||||
tools: composer:v2, flex
|
|
||||||
- name: Validate composer.json and composer.lock
|
|
||||||
run: composer validate
|
|
||||||
- name: Install dependencies
|
|
||||||
run: composer install --prefer-dist --no-progress --quiet
|
|
||||||
- name: Remove doctrine/annotations
|
|
||||||
run: composer remove --dev doctrine/annotations
|
|
||||||
- name: Run test suite
|
|
||||||
run: composer test:unit
|
|
||||||
|
52
.github/workflows/test-suite.yml
vendored
52
.github/workflows/test-suite.yml
vendored
@ -30,13 +30,20 @@ jobs:
|
|||||||
run: |
|
run: |
|
||||||
apt-get update && apt-get install -y curl
|
apt-get update && apt-get install -y curl
|
||||||
apt-get install build-essential -y
|
apt-get install build-essential -y
|
||||||
- name: Setup test with Rust stable
|
- name: Run test with Rust stable
|
||||||
|
if: github.event_name != 'schedule'
|
||||||
uses: actions-rs/toolchain@v1
|
uses: actions-rs/toolchain@v1
|
||||||
with:
|
with:
|
||||||
toolchain: stable
|
toolchain: stable
|
||||||
override: true
|
override: true
|
||||||
|
- name: Run test with Rust nightly
|
||||||
|
if: github.event_name == 'schedule'
|
||||||
|
uses: actions-rs/toolchain@v1
|
||||||
|
with:
|
||||||
|
toolchain: nightly
|
||||||
|
override: true
|
||||||
- name: Cache dependencies
|
- name: Cache dependencies
|
||||||
uses: Swatinem/rust-cache@v2.7.1
|
uses: Swatinem/rust-cache@v2.4.0
|
||||||
- name: Run cargo check without any default features
|
- name: Run cargo check without any default features
|
||||||
uses: actions-rs/cargo@v1
|
uses: actions-rs/cargo@v1
|
||||||
with:
|
with:
|
||||||
@ -58,11 +65,7 @@ jobs:
|
|||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v3
|
||||||
- name: Cache dependencies
|
- name: Cache dependencies
|
||||||
uses: Swatinem/rust-cache@v2.7.1
|
uses: Swatinem/rust-cache@v2.4.0
|
||||||
- uses: actions-rs/toolchain@v1
|
|
||||||
with:
|
|
||||||
toolchain: stable
|
|
||||||
override: true
|
|
||||||
- name: Run cargo check without any default features
|
- name: Run cargo check without any default features
|
||||||
uses: actions-rs/cargo@v1
|
uses: actions-rs/cargo@v1
|
||||||
with:
|
with:
|
||||||
@ -75,12 +78,12 @@ jobs:
|
|||||||
args: --locked --release --all
|
args: --locked --release --all
|
||||||
|
|
||||||
test-all-features:
|
test-all-features:
|
||||||
name: Tests almost all features
|
name: Tests all features on cron schedule only
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
container:
|
container:
|
||||||
# Use ubuntu-18.04 to compile with glibc 2.27, which are the production expectations
|
# Use ubuntu-18.04 to compile with glibc 2.27, which are the production expectations
|
||||||
image: ubuntu:18.04
|
image: ubuntu:18.04
|
||||||
if: github.event_name == 'schedule' || github.event_name == 'workflow_dispatch'
|
if: github.event_name == 'schedule'
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v3
|
||||||
- name: Install needed dependencies
|
- name: Install needed dependencies
|
||||||
@ -91,19 +94,23 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
toolchain: stable
|
toolchain: stable
|
||||||
override: true
|
override: true
|
||||||
- name: Run cargo build with almost all features
|
- name: Run cargo build with all features
|
||||||
run: |
|
uses: actions-rs/cargo@v1
|
||||||
cargo build --workspace --locked --release --features "$(cargo xtask list-features --exclude-feature cuda)"
|
with:
|
||||||
- name: Run cargo test with almost all features
|
command: build
|
||||||
run: |
|
args: --workspace --locked --release --all-features
|
||||||
cargo test --workspace --locked --release --features "$(cargo xtask list-features --exclude-feature cuda)"
|
- name: Run cargo test with all features
|
||||||
|
uses: actions-rs/cargo@v1
|
||||||
|
with:
|
||||||
|
command: test
|
||||||
|
args: --workspace --locked --release --all-features
|
||||||
|
|
||||||
test-disabled-tokenization:
|
test-disabled-tokenization:
|
||||||
name: Test disabled tokenization
|
name: Test disabled tokenization
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
container:
|
container:
|
||||||
image: ubuntu:18.04
|
image: ubuntu:18.04
|
||||||
if: github.event_name == 'schedule' || github.event_name == 'workflow_dispatch'
|
if: github.event_name == 'schedule'
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v3
|
||||||
- name: Install needed dependencies
|
- name: Install needed dependencies
|
||||||
@ -116,10 +123,7 @@ jobs:
|
|||||||
override: true
|
override: true
|
||||||
- name: Run cargo tree without default features and check lindera is not present
|
- name: Run cargo tree without default features and check lindera is not present
|
||||||
run: |
|
run: |
|
||||||
if cargo tree -f '{p} {f}' -e normal --no-default-features | grep -vqz lindera; then
|
cargo tree -f '{p} {f}' -e normal --no-default-features | grep lindera -vqz
|
||||||
echo "lindera has been found in the sources and it shouldn't"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
- name: Run cargo tree with default features and check lindera is pressent
|
- name: Run cargo tree with default features and check lindera is pressent
|
||||||
run: |
|
run: |
|
||||||
cargo tree -f '{p} {f}' -e normal | grep lindera -qz
|
cargo tree -f '{p} {f}' -e normal | grep lindera -qz
|
||||||
@ -142,7 +146,7 @@ jobs:
|
|||||||
toolchain: stable
|
toolchain: stable
|
||||||
override: true
|
override: true
|
||||||
- name: Cache dependencies
|
- name: Cache dependencies
|
||||||
uses: Swatinem/rust-cache@v2.7.1
|
uses: Swatinem/rust-cache@v2.4.0
|
||||||
- name: Run tests in debug
|
- name: Run tests in debug
|
||||||
uses: actions-rs/cargo@v1
|
uses: actions-rs/cargo@v1
|
||||||
with:
|
with:
|
||||||
@ -157,11 +161,11 @@ jobs:
|
|||||||
- uses: actions-rs/toolchain@v1
|
- uses: actions-rs/toolchain@v1
|
||||||
with:
|
with:
|
||||||
profile: minimal
|
profile: minimal
|
||||||
toolchain: 1.75.0
|
toolchain: 1.69.0
|
||||||
override: true
|
override: true
|
||||||
components: clippy
|
components: clippy
|
||||||
- name: Cache dependencies
|
- name: Cache dependencies
|
||||||
uses: Swatinem/rust-cache@v2.7.1
|
uses: Swatinem/rust-cache@v2.4.0
|
||||||
- name: Run cargo clippy
|
- name: Run cargo clippy
|
||||||
uses: actions-rs/cargo@v1
|
uses: actions-rs/cargo@v1
|
||||||
with:
|
with:
|
||||||
@ -180,7 +184,7 @@ jobs:
|
|||||||
override: true
|
override: true
|
||||||
components: rustfmt
|
components: rustfmt
|
||||||
- name: Cache dependencies
|
- name: Cache dependencies
|
||||||
uses: Swatinem/rust-cache@v2.7.1
|
uses: Swatinem/rust-cache@v2.4.0
|
||||||
- name: Run cargo fmt
|
- name: Run cargo fmt
|
||||||
# Since we never ran the `build.rs` script in the benchmark directory we are missing one auto-generated import file.
|
# Since we never ran the `build.rs` script in the benchmark directory we are missing one auto-generated import file.
|
||||||
# Since we want to trigger (and fail) this action as fast as possible, instead of building the benchmark crate
|
# Since we want to trigger (and fail) this action as fast as possible, instead of building the benchmark crate
|
||||||
|
2
.gitignore
vendored
2
.gitignore
vendored
@ -9,8 +9,6 @@
|
|||||||
/data.ms
|
/data.ms
|
||||||
/snapshots
|
/snapshots
|
||||||
/dumps
|
/dumps
|
||||||
/bench
|
|
||||||
/_xtask_benchmark.ms
|
|
||||||
|
|
||||||
# Snapshots
|
# Snapshots
|
||||||
## ... large
|
## ... large
|
||||||
|
362
BENCHMARKS.md
362
BENCHMARKS.md
@ -1,362 +0,0 @@
|
|||||||
# Benchmarks
|
|
||||||
|
|
||||||
Currently this repository hosts two kinds of benchmarks:
|
|
||||||
|
|
||||||
1. The older "milli benchmarks", that use [criterion](https://github.com/bheisler/criterion.rs) and live in the "benchmarks" directory.
|
|
||||||
2. The newer "bench" that are workload-based and so split between the [`workloads`](./workloads/) directory and the [`xtask::bench`](./xtask/src/bench/) module.
|
|
||||||
|
|
||||||
This document describes the newer "bench" benchmarks. For more details on the "milli benchmarks", see [benchmarks/README.md](./benchmarks/README.md).
|
|
||||||
|
|
||||||
## Design philosophy for the benchmarks
|
|
||||||
|
|
||||||
The newer "bench" benchmarks are **integration** benchmarks, in the sense that they spawn an actual Meilisearch server and measure its performance end-to-end, including HTTP request overhead.
|
|
||||||
|
|
||||||
Since this is prone to fluctuating, the benchmarks regain a bit of precision by measuring the runtime of the individual spans using the [logging machinery](./CONTRIBUTING.md#logging) of Meilisearch.
|
|
||||||
|
|
||||||
A span roughly translates to a function call. The benchmark runner collects all the spans by name using the [logs route](https://github.com/orgs/meilisearch/discussions/721) and sums their runtime. The processed results are then sent to the [benchmark dashboard](https://bench.meilisearch.dev), which is in charge of storing and presenting the data.
|
|
||||||
|
|
||||||
## Running the benchmarks
|
|
||||||
|
|
||||||
Benchmarks can run locally or in CI.
|
|
||||||
|
|
||||||
### Locally
|
|
||||||
|
|
||||||
#### With a local benchmark dashboard
|
|
||||||
|
|
||||||
The benchmarks dashboard lives in its [own repository](https://github.com/meilisearch/benchboard). We provide binaries for Ubuntu/Debian, but you can build from source for other platforms (MacOS should work as it was developed under that platform).
|
|
||||||
|
|
||||||
Run the `benchboard` binary to create a fresh database of results. By default it will serve the results and the API to gather results on `http://localhost:9001`.
|
|
||||||
|
|
||||||
From the Meilisearch repository, you can then run benchmarks with:
|
|
||||||
|
|
||||||
```sh
|
|
||||||
cargo xtask bench -- workloads/my_workload_1.json ..
|
|
||||||
```
|
|
||||||
|
|
||||||
This command will build and run Meilisearch locally on port 7700, so make sure that this port is available.
|
|
||||||
To run benchmarks on a different commit, just use the usual git command to get back to the desired commit.
|
|
||||||
|
|
||||||
#### Without a local benchmark dashboard
|
|
||||||
|
|
||||||
To work with the raw results, you can also skip using a local benchmark dashboard.
|
|
||||||
|
|
||||||
Run:
|
|
||||||
|
|
||||||
```sh
|
|
||||||
cargo xtask bench --no-dashboard -- workloads/my_workload_1.json workloads/my_workload_2.json ..
|
|
||||||
```
|
|
||||||
|
|
||||||
For processing the results, look at [Looking at benchmark results/Without dashboard](#without-dashboard).
|
|
||||||
|
|
||||||
### In CI
|
|
||||||
|
|
||||||
We have dedicated runners to run workloads on CI. Currently, there are three ways of running the CI:
|
|
||||||
|
|
||||||
1. Automatically, on every push to `main`.
|
|
||||||
2. Manually, by clicking the [`Run workflow`](https://github.com/meilisearch/meilisearch/actions/workflows/bench-manual.yml) button and specifying the target reference (tag, commit or branch) as well as one or multiple workloads to run. The workloads must exist in the Meilisearch repository (conventionally, in the [`workloads`](./workloads/) directory) on the target reference. Globbing (e.g., `workloads/*.json`) works.
|
|
||||||
3. Manually on a PR, by posting a comment containing a `/bench` command, followed by one or multiple workloads to run. Globbing works. The workloads must exist in the Meilisearch repository in the branch of the PR.
|
|
||||||
```
|
|
||||||
/bench workloads/movies*.json /hackernews_1M.json
|
|
||||||
```
|
|
||||||
|
|
||||||
## Looking at benchmark results
|
|
||||||
|
|
||||||
### On the dashboard
|
|
||||||
|
|
||||||
Results are available on the global dashboard used by CI at <https://bench.meilisearch.dev> or on your [local dashboard](#with-a-local-benchmark-dashboard).
|
|
||||||
|
|
||||||
The dashboard homepage presents three sections:
|
|
||||||
|
|
||||||
1. The latest invocations (a call to `cargo xtask bench`, either local or by CI) with their reason (generally set to some helpful link in CI) and their status.
|
|
||||||
2. The latest workloads ran on `main`.
|
|
||||||
3. The latest workloads ran on other references.
|
|
||||||
|
|
||||||
By default, the workload shows the total runtime delta with the latest applicable commit on `main`. The latest applicable commit is the latest commit for workload invocations that do not originate on `main`, and the latest previous commit for workload invocations that originate on `main`.
|
|
||||||
|
|
||||||
You can explicitly request a detailed comparison by span with the `main` branch, the branch or origin, or any previous commit, by clicking the links at the bottom of the workload invocation.
|
|
||||||
|
|
||||||
In the detailed comparison view, the spans are sorted by improvements, regressions, stable (no statistically significant change) and unstable (the span runtime is comparable to its standard deviation).
|
|
||||||
|
|
||||||
You can click on the name of any span to get a box plot comparing the target commit with multiple commits of the selected branch.
|
|
||||||
|
|
||||||
### Without dashboard
|
|
||||||
|
|
||||||
After the workloads are done running, the reports will live in the Meilisearch repository, in the `bench/reports` directory (by default).
|
|
||||||
|
|
||||||
You can then convert these reports into other formats.
|
|
||||||
|
|
||||||
- To [Firefox profiler](https://profiler.firefox.com) format. Run:
|
|
||||||
```sh
|
|
||||||
cd bench/reports
|
|
||||||
cargo run --release --bin trace-to-firefox -- my_workload_1-0-trace.json
|
|
||||||
```
|
|
||||||
You can then upload the resulting `firefox-my_workload_1-0-trace.json` file to the online profiler.
|
|
||||||
|
|
||||||
|
|
||||||
## Designing benchmark workloads
|
|
||||||
|
|
||||||
Benchmark workloads conventionally live in the `workloads` directory of the Meilisearch repository.
|
|
||||||
|
|
||||||
They are JSON files with the following structure (comments are not actually supported, to make your own, remove them or copy some existing workload file):
|
|
||||||
|
|
||||||
```jsonc
|
|
||||||
{
|
|
||||||
// Name of the workload. Must be unique to the workload, as it will be used to group results on the dashboard.
|
|
||||||
"name": "hackernews.ndjson_1M,no-threads",
|
|
||||||
// Number of consecutive runs of the commands that should be performed.
|
|
||||||
// Each run uses a fresh instance of Meilisearch and a fresh database.
|
|
||||||
// Each run produces its own report file.
|
|
||||||
"run_count": 3,
|
|
||||||
// List of arguments to add to the Meilisearch command line.
|
|
||||||
"extra_cli_args": ["--max-indexing-threads=1"],
|
|
||||||
// List of named assets that can be used in the commands.
|
|
||||||
"assets": {
|
|
||||||
// name of the asset.
|
|
||||||
// Must be unique at the workload level.
|
|
||||||
// For better results, the same asset (same sha256) should have the same name accross workloads.
|
|
||||||
// Having multiple assets with the same name and distinct hashes is supported accross workloads,
|
|
||||||
// but will lead to superfluous downloads.
|
|
||||||
//
|
|
||||||
// Assets are stored in the `bench/assets/` directory by default.
|
|
||||||
"hackernews-100_000.ndjson": {
|
|
||||||
// If the assets exists in the local filesystem (Meilisearch repository or for your local workloads)
|
|
||||||
// Its file path can be specified here.
|
|
||||||
// `null` if the asset should be downloaded from a remote location.
|
|
||||||
"local_location": null,
|
|
||||||
// URL of the remote location where the asset can be downloaded.
|
|
||||||
// Use the `--assets-key` of the runner to pass an API key in the `Authorization: Bearer` header of the download requests.
|
|
||||||
// `null` if the asset should be imported from a local location.
|
|
||||||
// if both local and remote locations are specified, then the local one is tried first, then the remote one
|
|
||||||
// if the file is locally missing or its hash differs.
|
|
||||||
"remote_location": "https://milli-benchmarks.fra1.digitaloceanspaces.com/bench/datasets/hackernews/hackernews-100_000.ndjson",
|
|
||||||
// SHA256 of the asset.
|
|
||||||
// Optional, the `sha256` of the asset will be displayed during a run of the workload if it is missing.
|
|
||||||
// If present, the hash of the asset in the `bench/assets/` directory will be compared against this hash before
|
|
||||||
// running the workload. If the hashes differ, the asset will be downloaded anew.
|
|
||||||
"sha256": "60ecd23485d560edbd90d9ca31f0e6dba1455422f2a44e402600fbb5f7f1b213",
|
|
||||||
// Optional, one of "Auto", "Json", "NdJson" or "Raw".
|
|
||||||
// If missing, assumed to be "Auto".
|
|
||||||
// If "Auto", the format will be determined from the extension in the asset name.
|
|
||||||
"format": "NdJson"
|
|
||||||
},
|
|
||||||
"hackernews-200_000.ndjson": {
|
|
||||||
"local_location": null,
|
|
||||||
"remote_location": "https://milli-benchmarks.fra1.digitaloceanspaces.com/bench/datasets/hackernews/hackernews-200_000.ndjson",
|
|
||||||
"sha256": "785b0271fdb47cba574fab617d5d332276b835c05dd86e4a95251cf7892a1685"
|
|
||||||
},
|
|
||||||
"hackernews-300_000.ndjson": {
|
|
||||||
"local_location": null,
|
|
||||||
"remote_location": "https://milli-benchmarks.fra1.digitaloceanspaces.com/bench/datasets/hackernews/hackernews-300_000.ndjson",
|
|
||||||
"sha256": "de73c7154652eddfaf69cdc3b2f824d5c452f095f40a20a1c97bb1b5c4d80ab2"
|
|
||||||
},
|
|
||||||
"hackernews-400_000.ndjson": {
|
|
||||||
"local_location": null,
|
|
||||||
"remote_location": "https://milli-benchmarks.fra1.digitaloceanspaces.com/bench/datasets/hackernews/hackernews-400_000.ndjson",
|
|
||||||
"sha256": "c1b00a24689110f366447e434c201c086d6f456d54ed1c4995894102794d8fe7"
|
|
||||||
},
|
|
||||||
"hackernews-500_000.ndjson": {
|
|
||||||
"local_location": null,
|
|
||||||
"remote_location": "https://milli-benchmarks.fra1.digitaloceanspaces.com/bench/datasets/hackernews/hackernews-500_000.ndjson",
|
|
||||||
"sha256": "ae98f9dbef8193d750e3e2dbb6a91648941a1edca5f6e82c143e7996f4840083"
|
|
||||||
},
|
|
||||||
"hackernews-600_000.ndjson": {
|
|
||||||
"local_location": null,
|
|
||||||
"remote_location": "https://milli-benchmarks.fra1.digitaloceanspaces.com/bench/datasets/hackernews/hackernews-600_000.ndjson",
|
|
||||||
"sha256": "b495fdc72c4a944801f786400f22076ab99186bee9699f67cbab2f21f5b74dbe"
|
|
||||||
},
|
|
||||||
"hackernews-700_000.ndjson": {
|
|
||||||
"local_location": null,
|
|
||||||
"remote_location": "https://milli-benchmarks.fra1.digitaloceanspaces.com/bench/datasets/hackernews/hackernews-700_000.ndjson",
|
|
||||||
"sha256": "4b2c63974f3dabaa4954e3d4598b48324d03c522321ac05b0d583f36cb78a28b"
|
|
||||||
},
|
|
||||||
"hackernews-800_000.ndjson": {
|
|
||||||
"local_location": null,
|
|
||||||
"remote_location": "https://milli-benchmarks.fra1.digitaloceanspaces.com/bench/datasets/hackernews/hackernews-800_000.ndjson",
|
|
||||||
"sha256": "cb7b6afe0e6caa1be111be256821bc63b0771b2a0e1fad95af7aaeeffd7ba546"
|
|
||||||
},
|
|
||||||
"hackernews-900_000.ndjson": {
|
|
||||||
"local_location": null,
|
|
||||||
"remote_location": "https://milli-benchmarks.fra1.digitaloceanspaces.com/bench/datasets/hackernews/hackernews-900_000.ndjson",
|
|
||||||
"sha256": "e1154ddcd398f1c867758a93db5bcb21a07b9e55530c188a2917fdef332d3ba9"
|
|
||||||
},
|
|
||||||
"hackernews-1_000_000.ndjson": {
|
|
||||||
"local_location": null,
|
|
||||||
"remote_location": "https://milli-benchmarks.fra1.digitaloceanspaces.com/bench/datasets/hackernews/hackernews-1_000_000.ndjson",
|
|
||||||
"sha256": "27e25efd0b68b159b8b21350d9af76938710cb29ce0393fa71b41c4f3c630ffe"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
// Core of the workload.
|
|
||||||
// A list of commands to run sequentially.
|
|
||||||
// A command is a request to the Meilisearch instance that is executed while the profiling runs.
|
|
||||||
"commands": [
|
|
||||||
{
|
|
||||||
// Meilisearch route to call. `http://localhost:7700/` will be prepended.
|
|
||||||
"route": "indexes/movies/settings",
|
|
||||||
// HTTP method to call.
|
|
||||||
"method": "PATCH",
|
|
||||||
// If applicable, body of the request.
|
|
||||||
// Optional, if missing, the body will be empty.
|
|
||||||
"body": {
|
|
||||||
// One of "empty", "inline" or "asset".
|
|
||||||
// If using "empty", you can skip the entire "body" key.
|
|
||||||
"inline": {
|
|
||||||
// when "inline" is used, the body is the JSON object that is the value of the `"inline"` key.
|
|
||||||
"displayedAttributes": [
|
|
||||||
"title",
|
|
||||||
"by",
|
|
||||||
"score",
|
|
||||||
"time"
|
|
||||||
],
|
|
||||||
"searchableAttributes": [
|
|
||||||
"title"
|
|
||||||
],
|
|
||||||
"filterableAttributes": [
|
|
||||||
"by"
|
|
||||||
],
|
|
||||||
"sortableAttributes": [
|
|
||||||
"score",
|
|
||||||
"time"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
// Whether to wait before running the next request.
|
|
||||||
// One of:
|
|
||||||
// - DontWait: run the next command without waiting the response to this one.
|
|
||||||
// - WaitForResponse: run the next command as soon as the response from the server is received.
|
|
||||||
// - WaitForTask: run the next command once **all** the Meilisearch tasks created up to now have finished processing.
|
|
||||||
"synchronous": "DontWait"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"route": "indexes/movies/documents",
|
|
||||||
"method": "POST",
|
|
||||||
"body": {
|
|
||||||
// When using "asset", use the name of an asset as value to use the content of that asset as body.
|
|
||||||
// the content type is derived of the format of the asset:
|
|
||||||
// "NdJson" => "application/x-ndjson"
|
|
||||||
// "Json" => "application/json"
|
|
||||||
// "Raw" => "application/octet-stream"
|
|
||||||
// See [AssetFormat::to_content_type](https://github.com/meilisearch/meilisearch/blob/7b670a4afadb132ac4a01b6403108700501a391d/xtask/src/bench/assets.rs#L30)
|
|
||||||
// for details and up-to-date list.
|
|
||||||
"asset": "hackernews-100_000.ndjson"
|
|
||||||
},
|
|
||||||
"synchronous": "WaitForTask"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"route": "indexes/movies/documents",
|
|
||||||
"method": "POST",
|
|
||||||
"body": {
|
|
||||||
"asset": "hackernews-200_000.ndjson"
|
|
||||||
},
|
|
||||||
"synchronous": "WaitForResponse"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"route": "indexes/movies/documents",
|
|
||||||
"method": "POST",
|
|
||||||
"body": {
|
|
||||||
"asset": "hackernews-300_000.ndjson"
|
|
||||||
},
|
|
||||||
"synchronous": "WaitForResponse"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"route": "indexes/movies/documents",
|
|
||||||
"method": "POST",
|
|
||||||
"body": {
|
|
||||||
"asset": "hackernews-400_000.ndjson"
|
|
||||||
},
|
|
||||||
"synchronous": "WaitForResponse"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"route": "indexes/movies/documents",
|
|
||||||
"method": "POST",
|
|
||||||
"body": {
|
|
||||||
"asset": "hackernews-500_000.ndjson"
|
|
||||||
},
|
|
||||||
"synchronous": "WaitForResponse"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"route": "indexes/movies/documents",
|
|
||||||
"method": "POST",
|
|
||||||
"body": {
|
|
||||||
"asset": "hackernews-600_000.ndjson"
|
|
||||||
},
|
|
||||||
"synchronous": "WaitForResponse"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"route": "indexes/movies/documents",
|
|
||||||
"method": "POST",
|
|
||||||
"body": {
|
|
||||||
"asset": "hackernews-700_000.ndjson"
|
|
||||||
},
|
|
||||||
"synchronous": "WaitForResponse"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"route": "indexes/movies/documents",
|
|
||||||
"method": "POST",
|
|
||||||
"body": {
|
|
||||||
"asset": "hackernews-800_000.ndjson"
|
|
||||||
},
|
|
||||||
"synchronous": "WaitForResponse"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"route": "indexes/movies/documents",
|
|
||||||
"method": "POST",
|
|
||||||
"body": {
|
|
||||||
"asset": "hackernews-900_000.ndjson"
|
|
||||||
},
|
|
||||||
"synchronous": "WaitForResponse"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"route": "indexes/movies/documents",
|
|
||||||
"method": "POST",
|
|
||||||
"body": {
|
|
||||||
"asset": "hackernews-1_000_000.ndjson"
|
|
||||||
},
|
|
||||||
"synchronous": "WaitForTask"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Adding new assets
|
|
||||||
|
|
||||||
Assets reside in our DigitalOcean S3 space. Assuming you have team access to the DigitalOcean S3 space:
|
|
||||||
|
|
||||||
1. go to <https://cloud.digitalocean.com/spaces/milli-benchmarks?i=d1c552&path=bench%2Fdatasets%2F>
|
|
||||||
2. upload your dataset:
|
|
||||||
1. if your dataset is a single file, upload that single file using the "upload" button,
|
|
||||||
2. otherwise, create a folder using the "create folder" button, then inside that folder upload your individual files.
|
|
||||||
|
|
||||||
## Upgrading `https://bench.meilisearch.dev`
|
|
||||||
|
|
||||||
The URL of the server is in our password manager (look for "benchboard").
|
|
||||||
|
|
||||||
1. Make the needed modifications on the [benchboard repository](https://github.com/meilisearch/benchboard) and merge them to main.
|
|
||||||
2. Publish a new release to produce the Ubuntu/Debian binary.
|
|
||||||
3. Download the binary locally, send it to the server:
|
|
||||||
```
|
|
||||||
scp -6 ~/Downloads/benchboard root@\[<ipv6-address>\]:/bench/new-benchboard
|
|
||||||
```
|
|
||||||
Note that the ipv6 must be between escaped square brackets for SCP.
|
|
||||||
4. SSH to the server:
|
|
||||||
```
|
|
||||||
ssh root@<ipv6-address>
|
|
||||||
```
|
|
||||||
Note the ipv6 must **NOT** be between escaped square brackets for SSH 🥲
|
|
||||||
5. On the server, set the correct permissions for the new binary:
|
|
||||||
```
|
|
||||||
chown bench:bench /bench/new-benchboard
|
|
||||||
chmod 700 /bench/new-benchboard
|
|
||||||
```
|
|
||||||
6. On the server, move the new binary to the location of the running binary (if unsure, start by making a backup of the running binary):
|
|
||||||
```
|
|
||||||
mv /bench/{new-,}benchboard
|
|
||||||
```
|
|
||||||
7. Restart the benchboard service.
|
|
||||||
```
|
|
||||||
systemctl restart benchboard
|
|
||||||
```
|
|
||||||
8. Check that the service runs correctly.
|
|
||||||
```
|
|
||||||
systemctl status benchboard
|
|
||||||
```
|
|
||||||
9. Check the availability of the service by going to <https://bench.meilisearch.dev> on your browser.
|
|
@ -4,7 +4,7 @@ First, thank you for contributing to Meilisearch! The goal of this document is t
|
|||||||
|
|
||||||
Remember that there are many ways to contribute other than writing code: writing [tutorials or blog posts](https://github.com/meilisearch/awesome-meilisearch), improving [the documentation](https://github.com/meilisearch/documentation), submitting [bug reports](https://github.com/meilisearch/meilisearch/issues/new?assignees=&labels=&template=bug_report.md&title=) and [feature requests](https://github.com/meilisearch/product/discussions/categories/feedback-feature-proposal)...
|
Remember that there are many ways to contribute other than writing code: writing [tutorials or blog posts](https://github.com/meilisearch/awesome-meilisearch), improving [the documentation](https://github.com/meilisearch/documentation), submitting [bug reports](https://github.com/meilisearch/meilisearch/issues/new?assignees=&labels=&template=bug_report.md&title=) and [feature requests](https://github.com/meilisearch/product/discussions/categories/feedback-feature-proposal)...
|
||||||
|
|
||||||
Meilisearch can manage multiple indexes, handle the update store, and expose an HTTP API. Search and indexation are the domain of our core engine, [`milli`](https://github.com/meilisearch/meilisearch/tree/main/milli), while tokenization is handled by [our `charabia` library](https://github.com/meilisearch/charabia/).
|
The code in this repository is only concerned with managing multiple indexes, handling the update store, and exposing an HTTP API. Search and indexation are the domain of our core engine, [`milli`](https://github.com/meilisearch/milli), while tokenization is handled by [our `charabia` library](https://github.com/meilisearch/charabia/).
|
||||||
|
|
||||||
If Meilisearch does not offer optimized support for your language, please consider contributing to `charabia` by following the [CONTRIBUTING.md file](https://github.com/meilisearch/charabia/blob/main/CONTRIBUTING.md) and integrating your intended normalizer/segmenter.
|
If Meilisearch does not offer optimized support for your language, please consider contributing to `charabia` by following the [CONTRIBUTING.md file](https://github.com/meilisearch/charabia/blob/main/CONTRIBUTING.md) and integrating your intended normalizer/segmenter.
|
||||||
|
|
||||||
@ -75,36 +75,6 @@ If you get a "Too many open files" error you might want to increase the open fil
|
|||||||
ulimit -Sn 3000
|
ulimit -Sn 3000
|
||||||
```
|
```
|
||||||
|
|
||||||
#### Build tools
|
|
||||||
|
|
||||||
Meilisearch follows the [cargo xtask](https://github.com/matklad/cargo-xtask) workflow to provide some build tools.
|
|
||||||
|
|
||||||
Run `cargo xtask --help` from the root of the repository to find out what is available.
|
|
||||||
|
|
||||||
### Logging
|
|
||||||
|
|
||||||
Meilisearch uses [`tracing`](https://lib.rs/crates/tracing) for logging purposes. Tracing logs are structured and can be displayed as JSON to the end user, so prefer passing arguments as fields rather than interpolating them in the message.
|
|
||||||
|
|
||||||
Refer to the [documentation](https://docs.rs/tracing/0.1.40/tracing/index.html#using-the-macros) for the syntax of the spans and events.
|
|
||||||
|
|
||||||
Logging spans are used for 3 distinct purposes:
|
|
||||||
|
|
||||||
1. Regular logging
|
|
||||||
2. Profiling
|
|
||||||
3. Benchmarking
|
|
||||||
|
|
||||||
As a result, the spans should follow some rules:
|
|
||||||
|
|
||||||
- They should not be put on functions that are called too often. That is because opening and closing a span causes some overhead. For regular logging, avoid putting spans on functions that are taking less than a few hundred nanoseconds. For profiling or benchmarking, avoid putting spans on functions that are taking less than a few microseconds.
|
|
||||||
- For profiling and benchmarking, use the `TRACE` level.
|
|
||||||
- For profiling and benchmarking, use the following `target` prefixes:
|
|
||||||
- `indexing::` for spans meant when profiling the indexing operations.
|
|
||||||
- `search::` for spans meant when profiling the search operations.
|
|
||||||
|
|
||||||
### Benchmarking
|
|
||||||
|
|
||||||
See [BENCHMARKS.md](./BENCHMARKS.md)
|
|
||||||
|
|
||||||
## Git Guidelines
|
## Git Guidelines
|
||||||
|
|
||||||
### Git Branches
|
### Git Branches
|
||||||
|
3214
Cargo.lock
generated
3214
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
12
Cargo.toml
12
Cargo.toml
@ -2,7 +2,6 @@
|
|||||||
resolver = "2"
|
resolver = "2"
|
||||||
members = [
|
members = [
|
||||||
"meilisearch",
|
"meilisearch",
|
||||||
"meilitool",
|
|
||||||
"meilisearch-types",
|
"meilisearch-types",
|
||||||
"meilisearch-auth",
|
"meilisearch-auth",
|
||||||
"meili-snap",
|
"meili-snap",
|
||||||
@ -11,22 +10,17 @@ members = [
|
|||||||
"file-store",
|
"file-store",
|
||||||
"permissive-json-pointer",
|
"permissive-json-pointer",
|
||||||
"milli",
|
"milli",
|
||||||
|
"index-stats",
|
||||||
"filter-parser",
|
"filter-parser",
|
||||||
"flatten-serde-json",
|
"flatten-serde-json",
|
||||||
"json-depth-checker",
|
"json-depth-checker",
|
||||||
"benchmarks",
|
"benchmarks",
|
||||||
"fuzzers",
|
"fuzzers",
|
||||||
"tracing-trace",
|
|
||||||
"xtask",
|
|
||||||
"build-info",
|
|
||||||
]
|
]
|
||||||
|
|
||||||
[workspace.package]
|
[workspace.package]
|
||||||
version = "1.8.0"
|
version = "1.2.0"
|
||||||
authors = [
|
authors = ["Quentin de Quelen <quentin@dequelen.me>", "Clément Renault <clement@meilisearch.com>"]
|
||||||
"Quentin de Quelen <quentin@dequelen.me>",
|
|
||||||
"Clément Renault <clement@meilisearch.com>",
|
|
||||||
]
|
|
||||||
description = "Meilisearch HTTP server"
|
description = "Meilisearch HTTP server"
|
||||||
homepage = "https://meilisearch.com"
|
homepage = "https://meilisearch.com"
|
||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
|
15
Dockerfile
15
Dockerfile
@ -1,14 +1,14 @@
|
|||||||
# Compile
|
# Compile
|
||||||
FROM rust:1.75.0-alpine3.18 AS compiler
|
FROM rust:alpine3.16 AS compiler
|
||||||
|
|
||||||
RUN apk add -q --update-cache --no-cache build-base openssl-dev
|
RUN apk add -q --update-cache --no-cache build-base openssl-dev
|
||||||
|
|
||||||
WORKDIR /
|
WORKDIR /meilisearch
|
||||||
|
|
||||||
ARG COMMIT_SHA
|
ARG COMMIT_SHA
|
||||||
ARG COMMIT_DATE
|
ARG COMMIT_DATE
|
||||||
ARG GIT_TAG
|
ARG GIT_TAG
|
||||||
ENV VERGEN_GIT_SHA=${COMMIT_SHA} VERGEN_GIT_COMMIT_TIMESTAMP=${COMMIT_DATE} VERGEN_GIT_DESCRIBE=${GIT_TAG}
|
ENV VERGEN_GIT_SHA=${COMMIT_SHA} VERGEN_GIT_COMMIT_TIMESTAMP=${COMMIT_DATE} VERGEN_GIT_SEMVER_LIGHTWEIGHT=${GIT_TAG}
|
||||||
ENV RUSTFLAGS="-C target-feature=-crt-static"
|
ENV RUSTFLAGS="-C target-feature=-crt-static"
|
||||||
|
|
||||||
COPY . .
|
COPY . .
|
||||||
@ -17,7 +17,7 @@ RUN set -eux; \
|
|||||||
if [ "$apkArch" = "aarch64" ]; then \
|
if [ "$apkArch" = "aarch64" ]; then \
|
||||||
export JEMALLOC_SYS_WITH_LG_PAGE=16; \
|
export JEMALLOC_SYS_WITH_LG_PAGE=16; \
|
||||||
fi && \
|
fi && \
|
||||||
cargo build --release -p meilisearch -p meilitool
|
cargo build --release
|
||||||
|
|
||||||
# Run
|
# Run
|
||||||
FROM alpine:3.16
|
FROM alpine:3.16
|
||||||
@ -28,10 +28,9 @@ ENV MEILI_SERVER_PROVIDER docker
|
|||||||
RUN apk update --quiet \
|
RUN apk update --quiet \
|
||||||
&& apk add -q --no-cache libgcc tini curl
|
&& apk add -q --no-cache libgcc tini curl
|
||||||
|
|
||||||
# add meilisearch and meilitool to the `/bin` so you can run it from anywhere
|
# add meilisearch to the `/bin` so you can run it from anywhere and it's easy
|
||||||
# and it's easy to find.
|
# to find.
|
||||||
COPY --from=compiler /target/release/meilisearch /bin/meilisearch
|
COPY --from=compiler /meilisearch/target/release/meilisearch /bin/meilisearch
|
||||||
COPY --from=compiler /target/release/meilitool /bin/meilitool
|
|
||||||
# To stay compatible with the older version of the container (pre v0.27.0) we're
|
# To stay compatible with the older version of the container (pre v0.27.0) we're
|
||||||
# going to symlink the meilisearch binary in the path to `/meilisearch`
|
# going to symlink the meilisearch binary in the path to `/meilisearch`
|
||||||
RUN ln -s /bin/meilisearch /meilisearch
|
RUN ln -s /bin/meilisearch /meilisearch
|
||||||
|
2
LICENSE
2
LICENSE
@ -1,6 +1,6 @@
|
|||||||
MIT License
|
MIT License
|
||||||
|
|
||||||
Copyright (c) 2019-2024 Meili SAS
|
Copyright (c) 2019-2022 Meili SAS
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
of this software and associated documentation files (the "Software"), to deal
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
19
PROFILING.md
19
PROFILING.md
@ -1,19 +0,0 @@
|
|||||||
# Profiling Meilisearch
|
|
||||||
|
|
||||||
Search engine technologies are complex pieces of software that require thorough profiling tools. We chose to use [Puffin](https://github.com/EmbarkStudios/puffin), which the Rust gaming industry uses extensively. You can export and import the profiling reports using the top bar's _File_ menu options [in Puffin Viewer](https://github.com/embarkstudios/puffin#ui).
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
## Profiling the Indexing Process
|
|
||||||
|
|
||||||
When you enable [the `exportPuffinReports` experimental feature](https://www.meilisearch.com/docs/learn/experimental/overview) of Meilisearch, Puffin reports with the `.puffin` extension will be automatically exported to disk. When this option is enabled, the engine will automatically create a "frame" whenever it executes the `IndexScheduler::tick` method.
|
|
||||||
|
|
||||||
[Puffin Viewer](https://github.com/EmbarkStudios/puffin/tree/main/puffin_viewer) is used to analyze the reports. Those reports show areas where Meilisearch spent time during indexing.
|
|
||||||
|
|
||||||
Another piece of advice on the Puffin viewer UI interface is to consider the _Merge children with same ID_ option. It can hide the exact actual timings at which events were sent. Please turn it off when you see strange gaps on the Flamegraph. It can help.
|
|
||||||
|
|
||||||
## Profiling the Search Process
|
|
||||||
|
|
||||||
We still need to take the time to profile the search side of the engine with Puffin. It would require time to profile the filtering phase, query parsing, creation, and execution. We could even profile the Actix HTTP server.
|
|
||||||
|
|
||||||
The only issue we see is the framing system. Puffin requires a global frame-based profiling phase, which collides with Meilisearch's ability to accept and answer multiple requests on different threads simultaneously.
|
|
69
README.md
69
README.md
@ -1,20 +1,15 @@
|
|||||||
<p align="center">
|
<p align="center">
|
||||||
<a href="https://www.meilisearch.com/?utm_campaign=oss&utm_source=github&utm_medium=meilisearch&utm_content=logo#gh-light-mode-only" target="_blank">
|
|
||||||
<img src="assets/meilisearch-logo-light.svg?sanitize=true#gh-light-mode-only">
|
<img src="assets/meilisearch-logo-light.svg?sanitize=true#gh-light-mode-only">
|
||||||
</a>
|
|
||||||
<a href="https://www.meilisearch.com/?utm_campaign=oss&utm_source=github&utm_medium=meilisearch&utm_content=logo#gh-dark-mode-only" target="_blank">
|
|
||||||
<img src="assets/meilisearch-logo-dark.svg?sanitize=true#gh-dark-mode-only">
|
<img src="assets/meilisearch-logo-dark.svg?sanitize=true#gh-dark-mode-only">
|
||||||
</a>
|
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<h4 align="center">
|
<h4 align="center">
|
||||||
<a href="https://www.meilisearch.com/?utm_campaign=oss&utm_source=github&utm_medium=meilisearch&utm_content=nav">Website</a> |
|
<a href="https://www.meilisearch.com">Website</a> |
|
||||||
<a href="https://roadmap.meilisearch.com/tabs/1-under-consideration">Roadmap</a> |
|
<a href="https://roadmap.meilisearch.com/tabs/1-under-consideration">Roadmap</a> |
|
||||||
<a href="https://www.meilisearch.com/pricing?utm_campaign=oss&utm_source=github&utm_medium=meilisearch&utm_content=nav">Meilisearch Cloud</a> |
|
<a href="https://blog.meilisearch.com">Blog</a> |
|
||||||
<a href="https://blog.meilisearch.com/?utm_campaign=oss&utm_source=github&utm_medium=meilisearch&utm_content=nav">Blog</a> |
|
<a href="https://www.meilisearch.com/docs">Documentation</a> |
|
||||||
<a href="https://www.meilisearch.com/docs?utm_campaign=oss&utm_source=github&utm_medium=meilisearch&utm_content=nav">Documentation</a> |
|
<a href="https://www.meilisearch.com/docs/faq">FAQ</a> |
|
||||||
<a href="https://www.meilisearch.com/docs/faq?utm_campaign=oss&utm_source=github&utm_medium=meilisearch&utm_content=nav">FAQ</a> |
|
<a href="https://discord.meilisearch.com">Discord</a>
|
||||||
<a href="https://discord.meilisearch.com/?utm_campaign=oss&utm_source=github&utm_medium=meilisearch&utm_content=nav">Discord</a>
|
|
||||||
</h4>
|
</h4>
|
||||||
|
|
||||||
<p align="center">
|
<p align="center">
|
||||||
@ -28,70 +23,72 @@
|
|||||||
Meilisearch helps you shape a delightful search experience in a snap, offering features that work out-of-the-box to speed up your workflow.
|
Meilisearch helps you shape a delightful search experience in a snap, offering features that work out-of-the-box to speed up your workflow.
|
||||||
|
|
||||||
<p align="center" name="demo">
|
<p align="center" name="demo">
|
||||||
<a href="https://where2watch.meilisearch.com/?utm_campaign=oss&utm_source=github&utm_medium=meilisearch&utm_content=demo-gif#gh-light-mode-only" target="_blank">
|
<a href="https://where2watch.meilisearch.com/#gh-light-mode-only" target="_blank">
|
||||||
<img src="assets/demo-light.gif#gh-light-mode-only" alt="A bright colored application for finding movies screening near the user">
|
<img src="assets/demo-light.gif#gh-light-mode-only" alt="A bright colored application for finding movies screening near the user">
|
||||||
</a>
|
</a>
|
||||||
<a href="https://where2watch.meilisearch.com/?utm_campaign=oss&utm_source=github&utm_medium=meilisearch&utm_content=demo-gif#gh-dark-mode-only" target="_blank">
|
<a href="https://where2watch.meilisearch.com/#gh-dark-mode-only" target="_blank">
|
||||||
<img src="assets/demo-dark.gif#gh-dark-mode-only" alt="A dark colored application for finding movies screening near the user">
|
<img src="assets/demo-dark.gif#gh-dark-mode-only" alt="A dark colored application for finding movies screening near the user">
|
||||||
</a>
|
</a>
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
🔥 [**Try it!**](https://where2watch.meilisearch.com/?utm_campaign=oss&utm_source=github&utm_medium=meilisearch&utm_content=demo-link) 🔥
|
🔥 [**Try it!**](https://where2watch.meilisearch.com/) 🔥
|
||||||
|
|
||||||
## ✨ Features
|
## ✨ Features
|
||||||
|
|
||||||
- **Search-as-you-type:** find search results in less than 50 milliseconds
|
- **Search-as-you-type:** find search results in less than 50 milliseconds
|
||||||
- **[Typo tolerance](https://www.meilisearch.com/docs/learn/configuration/typo_tolerance?utm_campaign=oss&utm_source=github&utm_medium=meilisearch&utm_content=features):** get relevant matches even when queries contain typos and misspellings
|
- **[Typo tolerance](https://www.meilisearch.com/docs/learn/getting_started/customizing_relevancy#typo-tolerance):** get relevant matches even when queries contain typos and misspellings
|
||||||
- **[Filtering](https://www.meilisearch.com/docs/learn/fine_tuning_results/filtering?utm_campaign=oss&utm_source=github&utm_medium=meilisearch&utm_content=features) and [faceted search](https://www.meilisearch.com/docs/learn/fine_tuning_results/faceted_search?utm_campaign=oss&utm_source=github&utm_medium=meilisearch&utm_content=features):** enhance your users' search experience with custom filters and build a faceted search interface in a few lines of code
|
- **[Filtering](https://www.meilisearch.com/docs/learn/advanced/filtering) and [faceted search](https://www.meilisearch.com/docs/learn/advanced/faceted_search):** enhance your user's search experience with custom filters and build a faceted search interface in a few lines of code
|
||||||
- **[Sorting](https://www.meilisearch.com/docs/learn/fine_tuning_results/sorting?utm_campaign=oss&utm_source=github&utm_medium=meilisearch&utm_content=features):** sort results based on price, date, or pretty much anything else your users need
|
- **[Sorting](https://www.meilisearch.com/docs/learn/advanced/sorting):** sort results based on price, date, or pretty much anything else your users need
|
||||||
- **[Synonym support](https://www.meilisearch.com/docs/learn/configuration/synonyms?utm_campaign=oss&utm_source=github&utm_medium=meilisearch&utm_content=features):** configure synonyms to include more relevant content in your search results
|
- **[Synonym support](https://www.meilisearch.com/docs/learn/getting_started/customizing_relevancy#synonyms):** configure synonyms to include more relevant content in your search results
|
||||||
- **[Geosearch](https://www.meilisearch.com/docs/learn/fine_tuning_results/geosearch?utm_campaign=oss&utm_source=github&utm_medium=meilisearch&utm_content=features):** filter and sort documents based on geographic data
|
- **[Geosearch](https://www.meilisearch.com/docs/learn/advanced/geosearch):** filter and sort documents based on geographic data
|
||||||
- **[Extensive language support](https://www.meilisearch.com/docs/learn/what_is_meilisearch/language?utm_campaign=oss&utm_source=github&utm_medium=meilisearch&utm_content=features):** search datasets in any language, with optimized support for Chinese, Japanese, Hebrew, and languages using the Latin alphabet
|
- **[Extensive language support](https://www.meilisearch.com/docs/learn/what_is_meilisearch/language):** search datasets in any language, with optimized support for Chinese, Japanese, Hebrew, and languages using the Latin alphabet
|
||||||
- **[Security management](https://www.meilisearch.com/docs/learn/security/master_api_keys?utm_campaign=oss&utm_source=github&utm_medium=meilisearch&utm_content=features):** control which users can access what data with API keys that allow fine-grained permissions handling
|
- **[Security management](https://www.meilisearch.com/docs/learn/security/master_api_keys):** control which users can access what data with API keys that allow fine-grained permissions handling
|
||||||
- **[Multi-Tenancy](https://www.meilisearch.com/docs/learn/security/tenant_tokens?utm_campaign=oss&utm_source=github&utm_medium=meilisearch&utm_content=features):** personalize search results for any number of application tenants
|
- **[Multi-Tenancy](https://www.meilisearch.com/docs/learn/security/tenant_tokens):** personalize search results for any number of application tenants
|
||||||
- **Highly Customizable:** customize Meilisearch to your specific needs or use our out-of-the-box and hassle-free presets
|
- **Highly Customizable:** customize Meilisearch to your specific needs or use our out-of-the-box and hassle-free presets
|
||||||
- **[RESTful API](https://www.meilisearch.com/docs/reference/api/overview?utm_campaign=oss&utm_source=github&utm_medium=meilisearch&utm_content=features):** integrate Meilisearch in your technical stack with our plugins and SDKs
|
- **[RESTful API](https://www.meilisearch.com/docs/reference/api/overview):** integrate Meilisearch in your technical stack with our plugins and SDKs
|
||||||
- **Easy to install, deploy, and maintain**
|
- **Easy to install, deploy, and maintain**
|
||||||
|
|
||||||
## 📖 Documentation
|
## 📖 Documentation
|
||||||
|
|
||||||
You can consult Meilisearch's documentation at [https://www.meilisearch.com/docs](https://www.meilisearch.com/docs/?utm_campaign=oss&utm_source=github&utm_medium=meilisearch&utm_content=docs).
|
You can consult Meilisearch's documentation at [https://www.meilisearch.com/docs](https://www.meilisearch.com/docs/).
|
||||||
|
|
||||||
## 🚀 Getting started
|
## 🚀 Getting started
|
||||||
|
|
||||||
For basic instructions on how to set up Meilisearch, add documents to an index, and search for documents, take a look at our [Quick Start](https://www.meilisearch.com/docs/learn/getting_started/quick_start?utm_campaign=oss&utm_source=github&utm_medium=meilisearch&utm_content=get-started) guide.
|
For basic instructions on how to set up Meilisearch, add documents to an index, and search for documents, take a look at our [Quick Start](https://www.meilisearch.com/docs/learn/getting_started/quick_start) guide.
|
||||||
|
|
||||||
## ⚡ Supercharge your Meilisearch experience
|
You may also want to check out [Meilisearch 101](https://www.meilisearch.com/docs/learn/getting_started/filtering_and_sorting) for an introduction to some of Meilisearch's most popular features.
|
||||||
|
|
||||||
Say goodbye to server deployment and manual updates with [Meilisearch Cloud](https://www.meilisearch.com/cloud?utm_campaign=oss&utm_source=github&utm_medium=meilisearch). No credit card required.
|
## ☁️ Meilisearch cloud
|
||||||
|
|
||||||
|
Let us manage your infrastructure so you can focus on integrating a great search experience. Try [Meilisearch Cloud](https://meilisearch.com/pricing) today.
|
||||||
|
|
||||||
## 🧰 SDKs & integration tools
|
## 🧰 SDKs & integration tools
|
||||||
|
|
||||||
Install one of our SDKs in your project for seamless integration between Meilisearch and your favorite language or framework!
|
Install one of our SDKs in your project for seamless integration between Meilisearch and your favorite language or framework!
|
||||||
|
|
||||||
Take a look at the complete [Meilisearch integration list](https://www.meilisearch.com/docs/learn/what_is_meilisearch/sdks?utm_campaign=oss&utm_source=github&utm_medium=meilisearch&utm_content=sdks-link).
|
Take a look at the complete [Meilisearch integration list](https://www.meilisearch.com/docs/learn/what_is_meilisearch/sdks).
|
||||||
|
|
||||||
[](https://www.meilisearch.com/docs/learn/what_is_meilisearch/sdks?utm_campaign=oss&utm_source=github&utm_medium=meilisearch&utm_content=sdks-logos)
|
[](https://www.meilisearch.com/docs/learn/what_is_meilisearch/sdks)
|
||||||
|
|
||||||
## ⚙️ Advanced usage
|
## ⚙️ Advanced usage
|
||||||
|
|
||||||
Experienced users will want to keep our [API Reference](https://www.meilisearch.com/docs/reference/api/overview?utm_campaign=oss&utm_source=github&utm_medium=meilisearch&utm_content=advanced) close at hand.
|
Experienced users will want to keep our [API Reference](https://www.meilisearch.com/docs/reference/api/overview) close at hand.
|
||||||
|
|
||||||
We also offer a wide range of dedicated guides to all Meilisearch features, such as [filtering](https://www.meilisearch.com/docs/learn/fine_tuning_results/filtering?utm_campaign=oss&utm_source=github&utm_medium=meilisearch&utm_content=advanced), [sorting](https://www.meilisearch.com/docs/learn/fine_tuning_results/sorting?utm_campaign=oss&utm_source=github&utm_medium=meilisearch&utm_content=advanced), [geosearch](https://www.meilisearch.com/docs/learn/fine_tuning_results/geosearch?utm_campaign=oss&utm_source=github&utm_medium=meilisearch&utm_content=advanced), [API keys](https://www.meilisearch.com/docs/learn/security/master_api_keys?utm_campaign=oss&utm_source=github&utm_medium=meilisearch&utm_content=advanced), and [tenant tokens](https://www.meilisearch.com/docs/learn/security/tenant_tokens?utm_campaign=oss&utm_source=github&utm_medium=meilisearch&utm_content=advanced).
|
We also offer a wide range of dedicated guides to all Meilisearch features, such as [filtering](https://www.meilisearch.com/docs/learn/advanced/filtering), [sorting](https://www.meilisearch.com/docs/learn/advanced/sorting), [geosearch](https://www.meilisearch.com/docs/learn/advanced/geosearch), [API keys](https://www.meilisearch.com/docs/learn/security/master_api_keys), and [tenant tokens](https://www.meilisearch.com/docs/learn/security/tenant_tokens).
|
||||||
|
|
||||||
Finally, for more in-depth information, refer to our articles explaining fundamental Meilisearch concepts such as [documents](https://www.meilisearch.com/docs/learn/core_concepts/documents?utm_campaign=oss&utm_source=github&utm_medium=meilisearch&utm_content=advanced) and [indexes](https://www.meilisearch.com/docs/learn/core_concepts/indexes?utm_campaign=oss&utm_source=github&utm_medium=meilisearch&utm_content=advanced).
|
Finally, for more in-depth information, refer to our articles explaining fundamental Meilisearch concepts such as [documents](https://www.meilisearch.com/docs/learn/core_concepts/documents) and [indexes](https://www.meilisearch.com/docs/learn/core_concepts/indexes).
|
||||||
|
|
||||||
## 📊 Telemetry
|
## 📊 Telemetry
|
||||||
|
|
||||||
Meilisearch collects **anonymized** data from users to help us improve our product. You can [deactivate this](https://www.meilisearch.com/docs/learn/what_is_meilisearch/telemetry?utm_campaign=oss&utm_source=github&utm_medium=meilisearch&utm_content=telemetry#how-to-disable-data-collection) whenever you want.
|
Meilisearch collects **anonymized** data from users to help us improve our product. You can [deactivate this](https://www.meilisearch.com/docs/learn/what_is_meilisearch/telemetry#how-to-disable-data-collection) whenever you want.
|
||||||
|
|
||||||
To request deletion of collected data, please write to us at [privacy@meilisearch.com](mailto:privacy@meilisearch.com). Don't forget to include your `Instance UID` in the message, as this helps us quickly find and delete your data.
|
To request deletion of collected data, please write to us at [privacy@meilisearch.com](mailto:privacy@meilisearch.com). Don't forget to include your `Instance UID` in the message, as this helps us quickly find and delete your data.
|
||||||
|
|
||||||
If you want to know more about the kind of data we collect and what we use it for, check the [telemetry section](https://www.meilisearch.com/docs/learn/what_is_meilisearch/telemetry?utm_campaign=oss&utm_source=github&utm_medium=meilisearch&utm_content=telemetry#how-to-disable-data-collection) of our documentation.
|
If you want to know more about the kind of data we collect and what we use it for, check the [telemetry section](https://www.meilisearch.com/docs/learn/what_is_meilisearch/telemetry) of our documentation.
|
||||||
|
|
||||||
## 📫 Get in touch!
|
## 📫 Get in touch!
|
||||||
|
|
||||||
Meilisearch is a search engine created by [Meili](https://www.welcometothejungle.com/en/companies/meilisearch), a software development company based in France and with team members all over the world. Want to know more about us? [Check out our blog!](https://blog.meilisearch.com/?utm_campaign=oss&utm_source=github&utm_medium=meilisearch&utm_content=contact)
|
Meilisearch is a search engine created by [Meili](https://www.welcometothejungle.com/en/companies/meilisearch), a software development company based in France and with team members all over the world. Want to know more about us? [Check out our blog!](https://blog.meilisearch.com/)
|
||||||
|
|
||||||
🗞 [Subscribe to our newsletter](https://meilisearch.us2.list-manage.com/subscribe?u=27870f7b71c908a8b359599fb&id=79582d828e) if you don't want to miss any updates! We promise we won't clutter your mailbox: we only send one edition every two months.
|
🗞 [Subscribe to our newsletter](https://meilisearch.us2.list-manage.com/subscribe?u=27870f7b71c908a8b359599fb&id=79582d828e) if you don't want to miss any updates! We promise we won't clutter your mailbox: we only send one edition every two months.
|
||||||
|
|
||||||
@ -99,7 +96,7 @@ Meilisearch is a search engine created by [Meili](https://www.welcometothejungle
|
|||||||
|
|
||||||
- For feature requests, please visit our [product repository](https://github.com/meilisearch/product/discussions)
|
- For feature requests, please visit our [product repository](https://github.com/meilisearch/product/discussions)
|
||||||
- Found a bug? Open an [issue](https://github.com/meilisearch/meilisearch/issues)!
|
- Found a bug? Open an [issue](https://github.com/meilisearch/meilisearch/issues)!
|
||||||
- Want to be part of our Discord community? [Join us!](https://discord.meilisearch.com/?utm_campaign=oss&utm_source=github&utm_medium=meilisearch&utm_content=contact)
|
- Want to be part of our Discord community? [Join us!](https://discord.gg/meilisearch)
|
||||||
|
|
||||||
Thank you for your support!
|
Thank you for your support!
|
||||||
|
|
||||||
|
File diff suppressed because it is too large
Load Diff
Binary file not shown.
Before Width: | Height: | Size: 1.2 MiB |
@ -11,24 +11,24 @@ edition.workspace = true
|
|||||||
license.workspace = true
|
license.workspace = true
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
anyhow = "1.0.79"
|
anyhow = "1.0.70"
|
||||||
csv = "1.3.0"
|
csv = "1.2.1"
|
||||||
milli = { path = "../milli" }
|
milli = { path = "../milli" }
|
||||||
mimalloc = { version = "0.1.39", default-features = false }
|
mimalloc = { version = "0.1.36", default-features = false }
|
||||||
serde_json = { version = "1.0.111", features = ["preserve_order"] }
|
serde_json = { version = "1.0.95", features = ["preserve_order"] }
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
criterion = { version = "0.5.1", features = ["html_reports"] }
|
criterion = { version = "0.4.0", features = ["html_reports"] }
|
||||||
rand = "0.8.5"
|
rand = "0.8.5"
|
||||||
rand_chacha = "0.3.1"
|
rand_chacha = "0.3.1"
|
||||||
roaring = "0.10.2"
|
roaring = "0.10.1"
|
||||||
|
|
||||||
[build-dependencies]
|
[build-dependencies]
|
||||||
anyhow = "1.0.79"
|
anyhow = "1.0.70"
|
||||||
bytes = "1.5.0"
|
bytes = "1.4.0"
|
||||||
convert_case = "0.6.0"
|
convert_case = "0.6.0"
|
||||||
flate2 = "1.0.28"
|
flate2 = "1.0.25"
|
||||||
reqwest = { version = "0.11.23", features = ["blocking", "rustls-tls"], default-features = false }
|
reqwest = { version = "0.11.16", features = ["blocking", "rustls-tls"], default-features = false }
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
default = ["milli/all-tokenizations"]
|
default = ["milli/all-tokenizations"]
|
||||||
|
@ -6,7 +6,9 @@ use std::path::Path;
|
|||||||
|
|
||||||
use criterion::{criterion_group, criterion_main, Criterion};
|
use criterion::{criterion_group, criterion_main, Criterion};
|
||||||
use milli::heed::{EnvOpenOptions, RwTxn};
|
use milli::heed::{EnvOpenOptions, RwTxn};
|
||||||
use milli::update::{IndexDocuments, IndexDocumentsConfig, IndexerConfig, Settings};
|
use milli::update::{
|
||||||
|
DeleteDocuments, IndexDocuments, IndexDocumentsConfig, IndexerConfig, Settings,
|
||||||
|
};
|
||||||
use milli::Index;
|
use milli::Index;
|
||||||
use rand::seq::SliceRandom;
|
use rand::seq::SliceRandom;
|
||||||
use rand_chacha::rand_core::SeedableRng;
|
use rand_chacha::rand_core::SeedableRng;
|
||||||
@ -36,7 +38,7 @@ fn setup_index() -> Index {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn setup_settings<'t>(
|
fn setup_settings<'t>(
|
||||||
wtxn: &mut RwTxn<'t>,
|
wtxn: &mut RwTxn<'t, '_>,
|
||||||
index: &'t Index,
|
index: &'t Index,
|
||||||
primary_key: &str,
|
primary_key: &str,
|
||||||
searchable_fields: &[&str],
|
searchable_fields: &[&str],
|
||||||
@ -264,7 +266,17 @@ fn deleting_songs_in_batches_default(c: &mut Criterion) {
|
|||||||
(index, document_ids_to_delete)
|
(index, document_ids_to_delete)
|
||||||
},
|
},
|
||||||
move |(index, document_ids_to_delete)| {
|
move |(index, document_ids_to_delete)| {
|
||||||
delete_documents_from_ids(index, document_ids_to_delete)
|
let mut wtxn = index.write_txn().unwrap();
|
||||||
|
|
||||||
|
for ids in document_ids_to_delete {
|
||||||
|
let mut builder = DeleteDocuments::new(&mut wtxn, &index).unwrap();
|
||||||
|
builder.delete_documents(&ids);
|
||||||
|
builder.execute().unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
wtxn.commit().unwrap();
|
||||||
|
|
||||||
|
index.prepare_for_closing().wait();
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
});
|
});
|
||||||
@ -601,7 +613,17 @@ fn deleting_wiki_in_batches_default(c: &mut Criterion) {
|
|||||||
(index, document_ids_to_delete)
|
(index, document_ids_to_delete)
|
||||||
},
|
},
|
||||||
move |(index, document_ids_to_delete)| {
|
move |(index, document_ids_to_delete)| {
|
||||||
delete_documents_from_ids(index, document_ids_to_delete)
|
let mut wtxn = index.write_txn().unwrap();
|
||||||
|
|
||||||
|
for ids in document_ids_to_delete {
|
||||||
|
let mut builder = DeleteDocuments::new(&mut wtxn, &index).unwrap();
|
||||||
|
builder.delete_documents(&ids);
|
||||||
|
builder.execute().unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
wtxn.commit().unwrap();
|
||||||
|
|
||||||
|
index.prepare_for_closing().wait();
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
});
|
});
|
||||||
@ -853,29 +875,20 @@ fn deleting_movies_in_batches_default(c: &mut Criterion) {
|
|||||||
(index, document_ids_to_delete)
|
(index, document_ids_to_delete)
|
||||||
},
|
},
|
||||||
move |(index, document_ids_to_delete)| {
|
move |(index, document_ids_to_delete)| {
|
||||||
delete_documents_from_ids(index, document_ids_to_delete)
|
|
||||||
},
|
|
||||||
)
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
fn delete_documents_from_ids(index: Index, document_ids_to_delete: Vec<RoaringBitmap>) {
|
|
||||||
let mut wtxn = index.write_txn().unwrap();
|
let mut wtxn = index.write_txn().unwrap();
|
||||||
|
|
||||||
let indexer_config = IndexerConfig::default();
|
|
||||||
for ids in document_ids_to_delete {
|
for ids in document_ids_to_delete {
|
||||||
let config = IndexDocumentsConfig::default();
|
let mut builder = DeleteDocuments::new(&mut wtxn, &index).unwrap();
|
||||||
|
builder.delete_documents(&ids);
|
||||||
let mut builder =
|
|
||||||
IndexDocuments::new(&mut wtxn, &index, &indexer_config, config, |_| (), || false)
|
|
||||||
.unwrap();
|
|
||||||
(builder, _) = builder.remove_documents_from_db_no_batch(&ids).unwrap();
|
|
||||||
builder.execute().unwrap();
|
builder.execute().unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
wtxn.commit().unwrap();
|
wtxn.commit().unwrap();
|
||||||
|
|
||||||
index.prepare_for_closing().wait();
|
index.prepare_for_closing().wait();
|
||||||
|
},
|
||||||
|
)
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
fn indexing_movies_in_three_batches(c: &mut Criterion) {
|
fn indexing_movies_in_three_batches(c: &mut Criterion) {
|
||||||
@ -1099,7 +1112,17 @@ fn deleting_nested_movies_in_batches_default(c: &mut Criterion) {
|
|||||||
(index, document_ids_to_delete)
|
(index, document_ids_to_delete)
|
||||||
},
|
},
|
||||||
move |(index, document_ids_to_delete)| {
|
move |(index, document_ids_to_delete)| {
|
||||||
delete_documents_from_ids(index, document_ids_to_delete)
|
let mut wtxn = index.write_txn().unwrap();
|
||||||
|
|
||||||
|
for ids in document_ids_to_delete {
|
||||||
|
let mut builder = DeleteDocuments::new(&mut wtxn, &index).unwrap();
|
||||||
|
builder.delete_documents(&ids);
|
||||||
|
builder.execute().unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
wtxn.commit().unwrap();
|
||||||
|
|
||||||
|
index.prepare_for_closing().wait();
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
});
|
});
|
||||||
@ -1315,7 +1338,17 @@ fn deleting_geo_in_batches_default(c: &mut Criterion) {
|
|||||||
(index, document_ids_to_delete)
|
(index, document_ids_to_delete)
|
||||||
},
|
},
|
||||||
move |(index, document_ids_to_delete)| {
|
move |(index, document_ids_to_delete)| {
|
||||||
delete_documents_from_ids(index, document_ids_to_delete)
|
let mut wtxn = index.write_txn().unwrap();
|
||||||
|
|
||||||
|
for ids in document_ids_to_delete {
|
||||||
|
let mut builder = DeleteDocuments::new(&mut wtxn, &index).unwrap();
|
||||||
|
builder.delete_documents(&ids);
|
||||||
|
builder.execute().unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
wtxn.commit().unwrap();
|
||||||
|
|
||||||
|
index.prepare_for_closing().wait();
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
});
|
});
|
||||||
|
@ -1,18 +0,0 @@
|
|||||||
[package]
|
|
||||||
name = "build-info"
|
|
||||||
version.workspace = true
|
|
||||||
authors.workspace = true
|
|
||||||
description.workspace = true
|
|
||||||
homepage.workspace = true
|
|
||||||
readme.workspace = true
|
|
||||||
edition.workspace = true
|
|
||||||
license.workspace = true
|
|
||||||
|
|
||||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
|
||||||
|
|
||||||
[dependencies]
|
|
||||||
time = { version = "0.3.34", features = ["parsing"] }
|
|
||||||
|
|
||||||
[build-dependencies]
|
|
||||||
anyhow = "1.0.80"
|
|
||||||
vergen-git2 = "1.0.0-beta.2"
|
|
@ -1,22 +0,0 @@
|
|||||||
fn main() {
|
|
||||||
if let Err(err) = emit_git_variables() {
|
|
||||||
println!("cargo:warning=vergen: {}", err);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn emit_git_variables() -> anyhow::Result<()> {
|
|
||||||
// Note: any code that needs VERGEN_ environment variables should take care to define them manually in the Dockerfile and pass them
|
|
||||||
// in the corresponding GitHub workflow (publish_docker.yml).
|
|
||||||
// This is due to the Dockerfile building the binary outside of the git directory.
|
|
||||||
let mut builder = vergen_git2::Git2Builder::default();
|
|
||||||
|
|
||||||
builder.branch(true);
|
|
||||||
builder.commit_timestamp(true);
|
|
||||||
builder.commit_message(true);
|
|
||||||
builder.describe(true, true, None);
|
|
||||||
builder.sha(false);
|
|
||||||
|
|
||||||
let git2 = builder.build()?;
|
|
||||||
|
|
||||||
vergen_git2::Emitter::default().fail_on_error().add_instructions(&git2)?.emit()
|
|
||||||
}
|
|
@ -1,203 +0,0 @@
|
|||||||
use time::format_description::well_known::Iso8601;
|
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
|
||||||
pub struct BuildInfo {
|
|
||||||
pub branch: Option<&'static str>,
|
|
||||||
pub describe: Option<DescribeResult>,
|
|
||||||
pub commit_sha1: Option<&'static str>,
|
|
||||||
pub commit_msg: Option<&'static str>,
|
|
||||||
pub commit_timestamp: Option<time::OffsetDateTime>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl BuildInfo {
|
|
||||||
pub fn from_build() -> Self {
|
|
||||||
let branch: Option<&'static str> = option_env!("VERGEN_GIT_BRANCH");
|
|
||||||
let describe = DescribeResult::from_build();
|
|
||||||
let commit_sha1 = option_env!("VERGEN_GIT_SHA");
|
|
||||||
let commit_msg = option_env!("VERGEN_GIT_COMMIT_MESSAGE");
|
|
||||||
let commit_timestamp = option_env!("VERGEN_GIT_COMMIT_TIMESTAMP");
|
|
||||||
|
|
||||||
let commit_timestamp = commit_timestamp.and_then(|commit_timestamp| {
|
|
||||||
time::OffsetDateTime::parse(commit_timestamp, &Iso8601::DEFAULT).ok()
|
|
||||||
});
|
|
||||||
|
|
||||||
Self { branch, describe, commit_sha1, commit_msg, commit_timestamp }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
|
||||||
pub enum DescribeResult {
|
|
||||||
Prototype { name: &'static str },
|
|
||||||
Release { version: &'static str, major: u64, minor: u64, patch: u64 },
|
|
||||||
Prerelease { version: &'static str, major: u64, minor: u64, patch: u64, rc: u64 },
|
|
||||||
NotATag { describe: &'static str },
|
|
||||||
}
|
|
||||||
|
|
||||||
impl DescribeResult {
|
|
||||||
pub fn new(describe: &'static str) -> Self {
|
|
||||||
if let Some(name) = prototype_name(describe) {
|
|
||||||
Self::Prototype { name }
|
|
||||||
} else if let Some(release) = release_version(describe) {
|
|
||||||
release
|
|
||||||
} else if let Some(prerelease) = prerelease_version(describe) {
|
|
||||||
prerelease
|
|
||||||
} else {
|
|
||||||
Self::NotATag { describe }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn from_build() -> Option<Self> {
|
|
||||||
let describe: &'static str = option_env!("VERGEN_GIT_DESCRIBE")?;
|
|
||||||
Some(Self::new(describe))
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn as_tag(&self) -> Option<&'static str> {
|
|
||||||
match self {
|
|
||||||
DescribeResult::Prototype { name } => Some(name),
|
|
||||||
DescribeResult::Release { version, .. } => Some(version),
|
|
||||||
DescribeResult::Prerelease { version, .. } => Some(version),
|
|
||||||
DescribeResult::NotATag { describe: _ } => None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn as_prototype(&self) -> Option<&'static str> {
|
|
||||||
match self {
|
|
||||||
DescribeResult::Prototype { name } => Some(name),
|
|
||||||
DescribeResult::Release { .. }
|
|
||||||
| DescribeResult::Prerelease { .. }
|
|
||||||
| DescribeResult::NotATag { .. } => None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Parses the input as a prototype name.
|
|
||||||
///
|
|
||||||
/// Returns `Some(prototype_name)` if the following conditions are met on this value:
|
|
||||||
///
|
|
||||||
/// 1. starts with `prototype-`,
|
|
||||||
/// 2. ends with `-<some_number>`,
|
|
||||||
/// 3. does not end with `<some_number>-<some_number>`.
|
|
||||||
///
|
|
||||||
/// Otherwise, returns `None`.
|
|
||||||
fn prototype_name(describe: &'static str) -> Option<&'static str> {
|
|
||||||
if !describe.starts_with("prototype-") {
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut rsplit_prototype = describe.rsplit('-');
|
|
||||||
// last component MUST be a number
|
|
||||||
rsplit_prototype.next()?.parse::<u64>().ok()?;
|
|
||||||
// before than last component SHALL NOT be a number
|
|
||||||
rsplit_prototype.next()?.parse::<u64>().err()?;
|
|
||||||
|
|
||||||
Some(describe)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn release_version(describe: &'static str) -> Option<DescribeResult> {
|
|
||||||
if !describe.starts_with('v') {
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
|
|
||||||
// full release version don't contain a `-`
|
|
||||||
if describe.contains('-') {
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
|
|
||||||
// full release version parse as vX.Y.Z, with X, Y, Z numbers.
|
|
||||||
let mut dots = describe[1..].split('.');
|
|
||||||
let major: u64 = dots.next()?.parse().ok()?;
|
|
||||||
let minor: u64 = dots.next()?.parse().ok()?;
|
|
||||||
let patch: u64 = dots.next()?.parse().ok()?;
|
|
||||||
|
|
||||||
if dots.next().is_some() {
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
|
|
||||||
Some(DescribeResult::Release { version: describe, major, minor, patch })
|
|
||||||
}
|
|
||||||
|
|
||||||
fn prerelease_version(describe: &'static str) -> Option<DescribeResult> {
|
|
||||||
// prerelease version is in the shape vM.N.P-rc.C
|
|
||||||
let mut hyphen = describe.rsplit('-');
|
|
||||||
let prerelease = hyphen.next()?;
|
|
||||||
if !prerelease.starts_with("rc.") {
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
|
|
||||||
let rc: u64 = prerelease[3..].parse().ok()?;
|
|
||||||
|
|
||||||
let release = hyphen.next()?;
|
|
||||||
|
|
||||||
let DescribeResult::Release { version: _, major, minor, patch } = release_version(release)?
|
|
||||||
else {
|
|
||||||
return None;
|
|
||||||
};
|
|
||||||
|
|
||||||
Some(DescribeResult::Prerelease { version: describe, major, minor, patch, rc })
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod test {
|
|
||||||
use super::DescribeResult;
|
|
||||||
|
|
||||||
fn assert_not_a_tag(describe: &'static str) {
|
|
||||||
assert_eq!(DescribeResult::NotATag { describe }, DescribeResult::new(describe))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn assert_proto(describe: &'static str) {
|
|
||||||
assert_eq!(DescribeResult::Prototype { name: describe }, DescribeResult::new(describe))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn assert_release(describe: &'static str, major: u64, minor: u64, patch: u64) {
|
|
||||||
assert_eq!(
|
|
||||||
DescribeResult::Release { version: describe, major, minor, patch },
|
|
||||||
DescribeResult::new(describe)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn assert_prerelease(describe: &'static str, major: u64, minor: u64, patch: u64, rc: u64) {
|
|
||||||
assert_eq!(
|
|
||||||
DescribeResult::Prerelease { version: describe, major, minor, patch, rc },
|
|
||||||
DescribeResult::new(describe)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn not_a_tag() {
|
|
||||||
assert_not_a_tag("whatever-fuzzy");
|
|
||||||
assert_not_a_tag("whatever-fuzzy-5-ggg-dirty");
|
|
||||||
assert_not_a_tag("whatever-fuzzy-120-ggg-dirty");
|
|
||||||
|
|
||||||
// technically a tag, but not a proto nor a version, so not parsed as a tag
|
|
||||||
assert_not_a_tag("whatever");
|
|
||||||
|
|
||||||
// dirty version
|
|
||||||
assert_not_a_tag("v1.7.0-1-ggga-dirty");
|
|
||||||
assert_not_a_tag("v1.7.0-rc.1-1-ggga-dirty");
|
|
||||||
|
|
||||||
// after version
|
|
||||||
assert_not_a_tag("v1.7.0-1-ggga");
|
|
||||||
assert_not_a_tag("v1.7.0-rc.1-1-ggga");
|
|
||||||
|
|
||||||
// after proto
|
|
||||||
assert_not_a_tag("protoype-tag-0-1-ggga");
|
|
||||||
assert_not_a_tag("protoype-tag-0-1-ggga-dirty");
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn prototype() {
|
|
||||||
assert_proto("prototype-tag-0");
|
|
||||||
assert_proto("prototype-tag-10");
|
|
||||||
assert_proto("prototype-long-name-tag-10");
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn release() {
|
|
||||||
assert_release("v1.7.2", 1, 7, 2);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn prerelease() {
|
|
||||||
assert_prerelease("v1.7.2-rc.3", 1, 7, 2, 3);
|
|
||||||
}
|
|
||||||
}
|
|
@ -129,6 +129,3 @@ experimental_enable_metrics = false
|
|||||||
|
|
||||||
# Experimental RAM reduction during indexing, do not use in production, see: <https://github.com/meilisearch/product/discussions/652>
|
# Experimental RAM reduction during indexing, do not use in production, see: <https://github.com/meilisearch/product/discussions/652>
|
||||||
experimental_reduce_indexing_memory_usage = false
|
experimental_reduce_indexing_memory_usage = false
|
||||||
|
|
||||||
# Experimentally reduces the maximum number of tasks that will be processed at once, see: <https://github.com/orgs/meilisearch/discussions/713>
|
|
||||||
# experimental_max_number_of_batched_tasks = 100
|
|
||||||
|
@ -11,22 +11,22 @@ readme.workspace = true
|
|||||||
license.workspace = true
|
license.workspace = true
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
anyhow = "1.0.79"
|
anyhow = "1.0.70"
|
||||||
flate2 = "1.0.28"
|
flate2 = "1.0.25"
|
||||||
http = "0.2.11"
|
http = "0.2.9"
|
||||||
|
log = "0.4.17"
|
||||||
meilisearch-auth = { path = "../meilisearch-auth" }
|
meilisearch-auth = { path = "../meilisearch-auth" }
|
||||||
meilisearch-types = { path = "../meilisearch-types" }
|
meilisearch-types = { path = "../meilisearch-types" }
|
||||||
once_cell = "1.19.0"
|
once_cell = "1.17.1"
|
||||||
regex = "1.10.2"
|
regex = "1.7.3"
|
||||||
roaring = { version = "0.10.2", features = ["serde"] }
|
roaring = { version = "0.10.1", features = ["serde"] }
|
||||||
serde = { version = "1.0.195", features = ["derive"] }
|
serde = { version = "1.0.160", features = ["derive"] }
|
||||||
serde_json = { version = "1.0.111", features = ["preserve_order"] }
|
serde_json = { version = "1.0.95", features = ["preserve_order"] }
|
||||||
tar = "0.4.40"
|
tar = "0.4.38"
|
||||||
tempfile = "3.9.0"
|
tempfile = "3.5.0"
|
||||||
thiserror = "1.0.56"
|
thiserror = "1.0.40"
|
||||||
time = { version = "0.3.31", features = ["serde-well-known", "formatting", "parsing", "macros"] }
|
time = { version = "0.3.20", features = ["serde-well-known", "formatting", "parsing", "macros"] }
|
||||||
tracing = "0.1.40"
|
uuid = { version = "1.3.1", features = ["serde", "v4"] }
|
||||||
uuid = { version = "1.6.1", features = ["serde", "v4"] }
|
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
big_s = "1.0.2"
|
big_s = "1.0.2"
|
||||||
|
@ -12,7 +12,6 @@ use serde::{Deserialize, Serialize};
|
|||||||
use time::OffsetDateTime;
|
use time::OffsetDateTime;
|
||||||
|
|
||||||
mod error;
|
mod error;
|
||||||
mod new_writer;
|
|
||||||
mod reader;
|
mod reader;
|
||||||
mod writer;
|
mod writer;
|
||||||
|
|
||||||
@ -209,14 +208,12 @@ pub(crate) mod test {
|
|||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
|
|
||||||
use big_s::S;
|
use big_s::S;
|
||||||
use maplit::{btreemap, btreeset};
|
use maplit::btreeset;
|
||||||
use meilisearch_types::facet_values_sort::FacetValuesSort;
|
|
||||||
use meilisearch_types::features::RuntimeTogglableFeatures;
|
|
||||||
use meilisearch_types::index_uid_pattern::IndexUidPattern;
|
use meilisearch_types::index_uid_pattern::IndexUidPattern;
|
||||||
use meilisearch_types::keys::{Action, Key};
|
use meilisearch_types::keys::{Action, Key};
|
||||||
use meilisearch_types::milli;
|
|
||||||
use meilisearch_types::milli::update::Setting;
|
use meilisearch_types::milli::update::Setting;
|
||||||
use meilisearch_types::settings::{Checked, FacetingSettings, Settings};
|
use meilisearch_types::milli::{self};
|
||||||
|
use meilisearch_types::settings::{Checked, Settings};
|
||||||
use meilisearch_types::tasks::{Details, Status};
|
use meilisearch_types::tasks::{Details, Status};
|
||||||
use serde_json::{json, Map, Value};
|
use serde_json::{json, Map, Value};
|
||||||
use time::macros::datetime;
|
use time::macros::datetime;
|
||||||
@ -257,28 +254,17 @@ pub(crate) mod test {
|
|||||||
|
|
||||||
pub fn create_test_settings() -> Settings<Checked> {
|
pub fn create_test_settings() -> Settings<Checked> {
|
||||||
let settings = Settings {
|
let settings = Settings {
|
||||||
displayed_attributes: Setting::Set(vec![S("race"), S("name")]).into(),
|
displayed_attributes: Setting::Set(vec![S("race"), S("name")]),
|
||||||
searchable_attributes: Setting::Set(vec![S("name"), S("race")]).into(),
|
searchable_attributes: Setting::Set(vec![S("name"), S("race")]),
|
||||||
filterable_attributes: Setting::Set(btreeset! { S("race"), S("age") }),
|
filterable_attributes: Setting::Set(btreeset! { S("race"), S("age") }),
|
||||||
sortable_attributes: Setting::Set(btreeset! { S("age") }),
|
sortable_attributes: Setting::Set(btreeset! { S("age") }),
|
||||||
ranking_rules: Setting::NotSet,
|
ranking_rules: Setting::NotSet,
|
||||||
stop_words: Setting::NotSet,
|
stop_words: Setting::NotSet,
|
||||||
non_separator_tokens: Setting::NotSet,
|
|
||||||
separator_tokens: Setting::NotSet,
|
|
||||||
dictionary: Setting::NotSet,
|
|
||||||
synonyms: Setting::NotSet,
|
synonyms: Setting::NotSet,
|
||||||
distinct_attribute: Setting::NotSet,
|
distinct_attribute: Setting::NotSet,
|
||||||
proximity_precision: Setting::NotSet,
|
|
||||||
typo_tolerance: Setting::NotSet,
|
typo_tolerance: Setting::NotSet,
|
||||||
faceting: Setting::Set(FacetingSettings {
|
faceting: Setting::NotSet,
|
||||||
max_values_per_facet: Setting::Set(111),
|
|
||||||
sort_facet_values_by: Setting::Set(
|
|
||||||
btreemap! { S("age") => FacetValuesSort::Count },
|
|
||||||
),
|
|
||||||
}),
|
|
||||||
pagination: Setting::NotSet,
|
pagination: Setting::NotSet,
|
||||||
embedders: Setting::NotSet,
|
|
||||||
search_cutoff_ms: Setting::NotSet,
|
|
||||||
_kind: std::marker::PhantomData,
|
_kind: std::marker::PhantomData,
|
||||||
};
|
};
|
||||||
settings.check()
|
settings.check()
|
||||||
@ -426,11 +412,6 @@ pub(crate) mod test {
|
|||||||
}
|
}
|
||||||
keys.flush().unwrap();
|
keys.flush().unwrap();
|
||||||
|
|
||||||
// ========== experimental features
|
|
||||||
let features = create_test_features();
|
|
||||||
|
|
||||||
dump.create_experimental_features(features).unwrap();
|
|
||||||
|
|
||||||
// create the dump
|
// create the dump
|
||||||
let mut file = tempfile::tempfile().unwrap();
|
let mut file = tempfile::tempfile().unwrap();
|
||||||
dump.persist_to(&mut file).unwrap();
|
dump.persist_to(&mut file).unwrap();
|
||||||
@ -439,10 +420,6 @@ pub(crate) mod test {
|
|||||||
file
|
file
|
||||||
}
|
}
|
||||||
|
|
||||||
fn create_test_features() -> RuntimeTogglableFeatures {
|
|
||||||
RuntimeTogglableFeatures { vector_store: true, ..Default::default() }
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_creating_and_read_dump() {
|
fn test_creating_and_read_dump() {
|
||||||
let mut file = create_test_dump();
|
let mut file = create_test_dump();
|
||||||
@ -487,9 +464,5 @@ pub(crate) mod test {
|
|||||||
for (key, expected) in dump.keys().unwrap().zip(create_test_api_keys()) {
|
for (key, expected) in dump.keys().unwrap().zip(create_test_api_keys()) {
|
||||||
assert_eq!(key.unwrap(), expected);
|
assert_eq!(key.unwrap(), expected);
|
||||||
}
|
}
|
||||||
|
|
||||||
// ==== checking the features
|
|
||||||
let expected = create_test_features();
|
|
||||||
assert_eq!(dump.features().unwrap().unwrap(), expected);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,251 +0,0 @@
|
|||||||
use std::fs::File;
|
|
||||||
use std::io::{Read, Seek, Write};
|
|
||||||
use std::path::Path;
|
|
||||||
use std::result::Result as StdResult;
|
|
||||||
|
|
||||||
use flate2::write::GzEncoder;
|
|
||||||
use flate2::Compression;
|
|
||||||
use meilisearch_types::milli::documents::{
|
|
||||||
obkv_to_object, DocumentsBatchCursor, DocumentsBatchIndex, DocumentsBatchReader,
|
|
||||||
};
|
|
||||||
use tar::{Builder as TarBuilder, Header};
|
|
||||||
use time::OffsetDateTime;
|
|
||||||
use uuid::Uuid;
|
|
||||||
|
|
||||||
use crate::{Key, Metadata, Result, TaskId, CURRENT_DUMP_VERSION};
|
|
||||||
|
|
||||||
pub struct DumpWriter<W: Write> {
|
|
||||||
tar: TarBuilder<GzEncoder<W>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<W: Write> DumpWriter<W> {
|
|
||||||
pub fn new(instance_uuid: Option<Uuid>, writer: W) -> Result<Self> {
|
|
||||||
/// TODO: should we use a BuffWriter?
|
|
||||||
let gz_encoder = GzEncoder::new(writer, Compression::default());
|
|
||||||
let mut tar = TarBuilder::new(gz_encoder);
|
|
||||||
|
|
||||||
let mut header = Header::new_gnu();
|
|
||||||
|
|
||||||
// Append metadata into metadata.json.
|
|
||||||
let metadata = Metadata {
|
|
||||||
dump_version: CURRENT_DUMP_VERSION,
|
|
||||||
db_version: env!("CARGO_PKG_VERSION").to_string(),
|
|
||||||
dump_date: OffsetDateTime::now_utc(),
|
|
||||||
};
|
|
||||||
let data = serde_json::to_string(&metadata).unwrap();
|
|
||||||
header.set_size(data.len() as u64);
|
|
||||||
tar.append_data(&mut header, "metadata.json", data.as_bytes()).unwrap();
|
|
||||||
|
|
||||||
// Append instance uid into instance_uid.uuid.
|
|
||||||
if let Some(instance_uuid) = instance_uuid {
|
|
||||||
let data = instance_uuid.as_hyphenated().to_string();
|
|
||||||
header.set_size(data.len() as u64);
|
|
||||||
tar.append_data(&mut header, "instance_uid.uuid", data.as_bytes()).unwrap();
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(Self { tar })
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn dump_keys(&mut self, keys: &[Key]) -> Result<()> {
|
|
||||||
let mut buffer = Vec::new();
|
|
||||||
for key in keys {
|
|
||||||
serde_json::to_writer(&mut buffer, key)?;
|
|
||||||
buffer.push(b'\n');
|
|
||||||
}
|
|
||||||
let mut header = Header::new_gnu();
|
|
||||||
header.set_path("keys.jsonl");
|
|
||||||
header.set_size(buffer.len() as u64);
|
|
||||||
|
|
||||||
self.tar.append(&mut header, buffer.as_slice())?;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn create_tasks(&mut self) -> Result<FileWriter<W>> {
|
|
||||||
FileWriter::new(&mut self.tar, "tasks/queue.jsonl")
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn dump_update_file<R: Read + Seek>(
|
|
||||||
&mut self,
|
|
||||||
task_uid: TaskId,
|
|
||||||
update_file: DocumentsBatchReader<R>,
|
|
||||||
) -> Result<()> {
|
|
||||||
let path = format!("tasks/update_files/{}.jsonl", task_uid);
|
|
||||||
let mut fw = FileWriter::new(&mut self.tar, path)?;
|
|
||||||
let mut serializer = UpdateFileSerializer::new(update_file);
|
|
||||||
fw.calculate_len(SerializerIteratorReader::new(&mut serializer))?;
|
|
||||||
serializer.reset();
|
|
||||||
fw.write_data(SerializerIteratorReader::new(&mut serializer))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
trait SerializerIterator {
|
|
||||||
fn next_serialize_into(&mut self, buffer: &mut Vec<u8>) -> StdResult<bool, std::io::Error>;
|
|
||||||
}
|
|
||||||
|
|
||||||
struct SerializerIteratorReader<'i, I: SerializerIterator> {
|
|
||||||
iterator: &'i mut I,
|
|
||||||
buffer: Vec<u8>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<I: SerializerIterator> Read for SerializerIteratorReader<'_, I> {
|
|
||||||
fn read(&mut self, buf: &mut [u8]) -> StdResult<usize, std::io::Error> {
|
|
||||||
let mut size = 0;
|
|
||||||
loop {
|
|
||||||
// if the inner buffer is empty, fill it with a new document.
|
|
||||||
if self.buffer.is_empty() {
|
|
||||||
if !self.iterator.next_serialize_into(&mut self.buffer)? {
|
|
||||||
// nothing more to write, return the written size.
|
|
||||||
return Ok(size);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let doc_size = self.buffer.len();
|
|
||||||
let remaining_size = buf[size..].len();
|
|
||||||
if remaining_size < doc_size {
|
|
||||||
// if the serialized document size exceed the buf size,
|
|
||||||
// drain the inner buffer filling the remaining space.
|
|
||||||
buf[size..].copy_from_slice(&self.buffer[..remaining_size]);
|
|
||||||
self.buffer.drain(..remaining_size);
|
|
||||||
|
|
||||||
// then return.
|
|
||||||
return Ok(buf.len());
|
|
||||||
} else {
|
|
||||||
// otherwise write the whole inner buffer into the buf, clear it and continue.
|
|
||||||
buf[size..][..doc_size].copy_from_slice(&self.buffer);
|
|
||||||
size += doc_size;
|
|
||||||
self.buffer.clear();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'i, I: SerializerIterator> SerializerIteratorReader<'i, I> {
|
|
||||||
fn new(iterator: &'i mut I) -> Self {
|
|
||||||
Self { iterator, buffer: Vec::new() }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
struct UpdateFileSerializer<R: Read> {
|
|
||||||
cursor: DocumentsBatchCursor<R>,
|
|
||||||
documents_batch_index: DocumentsBatchIndex,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<R: Read + Seek> SerializerIterator for UpdateFileSerializer<R> {
|
|
||||||
fn next_serialize_into(&mut self, buffer: &mut Vec<u8>) -> StdResult<bool, std::io::Error> {
|
|
||||||
/// TODO: don't unwrap, original version: `cursor.next_document().map_err(milli::Error::from)?`
|
|
||||||
match self.cursor.next_document().unwrap() {
|
|
||||||
Some(doc) => {
|
|
||||||
/// TODO: don't unwrap
|
|
||||||
let json_value = obkv_to_object(&doc, &self.documents_batch_index).unwrap();
|
|
||||||
serde_json::to_writer(&mut *buffer, &json_value)?;
|
|
||||||
buffer.push(b'\n');
|
|
||||||
Ok(true)
|
|
||||||
}
|
|
||||||
None => Ok(false),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<R: Read + Seek> UpdateFileSerializer<R> {
|
|
||||||
fn new(reader: DocumentsBatchReader<R>) -> Self {
|
|
||||||
let (cursor, documents_batch_index) = reader.into_cursor_and_fields_index();
|
|
||||||
|
|
||||||
Self { cursor, documents_batch_index }
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Resets the cursor to be able to read from the start again.
|
|
||||||
pub fn reset(&mut self) {
|
|
||||||
self.cursor.reset();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct FileWriter<'a, W: Write> {
|
|
||||||
header: Header,
|
|
||||||
tar: &'a mut TarBuilder<GzEncoder<W>>,
|
|
||||||
size: Option<u64>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a, W: Write> FileWriter<'a, W> {
|
|
||||||
pub(crate) fn new<P: AsRef<Path>>(
|
|
||||||
tar: &'a mut TarBuilder<GzEncoder<W>>,
|
|
||||||
path: P,
|
|
||||||
) -> Result<Self> {
|
|
||||||
let mut header = Header::new_gnu();
|
|
||||||
header.set_path(path);
|
|
||||||
Ok(Self { header, tar, size: None })
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn calculate_len<R: Read>(&mut self, mut reader: R) -> Result<u64> {
|
|
||||||
let mut calculator = SizeCalculatorWriter::new();
|
|
||||||
std::io::copy(&mut reader, &mut calculator)?;
|
|
||||||
let size = calculator.into_inner();
|
|
||||||
self.size = Some(size);
|
|
||||||
|
|
||||||
Ok(size)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn write_data<R: Read>(mut self, reader: R) -> Result<()> {
|
|
||||||
let expected_size =
|
|
||||||
self.size.expect("calculate_len must be called before writing the data.");
|
|
||||||
self.header.set_size(expected_size);
|
|
||||||
|
|
||||||
let mut scr = SizeCalculatorReader::new(reader);
|
|
||||||
self.tar.append(&mut self.header, &mut scr)?;
|
|
||||||
assert_eq!(
|
|
||||||
expected_size,
|
|
||||||
scr.into_inner(),
|
|
||||||
"Provided data size is different from the pre-calculated size."
|
|
||||||
);
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
struct SizeCalculatorWriter {
|
|
||||||
size: usize,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl SizeCalculatorWriter {
|
|
||||||
fn new() -> Self {
|
|
||||||
Self { size: 0 }
|
|
||||||
}
|
|
||||||
|
|
||||||
fn into_inner(self) -> u64 {
|
|
||||||
self.size as u64
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Write for SizeCalculatorWriter {
|
|
||||||
fn write(&mut self, buf: &[u8]) -> StdResult<usize, std::io::Error> {
|
|
||||||
self.size += buf.len();
|
|
||||||
Ok(self.size)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn flush(&mut self) -> std::result::Result<(), std::io::Error> {
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
struct SizeCalculatorReader<R: Read> {
|
|
||||||
size: usize,
|
|
||||||
reader: R,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<R: Read> SizeCalculatorReader<R> {
|
|
||||||
fn new(reader: R) -> Self {
|
|
||||||
Self { size: 0, reader }
|
|
||||||
}
|
|
||||||
|
|
||||||
fn into_inner(self) -> u64 {
|
|
||||||
self.size as u64
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<R: Read> Read for SizeCalculatorReader<R> {
|
|
||||||
fn read(&mut self, buf: &mut [u8]) -> StdResult<usize, std::io::Error> {
|
|
||||||
let size = self.reader.read(buf)?;
|
|
||||||
self.size += size;
|
|
||||||
|
|
||||||
Ok(size)
|
|
||||||
}
|
|
||||||
}
|
|
@ -120,7 +120,7 @@ impl From<v1::settings::Settings> for v2::Settings<v2::Unchecked> {
|
|||||||
criterion.as_ref().map(ToString::to_string)
|
criterion.as_ref().map(ToString::to_string)
|
||||||
}
|
}
|
||||||
Err(()) => {
|
Err(()) => {
|
||||||
tracing::warn!(
|
log::warn!(
|
||||||
"Could not import the following ranking rule: `{}`.",
|
"Could not import the following ranking rule: `{}`.",
|
||||||
ranking_rule
|
ranking_rule
|
||||||
);
|
);
|
||||||
@ -152,11 +152,11 @@ impl From<v1::update::UpdateStatus> for Option<v2::updates::UpdateStatus> {
|
|||||||
use v2::updates::UpdateStatus as UpdateStatusV2;
|
use v2::updates::UpdateStatus as UpdateStatusV2;
|
||||||
Some(match source {
|
Some(match source {
|
||||||
UpdateStatusV1::Enqueued { content } => {
|
UpdateStatusV1::Enqueued { content } => {
|
||||||
tracing::warn!(
|
log::warn!(
|
||||||
"Cannot import task {} (importing enqueued tasks from v1 dumps is unsupported)",
|
"Cannot import task {} (importing enqueued tasks from v1 dumps is unsupported)",
|
||||||
content.update_id
|
content.update_id
|
||||||
);
|
);
|
||||||
tracing::warn!("Task will be skipped in the queue of imported tasks.");
|
log::warn!("Task will be skipped in the queue of imported tasks.");
|
||||||
|
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
@ -229,7 +229,7 @@ impl From<v1::update::UpdateType> for Option<v2::updates::UpdateMeta> {
|
|||||||
Some(match source {
|
Some(match source {
|
||||||
v1::update::UpdateType::ClearAll => v2::updates::UpdateMeta::ClearDocuments,
|
v1::update::UpdateType::ClearAll => v2::updates::UpdateMeta::ClearDocuments,
|
||||||
v1::update::UpdateType::Customs => {
|
v1::update::UpdateType::Customs => {
|
||||||
tracing::warn!("Ignoring task with type 'Customs' that is no longer supported");
|
log::warn!("Ignoring task with type 'Customs' that is no longer supported");
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
v1::update::UpdateType::DocumentsAddition { .. } => {
|
v1::update::UpdateType::DocumentsAddition { .. } => {
|
||||||
@ -296,7 +296,7 @@ impl From<v1::settings::RankingRule> for Option<v2::settings::Criterion> {
|
|||||||
v1::settings::RankingRule::Proximity => Some(v2::settings::Criterion::Proximity),
|
v1::settings::RankingRule::Proximity => Some(v2::settings::Criterion::Proximity),
|
||||||
v1::settings::RankingRule::Attribute => Some(v2::settings::Criterion::Attribute),
|
v1::settings::RankingRule::Attribute => Some(v2::settings::Criterion::Attribute),
|
||||||
v1::settings::RankingRule::WordsPosition => {
|
v1::settings::RankingRule::WordsPosition => {
|
||||||
tracing::warn!("Removing the 'WordsPosition' ranking rule that is no longer supported, please check the resulting ranking rules of your indexes");
|
log::warn!("Removing the 'WordsPosition' ranking rule that is no longer supported, please check the resulting ranking rules of your indexes");
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
v1::settings::RankingRule::Exactness => Some(v2::settings::Criterion::Exactness),
|
v1::settings::RankingRule::Exactness => Some(v2::settings::Criterion::Exactness),
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
use std::convert::TryInto;
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
|
|
||||||
use time::OffsetDateTime;
|
use time::OffsetDateTime;
|
||||||
@ -145,8 +146,8 @@ impl From<v2::updates::UpdateStatus> for v3::updates::UpdateStatus {
|
|||||||
started_processing_at: processing.started_processing_at,
|
started_processing_at: processing.started_processing_at,
|
||||||
}),
|
}),
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
tracing::warn!("Error with task {}: {}", processing.from.update_id, e);
|
log::warn!("Error with task {}: {}", processing.from.update_id, e);
|
||||||
tracing::warn!("Task will be marked as `Failed`.");
|
log::warn!("Task will be marked as `Failed`.");
|
||||||
v3::updates::UpdateStatus::Failed(v3::updates::Failed {
|
v3::updates::UpdateStatus::Failed(v3::updates::Failed {
|
||||||
from: v3::updates::Processing {
|
from: v3::updates::Processing {
|
||||||
from: v3::updates::Enqueued {
|
from: v3::updates::Enqueued {
|
||||||
@ -171,8 +172,8 @@ impl From<v2::updates::UpdateStatus> for v3::updates::UpdateStatus {
|
|||||||
enqueued_at: enqueued.enqueued_at,
|
enqueued_at: enqueued.enqueued_at,
|
||||||
}),
|
}),
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
tracing::warn!("Error with task {}: {}", enqueued.update_id, e);
|
log::warn!("Error with task {}: {}", enqueued.update_id, e);
|
||||||
tracing::warn!("Task will be marked as `Failed`.");
|
log::warn!("Task will be marked as `Failed`.");
|
||||||
v3::updates::UpdateStatus::Failed(v3::updates::Failed {
|
v3::updates::UpdateStatus::Failed(v3::updates::Failed {
|
||||||
from: v3::updates::Processing {
|
from: v3::updates::Processing {
|
||||||
from: v3::updates::Enqueued {
|
from: v3::updates::Enqueued {
|
||||||
@ -352,7 +353,7 @@ impl From<String> for v3::Code {
|
|||||||
"malformed_payload" => v3::Code::MalformedPayload,
|
"malformed_payload" => v3::Code::MalformedPayload,
|
||||||
"missing_payload" => v3::Code::MissingPayload,
|
"missing_payload" => v3::Code::MissingPayload,
|
||||||
other => {
|
other => {
|
||||||
tracing::warn!("Unknown error code {}", other);
|
log::warn!("Unknown error code {}", other);
|
||||||
v3::Code::UnretrievableErrorCode
|
v3::Code::UnretrievableErrorCode
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -76,20 +76,20 @@ impl CompatV3ToV4 {
|
|||||||
let index_uid = match index_uid {
|
let index_uid = match index_uid {
|
||||||
Some(uid) => uid,
|
Some(uid) => uid,
|
||||||
None => {
|
None => {
|
||||||
tracing::warn!(
|
log::warn!(
|
||||||
"Error while importing the update {}.",
|
"Error while importing the update {}.",
|
||||||
task.update.id()
|
task.update.id()
|
||||||
);
|
);
|
||||||
tracing::warn!(
|
log::warn!(
|
||||||
"The index associated to the uuid `{}` could not be retrieved.",
|
"The index associated to the uuid `{}` could not be retrieved.",
|
||||||
task.uuid.to_string()
|
task.uuid.to_string()
|
||||||
);
|
);
|
||||||
if task.update.is_finished() {
|
if task.update.is_finished() {
|
||||||
// we're fucking with his history but not his data, that's ok-ish.
|
// we're fucking with his history but not his data, that's ok-ish.
|
||||||
tracing::warn!("The index-uuid will be set as `unknown`.");
|
log::warn!("The index-uuid will be set as `unknown`.");
|
||||||
String::from("unknown")
|
String::from("unknown")
|
||||||
} else {
|
} else {
|
||||||
tracing::warn!("The task will be ignored.");
|
log::warn!("The task will be ignored.");
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -305,7 +305,7 @@ impl From<v4::ResponseError> for v5::ResponseError {
|
|||||||
"invalid_api_key_expires_at" => v5::Code::InvalidApiKeyExpiresAt,
|
"invalid_api_key_expires_at" => v5::Code::InvalidApiKeyExpiresAt,
|
||||||
"invalid_api_key_description" => v5::Code::InvalidApiKeyDescription,
|
"invalid_api_key_description" => v5::Code::InvalidApiKeyDescription,
|
||||||
other => {
|
other => {
|
||||||
tracing::warn!("Unknown error code {}", other);
|
log::warn!("Unknown error code {}", other);
|
||||||
v5::Code::UnretrievableErrorCode
|
v5::Code::UnretrievableErrorCode
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -191,10 +191,6 @@ impl CompatV5ToV6 {
|
|||||||
})
|
})
|
||||||
})))
|
})))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn features(&self) -> Result<Option<v6::RuntimeTogglableFeatures>> {
|
|
||||||
Ok(None)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub enum CompatIndexV5ToV6 {
|
pub enum CompatIndexV5ToV6 {
|
||||||
@ -304,7 +300,7 @@ impl From<v5::ResponseError> for v6::ResponseError {
|
|||||||
"immutable_field" => v6::Code::BadRequest,
|
"immutable_field" => v6::Code::BadRequest,
|
||||||
"api_key_already_exists" => v6::Code::ApiKeyAlreadyExists,
|
"api_key_already_exists" => v6::Code::ApiKeyAlreadyExists,
|
||||||
other => {
|
other => {
|
||||||
tracing::warn!("Unknown error code {}", other);
|
log::warn!("Unknown error code {}", other);
|
||||||
v6::Code::UnretrievableErrorCode
|
v6::Code::UnretrievableErrorCode
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@ -315,8 +311,8 @@ impl From<v5::ResponseError> for v6::ResponseError {
|
|||||||
impl<T> From<v5::Settings<T>> for v6::Settings<v6::Unchecked> {
|
impl<T> From<v5::Settings<T>> for v6::Settings<v6::Unchecked> {
|
||||||
fn from(settings: v5::Settings<T>) -> Self {
|
fn from(settings: v5::Settings<T>) -> Self {
|
||||||
v6::Settings {
|
v6::Settings {
|
||||||
displayed_attributes: v6::Setting::from(settings.displayed_attributes).into(),
|
displayed_attributes: settings.displayed_attributes.into(),
|
||||||
searchable_attributes: v6::Setting::from(settings.searchable_attributes).into(),
|
searchable_attributes: settings.searchable_attributes.into(),
|
||||||
filterable_attributes: settings.filterable_attributes.into(),
|
filterable_attributes: settings.filterable_attributes.into(),
|
||||||
sortable_attributes: settings.sortable_attributes.into(),
|
sortable_attributes: settings.sortable_attributes.into(),
|
||||||
ranking_rules: {
|
ranking_rules: {
|
||||||
@ -329,7 +325,7 @@ impl<T> From<v5::Settings<T>> for v6::Settings<v6::Unchecked> {
|
|||||||
new_ranking_rules.push(new_rule);
|
new_ranking_rules.push(new_rule);
|
||||||
}
|
}
|
||||||
Err(_) => {
|
Err(_) => {
|
||||||
tracing::warn!("Error while importing settings. The ranking rule `{rule}` does not exist anymore.")
|
log::warn!("Error while importing settings. The ranking rule `{rule}` does not exist anymore.")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -340,12 +336,8 @@ impl<T> From<v5::Settings<T>> for v6::Settings<v6::Unchecked> {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
stop_words: settings.stop_words.into(),
|
stop_words: settings.stop_words.into(),
|
||||||
non_separator_tokens: v6::Setting::NotSet,
|
|
||||||
separator_tokens: v6::Setting::NotSet,
|
|
||||||
dictionary: v6::Setting::NotSet,
|
|
||||||
synonyms: settings.synonyms.into(),
|
synonyms: settings.synonyms.into(),
|
||||||
distinct_attribute: settings.distinct_attribute.into(),
|
distinct_attribute: settings.distinct_attribute.into(),
|
||||||
proximity_precision: v6::Setting::NotSet,
|
|
||||||
typo_tolerance: match settings.typo_tolerance {
|
typo_tolerance: match settings.typo_tolerance {
|
||||||
v5::Setting::Set(typo) => v6::Setting::Set(v6::TypoTolerance {
|
v5::Setting::Set(typo) => v6::Setting::Set(v6::TypoTolerance {
|
||||||
enabled: typo.enabled.into(),
|
enabled: typo.enabled.into(),
|
||||||
@ -366,7 +358,6 @@ impl<T> From<v5::Settings<T>> for v6::Settings<v6::Unchecked> {
|
|||||||
faceting: match settings.faceting {
|
faceting: match settings.faceting {
|
||||||
v5::Setting::Set(faceting) => v6::Setting::Set(v6::FacetingSettings {
|
v5::Setting::Set(faceting) => v6::Setting::Set(v6::FacetingSettings {
|
||||||
max_values_per_facet: faceting.max_values_per_facet.into(),
|
max_values_per_facet: faceting.max_values_per_facet.into(),
|
||||||
sort_facet_values_by: v6::Setting::NotSet,
|
|
||||||
}),
|
}),
|
||||||
v5::Setting::Reset => v6::Setting::Reset,
|
v5::Setting::Reset => v6::Setting::Reset,
|
||||||
v5::Setting::NotSet => v6::Setting::NotSet,
|
v5::Setting::NotSet => v6::Setting::NotSet,
|
||||||
@ -378,8 +369,6 @@ impl<T> From<v5::Settings<T>> for v6::Settings<v6::Unchecked> {
|
|||||||
v5::Setting::Reset => v6::Setting::Reset,
|
v5::Setting::Reset => v6::Setting::Reset,
|
||||||
v5::Setting::NotSet => v6::Setting::NotSet,
|
v5::Setting::NotSet => v6::Setting::NotSet,
|
||||||
},
|
},
|
||||||
embedders: v6::Setting::NotSet,
|
|
||||||
search_cutoff_ms: v6::Setting::NotSet,
|
|
||||||
_kind: std::marker::PhantomData,
|
_kind: std::marker::PhantomData,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -13,12 +13,12 @@ use crate::{Result, Version};
|
|||||||
|
|
||||||
mod compat;
|
mod compat;
|
||||||
|
|
||||||
mod v1;
|
pub(self) mod v1;
|
||||||
mod v2;
|
pub(self) mod v2;
|
||||||
mod v3;
|
pub(self) mod v3;
|
||||||
mod v4;
|
pub(self) mod v4;
|
||||||
mod v5;
|
pub(self) mod v5;
|
||||||
mod v6;
|
pub(self) mod v6;
|
||||||
|
|
||||||
pub type Document = serde_json::Map<String, serde_json::Value>;
|
pub type Document = serde_json::Map<String, serde_json::Value>;
|
||||||
pub type UpdateFile = dyn Iterator<Item = Result<Document>>;
|
pub type UpdateFile = dyn Iterator<Item = Result<Document>>;
|
||||||
@ -107,13 +107,6 @@ impl DumpReader {
|
|||||||
DumpReader::Compat(compat) => compat.keys(),
|
DumpReader::Compat(compat) => compat.keys(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn features(&self) -> Result<Option<v6::RuntimeTogglableFeatures>> {
|
|
||||||
match self {
|
|
||||||
DumpReader::Current(current) => Ok(current.features()),
|
|
||||||
DumpReader::Compat(compat) => compat.features(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<V6Reader> for DumpReader {
|
impl From<V6Reader> for DumpReader {
|
||||||
@ -195,53 +188,6 @@ pub(crate) mod test {
|
|||||||
use meili_snap::insta;
|
use meili_snap::insta;
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::reader::v6::RuntimeTogglableFeatures;
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn import_dump_v6_experimental() {
|
|
||||||
let dump = File::open("tests/assets/v6-with-experimental.dump").unwrap();
|
|
||||||
let mut dump = DumpReader::open(dump).unwrap();
|
|
||||||
|
|
||||||
// top level infos
|
|
||||||
insta::assert_display_snapshot!(dump.date().unwrap(), @"2023-07-06 7:10:27.21958 +00:00:00");
|
|
||||||
insta::assert_debug_snapshot!(dump.instance_uid().unwrap(), @"None");
|
|
||||||
|
|
||||||
// tasks
|
|
||||||
let tasks = dump.tasks().unwrap().collect::<Result<Vec<_>>>().unwrap();
|
|
||||||
let (tasks, update_files): (Vec<_>, Vec<_>) = tasks.into_iter().unzip();
|
|
||||||
meili_snap::snapshot_hash!(meili_snap::json_string!(tasks), @"d45cd8571703e58ae53c7bd7ce3f5c22");
|
|
||||||
assert_eq!(update_files.len(), 2);
|
|
||||||
assert!(update_files[0].is_none()); // the dump creation
|
|
||||||
assert!(update_files[1].is_none()); // the processed document addition
|
|
||||||
|
|
||||||
// keys
|
|
||||||
let keys = dump.keys().unwrap().collect::<Result<Vec<_>>>().unwrap();
|
|
||||||
meili_snap::snapshot_hash!(meili_snap::json_string!(keys), @"13c2da155e9729c2344688cab29af71d");
|
|
||||||
|
|
||||||
// indexes
|
|
||||||
let mut indexes = dump.indexes().unwrap().collect::<Result<Vec<_>>>().unwrap();
|
|
||||||
// the index are not ordered in any way by default
|
|
||||||
indexes.sort_by_key(|index| index.metadata().uid.to_string());
|
|
||||||
|
|
||||||
let mut test = indexes.pop().unwrap();
|
|
||||||
assert!(indexes.is_empty());
|
|
||||||
|
|
||||||
insta::assert_json_snapshot!(test.metadata(), @r###"
|
|
||||||
{
|
|
||||||
"uid": "test",
|
|
||||||
"primaryKey": "id",
|
|
||||||
"createdAt": "2023-07-06T07:07:41.364694Z",
|
|
||||||
"updatedAt": "2023-07-06T07:07:41.396114Z"
|
|
||||||
}
|
|
||||||
"###);
|
|
||||||
|
|
||||||
assert_eq!(test.documents().unwrap().count(), 1);
|
|
||||||
|
|
||||||
assert_eq!(
|
|
||||||
dump.features().unwrap().unwrap(),
|
|
||||||
RuntimeTogglableFeatures { vector_store: true, ..Default::default() }
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn import_dump_v5() {
|
fn import_dump_v5() {
|
||||||
@ -319,8 +265,6 @@ pub(crate) mod test {
|
|||||||
let documents = spells.documents().unwrap().collect::<Result<Vec<_>>>().unwrap();
|
let documents = spells.documents().unwrap().collect::<Result<Vec<_>>>().unwrap();
|
||||||
assert_eq!(documents.len(), 10);
|
assert_eq!(documents.len(), 10);
|
||||||
meili_snap::snapshot_hash!(format!("{:#?}", documents), @"235016433dd04262c7f2da01d1e808ce");
|
meili_snap::snapshot_hash!(format!("{:#?}", documents), @"235016433dd04262c7f2da01d1e808ce");
|
||||||
|
|
||||||
assert_eq!(dump.features().unwrap(), None);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@ -526,12 +470,12 @@ pub(crate) mod test {
|
|||||||
assert!(indexes.is_empty());
|
assert!(indexes.is_empty());
|
||||||
|
|
||||||
// products
|
// products
|
||||||
insta::assert_json_snapshot!(products.metadata(), @r###"
|
insta::assert_json_snapshot!(products.metadata(), { ".createdAt" => "[now]", ".updatedAt" => "[now]" }, @r###"
|
||||||
{
|
{
|
||||||
"uid": "products",
|
"uid": "products",
|
||||||
"primaryKey": "sku",
|
"primaryKey": "sku",
|
||||||
"createdAt": "2022-10-09T20:27:22.688964637Z",
|
"createdAt": "[now]",
|
||||||
"updatedAt": "2022-10-09T20:27:23.951017769Z"
|
"updatedAt": "[now]"
|
||||||
}
|
}
|
||||||
"###);
|
"###);
|
||||||
|
|
||||||
@ -541,12 +485,12 @@ pub(crate) mod test {
|
|||||||
meili_snap::snapshot_hash!(format!("{:#?}", documents), @"548284a84de510f71e88e6cdea495cf5");
|
meili_snap::snapshot_hash!(format!("{:#?}", documents), @"548284a84de510f71e88e6cdea495cf5");
|
||||||
|
|
||||||
// movies
|
// movies
|
||||||
insta::assert_json_snapshot!(movies.metadata(), @r###"
|
insta::assert_json_snapshot!(movies.metadata(), { ".createdAt" => "[now]", ".updatedAt" => "[now]" }, @r###"
|
||||||
{
|
{
|
||||||
"uid": "movies",
|
"uid": "movies",
|
||||||
"primaryKey": "id",
|
"primaryKey": "id",
|
||||||
"createdAt": "2022-10-09T20:27:22.197788495Z",
|
"createdAt": "[now]",
|
||||||
"updatedAt": "2022-10-09T20:28:01.93111053Z"
|
"updatedAt": "[now]"
|
||||||
}
|
}
|
||||||
"###);
|
"###);
|
||||||
|
|
||||||
@ -571,12 +515,12 @@ pub(crate) mod test {
|
|||||||
meili_snap::snapshot_hash!(format!("{:#?}", documents), @"d751713988987e9331980363e24189ce");
|
meili_snap::snapshot_hash!(format!("{:#?}", documents), @"d751713988987e9331980363e24189ce");
|
||||||
|
|
||||||
// spells
|
// spells
|
||||||
insta::assert_json_snapshot!(spells.metadata(), @r###"
|
insta::assert_json_snapshot!(spells.metadata(), { ".createdAt" => "[now]", ".updatedAt" => "[now]" }, @r###"
|
||||||
{
|
{
|
||||||
"uid": "dnd_spells",
|
"uid": "dnd_spells",
|
||||||
"primaryKey": "index",
|
"primaryKey": "index",
|
||||||
"createdAt": "2022-10-09T20:27:24.242683494Z",
|
"createdAt": "[now]",
|
||||||
"updatedAt": "2022-10-09T20:27:24.312809641Z"
|
"updatedAt": "[now]"
|
||||||
}
|
}
|
||||||
"###);
|
"###);
|
||||||
|
|
||||||
@ -617,12 +561,12 @@ pub(crate) mod test {
|
|||||||
assert!(indexes.is_empty());
|
assert!(indexes.is_empty());
|
||||||
|
|
||||||
// products
|
// products
|
||||||
insta::assert_json_snapshot!(products.metadata(), @r###"
|
insta::assert_json_snapshot!(products.metadata(), { ".createdAt" => "[now]", ".updatedAt" => "[now]" }, @r###"
|
||||||
{
|
{
|
||||||
"uid": "products",
|
"uid": "products",
|
||||||
"primaryKey": "sku",
|
"primaryKey": "sku",
|
||||||
"createdAt": "2023-01-30T16:25:56.595257Z",
|
"createdAt": "[now]",
|
||||||
"updatedAt": "2023-01-30T16:25:58.70348Z"
|
"updatedAt": "[now]"
|
||||||
}
|
}
|
||||||
"###);
|
"###);
|
||||||
|
|
||||||
@ -632,12 +576,12 @@ pub(crate) mod test {
|
|||||||
meili_snap::snapshot_hash!(format!("{:#?}", documents), @"548284a84de510f71e88e6cdea495cf5");
|
meili_snap::snapshot_hash!(format!("{:#?}", documents), @"548284a84de510f71e88e6cdea495cf5");
|
||||||
|
|
||||||
// movies
|
// movies
|
||||||
insta::assert_json_snapshot!(movies.metadata(), @r###"
|
insta::assert_json_snapshot!(movies.metadata(), { ".createdAt" => "[now]", ".updatedAt" => "[now]" }, @r###"
|
||||||
{
|
{
|
||||||
"uid": "movies",
|
"uid": "movies",
|
||||||
"primaryKey": "id",
|
"primaryKey": "id",
|
||||||
"createdAt": "2023-01-30T16:25:56.192178Z",
|
"createdAt": "[now]",
|
||||||
"updatedAt": "2023-01-30T16:25:56.455714Z"
|
"updatedAt": "[now]"
|
||||||
}
|
}
|
||||||
"###);
|
"###);
|
||||||
|
|
||||||
@ -647,12 +591,12 @@ pub(crate) mod test {
|
|||||||
meili_snap::snapshot_hash!(format!("{:#?}", documents), @"0227598af846e574139ee0b80e03a720");
|
meili_snap::snapshot_hash!(format!("{:#?}", documents), @"0227598af846e574139ee0b80e03a720");
|
||||||
|
|
||||||
// spells
|
// spells
|
||||||
insta::assert_json_snapshot!(spells.metadata(), @r###"
|
insta::assert_json_snapshot!(spells.metadata(), { ".createdAt" => "[now]", ".updatedAt" => "[now]" }, @r###"
|
||||||
{
|
{
|
||||||
"uid": "dnd_spells",
|
"uid": "dnd_spells",
|
||||||
"primaryKey": "index",
|
"primaryKey": "index",
|
||||||
"createdAt": "2023-01-30T16:25:58.876405Z",
|
"createdAt": "[now]",
|
||||||
"updatedAt": "2023-01-30T16:25:59.079906Z"
|
"updatedAt": "[now]"
|
||||||
}
|
}
|
||||||
"###);
|
"###);
|
||||||
|
|
||||||
|
@ -56,7 +56,8 @@ pub enum RankingRule {
|
|||||||
Desc(String),
|
Desc(String),
|
||||||
}
|
}
|
||||||
|
|
||||||
static ASC_DESC_REGEX: Lazy<Regex> = Lazy::new(|| Regex::new(r"(asc|desc)\(([\w_-]+)\)").unwrap());
|
static ASC_DESC_REGEX: Lazy<Regex> =
|
||||||
|
Lazy::new(|| Regex::new(r#"(asc|desc)\(([\w_-]+)\)"#).unwrap());
|
||||||
|
|
||||||
impl FromStr for RankingRule {
|
impl FromStr for RankingRule {
|
||||||
type Err = ();
|
type Err = ();
|
||||||
|
@ -46,7 +46,6 @@ pub type Checked = settings::Checked;
|
|||||||
pub type Unchecked = settings::Unchecked;
|
pub type Unchecked = settings::Unchecked;
|
||||||
|
|
||||||
pub type Task = updates::UpdateEntry;
|
pub type Task = updates::UpdateEntry;
|
||||||
pub type Kind = updates::UpdateMeta;
|
|
||||||
|
|
||||||
// everything related to the errors
|
// everything related to the errors
|
||||||
pub type ResponseError = errors::ResponseError;
|
pub type ResponseError = errors::ResponseError;
|
||||||
@ -108,11 +107,8 @@ impl V2Reader {
|
|||||||
pub fn indexes(&self) -> Result<impl Iterator<Item = Result<V2IndexReader>> + '_> {
|
pub fn indexes(&self) -> Result<impl Iterator<Item = Result<V2IndexReader>> + '_> {
|
||||||
Ok(self.index_uuid.iter().map(|index| -> Result<_> {
|
Ok(self.index_uuid.iter().map(|index| -> Result<_> {
|
||||||
V2IndexReader::new(
|
V2IndexReader::new(
|
||||||
|
index.uid.clone(),
|
||||||
&self.dump.path().join("indexes").join(format!("index-{}", index.uuid)),
|
&self.dump.path().join("indexes").join(format!("index-{}", index.uuid)),
|
||||||
index,
|
|
||||||
BufReader::new(
|
|
||||||
File::open(self.dump.path().join("updates").join("data.jsonl")).unwrap(),
|
|
||||||
),
|
|
||||||
)
|
)
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
@ -147,41 +143,16 @@ pub struct V2IndexReader {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl V2IndexReader {
|
impl V2IndexReader {
|
||||||
pub fn new(path: &Path, index_uuid: &IndexUuid, tasks: BufReader<File>) -> Result<Self> {
|
pub fn new(name: String, path: &Path) -> Result<Self> {
|
||||||
let meta = File::open(path.join("meta.json"))?;
|
let meta = File::open(path.join("meta.json"))?;
|
||||||
let meta: DumpMeta = serde_json::from_reader(meta)?;
|
let meta: DumpMeta = serde_json::from_reader(meta)?;
|
||||||
|
|
||||||
let mut created_at = None;
|
|
||||||
let mut updated_at = None;
|
|
||||||
|
|
||||||
for line in tasks.lines() {
|
|
||||||
let task: Task = serde_json::from_str(&line?)?;
|
|
||||||
if !(task.uuid == index_uuid.uuid && task.is_finished()) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
let new_created_at = match task.update.meta() {
|
|
||||||
Kind::DocumentsAddition { .. } | Kind::Settings(_) => task.update.finished_at(),
|
|
||||||
_ => None,
|
|
||||||
};
|
|
||||||
let new_updated_at = task.update.finished_at();
|
|
||||||
|
|
||||||
if created_at.is_none() || created_at > new_created_at {
|
|
||||||
created_at = new_created_at;
|
|
||||||
}
|
|
||||||
|
|
||||||
if updated_at.is_none() || updated_at < new_updated_at {
|
|
||||||
updated_at = new_updated_at;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let current_time = OffsetDateTime::now_utc();
|
|
||||||
|
|
||||||
let metadata = IndexMetadata {
|
let metadata = IndexMetadata {
|
||||||
uid: index_uuid.uid.clone(),
|
uid: name,
|
||||||
primary_key: meta.primary_key,
|
primary_key: meta.primary_key,
|
||||||
created_at: created_at.unwrap_or(current_time),
|
// FIXME: Iterate over the whole task queue to find the creation and last update date.
|
||||||
updated_at: updated_at.unwrap_or(current_time),
|
created_at: OffsetDateTime::now_utc(),
|
||||||
|
updated_at: OffsetDateTime::now_utc(),
|
||||||
};
|
};
|
||||||
|
|
||||||
let ret = V2IndexReader {
|
let ret = V2IndexReader {
|
||||||
@ -277,12 +248,12 @@ pub(crate) mod test {
|
|||||||
assert!(indexes.is_empty());
|
assert!(indexes.is_empty());
|
||||||
|
|
||||||
// products
|
// products
|
||||||
insta::assert_json_snapshot!(products.metadata(), @r###"
|
insta::assert_json_snapshot!(products.metadata(), { ".createdAt" => "[now]", ".updatedAt" => "[now]" }, @r###"
|
||||||
{
|
{
|
||||||
"uid": "products",
|
"uid": "products",
|
||||||
"primaryKey": "sku",
|
"primaryKey": "sku",
|
||||||
"createdAt": "2022-10-09T20:27:22.688964637Z",
|
"createdAt": "[now]",
|
||||||
"updatedAt": "2022-10-09T20:27:23.951017769Z"
|
"updatedAt": "[now]"
|
||||||
}
|
}
|
||||||
"###);
|
"###);
|
||||||
|
|
||||||
@ -292,12 +263,12 @@ pub(crate) mod test {
|
|||||||
meili_snap::snapshot_hash!(format!("{:#?}", documents), @"548284a84de510f71e88e6cdea495cf5");
|
meili_snap::snapshot_hash!(format!("{:#?}", documents), @"548284a84de510f71e88e6cdea495cf5");
|
||||||
|
|
||||||
// movies
|
// movies
|
||||||
insta::assert_json_snapshot!(movies.metadata(), @r###"
|
insta::assert_json_snapshot!(movies.metadata(), { ".createdAt" => "[now]", ".updatedAt" => "[now]" }, @r###"
|
||||||
{
|
{
|
||||||
"uid": "movies",
|
"uid": "movies",
|
||||||
"primaryKey": "id",
|
"primaryKey": "id",
|
||||||
"createdAt": "2022-10-09T20:27:22.197788495Z",
|
"createdAt": "[now]",
|
||||||
"updatedAt": "2022-10-09T20:28:01.93111053Z"
|
"updatedAt": "[now]"
|
||||||
}
|
}
|
||||||
"###);
|
"###);
|
||||||
|
|
||||||
@ -322,12 +293,12 @@ pub(crate) mod test {
|
|||||||
meili_snap::snapshot_hash!(format!("{:#?}", documents), @"d751713988987e9331980363e24189ce");
|
meili_snap::snapshot_hash!(format!("{:#?}", documents), @"d751713988987e9331980363e24189ce");
|
||||||
|
|
||||||
// spells
|
// spells
|
||||||
insta::assert_json_snapshot!(spells.metadata(), @r###"
|
insta::assert_json_snapshot!(spells.metadata(), { ".createdAt" => "[now]", ".updatedAt" => "[now]" }, @r###"
|
||||||
{
|
{
|
||||||
"uid": "dnd_spells",
|
"uid": "dnd_spells",
|
||||||
"primaryKey": "index",
|
"primaryKey": "index",
|
||||||
"createdAt": "2022-10-09T20:27:24.242683494Z",
|
"createdAt": "[now]",
|
||||||
"updatedAt": "2022-10-09T20:27:24.312809641Z"
|
"updatedAt": "[now]"
|
||||||
}
|
}
|
||||||
"###);
|
"###);
|
||||||
|
|
||||||
@ -369,12 +340,12 @@ pub(crate) mod test {
|
|||||||
assert!(indexes.is_empty());
|
assert!(indexes.is_empty());
|
||||||
|
|
||||||
// products
|
// products
|
||||||
insta::assert_json_snapshot!(products.metadata(), @r###"
|
insta::assert_json_snapshot!(products.metadata(), { ".createdAt" => "[now]", ".updatedAt" => "[now]" }, @r###"
|
||||||
{
|
{
|
||||||
"uid": "products",
|
"uid": "products",
|
||||||
"primaryKey": "sku",
|
"primaryKey": "sku",
|
||||||
"createdAt": "2023-01-30T16:25:56.595257Z",
|
"createdAt": "[now]",
|
||||||
"updatedAt": "2023-01-30T16:25:58.70348Z"
|
"updatedAt": "[now]"
|
||||||
}
|
}
|
||||||
"###);
|
"###);
|
||||||
|
|
||||||
@ -384,12 +355,12 @@ pub(crate) mod test {
|
|||||||
meili_snap::snapshot_hash!(format!("{:#?}", documents), @"548284a84de510f71e88e6cdea495cf5");
|
meili_snap::snapshot_hash!(format!("{:#?}", documents), @"548284a84de510f71e88e6cdea495cf5");
|
||||||
|
|
||||||
// movies
|
// movies
|
||||||
insta::assert_json_snapshot!(movies.metadata(), @r###"
|
insta::assert_json_snapshot!(movies.metadata(), { ".createdAt" => "[now]", ".updatedAt" => "[now]" }, @r###"
|
||||||
{
|
{
|
||||||
"uid": "movies",
|
"uid": "movies",
|
||||||
"primaryKey": "id",
|
"primaryKey": "id",
|
||||||
"createdAt": "2023-01-30T16:25:56.192178Z",
|
"createdAt": "[now]",
|
||||||
"updatedAt": "2023-01-30T16:25:56.455714Z"
|
"updatedAt": "[now]"
|
||||||
}
|
}
|
||||||
"###);
|
"###);
|
||||||
|
|
||||||
@ -399,12 +370,12 @@ pub(crate) mod test {
|
|||||||
meili_snap::snapshot_hash!(format!("{:#?}", documents), @"0227598af846e574139ee0b80e03a720");
|
meili_snap::snapshot_hash!(format!("{:#?}", documents), @"0227598af846e574139ee0b80e03a720");
|
||||||
|
|
||||||
// spells
|
// spells
|
||||||
insta::assert_json_snapshot!(spells.metadata(), @r###"
|
insta::assert_json_snapshot!(spells.metadata(), { ".createdAt" => "[now]", ".updatedAt" => "[now]" }, @r###"
|
||||||
{
|
{
|
||||||
"uid": "dnd_spells",
|
"uid": "dnd_spells",
|
||||||
"primaryKey": "index",
|
"primaryKey": "index",
|
||||||
"createdAt": "2023-01-30T16:25:58.876405Z",
|
"createdAt": "[now]",
|
||||||
"updatedAt": "2023-01-30T16:25:59.079906Z"
|
"updatedAt": "[now]"
|
||||||
}
|
}
|
||||||
"###);
|
"###);
|
||||||
|
|
||||||
|
@ -61,7 +61,7 @@ pub enum IndexDocumentsMethod {
|
|||||||
#[cfg_attr(test, derive(serde::Serialize))]
|
#[cfg_attr(test, derive(serde::Serialize))]
|
||||||
#[non_exhaustive]
|
#[non_exhaustive]
|
||||||
pub enum UpdateFormat {
|
pub enum UpdateFormat {
|
||||||
/// The given update is a real **comma separated** CSV with headers on the first line.
|
/// The given update is a real **comma seperated** CSV with headers on the first line.
|
||||||
Csv,
|
Csv,
|
||||||
/// The given update is a JSON array with documents inside.
|
/// The given update is a JSON array with documents inside.
|
||||||
Json,
|
Json,
|
||||||
@ -227,14 +227,4 @@ impl UpdateStatus {
|
|||||||
_ => None,
|
_ => None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn finished_at(&self) -> Option<OffsetDateTime> {
|
|
||||||
match self {
|
|
||||||
UpdateStatus::Processing(_) => None,
|
|
||||||
UpdateStatus::Enqueued(_) => None,
|
|
||||||
UpdateStatus::Processed(u) => Some(u.processed_at),
|
|
||||||
UpdateStatus::Aborted(_) => None,
|
|
||||||
UpdateStatus::Failed(u) => Some(u.failed_at),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
#[allow(clippy::enum_variant_names)]
|
|
||||||
#[derive(Serialize, Deserialize, Debug, Clone, Copy)]
|
#[derive(Serialize, Deserialize, Debug, Clone, Copy)]
|
||||||
pub enum Code {
|
pub enum Code {
|
||||||
// index related error
|
// index related error
|
||||||
|
@ -95,7 +95,6 @@ impl fmt::Display for ErrorType {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(clippy::enum_variant_names)]
|
|
||||||
#[derive(Serialize, Deserialize, Debug, Clone, Copy)]
|
#[derive(Serialize, Deserialize, Debug, Clone, Copy)]
|
||||||
pub enum Code {
|
pub enum Code {
|
||||||
// index related error
|
// index related error
|
||||||
|
@ -31,7 +31,6 @@ impl ResponseError {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(clippy::enum_variant_names)]
|
|
||||||
#[derive(Deserialize, Debug, Clone, Copy)]
|
#[derive(Deserialize, Debug, Clone, Copy)]
|
||||||
#[cfg_attr(test, derive(serde::Serialize))]
|
#[cfg_attr(test, derive(serde::Serialize))]
|
||||||
pub enum Code {
|
pub enum Code {
|
||||||
|
@ -5,7 +5,6 @@ use std::path::Path;
|
|||||||
pub use meilisearch_types::milli;
|
pub use meilisearch_types::milli;
|
||||||
use tempfile::TempDir;
|
use tempfile::TempDir;
|
||||||
use time::OffsetDateTime;
|
use time::OffsetDateTime;
|
||||||
use tracing::debug;
|
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
|
|
||||||
use super::Document;
|
use super::Document;
|
||||||
@ -19,7 +18,6 @@ pub type Unchecked = meilisearch_types::settings::Unchecked;
|
|||||||
|
|
||||||
pub type Task = crate::TaskDump;
|
pub type Task = crate::TaskDump;
|
||||||
pub type Key = meilisearch_types::keys::Key;
|
pub type Key = meilisearch_types::keys::Key;
|
||||||
pub type RuntimeTogglableFeatures = meilisearch_types::features::RuntimeTogglableFeatures;
|
|
||||||
|
|
||||||
// ===== Other types to clarify the code of the compat module
|
// ===== Other types to clarify the code of the compat module
|
||||||
// everything related to the tasks
|
// everything related to the tasks
|
||||||
@ -49,7 +47,6 @@ pub struct V6Reader {
|
|||||||
metadata: Metadata,
|
metadata: Metadata,
|
||||||
tasks: BufReader<File>,
|
tasks: BufReader<File>,
|
||||||
keys: BufReader<File>,
|
keys: BufReader<File>,
|
||||||
features: Option<RuntimeTogglableFeatures>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl V6Reader {
|
impl V6Reader {
|
||||||
@ -61,29 +58,11 @@ impl V6Reader {
|
|||||||
Err(e) => return Err(e.into()),
|
Err(e) => return Err(e.into()),
|
||||||
};
|
};
|
||||||
|
|
||||||
let feature_file = match fs::read(dump.path().join("experimental-features.json")) {
|
|
||||||
Ok(feature_file) => Some(feature_file),
|
|
||||||
Err(error) => match error.kind() {
|
|
||||||
// Allows the file to be missing, this will only result in all experimental features disabled.
|
|
||||||
ErrorKind::NotFound => {
|
|
||||||
debug!("`experimental-features.json` not found in dump");
|
|
||||||
None
|
|
||||||
}
|
|
||||||
_ => return Err(error.into()),
|
|
||||||
},
|
|
||||||
};
|
|
||||||
let features = if let Some(feature_file) = feature_file {
|
|
||||||
Some(serde_json::from_reader(&*feature_file)?)
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
};
|
|
||||||
|
|
||||||
Ok(V6Reader {
|
Ok(V6Reader {
|
||||||
metadata: serde_json::from_reader(&*meta_file)?,
|
metadata: serde_json::from_reader(&*meta_file)?,
|
||||||
instance_uid,
|
instance_uid,
|
||||||
tasks: BufReader::new(File::open(dump.path().join("tasks").join("queue.jsonl"))?),
|
tasks: BufReader::new(File::open(dump.path().join("tasks").join("queue.jsonl"))?),
|
||||||
keys: BufReader::new(File::open(dump.path().join("keys.jsonl"))?),
|
keys: BufReader::new(File::open(dump.path().join("keys.jsonl"))?),
|
||||||
features,
|
|
||||||
dump,
|
dump,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@ -150,10 +129,6 @@ impl V6Reader {
|
|||||||
(&mut self.keys).lines().map(|line| -> Result<_> { Ok(serde_json::from_str(&line?)?) }),
|
(&mut self.keys).lines().map(|line| -> Result<_> { Ok(serde_json::from_str(&line?)?) }),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn features(&self) -> Option<RuntimeTogglableFeatures> {
|
|
||||||
self.features
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct UpdateFile {
|
pub struct UpdateFile {
|
||||||
|
@ -4,7 +4,6 @@ use std::path::PathBuf;
|
|||||||
|
|
||||||
use flate2::write::GzEncoder;
|
use flate2::write::GzEncoder;
|
||||||
use flate2::Compression;
|
use flate2::Compression;
|
||||||
use meilisearch_types::features::RuntimeTogglableFeatures;
|
|
||||||
use meilisearch_types::keys::Key;
|
use meilisearch_types::keys::Key;
|
||||||
use meilisearch_types::settings::{Checked, Settings};
|
use meilisearch_types::settings::{Checked, Settings};
|
||||||
use serde_json::{Map, Value};
|
use serde_json::{Map, Value};
|
||||||
@ -54,13 +53,6 @@ impl DumpWriter {
|
|||||||
TaskWriter::new(self.dir.path().join("tasks"))
|
TaskWriter::new(self.dir.path().join("tasks"))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn create_experimental_features(&self, features: RuntimeTogglableFeatures) -> Result<()> {
|
|
||||||
Ok(std::fs::write(
|
|
||||||
self.dir.path().join("experimental-features.json"),
|
|
||||||
serde_json::to_string(&features)?,
|
|
||||||
)?)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn persist_to(self, mut writer: impl Write) -> Result<()> {
|
pub fn persist_to(self, mut writer: impl Write) -> Result<()> {
|
||||||
let gz_encoder = GzEncoder::new(&mut writer, Compression::default());
|
let gz_encoder = GzEncoder::new(&mut writer, Compression::default());
|
||||||
let mut tar_encoder = tar::Builder::new(gz_encoder);
|
let mut tar_encoder = tar::Builder::new(gz_encoder);
|
||||||
@ -219,7 +211,7 @@ pub(crate) mod test {
|
|||||||
fn _create_directory_hierarchy(dir: &Path, depth: usize) -> String {
|
fn _create_directory_hierarchy(dir: &Path, depth: usize) -> String {
|
||||||
let mut ret = String::new();
|
let mut ret = String::new();
|
||||||
|
|
||||||
// the entries are not guaranteed to be returned in the same order thus we need to sort them.
|
// the entries are not guarenteed to be returned in the same order thus we need to sort them.
|
||||||
let mut entries =
|
let mut entries =
|
||||||
fs::read_dir(dir).unwrap().collect::<std::result::Result<Vec<_>, _>>().unwrap();
|
fs::read_dir(dir).unwrap().collect::<std::result::Result<Vec<_>, _>>().unwrap();
|
||||||
|
|
||||||
@ -292,7 +284,6 @@ pub(crate) mod test {
|
|||||||
│ ├---- update_files/
|
│ ├---- update_files/
|
||||||
│ │ └---- 1.jsonl
|
│ │ └---- 1.jsonl
|
||||||
│ └---- queue.jsonl
|
│ └---- queue.jsonl
|
||||||
├---- experimental-features.json
|
|
||||||
├---- instance_uid.uuid
|
├---- instance_uid.uuid
|
||||||
├---- keys.jsonl
|
├---- keys.jsonl
|
||||||
└---- metadata.json
|
└---- metadata.json
|
||||||
|
Binary file not shown.
@ -11,10 +11,9 @@ edition.workspace = true
|
|||||||
license.workspace = true
|
license.workspace = true
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
tempfile = "3.9.0"
|
tempfile = "3.5.0"
|
||||||
thiserror = "1.0.56"
|
thiserror = "1.0.40"
|
||||||
tracing = "0.1.40"
|
uuid = { version = "1.3.1", features = ["serde", "v4"] }
|
||||||
uuid = { version = "1.6.1", features = ["serde", "v4"] }
|
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
faux = "0.1.10"
|
faux = "0.1.9"
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
use std::fs::File as StdFile;
|
use std::fs::File as StdFile;
|
||||||
use std::io::Write;
|
use std::ops::{Deref, DerefMut};
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
|
|
||||||
@ -22,6 +22,20 @@ pub enum Error {
|
|||||||
|
|
||||||
pub type Result<T> = std::result::Result<T, Error>;
|
pub type Result<T> = std::result::Result<T, Error>;
|
||||||
|
|
||||||
|
impl Deref for File {
|
||||||
|
type Target = NamedTempFile;
|
||||||
|
|
||||||
|
fn deref(&self) -> &Self::Target {
|
||||||
|
&self.file
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DerefMut for File {
|
||||||
|
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||||
|
&mut self.file
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub struct FileStore {
|
pub struct FileStore {
|
||||||
path: PathBuf,
|
path: PathBuf,
|
||||||
@ -42,7 +56,7 @@ impl FileStore {
|
|||||||
let file = NamedTempFile::new_in(&self.path)?;
|
let file = NamedTempFile::new_in(&self.path)?;
|
||||||
let uuid = Uuid::new_v4();
|
let uuid = Uuid::new_v4();
|
||||||
let path = self.path.join(uuid.to_string());
|
let path = self.path.join(uuid.to_string());
|
||||||
let update_file = File { file: Some(file), path };
|
let update_file = File { file, path };
|
||||||
|
|
||||||
Ok((uuid, update_file))
|
Ok((uuid, update_file))
|
||||||
}
|
}
|
||||||
@ -53,7 +67,7 @@ impl FileStore {
|
|||||||
let file = NamedTempFile::new_in(&self.path)?;
|
let file = NamedTempFile::new_in(&self.path)?;
|
||||||
let uuid = Uuid::from_u128(uuid);
|
let uuid = Uuid::from_u128(uuid);
|
||||||
let path = self.path.join(uuid.to_string());
|
let path = self.path.join(uuid.to_string());
|
||||||
let update_file = File { file: Some(file), path };
|
let update_file = File { file, path };
|
||||||
|
|
||||||
Ok((uuid, update_file))
|
Ok((uuid, update_file))
|
||||||
}
|
}
|
||||||
@ -61,13 +75,7 @@ impl FileStore {
|
|||||||
/// Returns the file corresponding to the requested uuid.
|
/// Returns the file corresponding to the requested uuid.
|
||||||
pub fn get_update(&self, uuid: Uuid) -> Result<StdFile> {
|
pub fn get_update(&self, uuid: Uuid) -> Result<StdFile> {
|
||||||
let path = self.get_update_path(uuid);
|
let path = self.get_update_path(uuid);
|
||||||
let file = match StdFile::open(path) {
|
let file = StdFile::open(path)?;
|
||||||
Ok(file) => file,
|
|
||||||
Err(e) => {
|
|
||||||
tracing::error!("Can't access update file {uuid}: {e}");
|
|
||||||
return Err(e.into());
|
|
||||||
}
|
|
||||||
};
|
|
||||||
Ok(file)
|
Ok(file)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -102,13 +110,9 @@ impl FileStore {
|
|||||||
|
|
||||||
pub fn delete(&self, uuid: Uuid) -> Result<()> {
|
pub fn delete(&self, uuid: Uuid) -> Result<()> {
|
||||||
let path = self.path.join(uuid.to_string());
|
let path = self.path.join(uuid.to_string());
|
||||||
if let Err(e) = std::fs::remove_file(path) {
|
std::fs::remove_file(path)?;
|
||||||
tracing::error!("Can't delete file {uuid}: {e}");
|
|
||||||
Err(e.into())
|
|
||||||
} else {
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
/// List the Uuids of the files in the FileStore
|
/// List the Uuids of the files in the FileStore
|
||||||
pub fn all_uuids(&self) -> Result<impl Iterator<Item = Result<Uuid>>> {
|
pub fn all_uuids(&self) -> Result<impl Iterator<Item = Result<Uuid>>> {
|
||||||
@ -132,40 +136,16 @@ impl FileStore {
|
|||||||
|
|
||||||
pub struct File {
|
pub struct File {
|
||||||
path: PathBuf,
|
path: PathBuf,
|
||||||
file: Option<NamedTempFile>,
|
file: NamedTempFile,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl File {
|
impl File {
|
||||||
pub fn dry_file() -> Result<Self> {
|
|
||||||
Ok(Self { path: PathBuf::new(), file: None })
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn persist(self) -> Result<()> {
|
pub fn persist(self) -> Result<()> {
|
||||||
if let Some(file) = self.file {
|
self.file.persist(&self.path)?;
|
||||||
file.persist(&self.path)?;
|
|
||||||
}
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Write for File {
|
|
||||||
fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
|
|
||||||
if let Some(file) = self.file.as_mut() {
|
|
||||||
file.write(buf)
|
|
||||||
} else {
|
|
||||||
Ok(buf.len())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn flush(&mut self) -> std::io::Result<()> {
|
|
||||||
if let Some(file) = self.file.as_mut() {
|
|
||||||
file.flush()
|
|
||||||
} else {
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod test {
|
mod test {
|
||||||
use std::io::Write;
|
use std::io::Write;
|
||||||
|
@ -13,8 +13,7 @@ license.workspace = true
|
|||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
nom = "7.1.3"
|
nom = "7.1.3"
|
||||||
nom_locate = "4.2.0"
|
nom_locate = "4.1.0"
|
||||||
unescaper = "0.1.3"
|
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
insta = "1.34.0"
|
insta = "1.29.0"
|
||||||
|
@ -62,7 +62,6 @@ pub enum ErrorKind<'a> {
|
|||||||
MisusedGeoRadius,
|
MisusedGeoRadius,
|
||||||
MisusedGeoBoundingBox,
|
MisusedGeoBoundingBox,
|
||||||
InvalidPrimary,
|
InvalidPrimary,
|
||||||
InvalidEscapedNumber,
|
|
||||||
ExpectedEof,
|
ExpectedEof,
|
||||||
ExpectedValue(ExpectedValueKind),
|
ExpectedValue(ExpectedValueKind),
|
||||||
MalformedValue,
|
MalformedValue,
|
||||||
@ -148,9 +147,6 @@ impl<'a> Display for Error<'a> {
|
|||||||
let text = if input.trim().is_empty() { "but instead got nothing.".to_string() } else { format!("at `{}`.", escaped_input) };
|
let text = if input.trim().is_empty() { "but instead got nothing.".to_string() } else { format!("at `{}`.", escaped_input) };
|
||||||
writeln!(f, "Was expecting an operation `=`, `!=`, `>=`, `>`, `<=`, `<`, `IN`, `NOT IN`, `TO`, `EXISTS`, `NOT EXISTS`, `IS NULL`, `IS NOT NULL`, `IS EMPTY`, `IS NOT EMPTY`, `_geoRadius`, or `_geoBoundingBox` {}", text)?
|
writeln!(f, "Was expecting an operation `=`, `!=`, `>=`, `>`, `<=`, `<`, `IN`, `NOT IN`, `TO`, `EXISTS`, `NOT EXISTS`, `IS NULL`, `IS NOT NULL`, `IS EMPTY`, `IS NOT EMPTY`, `_geoRadius`, or `_geoBoundingBox` {}", text)?
|
||||||
}
|
}
|
||||||
ErrorKind::InvalidEscapedNumber => {
|
|
||||||
writeln!(f, "Found an invalid escaped sequence number: `{}`.", escaped_input)?
|
|
||||||
}
|
|
||||||
ErrorKind::ExpectedEof => {
|
ErrorKind::ExpectedEof => {
|
||||||
writeln!(f, "Found unexpected characters at the end of the filter: `{}`. You probably forgot an `OR` or an `AND` rule.", escaped_input)?
|
writeln!(f, "Found unexpected characters at the end of the filter: `{}`. You probably forgot an `OR` or an `AND` rule.", escaped_input)?
|
||||||
}
|
}
|
||||||
|
@ -472,81 +472,8 @@ pub fn parse_filter(input: Span) -> IResult<FilterCondition> {
|
|||||||
terminated(|input| parse_expression(input, 0), eof)(input)
|
terminated(|input| parse_expression(input, 0), eof)(input)
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> std::fmt::Display for FilterCondition<'a> {
|
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
||||||
match self {
|
|
||||||
FilterCondition::Not(filter) => {
|
|
||||||
write!(f, "NOT ({filter})")
|
|
||||||
}
|
|
||||||
FilterCondition::Condition { fid, op } => {
|
|
||||||
write!(f, "{fid} {op}")
|
|
||||||
}
|
|
||||||
FilterCondition::In { fid, els } => {
|
|
||||||
write!(f, "{fid} IN[")?;
|
|
||||||
for el in els {
|
|
||||||
write!(f, "{el}, ")?;
|
|
||||||
}
|
|
||||||
write!(f, "]")
|
|
||||||
}
|
|
||||||
FilterCondition::Or(els) => {
|
|
||||||
write!(f, "OR[")?;
|
|
||||||
for el in els {
|
|
||||||
write!(f, "{el}, ")?;
|
|
||||||
}
|
|
||||||
write!(f, "]")
|
|
||||||
}
|
|
||||||
FilterCondition::And(els) => {
|
|
||||||
write!(f, "AND[")?;
|
|
||||||
for el in els {
|
|
||||||
write!(f, "{el}, ")?;
|
|
||||||
}
|
|
||||||
write!(f, "]")
|
|
||||||
}
|
|
||||||
FilterCondition::GeoLowerThan { point, radius } => {
|
|
||||||
write!(f, "_geoRadius({}, {}, {})", point[0], point[1], radius)
|
|
||||||
}
|
|
||||||
FilterCondition::GeoBoundingBox {
|
|
||||||
top_right_point: top_left_point,
|
|
||||||
bottom_left_point: bottom_right_point,
|
|
||||||
} => {
|
|
||||||
write!(
|
|
||||||
f,
|
|
||||||
"_geoBoundingBox([{}, {}], [{}, {}])",
|
|
||||||
top_left_point[0],
|
|
||||||
top_left_point[1],
|
|
||||||
bottom_right_point[0],
|
|
||||||
bottom_right_point[1]
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl<'a> std::fmt::Display for Condition<'a> {
|
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
||||||
match self {
|
|
||||||
Condition::GreaterThan(token) => write!(f, "> {token}"),
|
|
||||||
Condition::GreaterThanOrEqual(token) => write!(f, ">= {token}"),
|
|
||||||
Condition::Equal(token) => write!(f, "= {token}"),
|
|
||||||
Condition::NotEqual(token) => write!(f, "!= {token}"),
|
|
||||||
Condition::Null => write!(f, "IS NULL"),
|
|
||||||
Condition::Empty => write!(f, "IS EMPTY"),
|
|
||||||
Condition::Exists => write!(f, "EXISTS"),
|
|
||||||
Condition::LowerThan(token) => write!(f, "< {token}"),
|
|
||||||
Condition::LowerThanOrEqual(token) => write!(f, "<= {token}"),
|
|
||||||
Condition::Between { from, to } => write!(f, "{from} TO {to}"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl<'a> std::fmt::Display for Token<'a> {
|
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
||||||
write!(f, "{{{}}}", self.value())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
pub mod tests {
|
pub mod tests {
|
||||||
use FilterCondition as Fc;
|
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
/// Create a raw [Token]. You must specify the string that appear BEFORE your element followed by your element
|
/// Create a raw [Token]. You must specify the string that appear BEFORE your element followed by your element
|
||||||
@ -558,22 +485,14 @@ pub mod tests {
|
|||||||
unsafe { Span::new_from_raw_offset(offset, lines as u32, value, "") }.into()
|
unsafe { Span::new_from_raw_offset(offset, lines as u32, value, "") }.into()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn parse() {
|
||||||
|
use FilterCondition as Fc;
|
||||||
|
|
||||||
fn p(s: &str) -> impl std::fmt::Display + '_ {
|
fn p(s: &str) -> impl std::fmt::Display + '_ {
|
||||||
Fc::parse(s).unwrap().unwrap()
|
Fc::parse(s).unwrap().unwrap()
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn parse_escaped() {
|
|
||||||
insta::assert_display_snapshot!(p(r"title = 'foo\\'"), @r#"{title} = {foo\}"#);
|
|
||||||
insta::assert_display_snapshot!(p(r"title = 'foo\\\\'"), @r#"{title} = {foo\\}"#);
|
|
||||||
insta::assert_display_snapshot!(p(r"title = 'foo\\\\\\'"), @r#"{title} = {foo\\\}"#);
|
|
||||||
insta::assert_display_snapshot!(p(r"title = 'foo\\\\\\\\'"), @r#"{title} = {foo\\\\}"#);
|
|
||||||
// but it also works with other sequencies
|
|
||||||
insta::assert_display_snapshot!(p(r#"title = 'foo\x20\n\t\"\'"'"#), @"{title} = {foo \n\t\"\'\"}");
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn parse() {
|
|
||||||
// Test equal
|
// Test equal
|
||||||
insta::assert_display_snapshot!(p("channel = Ponce"), @"{channel} = {Ponce}");
|
insta::assert_display_snapshot!(p("channel = Ponce"), @"{channel} = {Ponce}");
|
||||||
insta::assert_display_snapshot!(p("subscribers = 12"), @"{subscribers} = {12}");
|
insta::assert_display_snapshot!(p("subscribers = 12"), @"{subscribers} = {12}");
|
||||||
@ -933,3 +852,74 @@ pub mod tests {
|
|||||||
assert_eq!(token.value(), s);
|
assert_eq!(token.value(), s);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl<'a> std::fmt::Display for FilterCondition<'a> {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
match self {
|
||||||
|
FilterCondition::Not(filter) => {
|
||||||
|
write!(f, "NOT ({filter})")
|
||||||
|
}
|
||||||
|
FilterCondition::Condition { fid, op } => {
|
||||||
|
write!(f, "{fid} {op}")
|
||||||
|
}
|
||||||
|
FilterCondition::In { fid, els } => {
|
||||||
|
write!(f, "{fid} IN[")?;
|
||||||
|
for el in els {
|
||||||
|
write!(f, "{el}, ")?;
|
||||||
|
}
|
||||||
|
write!(f, "]")
|
||||||
|
}
|
||||||
|
FilterCondition::Or(els) => {
|
||||||
|
write!(f, "OR[")?;
|
||||||
|
for el in els {
|
||||||
|
write!(f, "{el}, ")?;
|
||||||
|
}
|
||||||
|
write!(f, "]")
|
||||||
|
}
|
||||||
|
FilterCondition::And(els) => {
|
||||||
|
write!(f, "AND[")?;
|
||||||
|
for el in els {
|
||||||
|
write!(f, "{el}, ")?;
|
||||||
|
}
|
||||||
|
write!(f, "]")
|
||||||
|
}
|
||||||
|
FilterCondition::GeoLowerThan { point, radius } => {
|
||||||
|
write!(f, "_geoRadius({}, {}, {})", point[0], point[1], radius)
|
||||||
|
}
|
||||||
|
FilterCondition::GeoBoundingBox {
|
||||||
|
top_right_point: top_left_point,
|
||||||
|
bottom_left_point: bottom_right_point,
|
||||||
|
} => {
|
||||||
|
write!(
|
||||||
|
f,
|
||||||
|
"_geoBoundingBox([{}, {}], [{}, {}])",
|
||||||
|
top_left_point[0],
|
||||||
|
top_left_point[1],
|
||||||
|
bottom_right_point[0],
|
||||||
|
bottom_right_point[1]
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl<'a> std::fmt::Display for Condition<'a> {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
match self {
|
||||||
|
Condition::GreaterThan(token) => write!(f, "> {token}"),
|
||||||
|
Condition::GreaterThanOrEqual(token) => write!(f, ">= {token}"),
|
||||||
|
Condition::Equal(token) => write!(f, "= {token}"),
|
||||||
|
Condition::NotEqual(token) => write!(f, "!= {token}"),
|
||||||
|
Condition::Null => write!(f, "IS NULL"),
|
||||||
|
Condition::Empty => write!(f, "IS EMPTY"),
|
||||||
|
Condition::Exists => write!(f, "EXISTS"),
|
||||||
|
Condition::LowerThan(token) => write!(f, "< {token}"),
|
||||||
|
Condition::LowerThanOrEqual(token) => write!(f, "<= {token}"),
|
||||||
|
Condition::Between { from, to } => write!(f, "{from} TO {to}"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl<'a> std::fmt::Display for Token<'a> {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
write!(f, "{{{}}}", self.value())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -42,7 +42,7 @@ fn quoted_by(quote: char, input: Span) -> IResult<Token> {
|
|||||||
)));
|
)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// if it was preceded by a `\` or if it was anything else we can continue to advance
|
// if it was preceeded by a `\` or if it was anything else we can continue to advance
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok((
|
Ok((
|
||||||
@ -171,24 +171,7 @@ pub fn parse_value(input: Span) -> IResult<Token> {
|
|||||||
})
|
})
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
match unescaper::unescape(value.value()) {
|
|
||||||
Ok(content) => {
|
|
||||||
if content.len() != value.value().len() {
|
|
||||||
Ok((input, Token::new(value.original_span(), Some(content))))
|
|
||||||
} else {
|
|
||||||
Ok((input, value))
|
Ok((input, value))
|
||||||
}
|
|
||||||
}
|
|
||||||
Err(unescaper::Error::IncompleteStr(_)) => Err(nom::Err::Incomplete(nom::Needed::Unknown)),
|
|
||||||
Err(unescaper::Error::ParseIntError { .. }) => Err(nom::Err::Error(Error::new_from_kind(
|
|
||||||
value.original_span(),
|
|
||||||
ErrorKind::InvalidEscapedNumber,
|
|
||||||
))),
|
|
||||||
Err(unescaper::Error::InvalidChar { .. }) => Err(nom::Err::Error(Error::new_from_kind(
|
|
||||||
value.original_span(),
|
|
||||||
ErrorKind::MalformedValue,
|
|
||||||
))),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn is_value_component(c: char) -> bool {
|
fn is_value_component(c: char) -> bool {
|
||||||
@ -270,8 +253,8 @@ pub mod test {
|
|||||||
("aaaa", "", rtok("", "aaaa"), "aaaa"),
|
("aaaa", "", rtok("", "aaaa"), "aaaa"),
|
||||||
(r#"aa"aa"#, r#""aa"#, rtok("", "aa"), "aa"),
|
(r#"aa"aa"#, r#""aa"#, rtok("", "aa"), "aa"),
|
||||||
(r#"aa\"aa"#, r#""#, rtok("", r#"aa\"aa"#), r#"aa"aa"#),
|
(r#"aa\"aa"#, r#""#, rtok("", r#"aa\"aa"#), r#"aa"aa"#),
|
||||||
(r"aa\\\aa", r#""#, rtok("", r"aa\\\aa"), r"aa\\\aa"),
|
(r#"aa\\\aa"#, r#""#, rtok("", r#"aa\\\aa"#), r#"aa\\\aa"#),
|
||||||
(r#"aa\\"\aa"#, r#""\aa"#, rtok("", r"aa\\"), r"aa\\"),
|
(r#"aa\\"\aa"#, r#""\aa"#, rtok("", r#"aa\\"#), r#"aa\\"#),
|
||||||
(r#"aa\\\"\aa"#, r#""#, rtok("", r#"aa\\\"\aa"#), r#"aa\\"\aa"#),
|
(r#"aa\\\"\aa"#, r#""#, rtok("", r#"aa\\\"\aa"#), r#"aa\\"\aa"#),
|
||||||
(r#"\"\""#, r#""#, rtok("", r#"\"\""#), r#""""#),
|
(r#"\"\""#, r#""#, rtok("", r#"\"\""#), r#""""#),
|
||||||
];
|
];
|
||||||
@ -301,12 +284,12 @@ pub mod test {
|
|||||||
);
|
);
|
||||||
// simple quote
|
// simple quote
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
unescape(Span::new_extra(r"Hello \'World\'", ""), '\''),
|
unescape(Span::new_extra(r#"Hello \'World\'"#, ""), '\''),
|
||||||
r#"Hello 'World'"#.to_string()
|
r#"Hello 'World'"#.to_string()
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
unescape(Span::new_extra(r"Hello \\\'World\\\'", ""), '\''),
|
unescape(Span::new_extra(r#"Hello \\\'World\\\'"#, ""), '\''),
|
||||||
r"Hello \\'World\\'".to_string()
|
r#"Hello \\'World\\'"#.to_string()
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -335,19 +318,19 @@ pub mod test {
|
|||||||
("\"cha'nnel\"", "cha'nnel", false),
|
("\"cha'nnel\"", "cha'nnel", false),
|
||||||
("I'm tamo", "I", false),
|
("I'm tamo", "I", false),
|
||||||
// escaped thing but not quote
|
// escaped thing but not quote
|
||||||
(r#""\\""#, r"\", true),
|
(r#""\\""#, r#"\\"#, false),
|
||||||
(r#""\\\\\\""#, r"\\\", true),
|
(r#""\\\\\\""#, r#"\\\\\\"#, false),
|
||||||
(r#""aa\\aa""#, r"aa\aa", true),
|
(r#""aa\\aa""#, r#"aa\\aa"#, false),
|
||||||
// with double quote
|
// with double quote
|
||||||
(r#""Hello \"world\"""#, r#"Hello "world""#, true),
|
(r#""Hello \"world\"""#, r#"Hello "world""#, true),
|
||||||
(r#""Hello \\\"world\\\"""#, r#"Hello \"world\""#, true),
|
(r#""Hello \\\"world\\\"""#, r#"Hello \\"world\\""#, true),
|
||||||
(r#""I'm \"super\" tamo""#, r#"I'm "super" tamo"#, true),
|
(r#""I'm \"super\" tamo""#, r#"I'm "super" tamo"#, true),
|
||||||
(r#""\"\"""#, r#""""#, true),
|
(r#""\"\"""#, r#""""#, true),
|
||||||
// with simple quote
|
// with simple quote
|
||||||
(r"'Hello \'world\''", r#"Hello 'world'"#, true),
|
(r#"'Hello \'world\''"#, r#"Hello 'world'"#, true),
|
||||||
(r"'Hello \\\'world\\\''", r"Hello \'world\'", true),
|
(r#"'Hello \\\'world\\\''"#, r#"Hello \\'world\\'"#, true),
|
||||||
(r#"'I\'m "super" tamo'"#, r#"I'm "super" tamo"#, true),
|
(r#"'I\'m "super" tamo'"#, r#"I'm "super" tamo"#, true),
|
||||||
(r"'\'\''", r#"''"#, true),
|
(r#"'\'\''"#, r#"''"#, true),
|
||||||
];
|
];
|
||||||
|
|
||||||
for (input, expected, escaped) in test_case {
|
for (input, expected, escaped) in test_case {
|
||||||
@ -367,14 +350,7 @@ pub mod test {
|
|||||||
"Filter `{}` was not supposed to be escaped",
|
"Filter `{}` was not supposed to be escaped",
|
||||||
input
|
input
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(token.value(), expected, "Filter `{}` failed.", input);
|
||||||
token.value(),
|
|
||||||
expected,
|
|
||||||
"Filter `{}` failed by giving `{}` instead of `{}`.",
|
|
||||||
input,
|
|
||||||
token.value(),
|
|
||||||
expected
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -16,7 +16,7 @@ license.workspace = true
|
|||||||
serde_json = "1.0"
|
serde_json = "1.0"
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
criterion = { version = "0.5.1", features = ["html_reports"] }
|
criterion = { version = "0.4.0", features = ["html_reports"] }
|
||||||
|
|
||||||
[[bench]]
|
[[bench]]
|
||||||
name = "benchmarks"
|
name = "benchmarks"
|
||||||
|
@ -11,10 +11,10 @@ edition.workspace = true
|
|||||||
license.workspace = true
|
license.workspace = true
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
arbitrary = { version = "1.3.2", features = ["derive"] }
|
arbitrary = { version = "1.3.0", features = ["derive"] }
|
||||||
clap = { version = "4.4.17", features = ["derive"] }
|
clap = { version = "4.3.0", features = ["derive"] }
|
||||||
fastrand = "2.0.1"
|
fastrand = "1.9.0"
|
||||||
milli = { path = "../milli" }
|
milli = { path = "../milli" }
|
||||||
serde = { version = "1.0.195", features = ["derive"] }
|
serde = { version = "1.0.160", features = ["derive"] }
|
||||||
serde_json = { version = "1.0.111", features = ["preserve_order"] }
|
serde_json = { version = "1.0.95", features = ["preserve_order"] }
|
||||||
tempfile = "3.9.0"
|
tempfile = "3.5.0"
|
||||||
|
@ -113,7 +113,7 @@ fn main() {
|
|||||||
index.documents(&wtxn, res.documents_ids).unwrap();
|
index.documents(&wtxn, res.documents_ids).unwrap();
|
||||||
progression.fetch_add(1, Ordering::Relaxed);
|
progression.fetch_add(1, Ordering::Relaxed);
|
||||||
}
|
}
|
||||||
wtxn.abort();
|
wtxn.abort().unwrap();
|
||||||
});
|
});
|
||||||
if let err @ Err(_) = handle.join() {
|
if let err @ Err(_) = handle.join() {
|
||||||
stop.store(true, Ordering::Relaxed);
|
stop.store(true, Ordering::Relaxed);
|
||||||
|
@ -11,37 +11,29 @@ edition.workspace = true
|
|||||||
license.workspace = true
|
license.workspace = true
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
anyhow = "1.0.79"
|
anyhow = "1.0.70"
|
||||||
bincode = "1.3.3"
|
bincode = "1.3.3"
|
||||||
csv = "1.3.0"
|
csv = "1.2.1"
|
||||||
derive_builder = "0.12.0"
|
derive_builder = "0.12.0"
|
||||||
dump = { path = "../dump" }
|
dump = { path = "../dump" }
|
||||||
enum-iterator = "1.5.0"
|
enum-iterator = "1.4.0"
|
||||||
file-store = { path = "../file-store" }
|
file-store = { path = "../file-store" }
|
||||||
flate2 = "1.0.28"
|
log = "0.4.17"
|
||||||
meilisearch-auth = { path = "../meilisearch-auth" }
|
meilisearch-auth = { path = "../meilisearch-auth" }
|
||||||
meilisearch-types = { path = "../meilisearch-types" }
|
meilisearch-types = { path = "../meilisearch-types" }
|
||||||
page_size = "0.5.0"
|
page_size = "0.5.0"
|
||||||
puffin = { version = "0.16.0", features = ["serialization"] }
|
roaring = { version = "0.10.1", features = ["serde"] }
|
||||||
rayon = "1.8.1"
|
serde = { version = "1.0.160", features = ["derive"] }
|
||||||
roaring = { version = "0.10.2", features = ["serde"] }
|
serde_json = { version = "1.0.95", features = ["preserve_order"] }
|
||||||
serde = { version = "1.0.195", features = ["derive"] }
|
|
||||||
serde_json = { version = "1.0.111", features = ["preserve_order"] }
|
|
||||||
synchronoise = "1.0.1"
|
synchronoise = "1.0.1"
|
||||||
tempfile = "3.9.0"
|
tempfile = "3.5.0"
|
||||||
thiserror = "1.0.56"
|
thiserror = "1.0.40"
|
||||||
time = { version = "0.3.31", features = [
|
time = { version = "0.3.20", features = ["serde-well-known", "formatting", "parsing", "macros"] }
|
||||||
"serde-well-known",
|
uuid = { version = "1.3.1", features = ["serde", "v4"] }
|
||||||
"formatting",
|
|
||||||
"parsing",
|
|
||||||
"macros",
|
|
||||||
] }
|
|
||||||
tracing = "0.1.40"
|
|
||||||
ureq = "2.9.1"
|
|
||||||
uuid = { version = "1.6.1", features = ["serde", "v4"] }
|
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
big_s = "1.0.2"
|
big_s = "1.0.2"
|
||||||
crossbeam = "0.8.4"
|
crossbeam = "0.8.2"
|
||||||
insta = { version = "1.34.0", features = ["json", "redactions"] }
|
insta = { version = "1.29.0", features = ["json", "redactions"] }
|
||||||
meili-snap = { path = "../meili-snap" }
|
meili-snap = { path = "../meili-snap" }
|
||||||
|
nelson = { git = "https://github.com/meilisearch/nelson.git", rev = "675f13885548fb415ead8fbb447e9e6d9314000a"}
|
||||||
|
@ -870,7 +870,7 @@ mod tests {
|
|||||||
debug_snapshot!(autobatch_from(false,None, [doc_imp(UpdateDocuments, false, None), settings(false), idx_del()]), @"Some((IndexDeletion { ids: [0, 2, 1] }, false))");
|
debug_snapshot!(autobatch_from(false,None, [doc_imp(UpdateDocuments, false, None), settings(false), idx_del()]), @"Some((IndexDeletion { ids: [0, 2, 1] }, false))");
|
||||||
debug_snapshot!(autobatch_from(false,None, [doc_imp(ReplaceDocuments,false, None), settings(false), doc_clr(), idx_del()]), @"Some((IndexDeletion { ids: [1, 3, 0, 2] }, false))");
|
debug_snapshot!(autobatch_from(false,None, [doc_imp(ReplaceDocuments,false, None), settings(false), doc_clr(), idx_del()]), @"Some((IndexDeletion { ids: [1, 3, 0, 2] }, false))");
|
||||||
debug_snapshot!(autobatch_from(false,None, [doc_imp(UpdateDocuments, false, None), settings(false), doc_clr(), idx_del()]), @"Some((IndexDeletion { ids: [1, 3, 0, 2] }, false))");
|
debug_snapshot!(autobatch_from(false,None, [doc_imp(UpdateDocuments, false, None), settings(false), doc_clr(), idx_del()]), @"Some((IndexDeletion { ids: [1, 3, 0, 2] }, false))");
|
||||||
// The third and final case is when the first task doesn't create an index but is directly followed by a task creating an index. In this case we can't batch whit what
|
// The third and final case is when the first task doesn't create an index but is directly followed by a task creating an index. In this case we can't batch whith what
|
||||||
// follows because we first need to process the erronous batch.
|
// follows because we first need to process the erronous batch.
|
||||||
debug_snapshot!(autobatch_from(false,None, [doc_imp(ReplaceDocuments,false, None), settings(true), idx_del()]), @"Some((DocumentOperation { method: ReplaceDocuments, allow_index_creation: false, primary_key: None, operation_ids: [0] }, false))");
|
debug_snapshot!(autobatch_from(false,None, [doc_imp(ReplaceDocuments,false, None), settings(true), idx_del()]), @"Some((DocumentOperation { method: ReplaceDocuments, allow_index_creation: false, primary_key: None, operation_ids: [0] }, false))");
|
||||||
debug_snapshot!(autobatch_from(false,None, [doc_imp(UpdateDocuments, false, None), settings(true), idx_del()]), @"Some((DocumentOperation { method: UpdateDocuments, allow_index_creation: false, primary_key: None, operation_ids: [0] }, false))");
|
debug_snapshot!(autobatch_from(false,None, [doc_imp(UpdateDocuments, false, None), settings(true), idx_del()]), @"Some((DocumentOperation { method: UpdateDocuments, allow_index_creation: false, primary_key: None, operation_ids: [0] }, false))");
|
||||||
|
@ -19,19 +19,20 @@ one indexing operation.
|
|||||||
|
|
||||||
use std::collections::{BTreeSet, HashSet};
|
use std::collections::{BTreeSet, HashSet};
|
||||||
use std::ffi::OsStr;
|
use std::ffi::OsStr;
|
||||||
use std::fmt;
|
|
||||||
use std::fs::{self, File};
|
use std::fs::{self, File};
|
||||||
use std::io::BufWriter;
|
use std::io::BufWriter;
|
||||||
|
|
||||||
use dump::IndexMetadata;
|
use dump::IndexMetadata;
|
||||||
|
use log::{debug, error, info};
|
||||||
use meilisearch_types::error::Code;
|
use meilisearch_types::error::Code;
|
||||||
use meilisearch_types::heed::{RoTxn, RwTxn};
|
use meilisearch_types::heed::{RoTxn, RwTxn};
|
||||||
use meilisearch_types::milli::documents::{obkv_to_object, DocumentsBatchReader};
|
use meilisearch_types::milli::documents::{obkv_to_object, DocumentsBatchReader};
|
||||||
use meilisearch_types::milli::heed::CompactionOption;
|
use meilisearch_types::milli::heed::CompactionOption;
|
||||||
use meilisearch_types::milli::update::{
|
use meilisearch_types::milli::update::{
|
||||||
IndexDocumentsConfig, IndexDocumentsMethod, IndexerConfig, Settings as MilliSettings,
|
DeleteDocuments, DocumentDeletionResult, IndexDocumentsConfig, IndexDocumentsMethod,
|
||||||
|
Settings as MilliSettings,
|
||||||
};
|
};
|
||||||
use meilisearch_types::milli::{self, Filter};
|
use meilisearch_types::milli::{self, Filter, BEU32};
|
||||||
use meilisearch_types::settings::{apply_settings_to_builder, Settings, Unchecked};
|
use meilisearch_types::settings::{apply_settings_to_builder, Settings, Unchecked};
|
||||||
use meilisearch_types::tasks::{Details, IndexSwap, Kind, KindWithContent, Status, Task};
|
use meilisearch_types::tasks::{Details, IndexSwap, Kind, KindWithContent, Status, Task};
|
||||||
use meilisearch_types::{compression, Index, VERSION_FILE_NAME};
|
use meilisearch_types::{compression, Index, VERSION_FILE_NAME};
|
||||||
@ -42,7 +43,7 @@ use uuid::Uuid;
|
|||||||
|
|
||||||
use crate::autobatcher::{self, BatchKind};
|
use crate::autobatcher::{self, BatchKind};
|
||||||
use crate::utils::{self, swap_index_uid_in_task};
|
use crate::utils::{self, swap_index_uid_in_task};
|
||||||
use crate::{Error, IndexScheduler, MustStopProcessing, ProcessingTasks, Result, TaskId};
|
use crate::{Error, IndexScheduler, ProcessingTasks, Result, TaskId};
|
||||||
|
|
||||||
/// Represents a combination of tasks that can all be processed at the same time.
|
/// Represents a combination of tasks that can all be processed at the same time.
|
||||||
///
|
///
|
||||||
@ -59,13 +60,17 @@ pub(crate) enum Batch {
|
|||||||
/// The list of tasks that were processing when this task cancelation appeared.
|
/// The list of tasks that were processing when this task cancelation appeared.
|
||||||
previous_processing_tasks: RoaringBitmap,
|
previous_processing_tasks: RoaringBitmap,
|
||||||
},
|
},
|
||||||
TaskDeletions(Vec<Task>),
|
TaskDeletion(Task),
|
||||||
SnapshotCreation(Vec<Task>),
|
SnapshotCreation(Vec<Task>),
|
||||||
Dump(Task),
|
Dump(Task),
|
||||||
IndexOperation {
|
IndexOperation {
|
||||||
op: IndexOperation,
|
op: IndexOperation,
|
||||||
must_create_index: bool,
|
must_create_index: bool,
|
||||||
},
|
},
|
||||||
|
IndexDocumentDeletionByFilter {
|
||||||
|
index_uid: String,
|
||||||
|
task: Task,
|
||||||
|
},
|
||||||
IndexCreation {
|
IndexCreation {
|
||||||
index_uid: String,
|
index_uid: String,
|
||||||
primary_key: Option<String>,
|
primary_key: Option<String>,
|
||||||
@ -103,9 +108,11 @@ pub(crate) enum IndexOperation {
|
|||||||
operations: Vec<DocumentOperation>,
|
operations: Vec<DocumentOperation>,
|
||||||
tasks: Vec<Task>,
|
tasks: Vec<Task>,
|
||||||
},
|
},
|
||||||
IndexDocumentDeletionByFilter {
|
DocumentDeletion {
|
||||||
index_uid: String,
|
index_uid: String,
|
||||||
task: Task,
|
// The vec associated with each document deletion tasks.
|
||||||
|
documents: Vec<Vec<String>>,
|
||||||
|
tasks: Vec<Task>,
|
||||||
},
|
},
|
||||||
DocumentClear {
|
DocumentClear {
|
||||||
index_uid: String,
|
index_uid: String,
|
||||||
@ -142,27 +149,23 @@ pub(crate) enum IndexOperation {
|
|||||||
|
|
||||||
impl Batch {
|
impl Batch {
|
||||||
/// Return the task ids associated with this batch.
|
/// Return the task ids associated with this batch.
|
||||||
pub fn ids(&self) -> RoaringBitmap {
|
pub fn ids(&self) -> Vec<TaskId> {
|
||||||
match self {
|
match self {
|
||||||
Batch::TaskCancelation { task, .. }
|
Batch::TaskCancelation { task, .. }
|
||||||
|
| Batch::TaskDeletion(task)
|
||||||
| Batch::Dump(task)
|
| Batch::Dump(task)
|
||||||
| Batch::IndexCreation { task, .. }
|
| Batch::IndexCreation { task, .. }
|
||||||
| Batch::IndexUpdate { task, .. } => {
|
| Batch::IndexDocumentDeletionByFilter { task, .. }
|
||||||
RoaringBitmap::from_sorted_iter(std::iter::once(task.uid)).unwrap()
|
| Batch::IndexUpdate { task, .. } => vec![task.uid],
|
||||||
}
|
Batch::SnapshotCreation(tasks) | Batch::IndexDeletion { tasks, .. } => {
|
||||||
Batch::SnapshotCreation(tasks)
|
tasks.iter().map(|task| task.uid).collect()
|
||||||
| Batch::TaskDeletions(tasks)
|
|
||||||
| Batch::IndexDeletion { tasks, .. } => {
|
|
||||||
RoaringBitmap::from_iter(tasks.iter().map(|task| task.uid))
|
|
||||||
}
|
}
|
||||||
Batch::IndexOperation { op, .. } => match op {
|
Batch::IndexOperation { op, .. } => match op {
|
||||||
IndexOperation::DocumentOperation { tasks, .. }
|
IndexOperation::DocumentOperation { tasks, .. }
|
||||||
|
| IndexOperation::DocumentDeletion { tasks, .. }
|
||||||
| IndexOperation::Settings { tasks, .. }
|
| IndexOperation::Settings { tasks, .. }
|
||||||
| IndexOperation::DocumentClear { tasks, .. } => {
|
| IndexOperation::DocumentClear { tasks, .. } => {
|
||||||
RoaringBitmap::from_iter(tasks.iter().map(|task| task.uid))
|
tasks.iter().map(|task| task.uid).collect()
|
||||||
}
|
|
||||||
IndexOperation::IndexDocumentDeletionByFilter { task, .. } => {
|
|
||||||
RoaringBitmap::from_sorted_iter(std::iter::once(task.uid)).unwrap()
|
|
||||||
}
|
}
|
||||||
IndexOperation::SettingsAndDocumentOperation {
|
IndexOperation::SettingsAndDocumentOperation {
|
||||||
document_import_tasks: tasks,
|
document_import_tasks: tasks,
|
||||||
@ -173,11 +176,9 @@ impl Batch {
|
|||||||
cleared_tasks: tasks,
|
cleared_tasks: tasks,
|
||||||
settings_tasks: other,
|
settings_tasks: other,
|
||||||
..
|
..
|
||||||
} => RoaringBitmap::from_iter(tasks.iter().chain(other).map(|task| task.uid)),
|
} => tasks.iter().chain(other).map(|task| task.uid).collect(),
|
||||||
},
|
},
|
||||||
Batch::IndexSwap { task } => {
|
Batch::IndexSwap { task } => vec![task.uid],
|
||||||
RoaringBitmap::from_sorted_iter(std::iter::once(task.uid)).unwrap()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -186,37 +187,15 @@ impl Batch {
|
|||||||
use Batch::*;
|
use Batch::*;
|
||||||
match self {
|
match self {
|
||||||
TaskCancelation { .. }
|
TaskCancelation { .. }
|
||||||
| TaskDeletions(_)
|
| TaskDeletion(_)
|
||||||
| SnapshotCreation(_)
|
| SnapshotCreation(_)
|
||||||
| Dump(_)
|
| Dump(_)
|
||||||
| IndexSwap { .. } => None,
|
| IndexSwap { .. } => None,
|
||||||
IndexOperation { op, .. } => Some(op.index_uid()),
|
IndexOperation { op, .. } => Some(op.index_uid()),
|
||||||
IndexCreation { index_uid, .. }
|
IndexCreation { index_uid, .. }
|
||||||
| IndexUpdate { index_uid, .. }
|
| IndexUpdate { index_uid, .. }
|
||||||
| IndexDeletion { index_uid, .. } => Some(index_uid),
|
| IndexDeletion { index_uid, .. }
|
||||||
}
|
| IndexDocumentDeletionByFilter { index_uid, .. } => Some(index_uid),
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl fmt::Display for Batch {
|
|
||||||
/// A text used when we debug the profiling reports.
|
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
||||||
let index_uid = self.index_uid();
|
|
||||||
let tasks = self.ids();
|
|
||||||
match self {
|
|
||||||
Batch::TaskCancelation { .. } => f.write_str("TaskCancelation")?,
|
|
||||||
Batch::TaskDeletions(_) => f.write_str("TaskDeletion")?,
|
|
||||||
Batch::SnapshotCreation(_) => f.write_str("SnapshotCreation")?,
|
|
||||||
Batch::Dump(_) => f.write_str("Dump")?,
|
|
||||||
Batch::IndexOperation { op, .. } => write!(f, "{op}")?,
|
|
||||||
Batch::IndexCreation { .. } => f.write_str("IndexCreation")?,
|
|
||||||
Batch::IndexUpdate { .. } => f.write_str("IndexUpdate")?,
|
|
||||||
Batch::IndexDeletion { .. } => f.write_str("IndexDeletion")?,
|
|
||||||
Batch::IndexSwap { .. } => f.write_str("IndexSwap")?,
|
|
||||||
};
|
|
||||||
match index_uid {
|
|
||||||
Some(name) => f.write_fmt(format_args!(" on {name:?} from tasks: {tasks:?}")),
|
|
||||||
None => f.write_fmt(format_args!(" from tasks: {tasks:?}")),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -225,7 +204,7 @@ impl IndexOperation {
|
|||||||
pub fn index_uid(&self) -> &str {
|
pub fn index_uid(&self) -> &str {
|
||||||
match self {
|
match self {
|
||||||
IndexOperation::DocumentOperation { index_uid, .. }
|
IndexOperation::DocumentOperation { index_uid, .. }
|
||||||
| IndexOperation::IndexDocumentDeletionByFilter { index_uid, .. }
|
| IndexOperation::DocumentDeletion { index_uid, .. }
|
||||||
| IndexOperation::DocumentClear { index_uid, .. }
|
| IndexOperation::DocumentClear { index_uid, .. }
|
||||||
| IndexOperation::Settings { index_uid, .. }
|
| IndexOperation::Settings { index_uid, .. }
|
||||||
| IndexOperation::DocumentClearAndSetting { index_uid, .. }
|
| IndexOperation::DocumentClearAndSetting { index_uid, .. }
|
||||||
@ -234,27 +213,6 @@ impl IndexOperation {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl fmt::Display for IndexOperation {
|
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
||||||
match self {
|
|
||||||
IndexOperation::DocumentOperation { .. } => {
|
|
||||||
f.write_str("IndexOperation::DocumentOperation")
|
|
||||||
}
|
|
||||||
IndexOperation::IndexDocumentDeletionByFilter { .. } => {
|
|
||||||
f.write_str("IndexOperation::IndexDocumentDeletionByFilter")
|
|
||||||
}
|
|
||||||
IndexOperation::DocumentClear { .. } => f.write_str("IndexOperation::DocumentClear"),
|
|
||||||
IndexOperation::Settings { .. } => f.write_str("IndexOperation::Settings"),
|
|
||||||
IndexOperation::DocumentClearAndSetting { .. } => {
|
|
||||||
f.write_str("IndexOperation::DocumentClearAndSetting")
|
|
||||||
}
|
|
||||||
IndexOperation::SettingsAndDocumentOperation { .. } => {
|
|
||||||
f.write_str("IndexOperation::SettingsAndDocumentOperation")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl IndexScheduler {
|
impl IndexScheduler {
|
||||||
/// Convert an [`BatchKind`](crate::autobatcher::BatchKind) into a [`Batch`].
|
/// Convert an [`BatchKind`](crate::autobatcher::BatchKind) into a [`Batch`].
|
||||||
///
|
///
|
||||||
@ -281,12 +239,9 @@ impl IndexScheduler {
|
|||||||
let task = self.get_task(rtxn, id)?.ok_or(Error::CorruptedTaskQueue)?;
|
let task = self.get_task(rtxn, id)?.ok_or(Error::CorruptedTaskQueue)?;
|
||||||
match &task.kind {
|
match &task.kind {
|
||||||
KindWithContent::DocumentDeletionByFilter { index_uid, .. } => {
|
KindWithContent::DocumentDeletionByFilter { index_uid, .. } => {
|
||||||
Ok(Some(Batch::IndexOperation {
|
Ok(Some(Batch::IndexDocumentDeletionByFilter {
|
||||||
op: IndexOperation::IndexDocumentDeletionByFilter {
|
|
||||||
index_uid: index_uid.clone(),
|
index_uid: index_uid.clone(),
|
||||||
task,
|
task,
|
||||||
},
|
|
||||||
must_create_index: false,
|
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
_ => unreachable!(),
|
_ => unreachable!(),
|
||||||
@ -342,27 +297,18 @@ impl IndexScheduler {
|
|||||||
BatchKind::DocumentDeletion { deletion_ids } => {
|
BatchKind::DocumentDeletion { deletion_ids } => {
|
||||||
let tasks = self.get_existing_tasks(rtxn, deletion_ids)?;
|
let tasks = self.get_existing_tasks(rtxn, deletion_ids)?;
|
||||||
|
|
||||||
let mut operations = Vec::with_capacity(tasks.len());
|
let mut documents = Vec::new();
|
||||||
let mut documents_counts = Vec::with_capacity(tasks.len());
|
|
||||||
for task in &tasks {
|
for task in &tasks {
|
||||||
match task.kind {
|
match task.kind {
|
||||||
KindWithContent::DocumentDeletion { ref documents_ids, .. } => {
|
KindWithContent::DocumentDeletion { ref documents_ids, .. } => {
|
||||||
operations.push(DocumentOperation::Delete(documents_ids.clone()));
|
documents.push(documents_ids.clone())
|
||||||
documents_counts.push(documents_ids.len() as u64);
|
|
||||||
}
|
}
|
||||||
_ => unreachable!(),
|
_ => unreachable!(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(Some(Batch::IndexOperation {
|
Ok(Some(Batch::IndexOperation {
|
||||||
op: IndexOperation::DocumentOperation {
|
op: IndexOperation::DocumentDeletion { index_uid, documents, tasks },
|
||||||
index_uid,
|
|
||||||
primary_key: None,
|
|
||||||
method: IndexDocumentsMethod::ReplaceDocuments,
|
|
||||||
documents_counts,
|
|
||||||
operations,
|
|
||||||
tasks,
|
|
||||||
},
|
|
||||||
must_create_index,
|
must_create_index,
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
@ -521,13 +467,10 @@ impl IndexScheduler {
|
|||||||
/// 3. We get the *next* snapshot to process.
|
/// 3. We get the *next* snapshot to process.
|
||||||
/// 4. We get the *next* dump to process.
|
/// 4. We get the *next* dump to process.
|
||||||
/// 5. We get the *next* tasks to process for a specific index.
|
/// 5. We get the *next* tasks to process for a specific index.
|
||||||
#[tracing::instrument(level = "trace", skip(self, rtxn), target = "indexing::scheduler")]
|
|
||||||
pub(crate) fn create_next_batch(&self, rtxn: &RoTxn) -> Result<Option<Batch>> {
|
pub(crate) fn create_next_batch(&self, rtxn: &RoTxn) -> Result<Option<Batch>> {
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
self.maybe_fail(crate::tests::FailureLocation::InsideCreateBatch)?;
|
self.maybe_fail(crate::tests::FailureLocation::InsideCreateBatch)?;
|
||||||
|
|
||||||
puffin::profile_function!();
|
|
||||||
|
|
||||||
let enqueued = &self.get_status(rtxn, Status::Enqueued)?;
|
let enqueued = &self.get_status(rtxn, Status::Enqueued)?;
|
||||||
let to_cancel = self.get_kind(rtxn, Kind::TaskCancelation)? & enqueued;
|
let to_cancel = self.get_kind(rtxn, Kind::TaskCancelation)? & enqueued;
|
||||||
|
|
||||||
@ -546,9 +489,9 @@ impl IndexScheduler {
|
|||||||
|
|
||||||
// 2. we get the next task to delete
|
// 2. we get the next task to delete
|
||||||
let to_delete = self.get_kind(rtxn, Kind::TaskDeletion)? & enqueued;
|
let to_delete = self.get_kind(rtxn, Kind::TaskDeletion)? & enqueued;
|
||||||
if !to_delete.is_empty() {
|
if let Some(task_id) = to_delete.min() {
|
||||||
let tasks = self.get_existing_tasks(rtxn, to_delete)?;
|
let task = self.get_task(rtxn, task_id)?.ok_or(Error::CorruptedTaskQueue)?;
|
||||||
return Ok(Some(Batch::TaskDeletions(tasks)));
|
return Ok(Some(Batch::TaskDeletion(task)));
|
||||||
}
|
}
|
||||||
|
|
||||||
// 3. we batch the snapshot.
|
// 3. we batch the snapshot.
|
||||||
@ -591,9 +534,7 @@ impl IndexScheduler {
|
|||||||
let index_tasks = self.index_tasks(rtxn, index_name)? & enqueued;
|
let index_tasks = self.index_tasks(rtxn, index_name)? & enqueued;
|
||||||
|
|
||||||
// If autobatching is disabled we only take one task at a time.
|
// If autobatching is disabled we only take one task at a time.
|
||||||
// Otherwise, we take only a maximum of tasks to create batches.
|
let tasks_limit = if self.autobatching_enabled { usize::MAX } else { 1 };
|
||||||
let tasks_limit =
|
|
||||||
if self.autobatching_enabled { self.max_number_of_batched_tasks } else { 1 };
|
|
||||||
|
|
||||||
let enqueued = index_tasks
|
let enqueued = index_tasks
|
||||||
.into_iter()
|
.into_iter()
|
||||||
@ -627,7 +568,6 @@ impl IndexScheduler {
|
|||||||
/// The list of tasks that were processed. The metadata of each task in the returned
|
/// The list of tasks that were processed. The metadata of each task in the returned
|
||||||
/// list is updated accordingly, with the exception of the its date fields
|
/// list is updated accordingly, with the exception of the its date fields
|
||||||
/// [`finished_at`](meilisearch_types::tasks::Task::finished_at) and [`started_at`](meilisearch_types::tasks::Task::started_at).
|
/// [`finished_at`](meilisearch_types::tasks::Task::finished_at) and [`started_at`](meilisearch_types::tasks::Task::started_at).
|
||||||
#[tracing::instrument(level = "trace", skip(self, batch), target = "indexing::scheduler", fields(batch=batch.to_string()))]
|
|
||||||
pub(crate) fn process_batch(&self, batch: Batch) -> Result<Vec<Task>> {
|
pub(crate) fn process_batch(&self, batch: Batch) -> Result<Vec<Task>> {
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
{
|
{
|
||||||
@ -635,9 +575,6 @@ impl IndexScheduler {
|
|||||||
self.maybe_fail(crate::tests::FailureLocation::PanicInsideProcessBatch)?;
|
self.maybe_fail(crate::tests::FailureLocation::PanicInsideProcessBatch)?;
|
||||||
self.breakpoint(crate::Breakpoint::InsideProcessBatch);
|
self.breakpoint(crate::Breakpoint::InsideProcessBatch);
|
||||||
}
|
}
|
||||||
|
|
||||||
puffin::profile_function!(batch.to_string());
|
|
||||||
|
|
||||||
match batch {
|
match batch {
|
||||||
Batch::TaskCancelation { mut task, previous_started_at, previous_processing_tasks } => {
|
Batch::TaskCancelation { mut task, previous_started_at, previous_processing_tasks } => {
|
||||||
// 1. Retrieve the tasks that matched the query at enqueue-time.
|
// 1. Retrieve the tasks that matched the query at enqueue-time.
|
||||||
@ -677,10 +614,9 @@ impl IndexScheduler {
|
|||||||
Ok(()) => {
|
Ok(()) => {
|
||||||
for content_uuid in canceled_tasks_content_uuids {
|
for content_uuid in canceled_tasks_content_uuids {
|
||||||
if let Err(error) = self.delete_update_file(content_uuid) {
|
if let Err(error) = self.delete_update_file(content_uuid) {
|
||||||
tracing::error!(
|
error!(
|
||||||
file_content_uuid = %content_uuid,
|
"We failed deleting the content file indentified as {}: {}",
|
||||||
%error,
|
content_uuid, error
|
||||||
"Failed deleting content file"
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -690,31 +626,19 @@ impl IndexScheduler {
|
|||||||
|
|
||||||
Ok(vec![task])
|
Ok(vec![task])
|
||||||
}
|
}
|
||||||
Batch::TaskDeletions(mut tasks) => {
|
Batch::TaskDeletion(mut task) => {
|
||||||
// 1. Retrieve the tasks that matched the query at enqueue-time.
|
// 1. Retrieve the tasks that matched the query at enqueue-time.
|
||||||
let mut matched_tasks = RoaringBitmap::new();
|
let matched_tasks =
|
||||||
|
|
||||||
for task in tasks.iter() {
|
|
||||||
if let KindWithContent::TaskDeletion { tasks, query: _ } = &task.kind {
|
if let KindWithContent::TaskDeletion { tasks, query: _ } = &task.kind {
|
||||||
matched_tasks |= tasks;
|
tasks
|
||||||
} else {
|
} else {
|
||||||
unreachable!()
|
unreachable!()
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut wtxn = self.env.write_txn()?;
|
|
||||||
let mut deleted_tasks = self.delete_matched_tasks(&mut wtxn, &matched_tasks)?;
|
|
||||||
wtxn.commit()?;
|
|
||||||
|
|
||||||
for task in tasks.iter_mut() {
|
|
||||||
task.status = Status::Succeeded;
|
|
||||||
let KindWithContent::TaskDeletion { tasks, query: _ } = &task.kind else {
|
|
||||||
unreachable!()
|
|
||||||
};
|
};
|
||||||
|
|
||||||
let deleted_tasks_count = deleted_tasks.intersection_len(tasks);
|
let mut wtxn = self.env.write_txn()?;
|
||||||
deleted_tasks -= tasks;
|
let deleted_tasks_count = self.delete_matched_tasks(&mut wtxn, matched_tasks)?;
|
||||||
|
|
||||||
|
task.status = Status::Succeeded;
|
||||||
match &mut task.details {
|
match &mut task.details {
|
||||||
Some(Details::TaskDeletion {
|
Some(Details::TaskDeletion {
|
||||||
matched_tasks: _,
|
matched_tasks: _,
|
||||||
@ -725,8 +649,8 @@ impl IndexScheduler {
|
|||||||
}
|
}
|
||||||
_ => unreachable!(),
|
_ => unreachable!(),
|
||||||
}
|
}
|
||||||
}
|
wtxn.commit()?;
|
||||||
Ok(tasks)
|
Ok(vec![task])
|
||||||
}
|
}
|
||||||
Batch::SnapshotCreation(mut tasks) => {
|
Batch::SnapshotCreation(mut tasks) => {
|
||||||
fs::create_dir_all(&self.snapshots_path)?;
|
fs::create_dir_all(&self.snapshots_path)?;
|
||||||
@ -738,7 +662,7 @@ impl IndexScheduler {
|
|||||||
|
|
||||||
// 2. Snapshot the index-scheduler LMDB env
|
// 2. Snapshot the index-scheduler LMDB env
|
||||||
//
|
//
|
||||||
// When we call copy_to_file, LMDB opens a read transaction by itself,
|
// When we call copy_to_path, LMDB opens a read transaction by itself,
|
||||||
// we can't provide our own. It is an issue as we would like to know
|
// we can't provide our own. It is an issue as we would like to know
|
||||||
// the update files to copy but new ones can be enqueued between the copy
|
// the update files to copy but new ones can be enqueued between the copy
|
||||||
// of the env and the new transaction we open to retrieve the enqueued tasks.
|
// of the env and the new transaction we open to retrieve the enqueued tasks.
|
||||||
@ -751,7 +675,7 @@ impl IndexScheduler {
|
|||||||
// 2.1 First copy the LMDB env of the index-scheduler
|
// 2.1 First copy the LMDB env of the index-scheduler
|
||||||
let dst = temp_snapshot_dir.path().join("tasks");
|
let dst = temp_snapshot_dir.path().join("tasks");
|
||||||
fs::create_dir_all(&dst)?;
|
fs::create_dir_all(&dst)?;
|
||||||
self.env.copy_to_file(dst.join("data.mdb"), CompactionOption::Enabled)?;
|
self.env.copy_to_path(dst.join("data.mdb"), CompactionOption::Enabled)?;
|
||||||
|
|
||||||
// 2.2 Create a read transaction on the index-scheduler
|
// 2.2 Create a read transaction on the index-scheduler
|
||||||
let rtxn = self.env.read_txn()?;
|
let rtxn = self.env.read_txn()?;
|
||||||
@ -776,7 +700,7 @@ impl IndexScheduler {
|
|||||||
let index = self.index_mapper.index(&rtxn, name)?;
|
let index = self.index_mapper.index(&rtxn, name)?;
|
||||||
let dst = temp_snapshot_dir.path().join("indexes").join(uuid.to_string());
|
let dst = temp_snapshot_dir.path().join("indexes").join(uuid.to_string());
|
||||||
fs::create_dir_all(&dst)?;
|
fs::create_dir_all(&dst)?;
|
||||||
index.copy_to_file(dst.join("data.mdb"), CompactionOption::Enabled)?;
|
index.copy_to_path(dst.join("data.mdb"), CompactionOption::Enabled)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
drop(rtxn);
|
drop(rtxn);
|
||||||
@ -789,7 +713,7 @@ impl IndexScheduler {
|
|||||||
.map_size(1024 * 1024 * 1024) // 1 GiB
|
.map_size(1024 * 1024 * 1024) // 1 GiB
|
||||||
.max_dbs(2)
|
.max_dbs(2)
|
||||||
.open(&self.auth_path)?;
|
.open(&self.auth_path)?;
|
||||||
auth.copy_to_file(dst.join("data.mdb"), CompactionOption::Enabled)?;
|
auth.copy_to_path(dst.join("data.mdb"), CompactionOption::Enabled)?;
|
||||||
|
|
||||||
// 5. Copy and tarball the flat snapshot
|
// 5. Copy and tarball the flat snapshot
|
||||||
// 5.1 Find the original name of the database
|
// 5.1 Find the original name of the database
|
||||||
@ -845,10 +769,6 @@ impl IndexScheduler {
|
|||||||
// 2. dump the tasks
|
// 2. dump the tasks
|
||||||
let mut dump_tasks = dump.create_tasks_queue()?;
|
let mut dump_tasks = dump.create_tasks_queue()?;
|
||||||
for ret in self.all_tasks.iter(&rtxn)? {
|
for ret in self.all_tasks.iter(&rtxn)? {
|
||||||
if self.must_stop_processing.get() {
|
|
||||||
return Err(Error::AbortedTask);
|
|
||||||
}
|
|
||||||
|
|
||||||
let (_, mut t) = ret?;
|
let (_, mut t) = ret?;
|
||||||
let status = t.status;
|
let status = t.status;
|
||||||
let content_file = t.content_uuid();
|
let content_file = t.content_uuid();
|
||||||
@ -869,9 +789,6 @@ impl IndexScheduler {
|
|||||||
|
|
||||||
// 2.1. Dump the `content_file` associated with the task if there is one and the task is not finished yet.
|
// 2.1. Dump the `content_file` associated with the task if there is one and the task is not finished yet.
|
||||||
if let Some(content_file) = content_file {
|
if let Some(content_file) = content_file {
|
||||||
if self.must_stop_processing.get() {
|
|
||||||
return Err(Error::AbortedTask);
|
|
||||||
}
|
|
||||||
if status == Status::Enqueued {
|
if status == Status::Enqueued {
|
||||||
let content_file = self.file_store.get_update(content_file)?;
|
let content_file = self.file_store.get_update(content_file)?;
|
||||||
|
|
||||||
@ -911,35 +828,21 @@ impl IndexScheduler {
|
|||||||
|
|
||||||
// 3.1. Dump the documents
|
// 3.1. Dump the documents
|
||||||
for ret in index.all_documents(&rtxn)? {
|
for ret in index.all_documents(&rtxn)? {
|
||||||
if self.must_stop_processing.get() {
|
|
||||||
return Err(Error::AbortedTask);
|
|
||||||
}
|
|
||||||
let (_id, doc) = ret?;
|
let (_id, doc) = ret?;
|
||||||
let document = milli::obkv_to_json(&all_fields, &fields_ids_map, doc)?;
|
let document = milli::obkv_to_json(&all_fields, &fields_ids_map, doc)?;
|
||||||
index_dumper.push_document(&document)?;
|
index_dumper.push_document(&document)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 3.2. Dump the settings
|
// 3.2. Dump the settings
|
||||||
let settings = meilisearch_types::settings::settings(
|
let settings = meilisearch_types::settings::settings(index, &rtxn)?;
|
||||||
index,
|
|
||||||
&rtxn,
|
|
||||||
meilisearch_types::settings::SecretPolicy::RevealSecrets,
|
|
||||||
)?;
|
|
||||||
index_dumper.settings(&settings)?;
|
index_dumper.settings(&settings)?;
|
||||||
Ok(())
|
Ok(())
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
// 4. Dump experimental feature settings
|
|
||||||
let features = self.features().runtime_features();
|
|
||||||
dump.create_experimental_features(features)?;
|
|
||||||
|
|
||||||
let dump_uid = started_at.format(format_description!(
|
let dump_uid = started_at.format(format_description!(
|
||||||
"[year repr:full][month repr:numerical][day padding:zero]-[hour padding:zero][minute padding:zero][second padding:zero][subsecond digits:3]"
|
"[year repr:full][month repr:numerical][day padding:zero]-[hour padding:zero][minute padding:zero][second padding:zero][subsecond digits:3]"
|
||||||
)).unwrap();
|
)).unwrap();
|
||||||
|
|
||||||
if self.must_stop_processing.get() {
|
|
||||||
return Err(Error::AbortedTask);
|
|
||||||
}
|
|
||||||
let path = self.dumps_path.join(format!("{}.dump", dump_uid));
|
let path = self.dumps_path.join(format!("{}.dump", dump_uid));
|
||||||
let file = File::create(path)?;
|
let file = File::create(path)?;
|
||||||
dump.persist_to(BufWriter::new(file))?;
|
dump.persist_to(BufWriter::new(file))?;
|
||||||
@ -960,10 +863,6 @@ impl IndexScheduler {
|
|||||||
self.index_mapper.index(&rtxn, &index_uid)?
|
self.index_mapper.index(&rtxn, &index_uid)?
|
||||||
};
|
};
|
||||||
|
|
||||||
// the index operation can take a long time, so save this handle to make it available to the search for the duration of the tick
|
|
||||||
self.index_mapper
|
|
||||||
.set_currently_updating_index(Some((index_uid.clone(), index.clone())));
|
|
||||||
|
|
||||||
let mut index_wtxn = index.write_txn()?;
|
let mut index_wtxn = index.write_txn()?;
|
||||||
let tasks = self.apply_index_operation(&mut index_wtxn, &index, op)?;
|
let tasks = self.apply_index_operation(&mut index_wtxn, &index, op)?;
|
||||||
index_wtxn.commit()?;
|
index_wtxn.commit()?;
|
||||||
@ -983,14 +882,56 @@ impl IndexScheduler {
|
|||||||
|
|
||||||
match res {
|
match res {
|
||||||
Ok(_) => (),
|
Ok(_) => (),
|
||||||
Err(e) => tracing::error!(
|
Err(e) => error!("Could not write the stats of the index {}", e),
|
||||||
error = &e as &dyn std::error::Error,
|
|
||||||
"Could not write the stats of the index"
|
|
||||||
),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(tasks)
|
Ok(tasks)
|
||||||
}
|
}
|
||||||
|
Batch::IndexDocumentDeletionByFilter { mut task, index_uid: _ } => {
|
||||||
|
let (index_uid, filter) =
|
||||||
|
if let KindWithContent::DocumentDeletionByFilter { index_uid, filter_expr } =
|
||||||
|
&task.kind
|
||||||
|
{
|
||||||
|
(index_uid, filter_expr)
|
||||||
|
} else {
|
||||||
|
unreachable!()
|
||||||
|
};
|
||||||
|
let index = {
|
||||||
|
let rtxn = self.env.read_txn()?;
|
||||||
|
self.index_mapper.index(&rtxn, index_uid)?
|
||||||
|
};
|
||||||
|
let deleted_documents = delete_document_by_filter(filter, index);
|
||||||
|
let original_filter = if let Some(Details::DocumentDeletionByFilter {
|
||||||
|
original_filter,
|
||||||
|
deleted_documents: _,
|
||||||
|
}) = task.details
|
||||||
|
{
|
||||||
|
original_filter
|
||||||
|
} else {
|
||||||
|
// In the case of a `documentDeleteByFilter` the details MUST be set
|
||||||
|
unreachable!();
|
||||||
|
};
|
||||||
|
|
||||||
|
match deleted_documents {
|
||||||
|
Ok(deleted_documents) => {
|
||||||
|
task.status = Status::Succeeded;
|
||||||
|
task.details = Some(Details::DocumentDeletionByFilter {
|
||||||
|
original_filter,
|
||||||
|
deleted_documents: Some(deleted_documents),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
task.status = Status::Failed;
|
||||||
|
task.details = Some(Details::DocumentDeletionByFilter {
|
||||||
|
original_filter,
|
||||||
|
deleted_documents: Some(0),
|
||||||
|
});
|
||||||
|
task.error = Some(e.into());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(vec![task])
|
||||||
|
}
|
||||||
Batch::IndexCreation { index_uid, primary_key, task } => {
|
Batch::IndexCreation { index_uid, primary_key, task } => {
|
||||||
let wtxn = self.env.write_txn()?;
|
let wtxn = self.env.write_txn()?;
|
||||||
if self.index_mapper.exists(&wtxn, &index_uid)? {
|
if self.index_mapper.exists(&wtxn, &index_uid)? {
|
||||||
@ -1014,7 +955,7 @@ impl IndexScheduler {
|
|||||||
builder.set_primary_key(primary_key);
|
builder.set_primary_key(primary_key);
|
||||||
let must_stop_processing = self.must_stop_processing.clone();
|
let must_stop_processing = self.must_stop_processing.clone();
|
||||||
builder.execute(
|
builder.execute(
|
||||||
|indexing_step| tracing::debug!(update = ?indexing_step),
|
|indexing_step| debug!("update: {:?}", indexing_step),
|
||||||
|| must_stop_processing.get(),
|
|| must_stop_processing.get(),
|
||||||
)?;
|
)?;
|
||||||
index_wtxn.commit()?;
|
index_wtxn.commit()?;
|
||||||
@ -1041,10 +982,7 @@ impl IndexScheduler {
|
|||||||
|
|
||||||
match res {
|
match res {
|
||||||
Ok(_) => (),
|
Ok(_) => (),
|
||||||
Err(e) => tracing::error!(
|
Err(e) => error!("Could not write the stats of the index {}", e),
|
||||||
error = &e as &dyn std::error::Error,
|
|
||||||
"Could not write the stats of the index"
|
|
||||||
),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(vec![task])
|
Ok(vec![task])
|
||||||
@ -1139,7 +1077,7 @@ impl IndexScheduler {
|
|||||||
for task_id in &index_lhs_task_ids | &index_rhs_task_ids {
|
for task_id in &index_lhs_task_ids | &index_rhs_task_ids {
|
||||||
let mut task = self.get_task(wtxn, task_id)?.ok_or(Error::CorruptedTaskQueue)?;
|
let mut task = self.get_task(wtxn, task_id)?.ok_or(Error::CorruptedTaskQueue)?;
|
||||||
swap_index_uid_in_task(&mut task, (lhs, rhs));
|
swap_index_uid_in_task(&mut task, (lhs, rhs));
|
||||||
self.all_tasks.put(wtxn, &task_id, &task)?;
|
self.all_tasks.put(wtxn, &BEU32::new(task_id), &task)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 4. remove the task from indexuid = before_name
|
// 4. remove the task from indexuid = before_name
|
||||||
@ -1163,19 +1101,12 @@ impl IndexScheduler {
|
|||||||
///
|
///
|
||||||
/// ## Return
|
/// ## Return
|
||||||
/// The list of processed tasks.
|
/// The list of processed tasks.
|
||||||
#[tracing::instrument(
|
|
||||||
level = "trace",
|
|
||||||
skip(self, index_wtxn, index),
|
|
||||||
target = "indexing::scheduler"
|
|
||||||
)]
|
|
||||||
fn apply_index_operation<'i>(
|
fn apply_index_operation<'i>(
|
||||||
&self,
|
&self,
|
||||||
index_wtxn: &mut RwTxn<'i>,
|
index_wtxn: &mut RwTxn<'i, '_>,
|
||||||
index: &'i Index,
|
index: &'i Index,
|
||||||
operation: IndexOperation,
|
operation: IndexOperation,
|
||||||
) -> Result<Vec<Task>> {
|
) -> Result<Vec<Task>> {
|
||||||
puffin::profile_function!();
|
|
||||||
|
|
||||||
match operation {
|
match operation {
|
||||||
IndexOperation::DocumentClear { mut tasks, .. } => {
|
IndexOperation::DocumentClear { mut tasks, .. } => {
|
||||||
let count = milli::update::ClearDocuments::new(index_wtxn, index).execute()?;
|
let count = milli::update::ClearDocuments::new(index_wtxn, index).execute()?;
|
||||||
@ -1228,7 +1159,7 @@ impl IndexScheduler {
|
|||||||
milli::update::Settings::new(index_wtxn, index, indexer_config);
|
milli::update::Settings::new(index_wtxn, index, indexer_config);
|
||||||
builder.set_primary_key(primary_key);
|
builder.set_primary_key(primary_key);
|
||||||
builder.execute(
|
builder.execute(
|
||||||
|indexing_step| tracing::debug!(update = ?indexing_step),
|
|indexing_step| debug!("update: {:?}", indexing_step),
|
||||||
|| must_stop_processing.clone().get(),
|
|| must_stop_processing.clone().get(),
|
||||||
)?;
|
)?;
|
||||||
primary_key_has_been_set = true;
|
primary_key_has_been_set = true;
|
||||||
@ -1238,16 +1169,12 @@ impl IndexScheduler {
|
|||||||
|
|
||||||
let config = IndexDocumentsConfig { update_method: method, ..Default::default() };
|
let config = IndexDocumentsConfig { update_method: method, ..Default::default() };
|
||||||
|
|
||||||
let embedder_configs = index.embedding_configs(index_wtxn)?;
|
|
||||||
// TODO: consider Arc'ing the map too (we only need read access + we'll be cloning it multiple times, so really makes sense)
|
|
||||||
let embedders = self.embedders(embedder_configs)?;
|
|
||||||
|
|
||||||
let mut builder = milli::update::IndexDocuments::new(
|
let mut builder = milli::update::IndexDocuments::new(
|
||||||
index_wtxn,
|
index_wtxn,
|
||||||
index,
|
index,
|
||||||
indexer_config,
|
indexer_config,
|
||||||
config,
|
config,
|
||||||
|indexing_step| tracing::trace!(?indexing_step, "Update"),
|
|indexing_step| debug!("update: {:?}", indexing_step),
|
||||||
|| must_stop_processing.get(),
|
|| must_stop_processing.get(),
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
@ -1260,8 +1187,6 @@ impl IndexScheduler {
|
|||||||
let (new_builder, user_result) = builder.add_documents(reader)?;
|
let (new_builder, user_result) = builder.add_documents(reader)?;
|
||||||
builder = new_builder;
|
builder = new_builder;
|
||||||
|
|
||||||
builder = builder.with_embedders(embedders.clone());
|
|
||||||
|
|
||||||
let received_documents =
|
let received_documents =
|
||||||
if let Some(Details::DocumentAdditionOrUpdate {
|
if let Some(Details::DocumentAdditionOrUpdate {
|
||||||
received_documents,
|
received_documents,
|
||||||
@ -1296,8 +1221,7 @@ impl IndexScheduler {
|
|||||||
let (new_builder, user_result) =
|
let (new_builder, user_result) =
|
||||||
builder.remove_documents(document_ids)?;
|
builder.remove_documents(document_ids)?;
|
||||||
builder = new_builder;
|
builder = new_builder;
|
||||||
// Uses Invariant: remove documents actually always returns Ok for the inner result
|
|
||||||
let count = user_result.unwrap();
|
|
||||||
let provided_ids =
|
let provided_ids =
|
||||||
if let Some(Details::DocumentDeletion { provided_ids, .. }) =
|
if let Some(Details::DocumentDeletion { provided_ids, .. }) =
|
||||||
task.details
|
task.details
|
||||||
@ -1308,18 +1232,30 @@ impl IndexScheduler {
|
|||||||
unreachable!();
|
unreachable!();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
match user_result {
|
||||||
|
Ok(count) => {
|
||||||
task.status = Status::Succeeded;
|
task.status = Status::Succeeded;
|
||||||
task.details = Some(Details::DocumentDeletion {
|
task.details = Some(Details::DocumentDeletion {
|
||||||
provided_ids,
|
provided_ids,
|
||||||
deleted_documents: Some(count),
|
deleted_documents: Some(count),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
Err(e) => {
|
||||||
|
task.status = Status::Failed;
|
||||||
|
task.details = Some(Details::DocumentDeletion {
|
||||||
|
provided_ids,
|
||||||
|
deleted_documents: Some(0),
|
||||||
|
});
|
||||||
|
task.error = Some(milli::Error::from(e).into());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if !tasks.iter().all(|res| res.error.is_some()) {
|
if !tasks.iter().all(|res| res.error.is_some()) {
|
||||||
let addition = builder.execute()?;
|
let addition = builder.execute()?;
|
||||||
tracing::info!(indexing_result = ?addition, "document indexing done");
|
info!("document addition done: {:?}", addition);
|
||||||
} else if primary_key_has_been_set {
|
} else if primary_key_has_been_set {
|
||||||
// Everything failed but we've set a primary key.
|
// Everything failed but we've set a primary key.
|
||||||
// We need to remove it.
|
// We need to remove it.
|
||||||
@ -1327,59 +1263,30 @@ impl IndexScheduler {
|
|||||||
milli::update::Settings::new(index_wtxn, index, indexer_config);
|
milli::update::Settings::new(index_wtxn, index, indexer_config);
|
||||||
builder.reset_primary_key();
|
builder.reset_primary_key();
|
||||||
builder.execute(
|
builder.execute(
|
||||||
|indexing_step| tracing::trace!(update = ?indexing_step),
|
|indexing_step| debug!("update: {:?}", indexing_step),
|
||||||
|| must_stop_processing.clone().get(),
|
|| must_stop_processing.clone().get(),
|
||||||
)?;
|
)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(tasks)
|
Ok(tasks)
|
||||||
}
|
}
|
||||||
IndexOperation::IndexDocumentDeletionByFilter { mut task, index_uid: _ } => {
|
IndexOperation::DocumentDeletion { index_uid: _, documents, mut tasks } => {
|
||||||
let filter =
|
let mut builder = milli::update::DeleteDocuments::new(index_wtxn, index)?;
|
||||||
if let KindWithContent::DocumentDeletionByFilter { filter_expr, .. } =
|
documents.iter().flatten().for_each(|id| {
|
||||||
&task.kind
|
builder.delete_external_id(id);
|
||||||
{
|
});
|
||||||
filter_expr
|
|
||||||
} else {
|
|
||||||
unreachable!()
|
|
||||||
};
|
|
||||||
let deleted_documents = delete_document_by_filter(
|
|
||||||
index_wtxn,
|
|
||||||
filter,
|
|
||||||
self.index_mapper.indexer_config(),
|
|
||||||
self.must_stop_processing.clone(),
|
|
||||||
index,
|
|
||||||
);
|
|
||||||
let original_filter = if let Some(Details::DocumentDeletionByFilter {
|
|
||||||
original_filter,
|
|
||||||
deleted_documents: _,
|
|
||||||
}) = task.details
|
|
||||||
{
|
|
||||||
original_filter
|
|
||||||
} else {
|
|
||||||
// In the case of a `documentDeleteByFilter` the details MUST be set
|
|
||||||
unreachable!();
|
|
||||||
};
|
|
||||||
|
|
||||||
match deleted_documents {
|
let DocumentDeletionResult { deleted_documents, .. } = builder.execute()?;
|
||||||
Ok(deleted_documents) => {
|
|
||||||
|
for (task, documents) in tasks.iter_mut().zip(documents) {
|
||||||
task.status = Status::Succeeded;
|
task.status = Status::Succeeded;
|
||||||
task.details = Some(Details::DocumentDeletionByFilter {
|
task.details = Some(Details::DocumentDeletion {
|
||||||
original_filter,
|
provided_ids: documents.len(),
|
||||||
deleted_documents: Some(deleted_documents),
|
deleted_documents: Some(deleted_documents.min(documents.len() as u64)),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
Err(e) => {
|
|
||||||
task.status = Status::Failed;
|
|
||||||
task.details = Some(Details::DocumentDeletionByFilter {
|
|
||||||
original_filter,
|
|
||||||
deleted_documents: Some(0),
|
|
||||||
});
|
|
||||||
task.error = Some(e.into());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(vec![task])
|
Ok(tasks)
|
||||||
}
|
}
|
||||||
IndexOperation::Settings { index_uid: _, settings, mut tasks } => {
|
IndexOperation::Settings { index_uid: _, settings, mut tasks } => {
|
||||||
let indexer_config = self.index_mapper.indexer_config();
|
let indexer_config = self.index_mapper.indexer_config();
|
||||||
@ -1397,7 +1304,7 @@ impl IndexScheduler {
|
|||||||
|
|
||||||
let must_stop_processing = self.must_stop_processing.clone();
|
let must_stop_processing = self.must_stop_processing.clone();
|
||||||
builder.execute(
|
builder.execute(
|
||||||
|indexing_step| tracing::debug!(update = ?indexing_step),
|
|indexing_step| debug!("update: {:?}", indexing_step),
|
||||||
|| must_stop_processing.get(),
|
|| must_stop_processing.get(),
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
@ -1471,11 +1378,7 @@ impl IndexScheduler {
|
|||||||
/// Delete each given task from all the databases (if it is deleteable).
|
/// Delete each given task from all the databases (if it is deleteable).
|
||||||
///
|
///
|
||||||
/// Return the number of tasks that were actually deleted.
|
/// Return the number of tasks that were actually deleted.
|
||||||
fn delete_matched_tasks(
|
fn delete_matched_tasks(&self, wtxn: &mut RwTxn, matched_tasks: &RoaringBitmap) -> Result<u64> {
|
||||||
&self,
|
|
||||||
wtxn: &mut RwTxn,
|
|
||||||
matched_tasks: &RoaringBitmap,
|
|
||||||
) -> Result<RoaringBitmap> {
|
|
||||||
// 1. Remove from this list the tasks that we are not allowed to delete
|
// 1. Remove from this list the tasks that we are not allowed to delete
|
||||||
let enqueued_tasks = self.get_status(wtxn, Status::Enqueued)?;
|
let enqueued_tasks = self.get_status(wtxn, Status::Enqueued)?;
|
||||||
let processing_tasks = &self.processing_tasks.read().unwrap().processing.clone();
|
let processing_tasks = &self.processing_tasks.read().unwrap().processing.clone();
|
||||||
@ -1527,9 +1430,10 @@ impl IndexScheduler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for task in to_delete_tasks.iter() {
|
for task in to_delete_tasks.iter() {
|
||||||
self.all_tasks.delete(wtxn, &task)?;
|
self.all_tasks.delete(wtxn, &BEU32::new(task))?;
|
||||||
}
|
}
|
||||||
for canceled_by in affected_canceled_by {
|
for canceled_by in affected_canceled_by {
|
||||||
|
let canceled_by = BEU32::new(canceled_by);
|
||||||
if let Some(mut tasks) = self.canceled_by.get(wtxn, &canceled_by)? {
|
if let Some(mut tasks) = self.canceled_by.get(wtxn, &canceled_by)? {
|
||||||
tasks -= &to_delete_tasks;
|
tasks -= &to_delete_tasks;
|
||||||
if tasks.is_empty() {
|
if tasks.is_empty() {
|
||||||
@ -1540,7 +1444,7 @@ impl IndexScheduler {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(to_delete_tasks)
|
Ok(to_delete_tasks.len())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Cancel each given task from all the databases (if it is cancelable).
|
/// Cancel each given task from all the databases (if it is cancelable).
|
||||||
@ -1577,47 +1481,29 @@ impl IndexScheduler {
|
|||||||
task.details = task.details.map(|d| d.to_failed());
|
task.details = task.details.map(|d| d.to_failed());
|
||||||
self.update_task(wtxn, &task)?;
|
self.update_task(wtxn, &task)?;
|
||||||
}
|
}
|
||||||
self.canceled_by.put(wtxn, &cancel_task_id, &tasks_to_cancel)?;
|
self.canceled_by.put(wtxn, &BEU32::new(cancel_task_id), &tasks_to_cancel)?;
|
||||||
|
|
||||||
Ok(content_files_to_delete)
|
Ok(content_files_to_delete)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn delete_document_by_filter<'a>(
|
fn delete_document_by_filter(filter: &serde_json::Value, index: Index) -> Result<u64> {
|
||||||
wtxn: &mut RwTxn<'a>,
|
|
||||||
filter: &serde_json::Value,
|
|
||||||
indexer_config: &IndexerConfig,
|
|
||||||
must_stop_processing: MustStopProcessing,
|
|
||||||
index: &'a Index,
|
|
||||||
) -> Result<u64> {
|
|
||||||
let filter = Filter::from_json(filter)?;
|
let filter = Filter::from_json(filter)?;
|
||||||
Ok(if let Some(filter) = filter {
|
Ok(if let Some(filter) = filter {
|
||||||
let candidates = filter.evaluate(wtxn, index).map_err(|err| match err {
|
let mut wtxn = index.write_txn()?;
|
||||||
|
|
||||||
|
let candidates = filter.evaluate(&wtxn, &index).map_err(|err| match err {
|
||||||
milli::Error::UserError(milli::UserError::InvalidFilter(_)) => {
|
milli::Error::UserError(milli::UserError::InvalidFilter(_)) => {
|
||||||
Error::from(err).with_custom_error_code(Code::InvalidDocumentFilter)
|
Error::from(err).with_custom_error_code(Code::InvalidDocumentFilter)
|
||||||
}
|
}
|
||||||
e => e.into(),
|
e => e.into(),
|
||||||
})?;
|
})?;
|
||||||
|
let mut delete_operation = DeleteDocuments::new(&mut wtxn, &index)?;
|
||||||
let config = IndexDocumentsConfig {
|
delete_operation.delete_documents(&candidates);
|
||||||
update_method: IndexDocumentsMethod::ReplaceDocuments,
|
let deleted_documents =
|
||||||
..Default::default()
|
delete_operation.execute().map(|result| result.deleted_documents)?;
|
||||||
};
|
wtxn.commit()?;
|
||||||
|
deleted_documents
|
||||||
let mut builder = milli::update::IndexDocuments::new(
|
|
||||||
wtxn,
|
|
||||||
index,
|
|
||||||
indexer_config,
|
|
||||||
config,
|
|
||||||
|indexing_step| tracing::debug!(update = ?indexing_step),
|
|
||||||
|| must_stop_processing.get(),
|
|
||||||
)?;
|
|
||||||
|
|
||||||
let (new_builder, count) = builder.remove_documents_from_db_no_batch(&candidates)?;
|
|
||||||
builder = new_builder;
|
|
||||||
|
|
||||||
let _ = builder.execute()?;
|
|
||||||
count
|
|
||||||
} else {
|
} else {
|
||||||
0
|
0
|
||||||
})
|
})
|
||||||
|
@ -48,8 +48,6 @@ impl From<DateField> for Code {
|
|||||||
pub enum Error {
|
pub enum Error {
|
||||||
#[error("{1}")]
|
#[error("{1}")]
|
||||||
WithCustomErrorCode(Code, Box<Self>),
|
WithCustomErrorCode(Code, Box<Self>),
|
||||||
#[error("Received bad task id: {received} should be >= to {expected}.")]
|
|
||||||
BadTaskId { received: TaskId, expected: TaskId },
|
|
||||||
#[error("Index `{0}` not found.")]
|
#[error("Index `{0}` not found.")]
|
||||||
IndexNotFound(String),
|
IndexNotFound(String),
|
||||||
#[error("Index `{0}` already exists.")]
|
#[error("Index `{0}` already exists.")]
|
||||||
@ -110,8 +108,6 @@ pub enum Error {
|
|||||||
TaskDeletionWithEmptyQuery,
|
TaskDeletionWithEmptyQuery,
|
||||||
#[error("Query parameters to filter the tasks to cancel are missing. Available query parameters are: `uids`, `indexUids`, `statuses`, `types`, `canceledBy`, `beforeEnqueuedAt`, `afterEnqueuedAt`, `beforeStartedAt`, `afterStartedAt`, `beforeFinishedAt`, `afterFinishedAt`.")]
|
#[error("Query parameters to filter the tasks to cancel are missing. Available query parameters are: `uids`, `indexUids`, `statuses`, `types`, `canceledBy`, `beforeEnqueuedAt`, `afterEnqueuedAt`, `beforeStartedAt`, `afterStartedAt`, `beforeFinishedAt`, `afterFinishedAt`.")]
|
||||||
TaskCancelationWithEmptyQuery,
|
TaskCancelationWithEmptyQuery,
|
||||||
#[error("Aborted task")]
|
|
||||||
AbortedTask,
|
|
||||||
|
|
||||||
#[error(transparent)]
|
#[error(transparent)]
|
||||||
Dump(#[from] dump::Error),
|
Dump(#[from] dump::Error),
|
||||||
@ -127,8 +123,6 @@ pub enum Error {
|
|||||||
IoError(#[from] std::io::Error),
|
IoError(#[from] std::io::Error),
|
||||||
#[error(transparent)]
|
#[error(transparent)]
|
||||||
Persist(#[from] tempfile::PersistError),
|
Persist(#[from] tempfile::PersistError),
|
||||||
#[error(transparent)]
|
|
||||||
FeatureNotEnabled(#[from] FeatureNotEnabledError),
|
|
||||||
|
|
||||||
#[error(transparent)]
|
#[error(transparent)]
|
||||||
Anyhow(#[from] anyhow::Error),
|
Anyhow(#[from] anyhow::Error),
|
||||||
@ -148,22 +142,11 @@ pub enum Error {
|
|||||||
PlannedFailure,
|
PlannedFailure,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, thiserror::Error)]
|
|
||||||
#[error(
|
|
||||||
"{disabled_action} requires enabling the `{feature}` experimental feature. See {issue_link}"
|
|
||||||
)]
|
|
||||||
pub struct FeatureNotEnabledError {
|
|
||||||
pub disabled_action: &'static str,
|
|
||||||
pub feature: &'static str,
|
|
||||||
pub issue_link: &'static str,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Error {
|
impl Error {
|
||||||
pub fn is_recoverable(&self) -> bool {
|
pub fn is_recoverable(&self) -> bool {
|
||||||
match self {
|
match self {
|
||||||
Error::IndexNotFound(_)
|
Error::IndexNotFound(_)
|
||||||
| Error::WithCustomErrorCode(_, _)
|
| Error::WithCustomErrorCode(_, _)
|
||||||
| Error::BadTaskId { .. }
|
|
||||||
| Error::IndexAlreadyExists(_)
|
| Error::IndexAlreadyExists(_)
|
||||||
| Error::SwapDuplicateIndexFound(_)
|
| Error::SwapDuplicateIndexFound(_)
|
||||||
| Error::SwapDuplicateIndexesFound(_)
|
| Error::SwapDuplicateIndexesFound(_)
|
||||||
@ -180,7 +163,6 @@ impl Error {
|
|||||||
| Error::TaskNotFound(_)
|
| Error::TaskNotFound(_)
|
||||||
| Error::TaskDeletionWithEmptyQuery
|
| Error::TaskDeletionWithEmptyQuery
|
||||||
| Error::TaskCancelationWithEmptyQuery
|
| Error::TaskCancelationWithEmptyQuery
|
||||||
| Error::AbortedTask
|
|
||||||
| Error::Dump(_)
|
| Error::Dump(_)
|
||||||
| Error::Heed(_)
|
| Error::Heed(_)
|
||||||
| Error::Milli(_)
|
| Error::Milli(_)
|
||||||
@ -188,7 +170,6 @@ impl Error {
|
|||||||
| Error::FileStore(_)
|
| Error::FileStore(_)
|
||||||
| Error::IoError(_)
|
| Error::IoError(_)
|
||||||
| Error::Persist(_)
|
| Error::Persist(_)
|
||||||
| Error::FeatureNotEnabled(_)
|
|
||||||
| Error::Anyhow(_) => true,
|
| Error::Anyhow(_) => true,
|
||||||
Error::CreateBatch(_)
|
Error::CreateBatch(_)
|
||||||
| Error::CorruptedTaskQueue
|
| Error::CorruptedTaskQueue
|
||||||
@ -208,7 +189,6 @@ impl ErrorCode for Error {
|
|||||||
fn error_code(&self) -> Code {
|
fn error_code(&self) -> Code {
|
||||||
match self {
|
match self {
|
||||||
Error::WithCustomErrorCode(code, _) => *code,
|
Error::WithCustomErrorCode(code, _) => *code,
|
||||||
Error::BadTaskId { .. } => Code::BadRequest,
|
|
||||||
Error::IndexNotFound(_) => Code::IndexNotFound,
|
Error::IndexNotFound(_) => Code::IndexNotFound,
|
||||||
Error::IndexAlreadyExists(_) => Code::IndexAlreadyExists,
|
Error::IndexAlreadyExists(_) => Code::IndexAlreadyExists,
|
||||||
Error::SwapDuplicateIndexesFound(_) => Code::InvalidSwapDuplicateIndexFound,
|
Error::SwapDuplicateIndexesFound(_) => Code::InvalidSwapDuplicateIndexFound,
|
||||||
@ -234,7 +214,6 @@ impl ErrorCode for Error {
|
|||||||
Error::FileStore(e) => e.error_code(),
|
Error::FileStore(e) => e.error_code(),
|
||||||
Error::IoError(e) => e.error_code(),
|
Error::IoError(e) => e.error_code(),
|
||||||
Error::Persist(e) => e.error_code(),
|
Error::Persist(e) => e.error_code(),
|
||||||
Error::FeatureNotEnabled(_) => Code::FeatureNotEnabled,
|
|
||||||
|
|
||||||
// Irrecoverable errors
|
// Irrecoverable errors
|
||||||
Error::Anyhow(_) => Code::Internal,
|
Error::Anyhow(_) => Code::Internal,
|
||||||
@ -243,9 +222,6 @@ impl ErrorCode for Error {
|
|||||||
Error::TaskDatabaseUpdate(_) => Code::Internal,
|
Error::TaskDatabaseUpdate(_) => Code::Internal,
|
||||||
Error::CreateBatch(_) => Code::Internal,
|
Error::CreateBatch(_) => Code::Internal,
|
||||||
|
|
||||||
// This one should never be seen by the end user
|
|
||||||
Error::AbortedTask => Code::Internal,
|
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
Error::PlannedFailure => Code::Internal,
|
Error::PlannedFailure => Code::Internal,
|
||||||
}
|
}
|
||||||
|
@ -1,130 +0,0 @@
|
|||||||
use std::sync::{Arc, RwLock};
|
|
||||||
|
|
||||||
use meilisearch_types::features::{InstanceTogglableFeatures, RuntimeTogglableFeatures};
|
|
||||||
use meilisearch_types::heed::types::{SerdeJson, Str};
|
|
||||||
use meilisearch_types::heed::{Database, Env, RwTxn};
|
|
||||||
|
|
||||||
use crate::error::FeatureNotEnabledError;
|
|
||||||
use crate::Result;
|
|
||||||
|
|
||||||
const EXPERIMENTAL_FEATURES: &str = "experimental-features";
|
|
||||||
|
|
||||||
#[derive(Clone)]
|
|
||||||
pub(crate) struct FeatureData {
|
|
||||||
persisted: Database<Str, SerdeJson<RuntimeTogglableFeatures>>,
|
|
||||||
runtime: Arc<RwLock<RuntimeTogglableFeatures>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy)]
|
|
||||||
pub struct RoFeatures {
|
|
||||||
runtime: RuntimeTogglableFeatures,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl RoFeatures {
|
|
||||||
fn new(data: &FeatureData) -> Self {
|
|
||||||
let runtime = data.runtime_features();
|
|
||||||
Self { runtime }
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn runtime_features(&self) -> RuntimeTogglableFeatures {
|
|
||||||
self.runtime
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn check_metrics(&self) -> Result<()> {
|
|
||||||
if self.runtime.metrics {
|
|
||||||
Ok(())
|
|
||||||
} else {
|
|
||||||
Err(FeatureNotEnabledError {
|
|
||||||
disabled_action: "Getting metrics",
|
|
||||||
feature: "metrics",
|
|
||||||
issue_link: "https://github.com/meilisearch/product/discussions/625",
|
|
||||||
}
|
|
||||||
.into())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn check_logs_route(&self) -> Result<()> {
|
|
||||||
if self.runtime.logs_route {
|
|
||||||
Ok(())
|
|
||||||
} else {
|
|
||||||
Err(FeatureNotEnabledError {
|
|
||||||
disabled_action: "Modifying logs through the `/logs/*` routes",
|
|
||||||
feature: "logs route",
|
|
||||||
issue_link: "https://github.com/orgs/meilisearch/discussions/721",
|
|
||||||
}
|
|
||||||
.into())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn check_vector(&self, disabled_action: &'static str) -> Result<()> {
|
|
||||||
if self.runtime.vector_store {
|
|
||||||
Ok(())
|
|
||||||
} else {
|
|
||||||
Err(FeatureNotEnabledError {
|
|
||||||
disabled_action,
|
|
||||||
feature: "vector store",
|
|
||||||
issue_link: "https://github.com/meilisearch/product/discussions/677",
|
|
||||||
}
|
|
||||||
.into())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn check_puffin(&self) -> Result<()> {
|
|
||||||
if self.runtime.export_puffin_reports {
|
|
||||||
Ok(())
|
|
||||||
} else {
|
|
||||||
Err(FeatureNotEnabledError {
|
|
||||||
disabled_action: "Outputting Puffin reports to disk",
|
|
||||||
feature: "export puffin reports",
|
|
||||||
issue_link: "https://github.com/meilisearch/product/discussions/693",
|
|
||||||
}
|
|
||||||
.into())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl FeatureData {
|
|
||||||
pub fn new(env: &Env, instance_features: InstanceTogglableFeatures) -> Result<Self> {
|
|
||||||
let mut wtxn = env.write_txn()?;
|
|
||||||
let runtime_features_db = env.create_database(&mut wtxn, Some(EXPERIMENTAL_FEATURES))?;
|
|
||||||
wtxn.commit()?;
|
|
||||||
|
|
||||||
let txn = env.read_txn()?;
|
|
||||||
let persisted_features: RuntimeTogglableFeatures =
|
|
||||||
runtime_features_db.get(&txn, EXPERIMENTAL_FEATURES)?.unwrap_or_default();
|
|
||||||
let runtime = Arc::new(RwLock::new(RuntimeTogglableFeatures {
|
|
||||||
metrics: instance_features.metrics || persisted_features.metrics,
|
|
||||||
logs_route: instance_features.logs_route || persisted_features.logs_route,
|
|
||||||
..persisted_features
|
|
||||||
}));
|
|
||||||
|
|
||||||
Ok(Self { persisted: runtime_features_db, runtime })
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn put_runtime_features(
|
|
||||||
&self,
|
|
||||||
mut wtxn: RwTxn,
|
|
||||||
features: RuntimeTogglableFeatures,
|
|
||||||
) -> Result<()> {
|
|
||||||
self.persisted.put(&mut wtxn, EXPERIMENTAL_FEATURES, &features)?;
|
|
||||||
wtxn.commit()?;
|
|
||||||
|
|
||||||
// safe to unwrap, the lock will only fail if:
|
|
||||||
// 1. requested by the same thread concurrently -> it is called and released in methods that don't call each other
|
|
||||||
// 2. there's a panic while the thread is held -> it is only used for an assignment here.
|
|
||||||
let mut toggled_features = self.runtime.write().unwrap();
|
|
||||||
*toggled_features = features;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn runtime_features(&self) -> RuntimeTogglableFeatures {
|
|
||||||
// sound to unwrap, the lock will only fail if:
|
|
||||||
// 1. requested by the same thread concurrently -> it is called and released in methods that don't call each other
|
|
||||||
// 2. there's a panic while the thread is held -> it is only used for copying the data here
|
|
||||||
*self.runtime.read().unwrap()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn features(&self) -> RoFeatures {
|
|
||||||
RoFeatures::new(self)
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,8 +1,12 @@
|
|||||||
|
/// the map size to use when we don't succeed in reading it in indexes.
|
||||||
|
const DEFAULT_MAP_SIZE: usize = 10 * 1024 * 1024 * 1024; // 10 GiB
|
||||||
|
|
||||||
use std::collections::BTreeMap;
|
use std::collections::BTreeMap;
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
|
||||||
use meilisearch_types::heed::{EnvClosingEvent, EnvFlags, EnvOpenOptions};
|
use meilisearch_types::heed::flags::Flags;
|
||||||
|
use meilisearch_types::heed::{EnvClosingEvent, EnvOpenOptions};
|
||||||
use meilisearch_types::milli::Index;
|
use meilisearch_types::milli::Index;
|
||||||
use time::OffsetDateTime;
|
use time::OffsetDateTime;
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
@ -219,9 +223,7 @@ impl IndexMap {
|
|||||||
enable_mdb_writemap: bool,
|
enable_mdb_writemap: bool,
|
||||||
map_size_growth: usize,
|
map_size_growth: usize,
|
||||||
) {
|
) {
|
||||||
let Some(index) = self.available.remove(uuid) else {
|
let Some(index) = self.available.remove(uuid) else { return; };
|
||||||
return;
|
|
||||||
};
|
|
||||||
self.close(*uuid, index, enable_mdb_writemap, map_size_growth);
|
self.close(*uuid, index, enable_mdb_writemap, map_size_growth);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -232,7 +234,7 @@ impl IndexMap {
|
|||||||
enable_mdb_writemap: bool,
|
enable_mdb_writemap: bool,
|
||||||
map_size_growth: usize,
|
map_size_growth: usize,
|
||||||
) {
|
) {
|
||||||
let map_size = index.map_size() + map_size_growth;
|
let map_size = index.map_size().unwrap_or(DEFAULT_MAP_SIZE) + map_size_growth;
|
||||||
let closing_event = index.prepare_for_closing();
|
let closing_event = index.prepare_for_closing();
|
||||||
let generation = self.next_generation();
|
let generation = self.next_generation();
|
||||||
self.unavailable.insert(
|
self.unavailable.insert(
|
||||||
@ -305,7 +307,7 @@ fn create_or_open_index(
|
|||||||
options.map_size(clamp_to_page_size(map_size));
|
options.map_size(clamp_to_page_size(map_size));
|
||||||
options.max_readers(1024);
|
options.max_readers(1024);
|
||||||
if enable_mdb_writemap {
|
if enable_mdb_writemap {
|
||||||
unsafe { options.flags(EnvFlags::WRITE_MAP) };
|
unsafe { options.flag(Flags::MdbWriteMap) };
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some((created, updated)) = date {
|
if let Some((created, updated)) = date {
|
||||||
@ -384,7 +386,7 @@ mod tests {
|
|||||||
|
|
||||||
fn assert_index_size(index: Index, expected: usize) {
|
fn assert_index_size(index: Index, expected: usize) {
|
||||||
let expected = clamp_to_page_size(expected);
|
let expected = clamp_to_page_size(expected);
|
||||||
let index_map_size = index.map_size();
|
let index_map_size = index.map_size().unwrap();
|
||||||
assert_eq!(index_map_size, expected);
|
assert_eq!(index_map_size, expected);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -3,13 +3,13 @@ use std::sync::{Arc, RwLock};
|
|||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
use std::{fs, thread};
|
use std::{fs, thread};
|
||||||
|
|
||||||
|
use log::error;
|
||||||
use meilisearch_types::heed::types::{SerdeJson, Str};
|
use meilisearch_types::heed::types::{SerdeJson, Str};
|
||||||
use meilisearch_types::heed::{Database, Env, RoTxn, RwTxn};
|
use meilisearch_types::heed::{Database, Env, RoTxn, RwTxn};
|
||||||
use meilisearch_types::milli::update::IndexerConfig;
|
use meilisearch_types::milli::update::IndexerConfig;
|
||||||
use meilisearch_types::milli::{FieldDistribution, Index};
|
use meilisearch_types::milli::{FieldDistribution, Index};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use time::OffsetDateTime;
|
use time::OffsetDateTime;
|
||||||
use tracing::error;
|
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
|
|
||||||
use self::index_map::IndexMap;
|
use self::index_map::IndexMap;
|
||||||
@ -69,10 +69,6 @@ pub struct IndexMapper {
|
|||||||
/// Whether we open a meilisearch index with the MDB_WRITEMAP option or not.
|
/// Whether we open a meilisearch index with the MDB_WRITEMAP option or not.
|
||||||
enable_mdb_writemap: bool,
|
enable_mdb_writemap: bool,
|
||||||
pub indexer_config: Arc<IndexerConfig>,
|
pub indexer_config: Arc<IndexerConfig>,
|
||||||
|
|
||||||
/// A few types of long running batches of tasks that act on a single index set this field
|
|
||||||
/// so that a handle to the index is available from other threads (search) in an optimized manner.
|
|
||||||
currently_updating_index: Arc<RwLock<Option<(String, Index)>>>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Whether the index is available for use or is forbidden to be inserted back in the index map
|
/// Whether the index is available for use or is forbidden to be inserted back in the index map
|
||||||
@ -155,7 +151,6 @@ impl IndexMapper {
|
|||||||
index_growth_amount,
|
index_growth_amount,
|
||||||
enable_mdb_writemap,
|
enable_mdb_writemap,
|
||||||
indexer_config: Arc::new(indexer_config),
|
indexer_config: Arc::new(indexer_config),
|
||||||
currently_updating_index: Default::default(),
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -308,14 +303,6 @@ impl IndexMapper {
|
|||||||
|
|
||||||
/// Return an index, may open it if it wasn't already opened.
|
/// Return an index, may open it if it wasn't already opened.
|
||||||
pub fn index(&self, rtxn: &RoTxn, name: &str) -> Result<Index> {
|
pub fn index(&self, rtxn: &RoTxn, name: &str) -> Result<Index> {
|
||||||
if let Some((current_name, current_index)) =
|
|
||||||
self.currently_updating_index.read().unwrap().as_ref()
|
|
||||||
{
|
|
||||||
if current_name == name {
|
|
||||||
return Ok(current_index.clone());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let uuid = self
|
let uuid = self
|
||||||
.index_mapping
|
.index_mapping
|
||||||
.get(rtxn, name)?
|
.get(rtxn, name)?
|
||||||
@ -487,8 +474,4 @@ impl IndexMapper {
|
|||||||
pub fn indexer_config(&self) -> &IndexerConfig {
|
pub fn indexer_config(&self) -> &IndexerConfig {
|
||||||
&self.indexer_config
|
&self.indexer_config
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_currently_updating_index(&self, index: Option<(String, Index)>) {
|
|
||||||
*self.currently_updating_index.write().unwrap() = index;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
use std::collections::BTreeSet;
|
use std::collections::BTreeSet;
|
||||||
use std::fmt::Write;
|
use std::fmt::Write;
|
||||||
|
|
||||||
use meilisearch_types::heed::types::{SerdeBincode, SerdeJson, Str};
|
use meilisearch_types::heed::types::{OwnedType, SerdeBincode, SerdeJson, Str};
|
||||||
use meilisearch_types::heed::{Database, RoTxn};
|
use meilisearch_types::heed::{Database, RoTxn};
|
||||||
use meilisearch_types::milli::{CboRoaringBitmapCodec, RoaringBitmapCodec, BEU32};
|
use meilisearch_types::milli::{CboRoaringBitmapCodec, RoaringBitmapCodec, BEU32};
|
||||||
use meilisearch_types::tasks::{Details, Task};
|
use meilisearch_types::tasks::{Details, Task};
|
||||||
@ -15,7 +15,6 @@ pub fn snapshot_index_scheduler(scheduler: &IndexScheduler) -> String {
|
|||||||
|
|
||||||
let IndexScheduler {
|
let IndexScheduler {
|
||||||
autobatching_enabled,
|
autobatching_enabled,
|
||||||
cleanup_enabled: _,
|
|
||||||
must_stop_processing: _,
|
must_stop_processing: _,
|
||||||
processing_tasks,
|
processing_tasks,
|
||||||
file_store,
|
file_store,
|
||||||
@ -29,21 +28,15 @@ pub fn snapshot_index_scheduler(scheduler: &IndexScheduler) -> String {
|
|||||||
started_at,
|
started_at,
|
||||||
finished_at,
|
finished_at,
|
||||||
index_mapper,
|
index_mapper,
|
||||||
features: _,
|
|
||||||
max_number_of_tasks: _,
|
max_number_of_tasks: _,
|
||||||
max_number_of_batched_tasks: _,
|
|
||||||
puffin_frame: _,
|
|
||||||
wake_up: _,
|
wake_up: _,
|
||||||
dumps_path: _,
|
dumps_path: _,
|
||||||
snapshots_path: _,
|
snapshots_path: _,
|
||||||
auth_path: _,
|
auth_path: _,
|
||||||
version_file_path: _,
|
version_file_path: _,
|
||||||
webhook_url: _,
|
|
||||||
webhook_authorization_header: _,
|
|
||||||
test_breakpoint_sdr: _,
|
test_breakpoint_sdr: _,
|
||||||
planned_failures: _,
|
planned_failures: _,
|
||||||
run_loop_iteration: _,
|
run_loop_iteration: _,
|
||||||
embedders: _,
|
|
||||||
} = scheduler;
|
} = scheduler;
|
||||||
|
|
||||||
let rtxn = env.read_txn().unwrap();
|
let rtxn = env.read_txn().unwrap();
|
||||||
@ -119,7 +112,7 @@ pub fn snapshot_bitmap(r: &RoaringBitmap) -> String {
|
|||||||
snap
|
snap
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn snapshot_all_tasks(rtxn: &RoTxn, db: Database<BEU32, SerdeJson<Task>>) -> String {
|
pub fn snapshot_all_tasks(rtxn: &RoTxn, db: Database<OwnedType<BEU32>, SerdeJson<Task>>) -> String {
|
||||||
let mut snap = String::new();
|
let mut snap = String::new();
|
||||||
let iter = db.iter(rtxn).unwrap();
|
let iter = db.iter(rtxn).unwrap();
|
||||||
for next in iter {
|
for next in iter {
|
||||||
@ -129,7 +122,10 @@ pub fn snapshot_all_tasks(rtxn: &RoTxn, db: Database<BEU32, SerdeJson<Task>>) ->
|
|||||||
snap
|
snap
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn snapshot_date_db(rtxn: &RoTxn, db: Database<BEI128, CboRoaringBitmapCodec>) -> String {
|
pub fn snapshot_date_db(
|
||||||
|
rtxn: &RoTxn,
|
||||||
|
db: Database<OwnedType<BEI128>, CboRoaringBitmapCodec>,
|
||||||
|
) -> String {
|
||||||
let mut snap = String::new();
|
let mut snap = String::new();
|
||||||
let iter = db.iter(rtxn).unwrap();
|
let iter = db.iter(rtxn).unwrap();
|
||||||
for next in iter {
|
for next in iter {
|
||||||
@ -249,7 +245,10 @@ pub fn snapshot_index_tasks(rtxn: &RoTxn, db: Database<Str, RoaringBitmapCodec>)
|
|||||||
}
|
}
|
||||||
snap
|
snap
|
||||||
}
|
}
|
||||||
pub fn snapshot_canceled_by(rtxn: &RoTxn, db: Database<BEU32, RoaringBitmapCodec>) -> String {
|
pub fn snapshot_canceled_by(
|
||||||
|
rtxn: &RoTxn,
|
||||||
|
db: Database<OwnedType<BEU32>, RoaringBitmapCodec>,
|
||||||
|
) -> String {
|
||||||
let mut snap = String::new();
|
let mut snap = String::new();
|
||||||
let iter = db.iter(rtxn).unwrap();
|
let iter = db.iter(rtxn).unwrap();
|
||||||
for next in iter {
|
for next in iter {
|
||||||
|
File diff suppressed because it is too large
Load Diff
@ -1,14 +0,0 @@
|
|||||||
---
|
|
||||||
source: index-scheduler/src/lib.rs
|
|
||||||
expression: task.details
|
|
||||||
---
|
|
||||||
{
|
|
||||||
"embedders": {
|
|
||||||
"default": {
|
|
||||||
"source": "rest",
|
|
||||||
"apiKey": "MyXXXX...",
|
|
||||||
"dimensions": 4,
|
|
||||||
"url": "http://localhost:7777"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,23 +0,0 @@
|
|||||||
---
|
|
||||||
source: index-scheduler/src/lib.rs
|
|
||||||
expression: embedding_config.embedder_options
|
|
||||||
---
|
|
||||||
{
|
|
||||||
"Rest": {
|
|
||||||
"api_key": "My super secret",
|
|
||||||
"distribution": null,
|
|
||||||
"dimensions": 4,
|
|
||||||
"url": "http://localhost:7777",
|
|
||||||
"query": null,
|
|
||||||
"input_field": [
|
|
||||||
"input"
|
|
||||||
],
|
|
||||||
"path_to_embeddings": [
|
|
||||||
"data"
|
|
||||||
],
|
|
||||||
"embedding_object": [
|
|
||||||
"embedding"
|
|
||||||
],
|
|
||||||
"input_type": "text"
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,14 +0,0 @@
|
|||||||
---
|
|
||||||
source: index-scheduler/src/lib.rs
|
|
||||||
expression: task.details
|
|
||||||
---
|
|
||||||
{
|
|
||||||
"embedders": {
|
|
||||||
"default": {
|
|
||||||
"source": "rest",
|
|
||||||
"apiKey": "MyXXXX...",
|
|
||||||
"dimensions": 4,
|
|
||||||
"url": "http://localhost:7777"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,35 +0,0 @@
|
|||||||
---
|
|
||||||
source: index-scheduler/src/lib.rs
|
|
||||||
---
|
|
||||||
### Autobatching Enabled = true
|
|
||||||
### Processing Tasks:
|
|
||||||
[]
|
|
||||||
----------------------------------------------------------------------
|
|
||||||
### All Tasks:
|
|
||||||
0 {uid: 0, status: enqueued, details: { dump_uid: None }, kind: DumpCreation { keys: [], instance_uid: None }}
|
|
||||||
----------------------------------------------------------------------
|
|
||||||
### Status:
|
|
||||||
enqueued [0,]
|
|
||||||
----------------------------------------------------------------------
|
|
||||||
### Kind:
|
|
||||||
"dumpCreation" [0,]
|
|
||||||
----------------------------------------------------------------------
|
|
||||||
### Index Tasks:
|
|
||||||
----------------------------------------------------------------------
|
|
||||||
### Index Mapper:
|
|
||||||
|
|
||||||
----------------------------------------------------------------------
|
|
||||||
### Canceled By:
|
|
||||||
|
|
||||||
----------------------------------------------------------------------
|
|
||||||
### Enqueued At:
|
|
||||||
[timestamp] [0,]
|
|
||||||
----------------------------------------------------------------------
|
|
||||||
### Started At:
|
|
||||||
----------------------------------------------------------------------
|
|
||||||
### Finished At:
|
|
||||||
----------------------------------------------------------------------
|
|
||||||
### File Store:
|
|
||||||
|
|
||||||
----------------------------------------------------------------------
|
|
||||||
|
|
@ -1,45 +0,0 @@
|
|||||||
---
|
|
||||||
source: index-scheduler/src/lib.rs
|
|
||||||
---
|
|
||||||
### Autobatching Enabled = true
|
|
||||||
### Processing Tasks:
|
|
||||||
[]
|
|
||||||
----------------------------------------------------------------------
|
|
||||||
### All Tasks:
|
|
||||||
0 {uid: 0, status: canceled, canceled_by: 1, details: { dump_uid: None }, kind: DumpCreation { keys: [], instance_uid: None }}
|
|
||||||
1 {uid: 1, status: succeeded, details: { matched_tasks: 1, canceled_tasks: Some(0), original_filter: "cancel dump" }, kind: TaskCancelation { query: "cancel dump", tasks: RoaringBitmap<[0]> }}
|
|
||||||
----------------------------------------------------------------------
|
|
||||||
### Status:
|
|
||||||
enqueued []
|
|
||||||
succeeded [1,]
|
|
||||||
canceled [0,]
|
|
||||||
----------------------------------------------------------------------
|
|
||||||
### Kind:
|
|
||||||
"taskCancelation" [1,]
|
|
||||||
"dumpCreation" [0,]
|
|
||||||
----------------------------------------------------------------------
|
|
||||||
### Index Tasks:
|
|
||||||
----------------------------------------------------------------------
|
|
||||||
### Index Mapper:
|
|
||||||
|
|
||||||
----------------------------------------------------------------------
|
|
||||||
### Canceled By:
|
|
||||||
1 [0,]
|
|
||||||
|
|
||||||
----------------------------------------------------------------------
|
|
||||||
### Enqueued At:
|
|
||||||
[timestamp] [0,]
|
|
||||||
[timestamp] [1,]
|
|
||||||
----------------------------------------------------------------------
|
|
||||||
### Started At:
|
|
||||||
[timestamp] [0,]
|
|
||||||
[timestamp] [1,]
|
|
||||||
----------------------------------------------------------------------
|
|
||||||
### Finished At:
|
|
||||||
[timestamp] [0,]
|
|
||||||
[timestamp] [1,]
|
|
||||||
----------------------------------------------------------------------
|
|
||||||
### File Store:
|
|
||||||
|
|
||||||
----------------------------------------------------------------------
|
|
||||||
|
|
@ -1,38 +0,0 @@
|
|||||||
---
|
|
||||||
source: index-scheduler/src/lib.rs
|
|
||||||
---
|
|
||||||
### Autobatching Enabled = true
|
|
||||||
### Processing Tasks:
|
|
||||||
[0,]
|
|
||||||
----------------------------------------------------------------------
|
|
||||||
### All Tasks:
|
|
||||||
0 {uid: 0, status: enqueued, details: { dump_uid: None }, kind: DumpCreation { keys: [], instance_uid: None }}
|
|
||||||
1 {uid: 1, status: enqueued, details: { matched_tasks: 1, canceled_tasks: None, original_filter: "cancel dump" }, kind: TaskCancelation { query: "cancel dump", tasks: RoaringBitmap<[0]> }}
|
|
||||||
----------------------------------------------------------------------
|
|
||||||
### Status:
|
|
||||||
enqueued [0,1,]
|
|
||||||
----------------------------------------------------------------------
|
|
||||||
### Kind:
|
|
||||||
"taskCancelation" [1,]
|
|
||||||
"dumpCreation" [0,]
|
|
||||||
----------------------------------------------------------------------
|
|
||||||
### Index Tasks:
|
|
||||||
----------------------------------------------------------------------
|
|
||||||
### Index Mapper:
|
|
||||||
|
|
||||||
----------------------------------------------------------------------
|
|
||||||
### Canceled By:
|
|
||||||
|
|
||||||
----------------------------------------------------------------------
|
|
||||||
### Enqueued At:
|
|
||||||
[timestamp] [0,]
|
|
||||||
[timestamp] [1,]
|
|
||||||
----------------------------------------------------------------------
|
|
||||||
### Started At:
|
|
||||||
----------------------------------------------------------------------
|
|
||||||
### Finished At:
|
|
||||||
----------------------------------------------------------------------
|
|
||||||
### File Store:
|
|
||||||
|
|
||||||
----------------------------------------------------------------------
|
|
||||||
|
|
@ -34,10 +34,12 @@ catto: { number_of_documents: 1, field_distribution: {"id": 1} }
|
|||||||
[timestamp] [3,]
|
[timestamp] [3,]
|
||||||
----------------------------------------------------------------------
|
----------------------------------------------------------------------
|
||||||
### Started At:
|
### Started At:
|
||||||
[timestamp] [2,3,]
|
[timestamp] [2,]
|
||||||
|
[timestamp] [3,]
|
||||||
----------------------------------------------------------------------
|
----------------------------------------------------------------------
|
||||||
### Finished At:
|
### Finished At:
|
||||||
[timestamp] [2,3,]
|
[timestamp] [2,]
|
||||||
|
[timestamp] [3,]
|
||||||
----------------------------------------------------------------------
|
----------------------------------------------------------------------
|
||||||
### File Store:
|
### File Store:
|
||||||
00000000-0000-0000-0000-000000000001
|
00000000-0000-0000-0000-000000000001
|
||||||
|
@ -1,90 +0,0 @@
|
|||||||
---
|
|
||||||
source: index-scheduler/src/lib.rs
|
|
||||||
---
|
|
||||||
[
|
|
||||||
{
|
|
||||||
"uid": 0,
|
|
||||||
"enqueuedAt": "[date]",
|
|
||||||
"startedAt": "[date]",
|
|
||||||
"finishedAt": "[date]",
|
|
||||||
"error": null,
|
|
||||||
"canceledBy": null,
|
|
||||||
"details": {
|
|
||||||
"IndexInfo": {
|
|
||||||
"primary_key": null
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"status": "succeeded",
|
|
||||||
"kind": {
|
|
||||||
"indexCreation": {
|
|
||||||
"index_uid": "doggo",
|
|
||||||
"primary_key": null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"uid": 1,
|
|
||||||
"enqueuedAt": "[date]",
|
|
||||||
"startedAt": "[date]",
|
|
||||||
"finishedAt": "[date]",
|
|
||||||
"error": {
|
|
||||||
"message": "Index `doggo` already exists.",
|
|
||||||
"code": "index_already_exists",
|
|
||||||
"type": "invalid_request",
|
|
||||||
"link": "https://docs.meilisearch.com/errors#index_already_exists"
|
|
||||||
},
|
|
||||||
"canceledBy": null,
|
|
||||||
"details": {
|
|
||||||
"IndexInfo": {
|
|
||||||
"primary_key": null
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"status": "failed",
|
|
||||||
"kind": {
|
|
||||||
"indexCreation": {
|
|
||||||
"index_uid": "doggo",
|
|
||||||
"primary_key": null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"uid": 2,
|
|
||||||
"enqueuedAt": "[date]",
|
|
||||||
"startedAt": "[date]",
|
|
||||||
"finishedAt": "[date]",
|
|
||||||
"error": null,
|
|
||||||
"canceledBy": null,
|
|
||||||
"details": {
|
|
||||||
"IndexInfo": {
|
|
||||||
"primary_key": null
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"status": "enqueued",
|
|
||||||
"kind": {
|
|
||||||
"indexCreation": {
|
|
||||||
"index_uid": "doggo",
|
|
||||||
"primary_key": null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"uid": 3,
|
|
||||||
"enqueuedAt": "[date]",
|
|
||||||
"startedAt": "[date]",
|
|
||||||
"finishedAt": "[date]",
|
|
||||||
"error": null,
|
|
||||||
"canceledBy": null,
|
|
||||||
"details": {
|
|
||||||
"IndexInfo": {
|
|
||||||
"primary_key": null
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"status": "enqueued",
|
|
||||||
"kind": {
|
|
||||||
"indexCreation": {
|
|
||||||
"index_uid": "doggo",
|
|
||||||
"primary_key": null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
@ -1,90 +0,0 @@
|
|||||||
---
|
|
||||||
source: index-scheduler/src/lib.rs
|
|
||||||
---
|
|
||||||
[
|
|
||||||
{
|
|
||||||
"uid": 0,
|
|
||||||
"enqueuedAt": "[date]",
|
|
||||||
"startedAt": "[date]",
|
|
||||||
"finishedAt": "[date]",
|
|
||||||
"error": null,
|
|
||||||
"canceledBy": null,
|
|
||||||
"details": {
|
|
||||||
"IndexInfo": {
|
|
||||||
"primary_key": null
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"status": "succeeded",
|
|
||||||
"kind": {
|
|
||||||
"indexCreation": {
|
|
||||||
"index_uid": "doggo",
|
|
||||||
"primary_key": null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"uid": 1,
|
|
||||||
"enqueuedAt": "[date]",
|
|
||||||
"startedAt": "[date]",
|
|
||||||
"finishedAt": "[date]",
|
|
||||||
"error": {
|
|
||||||
"message": "Index `doggo` already exists.",
|
|
||||||
"code": "index_already_exists",
|
|
||||||
"type": "invalid_request",
|
|
||||||
"link": "https://docs.meilisearch.com/errors#index_already_exists"
|
|
||||||
},
|
|
||||||
"canceledBy": null,
|
|
||||||
"details": {
|
|
||||||
"IndexInfo": {
|
|
||||||
"primary_key": null
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"status": "failed",
|
|
||||||
"kind": {
|
|
||||||
"indexCreation": {
|
|
||||||
"index_uid": "doggo",
|
|
||||||
"primary_key": null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"uid": 2,
|
|
||||||
"enqueuedAt": "[date]",
|
|
||||||
"startedAt": "[date]",
|
|
||||||
"finishedAt": "[date]",
|
|
||||||
"error": null,
|
|
||||||
"canceledBy": null,
|
|
||||||
"details": {
|
|
||||||
"IndexInfo": {
|
|
||||||
"primary_key": null
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"status": "enqueued",
|
|
||||||
"kind": {
|
|
||||||
"indexCreation": {
|
|
||||||
"index_uid": "doggo",
|
|
||||||
"primary_key": null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"uid": 3,
|
|
||||||
"enqueuedAt": "[date]",
|
|
||||||
"startedAt": "[date]",
|
|
||||||
"finishedAt": "[date]",
|
|
||||||
"error": null,
|
|
||||||
"canceledBy": null,
|
|
||||||
"details": {
|
|
||||||
"IndexInfo": {
|
|
||||||
"primary_key": null
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"status": "enqueued",
|
|
||||||
"kind": {
|
|
||||||
"indexCreation": {
|
|
||||||
"index_uid": "doggo",
|
|
||||||
"primary_key": null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
@ -1,36 +0,0 @@
|
|||||||
---
|
|
||||||
source: index-scheduler/src/lib.rs
|
|
||||||
---
|
|
||||||
### Autobatching Enabled = true
|
|
||||||
### Processing Tasks:
|
|
||||||
[]
|
|
||||||
----------------------------------------------------------------------
|
|
||||||
### All Tasks:
|
|
||||||
0 {uid: 0, status: enqueued, details: { settings: Settings { displayed_attributes: WildcardSetting(NotSet), searchable_attributes: WildcardSetting(NotSet), filterable_attributes: NotSet, sortable_attributes: NotSet, ranking_rules: NotSet, stop_words: NotSet, non_separator_tokens: NotSet, separator_tokens: NotSet, dictionary: NotSet, synonyms: NotSet, distinct_attribute: NotSet, proximity_precision: NotSet, typo_tolerance: NotSet, faceting: NotSet, pagination: NotSet, embedders: Set({"default": Set(EmbeddingSettings { source: Set(Rest), model: NotSet, revision: NotSet, api_key: Set("My super secret"), dimensions: Set(4), document_template: NotSet, url: Set("http://localhost:7777"), query: NotSet, input_field: NotSet, path_to_embeddings: NotSet, embedding_object: NotSet, input_type: NotSet, distribution: NotSet })}), search_cutoff_ms: NotSet, _kind: PhantomData<meilisearch_types::settings::Unchecked> } }, kind: SettingsUpdate { index_uid: "doggos", new_settings: Settings { displayed_attributes: WildcardSetting(NotSet), searchable_attributes: WildcardSetting(NotSet), filterable_attributes: NotSet, sortable_attributes: NotSet, ranking_rules: NotSet, stop_words: NotSet, non_separator_tokens: NotSet, separator_tokens: NotSet, dictionary: NotSet, synonyms: NotSet, distinct_attribute: NotSet, proximity_precision: NotSet, typo_tolerance: NotSet, faceting: NotSet, pagination: NotSet, embedders: Set({"default": Set(EmbeddingSettings { source: Set(Rest), model: NotSet, revision: NotSet, api_key: Set("My super secret"), dimensions: Set(4), document_template: NotSet, url: Set("http://localhost:7777"), query: NotSet, input_field: NotSet, path_to_embeddings: NotSet, embedding_object: NotSet, input_type: NotSet, distribution: NotSet })}), search_cutoff_ms: NotSet, _kind: PhantomData<meilisearch_types::settings::Unchecked> }, is_deletion: false, allow_index_creation: true }}
|
|
||||||
----------------------------------------------------------------------
|
|
||||||
### Status:
|
|
||||||
enqueued [0,]
|
|
||||||
----------------------------------------------------------------------
|
|
||||||
### Kind:
|
|
||||||
"settingsUpdate" [0,]
|
|
||||||
----------------------------------------------------------------------
|
|
||||||
### Index Tasks:
|
|
||||||
doggos [0,]
|
|
||||||
----------------------------------------------------------------------
|
|
||||||
### Index Mapper:
|
|
||||||
|
|
||||||
----------------------------------------------------------------------
|
|
||||||
### Canceled By:
|
|
||||||
|
|
||||||
----------------------------------------------------------------------
|
|
||||||
### Enqueued At:
|
|
||||||
[timestamp] [0,]
|
|
||||||
----------------------------------------------------------------------
|
|
||||||
### Started At:
|
|
||||||
----------------------------------------------------------------------
|
|
||||||
### Finished At:
|
|
||||||
----------------------------------------------------------------------
|
|
||||||
### File Store:
|
|
||||||
|
|
||||||
----------------------------------------------------------------------
|
|
||||||
|
|
@ -1,40 +0,0 @@
|
|||||||
---
|
|
||||||
source: index-scheduler/src/lib.rs
|
|
||||||
---
|
|
||||||
### Autobatching Enabled = true
|
|
||||||
### Processing Tasks:
|
|
||||||
[]
|
|
||||||
----------------------------------------------------------------------
|
|
||||||
### All Tasks:
|
|
||||||
0 {uid: 0, status: succeeded, details: { settings: Settings { displayed_attributes: WildcardSetting(NotSet), searchable_attributes: WildcardSetting(NotSet), filterable_attributes: NotSet, sortable_attributes: NotSet, ranking_rules: NotSet, stop_words: NotSet, non_separator_tokens: NotSet, separator_tokens: NotSet, dictionary: NotSet, synonyms: NotSet, distinct_attribute: NotSet, proximity_precision: NotSet, typo_tolerance: NotSet, faceting: NotSet, pagination: NotSet, embedders: Set({"default": Set(EmbeddingSettings { source: Set(Rest), model: NotSet, revision: NotSet, api_key: Set("My super secret"), dimensions: Set(4), document_template: NotSet, url: Set("http://localhost:7777"), query: NotSet, input_field: NotSet, path_to_embeddings: NotSet, embedding_object: NotSet, input_type: NotSet, distribution: NotSet })}), search_cutoff_ms: NotSet, _kind: PhantomData<meilisearch_types::settings::Unchecked> } }, kind: SettingsUpdate { index_uid: "doggos", new_settings: Settings { displayed_attributes: WildcardSetting(NotSet), searchable_attributes: WildcardSetting(NotSet), filterable_attributes: NotSet, sortable_attributes: NotSet, ranking_rules: NotSet, stop_words: NotSet, non_separator_tokens: NotSet, separator_tokens: NotSet, dictionary: NotSet, synonyms: NotSet, distinct_attribute: NotSet, proximity_precision: NotSet, typo_tolerance: NotSet, faceting: NotSet, pagination: NotSet, embedders: Set({"default": Set(EmbeddingSettings { source: Set(Rest), model: NotSet, revision: NotSet, api_key: Set("My super secret"), dimensions: Set(4), document_template: NotSet, url: Set("http://localhost:7777"), query: NotSet, input_field: NotSet, path_to_embeddings: NotSet, embedding_object: NotSet, input_type: NotSet, distribution: NotSet })}), search_cutoff_ms: NotSet, _kind: PhantomData<meilisearch_types::settings::Unchecked> }, is_deletion: false, allow_index_creation: true }}
|
|
||||||
----------------------------------------------------------------------
|
|
||||||
### Status:
|
|
||||||
enqueued []
|
|
||||||
succeeded [0,]
|
|
||||||
----------------------------------------------------------------------
|
|
||||||
### Kind:
|
|
||||||
"settingsUpdate" [0,]
|
|
||||||
----------------------------------------------------------------------
|
|
||||||
### Index Tasks:
|
|
||||||
doggos [0,]
|
|
||||||
----------------------------------------------------------------------
|
|
||||||
### Index Mapper:
|
|
||||||
doggos: { number_of_documents: 0, field_distribution: {} }
|
|
||||||
|
|
||||||
----------------------------------------------------------------------
|
|
||||||
### Canceled By:
|
|
||||||
|
|
||||||
----------------------------------------------------------------------
|
|
||||||
### Enqueued At:
|
|
||||||
[timestamp] [0,]
|
|
||||||
----------------------------------------------------------------------
|
|
||||||
### Started At:
|
|
||||||
[timestamp] [0,]
|
|
||||||
----------------------------------------------------------------------
|
|
||||||
### Finished At:
|
|
||||||
[timestamp] [0,]
|
|
||||||
----------------------------------------------------------------------
|
|
||||||
### File Store:
|
|
||||||
|
|
||||||
----------------------------------------------------------------------
|
|
||||||
|
|
@ -1,36 +0,0 @@
|
|||||||
---
|
|
||||||
source: index-scheduler/src/lib.rs
|
|
||||||
---
|
|
||||||
### Autobatching Enabled = true
|
|
||||||
### Processing Tasks:
|
|
||||||
[]
|
|
||||||
----------------------------------------------------------------------
|
|
||||||
### All Tasks:
|
|
||||||
0 {uid: 0, status: enqueued, details: { primary_key: Some("id") }, kind: IndexCreation { index_uid: "index_a", primary_key: Some("id") }}
|
|
||||||
----------------------------------------------------------------------
|
|
||||||
### Status:
|
|
||||||
enqueued [0,]
|
|
||||||
----------------------------------------------------------------------
|
|
||||||
### Kind:
|
|
||||||
"indexCreation" [0,]
|
|
||||||
----------------------------------------------------------------------
|
|
||||||
### Index Tasks:
|
|
||||||
index_a [0,]
|
|
||||||
----------------------------------------------------------------------
|
|
||||||
### Index Mapper:
|
|
||||||
|
|
||||||
----------------------------------------------------------------------
|
|
||||||
### Canceled By:
|
|
||||||
|
|
||||||
----------------------------------------------------------------------
|
|
||||||
### Enqueued At:
|
|
||||||
[timestamp] [0,]
|
|
||||||
----------------------------------------------------------------------
|
|
||||||
### Started At:
|
|
||||||
----------------------------------------------------------------------
|
|
||||||
### Finished At:
|
|
||||||
----------------------------------------------------------------------
|
|
||||||
### File Store:
|
|
||||||
|
|
||||||
----------------------------------------------------------------------
|
|
||||||
|
|
@ -3,9 +3,9 @@
|
|||||||
use std::collections::{BTreeSet, HashSet};
|
use std::collections::{BTreeSet, HashSet};
|
||||||
use std::ops::Bound;
|
use std::ops::Bound;
|
||||||
|
|
||||||
use meilisearch_types::heed::types::DecodeIgnore;
|
use meilisearch_types::heed::types::{DecodeIgnore, OwnedType};
|
||||||
use meilisearch_types::heed::{Database, RoTxn, RwTxn};
|
use meilisearch_types::heed::{Database, RoTxn, RwTxn};
|
||||||
use meilisearch_types::milli::CboRoaringBitmapCodec;
|
use meilisearch_types::milli::{CboRoaringBitmapCodec, BEU32};
|
||||||
use meilisearch_types::tasks::{Details, IndexSwap, Kind, KindWithContent, Status};
|
use meilisearch_types::tasks::{Details, IndexSwap, Kind, KindWithContent, Status};
|
||||||
use roaring::{MultiOps, RoaringBitmap};
|
use roaring::{MultiOps, RoaringBitmap};
|
||||||
use time::OffsetDateTime;
|
use time::OffsetDateTime;
|
||||||
@ -18,7 +18,7 @@ impl IndexScheduler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn last_task_id(&self, rtxn: &RoTxn) -> Result<Option<TaskId>> {
|
pub(crate) fn last_task_id(&self, rtxn: &RoTxn) -> Result<Option<TaskId>> {
|
||||||
Ok(self.all_tasks.remap_data_type::<DecodeIgnore>().last(rtxn)?.map(|(k, _)| k + 1))
|
Ok(self.all_tasks.remap_data_type::<DecodeIgnore>().last(rtxn)?.map(|(k, _)| k.get() + 1))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn next_task_id(&self, rtxn: &RoTxn) -> Result<TaskId> {
|
pub(crate) fn next_task_id(&self, rtxn: &RoTxn) -> Result<TaskId> {
|
||||||
@ -26,7 +26,7 @@ impl IndexScheduler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn get_task(&self, rtxn: &RoTxn, task_id: TaskId) -> Result<Option<Task>> {
|
pub(crate) fn get_task(&self, rtxn: &RoTxn, task_id: TaskId) -> Result<Option<Task>> {
|
||||||
Ok(self.all_tasks.get(rtxn, &task_id)?)
|
Ok(self.all_tasks.get(rtxn, &BEU32::new(task_id))?)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Convert an iterator to a `Vec` of tasks. The tasks MUST exist or a
|
/// Convert an iterator to a `Vec` of tasks. The tasks MUST exist or a
|
||||||
@ -88,7 +88,7 @@ impl IndexScheduler {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
self.all_tasks.put(wtxn, &task.uid, task)?;
|
self.all_tasks.put(wtxn, &BEU32::new(task.uid), task)?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -169,11 +169,11 @@ impl IndexScheduler {
|
|||||||
|
|
||||||
pub(crate) fn insert_task_datetime(
|
pub(crate) fn insert_task_datetime(
|
||||||
wtxn: &mut RwTxn,
|
wtxn: &mut RwTxn,
|
||||||
database: Database<BEI128, CboRoaringBitmapCodec>,
|
database: Database<OwnedType<BEI128>, CboRoaringBitmapCodec>,
|
||||||
time: OffsetDateTime,
|
time: OffsetDateTime,
|
||||||
task_id: TaskId,
|
task_id: TaskId,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
let timestamp = time.unix_timestamp_nanos();
|
let timestamp = BEI128::new(time.unix_timestamp_nanos());
|
||||||
let mut task_ids = database.get(wtxn, ×tamp)?.unwrap_or_default();
|
let mut task_ids = database.get(wtxn, ×tamp)?.unwrap_or_default();
|
||||||
task_ids.insert(task_id);
|
task_ids.insert(task_id);
|
||||||
database.put(wtxn, ×tamp, &RoaringBitmap::from_iter(task_ids))?;
|
database.put(wtxn, ×tamp, &RoaringBitmap::from_iter(task_ids))?;
|
||||||
@ -182,11 +182,11 @@ pub(crate) fn insert_task_datetime(
|
|||||||
|
|
||||||
pub(crate) fn remove_task_datetime(
|
pub(crate) fn remove_task_datetime(
|
||||||
wtxn: &mut RwTxn,
|
wtxn: &mut RwTxn,
|
||||||
database: Database<BEI128, CboRoaringBitmapCodec>,
|
database: Database<OwnedType<BEI128>, CboRoaringBitmapCodec>,
|
||||||
time: OffsetDateTime,
|
time: OffsetDateTime,
|
||||||
task_id: TaskId,
|
task_id: TaskId,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
let timestamp = time.unix_timestamp_nanos();
|
let timestamp = BEI128::new(time.unix_timestamp_nanos());
|
||||||
if let Some(mut existing) = database.get(wtxn, ×tamp)? {
|
if let Some(mut existing) = database.get(wtxn, ×tamp)? {
|
||||||
existing.remove(task_id);
|
existing.remove(task_id);
|
||||||
if existing.is_empty() {
|
if existing.is_empty() {
|
||||||
@ -202,7 +202,7 @@ pub(crate) fn remove_task_datetime(
|
|||||||
pub(crate) fn keep_tasks_within_datetimes(
|
pub(crate) fn keep_tasks_within_datetimes(
|
||||||
rtxn: &RoTxn,
|
rtxn: &RoTxn,
|
||||||
tasks: &mut RoaringBitmap,
|
tasks: &mut RoaringBitmap,
|
||||||
database: Database<BEI128, CboRoaringBitmapCodec>,
|
database: Database<OwnedType<BEI128>, CboRoaringBitmapCodec>,
|
||||||
after: Option<OffsetDateTime>,
|
after: Option<OffsetDateTime>,
|
||||||
before: Option<OffsetDateTime>,
|
before: Option<OffsetDateTime>,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
@ -213,8 +213,8 @@ pub(crate) fn keep_tasks_within_datetimes(
|
|||||||
(Some(after), Some(before)) => (Bound::Excluded(*after), Bound::Excluded(*before)),
|
(Some(after), Some(before)) => (Bound::Excluded(*after), Bound::Excluded(*before)),
|
||||||
};
|
};
|
||||||
let mut collected_task_ids = RoaringBitmap::new();
|
let mut collected_task_ids = RoaringBitmap::new();
|
||||||
let start = map_bound(start, |b| b.unix_timestamp_nanos());
|
let start = map_bound(start, |b| BEI128::new(b.unix_timestamp_nanos()));
|
||||||
let end = map_bound(end, |b| b.unix_timestamp_nanos());
|
let end = map_bound(end, |b| BEI128::new(b.unix_timestamp_nanos()));
|
||||||
let iter = database.range(rtxn, &(start, end))?;
|
let iter = database.range(rtxn, &(start, end))?;
|
||||||
for r in iter {
|
for r in iter {
|
||||||
let (_timestamp, task_ids) = r?;
|
let (_timestamp, task_ids) = r?;
|
||||||
@ -337,6 +337,8 @@ impl IndexScheduler {
|
|||||||
let rtxn = self.env.read_txn().unwrap();
|
let rtxn = self.env.read_txn().unwrap();
|
||||||
for task in self.all_tasks.iter(&rtxn).unwrap() {
|
for task in self.all_tasks.iter(&rtxn).unwrap() {
|
||||||
let (task_id, task) = task.unwrap();
|
let (task_id, task) = task.unwrap();
|
||||||
|
let task_id = task_id.get();
|
||||||
|
|
||||||
let task_index_uid = task.index_uid().map(ToOwned::to_owned);
|
let task_index_uid = task.index_uid().map(ToOwned::to_owned);
|
||||||
|
|
||||||
let Task {
|
let Task {
|
||||||
@ -359,13 +361,16 @@ impl IndexScheduler {
|
|||||||
.unwrap()
|
.unwrap()
|
||||||
.contains(task.uid));
|
.contains(task.uid));
|
||||||
}
|
}
|
||||||
let db_enqueued_at =
|
let db_enqueued_at = self
|
||||||
self.enqueued_at.get(&rtxn, &enqueued_at.unix_timestamp_nanos()).unwrap().unwrap();
|
.enqueued_at
|
||||||
|
.get(&rtxn, &BEI128::new(enqueued_at.unix_timestamp_nanos()))
|
||||||
|
.unwrap()
|
||||||
|
.unwrap();
|
||||||
assert!(db_enqueued_at.contains(task_id));
|
assert!(db_enqueued_at.contains(task_id));
|
||||||
if let Some(started_at) = started_at {
|
if let Some(started_at) = started_at {
|
||||||
let db_started_at = self
|
let db_started_at = self
|
||||||
.started_at
|
.started_at
|
||||||
.get(&rtxn, &started_at.unix_timestamp_nanos())
|
.get(&rtxn, &BEI128::new(started_at.unix_timestamp_nanos()))
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.unwrap();
|
.unwrap();
|
||||||
assert!(db_started_at.contains(task_id));
|
assert!(db_started_at.contains(task_id));
|
||||||
@ -373,7 +378,7 @@ impl IndexScheduler {
|
|||||||
if let Some(finished_at) = finished_at {
|
if let Some(finished_at) = finished_at {
|
||||||
let db_finished_at = self
|
let db_finished_at = self
|
||||||
.finished_at
|
.finished_at
|
||||||
.get(&rtxn, &finished_at.unix_timestamp_nanos())
|
.get(&rtxn, &BEI128::new(finished_at.unix_timestamp_nanos()))
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.unwrap();
|
.unwrap();
|
||||||
assert!(db_finished_at.contains(task_id));
|
assert!(db_finished_at.contains(task_id));
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
use std::borrow::Cow;
|
use std::borrow::Cow;
|
||||||
|
use std::convert::TryInto;
|
||||||
|
|
||||||
use meilisearch_types::heed::{BoxedError, BytesDecode, BytesEncode};
|
use meilisearch_types::heed::{BytesDecode, BytesEncode};
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
|
|
||||||
/// A heed codec for value of struct Uuid.
|
/// A heed codec for value of struct Uuid.
|
||||||
@ -9,15 +10,15 @@ pub struct UuidCodec;
|
|||||||
impl<'a> BytesDecode<'a> for UuidCodec {
|
impl<'a> BytesDecode<'a> for UuidCodec {
|
||||||
type DItem = Uuid;
|
type DItem = Uuid;
|
||||||
|
|
||||||
fn bytes_decode(bytes: &'a [u8]) -> Result<Self::DItem, BoxedError> {
|
fn bytes_decode(bytes: &'a [u8]) -> Option<Self::DItem> {
|
||||||
bytes.try_into().map(Uuid::from_bytes).map_err(Into::into)
|
bytes.try_into().ok().map(Uuid::from_bytes)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl BytesEncode<'_> for UuidCodec {
|
impl BytesEncode<'_> for UuidCodec {
|
||||||
type EItem = Uuid;
|
type EItem = Uuid;
|
||||||
|
|
||||||
fn bytes_encode(item: &Self::EItem) -> Result<Cow<[u8]>, BoxedError> {
|
fn bytes_encode(item: &Self::EItem) -> Option<Cow<[u8]>> {
|
||||||
Ok(Cow::Borrowed(item.as_bytes()))
|
Some(Cow::Borrowed(item.as_bytes()))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
12
index-stats/Cargo.toml
Normal file
12
index-stats/Cargo.toml
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
[package]
|
||||||
|
name = "index-stats"
|
||||||
|
description = "A small program that computes internal stats of a Meilisearch index"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2021"
|
||||||
|
publish = false
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
anyhow = "1.0.71"
|
||||||
|
clap = { version = "4.3.5", features = ["derive"] }
|
||||||
|
milli = { path = "../milli" }
|
||||||
|
piechart = "1.0.0"
|
224
index-stats/src/main.rs
Normal file
224
index-stats/src/main.rs
Normal file
@ -0,0 +1,224 @@
|
|||||||
|
use std::cmp::Reverse;
|
||||||
|
use std::path::PathBuf;
|
||||||
|
|
||||||
|
use clap::Parser;
|
||||||
|
use milli::heed::{types::ByteSlice, EnvOpenOptions, PolyDatabase, RoTxn};
|
||||||
|
use milli::index::db_name::*;
|
||||||
|
use milli::index::Index;
|
||||||
|
use piechart::{Chart, Color, Data};
|
||||||
|
|
||||||
|
/// Simple program to greet a person
|
||||||
|
#[derive(Parser, Debug)]
|
||||||
|
#[command(author, version, about, long_about = None)]
|
||||||
|
struct Args {
|
||||||
|
/// The path to the LMDB Meilisearch index database.
|
||||||
|
path: PathBuf,
|
||||||
|
|
||||||
|
/// The radius of the graphs
|
||||||
|
#[clap(long, default_value_t = 10)]
|
||||||
|
graph_radius: u16,
|
||||||
|
|
||||||
|
/// The radius of the graphs
|
||||||
|
#[clap(long, default_value_t = 6)]
|
||||||
|
graph_aspect_ratio: u16,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() -> anyhow::Result<()> {
|
||||||
|
let Args { path, graph_radius, graph_aspect_ratio } = Args::parse();
|
||||||
|
let env = EnvOpenOptions::new().max_dbs(24).open(path)?;
|
||||||
|
|
||||||
|
// TODO not sure to keep that...
|
||||||
|
// if removed put the pub(crate) back in the Index struct
|
||||||
|
matches!(
|
||||||
|
Option::<Index>::None,
|
||||||
|
Some(Index {
|
||||||
|
env: _,
|
||||||
|
main: _,
|
||||||
|
word_docids: _,
|
||||||
|
exact_word_docids: _,
|
||||||
|
word_prefix_docids: _,
|
||||||
|
exact_word_prefix_docids: _,
|
||||||
|
word_pair_proximity_docids: _,
|
||||||
|
word_prefix_pair_proximity_docids: _,
|
||||||
|
prefix_word_pair_proximity_docids: _,
|
||||||
|
word_position_docids: _,
|
||||||
|
word_fid_docids: _,
|
||||||
|
field_id_word_count_docids: _,
|
||||||
|
word_prefix_position_docids: _,
|
||||||
|
word_prefix_fid_docids: _,
|
||||||
|
script_language_docids: _,
|
||||||
|
facet_id_exists_docids: _,
|
||||||
|
facet_id_is_null_docids: _,
|
||||||
|
facet_id_is_empty_docids: _,
|
||||||
|
facet_id_f64_docids: _,
|
||||||
|
facet_id_string_docids: _,
|
||||||
|
field_id_docid_facet_f64s: _,
|
||||||
|
field_id_docid_facet_strings: _,
|
||||||
|
documents: _,
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
let mut wtxn = env.write_txn()?;
|
||||||
|
let main = env.create_poly_database(&mut wtxn, Some(MAIN))?;
|
||||||
|
let word_docids = env.create_poly_database(&mut wtxn, Some(WORD_DOCIDS))?;
|
||||||
|
let exact_word_docids = env.create_poly_database(&mut wtxn, Some(EXACT_WORD_DOCIDS))?;
|
||||||
|
let word_prefix_docids = env.create_poly_database(&mut wtxn, Some(WORD_PREFIX_DOCIDS))?;
|
||||||
|
let exact_word_prefix_docids =
|
||||||
|
env.create_poly_database(&mut wtxn, Some(EXACT_WORD_PREFIX_DOCIDS))?;
|
||||||
|
let word_pair_proximity_docids =
|
||||||
|
env.create_poly_database(&mut wtxn, Some(WORD_PAIR_PROXIMITY_DOCIDS))?;
|
||||||
|
let script_language_docids =
|
||||||
|
env.create_poly_database(&mut wtxn, Some(SCRIPT_LANGUAGE_DOCIDS))?;
|
||||||
|
let word_prefix_pair_proximity_docids =
|
||||||
|
env.create_poly_database(&mut wtxn, Some(WORD_PREFIX_PAIR_PROXIMITY_DOCIDS))?;
|
||||||
|
let prefix_word_pair_proximity_docids =
|
||||||
|
env.create_poly_database(&mut wtxn, Some(PREFIX_WORD_PAIR_PROXIMITY_DOCIDS))?;
|
||||||
|
let word_position_docids = env.create_poly_database(&mut wtxn, Some(WORD_POSITION_DOCIDS))?;
|
||||||
|
let word_fid_docids = env.create_poly_database(&mut wtxn, Some(WORD_FIELD_ID_DOCIDS))?;
|
||||||
|
let field_id_word_count_docids =
|
||||||
|
env.create_poly_database(&mut wtxn, Some(FIELD_ID_WORD_COUNT_DOCIDS))?;
|
||||||
|
let word_prefix_position_docids =
|
||||||
|
env.create_poly_database(&mut wtxn, Some(WORD_PREFIX_POSITION_DOCIDS))?;
|
||||||
|
let word_prefix_fid_docids =
|
||||||
|
env.create_poly_database(&mut wtxn, Some(WORD_PREFIX_FIELD_ID_DOCIDS))?;
|
||||||
|
let facet_id_f64_docids = env.create_poly_database(&mut wtxn, Some(FACET_ID_F64_DOCIDS))?;
|
||||||
|
let facet_id_string_docids =
|
||||||
|
env.create_poly_database(&mut wtxn, Some(FACET_ID_STRING_DOCIDS))?;
|
||||||
|
let facet_id_exists_docids =
|
||||||
|
env.create_poly_database(&mut wtxn, Some(FACET_ID_EXISTS_DOCIDS))?;
|
||||||
|
let facet_id_is_null_docids =
|
||||||
|
env.create_poly_database(&mut wtxn, Some(FACET_ID_IS_NULL_DOCIDS))?;
|
||||||
|
let facet_id_is_empty_docids =
|
||||||
|
env.create_poly_database(&mut wtxn, Some(FACET_ID_IS_EMPTY_DOCIDS))?;
|
||||||
|
let field_id_docid_facet_f64s =
|
||||||
|
env.create_poly_database(&mut wtxn, Some(FIELD_ID_DOCID_FACET_F64S))?;
|
||||||
|
let field_id_docid_facet_strings =
|
||||||
|
env.create_poly_database(&mut wtxn, Some(FIELD_ID_DOCID_FACET_STRINGS))?;
|
||||||
|
let documents = env.create_poly_database(&mut wtxn, Some(DOCUMENTS))?;
|
||||||
|
wtxn.commit()?;
|
||||||
|
|
||||||
|
let list = [
|
||||||
|
(main, MAIN),
|
||||||
|
(word_docids, WORD_DOCIDS),
|
||||||
|
(exact_word_docids, EXACT_WORD_DOCIDS),
|
||||||
|
(word_prefix_docids, WORD_PREFIX_DOCIDS),
|
||||||
|
(exact_word_prefix_docids, EXACT_WORD_PREFIX_DOCIDS),
|
||||||
|
(word_pair_proximity_docids, WORD_PAIR_PROXIMITY_DOCIDS),
|
||||||
|
(script_language_docids, SCRIPT_LANGUAGE_DOCIDS),
|
||||||
|
(word_prefix_pair_proximity_docids, WORD_PREFIX_PAIR_PROXIMITY_DOCIDS),
|
||||||
|
(prefix_word_pair_proximity_docids, PREFIX_WORD_PAIR_PROXIMITY_DOCIDS),
|
||||||
|
(word_position_docids, WORD_POSITION_DOCIDS),
|
||||||
|
(word_fid_docids, WORD_FIELD_ID_DOCIDS),
|
||||||
|
(field_id_word_count_docids, FIELD_ID_WORD_COUNT_DOCIDS),
|
||||||
|
(word_prefix_position_docids, WORD_PREFIX_POSITION_DOCIDS),
|
||||||
|
(word_prefix_fid_docids, WORD_PREFIX_FIELD_ID_DOCIDS),
|
||||||
|
(facet_id_f64_docids, FACET_ID_F64_DOCIDS),
|
||||||
|
(facet_id_string_docids, FACET_ID_STRING_DOCIDS),
|
||||||
|
(facet_id_exists_docids, FACET_ID_EXISTS_DOCIDS),
|
||||||
|
(facet_id_is_null_docids, FACET_ID_IS_NULL_DOCIDS),
|
||||||
|
(facet_id_is_empty_docids, FACET_ID_IS_EMPTY_DOCIDS),
|
||||||
|
(field_id_docid_facet_f64s, FIELD_ID_DOCID_FACET_F64S),
|
||||||
|
(field_id_docid_facet_strings, FIELD_ID_DOCID_FACET_STRINGS),
|
||||||
|
(documents, DOCUMENTS),
|
||||||
|
];
|
||||||
|
|
||||||
|
let rtxn = env.read_txn()?;
|
||||||
|
let result: Result<Vec<_>, _> =
|
||||||
|
list.into_iter().map(|(db, name)| compute_stats(&rtxn, db).map(|s| (s, name))).collect();
|
||||||
|
let mut stats = result?;
|
||||||
|
|
||||||
|
println!("{:1$} Number of Entries", "", graph_radius as usize * 2);
|
||||||
|
stats.sort_by_key(|(s, _)| Reverse(s.number_of_entries));
|
||||||
|
let data = compute_graph_data(stats.iter().map(|(s, n)| (s.number_of_entries as f32, *n)));
|
||||||
|
Chart::new().radius(graph_radius).aspect_ratio(graph_aspect_ratio).draw(&data);
|
||||||
|
display_legend(&data);
|
||||||
|
print!("\r\n");
|
||||||
|
|
||||||
|
println!("{:1$} Size of Entries", "", graph_radius as usize * 2);
|
||||||
|
stats.sort_by_key(|(s, _)| Reverse(s.size_of_entries));
|
||||||
|
let data = compute_graph_data(stats.iter().map(|(s, n)| (s.size_of_entries as f32, *n)));
|
||||||
|
Chart::new().radius(graph_radius).aspect_ratio(graph_aspect_ratio).draw(&data);
|
||||||
|
display_legend(&data);
|
||||||
|
print!("\r\n");
|
||||||
|
|
||||||
|
println!("{:1$} Size of Data", "", graph_radius as usize * 2);
|
||||||
|
stats.sort_by_key(|(s, _)| Reverse(s.size_of_data));
|
||||||
|
let data = compute_graph_data(stats.iter().map(|(s, n)| (s.size_of_data as f32, *n)));
|
||||||
|
Chart::new().radius(graph_radius).aspect_ratio(graph_aspect_ratio).draw(&data);
|
||||||
|
display_legend(&data);
|
||||||
|
print!("\r\n");
|
||||||
|
|
||||||
|
println!("{:1$} Size of Keys", "", graph_radius as usize * 2);
|
||||||
|
stats.sort_by_key(|(s, _)| Reverse(s.size_of_keys));
|
||||||
|
let data = compute_graph_data(stats.iter().map(|(s, n)| (s.size_of_keys as f32, *n)));
|
||||||
|
Chart::new().radius(graph_radius).aspect_ratio(graph_aspect_ratio).draw(&data);
|
||||||
|
display_legend(&data);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn display_legend(data: &[Data]) {
|
||||||
|
let total: f32 = data.iter().map(|d| d.value).sum();
|
||||||
|
for Data { label, value, color, fill } in data {
|
||||||
|
println!(
|
||||||
|
"{} {} {:.02}%",
|
||||||
|
color.unwrap().paint(fill.to_string()),
|
||||||
|
label,
|
||||||
|
value / total * 100.0
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn compute_graph_data<'a>(stats: impl IntoIterator<Item = (f32, &'a str)>) -> Vec<Data> {
|
||||||
|
let mut colors = [
|
||||||
|
Color::Red,
|
||||||
|
Color::Green,
|
||||||
|
Color::Yellow,
|
||||||
|
Color::Blue,
|
||||||
|
Color::Purple,
|
||||||
|
Color::Cyan,
|
||||||
|
Color::White,
|
||||||
|
]
|
||||||
|
.into_iter()
|
||||||
|
.cycle();
|
||||||
|
|
||||||
|
let mut characters = ['▴', '▵', '▾', '▿', '▪', '▫', '•', '◦'].into_iter().cycle();
|
||||||
|
|
||||||
|
stats
|
||||||
|
.into_iter()
|
||||||
|
.map(|(value, name)| Data {
|
||||||
|
label: (*name).into(),
|
||||||
|
value,
|
||||||
|
color: Some(colors.next().unwrap().into()),
|
||||||
|
fill: characters.next().unwrap(),
|
||||||
|
})
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct Stats {
|
||||||
|
pub number_of_entries: u64,
|
||||||
|
pub size_of_keys: u64,
|
||||||
|
pub size_of_data: u64,
|
||||||
|
pub size_of_entries: u64,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn compute_stats(rtxn: &RoTxn, db: PolyDatabase) -> anyhow::Result<Stats> {
|
||||||
|
let mut number_of_entries = 0;
|
||||||
|
let mut size_of_keys = 0;
|
||||||
|
let mut size_of_data = 0;
|
||||||
|
|
||||||
|
for result in db.iter::<_, ByteSlice, ByteSlice>(rtxn)? {
|
||||||
|
let (key, data) = result?;
|
||||||
|
number_of_entries += 1;
|
||||||
|
size_of_keys += key.len() as u64;
|
||||||
|
size_of_data += data.len() as u64;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(Stats {
|
||||||
|
number_of_entries,
|
||||||
|
size_of_keys,
|
||||||
|
size_of_data,
|
||||||
|
size_of_entries: size_of_keys + size_of_data,
|
||||||
|
})
|
||||||
|
}
|
@ -15,7 +15,7 @@ license.workspace = true
|
|||||||
serde_json = "1.0"
|
serde_json = "1.0"
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
criterion = "0.5.1"
|
criterion = "0.4.0"
|
||||||
|
|
||||||
[[bench]]
|
[[bench]]
|
||||||
name = "depth"
|
name = "depth"
|
||||||
|
@ -11,6 +11,6 @@ edition.workspace = true
|
|||||||
license.workspace = true
|
license.workspace = true
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
insta = { version = "^1.34.0", features = ["json", "redactions"] }
|
insta = { version = "^1.29.0", features = ["json", "redactions"] }
|
||||||
md5 = "0.7.0"
|
md5 = "0.7.0"
|
||||||
once_cell = "1.19"
|
once_cell = "1.17"
|
||||||
|
@ -167,9 +167,7 @@ macro_rules! snapshot {
|
|||||||
let (settings, snap_name, _) = $crate::default_snapshot_settings_for_test(test_name, Some(&snap_name));
|
let (settings, snap_name, _) = $crate::default_snapshot_settings_for_test(test_name, Some(&snap_name));
|
||||||
settings.bind(|| {
|
settings.bind(|| {
|
||||||
let snap = format!("{}", $value);
|
let snap = format!("{}", $value);
|
||||||
insta::allow_duplicates! {
|
|
||||||
meili_snap::insta::assert_snapshot!(format!("{}", snap_name), snap);
|
meili_snap::insta::assert_snapshot!(format!("{}", snap_name), snap);
|
||||||
}
|
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
($value:expr, @$inline:literal) => {
|
($value:expr, @$inline:literal) => {
|
||||||
@ -178,9 +176,7 @@ macro_rules! snapshot {
|
|||||||
let (settings, _, _) = $crate::default_snapshot_settings_for_test("", Some("_dummy_argument"));
|
let (settings, _, _) = $crate::default_snapshot_settings_for_test("", Some("_dummy_argument"));
|
||||||
settings.bind(|| {
|
settings.bind(|| {
|
||||||
let snap = format!("{}", $value);
|
let snap = format!("{}", $value);
|
||||||
insta::allow_duplicates! {
|
|
||||||
meili_snap::insta::assert_snapshot!(snap, @$inline);
|
meili_snap::insta::assert_snapshot!(snap, @$inline);
|
||||||
}
|
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
($value:expr) => {
|
($value:expr) => {
|
||||||
@ -198,37 +194,11 @@ macro_rules! snapshot {
|
|||||||
let (settings, snap_name, _) = $crate::default_snapshot_settings_for_test(test_name, None);
|
let (settings, snap_name, _) = $crate::default_snapshot_settings_for_test(test_name, None);
|
||||||
settings.bind(|| {
|
settings.bind(|| {
|
||||||
let snap = format!("{}", $value);
|
let snap = format!("{}", $value);
|
||||||
insta::allow_duplicates! {
|
|
||||||
meili_snap::insta::assert_snapshot!(format!("{}", snap_name), snap);
|
meili_snap::insta::assert_snapshot!(format!("{}", snap_name), snap);
|
||||||
}
|
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Create a string from the value by serializing it as Json, optionally
|
|
||||||
/// redacting some parts of it.
|
|
||||||
///
|
|
||||||
/// The second argument to the macro can be an object expression for redaction.
|
|
||||||
/// It's in the form { selector => replacement }. For more information about redactions
|
|
||||||
/// refer to the redactions feature in the `insta` guide.
|
|
||||||
#[macro_export]
|
|
||||||
macro_rules! json_string {
|
|
||||||
($value:expr, {$($k:expr => $v:expr),*$(,)?}) => {
|
|
||||||
{
|
|
||||||
let (_, snap) = meili_snap::insta::_prepare_snapshot_for_redaction!($value, {$($k => $v),*}, Json, File);
|
|
||||||
snap
|
|
||||||
}
|
|
||||||
};
|
|
||||||
($value:expr) => {{
|
|
||||||
let value = meili_snap::insta::_macro_support::serialize_value(
|
|
||||||
&$value,
|
|
||||||
meili_snap::insta::_macro_support::SerializationFormat::Json,
|
|
||||||
meili_snap::insta::_macro_support::SnapshotLocation::File
|
|
||||||
);
|
|
||||||
value
|
|
||||||
}};
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use crate as meili_snap;
|
use crate as meili_snap;
|
||||||
@ -280,3 +250,27 @@ mod tests {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Create a string from the value by serializing it as Json, optionally
|
||||||
|
/// redacting some parts of it.
|
||||||
|
///
|
||||||
|
/// The second argument to the macro can be an object expression for redaction.
|
||||||
|
/// It's in the form { selector => replacement }. For more information about redactions
|
||||||
|
/// refer to the redactions feature in the `insta` guide.
|
||||||
|
#[macro_export]
|
||||||
|
macro_rules! json_string {
|
||||||
|
($value:expr, {$($k:expr => $v:expr),*$(,)?}) => {
|
||||||
|
{
|
||||||
|
let (_, snap) = meili_snap::insta::_prepare_snapshot_for_redaction!($value, {$($k => $v),*}, Json, File);
|
||||||
|
snap
|
||||||
|
}
|
||||||
|
};
|
||||||
|
($value:expr) => {{
|
||||||
|
let value = meili_snap::insta::_macro_support::serialize_value(
|
||||||
|
&$value,
|
||||||
|
meili_snap::insta::_macro_support::SerializationFormat::Json,
|
||||||
|
meili_snap::insta::_macro_support::SnapshotLocation::File
|
||||||
|
);
|
||||||
|
value
|
||||||
|
}};
|
||||||
|
}
|
||||||
|
@ -11,16 +11,16 @@ edition.workspace = true
|
|||||||
license.workspace = true
|
license.workspace = true
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
base64 = "0.21.7"
|
base64 = "0.21.0"
|
||||||
enum-iterator = "1.5.0"
|
enum-iterator = "1.4.0"
|
||||||
hmac = "0.12.1"
|
hmac = "0.12.1"
|
||||||
maplit = "1.0.2"
|
maplit = "1.0.2"
|
||||||
meilisearch-types = { path = "../meilisearch-types" }
|
meilisearch-types = { path = "../meilisearch-types" }
|
||||||
rand = "0.8.5"
|
rand = "0.8.5"
|
||||||
roaring = { version = "0.10.2", features = ["serde"] }
|
roaring = { version = "0.10.1", features = ["serde"] }
|
||||||
serde = { version = "1.0.195", features = ["derive"] }
|
serde = { version = "1.0.160", features = ["derive"] }
|
||||||
serde_json = { version = "1.0.111", features = ["preserve_order"] }
|
serde_json = { version = "1.0.95", features = ["preserve_order"] }
|
||||||
sha2 = "0.10.8"
|
sha2 = "0.10.6"
|
||||||
thiserror = "1.0.56"
|
thiserror = "1.0.40"
|
||||||
time = { version = "0.3.31", features = ["serde-well-known", "formatting", "parsing", "macros"] }
|
time = { version = "0.3.20", features = ["serde-well-known", "formatting", "parsing", "macros"] }
|
||||||
uuid = { version = "1.6.1", features = ["serde", "v4"] }
|
uuid = { version = "1.3.1", features = ["serde", "v4"] }
|
||||||
|
@ -1,22 +1,20 @@
|
|||||||
use std::borrow::Cow;
|
use std::borrow::Cow;
|
||||||
use std::cmp::Reverse;
|
use std::cmp::Reverse;
|
||||||
use std::collections::HashSet;
|
use std::collections::HashSet;
|
||||||
|
use std::convert::{TryFrom, TryInto};
|
||||||
use std::fs::create_dir_all;
|
use std::fs::create_dir_all;
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
use std::result::Result as StdResult;
|
|
||||||
use std::str;
|
use std::str;
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
use hmac::{Hmac, Mac};
|
use hmac::{Hmac, Mac};
|
||||||
use meilisearch_types::heed::BoxedError;
|
|
||||||
use meilisearch_types::index_uid_pattern::IndexUidPattern;
|
use meilisearch_types::index_uid_pattern::IndexUidPattern;
|
||||||
use meilisearch_types::keys::KeyId;
|
use meilisearch_types::keys::KeyId;
|
||||||
use meilisearch_types::milli;
|
use meilisearch_types::milli;
|
||||||
use meilisearch_types::milli::heed::types::{Bytes, DecodeIgnore, SerdeJson};
|
use meilisearch_types::milli::heed::types::{ByteSlice, DecodeIgnore, SerdeJson};
|
||||||
use meilisearch_types::milli::heed::{Database, Env, EnvOpenOptions, RwTxn};
|
use meilisearch_types::milli::heed::{Database, Env, EnvOpenOptions, RwTxn};
|
||||||
use sha2::Sha256;
|
use sha2::Sha256;
|
||||||
use thiserror::Error;
|
|
||||||
use time::OffsetDateTime;
|
use time::OffsetDateTime;
|
||||||
use uuid::fmt::Hyphenated;
|
use uuid::fmt::Hyphenated;
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
@ -32,7 +30,7 @@ const KEY_ID_ACTION_INDEX_EXPIRATION_DB_NAME: &str = "keyid-action-index-expirat
|
|||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct HeedAuthStore {
|
pub struct HeedAuthStore {
|
||||||
env: Arc<Env>,
|
env: Arc<Env>,
|
||||||
keys: Database<Bytes, SerdeJson<Key>>,
|
keys: Database<ByteSlice, SerdeJson<Key>>,
|
||||||
action_keyid_index_expiration: Database<KeyIdActionCodec, SerdeJson<Option<OffsetDateTime>>>,
|
action_keyid_index_expiration: Database<KeyIdActionCodec, SerdeJson<Option<OffsetDateTime>>>,
|
||||||
should_close_on_drop: bool,
|
should_close_on_drop: bool,
|
||||||
}
|
}
|
||||||
@ -131,9 +129,6 @@ impl HeedAuthStore {
|
|||||||
Action::DumpsAll => {
|
Action::DumpsAll => {
|
||||||
actions.insert(Action::DumpsCreate);
|
actions.insert(Action::DumpsCreate);
|
||||||
}
|
}
|
||||||
Action::SnapshotsAll => {
|
|
||||||
actions.insert(Action::SnapshotsCreate);
|
|
||||||
}
|
|
||||||
Action::TasksAll => {
|
Action::TasksAll => {
|
||||||
actions.extend([Action::TasksGet, Action::TasksDelete, Action::TasksCancel]);
|
actions.extend([Action::TasksGet, Action::TasksDelete, Action::TasksCancel]);
|
||||||
}
|
}
|
||||||
@ -278,7 +273,7 @@ impl HeedAuthStore {
|
|||||||
fn delete_key_from_inverted_db(&self, wtxn: &mut RwTxn, key: &KeyId) -> Result<()> {
|
fn delete_key_from_inverted_db(&self, wtxn: &mut RwTxn, key: &KeyId) -> Result<()> {
|
||||||
let mut iter = self
|
let mut iter = self
|
||||||
.action_keyid_index_expiration
|
.action_keyid_index_expiration
|
||||||
.remap_types::<Bytes, DecodeIgnore>()
|
.remap_types::<ByteSlice, DecodeIgnore>()
|
||||||
.prefix_iter_mut(wtxn, key.as_bytes())?;
|
.prefix_iter_mut(wtxn, key.as_bytes())?;
|
||||||
while iter.next().transpose()?.is_some() {
|
while iter.next().transpose()?.is_some() {
|
||||||
// safety: we don't keep references from inside the LMDB database.
|
// safety: we don't keep references from inside the LMDB database.
|
||||||
@ -296,24 +291,23 @@ pub struct KeyIdActionCodec;
|
|||||||
impl<'a> milli::heed::BytesDecode<'a> for KeyIdActionCodec {
|
impl<'a> milli::heed::BytesDecode<'a> for KeyIdActionCodec {
|
||||||
type DItem = (KeyId, Action, Option<&'a [u8]>);
|
type DItem = (KeyId, Action, Option<&'a [u8]>);
|
||||||
|
|
||||||
fn bytes_decode(bytes: &'a [u8]) -> StdResult<Self::DItem, BoxedError> {
|
fn bytes_decode(bytes: &'a [u8]) -> Option<Self::DItem> {
|
||||||
let (key_id_bytes, action_bytes) = try_split_array_at(bytes).ok_or(SliceTooShortError)?;
|
let (key_id_bytes, action_bytes) = try_split_array_at(bytes)?;
|
||||||
let (&action_byte, index) =
|
let (action_bytes, index) = match try_split_array_at(action_bytes)? {
|
||||||
match try_split_array_at(action_bytes).ok_or(SliceTooShortError)? {
|
(action, []) => (action, None),
|
||||||
([action], []) => (action, None),
|
(action, index) => (action, Some(index)),
|
||||||
([action], index) => (action, Some(index)),
|
|
||||||
};
|
};
|
||||||
let key_id = Uuid::from_bytes(*key_id_bytes);
|
let key_id = Uuid::from_bytes(*key_id_bytes);
|
||||||
let action = Action::from_repr(action_byte).ok_or(InvalidActionError { action_byte })?;
|
let action = Action::from_repr(u8::from_be_bytes(*action_bytes))?;
|
||||||
|
|
||||||
Ok((key_id, action, index))
|
Some((key_id, action, index))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> milli::heed::BytesEncode<'a> for KeyIdActionCodec {
|
impl<'a> milli::heed::BytesEncode<'a> for KeyIdActionCodec {
|
||||||
type EItem = (&'a KeyId, &'a Action, Option<&'a [u8]>);
|
type EItem = (&'a KeyId, &'a Action, Option<&'a [u8]>);
|
||||||
|
|
||||||
fn bytes_encode((key_id, action, index): &Self::EItem) -> StdResult<Cow<[u8]>, BoxedError> {
|
fn bytes_encode((key_id, action, index): &Self::EItem) -> Option<Cow<[u8]>> {
|
||||||
let mut bytes = Vec::new();
|
let mut bytes = Vec::new();
|
||||||
|
|
||||||
bytes.extend_from_slice(key_id.as_bytes());
|
bytes.extend_from_slice(key_id.as_bytes());
|
||||||
@ -323,20 +317,10 @@ impl<'a> milli::heed::BytesEncode<'a> for KeyIdActionCodec {
|
|||||||
bytes.extend_from_slice(index);
|
bytes.extend_from_slice(index);
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(Cow::Owned(bytes))
|
Some(Cow::Owned(bytes))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Error, Debug)]
|
|
||||||
#[error("the slice is too short")]
|
|
||||||
pub struct SliceTooShortError;
|
|
||||||
|
|
||||||
#[derive(Error, Debug)]
|
|
||||||
#[error("cannot construct a valid Action from {action_byte}")]
|
|
||||||
pub struct InvalidActionError {
|
|
||||||
pub action_byte: u8,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn generate_key_as_hexa(uid: Uuid, master_key: &[u8]) -> String {
|
pub fn generate_key_as_hexa(uid: Uuid, master_key: &[u8]) -> String {
|
||||||
// format uid as hyphenated allowing user to generate their own keys.
|
// format uid as hyphenated allowing user to generate their own keys.
|
||||||
let mut uid_buffer = [0; Hyphenated::LENGTH];
|
let mut uid_buffer = [0; Hyphenated::LENGTH];
|
||||||
|
@ -11,31 +11,31 @@ edition.workspace = true
|
|||||||
license.workspace = true
|
license.workspace = true
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
actix-web = { version = "4.5.1", default-features = false }
|
actix-web = { version = "4.3.1", default-features = false }
|
||||||
anyhow = "1.0.79"
|
anyhow = "1.0.70"
|
||||||
convert_case = "0.6.0"
|
convert_case = "0.6.0"
|
||||||
csv = "1.3.0"
|
csv = "1.2.1"
|
||||||
deserr = { version = "0.6.1", features = ["actix-web"] }
|
deserr = "0.5.0"
|
||||||
either = { version = "1.9.0", features = ["serde"] }
|
either = { version = "1.8.1", features = ["serde"] }
|
||||||
enum-iterator = "1.5.0"
|
enum-iterator = "1.4.0"
|
||||||
file-store = { path = "../file-store" }
|
file-store = { path = "../file-store" }
|
||||||
flate2 = "1.0.28"
|
flate2 = "1.0.25"
|
||||||
fst = "0.4.7"
|
fst = "0.4.7"
|
||||||
memmap2 = "0.7.1"
|
memmap2 = "0.5.10"
|
||||||
milli = { path = "../milli" }
|
milli = { path = "../milli" }
|
||||||
roaring = { version = "0.10.2", features = ["serde"] }
|
roaring = { version = "0.10.1", features = ["serde"] }
|
||||||
serde = { version = "1.0.195", features = ["derive"] }
|
serde = { version = "1.0.160", features = ["derive"] }
|
||||||
serde-cs = "0.2.4"
|
serde-cs = "0.2.4"
|
||||||
serde_json = "1.0.111"
|
serde_json = "1.0.95"
|
||||||
tar = "0.4.40"
|
tar = "0.4.38"
|
||||||
tempfile = "3.9.0"
|
tempfile = "3.5.0"
|
||||||
thiserror = "1.0.56"
|
thiserror = "1.0.40"
|
||||||
time = { version = "0.3.31", features = ["serde-well-known", "formatting", "parsing", "macros"] }
|
time = { version = "0.3.20", features = ["serde-well-known", "formatting", "parsing", "macros"] }
|
||||||
tokio = "1.35"
|
tokio = "1.27"
|
||||||
uuid = { version = "1.6.1", features = ["serde", "v4"] }
|
uuid = { version = "1.3.1", features = ["serde", "v4"] }
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
insta = "1.34.0"
|
insta = "1.29.0"
|
||||||
meili-snap = { path = "../meili-snap" }
|
meili-snap = { path = "../meili-snap" }
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
@ -44,16 +44,12 @@ all-tokenizations = ["milli/all-tokenizations"]
|
|||||||
|
|
||||||
# chinese specialized tokenization
|
# chinese specialized tokenization
|
||||||
chinese = ["milli/chinese"]
|
chinese = ["milli/chinese"]
|
||||||
chinese-pinyin = ["milli/chinese-pinyin"]
|
|
||||||
# hebrew specialized tokenization
|
# hebrew specialized tokenization
|
||||||
hebrew = ["milli/hebrew"]
|
hebrew = ["milli/hebrew"]
|
||||||
# japanese specialized tokenization
|
# japanese specialized tokenization
|
||||||
japanese = ["milli/japanese"]
|
japanese = ["milli/japanese"]
|
||||||
# thai specialized tokenization
|
# thai specialized tokenization
|
||||||
thai = ["milli/thai"]
|
thai = ["milli/thai"]
|
||||||
|
|
||||||
# allow greek specialized tokenization
|
# allow greek specialized tokenization
|
||||||
greek = ["milli/greek"]
|
greek = ["milli/greek"]
|
||||||
# allow khmer specialized tokenization
|
|
||||||
khmer = ["milli/khmer"]
|
|
||||||
# allow vietnamese specialized tokenization
|
|
||||||
vietnamese = ["milli/vietnamese"]
|
|
||||||
|
@ -151,10 +151,6 @@ make_missing_field_convenience_builder!(MissingApiKeyExpiresAt, missing_api_key_
|
|||||||
make_missing_field_convenience_builder!(MissingApiKeyIndexes, missing_api_key_indexes);
|
make_missing_field_convenience_builder!(MissingApiKeyIndexes, missing_api_key_indexes);
|
||||||
make_missing_field_convenience_builder!(MissingSwapIndexes, missing_swap_indexes);
|
make_missing_field_convenience_builder!(MissingSwapIndexes, missing_swap_indexes);
|
||||||
make_missing_field_convenience_builder!(MissingDocumentFilter, missing_document_filter);
|
make_missing_field_convenience_builder!(MissingDocumentFilter, missing_document_filter);
|
||||||
make_missing_field_convenience_builder!(
|
|
||||||
MissingFacetSearchFacetName,
|
|
||||||
missing_facet_search_facet_name
|
|
||||||
);
|
|
||||||
|
|
||||||
// Integrate a sub-error into a [`DeserrError`] by taking its error message but using
|
// Integrate a sub-error into a [`DeserrError`] by taking its error message but using
|
||||||
// the default error code (C) from `Self`
|
// the default error code (C) from `Self`
|
||||||
@ -188,4 +184,3 @@ merge_with_error_impl_take_error_message!(ParseOffsetDateTimeError);
|
|||||||
merge_with_error_impl_take_error_message!(ParseTaskKindError);
|
merge_with_error_impl_take_error_message!(ParseTaskKindError);
|
||||||
merge_with_error_impl_take_error_message!(ParseTaskStatusError);
|
merge_with_error_impl_take_error_message!(ParseTaskStatusError);
|
||||||
merge_with_error_impl_take_error_message!(IndexUidFormatError);
|
merge_with_error_impl_take_error_message!(IndexUidFormatError);
|
||||||
merge_with_error_impl_take_error_message!(InvalidSearchSemanticRatio);
|
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
|
use std::borrow::Borrow;
|
||||||
use std::fmt::{self, Debug, Display};
|
use std::fmt::{self, Debug, Display};
|
||||||
use std::fs::File;
|
use std::fs::File;
|
||||||
use std::io::{self, BufWriter, Write};
|
use std::io::{self, Seek, Write};
|
||||||
use std::marker::PhantomData;
|
use std::marker::PhantomData;
|
||||||
|
|
||||||
use memmap2::MmapOptions;
|
use memmap2::MmapOptions;
|
||||||
@ -41,7 +42,7 @@ impl Display for DocumentFormatError {
|
|||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
match self {
|
match self {
|
||||||
Self::Io(e) => write!(f, "{e}"),
|
Self::Io(e) => write!(f, "{e}"),
|
||||||
Self::MalformedPayload(me, b) => match me {
|
Self::MalformedPayload(me, b) => match me.borrow() {
|
||||||
Error::Json(se) => {
|
Error::Json(se) => {
|
||||||
let mut message = match se.classify() {
|
let mut message = match se.classify() {
|
||||||
Category::Data => {
|
Category::Data => {
|
||||||
@ -104,8 +105,8 @@ impl ErrorCode for DocumentFormatError {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Reads CSV from input and write an obkv batch to writer.
|
/// Reads CSV from input and write an obkv batch to writer.
|
||||||
pub fn read_csv(file: &File, writer: impl Write, delimiter: u8) -> Result<u64> {
|
pub fn read_csv(file: &File, writer: impl Write + Seek, delimiter: u8) -> Result<u64> {
|
||||||
let mut builder = DocumentsBatchBuilder::new(BufWriter::new(writer));
|
let mut builder = DocumentsBatchBuilder::new(writer);
|
||||||
let mmap = unsafe { MmapOptions::new().map(file)? };
|
let mmap = unsafe { MmapOptions::new().map(file)? };
|
||||||
let csv = csv::ReaderBuilder::new().delimiter(delimiter).from_reader(mmap.as_ref());
|
let csv = csv::ReaderBuilder::new().delimiter(delimiter).from_reader(mmap.as_ref());
|
||||||
builder.append_csv(csv).map_err(|e| (PayloadType::Csv { delimiter }, e))?;
|
builder.append_csv(csv).map_err(|e| (PayloadType::Csv { delimiter }, e))?;
|
||||||
@ -117,8 +118,8 @@ pub fn read_csv(file: &File, writer: impl Write, delimiter: u8) -> Result<u64> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Reads JSON from temporary file and write an obkv batch to writer.
|
/// Reads JSON from temporary file and write an obkv batch to writer.
|
||||||
pub fn read_json(file: &File, writer: impl Write) -> Result<u64> {
|
pub fn read_json(file: &File, writer: impl Write + Seek) -> Result<u64> {
|
||||||
let mut builder = DocumentsBatchBuilder::new(BufWriter::new(writer));
|
let mut builder = DocumentsBatchBuilder::new(writer);
|
||||||
let mmap = unsafe { MmapOptions::new().map(file)? };
|
let mmap = unsafe { MmapOptions::new().map(file)? };
|
||||||
let mut deserializer = serde_json::Deserializer::from_slice(&mmap);
|
let mut deserializer = serde_json::Deserializer::from_slice(&mmap);
|
||||||
|
|
||||||
@ -151,8 +152,8 @@ pub fn read_json(file: &File, writer: impl Write) -> Result<u64> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Reads JSON from temporary file and write an obkv batch to writer.
|
/// Reads JSON from temporary file and write an obkv batch to writer.
|
||||||
pub fn read_ndjson(file: &File, writer: impl Write) -> Result<u64> {
|
pub fn read_ndjson(file: &File, writer: impl Write + Seek) -> Result<u64> {
|
||||||
let mut builder = DocumentsBatchBuilder::new(BufWriter::new(writer));
|
let mut builder = DocumentsBatchBuilder::new(writer);
|
||||||
let mmap = unsafe { MmapOptions::new().map(file)? };
|
let mmap = unsafe { MmapOptions::new().map(file)? };
|
||||||
|
|
||||||
for result in serde_json::Deserializer::from_slice(&mmap).into_iter() {
|
for result in serde_json::Deserializer::from_slice(&mmap).into_iter() {
|
||||||
|
@ -2,7 +2,6 @@ use std::{fmt, io};
|
|||||||
|
|
||||||
use actix_web::http::StatusCode;
|
use actix_web::http::StatusCode;
|
||||||
use actix_web::{self as aweb, HttpResponseBuilder};
|
use actix_web::{self as aweb, HttpResponseBuilder};
|
||||||
use aweb::http::header;
|
|
||||||
use aweb::rt::task::JoinError;
|
use aweb::rt::task::JoinError;
|
||||||
use convert_case::Casing;
|
use convert_case::Casing;
|
||||||
use milli::heed::{Error as HeedError, MdbError};
|
use milli::heed::{Error as HeedError, MdbError};
|
||||||
@ -57,14 +56,7 @@ where
|
|||||||
impl aweb::error::ResponseError for ResponseError {
|
impl aweb::error::ResponseError for ResponseError {
|
||||||
fn error_response(&self) -> aweb::HttpResponse {
|
fn error_response(&self) -> aweb::HttpResponse {
|
||||||
let json = serde_json::to_vec(self).unwrap();
|
let json = serde_json::to_vec(self).unwrap();
|
||||||
let mut builder = HttpResponseBuilder::new(self.status_code());
|
HttpResponseBuilder::new(self.status_code()).content_type("application/json").body(json)
|
||||||
builder.content_type("application/json");
|
|
||||||
|
|
||||||
if self.code == StatusCode::SERVICE_UNAVAILABLE {
|
|
||||||
builder.insert_header((header::RETRY_AFTER, "10"));
|
|
||||||
}
|
|
||||||
|
|
||||||
builder.body(json)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn status_code(&self) -> StatusCode {
|
fn status_code(&self) -> StatusCode {
|
||||||
@ -225,26 +217,19 @@ InvalidDocumentFields , InvalidRequest , BAD_REQUEST ;
|
|||||||
MissingDocumentFilter , InvalidRequest , BAD_REQUEST ;
|
MissingDocumentFilter , InvalidRequest , BAD_REQUEST ;
|
||||||
InvalidDocumentFilter , InvalidRequest , BAD_REQUEST ;
|
InvalidDocumentFilter , InvalidRequest , BAD_REQUEST ;
|
||||||
InvalidDocumentGeoField , InvalidRequest , BAD_REQUEST ;
|
InvalidDocumentGeoField , InvalidRequest , BAD_REQUEST ;
|
||||||
InvalidVectorDimensions , InvalidRequest , BAD_REQUEST ;
|
|
||||||
InvalidVectorsType , InvalidRequest , BAD_REQUEST ;
|
|
||||||
InvalidDocumentId , InvalidRequest , BAD_REQUEST ;
|
InvalidDocumentId , InvalidRequest , BAD_REQUEST ;
|
||||||
InvalidDocumentLimit , InvalidRequest , BAD_REQUEST ;
|
InvalidDocumentLimit , InvalidRequest , BAD_REQUEST ;
|
||||||
InvalidDocumentOffset , InvalidRequest , BAD_REQUEST ;
|
InvalidDocumentOffset , InvalidRequest , BAD_REQUEST ;
|
||||||
InvalidEmbedder , InvalidRequest , BAD_REQUEST ;
|
|
||||||
InvalidHybridQuery , InvalidRequest , BAD_REQUEST ;
|
|
||||||
InvalidIndexLimit , InvalidRequest , BAD_REQUEST ;
|
InvalidIndexLimit , InvalidRequest , BAD_REQUEST ;
|
||||||
InvalidIndexOffset , InvalidRequest , BAD_REQUEST ;
|
InvalidIndexOffset , InvalidRequest , BAD_REQUEST ;
|
||||||
InvalidIndexPrimaryKey , InvalidRequest , BAD_REQUEST ;
|
InvalidIndexPrimaryKey , InvalidRequest , BAD_REQUEST ;
|
||||||
InvalidIndexUid , InvalidRequest , BAD_REQUEST ;
|
InvalidIndexUid , InvalidRequest , BAD_REQUEST ;
|
||||||
InvalidSearchAttributesToSearchOn , InvalidRequest , BAD_REQUEST ;
|
|
||||||
InvalidSearchAttributesToCrop , InvalidRequest , BAD_REQUEST ;
|
InvalidSearchAttributesToCrop , InvalidRequest , BAD_REQUEST ;
|
||||||
InvalidSearchAttributesToHighlight , InvalidRequest , BAD_REQUEST ;
|
InvalidSearchAttributesToHighlight , InvalidRequest , BAD_REQUEST ;
|
||||||
InvalidSearchAttributesToRetrieve , InvalidRequest , BAD_REQUEST ;
|
InvalidSearchAttributesToRetrieve , InvalidRequest , BAD_REQUEST ;
|
||||||
InvalidSearchCropLength , InvalidRequest , BAD_REQUEST ;
|
InvalidSearchCropLength , InvalidRequest , BAD_REQUEST ;
|
||||||
InvalidSearchCropMarker , InvalidRequest , BAD_REQUEST ;
|
InvalidSearchCropMarker , InvalidRequest , BAD_REQUEST ;
|
||||||
InvalidSearchFacets , InvalidRequest , BAD_REQUEST ;
|
InvalidSearchFacets , InvalidRequest , BAD_REQUEST ;
|
||||||
InvalidSearchSemanticRatio , InvalidRequest , BAD_REQUEST ;
|
|
||||||
InvalidFacetSearchFacetName , InvalidRequest , BAD_REQUEST ;
|
|
||||||
InvalidSearchFilter , InvalidRequest , BAD_REQUEST ;
|
InvalidSearchFilter , InvalidRequest , BAD_REQUEST ;
|
||||||
InvalidSearchHighlightPostTag , InvalidRequest , BAD_REQUEST ;
|
InvalidSearchHighlightPostTag , InvalidRequest , BAD_REQUEST ;
|
||||||
InvalidSearchHighlightPreTag , InvalidRequest , BAD_REQUEST ;
|
InvalidSearchHighlightPreTag , InvalidRequest , BAD_REQUEST ;
|
||||||
@ -254,28 +239,17 @@ InvalidSearchMatchingStrategy , InvalidRequest , BAD_REQUEST ;
|
|||||||
InvalidSearchOffset , InvalidRequest , BAD_REQUEST ;
|
InvalidSearchOffset , InvalidRequest , BAD_REQUEST ;
|
||||||
InvalidSearchPage , InvalidRequest , BAD_REQUEST ;
|
InvalidSearchPage , InvalidRequest , BAD_REQUEST ;
|
||||||
InvalidSearchQ , InvalidRequest , BAD_REQUEST ;
|
InvalidSearchQ , InvalidRequest , BAD_REQUEST ;
|
||||||
InvalidFacetSearchQuery , InvalidRequest , BAD_REQUEST ;
|
|
||||||
InvalidFacetSearchName , InvalidRequest , BAD_REQUEST ;
|
|
||||||
InvalidSearchVector , InvalidRequest , BAD_REQUEST ;
|
|
||||||
InvalidSearchShowMatchesPosition , InvalidRequest , BAD_REQUEST ;
|
InvalidSearchShowMatchesPosition , InvalidRequest , BAD_REQUEST ;
|
||||||
InvalidSearchShowRankingScore , InvalidRequest , BAD_REQUEST ;
|
|
||||||
InvalidSearchShowRankingScoreDetails , InvalidRequest , BAD_REQUEST ;
|
|
||||||
InvalidSearchSort , InvalidRequest , BAD_REQUEST ;
|
InvalidSearchSort , InvalidRequest , BAD_REQUEST ;
|
||||||
InvalidSettingsDisplayedAttributes , InvalidRequest , BAD_REQUEST ;
|
InvalidSettingsDisplayedAttributes , InvalidRequest , BAD_REQUEST ;
|
||||||
InvalidSettingsDistinctAttribute , InvalidRequest , BAD_REQUEST ;
|
InvalidSettingsDistinctAttribute , InvalidRequest , BAD_REQUEST ;
|
||||||
InvalidSettingsProximityPrecision , InvalidRequest , BAD_REQUEST ;
|
|
||||||
InvalidSettingsFaceting , InvalidRequest , BAD_REQUEST ;
|
InvalidSettingsFaceting , InvalidRequest , BAD_REQUEST ;
|
||||||
InvalidSettingsFilterableAttributes , InvalidRequest , BAD_REQUEST ;
|
InvalidSettingsFilterableAttributes , InvalidRequest , BAD_REQUEST ;
|
||||||
InvalidSettingsPagination , InvalidRequest , BAD_REQUEST ;
|
InvalidSettingsPagination , InvalidRequest , BAD_REQUEST ;
|
||||||
InvalidSettingsSearchCutoffMs , InvalidRequest , BAD_REQUEST ;
|
|
||||||
InvalidSettingsEmbedders , InvalidRequest , BAD_REQUEST ;
|
|
||||||
InvalidSettingsRankingRules , InvalidRequest , BAD_REQUEST ;
|
InvalidSettingsRankingRules , InvalidRequest , BAD_REQUEST ;
|
||||||
InvalidSettingsSearchableAttributes , InvalidRequest , BAD_REQUEST ;
|
InvalidSettingsSearchableAttributes , InvalidRequest , BAD_REQUEST ;
|
||||||
InvalidSettingsSortableAttributes , InvalidRequest , BAD_REQUEST ;
|
InvalidSettingsSortableAttributes , InvalidRequest , BAD_REQUEST ;
|
||||||
InvalidSettingsStopWords , InvalidRequest , BAD_REQUEST ;
|
InvalidSettingsStopWords , InvalidRequest , BAD_REQUEST ;
|
||||||
InvalidSettingsNonSeparatorTokens , InvalidRequest , BAD_REQUEST ;
|
|
||||||
InvalidSettingsSeparatorTokens , InvalidRequest , BAD_REQUEST ;
|
|
||||||
InvalidSettingsDictionary , InvalidRequest , BAD_REQUEST ;
|
|
||||||
InvalidSettingsSynonyms , InvalidRequest , BAD_REQUEST ;
|
InvalidSettingsSynonyms , InvalidRequest , BAD_REQUEST ;
|
||||||
InvalidSettingsTypoTolerance , InvalidRequest , BAD_REQUEST ;
|
InvalidSettingsTypoTolerance , InvalidRequest , BAD_REQUEST ;
|
||||||
InvalidState , Internal , INTERNAL_SERVER_ERROR ;
|
InvalidState , Internal , INTERNAL_SERVER_ERROR ;
|
||||||
@ -295,7 +269,6 @@ InvalidTaskStatuses , InvalidRequest , BAD_REQUEST ;
|
|||||||
InvalidTaskTypes , InvalidRequest , BAD_REQUEST ;
|
InvalidTaskTypes , InvalidRequest , BAD_REQUEST ;
|
||||||
InvalidTaskUids , InvalidRequest , BAD_REQUEST ;
|
InvalidTaskUids , InvalidRequest , BAD_REQUEST ;
|
||||||
IoError , System , UNPROCESSABLE_ENTITY;
|
IoError , System , UNPROCESSABLE_ENTITY;
|
||||||
FeatureNotEnabled , InvalidRequest , BAD_REQUEST ;
|
|
||||||
MalformedPayload , InvalidRequest , BAD_REQUEST ;
|
MalformedPayload , InvalidRequest , BAD_REQUEST ;
|
||||||
MaxFieldsLimitExceeded , InvalidRequest , BAD_REQUEST ;
|
MaxFieldsLimitExceeded , InvalidRequest , BAD_REQUEST ;
|
||||||
MissingApiKeyActions , InvalidRequest , BAD_REQUEST ;
|
MissingApiKeyActions , InvalidRequest , BAD_REQUEST ;
|
||||||
@ -304,25 +277,18 @@ MissingApiKeyIndexes , InvalidRequest , BAD_REQUEST ;
|
|||||||
MissingAuthorizationHeader , Auth , UNAUTHORIZED ;
|
MissingAuthorizationHeader , Auth , UNAUTHORIZED ;
|
||||||
MissingContentType , InvalidRequest , UNSUPPORTED_MEDIA_TYPE ;
|
MissingContentType , InvalidRequest , UNSUPPORTED_MEDIA_TYPE ;
|
||||||
MissingDocumentId , InvalidRequest , BAD_REQUEST ;
|
MissingDocumentId , InvalidRequest , BAD_REQUEST ;
|
||||||
MissingFacetSearchFacetName , InvalidRequest , BAD_REQUEST ;
|
|
||||||
MissingIndexUid , InvalidRequest , BAD_REQUEST ;
|
MissingIndexUid , InvalidRequest , BAD_REQUEST ;
|
||||||
MissingMasterKey , Auth , UNAUTHORIZED ;
|
MissingMasterKey , Auth , UNAUTHORIZED ;
|
||||||
MissingPayload , InvalidRequest , BAD_REQUEST ;
|
MissingPayload , InvalidRequest , BAD_REQUEST ;
|
||||||
MissingSearchHybrid , InvalidRequest , BAD_REQUEST ;
|
|
||||||
MissingSwapIndexes , InvalidRequest , BAD_REQUEST ;
|
MissingSwapIndexes , InvalidRequest , BAD_REQUEST ;
|
||||||
MissingTaskFilters , InvalidRequest , BAD_REQUEST ;
|
MissingTaskFilters , InvalidRequest , BAD_REQUEST ;
|
||||||
NoSpaceLeftOnDevice , System , UNPROCESSABLE_ENTITY;
|
NoSpaceLeftOnDevice , System , UNPROCESSABLE_ENTITY;
|
||||||
PayloadTooLarge , InvalidRequest , PAYLOAD_TOO_LARGE ;
|
PayloadTooLarge , InvalidRequest , PAYLOAD_TOO_LARGE ;
|
||||||
TooManySearchRequests , System , SERVICE_UNAVAILABLE ;
|
|
||||||
TaskNotFound , InvalidRequest , NOT_FOUND ;
|
TaskNotFound , InvalidRequest , NOT_FOUND ;
|
||||||
TooManyOpenFiles , System , UNPROCESSABLE_ENTITY ;
|
TooManyOpenFiles , System , UNPROCESSABLE_ENTITY ;
|
||||||
TooManyVectors , InvalidRequest , BAD_REQUEST ;
|
|
||||||
UnretrievableDocument , Internal , BAD_REQUEST ;
|
UnretrievableDocument , Internal , BAD_REQUEST ;
|
||||||
UnretrievableErrorCode , InvalidRequest , BAD_REQUEST ;
|
UnretrievableErrorCode , InvalidRequest , BAD_REQUEST ;
|
||||||
UnsupportedMediaType , InvalidRequest , UNSUPPORTED_MEDIA_TYPE ;
|
UnsupportedMediaType , InvalidRequest , UNSUPPORTED_MEDIA_TYPE
|
||||||
|
|
||||||
// Experimental features
|
|
||||||
VectorEmbeddingError , InvalidRequest , BAD_REQUEST
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ErrorCode for JoinError {
|
impl ErrorCode for JoinError {
|
||||||
@ -344,6 +310,7 @@ impl ErrorCode for milli::Error {
|
|||||||
UserError::SerdeJson(_)
|
UserError::SerdeJson(_)
|
||||||
| UserError::InvalidLmdbOpenOptions
|
| UserError::InvalidLmdbOpenOptions
|
||||||
| UserError::DocumentLimitReached
|
| UserError::DocumentLimitReached
|
||||||
|
| UserError::AccessingSoftDeletedDocument { .. }
|
||||||
| UserError::UnknownInternalDocumentId { .. } => Code::Internal,
|
| UserError::UnknownInternalDocumentId { .. } => Code::Internal,
|
||||||
UserError::InvalidStoreFile => Code::InvalidStoreFile,
|
UserError::InvalidStoreFile => Code::InvalidStoreFile,
|
||||||
UserError::NoSpaceLeftOnDevice => Code::NoSpaceLeftOnDevice,
|
UserError::NoSpaceLeftOnDevice => Code::NoSpaceLeftOnDevice,
|
||||||
@ -355,17 +322,6 @@ impl ErrorCode for milli::Error {
|
|||||||
UserError::InvalidDocumentId { .. } | UserError::TooManyDocumentIds { .. } => {
|
UserError::InvalidDocumentId { .. } | UserError::TooManyDocumentIds { .. } => {
|
||||||
Code::InvalidDocumentId
|
Code::InvalidDocumentId
|
||||||
}
|
}
|
||||||
UserError::MissingDocumentField(_) => Code::InvalidDocumentFields,
|
|
||||||
UserError::InvalidFieldForSource { .. }
|
|
||||||
| UserError::MissingFieldForSource { .. }
|
|
||||||
| UserError::InvalidOpenAiModel { .. }
|
|
||||||
| UserError::InvalidOpenAiModelDimensions { .. }
|
|
||||||
| UserError::InvalidOpenAiModelDimensionsMax { .. }
|
|
||||||
| UserError::InvalidSettingsDimensions { .. }
|
|
||||||
| UserError::InvalidUrl { .. }
|
|
||||||
| UserError::InvalidPrompt(_) => Code::InvalidSettingsEmbedders,
|
|
||||||
UserError::TooManyEmbedders(_) => Code::InvalidSettingsEmbedders,
|
|
||||||
UserError::InvalidPromptForEmbeddings(..) => Code::InvalidSettingsEmbedders,
|
|
||||||
UserError::NoPrimaryKeyCandidateFound => Code::IndexPrimaryKeyNoCandidateFound,
|
UserError::NoPrimaryKeyCandidateFound => Code::IndexPrimaryKeyNoCandidateFound,
|
||||||
UserError::MultiplePrimaryKeyCandidatesFound { .. } => {
|
UserError::MultiplePrimaryKeyCandidatesFound { .. } => {
|
||||||
Code::IndexPrimaryKeyMultipleCandidatesFound
|
Code::IndexPrimaryKeyMultipleCandidatesFound
|
||||||
@ -374,24 +330,12 @@ impl ErrorCode for milli::Error {
|
|||||||
UserError::SortRankingRuleMissing => Code::InvalidSearchSort,
|
UserError::SortRankingRuleMissing => Code::InvalidSearchSort,
|
||||||
UserError::InvalidFacetsDistribution { .. } => Code::InvalidSearchFacets,
|
UserError::InvalidFacetsDistribution { .. } => Code::InvalidSearchFacets,
|
||||||
UserError::InvalidSortableAttribute { .. } => Code::InvalidSearchSort,
|
UserError::InvalidSortableAttribute { .. } => Code::InvalidSearchSort,
|
||||||
UserError::InvalidSearchableAttribute { .. } => {
|
|
||||||
Code::InvalidSearchAttributesToSearchOn
|
|
||||||
}
|
|
||||||
UserError::InvalidFacetSearchFacetName { .. } => {
|
|
||||||
Code::InvalidFacetSearchFacetName
|
|
||||||
}
|
|
||||||
UserError::CriterionError(_) => Code::InvalidSettingsRankingRules,
|
UserError::CriterionError(_) => Code::InvalidSettingsRankingRules,
|
||||||
UserError::InvalidGeoField { .. } => Code::InvalidDocumentGeoField,
|
UserError::InvalidGeoField { .. } => Code::InvalidDocumentGeoField,
|
||||||
UserError::InvalidVectorDimensions { .. } => Code::InvalidVectorDimensions,
|
|
||||||
UserError::InvalidVectorsMapType { .. } => Code::InvalidVectorsType,
|
|
||||||
UserError::InvalidVectorsType { .. } => Code::InvalidVectorsType,
|
|
||||||
UserError::TooManyVectors(_, _) => Code::TooManyVectors,
|
|
||||||
UserError::SortError(_) => Code::InvalidSearchSort,
|
UserError::SortError(_) => Code::InvalidSearchSort,
|
||||||
UserError::InvalidMinTypoWordLenSetting(_, _) => {
|
UserError::InvalidMinTypoWordLenSetting(_, _) => {
|
||||||
Code::InvalidSettingsTypoTolerance
|
Code::InvalidSettingsTypoTolerance
|
||||||
}
|
}
|
||||||
UserError::InvalidEmbedder(_) => Code::InvalidEmbedder,
|
|
||||||
UserError::VectorEmbeddingError(_) => Code::VectorEmbeddingError,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -421,11 +365,11 @@ impl ErrorCode for HeedError {
|
|||||||
HeedError::Mdb(MdbError::Invalid) => Code::InvalidStoreFile,
|
HeedError::Mdb(MdbError::Invalid) => Code::InvalidStoreFile,
|
||||||
HeedError::Io(e) => e.error_code(),
|
HeedError::Io(e) => e.error_code(),
|
||||||
HeedError::Mdb(_)
|
HeedError::Mdb(_)
|
||||||
| HeedError::Encoding(_)
|
| HeedError::Encoding
|
||||||
| HeedError::Decoding(_)
|
| HeedError::Decoding
|
||||||
| HeedError::InvalidDatabaseTyping
|
| HeedError::InvalidDatabaseTyping
|
||||||
| HeedError::DatabaseClosing
|
| HeedError::DatabaseClosing
|
||||||
| HeedError::BadOpenOptions { .. } => Code::Internal,
|
| HeedError::BadOpenOptions => Code::Internal,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -479,15 +423,6 @@ impl fmt::Display for DeserrParseIntError {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl fmt::Display for deserr_codes::InvalidSearchSemanticRatio {
|
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
||||||
write!(
|
|
||||||
f,
|
|
||||||
"the value of `semanticRatio` is invalid, expected a float between `0.0` and `1.0`."
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[macro_export]
|
#[macro_export]
|
||||||
macro_rules! internal_error {
|
macro_rules! internal_error {
|
||||||
($target:ty : $($other:path), *) => {
|
($target:ty : $($other:path), *) => {
|
||||||
|
@ -1,33 +0,0 @@
|
|||||||
use deserr::Deserr;
|
|
||||||
use milli::OrderBy;
|
|
||||||
use serde::{Deserialize, Serialize};
|
|
||||||
|
|
||||||
#[derive(Debug, Default, Copy, Clone, PartialEq, Eq, Serialize, Deserialize, Deserr)]
|
|
||||||
#[serde(rename_all = "camelCase")]
|
|
||||||
#[deserr(rename_all = camelCase)]
|
|
||||||
pub enum FacetValuesSort {
|
|
||||||
/// Facet values are sorted in alphabetical order, ascending from A to Z.
|
|
||||||
#[default]
|
|
||||||
Alpha,
|
|
||||||
/// Facet values are sorted by decreasing count.
|
|
||||||
/// The count is the number of records containing this facet value in the results of the query.
|
|
||||||
Count,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<FacetValuesSort> for OrderBy {
|
|
||||||
fn from(val: FacetValuesSort) -> Self {
|
|
||||||
match val {
|
|
||||||
FacetValuesSort::Alpha => OrderBy::Lexicographic,
|
|
||||||
FacetValuesSort::Count => OrderBy::Count,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<OrderBy> for FacetValuesSort {
|
|
||||||
fn from(val: OrderBy) -> Self {
|
|
||||||
match val {
|
|
||||||
OrderBy::Lexicographic => FacetValuesSort::Alpha,
|
|
||||||
OrderBy::Count => FacetValuesSort::Count,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,16 +0,0 @@
|
|||||||
use serde::{Deserialize, Serialize};
|
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Debug, Clone, Copy, Default, PartialEq, Eq)]
|
|
||||||
#[serde(rename_all = "camelCase", default)]
|
|
||||||
pub struct RuntimeTogglableFeatures {
|
|
||||||
pub vector_store: bool,
|
|
||||||
pub metrics: bool,
|
|
||||||
pub logs_route: bool,
|
|
||||||
pub export_puffin_reports: bool,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Default, Debug, Clone, Copy)]
|
|
||||||
pub struct InstanceTogglableFeatures {
|
|
||||||
pub metrics: bool,
|
|
||||||
pub logs_route: bool,
|
|
||||||
}
|
|
@ -147,7 +147,9 @@ impl Key {
|
|||||||
fn parse_expiration_date(
|
fn parse_expiration_date(
|
||||||
string: Option<String>,
|
string: Option<String>,
|
||||||
) -> std::result::Result<Option<OffsetDateTime>, ParseOffsetDateTimeError> {
|
) -> std::result::Result<Option<OffsetDateTime>, ParseOffsetDateTimeError> {
|
||||||
let Some(string) = string else { return Ok(None) };
|
let Some(string) = string else {
|
||||||
|
return Ok(None)
|
||||||
|
};
|
||||||
let datetime = if let Ok(datetime) = OffsetDateTime::parse(&string, &Rfc3339) {
|
let datetime = if let Ok(datetime) = OffsetDateTime::parse(&string, &Rfc3339) {
|
||||||
datetime
|
datetime
|
||||||
} else if let Ok(primitive_datetime) = PrimitiveDateTime::parse(
|
} else if let Ok(primitive_datetime) = PrimitiveDateTime::parse(
|
||||||
@ -257,12 +259,6 @@ pub enum Action {
|
|||||||
#[serde(rename = "dumps.create")]
|
#[serde(rename = "dumps.create")]
|
||||||
#[deserr(rename = "dumps.create")]
|
#[deserr(rename = "dumps.create")]
|
||||||
DumpsCreate,
|
DumpsCreate,
|
||||||
#[serde(rename = "snapshots.*")]
|
|
||||||
#[deserr(rename = "snapshots.*")]
|
|
||||||
SnapshotsAll,
|
|
||||||
#[serde(rename = "snapshots.create")]
|
|
||||||
#[deserr(rename = "snapshots.create")]
|
|
||||||
SnapshotsCreate,
|
|
||||||
#[serde(rename = "version")]
|
#[serde(rename = "version")]
|
||||||
#[deserr(rename = "version")]
|
#[deserr(rename = "version")]
|
||||||
Version,
|
Version,
|
||||||
@ -278,12 +274,6 @@ pub enum Action {
|
|||||||
#[serde(rename = "keys.delete")]
|
#[serde(rename = "keys.delete")]
|
||||||
#[deserr(rename = "keys.delete")]
|
#[deserr(rename = "keys.delete")]
|
||||||
KeysDelete,
|
KeysDelete,
|
||||||
#[serde(rename = "experimental.get")]
|
|
||||||
#[deserr(rename = "experimental.get")]
|
|
||||||
ExperimentalFeaturesGet,
|
|
||||||
#[serde(rename = "experimental.update")]
|
|
||||||
#[deserr(rename = "experimental.update")]
|
|
||||||
ExperimentalFeaturesUpdate,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Action {
|
impl Action {
|
||||||
@ -315,14 +305,11 @@ impl Action {
|
|||||||
METRICS_GET => Some(Self::MetricsGet),
|
METRICS_GET => Some(Self::MetricsGet),
|
||||||
DUMPS_ALL => Some(Self::DumpsAll),
|
DUMPS_ALL => Some(Self::DumpsAll),
|
||||||
DUMPS_CREATE => Some(Self::DumpsCreate),
|
DUMPS_CREATE => Some(Self::DumpsCreate),
|
||||||
SNAPSHOTS_CREATE => Some(Self::SnapshotsCreate),
|
|
||||||
VERSION => Some(Self::Version),
|
VERSION => Some(Self::Version),
|
||||||
KEYS_CREATE => Some(Self::KeysAdd),
|
KEYS_CREATE => Some(Self::KeysAdd),
|
||||||
KEYS_GET => Some(Self::KeysGet),
|
KEYS_GET => Some(Self::KeysGet),
|
||||||
KEYS_UPDATE => Some(Self::KeysUpdate),
|
KEYS_UPDATE => Some(Self::KeysUpdate),
|
||||||
KEYS_DELETE => Some(Self::KeysDelete),
|
KEYS_DELETE => Some(Self::KeysDelete),
|
||||||
EXPERIMENTAL_FEATURES_GET => Some(Self::ExperimentalFeaturesGet),
|
|
||||||
EXPERIMENTAL_FEATURES_UPDATE => Some(Self::ExperimentalFeaturesUpdate),
|
|
||||||
_otherwise => None,
|
_otherwise => None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -360,12 +347,9 @@ pub mod actions {
|
|||||||
pub const METRICS_GET: u8 = MetricsGet.repr();
|
pub const METRICS_GET: u8 = MetricsGet.repr();
|
||||||
pub const DUMPS_ALL: u8 = DumpsAll.repr();
|
pub const DUMPS_ALL: u8 = DumpsAll.repr();
|
||||||
pub const DUMPS_CREATE: u8 = DumpsCreate.repr();
|
pub const DUMPS_CREATE: u8 = DumpsCreate.repr();
|
||||||
pub const SNAPSHOTS_CREATE: u8 = SnapshotsCreate.repr();
|
|
||||||
pub const VERSION: u8 = Version.repr();
|
pub const VERSION: u8 = Version.repr();
|
||||||
pub const KEYS_CREATE: u8 = KeysAdd.repr();
|
pub const KEYS_CREATE: u8 = KeysAdd.repr();
|
||||||
pub const KEYS_GET: u8 = KeysGet.repr();
|
pub const KEYS_GET: u8 = KeysGet.repr();
|
||||||
pub const KEYS_UPDATE: u8 = KeysUpdate.repr();
|
pub const KEYS_UPDATE: u8 = KeysUpdate.repr();
|
||||||
pub const KEYS_DELETE: u8 = KeysDelete.repr();
|
pub const KEYS_DELETE: u8 = KeysDelete.repr();
|
||||||
pub const EXPERIMENTAL_FEATURES_GET: u8 = ExperimentalFeaturesGet.repr();
|
|
||||||
pub const EXPERIMENTAL_FEATURES_UPDATE: u8 = ExperimentalFeaturesUpdate.repr();
|
|
||||||
}
|
}
|
||||||
|
@ -2,14 +2,11 @@ pub mod compression;
|
|||||||
pub mod deserr;
|
pub mod deserr;
|
||||||
pub mod document_formats;
|
pub mod document_formats;
|
||||||
pub mod error;
|
pub mod error;
|
||||||
pub mod facet_values_sort;
|
|
||||||
pub mod features;
|
|
||||||
pub mod index_uid;
|
pub mod index_uid;
|
||||||
pub mod index_uid_pattern;
|
pub mod index_uid_pattern;
|
||||||
pub mod keys;
|
pub mod keys;
|
||||||
pub mod settings;
|
pub mod settings;
|
||||||
pub mod star_or;
|
pub mod star_or;
|
||||||
pub mod task_view;
|
|
||||||
pub mod tasks;
|
pub mod tasks;
|
||||||
pub mod versioning;
|
pub mod versioning;
|
||||||
pub use milli::{heed, Index};
|
pub use milli::{heed, Index};
|
||||||
|
@ -3,21 +3,19 @@ use std::convert::Infallible;
|
|||||||
use std::fmt;
|
use std::fmt;
|
||||||
use std::marker::PhantomData;
|
use std::marker::PhantomData;
|
||||||
use std::num::NonZeroUsize;
|
use std::num::NonZeroUsize;
|
||||||
use std::ops::{ControlFlow, Deref};
|
use std::ops::ControlFlow;
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
|
|
||||||
use deserr::{DeserializeError, Deserr, ErrorKind, MergeWithError, ValuePointerRef};
|
use deserr::{DeserializeError, Deserr, ErrorKind, MergeWithError, ValuePointerRef};
|
||||||
use fst::IntoStreamer;
|
use fst::IntoStreamer;
|
||||||
use milli::proximity::ProximityPrecision;
|
|
||||||
use milli::update::Setting;
|
use milli::update::Setting;
|
||||||
use milli::{Criterion, CriterionError, Index, DEFAULT_VALUES_PER_FACET};
|
use milli::{Criterion, CriterionError, Index, DEFAULT_VALUES_PER_FACET};
|
||||||
use serde::{Deserialize, Serialize, Serializer};
|
use serde::{Deserialize, Serialize, Serializer};
|
||||||
|
|
||||||
use crate::deserr::DeserrJsonError;
|
use crate::deserr::DeserrJsonError;
|
||||||
use crate::error::deserr_codes::*;
|
use crate::error::deserr_codes::*;
|
||||||
use crate::facet_values_sort::FacetValuesSort;
|
|
||||||
|
|
||||||
/// The maximum number of results that the engine
|
/// The maximimum number of results that the engine
|
||||||
/// will be able to return in one search call.
|
/// will be able to return in one search call.
|
||||||
pub const DEFAULT_PAGINATION_MAX_TOTAL_HITS: usize = 1000;
|
pub const DEFAULT_PAGINATION_MAX_TOTAL_HITS: usize = 1000;
|
||||||
|
|
||||||
@ -104,9 +102,6 @@ pub struct FacetingSettings {
|
|||||||
#[serde(default, skip_serializing_if = "Setting::is_not_set")]
|
#[serde(default, skip_serializing_if = "Setting::is_not_set")]
|
||||||
#[deserr(default)]
|
#[deserr(default)]
|
||||||
pub max_values_per_facet: Setting<usize>,
|
pub max_values_per_facet: Setting<usize>,
|
||||||
#[serde(default, skip_serializing_if = "Setting::is_not_set")]
|
|
||||||
#[deserr(default)]
|
|
||||||
pub sort_facet_values_by: Setting<BTreeMap<String, FacetValuesSort>>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq, Eq, Deserr)]
|
#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq, Eq, Deserr)]
|
||||||
@ -143,13 +138,21 @@ impl MergeWithError<milli::CriterionError> for DeserrJsonError<InvalidSettingsRa
|
|||||||
)]
|
)]
|
||||||
#[deserr(error = DeserrJsonError, rename_all = camelCase, deny_unknown_fields)]
|
#[deserr(error = DeserrJsonError, rename_all = camelCase, deny_unknown_fields)]
|
||||||
pub struct Settings<T> {
|
pub struct Settings<T> {
|
||||||
#[serde(default, skip_serializing_if = "Setting::is_not_set")]
|
#[serde(
|
||||||
|
default,
|
||||||
|
serialize_with = "serialize_with_wildcard",
|
||||||
|
skip_serializing_if = "Setting::is_not_set"
|
||||||
|
)]
|
||||||
#[deserr(default, error = DeserrJsonError<InvalidSettingsDisplayedAttributes>)]
|
#[deserr(default, error = DeserrJsonError<InvalidSettingsDisplayedAttributes>)]
|
||||||
pub displayed_attributes: WildcardSetting,
|
pub displayed_attributes: Setting<Vec<String>>,
|
||||||
|
|
||||||
#[serde(default, skip_serializing_if = "Setting::is_not_set")]
|
#[serde(
|
||||||
|
default,
|
||||||
|
serialize_with = "serialize_with_wildcard",
|
||||||
|
skip_serializing_if = "Setting::is_not_set"
|
||||||
|
)]
|
||||||
#[deserr(default, error = DeserrJsonError<InvalidSettingsSearchableAttributes>)]
|
#[deserr(default, error = DeserrJsonError<InvalidSettingsSearchableAttributes>)]
|
||||||
pub searchable_attributes: WildcardSetting,
|
pub searchable_attributes: Setting<Vec<String>>,
|
||||||
|
|
||||||
#[serde(default, skip_serializing_if = "Setting::is_not_set")]
|
#[serde(default, skip_serializing_if = "Setting::is_not_set")]
|
||||||
#[deserr(default, error = DeserrJsonError<InvalidSettingsFilterableAttributes>)]
|
#[deserr(default, error = DeserrJsonError<InvalidSettingsFilterableAttributes>)]
|
||||||
@ -164,24 +167,12 @@ pub struct Settings<T> {
|
|||||||
#[deserr(default, error = DeserrJsonError<InvalidSettingsStopWords>)]
|
#[deserr(default, error = DeserrJsonError<InvalidSettingsStopWords>)]
|
||||||
pub stop_words: Setting<BTreeSet<String>>,
|
pub stop_words: Setting<BTreeSet<String>>,
|
||||||
#[serde(default, skip_serializing_if = "Setting::is_not_set")]
|
#[serde(default, skip_serializing_if = "Setting::is_not_set")]
|
||||||
#[deserr(default, error = DeserrJsonError<InvalidSettingsNonSeparatorTokens>)]
|
|
||||||
pub non_separator_tokens: Setting<BTreeSet<String>>,
|
|
||||||
#[serde(default, skip_serializing_if = "Setting::is_not_set")]
|
|
||||||
#[deserr(default, error = DeserrJsonError<InvalidSettingsSeparatorTokens>)]
|
|
||||||
pub separator_tokens: Setting<BTreeSet<String>>,
|
|
||||||
#[serde(default, skip_serializing_if = "Setting::is_not_set")]
|
|
||||||
#[deserr(default, error = DeserrJsonError<InvalidSettingsDictionary>)]
|
|
||||||
pub dictionary: Setting<BTreeSet<String>>,
|
|
||||||
#[serde(default, skip_serializing_if = "Setting::is_not_set")]
|
|
||||||
#[deserr(default, error = DeserrJsonError<InvalidSettingsSynonyms>)]
|
#[deserr(default, error = DeserrJsonError<InvalidSettingsSynonyms>)]
|
||||||
pub synonyms: Setting<BTreeMap<String, Vec<String>>>,
|
pub synonyms: Setting<BTreeMap<String, Vec<String>>>,
|
||||||
#[serde(default, skip_serializing_if = "Setting::is_not_set")]
|
#[serde(default, skip_serializing_if = "Setting::is_not_set")]
|
||||||
#[deserr(default, error = DeserrJsonError<InvalidSettingsDistinctAttribute>)]
|
#[deserr(default, error = DeserrJsonError<InvalidSettingsDistinctAttribute>)]
|
||||||
pub distinct_attribute: Setting<String>,
|
pub distinct_attribute: Setting<String>,
|
||||||
#[serde(default, skip_serializing_if = "Setting::is_not_set")]
|
#[serde(default, skip_serializing_if = "Setting::is_not_set")]
|
||||||
#[deserr(default, error = DeserrJsonError<InvalidSettingsProximityPrecision>)]
|
|
||||||
pub proximity_precision: Setting<ProximityPrecisionView>,
|
|
||||||
#[serde(default, skip_serializing_if = "Setting::is_not_set")]
|
|
||||||
#[deserr(default, error = DeserrJsonError<InvalidSettingsTypoTolerance>)]
|
#[deserr(default, error = DeserrJsonError<InvalidSettingsTypoTolerance>)]
|
||||||
pub typo_tolerance: Setting<TypoSettings>,
|
pub typo_tolerance: Setting<TypoSettings>,
|
||||||
#[serde(default, skip_serializing_if = "Setting::is_not_set")]
|
#[serde(default, skip_serializing_if = "Setting::is_not_set")]
|
||||||
@ -191,75 +182,25 @@ pub struct Settings<T> {
|
|||||||
#[deserr(default, error = DeserrJsonError<InvalidSettingsPagination>)]
|
#[deserr(default, error = DeserrJsonError<InvalidSettingsPagination>)]
|
||||||
pub pagination: Setting<PaginationSettings>,
|
pub pagination: Setting<PaginationSettings>,
|
||||||
|
|
||||||
#[serde(default, skip_serializing_if = "Setting::is_not_set")]
|
|
||||||
#[deserr(default, error = DeserrJsonError<InvalidSettingsEmbedders>)]
|
|
||||||
pub embedders: Setting<BTreeMap<String, Setting<milli::vector::settings::EmbeddingSettings>>>,
|
|
||||||
#[serde(default, skip_serializing_if = "Setting::is_not_set")]
|
|
||||||
#[deserr(default, error = DeserrJsonError<InvalidSettingsSearchCutoffMs>)]
|
|
||||||
pub search_cutoff_ms: Setting<u64>,
|
|
||||||
|
|
||||||
#[serde(skip)]
|
#[serde(skip)]
|
||||||
#[deserr(skip)]
|
#[deserr(skip)]
|
||||||
pub _kind: PhantomData<T>,
|
pub _kind: PhantomData<T>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T> Settings<T> {
|
|
||||||
pub fn hide_secrets(&mut self) {
|
|
||||||
let Setting::Set(embedders) = &mut self.embedders else {
|
|
||||||
return;
|
|
||||||
};
|
|
||||||
|
|
||||||
for mut embedder in embedders.values_mut() {
|
|
||||||
let Setting::Set(embedder) = &mut embedder else {
|
|
||||||
continue;
|
|
||||||
};
|
|
||||||
|
|
||||||
let Setting::Set(api_key) = &mut embedder.api_key else {
|
|
||||||
continue;
|
|
||||||
};
|
|
||||||
|
|
||||||
Self::hide_secret(api_key);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn hide_secret(secret: &mut String) {
|
|
||||||
match secret.len() {
|
|
||||||
x if x < 10 => {
|
|
||||||
secret.replace_range(.., "XXX...");
|
|
||||||
}
|
|
||||||
x if x < 20 => {
|
|
||||||
secret.replace_range(2.., "XXXX...");
|
|
||||||
}
|
|
||||||
x if x < 30 => {
|
|
||||||
secret.replace_range(3.., "XXXXX...");
|
|
||||||
}
|
|
||||||
_x => {
|
|
||||||
secret.replace_range(5.., "XXXXXX...");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Settings<Checked> {
|
impl Settings<Checked> {
|
||||||
pub fn cleared() -> Settings<Checked> {
|
pub fn cleared() -> Settings<Checked> {
|
||||||
Settings {
|
Settings {
|
||||||
displayed_attributes: Setting::Reset.into(),
|
displayed_attributes: Setting::Reset,
|
||||||
searchable_attributes: Setting::Reset.into(),
|
searchable_attributes: Setting::Reset,
|
||||||
filterable_attributes: Setting::Reset,
|
filterable_attributes: Setting::Reset,
|
||||||
sortable_attributes: Setting::Reset,
|
sortable_attributes: Setting::Reset,
|
||||||
ranking_rules: Setting::Reset,
|
ranking_rules: Setting::Reset,
|
||||||
stop_words: Setting::Reset,
|
stop_words: Setting::Reset,
|
||||||
synonyms: Setting::Reset,
|
synonyms: Setting::Reset,
|
||||||
non_separator_tokens: Setting::Reset,
|
|
||||||
separator_tokens: Setting::Reset,
|
|
||||||
dictionary: Setting::Reset,
|
|
||||||
distinct_attribute: Setting::Reset,
|
distinct_attribute: Setting::Reset,
|
||||||
proximity_precision: Setting::Reset,
|
|
||||||
typo_tolerance: Setting::Reset,
|
typo_tolerance: Setting::Reset,
|
||||||
faceting: Setting::Reset,
|
faceting: Setting::Reset,
|
||||||
pagination: Setting::Reset,
|
pagination: Setting::Reset,
|
||||||
embedders: Setting::Reset,
|
|
||||||
search_cutoff_ms: Setting::Reset,
|
|
||||||
_kind: PhantomData,
|
_kind: PhantomData,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -272,17 +213,11 @@ impl Settings<Checked> {
|
|||||||
sortable_attributes,
|
sortable_attributes,
|
||||||
ranking_rules,
|
ranking_rules,
|
||||||
stop_words,
|
stop_words,
|
||||||
non_separator_tokens,
|
|
||||||
separator_tokens,
|
|
||||||
dictionary,
|
|
||||||
synonyms,
|
synonyms,
|
||||||
distinct_attribute,
|
distinct_attribute,
|
||||||
proximity_precision,
|
|
||||||
typo_tolerance,
|
typo_tolerance,
|
||||||
faceting,
|
faceting,
|
||||||
pagination,
|
pagination,
|
||||||
embedders,
|
|
||||||
search_cutoff_ms,
|
|
||||||
..
|
..
|
||||||
} = self;
|
} = self;
|
||||||
|
|
||||||
@ -293,17 +228,11 @@ impl Settings<Checked> {
|
|||||||
sortable_attributes,
|
sortable_attributes,
|
||||||
ranking_rules,
|
ranking_rules,
|
||||||
stop_words,
|
stop_words,
|
||||||
non_separator_tokens,
|
|
||||||
separator_tokens,
|
|
||||||
dictionary,
|
|
||||||
synonyms,
|
synonyms,
|
||||||
distinct_attribute,
|
distinct_attribute,
|
||||||
proximity_precision,
|
|
||||||
typo_tolerance,
|
typo_tolerance,
|
||||||
faceting,
|
faceting,
|
||||||
pagination,
|
pagination,
|
||||||
embedders,
|
|
||||||
search_cutoff_ms,
|
|
||||||
_kind: PhantomData,
|
_kind: PhantomData,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -311,7 +240,7 @@ impl Settings<Checked> {
|
|||||||
|
|
||||||
impl Settings<Unchecked> {
|
impl Settings<Unchecked> {
|
||||||
pub fn check(self) -> Settings<Checked> {
|
pub fn check(self) -> Settings<Checked> {
|
||||||
let displayed_attributes = match self.displayed_attributes.0 {
|
let displayed_attributes = match self.displayed_attributes {
|
||||||
Setting::Set(fields) => {
|
Setting::Set(fields) => {
|
||||||
if fields.iter().any(|f| f == "*") {
|
if fields.iter().any(|f| f == "*") {
|
||||||
Setting::Reset
|
Setting::Reset
|
||||||
@ -322,7 +251,7 @@ impl Settings<Unchecked> {
|
|||||||
otherwise => otherwise,
|
otherwise => otherwise,
|
||||||
};
|
};
|
||||||
|
|
||||||
let searchable_attributes = match self.searchable_attributes.0 {
|
let searchable_attributes = match self.searchable_attributes {
|
||||||
Setting::Set(fields) => {
|
Setting::Set(fields) => {
|
||||||
if fields.iter().any(|f| f == "*") {
|
if fields.iter().any(|f| f == "*") {
|
||||||
Setting::Reset
|
Setting::Reset
|
||||||
@ -334,41 +263,20 @@ impl Settings<Unchecked> {
|
|||||||
};
|
};
|
||||||
|
|
||||||
Settings {
|
Settings {
|
||||||
displayed_attributes: displayed_attributes.into(),
|
displayed_attributes,
|
||||||
searchable_attributes: searchable_attributes.into(),
|
searchable_attributes,
|
||||||
filterable_attributes: self.filterable_attributes,
|
filterable_attributes: self.filterable_attributes,
|
||||||
sortable_attributes: self.sortable_attributes,
|
sortable_attributes: self.sortable_attributes,
|
||||||
ranking_rules: self.ranking_rules,
|
ranking_rules: self.ranking_rules,
|
||||||
stop_words: self.stop_words,
|
stop_words: self.stop_words,
|
||||||
synonyms: self.synonyms,
|
synonyms: self.synonyms,
|
||||||
non_separator_tokens: self.non_separator_tokens,
|
|
||||||
separator_tokens: self.separator_tokens,
|
|
||||||
dictionary: self.dictionary,
|
|
||||||
distinct_attribute: self.distinct_attribute,
|
distinct_attribute: self.distinct_attribute,
|
||||||
proximity_precision: self.proximity_precision,
|
|
||||||
typo_tolerance: self.typo_tolerance,
|
typo_tolerance: self.typo_tolerance,
|
||||||
faceting: self.faceting,
|
faceting: self.faceting,
|
||||||
pagination: self.pagination,
|
pagination: self.pagination,
|
||||||
embedders: self.embedders,
|
|
||||||
search_cutoff_ms: self.search_cutoff_ms,
|
|
||||||
_kind: PhantomData,
|
_kind: PhantomData,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn validate(self) -> Result<Self, milli::Error> {
|
|
||||||
self.validate_embedding_settings()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn validate_embedding_settings(mut self) -> Result<Self, milli::Error> {
|
|
||||||
let Setting::Set(mut configs) = self.embedders else { return Ok(self) };
|
|
||||||
for (name, config) in configs.iter_mut() {
|
|
||||||
let config_to_check = std::mem::take(config);
|
|
||||||
let checked_config = milli::update::validate_embedding_settings(config_to_check, name)?;
|
|
||||||
*config = checked_config
|
|
||||||
}
|
|
||||||
self.embedders = Setting::Set(configs);
|
|
||||||
Ok(self)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
@ -383,40 +291,19 @@ pub fn apply_settings_to_builder(
|
|||||||
settings: &Settings<Checked>,
|
settings: &Settings<Checked>,
|
||||||
builder: &mut milli::update::Settings,
|
builder: &mut milli::update::Settings,
|
||||||
) {
|
) {
|
||||||
let Settings {
|
match settings.searchable_attributes {
|
||||||
displayed_attributes,
|
|
||||||
searchable_attributes,
|
|
||||||
filterable_attributes,
|
|
||||||
sortable_attributes,
|
|
||||||
ranking_rules,
|
|
||||||
stop_words,
|
|
||||||
non_separator_tokens,
|
|
||||||
separator_tokens,
|
|
||||||
dictionary,
|
|
||||||
synonyms,
|
|
||||||
distinct_attribute,
|
|
||||||
proximity_precision,
|
|
||||||
typo_tolerance,
|
|
||||||
faceting,
|
|
||||||
pagination,
|
|
||||||
embedders,
|
|
||||||
search_cutoff_ms,
|
|
||||||
_kind,
|
|
||||||
} = settings;
|
|
||||||
|
|
||||||
match searchable_attributes.deref() {
|
|
||||||
Setting::Set(ref names) => builder.set_searchable_fields(names.clone()),
|
Setting::Set(ref names) => builder.set_searchable_fields(names.clone()),
|
||||||
Setting::Reset => builder.reset_searchable_fields(),
|
Setting::Reset => builder.reset_searchable_fields(),
|
||||||
Setting::NotSet => (),
|
Setting::NotSet => (),
|
||||||
}
|
}
|
||||||
|
|
||||||
match displayed_attributes.deref() {
|
match settings.displayed_attributes {
|
||||||
Setting::Set(ref names) => builder.set_displayed_fields(names.clone()),
|
Setting::Set(ref names) => builder.set_displayed_fields(names.clone()),
|
||||||
Setting::Reset => builder.reset_displayed_fields(),
|
Setting::Reset => builder.reset_displayed_fields(),
|
||||||
Setting::NotSet => (),
|
Setting::NotSet => (),
|
||||||
}
|
}
|
||||||
|
|
||||||
match filterable_attributes {
|
match settings.filterable_attributes {
|
||||||
Setting::Set(ref facets) => {
|
Setting::Set(ref facets) => {
|
||||||
builder.set_filterable_fields(facets.clone().into_iter().collect())
|
builder.set_filterable_fields(facets.clone().into_iter().collect())
|
||||||
}
|
}
|
||||||
@ -424,13 +311,13 @@ pub fn apply_settings_to_builder(
|
|||||||
Setting::NotSet => (),
|
Setting::NotSet => (),
|
||||||
}
|
}
|
||||||
|
|
||||||
match sortable_attributes {
|
match settings.sortable_attributes {
|
||||||
Setting::Set(ref fields) => builder.set_sortable_fields(fields.iter().cloned().collect()),
|
Setting::Set(ref fields) => builder.set_sortable_fields(fields.iter().cloned().collect()),
|
||||||
Setting::Reset => builder.reset_sortable_fields(),
|
Setting::Reset => builder.reset_sortable_fields(),
|
||||||
Setting::NotSet => (),
|
Setting::NotSet => (),
|
||||||
}
|
}
|
||||||
|
|
||||||
match ranking_rules {
|
match settings.ranking_rules {
|
||||||
Setting::Set(ref criteria) => {
|
Setting::Set(ref criteria) => {
|
||||||
builder.set_criteria(criteria.iter().map(|c| c.clone().into()).collect())
|
builder.set_criteria(criteria.iter().map(|c| c.clone().into()).collect())
|
||||||
}
|
}
|
||||||
@ -438,53 +325,25 @@ pub fn apply_settings_to_builder(
|
|||||||
Setting::NotSet => (),
|
Setting::NotSet => (),
|
||||||
}
|
}
|
||||||
|
|
||||||
match stop_words {
|
match settings.stop_words {
|
||||||
Setting::Set(ref stop_words) => builder.set_stop_words(stop_words.clone()),
|
Setting::Set(ref stop_words) => builder.set_stop_words(stop_words.clone()),
|
||||||
Setting::Reset => builder.reset_stop_words(),
|
Setting::Reset => builder.reset_stop_words(),
|
||||||
Setting::NotSet => (),
|
Setting::NotSet => (),
|
||||||
}
|
}
|
||||||
|
|
||||||
match non_separator_tokens {
|
match settings.synonyms {
|
||||||
Setting::Set(ref non_separator_tokens) => {
|
|
||||||
builder.set_non_separator_tokens(non_separator_tokens.clone())
|
|
||||||
}
|
|
||||||
Setting::Reset => builder.reset_non_separator_tokens(),
|
|
||||||
Setting::NotSet => (),
|
|
||||||
}
|
|
||||||
|
|
||||||
match separator_tokens {
|
|
||||||
Setting::Set(ref separator_tokens) => {
|
|
||||||
builder.set_separator_tokens(separator_tokens.clone())
|
|
||||||
}
|
|
||||||
Setting::Reset => builder.reset_separator_tokens(),
|
|
||||||
Setting::NotSet => (),
|
|
||||||
}
|
|
||||||
|
|
||||||
match dictionary {
|
|
||||||
Setting::Set(ref dictionary) => builder.set_dictionary(dictionary.clone()),
|
|
||||||
Setting::Reset => builder.reset_dictionary(),
|
|
||||||
Setting::NotSet => (),
|
|
||||||
}
|
|
||||||
|
|
||||||
match synonyms {
|
|
||||||
Setting::Set(ref synonyms) => builder.set_synonyms(synonyms.clone().into_iter().collect()),
|
Setting::Set(ref synonyms) => builder.set_synonyms(synonyms.clone().into_iter().collect()),
|
||||||
Setting::Reset => builder.reset_synonyms(),
|
Setting::Reset => builder.reset_synonyms(),
|
||||||
Setting::NotSet => (),
|
Setting::NotSet => (),
|
||||||
}
|
}
|
||||||
|
|
||||||
match distinct_attribute {
|
match settings.distinct_attribute {
|
||||||
Setting::Set(ref attr) => builder.set_distinct_field(attr.clone()),
|
Setting::Set(ref attr) => builder.set_distinct_field(attr.clone()),
|
||||||
Setting::Reset => builder.reset_distinct_field(),
|
Setting::Reset => builder.reset_distinct_field(),
|
||||||
Setting::NotSet => (),
|
Setting::NotSet => (),
|
||||||
}
|
}
|
||||||
|
|
||||||
match proximity_precision {
|
match settings.typo_tolerance {
|
||||||
Setting::Set(ref precision) => builder.set_proximity_precision((*precision).into()),
|
|
||||||
Setting::Reset => builder.reset_proximity_precision(),
|
|
||||||
Setting::NotSet => (),
|
|
||||||
}
|
|
||||||
|
|
||||||
match typo_tolerance {
|
|
||||||
Setting::Set(ref value) => {
|
Setting::Set(ref value) => {
|
||||||
match value.enabled {
|
match value.enabled {
|
||||||
Setting::Set(val) => builder.set_autorize_typos(val),
|
Setting::Set(val) => builder.set_autorize_typos(val),
|
||||||
@ -539,29 +398,17 @@ pub fn apply_settings_to_builder(
|
|||||||
Setting::NotSet => (),
|
Setting::NotSet => (),
|
||||||
}
|
}
|
||||||
|
|
||||||
match faceting {
|
match settings.faceting {
|
||||||
Setting::Set(FacetingSettings { max_values_per_facet, sort_facet_values_by }) => {
|
Setting::Set(ref value) => match value.max_values_per_facet {
|
||||||
match max_values_per_facet {
|
Setting::Set(val) => builder.set_max_values_per_facet(val),
|
||||||
Setting::Set(val) => builder.set_max_values_per_facet(*val),
|
Setting::Reset => builder.reset_max_values_per_facet(),
|
||||||
|
Setting::NotSet => (),
|
||||||
|
},
|
||||||
Setting::Reset => builder.reset_max_values_per_facet(),
|
Setting::Reset => builder.reset_max_values_per_facet(),
|
||||||
Setting::NotSet => (),
|
Setting::NotSet => (),
|
||||||
}
|
}
|
||||||
match sort_facet_values_by {
|
|
||||||
Setting::Set(val) => builder.set_sort_facet_values_by(
|
|
||||||
val.iter().map(|(name, order)| (name.clone(), (*order).into())).collect(),
|
|
||||||
),
|
|
||||||
Setting::Reset => builder.reset_sort_facet_values_by(),
|
|
||||||
Setting::NotSet => (),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Setting::Reset => {
|
|
||||||
builder.reset_max_values_per_facet();
|
|
||||||
builder.reset_sort_facet_values_by();
|
|
||||||
}
|
|
||||||
Setting::NotSet => (),
|
|
||||||
}
|
|
||||||
|
|
||||||
match pagination {
|
match settings.pagination {
|
||||||
Setting::Set(ref value) => match value.max_total_hits {
|
Setting::Set(ref value) => match value.max_total_hits {
|
||||||
Setting::Set(val) => builder.set_pagination_max_total_hits(val),
|
Setting::Set(val) => builder.set_pagination_max_total_hits(val),
|
||||||
Setting::Reset => builder.reset_pagination_max_total_hits(),
|
Setting::Reset => builder.reset_pagination_max_total_hits(),
|
||||||
@ -570,29 +417,11 @@ pub fn apply_settings_to_builder(
|
|||||||
Setting::Reset => builder.reset_pagination_max_total_hits(),
|
Setting::Reset => builder.reset_pagination_max_total_hits(),
|
||||||
Setting::NotSet => (),
|
Setting::NotSet => (),
|
||||||
}
|
}
|
||||||
|
|
||||||
match embedders {
|
|
||||||
Setting::Set(value) => builder.set_embedder_settings(value.clone()),
|
|
||||||
Setting::Reset => builder.reset_embedder_settings(),
|
|
||||||
Setting::NotSet => (),
|
|
||||||
}
|
|
||||||
|
|
||||||
match search_cutoff_ms {
|
|
||||||
Setting::Set(cutoff) => builder.set_search_cutoff(*cutoff),
|
|
||||||
Setting::Reset => builder.reset_search_cutoff(),
|
|
||||||
Setting::NotSet => (),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub enum SecretPolicy {
|
|
||||||
RevealSecrets,
|
|
||||||
HideSecrets,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn settings(
|
pub fn settings(
|
||||||
index: &Index,
|
index: &Index,
|
||||||
rtxn: &crate::heed::RoTxn,
|
rtxn: &crate::heed::RoTxn,
|
||||||
secret_policy: SecretPolicy,
|
|
||||||
) -> Result<Settings<Checked>, milli::Error> {
|
) -> Result<Settings<Checked>, milli::Error> {
|
||||||
let displayed_attributes =
|
let displayed_attributes =
|
||||||
index.displayed_fields(rtxn)?.map(|fields| fields.into_iter().map(String::from).collect());
|
index.displayed_fields(rtxn)?.map(|fields| fields.into_iter().map(String::from).collect());
|
||||||
@ -614,16 +443,15 @@ pub fn settings(
|
|||||||
})
|
})
|
||||||
.transpose()?
|
.transpose()?
|
||||||
.unwrap_or_default();
|
.unwrap_or_default();
|
||||||
|
|
||||||
let non_separator_tokens = index.non_separator_tokens(rtxn)?.unwrap_or_default();
|
|
||||||
let separator_tokens = index.separator_tokens(rtxn)?.unwrap_or_default();
|
|
||||||
let dictionary = index.dictionary(rtxn)?.unwrap_or_default();
|
|
||||||
|
|
||||||
let distinct_field = index.distinct_field(rtxn)?.map(String::from);
|
let distinct_field = index.distinct_field(rtxn)?.map(String::from);
|
||||||
|
|
||||||
let proximity_precision = index.proximity_precision(rtxn)?.map(ProximityPrecisionView::from);
|
// in milli each word in the synonyms map were split on their separator. Since we lost
|
||||||
|
// this information we are going to put space between words.
|
||||||
let synonyms = index.user_defined_synonyms(rtxn)?;
|
let synonyms = index
|
||||||
|
.synonyms(rtxn)?
|
||||||
|
.iter()
|
||||||
|
.map(|(key, values)| (key.join(" "), values.iter().map(|value| value.join(" ")).collect()))
|
||||||
|
.collect();
|
||||||
|
|
||||||
let min_typo_word_len = MinWordSizeTyposSetting {
|
let min_typo_word_len = MinWordSizeTyposSetting {
|
||||||
one_typo: Setting::Set(index.min_word_len_one_typo(rtxn)?),
|
one_typo: Setting::Set(index.min_word_len_one_typo(rtxn)?),
|
||||||
@ -646,78 +474,39 @@ pub fn settings(
|
|||||||
|
|
||||||
let faceting = FacetingSettings {
|
let faceting = FacetingSettings {
|
||||||
max_values_per_facet: Setting::Set(
|
max_values_per_facet: Setting::Set(
|
||||||
index
|
index.max_values_per_facet(rtxn)?.unwrap_or(DEFAULT_VALUES_PER_FACET),
|
||||||
.max_values_per_facet(rtxn)?
|
|
||||||
.map(|x| x as usize)
|
|
||||||
.unwrap_or(DEFAULT_VALUES_PER_FACET),
|
|
||||||
),
|
|
||||||
sort_facet_values_by: Setting::Set(
|
|
||||||
index
|
|
||||||
.sort_facet_values_by(rtxn)?
|
|
||||||
.into_iter()
|
|
||||||
.map(|(name, sort)| (name, sort.into()))
|
|
||||||
.collect(),
|
|
||||||
),
|
),
|
||||||
};
|
};
|
||||||
|
|
||||||
let pagination = PaginationSettings {
|
let pagination = PaginationSettings {
|
||||||
max_total_hits: Setting::Set(
|
max_total_hits: Setting::Set(
|
||||||
index
|
index.pagination_max_total_hits(rtxn)?.unwrap_or(DEFAULT_PAGINATION_MAX_TOTAL_HITS),
|
||||||
.pagination_max_total_hits(rtxn)?
|
|
||||||
.map(|x| x as usize)
|
|
||||||
.unwrap_or(DEFAULT_PAGINATION_MAX_TOTAL_HITS),
|
|
||||||
),
|
),
|
||||||
};
|
};
|
||||||
|
|
||||||
let embedders: BTreeMap<_, _> = index
|
Ok(Settings {
|
||||||
.embedding_configs(rtxn)?
|
|
||||||
.into_iter()
|
|
||||||
.map(|(name, config)| (name, Setting::Set(config.into())))
|
|
||||||
.collect();
|
|
||||||
let embedders = if embedders.is_empty() { Setting::NotSet } else { Setting::Set(embedders) };
|
|
||||||
|
|
||||||
let search_cutoff_ms = index.search_cutoff(rtxn)?;
|
|
||||||
|
|
||||||
let mut settings = Settings {
|
|
||||||
displayed_attributes: match displayed_attributes {
|
displayed_attributes: match displayed_attributes {
|
||||||
Some(attrs) => Setting::Set(attrs),
|
Some(attrs) => Setting::Set(attrs),
|
||||||
None => Setting::Reset,
|
None => Setting::Reset,
|
||||||
}
|
},
|
||||||
.into(),
|
|
||||||
searchable_attributes: match searchable_attributes {
|
searchable_attributes: match searchable_attributes {
|
||||||
Some(attrs) => Setting::Set(attrs),
|
Some(attrs) => Setting::Set(attrs),
|
||||||
None => Setting::Reset,
|
None => Setting::Reset,
|
||||||
}
|
},
|
||||||
.into(),
|
|
||||||
filterable_attributes: Setting::Set(filterable_attributes),
|
filterable_attributes: Setting::Set(filterable_attributes),
|
||||||
sortable_attributes: Setting::Set(sortable_attributes),
|
sortable_attributes: Setting::Set(sortable_attributes),
|
||||||
ranking_rules: Setting::Set(criteria.iter().map(|c| c.clone().into()).collect()),
|
ranking_rules: Setting::Set(criteria.iter().map(|c| c.clone().into()).collect()),
|
||||||
stop_words: Setting::Set(stop_words),
|
stop_words: Setting::Set(stop_words),
|
||||||
non_separator_tokens: Setting::Set(non_separator_tokens),
|
|
||||||
separator_tokens: Setting::Set(separator_tokens),
|
|
||||||
dictionary: Setting::Set(dictionary),
|
|
||||||
distinct_attribute: match distinct_field {
|
distinct_attribute: match distinct_field {
|
||||||
Some(field) => Setting::Set(field),
|
Some(field) => Setting::Set(field),
|
||||||
None => Setting::Reset,
|
None => Setting::Reset,
|
||||||
},
|
},
|
||||||
proximity_precision: Setting::Set(proximity_precision.unwrap_or_default()),
|
|
||||||
synonyms: Setting::Set(synonyms),
|
synonyms: Setting::Set(synonyms),
|
||||||
typo_tolerance: Setting::Set(typo_tolerance),
|
typo_tolerance: Setting::Set(typo_tolerance),
|
||||||
faceting: Setting::Set(faceting),
|
faceting: Setting::Set(faceting),
|
||||||
pagination: Setting::Set(pagination),
|
pagination: Setting::Set(pagination),
|
||||||
embedders,
|
|
||||||
search_cutoff_ms: match search_cutoff_ms {
|
|
||||||
Some(cutoff) => Setting::Set(cutoff),
|
|
||||||
None => Setting::Reset,
|
|
||||||
},
|
|
||||||
_kind: PhantomData,
|
_kind: PhantomData,
|
||||||
};
|
})
|
||||||
|
|
||||||
if let SecretPolicy::HideSecrets = secret_policy {
|
|
||||||
settings.hide_secrets()
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(settings)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, Eq, Deserr)]
|
#[derive(Debug, Clone, PartialEq, Eq, Deserr)]
|
||||||
@ -816,67 +605,6 @@ impl From<RankingRuleView> for Criterion {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Default, Debug, Clone, Copy, PartialEq, Eq, Deserr, Serialize, Deserialize)]
|
|
||||||
#[serde(deny_unknown_fields, rename_all = "camelCase")]
|
|
||||||
#[deserr(error = DeserrJsonError<InvalidSettingsProximityPrecision>, rename_all = camelCase, deny_unknown_fields)]
|
|
||||||
pub enum ProximityPrecisionView {
|
|
||||||
#[default]
|
|
||||||
ByWord,
|
|
||||||
ByAttribute,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<ProximityPrecision> for ProximityPrecisionView {
|
|
||||||
fn from(value: ProximityPrecision) -> Self {
|
|
||||||
match value {
|
|
||||||
ProximityPrecision::ByWord => ProximityPrecisionView::ByWord,
|
|
||||||
ProximityPrecision::ByAttribute => ProximityPrecisionView::ByAttribute,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl From<ProximityPrecisionView> for ProximityPrecision {
|
|
||||||
fn from(value: ProximityPrecisionView) -> Self {
|
|
||||||
match value {
|
|
||||||
ProximityPrecisionView::ByWord => ProximityPrecision::ByWord,
|
|
||||||
ProximityPrecisionView::ByAttribute => ProximityPrecision::ByAttribute,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, Default, Deserialize, PartialEq, Eq)]
|
|
||||||
pub struct WildcardSetting(Setting<Vec<String>>);
|
|
||||||
|
|
||||||
impl From<Setting<Vec<String>>> for WildcardSetting {
|
|
||||||
fn from(setting: Setting<Vec<String>>) -> Self {
|
|
||||||
Self(setting)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Serialize for WildcardSetting {
|
|
||||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
|
||||||
where
|
|
||||||
S: Serializer,
|
|
||||||
{
|
|
||||||
serialize_with_wildcard(&self.0, serializer)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<E: deserr::DeserializeError> Deserr<E> for WildcardSetting {
|
|
||||||
fn deserialize_from_value<V: deserr::IntoValue>(
|
|
||||||
value: deserr::Value<V>,
|
|
||||||
location: ValuePointerRef<'_>,
|
|
||||||
) -> Result<Self, E> {
|
|
||||||
Ok(Self(Setting::deserialize_from_value(value, location)?))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl std::ops::Deref for WildcardSetting {
|
|
||||||
type Target = Setting<Vec<String>>;
|
|
||||||
|
|
||||||
fn deref(&self) -> &Self::Target {
|
|
||||||
&self.0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
pub(crate) mod test {
|
pub(crate) mod test {
|
||||||
use super::*;
|
use super::*;
|
||||||
@ -885,23 +613,17 @@ pub(crate) mod test {
|
|||||||
fn test_setting_check() {
|
fn test_setting_check() {
|
||||||
// test no changes
|
// test no changes
|
||||||
let settings = Settings {
|
let settings = Settings {
|
||||||
displayed_attributes: Setting::Set(vec![String::from("hello")]).into(),
|
displayed_attributes: Setting::Set(vec![String::from("hello")]),
|
||||||
searchable_attributes: Setting::Set(vec![String::from("hello")]).into(),
|
searchable_attributes: Setting::Set(vec![String::from("hello")]),
|
||||||
filterable_attributes: Setting::NotSet,
|
filterable_attributes: Setting::NotSet,
|
||||||
sortable_attributes: Setting::NotSet,
|
sortable_attributes: Setting::NotSet,
|
||||||
ranking_rules: Setting::NotSet,
|
ranking_rules: Setting::NotSet,
|
||||||
stop_words: Setting::NotSet,
|
stop_words: Setting::NotSet,
|
||||||
non_separator_tokens: Setting::NotSet,
|
|
||||||
separator_tokens: Setting::NotSet,
|
|
||||||
dictionary: Setting::NotSet,
|
|
||||||
synonyms: Setting::NotSet,
|
synonyms: Setting::NotSet,
|
||||||
distinct_attribute: Setting::NotSet,
|
distinct_attribute: Setting::NotSet,
|
||||||
proximity_precision: Setting::NotSet,
|
|
||||||
typo_tolerance: Setting::NotSet,
|
typo_tolerance: Setting::NotSet,
|
||||||
faceting: Setting::NotSet,
|
faceting: Setting::NotSet,
|
||||||
pagination: Setting::NotSet,
|
pagination: Setting::NotSet,
|
||||||
embedders: Setting::NotSet,
|
|
||||||
search_cutoff_ms: Setting::NotSet,
|
|
||||||
_kind: PhantomData::<Unchecked>,
|
_kind: PhantomData::<Unchecked>,
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -912,29 +634,22 @@ pub(crate) mod test {
|
|||||||
// test wildcard
|
// test wildcard
|
||||||
// test no changes
|
// test no changes
|
||||||
let settings = Settings {
|
let settings = Settings {
|
||||||
displayed_attributes: Setting::Set(vec![String::from("*")]).into(),
|
displayed_attributes: Setting::Set(vec![String::from("*")]),
|
||||||
searchable_attributes: Setting::Set(vec![String::from("hello"), String::from("*")])
|
searchable_attributes: Setting::Set(vec![String::from("hello"), String::from("*")]),
|
||||||
.into(),
|
|
||||||
filterable_attributes: Setting::NotSet,
|
filterable_attributes: Setting::NotSet,
|
||||||
sortable_attributes: Setting::NotSet,
|
sortable_attributes: Setting::NotSet,
|
||||||
ranking_rules: Setting::NotSet,
|
ranking_rules: Setting::NotSet,
|
||||||
stop_words: Setting::NotSet,
|
stop_words: Setting::NotSet,
|
||||||
non_separator_tokens: Setting::NotSet,
|
|
||||||
separator_tokens: Setting::NotSet,
|
|
||||||
dictionary: Setting::NotSet,
|
|
||||||
synonyms: Setting::NotSet,
|
synonyms: Setting::NotSet,
|
||||||
distinct_attribute: Setting::NotSet,
|
distinct_attribute: Setting::NotSet,
|
||||||
proximity_precision: Setting::NotSet,
|
|
||||||
typo_tolerance: Setting::NotSet,
|
typo_tolerance: Setting::NotSet,
|
||||||
faceting: Setting::NotSet,
|
faceting: Setting::NotSet,
|
||||||
pagination: Setting::NotSet,
|
pagination: Setting::NotSet,
|
||||||
embedders: Setting::NotSet,
|
|
||||||
search_cutoff_ms: Setting::NotSet,
|
|
||||||
_kind: PhantomData::<Unchecked>,
|
_kind: PhantomData::<Unchecked>,
|
||||||
};
|
};
|
||||||
|
|
||||||
let checked = settings.check();
|
let checked = settings.check();
|
||||||
assert_eq!(checked.displayed_attributes, Setting::Reset.into());
|
assert_eq!(checked.displayed_attributes, Setting::Reset);
|
||||||
assert_eq!(checked.searchable_attributes, Setting::Reset.into());
|
assert_eq!(checked.searchable_attributes, Setting::Reset);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user