1use akar_extension::{Extension, ExtensionContext};
11use std::sync::Arc;
12
13pub struct SqliteExtension;
15
16impl Default for SqliteExtension {
17 fn default() -> Self {
18 Self::new()
19 }
20}
21
22impl SqliteExtension {
23 pub fn new() -> Self {
24 Self
25 }
26}
27
28impl Extension for SqliteExtension {
29 fn name(&self) -> &'static str {
30 "SQLITE"
31 }
32
33 fn load(&self, context: &ExtensionContext) -> Result<(), String> {
34 #[allow(unused_imports)]
35 use akar_function::Value;
36 use akar_function::registry::{ScalarFunction, TableFunction};
37
38 #[cfg(feature = "bundled")]
40 {
41 let query_fn: Arc<dyn Fn(&[Value]) -> Result<Value, String> + Send + Sync> = Arc::new(|args| {
42 if args.len() < 2 {
43 return Err("sqlite_query requires (path, sql) arguments".into());
44 }
45 let path = match &args[0] {
46 Value::String(s) => s.clone(),
47 _ => return Err("sqlite_query: first argument must be a path string".into()),
48 };
49 let sql = match &args[1] {
50 Value::String(s) => s.clone(),
51 _ => return Err("sqlite_query: second argument must be a SQL string".into()),
52 };
53
54 let conn =
55 rusqlite::Connection::open(&path).map_err(|e| format!("Failed to open SQLite DB '{path}': {e}"))?;
56
57 let mut stmt = conn.prepare(&sql).map_err(|e| format!("SQLite prepare error: {e}"))?;
58
59 let col_count = stmt.column_count();
60 let mut rows = stmt.query([]).map_err(|e| format!("SQLite query error: {e}"))?;
61
62 if let Some(row) = rows.next().map_err(|e| format!("SQLite row error: {e}"))? {
64 let mut parts = Vec::with_capacity(col_count);
65 for i in 0..col_count {
66 let val: String = row.get::<_, String>(i).unwrap_or_else(|_| "NULL".into());
67 parts.push(val);
68 }
69 Ok(Value::String(parts.join(",")))
70 } else {
71 Ok(Value::String("(empty)".into()))
72 }
73 });
74
75 context.register_scalar_function(
76 "sqlite_query",
77 ScalarFunction::CustomScalar {
78 name: "sqlite_query".into(),
79 execute: query_fn,
80 },
81 );
82
83 let scan_fn: Arc<dyn Fn(&[Value], &mut akar_function::DataChunk) -> Result<(), String> + Send + Sync> =
85 Arc::new(|args, _chunk| {
86 if args.len() < 2 {
87 return Err("sqlite_scan requires (path, table) arguments".into());
88 }
89 let _path = match &args[0] {
90 Value::String(s) => s.clone(),
91 _ => return Err("sqlite_scan: first argument must be a path string".into()),
92 };
93 let _table = match &args[1] {
94 Value::String(s) => s.clone(),
95 _ => return Err("sqlite_scan: second argument must be a table name".into()),
96 };
97
98 Ok(())
100 });
101
102 context.register_table_function(
103 "sqlite_scan",
104 TableFunction::CustomTable {
105 name: "sqlite_scan".into(),
106 execute: scan_fn,
107 },
108 );
109
110 tracing::info!("SQLite extension loaded: 2 functions registered (rusqlite native)");
111 }
112
113 #[cfg(not(feature = "bundled"))]
114 {
115 context.register_scalar_function(
116 "sqlite_query",
117 ScalarFunction::CustomScalar {
118 name: "sqlite_query".into(),
119 execute: Arc::new(|_| Err("SQLite not available (feature 'bundled' disabled)".into())),
120 },
121 );
122 context.register_table_function(
123 "sqlite_scan",
124 TableFunction::CustomTable {
125 name: "sqlite_scan".into(),
126 execute: Arc::new(|_, _| Err("SQLite not available (feature 'bundled' disabled)".into())),
127 },
128 );
129 tracing::info!("SQLite extension loaded: 2 functions registered (placeholder)");
130 }
131
132 Ok(())
133 }
134}
135
136#[cfg(test)]
137mod tests {
138 use super::*;
139
140 #[test]
141 fn test_sqlite_extension_name() {
142 let ext = SqliteExtension::new();
143 assert_eq!(ext.name(), "SQLITE");
144 }
145}