mirror of
https://github.com/meilisearch/MeiliSearch
synced 2024-11-30 16:52:55 +01:00
4436 lines
138 KiB
Rust
4436 lines
138 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 (_).",
|
|
"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 (_).",
|
|
"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",
|
|
"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",
|
|
"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",
|
|
"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",
|
|
"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_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
|
|
}
|
|
"###);
|
|
}
|