diff --git a/Changelog.md b/Changelog.md index 91bd31f..c3b47fe 100644 --- a/Changelog.md +++ b/Changelog.md @@ -6,6 +6,10 @@ * Adds `types::Value` for dynamic column types. * Adds support for user-defined aggregate functions (behind the existing `functions` Cargo feature). * Introduces a `RowIndex` trait allowing columns to be fetched via index (as before) or name (new). +* Introduces `ZeroBlob` type under the `blob` module/feature exposing SQLite's zeroblob API. +* Adds CI testing for Windows via AppVeyor. +* Fixes a warning building libsqlite3-sys under Rust 1.6. +* Adds an unsafe `handle()` method to `Connection`. Please file an issue if you actually use it. # Version 0.6.0 (2015-12-17) diff --git a/README.md b/README.md index a813174..05635c7 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,7 @@ # Rusqlite -[![Build Status](https://api.travis-ci.org/jgallagher/rusqlite.svg?branch=master)](https://travis-ci.org/jgallagher/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) 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 @@ -72,45 +73,6 @@ features](http://doc.crates.io/manifest.html#the-features-section). They are: * [`blob`](http://jgallagher.github.io/rusqlite/rusqlite/blob/index.html) gives `std::io::{Read, Write, Seek}` access to SQL BLOBs. -### Design of Rows and Row - -To retrieve the result rows from a query, SQLite requires you to call -[sqlite3_step()](https://www.sqlite.org/c3ref/step.html) on a prepared statement. You can only -retrieve the values of the "current" row. From the Rust point of view, this means that each row -is only valid until the next row is fetched. [rust-sqlite3](https://github.com/dckc/rust-sqlite3) -solves this the correct way with lifetimes. However, this means that the result rows do not -satisfy the [Iterator](http://doc.rust-lang.org/std/iter/trait.Iterator.html) trait, which means -you cannot (as easily) loop over the rows, or use many of the helpful Iterator methods like `map` -and `filter`. - -Instead, Rusqlite's `Rows` handle does conform to `Iterator`. It ensures safety by -performing checks at runtime to ensure you do not try to retrieve the values of a "stale" row, and -will panic if you do so. A specific example that will panic: - -```rust -fn bad_function_will_panic(conn: &Connection) -> Result { - let mut stmt = try!(conn.prepare("SELECT id FROM my_table")); - let mut rows = try!(stmt.query(&[])); - - let row0 = try!(rows.next().unwrap()); - // row 0 is valid now... - - let row1 = try!(rows.next().unwrap()); - // row 0 is now STALE, and row 1 is valid - - let my_id = row0.get(0); // WILL PANIC because row 0 is stale - Ok(my_id) -} -``` - -There are other, less obvious things that may result in a panic as well, such as calling -`collect()` on a `Rows` and then trying to use the collected rows. - -Strongly consider using the method `query_map()` instead, if you can. -`query_map()` returns an iterator over rows-mapped-to-some-type. This -iterator does not have any of the above issues with panics due to attempting to -access stale rows. - ## Author John Gallagher, johnkgallagher@gmail.com diff --git a/appveyor.yml b/appveyor.yml new file mode 100644 index 0000000..9fb260e --- /dev/null +++ b/appveyor.yml @@ -0,0 +1,22 @@ +environment: + TARGET: 1.6.0-x86_64-pc-windows-gnu + MSYS2_BITS: 64 +install: + - ps: Start-FileDownload "https://static.rust-lang.org/dist/rust-${env:TARGET}.exe" + - rust-%TARGET%.exe /VERYSILENT /NORESTART /DIR="C:\Program Files (x86)\Rust" + - SET PATH=%PATH%;C:\Program Files (x86)\Rust\bin + - if defined MSYS2_BITS set PATH=%PATH%;C:\msys64\mingw%MSYS2_BITS%\bin + - rustc -V + - cargo -V + - ps: Start-FileDownload 'http://sqlite.org/2016/sqlite-dll-win64-x64-3100200.zip' + - cmd: 7z e sqlite-dll-win64-x64-3100200.zip -y > nul + - SET SQLITE3_LIB_DIR=%APPVEYOR_BUILD_FOLDER% + +build: false + +test_script: + - cargo test --lib --verbose + - cargo test --lib --features "backup blob functions load_extension trace" + +cache: + - C:\Users\appveyor\.cargo diff --git a/libsqlite3-sys/src/bindgen.rs b/libsqlite3-sys/src/bindgen.rs index a60be96..1b60335 100644 --- a/libsqlite3-sys/src/bindgen.rs +++ b/libsqlite3-sys/src/bindgen.rs @@ -1,4 +1,4 @@ -#![allow(raw_pointer_derive, non_snake_case, non_camel_case_types)] +#![allow(non_snake_case, non_camel_case_types)] /* automatically generated by rust-bindgen */ pub type va_list = __builtin_va_list; diff --git a/src/blob.rs b/src/blob.rs index a13f3e7..f8b1710 100644 --- a/src/blob.rs +++ b/src/blob.rs @@ -17,6 +17,7 @@ //! extern crate rusqlite; //! //! use rusqlite::{Connection, DatabaseName}; +//! use rusqlite::blob::ZeroBlob; //! use std::io::{Read, Write, Seek, SeekFrom}; //! //! fn main() { @@ -38,7 +39,7 @@ //! let bytes_read = blob.read(&mut buf[..]).unwrap(); //! assert_eq!(bytes_read, 10); // note we read 10 bytes because the blob has size 10 //! -//! db.execute("INSERT INTO test (content) VALUES (ZEROBLOB(64))", &[]).unwrap(); +//! db.execute("INSERT INTO test (content) VALUES (?)", &[&ZeroBlob(64)]).unwrap(); //! //! // given a new row ID, we can reopen the blob on that row //! let rowid = db.last_insert_rowid(); @@ -51,8 +52,10 @@ use std::io; use std::cmp::min; use std::mem; use std::ptr; +use libc::c_int; use super::ffi; +use super::types::ToSql; use {Result, Connection, DatabaseName}; /// Handle to an open BLOB. @@ -232,6 +235,19 @@ impl<'conn> Drop for Blob<'conn> { } } +/// BLOB of length N that is filled with zeroes. +/// Zeroblobs are intended to serve as placeholders for BLOBs whose content is later written using incremental BLOB I/O routines. +/// A negative value for the zeroblob results in a zero-length BLOB. +#[derive(Copy,Clone)] +pub struct ZeroBlob(pub i32); + +impl ToSql for ZeroBlob { + unsafe fn bind_parameter(&self, stmt: *mut ffi::sqlite3_stmt, col: c_int) -> c_int { + let ZeroBlob(length) = *self; + ffi::sqlite3_bind_zeroblob(stmt, col, length) + } +} + #[cfg(test)] mod test { use std::io::{BufReader, BufRead, BufWriter, Read, Write, Seek, SeekFrom}; diff --git a/src/functions.rs b/src/functions.rs index 0b9b51a..49e0032 100644 --- a/src/functions.rs +++ b/src/functions.rs @@ -489,9 +489,10 @@ impl InnerConnection { where D: Aggregate, T: ToResult { - unsafe fn aggregate_context(ctx: *mut sqlite3_context) -> Option<*mut *mut A> { - let pac = ffi::sqlite3_aggregate_context(ctx, ::std::mem::size_of::<*mut A>() as c_int) - as *mut *mut A; + unsafe fn aggregate_context(ctx: *mut sqlite3_context, + bytes: usize) + -> Option<*mut *mut A> { + let pac = ffi::sqlite3_aggregate_context(ctx, bytes as c_int) as *mut *mut A; if pac.is_null() { return None; } @@ -525,7 +526,7 @@ impl InnerConnection { assert!(!boxed_aggr.is_null(), "Internal error - null aggregate pointer"); - let pac = match aggregate_context(ctx) { + let pac = match aggregate_context(ctx, ::std::mem::size_of::<*mut A>()) { Some(pac) => pac, None => { ffi::sqlite3_result_error_nomem(ctx); @@ -556,19 +557,18 @@ impl InnerConnection { assert!(!boxed_aggr.is_null(), "Internal error - null aggregate pointer"); - let pac = match aggregate_context(ctx) { - Some(pac) => pac, - None => { - ffi::sqlite3_result_error_nomem(ctx); - return; + // Within the xFinal callback, it is customary to set N=0 in calls to + // sqlite3_aggregate_context(C,N) so that no pointless memory allocations occur. + let a: Option = match aggregate_context(ctx, 0) { + Some(pac) => { + if (*pac).is_null() { + None + } else { + let a = Box::from_raw(*pac); + Some(*a) + } } - }; - - let a: Option = if (*pac).is_null() { - None - } else { - let a = Box::from_raw(*pac); - Some(*a) + None => None, }; match (*boxed_aggr).finalize(a) { diff --git a/src/lib.rs b/src/lib.rs index ecbdfa0..9f64d3b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -495,6 +495,18 @@ impl Connection { self.db.borrow_mut().load_extension(dylib_path.as_ref(), entry_point) } + /// Get access to the underlying SQLite database connection handle. + /// + /// # Warning + /// + /// You should not need to use this function. If you do need to, please [open an issue + /// on the rusqlite repository](https://github.com/jgallagher/rusqlite/issues) and describe + /// your use case. This function is unsafe because it gives you raw access to the SQLite + /// connection, and what you do with it could impact the safety of this `Connection`. + pub unsafe fn handle(&self) -> *mut ffi::Struct_sqlite3 { + self.db.borrow().db() + } + fn decode_result(&self, code: c_int) -> Result<()> { self.db.borrow_mut().decode_result(code) } @@ -999,6 +1011,9 @@ pub type SqliteRows<'stmt> = Rows<'stmt>; /// /// ## Warning /// +/// Strongly consider using `query_map` or `query_and_then` instead of `query`; the former do not +/// suffer from the following problem. +/// /// Due to the way SQLite returns result rows of a query, it is not safe to attempt to get values /// from a row after it has become stale (i.e., `next()` has been called again on the `Rows` /// iterator). For example: @@ -1010,7 +1025,7 @@ pub type SqliteRows<'stmt> = Rows<'stmt>; /// let mut rows = try!(stmt.query(&[])); /// /// let row0 = try!(rows.next().unwrap()); -/// // row 0 is value now... +/// // row 0 is valid for now... /// /// let row1 = try!(rows.next().unwrap()); /// // row 0 is now STALE, and row 1 is valid @@ -1024,12 +1039,6 @@ pub type SqliteRows<'stmt> = Rows<'stmt>; /// (which would result in a collection of rows, only the last of which can safely be used) and /// `min`/`max` (which could return a stale row unless the last row happened to be the min or max, /// respectively). -/// -/// This problem could be solved by changing the signature of `next` to tie the lifetime of the -/// returned row to the lifetime of (a mutable reference to) the result rows handle, but this would -/// no longer implement `Iterator`, and therefore you would lose access to the majority of -/// functions which are useful (such as support for `for ... in ...` looping, `map`, `filter`, -/// etc.). pub struct Rows<'stmt> { stmt: &'stmt Statement<'stmt>, current_row: Rc>,