Skip to main content

hematite/sql/
interface.rs

1//! High-level SQL interface
2
3use crate::error::{HematiteError, Result};
4use crate::query::{
5    DateTimeValue, DateValue, DecimalValue, JournalMode, TimeValue, TimeWithTimeZoneValue, Value,
6};
7use crate::sql::connection::{Connection, PreparedStatement, Transaction};
8use crate::sql::result::{ExecutedStatement, FromRow, ResultSet, Row, StatementResult};
9use crate::sql::script::ScriptIter;
10
11/// High-level interface for SQL operations
12pub struct Hematite {
13    pub connection: Connection,
14}
15
16impl Hematite {
17    /// Create a new database instance with an in-memory database
18    pub fn new_in_memory() -> Result<Self> {
19        let connection = Connection::new_in_memory()?;
20        Ok(Self { connection })
21    }
22
23    /// Create a new database instance with a file-based database
24    pub fn new(database_path: &str) -> Result<Self> {
25        let connection = Connection::new(database_path)?;
26        Ok(Self { connection })
27    }
28
29    /// Execute a SQL statement and return the result
30    pub fn execute(&mut self, sql: &str) -> Result<StatementResult> {
31        match self.connection.execute_result(sql)? {
32            ExecutedStatement::Statement(result) => Ok(result),
33            ExecutedStatement::Query(_) => Err(HematiteError::ParseError(
34                "Use query() method for SELECT statements".to_string(),
35            )),
36        }
37    }
38
39    /// Execute a SQL query and return the result set
40    pub fn query(&mut self, sql: &str) -> Result<ResultSet> {
41        match self.connection.execute_result(sql)? {
42            ExecutedStatement::Query(result_set) => Ok(result_set),
43            ExecutedStatement::Statement(_) => Err(HematiteError::ParseError(
44                "Use execute() method for non-SELECT statements".to_string(),
45            )),
46        }
47    }
48
49    /// Execute a SQL statement and return the first row of the result
50    pub fn query_row<F, R>(&mut self, sql: &str, f: F) -> Result<Option<R>>
51    where
52        F: FnOnce(&Row) -> Result<R>,
53    {
54        let result_set = self.query(sql)?;
55
56        if let Some(row) = result_set.get_row(0) {
57            Ok(Some(f(row)?))
58        } else {
59            Ok(None)
60        }
61    }
62
63    /// Execute a SQL statement and return the first column of the first row
64    pub fn query_one<T>(&mut self, sql: &str) -> Result<Option<T>>
65    where
66        T: FromValue,
67    {
68        self.query_row(sql, |row| {
69            if let Some(value) = row.get(0) {
70                T::from_value(value)
71            } else {
72                Err(HematiteError::ParseError("No value found".to_string()))
73            }
74        })
75    }
76
77    pub fn query_as<T>(&mut self, sql: &str) -> Result<Vec<T>>
78    where
79        T: FromRow,
80    {
81        self.query(sql)?.to_structs()
82    }
83
84    pub fn query_one_as<T>(&mut self, sql: &str) -> Result<Option<T>>
85    where
86        T: FromRow,
87    {
88        self.query(sql)?.get_row(0).map(T::from_row).transpose()
89    }
90
91    /// Prepare a SQL statement for repeated execution
92    pub fn prepare(&mut self, sql: &str) -> Result<PreparedStatement> {
93        self.connection.prepare(sql)
94    }
95
96    /// Execute a prepared statement
97    pub fn execute_prepared(&mut self, stmt: &mut PreparedStatement) -> Result<StatementResult> {
98        match ExecutedStatement::from_query_result(stmt.execute(&mut self.connection)?) {
99            ExecutedStatement::Statement(result) => Ok(result),
100            ExecutedStatement::Query(_) => Err(HematiteError::ParseError(
101                "Use query_prepared() method for SELECT statements".to_string(),
102            )),
103        }
104    }
105
106    pub fn execute_result(&mut self, sql: &str) -> Result<ExecutedStatement> {
107        self.connection.execute_result(sql)
108    }
109
110    pub fn iter_script<'a>(&'a mut self, sql: &str) -> Result<ScriptIter<'a>> {
111        self.connection.iter_script(sql)
112    }
113
114    /// Begin a new transaction
115    pub fn transaction(&'_ mut self) -> Result<Transaction<'_>> {
116        self.connection.begin_transaction()
117    }
118
119    pub fn journal_mode(&self) -> Result<JournalMode> {
120        self.connection.journal_mode()
121    }
122
123    pub fn set_journal_mode(&mut self, journal_mode: JournalMode) -> Result<()> {
124        self.connection.set_journal_mode(journal_mode)
125    }
126
127    pub fn checkpoint_wal(&mut self) -> Result<()> {
128        self.connection.checkpoint_wal()
129    }
130
131    /// Execute multiple SQL statements in sequence
132    pub fn execute_batch(&mut self, sql: &str) -> Result<()> {
133        self.connection.execute_batch(sql)
134    }
135}
136
137/// Trait for converting database values to Rust types
138pub trait FromValue: Sized {
139    fn from_value(value: &Value) -> Result<Self>;
140}
141
142impl FromValue for i32 {
143    fn from_value(value: &Value) -> Result<Self> {
144        match value {
145            Value::Integer(i) => Ok(*i),
146            _ => Err(HematiteError::ParseError(format!(
147                "Expected INT, found {:?}",
148                value
149            ))),
150        }
151    }
152}
153
154impl FromValue for String {
155    fn from_value(value: &Value) -> Result<Self> {
156        match value {
157            Value::Text(s) => Ok(s.clone()),
158            Value::Enum(s) => Ok(s.clone()),
159            Value::Decimal(s) => Ok(s.to_string()),
160            Value::Date(s) => Ok(s.to_string()),
161            Value::Time(s) => Ok(s.to_string()),
162            Value::DateTime(s) => Ok(s.to_string()),
163            Value::TimeWithTimeZone(s) => Ok(s.to_string()),
164            _ => Err(HematiteError::ParseError(format!(
165                "Expected TEXT, found {:?}",
166                value
167            ))),
168        }
169    }
170}
171
172impl FromValue for bool {
173    fn from_value(value: &Value) -> Result<Self> {
174        match value {
175            Value::Boolean(b) => Ok(*b),
176            _ => Err(HematiteError::ParseError(format!(
177                "Expected BOOLEAN, found {:?}",
178                value
179            ))),
180        }
181    }
182}
183
184impl FromValue for f64 {
185    fn from_value(value: &Value) -> Result<Self> {
186        match value {
187            Value::Float32(f) => Ok(*f as f64),
188            Value::Float(f) => Ok(*f),
189            Value::Integer(i) => Ok(*i as f64), // Allow integer to float conversion
190            Value::UInteger(i) => Ok(*i as f64),
191            Value::BigInt(i) => Ok(*i as f64),
192            Value::UBigInt(i) => Ok(*i as f64),
193            Value::Int128(i) => Ok(*i as f64),
194            Value::UInt128(i) => Ok(*i as f64),
195            _ => Err(HematiteError::ParseError(format!(
196                "Expected FLOAT, found {:?}",
197                value
198            ))),
199        }
200    }
201}
202
203impl FromValue for Value {
204    fn from_value(value: &Value) -> Result<Self> {
205        Ok(value.clone())
206    }
207}
208
209impl FromValue for i64 {
210    fn from_value(value: &Value) -> Result<Self> {
211        match value {
212            Value::BigInt(i) => Ok(*i),
213            Value::Integer(i) => Ok(*i as i64),
214            Value::UInteger(i) => Ok(*i as i64),
215            _ => Err(HematiteError::ParseError(format!(
216                "Expected INT64, found {:?}",
217                value
218            ))),
219        }
220    }
221}
222
223impl FromValue for i128 {
224    fn from_value(value: &Value) -> Result<Self> {
225        match value {
226            Value::Int128(i) => Ok(*i),
227            Value::BigInt(i) => Ok(*i as i128),
228            Value::Integer(i) => Ok(*i as i128),
229            Value::UInteger(i) => Ok(*i as i128),
230            Value::UBigInt(i) => Ok(*i as i128),
231            _ => Err(HematiteError::ParseError(format!(
232                "Expected INT128, found {:?}",
233                value
234            ))),
235        }
236    }
237}
238
239impl FromValue for u32 {
240    fn from_value(value: &Value) -> Result<Self> {
241        match value {
242            Value::UInteger(i) => Ok(*i),
243            Value::Integer(i) if *i >= 0 => Ok(*i as u32),
244            _ => Err(HematiteError::ParseError(format!(
245                "Expected UINT, found {:?}",
246                value
247            ))),
248        }
249    }
250}
251
252impl FromValue for u64 {
253    fn from_value(value: &Value) -> Result<Self> {
254        match value {
255            Value::UBigInt(i) => Ok(*i),
256            Value::UInteger(i) => Ok(*i as u64),
257            Value::Integer(i) if *i >= 0 => Ok(*i as u64),
258            _ => Err(HematiteError::ParseError(format!(
259                "Expected UINT64, found {:?}",
260                value
261            ))),
262        }
263    }
264}
265
266impl FromValue for u128 {
267    fn from_value(value: &Value) -> Result<Self> {
268        match value {
269            Value::UInt128(i) => Ok(*i),
270            Value::UBigInt(i) => Ok(*i as u128),
271            Value::UInteger(i) => Ok(*i as u128),
272            Value::Integer(i) if *i >= 0 => Ok(*i as u128),
273            _ => Err(HematiteError::ParseError(format!(
274                "Expected UINT128, found {:?}",
275                value
276            ))),
277        }
278    }
279}
280
281impl FromValue for DecimalValue {
282    fn from_value(value: &Value) -> Result<Self> {
283        match value {
284            Value::Decimal(value) => Ok(value.clone()),
285            _ => Err(HematiteError::ParseError(format!(
286                "Expected DECIMAL, found {:?}",
287                value
288            ))),
289        }
290    }
291}
292
293impl FromValue for Vec<u8> {
294    fn from_value(value: &Value) -> Result<Self> {
295        match value {
296            Value::Blob(value) => Ok(value.clone()),
297            _ => Err(HematiteError::ParseError(format!(
298                "Expected BLOB, found {:?}",
299                value
300            ))),
301        }
302    }
303}
304
305impl FromValue for DateValue {
306    fn from_value(value: &Value) -> Result<Self> {
307        match value {
308            Value::Date(value) => Ok(*value),
309            _ => Err(HematiteError::ParseError(format!(
310                "Expected DATE, found {:?}",
311                value
312            ))),
313        }
314    }
315}
316
317impl FromValue for DateTimeValue {
318    fn from_value(value: &Value) -> Result<Self> {
319        match value {
320            Value::DateTime(value) => Ok(*value),
321            _ => Err(HematiteError::ParseError(format!(
322                "Expected DATETIME, found {:?}",
323                value
324            ))),
325        }
326    }
327}
328
329impl FromValue for TimeValue {
330    fn from_value(value: &Value) -> Result<Self> {
331        match value {
332            Value::Time(value) => Ok(*value),
333            _ => Err(HematiteError::ParseError(format!(
334                "Expected TIME, found {:?}",
335                value
336            ))),
337        }
338    }
339}
340
341impl FromValue for TimeWithTimeZoneValue {
342    fn from_value(value: &Value) -> Result<Self> {
343        match value {
344            Value::TimeWithTimeZone(value) => Ok(*value),
345            _ => Err(HematiteError::ParseError(format!(
346                "Expected TIME WITH TIME ZONE, found {:?}",
347                value
348            ))),
349        }
350    }
351}
352
353/// Builder pattern for creating database connections
354pub struct HematiteBuilder {
355    database_path: String,
356}
357
358impl HematiteBuilder {
359    pub fn new() -> Self {
360        Self {
361            database_path: "_test.db".to_string(),
362        }
363    }
364
365    pub fn database_path(mut self, path: &str) -> Self {
366        self.database_path = path.to_string();
367        self
368    }
369
370    pub fn build(self) -> Result<Hematite> {
371        Hematite::new(&self.database_path)
372    }
373}
374
375impl Default for HematiteBuilder {
376    fn default() -> Self {
377        Self::new()
378    }
379}