diff --git a/meilisearch-http/src/routes/indexes/search.rs b/meilisearch-http/src/routes/indexes/search.rs index 828fb40ad..4b5e0dbca 100644 --- a/meilisearch-http/src/routes/indexes/search.rs +++ b/meilisearch-http/src/routes/indexes/search.rs @@ -2,8 +2,9 @@ use actix_web::{web, HttpRequest, HttpResponse}; use log::debug; use meilisearch_auth::IndexSearchRules; use meilisearch_lib::index::{ - SearchQuery, DEFAULT_CROP_LENGTH, DEFAULT_CROP_MARKER, DEFAULT_HIGHLIGHT_POST_TAG, - DEFAULT_HIGHLIGHT_PRE_TAG, DEFAULT_HIT_PER_PAGE, DEFAULT_PAGE, MatchingStrategy + MatchingStrategy, SearchQuery, DEFAULT_CROP_LENGTH, DEFAULT_CROP_MARKER, + DEFAULT_HIGHLIGHT_POST_TAG, DEFAULT_HIGHLIGHT_PRE_TAG, DEFAULT_SEARCH_LIMIT, + DEFAULT_SEARCH_OFFSET, }; use meilisearch_lib::MeiliSearch; use meilisearch_types::error::ResponseError; @@ -27,12 +28,12 @@ pub fn configure(cfg: &mut web::ServiceConfig) { #[serde(rename_all = "camelCase", deny_unknown_fields)] pub struct SearchQueryGet { q: Option, - offset: Option, - limit: Option, - #[serde(default = "DEFAULT_PAGE")] - page: usize, - #[serde(default = "DEFAULT_HIT_PER_PAGE")] - hits_per_page: usize, + #[serde(default = "DEFAULT_SEARCH_OFFSET")] + offset: usize, + #[serde(default = "DEFAULT_SEARCH_LIMIT")] + limit: usize, + page: Option, + hits_per_page: Option, attributes_to_retrieve: Option>, attributes_to_crop: Option>, #[serde(default = "DEFAULT_CROP_LENGTH")] diff --git a/meilisearch-lib/src/index/mod.rs b/meilisearch-lib/src/index/mod.rs index 08f1f42d5..0aeaba14e 100644 --- a/meilisearch-lib/src/index/mod.rs +++ b/meilisearch-lib/src/index/mod.rs @@ -1,7 +1,7 @@ pub use search::{ - HitsInfo, SearchQuery, SearchResult, DEFAULT_CROP_LENGTH, DEFAULT_CROP_MARKER, - DEFAULT_HIGHLIGHT_POST_TAG, DEFAULT_HIGHLIGHT_PRE_TAG, DEFAULT_HIT_PER_PAGE, DEFAULT_PAGE, - DEFAULT_SEARCH_LIMIT, MatchingStrategy + HitsInfo, MatchingStrategy, SearchQuery, SearchResult, DEFAULT_CROP_LENGTH, + DEFAULT_CROP_MARKER, DEFAULT_HIGHLIGHT_POST_TAG, DEFAULT_HIGHLIGHT_PRE_TAG, + DEFAULT_SEARCH_LIMIT, DEFAULT_SEARCH_OFFSET, }; pub use updates::{apply_settings_to_builder, Checked, Facets, Settings, Unchecked}; diff --git a/meilisearch-lib/src/index/search.rs b/meilisearch-lib/src/index/search.rs index 9badd9cb6..ea2fb65d7 100644 --- a/meilisearch-lib/src/index/search.rs +++ b/meilisearch-lib/src/index/search.rs @@ -21,13 +21,12 @@ use super::index::Index; pub type Document = serde_json::Map; type MatchesPosition = BTreeMap>; +pub const DEFAULT_SEARCH_OFFSET: fn() -> usize = || 0; pub const DEFAULT_SEARCH_LIMIT: fn() -> usize = || 20; pub const DEFAULT_CROP_LENGTH: fn() -> usize = || 10; pub const DEFAULT_CROP_MARKER: fn() -> String = || "…".to_string(); pub const DEFAULT_HIGHLIGHT_PRE_TAG: fn() -> String = || "".to_string(); pub const DEFAULT_HIGHLIGHT_POST_TAG: fn() -> String = || "".to_string(); -pub const DEFAULT_PAGE: fn() -> usize = || 1; -pub const DEFAULT_HIT_PER_PAGE: fn() -> usize = || 20; /// The maximum number of results that the engine /// will be able to return in one search call. @@ -37,12 +36,12 @@ pub const DEFAULT_PAGINATION_MAX_TOTAL_HITS: usize = 1000; #[serde(rename_all = "camelCase", deny_unknown_fields)] pub struct SearchQuery { pub q: Option, - pub offset: Option, - pub limit: Option, - #[serde(default = "DEFAULT_PAGE")] - pub page: usize, - #[serde(default = "DEFAULT_HIT_PER_PAGE")] - pub hits_per_page: usize, + #[serde(default = "DEFAULT_SEARCH_OFFSET")] + pub offset: usize, + #[serde(default = "DEFAULT_SEARCH_LIMIT")] + pub limit: usize, + pub page: Option, + pub hits_per_page: Option, pub attributes_to_retrieve: Option>, pub attributes_to_crop: Option>, #[serde(default = "DEFAULT_CROP_LENGTH")] @@ -145,33 +144,26 @@ impl Index { .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 is_finite_pagination = query.offset.is_none() && query.limit.is_none(); + let is_finite_pagination = query.page.or(query.hits_per_page).is_some(); search.exhaustive_number_hits(is_finite_pagination); + // compute the offset on the limit depending on the pagination mode. let (offset, limit) = if is_finite_pagination { - match query.page.checked_sub(1) { - Some(page) => { - let offset = min(query.hits_per_page * page, max_total_hits); - let limit = min(query.hits_per_page, max_total_hits.saturating_sub(offset)); + let limit = query.hits_per_page.unwrap_or_else(DEFAULT_SEARCH_LIMIT); + let page = query.page.unwrap_or(1); - (offset, limit) - } - // page 0 returns 0 hits - None => (0, 0), - } + // page 0 gives a limit of 0 forcing Meilisearch to return no document. + page.checked_sub(1).map_or((0, 0), |p| (limit * p, limit)) } else { - let offset = min(query.offset.unwrap_or(0), max_total_hits); - let limit = min( - query.limit.unwrap_or_else(DEFAULT_SEARCH_LIMIT), - max_total_hits.saturating_sub(offset), - ); - - (offset, limit) + (query.offset, query.limit) }; + // Make sure that a user can't get more documents than the hard limit, + // we align that on the offset too. + let offset = min(offset, max_total_hits); + let limit = min(limit, max_total_hits.saturating_sub(offset)); + search.offset(offset); search.limit(limit); @@ -297,20 +289,21 @@ impl Index { let number_of_hits = min(candidates.len() as usize, max_total_hits); let hits_info = if is_finite_pagination { + let hits_per_page = query.hits_per_page.unwrap_or_else(DEFAULT_SEARCH_LIMIT); // If hit_per_page is 0, then pages can't be computed and so we respond 0. - let total_pages = (number_of_hits + query.hits_per_page.saturating_sub(1)) - .checked_div(query.hits_per_page) + let total_pages = (number_of_hits + hits_per_page.saturating_sub(1)) + .checked_div(hits_per_page) .unwrap_or(0); HitsInfo::Pagination { - hits_per_page: query.hits_per_page, - page: query.page, + hits_per_page: hits_per_page, + page: query.page.unwrap_or(1), total_pages, total_hits: number_of_hits, } } else { HitsInfo::OffsetLimit { - limit: query.limit.unwrap_or_else(DEFAULT_SEARCH_LIMIT), + limit: query.limit, offset, estimated_total_hits: number_of_hits, } diff --git a/meilisearch-lib/src/index_controller/mod.rs b/meilisearch-lib/src/index_controller/mod.rs index b5a1daef6..87644a44a 100644 --- a/meilisearch-lib/src/index_controller/mod.rs +++ b/meilisearch-lib/src/index_controller/mod.rs @@ -691,10 +691,10 @@ mod test { let index_uuid = Uuid::new_v4(); let query = SearchQuery { q: Some(String::from("hello world")), - offset: Some(10), - limit: Some(0), - page: 1, - hits_per_page: 10, + offset: 10, + limit: 0, + page: Some(1), + hits_per_page: Some(10), attributes_to_retrieve: Some(vec!["string".to_owned()].into_iter().collect()), attributes_to_crop: None, crop_length: 18,