Skip to content

Commit c296938

Browse files
committed
Support ignore_unavailable query param
1 parent 03ed8cf commit c296938

File tree

9 files changed

+129
-11
lines changed

9 files changed

+129
-11
lines changed

quickwit/quickwit-metastore/src/tests/index.rs

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -677,9 +677,14 @@ pub async fn test_metastore_list_indexes<MetastoreToTest: MetastoreServiceExt +
677677
let index_uri_4 = format!("ram:///indexes/{index_id_4}");
678678
let index_config_4 = IndexConfig::for_test(&index_id_4, &index_uri_4);
679679

680+
let index_id_5 = format!("my-exact-index-{index_id_fragment}-5");
681+
let index_uri_5 = format!("ram:///indexes/{index_id_5}");
682+
let index_config_5 = IndexConfig::for_test(&index_id_5, &index_uri_5);
683+
680684
let index_id_patterns = vec![
681685
format!("prefix-*-{index_id_fragment}-suffix-*"),
682686
format!("prefix*{index_id_fragment}*suffix-*"),
687+
format!("my-exact-index-{index_id_fragment}-5"),
683688
];
684689
let indexes_count = metastore
685690
.list_indexes_metadata(ListIndexesMetadataRequest { index_id_patterns })
@@ -715,8 +720,17 @@ pub async fn test_metastore_list_indexes<MetastoreToTest: MetastoreServiceExt +
715720
.unwrap()
716721
.index_uid()
717722
.clone();
723+
let index_uid_5 = metastore
724+
.create_index(CreateIndexRequest::try_from_index_config(&index_config_5).unwrap())
725+
.await
726+
.unwrap()
727+
.index_uid()
728+
.clone();
718729

719-
let index_id_patterns = vec![format!("prefix-*-{index_id_fragment}-suffix-*")];
730+
let index_id_patterns = vec![
731+
format!("prefix-*-{index_id_fragment}-suffix-*"),
732+
format!("my-exact-index-{index_id_fragment}-5"),
733+
];
720734
let indexes_count = metastore
721735
.list_indexes_metadata(ListIndexesMetadataRequest { index_id_patterns })
722736
.await
@@ -725,12 +739,13 @@ pub async fn test_metastore_list_indexes<MetastoreToTest: MetastoreServiceExt +
725739
.await
726740
.unwrap()
727741
.len();
728-
assert_eq!(indexes_count, 2);
742+
assert_eq!(indexes_count, 3);
729743

730744
cleanup_index(&mut metastore, index_uid_1).await;
731745
cleanup_index(&mut metastore, index_uid_2).await;
732746
cleanup_index(&mut metastore, index_uid_3).await;
733747
cleanup_index(&mut metastore, index_uid_4).await;
748+
cleanup_index(&mut metastore, index_uid_5).await;
734749
}
735750

736751
pub async fn test_metastore_delete_index<

quickwit/quickwit-proto/protos/quickwit/search.proto

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -243,6 +243,10 @@ message SearchRequest {
243243
optional PartialHit search_after = 16;
244244

245245
CountHits count_hits = 17;
246+
247+
// When an exact index_id is provided (not a pattern), the query fails if that
248+
// index is missing and this is false.
249+
bool ignore_missing_indexes = 18;
246250
}
247251

248252
enum CountHits {

quickwit/quickwit-proto/src/codegen/quickwit/quickwit.search.rs

Lines changed: 7 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

quickwit/quickwit-search/src/root.rs

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -366,6 +366,7 @@ fn simplify_search_request_for_scroll_api(req: &SearchRequest) -> crate::Result<
366366
// request is simplified after initial query, and we cache the hit count, so we don't need
367367
// to recompute it afterward.
368368
count_hits: quickwit_proto::search::CountHits::Underestimate as i32,
369+
ignore_missing_indexes: req.ignore_missing_indexes,
369370
})
370371
}
371372

@@ -1156,7 +1157,12 @@ async fn plan_splits_for_root_search(
11561157
.deserialize_indexes_metadata()
11571158
.await?;
11581159

1159-
check_all_index_metadata_found(&indexes_metadata[..], &search_request.index_id_patterns[..])?;
1160+
if !search_request.ignore_missing_indexes {
1161+
check_all_index_metadata_found(
1162+
&indexes_metadata[..],
1163+
&search_request.index_id_patterns[..],
1164+
)?;
1165+
}
11601166

11611167
if indexes_metadata.is_empty() {
11621168
return Ok((Vec::new(), HashMap::default()));
@@ -1243,7 +1249,12 @@ pub async fn search_plan(
12431249
.deserialize_indexes_metadata()
12441250
.await?;
12451251

1246-
check_all_index_metadata_found(&indexes_metadata[..], &search_request.index_id_patterns[..])?;
1252+
if !search_request.ignore_missing_indexes {
1253+
check_all_index_metadata_found(
1254+
&indexes_metadata[..],
1255+
&search_request.index_id_patterns[..],
1256+
)?;
1257+
}
12471258
if indexes_metadata.is_empty() {
12481259
return Ok(SearchPlanResponse {
12491260
result: serde_json::to_string(&SearchPlanResponseRest {

quickwit/quickwit-serve/src/elasticsearch_api/model/multi_search.rs

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ use crate::simple_list::{from_simple_list, to_simple_list};
2525

2626
// Multi search doc: https://www.elastic.co/guide/en/elasticsearch/reference/current/search-multi-search.html
2727

28+
#[serde_as]
2829
#[serde_with::skip_serializing_none]
2930
#[derive(Default, Debug, Serialize, Deserialize)]
3031
#[serde(deny_unknown_fields)]
@@ -50,6 +51,9 @@ pub struct MultiSearchQueryParams {
5051
pub ignore_throttled: Option<bool>,
5152
#[serde(default)]
5253
pub ignore_unavailable: Option<bool>,
54+
#[serde_as(deserialize_as = "OneOrMany<_, PreferMany>")]
55+
#[serde(default)]
56+
pub index: Vec<String>,
5357
#[serde(default)]
5458
pub max_concurrent_searches: Option<u64>,
5559
#[serde(default)]
@@ -100,6 +104,26 @@ pub struct MultiSearchHeader {
100104
pub routing: Option<Vec<String>>,
101105
}
102106

107+
impl MultiSearchHeader {
108+
pub fn apply_query_param_defaults(&mut self, defaults: &MultiSearchQueryParams) {
109+
if self.allow_no_indices.is_none() {
110+
self.allow_no_indices = defaults.allow_no_indices;
111+
}
112+
if self.expand_wildcards.is_none() {
113+
self.expand_wildcards = defaults.expand_wildcards.clone();
114+
}
115+
if self.ignore_unavailable.is_none() {
116+
self.ignore_unavailable = defaults.ignore_unavailable;
117+
}
118+
if self.index.is_empty() {
119+
self.index = defaults.index.clone();
120+
}
121+
if self.routing.is_none() {
122+
self.routing = defaults.routing.clone();
123+
}
124+
}
125+
}
126+
103127
#[derive(Serialize)]
104128
pub struct MultiSearchResponse {
105129
pub responses: Vec<MultiSearchSingleResponse>,

quickwit/quickwit-serve/src/elasticsearch_api/rest_handler.rs

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -358,6 +358,7 @@ fn build_request_for_es_api(
358358

359359
let max_hits = search_params.size.or(search_body.size).unwrap_or(10);
360360
let start_offset = search_params.from.or(search_body.from).unwrap_or(0);
361+
let ignore_missing_indexes = search_params.ignore_unavailable.unwrap_or(false);
361362
let count_hits = match search_params
362363
.track_total_hits
363364
.or(search_body.track_total_hits)
@@ -410,6 +411,7 @@ fn build_request_for_es_api(
410411
scroll_ttl_secs,
411412
search_after,
412413
count_hits,
414+
ignore_missing_indexes,
413415
},
414416
has_doc_id_field,
415417
))
@@ -814,13 +816,15 @@ async fn es_compat_index_multi_search(
814816
let mut payload_lines = str_lines(str_payload);
815817

816818
while let Some(line) = payload_lines.next() {
817-
let request_header = serde_json::from_str::<MultiSearchHeader>(line).map_err(|err| {
818-
SearchError::InvalidArgument(format!(
819-
"failed to parse request header `{}...`: {}",
820-
truncate_str(line, 20),
821-
err
822-
))
823-
})?;
819+
let mut request_header =
820+
serde_json::from_str::<MultiSearchHeader>(line).map_err(|err| {
821+
SearchError::InvalidArgument(format!(
822+
"failed to parse request header `{}...`: {}",
823+
truncate_str(line, 20),
824+
err
825+
))
826+
})?;
827+
request_header.apply_query_param_defaults(&multi_search_params);
824828
if request_header.index.is_empty() {
825829
return Err(ElasticsearchError::from(SearchError::InvalidArgument(
826830
"`_msearch` request header must define at least one index".to_string(),

quickwit/quickwit-serve/src/search_api/rest_handler.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -264,6 +264,7 @@ pub fn search_request_from_api_request(
264264
scroll_ttl_secs: None,
265265
search_after: None,
266266
count_hits: search_request.count_all.into(),
267+
ignore_missing_indexes: false,
267268
};
268269
Ok(search_request)
269270
}

quickwit/rest-api-tests/scenarii/es_compatibility/0025-msearch.yaml

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,3 +104,25 @@ expected:
104104
$expect: "len(val) == 1" # Contains only 'actor'
105105
actor:
106106
id: 5688
107+
---
108+
# test missing index
109+
endpoint: "_msearch"
110+
method: POST
111+
ndjson:
112+
- {"index":"idontexist"}
113+
- {"query" : {"match" : { "type": "PushEvent"}}, "size": 0}
114+
expected:
115+
responses:
116+
- status: 404
117+
---
118+
endpoint: "_msearch"
119+
method: POST
120+
ndjson:
121+
- {"index":"idontexist", "ignore_unavailable": true}
122+
- {"query" : {"match" : { "type": "PushEvent"}}, "size": 0}
123+
expected:
124+
responses:
125+
- hits:
126+
total:
127+
value: 0
128+
status: 200
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
endpoint: "idontexist/_search"
2+
params:
3+
q: "*"
4+
status_code: 404
5+
---
6+
endpoint: "idontexist/_search"
7+
params:
8+
q: "*"
9+
ignore_unavailable: "true"
10+
expected:
11+
hits:
12+
total:
13+
value: 0
14+
---
15+
endpoint: "gharchive-*,idontexist/_search"
16+
params:
17+
q: "*"
18+
status_code: 404
19+
---
20+
endpoint: "gharchive-*,idontexist/_search"
21+
params:
22+
q: "*"
23+
ignore_unavailable: "true"
24+
expected:
25+
hits:
26+
total:
27+
value: 4
28+
29+
30+

0 commit comments

Comments
 (0)