mirror of
https://github.com/isar/rusqlite.git
synced 2024-11-26 11:31:37 +08:00
Merge pull request #411 from thomcc/i128_blob
Add a feature for storing i128 as blobs.
This commit is contained in:
commit
0d08bf5e05
@ -37,6 +37,7 @@ script:
|
|||||||
- cargo test --features serde_json
|
- cargo test --features serde_json
|
||||||
- cargo test --features bundled
|
- cargo test --features bundled
|
||||||
- cargo test --features sqlcipher
|
- cargo test --features sqlcipher
|
||||||
|
- cargo test --features i128_blob
|
||||||
- cargo test --features "unlock_notify bundled"
|
- cargo test --features "unlock_notify bundled"
|
||||||
- cargo test --features "array bundled csvtab vtab"
|
- cargo test --features "array bundled csvtab vtab"
|
||||||
- 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"
|
||||||
|
@ -32,6 +32,7 @@ bundled = ["libsqlite3-sys/bundled"]
|
|||||||
buildtime_bindgen = ["libsqlite3-sys/buildtime_bindgen"]
|
buildtime_bindgen = ["libsqlite3-sys/buildtime_bindgen"]
|
||||||
limits = []
|
limits = []
|
||||||
hooks = []
|
hooks = []
|
||||||
|
i128_blob = ["byteorder"]
|
||||||
sqlcipher = ["libsqlite3-sys/sqlcipher"]
|
sqlcipher = ["libsqlite3-sys/sqlcipher"]
|
||||||
unlock_notify = ["libsqlite3-sys/unlock_notify"]
|
unlock_notify = ["libsqlite3-sys/unlock_notify"]
|
||||||
# xSavepoint, xRelease and xRollbackTo: 3.7.7 (2011-06-23)
|
# 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 }
|
serde_json = { version = "1.0", optional = true }
|
||||||
csv = { version = "1.0", optional = true }
|
csv = { version = "1.0", optional = true }
|
||||||
lazy_static = { version = "1.0", optional = true }
|
lazy_static = { version = "1.0", optional = true }
|
||||||
|
byteorder = { version = "1.2", features = ["i128"], optional = true }
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
tempdir = "0.3"
|
tempdir = "0.3"
|
||||||
|
@ -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.
|
* `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.
|
* [`csvtab`](https://sqlite.org/csv.html), CSV virtual table written in Rust.
|
||||||
* [`array`](https://sqlite.org/carray.html), The `rarray()` Table-Valued Function.
|
* [`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
|
## Notes on building rusqlite and libsqlite3-sys
|
||||||
|
|
||||||
|
@ -67,6 +67,9 @@ extern crate bitflags;
|
|||||||
#[macro_use]
|
#[macro_use]
|
||||||
extern crate lazy_static;
|
extern crate lazy_static;
|
||||||
|
|
||||||
|
#[cfg(feature = "i128_blob")]
|
||||||
|
extern crate byteorder;
|
||||||
|
|
||||||
use std::cell::RefCell;
|
use std::cell::RefCell;
|
||||||
use std::convert;
|
use std::convert;
|
||||||
use std::default::Default;
|
use std::default::Default;
|
||||||
|
@ -161,6 +161,10 @@ impl<'a, 'stmt> Row<'a, 'stmt> {
|
|||||||
///
|
///
|
||||||
/// Returns an `Error::InvalidColumnName` if `idx` is not a valid column
|
/// Returns an `Error::InvalidColumnName` if `idx` is not a valid column
|
||||||
/// name for this row.
|
/// 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> {
|
pub fn get_checked<I: RowIndex, T: FromSql>(&self, idx: I) -> Result<T> {
|
||||||
let idx = try!(idx.idx(self.stmt));
|
let idx = try!(idx.idx(self.stmt));
|
||||||
let value = self.stmt.value_ref(idx);
|
let value = self.stmt.value_ref(idx);
|
||||||
@ -170,6 +174,10 @@ impl<'a, 'stmt> Row<'a, 'stmt> {
|
|||||||
FromSqlError::Other(err) => {
|
FromSqlError::Other(err) => {
|
||||||
Error::FromSqlConversionFailure(idx as usize, value.data_type(), err)
|
Error::FromSqlConversionFailure(idx as usize, value.data_type(), err)
|
||||||
}
|
}
|
||||||
|
#[cfg(feature = "i128_blob")]
|
||||||
|
FromSqlError::InvalidI128Size(_) => {
|
||||||
|
Error::InvalidColumnType(idx, value.data_type())
|
||||||
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -13,6 +13,11 @@ pub enum FromSqlError {
|
|||||||
/// requested type.
|
/// requested type.
|
||||||
OutOfRange(i64),
|
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.
|
/// An error case available for implementors of the `FromSql` trait.
|
||||||
Other(Box<Error + Send + Sync>),
|
Other(Box<Error + Send + Sync>),
|
||||||
}
|
}
|
||||||
@ -22,6 +27,9 @@ impl fmt::Display for FromSqlError {
|
|||||||
match *self {
|
match *self {
|
||||||
FromSqlError::InvalidType => write!(f, "Invalid type"),
|
FromSqlError::InvalidType => write!(f, "Invalid type"),
|
||||||
FromSqlError::OutOfRange(i) => write!(f, "Value {} out of range", i),
|
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),
|
FromSqlError::Other(ref err) => err.fmt(f),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -32,6 +40,9 @@ impl Error for FromSqlError {
|
|||||||
match *self {
|
match *self {
|
||||||
FromSqlError::InvalidType => "invalid type",
|
FromSqlError::InvalidType => "invalid type",
|
||||||
FromSqlError::OutOfRange(_) => "value out of range",
|
FromSqlError::OutOfRange(_) => "value out of range",
|
||||||
|
#[cfg(feature = "i128_blob")]
|
||||||
|
FromSqlError::InvalidI128Size(_) =>
|
||||||
|
"unexpected blob size for 128bit value",
|
||||||
FromSqlError::Other(ref err) => err.description(),
|
FromSqlError::Other(ref err) => err.description(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -41,6 +52,8 @@ impl Error for FromSqlError {
|
|||||||
match *self {
|
match *self {
|
||||||
FromSqlError::Other(ref err) => err.cause(),
|
FromSqlError::Other(ref err) => err.cause(),
|
||||||
FromSqlError::InvalidType | FromSqlError::OutOfRange(_) => None,
|
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> {
|
impl<T: FromSql> FromSql for Option<T> {
|
||||||
fn column_result(value: ValueRef) -> FromSqlResult<Self> {
|
fn column_result(value: ValueRef) -> FromSqlResult<Self> {
|
||||||
match value {
|
match value {
|
||||||
|
@ -59,6 +59,12 @@ from_value!(u32);
|
|||||||
from_value!(f64);
|
from_value!(f64);
|
||||||
from_value!(Vec<u8>);
|
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> {
|
impl<'a> ToSql for ToSqlOutput<'a> {
|
||||||
fn to_sql(&self) -> Result<ToSqlOutput> {
|
fn to_sql(&self) -> Result<ToSqlOutput> {
|
||||||
Ok(match *self {
|
Ok(match *self {
|
||||||
@ -112,6 +118,9 @@ to_sql_self!(u16);
|
|||||||
to_sql_self!(u32);
|
to_sql_self!(u32);
|
||||||
to_sql_self!(f64);
|
to_sql_self!(f64);
|
||||||
|
|
||||||
|
#[cfg(feature = "i128_blob")]
|
||||||
|
to_sql_self!(i128);
|
||||||
|
|
||||||
impl<'a, T: ?Sized> ToSql for &'a T
|
impl<'a, T: ?Sized> ToSql for &'a T
|
||||||
where
|
where
|
||||||
T: ToSql,
|
T: ToSql,
|
||||||
@ -194,4 +203,38 @@ mod test {
|
|||||||
let r = cow.to_sql();
|
let r = cow.to_sql();
|
||||||
assert!(r.is_ok());
|
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()),
|
||||||
|
]);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -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(
|
macro_rules! from_i64(
|
||||||
($t:ty) => (
|
($t:ty) => (
|
||||||
impl From<$t> for Value {
|
impl From<$t> for Value {
|
||||||
|
Loading…
Reference in New Issue
Block a user