3821: Add normalized and detailed scores to documents returned by a query r=dureuill a=dureuill

# Pull Request

## Related issue
Fixes #3771 

## What does this PR do?

### User standpoint

<details>
<summary>Request ranking score</summary>

```
echo '{ 
  "q": "Badman dark knight returns",
  "showRankingScore": true, 
  "limit": 10,
  "attributesToRetrieve": ["title"]
}' | mieli search -i index-word-count-10-count
```

</details>


<details>
<summary>Response</summary>

```json
{
  "hits": [
    {
      "title": "Batman: The Dark Knight Returns, Part 1",
      "_rankingScore": 0.947520325203252
    },
    {
      "title": "Batman: The Dark Knight Returns, Part 2",
      "_rankingScore": 0.947520325203252
    },
    {
      "title": "Batman Unmasked: The Psychology of the Dark Knight",
      "_rankingScore": 0.6657594086021505
    },
    {
      "title": "Legends of the Dark Knight: The History of Batman",
      "_rankingScore": 0.6654905913978495
    },
    {
      "title": "Angel and the Badman",
      "_rankingScore": 0.2196969696969697
    },
    {
      "title": "Angel and the Badman",
      "_rankingScore": 0.2196969696969697
    },
    {
      "title": "Batman",
      "_rankingScore": 0.11553030303030302
    },
    {
      "title": "Batman Begins",
      "_rankingScore": 0.11553030303030302
    },
    {
      "title": "Batman Returns",
      "_rankingScore": 0.11553030303030302
    },
    {
      "title": "Batman Forever",
      "_rankingScore": 0.11553030303030302
    }
  ],
  "query": "Badman dark knight returns",
  "processingTimeMs": 12,
  "limit": 10,
  "offset": 0,
  "estimatedTotalHits": 46
}
```

</details>



- If adding a `showRankingScore` parameter to the search query, then documents returned by a search now contain an additional field `_rankingScore` that is a float bigger than 0 and lower or equal to 1.0. This field represents the relevancy of the document, relatively to the search query and the settings of the index, with 1.0 meaning "perfect match" and 0 meaning "not matching the query" (Meilisearch should never return documents not matching the query at all). 
  - The `sort` and `geosort` ranking rules do not influence the `_rankingScore`.

<details>
<summary>Request detailed ranking scores</summary>

```
echo '{ 
  "q": "Badman dark knight returns",
  "showRankingScoreDetails": true, 
  "limit": 5, 
  "attributesToRetrieve": ["title"]
}' | mieli search -i index-word-count-10-count
```

</details>

<details>
<summary>Response</summary>

```json
{
  "hits": [
    {
      "title": "Batman: The Dark Knight Returns, Part 1",
      "_rankingScoreDetails": {
        "words": {
          "order": 0,
          "matchingWords": 4,
          "maxMatchingWords": 4,
          "score": 1.0
        },
        "typo": {
          "order": 1,
          "typoCount": 1,
          "maxTypoCount": 4,
          "score": 0.8
        },
        "proximity": {
          "order": 2,
          "score": 0.9545454545454546
        },
        "attribute": {
          "order": 3,
          "attributes_ranking_order": 1.0,
          "attributes_query_word_order": 0.926829268292683,
          "score": 0.926829268292683
        },
        "exactness": {
          "order": 4,
          "matchType": "noExactMatch",
          "score": 0.26666666666666666
        }
      }
    },
    {
      "title": "Batman: The Dark Knight Returns, Part 2",
      "_rankingScoreDetails": {
        "words": {
          "order": 0,
          "matchingWords": 4,
          "maxMatchingWords": 4,
          "score": 1.0
        },
        "typo": {
          "order": 1,
          "typoCount": 1,
          "maxTypoCount": 4,
          "score": 0.8
        },
        "proximity": {
          "order": 2,
          "score": 0.9545454545454546
        },
        "attribute": {
          "order": 3,
          "attributes_ranking_order": 1.0,
          "attributes_query_word_order": 0.926829268292683,
          "score": 0.926829268292683
        },
        "exactness": {
          "order": 4,
          "matchType": "noExactMatch",
          "score": 0.26666666666666666
        }
      }
    },
    {
      "title": "Batman Unmasked: The Psychology of the Dark Knight",
      "_rankingScoreDetails": {
        "words": {
          "order": 0,
          "matchingWords": 3,
          "maxMatchingWords": 4,
          "score": 0.75
        },
        "typo": {
          "order": 1,
          "typoCount": 1,
          "maxTypoCount": 3,
          "score": 0.75
        },
        "proximity": {
          "order": 2,
          "score": 0.6666666666666666
        },
        "attribute": {
          "order": 3,
          "attributes_ranking_order": 1.0,
          "attributes_query_word_order": 0.8064516129032258,
          "score": 0.8064516129032258
        },
        "exactness": {
          "order": 4,
          "matchType": "noExactMatch",
          "score": 0.25
        }
      }
    },
    {
      "title": "Legends of the Dark Knight: The History of Batman",
      "_rankingScoreDetails": {
        "words": {
          "order": 0,
          "matchingWords": 3,
          "maxMatchingWords": 4,
          "score": 0.75
        },
        "typo": {
          "order": 1,
          "typoCount": 1,
          "maxTypoCount": 3,
          "score": 0.75
        },
        "proximity": {
          "order": 2,
          "score": 0.6666666666666666
        },
        "attribute": {
          "order": 3,
          "attributes_ranking_order": 1.0,
          "attributes_query_word_order": 0.7419354838709677,
          "score": 0.7419354838709677
        },
        "exactness": {
          "order": 4,
          "matchType": "noExactMatch",
          "score": 0.25
        }
      }
    },
    {
      "title": "Angel and the Badman",
      "_rankingScoreDetails": {
        "words": {
          "order": 0,
          "matchingWords": 1,
          "maxMatchingWords": 4,
          "score": 0.25
        },
        "typo": {
          "order": 1,
          "typoCount": 0,
          "maxTypoCount": 1,
          "score": 1.0
        },
        "proximity": {
          "order": 2,
          "score": 1.0
        },
        "attribute": {
          "order": 3,
          "attributes_ranking_order": 1.0,
          "attributes_query_word_order": 0.8181818181818182,
          "score": 0.8181818181818182
        },
        "exactness": {
          "order": 4,
          "matchType": "noExactMatch",
          "score": 0.3333333333333333
        }
      }
    }
  ],
  "query": "Badman dark knight returns",
  "processingTimeMs": 9,
  "limit": 5,
  "offset": 0,
  "estimatedTotalHits": 46
}
```

</details>

- If adding a `showRankingScoreDetails` parameter to the search query, then the returned documents will now contain an additional `_rankingScoreDetails` field that is a JSON object containing one field per ranking rule that was applied, whose value is a JSON object with the following fields:
  - `order`: a number indicating the order this rule was applied (0 is the first applied ranking rule)
  - `score` (except for `sort` and `geosort`): a float indicating how the document matched this particular rule.
  - other fields that are specific to the rule, indicating for example how many words matched for a document and how many typos were counted in a matching document.
- If the `displayableAttributes` list is defined in the settings of the index, any ranking rule using an attribute **not** part of that list will be marked as `<hidden-rule>` in the `_rankingScoreDetails`.  

- Search queries that are part of a `multi-search` requests are modified in the same way and each of the queries can take the `showRankingScore` and `showRankingScoreDetails` parameters independently. The results are still returned in separate lists and providing a unified list of results between multiple queries is not in the scope of this PR (but is unblocked by this PR and can be done manually by using the scores of the various documents). 

### Implementation standpoint

- Fix difference in how the position of terms were computed at indexing time and query time: this difference meant that a query containing a hard separator would fail the exactness check.
- Fix the id reported by the sort ranking rule (very minor)
- Change how the cost of removing words is computed. After this change the cost no longer works for any other ranking rule than `words`. Also made `words` have a cost of 0 such that the entire cost of `words` is given by the termRemovalStrategy. The new cost computation makes it so the score is computed in a way consistent with the number of words in the query. Additionally, the words that appear in phrases in the query are also counted as matching words.
- When any score computation is requested through `showRankingScore` or `showRankingScoreDetails`, remove optimization where ranking rules are not executed on buckets of a single document: this is important to allow the computation of an accurate score.
- add virtual conditions to fid and position to always have the max cost: this ensures that the score is independent from the dataset
- the Position ranking rule now takes into account the distance to the position of the word in the query instead of the distance to the position 0.
- modified proximity ranking rule cost calculation so that the cost is 0 for documents that are perfectly matching the query
- Add a new `milli::score_details` module containing all the types that are involved in score computation.
- Make it so a bucket of result now contains a `ScoreDetails` and changed the ranking rules to produce their `ScoreDetails`.
- Expose the scores in the REST API.
- Add very light analytics for scoring.
- Update the search tests to add the expected scores.

Co-authored-by: Louis Dureuil <louis@meilisearch.com>
This commit is contained in:
meili-bors[bot] 2023-06-26 09:32:43 +00:00 committed by GitHub
commit 2d34005965
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
110 changed files with 10695 additions and 476 deletions

View File

@ -240,6 +240,8 @@ InvalidSearchOffset , InvalidRequest , BAD_REQUEST ;
InvalidSearchPage , InvalidRequest , BAD_REQUEST ; InvalidSearchPage , InvalidRequest , BAD_REQUEST ;
InvalidSearchQ , InvalidRequest , BAD_REQUEST ; InvalidSearchQ , InvalidRequest , BAD_REQUEST ;
InvalidSearchShowMatchesPosition , InvalidRequest , BAD_REQUEST ; InvalidSearchShowMatchesPosition , InvalidRequest , BAD_REQUEST ;
InvalidSearchShowRankingScore , InvalidRequest , BAD_REQUEST ;
InvalidSearchShowRankingScoreDetails , InvalidRequest , BAD_REQUEST ;
InvalidSearchSort , InvalidRequest , BAD_REQUEST ; InvalidSearchSort , InvalidRequest , BAD_REQUEST ;
InvalidSettingsDisplayedAttributes , InvalidRequest , BAD_REQUEST ; InvalidSettingsDisplayedAttributes , InvalidRequest , BAD_REQUEST ;
InvalidSettingsDistinctAttribute , InvalidRequest , BAD_REQUEST ; InvalidSettingsDistinctAttribute , InvalidRequest , BAD_REQUEST ;

View File

@ -569,6 +569,10 @@ pub struct SearchAggregator {
// facets // facets
facets_sum_of_terms: usize, facets_sum_of_terms: usize,
facets_total_number_of_facets: usize, facets_total_number_of_facets: usize,
// scoring
show_ranking_score: bool,
show_ranking_score_details: bool,
} }
impl SearchAggregator { impl SearchAggregator {
@ -632,6 +636,9 @@ impl SearchAggregator {
ret.crop_length = query.crop_length != DEFAULT_CROP_LENGTH(); ret.crop_length = query.crop_length != DEFAULT_CROP_LENGTH();
ret.show_matches_position = query.show_matches_position; ret.show_matches_position = query.show_matches_position;
ret.show_ranking_score = query.show_ranking_score;
ret.show_ranking_score_details = query.show_ranking_score_details;
ret ret
} }
@ -706,6 +713,10 @@ impl SearchAggregator {
let matching_strategy = self.matching_strategy.entry(key).or_insert(0); let matching_strategy = self.matching_strategy.entry(key).or_insert(0);
*matching_strategy = matching_strategy.saturating_add(value); *matching_strategy = matching_strategy.saturating_add(value);
} }
// scoring
self.show_ranking_score |= other.show_ranking_score;
self.show_ranking_score_details |= other.show_ranking_score_details;
} }
pub fn into_event(self, user: &User, event_name: &str) -> Option<Track> { pub fn into_event(self, user: &User, event_name: &str) -> Option<Track> {
@ -760,7 +771,11 @@ impl SearchAggregator {
}, },
"matching_strategy": { "matching_strategy": {
"most_used_strategy": self.matching_strategy.iter().max_by_key(|(_, v)| *v).map(|(k, _)| json!(k)).unwrap_or_else(|| json!(null)), "most_used_strategy": self.matching_strategy.iter().max_by_key(|(_, v)| *v).map(|(k, _)| json!(k)).unwrap_or_else(|| json!(null)),
} },
"scoring": {
"show_ranking_score": self.show_ranking_score,
"show_ranking_score_details": self.show_ranking_score_details,
},
}); });
Some(Track { Some(Track {

View File

@ -56,6 +56,10 @@ pub struct SearchQueryGet {
sort: Option<String>, sort: Option<String>,
#[deserr(default, error = DeserrQueryParamError<InvalidSearchShowMatchesPosition>)] #[deserr(default, error = DeserrQueryParamError<InvalidSearchShowMatchesPosition>)]
show_matches_position: Param<bool>, show_matches_position: Param<bool>,
#[deserr(default, error = DeserrQueryParamError<InvalidSearchShowRankingScore>)]
show_ranking_score: Param<bool>,
#[deserr(default, error = DeserrQueryParamError<InvalidSearchShowRankingScoreDetails>)]
show_ranking_score_details: Param<bool>,
#[deserr(default, error = DeserrQueryParamError<InvalidSearchFacets>)] #[deserr(default, error = DeserrQueryParamError<InvalidSearchFacets>)]
facets: Option<CS<String>>, facets: Option<CS<String>>,
#[deserr( default = DEFAULT_HIGHLIGHT_PRE_TAG(), error = DeserrQueryParamError<InvalidSearchHighlightPreTag>)] #[deserr( default = DEFAULT_HIGHLIGHT_PRE_TAG(), error = DeserrQueryParamError<InvalidSearchHighlightPreTag>)]
@ -91,6 +95,8 @@ impl From<SearchQueryGet> for SearchQuery {
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.0, show_matches_position: other.show_matches_position.0,
show_ranking_score: other.show_ranking_score.0,
show_ranking_score_details: other.show_ranking_score_details.0,
facets: other.facets.map(|o| o.into_iter().collect()), 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,

View File

@ -9,6 +9,7 @@ use meilisearch_auth::IndexSearchRules;
use meilisearch_types::deserr::DeserrJsonError; use meilisearch_types::deserr::DeserrJsonError;
use meilisearch_types::error::deserr_codes::*; use meilisearch_types::error::deserr_codes::*;
use meilisearch_types::index_uid::IndexUid; use meilisearch_types::index_uid::IndexUid;
use meilisearch_types::milli::score_details::{ScoreDetails, ScoringStrategy};
use meilisearch_types::settings::DEFAULT_PAGINATION_MAX_TOTAL_HITS; use meilisearch_types::settings::DEFAULT_PAGINATION_MAX_TOTAL_HITS;
use meilisearch_types::{milli, Document}; use meilisearch_types::{milli, Document};
use milli::tokenizer::TokenizerBuilder; use milli::tokenizer::TokenizerBuilder;
@ -54,6 +55,10 @@ pub struct SearchQuery {
pub attributes_to_highlight: Option<HashSet<String>>, pub attributes_to_highlight: Option<HashSet<String>>,
#[deserr(default, error = DeserrJsonError<InvalidSearchShowMatchesPosition>, default)] #[deserr(default, error = DeserrJsonError<InvalidSearchShowMatchesPosition>, default)]
pub show_matches_position: bool, pub show_matches_position: bool,
#[deserr(default, error = DeserrJsonError<InvalidSearchShowRankingScore>, default)]
pub show_ranking_score: bool,
#[deserr(default, error = DeserrJsonError<InvalidSearchShowRankingScoreDetails>, default)]
pub show_ranking_score_details: bool,
#[deserr(default, error = DeserrJsonError<InvalidSearchFilter>)] #[deserr(default, error = DeserrJsonError<InvalidSearchFilter>)]
pub filter: Option<Value>, pub filter: Option<Value>,
#[deserr(default, error = DeserrJsonError<InvalidSearchSort>)] #[deserr(default, error = DeserrJsonError<InvalidSearchSort>)]
@ -103,6 +108,10 @@ pub struct SearchQueryWithIndex {
pub crop_length: usize, pub crop_length: usize,
#[deserr(default, error = DeserrJsonError<InvalidSearchAttributesToHighlight>)] #[deserr(default, error = DeserrJsonError<InvalidSearchAttributesToHighlight>)]
pub attributes_to_highlight: Option<HashSet<String>>, pub attributes_to_highlight: Option<HashSet<String>>,
#[deserr(default, error = DeserrJsonError<InvalidSearchShowRankingScore>, default)]
pub show_ranking_score: bool,
#[deserr(default, error = DeserrJsonError<InvalidSearchShowRankingScoreDetails>, default)]
pub show_ranking_score_details: bool,
#[deserr(default, error = DeserrJsonError<InvalidSearchShowMatchesPosition>, default)] #[deserr(default, error = DeserrJsonError<InvalidSearchShowMatchesPosition>, default)]
pub show_matches_position: bool, pub show_matches_position: bool,
#[deserr(default, error = DeserrJsonError<InvalidSearchFilter>)] #[deserr(default, error = DeserrJsonError<InvalidSearchFilter>)]
@ -134,6 +143,8 @@ impl SearchQueryWithIndex {
attributes_to_crop, attributes_to_crop,
crop_length, crop_length,
attributes_to_highlight, attributes_to_highlight,
show_ranking_score,
show_ranking_score_details,
show_matches_position, show_matches_position,
filter, filter,
sort, sort,
@ -155,6 +166,8 @@ impl SearchQueryWithIndex {
attributes_to_crop, attributes_to_crop,
crop_length, crop_length,
attributes_to_highlight, attributes_to_highlight,
show_ranking_score,
show_ranking_score_details,
show_matches_position, show_matches_position,
filter, filter,
sort, sort,
@ -194,7 +207,7 @@ impl From<MatchingStrategy> for TermsMatchingStrategy {
} }
} }
#[derive(Debug, Clone, Serialize, PartialEq, Eq)] #[derive(Debug, Clone, Serialize, PartialEq)]
pub struct SearchHit { pub struct SearchHit {
#[serde(flatten)] #[serde(flatten)]
pub document: Document, pub document: Document,
@ -202,6 +215,10 @@ pub struct SearchHit {
pub formatted: Document, pub formatted: Document,
#[serde(rename = "_matchesPosition", skip_serializing_if = "Option::is_none")] #[serde(rename = "_matchesPosition", skip_serializing_if = "Option::is_none")]
pub matches_position: Option<MatchesPosition>, pub matches_position: Option<MatchesPosition>,
#[serde(rename = "_rankingScore", skip_serializing_if = "Option::is_none")]
pub ranking_score: Option<f64>,
#[serde(rename = "_rankingScoreDetails", skip_serializing_if = "Option::is_none")]
pub ranking_score_details: Option<serde_json::Map<String, serde_json::Value>>,
} }
#[derive(Serialize, Debug, Clone, PartialEq)] #[derive(Serialize, Debug, Clone, PartialEq)]
@ -283,6 +300,11 @@ pub fn perform_search(
.unwrap_or(DEFAULT_PAGINATION_MAX_TOTAL_HITS); .unwrap_or(DEFAULT_PAGINATION_MAX_TOTAL_HITS);
search.exhaustive_number_hits(is_finite_pagination); search.exhaustive_number_hits(is_finite_pagination);
search.scoring_strategy(if query.show_ranking_score || query.show_ranking_score_details {
ScoringStrategy::Detailed
} else {
ScoringStrategy::Skip
});
// compute the offset on the limit depending on the pagination mode. // compute the offset on the limit depending on the pagination mode.
let (offset, limit) = if is_finite_pagination { let (offset, limit) = if is_finite_pagination {
@ -320,7 +342,8 @@ pub fn perform_search(
search.sort_criteria(sort); search.sort_criteria(sort);
} }
let milli::SearchResult { documents_ids, matching_words, candidates, .. } = search.execute()?; let milli::SearchResult { documents_ids, matching_words, candidates, document_scores, .. } =
search.execute()?;
let fields_ids_map = index.fields_ids_map(&rtxn).unwrap(); let fields_ids_map = index.fields_ids_map(&rtxn).unwrap();
@ -392,7 +415,7 @@ pub fn perform_search(
let documents_iter = index.documents(&rtxn, documents_ids)?; let documents_iter = index.documents(&rtxn, documents_ids)?;
for (_id, obkv) in documents_iter { for ((_id, obkv), score) in documents_iter.into_iter().zip(document_scores.into_iter()) {
// First generate a document with all the displayed fields // First generate a document with all the displayed fields
let displayed_document = make_document(&displayed_ids, &fields_ids_map, obkv)?; let displayed_document = make_document(&displayed_ids, &fields_ids_map, obkv)?;
@ -416,7 +439,18 @@ pub fn perform_search(
insert_geo_distance(sort, &mut document); insert_geo_distance(sort, &mut document);
} }
let hit = SearchHit { document, formatted, matches_position }; let ranking_score =
query.show_ranking_score.then(|| ScoreDetails::global_score(score.iter()));
let ranking_score_details =
query.show_ranking_score_details.then(|| ScoreDetails::to_json_map(score.iter()));
let hit = SearchHit {
document,
formatted,
matches_position,
ranking_score_details,
ranking_score,
};
documents.push(hit); documents.push(hit);
} }

View File

@ -1,3 +1,4 @@
use insta::{allow_duplicates, assert_json_snapshot};
use serde_json::json; use serde_json::json;
use super::*; use super::*;
@ -18,30 +19,43 @@ async fn formatted_contain_wildcard() {
|response, code| |response, code|
{ {
assert_eq!(code, 200, "{}", response); assert_eq!(code, 200, "{}", response);
assert_eq!( allow_duplicates! {
response["hits"][0], assert_json_snapshot!(response["hits"][0],
json!({ { "._rankingScore" => "[score]" },
"_formatted": { @r###"
"id": "852", {
"cattos": "<em>pésti</em>", "_formatted": {
}, "id": "852",
"_matchesPosition": {"cattos": [{"start": 0, "length": 5}]}, "cattos": "<em>pésti</em>"
}) },
); "_matchesPosition": {
} "cattos": [
{
"start": 0,
"length": 5
}
]
}
}
"###);
}
}
) )
.await; .await;
index index
.search(json!({ "q": "pésti", "attributesToRetrieve": ["*"] }), |response, code| { .search(json!({ "q": "pésti", "attributesToRetrieve": ["*"] }), |response, code| {
assert_eq!(code, 200, "{}", response); assert_eq!(code, 200, "{}", response);
assert_eq!( allow_duplicates! {
response["hits"][0], assert_json_snapshot!(response["hits"][0],
json!({ { "._rankingScore" => "[score]" },
"id": 852, @r###"
"cattos": "pésti", {
}) "id": 852,
); "cattos": "pésti"
}
"###)
}
}) })
.await; .await;
@ -50,20 +64,29 @@ async fn formatted_contain_wildcard() {
json!({ "q": "pésti", "attributesToRetrieve": ["*"], "attributesToHighlight": ["id"], "showMatchesPosition": true }), json!({ "q": "pésti", "attributesToRetrieve": ["*"], "attributesToHighlight": ["id"], "showMatchesPosition": true }),
|response, code| { |response, code| {
assert_eq!(code, 200, "{}", response); assert_eq!(code, 200, "{}", response);
assert_eq!( allow_duplicates! {
response["hits"][0], assert_json_snapshot!(response["hits"][0],
json!({ { "._rankingScore" => "[score]" },
"id": 852, @r###"
"cattos": "pésti", {
"_formatted": { "id": 852,
"id": "852", "cattos": "pésti",
"cattos": "pésti", "_formatted": {
}, "id": "852",
"_matchesPosition": {"cattos": [{"start": 0, "length": 5}]}, "cattos": "pésti"
}) },
); "_matchesPosition": {
} "cattos": [
) {
"start": 0,
"length": 5
}
]
}
}
"###)
}
})
.await; .await;
index index
@ -71,17 +94,20 @@ async fn formatted_contain_wildcard() {
json!({ "q": "pésti", "attributesToRetrieve": ["*"], "attributesToCrop": ["*"] }), json!({ "q": "pésti", "attributesToRetrieve": ["*"], "attributesToCrop": ["*"] }),
|response, code| { |response, code| {
assert_eq!(code, 200, "{}", response); assert_eq!(code, 200, "{}", response);
assert_eq!( allow_duplicates! {
response["hits"][0], assert_json_snapshot!(response["hits"][0],
json!({ { "._rankingScore" => "[score]" },
"id": 852, @r###"
"cattos": "pésti", {
"_formatted": { "id": 852,
"id": "852", "cattos": "pésti",
"cattos": "pésti", "_formatted": {
} "id": "852",
}) "cattos": "pésti"
); }
}
"###);
}
}, },
) )
.await; .await;
@ -89,17 +115,20 @@ async fn formatted_contain_wildcard() {
index index
.search(json!({ "q": "pésti", "attributesToCrop": ["*"] }), |response, code| { .search(json!({ "q": "pésti", "attributesToCrop": ["*"] }), |response, code| {
assert_eq!(code, 200, "{}", response); assert_eq!(code, 200, "{}", response);
assert_eq!( allow_duplicates! {
response["hits"][0], assert_json_snapshot!(response["hits"][0],
json!({ { "._rankingScore" => "[score]" },
"id": 852, @r###"
"cattos": "pésti", {
"_formatted": { "id": 852,
"id": "852", "cattos": "pésti",
"cattos": "pésti", "_formatted": {
} "id": "852",
}) "cattos": "pésti"
); }
}
"###)
}
}) })
.await; .await;
} }
@ -116,21 +145,24 @@ async fn format_nested() {
index index
.search(json!({ "q": "pésti", "attributesToRetrieve": ["doggos"] }), |response, code| { .search(json!({ "q": "pésti", "attributesToRetrieve": ["doggos"] }), |response, code| {
assert_eq!(code, 200, "{}", response); assert_eq!(code, 200, "{}", response);
assert_eq!( allow_duplicates! {
response["hits"][0], assert_json_snapshot!(response["hits"][0],
json!({ { "._rankingScore" => "[score]" },
"doggos": [ @r###"
{ {
"name": "bobby", "doggos": [
"age": 2, {
}, "name": "bobby",
{ "age": 2
"name": "buddy", },
"age": 4, {
}, "name": "buddy",
], "age": 4
}) }
); ]
}
"###)
}
}) })
.await; .await;
@ -139,19 +171,22 @@ async fn format_nested() {
json!({ "q": "pésti", "attributesToRetrieve": ["doggos.name"] }), json!({ "q": "pésti", "attributesToRetrieve": ["doggos.name"] }),
|response, code| { |response, code| {
assert_eq!(code, 200, "{}", response); assert_eq!(code, 200, "{}", response);
assert_eq!( allow_duplicates! {
response["hits"][0], assert_json_snapshot!(response["hits"][0],
json!({ { "._rankingScore" => "[score]" },
"doggos": [ @r###"
{ {
"name": "bobby", "doggos": [
}, {
{ "name": "bobby"
"name": "buddy", },
}, {
], "name": "buddy"
}) }
); ]
}
"###)
}
}, },
) )
.await; .await;
@ -161,20 +196,30 @@ async fn format_nested() {
json!({ "q": "bobby", "attributesToRetrieve": ["doggos.name"], "showMatchesPosition": true }), json!({ "q": "bobby", "attributesToRetrieve": ["doggos.name"], "showMatchesPosition": true }),
|response, code| { |response, code| {
assert_eq!(code, 200, "{}", response); assert_eq!(code, 200, "{}", response);
assert_eq!( allow_duplicates! {
response["hits"][0], assert_json_snapshot!(response["hits"][0],
json!({ { "._rankingScore" => "[score]" },
"doggos": [ @r###"
{ {
"name": "bobby", "doggos": [
}, {
{ "name": "bobby"
"name": "buddy", },
}, {
], "name": "buddy"
"_matchesPosition": {"doggos.name": [{"start": 0, "length": 5}]}, }
}) ],
); "_matchesPosition": {
"doggos.name": [
{
"start": 0,
"length": 5
}
]
}
}
"###)
}
} }
) )
.await; .await;
@ -183,21 +228,24 @@ async fn format_nested() {
.search(json!({ "q": "pésti", "attributesToRetrieve": [], "attributesToHighlight": ["doggos.name"] }), .search(json!({ "q": "pésti", "attributesToRetrieve": [], "attributesToHighlight": ["doggos.name"] }),
|response, code| { |response, code| {
assert_eq!(code, 200, "{}", response); assert_eq!(code, 200, "{}", response);
assert_eq!( allow_duplicates! {
response["hits"][0], assert_json_snapshot!(response["hits"][0],
json!({ { "._rankingScore" => "[score]" },
"_formatted": { @r###"
"doggos": [ {
{ "_formatted": {
"name": "bobby", "doggos": [
}, {
{ "name": "bobby"
"name": "buddy", },
}, {
], "name": "buddy"
}, }
}) ]
); }
}
"###)
}
}) })
.await; .await;
@ -205,21 +253,24 @@ async fn format_nested() {
.search(json!({ "q": "pésti", "attributesToRetrieve": [], "attributesToCrop": ["doggos.name"] }), .search(json!({ "q": "pésti", "attributesToRetrieve": [], "attributesToCrop": ["doggos.name"] }),
|response, code| { |response, code| {
assert_eq!(code, 200, "{}", response); assert_eq!(code, 200, "{}", response);
assert_eq!( allow_duplicates! {
response["hits"][0], assert_json_snapshot!(response["hits"][0],
json!({ { "._rankingScore" => "[score]" },
"_formatted": { @r###"
"doggos": [ {
{ "_formatted": {
"name": "bobby", "doggos": [
}, {
{ "name": "bobby"
"name": "buddy", },
}, {
], "name": "buddy"
}, }
}) ]
); }
}
"###)
}
}) })
.await; .await;
@ -227,55 +278,61 @@ async fn format_nested() {
.search(json!({ "q": "pésti", "attributesToRetrieve": ["doggos.name"], "attributesToHighlight": ["doggos.age"] }), .search(json!({ "q": "pésti", "attributesToRetrieve": ["doggos.name"], "attributesToHighlight": ["doggos.age"] }),
|response, code| { |response, code| {
assert_eq!(code, 200, "{}", response); assert_eq!(code, 200, "{}", response);
assert_eq!( allow_duplicates! {
response["hits"][0], assert_json_snapshot!(response["hits"][0],
json!({ { "._rankingScore" => "[score]" },
"doggos": [ @r###"
{ {
"name": "bobby", "doggos": [
}, {
{ "name": "bobby"
"name": "buddy",
},
],
"_formatted": {
"doggos": [
{
"name": "bobby",
"age": "2",
},
{
"name": "buddy",
"age": "4",
},
],
}, },
}) {
); "name": "buddy"
}) }
],
"_formatted": {
"doggos": [
{
"name": "bobby",
"age": "2"
},
{
"name": "buddy",
"age": "4"
}
]
}
}
"###)
}
})
.await; .await;
index index
.search(json!({ "q": "pésti", "attributesToRetrieve": [], "attributesToHighlight": ["doggos.age"], "attributesToCrop": ["doggos.name"] }), .search(json!({ "q": "pésti", "attributesToRetrieve": [], "attributesToHighlight": ["doggos.age"], "attributesToCrop": ["doggos.name"] }),
|response, code| { |response, code| {
assert_eq!(code, 200, "{}", response); assert_eq!(code, 200, "{}", response);
assert_eq!( allow_duplicates! {
response["hits"][0], assert_json_snapshot!(response["hits"][0],
json!({ { "._rankingScore" => "[score]" },
"_formatted": { @r###"
"doggos": [
{ {
"name": "bobby", "_formatted": {
"age": "2", "doggos": [
}, {
{ "name": "bobby",
"name": "buddy", "age": "2"
"age": "4", },
}, {
], "name": "buddy",
}, "age": "4"
}) }
); ]
}
}
"###)
}
} }
) )
.await; .await;
@ -297,54 +354,66 @@ async fn displayedattr_2_smol() {
.search(json!({ "attributesToRetrieve": ["father", "id"], "attributesToHighlight": ["mother"], "attributesToCrop": ["cattos"] }), .search(json!({ "attributesToRetrieve": ["father", "id"], "attributesToHighlight": ["mother"], "attributesToCrop": ["cattos"] }),
|response, code| { |response, code| {
assert_eq!(code, 200, "{}", response); assert_eq!(code, 200, "{}", response);
assert_eq!( allow_duplicates! {
response["hits"][0], assert_json_snapshot!(response["hits"][0],
json!({ { "._rankingScore" => "[score]" },
"id": 852, @r###"
}) {
); "id": 852
}
"###)
}
}) })
.await; .await;
index index
.search(json!({ "attributesToRetrieve": ["id"] }), |response, code| { .search(json!({ "attributesToRetrieve": ["id"] }), |response, code| {
assert_eq!(code, 200, "{}", response); assert_eq!(code, 200, "{}", response);
assert_eq!( allow_duplicates! {
response["hits"][0], assert_json_snapshot!(response["hits"][0],
json!({ { "._rankingScore" => "[score]" },
"id": 852, @r###"
}) {
); "id": 852
}
"###)
}
}) })
.await; .await;
index index
.search(json!({ "attributesToHighlight": ["id"] }), |response, code| { .search(json!({ "attributesToHighlight": ["id"] }), |response, code| {
assert_eq!(code, 200, "{}", response); assert_eq!(code, 200, "{}", response);
assert_eq!( allow_duplicates! {
response["hits"][0], assert_json_snapshot!(response["hits"][0],
json!({ { "._rankingScore" => "[score]" },
"id": 852, @r###"
"_formatted": { {
"id": "852", "id": 852,
} "_formatted": {
}) "id": "852"
); }
}
"###)
}
}) })
.await; .await;
index index
.search(json!({ "attributesToCrop": ["id"] }), |response, code| { .search(json!({ "attributesToCrop": ["id"] }), |response, code| {
assert_eq!(code, 200, "{}", response); assert_eq!(code, 200, "{}", response);
assert_eq!( allow_duplicates! {
response["hits"][0], assert_json_snapshot!(response["hits"][0],
json!({ { "._rankingScore" => "[score]" },
"id": 852, @r###"
"_formatted": { {
"id": "852", "id": 852,
} "_formatted": {
}) "id": "852"
); }
}
"###)
}
}) })
.await; .await;
@ -353,15 +422,18 @@ async fn displayedattr_2_smol() {
json!({ "attributesToHighlight": ["id"], "attributesToCrop": ["id"] }), json!({ "attributesToHighlight": ["id"], "attributesToCrop": ["id"] }),
|response, code| { |response, code| {
assert_eq!(code, 200, "{}", response); assert_eq!(code, 200, "{}", response);
assert_eq!( allow_duplicates! {
response["hits"][0], assert_json_snapshot!(response["hits"][0],
json!({ { "._rankingScore" => "[score]" },
"id": 852, @r###"
"_formatted": { {
"id": "852", "id": 852,
} "_formatted": {
}) "id": "852"
); }
}
"###)
}
}, },
) )
.await; .await;
@ -369,31 +441,41 @@ async fn displayedattr_2_smol() {
index index
.search(json!({ "attributesToHighlight": ["cattos"] }), |response, code| { .search(json!({ "attributesToHighlight": ["cattos"] }), |response, code| {
assert_eq!(code, 200, "{}", response); assert_eq!(code, 200, "{}", response);
assert_eq!( allow_duplicates! {
response["hits"][0], assert_json_snapshot!(response["hits"][0],
json!({ { "._rankingScore" => "[score]" },
"id": 852, @r###"
}) {
); "id": 852
}
"###)
}
}) })
.await; .await;
index index
.search(json!({ "attributesToCrop": ["cattos"] }), |response, code| { .search(json!({ "attributesToCrop": ["cattos"] }), |response, code| {
assert_eq!(code, 200, "{}", response); assert_eq!(code, 200, "{}", response);
assert_eq!( allow_duplicates! {
response["hits"][0], assert_json_snapshot!(response["hits"][0],
json!({ { "._rankingScore" => "[score]" },
"id": 852, @r###"
}) {
); "id": 852
}
"###)
}
}) })
.await; .await;
index index
.search(json!({ "attributesToRetrieve": ["cattos"] }), |response, code| { .search(json!({ "attributesToRetrieve": ["cattos"] }), |response, code| {
assert_eq!(code, 200, "{}", response); assert_eq!(code, 200, "{}", response);
assert_eq!(response["hits"][0], json!({})); allow_duplicates! {
assert_json_snapshot!(response["hits"][0],
{ "._rankingScore" => "[score]" },
@"{}")
}
}) })
.await; .await;
@ -402,7 +484,11 @@ async fn displayedattr_2_smol() {
json!({ "attributesToRetrieve": ["cattos"], "attributesToHighlight": ["cattos"], "attributesToCrop": ["cattos"] }), json!({ "attributesToRetrieve": ["cattos"], "attributesToHighlight": ["cattos"], "attributesToCrop": ["cattos"] }),
|response, code| { |response, code| {
assert_eq!(code, 200, "{}", response); assert_eq!(code, 200, "{}", response);
assert_eq!(response["hits"][0], json!({})); allow_duplicates! {
assert_json_snapshot!(response["hits"][0],
{ "._rankingScore" => "[score]" },
@"{}")
}
} }
) )
@ -413,14 +499,17 @@ async fn displayedattr_2_smol() {
json!({ "attributesToRetrieve": ["cattos"], "attributesToHighlight": ["id"] }), json!({ "attributesToRetrieve": ["cattos"], "attributesToHighlight": ["id"] }),
|response, code| { |response, code| {
assert_eq!(code, 200, "{}", response); assert_eq!(code, 200, "{}", response);
assert_eq!( allow_duplicates! {
response["hits"][0], assert_json_snapshot!(response["hits"][0],
json!({ { "._rankingScore" => "[score]" },
"_formatted": { @r###"
"id": "852", {
} "_formatted": {
}) "id": "852"
); }
}
"###)
}
}, },
) )
.await; .await;
@ -430,14 +519,17 @@ async fn displayedattr_2_smol() {
json!({ "attributesToRetrieve": ["cattos"], "attributesToCrop": ["id"] }), json!({ "attributesToRetrieve": ["cattos"], "attributesToCrop": ["id"] }),
|response, code| { |response, code| {
assert_eq!(code, 200, "{}", response); assert_eq!(code, 200, "{}", response);
assert_eq!( allow_duplicates! {
response["hits"][0], assert_json_snapshot!(response["hits"][0],
json!({ { "._rankingScore" => "[score]" },
"_formatted": { @r###"
"id": "852", {
} "_formatted": {
}) "id": "852"
); }
}
"###)
}
}, },
) )
.await; .await;

View File

@ -65,7 +65,7 @@ async fn simple_search_single_index() {
]})) ]}))
.await; .await;
snapshot!(code, @"200 OK"); snapshot!(code, @"200 OK");
insta::assert_json_snapshot!(response["results"], { "[].processingTimeMs" => "[time]" }, @r###" insta::assert_json_snapshot!(response["results"], { "[].processingTimeMs" => "[time]", ".**._rankingScore" => "[score]" }, @r###"
[ [
{ {
"indexUid": "test", "indexUid": "test",
@ -170,7 +170,7 @@ async fn simple_search_two_indexes() {
]})) ]}))
.await; .await;
snapshot!(code, @"200 OK"); snapshot!(code, @"200 OK");
insta::assert_json_snapshot!(response["results"], { "[].processingTimeMs" => "[time]" }, @r###" insta::assert_json_snapshot!(response["results"], { "[].processingTimeMs" => "[time]", ".**._rankingScore" => "[score]" }, @r###"
[ [
{ {
"indexUid": "test", "indexUid": "test",

View File

@ -53,6 +53,7 @@ fn main() -> Result<(), Box<dyn Error>> {
&mut ctx, &mut ctx,
&(!query.trim().is_empty()).then(|| query.trim().to_owned()), &(!query.trim().is_empty()).then(|| query.trim().to_owned()),
TermsMatchingStrategy::Last, TermsMatchingStrategy::Last,
milli::score_details::ScoringStrategy::Skip,
false, false,
&None, &None,
&None, &None,

View File

@ -2488,8 +2488,12 @@ pub(crate) mod tests {
let rtxn = index.read_txn().unwrap(); let rtxn = index.read_txn().unwrap();
let search = Search::new(&rtxn, &index); let search = Search::new(&rtxn, &index);
let SearchResult { matching_words: _, candidates: _, mut documents_ids } = let SearchResult {
search.execute().unwrap(); matching_words: _,
candidates: _,
document_scores: _,
mut documents_ids,
} = search.execute().unwrap();
let primary_key_id = index.fields_ids_map(&rtxn).unwrap().id("primary_key").unwrap(); let primary_key_id = index.fields_ids_map(&rtxn).unwrap().id("primary_key").unwrap();
documents_ids.sort_unstable(); documents_ids.sort_unstable();
let docs = index.documents(&rtxn, documents_ids).unwrap(); let docs = index.documents(&rtxn, documents_ids).unwrap();

View File

@ -17,6 +17,7 @@ mod fields_ids_map;
pub mod heed_codec; pub mod heed_codec;
pub mod index; pub mod index;
pub mod proximity; pub mod proximity;
pub mod score_details;
mod search; mod search;
pub mod update; pub mod update;

314
milli/src/score_details.rs Normal file
View File

@ -0,0 +1,314 @@
use serde::Serialize;
use crate::distance_between_two_points;
#[derive(Debug, Clone, PartialEq)]
pub enum ScoreDetails {
Words(Words),
Typo(Typo),
Proximity(Rank),
Fid(Rank),
Position(Rank),
ExactAttribute(ExactAttribute),
Exactness(Rank),
Sort(Sort),
GeoSort(GeoSort),
}
impl ScoreDetails {
pub fn local_score(&self) -> Option<f64> {
self.rank().map(Rank::local_score)
}
pub fn rank(&self) -> Option<Rank> {
match self {
ScoreDetails::Words(details) => Some(details.rank()),
ScoreDetails::Typo(details) => Some(details.rank()),
ScoreDetails::Proximity(details) => Some(*details),
ScoreDetails::Fid(details) => Some(*details),
ScoreDetails::Position(details) => Some(*details),
ScoreDetails::ExactAttribute(details) => Some(details.rank()),
ScoreDetails::Exactness(details) => Some(*details),
ScoreDetails::Sort(_) => None,
ScoreDetails::GeoSort(_) => None,
}
}
pub fn global_score<'a>(details: impl Iterator<Item = &'a Self>) -> f64 {
Rank::global_score(details.filter_map(Self::rank))
}
/// Panics
///
/// - If Position is not preceded by Fid
/// - If Exactness is not preceded by ExactAttribute
pub fn to_json_map<'a>(
details: impl Iterator<Item = &'a Self>,
) -> serde_json::Map<String, serde_json::Value> {
let mut order = 0;
let mut fid_details = None;
let mut details_map = serde_json::Map::default();
for details in details {
match details {
ScoreDetails::Words(words) => {
let words_details = serde_json::json!({
"order": order,
"matchingWords": words.matching_words,
"maxMatchingWords": words.max_matching_words,
"score": words.rank().local_score(),
});
details_map.insert("words".into(), words_details);
order += 1;
}
ScoreDetails::Typo(typo) => {
let typo_details = serde_json::json!({
"order": order,
"typoCount": typo.typo_count,
"maxTypoCount": typo.max_typo_count,
"score": typo.rank().local_score(),
});
details_map.insert("typo".into(), typo_details);
order += 1;
}
ScoreDetails::Proximity(proximity) => {
let proximity_details = serde_json::json!({
"order": order,
"score": proximity.local_score(),
});
details_map.insert("proximity".into(), proximity_details);
order += 1;
}
ScoreDetails::Fid(fid) => {
// copy the rank for future use in Position.
fid_details = Some(*fid);
// For now, fid is a virtual rule always followed by the "position" rule
let fid_details = serde_json::json!({
"order": order,
"attribute_ranking_order_score": fid.local_score(),
});
details_map.insert("attribute".into(), fid_details);
order += 1;
}
ScoreDetails::Position(position) => {
// For now, position is a virtual rule always preceded by the "fid" rule
let attribute_details = details_map
.get_mut("attribute")
.expect("position not preceded by attribute");
let attribute_details = attribute_details
.as_object_mut()
.expect("attribute details was not an object");
let Some(fid_details) = fid_details
else {
unimplemented!("position not preceded by attribute");
};
attribute_details
.insert("query_word_distance_score".into(), position.local_score().into());
let score = Rank::global_score([fid_details, *position].iter().copied());
attribute_details.insert("score".into(), score.into());
// do not update the order since this was already done by fid
}
ScoreDetails::ExactAttribute(exact_attribute) => {
let exactness_details = serde_json::json!({
"order": order,
"matchType": exact_attribute,
"score": exact_attribute.rank().local_score(),
});
details_map.insert("exactness".into(), exactness_details);
order += 1;
}
ScoreDetails::Exactness(details) => {
// For now, exactness is a virtual rule always preceded by the "ExactAttribute" rule
let exactness_details = details_map
.get_mut("exactness")
.expect("Exactness not preceded by exactAttribute");
let exactness_details = exactness_details
.as_object_mut()
.expect("exactness details was not an object");
if exactness_details.get("matchType").expect("missing 'matchType'")
== &serde_json::json!(ExactAttribute::NoExactMatch)
{
let score = Rank::global_score(
[ExactAttribute::NoExactMatch.rank(), *details].iter().copied(),
);
*exactness_details.get_mut("score").expect("missing score") = score.into();
}
// do not update the order since this was already done by exactAttribute
}
ScoreDetails::Sort(details) => {
let sort = if details.redacted {
format!("<hidden-rule-{order}>")
} else {
format!(
"{}:{}",
details.field_name,
if details.ascending { "asc" } else { "desc" }
)
};
let value =
if details.redacted { "<hidden>".into() } else { details.value.clone() };
let sort_details = serde_json::json!({
"order": order,
"value": value,
});
details_map.insert(sort, sort_details);
order += 1;
}
ScoreDetails::GeoSort(details) => {
let sort = format!(
"_geoPoint({}, {}):{}",
details.target_point[0],
details.target_point[1],
if details.ascending { "asc" } else { "desc" }
);
let point = if let Some(value) = details.value {
serde_json::json!({ "lat": value[0], "lng": value[1]})
} else {
serde_json::Value::Null
};
let sort_details = serde_json::json!({
"order": order,
"value": point,
"distance": details.distance(),
});
details_map.insert(sort, sort_details);
order += 1;
}
}
}
details_map
}
}
/// The strategy to compute scores.
///
/// It makes sense to pass down this strategy to the internals of the search, because
/// some optimizations (today, mainly skipping ranking rules for universes of a single document)
/// are not correct to do when computing the scores.
///
/// This strategy could feasibly be extended to differentiate between the normalized score and the
/// detailed scores, but it is not useful today as the normalized score is *derived from* the
/// detailed scores.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum ScoringStrategy {
/// Don't compute scores
#[default]
Skip,
/// Compute detailed scores
Detailed,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct Words {
pub matching_words: u32,
pub max_matching_words: u32,
}
impl Words {
pub fn rank(&self) -> Rank {
Rank { rank: self.matching_words, max_rank: self.max_matching_words }
}
pub(crate) fn from_rank(rank: Rank) -> Words {
Words { matching_words: rank.rank, max_matching_words: rank.max_rank }
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct Typo {
pub typo_count: u32,
pub max_typo_count: u32,
}
impl Typo {
pub fn rank(&self) -> Rank {
Rank {
rank: self.max_typo_count - self.typo_count + 1,
max_rank: (self.max_typo_count + 1),
}
}
// max_rank = max_typo + 1
// max_typo = max_rank - 1
//
// rank = max_typo - typo + 1
// rank = max_rank - 1 - typo + 1
// rank + typo = max_rank
// typo = max_rank - rank
pub fn from_rank(rank: Rank) -> Typo {
Typo { typo_count: rank.max_rank - rank.rank, max_typo_count: rank.max_rank - 1 }
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct Rank {
/// The ordinal rank, such that `max_rank` is the first rank, and 0 is the last rank.
///
/// The higher the better. Documents with a rank of 0 have a score of 0 and are typically never returned
/// (they don't match the query).
pub rank: u32,
/// The maximum possible rank. Documents with this rank have a score of 1.
///
/// The max rank should not be 0.
pub max_rank: u32,
}
impl Rank {
pub fn local_score(self) -> f64 {
self.rank as f64 / self.max_rank as f64
}
pub fn global_score(details: impl Iterator<Item = Self>) -> f64 {
let mut rank = Rank { rank: 1, max_rank: 1 };
for inner_rank in details {
rank.rank -= 1;
rank.rank *= inner_rank.max_rank;
rank.max_rank *= inner_rank.max_rank;
rank.rank += inner_rank.rank;
}
rank.local_score()
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize)]
#[serde(rename_all = "camelCase")]
pub enum ExactAttribute {
ExactMatch,
MatchesStart,
NoExactMatch,
}
impl ExactAttribute {
pub fn rank(&self) -> Rank {
let rank = match self {
ExactAttribute::ExactMatch => 3,
ExactAttribute::MatchesStart => 2,
ExactAttribute::NoExactMatch => 1,
};
Rank { rank, max_rank: 3 }
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct Sort {
pub field_name: String,
pub ascending: bool,
pub redacted: bool,
pub value: serde_json::Value,
}
#[derive(Debug, Clone, Copy, PartialEq, PartialOrd)]
pub struct GeoSort {
pub target_point: [f64; 2],
pub ascending: bool,
pub value: Option<[f64; 2]>,
}
impl GeoSort {
pub fn distance(&self) -> Option<f64> {
self.value.map(|value| distance_between_two_points(&self.target_point, &value))
}
}

View File

@ -7,6 +7,7 @@ use roaring::bitmap::RoaringBitmap;
pub use self::facet::{FacetDistribution, Filter, DEFAULT_VALUES_PER_FACET}; pub use self::facet::{FacetDistribution, Filter, DEFAULT_VALUES_PER_FACET};
pub use self::new::matches::{FormatOptions, MatchBounds, Matcher, MatcherBuilder, MatchingWords}; pub use self::new::matches::{FormatOptions, MatchBounds, Matcher, MatcherBuilder, MatchingWords};
use self::new::PartialSearchResult; use self::new::PartialSearchResult;
use crate::score_details::{ScoreDetails, ScoringStrategy};
use crate::{ use crate::{
execute_search, AscDesc, DefaultSearchLogger, DocumentId, Index, Result, SearchContext, execute_search, AscDesc, DefaultSearchLogger, DocumentId, Index, Result, SearchContext,
}; };
@ -29,6 +30,7 @@ pub struct Search<'a> {
sort_criteria: Option<Vec<AscDesc>>, sort_criteria: Option<Vec<AscDesc>>,
geo_strategy: new::GeoSortStrategy, geo_strategy: new::GeoSortStrategy,
terms_matching_strategy: TermsMatchingStrategy, terms_matching_strategy: TermsMatchingStrategy,
scoring_strategy: ScoringStrategy,
words_limit: usize, words_limit: usize,
exhaustive_number_hits: bool, exhaustive_number_hits: bool,
rtxn: &'a heed::RoTxn<'a>, rtxn: &'a heed::RoTxn<'a>,
@ -45,6 +47,7 @@ impl<'a> Search<'a> {
sort_criteria: None, sort_criteria: None,
geo_strategy: new::GeoSortStrategy::default(), geo_strategy: new::GeoSortStrategy::default(),
terms_matching_strategy: TermsMatchingStrategy::default(), terms_matching_strategy: TermsMatchingStrategy::default(),
scoring_strategy: Default::default(),
exhaustive_number_hits: false, exhaustive_number_hits: false,
words_limit: 10, words_limit: 10,
rtxn, rtxn,
@ -77,6 +80,11 @@ impl<'a> Search<'a> {
self self
} }
pub fn scoring_strategy(&mut self, value: ScoringStrategy) -> &mut Search<'a> {
self.scoring_strategy = value;
self
}
pub fn words_limit(&mut self, value: usize) -> &mut Search<'a> { pub fn words_limit(&mut self, value: usize) -> &mut Search<'a> {
self.words_limit = value; self.words_limit = value;
self self
@ -93,7 +101,7 @@ impl<'a> Search<'a> {
self self
} }
/// Force the search to exhastivelly compute the number of candidates, /// Forces the search to exhaustively compute the number of candidates,
/// this will increase the search time but allows finite pagination. /// this will increase the search time but allows finite pagination.
pub fn exhaustive_number_hits(&mut self, exhaustive_number_hits: bool) -> &mut Search<'a> { pub fn exhaustive_number_hits(&mut self, exhaustive_number_hits: bool) -> &mut Search<'a> {
self.exhaustive_number_hits = exhaustive_number_hits; self.exhaustive_number_hits = exhaustive_number_hits;
@ -102,11 +110,12 @@ impl<'a> Search<'a> {
pub fn execute(&self) -> Result<SearchResult> { pub fn execute(&self) -> Result<SearchResult> {
let mut ctx = SearchContext::new(self.index, self.rtxn); let mut ctx = SearchContext::new(self.index, self.rtxn);
let PartialSearchResult { located_query_terms, candidates, documents_ids } = let PartialSearchResult { located_query_terms, candidates, documents_ids, document_scores } =
execute_search( execute_search(
&mut ctx, &mut ctx,
&self.query, &self.query,
self.terms_matching_strategy, self.terms_matching_strategy,
self.scoring_strategy,
self.exhaustive_number_hits, self.exhaustive_number_hits,
&self.filter, &self.filter,
&self.sort_criteria, &self.sort_criteria,
@ -124,7 +133,7 @@ impl<'a> Search<'a> {
None => MatchingWords::default(), None => MatchingWords::default(),
}; };
Ok(SearchResult { matching_words, candidates, documents_ids }) Ok(SearchResult { matching_words, candidates, document_scores, documents_ids })
} }
} }
@ -138,6 +147,7 @@ impl fmt::Debug for Search<'_> {
sort_criteria, sort_criteria,
geo_strategy: _, geo_strategy: _,
terms_matching_strategy, terms_matching_strategy,
scoring_strategy,
words_limit, words_limit,
exhaustive_number_hits, exhaustive_number_hits,
rtxn: _, rtxn: _,
@ -150,6 +160,7 @@ impl fmt::Debug for Search<'_> {
.field("limit", limit) .field("limit", limit)
.field("sort_criteria", sort_criteria) .field("sort_criteria", sort_criteria)
.field("terms_matching_strategy", terms_matching_strategy) .field("terms_matching_strategy", terms_matching_strategy)
.field("scoring_strategy", scoring_strategy)
.field("exhaustive_number_hits", exhaustive_number_hits) .field("exhaustive_number_hits", exhaustive_number_hits)
.field("words_limit", words_limit) .field("words_limit", words_limit)
.finish() .finish()
@ -160,8 +171,8 @@ impl fmt::Debug for Search<'_> {
pub struct SearchResult { pub struct SearchResult {
pub matching_words: MatchingWords, pub matching_words: MatchingWords,
pub candidates: RoaringBitmap, pub candidates: RoaringBitmap,
// TODO those documents ids should be associated with their criteria scores.
pub documents_ids: Vec<DocumentId>, pub documents_ids: Vec<DocumentId>,
pub document_scores: Vec<Vec<ScoreDetails>>,
} }
#[derive(Debug, Clone, Copy, PartialEq, Eq)] #[derive(Debug, Clone, Copy, PartialEq, Eq)]

View File

@ -3,14 +3,18 @@ use roaring::RoaringBitmap;
use super::logger::SearchLogger; use super::logger::SearchLogger;
use super::ranking_rules::{BoxRankingRule, RankingRuleQueryTrait}; use super::ranking_rules::{BoxRankingRule, RankingRuleQueryTrait};
use super::SearchContext; use super::SearchContext;
use crate::score_details::{ScoreDetails, ScoringStrategy};
use crate::search::new::distinct::{apply_distinct_rule, distinct_single_docid, DistinctOutput}; use crate::search::new::distinct::{apply_distinct_rule, distinct_single_docid, DistinctOutput};
use crate::Result; use crate::Result;
pub struct BucketSortOutput { pub struct BucketSortOutput {
pub docids: Vec<u32>, pub docids: Vec<u32>,
pub scores: Vec<Vec<ScoreDetails>>,
pub all_candidates: RoaringBitmap, pub all_candidates: RoaringBitmap,
} }
// TODO: would probably be good to regroup some of these inside of a struct?
#[allow(clippy::too_many_arguments)]
pub fn bucket_sort<'ctx, Q: RankingRuleQueryTrait>( pub fn bucket_sort<'ctx, Q: RankingRuleQueryTrait>(
ctx: &mut SearchContext<'ctx>, ctx: &mut SearchContext<'ctx>,
mut ranking_rules: Vec<BoxRankingRule<'ctx, Q>>, mut ranking_rules: Vec<BoxRankingRule<'ctx, Q>>,
@ -18,6 +22,7 @@ pub fn bucket_sort<'ctx, Q: RankingRuleQueryTrait>(
universe: &RoaringBitmap, universe: &RoaringBitmap,
from: usize, from: usize,
length: usize, length: usize,
scoring_strategy: ScoringStrategy,
logger: &mut dyn SearchLogger<Q>, logger: &mut dyn SearchLogger<Q>,
) -> Result<BucketSortOutput> { ) -> Result<BucketSortOutput> {
logger.initial_query(query); logger.initial_query(query);
@ -31,7 +36,11 @@ pub fn bucket_sort<'ctx, Q: RankingRuleQueryTrait>(
}; };
if universe.len() < from as u64 { if universe.len() < from as u64 {
return Ok(BucketSortOutput { docids: vec![], all_candidates: universe.clone() }); return Ok(BucketSortOutput {
docids: vec![],
scores: vec![],
all_candidates: universe.clone(),
});
} }
if ranking_rules.is_empty() { if ranking_rules.is_empty() {
if let Some(distinct_fid) = distinct_fid { if let Some(distinct_fid) = distinct_fid {
@ -49,22 +58,32 @@ pub fn bucket_sort<'ctx, Q: RankingRuleQueryTrait>(
} }
let mut all_candidates = universe - excluded; let mut all_candidates = universe - excluded;
all_candidates.extend(results.iter().copied()); all_candidates.extend(results.iter().copied());
return Ok(BucketSortOutput { docids: results, all_candidates }); return Ok(BucketSortOutput {
scores: vec![Default::default(); results.len()],
docids: results,
all_candidates,
});
} else { } else {
let docids = universe.iter().skip(from).take(length).collect(); let docids: Vec<u32> = universe.iter().skip(from).take(length).collect();
return Ok(BucketSortOutput { docids, all_candidates: universe.clone() }); return Ok(BucketSortOutput {
scores: vec![Default::default(); docids.len()],
docids,
all_candidates: universe.clone(),
});
}; };
} }
let ranking_rules_len = ranking_rules.len(); let ranking_rules_len = ranking_rules.len();
logger.start_iteration_ranking_rule(0, ranking_rules[0].as_ref(), query, universe); logger.start_iteration_ranking_rule(0, ranking_rules[0].as_ref(), query, universe);
ranking_rules[0].start_iteration(ctx, logger, universe, query)?; ranking_rules[0].start_iteration(ctx, logger, universe, query)?;
let mut ranking_rule_scores: Vec<ScoreDetails> = vec![];
let mut ranking_rule_universes: Vec<RoaringBitmap> = let mut ranking_rule_universes: Vec<RoaringBitmap> =
vec![RoaringBitmap::default(); ranking_rules_len]; vec![RoaringBitmap::default(); ranking_rules_len];
ranking_rule_universes[0] = universe.clone(); ranking_rule_universes[0] = universe.clone();
let mut cur_ranking_rule_index = 0; let mut cur_ranking_rule_index = 0;
/// Finish iterating over the current ranking rule, yielding /// Finish iterating over the current ranking rule, yielding
@ -89,11 +108,15 @@ pub fn bucket_sort<'ctx, Q: RankingRuleQueryTrait>(
} else { } else {
cur_ranking_rule_index -= 1; cur_ranking_rule_index -= 1;
} }
if ranking_rule_scores.len() > cur_ranking_rule_index {
ranking_rule_scores.pop();
}
}; };
} }
let mut all_candidates = universe.clone(); let mut all_candidates = universe.clone();
let mut valid_docids = vec![]; let mut valid_docids = vec![];
let mut valid_scores = vec![];
let mut cur_offset = 0usize; let mut cur_offset = 0usize;
macro_rules! maybe_add_to_results { macro_rules! maybe_add_to_results {
@ -104,21 +127,26 @@ pub fn bucket_sort<'ctx, Q: RankingRuleQueryTrait>(
length, length,
logger, logger,
&mut valid_docids, &mut valid_docids,
&mut valid_scores,
&mut all_candidates, &mut all_candidates,
&mut ranking_rule_universes, &mut ranking_rule_universes,
&mut ranking_rules, &mut ranking_rules,
cur_ranking_rule_index, cur_ranking_rule_index,
&mut cur_offset, &mut cur_offset,
distinct_fid, distinct_fid,
&ranking_rule_scores,
$candidates, $candidates,
)?; )?;
}; };
} }
while valid_docids.len() < length { while valid_docids.len() < length {
// The universe for this bucket is zero or one element, so we don't need to sort // The universe for this bucket is zero, so we don't need to sort
// anything, just extend the results and go back to the parent ranking rule. // anything, just go back to the parent ranking rule.
if ranking_rule_universes[cur_ranking_rule_index].len() <= 1 { if ranking_rule_universes[cur_ranking_rule_index].is_empty()
|| (scoring_strategy == ScoringStrategy::Skip
&& ranking_rule_universes[cur_ranking_rule_index].len() == 1)
{
let bucket = std::mem::take(&mut ranking_rule_universes[cur_ranking_rule_index]); let bucket = std::mem::take(&mut ranking_rule_universes[cur_ranking_rule_index]);
maybe_add_to_results!(bucket); maybe_add_to_results!(bucket);
back!(); back!();
@ -130,6 +158,8 @@ pub fn bucket_sort<'ctx, Q: RankingRuleQueryTrait>(
continue; continue;
}; };
ranking_rule_scores.push(next_bucket.score);
logger.next_bucket_ranking_rule( logger.next_bucket_ranking_rule(
cur_ranking_rule_index, cur_ranking_rule_index,
ranking_rules[cur_ranking_rule_index].as_ref(), ranking_rules[cur_ranking_rule_index].as_ref(),
@ -143,10 +173,11 @@ pub fn bucket_sort<'ctx, Q: RankingRuleQueryTrait>(
ranking_rule_universes[cur_ranking_rule_index] -= &next_bucket.candidates; ranking_rule_universes[cur_ranking_rule_index] -= &next_bucket.candidates;
if cur_ranking_rule_index == ranking_rules_len - 1 if cur_ranking_rule_index == ranking_rules_len - 1
|| next_bucket.candidates.len() <= 1 || (scoring_strategy == ScoringStrategy::Skip && next_bucket.candidates.len() <= 1)
|| cur_offset + (next_bucket.candidates.len() as usize) < from || cur_offset + (next_bucket.candidates.len() as usize) < from
{ {
maybe_add_to_results!(next_bucket.candidates); maybe_add_to_results!(next_bucket.candidates);
ranking_rule_scores.pop();
continue; continue;
} }
@ -166,7 +197,7 @@ pub fn bucket_sort<'ctx, Q: RankingRuleQueryTrait>(
)?; )?;
} }
Ok(BucketSortOutput { docids: valid_docids, all_candidates }) Ok(BucketSortOutput { docids: valid_docids, scores: valid_scores, all_candidates })
} }
/// Add the candidates to the results. Take `distinct`, `from`, `length`, and `cur_offset` /// Add the candidates to the results. Take `distinct`, `from`, `length`, and `cur_offset`
@ -179,14 +210,18 @@ fn maybe_add_to_results<'ctx, Q: RankingRuleQueryTrait>(
logger: &mut dyn SearchLogger<Q>, logger: &mut dyn SearchLogger<Q>,
valid_docids: &mut Vec<u32>, valid_docids: &mut Vec<u32>,
valid_scores: &mut Vec<Vec<ScoreDetails>>,
all_candidates: &mut RoaringBitmap, all_candidates: &mut RoaringBitmap,
ranking_rule_universes: &mut [RoaringBitmap], ranking_rule_universes: &mut [RoaringBitmap],
ranking_rules: &mut [BoxRankingRule<'ctx, Q>], ranking_rules: &mut [BoxRankingRule<'ctx, Q>],
cur_ranking_rule_index: usize, cur_ranking_rule_index: usize,
cur_offset: &mut usize, cur_offset: &mut usize,
distinct_fid: Option<u16>, distinct_fid: Option<u16>,
ranking_rule_scores: &[ScoreDetails],
candidates: RoaringBitmap, candidates: RoaringBitmap,
) -> Result<()> { ) -> Result<()> {
// First apply the distinct rule on the candidates, reducing the universes if necessary // First apply the distinct rule on the candidates, reducing the universes if necessary
@ -231,13 +266,17 @@ fn maybe_add_to_results<'ctx, Q: RankingRuleQueryTrait>(
let candidates = let candidates =
candidates.iter().take(length - valid_docids.len()).copied().collect::<Vec<_>>(); candidates.iter().take(length - valid_docids.len()).copied().collect::<Vec<_>>();
logger.add_to_results(&candidates); logger.add_to_results(&candidates);
valid_docids.extend(&candidates); valid_docids.extend_from_slice(&candidates);
valid_scores
.extend(std::iter::repeat(ranking_rule_scores.to_owned()).take(candidates.len()));
} }
} else { } else {
// if we have passed the offset already, add some of the documents (up to the limit) // if we have passed the offset already, add some of the documents (up to the limit)
let candidates = candidates.iter().take(length - valid_docids.len()).collect::<Vec<u32>>(); let candidates = candidates.iter().take(length - valid_docids.len()).collect::<Vec<u32>>();
logger.add_to_results(&candidates); logger.add_to_results(&candidates);
valid_docids.extend(&candidates); valid_docids.extend_from_slice(&candidates);
valid_scores
.extend(std::iter::repeat(ranking_rule_scores.to_owned()).take(candidates.len()));
} }
*cur_offset += candidates.len() as usize; *cur_offset += candidates.len() as usize;

View File

@ -2,6 +2,7 @@ use roaring::{MultiOps, RoaringBitmap};
use super::query_graph::QueryGraph; use super::query_graph::QueryGraph;
use super::ranking_rules::{RankingRule, RankingRuleOutput}; use super::ranking_rules::{RankingRule, RankingRuleOutput};
use crate::score_details::{self, ScoreDetails};
use crate::search::new::query_graph::QueryNodeData; use crate::search::new::query_graph::QueryNodeData;
use crate::search::new::query_term::ExactTerm; use crate::search::new::query_term::ExactTerm;
use crate::{Result, SearchContext, SearchLogger}; use crate::{Result, SearchContext, SearchLogger};
@ -244,7 +245,13 @@ impl State {
candidates &= universe; candidates &= universe;
( (
State::AttributeStarts(query_graph.clone(), candidates_per_attribute), State::AttributeStarts(query_graph.clone(), candidates_per_attribute),
Some(RankingRuleOutput { query: query_graph, candidates }), Some(RankingRuleOutput {
query: query_graph,
candidates,
score: ScoreDetails::ExactAttribute(
score_details::ExactAttribute::ExactMatch,
),
}),
) )
} }
State::AttributeStarts(query_graph, candidates_per_attribute) => { State::AttributeStarts(query_graph, candidates_per_attribute) => {
@ -257,12 +264,24 @@ impl State {
candidates &= universe; candidates &= universe;
( (
State::Empty(query_graph.clone()), State::Empty(query_graph.clone()),
Some(RankingRuleOutput { query: query_graph, candidates }), Some(RankingRuleOutput {
query: query_graph,
candidates,
score: ScoreDetails::ExactAttribute(
score_details::ExactAttribute::MatchesStart,
),
}),
) )
} }
State::Empty(query_graph) => ( State::Empty(query_graph) => (
State::Empty(query_graph.clone()), State::Empty(query_graph.clone()),
Some(RankingRuleOutput { query: query_graph, candidates: universe.clone() }), Some(RankingRuleOutput {
query: query_graph,
candidates: universe.clone(),
score: ScoreDetails::ExactAttribute(
score_details::ExactAttribute::NoExactMatch,
),
}),
), ),
}; };
(state, output) (state, output)

View File

@ -8,6 +8,7 @@ use rstar::RTree;
use super::ranking_rules::{RankingRule, RankingRuleOutput, RankingRuleQueryTrait}; use super::ranking_rules::{RankingRule, RankingRuleOutput, RankingRuleQueryTrait};
use crate::heed_codec::facet::{FieldDocIdFacetCodec, OrderedF64Codec}; use crate::heed_codec::facet::{FieldDocIdFacetCodec, OrderedF64Codec};
use crate::score_details::{self, ScoreDetails};
use crate::{ use crate::{
distance_between_two_points, lat_lng_to_xyz, GeoPoint, Index, Result, SearchContext, distance_between_two_points, lat_lng_to_xyz, GeoPoint, Index, Result, SearchContext,
SearchLogger, SearchLogger,
@ -80,7 +81,7 @@ pub struct GeoSort<Q: RankingRuleQueryTrait> {
field_ids: Option<[u16; 2]>, field_ids: Option<[u16; 2]>,
rtree: Option<RTree<GeoPoint>>, rtree: Option<RTree<GeoPoint>>,
cached_sorted_docids: VecDeque<u32>, cached_sorted_docids: VecDeque<(u32, [f64; 2])>,
geo_candidates: RoaringBitmap, geo_candidates: RoaringBitmap,
} }
@ -130,7 +131,7 @@ impl<Q: RankingRuleQueryTrait> GeoSort<Q> {
let point = lat_lng_to_xyz(&self.point); let point = lat_lng_to_xyz(&self.point);
for point in rtree.nearest_neighbor_iter(&point) { for point in rtree.nearest_neighbor_iter(&point) {
if self.geo_candidates.contains(point.data.0) { if self.geo_candidates.contains(point.data.0) {
self.cached_sorted_docids.push_back(point.data.0); self.cached_sorted_docids.push_back(point.data);
if self.cached_sorted_docids.len() >= cache_size { if self.cached_sorted_docids.len() >= cache_size {
break; break;
} }
@ -142,7 +143,7 @@ impl<Q: RankingRuleQueryTrait> GeoSort<Q> {
let point = lat_lng_to_xyz(&opposite_of(self.point)); let point = lat_lng_to_xyz(&opposite_of(self.point));
for point in rtree.nearest_neighbor_iter(&point) { for point in rtree.nearest_neighbor_iter(&point) {
if self.geo_candidates.contains(point.data.0) { if self.geo_candidates.contains(point.data.0) {
self.cached_sorted_docids.push_front(point.data.0); self.cached_sorted_docids.push_front(point.data);
if self.cached_sorted_docids.len() >= cache_size { if self.cached_sorted_docids.len() >= cache_size {
break; break;
} }
@ -177,7 +178,7 @@ impl<Q: RankingRuleQueryTrait> GeoSort<Q> {
// computing the distance between two points is expensive thus we cache the result // computing the distance between two points is expensive thus we cache the result
documents documents
.sort_by_cached_key(|(_, p)| distance_between_two_points(&self.point, p) as usize); .sort_by_cached_key(|(_, p)| distance_between_two_points(&self.point, p) as usize);
self.cached_sorted_docids.extend(documents.into_iter().map(|(doc_id, _)| doc_id)); self.cached_sorted_docids.extend(documents.into_iter());
}; };
Ok(()) Ok(())
@ -220,12 +221,19 @@ impl<'ctx, Q: RankingRuleQueryTrait> RankingRule<'ctx, Q> for GeoSort<Q> {
logger: &mut dyn SearchLogger<Q>, logger: &mut dyn SearchLogger<Q>,
universe: &RoaringBitmap, universe: &RoaringBitmap,
) -> Result<Option<RankingRuleOutput<Q>>> { ) -> Result<Option<RankingRuleOutput<Q>>> {
assert!(universe.len() > 1);
let query = self.query.as_ref().unwrap().clone(); let query = self.query.as_ref().unwrap().clone();
self.geo_candidates &= universe; self.geo_candidates &= universe;
if self.geo_candidates.is_empty() { if self.geo_candidates.is_empty() {
return Ok(Some(RankingRuleOutput { query, candidates: universe.clone() })); return Ok(Some(RankingRuleOutput {
query,
candidates: universe.clone(),
score: ScoreDetails::GeoSort(score_details::GeoSort {
target_point: self.point,
ascending: self.ascending,
value: None,
}),
}));
} }
let ascending = self.ascending; let ascending = self.ascending;
@ -236,11 +244,16 @@ impl<'ctx, Q: RankingRuleQueryTrait> RankingRule<'ctx, Q> for GeoSort<Q> {
cache.pop_back() cache.pop_back()
} }
}; };
while let Some(id) = next(&mut self.cached_sorted_docids) { while let Some((id, point)) = next(&mut self.cached_sorted_docids) {
if self.geo_candidates.contains(id) { if self.geo_candidates.contains(id) {
return Ok(Some(RankingRuleOutput { return Ok(Some(RankingRuleOutput {
query, query,
candidates: RoaringBitmap::from_iter([id]), candidates: RoaringBitmap::from_iter([id]),
score: ScoreDetails::GeoSort(score_details::GeoSort {
target_point: self.point,
ascending: self.ascending,
value: Some(point),
}),
})); }));
} }
} }

View File

@ -50,6 +50,7 @@ use super::ranking_rule_graph::{
}; };
use super::small_bitmap::SmallBitmap; use super::small_bitmap::SmallBitmap;
use super::{QueryGraph, RankingRule, RankingRuleOutput, SearchContext}; use super::{QueryGraph, RankingRule, RankingRuleOutput, SearchContext};
use crate::score_details::Rank;
use crate::search::new::query_term::LocatedQueryTermSubset; use crate::search::new::query_term::LocatedQueryTermSubset;
use crate::search::new::ranking_rule_graph::PathVisitor; use crate::search::new::ranking_rule_graph::PathVisitor;
use crate::{Result, TermsMatchingStrategy}; use crate::{Result, TermsMatchingStrategy};
@ -118,6 +119,8 @@ pub struct GraphBasedRankingRuleState<G: RankingRuleGraphTrait> {
all_costs: MappedInterner<QueryNode, Vec<u64>>, all_costs: MappedInterner<QueryNode, Vec<u64>>,
/// An index in the first element of `all_distances`, giving the cost of the next bucket /// An index in the first element of `all_distances`, giving the cost of the next bucket
cur_cost: u64, cur_cost: u64,
/// One above the highest possible cost for this rule
next_max_cost: u64,
} }
impl<'ctx, G: RankingRuleGraphTrait> RankingRule<'ctx, QueryGraph> for GraphBasedRankingRule<G> { impl<'ctx, G: RankingRuleGraphTrait> RankingRule<'ctx, QueryGraph> for GraphBasedRankingRule<G> {
@ -131,7 +134,20 @@ impl<'ctx, G: RankingRuleGraphTrait> RankingRule<'ctx, QueryGraph> for GraphBase
_universe: &RoaringBitmap, _universe: &RoaringBitmap,
query_graph: &QueryGraph, query_graph: &QueryGraph,
) -> Result<()> { ) -> Result<()> {
// the `next_max_cost` is the successor integer to the maximum cost of the paths in the graph.
//
// When there is a matching strategy, it also factors the additional costs of:
// 1. The words that are matched in phrases
// 2. Skipping words (by adding them to the paths with a cost)
let mut next_max_cost = 1;
let removal_cost = if let Some(terms_matching_strategy) = self.terms_matching_strategy { let removal_cost = if let Some(terms_matching_strategy) = self.terms_matching_strategy {
// add the cost of the phrase to the next_max_cost
next_max_cost += query_graph
.words_in_phrases_count(ctx)
// remove 1 from the words in phrases count, because when there is a phrase we can now have a document
// where only the phrase is matching, and none of the non-phrase words.
// With the `1` that `next_max_cost` is initialized with, this gets counted twice.
.saturating_sub(1) as u64;
match terms_matching_strategy { match terms_matching_strategy {
TermsMatchingStrategy::Last => { TermsMatchingStrategy::Last => {
let removal_order = let removal_order =
@ -139,13 +155,12 @@ impl<'ctx, G: RankingRuleGraphTrait> RankingRule<'ctx, QueryGraph> for GraphBase
let mut forbidden_nodes = let mut forbidden_nodes =
SmallBitmap::for_interned_values_in(&query_graph.nodes); SmallBitmap::for_interned_values_in(&query_graph.nodes);
let mut costs = query_graph.nodes.map(|_| None); let mut costs = query_graph.nodes.map(|_| None);
let mut cost = 100; // FIXME: this works because only words uses termsmatchingstrategy at the moment.
for ns in removal_order { for ns in removal_order {
for n in ns.iter() { for n in ns.iter() {
*costs.get_mut(n) = Some((cost, forbidden_nodes.clone())); *costs.get_mut(n) = Some((1, forbidden_nodes.clone()));
} }
forbidden_nodes.union(&ns); forbidden_nodes.union(&ns);
cost += 100;
} }
costs costs
} }
@ -162,12 +177,16 @@ impl<'ctx, G: RankingRuleGraphTrait> RankingRule<'ctx, QueryGraph> for GraphBase
// Then pre-compute the cost of all paths from each node to the end node // Then pre-compute the cost of all paths from each node to the end node
let all_costs = graph.find_all_costs_to_end(); let all_costs = graph.find_all_costs_to_end();
next_max_cost +=
all_costs.get(graph.query_graph.root_node).iter().copied().max().unwrap_or(0);
let state = GraphBasedRankingRuleState { let state = GraphBasedRankingRuleState {
graph, graph,
conditions_cache: condition_docids_cache, conditions_cache: condition_docids_cache,
dead_ends_cache, dead_ends_cache,
all_costs, all_costs,
cur_cost: 0, cur_cost: 0,
next_max_cost,
}; };
self.state = Some(state); self.state = Some(state);
@ -181,17 +200,13 @@ impl<'ctx, G: RankingRuleGraphTrait> RankingRule<'ctx, QueryGraph> for GraphBase
logger: &mut dyn SearchLogger<QueryGraph>, logger: &mut dyn SearchLogger<QueryGraph>,
universe: &RoaringBitmap, universe: &RoaringBitmap,
) -> Result<Option<RankingRuleOutput<QueryGraph>>> { ) -> Result<Option<RankingRuleOutput<QueryGraph>>> {
// If universe.len() <= 1, the bucket sort algorithm
// should not have called this function.
assert!(universe.len() > 1);
// Will crash if `next_bucket` is called before `start_iteration` or after `end_iteration`, // Will crash if `next_bucket` is called before `start_iteration` or after `end_iteration`,
// should never happen // should never happen
let mut state = self.state.take().unwrap(); let mut state = self.state.take().unwrap();
let all_costs = state.all_costs.get(state.graph.query_graph.root_node);
// Retrieve the cost of the paths to compute // Retrieve the cost of the paths to compute
let Some(&cost) = state let Some(&cost) = all_costs
.all_costs
.get(state.graph.query_graph.root_node)
.iter() .iter()
.find(|c| **c >= state.cur_cost) else { .find(|c| **c >= state.cur_cost) else {
self.state = None; self.state = None;
@ -207,8 +222,12 @@ impl<'ctx, G: RankingRuleGraphTrait> RankingRule<'ctx, QueryGraph> for GraphBase
dead_ends_cache, dead_ends_cache,
all_costs, all_costs,
cur_cost: _, cur_cost: _,
next_max_cost,
} = &mut state; } = &mut state;
let rank = *next_max_cost - cost;
let score = G::rank_to_score(Rank { rank: rank as u32, max_rank: *next_max_cost as u32 });
let mut universe = universe.clone(); let mut universe = universe.clone();
let mut used_conditions = SmallBitmap::for_interned_values_in(&graph.conditions_interner); let mut used_conditions = SmallBitmap::for_interned_values_in(&graph.conditions_interner);
@ -295,8 +314,6 @@ impl<'ctx, G: RankingRuleGraphTrait> RankingRule<'ctx, QueryGraph> for GraphBase
// We modify the next query graph so that it only contains the subgraph // We modify the next query graph so that it only contains the subgraph
// that was used to compute this bucket // that was used to compute this bucket
// But we only do it in case the bucket length is >1, because otherwise
// we know the child ranking rule won't be called anyway
let paths: Vec<Vec<(Option<LocatedQueryTermSubset>, LocatedQueryTermSubset)>> = good_paths let paths: Vec<Vec<(Option<LocatedQueryTermSubset>, LocatedQueryTermSubset)>> = good_paths
.into_iter() .into_iter()
@ -325,7 +342,7 @@ impl<'ctx, G: RankingRuleGraphTrait> RankingRule<'ctx, QueryGraph> for GraphBase
self.state = Some(state); self.state = Some(state);
Ok(Some(RankingRuleOutput { query: next_query_graph, candidates: bucket })) Ok(Some(RankingRuleOutput { query: next_query_graph, candidates: bucket, score }))
} }
fn end_iteration( fn end_iteration(

View File

@ -510,6 +510,7 @@ mod tests {
&mut ctx, &mut ctx,
&Some(query.to_string()), &Some(query.to_string()),
crate::TermsMatchingStrategy::default(), crate::TermsMatchingStrategy::default(),
crate::score_details::ScoringStrategy::Skip,
false, false,
&None, &None,
&None, &None,

View File

@ -44,6 +44,7 @@ use self::geo_sort::GeoSort;
pub use self::geo_sort::Strategy as GeoSortStrategy; pub use self::geo_sort::Strategy as GeoSortStrategy;
use self::graph_based_ranking_rule::Words; use self::graph_based_ranking_rule::Words;
use self::interner::Interned; use self::interner::Interned;
use crate::score_details::{ScoreDetails, ScoringStrategy};
use crate::search::new::distinct::apply_distinct_rule; use crate::search::new::distinct::apply_distinct_rule;
use crate::{AscDesc, DocumentId, Filter, Index, Member, Result, TermsMatchingStrategy, UserError}; use crate::{AscDesc, DocumentId, Filter, Index, Member, Result, TermsMatchingStrategy, UserError};
@ -350,6 +351,7 @@ pub fn execute_search(
ctx: &mut SearchContext, ctx: &mut SearchContext,
query: &Option<String>, query: &Option<String>,
terms_matching_strategy: TermsMatchingStrategy, terms_matching_strategy: TermsMatchingStrategy,
scoring_strategy: ScoringStrategy,
exhaustive_number_hits: bool, exhaustive_number_hits: bool,
filters: &Option<Filter>, filters: &Option<Filter>,
sort_criteria: &Option<Vec<AscDesc>>, sort_criteria: &Option<Vec<AscDesc>>,
@ -411,7 +413,16 @@ pub fn execute_search(
universe = universe =
resolve_universe(ctx, &universe, &graph, terms_matching_strategy, query_graph_logger)?; resolve_universe(ctx, &universe, &graph, terms_matching_strategy, query_graph_logger)?;
bucket_sort(ctx, ranking_rules, &graph, &universe, from, length, query_graph_logger)? bucket_sort(
ctx,
ranking_rules,
&graph,
&universe,
from,
length,
scoring_strategy,
query_graph_logger,
)?
} else { } else {
let ranking_rules = let ranking_rules =
get_ranking_rules_for_placeholder_search(ctx, sort_criteria, geo_strategy)?; get_ranking_rules_for_placeholder_search(ctx, sort_criteria, geo_strategy)?;
@ -422,17 +433,20 @@ pub fn execute_search(
&universe, &universe,
from, from,
length, length,
scoring_strategy,
placeholder_search_logger, placeholder_search_logger,
)? )?
}; };
let BucketSortOutput { docids, mut all_candidates } = bucket_sort_output; let BucketSortOutput { docids, scores, mut all_candidates } = bucket_sort_output;
let fields_ids_map = ctx.index.fields_ids_map(ctx.txn)?;
// The candidates is the universe unless the exhaustive number of hits // The candidates is the universe unless the exhaustive number of hits
// is requested and a distinct attribute is set. // is requested and a distinct attribute is set.
if exhaustive_number_hits { if exhaustive_number_hits {
if let Some(f) = ctx.index.distinct_field(ctx.txn)? { if let Some(f) = ctx.index.distinct_field(ctx.txn)? {
if let Some(distinct_fid) = ctx.index.fields_ids_map(ctx.txn)?.id(f) { if let Some(distinct_fid) = fields_ids_map.id(f) {
all_candidates = apply_distinct_rule(ctx, distinct_fid, &all_candidates)?.remaining; all_candidates = apply_distinct_rule(ctx, distinct_fid, &all_candidates)?.remaining;
} }
} }
@ -440,6 +454,7 @@ pub fn execute_search(
Ok(PartialSearchResult { Ok(PartialSearchResult {
candidates: all_candidates, candidates: all_candidates,
document_scores: scores,
documents_ids: docids, documents_ids: docids,
located_query_terms, located_query_terms,
}) })
@ -491,4 +506,5 @@ pub struct PartialSearchResult {
pub located_query_terms: Option<Vec<LocatedQueryTerm>>, pub located_query_terms: Option<Vec<LocatedQueryTerm>>,
pub candidates: RoaringBitmap, pub candidates: RoaringBitmap,
pub documents_ids: Vec<DocumentId>, pub documents_ids: Vec<DocumentId>,
pub document_scores: Vec<Vec<ScoreDetails>>,
} }

View File

@ -342,6 +342,25 @@ impl QueryGraph {
} }
res res
} }
/// Number of words in the phrases in this query graph
pub(crate) fn words_in_phrases_count(&self, ctx: &SearchContext) -> usize {
let mut word_count = 0;
for (_, node) in self.nodes.iter() {
match &node.data {
QueryNodeData::Term(term) => {
let Some(phrase) = term.term_subset.original_phrase(ctx)
else {
continue
};
let phrase = ctx.phrase_interner.get(phrase);
word_count += phrase.words.iter().copied().filter(|a| a.is_some()).count()
}
_ => continue,
}
}
word_count
}
} }
fn add_node(nodes_data: &mut Vec<QueryNodeData>, node_data: QueryNodeData) -> u16 { fn add_node(nodes_data: &mut Vec<QueryNodeData>, node_data: QueryNodeData) -> u16 {

View File

@ -49,10 +49,15 @@ impl<G: RankingRuleGraphTrait> RankingRuleGraph<G> {
if let Some((cost_of_ignoring, forbidden_nodes)) = if let Some((cost_of_ignoring, forbidden_nodes)) =
cost_of_ignoring_node.get(dest_idx) cost_of_ignoring_node.get(dest_idx)
{ {
let dest = graph_nodes.get(dest_idx);
let dest_size = match &dest.data {
QueryNodeData::Term(term) => term.term_ids.len(),
_ => panic!(),
};
let new_edge_id = edges_store.insert(Some(Edge { let new_edge_id = edges_store.insert(Some(Edge {
source_node: source_id, source_node: source_id,
dest_node: dest_idx, dest_node: dest_idx,
cost: *cost_of_ignoring, cost: *cost_of_ignoring * dest_size as u32,
condition: None, condition: None,
nodes_to_skip: forbidden_nodes.clone(), nodes_to_skip: forbidden_nodes.clone(),
})); }));

View File

@ -1,6 +1,7 @@
use roaring::RoaringBitmap; use roaring::RoaringBitmap;
use super::{ComputedCondition, RankingRuleGraphTrait}; use super::{ComputedCondition, RankingRuleGraphTrait};
use crate::score_details::{Rank, ScoreDetails};
use crate::search::new::interner::{DedupInterner, Interned}; use crate::search::new::interner::{DedupInterner, Interned};
use crate::search::new::query_term::{ExactTerm, LocatedQueryTermSubset}; use crate::search::new::query_term::{ExactTerm, LocatedQueryTermSubset};
use crate::search::new::resolve_query_graph::compute_query_term_subset_docids; use crate::search::new::resolve_query_graph::compute_query_term_subset_docids;
@ -84,4 +85,8 @@ impl RankingRuleGraphTrait for ExactnessGraph {
Ok(vec![(0, exact_condition), (dest_node.term_ids.len() as u32, skip_condition)]) Ok(vec![(0, exact_condition), (dest_node.term_ids.len() as u32, skip_condition)])
} }
fn rank_to_score(rank: Rank) -> ScoreDetails {
ScoreDetails::Exactness(rank)
}
} }

View File

@ -2,6 +2,7 @@ use fxhash::FxHashSet;
use roaring::RoaringBitmap; use roaring::RoaringBitmap;
use super::{ComputedCondition, RankingRuleGraphTrait}; use super::{ComputedCondition, RankingRuleGraphTrait};
use crate::score_details::{Rank, ScoreDetails};
use crate::search::new::interner::{DedupInterner, Interned}; use crate::search::new::interner::{DedupInterner, Interned};
use crate::search::new::query_term::LocatedQueryTermSubset; use crate::search::new::query_term::LocatedQueryTermSubset;
use crate::search::new::resolve_query_graph::compute_query_term_subset_docids_within_field_id; use crate::search::new::resolve_query_graph::compute_query_term_subset_docids_within_field_id;
@ -68,13 +69,42 @@ impl RankingRuleGraphTrait for FidGraph {
} }
let mut edges = vec![]; let mut edges = vec![];
for fid in all_fields { for fid in all_fields.iter().copied() {
edges.push(( edges.push((
fid as u32 * term.term_ids.len() as u32, fid as u32 * term.term_ids.len() as u32,
conditions_interner.insert(FidCondition { term: term.clone(), fid }), conditions_interner.insert(FidCondition { term: term.clone(), fid }),
)); ));
} }
// always lookup the max_fid if we don't already and add an artificial condition for max scoring
let max_fid: Option<u16> = {
if let Some(max_fid) = ctx
.index
.searchable_fields_ids(ctx.txn)?
.map(|field_ids| field_ids.into_iter().max())
{
max_fid
} else {
ctx.index.fields_ids_map(ctx.txn)?.ids().max()
}
};
if let Some(max_fid) = max_fid {
if !all_fields.contains(&max_fid) {
edges.push((
max_fid as u32 * term.term_ids.len() as u32, // TODO improve the fid score i.e. fid^10.
conditions_interner.insert(FidCondition {
term: term.clone(), // TODO remove this ugly clone
fid: max_fid,
}),
));
}
}
Ok(edges) Ok(edges)
} }
fn rank_to_score(rank: Rank) -> ScoreDetails {
ScoreDetails::Fid(rank)
}
} }

View File

@ -41,6 +41,7 @@ use super::interner::{DedupInterner, FixedSizeInterner, Interned, MappedInterner
use super::query_term::LocatedQueryTermSubset; use super::query_term::LocatedQueryTermSubset;
use super::small_bitmap::SmallBitmap; use super::small_bitmap::SmallBitmap;
use super::{QueryGraph, QueryNode, SearchContext}; use super::{QueryGraph, QueryNode, SearchContext};
use crate::score_details::{Rank, ScoreDetails};
use crate::Result; use crate::Result;
pub struct ComputedCondition { pub struct ComputedCondition {
@ -110,6 +111,9 @@ pub trait RankingRuleGraphTrait: Sized + 'static {
source_node: Option<&LocatedQueryTermSubset>, source_node: Option<&LocatedQueryTermSubset>,
dest_node: &LocatedQueryTermSubset, dest_node: &LocatedQueryTermSubset,
) -> Result<Vec<(u32, Interned<Self::Condition>)>>; ) -> Result<Vec<(u32, Interned<Self::Condition>)>>;
/// Convert the rank of a path to its corresponding score for the ranking rule
fn rank_to_score(rank: Rank) -> ScoreDetails;
} }
/// The graph used by graph-based ranking rules. /// The graph used by graph-based ranking rules.

View File

@ -2,6 +2,7 @@ use fxhash::{FxHashMap, FxHashSet};
use roaring::RoaringBitmap; use roaring::RoaringBitmap;
use super::{ComputedCondition, RankingRuleGraphTrait}; use super::{ComputedCondition, RankingRuleGraphTrait};
use crate::score_details::{Rank, ScoreDetails};
use crate::search::new::interner::{DedupInterner, Interned}; use crate::search::new::interner::{DedupInterner, Interned};
use crate::search::new::query_term::LocatedQueryTermSubset; use crate::search::new::query_term::LocatedQueryTermSubset;
use crate::search::new::resolve_query_graph::compute_query_term_subset_docids_within_position; use crate::search::new::resolve_query_graph::compute_query_term_subset_docids_within_position;
@ -77,6 +78,8 @@ impl RankingRuleGraphTrait for PositionGraph {
let mut positions_for_costs = FxHashMap::<u32, Vec<u16>>::default(); let mut positions_for_costs = FxHashMap::<u32, Vec<u16>>::default();
for position in all_positions { for position in all_positions {
// FIXME: bucketed position???
let distance = position.abs_diff(*term.positions.start());
let cost = { let cost = {
let mut cost = 0; let mut cost = 0;
for i in 0..term.term_ids.len() { for i in 0..term.term_ids.len() {
@ -84,15 +87,17 @@ impl RankingRuleGraphTrait for PositionGraph {
// Because if two words are in the same bucketed position (e.g. 32) and consecutive, // Because if two words are in the same bucketed position (e.g. 32) and consecutive,
// then their position cost will be 32+32=64, but an ngram of these two words at the // then their position cost will be 32+32=64, but an ngram of these two words at the
// same position will have a cost of 32+32+1=65 // same position will have a cost of 32+32+1=65
cost += cost_from_position(position as u32 + i as u32); cost += cost_from_distance(distance as u32 + i as u32);
} }
cost cost
}; };
positions_for_costs.entry(cost).or_default().push(position); positions_for_costs.entry(cost).or_default().push(position);
} }
let mut edges = vec![]; let max_cost = term.term_ids.len() as u32 * 10;
let max_cost_exists = positions_for_costs.contains_key(&max_cost);
let mut edges = vec![];
for (cost, positions) in positions_for_costs { for (cost, positions) in positions_for_costs {
edges.push(( edges.push((
cost, cost,
@ -100,12 +105,25 @@ impl RankingRuleGraphTrait for PositionGraph {
)); ));
} }
if !max_cost_exists {
// artificial empty condition for computing max cost
edges.push((
max_cost,
conditions_interner
.insert(PositionCondition { term: term.clone(), positions: Vec::default() }),
));
}
Ok(edges) Ok(edges)
} }
fn rank_to_score(rank: Rank) -> ScoreDetails {
ScoreDetails::Position(rank)
}
} }
fn cost_from_position(sum_positions: u32) -> u32 { fn cost_from_distance(distance: u32) -> u32 {
match sum_positions { match distance {
0 => 0, 0 => 0,
1 => 1, 1 => 1,
2..=4 => 2, 2..=4 => 2,

View File

@ -12,11 +12,11 @@ pub fn build_edges(
left_term: Option<&LocatedQueryTermSubset>, left_term: Option<&LocatedQueryTermSubset>,
right_term: &LocatedQueryTermSubset, right_term: &LocatedQueryTermSubset,
) -> Result<Vec<(u32, Interned<ProximityCondition>)>> { ) -> Result<Vec<(u32, Interned<ProximityCondition>)>> {
let right_ngram_length = right_term.term_ids.len(); let right_ngram_max = right_term.term_ids.len().saturating_sub(1);
let Some(left_term) = left_term else { let Some(left_term) = left_term else {
return Ok(vec![( return Ok(vec![(
(right_ngram_length - 1) as u32, right_ngram_max as u32,
conditions_interner.insert(ProximityCondition::Term { term: right_term.clone() }), conditions_interner.insert(ProximityCondition::Term { term: right_term.clone() }),
)]) )])
}; };
@ -29,25 +29,25 @@ pub fn build_edges(
// The remaining query graph represents `the sun .. are beautiful` // The remaining query graph represents `the sun .. are beautiful`
// but `sun` and `are` have no proximity condition between them // but `sun` and `are` have no proximity condition between them
return Ok(vec![( return Ok(vec![(
(right_ngram_length - 1) as u32, right_ngram_max as u32,
conditions_interner.insert(ProximityCondition::Term { term: right_term.clone() }), conditions_interner.insert(ProximityCondition::Term { term: right_term.clone() }),
)]); )]);
} }
let mut conditions = vec![]; let mut conditions = vec![];
for cost in right_ngram_length..(7 + right_ngram_length) { for cost in right_ngram_max..(7 + right_ngram_max) {
conditions.push(( conditions.push((
cost as u32, cost as u32,
conditions_interner.insert(ProximityCondition::Uninit { conditions_interner.insert(ProximityCondition::Uninit {
left_term: left_term.clone(), left_term: left_term.clone(),
right_term: right_term.clone(), right_term: right_term.clone(),
cost: cost as u8, cost: (cost + 1) as u8,
}), }),
)) ))
} }
conditions.push(( conditions.push((
(7 + right_ngram_length) as u32, (7 + right_ngram_max) as u32,
conditions_interner.insert(ProximityCondition::Term { term: right_term.clone() }), conditions_interner.insert(ProximityCondition::Term { term: right_term.clone() }),
)); ));

View File

@ -4,6 +4,7 @@ pub mod compute_docids;
use roaring::RoaringBitmap; use roaring::RoaringBitmap;
use super::{ComputedCondition, RankingRuleGraphTrait}; use super::{ComputedCondition, RankingRuleGraphTrait};
use crate::score_details::{Rank, ScoreDetails};
use crate::search::new::interner::{DedupInterner, Interned}; use crate::search::new::interner::{DedupInterner, Interned};
use crate::search::new::query_term::LocatedQueryTermSubset; use crate::search::new::query_term::LocatedQueryTermSubset;
use crate::search::new::SearchContext; use crate::search::new::SearchContext;
@ -36,4 +37,8 @@ impl RankingRuleGraphTrait for ProximityGraph {
) -> Result<Vec<(u32, Interned<Self::Condition>)>> { ) -> Result<Vec<(u32, Interned<Self::Condition>)>> {
build::build_edges(ctx, conditions_interner, source_term, dest_term) build::build_edges(ctx, conditions_interner, source_term, dest_term)
} }
fn rank_to_score(rank: Rank) -> ScoreDetails {
ScoreDetails::Proximity(rank)
}
} }

View File

@ -1,6 +1,7 @@
use roaring::RoaringBitmap; use roaring::RoaringBitmap;
use super::{ComputedCondition, RankingRuleGraphTrait}; use super::{ComputedCondition, RankingRuleGraphTrait};
use crate::score_details::{self, Rank, ScoreDetails};
use crate::search::new::interner::{DedupInterner, Interned}; use crate::search::new::interner::{DedupInterner, Interned};
use crate::search::new::query_term::LocatedQueryTermSubset; use crate::search::new::query_term::LocatedQueryTermSubset;
use crate::search::new::resolve_query_graph::compute_query_term_subset_docids; use crate::search::new::resolve_query_graph::compute_query_term_subset_docids;
@ -75,4 +76,8 @@ impl RankingRuleGraphTrait for TypoGraph {
} }
Ok(edges) Ok(edges)
} }
fn rank_to_score(rank: Rank) -> ScoreDetails {
ScoreDetails::Typo(score_details::Typo::from_rank(rank))
}
} }

View File

@ -1,6 +1,7 @@
use roaring::RoaringBitmap; use roaring::RoaringBitmap;
use super::{ComputedCondition, RankingRuleGraphTrait}; use super::{ComputedCondition, RankingRuleGraphTrait};
use crate::score_details::{self, Rank, ScoreDetails};
use crate::search::new::interner::{DedupInterner, Interned}; use crate::search::new::interner::{DedupInterner, Interned};
use crate::search::new::query_term::LocatedQueryTermSubset; use crate::search::new::query_term::LocatedQueryTermSubset;
use crate::search::new::resolve_query_graph::compute_query_term_subset_docids; use crate::search::new::resolve_query_graph::compute_query_term_subset_docids;
@ -41,9 +42,10 @@ impl RankingRuleGraphTrait for WordsGraph {
_from: Option<&LocatedQueryTermSubset>, _from: Option<&LocatedQueryTermSubset>,
to_term: &LocatedQueryTermSubset, to_term: &LocatedQueryTermSubset,
) -> Result<Vec<(u32, Interned<Self::Condition>)>> { ) -> Result<Vec<(u32, Interned<Self::Condition>)>> {
Ok(vec![( Ok(vec![(0, conditions_interner.insert(WordsCondition { term: to_term.clone() }))])
to_term.term_ids.len() as u32, }
conditions_interner.insert(WordsCondition { term: to_term.clone() }),
)]) fn rank_to_score(rank: Rank) -> ScoreDetails {
ScoreDetails::Words(score_details::Words::from_rank(rank))
} }
} }

View File

@ -2,6 +2,7 @@ use roaring::RoaringBitmap;
use super::logger::SearchLogger; use super::logger::SearchLogger;
use super::{QueryGraph, SearchContext}; use super::{QueryGraph, SearchContext};
use crate::score_details::ScoreDetails;
use crate::Result; use crate::Result;
/// An internal trait implemented by only [`PlaceholderQuery`] and [`QueryGraph`] /// An internal trait implemented by only [`PlaceholderQuery`] and [`QueryGraph`]
@ -66,4 +67,6 @@ pub struct RankingRuleOutput<Q> {
pub query: Q, pub query: Q,
/// The allowed candidates for the child ranking rule /// The allowed candidates for the child ranking rule
pub candidates: RoaringBitmap, pub candidates: RoaringBitmap,
/// The score for the candidates of the current bucket
pub score: ScoreDetails,
} }

View File

@ -1,9 +1,11 @@
use heed::BytesDecode;
use roaring::RoaringBitmap; use roaring::RoaringBitmap;
use super::logger::SearchLogger; use super::logger::SearchLogger;
use super::{RankingRule, RankingRuleOutput, RankingRuleQueryTrait, SearchContext}; use super::{RankingRule, RankingRuleOutput, RankingRuleQueryTrait, SearchContext};
use crate::heed_codec::facet::FacetGroupKeyCodec; use crate::heed_codec::facet::{FacetGroupKeyCodec, OrderedF64Codec};
use crate::heed_codec::ByteSliceRefCodec; use crate::heed_codec::{ByteSliceRefCodec, StrRefCodec};
use crate::score_details::{self, ScoreDetails};
use crate::search::facet::{ascending_facet_sort, descending_facet_sort}; use crate::search::facet::{ascending_facet_sort, descending_facet_sort};
use crate::{FieldId, Index, Result}; use crate::{FieldId, Index, Result};
@ -49,6 +51,7 @@ pub struct Sort<'ctx, Query> {
is_ascending: bool, is_ascending: bool,
original_query: Option<Query>, original_query: Option<Query>,
iter: Option<RankingRuleOutputIterWrapper<'ctx, Query>>, iter: Option<RankingRuleOutputIterWrapper<'ctx, Query>>,
must_redact: bool,
} }
impl<'ctx, Query> Sort<'ctx, Query> { impl<'ctx, Query> Sort<'ctx, Query> {
pub fn new( pub fn new(
@ -59,15 +62,30 @@ impl<'ctx, Query> Sort<'ctx, Query> {
) -> Result<Self> { ) -> Result<Self> {
let fields_ids_map = index.fields_ids_map(rtxn)?; let fields_ids_map = index.fields_ids_map(rtxn)?;
let field_id = fields_ids_map.id(&field_name); let field_id = fields_ids_map.id(&field_name);
let must_redact = Self::must_redact(index, rtxn, &field_name)?;
Ok(Self { field_name, field_id, is_ascending, original_query: None, iter: None }) Ok(Self {
field_name,
field_id,
is_ascending,
original_query: None,
iter: None,
must_redact,
})
}
fn must_redact(index: &Index, rtxn: &'ctx heed::RoTxn, field_name: &str) -> Result<bool> {
let Some(displayed_fields) = index.displayed_fields(rtxn)?
else { return Ok(false); };
Ok(!displayed_fields.iter().any(|&field| field == field_name))
} }
} }
impl<'ctx, Query: RankingRuleQueryTrait> RankingRule<'ctx, Query> for Sort<'ctx, Query> { impl<'ctx, Query: RankingRuleQueryTrait> RankingRule<'ctx, Query> for Sort<'ctx, Query> {
fn id(&self) -> String { fn id(&self) -> String {
let Self { field_name, is_ascending, .. } = self; let Self { field_name, is_ascending, .. } = self;
format!("{field_name}:{}", if *is_ascending { "asc" } else { "desc " }) format!("{field_name}:{}", if *is_ascending { "asc" } else { "desc" })
} }
fn start_iteration( fn start_iteration(
&mut self, &mut self,
@ -118,12 +136,45 @@ impl<'ctx, Query: RankingRuleQueryTrait> RankingRule<'ctx, Query> for Sort<'ctx,
(itertools::Either::Right(number_iter), itertools::Either::Right(string_iter)) (itertools::Either::Right(number_iter), itertools::Either::Right(string_iter))
}; };
let number_iter = number_iter.map(|r| -> Result<_> {
let (docids, bytes) = r?;
Ok((
docids,
serde_json::Value::Number(
serde_json::Number::from_f64(
OrderedF64Codec::bytes_decode(bytes).expect("some number"),
)
.expect("too big float"),
),
))
});
let string_iter = string_iter.map(|r| -> Result<_> {
let (docids, bytes) = r?;
Ok((
docids,
serde_json::Value::String(
StrRefCodec::bytes_decode(bytes).expect("some string").to_owned(),
),
))
});
let query_graph = parent_query.clone(); let query_graph = parent_query.clone();
let ascending = self.is_ascending;
let field_name = self.field_name.clone();
let must_redact = self.must_redact;
RankingRuleOutputIterWrapper::new(Box::new(number_iter.chain(string_iter).map( RankingRuleOutputIterWrapper::new(Box::new(number_iter.chain(string_iter).map(
move |r| { move |r| {
let (docids, _) = r?; let (docids, value) = r?;
Ok(RankingRuleOutput { query: query_graph.clone(), candidates: docids }) Ok(RankingRuleOutput {
query: query_graph.clone(),
candidates: docids,
score: ScoreDetails::Sort(score_details::Sort {
field_name: field_name.clone(),
ascending,
redacted: must_redact,
value,
}),
})
}, },
))) )))
} }
@ -146,7 +197,16 @@ impl<'ctx, Query: RankingRuleQueryTrait> RankingRule<'ctx, Query> for Sort<'ctx,
Ok(Some(bucket)) Ok(Some(bucket))
} else { } else {
let query = self.original_query.as_ref().unwrap().clone(); let query = self.original_query.as_ref().unwrap().clone();
Ok(Some(RankingRuleOutput { query, candidates: universe.clone() })) Ok(Some(RankingRuleOutput {
query,
candidates: universe.clone(),
score: ScoreDetails::Sort(score_details::Sort {
field_name: self.field_name.clone(),
ascending: self.is_ascending,
redacted: self.must_redact,
value: serde_json::Value::Null,
}),
}))
} }
} }

View File

@ -122,8 +122,11 @@ fn test_attribute_fid_simple() {
let mut s = Search::new(&txn, &index); let mut s = Search::new(&txn, &index);
s.terms_matching_strategy(TermsMatchingStrategy::All); s.terms_matching_strategy(TermsMatchingStrategy::All);
s.query("the quick brown fox jumps over the lazy dog"); s.query("the quick brown fox jumps over the lazy dog");
let SearchResult { documents_ids, .. } = s.execute().unwrap(); s.scoring_strategy(crate::score_details::ScoringStrategy::Detailed);
insta::assert_snapshot!(format!("{documents_ids:?}"), @"[2, 6, 5, 4, 3, 9, 7, 8, 11, 10, 12, 13, 14, 0]"); let SearchResult { documents_ids, document_scores, .. } = s.execute().unwrap();
let document_ids_scores: Vec<_> =
documents_ids.iter().zip(document_scores.into_iter()).collect();
insta::assert_snapshot!(format!("{document_ids_scores:#?}"));
} }
#[test] #[test]
@ -135,6 +138,11 @@ fn test_attribute_fid_ngrams() {
let mut s = Search::new(&txn, &index); let mut s = Search::new(&txn, &index);
s.terms_matching_strategy(TermsMatchingStrategy::All); s.terms_matching_strategy(TermsMatchingStrategy::All);
s.query("the quick brown fox jumps over the lazy dog"); s.query("the quick brown fox jumps over the lazy dog");
let SearchResult { documents_ids, .. } = s.execute().unwrap(); s.scoring_strategy(crate::score_details::ScoringStrategy::Detailed);
insta::assert_snapshot!(format!("{documents_ids:?}"), @"[2, 6, 5, 4, 3, 9, 7, 8, 11, 10, 12, 13, 14, 0]");
let SearchResult { documents_ids, document_scores, .. } = s.execute().unwrap();
let document_ids_scores: Vec<_> =
documents_ids.iter().zip(document_scores.into_iter()).collect();
insta::assert_snapshot!(format!("{document_ids_scores:#?}"));
} }

View File

@ -137,8 +137,13 @@ fn test_attribute_position_simple() {
let mut s = Search::new(&txn, &index); let mut s = Search::new(&txn, &index);
s.terms_matching_strategy(TermsMatchingStrategy::All); s.terms_matching_strategy(TermsMatchingStrategy::All);
s.query("quick brown"); s.query("quick brown");
let SearchResult { documents_ids, .. } = s.execute().unwrap(); s.scoring_strategy(crate::score_details::ScoringStrategy::Detailed);
insta::assert_snapshot!(format!("{documents_ids:?}"), @"[10, 11, 12, 13, 2, 3, 4, 1, 0, 6, 8, 7, 9, 5]");
let SearchResult { documents_ids, document_scores, .. } = s.execute().unwrap();
let document_ids_scores: Vec<_> =
documents_ids.iter().zip(document_scores.into_iter()).collect();
insta::assert_snapshot!(format!("{document_ids_scores:#?}"));
} }
#[test] #[test]
fn test_attribute_position_repeated() { fn test_attribute_position_repeated() {
@ -149,8 +154,13 @@ fn test_attribute_position_repeated() {
let mut s = Search::new(&txn, &index); let mut s = Search::new(&txn, &index);
s.terms_matching_strategy(TermsMatchingStrategy::All); s.terms_matching_strategy(TermsMatchingStrategy::All);
s.query("a a a a a"); s.query("a a a a a");
let SearchResult { documents_ids, .. } = s.execute().unwrap(); s.scoring_strategy(crate::score_details::ScoringStrategy::Detailed);
insta::assert_snapshot!(format!("{documents_ids:?}"), @"[5, 7, 8, 9, 6]");
let SearchResult { documents_ids, document_scores, .. } = s.execute().unwrap();
let document_ids_scores: Vec<_> =
documents_ids.iter().zip(document_scores.into_iter()).collect();
insta::assert_snapshot!(format!("{document_ids_scores:#?}"));
} }
#[test] #[test]
@ -162,8 +172,13 @@ fn test_attribute_position_different_fields() {
let mut s = Search::new(&txn, &index); let mut s = Search::new(&txn, &index);
s.terms_matching_strategy(TermsMatchingStrategy::All); s.terms_matching_strategy(TermsMatchingStrategy::All);
s.query("quick brown"); s.query("quick brown");
let SearchResult { documents_ids, .. } = s.execute().unwrap(); s.scoring_strategy(crate::score_details::ScoringStrategy::Detailed);
insta::assert_snapshot!(format!("{documents_ids:?}"), @"[10, 11, 12, 13, 2, 3, 4, 1, 0, 6, 8, 7, 9, 5]");
let SearchResult { documents_ids, document_scores, .. } = s.execute().unwrap();
let document_ids_scores: Vec<_> =
documents_ids.iter().zip(document_scores.into_iter()).collect();
insta::assert_snapshot!(format!("{document_ids_scores:#?}"));
} }
#[test] #[test]
@ -175,6 +190,11 @@ fn test_attribute_position_ngrams() {
let mut s = Search::new(&txn, &index); let mut s = Search::new(&txn, &index);
s.terms_matching_strategy(TermsMatchingStrategy::All); s.terms_matching_strategy(TermsMatchingStrategy::All);
s.query("quick brown"); s.query("quick brown");
let SearchResult { documents_ids, .. } = s.execute().unwrap(); s.scoring_strategy(crate::score_details::ScoringStrategy::Detailed);
insta::assert_snapshot!(format!("{documents_ids:?}"), @"[10, 11, 12, 13, 2, 3, 4, 1, 0, 6, 8, 7, 9, 5]");
let SearchResult { documents_ids, document_scores, .. } = s.execute().unwrap();
let document_ids_scores: Vec<_> =
documents_ids.iter().zip(document_scores.into_iter()).collect();
insta::assert_snapshot!(format!("{document_ids_scores:#?}"));
} }

View File

@ -474,8 +474,14 @@ fn test_exactness_simple_ordered() {
let mut s = Search::new(&txn, &index); let mut s = Search::new(&txn, &index);
s.terms_matching_strategy(TermsMatchingStrategy::Last); s.terms_matching_strategy(TermsMatchingStrategy::Last);
s.query("the quick brown fox jumps over the lazy dog"); s.query("the quick brown fox jumps over the lazy dog");
let SearchResult { documents_ids, .. } = s.execute().unwrap(); s.scoring_strategy(crate::score_details::ScoringStrategy::Detailed);
insta::assert_snapshot!(format!("{documents_ids:?}"), @"[9, 8, 7, 6, 5, 4, 3, 2, 1]");
let SearchResult { documents_ids, document_scores, .. } = s.execute().unwrap();
let document_ids_scores: Vec<_> =
documents_ids.iter().zip(document_scores.into_iter()).collect();
insta::assert_snapshot!(format!("{document_ids_scores:#?}"));
let texts = collect_field_values(&index, &txn, "text", &documents_ids); let texts = collect_field_values(&index, &txn, "text", &documents_ids);
insta::assert_debug_snapshot!(texts, @r###" insta::assert_debug_snapshot!(texts, @r###"
[ [
@ -501,8 +507,14 @@ fn test_exactness_simple_reversed() {
let mut s = Search::new(&txn, &index); let mut s = Search::new(&txn, &index);
s.terms_matching_strategy(TermsMatchingStrategy::Last); s.terms_matching_strategy(TermsMatchingStrategy::Last);
s.query("the quick brown fox jumps over the lazy dog"); s.query("the quick brown fox jumps over the lazy dog");
let SearchResult { documents_ids, .. } = s.execute().unwrap(); s.scoring_strategy(crate::score_details::ScoringStrategy::Detailed);
insta::assert_snapshot!(format!("{documents_ids:?}"), @"[9, 8, 3, 4, 5, 6, 7]");
let SearchResult { documents_ids, document_scores, .. } = s.execute().unwrap();
let document_ids_scores: Vec<_> =
documents_ids.iter().zip(document_scores.into_iter()).collect();
insta::assert_snapshot!(format!("{document_ids_scores:#?}"));
let texts = collect_field_values(&index, &txn, "text", &documents_ids); let texts = collect_field_values(&index, &txn, "text", &documents_ids);
insta::assert_debug_snapshot!(texts, @r###" insta::assert_debug_snapshot!(texts, @r###"
[ [
@ -519,8 +531,14 @@ fn test_exactness_simple_reversed() {
let mut s = Search::new(&txn, &index); let mut s = Search::new(&txn, &index);
s.terms_matching_strategy(TermsMatchingStrategy::Last); s.terms_matching_strategy(TermsMatchingStrategy::Last);
s.query("the quick brown fox jumps over the lazy dog"); s.query("the quick brown fox jumps over the lazy dog");
let SearchResult { documents_ids, .. } = s.execute().unwrap(); s.scoring_strategy(crate::score_details::ScoringStrategy::Detailed);
insta::assert_snapshot!(format!("{documents_ids:?}"), @"[9, 8, 3, 4, 5, 6, 7]");
let SearchResult { documents_ids, document_scores, .. } = s.execute().unwrap();
let document_ids_scores: Vec<_> =
documents_ids.iter().zip(document_scores.into_iter()).collect();
insta::assert_snapshot!(format!("{document_ids_scores:#?}"));
let texts = collect_field_values(&index, &txn, "text", &documents_ids); let texts = collect_field_values(&index, &txn, "text", &documents_ids);
insta::assert_debug_snapshot!(texts, @r###" insta::assert_debug_snapshot!(texts, @r###"
[ [
@ -544,8 +562,14 @@ fn test_exactness_simple_random() {
let mut s = Search::new(&txn, &index); let mut s = Search::new(&txn, &index);
s.terms_matching_strategy(TermsMatchingStrategy::Last); s.terms_matching_strategy(TermsMatchingStrategy::Last);
s.query("the quick brown fox jumps over the lazy dog"); s.query("the quick brown fox jumps over the lazy dog");
let SearchResult { documents_ids, .. } = s.execute().unwrap(); s.scoring_strategy(crate::score_details::ScoringStrategy::Detailed);
insta::assert_snapshot!(format!("{documents_ids:?}"), @"[8, 7, 4, 6, 3, 5]");
let SearchResult { documents_ids, document_scores, .. } = s.execute().unwrap();
let document_ids_scores: Vec<_> =
documents_ids.iter().zip(document_scores.into_iter()).collect();
insta::assert_snapshot!(format!("{document_ids_scores:#?}"));
let texts = collect_field_values(&index, &txn, "text", &documents_ids); let texts = collect_field_values(&index, &txn, "text", &documents_ids);
insta::assert_debug_snapshot!(texts, @r###" insta::assert_debug_snapshot!(texts, @r###"
[ [
@ -568,8 +592,14 @@ fn test_exactness_attribute_starts_with_simple() {
let mut s = Search::new(&txn, &index); let mut s = Search::new(&txn, &index);
s.terms_matching_strategy(TermsMatchingStrategy::Last); s.terms_matching_strategy(TermsMatchingStrategy::Last);
s.query("this balcony"); s.query("this balcony");
let SearchResult { documents_ids, .. } = s.execute().unwrap(); s.scoring_strategy(crate::score_details::ScoringStrategy::Detailed);
insta::assert_snapshot!(format!("{documents_ids:?}"), @"[2, 1, 0]");
let SearchResult { documents_ids, document_scores, .. } = s.execute().unwrap();
let document_ids_scores: Vec<_> =
documents_ids.iter().zip(document_scores.into_iter()).collect();
insta::assert_snapshot!(format!("{document_ids_scores:#?}"));
let texts = collect_field_values(&index, &txn, "text", &documents_ids); let texts = collect_field_values(&index, &txn, "text", &documents_ids);
insta::assert_debug_snapshot!(texts, @r###" insta::assert_debug_snapshot!(texts, @r###"
[ [
@ -589,8 +619,14 @@ fn test_exactness_attribute_starts_with_phrase() {
let mut s = Search::new(&txn, &index); let mut s = Search::new(&txn, &index);
s.terms_matching_strategy(TermsMatchingStrategy::Last); s.terms_matching_strategy(TermsMatchingStrategy::Last);
s.query("\"overlooking the sea\" is a beautiful balcony"); s.query("\"overlooking the sea\" is a beautiful balcony");
let SearchResult { documents_ids, .. } = s.execute().unwrap(); s.scoring_strategy(crate::score_details::ScoringStrategy::Detailed);
insta::assert_snapshot!(format!("{documents_ids:?}"), @"[6, 5, 4, 1]");
let SearchResult { documents_ids, document_scores, .. } = s.execute().unwrap();
let document_ids_scores: Vec<_> =
documents_ids.iter().zip(document_scores.into_iter()).collect();
insta::assert_snapshot!(format!("{document_ids_scores:#?}"));
let texts = collect_field_values(&index, &txn, "text", &documents_ids); let texts = collect_field_values(&index, &txn, "text", &documents_ids);
insta::assert_debug_snapshot!(texts, @r###" insta::assert_debug_snapshot!(texts, @r###"
[ [
@ -604,8 +640,14 @@ fn test_exactness_attribute_starts_with_phrase() {
let mut s = Search::new(&txn, &index); let mut s = Search::new(&txn, &index);
s.terms_matching_strategy(TermsMatchingStrategy::Last); s.terms_matching_strategy(TermsMatchingStrategy::Last);
s.query("overlooking the sea is a beautiful balcony"); s.query("overlooking the sea is a beautiful balcony");
let SearchResult { documents_ids, .. } = s.execute().unwrap(); s.scoring_strategy(crate::score_details::ScoringStrategy::Detailed);
insta::assert_snapshot!(format!("{documents_ids:?}"), @"[6, 5, 4, 3, 1, 7]");
let SearchResult { documents_ids, document_scores, .. } = s.execute().unwrap();
let document_ids_scores: Vec<_> =
documents_ids.iter().zip(document_scores.into_iter()).collect();
insta::assert_snapshot!(format!("{document_ids_scores:#?}"));
let texts = collect_field_values(&index, &txn, "text", &documents_ids); let texts = collect_field_values(&index, &txn, "text", &documents_ids);
insta::assert_debug_snapshot!(texts, @r###" insta::assert_debug_snapshot!(texts, @r###"
[ [
@ -628,8 +670,14 @@ fn test_exactness_all_candidates_with_typo() {
let mut s = Search::new(&txn, &index); let mut s = Search::new(&txn, &index);
s.terms_matching_strategy(TermsMatchingStrategy::Last); s.terms_matching_strategy(TermsMatchingStrategy::Last);
s.query("overlocking the sea is a beautiful balcony"); s.query("overlocking the sea is a beautiful balcony");
let SearchResult { documents_ids, .. } = s.execute().unwrap(); s.scoring_strategy(crate::score_details::ScoringStrategy::Detailed);
insta::assert_snapshot!(format!("{documents_ids:?}"), @"[4, 5, 6, 1, 7]");
let SearchResult { documents_ids, document_scores, .. } = s.execute().unwrap();
let document_ids_scores: Vec<_> =
documents_ids.iter().zip(document_scores.into_iter()).collect();
insta::assert_snapshot!(format!("{document_ids_scores:#?}"));
let texts = collect_field_values(&index, &txn, "text", &documents_ids); let texts = collect_field_values(&index, &txn, "text", &documents_ids);
// "overlooking" is returned here because the term matching strategy allows it // "overlooking" is returned here because the term matching strategy allows it
// but it has the worst exactness score (0 exact words) // but it has the worst exactness score (0 exact words)
@ -659,8 +707,14 @@ fn test_exactness_after_words() {
let mut s = Search::new(&txn, &index); let mut s = Search::new(&txn, &index);
s.terms_matching_strategy(TermsMatchingStrategy::Last); s.terms_matching_strategy(TermsMatchingStrategy::Last);
s.query("the quick brown fox jumps over the lazy dog"); s.query("the quick brown fox jumps over the lazy dog");
let SearchResult { documents_ids, .. } = s.execute().unwrap(); s.scoring_strategy(crate::score_details::ScoringStrategy::Detailed);
insta::assert_snapshot!(format!("{documents_ids:?}"), @"[19, 9, 18, 8, 17, 16, 6, 7, 15, 5, 14, 4, 13, 3, 12, 2, 1, 11]");
let SearchResult { documents_ids, document_scores, .. } = s.execute().unwrap();
let document_ids_scores: Vec<_> =
documents_ids.iter().zip(document_scores.into_iter()).collect();
insta::assert_snapshot!(format!("{document_ids_scores:#?}"));
let texts = collect_field_values(&index, &txn, "text", &documents_ids); let texts = collect_field_values(&index, &txn, "text", &documents_ids);
insta::assert_debug_snapshot!(texts, @r###" insta::assert_debug_snapshot!(texts, @r###"
@ -702,7 +756,13 @@ fn test_words_after_exactness() {
let mut s = Search::new(&txn, &index); let mut s = Search::new(&txn, &index);
s.terms_matching_strategy(TermsMatchingStrategy::Last); s.terms_matching_strategy(TermsMatchingStrategy::Last);
s.query("the quick brown fox jumps over the lazy dog"); s.query("the quick brown fox jumps over the lazy dog");
let SearchResult { documents_ids, .. } = s.execute().unwrap(); s.scoring_strategy(crate::score_details::ScoringStrategy::Detailed);
let SearchResult { documents_ids, document_scores, .. } = s.execute().unwrap();
let document_ids_scores: Vec<_> =
documents_ids.iter().zip(document_scores.into_iter()).collect();
insta::assert_snapshot!(format!("{document_ids_scores:#?}"));
insta::assert_snapshot!(format!("{documents_ids:?}"), @"[19, 9, 18, 8, 17, 16, 6, 7, 15, 5, 14, 4, 13, 3, 12, 2, 1, 11]"); insta::assert_snapshot!(format!("{documents_ids:?}"), @"[19, 9, 18, 8, 17, 16, 6, 7, 15, 5, 14, 4, 13, 3, 12, 2, 1, 11]");
let texts = collect_field_values(&index, &txn, "text", &documents_ids); let texts = collect_field_values(&index, &txn, "text", &documents_ids);
@ -745,7 +805,14 @@ fn test_proximity_after_exactness() {
let mut s = Search::new(&txn, &index); let mut s = Search::new(&txn, &index);
s.terms_matching_strategy(TermsMatchingStrategy::Last); s.terms_matching_strategy(TermsMatchingStrategy::Last);
s.query("the quick brown fox jumps over the lazy dog"); s.query("the quick brown fox jumps over the lazy dog");
let SearchResult { documents_ids, .. } = s.execute().unwrap(); s.scoring_strategy(crate::score_details::ScoringStrategy::Detailed);
let SearchResult { documents_ids, document_scores, .. } = s.execute().unwrap();
let document_ids_scores: Vec<_> =
documents_ids.iter().zip(document_scores.into_iter()).collect();
insta::assert_snapshot!(format!("{document_ids_scores:#?}"));
insta::assert_snapshot!(format!("{documents_ids:?}"), @"[2, 1, 0, 4, 5, 8, 7, 3, 6]"); insta::assert_snapshot!(format!("{documents_ids:?}"), @"[2, 1, 0, 4, 5, 8, 7, 3, 6]");
let texts = collect_field_values(&index, &txn, "text", &documents_ids); let texts = collect_field_values(&index, &txn, "text", &documents_ids);
@ -776,7 +843,13 @@ fn test_proximity_after_exactness() {
let mut s = Search::new(&txn, &index); let mut s = Search::new(&txn, &index);
s.terms_matching_strategy(TermsMatchingStrategy::Last); s.terms_matching_strategy(TermsMatchingStrategy::Last);
s.query("the quick brown fox jumps over the lazy dog"); s.query("the quick brown fox jumps over the lazy dog");
let SearchResult { documents_ids, .. } = s.execute().unwrap(); s.scoring_strategy(crate::score_details::ScoringStrategy::Detailed);
let SearchResult { documents_ids, document_scores, .. } = s.execute().unwrap();
let document_ids_scores: Vec<_> =
documents_ids.iter().zip(document_scores.into_iter()).collect();
insta::assert_snapshot!(format!("{document_ids_scores:#?}"));
insta::assert_snapshot!(format!("{documents_ids:?}"), @"[0, 1, 2]"); insta::assert_snapshot!(format!("{documents_ids:?}"), @"[0, 1, 2]");
let texts = collect_field_values(&index, &txn, "text", &documents_ids); let texts = collect_field_values(&index, &txn, "text", &documents_ids);
@ -804,7 +877,13 @@ fn test_exactness_followed_by_typo_prefer_no_typo_prefix() {
let mut s = Search::new(&txn, &index); let mut s = Search::new(&txn, &index);
s.terms_matching_strategy(TermsMatchingStrategy::Last); s.terms_matching_strategy(TermsMatchingStrategy::Last);
s.query("quick brown fox extra"); s.query("quick brown fox extra");
let SearchResult { documents_ids, .. } = s.execute().unwrap(); s.scoring_strategy(crate::score_details::ScoringStrategy::Detailed);
let SearchResult { documents_ids, document_scores, .. } = s.execute().unwrap();
let document_ids_scores: Vec<_> =
documents_ids.iter().zip(document_scores.into_iter()).collect();
insta::assert_snapshot!(format!("{document_ids_scores:#?}"));
insta::assert_snapshot!(format!("{documents_ids:?}"), @"[2, 1, 0, 4, 3]"); insta::assert_snapshot!(format!("{documents_ids:?}"), @"[2, 1, 0, 4, 3]");
let texts = collect_field_values(&index, &txn, "text", &documents_ids); let texts = collect_field_values(&index, &txn, "text", &documents_ids);
@ -834,7 +913,13 @@ fn test_typo_followed_by_exactness() {
let mut s = Search::new(&txn, &index); let mut s = Search::new(&txn, &index);
s.terms_matching_strategy(TermsMatchingStrategy::Last); s.terms_matching_strategy(TermsMatchingStrategy::Last);
s.query("extraordinarily quick brown fox"); s.query("extraordinarily quick brown fox");
let SearchResult { documents_ids, .. } = s.execute().unwrap(); s.scoring_strategy(crate::score_details::ScoringStrategy::Detailed);
let SearchResult { documents_ids, document_scores, .. } = s.execute().unwrap();
let document_ids_scores: Vec<_> =
documents_ids.iter().zip(document_scores.into_iter()).collect();
insta::assert_snapshot!(format!("{document_ids_scores:#?}"));
insta::assert_snapshot!(format!("{documents_ids:?}"), @"[1, 0, 4, 3]"); insta::assert_snapshot!(format!("{documents_ids:?}"), @"[1, 0, 4, 3]");
let texts = collect_field_values(&index, &txn, "text", &documents_ids); let texts = collect_field_values(&index, &txn, "text", &documents_ids);

View File

@ -7,6 +7,7 @@ use heed::RoTxn;
use maplit::hashset; use maplit::hashset;
use crate::index::tests::TempIndex; use crate::index::tests::TempIndex;
use crate::score_details::ScoreDetails;
use crate::search::new::tests::collect_field_values; use crate::search::new::tests::collect_field_values;
use crate::{AscDesc, Criterion, GeoSortStrategy, Member, Search, SearchResult}; use crate::{AscDesc, Criterion, GeoSortStrategy, Member, Search, SearchResult};
@ -28,30 +29,37 @@ fn execute_iterative_and_rtree_returns_the_same<'a>(
rtxn: &RoTxn<'a>, rtxn: &RoTxn<'a>,
index: &TempIndex, index: &TempIndex,
search: &mut Search<'a>, search: &mut Search<'a>,
) -> Vec<usize> { ) -> (Vec<usize>, Vec<Vec<ScoreDetails>>) {
search.geo_sort_strategy(GeoSortStrategy::AlwaysIterative(2)); search.geo_sort_strategy(GeoSortStrategy::AlwaysIterative(2));
let SearchResult { documents_ids, .. } = search.execute().unwrap(); let SearchResult { documents_ids, document_scores: iterative_scores_bucketed, .. } =
search.execute().unwrap();
let iterative_ids_bucketed = collect_field_values(index, rtxn, "id", &documents_ids); let iterative_ids_bucketed = collect_field_values(index, rtxn, "id", &documents_ids);
search.geo_sort_strategy(GeoSortStrategy::AlwaysIterative(1000)); search.geo_sort_strategy(GeoSortStrategy::AlwaysIterative(1000));
let SearchResult { documents_ids, .. } = search.execute().unwrap(); let SearchResult { documents_ids, document_scores: iterative_scores, .. } =
search.execute().unwrap();
let iterative_ids = collect_field_values(index, rtxn, "id", &documents_ids); let iterative_ids = collect_field_values(index, rtxn, "id", &documents_ids);
assert_eq!(iterative_ids_bucketed, iterative_ids, "iterative bucket"); assert_eq!(iterative_ids_bucketed, iterative_ids, "iterative bucket");
assert_eq!(iterative_scores_bucketed, iterative_scores, "iterative bucket score");
search.geo_sort_strategy(GeoSortStrategy::AlwaysRtree(2)); search.geo_sort_strategy(GeoSortStrategy::AlwaysRtree(2));
let SearchResult { documents_ids, .. } = search.execute().unwrap(); let SearchResult { documents_ids, document_scores: rtree_scores_bucketed, .. } =
search.execute().unwrap();
let rtree_ids_bucketed = collect_field_values(index, rtxn, "id", &documents_ids); let rtree_ids_bucketed = collect_field_values(index, rtxn, "id", &documents_ids);
search.geo_sort_strategy(GeoSortStrategy::AlwaysRtree(1000)); search.geo_sort_strategy(GeoSortStrategy::AlwaysRtree(1000));
let SearchResult { documents_ids, .. } = search.execute().unwrap(); let SearchResult { documents_ids, document_scores: rtree_scores, .. } =
search.execute().unwrap();
let rtree_ids = collect_field_values(index, rtxn, "id", &documents_ids); let rtree_ids = collect_field_values(index, rtxn, "id", &documents_ids);
assert_eq!(rtree_ids_bucketed, rtree_ids, "rtree bucket"); assert_eq!(rtree_ids_bucketed, rtree_ids, "rtree bucket");
assert_eq!(rtree_scores_bucketed, rtree_scores, "rtree bucket score");
assert_eq!(iterative_ids, rtree_ids, "iterative vs rtree"); assert_eq!(iterative_ids, rtree_ids, "iterative vs rtree");
assert_eq!(iterative_scores, rtree_scores, "iterative vs rtree scores");
iterative_ids.into_iter().map(|id| id.parse().unwrap()).collect() (iterative_ids.into_iter().map(|id| id.parse().unwrap()).collect(), iterative_scores)
} }
#[test] #[test]
@ -73,14 +81,17 @@ fn test_geo_sort() {
let rtxn = index.read_txn().unwrap(); let rtxn = index.read_txn().unwrap();
let mut s = Search::new(&rtxn, &index); let mut s = Search::new(&rtxn, &index);
s.scoring_strategy(crate::score_details::ScoringStrategy::Detailed);
s.sort_criteria(vec![AscDesc::Asc(Member::Geo([0., 0.]))]); s.sort_criteria(vec![AscDesc::Asc(Member::Geo([0., 0.]))]);
let ids = execute_iterative_and_rtree_returns_the_same(&rtxn, &index, &mut s); let (ids, scores) = execute_iterative_and_rtree_returns_the_same(&rtxn, &index, &mut s);
insta::assert_snapshot!(format!("{ids:?}"), @"[0, 1, 2, 3, 4, 5, 6, 8, 7, 10, 9]"); insta::assert_snapshot!(format!("{ids:?}"), @"[0, 1, 2, 3, 4, 5, 6, 8, 7, 10, 9]");
insta::assert_snapshot!(format!("{scores:#?}"));
s.sort_criteria(vec![AscDesc::Desc(Member::Geo([0., 0.]))]); s.sort_criteria(vec![AscDesc::Desc(Member::Geo([0., 0.]))]);
let ids = execute_iterative_and_rtree_returns_the_same(&rtxn, &index, &mut s); let (ids, scores) = execute_iterative_and_rtree_returns_the_same(&rtxn, &index, &mut s);
insta::assert_snapshot!(format!("{ids:?}"), @"[5, 4, 3, 2, 1, 0, 6, 8, 7, 10, 9]"); insta::assert_snapshot!(format!("{ids:?}"), @"[5, 4, 3, 2, 1, 0, 6, 8, 7, 10, 9]");
insta::assert_snapshot!(format!("{scores:#?}"));
} }
#[test] #[test]
@ -101,52 +112,63 @@ fn test_geo_sort_around_the_edge_of_the_flat_earth() {
let rtxn = index.read_txn().unwrap(); let rtxn = index.read_txn().unwrap();
let mut s = Search::new(&rtxn, &index); let mut s = Search::new(&rtxn, &index);
s.scoring_strategy(crate::score_details::ScoringStrategy::Detailed);
// --- asc // --- asc
s.sort_criteria(vec![AscDesc::Asc(Member::Geo([0., 0.]))]); s.sort_criteria(vec![AscDesc::Asc(Member::Geo([0., 0.]))]);
let ids = execute_iterative_and_rtree_returns_the_same(&rtxn, &index, &mut s); let (ids, scores) = execute_iterative_and_rtree_returns_the_same(&rtxn, &index, &mut s);
insta::assert_snapshot!(format!("{ids:?}"), @"[0, 1, 2, 3, 4]"); insta::assert_snapshot!(format!("{ids:?}"), @"[0, 1, 2, 3, 4]");
insta::assert_snapshot!(format!("{scores:#?}"));
// ensuring the lat doesn't wrap around // ensuring the lat doesn't wrap around
s.sort_criteria(vec![AscDesc::Asc(Member::Geo([85., 0.]))]); s.sort_criteria(vec![AscDesc::Asc(Member::Geo([85., 0.]))]);
let ids = execute_iterative_and_rtree_returns_the_same(&rtxn, &index, &mut s); let (ids, scores) = execute_iterative_and_rtree_returns_the_same(&rtxn, &index, &mut s);
insta::assert_snapshot!(format!("{ids:?}"), @"[1, 0, 3, 4, 2]"); insta::assert_snapshot!(format!("{ids:?}"), @"[1, 0, 3, 4, 2]");
insta::assert_snapshot!(format!("{scores:#?}"));
s.sort_criteria(vec![AscDesc::Asc(Member::Geo([-85., 0.]))]); s.sort_criteria(vec![AscDesc::Asc(Member::Geo([-85., 0.]))]);
let ids = execute_iterative_and_rtree_returns_the_same(&rtxn, &index, &mut s); let (ids, scores) = execute_iterative_and_rtree_returns_the_same(&rtxn, &index, &mut s);
insta::assert_snapshot!(format!("{ids:?}"), @"[2, 0, 3, 4, 1]"); insta::assert_snapshot!(format!("{ids:?}"), @"[2, 0, 3, 4, 1]");
insta::assert_snapshot!(format!("{scores:#?}"));
// ensuring the lng does wrap around // ensuring the lng does wrap around
s.sort_criteria(vec![AscDesc::Asc(Member::Geo([0., 175.]))]); s.sort_criteria(vec![AscDesc::Asc(Member::Geo([0., 175.]))]);
let ids = execute_iterative_and_rtree_returns_the_same(&rtxn, &index, &mut s); let (ids, scores) = execute_iterative_and_rtree_returns_the_same(&rtxn, &index, &mut s);
insta::assert_snapshot!(format!("{ids:?}"), @"[3, 4, 2, 1, 0]"); insta::assert_snapshot!(format!("{ids:?}"), @"[3, 4, 2, 1, 0]");
insta::assert_snapshot!(format!("{scores:#?}"));
s.sort_criteria(vec![AscDesc::Asc(Member::Geo([0., -175.]))]); s.sort_criteria(vec![AscDesc::Asc(Member::Geo([0., -175.]))]);
let ids = execute_iterative_and_rtree_returns_the_same(&rtxn, &index, &mut s); let (ids, scores) = execute_iterative_and_rtree_returns_the_same(&rtxn, &index, &mut s);
insta::assert_snapshot!(format!("{ids:?}"), @"[4, 3, 2, 1, 0]"); insta::assert_snapshot!(format!("{ids:?}"), @"[4, 3, 2, 1, 0]");
insta::assert_snapshot!(format!("{scores:#?}"));
// --- desc // --- desc
s.sort_criteria(vec![AscDesc::Desc(Member::Geo([0., 0.]))]); s.sort_criteria(vec![AscDesc::Desc(Member::Geo([0., 0.]))]);
let ids = execute_iterative_and_rtree_returns_the_same(&rtxn, &index, &mut s); let (ids, scores) = execute_iterative_and_rtree_returns_the_same(&rtxn, &index, &mut s);
insta::assert_snapshot!(format!("{ids:?}"), @"[4, 3, 2, 1, 0]"); insta::assert_snapshot!(format!("{ids:?}"), @"[4, 3, 2, 1, 0]");
insta::assert_snapshot!(format!("{scores:#?}"));
// ensuring the lat doesn't wrap around // ensuring the lat doesn't wrap around
s.sort_criteria(vec![AscDesc::Desc(Member::Geo([85., 0.]))]); s.sort_criteria(vec![AscDesc::Desc(Member::Geo([85., 0.]))]);
let ids = execute_iterative_and_rtree_returns_the_same(&rtxn, &index, &mut s); let (ids, scores) = execute_iterative_and_rtree_returns_the_same(&rtxn, &index, &mut s);
insta::assert_snapshot!(format!("{ids:?}"), @"[2, 4, 3, 0, 1]"); insta::assert_snapshot!(format!("{ids:?}"), @"[2, 4, 3, 0, 1]");
insta::assert_snapshot!(format!("{scores:#?}"));
s.sort_criteria(vec![AscDesc::Desc(Member::Geo([-85., 0.]))]); s.sort_criteria(vec![AscDesc::Desc(Member::Geo([-85., 0.]))]);
let ids = execute_iterative_and_rtree_returns_the_same(&rtxn, &index, &mut s); let (ids, scores) = execute_iterative_and_rtree_returns_the_same(&rtxn, &index, &mut s);
insta::assert_snapshot!(format!("{ids:?}"), @"[1, 4, 3, 0, 2]"); insta::assert_snapshot!(format!("{ids:?}"), @"[1, 4, 3, 0, 2]");
insta::assert_snapshot!(format!("{scores:#?}"));
// ensuring the lng does wrap around // ensuring the lng does wrap around
s.sort_criteria(vec![AscDesc::Desc(Member::Geo([0., 175.]))]); s.sort_criteria(vec![AscDesc::Desc(Member::Geo([0., 175.]))]);
let ids = execute_iterative_and_rtree_returns_the_same(&rtxn, &index, &mut s); let (ids, scores) = execute_iterative_and_rtree_returns_the_same(&rtxn, &index, &mut s);
insta::assert_snapshot!(format!("{ids:?}"), @"[0, 1, 2, 4, 3]"); insta::assert_snapshot!(format!("{ids:?}"), @"[0, 1, 2, 4, 3]");
insta::assert_snapshot!(format!("{scores:#?}"));
s.sort_criteria(vec![AscDesc::Desc(Member::Geo([0., -175.]))]); s.sort_criteria(vec![AscDesc::Desc(Member::Geo([0., -175.]))]);
let ids = execute_iterative_and_rtree_returns_the_same(&rtxn, &index, &mut s); let (ids, scores) = execute_iterative_and_rtree_returns_the_same(&rtxn, &index, &mut s);
insta::assert_snapshot!(format!("{ids:?}"), @"[0, 1, 2, 3, 4]"); insta::assert_snapshot!(format!("{ids:?}"), @"[0, 1, 2, 3, 4]");
insta::assert_snapshot!(format!("{scores:#?}"));
} }
#[test] #[test]
@ -166,19 +188,98 @@ fn geo_sort_mixed_with_words() {
let rtxn = index.read_txn().unwrap(); let rtxn = index.read_txn().unwrap();
let mut s = Search::new(&rtxn, &index); let mut s = Search::new(&rtxn, &index);
s.scoring_strategy(crate::score_details::ScoringStrategy::Detailed);
s.sort_criteria(vec![AscDesc::Asc(Member::Geo([0., 0.]))]); s.sort_criteria(vec![AscDesc::Asc(Member::Geo([0., 0.]))]);
s.query("jean"); s.query("jean");
let ids = execute_iterative_and_rtree_returns_the_same(&rtxn, &index, &mut s); let (ids, scores) = execute_iterative_and_rtree_returns_the_same(&rtxn, &index, &mut s);
insta::assert_snapshot!(format!("{ids:?}"), @"[0, 2, 3]"); insta::assert_snapshot!(format!("{ids:?}"), @"[0, 2, 3]");
insta::assert_snapshot!(format!("{scores:#?}"));
s.query("bob"); s.query("bob");
let ids = execute_iterative_and_rtree_returns_the_same(&rtxn, &index, &mut s); let (ids, scores) = execute_iterative_and_rtree_returns_the_same(&rtxn, &index, &mut s);
insta::assert_snapshot!(format!("{ids:?}"), @"[2, 4]"); insta::assert_snapshot!(format!("{ids:?}"), @"[2, 4]");
insta::assert_snapshot!(format!("{scores:#?}"), @r###"
[
[
Words(
Words {
matching_words: 1,
max_matching_words: 1,
},
),
GeoSort(
GeoSort {
target_point: [
0.0,
0.0,
],
ascending: true,
value: Some(
[
-89.0,
0.0,
],
),
},
),
],
[
Words(
Words {
matching_words: 1,
max_matching_words: 1,
},
),
GeoSort(
GeoSort {
target_point: [
0.0,
0.0,
],
ascending: true,
value: Some(
[
0.0,
-179.0,
],
),
},
),
],
]
"###);
s.query("intel"); s.query("intel");
let ids = execute_iterative_and_rtree_returns_the_same(&rtxn, &index, &mut s); let (ids, scores) = execute_iterative_and_rtree_returns_the_same(&rtxn, &index, &mut s);
insta::assert_snapshot!(format!("{ids:?}"), @"[1]"); insta::assert_snapshot!(format!("{ids:?}"), @"[1]");
insta::assert_snapshot!(format!("{scores:#?}"), @r###"
[
[
Words(
Words {
matching_words: 1,
max_matching_words: 1,
},
),
GeoSort(
GeoSort {
target_point: [
0.0,
0.0,
],
ascending: true,
value: Some(
[
88.0,
0.0,
],
),
},
),
],
]
"###);
} }
#[test] #[test]
@ -198,9 +299,11 @@ fn geo_sort_without_any_geo_faceted_documents() {
let rtxn = index.read_txn().unwrap(); let rtxn = index.read_txn().unwrap();
let mut s = Search::new(&rtxn, &index); let mut s = Search::new(&rtxn, &index);
s.scoring_strategy(crate::score_details::ScoringStrategy::Detailed);
s.sort_criteria(vec![AscDesc::Asc(Member::Geo([0., 0.]))]); s.sort_criteria(vec![AscDesc::Asc(Member::Geo([0., 0.]))]);
s.query("jean"); s.query("jean");
let ids = execute_iterative_and_rtree_returns_the_same(&rtxn, &index, &mut s); let (ids, scores) = execute_iterative_and_rtree_returns_the_same(&rtxn, &index, &mut s);
insta::assert_snapshot!(format!("{ids:?}"), @"[0, 2, 3]"); insta::assert_snapshot!(format!("{ids:?}"), @"[0, 2, 3]");
insta::assert_snapshot!(format!("{scores:#?}"));
} }

View File

@ -80,10 +80,13 @@ fn test_2gram_simple() {
let mut s = Search::new(&txn, &index); let mut s = Search::new(&txn, &index);
s.terms_matching_strategy(TermsMatchingStrategy::All); s.terms_matching_strategy(TermsMatchingStrategy::All);
s.scoring_strategy(crate::score_details::ScoringStrategy::Detailed);
s.query("sun flower"); s.query("sun flower");
let SearchResult { documents_ids, .. } = s.execute().unwrap(); let SearchResult { documents_ids, document_scores, .. } = s.execute().unwrap();
// will also match documents with "sunflower" + prefix tolerance // will also match documents with "sunflower" + prefix tolerance
insta::assert_snapshot!(format!("{documents_ids:?}"), @"[0, 1, 2, 3, 5]"); insta::assert_snapshot!(format!("{documents_ids:?}"), @"[0, 1, 2, 3, 5]");
// scores are empty because the only rule is Words with All matching strategy
insta::assert_snapshot!(format!("{document_scores:?}"), @"[[], [], [], [], []]");
let texts = collect_field_values(&index, &txn, "text", &documents_ids); let texts = collect_field_values(&index, &txn, "text", &documents_ids);
insta::assert_debug_snapshot!(texts, @r###" insta::assert_debug_snapshot!(texts, @r###"
[ [

View File

@ -124,6 +124,8 @@ fn create_edge_cases_index() -> TempIndex {
}, },
// The next 5 documents lay out a trap with the split word, phrase search, or synonym `sun flower`. // The next 5 documents lay out a trap with the split word, phrase search, or synonym `sun flower`.
// If the search query is "sunflower", the split word "Sun Flower" will match some documents. // If the search query is "sunflower", the split word "Sun Flower" will match some documents.
// The next 5 documents lay out a trap with the split word, phrase search, or synonym `sun flower`.
// If the search query is "sunflower", the split word "Sun Flower" will match some documents.
// If the query is `sunflower wilting`, then we should make sure that // If the query is `sunflower wilting`, then we should make sure that
// the proximity condition `flower wilting: sprx N` also comes with the condition // the proximity condition `flower wilting: sprx N` also comes with the condition
// `sun wilting: sprx N+1`, but this is not the exact condition we use for now. // `sun wilting: sprx N+1`, but this is not the exact condition we use for now.
@ -140,6 +142,7 @@ fn create_edge_cases_index() -> TempIndex {
{ {
"id": 3, "id": 3,
// This document matches the query `sunflower wilting`, but the sprximity condition // This document matches the query `sunflower wilting`, but the sprximity condition
// This document matches the query `sunflower wilting`, but the sprximity condition
// between `sunflower` and `wilting` cannot be through the split-word `Sun Flower` // between `sunflower` and `wilting` cannot be through the split-word `Sun Flower`
// which would reduce to only `flower` and `wilting` being in sprximity. // which would reduce to only `flower` and `wilting` being in sprximity.
"text": "A flower wilting under the sun, unlike a sunflower" "text": "A flower wilting under the sun, unlike a sunflower"
@ -270,13 +273,13 @@ fn test_proximity_simple() {
s.terms_matching_strategy(TermsMatchingStrategy::All); s.terms_matching_strategy(TermsMatchingStrategy::All);
s.query("the quick brown fox jumps over the lazy dog"); s.query("the quick brown fox jumps over the lazy dog");
let SearchResult { documents_ids, .. } = s.execute().unwrap(); let SearchResult { documents_ids, .. } = s.execute().unwrap();
insta::assert_snapshot!(format!("{documents_ids:?}"), @"[4, 9, 10, 7, 6, 5, 2, 3, 0, 1]"); insta::assert_snapshot!(format!("{documents_ids:?}"), @"[9, 10, 4, 7, 6, 5, 2, 3, 0, 1]");
let texts = collect_field_values(&index, &txn, "text", &documents_ids); let texts = collect_field_values(&index, &txn, "text", &documents_ids);
insta::assert_debug_snapshot!(texts, @r###" insta::assert_debug_snapshot!(texts, @r###"
[ [
"\"the quickbrown fox jumps over the lazy dog\"",
"\"the quack brown fox jumps over the lazy dog\"", "\"the quack brown fox jumps over the lazy dog\"",
"\"the quick brown fox jumps over the lazy dog\"", "\"the quick brown fox jumps over the lazy dog\"",
"\"the quickbrown fox jumps over the lazy dog\"",
"\"the really quick brown fox jumps over the lazy dog\"", "\"the really quick brown fox jumps over the lazy dog\"",
"\"the really quick brown fox jumps over the very lazy dog\"", "\"the really quick brown fox jumps over the very lazy dog\"",
"\"brown quick fox jumps over the lazy dog\"", "\"brown quick fox jumps over the lazy dog\"",
@ -295,9 +298,12 @@ fn test_proximity_split_word() {
let mut s = Search::new(&txn, &index); let mut s = Search::new(&txn, &index);
s.terms_matching_strategy(TermsMatchingStrategy::All); s.terms_matching_strategy(TermsMatchingStrategy::All);
s.scoring_strategy(crate::score_details::ScoringStrategy::Detailed);
s.query("sunflower wilting"); s.query("sunflower wilting");
let SearchResult { documents_ids, .. } = s.execute().unwrap(); let SearchResult { documents_ids, document_scores, .. } = s.execute().unwrap();
insta::assert_snapshot!(format!("{documents_ids:?}"), @"[2, 4, 5, 1, 3]"); insta::assert_snapshot!(format!("{documents_ids:?}"), @"[2, 4, 5, 1, 3]");
insta::assert_snapshot!(format!("{document_scores:#?}"));
let texts = collect_field_values(&index, &txn, "text", &documents_ids); let texts = collect_field_values(&index, &txn, "text", &documents_ids);
// "2" and "4" should be swapped ideally // "2" and "4" should be swapped ideally
insta::assert_debug_snapshot!(texts, @r###" insta::assert_debug_snapshot!(texts, @r###"
@ -312,9 +318,11 @@ fn test_proximity_split_word() {
let mut s = Search::new(&txn, &index); let mut s = Search::new(&txn, &index);
s.terms_matching_strategy(TermsMatchingStrategy::All); s.terms_matching_strategy(TermsMatchingStrategy::All);
s.scoring_strategy(crate::score_details::ScoringStrategy::Detailed);
s.query("\"sun flower\" wilting"); s.query("\"sun flower\" wilting");
let SearchResult { documents_ids, .. } = s.execute().unwrap(); let SearchResult { documents_ids, document_scores, .. } = s.execute().unwrap();
insta::assert_snapshot!(format!("{documents_ids:?}"), @"[2, 4, 1]"); insta::assert_snapshot!(format!("{documents_ids:?}"), @"[2, 4, 1]");
insta::assert_snapshot!(format!("{document_scores:#?}"));
let texts = collect_field_values(&index, &txn, "text", &documents_ids); let texts = collect_field_values(&index, &txn, "text", &documents_ids);
// "2" and "4" should be swapped ideally // "2" and "4" should be swapped ideally
insta::assert_debug_snapshot!(texts, @r###" insta::assert_debug_snapshot!(texts, @r###"
@ -337,9 +345,11 @@ fn test_proximity_split_word() {
let mut s = Search::new(&txn, &index); let mut s = Search::new(&txn, &index);
s.terms_matching_strategy(TermsMatchingStrategy::All); s.terms_matching_strategy(TermsMatchingStrategy::All);
s.scoring_strategy(crate::score_details::ScoringStrategy::Detailed);
s.query("xyz wilting"); s.query("xyz wilting");
let SearchResult { documents_ids, .. } = s.execute().unwrap(); let SearchResult { documents_ids, document_scores, .. } = s.execute().unwrap();
insta::assert_snapshot!(format!("{documents_ids:?}"), @"[2, 4, 1]"); insta::assert_snapshot!(format!("{documents_ids:?}"), @"[2, 4, 1]");
insta::assert_snapshot!(format!("{document_scores:#?}"));
let texts = collect_field_values(&index, &txn, "text", &documents_ids); let texts = collect_field_values(&index, &txn, "text", &documents_ids);
// "2" and "4" should be swapped ideally // "2" and "4" should be swapped ideally
insta::assert_debug_snapshot!(texts, @r###" insta::assert_debug_snapshot!(texts, @r###"
@ -358,9 +368,11 @@ fn test_proximity_prefix_db() {
let mut s = Search::new(&txn, &index); let mut s = Search::new(&txn, &index);
s.terms_matching_strategy(TermsMatchingStrategy::All); s.terms_matching_strategy(TermsMatchingStrategy::All);
s.scoring_strategy(crate::score_details::ScoringStrategy::Detailed);
s.query("best s"); s.query("best s");
let SearchResult { documents_ids, .. } = s.execute().unwrap(); let SearchResult { documents_ids, document_scores, .. } = s.execute().unwrap();
insta::assert_snapshot!(format!("{documents_ids:?}"), @"[10, 13, 9, 12, 8, 6, 7, 11, 15]"); insta::assert_snapshot!(format!("{documents_ids:?}"), @"[10, 13, 9, 12, 8, 6, 7, 11, 15]");
insta::assert_snapshot!(format!("{document_scores:#?}"));
let texts = collect_field_values(&index, &txn, "text", &documents_ids); let texts = collect_field_values(&index, &txn, "text", &documents_ids);
// This test illustrates the loss of precision from using the prefix DB // This test illustrates the loss of precision from using the prefix DB
@ -381,9 +393,11 @@ fn test_proximity_prefix_db() {
// Difference when using the `su` prefix, which is not in the prefix DB // Difference when using the `su` prefix, which is not in the prefix DB
let mut s = Search::new(&txn, &index); let mut s = Search::new(&txn, &index);
s.terms_matching_strategy(TermsMatchingStrategy::All); s.terms_matching_strategy(TermsMatchingStrategy::All);
s.scoring_strategy(crate::score_details::ScoringStrategy::Detailed);
s.query("best su"); s.query("best su");
let SearchResult { documents_ids, .. } = s.execute().unwrap(); let SearchResult { documents_ids, document_scores, .. } = s.execute().unwrap();
insta::assert_snapshot!(format!("{documents_ids:?}"), @"[10, 13, 9, 12, 8, 11, 7, 6, 15]"); insta::assert_snapshot!(format!("{documents_ids:?}"), @"[10, 13, 9, 12, 8, 11, 7, 6, 15]");
insta::assert_snapshot!(format!("{document_scores:#?}"));
let texts = collect_field_values(&index, &txn, "text", &documents_ids); let texts = collect_field_values(&index, &txn, "text", &documents_ids);
insta::assert_debug_snapshot!(texts, @r###" insta::assert_debug_snapshot!(texts, @r###"
@ -406,9 +420,11 @@ fn test_proximity_prefix_db() {
let mut s = Search::new(&txn, &index); let mut s = Search::new(&txn, &index);
s.terms_matching_strategy(TermsMatchingStrategy::All); s.terms_matching_strategy(TermsMatchingStrategy::All);
s.scoring_strategy(crate::score_details::ScoringStrategy::Detailed);
s.query("best win"); s.query("best win");
let SearchResult { documents_ids, .. } = s.execute().unwrap(); let SearchResult { documents_ids, document_scores, .. } = s.execute().unwrap();
insta::assert_snapshot!(format!("{documents_ids:?}"), @"[15, 16, 17, 18, 19, 20, 21, 22]"); insta::assert_snapshot!(format!("{documents_ids:?}"), @"[15, 16, 17, 18, 19, 20, 21, 22]");
insta::assert_snapshot!(format!("{document_scores:#?}"));
let texts = collect_field_values(&index, &txn, "text", &documents_ids); let texts = collect_field_values(&index, &txn, "text", &documents_ids);
insta::assert_debug_snapshot!(texts, @r###" insta::assert_debug_snapshot!(texts, @r###"
@ -428,9 +444,11 @@ fn test_proximity_prefix_db() {
let mut s = Search::new(&txn, &index); let mut s = Search::new(&txn, &index);
s.terms_matching_strategy(TermsMatchingStrategy::All); s.terms_matching_strategy(TermsMatchingStrategy::All);
s.scoring_strategy(crate::score_details::ScoringStrategy::Detailed);
s.query("best wint"); s.query("best wint");
let SearchResult { documents_ids, .. } = s.execute().unwrap(); let SearchResult { documents_ids, document_scores, .. } = s.execute().unwrap();
insta::assert_snapshot!(format!("{documents_ids:?}"), @"[19, 22, 18, 21, 17, 20, 16, 15]"); insta::assert_snapshot!(format!("{documents_ids:?}"), @"[19, 22, 18, 21, 17, 20, 16, 15]");
insta::assert_snapshot!(format!("{document_scores:#?}"));
let texts = collect_field_values(&index, &txn, "text", &documents_ids); let texts = collect_field_values(&index, &txn, "text", &documents_ids);
insta::assert_debug_snapshot!(texts, @r###" insta::assert_debug_snapshot!(texts, @r###"
@ -450,9 +468,11 @@ fn test_proximity_prefix_db() {
let mut s = Search::new(&txn, &index); let mut s = Search::new(&txn, &index);
s.terms_matching_strategy(TermsMatchingStrategy::All); s.terms_matching_strategy(TermsMatchingStrategy::All);
s.scoring_strategy(crate::score_details::ScoringStrategy::Detailed);
s.query("best wi"); s.query("best wi");
let SearchResult { documents_ids, .. } = s.execute().unwrap(); let SearchResult { documents_ids, document_scores, .. } = s.execute().unwrap();
insta::assert_snapshot!(format!("{documents_ids:?}"), @"[19, 22, 18, 21, 17, 15, 16, 20]"); insta::assert_snapshot!(format!("{documents_ids:?}"), @"[19, 22, 18, 21, 17, 15, 16, 20]");
insta::assert_snapshot!(format!("{document_scores:#?}"));
let texts = collect_field_values(&index, &txn, "text", &documents_ids); let texts = collect_field_values(&index, &txn, "text", &documents_ids);
insta::assert_debug_snapshot!(texts, @r###" insta::assert_debug_snapshot!(texts, @r###"

View File

@ -60,8 +60,41 @@ fn test_trap_basic() {
let mut s = Search::new(&txn, &index); let mut s = Search::new(&txn, &index);
s.terms_matching_strategy(TermsMatchingStrategy::All); s.terms_matching_strategy(TermsMatchingStrategy::All);
s.query("summer holiday"); s.query("summer holiday");
let SearchResult { documents_ids, .. } = s.execute().unwrap(); s.scoring_strategy(crate::score_details::ScoringStrategy::Detailed);
let SearchResult { documents_ids, document_scores, .. } = s.execute().unwrap();
insta::assert_snapshot!(format!("{documents_ids:?}"), @"[0, 1]"); insta::assert_snapshot!(format!("{documents_ids:?}"), @"[0, 1]");
insta::assert_snapshot!(format!("{document_scores:#?}"), @r###"
[
[
Proximity(
Rank {
rank: 8,
max_rank: 8,
},
),
Typo(
Typo {
typo_count: 0,
max_typo_count: 2,
},
),
],
[
Proximity(
Rank {
rank: 8,
max_rank: 8,
},
),
Typo(
Typo {
typo_count: 0,
max_typo_count: 2,
},
),
],
]
"###);
let texts = collect_field_values(&index, &txn, "text", &documents_ids); let texts = collect_field_values(&index, &txn, "text", &documents_ids);
// This is incorrect, 1 should come before 0 // This is incorrect, 1 should come before 0
insta::assert_debug_snapshot!(texts, @r###" insta::assert_debug_snapshot!(texts, @r###"

View File

@ -0,0 +1,244 @@
---
source: milli/src/search/new/tests/attribute_fid.rs
expression: "format!(\"{document_ids_scores:#?}\")"
---
[
(
2,
[
Fid(
Rank {
rank: 19,
max_rank: 19,
},
),
Position(
Rank {
rank: 91,
max_rank: 91,
},
),
],
),
(
6,
[
Fid(
Rank {
rank: 15,
max_rank: 19,
},
),
Position(
Rank {
rank: 81,
max_rank: 91,
},
),
],
),
(
5,
[
Fid(
Rank {
rank: 14,
max_rank: 19,
},
),
Position(
Rank {
rank: 79,
max_rank: 91,
},
),
],
),
(
4,
[
Fid(
Rank {
rank: 13,
max_rank: 19,
},
),
Position(
Rank {
rank: 77,
max_rank: 91,
},
),
],
),
(
3,
[
Fid(
Rank {
rank: 12,
max_rank: 19,
},
),
Position(
Rank {
rank: 83,
max_rank: 91,
},
),
],
),
(
9,
[
Fid(
Rank {
rank: 11,
max_rank: 19,
},
),
Position(
Rank {
rank: 75,
max_rank: 91,
},
),
],
),
(
8,
[
Fid(
Rank {
rank: 10,
max_rank: 19,
},
),
Position(
Rank {
rank: 79,
max_rank: 91,
},
),
],
),
(
7,
[
Fid(
Rank {
rank: 10,
max_rank: 19,
},
),
Position(
Rank {
rank: 73,
max_rank: 91,
},
),
],
),
(
11,
[
Fid(
Rank {
rank: 7,
max_rank: 19,
},
),
Position(
Rank {
rank: 77,
max_rank: 91,
},
),
],
),
(
10,
[
Fid(
Rank {
rank: 6,
max_rank: 19,
},
),
Position(
Rank {
rank: 81,
max_rank: 91,
},
),
],
),
(
13,
[
Fid(
Rank {
rank: 6,
max_rank: 19,
},
),
Position(
Rank {
rank: 81,
max_rank: 91,
},
),
],
),
(
12,
[
Fid(
Rank {
rank: 6,
max_rank: 19,
},
),
Position(
Rank {
rank: 78,
max_rank: 91,
},
),
],
),
(
14,
[
Fid(
Rank {
rank: 5,
max_rank: 19,
},
),
Position(
Rank {
rank: 75,
max_rank: 91,
},
),
],
),
(
0,
[
Fid(
Rank {
rank: 1,
max_rank: 19,
},
),
Position(
Rank {
rank: 91,
max_rank: 91,
},
),
],
),
]

View File

@ -0,0 +1,244 @@
---
source: milli/src/search/new/tests/attribute_fid.rs
expression: "format!(\"{document_ids_scores:#?}\")"
---
[
(
2,
[
Fid(
Rank {
rank: 19,
max_rank: 19,
},
),
Position(
Rank {
rank: 91,
max_rank: 91,
},
),
],
),
(
6,
[
Fid(
Rank {
rank: 15,
max_rank: 19,
},
),
Position(
Rank {
rank: 81,
max_rank: 91,
},
),
],
),
(
5,
[
Fid(
Rank {
rank: 14,
max_rank: 19,
},
),
Position(
Rank {
rank: 79,
max_rank: 91,
},
),
],
),
(
4,
[
Fid(
Rank {
rank: 13,
max_rank: 19,
},
),
Position(
Rank {
rank: 77,
max_rank: 91,
},
),
],
),
(
3,
[
Fid(
Rank {
rank: 12,
max_rank: 19,
},
),
Position(
Rank {
rank: 83,
max_rank: 91,
},
),
],
),
(
9,
[
Fid(
Rank {
rank: 11,
max_rank: 19,
},
),
Position(
Rank {
rank: 75,
max_rank: 91,
},
),
],
),
(
8,
[
Fid(
Rank {
rank: 10,
max_rank: 19,
},
),
Position(
Rank {
rank: 79,
max_rank: 91,
},
),
],
),
(
7,
[
Fid(
Rank {
rank: 10,
max_rank: 19,
},
),
Position(
Rank {
rank: 73,
max_rank: 91,
},
),
],
),
(
11,
[
Fid(
Rank {
rank: 7,
max_rank: 19,
},
),
Position(
Rank {
rank: 77,
max_rank: 91,
},
),
],
),
(
10,
[
Fid(
Rank {
rank: 6,
max_rank: 19,
},
),
Position(
Rank {
rank: 81,
max_rank: 91,
},
),
],
),
(
13,
[
Fid(
Rank {
rank: 6,
max_rank: 19,
},
),
Position(
Rank {
rank: 81,
max_rank: 91,
},
),
],
),
(
12,
[
Fid(
Rank {
rank: 6,
max_rank: 19,
},
),
Position(
Rank {
rank: 78,
max_rank: 91,
},
),
],
),
(
14,
[
Fid(
Rank {
rank: 5,
max_rank: 19,
},
),
Position(
Rank {
rank: 75,
max_rank: 91,
},
),
],
),
(
0,
[
Fid(
Rank {
rank: 1,
max_rank: 19,
},
),
Position(
Rank {
rank: 91,
max_rank: 91,
},
),
],
),
]

View File

@ -0,0 +1,244 @@
---
source: milli/src/search/new/tests/attribute_position.rs
expression: "format!(\"{document_ids_scores:#?}\")"
---
[
(
10,
[
Fid(
Rank {
rank: 5,
max_rank: 5,
},
),
Position(
Rank {
rank: 21,
max_rank: 21,
},
),
],
),
(
12,
[
Fid(
Rank {
rank: 5,
max_rank: 5,
},
),
Position(
Rank {
rank: 21,
max_rank: 21,
},
),
],
),
(
11,
[
Fid(
Rank {
rank: 5,
max_rank: 5,
},
),
Position(
Rank {
rank: 20,
max_rank: 21,
},
),
],
),
(
13,
[
Fid(
Rank {
rank: 5,
max_rank: 5,
},
),
Position(
Rank {
rank: 20,
max_rank: 21,
},
),
],
),
(
3,
[
Fid(
Rank {
rank: 5,
max_rank: 5,
},
),
Position(
Rank {
rank: 19,
max_rank: 21,
},
),
],
),
(
4,
[
Fid(
Rank {
rank: 5,
max_rank: 5,
},
),
Position(
Rank {
rank: 19,
max_rank: 21,
},
),
],
),
(
2,
[
Fid(
Rank {
rank: 5,
max_rank: 5,
},
),
Position(
Rank {
rank: 18,
max_rank: 21,
},
),
],
),
(
0,
[
Fid(
Rank {
rank: 5,
max_rank: 5,
},
),
Position(
Rank {
rank: 15,
max_rank: 21,
},
),
],
),
(
1,
[
Fid(
Rank {
rank: 5,
max_rank: 5,
},
),
Position(
Rank {
rank: 15,
max_rank: 21,
},
),
],
),
(
6,
[
Fid(
Rank {
rank: 5,
max_rank: 5,
},
),
Position(
Rank {
rank: 13,
max_rank: 21,
},
),
],
),
(
8,
[
Fid(
Rank {
rank: 5,
max_rank: 5,
},
),
Position(
Rank {
rank: 5,
max_rank: 21,
},
),
],
),
(
7,
[
Fid(
Rank {
rank: 5,
max_rank: 5,
},
),
Position(
Rank {
rank: 4,
max_rank: 21,
},
),
],
),
(
9,
[
Fid(
Rank {
rank: 5,
max_rank: 5,
},
),
Position(
Rank {
rank: 4,
max_rank: 21,
},
),
],
),
(
5,
[
Fid(
Rank {
rank: 5,
max_rank: 5,
},
),
Position(
Rank {
rank: 1,
max_rank: 21,
},
),
],
),
]

View File

@ -0,0 +1,244 @@
---
source: milli/src/search/new/tests/attribute_position.rs
expression: "format!(\"{document_ids_scores:#?}\")"
---
[
(
10,
[
Fid(
Rank {
rank: 5,
max_rank: 5,
},
),
Position(
Rank {
rank: 21,
max_rank: 21,
},
),
],
),
(
12,
[
Fid(
Rank {
rank: 5,
max_rank: 5,
},
),
Position(
Rank {
rank: 21,
max_rank: 21,
},
),
],
),
(
11,
[
Fid(
Rank {
rank: 5,
max_rank: 5,
},
),
Position(
Rank {
rank: 20,
max_rank: 21,
},
),
],
),
(
13,
[
Fid(
Rank {
rank: 5,
max_rank: 5,
},
),
Position(
Rank {
rank: 20,
max_rank: 21,
},
),
],
),
(
3,
[
Fid(
Rank {
rank: 5,
max_rank: 5,
},
),
Position(
Rank {
rank: 19,
max_rank: 21,
},
),
],
),
(
4,
[
Fid(
Rank {
rank: 5,
max_rank: 5,
},
),
Position(
Rank {
rank: 19,
max_rank: 21,
},
),
],
),
(
2,
[
Fid(
Rank {
rank: 5,
max_rank: 5,
},
),
Position(
Rank {
rank: 18,
max_rank: 21,
},
),
],
),
(
0,
[
Fid(
Rank {
rank: 5,
max_rank: 5,
},
),
Position(
Rank {
rank: 15,
max_rank: 21,
},
),
],
),
(
1,
[
Fid(
Rank {
rank: 5,
max_rank: 5,
},
),
Position(
Rank {
rank: 15,
max_rank: 21,
},
),
],
),
(
6,
[
Fid(
Rank {
rank: 5,
max_rank: 5,
},
),
Position(
Rank {
rank: 13,
max_rank: 21,
},
),
],
),
(
8,
[
Fid(
Rank {
rank: 5,
max_rank: 5,
},
),
Position(
Rank {
rank: 5,
max_rank: 21,
},
),
],
),
(
7,
[
Fid(
Rank {
rank: 5,
max_rank: 5,
},
),
Position(
Rank {
rank: 4,
max_rank: 21,
},
),
],
),
(
9,
[
Fid(
Rank {
rank: 5,
max_rank: 5,
},
),
Position(
Rank {
rank: 4,
max_rank: 21,
},
),
],
),
(
5,
[
Fid(
Rank {
rank: 5,
max_rank: 5,
},
),
Position(
Rank {
rank: 1,
max_rank: 21,
},
),
],
),
]

View File

@ -0,0 +1,91 @@
---
source: milli/src/search/new/tests/attribute_position.rs
expression: "format!(\"{document_ids_scores:#?}\")"
---
[
(
5,
[
Fid(
Rank {
rank: 11,
max_rank: 11,
},
),
Position(
Rank {
rank: 51,
max_rank: 51,
},
),
],
),
(
7,
[
Fid(
Rank {
rank: 11,
max_rank: 11,
},
),
Position(
Rank {
rank: 51,
max_rank: 51,
},
),
],
),
(
8,
[
Fid(
Rank {
rank: 11,
max_rank: 11,
},
),
Position(
Rank {
rank: 51,
max_rank: 51,
},
),
],
),
(
9,
[
Fid(
Rank {
rank: 11,
max_rank: 11,
},
),
Position(
Rank {
rank: 51,
max_rank: 51,
},
),
],
),
(
6,
[
Fid(
Rank {
rank: 11,
max_rank: 11,
},
),
Position(
Rank {
rank: 50,
max_rank: 51,
},
),
],
),
]

View File

@ -0,0 +1,244 @@
---
source: milli/src/search/new/tests/attribute_position.rs
expression: "format!(\"{document_ids_scores:#?}\")"
---
[
(
10,
[
Fid(
Rank {
rank: 5,
max_rank: 5,
},
),
Position(
Rank {
rank: 21,
max_rank: 21,
},
),
],
),
(
12,
[
Fid(
Rank {
rank: 5,
max_rank: 5,
},
),
Position(
Rank {
rank: 21,
max_rank: 21,
},
),
],
),
(
11,
[
Fid(
Rank {
rank: 5,
max_rank: 5,
},
),
Position(
Rank {
rank: 20,
max_rank: 21,
},
),
],
),
(
13,
[
Fid(
Rank {
rank: 5,
max_rank: 5,
},
),
Position(
Rank {
rank: 20,
max_rank: 21,
},
),
],
),
(
3,
[
Fid(
Rank {
rank: 5,
max_rank: 5,
},
),
Position(
Rank {
rank: 19,
max_rank: 21,
},
),
],
),
(
4,
[
Fid(
Rank {
rank: 5,
max_rank: 5,
},
),
Position(
Rank {
rank: 19,
max_rank: 21,
},
),
],
),
(
2,
[
Fid(
Rank {
rank: 5,
max_rank: 5,
},
),
Position(
Rank {
rank: 18,
max_rank: 21,
},
),
],
),
(
0,
[
Fid(
Rank {
rank: 5,
max_rank: 5,
},
),
Position(
Rank {
rank: 15,
max_rank: 21,
},
),
],
),
(
1,
[
Fid(
Rank {
rank: 5,
max_rank: 5,
},
),
Position(
Rank {
rank: 15,
max_rank: 21,
},
),
],
),
(
6,
[
Fid(
Rank {
rank: 5,
max_rank: 5,
},
),
Position(
Rank {
rank: 13,
max_rank: 21,
},
),
],
),
(
8,
[
Fid(
Rank {
rank: 5,
max_rank: 5,
},
),
Position(
Rank {
rank: 5,
max_rank: 21,
},
),
],
),
(
7,
[
Fid(
Rank {
rank: 5,
max_rank: 5,
},
),
Position(
Rank {
rank: 4,
max_rank: 21,
},
),
],
),
(
9,
[
Fid(
Rank {
rank: 5,
max_rank: 5,
},
),
Position(
Rank {
rank: 4,
max_rank: 21,
},
),
],
),
(
5,
[
Fid(
Rank {
rank: 5,
max_rank: 5,
},
),
Position(
Rank {
rank: 1,
max_rank: 21,
},
),
],
),
]

View File

@ -0,0 +1,366 @@
---
source: milli/src/search/new/tests/exactness.rs
expression: "format!(\"{document_ids_scores:#?}\")"
---
[
(
19,
[
Words(
Words {
matching_words: 9,
max_matching_words: 9,
},
),
ExactAttribute(
ExactMatch,
),
Exactness(
Rank {
rank: 10,
max_rank: 10,
},
),
],
),
(
9,
[
Words(
Words {
matching_words: 9,
max_matching_words: 9,
},
),
ExactAttribute(
NoExactMatch,
),
Exactness(
Rank {
rank: 7,
max_rank: 10,
},
),
],
),
(
18,
[
Words(
Words {
matching_words: 8,
max_matching_words: 9,
},
),
ExactAttribute(
ExactMatch,
),
Exactness(
Rank {
rank: 9,
max_rank: 9,
},
),
],
),
(
8,
[
Words(
Words {
matching_words: 8,
max_matching_words: 9,
},
),
ExactAttribute(
NoExactMatch,
),
Exactness(
Rank {
rank: 6,
max_rank: 9,
},
),
],
),
(
17,
[
Words(
Words {
matching_words: 7,
max_matching_words: 9,
},
),
ExactAttribute(
ExactMatch,
),
Exactness(
Rank {
rank: 8,
max_rank: 8,
},
),
],
),
(
16,
[
Words(
Words {
matching_words: 7,
max_matching_words: 9,
},
),
ExactAttribute(
NoExactMatch,
),
Exactness(
Rank {
rank: 8,
max_rank: 8,
},
),
],
),
(
6,
[
Words(
Words {
matching_words: 7,
max_matching_words: 9,
},
),
ExactAttribute(
NoExactMatch,
),
Exactness(
Rank {
rank: 5,
max_rank: 8,
},
),
],
),
(
7,
[
Words(
Words {
matching_words: 7,
max_matching_words: 9,
},
),
ExactAttribute(
NoExactMatch,
),
Exactness(
Rank {
rank: 5,
max_rank: 8,
},
),
],
),
(
15,
[
Words(
Words {
matching_words: 5,
max_matching_words: 9,
},
),
ExactAttribute(
ExactMatch,
),
Exactness(
Rank {
rank: 6,
max_rank: 6,
},
),
],
),
(
5,
[
Words(
Words {
matching_words: 5,
max_matching_words: 9,
},
),
ExactAttribute(
NoExactMatch,
),
Exactness(
Rank {
rank: 3,
max_rank: 6,
},
),
],
),
(
14,
[
Words(
Words {
matching_words: 4,
max_matching_words: 9,
},
),
ExactAttribute(
ExactMatch,
),
Exactness(
Rank {
rank: 5,
max_rank: 5,
},
),
],
),
(
4,
[
Words(
Words {
matching_words: 4,
max_matching_words: 9,
},
),
ExactAttribute(
NoExactMatch,
),
Exactness(
Rank {
rank: 3,
max_rank: 5,
},
),
],
),
(
13,
[
Words(
Words {
matching_words: 3,
max_matching_words: 9,
},
),
ExactAttribute(
ExactMatch,
),
Exactness(
Rank {
rank: 4,
max_rank: 4,
},
),
],
),
(
3,
[
Words(
Words {
matching_words: 3,
max_matching_words: 9,
},
),
ExactAttribute(
NoExactMatch,
),
Exactness(
Rank {
rank: 2,
max_rank: 4,
},
),
],
),
(
12,
[
Words(
Words {
matching_words: 2,
max_matching_words: 9,
},
),
ExactAttribute(
ExactMatch,
),
Exactness(
Rank {
rank: 3,
max_rank: 3,
},
),
],
),
(
2,
[
Words(
Words {
matching_words: 2,
max_matching_words: 9,
},
),
ExactAttribute(
NoExactMatch,
),
Exactness(
Rank {
rank: 2,
max_rank: 3,
},
),
],
),
(
1,
[
Words(
Words {
matching_words: 1,
max_matching_words: 9,
},
),
ExactAttribute(
ExactMatch,
),
Exactness(
Rank {
rank: 2,
max_rank: 2,
},
),
],
),
(
11,
[
Words(
Words {
matching_words: 1,
max_matching_words: 9,
},
),
ExactAttribute(
ExactMatch,
),
Exactness(
Rank {
rank: 2,
max_rank: 2,
},
),
],
),
]

View File

@ -0,0 +1,106 @@
---
source: milli/src/search/new/tests/exactness.rs
expression: "format!(\"{document_ids_scores:#?}\")"
---
[
(
4,
[
Words(
Words {
matching_words: 7,
max_matching_words: 7,
},
),
ExactAttribute(
NoExactMatch,
),
Exactness(
Rank {
rank: 7,
max_rank: 8,
},
),
],
),
(
5,
[
Words(
Words {
matching_words: 7,
max_matching_words: 7,
},
),
ExactAttribute(
NoExactMatch,
),
Exactness(
Rank {
rank: 7,
max_rank: 8,
},
),
],
),
(
6,
[
Words(
Words {
matching_words: 7,
max_matching_words: 7,
},
),
ExactAttribute(
NoExactMatch,
),
Exactness(
Rank {
rank: 7,
max_rank: 8,
},
),
],
),
(
1,
[
Words(
Words {
matching_words: 4,
max_matching_words: 7,
},
),
ExactAttribute(
NoExactMatch,
),
Exactness(
Rank {
rank: 4,
max_rank: 5,
},
),
],
),
(
7,
[
Words(
Words {
matching_words: 1,
max_matching_words: 7,
},
),
ExactAttribute(
NoExactMatch,
),
Exactness(
Rank {
rank: 1,
max_rank: 2,
},
),
],
),
]

View File

@ -0,0 +1,126 @@
---
source: milli/src/search/new/tests/exactness.rs
expression: "format!(\"{document_ids_scores:#?}\")"
---
[
(
6,
[
Words(
Words {
matching_words: 7,
max_matching_words: 7,
},
),
ExactAttribute(
ExactMatch,
),
Exactness(
Rank {
rank: 8,
max_rank: 8,
},
),
],
),
(
5,
[
Words(
Words {
matching_words: 7,
max_matching_words: 7,
},
),
ExactAttribute(
MatchesStart,
),
Exactness(
Rank {
rank: 8,
max_rank: 8,
},
),
],
),
(
4,
[
Words(
Words {
matching_words: 7,
max_matching_words: 7,
},
),
ExactAttribute(
NoExactMatch,
),
Exactness(
Rank {
rank: 8,
max_rank: 8,
},
),
],
),
(
3,
[
Words(
Words {
matching_words: 7,
max_matching_words: 7,
},
),
ExactAttribute(
NoExactMatch,
),
Exactness(
Rank {
rank: 7,
max_rank: 8,
},
),
],
),
(
1,
[
Words(
Words {
matching_words: 4,
max_matching_words: 7,
},
),
ExactAttribute(
NoExactMatch,
),
Exactness(
Rank {
rank: 5,
max_rank: 5,
},
),
],
),
(
7,
[
Words(
Words {
matching_words: 1,
max_matching_words: 7,
},
),
ExactAttribute(
ExactMatch,
),
Exactness(
Rank {
rank: 2,
max_rank: 2,
},
),
],
),
]

View File

@ -0,0 +1,86 @@
---
source: milli/src/search/new/tests/exactness.rs
expression: "format!(\"{document_ids_scores:#?}\")"
---
[
(
6,
[
Words(
Words {
matching_words: 7,
max_matching_words: 7,
},
),
ExactAttribute(
ExactMatch,
),
Exactness(
Rank {
rank: 6,
max_rank: 6,
},
),
],
),
(
5,
[
Words(
Words {
matching_words: 7,
max_matching_words: 7,
},
),
ExactAttribute(
MatchesStart,
),
Exactness(
Rank {
rank: 6,
max_rank: 6,
},
),
],
),
(
4,
[
Words(
Words {
matching_words: 7,
max_matching_words: 7,
},
),
ExactAttribute(
NoExactMatch,
),
Exactness(
Rank {
rank: 6,
max_rank: 6,
},
),
],
),
(
1,
[
Words(
Words {
matching_words: 4,
max_matching_words: 7,
},
),
ExactAttribute(
NoExactMatch,
),
Exactness(
Rank {
rank: 3,
max_rank: 3,
},
),
],
),
]

View File

@ -0,0 +1,66 @@
---
source: milli/src/search/new/tests/exactness.rs
expression: "format!(\"{document_ids_scores:#?}\")"
---
[
(
2,
[
Words(
Words {
matching_words: 2,
max_matching_words: 2,
},
),
ExactAttribute(
ExactMatch,
),
Exactness(
Rank {
rank: 3,
max_rank: 3,
},
),
],
),
(
1,
[
Words(
Words {
matching_words: 2,
max_matching_words: 2,
},
),
ExactAttribute(
MatchesStart,
),
Exactness(
Rank {
rank: 3,
max_rank: 3,
},
),
],
),
(
0,
[
Words(
Words {
matching_words: 2,
max_matching_words: 2,
},
),
ExactAttribute(
NoExactMatch,
),
Exactness(
Rank {
rank: 3,
max_rank: 3,
},
),
],
),
]

View File

@ -0,0 +1,136 @@
---
source: milli/src/search/new/tests/exactness.rs
expression: "format!(\"{document_ids_scores:#?}\")"
---
[
(
2,
[
Words(
Words {
matching_words: 4,
max_matching_words: 4,
},
),
ExactAttribute(
NoExactMatch,
),
Exactness(
Rank {
rank: 5,
max_rank: 5,
},
),
Typo(
Typo {
typo_count: 0,
max_typo_count: 1,
},
),
],
),
(
1,
[
Words(
Words {
matching_words: 4,
max_matching_words: 4,
},
),
ExactAttribute(
NoExactMatch,
),
Exactness(
Rank {
rank: 4,
max_rank: 5,
},
),
Typo(
Typo {
typo_count: 0,
max_typo_count: 2,
},
),
],
),
(
0,
[
Words(
Words {
matching_words: 4,
max_matching_words: 4,
},
),
ExactAttribute(
NoExactMatch,
),
Exactness(
Rank {
rank: 4,
max_rank: 5,
},
),
Typo(
Typo {
typo_count: 1,
max_typo_count: 2,
},
),
],
),
(
4,
[
Words(
Words {
matching_words: 4,
max_matching_words: 4,
},
),
ExactAttribute(
NoExactMatch,
),
Exactness(
Rank {
rank: 4,
max_rank: 5,
},
),
Typo(
Typo {
typo_count: 1,
max_typo_count: 2,
},
),
],
),
(
3,
[
Words(
Words {
matching_words: 4,
max_matching_words: 4,
},
),
ExactAttribute(
NoExactMatch,
),
Exactness(
Rank {
rank: 3,
max_rank: 5,
},
),
Typo(
Typo {
typo_count: 2,
max_typo_count: 3,
},
),
],
),
]

View File

@ -0,0 +1,186 @@
---
source: milli/src/search/new/tests/exactness.rs
expression: "format!(\"{document_ids_scores:#?}\")"
---
[
(
9,
[
Words(
Words {
matching_words: 9,
max_matching_words: 9,
},
),
ExactAttribute(
ExactMatch,
),
Exactness(
Rank {
rank: 10,
max_rank: 10,
},
),
],
),
(
8,
[
Words(
Words {
matching_words: 8,
max_matching_words: 9,
},
),
ExactAttribute(
ExactMatch,
),
Exactness(
Rank {
rank: 9,
max_rank: 9,
},
),
],
),
(
7,
[
Words(
Words {
matching_words: 7,
max_matching_words: 9,
},
),
ExactAttribute(
ExactMatch,
),
Exactness(
Rank {
rank: 8,
max_rank: 8,
},
),
],
),
(
6,
[
Words(
Words {
matching_words: 7,
max_matching_words: 9,
},
),
ExactAttribute(
NoExactMatch,
),
Exactness(
Rank {
rank: 8,
max_rank: 8,
},
),
],
),
(
5,
[
Words(
Words {
matching_words: 5,
max_matching_words: 9,
},
),
ExactAttribute(
ExactMatch,
),
Exactness(
Rank {
rank: 6,
max_rank: 6,
},
),
],
),
(
4,
[
Words(
Words {
matching_words: 4,
max_matching_words: 9,
},
),
ExactAttribute(
ExactMatch,
),
Exactness(
Rank {
rank: 5,
max_rank: 5,
},
),
],
),
(
3,
[
Words(
Words {
matching_words: 3,
max_matching_words: 9,
},
),
ExactAttribute(
ExactMatch,
),
Exactness(
Rank {
rank: 4,
max_rank: 4,
},
),
],
),
(
2,
[
Words(
Words {
matching_words: 2,
max_matching_words: 9,
},
),
ExactAttribute(
ExactMatch,
),
Exactness(
Rank {
rank: 3,
max_rank: 3,
},
),
],
),
(
1,
[
Words(
Words {
matching_words: 1,
max_matching_words: 9,
},
),
ExactAttribute(
ExactMatch,
),
Exactness(
Rank {
rank: 2,
max_rank: 2,
},
),
],
),
]

View File

@ -0,0 +1,126 @@
---
source: milli/src/search/new/tests/exactness.rs
expression: "format!(\"{document_ids_scores:#?}\")"
---
[
(
8,
[
Words(
Words {
matching_words: 9,
max_matching_words: 9,
},
),
ExactAttribute(
NoExactMatch,
),
Exactness(
Rank {
rank: 10,
max_rank: 10,
},
),
],
),
(
7,
[
Words(
Words {
matching_words: 3,
max_matching_words: 9,
},
),
ExactAttribute(
NoExactMatch,
),
Exactness(
Rank {
rank: 4,
max_rank: 4,
},
),
],
),
(
4,
[
Words(
Words {
matching_words: 2,
max_matching_words: 9,
},
),
ExactAttribute(
NoExactMatch,
),
Exactness(
Rank {
rank: 3,
max_rank: 3,
},
),
],
),
(
6,
[
Words(
Words {
matching_words: 2,
max_matching_words: 9,
},
),
ExactAttribute(
NoExactMatch,
),
Exactness(
Rank {
rank: 3,
max_rank: 3,
},
),
],
),
(
3,
[
Words(
Words {
matching_words: 1,
max_matching_words: 9,
},
),
ExactAttribute(
NoExactMatch,
),
Exactness(
Rank {
rank: 2,
max_rank: 2,
},
),
],
),
(
5,
[
Words(
Words {
matching_words: 1,
max_matching_words: 9,
},
),
ExactAttribute(
NoExactMatch,
),
Exactness(
Rank {
rank: 2,
max_rank: 2,
},
),
],
),
]

View File

@ -0,0 +1,146 @@
---
source: milli/src/search/new/tests/exactness.rs
expression: "format!(\"{document_ids_scores:#?}\")"
---
[
(
9,
[
Words(
Words {
matching_words: 9,
max_matching_words: 9,
},
),
ExactAttribute(
ExactMatch,
),
Exactness(
Rank {
rank: 10,
max_rank: 10,
},
),
],
),
(
8,
[
Words(
Words {
matching_words: 9,
max_matching_words: 9,
},
),
ExactAttribute(
NoExactMatch,
),
Exactness(
Rank {
rank: 10,
max_rank: 10,
},
),
],
),
(
3,
[
Words(
Words {
matching_words: 1,
max_matching_words: 9,
},
),
ExactAttribute(
MatchesStart,
),
Exactness(
Rank {
rank: 2,
max_rank: 2,
},
),
],
),
(
4,
[
Words(
Words {
matching_words: 1,
max_matching_words: 9,
},
),
ExactAttribute(
NoExactMatch,
),
Exactness(
Rank {
rank: 2,
max_rank: 2,
},
),
],
),
(
5,
[
Words(
Words {
matching_words: 1,
max_matching_words: 9,
},
),
ExactAttribute(
NoExactMatch,
),
Exactness(
Rank {
rank: 2,
max_rank: 2,
},
),
],
),
(
6,
[
Words(
Words {
matching_words: 1,
max_matching_words: 9,
},
),
ExactAttribute(
NoExactMatch,
),
Exactness(
Rank {
rank: 2,
max_rank: 2,
},
),
],
),
(
7,
[
Words(
Words {
matching_words: 1,
max_matching_words: 9,
},
),
ExactAttribute(
NoExactMatch,
),
Exactness(
Rank {
rank: 2,
max_rank: 2,
},
),
],
),
]

View File

@ -0,0 +1,146 @@
---
source: milli/src/search/new/tests/exactness.rs
expression: "format!(\"{document_ids_scores:#?}\")"
---
[
(
9,
[
Words(
Words {
matching_words: 9,
max_matching_words: 9,
},
),
ExactAttribute(
ExactMatch,
),
Exactness(
Rank {
rank: 10,
max_rank: 10,
},
),
],
),
(
8,
[
Words(
Words {
matching_words: 9,
max_matching_words: 9,
},
),
ExactAttribute(
NoExactMatch,
),
Exactness(
Rank {
rank: 10,
max_rank: 10,
},
),
],
),
(
3,
[
Words(
Words {
matching_words: 1,
max_matching_words: 9,
},
),
ExactAttribute(
MatchesStart,
),
Exactness(
Rank {
rank: 2,
max_rank: 2,
},
),
],
),
(
4,
[
Words(
Words {
matching_words: 1,
max_matching_words: 9,
},
),
ExactAttribute(
NoExactMatch,
),
Exactness(
Rank {
rank: 2,
max_rank: 2,
},
),
],
),
(
5,
[
Words(
Words {
matching_words: 1,
max_matching_words: 9,
},
),
ExactAttribute(
NoExactMatch,
),
Exactness(
Rank {
rank: 2,
max_rank: 2,
},
),
],
),
(
6,
[
Words(
Words {
matching_words: 1,
max_matching_words: 9,
},
),
ExactAttribute(
NoExactMatch,
),
Exactness(
Rank {
rank: 2,
max_rank: 2,
},
),
],
),
(
7,
[
Words(
Words {
matching_words: 1,
max_matching_words: 9,
},
),
ExactAttribute(
NoExactMatch,
),
Exactness(
Rank {
rank: 2,
max_rank: 2,
},
),
],
),
]

View File

@ -0,0 +1,84 @@
---
source: milli/src/search/new/tests/exactness.rs
expression: "format!(\"{document_ids_scores:#?}\")"
---
[
(
0,
[
Words(
Words {
matching_words: 9,
max_matching_words: 9,
},
),
ExactAttribute(
NoExactMatch,
),
Exactness(
Rank {
rank: 10,
max_rank: 10,
},
),
Proximity(
Rank {
rank: 35,
max_rank: 57,
},
),
],
),
(
1,
[
Words(
Words {
matching_words: 9,
max_matching_words: 9,
},
),
ExactAttribute(
NoExactMatch,
),
Exactness(
Rank {
rank: 10,
max_rank: 10,
},
),
Proximity(
Rank {
rank: 35,
max_rank: 57,
},
),
],
),
(
2,
[
Words(
Words {
matching_words: 9,
max_matching_words: 9,
},
),
ExactAttribute(
NoExactMatch,
),
Exactness(
Rank {
rank: 10,
max_rank: 10,
},
),
Proximity(
Rank {
rank: 35,
max_rank: 57,
},
),
],
),
]

View File

@ -0,0 +1,240 @@
---
source: milli/src/search/new/tests/exactness.rs
expression: "format!(\"{document_ids_scores:#?}\")"
---
[
(
2,
[
Words(
Words {
matching_words: 9,
max_matching_words: 9,
},
),
ExactAttribute(
ExactMatch,
),
Exactness(
Rank {
rank: 10,
max_rank: 10,
},
),
Proximity(
Rank {
rank: 57,
max_rank: 57,
},
),
],
),
(
1,
[
Words(
Words {
matching_words: 9,
max_matching_words: 9,
},
),
ExactAttribute(
NoExactMatch,
),
Exactness(
Rank {
rank: 10,
max_rank: 10,
},
),
Proximity(
Rank {
rank: 56,
max_rank: 57,
},
),
],
),
(
0,
[
Words(
Words {
matching_words: 9,
max_matching_words: 9,
},
),
ExactAttribute(
NoExactMatch,
),
Exactness(
Rank {
rank: 10,
max_rank: 10,
},
),
Proximity(
Rank {
rank: 35,
max_rank: 57,
},
),
],
),
(
4,
[
Words(
Words {
matching_words: 4,
max_matching_words: 9,
},
),
ExactAttribute(
MatchesStart,
),
Exactness(
Rank {
rank: 5,
max_rank: 5,
},
),
Proximity(
Rank {
rank: 22,
max_rank: 22,
},
),
],
),
(
5,
[
Words(
Words {
matching_words: 4,
max_matching_words: 9,
},
),
ExactAttribute(
MatchesStart,
),
Exactness(
Rank {
rank: 5,
max_rank: 5,
},
),
Proximity(
Rank {
rank: 22,
max_rank: 22,
},
),
],
),
(
8,
[
Words(
Words {
matching_words: 4,
max_matching_words: 9,
},
),
ExactAttribute(
MatchesStart,
),
Exactness(
Rank {
rank: 5,
max_rank: 5,
},
),
Proximity(
Rank {
rank: 22,
max_rank: 22,
},
),
],
),
(
7,
[
Words(
Words {
matching_words: 4,
max_matching_words: 9,
},
),
ExactAttribute(
NoExactMatch,
),
Exactness(
Rank {
rank: 5,
max_rank: 5,
},
),
Proximity(
Rank {
rank: 21,
max_rank: 22,
},
),
],
),
(
3,
[
Words(
Words {
matching_words: 4,
max_matching_words: 9,
},
),
ExactAttribute(
NoExactMatch,
),
Exactness(
Rank {
rank: 5,
max_rank: 5,
},
),
Proximity(
Rank {
rank: 17,
max_rank: 22,
},
),
],
),
(
6,
[
Words(
Words {
matching_words: 4,
max_matching_words: 9,
},
),
ExactAttribute(
NoExactMatch,
),
Exactness(
Rank {
rank: 5,
max_rank: 5,
},
),
Proximity(
Rank {
rank: 17,
max_rank: 22,
},
),
],
),
]

View File

@ -0,0 +1,110 @@
---
source: milli/src/search/new/tests/exactness.rs
expression: "format!(\"{document_ids_scores:#?}\")"
---
[
(
1,
[
Words(
Words {
matching_words: 4,
max_matching_words: 4,
},
),
Typo(
Typo {
typo_count: 0,
max_typo_count: 5,
},
),
ExactAttribute(
ExactMatch,
),
Exactness(
Rank {
rank: 5,
max_rank: 5,
},
),
],
),
(
0,
[
Words(
Words {
matching_words: 4,
max_matching_words: 4,
},
),
Typo(
Typo {
typo_count: 1,
max_typo_count: 5,
},
),
ExactAttribute(
NoExactMatch,
),
Exactness(
Rank {
rank: 4,
max_rank: 5,
},
),
],
),
(
4,
[
Words(
Words {
matching_words: 4,
max_matching_words: 4,
},
),
Typo(
Typo {
typo_count: 2,
max_typo_count: 5,
},
),
ExactAttribute(
NoExactMatch,
),
Exactness(
Rank {
rank: 4,
max_rank: 5,
},
),
],
),
(
3,
[
Words(
Words {
matching_words: 4,
max_matching_words: 4,
},
),
Typo(
Typo {
typo_count: 2,
max_typo_count: 5,
},
),
ExactAttribute(
NoExactMatch,
),
Exactness(
Rank {
rank: 3,
max_rank: 5,
},
),
],
),
]

View File

@ -0,0 +1,366 @@
---
source: milli/src/search/new/tests/exactness.rs
expression: "format!(\"{document_ids_scores:#?}\")"
---
[
(
19,
[
Words(
Words {
matching_words: 9,
max_matching_words: 9,
},
),
ExactAttribute(
ExactMatch,
),
Exactness(
Rank {
rank: 10,
max_rank: 10,
},
),
],
),
(
9,
[
Words(
Words {
matching_words: 9,
max_matching_words: 9,
},
),
ExactAttribute(
NoExactMatch,
),
Exactness(
Rank {
rank: 7,
max_rank: 10,
},
),
],
),
(
18,
[
Words(
Words {
matching_words: 8,
max_matching_words: 9,
},
),
ExactAttribute(
ExactMatch,
),
Exactness(
Rank {
rank: 9,
max_rank: 9,
},
),
],
),
(
8,
[
Words(
Words {
matching_words: 8,
max_matching_words: 9,
},
),
ExactAttribute(
NoExactMatch,
),
Exactness(
Rank {
rank: 6,
max_rank: 9,
},
),
],
),
(
17,
[
Words(
Words {
matching_words: 7,
max_matching_words: 9,
},
),
ExactAttribute(
ExactMatch,
),
Exactness(
Rank {
rank: 8,
max_rank: 8,
},
),
],
),
(
16,
[
Words(
Words {
matching_words: 7,
max_matching_words: 9,
},
),
ExactAttribute(
NoExactMatch,
),
Exactness(
Rank {
rank: 8,
max_rank: 8,
},
),
],
),
(
6,
[
Words(
Words {
matching_words: 7,
max_matching_words: 9,
},
),
ExactAttribute(
NoExactMatch,
),
Exactness(
Rank {
rank: 5,
max_rank: 8,
},
),
],
),
(
7,
[
Words(
Words {
matching_words: 7,
max_matching_words: 9,
},
),
ExactAttribute(
NoExactMatch,
),
Exactness(
Rank {
rank: 5,
max_rank: 8,
},
),
],
),
(
15,
[
Words(
Words {
matching_words: 5,
max_matching_words: 9,
},
),
ExactAttribute(
ExactMatch,
),
Exactness(
Rank {
rank: 6,
max_rank: 6,
},
),
],
),
(
5,
[
Words(
Words {
matching_words: 5,
max_matching_words: 9,
},
),
ExactAttribute(
NoExactMatch,
),
Exactness(
Rank {
rank: 3,
max_rank: 6,
},
),
],
),
(
14,
[
Words(
Words {
matching_words: 4,
max_matching_words: 9,
},
),
ExactAttribute(
ExactMatch,
),
Exactness(
Rank {
rank: 5,
max_rank: 5,
},
),
],
),
(
4,
[
Words(
Words {
matching_words: 4,
max_matching_words: 9,
},
),
ExactAttribute(
NoExactMatch,
),
Exactness(
Rank {
rank: 3,
max_rank: 5,
},
),
],
),
(
13,
[
Words(
Words {
matching_words: 3,
max_matching_words: 9,
},
),
ExactAttribute(
ExactMatch,
),
Exactness(
Rank {
rank: 4,
max_rank: 4,
},
),
],
),
(
3,
[
Words(
Words {
matching_words: 3,
max_matching_words: 9,
},
),
ExactAttribute(
NoExactMatch,
),
Exactness(
Rank {
rank: 2,
max_rank: 4,
},
),
],
),
(
12,
[
Words(
Words {
matching_words: 2,
max_matching_words: 9,
},
),
ExactAttribute(
ExactMatch,
),
Exactness(
Rank {
rank: 3,
max_rank: 3,
},
),
],
),
(
2,
[
Words(
Words {
matching_words: 2,
max_matching_words: 9,
},
),
ExactAttribute(
NoExactMatch,
),
Exactness(
Rank {
rank: 2,
max_rank: 3,
},
),
],
),
(
1,
[
Words(
Words {
matching_words: 1,
max_matching_words: 9,
},
),
ExactAttribute(
ExactMatch,
),
Exactness(
Rank {
rank: 2,
max_rank: 2,
},
),
],
),
(
11,
[
Words(
Words {
matching_words: 1,
max_matching_words: 9,
},
),
ExactAttribute(
ExactMatch,
),
Exactness(
Rank {
rank: 2,
max_rank: 2,
},
),
],
),
]

View File

@ -0,0 +1,168 @@
---
source: milli/src/search/new/tests/geo_sort.rs
expression: "format!(\"{scores:#?}\")"
---
[
[
GeoSort(
GeoSort {
target_point: [
0.0,
0.0,
],
ascending: true,
value: Some(
[
0.0,
0.0,
],
),
},
),
],
[
GeoSort(
GeoSort {
target_point: [
0.0,
0.0,
],
ascending: true,
value: Some(
[
1.0,
1.0,
],
),
},
),
],
[
GeoSort(
GeoSort {
target_point: [
0.0,
0.0,
],
ascending: true,
value: Some(
[
2.0,
-1.0,
],
),
},
),
],
[
GeoSort(
GeoSort {
target_point: [
0.0,
0.0,
],
ascending: true,
value: Some(
[
-2.0,
-2.0,
],
),
},
),
],
[
GeoSort(
GeoSort {
target_point: [
0.0,
0.0,
],
ascending: true,
value: Some(
[
3.0,
5.0,
],
),
},
),
],
[
GeoSort(
GeoSort {
target_point: [
0.0,
0.0,
],
ascending: true,
value: Some(
[
6.0,
-5.0,
],
),
},
),
],
[
GeoSort(
GeoSort {
target_point: [
0.0,
0.0,
],
ascending: true,
value: None,
},
),
],
[
GeoSort(
GeoSort {
target_point: [
0.0,
0.0,
],
ascending: true,
value: None,
},
),
],
[
GeoSort(
GeoSort {
target_point: [
0.0,
0.0,
],
ascending: true,
value: None,
},
),
],
[
GeoSort(
GeoSort {
target_point: [
0.0,
0.0,
],
ascending: true,
value: None,
},
),
],
[
GeoSort(
GeoSort {
target_point: [
0.0,
0.0,
],
ascending: true,
value: None,
},
),
],
]

View File

@ -0,0 +1,168 @@
---
source: milli/src/search/new/tests/geo_sort.rs
expression: "format!(\"{scores:#?}\")"
---
[
[
GeoSort(
GeoSort {
target_point: [
0.0,
0.0,
],
ascending: false,
value: Some(
[
6.0,
-5.0,
],
),
},
),
],
[
GeoSort(
GeoSort {
target_point: [
0.0,
0.0,
],
ascending: false,
value: Some(
[
3.0,
5.0,
],
),
},
),
],
[
GeoSort(
GeoSort {
target_point: [
0.0,
0.0,
],
ascending: false,
value: Some(
[
-2.0,
-2.0,
],
),
},
),
],
[
GeoSort(
GeoSort {
target_point: [
0.0,
0.0,
],
ascending: false,
value: Some(
[
2.0,
-1.0,
],
),
},
),
],
[
GeoSort(
GeoSort {
target_point: [
0.0,
0.0,
],
ascending: false,
value: Some(
[
1.0,
1.0,
],
),
},
),
],
[
GeoSort(
GeoSort {
target_point: [
0.0,
0.0,
],
ascending: false,
value: Some(
[
0.0,
0.0,
],
),
},
),
],
[
GeoSort(
GeoSort {
target_point: [
0.0,
0.0,
],
ascending: false,
value: None,
},
),
],
[
GeoSort(
GeoSort {
target_point: [
0.0,
0.0,
],
ascending: false,
value: None,
},
),
],
[
GeoSort(
GeoSort {
target_point: [
0.0,
0.0,
],
ascending: false,
value: None,
},
),
],
[
GeoSort(
GeoSort {
target_point: [
0.0,
0.0,
],
ascending: false,
value: None,
},
),
],
[
GeoSort(
GeoSort {
target_point: [
0.0,
0.0,
],
ascending: false,
value: None,
},
),
],
]

View File

@ -0,0 +1,91 @@
---
source: milli/src/search/new/tests/geo_sort.rs
expression: "format!(\"{scores:#?}\")"
---
[
[
GeoSort(
GeoSort {
target_point: [
0.0,
-175.0,
],
ascending: true,
value: Some(
[
0.0,
-179.0,
],
),
},
),
],
[
GeoSort(
GeoSort {
target_point: [
0.0,
-175.0,
],
ascending: true,
value: Some(
[
0.0,
178.0,
],
),
},
),
],
[
GeoSort(
GeoSort {
target_point: [
0.0,
-175.0,
],
ascending: true,
value: Some(
[
-89.0,
0.0,
],
),
},
),
],
[
GeoSort(
GeoSort {
target_point: [
0.0,
-175.0,
],
ascending: true,
value: Some(
[
88.0,
0.0,
],
),
},
),
],
[
GeoSort(
GeoSort {
target_point: [
0.0,
-175.0,
],
ascending: true,
value: Some(
[
0.0,
0.0,
],
),
},
),
],
]

View File

@ -0,0 +1,91 @@
---
source: milli/src/search/new/tests/geo_sort.rs
expression: "format!(\"{scores:#?}\")"
---
[
[
GeoSort(
GeoSort {
target_point: [
0.0,
0.0,
],
ascending: false,
value: Some(
[
0.0,
-179.0,
],
),
},
),
],
[
GeoSort(
GeoSort {
target_point: [
0.0,
0.0,
],
ascending: false,
value: Some(
[
0.0,
178.0,
],
),
},
),
],
[
GeoSort(
GeoSort {
target_point: [
0.0,
0.0,
],
ascending: false,
value: Some(
[
-89.0,
0.0,
],
),
},
),
],
[
GeoSort(
GeoSort {
target_point: [
0.0,
0.0,
],
ascending: false,
value: Some(
[
88.0,
0.0,
],
),
},
),
],
[
GeoSort(
GeoSort {
target_point: [
0.0,
0.0,
],
ascending: false,
value: Some(
[
0.0,
0.0,
],
),
},
),
],
]

View File

@ -0,0 +1,91 @@
---
source: milli/src/search/new/tests/geo_sort.rs
expression: "format!(\"{scores:#?}\")"
---
[
[
GeoSort(
GeoSort {
target_point: [
85.0,
0.0,
],
ascending: false,
value: Some(
[
-89.0,
0.0,
],
),
},
),
],
[
GeoSort(
GeoSort {
target_point: [
85.0,
0.0,
],
ascending: false,
value: Some(
[
0.0,
-179.0,
],
),
},
),
],
[
GeoSort(
GeoSort {
target_point: [
85.0,
0.0,
],
ascending: false,
value: Some(
[
0.0,
178.0,
],
),
},
),
],
[
GeoSort(
GeoSort {
target_point: [
85.0,
0.0,
],
ascending: false,
value: Some(
[
0.0,
0.0,
],
),
},
),
],
[
GeoSort(
GeoSort {
target_point: [
85.0,
0.0,
],
ascending: false,
value: Some(
[
88.0,
0.0,
],
),
},
),
],
]

View File

@ -0,0 +1,91 @@
---
source: milli/src/search/new/tests/geo_sort.rs
expression: "format!(\"{scores:#?}\")"
---
[
[
GeoSort(
GeoSort {
target_point: [
-85.0,
0.0,
],
ascending: false,
value: Some(
[
88.0,
0.0,
],
),
},
),
],
[
GeoSort(
GeoSort {
target_point: [
-85.0,
0.0,
],
ascending: false,
value: Some(
[
0.0,
-179.0,
],
),
},
),
],
[
GeoSort(
GeoSort {
target_point: [
-85.0,
0.0,
],
ascending: false,
value: Some(
[
0.0,
178.0,
],
),
},
),
],
[
GeoSort(
GeoSort {
target_point: [
-85.0,
0.0,
],
ascending: false,
value: Some(
[
0.0,
0.0,
],
),
},
),
],
[
GeoSort(
GeoSort {
target_point: [
-85.0,
0.0,
],
ascending: false,
value: Some(
[
-89.0,
0.0,
],
),
},
),
],
]

View File

@ -0,0 +1,91 @@
---
source: milli/src/search/new/tests/geo_sort.rs
expression: "format!(\"{scores:#?}\")"
---
[
[
GeoSort(
GeoSort {
target_point: [
0.0,
175.0,
],
ascending: false,
value: Some(
[
0.0,
0.0,
],
),
},
),
],
[
GeoSort(
GeoSort {
target_point: [
0.0,
175.0,
],
ascending: false,
value: Some(
[
88.0,
0.0,
],
),
},
),
],
[
GeoSort(
GeoSort {
target_point: [
0.0,
175.0,
],
ascending: false,
value: Some(
[
-89.0,
0.0,
],
),
},
),
],
[
GeoSort(
GeoSort {
target_point: [
0.0,
175.0,
],
ascending: false,
value: Some(
[
0.0,
-179.0,
],
),
},
),
],
[
GeoSort(
GeoSort {
target_point: [
0.0,
175.0,
],
ascending: false,
value: Some(
[
0.0,
178.0,
],
),
},
),
],
]

View File

@ -0,0 +1,91 @@
---
source: milli/src/search/new/tests/geo_sort.rs
expression: "format!(\"{scores:#?}\")"
---
[
[
GeoSort(
GeoSort {
target_point: [
0.0,
0.0,
],
ascending: true,
value: Some(
[
0.0,
0.0,
],
),
},
),
],
[
GeoSort(
GeoSort {
target_point: [
0.0,
0.0,
],
ascending: true,
value: Some(
[
88.0,
0.0,
],
),
},
),
],
[
GeoSort(
GeoSort {
target_point: [
0.0,
0.0,
],
ascending: true,
value: Some(
[
-89.0,
0.0,
],
),
},
),
],
[
GeoSort(
GeoSort {
target_point: [
0.0,
0.0,
],
ascending: true,
value: Some(
[
0.0,
178.0,
],
),
},
),
],
[
GeoSort(
GeoSort {
target_point: [
0.0,
0.0,
],
ascending: true,
value: Some(
[
0.0,
-179.0,
],
),
},
),
],
]

View File

@ -0,0 +1,91 @@
---
source: milli/src/search/new/tests/geo_sort.rs
expression: "format!(\"{scores:#?}\")"
---
[
[
GeoSort(
GeoSort {
target_point: [
0.0,
-175.0,
],
ascending: false,
value: Some(
[
0.0,
0.0,
],
),
},
),
],
[
GeoSort(
GeoSort {
target_point: [
0.0,
-175.0,
],
ascending: false,
value: Some(
[
88.0,
0.0,
],
),
},
),
],
[
GeoSort(
GeoSort {
target_point: [
0.0,
-175.0,
],
ascending: false,
value: Some(
[
-89.0,
0.0,
],
),
},
),
],
[
GeoSort(
GeoSort {
target_point: [
0.0,
-175.0,
],
ascending: false,
value: Some(
[
0.0,
178.0,
],
),
},
),
],
[
GeoSort(
GeoSort {
target_point: [
0.0,
-175.0,
],
ascending: false,
value: Some(
[
0.0,
-179.0,
],
),
},
),
],
]

View File

@ -0,0 +1,91 @@
---
source: milli/src/search/new/tests/geo_sort.rs
expression: "format!(\"{scores:#?}\")"
---
[
[
GeoSort(
GeoSort {
target_point: [
85.0,
0.0,
],
ascending: true,
value: Some(
[
88.0,
0.0,
],
),
},
),
],
[
GeoSort(
GeoSort {
target_point: [
85.0,
0.0,
],
ascending: true,
value: Some(
[
0.0,
0.0,
],
),
},
),
],
[
GeoSort(
GeoSort {
target_point: [
85.0,
0.0,
],
ascending: true,
value: Some(
[
0.0,
178.0,
],
),
},
),
],
[
GeoSort(
GeoSort {
target_point: [
85.0,
0.0,
],
ascending: true,
value: Some(
[
0.0,
-179.0,
],
),
},
),
],
[
GeoSort(
GeoSort {
target_point: [
85.0,
0.0,
],
ascending: true,
value: Some(
[
-89.0,
0.0,
],
),
},
),
],
]

View File

@ -0,0 +1,91 @@
---
source: milli/src/search/new/tests/geo_sort.rs
expression: "format!(\"{scores:#?}\")"
---
[
[
GeoSort(
GeoSort {
target_point: [
-85.0,
0.0,
],
ascending: true,
value: Some(
[
-89.0,
0.0,
],
),
},
),
],
[
GeoSort(
GeoSort {
target_point: [
-85.0,
0.0,
],
ascending: true,
value: Some(
[
0.0,
0.0,
],
),
},
),
],
[
GeoSort(
GeoSort {
target_point: [
-85.0,
0.0,
],
ascending: true,
value: Some(
[
0.0,
178.0,
],
),
},
),
],
[
GeoSort(
GeoSort {
target_point: [
-85.0,
0.0,
],
ascending: true,
value: Some(
[
0.0,
-179.0,
],
),
},
),
],
[
GeoSort(
GeoSort {
target_point: [
-85.0,
0.0,
],
ascending: true,
value: Some(
[
88.0,
0.0,
],
),
},
),
],
]

View File

@ -0,0 +1,91 @@
---
source: milli/src/search/new/tests/geo_sort.rs
expression: "format!(\"{scores:#?}\")"
---
[
[
GeoSort(
GeoSort {
target_point: [
0.0,
175.0,
],
ascending: true,
value: Some(
[
0.0,
178.0,
],
),
},
),
],
[
GeoSort(
GeoSort {
target_point: [
0.0,
175.0,
],
ascending: true,
value: Some(
[
0.0,
-179.0,
],
),
},
),
],
[
GeoSort(
GeoSort {
target_point: [
0.0,
175.0,
],
ascending: true,
value: Some(
[
-89.0,
0.0,
],
),
},
),
],
[
GeoSort(
GeoSort {
target_point: [
0.0,
175.0,
],
ascending: true,
value: Some(
[
88.0,
0.0,
],
),
},
),
],
[
GeoSort(
GeoSort {
target_point: [
0.0,
175.0,
],
ascending: true,
value: Some(
[
0.0,
0.0,
],
),
},
),
],
]

View File

@ -0,0 +1,75 @@
---
source: milli/src/search/new/tests/geo_sort.rs
expression: "format!(\"{scores:#?}\")"
---
[
[
Words(
Words {
matching_words: 1,
max_matching_words: 1,
},
),
GeoSort(
GeoSort {
target_point: [
0.0,
0.0,
],
ascending: true,
value: Some(
[
0.0,
0.0,
],
),
},
),
],
[
Words(
Words {
matching_words: 1,
max_matching_words: 1,
},
),
GeoSort(
GeoSort {
target_point: [
0.0,
0.0,
],
ascending: true,
value: Some(
[
-89.0,
0.0,
],
),
},
),
],
[
Words(
Words {
matching_words: 1,
max_matching_words: 1,
},
),
GeoSort(
GeoSort {
target_point: [
0.0,
0.0,
],
ascending: true,
value: Some(
[
0.0,
178.0,
],
),
},
),
],
]

View File

@ -0,0 +1,60 @@
---
source: milli/src/search/new/tests/geo_sort.rs
expression: "format!(\"{scores:#?}\")"
---
[
[
Words(
Words {
matching_words: 1,
max_matching_words: 1,
},
),
GeoSort(
GeoSort {
target_point: [
0.0,
0.0,
],
ascending: true,
value: None,
},
),
],
[
Words(
Words {
matching_words: 1,
max_matching_words: 1,
},
),
GeoSort(
GeoSort {
target_point: [
0.0,
0.0,
],
ascending: true,
value: None,
},
),
],
[
Words(
Words {
matching_words: 1,
max_matching_words: 1,
},
),
GeoSort(
GeoSort {
target_point: [
0.0,
0.0,
],
ascending: true,
value: None,
},
),
],
]

View File

@ -0,0 +1,70 @@
---
source: milli/src/search/new/tests/proximity.rs
expression: "format!(\"{document_scores:#?}\")"
---
[
[
Proximity(
Rank {
rank: 8,
max_rank: 8,
},
),
],
[
Proximity(
Rank {
rank: 7,
max_rank: 8,
},
),
],
[
Proximity(
Rank {
rank: 6,
max_rank: 8,
},
),
],
[
Proximity(
Rank {
rank: 6,
max_rank: 8,
},
),
],
[
Proximity(
Rank {
rank: 5,
max_rank: 8,
},
),
],
[
Proximity(
Rank {
rank: 5,
max_rank: 8,
},
),
],
[
Proximity(
Rank {
rank: 4,
max_rank: 8,
},
),
],
[
Proximity(
Rank {
rank: 1,
max_rank: 8,
},
),
],
]

View File

@ -0,0 +1,70 @@
---
source: milli/src/search/new/tests/proximity.rs
expression: "format!(\"{document_scores:#?}\")"
---
[
[
Proximity(
Rank {
rank: 8,
max_rank: 8,
},
),
],
[
Proximity(
Rank {
rank: 7,
max_rank: 8,
},
),
],
[
Proximity(
Rank {
rank: 6,
max_rank: 8,
},
),
],
[
Proximity(
Rank {
rank: 6,
max_rank: 8,
},
),
],
[
Proximity(
Rank {
rank: 5,
max_rank: 8,
},
),
],
[
Proximity(
Rank {
rank: 1,
max_rank: 8,
},
),
],
[
Proximity(
Rank {
rank: 1,
max_rank: 8,
},
),
],
[
Proximity(
Rank {
rank: 1,
max_rank: 8,
},
),
],
]

View File

@ -0,0 +1,78 @@
---
source: milli/src/search/new/tests/proximity.rs
expression: "format!(\"{document_scores:#?}\")"
---
[
[
Proximity(
Rank {
rank: 8,
max_rank: 8,
},
),
],
[
Proximity(
Rank {
rank: 7,
max_rank: 8,
},
),
],
[
Proximity(
Rank {
rank: 6,
max_rank: 8,
},
),
],
[
Proximity(
Rank {
rank: 6,
max_rank: 8,
},
),
],
[
Proximity(
Rank {
rank: 5,
max_rank: 8,
},
),
],
[
Proximity(
Rank {
rank: 1,
max_rank: 8,
},
),
],
[
Proximity(
Rank {
rank: 1,
max_rank: 8,
},
),
],
[
Proximity(
Rank {
rank: 1,
max_rank: 8,
},
),
],
[
Proximity(
Rank {
rank: 1,
max_rank: 8,
},
),
],
]

View File

@ -0,0 +1,78 @@
---
source: milli/src/search/new/tests/proximity.rs
expression: "format!(\"{document_scores:#?}\")"
---
[
[
Proximity(
Rank {
rank: 8,
max_rank: 8,
},
),
],
[
Proximity(
Rank {
rank: 7,
max_rank: 8,
},
),
],
[
Proximity(
Rank {
rank: 6,
max_rank: 8,
},
),
],
[
Proximity(
Rank {
rank: 6,
max_rank: 8,
},
),
],
[
Proximity(
Rank {
rank: 5,
max_rank: 8,
},
),
],
[
Proximity(
Rank {
rank: 5,
max_rank: 8,
},
),
],
[
Proximity(
Rank {
rank: 4,
max_rank: 8,
},
),
],
[
Proximity(
Rank {
rank: 1,
max_rank: 8,
},
),
],
[
Proximity(
Rank {
rank: 1,
max_rank: 8,
},
),
],
]

View File

@ -0,0 +1,70 @@
---
source: milli/src/search/new/tests/proximity.rs
expression: "format!(\"{document_scores:#?}\")"
---
[
[
Proximity(
Rank {
rank: 1,
max_rank: 8,
},
),
],
[
Proximity(
Rank {
rank: 1,
max_rank: 8,
},
),
],
[
Proximity(
Rank {
rank: 1,
max_rank: 8,
},
),
],
[
Proximity(
Rank {
rank: 1,
max_rank: 8,
},
),
],
[
Proximity(
Rank {
rank: 1,
max_rank: 8,
},
),
],
[
Proximity(
Rank {
rank: 1,
max_rank: 8,
},
),
],
[
Proximity(
Rank {
rank: 1,
max_rank: 8,
},
),
],
[
Proximity(
Rank {
rank: 1,
max_rank: 8,
},
),
],
]

View File

@ -0,0 +1,46 @@
---
source: milli/src/search/new/tests/proximity.rs
expression: "format!(\"{document_scores:#?}\")"
---
[
[
Proximity(
Rank {
rank: 8,
max_rank: 8,
},
),
],
[
Proximity(
Rank {
rank: 8,
max_rank: 8,
},
),
],
[
Proximity(
Rank {
rank: 8,
max_rank: 8,
},
),
],
[
Proximity(
Rank {
rank: 1,
max_rank: 8,
},
),
],
[
Proximity(
Rank {
rank: 1,
max_rank: 8,
},
),
],
]

View File

@ -0,0 +1,30 @@
---
source: milli/src/search/new/tests/proximity.rs
expression: "format!(\"{document_scores:#?}\")"
---
[
[
Proximity(
Rank {
rank: 8,
max_rank: 8,
},
),
],
[
Proximity(
Rank {
rank: 8,
max_rank: 8,
},
),
],
[
Proximity(
Rank {
rank: 1,
max_rank: 8,
},
),
],
]

View File

@ -0,0 +1,30 @@
---
source: milli/src/search/new/tests/proximity.rs
expression: "format!(\"{document_scores:#?}\")"
---
[
[
Proximity(
Rank {
rank: 8,
max_rank: 8,
},
),
],
[
Proximity(
Rank {
rank: 8,
max_rank: 8,
},
),
],
[
Proximity(
Rank {
rank: 1,
max_rank: 8,
},
),
],
]

View File

@ -0,0 +1,206 @@
---
source: milli/src/search/new/tests/sort.rs
expression: document_scores_json
---
[
{
"vague:asc": {
"order": 0,
"value": 0.0
},
"<hidden-rule-1>": {
"order": 1,
"value": "<hidden>"
}
},
{
"vague:asc": {
"order": 0,
"value": 1.0
},
"<hidden-rule-1>": {
"order": 1,
"value": "<hidden>"
}
},
{
"vague:asc": {
"order": 0,
"value": 1.0
},
"<hidden-rule-1>": {
"order": 1,
"value": "<hidden>"
}
},
{
"vague:asc": {
"order": 0,
"value": 1.0
},
"<hidden-rule-1>": {
"order": 1,
"value": "<hidden>"
}
},
{
"vague:asc": {
"order": 0,
"value": 1.1367
},
"<hidden-rule-1>": {
"order": 1,
"value": "<hidden>"
}
},
{
"vague:asc": {
"order": 0,
"value": 1.2367
},
"<hidden-rule-1>": {
"order": 1,
"value": "<hidden>"
}
},
{
"vague:asc": {
"order": 0,
"value": 1.5673
},
"<hidden-rule-1>": {
"order": 1,
"value": "<hidden>"
}
},
{
"vague:asc": {
"order": 0,
"value": "0"
},
"<hidden-rule-1>": {
"order": 1,
"value": "<hidden>"
}
},
{
"vague:asc": {
"order": 0,
"value": "1"
},
"<hidden-rule-1>": {
"order": 1,
"value": "<hidden>"
}
},
{
"vague:asc": {
"order": 0,
"value": "false"
},
"<hidden-rule-1>": {
"order": 1,
"value": "<hidden>"
}
},
{
"vague:asc": {
"order": 0,
"value": "false"
},
"<hidden-rule-1>": {
"order": 1,
"value": "<hidden>"
}
},
{
"vague:asc": {
"order": 0,
"value": "true"
},
"<hidden-rule-1>": {
"order": 1,
"value": "<hidden>"
}
},
{
"vague:asc": {
"order": 0,
"value": "true"
},
"<hidden-rule-1>": {
"order": 1,
"value": "<hidden>"
}
},
{
"vague:asc": {
"order": 0,
"value": null
},
"<hidden-rule-1>": {
"order": 1,
"value": "<hidden>"
}
},
{
"vague:asc": {
"order": 0,
"value": null
},
"<hidden-rule-1>": {
"order": 1,
"value": "<hidden>"
}
},
{
"vague:asc": {
"order": 0,
"value": null
},
"<hidden-rule-1>": {
"order": 1,
"value": "<hidden>"
}
},
{
"vague:asc": {
"order": 0,
"value": null
},
"<hidden-rule-1>": {
"order": 1,
"value": "<hidden>"
}
},
{
"vague:asc": {
"order": 0,
"value": null
},
"<hidden-rule-1>": {
"order": 1,
"value": "<hidden>"
}
},
{
"vague:asc": {
"order": 0,
"value": null
},
"<hidden-rule-1>": {
"order": 1,
"value": "<hidden>"
}
},
{
"vague:asc": {
"order": 0,
"value": null
},
"<hidden-rule-1>": {
"order": 1,
"value": "<hidden>"
}
}
]

View File

@ -0,0 +1,206 @@
---
source: milli/src/search/new/tests/sort.rs
expression: "format!(\"{document_scores:#?}\")"
---
[
[
Sort(
Sort {
field_name: "vague",
ascending: false,
redacted: false,
value: Number(2.0),
},
),
],
[
Sort(
Sort {
field_name: "vague",
ascending: false,
redacted: false,
value: Number(1.5673),
},
),
],
[
Sort(
Sort {
field_name: "vague",
ascending: false,
redacted: false,
value: Number(1.2367),
},
),
],
[
Sort(
Sort {
field_name: "vague",
ascending: false,
redacted: false,
value: Number(1.1367),
},
),
],
[
Sort(
Sort {
field_name: "vague",
ascending: false,
redacted: false,
value: Number(1.0),
},
),
],
[
Sort(
Sort {
field_name: "vague",
ascending: false,
redacted: false,
value: Number(1.0),
},
),
],
[
Sort(
Sort {
field_name: "vague",
ascending: false,
redacted: false,
value: Number(0.0),
},
),
],
[
Sort(
Sort {
field_name: "vague",
ascending: false,
redacted: false,
value: String("true"),
},
),
],
[
Sort(
Sort {
field_name: "vague",
ascending: false,
redacted: false,
value: String("true"),
},
),
],
[
Sort(
Sort {
field_name: "vague",
ascending: false,
redacted: false,
value: String("false"),
},
),
],
[
Sort(
Sort {
field_name: "vague",
ascending: false,
redacted: false,
value: String("false"),
},
),
],
[
Sort(
Sort {
field_name: "vague",
ascending: false,
redacted: false,
value: String("1"),
},
),
],
[
Sort(
Sort {
field_name: "vague",
ascending: false,
redacted: false,
value: String("0"),
},
),
],
[
Sort(
Sort {
field_name: "vague",
ascending: false,
redacted: false,
value: Null,
},
),
],
[
Sort(
Sort {
field_name: "vague",
ascending: false,
redacted: false,
value: Null,
},
),
],
[
Sort(
Sort {
field_name: "vague",
ascending: false,
redacted: false,
value: Null,
},
),
],
[
Sort(
Sort {
field_name: "vague",
ascending: false,
redacted: false,
value: Null,
},
),
],
[
Sort(
Sort {
field_name: "vague",
ascending: false,
redacted: false,
value: Null,
},
),
],
[
Sort(
Sort {
field_name: "vague",
ascending: false,
redacted: false,
value: Null,
},
),
],
[
Sort(
Sort {
field_name: "vague",
ascending: false,
redacted: false,
value: Null,
},
),
],
]

View File

@ -0,0 +1,206 @@
---
source: milli/src/search/new/tests/sort.rs
expression: "format!(\"{document_scores:#?}\")"
---
[
[
Sort(
Sort {
field_name: "letter",
ascending: false,
redacted: false,
value: String("i"),
},
),
],
[
Sort(
Sort {
field_name: "letter",
ascending: false,
redacted: false,
value: String("i"),
},
),
],
[
Sort(
Sort {
field_name: "letter",
ascending: false,
redacted: false,
value: String("i"),
},
),
],
[
Sort(
Sort {
field_name: "letter",
ascending: false,
redacted: false,
value: String("h"),
},
),
],
[
Sort(
Sort {
field_name: "letter",
ascending: false,
redacted: false,
value: String("g"),
},
),
],
[
Sort(
Sort {
field_name: "letter",
ascending: false,
redacted: false,
value: String("g"),
},
),
],
[
Sort(
Sort {
field_name: "letter",
ascending: false,
redacted: false,
value: String("f"),
},
),
],
[
Sort(
Sort {
field_name: "letter",
ascending: false,
redacted: false,
value: String("f"),
},
),
],
[
Sort(
Sort {
field_name: "letter",
ascending: false,
redacted: false,
value: String("f"),
},
),
],
[
Sort(
Sort {
field_name: "letter",
ascending: false,
redacted: false,
value: String("e"),
},
),
],
[
Sort(
Sort {
field_name: "letter",
ascending: false,
redacted: false,
value: String("e"),
},
),
],
[
Sort(
Sort {
field_name: "letter",
ascending: false,
redacted: false,
value: String("e"),
},
),
],
[
Sort(
Sort {
field_name: "letter",
ascending: false,
redacted: false,
value: String("e"),
},
),
],
[
Sort(
Sort {
field_name: "letter",
ascending: false,
redacted: false,
value: String("e"),
},
),
],
[
Sort(
Sort {
field_name: "letter",
ascending: false,
redacted: false,
value: String("e"),
},
),
],
[
Sort(
Sort {
field_name: "letter",
ascending: false,
redacted: false,
value: String("d"),
},
),
],
[
Sort(
Sort {
field_name: "letter",
ascending: false,
redacted: false,
value: String("c"),
},
),
],
[
Sort(
Sort {
field_name: "letter",
ascending: false,
redacted: false,
value: String("c"),
},
),
],
[
Sort(
Sort {
field_name: "letter",
ascending: false,
redacted: false,
value: String("c"),
},
),
],
[
Sort(
Sort {
field_name: "letter",
ascending: false,
redacted: false,
value: String("b"),
},
),
],
]

View File

@ -0,0 +1,206 @@
---
source: milli/src/search/new/tests/sort.rs
expression: "format!(\"{document_scores:#?}\")"
---
[
[
Sort(
Sort {
field_name: "rank",
ascending: false,
redacted: false,
value: Number(5.0),
},
),
],
[
Sort(
Sort {
field_name: "rank",
ascending: false,
redacted: false,
value: Number(4.0),
},
),
],
[
Sort(
Sort {
field_name: "rank",
ascending: false,
redacted: false,
value: Number(3.0),
},
),
],
[
Sort(
Sort {
field_name: "rank",
ascending: false,
redacted: false,
value: Number(2.0),
},
),
],
[
Sort(
Sort {
field_name: "rank",
ascending: false,
redacted: false,
value: Number(2.0),
},
),
],
[
Sort(
Sort {
field_name: "rank",
ascending: false,
redacted: false,
value: Number(2.0),
},
),
],
[
Sort(
Sort {
field_name: "rank",
ascending: false,
redacted: false,
value: Number(2.0),
},
),
],
[
Sort(
Sort {
field_name: "rank",
ascending: false,
redacted: false,
value: Number(2.0),
},
),
],
[
Sort(
Sort {
field_name: "rank",
ascending: false,
redacted: false,
value: Number(1.0),
},
),
],
[
Sort(
Sort {
field_name: "rank",
ascending: false,
redacted: false,
value: Number(1.0),
},
),
],
[
Sort(
Sort {
field_name: "rank",
ascending: false,
redacted: false,
value: Number(1.0),
},
),
],
[
Sort(
Sort {
field_name: "rank",
ascending: false,
redacted: false,
value: Number(1.0),
},
),
],
[
Sort(
Sort {
field_name: "rank",
ascending: false,
redacted: false,
value: Number(1.0),
},
),
],
[
Sort(
Sort {
field_name: "rank",
ascending: false,
redacted: false,
value: Number(1.0),
},
),
],
[
Sort(
Sort {
field_name: "rank",
ascending: false,
redacted: false,
value: Number(1.0),
},
),
],
[
Sort(
Sort {
field_name: "rank",
ascending: false,
redacted: false,
value: Number(0.0),
},
),
],
[
Sort(
Sort {
field_name: "rank",
ascending: false,
redacted: false,
value: Number(0.0),
},
),
],
[
Sort(
Sort {
field_name: "rank",
ascending: false,
redacted: false,
value: Number(0.0),
},
),
],
[
Sort(
Sort {
field_name: "rank",
ascending: false,
redacted: false,
value: Number(0.0),
},
),
],
[
Sort(
Sort {
field_name: "rank",
ascending: false,
redacted: false,
value: Number(0.0),
},
),
],
]

View File

@ -0,0 +1,206 @@
---
source: milli/src/search/new/tests/sort.rs
expression: "format!(\"{document_scores:#?}\")"
---
[
[
Sort(
Sort {
field_name: "vague",
ascending: true,
redacted: false,
value: Number(0.0),
},
),
],
[
Sort(
Sort {
field_name: "vague",
ascending: true,
redacted: false,
value: Number(1.0),
},
),
],
[
Sort(
Sort {
field_name: "vague",
ascending: true,
redacted: false,
value: Number(1.0),
},
),
],
[
Sort(
Sort {
field_name: "vague",
ascending: true,
redacted: false,
value: Number(1.0),
},
),
],
[
Sort(
Sort {
field_name: "vague",
ascending: true,
redacted: false,
value: Number(1.1367),
},
),
],
[
Sort(
Sort {
field_name: "vague",
ascending: true,
redacted: false,
value: Number(1.2367),
},
),
],
[
Sort(
Sort {
field_name: "vague",
ascending: true,
redacted: false,
value: Number(1.5673),
},
),
],
[
Sort(
Sort {
field_name: "vague",
ascending: true,
redacted: false,
value: String("0"),
},
),
],
[
Sort(
Sort {
field_name: "vague",
ascending: true,
redacted: false,
value: String("1"),
},
),
],
[
Sort(
Sort {
field_name: "vague",
ascending: true,
redacted: false,
value: String("false"),
},
),
],
[
Sort(
Sort {
field_name: "vague",
ascending: true,
redacted: false,
value: String("false"),
},
),
],
[
Sort(
Sort {
field_name: "vague",
ascending: true,
redacted: false,
value: String("true"),
},
),
],
[
Sort(
Sort {
field_name: "vague",
ascending: true,
redacted: false,
value: String("true"),
},
),
],
[
Sort(
Sort {
field_name: "vague",
ascending: true,
redacted: false,
value: Null,
},
),
],
[
Sort(
Sort {
field_name: "vague",
ascending: true,
redacted: false,
value: Null,
},
),
],
[
Sort(
Sort {
field_name: "vague",
ascending: true,
redacted: false,
value: Null,
},
),
],
[
Sort(
Sort {
field_name: "vague",
ascending: true,
redacted: false,
value: Null,
},
),
],
[
Sort(
Sort {
field_name: "vague",
ascending: true,
redacted: false,
value: Null,
},
),
],
[
Sort(
Sort {
field_name: "vague",
ascending: true,
redacted: false,
value: Null,
},
),
],
[
Sort(
Sort {
field_name: "vague",
ascending: true,
redacted: false,
value: Null,
},
),
],
]

View File

@ -0,0 +1,129 @@
---
source: milli/src/search/new/tests/stop_words.rs
expression: "format!(\"{document_scores:#?}\")"
---
[
[
Words(
Words {
matching_words: 3,
max_matching_words: 3,
},
),
Typo(
Typo {
typo_count: 0,
max_typo_count: 2,
},
),
Proximity(
Rank {
rank: 1,
max_rank: 1,
},
),
Fid(
Rank {
rank: 1,
max_rank: 1,
},
),
Position(
Rank {
rank: 31,
max_rank: 31,
},
),
ExactAttribute(
NoExactMatch,
),
Exactness(
Rank {
rank: 4,
max_rank: 4,
},
),
],
[
Words(
Words {
matching_words: 3,
max_matching_words: 3,
},
),
Typo(
Typo {
typo_count: 0,
max_typo_count: 2,
},
),
Proximity(
Rank {
rank: 1,
max_rank: 1,
},
),
Fid(
Rank {
rank: 1,
max_rank: 1,
},
),
Position(
Rank {
rank: 31,
max_rank: 31,
},
),
ExactAttribute(
NoExactMatch,
),
Exactness(
Rank {
rank: 4,
max_rank: 4,
},
),
],
[
Words(
Words {
matching_words: 3,
max_matching_words: 3,
},
),
Typo(
Typo {
typo_count: 0,
max_typo_count: 2,
},
),
Proximity(
Rank {
rank: 1,
max_rank: 1,
},
),
Fid(
Rank {
rank: 1,
max_rank: 1,
},
),
Position(
Rank {
rank: 27,
max_rank: 31,
},
),
ExactAttribute(
NoExactMatch,
),
Exactness(
Rank {
rank: 4,
max_rank: 4,
},
),
],
]

View File

@ -0,0 +1,13 @@
---
source: milli/src/search/new/tests/stop_words.rs
expression: "format!(\"{document_scores:#?}\")"
---
[
[],
[],
[],
[],
[],
[],
[],
]

View File

@ -0,0 +1,12 @@
---
source: milli/src/search/new/tests/typo.rs
expression: "format!(\"{document_scores:#?}\")"
---
[
[],
[],
[],
[],
[],
[],
]

View File

@ -0,0 +1,54 @@
---
source: milli/src/search/new/tests/typo.rs
expression: "format!(\"{document_scores:#?}\")"
---
[
[
Typo(
Typo {
typo_count: 0,
max_typo_count: 5,
},
),
],
[
Typo(
Typo {
typo_count: 0,
max_typo_count: 5,
},
),
],
[
Typo(
Typo {
typo_count: 1,
max_typo_count: 5,
},
),
],
[
Typo(
Typo {
typo_count: 1,
max_typo_count: 5,
},
),
],
[
Typo(
Typo {
typo_count: 2,
max_typo_count: 5,
},
),
],
[
Typo(
Typo {
typo_count: 5,
max_typo_count: 5,
},
),
],
]

View File

@ -0,0 +1,54 @@
---
source: milli/src/search/new/tests/typo.rs
expression: "format!(\"{document_scores:#?}\")"
---
[
[
Typo(
Typo {
typo_count: 0,
max_typo_count: 6,
},
),
],
[
Typo(
Typo {
typo_count: 0,
max_typo_count: 6,
},
),
],
[
Typo(
Typo {
typo_count: 2,
max_typo_count: 6,
},
),
],
[
Typo(
Typo {
typo_count: 2,
max_typo_count: 6,
},
),
],
[
Typo(
Typo {
typo_count: 3,
max_typo_count: 6,
},
),
],
[
Typo(
Typo {
typo_count: 4,
max_typo_count: 6,
},
),
],
]

View File

@ -0,0 +1,9 @@
---
source: milli/src/search/new/tests/typo.rs
expression: "format!(\"{document_scores:#?}\")"
---
[
[],
[],
[],
]

View File

@ -0,0 +1,9 @@
---
source: milli/src/search/new/tests/typo.rs
expression: "format!(\"{document_scores:#?}\")"
---
[
[],
[],
[],
]

View File

@ -0,0 +1,244 @@
---
source: milli/src/search/new/tests/typo.rs
expression: "format!(\"{document_scores:#?}\")"
---
[
[
Words(
Words {
matching_words: 9,
max_matching_words: 9,
},
),
Typo(
Typo {
typo_count: 0,
max_typo_count: 9,
},
),
],
[
Words(
Words {
matching_words: 9,
max_matching_words: 9,
},
),
Typo(
Typo {
typo_count: 1,
max_typo_count: 9,
},
),
],
[
Words(
Words {
matching_words: 8,
max_matching_words: 9,
},
),
Typo(
Typo {
typo_count: 0,
max_typo_count: 8,
},
),
],
[
Words(
Words {
matching_words: 7,
max_matching_words: 9,
},
),
Typo(
Typo {
typo_count: 0,
max_typo_count: 7,
},
),
],
[
Words(
Words {
matching_words: 7,
max_matching_words: 9,
},
),
Typo(
Typo {
typo_count: 0,
max_typo_count: 7,
},
),
],
[
Words(
Words {
matching_words: 7,
max_matching_words: 9,
},
),
Typo(
Typo {
typo_count: 0,
max_typo_count: 7,
},
),
],
[
Words(
Words {
matching_words: 5,
max_matching_words: 9,
},
),
Typo(
Typo {
typo_count: 0,
max_typo_count: 5,
},
),
],
[
Words(
Words {
matching_words: 4,
max_matching_words: 9,
},
),
Typo(
Typo {
typo_count: 0,
max_typo_count: 4,
},
),
],
[
Words(
Words {
matching_words: 3,
max_matching_words: 9,
},
),
Typo(
Typo {
typo_count: 0,
max_typo_count: 3,
},
),
],
[
Words(
Words {
matching_words: 3,
max_matching_words: 9,
},
),
Typo(
Typo {
typo_count: 0,
max_typo_count: 3,
},
),
],
[
Words(
Words {
matching_words: 3,
max_matching_words: 9,
},
),
Typo(
Typo {
typo_count: 0,
max_typo_count: 3,
},
),
],
[
Words(
Words {
matching_words: 2,
max_matching_words: 9,
},
),
Typo(
Typo {
typo_count: 0,
max_typo_count: 2,
},
),
],
[
Words(
Words {
matching_words: 2,
max_matching_words: 9,
},
),
Typo(
Typo {
typo_count: 1,
max_typo_count: 2,
},
),
],
[
Words(
Words {
matching_words: 1,
max_matching_words: 9,
},
),
Typo(
Typo {
typo_count: 0,
max_typo_count: 1,
},
),
],
[
Words(
Words {
matching_words: 1,
max_matching_words: 9,
},
),
Typo(
Typo {
typo_count: 0,
max_typo_count: 1,
},
),
],
[
Words(
Words {
matching_words: 1,
max_matching_words: 9,
},
),
Typo(
Typo {
typo_count: 0,
max_typo_count: 1,
},
),
],
[
Words(
Words {
matching_words: 1,
max_matching_words: 9,
},
),
Typo(
Typo {
typo_count: 0,
max_typo_count: 1,
},
),
],
]

View File

@ -0,0 +1,244 @@
---
source: milli/src/search/new/tests/typo.rs
expression: "format!(\"{document_scores:#?}\")"
---
[
[
Words(
Words {
matching_words: 9,
max_matching_words: 9,
},
),
Typo(
Typo {
typo_count: 0,
max_typo_count: 9,
},
),
],
[
Words(
Words {
matching_words: 9,
max_matching_words: 9,
},
),
Typo(
Typo {
typo_count: 1,
max_typo_count: 9,
},
),
],
[
Words(
Words {
matching_words: 8,
max_matching_words: 9,
},
),
Typo(
Typo {
typo_count: 0,
max_typo_count: 8,
},
),
],
[
Words(
Words {
matching_words: 7,
max_matching_words: 9,
},
),
Typo(
Typo {
typo_count: 0,
max_typo_count: 7,
},
),
],
[
Words(
Words {
matching_words: 7,
max_matching_words: 9,
},
),
Typo(
Typo {
typo_count: 0,
max_typo_count: 7,
},
),
],
[
Words(
Words {
matching_words: 7,
max_matching_words: 9,
},
),
Typo(
Typo {
typo_count: 0,
max_typo_count: 7,
},
),
],
[
Words(
Words {
matching_words: 5,
max_matching_words: 9,
},
),
Typo(
Typo {
typo_count: 0,
max_typo_count: 5,
},
),
],
[
Words(
Words {
matching_words: 4,
max_matching_words: 9,
},
),
Typo(
Typo {
typo_count: 0,
max_typo_count: 4,
},
),
],
[
Words(
Words {
matching_words: 3,
max_matching_words: 9,
},
),
Typo(
Typo {
typo_count: 0,
max_typo_count: 3,
},
),
],
[
Words(
Words {
matching_words: 3,
max_matching_words: 9,
},
),
Typo(
Typo {
typo_count: 0,
max_typo_count: 3,
},
),
],
[
Words(
Words {
matching_words: 3,
max_matching_words: 9,
},
),
Typo(
Typo {
typo_count: 0,
max_typo_count: 3,
},
),
],
[
Words(
Words {
matching_words: 2,
max_matching_words: 9,
},
),
Typo(
Typo {
typo_count: 0,
max_typo_count: 2,
},
),
],
[
Words(
Words {
matching_words: 2,
max_matching_words: 9,
},
),
Typo(
Typo {
typo_count: 1,
max_typo_count: 2,
},
),
],
[
Words(
Words {
matching_words: 1,
max_matching_words: 9,
},
),
Typo(
Typo {
typo_count: 0,
max_typo_count: 1,
},
),
],
[
Words(
Words {
matching_words: 1,
max_matching_words: 9,
},
),
Typo(
Typo {
typo_count: 0,
max_typo_count: 1,
},
),
],
[
Words(
Words {
matching_words: 1,
max_matching_words: 9,
},
),
Typo(
Typo {
typo_count: 0,
max_typo_count: 1,
},
),
],
[
Words(
Words {
matching_words: 1,
max_matching_words: 9,
},
),
Typo(
Typo {
typo_count: 0,
max_typo_count: 1,
},
),
],
]

View File

@ -0,0 +1,30 @@
---
source: milli/src/search/new/tests/typo.rs
expression: "format!(\"{document_scores:#?}\")"
---
[
[
Typo(
Typo {
typo_count: 0,
max_typo_count: 13,
},
),
],
[
Typo(
Typo {
typo_count: 0,
max_typo_count: 13,
},
),
],
[
Typo(
Typo {
typo_count: 1,
max_typo_count: 13,
},
),
],
]

View File

@ -0,0 +1,30 @@
---
source: milli/src/search/new/tests/typo.rs
expression: "format!(\"{document_scores:#?}\")"
---
[
[
Typo(
Typo {
typo_count: 0,
max_typo_count: 13,
},
),
],
[
Typo(
Typo {
typo_count: 2,
max_typo_count: 13,
},
),
],
[
Typo(
Typo {
typo_count: 2,
max_typo_count: 13,
},
),
],
]

View File

@ -0,0 +1,62 @@
---
source: milli/src/search/new/tests/typo_proximity.rs
expression: "format!(\"{document_scores:#?}\")"
---
[
[
Typo(
Typo {
typo_count: 0,
max_typo_count: 3,
},
),
Proximity(
Rank {
rank: 8,
max_rank: 8,
},
),
],
[
Typo(
Typo {
typo_count: 0,
max_typo_count: 3,
},
),
Proximity(
Rank {
rank: 5,
max_rank: 8,
},
),
],
[
Typo(
Typo {
typo_count: 1,
max_typo_count: 3,
},
),
Proximity(
Rank {
rank: 8,
max_rank: 8,
},
),
],
[
Typo(
Typo {
typo_count: 1,
max_typo_count: 3,
},
),
Proximity(
Rank {
rank: 7,
max_rank: 8,
},
),
],
]

View File

@ -0,0 +1,34 @@
---
source: milli/src/search/new/tests/typo_proximity.rs
expression: "format!(\"{document_scores:#?}\")"
---
[
[
Typo(
Typo {
typo_count: 1,
max_typo_count: 5,
},
),
Proximity(
Rank {
rank: 15,
max_rank: 15,
},
),
],
[
Typo(
Typo {
typo_count: 1,
max_typo_count: 5,
},
),
Proximity(
Rank {
rank: 8,
max_rank: 15,
},
),
],
]

View File

@ -0,0 +1,216 @@
---
source: milli/src/search/new/tests/words_tms.rs
expression: "format!(\"{document_scores:#?}\")"
---
[
[
Words(
Words {
matching_words: 9,
max_matching_words: 9,
},
),
Proximity(
Rank {
rank: 50,
max_rank: 50,
},
),
],
[
Words(
Words {
matching_words: 9,
max_matching_words: 9,
},
),
Proximity(
Rank {
rank: 50,
max_rank: 50,
},
),
],
[
Words(
Words {
matching_words: 9,
max_matching_words: 9,
},
),
Proximity(
Rank {
rank: 49,
max_rank: 50,
},
),
],
[
Words(
Words {
matching_words: 9,
max_matching_words: 9,
},
),
Proximity(
Rank {
rank: 49,
max_rank: 50,
},
),
],
[
Words(
Words {
matching_words: 9,
max_matching_words: 9,
},
),
Proximity(
Rank {
rank: 48,
max_rank: 50,
},
),
],
[
Words(
Words {
matching_words: 9,
max_matching_words: 9,
},
),
Proximity(
Rank {
rank: 41,
max_rank: 50,
},
),
],
[
Words(
Words {
matching_words: 9,
max_matching_words: 9,
},
),
Proximity(
Rank {
rank: 40,
max_rank: 50,
},
),
],
[
Words(
Words {
matching_words: 8,
max_matching_words: 9,
},
),
Proximity(
Rank {
rank: 43,
max_rank: 43,
},
),
],
[
Words(
Words {
matching_words: 7,
max_matching_words: 9,
},
),
Proximity(
Rank {
rank: 36,
max_rank: 36,
},
),
],
[
Words(
Words {
matching_words: 7,
max_matching_words: 9,
},
),
Proximity(
Rank {
rank: 31,
max_rank: 36,
},
),
],
[
Words(
Words {
matching_words: 5,
max_matching_words: 9,
},
),
Proximity(
Rank {
rank: 22,
max_rank: 22,
},
),
],
[
Words(
Words {
matching_words: 4,
max_matching_words: 9,
},
),
Proximity(
Rank {
rank: 15,
max_rank: 15,
},
),
],
[
Words(
Words {
matching_words: 4,
max_matching_words: 9,
},
),
Proximity(
Rank {
rank: 15,
max_rank: 15,
},
),
],
[
Words(
Words {
matching_words: 4,
max_matching_words: 9,
},
),
Proximity(
Rank {
rank: 15,
max_rank: 15,
},
),
],
[
Words(
Words {
matching_words: 3,
max_matching_words: 9,
},
),
Proximity(
Rank {
rank: 8,
max_rank: 8,
},
),
],
]

View File

@ -0,0 +1,160 @@
---
source: milli/src/search/new/tests/words_tms.rs
expression: "format!(\"{document_scores:#?}\")"
---
[
[
Words(
Words {
matching_words: 9,
max_matching_words: 9,
},
),
Proximity(
Rank {
rank: 43,
max_rank: 43,
},
),
],
[
Words(
Words {
matching_words: 9,
max_matching_words: 9,
},
),
Proximity(
Rank {
rank: 43,
max_rank: 43,
},
),
],
[
Words(
Words {
matching_words: 9,
max_matching_words: 9,
},
),
Proximity(
Rank {
rank: 42,
max_rank: 43,
},
),
],
[
Words(
Words {
matching_words: 9,
max_matching_words: 9,
},
),
Proximity(
Rank {
rank: 42,
max_rank: 43,
},
),
],
[
Words(
Words {
matching_words: 9,
max_matching_words: 9,
},
),
Proximity(
Rank {
rank: 41,
max_rank: 43,
},
),
],
[
Words(
Words {
matching_words: 9,
max_matching_words: 9,
},
),
Proximity(
Rank {
rank: 34,
max_rank: 43,
},
),
],
[
Words(
Words {
matching_words: 9,
max_matching_words: 9,
},
),
Proximity(
Rank {
rank: 33,
max_rank: 43,
},
),
],
[
Words(
Words {
matching_words: 8,
max_matching_words: 9,
},
),
Proximity(
Rank {
rank: 36,
max_rank: 36,
},
),
],
[
Words(
Words {
matching_words: 7,
max_matching_words: 9,
},
),
Proximity(
Rank {
rank: 29,
max_rank: 29,
},
),
],
[
Words(
Words {
matching_words: 7,
max_matching_words: 9,
},
),
Proximity(
Rank {
rank: 24,
max_rank: 29,
},
),
],
[
Words(
Words {
matching_words: 5,
max_matching_words: 9,
},
),
Proximity(
Rank {
rank: 15,
max_rank: 15,
},
),
],
]

View File

@ -0,0 +1,286 @@
---
source: milli/src/search/new/tests/words_tms.rs
expression: "format!(\"{document_scores:#?}\")"
---
[
[
Words(
Words {
matching_words: 9,
max_matching_words: 9,
},
),
Proximity(
Rank {
rank: 57,
max_rank: 57,
},
),
],
[
Words(
Words {
matching_words: 9,
max_matching_words: 9,
},
),
Proximity(
Rank {
rank: 57,
max_rank: 57,
},
),
],
[
Words(
Words {
matching_words: 9,
max_matching_words: 9,
},
),
Proximity(
Rank {
rank: 56,
max_rank: 57,
},
),
],
[
Words(
Words {
matching_words: 9,
max_matching_words: 9,
},
),
Proximity(
Rank {
rank: 56,
max_rank: 57,
},
),
],
[
Words(
Words {
matching_words: 9,
max_matching_words: 9,
},
),
Proximity(
Rank {
rank: 55,
max_rank: 57,
},
),
],
[
Words(
Words {
matching_words: 9,
max_matching_words: 9,
},
),
Proximity(
Rank {
rank: 54,
max_rank: 57,
},
),
],
[
Words(
Words {
matching_words: 9,
max_matching_words: 9,
},
),
Proximity(
Rank {
rank: 53,
max_rank: 57,
},
),
],
[
Words(
Words {
matching_words: 9,
max_matching_words: 9,
},
),
Proximity(
Rank {
rank: 52,
max_rank: 57,
},
),
],
[
Words(
Words {
matching_words: 9,
max_matching_words: 9,
},
),
Proximity(
Rank {
rank: 51,
max_rank: 57,
},
),
],
[
Words(
Words {
matching_words: 9,
max_matching_words: 9,
},
),
Proximity(
Rank {
rank: 48,
max_rank: 57,
},
),
],
[
Words(
Words {
matching_words: 9,
max_matching_words: 9,
},
),
Proximity(
Rank {
rank: 47,
max_rank: 57,
},
),
],
[
Words(
Words {
matching_words: 9,
max_matching_words: 9,
},
),
Proximity(
Rank {
rank: 1,
max_rank: 57,
},
),
],
[
Words(
Words {
matching_words: 8,
max_matching_words: 9,
},
),
Proximity(
Rank {
rank: 50,
max_rank: 50,
},
),
],
[
Words(
Words {
matching_words: 7,
max_matching_words: 9,
},
),
Proximity(
Rank {
rank: 43,
max_rank: 43,
},
),
],
[
Words(
Words {
matching_words: 7,
max_matching_words: 9,
},
),
Proximity(
Rank {
rank: 38,
max_rank: 43,
},
),
],
[
Words(
Words {
matching_words: 5,
max_matching_words: 9,
},
),
Proximity(
Rank {
rank: 29,
max_rank: 29,
},
),
],
[
Words(
Words {
matching_words: 4,
max_matching_words: 9,
},
),
Proximity(
Rank {
rank: 22,
max_rank: 22,
},
),
],
[
Words(
Words {
matching_words: 4,
max_matching_words: 9,
},
),
Proximity(
Rank {
rank: 22,
max_rank: 22,
},
),
],
[
Words(
Words {
matching_words: 4,
max_matching_words: 9,
},
),
Proximity(
Rank {
rank: 22,
max_rank: 22,
},
),
],
[
Words(
Words {
matching_words: 3,
max_matching_words: 9,
},
),
Proximity(
Rank {
rank: 15,
max_rank: 15,
},
),
],
]

View File

@ -0,0 +1,286 @@
---
source: milli/src/search/new/tests/words_tms.rs
expression: "format!(\"{document_scores:#?}\")"
---
[
[
Words(
Words {
matching_words: 9,
max_matching_words: 9,
},
),
Proximity(
Rank {
rank: 57,
max_rank: 57,
},
),
],
[
Words(
Words {
matching_words: 9,
max_matching_words: 9,
},
),
Proximity(
Rank {
rank: 56,
max_rank: 57,
},
),
],
[
Words(
Words {
matching_words: 9,
max_matching_words: 9,
},
),
Proximity(
Rank {
rank: 55,
max_rank: 57,
},
),
],
[
Words(
Words {
matching_words: 9,
max_matching_words: 9,
},
),
Proximity(
Rank {
rank: 54,
max_rank: 57,
},
),
],
[
Words(
Words {
matching_words: 9,
max_matching_words: 9,
},
),
Proximity(
Rank {
rank: 54,
max_rank: 57,
},
),
],
[
Words(
Words {
matching_words: 9,
max_matching_words: 9,
},
),
Proximity(
Rank {
rank: 54,
max_rank: 57,
},
),
],
[
Words(
Words {
matching_words: 9,
max_matching_words: 9,
},
),
Proximity(
Rank {
rank: 53,
max_rank: 57,
},
),
],
[
Words(
Words {
matching_words: 9,
max_matching_words: 9,
},
),
Proximity(
Rank {
rank: 53,
max_rank: 57,
},
),
],
[
Words(
Words {
matching_words: 9,
max_matching_words: 9,
},
),
Proximity(
Rank {
rank: 52,
max_rank: 57,
},
),
],
[
Words(
Words {
matching_words: 9,
max_matching_words: 9,
},
),
Proximity(
Rank {
rank: 47,
max_rank: 57,
},
),
],
[
Words(
Words {
matching_words: 9,
max_matching_words: 9,
},
),
Proximity(
Rank {
rank: 45,
max_rank: 57,
},
),
],
[
Words(
Words {
matching_words: 9,
max_matching_words: 9,
},
),
Proximity(
Rank {
rank: 1,
max_rank: 57,
},
),
],
[
Words(
Words {
matching_words: 8,
max_matching_words: 9,
},
),
Proximity(
Rank {
rank: 47,
max_rank: 50,
},
),
],
[
Words(
Words {
matching_words: 7,
max_matching_words: 9,
},
),
Proximity(
Rank {
rank: 40,
max_rank: 43,
},
),
],
[
Words(
Words {
matching_words: 7,
max_matching_words: 9,
},
),
Proximity(
Rank {
rank: 35,
max_rank: 43,
},
),
],
[
Words(
Words {
matching_words: 5,
max_matching_words: 9,
},
),
Proximity(
Rank {
rank: 26,
max_rank: 29,
},
),
],
[
Words(
Words {
matching_words: 4,
max_matching_words: 9,
},
),
Proximity(
Rank {
rank: 19,
max_rank: 22,
},
),
],
[
Words(
Words {
matching_words: 4,
max_matching_words: 9,
},
),
Proximity(
Rank {
rank: 19,
max_rank: 22,
},
),
],
[
Words(
Words {
matching_words: 4,
max_matching_words: 9,
},
),
Proximity(
Rank {
rank: 19,
max_rank: 22,
},
),
],
[
Words(
Words {
matching_words: 3,
max_matching_words: 9,
},
),
Proximity(
Rank {
rank: 13,
max_rank: 15,
},
),
],
]

Some files were not shown because too many files have changed in this diff Show More