diff --git a/.github/scripts/check-release.sh b/.github/scripts/check-release.sh new file mode 100644 index 000000000..2ce171459 --- /dev/null +++ b/.github/scripts/check-release.sh @@ -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 diff --git a/.github/is-latest-release.sh b/.github/scripts/is-latest-release.sh similarity index 90% rename from .github/is-latest-release.sh rename to .github/scripts/is-latest-release.sh index 0c1db61c2..81534a2f7 100644 --- a/.github/is-latest-release.sh +++ b/.github/scripts/is-latest-release.sh @@ -1,14 +1,14 @@ #!/bin/sh -# Checks if the current tag should be the latest (in terms of semver and not of release date). -# 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 +# 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. # 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 GREP_SEMVER_REGEXP='v\([0-9]*\)[.]\([0-9]*\)[.]\([0-9]*\)$' # i.e. v[number].[number].[number] diff --git a/.github/workflows/publish-binaries.yml b/.github/workflows/publish-binaries.yml index d99d1ec41..a9fa50223 100644 --- a/.github/workflows/publish-binaries.yml +++ b/.github/workflows/publish-binaries.yml @@ -5,9 +5,33 @@ on: name: Publish binaries to release jobs: + check-version: + name: Check the version validity + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + # Check if the tag has the v.. 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: name: Publish binary for ${{ matrix.os }} runs-on: ${{ matrix.os }} + needs: check-version strategy: fail-fast: false matrix: @@ -41,6 +65,7 @@ jobs: publish-aarch64: name: Publish binary for aarch64 runs-on: ${{ matrix.os }} + needs: check-version continue-on-error: false strategy: fail-fast: false diff --git a/.github/workflows/publish-deb-brew-pkg.yml b/.github/workflows/publish-deb-brew-pkg.yml index cbf2e54d8..96bf9af9e 100644 --- a/.github/workflows/publish-deb-brew-pkg.yml +++ b/.github/workflows/publish-deb-brew-pkg.yml @@ -5,9 +5,18 @@ on: types: [released] 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: name: Publish debian packagge runs-on: ubuntu-18.04 + needs: check-version steps: - uses: hecrj/setup-rust-action@master with: @@ -30,6 +39,7 @@ jobs: homebrew: name: Bump Homebrew formula runs-on: ubuntu-18.04 + needs: check-version steps: - name: Create PR to Homebrew uses: mislav/bump-homebrew-formula-action@v1 diff --git a/.github/workflows/publish-docker-images.yml b/.github/workflows/publish-docker-images.yml index a6e62448a..72234fc01 100644 --- a/.github/workflows/publish-docker-images.yml +++ b/.github/workflows/publish-docker-images.yml @@ -5,8 +5,6 @@ on: push: tags: - '*' - release: - types: [released] name: Publish tagged images to Docker Hub @@ -14,45 +12,54 @@ jobs: docker: runs-on: docker steps: + - uses: actions/checkout@v2 + + # Check if the tag has the v.. 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 uses: docker/setup-qemu-action@v2 - name: Set up Docker Buildx uses: docker/setup-buildx-action@v2 - - name: Login to DockerHub + - name: Login to Docker Hub if: github.event_name != 'schedule' uses: docker/login-action@v2 with: username: ${{ secrets.DOCKERHUB_USERNAME }} 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.. 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 id: meta uses: docker/metadata-action@v4 with: 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 flavor: latest=false tags: | type=ref,event=tag - type=semver,pattern=v{{major}}.{{minor}},enable=${{ steps.check-tag-format.outputs.match }} - type=raw,value=latest,enable=${{ github.event_name == 'release' }} + type=semver,pattern=v{{major}}.{{minor}},enable=${{ steps.check-tag-format.outputs.stable == 'true' }} + type=raw,value=latest,enable=${{ steps.check-tag-format.outputs.stable == 'true' }} - name: Build and push id: docker_build diff --git a/Cargo.lock b/Cargo.lock index 1bd47e355..82f83375e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1123,8 +1123,8 @@ dependencies = [ [[package]] name = "filter-parser" -version = "0.29.3" -source = "git+https://github.com/meilisearch/milli.git?tag=v0.29.3#f1d848bb9add86b9414d110a083dfa0462d5d636" +version = "0.31.1" +source = "git+https://github.com/meilisearch/milli.git?tag=v0.31.1#83ad1aaf0552db9f63fc21ae9fe3976e61577dc8" dependencies = [ "nom", "nom_locate", @@ -1148,8 +1148,8 @@ dependencies = [ [[package]] name = "flatten-serde-json" -version = "0.29.3" -source = "git+https://github.com/meilisearch/milli.git?tag=v0.29.3#f1d848bb9add86b9414d110a083dfa0462d5d636" +version = "0.31.1" +source = "git+https://github.com/meilisearch/milli.git?tag=v0.31.1#83ad1aaf0552db9f63fc21ae9fe3976e61577dc8" dependencies = [ "serde_json", ] @@ -1661,8 +1661,8 @@ dependencies = [ [[package]] name = "json-depth-checker" -version = "0.29.3" -source = "git+https://github.com/meilisearch/milli.git?tag=v0.29.3#f1d848bb9add86b9414d110a083dfa0462d5d636" +version = "0.31.1" +source = "git+https://github.com/meilisearch/milli.git?tag=v0.31.1#83ad1aaf0552db9f63fc21ae9fe3976e61577dc8" dependencies = [ "serde_json", ] @@ -2003,7 +2003,6 @@ checksum = "a3e378b66a060d48947b590737b30a1be76706c8dd7b8ba0f2fe3989c68a853f" name = "meilisearch-auth" version = "0.28.0" dependencies = [ - "base64", "enum-iterator", "hmac", "meilisearch-types", @@ -2014,7 +2013,7 @@ dependencies = [ "sha2", "thiserror", "time 0.3.9", - "uuid", + "uuid 1.1.2", ] [[package]] @@ -2069,7 +2068,6 @@ dependencies = [ "serde", "serde-cs", "serde_json", - "serde_url_params", "sha-1", "sha2", "siphasher", @@ -2084,9 +2082,10 @@ dependencies = [ "tokio", "tokio-stream", "urlencoding", - "uuid", + "uuid 1.1.2", "vergen", "walkdir", + "yaup", "zip", ] @@ -2147,7 +2146,7 @@ dependencies = [ "thiserror", "time 0.3.9", "tokio", - "uuid", + "uuid 1.1.2", "walkdir", "whoami", ] @@ -2189,8 +2188,8 @@ dependencies = [ [[package]] name = "milli" -version = "0.29.3" -source = "git+https://github.com/meilisearch/milli.git?tag=v0.29.3#f1d848bb9add86b9414d110a083dfa0462d5d636" +version = "0.31.1" +source = "git+https://github.com/meilisearch/milli.git?tag=v0.31.1#83ad1aaf0552db9f63fc21ae9fe3976e61577dc8" dependencies = [ "bimap", "bincode", @@ -2229,7 +2228,7 @@ dependencies = [ "tempfile", "thiserror", "time 0.3.9", - "uuid", + "uuid 0.8.2", ] [[package]] @@ -2515,7 +2514,7 @@ checksum = "d4fd5641d01c8f18a23da7b6fe29298ff4b55afcccdf78973b24cf3175fee32e" [[package]] name = "permissive-json-pointer" -version = "0.2.0" +version = "0.28.0" dependencies = [ "big_s", "serde_json", @@ -3113,16 +3112,6 @@ dependencies = [ "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]] name = "serde_urlencoded" version = "0.7.1" @@ -3686,6 +3675,15 @@ name = "uuid" version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bc5cf98d8186244414c848017f0e2676b3fcb46807f6668a97dfe67359a3c4b7" +dependencies = [ + "getrandom", +] + +[[package]] +name = "uuid" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd6469f4314d5f1ffec476e05f17cc9a78bc7a27a6a857842170bdf8d6f98d2f" dependencies = [ "getrandom", "serde", @@ -3994,6 +3992,16 @@ version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" 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]] name = "zerocopy" version = "0.3.0" diff --git a/meilisearch-auth/Cargo.toml b/meilisearch-auth/Cargo.toml index ed3a589e2..3ba5408e8 100644 --- a/meilisearch-auth/Cargo.toml +++ b/meilisearch-auth/Cargo.toml @@ -4,15 +4,14 @@ version = "0.28.0" edition = "2021" [dependencies] -base64 = "0.13.0" enum-iterator = "0.7.0" hmac = "0.12.1" 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" serde = { version = "1.0.136", features = ["derive"] } serde_json = { version = "1.0.79", features = ["preserve_order"] } sha2 = "0.10.2" thiserror = "1.0.30" 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"] } diff --git a/meilisearch-auth/src/lib.rs b/meilisearch-auth/src/lib.rs index 81443348a..17f1a3567 100644 --- a/meilisearch-auth/src/lib.rs +++ b/meilisearch-auth/src/lib.rs @@ -18,7 +18,7 @@ pub use action::{actions, Action}; use error::{AuthControllerError, Result}; pub use key::Key; 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; use store::HeedAuthStore; @@ -139,7 +139,7 @@ impl AuthController { pub fn generate_key(&self, uid: Uuid) -> Option { self.master_key .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 diff --git a/meilisearch-auth/src/store.rs b/meilisearch-auth/src/store.rs index f21382068..dda27f537 100644 --- a/meilisearch-auth/src/store.rs +++ b/meilisearch-auth/src/store.rs @@ -14,8 +14,9 @@ use hmac::{Hmac, Mac}; use meilisearch_types::star_or::StarOr; use milli::heed::types::{ByteSlice, DecodeIgnore, SerdeJson}; use milli::heed::{Database, Env, EnvOpenOptions, RwTxn}; -use sha2::{Digest, Sha256}; +use sha2::Sha256; use time::OffsetDateTime; +use uuid::fmt::Hyphenated; use uuid::Uuid; use super::error::Result; @@ -169,13 +170,16 @@ impl HeedAuthStore { .remap_data_type::() .iter(&rtxn)? .filter_map(|res| match res { - Ok((uid, _)) - if generate_key_as_base64(uid, master_key).as_bytes() == encoded_key => - { + Ok((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(); @@ -281,13 +285,17 @@ impl<'a> milli::heed::BytesEncode<'a> for KeyIdActionCodec { } } -pub fn generate_key_as_base64(uid: &[u8], master_key: &[u8]) -> String { - let master_key_sha = Sha256::digest(master_key); - let mut mac = Hmac::::new_from_slice(master_key_sha.as_slice()).unwrap(); - mac.update(uid); +pub fn generate_key_as_hexa(uid: Uuid, master_key: &[u8]) -> String { + // format uid as hyphenated allowing user to generate their own keys. + let mut uid_buffer = [0; Hyphenated::LENGTH]; + let uid = uid.hyphenated().encode_lower(&mut uid_buffer); + + // new_from_slice function never fail. + let mut mac = Hmac::::new_from_slice(master_key).unwrap(); + mac.update(uid.as_bytes()); 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. diff --git a/meilisearch-http/Cargo.toml b/meilisearch-http/Cargo.toml index 763337888..6af4dce48 100644 --- a/meilisearch-http/Cargo.toml +++ b/meilisearch-http/Cargo.toml @@ -75,7 +75,7 @@ thiserror = "1.0.30" time = { version = "0.3.7", features = ["serde-well-known", "formatting", "parsing", "macros"] } tokio = { version = "1.17.0", features = ["full"] } 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" [dev-dependencies] @@ -83,8 +83,8 @@ actix-rt = "2.7.0" assert-json-diff = "2.0.1" manifest-dir-macros = "0.1.14" maplit = "1.0.2" -serde_url_params = "0.2.1" urlencoding = "2.1.0" +yaup = "0.2.0" [features] default = ["analytics", "mini-dashboard"] @@ -105,5 +105,5 @@ mini-dashboard = [ tikv-jemallocator = "0.4.3" [package.metadata.mini-dashboard] -assets-url = "https://github.com/meilisearch/mini-dashboard/releases/download/v0.1.10/build.zip" -sha1 = "1adf96592c267425c110bfefc36b7fc6bfb0f93d" +assets-url = "https://github.com/meilisearch/mini-dashboard/releases/download/v0.2.0/build.zip" +sha1 = "25d1615c608541375a08bd722c3fd3315f926be6" diff --git a/meilisearch-http/src/analytics/segment_analytics.rs b/meilisearch-http/src/analytics/segment_analytics.rs index 1007a242a..b04d814aa 100644 --- a/meilisearch-http/src/analytics/segment_analytics.rs +++ b/meilisearch-http/src/analytics/segment_analytics.rs @@ -574,7 +574,7 @@ impl DocumentsAggregator { let content_type = request .headers() .get(CONTENT_TYPE) - .map(|s| s.to_str().unwrap_or("unknown")) + .and_then(|s| s.to_str().ok()) .unwrap_or("unknown") .to_string(); ret.content_types.insert(content_type); @@ -591,13 +591,13 @@ impl DocumentsAggregator { self.updated |= other.updated; // 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); } - for primary_key in other.primary_keys.into_iter() { + for primary_key in other.primary_keys { 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.index_creation |= other.index_creation; diff --git a/meilisearch-http/src/routes/indexes/search.rs b/meilisearch-http/src/routes/indexes/search.rs index a5d1f1a5c..62bd65e14 100644 --- a/meilisearch-http/src/routes/indexes/search.rs +++ b/meilisearch-http/src/routes/indexes/search.rs @@ -14,7 +14,6 @@ use serde_json::Value; use crate::analytics::{Analytics, SearchAggregator}; use crate::extractors::authentication::{policies::*, GuardedData}; use crate::extractors::sequential_extractor::SeqHandler; -use crate::routes::{fold_star_or, StarOr}; pub fn configure(cfg: &mut web::ServiceConfig) { cfg.service( @@ -30,16 +29,16 @@ pub struct SearchQueryGet { q: Option, offset: Option, limit: Option, - attributes_to_retrieve: Option>>, - attributes_to_crop: Option>>, + attributes_to_retrieve: Option>, + attributes_to_crop: Option>, #[serde(default = "DEFAULT_CROP_LENGTH")] crop_length: usize, - attributes_to_highlight: Option>>, + attributes_to_highlight: Option>, filter: Option, sort: Option, #[serde(default = "Default::default")] show_matches_position: bool, - facets: Option>>, + facets: Option>, #[serde(default = "DEFAULT_HIGHLIGHT_PRE_TAG")] highlight_pre_tag: String, #[serde(default = "DEFAULT_HIGHLIGHT_POST_TAG")] @@ -62,14 +61,18 @@ impl From for SearchQuery { q: other.q, offset: other.offset, limit: other.limit.unwrap_or_else(DEFAULT_SEARCH_LIMIT), - attributes_to_retrieve: other.attributes_to_retrieve.and_then(fold_star_or), - attributes_to_crop: other.attributes_to_crop.and_then(fold_star_or), + attributes_to_retrieve: other + .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, - 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, sort: other.sort.map(|attr| fix_sort_query_parameters(&attr)), 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_post_tag: other.highlight_post_tag, crop_marker: other.crop_marker, diff --git a/meilisearch-http/src/routes/indexes/settings.rs b/meilisearch-http/src/routes/indexes/settings.rs index 962fe7d82..bc8642def 100644 --- a/meilisearch-http/src/routes/indexes/settings.rs +++ b/meilisearch-http/src/routes/indexes/settings.rs @@ -318,7 +318,7 @@ make_setting_route!( "Pagination Updated".to_string(), json!({ "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), @@ -349,7 +349,9 @@ generate_configure!( stop_words, synonyms, ranking_rules, - typo_tolerance + typo_tolerance, + pagination, + faceting ); pub async fn update_all( @@ -409,6 +411,18 @@ pub async fn update_all( .map(|s| s.two_typos.set())) .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), ); diff --git a/meilisearch-http/src/routes/tasks.rs b/meilisearch-http/src/routes/tasks.rs index fed7fa634..016aadfb8 100644 --- a/meilisearch-http/src/routes/tasks.rs +++ b/meilisearch-http/src/routes/tasks.rs @@ -25,7 +25,7 @@ pub fn configure(cfg: &mut web::ServiceConfig) { #[derive(Deserialize, Debug)] #[serde(rename_all = "camelCase", deny_unknown_fields)] -pub struct TaskFilterQuery { +pub struct TasksFilterQuery { #[serde(rename = "type")] type_: Option>>, status: Option>>, @@ -61,17 +61,11 @@ fn task_status_matches_events(status: &TaskStatus, events: &[TaskEvent]) -> bool async fn get_tasks( meilisearch: GuardedData, MeiliSearch>, - params: web::Query, + params: web::Query, req: HttpRequest, analytics: web::Data, ) -> Result { - analytics.publish( - "Tasks Seen".to_string(), - json!({ "per_task_uid": false }), - Some(&req), - ); - - let TaskFilterQuery { + let TasksFilterQuery { type_, status, index_uid, @@ -87,6 +81,16 @@ async fn get_tasks( let status: Option> = status.and_then(fold_star_or); let index_uid: Option> = 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 // restrictions are also applied. let indexes_filters = match index_uid { diff --git a/meilisearch-http/tests/auth/api_keys.rs b/meilisearch-http/tests/auth/api_keys.rs index 9dcbd9b55..7fdf2f129 100644 --- a/meilisearch-http/tests/auth/api_keys.rs +++ b/meilisearch-http/tests/auth/api_keys.rs @@ -42,6 +42,7 @@ async fn add_valid_api_key() { "name": "indexing-key", "description": "Indexing API key", "uid": "4bc0887a-0e41-4f3b-935d-0c451dcee9c8", + "key": "d9e776b8412f1db6974c9a5556b961c3559440b6588216f4ea5d9ed49f7c8f3c", "indexes": ["products"], "actions": [ "search", diff --git a/meilisearch-http/tests/common/index.rs b/meilisearch-http/tests/common/index.rs index 54618442c..90d138ced 100644 --- a/meilisearch-http/tests/common/index.rs +++ b/meilisearch-http/tests/common/index.rs @@ -222,7 +222,7 @@ impl Index<'_> { } 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); self.service.get(url).await } diff --git a/meilisearch-http/tests/dumps/mod.rs b/meilisearch-http/tests/dumps/mod.rs index 851f502a9..389f6b480 100644 --- a/meilisearch-http/tests/dumps/mod.rs +++ b/meilisearch-http/tests/dumps/mod.rs @@ -61,7 +61,7 @@ async fn import_dump_v2_movie_raw() { assert_eq!(code, 200); assert_eq!( 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; @@ -125,7 +125,7 @@ async fn import_dump_v2_movie_with_settings() { assert_eq!(code, 200); assert_eq!( 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; @@ -189,7 +189,7 @@ async fn import_dump_v2_rubygems_with_settings() { assert_eq!(code, 200); assert_eq!( 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; @@ -253,7 +253,7 @@ async fn import_dump_v3_movie_raw() { assert_eq!(code, 200); assert_eq!( 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; @@ -317,7 +317,7 @@ async fn import_dump_v3_movie_with_settings() { assert_eq!(code, 200); assert_eq!( 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; @@ -381,7 +381,7 @@ async fn import_dump_v3_rubygems_with_settings() { assert_eq!(code, 200); assert_eq!( 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; @@ -445,7 +445,7 @@ async fn import_dump_v4_movie_raw() { assert_eq!(code, 200); assert_eq!( 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; @@ -509,7 +509,7 @@ async fn import_dump_v4_movie_with_settings() { assert_eq!(code, 200); assert_eq!( 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; @@ -573,7 +573,7 @@ async fn import_dump_v4_rubygems_with_settings() { assert_eq!(code, 200); assert_eq!( 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; diff --git a/meilisearch-http/tests/search/errors.rs b/meilisearch-http/tests/search/errors.rs index 500825364..98da0495a 100644 --- a/meilisearch-http/tests/search/errors.rs +++ b/meilisearch-http/tests/search/errors.rs @@ -45,26 +45,18 @@ async fn search_invalid_highlight_and_crop_tags() { for field in fields { // object - index - .search( - json!({field.to_string(): {"marker": ""}}), - |response, code| { - assert_eq!(code, 400, "field {} passing object: {}", &field, response); - assert_eq!(response["code"], "bad_request"); - }, - ) + let (response, code) = index + .search_post(json!({field.to_string(): {"marker": ""}})) .await; + assert_eq!(code, 400, "field {} passing object: {}", &field, response); + assert_eq!(response["code"], "bad_request"); // array - index - .search( - json!({field.to_string(): ["marker", ""]}), - |response, code| { - assert_eq!(code, 400, "field {} passing array: {}", &field, response); - assert_eq!(response["code"], "bad_request"); - }, - ) + let (response, code) = index + .search_post(json!({field.to_string(): ["marker", ""]})) .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" }); index - .search(json!({"filter": [["title & Glass"]]}), |response, code| { + .search(json!({"filter": ["title & Glass"]}), |response, code| { assert_eq!(response, expected_response); assert_eq!(code, 400); }) @@ -172,7 +164,7 @@ async fn filter_invalid_attribute_array() { "link": "https://docs.meilisearch.com/errors#invalid_filter" }); index - .search(json!({"filter": [["many = Glass"]]}), |response, code| { + .search(json!({"filter": ["many = Glass"]}), |response, code| { assert_eq!(response, expected_response); assert_eq!(code, 400); }) @@ -226,7 +218,7 @@ async fn filter_reserved_geo_attribute_array() { "link": "https://docs.meilisearch.com/errors#invalid_filter" }); index - .search(json!({"filter": [["_geo = Glass"]]}), |response, code| { + .search(json!({"filter": ["_geo = Glass"]}), |response, code| { assert_eq!(response, expected_response); assert_eq!(code, 400); }) @@ -281,7 +273,7 @@ async fn filter_reserved_attribute_array() { }); index .search( - json!({"filter": [["_geoDistance = Glass"]]}), + json!({"filter": ["_geoDistance = Glass"]}), |response, code| { assert_eq!(response, expected_response); assert_eq!(code, 400); diff --git a/meilisearch-http/tests/search/formatted.rs b/meilisearch-http/tests/search/formatted.rs index 9876bac3a..7303a7154 100644 --- a/meilisearch-http/tests/search/formatted.rs +++ b/meilisearch-http/tests/search/formatted.rs @@ -15,85 +15,100 @@ async fn formatted_contain_wildcard() { index.add_documents(documents, None).await; index.wait_task(1).await; - let (response, code) = index - .search_post(json!({ "q": "pesti", "attributesToRetrieve": ["father", "mother"], "attributesToHighlight": ["father", "mother", "*"], "attributesToCrop": ["doggos"], "showMatchesPosition": true })) - .await; - assert_eq!(code, 200, "{}", response); - assert_eq!( - response["hits"][0], - json!({ - "_formatted": { - "id": "852", - "cattos": "pesti", + index.search(json!({ "q": "pesti", "attributesToRetrieve": ["father", "mother"], "attributesToHighlight": ["father", "mother", "*"], "attributesToCrop": ["doggos"], "showMatchesPosition": true }), + |response, code| + { + assert_eq!(code, 200, "{}", response); + assert_eq!( + response["hits"][0], + json!({ + "_formatted": { + "id": "852", + "cattos": "pesti", + }, + "_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; - assert_eq!(code, 200, "{}", response); - assert_eq!( - response["hits"][0], - json!({ - "id": 852, - "cattos": "pesti", - }) - ); - let (response, code) = index - .search_post( + index + .search( 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; - 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 - .search_post( + index + .search( 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; - assert_eq!(code, 200, "{}", response); - assert_eq!( - response["hits"][0], - json!({ - "id": 852, - "cattos": "pesti", - "_formatted": { - "id": "852", - "cattos": "pesti", - } - }) - ); - let (response, code) = index - .search_post(json!({ "q": "pesti", "attributesToCrop": ["*"] })) + index + .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; - assert_eq!(code, 200, "{}", response); - assert_eq!( - response["hits"][0], - json!({ - "id": 852, - "cattos": "pesti", - "_formatted": { - "id": "852", - "cattos": "pesti", - } - }) - ); } #[actix_rt::test] @@ -105,108 +120,122 @@ async fn format_nested() { index.add_documents(documents, None).await; index.wait_task(0).await; - let (response, code) = index - .search_post(json!({ "q": "pesti", "attributesToRetrieve": ["doggos"] })) - .await; - assert_eq!(code, 200, "{}", response); - assert_eq!( - response["hits"][0], - json!({ - "doggos": [ - { - "name": "bobby", - "age": 2, - }, - { - "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 }), + index + .search( + json!({ "q": "pesti", "attributesToRetrieve": ["doggos"] }), + |response, code| { + assert_eq!(code, 200, "{}", response); + assert_eq!( + response["hits"][0], + json!({ + "doggos": [ + { + "name": "bobby", + "age": 2, + }, + { + "name": "buddy", + "age": 4, + }, + ], + }) + ); + }, ) .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 - .search_post(json!({ "q": "pesti", "attributesToRetrieve": [], "attributesToHighlight": ["doggos.name"] })) - .await; - assert_eq!(code, 200, "{}", response); - assert_eq!( - response["hits"][0], - json!({ - "_formatted": { - "doggos": [ - { - "name": "bobby", - }, - { - "name": "buddy", - }, - ], + index + .search( + json!({ "q": "pesti", "attributesToRetrieve": ["doggos.name"] }), + |response, code| { + assert_eq!(code, 200, "{}", response); + assert_eq!( + response["hits"][0], + json!({ + "doggos": [ + { + "name": "bobby", + }, + { + "name": "buddy", + }, + ], + }) + ); }, - }) - ); - - let (response, code) = index - .search_post(json!({ "q": "pesti", "attributesToRetrieve": [], "attributesToCrop": ["doggos.name"] })) + ) .await; - assert_eq!(code, 200, "{}", response); - assert_eq!( - response["hits"][0], - json!({ - "_formatted": { - "doggos": [ - { - "name": "bobby", - }, - { - "name": "buddy", - }, - ], - }, - }) - ); - let (response, code) = index - .search_post(json!({ "q": "pesti", "attributesToRetrieve": ["doggos.name"], "attributesToHighlight": ["doggos.age"] })) + index + .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; + + 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!( 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; - 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!( response["hits"][0], json!({ @@ -255,6 +286,9 @@ async fn format_nested() { }, }) ); + } + ) + .await; } #[actix_rt::test] @@ -271,9 +305,9 @@ async fn displayedattr_2_smol() { index.add_documents(documents, None).await; index.wait_task(1).await; - let (response, code) = index - .search_post(json!({ "attributesToRetrieve": ["father", "id"], "attributesToHighlight": ["mother"], "attributesToCrop": ["cattos"] })) - .await; + index + .search(json!({ "attributesToRetrieve": ["father", "id"], "attributesToHighlight": ["mother"], "attributesToCrop": ["cattos"] }), + |response, code| { assert_eq!(code, 200, "{}", response); assert_eq!( response["hits"][0], @@ -281,119 +315,157 @@ async fn displayedattr_2_smol() { "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; - assert_eq!(code, 200, "{}", response); - assert_eq!( - response["hits"][0], - json!({ - "id": 852, - "_formatted": { - "id": "852", - } - }) - ); - let (response, code) = index - .search_post(json!({ "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": ["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"] }), + index + .search( + json!({ "attributesToRetrieve": ["id"] }), + |response, code| { + assert_eq!(code, 200, "{}", response); + assert_eq!( + response["hits"][0], + json!({ + "id": 852, + }) + ); + }, ) .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!(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 - .search_post(json!({ "attributesToRetrieve": ["cattos"], "attributesToCrop": ["id"] })) + index + .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; - assert_eq!(code, 200, "{}", response); - assert_eq!( - response["hits"][0], - json!({ - "_formatted": { - "id": "852", - } - }) - ); } diff --git a/meilisearch-http/tests/search/mod.rs b/meilisearch-http/tests/search/mod.rs index 5b23f9d34..17f53fa2d 100644 --- a/meilisearch-http/tests/search/mod.rs +++ b/meilisearch-http/tests/search/mod.rs @@ -567,7 +567,7 @@ async fn placeholder_search_is_hard_limited() { .await; index - .update_settings(json!({ "pagination": { "limitedTo": 10_000 } })) + .update_settings(json!({ "pagination": { "maxTotalHits": 10_000 } })) .await; index.wait_task(1).await; @@ -636,7 +636,7 @@ async fn search_is_hard_limited() { .await; index - .update_settings(json!({ "pagination": { "limitedTo": 10_000 } })) + .update_settings(json!({ "pagination": { "maxTotalHits": 10_000 } })) .await; index.wait_task(1).await; diff --git a/meilisearch-http/tests/settings/get_settings.rs b/meilisearch-http/tests/settings/get_settings.rs index 0862b15c5..9d10b7820 100644 --- a/meilisearch-http/tests/settings/get_settings.rs +++ b/meilisearch-http/tests/settings/get_settings.rs @@ -27,7 +27,13 @@ static DEFAULT_SETTINGS_VALUES: Lazy> = Lazy::new(| map.insert( "faceting", json!({ - "maxValuesByFacet": json!(100), + "maxValuesPerFacet": json!(100), + }), + ); + map.insert( + "pagination", + json!({ + "maxTotalHits": json!(1000), }), ); map @@ -76,7 +82,7 @@ async fn get_settings() { assert_eq!( settings["pagination"], json!({ - "limitedTo": 1000, + "maxTotalHits": 1000, }) ); } @@ -206,7 +212,7 @@ async fn error_update_setting_unexisting_index_invalid_uid() { } macro_rules! test_setting_routes { - ($($setting:ident), *) => { + ($($setting:ident $write_method:ident), *) => { $( mod $setting { use crate::common::Server; @@ -232,7 +238,7 @@ macro_rules! test_setting_routes { .chars() .map(|c| if c == '_' { '-' } else { c }) .collect::()); - 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); server.index("").wait_task(0).await; let (response, code) = server.index("test").get().await; @@ -276,13 +282,15 @@ macro_rules! test_setting_routes { } test_setting_routes!( - filterable_attributes, - displayed_attributes, - searchable_attributes, - distinct_attribute, - stop_words, - ranking_rules, - synonyms + filterable_attributes put, + displayed_attributes put, + searchable_attributes put, + distinct_attribute put, + stop_words put, + ranking_rules put, + synonyms put, + pagination patch, + faceting patch ); #[actix_rt::test] diff --git a/meilisearch-lib/Cargo.toml b/meilisearch-lib/Cargo.toml index be26663b8..6fbcacb94 100644 --- a/meilisearch-lib/Cargo.toml +++ b/meilisearch-lib/Cargo.toml @@ -3,8 +3,6 @@ name = "meilisearch-lib" version = "0.28.0" edition = "2021" -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html - [dependencies] actix-web = { version = "4.0.1", default-features = false } anyhow = { version = "1.0.56", features = ["backtrace"] } @@ -30,7 +28,7 @@ lazy_static = "1.4.0" log = "0.4.14" meilisearch-auth = { path = "../meilisearch-auth" } 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" num_cpus = "1.13.1" obkv = "0.2.0" @@ -53,7 +51,7 @@ tempfile = "3.3.0" thiserror = "1.0.30" time = { version = "0.3.7", features = ["serde-well-known", "formatting", "parsing", "macros"] } 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" whoami = { version = "1.2.1", optional = true } diff --git a/meilisearch-lib/src/index/dump.rs b/meilisearch-lib/src/index/dump.rs index e201e738b..c6feb187f 100644 --- a/meilisearch-lib/src/index/dump.rs +++ b/meilisearch-lib/src/index/dump.rs @@ -27,7 +27,7 @@ const DATA_FILE_NAME: &str = "documents.jsonl"; impl Index { pub fn dump(&self, path: impl AsRef) -> Result<()> { // 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)); create_dir_all(&path)?; diff --git a/meilisearch-lib/src/index/index.rs b/meilisearch-lib/src/index/index.rs index d4772b73b..518e9ce3e 100644 --- a/meilisearch-lib/src/index/index.rs +++ b/meilisearch-lib/src/index/index.rs @@ -4,9 +4,10 @@ use std::marker::PhantomData; use std::ops::Deref; use std::path::Path; use std::sync::Arc; +use walkdir::WalkDir; use fst::IntoStreamer; -use milli::heed::{EnvOpenOptions, RoTxn}; +use milli::heed::{CompactionOption, EnvOpenOptions, RoTxn}; use milli::update::{IndexerConfig, Setting}; use milli::{obkv_to_json, FieldDistribution, DEFAULT_VALUES_PER_FACET}; use serde::{Deserialize, Serialize}; @@ -14,8 +15,7 @@ use serde_json::{Map, Value}; use time::OffsetDateTime; use uuid::Uuid; -use crate::index::search::DEFAULT_PAGINATION_LIMITED_TO; -use crate::EnvSizer; +use crate::index::search::DEFAULT_PAGINATION_MAX_TOTAL_HITS; use super::error::IndexError; use super::error::Result; @@ -202,9 +202,9 @@ impl Index { }; let pagination = PaginationSettings { - limited_to: Setting::Set( - self.pagination_limited_to(txn)? - .unwrap_or(DEFAULT_PAGINATION_LIMITED_TO), + max_total_hits: Setting::Set( + self.pagination_max_total_hits(txn)? + .unwrap_or(DEFAULT_PAGINATION_MAX_TOTAL_HITS), ), }; @@ -245,7 +245,7 @@ impl Index { let fields_ids_map = self.fields_ids_map(&txn)?; 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(); @@ -302,7 +302,12 @@ impl Index { } 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) -> Result<()> { @@ -310,9 +315,7 @@ impl Index { create_dir_all(&dst)?; dst.push("data.mdb"); let _txn = self.write_txn()?; - self.inner - .env - .copy_to_path(dst, milli::heed::CompactionOption::Enabled)?; + self.inner.copy_to_path(dst, CompactionOption::Enabled)?; Ok(()) } } diff --git a/meilisearch-lib/src/index/search.rs b/meilisearch-lib/src/index/search.rs index 781a5bb66..58bcf7ef4 100644 --- a/meilisearch-lib/src/index/search.rs +++ b/meilisearch-lib/src/index/search.rs @@ -29,7 +29,7 @@ pub const DEFAULT_HIGHLIGHT_POST_TAG: fn() -> String = || "".to_string(); /// The maximimum number of results that the engine /// 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)] #[serde(rename_all = "camelCase", deny_unknown_fields)] @@ -91,14 +91,14 @@ impl Index { search.query(query); } - let pagination_limited_to = self - .pagination_limited_to(&rtxn)? - .unwrap_or(DEFAULT_PAGINATION_LIMITED_TO); + let max_total_hits = self + .pagination_max_total_hits(&rtxn)? + .unwrap_or(DEFAULT_PAGINATION_MAX_TOTAL_HITS); // Make sure that a user can't get more documents than the hard limit, // we align that on the offset too. - let offset = min(query.offset.unwrap_or(0), pagination_limited_to); - let limit = min(query.limit, pagination_limited_to.saturating_sub(offset)); + let offset = min(query.offset.unwrap_or(0), max_total_hits); + let limit = min(query.limit, max_total_hits.saturating_sub(offset)); search.offset(offset); search.limit(limit); diff --git a/meilisearch-lib/src/index/updates.rs b/meilisearch-lib/src/index/updates.rs index 95edbbf9d..07695af05 100644 --- a/meilisearch-lib/src/index/updates.rs +++ b/meilisearch-lib/src/index/updates.rs @@ -86,7 +86,7 @@ pub struct FacetingSettings { pub struct PaginationSettings { #[cfg_attr(test, proptest(strategy = "test::setting_strategy()"))] #[serde(default, skip_serializing_if = "Setting::is_not_set")] - pub limited_to: Setting, + pub max_total_hits: Setting, } /// 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 { - Setting::Set(ref value) => match value.limited_to { - Setting::Set(val) => builder.set_pagination_limited_to(val), - Setting::Reset => builder.reset_pagination_limited_to(), + Setting::Set(ref value) => match value.max_total_hits { + Setting::Set(val) => builder.set_pagination_max_total_hits(val), + Setting::Reset => builder.reset_pagination_max_total_hits(), Setting::NotSet => (), }, - Setting::Reset => builder.reset_pagination_limited_to(), + Setting::Reset => builder.reset_pagination_max_total_hits(), Setting::NotSet => (), } } diff --git a/meilisearch-lib/src/index_resolver/meta_store.rs b/meilisearch-lib/src/index_resolver/meta_store.rs index f53f9cae9..f335d9923 100644 --- a/meilisearch-lib/src/index_resolver/meta_store.rs +++ b/meilisearch-lib/src/index_resolver/meta_store.rs @@ -3,6 +3,7 @@ use std::fs::{create_dir_all, File}; use std::io::{BufRead, BufReader, Write}; use std::path::{Path, PathBuf}; use std::sync::Arc; +use walkdir::WalkDir; use milli::heed::types::{SerdeBincode, Str}; use milli::heed::{CompactionOption, Database, Env}; @@ -11,7 +12,6 @@ use uuid::Uuid; use super::error::{IndexResolverError, Result}; use crate::tasks::task::TaskId; -use crate::EnvSizer; #[derive(Serialize, Deserialize)] pub struct DumpEntry { @@ -131,7 +131,12 @@ impl HeedMetaStore { } fn get_size(&self) -> Result { - 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<()> { diff --git a/meilisearch-lib/src/lib.rs b/meilisearch-lib/src/lib.rs index 3d3d5e860..70fd2ba51 100644 --- a/meilisearch-lib/src/lib.rs +++ b/meilisearch-lib/src/lib.rs @@ -20,23 +20,6 @@ pub use milli::heed; mod compression; 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 /// validity of the data in it. /// We consider a database as non empty when it's a non empty directory. diff --git a/meilisearch-lib/src/snapshot.rs b/meilisearch-lib/src/snapshot.rs index 527195729..da4907939 100644 --- a/meilisearch-lib/src/snapshot.rs +++ b/meilisearch-lib/src/snapshot.rs @@ -7,6 +7,7 @@ use anyhow::bail; use fs_extra::dir::{self, CopyOptions}; use log::{info, trace}; use meilisearch_auth::open_auth_store_env; +use milli::heed::CompactionOption; use tokio::sync::RwLock; use tokio::time::sleep; use walkdir::WalkDir; @@ -181,9 +182,7 @@ impl SnapshotJob { let mut options = milli::heed::EnvOpenOptions::new(); options.map_size(self.index_size); let index = milli::Index::new(options, entry.path())?; - index - .env - .copy_to_path(dst, milli::heed::CompactionOption::Enabled)?; + index.copy_to_path(dst, CompactionOption::Enabled)?; } Ok(()) diff --git a/permissive-json-pointer/Cargo.toml b/permissive-json-pointer/Cargo.toml index b50f30f19..9e01b81ab 100644 --- a/permissive-json-pointer/Cargo.toml +++ b/permissive-json-pointer/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "permissive-json-pointer" -version = "0.2.0" +version = "0.28.0" edition = "2021" description = "A permissive json pointer" readme = "README.md"