mirror of
https://github.com/isar/rusqlite.git
synced 2024-11-23 17:19:19 +08:00
Initial commit
This commit is contained in:
commit
ca3a0f0580
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
@ -0,0 +1,2 @@
|
||||
/target/
|
||||
Cargo.lock
|
7
Cargo.toml
Normal file
7
Cargo.toml
Normal 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
1287
src/ffi/bindgen.rs
Normal file
File diff suppressed because it is too large
Load Diff
83
src/ffi/mod.rs
Normal file
83
src/ffi/mod.rs
Normal 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
501
src/lib.rs
Normal 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
195
src/transaction.rs
Normal 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
180
src/types.rs
Normal 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);
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user