1use akar_extension::{Extension, ExtensionContext};
12use std::sync::Arc;
13
14#[cfg(feature = "native")]
21fn postgres_value_to_string(row: &tokio_postgres::Row, i: usize) -> String {
22 let type_name = row.columns().get(i).map(|c| c.type_().name()).unwrap_or("unknown").to_string();
23 if let Ok(Some(v)) = row.try_get::<_, Option<String>>(i) {
24 return v;
25 }
26 if let Ok(Some(v)) = row.try_get::<_, Option<i64>>(i) {
27 return v.to_string();
28 }
29 if let Ok(Some(v)) = row.try_get::<_, Option<i32>>(i) {
30 return v.to_string();
31 }
32 if let Ok(Some(v)) = row.try_get::<_, Option<f64>>(i) {
33 return v.to_string();
34 }
35 if let Ok(Some(v)) = row.try_get::<_, Option<bool>>(i) {
36 return v.to_string();
37 }
38 if row.try_get::<_, Option<i64>>(i).is_ok() {
40 "NULL".to_string()
41 } else {
42 format!("<{type_name}>")
43 }
44}
45
46pub struct PostgresExtension;
48
49impl Default for PostgresExtension {
50 fn default() -> Self {
51 Self::new()
52 }
53}
54
55impl PostgresExtension {
56 pub fn new() -> Self {
57 Self
58 }
59}
60
61impl Extension for PostgresExtension {
62 fn name(&self) -> &'static str {
63 "POSTGRES"
64 }
65
66 fn load(&self, context: &ExtensionContext) -> Result<(), String> {
67 use akar_function::registry::ScalarFunction;
68
69 #[cfg(feature = "native")]
70 {
71 use akar_function::Value;
72
73 let query_fn: Arc<dyn Fn(&[Value]) -> Result<Value, String> + Send + Sync> = Arc::new(|args| {
75 if args.len() < 2 {
76 return Err("sql_query requires (connection_string, sql) arguments".into());
77 }
78 let conn_str = match &args[0] {
79 Value::String(s) => s.clone(),
80 _ => return Err("sql_query: first argument must be a connection string".into()),
81 };
82 let sql = match &args[1] {
83 Value::String(s) => s.clone(),
84 _ => return Err("sql_query: second argument must be a SQL string".into()),
85 };
86
87 let rt = tokio::runtime::Runtime::new().map_err(|e| format!("Failed to create tokio runtime: {e}"))?;
89
90 let (client, connection) = rt
91 .block_on(async { tokio_postgres::connect(&conn_str, tokio_postgres::NoTls).await })
92 .map_err(|e| format!("PostgreSQL connect error: {e}"))?;
93
94 rt.spawn(async move {
96 if let Err(e) = connection.await {
97 tracing::warn!("PostgreSQL connection error: {e}");
98 }
99 });
100
101 let rows = rt
102 .block_on(async { client.query(&sql, &[]).await })
103 .map_err(|e| format!("PostgreSQL query error: {e}"))?;
104
105 let mut parts = Vec::new();
107 for row in &rows {
108 for i in 0..row.len() {
109 parts.push(postgres_value_to_string(row, i));
110 }
111 }
112 if parts.is_empty() {
113 Ok(Value::String("(empty)".into()))
114 } else {
115 Ok(Value::String(parts.join(",")))
116 }
117 });
118
119 context.register_scalar_function(
120 "sql_query",
121 ScalarFunction::CustomScalar {
122 name: "sql_query".into(),
123 execute: query_fn,
124 },
125 );
126
127 tracing::info!("PostgreSQL extension loaded: 1 function registered (tokio-postgres native)");
128 }
129
130 #[cfg(not(feature = "native"))]
131 {
132 context.register_scalar_function(
133 "sql_query",
134 ScalarFunction::CustomScalar {
135 name: "sql_query".into(),
136 execute: Arc::new(|_| Err("PostgreSQL not available (feature 'native' disabled)".into())),
137 },
138 );
139 tracing::info!("PostgreSQL extension loaded: 1 function registered (placeholder)");
140 }
141
142 Ok(())
143 }
144}
145
146#[cfg(test)]
147mod tests {
148 use super::*;
149
150 #[test]
151 fn test_postgres_extension_name() {
152 let ext = PostgresExtension::new();
153 assert_eq!(ext.name(), "POSTGRES");
154 }
155}