From 084add9cdbe9056481990dd614409694467c3596 Mon Sep 17 00:00:00 2001 From: Vlad Stepanov <8uk.8ak@gmail.com> Date: Sat, 29 Feb 2020 22:38:48 +0300 Subject: [PATCH 1/9] #115: implement time-rs support --- .github/workflows/rust.yml | 96 +++++++-- Cargo.lock | 33 ++++ Cargo.toml | 13 +- README.md | 26 +-- sqlx-core/Cargo.toml | 3 +- sqlx-core/src/mysql/types/mod.rs | 3 + sqlx-core/src/mysql/types/time.rs | 276 ++++++++++++++++++++++++++ sqlx-core/src/postgres/types/mod.rs | 3 + sqlx-core/src/postgres/types/time.rs | 286 +++++++++++++++++++++++++++ sqlx-core/src/types.rs | 6 + sqlx-macros/Cargo.toml | 1 + sqlx-macros/src/database/mysql.rs | 20 +- sqlx-macros/src/database/postgres.rs | 20 +- tests/mysql-types-chrono.rs | 4 + tests/mysql-types-time.rs | 92 +++++++++ tests/postgres-types-chrono.rs | 4 + tests/postgres-types-time.rs | 108 ++++++++++ 17 files changed, 958 insertions(+), 36 deletions(-) create mode 100644 sqlx-core/src/mysql/types/time.rs create mode 100644 sqlx-core/src/postgres/types/time.rs create mode 100644 tests/mysql-types-time.rs create mode 100644 tests/postgres-types-time.rs diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index ab6da813..f0e38df3 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -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 diff --git a/Cargo.lock b/Cargo.lock index 4f654905..ba41cdf9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -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" diff --git a/Cargo.toml b/Cargo.toml index fff12f67..af92747f 100644 --- a/Cargo.toml +++ b/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" ] diff --git a/README.md b/README.md index 1b2346c5..f77de00a 100644 --- a/README.md +++ b/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 diff --git a/sqlx-core/Cargo.toml b/sqlx-core/Cargo.toml index 0cf72e78..3c435f76 100644 --- a/sqlx-core/Cargo.toml +++ b/sqlx-core/Cargo.toml @@ -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 } diff --git a/sqlx-core/src/mysql/types/mod.rs b/sqlx-core/src/mysql/types/mod.rs index 39e8252b..50b2141c 100644 --- a/sqlx-core/src/mysql/types/mod.rs +++ b/sqlx-core/src/mysql/types/mod.rs @@ -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; diff --git a/sqlx-core/src/mysql/types/time.rs b/sqlx-core/src/mysql/types/time.rs new file mode 100644 index 00000000..201ee58a --- /dev/null +++ b/sqlx-core/src/mysql/types/time.rs @@ -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 for MySql { + fn type_info() -> MySqlTypeInfo { + MySqlTypeInfo::new(TypeId::TIMESTAMP) + } +} + +impl Encode for OffsetDateTime { + fn encode(&self, buf: &mut Vec) { + let utc_dt = self.to_offset(UtcOffset::UTC); + let primitive_dt = PrimitiveDateTime::new(utc_dt.date(), utc_dt.time()); + + Encode::::encode(&primitive_dt, buf); + } +} + +impl Decode for OffsetDateTime { + fn decode(buf: &[u8]) -> Result { + let primitive: PrimitiveDateTime = Decode::::decode(buf)?; + + Ok(primitive.assume_utc()) + } +} + +impl HasSqlType