Skip to main content

akar_main/connection/
standalone_call.rs

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
13/// Callback for executing an arbitrary Cypher query string.
14pub 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
106/// Evaluate an expression to a Value, descending into list literals so
107/// `CALL vector_similarity_scan(..., [0.1, 0.2, ...], k)` receives a
108/// `Value::List` for the query vector. Scalars come from
109/// `eval_ast_expr_to_value`; any other expression falls back to `Value::Null`.
110fn 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    /// Execute `CALL vector_similarity_scan(table, column, query_vector, top_k)`.
200    ///
201    /// This is routed here (before the table-function registry) because
202    /// `vector_similarity_scan` is registered as `TableFunction::Custom`, which
203    /// `FunctionRegistry::execute_table_function` rejects (it has no callback).
204    /// We instead build a `PhysicalVectorSimilarityScan` directly against the
205    /// catalog's HNSW index, so the explicit ANN read path actually runs.
206    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        // Evaluate each argument into a Value (handles constant scalars and list
214        // literals; non-constant expressions evaluate to Null).
215        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        // Resolve the vector index by column (first index on the table whose
268        // indexed column matches), falling back to the first index on the table.
269        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                // Read the live runtime toggle (SET concurrent_writes) rather
571                // than the static config so current_setting stays in sync (P52.50).
572                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
848// ==================== Export CSV / Parquet handlers ====================
849// Wraps COPY TO as CALL functions: export_csv / export_parquet
850// Usage:  CALL export_csv('file.csv', 'MATCH (n) RETURN n');
851//         CALL export_parquet('file.parquet', 'MATCH (n) RETURN n');
852
853struct 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
940// ==================== Projected Graph handlers ====================
941
942struct 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                // NATIVE projected graph: return name, type marker
976                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}