mirror of
https://github.com/isar/rusqlite.git
synced 2024-11-23 00:39:20 +08:00
Add a feature for storing i128 as blobs.
This is behind the `i128_blob` feature. Blobs are stored as 16 byte big-endian values, with their most significant bit flipped. This is so that sorting, comparison, etc all work properly, even with negative numbers. This also allows the representation to be stable across different computers. It's possible that the `FromSql` implementation should handle the case that the real value is stored in an integer. I didn't do this, but would be willing to make the change. I don't think we should store them this way though, since I don't think users would be able to sort/compare them sanely. Support for `u128` is not implemented, as comparison with i128 values would work strangely. This also is consistent with `u64` not being allowed, not that I think that would be reason enough on it's own. The `byteorder` crate is used if this feature is flipped, as it's quite small and implements things more or less optimally. If/when `i128::{to,from}_be_bytes` gets stabilized (https://github.com/rust-lang/rust/issues/52963), we should probably use that instead.
This commit is contained in:
parent
7176be2d6d
commit
572471c40f
@ -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"
|
||||
|
@ -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"
|
||||
|
@ -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
|
||||
|
||||
|
@ -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;
|
||||
|
@ -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())
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -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 {
|
||||
|
@ -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()),
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
@ -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 {
|
||||
|
Loading…
Reference in New Issue
Block a user