1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
#![allow(non_camel_case_types)]

#[cfg(test)]
extern crate temporary;

extern crate libc;

use libc::{c_char, c_double, c_int, c_longlong, c_uchar, c_ulonglong, c_void};

#[repr(C)]
pub struct sqlite3;

#[repr(C)]
pub struct sqlite3_stmt;

pub type sqlite_int64 = c_longlong;
pub type sqlite_uint64 = c_ulonglong;

pub type sqlite3_int64 = sqlite_int64;
pub type sqlite3_uint64 = sqlite_uint64;

pub const SQLITE_OK: c_int = 0;
pub const SQLITE_ERROR: c_int = 1;
pub const SQLITE_INTERNAL: c_int = 2;
pub const SQLITE_PERM: c_int = 3;
pub const SQLITE_ABORT: c_int = 4;
pub const SQLITE_BUSY: c_int = 5;
pub const SQLITE_LOCKED: c_int = 6;
pub const SQLITE_NOMEM: c_int = 7;
pub const SQLITE_READONLY: c_int = 8;
pub const SQLITE_INTERRUPT: c_int = 9;
pub const SQLITE_IOERR: c_int = 10;
pub const SQLITE_CORRUPT: c_int = 11;
pub const SQLITE_NOTFOUND: c_int = 12;
pub const SQLITE_FULL: c_int = 13;
pub const SQLITE_CANTOPEN: c_int = 14;
pub const SQLITE_PROTOCOL: c_int = 15;
pub const SQLITE_EMPTY: c_int = 16;
pub const SQLITE_SCHEMA: c_int = 17;
pub const SQLITE_TOOBIG: c_int = 18;
pub const SQLITE_CONSTRAINT: c_int = 19;
pub const SQLITE_MISMATCH: c_int = 20;
pub const SQLITE_MISUSE: c_int = 21;
pub const SQLITE_NOLFS: c_int = 22;
pub const SQLITE_AUTH: c_int = 23;
pub const SQLITE_FORMAT: c_int = 24;
pub const SQLITE_RANGE: c_int = 25;
pub const SQLITE_NOTADB: c_int = 26;
pub const SQLITE_NOTICE: c_int = 27;
pub const SQLITE_WARNING: c_int = 28;
pub const SQLITE_ROW: c_int = 100;
pub const SQLITE_DONE: c_int = 101;

pub type sqlite3_exec_callback = extern fn(*mut c_void, c_int, *mut *mut c_char,
                                           *mut *mut c_char) -> c_int;

pub type sqlite3_bind_callback = extern fn(*mut c_void);

extern "C" {
    pub fn sqlite3_bind_double(stmt: *mut sqlite3_stmt, i: c_int, value: c_double) -> c_int;
    pub fn sqlite3_bind_int(stmt: *mut sqlite3_stmt, i: c_int, value: c_int) -> c_int;
    pub fn sqlite3_bind_int64(stmt: *mut sqlite3_stmt, i: c_int, value: sqlite3_int64) -> c_int;

    pub fn sqlite3_bind_text(stmt: *mut sqlite3_stmt, i: c_int, data: *const c_char, n: c_int,
                             del: Option<sqlite3_bind_callback>) -> c_int;

    pub fn sqlite3_close(db: *mut sqlite3) -> c_int;
    pub fn sqlite3_column_double(stmt: *mut sqlite3_stmt, i: c_int) -> c_double;
    pub fn sqlite3_column_int(stmt: *mut sqlite3_stmt, i: c_int) -> c_int;
    pub fn sqlite3_column_int64(stmt: *mut sqlite3_stmt, i: c_int) -> sqlite3_int64;
    pub fn sqlite3_column_text(stmt: *mut sqlite3_stmt, i: c_int) -> *const c_uchar;
    pub fn sqlite3_errmsg(db: *mut sqlite3) -> *const c_char;

    pub fn sqlite3_exec(db: *mut sqlite3, sql: *const c_char,
                        callback: Option<sqlite3_exec_callback>, arg: *mut c_void,
                        errmsg: *mut *mut c_char) -> c_int;

    pub fn sqlite3_finalize(stmt: *mut sqlite3_stmt) -> c_int;
    pub fn sqlite3_free(p: *mut c_void);
    pub fn sqlite3_malloc(n: c_int) -> *mut c_void;
    pub fn sqlite3_open(filename: *const c_char, db: *mut *mut sqlite3) -> c_int;

    pub fn sqlite3_prepare(db: *mut sqlite3, sql: *const c_char, n: c_int,
                           stmt: *mut *mut sqlite3_stmt, tail: *mut *const c_char) -> c_int;

    pub fn sqlite3_reset(stmt: *mut sqlite3_stmt) -> c_int;
    pub fn sqlite3_step(stmt: *mut sqlite3_stmt) -> c_int;
}

#[cfg(test)]
mod tests {
    use std::ffi::{CStr, CString};
    use std::ops::Deref;
    use temporary::Directory;

    macro_rules! ok(
        ($result:expr) => ($result.unwrap());
    );

    macro_rules! success(
        ($result:expr) => (assert!($result == ::SQLITE_OK));
    );

    macro_rules! c_str(
        ($pointer:expr) => (CStr::from_ptr($pointer as *const _));
    );

    macro_rules! c_string(
        ($string:expr) => (ok!(CString::new($string)));
    );

    #[test]
    fn workflow() {
        use libc::{c_char, c_int, c_void};

        open(|database| unsafe {
            success!(::sqlite3_exec(database, c_string!(
                "CREATE TABLE `users` (id INTEGER, name VARCHAR(255), age REAL);"
            ).as_ptr(), None, 0 as *mut _, 0 as *mut _));

            {
                let mut statement = 0 as *mut _;
                let mut tail = 0 as *const _;
                success!(::sqlite3_prepare(database, c_string!(
                    "INSERT INTO `users` (id, name, age) VALUES (?, ?, ?);"
                ).as_ptr(), -1, &mut statement, &mut tail));

                let name = c_string!("Alice");
                success!(::sqlite3_bind_int(statement, 1, 1));
                success!(::sqlite3_bind_text(statement, 2, name.as_ptr(), -1, None));
                success!(::sqlite3_bind_double(statement, 3, 20.99));
                assert!(::sqlite3_step(statement) == ::SQLITE_DONE);

                success!(::sqlite3_finalize(statement));
            }

            {
                let mut done = false;
                success!(::sqlite3_exec(database, c_string!(
                    "SELECT * FROM `users`;"
                ).as_ptr(), Some(list), &mut done as *mut _ as *mut _, 0 as *mut _));
                assert!(done);
            }

            {
                let mut statement = 0 as *mut _;
                let mut tail = 0 as *const _;
                success!(::sqlite3_prepare(database, c_string!(
                    "SELECT * FROM `users`;"
                ).as_ptr(), -1, &mut statement, &mut tail));

                assert!(::sqlite3_step(statement) == ::SQLITE_ROW);
                assert!(::sqlite3_column_int(statement, 0) == 1);
                assert!(c_str!(::sqlite3_column_text(statement, 1)) == c_string!("Alice").deref());
                assert!(::sqlite3_column_double(statement, 2) == 20.99);

                assert!(::sqlite3_step(statement) == ::SQLITE_DONE);

                success!(::sqlite3_finalize(statement));
            }
        });

        extern fn list(done: *mut c_void, count: c_int, values: *mut *mut c_char,
                       _: *mut *mut c_char) -> c_int {

            unsafe {
                assert!(count == 3);

                assert!(c_str!(*values) == c_string!("1").deref());
                assert!(c_str!(*values.offset(1)) == c_string!("Alice").deref());
                assert!(c_str!(*values.offset(2)) == c_string!("20.99").deref());

                *(done as *mut bool) = true;
            }
            0
        }
    }

    fn open<F>(mut code: F) where F: FnMut(*mut ::sqlite3) {
        let (path, _directory) = setup();
        let mut database = 0 as *mut _;
        unsafe {
            success!(::sqlite3_open(path.as_ptr(), &mut database));
            code(database);
            success!(::sqlite3_close(database));
        }
    }

    fn setup() -> (CString, Directory) {
        let directory = ok!(Directory::new("sqlite-sys"));
        let path = directory.path().join("database.sqlite3");
        let path = c_string!(ok!(path.to_str()));
        (path, directory)
    }
}