mirror of
https://github.com/meilisearch/MeiliSearch
synced 2024-11-22 21:04:27 +01:00
5969 lines
181 KiB
Rust
5969 lines
181 KiB
Rust
use meili_snap::{json_string, snapshot};
|
|
|
|
use super::{DOCUMENTS, FRUITS_DOCUMENTS, NESTED_DOCUMENTS};
|
|
use crate::common::Server;
|
|
use crate::json;
|
|
use crate::search::{SCORE_DOCUMENTS, VECTOR_DOCUMENTS};
|
|
|
|
#[actix_rt::test]
|
|
async fn search_empty_list() {
|
|
let server = Server::new().await;
|
|
|
|
let (response, code) = server.multi_search(json!({"queries": []})).await;
|
|
snapshot!(code, @"200 OK");
|
|
snapshot!(json_string!(response), @r###"
|
|
{
|
|
"results": []
|
|
}
|
|
"###);
|
|
}
|
|
|
|
#[actix_rt::test]
|
|
async fn federation_empty_list() {
|
|
let server = Server::new().await;
|
|
|
|
let (response, code) = server.multi_search(json!({"federation": {}, "queries": []})).await;
|
|
snapshot!(code, @"200 OK");
|
|
snapshot!(json_string!(response, {".processingTimeMs" => "[time]"}), @r###"
|
|
{
|
|
"hits": [],
|
|
"processingTimeMs": "[time]",
|
|
"limit": 20,
|
|
"offset": 0,
|
|
"estimatedTotalHits": 0
|
|
}
|
|
"###);
|
|
}
|
|
|
|
#[actix_rt::test]
|
|
async fn search_json_object() {
|
|
let server = Server::new().await;
|
|
|
|
let (response, code) = server.multi_search(json!({})).await;
|
|
snapshot!(code, @"400 Bad Request");
|
|
snapshot!(json_string!(response), @r###"
|
|
{
|
|
"message": "Missing field `queries`",
|
|
"code": "bad_request",
|
|
"type": "invalid_request",
|
|
"link": "https://docs.meilisearch.com/errors#bad_request"
|
|
}
|
|
"###);
|
|
}
|
|
|
|
#[actix_rt::test]
|
|
async fn federation_no_queries() {
|
|
let server = Server::new().await;
|
|
|
|
let (response, code) = server.multi_search(json!({"federation": {}})).await;
|
|
snapshot!(code, @"400 Bad Request");
|
|
snapshot!(json_string!(response), @r###"
|
|
{
|
|
"message": "Missing field `queries`",
|
|
"code": "bad_request",
|
|
"type": "invalid_request",
|
|
"link": "https://docs.meilisearch.com/errors#bad_request"
|
|
}
|
|
"###);
|
|
}
|
|
|
|
#[actix_rt::test]
|
|
async fn search_json_array() {
|
|
let server = Server::new().await;
|
|
|
|
let (response, code) = server.multi_search(json!([])).await;
|
|
snapshot!(code, @"400 Bad Request");
|
|
snapshot!(json_string!(response), @r###"
|
|
{
|
|
"message": "Invalid value type: expected an object, but found an array: `[]`",
|
|
"code": "bad_request",
|
|
"type": "invalid_request",
|
|
"link": "https://docs.meilisearch.com/errors#bad_request"
|
|
}
|
|
"###);
|
|
}
|
|
|
|
#[actix_rt::test]
|
|
async fn simple_search_single_index() {
|
|
let server = Server::new().await;
|
|
let index = server.index("test");
|
|
|
|
let documents = DOCUMENTS.clone();
|
|
index.add_documents(documents, None).await;
|
|
index.wait_task(0).await;
|
|
|
|
let (response, code) = server
|
|
.multi_search(json!({"queries": [
|
|
{"indexUid": "test", "q": "glass"},
|
|
{"indexUid": "test", "q": "captain"},
|
|
]}))
|
|
.await;
|
|
snapshot!(code, @"200 OK");
|
|
insta::assert_json_snapshot!(response["results"], { "[].processingTimeMs" => "[time]", ".**._rankingScore" => "[score]" }, @r###"
|
|
[
|
|
{
|
|
"indexUid": "test",
|
|
"hits": [
|
|
{
|
|
"title": "Gläss",
|
|
"id": "450465",
|
|
"color": [
|
|
"blue",
|
|
"red"
|
|
],
|
|
"_vectors": {
|
|
"manual": [
|
|
-100.0,
|
|
340.0,
|
|
90.0
|
|
]
|
|
}
|
|
}
|
|
],
|
|
"query": "glass",
|
|
"processingTimeMs": "[time]",
|
|
"limit": 20,
|
|
"offset": 0,
|
|
"estimatedTotalHits": 1
|
|
},
|
|
{
|
|
"indexUid": "test",
|
|
"hits": [
|
|
{
|
|
"title": "Captain Marvel",
|
|
"id": "299537",
|
|
"color": [
|
|
"yellow",
|
|
"blue"
|
|
],
|
|
"_vectors": {
|
|
"manual": [
|
|
1.0,
|
|
2.0,
|
|
54.0
|
|
]
|
|
}
|
|
}
|
|
],
|
|
"query": "captain",
|
|
"processingTimeMs": "[time]",
|
|
"limit": 20,
|
|
"offset": 0,
|
|
"estimatedTotalHits": 1
|
|
}
|
|
]
|
|
"###);
|
|
}
|
|
|
|
#[actix_rt::test]
|
|
async fn federation_single_search_single_index() {
|
|
let server = Server::new().await;
|
|
let index = server.index("test");
|
|
|
|
let documents = DOCUMENTS.clone();
|
|
index.add_documents(documents, None).await;
|
|
index.wait_task(0).await;
|
|
|
|
let (response, code) = server
|
|
.multi_search(json!({"federation": {}, "queries": [
|
|
{"indexUid" : "test", "q": "glass"},
|
|
]}))
|
|
.await;
|
|
snapshot!(code, @"200 OK");
|
|
insta::assert_json_snapshot!(response, { ".processingTimeMs" => "[time]", ".**._rankingScore" => "[score]" }, @r###"
|
|
{
|
|
"hits": [
|
|
{
|
|
"title": "Gläss",
|
|
"id": "450465",
|
|
"color": [
|
|
"blue",
|
|
"red"
|
|
],
|
|
"_vectors": {
|
|
"manual": [
|
|
-100.0,
|
|
340.0,
|
|
90.0
|
|
]
|
|
},
|
|
"_federation": {
|
|
"indexUid": "test",
|
|
"queriesPosition": 0,
|
|
"weightedRankingScore": 1.0
|
|
}
|
|
}
|
|
],
|
|
"processingTimeMs": "[time]",
|
|
"limit": 20,
|
|
"offset": 0,
|
|
"estimatedTotalHits": 1
|
|
}
|
|
"###);
|
|
}
|
|
|
|
#[actix_rt::test]
|
|
async fn federation_multiple_search_single_index() {
|
|
let server = Server::new().await;
|
|
let index = server.index("test");
|
|
|
|
let documents = SCORE_DOCUMENTS.clone();
|
|
index.add_documents(documents, None).await;
|
|
index.wait_task(0).await;
|
|
|
|
let (response, code) = server
|
|
.multi_search(json!({"federation": {}, "queries": [
|
|
{"indexUid": "test", "q": "the bat"},
|
|
{"indexUid": "test", "q": "badman returns"},
|
|
{"indexUid" : "test", "q": "batman"},
|
|
{"indexUid": "test", "q": "batman returns"},
|
|
]}))
|
|
.await;
|
|
snapshot!(code, @"200 OK");
|
|
insta::assert_json_snapshot!(response, { ".processingTimeMs" => "[time]", ".**._rankingScore" => "[score]" }, @r###"
|
|
{
|
|
"hits": [
|
|
{
|
|
"title": "Batman",
|
|
"id": "D",
|
|
"_federation": {
|
|
"indexUid": "test",
|
|
"queriesPosition": 2,
|
|
"weightedRankingScore": 1.0
|
|
}
|
|
},
|
|
{
|
|
"title": "Batman Returns",
|
|
"id": "C",
|
|
"_federation": {
|
|
"indexUid": "test",
|
|
"queriesPosition": 3,
|
|
"weightedRankingScore": 1.0
|
|
}
|
|
},
|
|
{
|
|
"title": "Batman the dark knight returns: Part 1",
|
|
"id": "A",
|
|
"_federation": {
|
|
"indexUid": "test",
|
|
"queriesPosition": 2,
|
|
"weightedRankingScore": 0.9848484848484848
|
|
}
|
|
},
|
|
{
|
|
"title": "Batman the dark knight returns: Part 2",
|
|
"id": "B",
|
|
"_federation": {
|
|
"indexUid": "test",
|
|
"queriesPosition": 2,
|
|
"weightedRankingScore": 0.9848484848484848
|
|
}
|
|
},
|
|
{
|
|
"title": "Badman",
|
|
"id": "E",
|
|
"_federation": {
|
|
"indexUid": "test",
|
|
"queriesPosition": 1,
|
|
"weightedRankingScore": 0.5
|
|
}
|
|
}
|
|
],
|
|
"processingTimeMs": "[time]",
|
|
"limit": 20,
|
|
"offset": 0,
|
|
"estimatedTotalHits": 5
|
|
}
|
|
"###);
|
|
}
|
|
|
|
#[actix_rt::test]
|
|
async fn federation_two_search_single_index() {
|
|
let server = Server::new().await;
|
|
let index = server.index("test");
|
|
|
|
let documents = DOCUMENTS.clone();
|
|
index.add_documents(documents, None).await;
|
|
index.wait_task(0).await;
|
|
|
|
let (response, code) = server
|
|
.multi_search(json!({"federation": {}, "queries": [
|
|
{"indexUid" : "test", "q": "glass"},
|
|
{"indexUid": "test", "q": "captain"},
|
|
]}))
|
|
.await;
|
|
snapshot!(code, @"200 OK");
|
|
insta::assert_json_snapshot!(response, { ".processingTimeMs" => "[time]", ".**._rankingScore" => "[score]" }, @r###"
|
|
{
|
|
"hits": [
|
|
{
|
|
"title": "Gläss",
|
|
"id": "450465",
|
|
"color": [
|
|
"blue",
|
|
"red"
|
|
],
|
|
"_vectors": {
|
|
"manual": [
|
|
-100.0,
|
|
340.0,
|
|
90.0
|
|
]
|
|
},
|
|
"_federation": {
|
|
"indexUid": "test",
|
|
"queriesPosition": 0,
|
|
"weightedRankingScore": 1.0
|
|
}
|
|
},
|
|
{
|
|
"title": "Captain Marvel",
|
|
"id": "299537",
|
|
"color": [
|
|
"yellow",
|
|
"blue"
|
|
],
|
|
"_vectors": {
|
|
"manual": [
|
|
1.0,
|
|
2.0,
|
|
54.0
|
|
]
|
|
},
|
|
"_federation": {
|
|
"indexUid": "test",
|
|
"queriesPosition": 1,
|
|
"weightedRankingScore": 0.9848484848484848
|
|
}
|
|
}
|
|
],
|
|
"processingTimeMs": "[time]",
|
|
"limit": 20,
|
|
"offset": 0,
|
|
"estimatedTotalHits": 2
|
|
}
|
|
"###);
|
|
}
|
|
|
|
#[actix_rt::test]
|
|
async fn simple_search_missing_index_uid() {
|
|
let server = Server::new().await;
|
|
let index = server.index("test");
|
|
|
|
let documents = DOCUMENTS.clone();
|
|
index.add_documents(documents, None).await;
|
|
index.wait_task(0).await;
|
|
|
|
let (response, code) = server
|
|
.multi_search(json!({"queries": [
|
|
{"q": "glass"},
|
|
]}))
|
|
.await;
|
|
snapshot!(code, @"400 Bad Request");
|
|
insta::assert_json_snapshot!(response, @r###"
|
|
{
|
|
"message": "Missing field `indexUid` inside `.queries[0]`",
|
|
"code": "missing_index_uid",
|
|
"type": "invalid_request",
|
|
"link": "https://docs.meilisearch.com/errors#missing_index_uid"
|
|
}
|
|
"###);
|
|
}
|
|
|
|
#[actix_rt::test]
|
|
async fn federation_simple_search_missing_index_uid() {
|
|
let server = Server::new().await;
|
|
let index = server.index("test");
|
|
|
|
let documents = DOCUMENTS.clone();
|
|
index.add_documents(documents, None).await;
|
|
index.wait_task(0).await;
|
|
|
|
let (response, code) = server
|
|
.multi_search(json!({"federation": {}, "queries": [
|
|
{"q": "glass"},
|
|
]}))
|
|
.await;
|
|
snapshot!(code, @"400 Bad Request");
|
|
insta::assert_json_snapshot!(response, @r###"
|
|
{
|
|
"message": "Missing field `indexUid` inside `.queries[0]`",
|
|
"code": "missing_index_uid",
|
|
"type": "invalid_request",
|
|
"link": "https://docs.meilisearch.com/errors#missing_index_uid"
|
|
}
|
|
"###);
|
|
}
|
|
|
|
#[actix_rt::test]
|
|
async fn simple_search_illegal_index_uid() {
|
|
let server = Server::new().await;
|
|
let index = server.index("test");
|
|
|
|
let documents = DOCUMENTS.clone();
|
|
index.add_documents(documents, None).await;
|
|
index.wait_task(0).await;
|
|
|
|
let (response, code) = server
|
|
.multi_search(json!({"queries": [
|
|
{"indexUid": "hé", "q": "glass"},
|
|
]}))
|
|
.await;
|
|
snapshot!(code, @"400 Bad Request");
|
|
insta::assert_json_snapshot!(response, @r###"
|
|
{
|
|
"message": "Invalid value at `.queries[0].indexUid`: `hé` is not a valid index uid. Index uid can be an integer or a string containing only alphanumeric characters, hyphens (-) and underscores (_), and can not be more than 512 bytes.",
|
|
"code": "invalid_index_uid",
|
|
"type": "invalid_request",
|
|
"link": "https://docs.meilisearch.com/errors#invalid_index_uid"
|
|
}
|
|
"###);
|
|
}
|
|
|
|
#[actix_rt::test]
|
|
async fn federation_search_illegal_index_uid() {
|
|
let server = Server::new().await;
|
|
let index = server.index("test");
|
|
|
|
let documents = DOCUMENTS.clone();
|
|
index.add_documents(documents, None).await;
|
|
index.wait_task(0).await;
|
|
|
|
let (response, code) = server
|
|
.multi_search(json!({"federation": {}, "queries": [
|
|
{"indexUid": "hé", "q": "glass"},
|
|
]}))
|
|
.await;
|
|
snapshot!(code, @"400 Bad Request");
|
|
insta::assert_json_snapshot!(response, @r###"
|
|
{
|
|
"message": "Invalid value at `.queries[0].indexUid`: `hé` is not a valid index uid. Index uid can be an integer or a string containing only alphanumeric characters, hyphens (-) and underscores (_), and can not be more than 512 bytes.",
|
|
"code": "invalid_index_uid",
|
|
"type": "invalid_request",
|
|
"link": "https://docs.meilisearch.com/errors#invalid_index_uid"
|
|
}
|
|
"###);
|
|
}
|
|
|
|
#[actix_rt::test]
|
|
async fn simple_search_two_indexes() {
|
|
let server = Server::new().await;
|
|
let index = server.index("test");
|
|
|
|
let documents = DOCUMENTS.clone();
|
|
index.add_documents(documents, None).await;
|
|
index.wait_task(0).await;
|
|
|
|
let index = server.index("nested");
|
|
let documents = NESTED_DOCUMENTS.clone();
|
|
index.add_documents(documents, None).await;
|
|
index.wait_task(1).await;
|
|
|
|
let (response, code) = server
|
|
.multi_search(json!({"queries": [
|
|
{"indexUid" : "test", "q": "glass"},
|
|
{"indexUid": "nested", "q": "pésti"},
|
|
]}))
|
|
.await;
|
|
snapshot!(code, @"200 OK");
|
|
insta::assert_json_snapshot!(response["results"], { "[].processingTimeMs" => "[time]", ".**._rankingScore" => "[score]" }, @r###"
|
|
[
|
|
{
|
|
"indexUid": "test",
|
|
"hits": [
|
|
{
|
|
"title": "Gläss",
|
|
"id": "450465",
|
|
"color": [
|
|
"blue",
|
|
"red"
|
|
],
|
|
"_vectors": {
|
|
"manual": [
|
|
-100.0,
|
|
340.0,
|
|
90.0
|
|
]
|
|
}
|
|
}
|
|
],
|
|
"query": "glass",
|
|
"processingTimeMs": "[time]",
|
|
"limit": 20,
|
|
"offset": 0,
|
|
"estimatedTotalHits": 1
|
|
},
|
|
{
|
|
"indexUid": "nested",
|
|
"hits": [
|
|
{
|
|
"id": 852,
|
|
"father": "jean",
|
|
"mother": "michelle",
|
|
"doggos": [
|
|
{
|
|
"name": "bobby",
|
|
"age": 2
|
|
},
|
|
{
|
|
"name": "buddy",
|
|
"age": 4
|
|
}
|
|
],
|
|
"cattos": "pésti",
|
|
"_vectors": {
|
|
"manual": [
|
|
1.0,
|
|
2.0,
|
|
3.0
|
|
]
|
|
}
|
|
},
|
|
{
|
|
"id": 654,
|
|
"father": "pierre",
|
|
"mother": "sabine",
|
|
"doggos": [
|
|
{
|
|
"name": "gros bill",
|
|
"age": 8
|
|
}
|
|
],
|
|
"cattos": [
|
|
"simba",
|
|
"pestiféré"
|
|
],
|
|
"_vectors": {
|
|
"manual": [
|
|
1.0,
|
|
2.0,
|
|
54.0
|
|
]
|
|
}
|
|
}
|
|
],
|
|
"query": "pésti",
|
|
"processingTimeMs": "[time]",
|
|
"limit": 20,
|
|
"offset": 0,
|
|
"estimatedTotalHits": 2
|
|
}
|
|
]
|
|
"###);
|
|
}
|
|
|
|
#[actix_rt::test]
|
|
async fn federation_two_search_two_indexes() {
|
|
let server = Server::new().await;
|
|
let index = server.index("test");
|
|
|
|
let documents = DOCUMENTS.clone();
|
|
index.add_documents(documents, None).await;
|
|
index.wait_task(0).await;
|
|
|
|
let index = server.index("nested");
|
|
let documents = NESTED_DOCUMENTS.clone();
|
|
index.add_documents(documents, None).await;
|
|
index.wait_task(1).await;
|
|
|
|
let (response, code) = server
|
|
.multi_search(json!({"federation": {}, "queries": [
|
|
{"indexUid" : "test", "q": "glass"},
|
|
{"indexUid": "nested", "q": "pésti"},
|
|
]}))
|
|
.await;
|
|
snapshot!(code, @"200 OK");
|
|
insta::assert_json_snapshot!(response, { ".processingTimeMs" => "[time]", ".**._rankingScore" => "[score]" }, @r###"
|
|
{
|
|
"hits": [
|
|
{
|
|
"title": "Gläss",
|
|
"id": "450465",
|
|
"color": [
|
|
"blue",
|
|
"red"
|
|
],
|
|
"_vectors": {
|
|
"manual": [
|
|
-100.0,
|
|
340.0,
|
|
90.0
|
|
]
|
|
},
|
|
"_federation": {
|
|
"indexUid": "test",
|
|
"queriesPosition": 0,
|
|
"weightedRankingScore": 1.0
|
|
}
|
|
},
|
|
{
|
|
"id": 852,
|
|
"father": "jean",
|
|
"mother": "michelle",
|
|
"doggos": [
|
|
{
|
|
"name": "bobby",
|
|
"age": 2
|
|
},
|
|
{
|
|
"name": "buddy",
|
|
"age": 4
|
|
}
|
|
],
|
|
"cattos": "pésti",
|
|
"_vectors": {
|
|
"manual": [
|
|
1.0,
|
|
2.0,
|
|
3.0
|
|
]
|
|
},
|
|
"_federation": {
|
|
"indexUid": "nested",
|
|
"queriesPosition": 1,
|
|
"weightedRankingScore": 1.0
|
|
}
|
|
},
|
|
{
|
|
"id": 654,
|
|
"father": "pierre",
|
|
"mother": "sabine",
|
|
"doggos": [
|
|
{
|
|
"name": "gros bill",
|
|
"age": 8
|
|
}
|
|
],
|
|
"cattos": [
|
|
"simba",
|
|
"pestiféré"
|
|
],
|
|
"_vectors": {
|
|
"manual": [
|
|
1.0,
|
|
2.0,
|
|
54.0
|
|
]
|
|
},
|
|
"_federation": {
|
|
"indexUid": "nested",
|
|
"queriesPosition": 1,
|
|
"weightedRankingScore": 0.7803030303030303
|
|
}
|
|
}
|
|
],
|
|
"processingTimeMs": "[time]",
|
|
"limit": 20,
|
|
"offset": 0,
|
|
"estimatedTotalHits": 3
|
|
}
|
|
"###);
|
|
}
|
|
|
|
#[actix_rt::test]
|
|
async fn federation_multiple_search_multiple_indexes() {
|
|
let server = Server::new().await;
|
|
let index = server.index("test");
|
|
|
|
let documents = DOCUMENTS.clone();
|
|
index.add_documents(documents, None).await;
|
|
index.wait_task(0).await;
|
|
|
|
let index = server.index("nested");
|
|
let documents = NESTED_DOCUMENTS.clone();
|
|
index.add_documents(documents, None).await;
|
|
index.wait_task(1).await;
|
|
|
|
let index = server.index("score");
|
|
let documents = SCORE_DOCUMENTS.clone();
|
|
index.add_documents(documents, None).await;
|
|
index.wait_task(2).await;
|
|
|
|
let (response, code) = server
|
|
.multi_search(json!({"federation": {}, "queries": [
|
|
{"indexUid" : "test", "q": "glass"},
|
|
{"indexUid" : "test", "q": "captain"},
|
|
{"indexUid": "nested", "q": "pésti"},
|
|
{"indexUid" : "test", "q": "Escape"},
|
|
{"indexUid": "nested", "q": "jean"},
|
|
{"indexUid": "score", "q": "jean"},
|
|
{"indexUid": "test", "q": "the bat"},
|
|
{"indexUid": "score", "q": "the bat"},
|
|
{"indexUid": "score", "q": "badman returns"},
|
|
{"indexUid" : "score", "q": "batman"},
|
|
{"indexUid": "score", "q": "batman returns"},
|
|
]}))
|
|
.await;
|
|
snapshot!(code, @"200 OK");
|
|
insta::assert_json_snapshot!(response, { ".processingTimeMs" => "[time]", ".**._rankingScore" => "[score]" }, @r###"
|
|
{
|
|
"hits": [
|
|
{
|
|
"title": "Gläss",
|
|
"id": "450465",
|
|
"color": [
|
|
"blue",
|
|
"red"
|
|
],
|
|
"_vectors": {
|
|
"manual": [
|
|
-100.0,
|
|
340.0,
|
|
90.0
|
|
]
|
|
},
|
|
"_federation": {
|
|
"indexUid": "test",
|
|
"queriesPosition": 0,
|
|
"weightedRankingScore": 1.0
|
|
}
|
|
},
|
|
{
|
|
"id": 852,
|
|
"father": "jean",
|
|
"mother": "michelle",
|
|
"doggos": [
|
|
{
|
|
"name": "bobby",
|
|
"age": 2
|
|
},
|
|
{
|
|
"name": "buddy",
|
|
"age": 4
|
|
}
|
|
],
|
|
"cattos": "pésti",
|
|
"_vectors": {
|
|
"manual": [
|
|
1.0,
|
|
2.0,
|
|
3.0
|
|
]
|
|
},
|
|
"_federation": {
|
|
"indexUid": "nested",
|
|
"queriesPosition": 2,
|
|
"weightedRankingScore": 1.0
|
|
}
|
|
},
|
|
{
|
|
"title": "Batman",
|
|
"id": "D",
|
|
"_federation": {
|
|
"indexUid": "score",
|
|
"queriesPosition": 9,
|
|
"weightedRankingScore": 1.0
|
|
}
|
|
},
|
|
{
|
|
"title": "Batman Returns",
|
|
"id": "C",
|
|
"_federation": {
|
|
"indexUid": "score",
|
|
"queriesPosition": 10,
|
|
"weightedRankingScore": 1.0
|
|
}
|
|
},
|
|
{
|
|
"title": "Captain Marvel",
|
|
"id": "299537",
|
|
"color": [
|
|
"yellow",
|
|
"blue"
|
|
],
|
|
"_vectors": {
|
|
"manual": [
|
|
1.0,
|
|
2.0,
|
|
54.0
|
|
]
|
|
},
|
|
"_federation": {
|
|
"indexUid": "test",
|
|
"queriesPosition": 1,
|
|
"weightedRankingScore": 0.9848484848484848
|
|
}
|
|
},
|
|
{
|
|
"title": "Escape Room",
|
|
"id": "522681",
|
|
"color": [
|
|
"yellow",
|
|
"red"
|
|
],
|
|
"_vectors": {
|
|
"manual": [
|
|
10.0,
|
|
-23.0,
|
|
32.0
|
|
]
|
|
},
|
|
"_federation": {
|
|
"indexUid": "test",
|
|
"queriesPosition": 3,
|
|
"weightedRankingScore": 0.9848484848484848
|
|
}
|
|
},
|
|
{
|
|
"id": 951,
|
|
"father": "jean-baptiste",
|
|
"mother": "sophie",
|
|
"doggos": [
|
|
{
|
|
"name": "turbo",
|
|
"age": 5
|
|
},
|
|
{
|
|
"name": "fast",
|
|
"age": 6
|
|
}
|
|
],
|
|
"cattos": [
|
|
"moumoute",
|
|
"gomez"
|
|
],
|
|
"_vectors": {
|
|
"manual": [
|
|
10.0,
|
|
23.0,
|
|
32.0
|
|
]
|
|
},
|
|
"_federation": {
|
|
"indexUid": "nested",
|
|
"queriesPosition": 4,
|
|
"weightedRankingScore": 0.9848484848484848
|
|
}
|
|
},
|
|
{
|
|
"title": "Batman the dark knight returns: Part 1",
|
|
"id": "A",
|
|
"_federation": {
|
|
"indexUid": "score",
|
|
"queriesPosition": 9,
|
|
"weightedRankingScore": 0.9848484848484848
|
|
}
|
|
},
|
|
{
|
|
"title": "Batman the dark knight returns: Part 2",
|
|
"id": "B",
|
|
"_federation": {
|
|
"indexUid": "score",
|
|
"queriesPosition": 9,
|
|
"weightedRankingScore": 0.9848484848484848
|
|
}
|
|
},
|
|
{
|
|
"id": 654,
|
|
"father": "pierre",
|
|
"mother": "sabine",
|
|
"doggos": [
|
|
{
|
|
"name": "gros bill",
|
|
"age": 8
|
|
}
|
|
],
|
|
"cattos": [
|
|
"simba",
|
|
"pestiféré"
|
|
],
|
|
"_vectors": {
|
|
"manual": [
|
|
1.0,
|
|
2.0,
|
|
54.0
|
|
]
|
|
},
|
|
"_federation": {
|
|
"indexUid": "nested",
|
|
"queriesPosition": 2,
|
|
"weightedRankingScore": 0.7803030303030303
|
|
}
|
|
},
|
|
{
|
|
"title": "Badman",
|
|
"id": "E",
|
|
"_federation": {
|
|
"indexUid": "score",
|
|
"queriesPosition": 8,
|
|
"weightedRankingScore": 0.5
|
|
}
|
|
},
|
|
{
|
|
"title": "How to Train Your Dragon: The Hidden World",
|
|
"id": "166428",
|
|
"color": [
|
|
"green",
|
|
"red"
|
|
],
|
|
"_vectors": {
|
|
"manual": [
|
|
-100.0,
|
|
231.0,
|
|
32.0
|
|
]
|
|
},
|
|
"_federation": {
|
|
"indexUid": "test",
|
|
"queriesPosition": 6,
|
|
"weightedRankingScore": 0.4166666666666667
|
|
}
|
|
}
|
|
],
|
|
"processingTimeMs": "[time]",
|
|
"limit": 20,
|
|
"offset": 0,
|
|
"estimatedTotalHits": 12
|
|
}
|
|
"###);
|
|
}
|
|
|
|
#[actix_rt::test]
|
|
async fn search_one_index_doesnt_exist() {
|
|
let server = Server::new().await;
|
|
let index = server.index("test");
|
|
|
|
let documents = DOCUMENTS.clone();
|
|
index.add_documents(documents, None).await;
|
|
index.wait_task(0).await;
|
|
|
|
let (response, code) = server
|
|
.multi_search(json!({"queries": [
|
|
{"indexUid" : "test", "q": "glass"},
|
|
{"indexUid": "nested", "q": "pésti"},
|
|
]}))
|
|
.await;
|
|
snapshot!(code, @"400 Bad Request");
|
|
snapshot!(json_string!(response), @r###"
|
|
{
|
|
"message": "Inside `.queries[1]`: Index `nested` not found.",
|
|
"code": "index_not_found",
|
|
"type": "invalid_request",
|
|
"link": "https://docs.meilisearch.com/errors#index_not_found"
|
|
}
|
|
"###);
|
|
}
|
|
|
|
#[actix_rt::test]
|
|
async fn federation_one_index_doesnt_exist() {
|
|
let server = Server::new().await;
|
|
let index = server.index("test");
|
|
|
|
let documents = DOCUMENTS.clone();
|
|
index.add_documents(documents, None).await;
|
|
index.wait_task(0).await;
|
|
|
|
let (response, code) = server
|
|
.multi_search(json!({"federation": {}, "queries": [
|
|
{"indexUid" : "test", "q": "glass"},
|
|
{"indexUid": "nested", "q": "pésti"},
|
|
]}))
|
|
.await;
|
|
snapshot!(code, @"400 Bad Request");
|
|
snapshot!(json_string!(response), @r###"
|
|
{
|
|
"message": "Inside `.queries[1]`: Index `nested` not found.",
|
|
"code": "index_not_found",
|
|
"type": "invalid_request",
|
|
"link": "https://docs.meilisearch.com/errors#index_not_found"
|
|
}
|
|
"###);
|
|
}
|
|
|
|
#[actix_rt::test]
|
|
async fn search_multiple_indexes_dont_exist() {
|
|
let server = Server::new().await;
|
|
|
|
let (response, code) = server
|
|
.multi_search(json!({"queries": [
|
|
{"indexUid" : "test", "q": "glass"},
|
|
{"indexUid": "nested", "q": "pésti"},
|
|
]}))
|
|
.await;
|
|
snapshot!(code, @"400 Bad Request");
|
|
snapshot!(json_string!(response), @r###"
|
|
{
|
|
"message": "Inside `.queries[0]`: Index `test` not found.",
|
|
"code": "index_not_found",
|
|
"type": "invalid_request",
|
|
"link": "https://docs.meilisearch.com/errors#index_not_found"
|
|
}
|
|
"###);
|
|
}
|
|
|
|
#[actix_rt::test]
|
|
async fn federation_multiple_indexes_dont_exist() {
|
|
let server = Server::new().await;
|
|
|
|
let (response, code) = server
|
|
.multi_search(json!({"federation": {}, "queries": [
|
|
{"indexUid" : "test", "q": "glass"},
|
|
{"indexUid": "nested", "q": "pésti"},
|
|
]}))
|
|
.await;
|
|
snapshot!(code, @"400 Bad Request");
|
|
// order of indexes that are not found depends on the alphabetical order of index names
|
|
// the query index is the lowest index with that index
|
|
snapshot!(json_string!(response), @r###"
|
|
{
|
|
"message": "Inside `.queries[1]`: Index `nested` not found.",
|
|
"code": "index_not_found",
|
|
"type": "invalid_request",
|
|
"link": "https://docs.meilisearch.com/errors#index_not_found"
|
|
}
|
|
"###);
|
|
}
|
|
|
|
#[actix_rt::test]
|
|
async fn search_one_query_error() {
|
|
let server = Server::new().await;
|
|
|
|
let index = server.index("test");
|
|
|
|
let documents = DOCUMENTS.clone();
|
|
index.add_documents(documents, None).await;
|
|
index.wait_task(0).await;
|
|
|
|
let index = server.index("nested");
|
|
let documents = NESTED_DOCUMENTS.clone();
|
|
index.add_documents(documents, None).await;
|
|
index.wait_task(1).await;
|
|
|
|
let (response, code) = server
|
|
.multi_search(json!({"queries": [
|
|
{"indexUid" : "test", "q": "glass", "facets": ["title"]},
|
|
{"indexUid": "nested", "q": "pésti"},
|
|
]}))
|
|
.await;
|
|
snapshot!(code, @"400 Bad Request");
|
|
snapshot!(json_string!(response), @r###"
|
|
{
|
|
"message": "Inside `.queries[0]`: Invalid facet distribution, this index does not have configured filterable attributes.",
|
|
"code": "invalid_search_facets",
|
|
"type": "invalid_request",
|
|
"link": "https://docs.meilisearch.com/errors#invalid_search_facets"
|
|
}
|
|
"###);
|
|
}
|
|
|
|
#[actix_rt::test]
|
|
async fn federation_one_query_error() {
|
|
let server = Server::new().await;
|
|
|
|
let index = server.index("test");
|
|
|
|
let documents = DOCUMENTS.clone();
|
|
index.add_documents(documents, None).await;
|
|
index.wait_task(0).await;
|
|
|
|
let index = server.index("nested");
|
|
let documents = NESTED_DOCUMENTS.clone();
|
|
index.add_documents(documents, None).await;
|
|
index.wait_task(1).await;
|
|
|
|
let (response, code) = server
|
|
.multi_search(json!({"federation": {}, "queries": [
|
|
{"indexUid" : "test", "q": "glass"},
|
|
{"indexUid": "nested", "q": "pésti", "filter": ["title = toto"]},
|
|
]}))
|
|
.await;
|
|
snapshot!(code, @"400 Bad Request");
|
|
snapshot!(json_string!(response), @r###"
|
|
{
|
|
"message": "Inside `.queries[1]`: Attribute `title` is not filterable. This index does not have configured filterable attributes.\n1:6 title = toto",
|
|
"code": "invalid_search_filter",
|
|
"type": "invalid_request",
|
|
"link": "https://docs.meilisearch.com/errors#invalid_search_filter"
|
|
}
|
|
"###);
|
|
}
|
|
|
|
#[actix_rt::test]
|
|
async fn federation_one_query_sort_error() {
|
|
let server = Server::new().await;
|
|
|
|
let index = server.index("test");
|
|
|
|
let documents = DOCUMENTS.clone();
|
|
index.add_documents(documents, None).await;
|
|
index.wait_task(0).await;
|
|
|
|
let index = server.index("nested");
|
|
let documents = NESTED_DOCUMENTS.clone();
|
|
index.add_documents(documents, None).await;
|
|
index.wait_task(1).await;
|
|
|
|
let (response, code) = server
|
|
.multi_search(json!({"federation": {}, "queries": [
|
|
{"indexUid" : "test", "q": "glass"},
|
|
{"indexUid": "nested", "q": "pésti", "sort": ["doggos:desc"]},
|
|
]}))
|
|
.await;
|
|
snapshot!(code, @"400 Bad Request");
|
|
snapshot!(json_string!(response), @r###"
|
|
{
|
|
"message": "Inside `.queries[1]`: Attribute `doggos` is not sortable. This index does not have configured sortable attributes.",
|
|
"code": "invalid_search_sort",
|
|
"type": "invalid_request",
|
|
"link": "https://docs.meilisearch.com/errors#invalid_search_sort"
|
|
}
|
|
"###);
|
|
}
|
|
|
|
#[actix_rt::test]
|
|
async fn search_multiple_query_errors() {
|
|
let server = Server::new().await;
|
|
|
|
let index = server.index("test");
|
|
|
|
let documents = DOCUMENTS.clone();
|
|
index.add_documents(documents, None).await;
|
|
index.wait_task(0).await;
|
|
|
|
let index = server.index("nested");
|
|
let documents = NESTED_DOCUMENTS.clone();
|
|
index.add_documents(documents, None).await;
|
|
index.wait_task(1).await;
|
|
|
|
let (response, code) = server
|
|
.multi_search(json!({"queries": [
|
|
{"indexUid" : "test", "q": "glass", "facets": ["title"]},
|
|
{"indexUid": "nested", "q": "pésti", "facets": ["doggos"]},
|
|
]}))
|
|
.await;
|
|
snapshot!(code, @"400 Bad Request");
|
|
snapshot!(json_string!(response), @r###"
|
|
{
|
|
"message": "Inside `.queries[0]`: Invalid facet distribution, this index does not have configured filterable attributes.",
|
|
"code": "invalid_search_facets",
|
|
"type": "invalid_request",
|
|
"link": "https://docs.meilisearch.com/errors#invalid_search_facets"
|
|
}
|
|
"###);
|
|
}
|
|
|
|
#[actix_rt::test]
|
|
async fn federation_multiple_query_errors() {
|
|
let server = Server::new().await;
|
|
|
|
let index = server.index("test");
|
|
|
|
let documents = DOCUMENTS.clone();
|
|
index.add_documents(documents, None).await;
|
|
index.wait_task(0).await;
|
|
|
|
let index = server.index("nested");
|
|
let documents = NESTED_DOCUMENTS.clone();
|
|
index.add_documents(documents, None).await;
|
|
index.wait_task(1).await;
|
|
|
|
let (response, code) = server
|
|
.multi_search(json!({"queries": [
|
|
{"indexUid" : "test", "q": "glass", "filter": ["title = toto"]},
|
|
{"indexUid": "nested", "q": "pésti", "filter": ["doggos IN [intel, kefir]"]},
|
|
]}))
|
|
.await;
|
|
snapshot!(code, @"400 Bad Request");
|
|
snapshot!(json_string!(response), @r###"
|
|
{
|
|
"message": "Inside `.queries[0]`: Attribute `title` is not filterable. This index does not have configured filterable attributes.\n1:6 title = toto",
|
|
"code": "invalid_search_filter",
|
|
"type": "invalid_request",
|
|
"link": "https://docs.meilisearch.com/errors#invalid_search_filter"
|
|
}
|
|
"###);
|
|
}
|
|
|
|
#[actix_rt::test]
|
|
async fn federation_multiple_query_sort_errors() {
|
|
let server = Server::new().await;
|
|
|
|
let index = server.index("test");
|
|
|
|
let documents = DOCUMENTS.clone();
|
|
index.add_documents(documents, None).await;
|
|
index.wait_task(0).await;
|
|
|
|
let index = server.index("nested");
|
|
let documents = NESTED_DOCUMENTS.clone();
|
|
index.add_documents(documents, None).await;
|
|
index.wait_task(1).await;
|
|
|
|
let (response, code) = server
|
|
.multi_search(json!({"queries": [
|
|
{"indexUid" : "test", "q": "glass", "sort": ["title:desc"]},
|
|
{"indexUid": "nested", "q": "pésti", "sort": ["doggos:desc"]},
|
|
]}))
|
|
.await;
|
|
snapshot!(code, @"400 Bad Request");
|
|
snapshot!(json_string!(response), @r###"
|
|
{
|
|
"message": "Inside `.queries[0]`: Attribute `title` is not sortable. This index does not have configured sortable attributes.",
|
|
"code": "invalid_search_sort",
|
|
"type": "invalid_request",
|
|
"link": "https://docs.meilisearch.com/errors#invalid_search_sort"
|
|
}
|
|
"###);
|
|
}
|
|
|
|
#[actix_rt::test]
|
|
async fn federation_multiple_query_errors_interleaved() {
|
|
let server = Server::new().await;
|
|
|
|
let index = server.index("test");
|
|
|
|
let documents = DOCUMENTS.clone();
|
|
index.add_documents(documents, None).await;
|
|
index.wait_task(0).await;
|
|
|
|
let index = server.index("nested");
|
|
let documents = NESTED_DOCUMENTS.clone();
|
|
index.add_documents(documents, None).await;
|
|
index.wait_task(1).await;
|
|
|
|
let (response, code) = server
|
|
.multi_search(json!({"queries": [
|
|
{"indexUid" : "test", "q": "glass"},
|
|
{"indexUid": "nested", "q": "pésti", "filter": ["doggos IN [intel, kefir]"]},
|
|
{"indexUid" : "test", "q": "glass", "filter": ["title = toto"]},
|
|
]}))
|
|
.await;
|
|
snapshot!(code, @"400 Bad Request");
|
|
snapshot!(json_string!(response), @r###"
|
|
{
|
|
"message": "Inside `.queries[1]`: Attribute `doggos` is not filterable. This index does not have configured filterable attributes.\n1:7 doggos IN [intel, kefir]",
|
|
"code": "invalid_search_filter",
|
|
"type": "invalid_request",
|
|
"link": "https://docs.meilisearch.com/errors#invalid_search_filter"
|
|
}
|
|
"###);
|
|
}
|
|
|
|
#[actix_rt::test]
|
|
async fn federation_multiple_query_sort_errors_interleaved() {
|
|
let server = Server::new().await;
|
|
|
|
let index = server.index("test");
|
|
|
|
let documents = DOCUMENTS.clone();
|
|
index.add_documents(documents, None).await;
|
|
index.wait_task(0).await;
|
|
|
|
let index = server.index("nested");
|
|
let documents = NESTED_DOCUMENTS.clone();
|
|
index.add_documents(documents, None).await;
|
|
index.wait_task(1).await;
|
|
|
|
let (response, code) = server
|
|
.multi_search(json!({"queries": [
|
|
{"indexUid" : "test", "q": "glass"},
|
|
{"indexUid": "nested", "q": "pésti", "sort": ["doggos:desc"]},
|
|
{"indexUid" : "test", "q": "glass", "sort": ["title:desc"]},
|
|
]}))
|
|
.await;
|
|
snapshot!(code, @"400 Bad Request");
|
|
snapshot!(json_string!(response), @r###"
|
|
{
|
|
"message": "Inside `.queries[1]`: Attribute `doggos` is not sortable. This index does not have configured sortable attributes.",
|
|
"code": "invalid_search_sort",
|
|
"type": "invalid_request",
|
|
"link": "https://docs.meilisearch.com/errors#invalid_search_sort"
|
|
}
|
|
"###);
|
|
}
|
|
|
|
#[actix_rt::test]
|
|
async fn federation_filter() {
|
|
let server = Server::new().await;
|
|
|
|
let index = server.index("fruits");
|
|
|
|
let documents = FRUITS_DOCUMENTS.clone();
|
|
let (value, _) = index.add_documents(documents, None).await;
|
|
index.wait_task(value.uid()).await;
|
|
|
|
let (value, _) = index
|
|
.update_settings(
|
|
json!({"searchableAttributes": ["name"], "filterableAttributes": ["BOOST"]}),
|
|
)
|
|
.await;
|
|
index.wait_task(value.uid()).await;
|
|
|
|
let (response, code) = server
|
|
.multi_search(json!({"federation": {}, "queries": [
|
|
{"indexUid" : "fruits", "q": "apple red", "filter": "BOOST = true", "showRankingScore": true, "federationOptions": {"weight": 3.0}},
|
|
{"indexUid": "fruits", "q": "apple red", "showRankingScore": true},
|
|
]}))
|
|
.await;
|
|
snapshot!(code, @"200 OK");
|
|
insta::assert_json_snapshot!(response, { ".processingTimeMs" => "[time]" }, @r###"
|
|
{
|
|
"hits": [
|
|
{
|
|
"name": "Exclusive sale: Red delicious apple",
|
|
"id": "red-delicious-boosted",
|
|
"BOOST": true,
|
|
"_federation": {
|
|
"indexUid": "fruits",
|
|
"queriesPosition": 0,
|
|
"weightedRankingScore": 2.7281746031746033
|
|
},
|
|
"_rankingScore": 0.9093915343915344
|
|
},
|
|
{
|
|
"name": "Exclusive sale: green apple",
|
|
"id": "green-apple-boosted",
|
|
"BOOST": true,
|
|
"_federation": {
|
|
"indexUid": "fruits",
|
|
"queriesPosition": 0,
|
|
"weightedRankingScore": 1.318181818181818
|
|
},
|
|
"_rankingScore": 0.4393939393939394
|
|
},
|
|
{
|
|
"name": "Red apple gala",
|
|
"id": "red-apple-gala",
|
|
"_federation": {
|
|
"indexUid": "fruits",
|
|
"queriesPosition": 1,
|
|
"weightedRankingScore": 0.953042328042328
|
|
},
|
|
"_rankingScore": 0.953042328042328
|
|
}
|
|
],
|
|
"processingTimeMs": "[time]",
|
|
"limit": 20,
|
|
"offset": 0,
|
|
"estimatedTotalHits": 3
|
|
}
|
|
"###);
|
|
}
|
|
|
|
#[actix_rt::test]
|
|
async fn federation_sort_same_indexes_same_criterion_same_direction() {
|
|
let server = Server::new().await;
|
|
|
|
let index = server.index("nested");
|
|
|
|
let documents = NESTED_DOCUMENTS.clone();
|
|
let (value, _) = index.add_documents(documents, None).await;
|
|
index.wait_task(value.uid()).await;
|
|
|
|
let (value, _) = index
|
|
.update_settings(json!({
|
|
"sortableAttributes": ["mother"],
|
|
"rankingRules": [
|
|
"sort",
|
|
"words",
|
|
"typo",
|
|
"proximity",
|
|
"attribute",
|
|
"exactness"
|
|
]
|
|
}))
|
|
.await;
|
|
index.wait_task(value.uid()).await;
|
|
|
|
// two identical placeholder search should have all results from first query
|
|
let (response, code) = server
|
|
.multi_search(json!({"federation": {}, "queries": [
|
|
{"indexUid" : "nested", "q": "", "sort": ["mother:asc"], "showRankingScore": true },
|
|
{"indexUid" : "nested", "q": "", "sort": ["mother:asc"], "showRankingScore": true },
|
|
]}))
|
|
.await;
|
|
snapshot!(code, @"200 OK");
|
|
insta::assert_json_snapshot!(response, { ".processingTimeMs" => "[time]" }, @r###"
|
|
{
|
|
"hits": [
|
|
{
|
|
"id": 852,
|
|
"father": "jean",
|
|
"mother": "michelle",
|
|
"doggos": [
|
|
{
|
|
"name": "bobby",
|
|
"age": 2
|
|
},
|
|
{
|
|
"name": "buddy",
|
|
"age": 4
|
|
}
|
|
],
|
|
"cattos": "pésti",
|
|
"_vectors": {
|
|
"manual": [
|
|
1.0,
|
|
2.0,
|
|
3.0
|
|
]
|
|
},
|
|
"_federation": {
|
|
"indexUid": "nested",
|
|
"queriesPosition": 0,
|
|
"weightedRankingScore": 1.0
|
|
},
|
|
"_rankingScore": 1.0
|
|
},
|
|
{
|
|
"id": 750,
|
|
"father": "romain",
|
|
"mother": "michelle",
|
|
"cattos": [
|
|
"enigma"
|
|
],
|
|
"_vectors": {
|
|
"manual": [
|
|
10.0,
|
|
23.0,
|
|
32.0
|
|
]
|
|
},
|
|
"_federation": {
|
|
"indexUid": "nested",
|
|
"queriesPosition": 0,
|
|
"weightedRankingScore": 1.0
|
|
},
|
|
"_rankingScore": 1.0
|
|
},
|
|
{
|
|
"id": 654,
|
|
"father": "pierre",
|
|
"mother": "sabine",
|
|
"doggos": [
|
|
{
|
|
"name": "gros bill",
|
|
"age": 8
|
|
}
|
|
],
|
|
"cattos": [
|
|
"simba",
|
|
"pestiféré"
|
|
],
|
|
"_vectors": {
|
|
"manual": [
|
|
1.0,
|
|
2.0,
|
|
54.0
|
|
]
|
|
},
|
|
"_federation": {
|
|
"indexUid": "nested",
|
|
"queriesPosition": 0,
|
|
"weightedRankingScore": 1.0
|
|
},
|
|
"_rankingScore": 1.0
|
|
},
|
|
{
|
|
"id": 951,
|
|
"father": "jean-baptiste",
|
|
"mother": "sophie",
|
|
"doggos": [
|
|
{
|
|
"name": "turbo",
|
|
"age": 5
|
|
},
|
|
{
|
|
"name": "fast",
|
|
"age": 6
|
|
}
|
|
],
|
|
"cattos": [
|
|
"moumoute",
|
|
"gomez"
|
|
],
|
|
"_vectors": {
|
|
"manual": [
|
|
10.0,
|
|
23.0,
|
|
32.0
|
|
]
|
|
},
|
|
"_federation": {
|
|
"indexUid": "nested",
|
|
"queriesPosition": 0,
|
|
"weightedRankingScore": 1.0
|
|
},
|
|
"_rankingScore": 1.0
|
|
}
|
|
],
|
|
"processingTimeMs": "[time]",
|
|
"limit": 20,
|
|
"offset": 0,
|
|
"estimatedTotalHits": 4
|
|
}
|
|
"###);
|
|
|
|
// mix and match query
|
|
let (response, code) = server
|
|
.multi_search(json!({"federation": {}, "queries": [
|
|
{"indexUid" : "nested", "q": "pésti", "sort": ["mother:asc"], "showRankingScore": true },
|
|
{"indexUid" : "nested", "q": "jean", "sort": ["mother:asc"], "showRankingScore": true },
|
|
]}))
|
|
.await;
|
|
snapshot!(code, @"200 OK");
|
|
insta::assert_json_snapshot!(response, { ".processingTimeMs" => "[time]" }, @r###"
|
|
{
|
|
"hits": [
|
|
{
|
|
"id": 852,
|
|
"father": "jean",
|
|
"mother": "michelle",
|
|
"doggos": [
|
|
{
|
|
"name": "bobby",
|
|
"age": 2
|
|
},
|
|
{
|
|
"name": "buddy",
|
|
"age": 4
|
|
}
|
|
],
|
|
"cattos": "pésti",
|
|
"_vectors": {
|
|
"manual": [
|
|
1.0,
|
|
2.0,
|
|
3.0
|
|
]
|
|
},
|
|
"_federation": {
|
|
"indexUid": "nested",
|
|
"queriesPosition": 0,
|
|
"weightedRankingScore": 1.0
|
|
},
|
|
"_rankingScore": 1.0
|
|
},
|
|
{
|
|
"id": 654,
|
|
"father": "pierre",
|
|
"mother": "sabine",
|
|
"doggos": [
|
|
{
|
|
"name": "gros bill",
|
|
"age": 8
|
|
}
|
|
],
|
|
"cattos": [
|
|
"simba",
|
|
"pestiféré"
|
|
],
|
|
"_vectors": {
|
|
"manual": [
|
|
1.0,
|
|
2.0,
|
|
54.0
|
|
]
|
|
},
|
|
"_federation": {
|
|
"indexUid": "nested",
|
|
"queriesPosition": 0,
|
|
"weightedRankingScore": 0.7803030303030303
|
|
},
|
|
"_rankingScore": 0.7803030303030303
|
|
},
|
|
{
|
|
"id": 951,
|
|
"father": "jean-baptiste",
|
|
"mother": "sophie",
|
|
"doggos": [
|
|
{
|
|
"name": "turbo",
|
|
"age": 5
|
|
},
|
|
{
|
|
"name": "fast",
|
|
"age": 6
|
|
}
|
|
],
|
|
"cattos": [
|
|
"moumoute",
|
|
"gomez"
|
|
],
|
|
"_vectors": {
|
|
"manual": [
|
|
10.0,
|
|
23.0,
|
|
32.0
|
|
]
|
|
},
|
|
"_federation": {
|
|
"indexUid": "nested",
|
|
"queriesPosition": 1,
|
|
"weightedRankingScore": 0.9848484848484848
|
|
},
|
|
"_rankingScore": 0.9848484848484848
|
|
}
|
|
],
|
|
"processingTimeMs": "[time]",
|
|
"limit": 20,
|
|
"offset": 0,
|
|
"estimatedTotalHits": 3
|
|
}
|
|
"###);
|
|
}
|
|
|
|
#[actix_rt::test]
|
|
async fn federation_sort_same_indexes_same_criterion_opposite_direction() {
|
|
let server = Server::new().await;
|
|
|
|
let index = server.index("nested");
|
|
|
|
let documents = NESTED_DOCUMENTS.clone();
|
|
let (value, _) = index.add_documents(documents, None).await;
|
|
index.wait_task(value.uid()).await;
|
|
|
|
let (value, _) = index
|
|
.update_settings(json!({
|
|
"sortableAttributes": ["mother"],
|
|
"rankingRules": [
|
|
"sort",
|
|
"words",
|
|
"typo",
|
|
"proximity",
|
|
"attribute",
|
|
"exactness"
|
|
]
|
|
}))
|
|
.await;
|
|
index.wait_task(value.uid()).await;
|
|
|
|
// two identical placeholder search should have all results from first query
|
|
let (response, code) = server
|
|
.multi_search(json!({"federation": {}, "queries": [
|
|
{"indexUid" : "nested", "q": "", "sort": ["mother:asc"], "showRankingScore": true },
|
|
{"indexUid" : "nested", "q": "", "sort": ["mother:desc"], "showRankingScore": true },
|
|
]}))
|
|
.await;
|
|
snapshot!(code, @"400 Bad Request");
|
|
insta::assert_json_snapshot!(response, { ".processingTimeMs" => "[time]" }, @r###"
|
|
{
|
|
"message": "Inside `.queries[1]`: The results of queries #0 and #1 are incompatible: \n 1. `queries[0].sort[0]`, `nested.rankingRules[0]`: ascending sort rule(s) on field `mother`\n 2. `queries[1].sort[0]`, `nested.rankingRules[0]`: descending sort rule(s) on field `mother`\n - cannot compare two sort rules in opposite directions\n - note: The ranking rules of query #0 were modified during canonicalization:\n 1. Removed relevancy rule `words` at position #1 in ranking rules because the query is a placeholder search (`q`: \"\")\n 2. Removed relevancy rule `typo` at position #2 in ranking rules because the query is a placeholder search (`q`: \"\")\n 3. Removed relevancy rule `proximity` at position #3 in ranking rules because the query is a placeholder search (`q`: \"\")\n 4. Removed relevancy rule `attribute` at position #4 in ranking rules because the query is a placeholder search (`q`: \"\")\n 5. Removed relevancy rule `exactness` at position #5 in ranking rules because the query is a placeholder search (`q`: \"\")\n - note: The ranking rules of query #1 were modified during canonicalization:\n 1. Removed relevancy rule `words` at position #1 in ranking rules because the query is a placeholder search (`q`: \"\")\n 2. Removed relevancy rule `typo` at position #2 in ranking rules because the query is a placeholder search (`q`: \"\")\n 3. Removed relevancy rule `proximity` at position #3 in ranking rules because the query is a placeholder search (`q`: \"\")\n 4. Removed relevancy rule `attribute` at position #4 in ranking rules because the query is a placeholder search (`q`: \"\")\n 5. Removed relevancy rule `exactness` at position #5 in ranking rules because the query is a placeholder search (`q`: \"\")\n",
|
|
"code": "invalid_multi_search_query_ranking_rules",
|
|
"type": "invalid_request",
|
|
"link": "https://docs.meilisearch.com/errors#invalid_multi_search_query_ranking_rules"
|
|
}
|
|
"###);
|
|
|
|
// mix and match query: should be ranked by ranking score
|
|
let (response, code) = server
|
|
.multi_search(json!({"federation": {}, "queries": [
|
|
{"indexUid" : "nested", "q": "pésti", "sort": ["mother:asc"], "showRankingScore": true },
|
|
{"indexUid" : "nested", "q": "jean", "sort": ["mother:desc"], "showRankingScore": true },
|
|
]}))
|
|
.await;
|
|
snapshot!(code, @"400 Bad Request");
|
|
insta::assert_json_snapshot!(response, { ".processingTimeMs" => "[time]" }, @r###"
|
|
{
|
|
"message": "Inside `.queries[1]`: The results of queries #0 and #1 are incompatible: \n 1. `queries[0].sort[0]`, `nested.rankingRules[0]`: ascending sort rule(s) on field `mother`\n 2. `queries[1].sort[0]`, `nested.rankingRules[0]`: descending sort rule(s) on field `mother`\n - cannot compare two sort rules in opposite directions\n",
|
|
"code": "invalid_multi_search_query_ranking_rules",
|
|
"type": "invalid_request",
|
|
"link": "https://docs.meilisearch.com/errors#invalid_multi_search_query_ranking_rules"
|
|
}
|
|
"###);
|
|
}
|
|
|
|
#[actix_rt::test]
|
|
async fn federation_sort_same_indexes_different_criterion_same_direction() {
|
|
let server = Server::new().await;
|
|
|
|
let index = server.index("nested");
|
|
|
|
let documents = NESTED_DOCUMENTS.clone();
|
|
let (value, _) = index.add_documents(documents, None).await;
|
|
index.wait_task(value.uid()).await;
|
|
|
|
let (value, _) = index
|
|
.update_settings(json!({
|
|
"sortableAttributes": ["mother", "father"],
|
|
"rankingRules": [
|
|
"sort",
|
|
"words",
|
|
"typo",
|
|
"proximity",
|
|
"attribute",
|
|
"exactness"
|
|
]
|
|
}))
|
|
.await;
|
|
index.wait_task(value.uid()).await;
|
|
|
|
// return mothers and fathers ordered accross fields.
|
|
let (response, code) = server
|
|
.multi_search(json!({"federation": {}, "queries": [
|
|
{"indexUid" : "nested", "q": "", "sort": ["mother:asc"], "showRankingScore": true },
|
|
{"indexUid" : "nested", "q": "", "sort": ["father:asc"], "showRankingScore": true },
|
|
]}))
|
|
.await;
|
|
snapshot!(code, @"200 OK");
|
|
insta::assert_json_snapshot!(response, { ".processingTimeMs" => "[time]" }, @r###"
|
|
{
|
|
"hits": [
|
|
{
|
|
"id": 852,
|
|
"father": "jean",
|
|
"mother": "michelle",
|
|
"doggos": [
|
|
{
|
|
"name": "bobby",
|
|
"age": 2
|
|
},
|
|
{
|
|
"name": "buddy",
|
|
"age": 4
|
|
}
|
|
],
|
|
"cattos": "pésti",
|
|
"_vectors": {
|
|
"manual": [
|
|
1.0,
|
|
2.0,
|
|
3.0
|
|
]
|
|
},
|
|
"_federation": {
|
|
"indexUid": "nested",
|
|
"queriesPosition": 1,
|
|
"weightedRankingScore": 1.0
|
|
},
|
|
"_rankingScore": 1.0
|
|
},
|
|
{
|
|
"id": 951,
|
|
"father": "jean-baptiste",
|
|
"mother": "sophie",
|
|
"doggos": [
|
|
{
|
|
"name": "turbo",
|
|
"age": 5
|
|
},
|
|
{
|
|
"name": "fast",
|
|
"age": 6
|
|
}
|
|
],
|
|
"cattos": [
|
|
"moumoute",
|
|
"gomez"
|
|
],
|
|
"_vectors": {
|
|
"manual": [
|
|
10.0,
|
|
23.0,
|
|
32.0
|
|
]
|
|
},
|
|
"_federation": {
|
|
"indexUid": "nested",
|
|
"queriesPosition": 1,
|
|
"weightedRankingScore": 1.0
|
|
},
|
|
"_rankingScore": 1.0
|
|
},
|
|
{
|
|
"id": 750,
|
|
"father": "romain",
|
|
"mother": "michelle",
|
|
"cattos": [
|
|
"enigma"
|
|
],
|
|
"_vectors": {
|
|
"manual": [
|
|
10.0,
|
|
23.0,
|
|
32.0
|
|
]
|
|
},
|
|
"_federation": {
|
|
"indexUid": "nested",
|
|
"queriesPosition": 0,
|
|
"weightedRankingScore": 1.0
|
|
},
|
|
"_rankingScore": 1.0
|
|
},
|
|
{
|
|
"id": 654,
|
|
"father": "pierre",
|
|
"mother": "sabine",
|
|
"doggos": [
|
|
{
|
|
"name": "gros bill",
|
|
"age": 8
|
|
}
|
|
],
|
|
"cattos": [
|
|
"simba",
|
|
"pestiféré"
|
|
],
|
|
"_vectors": {
|
|
"manual": [
|
|
1.0,
|
|
2.0,
|
|
54.0
|
|
]
|
|
},
|
|
"_federation": {
|
|
"indexUid": "nested",
|
|
"queriesPosition": 1,
|
|
"weightedRankingScore": 1.0
|
|
},
|
|
"_rankingScore": 1.0
|
|
}
|
|
],
|
|
"processingTimeMs": "[time]",
|
|
"limit": 20,
|
|
"offset": 0,
|
|
"estimatedTotalHits": 4
|
|
}
|
|
"###);
|
|
|
|
// mix and match query: will be sorted across mother and father names
|
|
let (response, code) = server
|
|
.multi_search(json!({"federation": {}, "queries": [
|
|
{"indexUid" : "nested", "q": "pésti", "sort": ["mother:desc"], "showRankingScore": true },
|
|
{"indexUid" : "nested", "q": "jean-bap", "sort": ["father:desc"], "showRankingScore": true },
|
|
{"indexUid" : "nested", "q": "jea", "sort": ["father:desc"], "showRankingScore": true },
|
|
]}))
|
|
.await;
|
|
snapshot!(code, @"200 OK");
|
|
insta::assert_json_snapshot!(response, { ".processingTimeMs" => "[time]" }, @r###"
|
|
{
|
|
"hits": [
|
|
{
|
|
"id": 654,
|
|
"father": "pierre",
|
|
"mother": "sabine",
|
|
"doggos": [
|
|
{
|
|
"name": "gros bill",
|
|
"age": 8
|
|
}
|
|
],
|
|
"cattos": [
|
|
"simba",
|
|
"pestiféré"
|
|
],
|
|
"_vectors": {
|
|
"manual": [
|
|
1.0,
|
|
2.0,
|
|
54.0
|
|
]
|
|
},
|
|
"_federation": {
|
|
"indexUid": "nested",
|
|
"queriesPosition": 0,
|
|
"weightedRankingScore": 0.7803030303030303
|
|
},
|
|
"_rankingScore": 0.7803030303030303
|
|
},
|
|
{
|
|
"id": 852,
|
|
"father": "jean",
|
|
"mother": "michelle",
|
|
"doggos": [
|
|
{
|
|
"name": "bobby",
|
|
"age": 2
|
|
},
|
|
{
|
|
"name": "buddy",
|
|
"age": 4
|
|
}
|
|
],
|
|
"cattos": "pésti",
|
|
"_vectors": {
|
|
"manual": [
|
|
1.0,
|
|
2.0,
|
|
3.0
|
|
]
|
|
},
|
|
"_federation": {
|
|
"indexUid": "nested",
|
|
"queriesPosition": 0,
|
|
"weightedRankingScore": 1.0
|
|
},
|
|
"_rankingScore": 1.0
|
|
},
|
|
{
|
|
"id": 951,
|
|
"father": "jean-baptiste",
|
|
"mother": "sophie",
|
|
"doggos": [
|
|
{
|
|
"name": "turbo",
|
|
"age": 5
|
|
},
|
|
{
|
|
"name": "fast",
|
|
"age": 6
|
|
}
|
|
],
|
|
"cattos": [
|
|
"moumoute",
|
|
"gomez"
|
|
],
|
|
"_vectors": {
|
|
"manual": [
|
|
10.0,
|
|
23.0,
|
|
32.0
|
|
]
|
|
},
|
|
"_federation": {
|
|
"indexUid": "nested",
|
|
"queriesPosition": 1,
|
|
"weightedRankingScore": 0.9991181657848324
|
|
},
|
|
"_rankingScore": 0.9991181657848324
|
|
}
|
|
],
|
|
"processingTimeMs": "[time]",
|
|
"limit": 20,
|
|
"offset": 0,
|
|
"estimatedTotalHits": 3
|
|
}
|
|
"###);
|
|
}
|
|
|
|
#[actix_rt::test]
|
|
async fn federation_sort_same_indexes_different_criterion_opposite_direction() {
|
|
let server = Server::new().await;
|
|
|
|
let index = server.index("nested");
|
|
|
|
let documents = NESTED_DOCUMENTS.clone();
|
|
let (value, _) = index.add_documents(documents, None).await;
|
|
index.wait_task(value.uid()).await;
|
|
|
|
let (value, _) = index
|
|
.update_settings(json!({
|
|
"sortableAttributes": ["mother", "father"],
|
|
"rankingRules": [
|
|
"sort",
|
|
"words",
|
|
"typo",
|
|
"proximity",
|
|
"attribute",
|
|
"exactness"
|
|
]
|
|
}))
|
|
.await;
|
|
index.wait_task(value.uid()).await;
|
|
|
|
// two identical placeholder search should have all results from first query
|
|
let (response, code) = server
|
|
.multi_search(json!({"federation": {}, "queries": [
|
|
{"indexUid" : "nested", "q": "", "sort": ["mother:asc"], "showRankingScore": true },
|
|
{"indexUid" : "nested", "q": "", "sort": ["father:desc"], "showRankingScore": true },
|
|
]}))
|
|
.await;
|
|
snapshot!(code, @"400 Bad Request");
|
|
insta::assert_json_snapshot!(response, { ".processingTimeMs" => "[time]" }, @r###"
|
|
{
|
|
"message": "Inside `.queries[1]`: The results of queries #0 and #1 are incompatible: \n 1. `queries[0].sort[0]`, `nested.rankingRules[0]`: ascending sort rule(s) on field `mother`\n 2. `queries[1].sort[0]`, `nested.rankingRules[0]`: descending sort rule(s) on field `father`\n - cannot compare two sort rules in opposite directions\n - note: The ranking rules of query #0 were modified during canonicalization:\n 1. Removed relevancy rule `words` at position #1 in ranking rules because the query is a placeholder search (`q`: \"\")\n 2. Removed relevancy rule `typo` at position #2 in ranking rules because the query is a placeholder search (`q`: \"\")\n 3. Removed relevancy rule `proximity` at position #3 in ranking rules because the query is a placeholder search (`q`: \"\")\n 4. Removed relevancy rule `attribute` at position #4 in ranking rules because the query is a placeholder search (`q`: \"\")\n 5. Removed relevancy rule `exactness` at position #5 in ranking rules because the query is a placeholder search (`q`: \"\")\n - note: The ranking rules of query #1 were modified during canonicalization:\n 1. Removed relevancy rule `words` at position #1 in ranking rules because the query is a placeholder search (`q`: \"\")\n 2. Removed relevancy rule `typo` at position #2 in ranking rules because the query is a placeholder search (`q`: \"\")\n 3. Removed relevancy rule `proximity` at position #3 in ranking rules because the query is a placeholder search (`q`: \"\")\n 4. Removed relevancy rule `attribute` at position #4 in ranking rules because the query is a placeholder search (`q`: \"\")\n 5. Removed relevancy rule `exactness` at position #5 in ranking rules because the query is a placeholder search (`q`: \"\")\n",
|
|
"code": "invalid_multi_search_query_ranking_rules",
|
|
"type": "invalid_request",
|
|
"link": "https://docs.meilisearch.com/errors#invalid_multi_search_query_ranking_rules"
|
|
}
|
|
"###);
|
|
|
|
// mix and match query: should be ranked by ranking score
|
|
let (response, code) = server
|
|
.multi_search(json!({"federation": {}, "queries": [
|
|
{"indexUid" : "nested", "q": "pésti", "sort": ["mother:asc"], "showRankingScore": true },
|
|
{"indexUid" : "nested", "q": "jean", "sort": ["father:desc"], "showRankingScore": true },
|
|
]}))
|
|
.await;
|
|
snapshot!(code, @"400 Bad Request");
|
|
insta::assert_json_snapshot!(response, { ".processingTimeMs" => "[time]" }, @r###"
|
|
{
|
|
"message": "Inside `.queries[1]`: The results of queries #0 and #1 are incompatible: \n 1. `queries[0].sort[0]`, `nested.rankingRules[0]`: ascending sort rule(s) on field `mother`\n 2. `queries[1].sort[0]`, `nested.rankingRules[0]`: descending sort rule(s) on field `father`\n - cannot compare two sort rules in opposite directions\n",
|
|
"code": "invalid_multi_search_query_ranking_rules",
|
|
"type": "invalid_request",
|
|
"link": "https://docs.meilisearch.com/errors#invalid_multi_search_query_ranking_rules"
|
|
}
|
|
"###);
|
|
}
|
|
|
|
#[actix_rt::test]
|
|
async fn federation_sort_different_indexes_same_criterion_same_direction() {
|
|
let server = Server::new().await;
|
|
|
|
let index = server.index("movies");
|
|
|
|
let documents = DOCUMENTS.clone();
|
|
let (value, _) = index.add_documents(documents, None).await;
|
|
index.wait_task(value.uid()).await;
|
|
|
|
let (value, _) = index
|
|
.update_settings(json!({
|
|
"sortableAttributes": ["title"],
|
|
"rankingRules": [
|
|
"sort",
|
|
"words",
|
|
"typo",
|
|
"proximity",
|
|
"attribute",
|
|
"exactness"
|
|
]
|
|
}))
|
|
.await;
|
|
index.wait_task(value.uid()).await;
|
|
|
|
let index = server.index("batman");
|
|
|
|
let documents = SCORE_DOCUMENTS.clone();
|
|
let (value, _) = index.add_documents(documents, None).await;
|
|
index.wait_task(value.uid()).await;
|
|
|
|
let (value, _) = index
|
|
.update_settings(json!({
|
|
"sortableAttributes": ["title"],
|
|
"rankingRules": [
|
|
"sort",
|
|
"words",
|
|
"typo",
|
|
"proximity",
|
|
"attribute",
|
|
"exactness"
|
|
]
|
|
}))
|
|
.await;
|
|
index.wait_task(value.uid()).await;
|
|
|
|
// return titles ordered accross indexes
|
|
let (response, code) = server
|
|
.multi_search(json!({"federation": {}, "queries": [
|
|
{"indexUid" : "movies", "q": "", "sort": ["title:asc"], "showRankingScore": true },
|
|
{"indexUid" : "batman", "q": "", "sort": ["title:asc"], "showRankingScore": true },
|
|
]}))
|
|
.await;
|
|
snapshot!(code, @"200 OK");
|
|
insta::assert_json_snapshot!(response, { ".processingTimeMs" => "[time]" }, @r###"
|
|
{
|
|
"hits": [
|
|
{
|
|
"title": "Badman",
|
|
"id": "E",
|
|
"_federation": {
|
|
"indexUid": "batman",
|
|
"queriesPosition": 1,
|
|
"weightedRankingScore": 1.0
|
|
},
|
|
"_rankingScore": 1.0
|
|
},
|
|
{
|
|
"title": "Batman",
|
|
"id": "D",
|
|
"_federation": {
|
|
"indexUid": "batman",
|
|
"queriesPosition": 1,
|
|
"weightedRankingScore": 1.0
|
|
},
|
|
"_rankingScore": 1.0
|
|
},
|
|
{
|
|
"title": "Batman Returns",
|
|
"id": "C",
|
|
"_federation": {
|
|
"indexUid": "batman",
|
|
"queriesPosition": 1,
|
|
"weightedRankingScore": 1.0
|
|
},
|
|
"_rankingScore": 1.0
|
|
},
|
|
{
|
|
"title": "Batman the dark knight returns: Part 1",
|
|
"id": "A",
|
|
"_federation": {
|
|
"indexUid": "batman",
|
|
"queriesPosition": 1,
|
|
"weightedRankingScore": 1.0
|
|
},
|
|
"_rankingScore": 1.0
|
|
},
|
|
{
|
|
"title": "Batman the dark knight returns: Part 2",
|
|
"id": "B",
|
|
"_federation": {
|
|
"indexUid": "batman",
|
|
"queriesPosition": 1,
|
|
"weightedRankingScore": 1.0
|
|
},
|
|
"_rankingScore": 1.0
|
|
},
|
|
{
|
|
"title": "Captain Marvel",
|
|
"id": "299537",
|
|
"color": [
|
|
"yellow",
|
|
"blue"
|
|
],
|
|
"_vectors": {
|
|
"manual": [
|
|
1.0,
|
|
2.0,
|
|
54.0
|
|
]
|
|
},
|
|
"_federation": {
|
|
"indexUid": "movies",
|
|
"queriesPosition": 0,
|
|
"weightedRankingScore": 1.0
|
|
},
|
|
"_rankingScore": 1.0
|
|
},
|
|
{
|
|
"title": "Escape Room",
|
|
"id": "522681",
|
|
"color": [
|
|
"yellow",
|
|
"red"
|
|
],
|
|
"_vectors": {
|
|
"manual": [
|
|
10.0,
|
|
-23.0,
|
|
32.0
|
|
]
|
|
},
|
|
"_federation": {
|
|
"indexUid": "movies",
|
|
"queriesPosition": 0,
|
|
"weightedRankingScore": 1.0
|
|
},
|
|
"_rankingScore": 1.0
|
|
},
|
|
{
|
|
"title": "Gläss",
|
|
"id": "450465",
|
|
"color": [
|
|
"blue",
|
|
"red"
|
|
],
|
|
"_vectors": {
|
|
"manual": [
|
|
-100.0,
|
|
340.0,
|
|
90.0
|
|
]
|
|
},
|
|
"_federation": {
|
|
"indexUid": "movies",
|
|
"queriesPosition": 0,
|
|
"weightedRankingScore": 1.0
|
|
},
|
|
"_rankingScore": 1.0
|
|
},
|
|
{
|
|
"title": "How to Train Your Dragon: The Hidden World",
|
|
"id": "166428",
|
|
"color": [
|
|
"green",
|
|
"red"
|
|
],
|
|
"_vectors": {
|
|
"manual": [
|
|
-100.0,
|
|
231.0,
|
|
32.0
|
|
]
|
|
},
|
|
"_federation": {
|
|
"indexUid": "movies",
|
|
"queriesPosition": 0,
|
|
"weightedRankingScore": 1.0
|
|
},
|
|
"_rankingScore": 1.0
|
|
},
|
|
{
|
|
"title": "Shazam!",
|
|
"id": "287947",
|
|
"color": [
|
|
"green",
|
|
"blue"
|
|
],
|
|
"_vectors": {
|
|
"manual": [
|
|
1.0,
|
|
2.0,
|
|
3.0
|
|
]
|
|
},
|
|
"_federation": {
|
|
"indexUid": "movies",
|
|
"queriesPosition": 0,
|
|
"weightedRankingScore": 1.0
|
|
},
|
|
"_rankingScore": 1.0
|
|
}
|
|
],
|
|
"processingTimeMs": "[time]",
|
|
"limit": 20,
|
|
"offset": 0,
|
|
"estimatedTotalHits": 10
|
|
}
|
|
"###);
|
|
|
|
// mix and match query: will be sorted across indexes
|
|
let (response, code) = server
|
|
.multi_search(json!({"federation": {}, "queries": [
|
|
{"indexUid" : "batman", "q": "badman returns", "sort": ["title:desc"], "showRankingScore": true },
|
|
{"indexUid" : "movies", "q": "captain", "sort": ["title:desc"], "showRankingScore": true },
|
|
{"indexUid" : "batman", "q": "the bat", "sort": ["title:desc"], "showRankingScore": true },
|
|
]}))
|
|
.await;
|
|
snapshot!(code, @"200 OK");
|
|
insta::assert_json_snapshot!(response, { ".processingTimeMs" => "[time]" }, @r###"
|
|
{
|
|
"hits": [
|
|
{
|
|
"title": "Captain Marvel",
|
|
"id": "299537",
|
|
"color": [
|
|
"yellow",
|
|
"blue"
|
|
],
|
|
"_vectors": {
|
|
"manual": [
|
|
1.0,
|
|
2.0,
|
|
54.0
|
|
]
|
|
},
|
|
"_federation": {
|
|
"indexUid": "movies",
|
|
"queriesPosition": 1,
|
|
"weightedRankingScore": 0.9848484848484848
|
|
},
|
|
"_rankingScore": 0.9848484848484848
|
|
},
|
|
{
|
|
"title": "Batman the dark knight returns: Part 2",
|
|
"id": "B",
|
|
"_federation": {
|
|
"indexUid": "batman",
|
|
"queriesPosition": 2,
|
|
"weightedRankingScore": 0.9528218694885362
|
|
},
|
|
"_rankingScore": 0.9528218694885362
|
|
},
|
|
{
|
|
"title": "Batman the dark knight returns: Part 1",
|
|
"id": "A",
|
|
"_federation": {
|
|
"indexUid": "batman",
|
|
"queriesPosition": 2,
|
|
"weightedRankingScore": 0.9528218694885362
|
|
},
|
|
"_rankingScore": 0.9528218694885362
|
|
},
|
|
{
|
|
"title": "Batman Returns",
|
|
"id": "C",
|
|
"_federation": {
|
|
"indexUid": "batman",
|
|
"queriesPosition": 0,
|
|
"weightedRankingScore": 0.8317901234567902
|
|
},
|
|
"_rankingScore": 0.8317901234567902
|
|
},
|
|
{
|
|
"title": "Batman",
|
|
"id": "D",
|
|
"_federation": {
|
|
"indexUid": "batman",
|
|
"queriesPosition": 0,
|
|
"weightedRankingScore": 0.23106060606060605
|
|
},
|
|
"_rankingScore": 0.23106060606060605
|
|
},
|
|
{
|
|
"title": "Badman",
|
|
"id": "E",
|
|
"_federation": {
|
|
"indexUid": "batman",
|
|
"queriesPosition": 0,
|
|
"weightedRankingScore": 0.5
|
|
},
|
|
"_rankingScore": 0.5
|
|
}
|
|
],
|
|
"processingTimeMs": "[time]",
|
|
"limit": 20,
|
|
"offset": 0,
|
|
"estimatedTotalHits": 6
|
|
}
|
|
"###);
|
|
}
|
|
|
|
#[actix_rt::test]
|
|
async fn federation_sort_different_ranking_rules() {
|
|
let server = Server::new().await;
|
|
|
|
let index = server.index("movies");
|
|
|
|
let documents = DOCUMENTS.clone();
|
|
let (value, _) = index.add_documents(documents, None).await;
|
|
index.wait_task(value.uid()).await;
|
|
|
|
let (value, _) = index
|
|
.update_settings(json!({
|
|
"sortableAttributes": ["title"],
|
|
"rankingRules": [
|
|
"sort",
|
|
"words",
|
|
"typo",
|
|
"proximity",
|
|
"attribute",
|
|
"exactness"
|
|
]
|
|
}))
|
|
.await;
|
|
index.wait_task(value.uid()).await;
|
|
|
|
let index = server.index("batman");
|
|
|
|
let documents = SCORE_DOCUMENTS.clone();
|
|
let (value, _) = index.add_documents(documents, None).await;
|
|
index.wait_task(value.uid()).await;
|
|
|
|
let (value, _) = index
|
|
.update_settings(json!({
|
|
"sortableAttributes": ["title"],
|
|
"rankingRules": [
|
|
"words",
|
|
"typo",
|
|
"proximity",
|
|
"attribute",
|
|
"sort",
|
|
"exactness"
|
|
]
|
|
}))
|
|
.await;
|
|
index.wait_task(value.uid()).await;
|
|
|
|
// return titles ordered accross indexes
|
|
let (response, code) = server
|
|
.multi_search(json!({"federation": {}, "queries": [
|
|
{"indexUid" : "movies", "q": "", "sort": ["title:asc"], "showRankingScore": true },
|
|
{"indexUid" : "batman", "q": "", "sort": ["title:asc"], "showRankingScore": true },
|
|
]}))
|
|
.await;
|
|
snapshot!(code, @"200 OK");
|
|
insta::assert_json_snapshot!(response, { ".processingTimeMs" => "[time]" }, @r###"
|
|
{
|
|
"hits": [
|
|
{
|
|
"title": "Badman",
|
|
"id": "E",
|
|
"_federation": {
|
|
"indexUid": "batman",
|
|
"queriesPosition": 1,
|
|
"weightedRankingScore": 1.0
|
|
},
|
|
"_rankingScore": 1.0
|
|
},
|
|
{
|
|
"title": "Batman",
|
|
"id": "D",
|
|
"_federation": {
|
|
"indexUid": "batman",
|
|
"queriesPosition": 1,
|
|
"weightedRankingScore": 1.0
|
|
},
|
|
"_rankingScore": 1.0
|
|
},
|
|
{
|
|
"title": "Batman Returns",
|
|
"id": "C",
|
|
"_federation": {
|
|
"indexUid": "batman",
|
|
"queriesPosition": 1,
|
|
"weightedRankingScore": 1.0
|
|
},
|
|
"_rankingScore": 1.0
|
|
},
|
|
{
|
|
"title": "Batman the dark knight returns: Part 1",
|
|
"id": "A",
|
|
"_federation": {
|
|
"indexUid": "batman",
|
|
"queriesPosition": 1,
|
|
"weightedRankingScore": 1.0
|
|
},
|
|
"_rankingScore": 1.0
|
|
},
|
|
{
|
|
"title": "Batman the dark knight returns: Part 2",
|
|
"id": "B",
|
|
"_federation": {
|
|
"indexUid": "batman",
|
|
"queriesPosition": 1,
|
|
"weightedRankingScore": 1.0
|
|
},
|
|
"_rankingScore": 1.0
|
|
},
|
|
{
|
|
"title": "Captain Marvel",
|
|
"id": "299537",
|
|
"color": [
|
|
"yellow",
|
|
"blue"
|
|
],
|
|
"_vectors": {
|
|
"manual": [
|
|
1.0,
|
|
2.0,
|
|
54.0
|
|
]
|
|
},
|
|
"_federation": {
|
|
"indexUid": "movies",
|
|
"queriesPosition": 0,
|
|
"weightedRankingScore": 1.0
|
|
},
|
|
"_rankingScore": 1.0
|
|
},
|
|
{
|
|
"title": "Escape Room",
|
|
"id": "522681",
|
|
"color": [
|
|
"yellow",
|
|
"red"
|
|
],
|
|
"_vectors": {
|
|
"manual": [
|
|
10.0,
|
|
-23.0,
|
|
32.0
|
|
]
|
|
},
|
|
"_federation": {
|
|
"indexUid": "movies",
|
|
"queriesPosition": 0,
|
|
"weightedRankingScore": 1.0
|
|
},
|
|
"_rankingScore": 1.0
|
|
},
|
|
{
|
|
"title": "Gläss",
|
|
"id": "450465",
|
|
"color": [
|
|
"blue",
|
|
"red"
|
|
],
|
|
"_vectors": {
|
|
"manual": [
|
|
-100.0,
|
|
340.0,
|
|
90.0
|
|
]
|
|
},
|
|
"_federation": {
|
|
"indexUid": "movies",
|
|
"queriesPosition": 0,
|
|
"weightedRankingScore": 1.0
|
|
},
|
|
"_rankingScore": 1.0
|
|
},
|
|
{
|
|
"title": "How to Train Your Dragon: The Hidden World",
|
|
"id": "166428",
|
|
"color": [
|
|
"green",
|
|
"red"
|
|
],
|
|
"_vectors": {
|
|
"manual": [
|
|
-100.0,
|
|
231.0,
|
|
32.0
|
|
]
|
|
},
|
|
"_federation": {
|
|
"indexUid": "movies",
|
|
"queriesPosition": 0,
|
|
"weightedRankingScore": 1.0
|
|
},
|
|
"_rankingScore": 1.0
|
|
},
|
|
{
|
|
"title": "Shazam!",
|
|
"id": "287947",
|
|
"color": [
|
|
"green",
|
|
"blue"
|
|
],
|
|
"_vectors": {
|
|
"manual": [
|
|
1.0,
|
|
2.0,
|
|
3.0
|
|
]
|
|
},
|
|
"_federation": {
|
|
"indexUid": "movies",
|
|
"queriesPosition": 0,
|
|
"weightedRankingScore": 1.0
|
|
},
|
|
"_rankingScore": 1.0
|
|
}
|
|
],
|
|
"processingTimeMs": "[time]",
|
|
"limit": 20,
|
|
"offset": 0,
|
|
"estimatedTotalHits": 10
|
|
}
|
|
"###);
|
|
|
|
// mix and match query: order difficult to understand
|
|
let (response, code) = server
|
|
.multi_search(json!({"federation": {}, "queries": [
|
|
{"indexUid" : "batman", "q": "badman returns", "sort": ["title:desc"], "showRankingScore": true },
|
|
{"indexUid" : "movies", "q": "captain", "sort": ["title:desc"], "showRankingScore": true },
|
|
{"indexUid" : "batman", "q": "the bat", "sort": ["title:desc"], "showRankingScore": true },
|
|
]}))
|
|
.await;
|
|
snapshot!(code, @"400 Bad Request");
|
|
insta::assert_json_snapshot!(response, { ".processingTimeMs" => "[time]" }, @r###"
|
|
{
|
|
"message": "Inside `.queries[1]`: The results of queries #2 and #1 are incompatible: \n 1. `queries[2]`, `batman.rankingRules[0..=3]`: relevancy rule(s) words, typo, proximity, attribute\n 2. `queries[1].sort[0]`, `movies.rankingRules[0]`: descending sort rule(s) on field `title`\n - cannot compare a relevancy rule with a sort rule\n",
|
|
"code": "invalid_multi_search_query_ranking_rules",
|
|
"type": "invalid_request",
|
|
"link": "https://docs.meilisearch.com/errors#invalid_multi_search_query_ranking_rules"
|
|
}
|
|
"###);
|
|
}
|
|
|
|
#[actix_rt::test]
|
|
async fn federation_sort_different_indexes_same_criterion_opposite_direction() {
|
|
let server = Server::new().await;
|
|
|
|
let index = server.index("movies");
|
|
|
|
let documents = DOCUMENTS.clone();
|
|
let (value, _) = index.add_documents(documents, None).await;
|
|
index.wait_task(value.uid()).await;
|
|
|
|
let (value, _) = index
|
|
.update_settings(json!({
|
|
"sortableAttributes": ["title"],
|
|
"rankingRules": [
|
|
"sort",
|
|
"words",
|
|
"typo",
|
|
"proximity",
|
|
"attribute",
|
|
"exactness"
|
|
]
|
|
}))
|
|
.await;
|
|
index.wait_task(value.uid()).await;
|
|
|
|
let index = server.index("batman");
|
|
|
|
let documents = SCORE_DOCUMENTS.clone();
|
|
let (value, _) = index.add_documents(documents, None).await;
|
|
index.wait_task(value.uid()).await;
|
|
|
|
let (value, _) = index
|
|
.update_settings(json!({
|
|
"sortableAttributes": ["title"],
|
|
"rankingRules": [
|
|
"sort",
|
|
"words",
|
|
"typo",
|
|
"proximity",
|
|
"attribute",
|
|
"exactness"
|
|
]
|
|
}))
|
|
.await;
|
|
index.wait_task(value.uid()).await;
|
|
|
|
// all results from query 0
|
|
let (response, code) = server
|
|
.multi_search(json!({"federation": {}, "queries": [
|
|
{"indexUid" : "movies", "q": "", "sort": ["title:asc"], "showRankingScore": true },
|
|
{"indexUid" : "batman", "q": "", "sort": ["title:desc"], "showRankingScore": true },
|
|
]}))
|
|
.await;
|
|
snapshot!(code, @"400 Bad Request");
|
|
insta::assert_json_snapshot!(response, { ".processingTimeMs" => "[time]" }, @r###"
|
|
{
|
|
"message": "Inside `.queries[0]`: The results of queries #1 and #0 are incompatible: \n 1. `queries[1].sort[0]`, `batman.rankingRules[0]`: descending sort rule(s) on field `title`\n 2. `queries[0].sort[0]`, `movies.rankingRules[0]`: ascending sort rule(s) on field `title`\n - cannot compare two sort rules in opposite directions\n - note: The ranking rules of query #1 were modified during canonicalization:\n 1. Removed relevancy rule `words` at position #1 in ranking rules because the query is a placeholder search (`q`: \"\")\n 2. Removed relevancy rule `typo` at position #2 in ranking rules because the query is a placeholder search (`q`: \"\")\n 3. Removed relevancy rule `proximity` at position #3 in ranking rules because the query is a placeholder search (`q`: \"\")\n 4. Removed relevancy rule `attribute` at position #4 in ranking rules because the query is a placeholder search (`q`: \"\")\n 5. Removed relevancy rule `exactness` at position #5 in ranking rules because the query is a placeholder search (`q`: \"\")\n - note: The ranking rules of query #0 were modified during canonicalization:\n 1. Removed relevancy rule `words` at position #1 in ranking rules because the query is a placeholder search (`q`: \"\")\n 2. Removed relevancy rule `typo` at position #2 in ranking rules because the query is a placeholder search (`q`: \"\")\n 3. Removed relevancy rule `proximity` at position #3 in ranking rules because the query is a placeholder search (`q`: \"\")\n 4. Removed relevancy rule `attribute` at position #4 in ranking rules because the query is a placeholder search (`q`: \"\")\n 5. Removed relevancy rule `exactness` at position #5 in ranking rules because the query is a placeholder search (`q`: \"\")\n",
|
|
"code": "invalid_multi_search_query_ranking_rules",
|
|
"type": "invalid_request",
|
|
"link": "https://docs.meilisearch.com/errors#invalid_multi_search_query_ranking_rules"
|
|
}
|
|
"###);
|
|
|
|
// mix and match query: will be sorted by ranking score
|
|
let (response, code) = server
|
|
.multi_search(json!({"federation": {}, "queries": [
|
|
{"indexUid" : "batman", "q": "badman returns", "sort": ["title:asc"], "showRankingScore": true },
|
|
{"indexUid" : "movies", "q": "captain", "sort": ["title:desc"], "showRankingScore": true },
|
|
{"indexUid" : "batman", "q": "the bat", "sort": ["title:asc"], "showRankingScore": true },
|
|
]}))
|
|
.await;
|
|
snapshot!(code, @"400 Bad Request");
|
|
insta::assert_json_snapshot!(response, { ".processingTimeMs" => "[time]" }, @r###"
|
|
{
|
|
"message": "Inside `.queries[1]`: The results of queries #2 and #1 are incompatible: \n 1. `queries[2].sort[0]`, `batman.rankingRules[0]`: ascending sort rule(s) on field `title`\n 2. `queries[1].sort[0]`, `movies.rankingRules[0]`: descending sort rule(s) on field `title`\n - cannot compare two sort rules in opposite directions\n",
|
|
"code": "invalid_multi_search_query_ranking_rules",
|
|
"type": "invalid_request",
|
|
"link": "https://docs.meilisearch.com/errors#invalid_multi_search_query_ranking_rules"
|
|
}
|
|
"###);
|
|
}
|
|
|
|
#[actix_rt::test]
|
|
async fn federation_sort_different_indexes_different_criterion_same_direction() {
|
|
let server = Server::new().await;
|
|
|
|
let index = server.index("movies");
|
|
|
|
let documents = DOCUMENTS.clone();
|
|
let (value, _) = index.add_documents(documents, None).await;
|
|
index.wait_task(value.uid()).await;
|
|
|
|
let (value, _) = index
|
|
.update_settings(json!({
|
|
"sortableAttributes": ["title"],
|
|
"rankingRules": [
|
|
"sort",
|
|
"words",
|
|
"typo",
|
|
"proximity",
|
|
"attribute",
|
|
"exactness"
|
|
]
|
|
}))
|
|
.await;
|
|
index.wait_task(value.uid()).await;
|
|
|
|
let index = server.index("batman");
|
|
|
|
let documents = SCORE_DOCUMENTS.clone();
|
|
let (value, _) = index.add_documents(documents, None).await;
|
|
index.wait_task(value.uid()).await;
|
|
|
|
let (value, _) = index
|
|
.update_settings(json!({
|
|
"sortableAttributes": ["id"],
|
|
"rankingRules": [
|
|
"sort",
|
|
"words",
|
|
"typo",
|
|
"proximity",
|
|
"attribute",
|
|
"exactness"
|
|
]
|
|
}))
|
|
.await;
|
|
index.wait_task(value.uid()).await;
|
|
|
|
// return titles ordered accross indexes
|
|
let (response, code) = server
|
|
.multi_search(json!({"federation": {}, "queries": [
|
|
{"indexUid" : "movies", "q": "", "sort": ["title:asc"], "showRankingScore": true },
|
|
{"indexUid" : "batman", "q": "", "sort": ["id:asc"], "showRankingScore": true },
|
|
]}))
|
|
.await;
|
|
snapshot!(code, @"200 OK");
|
|
insta::assert_json_snapshot!(response, { ".processingTimeMs" => "[time]" }, @r###"
|
|
{
|
|
"hits": [
|
|
{
|
|
"title": "Batman the dark knight returns: Part 1",
|
|
"id": "A",
|
|
"_federation": {
|
|
"indexUid": "batman",
|
|
"queriesPosition": 1,
|
|
"weightedRankingScore": 1.0
|
|
},
|
|
"_rankingScore": 1.0
|
|
},
|
|
{
|
|
"title": "Batman the dark knight returns: Part 2",
|
|
"id": "B",
|
|
"_federation": {
|
|
"indexUid": "batman",
|
|
"queriesPosition": 1,
|
|
"weightedRankingScore": 1.0
|
|
},
|
|
"_rankingScore": 1.0
|
|
},
|
|
{
|
|
"title": "Batman Returns",
|
|
"id": "C",
|
|
"_federation": {
|
|
"indexUid": "batman",
|
|
"queriesPosition": 1,
|
|
"weightedRankingScore": 1.0
|
|
},
|
|
"_rankingScore": 1.0
|
|
},
|
|
{
|
|
"title": "Captain Marvel",
|
|
"id": "299537",
|
|
"color": [
|
|
"yellow",
|
|
"blue"
|
|
],
|
|
"_vectors": {
|
|
"manual": [
|
|
1.0,
|
|
2.0,
|
|
54.0
|
|
]
|
|
},
|
|
"_federation": {
|
|
"indexUid": "movies",
|
|
"queriesPosition": 0,
|
|
"weightedRankingScore": 1.0
|
|
},
|
|
"_rankingScore": 1.0
|
|
},
|
|
{
|
|
"title": "Batman",
|
|
"id": "D",
|
|
"_federation": {
|
|
"indexUid": "batman",
|
|
"queriesPosition": 1,
|
|
"weightedRankingScore": 1.0
|
|
},
|
|
"_rankingScore": 1.0
|
|
},
|
|
{
|
|
"title": "Badman",
|
|
"id": "E",
|
|
"_federation": {
|
|
"indexUid": "batman",
|
|
"queriesPosition": 1,
|
|
"weightedRankingScore": 1.0
|
|
},
|
|
"_rankingScore": 1.0
|
|
},
|
|
{
|
|
"title": "Escape Room",
|
|
"id": "522681",
|
|
"color": [
|
|
"yellow",
|
|
"red"
|
|
],
|
|
"_vectors": {
|
|
"manual": [
|
|
10.0,
|
|
-23.0,
|
|
32.0
|
|
]
|
|
},
|
|
"_federation": {
|
|
"indexUid": "movies",
|
|
"queriesPosition": 0,
|
|
"weightedRankingScore": 1.0
|
|
},
|
|
"_rankingScore": 1.0
|
|
},
|
|
{
|
|
"title": "Gläss",
|
|
"id": "450465",
|
|
"color": [
|
|
"blue",
|
|
"red"
|
|
],
|
|
"_vectors": {
|
|
"manual": [
|
|
-100.0,
|
|
340.0,
|
|
90.0
|
|
]
|
|
},
|
|
"_federation": {
|
|
"indexUid": "movies",
|
|
"queriesPosition": 0,
|
|
"weightedRankingScore": 1.0
|
|
},
|
|
"_rankingScore": 1.0
|
|
},
|
|
{
|
|
"title": "How to Train Your Dragon: The Hidden World",
|
|
"id": "166428",
|
|
"color": [
|
|
"green",
|
|
"red"
|
|
],
|
|
"_vectors": {
|
|
"manual": [
|
|
-100.0,
|
|
231.0,
|
|
32.0
|
|
]
|
|
},
|
|
"_federation": {
|
|
"indexUid": "movies",
|
|
"queriesPosition": 0,
|
|
"weightedRankingScore": 1.0
|
|
},
|
|
"_rankingScore": 1.0
|
|
},
|
|
{
|
|
"title": "Shazam!",
|
|
"id": "287947",
|
|
"color": [
|
|
"green",
|
|
"blue"
|
|
],
|
|
"_vectors": {
|
|
"manual": [
|
|
1.0,
|
|
2.0,
|
|
3.0
|
|
]
|
|
},
|
|
"_federation": {
|
|
"indexUid": "movies",
|
|
"queriesPosition": 0,
|
|
"weightedRankingScore": 1.0
|
|
},
|
|
"_rankingScore": 1.0
|
|
}
|
|
],
|
|
"processingTimeMs": "[time]",
|
|
"limit": 20,
|
|
"offset": 0,
|
|
"estimatedTotalHits": 10
|
|
}
|
|
"###);
|
|
|
|
// mix and match query: will be sorted across indexes and criterion
|
|
let (response, code) = server
|
|
.multi_search(json!({"federation": {}, "queries": [
|
|
{"indexUid" : "batman", "q": "badman returns", "sort": ["id:desc"], "showRankingScore": true },
|
|
{"indexUid" : "movies", "q": "captain", "sort": ["title:desc"], "showRankingScore": true },
|
|
{"indexUid" : "batman", "q": "the bat", "sort": ["id:desc"], "showRankingScore": true },
|
|
]}))
|
|
.await;
|
|
snapshot!(code, @"200 OK");
|
|
insta::assert_json_snapshot!(response, { ".processingTimeMs" => "[time]" }, @r###"
|
|
{
|
|
"hits": [
|
|
{
|
|
"title": "Badman",
|
|
"id": "E",
|
|
"_federation": {
|
|
"indexUid": "batman",
|
|
"queriesPosition": 0,
|
|
"weightedRankingScore": 0.5
|
|
},
|
|
"_rankingScore": 0.5
|
|
},
|
|
{
|
|
"title": "Batman",
|
|
"id": "D",
|
|
"_federation": {
|
|
"indexUid": "batman",
|
|
"queriesPosition": 0,
|
|
"weightedRankingScore": 0.23106060606060605
|
|
},
|
|
"_rankingScore": 0.23106060606060605
|
|
},
|
|
{
|
|
"title": "Captain Marvel",
|
|
"id": "299537",
|
|
"color": [
|
|
"yellow",
|
|
"blue"
|
|
],
|
|
"_vectors": {
|
|
"manual": [
|
|
1.0,
|
|
2.0,
|
|
54.0
|
|
]
|
|
},
|
|
"_federation": {
|
|
"indexUid": "movies",
|
|
"queriesPosition": 1,
|
|
"weightedRankingScore": 0.9848484848484848
|
|
},
|
|
"_rankingScore": 0.9848484848484848
|
|
},
|
|
{
|
|
"title": "Batman Returns",
|
|
"id": "C",
|
|
"_federation": {
|
|
"indexUid": "batman",
|
|
"queriesPosition": 0,
|
|
"weightedRankingScore": 0.8317901234567902
|
|
},
|
|
"_rankingScore": 0.8317901234567902
|
|
},
|
|
{
|
|
"title": "Batman the dark knight returns: Part 2",
|
|
"id": "B",
|
|
"_federation": {
|
|
"indexUid": "batman",
|
|
"queriesPosition": 2,
|
|
"weightedRankingScore": 0.9528218694885362
|
|
},
|
|
"_rankingScore": 0.9528218694885362
|
|
},
|
|
{
|
|
"title": "Batman the dark knight returns: Part 1",
|
|
"id": "A",
|
|
"_federation": {
|
|
"indexUid": "batman",
|
|
"queriesPosition": 2,
|
|
"weightedRankingScore": 0.9528218694885362
|
|
},
|
|
"_rankingScore": 0.9528218694885362
|
|
}
|
|
],
|
|
"processingTimeMs": "[time]",
|
|
"limit": 20,
|
|
"offset": 0,
|
|
"estimatedTotalHits": 6
|
|
}
|
|
"###);
|
|
}
|
|
|
|
#[actix_rt::test]
|
|
async fn federation_sort_different_indexes_different_criterion_opposite_direction() {
|
|
let server = Server::new().await;
|
|
|
|
let index = server.index("movies");
|
|
|
|
let documents = DOCUMENTS.clone();
|
|
let (value, _) = index.add_documents(documents, None).await;
|
|
index.wait_task(value.uid()).await;
|
|
|
|
let (value, _) = index
|
|
.update_settings(json!({
|
|
"sortableAttributes": ["title"],
|
|
"rankingRules": [
|
|
"sort",
|
|
"words",
|
|
"typo",
|
|
"proximity",
|
|
"attribute",
|
|
"exactness"
|
|
]
|
|
}))
|
|
.await;
|
|
index.wait_task(value.uid()).await;
|
|
|
|
let index = server.index("batman");
|
|
|
|
let documents = SCORE_DOCUMENTS.clone();
|
|
let (value, _) = index.add_documents(documents, None).await;
|
|
index.wait_task(value.uid()).await;
|
|
|
|
let (value, _) = index
|
|
.update_settings(json!({
|
|
"sortableAttributes": ["id"],
|
|
"rankingRules": [
|
|
"sort",
|
|
"words",
|
|
"typo",
|
|
"proximity",
|
|
"attribute",
|
|
"exactness"
|
|
]
|
|
}))
|
|
.await;
|
|
index.wait_task(value.uid()).await;
|
|
|
|
// all results from query 0 first
|
|
let (response, code) = server
|
|
.multi_search(json!({"federation": {}, "queries": [
|
|
{"indexUid" : "movies", "q": "", "sort": ["title:asc"], "showRankingScore": true },
|
|
{"indexUid" : "batman", "q": "", "sort": ["id:desc"], "showRankingScore": true },
|
|
]}))
|
|
.await;
|
|
snapshot!(code, @"400 Bad Request");
|
|
insta::assert_json_snapshot!(response, { ".processingTimeMs" => "[time]" }, @r###"
|
|
{
|
|
"message": "Inside `.queries[0]`: The results of queries #1 and #0 are incompatible: \n 1. `queries[1].sort[0]`, `batman.rankingRules[0]`: descending sort rule(s) on field `id`\n 2. `queries[0].sort[0]`, `movies.rankingRules[0]`: ascending sort rule(s) on field `title`\n - cannot compare two sort rules in opposite directions\n - note: The ranking rules of query #1 were modified during canonicalization:\n 1. Removed relevancy rule `words` at position #1 in ranking rules because the query is a placeholder search (`q`: \"\")\n 2. Removed relevancy rule `typo` at position #2 in ranking rules because the query is a placeholder search (`q`: \"\")\n 3. Removed relevancy rule `proximity` at position #3 in ranking rules because the query is a placeholder search (`q`: \"\")\n 4. Removed relevancy rule `attribute` at position #4 in ranking rules because the query is a placeholder search (`q`: \"\")\n 5. Removed relevancy rule `exactness` at position #5 in ranking rules because the query is a placeholder search (`q`: \"\")\n - note: The ranking rules of query #0 were modified during canonicalization:\n 1. Removed relevancy rule `words` at position #1 in ranking rules because the query is a placeholder search (`q`: \"\")\n 2. Removed relevancy rule `typo` at position #2 in ranking rules because the query is a placeholder search (`q`: \"\")\n 3. Removed relevancy rule `proximity` at position #3 in ranking rules because the query is a placeholder search (`q`: \"\")\n 4. Removed relevancy rule `attribute` at position #4 in ranking rules because the query is a placeholder search (`q`: \"\")\n 5. Removed relevancy rule `exactness` at position #5 in ranking rules because the query is a placeholder search (`q`: \"\")\n",
|
|
"code": "invalid_multi_search_query_ranking_rules",
|
|
"type": "invalid_request",
|
|
"link": "https://docs.meilisearch.com/errors#invalid_multi_search_query_ranking_rules"
|
|
}
|
|
"###);
|
|
|
|
// mix and match query: more or less by ranking score
|
|
let (response, code) = server
|
|
.multi_search(json!({"federation": {}, "queries": [
|
|
{"indexUid" : "batman", "q": "badman returns", "sort": ["id:desc"], "showRankingScore": true },
|
|
{"indexUid" : "movies", "q": "captain", "sort": ["title:asc"], "showRankingScore": true },
|
|
{"indexUid" : "batman", "q": "the bat", "sort": ["id:desc"], "showRankingScore": true },
|
|
]}))
|
|
.await;
|
|
snapshot!(code, @"400 Bad Request");
|
|
insta::assert_json_snapshot!(response, { ".processingTimeMs" => "[time]" }, @r###"
|
|
{
|
|
"message": "Inside `.queries[1]`: The results of queries #2 and #1 are incompatible: \n 1. `queries[2].sort[0]`, `batman.rankingRules[0]`: descending sort rule(s) on field `id`\n 2. `queries[1].sort[0]`, `movies.rankingRules[0]`: ascending sort rule(s) on field `title`\n - cannot compare two sort rules in opposite directions\n",
|
|
"code": "invalid_multi_search_query_ranking_rules",
|
|
"type": "invalid_request",
|
|
"link": "https://docs.meilisearch.com/errors#invalid_multi_search_query_ranking_rules"
|
|
}
|
|
"###);
|
|
}
|
|
|
|
#[actix_rt::test]
|
|
async fn federation_limit_offset() {
|
|
let server = Server::new().await;
|
|
let index = server.index("test");
|
|
|
|
let documents = DOCUMENTS.clone();
|
|
index.add_documents(documents, None).await;
|
|
index.wait_task(0).await;
|
|
|
|
let index = server.index("nested");
|
|
let documents = NESTED_DOCUMENTS.clone();
|
|
index.add_documents(documents, None).await;
|
|
index.wait_task(1).await;
|
|
|
|
let index = server.index("score");
|
|
let documents = SCORE_DOCUMENTS.clone();
|
|
index.add_documents(documents, None).await;
|
|
index.wait_task(2).await;
|
|
{
|
|
let (response, code) = server
|
|
.multi_search(json!({"federation": {}, "queries": [
|
|
{"indexUid" : "test", "q": "glass", "attributesToRetrieve": ["title"]},
|
|
{"indexUid" : "test", "q": "captain", "attributesToRetrieve": ["title"]},
|
|
{"indexUid": "nested", "q": "pésti", "attributesToRetrieve": ["id"]},
|
|
{"indexUid" : "test", "q": "Escape", "attributesToRetrieve": ["title"]},
|
|
{"indexUid": "nested", "q": "jean", "attributesToRetrieve": ["id"]},
|
|
{"indexUid": "score", "q": "jean", "attributesToRetrieve": ["title"]},
|
|
{"indexUid": "test", "q": "the bat", "attributesToRetrieve": ["title"]},
|
|
{"indexUid": "score", "q": "the bat", "attributesToRetrieve": ["title"]},
|
|
{"indexUid": "score", "q": "badman returns", "attributesToRetrieve": ["title"]},
|
|
{"indexUid" : "score", "q": "batman", "attributesToRetrieve": ["title"]},
|
|
{"indexUid": "score", "q": "batman returns", "attributesToRetrieve": ["title"]},
|
|
]}))
|
|
.await;
|
|
snapshot!(code, @"200 OK");
|
|
insta::assert_json_snapshot!(response, { ".processingTimeMs" => "[time]", ".**._rankingScore" => "[score]" }, @r###"
|
|
{
|
|
"hits": [
|
|
{
|
|
"title": "Gläss",
|
|
"_federation": {
|
|
"indexUid": "test",
|
|
"queriesPosition": 0,
|
|
"weightedRankingScore": 1.0
|
|
}
|
|
},
|
|
{
|
|
"id": 852,
|
|
"_federation": {
|
|
"indexUid": "nested",
|
|
"queriesPosition": 2,
|
|
"weightedRankingScore": 1.0
|
|
}
|
|
},
|
|
{
|
|
"title": "Batman",
|
|
"_federation": {
|
|
"indexUid": "score",
|
|
"queriesPosition": 9,
|
|
"weightedRankingScore": 1.0
|
|
}
|
|
},
|
|
{
|
|
"title": "Batman Returns",
|
|
"_federation": {
|
|
"indexUid": "score",
|
|
"queriesPosition": 10,
|
|
"weightedRankingScore": 1.0
|
|
}
|
|
},
|
|
{
|
|
"title": "Captain Marvel",
|
|
"_federation": {
|
|
"indexUid": "test",
|
|
"queriesPosition": 1,
|
|
"weightedRankingScore": 0.9848484848484848
|
|
}
|
|
},
|
|
{
|
|
"title": "Escape Room",
|
|
"_federation": {
|
|
"indexUid": "test",
|
|
"queriesPosition": 3,
|
|
"weightedRankingScore": 0.9848484848484848
|
|
}
|
|
},
|
|
{
|
|
"id": 951,
|
|
"_federation": {
|
|
"indexUid": "nested",
|
|
"queriesPosition": 4,
|
|
"weightedRankingScore": 0.9848484848484848
|
|
}
|
|
},
|
|
{
|
|
"title": "Batman the dark knight returns: Part 1",
|
|
"_federation": {
|
|
"indexUid": "score",
|
|
"queriesPosition": 9,
|
|
"weightedRankingScore": 0.9848484848484848
|
|
}
|
|
},
|
|
{
|
|
"title": "Batman the dark knight returns: Part 2",
|
|
"_federation": {
|
|
"indexUid": "score",
|
|
"queriesPosition": 9,
|
|
"weightedRankingScore": 0.9848484848484848
|
|
}
|
|
},
|
|
{
|
|
"id": 654,
|
|
"_federation": {
|
|
"indexUid": "nested",
|
|
"queriesPosition": 2,
|
|
"weightedRankingScore": 0.7803030303030303
|
|
}
|
|
},
|
|
{
|
|
"title": "Badman",
|
|
"_federation": {
|
|
"indexUid": "score",
|
|
"queriesPosition": 8,
|
|
"weightedRankingScore": 0.5
|
|
}
|
|
},
|
|
{
|
|
"title": "How to Train Your Dragon: The Hidden World",
|
|
"_federation": {
|
|
"indexUid": "test",
|
|
"queriesPosition": 6,
|
|
"weightedRankingScore": 0.4166666666666667
|
|
}
|
|
}
|
|
],
|
|
"processingTimeMs": "[time]",
|
|
"limit": 20,
|
|
"offset": 0,
|
|
"estimatedTotalHits": 12
|
|
}
|
|
"###);
|
|
}
|
|
|
|
{
|
|
let (response, code) = server
|
|
.multi_search(json!({"federation": {"limit": 1}, "queries": [
|
|
{"indexUid" : "test", "q": "glass", "attributesToRetrieve": ["title"]},
|
|
{"indexUid" : "test", "q": "captain", "attributesToRetrieve": ["title"]},
|
|
{"indexUid": "nested", "q": "pésti", "attributesToRetrieve": ["id"]},
|
|
{"indexUid" : "test", "q": "Escape", "attributesToRetrieve": ["title"]},
|
|
{"indexUid": "nested", "q": "jean", "attributesToRetrieve": ["id"]},
|
|
{"indexUid": "score", "q": "jean", "attributesToRetrieve": ["title"]},
|
|
{"indexUid": "test", "q": "the bat", "attributesToRetrieve": ["title"]},
|
|
{"indexUid": "score", "q": "the bat", "attributesToRetrieve": ["title"]},
|
|
{"indexUid": "score", "q": "badman returns", "attributesToRetrieve": ["title"]},
|
|
{"indexUid" : "score", "q": "batman", "attributesToRetrieve": ["title"]},
|
|
{"indexUid": "score", "q": "batman returns", "attributesToRetrieve": ["title"]},
|
|
]}))
|
|
.await;
|
|
snapshot!(code, @"200 OK");
|
|
insta::assert_json_snapshot!(response, { ".processingTimeMs" => "[time]", ".**._rankingScore" => "[score]" }, @r###"
|
|
{
|
|
"hits": [
|
|
{
|
|
"title": "Gläss",
|
|
"_federation": {
|
|
"indexUid": "test",
|
|
"queriesPosition": 0,
|
|
"weightedRankingScore": 1.0
|
|
}
|
|
}
|
|
],
|
|
"processingTimeMs": "[time]",
|
|
"limit": 1,
|
|
"offset": 0,
|
|
"estimatedTotalHits": 12
|
|
}
|
|
"###);
|
|
}
|
|
|
|
{
|
|
let (response, code) = server
|
|
.multi_search(json!({"federation": {"offset": 2}, "queries": [
|
|
{"indexUid" : "test", "q": "glass", "attributesToRetrieve": ["title"]},
|
|
{"indexUid" : "test", "q": "captain", "attributesToRetrieve": ["title"]},
|
|
{"indexUid": "nested", "q": "pésti", "attributesToRetrieve": ["id"]},
|
|
{"indexUid" : "test", "q": "Escape", "attributesToRetrieve": ["title"]},
|
|
{"indexUid": "nested", "q": "jean", "attributesToRetrieve": ["id"]},
|
|
{"indexUid": "score", "q": "jean", "attributesToRetrieve": ["title"]},
|
|
{"indexUid": "test", "q": "the bat", "attributesToRetrieve": ["title"]},
|
|
{"indexUid": "score", "q": "the bat", "attributesToRetrieve": ["title"]},
|
|
{"indexUid": "score", "q": "badman returns", "attributesToRetrieve": ["title"]},
|
|
{"indexUid" : "score", "q": "batman", "attributesToRetrieve": ["title"]},
|
|
{"indexUid": "score", "q": "batman returns", "attributesToRetrieve": ["title"]},
|
|
]}))
|
|
.await;
|
|
snapshot!(code, @"200 OK");
|
|
insta::assert_json_snapshot!(response, { ".processingTimeMs" => "[time]", ".**._rankingScore" => "[score]" }, @r###"
|
|
{
|
|
"hits": [
|
|
{
|
|
"title": "Batman",
|
|
"_federation": {
|
|
"indexUid": "score",
|
|
"queriesPosition": 9,
|
|
"weightedRankingScore": 1.0
|
|
}
|
|
},
|
|
{
|
|
"title": "Batman Returns",
|
|
"_federation": {
|
|
"indexUid": "score",
|
|
"queriesPosition": 10,
|
|
"weightedRankingScore": 1.0
|
|
}
|
|
},
|
|
{
|
|
"title": "Captain Marvel",
|
|
"_federation": {
|
|
"indexUid": "test",
|
|
"queriesPosition": 1,
|
|
"weightedRankingScore": 0.9848484848484848
|
|
}
|
|
},
|
|
{
|
|
"title": "Escape Room",
|
|
"_federation": {
|
|
"indexUid": "test",
|
|
"queriesPosition": 3,
|
|
"weightedRankingScore": 0.9848484848484848
|
|
}
|
|
},
|
|
{
|
|
"id": 951,
|
|
"_federation": {
|
|
"indexUid": "nested",
|
|
"queriesPosition": 4,
|
|
"weightedRankingScore": 0.9848484848484848
|
|
}
|
|
},
|
|
{
|
|
"title": "Batman the dark knight returns: Part 1",
|
|
"_federation": {
|
|
"indexUid": "score",
|
|
"queriesPosition": 9,
|
|
"weightedRankingScore": 0.9848484848484848
|
|
}
|
|
},
|
|
{
|
|
"title": "Batman the dark knight returns: Part 2",
|
|
"_federation": {
|
|
"indexUid": "score",
|
|
"queriesPosition": 9,
|
|
"weightedRankingScore": 0.9848484848484848
|
|
}
|
|
},
|
|
{
|
|
"id": 654,
|
|
"_federation": {
|
|
"indexUid": "nested",
|
|
"queriesPosition": 2,
|
|
"weightedRankingScore": 0.7803030303030303
|
|
}
|
|
},
|
|
{
|
|
"title": "Badman",
|
|
"_federation": {
|
|
"indexUid": "score",
|
|
"queriesPosition": 8,
|
|
"weightedRankingScore": 0.5
|
|
}
|
|
},
|
|
{
|
|
"title": "How to Train Your Dragon: The Hidden World",
|
|
"_federation": {
|
|
"indexUid": "test",
|
|
"queriesPosition": 6,
|
|
"weightedRankingScore": 0.4166666666666667
|
|
}
|
|
}
|
|
],
|
|
"processingTimeMs": "[time]",
|
|
"limit": 20,
|
|
"offset": 2,
|
|
"estimatedTotalHits": 12
|
|
}
|
|
"###);
|
|
}
|
|
|
|
{
|
|
let (response, code) = server
|
|
.multi_search(json!({"federation": {"offset": 12}, "queries": [
|
|
{"indexUid" : "test", "q": "glass", "attributesToRetrieve": ["title"]},
|
|
{"indexUid" : "test", "q": "captain", "attributesToRetrieve": ["title"]},
|
|
{"indexUid": "nested", "q": "pésti", "attributesToRetrieve": ["id"]},
|
|
{"indexUid" : "test", "q": "Escape", "attributesToRetrieve": ["title"]},
|
|
{"indexUid": "nested", "q": "jean", "attributesToRetrieve": ["id"]},
|
|
{"indexUid": "score", "q": "jean", "attributesToRetrieve": ["title"]},
|
|
{"indexUid": "test", "q": "the bat", "attributesToRetrieve": ["title"]},
|
|
{"indexUid": "score", "q": "the bat", "attributesToRetrieve": ["title"]},
|
|
{"indexUid": "score", "q": "badman returns", "attributesToRetrieve": ["title"]},
|
|
{"indexUid" : "score", "q": "batman", "attributesToRetrieve": ["title"]},
|
|
{"indexUid": "score", "q": "batman returns", "attributesToRetrieve": ["title"]},
|
|
]}))
|
|
.await;
|
|
snapshot!(code, @"200 OK");
|
|
insta::assert_json_snapshot!(response, { ".processingTimeMs" => "[time]", ".**._rankingScore" => "[score]" }, @r###"
|
|
{
|
|
"hits": [],
|
|
"processingTimeMs": "[time]",
|
|
"limit": 20,
|
|
"offset": 12,
|
|
"estimatedTotalHits": 12
|
|
}
|
|
"###);
|
|
}
|
|
}
|
|
|
|
#[actix_rt::test]
|
|
async fn federation_formatting() {
|
|
let server = Server::new().await;
|
|
let index = server.index("test");
|
|
|
|
let documents = DOCUMENTS.clone();
|
|
index.add_documents(documents, None).await;
|
|
index.wait_task(0).await;
|
|
|
|
let index = server.index("nested");
|
|
let documents = NESTED_DOCUMENTS.clone();
|
|
index.add_documents(documents, None).await;
|
|
index.wait_task(1).await;
|
|
|
|
let index = server.index("score");
|
|
let documents = SCORE_DOCUMENTS.clone();
|
|
index.add_documents(documents, None).await;
|
|
index.wait_task(2).await;
|
|
{
|
|
let (response, code) = server
|
|
.multi_search(json!({"federation": {}, "queries": [
|
|
{"indexUid" : "test", "q": "glass", "attributesToRetrieve": ["title"], "attributesToHighlight": ["title"]},
|
|
{"indexUid" : "test", "q": "captain", "attributesToRetrieve": ["title"], "attributesToHighlight": ["title"]},
|
|
{"indexUid": "nested", "q": "pésti", "attributesToRetrieve": ["id"]},
|
|
{"indexUid" : "test", "q": "Escape", "attributesToRetrieve": ["title"], "attributesToHighlight": ["title"]},
|
|
{"indexUid": "nested", "q": "jean", "attributesToRetrieve": ["id"]},
|
|
{"indexUid": "score", "q": "jean", "attributesToRetrieve": ["title"], "attributesToHighlight": ["title"]},
|
|
{"indexUid": "test", "q": "the bat", "attributesToRetrieve": ["title"], "attributesToHighlight": ["title"]},
|
|
{"indexUid": "score", "q": "the bat", "attributesToRetrieve": ["title"], "attributesToHighlight": ["title"]},
|
|
{"indexUid": "score", "q": "badman returns", "attributesToRetrieve": ["title"], "attributesToHighlight": ["title"]},
|
|
{"indexUid" : "score", "q": "batman", "attributesToRetrieve": ["title"], "attributesToHighlight": ["title"]},
|
|
{"indexUid": "score", "q": "batman returns", "attributesToRetrieve": ["title"], "attributesToHighlight": ["title"]},
|
|
]}))
|
|
.await;
|
|
snapshot!(code, @"200 OK");
|
|
insta::assert_json_snapshot!(response, { ".processingTimeMs" => "[time]", ".**._rankingScore" => "[score]" }, @r###"
|
|
{
|
|
"hits": [
|
|
{
|
|
"title": "Gläss",
|
|
"_federation": {
|
|
"indexUid": "test",
|
|
"queriesPosition": 0,
|
|
"weightedRankingScore": 1.0
|
|
},
|
|
"_formatted": {
|
|
"title": "<em>Gläss</em>"
|
|
}
|
|
},
|
|
{
|
|
"id": 852,
|
|
"_federation": {
|
|
"indexUid": "nested",
|
|
"queriesPosition": 2,
|
|
"weightedRankingScore": 1.0
|
|
}
|
|
},
|
|
{
|
|
"title": "Batman",
|
|
"_federation": {
|
|
"indexUid": "score",
|
|
"queriesPosition": 9,
|
|
"weightedRankingScore": 1.0
|
|
},
|
|
"_formatted": {
|
|
"title": "<em>Batman</em>"
|
|
}
|
|
},
|
|
{
|
|
"title": "Batman Returns",
|
|
"_federation": {
|
|
"indexUid": "score",
|
|
"queriesPosition": 10,
|
|
"weightedRankingScore": 1.0
|
|
},
|
|
"_formatted": {
|
|
"title": "<em>Batman</em> <em>Returns</em>"
|
|
}
|
|
},
|
|
{
|
|
"title": "Captain Marvel",
|
|
"_federation": {
|
|
"indexUid": "test",
|
|
"queriesPosition": 1,
|
|
"weightedRankingScore": 0.9848484848484848
|
|
},
|
|
"_formatted": {
|
|
"title": "<em>Captain</em> Marvel"
|
|
}
|
|
},
|
|
{
|
|
"title": "Escape Room",
|
|
"_federation": {
|
|
"indexUid": "test",
|
|
"queriesPosition": 3,
|
|
"weightedRankingScore": 0.9848484848484848
|
|
},
|
|
"_formatted": {
|
|
"title": "<em>Escape</em> Room"
|
|
}
|
|
},
|
|
{
|
|
"id": 951,
|
|
"_federation": {
|
|
"indexUid": "nested",
|
|
"queriesPosition": 4,
|
|
"weightedRankingScore": 0.9848484848484848
|
|
}
|
|
},
|
|
{
|
|
"title": "Batman the dark knight returns: Part 1",
|
|
"_federation": {
|
|
"indexUid": "score",
|
|
"queriesPosition": 9,
|
|
"weightedRankingScore": 0.9848484848484848
|
|
},
|
|
"_formatted": {
|
|
"title": "<em>Batman</em> the dark knight returns: Part 1"
|
|
}
|
|
},
|
|
{
|
|
"title": "Batman the dark knight returns: Part 2",
|
|
"_federation": {
|
|
"indexUid": "score",
|
|
"queriesPosition": 9,
|
|
"weightedRankingScore": 0.9848484848484848
|
|
},
|
|
"_formatted": {
|
|
"title": "<em>Batman</em> the dark knight returns: Part 2"
|
|
}
|
|
},
|
|
{
|
|
"id": 654,
|
|
"_federation": {
|
|
"indexUid": "nested",
|
|
"queriesPosition": 2,
|
|
"weightedRankingScore": 0.7803030303030303
|
|
}
|
|
},
|
|
{
|
|
"title": "Badman",
|
|
"_federation": {
|
|
"indexUid": "score",
|
|
"queriesPosition": 8,
|
|
"weightedRankingScore": 0.5
|
|
},
|
|
"_formatted": {
|
|
"title": "<em>Badman</em>"
|
|
}
|
|
},
|
|
{
|
|
"title": "How to Train Your Dragon: The Hidden World",
|
|
"_federation": {
|
|
"indexUid": "test",
|
|
"queriesPosition": 6,
|
|
"weightedRankingScore": 0.4166666666666667
|
|
},
|
|
"_formatted": {
|
|
"title": "How to Train Your Dragon: <em>The</em> Hidden World"
|
|
}
|
|
}
|
|
],
|
|
"processingTimeMs": "[time]",
|
|
"limit": 20,
|
|
"offset": 0,
|
|
"estimatedTotalHits": 12
|
|
}
|
|
"###);
|
|
}
|
|
|
|
{
|
|
let (response, code) = server
|
|
.multi_search(json!({"federation": {"limit": 1}, "queries": [
|
|
{"indexUid" : "test", "q": "glass", "attributesToRetrieve": ["title"]},
|
|
{"indexUid" : "test", "q": "captain", "attributesToRetrieve": ["title"]},
|
|
{"indexUid": "nested", "q": "pésti", "attributesToRetrieve": ["id"]},
|
|
{"indexUid" : "test", "q": "Escape", "attributesToRetrieve": ["title"]},
|
|
{"indexUid": "nested", "q": "jean", "attributesToRetrieve": ["id"]},
|
|
{"indexUid": "score", "q": "jean", "attributesToRetrieve": ["title"]},
|
|
{"indexUid": "test", "q": "the bat", "attributesToRetrieve": ["title"]},
|
|
{"indexUid": "score", "q": "the bat", "attributesToRetrieve": ["title"]},
|
|
{"indexUid": "score", "q": "badman returns", "attributesToRetrieve": ["title"]},
|
|
{"indexUid" : "score", "q": "batman", "attributesToRetrieve": ["title"]},
|
|
{"indexUid": "score", "q": "batman returns", "attributesToRetrieve": ["title"]},
|
|
]}))
|
|
.await;
|
|
snapshot!(code, @"200 OK");
|
|
insta::assert_json_snapshot!(response, { ".processingTimeMs" => "[time]", ".**._rankingScore" => "[score]" }, @r###"
|
|
{
|
|
"hits": [
|
|
{
|
|
"title": "Gläss",
|
|
"_federation": {
|
|
"indexUid": "test",
|
|
"queriesPosition": 0,
|
|
"weightedRankingScore": 1.0
|
|
}
|
|
}
|
|
],
|
|
"processingTimeMs": "[time]",
|
|
"limit": 1,
|
|
"offset": 0,
|
|
"estimatedTotalHits": 12
|
|
}
|
|
"###);
|
|
}
|
|
|
|
{
|
|
let (response, code) = server
|
|
.multi_search(json!({"federation": {"offset": 2}, "queries": [
|
|
{"indexUid" : "test", "q": "glass", "attributesToRetrieve": ["title"]},
|
|
{"indexUid" : "test", "q": "captain", "attributesToRetrieve": ["title"]},
|
|
{"indexUid": "nested", "q": "pésti", "attributesToRetrieve": ["id"]},
|
|
{"indexUid" : "test", "q": "Escape", "attributesToRetrieve": ["title"]},
|
|
{"indexUid": "nested", "q": "jean", "attributesToRetrieve": ["id"]},
|
|
{"indexUid": "score", "q": "jean", "attributesToRetrieve": ["title"]},
|
|
{"indexUid": "test", "q": "the bat", "attributesToRetrieve": ["title"]},
|
|
{"indexUid": "score", "q": "the bat", "attributesToRetrieve": ["title"]},
|
|
{"indexUid": "score", "q": "badman returns", "attributesToRetrieve": ["title"]},
|
|
{"indexUid" : "score", "q": "batman", "attributesToRetrieve": ["title"]},
|
|
{"indexUid": "score", "q": "batman returns", "attributesToRetrieve": ["title"]},
|
|
]}))
|
|
.await;
|
|
snapshot!(code, @"200 OK");
|
|
insta::assert_json_snapshot!(response, { ".processingTimeMs" => "[time]", ".**._rankingScore" => "[score]" }, @r###"
|
|
{
|
|
"hits": [
|
|
{
|
|
"title": "Batman",
|
|
"_federation": {
|
|
"indexUid": "score",
|
|
"queriesPosition": 9,
|
|
"weightedRankingScore": 1.0
|
|
}
|
|
},
|
|
{
|
|
"title": "Batman Returns",
|
|
"_federation": {
|
|
"indexUid": "score",
|
|
"queriesPosition": 10,
|
|
"weightedRankingScore": 1.0
|
|
}
|
|
},
|
|
{
|
|
"title": "Captain Marvel",
|
|
"_federation": {
|
|
"indexUid": "test",
|
|
"queriesPosition": 1,
|
|
"weightedRankingScore": 0.9848484848484848
|
|
}
|
|
},
|
|
{
|
|
"title": "Escape Room",
|
|
"_federation": {
|
|
"indexUid": "test",
|
|
"queriesPosition": 3,
|
|
"weightedRankingScore": 0.9848484848484848
|
|
}
|
|
},
|
|
{
|
|
"id": 951,
|
|
"_federation": {
|
|
"indexUid": "nested",
|
|
"queriesPosition": 4,
|
|
"weightedRankingScore": 0.9848484848484848
|
|
}
|
|
},
|
|
{
|
|
"title": "Batman the dark knight returns: Part 1",
|
|
"_federation": {
|
|
"indexUid": "score",
|
|
"queriesPosition": 9,
|
|
"weightedRankingScore": 0.9848484848484848
|
|
}
|
|
},
|
|
{
|
|
"title": "Batman the dark knight returns: Part 2",
|
|
"_federation": {
|
|
"indexUid": "score",
|
|
"queriesPosition": 9,
|
|
"weightedRankingScore": 0.9848484848484848
|
|
}
|
|
},
|
|
{
|
|
"id": 654,
|
|
"_federation": {
|
|
"indexUid": "nested",
|
|
"queriesPosition": 2,
|
|
"weightedRankingScore": 0.7803030303030303
|
|
}
|
|
},
|
|
{
|
|
"title": "Badman",
|
|
"_federation": {
|
|
"indexUid": "score",
|
|
"queriesPosition": 8,
|
|
"weightedRankingScore": 0.5
|
|
}
|
|
},
|
|
{
|
|
"title": "How to Train Your Dragon: The Hidden World",
|
|
"_federation": {
|
|
"indexUid": "test",
|
|
"queriesPosition": 6,
|
|
"weightedRankingScore": 0.4166666666666667
|
|
}
|
|
}
|
|
],
|
|
"processingTimeMs": "[time]",
|
|
"limit": 20,
|
|
"offset": 2,
|
|
"estimatedTotalHits": 12
|
|
}
|
|
"###);
|
|
}
|
|
|
|
{
|
|
let (response, code) = server
|
|
.multi_search(json!({"federation": {"offset": 12}, "queries": [
|
|
{"indexUid" : "test", "q": "glass", "attributesToRetrieve": ["title"]},
|
|
{"indexUid" : "test", "q": "captain", "attributesToRetrieve": ["title"]},
|
|
{"indexUid": "nested", "q": "pésti", "attributesToRetrieve": ["id"]},
|
|
{"indexUid" : "test", "q": "Escape", "attributesToRetrieve": ["title"]},
|
|
{"indexUid": "nested", "q": "jean", "attributesToRetrieve": ["id"]},
|
|
{"indexUid": "score", "q": "jean", "attributesToRetrieve": ["title"]},
|
|
{"indexUid": "test", "q": "the bat", "attributesToRetrieve": ["title"]},
|
|
{"indexUid": "score", "q": "the bat", "attributesToRetrieve": ["title"]},
|
|
{"indexUid": "score", "q": "badman returns", "attributesToRetrieve": ["title"]},
|
|
{"indexUid" : "score", "q": "batman", "attributesToRetrieve": ["title"]},
|
|
{"indexUid": "score", "q": "batman returns", "attributesToRetrieve": ["title"]},
|
|
]}))
|
|
.await;
|
|
snapshot!(code, @"200 OK");
|
|
insta::assert_json_snapshot!(response, { ".processingTimeMs" => "[time]", ".**._rankingScore" => "[score]" }, @r###"
|
|
{
|
|
"hits": [],
|
|
"processingTimeMs": "[time]",
|
|
"limit": 20,
|
|
"offset": 12,
|
|
"estimatedTotalHits": 12
|
|
}
|
|
"###);
|
|
}
|
|
}
|
|
|
|
#[actix_rt::test]
|
|
async fn federation_invalid_weight() {
|
|
let server = Server::new().await;
|
|
|
|
let index = server.index("fruits");
|
|
|
|
let documents = FRUITS_DOCUMENTS.clone();
|
|
let (value, _) = index.add_documents(documents, None).await;
|
|
index.wait_task(value.uid()).await;
|
|
|
|
let (value, _) = index
|
|
.update_settings(
|
|
json!({"searchableAttributes": ["name"], "filterableAttributes": ["BOOST"]}),
|
|
)
|
|
.await;
|
|
index.wait_task(value.uid()).await;
|
|
|
|
let (response, code) = server
|
|
.multi_search(json!({"federation": {}, "queries": [
|
|
{"indexUid" : "fruits", "q": "apple red", "filter": "BOOST = true", "showRankingScore": true, "federationOptions": {"weight": 3.0}},
|
|
{"indexUid": "fruits", "q": "apple red", "showRankingScore": true, "federationOptions": {"weight": -12}},
|
|
]}))
|
|
.await;
|
|
snapshot!(code, @"400 Bad Request");
|
|
insta::assert_json_snapshot!(response, { ".processingTimeMs" => "[time]" }, @r###"
|
|
{
|
|
"message": "Invalid value at `.queries[1].federationOptions.weight`: the value of `weight` is invalid, expected a positive float (>= 0.0).",
|
|
"code": "invalid_multi_search_weight",
|
|
"type": "invalid_request",
|
|
"link": "https://docs.meilisearch.com/errors#invalid_multi_search_weight"
|
|
}
|
|
"###);
|
|
}
|
|
|
|
#[actix_rt::test]
|
|
async fn federation_null_weight() {
|
|
let server = Server::new().await;
|
|
|
|
let index = server.index("fruits");
|
|
|
|
let documents = FRUITS_DOCUMENTS.clone();
|
|
let (value, _) = index.add_documents(documents, None).await;
|
|
index.wait_task(value.uid()).await;
|
|
|
|
let (value, _) = index
|
|
.update_settings(
|
|
json!({"searchableAttributes": ["name"], "filterableAttributes": ["BOOST"]}),
|
|
)
|
|
.await;
|
|
index.wait_task(value.uid()).await;
|
|
|
|
let (response, code) = server
|
|
.multi_search(json!({"federation": {}, "queries": [
|
|
{"indexUid" : "fruits", "q": "apple red", "filter": "BOOST = true", "showRankingScore": true, "federationOptions": {"weight": 3.0}},
|
|
{"indexUid": "fruits", "q": "apple red", "showRankingScore": true, "federationOptions": {"weight": 0.0} },
|
|
]}))
|
|
.await;
|
|
snapshot!(code, @"200 OK");
|
|
insta::assert_json_snapshot!(response, { ".processingTimeMs" => "[time]" }, @r###"
|
|
{
|
|
"hits": [
|
|
{
|
|
"name": "Exclusive sale: Red delicious apple",
|
|
"id": "red-delicious-boosted",
|
|
"BOOST": true,
|
|
"_federation": {
|
|
"indexUid": "fruits",
|
|
"queriesPosition": 0,
|
|
"weightedRankingScore": 2.7281746031746033
|
|
},
|
|
"_rankingScore": 0.9093915343915344
|
|
},
|
|
{
|
|
"name": "Exclusive sale: green apple",
|
|
"id": "green-apple-boosted",
|
|
"BOOST": true,
|
|
"_federation": {
|
|
"indexUid": "fruits",
|
|
"queriesPosition": 0,
|
|
"weightedRankingScore": 1.318181818181818
|
|
},
|
|
"_rankingScore": 0.4393939393939394
|
|
},
|
|
{
|
|
"name": "Red apple gala",
|
|
"id": "red-apple-gala",
|
|
"_federation": {
|
|
"indexUid": "fruits",
|
|
"queriesPosition": 1,
|
|
"weightedRankingScore": 0.0
|
|
},
|
|
"_rankingScore": 0.953042328042328
|
|
}
|
|
],
|
|
"processingTimeMs": "[time]",
|
|
"limit": 20,
|
|
"offset": 0,
|
|
"estimatedTotalHits": 3
|
|
}
|
|
"###);
|
|
}
|
|
|
|
#[actix_rt::test]
|
|
async fn federation_federated_contains_pagination() {
|
|
let server = Server::new().await;
|
|
|
|
let index = server.index("fruits");
|
|
|
|
let documents = FRUITS_DOCUMENTS.clone();
|
|
let (value, _) = index.add_documents(documents, None).await;
|
|
index.wait_task(value.uid()).await;
|
|
|
|
// fail when a federated query contains "limit"
|
|
let (response, code) = server
|
|
.multi_search(json!({"federation": {}, "queries": [
|
|
{"indexUid" : "fruits", "q": "apple red"},
|
|
{"indexUid": "fruits", "q": "apple red", "limit": 5},
|
|
]}))
|
|
.await;
|
|
snapshot!(code, @"400 Bad Request");
|
|
insta::assert_json_snapshot!(response, { ".processingTimeMs" => "[time]" }, @r###"
|
|
{
|
|
"message": "Inside `.queries[1]`: Using pagination options is not allowed in federated queries.\n - Hint: remove `limit` from query #1 or remove `federation` from the request\n - Hint: pass `federation.limit` and `federation.offset` for pagination in federated search",
|
|
"code": "invalid_multi_search_query_pagination",
|
|
"type": "invalid_request",
|
|
"link": "https://docs.meilisearch.com/errors#invalid_multi_search_query_pagination"
|
|
}
|
|
"###);
|
|
// fail when a federated query contains "offset"
|
|
let (response, code) = server
|
|
.multi_search(json!({"federation": {}, "queries": [
|
|
{"indexUid" : "fruits", "q": "apple red"},
|
|
{"indexUid": "fruits", "q": "apple red", "offset": 5},
|
|
]}))
|
|
.await;
|
|
snapshot!(code, @"400 Bad Request");
|
|
insta::assert_json_snapshot!(response, { ".processingTimeMs" => "[time]" }, @r###"
|
|
{
|
|
"message": "Inside `.queries[1]`: Using pagination options is not allowed in federated queries.\n - Hint: remove `offset` from query #1 or remove `federation` from the request\n - Hint: pass `federation.limit` and `federation.offset` for pagination in federated search",
|
|
"code": "invalid_multi_search_query_pagination",
|
|
"type": "invalid_request",
|
|
"link": "https://docs.meilisearch.com/errors#invalid_multi_search_query_pagination"
|
|
}
|
|
"###);
|
|
// fail when a federated query contains "page"
|
|
let (response, code) = server
|
|
.multi_search(json!({"federation": {}, "queries": [
|
|
{"indexUid" : "fruits", "q": "apple red"},
|
|
{"indexUid": "fruits", "q": "apple red", "page": 2},
|
|
]}))
|
|
.await;
|
|
snapshot!(code, @"400 Bad Request");
|
|
insta::assert_json_snapshot!(response, { ".processingTimeMs" => "[time]" }, @r###"
|
|
{
|
|
"message": "Inside `.queries[1]`: Using pagination options is not allowed in federated queries.\n - Hint: remove `page` from query #1 or remove `federation` from the request\n - Hint: pass `federation.limit` and `federation.offset` for pagination in federated search",
|
|
"code": "invalid_multi_search_query_pagination",
|
|
"type": "invalid_request",
|
|
"link": "https://docs.meilisearch.com/errors#invalid_multi_search_query_pagination"
|
|
}
|
|
"###);
|
|
// fail when a federated query contains "hitsPerPage"
|
|
let (response, code) = server
|
|
.multi_search(json!({"federation": {}, "queries": [
|
|
{"indexUid" : "fruits", "q": "apple red"},
|
|
{"indexUid": "fruits", "q": "apple red", "hitsPerPage": 5},
|
|
]}))
|
|
.await;
|
|
snapshot!(code, @"400 Bad Request");
|
|
insta::assert_json_snapshot!(response, { ".processingTimeMs" => "[time]" }, @r###"
|
|
{
|
|
"message": "Inside `.queries[1]`: Using pagination options is not allowed in federated queries.\n - Hint: remove `hitsPerPage` from query #1 or remove `federation` from the request\n - Hint: pass `federation.limit` and `federation.offset` for pagination in federated search",
|
|
"code": "invalid_multi_search_query_pagination",
|
|
"type": "invalid_request",
|
|
"link": "https://docs.meilisearch.com/errors#invalid_multi_search_query_pagination"
|
|
}
|
|
"###);
|
|
}
|
|
|
|
#[actix_rt::test]
|
|
async fn federation_federated_contains_facets() {
|
|
let server = Server::new().await;
|
|
|
|
let index = server.index("fruits");
|
|
|
|
let (value, _) = index
|
|
.update_settings(
|
|
json!({"searchableAttributes": ["name"], "filterableAttributes": ["BOOST"]}),
|
|
)
|
|
.await;
|
|
|
|
index.wait_task(value.uid()).await;
|
|
|
|
let documents = FRUITS_DOCUMENTS.clone();
|
|
let (value, _) = index.add_documents(documents, None).await;
|
|
index.wait_task(value.uid()).await;
|
|
|
|
// empty facets are actually OK
|
|
let (response, code) = server
|
|
.multi_search(json!({"federation": {}, "queries": [
|
|
{"indexUid" : "fruits", "q": "apple red"},
|
|
{"indexUid": "fruits", "q": "apple red", "facets": []},
|
|
]}))
|
|
.await;
|
|
snapshot!(code, @"200 OK");
|
|
insta::assert_json_snapshot!(response, { ".processingTimeMs" => "[time]" }, @r###"
|
|
{
|
|
"hits": [
|
|
{
|
|
"name": "Red apple gala",
|
|
"id": "red-apple-gala",
|
|
"_federation": {
|
|
"indexUid": "fruits",
|
|
"queriesPosition": 0,
|
|
"weightedRankingScore": 0.953042328042328
|
|
}
|
|
},
|
|
{
|
|
"name": "Exclusive sale: Red delicious apple",
|
|
"id": "red-delicious-boosted",
|
|
"BOOST": true,
|
|
"_federation": {
|
|
"indexUid": "fruits",
|
|
"queriesPosition": 0,
|
|
"weightedRankingScore": 0.9093915343915344
|
|
}
|
|
},
|
|
{
|
|
"name": "Exclusive sale: green apple",
|
|
"id": "green-apple-boosted",
|
|
"BOOST": true,
|
|
"_federation": {
|
|
"indexUid": "fruits",
|
|
"queriesPosition": 0,
|
|
"weightedRankingScore": 0.4393939393939394
|
|
}
|
|
}
|
|
],
|
|
"processingTimeMs": "[time]",
|
|
"limit": 20,
|
|
"offset": 0,
|
|
"estimatedTotalHits": 3
|
|
}
|
|
"###);
|
|
|
|
// fails
|
|
let (response, code) = server
|
|
.multi_search(json!({"federation": {}, "queries": [
|
|
{"indexUid" : "fruits", "q": "apple red"},
|
|
{"indexUid": "fruits", "q": "apple red", "facets": ["BOOSTED"]},
|
|
]}))
|
|
.await;
|
|
snapshot!(code, @"400 Bad Request");
|
|
insta::assert_json_snapshot!(response, { ".processingTimeMs" => "[time]" }, @r###"
|
|
{
|
|
"message": "Inside `.queries[1]`: Using facet options is not allowed in federated queries.\n - Hint: remove `facets` from query #1 or remove `federation` from the request\n - Hint: pass `federation.facetsByIndex.fruits: [\"BOOSTED\"]` for facets in federated search",
|
|
"code": "invalid_multi_search_query_facets",
|
|
"type": "invalid_request",
|
|
"link": "https://docs.meilisearch.com/errors#invalid_multi_search_query_facets"
|
|
}
|
|
"###);
|
|
}
|
|
|
|
#[actix_rt::test]
|
|
async fn federation_non_faceted_for_an_index() {
|
|
let server = Server::new().await;
|
|
|
|
let index = server.index("fruits");
|
|
|
|
let (value, _) = index
|
|
.update_settings(
|
|
json!({"searchableAttributes": ["name"], "filterableAttributes": ["BOOST", "id", "name"]}),
|
|
)
|
|
.await;
|
|
|
|
index.wait_task(value.uid()).await;
|
|
|
|
let index = server.index("fruits-no-name");
|
|
|
|
let (value, _) = index
|
|
.update_settings(
|
|
json!({"searchableAttributes": ["name"], "filterableAttributes": ["BOOST", "id"]}),
|
|
)
|
|
.await;
|
|
|
|
index.wait_task(value.uid()).await;
|
|
|
|
let index = server.index("fruits-no-facets");
|
|
|
|
let (value, _) = index.update_settings(json!({"searchableAttributes": ["name"]})).await;
|
|
|
|
index.wait_task(value.uid()).await;
|
|
|
|
let documents = FRUITS_DOCUMENTS.clone();
|
|
let (value, _) = index.add_documents(documents, None).await;
|
|
index.wait_task(value.uid()).await;
|
|
|
|
// fails
|
|
let (response, code) = server
|
|
.multi_search(json!({"federation": {
|
|
"facetsByIndex": {
|
|
"fruits": ["BOOST", "id", "name"],
|
|
"fruits-no-name": ["BOOST", "id", "name"],
|
|
}
|
|
}, "queries": [
|
|
{"indexUid" : "fruits", "q": "apple red"},
|
|
{"indexUid": "fruits-no-name", "q": "apple red"},
|
|
]}))
|
|
.await;
|
|
snapshot!(code, @"400 Bad Request");
|
|
insta::assert_json_snapshot!(response, { ".processingTimeMs" => "[time]" }, @r###"
|
|
{
|
|
"message": "Inside `.federation.facetsByIndex.fruits-no-name`: Invalid facet distribution, attribute `name` is not filterable. The available filterable attributes are `BOOST, id`.\n - Note: index `fruits-no-name` used in `.queries[1]`",
|
|
"code": "invalid_multi_search_facets",
|
|
"type": "invalid_request",
|
|
"link": "https://docs.meilisearch.com/errors#invalid_multi_search_facets"
|
|
}
|
|
"###);
|
|
|
|
// still fails
|
|
let (response, code) = server
|
|
.multi_search(json!({"federation": {
|
|
"facetsByIndex": {
|
|
"fruits": ["BOOST", "id", "name"],
|
|
"fruits-no-name": ["BOOST", "id", "name"],
|
|
}
|
|
}, "queries": [
|
|
{"indexUid" : "fruits", "q": "apple red"},
|
|
{"indexUid": "fruits", "q": "apple red"},
|
|
]}))
|
|
.await;
|
|
snapshot!(code, @"400 Bad Request");
|
|
insta::assert_json_snapshot!(response, { ".processingTimeMs" => "[time]" }, @r###"
|
|
{
|
|
"message": "Inside `.federation.facetsByIndex.fruits-no-name`: Invalid facet distribution, attribute `name` is not filterable. The available filterable attributes are `BOOST, id`.\n - Note: index `fruits-no-name` is not used in queries",
|
|
"code": "invalid_multi_search_facets",
|
|
"type": "invalid_request",
|
|
"link": "https://docs.meilisearch.com/errors#invalid_multi_search_facets"
|
|
}
|
|
"###);
|
|
|
|
// fails
|
|
let (response, code) = server
|
|
.multi_search(json!({"federation": {
|
|
"facetsByIndex": {
|
|
"fruits": ["BOOST", "id", "name"],
|
|
"fruits-no-name": ["BOOST", "id"],
|
|
"fruits-no-facets": ["BOOST", "id"],
|
|
}
|
|
}, "queries": [
|
|
{"indexUid" : "fruits", "q": "apple red"},
|
|
{"indexUid": "fruits", "q": "apple red"},
|
|
]}))
|
|
.await;
|
|
snapshot!(code, @"400 Bad Request");
|
|
insta::assert_json_snapshot!(response, { ".processingTimeMs" => "[time]" }, @r###"
|
|
{
|
|
"message": "Inside `.federation.facetsByIndex.fruits-no-facets`: Invalid facet distribution, this index does not have configured filterable attributes.\n - Note: index `fruits-no-facets` is not used in queries",
|
|
"code": "invalid_multi_search_facets",
|
|
"type": "invalid_request",
|
|
"link": "https://docs.meilisearch.com/errors#invalid_multi_search_facets"
|
|
}
|
|
"###);
|
|
|
|
// also fails
|
|
let (response, code) = server
|
|
.multi_search(json!({"federation": {
|
|
"facetsByIndex": {
|
|
"zorglub": ["BOOST", "id", "name"],
|
|
"fruits": ["BOOST", "id", "name"],
|
|
}
|
|
}, "queries": [
|
|
{"indexUid" : "fruits", "q": "apple red"},
|
|
{"indexUid": "fruits", "q": "apple red"},
|
|
]}))
|
|
.await;
|
|
snapshot!(code, @"400 Bad Request");
|
|
insta::assert_json_snapshot!(response, { ".processingTimeMs" => "[time]" }, @r###"
|
|
{
|
|
"message": "Inside `.federation.facetsByIndex.zorglub`: Index `zorglub` not found.\n - Note: index `zorglub` is not used in queries",
|
|
"code": "index_not_found",
|
|
"type": "invalid_request",
|
|
"link": "https://docs.meilisearch.com/errors#index_not_found"
|
|
}
|
|
"###);
|
|
}
|
|
|
|
#[actix_rt::test]
|
|
async fn federation_non_federated_contains_federation_option() {
|
|
let server = Server::new().await;
|
|
|
|
let index = server.index("fruits");
|
|
|
|
let documents = FRUITS_DOCUMENTS.clone();
|
|
let (value, _) = index.add_documents(documents, None).await;
|
|
index.wait_task(value.uid()).await;
|
|
|
|
// fail when a non-federated query contains "federationOptions"
|
|
let (response, code) = server
|
|
.multi_search(json!({"queries": [
|
|
{"indexUid" : "fruits", "q": "apple red"},
|
|
{"indexUid": "fruits", "q": "apple red", "federationOptions": {}},
|
|
]}))
|
|
.await;
|
|
snapshot!(code, @"400 Bad Request");
|
|
insta::assert_json_snapshot!(response, { ".processingTimeMs" => "[time]" }, @r###"
|
|
{
|
|
"message": "Inside `.queries[1]`: Using `federationOptions` is not allowed in a non-federated search.\n - Hint: remove `federationOptions` from query #1 or add `federation` to the request.",
|
|
"code": "invalid_multi_search_federation_options",
|
|
"type": "invalid_request",
|
|
"link": "https://docs.meilisearch.com/errors#invalid_multi_search_federation_options"
|
|
}
|
|
"###);
|
|
}
|
|
|
|
#[actix_rt::test]
|
|
async fn federation_vector_single_index() {
|
|
let server = Server::new().await;
|
|
let (_, code) = server
|
|
.set_features(json!({
|
|
"vectorStore": true
|
|
}))
|
|
.await;
|
|
|
|
snapshot!(code, @"200 OK");
|
|
|
|
let index = server.index("vectors");
|
|
|
|
let (value, _) = index
|
|
.update_settings(json!({"embedders": {
|
|
"animal": {
|
|
"source": "userProvided",
|
|
"dimensions": 3
|
|
},
|
|
"sentiment": {
|
|
"source": "userProvided",
|
|
"dimensions": 2
|
|
}
|
|
}}))
|
|
.await;
|
|
index.wait_task(value.uid()).await;
|
|
|
|
let documents = VECTOR_DOCUMENTS.clone();
|
|
let (value, code) = index.add_documents(documents, None).await;
|
|
snapshot!(code, @"202 Accepted");
|
|
index.wait_task(value.uid()).await;
|
|
|
|
// same embedder
|
|
let (response, code) = server
|
|
.multi_search(json!({"federation": {}, "queries": [
|
|
{"indexUid" : "vectors", "vector": [1.0, 0.0, 0.5], "hybrid": {"semanticRatio": 1.0, "embedder": "animal"}},
|
|
{"indexUid": "vectors", "vector": [0.5, 0.5, 0.5], "hybrid": {"semanticRatio": 1.0, "embedder": "animal"}},
|
|
]}))
|
|
.await;
|
|
snapshot!(code, @"200 OK");
|
|
insta::assert_json_snapshot!(response, { ".processingTimeMs" => "[time]", ".**._rankingScore" => "[score]" }, @r###"
|
|
{
|
|
"hits": [
|
|
{
|
|
"id": "B",
|
|
"description": "the kitten scratched the beagle",
|
|
"_federation": {
|
|
"indexUid": "vectors",
|
|
"queriesPosition": 1,
|
|
"weightedRankingScore": 0.9870882034301758
|
|
}
|
|
},
|
|
{
|
|
"id": "D",
|
|
"description": "the little boy pets the puppy",
|
|
"_federation": {
|
|
"indexUid": "vectors",
|
|
"queriesPosition": 0,
|
|
"weightedRankingScore": 0.9728479385375975
|
|
}
|
|
},
|
|
{
|
|
"id": "C",
|
|
"description": "the dog had to stay alone today",
|
|
"_federation": {
|
|
"indexUid": "vectors",
|
|
"queriesPosition": 0,
|
|
"weightedRankingScore": 0.9701486229896544
|
|
}
|
|
},
|
|
{
|
|
"id": "A",
|
|
"description": "the dog barks at the cat",
|
|
"_federation": {
|
|
"indexUid": "vectors",
|
|
"queriesPosition": 1,
|
|
"weightedRankingScore": 0.9191691875457764
|
|
}
|
|
}
|
|
],
|
|
"processingTimeMs": "[time]",
|
|
"limit": 20,
|
|
"offset": 0,
|
|
"estimatedTotalHits": 4,
|
|
"semanticHitCount": 4
|
|
}
|
|
"###);
|
|
|
|
// distinct embedder
|
|
let (response, code) = server
|
|
.multi_search(json!({"federation": {}, "queries": [
|
|
{"indexUid" : "vectors", "vector": [1.0, 0.0, 0.5], "hybrid": {"semanticRatio": 1.0, "embedder": "animal"}},
|
|
// joyful and energetic first
|
|
{"indexUid": "vectors", "vector": [0.8, 0.6], "hybrid": {"semanticRatio": 1.0, "embedder": "sentiment"}},
|
|
]}))
|
|
.await;
|
|
snapshot!(code, @"200 OK");
|
|
insta::assert_json_snapshot!(response, { ".processingTimeMs" => "[time]", ".**._rankingScore" => "[score]" }, @r###"
|
|
{
|
|
"hits": [
|
|
{
|
|
"id": "D",
|
|
"description": "the little boy pets the puppy",
|
|
"_federation": {
|
|
"indexUid": "vectors",
|
|
"queriesPosition": 1,
|
|
"weightedRankingScore": 0.979868710041046
|
|
}
|
|
},
|
|
{
|
|
"id": "C",
|
|
"description": "the dog had to stay alone today",
|
|
"_federation": {
|
|
"indexUid": "vectors",
|
|
"queriesPosition": 0,
|
|
"weightedRankingScore": 0.9701486229896544
|
|
}
|
|
},
|
|
{
|
|
"id": "B",
|
|
"description": "the kitten scratched the beagle",
|
|
"_federation": {
|
|
"indexUid": "vectors",
|
|
"queriesPosition": 0,
|
|
"weightedRankingScore": 0.8601469993591309
|
|
}
|
|
},
|
|
{
|
|
"id": "A",
|
|
"description": "the dog barks at the cat",
|
|
"_federation": {
|
|
"indexUid": "vectors",
|
|
"queriesPosition": 0,
|
|
"weightedRankingScore": 0.8432406187057495
|
|
}
|
|
}
|
|
],
|
|
"processingTimeMs": "[time]",
|
|
"limit": 20,
|
|
"offset": 0,
|
|
"estimatedTotalHits": 4,
|
|
"semanticHitCount": 4
|
|
}
|
|
"###);
|
|
|
|
// hybrid search, distinct embedder
|
|
let (response, code) = server
|
|
.multi_search(json!({"federation": {}, "queries": [
|
|
{"indexUid" : "vectors", "vector": [1.0, 0.0, 0.5], "hybrid": {"semanticRatio": 1.0, "embedder": "animal"}, "showRankingScore": true},
|
|
// joyful and energetic first
|
|
{"indexUid": "vectors", "vector": [0.8, 0.6], "q": "beagle", "hybrid": {"semanticRatio": 1.0, "embedder": "sentiment"},"showRankingScore": true},
|
|
{"indexUid": "vectors", "q": "dog", "showRankingScore": true},
|
|
]}))
|
|
.await;
|
|
snapshot!(code, @"200 OK");
|
|
insta::assert_json_snapshot!(response, { ".processingTimeMs" => "[time]", ".**._rankingScore" => "[score]" }, @r###"
|
|
{
|
|
"hits": [
|
|
{
|
|
"id": "D",
|
|
"description": "the little boy pets the puppy",
|
|
"_federation": {
|
|
"indexUid": "vectors",
|
|
"queriesPosition": 1,
|
|
"weightedRankingScore": 0.979868710041046
|
|
},
|
|
"_rankingScore": "[score]"
|
|
},
|
|
{
|
|
"id": "C",
|
|
"description": "the dog had to stay alone today",
|
|
"_federation": {
|
|
"indexUid": "vectors",
|
|
"queriesPosition": 0,
|
|
"weightedRankingScore": 0.9701486229896544
|
|
},
|
|
"_rankingScore": "[score]"
|
|
},
|
|
{
|
|
"id": "A",
|
|
"description": "the dog barks at the cat",
|
|
"_federation": {
|
|
"indexUid": "vectors",
|
|
"queriesPosition": 2,
|
|
"weightedRankingScore": 0.9242424242424242
|
|
},
|
|
"_rankingScore": "[score]"
|
|
},
|
|
{
|
|
"id": "B",
|
|
"description": "the kitten scratched the beagle",
|
|
"_federation": {
|
|
"indexUid": "vectors",
|
|
"queriesPosition": 0,
|
|
"weightedRankingScore": 0.8601469993591309
|
|
},
|
|
"_rankingScore": "[score]"
|
|
}
|
|
],
|
|
"processingTimeMs": "[time]",
|
|
"limit": 20,
|
|
"offset": 0,
|
|
"estimatedTotalHits": 4,
|
|
"semanticHitCount": 3
|
|
}
|
|
"###);
|
|
}
|
|
|
|
#[actix_rt::test]
|
|
async fn federation_vector_two_indexes() {
|
|
let server = Server::new().await;
|
|
let (_, code) = server
|
|
.set_features(json!({
|
|
"vectorStore": true
|
|
}))
|
|
.await;
|
|
|
|
snapshot!(code, @"200 OK");
|
|
|
|
let index = server.index("vectors-animal");
|
|
|
|
let (value, _) = index
|
|
.update_settings(json!({"embedders": {
|
|
"animal": {
|
|
"source": "userProvided",
|
|
"dimensions": 3
|
|
},
|
|
}}))
|
|
.await;
|
|
index.wait_task(value.uid()).await;
|
|
|
|
let documents = VECTOR_DOCUMENTS.clone();
|
|
let (value, code) = index.add_documents(documents, None).await;
|
|
snapshot!(code, @"202 Accepted");
|
|
index.wait_task(value.uid()).await;
|
|
|
|
let index = server.index("vectors-sentiment");
|
|
|
|
let (value, _) = index
|
|
.update_settings(json!({"embedders": {
|
|
"sentiment": {
|
|
"source": "userProvided",
|
|
"dimensions": 2
|
|
}
|
|
}}))
|
|
.await;
|
|
index.wait_task(value.uid()).await;
|
|
|
|
let documents = VECTOR_DOCUMENTS.clone();
|
|
let (value, code) = index.add_documents(documents, None).await;
|
|
snapshot!(code, @"202 Accepted");
|
|
index.wait_task(value.uid()).await;
|
|
|
|
let (response, code) = server
|
|
.multi_search(json!({"federation": {}, "queries": [
|
|
{"indexUid" : "vectors-animal", "vector": [1.0, 0.0, 0.5], "hybrid": {"semanticRatio": 1.0, "embedder": "animal"}},
|
|
// joyful and energetic first
|
|
{"indexUid": "vectors-sentiment", "vector": [0.8, 0.6], "hybrid": {"semanticRatio": 1.0, "embedder": "sentiment"}},
|
|
{"indexUid": "vectors-sentiment", "q": "dog"},
|
|
]}))
|
|
.await;
|
|
snapshot!(code, @"200 OK");
|
|
insta::assert_json_snapshot!(response, { ".processingTimeMs" => "[time]", ".**._rankingScore" => "[score]" }, @r###"
|
|
{
|
|
"hits": [
|
|
{
|
|
"id": "D",
|
|
"description": "the little boy pets the puppy",
|
|
"_vectors": {
|
|
"animal": [
|
|
0.8,
|
|
0.09,
|
|
0.8
|
|
]
|
|
},
|
|
"_federation": {
|
|
"indexUid": "vectors-sentiment",
|
|
"queriesPosition": 1,
|
|
"weightedRankingScore": 0.979868710041046
|
|
}
|
|
},
|
|
{
|
|
"id": "D",
|
|
"description": "the little boy pets the puppy",
|
|
"_vectors": {
|
|
"sentiment": [
|
|
0.8,
|
|
0.3
|
|
]
|
|
},
|
|
"_federation": {
|
|
"indexUid": "vectors-animal",
|
|
"queriesPosition": 0,
|
|
"weightedRankingScore": 0.9728479385375975
|
|
}
|
|
},
|
|
{
|
|
"id": "C",
|
|
"description": "the dog had to stay alone today",
|
|
"_vectors": {
|
|
"sentiment": [
|
|
-1.0,
|
|
0.1
|
|
]
|
|
},
|
|
"_federation": {
|
|
"indexUid": "vectors-animal",
|
|
"queriesPosition": 0,
|
|
"weightedRankingScore": 0.9701486229896544
|
|
}
|
|
},
|
|
{
|
|
"id": "A",
|
|
"description": "the dog barks at the cat",
|
|
"_vectors": {
|
|
"animal": [
|
|
0.9,
|
|
0.8,
|
|
0.05
|
|
]
|
|
},
|
|
"_federation": {
|
|
"indexUid": "vectors-sentiment",
|
|
"queriesPosition": 2,
|
|
"weightedRankingScore": 0.9242424242424242
|
|
}
|
|
},
|
|
{
|
|
"id": "C",
|
|
"description": "the dog had to stay alone today",
|
|
"_vectors": {
|
|
"animal": [
|
|
0.85,
|
|
0.02,
|
|
0.1
|
|
]
|
|
},
|
|
"_federation": {
|
|
"indexUid": "vectors-sentiment",
|
|
"queriesPosition": 2,
|
|
"weightedRankingScore": 0.9242424242424242
|
|
}
|
|
},
|
|
{
|
|
"id": "B",
|
|
"description": "the kitten scratched the beagle",
|
|
"_vectors": {
|
|
"sentiment": [
|
|
-0.2,
|
|
0.65
|
|
]
|
|
},
|
|
"_federation": {
|
|
"indexUid": "vectors-animal",
|
|
"queriesPosition": 0,
|
|
"weightedRankingScore": 0.8601469993591309
|
|
}
|
|
},
|
|
{
|
|
"id": "A",
|
|
"description": "the dog barks at the cat",
|
|
"_vectors": {
|
|
"sentiment": [
|
|
-0.1,
|
|
0.55
|
|
]
|
|
},
|
|
"_federation": {
|
|
"indexUid": "vectors-animal",
|
|
"queriesPosition": 0,
|
|
"weightedRankingScore": 0.8432406187057495
|
|
}
|
|
},
|
|
{
|
|
"id": "B",
|
|
"description": "the kitten scratched the beagle",
|
|
"_vectors": {
|
|
"animal": [
|
|
0.8,
|
|
0.9,
|
|
0.5
|
|
]
|
|
},
|
|
"_federation": {
|
|
"indexUid": "vectors-sentiment",
|
|
"queriesPosition": 1,
|
|
"weightedRankingScore": 0.6690993905067444
|
|
}
|
|
}
|
|
],
|
|
"processingTimeMs": "[time]",
|
|
"limit": 20,
|
|
"offset": 0,
|
|
"estimatedTotalHits": 8,
|
|
"semanticHitCount": 6
|
|
}
|
|
"###);
|
|
|
|
// hybrid search, distinct embedder
|
|
let (response, code) = server
|
|
.multi_search(json!({"federation": {}, "queries": [
|
|
{"indexUid" : "vectors-animal", "vector": [1.0, 0.0, 0.5], "hybrid": {"semanticRatio": 1.0, "embedder": "animal"}, "showRankingScore": true},
|
|
{"indexUid": "vectors-sentiment", "vector": [-1, 0.6], "q": "beagle", "hybrid": {"semanticRatio": 1.0, "embedder": "sentiment"}, "showRankingScore": true},
|
|
]}))
|
|
.await;
|
|
snapshot!(code, @"200 OK");
|
|
insta::assert_json_snapshot!(response, { ".processingTimeMs" => "[time]", ".**._rankingScore" => "[score]" }, @r###"
|
|
{
|
|
"hits": [
|
|
{
|
|
"id": "D",
|
|
"description": "the little boy pets the puppy",
|
|
"_vectors": {
|
|
"sentiment": [
|
|
0.8,
|
|
0.3
|
|
]
|
|
},
|
|
"_federation": {
|
|
"indexUid": "vectors-animal",
|
|
"queriesPosition": 0,
|
|
"weightedRankingScore": 0.9728479385375975
|
|
},
|
|
"_rankingScore": "[score]"
|
|
},
|
|
{
|
|
"id": "C",
|
|
"description": "the dog had to stay alone today",
|
|
"_vectors": {
|
|
"sentiment": [
|
|
-1.0,
|
|
0.1
|
|
]
|
|
},
|
|
"_federation": {
|
|
"indexUid": "vectors-animal",
|
|
"queriesPosition": 0,
|
|
"weightedRankingScore": 0.9701486229896544
|
|
},
|
|
"_rankingScore": "[score]"
|
|
},
|
|
{
|
|
"id": "C",
|
|
"description": "the dog had to stay alone today",
|
|
"_vectors": {
|
|
"animal": [
|
|
0.85,
|
|
0.02,
|
|
0.1
|
|
]
|
|
},
|
|
"_federation": {
|
|
"indexUid": "vectors-sentiment",
|
|
"queriesPosition": 1,
|
|
"weightedRankingScore": 0.9522157907485962
|
|
},
|
|
"_rankingScore": "[score]"
|
|
},
|
|
{
|
|
"id": "B",
|
|
"description": "the kitten scratched the beagle",
|
|
"_vectors": {
|
|
"animal": [
|
|
0.8,
|
|
0.9,
|
|
0.5
|
|
]
|
|
},
|
|
"_federation": {
|
|
"indexUid": "vectors-sentiment",
|
|
"queriesPosition": 1,
|
|
"weightedRankingScore": 0.8719604015350342
|
|
},
|
|
"_rankingScore": "[score]"
|
|
},
|
|
{
|
|
"id": "B",
|
|
"description": "the kitten scratched the beagle",
|
|
"_vectors": {
|
|
"sentiment": [
|
|
-0.2,
|
|
0.65
|
|
]
|
|
},
|
|
"_federation": {
|
|
"indexUid": "vectors-animal",
|
|
"queriesPosition": 0,
|
|
"weightedRankingScore": 0.8601469993591309
|
|
},
|
|
"_rankingScore": "[score]"
|
|
},
|
|
{
|
|
"id": "A",
|
|
"description": "the dog barks at the cat",
|
|
"_vectors": {
|
|
"sentiment": [
|
|
-0.1,
|
|
0.55
|
|
]
|
|
},
|
|
"_federation": {
|
|
"indexUid": "vectors-animal",
|
|
"queriesPosition": 0,
|
|
"weightedRankingScore": 0.8432406187057495
|
|
},
|
|
"_rankingScore": "[score]"
|
|
},
|
|
{
|
|
"id": "A",
|
|
"description": "the dog barks at the cat",
|
|
"_vectors": {
|
|
"animal": [
|
|
0.9,
|
|
0.8,
|
|
0.05
|
|
]
|
|
},
|
|
"_federation": {
|
|
"indexUid": "vectors-sentiment",
|
|
"queriesPosition": 1,
|
|
"weightedRankingScore": 0.8297949433326721
|
|
},
|
|
"_rankingScore": "[score]"
|
|
},
|
|
{
|
|
"id": "D",
|
|
"description": "the little boy pets the puppy",
|
|
"_vectors": {
|
|
"animal": [
|
|
0.8,
|
|
0.09,
|
|
0.8
|
|
]
|
|
},
|
|
"_federation": {
|
|
"indexUid": "vectors-sentiment",
|
|
"queriesPosition": 1,
|
|
"weightedRankingScore": 0.18887794017791748
|
|
},
|
|
"_rankingScore": "[score]"
|
|
}
|
|
],
|
|
"processingTimeMs": "[time]",
|
|
"limit": 20,
|
|
"offset": 0,
|
|
"estimatedTotalHits": 8,
|
|
"semanticHitCount": 8
|
|
}
|
|
"###);
|
|
}
|
|
|
|
#[actix_rt::test]
|
|
async fn federation_facets_different_indexes_same_facet() {
|
|
let server = Server::new().await;
|
|
|
|
let index = server.index("movies");
|
|
|
|
let documents = DOCUMENTS.clone();
|
|
let (value, _) = index.add_documents(documents, None).await;
|
|
index.wait_task(value.uid()).await;
|
|
|
|
let (value, _) = index
|
|
.update_settings(json!({
|
|
"sortableAttributes": ["title"],
|
|
"filterableAttributes": ["title", "color"],
|
|
"rankingRules": [
|
|
"sort",
|
|
"words",
|
|
"typo",
|
|
"proximity",
|
|
"attribute",
|
|
"exactness"
|
|
]
|
|
}))
|
|
.await;
|
|
index.wait_task(value.uid()).await;
|
|
|
|
let index = server.index("batman");
|
|
|
|
let documents = SCORE_DOCUMENTS.clone();
|
|
let (value, _) = index.add_documents(documents, None).await;
|
|
index.wait_task(value.uid()).await;
|
|
|
|
let (value, _) = index
|
|
.update_settings(json!({
|
|
"sortableAttributes": ["title"],
|
|
"filterableAttributes": ["title"],
|
|
"rankingRules": [
|
|
"sort",
|
|
"words",
|
|
"typo",
|
|
"proximity",
|
|
"attribute",
|
|
"exactness"
|
|
]
|
|
}))
|
|
.await;
|
|
index.wait_task(value.uid()).await;
|
|
|
|
let index = server.index("batman-2");
|
|
|
|
let documents = SCORE_DOCUMENTS.clone();
|
|
let (value, _) = index.add_documents(documents, None).await;
|
|
index.wait_task(value.uid()).await;
|
|
|
|
let (value, _) = index
|
|
.update_settings(json!({
|
|
"sortableAttributes": ["title"],
|
|
"filterableAttributes": ["title"],
|
|
"rankingRules": [
|
|
"sort",
|
|
"words",
|
|
"typo",
|
|
"proximity",
|
|
"attribute",
|
|
"exactness"
|
|
]
|
|
}))
|
|
.await;
|
|
index.wait_task(value.uid()).await;
|
|
|
|
// return titles ordered accross indexes
|
|
let (response, code) = server
|
|
.multi_search(json!({"federation": {
|
|
"facetsByIndex": {
|
|
"movies": ["title", "color"],
|
|
"batman": ["title"],
|
|
"batman-2": ["title"],
|
|
}
|
|
}, "queries": [
|
|
{"indexUid" : "movies", "q": "", "sort": ["title:asc"], "attributesToRetrieve": ["title"] },
|
|
{"indexUid" : "batman", "q": "", "sort": ["title:asc"], "attributesToRetrieve": ["title"] },
|
|
{"indexUid" : "batman-2", "q": "", "sort": ["title:asc"], "attributesToRetrieve": ["title"] },
|
|
]}))
|
|
.await;
|
|
snapshot!(code, @"200 OK");
|
|
insta::assert_json_snapshot!(response, { ".processingTimeMs" => "[time]" }, @r###"
|
|
{
|
|
"hits": [
|
|
{
|
|
"title": "Badman",
|
|
"_federation": {
|
|
"indexUid": "batman",
|
|
"queriesPosition": 1,
|
|
"weightedRankingScore": 1.0
|
|
}
|
|
},
|
|
{
|
|
"title": "Badman",
|
|
"_federation": {
|
|
"indexUid": "batman-2",
|
|
"queriesPosition": 2,
|
|
"weightedRankingScore": 1.0
|
|
}
|
|
},
|
|
{
|
|
"title": "Batman",
|
|
"_federation": {
|
|
"indexUid": "batman",
|
|
"queriesPosition": 1,
|
|
"weightedRankingScore": 1.0
|
|
}
|
|
},
|
|
{
|
|
"title": "Batman",
|
|
"_federation": {
|
|
"indexUid": "batman-2",
|
|
"queriesPosition": 2,
|
|
"weightedRankingScore": 1.0
|
|
}
|
|
},
|
|
{
|
|
"title": "Batman Returns",
|
|
"_federation": {
|
|
"indexUid": "batman",
|
|
"queriesPosition": 1,
|
|
"weightedRankingScore": 1.0
|
|
}
|
|
},
|
|
{
|
|
"title": "Batman Returns",
|
|
"_federation": {
|
|
"indexUid": "batman-2",
|
|
"queriesPosition": 2,
|
|
"weightedRankingScore": 1.0
|
|
}
|
|
},
|
|
{
|
|
"title": "Batman the dark knight returns: Part 1",
|
|
"_federation": {
|
|
"indexUid": "batman",
|
|
"queriesPosition": 1,
|
|
"weightedRankingScore": 1.0
|
|
}
|
|
},
|
|
{
|
|
"title": "Batman the dark knight returns: Part 1",
|
|
"_federation": {
|
|
"indexUid": "batman-2",
|
|
"queriesPosition": 2,
|
|
"weightedRankingScore": 1.0
|
|
}
|
|
},
|
|
{
|
|
"title": "Batman the dark knight returns: Part 2",
|
|
"_federation": {
|
|
"indexUid": "batman",
|
|
"queriesPosition": 1,
|
|
"weightedRankingScore": 1.0
|
|
}
|
|
},
|
|
{
|
|
"title": "Batman the dark knight returns: Part 2",
|
|
"_federation": {
|
|
"indexUid": "batman-2",
|
|
"queriesPosition": 2,
|
|
"weightedRankingScore": 1.0
|
|
}
|
|
},
|
|
{
|
|
"title": "Captain Marvel",
|
|
"_federation": {
|
|
"indexUid": "movies",
|
|
"queriesPosition": 0,
|
|
"weightedRankingScore": 1.0
|
|
}
|
|
},
|
|
{
|
|
"title": "Escape Room",
|
|
"_federation": {
|
|
"indexUid": "movies",
|
|
"queriesPosition": 0,
|
|
"weightedRankingScore": 1.0
|
|
}
|
|
},
|
|
{
|
|
"title": "Gläss",
|
|
"_federation": {
|
|
"indexUid": "movies",
|
|
"queriesPosition": 0,
|
|
"weightedRankingScore": 1.0
|
|
}
|
|
},
|
|
{
|
|
"title": "How to Train Your Dragon: The Hidden World",
|
|
"_federation": {
|
|
"indexUid": "movies",
|
|
"queriesPosition": 0,
|
|
"weightedRankingScore": 1.0
|
|
}
|
|
},
|
|
{
|
|
"title": "Shazam!",
|
|
"_federation": {
|
|
"indexUid": "movies",
|
|
"queriesPosition": 0,
|
|
"weightedRankingScore": 1.0
|
|
}
|
|
}
|
|
],
|
|
"processingTimeMs": "[time]",
|
|
"limit": 20,
|
|
"offset": 0,
|
|
"estimatedTotalHits": 15,
|
|
"facetsByIndex": {
|
|
"batman": {
|
|
"distribution": {
|
|
"title": {
|
|
"Badman": 1,
|
|
"Batman": 1,
|
|
"Batman Returns": 1,
|
|
"Batman the dark knight returns: Part 1": 1,
|
|
"Batman the dark knight returns: Part 2": 1
|
|
}
|
|
},
|
|
"stats": {}
|
|
},
|
|
"batman-2": {
|
|
"distribution": {
|
|
"title": {
|
|
"Badman": 1,
|
|
"Batman": 1,
|
|
"Batman Returns": 1,
|
|
"Batman the dark knight returns: Part 1": 1,
|
|
"Batman the dark knight returns: Part 2": 1
|
|
}
|
|
},
|
|
"stats": {}
|
|
},
|
|
"movies": {
|
|
"distribution": {
|
|
"color": {
|
|
"blue": 3,
|
|
"green": 2,
|
|
"red": 3,
|
|
"yellow": 2
|
|
},
|
|
"title": {
|
|
"Captain Marvel": 1,
|
|
"Escape Room": 1,
|
|
"Gläss": 1,
|
|
"How to Train Your Dragon: The Hidden World": 1,
|
|
"Shazam!": 1
|
|
}
|
|
},
|
|
"stats": {}
|
|
}
|
|
}
|
|
}
|
|
"###);
|
|
|
|
let (response, code) = server
|
|
.multi_search(json!({"federation": {
|
|
"facetsByIndex": {
|
|
"movies": ["title"],
|
|
"batman": ["title"],
|
|
"batman-2": ["title"]
|
|
},
|
|
"mergeFacets": {}
|
|
}, "queries": [
|
|
{"indexUid" : "movies", "q": "", "sort": ["title:asc"], "attributesToRetrieve": ["title"] },
|
|
{"indexUid" : "batman", "q": "", "sort": ["title:asc"], "attributesToRetrieve": ["title"] },
|
|
{"indexUid" : "batman-2", "q": "", "sort": ["title:asc"], "attributesToRetrieve": ["title"] },
|
|
]}))
|
|
.await;
|
|
snapshot!(code, @"200 OK");
|
|
insta::assert_json_snapshot!(response, { ".processingTimeMs" => "[time]" }, @r###"
|
|
{
|
|
"hits": [
|
|
{
|
|
"title": "Badman",
|
|
"_federation": {
|
|
"indexUid": "batman",
|
|
"queriesPosition": 1,
|
|
"weightedRankingScore": 1.0
|
|
}
|
|
},
|
|
{
|
|
"title": "Badman",
|
|
"_federation": {
|
|
"indexUid": "batman-2",
|
|
"queriesPosition": 2,
|
|
"weightedRankingScore": 1.0
|
|
}
|
|
},
|
|
{
|
|
"title": "Batman",
|
|
"_federation": {
|
|
"indexUid": "batman",
|
|
"queriesPosition": 1,
|
|
"weightedRankingScore": 1.0
|
|
}
|
|
},
|
|
{
|
|
"title": "Batman",
|
|
"_federation": {
|
|
"indexUid": "batman-2",
|
|
"queriesPosition": 2,
|
|
"weightedRankingScore": 1.0
|
|
}
|
|
},
|
|
{
|
|
"title": "Batman Returns",
|
|
"_federation": {
|
|
"indexUid": "batman",
|
|
"queriesPosition": 1,
|
|
"weightedRankingScore": 1.0
|
|
}
|
|
},
|
|
{
|
|
"title": "Batman Returns",
|
|
"_federation": {
|
|
"indexUid": "batman-2",
|
|
"queriesPosition": 2,
|
|
"weightedRankingScore": 1.0
|
|
}
|
|
},
|
|
{
|
|
"title": "Batman the dark knight returns: Part 1",
|
|
"_federation": {
|
|
"indexUid": "batman",
|
|
"queriesPosition": 1,
|
|
"weightedRankingScore": 1.0
|
|
}
|
|
},
|
|
{
|
|
"title": "Batman the dark knight returns: Part 1",
|
|
"_federation": {
|
|
"indexUid": "batman-2",
|
|
"queriesPosition": 2,
|
|
"weightedRankingScore": 1.0
|
|
}
|
|
},
|
|
{
|
|
"title": "Batman the dark knight returns: Part 2",
|
|
"_federation": {
|
|
"indexUid": "batman",
|
|
"queriesPosition": 1,
|
|
"weightedRankingScore": 1.0
|
|
}
|
|
},
|
|
{
|
|
"title": "Batman the dark knight returns: Part 2",
|
|
"_federation": {
|
|
"indexUid": "batman-2",
|
|
"queriesPosition": 2,
|
|
"weightedRankingScore": 1.0
|
|
}
|
|
},
|
|
{
|
|
"title": "Captain Marvel",
|
|
"_federation": {
|
|
"indexUid": "movies",
|
|
"queriesPosition": 0,
|
|
"weightedRankingScore": 1.0
|
|
}
|
|
},
|
|
{
|
|
"title": "Escape Room",
|
|
"_federation": {
|
|
"indexUid": "movies",
|
|
"queriesPosition": 0,
|
|
"weightedRankingScore": 1.0
|
|
}
|
|
},
|
|
{
|
|
"title": "Gläss",
|
|
"_federation": {
|
|
"indexUid": "movies",
|
|
"queriesPosition": 0,
|
|
"weightedRankingScore": 1.0
|
|
}
|
|
},
|
|
{
|
|
"title": "How to Train Your Dragon: The Hidden World",
|
|
"_federation": {
|
|
"indexUid": "movies",
|
|
"queriesPosition": 0,
|
|
"weightedRankingScore": 1.0
|
|
}
|
|
},
|
|
{
|
|
"title": "Shazam!",
|
|
"_federation": {
|
|
"indexUid": "movies",
|
|
"queriesPosition": 0,
|
|
"weightedRankingScore": 1.0
|
|
}
|
|
}
|
|
],
|
|
"processingTimeMs": "[time]",
|
|
"limit": 20,
|
|
"offset": 0,
|
|
"estimatedTotalHits": 15,
|
|
"facetDistribution": {
|
|
"title": {
|
|
"Badman": 2,
|
|
"Batman": 2,
|
|
"Batman Returns": 2,
|
|
"Batman the dark knight returns: Part 1": 2,
|
|
"Batman the dark knight returns: Part 2": 2,
|
|
"Captain Marvel": 1,
|
|
"Escape Room": 1,
|
|
"Gläss": 1,
|
|
"How to Train Your Dragon: The Hidden World": 1,
|
|
"Shazam!": 1
|
|
}
|
|
},
|
|
"facetStats": {}
|
|
}
|
|
"###);
|
|
|
|
// mix and match query: will be sorted across indexes
|
|
let (response, code) = server
|
|
.multi_search(json!({"federation": {
|
|
"facetsByIndex": {
|
|
"movies": [],
|
|
"batman": ["title"],
|
|
"batman-2": ["title"]
|
|
}
|
|
}, "queries": [
|
|
{"indexUid" : "batman", "q": "badman returns", "sort": ["title:desc"], "attributesToRetrieve": ["title"] },
|
|
{"indexUid" : "batman-2", "q": "badman returns", "sort": ["title:desc"], "attributesToRetrieve": ["title"] },
|
|
{"indexUid" : "movies", "q": "captain", "sort": ["title:desc"], "attributesToRetrieve": ["title"] },
|
|
{"indexUid" : "batman", "q": "the bat", "sort": ["title:desc"], "attributesToRetrieve": ["title"] },
|
|
]}))
|
|
.await;
|
|
snapshot!(code, @"200 OK");
|
|
insta::assert_json_snapshot!(response, { ".processingTimeMs" => "[time]" }, @r###"
|
|
{
|
|
"hits": [
|
|
{
|
|
"title": "Captain Marvel",
|
|
"_federation": {
|
|
"indexUid": "movies",
|
|
"queriesPosition": 2,
|
|
"weightedRankingScore": 0.9848484848484848
|
|
}
|
|
},
|
|
{
|
|
"title": "Batman the dark knight returns: Part 2",
|
|
"_federation": {
|
|
"indexUid": "batman",
|
|
"queriesPosition": 3,
|
|
"weightedRankingScore": 0.9528218694885362
|
|
}
|
|
},
|
|
{
|
|
"title": "Batman the dark knight returns: Part 2",
|
|
"_federation": {
|
|
"indexUid": "batman-2",
|
|
"queriesPosition": 1,
|
|
"weightedRankingScore": 0.7028218694885362
|
|
}
|
|
},
|
|
{
|
|
"title": "Batman the dark knight returns: Part 1",
|
|
"_federation": {
|
|
"indexUid": "batman",
|
|
"queriesPosition": 3,
|
|
"weightedRankingScore": 0.9528218694885362
|
|
}
|
|
},
|
|
{
|
|
"title": "Batman the dark knight returns: Part 1",
|
|
"_federation": {
|
|
"indexUid": "batman-2",
|
|
"queriesPosition": 1,
|
|
"weightedRankingScore": 0.7028218694885362
|
|
}
|
|
},
|
|
{
|
|
"title": "Batman Returns",
|
|
"_federation": {
|
|
"indexUid": "batman",
|
|
"queriesPosition": 0,
|
|
"weightedRankingScore": 0.8317901234567902
|
|
}
|
|
},
|
|
{
|
|
"title": "Batman Returns",
|
|
"_federation": {
|
|
"indexUid": "batman-2",
|
|
"queriesPosition": 1,
|
|
"weightedRankingScore": 0.8317901234567902
|
|
}
|
|
},
|
|
{
|
|
"title": "Batman",
|
|
"_federation": {
|
|
"indexUid": "batman",
|
|
"queriesPosition": 0,
|
|
"weightedRankingScore": 0.23106060606060605
|
|
}
|
|
},
|
|
{
|
|
"title": "Batman",
|
|
"_federation": {
|
|
"indexUid": "batman-2",
|
|
"queriesPosition": 1,
|
|
"weightedRankingScore": 0.23106060606060605
|
|
}
|
|
},
|
|
{
|
|
"title": "Badman",
|
|
"_federation": {
|
|
"indexUid": "batman",
|
|
"queriesPosition": 0,
|
|
"weightedRankingScore": 0.5
|
|
}
|
|
},
|
|
{
|
|
"title": "Badman",
|
|
"_federation": {
|
|
"indexUid": "batman-2",
|
|
"queriesPosition": 1,
|
|
"weightedRankingScore": 0.5
|
|
}
|
|
}
|
|
],
|
|
"processingTimeMs": "[time]",
|
|
"limit": 20,
|
|
"offset": 0,
|
|
"estimatedTotalHits": 11,
|
|
"facetsByIndex": {
|
|
"batman": {
|
|
"distribution": {
|
|
"title": {
|
|
"Badman": 1,
|
|
"Batman": 1,
|
|
"Batman Returns": 1,
|
|
"Batman the dark knight returns: Part 1": 1,
|
|
"Batman the dark knight returns: Part 2": 1
|
|
}
|
|
},
|
|
"stats": {}
|
|
},
|
|
"batman-2": {
|
|
"distribution": {
|
|
"title": {
|
|
"Badman": 1,
|
|
"Batman": 1,
|
|
"Batman Returns": 1,
|
|
"Batman the dark knight returns: Part 1": 1,
|
|
"Batman the dark knight returns: Part 2": 1
|
|
}
|
|
},
|
|
"stats": {}
|
|
},
|
|
"movies": {
|
|
"distribution": {},
|
|
"stats": {}
|
|
}
|
|
}
|
|
}
|
|
"###);
|
|
}
|
|
|
|
#[actix_rt::test]
|
|
async fn federation_facets_same_indexes() {
|
|
let server = Server::new().await;
|
|
|
|
let index = server.index("doggos");
|
|
|
|
let documents = NESTED_DOCUMENTS.clone();
|
|
let (value, _) = index.add_documents(documents, None).await;
|
|
index.wait_task(value.uid()).await;
|
|
|
|
let (value, _) = index
|
|
.update_settings(json!({
|
|
"filterableAttributes": ["father", "mother", "doggos.age"],
|
|
"rankingRules": [
|
|
"sort",
|
|
"words",
|
|
"typo",
|
|
"proximity",
|
|
"attribute",
|
|
"exactness"
|
|
]
|
|
}))
|
|
.await;
|
|
index.wait_task(value.uid()).await;
|
|
|
|
let index = server.index("doggos-2");
|
|
|
|
let documents = NESTED_DOCUMENTS.clone();
|
|
let (value, _) = index.add_documents(documents, None).await;
|
|
index.wait_task(value.uid()).await;
|
|
|
|
let (value, _) = index
|
|
.update_settings(json!({
|
|
"filterableAttributes": ["father", "mother", "doggos.age"],
|
|
"rankingRules": [
|
|
"sort",
|
|
"words",
|
|
"typo",
|
|
"proximity",
|
|
"attribute",
|
|
"exactness"
|
|
]
|
|
}))
|
|
.await;
|
|
index.wait_task(value.uid()).await;
|
|
|
|
let (response, code) = server
|
|
.multi_search(json!({"federation": {
|
|
"facetsByIndex": {
|
|
"doggos": ["father", "mother", "doggos.age"]
|
|
}
|
|
}, "queries": [
|
|
{"indexUid" : "doggos", "q": "je", "attributesToRetrieve": ["id"] },
|
|
{"indexUid" : "doggos", "q": "michel", "attributesToRetrieve": ["id"] },
|
|
]}))
|
|
.await;
|
|
snapshot!(code, @"200 OK");
|
|
insta::assert_json_snapshot!(response, { ".processingTimeMs" => "[time]" }, @r###"
|
|
{
|
|
"hits": [
|
|
{
|
|
"id": 852,
|
|
"_federation": {
|
|
"indexUid": "doggos",
|
|
"queriesPosition": 0,
|
|
"weightedRankingScore": 0.9621212121212122
|
|
}
|
|
},
|
|
{
|
|
"id": 951,
|
|
"_federation": {
|
|
"indexUid": "doggos",
|
|
"queriesPosition": 0,
|
|
"weightedRankingScore": 0.9621212121212122
|
|
}
|
|
},
|
|
{
|
|
"id": 750,
|
|
"_federation": {
|
|
"indexUid": "doggos",
|
|
"queriesPosition": 1,
|
|
"weightedRankingScore": 0.9621212121212122
|
|
}
|
|
}
|
|
],
|
|
"processingTimeMs": "[time]",
|
|
"limit": 20,
|
|
"offset": 0,
|
|
"estimatedTotalHits": 3,
|
|
"facetsByIndex": {
|
|
"doggos": {
|
|
"distribution": {
|
|
"doggos.age": {
|
|
"2": 1,
|
|
"4": 1,
|
|
"5": 1,
|
|
"6": 1
|
|
},
|
|
"father": {
|
|
"jean": 1,
|
|
"jean-baptiste": 1,
|
|
"romain": 1
|
|
},
|
|
"mother": {
|
|
"michelle": 2,
|
|
"sophie": 1
|
|
}
|
|
},
|
|
"stats": {
|
|
"doggos.age": {
|
|
"min": 2.0,
|
|
"max": 6.0
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
"###);
|
|
|
|
let (response, code) = server
|
|
.multi_search(json!({"federation": {
|
|
"facetsByIndex": {
|
|
"doggos": ["father", "mother", "doggos.age"],
|
|
"doggos-2": ["father", "mother", "doggos.age"]
|
|
}
|
|
}, "queries": [
|
|
{"indexUid" : "doggos", "q": "je", "attributesToRetrieve": ["id"] },
|
|
{"indexUid" : "doggos-2", "q": "michel", "attributesToRetrieve": ["id"] },
|
|
]}))
|
|
.await;
|
|
snapshot!(code, @"200 OK");
|
|
insta::assert_json_snapshot!(response, { ".processingTimeMs" => "[time]" }, @r###"
|
|
{
|
|
"hits": [
|
|
{
|
|
"id": 852,
|
|
"_federation": {
|
|
"indexUid": "doggos",
|
|
"queriesPosition": 0,
|
|
"weightedRankingScore": 0.9621212121212122
|
|
}
|
|
},
|
|
{
|
|
"id": 951,
|
|
"_federation": {
|
|
"indexUid": "doggos",
|
|
"queriesPosition": 0,
|
|
"weightedRankingScore": 0.9621212121212122
|
|
}
|
|
},
|
|
{
|
|
"id": 852,
|
|
"_federation": {
|
|
"indexUid": "doggos-2",
|
|
"queriesPosition": 1,
|
|
"weightedRankingScore": 0.9621212121212122
|
|
}
|
|
},
|
|
{
|
|
"id": 750,
|
|
"_federation": {
|
|
"indexUid": "doggos-2",
|
|
"queriesPosition": 1,
|
|
"weightedRankingScore": 0.9621212121212122
|
|
}
|
|
}
|
|
],
|
|
"processingTimeMs": "[time]",
|
|
"limit": 20,
|
|
"offset": 0,
|
|
"estimatedTotalHits": 4,
|
|
"facetsByIndex": {
|
|
"doggos": {
|
|
"distribution": {
|
|
"doggos.age": {
|
|
"2": 1,
|
|
"4": 1,
|
|
"5": 1,
|
|
"6": 1
|
|
},
|
|
"father": {
|
|
"jean": 1,
|
|
"jean-baptiste": 1
|
|
},
|
|
"mother": {
|
|
"michelle": 1,
|
|
"sophie": 1
|
|
}
|
|
},
|
|
"stats": {
|
|
"doggos.age": {
|
|
"min": 2.0,
|
|
"max": 6.0
|
|
}
|
|
}
|
|
},
|
|
"doggos-2": {
|
|
"distribution": {
|
|
"doggos.age": {
|
|
"2": 1,
|
|
"4": 1
|
|
},
|
|
"father": {
|
|
"jean": 1,
|
|
"romain": 1
|
|
},
|
|
"mother": {
|
|
"michelle": 2
|
|
}
|
|
},
|
|
"stats": {
|
|
"doggos.age": {
|
|
"min": 2.0,
|
|
"max": 4.0
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
"###);
|
|
|
|
let (response, code) = server
|
|
.multi_search(json!({"federation": {
|
|
"facetsByIndex": {
|
|
"doggos": ["father", "mother", "doggos.age"],
|
|
"doggos-2": ["father", "mother", "doggos.age"]
|
|
},
|
|
"mergeFacets": {},
|
|
}, "queries": [
|
|
{"indexUid" : "doggos", "q": "je", "attributesToRetrieve": ["id"] },
|
|
{"indexUid" : "doggos-2", "q": "michel", "attributesToRetrieve": ["id"] },
|
|
]}))
|
|
.await;
|
|
snapshot!(code, @"200 OK");
|
|
insta::assert_json_snapshot!(response, { ".processingTimeMs" => "[time]" }, @r###"
|
|
{
|
|
"hits": [
|
|
{
|
|
"id": 852,
|
|
"_federation": {
|
|
"indexUid": "doggos",
|
|
"queriesPosition": 0,
|
|
"weightedRankingScore": 0.9621212121212122
|
|
}
|
|
},
|
|
{
|
|
"id": 951,
|
|
"_federation": {
|
|
"indexUid": "doggos",
|
|
"queriesPosition": 0,
|
|
"weightedRankingScore": 0.9621212121212122
|
|
}
|
|
},
|
|
{
|
|
"id": 852,
|
|
"_federation": {
|
|
"indexUid": "doggos-2",
|
|
"queriesPosition": 1,
|
|
"weightedRankingScore": 0.9621212121212122
|
|
}
|
|
},
|
|
{
|
|
"id": 750,
|
|
"_federation": {
|
|
"indexUid": "doggos-2",
|
|
"queriesPosition": 1,
|
|
"weightedRankingScore": 0.9621212121212122
|
|
}
|
|
}
|
|
],
|
|
"processingTimeMs": "[time]",
|
|
"limit": 20,
|
|
"offset": 0,
|
|
"estimatedTotalHits": 4,
|
|
"facetDistribution": {
|
|
"doggos.age": {
|
|
"2": 2,
|
|
"4": 2,
|
|
"5": 1,
|
|
"6": 1
|
|
},
|
|
"father": {
|
|
"jean": 2,
|
|
"jean-baptiste": 1,
|
|
"romain": 1
|
|
},
|
|
"mother": {
|
|
"michelle": 3,
|
|
"sophie": 1
|
|
}
|
|
},
|
|
"facetStats": {
|
|
"doggos.age": {
|
|
"min": 2.0,
|
|
"max": 6.0
|
|
}
|
|
}
|
|
}
|
|
"###);
|
|
}
|
|
|
|
#[actix_rt::test]
|
|
async fn federation_inconsistent_merge_order() {
|
|
let server = Server::new().await;
|
|
|
|
let index = server.index("movies");
|
|
|
|
let documents = DOCUMENTS.clone();
|
|
let (value, _) = index.add_documents(documents, None).await;
|
|
index.wait_task(value.uid()).await;
|
|
|
|
let (value, _) = index
|
|
.update_settings(json!({
|
|
"sortableAttributes": ["title"],
|
|
"filterableAttributes": ["title", "color"],
|
|
"rankingRules": [
|
|
"sort",
|
|
"words",
|
|
"typo",
|
|
"proximity",
|
|
"attribute",
|
|
"exactness"
|
|
]
|
|
}))
|
|
.await;
|
|
index.wait_task(value.uid()).await;
|
|
|
|
let index = server.index("movies-2");
|
|
|
|
let documents = DOCUMENTS.clone();
|
|
let (value, _) = index.add_documents(documents, None).await;
|
|
index.wait_task(value.uid()).await;
|
|
|
|
let (value, _) = index
|
|
.update_settings(json!({
|
|
"sortableAttributes": ["title"],
|
|
"filterableAttributes": ["title", "color"],
|
|
"rankingRules": [
|
|
"sort",
|
|
"words",
|
|
"typo",
|
|
"proximity",
|
|
"attribute",
|
|
"exactness"
|
|
],
|
|
"faceting": {
|
|
"sortFacetValuesBy": { "color": "count" }
|
|
}
|
|
}))
|
|
.await;
|
|
index.wait_task(value.uid()).await;
|
|
|
|
let index = server.index("batman");
|
|
|
|
let documents = SCORE_DOCUMENTS.clone();
|
|
let (value, _) = index.add_documents(documents, None).await;
|
|
index.wait_task(value.uid()).await;
|
|
|
|
let (value, _) = index
|
|
.update_settings(json!({
|
|
"sortableAttributes": ["title"],
|
|
"filterableAttributes": ["title"],
|
|
"rankingRules": [
|
|
"sort",
|
|
"words",
|
|
"typo",
|
|
"proximity",
|
|
"attribute",
|
|
"exactness"
|
|
]
|
|
}))
|
|
.await;
|
|
index.wait_task(value.uid()).await;
|
|
|
|
// without merging, it works
|
|
let (response, code) = server
|
|
.multi_search(json!({"federation": {
|
|
"facetsByIndex": {
|
|
"movies": ["title", "color"],
|
|
"batman": ["title"],
|
|
"movies-2": ["title", "color"],
|
|
}
|
|
}, "queries": [
|
|
{"indexUid" : "movies", "q": "", "sort": ["title:asc"], "attributesToRetrieve": ["title"] },
|
|
{"indexUid" : "batman", "q": "", "sort": ["title:asc"], "attributesToRetrieve": ["title"] },
|
|
{"indexUid" : "movies-2", "q": "", "sort": ["title:asc"], "attributesToRetrieve": ["title"] },
|
|
]}))
|
|
.await;
|
|
snapshot!(code, @"200 OK");
|
|
insta::assert_json_snapshot!(response, { ".processingTimeMs" => "[time]" }, @r###"
|
|
{
|
|
"hits": [
|
|
{
|
|
"title": "Badman",
|
|
"_federation": {
|
|
"indexUid": "batman",
|
|
"queriesPosition": 1,
|
|
"weightedRankingScore": 1.0
|
|
}
|
|
},
|
|
{
|
|
"title": "Batman",
|
|
"_federation": {
|
|
"indexUid": "batman",
|
|
"queriesPosition": 1,
|
|
"weightedRankingScore": 1.0
|
|
}
|
|
},
|
|
{
|
|
"title": "Batman Returns",
|
|
"_federation": {
|
|
"indexUid": "batman",
|
|
"queriesPosition": 1,
|
|
"weightedRankingScore": 1.0
|
|
}
|
|
},
|
|
{
|
|
"title": "Batman the dark knight returns: Part 1",
|
|
"_federation": {
|
|
"indexUid": "batman",
|
|
"queriesPosition": 1,
|
|
"weightedRankingScore": 1.0
|
|
}
|
|
},
|
|
{
|
|
"title": "Batman the dark knight returns: Part 2",
|
|
"_federation": {
|
|
"indexUid": "batman",
|
|
"queriesPosition": 1,
|
|
"weightedRankingScore": 1.0
|
|
}
|
|
},
|
|
{
|
|
"title": "Captain Marvel",
|
|
"_federation": {
|
|
"indexUid": "movies",
|
|
"queriesPosition": 0,
|
|
"weightedRankingScore": 1.0
|
|
}
|
|
},
|
|
{
|
|
"title": "Captain Marvel",
|
|
"_federation": {
|
|
"indexUid": "movies-2",
|
|
"queriesPosition": 2,
|
|
"weightedRankingScore": 1.0
|
|
}
|
|
},
|
|
{
|
|
"title": "Escape Room",
|
|
"_federation": {
|
|
"indexUid": "movies",
|
|
"queriesPosition": 0,
|
|
"weightedRankingScore": 1.0
|
|
}
|
|
},
|
|
{
|
|
"title": "Escape Room",
|
|
"_federation": {
|
|
"indexUid": "movies-2",
|
|
"queriesPosition": 2,
|
|
"weightedRankingScore": 1.0
|
|
}
|
|
},
|
|
{
|
|
"title": "Gläss",
|
|
"_federation": {
|
|
"indexUid": "movies",
|
|
"queriesPosition": 0,
|
|
"weightedRankingScore": 1.0
|
|
}
|
|
},
|
|
{
|
|
"title": "Gläss",
|
|
"_federation": {
|
|
"indexUid": "movies-2",
|
|
"queriesPosition": 2,
|
|
"weightedRankingScore": 1.0
|
|
}
|
|
},
|
|
{
|
|
"title": "How to Train Your Dragon: The Hidden World",
|
|
"_federation": {
|
|
"indexUid": "movies",
|
|
"queriesPosition": 0,
|
|
"weightedRankingScore": 1.0
|
|
}
|
|
},
|
|
{
|
|
"title": "How to Train Your Dragon: The Hidden World",
|
|
"_federation": {
|
|
"indexUid": "movies-2",
|
|
"queriesPosition": 2,
|
|
"weightedRankingScore": 1.0
|
|
}
|
|
},
|
|
{
|
|
"title": "Shazam!",
|
|
"_federation": {
|
|
"indexUid": "movies",
|
|
"queriesPosition": 0,
|
|
"weightedRankingScore": 1.0
|
|
}
|
|
},
|
|
{
|
|
"title": "Shazam!",
|
|
"_federation": {
|
|
"indexUid": "movies-2",
|
|
"queriesPosition": 2,
|
|
"weightedRankingScore": 1.0
|
|
}
|
|
}
|
|
],
|
|
"processingTimeMs": "[time]",
|
|
"limit": 20,
|
|
"offset": 0,
|
|
"estimatedTotalHits": 15,
|
|
"facetsByIndex": {
|
|
"batman": {
|
|
"distribution": {
|
|
"title": {
|
|
"Badman": 1,
|
|
"Batman": 1,
|
|
"Batman Returns": 1,
|
|
"Batman the dark knight returns: Part 1": 1,
|
|
"Batman the dark knight returns: Part 2": 1
|
|
}
|
|
},
|
|
"stats": {}
|
|
},
|
|
"movies": {
|
|
"distribution": {
|
|
"color": {
|
|
"blue": 3,
|
|
"green": 2,
|
|
"red": 3,
|
|
"yellow": 2
|
|
},
|
|
"title": {
|
|
"Captain Marvel": 1,
|
|
"Escape Room": 1,
|
|
"Gläss": 1,
|
|
"How to Train Your Dragon: The Hidden World": 1,
|
|
"Shazam!": 1
|
|
}
|
|
},
|
|
"stats": {}
|
|
},
|
|
"movies-2": {
|
|
"distribution": {
|
|
"color": {
|
|
"red": 3,
|
|
"blue": 3,
|
|
"yellow": 2,
|
|
"green": 2
|
|
},
|
|
"title": {
|
|
"Captain Marvel": 1,
|
|
"Escape Room": 1,
|
|
"Gläss": 1,
|
|
"How to Train Your Dragon: The Hidden World": 1,
|
|
"Shazam!": 1
|
|
}
|
|
},
|
|
"stats": {}
|
|
}
|
|
}
|
|
}
|
|
"###);
|
|
|
|
// fails with merging
|
|
let (response, code) = server
|
|
.multi_search(json!({"federation": {
|
|
"facetsByIndex": {
|
|
"movies": ["title", "color"],
|
|
"batman": ["title"],
|
|
"movies-2": ["title", "color"],
|
|
},
|
|
"mergeFacets": {}
|
|
}, "queries": [
|
|
{"indexUid" : "movies", "q": "", "sort": ["title:asc"], "attributesToRetrieve": ["title"] },
|
|
{"indexUid" : "batman", "q": "", "sort": ["title:asc"], "attributesToRetrieve": ["title"] },
|
|
{"indexUid" : "movies-2", "q": "", "sort": ["title:asc"], "attributesToRetrieve": ["title"] },
|
|
]}))
|
|
.await;
|
|
snapshot!(code, @"400 Bad Request");
|
|
insta::assert_json_snapshot!(response, { ".processingTimeMs" => "[time]" }, @r###"
|
|
{
|
|
"message": "Inside `.federation.facetsByIndex.movies-2`: Inconsistent order for values in facet `color`: index `movies` orders alphabetically, but index `movies-2` orders by count.\n - Hint: Remove `federation.mergeFacets` or change `faceting.sortFacetValuesBy` to be consistent in settings.\n - Note: index `movies-2` used in `.queries[2]`",
|
|
"code": "invalid_multi_search_facet_order",
|
|
"type": "invalid_request",
|
|
"link": "https://docs.meilisearch.com/errors#invalid_multi_search_facet_order"
|
|
}
|
|
"###);
|
|
|
|
// can limit the number of values
|
|
let (response, code) = server
|
|
.multi_search(json!({"federation": {
|
|
"facetsByIndex": {
|
|
"movies": ["title", "color"],
|
|
"batman": ["title"],
|
|
"movies-2": ["title"],
|
|
},
|
|
"mergeFacets": {
|
|
"maxValuesPerFacet": 3,
|
|
}
|
|
}, "queries": [
|
|
{"indexUid" : "movies", "q": "", "sort": ["title:asc"], "attributesToRetrieve": ["title"] },
|
|
{"indexUid" : "batman", "q": "", "sort": ["title:asc"], "attributesToRetrieve": ["title"] },
|
|
{"indexUid" : "movies-2", "q": "", "sort": ["title:asc"], "attributesToRetrieve": ["title"] },
|
|
]}))
|
|
.await;
|
|
snapshot!(code, @"200 OK");
|
|
insta::assert_json_snapshot!(response, { ".processingTimeMs" => "[time]" }, @r###"
|
|
{
|
|
"hits": [
|
|
{
|
|
"title": "Badman",
|
|
"_federation": {
|
|
"indexUid": "batman",
|
|
"queriesPosition": 1,
|
|
"weightedRankingScore": 1.0
|
|
}
|
|
},
|
|
{
|
|
"title": "Batman",
|
|
"_federation": {
|
|
"indexUid": "batman",
|
|
"queriesPosition": 1,
|
|
"weightedRankingScore": 1.0
|
|
}
|
|
},
|
|
{
|
|
"title": "Batman Returns",
|
|
"_federation": {
|
|
"indexUid": "batman",
|
|
"queriesPosition": 1,
|
|
"weightedRankingScore": 1.0
|
|
}
|
|
},
|
|
{
|
|
"title": "Batman the dark knight returns: Part 1",
|
|
"_federation": {
|
|
"indexUid": "batman",
|
|
"queriesPosition": 1,
|
|
"weightedRankingScore": 1.0
|
|
}
|
|
},
|
|
{
|
|
"title": "Batman the dark knight returns: Part 2",
|
|
"_federation": {
|
|
"indexUid": "batman",
|
|
"queriesPosition": 1,
|
|
"weightedRankingScore": 1.0
|
|
}
|
|
},
|
|
{
|
|
"title": "Captain Marvel",
|
|
"_federation": {
|
|
"indexUid": "movies",
|
|
"queriesPosition": 0,
|
|
"weightedRankingScore": 1.0
|
|
}
|
|
},
|
|
{
|
|
"title": "Captain Marvel",
|
|
"_federation": {
|
|
"indexUid": "movies-2",
|
|
"queriesPosition": 2,
|
|
"weightedRankingScore": 1.0
|
|
}
|
|
},
|
|
{
|
|
"title": "Escape Room",
|
|
"_federation": {
|
|
"indexUid": "movies",
|
|
"queriesPosition": 0,
|
|
"weightedRankingScore": 1.0
|
|
}
|
|
},
|
|
{
|
|
"title": "Escape Room",
|
|
"_federation": {
|
|
"indexUid": "movies-2",
|
|
"queriesPosition": 2,
|
|
"weightedRankingScore": 1.0
|
|
}
|
|
},
|
|
{
|
|
"title": "Gläss",
|
|
"_federation": {
|
|
"indexUid": "movies",
|
|
"queriesPosition": 0,
|
|
"weightedRankingScore": 1.0
|
|
}
|
|
},
|
|
{
|
|
"title": "Gläss",
|
|
"_federation": {
|
|
"indexUid": "movies-2",
|
|
"queriesPosition": 2,
|
|
"weightedRankingScore": 1.0
|
|
}
|
|
},
|
|
{
|
|
"title": "How to Train Your Dragon: The Hidden World",
|
|
"_federation": {
|
|
"indexUid": "movies",
|
|
"queriesPosition": 0,
|
|
"weightedRankingScore": 1.0
|
|
}
|
|
},
|
|
{
|
|
"title": "How to Train Your Dragon: The Hidden World",
|
|
"_federation": {
|
|
"indexUid": "movies-2",
|
|
"queriesPosition": 2,
|
|
"weightedRankingScore": 1.0
|
|
}
|
|
},
|
|
{
|
|
"title": "Shazam!",
|
|
"_federation": {
|
|
"indexUid": "movies",
|
|
"queriesPosition": 0,
|
|
"weightedRankingScore": 1.0
|
|
}
|
|
},
|
|
{
|
|
"title": "Shazam!",
|
|
"_federation": {
|
|
"indexUid": "movies-2",
|
|
"queriesPosition": 2,
|
|
"weightedRankingScore": 1.0
|
|
}
|
|
}
|
|
],
|
|
"processingTimeMs": "[time]",
|
|
"limit": 20,
|
|
"offset": 0,
|
|
"estimatedTotalHits": 15,
|
|
"facetDistribution": {
|
|
"color": {
|
|
"blue": 3,
|
|
"green": 2,
|
|
"red": 3
|
|
},
|
|
"title": {
|
|
"Badman": 1,
|
|
"Batman": 1,
|
|
"Batman Returns": 1
|
|
}
|
|
},
|
|
"facetStats": {}
|
|
}
|
|
"###);
|
|
}
|