Upgrade to time v0.2 and put it behind a feature flag

This also removes the usage of time in the crate's top-level
documentation example, as was done for the README in #625.

Fix #653.
This commit is contained in:
Nikhil Benesch 2020-07-11 11:59:29 -04:00 committed by Thom Chiovoloni
parent 464b8283b2
commit b83d22e2b7
5 changed files with 79 additions and 74 deletions

View File

@ -83,6 +83,9 @@ bundled-full = [
"load_extension", "load_extension",
"serde_json", "serde_json",
"series", "series",
# time v0.2 does not work with tarpaulin v0.14.0. See time-rs/time#265.
# Re-enable when time v0.3 is released with the fix.
# "time",
"trace", "trace",
"unlock_notify", "unlock_notify",
"url", "url",
@ -92,7 +95,7 @@ bundled-full = [
] ]
[dependencies] [dependencies]
time = "0.1.0" time = { version = "0.2", optional = true }
bitflags = "1.0" bitflags = "1.0"
lru-cache = "0.1" lru-cache = "0.1"
chrono = { version = "0.4", optional = true } chrono = { version = "0.4", optional = true }
@ -141,7 +144,7 @@ name = "exec"
harness = false harness = false
[package.metadata.docs.rs] [package.metadata.docs.rs]
features = [ "backup", "blob", "chrono", "collation", "functions", "limits", "load_extension", "serde_json", "trace", "url", "vtab", "window", "modern_sqlite", "column_decltype" ] features = [ "backup", "blob", "chrono", "collation", "functions", "limits", "load_extension", "serde_json", "time", "trace", "url", "vtab", "window", "modern_sqlite", "column_decltype" ]
all-features = false all-features = false
no-default-features = true no-default-features = true
default-target = "x86_64-unknown-linux-gnu" default-target = "x86_64-unknown-linux-gnu"

View File

@ -91,6 +91,9 @@ features](https://doc.rust-lang.org/cargo/reference/manifest.html#the-features-s
* `serde_json` implements [`FromSql`](https://docs.rs/rusqlite/~0/rusqlite/types/trait.FromSql.html) * `serde_json` implements [`FromSql`](https://docs.rs/rusqlite/~0/rusqlite/types/trait.FromSql.html)
and [`ToSql`](https://docs.rs/rusqlite/~0/rusqlite/types/trait.ToSql.html) for the and [`ToSql`](https://docs.rs/rusqlite/~0/rusqlite/types/trait.ToSql.html) for the
`Value` type from the [`serde_json` crate](https://crates.io/crates/serde_json). `Value` type from the [`serde_json` crate](https://crates.io/crates/serde_json).
* `time` implements [`FromSql`](https://docs.rs/rusqlite/~0/rusqlite/types/trait.FromSql.html)
and [`ToSql`](https://docs.rs/rusqlite/~0/rusqlite/types/trait.ToSql.html) for the
`time::OffsetDateTime` type from the [`time` crate](https://crates.io/crates/time).
* `url` implements [`FromSql`](https://docs.rs/rusqlite/~0/rusqlite/types/trait.FromSql.html) * `url` implements [`FromSql`](https://docs.rs/rusqlite/~0/rusqlite/types/trait.FromSql.html)
and [`ToSql`](https://docs.rs/rusqlite/~0/rusqlite/types/trait.ToSql.html) for the and [`ToSql`](https://docs.rs/rusqlite/~0/rusqlite/types/trait.ToSql.html) for the
`Url` type from the [`url` crate](https://crates.io/crates/url). `Url` type from the [`url` crate](https://crates.io/crates/url).

View File

@ -3,13 +3,11 @@
//! //!
//! ```rust //! ```rust
//! use rusqlite::{params, Connection, Result}; //! use rusqlite::{params, Connection, Result};
//! use time::Timespec;
//! //!
//! #[derive(Debug)] //! #[derive(Debug)]
//! struct Person { //! struct Person {
//! id: i32, //! id: i32,
//! name: String, //! name: String,
//! time_created: Timespec,
//! data: Option<Vec<u8>>, //! data: Option<Vec<u8>>,
//! } //! }
//! //!
@ -20,7 +18,6 @@
//! "CREATE TABLE person ( //! "CREATE TABLE person (
//! id INTEGER PRIMARY KEY, //! id INTEGER PRIMARY KEY,
//! name TEXT NOT NULL, //! name TEXT NOT NULL,
//! time_created TEXT NOT NULL,
//! data BLOB //! data BLOB
//! )", //! )",
//! params![], //! params![],
@ -28,22 +25,19 @@
//! let me = Person { //! let me = Person {
//! id: 0, //! id: 0,
//! name: "Steven".to_string(), //! name: "Steven".to_string(),
//! time_created: time::get_time(),
//! data: None, //! data: None,
//! }; //! };
//! conn.execute( //! conn.execute(
//! "INSERT INTO person (name, time_created, data) //! "INSERT INTO person (name, data) VALUES (?1, ?2)",
//! VALUES (?1, ?2, ?3)", //! params![me.name, me.data],
//! params![me.name, me.time_created, me.data],
//! )?; //! )?;
//! //!
//! let mut stmt = conn.prepare("SELECT id, name, time_created, data FROM person")?; //! let mut stmt = conn.prepare("SELECT id, name, data FROM person")?;
//! let person_iter = stmt.query_map(params![], |row| { //! let person_iter = stmt.query_map(params![], |row| {
//! Ok(Person { //! Ok(Person {
//! id: row.get(0)?, //! id: row.get(0)?,
//! name: row.get(1)?, //! name: row.get(1)?,
//! time_created: row.get(2)?, //! data: row.get(2)?,
//! data: row.get(3)?,
//! }) //! })
//! })?; //! })?;
//! //!

View File

@ -10,40 +10,39 @@
//! * Strings (`String` and `&str`) //! * Strings (`String` and `&str`)
//! * Blobs (`Vec<u8>` and `&[u8]`) //! * Blobs (`Vec<u8>` and `&[u8]`)
//! //!
//! Additionally, because it is such a common data type, implementations are //! Additionally, if the `time` feature is enabled, implementations are
//! provided for `time::Timespec` that use the RFC 3339 date/time format, //! 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 //! `"%Y-%m-%dT%H:%M:%S.%fZ"`, to store time values as strings. These values
//! can be parsed by SQLite's builtin //! can be parsed by SQLite's builtin
//! [datetime](https://www.sqlite.org/lang_datefunc.html) functions. If you //! [datetime](https://www.sqlite.org/lang_datefunc.html) functions. If you
//! want different storage for timespecs, you can use a newtype. For example, to //! want different storage for datetimes, you can use a newtype.
//! 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())
//! }
//! }
//! ```
//! //!
#![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` //! `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 //! implements `ToSql` or `FromSql` for the cases where you want to know if a
//! value was NULL (which gets translated to `None`). //! value was NULL (which gets translated to `None`).
@ -60,6 +59,7 @@ mod chrono;
mod from_sql; mod from_sql;
#[cfg(feature = "serde_json")] #[cfg(feature = "serde_json")]
mod serde_json; mod serde_json;
#[cfg(feature = "time")]
mod time; mod time;
mod to_sql; mod to_sql;
#[cfg(feature = "url")] #[cfg(feature = "url")]
@ -273,8 +273,9 @@ mod test {
assert!(is_invalid_column_type( assert!(is_invalid_column_type(
row.get::<_, String>(0).err().unwrap() row.get::<_, String>(0).err().unwrap()
)); ));
#[cfg(feature = "time")]
assert!(is_invalid_column_type( assert!(is_invalid_column_type(
row.get::<_, time::Timespec>(0).err().unwrap() row.get::<_, time::OffsetDateTime>(0).err().unwrap()
)); ));
assert!(is_invalid_column_type( assert!(is_invalid_column_type(
row.get::<_, Option<c_int>>(0).err().unwrap() row.get::<_, Option<c_int>>(0).err().unwrap()
@ -335,8 +336,9 @@ mod test {
assert!(is_invalid_column_type( assert!(is_invalid_column_type(
row.get::<_, Vec<u8>>(4).err().unwrap() row.get::<_, Vec<u8>>(4).err().unwrap()
)); ));
#[cfg(feature = "time")]
assert!(is_invalid_column_type( assert!(is_invalid_column_type(
row.get::<_, time::Timespec>(4).err().unwrap() row.get::<_, time::OffsetDateTime>(4).err().unwrap()
)); ));
} }

View File

@ -1,40 +1,40 @@
//! `ToSql` and `FromSql` implementation for [`time::OffsetDateTime`].
use crate::types::{FromSql, FromSqlError, FromSqlResult, ToSql, ToSqlOutput, ValueRef}; use crate::types::{FromSql, FromSqlError, FromSqlResult, ToSql, ToSqlOutput, ValueRef};
use crate::Result; use crate::Result;
use time::{OffsetDateTime, PrimitiveDateTime, UtcOffset};
const CURRENT_TIMESTAMP_FMT: &str = "%Y-%m-%d %H:%M:%S"; 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: &str = "%Y-%m-%dT%H:%M:%S.%NZ";
const SQLITE_DATETIME_FMT_LEGACY: &str = "%Y-%m-%d %H:%M:%S:%f %Z"; 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<'_>> { fn to_sql(&self) -> Result<ToSqlOutput<'_>> {
let time_string = time::at_utc(*self) let time_string = self.to_offset(UtcOffset::UTC).format(SQLITE_DATETIME_FMT);
.strftime(SQLITE_DATETIME_FMT)
.unwrap()
.to_string();
Ok(ToSqlOutput::from(time_string)) Ok(ToSqlOutput::from(time_string))
} }
} }
impl FromSql for time::Timespec { impl FromSql for OffsetDateTime {
fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self> { fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self> {
value value.as_str().and_then(|s| {
.as_str()
.and_then(|s| {
match s.len() { match s.len() {
19 => time::strptime(s, CURRENT_TIMESTAMP_FMT), 19 => PrimitiveDateTime::parse(s, CURRENT_TIMESTAMP_FMT).map(|d| d.assume_utc()),
_ => time::strptime(s, SQLITE_DATETIME_FMT).or_else(|err| { _ => PrimitiveDateTime::parse(s, SQLITE_DATETIME_FMT)
time::strptime(s, SQLITE_DATETIME_FMT_LEGACY).map_err(|_| err) .map(|d| d.assume_utc())
.or_else(|err| {
OffsetDateTime::parse(s, SQLITE_DATETIME_FMT_LEGACY).map_err(|_| err)
}), }),
} }
.map_err(|err| FromSqlError::Other(Box::new(err))) .map_err(|err| FromSqlError::Other(Box::new(err)))
}) })
.map(|tm| tm.to_timespec())
} }
} }
#[cfg(test)] #[cfg(test)]
mod test { mod test {
use crate::{Connection, Result, NO_PARAMS}; use crate::{Connection, Result, NO_PARAMS};
use std::time::Duration;
use time::OffsetDateTime;
fn checked_memory_handle() -> Connection { fn checked_memory_handle() -> Connection {
let db = Connection::open_in_memory().unwrap(); let db = Connection::open_in_memory().unwrap();
@ -44,22 +44,25 @@ mod test {
} }
#[test] #[test]
fn test_timespec() { fn test_offset_date_time() {
let db = checked_memory_handle(); let db = checked_memory_handle();
let mut ts_vec = vec![]; let mut ts_vec = vec![];
ts_vec.push(time::Timespec::new(10_000, 0)); //January 1, 1970 2:46:40 AM let make_datetime =
ts_vec.push(time::Timespec::new(10_000, 1000)); //January 1, 1970 2:46:40 AM (and one microsecond) |secs, nanos| OffsetDateTime::from_unix_timestamp(secs) + Duration::from_nanos(nanos);
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(make_datetime(10_000, 0)); //January 1, 1970 2:46:40 AM
ts_vec.push(time::Timespec::new(3_000_000_000, 999_999_999)); //January 24, 2065 ts_vec.push(make_datetime(10_000, 1000)); //January 1, 1970 2:46:40 AM (and one microsecond)
ts_vec.push(time::Timespec::new(10_000_000_000, 0)); //November 20, 2286 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 { for ts in ts_vec {
db.execute("INSERT INTO foo(t) VALUES (?)", &[&ts]).unwrap(); 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)) .query_row("SELECT t FROM foo", NO_PARAMS, |r| r.get(0))
.unwrap(); .unwrap();
@ -72,7 +75,7 @@ mod test {
#[test] #[test]
fn test_sqlite_functions() { fn test_sqlite_functions() {
let db = checked_memory_handle(); 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)); db.query_row("SELECT CURRENT_TIMESTAMP", NO_PARAMS, |r| r.get(0));
assert!(result.is_ok()); assert!(result.is_ok());
} }