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    formatter: 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            if request.ui_mode == UiMode::Tui {
83                sql::execute_with_formatter(
84                    db,
85                    cmd,
86                    batch_mode,
87                    request.ui_mode,
88                    writer,
89                    formatter,
90                    None,
91                    request.limit,
92                    request.quiet,
93                )
94            } else {
95                sql::execute_with_formatter(
96                    db,
97                    cmd,
98                    batch_mode,
99                    UiMode::Batch,
100                    writer,
101                    formatter,
102                    None,
103                    request.limit,
104                    request.quiet,
105                )
106            }
107        }
108        AdminCommand::Kv(cmd) => {
109            if request.ui_mode == UiMode::Tui {
110                let columns = kv_columns_for(&cmd);
111                kv::execute_tui(
112                    db,
113                    cmd,
114                    batch_mode,
115                    request.output,
116                    columns,
117                    request.limit,
118                    request.quiet,
119                    request.connection_label,
120                    request.data_dir.clone(),
121                )
122            } else {
123                let columns = kv_columns_for(&cmd);
124                let mut streaming = StreamingWriter::new(writer, formatter, columns, request.limit)
125                    .with_quiet(request.quiet);
126                kv::execute(db, cmd, &mut streaming)
127            }
128        }
129        AdminCommand::Vector(cmd) => {
130            if request.ui_mode == UiMode::Tui {
131                let columns = vector_columns_for(&cmd);
132                vector::execute_tui(
133                    db,
134                    cmd,
135                    batch_mode,
136                    request.output,
137                    columns,
138                    request.limit,
139                    request.quiet,
140                    request.connection_label,
141                    request.data_dir.clone(),
142                )
143            } else {
144                let columns = vector_columns_for(&cmd);
145                let mut streaming = StreamingWriter::new(writer, formatter, columns, request.limit)
146                    .with_quiet(request.quiet);
147                vector::execute(db, cmd, batch_mode, &mut streaming)
148            }
149        }
150        AdminCommand::Hnsw(cmd) => {
151            if request.ui_mode == UiMode::Tui {
152                let columns = hnsw_columns_for(&cmd);
153                hnsw::execute_tui(
154                    db,
155                    cmd,
156                    batch_mode,
157                    request.output,
158                    columns,
159                    request.limit,
160                    request.quiet,
161                    request.connection_label,
162                    request.data_dir.clone(),
163                )
164            } else {
165                let columns = hnsw_columns_for(&cmd);
166                let mut streaming = StreamingWriter::new(writer, formatter, columns, request.limit)
167                    .with_quiet(request.quiet);
168                hnsw::execute(db, cmd, &mut streaming)
169            }
170        }
171        AdminCommand::Columnar(cmd) => {
172            if request.ui_mode == UiMode::Tui {
173                columnar::execute_tui(
174                    db,
175                    cmd,
176                    batch_mode,
177                    request.output,
178                    request.limit,
179                    request.quiet,
180                    request.connection_label,
181                    request.data_dir.clone(),
182                )
183            } else {
184                columnar::execute_with_formatter(
185                    db,
186                    cmd,
187                    batch_mode,
188                    writer,
189                    formatter,
190                    request.limit,
191                    request.quiet,
192                )
193            }
194        }
195        AdminCommand::Lifecycle(cmd) => {
196            lifecycle::execute_with_formatter(&cmd, request.data_dir.as_deref(), writer, formatter)
197        }
198    }
199}
200
201pub async fn execute_remote_action<W: Write>(
202    client: &HttpClient,
203    batch_mode: &BatchMode,
204    request: AdminRequest,
205    writer: &mut W,
206    formatter: Box<dyn Formatter>,
207) -> Result<()> {
208    ensure_action_supported(request.action)?;
209    ensure_action_matches_command(request.action, &request.command)?;
210
211    match request.command {
212        AdminCommand::Sql(cmd) => {
213            sql::execute_remote_with_formatter(
214                client,
215                &cmd,
216                batch_mode,
217                request.ui_mode,
218                writer,
219                formatter,
220                None,
221                request.limit,
222                request.quiet,
223            )
224            .await
225        }
226        AdminCommand::Kv(cmd) => {
227            if request.ui_mode == UiMode::Tui {
228                let columns = kv_columns_for(&cmd);
229                kv::execute_remote_tui(
230                    client,
231                    &cmd,
232                    columns,
233                    request.output,
234                    request.limit,
235                    request.quiet,
236                    request.connection_label,
237                    None,
238                )
239                .await
240            } else {
241                kv::execute_remote_with_formatter(
242                    client,
243                    &cmd,
244                    writer,
245                    formatter,
246                    request.limit,
247                    request.quiet,
248                )
249                .await
250            }
251        }
252        AdminCommand::Vector(cmd) => {
253            if request.ui_mode == UiMode::Tui {
254                let columns = vector_columns_for(&cmd);
255                vector::execute_remote_tui(
256                    client,
257                    &cmd,
258                    batch_mode,
259                    columns,
260                    request.output,
261                    request.limit,
262                    request.quiet,
263                    request.connection_label,
264                    None,
265                )
266                .await
267            } else {
268                vector::execute_remote_with_formatter(
269                    client,
270                    &cmd,
271                    batch_mode,
272                    writer,
273                    formatter,
274                    request.limit,
275                    request.quiet,
276                )
277                .await
278            }
279        }
280        AdminCommand::Hnsw(cmd) => {
281            if request.ui_mode == UiMode::Tui {
282                let columns = hnsw_columns_for(&cmd);
283                hnsw::execute_remote_tui(
284                    client,
285                    &cmd,
286                    columns,
287                    request.output,
288                    request.limit,
289                    request.quiet,
290                    request.connection_label,
291                    None,
292                )
293                .await
294            } else {
295                hnsw::execute_remote_with_formatter(
296                    client,
297                    &cmd,
298                    writer,
299                    formatter,
300                    request.limit,
301                    request.quiet,
302                )
303                .await
304            }
305        }
306        AdminCommand::Columnar(cmd) => {
307            if request.ui_mode == UiMode::Tui {
308                columnar::execute_remote_tui(
309                    client,
310                    &cmd,
311                    batch_mode,
312                    request.output,
313                    request.limit,
314                    request.quiet,
315                    request.connection_label,
316                    None,
317                )
318                .await
319            } else {
320                columnar::execute_remote_with_formatter(
321                    client,
322                    &cmd,
323                    batch_mode,
324                    writer,
325                    formatter,
326                    request.limit,
327                    request.quiet,
328                )
329                .await
330            }
331        }
332        AdminCommand::Lifecycle(cmd) => {
333            lifecycle::execute_remote_with_formatter(client, &cmd, writer, formatter).await
334        }
335    }
336}
337
338fn ensure_action_supported(action: AdminAction) -> Result<()> {
339    let _ = action;
340    Ok(())
341}
342
343fn ensure_action_matches_command(action: AdminAction, command: &AdminCommand) -> Result<()> {
344    let ok = match command {
345        AdminCommand::Sql(_) => matches!(
346            action,
347            AdminAction::Read | AdminAction::Create | AdminAction::Update | AdminAction::Delete
348        ),
349        AdminCommand::Kv(cmd) => matches_kv_action(action, cmd),
350        AdminCommand::Vector(cmd) => matches_vector_action(action, cmd),
351        AdminCommand::Hnsw(cmd) => matches_hnsw_action(action, cmd),
352        AdminCommand::Columnar(cmd) => matches_columnar_action(action, cmd),
353        AdminCommand::Lifecycle(cmd) => lifecycle_action_for(cmd) == action,
354    };
355
356    if ok {
357        Ok(())
358    } else {
359        Err(CliError::InvalidArgument(format!(
360            "Admin action '{}' does not match the selected command.",
361            action_label(action)
362        )))
363    }
364}
365
366fn lifecycle_action_for(command: &LifecycleCommand) -> AdminAction {
367    match command {
368        LifecycleCommand::Archive => AdminAction::Archive,
369        LifecycleCommand::Restore => AdminAction::Restore,
370        LifecycleCommand::Backup => AdminAction::Backup,
371        LifecycleCommand::Export => AdminAction::Export,
372    }
373}
374
375fn matches_kv_action(action: AdminAction, command: &KvCommand) -> bool {
376    match command {
377        KvCommand::Get { .. } | KvCommand::List { .. } | KvCommand::Txn(_) => {
378            matches!(action, AdminAction::Read)
379        }
380        KvCommand::Put { .. } => matches!(action, AdminAction::Create | AdminAction::Update),
381        KvCommand::Delete { .. } => matches!(action, AdminAction::Delete),
382    }
383}
384
385fn matches_vector_action(action: AdminAction, command: &VectorCommand) -> bool {
386    match command {
387        VectorCommand::Search { .. } => matches!(action, AdminAction::Read),
388        VectorCommand::Upsert { .. } => matches!(action, AdminAction::Create | AdminAction::Update),
389        VectorCommand::Delete { .. } => matches!(action, AdminAction::Delete),
390    }
391}
392
393fn matches_hnsw_action(action: AdminAction, command: &HnswCommand) -> bool {
394    match command {
395        HnswCommand::Stats { .. } => matches!(action, AdminAction::Read),
396        HnswCommand::Create { .. } => matches!(action, AdminAction::Create),
397        HnswCommand::Drop { .. } => matches!(action, AdminAction::Delete),
398    }
399}
400
401fn matches_columnar_action(action: AdminAction, command: &ColumnarCommand) -> bool {
402    match command {
403        ColumnarCommand::Scan { .. }
404        | ColumnarCommand::Stats { .. }
405        | ColumnarCommand::List
406        | ColumnarCommand::Index(IndexCommand::List { .. }) => {
407            matches!(action, AdminAction::Read)
408        }
409        ColumnarCommand::Ingest { .. } | ColumnarCommand::Index(IndexCommand::Create { .. }) => {
410            matches!(action, AdminAction::Create)
411        }
412        ColumnarCommand::Index(IndexCommand::Drop { .. }) => {
413            matches!(action, AdminAction::Delete)
414        }
415    }
416}
417
418fn kv_columns_for(cmd: &KvCommand) -> Vec<crate::models::Column> {
419    match cmd {
420        KvCommand::Put { .. } | KvCommand::Delete { .. } => kv::kv_status_columns(),
421        KvCommand::Get { .. } | KvCommand::List { .. } => kv::kv_columns(),
422        KvCommand::Txn(_) => kv::kv_columns(),
423    }
424}
425
426fn vector_columns_for(cmd: &VectorCommand) -> Vec<crate::models::Column> {
427    match cmd {
428        VectorCommand::Search { .. } => vector::vector_search_columns(),
429        VectorCommand::Upsert { .. } | VectorCommand::Delete { .. } => {
430            vector::vector_status_columns()
431        }
432    }
433}
434
435fn hnsw_columns_for(cmd: &HnswCommand) -> Vec<crate::models::Column> {
436    match cmd {
437        HnswCommand::Stats { .. } => hnsw::hnsw_stats_columns(),
438        HnswCommand::Create { .. } | HnswCommand::Drop { .. } => hnsw::hnsw_status_columns(),
439    }
440}
441
442fn action_label(action: AdminAction) -> &'static str {
443    match action {
444        AdminAction::Read => "read",
445        AdminAction::Create => "create",
446        AdminAction::Update => "update",
447        AdminAction::Delete => "delete",
448        AdminAction::Archive => "archive",
449        AdminAction::Restore => "restore",
450        AdminAction::Backup => "backup",
451        AdminAction::Export => "export",
452    }
453}