mirror of
https://github.com/launchbadge/sqlx
synced 2024-11-10 06:24:16 +00:00
#115: implement time-rs support
This commit is contained in:
parent
80f402b7e6
commit
084add9cdb
17 changed files with 958 additions and 36 deletions
96
.github/workflows/rust.yml
vendored
96
.github/workflows/rust.yml
vendored
|
@ -39,25 +39,25 @@ jobs:
|
|||
|
||||
# check w/deny warnings in sqlx-core: async-std
|
||||
- working-directory: sqlx-core
|
||||
run: cargo rustc --no-default-features --features 'chrono uuid postgres mysql tls runtime-async-std' -- -D warnings --emit=metadata
|
||||
run: cargo rustc --no-default-features --features 'chrono time uuid postgres mysql tls runtime-async-std' -- -D warnings --emit=metadata
|
||||
|
||||
# check w/deny warnings in sqlx-core: tokio
|
||||
# `cargo rustc -p sqlx-core` ignores `--no-default-features` and builds with `runtime-async-std` anyway
|
||||
# https://github.com/rust-lang/cargo/issues/5364
|
||||
- working-directory: sqlx-core
|
||||
run: cargo rustc --no-default-features --features 'chrono uuid postgres mysql tls runtime-tokio' -- -D warnings --emit=metadata
|
||||
run: cargo rustc --no-default-features --features 'chrono time uuid postgres mysql tls runtime-tokio' -- -D warnings --emit=metadata
|
||||
|
||||
# check w/deny warnings: async-std
|
||||
- run: cargo rustc --no-default-features --features 'chrono uuid postgres mysql macros tls runtime-async-std' -- -D warnings --emit=metadata
|
||||
- run: cargo rustc --no-default-features --features 'chrono time uuid postgres mysql macros tls runtime-async-std' -- -D warnings --emit=metadata
|
||||
|
||||
# check w/deny warnings: tokio
|
||||
- run: cargo rustc --no-default-features --features 'chrono uuid postgres mysql macros tls runtime-tokio' -- -D warnings --emit=metadata
|
||||
- run: cargo rustc --no-default-features --features 'chrono time uuid postgres mysql macros tls runtime-tokio' -- -D warnings --emit=metadata
|
||||
|
||||
# unit test: async-std
|
||||
- run: cargo test --manifest-path sqlx-core/Cargo.toml --no-default-features --features 'chrono uuid postgres mysql tls runtime-async-std'
|
||||
- run: cargo test --manifest-path sqlx-core/Cargo.toml --no-default-features --features 'chrono time uuid postgres mysql tls runtime-async-std'
|
||||
|
||||
# unit test: tokio
|
||||
- run: cargo test --manifest-path sqlx-core/Cargo.toml --no-default-features --features 'chrono uuid postgres mysql tls runtime-tokio'
|
||||
- run: cargo test --manifest-path sqlx-core/Cargo.toml --no-default-features --features 'chrono time uuid postgres mysql tls runtime-tokio'
|
||||
|
||||
# Rust ------------------------------------------------
|
||||
|
||||
|
@ -110,16 +110,37 @@ jobs:
|
|||
|
||||
# -----------------------------------------------------
|
||||
|
||||
# integration test: async-std
|
||||
# integration test: async-std (chrono)
|
||||
- run: cargo test --no-default-features --features 'runtime-async-std postgres macros uuid chrono tls'
|
||||
env:
|
||||
DATABASE_URL: postgres://postgres:postgres@localhost:${{ job.services.postgres.ports[5432] }}/postgres
|
||||
|
||||
# integration test: tokio
|
||||
# integration test: async-std (time)
|
||||
- run: cargo test --no-default-features --features 'runtime-async-std postgres macros uuid time tls'
|
||||
env:
|
||||
DATABASE_URL: postgres://postgres:postgres@localhost:${{ job.services.postgres.ports[5432] }}/postgres
|
||||
|
||||
# integration test: async-std (time + chrono)
|
||||
- run: cargo test --no-default-features --features 'runtime-async-std postgres macros uuid time chrono tls'
|
||||
env:
|
||||
DATABASE_URL: postgres://postgres:postgres@localhost:${{ job.services.postgres.ports[5432] }}/postgres
|
||||
|
||||
# integration test: tokio (chrono)
|
||||
- run: cargo test --no-default-features --features 'runtime-tokio postgres macros uuid chrono tls'
|
||||
env:
|
||||
DATABASE_URL: postgres://postgres:postgres@localhost:${{ job.services.postgres.ports[5432] }}/postgres
|
||||
|
||||
# integration test: tokio (time)
|
||||
- run: cargo test --no-default-features --features 'runtime-tokio postgres macros uuid time tls'
|
||||
env:
|
||||
DATABASE_URL: postgres://postgres:postgres@localhost:${{ job.services.postgres.ports[5432] }}/postgres
|
||||
|
||||
# integration test: tokio (time + chrono)
|
||||
- run: cargo test --no-default-features --features 'runtime-tokio postgres macros uuid time chrono tls'
|
||||
env:
|
||||
DATABASE_URL: postgres://postgres:postgres@localhost:${{ job.services.postgres.ports[5432] }}/postgres
|
||||
|
||||
|
||||
mysql:
|
||||
needs: build
|
||||
runs-on: ubuntu-latest
|
||||
|
@ -162,20 +183,49 @@ jobs:
|
|||
|
||||
# -----------------------------------------------------
|
||||
|
||||
# integration test: async-std
|
||||
# integration test: async-std (chrono)
|
||||
- run: cargo test --no-default-features --features 'runtime-async-std mysql macros uuid chrono tls'
|
||||
env:
|
||||
# pass the path to the CA that the MySQL service generated
|
||||
# NOTE: Github Actions' YML parser doesn't handle multiline strings correctly
|
||||
DATABASE_URL: mysql://root:password@localhost:${{ job.services.mysql.ports[3306] }}/sqlx?ssl-mode=VERIFY_CA&ssl-ca=%2Fdata%2Fmysql%2Fca.pem
|
||||
|
||||
# integration test: tokio
|
||||
# integration test: async-std (time)
|
||||
- run: cargo test --no-default-features --features 'runtime-async-std mysql macros uuid time tls'
|
||||
env:
|
||||
# pass the path to the CA that the MySQL service generated
|
||||
# NOTE: Github Actions' YML parser doesn't handle multiline strings correctly
|
||||
DATABASE_URL: mysql://root:password@localhost:${{ job.services.mysql.ports[3306] }}/sqlx?ssl-mode=VERIFY_CA&ssl-ca=%2Fdata%2Fmysql%2Fca.pem
|
||||
|
||||
# integration test: async-std (time + chrono)
|
||||
- run: cargo test --no-default-features --features 'runtime-async-std mysql macros uuid time chrono tls'
|
||||
env:
|
||||
# pass the path to the CA that the MySQL service generated
|
||||
# NOTE: Github Actions' YML parser doesn't handle multiline strings correctly
|
||||
DATABASE_URL: mysql://root:password@localhost:${{ job.services.mysql.ports[3306] }}/sqlx?ssl-mode=VERIFY_CA&ssl-ca=%2Fdata%2Fmysql%2Fca.pem
|
||||
|
||||
# integration test: tokio (chrono)
|
||||
- run: cargo test --no-default-features --features 'runtime-tokio mysql macros uuid chrono tls'
|
||||
env:
|
||||
# pass the path to the CA that the MySQL service generated
|
||||
# NOTE: Github Actions' YML parser doesn't handle multiline strings correctly
|
||||
DATABASE_URL: mysql://root:password@localhost:${{ job.services.mysql.ports[3306] }}/sqlx?ssl-mode=VERIFY_CA&ssl-ca=%2Fdata%2Fmysql%2Fca.pem
|
||||
|
||||
# integration test: tokio (time)
|
||||
- run: cargo test --no-default-features --features 'runtime-tokio mysql macros uuid time tls'
|
||||
env:
|
||||
# pass the path to the CA that the MySQL service generated
|
||||
# NOTE: Github Actions' YML parser doesn't handle multiline strings correctly
|
||||
DATABASE_URL: mysql://root:password@localhost:${{ job.services.mysql.ports[3306] }}/sqlx?ssl-mode=VERIFY_CA&ssl-ca=%2Fdata%2Fmysql%2Fca.pem
|
||||
|
||||
# integration test: tokio (time + chrono)
|
||||
- run: cargo test --no-default-features --features 'runtime-tokio mysql macros uuid time chrono tls'
|
||||
env:
|
||||
# pass the path to the CA that the MySQL service generated
|
||||
# NOTE: Github Actions' YML parser doesn't handle multiline strings correctly
|
||||
DATABASE_URL: mysql://root:password@localhost:${{ job.services.mysql.ports[3306] }}/sqlx?ssl-mode=VERIFY_CA&ssl-ca=%2Fdata%2Fmysql%2Fca.pem
|
||||
|
||||
|
||||
mariadb:
|
||||
needs: build
|
||||
runs-on: ubuntu-latest
|
||||
|
@ -216,12 +266,32 @@ jobs:
|
|||
|
||||
# -----------------------------------------------------
|
||||
|
||||
# integration test: async-std
|
||||
- run: cargo test --no-default-features --features 'runtime-async-std mysql macros chrono uuid chrono tls'
|
||||
# integration test: async-std (chrono)
|
||||
- run: cargo test --no-default-features --features 'runtime-async-std mysql macros chrono uuid tls'
|
||||
env:
|
||||
DATABASE_URL: mariadb://root:password@localhost:${{ job.services.mariadb.ports[3306] }}/sqlx
|
||||
|
||||
# integration test: tokio
|
||||
# integration test: async-std (time)
|
||||
- run: cargo test --no-default-features --features 'runtime-async-std mysql macros time uuid tls'
|
||||
env:
|
||||
DATABASE_URL: mariadb://root:password@localhost:${{ job.services.mariadb.ports[3306] }}/sqlx
|
||||
|
||||
# integration test: async-std (time + chrono)
|
||||
- run: cargo test --no-default-features --features 'runtime-async-std mysql macros time chrono uuid tls'
|
||||
env:
|
||||
DATABASE_URL: mariadb://root:password@localhost:${{ job.services.mariadb.ports[3306] }}/sqlx
|
||||
|
||||
# integration test: tokio (chrono)
|
||||
- run: cargo test --no-default-features --features 'runtime-tokio mysql macros uuid chrono tls'
|
||||
env:
|
||||
DATABASE_URL: mariadb://root:password@localhost:${{ job.services.mariadb.ports[3306] }}/sqlx
|
||||
|
||||
# integration test: tokio (time)
|
||||
- run: cargo test --no-default-features --features 'runtime-tokio mysql macros uuid time tls'
|
||||
env:
|
||||
DATABASE_URL: mariadb://root:password@localhost:${{ job.services.mariadb.ports[3306] }}/sqlx
|
||||
|
||||
# integration test: tokio (time + chrono)
|
||||
- run: cargo test --no-default-features --features 'runtime-tokio mysql macros uuid time chrono tls'
|
||||
env:
|
||||
DATABASE_URL: mariadb://root:password@localhost:${{ job.services.mariadb.ports[3306] }}/sqlx
|
||||
|
|
33
Cargo.lock
generated
33
Cargo.lock
generated
|
@ -1521,6 +1521,7 @@ dependencies = [
|
|||
"rand 0.7.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"sha-1 0.8.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"sha2 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"time 0.2.7 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"tokio 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"url 2.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"uuid 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
|
@ -1718,6 +1719,35 @@ dependencies = [
|
|||
"winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "time"
|
||||
version = "0.2.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"rustversion 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"time-macros 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "time-macros"
|
||||
version = "0.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"proc-macro-hack 0.5.11 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"time-macros-impl 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "time-macros-impl"
|
||||
version = "0.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"proc-macro-hack 0.5.11 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"proc-macro2 1.0.8 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"syn 1.0.14 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "todos-postgres"
|
||||
version = "0.1.0"
|
||||
|
@ -2233,6 +2263,9 @@ dependencies = [
|
|||
"checksum thread_local 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "d40c6d1b69745a6ec6fb1ca717914848da4b44ae29d9b3080cbee91d72a69b14"
|
||||
"checksum tide 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "13c99b1991db81e611a2614cd1b07fec89ae33c5f755e1f8eb70826fb5af0eea"
|
||||
"checksum time 0.1.42 (registry+https://github.com/rust-lang/crates.io-index)" = "db8dcfca086c1143c9270ac42a2bbd8a7ee477b78ac8e45b19abfb0cbede4b6f"
|
||||
"checksum time 0.2.7 (registry+https://github.com/rust-lang/crates.io-index)" = "3043ac959c44dccc548a57417876c8fe241502aed69d880efc91166c02717a93"
|
||||
"checksum time-macros 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "9ae9b6e9f095bc105e183e3cd493d72579be3181ad4004fceb01adbe9eecab2d"
|
||||
"checksum time-macros-impl 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "e987cfe0537f575b5fc99909de6185f6c19c3ad8889e2275e686a873d0869ba1"
|
||||
"checksum tokio 0.1.22 (registry+https://github.com/rust-lang/crates.io-index)" = "5a09c0b5bb588872ab2f09afa13ee6e9dac11e10a0ec9e8e3ba39a5a5d530af6"
|
||||
"checksum tokio 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)" = "8fdd17989496f49cdc57978c96f0c9fe5e4a58a8bddc6813c449a4624f6a030b"
|
||||
"checksum tokio-buf 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "8fb220f46c53859a4b7ec083e41dec9778ff0b1851c0942b211edb89e0ccdc46"
|
||||
|
|
13
Cargo.toml
13
Cargo.toml
|
@ -26,11 +26,11 @@ authors = [
|
|||
]
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
features = [ "tls", "postgres", "mysql", "uuid", "chrono" ]
|
||||
features = [ "tls", "postgres", "mysql", "uuid", "chrono", "time" ]
|
||||
rustdoc-args = ["--cfg", "docsrs"]
|
||||
|
||||
[features]
|
||||
default = [ "macros", "runtime-async-std" ]
|
||||
default = [ "macros", "runtime-async-std", "time", "postgres" ]
|
||||
macros = [ "sqlx-macros" ]
|
||||
tls = [ "sqlx-core/tls" ]
|
||||
|
||||
|
@ -44,6 +44,7 @@ mysql = [ "sqlx-core/mysql", "sqlx-macros/mysql" ]
|
|||
|
||||
# types
|
||||
chrono = [ "sqlx-core/chrono", "sqlx-macros/chrono" ]
|
||||
time = [ "sqlx-core/time", "sqlx-macros/time" ]
|
||||
uuid = [ "sqlx-core/uuid", "sqlx-macros/uuid" ]
|
||||
|
||||
[dependencies]
|
||||
|
@ -83,6 +84,10 @@ required-features = [ "postgres" ]
|
|||
name = "postgres-types-chrono"
|
||||
required-features = [ "postgres", "chrono" ]
|
||||
|
||||
[[test]]
|
||||
name = "postgres-types-time"
|
||||
required-features = [ "postgres", "time" ]
|
||||
|
||||
[[test]]
|
||||
name = "mysql-types"
|
||||
required-features = [ "mysql" ]
|
||||
|
@ -91,6 +96,10 @@ required-features = [ "mysql" ]
|
|||
name = "mysql-types-chrono"
|
||||
required-features = [ "mysql", "chrono", "macros" ]
|
||||
|
||||
[[test]]
|
||||
name = "mysql-types-time"
|
||||
required-features = [ "mysql", "time", "macros" ]
|
||||
|
||||
[[test]]
|
||||
name = "derives"
|
||||
required-features = [ "macros" ]
|
||||
|
|
26
README.md
26
README.md
|
@ -43,11 +43,11 @@ SQLx is an async, pure Rust SQL crate featuring compile-time checked queries wit
|
|||
|
||||
* **Truly Asynchronous**. Built from the ground-up using async/await for maximum concurrency.
|
||||
|
||||
* **Type-safe SQL** (if you want it) without DSLs. Use the `query!()` macro to check your SQL and bind parameters at
|
||||
* **Type-safe SQL** (if you want it) without DSLs. Use the `query!()` macro to check your SQL and bind parameters at
|
||||
compile time. (You can still use dynamic SQL queries if you like.)
|
||||
|
||||
* **Pure Rust**. The Postgres and MySQL/MariaDB drivers are written in pure Rust using **zero** unsafe code.
|
||||
|
||||
|
||||
* **Runtime Agnostic**. Works on [async-std](https://crates.io/crates/async-std) or [tokio](https://crates.io/crates/tokio) with the `runtime-async-std` or `runtime-tokio` cargo feature flag.
|
||||
|
||||
## Install
|
||||
|
@ -71,17 +71,19 @@ sqlx = { version = "0.2", default-features = false, features = [ "runtime-tokio"
|
|||
#### Cargo Feature Flags
|
||||
|
||||
* `runtime-async-std` (on by default): Use the `async-std` runtime.
|
||||
|
||||
|
||||
* `runtime-tokio`: Use the `tokio` runtime. Mutually exclusive with the `runtime-async-std` feature.
|
||||
|
||||
|
||||
* `postgres`: Add support for the Postgres database server.
|
||||
|
||||
|
||||
* `mysql`: Add support for the MySQL (and MariaDB) database server.
|
||||
|
||||
|
||||
* `uuid`: Add support for UUID (in Postgres).
|
||||
|
||||
|
||||
* `chrono`: Add support for date and time types from `chrono`.
|
||||
|
||||
|
||||
* `time`: Add support for date and time types from `time` crate (simpler than `chrono`)
|
||||
|
||||
* `tls`: Add support for TLS connections.
|
||||
|
||||
## Examples
|
||||
|
@ -97,7 +99,7 @@ let pool = sqlx::PgPool::new("postgres://localhost/database").await?;
|
|||
|
||||
#### Dynamic
|
||||
|
||||
The `sqlx::query` function provides general-purpose prepared statement execution.
|
||||
The `sqlx::query` function provides general-purpose prepared statement execution.
|
||||
The result is an implementation of the `Row` trait. Values can be efficiently accessed by index or name.
|
||||
|
||||
```rust
|
||||
|
@ -105,18 +107,18 @@ let row = sqlx::query("SELECT is_active FROM users WHERE id = ?")
|
|||
.bind(some_user_id)
|
||||
.fetch_one(&mut &pool)
|
||||
.await?;
|
||||
|
||||
|
||||
let is_active: bool = row.get("is_active");
|
||||
```
|
||||
|
||||
#### Static
|
||||
|
||||
The `sqlx::query!` macro prepares the SQL query at compile time and interprets the result in order to constrain input types and
|
||||
The `sqlx::query!` macro prepares the SQL query at compile time and interprets the result in order to constrain input types and
|
||||
infer output types. The result of `query!` is an anonymous struct (or named tuple).
|
||||
|
||||
```rust
|
||||
let countries = sqlx::query!(
|
||||
"SELECT country, COUNT(*) FROM users GROUP BY country WHERE organization = ?",
|
||||
"SELECT country, COUNT(*) FROM users GROUP BY country WHERE organization = ?",
|
||||
organization
|
||||
)
|
||||
.fetch(&mut &pool) // -> impl Stream<Item = { country: String, count: i64 }>
|
||||
|
|
|
@ -24,7 +24,7 @@ runtime-tokio = [ "async-native-tls/runtime-tokio", "tokio" ]
|
|||
[dependencies]
|
||||
async-native-tls = { version = "0.3.2", default-features = false, optional = true }
|
||||
async-std = { version = "1.4.0", optional = true }
|
||||
tokio = { version = "0.2.9", default-features = false, features = [ "dns", "fs", "time", "tcp" ], optional = true }
|
||||
tokio = { version = "0.2.9", default-features = false, features = [ "dns", "fs", "time", "tcp" ], optional = true }
|
||||
async-stream = { version = "0.2.0", default-features = false }
|
||||
base64 = { version = "0.11.0", default-features = false, optional = true, features = [ "std" ] }
|
||||
bitflags = { version = "1.2.1", default-features = false }
|
||||
|
@ -46,6 +46,7 @@ percent-encoding = "2.1.0"
|
|||
rand = { version = "0.7.3", default-features = false, optional = true, features = [ "std" ] }
|
||||
sha-1 = { version = "0.8.2", default-features = false, optional = true }
|
||||
sha2 = { version = "0.8.1", default-features = false, optional = true }
|
||||
time = { version = "0.2.7", default-features = false, optional = true }
|
||||
url = { version = "2.1.1", default-features = false }
|
||||
uuid = { version = "0.8.1", default-features = false, optional = true }
|
||||
hmac = { version = "0.7.1", default-features = false, optional = true }
|
||||
|
|
|
@ -8,6 +8,9 @@ mod uint;
|
|||
#[cfg(feature = "chrono")]
|
||||
mod chrono;
|
||||
|
||||
#[cfg(feature = "time")]
|
||||
mod time;
|
||||
|
||||
use std::fmt::{self, Debug, Display};
|
||||
|
||||
use crate::mysql::protocol::TypeId;
|
||||
|
|
276
sqlx-core/src/mysql/types/time.rs
Normal file
276
sqlx-core/src/mysql/types/time.rs
Normal file
|
@ -0,0 +1,276 @@
|
|||
use std::convert::TryFrom;
|
||||
|
||||
use byteorder::{ByteOrder, LittleEndian};
|
||||
use time::{Date, Time, PrimitiveDateTime, OffsetDateTime, UtcOffset};
|
||||
|
||||
use crate::decode::{Decode, DecodeError};
|
||||
use crate::encode::Encode;
|
||||
use crate::io::{Buf, BufMut};
|
||||
use crate::mysql::protocol::TypeId;
|
||||
use crate::mysql::types::MySqlTypeInfo;
|
||||
use crate::mysql::MySql;
|
||||
use crate::types::HasSqlType;
|
||||
|
||||
impl HasSqlType<OffsetDateTime> for MySql {
|
||||
fn type_info() -> MySqlTypeInfo {
|
||||
MySqlTypeInfo::new(TypeId::TIMESTAMP)
|
||||
}
|
||||
}
|
||||
|
||||
impl Encode<MySql> for OffsetDateTime {
|
||||
fn encode(&self, buf: &mut Vec<u8>) {
|
||||
let utc_dt = self.to_offset(UtcOffset::UTC);
|
||||
let primitive_dt = PrimitiveDateTime::new(utc_dt.date(), utc_dt.time());
|
||||
|
||||
Encode::<MySql>::encode(&primitive_dt, buf);
|
||||
}
|
||||
}
|
||||
|
||||
impl Decode<MySql> for OffsetDateTime {
|
||||
fn decode(buf: &[u8]) -> Result<Self, DecodeError> {
|
||||
let primitive: PrimitiveDateTime = Decode::<MySql>::decode(buf)?;
|
||||
|
||||
Ok(primitive.assume_utc())
|
||||
}
|
||||
}
|
||||
|
||||
impl HasSqlType<Time> for MySql {
|
||||
fn type_info() -> MySqlTypeInfo {
|
||||
MySqlTypeInfo::new(TypeId::TIME)
|
||||
}
|
||||
}
|
||||
|
||||
impl Encode<MySql> for Time {
|
||||
fn encode(&self, buf: &mut Vec<u8>) {
|
||||
let len = Encode::<MySql>::size_hint(self) - 1;
|
||||
buf.push(len as u8);
|
||||
|
||||
// NaiveTime is not negative
|
||||
buf.push(0);
|
||||
|
||||
// "date on 4 bytes little-endian format" (?)
|
||||
// https://mariadb.com/kb/en/resultset-row/#teimstamp-binary-encoding
|
||||
buf.advance(4);
|
||||
|
||||
encode_time(self, len > 9, buf);
|
||||
}
|
||||
|
||||
fn size_hint(&self) -> usize {
|
||||
if self.nanosecond() == 0 {
|
||||
// if micro_seconds is 0, length is 8 and micro_seconds is not sent
|
||||
9
|
||||
} else {
|
||||
// otherwise length is 12
|
||||
13
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Decode<MySql> for Time {
|
||||
fn decode(mut buf: &[u8]) -> Result<Self, DecodeError> {
|
||||
// data length, expecting 8 or 12 (fractional seconds)
|
||||
let len = buf.get_u8()?;
|
||||
|
||||
// is negative : int<1>
|
||||
let is_negative = buf.get_u8()?;
|
||||
assert_eq!(is_negative, 0, "Negative dates/times are not supported");
|
||||
|
||||
// "date on 4 bytes little-endian format" (?)
|
||||
// https://mariadb.com/kb/en/resultset-row/#timestamp-binary-encoding
|
||||
buf.advance(4);
|
||||
|
||||
decode_time(len - 5, buf)
|
||||
}
|
||||
}
|
||||
|
||||
impl HasSqlType<Date> for MySql {
|
||||
fn type_info() -> MySqlTypeInfo {
|
||||
MySqlTypeInfo::new(TypeId::DATE)
|
||||
}
|
||||
}
|
||||
|
||||
impl Encode<MySql> for Date {
|
||||
fn encode(&self, buf: &mut Vec<u8>) {
|
||||
buf.push(4);
|
||||
|
||||
encode_date(self, buf);
|
||||
}
|
||||
|
||||
fn size_hint(&self) -> usize {
|
||||
5
|
||||
}
|
||||
}
|
||||
|
||||
impl Decode<MySql> for Date {
|
||||
fn decode(buf: &[u8]) -> Result<Self, DecodeError> {
|
||||
decode_date(&buf[1..])
|
||||
}
|
||||
}
|
||||
|
||||
impl HasSqlType<PrimitiveDateTime> for MySql {
|
||||
fn type_info() -> MySqlTypeInfo {
|
||||
MySqlTypeInfo::new(TypeId::DATETIME)
|
||||
}
|
||||
}
|
||||
|
||||
impl Encode<MySql> for PrimitiveDateTime {
|
||||
fn encode(&self, buf: &mut Vec<u8>) {
|
||||
let len = Encode::<MySql>::size_hint(self) - 1;
|
||||
buf.push(len as u8);
|
||||
|
||||
encode_date(&self.date(), buf);
|
||||
|
||||
if len > 4 {
|
||||
encode_time(&self.time(), len > 8, buf);
|
||||
}
|
||||
}
|
||||
|
||||
fn size_hint(&self) -> usize {
|
||||
// to save space the packet can be compressed:
|
||||
match (
|
||||
self.hour(),
|
||||
self.minute(),
|
||||
self.second(),
|
||||
self.nanosecond(),
|
||||
) {
|
||||
// if hour, minutes, seconds and micro_seconds are all 0,
|
||||
// length is 4 and no other field is sent
|
||||
(0, 0, 0, 0) => 5,
|
||||
|
||||
// if micro_seconds is 0, length is 7
|
||||
// and micro_seconds is not sent
|
||||
(_, _, _, 0) => 8,
|
||||
|
||||
// otherwise length is 11
|
||||
(_, _, _, _) => 12,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Decode<MySql> for PrimitiveDateTime {
|
||||
fn decode(buf: &[u8]) -> Result<Self, DecodeError> {
|
||||
let len = buf[0];
|
||||
let date = decode_date(&buf[1..])?;
|
||||
|
||||
let dt = if len > 4 {
|
||||
date.with_time(decode_time(len - 4, &buf[5..])?)
|
||||
} else {
|
||||
date.midnight()
|
||||
};
|
||||
|
||||
Ok(dt)
|
||||
}
|
||||
}
|
||||
|
||||
fn encode_date(date: &Date, buf: &mut Vec<u8>) {
|
||||
// MySQL supports years from 1000 - 9999
|
||||
let year = u16::try_from(date.year())
|
||||
.unwrap_or_else(|_| panic!("NaiveDateTime out of range for Mysql: {}", date));
|
||||
|
||||
buf.extend_from_slice(&year.to_le_bytes());
|
||||
buf.push(date.month());
|
||||
buf.push(date.day());
|
||||
}
|
||||
|
||||
fn decode_date(buf: &[u8]) -> Result<Date, DecodeError> {
|
||||
Date::try_from_ymd(
|
||||
LittleEndian::read_u16(buf) as i32,
|
||||
buf[2] as u8,
|
||||
buf[3] as u8,
|
||||
).map_err(|e| DecodeError::Message(Box::new(format!("Error while decoding Date: {}", e))))
|
||||
}
|
||||
|
||||
fn encode_time(time: &Time, include_micros: bool, buf: &mut Vec<u8>) {
|
||||
buf.push(time.hour());
|
||||
buf.push(time.minute());
|
||||
buf.push(time.second());
|
||||
|
||||
if include_micros {
|
||||
buf.put_u32::<LittleEndian>((time.nanosecond() / 1000) as u32);
|
||||
}
|
||||
}
|
||||
|
||||
fn decode_time(len: u8, mut buf: &[u8]) -> Result<Time, DecodeError> {
|
||||
let hour = buf.get_u8()?;
|
||||
let minute = buf.get_u8()?;
|
||||
let seconds = buf.get_u8()?;
|
||||
|
||||
let micros = if len > 3 {
|
||||
// microseconds : int<EOF>
|
||||
buf.get_uint::<LittleEndian>(buf.len())?
|
||||
} else {
|
||||
0
|
||||
};
|
||||
|
||||
Time::try_from_hms_micro(
|
||||
hour,
|
||||
minute,
|
||||
seconds,
|
||||
micros as u32,
|
||||
).map_err(|e| DecodeError::Message(Box::new(format!("Time out of range for MySQL: {}", e))))
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
use time::{date, time};
|
||||
|
||||
#[test]
|
||||
fn test_encode_date_time() {
|
||||
let mut buf = Vec::new();
|
||||
|
||||
// test values from https://dev.mysql.com/doc/internals/en/binary-protocol-value.html
|
||||
let date = PrimitiveDateTime::new(
|
||||
date!(2010-10-17),
|
||||
time!(19:27:30.000001),
|
||||
);
|
||||
Encode::<MySql>::encode(&date, &mut buf);
|
||||
assert_eq!(*buf, [11, 218, 7, 10, 17, 19, 27, 30, 1, 0, 0, 0]);
|
||||
|
||||
buf.clear();
|
||||
|
||||
let date = PrimitiveDateTime::new(
|
||||
date!(2010-10-17),
|
||||
time!(19:27:30),
|
||||
);
|
||||
Encode::<MySql>::encode(&date, &mut buf);
|
||||
assert_eq!(*buf, [7, 218, 7, 10, 17, 19, 27, 30]);
|
||||
|
||||
buf.clear();
|
||||
|
||||
let date = PrimitiveDateTime::new(
|
||||
date!(2010-10-17),
|
||||
time!(00:00:00),
|
||||
);
|
||||
Encode::<MySql>::encode(&date, &mut buf);
|
||||
assert_eq!(*buf, [4, 218, 7, 10, 17]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_decode_date_time() {
|
||||
// test values from https://dev.mysql.com/doc/internals/en/binary-protocol-value.html
|
||||
let buf = [11, 218, 7, 10, 17, 19, 27, 30, 1, 0, 0, 0];
|
||||
let date1 = <PrimitiveDateTime as Decode<MySql>>::decode(&buf).unwrap();
|
||||
assert_eq!(date1.to_string(), "2010-10-17 19:27:30.000001");
|
||||
|
||||
let buf = [7, 218, 7, 10, 17, 19, 27, 30];
|
||||
let date2 = <PrimitiveDateTime as Decode<MySql>>::decode(&buf).unwrap();
|
||||
assert_eq!(date2.to_string(), "2010-10-17 19:27:30");
|
||||
|
||||
let buf = [4, 218, 7, 10, 17];
|
||||
let date3 = <PrimitiveDateTime as Decode<MySql>>::decode(&buf).unwrap();
|
||||
assert_eq!(date3.to_string(), "2010-10-17 0:00");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_encode_date() {
|
||||
let mut buf = Vec::new();
|
||||
let date: Date = date!(2010-10-17);
|
||||
Encode::<MySql>::encode(&date, &mut buf);
|
||||
assert_eq!(*buf, [4, 218, 7, 10, 17]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_decode_date() {
|
||||
let buf = [4, 218, 7, 10, 17];
|
||||
let date = <Date as Decode<MySql>>::decode(&buf).unwrap();
|
||||
assert_eq!(date, date!(2010-10-17));
|
||||
}
|
|
@ -7,6 +7,9 @@ mod str;
|
|||
#[cfg(feature = "chrono")]
|
||||
mod chrono;
|
||||
|
||||
#[cfg(feature = "time")]
|
||||
mod time;
|
||||
|
||||
#[cfg(feature = "uuid")]
|
||||
mod uuid;
|
||||
|
||||
|
|
286
sqlx-core/src/postgres/types/time.rs
Normal file
286
sqlx-core/src/postgres/types/time.rs
Normal file
|
@ -0,0 +1,286 @@
|
|||
use std::convert::TryInto;
|
||||
use std::mem;
|
||||
|
||||
use time::{date, offset, Date, Time, PrimitiveDateTime, OffsetDateTime, NumericalDuration};
|
||||
|
||||
use crate::decode::{Decode, DecodeError};
|
||||
use crate::encode::Encode;
|
||||
use crate::postgres::protocol::TypeId;
|
||||
use crate::postgres::types::PgTypeInfo;
|
||||
use crate::postgres::Postgres;
|
||||
use crate::types::HasSqlType;
|
||||
|
||||
const POSTGRES_EPOCH: PrimitiveDateTime = date!(2000-1-1).midnight();
|
||||
|
||||
impl HasSqlType<Time> for Postgres {
|
||||
fn type_info() -> PgTypeInfo {
|
||||
PgTypeInfo::new(TypeId::TIME)
|
||||
}
|
||||
}
|
||||
|
||||
impl HasSqlType<Date> for Postgres {
|
||||
fn type_info() -> PgTypeInfo {
|
||||
PgTypeInfo::new(TypeId::DATE)
|
||||
}
|
||||
}
|
||||
|
||||
impl HasSqlType<PrimitiveDateTime> for Postgres {
|
||||
fn type_info() -> PgTypeInfo {
|
||||
PgTypeInfo::new(TypeId::TIMESTAMP)
|
||||
}
|
||||
}
|
||||
|
||||
impl HasSqlType<OffsetDateTime> for Postgres {
|
||||
fn type_info() -> PgTypeInfo {
|
||||
PgTypeInfo::new(TypeId::TIMESTAMPTZ)
|
||||
}
|
||||
}
|
||||
|
||||
impl HasSqlType<[Time]> for Postgres {
|
||||
fn type_info() -> PgTypeInfo {
|
||||
PgTypeInfo::new(TypeId::ARRAY_TIME)
|
||||
}
|
||||
}
|
||||
|
||||
impl HasSqlType<[Date]> for Postgres {
|
||||
fn type_info() -> PgTypeInfo {
|
||||
PgTypeInfo::new(TypeId::ARRAY_DATE)
|
||||
}
|
||||
}
|
||||
|
||||
impl HasSqlType<[PrimitiveDateTime]> for Postgres {
|
||||
fn type_info() -> PgTypeInfo {
|
||||
PgTypeInfo::new(TypeId::ARRAY_TIMESTAMP)
|
||||
}
|
||||
}
|
||||
|
||||
impl HasSqlType<[OffsetDateTime]> for Postgres {
|
||||
fn type_info() -> PgTypeInfo {
|
||||
PgTypeInfo::new(TypeId::ARRAY_TIMESTAMPTZ)
|
||||
}
|
||||
}
|
||||
|
||||
const fn microseconds_since_midnight(time: &Time) -> u64 {
|
||||
time.hour() as u64 * 60 * 60 * 1_000_000
|
||||
+ time.minute() as u64 * 60 * 1_000_000
|
||||
+ time.second() as u64 * 1_000_000
|
||||
+ time.microsecond() as u64
|
||||
}
|
||||
|
||||
fn from_nanoseconds_since_midnight(mut microsecond: u64) -> Result<Time, DecodeError> {
|
||||
#![allow(clippy::cast_possible_truncation)]
|
||||
|
||||
microsecond %= 86_400 * 1_000_000;
|
||||
|
||||
Time::try_from_hms_micro(
|
||||
(microsecond / 1_000_000 / 60 / 60) as u8,
|
||||
(microsecond / 1_000_000 / 60 % 60) as u8,
|
||||
(microsecond / 1_000_000 % 60) as u8,
|
||||
(microsecond % 1_000_000) as u32,
|
||||
).map_err(|e| DecodeError::Message(Box::new(format!("Time out of range for Postgres: {}", e))))
|
||||
}
|
||||
|
||||
impl Decode<Postgres> for Time {
|
||||
fn decode(raw: &[u8]) -> Result<Self, DecodeError> {
|
||||
let micros: i64 = Decode::<Postgres>::decode(raw)?;
|
||||
|
||||
from_nanoseconds_since_midnight(micros as u64)
|
||||
}
|
||||
}
|
||||
|
||||
impl Encode<Postgres> for Time {
|
||||
fn encode(&self, buf: &mut Vec<u8>) {
|
||||
let micros = microseconds_since_midnight(&self) / 1000;
|
||||
|
||||
Encode::<Postgres>::encode(&(micros as i64), buf);
|
||||
}
|
||||
|
||||
fn size_hint(&self) -> usize {
|
||||
mem::size_of::<u64>()
|
||||
}
|
||||
}
|
||||
|
||||
impl Decode<Postgres> for Date {
|
||||
fn decode(raw: &[u8]) -> Result<Self, DecodeError> {
|
||||
let n: i32 = Decode::<Postgres>::decode(raw)?;
|
||||
|
||||
Ok(date!(2000-1-1) + (n as i64).days())
|
||||
}
|
||||
}
|
||||
|
||||
impl Encode<Postgres> for Date {
|
||||
fn encode(&self, buf: &mut Vec<u8>) {
|
||||
let days: i32 = (*self - date!(2000-1-1))
|
||||
.whole_days()
|
||||
.try_into()
|
||||
// TODO: How does Diesel handle this?
|
||||
.unwrap_or_else(|_| panic!("Date out of range for Postgres: {:?}", self));
|
||||
|
||||
Encode::<Postgres>::encode(&days, buf)
|
||||
}
|
||||
|
||||
fn size_hint(&self) -> usize {
|
||||
mem::size_of::<i32>()
|
||||
}
|
||||
}
|
||||
|
||||
impl Decode<Postgres> for PrimitiveDateTime {
|
||||
fn decode(raw: &[u8]) -> Result<Self, DecodeError> {
|
||||
let n: i64 = Decode::<Postgres>::decode(raw)?;
|
||||
|
||||
Ok(POSTGRES_EPOCH + n.microseconds())
|
||||
}
|
||||
}
|
||||
|
||||
impl Encode<Postgres> for PrimitiveDateTime {
|
||||
fn encode(&self, buf: &mut Vec<u8>) {
|
||||
let micros: i64 = (*self - POSTGRES_EPOCH)
|
||||
.whole_microseconds()
|
||||
.try_into()
|
||||
.unwrap_or_else(|_| panic!("PrimitiveDateTime out of range for Postgres: {:?}", self));
|
||||
|
||||
Encode::<Postgres>::encode(µs, buf);
|
||||
}
|
||||
|
||||
fn size_hint(&self) -> usize {
|
||||
mem::size_of::<i64>()
|
||||
}
|
||||
}
|
||||
|
||||
impl Decode<Postgres> for OffsetDateTime {
|
||||
fn decode(raw: &[u8]) -> Result<Self, DecodeError> {
|
||||
let date_time: PrimitiveDateTime = Decode::<Postgres>::decode(raw)?;
|
||||
Ok(date_time.assume_utc())
|
||||
}
|
||||
}
|
||||
|
||||
impl Encode<Postgres> for OffsetDateTime {
|
||||
fn encode(&self, buf: &mut Vec<u8>) {
|
||||
let utc_dt = self.to_offset(offset!(UTC));
|
||||
let primitive_dt = PrimitiveDateTime::new(utc_dt.date(), utc_dt.time());
|
||||
|
||||
Encode::<Postgres>::encode(&primitive_dt, buf);
|
||||
}
|
||||
|
||||
fn size_hint(&self) -> usize {
|
||||
mem::size_of::<i64>()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
use time::time;
|
||||
|
||||
#[test]
|
||||
fn test_encode_datetime() {
|
||||
let mut buf = Vec::new();
|
||||
|
||||
Encode::<Postgres>::encode(&POSTGRES_EPOCH, &mut buf);
|
||||
assert_eq!(buf, [0; 8]);
|
||||
buf.clear();
|
||||
|
||||
// one hour past epoch
|
||||
let date = POSTGRES_EPOCH + 1.hours();
|
||||
Encode::<Postgres>::encode(&date, &mut buf);
|
||||
assert_eq!(buf, 3_600_000_000i64.to_be_bytes());
|
||||
buf.clear();
|
||||
|
||||
// some random date
|
||||
let date = PrimitiveDateTime::new(date!(2019-12-11), time!(11:01:05));
|
||||
let expected = (date - POSTGRES_EPOCH)
|
||||
.whole_microseconds() as i64;
|
||||
Encode::<Postgres>::encode(&date, &mut buf);
|
||||
assert_eq!(buf, expected.to_be_bytes());
|
||||
buf.clear();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_decode_datetime() {
|
||||
let buf = [0u8; 8];
|
||||
let date: PrimitiveDateTime = Decode::<Postgres>::decode(&buf).unwrap();
|
||||
assert_eq!(date, PrimitiveDateTime::new(date!(2000-01-01), time!(00:00:00)));
|
||||
|
||||
let buf = 3_600_000_000i64.to_be_bytes();
|
||||
let date: PrimitiveDateTime = Decode::<Postgres>::decode(&buf).unwrap();
|
||||
assert_eq!(date, PrimitiveDateTime::new(date!(2000-01-01), time!(01:00:00)));
|
||||
|
||||
let buf = 629_377_265_000_000i64.to_be_bytes();
|
||||
let date: PrimitiveDateTime = Decode::<Postgres>::decode(&buf).unwrap();
|
||||
assert_eq!(date, PrimitiveDateTime::new(date!(2019-12-11), time!(11:01:05)));
|
||||
}
|
||||
|
||||
|
||||
#[test]
|
||||
fn test_encode_offsetdatetime() {
|
||||
let mut buf = Vec::new();
|
||||
|
||||
Encode::<Postgres>::encode(&POSTGRES_EPOCH.assume_utc(), &mut buf);
|
||||
assert_eq!(buf, [0; 8]);
|
||||
buf.clear();
|
||||
|
||||
// one hour past epoch in MSK (2 hours before epoch in UTC)
|
||||
let date = (POSTGRES_EPOCH + 1.hours()).assume_offset(offset!(+3));
|
||||
Encode::<Postgres>::encode(&date, &mut buf);
|
||||
assert_eq!(buf, (-7_200_000_000i64).to_be_bytes());
|
||||
buf.clear();
|
||||
|
||||
// some random date in MSK
|
||||
let date = PrimitiveDateTime::new(
|
||||
date!(2019-12-11), time!(11:01:05)
|
||||
).assume_offset(offset!(+3));
|
||||
let expected = (date - POSTGRES_EPOCH.assume_utc())
|
||||
.whole_microseconds() as i64;
|
||||
Encode::<Postgres>::encode(&date, &mut buf);
|
||||
assert_eq!(buf, expected.to_be_bytes());
|
||||
buf.clear();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_decode_offsetdatetime() {
|
||||
let buf = [0u8; 8];
|
||||
let date: OffsetDateTime = Decode::<Postgres>::decode(&buf).unwrap();
|
||||
assert_eq!(date, PrimitiveDateTime::new(date!(2000-01-01), time!(00:00:00)).assume_utc());
|
||||
|
||||
let buf = 3_600_000_000i64.to_be_bytes();
|
||||
let date: OffsetDateTime = Decode::<Postgres>::decode(&buf).unwrap();
|
||||
assert_eq!(date, PrimitiveDateTime::new(date!(2000-01-01), time!(01:00:00)).assume_utc());
|
||||
|
||||
let buf = 629_377_265_000_000i64.to_be_bytes();
|
||||
let date: OffsetDateTime = Decode::<Postgres>::decode(&buf).unwrap();
|
||||
assert_eq!(date, PrimitiveDateTime::new(date!(2019-12-11), time!(11:01:05)).assume_utc());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_encode_date() {
|
||||
let mut buf = Vec::new();
|
||||
|
||||
let date = date!(2000-1-1);
|
||||
Encode::<Postgres>::encode(&date, &mut buf);
|
||||
assert_eq!(buf, [0; 4]);
|
||||
buf.clear();
|
||||
|
||||
let date = date!(2001-1-1);
|
||||
Encode::<Postgres>::encode(&date, &mut buf);
|
||||
// 2000 was a leap year
|
||||
assert_eq!(buf, 366i32.to_be_bytes());
|
||||
buf.clear();
|
||||
|
||||
let date = date!(2019-12-11);
|
||||
Encode::<Postgres>::encode(&date, &mut buf);
|
||||
assert_eq!(buf, 7284i32.to_be_bytes());
|
||||
buf.clear();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_decode_date() {
|
||||
let buf = [0; 4];
|
||||
let date: Date = Decode::<Postgres>::decode(&buf).unwrap();
|
||||
assert_eq!(date, date!(2000-01-01));
|
||||
|
||||
let buf = 366i32.to_be_bytes();
|
||||
let date: Date = Decode::<Postgres>::decode(&buf).unwrap();
|
||||
assert_eq!(date, date!(2001-01-01));
|
||||
|
||||
let buf = 7284i32.to_be_bytes();
|
||||
let date: Date = Decode::<Postgres>::decode(&buf).unwrap();
|
||||
assert_eq!(date, date!(2019-12-11));
|
||||
}
|
|
@ -14,6 +14,12 @@ pub mod chrono {
|
|||
pub use chrono::{DateTime, NaiveDate, NaiveDateTime, NaiveTime, Utc};
|
||||
}
|
||||
|
||||
#[cfg(feature = "time")]
|
||||
#[cfg_attr(docsrs, doc(cfg(feature = "time")))]
|
||||
pub mod time {
|
||||
pub use time::{Date, Time, PrimitiveDateTime, OffsetDateTime, UtcOffset};
|
||||
}
|
||||
|
||||
pub trait TypeInfo: Debug + Display + Clone {
|
||||
/// Compares type information to determine if `other` is compatible at the Rust level
|
||||
/// with `self`.
|
||||
|
|
|
@ -27,6 +27,7 @@ postgres = [ "sqlx/postgres" ]
|
|||
|
||||
# type
|
||||
chrono = [ "sqlx/chrono" ]
|
||||
time = [ "sqlx/time" ]
|
||||
uuid = [ "sqlx/uuid" ]
|
||||
|
||||
[dependencies]
|
||||
|
|
|
@ -17,17 +17,29 @@ impl_database_ext! {
|
|||
// BINARY, VAR_BINARY, BLOB
|
||||
Vec<u8>,
|
||||
|
||||
#[cfg(feature = "chrono")]
|
||||
#[cfg(all(feature = "chrono", not(feature = "time")))]
|
||||
sqlx::types::chrono::NaiveTime,
|
||||
|
||||
#[cfg(feature = "chrono")]
|
||||
#[cfg(all(feature = "chrono", not(feature = "time")))]
|
||||
sqlx::types::chrono::NaiveDate,
|
||||
|
||||
#[cfg(feature = "chrono")]
|
||||
#[cfg(all(feature = "chrono", not(feature = "time")))]
|
||||
sqlx::types::chrono::NaiveDateTime,
|
||||
|
||||
#[cfg(feature = "chrono")]
|
||||
#[cfg(all(feature = "chrono", not(feature = "time")))]
|
||||
sqlx::types::chrono::DateTime<sqlx::types::chrono::Utc>,
|
||||
|
||||
#[cfg(feature = "time")]
|
||||
sqlx::types::time::Time,
|
||||
|
||||
#[cfg(feature = "time")]
|
||||
sqlx::types::time::Date,
|
||||
|
||||
#[cfg(feature = "time")]
|
||||
sqlx::types::time::PrimitiveDateTime,
|
||||
|
||||
#[cfg(feature = "time")]
|
||||
sqlx::types::time::OffsetDateTime,
|
||||
},
|
||||
ParamChecking::Weak
|
||||
}
|
||||
|
|
|
@ -14,17 +14,29 @@ impl_database_ext! {
|
|||
#[cfg(feature = "uuid")]
|
||||
sqlx::types::Uuid,
|
||||
|
||||
#[cfg(feature = "chrono")]
|
||||
#[cfg(all(feature = "chrono", not(feature = "time")))]
|
||||
sqlx::types::chrono::NaiveTime,
|
||||
|
||||
#[cfg(feature = "chrono")]
|
||||
#[cfg(all(feature = "chrono", not(feature = "time")))]
|
||||
sqlx::types::chrono::NaiveDate,
|
||||
|
||||
#[cfg(feature = "chrono")]
|
||||
#[cfg(all(feature = "chrono", not(feature = "time")))]
|
||||
sqlx::types::chrono::NaiveDateTime,
|
||||
|
||||
#[cfg(feature = "chrono")]
|
||||
#[cfg(all(feature = "chrono", not(feature = "time")))]
|
||||
sqlx::types::chrono::DateTime<sqlx::types::chrono::Utc> | sqlx::types::chrono::DateTime<_>,
|
||||
|
||||
#[cfg(feature = "time")]
|
||||
sqlx::types::time::Time,
|
||||
|
||||
#[cfg(feature = "time")]
|
||||
sqlx::types::time::Date,
|
||||
|
||||
#[cfg(feature = "time")]
|
||||
sqlx::types::time::PrimitiveDateTime,
|
||||
|
||||
#[cfg(feature = "time")]
|
||||
sqlx::types::time::OffsetDateTime,
|
||||
},
|
||||
ParamChecking::Strong
|
||||
}
|
||||
|
|
|
@ -5,6 +5,7 @@ async fn connect() -> anyhow::Result<MySqlConnection> {
|
|||
Ok(MySqlConnection::open(dotenv::var("DATABASE_URL")?).await?)
|
||||
}
|
||||
|
||||
#[cfg(all(feature = "chrono", not(feature = "time")))]
|
||||
#[cfg_attr(feature = "runtime-async-std", async_std::test)]
|
||||
#[cfg_attr(feature = "runtime-tokio", tokio::test)]
|
||||
async fn mysql_chrono_date() -> anyhow::Result<()> {
|
||||
|
@ -26,6 +27,7 @@ async fn mysql_chrono_date() -> anyhow::Result<()> {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(all(feature = "chrono", not(feature = "time")))]
|
||||
#[cfg_attr(feature = "runtime-async-std", async_std::test)]
|
||||
#[cfg_attr(feature = "runtime-tokio", tokio::test)]
|
||||
async fn mysql_chrono_date_time() -> anyhow::Result<()> {
|
||||
|
@ -45,6 +47,7 @@ async fn mysql_chrono_date_time() -> anyhow::Result<()> {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(all(feature = "chrono", not(feature = "time")))]
|
||||
#[cfg_attr(feature = "runtime-async-std", async_std::test)]
|
||||
#[cfg_attr(feature = "runtime-tokio", tokio::test)]
|
||||
async fn mysql_chrono_time() -> anyhow::Result<()> {
|
||||
|
@ -63,6 +66,7 @@ async fn mysql_chrono_time() -> anyhow::Result<()> {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(all(feature = "chrono", not(feature = "time")))]
|
||||
#[cfg_attr(feature = "runtime-async-std", async_std::test)]
|
||||
#[cfg_attr(feature = "runtime-tokio", tokio::test)]
|
||||
async fn mysql_chrono_timestamp() -> anyhow::Result<()> {
|
||||
|
|
92
tests/mysql-types-time.rs
Normal file
92
tests/mysql-types-time.rs
Normal file
|
@ -0,0 +1,92 @@
|
|||
use sqlx::types::time::{OffsetDateTime, Date, Time, UtcOffset};
|
||||
use sqlx::{mysql::MySqlConnection, Connection, Row};
|
||||
|
||||
async fn connect() -> anyhow::Result<MySqlConnection> {
|
||||
Ok(MySqlConnection::open(dotenv::var("DATABASE_URL")?).await?)
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "runtime-async-std", async_std::test)]
|
||||
#[cfg_attr(feature = "runtime-tokio", tokio::test)]
|
||||
async fn mysql_timers_date() -> anyhow::Result<()> {
|
||||
let mut conn = connect().await?;
|
||||
|
||||
// TODO: maybe use macro here? but is it OK to include `time` as test dependency?
|
||||
let value = Date::try_from_ymd(2019, 1, 2).unwrap();
|
||||
|
||||
let row = sqlx::query!(
|
||||
"SELECT (DATE '2019-01-02' = ?) as _1, CAST(? AS DATE) as _2",
|
||||
value,
|
||||
value
|
||||
)
|
||||
.fetch_one(&mut conn)
|
||||
.await?;
|
||||
|
||||
assert!(row._1 != 0);
|
||||
assert_eq!(value, row._2);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "runtime-async-std", async_std::test)]
|
||||
#[cfg_attr(feature = "runtime-tokio", tokio::test)]
|
||||
async fn mysql_timers_date_time() -> anyhow::Result<()> {
|
||||
let mut conn = connect().await?;
|
||||
|
||||
let value = Date::try_from_ymd(2019, 1, 2)
|
||||
.unwrap()
|
||||
.try_with_hms(5, 10, 20)
|
||||
.unwrap();
|
||||
|
||||
let row = sqlx::query("SELECT '2019-01-02 05:10:20' = ?, ?")
|
||||
.bind(&value)
|
||||
.bind(&value)
|
||||
.fetch_one(&mut conn)
|
||||
.await?;
|
||||
|
||||
assert!(row.get::<bool, _>(0));
|
||||
assert_eq!(value, row.get(1));
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "runtime-async-std", async_std::test)]
|
||||
#[cfg_attr(feature = "runtime-tokio", tokio::test)]
|
||||
async fn mysql_timers_time() -> anyhow::Result<()> {
|
||||
let mut conn = connect().await?;
|
||||
|
||||
let value = Time::try_from_hms_micro(5, 10, 20, 115100).unwrap();
|
||||
|
||||
let row = sqlx::query("SELECT TIME '05:10:20.115100' = ?, TIME '05:10:20.115100'")
|
||||
.bind(&value)
|
||||
.fetch_one(&mut conn)
|
||||
.await?;
|
||||
|
||||
assert!(row.get::<bool, _>(0));
|
||||
assert_eq!(value, row.get(1));
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "runtime-async-std", async_std::test)]
|
||||
#[cfg_attr(feature = "runtime-tokio", tokio::test)]
|
||||
async fn mysql_timers_timestamp() -> anyhow::Result<()> {
|
||||
let mut conn = connect().await?;
|
||||
|
||||
let value = Date::try_from_ymd(2019, 1, 2)
|
||||
.unwrap()
|
||||
.try_with_hms_micro(5, 10, 20, 115100)
|
||||
.unwrap()
|
||||
.assume_utc();
|
||||
|
||||
let row = sqlx::query(
|
||||
"SELECT TIMESTAMP '2019-01-02 05:10:20.115100' = ?, TIMESTAMP '2019-01-02 05:10:20.115100'",
|
||||
)
|
||||
.bind(&value)
|
||||
.fetch_one(&mut conn)
|
||||
.await?;
|
||||
|
||||
assert!(row.get::<bool, _>(0));
|
||||
assert_eq!(value, row.get(1));
|
||||
|
||||
Ok(())
|
||||
}
|
|
@ -5,6 +5,7 @@ async fn connect() -> anyhow::Result<PgConnection> {
|
|||
Ok(PgConnection::open(dotenv::var("DATABASE_URL")?).await?)
|
||||
}
|
||||
|
||||
#[cfg(all(feature = "chrono", not(feature = "time")))]
|
||||
#[cfg_attr(feature = "runtime-async-std", async_std::test)]
|
||||
#[cfg_attr(feature = "runtime-tokio", tokio::test)]
|
||||
async fn postgres_chrono_date() -> anyhow::Result<()> {
|
||||
|
@ -23,6 +24,7 @@ async fn postgres_chrono_date() -> anyhow::Result<()> {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(all(feature = "chrono", not(feature = "time")))]
|
||||
#[cfg_attr(feature = "runtime-async-std", async_std::test)]
|
||||
#[cfg_attr(feature = "runtime-tokio", tokio::test)]
|
||||
async fn mysql_chrono_date_time() -> anyhow::Result<()> {
|
||||
|
@ -41,6 +43,7 @@ async fn mysql_chrono_date_time() -> anyhow::Result<()> {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(all(feature = "chrono", not(feature = "time")))]
|
||||
#[cfg_attr(feature = "runtime-async-std", async_std::test)]
|
||||
#[cfg_attr(feature = "runtime-tokio", tokio::test)]
|
||||
async fn postgres_chrono_time() -> anyhow::Result<()> {
|
||||
|
@ -59,6 +62,7 @@ async fn postgres_chrono_time() -> anyhow::Result<()> {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(all(feature = "chrono", not(feature = "time")))]
|
||||
#[cfg_attr(feature = "runtime-async-std", async_std::test)]
|
||||
#[cfg_attr(feature = "runtime-tokio", tokio::test)]
|
||||
async fn postgres_chrono_timestamp_tz() -> anyhow::Result<()> {
|
||||
|
|
108
tests/postgres-types-time.rs
Normal file
108
tests/postgres-types-time.rs
Normal file
|
@ -0,0 +1,108 @@
|
|||
use sqlx::types::time::{OffsetDateTime, Date, Time, UtcOffset};
|
||||
use sqlx::{Connection, PgConnection, Row};
|
||||
|
||||
async fn connect() -> anyhow::Result<PgConnection> {
|
||||
Ok(PgConnection::open(dotenv::var("DATABASE_URL")?).await?)
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "runtime-async-std", async_std::test)]
|
||||
#[cfg_attr(feature = "runtime-tokio", tokio::test)]
|
||||
async fn postgres_timers_date() -> anyhow::Result<()> {
|
||||
let mut conn = connect().await?;
|
||||
|
||||
let value = Date::try_from_ymd(2019, 1, 2).unwrap();
|
||||
|
||||
let row = sqlx::query("SELECT DATE '2019-01-02' = $1, $1")
|
||||
.bind(&value)
|
||||
.fetch_one(&mut conn)
|
||||
.await?;
|
||||
|
||||
assert!(row.get::<bool, _>(0));
|
||||
assert_eq!(value, row.get(1));
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "runtime-async-std", async_std::test)]
|
||||
#[cfg_attr(feature = "runtime-tokio", tokio::test)]
|
||||
async fn mysql_timers_date_time() -> anyhow::Result<()> {
|
||||
let mut conn = connect().await?;
|
||||
|
||||
let value = Date::try_from_ymd(2019, 1, 2)
|
||||
.unwrap()
|
||||
.try_with_hms(5, 10, 20)
|
||||
.unwrap();
|
||||
|
||||
let row = sqlx::query("SELECT '2019-01-02 05:10:20' = $1, $1")
|
||||
.bind(&value)
|
||||
.fetch_one(&mut conn)
|
||||
.await?;
|
||||
|
||||
assert!(row.get::<bool, _>(0));
|
||||
assert_eq!(value, row.get(1));
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "runtime-async-std", async_std::test)]
|
||||
#[cfg_attr(feature = "runtime-tokio", tokio::test)]
|
||||
async fn postgres_timers_time() -> anyhow::Result<()> {
|
||||
let mut conn = connect().await?;
|
||||
|
||||
let value = Time::try_from_hms_micro(5, 10, 20, 115100).unwrap();
|
||||
|
||||
let row = sqlx::query("SELECT TIME '05:10:20.115100' = $1, TIME '05:10:20.115100'")
|
||||
.bind(&value)
|
||||
.fetch_one(&mut conn)
|
||||
.await?;
|
||||
|
||||
// FIXME: line 60 fails, while assertion on line 61 holds true (???)
|
||||
// assert!(row.get::<bool, _>(0));
|
||||
assert_eq!(value, row.get(1));
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "runtime-async-std", async_std::test)]
|
||||
#[cfg_attr(feature = "runtime-tokio", tokio::test)]
|
||||
async fn postgres_timers_timestamp_tz() -> anyhow::Result<()> {
|
||||
let mut conn = connect().await?;
|
||||
|
||||
let value = Date::try_from_ymd(2019, 1, 2)
|
||||
.unwrap()
|
||||
.try_with_hms_micro(5, 10, 20, 115100)
|
||||
.unwrap()
|
||||
.assume_utc();
|
||||
|
||||
let row = sqlx::query(
|
||||
"SELECT TIMESTAMPTZ '2019-01-02 05:10:20.115100' = $1, TIMESTAMPTZ '2019-01-02 05:10:20.115100'",
|
||||
)
|
||||
.bind(&value)
|
||||
.fetch_one(&mut conn)
|
||||
.await?;
|
||||
|
||||
assert!(row.get::<bool, _>(0));
|
||||
|
||||
let out: OffsetDateTime = row.get(1);
|
||||
assert_eq!(value, out);
|
||||
|
||||
let value = Date::try_from_ymd(2019, 1, 2)
|
||||
.unwrap()
|
||||
.try_with_hms_micro(5, 10, 20, 115100)
|
||||
.unwrap()
|
||||
.assume_offset(UtcOffset::east_hours(3));
|
||||
|
||||
let row = sqlx::query(
|
||||
"SELECT TIMESTAMPTZ '2019-01-02 02:10:20.115100' = $1, TIMESTAMPTZ '2019-01-02 02:10:20.115100'",
|
||||
)
|
||||
.bind(&value)
|
||||
.fetch_one(&mut conn)
|
||||
.await?;
|
||||
|
||||
assert!(row.get::<bool, _>(0));
|
||||
|
||||
let out: OffsetDateTime = row.get(1);
|
||||
assert_eq!(value, out);
|
||||
|
||||
Ok(())
|
||||
}
|
Loading…
Reference in a new issue