mirror of
https://github.com/isar/rusqlite.git
synced 2024-11-23 00:39:20 +08:00
Implement From/ToSql for more types
This implements `FromSql` for `u64`, `usize` and `f32`, and `ToSql` for `f32`. I also updated the documentation to describe how it currently works, and changed the implementation to use `try_from` for integral casts rather rather than custom code. Test added.
This commit is contained in:
parent
2461540306
commit
ebcec59969
@ -1,6 +1,7 @@
|
||||
use super::{Value, ValueRef};
|
||||
use std::error::Error;
|
||||
use std::fmt;
|
||||
use std::convert::TryInto;
|
||||
|
||||
/// Enum listing possible errors from `FromSql` trait.
|
||||
#[derive(Debug)]
|
||||
@ -90,29 +91,12 @@ pub trait FromSql: Sized {
|
||||
fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self>;
|
||||
}
|
||||
|
||||
impl FromSql for isize {
|
||||
fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self> {
|
||||
i64::column_result(value).and_then(|i| {
|
||||
if i < isize::min_value() as i64 || i > isize::max_value() as i64 {
|
||||
Err(FromSqlError::OutOfRange(i))
|
||||
} else {
|
||||
Ok(i as isize)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
macro_rules! from_sql_integral(
|
||||
($t:ident) => (
|
||||
impl FromSql for $t {
|
||||
fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self> {
|
||||
i64::column_result(value).and_then(|i| {
|
||||
if i < i64::from($t::min_value()) || i > i64::from($t::max_value()) {
|
||||
Err(FromSqlError::OutOfRange(i))
|
||||
} else {
|
||||
Ok(i as $t)
|
||||
}
|
||||
})
|
||||
let i = i64::column_result(value)?;
|
||||
i.try_into().map_err(|_| FromSqlError::OutOfRange(i))
|
||||
}
|
||||
}
|
||||
)
|
||||
@ -121,9 +105,13 @@ macro_rules! from_sql_integral(
|
||||
from_sql_integral!(i8);
|
||||
from_sql_integral!(i16);
|
||||
from_sql_integral!(i32);
|
||||
// from_sql_integral!(i64); // Not needed because the native type is i64.
|
||||
from_sql_integral!(isize);
|
||||
from_sql_integral!(u8);
|
||||
from_sql_integral!(u16);
|
||||
from_sql_integral!(u32);
|
||||
from_sql_integral!(u64);
|
||||
from_sql_integral!(usize);
|
||||
|
||||
impl FromSql for i64 {
|
||||
fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self> {
|
||||
@ -131,6 +119,16 @@ impl FromSql for i64 {
|
||||
}
|
||||
}
|
||||
|
||||
impl FromSql for f32 {
|
||||
fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self> {
|
||||
match value {
|
||||
ValueRef::Integer(i) => Ok(i as f32),
|
||||
ValueRef::Real(f) => Ok(f as f32),
|
||||
_ => Err(FromSqlError::InvalidType),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl FromSql for f64 {
|
||||
fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self> {
|
||||
match value {
|
||||
|
@ -4,11 +4,27 @@
|
||||
//! the `ToSql` and `FromSql` traits are provided for the basic types that
|
||||
//! SQLite provides methods for:
|
||||
//!
|
||||
//! * Integers (`i32` and `i64`; SQLite uses `i64` internally, so getting an
|
||||
//! `i32` will truncate if the value is too large or too small).
|
||||
//! * Reals (`f64`)
|
||||
//! * Strings (`String` and `&str`)
|
||||
//! * Blobs (`Vec<u8>` and `&[u8]`)
|
||||
//! * Numbers
|
||||
//!
|
||||
//! The number situation is a little complicated due to the fact that all
|
||||
//! numbers in SQLite are stored as `INTEGER` (`i64`) or `REAL` (`f64`).
|
||||
//!
|
||||
//! `ToSql` cannot fail and is therefore implemented for all number types that
|
||||
//! can be losslessly converted to one of these types, i.e. `u8`, `u16`, `u32`,
|
||||
//! `i8`, `i16`, `i32`, `i64`, `isize`, `f32` and `f64`. It is *not* implemented
|
||||
//! for `u64` or `usize`.
|
||||
//!
|
||||
//! `FromSql` can fail, and is implemented for all primitive number types,
|
||||
//! however you may get a runtime error or rounding depending on the types
|
||||
//! and values.
|
||||
//!
|
||||
//! * `INTEGER` to integer: returns an `Error::IntegralValueOutOfRange` error
|
||||
//! if the value does not fit.
|
||||
//! * `REAL` to integer: always returns an `Error::InvalidColumnType` error.
|
||||
//! * `INTEGER` to float: casts using `as` operator. Never fails.
|
||||
//! * `REAL` to float: casts using `as` operator. Never fails.
|
||||
//!
|
||||
//! Additionally, if the `time` feature is enabled, implementations are
|
||||
//! provided for `time::OffsetDateTime` that use the RFC 3339 date/time format,
|
||||
@ -116,7 +132,7 @@ impl fmt::Display for Type {
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::Value;
|
||||
use crate::{Connection, Error, NO_PARAMS};
|
||||
use crate::{Connection, Error, NO_PARAMS, params, Statement};
|
||||
use std::f64::EPSILON;
|
||||
use std::os::raw::{c_double, c_int};
|
||||
|
||||
@ -369,4 +385,76 @@ mod test {
|
||||
}
|
||||
assert_eq!(Value::Null, row.get::<_, Value>(4).unwrap());
|
||||
}
|
||||
|
||||
macro_rules! test_conversion {
|
||||
($db_etc:ident, $insert_value:expr, $get_type:ty, expect $expected_value:expr) => {
|
||||
$db_etc.insert_statement.execute(params![$insert_value]).unwrap();
|
||||
let res = $db_etc.query_statement.query_row(NO_PARAMS, |row| {
|
||||
row.get::<_, $get_type>(0)
|
||||
});
|
||||
assert_eq!(res.unwrap(), $expected_value);
|
||||
$db_etc.delete_statement.execute(NO_PARAMS).unwrap();
|
||||
};
|
||||
($db_etc:ident, $insert_value:expr, $get_type:ty, expect_error) => {
|
||||
$db_etc.insert_statement.execute(params![$insert_value]).unwrap();
|
||||
let res = $db_etc.query_statement.query_row(NO_PARAMS, |row| {
|
||||
row.get::<_, $get_type>(0)
|
||||
});
|
||||
res.unwrap_err();
|
||||
$db_etc.delete_statement.execute(NO_PARAMS).unwrap();
|
||||
};
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_numeric_conversions() {
|
||||
// Test what happens when we store an f32 and retrieve an i32 etc.
|
||||
let db = Connection::open_in_memory().unwrap();
|
||||
db.execute_batch("CREATE TABLE foo (x)").unwrap();
|
||||
|
||||
// SQLite actually ignores the column types, so we just need to test
|
||||
// different numeric values.
|
||||
|
||||
struct DbEtc<'conn> {
|
||||
insert_statement: Statement<'conn>,
|
||||
query_statement: Statement<'conn>,
|
||||
delete_statement: Statement<'conn>,
|
||||
}
|
||||
|
||||
let mut db_etc = DbEtc {
|
||||
insert_statement: db.prepare("INSERT INTO foo VALUES (?1)").unwrap(),
|
||||
query_statement: db.prepare("SELECT x FROM foo").unwrap(),
|
||||
delete_statement: db.prepare("DELETE FROM foo").unwrap(),
|
||||
};
|
||||
|
||||
// Basic non-converting test.
|
||||
test_conversion!(db_etc, 0u8, u8, expect 0u8);
|
||||
|
||||
// In-range integral conversions.
|
||||
test_conversion!(db_etc, 100u8, i8, expect 100i8);
|
||||
test_conversion!(db_etc, 200u8, u8, expect 200u8);
|
||||
test_conversion!(db_etc, 100u16, i8, expect 100i8);
|
||||
test_conversion!(db_etc, 200u16, u8, expect 200u8);
|
||||
test_conversion!(db_etc, u32::MAX, u64, expect u32::MAX as u64);
|
||||
test_conversion!(db_etc, i64::MIN, i64, expect i64::MIN);
|
||||
test_conversion!(db_etc, i64::MAX, i64, expect i64::MAX);
|
||||
test_conversion!(db_etc, i64::MAX, u64, expect i64::MAX as u64);
|
||||
|
||||
// Out-of-range integral conversions.
|
||||
test_conversion!(db_etc, 200u8, i8, expect_error);
|
||||
test_conversion!(db_etc, 400u16, i8, expect_error);
|
||||
test_conversion!(db_etc, 400u16, u8, expect_error);
|
||||
test_conversion!(db_etc, -1i8, u8, expect_error);
|
||||
test_conversion!(db_etc, i64::MIN, u64, expect_error);
|
||||
|
||||
// Integer to float, always works.
|
||||
test_conversion!(db_etc, i64::MIN, f32, expect i64::MIN as f32);
|
||||
test_conversion!(db_etc, i64::MAX, f32, expect i64::MAX as f32);
|
||||
test_conversion!(db_etc, i64::MIN, f64, expect i64::MIN as f64);
|
||||
test_conversion!(db_etc, i64::MAX, f64, expect i64::MAX as f64);
|
||||
|
||||
// Float to int conversion, never works even if the actual value is an
|
||||
// integer.
|
||||
test_conversion!(db_etc, 0f64, i64, expect_error);
|
||||
|
||||
}
|
||||
}
|
||||
|
@ -59,6 +59,7 @@ from_value!(isize);
|
||||
from_value!(u8);
|
||||
from_value!(u16);
|
||||
from_value!(u32);
|
||||
from_value!(f32);
|
||||
from_value!(f64);
|
||||
from_value!(Vec<u8>);
|
||||
|
||||
@ -147,6 +148,7 @@ to_sql_self!(isize);
|
||||
to_sql_self!(u8);
|
||||
to_sql_self!(u16);
|
||||
to_sql_self!(u32);
|
||||
to_sql_self!(f32);
|
||||
to_sql_self!(f64);
|
||||
|
||||
#[cfg(feature = "i128_blob")]
|
||||
|
@ -78,6 +78,12 @@ impl From<i64> for Value {
|
||||
}
|
||||
}
|
||||
|
||||
impl From<f32> for Value {
|
||||
fn from(f: f32) -> Value {
|
||||
Value::Real(f.into())
|
||||
}
|
||||
}
|
||||
|
||||
impl From<f64> for Value {
|
||||
fn from(f: f64) -> Value {
|
||||
Value::Real(f)
|
||||
|
Loading…
Reference in New Issue
Block a user