Initial commit

This commit is contained in:
John Gallagher 2014-10-19 19:56:41 -04:00
commit ca3a0f0580
7 changed files with 2255 additions and 0 deletions

2
.gitignore vendored Normal file
View File

@ -0,0 +1,2 @@
/target/
Cargo.lock

7
Cargo.toml Normal file
View File

@ -0,0 +1,7 @@
[package]
name = "rusqlite"
version = "0.0.0"
authors = ["John Gallagher <jgallagher@bignerdranch.com>"]
[lib]
name = "rusqlite"

1287
src/ffi/bindgen.rs Normal file

File diff suppressed because it is too large Load Diff

83
src/ffi/mod.rs Normal file
View File

@ -0,0 +1,83 @@
pub use self::bindgen::*;
use std::mem;
use libc::{c_int, c_void};
mod bindgen;
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;
// SQLite datatype constants.
pub const SQLITE_NULL : c_int = 5;
pub type SqliteDestructor = extern "C" fn(*mut c_void);
pub fn SQLITE_TRANSIENT() -> SqliteDestructor {
unsafe { mem::transmute(-1i) }
}
pub fn code_to_str(code: c_int) -> &'static str {
match code {
SQLITE_OK => "Successful result",
SQLITE_ERROR => "SQL error or missing database",
SQLITE_INTERNAL => "Internal logic error in SQLite",
SQLITE_PERM => "Access permission denied",
SQLITE_ABORT => "Callback routine requested an abort",
SQLITE_BUSY => "The database file is locked",
SQLITE_LOCKED => "A table in the database is locked",
SQLITE_NOMEM => "A malloc() failed",
SQLITE_READONLY => "Attempt to write a readonly database",
SQLITE_INTERRUPT => "Operation terminated by sqlite3_interrupt()",
SQLITE_IOERR => "Some kind of disk I/O error occurred",
SQLITE_CORRUPT => "The database disk image is malformed",
SQLITE_NOTFOUND => "Unknown opcode in sqlite3_file_control()",
SQLITE_FULL => "Insertion failed because database is full",
SQLITE_CANTOPEN => "Unable to open the database file",
SQLITE_PROTOCOL => "Database lock protocol error",
SQLITE_EMPTY => "Database is empty",
SQLITE_SCHEMA => "The database schema changed",
SQLITE_TOOBIG => "String or BLOB exceeds size limit",
SQLITE_CONSTRAINT=> "Abort due to constraint violation",
SQLITE_MISMATCH => "Data type mismatch",
SQLITE_MISUSE => "Library used incorrectly",
SQLITE_NOLFS => "Uses OS features not supported on host",
SQLITE_AUTH => "Authorization denied",
SQLITE_FORMAT => "Auxiliary database format error",
SQLITE_RANGE => "2nd parameter to sqlite3_bind out of range",
SQLITE_NOTADB => "File opened that is not a database file",
SQLITE_NOTICE => "Notifications from sqlite3_log()",
SQLITE_WARNING => "Warnings from sqlite3_log()",
SQLITE_ROW => "sqlite3_step() has another row ready",
SQLITE_DONE => "sqlite3_step() has finished executing",
_ => "Unknown error code",
}
}

501
src/lib.rs Normal file
View File

@ -0,0 +1,501 @@
#![feature(globs)]
#![feature(unsafe_destructor)]
#![feature(macro_rules)]
extern crate libc;
use std::mem;
use std::ptr;
use std::fmt;
use std::rc::{Rc};
use std::cell::{RefCell, Cell};
use std::c_str::{CString};
use libc::{c_int, c_void, c_char};
use types::{ToSql, FromSql};
pub use transaction::{SqliteTransaction};
pub use transaction::{SqliteTransactionMode,
SqliteTransactionDeferred,
SqliteTransactionImmediate,
SqliteTransactionExclusive};
pub mod types;
mod transaction;
#[allow(dead_code,non_snake_case,non_camel_case_types)] mod ffi;
pub type SqliteResult<T> = Result<T, SqliteError>;
unsafe fn errmsg_to_string(errmsg: *const c_char) -> String {
let c_str = CString::new(errmsg, false);
c_str.as_str().unwrap_or("Invalid error message encoding").to_string()
}
#[deriving(Show)]
pub struct SqliteError {
pub code: c_int,
pub message: String,
}
impl SqliteError {
fn from_handle(db: *mut ffi::Struct_sqlite3, code: c_int) -> SqliteError {
let message = if db.is_null() {
ffi::code_to_str(code).to_string()
} else {
unsafe { errmsg_to_string(ffi::sqlite3_errmsg(db)) }
};
SqliteError{ code: code, message: message }
}
}
pub struct SqliteConnection {
db: RefCell<InnerSqliteConnection>,
}
impl SqliteConnection {
pub fn open(path: &str) -> SqliteResult<SqliteConnection> {
let flags = SQLITE_OPEN_READ_WRITE | SQLITE_OPEN_CREATE;
SqliteConnection::open_with_flags(path, flags)
}
pub fn open_with_flags(path: &str, flags: SqliteOpenFlags) -> SqliteResult<SqliteConnection> {
InnerSqliteConnection::open_with_flags(path, flags).map(|db| {
SqliteConnection{ db: RefCell::new(db) }
})
}
pub fn transaction<'a>(&'a self) -> SqliteResult<SqliteTransaction<'a>> {
SqliteTransaction::new(self, SqliteTransactionDeferred)
}
pub fn transaction_with_mode<'a>(&'a self, mode: SqliteTransactionMode)
-> SqliteResult<SqliteTransaction<'a>> {
SqliteTransaction::new(self, mode)
}
pub fn execute_batch(&self, sql: &str) -> SqliteResult<()> {
self.db.borrow_mut().execute_batch(sql)
}
pub fn execute(&self, sql: &str, params: &[&ToSql]) -> SqliteResult<uint> {
self.prepare(sql).and_then(|mut stmt| stmt.execute(params))
}
pub fn last_insert_rowid(&self) -> i64 {
self.db.borrow_mut().last_insert_rowid()
}
pub fn query_row<T>(&self, sql: &str, params: &[&ToSql],
f: |SqliteResult<SqliteRow>| -> T) -> T {
f(self.prepare(sql).unwrap().query(params).unwrap().next().unwrap())
}
pub fn prepare<'a>(&'a self, sql: &str) -> SqliteResult<SqliteStatement<'a>> {
self.db.borrow_mut().prepare(self, sql)
}
pub fn close(self) -> SqliteResult<()> {
self.db.borrow_mut().close()
}
fn decode_result(&self, code: c_int) -> SqliteResult<()> {
self.db.borrow_mut().decode_result(code)
}
fn changes(&self) -> uint {
self.db.borrow_mut().changes()
}
}
impl fmt::Show for SqliteConnection {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "SqliteConnection()")
}
}
struct InnerSqliteConnection {
db: *mut ffi::Struct_sqlite3,
}
bitflags! {
#[repr(C)] flags SqliteOpenFlags: c_int {
const SQLITE_OPEN_READ_ONLY = 0x00000001,
const SQLITE_OPEN_READ_WRITE = 0x00000002,
const SQLITE_OPEN_CREATE = 0x00000004,
const SQLITE_OPEN_URI = 0x00000040,
const SQLITE_OPEN_MEMORY = 0x00000080,
const SQLITE_OPEN_NO_MUTEX = 0x00008000,
const SQLITE_OPEN_FULL_MUTEX = 0x00010000,
const SQLITE_OPEN_SHARED_CACHE = 0x00020000,
const SQLITE_OPEN_PRIVATE_CACHE = 0x00040000,
}
}
impl InnerSqliteConnection {
fn open_with_flags(path: &str, flags: SqliteOpenFlags) -> SqliteResult<InnerSqliteConnection> {
path.with_c_str(|c_path| unsafe {
let mut db: *mut ffi::sqlite3 = mem::uninitialized();
let r = ffi::sqlite3_open_v2(c_path, &mut db, flags.bits(), ptr::null());
if r != ffi::SQLITE_OK {
let e = if db.is_null() {
SqliteError{ code: r,
message: ffi::code_to_str(r).to_string() }
} else {
ffi::sqlite3_close(db);
SqliteError::from_handle(db, r)
};
return Err(e);
}
Ok(InnerSqliteConnection{ db: db })
})
}
fn decode_result(&mut self, code: c_int) -> SqliteResult<()> {
if code == ffi::SQLITE_OK {
Ok(())
} else {
Err(SqliteError::from_handle(self.db, code))
}
}
fn close(&mut self) -> SqliteResult<()> {
let r = unsafe { ffi::sqlite3_close(self.db) };
self.db = ptr::null_mut();
self.decode_result(r)
}
fn execute_batch(&mut self, sql: &str) -> SqliteResult<()> {
sql.with_c_str(|c_sql| unsafe {
let mut errmsg: *mut c_char = mem::uninitialized();
let r = ffi::sqlite3_exec(self.db, c_sql, None, ptr::null_mut(), &mut errmsg);
if r == ffi::SQLITE_OK {
Ok(())
} else {
let message = errmsg_to_string(&*errmsg);
ffi::sqlite3_free(errmsg as *mut c_void);
Err(SqliteError{ code: r, message: message })
}
})
}
fn last_insert_rowid(&self) -> i64 {
unsafe {
ffi::sqlite3_last_insert_rowid(self.db)
}
}
fn prepare<'a>(&mut self,
conn: &'a SqliteConnection,
sql: &str) -> SqliteResult<SqliteStatement<'a>> {
let mut c_stmt: *mut ffi::sqlite3_stmt = unsafe { mem::uninitialized() };
let r = sql.with_c_str(|c_sql| unsafe {
let len_with_nul = (sql.len() + 1) as c_int;
ffi::sqlite3_prepare_v2(self.db, c_sql, len_with_nul, &mut c_stmt, ptr::null_mut())
});
self.decode_result(r).map(|_| {
SqliteStatement::new(conn, c_stmt)
})
}
fn changes(&mut self) -> uint {
unsafe{ ffi::sqlite3_changes(self.db) as uint }
}
}
impl Drop for InnerSqliteConnection {
#[allow(unused_must_use)]
fn drop(&mut self) {
self.close();
}
}
pub struct SqliteStatement<'conn> {
conn: &'conn SqliteConnection,
stmt: *mut ffi::sqlite3_stmt,
needs_reset: bool,
}
impl<'conn> SqliteStatement<'conn> {
fn new(conn: &SqliteConnection, stmt: *mut ffi::sqlite3_stmt) -> SqliteStatement {
SqliteStatement{ conn: conn, stmt: stmt, needs_reset: false }
}
pub fn execute(&mut self, params: &[&ToSql]) -> SqliteResult<uint> {
self.reset_if_needed();
unsafe {
assert_eq!(params.len() as c_int, ffi::sqlite3_bind_parameter_count(self.stmt));
for (i, p) in params.iter().enumerate() {
try!(self.conn.decode_result(p.bind_parameter(self.stmt, (i + 1) as c_int)));
}
self.needs_reset = true;
let r = ffi::sqlite3_step(self.stmt);
match r {
ffi::SQLITE_DONE => Ok(self.conn.changes()),
ffi::SQLITE_ROW => Err(SqliteError{ code: r,
message: "Unexpected row result - did you mean to call query?".to_string() }),
_ => Err(self.conn.decode_result(r).unwrap_err()),
}
}
}
pub fn query<'a>(&'a mut self, params: &[&ToSql]) -> SqliteResult<SqliteRows<'a>> {
self.reset_if_needed();
unsafe {
assert_eq!(params.len() as c_int, ffi::sqlite3_bind_parameter_count(self.stmt));
for (i, p) in params.iter().enumerate() {
try!(self.conn.decode_result(p.bind_parameter(self.stmt, (i + 1) as c_int)));
}
self.needs_reset = true;
Ok(SqliteRows::new(self))
}
}
pub fn finalize(mut self) -> SqliteResult<()> {
self.finalize_()
}
fn reset_if_needed(&mut self) {
if self.needs_reset {
unsafe { ffi::sqlite3_reset(self.stmt); };
self.needs_reset = false;
}
}
fn finalize_(&mut self) -> SqliteResult<()> {
let r = unsafe { ffi::sqlite3_finalize(self.stmt) };
self.stmt = ptr::null_mut();
self.conn.decode_result(r)
}
}
impl<'conn> fmt::Show for SqliteStatement<'conn> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "Statement( conn: {}, stmt: {} )", self.conn, self.stmt)
}
}
#[unsafe_destructor]
impl<'conn> Drop for SqliteStatement<'conn> {
#[allow(unused_must_use)]
fn drop(&mut self) {
self.finalize_();
}
}
pub struct SqliteRows<'stmt> {
stmt: &'stmt SqliteStatement<'stmt>,
current_row: Rc<Cell<c_int>>,
failed: bool,
}
impl<'stmt> SqliteRows<'stmt> {
fn new(stmt: &'stmt SqliteStatement<'stmt>) -> SqliteRows<'stmt> {
SqliteRows{ stmt: stmt, current_row: Rc::new(Cell::new(0)), failed: false }
}
}
impl<'stmt> Iterator<SqliteResult<SqliteRow<'stmt>>> for SqliteRows<'stmt> {
fn next(&mut self) -> Option<SqliteResult<SqliteRow<'stmt>>> {
if self.failed {
return None;
}
match unsafe { ffi::sqlite3_step(self.stmt.stmt) } {
ffi::SQLITE_ROW => {
let current_row = self.current_row.get() + 1;
self.current_row.set(current_row);
Some(Ok(SqliteRow{
stmt: self.stmt,
current_row: self.current_row.clone(),
row_idx: current_row,
}))
},
ffi::SQLITE_DONE => None,
code => {
self.failed = true;
Some(Err(self.stmt.conn.decode_result(code).unwrap_err()))
}
}
}
}
pub struct SqliteRow<'stmt> {
stmt: &'stmt SqliteStatement<'stmt>,
current_row: Rc<Cell<c_int>>,
row_idx: c_int,
}
impl<'stmt> SqliteRow<'stmt> {
pub fn get<T: FromSql>(&self, idx: c_int) -> T {
self.get_opt(idx).unwrap()
}
pub fn get_opt<T: FromSql>(&self, idx: c_int) -> SqliteResult<T> {
if self.row_idx != self.current_row.get() {
return Err(SqliteError{ code: ffi::SQLITE_MISUSE,
message: "Cannot get values from a row after advancing to next row".to_string() });
}
unsafe {
if idx < 0 || idx >= ffi::sqlite3_column_count(self.stmt.stmt) {
return Err(SqliteError{ code: ffi::SQLITE_MISUSE,
message: "Invalid column index".to_string() });
}
Ok(FromSql::column_result(self.stmt.stmt, idx))
}
}
}
#[cfg(test)]
mod test {
use super::*;
fn checked_memory_handle() -> SqliteConnection {
SqliteConnection::open(":memory:").unwrap()
}
#[test]
fn test_open() {
assert!(SqliteConnection::open(":memory:").is_ok());
let db = checked_memory_handle();
assert!(db.close().is_ok());
}
#[test]
fn test_open_with_flags() {
for bad_flags in [
SqliteOpenFlags::empty(),
SQLITE_OPEN_READ_ONLY | SQLITE_OPEN_READ_WRITE,
SQLITE_OPEN_READ_ONLY | SQLITE_OPEN_CREATE,
].iter() {
assert!(SqliteConnection::open_with_flags(":memory:", *bad_flags).is_err());
}
assert!(SqliteConnection::open_with_flags(
"file::memory:", SQLITE_OPEN_READ_ONLY|SQLITE_OPEN_URI).is_ok());
assert!(SqliteConnection::open_with_flags(
"/invalid", SQLITE_OPEN_READ_ONLY|SQLITE_OPEN_MEMORY).is_ok());
}
#[test]
fn test_execute_batch() {
let db = checked_memory_handle();
let sql = "BEGIN;
CREATE TABLE foo(x INTEGER);
INSERT INTO foo VALUES(1);
INSERT INTO foo VALUES(2);
INSERT INTO foo VALUES(3);
INSERT INTO foo VALUES(4);
END;";
db.execute_batch(sql).unwrap();
db.execute_batch("UPDATE foo SET x = 3 WHERE x < 3").unwrap();
assert!(db.execute_batch("INVALID SQL").is_err());
}
#[test]
fn test_execute() {
let db = checked_memory_handle();
db.execute_batch("CREATE TABLE foo(x INTEGER)").unwrap();
assert_eq!(db.execute("INSERT INTO foo(x) VALUES (?)", &[&1i32]).unwrap(), 1);
assert_eq!(db.execute("INSERT INTO foo(x) VALUES (?)", &[&2i32]).unwrap(), 1);
assert_eq!(3i32, db.query_row("SELECT SUM(x) FROM foo", [], |r| r.unwrap().get(0)));
}
#[test]
fn test_prepare_execute() {
let db = checked_memory_handle();
db.execute_batch("CREATE TABLE foo(x INTEGER);").unwrap();
let mut insert_stmt = db.prepare("INSERT INTO foo(x) VALUES(?)").unwrap();
assert_eq!(insert_stmt.execute(&[&1i32]).unwrap(), 1);
assert_eq!(insert_stmt.execute(&[&2i32]).unwrap(), 1);
assert_eq!(insert_stmt.execute(&[&3i32]).unwrap(), 1);
assert_eq!(insert_stmt.execute(&[&"hello".to_string()]).unwrap(), 1);
assert_eq!(insert_stmt.execute(&[&"goodbye".to_string()]).unwrap(), 1);
assert_eq!(insert_stmt.execute(&[&types::Null]).unwrap(), 1);
let mut update_stmt = db.prepare("UPDATE foo SET x=? WHERE x<?").unwrap();
assert_eq!(update_stmt.execute(&[&3i32, &3i32]).unwrap(), 2);
assert_eq!(update_stmt.execute(&[&3i32, &3i32]).unwrap(), 0);
assert_eq!(update_stmt.execute(&[&8i32, &8i32]).unwrap(), 3);
}
#[test]
fn test_prepare_query() {
let db = checked_memory_handle();
db.execute_batch("CREATE TABLE foo(x INTEGER);").unwrap();
let mut insert_stmt = db.prepare("INSERT INTO foo(x) VALUES(?)").unwrap();
assert_eq!(insert_stmt.execute(&[&1i32]).unwrap(), 1);
assert_eq!(insert_stmt.execute(&[&2i32]).unwrap(), 1);
assert_eq!(insert_stmt.execute(&[&3i32]).unwrap(), 1);
let mut query = db.prepare("SELECT x FROM foo WHERE x < ? ORDER BY x DESC").unwrap();
{
let rows = query.query(&[&4i32]).unwrap();
let v: Vec<i32> = rows.map(|r| r.unwrap().get(0)).collect();
assert_eq!(v.as_slice(), [3i32, 2, 1].as_slice());
}
{
let rows = query.query(&[&3i32]).unwrap();
let v: Vec<i32> = rows.map(|r| r.unwrap().get(0)).collect();
assert_eq!(v.as_slice(), [2i32, 1].as_slice());
}
}
#[test]
fn test_prepare_failures() {
let db = checked_memory_handle();
db.execute_batch("CREATE TABLE foo(x INTEGER);").unwrap();
let err = db.prepare("SELECT * FROM does_not_exist").unwrap_err();
assert!(err.message.as_slice().contains("does_not_exist"));
}
#[test]
fn test_row_expiration() {
let db = checked_memory_handle();
db.execute_batch("CREATE TABLE foo(x INTEGER)").unwrap();
db.execute_batch("INSERT INTO foo(x) VALUES(1)").unwrap();
db.execute_batch("INSERT INTO foo(x) VALUES(2)").unwrap();
let mut stmt = db.prepare("SELECT x FROM foo ORDER BY x").unwrap();
let mut rows = stmt.query([]).unwrap();
let first = rows.next().unwrap().unwrap();
let second = rows.next().unwrap().unwrap();
assert_eq!(2i32, second.get(0));
let result = first.get_opt::<i32>(0);
assert!(result.unwrap_err().message.as_slice().contains("advancing to next row"));
}
#[test]
fn test_last_insert_rowid() {
let db = checked_memory_handle();
db.execute_batch("CREATE TABLE foo(x INTEGER PRIMARY KEY)").unwrap();
db.execute_batch("INSERT INTO foo DEFAULT VALUES").unwrap();
assert_eq!(db.last_insert_rowid(), 1);
let mut stmt = db.prepare("INSERT INTO foo DEFAULT VALUES").unwrap();
for _ in range(0i, 9) {
stmt.execute([]).unwrap();
}
assert_eq!(db.last_insert_rowid(), 10);
}
}

195
src/transaction.rs Normal file
View File

@ -0,0 +1,195 @@
use {SqliteResult, SqliteConnection};
pub enum SqliteTransactionMode {
SqliteTransactionDeferred,
SqliteTransactionImmediate,
SqliteTransactionExclusive,
}
pub struct SqliteTransaction<'conn> {
conn: &'conn SqliteConnection,
depth: u32,
commit: bool,
finished: bool,
}
impl<'conn> SqliteTransaction<'conn> {
pub fn new(conn: &SqliteConnection,
mode: SqliteTransactionMode) -> SqliteResult<SqliteTransaction> {
let query = match mode {
SqliteTransactionDeferred => "BEGIN DEFERRED",
SqliteTransactionImmediate => "BEGIN IMMEDIATE",
SqliteTransactionExclusive => "BEGIN EXCLUSIVE",
};
conn.execute_batch(query).map(|_| {
SqliteTransaction{ conn: conn, depth: 0, commit: false, finished: false }
})
}
pub fn savepoint<'a>(&'a self) -> SqliteResult<SqliteTransaction<'a>> {
self.conn.execute_batch("SAVEPOINT sp").map(|_| {
SqliteTransaction{
conn: self.conn, depth: self.depth + 1, commit: false, finished: false
}
})
}
pub fn will_commit(&self) -> bool {
self.commit
}
pub fn will_rollback(&self) -> bool {
!self.commit
}
pub fn set_commit(&mut self) {
self.commit = true
}
pub fn set_rollback(&mut self) {
self.commit = false
}
pub fn commit(mut self) -> SqliteResult<()> {
self.commit_()
}
fn commit_(&mut self) -> SqliteResult<()> {
self.finished = true;
self.conn.execute_batch(if self.depth == 0 { "COMMIT" } else { "RELEASE sp" })
}
pub fn rollback(mut self) -> SqliteResult<()> {
self.rollback_()
}
fn rollback_(&mut self) -> SqliteResult<()> {
self.finished = true;
self.conn.execute_batch(if self.depth == 0 { "ROLLBACK" } else { "ROLLBACK TO sp" })
}
pub fn finish(mut self) -> SqliteResult<()> {
self.finish_()
}
fn finish_(&mut self) -> SqliteResult<()> {
match (self.finished, self.commit) {
(true, _) => Ok(()),
(false, true) => self.commit_(),
(false, false) => self.rollback_(),
}
}
}
#[unsafe_destructor]
#[allow(unused_must_use)]
impl<'conn> Drop for SqliteTransaction<'conn> {
fn drop(&mut self) {
self.finish_();
}
}
#[cfg(test)]
mod test {
extern crate test;
use SqliteConnection;
fn checked_memory_handle() -> SqliteConnection {
let db = SqliteConnection::open(":memory:").unwrap();
db.execute_batch("CREATE TABLE foo (x INTEGER)").unwrap();
db
}
#[test]
fn test_drop() {
let db = checked_memory_handle();
{
let _tx = db.transaction().unwrap();
db.execute_batch("INSERT INTO foo VALUES(1)").unwrap();
// default: rollback
}
{
let mut tx = db.transaction().unwrap();
db.execute_batch("INSERT INTO foo VALUES(2)").unwrap();
tx.set_commit()
}
{
let _tx = db.transaction().unwrap();
assert_eq!(2i32, db.query_row("SELECT SUM(x) FROM foo", [], |r| r.unwrap().get(0)));
}
}
#[test]
fn test_explicit_rollback_commit() {
let db = checked_memory_handle();
{
let tx = db.transaction().unwrap();
db.execute_batch("INSERT INTO foo VALUES(1)").unwrap();
tx.rollback().unwrap();
}
{
let tx = db.transaction().unwrap();
db.execute_batch("INSERT INTO foo VALUES(2)").unwrap();
tx.commit().unwrap();
}
{
let _tx = db.transaction().unwrap();
assert_eq!(2i32, db.query_row("SELECT SUM(x) FROM foo", [], |r| r.unwrap().get(0)));
}
}
#[test]
fn test_savepoint() {
let db = checked_memory_handle();
{
let mut tx = db.transaction().unwrap();
db.execute_batch("INSERT INTO foo VALUES(1)").unwrap();
tx.set_commit();
{
let mut sp1 = tx.savepoint().unwrap();
db.execute_batch("INSERT INTO foo VALUES(2)").unwrap();
sp1.set_commit();
{
let sp2 = sp1.savepoint().unwrap();
db.execute_batch("INSERT INTO foo VALUES(4)").unwrap();
// will rollback sp2
{
let sp3 = sp2.savepoint().unwrap();
db.execute_batch("INSERT INTO foo VALUES(8)").unwrap();
sp3.commit().unwrap();
// committed sp3, but will be erased by sp2 rollback
}
}
}
}
assert_eq!(3i32, db.query_row("SELECT SUM(x) FROM foo", [], |r| r.unwrap().get(0)));
}
#[bench]
fn test_no_transaction_insert(bencher: &mut test::Bencher) {
let db = checked_memory_handle();
let mut stmt = db.prepare("INSERT INTO foo VALUES(1)").unwrap();
bencher.iter(|| {
for _ in range(0i32, 1000) {
stmt.execute([]).unwrap();
}
})
}
#[bench]
fn test_transaction_insert(bencher: &mut test::Bencher) {
let db = checked_memory_handle();
let mut stmt = db.prepare("INSERT INTO foo VALUES(1)").unwrap();
bencher.iter(|| {
let mut tx = db.transaction().unwrap();
tx.set_commit();
for _ in range(0i32, 1000) {
stmt.execute([]).unwrap();
}
})
}
}

180
src/types.rs Normal file
View File

@ -0,0 +1,180 @@
use libc::{c_int, c_double};
use std::c_str::{CString};
use std::mem;
use std::vec;
use super::ffi;
pub trait ToSql {
unsafe fn bind_parameter(&self, stmt: *mut ffi::sqlite3_stmt, col: c_int) -> c_int;
}
pub trait FromSql {
unsafe fn column_result(stmt: *mut ffi::sqlite3_stmt, col: c_int) -> Self;
}
macro_rules! raw_to_impl(
($t:ty, $f:ident) => (
impl ToSql for $t {
unsafe fn bind_parameter(&self, stmt: *mut ffi::sqlite3_stmt, col: c_int) -> c_int {
ffi::$f(stmt, col, *self)
}
}
)
)
raw_to_impl!(c_int, sqlite3_bind_int)
raw_to_impl!(i64, sqlite3_bind_int64)
raw_to_impl!(c_double, sqlite3_bind_double)
impl<'a> ToSql for &'a str {
unsafe fn bind_parameter(&self, stmt: *mut ffi::sqlite3_stmt, col: c_int) -> c_int {
self.with_c_str(|c_str| {
ffi::sqlite3_bind_text(stmt, col, c_str, -1, Some(ffi::SQLITE_TRANSIENT()))
})
}
}
impl ToSql for String {
unsafe fn bind_parameter(&self, stmt: *mut ffi::sqlite3_stmt, col: c_int) -> c_int {
self.as_slice().bind_parameter(stmt, col)
}
}
impl<'a> ToSql for &'a [u8] {
unsafe fn bind_parameter(&self, stmt: *mut ffi::sqlite3_stmt, col: c_int) -> c_int {
ffi::sqlite3_bind_blob(
stmt, col, mem::transmute(self.as_ptr()), self.len() as c_int,
Some(ffi::SQLITE_TRANSIENT()))
}
}
impl ToSql for Vec<u8> {
unsafe fn bind_parameter(&self, stmt: *mut ffi::sqlite3_stmt, col: c_int) -> c_int {
self.as_slice().bind_parameter(stmt, col)
}
}
impl<T: ToSql> ToSql for Option<T> {
unsafe fn bind_parameter(&self, stmt: *mut ffi::sqlite3_stmt, col: c_int) -> c_int {
match *self {
None => ffi::sqlite3_bind_null(stmt, col),
Some(ref t) => t.bind_parameter(stmt, col),
}
}
}
pub struct Null;
impl ToSql for Null {
unsafe fn bind_parameter(&self, stmt: *mut ffi::sqlite3_stmt, col: c_int) -> c_int {
ffi::sqlite3_bind_null(stmt, col)
}
}
macro_rules! raw_from_impl(
($t:ty, $f:ident) => (
impl FromSql for $t {
unsafe fn column_result(stmt: *mut ffi::sqlite3_stmt, col: c_int) -> $t {
ffi::$f(stmt, col)
}
}
)
)
raw_from_impl!(c_int, sqlite3_column_int)
raw_from_impl!(i64, sqlite3_column_int64)
raw_from_impl!(c_double, sqlite3_column_double)
impl FromSql for String {
unsafe fn column_result(stmt: *mut ffi::sqlite3_stmt, col: c_int) -> String {
let c_text = ffi::sqlite3_column_text(stmt, col);
if c_text.is_null() {
"".to_string()
} else {
match CString::new(mem::transmute(c_text), false).as_str() {
Some(s) => s.to_string(),
None => "".to_string(),
}
}
}
}
impl FromSql for Vec<u8> {
unsafe fn column_result(stmt: *mut ffi::sqlite3_stmt, col: c_int) -> Vec<u8> {
let c_blob = ffi::sqlite3_column_blob(stmt, col);
let len = ffi::sqlite3_column_bytes(stmt, col);
assert!(len >= 0); let len = len as uint;
vec::raw::from_buf(mem::transmute(c_blob), len)
}
}
impl<T: FromSql> FromSql for Option<T> {
unsafe fn column_result(stmt: *mut ffi::sqlite3_stmt, col: c_int) -> Option<T> {
if ffi::sqlite3_column_type(stmt, col) == ffi::SQLITE_NULL {
None
} else {
Some(FromSql::column_result(stmt, col))
}
}
}
#[cfg(test)]
mod test {
use SqliteConnection;
fn checked_memory_handle() -> SqliteConnection {
let db = SqliteConnection::open(":memory:").unwrap();
db.execute_batch("CREATE TABLE foo (b BLOB, t TEXT)").unwrap();
db
}
#[test]
fn test_blob() {
let db = checked_memory_handle();
let v1234 = vec![1u8,2,3,4];
db.execute("INSERT INTO foo(b) VALUES (?)", &[&v1234]).unwrap();
let v: Vec<u8> = db.query_row("SELECT b FROM foo", [], |r| r.unwrap().get(0));
assert_eq!(v, v1234);
}
#[test]
fn test_str() {
let db = checked_memory_handle();
let s = "hello, world!";
db.execute("INSERT INTO foo(t) VALUES (?)", &[&s.to_string()]).unwrap();
let from: String = db.query_row("SELECT t FROM foo", [], |r| r.unwrap().get(0));
assert_eq!(from.as_slice(), s);
}
#[test]
fn test_option() {
let db = checked_memory_handle();
let s = Some("hello, world!");
let b = Some(vec![1u8,2,3,4]);
db.execute("INSERT INTO foo(t) VALUES (?)", &[&s]).unwrap();
db.execute("INSERT INTO foo(b) VALUES (?)", &[&b]).unwrap();
let mut stmt = db.prepare("SELECT t, b FROM foo ORDER BY ROWID ASC").unwrap();
let mut rows = stmt.query([]).unwrap();
let row1 = rows.next().unwrap().unwrap();
let s1: Option<String> = row1.get(0);
let b1: Option<Vec<u8>> = row1.get(1);
assert_eq!(s.unwrap(), s1.unwrap().as_slice());
assert!(b1.is_none());
let row2 = rows.next().unwrap().unwrap();
let s2: Option<String> = row2.get(0);
let b2: Option<Vec<u8>> = row2.get(1);
assert!(s2.is_none());
assert_eq!(b, b2);
}
}