aboutsummaryrefslogtreecommitdiff
path: root/src/types
diff options
context:
space:
mode:
Diffstat (limited to 'src/types')
-rw-r--r--src/types/from_sql.rs6
-rw-r--r--src/types/mod.rs73
-rw-r--r--src/types/time.rs59
-rw-r--r--src/types/to_sql.rs1
-rw-r--r--src/types/value.rs1
-rw-r--r--src/types/value_ref.rs1
6 files changed, 77 insertions, 64 deletions
diff --git a/src/types/from_sql.rs b/src/types/from_sql.rs
index e8eadc5..3fe74b4 100644
--- a/src/types/from_sql.rs
+++ b/src/types/from_sql.rs
@@ -86,6 +86,7 @@ pub type FromSqlResult<T> = Result<T, FromSqlError>;
/// fetching values as i64 and then doing the interpretation themselves or by
/// defining a newtype and implementing `FromSql`/`ToSql` for it.
pub trait FromSql: Sized {
+ /// Converts SQLite value into Rust value.
fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self>;
}
@@ -142,10 +143,7 @@ impl FromSql for f64 {
impl FromSql for bool {
fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self> {
- i64::column_result(value).map(|i| match i {
- 0 => false,
- _ => true,
- })
+ i64::column_result(value).map(|i| !matches!(i, 0))
}
}
diff --git a/src/types/mod.rs b/src/types/mod.rs
index d79ff82..2d163cf 100644
--- a/src/types/mod.rs
+++ b/src/types/mod.rs
@@ -10,40 +10,39 @@
//! * Strings (`String` and `&str`)
//! * Blobs (`Vec<u8>` and `&[u8]`)
//!
-//! Additionally, because it is such a common data type, implementations are
-//! provided for `time::Timespec` that use the RFC 3339 date/time format,
+//! Additionally, if the `time` feature is enabled, implementations are
+//! provided for `time::OffsetDateTime` that use the RFC 3339 date/time format,
//! `"%Y-%m-%dT%H:%M:%S.%fZ"`, to store time values as strings. These values
//! can be parsed by SQLite's builtin
//! [datetime](https://www.sqlite.org/lang_datefunc.html) functions. If you
-//! want different storage for timespecs, you can use a newtype. For example, to
-//! store timespecs as `f64`s:
-//!
-//! ```rust
-//! use rusqlite::types::{FromSql, FromSqlResult, ToSql, ToSqlOutput, ValueRef};
-//! use rusqlite::Result;
-//!
-//! pub struct TimespecSql(pub time::Timespec);
-//!
-//! impl FromSql for TimespecSql {
-//! fn column_result(value: ValueRef) -> FromSqlResult<Self> {
-//! f64::column_result(value).map(|as_f64| {
-//! TimespecSql(time::Timespec {
-//! sec: as_f64.trunc() as i64,
-//! nsec: (as_f64.fract() * 1.0e9) as i32,
-//! })
-//! })
-//! }
-//! }
-//!
-//! impl ToSql for TimespecSql {
-//! fn to_sql(&self) -> Result<ToSqlOutput> {
-//! let TimespecSql(ts) = *self;
-//! let as_f64 = ts.sec as f64 + (ts.nsec as f64) / 1.0e9;
-//! Ok(as_f64.into())
-//! }
-//! }
-//! ```
+//! want different storage for datetimes, you can use a newtype.
//!
+#![cfg_attr(feature = "time", doc = r##"
+For example, to store datetimes as `i64`s counting the number of seconds since
+the Unix epoch:
+
+```
+use rusqlite::types::{FromSql, FromSqlResult, ToSql, ToSqlOutput, ValueRef};
+use rusqlite::Result;
+
+pub struct DateTimeSql(pub time::OffsetDateTime);
+
+impl FromSql for DateTimeSql {
+ fn column_result(value: ValueRef) -> FromSqlResult<Self> {
+ i64::column_result(value).map(|as_i64| {
+ DateTimeSql(time::OffsetDateTime::from_unix_timestamp(as_i64))
+ })
+ }
+}
+
+impl ToSql for DateTimeSql {
+ fn to_sql(&self) -> Result<ToSqlOutput> {
+ Ok(self.0.timestamp().into())
+ }
+}
+```
+
+"##)]
//! `ToSql` and `FromSql` are also implemented for `Option<T>` where `T`
//! implements `ToSql` or `FromSql` for the cases where you want to know if a
//! value was NULL (which gets translated to `None`).
@@ -60,6 +59,7 @@ mod chrono;
mod from_sql;
#[cfg(feature = "serde_json")]
mod serde_json;
+#[cfg(feature = "time")]
mod time;
mod to_sql;
#[cfg(feature = "url")]
@@ -82,12 +82,19 @@ mod value_ref;
#[derive(Copy, Clone)]
pub struct Null;
+/// SQLite data types.
+/// See [Fundamental Datatypes](https://sqlite.org/c3ref/c_blob.html).
#[derive(Clone, Debug, PartialEq)]
pub enum Type {
+ /// NULL
Null,
+ /// 64-bit signed integer
Integer,
+ /// 64-bit IEEE floating point number
Real,
+ /// String
Text,
+ /// BLOB
Blob,
}
@@ -266,8 +273,9 @@ mod test {
assert!(is_invalid_column_type(
row.get::<_, String>(0).err().unwrap()
));
+ #[cfg(feature = "time")]
assert!(is_invalid_column_type(
- row.get::<_, time::Timespec>(0).err().unwrap()
+ row.get::<_, time::OffsetDateTime>(0).err().unwrap()
));
assert!(is_invalid_column_type(
row.get::<_, Option<c_int>>(0).err().unwrap()
@@ -328,8 +336,9 @@ mod test {
assert!(is_invalid_column_type(
row.get::<_, Vec<u8>>(4).err().unwrap()
));
+ #[cfg(feature = "time")]
assert!(is_invalid_column_type(
- row.get::<_, time::Timespec>(4).err().unwrap()
+ row.get::<_, time::OffsetDateTime>(4).err().unwrap()
));
}
diff --git a/src/types/time.rs b/src/types/time.rs
index 097b22a..8589167 100644
--- a/src/types/time.rs
+++ b/src/types/time.rs
@@ -1,40 +1,40 @@
+//! `ToSql` and `FromSql` implementation for [`time::OffsetDateTime`].
use crate::types::{FromSql, FromSqlError, FromSqlResult, ToSql, ToSqlOutput, ValueRef};
use crate::Result;
+use time::{OffsetDateTime, PrimitiveDateTime, UtcOffset};
const CURRENT_TIMESTAMP_FMT: &str = "%Y-%m-%d %H:%M:%S";
-const SQLITE_DATETIME_FMT: &str = "%Y-%m-%dT%H:%M:%S.%fZ";
-const SQLITE_DATETIME_FMT_LEGACY: &str = "%Y-%m-%d %H:%M:%S:%f %Z";
+const SQLITE_DATETIME_FMT: &str = "%Y-%m-%dT%H:%M:%S.%NZ";
+const SQLITE_DATETIME_FMT_LEGACY: &str = "%Y-%m-%d %H:%M:%S:%N %z";
-impl ToSql for time::Timespec {
+impl ToSql for OffsetDateTime {
fn to_sql(&self) -> Result<ToSqlOutput<'_>> {
- let time_string = time::at_utc(*self)
- .strftime(SQLITE_DATETIME_FMT)
- .unwrap()
- .to_string();
+ let time_string = self.to_offset(UtcOffset::UTC).format(SQLITE_DATETIME_FMT);
Ok(ToSqlOutput::from(time_string))
}
}
-impl FromSql for time::Timespec {
+impl FromSql for OffsetDateTime {
fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self> {
- value
- .as_str()
- .and_then(|s| {
- match s.len() {
- 19 => time::strptime(s, CURRENT_TIMESTAMP_FMT),
- _ => time::strptime(s, SQLITE_DATETIME_FMT).or_else(|err| {
- time::strptime(s, SQLITE_DATETIME_FMT_LEGACY).or_else(|_| Err(err))
+ value.as_str().and_then(|s| {
+ match s.len() {
+ 19 => PrimitiveDateTime::parse(s, CURRENT_TIMESTAMP_FMT).map(|d| d.assume_utc()),
+ _ => PrimitiveDateTime::parse(s, SQLITE_DATETIME_FMT)
+ .map(|d| d.assume_utc())
+ .or_else(|err| {
+ OffsetDateTime::parse(s, SQLITE_DATETIME_FMT_LEGACY).map_err(|_| err)
}),
- }
- .or_else(|err| Err(FromSqlError::Other(Box::new(err))))
- })
- .map(|tm| tm.to_timespec())
+ }
+ .map_err(|err| FromSqlError::Other(Box::new(err)))
+ })
}
}
#[cfg(test)]
mod test {
use crate::{Connection, Result, NO_PARAMS};
+ use std::time::Duration;
+ use time::OffsetDateTime;
fn checked_memory_handle() -> Connection {
let db = Connection::open_in_memory().unwrap();
@@ -44,22 +44,25 @@ mod test {
}
#[test]
- fn test_timespec() {
+ fn test_offset_date_time() {
let db = checked_memory_handle();
let mut ts_vec = vec![];
- ts_vec.push(time::Timespec::new(10_000, 0)); //January 1, 1970 2:46:40 AM
- ts_vec.push(time::Timespec::new(10_000, 1000)); //January 1, 1970 2:46:40 AM (and one microsecond)
- ts_vec.push(time::Timespec::new(1_500_391_124, 1_000_000)); //July 18, 2017
- ts_vec.push(time::Timespec::new(2_000_000_000, 2_000_000)); //May 18, 2033
- ts_vec.push(time::Timespec::new(3_000_000_000, 999_999_999)); //January 24, 2065
- ts_vec.push(time::Timespec::new(10_000_000_000, 0)); //November 20, 2286
+ let make_datetime =
+ |secs, nanos| OffsetDateTime::from_unix_timestamp(secs) + Duration::from_nanos(nanos);
+
+ ts_vec.push(make_datetime(10_000, 0)); //January 1, 1970 2:46:40 AM
+ ts_vec.push(make_datetime(10_000, 1000)); //January 1, 1970 2:46:40 AM (and one microsecond)
+ ts_vec.push(make_datetime(1_500_391_124, 1_000_000)); //July 18, 2017
+ ts_vec.push(make_datetime(2_000_000_000, 2_000_000)); //May 18, 2033
+ ts_vec.push(make_datetime(3_000_000_000, 999_999_999)); //January 24, 2065
+ ts_vec.push(make_datetime(10_000_000_000, 0)); //November 20, 2286
for ts in ts_vec {
db.execute("INSERT INTO foo(t) VALUES (?)", &[&ts]).unwrap();
- let from: time::Timespec = db
+ let from: OffsetDateTime = db
.query_row("SELECT t FROM foo", NO_PARAMS, |r| r.get(0))
.unwrap();
@@ -72,7 +75,7 @@ mod test {
#[test]
fn test_sqlite_functions() {
let db = checked_memory_handle();
- let result: Result<time::Timespec> =
+ let result: Result<OffsetDateTime> =
db.query_row("SELECT CURRENT_TIMESTAMP", NO_PARAMS, |r| r.get(0));
assert!(result.is_ok());
}
diff --git a/src/types/to_sql.rs b/src/types/to_sql.rs
index bec53f8..937c0f8 100644
--- a/src/types/to_sql.rs
+++ b/src/types/to_sql.rs
@@ -87,6 +87,7 @@ impl ToSql for ToSqlOutput<'_> {
/// A trait for types that can be converted into SQLite values.
pub trait ToSql {
+ /// Converts Rust value to SQLite value
fn to_sql(&self) -> Result<ToSqlOutput<'_>>;
}
diff --git a/src/types/value.rs b/src/types/value.rs
index 332d78b..64dc203 100644
--- a/src/types/value.rs
+++ b/src/types/value.rs
@@ -109,6 +109,7 @@ where
}
impl Value {
+ /// Returns SQLite fundamental datatype.
pub fn data_type(&self) -> Type {
match *self {
Value::Null => Type::Null,
diff --git a/src/types/value_ref.rs b/src/types/value_ref.rs
index 80d2457..2f32434 100644
--- a/src/types/value_ref.rs
+++ b/src/types/value_ref.rs
@@ -20,6 +20,7 @@ pub enum ValueRef<'a> {
}
impl ValueRef<'_> {
+ /// Returns SQLite fundamental datatype.
pub fn data_type(&self) -> Type {
match *self {
ValueRef::Null => Type::Null,