Merge pull request #411 from thomcc/i128_blob

Add a feature for storing i128 as blobs.
This commit is contained in:
gwenn 2018-10-19 21:49:09 +02:00 committed by GitHub
commit 0d08bf5e05
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 98 additions and 0 deletions

View File

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

View File

@ -32,6 +32,7 @@ bundled = ["libsqlite3-sys/bundled"]
buildtime_bindgen = ["libsqlite3-sys/buildtime_bindgen"]
limits = []
hooks = []
i128_blob = ["byteorder"]
sqlcipher = ["libsqlite3-sys/sqlcipher"]
unlock_notify = ["libsqlite3-sys/unlock_notify"]
# xSavepoint, xRelease and xRollbackTo: 3.7.7 (2011-06-23)
@ -48,6 +49,7 @@ chrono = { version = "0.4", optional = true }
serde_json = { version = "1.0", optional = true }
csv = { version = "1.0", optional = true }
lazy_static = { version = "1.0", optional = true }
byteorder = { version = "1.2", features = ["i128"], optional = true }
[dev-dependencies]
tempdir = "0.3"

View File

@ -98,6 +98,7 @@ features](https://doc.rust-lang.org/cargo/reference/manifest.html#the-features-s
* `vtab` for [virtual table](https://sqlite.org/vtab.html) support (allows you to write virtual table implemntations in Rust). Currently, only read-only virtual tables are supported.
* [`csvtab`](https://sqlite.org/csv.html), CSV virtual table written in Rust.
* [`array`](https://sqlite.org/carray.html), The `rarray()` Table-Valued Function.
* `i128_blob` allows storing values of type `i128` type in SQLite databases. Internally, the data is stored as a 16 byte big-endian blob, with the most significant bit flipped, which allows ordering and comparison between different blobs storing i128s to work as expected.
## Notes on building rusqlite and libsqlite3-sys

View File

@ -67,6 +67,9 @@ extern crate bitflags;
#[macro_use]
extern crate lazy_static;
#[cfg(feature = "i128_blob")]
extern crate byteorder;
use std::cell::RefCell;
use std::convert;
use std::default::Default;

View File

@ -161,6 +161,10 @@ impl<'a, 'stmt> Row<'a, 'stmt> {
///
/// Returns an `Error::InvalidColumnName` if `idx` is not a valid column
/// name for this row.
///
/// If the result type is i128 (which requires the `i128_blob` feature to be
/// enabled), and the underlying SQLite column is a blob whose size is not
/// 16 bytes, `Error::InvalidColumnType` will also be returned.
pub fn get_checked<I: RowIndex, T: FromSql>(&self, idx: I) -> Result<T> {
let idx = try!(idx.idx(self.stmt));
let value = self.stmt.value_ref(idx);
@ -170,6 +174,10 @@ impl<'a, 'stmt> Row<'a, 'stmt> {
FromSqlError::Other(err) => {
Error::FromSqlConversionFailure(idx as usize, value.data_type(), err)
}
#[cfg(feature = "i128_blob")]
FromSqlError::InvalidI128Size(_) => {
Error::InvalidColumnType(idx, value.data_type())
}
})
}

View File

@ -13,6 +13,11 @@ pub enum FromSqlError {
/// requested type.
OutOfRange(i64),
/// Error returned when reading an `i128` from a blob with a size
/// other than 16. Only available when the `i128_blob` feature is enabled.
#[cfg(feature = "i128_blob")]
InvalidI128Size(usize),
/// An error case available for implementors of the `FromSql` trait.
Other(Box<Error + Send + Sync>),
}
@ -22,6 +27,9 @@ impl fmt::Display for FromSqlError {
match *self {
FromSqlError::InvalidType => write!(f, "Invalid type"),
FromSqlError::OutOfRange(i) => write!(f, "Value {} out of range", i),
#[cfg(feature = "i128_blob")]
FromSqlError::InvalidI128Size(s) =>
write!(f, "Cannot read 128bit value out of {} byte blob", s),
FromSqlError::Other(ref err) => err.fmt(f),
}
}
@ -32,6 +40,9 @@ impl Error for FromSqlError {
match *self {
FromSqlError::InvalidType => "invalid type",
FromSqlError::OutOfRange(_) => "value out of range",
#[cfg(feature = "i128_blob")]
FromSqlError::InvalidI128Size(_) =>
"unexpected blob size for 128bit value",
FromSqlError::Other(ref err) => err.description(),
}
}
@ -41,6 +52,8 @@ impl Error for FromSqlError {
match *self {
FromSqlError::Other(ref err) => err.cause(),
FromSqlError::InvalidType | FromSqlError::OutOfRange(_) => None,
#[cfg(feature = "i128_blob")]
FromSqlError::InvalidI128Size(_) => None,
}
}
}
@ -135,6 +148,21 @@ impl FromSql for Vec<u8> {
}
}
#[cfg(feature = "i128_blob")]
impl FromSql for i128 {
fn column_result(value: ValueRef) -> FromSqlResult<Self> {
use byteorder::{BigEndian, ByteOrder};
value.as_blob().and_then(|bytes| {
if bytes.len() == 16 {
Ok(BigEndian::read_i128(bytes) ^ (1i128 << 127))
} else {
Err(FromSqlError::InvalidI128Size(bytes.len()))
}
})
}
}
impl<T: FromSql> FromSql for Option<T> {
fn column_result(value: ValueRef) -> FromSqlResult<Self> {
match value {

View File

@ -59,6 +59,12 @@ from_value!(u32);
from_value!(f64);
from_value!(Vec<u8>);
// It would be nice if we could avoid the heap allocation (of the `Vec`) that
// `i128` needs in `Into<Value>`, but it's probably fine for the moment, and not
// worth adding another case to Value.
#[cfg(feature = "i128_blob")]
from_value!(i128);
impl<'a> ToSql for ToSqlOutput<'a> {
fn to_sql(&self) -> Result<ToSqlOutput> {
Ok(match *self {
@ -112,6 +118,9 @@ to_sql_self!(u16);
to_sql_self!(u32);
to_sql_self!(f64);
#[cfg(feature = "i128_blob")]
to_sql_self!(i128);
impl<'a, T: ?Sized> ToSql for &'a T
where
T: ToSql,
@ -194,4 +203,38 @@ mod test {
let r = cow.to_sql();
assert!(r.is_ok());
}
#[cfg(feature = "i128_blob")]
#[test]
fn test_i128() {
use {Connection, NO_PARAMS};
use std::i128;
let db = Connection::open_in_memory().unwrap();
db.execute_batch("CREATE TABLE foo (i128 BLOB, desc TEXT)").unwrap();
db.execute("
INSERT INTO foo(i128, desc) VALUES
(?, 'zero'),
(?, 'neg one'), (?, 'neg two'),
(?, 'pos one'), (?, 'pos two'),
(?, 'min'), (?, 'max')",
&[0i128, -1i128, -2i128, 1i128, 2i128, i128::MIN, i128::MAX]
).unwrap();
let mut stmt = db.prepare("SELECT i128, desc FROM foo ORDER BY i128 ASC").unwrap();
let res = stmt.query_map(
NO_PARAMS,
|row| (row.get::<_, i128>(0), row.get::<_, String>(1))
).unwrap().collect::<Result<Vec<_>, _>>().unwrap();
assert_eq!(res, &[
(i128::MIN, "min".to_owned()),
(-2, "neg two".to_owned()),
(-1, "neg one".to_owned()),
(0, "zero".to_owned()),
(1, "pos one".to_owned()),
(2, "pos two".to_owned()),
(i128::MAX, "max".to_owned()),
]);
}
}

View File

@ -36,6 +36,18 @@ impl From<isize> for Value {
}
}
#[cfg(feature = "i128_blob")]
impl From<i128> for Value {
fn from(i: i128) -> Value {
use byteorder::{BigEndian, ByteOrder};
let mut buf = vec![0u8; 16];
// We store these biased (e.g. with the most significant bit flipped)
// so that comparisons with negative numbers work properly.
BigEndian::write_i128(&mut buf, i ^ (1i128 << 127));
Value::Blob(buf)
}
}
macro_rules! from_i64(
($t:ty) => (
impl From<$t> for Value {