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 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}