Skip to main content

alopex_cli/tui/admin/
actions.rs

1//! Admin action dispatcher for lifecycle operations.
2#![allow(dead_code)]
3
4use std::collections::HashSet;
5use std::io::Write;
6use std::path::PathBuf;
7
8use alopex_embedded::Database;
9
10use crate::batch::BatchMode;
11use crate::cli::{
12    ColumnarCommand, HnswCommand, IndexCommand, KvCommand, LifecycleCommand, OutputFormat,
13    SqlCommand, VectorCommand,
14};
15use crate::client::http::HttpClient;
16use crate::commands::{columnar, hnsw, kv, lifecycle, sql, vector};
17use crate::error::{CliError, Result};
18use crate::output::formatter::Formatter;
19use crate::streaming::writer::StreamingWriter;
20use crate::ui::mode::UiMode;
21
22#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
23pub enum AdminAction {
24    Read,
25    Create,
26    Update,
27    Delete,
28    Archive,
29    Restore,
30    Backup,
31    Export,
32}
33
34pub fn all_actions() -> HashSet<AdminAction> {
35    [
36        AdminAction::Read,
37        AdminAction::Create,
38        AdminAction::Update,
39        AdminAction::Delete,
40        AdminAction::Archive,
41        AdminAction::Restore,
42        AdminAction::Backup,
43        AdminAction::Export,
44    ]
45    .into_iter()
46    .collect()
47}
48
49#[derive(Debug)]
50pub enum AdminCommand {
51    Sql(SqlCommand),
52    Kv(KvCommand),
53    Vector(VectorCommand),
54    Hnsw(HnswCommand),
55    Columnar(ColumnarCommand),
56    Lifecycle(LifecycleCommand),
57}
58
59pub struct AdminRequest {
60    pub action: AdminAction,
61    pub command: AdminCommand,
62    pub limit: Option<usize>,
63    pub quiet: bool,
64    pub ui_mode: UiMode,
65    pub connection_label: String,
66    pub output: OutputFormat,
67    pub data_dir: Option<PathBuf>,
68}
69
70pub fn execute_local_action<W: Write>(
71    db: &Database,
72    batch_mode: &BatchMode,
73    request: AdminRequest,
74    writer: &mut W,
75    make_formatter: &mut dyn FnMut() -> Box<dyn Formatter>,
76) -> Result<()> {
77    ensure_action_supported(request.action)?;
78    ensure_action_matches_command(request.action, &request.command)?;
79
80    match request.command {
81        AdminCommand::Sql(cmd) => {
82            let ui_mode = if request.ui_mode == UiMode::Tui {
83                request.ui_mode
84            } else {
85                UiMode::Batch
86            };
87            sql::execute_with_formatter_factory(
88                db,
89                cmd,
90                batch_mode,
91                ui_mode,
92                writer,
93                make_formatter,
94                None,
95                request.limit,
96                request.quiet,
97            )
98        }
99        AdminCommand::Kv(cmd) => {
100            if request.ui_mode == UiMode::Tui {
101                let columns = kv_columns_for(&cmd);
102                kv::execute_tui(
103                    db,
104                    cmd,
105                    batch_mode,
106                    request.output,
107                    columns,
108                    request.limit,
109                    request.quiet,
110                    request.connection_label,
111                    request.data_dir.clone(),
112                )
113            } else {
114                let columns = kv_columns_for(&cmd);
115                let mut streaming =
116                    StreamingWriter::new(writer, make_formatter(), columns, request.limit)
117                        .with_quiet(request.quiet);
118                kv::execute(db, cmd, &mut streaming)
119            }
120        }
121        AdminCommand::Vector(cmd) => {
122            if request.ui_mode == UiMode::Tui {
123                let columns = vector_columns_for(&cmd);
124                vector::execute_tui(
125                    db,
126                    cmd,
127                    batch_mode,
128                    request.output,
129                    columns,
130                    request.limit,
131                    request.quiet,
132                    request.connection_label,
133                    request.data_dir.clone(),
134                )
135            } else {
136                let columns = vector_columns_for(&cmd);
137                let mut streaming =
138                    StreamingWriter::new(writer, make_formatter(), columns, request.limit)
139                        .with_quiet(request.quiet);
140                vector::execute(db, cmd, batch_mode, &mut streaming)
141            }
142        }
143        AdminCommand::Hnsw(cmd) => {
144            if request.ui_mode == UiMode::Tui {
145                let columns = hnsw_columns_for(&cmd);
146                hnsw::execute_tui(
147                    db,
148                    cmd,
149                    batch_mode,
150                    request.output,
151                    columns,
152                    request.limit,
153                    request.quiet,
154                    request.connection_label,
155                    request.data_dir.clone(),
156                )
157            } else {
158                let columns = hnsw_columns_for(&cmd);
159                let mut streaming =
160                    StreamingWriter::new(writer, make_formatter(), columns, request.limit)
161                        .with_quiet(request.quiet);
162                hnsw::execute(db, cmd, &mut streaming)
163            }
164        }
165        AdminCommand::Columnar(cmd) => {
166            if request.ui_mode == UiMode::Tui {
167                columnar::execute_tui(
168                    db,
169                    cmd,
170                    batch_mode,
171                    request.output,
172                    request.limit,
173                    request.quiet,
174                    request.connection_label,
175                    request.data_dir.clone(),
176                )
177            } else {
178                columnar::execute_with_formatter(
179                    db,
180                    cmd,
181                    batch_mode,
182                    writer,
183                    make_formatter(),
184                    request.limit,
185                    request.quiet,
186                )
187            }
188        }
189        AdminCommand::Lifecycle(cmd) => lifecycle::execute_with_formatter(
190            &cmd,
191            request.data_dir.as_deref(),
192            writer,
193            make_formatter(),
194        ),
195    }
196}
197
198pub async fn execute_remote_action<W: Write>(
199    client: &HttpClient,
200    batch_mode: &BatchMode,
201    request: AdminRequest,
202    writer: &mut W,
203    make_formatter: &mut dyn FnMut() -> Box<dyn Formatter>,
204) -> Result<()> {
205    ensure_action_supported(request.action)?;
206    ensure_action_matches_command(request.action, &request.command)?;
207
208    match request.command {
209        AdminCommand::Sql(cmd) => {
210            sql::execute_remote_with_formatter_factory(
211                client,
212                &cmd,
213                batch_mode,
214                request.ui_mode,
215                writer,
216                make_formatter,
217                None,
218                request.limit,
219                request.quiet,
220            )
221            .await
222        }
223        AdminCommand::Kv(cmd) => {
224            if request.ui_mode == UiMode::Tui {
225                let columns = kv_columns_for(&cmd);
226                kv::execute_remote_tui(
227                    client,
228                    &cmd,
229                    columns,
230                    request.output,
231                    request.limit,
232                    request.quiet,
233                    request.connection_label,
234                    None,
235                )
236                .await
237            } else {
238                kv::execute_remote_with_formatter(
239                    client,
240                    &cmd,
241                    writer,
242                    make_formatter(),
243                    request.limit,
244                    request.quiet,
245                )
246                .await
247            }
248        }
249        AdminCommand::Vector(cmd) => {
250            if request.ui_mode == UiMode::Tui {
251                let columns = vector_columns_for(&cmd);
252                vector::execute_remote_tui(
253                    client,
254                    &cmd,
255                    batch_mode,
256                    columns,
257                    request.output,
258                    request.limit,
259                    request.quiet,
260                    request.connection_label,
261                    None,
262                )
263                .await
264            } else {
265                vector::execute_remote_with_formatter(
266                    client,
267                    &cmd,
268                    batch_mode,
269                    writer,
270                    make_formatter(),
271                    request.limit,
272                    request.quiet,
273                )
274                .await
275            }
276        }
277        AdminCommand::Hnsw(cmd) => {
278            if request.ui_mode == UiMode::Tui {
279                let columns = hnsw_columns_for(&cmd);
280                hnsw::execute_remote_tui(
281                    client,
282                    &cmd,
283                    columns,
284                    request.output,
285                    request.limit,
286                    request.quiet,
287                    request.connection_label,
288                    None,
289                )
290                .await
291            } else {
292                hnsw::execute_remote_with_formatter(
293                    client,
294                    &cmd,
295                    writer,
296                    make_formatter(),
297                    request.limit,
298                    request.quiet,
299                )
300                .await
301            }
302        }
303        AdminCommand::Columnar(cmd) => {
304            if request.ui_mode == UiMode::Tui {
305                columnar::execute_remote_tui(
306                    client,
307                    &cmd,
308                    batch_mode,
309                    request.output,
310                    request.limit,
311                    request.quiet,
312                    request.connection_label,
313                    None,
314                )
315                .await
316            } else {
317                columnar::execute_remote_with_formatter(
318                    client,
319                    &cmd,
320                    batch_mode,
321                    writer,
322                    make_formatter(),
323                    request.limit,
324                    request.quiet,
325                )
326                .await
327            }
328        }
329        AdminCommand::Lifecycle(cmd) => {
330            lifecycle::execute_remote_with_formatter(
331                client,
332                &cmd,
333                lifecycle::RemoteLifecycleSupport::unknown(),
334                writer,
335                make_formatter(),
336            )
337            .await
338        }
339    }
340}
341
342fn ensure_action_supported(action: AdminAction) -> Result<()> {
343    let _ = action;
344    Ok(())
345}
346
347fn ensure_action_matches_command(action: AdminAction, command: &AdminCommand) -> Result<()> {
348    let ok = match command {
349        AdminCommand::Sql(_) => matches!(
350            action,
351            AdminAction::Read | AdminAction::Create | AdminAction::Update | AdminAction::Delete
352        ),
353        AdminCommand::Kv(cmd) => matches_kv_action(action, cmd),
354        AdminCommand::Vector(cmd) => matches_vector_action(action, cmd),
355        AdminCommand::Hnsw(cmd) => matches_hnsw_action(action, cmd),
356        AdminCommand::Columnar(cmd) => matches_columnar_action(action, cmd),
357        AdminCommand::Lifecycle(cmd) => lifecycle_action_for(cmd) == action,
358    };
359
360    if ok {
361        Ok(())
362    } else {
363        Err(CliError::InvalidArgument(format!(
364            "Admin action '{}' does not match the selected command.",
365            action_label(action)
366        )))
367    }
368}
369
370fn lifecycle_action_for(command: &LifecycleCommand) -> AdminAction {
371    match command {
372        LifecycleCommand::Archive => AdminAction::Archive,
373        LifecycleCommand::Restore { .. } => AdminAction::Restore,
374        LifecycleCommand::Backup { .. } => AdminAction::Backup,
375        LifecycleCommand::Export => AdminAction::Export,
376    }
377}
378
379fn matches_kv_action(action: AdminAction, command: &KvCommand) -> bool {
380    match command {
381        KvCommand::Get { .. } | KvCommand::List { .. } | KvCommand::Txn(_) => {
382            matches!(action, AdminAction::Read)
383        }
384        KvCommand::Put { .. } => matches!(action, AdminAction::Create | AdminAction::Update),
385        KvCommand::Delete { .. } => matches!(action, AdminAction::Delete),
386    }
387}
388
389fn matches_vector_action(action: AdminAction, command: &VectorCommand) -> bool {
390    match command {
391        VectorCommand::Search { .. } => matches!(action, AdminAction::Read),
392        VectorCommand::Upsert { .. } => matches!(action, AdminAction::Create | AdminAction::Update),
393        VectorCommand::Delete { .. } => matches!(action, AdminAction::Delete),
394    }
395}
396
397fn matches_hnsw_action(action: AdminAction, command: &HnswCommand) -> bool {
398    match command {
399        HnswCommand::Stats { .. } => matches!(action, AdminAction::Read),
400        HnswCommand::Create { .. } => matches!(action, AdminAction::Create),
401        HnswCommand::Drop { .. } => matches!(action, AdminAction::Delete),
402    }
403}
404
405fn matches_columnar_action(action: AdminAction, command: &ColumnarCommand) -> bool {
406    match command {
407        ColumnarCommand::Scan { .. }
408        | ColumnarCommand::Stats { .. }
409        | ColumnarCommand::List
410        | ColumnarCommand::Index(IndexCommand::List { .. }) => {
411            matches!(action, AdminAction::Read)
412        }
413        ColumnarCommand::Ingest { .. } | ColumnarCommand::Index(IndexCommand::Create { .. }) => {
414            matches!(action, AdminAction::Create)
415        }
416        ColumnarCommand::Index(IndexCommand::Drop { .. }) => {
417            matches!(action, AdminAction::Delete)
418        }
419    }
420}
421
422fn kv_columns_for(cmd: &KvCommand) -> Vec<crate::models::Column> {
423    match cmd {
424        KvCommand::Put { .. } | KvCommand::Delete { .. } => kv::kv_status_columns(),
425        KvCommand::Get { .. } | KvCommand::List { .. } => kv::kv_columns(),
426        KvCommand::Txn(_) => kv::kv_columns(),
427    }
428}
429
430fn vector_columns_for(cmd: &VectorCommand) -> Vec<crate::models::Column> {
431    match cmd {
432        VectorCommand::Search { .. } => vector::vector_search_columns(),
433        VectorCommand::Upsert { .. } | VectorCommand::Delete { .. } => {
434            vector::vector_status_columns()
435        }
436    }
437}
438
439fn hnsw_columns_for(cmd: &HnswCommand) -> Vec<crate::models::Column> {
440    match cmd {
441        HnswCommand::Stats { .. } => hnsw::hnsw_stats_columns(),
442        HnswCommand::Create { .. } | HnswCommand::Drop { .. } => hnsw::hnsw_status_columns(),
443    }
444}
445
446fn action_label(action: AdminAction) -> &'static str {
447    match action {
448        AdminAction::Read => "read",
449        AdminAction::Create => "create",
450        AdminAction::Update => "update",
451        AdminAction::Delete => "delete",
452        AdminAction::Archive => "archive",
453        AdminAction::Restore => "restore",
454        AdminAction::Backup => "backup",
455        AdminAction::Export => "export",
456    }
457}