@@ -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 {
@@ -3240,6 +3251,102 @@ mod tests {
32403251 Ok ( ( ) )
32413252 }
32423253
3254+ #[ tokio:: test]
3255+ async fn test_root_search_missing_index ( ) -> anyhow:: Result < ( ) > {
3256+ let mut mock_metastore = MockMetastoreService :: new ( ) ;
3257+ let index_metadata = IndexMetadata :: for_test ( "test-index1" , "ram:///test-index" ) ;
3258+ let index_uid = index_metadata. index_uid . clone ( ) ;
3259+ mock_metastore
3260+ . expect_list_indexes_metadata ( )
3261+ . returning ( move |_index_ids_query| {
3262+ Ok ( ListIndexesMetadataResponse :: for_test ( vec ! [
3263+ index_metadata. clone( ) ,
3264+ ] ) )
3265+ } ) ;
3266+ mock_metastore
3267+ . expect_list_splits ( )
3268+ . returning ( move |_list_splits_request| {
3269+ let splits = vec ! [
3270+ MockSplitBuilder :: new( "split1" )
3271+ . with_index_uid( & index_uid)
3272+ . build( ) ,
3273+ ] ;
3274+ let splits_response = ListSplitsResponse :: try_from_splits ( splits) . unwrap ( ) ;
3275+ Ok ( ServiceStream :: from ( vec ! [ Ok ( splits_response) ] ) )
3276+ } ) ;
3277+ let mock_metastore_client = MetastoreServiceClient :: from_mock ( mock_metastore) ;
3278+ let mut mock_search_service = MockSearchService :: new ( ) ;
3279+ mock_search_service. expect_leaf_search ( ) . returning (
3280+ |_leaf_search_req : quickwit_proto:: search:: LeafSearchRequest | {
3281+ Ok ( quickwit_proto:: search:: LeafSearchResponse {
3282+ num_hits : 3 ,
3283+ partial_hits : vec ! [
3284+ mock_partial_hit( "split1" , 3 , 1 ) ,
3285+ mock_partial_hit( "split1" , 2 , 2 ) ,
3286+ mock_partial_hit( "split1" , 1 , 3 ) ,
3287+ ] ,
3288+ failed_splits : Vec :: new ( ) ,
3289+ num_attempted_splits : 1 ,
3290+ ..Default :: default ( )
3291+ } )
3292+ } ,
3293+ ) ;
3294+ mock_search_service. expect_fetch_docs ( ) . returning (
3295+ |fetch_docs_req : quickwit_proto:: search:: FetchDocsRequest | {
3296+ Ok ( quickwit_proto:: search:: FetchDocsResponse {
3297+ hits : get_doc_for_fetch_req ( fetch_docs_req) ,
3298+ } )
3299+ } ,
3300+ ) ;
3301+ let searcher_pool = searcher_pool_for_test ( [ ( "127.0.0.1:1001" , mock_search_service) ] ) ;
3302+ let search_job_placer = SearchJobPlacer :: new ( searcher_pool) ;
3303+ let cluster_client = ClusterClient :: new ( search_job_placer. clone ( ) ) ;
3304+
3305+ let searcher_context = SearcherContext :: for_test ( ) ;
3306+
3307+ // search with ignore_missing_indexes=true succeeds
3308+ let search_request = quickwit_proto:: search:: SearchRequest {
3309+ index_id_patterns : vec ! [ "test-index1" . to_string( ) , "test-index2" . to_string( ) ] ,
3310+ query_ast : qast_json_helper ( "test" , & [ "body" ] ) ,
3311+ max_hits : 10 ,
3312+ ignore_missing_indexes : true ,
3313+ ..Default :: default ( )
3314+ } ;
3315+ let search_response = root_search (
3316+ & searcher_context,
3317+ search_request,
3318+ mock_metastore_client. clone ( ) ,
3319+ & cluster_client,
3320+ )
3321+ . await
3322+ . unwrap ( ) ;
3323+ assert_eq ! ( search_response. num_hits, 3 ) ;
3324+ assert_eq ! ( search_response. hits. len( ) , 3 ) ;
3325+
3326+ // search with ignore_missing_indexes=false fails
3327+ let search_request = quickwit_proto:: search:: SearchRequest {
3328+ index_id_patterns : vec ! [ "test-index1" . to_string( ) , "test-index2" . to_string( ) ] ,
3329+ query_ast : qast_json_helper ( "test" , & [ "body" ] ) ,
3330+ max_hits : 10 ,
3331+ ignore_missing_indexes : false ,
3332+ ..Default :: default ( )
3333+ } ;
3334+ let search_error = root_search (
3335+ & searcher_context,
3336+ search_request,
3337+ mock_metastore_client,
3338+ & cluster_client,
3339+ )
3340+ . await
3341+ . unwrap_err ( ) ;
3342+ if let SearchError :: IndexesNotFound { index_ids } = search_error {
3343+ assert_eq ! ( index_ids, vec![ "test-index2" . to_string( ) ] ) ;
3344+ } else {
3345+ panic ! ( "unexpected error type: {search_error}" ) ;
3346+ }
3347+ Ok ( ( ) )
3348+ }
3349+
32433350 #[ tokio:: test]
32443351 async fn test_root_search_multiple_splits_retry_on_other_node ( ) -> anyhow:: Result < ( ) > {
32453352 let search_request = quickwit_proto:: search:: SearchRequest {
@@ -4112,6 +4219,69 @@ mod tests {
41124219 Ok ( ( ) )
41134220 }
41144221
4222+ #[ tokio:: test]
4223+ async fn test_search_plan_missing_index ( ) -> anyhow:: Result < ( ) > {
4224+ let mut mock_metastore = MockMetastoreService :: new ( ) ;
4225+ let index_metadata = IndexMetadata :: for_test ( "test-index1" , "ram:///test-index" ) ;
4226+ let index_uid = index_metadata. index_uid . clone ( ) ;
4227+ mock_metastore
4228+ . expect_list_indexes_metadata ( )
4229+ . returning ( move |_index_ids_query| {
4230+ Ok ( ListIndexesMetadataResponse :: for_test ( vec ! [
4231+ index_metadata. clone( ) ,
4232+ ] ) )
4233+ } ) ;
4234+ mock_metastore
4235+ . expect_list_splits ( )
4236+ . returning ( move |_filter| {
4237+ let splits = vec ! [
4238+ MockSplitBuilder :: new( "split1" )
4239+ . with_index_uid( & index_uid)
4240+ . build( ) ,
4241+ MockSplitBuilder :: new( "split2" )
4242+ . with_index_uid( & index_uid)
4243+ . build( ) ,
4244+ ] ;
4245+ let splits_response = ListSplitsResponse :: try_from_splits ( splits) . unwrap ( ) ;
4246+ Ok ( ServiceStream :: from ( vec ! [ Ok ( splits_response) ] ) )
4247+ } ) ;
4248+ let mock_metastore_service = MetastoreServiceClient :: from_mock ( mock_metastore) ;
4249+
4250+ // plan with ignore_missing_indexes=true succeeds
4251+ search_plan (
4252+ quickwit_proto:: search:: SearchRequest {
4253+ index_id_patterns : vec ! [ "test-index1" . to_string( ) , "test-index2" . to_string( ) ] ,
4254+ query_ast : qast_json_helper ( "test-query" , & [ "body" ] ) ,
4255+ max_hits : 10 ,
4256+ ignore_missing_indexes : true ,
4257+ ..Default :: default ( )
4258+ } ,
4259+ mock_metastore_service. clone ( ) ,
4260+ )
4261+ . await
4262+ . unwrap ( ) ;
4263+
4264+ // plan with ignore_missing_indexes=false fails
4265+ let search_error = search_plan (
4266+ quickwit_proto:: search:: SearchRequest {
4267+ index_id_patterns : vec ! [ "test-index1" . to_string( ) , "test-index2" . to_string( ) ] ,
4268+ query_ast : qast_json_helper ( "test-query" , & [ "body" ] ) ,
4269+ max_hits : 10 ,
4270+ ignore_missing_indexes : false ,
4271+ ..Default :: default ( )
4272+ } ,
4273+ mock_metastore_service. clone ( ) ,
4274+ )
4275+ . await
4276+ . unwrap_err ( ) ;
4277+ if let SearchError :: IndexesNotFound { index_ids } = search_error {
4278+ assert_eq ! ( index_ids, vec![ "test-index2" . to_string( ) ] ) ;
4279+ } else {
4280+ panic ! ( "unexpected error type: {search_error}" ) ;
4281+ }
4282+ Ok ( ( ) )
4283+ }
4284+
41154285 #[ test]
41164286 fn test_extract_timestamp_range_from_ast ( ) {
41174287 use std:: ops:: Bound ;
0 commit comments