1#![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}