Merge remote-tracking branch 'jgallagher/master' into vtab

This commit is contained in:
gwenn 2018-04-30 22:02:14 +02:00
commit 9c5c9e3c7e
20 changed files with 19079 additions and 9958 deletions

View File

@ -29,6 +29,7 @@ script:
- cargo test --features backup
- cargo test --features blob
- cargo test --features functions
- cargo test --features hooks
- cargo test --features limits
- cargo test --features load_extension
- cargo test --features trace
@ -37,7 +38,7 @@ script:
- cargo test --features bundled
- cargo test --features sqlcipher
- cargo test --features "csvtab functions vtab"
- cargo test --features "backup blob chrono csvtab functions limits load_extension serde_json trace vtab"
- cargo test --features "backup blob chrono csvtab functions limits load_extension serde_json trace vtab buildtime_bindgen"
- cargo test --features "backup blob chrono csvtab functions limits load_extension serde_json trace vtab bundled"
- cargo test --features "backup blob chrono csvtab functions limits load_extension serde_json trace vtab bundled buildtime_bindgen"
- cargo test --features "backup blob chrono csvtab functions hooks limits load_extension serde_json trace vtab"
- cargo test --features "backup blob chrono csvtab functions hooks limits load_extension serde_json trace vtab buildtime_bindgen"
- cargo test --features "backup blob chrono csvtab functions hooks limits load_extension serde_json trace vtab bundled"
- cargo test --features "backup blob chrono csvtab functions hooks limits load_extension serde_json trace vtab bundled buildtime_bindgen"

View File

@ -26,6 +26,7 @@ trace = ["libsqlite3-sys/min_sqlite_version_3_6_23"]
bundled = ["libsqlite3-sys/bundled"]
buildtime_bindgen = ["libsqlite3-sys/buildtime_bindgen"]
limits = []
hooks = []
sqlcipher = ["libsqlite3-sys/sqlcipher"]
vtab = ["libsqlite3-sys/min_sqlite_version_3_7_7"]
csvtab = ["csv"]
@ -40,7 +41,7 @@ csv = { version = "0.15", optional = true }
[dev-dependencies]
tempdir = "0.3"
lazy_static = "0.2"
lazy_static = "1.0"
regex = "0.2"
[dependencies.libsqlite3-sys]

View File

@ -1,7 +1,9 @@
# Rusqlite
[![Travis Build Status](https://api.travis-ci.org/jgallagher/rusqlite.svg?branch=master)](https://travis-ci.org/jgallagher/rusqlite)
[![AppVeyor Build Status](https://ci.appveyor.com/api/projects/status/github/jgallagher/rusqlite?branch=master&svg=true)](https://ci.appveyor.com/project/jgallagher/rusqlite) [![Latest Version](https://img.shields.io/crates/v/rusqlite.svg)](https://crates.io/crates/rusqlite)
[![AppVeyor Build Status](https://ci.appveyor.com/api/projects/status/github/jgallagher/rusqlite?branch=master&svg=true)](https://ci.appveyor.com/project/jgallagher/rusqlite)
[![dependency status](https://deps.rs/repo/github/jgallagher/rusqlite/status.svg)](https://deps.rs/repo/github/jgallagher/rusqlite)
[![Latest Version](https://img.shields.io/crates/v/rusqlite.svg)](https://crates.io/crates/rusqlite)
Rusqlite is an ergonomic wrapper for using SQLite from Rust. It attempts to expose
an interface similar to [rust-postgres](https://github.com/sfackler/rust-postgres). View the full

View File

@ -1,8 +1,8 @@
environment:
matrix:
- TARGET: 1.21.0-x86_64-pc-windows-gnu
- TARGET: 1.24.1-x86_64-pc-windows-gnu
MSYS2_BITS: 64
- TARGET: 1.21.0-x86_64-pc-windows-msvc
- TARGET: 1.24.1-x86_64-pc-windows-msvc
VCPKG_DEFAULT_TRIPLET: x64-windows
VCPKGRS_DYNAMIC: 1
- TARGET: nightly-x86_64-pc-windows-msvc
@ -34,10 +34,10 @@ build: false
test_script:
- cargo test --lib --verbose
- cargo test --lib --verbose --features bundled
- cargo test --lib --features "backup blob chrono csvtab functions limits load_extension serde_json trace vtab"
- cargo test --lib --features "backup blob chrono csvtab functions limits load_extension serde_json trace vtab buildtime_bindgen"
- cargo test --lib --features "backup blob chrono csvtab functions limits load_extension serde_json trace vtab bundled"
- cargo test --lib --features "backup blob chrono csvtab functions limits load_extension serde_json trace vtab bundled buildtime_bindgen"
- cargo test --lib --features "backup blob chrono csvtab functions hooks limits load_extension serde_json trace vtab"
- cargo test --lib --features "backup blob chrono csvtab functions hooks limits load_extension serde_json trace vtab buildtime_bindgen"
- cargo test --lib --features "backup blob chrono csvtab functions hooks limits load_extension serde_json trace vtab bundled"
- cargo test --lib --features "backup blob chrono csvtab functions hooks limits load_extension serde_json trace vtab bundled buildtime_bindgen"
cache:
- C:\Users\appveyor\.cargo

View File

@ -1,6 +1,6 @@
[package]
name = "libsqlite3-sys"
version = "0.9.1"
version = "0.9.2"
authors = ["John Gallagher <jgallagher@bignerdranch.com>"]
repository = "https://github.com/jgallagher/rusqlite"
description = "Native bindings to the libsqlite3 library"
@ -24,7 +24,7 @@ min_sqlite_version_3_7_7 = ["pkg-config", "vcpkg"]
min_sqlite_version_3_7_16 = ["pkg-config", "vcpkg"]
[build-dependencies]
bindgen = { version = "0.31", optional = true }
bindgen = { version = "0.36", optional = true }
pkg-config = { version = "0.3", optional = true }
cc = { version = "1.0", optional = true }

View File

@ -218,6 +218,7 @@ mod build {
bindgen::builder()
.header(header.clone())
.parse_callbacks(Box::new(SqliteTypeChooser))
.rustfmt_bindings(true)
.generate()
.expect(&format!("could not run bindgen on header {}", header))
.write(Box::new(&mut output))

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -134,7 +134,7 @@ struct sqlite3_api_routines {
int (*set_authorizer)(sqlite3*,int(*)(void*,int,const char*,const char*,
const char*,const char*),void*);
void (*set_auxdata)(sqlite3_context*,int,void*,void (*)(void*));
char * (*snprintf)(int,char*,const char*,...);
char * (*xsnprintf)(int,char*,const char*,...);
int (*step)(sqlite3_stmt*);
int (*table_column_metadata)(sqlite3*,const char*,const char*,const char*,
char const**,char const**,int*,int*,int*);
@ -246,7 +246,7 @@ struct sqlite3_api_routines {
int (*uri_boolean)(const char*,const char*,int);
sqlite3_int64 (*uri_int64)(const char*,const char*,sqlite3_int64);
const char *(*uri_parameter)(const char*,const char*);
char *(*vsnprintf)(int,char*,const char*,va_list);
char *(*xvsnprintf)(int,char*,const char*,va_list);
int (*wal_checkpoint_v2)(sqlite3*,const char*,int,int*,int*);
/* Version 3.8.7 and later */
int (*auto_extension)(void(*)(void));
@ -282,6 +282,19 @@ struct sqlite3_api_routines {
/* Version 3.14.0 and later */
int (*trace_v2)(sqlite3*,unsigned,int(*)(unsigned,void*,void*,void*),void*);
char *(*expanded_sql)(sqlite3_stmt*);
/* Version 3.18.0 and later */
void (*set_last_insert_rowid)(sqlite3*,sqlite3_int64);
/* Version 3.20.0 and later */
int (*prepare_v3)(sqlite3*,const char*,int,unsigned int,
sqlite3_stmt**,const char**);
int (*prepare16_v3)(sqlite3*,const void*,int,unsigned int,
sqlite3_stmt**,const void**);
int (*bind_pointer)(sqlite3_stmt*,int,void*,const char*,void(*)(void*));
void (*result_pointer)(sqlite3_context*,void*,const char*,void(*)(void*));
void *(*value_pointer)(sqlite3_value*,const char*);
int (*vtab_nochange)(sqlite3_context*);
int (*value_nochange)(sqlite3_value*);
const char *(*vtab_collation)(sqlite3_index_info*,int);
};
/*
@ -408,7 +421,7 @@ typedef int (*sqlite3_loadext_entry)(
#define sqlite3_rollback_hook sqlite3_api->rollback_hook
#define sqlite3_set_authorizer sqlite3_api->set_authorizer
#define sqlite3_set_auxdata sqlite3_api->set_auxdata
#define sqlite3_snprintf sqlite3_api->snprintf
#define sqlite3_snprintf sqlite3_api->xsnprintf
#define sqlite3_step sqlite3_api->step
#define sqlite3_table_column_metadata sqlite3_api->table_column_metadata
#define sqlite3_thread_cleanup sqlite3_api->thread_cleanup
@ -432,7 +445,7 @@ typedef int (*sqlite3_loadext_entry)(
#define sqlite3_value_text16le sqlite3_api->value_text16le
#define sqlite3_value_type sqlite3_api->value_type
#define sqlite3_vmprintf sqlite3_api->vmprintf
#define sqlite3_vsnprintf sqlite3_api->vsnprintf
#define sqlite3_vsnprintf sqlite3_api->xvsnprintf
#define sqlite3_overload_function sqlite3_api->overload_function
#define sqlite3_prepare_v2 sqlite3_api->prepare_v2
#define sqlite3_prepare16_v2 sqlite3_api->prepare16_v2
@ -508,7 +521,7 @@ typedef int (*sqlite3_loadext_entry)(
#define sqlite3_uri_boolean sqlite3_api->uri_boolean
#define sqlite3_uri_int64 sqlite3_api->uri_int64
#define sqlite3_uri_parameter sqlite3_api->uri_parameter
#define sqlite3_uri_vsnprintf sqlite3_api->vsnprintf
#define sqlite3_uri_vsnprintf sqlite3_api->xvsnprintf
#define sqlite3_wal_checkpoint_v2 sqlite3_api->wal_checkpoint_v2
/* Version 3.8.7 and later */
#define sqlite3_auto_extension sqlite3_api->auto_extension
@ -540,6 +553,18 @@ typedef int (*sqlite3_loadext_entry)(
/* Version 3.14.0 and later */
#define sqlite3_trace_v2 sqlite3_api->trace_v2
#define sqlite3_expanded_sql sqlite3_api->expanded_sql
/* Version 3.18.0 and later */
#define sqlite3_set_last_insert_rowid sqlite3_api->set_last_insert_rowid
/* Version 3.20.0 and later */
#define sqlite3_prepare_v3 sqlite3_api->prepare_v3
#define sqlite3_prepare16_v3 sqlite3_api->prepare16_v3
#define sqlite3_bind_pointer sqlite3_api->bind_pointer
#define sqlite3_result_pointer sqlite3_api->result_pointer
#define sqlite3_value_pointer sqlite3_api->value_pointer
/* Version 3.22.0 and later */
#define sqlite3_vtab_nochange sqlite3_api->vtab_nochange
#define sqlite3_value_nochange sqltie3_api->value_nochange
#define sqlite3_vtab_collation sqltie3_api->vtab_collation
#endif /* !defined(SQLITE_CORE) && !defined(SQLITE_OMIT_LOAD_EXTENSION) */
#if !defined(SQLITE_CORE) && !defined(SQLITE_OMIT_LOAD_EXTENSION)

26
libsqlite3-sys/upgrade.sh Executable file
View File

@ -0,0 +1,26 @@
SCRIPT_DIR=$(cd "$(dirname "$_")" && pwd)
echo $SCRIPT_DIR
cd $SCRIPT_DIR
SQLITE3_LIB_DIR=$SCRIPT_DIR/sqlite3
# Download and extract amalgamation
SQLITE=sqlite-amalgamation-3220000
curl -O http://sqlite.org/2018/$SQLITE.zip
unzip -p $SQLITE.zip $SQLITE/sqlite3.c > $SQLITE3_LIB_DIR/sqlite3.c
unzip -p $SQLITE.zip $SQLITE/sqlite3.h > $SQLITE3_LIB_DIR/sqlite3.h
unzip -p $SQLITE.zip $SQLITE/sqlite3ext.h > $SQLITE3_LIB_DIR/sqlite3ext.h
rm -f $SQLITE.zip
# Regenerate bindgen file
rm -f $SQLITE3_LIB_DIR/bindgen_bundled_version.rs
SQLITE3_INCLUDE_DIR=$SQLITE3_LIB_DIR
cargo update
# Just to make sure there is only one bindgen.rs file in target dir
find $SCRIPT_DIR/target -type f -name bindgen.rs -exec rm {} \;
cargo build --features "buildtime_bindgen" --no-default-features
find $SCRIPT_DIR/target -type f -name bindgen.rs -exec cp {} $SQLITE3_LIB_DIR/bindgen_bundled_version.rs \;
# Sanity check
cd $SCRIPT_DIR/..
cargo update
cargo test --features "backup blob chrono functions limits load_extension serde_json trace bundled"
echo 'You should increment the version in libsqlite3-sys/Cargo.toml'

View File

@ -50,7 +50,6 @@
//! ```
use std::io;
use std::cmp::min;
use std::mem;
use std::ptr;
use super::ffi;
@ -65,7 +64,7 @@ pub struct Blob<'conn> {
}
impl Connection {
/// Open a handle to the BLOB located in `row`, `column`, `table` in database `db`.
/// Open a handle to the BLOB located in `row_id`, `column`, `table` in database `db`.
///
/// # Failure
///
@ -75,7 +74,7 @@ impl Connection {
db: DatabaseName,
table: &str,
column: &str,
row: i64,
row_id: i64,
read_only: bool)
-> Result<Blob<'a>> {
let mut c = self.db.borrow_mut();
@ -88,7 +87,7 @@ impl Connection {
db.as_ptr(),
table.as_ptr(),
column.as_ptr(),
row,
row_id,
if read_only { 0 } else { 1 },
&mut blob)
};
@ -156,7 +155,7 @@ impl<'conn> io::Read for Blob<'conn> {
return Ok(0);
}
let rc =
unsafe { ffi::sqlite3_blob_read(self.blob, mem::transmute(buf.as_ptr()), n, self.pos) };
unsafe { ffi::sqlite3_blob_read(self.blob, buf.as_ptr() as *mut _, n, self.pos) };
self.conn
.decode_result(rc)
.map(|_| {
@ -185,7 +184,7 @@ impl<'conn> io::Write for Blob<'conn> {
return Ok(0);
}
let rc = unsafe {
ffi::sqlite3_blob_write(self.blob, mem::transmute(buf.as_ptr()), n, self.pos)
ffi::sqlite3_blob_write(self.blob, buf.as_ptr() as *mut _, n, self.pos)
};
self.conn
.decode_result(rc)

View File

@ -46,6 +46,7 @@ impl Connection {
self.cache.set_capacity(capacity)
}
/// Remove/finalize all prepared statements currently in the cache.
pub fn flush_prepared_statement_cache(&self) {
self.cache.flush()
}
@ -124,7 +125,7 @@ impl StatementCache {
sql: &str)
-> Result<CachedStatement<'conn>> {
let mut cache = self.0.borrow_mut();
let stmt = match cache.remove(sql) {
let stmt = match cache.remove(sql.trim()) {
Some(raw_stmt) => Ok(Statement::new(conn, raw_stmt)),
None => conn.prepare(sql),
};
@ -135,7 +136,7 @@ impl StatementCache {
fn cache_stmt(&self, stmt: RawStatement) {
let mut cache = self.0.borrow_mut();
stmt.clear_bindings();
let sql = String::from_utf8_lossy(stmt.sql().to_bytes()).to_string();
let sql = String::from_utf8_lossy(stmt.sql().to_bytes()).trim().to_string();
cache.insert(sql, stmt);
}
@ -285,4 +286,27 @@ mod test {
conn.close().expect("connection not closed");
}
#[test]
fn test_cache_key() {
let db = Connection::open_in_memory().unwrap();
let cache = &db.cache;
assert_eq!(0, cache.len());
//let sql = " PRAGMA schema_version; -- comment";
let sql = "PRAGMA schema_version; ";
{
let mut stmt = db.prepare_cached(sql).unwrap();
assert_eq!(0, cache.len());
assert_eq!(0, stmt.query_row(&[], |r| r.get::<i32, i64>(0)).unwrap());
}
assert_eq!(1, cache.len());
{
let mut stmt = db.prepare_cached(sql).unwrap();
assert_eq!(0, cache.len());
assert_eq!(0, stmt.query_row(&[], |r| r.get::<i32, i64>(0)).unwrap());
}
assert_eq!(1, cache.len());
}
}

View File

@ -443,7 +443,7 @@ impl InnerConnection {
}
};
if (*pac).is_null() {
if (*pac as *mut A).is_null() {
*pac = Box::into_raw(Box::new((*boxed_aggr).init()));
}
@ -470,7 +470,7 @@ impl InnerConnection {
// sqlite3_aggregate_context(C,N) so that no pointless memory allocations occur.
let a: Option<A> = match aggregate_context(ctx, 0) {
Some(pac) => {
if (*pac).is_null() {
if (*pac as *mut A).is_null() {
None
} else {
let a = Box::from_raw(*pac);

309
src/hooks.rs Normal file
View File

@ -0,0 +1,309 @@
//! Commit, Data Change and Rollback Notification Callbacks
#![allow(non_camel_case_types)]
use std::mem;
use std::ptr;
use std::os::raw::{c_int, c_char, c_void};
use ffi;
use {Connection, InnerConnection};
/// Authorizer Action Codes
#[derive(Debug, PartialEq)]
pub enum Action {
UNKNOWN = -1,
SQLITE_CREATE_INDEX = ffi::SQLITE_CREATE_INDEX as isize,
SQLITE_CREATE_TABLE = ffi::SQLITE_CREATE_TABLE as isize,
SQLITE_CREATE_TEMP_INDEX = ffi::SQLITE_CREATE_TEMP_INDEX as isize,
SQLITE_CREATE_TEMP_TABLE = ffi::SQLITE_CREATE_TEMP_TABLE as isize,
SQLITE_CREATE_TEMP_TRIGGER = ffi::SQLITE_CREATE_TEMP_TRIGGER as isize,
SQLITE_CREATE_TEMP_VIEW = ffi::SQLITE_CREATE_TEMP_VIEW as isize,
SQLITE_CREATE_TRIGGER = ffi::SQLITE_CREATE_TRIGGER as isize,
SQLITE_CREATE_VIEW = ffi::SQLITE_CREATE_VIEW as isize,
SQLITE_DELETE = ffi::SQLITE_DELETE as isize,
SQLITE_DROP_INDEX = ffi::SQLITE_DROP_INDEX as isize,
SQLITE_DROP_TABLE = ffi::SQLITE_DROP_TABLE as isize,
SQLITE_DROP_TEMP_INDEX = ffi::SQLITE_DROP_TEMP_INDEX as isize,
SQLITE_DROP_TEMP_TABLE = ffi::SQLITE_DROP_TEMP_TABLE as isize,
SQLITE_DROP_TEMP_TRIGGER = ffi::SQLITE_DROP_TEMP_TRIGGER as isize,
SQLITE_DROP_TEMP_VIEW = ffi::SQLITE_DROP_TEMP_VIEW as isize,
SQLITE_DROP_TRIGGER = ffi::SQLITE_DROP_TRIGGER as isize,
SQLITE_DROP_VIEW = ffi::SQLITE_DROP_VIEW as isize,
SQLITE_INSERT = ffi::SQLITE_INSERT as isize,
SQLITE_PRAGMA = ffi::SQLITE_PRAGMA as isize,
SQLITE_READ = ffi::SQLITE_READ as isize,
SQLITE_SELECT = ffi::SQLITE_SELECT as isize,
SQLITE_TRANSACTION = ffi::SQLITE_TRANSACTION as isize,
SQLITE_UPDATE = ffi::SQLITE_UPDATE as isize,
SQLITE_ATTACH = ffi::SQLITE_ATTACH as isize,
SQLITE_DETACH = ffi::SQLITE_DETACH as isize,
SQLITE_ALTER_TABLE = ffi::SQLITE_ALTER_TABLE as isize,
SQLITE_REINDEX = ffi::SQLITE_REINDEX as isize,
SQLITE_ANALYZE = ffi::SQLITE_ANALYZE as isize,
SQLITE_CREATE_VTABLE = ffi::SQLITE_CREATE_VTABLE as isize,
SQLITE_DROP_VTABLE = ffi::SQLITE_DROP_VTABLE as isize,
SQLITE_FUNCTION = ffi::SQLITE_FUNCTION as isize,
SQLITE_SAVEPOINT = ffi::SQLITE_SAVEPOINT as isize,
SQLITE_COPY = ffi::SQLITE_COPY as isize,
SQLITE_RECURSIVE = 33,
}
impl From<i32> for Action {
fn from(code: i32) -> Action {
match code {
ffi::SQLITE_CREATE_INDEX => Action::SQLITE_CREATE_INDEX,
ffi::SQLITE_CREATE_TABLE => Action::SQLITE_CREATE_TABLE,
ffi::SQLITE_CREATE_TEMP_INDEX => Action::SQLITE_CREATE_TEMP_INDEX,
ffi::SQLITE_CREATE_TEMP_TABLE => Action::SQLITE_CREATE_TEMP_TABLE,
ffi::SQLITE_CREATE_TEMP_TRIGGER => Action::SQLITE_CREATE_TEMP_TRIGGER,
ffi::SQLITE_CREATE_TEMP_VIEW => Action::SQLITE_CREATE_TEMP_VIEW,
ffi::SQLITE_CREATE_TRIGGER => Action::SQLITE_CREATE_TRIGGER,
ffi::SQLITE_CREATE_VIEW => Action::SQLITE_CREATE_VIEW,
ffi::SQLITE_DELETE => Action::SQLITE_DELETE,
ffi::SQLITE_DROP_INDEX => Action::SQLITE_DROP_INDEX,
ffi::SQLITE_DROP_TABLE => Action::SQLITE_DROP_TABLE,
ffi::SQLITE_DROP_TEMP_INDEX => Action::SQLITE_DROP_TEMP_INDEX,
ffi::SQLITE_DROP_TEMP_TABLE => Action::SQLITE_DROP_TEMP_TABLE,
ffi::SQLITE_DROP_TEMP_TRIGGER => Action::SQLITE_DROP_TEMP_TRIGGER,
ffi::SQLITE_DROP_TEMP_VIEW => Action::SQLITE_DROP_TEMP_VIEW,
ffi::SQLITE_DROP_TRIGGER => Action::SQLITE_DROP_TRIGGER,
ffi::SQLITE_DROP_VIEW => Action::SQLITE_DROP_VIEW,
ffi::SQLITE_INSERT => Action::SQLITE_INSERT,
ffi::SQLITE_PRAGMA => Action::SQLITE_PRAGMA,
ffi::SQLITE_READ => Action::SQLITE_READ,
ffi::SQLITE_SELECT => Action::SQLITE_SELECT,
ffi::SQLITE_TRANSACTION => Action::SQLITE_TRANSACTION,
ffi::SQLITE_UPDATE => Action::SQLITE_UPDATE,
ffi::SQLITE_ATTACH => Action::SQLITE_ATTACH,
ffi::SQLITE_DETACH => Action::SQLITE_DETACH,
ffi::SQLITE_ALTER_TABLE => Action::SQLITE_ALTER_TABLE,
ffi::SQLITE_REINDEX => Action::SQLITE_REINDEX,
ffi::SQLITE_ANALYZE => Action::SQLITE_ANALYZE,
ffi::SQLITE_CREATE_VTABLE => Action::SQLITE_CREATE_VTABLE,
ffi::SQLITE_DROP_VTABLE => Action::SQLITE_DROP_VTABLE,
ffi::SQLITE_FUNCTION => Action::SQLITE_FUNCTION,
ffi::SQLITE_SAVEPOINT => Action::SQLITE_SAVEPOINT,
ffi::SQLITE_COPY => Action::SQLITE_COPY,
33 => Action::SQLITE_RECURSIVE,
_ => Action::UNKNOWN,
}
}
}
impl Connection {
/// Register a callback function to be invoked whenever a transaction is committed.
///
/// The callback returns `true` to rollback.
pub fn commit_hook<F>(&self, hook: F)
where F: FnMut() -> bool
{
self.db.borrow_mut().commit_hook(hook);
}
/// Register a callback function to be invoked whenever a transaction is committed.
///
/// The callback returns `true` to rollback.
pub fn rollback_hook<F>(&self, hook: F)
where F: FnMut()
{
self.db.borrow_mut().rollback_hook(hook);
}
/// Register a callback function to be invoked whenever a row is updated,
/// inserted or deleted in a rowid table.
///
/// The callback parameters are:
///
/// - the type of database update (SQLITE_INSERT, SQLITE_UPDATE or SQLITE_DELETE),
/// - the name of the database ("main", "temp", ...),
/// - the name of the table that is updated,
/// - the ROWID of the row that is updated.
pub fn update_hook<F>(&self, hook: F)
where F: FnMut(Action, &str, &str, i64)
{
self.db.borrow_mut().update_hook(hook);
}
/// Remove hook installed by `update_hook`.
pub fn remove_update_hook(&self) {
self.db.borrow_mut().remove_update_hook();
}
/// Remove hook installed by `commit_hook`.
pub fn remove_commit_hook(&self) {
self.db.borrow_mut().remove_commit_hook();
}
/// Remove hook installed by `rollback_hook`.
pub fn remove_rollback_hook(&self) {
self.db.borrow_mut().remove_rollback_hook();
}
}
impl InnerConnection {
pub fn remove_hooks(&mut self) {
self.remove_update_hook();
self.remove_commit_hook();
self.remove_rollback_hook();
}
fn commit_hook<F>(&self, hook: F)
where F: FnMut() -> bool
{
unsafe extern "C" fn call_boxed_closure<F>(p_arg: *mut c_void) -> c_int
where F: FnMut() -> bool
{
let boxed_hook: *mut F = mem::transmute(p_arg);
assert!(!boxed_hook.is_null(),
"Internal error - null function pointer");
if (*boxed_hook)() { 1 } else { 0 }
}
let previous_hook = {
let boxed_hook: *mut F = Box::into_raw(Box::new(hook));
unsafe {
ffi::sqlite3_commit_hook(self.db(),
Some(call_boxed_closure::<F>),
boxed_hook as *mut _)
}
};
free_boxed_hook(previous_hook);
}
fn rollback_hook<F>(&self, hook: F)
where F: FnMut()
{
unsafe extern "C" fn call_boxed_closure<F>(p_arg: *mut c_void)
where F: FnMut()
{
let boxed_hook: *mut F = mem::transmute(p_arg);
assert!(!boxed_hook.is_null(),
"Internal error - null function pointer");
(*boxed_hook)();
}
let previous_hook = {
let boxed_hook: *mut F = Box::into_raw(Box::new(hook));
unsafe {
ffi::sqlite3_rollback_hook(self.db(),
Some(call_boxed_closure::<F>),
boxed_hook as *mut _)
}
};
free_boxed_hook(previous_hook);
}
fn update_hook<F>(&mut self, hook: F)
where F: FnMut(Action, &str, &str, i64)
{
unsafe extern "C" fn call_boxed_closure<F>(p_arg: *mut c_void,
action_code: c_int,
db_str: *const c_char,
tbl_str: *const c_char,
row_id: i64)
where F: FnMut(Action, &str, &str, i64)
{
use std::ffi::CStr;
use std::str;
let boxed_hook: *mut F = mem::transmute(p_arg);
assert!(!boxed_hook.is_null(),
"Internal error - null function pointer");
let action = Action::from(action_code);
let db_name = {
let c_slice = CStr::from_ptr(db_str).to_bytes();
str::from_utf8_unchecked(c_slice)
};
let tbl_name = {
let c_slice = CStr::from_ptr(tbl_str).to_bytes();
str::from_utf8_unchecked(c_slice)
};
(*boxed_hook)(action, db_name, tbl_name, row_id);
}
let previous_hook = {
let boxed_hook: *mut F = Box::into_raw(Box::new(hook));
unsafe {
ffi::sqlite3_update_hook(self.db(),
Some(call_boxed_closure::<F>),
boxed_hook as *mut _)
}
};
free_boxed_hook(previous_hook);
}
fn remove_update_hook(&mut self) {
let previous_hook = unsafe { ffi::sqlite3_update_hook(self.db(), None, ptr::null_mut()) };
free_boxed_hook(previous_hook);
}
fn remove_commit_hook(&mut self) {
let previous_hook = unsafe { ffi::sqlite3_commit_hook(self.db(), None, ptr::null_mut()) };
free_boxed_hook(previous_hook);
}
fn remove_rollback_hook(&mut self) {
let previous_hook = unsafe { ffi::sqlite3_rollback_hook(self.db(), None, ptr::null_mut()) };
free_boxed_hook(previous_hook);
}
}
fn free_boxed_hook(hook: *mut c_void) {
if !hook.is_null() {
// TODO make sure that size_of::<*mut F>() is always equal to size_of::<*mut c_void>()
let _: Box<*mut c_void> = unsafe { Box::from_raw(hook as *mut _) };
}
}
#[cfg(test)]
mod test {
use super::Action;
use Connection;
#[test]
fn test_commit_hook() {
let db = Connection::open_in_memory().unwrap();
let mut called = false;
db.commit_hook(|| {
called = true;
false
});
db.execute_batch("BEGIN; CREATE TABLE foo (t TEXT); COMMIT;")
.unwrap();
assert!(called);
}
#[test]
fn test_rollback_hook() {
let db = Connection::open_in_memory().unwrap();
let mut called = false;
db.rollback_hook(|| { called = true; });
db.execute_batch("BEGIN; CREATE TABLE foo (t TEXT); ROLLBACK;")
.unwrap();
assert!(called);
}
#[test]
fn test_update_hook() {
let db = Connection::open_in_memory().unwrap();
let mut called = false;
db.update_hook(|action, db, tbl, row_id| {
assert_eq!(Action::SQLITE_INSERT, action);
assert_eq!("main", db);
assert_eq!("foo", tbl);
assert_eq!(1, row_id);
called = true;
});
db.execute_batch("CREATE TABLE foo (t TEXT)").unwrap();
db.execute_batch("INSERT INTO foo VALUES ('lisa')").unwrap();
assert!(called);
}
}

View File

@ -121,6 +121,10 @@ pub mod functions;
pub mod blob;
#[cfg(feature = "limits")]
pub mod limits;
#[cfg(feature = "hooks")]
mod hooks;
#[cfg(feature = "hooks")]
pub use hooks::*;
#[cfg(all(feature = "vtab", feature = "functions"))]
pub mod vtab;
@ -197,7 +201,7 @@ impl Connection {
/// Open a new connection to a SQLite database.
///
/// `Connection::open(path)` is equivalent to `Connection::open_with_flags(path,
/// SQLITE_OPEN_READ_WRITE | SQLITE_OPEN_CREATE)`.
/// OpenFlags::SQLITE_OPEN_READ_WRITE | OpenFlags::SQLITE_OPEN_CREATE)`.
///
/// # Failure
///
@ -794,6 +798,10 @@ impl InnerConnection {
}
fn close(&mut self) -> Result<()> {
if self.db.is_null() {
return Ok(());
}
self.remove_hooks();
unsafe {
let r = ffi::sqlite3_close(self.db());
let r = self.decode_result(r);
@ -871,6 +879,10 @@ impl InnerConnection {
fn changes(&mut self) -> c_int {
unsafe { ffi::sqlite3_changes(self.db()) }
}
#[cfg(not(feature = "hooks"))]
fn remove_hooks(&mut self) {
}
}
impl Drop for InnerConnection {

View File

@ -45,7 +45,7 @@ impl<'conn> Statement<'conn> {
let bytes = name.as_bytes();
let n = self.column_count();
for i in 0..n {
if bytes == self.stmt.column_name(i).to_bytes() {
if bytes.eq_ignore_ascii_case(self.stmt.column_name(i).to_bytes()) {
return Ok(i);
}
}
@ -785,4 +785,30 @@ mod test {
let y: Result<i64> = stmt.query_row(&[&1i32], |r| r.get(0));
assert_eq!(3i64, y.unwrap());
}
#[test]
fn test_query_by_column_name() {
let db = Connection::open_in_memory().unwrap();
let sql = "BEGIN;
CREATE TABLE foo(x INTEGER, y INTEGER);
INSERT INTO foo VALUES(1, 3);
END;";
db.execute_batch(sql).unwrap();
let mut stmt = db.prepare("SELECT y FROM foo").unwrap();
let y: Result<i64> = stmt.query_row(&[], |r| r.get("y"));
assert_eq!(3i64, y.unwrap());
}
#[test]
fn test_query_by_column_name_ignore_case() {
let db = Connection::open_in_memory().unwrap();
let sql = "BEGIN;
CREATE TABLE foo(x INTEGER, y INTEGER);
INSERT INTO foo VALUES(1, 3);
END;";
db.execute_batch(sql).unwrap();
let mut stmt = db.prepare("SELECT y as Y FROM foo").unwrap();
let y: Result<i64> = stmt.query_row(&[], |r| r.get("y"));
assert_eq!(3i64, y.unwrap());
}
}

View File

@ -26,6 +26,9 @@ pub enum DropBehavior {
/// Do not commit or roll back changes - this will leave the transaction or savepoint
/// open, so should be used with care.
Ignore,
/// Panic. Used to enforce intentional behavior during development.
Panic,
}
/// Old name for `Transaction`. `SqliteTransaction` is deprecated.
@ -94,6 +97,9 @@ pub struct Savepoint<'conn> {
impl<'conn> Transaction<'conn> {
/// Begin a new transaction. Cannot be nested; see `savepoint` for nested transactions.
// Even though we don't mutate the connection, we take a `&mut Connection`
// so as to prevent nested or concurrent transactions on the same
// connection.
pub fn new(conn: &mut Connection, behavior: TransactionBehavior) -> Result<Transaction> {
let query = match behavior {
TransactionBehavior::Deferred => "BEGIN DEFERRED",
@ -192,6 +198,7 @@ impl<'conn> Transaction<'conn> {
DropBehavior::Commit => self.commit_(),
DropBehavior::Rollback => self.rollback_(),
DropBehavior::Ignore => Ok(()),
DropBehavior::Panic => panic!("Transaction dropped unexpectedly."),
}
}
}
@ -303,6 +310,7 @@ impl<'conn> Savepoint<'conn> {
DropBehavior::Commit => self.commit_(),
DropBehavior::Rollback => self.rollback(),
DropBehavior::Ignore => Ok(()),
DropBehavior::Panic => panic!("Savepoint dropped unexpectedly."),
}
}
}

View File

@ -10,12 +10,13 @@
//! * Strings (`String` and `&str`)
//! * Blobs (`Vec<u8>` and `&[u8]`)
//!
//! Additionally, because it is such a common data type, implementations are provided for
//! `time::Timespec` that use a string for storage (using the same format string,
//! `"%Y-%m-%d %H:%M:%S"`, as SQLite's builtin
//! [datetime](https://www.sqlite.org/lang_datefunc.html) function. Note that this storage
//! truncates timespecs to the nearest second. If you want different storage for timespecs, you can
//! use a newtype. For example, to store timespecs as `f64`s:
//! Additionally, because it is such a common data type, implementations are
//! provided for `time::Timespec` that use the RFC 3339 date/time format,
//! `"%Y-%m-%dT%H:%M:%S.%fZ"`, to store time values as strings. These values
//! can be parsed by SQLite's builtin
//! [datetime](https://www.sqlite.org/lang_datefunc.html) functions. If you
//! want different storage for timespecs, you can use a newtype. For example, to
//! store timespecs as `f64`s:
//!
//! ```rust
//! extern crate rusqlite;

View File

@ -3,7 +3,8 @@ extern crate time;
use Result;
use types::{FromSql, FromSqlError, FromSqlResult, ToSql, ToSqlOutput, ValueRef};
const SQLITE_DATETIME_FMT: &str = "%Y-%m-%d %H:%M:%S:%f %Z";
const SQLITE_DATETIME_FMT: &str = "%Y-%m-%dT%H:%M:%S.%fZ";
const SQLITE_DATETIME_FMT_LEGACY: &str = "%Y-%m-%d %H:%M:%S:%f %Z";
impl ToSql for time::Timespec {
fn to_sql(&self) -> Result<ToSqlOutput> {
@ -19,10 +20,12 @@ impl FromSql for time::Timespec {
fn column_result(value: ValueRef) -> FromSqlResult<Self> {
value
.as_str()
.and_then(|s| match time::strptime(s, SQLITE_DATETIME_FMT) {
Ok(tm) => Ok(tm.to_timespec()),
Err(err) => Err(FromSqlError::Other(Box::new(err))),
})
.and_then(|s| {
time::strptime(s, SQLITE_DATETIME_FMT)
.or_else(|err| {
time::strptime(s, SQLITE_DATETIME_FMT_LEGACY)
.or(Err(FromSqlError::Other(Box::new(err))))})})
.map(|tm| tm.to_timespec())
}
}