mirror of
https://github.com/isar/rusqlite.git
synced 2024-11-23 00:39:20 +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",
|
"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"
|
||||||
|
@ -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).
|
||||||
|
14
src/lib.rs
14
src/lib.rs
@ -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)?,
|
|
||||||
//! })
|
//! })
|
||||||
//! })?;
|
//! })?;
|
||||||
//!
|
//!
|
||||||
|
@ -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()
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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()
|
match s.len() {
|
||||||
.and_then(|s| {
|
19 => PrimitiveDateTime::parse(s, CURRENT_TIMESTAMP_FMT).map(|d| d.assume_utc()),
|
||||||
match s.len() {
|
_ => PrimitiveDateTime::parse(s, SQLITE_DATETIME_FMT)
|
||||||
19 => time::strptime(s, CURRENT_TIMESTAMP_FMT),
|
.map(|d| d.assume_utc())
|
||||||
_ => time::strptime(s, SQLITE_DATETIME_FMT).or_else(|err| {
|
.or_else(|err| {
|
||||||
time::strptime(s, SQLITE_DATETIME_FMT_LEGACY).map_err(|_| 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());
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user