1use akar_common::error::ProcessorError;
2use akar_common::types::Value;
3use akar_common::vector::DataChunk;
4use akar_parser::ast::Expression;
5use akar_processor::physical::write_ops::vectorsimilarityscan::PhysicalVectorSimilarityScan;
6use akar_processor::physical_operator::PhysicalOperatorExec;
7use akar_processor::processor::{StandaloneCallFn, StandaloneCallHandler, StandaloneCallRegistry};
8use std::sync::Arc;
9
10use crate::connection::utils::{ast_constant_to_value, rows_to_datachunk, value_to_csv_string};
11use crate::database::Database;
12
13pub type QueryFn = Arc<dyn Fn(&str) -> Result<crate::query_result::QueryResult, String> + Send + Sync>;
15
16pub struct DbStandaloneCallHandler {
17 database: Arc<Database>,
18 registry: StandaloneCallRegistry,
19}
20
21impl DbStandaloneCallHandler {
22 pub fn new(database: Arc<Database>) -> Self {
23 Self::with_query_executor(database, None)
24 }
25
26 pub fn with_query_executor(database: Arc<Database>, query_fn: Option<QueryFn>) -> Self {
27 let mut registry = StandaloneCallRegistry::new();
28 registry.register(Arc::new(ShowTablesHandler {
29 database: database.clone(),
30 }));
31 registry.register(Arc::new(TableInfoHandler {
32 database: database.clone(),
33 }));
34 registry.register(Arc::new(ShowFunctionsHandler {
35 database: database.clone(),
36 }));
37 registry.register(Arc::new(ShowIndexesHandler {
38 database: database.clone(),
39 }));
40 registry.register(Arc::new(ShowSequencesHandler {
41 database: database.clone(),
42 }));
43 registry.register(Arc::new(ShowMacrosHandler {
44 database: database.clone(),
45 }));
46 registry.register(Arc::new(ShowConnectionHandler {
47 database: database.clone(),
48 }));
49 registry.register(Arc::new(DbVersionHandler));
50 registry.register(Arc::new(CatalogVersionHandler {
51 database: database.clone(),
52 }));
53 registry.register(Arc::new(CurrentSettingHandler {
54 database: database.clone(),
55 }));
56 registry.register(Arc::new(StatsInfoHandler {
57 database: database.clone(),
58 }));
59 registry.register(Arc::new(StorageInfoHandler {
60 database: database.clone(),
61 }));
62 registry.register(Arc::new(ShowAttachedDatabasesHandler));
63 registry.register(Arc::new(BmInfoHandler {
64 database: database.clone(),
65 }));
66 registry.register(Arc::new(FileInfoHandler {
67 database: database.clone(),
68 }));
69 registry.register(Arc::new(FreeSpaceInfoHandler {
70 database: database.clone(),
71 }));
72 registry.register(Arc::new(DiskSizeInfoHandler {
73 database: database.clone(),
74 }));
75 registry.register(Arc::new(StorageVersionHandler));
76 registry.register(Arc::new(ShowLoadedExtensionsHandler {
77 database: database.clone(),
78 }));
79 registry.register(Arc::new(ShowOfficialExtensionsHandler));
80 registry.register(Arc::new(ClearWarningsHandler));
81 registry.register(Arc::new(ShowWarningsHandler));
82 registry.register(Arc::new(ShowProjectedGraphsHandler {
83 database: database.clone(),
84 }));
85 registry.register(Arc::new(ProjectedGraphInfoHandler {
86 database: database.clone(),
87 }));
88 registry.register(Arc::new(DropProjectedGraphHandler {
89 database: database.clone(),
90 }));
91 if let Some(qf) = query_fn {
92 registry.register(Arc::new(ExportCsvHandler { query_fn: qf.clone() }));
93 registry.register(Arc::new(ExportParquetHandler { query_fn: qf }));
94 }
95 Self { database, registry }
96 }
97}
98
99fn eval_ast_expr_to_value(expr: &Expression) -> Value {
100 match expr {
101 Expression::Constant(c) => ast_constant_to_value(c),
102 _ => Value::Null,
103 }
104}
105
106fn eval_ast_expr_to_value_deep(expr: &Expression) -> Value {
111 match expr {
112 Expression::List(items) => Value::List(items.iter().map(eval_ast_expr_to_value_deep).collect()),
113 other => eval_ast_expr_to_value(other),
114 }
115}
116
117fn extract_arg_string(args: &[Expression], idx: usize) -> Result<String, String> {
118 if idx >= args.len() {
119 return Err(format!("Missing argument at index {} ({} provided)", idx, args.len()));
120 }
121 match &args[idx] {
122 Expression::Constant(c) => match c {
123 akar_parser::ast::Constant::String(s) => Ok(s.clone()),
124 _ => Err(format!("Argument {} expected a string literal, got {:?}", idx, c)),
125 },
126 other => Err(format!(
127 "Argument {} expected a constant expression, got: {:?}",
128 idx, other
129 )),
130 }
131}
132
133impl StandaloneCallHandler for DbStandaloneCallHandler {
134 fn execute_call(&self, name: &str, args: &[Expression]) -> Result<Vec<DataChunk>, ProcessorError> {
135 if name.eq_ignore_ascii_case("vector_similarity_scan") {
136 return self.execute_vector_similarity_scan_call(args);
137 }
138
139 if let Some(handler) = self.registry.get(name) {
140 let result_rows = handler.execute(args)?;
141 return Self::format_result(result_rows);
142 }
143
144 let args_vals: Vec<Value> = args.iter().map(eval_ast_expr_to_value).collect();
145 let graph = akar_processor::processor::CatalogGraphSource::new(Some(&self.database.table_catalog()));
146 let registry = self
147 .database
148 .function_registry
149 .lock()
150 .map_err(|e| format!("Lock poisoned: {e}"))?;
151 match registry.execute_table_function(name, &args_vals, Some(&graph)) {
152 Ok(rows) => Self::format_result(rows),
153 Err(original_err) => {
154 let known_calls = [
155 "show_tables",
156 "table_info",
157 "show_functions",
158 "show_indexes",
159 "show_sequences",
160 "show_macros",
161 "show_connection",
162 "db_version",
163 "catalog_version",
164 "current_setting",
165 "stats_info",
166 "storage_info",
167 "show_attached_databases",
168 "bm_info",
169 "file_info",
170 "free_space_info",
171 "disk_size_info",
172 "storage_version",
173 "show_loaded_extensions",
174 "show_official_extensions",
175 "clear_warnings",
176 "show_warnings",
177 "show_projected_graphs",
178 "projected_graph_info",
179 "drop_projected_graph",
180 "export_csv",
181 "export_parquet",
182 ];
183 let lower = name.to_lowercase();
184 let suggestion = known_calls
185 .iter()
186 .find(|k| k.contains(&lower) || lower.contains(**k))
187 .map(|k| format!(" Did you mean CALL {}()?", k))
188 .unwrap_or_default();
189 Err(ProcessorError::Execution(format!(
190 "CALL '{}' failed: {}.{}",
191 name, original_err, suggestion
192 )))
193 }
194 }
195 }
196}
197
198impl DbStandaloneCallHandler {
199 fn execute_vector_similarity_scan_call(&self, args: &[Expression]) -> Result<Vec<DataChunk>, ProcessorError> {
207 if args.len() < 4 {
208 return Err(
209 "vector_similarity_scan requires 4 arguments: table_name, column_name, query_vector, top_k".into(),
210 );
211 }
212
213 let values: Vec<Value> = args.iter().map(eval_ast_expr_to_value_deep).collect();
216
217 let table_name = match &values[0] {
218 Value::String(s) => s.clone(),
219 other => {
220 return Err(format!(
221 "First argument to vector_similarity_scan must be a table name string, got: {other:?}"
222 )
223 .into());
224 }
225 };
226 let column_name = match &values[1] {
227 Value::String(s) => s.clone(),
228 other => {
229 return Err(format!(
230 "Second argument to vector_similarity_scan must be a column name string, got: {other:?}"
231 )
232 .into());
233 }
234 };
235 let query_vector = match &values[2] {
236 Value::List(items) => {
237 let mut vec = Vec::with_capacity(items.len());
238 for item in items {
239 match item {
240 Value::Double(d) => vec.push(*d),
241 Value::Int64(i) => vec.push(*i as f64),
242 Value::Int32(i) => vec.push(*i as f64),
243 Value::Float(f) => vec.push(*f as f64),
244 Value::UInt64(u) => vec.push(*u as f64),
245 _ => return Err("query_vector must be a list of numbers".to_string().into()),
246 }
247 }
248 vec
249 }
250 other => {
251 return Err(format!(
252 "Third argument to vector_similarity_scan must be a list of numbers, got: {other:?}"
253 )
254 .into());
255 }
256 };
257 let top_k = match &values[3] {
258 Value::Int64(k) if *k > 0 => *k as u64,
259 other => {
260 return Err(format!(
261 "Fourth argument to vector_similarity_scan must be a positive integer, got: {other:?}"
262 )
263 .into());
264 }
265 };
266
267 let tc = self.database.table_catalog();
270 let index_name = {
271 let mut by_column = None;
272 let mut first_on_table = None;
273 for entry in tc.all_vector_indexes() {
274 if entry.table_name == table_name {
275 if by_column.is_none() && entry.column_name == column_name {
276 by_column = Some(entry.name.clone());
277 }
278 if first_on_table.is_none() {
279 first_on_table = Some(entry.name.clone());
280 }
281 }
282 }
283 by_column.or(first_on_table).ok_or_else(|| {
284 format!(
285 "No vector index found on table '{}' for column '{}'",
286 table_name, column_name
287 )
288 })?
289 };
290
291 let scan = PhysicalVectorSimilarityScan {
292 index_name,
293 index_id: 0,
294 query_vector,
295 top_k,
296 table_name,
297 table_catalog: Some(tc),
298 };
299 scan.execute(vec![])
300 }
301
302 fn format_result(result_rows: Vec<Vec<Value>>) -> Result<Vec<DataChunk>, ProcessorError> {
303 if result_rows.is_empty() {
304 Ok(vec![])
305 } else {
306 let num_cols = result_rows[0].len();
307 let col_names_strings = (0..num_cols).map(|i| format!("col_{}", i)).collect::<Vec<_>>();
308 let col_names = col_names_strings.iter().map(|s| s.as_str()).collect::<Vec<_>>();
309 Ok(vec![rows_to_datachunk(result_rows, &col_names)?])
310 }
311 }
312}
313
314struct ShowTablesHandler {
315 database: Arc<Database>,
316}
317
318impl StandaloneCallFn for ShowTablesHandler {
319 fn execute(&self, _args: &[Expression]) -> Result<Vec<Vec<Value>>, ProcessorError> {
320 let catalog = self
321 .database
322 .catalog
323 .lock()
324 .map_err(|e| format!("Lock poisoned: {e}"))?;
325 let entries: Vec<Vec<Value>> = catalog
326 .all_entries()
327 .map(|e| {
328 let kind = match e {
329 akar_catalog::CatalogEntry::NodeTable(_) => "NODE",
330 akar_catalog::CatalogEntry::RelTable(_) => "REL",
331 akar_catalog::CatalogEntry::Sequence(_) => "SEQUENCE",
332 akar_catalog::CatalogEntry::Macro(_) => "MACRO",
333 akar_catalog::CatalogEntry::VectorIndex(_) => "VECTOR_INDEX",
334 akar_catalog::CatalogEntry::Foreign(_) => "FOREIGN",
335 };
336 let comment = catalog.get_table_comment(e.name()).cloned().unwrap_or_default();
337 vec![
338 Value::String(e.name().to_string()),
339 Value::String(kind.to_string()),
340 Value::String(comment),
341 ]
342 })
343 .collect();
344 Ok(entries)
345 }
346
347 fn aliases(&self) -> Vec<&'static str> {
348 vec!["show_tables", "show tables", "list_tables", "list tables", "tables"]
349 }
350}
351
352struct TableInfoHandler {
353 database: Arc<Database>,
354}
355
356impl StandaloneCallFn for TableInfoHandler {
357 fn execute(&self, args: &[Expression]) -> Result<Vec<Vec<Value>>, ProcessorError> {
358 let table_name = extract_arg_string(args, 0)?;
359 let cat = self
360 .database
361 .catalog
362 .lock()
363 .map_err(|e| format!("Lock poisoned: {e}"))?;
364 let entry = cat
365 .get_entry_by_name(&table_name)
366 .ok_or_else(|| format!("Table '{table_name}' not found"))?;
367 let columns = entry.columns();
368 let rows: Vec<Vec<Value>> = columns
369 .iter()
370 .map(|col| {
371 vec![
372 Value::String(table_name.clone()),
373 Value::String(col.name.clone()),
374 Value::String(format!("{:?}", col.logical_type)),
375 Value::String(if col.is_primary_key { "NO" } else { "YES" }.into()),
376 ]
377 })
378 .collect();
379 Ok(rows)
380 }
381
382 fn aliases(&self) -> Vec<&'static str> {
383 vec!["table_info"]
384 }
385}
386
387struct ShowFunctionsHandler {
388 database: Arc<Database>,
389}
390
391impl StandaloneCallFn for ShowFunctionsHandler {
392 fn execute(&self, _args: &[Expression]) -> Result<Vec<Vec<Value>>, ProcessorError> {
393 let registry = self
394 .database
395 .function_registry
396 .lock()
397 .map_err(|e| format!("Lock poisoned: {e}"))?;
398 let funcs = registry.list_all();
399 Ok(funcs
400 .into_iter()
401 .map(|(name, kind)| vec![Value::String(name), Value::String(kind)])
402 .collect())
403 }
404
405 fn aliases(&self) -> Vec<&'static str> {
406 vec!["show_functions"]
407 }
408}
409
410struct ShowIndexesHandler {
411 database: Arc<Database>,
412}
413
414impl StandaloneCallFn for ShowIndexesHandler {
415 fn execute(&self, _args: &[Expression]) -> Result<Vec<Vec<Value>>, ProcessorError> {
416 let cat = self
417 .database
418 .catalog
419 .lock()
420 .map_err(|e| format!("Lock poisoned: {e}"))?;
421 let indexes = cat.indexes();
422 Ok(indexes
423 .into_iter()
424 .map(|(name, table, kind, col)| {
425 vec![
426 Value::String(name),
427 Value::String(table),
428 Value::String(kind),
429 Value::String(col),
430 ]
431 })
432 .collect())
433 }
434
435 fn aliases(&self) -> Vec<&'static str> {
436 vec!["show_indexes"]
437 }
438}
439
440struct ShowSequencesHandler {
441 database: Arc<Database>,
442}
443
444impl StandaloneCallFn for ShowSequencesHandler {
445 fn execute(&self, _args: &[Expression]) -> Result<Vec<Vec<Value>>, ProcessorError> {
446 let cat = self
447 .database
448 .catalog
449 .lock()
450 .map_err(|e| format!("Lock poisoned: {e}"))?;
451 let seqs = cat.sequences();
452 Ok(seqs
453 .into_iter()
454 .map(|s| vec![Value::String(s.name.clone()), Value::Int64(s.curr_val())])
455 .collect())
456 }
457
458 fn aliases(&self) -> Vec<&'static str> {
459 vec!["show_sequences"]
460 }
461}
462
463struct ShowMacrosHandler {
464 database: Arc<Database>,
465}
466
467impl StandaloneCallFn for ShowMacrosHandler {
468 fn execute(&self, _args: &[Expression]) -> Result<Vec<Vec<Value>>, ProcessorError> {
469 let cat = self
470 .database
471 .catalog
472 .lock()
473 .map_err(|e| format!("Lock poisoned: {e}"))?;
474 let macros = cat.macros();
475 Ok(macros
476 .into_iter()
477 .map(|m| {
478 vec![
479 Value::String(m.name.clone()),
480 Value::String(
481 m.default_args
482 .iter()
483 .map(|(k, v)| format!("{k}={v}"))
484 .collect::<Vec<_>>()
485 .join(", "),
486 ),
487 ]
488 })
489 .collect())
490 }
491
492 fn aliases(&self) -> Vec<&'static str> {
493 vec!["show_macros"]
494 }
495}
496
497struct ShowConnectionHandler {
498 database: Arc<Database>,
499}
500
501impl StandaloneCallFn for ShowConnectionHandler {
502 fn execute(&self, args: &[Expression]) -> Result<Vec<Vec<Value>>, ProcessorError> {
503 let table_name = extract_arg_string(args, 0)?;
504 let cat = self
505 .database
506 .catalog
507 .lock()
508 .map_err(|e| format!("Lock poisoned: {e}"))?;
509 let info = cat
510 .connection_info(&table_name)
511 .ok_or_else(|| format!("Table '{table_name}' not found"))?;
512 Ok(vec![info])
513 }
514
515 fn aliases(&self) -> Vec<&'static str> {
516 vec!["show_connection"]
517 }
518}
519
520struct DbVersionHandler;
521
522impl StandaloneCallFn for DbVersionHandler {
523 fn execute(&self, _args: &[Expression]) -> Result<Vec<Vec<Value>>, ProcessorError> {
524 let version = env!("CARGO_PKG_VERSION");
525 Ok(vec![vec![Value::String(version.to_string())]])
526 }
527
528 fn aliases(&self) -> Vec<&'static str> {
529 vec!["db_version"]
530 }
531}
532
533struct CatalogVersionHandler {
534 database: Arc<Database>,
535}
536
537impl StandaloneCallFn for CatalogVersionHandler {
538 fn execute(&self, _args: &[Expression]) -> Result<Vec<Vec<Value>>, ProcessorError> {
539 let cat = self
540 .database
541 .catalog
542 .lock()
543 .map_err(|e| format!("Lock poisoned: {e}"))?;
544 let ver = cat.version();
545 Ok(vec![vec![Value::Int64(ver as i64)]])
546 }
547
548 fn aliases(&self) -> Vec<&'static str> {
549 vec!["catalog_version"]
550 }
551}
552
553struct CurrentSettingHandler {
554 database: Arc<Database>,
555}
556
557impl StandaloneCallFn for CurrentSettingHandler {
558 fn execute(&self, args: &[Expression]) -> Result<Vec<Vec<Value>>, ProcessorError> {
559 let key = extract_arg_string(args, 0).unwrap_or_else(|_| String::new());
560 let (k, v) = match key.to_lowercase().as_str() {
561 "spill_threshold" => ("spill_threshold", self.database.effective_spill_threshold().to_string()),
562 "checkpoint_threshold" => (
563 "checkpoint_threshold",
564 self.database.config.checkpoint_threshold.to_string(),
565 ),
566 "buffer_pool_size" => ("buffer_pool_size", self.database.config.buffer_pool_size.to_string()),
567 "max_num_threads" => ("max_num_threads", self.database.config.max_num_threads.to_string()),
568 "concurrent_writes" => (
569 "concurrent_writes",
570 self.database.transaction_manager.allow_concurrent_writes().to_string(),
573 ),
574 "read_only" => ("read_only", self.database.config.read_only.to_string()),
575 _ => (key.as_str(), "UNKNOWN".to_string()),
576 };
577 Ok(vec![vec![Value::String(k.to_string()), Value::String(v)]])
578 }
579
580 fn aliases(&self) -> Vec<&'static str> {
581 vec!["current_setting"]
582 }
583}
584
585struct StatsInfoHandler {
586 database: Arc<Database>,
587}
588
589impl StandaloneCallFn for StatsInfoHandler {
590 fn execute(&self, args: &[Expression]) -> Result<Vec<Vec<Value>>, ProcessorError> {
591 let table_name = extract_arg_string(args, 0)?;
592 let (row_count, storage_size) = {
593 let cat = self
594 .database
595 .catalog
596 .lock()
597 .map_err(|e| format!("Lock poisoned: {e}"))?;
598 let table_id = cat
599 .get_table_id(&table_name)
600 .ok_or_else(|| format!("Table '{table_name}' not found"))?;
601 let stats = self
602 .database
603 .stats_store
604 .lock()
605 .map_err(|e| format!("Lock poisoned: {e}"))?;
606 stats.table_stats_by_id(table_id)
607 };
608 Ok(vec![vec![
609 Value::String(table_name),
610 Value::Int64(row_count as i64),
611 Value::String(crate::connection::utils::format_storage_size(storage_size)),
612 ]])
613 }
614
615 fn aliases(&self) -> Vec<&'static str> {
616 vec!["stats_info"]
617 }
618}
619
620struct StorageInfoHandler {
621 database: Arc<Database>,
622}
623
624impl StandaloneCallFn for StorageInfoHandler {
625 fn execute(&self, _args: &[Expression]) -> Result<Vec<Vec<Value>>, ProcessorError> {
626 let sm = &self.database.storage_manager;
627 let info = sm.storage_info();
628 Ok(vec![vec![
629 Value::String(info.db_path),
630 Value::Int64(info.page_size as i64),
631 Value::Int64(info.total_pages as i64),
632 Value::Int64(info.free_pages as i64),
633 ]])
634 }
635
636 fn aliases(&self) -> Vec<&'static str> {
637 vec!["storage_info"]
638 }
639}
640
641struct ShowAttachedDatabasesHandler;
642
643impl StandaloneCallFn for ShowAttachedDatabasesHandler {
644 fn execute(&self, _args: &[Expression]) -> Result<Vec<Vec<Value>>, ProcessorError> {
645 Ok(vec![vec![
646 Value::String("main".to_string()),
647 Value::String("local".to_string()),
648 ]])
649 }
650
651 fn aliases(&self) -> Vec<&'static str> {
652 vec!["show_attached_databases"]
653 }
654}
655
656struct BmInfoHandler {
657 database: Arc<Database>,
658}
659
660impl StandaloneCallFn for BmInfoHandler {
661 fn execute(&self, _args: &[Expression]) -> Result<Vec<Vec<Value>>, ProcessorError> {
662 let bm = &self.database.storage_manager;
663 let info = bm.buffer_info();
664 Ok(vec![vec![
665 Value::String("buffer_pool".to_string()),
666 Value::Int64(info.total_memory as i64),
667 Value::Int64(info.used_memory as i64),
668 Value::Int64(info.num_pinned as i64),
669 ]])
670 }
671
672 fn aliases(&self) -> Vec<&'static str> {
673 vec!["bm_info"]
674 }
675}
676
677struct FileInfoHandler {
678 database: Arc<Database>,
679}
680
681impl StandaloneCallFn for FileInfoHandler {
682 fn execute(&self, _args: &[Expression]) -> Result<Vec<Vec<Value>>, ProcessorError> {
683 let sm = &self.database.storage_manager;
684 let info = sm.file_info();
685 Ok(vec![vec![
686 Value::Int64(info.total_file_size as i64),
687 Value::Int64(info.num_data_pages as i64),
688 Value::Int64(info.wal_size as i64),
689 ]])
690 }
691
692 fn aliases(&self) -> Vec<&'static str> {
693 vec!["file_info"]
694 }
695}
696
697struct FreeSpaceInfoHandler {
698 database: Arc<Database>,
699}
700
701impl StandaloneCallFn for FreeSpaceInfoHandler {
702 fn execute(&self, _args: &[Expression]) -> Result<Vec<Vec<Value>>, ProcessorError> {
703 let sm = &self.database.storage_manager;
704 let info = sm.fsm_info();
705 Ok(vec![vec![
706 Value::Int64(info.total_free_pages as i64),
707 Value::Int64(info.num_entries as i64),
708 ]])
709 }
710
711 fn aliases(&self) -> Vec<&'static str> {
712 vec!["free_space_info"]
713 }
714}
715
716struct DiskSizeInfoHandler {
717 database: Arc<Database>,
718}
719
720impl StandaloneCallFn for DiskSizeInfoHandler {
721 fn execute(&self, _args: &[Expression]) -> Result<Vec<Vec<Value>>, ProcessorError> {
722 let sm = &self.database.storage_manager;
723 let info = sm.file_info();
724 Ok(vec![vec![
725 Value::Int64(info.total_file_size as i64),
726 Value::Int64(info.num_data_pages as i64),
727 Value::Int64(info.wal_size as i64),
728 ]])
729 }
730
731 fn aliases(&self) -> Vec<&'static str> {
732 vec!["disk_size_info"]
733 }
734}
735
736struct StorageVersionHandler;
737
738impl StandaloneCallFn for StorageVersionHandler {
739 fn execute(&self, _args: &[Expression]) -> Result<Vec<Vec<Value>>, ProcessorError> {
740 Ok(vec![vec![Value::String(
741 akar_storage::version_info::STORAGE_VERSION.to_string(),
742 )]])
743 }
744
745 fn aliases(&self) -> Vec<&'static str> {
746 vec!["storage_version"]
747 }
748}
749
750struct ShowLoadedExtensionsHandler {
751 database: Arc<Database>,
752}
753
754impl StandaloneCallFn for ShowLoadedExtensionsHandler {
755 fn execute(&self, _args: &[Expression]) -> Result<Vec<Vec<Value>>, ProcessorError> {
756 let reg = self
757 .database
758 .extension_registry
759 .lock()
760 .map_err(|e| format!("Lock poisoned: {e}"))?;
761 let names: Vec<Vec<Value>> = reg.names().iter().map(|n| vec![Value::String(n.clone())]).collect();
762 Ok(names)
763 }
764
765 fn aliases(&self) -> Vec<&'static str> {
766 vec!["show_loaded_extensions"]
767 }
768}
769
770struct ShowOfficialExtensionsHandler;
771
772impl StandaloneCallFn for ShowOfficialExtensionsHandler {
773 fn execute(&self, _args: &[Expression]) -> Result<Vec<Vec<Value>>, ProcessorError> {
774 Ok(vec![
775 vec![Value::String("json".into()), Value::String("JSON functions".into())],
776 vec![Value::String("fts".into()), Value::String("Full-Text Search".into())],
777 vec![
778 Value::String("vector".into()),
779 Value::String("Vector similarity search".into()),
780 ],
781 vec![
782 Value::String("httpfs".into()),
783 Value::String("HTTP/S3 file access".into()),
784 ],
785 vec![
786 Value::String("duckdb".into()),
787 Value::String("DuckDB integration".into()),
788 ],
789 vec![
790 Value::String("sqlite".into()),
791 Value::String("SQLite integration".into()),
792 ],
793 vec![
794 Value::String("postgres".into()),
795 Value::String("PostgreSQL integration".into()),
796 ],
797 vec![
798 Value::String("delta".into()),
799 Value::String("Delta Lake integration".into()),
800 ],
801 vec![
802 Value::String("iceberg".into()),
803 Value::String("Apache Iceberg integration".into()),
804 ],
805 vec![
806 Value::String("azure".into()),
807 Value::String("Azure Blob Storage".into()),
808 ],
809 vec![
810 Value::String("unity_catalog".into()),
811 Value::String("Unity Catalog integration".into()),
812 ],
813 vec![Value::String("neo4j".into()), Value::String("Neo4j integration".into())],
814 vec![Value::String("llm".into()), Value::String("LLM integration".into())],
815 vec![Value::String("algo".into()), Value::String("Graph algorithms".into())],
816 ])
817 }
818
819 fn aliases(&self) -> Vec<&'static str> {
820 vec!["show_official_extensions"]
821 }
822}
823
824struct ClearWarningsHandler;
825
826impl StandaloneCallFn for ClearWarningsHandler {
827 fn execute(&self, _args: &[Expression]) -> Result<Vec<Vec<Value>>, ProcessorError> {
828 Ok(vec![vec![Value::String("Warnings cleared".into())]])
829 }
830
831 fn aliases(&self) -> Vec<&'static str> {
832 vec!["clear_warnings"]
833 }
834}
835
836struct ShowWarningsHandler;
837
838impl StandaloneCallFn for ShowWarningsHandler {
839 fn execute(&self, _args: &[Expression]) -> Result<Vec<Vec<Value>>, ProcessorError> {
840 Ok(vec![])
841 }
842
843 fn aliases(&self) -> Vec<&'static str> {
844 vec!["show_warnings"]
845 }
846}
847
848struct ExportCsvHandler {
854 query_fn: QueryFn,
855}
856
857impl StandaloneCallFn for ExportCsvHandler {
858 fn execute(&self, args: &[Expression]) -> Result<Vec<Vec<Value>>, ProcessorError> {
859 let file_path = extract_arg_string(args, 0)?;
860 let query_string = extract_arg_string(args, 1)?;
861 let result = (self.query_fn)(&query_string)?;
862
863 let path = std::path::Path::new(&file_path);
864 let mut w = csv::WriterBuilder::new()
865 .has_headers(true)
866 .from_path(path)
867 .map_err(|e| format!("Cannot create CSV file '{}': {}", file_path, e))?;
868
869 if let Some(first_chunk) = result.chunks.first() {
870 let header: Vec<String> = if first_chunk.field_names.is_empty() {
871 (0..first_chunk.num_fields()).map(|i| format!("column_{}", i)).collect()
872 } else {
873 first_chunk.field_names.clone()
874 };
875 if !header.is_empty() {
876 w.write_record(&header).map_err(|e| format!("CSV write error: {e}"))?;
877 }
878 }
879 for chunk in &result.chunks {
880 for row in 0..chunk.size {
881 let row_values: Vec<String> = (0..chunk.fields.len())
882 .map(|col_idx| {
883 chunk
884 .get_value(col_idx, row)
885 .map(|v| value_to_csv_string(&v))
886 .unwrap_or_default()
887 })
888 .collect();
889 w.write_record(&row_values)
890 .map_err(|e| format!("CSV write error: {e}"))?;
891 }
892 }
893 w.flush().map_err(|e| format!("CSV flush error: {e}"))?;
894 Ok(vec![vec![Value::String(format!(
895 "Exported {} rows to '{}'",
896 result.num_rows, file_path
897 ))]])
898 }
899
900 fn aliases(&self) -> Vec<&'static str> {
901 vec!["export_csv"]
902 }
903}
904
905struct ExportParquetHandler {
906 query_fn: QueryFn,
907}
908
909impl StandaloneCallFn for ExportParquetHandler {
910 fn execute(&self, args: &[Expression]) -> Result<Vec<Vec<Value>>, ProcessorError> {
911 let file_path = extract_arg_string(args, 0)?;
912 let query_string = extract_arg_string(args, 1)?;
913 let result = (self.query_fn)(&query_string)?;
914
915 #[cfg(feature = "parquet-export")]
916 {
917 crate::connection::ddl::write_parquet_to_file(&file_path, &result)
918 .map_err(|e| format!("Parquet export error: {e}"))?;
919 Ok(vec![vec![Value::String(format!(
920 "Exported {} rows to '{}'",
921 result.num_rows, file_path
922 ))]])
923 }
924 #[cfg(not(feature = "parquet-export"))]
925 {
926 let _ = file_path;
927 let _ = query_string;
928 let _ = result;
929 Err("Parquet export requires 'parquet-export' feature. \
930 Build with: cargo build --features parquet-export"
931 .into())
932 }
933 }
934
935 fn aliases(&self) -> Vec<&'static str> {
936 vec!["export_parquet"]
937 }
938}
939
940struct ShowProjectedGraphsHandler {
943 database: Arc<Database>,
944}
945
946impl StandaloneCallFn for ShowProjectedGraphsHandler {
947 fn execute(&self, _args: &[Expression]) -> Result<Vec<Vec<Value>>, ProcessorError> {
948 let cat = self.database.catalog.lock().map_err(|e| format!("Lock error: {e}"))?;
949 let graphs = cat.projected_graph_entries();
950 let rows: Vec<Vec<Value>> = graphs
951 .into_iter()
952 .map(|g| vec![Value::String(g.name.clone()), Value::String(g.entry_type.clone())])
953 .collect();
954 Ok(rows)
955 }
956
957 fn aliases(&self) -> Vec<&'static str> {
958 vec!["show_projected_graphs"]
959 }
960}
961
962struct ProjectedGraphInfoHandler {
963 database: Arc<Database>,
964}
965
966impl StandaloneCallFn for ProjectedGraphInfoHandler {
967 fn execute(&self, args: &[Expression]) -> Result<Vec<Vec<Value>>, ProcessorError> {
968 let graph_name = extract_arg_string(args, 0)?;
969 let cat = self.database.catalog.lock().map_err(|e| format!("Lock error: {e}"))?;
970 let info = cat
971 .get_projected_graph(&graph_name)
972 .ok_or_else(|| format!("Projected graph '{}' not found", graph_name))?;
973 match info.entry_type.as_str() {
974 "NATIVE" => {
975 Ok(vec![vec![
977 Value::String(info.name.clone()),
978 Value::String("NATIVE".into()),
979 Value::String("Node/rel tables defined at creation".into()),
980 ]])
981 }
982 "CYPHER" => {
983 let query = info.cypher_query.clone().unwrap_or_default();
984 Ok(vec![vec![
985 Value::String(info.name.clone()),
986 Value::String("CYPHER".into()),
987 Value::String(query),
988 ]])
989 }
990 other => Err(ProcessorError::Execution(format!(
991 "Unknown projected graph type: {other}"
992 ))),
993 }
994 }
995
996 fn aliases(&self) -> Vec<&'static str> {
997 vec!["projected_graph_info"]
998 }
999}
1000
1001struct DropProjectedGraphHandler {
1002 database: Arc<Database>,
1003}
1004
1005impl StandaloneCallFn for DropProjectedGraphHandler {
1006 fn execute(&self, args: &[Expression]) -> Result<Vec<Vec<Value>>, ProcessorError> {
1007 let graph_name = extract_arg_string(args, 0)?;
1008 let mut cat = self.database.catalog.lock().map_err(|e| format!("Lock error: {e}"))?;
1009 cat.drop_projected_graph(&graph_name)
1010 .map_err(|e| ProcessorError::Execution(format!("{e}")))?;
1011 Ok(vec![vec![Value::String(format!(
1012 "Projected graph '{}' dropped",
1013 graph_name
1014 ))]])
1015 }
1016
1017 fn aliases(&self) -> Vec<&'static str> {
1018 vec!["drop_projected_graph"]
1019 }
1020}