sqlite3/
types.rs

1//! Type conversions for binding parameters and getting query results.
2
3use super::{PreparedStatement, ResultRow,
4            ColIx, ParamIx};
5use super::{
6    SqliteResult, SqliteErrorCode, SqliteError,
7};
8use super::ColumnType::SQLITE_NULL;
9
10use time;
11
12/// Values that can be bound to parameters in prepared statements.
13pub trait ToSql {
14    /// Bind the `ix`th parameter to this value (`self`).
15    fn to_sql(&self, s: &mut PreparedStatement, ix: ParamIx) -> SqliteResult<()>;
16}
17
18/// A trait for result values from a query.
19///
20/// cf [sqlite3 result values][column].
21///
22/// *inspired by sfackler's FromSql (and some haskell bindings?)*
23///
24/// [column]: http://www.sqlite.org/c3ref/column_blob.html
25///
26///   - *TODO: many more implementors, including Option<T>*
27pub trait FromSql : Sized {
28    /// Try to extract a `Self` type value from the `col`th colum of a `ResultRow`.
29    fn from_sql(row: &ResultRow, col: ColIx) -> SqliteResult<Self>;
30}
31
32impl ToSql for i32 {
33    fn to_sql(&self, s: &mut PreparedStatement, ix: ParamIx) -> SqliteResult<()> {
34        s.bind_int(ix, *self)
35    }
36}
37
38impl FromSql for i32 {
39    fn from_sql(row: &ResultRow, col: ColIx) -> SqliteResult<i32> { Ok(row.column_int(col)) }
40}
41
42impl ToSql for i64 {
43    fn to_sql(&self, s: &mut PreparedStatement, ix: ParamIx) -> SqliteResult<()> {
44        s.bind_int64(ix, *self)
45    }
46}
47
48impl FromSql for i64 {
49    fn from_sql(row: &ResultRow, col: ColIx) -> SqliteResult<i64> { Ok(row.column_int64(col)) }
50}
51
52impl ToSql for f64 {
53    fn to_sql(&self, s: &mut PreparedStatement, ix: ParamIx) -> SqliteResult<()> {
54        s.bind_double(ix, *self)
55    }
56}
57
58impl FromSql for f64 {
59    fn from_sql(row: &ResultRow, col: ColIx) -> SqliteResult<f64> { Ok(row.column_double(col)) }
60}
61
62impl ToSql for bool {
63    fn to_sql(&self, s: &mut PreparedStatement, ix: ParamIx) -> SqliteResult<()> {
64        s.bind_int(ix, if *self { 1 } else { 0 })
65    }
66}
67
68impl FromSql for bool {
69    fn from_sql(row: &ResultRow, col: ColIx) -> SqliteResult<bool> { Ok(row.column_int(col)!=0) }
70}
71
72impl<T: ToSql + Clone> ToSql for Option<T> {
73    fn to_sql(&self, s: &mut PreparedStatement, ix: ParamIx) -> SqliteResult<()> {
74        match (*self).clone() {
75            Some(x) => x.to_sql(s, ix),
76            None => s.bind_null(ix)
77        }
78    }
79}
80
81impl<T: FromSql + Clone> FromSql for Option<T> {
82    fn from_sql(row: &ResultRow, col: ColIx) -> SqliteResult<Option<T>> {
83        match row.column_type(col) {
84            SQLITE_NULL => Ok(None),
85            _ => FromSql::from_sql(row, col).map(|x| Some(x))
86        }
87    }
88}
89
90impl ToSql for String {
91    fn to_sql(&self, s: &mut PreparedStatement, ix: ParamIx) -> SqliteResult<()> {
92        s.bind_text(ix, (*self).as_ref())
93    }
94}
95
96
97impl FromSql for String {
98    fn from_sql(row: &ResultRow, col: ColIx) -> SqliteResult<String> {
99        Ok(row.column_text(col).unwrap_or(String::new()))
100    }
101}
102
103impl<'a> ToSql for &'a [u8] {
104    fn to_sql(&self, s: &mut PreparedStatement, ix: ParamIx) -> SqliteResult<()> {
105        s.bind_blob(ix, *self)
106    }
107}
108
109impl FromSql for Vec<u8> {
110    fn from_sql(row: &ResultRow, col: ColIx) -> SqliteResult<Vec<u8>> {
111        Ok(row.column_blob(col).unwrap_or(Vec::new()))
112    }
113}
114
115
116/// Format of sqlite date strings
117///
118/// From [Date And Time Functions][lang_datefunc]:
119/// > The datetime() function returns "YYYY-MM-DD HH:MM:SS"
120/// [lang_datefunc]: http://www.sqlite.org/lang_datefunc.html
121pub static SQLITE_TIME_FMT: &'static str = "%F %T";
122
123impl FromSql for time::Tm {
124    fn from_sql(row: &ResultRow, col: ColIx) -> SqliteResult<time::Tm> {
125        let txt = row.column_text(col).unwrap_or(String::new());
126        Ok( try!(time::strptime(txt.as_ref(), SQLITE_TIME_FMT)) )
127    }
128}
129
130impl From<time::ParseError> for SqliteError {
131    fn from(err: time::ParseError) -> SqliteError {
132        SqliteError {
133            kind: SqliteErrorCode::SQLITE_MISMATCH,
134            desc: "Time did not match expected format",
135            detail: Some(format!("Time parsing error: {}", err)),
136        }
137    }
138}
139
140impl ToSql for time::Timespec {
141    fn to_sql(&self, s: &mut PreparedStatement, ix: ParamIx) -> SqliteResult<()> {
142        let timestr = time::at_utc(*self).strftime(SQLITE_TIME_FMT)
143            .unwrap() // unit tests ensure SQLITE_TIME_FMT is ok
144            .to_string();
145        s.bind_text(ix, timestr.as_ref())
146    }
147}
148
149impl FromSql for time::Timespec {
150    /// TODO: propagate error message
151    fn from_sql(row: &ResultRow, col: ColIx) -> SqliteResult<time::Timespec> {
152        let tmo: SqliteResult<time::Tm> = FromSql::from_sql(row, col);
153        tmo.map(|tm| tm.to_timespec())
154    }
155}
156
157#[cfg(test)]
158mod tests {
159    use time::Tm;
160    use super::super::{DatabaseConnection, SqliteResult, ResultSet};
161    use super::super::{ResultRowAccess};
162
163    fn with_query<T, F>(sql: &str, mut f: F) -> SqliteResult<T>
164        where F: FnMut(&mut ResultSet) -> T
165    {
166        let db = try!(DatabaseConnection::in_memory());
167        let mut s = try!(db.prepare(sql));
168        let mut rows = s.execute();
169        Ok(f(&mut rows))
170    }
171
172    #[test]
173    fn get_tm() {
174        fn go() -> SqliteResult<()> {
175            let conn = try!(DatabaseConnection::in_memory());
176            let mut stmt = try!(
177                conn.prepare("select datetime('2001-01-01', 'weekday 3', '3 hours')"));
178            let mut results = stmt.execute();
179            match results.step() {
180                Ok(Some(ref mut row)) => {
181                    assert_eq!(
182                        row.get::<u32, Tm>(0),
183                        Tm { tm_sec: 0,
184                             tm_min: 0,
185                             tm_hour: 3,
186                             tm_mday: 3,
187                             tm_mon: 0,
188                             tm_year: 101,
189                             tm_wday: 0,
190                             tm_yday: 0,
191                             tm_isdst: 0,
192                             tm_utcoff: 0,
193                             tm_nsec: 0
194                        });
195                    Ok(())
196                },
197                Ok(None) => panic!("no row"),
198                Err(oops) =>  panic!("error: {:?}", oops)
199            }
200        }
201        go().unwrap();
202    }
203
204    #[test]
205    fn get_invalid_tm() {
206        with_query("select 'not a time'", |results| {
207            match results.step() {
208                Ok(Some(ref mut row)) => {
209                    let x : SqliteResult<Tm> = row.get_opt(0u32);
210                    assert!(x.is_err());
211                },
212                Ok(None) => panic!("no row"),
213                Err(oops) =>  panic!("error: {:?}", oops)
214            };
215        }).unwrap();
216    }
217
218    #[test]
219    fn select_blob() {
220        with_query("select x'ff0db0'", |results| {
221            match results.step() {
222                Ok(Some(ref mut row)) => {
223                    let x : SqliteResult<Vec<u8>> = row.get_opt(0u32);
224                    assert_eq!(x.ok().unwrap(), [0xff, 0x0d, 0xb0].to_vec());
225                },
226                Ok(None) => panic!("no row"),
227                Err(oops) =>  panic!("error: {:?}", oops)
228            };
229        }).unwrap();
230    }
231}
232
233// Local Variables:
234// flycheck-rust-crate-root: "lib.rs"
235// End: