1- use crate :: client:: sdk:: { Api , block, block_with_wakeup, none_if_404} ;
1+ use crate :: client:: sdk:: { Api , ApiError , block, block_with_wakeup, none_if_404} ;
22use crate :: commands:: databases;
33use rayon:: prelude:: * ;
44use serde:: { Deserialize , Serialize } ;
@@ -182,8 +182,8 @@ fn connection_label_to_id_map(connections: &[ConnectionRef]) -> HashMap<String,
182182 m
183183}
184184
185- fn connection_lookup ( api : & Api ) -> HashMap < String , String > {
186- let resp = block ( api. client ( ) . connections ( ) . list ( ) ) . unwrap_or_else ( |e| e . exit ( ) ) ;
185+ fn connection_lookup ( api : & Api ) -> Result < HashMap < String , String > , ApiError > {
186+ let resp = block ( api. client ( ) . connections ( ) . list ( ) ) ? ;
187187 let refs: Vec < ConnectionRef > = resp
188188 . connections
189189 . into_iter ( )
@@ -192,7 +192,7 @@ fn connection_lookup(api: &Api) -> HashMap<String, String> {
192192 name : c. name ,
193193 } )
194194 . collect ( ) ;
195- connection_label_to_id_map ( & refs)
195+ Ok ( connection_label_to_id_map ( & refs) )
196196}
197197
198198/// Pick the connection id to address a per-table index call with during a
@@ -233,15 +233,15 @@ struct ScanTarget {
233233/// connection id, so each database needs a `databases get`; a database deleted
234234/// between the list and the get (404) is skipped, any other error surfaces
235235/// loudly to match the rest of this path.
236- fn managed_db_connection_ids ( api : & Api ) -> Vec < String > {
237- let ids = databases:: list_database_ids ( api) . unwrap_or_else ( |e| e. exit ( ) ) ;
238- ids. par_iter ( )
239- . filter_map ( |id| {
240- none_if_404 ( databases:: get_database ( api, id) )
241- . unwrap_or_else ( |e| e. exit ( ) )
242- . map ( |db| db. default_connection_id )
236+ fn managed_db_connection_ids ( api : & Api ) -> Result < Vec < String > , ApiError > {
237+ let ids = databases:: list_database_ids ( api) ?;
238+ let conn_ids: Result < Vec < Option < String > > , ApiError > = ids
239+ . par_iter ( )
240+ . map ( |id| {
241+ Ok ( none_if_404 ( databases:: get_database ( api, id) ) ?. map ( |db| db. default_connection_id ) )
243242 } )
244- . collect ( )
243+ . collect ( ) ;
244+ Ok ( conn_ids?. into_iter ( ) . flatten ( ) . collect ( ) )
245245}
246246
247247/// Build the per-table scan list for a whole-workspace (unscoped) `indexes
@@ -254,11 +254,15 @@ fn managed_db_connection_ids(api: &Api) -> Vec<String> {
254254/// with a connection-scoped `information_schema` call, exactly like the
255255/// `--connection-id` path. The two table sets are disjoint: a managed database's
256256/// connection is never returned by `connections list`.
257- fn workspace_scan_targets ( api : & Api , schema : Option < & str > , table : Option < & str > ) -> Vec < ScanTarget > {
257+ fn workspace_scan_targets (
258+ api : & Api ,
259+ schema : Option < & str > ,
260+ table : Option < & str > ,
261+ ) -> Result < Vec < ScanTarget > , ApiError > {
258262 // Regular connections: one workspace-wide enumeration, label (= connection
259263 // name) mapped back to its id, falling back to the label itself (#161).
260- let name_to_id = connection_lookup ( api) ;
261- let mut targets: Vec < ScanTarget > = collect_tables ( api, None , schema, table)
264+ let name_to_id = connection_lookup ( api) ? ;
265+ let mut targets: Vec < ScanTarget > = collect_tables ( api, None , schema, table) ?
262266 . into_iter ( )
263267 . map ( |t| {
264268 let conn_id = scan_connection_id ( None , & t. connection , & name_to_id) . to_string ( ) ;
@@ -268,21 +272,23 @@ fn workspace_scan_targets(api: &Api, schema: Option<&str>, table: Option<&str>)
268272
269273 // Managed databases: discover their hidden connections, then scan each
270274 // scoped (the per-connection enumeration is what surfaces `__db_*` tables).
271- let db_conns = managed_db_connection_ids ( api) ;
272- let managed: Vec < ScanTarget > = db_conns
275+ let db_conns = managed_db_connection_ids ( api) ? ;
276+ let managed: Result < Vec < Vec < ScanTarget > > , ApiError > = db_conns
273277 . par_iter ( )
274- . flat_map ( |conn| {
275- collect_tables ( api, Some ( conn) , schema, table)
276- . into_iter ( )
277- . map ( |t| ScanTarget {
278- conn_id : conn. clone ( ) ,
279- table : t,
280- } )
281- . collect :: < Vec < _ > > ( )
278+ . map ( |conn| {
279+ collect_tables ( api, Some ( conn) , schema, table) . map ( |tables| {
280+ tables
281+ . into_iter ( )
282+ . map ( |t| ScanTarget {
283+ conn_id : conn. clone ( ) ,
284+ table : t,
285+ } )
286+ . collect :: < Vec < _ > > ( )
287+ } )
282288 } )
283289 . collect ( ) ;
284- targets. extend ( managed) ;
285- targets
290+ targets. extend ( managed? . into_iter ( ) . flatten ( ) ) ;
291+ Ok ( targets)
286292}
287293
288294/// Gather index rows across a connection's (or the workspace's) tables — the
@@ -298,36 +304,36 @@ fn collect_connection_wide(
298304 connection_id : Option < & str > ,
299305 schema : Option < & str > ,
300306 table : Option < & str > ,
301- ) -> Vec < IndexRow > {
307+ ) -> Result < Vec < IndexRow > , ApiError > {
302308 let targets = match connection_id {
303- Some ( cid) => collect_tables ( api, Some ( cid) , schema, table)
309+ Some ( cid) => collect_tables ( api, Some ( cid) , schema, table) ?
304310 . into_iter ( )
305311 . map ( |t| ScanTarget {
306312 conn_id : cid. to_string ( ) ,
307313 table : t,
308314 } )
309315 . collect ( ) ,
310- None => workspace_scan_targets ( api, schema, table) ,
316+ None => workspace_scan_targets ( api, schema, table) ? ,
311317 } ;
312- let per_table: Vec < ( String , Vec < Index > ) > = targets
318+ let per_table: Result < Vec < ( String , Vec < Index > ) > , ApiError > = targets
313319 . par_iter ( )
314320 . map ( |tg| {
315321 let t = & tg. table ;
316322 let full = format ! ( "{}.{}.{}" , t. connection, t. schema, t. table) ;
317- let indexes = list_one_table_scan ( api, & tg. conn_id , & t. schema , & t. table ) ;
318- ( full, indexes)
323+ let indexes = list_one_table_scan ( api, & tg. conn_id , & t. schema , & t. table ) ? ;
324+ Ok ( ( full, indexes) )
319325 } )
320326 . collect ( ) ;
321327 let mut rows: Vec < IndexRow > = Vec :: new ( ) ;
322- for ( full, indexes) in per_table {
328+ for ( full, indexes) in per_table? {
323329 for i in indexes {
324330 rows. push ( IndexRow {
325331 inner : i,
326332 table : Some ( full. clone ( ) ) ,
327333 } ) ;
328334 }
329335 }
330- rows
336+ Ok ( rows)
331337}
332338
333339/// How to continue after merging one `/information_schema` page.
@@ -358,7 +364,7 @@ fn collect_tables(
358364 connection_id : Option < & str > ,
359365 schema : Option < & str > ,
360366 table : Option < & str > ,
361- ) -> Vec < InfoTable > {
367+ ) -> Result < Vec < InfoTable > , ApiError > {
362368 let mut out = Vec :: new ( ) ;
363369 let mut cursor: Option < String > = None ;
364370 loop {
@@ -369,8 +375,7 @@ fn collect_tables(
369375 None ,
370376 None ,
371377 cursor. as_deref ( ) ,
372- ) )
373- . unwrap_or_else ( |e| e. exit ( ) ) ;
378+ ) ) ?;
374379 out. extend ( resp. tables . into_iter ( ) . map ( |t| InfoTable {
375380 connection : t. connection ,
376381 schema : t. schema ,
@@ -383,23 +388,33 @@ fn collect_tables(
383388 }
384389 }
385390 sort_info_tables ( & mut out) ;
386- out
391+ Ok ( out)
387392}
388393
389- fn list_one_table ( api : & Api , connection_id : & str , schema : & str , table : & str ) -> Vec < Index > {
394+ fn list_one_table (
395+ api : & Api ,
396+ connection_id : & str ,
397+ schema : & str ,
398+ table : & str ,
399+ ) -> Result < Vec < Index > , ApiError > {
390400 // The SDK's typed `IndexInfoResponse.status` is a closed `ready`/`pending`
391401 // enum; the CLI accepts any status string for display. Keep the CLI's own
392402 // tolerant deserialization via the seam's untyped GET escape hatch.
393403 let path = format ! ( "/connections/{connection_id}/tables/{schema}/{table}/indexes" ) ;
394- let body: ListResponse = api. get_json ( & path, & [ ] ) . unwrap_or_else ( |e| e . exit ( ) ) ;
395- body. indexes
404+ let body: ListResponse = api. get_json ( & path, & [ ] ) ? ;
405+ Ok ( body. indexes )
396406}
397407
398- fn list_one_table_scan ( api : & Api , connection_id : & str , schema : & str , table : & str ) -> Vec < Index > {
408+ fn list_one_table_scan (
409+ api : & Api ,
410+ connection_id : & str ,
411+ schema : & str ,
412+ table : & str ,
413+ ) -> Result < Vec < Index > , ApiError > {
399414 let path = format ! ( "/connections/{connection_id}/tables/{schema}/{table}/indexes" ) ;
400- match none_if_404 ( api. get_json :: < ListResponse > ( & path, & [ ] ) ) . unwrap_or_else ( |e| e . exit ( ) ) {
401- Some ( body) => body. indexes ,
402- None => Vec :: new ( ) ,
415+ match none_if_404 ( api. get_json :: < ListResponse > ( & path, & [ ] ) ) ? {
416+ Some ( body) => Ok ( body. indexes ) ,
417+ None => Ok ( Vec :: new ( ) ) ,
403418 }
404419}
405420
@@ -479,7 +494,7 @@ pub fn infer_for_search(
479494 let connection_id = crate :: commands:: connections:: resolve_connection_id ( & api, connection_name) ;
480495
481496 // Fetch indexes for this table
482- let indexes = list_one_table ( & api, & connection_id, schema, table) ;
497+ let indexes = list_one_table ( & api, & connection_id, schema, table) . unwrap_or_else ( |e| e . exit ( ) ) ;
483498
484499 let location = format ! ( "{}.{}.{}" , connection_name, schema, table) ;
485500 match resolve_search_params ( & indexes, hint_type, hint_column, & location) {
@@ -500,9 +515,13 @@ pub fn list(
500515) {
501516 let api = Api :: new ( Some ( workspace_id) ) ;
502517
503- let ( rows, multi_table) = match ( connection_id, schema, table) {
504- ( Some ( cid) , Some ( sch) , Some ( tbl) ) => {
505- let indexes = list_one_table ( & api, cid, sch, tbl) ;
518+ // One spinner over the whole fetch — the unscoped path is a
519+ // whole-workspace scan (many requests) that otherwise sits silent.
520+ // The database discovery inside is deliberately spinner-less
521+ // (databases::list_database_ids) so nothing fights for the line.
522+ let spinner = crate :: util:: spinner ( "Loading indexes…" ) ;
523+ let result = match ( connection_id, schema, table) {
524+ ( Some ( cid) , Some ( sch) , Some ( tbl) ) => list_one_table ( & api, cid, sch, tbl) . map ( |indexes| {
506525 let rows: Vec < IndexRow > = indexes
507526 . into_iter ( )
508527 . map ( |i| IndexRow {
@@ -511,12 +530,11 @@ pub fn list(
511530 } )
512531 . collect ( ) ;
513532 ( rows, false )
514- }
515- _ => (
516- collect_connection_wide ( & api, connection_id, schema, table) ,
517- true ,
518- ) ,
533+ } ) ,
534+ _ => collect_connection_wide ( & api, connection_id, schema, table) . map ( |rows| ( rows, true ) ) ,
519535 } ;
536+ spinner. finish_and_clear ( ) ;
537+ let ( rows, multi_table) = result. unwrap_or_else ( |e| e. exit ( ) ) ;
520538
521539 match format {
522540 "json" => println ! ( "{}" , serde_json:: to_string_pretty( & rows) . unwrap( ) ) ,
@@ -872,7 +890,7 @@ mod tests {
872890 . create ( ) ;
873891
874892 let api = Api :: test_new ( & server. url ( ) , "k" , Some ( "ws" ) ) ;
875- let rows = collect_connection_wide ( & api, Some ( "conn-real" ) , None , None ) ;
893+ let rows = collect_connection_wide ( & api, Some ( "conn-real" ) , None , None ) . unwrap ( ) ;
876894 info. assert ( ) ;
877895 idx. assert ( ) ;
878896 assert_eq ! ( rows. len( ) , 1 ) ;
@@ -954,7 +972,7 @@ mod tests {
954972 . create ( ) ;
955973
956974 let api = Api :: test_new ( & server. url ( ) , "k" , Some ( "ws" ) ) ;
957- let rows = collect_connection_wide ( & api, None , None , None ) ;
975+ let rows = collect_connection_wide ( & api, None , None , None ) . unwrap ( ) ;
958976 conns. assert ( ) ;
959977 info_ws. assert ( ) ;
960978 dbs. assert ( ) ;
@@ -1053,7 +1071,7 @@ mod tests {
10531071 . create ( ) ;
10541072
10551073 let api = Api :: test_new ( & server. url ( ) , "k" , Some ( "ws" ) ) ;
1056- let mut rows = collect_connection_wide ( & api, None , None , None ) ;
1074+ let mut rows = collect_connection_wide ( & api, None , None , None ) . unwrap ( ) ;
10571075 conns. assert ( ) ;
10581076 info_ws. assert ( ) ;
10591077 reg_idx. assert ( ) ;
@@ -1087,7 +1105,7 @@ mod tests {
10871105 . create ( ) ;
10881106
10891107 let api = Api :: test_new ( & server. url ( ) , "k" , Some ( "ws1" ) ) ;
1090- let tables = collect_tables ( & api, None , None , None ) ;
1108+ let tables = collect_tables ( & api, None , None , None ) . unwrap ( ) ;
10911109 mock. assert ( ) ;
10921110 assert_eq ! ( tables. len( ) , 2 ) ;
10931111 assert_eq ! ( tables[ 0 ] . table, "a" ) ;
@@ -1107,7 +1125,7 @@ mod tests {
11071125 . create ( ) ;
11081126
11091127 let api = Api :: test_new ( & server. url ( ) , "k" , Some ( "ws" ) ) ;
1110- let rows = list_one_table_scan ( & api, "cid" , "sch" , "tbl" ) ;
1128+ let rows = list_one_table_scan ( & api, "cid" , "sch" , "tbl" ) . unwrap ( ) ;
11111129 mock. assert ( ) ;
11121130 assert ! ( rows. is_empty( ) ) ;
11131131 }
@@ -1134,7 +1152,7 @@ mod tests {
11341152 . create ( ) ;
11351153
11361154 let api = Api :: test_new ( & server. url ( ) , "k" , None ) ;
1137- let rows = list_one_table ( & api, "cid" , "sch" , "tbl" ) ;
1155+ let rows = list_one_table ( & api, "cid" , "sch" , "tbl" ) . unwrap ( ) ;
11381156 mock. assert ( ) ;
11391157 assert_eq ! ( rows. len( ) , 1 ) ;
11401158 assert_eq ! ( rows[ 0 ] . index_name, "ix1" ) ;
@@ -1164,7 +1182,7 @@ mod tests {
11641182 . create ( ) ;
11651183
11661184 let api = Api :: test_new ( & server. url ( ) , "k" , None ) ;
1167- let rows = list_one_table ( & api, "cid" , "sch" , "tbl" ) ;
1185+ let rows = list_one_table ( & api, "cid" , "sch" , "tbl" ) . unwrap ( ) ;
11681186 mock. assert ( ) ;
11691187 assert_eq ! ( rows. len( ) , 1 ) ;
11701188 assert_eq ! ( rows[ 0 ] . status, "building" ) ;
@@ -1181,7 +1199,7 @@ mod tests {
11811199 . create ( ) ;
11821200
11831201 let api = Api :: test_new ( & server. url ( ) , "k" , None ) ;
1184- let rows = list_one_table_scan ( & api, "x" , "s" , "t" ) ;
1202+ let rows = list_one_table_scan ( & api, "x" , "s" , "t" ) . unwrap ( ) ;
11851203 mock. assert ( ) ;
11861204 assert ! ( rows. is_empty( ) ) ;
11871205 }
0 commit comments