Skip to main content

windjammer_runtime/
db.rs

1//! Database operations (SQLite and PostgreSQL)
2//!
3//! Windjammer's `std::db` module maps to these functions.
4//! Note: This is a simplified wrapper. For production use, consider using sqlx directly.
5
6use std::collections::HashMap;
7
8/// Database type
9#[derive(Debug, Clone, PartialEq)]
10pub enum DatabaseType {
11    SQLite,
12    Postgres,
13}
14
15/// Database connection
16#[derive(Debug)]
17pub struct Connection {
18    connection_string: String,
19    db_type: DatabaseType,
20}
21
22/// Database row
23#[derive(Debug, Clone)]
24pub struct Row {
25    pub columns: HashMap<String, String>,
26}
27
28impl Row {
29    /// Get column value as string
30    pub fn get(&self, column: &str) -> Option<String> {
31        self.columns.get(column).cloned()
32    }
33
34    /// Get column value as integer
35    pub fn get_int(&self, column: &str) -> Option<i64> {
36        self.get(column)?.parse().ok()
37    }
38
39    /// Get column value as float
40    pub fn get_float(&self, column: &str) -> Option<f64> {
41        self.get(column)?.parse().ok()
42    }
43
44    /// Get column value as boolean
45    pub fn get_bool(&self, column: &str) -> Option<bool> {
46        self.get(column)?.parse().ok()
47    }
48}
49
50/// Open a SQLite database connection
51pub fn open_sqlite(path: &str) -> Result<Connection, String> {
52    Ok(Connection {
53        connection_string: path.to_string(),
54        db_type: DatabaseType::SQLite,
55    })
56}
57
58/// Open a PostgreSQL database connection
59/// Format: "postgres://user:password@host:port/database"
60pub fn open_postgres(connection_string: &str) -> Result<Connection, String> {
61    if !connection_string.starts_with("postgres://")
62        && !connection_string.starts_with("postgresql://")
63    {
64        return Err(
65            "PostgreSQL connection string must start with postgres:// or postgresql://".to_string(),
66        );
67    }
68
69    Ok(Connection {
70        connection_string: connection_string.to_string(),
71        db_type: DatabaseType::Postgres,
72    })
73}
74
75/// Open a database connection (auto-detect type)
76pub fn open(connection_string: &str) -> Result<Connection, String> {
77    if connection_string.starts_with("postgres://")
78        || connection_string.starts_with("postgresql://")
79    {
80        open_postgres(connection_string)
81    } else {
82        open_sqlite(connection_string)
83    }
84}
85
86impl Connection {
87    /// Get database type
88    pub fn db_type(&self) -> &DatabaseType {
89        &self.db_type
90    }
91
92    /// Get connection string
93    pub fn connection_string(&self) -> &str {
94        &self.connection_string
95    }
96
97    /// Execute a query that returns rows
98    pub fn query(&self, sql: &str, _params: &[String]) -> Result<Vec<Row>, String> {
99        // Placeholder implementation
100        // In a full implementation, this would use sqlx to execute the query
101        Err(format!(
102            "Database query not yet fully implemented. SQL: {} (type: {:?})",
103            sql, self.db_type
104        ))
105    }
106
107    /// Execute a statement that doesn't return rows (INSERT, UPDATE, DELETE)
108    pub fn execute(&self, sql: &str, _params: &[String]) -> Result<u64, String> {
109        // Placeholder implementation
110        // In a full implementation, this would use sqlx to execute the statement
111        Err(format!(
112            "Database execute not yet fully implemented. SQL: {} (type: {:?})",
113            sql, self.db_type
114        ))
115    }
116
117    /// Begin a transaction
118    pub fn begin_transaction(&self) -> Result<(), String> {
119        Err("Transactions not yet implemented".to_string())
120    }
121
122    /// Commit a transaction
123    pub fn commit(&self) -> Result<(), String> {
124        Err("Transactions not yet implemented".to_string())
125    }
126
127    /// Rollback a transaction
128    pub fn rollback(&self) -> Result<(), String> {
129        Err("Transactions not yet implemented".to_string())
130    }
131
132    /// Close the connection
133    pub fn close(self) -> Result<(), String> {
134        Ok(())
135    }
136}
137
138#[cfg(test)]
139mod tests {
140    use super::*;
141
142    #[test]
143    fn test_open_sqlite() {
144        let conn = open_sqlite(":memory:");
145        assert!(conn.is_ok());
146        let conn = conn.unwrap();
147        assert_eq!(conn.db_type(), &DatabaseType::SQLite);
148    }
149
150    #[test]
151    fn test_open_postgres() {
152        let conn = open_postgres("postgres://user:pass@localhost/db");
153        assert!(conn.is_ok());
154        let conn = conn.unwrap();
155        assert_eq!(conn.db_type(), &DatabaseType::Postgres);
156    }
157
158    #[test]
159    fn test_open_postgres_invalid() {
160        let conn = open_postgres("invalid://connection");
161        assert!(conn.is_err());
162    }
163
164    #[test]
165    fn test_open_auto_detect() {
166        // Should detect SQLite
167        let sqlite_conn = open(":memory:").unwrap();
168        assert_eq!(sqlite_conn.db_type(), &DatabaseType::SQLite);
169
170        // Should detect Postgres
171        let pg_conn = open("postgres://localhost/db").unwrap();
172        assert_eq!(pg_conn.db_type(), &DatabaseType::Postgres);
173
174        // Should detect Postgres with postgresql:// prefix
175        let pg_conn2 = open("postgresql://localhost/db").unwrap();
176        assert_eq!(pg_conn2.db_type(), &DatabaseType::Postgres);
177    }
178
179    #[test]
180    fn test_row_get() {
181        let mut columns = HashMap::new();
182        columns.insert("name".to_string(), "Alice".to_string());
183        columns.insert("age".to_string(), "30".to_string());
184        columns.insert("active".to_string(), "true".to_string());
185        columns.insert("score".to_string(), "95.5".to_string());
186
187        let row = Row { columns };
188        assert_eq!(row.get("name"), Some("Alice".to_string()));
189        assert_eq!(row.get_int("age"), Some(30));
190        assert_eq!(row.get_bool("active"), Some(true));
191        assert_eq!(row.get_float("score"), Some(95.5));
192    }
193
194    #[test]
195    fn test_query_placeholder() {
196        let conn = open(":memory:").unwrap();
197        let result = conn.query("SELECT * FROM users", &[]);
198        // Should return error for now (not fully implemented)
199        assert!(result.is_err());
200    }
201
202    #[test]
203    fn test_execute_placeholder() {
204        let conn = open_postgres("postgres://localhost/test").unwrap();
205        let result = conn.execute("INSERT INTO users (name) VALUES ('Alice')", &[]);
206        // Should return error for now (not fully implemented)
207        assert!(result.is_err());
208    }
209}