2598: Bring `stable` into `main` (v0.28.0) r=curquiza a=curquiza



Co-authored-by: Tamo <tamo@meilisearch.com>
Co-authored-by: bors[bot] <26634292+bors[bot]@users.noreply.github.com>
Co-authored-by: Kerollmops <clement@meilisearch.com>
Co-authored-by: Janith Petangoda <22471198+janithpet@users.noreply.github.com>
Co-authored-by: Clémentine Urquizar <clementine@meilisearch.com>
Co-authored-by: Clémentine Urquizar - curqui <clementine@meilisearch.com>
Co-authored-by: Irevoire <tamo@meilisearch.com>
Co-authored-by: ManyTheFish <many@meilisearch.com>
This commit is contained in:
bors[bot] 2022-07-11 14:06:02 +00:00 committed by GitHub
commit 01a47e2db5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
30 changed files with 629 additions and 462 deletions

28
.github/scripts/check-release.sh vendored Normal file
View File

@ -0,0 +1,28 @@
#!/bin/bash
# check_tag $current_tag $file_tag $file_name
function check_tag {
if [[ "$1" != "$2" ]]; then
echo "Error: the current tag does not match the version in $3: found $2 - expected $1"
ret=1
fi
}
ret=0
current_tag=${GITHUB_REF#'refs/tags/v'}
toml_files='*/Cargo.toml'
for toml_file in $toml_files;
do
file_tag="$(grep '^version = ' $toml_file | cut -d '=' -f 2 | tr -d '"' | tr -d ' ')"
check_tag $current_tag $file_tag $toml_file
done
lock_file='Cargo.lock'
lock_tag=$(grep -A 1 'name = "meilisearch-auth"' $lock_file | grep version | cut -d '=' -f 2 | tr -d '"' | tr -d ' ')
check_tag $current_tag $lock_tag $lock_file
if [[ "$ret" -eq 0 ]] ; then
echo 'OK'
fi
exit $ret

View File

@ -1,14 +1,14 @@
#!/bin/sh #!/bin/sh
# Checks if the current tag should be the latest (in terms of semver and not of release date). # Was used in our CIs to publish the latest docker image. Not used anymore, will be used again when v1 and v2 will be out and we will want to maintain multiple stable versions.
# Ex: previous tag -> v0.10.1
# new tag -> v0.8.12
# The new tag should not be the latest
# So it returns "false", the CI should not run for the release v0.8.2
# Used in GHA in publish-docker-latest.yml
# Returns "true" or "false" (as a string) to be used in the `if` in GHA # Returns "true" or "false" (as a string) to be used in the `if` in GHA
# Checks if the current tag should be the latest (in terms of semver and not of release date).
# Ex: previous tag -> v2.1.1
# new tag -> v1.20.3
# The new tag (v1.20.3) should NOT be the latest
# So it returns "false", the `latest` tag should not be updated for the release v1.20.3 and still need to correspond to v2.1.1
# GLOBAL # GLOBAL
GREP_SEMVER_REGEXP='v\([0-9]*\)[.]\([0-9]*\)[.]\([0-9]*\)$' # i.e. v[number].[number].[number] GREP_SEMVER_REGEXP='v\([0-9]*\)[.]\([0-9]*\)[.]\([0-9]*\)$' # i.e. v[number].[number].[number]

View File

@ -5,9 +5,33 @@ on:
name: Publish binaries to release name: Publish binaries to release
jobs: jobs:
check-version:
name: Check the version validity
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
# Check if the tag has the v<nmumber>.<number>.<number> format.
# If yes, it means we are publishing an official release.
# If no, we are releasing a RC, so no need to check the version.
- name: Check tag format
if: github.event_name != 'schedule'
id: check-tag-format
run: |
escaped_tag=$(printf "%q" ${{ github.ref_name }})
if [[ $escaped_tag =~ ^v[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
echo ::set-output name=stable::true
else
echo ::set-output name=stable::false
fi
- name: Check release validity
if: steps.check-tag-format.outputs.stable == 'true'
run: bash .github/scripts/check-release.sh
publish: publish:
name: Publish binary for ${{ matrix.os }} name: Publish binary for ${{ matrix.os }}
runs-on: ${{ matrix.os }} runs-on: ${{ matrix.os }}
needs: check-version
strategy: strategy:
fail-fast: false fail-fast: false
matrix: matrix:
@ -41,6 +65,7 @@ jobs:
publish-aarch64: publish-aarch64:
name: Publish binary for aarch64 name: Publish binary for aarch64
runs-on: ${{ matrix.os }} runs-on: ${{ matrix.os }}
needs: check-version
continue-on-error: false continue-on-error: false
strategy: strategy:
fail-fast: false fail-fast: false

View File

@ -5,9 +5,18 @@ on:
types: [released] types: [released]
jobs: jobs:
check-version:
name: Check the version validity
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Check release validity
run: bash .github/scripts/check-release.sh
debian: debian:
name: Publish debian packagge name: Publish debian packagge
runs-on: ubuntu-18.04 runs-on: ubuntu-18.04
needs: check-version
steps: steps:
- uses: hecrj/setup-rust-action@master - uses: hecrj/setup-rust-action@master
with: with:
@ -30,6 +39,7 @@ jobs:
homebrew: homebrew:
name: Bump Homebrew formula name: Bump Homebrew formula
runs-on: ubuntu-18.04 runs-on: ubuntu-18.04
needs: check-version
steps: steps:
- name: Create PR to Homebrew - name: Create PR to Homebrew
uses: mislav/bump-homebrew-formula-action@v1 uses: mislav/bump-homebrew-formula-action@v1

View File

@ -5,8 +5,6 @@ on:
push: push:
tags: tags:
- '*' - '*'
release:
types: [released]
name: Publish tagged images to Docker Hub name: Publish tagged images to Docker Hub
@ -14,45 +12,54 @@ jobs:
docker: docker:
runs-on: docker runs-on: docker
steps: steps:
- uses: actions/checkout@v2
# Check if the tag has the v<nmumber>.<number>.<number> format. If yes, it means we are publishing an official release.
# In this situation, we need to set `output.stable` to create/update the following tags (additionally to the `vX.Y.Z` Docker tag):
# - a `vX.Y` (without patch version) Docker tag
# - a `latest` Docker tag
- name: Check tag format
if: github.event_name != 'schedule'
id: check-tag-format
run: |
escaped_tag=$(printf "%q" ${{ github.ref_name }})
if [[ $escaped_tag =~ ^v[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
echo ::set-output name=stable::true
else
echo ::set-output name=stable::false
fi
# Check only the validity of the tag for official releases (not for pre-releases or other tags)
- name: Check release validity
if: github.event_name != 'schedule' && steps.check-tag-format.outputs.stable == 'true'
run: bash .github/scripts/check-release.sh
- name: Set up QEMU - name: Set up QEMU
uses: docker/setup-qemu-action@v2 uses: docker/setup-qemu-action@v2
- name: Set up Docker Buildx - name: Set up Docker Buildx
uses: docker/setup-buildx-action@v2 uses: docker/setup-buildx-action@v2
- name: Login to DockerHub - name: Login to Docker Hub
if: github.event_name != 'schedule' if: github.event_name != 'schedule'
uses: docker/login-action@v2 uses: docker/login-action@v2
with: with:
username: ${{ secrets.DOCKERHUB_USERNAME }} username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }} password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Check tag format
id: check-tag-format
run: |
# Escape submitted tag name
escaped_tag=$(printf "%q" ${{ github.ref_name }})
# Check if tag has format v<nmumber>.<number>.<number> and set output.match
# to create a vX.Y (without patch version) Docker tag
if [[ $escaped_tag =~ ^v[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
echo ::set-output name=match::true
else
echo ::set-output name=match::false
fi
- name: Docker meta - name: Docker meta
id: meta id: meta
uses: docker/metadata-action@v4 uses: docker/metadata-action@v4
with: with:
images: getmeili/meilisearch images: getmeili/meilisearch
# The lastest tag is only pushed for the official Meilisearch release # The lastest and `vX.Y` tags are only pushed for the official Meilisearch releases
# See https://github.com/docker/metadata-action#latest-tag # See https://github.com/docker/metadata-action#latest-tag
flavor: latest=false flavor: latest=false
tags: | tags: |
type=ref,event=tag type=ref,event=tag
type=semver,pattern=v{{major}}.{{minor}},enable=${{ steps.check-tag-format.outputs.match }} type=semver,pattern=v{{major}}.{{minor}},enable=${{ steps.check-tag-format.outputs.stable == 'true' }}
type=raw,value=latest,enable=${{ github.event_name == 'release' }} type=raw,value=latest,enable=${{ steps.check-tag-format.outputs.stable == 'true' }}
- name: Build and push - name: Build and push
id: docker_build id: docker_build

58
Cargo.lock generated
View File

@ -1123,8 +1123,8 @@ dependencies = [
[[package]] [[package]]
name = "filter-parser" name = "filter-parser"
version = "0.29.3" version = "0.31.1"
source = "git+https://github.com/meilisearch/milli.git?tag=v0.29.3#f1d848bb9add86b9414d110a083dfa0462d5d636" source = "git+https://github.com/meilisearch/milli.git?tag=v0.31.1#83ad1aaf0552db9f63fc21ae9fe3976e61577dc8"
dependencies = [ dependencies = [
"nom", "nom",
"nom_locate", "nom_locate",
@ -1148,8 +1148,8 @@ dependencies = [
[[package]] [[package]]
name = "flatten-serde-json" name = "flatten-serde-json"
version = "0.29.3" version = "0.31.1"
source = "git+https://github.com/meilisearch/milli.git?tag=v0.29.3#f1d848bb9add86b9414d110a083dfa0462d5d636" source = "git+https://github.com/meilisearch/milli.git?tag=v0.31.1#83ad1aaf0552db9f63fc21ae9fe3976e61577dc8"
dependencies = [ dependencies = [
"serde_json", "serde_json",
] ]
@ -1661,8 +1661,8 @@ dependencies = [
[[package]] [[package]]
name = "json-depth-checker" name = "json-depth-checker"
version = "0.29.3" version = "0.31.1"
source = "git+https://github.com/meilisearch/milli.git?tag=v0.29.3#f1d848bb9add86b9414d110a083dfa0462d5d636" source = "git+https://github.com/meilisearch/milli.git?tag=v0.31.1#83ad1aaf0552db9f63fc21ae9fe3976e61577dc8"
dependencies = [ dependencies = [
"serde_json", "serde_json",
] ]
@ -2003,7 +2003,6 @@ checksum = "a3e378b66a060d48947b590737b30a1be76706c8dd7b8ba0f2fe3989c68a853f"
name = "meilisearch-auth" name = "meilisearch-auth"
version = "0.28.0" version = "0.28.0"
dependencies = [ dependencies = [
"base64",
"enum-iterator", "enum-iterator",
"hmac", "hmac",
"meilisearch-types", "meilisearch-types",
@ -2014,7 +2013,7 @@ dependencies = [
"sha2", "sha2",
"thiserror", "thiserror",
"time 0.3.9", "time 0.3.9",
"uuid", "uuid 1.1.2",
] ]
[[package]] [[package]]
@ -2069,7 +2068,6 @@ dependencies = [
"serde", "serde",
"serde-cs", "serde-cs",
"serde_json", "serde_json",
"serde_url_params",
"sha-1", "sha-1",
"sha2", "sha2",
"siphasher", "siphasher",
@ -2084,9 +2082,10 @@ dependencies = [
"tokio", "tokio",
"tokio-stream", "tokio-stream",
"urlencoding", "urlencoding",
"uuid", "uuid 1.1.2",
"vergen", "vergen",
"walkdir", "walkdir",
"yaup",
"zip", "zip",
] ]
@ -2147,7 +2146,7 @@ dependencies = [
"thiserror", "thiserror",
"time 0.3.9", "time 0.3.9",
"tokio", "tokio",
"uuid", "uuid 1.1.2",
"walkdir", "walkdir",
"whoami", "whoami",
] ]
@ -2189,8 +2188,8 @@ dependencies = [
[[package]] [[package]]
name = "milli" name = "milli"
version = "0.29.3" version = "0.31.1"
source = "git+https://github.com/meilisearch/milli.git?tag=v0.29.3#f1d848bb9add86b9414d110a083dfa0462d5d636" source = "git+https://github.com/meilisearch/milli.git?tag=v0.31.1#83ad1aaf0552db9f63fc21ae9fe3976e61577dc8"
dependencies = [ dependencies = [
"bimap", "bimap",
"bincode", "bincode",
@ -2229,7 +2228,7 @@ dependencies = [
"tempfile", "tempfile",
"thiserror", "thiserror",
"time 0.3.9", "time 0.3.9",
"uuid", "uuid 0.8.2",
] ]
[[package]] [[package]]
@ -2515,7 +2514,7 @@ checksum = "d4fd5641d01c8f18a23da7b6fe29298ff4b55afcccdf78973b24cf3175fee32e"
[[package]] [[package]]
name = "permissive-json-pointer" name = "permissive-json-pointer"
version = "0.2.0" version = "0.28.0"
dependencies = [ dependencies = [
"big_s", "big_s",
"serde_json", "serde_json",
@ -3113,16 +3112,6 @@ dependencies = [
"serde", "serde",
] ]
[[package]]
name = "serde_url_params"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2c43307d0640738af32fe8d01e47119bc0fc8a686be470a44a586caff76dfb34"
dependencies = [
"serde",
"url",
]
[[package]] [[package]]
name = "serde_urlencoded" name = "serde_urlencoded"
version = "0.7.1" version = "0.7.1"
@ -3686,6 +3675,15 @@ name = "uuid"
version = "0.8.2" version = "0.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bc5cf98d8186244414c848017f0e2676b3fcb46807f6668a97dfe67359a3c4b7" checksum = "bc5cf98d8186244414c848017f0e2676b3fcb46807f6668a97dfe67359a3c4b7"
dependencies = [
"getrandom",
]
[[package]]
name = "uuid"
version = "1.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dd6469f4314d5f1ffec476e05f17cc9a78bc7a27a6a857842170bdf8d6f98d2f"
dependencies = [ dependencies = [
"getrandom", "getrandom",
"serde", "serde",
@ -3994,6 +3992,16 @@ version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b6d12cb7a57bbf2ab670ed9545bae3648048547f9039279a89ce000208e585c1" checksum = "b6d12cb7a57bbf2ab670ed9545bae3648048547f9039279a89ce000208e585c1"
[[package]]
name = "yaup"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3bc9ef6963f7e857050aabf31ebc44184f278bcfec4c3671552c1a916b152b45"
dependencies = [
"serde",
"url",
]
[[package]] [[package]]
name = "zerocopy" name = "zerocopy"
version = "0.3.0" version = "0.3.0"

View File

@ -4,15 +4,14 @@ version = "0.28.0"
edition = "2021" edition = "2021"
[dependencies] [dependencies]
base64 = "0.13.0"
enum-iterator = "0.7.0" enum-iterator = "0.7.0"
hmac = "0.12.1" hmac = "0.12.1"
meilisearch-types = { path = "../meilisearch-types" } meilisearch-types = { path = "../meilisearch-types" }
milli = { git = "https://github.com/meilisearch/milli.git", tag = "v0.29.3" } milli = { git = "https://github.com/meilisearch/milli.git", tag = "v0.31.1" }
rand = "0.8.4" rand = "0.8.4"
serde = { version = "1.0.136", features = ["derive"] } serde = { version = "1.0.136", features = ["derive"] }
serde_json = { version = "1.0.79", features = ["preserve_order"] } serde_json = { version = "1.0.79", features = ["preserve_order"] }
sha2 = "0.10.2" sha2 = "0.10.2"
thiserror = "1.0.30" thiserror = "1.0.30"
time = { version = "0.3.7", features = ["serde-well-known", "formatting", "parsing", "macros"] } time = { version = "0.3.7", features = ["serde-well-known", "formatting", "parsing", "macros"] }
uuid = { version = "0.8.2", features = ["serde", "v4"] } uuid = { version = "1.1.2", features = ["serde", "v4"] }

View File

@ -18,7 +18,7 @@ pub use action::{actions, Action};
use error::{AuthControllerError, Result}; use error::{AuthControllerError, Result};
pub use key::Key; pub use key::Key;
use meilisearch_types::star_or::StarOr; use meilisearch_types::star_or::StarOr;
use store::generate_key_as_base64; use store::generate_key_as_hexa;
pub use store::open_auth_store_env; pub use store::open_auth_store_env;
use store::HeedAuthStore; use store::HeedAuthStore;
@ -139,7 +139,7 @@ impl AuthController {
pub fn generate_key(&self, uid: Uuid) -> Option<String> { pub fn generate_key(&self, uid: Uuid) -> Option<String> {
self.master_key self.master_key
.as_ref() .as_ref()
.map(|master_key| generate_key_as_base64(uid.as_bytes(), master_key.as_bytes())) .map(|master_key| generate_key_as_hexa(uid, master_key.as_bytes()))
} }
/// Check if the provided key is authorized to make a specific action /// Check if the provided key is authorized to make a specific action

View File

@ -14,8 +14,9 @@ use hmac::{Hmac, Mac};
use meilisearch_types::star_or::StarOr; use meilisearch_types::star_or::StarOr;
use milli::heed::types::{ByteSlice, DecodeIgnore, SerdeJson}; use milli::heed::types::{ByteSlice, DecodeIgnore, SerdeJson};
use milli::heed::{Database, Env, EnvOpenOptions, RwTxn}; use milli::heed::{Database, Env, EnvOpenOptions, RwTxn};
use sha2::{Digest, Sha256}; use sha2::Sha256;
use time::OffsetDateTime; use time::OffsetDateTime;
use uuid::fmt::Hyphenated;
use uuid::Uuid; use uuid::Uuid;
use super::error::Result; use super::error::Result;
@ -169,13 +170,16 @@ impl HeedAuthStore {
.remap_data_type::<DecodeIgnore>() .remap_data_type::<DecodeIgnore>()
.iter(&rtxn)? .iter(&rtxn)?
.filter_map(|res| match res { .filter_map(|res| match res {
Ok((uid, _)) Ok((uid, _)) => {
if generate_key_as_base64(uid, master_key).as_bytes() == encoded_key =>
{
let (uid, _) = try_split_array_at(uid)?; let (uid, _) = try_split_array_at(uid)?;
Some(Uuid::from_bytes(*uid)) let uid = Uuid::from_bytes(*uid);
if generate_key_as_hexa(uid, master_key).as_bytes() == encoded_key {
Some(uid)
} else {
None
}
} }
_ => None, Err(_) => None,
}) })
.next(); .next();
@ -281,13 +285,17 @@ impl<'a> milli::heed::BytesEncode<'a> for KeyIdActionCodec {
} }
} }
pub fn generate_key_as_base64(uid: &[u8], master_key: &[u8]) -> String { pub fn generate_key_as_hexa(uid: Uuid, master_key: &[u8]) -> String {
let master_key_sha = Sha256::digest(master_key); // format uid as hyphenated allowing user to generate their own keys.
let mut mac = Hmac::<Sha256>::new_from_slice(master_key_sha.as_slice()).unwrap(); let mut uid_buffer = [0; Hyphenated::LENGTH];
mac.update(uid); let uid = uid.hyphenated().encode_lower(&mut uid_buffer);
// new_from_slice function never fail.
let mut mac = Hmac::<Sha256>::new_from_slice(master_key).unwrap();
mac.update(uid.as_bytes());
let result = mac.finalize(); let result = mac.finalize();
base64::encode_config(result.into_bytes(), base64::URL_SAFE_NO_PAD) format!("{:x}", result.into_bytes())
} }
/// Divides one slice into two at an index, returns `None` if mid is out of bounds. /// Divides one slice into two at an index, returns `None` if mid is out of bounds.

View File

@ -75,7 +75,7 @@ thiserror = "1.0.30"
time = { version = "0.3.7", features = ["serde-well-known", "formatting", "parsing", "macros"] } time = { version = "0.3.7", features = ["serde-well-known", "formatting", "parsing", "macros"] }
tokio = { version = "1.17.0", features = ["full"] } tokio = { version = "1.17.0", features = ["full"] }
tokio-stream = "0.1.8" tokio-stream = "0.1.8"
uuid = { version = "0.8.2", features = ["serde", "v4"] } uuid = { version = "1.1.2", features = ["serde", "v4"] }
walkdir = "2.3.2" walkdir = "2.3.2"
[dev-dependencies] [dev-dependencies]
@ -83,8 +83,8 @@ actix-rt = "2.7.0"
assert-json-diff = "2.0.1" assert-json-diff = "2.0.1"
manifest-dir-macros = "0.1.14" manifest-dir-macros = "0.1.14"
maplit = "1.0.2" maplit = "1.0.2"
serde_url_params = "0.2.1"
urlencoding = "2.1.0" urlencoding = "2.1.0"
yaup = "0.2.0"
[features] [features]
default = ["analytics", "mini-dashboard"] default = ["analytics", "mini-dashboard"]
@ -105,5 +105,5 @@ mini-dashboard = [
tikv-jemallocator = "0.4.3" tikv-jemallocator = "0.4.3"
[package.metadata.mini-dashboard] [package.metadata.mini-dashboard]
assets-url = "https://github.com/meilisearch/mini-dashboard/releases/download/v0.1.10/build.zip" assets-url = "https://github.com/meilisearch/mini-dashboard/releases/download/v0.2.0/build.zip"
sha1 = "1adf96592c267425c110bfefc36b7fc6bfb0f93d" sha1 = "25d1615c608541375a08bd722c3fd3315f926be6"

View File

@ -574,7 +574,7 @@ impl DocumentsAggregator {
let content_type = request let content_type = request
.headers() .headers()
.get(CONTENT_TYPE) .get(CONTENT_TYPE)
.map(|s| s.to_str().unwrap_or("unknown")) .and_then(|s| s.to_str().ok())
.unwrap_or("unknown") .unwrap_or("unknown")
.to_string(); .to_string();
ret.content_types.insert(content_type); ret.content_types.insert(content_type);
@ -591,13 +591,13 @@ impl DocumentsAggregator {
self.updated |= other.updated; self.updated |= other.updated;
// we can't create a union because there is no `into_union` method // we can't create a union because there is no `into_union` method
for user_agent in other.user_agents.into_iter() { for user_agent in other.user_agents {
self.user_agents.insert(user_agent); self.user_agents.insert(user_agent);
} }
for primary_key in other.primary_keys.into_iter() { for primary_key in other.primary_keys {
self.primary_keys.insert(primary_key); self.primary_keys.insert(primary_key);
} }
for content_type in other.content_types.into_iter() { for content_type in other.content_types {
self.content_types.insert(content_type); self.content_types.insert(content_type);
} }
self.index_creation |= other.index_creation; self.index_creation |= other.index_creation;

View File

@ -14,7 +14,6 @@ use serde_json::Value;
use crate::analytics::{Analytics, SearchAggregator}; use crate::analytics::{Analytics, SearchAggregator};
use crate::extractors::authentication::{policies::*, GuardedData}; use crate::extractors::authentication::{policies::*, GuardedData};
use crate::extractors::sequential_extractor::SeqHandler; use crate::extractors::sequential_extractor::SeqHandler;
use crate::routes::{fold_star_or, StarOr};
pub fn configure(cfg: &mut web::ServiceConfig) { pub fn configure(cfg: &mut web::ServiceConfig) {
cfg.service( cfg.service(
@ -30,16 +29,16 @@ pub struct SearchQueryGet {
q: Option<String>, q: Option<String>,
offset: Option<usize>, offset: Option<usize>,
limit: Option<usize>, limit: Option<usize>,
attributes_to_retrieve: Option<CS<StarOr<String>>>, attributes_to_retrieve: Option<CS<String>>,
attributes_to_crop: Option<CS<StarOr<String>>>, attributes_to_crop: Option<CS<String>>,
#[serde(default = "DEFAULT_CROP_LENGTH")] #[serde(default = "DEFAULT_CROP_LENGTH")]
crop_length: usize, crop_length: usize,
attributes_to_highlight: Option<CS<StarOr<String>>>, attributes_to_highlight: Option<CS<String>>,
filter: Option<String>, filter: Option<String>,
sort: Option<String>, sort: Option<String>,
#[serde(default = "Default::default")] #[serde(default = "Default::default")]
show_matches_position: bool, show_matches_position: bool,
facets: Option<CS<StarOr<String>>>, facets: Option<CS<String>>,
#[serde(default = "DEFAULT_HIGHLIGHT_PRE_TAG")] #[serde(default = "DEFAULT_HIGHLIGHT_PRE_TAG")]
highlight_pre_tag: String, highlight_pre_tag: String,
#[serde(default = "DEFAULT_HIGHLIGHT_POST_TAG")] #[serde(default = "DEFAULT_HIGHLIGHT_POST_TAG")]
@ -62,14 +61,18 @@ impl From<SearchQueryGet> for SearchQuery {
q: other.q, q: other.q,
offset: other.offset, offset: other.offset,
limit: other.limit.unwrap_or_else(DEFAULT_SEARCH_LIMIT), limit: other.limit.unwrap_or_else(DEFAULT_SEARCH_LIMIT),
attributes_to_retrieve: other.attributes_to_retrieve.and_then(fold_star_or), attributes_to_retrieve: other
attributes_to_crop: other.attributes_to_crop.and_then(fold_star_or), .attributes_to_retrieve
.map(|o| o.into_iter().collect()),
attributes_to_crop: other.attributes_to_crop.map(|o| o.into_iter().collect()),
crop_length: other.crop_length, crop_length: other.crop_length,
attributes_to_highlight: other.attributes_to_highlight.and_then(fold_star_or), attributes_to_highlight: other
.attributes_to_highlight
.map(|o| o.into_iter().collect()),
filter, filter,
sort: other.sort.map(|attr| fix_sort_query_parameters(&attr)), sort: other.sort.map(|attr| fix_sort_query_parameters(&attr)),
show_matches_position: other.show_matches_position, show_matches_position: other.show_matches_position,
facets: other.facets.and_then(fold_star_or), facets: other.facets.map(|o| o.into_iter().collect()),
highlight_pre_tag: other.highlight_pre_tag, highlight_pre_tag: other.highlight_pre_tag,
highlight_post_tag: other.highlight_post_tag, highlight_post_tag: other.highlight_post_tag,
crop_marker: other.crop_marker, crop_marker: other.crop_marker,

View File

@ -318,7 +318,7 @@ make_setting_route!(
"Pagination Updated".to_string(), "Pagination Updated".to_string(),
json!({ json!({
"pagination": { "pagination": {
"limited_to": setting.as_ref().and_then(|s| s.limited_to.set()), "max_total_hits": setting.as_ref().and_then(|s| s.max_total_hits.set()),
}, },
}), }),
Some(req), Some(req),
@ -349,7 +349,9 @@ generate_configure!(
stop_words, stop_words,
synonyms, synonyms,
ranking_rules, ranking_rules,
typo_tolerance typo_tolerance,
pagination,
faceting
); );
pub async fn update_all( pub async fn update_all(
@ -409,6 +411,18 @@ pub async fn update_all(
.map(|s| s.two_typos.set())) .map(|s| s.two_typos.set()))
.flatten(), .flatten(),
}, },
"faceting": {
"max_values_per_facet": settings.faceting
.as_ref()
.set()
.and_then(|s| s.max_values_per_facet.as_ref().set()),
},
"pagination": {
"max_total_hits": settings.pagination
.as_ref()
.set()
.and_then(|s| s.max_total_hits.as_ref().set()),
},
}), }),
Some(&req), Some(&req),
); );

View File

@ -25,7 +25,7 @@ pub fn configure(cfg: &mut web::ServiceConfig) {
#[derive(Deserialize, Debug)] #[derive(Deserialize, Debug)]
#[serde(rename_all = "camelCase", deny_unknown_fields)] #[serde(rename_all = "camelCase", deny_unknown_fields)]
pub struct TaskFilterQuery { pub struct TasksFilterQuery {
#[serde(rename = "type")] #[serde(rename = "type")]
type_: Option<CS<StarOr<TaskType>>>, type_: Option<CS<StarOr<TaskType>>>,
status: Option<CS<StarOr<TaskStatus>>>, status: Option<CS<StarOr<TaskStatus>>>,
@ -61,17 +61,11 @@ fn task_status_matches_events(status: &TaskStatus, events: &[TaskEvent]) -> bool
async fn get_tasks( async fn get_tasks(
meilisearch: GuardedData<ActionPolicy<{ actions::TASKS_GET }>, MeiliSearch>, meilisearch: GuardedData<ActionPolicy<{ actions::TASKS_GET }>, MeiliSearch>,
params: web::Query<TaskFilterQuery>, params: web::Query<TasksFilterQuery>,
req: HttpRequest, req: HttpRequest,
analytics: web::Data<dyn Analytics>, analytics: web::Data<dyn Analytics>,
) -> Result<HttpResponse, ResponseError> { ) -> Result<HttpResponse, ResponseError> {
analytics.publish( let TasksFilterQuery {
"Tasks Seen".to_string(),
json!({ "per_task_uid": false }),
Some(&req),
);
let TaskFilterQuery {
type_, type_,
status, status,
index_uid, index_uid,
@ -87,6 +81,16 @@ async fn get_tasks(
let status: Option<Vec<_>> = status.and_then(fold_star_or); let status: Option<Vec<_>> = status.and_then(fold_star_or);
let index_uid: Option<Vec<_>> = index_uid.and_then(fold_star_or); let index_uid: Option<Vec<_>> = index_uid.and_then(fold_star_or);
analytics.publish(
"Tasks Seen".to_string(),
json!({
"filtered_by_index_uid": index_uid.as_ref().map_or(false, |v| !v.is_empty()),
"filtered_by_type": type_.as_ref().map_or(false, |v| !v.is_empty()),
"filtered_by_status": status.as_ref().map_or(false, |v| !v.is_empty()),
}),
Some(&req),
);
// Then we filter on potential indexes and make sure that the search filter // Then we filter on potential indexes and make sure that the search filter
// restrictions are also applied. // restrictions are also applied.
let indexes_filters = match index_uid { let indexes_filters = match index_uid {

View File

@ -42,6 +42,7 @@ async fn add_valid_api_key() {
"name": "indexing-key", "name": "indexing-key",
"description": "Indexing API key", "description": "Indexing API key",
"uid": "4bc0887a-0e41-4f3b-935d-0c451dcee9c8", "uid": "4bc0887a-0e41-4f3b-935d-0c451dcee9c8",
"key": "d9e776b8412f1db6974c9a5556b961c3559440b6588216f4ea5d9ed49f7c8f3c",
"indexes": ["products"], "indexes": ["products"],
"actions": [ "actions": [
"search", "search",

View File

@ -222,7 +222,7 @@ impl Index<'_> {
} }
pub async fn search_get(&self, query: Value) -> (Value, StatusCode) { pub async fn search_get(&self, query: Value) -> (Value, StatusCode) {
let params = serde_url_params::to_string(&query).unwrap(); let params = yaup::to_string(&query).unwrap();
let url = format!("/indexes/{}/search?{}", encode(self.uid.as_ref()), params); let url = format!("/indexes/{}/search?{}", encode(self.uid.as_ref()), params);
self.service.get(url).await self.service.get(url).await
} }

View File

@ -61,7 +61,7 @@ async fn import_dump_v2_movie_raw() {
assert_eq!(code, 200); assert_eq!(code, 200);
assert_eq!( assert_eq!(
settings, settings,
json!({"displayedAttributes": ["*"], "searchableAttributes": ["*"], "filterableAttributes": [], "sortableAttributes": [], "rankingRules": ["words", "typo", "proximity", "attribute", "exactness"], "stopWords": [], "synonyms": {}, "distinctAttribute": null, "typoTolerance": {"enabled": true, "minWordSizeForTypos": {"oneTypo": 5, "twoTypos": 9}, "disableOnWords": [], "disableOnAttributes": [] }, "faceting": { "maxValuesPerFacet": 100 }, "pagination": { "limitedTo": 1000 } }) json!({"displayedAttributes": ["*"], "searchableAttributes": ["*"], "filterableAttributes": [], "sortableAttributes": [], "rankingRules": ["words", "typo", "proximity", "attribute", "exactness"], "stopWords": [], "synonyms": {}, "distinctAttribute": null, "typoTolerance": {"enabled": true, "minWordSizeForTypos": {"oneTypo": 5, "twoTypos": 9}, "disableOnWords": [], "disableOnAttributes": [] }, "faceting": { "maxValuesPerFacet": 100 }, "pagination": { "maxTotalHits": 1000 } })
); );
let (tasks, code) = index.list_tasks().await; let (tasks, code) = index.list_tasks().await;
@ -125,7 +125,7 @@ async fn import_dump_v2_movie_with_settings() {
assert_eq!(code, 200); assert_eq!(code, 200);
assert_eq!( assert_eq!(
settings, settings,
json!({ "displayedAttributes": ["title", "genres", "overview", "poster", "release_date"], "searchableAttributes": ["title", "overview"], "filterableAttributes": ["genres"], "sortableAttributes": [], "rankingRules": ["words", "typo", "proximity", "attribute", "exactness"], "stopWords": ["of", "the"], "synonyms": {}, "distinctAttribute": null, "typoTolerance": {"enabled": true, "minWordSizeForTypos": { "oneTypo": 5, "twoTypos": 9 }, "disableOnWords": [], "disableOnAttributes": [] }, "faceting": { "maxValuesPerFacet": 100 }, "pagination": { "limitedTo": 1000 } }) json!({ "displayedAttributes": ["title", "genres", "overview", "poster", "release_date"], "searchableAttributes": ["title", "overview"], "filterableAttributes": ["genres"], "sortableAttributes": [], "rankingRules": ["words", "typo", "proximity", "attribute", "exactness"], "stopWords": ["of", "the"], "synonyms": {}, "distinctAttribute": null, "typoTolerance": {"enabled": true, "minWordSizeForTypos": { "oneTypo": 5, "twoTypos": 9 }, "disableOnWords": [], "disableOnAttributes": [] }, "faceting": { "maxValuesPerFacet": 100 }, "pagination": { "maxTotalHits": 1000 } })
); );
let (tasks, code) = index.list_tasks().await; let (tasks, code) = index.list_tasks().await;
@ -189,7 +189,7 @@ async fn import_dump_v2_rubygems_with_settings() {
assert_eq!(code, 200); assert_eq!(code, 200);
assert_eq!( assert_eq!(
settings, settings,
json!({"displayedAttributes": ["name", "summary", "description", "version", "total_downloads"], "searchableAttributes": ["name", "summary"], "filterableAttributes": ["version"], "sortableAttributes": [], "rankingRules": ["typo", "words", "fame:desc", "proximity", "attribute", "exactness", "total_downloads:desc"], "stopWords": [], "synonyms": {}, "distinctAttribute": null, "typoTolerance": {"enabled": true, "minWordSizeForTypos": {"oneTypo": 5, "twoTypos": 9}, "disableOnWords": [], "disableOnAttributes": [] }, "faceting": { "maxValuesPerFacet": 100 }, "pagination": { "limitedTo": 1000 }}) json!({"displayedAttributes": ["name", "summary", "description", "version", "total_downloads"], "searchableAttributes": ["name", "summary"], "filterableAttributes": ["version"], "sortableAttributes": [], "rankingRules": ["typo", "words", "fame:desc", "proximity", "attribute", "exactness", "total_downloads:desc"], "stopWords": [], "synonyms": {}, "distinctAttribute": null, "typoTolerance": {"enabled": true, "minWordSizeForTypos": {"oneTypo": 5, "twoTypos": 9}, "disableOnWords": [], "disableOnAttributes": [] }, "faceting": { "maxValuesPerFacet": 100 }, "pagination": { "maxTotalHits": 1000 }})
); );
let (tasks, code) = index.list_tasks().await; let (tasks, code) = index.list_tasks().await;
@ -253,7 +253,7 @@ async fn import_dump_v3_movie_raw() {
assert_eq!(code, 200); assert_eq!(code, 200);
assert_eq!( assert_eq!(
settings, settings,
json!({"displayedAttributes": ["*"], "searchableAttributes": ["*"], "filterableAttributes": [], "sortableAttributes": [], "rankingRules": ["words", "typo", "proximity", "attribute", "exactness"], "stopWords": [], "synonyms": {}, "distinctAttribute": null, "typoTolerance": {"enabled": true, "minWordSizeForTypos": {"oneTypo": 5, "twoTypos": 9}, "disableOnWords": [], "disableOnAttributes": [] }, "faceting": { "maxValuesPerFacet": 100 }, "pagination": { "limitedTo": 1000 } }) json!({"displayedAttributes": ["*"], "searchableAttributes": ["*"], "filterableAttributes": [], "sortableAttributes": [], "rankingRules": ["words", "typo", "proximity", "attribute", "exactness"], "stopWords": [], "synonyms": {}, "distinctAttribute": null, "typoTolerance": {"enabled": true, "minWordSizeForTypos": {"oneTypo": 5, "twoTypos": 9}, "disableOnWords": [], "disableOnAttributes": [] }, "faceting": { "maxValuesPerFacet": 100 }, "pagination": { "maxTotalHits": 1000 } })
); );
let (tasks, code) = index.list_tasks().await; let (tasks, code) = index.list_tasks().await;
@ -317,7 +317,7 @@ async fn import_dump_v3_movie_with_settings() {
assert_eq!(code, 200); assert_eq!(code, 200);
assert_eq!( assert_eq!(
settings, settings,
json!({ "displayedAttributes": ["title", "genres", "overview", "poster", "release_date"], "searchableAttributes": ["title", "overview"], "filterableAttributes": ["genres"], "sortableAttributes": [], "rankingRules": ["words", "typo", "proximity", "attribute", "exactness"], "stopWords": ["of", "the"], "synonyms": {}, "distinctAttribute": null, "typoTolerance": {"enabled": true, "minWordSizeForTypos": { "oneTypo": 5, "twoTypos": 9 }, "disableOnWords": [], "disableOnAttributes": [] }, "faceting": { "maxValuesPerFacet": 100 }, "pagination": { "limitedTo": 1000 } }) json!({ "displayedAttributes": ["title", "genres", "overview", "poster", "release_date"], "searchableAttributes": ["title", "overview"], "filterableAttributes": ["genres"], "sortableAttributes": [], "rankingRules": ["words", "typo", "proximity", "attribute", "exactness"], "stopWords": ["of", "the"], "synonyms": {}, "distinctAttribute": null, "typoTolerance": {"enabled": true, "minWordSizeForTypos": { "oneTypo": 5, "twoTypos": 9 }, "disableOnWords": [], "disableOnAttributes": [] }, "faceting": { "maxValuesPerFacet": 100 }, "pagination": { "maxTotalHits": 1000 } })
); );
let (tasks, code) = index.list_tasks().await; let (tasks, code) = index.list_tasks().await;
@ -381,7 +381,7 @@ async fn import_dump_v3_rubygems_with_settings() {
assert_eq!(code, 200); assert_eq!(code, 200);
assert_eq!( assert_eq!(
settings, settings,
json!({"displayedAttributes": ["name", "summary", "description", "version", "total_downloads"], "searchableAttributes": ["name", "summary"], "filterableAttributes": ["version"], "sortableAttributes": [], "rankingRules": ["typo", "words", "fame:desc", "proximity", "attribute", "exactness", "total_downloads:desc"], "stopWords": [], "synonyms": {}, "distinctAttribute": null, "typoTolerance": {"enabled": true, "minWordSizeForTypos": {"oneTypo": 5, "twoTypos": 9}, "disableOnWords": [], "disableOnAttributes": [] }, "faceting": { "maxValuesPerFacet": 100 }, "pagination": { "limitedTo": 1000 } }) json!({"displayedAttributes": ["name", "summary", "description", "version", "total_downloads"], "searchableAttributes": ["name", "summary"], "filterableAttributes": ["version"], "sortableAttributes": [], "rankingRules": ["typo", "words", "fame:desc", "proximity", "attribute", "exactness", "total_downloads:desc"], "stopWords": [], "synonyms": {}, "distinctAttribute": null, "typoTolerance": {"enabled": true, "minWordSizeForTypos": {"oneTypo": 5, "twoTypos": 9}, "disableOnWords": [], "disableOnAttributes": [] }, "faceting": { "maxValuesPerFacet": 100 }, "pagination": { "maxTotalHits": 1000 } })
); );
let (tasks, code) = index.list_tasks().await; let (tasks, code) = index.list_tasks().await;
@ -445,7 +445,7 @@ async fn import_dump_v4_movie_raw() {
assert_eq!(code, 200); assert_eq!(code, 200);
assert_eq!( assert_eq!(
settings, settings,
json!({ "displayedAttributes": ["*"], "searchableAttributes": ["*"], "filterableAttributes": [], "sortableAttributes": [], "rankingRules": ["words", "typo", "proximity", "attribute", "exactness"], "stopWords": [], "synonyms": {}, "distinctAttribute": null, "typoTolerance": {"enabled": true, "minWordSizeForTypos": {"oneTypo": 5, "twoTypos": 9}, "disableOnWords": [], "disableOnAttributes": [] }, "faceting": { "maxValuesPerFacet": 100 }, "pagination": { "limitedTo": 1000 } }) json!({ "displayedAttributes": ["*"], "searchableAttributes": ["*"], "filterableAttributes": [], "sortableAttributes": [], "rankingRules": ["words", "typo", "proximity", "attribute", "exactness"], "stopWords": [], "synonyms": {}, "distinctAttribute": null, "typoTolerance": {"enabled": true, "minWordSizeForTypos": {"oneTypo": 5, "twoTypos": 9}, "disableOnWords": [], "disableOnAttributes": [] }, "faceting": { "maxValuesPerFacet": 100 }, "pagination": { "maxTotalHits": 1000 } })
); );
let (tasks, code) = index.list_tasks().await; let (tasks, code) = index.list_tasks().await;
@ -509,7 +509,7 @@ async fn import_dump_v4_movie_with_settings() {
assert_eq!(code, 200); assert_eq!(code, 200);
assert_eq!( assert_eq!(
settings, settings,
json!({ "displayedAttributes": ["title", "genres", "overview", "poster", "release_date"], "searchableAttributes": ["title", "overview"], "filterableAttributes": ["genres"], "sortableAttributes": [], "rankingRules": ["words", "typo", "proximity", "attribute", "exactness"], "stopWords": ["of", "the"], "synonyms": {}, "distinctAttribute": null, "typoTolerance": {"enabled": true, "minWordSizeForTypos": { "oneTypo": 5, "twoTypos": 9 }, "disableOnWords": [], "disableOnAttributes": [] }, "faceting": { "maxValuesPerFacet": 100 }, "pagination": { "limitedTo": 1000 } }) json!({ "displayedAttributes": ["title", "genres", "overview", "poster", "release_date"], "searchableAttributes": ["title", "overview"], "filterableAttributes": ["genres"], "sortableAttributes": [], "rankingRules": ["words", "typo", "proximity", "attribute", "exactness"], "stopWords": ["of", "the"], "synonyms": {}, "distinctAttribute": null, "typoTolerance": {"enabled": true, "minWordSizeForTypos": { "oneTypo": 5, "twoTypos": 9 }, "disableOnWords": [], "disableOnAttributes": [] }, "faceting": { "maxValuesPerFacet": 100 }, "pagination": { "maxTotalHits": 1000 } })
); );
let (tasks, code) = index.list_tasks().await; let (tasks, code) = index.list_tasks().await;
@ -573,7 +573,7 @@ async fn import_dump_v4_rubygems_with_settings() {
assert_eq!(code, 200); assert_eq!(code, 200);
assert_eq!( assert_eq!(
settings, settings,
json!({ "displayedAttributes": ["name", "summary", "description", "version", "total_downloads"], "searchableAttributes": ["name", "summary"], "filterableAttributes": ["version"], "sortableAttributes": [], "rankingRules": ["typo", "words", "fame:desc", "proximity", "attribute", "exactness", "total_downloads:desc"], "stopWords": [], "synonyms": {}, "distinctAttribute": null, "typoTolerance": {"enabled": true, "minWordSizeForTypos": {"oneTypo": 5, "twoTypos": 9}, "disableOnWords": [], "disableOnAttributes": [] }, "faceting": { "maxValuesPerFacet": 100 }, "pagination": { "limitedTo": 1000 } }) json!({ "displayedAttributes": ["name", "summary", "description", "version", "total_downloads"], "searchableAttributes": ["name", "summary"], "filterableAttributes": ["version"], "sortableAttributes": [], "rankingRules": ["typo", "words", "fame:desc", "proximity", "attribute", "exactness", "total_downloads:desc"], "stopWords": [], "synonyms": {}, "distinctAttribute": null, "typoTolerance": {"enabled": true, "minWordSizeForTypos": {"oneTypo": 5, "twoTypos": 9}, "disableOnWords": [], "disableOnAttributes": [] }, "faceting": { "maxValuesPerFacet": 100 }, "pagination": { "maxTotalHits": 1000 } })
); );
let (tasks, code) = index.list_tasks().await; let (tasks, code) = index.list_tasks().await;

View File

@ -45,26 +45,18 @@ async fn search_invalid_highlight_and_crop_tags() {
for field in fields { for field in fields {
// object // object
index let (response, code) = index
.search( .search_post(json!({field.to_string(): {"marker": "<crop>"}}))
json!({field.to_string(): {"marker": "<crop>"}}),
|response, code| {
assert_eq!(code, 400, "field {} passing object: {}", &field, response);
assert_eq!(response["code"], "bad_request");
},
)
.await; .await;
assert_eq!(code, 400, "field {} passing object: {}", &field, response);
assert_eq!(response["code"], "bad_request");
// array // array
index let (response, code) = index
.search( .search_post(json!({field.to_string(): ["marker", "<crop>"]}))
json!({field.to_string(): ["marker", "<crop>"]}),
|response, code| {
assert_eq!(code, 400, "field {} passing array: {}", &field, response);
assert_eq!(response["code"], "bad_request");
},
)
.await; .await;
assert_eq!(code, 400, "field {} passing array: {}", &field, response);
assert_eq!(response["code"], "bad_request");
} }
} }
@ -115,7 +107,7 @@ async fn filter_invalid_syntax_array() {
"link": "https://docs.meilisearch.com/errors#invalid_filter" "link": "https://docs.meilisearch.com/errors#invalid_filter"
}); });
index index
.search(json!({"filter": [["title & Glass"]]}), |response, code| { .search(json!({"filter": ["title & Glass"]}), |response, code| {
assert_eq!(response, expected_response); assert_eq!(response, expected_response);
assert_eq!(code, 400); assert_eq!(code, 400);
}) })
@ -172,7 +164,7 @@ async fn filter_invalid_attribute_array() {
"link": "https://docs.meilisearch.com/errors#invalid_filter" "link": "https://docs.meilisearch.com/errors#invalid_filter"
}); });
index index
.search(json!({"filter": [["many = Glass"]]}), |response, code| { .search(json!({"filter": ["many = Glass"]}), |response, code| {
assert_eq!(response, expected_response); assert_eq!(response, expected_response);
assert_eq!(code, 400); assert_eq!(code, 400);
}) })
@ -226,7 +218,7 @@ async fn filter_reserved_geo_attribute_array() {
"link": "https://docs.meilisearch.com/errors#invalid_filter" "link": "https://docs.meilisearch.com/errors#invalid_filter"
}); });
index index
.search(json!({"filter": [["_geo = Glass"]]}), |response, code| { .search(json!({"filter": ["_geo = Glass"]}), |response, code| {
assert_eq!(response, expected_response); assert_eq!(response, expected_response);
assert_eq!(code, 400); assert_eq!(code, 400);
}) })
@ -281,7 +273,7 @@ async fn filter_reserved_attribute_array() {
}); });
index index
.search( .search(
json!({"filter": [["_geoDistance = Glass"]]}), json!({"filter": ["_geoDistance = Glass"]}),
|response, code| { |response, code| {
assert_eq!(response, expected_response); assert_eq!(response, expected_response);
assert_eq!(code, 400); assert_eq!(code, 400);

View File

@ -15,85 +15,100 @@ async fn formatted_contain_wildcard() {
index.add_documents(documents, None).await; index.add_documents(documents, None).await;
index.wait_task(1).await; index.wait_task(1).await;
let (response, code) = index index.search(json!({ "q": "pesti", "attributesToRetrieve": ["father", "mother"], "attributesToHighlight": ["father", "mother", "*"], "attributesToCrop": ["doggos"], "showMatchesPosition": true }),
.search_post(json!({ "q": "pesti", "attributesToRetrieve": ["father", "mother"], "attributesToHighlight": ["father", "mother", "*"], "attributesToCrop": ["doggos"], "showMatchesPosition": true })) |response, code|
.await; {
assert_eq!(code, 200, "{}", response); assert_eq!(code, 200, "{}", response);
assert_eq!( assert_eq!(
response["hits"][0], response["hits"][0],
json!({ json!({
"_formatted": { "_formatted": {
"id": "852", "id": "852",
"cattos": "<em>pesti</em>", "cattos": "<em>pesti</em>",
},
"_matchesPosition": {"cattos": [{"start": 0, "length": 5}]},
})
);
}
)
.await;
index
.search(
json!({ "q": "pesti", "attributesToRetrieve": ["*"] }),
|response, code| {
assert_eq!(code, 200, "{}", response);
assert_eq!(
response["hits"][0],
json!({
"id": 852,
"cattos": "pesti",
})
);
}, },
"_matchesPosition": {"cattos": [{"start": 0, "length": 5}]}, )
})
);
let (response, code) = index
.search_post(json!({ "q": "pesti", "attributesToRetrieve": ["*"] }))
.await; .await;
assert_eq!(code, 200, "{}", response);
assert_eq!(
response["hits"][0],
json!({
"id": 852,
"cattos": "pesti",
})
);
let (response, code) = index index
.search_post( .search(
json!({ "q": "pesti", "attributesToRetrieve": ["*"], "attributesToHighlight": ["id"], "showMatchesPosition": true }), json!({ "q": "pesti", "attributesToRetrieve": ["*"], "attributesToHighlight": ["id"], "showMatchesPosition": true }),
|response, code| {
assert_eq!(code, 200, "{}", response);
assert_eq!(
response["hits"][0],
json!({
"id": 852,
"cattos": "pesti",
"_formatted": {
"id": "852",
"cattos": "pesti",
},
"_matchesPosition": {"cattos": [{"start": 0, "length": 5}]},
})
);
}
) )
.await; .await;
assert_eq!(code, 200, "{}", response);
assert_eq!(
response["hits"][0],
json!({
"id": 852,
"cattos": "pesti",
"_formatted": {
"id": "852",
"cattos": "pesti",
},
"_matchesPosition": {"cattos": [{"start": 0, "length": 5}]},
})
);
let (response, code) = index index
.search_post( .search(
json!({ "q": "pesti", "attributesToRetrieve": ["*"], "attributesToCrop": ["*"] }), json!({ "q": "pesti", "attributesToRetrieve": ["*"], "attributesToCrop": ["*"] }),
|response, code| {
assert_eq!(code, 200, "{}", response);
assert_eq!(
response["hits"][0],
json!({
"id": 852,
"cattos": "pesti",
"_formatted": {
"id": "852",
"cattos": "pesti",
}
})
);
},
) )
.await; .await;
assert_eq!(code, 200, "{}", response);
assert_eq!(
response["hits"][0],
json!({
"id": 852,
"cattos": "pesti",
"_formatted": {
"id": "852",
"cattos": "pesti",
}
})
);
let (response, code) = index index
.search_post(json!({ "q": "pesti", "attributesToCrop": ["*"] })) .search(
json!({ "q": "pesti", "attributesToCrop": ["*"] }),
|response, code| {
assert_eq!(code, 200, "{}", response);
assert_eq!(
response["hits"][0],
json!({
"id": 852,
"cattos": "pesti",
"_formatted": {
"id": "852",
"cattos": "pesti",
}
})
);
},
)
.await; .await;
assert_eq!(code, 200, "{}", response);
assert_eq!(
response["hits"][0],
json!({
"id": 852,
"cattos": "pesti",
"_formatted": {
"id": "852",
"cattos": "pesti",
}
})
);
} }
#[actix_rt::test] #[actix_rt::test]
@ -105,108 +120,122 @@ async fn format_nested() {
index.add_documents(documents, None).await; index.add_documents(documents, None).await;
index.wait_task(0).await; index.wait_task(0).await;
let (response, code) = index index
.search_post(json!({ "q": "pesti", "attributesToRetrieve": ["doggos"] })) .search(
.await; json!({ "q": "pesti", "attributesToRetrieve": ["doggos"] }),
assert_eq!(code, 200, "{}", response); |response, code| {
assert_eq!( assert_eq!(code, 200, "{}", response);
response["hits"][0], assert_eq!(
json!({ response["hits"][0],
"doggos": [ json!({
{ "doggos": [
"name": "bobby", {
"age": 2, "name": "bobby",
}, "age": 2,
{ },
"name": "buddy", {
"age": 4, "name": "buddy",
}, "age": 4,
], },
}) ],
); })
);
let (response, code) = index },
.search_post(json!({ "q": "pesti", "attributesToRetrieve": ["doggos.name"] }))
.await;
assert_eq!(code, 200, "{}", response);
assert_eq!(
response["hits"][0],
json!({
"doggos": [
{
"name": "bobby",
},
{
"name": "buddy",
},
],
})
);
let (response, code) = index
.search_post(
json!({ "q": "bobby", "attributesToRetrieve": ["doggos.name"], "showMatchesPosition": true }),
) )
.await; .await;
assert_eq!(code, 200, "{}", response);
assert_eq!(
response["hits"][0],
json!({
"doggos": [
{
"name": "bobby",
},
{
"name": "buddy",
},
],
"_matchesPosition": {"doggos.name": [{"start": 0, "length": 5}]},
})
);
let (response, code) = index index
.search_post(json!({ "q": "pesti", "attributesToRetrieve": [], "attributesToHighlight": ["doggos.name"] })) .search(
.await; json!({ "q": "pesti", "attributesToRetrieve": ["doggos.name"] }),
assert_eq!(code, 200, "{}", response); |response, code| {
assert_eq!( assert_eq!(code, 200, "{}", response);
response["hits"][0], assert_eq!(
json!({ response["hits"][0],
"_formatted": { json!({
"doggos": [ "doggos": [
{ {
"name": "bobby", "name": "bobby",
}, },
{ {
"name": "buddy", "name": "buddy",
}, },
], ],
})
);
}, },
}) )
);
let (response, code) = index
.search_post(json!({ "q": "pesti", "attributesToRetrieve": [], "attributesToCrop": ["doggos.name"] }))
.await; .await;
assert_eq!(code, 200, "{}", response);
assert_eq!(
response["hits"][0],
json!({
"_formatted": {
"doggos": [
{
"name": "bobby",
},
{
"name": "buddy",
},
],
},
})
);
let (response, code) = index index
.search_post(json!({ "q": "pesti", "attributesToRetrieve": ["doggos.name"], "attributesToHighlight": ["doggos.age"] })) .search(
json!({ "q": "bobby", "attributesToRetrieve": ["doggos.name"], "showMatchesPosition": true }),
|response, code| {
assert_eq!(code, 200, "{}", response);
assert_eq!(
response["hits"][0],
json!({
"doggos": [
{
"name": "bobby",
},
{
"name": "buddy",
},
],
"_matchesPosition": {"doggos.name": [{"start": 0, "length": 5}]},
})
);
}
)
.await; .await;
index
.search(json!({ "q": "pesti", "attributesToRetrieve": [], "attributesToHighlight": ["doggos.name"] }),
|response, code| {
assert_eq!(code, 200, "{}", response);
assert_eq!(
response["hits"][0],
json!({
"_formatted": {
"doggos": [
{
"name": "bobby",
},
{
"name": "buddy",
},
],
},
})
);
})
.await;
index
.search(json!({ "q": "pesti", "attributesToRetrieve": [], "attributesToCrop": ["doggos.name"] }),
|response, code| {
assert_eq!(code, 200, "{}", response);
assert_eq!(
response["hits"][0],
json!({
"_formatted": {
"doggos": [
{
"name": "bobby",
},
{
"name": "buddy",
},
],
},
})
);
})
.await;
index
.search(json!({ "q": "pesti", "attributesToRetrieve": ["doggos.name"], "attributesToHighlight": ["doggos.age"] }),
|response, code| {
assert_eq!(code, 200, "{}", response); assert_eq!(code, 200, "{}", response);
assert_eq!( assert_eq!(
response["hits"][0], response["hits"][0],
@ -233,11 +262,13 @@ async fn format_nested() {
}, },
}) })
); );
})
let (response, code) = index
.search_post(json!({ "q": "pesti", "attributesToRetrieve": [], "attributesToHighlight": ["doggos.age"], "attributesToCrop": ["doggos.name"] }))
.await; .await;
assert_eq!(code, 200, "{}", response);
index
.search(json!({ "q": "pesti", "attributesToRetrieve": [], "attributesToHighlight": ["doggos.age"], "attributesToCrop": ["doggos.name"] }),
|response, code| {
assert_eq!(code, 200, "{}", response);
assert_eq!( assert_eq!(
response["hits"][0], response["hits"][0],
json!({ json!({
@ -255,6 +286,9 @@ async fn format_nested() {
}, },
}) })
); );
}
)
.await;
} }
#[actix_rt::test] #[actix_rt::test]
@ -271,9 +305,9 @@ async fn displayedattr_2_smol() {
index.add_documents(documents, None).await; index.add_documents(documents, None).await;
index.wait_task(1).await; index.wait_task(1).await;
let (response, code) = index index
.search_post(json!({ "attributesToRetrieve": ["father", "id"], "attributesToHighlight": ["mother"], "attributesToCrop": ["cattos"] })) .search(json!({ "attributesToRetrieve": ["father", "id"], "attributesToHighlight": ["mother"], "attributesToCrop": ["cattos"] }),
.await; |response, code| {
assert_eq!(code, 200, "{}", response); assert_eq!(code, 200, "{}", response);
assert_eq!( assert_eq!(
response["hits"][0], response["hits"][0],
@ -281,119 +315,157 @@ async fn displayedattr_2_smol() {
"id": 852, "id": 852,
}) })
); );
let (response, code) = index
.search_post(json!({ "attributesToRetrieve": ["id"] }))
.await;
assert_eq!(code, 200, "{}", response);
assert_eq!(
response["hits"][0],
json!({
"id": 852,
}) })
);
let (response, code) = index
.search_post(json!({ "attributesToHighlight": ["id"] }))
.await; .await;
assert_eq!(code, 200, "{}", response);
assert_eq!(
response["hits"][0],
json!({
"id": 852,
"_formatted": {
"id": "852",
}
})
);
let (response, code) = index index
.search_post(json!({ "attributesToCrop": ["id"] })) .search(
.await; json!({ "attributesToRetrieve": ["id"] }),
assert_eq!(code, 200, "{}", response); |response, code| {
assert_eq!( assert_eq!(code, 200, "{}", response);
response["hits"][0], assert_eq!(
json!({ response["hits"][0],
"id": 852, json!({
"_formatted": { "id": 852,
"id": "852", })
} );
}) },
);
let (response, code) = index
.search_post(json!({ "attributesToHighlight": ["id"], "attributesToCrop": ["id"] }))
.await;
assert_eq!(code, 200, "{}", response);
assert_eq!(
response["hits"][0],
json!({
"id": 852,
"_formatted": {
"id": "852",
}
})
);
let (response, code) = index
.search_post(json!({ "attributesToHighlight": ["cattos"] }))
.await;
assert_eq!(code, 200, "{}", response);
assert_eq!(
response["hits"][0],
json!({
"id": 852,
})
);
let (response, code) = index
.search_post(json!({ "attributesToCrop": ["cattos"] }))
.await;
assert_eq!(code, 200, "{}", response);
assert_eq!(
response["hits"][0],
json!({
"id": 852,
})
);
let (response, code) = index
.search_post(json!({ "attributesToRetrieve": ["cattos"] }))
.await;
assert_eq!(code, 200, "{}", response);
assert_eq!(response["hits"][0], json!({}));
let (response, code) = index
.search_post(
json!({ "attributesToRetrieve": ["cattos"], "attributesToHighlight": ["cattos"], "attributesToCrop": ["cattos"] }),
) )
.await; .await;
index
.search(
json!({ "attributesToHighlight": ["id"] }),
|response, code| {
assert_eq!(code, 200, "{}", response);
assert_eq!(
response["hits"][0],
json!({
"id": 852,
"_formatted": {
"id": "852",
}
})
);
},
)
.await;
index
.search(json!({ "attributesToCrop": ["id"] }), |response, code| {
assert_eq!(code, 200, "{}", response);
assert_eq!(
response["hits"][0],
json!({
"id": 852,
"_formatted": {
"id": "852",
}
})
);
})
.await;
index
.search(
json!({ "attributesToHighlight": ["id"], "attributesToCrop": ["id"] }),
|response, code| {
assert_eq!(code, 200, "{}", response);
assert_eq!(
response["hits"][0],
json!({
"id": 852,
"_formatted": {
"id": "852",
}
})
);
},
)
.await;
index
.search(
json!({ "attributesToHighlight": ["cattos"] }),
|response, code| {
assert_eq!(code, 200, "{}", response);
assert_eq!(
response["hits"][0],
json!({
"id": 852,
})
);
},
)
.await;
index
.search(
json!({ "attributesToCrop": ["cattos"] }),
|response, code| {
assert_eq!(code, 200, "{}", response);
assert_eq!(
response["hits"][0],
json!({
"id": 852,
})
);
},
)
.await;
index
.search(
json!({ "attributesToRetrieve": ["cattos"] }),
|response, code| {
assert_eq!(code, 200, "{}", response);
assert_eq!(response["hits"][0], json!({}));
},
)
.await;
index
.search(
json!({ "attributesToRetrieve": ["cattos"], "attributesToHighlight": ["cattos"], "attributesToCrop": ["cattos"] }),
|response, code| {
assert_eq!(code, 200, "{}", response); assert_eq!(code, 200, "{}", response);
assert_eq!(response["hits"][0], json!({})); assert_eq!(response["hits"][0], json!({}));
let (response, code) = index
.search_post(json!({ "attributesToRetrieve": ["cattos"], "attributesToHighlight": ["id"] }))
.await;
assert_eq!(code, 200, "{}", response);
assert_eq!(
response["hits"][0],
json!({
"_formatted": {
"id": "852",
} }
}) )
); .await;
let (response, code) = index index
.search_post(json!({ "attributesToRetrieve": ["cattos"], "attributesToCrop": ["id"] })) .search(
json!({ "attributesToRetrieve": ["cattos"], "attributesToHighlight": ["id"] }),
|response, code| {
assert_eq!(code, 200, "{}", response);
assert_eq!(
response["hits"][0],
json!({
"_formatted": {
"id": "852",
}
})
);
},
)
.await;
index
.search(
json!({ "attributesToRetrieve": ["cattos"], "attributesToCrop": ["id"] }),
|response, code| {
assert_eq!(code, 200, "{}", response);
assert_eq!(
response["hits"][0],
json!({
"_formatted": {
"id": "852",
}
})
);
},
)
.await; .await;
assert_eq!(code, 200, "{}", response);
assert_eq!(
response["hits"][0],
json!({
"_formatted": {
"id": "852",
}
})
);
} }

View File

@ -567,7 +567,7 @@ async fn placeholder_search_is_hard_limited() {
.await; .await;
index index
.update_settings(json!({ "pagination": { "limitedTo": 10_000 } })) .update_settings(json!({ "pagination": { "maxTotalHits": 10_000 } }))
.await; .await;
index.wait_task(1).await; index.wait_task(1).await;
@ -636,7 +636,7 @@ async fn search_is_hard_limited() {
.await; .await;
index index
.update_settings(json!({ "pagination": { "limitedTo": 10_000 } })) .update_settings(json!({ "pagination": { "maxTotalHits": 10_000 } }))
.await; .await;
index.wait_task(1).await; index.wait_task(1).await;

View File

@ -27,7 +27,13 @@ static DEFAULT_SETTINGS_VALUES: Lazy<HashMap<&'static str, Value>> = Lazy::new(|
map.insert( map.insert(
"faceting", "faceting",
json!({ json!({
"maxValuesByFacet": json!(100), "maxValuesPerFacet": json!(100),
}),
);
map.insert(
"pagination",
json!({
"maxTotalHits": json!(1000),
}), }),
); );
map map
@ -76,7 +82,7 @@ async fn get_settings() {
assert_eq!( assert_eq!(
settings["pagination"], settings["pagination"],
json!({ json!({
"limitedTo": 1000, "maxTotalHits": 1000,
}) })
); );
} }
@ -206,7 +212,7 @@ async fn error_update_setting_unexisting_index_invalid_uid() {
} }
macro_rules! test_setting_routes { macro_rules! test_setting_routes {
($($setting:ident), *) => { ($($setting:ident $write_method:ident), *) => {
$( $(
mod $setting { mod $setting {
use crate::common::Server; use crate::common::Server;
@ -232,7 +238,7 @@ macro_rules! test_setting_routes {
.chars() .chars()
.map(|c| if c == '_' { '-' } else { c }) .map(|c| if c == '_' { '-' } else { c })
.collect::<String>()); .collect::<String>());
let (response, code) = server.service.put(url, serde_json::Value::Null).await; let (response, code) = server.service.$write_method(url, serde_json::Value::Null).await;
assert_eq!(code, 202, "{}", response); assert_eq!(code, 202, "{}", response);
server.index("").wait_task(0).await; server.index("").wait_task(0).await;
let (response, code) = server.index("test").get().await; let (response, code) = server.index("test").get().await;
@ -276,13 +282,15 @@ macro_rules! test_setting_routes {
} }
test_setting_routes!( test_setting_routes!(
filterable_attributes, filterable_attributes put,
displayed_attributes, displayed_attributes put,
searchable_attributes, searchable_attributes put,
distinct_attribute, distinct_attribute put,
stop_words, stop_words put,
ranking_rules, ranking_rules put,
synonyms synonyms put,
pagination patch,
faceting patch
); );
#[actix_rt::test] #[actix_rt::test]

View File

@ -3,8 +3,6 @@ name = "meilisearch-lib"
version = "0.28.0" version = "0.28.0"
edition = "2021" edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies] [dependencies]
actix-web = { version = "4.0.1", default-features = false } actix-web = { version = "4.0.1", default-features = false }
anyhow = { version = "1.0.56", features = ["backtrace"] } anyhow = { version = "1.0.56", features = ["backtrace"] }
@ -30,7 +28,7 @@ lazy_static = "1.4.0"
log = "0.4.14" log = "0.4.14"
meilisearch-auth = { path = "../meilisearch-auth" } meilisearch-auth = { path = "../meilisearch-auth" }
meilisearch-types = { path = "../meilisearch-types" } meilisearch-types = { path = "../meilisearch-types" }
milli = { git = "https://github.com/meilisearch/milli.git", tag = "v0.29.3" } milli = { git = "https://github.com/meilisearch/milli.git", tag = "v0.31.1" }
mime = "0.3.16" mime = "0.3.16"
num_cpus = "1.13.1" num_cpus = "1.13.1"
obkv = "0.2.0" obkv = "0.2.0"
@ -53,7 +51,7 @@ tempfile = "3.3.0"
thiserror = "1.0.30" thiserror = "1.0.30"
time = { version = "0.3.7", features = ["serde-well-known", "formatting", "parsing", "macros"] } time = { version = "0.3.7", features = ["serde-well-known", "formatting", "parsing", "macros"] }
tokio = { version = "1.17.0", features = ["full"] } tokio = { version = "1.17.0", features = ["full"] }
uuid = { version = "0.8.2", features = ["serde", "v4"] } uuid = { version = "1.1.2", features = ["serde", "v4"] }
walkdir = "2.3.2" walkdir = "2.3.2"
whoami = { version = "1.2.1", optional = true } whoami = { version = "1.2.1", optional = true }

View File

@ -27,7 +27,7 @@ const DATA_FILE_NAME: &str = "documents.jsonl";
impl Index { impl Index {
pub fn dump(&self, path: impl AsRef<Path>) -> Result<()> { pub fn dump(&self, path: impl AsRef<Path>) -> Result<()> {
// acquire write txn make sure any ongoing write is finished before we start. // acquire write txn make sure any ongoing write is finished before we start.
let txn = self.env.write_txn()?; let txn = self.write_txn()?;
let path = path.as_ref().join(format!("indexes/{}", self.uuid)); let path = path.as_ref().join(format!("indexes/{}", self.uuid));
create_dir_all(&path)?; create_dir_all(&path)?;

View File

@ -4,9 +4,10 @@ use std::marker::PhantomData;
use std::ops::Deref; use std::ops::Deref;
use std::path::Path; use std::path::Path;
use std::sync::Arc; use std::sync::Arc;
use walkdir::WalkDir;
use fst::IntoStreamer; use fst::IntoStreamer;
use milli::heed::{EnvOpenOptions, RoTxn}; use milli::heed::{CompactionOption, EnvOpenOptions, RoTxn};
use milli::update::{IndexerConfig, Setting}; use milli::update::{IndexerConfig, Setting};
use milli::{obkv_to_json, FieldDistribution, DEFAULT_VALUES_PER_FACET}; use milli::{obkv_to_json, FieldDistribution, DEFAULT_VALUES_PER_FACET};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
@ -14,8 +15,7 @@ use serde_json::{Map, Value};
use time::OffsetDateTime; use time::OffsetDateTime;
use uuid::Uuid; use uuid::Uuid;
use crate::index::search::DEFAULT_PAGINATION_LIMITED_TO; use crate::index::search::DEFAULT_PAGINATION_MAX_TOTAL_HITS;
use crate::EnvSizer;
use super::error::IndexError; use super::error::IndexError;
use super::error::Result; use super::error::Result;
@ -202,9 +202,9 @@ impl Index {
}; };
let pagination = PaginationSettings { let pagination = PaginationSettings {
limited_to: Setting::Set( max_total_hits: Setting::Set(
self.pagination_limited_to(txn)? self.pagination_max_total_hits(txn)?
.unwrap_or(DEFAULT_PAGINATION_LIMITED_TO), .unwrap_or(DEFAULT_PAGINATION_MAX_TOTAL_HITS),
), ),
}; };
@ -245,7 +245,7 @@ impl Index {
let fields_ids_map = self.fields_ids_map(&txn)?; let fields_ids_map = self.fields_ids_map(&txn)?;
let all_fields: Vec<_> = fields_ids_map.iter().map(|(id, _)| id).collect(); let all_fields: Vec<_> = fields_ids_map.iter().map(|(id, _)| id).collect();
let iter = self.documents.range(&txn, &(..))?.skip(offset).take(limit); let iter = self.all_documents(&txn)?.skip(offset).take(limit);
let mut documents = Vec::new(); let mut documents = Vec::new();
@ -302,7 +302,12 @@ impl Index {
} }
pub fn size(&self) -> u64 { pub fn size(&self) -> u64 {
self.env.size() WalkDir::new(self.inner.path())
.into_iter()
.filter_map(|entry| entry.ok())
.filter_map(|entry| entry.metadata().ok())
.filter(|metadata| metadata.is_file())
.fold(0, |acc, m| acc + m.len())
} }
pub fn snapshot(&self, path: impl AsRef<Path>) -> Result<()> { pub fn snapshot(&self, path: impl AsRef<Path>) -> Result<()> {
@ -310,9 +315,7 @@ impl Index {
create_dir_all(&dst)?; create_dir_all(&dst)?;
dst.push("data.mdb"); dst.push("data.mdb");
let _txn = self.write_txn()?; let _txn = self.write_txn()?;
self.inner self.inner.copy_to_path(dst, CompactionOption::Enabled)?;
.env
.copy_to_path(dst, milli::heed::CompactionOption::Enabled)?;
Ok(()) Ok(())
} }
} }

View File

@ -29,7 +29,7 @@ pub const DEFAULT_HIGHLIGHT_POST_TAG: fn() -> String = || "</em>".to_string();
/// The maximimum 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_LIMITED_TO: usize = 1000; pub const DEFAULT_PAGINATION_MAX_TOTAL_HITS: usize = 1000;
#[derive(Deserialize, Debug, Clone, PartialEq)] #[derive(Deserialize, Debug, Clone, PartialEq)]
#[serde(rename_all = "camelCase", deny_unknown_fields)] #[serde(rename_all = "camelCase", deny_unknown_fields)]
@ -91,14 +91,14 @@ impl Index {
search.query(query); search.query(query);
} }
let pagination_limited_to = self let max_total_hits = self
.pagination_limited_to(&rtxn)? .pagination_max_total_hits(&rtxn)?
.unwrap_or(DEFAULT_PAGINATION_LIMITED_TO); .unwrap_or(DEFAULT_PAGINATION_MAX_TOTAL_HITS);
// Make sure that a user can't get more documents than the hard limit, // Make sure that a user can't get more documents than the hard limit,
// we align that on the offset too. // we align that on the offset too.
let offset = min(query.offset.unwrap_or(0), pagination_limited_to); let offset = min(query.offset.unwrap_or(0), max_total_hits);
let limit = min(query.limit, pagination_limited_to.saturating_sub(offset)); let limit = min(query.limit, max_total_hits.saturating_sub(offset));
search.offset(offset); search.offset(offset);
search.limit(limit); search.limit(limit);

View File

@ -86,7 +86,7 @@ pub struct FacetingSettings {
pub struct PaginationSettings { pub struct PaginationSettings {
#[cfg_attr(test, proptest(strategy = "test::setting_strategy()"))] #[cfg_attr(test, proptest(strategy = "test::setting_strategy()"))]
#[serde(default, skip_serializing_if = "Setting::is_not_set")] #[serde(default, skip_serializing_if = "Setting::is_not_set")]
pub limited_to: Setting<usize>, pub max_total_hits: Setting<usize>,
} }
/// Holds all the settings for an index. `T` can either be `Checked` if they represents settings /// Holds all the settings for an index. `T` can either be `Checked` if they represents settings
@ -474,12 +474,12 @@ pub fn apply_settings_to_builder(
} }
match settings.pagination { match settings.pagination {
Setting::Set(ref value) => match value.limited_to { Setting::Set(ref value) => match value.max_total_hits {
Setting::Set(val) => builder.set_pagination_limited_to(val), Setting::Set(val) => builder.set_pagination_max_total_hits(val),
Setting::Reset => builder.reset_pagination_limited_to(), Setting::Reset => builder.reset_pagination_max_total_hits(),
Setting::NotSet => (), Setting::NotSet => (),
}, },
Setting::Reset => builder.reset_pagination_limited_to(), Setting::Reset => builder.reset_pagination_max_total_hits(),
Setting::NotSet => (), Setting::NotSet => (),
} }
} }

View File

@ -3,6 +3,7 @@ use std::fs::{create_dir_all, File};
use std::io::{BufRead, BufReader, Write}; use std::io::{BufRead, BufReader, Write};
use std::path::{Path, PathBuf}; use std::path::{Path, PathBuf};
use std::sync::Arc; use std::sync::Arc;
use walkdir::WalkDir;
use milli::heed::types::{SerdeBincode, Str}; use milli::heed::types::{SerdeBincode, Str};
use milli::heed::{CompactionOption, Database, Env}; use milli::heed::{CompactionOption, Database, Env};
@ -11,7 +12,6 @@ use uuid::Uuid;
use super::error::{IndexResolverError, Result}; use super::error::{IndexResolverError, Result};
use crate::tasks::task::TaskId; use crate::tasks::task::TaskId;
use crate::EnvSizer;
#[derive(Serialize, Deserialize)] #[derive(Serialize, Deserialize)]
pub struct DumpEntry { pub struct DumpEntry {
@ -131,7 +131,12 @@ impl HeedMetaStore {
} }
fn get_size(&self) -> Result<u64> { fn get_size(&self) -> Result<u64> {
Ok(self.env.size()) Ok(WalkDir::new(self.env.path())
.into_iter()
.filter_map(|entry| entry.ok())
.filter_map(|entry| entry.metadata().ok())
.filter(|metadata| metadata.is_file())
.fold(0, |acc, m| acc + m.len()))
} }
pub fn dump(&self, path: PathBuf) -> Result<()> { pub fn dump(&self, path: PathBuf) -> Result<()> {

View File

@ -20,23 +20,6 @@ pub use milli::heed;
mod compression; mod compression;
pub mod document_formats; pub mod document_formats;
use walkdir::WalkDir;
pub trait EnvSizer {
fn size(&self) -> u64;
}
impl EnvSizer for milli::heed::Env {
fn size(&self) -> u64 {
WalkDir::new(self.path())
.into_iter()
.filter_map(|entry| entry.ok())
.filter_map(|entry| entry.metadata().ok())
.filter(|metadata| metadata.is_file())
.fold(0, |acc, m| acc + m.len())
}
}
/// Check if a db is empty. It does not provide any information on the /// Check if a db is empty. It does not provide any information on the
/// validity of the data in it. /// validity of the data in it.
/// We consider a database as non empty when it's a non empty directory. /// We consider a database as non empty when it's a non empty directory.

View File

@ -7,6 +7,7 @@ use anyhow::bail;
use fs_extra::dir::{self, CopyOptions}; use fs_extra::dir::{self, CopyOptions};
use log::{info, trace}; use log::{info, trace};
use meilisearch_auth::open_auth_store_env; use meilisearch_auth::open_auth_store_env;
use milli::heed::CompactionOption;
use tokio::sync::RwLock; use tokio::sync::RwLock;
use tokio::time::sleep; use tokio::time::sleep;
use walkdir::WalkDir; use walkdir::WalkDir;
@ -181,9 +182,7 @@ impl SnapshotJob {
let mut options = milli::heed::EnvOpenOptions::new(); let mut options = milli::heed::EnvOpenOptions::new();
options.map_size(self.index_size); options.map_size(self.index_size);
let index = milli::Index::new(options, entry.path())?; let index = milli::Index::new(options, entry.path())?;
index index.copy_to_path(dst, CompactionOption::Enabled)?;
.env
.copy_to_path(dst, milli::heed::CompactionOption::Enabled)?;
} }
Ok(()) Ok(())

View File

@ -1,6 +1,6 @@
[package] [package]
name = "permissive-json-pointer" name = "permissive-json-pointer"
version = "0.2.0" version = "0.28.0"
edition = "2021" edition = "2021"
description = "A permissive json pointer" description = "A permissive json pointer"
readme = "README.md" readme = "README.md"