mirror of
https://github.com/isar/rusqlite.git
synced 2024-11-26 11:31:37 +08:00
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:
parent
464b8283b2
commit
b83d22e2b7
@ -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"
|
||||
|
@ -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).
|
||||
|
14
src/lib.rs
14
src/lib.rs
@ -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)?,
|
||||
//! })
|
||||
//! })?;
|
||||
//!
|
||||
|
@ -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()
|
||||
));
|
||||
}
|
||||
|
||||
|
@ -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());
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user