├── .gitignore ├── Cargo.toml ├── .travis.yml ├── README.md ├── LICENSE └── src ├── ffi.rs ├── types.rs ├── database.rs ├── cursor.rs └── sqlite3.rs /.gitignore: -------------------------------------------------------------------------------- 1 | libsqlite* 2 | *.o 3 | *.dSYM 4 | sqlite 5 | target 6 | 7 | # Libraries should ignore Cargo.lock 8 | Cargo.lock 9 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "sqlite3" 3 | version = "0.1.0" 4 | authors = [ "" ] 5 | 6 | [lib] 7 | name = "sqlite3" 8 | 9 | [dependencies] 10 | libc = "0.1.5" 11 | log = "0.3.1" 12 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | before_install: 2 | - sudo apt-get update 3 | install: 4 | - sudo apt-get install sqlite3 5 | - curl https://static.rust-lang.org/rustup.sh | sudo bash 6 | notifications: 7 | email: false 8 | script: 9 | - /usr/local/bin/cargo build --verbose 10 | - /usr/local/bin/cargo test --verbose 11 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # SQLite3 Bindings for Rust 2 | 3 | To compile use `rustc src/sqlite3.rs` or if you have Cargo installed `cargo build`. 4 | 5 | The interface is currently evolving both along with Rust and as I think of 6 | better ways to implement various behaviors. If you have ideas about better 7 | behavior for anything in this binding please feel free to open an issue. 8 | 9 | Build status: [![Build Status](https://travis-ci.org/linuxfood/rustsqlite.svg?branch=master)](https://travis-ci.org/linuxfood/rustsqlite) 10 | 11 | ## Maintainers 12 | 13 | These are the brave souls who have kept this project alive in some fashion or another. 14 | Note to future maintainers: if you feel you've made significant contributions to the project, 15 | do add yourself to the list. 16 | 17 | - [@linuxfood](https://github.com/linuxfood) originally wrote the bindings, then wandered away - 2011 18 | - [@kud1ing](https://github.com/kud1ing) took over maintainership for quite awhile - 2011-2014 19 | - [@lifthrasiir](https://github.com/lifthrasiir) now carries the torch - 2014 20 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | 2 | The included SQLite3 source (sqlite3.c, sqlite3.h) are licensed under the following: 3 | 4 | The author disclaims copyright to this source code. In place of 5 | a legal notice, here is a blessing: 6 | 7 | May you do good and not evil. 8 | May you find forgiveness for yourself and forgive others. 9 | May you share freely, never taking more than you give. 10 | 11 | The remainder of the source code carry the following license: 12 | 13 | Copyright (c) 2011, Brian Smith 14 | All rights reserved. 15 | 16 | Redistribution and use in source and binary forms, with or without 17 | modification, are permitted provided that the following conditions are met: 18 | 19 | * Redistributions of source code must retain the above copyright notice, 20 | this list of conditions and the following disclaimer. 21 | 22 | * Redistributions in binary form must reproduce the above copyright notice, 23 | this list of conditions and the following disclaimer in the documentation 24 | and/or other materials provided with the distribution. 25 | 26 | * Neither the name of Brian Smith nor the names of its contributors 27 | may be used to endorse or promote products derived from this software 28 | without specific prior written permission. 29 | 30 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 31 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 32 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 33 | ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE 34 | LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 35 | CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 36 | SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 37 | INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 38 | CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 39 | ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 40 | POSSIBILITY OF SUCH DAMAGE. 41 | 42 | -------------------------------------------------------------------------------- /src/ffi.rs: -------------------------------------------------------------------------------- 1 | /* 2 | ** Copyright (c) 2011, Brian Smith 3 | ** All rights reserved. 4 | ** 5 | ** Redistribution and use in source and binary forms, with or without 6 | ** modification, are permitted provided that the following conditions are met: 7 | ** 8 | ** * Redistributions of source code must retain the above copyright notice, 9 | ** this list of conditions and the following disclaimer. 10 | ** 11 | ** * Redistributions in binary form must reproduce the above copyright notice, 12 | ** this list of conditions and the following disclaimer in the documentation 13 | ** and/or other materials provided with the distribution. 14 | ** 15 | ** * Neither the name of Brian Smith nor the names of its contributors 16 | ** may be used to endorse or promote products derived from this software 17 | ** without specific prior written permission. 18 | ** 19 | ** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 20 | ** AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 21 | ** IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 22 | ** ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE 23 | ** LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 24 | ** CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 25 | ** SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 26 | ** INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 27 | ** CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 28 | ** ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 29 | ** POSSIBILITY OF SUCH DAMAGE. 30 | */ 31 | 32 | use libc::*; 33 | use types::*; 34 | 35 | #[link(name = "sqlite3")] 36 | extern { 37 | pub fn sqlite3_open(path: *const c_char, hnd: *mut *mut dbh) -> ResultCode; 38 | pub fn sqlite3_close(dbh: *mut dbh) -> ResultCode; 39 | pub fn sqlite3_errmsg(dbh: *mut dbh) -> *const c_char; 40 | pub fn sqlite3_changes(dbh: *mut dbh) -> c_int; 41 | pub fn sqlite3_last_insert_rowid(dbh: *mut dbh) -> i64; 42 | pub fn sqlite3_complete(sql: *const c_char) -> c_int; 43 | 44 | pub fn sqlite3_prepare_v2( 45 | hnd: *mut dbh, 46 | sql: *const c_char, 47 | sql_len: c_int, 48 | shnd: *mut *mut stmt, 49 | tail: *mut *const c_char 50 | ) -> ResultCode; 51 | 52 | pub fn sqlite3_exec( 53 | dbh: *mut dbh, 54 | sql: *const c_char, 55 | cb: *mut _notused, 56 | d: *mut _notused, 57 | err: *mut *mut c_char 58 | ) -> ResultCode; 59 | 60 | pub fn sqlite3_step(sth: *mut stmt) -> ResultCode; 61 | pub fn sqlite3_reset(sth: *mut stmt) -> ResultCode; 62 | pub fn sqlite3_finalize(sth: *mut stmt) -> ResultCode; 63 | pub fn sqlite3_clear_bindings(sth: *mut stmt) -> ResultCode; 64 | 65 | pub fn sqlite3_column_name(sth: *mut stmt, icol: c_int) -> *const c_char; 66 | pub fn sqlite3_column_type(sth: *mut stmt, icol: c_int) -> c_int; 67 | pub fn sqlite3_data_count(sth: *mut stmt) -> c_int; 68 | pub fn sqlite3_column_bytes(sth: *mut stmt, icol: c_int) -> c_int; 69 | pub fn sqlite3_column_blob(sth: *mut stmt, icol: c_int) -> *const u8; 70 | 71 | pub fn sqlite3_column_text(sth: *mut stmt, icol: c_int) -> *const c_char; 72 | pub fn sqlite3_column_double(sth: *mut stmt, icol: c_int) -> f64; 73 | pub fn sqlite3_column_int(sth: *mut stmt, icol: c_int) -> c_int; 74 | pub fn sqlite3_column_int64(sth: *mut stmt, icol: c_int) -> i64; 75 | 76 | pub fn sqlite3_bind_blob(sth: *mut stmt, icol: c_int, buf: *const u8, buflen: c_int, d: *mut c_void) -> ResultCode; 77 | pub fn sqlite3_bind_text(sth: *mut stmt, icol: c_int, buf: *const c_char, buflen: c_int, d: *mut c_void) -> ResultCode; 78 | pub fn sqlite3_bind_null(sth: *mut stmt, icol: c_int) -> ResultCode; 79 | pub fn sqlite3_bind_int(sth: *mut stmt, icol: c_int, v: c_int) -> ResultCode; 80 | pub fn sqlite3_bind_int64(sth: *mut stmt, icol: c_int, v: i64) -> ResultCode; 81 | pub fn sqlite3_bind_double(sth: *mut stmt, icol: c_int, value: f64) -> ResultCode; 82 | pub fn sqlite3_bind_parameter_index(sth: *mut stmt, name: *const c_char) -> c_int; 83 | 84 | pub fn sqlite3_busy_timeout(dbh: *mut dbh, ms: c_int) -> ResultCode; 85 | } 86 | -------------------------------------------------------------------------------- /src/types.rs: -------------------------------------------------------------------------------- 1 | /* 2 | ** Copyright (c) 2011, Brian Smith 3 | ** All rights reserved. 4 | ** 5 | ** Redistribution and use in source and binary forms, with or without 6 | ** modification, are permitted provided that the following conditions are met: 7 | ** 8 | ** * Redistributions of source code must retain the above copyright notice, 9 | ** this list of conditions and the following disclaimer. 10 | ** 11 | ** * Redistributions in binary form must reproduce the above copyright notice, 12 | ** this list of conditions and the following disclaimer in the documentation 13 | ** and/or other materials provided with the distribution. 14 | ** 15 | ** * Neither the name of Brian Smith nor the names of its contributors 16 | ** may be used to endorse or promote products derived from this software 17 | ** without specific prior written permission. 18 | ** 19 | ** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 20 | ** AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 21 | ** IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 22 | ** ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE 23 | ** LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 24 | ** CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 25 | ** SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 26 | ** INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 27 | ** CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 28 | ** ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 29 | ** POSSIBILITY OF SUCH DAMAGE. 30 | */ 31 | 32 | use std::collections::HashMap; 33 | use std::fmt; 34 | 35 | use self::ResultCode::*; 36 | 37 | #[derive(Clone, Copy, PartialEq, Eq)] 38 | #[repr(C)] 39 | pub enum ResultCode { 40 | SQLITE_OK = 0, 41 | SQLITE_ERROR = 1, 42 | SQLITE_INTERNAL = 2, 43 | SQLITE_PERM = 3, 44 | SQLITE_ABORT = 4, 45 | SQLITE_BUSY = 5, 46 | SQLITE_LOCKED = 6, 47 | SQLITE_NOMEM = 7, 48 | SQLITE_READONLY = 8, 49 | SQLITE_INTERRUPT = 9, 50 | SQLITE_IOERR = 10, 51 | SQLITE_CORRUPT = 11, 52 | SQLITE_NOTFOUND = 12, 53 | SQLITE_FULL = 13, 54 | SQLITE_CANTOPEN = 14, 55 | SQLITE_PROTOCOL = 15, 56 | SQLITE_EMPTY = 16, 57 | SQLITE_SCHEMA = 17, 58 | SQLITE_TOOBIG = 18, 59 | SQLITE_CONSTRAINT = 19, 60 | SQLITE_MISMATCH = 20, 61 | SQLITE_MISUSE = 21, 62 | SQLITE_NOLFS = 22, 63 | SQLITE_AUTH = 23, 64 | SQLITE_FORMAT = 24, 65 | SQLITE_RANGE = 25, 66 | SQLITE_NOTADB = 26, 67 | SQLITE_ROW = 100, 68 | SQLITE_DONE = 101, 69 | } 70 | 71 | impl fmt::Debug for ResultCode { 72 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 73 | f.write_str(match *self { 74 | SQLITE_OK => "Ok", 75 | SQLITE_ERROR => "SQLITE_ERROR", 76 | SQLITE_INTERNAL => "SQLITE_INTERNAL", 77 | SQLITE_PERM => "SQLITE_PERM", 78 | SQLITE_ABORT => "SQLITE_ABORT", 79 | SQLITE_BUSY => "SQLITE_BUSY", 80 | SQLITE_LOCKED => "SQLITE_LOCKED", 81 | SQLITE_NOMEM => "SQLITE_NOMEM", 82 | SQLITE_READONLY => "SQLITE_READONLY", 83 | SQLITE_INTERRUPT => "SQLITE_INTERRUPT", 84 | SQLITE_IOERR => "SQLITE_IOERR", 85 | SQLITE_CORRUPT => "SQLITE_CORRUPT", 86 | SQLITE_NOTFOUND => "SQLITE_NOTFOUND", 87 | SQLITE_FULL => "SQLITE_FULL", 88 | SQLITE_CANTOPEN => "SQLITE_CANTOPEN", 89 | SQLITE_PROTOCOL => "SQLITE_PROTOCOL", 90 | SQLITE_EMPTY => "SQLITE_EMPTY", 91 | SQLITE_SCHEMA => "SQLITE_SCHEMA", 92 | SQLITE_TOOBIG => "SQLITE_TOOBIG", 93 | SQLITE_CONSTRAINT => "SQLITE_CONSTRAINT", 94 | SQLITE_MISMATCH => "SQLITE_MISMATCH", 95 | SQLITE_MISUSE => "SQLITE_MISUSE", 96 | SQLITE_NOLFS => "SQLITE_NOLFS", 97 | SQLITE_AUTH => "SQLITE_AUTH", 98 | SQLITE_FORMAT => "SQLITE_FORMAT", 99 | SQLITE_RANGE => "SQLITE_RANGE", 100 | SQLITE_NOTADB => "SQLITE_NOTADB", 101 | SQLITE_ROW => "SQLITE_ROW", 102 | SQLITE_DONE => "SQLITE_DONE", 103 | }) 104 | } 105 | } 106 | 107 | #[derive(PartialEq, Clone)] 108 | pub enum BindArg { 109 | Text(String), 110 | StaticText(&'static str), 111 | Float64(f64), 112 | Integer(isize), 113 | Integer64(i64), 114 | Blob(Vec), 115 | Null, 116 | } 117 | 118 | #[derive(Clone, Copy)] 119 | pub enum ColumnType { 120 | SQLITE_INTEGER, 121 | SQLITE_FLOAT, 122 | SQLITE_TEXT, 123 | SQLITE_BLOB, 124 | SQLITE_NULL, 125 | } 126 | 127 | pub type SqliteResult = Result; 128 | 129 | pub type RowMap = HashMap; 130 | 131 | pub enum dbh {} 132 | pub enum stmt {} 133 | pub enum _notused {} 134 | -------------------------------------------------------------------------------- /src/database.rs: -------------------------------------------------------------------------------- 1 | /* 2 | ** Copyright (c) 2011, Brian Smith 3 | ** All rights reserved. 4 | ** 5 | ** Redistribution and use in source and binary forms, with or without 6 | ** modification, are permitted provided that the following conditions are met: 7 | ** 8 | ** * Redistributions of source code must retain the above copyright notice, 9 | ** this list of conditions and the following disclaimer. 10 | ** 11 | ** * Redistributions in binary form must reproduce the above copyright notice, 12 | ** this list of conditions and the following disclaimer in the documentation 13 | ** and/or other materials provided with the distribution. 14 | ** 15 | ** * Neither the name of Brian Smith nor the names of its contributors 16 | ** may be used to endorse or promote products derived from this software 17 | ** without specific prior written permission. 18 | ** 19 | ** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 20 | ** AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 21 | ** IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 22 | ** ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE 23 | ** LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 24 | ** CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 25 | ** SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 26 | ** INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 27 | ** CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 28 | ** ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 29 | ** POSSIBILITY OF SUCH DAMAGE. 30 | */ 31 | 32 | use cursor::*; 33 | use ffi::*; 34 | use libc::c_int; 35 | use std::str; 36 | use std::ptr; 37 | use std::fmt; 38 | use std::borrow::ToOwned; 39 | use std::ffi::{CString, CStr}; 40 | use types::*; 41 | use types::ResultCode::*; 42 | 43 | /// The database connection. 44 | /// 45 | /// SQLite database is `Send`able but not `Copy`able nor `Sync`able. 46 | /// Consequently, it can be shared through `std::sync::Mutex` across tasks 47 | /// (as it grants an exclusive access to the connection) 48 | /// but cannot be shared through `std::sync::RWLock`. 49 | pub struct Database { 50 | dbh: *mut dbh, 51 | } 52 | 53 | unsafe impl Send for Database {} 54 | 55 | pub fn database_with_handle(dbh: *mut dbh) -> Database { 56 | Database { dbh: dbh } 57 | } 58 | 59 | impl fmt::Debug for Database { 60 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 61 | write!(f, "", self.dbh) 62 | } 63 | } 64 | 65 | impl Drop for Database { 66 | /// Closes the database connection. 67 | /// See http://www.sqlite.org/c3ref/close.html 68 | fn drop(&mut self) { 69 | debug!("`Database.drop()`: self={:?}", *self); 70 | unsafe { 71 | sqlite3_close(self.dbh); 72 | } 73 | } 74 | } 75 | 76 | impl Database { 77 | 78 | /// Returns the error message of the the most recent call. 79 | /// See http://www.sqlite.org/c3ref/errcode.html 80 | pub fn get_errmsg(&self) -> String { 81 | unsafe { 82 | let msg = sqlite3_errmsg(self.dbh); 83 | str::from_utf8(CStr::from_ptr(msg).to_bytes()).unwrap().to_owned() 84 | } 85 | } 86 | 87 | /// Prepares/compiles an SQL statement. 88 | /// See http://www.sqlite.org/c3ref/prepare.html 89 | pub fn prepare<'db>(&'db self, sql: &str, _tail: &Option<&str>) -> SqliteResult> { 90 | let sql = CString::new(sql.as_bytes()).unwrap(); 91 | let mut new_stmt = ptr::null_mut(); 92 | let r = unsafe { 93 | sqlite3_prepare_v2(self.dbh, sql.as_ptr(), sql.as_bytes().len() as c_int, &mut new_stmt, ptr::null_mut()) 94 | }; 95 | if r == SQLITE_OK { 96 | debug!("`Database.prepare()`: stmt={:?}", new_stmt); 97 | Ok( cursor_with_statement(new_stmt, &self.dbh)) 98 | } else { 99 | Err(r) 100 | } 101 | } 102 | 103 | /// Executes an SQL statement. 104 | /// See http://www.sqlite.org/c3ref/exec.html 105 | pub fn exec(&mut self, sql: &str) -> SqliteResult { 106 | let sql = CString::new(sql.as_bytes()).unwrap(); 107 | let r = unsafe { 108 | sqlite3_exec(self.dbh, sql.as_ptr(), ptr::null_mut(), ptr::null_mut(), ptr::null_mut()) 109 | }; 110 | 111 | if r == SQLITE_OK { Ok(true) } else { Err(r) } 112 | } 113 | 114 | /// Returns the number of modified/inserted/deleted rows by the most recent 115 | /// call. 116 | /// See http://www.sqlite.org/c3ref/changes.html 117 | pub fn get_changes(&self) -> isize { 118 | unsafe { 119 | sqlite3_changes(self.dbh) as isize 120 | } 121 | } 122 | 123 | /// Returns the ID of the last inserted row. 124 | /// See http://www.sqlite.org/c3ref/last_insert_rowid.html 125 | pub fn get_last_insert_rowid(&self) -> i64 { 126 | unsafe { 127 | sqlite3_last_insert_rowid(self.dbh) 128 | } 129 | } 130 | 131 | /// Sets a busy timeout. 132 | /// See http://www.sqlite.org/c3ref/busy_timeout.html 133 | pub fn set_busy_timeout(&mut self, ms: isize) -> ResultCode { 134 | unsafe { 135 | sqlite3_busy_timeout(self.dbh, ms as c_int) 136 | } 137 | } 138 | } 139 | -------------------------------------------------------------------------------- /src/cursor.rs: -------------------------------------------------------------------------------- 1 | /* 2 | ** Copyright (c) 2011, Brian Smith 3 | ** All rights reserved. 4 | ** 5 | ** Redistribution and use in source and binary forms, with or without 6 | ** modification, are permitted provided that the following conditions are met: 7 | ** 8 | ** * Redistributions of source code must retain the above copyright notice, 9 | ** this list of conditions and the following disclaimer. 10 | ** 11 | ** * Redistributions in binary form must reproduce the above copyright notice, 12 | ** this list of conditions and the following disclaimer in the documentation 13 | ** and/or other materials provided with the distribution. 14 | ** 15 | ** * Neither the name of Brian Smith nor the names of its contributors 16 | ** may be used to endorse or promote products derived from this software 17 | ** without specific prior written permission. 18 | ** 19 | ** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 20 | ** AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 21 | ** IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 22 | ** ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE 23 | ** LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 24 | ** CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 25 | ** SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 26 | ** INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 27 | ** CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 28 | ** ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 29 | ** POSSIBILITY OF SUCH DAMAGE. 30 | */ 31 | 32 | use ffi::*; 33 | use libc::{c_int, c_void, c_char}; 34 | use std::collections::HashMap; 35 | use std::mem::transmute; 36 | use std::str; 37 | use std::fmt; 38 | use std::slice; 39 | use std::ffi::{CString, CStr}; 40 | use types::*; 41 | use types::BindArg::*; 42 | use types::ColumnType::*; 43 | use types::ResultCode::*; 44 | 45 | /// The database cursor. 46 | /// 47 | /// Unlike `Database`, `Cursor` is dependent of the active database connection 48 | /// and cannot be shared across tasks (non-`Send`able). It is a compile time error 49 | /// that `Cursor` outlives `Database` either by `Database` escaping the block scope 50 | /// or by the task-shared reference to `Database` being expired. 51 | pub struct Cursor<'db> { 52 | stmt: *mut stmt, 53 | _dbh: &'db *mut dbh // make this non-`Send`able 54 | } 55 | 56 | pub fn cursor_with_statement<'db>(stmt: *mut stmt, dbh: &'db *mut dbh) -> Cursor<'db> { 57 | debug!("`Cursor.cursor_with_statement()`: stmt={:?}", stmt); 58 | Cursor { stmt: stmt, _dbh: dbh } 59 | } 60 | 61 | impl<'db> fmt::Debug for Cursor<'db> { 62 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 63 | write!(f, "", self._dbh, self.stmt) 64 | } 65 | } 66 | 67 | impl<'db> Drop for Cursor<'db> { 68 | /// Deletes a prepared SQL statement. 69 | /// See http://www.sqlite.org/c3ref/finalize.html 70 | fn drop(&mut self) { 71 | debug!("`Cursor.drop()`: self={:?}", *self); 72 | unsafe { 73 | sqlite3_finalize(self.stmt); 74 | } 75 | } 76 | } 77 | 78 | impl<'db> Cursor<'db> { 79 | 80 | /// Resets a prepared SQL statement, but does not reset its bindings. 81 | /// See http://www.sqlite.org/c3ref/reset.html 82 | pub fn reset(&mut self) -> ResultCode { 83 | unsafe { 84 | sqlite3_reset(self.stmt) 85 | } 86 | } 87 | 88 | /// Resets all bindings on a prepared SQL statement. 89 | /// See http://www.sqlite.org/c3ref/clear_bindings.html 90 | pub fn clear_bindings(&mut self) -> ResultCode { 91 | unsafe { 92 | sqlite3_clear_bindings(self.stmt) 93 | } 94 | } 95 | 96 | /// Evaluates a prepared SQL statement one ore more times. 97 | /// See http://www.sqlite.org/c3ref/step.html 98 | pub fn step(&mut self) -> ResultCode { 99 | unsafe { 100 | sqlite3_step(self.stmt) 101 | } 102 | } 103 | 104 | /// 105 | pub fn step_row(&mut self) -> SqliteResult> { 106 | let is_row: ResultCode = self.step(); 107 | if is_row == SQLITE_ROW { 108 | let column_cnt = self.get_column_count(); 109 | let mut i = 0; 110 | let mut sqlrow = HashMap::new(); 111 | while i < column_cnt { 112 | let name = self.get_column_name(i).to_string(); 113 | let coltype = self.get_column_type(i); 114 | let res = match coltype { 115 | SQLITE_INTEGER => sqlrow.insert(name, Integer(self.get_int(i))), 116 | SQLITE_FLOAT => sqlrow.insert(name, Float64(self.get_f64(i))), 117 | SQLITE_TEXT => sqlrow.insert(name, Text(self.get_text(i).unwrap().to_string())), 118 | SQLITE_BLOB => sqlrow.insert(name, Blob(self.get_blob(i).unwrap().to_vec())), 119 | SQLITE_NULL => sqlrow.insert(name, Null), 120 | }; 121 | assert!(res.is_none(), "Duplicate column name for sqlrow!"); 122 | i += 1; 123 | } 124 | 125 | Ok(Some(sqlrow)) 126 | } 127 | else if is_row == SQLITE_DONE { 128 | Ok(None) 129 | } else { 130 | Err(is_row) 131 | } 132 | } 133 | 134 | /// 135 | /// See http://www.sqlite.org/c3ref/column_blob.html 136 | pub fn get_blob<'a>(&'a mut self, i: isize) -> Option<&'a [u8]> { 137 | let ptr = unsafe {sqlite3_column_blob(self.stmt, i as c_int)}; 138 | let len = unsafe {sqlite3_column_bytes(self.stmt, i as c_int)} as usize; 139 | if ptr.is_null() { 140 | None 141 | } else { 142 | unsafe {Some(slice::from_raw_parts(ptr, len))} 143 | } 144 | } 145 | 146 | /// 147 | /// See http://www.sqlite.org/c3ref/column_blob.html 148 | pub fn get_int(&mut self, i: isize) -> isize { 149 | unsafe { 150 | return sqlite3_column_int(self.stmt, i as c_int) as isize; 151 | } 152 | } 153 | 154 | /// 155 | /// See http://www.sqlite.org/c3ref/column_blob.html 156 | pub fn get_i64(&mut self, i: isize) -> i64 { 157 | unsafe { 158 | return sqlite3_column_int64(self.stmt, i as c_int) as i64; 159 | } 160 | } 161 | 162 | /// 163 | /// See http://www.sqlite.org/c3ref/column_blob.html 164 | pub fn get_f64(&mut self, i: isize) -> f64 { 165 | unsafe { 166 | return sqlite3_column_double(self.stmt, i as c_int); 167 | } 168 | } 169 | 170 | /// 171 | /// See http://www.sqlite.org/c3ref/column_blob.html 172 | pub fn get_text<'a>(&'a mut self, i: isize) -> Option<&'a str> { 173 | let ptr = unsafe {sqlite3_column_text(self.stmt, i as c_int)} as *const u8; 174 | let len = unsafe {sqlite3_column_bytes(self.stmt, i as c_int)} as usize; 175 | if ptr.is_null() { 176 | None 177 | } else { 178 | unsafe {Some(str::from_utf8_unchecked(slice::from_raw_parts(ptr, len)))} 179 | } 180 | } 181 | 182 | /// 183 | /// See http://www.sqlite.org/c3ref/bind_parameter_index.html 184 | pub fn get_bind_index(&self, name: &str) -> isize { 185 | let name = CString::new(name.as_bytes()).unwrap(); 186 | let stmt = self.stmt; 187 | unsafe { 188 | return sqlite3_bind_parameter_index(stmt, name.as_ptr()) as isize 189 | } 190 | } 191 | 192 | /// Returns the number of columns in a result set. 193 | /// See http://www.sqlite.org/c3ref/data_count.html 194 | pub fn get_column_count(&self) -> isize { 195 | unsafe { 196 | return sqlite3_data_count(self.stmt) as isize; 197 | } 198 | } 199 | 200 | /// Returns the name of the column with index `i` in the result set. 201 | /// See http://www.sqlite.org/c3ref/column_name.html 202 | pub fn get_column_name<'a>(&'a self, i: isize) -> &'a str { 203 | unsafe { 204 | let name = sqlite3_column_name(self.stmt, i as c_int); 205 | let namestr = str::from_utf8(CStr::from_ptr(name).to_bytes()).unwrap(); 206 | transmute(namestr) // make it outlive the original `CString` 207 | } 208 | } 209 | 210 | /// Returns the type of the column with index `i` in the result set. 211 | /// See http://www.sqlite.org/c3ref/column_blob.html 212 | pub fn get_column_type(&self, i: isize) -> ColumnType { 213 | let ct; 214 | unsafe { 215 | ct = sqlite3_column_type(self.stmt, i as c_int) as isize; 216 | } 217 | let res = match ct { 218 | 1 /* SQLITE_INTEGER */ => SQLITE_INTEGER, 219 | 2 /* SQLITE_FLOAT */ => SQLITE_FLOAT, 220 | 3 /* SQLITE_TEXT */ => SQLITE_TEXT, 221 | 4 /* SQLITE_BLOB */ => SQLITE_BLOB, 222 | 5 /* SQLITE_NULL */ => SQLITE_NULL, 223 | _ => panic!(format!("sqlite internal error: Got an unknown column type ({}) back from the library.", ct)), 224 | }; 225 | return res; 226 | } 227 | 228 | /// Returns the names of all columns in the result set. 229 | pub fn get_column_names(&self) -> Vec { 230 | let cnt = self.get_column_count(); 231 | let mut i = 0; 232 | let mut r = Vec::new(); 233 | while i < cnt { 234 | r.push(self.get_column_name(i).to_string()); 235 | i += 1; 236 | } 237 | return r; 238 | } 239 | 240 | /// 241 | pub fn bind_params(&mut self, values: &[BindArg]) -> ResultCode { 242 | // SQL parameter index (starting from 1). 243 | let mut i = 1; 244 | for v in values.iter() { 245 | let r = self.bind_param(i, v); 246 | if r != SQLITE_OK { 247 | return r; 248 | } 249 | i += 1; 250 | } 251 | return SQLITE_OK; 252 | } 253 | 254 | /// 255 | /// See http://www.sqlite.org/c3ref/bind_blob.html 256 | pub fn bind_param(&mut self, i: isize, value: &BindArg) -> ResultCode { 257 | 258 | debug!("`Cursor.bind_param()`: self={:?}", *self); 259 | 260 | let r = match *value { 261 | Text(ref v) => { 262 | let l = v.len(); 263 | debug!(" `Text`: v={}, l={}", v, l); 264 | 265 | let v = CString::new(v.as_bytes()).unwrap(); 266 | unsafe { 267 | // FIXME: do not copy the data 268 | sqlite3_bind_text( 269 | self.stmt // the SQL statement 270 | , i as c_int // the SQL parameter index (starting from 1) 271 | , v.as_ptr() // the value to bind 272 | , l as c_int // the number of bytes 273 | , -1isize as *mut c_void// SQLITE_TRANSIENT => SQLite makes a copy 274 | ) 275 | } 276 | } 277 | 278 | StaticText(ref v) => { 279 | let l = v.len(); 280 | debug!(" `StaticText`: v={}, l={}", v, l); 281 | 282 | { 283 | let _v = v.as_bytes(); 284 | debug!(" _v={:?}", _v); 285 | unsafe { 286 | sqlite3_bind_text( 287 | self.stmt // the SQL statement 288 | , i as c_int // the SQL parameter index (starting from 1) 289 | , _v.as_ptr() as *const c_char // the value to bind 290 | , l as c_int // the number of bytes 291 | , 0 as *mut c_void// SQLITE_STATIC 292 | ) 293 | } 294 | } 295 | } 296 | 297 | Blob(ref v) => { 298 | let l = v.len(); 299 | debug!("`Blob`: v={:?}, l={}", v, l); 300 | 301 | unsafe { 302 | // FIXME: do not copy the data 303 | sqlite3_bind_blob( 304 | self.stmt // the SQL statement 305 | , i as c_int // the SQL parameter index (starting from 1) 306 | , v.as_ptr() // the value to bind 307 | , l as c_int // the number of bytes 308 | , -1isize as *mut c_void // SQLITE_TRANSIENT => SQLite makes a copy 309 | ) 310 | } 311 | } 312 | 313 | Integer(ref v) => { unsafe { sqlite3_bind_int(self.stmt, i as c_int, *v as c_int) } } 314 | 315 | Integer64(ref v) => { unsafe { sqlite3_bind_int64(self.stmt, i as c_int, *v) } } 316 | 317 | Float64(ref v) => { unsafe { sqlite3_bind_double(self.stmt, i as c_int, *v) } } 318 | 319 | Null => { unsafe { sqlite3_bind_null(self.stmt, i as c_int) } } 320 | 321 | }; 322 | 323 | return r; 324 | } 325 | } 326 | -------------------------------------------------------------------------------- /src/sqlite3.rs: -------------------------------------------------------------------------------- 1 | #![crate_name = "sqlite3"] 2 | #![crate_type = "lib"] 3 | 4 | #![allow(missing_copy_implementations)] 5 | 6 | #[macro_use] extern crate log; 7 | 8 | /* 9 | ** Copyright (c) 2011, Brian Smith 10 | ** All rights reserved. 11 | ** 12 | ** Redistribution and use in source and binary forms, with or without 13 | ** modification, are permitted provided that the following conditions are met: 14 | ** 15 | ** * Redistributions of source code must retain the above copyright notice, 16 | ** this list of conditions and the following disclaimer. 17 | ** 18 | ** * Redistributions in binary form must reproduce the above copyright notice, 19 | ** this list of conditions and the following disclaimer in the documentation 20 | ** and/or other materials provided with the distribution. 21 | ** 22 | ** * Neither the name of Brian Smith nor the names of its contributors 23 | ** may be used to endorse or promote products derived from this software 24 | ** without specific prior written permission. 25 | ** 26 | ** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 27 | ** AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 28 | ** IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 29 | ** ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE 30 | ** LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 31 | ** CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 32 | ** SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 33 | ** INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 34 | ** CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 35 | ** ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 36 | ** POSSIBILITY OF SUCH DAMAGE. 37 | */ 38 | 39 | extern crate libc; 40 | 41 | pub use cursor::*; 42 | pub use database::*; 43 | use ffi::*; 44 | pub use types::*; 45 | use types::ResultCode::*; 46 | use std::ptr; 47 | use std::ffi::CString; 48 | 49 | pub mod cursor; 50 | pub mod database; 51 | mod ffi; 52 | 53 | #[allow(non_camel_case_types)] 54 | pub mod types; 55 | 56 | 57 | 58 | /// Determines whether an SQL statement is complete. 59 | /// See http://www.sqlite.org/c3ref/complete.html 60 | pub fn sqlite_complete(sql: &str) -> SqliteResult { 61 | let sql = CString::new(sql.as_bytes()).unwrap(); 62 | let r = unsafe { 63 | sqlite3_complete(sql.as_ptr()) as isize 64 | }; 65 | if r == SQLITE_NOMEM as isize { 66 | return Err(SQLITE_NOMEM); 67 | } 68 | else if r == 1 { 69 | return Ok(true); 70 | } 71 | else { 72 | return Ok(false); 73 | } 74 | } 75 | 76 | 77 | /// Opens a new database connection. 78 | /// `path` can either be a filesystem path or ":memory:". 79 | /// See http://www.sqlite.org/c3ref/open.html 80 | pub fn open(path: &str) -> SqliteResult { 81 | let path = CString::new(path.as_bytes()).unwrap(); 82 | let mut dbh = ptr::null_mut(); 83 | let r = unsafe { 84 | sqlite3_open(path.as_ptr(), &mut dbh) 85 | }; 86 | if r != SQLITE_OK { 87 | unsafe { 88 | sqlite3_close(dbh); 89 | } 90 | Err(r) 91 | } else { 92 | debug!("`open()`: dbh={:?}", dbh); 93 | Ok(database_with_handle(dbh)) 94 | } 95 | } 96 | 97 | #[cfg(test)] 98 | mod tests { 99 | use super::*; 100 | use types::BindArg::*; 101 | use types::ResultCode::*; 102 | use std::thread; 103 | 104 | fn checked_prepare<'db>(database: &'db Database, sql: &str) -> Cursor<'db> { 105 | match database.prepare(sql, &None) { 106 | Ok(s) => s, 107 | Err(x) => panic!(format!("sqlite error: \"{}\" ({:?})", database.get_errmsg(), x)), 108 | } 109 | } 110 | 111 | fn checked_open() -> Database { 112 | match open(":memory:") { 113 | Ok(database) => database, 114 | Err(ref e) => panic!(format!("{:?}", *e)), 115 | } 116 | } 117 | 118 | fn checked_exec(database: &mut Database, sql: &str) { 119 | match database.exec(sql) { 120 | Ok(..) => {} 121 | Err(x) => panic!(format!("sqlite error: \"{}\" ({:?})", database.get_errmsg(), x)), 122 | } 123 | } 124 | 125 | #[test] 126 | fn open_db() { 127 | checked_open(); 128 | } 129 | 130 | #[test] 131 | fn exec_create_tbl() { 132 | let mut database = checked_open(); 133 | checked_exec(&mut database, "BEGIN; CREATE TABLE IF NOT EXISTS test (id INTEGER PRIMARY KEY AUTOINCREMENT); COMMIT;"); 134 | } 135 | 136 | #[test] 137 | fn prepare_insert_stmt() { 138 | let mut database = checked_open(); 139 | 140 | checked_exec(&mut database, "BEGIN; CREATE TABLE IF NOT EXISTS test (id INTEGER PRIMARY KEY AUTOINCREMENT); COMMIT;"); 141 | let mut sth = checked_prepare(&database, "INSERT OR IGNORE INTO test (id) VALUES (1)"); 142 | let res = sth.step(); 143 | debug!("test `prepare_insert_stmt`: res={:?}", res); 144 | } 145 | 146 | #[test] 147 | fn prepare_select_stmt() { 148 | let mut database = checked_open(); 149 | 150 | checked_exec(&mut database, 151 | "BEGIN; 152 | CREATE TABLE IF NOT EXISTS test (id INTEGER PRIMARY KEY AUTOINCREMENT); 153 | INSERT OR IGNORE INTO test (id) VALUES (1); 154 | COMMIT;" 155 | ); 156 | 157 | let mut sth = checked_prepare(&database, "SELECT id FROM test WHERE id = 1;"); 158 | assert!(sth.step() == SQLITE_ROW); 159 | assert!(sth.get_int(0) == 1); 160 | assert!(sth.step() == SQLITE_DONE); 161 | } 162 | 163 | #[test] 164 | fn prepare_select_stmt_blob() { 165 | let mut database = checked_open(); 166 | 167 | checked_exec(&mut database, 168 | "BEGIN; 169 | CREATE TABLE IF NOT EXISTS test (id INTEGER PRIMARY KEY AUTOINCREMENT, v TEXT); 170 | INSERT OR IGNORE INTO test (id, v) VALUES (1, x'00123456789abcdeff'); 171 | COMMIT;" 172 | ); 173 | 174 | let mut sth = checked_prepare(&database, "SELECT v FROM test WHERE id = 1;"); 175 | assert!(sth.step() == SQLITE_ROW); 176 | assert!(sth.get_blob(0) == Some(&[0x00, 0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xff][..])); 177 | assert!(sth.step() == SQLITE_DONE); 178 | } 179 | 180 | #[test] 181 | fn prepared_stmt_bind_int() { 182 | let mut database = checked_open(); 183 | 184 | checked_exec(&mut database, "BEGIN; CREATE TABLE IF NOT EXISTS test (id INTEGER PRIMARY KEY AUTOINCREMENT); COMMIT;"); 185 | 186 | checked_exec(&mut database, 187 | "INSERT OR IGNORE INTO test (id) VALUES(2); 188 | INSERT OR IGNORE INTO test (id) VALUES(3); 189 | INSERT OR IGNORE INTO test (id) VALUES(4);" 190 | ); 191 | let mut sth = checked_prepare(&database, "SELECT id FROM test WHERE id > ? AND id < ?"); 192 | assert!(sth.bind_param(1, &Integer(2)) == SQLITE_OK); 193 | assert!(sth.bind_param(2, &Integer(4)) == SQLITE_OK); 194 | 195 | assert!(sth.step() == SQLITE_ROW); 196 | assert!(sth.get_f64(0) as isize == 3); 197 | } 198 | 199 | #[test] 200 | fn prepared_stmt_bind_i64() { 201 | let mut database = checked_open(); 202 | 203 | checked_exec(&mut database, "BEGIN; CREATE TABLE IF NOT EXISTS test (id INTEGER PRIMARY KEY AUTOINCREMENT); COMMIT;"); 204 | 205 | checked_exec(&mut database, 206 | "INSERT OR IGNORE INTO test (id) VALUES(0); 207 | INSERT OR IGNORE INTO test (id) VALUES(1234567890123456);" 208 | ); 209 | let mut sth = checked_prepare(&database, "SELECT id FROM test WHERE id > ?"); 210 | assert!(sth.bind_param(1, &Integer64(1234567890120000)) == SQLITE_OK); 211 | 212 | assert!(sth.step() == SQLITE_ROW); 213 | assert!(sth.get_i64(0) == 1234567890123456); 214 | } 215 | 216 | #[test] 217 | fn prepared_stmt_bind_text() { 218 | let mut database = checked_open(); 219 | 220 | checked_exec(&mut database, "BEGIN; CREATE TABLE IF NOT EXISTS test (name text); COMMIT;"); 221 | 222 | let mut sth = checked_prepare(&database, "INSERT INTO test (name) VALUES (?)"); 223 | 224 | assert!(sth.bind_param(1, &Text("test".to_string())) == SQLITE_OK); 225 | } 226 | 227 | #[test] 228 | fn prepared_stmt_bind_params() { 229 | let mut database = checked_open(); 230 | 231 | checked_exec(&mut database, "BEGIN; CREATE TABLE IF NOT EXISTS test (name text, id integer); COMMIT;"); 232 | 233 | let mut sth = checked_prepare(&database, "INSERT INTO TEST (name, id) values (?, ?)"); 234 | assert!(sth.bind_params(&[Integer(12345), Text("test".to_string())]) == SQLITE_OK); 235 | } 236 | 237 | #[test] 238 | fn prepared_stmt_bind_static_text() { 239 | let mut database = checked_open(); 240 | 241 | checked_exec(&mut database, "BEGIN; CREATE TABLE IF NOT EXISTS test (id int, name text); COMMIT;"); 242 | 243 | let mut sth = checked_prepare(&database, "INSERT INTO test (name, id) VALUES (?, ?)"); 244 | 245 | assert!(sth.bind_param(1, &StaticText("test")) == SQLITE_OK); 246 | assert!(sth.bind_param(2, &Integer(100)) == SQLITE_OK); 247 | assert_eq!(sth.step(), SQLITE_DONE); 248 | 249 | let mut st2 = checked_prepare(&database, "SELECT * FROM test"); 250 | assert_eq!(st2.step(), SQLITE_ROW); 251 | assert_eq!(st2.get_int(0), 100); 252 | assert_eq!(st2.get_text(1), Some("test")); 253 | } 254 | 255 | #[test] 256 | fn prepared_stmt_bind_static_text_interleaved() { 257 | let mut database = checked_open(); 258 | 259 | checked_exec(&mut database, "BEGIN; CREATE TABLE IF NOT EXISTS test (id int, name text); COMMIT;"); 260 | 261 | let mut sth = checked_prepare(&database, "INSERT INTO test (name, id) VALUES (?, ?)"); 262 | let mut st2 = checked_prepare(&database, "SELECT * FROM test"); 263 | 264 | assert_eq!(st2.step(), SQLITE_DONE); 265 | 266 | assert_eq!(sth.bind_param(1, &StaticText("test")), SQLITE_OK); 267 | assert_eq!(sth.bind_param(2, &Integer(100)), SQLITE_OK); 268 | assert_eq!(sth.step(), SQLITE_DONE); 269 | 270 | // this is perfectly safe. 271 | assert_eq!(st2.reset(), SQLITE_OK); 272 | assert_eq!(st2.step(), SQLITE_ROW); 273 | assert_eq!(st2.get_int(0), 100); 274 | assert_eq!(st2.get_text(1), Some("test")); 275 | assert_eq!(st2.step(), SQLITE_DONE); 276 | 277 | // notes: 278 | // 279 | // while it is safe to make an update to the table *while* still reading from it, 280 | // the update may or may not visible from the reading cursor depending on the query 281 | // (e.g. `ORDER BY` on the unindexed table makes the cursor read every row in advance 282 | // and the further changes won't be visible) and the result wildly varies. 283 | // it is best not to rely on such behaviors. 284 | } 285 | 286 | #[test] 287 | fn column_names() { 288 | let mut database = checked_open(); 289 | 290 | checked_exec(&mut database, 291 | "BEGIN; 292 | CREATE TABLE IF NOT EXISTS test (id INTEGER PRIMARY KEY AUTOINCREMENT, v TEXT); 293 | INSERT OR IGNORE INTO test (id, v) VALUES(1, 'leeeee'); 294 | COMMIT;" 295 | ); 296 | let mut sth = checked_prepare(&database, "SELECT * FROM test"); 297 | assert!(sth.step() == SQLITE_ROW); 298 | assert!(sth.get_column_names() == vec!("id".to_string(), "v".to_string())); 299 | } 300 | 301 | #[test] 302 | #[should_panic] 303 | fn failed_prepare() { 304 | let mut database = checked_open(); 305 | 306 | checked_exec(&mut database, 307 | "BEGIN; 308 | CREATE TABLE IF NOT EXISTS test (id INTEGER PRIMARY KEY AUTOINCREMENT, v TEXT); 309 | INSERT OR IGNORE INTO test (id, v) VALUES(1, 'leeeee'); 310 | COMMIT;" 311 | ); 312 | let _sth = checked_prepare(&database, "SELECT q FRO test"); 313 | } 314 | 315 | #[test] 316 | fn bind_param_index() { 317 | let mut database = checked_open(); 318 | 319 | checked_exec(&mut database, 320 | "BEGIN; 321 | CREATE TABLE IF NOT EXISTS test (id INTEGER PRIMARY KEY AUTOINCREMENT, v TEXT); 322 | INSERT OR IGNORE INTO test (id, v) VALUES(1, 'leeeee'); 323 | COMMIT;" 324 | ); 325 | let sth = checked_prepare(&database, "SELECT * FROM test WHERE v=:Name"); 326 | assert_eq!(sth.get_bind_index(":Name"), 1); 327 | assert_eq!(sth.get_bind_index(":Bogus"), 0); 328 | } 329 | 330 | #[test] 331 | fn last_insert_id() { 332 | let mut database = checked_open(); 333 | checked_exec(&mut database, 334 | " 335 | BEGIN; 336 | CREATE TABLE IF NOT EXISTS test (v TEXT); 337 | INSERT OR IGNORE INTO test (v) VALUES ('This is a really long string.'); 338 | COMMIT; 339 | " 340 | ); 341 | debug!("test `last insert_id`: {}", database.get_last_insert_rowid()); 342 | assert!(database.get_last_insert_rowid() == 1_i64); 343 | } 344 | 345 | #[test] 346 | fn step_row_basics() { 347 | let mut database = checked_open(); 348 | checked_exec(&mut database, 349 | " 350 | BEGIN; 351 | CREATE TABLE IF NOT EXISTS test (id INTEGER, k TEXT, v REAL); 352 | INSERT OR IGNORE INTO test (id, k, v) VALUES(1, 'pi', 3.1415); 353 | INSERT OR IGNORE INTO test (id, k, v) VALUES(2, 'e', 2.17); 354 | INSERT OR IGNORE INTO test (id, k, v) VALUES(3, 'o', 1.618); 355 | COMMIT; 356 | " 357 | ); 358 | let mut sth = checked_prepare(&database, "SELECT * FROM test WHERE id=2"); 359 | let r = sth.step_row(); 360 | let possible_row = r.unwrap(); 361 | match possible_row { 362 | Some(x) => { 363 | let mut x = x; 364 | assert!(x.remove(&"id".to_string()) == Some(Integer(2))); 365 | assert!(x.remove(&"k".to_string()) == Some(Text("e".to_string()))); 366 | assert!(x.remove(&"v".to_string()) == Some(Float64(2.17))); 367 | } 368 | None => { 369 | panic!("didnt get even one row back."); 370 | } 371 | } 372 | } 373 | 374 | #[test] 375 | fn check_complete_sql() { 376 | let r1 = sqlite_complete("SELECT * FROM"); 377 | let r2 = sqlite_complete("SELECT * FROM bob;"); 378 | assert!(is_ok_and(r1, false)); 379 | assert!(is_ok_and(r2, true)); 380 | 381 | fn is_ok_and(r: SqliteResult, v: bool) -> bool { 382 | assert!(r.is_ok()); 383 | return r.unwrap() == v; 384 | } 385 | } 386 | 387 | #[test] 388 | fn get_text_without_step() { 389 | let db = checked_open(); 390 | let mut c = checked_prepare(&db, "select 1 + 1"); 391 | assert_eq!(c.get_text(0), None); 392 | } 393 | 394 | #[test] 395 | fn get_text_on_bogus_col() { 396 | let db = checked_open(); 397 | let mut c = checked_prepare(&db, "select 1 + 1"); 398 | c.step(); 399 | assert_eq!(c.get_text(1), None); 400 | } 401 | 402 | #[test] 403 | fn sendable_db() { 404 | let db = checked_open(); 405 | thread::scoped(move || { 406 | let mut c = checked_prepare(&db, "select 1 + 1"); 407 | c.step(); 408 | assert_eq!(c.get_int(0), 2); 409 | }).join(); 410 | } 411 | } 412 | 413 | --------------------------------------------------------------------------------