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",
"serde_json",
"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",
"unlock_notify",
"url",
@ -92,7 +95,7 @@ bundled-full = [
]
[dependencies]
time = "0.1.0"
time = { version = "0.2", optional = true }
bitflags = "1.0"
lru-cache = "0.1"
chrono = { version = "0.4", optional = true }
@ -141,7 +144,7 @@ name = "exec"
harness = false
[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
no-default-features = true
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)
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).
* `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)
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).

View File

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

View File

@ -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")]
@ -273,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()
@ -335,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()
));
}

View File

@ -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| {
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).map_err(|_| err)
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)
}),
}
.map_err(|err| FromSqlError::Other(Box::new(err)))
})
.map(|tm| tm.to_timespec())
}
}
#[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());
}