mirror of
https://github.com/isar/rusqlite.git
synced 2025-02-01 20:40:51 +08:00
Merge branch 'master' into gwenn-stmt-cache
This commit is contained in:
commit
9a4095365f
@ -6,6 +6,10 @@
|
|||||||
* Adds `types::Value` for dynamic column types.
|
* Adds `types::Value` for dynamic column types.
|
||||||
* Adds support for user-defined aggregate functions (behind the existing `functions` Cargo feature).
|
* 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 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)
|
# Version 0.6.0 (2015-12-17)
|
||||||
|
|
||||||
|
42
README.md
42
README.md
@ -1,6 +1,7 @@
|
|||||||
# Rusqlite
|
# 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
|
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
|
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)
|
* [`blob`](http://jgallagher.github.io/rusqlite/rusqlite/blob/index.html)
|
||||||
gives `std::io::{Read, Write, Seek}` access to SQL BLOBs.
|
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<i64> {
|
|
||||||
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
|
## Author
|
||||||
|
|
||||||
John Gallagher, johnkgallagher@gmail.com
|
John Gallagher, johnkgallagher@gmail.com
|
||||||
|
22
appveyor.yml
Normal file
22
appveyor.yml
Normal file
@ -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
|
@ -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 */
|
/* automatically generated by rust-bindgen */
|
||||||
|
|
||||||
pub type va_list = __builtin_va_list;
|
pub type va_list = __builtin_va_list;
|
||||||
|
18
src/blob.rs
18
src/blob.rs
@ -17,6 +17,7 @@
|
|||||||
//! extern crate rusqlite;
|
//! extern crate rusqlite;
|
||||||
//!
|
//!
|
||||||
//! use rusqlite::{Connection, DatabaseName};
|
//! use rusqlite::{Connection, DatabaseName};
|
||||||
|
//! use rusqlite::blob::ZeroBlob;
|
||||||
//! use std::io::{Read, Write, Seek, SeekFrom};
|
//! use std::io::{Read, Write, Seek, SeekFrom};
|
||||||
//!
|
//!
|
||||||
//! fn main() {
|
//! fn main() {
|
||||||
@ -38,7 +39,7 @@
|
|||||||
//! let bytes_read = blob.read(&mut buf[..]).unwrap();
|
//! let bytes_read = blob.read(&mut buf[..]).unwrap();
|
||||||
//! assert_eq!(bytes_read, 10); // note we read 10 bytes because the blob has size 10
|
//! 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
|
//! // given a new row ID, we can reopen the blob on that row
|
||||||
//! let rowid = db.last_insert_rowid();
|
//! let rowid = db.last_insert_rowid();
|
||||||
@ -51,8 +52,10 @@ use std::io;
|
|||||||
use std::cmp::min;
|
use std::cmp::min;
|
||||||
use std::mem;
|
use std::mem;
|
||||||
use std::ptr;
|
use std::ptr;
|
||||||
|
use libc::c_int;
|
||||||
|
|
||||||
use super::ffi;
|
use super::ffi;
|
||||||
|
use super::types::ToSql;
|
||||||
use {Result, Connection, DatabaseName};
|
use {Result, Connection, DatabaseName};
|
||||||
|
|
||||||
/// Handle to an open BLOB.
|
/// 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)]
|
#[cfg(test)]
|
||||||
mod test {
|
mod test {
|
||||||
use std::io::{BufReader, BufRead, BufWriter, Read, Write, Seek, SeekFrom};
|
use std::io::{BufReader, BufRead, BufWriter, Read, Write, Seek, SeekFrom};
|
||||||
|
@ -489,9 +489,10 @@ impl InnerConnection {
|
|||||||
where D: Aggregate<A, T>,
|
where D: Aggregate<A, T>,
|
||||||
T: ToResult
|
T: ToResult
|
||||||
{
|
{
|
||||||
unsafe fn aggregate_context<A>(ctx: *mut sqlite3_context) -> Option<*mut *mut A> {
|
unsafe fn aggregate_context<A>(ctx: *mut sqlite3_context,
|
||||||
let pac = ffi::sqlite3_aggregate_context(ctx, ::std::mem::size_of::<*mut A>() as c_int)
|
bytes: usize)
|
||||||
as *mut *mut A;
|
-> Option<*mut *mut A> {
|
||||||
|
let pac = ffi::sqlite3_aggregate_context(ctx, bytes as c_int) as *mut *mut A;
|
||||||
if pac.is_null() {
|
if pac.is_null() {
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
@ -525,7 +526,7 @@ impl InnerConnection {
|
|||||||
assert!(!boxed_aggr.is_null(),
|
assert!(!boxed_aggr.is_null(),
|
||||||
"Internal error - null aggregate pointer");
|
"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,
|
Some(pac) => pac,
|
||||||
None => {
|
None => {
|
||||||
ffi::sqlite3_result_error_nomem(ctx);
|
ffi::sqlite3_result_error_nomem(ctx);
|
||||||
@ -556,19 +557,18 @@ impl InnerConnection {
|
|||||||
assert!(!boxed_aggr.is_null(),
|
assert!(!boxed_aggr.is_null(),
|
||||||
"Internal error - null aggregate pointer");
|
"Internal error - null aggregate pointer");
|
||||||
|
|
||||||
let pac = match aggregate_context(ctx) {
|
// Within the xFinal callback, it is customary to set N=0 in calls to
|
||||||
Some(pac) => pac,
|
// sqlite3_aggregate_context(C,N) so that no pointless memory allocations occur.
|
||||||
None => {
|
let a: Option<A> = match aggregate_context(ctx, 0) {
|
||||||
ffi::sqlite3_result_error_nomem(ctx);
|
Some(pac) => {
|
||||||
return;
|
if (*pac).is_null() {
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
let a: Option<A> = if (*pac).is_null() {
|
|
||||||
None
|
None
|
||||||
} else {
|
} else {
|
||||||
let a = Box::from_raw(*pac);
|
let a = Box::from_raw(*pac);
|
||||||
Some(*a)
|
Some(*a)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None => None,
|
||||||
};
|
};
|
||||||
|
|
||||||
match (*boxed_aggr).finalize(a) {
|
match (*boxed_aggr).finalize(a) {
|
||||||
|
23
src/lib.rs
23
src/lib.rs
@ -495,6 +495,18 @@ impl Connection {
|
|||||||
self.db.borrow_mut().load_extension(dylib_path.as_ref(), entry_point)
|
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<()> {
|
fn decode_result(&self, code: c_int) -> Result<()> {
|
||||||
self.db.borrow_mut().decode_result(code)
|
self.db.borrow_mut().decode_result(code)
|
||||||
}
|
}
|
||||||
@ -999,6 +1011,9 @@ pub type SqliteRows<'stmt> = Rows<'stmt>;
|
|||||||
///
|
///
|
||||||
/// ## Warning
|
/// ## 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
|
/// 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`
|
/// from a row after it has become stale (i.e., `next()` has been called again on the `Rows`
|
||||||
/// iterator). For example:
|
/// iterator). For example:
|
||||||
@ -1010,7 +1025,7 @@ pub type SqliteRows<'stmt> = Rows<'stmt>;
|
|||||||
/// let mut rows = try!(stmt.query(&[]));
|
/// let mut rows = try!(stmt.query(&[]));
|
||||||
///
|
///
|
||||||
/// let row0 = try!(rows.next().unwrap());
|
/// let row0 = try!(rows.next().unwrap());
|
||||||
/// // row 0 is value now...
|
/// // row 0 is valid for now...
|
||||||
///
|
///
|
||||||
/// let row1 = try!(rows.next().unwrap());
|
/// let row1 = try!(rows.next().unwrap());
|
||||||
/// // row 0 is now STALE, and row 1 is valid
|
/// // 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
|
/// (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,
|
/// `min`/`max` (which could return a stale row unless the last row happened to be the min or max,
|
||||||
/// respectively).
|
/// 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> {
|
pub struct Rows<'stmt> {
|
||||||
stmt: &'stmt Statement<'stmt>,
|
stmt: &'stmt Statement<'stmt>,
|
||||||
current_row: Rc<Cell<c_int>>,
|
current_row: Rc<Cell<c_int>>,
|
||||||
|
Loading…
x
Reference in New Issue
Block a user