From 757a930e21860ab402b5afdf210c5f2a542b1b1d Mon Sep 17 00:00:00 2001 From: Ryan Leckey Date: Tue, 26 May 2020 01:56:03 -0700 Subject: [PATCH] refactor(core): remove the HRTB (higher rank trait bound) on Row in the aim of improving ergonomics * removes the lifetime from Row * removes MySqlQueryAs, SqliteQueryAs, etc. (no longer needed) * introduce query_scalar * introduce Decode::accepts to allow overriding runtime type checking per-type (replaces TypeInfo::compatible) * introduce Encode::produces to allow overriding the encoded type per-value * adds a lifetime to Arguments (and introduce the HRTB HasArguments) to support zero-copy encoding with SQLite * renames Database::RawBuffer to HasArguments::ArgumentBuffer * introduce Connect::connect_with to provide an ConnectOptions type explicitly to opt-out of connection string parsing * introduce Value and ValueRef traits to allow decoding-deferred extraction of values from Rows * introduce Encode::encode_by_ref and change Encode::encode to take by-value to try and re-use memory where possible * use thiserror to generate sqlx::Error * [!] temporarily removes query logging * [!] temporarily removes transactions --- Cargo.lock | 543 ++++++++++++++++++++++------ sqlx-core/Cargo.toml | 78 ++-- sqlx-core/src/arguments.rs | 16 +- sqlx-core/src/connection.rs | 125 ++----- sqlx-core/src/cursor.rs | 89 ----- sqlx-core/src/database.rs | 78 ++-- sqlx-core/src/decode.rs | 40 +- sqlx-core/src/describe.rs | 91 ++--- sqlx-core/src/encode.rs | 97 +++-- sqlx-core/src/error.rs | 433 +++++----------------- sqlx-core/src/executor.rs | 240 ++++++------ sqlx-core/src/ext/mod.rs | 1 + sqlx-core/src/ext/ustr.rs | 76 ++++ sqlx-core/src/from_row.rs | 116 ++++++ sqlx-core/src/io/buf.rs | 174 +++------ sqlx-core/src/io/buf_mut.rs | 85 +---- sqlx-core/src/io/buf_stream.rs | 220 ++++------- sqlx-core/src/io/decode.rs | 29 ++ sqlx-core/src/io/encode.rs | 16 + sqlx-core/src/io/mod.rs | 36 +- sqlx-core/src/io/write_and_flush.rs | 45 +++ sqlx-core/src/lib.rs | 68 ++-- sqlx-core/src/net/mod.rs | 5 + sqlx-core/src/net/socket.rs | 140 +++++++ sqlx-core/src/net/tls.rs | 197 ++++++++++ sqlx-core/src/pool/connection.rs | 22 +- sqlx-core/src/pool/executor.rs | 116 ------ sqlx-core/src/pool/inner.rs | 29 +- sqlx-core/src/pool/mod.rs | 26 +- sqlx-core/src/pool/options.rs | 7 +- sqlx-core/src/query.rs | 341 +++++++++-------- sqlx-core/src/query_as.rs | 295 +++++++-------- sqlx-core/src/query_scalar.rs | 132 +++++++ sqlx-core/src/row.rs | 362 +++++-------------- sqlx-core/src/runtime.rs | 34 -- sqlx-core/src/transaction.rs | 225 ------------ sqlx-core/src/type_info.rs | 3 + sqlx-core/src/types.rs | 146 -------- sqlx-core/src/types/json.rs | 90 +++++ sqlx-core/src/types/mod.rs | 69 ++++ sqlx-core/src/url.rs | 147 -------- sqlx-core/src/value.rs | 128 ++++++- sqlx-rt/src/lib.rs | 7 +- 43 files changed, 2549 insertions(+), 2668 deletions(-) delete mode 100644 sqlx-core/src/cursor.rs create mode 100644 sqlx-core/src/ext/mod.rs create mode 100644 sqlx-core/src/ext/ustr.rs create mode 100644 sqlx-core/src/from_row.rs create mode 100644 sqlx-core/src/io/decode.rs create mode 100644 sqlx-core/src/io/encode.rs create mode 100644 sqlx-core/src/io/write_and_flush.rs create mode 100644 sqlx-core/src/net/mod.rs create mode 100644 sqlx-core/src/net/socket.rs create mode 100644 sqlx-core/src/net/tls.rs delete mode 100644 sqlx-core/src/pool/executor.rs create mode 100644 sqlx-core/src/query_scalar.rs delete mode 100644 sqlx-core/src/runtime.rs delete mode 100644 sqlx-core/src/transaction.rs create mode 100644 sqlx-core/src/type_info.rs delete mode 100644 sqlx-core/src/types.rs create mode 100644 sqlx-core/src/types/json.rs create mode 100644 sqlx-core/src/types/mod.rs diff --git a/Cargo.lock b/Cargo.lock index 5752a3b0..6a882762 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1,5 +1,51 @@ # This file is automatically @generated by Cargo. # It is not intended for manual editing. +[[package]] +name = "actix-macros" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a60f9ba7c4e6df97f3aacb14bb5c0cd7d98a49dcbaed0d7f292912ad9a6a3ed2" +dependencies = [ + "quote", + "syn", +] + +[[package]] +name = "actix-rt" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "143fcc2912e0d1de2bcf4e2f720d2a60c28652ab4179685a1ee159e0fb3db227" +dependencies = [ + "actix-macros", + "actix-threadpool", + "copyless", + "futures-channel", + "futures-util", + "smallvec 1.4.0", + "tokio 0.2.21", +] + +[[package]] +name = "actix-threadpool" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91164716d956745c79dcea5e66d2aa04506549958accefcede5368c70f2fd4ff" +dependencies = [ + "derive_more", + "futures-channel", + "lazy_static", + "log", + "num_cpus", + "parking_lot 0.10.2", + "threadpool", +] + +[[package]] +name = "ahash" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f3e0bf23f51883cce372d5d5892211236856e4bb37fb942e1eb135ee0f146e3" + [[package]] name = "aho-corasick" version = "0.7.10" @@ -61,35 +107,32 @@ dependencies = [ "async-std", "native-tls", "thiserror", - "tokio 0.2.13", "url 2.1.1", ] [[package]] name = "async-std" -version = "1.5.0" +version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "538ecb01eb64eecd772087e5b6f7540cbc917f047727339a472dafed2185b267" +checksum = "a45cee2749d880d7066e328a7e161c7470ced883b2fd000ca4643e9f1dd5083a" dependencies = [ "async-attributes", "async-task", - "broadcaster", - "crossbeam-channel", - "crossbeam-deque", "crossbeam-utils 0.7.2", + "futures-channel", "futures-core", "futures-io", "futures-timer", "kv-log-macro", "log", "memchr", - "mio", - "mio-uds", "num_cpus", "once_cell", "pin-project-lite", "pin-utils", "slab", + "smol", + "wasm-bindgen-futures", ] [[package]] @@ -115,13 +158,9 @@ dependencies = [ [[package]] name = "async-task" -version = "1.3.1" +version = "3.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ac2c016b079e771204030951c366db398864f5026f84a44dafb0ff20f02085d" -dependencies = [ - "libc", - "winapi 0.3.8", -] +checksum = "c17772156ef2829aadc587461c7753af20b7e8db1529bc66855add962a3b35d3" [[package]] name = "async-trait" @@ -134,6 +173,15 @@ dependencies = [ "syn", ] +[[package]] +name = "atoi" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e0afb7287b68575f5ca0e5c7e40191cbd4be59d325781f46faa603e176eaef47" +dependencies = [ + "num-traits", +] + [[package]] name = "atty" version = "0.2.14" @@ -249,20 +297,6 @@ dependencies = [ "byte-tools", ] -[[package]] -name = "broadcaster" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d9c972e21e0d055a36cf73e4daae870941fe7a8abcd5ac3396aab9e4c126bd87" -dependencies = [ - "futures-channel", - "futures-core", - "futures-sink", - "futures-util", - "parking_lot 0.10.0", - "slab", -] - [[package]] name = "bumpalo" version = "3.2.1" @@ -402,6 +436,12 @@ dependencies = [ "url 1.7.2", ] +[[package]] +name = "copyless" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ff9c56c9fb2a49c05ef0e431485a22400af20d33226dc0764d891d09e724127" + [[package]] name = "core-foundation" version = "0.7.0" @@ -418,6 +458,20 @@ version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b3a71ab494c0b5b860bdc8407ae08978052417070c2ced38573a9157ad75b8ac" +[[package]] +name = "crossbeam" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69323bff1fb41c635347b8ead484a5ca6c3f11914d784170b158d8449ab07f8e" +dependencies = [ + "cfg-if", + "crossbeam-channel", + "crossbeam-deque", + "crossbeam-epoch", + "crossbeam-queue", + "crossbeam-utils 0.7.2", +] + [[package]] name = "crossbeam-channel" version = "0.4.2" @@ -501,6 +555,17 @@ version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "11c0346158a19b3627234e15596f5e465c360fcdb97d817bcb255e0510f5a788" +[[package]] +name = "derive_more" +version = "0.99.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2127768764f1556535c01b5326ef94bd60ff08dcfbdc544d53e69ed155610f5d" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "dialoguer" version = "0.5.0" @@ -634,9 +699,9 @@ dependencies = [ [[package]] name = "futures-channel" -version = "0.3.4" +version = "0.3.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0c77d04ce8edd9cb903932b608268b3fffec4163dc053b3b402bf47eac1f1a8" +checksum = "f366ad74c28cca6ba456d95e6422883cfb4b252a83bed929c83abfdbbf2967d5" dependencies = [ "futures-core", "futures-sink", @@ -644,9 +709,9 @@ dependencies = [ [[package]] name = "futures-core" -version = "0.3.4" +version = "0.3.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f25592f769825e89b92358db00d26f965761e094951ac44d3663ef25b7ac464a" +checksum = "59f5fff90fd5d971f936ad674802482ba441b6f09ba5e15fd8b39145582ca399" [[package]] name = "futures-cpupool" @@ -671,15 +736,15 @@ dependencies = [ [[package]] name = "futures-io" -version = "0.3.4" +version = "0.3.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a638959aa96152c7a4cddf50fcb1e3fede0583b27157c26e67d6f99904090dc6" +checksum = "de27142b013a8e869c14957e6d2edeef89e97c289e69d042ee3a49acd8b51789" [[package]] name = "futures-macro" -version = "0.3.4" +version = "0.3.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a5081aa3de1f7542a794a397cde100ed903b0630152d0973479018fd85423a7" +checksum = "d0b5a30a4328ab5473878237c447333c093297bded83a4983d10f4deea240d39" dependencies = [ "proc-macro-hack", "proc-macro2", @@ -689,27 +754,34 @@ dependencies = [ [[package]] name = "futures-sink" -version = "0.3.4" +version = "0.3.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3466821b4bc114d95b087b850a724c6f83115e929bc88f1fa98a3304a944c8a6" +checksum = "3f2032893cb734c7a05d85ce0cc8b8c4075278e93b24b66f9de99d6eb0fa8acc" [[package]] name = "futures-task" -version = "0.3.4" +version = "0.3.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b0a34e53cf6cdcd0178aa573aed466b646eb3db769570841fda0c7ede375a27" +checksum = "bdb66b5f09e22019b1ab0830f7785bcea8e7a42148683f99214f73f8ec21a626" +dependencies = [ + "once_cell", +] [[package]] name = "futures-timer" -version = "2.0.2" +version = "3.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1de7508b218029b0f01662ed8f61b1c964b3ae99d6f25462d0f55a595109df6" +checksum = "e64b03909df88034c26dc1547e8970b91f98bdb65165d6a4e9110d94263dbb2c" +dependencies = [ + "gloo-timers", + "send_wrapper", +] [[package]] name = "futures-util" -version = "0.3.4" +version = "0.3.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22766cf25d64306bedf0384da004d05c9974ab104fcc4528f1236181c18004c5" +checksum = "8764574ff08b701a084482c3c7031349104b07ac897393010494beaa18ce32c6" dependencies = [ "futures 0.1.29", "futures-channel", @@ -719,6 +791,7 @@ dependencies = [ "futures-sink", "futures-task", "memchr", + "pin-project", "pin-utils", "proc-macro-hack", "proc-macro-nested", @@ -752,6 +825,19 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b919933a397b79c37e33b77bb2aa3dc8eb6e165ad809e58ff75bc7db2e34574" +[[package]] +name = "gloo-timers" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47204a46aaff920a1ea58b11d03dec6f704287d27561724a4631e450654a891f" +dependencies = [ + "futures-channel", + "futures-core", + "js-sys", + "wasm-bindgen", + "web-sys", +] + [[package]] name = "h2" version = "0.1.26" @@ -770,6 +856,16 @@ dependencies = [ "tokio-io", ] +[[package]] +name = "hashbrown" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96282e96bfcd3da0d3aa9938bedf1e50df3269b6db08b4876d2da0bb1a0841cf" +dependencies = [ + "ahash", + "autocfg", +] + [[package]] name = "heck" version = "0.3.1" @@ -958,6 +1054,15 @@ version = "0.4.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b8b7a7c0c47db5545ed3fef7468ee7bb5b74691498139e4b3f6a20685dc6dd8e" +[[package]] +name = "js-sys" +version = "0.3.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce10c23ad2ea25ceca0093bd3192229da4c5b3c0f2de499c1ecac0d98d452177" +dependencies = [ + "wasm-bindgen", +] + [[package]] name = "jsonwebtoken" version = "6.0.1" @@ -1000,15 +1105,15 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" [[package]] name = "libc" -version = "0.2.68" +version = "0.2.71" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dea0c0405123bba743ee3f91f49b1c7cfb684eef0da0a50110f758ccf24cdff0" +checksum = "9457b06509d27052635f90d6466700c65095fdf75409b3fbdd903e988b886f49" [[package]] name = "libsqlite3-sys" -version = "0.17.1" +version = "0.17.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "266eb8c361198e8d1f682bc974e5d9e2ae90049fb1943890904d11dad7d4a77d" +checksum = "56d90181c2904c287e5390186be820e5ef311a3c62edebb7d6ca3d6a48ce041d" dependencies = [ "cc", "pkg-config", @@ -1017,9 +1122,9 @@ dependencies = [ [[package]] name = "lock_api" -version = "0.3.3" +version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "79b2de95ecb4691949fea4716ca53cdbcfccb2c612e19644a8bad05edcf9f47b" +checksum = "c4da24a77a3d8a6d4862d95f72e6fdb9c09a643ecdb402d754004a557f2bec75" dependencies = [ "scopeguard", ] @@ -1176,6 +1281,19 @@ dependencies = [ "winapi 0.3.8", ] +[[package]] +name = "nix" +version = "0.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50e4785f2c3b7589a0d0c1dd60285e1188adac4006e8abd6dd578e1567027363" +dependencies = [ + "bitflags", + "cc", + "cfg-if", + "libc", + "void", +] + [[package]] name = "num-bigint" version = "0.2.6" @@ -1274,12 +1392,12 @@ dependencies = [ [[package]] name = "parking_lot" -version = "0.10.0" +version = "0.10.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92e98c49ab0b7ce5b222f2cc9193fc4efe11c6d0bd4f648e374684a6857b1cfc" +checksum = "d3a704eb390aafdc107b0e392f56a82b668e3a71366993b5340f5833fd62505e" dependencies = [ "lock_api", - "parking_lot_core 0.7.0", + "parking_lot_core 0.7.2", ] [[package]] @@ -1299,15 +1417,15 @@ dependencies = [ [[package]] name = "parking_lot_core" -version = "0.7.0" +version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7582838484df45743c8434fbff785e8edf260c28748353d44bc0da32e0ceabf1" +checksum = "d58c7c768d4ba344e3e8d72518ac13e259d7c7ade24167003b8488e10b6740a3" dependencies = [ "cfg-if", "cloudabi", "libc", "redox_syscall", - "smallvec 1.2.0", + "smallvec 1.4.0", "winapi 0.3.8", ] @@ -1372,6 +1490,70 @@ version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d4fd5641d01c8f18a23da7b6fe29298ff4b55afcccdf78973b24cf3175fee32e" +[[package]] +name = "phf" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3dfb61232e34fcb633f43d12c58f83c1df82962dcdfa565a4e866ffc17dafe12" +dependencies = [ + "phf_macros", + "phf_shared", + "proc-macro-hack", +] + +[[package]] +name = "phf_generator" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "17367f0cc86f2d25802b2c26ee58a7b23faeccf78a396094c13dced0d0182526" +dependencies = [ + "phf_shared", + "rand", +] + +[[package]] +name = "phf_macros" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f6fde18ff429ffc8fe78e2bf7f8b7a5a5a6e2a8b58bc5a9ac69198bbda9189c" +dependencies = [ + "phf_generator", + "phf_shared", + "proc-macro-hack", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "phf_shared" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c00cf8b9eafe68dde5e9eaa2cef8ee84a9336a47d566ec55ca16589633b65af7" +dependencies = [ + "siphasher", +] + +[[package]] +name = "pin-project" +version = "0.4.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "edc93aeee735e60ecb40cf740eb319ff23eab1c5748abfdb5c180e4ce49f7791" +dependencies = [ + "pin-project-internal", +] + +[[package]] +name = "pin-project-internal" +version = "0.4.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e58db2081ba5b4c93bd6be09c40fd36cb9193a8336c384f3b40012e531aa7e40" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "pin-project-lite" version = "0.1.4" @@ -1380,9 +1562,21 @@ checksum = "237844750cfbb86f67afe27eee600dfbbcb6188d734139b534cbfbf4f96792ae" [[package]] name = "pin-utils" -version = "0.1.0-alpha.4" +version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5894c618ce612a3fa23881b152b608bafb8c56cfc22f434a3ba3120b40f7b587" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + +[[package]] +name = "piper" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6b0deb65f46e873ba8aa7c6a8dbe3f23cb1bf59c339a81a1d56361dde4d66ac8" +dependencies = [ + "crossbeam-utils 0.7.2", + "futures-io", + "futures-sink", + "futures-util", +] [[package]] name = "pkg-config" @@ -1436,9 +1630,9 @@ checksum = "8e946095f9d3ed29ec38de908c22f95d9ac008e424c7bcae54c75a79c527c694" [[package]] name = "proc-macro2" -version = "1.0.10" +version = "1.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df246d292ff63439fea9bc8c0a270bed0e390d5ebd4db4ba15aba81111b5abe3" +checksum = "1502d12e458c49a4c9cbff560d0fe0060c252bc29799ed94ca2ed4bb665a0101" dependencies = [ "unicode-xid", ] @@ -1469,6 +1663,7 @@ dependencies = [ "rand_chacha", "rand_core", "rand_hc", + "rand_pcg", ] [[package]] @@ -1499,6 +1694,15 @@ dependencies = [ "rand_core", ] +[[package]] +name = "rand_pcg" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "16abd0c1b639e9eb4d7c50c0b8100b0d0f849be2349829c740fe8e6eb4816429" +dependencies = [ + "rand_core", +] + [[package]] name = "redox_syscall" version = "0.1.56" @@ -1579,17 +1783,6 @@ dependencies = [ "semver", ] -[[package]] -name = "rustversion" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b3bba175698996010c4f6dce5e7f173b6eb781fce25d2cfc45e27091ce0b79f6" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - [[package]] name = "ryu" version = "1.0.3" @@ -1606,6 +1799,12 @@ dependencies = [ "winapi 0.3.8", ] +[[package]] +name = "scoped-tls-hkt" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2e9d7eaddb227e8fbaaa71136ae0e1e913ca159b86c7da82f3e8f0044ad3a63" + [[package]] name = "scopeguard" version = "1.1.0" @@ -1651,6 +1850,12 @@ version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3" +[[package]] +name = "send_wrapper" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f638d531eccd6e23b980caf34876660d38e265409d8e99b397ab71eb3612fad0" + [[package]] name = "serde" version = "1.0.110" @@ -1735,6 +1940,12 @@ dependencies = [ "libc", ] +[[package]] +name = "siphasher" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa8f3741c7372e75519bd9346068370c9cdaabcc1f9599cbcf2a2719352286b7" + [[package]] name = "slab" version = "0.4.2" @@ -1752,15 +1963,34 @@ dependencies = [ [[package]] name = "smallvec" -version = "1.2.0" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c2fb2ec9bcd216a5b0d0ccf31ab17b5ed1d627960edff65bbe95d3ce221cefc" +checksum = "c7cb5678e1615754284ec264d9bb5b4c27d2018577fd90ac0ceb578591ed5ee4" + +[[package]] +name = "smol" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "686c634ad1873fffef6aed20f180eede424fbf3bb31802394c90fd7335a661b7" +dependencies = [ + "async-task", + "crossbeam", + "futures-io", + "futures-util", + "nix", + "once_cell", + "piper", + "scoped-tls-hkt", + "slab", + "socket2", + "wepoll-binding", +] [[package]] name = "socket2" -version = "0.3.11" +version = "0.3.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e8b74de517221a2cb01a53349cf54182acdc31a074727d3079068448c0676d85" +checksum = "03088793f677dce356f3ccc2edb1b314ad191ab702a5de3faf49304f7e104918" dependencies = [ "cfg-if", "libc", @@ -1800,8 +2030,8 @@ dependencies = [ "sqlx-core", "sqlx-macros", "sqlx-test", - "time 0.2.9", - "tokio 0.2.13", + "time 0.2.16", + "tokio 0.2.21", "trybuild", ] @@ -1822,7 +2052,7 @@ dependencies = [ "serde_json", "sqlx", "structopt", - "tokio 0.2.13", + "tokio 0.2.21", "url 2.1.1", ] @@ -1830,41 +2060,51 @@ dependencies = [ name = "sqlx-core" version = "0.3.5" dependencies = [ - "async-native-tls", - "async-std", "async-stream", + "atoi", "base64 0.12.0", "bigdecimal", "bitflags", "byteorder", + "bytes 0.5.4", "chrono", + "crossbeam-channel", "crossbeam-queue", "crossbeam-utils 0.7.2", "digest", + "either", "futures-channel", "futures-core", "futures-util", "generic-array", + "hashbrown", "hex", "hmac", "ipnetwork", + "itoa", "libc", "libsqlite3-sys", "log", "md-5", "memchr", "num-bigint", + "parking_lot 0.10.2", "percent-encoding 2.1.0", + "phf", "rand", "serde", "serde_json", "sha-1", "sha2", + "smallvec 1.4.0", "sqlformat", - "time 0.2.9", - "tokio 0.2.13", + "sqlx-rt", + "thiserror", + "threadpool", + "time 0.2.16", "url 2.1.1", "uuid", + "whoami", ] [[package]] @@ -1956,10 +2196,23 @@ dependencies = [ "sha2", "sqlx-core", "syn", - "tokio 0.2.13", + "tokio 0.2.21", "url 2.1.1", ] +[[package]] +name = "sqlx-rt" +version = "0.1.0-pre" +dependencies = [ + "actix-rt", + "actix-threadpool", + "async-native-tls", + "async-std", + "native-tls", + "tokio 0.2.21", + "tokio-native-tls", +] + [[package]] name = "sqlx-test" version = "0.1.0" @@ -1969,14 +2222,14 @@ dependencies = [ "dotenv", "env_logger", "sqlx", - "tokio 0.2.13", + "tokio 0.2.21", ] [[package]] name = "standback" -version = "0.2.1" +version = "0.2.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4edf667ea8f60afc06d6aeec079d20d5800351109addec1faea678a8663da4e1" +checksum = "47e4b8c631c998468961a9ea159f064c5c8499b95b5e4a34b77849d45949d540" [[package]] name = "stdweb" @@ -2074,9 +2327,9 @@ checksum = "2d67a5a62ba6e01cb2192ff309324cb4875d0c451d55fe2319433abe7a05a8ee" [[package]] name = "syn" -version = "1.0.17" +version = "1.0.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0df0eb663f387145cab623dea85b09c2c5b4b0aef44e945d928e682fce71bb03" +checksum = "bb37da98a55b1d08529362d9cbb863be17556873df2585904ab9d2bc951291d0" dependencies = [ "proc-macro2", "quote", @@ -2137,18 +2390,18 @@ dependencies = [ [[package]] name = "thiserror" -version = "1.0.14" +version = "1.0.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0570dc61221295909abdb95c739f2e74325e14293b2026b0a7e195091ec54ae" +checksum = "b13f926965ad00595dd129fa12823b04bbf866e9085ab0a5f2b05b850fbfc344" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.14" +version = "1.0.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "227362df41d566be41a28f64401e07a043157c21c14b9785a0d8e256f940a8fd" +checksum = "893582086c2f98cde18f906265a65b5030a074b1046c674ae898be6519a7f479" dependencies = [ "proc-macro2", "quote", @@ -2164,6 +2417,15 @@ dependencies = [ "lazy_static", ] +[[package]] +name = "threadpool" +version = "1.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2f0c90a5f3459330ac8bc0d2f879c693bb7a2f59689c1083fc4ef83834da865" +dependencies = [ + "num_cpus", +] + [[package]] name = "tide" version = "0.6.0" @@ -2198,16 +2460,16 @@ dependencies = [ [[package]] name = "time" -version = "0.2.9" +version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6329a7835505d46f5f3a9a2c237f8d6bf5ca6f0015decb3698ba57fcdbb609ba" +checksum = "3a51cadc5b1eec673a685ff7c33192ff7b7603d0b75446fb354939ee615acb15" dependencies = [ "cfg-if", "libc", - "rustversion", "standback", "stdweb", "time-macros", + "version_check", "winapi 0.3.8", ] @@ -2253,9 +2515,9 @@ dependencies = [ [[package]] name = "tokio" -version = "0.2.13" +version = "0.2.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fa5e81d6bc4e67fe889d5783bd2a128ab2e0cfa487e0be16b6a8d177b101616" +checksum = "d099fa27b9702bed751524694adbe393e18b36b204da91eb1cbbbbb4a5ee2d58" dependencies = [ "bytes 0.5.4", "fnv", @@ -2328,6 +2590,16 @@ dependencies = [ "syn", ] +[[package]] +name = "tokio-native-tls" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd608593a919a8e05a7d1fc6df885e40f6a88d3a70a3a7eff23ff27964eda069" +dependencies = [ + "native-tls", + "tokio 0.2.21", +] + [[package]] name = "tokio-reactor" version = "0.1.12" @@ -2450,7 +2722,7 @@ version = "0.1.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5479532badd04e128284890390c1e876ef7a993d0570b3597ae43dfa1d59afa4" dependencies = [ - "smallvec 1.2.0", + "smallvec 1.4.0", ] [[package]] @@ -2523,6 +2795,12 @@ version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "078775d0255232fb988e6fccf26ddc9d1ac274299aaedcedce21c6f72cc533ce" +[[package]] +name = "void" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d" + [[package]] name = "want" version = "0.2.0" @@ -2542,9 +2820,9 @@ checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519" [[package]] name = "wasm-bindgen" -version = "0.2.60" +version = "0.2.63" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2cc57ce05287f8376e998cbddfb4c8cb43b84a7ec55cf4551d7c00eef317a47f" +checksum = "4c2dc4aa152834bc334f506c1a06b866416a8b6697d5c9f75b9a689c8486def0" dependencies = [ "cfg-if", "wasm-bindgen-macro", @@ -2552,9 +2830,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-backend" -version = "0.2.60" +version = "0.2.63" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d967d37bf6c16cca2973ca3af071d0a2523392e4a594548155d89a678f4237cd" +checksum = "ded84f06e0ed21499f6184df0e0cb3494727b0c5da89534e0fcc55c51d812101" dependencies = [ "bumpalo", "lazy_static", @@ -2566,10 +2844,22 @@ dependencies = [ ] [[package]] -name = "wasm-bindgen-macro" -version = "0.2.60" +name = "wasm-bindgen-futures" +version = "0.4.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8bd151b63e1ea881bb742cd20e1d6127cef28399558f3b5d415289bc41eee3a4" +checksum = "64487204d863f109eb77e8462189d111f27cb5712cc9fdb3461297a76963a2f6" +dependencies = [ + "cfg-if", + "js-sys", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.63" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "838e423688dac18d73e31edce74ddfac468e37b1506ad163ffaf0a46f703ffe3" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -2577,9 +2867,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.60" +version = "0.2.63" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d68a5b36eef1be7868f668632863292e37739656a80fc4b9acec7b0bd35a4931" +checksum = "3156052d8ec77142051a533cdd686cba889537b213f948cd1d20869926e68e92" dependencies = [ "proc-macro2", "quote", @@ -2590,9 +2880,44 @@ dependencies = [ [[package]] name = "wasm-bindgen-shared" -version = "0.2.60" +version = "0.2.63" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "daf76fe7d25ac79748a37538b7daeed1c7a6867c92d3245c12c6222e4a20d639" +checksum = "c9ba19973a58daf4db6f352eda73dc0e289493cd29fb2632eb172085b6521acd" + +[[package]] +name = "web-sys" +version = "0.3.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b72fe77fd39e4bd3eaa4412fd299a0be6b3dfe9d2597e2f1c20beb968f41d17" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "wepoll-binding" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "374fff4ff9701ff8b6ad0d14bacd3156c44063632d8c136186ff5967d48999a7" +dependencies = [ + "bitflags", + "wepoll-sys", +] + +[[package]] +name = "wepoll-sys" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9082a777aed991f6769e2b654aa0cb29f1c3d615daf009829b07b66c7aff6a24" +dependencies = [ + "cc", +] + +[[package]] +name = "whoami" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a08eb844b158ea881e81b94556eede7f7e306e4c7b976aad88f49e6e36dec391" [[package]] name = "winapi" diff --git a/sqlx-core/Cargo.toml b/sqlx-core/Cargo.toml index e3a976f0..f181aff0 100644 --- a/sqlx-core/Cargo.toml +++ b/sqlx-core/Cargo.toml @@ -6,74 +6,80 @@ description = "Core of SQLx, the rust SQL toolkit. Not intended to be used direc license = "MIT OR Apache-2.0" edition = "2018" authors = [ - "Ryan Leckey ", # ryan@launchbadge.com - "Austin Bonander ", # austin@launchbadge.com - "Zachery Gyurkovitz ", # zach@launchbadge.com - "Daniel Akhterov ", # daniel@launchbadge.com + "Ryan Leckey ", + "Austin Bonander ", + "Zachery Gyurkovitz ", + "Daniel Akhterov ", ] [features] default = [ "runtime-async-std" ] -unstable = [] -# intended mainly for CI and docs -all = ["all-database", "all-type"] -all-database = ["mysql", "sqlite", "postgres"] -all-type = ["bigdecimal", "json", "time", "chrono", "ipnetwork", "uuid"] -# we need a feature which activates `num-bigint` as well because -# `bigdecimal` uses types from it but does not reexport (tsk tsk) -bigdecimal = ["bigdecimal_", "num-bigint"] -postgres = [ "md-5", "sha2", "base64", "sha-1", "rand", "hmac", "futures-channel/sink", "futures-util/sink", "tokio/uds" ] -json = ["serde", "serde_json"] + +# [deprecated] TLS is not possible to disable due to it being conditional on multiple features +# Hopefully Cargo can handle this in the future +tls = [] + +# databases +postgres = [ "md-5", "sha2", "base64", "sha-1", "rand", "hmac", "futures-channel/sink", "futures-util/sink" ] mysql = [ "sha-1", "sha2", "generic-array", "num-bigint", "base64", "digest", "rand" ] sqlite = [ "libsqlite3-sys" ] -tls = [ "async-native-tls" ] -runtime-async-std = [ "async-native-tls/runtime-async-std", "async-std" ] -runtime-tokio = [ "async-native-tls/runtime-tokio", "tokio" ] -# intended for internal benchmarking, do not use -bench = [] + +# types +all-types = [ "chrono", "time", "bigdecimal", "ipnetwork", "json", "uuid" ] +bigdecimal = [ "bigdecimal_", "num-bigint" ] +json = [ "serde", "serde_json" ] + +# runtimes +runtime-async-std = [ "sqlx-rt/runtime-async-std" ] +runtime-tokio = [ "sqlx-rt/runtime-tokio" ] +runtime-actix = [ "sqlx-rt/runtime-actix" ] # support offline/decoupled building (enables serialization of `Describe`) offline = ["serde"] [dependencies] -async-native-tls = { version = "0.3.2", default-features = false, optional = true } -async-std = { version = "1.5.0", features = [ "unstable" ], optional = true } +atoi = "0.3.2" +sqlx-rt = { path = "../sqlx-rt", version = "0.1.0-pre" } async-stream = { version = "0.2.1", default-features = false } base64 = { version = "0.12.0", default-features = false, optional = true, features = [ "std" ] } bigdecimal_ = { version = "0.1.0", optional = true, package = "bigdecimal" } bitflags = { version = "1.2.1", default-features = false } +bytes = "0.5.4" byteorder = { version = "1.3.4", default-features = false, features = [ "std" ] } -chrono = { version = "0.4.10", default-features = false, features = [ "clock" ], optional = true } +chrono = { version = "0.4.11", default-features = false, features = [ "clock" ], optional = true } crossbeam-queue = "0.2.1" +crossbeam-channel = "0.4.2" crossbeam-utils = { version = "0.7.2", default-features = false } digest = { version = "0.8.1", default-features = false, optional = true, features = [ "std" ] } +either = "1.5.3" futures-channel = { version = "0.3.4", default-features = false, features = [ "alloc", "std" ] } futures-core = { version = "0.3.4", default-features = false } -futures-util = { version = "0.3.4", default-features = false } +futures-util = "0.3.4" generic-array = { version = "0.12.3", default-features = false, optional = true } +hashbrown = "0.7.1" hex = "0.4.2" hmac = { version = "0.7.1", default-features = false, optional = true } +itoa = "0.4.5" ipnetwork = { version = "0.16.0", default-features = false, optional = true } -libc = "0.2.68" +libc = "0.2.69" +libsqlite3-sys = { version = "0.17.3", optional = true, default-features = false, features = [ "pkg-config", "vcpkg", "bundled" ] } log = { version = "0.4.8", default-features = false } md-5 = { version = "0.8.0", default-features = false, optional = true } memchr = { version = "2.3.3", default-features = false } num-bigint = { version = "0.2.6", default-features = false, optional = true, features = [ "std" ] } percent-encoding = "2.1.0" +parking_lot = "0.10.2" +threadpool = "*" +phf = { version = "0.8.0", features = [ "macros" ] } rand = { version = "0.7.3", default-features = false, optional = true, features = [ "std" ] } +serde = { version = "1.0.106", features = [ "derive" ], optional = true } +serde_json = { version = "1.0.51", features = [ "raw_value" ], optional = true } sha-1 = { version = "0.8.2", default-features = false, optional = true } sha2 = { version = "0.8.1", default-features = false, optional = true } -tokio = { version = "0.2.13", default-features = false, features = [ "dns", "fs", "time", "tcp" ], optional = true } +sqlformat = "0.1.0" +thiserror = "1.0.15" +time = { version = "0.2.10", optional = true } +smallvec = "1.4.0" url = { version = "2.1.1", default-features = false } uuid = { version = "0.8.1", default-features = false, optional = true, features = [ "std" ] } -serde = { version = "1.0", features = [ "derive" ], optional = true } -time = { version = "0.2.7", optional = true } -serde_json = { version = "1.0", features = [ "raw_value" ], optional = true } -sqlformat = "0.1.0" - -# -[dependencies.libsqlite3-sys] -version = "0.17.1" -optional = true -default-features = false -features = [ "pkg-config", "vcpkg", "bundled" ] +whoami = "0.8.1" diff --git a/sqlx-core/src/arguments.rs b/sqlx-core/src/arguments.rs index c43af31c..aeaac892 100644 --- a/sqlx-core/src/arguments.rs +++ b/sqlx-core/src/arguments.rs @@ -1,20 +1,18 @@ -//! Traits for passing arguments to SQL queries. +//! Types and traits for passing arguments to SQL queries. use crate::database::Database; use crate::encode::Encode; -use crate::types::Type; /// A tuple of arguments to be sent to the database. -pub trait Arguments: Send + Sized + Default + 'static { - type Database: Database + ?Sized; +pub trait Arguments<'q>: Send + Sized + Default { + type Database: Database; - /// Reserves the capacity for at least `len` more values (of `size` bytes) to - /// be added to the arguments without a reallocation. - fn reserve(&mut self, len: usize, size: usize); + /// Reserves the capacity for at least `additional` more values (of `size` total bytes) to + /// be added to the arguments without a reallocation. + fn reserve(&mut self, additional: usize, size: usize); /// Add the value to the end of the arguments. fn add(&mut self, value: T) where - T: Type, - T: Encode; + T: 'q + Encode<'q, Self::Database>; } diff --git a/sqlx-core/src/connection.rs b/sqlx-core/src/connection.rs index 1b3e313c..044d4da0 100644 --- a/sqlx-core/src/connection.rs +++ b/sqlx-core/src/connection.rs @@ -1,123 +1,40 @@ -//! Contains the `Connection` and `Connect` traits. - -use std::convert::TryInto; +use std::str::FromStr; use futures_core::future::BoxFuture; -use crate::executor::Executor; -use crate::pool::{Pool, PoolConnection}; -use crate::transaction::Transaction; -use crate::url::Url; +use crate::database::Database; +use crate::error::{BoxDynError, Error}; -/// Represents a single database connection rather than a pool of database connections. -/// -/// Connections can be manually established outside of a [`Pool`] with [`Connect::connect`]. -/// -/// Prefer running queries from [`Pool`] unless there is a specific need for a single, sticky -/// connection. -pub trait Connection -where - Self: Send + 'static, - Self: Executor, -{ - /// Starts a new transaction. - /// - /// Wraps this connection in [`Transaction`] to manage the transaction lifecycle. To get the - /// original connection back, explicitly [`commit`] or [`rollback`] and this connection will - /// be returned. - /// - /// ```rust,ignore - /// let mut tx = conn.begin().await?; - /// // conn is now inaccessible as its wrapped in a transaction - /// - /// let conn = tx.commit().await?; - /// // conn is back now and out of the transaction - /// ``` - /// - /// [`commit`]: crate::transaction::Transaction::commit - /// [`rollback`]: crate::transaction::Transaction::rollback - fn begin(self) -> BoxFuture<'static, crate::Result>> - where - Self: Sized, - { - Box::pin(Transaction::new(0, self)) - } +/// Represents a single database connection. +pub trait Connection: Send + 'static { + type Database: Database; /// Explicitly close this database connection. /// /// This method is **not required** for safe and consistent operation. However, it is - /// recommended to call it instead of letting a connection `drop` as the database server + /// recommended to call it instead of letting a connection `drop` as the database backend /// will be faster at cleaning up resources. - fn close(self) -> BoxFuture<'static, crate::Result<()>>; + fn close(self) -> BoxFuture<'static, Result<(), Error>>; /// Checks if a connection to the database is still valid. - fn ping(&mut self) -> BoxFuture>; + fn ping(&mut self) -> BoxFuture>; } /// Represents a type that can directly establish a new connection. -pub trait Connect: Connection { +pub trait Connect: Sized + Connection { + type Options: FromStr + Send + Sync; + /// Establish a new database connection. - fn connect(url: T) -> BoxFuture<'static, crate::Result> - where - T: TryInto, - Self: Sized; -} + /// + /// A value of `Options` is parsed from the provided connection string. This parsing + /// is database-specific. + #[inline] + fn connect(url: &str) -> BoxFuture<'static, Result> { + let options = url.parse().map_err(Error::ParseConnectOptions); -#[allow(dead_code)] -pub(crate) enum ConnectionSource<'c, C> -where - C: Connect, -{ - ConnectionRef(&'c mut C), - Connection(C), - PoolConnection(Pool, PoolConnection), - Pool(Pool), -} - -impl<'c, C> ConnectionSource<'c, C> -where - C: Connect, -{ - #[allow(dead_code)] - pub(crate) async fn resolve(&mut self) -> crate::Result<&'_ mut C> { - if let ConnectionSource::Pool(pool) = self { - let conn = pool.acquire().await?; - - *self = ConnectionSource::PoolConnection(pool.clone(), conn); - } - - Ok(match self { - ConnectionSource::ConnectionRef(conn) => conn, - ConnectionSource::PoolConnection(_, ref mut conn) => conn, - ConnectionSource::Connection(ref mut conn) => conn, - ConnectionSource::Pool(_) => unreachable!(), - }) + Box::pin(async move { Ok(Self::connect_with(&options?).await?) }) } -} -impl<'c, C> From for ConnectionSource<'c, C> -where - C: Connect, -{ - fn from(connection: C) -> Self { - ConnectionSource::Connection(connection) - } -} - -impl<'c, C> From> for ConnectionSource<'c, C> -where - C: Connect, -{ - fn from(connection: PoolConnection) -> Self { - ConnectionSource::PoolConnection(Pool(connection.pool.clone()), connection) - } -} - -impl<'c, C> From> for ConnectionSource<'c, C> -where - C: Connect, -{ - fn from(pool: Pool) -> Self { - ConnectionSource::Pool(pool) - } + /// Establish a new database connection with the provided options. + fn connect_with(options: &Self::Options) -> BoxFuture<'_, Result>; } diff --git a/sqlx-core/src/cursor.rs b/sqlx-core/src/cursor.rs deleted file mode 100644 index 95fb41ec..00000000 --- a/sqlx-core/src/cursor.rs +++ /dev/null @@ -1,89 +0,0 @@ -//! Contains the `Cursor` trait. - -use futures_core::future::BoxFuture; - -use crate::database::Database; -use crate::executor::Execute; -use crate::pool::Pool; -use crate::row::HasRow; - -/// Represents a result set, which is generated by executing a query against the database. -/// -/// A `Cursor` can be created by either [`Executor::fetch`] or [`Query::fetch`]. -/// -/// ```rust,ignore -/// let mut cursor = sqlx::query("SELECT slug, title, description FROM articles") -/// .fetch(&mut conn); -/// ``` -/// -/// Initially the `Cursor` is positioned before the first row. The `next` method moves the cursor -/// to the next row, and because it returns `None` when there are no more rows, it can be used -/// in a `while` loop to iterate through all returned rows. -/// -/// ```rust,ignore -/// # #[derive(sqlx::FromRow)] -/// # struct Article<'a> { -/// # slug: &'a str, -/// # title: &'a str, -/// # description: &'a str, -/// # } -/// # -/// // For each row in the result set .. -/// while let Some(row) = cursor.next().await? { -/// // .. decode a domain type from the row -/// let obj = Article::from_row(row)?; -/// } -/// ``` -/// -/// This trait is sealed and cannot be implemented for types outside of SQLx. -/// -/// [`Executor::fetch`]: crate::executor::Executor::fetch -/// [`Query::fetch`]: crate::query::Query::fetch -#[must_use = "cursor must have `.next()` called to execute query"] -pub trait Cursor<'c, 'q> -where - Self: Send + Unpin + private::Sealed, -{ - /// The `Database` this `Cursor` is implemented for. - type Database: Database; - - #[doc(hidden)] - fn from_pool(pool: &Pool<::Connection>, query: E) -> Self - where - Self: Sized, - E: Execute<'q, Self::Database>; - - #[doc(hidden)] - fn from_connection( - connection: &'c mut ::Connection, - query: E, - ) -> Self - where - Self: Sized, - E: Execute<'q, Self::Database>; - - /// Creates a future that attempts to resolve the next row in the cursor. - fn next<'cur>( - &'cur mut self, - ) -> BoxFuture<'cur, crate::Result>::Row>>>; -} - -// Prevent users from implementing the `Row` trait. -pub(crate) mod private { - pub trait Sealed {} -} - -/// Associate [`Database`] with a [`Cursor`] of a generic lifetime. -/// -/// --- -/// -/// The upcoming Rust feature, [Generic Associated Types], should obviate -/// the need for this trait. -/// -/// [Generic Associated Types]: https://www.google.com/search?q=generic+associated+types+rust&oq=generic+associated+types+rust&aqs=chrome..69i57j0l5.3327j0j7&sourceid=chrome&ie=UTF-8 -pub trait HasCursor<'c, 'q> { - type Database: Database; - - /// The concrete `Cursor` implementation for this database. - type Cursor: Cursor<'c, 'q, Database = Self::Database>; -} diff --git a/sqlx-core/src/database.rs b/sqlx-core/src/database.rs index 87119102..9ebe8e5e 100644 --- a/sqlx-core/src/database.rs +++ b/sqlx-core/src/database.rs @@ -1,42 +1,66 @@ -use std::fmt::{Debug, Display}; +use std::fmt::Debug; use crate::arguments::Arguments; use crate::connection::Connect; -use crate::cursor::HasCursor; -use crate::error::DatabaseError; -use crate::row::HasRow; -use crate::types::TypeInfo; -use crate::value::HasRawValue; +use crate::row::Row; +use crate::type_info::TypeInfo; +use crate::value::{Value, ValueRef}; /// A database driver. /// -/// This trait encapsulates a complete driver implementation to a specific -/// database (e.g., **MySQL**, **Postgres**). -pub trait Database -where - Self: Debug + Sized + Send + 'static, - Self: for<'c> HasRow<'c, Database = Self>, - Self: for<'c> HasRawValue<'c, Database = Self>, - Self: for<'c, 'q> HasCursor<'c, 'q, Database = Self>, +/// This trait encapsulates a complete set of traits that implement a driver for a +/// specific database (e.g., MySQL, PostgreSQL). +pub trait Database: + Sized + + Send + + Debug + + for<'r> HasValueRef<'r, Database = Self> + + for<'q> HasArguments<'q, Database = Self> { /// The concrete `Connection` implementation for this database. type Connection: Connect; - /// The concrete `Arguments` implementation for this database. - type Arguments: Arguments; + /// The concrete `Row` implementation for this database. + type Row: Row; /// The concrete `TypeInfo` implementation for this database. type TypeInfo: TypeInfo; - /// The Rust type of table identifiers for this database. - type TableId: Display + Clone; - - /// The Rust type used as the buffer when encoding arguments. - /// - /// For example, **Postgres** and **MySQL** use `Vec`; - /// however, **SQLite** uses `Vec`. - type RawBuffer: Default; - - /// The concrete `DatabaseError` type used to report errors from the database. - type Error: DatabaseError + Send + Sync; + /// The concrete type used to hold an owned copy of the not-yet-decoded value that was + /// received from the database. + type Value: Value + 'static; +} + +/// Associate [`Database`] with a [`ValueRef`](crate::value::ValueRef) of a generic lifetime. +/// +/// --- +/// +/// The upcoming Rust feature, [Generic Associated Types], should obviate +/// the need for this trait. +/// +/// [Generic Associated Types]: https://www.google.com/search?q=generic+associated+types+rust&oq=generic+associated+types+rust&aqs=chrome..69i57j0l5.3327j0j7&sourceid=chrome&ie=UTF-8 +pub trait HasValueRef<'r> { + type Database: Database; + + /// The concrete type used to hold a reference to the not-yet-decoded value that has just been + /// received from the database. + type ValueRef: ValueRef<'r, Database = Self::Database>; +} + +/// Associate [`Database`] with an [`Arguments`](crate::arguments::Arguments) of a generic lifetime. +/// +/// --- +/// +/// The upcoming Rust feature, [Generic Associated Types], should obviate +/// the need for this trait. +/// +/// [Generic Associated Types]: https://www.google.com/search?q=generic+associated+types+rust&oq=generic+associated+types+rust&aqs=chrome..69i57j0l5.3327j0j7&sourceid=chrome&ie=UTF-8 +pub trait HasArguments<'q> { + type Database: Database; + + /// The concrete `Arguments` implementation for this database. + type Arguments: Arguments<'q, Database = Self::Database>; + + /// The concrete type used as a buffer for arguments while encoding. + type ArgumentBuffer: Default; } diff --git a/sqlx-core/src/decode.rs b/sqlx-core/src/decode.rs index 37361046..cbef6082 100644 --- a/sqlx-core/src/decode.rs +++ b/sqlx-core/src/decode.rs @@ -1,13 +1,37 @@ //! Types and traits for decoding values from the database. -use crate::database::Database; -use crate::value::HasRawValue; +use crate::database::{Database, HasValueRef}; +use crate::error::BoxDynError; +use crate::types::Type; +use crate::value::ValueRef; /// A type that can be decoded from the database. -pub trait Decode<'de, DB> -where - Self: Sized + 'de, - DB: Database, -{ - fn decode(value: >::RawValue) -> crate::Result; +pub trait Decode<'r, DB: Database>: Sized + Type { + /// Determines if a value of this type can be created from a value with the + /// given type information. + fn accepts(ty: &DB::TypeInfo) -> bool { + *ty == Self::type_info() + } + + /// Decode a new value of this type using a raw value from the database. + fn decode(value: >::ValueRef) -> Result; +} + +// implement `Decode` for Option for all SQL types +impl<'r, DB, T> Decode<'r, DB> for Option +where + DB: Database, + T: Decode<'r, DB>, +{ + fn accepts(ty: &DB::TypeInfo) -> bool { + T::accepts(ty) + } + + fn decode(value: >::ValueRef) -> Result { + if value.is_null() { + Ok(None) + } else { + Ok(Some(T::decode(value)?)) + } + } } diff --git a/sqlx-core/src/describe.rs b/sqlx-core/src/describe.rs index d66e4b4c..9036c90b 100644 --- a/sqlx-core/src/describe.rs +++ b/sqlx-core/src/describe.rs @@ -1,80 +1,67 @@ //! Types for returning SQL type information about queries. - -use std::fmt::{self, Debug}; +//! +//! The compile-time type checking within the query macros heavily lean on the information +//! provided within these types. use crate::database::Database; -/// The return type of [`Executor::describe`]. +// TODO(@mehcode): Remove [pub] from Describe/Column and use methods to expose the properties + +/// A representation of a statement that _could_ have been executed against the database. /// -/// [`Executor::describe`]: crate::executor::Executor::describe +/// Returned from [`Executor::describe`](crate::executor::Executor::describe). +/// +/// The compile-time verification within the query macros utilizes `describe` and this type to +/// act on an arbitrary query. +#[derive(Debug)] +#[non_exhaustive] #[cfg_attr(feature = "offline", derive(serde::Serialize, serde::Deserialize))] #[cfg_attr( feature = "offline", serde(bound( - serialize = "DB::TypeInfo: serde::Serialize, Column: serde::Serialize", - deserialize = "DB::TypeInfo: serde::de::DeserializeOwned, Column: serde::de::DeserializeOwned" + serialize = "DB::TypeInfo: serde::Serialize", + deserialize = "DB::TypeInfo: serde::de::DeserializeOwned" )) )] -#[non_exhaustive] pub struct Describe -where - DB: Database + ?Sized, -{ - // TODO: Describe#param_types should probably be Option as we either know all the params or we know none - /// The expected types for the parameters of the query. - pub param_types: Box<[Option]>, - - /// The type and table information, if any for the results of the query. - pub result_columns: Box<[Column]>, -} - -impl Debug for Describe where DB: Database, - DB::TypeInfo: Debug, - Column: Debug, { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - f.debug_struct("Describe") - .field("param_types", &self.param_types) - .field("result_columns", &self.result_columns) - .finish() - } + /// The expected types of the parameters. This is currently always an array of `None` values + /// on all databases drivers aside from PostgreSQL. + pub params: Vec>, + + /// The columns that will be found in the results from this query. + pub columns: Vec>, } -/// A single column of a result set. +#[derive(Debug)] +#[non_exhaustive] #[cfg_attr(feature = "offline", derive(serde::Serialize, serde::Deserialize))] #[cfg_attr( feature = "offline", serde(bound( - serialize = "DB::TableId: serde::Serialize, DB::TypeInfo: serde::Serialize", - deserialize = "DB::TableId: serde::de::DeserializeOwned, DB::TypeInfo: serde::de::DeserializeOwned" + serialize = "DB::TypeInfo: serde::Serialize", + deserialize = "DB::TypeInfo: serde::de::DeserializeOwned" )) )] -#[non_exhaustive] pub struct Column where - DB: Database + ?Sized, + DB: Database, { - pub name: Option>, - pub table_id: Option, - pub type_info: Option, - /// Whether or not the column cannot be `NULL` (or if that is even knowable). - pub non_null: Option, -} + /// The name of the result column. + /// + /// The column name is unreliable (and can change between database minor versions) if this + /// result column is an expression that has not been aliased. + pub name: String, -impl Debug for Column -where - DB: Database + ?Sized, - DB::TableId: Debug, - DB::TypeInfo: Debug, -{ - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - f.debug_struct("Column") - .field("name", &self.name) - .field("table_id", &self.table_id) - .field("type_info", &self.type_info) - .field("non_null", &self.non_null) - .finish() - } + /// The type information for the result column. + /// + /// This may be `None` if the type cannot be determined. This occurs in SQLite when + /// the column is an expression. + pub type_info: Option, + + /// Whether the column cannot be `NULL` (or if that is even knowable). + /// This value is only not `None` if received from a call to `describe`. + pub not_null: Option, } diff --git a/sqlx-core/src/encode.rs b/sqlx-core/src/encode.rs index 1fc2f104..e8fa3dd9 100644 --- a/sqlx-core/src/encode.rs +++ b/sqlx-core/src/encode.rs @@ -1,9 +1,9 @@ //! Types and traits for encoding values to the database. - -use crate::database::Database; -use crate::types::Type; use std::mem; +use crate::database::{Database, HasArguments}; +use crate::types::Type; + /// The return type of [Encode::encode]. pub enum IsNull { /// The value is null; no data was written. @@ -16,64 +16,87 @@ pub enum IsNull { } /// Encode a single value to be sent to the database. -pub trait Encode -where - DB: Database + ?Sized, -{ - /// Writes the value of `self` into `buf` in the expected format for the database. - fn encode(&self, buf: &mut DB::RawBuffer); - - fn encode_nullable(&self, buf: &mut DB::RawBuffer) -> IsNull { - self.encode(buf); - - IsNull::No +pub trait Encode<'q, DB: Database>: Type { + fn produces(&self) -> DB::TypeInfo { + Self::type_info() } + /// Writes the value of `self` into `buf` in the expected format for the database. + #[must_use] + fn encode(self, buf: &mut >::ArgumentBuffer) -> IsNull + where + Self: Sized, + { + self.encode_by_ref(buf) + } + + /// Writes the value of `self` into `buf` without moving `self`. + /// + /// Where possible, make use of `encode` instead as it can take advantage of re-using + /// memory. + #[must_use] + fn encode_by_ref(&self, buf: &mut >::ArgumentBuffer) -> IsNull; + + #[inline] fn size_hint(&self) -> usize { mem::size_of_val(self) } } -impl Encode for &'_ T +impl<'q, T, DB: Database> Encode<'q, DB> for &'_ T where - DB: Database, - T: Type, - T: Encode, + T: Encode<'q, DB>, { - fn encode(&self, buf: &mut DB::RawBuffer) { - (*self).encode(buf) + #[inline] + fn produces(&self) -> DB::TypeInfo { + (**self).produces() } - fn encode_nullable(&self, buf: &mut DB::RawBuffer) -> IsNull { - (*self).encode_nullable(buf) + #[inline] + fn encode(self, buf: &mut >::ArgumentBuffer) -> IsNull { + >::encode_by_ref(self, buf) } + #[inline] + fn encode_by_ref(&self, buf: &mut >::ArgumentBuffer) -> IsNull { + <&T as Encode>::encode(self, buf) + } + + #[inline] fn size_hint(&self) -> usize { - (*self).size_hint() + (**self).size_hint() } } -impl Encode for Option -where - DB: Database, - T: Type, - T: Encode, -{ - fn encode(&self, buf: &mut DB::RawBuffer) { - // Forward to [encode_nullable] and ignore the result - let _ = self.encode_nullable(buf); +impl<'q, T: 'q + Encode<'q, DB>, DB: Database> Encode<'q, DB> for Option { + #[inline] + fn produces(&self) -> DB::TypeInfo { + if let Some(v) = self { + v.produces() + } else { + T::type_info() + } } - fn encode_nullable(&self, buf: &mut DB::RawBuffer) -> IsNull { - if let Some(self_) = self { - self_.encode(buf); - - IsNull::No + #[inline] + fn encode(self, buf: &mut >::ArgumentBuffer) -> IsNull { + if let Some(v) = self { + v.encode(buf) } else { IsNull::Yes } } + #[inline] + fn encode_by_ref(&self, buf: &mut >::ArgumentBuffer) -> IsNull { + if let Some(v) = self { + v.encode_by_ref(buf) + } else { + IsNull::Yes + } + } + + #[inline] fn size_hint(&self) -> usize { self.as_ref().map_or(0, Encode::size_hint) } diff --git a/sqlx-core/src/error.rs b/sqlx-core/src/error.rs index f6ecf36a..e59d20ba 100644 --- a/sqlx-core/src/error.rs +++ b/sqlx-core/src/error.rs @@ -1,387 +1,148 @@ -//! Errorand Result types. +use std::any::type_name; +use std::borrow::Cow; +use std::error::Error as StdError; +use std::fmt::Display; +use std::io; +use std::result::Result as StdResult; use crate::database::Database; -use crate::types::Type; -use std::any::type_name; -use std::error::Error as StdError; -use std::fmt::{self, Debug, Display}; -use std::io; - -#[allow(unused_macros)] -macro_rules! decode_err { - ($s:literal, $($args:tt)*) => { - crate::Error::Decode(format!($s, $($args)*).into()) - }; - - ($expr:expr) => { - crate::Error::decode($expr) - }; -} /// A specialized `Result` type for SQLx. -pub type Result = std::result::Result; +pub type Result = StdResult; -/// A generic error that represents all the ways a method can fail inside of SQLx. -#[derive(Debug)] +// Convenience type alias for usage within SQLx. +pub(crate) type BoxDynError = Box; + +/// An unexpected `NULL` was encountered during decoding. +/// +/// Returned from [`Row::get`] if the value from the database is `NULL`, +/// and you are not decoding into an `Option`. +#[derive(thiserror::Error, Debug)] +#[error("unexpected null; try decoding as an `Option`")] +pub struct UnexpectedNullError; + +/// Represents all the ways a method can fail within SQLx. +#[derive(Debug, thiserror::Error)] #[non_exhaustive] pub enum Error { - /// Error communicating with the database. - Io(io::Error), + /// Error occurred while parsing a connection string. + #[error("error occurred while parsing a connection string: {0}")] + ParseConnectOptions(#[source] BoxDynError), - /// Connection URL was malformed. - UrlParse(url::ParseError), - - /// An error was returned by the database. + /// Error returned from the database. + #[error("error returned from database: {0}")] Database(Box), - /// No row was returned during [`query::Map::fetch_one`] or `QueryAs::fetch_one`. + /// Error communicating with the database backend. + #[error("error communicating with the server: {0}")] + Io(#[from] io::Error), + + /// Error occurred while attempting to establish a TLS connection. + #[error("error occurred while attempting to establish a TLS connection: {0}")] + Tls(#[source] BoxDynError), + + /// Unexpected or invalid data encountered while communicating with the database. /// - /// [`query::Map::fetch_one`]: crate::query::Map::fetch_one + /// This should indicate there is a programming error in a SQLx driver or there + /// is something corrupted with the connection to the database itself. + #[error("encountered unexpected or invalid data: {0}")] + Protocol(String), + + /// No rows returned by a query that expected to return at least one row. + #[error("no rows returned by a query that expected to return at least one row")] RowNotFound, - /// Column was not found by name in a Row (during [`Row::get`]). - /// - /// [`Row::get`]: crate::row::Row::get - ColumnNotFound(Box), - - /// Column index was out of bounds (e.g., asking for column 4 in a 2-column row). + /// Column index was out of bounds. + #[error("column index out of bounds: the len is {len}, but the index is {index}")] ColumnIndexOutOfBounds { index: usize, len: usize }, - /// Unexpected or invalid data was encountered. This would indicate that we received - /// data that we were not expecting or it was in a format we did not understand. This - /// generally means either there is a programming error in a SQLx driver or - /// something with the connection or the database database itself is corrupted. - /// - /// Context is provided by the included error message. - Protocol(Box), + /// No column found for the given name. + #[error("no column found for name: {0}")] + ColumnNotFound(String), + + /// Error occurred while decoding a value from a specific column. + #[error("error occurred while decoding column {index}: {source}")] + ColumnDecode { + index: String, + + #[source] + source: BoxDynError, + }, + + /// Error occurred while decoding a value. + #[error("error occurred while decoding: {0}")] + Decode(#[source] BoxDynError), /// A [`Pool::acquire`] timed out due to connections not becoming available or /// because another task encountered too many errors while trying to open a new connection. /// /// [`Pool::acquire`]: crate::pool::Pool::acquire - PoolTimedOut(Option>), + #[error("pool timed out while waiting for an open connection")] + PoolTimedOut, /// [`Pool::close`] was called while we were waiting in [`Pool::acquire`]. /// /// [`Pool::acquire`]: crate::pool::Pool::acquire /// [`Pool::close`]: crate::pool::Pool::close + #[error("attempted to acquire a connection on a closed pool")] PoolClosed, - - /// An error occurred while attempting to setup TLS. - /// This should only be returned from an explicit ask for TLS. - Tls(Box), - - /// An error occurred decoding data received from the database. - Decode(Box), } impl Error { #[allow(dead_code)] - pub(crate) fn decode(err: E) -> Self - where - E: StdError + Send + Sync + 'static, - { - Error::Decode(err.into()) + #[inline] + pub(crate) fn protocol(err: impl Display) -> Self { + Error::Protocol(err.to_string()) } #[allow(dead_code)] - pub(crate) fn mismatched_types(expected: DB::TypeInfo) -> Self - where - T: Type, - { - let ty_name = type_name::(); - - return decode_err!( - "mismatched types; Rust type `{}` (as SQL type {}) is not compatible with SQL type {}", - ty_name, - T::type_info(), - expected - ); - } -} - -impl StdError for Error { - fn source(&self) -> Option<&(dyn StdError + 'static)> { - match self { - Error::Io(error) => Some(error), - Error::UrlParse(error) => Some(error), - Error::PoolTimedOut(Some(error)) => Some(&**error), - Error::Decode(error) => Some(&**error), - Error::Tls(error) => Some(&**error), - Error::Database(error) => Some(error.as_ref_err()), - - _ => None, - } - } -} - -impl Display for Error { - // IntellijRust does not understand that [non_exhaustive] applies only for downstream crates - // noinspection RsMatchCheck - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - Error::Io(error) => write!(f, "{}", error), - - Error::UrlParse(error) => write!(f, "{}", error), - - Error::Decode(error) => write!(f, "{}", error), - - Error::Database(error) => Display::fmt(error, f), - - Error::RowNotFound => f.write_str("found no row when we expected at least one"), - - Error::ColumnNotFound(ref name) => { - write!(f, "no column found with the name {:?}", name) - } - - Error::ColumnIndexOutOfBounds { index, len } => write!( - f, - "column index out of bounds: there are {} columns but the index is {}", - len, index - ), - - Error::Protocol(ref err) => f.write_str(err), - - Error::PoolTimedOut(Some(ref err)) => { - write!(f, "timed out while waiting for an open connection: {}", err) - } - - Error::PoolTimedOut(None) => { - write!(f, "timed out while waiting for an open connection") - } - - Error::PoolClosed => f.write_str("attempted to acquire a connection on a closed pool"), - - Error::Tls(ref err) => write!(f, "error during TLS upgrade: {}", err), - } - } -} - -impl From for Error { #[inline] - fn from(err: io::Error) -> Self { - Error::Io(err) - } -} - -impl From for Error { - #[inline] - fn from(err: io::ErrorKind) -> Self { - Error::Io(err.into()) - } -} - -impl From for Error { - #[inline] - fn from(err: url::ParseError) -> Self { - Error::UrlParse(err) - } -} - -impl From> for Error { - #[inline] - fn from(err: ProtocolError) -> Self { - Error::Protocol(err.args.to_string().into_boxed_str()) - } -} - -impl From for Error { - #[inline] - fn from(err: UnexpectedNullError) -> Self { - Error::Decode(err.into()) - } -} - -#[cfg(feature = "tls")] -#[cfg_attr(docsrs, doc(cfg(feature = "tls")))] -impl From for Error { - #[inline] - fn from(err: async_native_tls::Error) -> Self { + pub(crate) fn tls(err: impl StdError + Send + Sync + 'static) -> Self { Error::Tls(err.into()) } } -impl From> for Error { - #[inline] - fn from(err: TlsError<'_>) -> Self { - Error::Tls(err.args.to_string().into()) - } +pub(crate) fn mismatched_types( + actual: &DB::TypeInfo, + expected: &DB::TypeInfo, +) -> BoxDynError { + let ty_name = type_name::(); + + return format!( + "mismatched types; Rust type `{}` (as SQL type `{}`) is not compatible with SQL type `{}`", + ty_name, actual, expected + ) + .into(); } -/// An error that was returned by the database. -pub trait DatabaseError: StdError + Send + Sync + 'static { +/// An error that was returned from the database. +pub trait DatabaseError: 'static + Send + Sync + StdError { /// The primary, human-readable error message. fn message(&self) -> &str; /// The (SQLSTATE) code for the error. - fn code(&self) -> Option<&str> { + fn code(&self) -> Option> { None } - - fn details(&self) -> Option<&str> { - None - } - - fn hint(&self) -> Option<&str> { - None - } - - fn table_name(&self) -> Option<&str> { - None - } - - fn column_name(&self) -> Option<&str> { - None - } - - fn constraint_name(&self) -> Option<&str> { - None - } - - #[doc(hidden)] - fn as_ref_err(&self) -> &(dyn StdError + Send + Sync + 'static); - - #[doc(hidden)] - fn as_mut_err(&mut self) -> &mut (dyn StdError + Send + Sync + 'static); - - #[doc(hidden)] - fn into_box_err(self: Box) -> Box; -} - -impl dyn DatabaseError { - /// Downcast this `&dyn DatabaseError` to a specific database error type: - /// - /// * [PgError][crate::postgres::PgError] (if the `postgres` feature is active) - /// * [MySqlError][crate::mysql::MySqlError] (if the `mysql` feature is active) - /// * [SqliteError][crate::sqlite::SqliteError] (if the `sqlite` feature is active) - /// - /// In a generic context you can use the [crate::database::Database::Error] associated type. - /// - /// ### Panics - /// If the type does not match; this is in contrast with [StdError::downcast_ref] - /// which returns `Option`. This was a deliberate design decision in favor of brevity as in - /// almost all cases you should know which database error type you're expecting. - /// - /// In any other cases, use [Self::try_downcast_ref] instead. - pub fn downcast_ref(&self) -> &T { - self.try_downcast_ref::().unwrap_or_else(|| { - panic!( - "downcasting to wrong DatabaseError type; original error: {:?}", - self - ) - }) - } - - /// Downcast this `&dyn DatabaseError` to a specific database error type: - /// - /// * [PgError][crate::postgres::PgError] (if the `postgres` feature is active) - /// * [MySqlError][crate::mysql::MySqlError] (if the `mysql` feature is active) - /// * [SqliteError][crate::sqlite::SqliteError] (if the `sqlite` feature is active) - /// - /// In a generic context you can use the [crate::database::Database::Error] associated type. - /// - /// Returns `None` if the downcast fails (the types do not match) - pub fn try_downcast_ref(&self) -> Option<&T> { - self.as_ref_err().downcast_ref() - } - - /// Only meant for internal use so no `try_` variant is currently provided - #[allow(dead_code)] - pub(crate) fn downcast_mut(&mut self) -> &mut T { - // tried to express this as the following: - // - // if let Some(e) = self.as_mut_err().downcast_mut() { return e; } - // - // however it didn't like using `self` again in the panic format - if self.as_ref_err().is::() { - return self.as_mut_err().downcast_mut().unwrap(); - } - - panic!( - "downcasting to wrong DatabaseError type; original error: {:?}", - self - ) - } - - /// Downcast this `Box` to a specific database error type: - /// - /// * [PgError][crate::postgres::PgError] (if the `postgres` feature is active) - /// * [MySqlError][crate::mysql::MySqlError] (if the `mysql` feature is active) - /// * [SqliteError][crate::sqlite::SqliteError] (if the `sqlite` feature is active) - /// - /// In a generic context you can use the [crate::database::Database::Error] associated type. - /// - /// ### Panics - /// If the type does not match; this is in contrast with [std::error::Error::downcast] - /// which returns `Result`. This was a deliberate design decision in favor of - /// brevity as in almost all cases you should know which database error type you're expecting. - /// - /// In any other cases, use [Self::try_downcast] instead. - pub fn downcast(self: Box) -> Box { - self.try_downcast().unwrap_or_else(|e| { - panic!( - "downcasting to wrong DatabaseError type; original error: {:?}", - e - ) - }) - } - - /// Downcast this `Box` to a specific database error type: - /// - /// * [PgError][crate::postgres::PgError] (if the `postgres` feature is active) - /// * [MySqlError][crate::mysql::MySqlError] (if the `mysql` feature is active) - /// * [SqliteError][crate::sqlite::SqliteError] (if the `sqlite` feature is active) - /// - /// In a generic context you can use the [crate::database::Database::Error] associated type. - /// - /// Returns `Err(self)` if the downcast fails (the types do not match). - pub fn try_downcast( - self: Box, - ) -> std::result::Result, Box> { - if self.as_ref_err().is::() { - Ok(self - .into_box_err() - .downcast() - .expect("type mismatch between DatabaseError::as_ref_err() and into_box_err()")) - } else { - Err(self) - } - } } -/// Used by the `protocol_error!()` macro for a lazily evaluated conversion to -/// `crate::Error::Protocol` so we can use the macro with `.ok_or()` without Clippy complaining. -pub(crate) struct ProtocolError<'a> { - pub args: fmt::Arguments<'a>, -} - -#[allow(unused_macros)] -macro_rules! protocol_err ( - ($($args:tt)*) => { - $crate::error::ProtocolError { args: format_args!($($args)*) } - } -); - -pub(crate) struct TlsError<'a> { - pub args: fmt::Arguments<'a>, -} - -#[allow(unused_macros)] -macro_rules! tls_err { - ($($args:tt)*) => { crate::error::TlsError { args: format_args!($($args)*)} }; -} - -/// An unexpected `NULL` was encountered during decoding. -/// -/// Returned from `Row::get` if the value from the database is `NULL` -/// and you are not decoding into an `Option`. -#[derive(Debug, Clone, Copy)] -pub struct UnexpectedNullError; - -impl Display for UnexpectedNullError { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.write_str("unexpected null; try decoding as an `Option`") +impl From for Error +where + E: DatabaseError, +{ + #[inline] + fn from(error: E) -> Self { + Error::Database(Box::new(error)) } } -impl StdError for UnexpectedNullError {} +// Format an error message as a `Protocol` error +macro_rules! err_protocol { + ($expr:expr) => { + $crate::error::Error::Protocol($expr.into()) + }; + + ($fmt:expr, $($arg:tt)*) => { + $crate::error::Error::Protocol(format!($fmt, $($arg)*)) + }; +} diff --git a/sqlx-core/src/executor.rs b/sqlx-core/src/executor.rs index 37dfe22f..5c238a99 100644 --- a/sqlx-core/src/executor.rs +++ b/sqlx-core/src/executor.rs @@ -1,156 +1,162 @@ +use std::fmt::Debug; + +use either::Either; use futures_core::future::BoxFuture; +use futures_core::stream::BoxStream; +use futures_util::{future, FutureExt, StreamExt, TryFutureExt, TryStreamExt}; -use crate::cursor::HasCursor; -use crate::database::Database; +use crate::database::{Database, HasArguments}; use crate::describe::Describe; +use crate::error::Error; -/// A type that contains or can provide a database connection to use for executing queries -/// against the database. +/// A type that contains or can provide a database +/// connection to use for executing queries against the database. /// -/// No guarantees are provided that successive queries run on the same physical database -/// connection. A [`Connection`](trait.Connection.html) is an `Executor` that guarantees that successive -/// queries are run on the same physical database connection. +/// No guarantees are provided that successive queries run on the same +/// physical database connection. /// -/// Implementations are provided for [`&Pool`](struct.Pool.html), -/// [`&mut PoolConnection`](struct.PoolConnection.html), -/// and [`&mut Connection`](trait.Connection.html). -pub trait Executor -where - Self: Send, -{ - /// The specific database that this type is implemented for. +/// A [`Connection`](crate::connection::Connection) is an `Executor` that guarantees that +/// successive queries are ran on the same physical database connection. +/// +/// Implemented for the following: +/// +/// * [`&Pool`] +/// * [`&mut PoolConnection`] +/// * [`&mut Connection`] +/// +pub trait Executor<'c>: Send + Debug + Sized { type Database: Database; - /// Executes the query for its side-effects and - /// discarding any potential result rows. - /// - /// Returns the number of rows affected, or 0 if not applicable. - fn execute<'e, 'q: 'e, 'c: 'e, E: 'e>( - &'c mut self, + /// Execute the query and return the total number of rows affected. + fn execute<'q: 'c, E>(self, query: E) -> BoxFuture<'c, Result> + where + E: Execute<'q, Self::Database>, + { + self.execute_many(query) + .try_fold(0, |acc, x| async move { Ok(acc + x) }) + .boxed() + } + + /// Execute multiple queries and return the rows affected from each query, in a stream. + fn execute_many<'q: 'c, E>(self, query: E) -> BoxStream<'c, Result> + where + E: Execute<'q, Self::Database>, + { + self.fetch_many(query) + .try_filter_map(|step| async move { + Ok(match step { + Either::Left(rows) => Some(rows), + Either::Right(_) => None, + }) + }) + .boxed() + } + + /// Execute the query and return the generated results as a stream. + fn fetch<'q: 'c, E>( + self, query: E, - ) -> BoxFuture<'e, crate::Result> + ) -> BoxStream<'c, Result<::Row, Error>> + where + E: Execute<'q, Self::Database>, + { + self.fetch_many(query) + .try_filter_map(|step| async move { + Ok(match step { + Either::Left(_) => None, + Either::Right(row) => Some(row), + }) + }) + .boxed() + } + + /// Execute multiple queries and return the generated results as a stream + /// from each query, in a stream. + fn fetch_many<'q: 'c, E>( + self, + query: E, + ) -> BoxStream<'c, Result::Row>, Error>> where E: Execute<'q, Self::Database>; - /// Executes a query for its result. - /// - /// Returns a [`Cursor`] that can be used to iterate through the [`Row`]s - /// of the result. - /// - /// [`Cursor`]: crate::cursor::Cursor - /// [`Row`]: crate::row::Row - fn fetch<'e, 'q, E>(&'e mut self, query: E) -> >::Cursor + /// Execute the query and return all the generated results, collected into a [`Vec`]. + fn fetch_all<'q: 'c, E>( + self, + query: E, + ) -> BoxFuture<'c, Result::Row>, Error>> + where + E: Execute<'q, Self::Database>, + { + self.fetch(query).try_collect().boxed() + } + + /// Execute the query and returns exactly one row. + fn fetch_one<'q: 'c, E>( + self, + query: E, + ) -> BoxFuture<'c, Result<::Row, Error>> + where + E: Execute<'q, Self::Database>, + { + self.fetch_optional(query) + .and_then(|row| match row { + Some(row) => future::ok(row), + None => future::err(Error::RowNotFound), + }) + .boxed() + } + + /// Execute the query and returns at most one row. + fn fetch_optional<'q: 'c, E>( + self, + query: E, + ) -> BoxFuture<'c, Result::Row>, Error>> where E: Execute<'q, Self::Database>; /// Prepare the SQL query and return type information about its parameters /// and results. /// - /// This is used by the query macros during compilation to + /// This is used by compile-time verification in the query macros to /// power their type inference. #[doc(hidden)] - fn describe<'e, 'q, E: 'e>( - &'e mut self, + fn describe<'q: 'c, E>( + self, query: E, - ) -> BoxFuture<'e, crate::Result>> - where - E: Execute<'q, Self::Database>; -} - -// HACK: Generic Associated Types (GATs) will enable us to rework how the Executor bound is done -// in Query to remove the need for this. -pub trait RefExecutor<'e> { - type Database: Database; - - fn fetch_by_ref<'q, E>(self, query: E) -> >::Cursor + ) -> BoxFuture<'c, Result, Error>> where E: Execute<'q, Self::Database>; } /// A type that may be executed against a database connection. -pub trait Execute<'q, DB> -where - Self: Send, - DB: Database, -{ - /// Returns the query to be executed and the arguments to bind against the query, if any. +/// +/// Implemented for the following: +/// +/// * [`&str`] +/// * [`Query`] +/// +pub trait Execute<'q, DB: Database>: Send { + /// Returns the query string that will be executed. + fn query(&self) -> &'q str; + + /// Returns the arguments to be bound against the query string. /// /// Returning `None` for `Arguments` indicates to use a "simple" query protocol and to not /// prepare the query. Returning `Some(Default::default())` is an empty arguments object that /// will be prepared (and cached) before execution. - fn into_parts(self) -> (&'q str, Option); - - /// Returns the query string, without any parameters replaced. - #[doc(hidden)] - fn query_string(&self) -> &'q str; + fn take_arguments(&mut self) -> Option<>::Arguments>; } -impl<'q, DB> Execute<'q, DB> for &'q str -where - DB: Database, -{ +// NOTE: `Execute` is explicitly not implemented for String and &String to make it slightly more +// involved to write `conn.execute(format!("SELECT {}", val))` +impl<'q, DB: Database> Execute<'q, DB> for &'q str { #[inline] - fn into_parts(self) -> (&'q str, Option) { - (self, None) - } - - #[inline] - #[doc(hidden)] - fn query_string(&self) -> &'q str { + fn query(&self) -> &'q str { self } -} - -impl Executor for &'_ mut T -where - T: Executor, -{ - type Database = T::Database; - - fn execute<'e, 'q: 'e, 'c: 'e, E: 'e>( - &'c mut self, - query: E, - ) -> BoxFuture<'e, crate::Result> - where - E: Execute<'q, Self::Database>, - { - (**self).execute(query) - } - - fn fetch<'e, 'q, E>(&'e mut self, query: E) -> >::Cursor - where - E: Execute<'q, Self::Database>, - { - (**self).fetch(query) - } - - #[doc(hidden)] - fn describe<'e, 'q, E: 'e>( - &'e mut self, - query: E, - ) -> BoxFuture<'e, crate::Result>> - where - E: Execute<'q, Self::Database>, - { - (**self).describe(query) - } -} - -// The following impl lets `&mut &Pool` continue to work -// This pattern was required in SQLx < 0.3 -// Going forward users will likely naturally use `&Pool` instead - -impl<'c, T> RefExecutor<'c> for &'c mut T -where - T: Copy + RefExecutor<'c>, -{ - type Database = T::Database; #[inline] - fn fetch_by_ref<'q, E>(self, query: E) -> >::Cursor - where - E: Execute<'q, Self::Database>, - { - (*self).fetch_by_ref(query) + fn take_arguments(&mut self) -> Option<>::Arguments> { + None } } diff --git a/sqlx-core/src/ext/mod.rs b/sqlx-core/src/ext/mod.rs new file mode 100644 index 00000000..4e920bb8 --- /dev/null +++ b/sqlx-core/src/ext/mod.rs @@ -0,0 +1 @@ +pub mod ustr; diff --git a/sqlx-core/src/ext/ustr.rs b/sqlx-core/src/ext/ustr.rs new file mode 100644 index 00000000..d8c1f00f --- /dev/null +++ b/sqlx-core/src/ext/ustr.rs @@ -0,0 +1,76 @@ +use std::borrow::Borrow; +use std::fmt::{self, Display, Formatter}; +use std::hash::{Hash, Hasher}; +use std::ops::Deref; +use std::sync::Arc; + +// U meaning micro +// a micro-string is either a reference-counted string or a static string +// this guarantees these are cheap to clone everywhere +#[derive(Debug, Clone, Eq)] +pub(crate) enum UStr { + Static(&'static str), + Shared(Arc), +} + +impl UStr { + #[allow(dead_code)] + pub(crate) fn new(s: &str) -> Self { + UStr::Shared(Arc::from(s.to_owned())) + } +} + +impl Deref for UStr { + type Target = str; + + #[inline] + fn deref(&self) -> &str { + match self { + UStr::Static(s) => s, + UStr::Shared(s) => s, + } + } +} + +impl Hash for UStr { + #[inline] + fn hash(&self, state: &mut H) { + // Forward the hash to the string representation of this + // A derive(Hash) encodes the enum discriminant + (&**self).hash(state); + } +} + +impl Borrow for UStr { + #[inline] + fn borrow(&self) -> &str { + &**self + } +} + +impl PartialEq for UStr { + fn eq(&self, other: &UStr) -> bool { + (**self).eq(&**other) + } +} + +impl From<&'static str> for UStr { + #[inline] + fn from(s: &'static str) -> Self { + UStr::Static(s) + } +} + +impl From for UStr { + #[inline] + fn from(s: String) -> Self { + UStr::Shared(s.into()) + } +} + +impl Display for UStr { + #[inline] + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + f.pad(self) + } +} diff --git a/sqlx-core/src/from_row.rs b/sqlx-core/src/from_row.rs new file mode 100644 index 00000000..3c26df37 --- /dev/null +++ b/sqlx-core/src/from_row.rs @@ -0,0 +1,116 @@ +use crate::error::Error; +use crate::row::Row; + +/// A record that can be built from a row returned by the database. +/// +/// In order to use [`query_as`] the output type must implement `FromRow`. +/// +/// # Deriving +/// +/// This trait can be automatically derived by SQLx for any struct. The generated implementation +/// will consist of a sequence of calls to [`Row::try_get`] using the name from each +/// struct field. +/// +/// ```rust,ignore +/// #[derive(sqlx::FromRow)] +/// struct User { +/// id: i32, +/// name: String, +/// } +/// ``` +/// +/// [`query_as`]: crate::query_as +/// [`Row::try_get`]: crate::row::Row::try_get +pub trait FromRow<'r, R: Row>: Sized { + fn from_row(row: &'r R) -> Result; +} + +// implement FromRow for tuples of types that implement Decode +// up to tuples of 9 values + +macro_rules! impl_from_row_for_tuple { + ($( ($idx:tt) -> $T:ident );+;) => { + impl<'r, R, $($T,)+> FromRow<'r, R> for ($($T,)+) + where + R: Row, + $($T: crate::decode::Decode<'r, R::Database>,)+ + { + #[inline] + fn from_row(row: &'r R) -> Result { + Ok(($(row.try_get($idx as usize)?,)+)) + } + } + }; +} + +impl_from_row_for_tuple!( + (0) -> T1; +); + +impl_from_row_for_tuple!( + (0) -> T1; + (1) -> T2; +); + +impl_from_row_for_tuple!( + (0) -> T1; + (1) -> T2; + (2) -> T3; +); + +impl_from_row_for_tuple!( + (0) -> T1; + (1) -> T2; + (2) -> T3; + (3) -> T4; +); + +impl_from_row_for_tuple!( + (0) -> T1; + (1) -> T2; + (2) -> T3; + (3) -> T4; + (4) -> T5; +); + +impl_from_row_for_tuple!( + (0) -> T1; + (1) -> T2; + (2) -> T3; + (3) -> T4; + (4) -> T5; + (5) -> T6; +); + +impl_from_row_for_tuple!( + (0) -> T1; + (1) -> T2; + (2) -> T3; + (3) -> T4; + (4) -> T5; + (5) -> T6; + (6) -> T7; +); + +impl_from_row_for_tuple!( + (0) -> T1; + (1) -> T2; + (2) -> T3; + (3) -> T4; + (4) -> T5; + (5) -> T6; + (6) -> T7; + (7) -> T8; +); + +impl_from_row_for_tuple!( + (0) -> T1; + (1) -> T2; + (2) -> T3; + (3) -> T4; + (4) -> T5; + (5) -> T6; + (6) -> T7; + (7) -> T8; + (8) -> T9; +); diff --git a/sqlx-core/src/io/buf.rs b/sqlx-core/src/io/buf.rs index 2c16e30b..f89d8e23 100644 --- a/sqlx-core/src/io/buf.rs +++ b/sqlx-core/src/io/buf.rs @@ -1,144 +1,58 @@ -use byteorder::ByteOrder; +use std::str::from_utf8; + +use bytes::{Buf, Bytes}; use memchr::memchr; -use std::{io, slice, str}; -pub trait Buf<'a> { - fn advance(&mut self, cnt: usize); +use crate::error::Error; - fn get_uint(&mut self, n: usize) -> io::Result; +pub trait BufExt: Buf { + // Read a nul-terminated byte sequence + fn get_bytes_nul(&mut self) -> Result; - fn get_i8(&mut self) -> io::Result; + // Read a byte sequence of the exact length + fn get_bytes(&mut self, len: usize) -> Bytes; - fn get_u8(&mut self) -> io::Result; + // Read a nul-terminated string + fn get_str_nul(&mut self) -> Result; - fn get_u16(&mut self) -> io::Result; - - fn get_i16(&mut self) -> io::Result; - - fn get_u24(&mut self) -> io::Result; - - fn get_i32(&mut self) -> io::Result; - - fn get_i64(&mut self) -> io::Result; - - fn get_u32(&mut self) -> io::Result; - - fn get_u64(&mut self) -> io::Result; - - fn get_str(&mut self, len: usize) -> io::Result<&'a str>; - - fn get_str_nul(&mut self) -> io::Result<&'a str>; - - fn get_bytes(&mut self, len: usize) -> io::Result<&'a [u8]>; + // Read a string of the exact length + fn get_str(&mut self, len: usize) -> Result; } -impl<'a> Buf<'a> for &'a [u8] { - fn advance(&mut self, cnt: usize) { - *self = &self[cnt..]; +impl BufExt for Bytes { + fn get_bytes_nul(&mut self) -> Result { + let nul = memchr(b'\0', self.bytes()) + .ok_or_else(|| err_protocol!("expected NUL in byte sequence"))?; + + let v = self.slice(0..nul); + + self.advance(nul + 1); + + Ok(v) } - fn get_uint(&mut self, n: usize) -> io::Result { - let val = T::read_uint(*self, n); - self.advance(n); - - Ok(val) - } - - fn get_i8(&mut self) -> io::Result { - let val = self[0]; - self.advance(1); - - Ok(val as i8) - } - - fn get_u8(&mut self) -> io::Result { - let val = self[0]; - self.advance(1); - - Ok(val) - } - - fn get_u16(&mut self) -> io::Result { - let val = T::read_u16(*self); - self.advance(2); - - Ok(val) - } - - fn get_i16(&mut self) -> io::Result { - let val = T::read_i16(*self); - self.advance(2); - - Ok(val) - } - - fn get_u24(&mut self) -> io::Result { - let val = T::read_u24(*self); - self.advance(3); - - Ok(val) - } - - fn get_i32(&mut self) -> io::Result { - let val = T::read_i32(*self); - self.advance(4); - - Ok(val) - } - - fn get_i64(&mut self) -> io::Result { - let val = T::read_i64(*self); - self.advance(4); - - Ok(val) - } - - fn get_u32(&mut self) -> io::Result { - let val = T::read_u32(*self); - self.advance(4); - - Ok(val) - } - - fn get_u64(&mut self) -> io::Result { - let val = T::read_u64(*self); - self.advance(8); - - Ok(val) - } - - fn get_str(&mut self, len: usize) -> io::Result<&'a str> { - str::from_utf8(self.get_bytes(len)?) - .map_err(|err| io::Error::new(io::ErrorKind::InvalidData, err)) - } - - fn get_str_nul(&mut self) -> io::Result<&'a str> { - let len = memchr(b'\0', &*self).ok_or(io::ErrorKind::InvalidData)?; - let s = &self.get_str(len + 1)?[..len]; - - Ok(s) - } - - fn get_bytes(&mut self, len: usize) -> io::Result<&'a [u8]> { - let buf = &self[..len]; + fn get_bytes(&mut self, len: usize) -> Bytes { + let v = self.slice(..len); self.advance(len); - Ok(buf) - } -} - -pub trait ToBuf { - fn to_buf(&self) -> &[u8]; -} - -impl ToBuf for [u8] { - fn to_buf(&self) -> &[u8] { - self - } -} - -impl ToBuf for u8 { - fn to_buf(&self) -> &[u8] { - slice::from_ref(self) + v + } + + fn get_str_nul(&mut self) -> Result { + self.get_bytes_nul().and_then(|bytes| { + from_utf8(&*bytes) + .map(ToOwned::to_owned) + .map_err(|err| err_protocol!("{}", err)) + }) + } + + fn get_str(&mut self, len: usize) -> Result { + let v = from_utf8(&self[..len]) + .map_err(|err| err_protocol!("{}", err)) + .map(ToOwned::to_owned)?; + + self.advance(len); + + Ok(v) } } diff --git a/sqlx-core/src/io/buf_mut.rs b/sqlx-core/src/io/buf_mut.rs index 8ebe8891..565d8500 100644 --- a/sqlx-core/src/io/buf_mut.rs +++ b/sqlx-core/src/io/buf_mut.rs @@ -1,85 +1,12 @@ -use byteorder::ByteOrder; -use std::{str, u16, u32, u8}; +use bytes::BufMut; -pub trait BufMut { - fn advance(&mut self, cnt: usize); - - fn put_u8(&mut self, val: u8); - - fn put_u16(&mut self, val: u16); - - fn put_i16(&mut self, val: i16); - - fn put_u24(&mut self, val: u32); - - fn put_i32(&mut self, val: i32); - - fn put_u32(&mut self, val: u32); - - fn put_u64(&mut self, val: u64); - - fn put_bytes(&mut self, val: &[u8]); - - fn put_str(&mut self, val: &str); - - fn put_str_nul(&mut self, val: &str); +pub trait BufMutExt: BufMut { + fn put_str_nul(&mut self, s: &str); } -impl BufMut for Vec { - fn advance(&mut self, cnt: usize) { - self.resize(self.len() + cnt, 0); - } - - fn put_u8(&mut self, val: u8) { - self.push(val); - } - - fn put_u16(&mut self, val: u16) { - let mut buf = [0; 2]; - T::write_u16(&mut buf, val); - self.extend_from_slice(&buf); - } - - fn put_i16(&mut self, val: i16) { - let mut buf = [0; 2]; - T::write_i16(&mut buf, val); - self.extend_from_slice(&buf); - } - - fn put_u24(&mut self, val: u32) { - let mut buf = [0; 3]; - T::write_u24(&mut buf, val); - self.extend_from_slice(&buf); - } - - fn put_i32(&mut self, val: i32) { - let mut buf = [0; 4]; - T::write_i32(&mut buf, val); - self.extend_from_slice(&buf); - } - - fn put_u32(&mut self, val: u32) { - let mut buf = [0; 4]; - T::write_u32(&mut buf, val); - self.extend_from_slice(&buf); - } - - fn put_u64(&mut self, val: u64) { - let mut buf = [0; 8]; - T::write_u64(&mut buf, val); - self.extend_from_slice(&buf); - } - - fn put_bytes(&mut self, val: &[u8]) { - self.extend_from_slice(val); - } - - fn put_str(&mut self, val: &str) { - self.extend_from_slice(val.as_bytes()); - } - - fn put_str_nul(&mut self, val: &str) { - self.put_str(val); +impl BufMutExt for Vec { + fn put_str_nul(&mut self, s: &str) { + self.extend(s.as_bytes()); self.push(0); } } diff --git a/sqlx-core/src/io/buf_stream.rs b/sqlx-core/src/io/buf_stream.rs index c33d5fab..f4af7475 100644 --- a/sqlx-core/src/io/buf_stream.rs +++ b/sqlx-core/src/io/buf_stream.rs @@ -1,33 +1,28 @@ -use std::future::Future; -use std::io::{self, BufRead}; +#![allow(dead_code)] + +use std::io; use std::ops::{Deref, DerefMut}; -use std::pin::Pin; -use std::task::{Context, Poll}; -use futures_util::ready; +use bytes::BytesMut; +use sqlx_rt::{AsyncRead, AsyncReadExt, AsyncWrite}; -use crate::runtime::{AsyncRead, AsyncReadExt, AsyncWrite}; +use crate::error::Error; +use crate::io::write_and_flush::WriteAndFlush; +use crate::io::{decode::Decode, encode::Encode}; +use std::io::Cursor; -const RBUF_SIZE: usize = 8 * 1024; +pub struct BufStream +where + S: AsyncRead + AsyncWrite + Unpin, +{ + stream: S, -pub struct BufStream { - pub(crate) stream: S, + // writes with `write` to the underlying stream are buffered + // this can be flushed with `flush` + pub(crate) wbuf: Vec, - // Have we reached end-of-file (been disconnected) - stream_eof: bool, - - // Buffer used when sending outgoing messages - wbuf: Vec, - - // Buffer used when reading incoming messages - rbuf: Vec, - rbuf_rindex: usize, - rbuf_windex: usize, -} - -pub struct GuardedFlush<'a, S: 'a> { - stream: &'a mut S, - buf: io::Cursor<&'a mut Vec>, + // we read into the read buffer using 100% safe code + rbuf: BytesMut, } impl BufStream @@ -37,109 +32,72 @@ where pub fn new(stream: S) -> Self { Self { stream, - stream_eof: false, - wbuf: Vec::with_capacity(1024), - rbuf: vec![0; RBUF_SIZE], - rbuf_rindex: 0, - rbuf_windex: 0, + wbuf: Vec::with_capacity(512), + rbuf: BytesMut::with_capacity(4096), } } - #[cfg(feature = "postgres")] - #[inline] - pub fn buffer<'c>(&'c self) -> &'c [u8] { - &self.rbuf[self.rbuf_rindex..] + pub fn write<'en, T>(&mut self, value: T) + where + T: Encode<'en, ()>, + { + self.write_with(value, ()) } - #[inline] - pub fn buffer_mut(&mut self) -> &mut Vec { - &mut self.wbuf + pub fn write_with<'en, T, C>(&mut self, value: T, context: C) + where + T: Encode<'en, C>, + { + value.encode_with(&mut self.wbuf, context); } - #[inline] - #[must_use = "write buffer is cleared on-drop even if future is not polled"] - pub fn flush(&mut self) -> GuardedFlush { - GuardedFlush { + pub fn flush(&mut self) -> WriteAndFlush<'_, S> { + WriteAndFlush { stream: &mut self.stream, - buf: io::Cursor::new(&mut self.wbuf), + buf: Cursor::new(&mut self.wbuf), } } - #[inline] - pub fn consume(&mut self, cnt: usize) { - self.rbuf_rindex += cnt; + pub async fn read<'de, T>(&mut self, cnt: usize) -> Result + where + T: Decode<'de, ()>, + { + self.read_with(cnt, ()).await } - pub async fn peek(&mut self, cnt: usize) -> io::Result<&[u8]> { - self.try_peek(cnt) - .await - .transpose() - .ok_or(io::ErrorKind::ConnectionAborted)? - } + pub async fn read_with<'de, T, C>(&mut self, cnt: usize, context: C) -> Result + where + T: Decode<'de, C>, + { + // zero-fills the space in the read buffer + self.rbuf.resize(cnt, 0); - pub async fn try_peek(&mut self, cnt: usize) -> io::Result> { - loop { - // Reaching end-of-file (read 0 bytes) will continuously - // return None from all future calls to read - if self.stream_eof { - return Ok(None); - } - - // If we have enough bytes in our read buffer, - // return immediately - if self.rbuf_windex >= (self.rbuf_rindex + cnt) { - let buf = &self.rbuf[self.rbuf_rindex..(self.rbuf_rindex + cnt)]; - - return Ok(Some(buf)); - } - - // If we are out of space to write to in the read buffer .. - if self.rbuf.len() < (self.rbuf_windex + cnt) { - if self.rbuf_rindex == self.rbuf_windex { - // We have consumed all data; simply reset the indexes - self.rbuf_rindex = 0; - self.rbuf_windex = 0; - } else { - // Allocate a new buffer - let mut new_rbuf = Vec::with_capacity(RBUF_SIZE); - - // Take the minimum of the read and write indexes - let min_index = self.rbuf_rindex.min(self.rbuf_windex); - - // Copy the old buffer to our new buffer - new_rbuf.extend_from_slice(&self.rbuf[min_index..]); - - // Zero-extend the new buffer - new_rbuf.resize(new_rbuf.capacity(), 0); - - // Replace the old buffer with our new buffer - self.rbuf = new_rbuf; - - // And reduce the indexes - self.rbuf_rindex -= min_index; - self.rbuf_windex -= min_index; - } - - // Do we need more space still - if self.rbuf.len() < (self.rbuf_windex + cnt) { - let needed = (self.rbuf_windex + cnt) - self.rbuf.len(); - - self.rbuf.resize(self.rbuf.len() + needed, 0); - } - } - - let n = self.stream.read(&mut self.rbuf[self.rbuf_windex..]).await?; - - self.rbuf_windex += n; + let mut read = 0; + while cnt > read { + // read in bytes from the stream into the read buffer starting + // from the offset we last read from + let n = self.stream.read(&mut self.rbuf[read..]).await?; if n == 0 { - self.stream_eof = true; + // a zero read when we had space in the read buffer + // should be treated as an EOF + + // and an unexpected EOF means the server told us to go away + + return Err(io::Error::from(io::ErrorKind::ConnectionAborted).into()); } + + read += n; } + + T::decode_with(self.rbuf.split_to(cnt).freeze(), context) } } -impl Deref for BufStream { +impl Deref for BufStream +where + S: AsyncRead + AsyncWrite + Unpin, +{ type Target = S; fn deref(&self) -> &Self::Target { @@ -147,53 +105,11 @@ impl Deref for BufStream { } } -impl DerefMut for BufStream { +impl DerefMut for BufStream +where + S: AsyncRead + AsyncWrite + Unpin, +{ fn deref_mut(&mut self) -> &mut Self::Target { &mut self.stream } } - -// TODO: Find a nicer way to do this -// Return `Ok(None)` immediately from a function if the wrapped value is `None` -#[allow(unused)] -macro_rules! ret_if_none { - ($val:expr) => { - match $val { - Some(val) => val, - None => { - return Ok(None); - } - } - }; -} - -impl<'a, S: AsyncWrite + Unpin> Future for GuardedFlush<'a, S> { - type Output = io::Result<()>; - - fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { - let Self { - ref mut stream, - ref mut buf, - } = *self; - - loop { - let read = buf.fill_buf()?; - - if !read.is_empty() { - let written = ready!(Pin::new(&mut *stream).poll_write(cx, read)?); - buf.consume(written); - } else { - break; - } - } - - Pin::new(stream).poll_flush(cx) - } -} - -impl<'a, S> Drop for GuardedFlush<'a, S> { - fn drop(&mut self) { - // clear the buffer regardless of whether the flush succeeded or not - self.buf.get_mut().clear(); - } -} diff --git a/sqlx-core/src/io/decode.rs b/sqlx-core/src/io/decode.rs new file mode 100644 index 00000000..2f397127 --- /dev/null +++ b/sqlx-core/src/io/decode.rs @@ -0,0 +1,29 @@ +use bytes::Bytes; + +use crate::error::Error; + +pub trait Decode<'de, Context = ()> +where + Self: Sized, +{ + fn decode(buf: Bytes) -> Result + where + Self: Decode<'de, ()>, + { + Self::decode_with(buf, ()) + } + + fn decode_with(buf: Bytes, context: Context) -> Result; +} + +impl Decode<'_> for Bytes { + fn decode_with(buf: Bytes, _: ()) -> Result { + Ok(buf) + } +} + +impl Decode<'_> for () { + fn decode_with(_: Bytes, _: ()) -> Result<(), Error> { + Ok(()) + } +} diff --git a/sqlx-core/src/io/encode.rs b/sqlx-core/src/io/encode.rs new file mode 100644 index 00000000..a417ef9e --- /dev/null +++ b/sqlx-core/src/io/encode.rs @@ -0,0 +1,16 @@ +pub trait Encode<'en, Context = ()> { + fn encode(&self, buf: &mut Vec) + where + Self: Encode<'en, ()>, + { + self.encode_with(buf, ()); + } + + fn encode_with(&self, buf: &mut Vec, context: Context); +} + +impl<'en, C> Encode<'en, C> for &'_ [u8] { + fn encode_with(&self, buf: &mut Vec, _: C) { + buf.extend_from_slice(self); + } +} diff --git a/sqlx-core/src/io/mod.rs b/sqlx-core/src/io/mod.rs index ff825306..f9949654 100644 --- a/sqlx-core/src/io/mod.rs +++ b/sqlx-core/src/io/mod.rs @@ -1,30 +1,12 @@ -#[macro_use] -mod buf_stream; - mod buf; mod buf_mut; -mod byte_str; -mod tls; +mod buf_stream; +mod decode; +mod encode; +mod write_and_flush; -pub use self::{ - buf::{Buf, ToBuf}, - buf_mut::BufMut, - buf_stream::BufStream, - byte_str::ByteStr, - tls::MaybeTlsStream, -}; - -#[cfg(test)] -#[doc(hidden)] -macro_rules! bytes ( - ($($b: expr), *) => {{ - use $crate::io::ToBuf; - - let mut buf = Vec::new(); - $( - buf.extend_from_slice($b.to_buf()); - )* - - buf - }} -); +pub use buf::BufExt; +pub use buf_mut::BufMutExt; +pub use buf_stream::BufStream; +pub use decode::Decode; +pub use encode::Encode; diff --git a/sqlx-core/src/io/write_and_flush.rs b/sqlx-core/src/io/write_and_flush.rs new file mode 100644 index 00000000..f8350da0 --- /dev/null +++ b/sqlx-core/src/io/write_and_flush.rs @@ -0,0 +1,45 @@ +use crate::error::Error; +use futures_core::Future; +use futures_util::ready; +use sqlx_rt::AsyncWrite; +use std::io::{BufRead, Cursor}; +use std::pin::Pin; +use std::task::{Context, Poll}; + +// Atomic operation that writes the full buffer to the stream, flushes the stream, and then +// clears the buffer (even if either of the two previous operations failed). +pub struct WriteAndFlush<'a, S: 'a> { + pub(super) stream: &'a mut S, + pub(super) buf: Cursor<&'a mut Vec>, +} + +impl<'a, S: AsyncWrite + Unpin> Future for WriteAndFlush<'a, S> { + type Output = Result<(), Error>; + + fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + let Self { + ref mut stream, + ref mut buf, + } = *self; + + loop { + let read = buf.fill_buf()?; + + if !read.is_empty() { + let written = ready!(Pin::new(&mut *stream).poll_write(cx, read)?); + buf.consume(written); + } else { + break; + } + } + + Pin::new(stream).poll_flush(cx).map_err(Error::Io) + } +} + +impl<'a, S> Drop for WriteAndFlush<'a, S> { + fn drop(&mut self) { + // clear the buffer regardless of whether the flush succeeded or not + self.buf.get_mut().clear(); + } +} diff --git a/sqlx-core/src/lib.rs b/sqlx-core/src/lib.rs index d692eec9..d3238db9 100644 --- a/sqlx-core/src/lib.rs +++ b/sqlx-core/src/lib.rs @@ -1,68 +1,42 @@ -//! Core of SQLx, the rust SQL toolkit. Not intended to be used directly. - +//! Core of SQLx, the rust SQL toolkit. +//! Not intended to be used directly. +#![recursion_limit = "512"] +// +// Allows an API be documented as only available in some specific platforms. +// +#![cfg_attr(docsrs, feature(doc_cfg))] +// // When compiling with support for SQLite we must allow some unsafe code in order to // interface with the inherently unsafe C module. This unsafe code is contained // to the sqlite module. #![cfg_attr(feature = "sqlite", deny(unsafe_code))] #![cfg_attr(not(feature = "sqlite"), forbid(unsafe_code))] -#![recursion_limit = "512"] -#![cfg_attr(docsrs, feature(doc_cfg))] -#![cfg_attr(all(test, feature = "bench"), feature(test))] -// #![warn(missing_docs)] -#[cfg(all(test, feature = "bench"))] -extern crate test; - -// HACK: Allow a feature name the same name as a dependency #[cfg(feature = "bigdecimal")] extern crate bigdecimal_ as bigdecimal; -mod runtime; - #[macro_use] pub mod error; -#[cfg(any(feature = "mysql", feature = "postgres"))] -#[macro_use] -mod io; - -pub mod connection; -pub mod cursor; -pub mod database; -pub mod value; - -#[macro_use] -pub mod executor; - -pub mod transaction; -mod url; - -#[macro_use] pub mod arguments; +pub mod connection; +pub mod database; pub mod decode; - -#[doc(hidden)] pub mod describe; - pub mod encode; +pub mod executor; +mod ext; +pub mod from_row; +mod io; +mod net; pub mod pool; pub mod query; - -#[macro_use] pub mod query_as; - -pub mod types; - -#[macro_use] +pub mod query_scalar; pub mod row; - -#[cfg(any(feature = "mysql", feature = "postgres", feature = "sqlite"))] -#[macro_use] -mod logging; - -#[cfg(feature = "mysql")] -#[cfg_attr(docsrs, doc(cfg(feature = "mysql")))] -pub mod mysql; +pub mod type_info; +pub mod types; +pub mod value; #[cfg(feature = "postgres")] #[cfg_attr(docsrs, doc(cfg(feature = "postgres")))] @@ -72,4 +46,6 @@ pub mod postgres; #[cfg_attr(docsrs, doc(cfg(feature = "sqlite")))] pub mod sqlite; -pub use error::{Error, Result}; +#[cfg(feature = "mysql")] +#[cfg_attr(docsrs, doc(cfg(feature = "mysql")))] +pub mod mysql; diff --git a/sqlx-core/src/net/mod.rs b/sqlx-core/src/net/mod.rs new file mode 100644 index 00000000..b3bca645 --- /dev/null +++ b/sqlx-core/src/net/mod.rs @@ -0,0 +1,5 @@ +mod socket; +mod tls; + +pub use socket::Socket; +pub use tls::MaybeTlsStream; diff --git a/sqlx-core/src/net/socket.rs b/sqlx-core/src/net/socket.rs new file mode 100644 index 00000000..fbfc7cf1 --- /dev/null +++ b/sqlx-core/src/net/socket.rs @@ -0,0 +1,140 @@ +#![allow(dead_code)] + +use std::io; +use std::net::Shutdown; +use std::pin::Pin; +use std::task::{Context, Poll}; + +use sqlx_rt::{AsyncRead, AsyncWrite, TcpStream}; + +#[derive(Debug)] +pub enum Socket { + Tcp(TcpStream), + + #[cfg(unix)] + Unix(sqlx_rt::UnixStream), +} + +impl Socket { + #[cfg(not(unix))] + pub async fn connect(host: &str, port: u16) -> io::Result { + TcpStream::connect((host, port)).await.map(Socket::Tcp) + } + + #[cfg(unix)] + pub async fn connect(host: &str, port: u16) -> io::Result { + if host.starts_with('/') { + // if the host starts with a forward slash, assume that this is a request + // to connect to a local socket + sqlx_rt::UnixStream::connect(format!("{}/.s.PGSQL.{}", host, port)) + .await + .map(Socket::Unix) + } else { + TcpStream::connect((host, port)).await.map(Socket::Tcp) + } + } + + pub fn shutdown(&self) -> io::Result<()> { + match self { + Socket::Tcp(s) => s.shutdown(Shutdown::Both), + + #[cfg(unix)] + Socket::Unix(s) => s.shutdown(Shutdown::Both), + } + } +} + +impl AsyncRead for Socket { + fn poll_read( + mut self: Pin<&mut Self>, + cx: &mut Context<'_>, + buf: &mut [u8], + ) -> Poll> { + match &mut *self { + Socket::Tcp(s) => Pin::new(s).poll_read(cx, buf), + + #[cfg(unix)] + Socket::Unix(s) => Pin::new(s).poll_read(cx, buf), + } + } + + #[cfg(any(feature = "runtime-actix", feature = "runtime-tokio"))] + fn poll_read_buf( + mut self: Pin<&mut Self>, + cx: &mut Context<'_>, + buf: &mut B, + ) -> Poll> + where + Self: Sized, + B: bytes::BufMut, + { + match &mut *self { + Socket::Tcp(s) => Pin::new(s).poll_read_buf(cx, buf), + + #[cfg(unix)] + Socket::Unix(s) => Pin::new(s).poll_read_buf(cx, buf), + } + } +} + +impl AsyncWrite for Socket { + fn poll_write( + mut self: Pin<&mut Self>, + cx: &mut Context<'_>, + buf: &[u8], + ) -> Poll> { + match &mut *self { + Socket::Tcp(s) => Pin::new(s).poll_write(cx, buf), + + #[cfg(unix)] + Socket::Unix(s) => Pin::new(s).poll_write(cx, buf), + } + } + + fn poll_flush(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { + match &mut *self { + Socket::Tcp(s) => Pin::new(s).poll_flush(cx), + + #[cfg(unix)] + Socket::Unix(s) => Pin::new(s).poll_flush(cx), + } + } + + #[cfg(any(feature = "runtime-actix", feature = "runtime-tokio"))] + fn poll_shutdown(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { + match &mut *self { + Socket::Tcp(s) => Pin::new(s).poll_shutdown(cx), + + #[cfg(unix)] + Socket::Unix(s) => Pin::new(s).poll_shutdown(cx), + } + } + + #[cfg(feature = "runtime-async-std")] + fn poll_close(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { + match &mut *self { + Socket::Tcp(s) => Pin::new(s).poll_close(cx), + + #[cfg(unix)] + Socket::Unix(s) => Pin::new(s).poll_close(cx), + } + } + + #[cfg(any(feature = "runtime-actix", feature = "runtime-tokio"))] + fn poll_write_buf( + mut self: Pin<&mut Self>, + cx: &mut Context<'_>, + buf: &mut B, + ) -> Poll> + where + Self: Sized, + B: bytes::Buf, + { + match &mut *self { + Socket::Tcp(s) => Pin::new(s).poll_write_buf(cx, buf), + + #[cfg(unix)] + Socket::Unix(s) => Pin::new(s).poll_write_buf(cx, buf), + } + } +} diff --git a/sqlx-core/src/net/tls.rs b/sqlx-core/src/net/tls.rs new file mode 100644 index 00000000..9e0463af --- /dev/null +++ b/sqlx-core/src/net/tls.rs @@ -0,0 +1,197 @@ +#![allow(dead_code)] + +use std::io; +use std::ops::{Deref, DerefMut}; +use std::pin::Pin; +use std::task::{Context, Poll}; + +use sqlx_rt::{AsyncRead, AsyncWrite, TlsConnector, TlsStream}; + +use crate::error::Error; +use std::mem::replace; + +pub enum MaybeTlsStream +where + S: AsyncRead + AsyncWrite + Unpin, +{ + Raw(S), + Tls(TlsStream), + Upgrading, +} + +impl MaybeTlsStream +where + S: AsyncRead + AsyncWrite + Unpin, +{ + #[inline] + pub fn is_tls(&self) -> bool { + matches!(self, Self::Tls(_)) + } + + pub async fn upgrade(&mut self, host: &str, connector: TlsConnector) -> Result<(), Error> { + let stream = match replace(self, MaybeTlsStream::Upgrading) { + MaybeTlsStream::Raw(stream) => stream, + + MaybeTlsStream::Tls(_) => { + // ignore upgrade, we are already a TLS connection + return Ok(()); + } + + MaybeTlsStream::Upgrading => { + // we previously failed to upgrade and now hold no connection + // this should only happen from an internal misuse of this method + return Err(Error::Io(io::ErrorKind::ConnectionAborted.into())); + } + }; + + *self = MaybeTlsStream::Tls( + connector + .connect(host, stream) + .await + .map_err(|err| Error::Tls(err.into()))?, + ); + + Ok(()) + } +} + +impl AsyncRead for MaybeTlsStream +where + S: Unpin + AsyncWrite + AsyncRead, +{ + fn poll_read( + mut self: Pin<&mut Self>, + cx: &mut Context<'_>, + buf: &mut [u8], + ) -> Poll> { + match &mut *self { + MaybeTlsStream::Raw(s) => Pin::new(s).poll_read(cx, buf), + MaybeTlsStream::Tls(s) => Pin::new(s).poll_read(cx, buf), + + MaybeTlsStream::Upgrading => Poll::Ready(Err(io::ErrorKind::ConnectionAborted.into())), + } + } + + #[cfg(any(feature = "runtime-actix", feature = "runtime-tokio"))] + fn poll_read_buf( + mut self: Pin<&mut Self>, + cx: &mut Context<'_>, + buf: &mut B, + ) -> Poll> + where + Self: Sized, + B: bytes::BufMut, + { + match &mut *self { + MaybeTlsStream::Raw(s) => Pin::new(s).poll_read_buf(cx, buf), + MaybeTlsStream::Tls(s) => Pin::new(s).poll_read_buf(cx, buf), + + MaybeTlsStream::Upgrading => Poll::Ready(Err(io::ErrorKind::ConnectionAborted.into())), + } + } +} + +impl AsyncWrite for MaybeTlsStream +where + S: Unpin + AsyncWrite + AsyncRead, +{ + fn poll_write( + mut self: Pin<&mut Self>, + cx: &mut Context<'_>, + buf: &[u8], + ) -> Poll> { + match &mut *self { + MaybeTlsStream::Raw(s) => Pin::new(s).poll_write(cx, buf), + MaybeTlsStream::Tls(s) => Pin::new(s).poll_write(cx, buf), + + MaybeTlsStream::Upgrading => Poll::Ready(Err(io::ErrorKind::ConnectionAborted.into())), + } + } + + fn poll_flush(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { + match &mut *self { + MaybeTlsStream::Raw(s) => Pin::new(s).poll_flush(cx), + MaybeTlsStream::Tls(s) => Pin::new(s).poll_flush(cx), + + MaybeTlsStream::Upgrading => Poll::Ready(Err(io::ErrorKind::ConnectionAborted.into())), + } + } + + #[cfg(any(feature = "runtime-actix", feature = "runtime-tokio"))] + fn poll_shutdown(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { + match &mut *self { + MaybeTlsStream::Raw(s) => Pin::new(s).poll_shutdown(cx), + MaybeTlsStream::Tls(s) => Pin::new(s).poll_shutdown(cx), + + MaybeTlsStream::Upgrading => Poll::Ready(Err(io::ErrorKind::ConnectionAborted.into())), + } + } + + #[cfg(feature = "runtime-async-std")] + fn poll_close(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { + match &mut *self { + MaybeTlsStream::Raw(s) => Pin::new(s).poll_close(cx), + MaybeTlsStream::Tls(s) => Pin::new(s).poll_close(cx), + + MaybeTlsStream::Upgrading => Poll::Ready(Err(io::ErrorKind::ConnectionAborted.into())), + } + } + + #[cfg(any(feature = "runtime-actix", feature = "runtime-tokio"))] + fn poll_write_buf( + mut self: Pin<&mut Self>, + cx: &mut Context<'_>, + buf: &mut B, + ) -> Poll> + where + Self: Sized, + B: bytes::Buf, + { + match &mut *self { + MaybeTlsStream::Raw(s) => Pin::new(s).poll_write_buf(cx, buf), + MaybeTlsStream::Tls(s) => Pin::new(s).poll_write_buf(cx, buf), + + MaybeTlsStream::Upgrading => Poll::Ready(Err(io::ErrorKind::ConnectionAborted.into())), + } + } +} + +impl Deref for MaybeTlsStream +where + S: Unpin + AsyncWrite + AsyncRead, +{ + type Target = S; + + fn deref(&self) -> &Self::Target { + match self { + MaybeTlsStream::Raw(s) => s, + + #[cfg(not(feature = "runtime-async-std"))] + MaybeTlsStream::Tls(s) => s.get_ref().get_ref().get_ref(), + + #[cfg(feature = "runtime-async-std")] + MaybeTlsStream::Tls(s) => s.get_ref(), + + MaybeTlsStream::Upgrading => panic!(io::Error::from(io::ErrorKind::ConnectionAborted)), + } + } +} + +impl DerefMut for MaybeTlsStream +where + S: Unpin + AsyncWrite + AsyncRead, +{ + fn deref_mut(&mut self) -> &mut Self::Target { + match self { + MaybeTlsStream::Raw(s) => s, + + #[cfg(not(feature = "runtime-async-std"))] + MaybeTlsStream::Tls(s) => s.get_mut().get_mut().get_mut(), + + #[cfg(feature = "runtime-async-std")] + MaybeTlsStream::Tls(s) => s.get_mut(), + + MaybeTlsStream::Upgrading => panic!(io::Error::from(io::ErrorKind::ConnectionAborted)), + } + } +} diff --git a/sqlx-core/src/pool/connection.rs b/sqlx-core/src/pool/connection.rs index 1953bb94..4fcb4b05 100644 --- a/sqlx-core/src/pool/connection.rs +++ b/sqlx-core/src/pool/connection.rs @@ -1,11 +1,14 @@ -use futures_core::future::BoxFuture; use std::borrow::{Borrow, BorrowMut}; +use std::fmt::{self, Debug, Formatter}; use std::ops::{Deref, DerefMut}; use std::sync::Arc; use std::time::Instant; +use futures_core::future::BoxFuture; + use super::inner::{DecrementSizeGuard, SharedPool}; use crate::connection::{Connect, Connection}; +use crate::error::Error; /// A connection checked out from [`Pool`][crate::pool::Pool]. /// @@ -36,6 +39,13 @@ pub(super) struct Floating<'p, C> { const DEREF_ERR: &str = "(bug) connection already released to pool"; +impl Debug for PoolConnection { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + // TODO: Show the type name of the connection ? + f.debug_struct("PoolConnection").finish() + } +} + impl Borrow for PoolConnection where C: Connect, @@ -78,7 +88,9 @@ impl Connection for PoolConnection where C: Connect, { - fn close(mut self) -> BoxFuture<'static, crate::Result<()>> { + type Database = C::Database; + + fn close(mut self) -> BoxFuture<'static, Result<(), Error>> { Box::pin(async move { let live = self.live.take().expect("PoolConnection double-dropped"); live.float(&self.pool).into_idle().close().await @@ -86,7 +98,7 @@ where } #[inline] - fn ping(&mut self) -> BoxFuture> { + fn ping(&mut self) -> BoxFuture> { Box::pin(self.deref_mut().ping()) } } @@ -185,7 +197,7 @@ impl<'s, C> Floating<'s, Idle> { } } - pub async fn ping(&mut self) -> crate::Result<()> + pub async fn ping(&mut self) -> Result<(), Error> where C: Connection, { @@ -199,7 +211,7 @@ impl<'s, C> Floating<'s, Idle> { } } - pub async fn close(self) -> crate::Result<()> + pub async fn close(self) -> Result<(), Error> where C: Connection, { diff --git a/sqlx-core/src/pool/executor.rs b/sqlx-core/src/pool/executor.rs deleted file mode 100644 index 5666404b..00000000 --- a/sqlx-core/src/pool/executor.rs +++ /dev/null @@ -1,116 +0,0 @@ -use futures_core::future::BoxFuture; - -use super::PoolConnection; -use crate::connection::Connect; -use crate::cursor::{Cursor, HasCursor}; -use crate::database::Database; -use crate::describe::Describe; -use crate::executor::Execute; -use crate::executor::{Executor, RefExecutor}; -use crate::pool::Pool; - -impl<'p, C, DB> Executor for &'p Pool -where - C: Connect, - DB: Database, - DB: for<'c, 'q> HasCursor<'c, 'q, Database = DB>, -{ - type Database = DB; - - fn execute<'e, 'q: 'e, 'c: 'e, E: 'e>( - &'c mut self, - query: E, - ) -> BoxFuture<'e, crate::Result> - where - E: Execute<'q, Self::Database>, - { - Box::pin(async move { self.acquire().await?.execute(query).await }) - } - - fn fetch<'e, 'q, E>(&'e mut self, query: E) -> >::Cursor - where - E: Execute<'q, DB>, - { - DB::Cursor::from_pool(self, query) - } - - #[doc(hidden)] - fn describe<'e, 'q, E: 'e>( - &'e mut self, - query: E, - ) -> BoxFuture<'e, crate::Result>> - where - E: Execute<'q, Self::Database>, - { - Box::pin(async move { self.acquire().await?.describe(query).await }) - } -} - -impl<'p, C, DB> RefExecutor<'p> for &'p Pool -where - C: Connect, - DB: Database, - DB: for<'c, 'q> HasCursor<'c, 'q>, - for<'c> &'c mut C: RefExecutor<'c>, -{ - type Database = DB; - - fn fetch_by_ref<'q, E>(self, query: E) -> >::Cursor - where - E: Execute<'q, DB>, - { - DB::Cursor::from_pool(self, query) - } -} - -impl Executor for PoolConnection -where - C: Connect, -{ - type Database = C::Database; - - fn execute<'e, 'q: 'e, 'c: 'e, E: 'e>( - &'c mut self, - query: E, - ) -> BoxFuture<'e, crate::Result> - where - E: Execute<'q, Self::Database>, - { - (**self).execute(query) - } - - fn fetch<'e, 'q, E>(&'e mut self, query: E) -> >::Cursor - where - E: Execute<'q, Self::Database>, - { - (**self).fetch(query) - } - - #[doc(hidden)] - fn describe<'e, 'q, E: 'e>( - &'e mut self, - query: E, - ) -> BoxFuture<'e, crate::Result>> - where - E: Execute<'q, Self::Database>, - { - (**self).describe(query) - } -} - -impl<'c, C, DB> RefExecutor<'c> for &'c mut PoolConnection -where - C: Connect, - DB: Database, - DB: for<'c2, 'q> HasCursor<'c2, 'q, Database = DB>, - &'c mut C: RefExecutor<'c, Database = DB>, -{ - type Database = DB; - - fn fetch_by_ref<'q, E>(self, query: E) -> >::Cursor - where - E: Execute<'q, Self::Database>, - { - (**self).fetch(query) - } -} diff --git a/sqlx-core/src/pool/inner.rs b/sqlx-core/src/pool/inner.rs index 708e0c58..b85f1bf7 100644 --- a/sqlx-core/src/pool/inner.rs +++ b/sqlx-core/src/pool/inner.rs @@ -8,13 +8,11 @@ use std::time::Instant; use crossbeam_queue::{ArrayQueue, SegQueue}; use futures_core::task::{Poll, Waker}; use futures_util::future; +use sqlx_rt::{sleep, spawn, timeout}; +use crate::connection::{Connect, Connection}; +use crate::error::Error; use crate::pool::deadline_as_timeout; -use crate::runtime::{sleep, spawn, timeout}; -use crate::{ - connection::{Connect, Connection}, - error::Error, -}; use super::connection::{Floating, Idle, Live}; use super::Options; @@ -106,7 +104,7 @@ where /// open a new connection, or if an idle connection is returned to the pool. /// /// Returns an error if `deadline` elapses before we are woken. - async fn wait_for_conn(&self, deadline: Instant) -> crate::Result<()> { + async fn wait_for_conn(&self, deadline: Instant) -> Result<(), Error> { let mut waker_pushed = false; timeout( @@ -124,7 +122,7 @@ where }), ) .await - .map_err(|_| crate::Error::PoolTimedOut(None)) + .map_err(|_| Error::PoolTimedOut) } } @@ -132,7 +130,7 @@ impl SharedPool where C: Connect, { - pub(super) async fn new_arc(url: &str, options: Options) -> crate::Result> { + pub(super) async fn new_arc(url: &str, options: Options) -> Result, Error> { let mut pool = Self { url: url.to_owned(), idle_conns: ArrayQueue::new(options.max_size as usize), @@ -151,7 +149,8 @@ where Ok(pool) } - pub(super) async fn acquire<'s>(&'s self) -> crate::Result>> { + #[allow(clippy::needless_lifetimes)] + pub(super) async fn acquire<'s>(&'s self) -> Result>, Error> { let start = Instant::now(); let deadline = start + self.options.connect_timeout; @@ -185,7 +184,7 @@ where } // takes `&mut self` so this can only be called during init - async fn init_min_connections(&mut self) -> crate::Result<()> { + async fn init_min_connections(&mut self) -> Result<(), Error> { for _ in 0..self.options.min_size { let deadline = Instant::now() + self.options.connect_timeout; @@ -208,7 +207,7 @@ where &'s self, deadline: Instant, guard: DecrementSizeGuard<'s>, - ) -> crate::Result>>> { + ) -> Result>>, Error> { if self.is_closed() { return Err(Error::PoolClosed); } @@ -216,25 +215,25 @@ where let timeout = super::deadline_as_timeout::(deadline)?; // result here is `Result, TimeoutError>` - match crate::runtime::timeout(timeout, C::connect(&self.url)).await { + match sqlx_rt::timeout(timeout, C::connect(&self.url)).await { // successfully established connection Ok(Ok(raw)) => Ok(Some(Floating::new_live(raw, guard))), // an IO error while connecting is assumed to be the system starting up - Ok(Err(crate::Error::Io(_))) => Ok(None), + Ok(Err(Error::Io(_))) => Ok(None), // TODO: Handle other database "boot period"s // [postgres] the database system is starting up // TODO: Make this check actually check if this is postgres - Ok(Err(crate::Error::Database(error))) if error.code() == Some("57P03") => Ok(None), + Ok(Err(Error::Database(error))) if error.code().as_deref() == Some("57P03") => Ok(None), // Any other error while connection should immediately // terminate and bubble the error up Ok(Err(e)) => Err(e), // timed out - Err(e) => Err(crate::Error::PoolTimedOut(Some(Box::new(e)))), + Err(_) => Err(Error::PoolTimedOut), } } } diff --git a/sqlx-core/src/pool/mod.rs b/sqlx-core/src/pool/mod.rs index 7f887640..cb6b10a8 100644 --- a/sqlx-core/src/pool/mod.rs +++ b/sqlx-core/src/pool/mod.rs @@ -1,4 +1,4 @@ -//! **Pool** for SQLx database connections. +//! Connection pool for SQLx database connections. use std::{ fmt, @@ -8,13 +8,12 @@ use std::{ use crate::connection::Connect; use crate::database::Database; -use crate::transaction::Transaction; +use crate::error::Error; use self::inner::SharedPool; use self::options::Options; mod connection; -mod executor; mod inner; mod options; @@ -35,17 +34,15 @@ where /// /// * MySQL/MariaDB: [crate::mysql::MySqlConnection] /// * PostgreSQL: [crate::postgres::PgConnection] - pub async fn new(url: &str) -> crate::Result { + pub async fn new(url: &str) -> Result { Self::builder().build(url).await } - async fn with_options(url: &str, options: Options) -> crate::Result { - let inner = SharedPool::::new_arc(url, options).await?; - - Ok(Pool(inner)) + async fn new_with(url: &str, options: Options) -> Result { + Ok(Pool(SharedPool::::new_arc(url, options).await?)) } - /// Returns a [Builder] to configure a new connection pool. + /// Returns a [`Builder`] to configure a new connection pool. pub fn builder() -> Builder { Builder::new() } @@ -53,7 +50,7 @@ where /// Retrieves a connection from the pool. /// /// Waits for at most the configured connection timeout before returning an error. - pub async fn acquire(&self) -> crate::Result> { + pub async fn acquire(&self) -> Result, Error> { self.0.acquire().await.map(|conn| conn.attach(&self.0)) } @@ -64,11 +61,6 @@ where self.0.try_acquire().map(|conn| conn.attach(&self.0)) } - /// Retrieves a new connection and immediately begins a new transaction. - pub async fn begin(&self) -> crate::Result>> { - Ok(Transaction::new(0, self.acquire().await?).await?) - } - /// Ends the use of a connection pool. Prevents any new connections /// and will close all active connections when they are returned to the pool. /// @@ -143,10 +135,10 @@ where /// get the time between the deadline and now and use that as our timeout /// /// returns `Error::PoolTimedOut` if the deadline is in the past -fn deadline_as_timeout(deadline: Instant) -> crate::Result { +fn deadline_as_timeout(deadline: Instant) -> Result { deadline .checked_duration_since(Instant::now()) - .ok_or(crate::Error::PoolTimedOut(None)) + .ok_or(Error::PoolTimedOut) } #[test] diff --git a/sqlx-core/src/pool/options.rs b/sqlx-core/src/pool/options.rs index 6922cc26..6cdb8873 100644 --- a/sqlx-core/src/pool/options.rs +++ b/sqlx-core/src/pool/options.rs @@ -3,6 +3,7 @@ use std::{marker::PhantomData, time::Duration}; use super::Pool; use crate::connection::Connect; use crate::database::Database; +use crate::error::Error; /// Builder for [Pool]. pub struct Builder { @@ -25,7 +26,7 @@ where max_size: 10, // don't open connections until necessary min_size: 0, - // try to connect for 10 seconds before erroring + // try to connect for 10 seconds before giving up connect_timeout: Duration::from_secs(60), // reap connections that have been alive > 30 minutes // prevents unbounded live-leaking of memory due to naive prepared statement caching @@ -113,11 +114,11 @@ where /// opened and placed into the pool. /// /// [`min_size`]: #method.min_size - pub async fn build(self, url: &str) -> crate::Result> + pub async fn build(self, url: &str) -> Result, Error> where C: Connect, { - Pool::::with_options(url, self.options).await + Pool::::new_with(url, self.options).await } } diff --git a/sqlx-core/src/query.rs b/sqlx-core/src/query.rs index 6ca51b19..aebdcb46 100644 --- a/sqlx-core/src/query.rs +++ b/sqlx-core/src/query.rs @@ -1,26 +1,23 @@ use std::marker::PhantomData; +use std::mem; use async_stream::try_stream; -use futures_core::Stream; -use futures_util::future::ready; -use futures_util::TryFutureExt; +use either::Either; +use futures_core::future::BoxFuture; +use futures_core::stream::BoxStream; +use futures_util::{future, FutureExt, StreamExt, TryFutureExt, TryStreamExt}; use crate::arguments::Arguments; -use crate::cursor::{Cursor, HasCursor}; -use crate::database::Database; +use crate::database::{Database, HasArguments}; use crate::encode::Encode; -use crate::executor::{Execute, Executor, RefExecutor}; -use crate::row::HasRow; -use crate::types::Type; +use crate::error::Error; +use crate::executor::{Execute, Executor}; /// Raw SQL query with bind parameters. Returned by [`query`][crate::query::query]. #[must_use = "query must be executed to affect database"] -pub struct Query<'q, DB> -where - DB: Database, -{ - pub(crate) query: &'q str, - pub(crate) arguments: DB::Arguments, +pub struct Query<'q, DB: Database> { + query: &'q str, + pub(crate) arguments: >::Arguments, database: PhantomData, } @@ -31,30 +28,25 @@ where /// [Query::execute] as it doesn't make sense to map the result type and then ignore it. /// /// [Query::bind] is also omitted; stylistically we recommend placing your `.bind()` calls -/// before `.try_map()` anyway. +/// before `.try_map()`. #[must_use = "query must be executed to affect database"] -pub struct Map<'q, DB, F> -where - DB: Database, -{ - query: Query<'q, DB>, +pub struct Map<'q, DB: Database, F> { + inner: Query<'q, DB>, mapper: F, } -// necessary because we can't have a blanket impl for `Query<'q, DB>` -// the compiler thinks that `ImmutableArguments` could be `DB::Arguments` even though -// that would be an infinitely recursive type impl<'q, DB> Execute<'q, DB> for Query<'q, DB> where DB: Database, { - fn into_parts(self) -> (&'q str, Option) { - (self.query, Some(self.arguments)) + #[inline] + fn query(&self) -> &'q str { + self.query } - #[doc(hidden)] - fn query_string(&self) -> &'q str { - self.query + #[inline] + fn take_arguments(&mut self) -> Option<>::Arguments> { + Some(mem::take(&mut self.arguments)) } } @@ -69,181 +61,236 @@ where /// will be returned when this query is executed. /// /// There is no validation that the value is of the type expected by the query. Most SQL - /// flavors will perform type coercion (Postgres will return a database error).s - pub fn bind(mut self, value: T) -> Self - where - T: Type, - T: Encode, - { + /// flavors will perform type coercion (Postgres will return a database error). + pub fn bind>(mut self, value: T) -> Self { self.arguments.add(value); self } - #[doc(hidden)] - pub fn bind_all(self, arguments: DB::Arguments) -> Query<'q, DB> { - Query { - query: self.query, - arguments, - database: PhantomData, - } - } -} - -impl<'q, DB> Query<'q, DB> -where - DB: Database, -{ /// Map each row in the result to another type. /// - /// The returned type has most of the same methods but does not have - /// [`.execute()`][Query::execute] or [`.bind()][Query::bind]. + /// See [`try_map`](Query::try_map) for a fallible version of this method. /// - /// See also: [query_as][crate::query_as::query_as]. - pub fn map(self, mapper: F) -> Map<'q, DB, impl TryMapRow> + /// The [`query_as`](crate::query_as::query_as) method will construct a mapped query using + /// a [`FromRow`](crate::row::FromRow) implementation. + #[inline] + pub fn map(self, f: F) -> Map<'q, DB, impl Fn(DB::Row) -> Result> where - O: Unpin, - F: MapRow, + F: Fn(DB::Row) -> O, { - self.try_map(MapRowAdapter(mapper)) + self.try_map(move |row| Ok(f(row))) } /// Map each row in the result to another type. /// - /// See also: [query_as][crate::query_as::query_as]. - pub fn try_map(self, mapper: F) -> Map<'q, DB, F> + /// The [`query_as`](crate::query_as::query_as) method will construct a mapped query using + /// a [`FromRow`](crate::row::FromRow) implementation. + #[inline] + pub fn try_map(self, f: F) -> Map<'q, DB, F> where - F: TryMapRow, + F: Fn(DB::Row) -> Result, { Map { - query: self, - mapper, + inner: self, + mapper: f, } } -} -impl<'q, DB> Query<'q, DB> -where - DB: Database, - Self: Execute<'q, DB>, -{ - pub async fn execute(self, mut executor: E) -> crate::Result + /// Execute the query and return the total number of rows affected. + #[inline] + pub async fn execute<'c, E>(self, executor: E) -> Result where - E: Executor, + 'q: 'c, + E: Executor<'c, Database = DB>, { executor.execute(self).await } - pub fn fetch<'e, E>(self, executor: E) -> >::Cursor + /// Execute multiple queries and return the rows affected from each query, in a stream. + #[inline] + pub async fn execute_many<'c, E>(self, executor: E) -> BoxStream<'c, Result> where - E: RefExecutor<'e, Database = DB>, + 'q: 'c, + E: Executor<'c, Database = DB>, { - executor.fetch_by_ref(self) + executor.execute_many(self) + } + + /// Execute the query and return the generated results as a stream. + #[inline] + pub fn fetch<'c, E>(self, executor: E) -> BoxStream<'c, Result> + where + 'q: 'c, + E: Executor<'c, Database = DB>, + { + executor.fetch(self) + } + + /// Execute multiple queries and return the generated results as a stream + /// from each query, in a stream. + #[inline] + pub fn fetch_many<'c, E>( + self, + executor: E, + ) -> BoxStream<'c, Result, Error>> + where + 'q: 'c, + E: Executor<'c, Database = DB>, + { + executor.fetch_many(self) + } + + /// Execute the query and return all the generated results, collected into a [`Vec`]. + #[inline] + pub async fn fetch_all<'c, E>(self, executor: E) -> Result, Error> + where + 'q: 'c, + E: Executor<'c, Database = DB>, + { + executor.fetch_all(self).await + } + + /// Execute the query and returns exactly one row. + #[inline] + pub async fn fetch_one<'c, E>(self, executor: E) -> Result + where + 'q: 'c, + E: Executor<'c, Database = DB>, + { + executor.fetch_one(self).await + } + + /// Execute the query and returns at most one row. + #[inline] + pub async fn fetch_optional<'c, E>(self, executor: E) -> Result, Error> + where + 'q: 'c, + E: Executor<'c, Database = DB>, + { + executor.fetch_optional(self).await } } -impl<'q, DB, F> Map<'q, DB, F> +impl<'q, DB, F: Send> Execute<'q, DB> for Map<'q, DB, F> where DB: Database, - Query<'q, DB>: Execute<'q, DB>, - F: TryMapRow, { - /// Execute the query and get a [Stream] of the results, returning our mapped type. - pub fn fetch<'e: 'q, E>( - mut self, - executor: E, - ) -> impl Stream> + Unpin + 'e + #[inline] + fn query(&self) -> &'q str { + self.inner.query + } + + #[inline] + fn take_arguments(&mut self) -> Option<>::Arguments> { + Some(mem::take(&mut self.inner.arguments)) + } +} + +impl<'q, DB, F, O> Map<'q, DB, F> +where + DB: Database, + F: Send + Sync + Fn(DB::Row) -> Result, + O: Send + Unpin, +{ + // FIXME: This is very close 1:1 with [`Executor::fetch`] + // noinspection DuplicatedCode + /// Execute the query and return the generated results as a stream. + pub fn fetch<'c, E>(self, executor: E) -> BoxStream<'c, Result> where - 'q: 'e, - E: RefExecutor<'e, Database = DB> + 'e, - F: 'e, - F::Output: 'e, + 'q: 'c, + E: 'c + Executor<'c, Database = DB>, + DB: 'c, + F: 'c, + O: 'c, + { + self.fetch_many(executor) + .try_filter_map(|step| async move { + Ok(match step { + Either::Left(_) => None, + Either::Right(o) => Some(o), + }) + }) + .boxed() + } + + /// Execute multiple queries and return the generated results as a stream + /// from each query, in a stream. + pub fn fetch_many<'c, E>(self, executor: E) -> BoxStream<'c, Result, Error>> + where + 'q: 'c, + E: 'c + Executor<'c, Database = DB>, + DB: 'c, + F: 'c, + O: 'c, { Box::pin(try_stream! { - let mut cursor = executor.fetch_by_ref(self.query); - while let Some(next) = cursor.next().await? { - let mapped = self.mapper.try_map_row(next)?; - yield mapped; + let mut s = executor.fetch_many(self.inner); + while let Some(v) = s.try_next().await? { + match v { + Either::Left(v) => yield Either::Left(v), + Either::Right(row) => { + let mapped = (self.mapper)(row)?; + yield Either::Right(mapped); + } + } } }) } - /// Get the first row in the result - pub async fn fetch_optional<'e, E>(self, executor: E) -> crate::Result> + /// Execute the query and return all the generated results, collected into a [`Vec`]. + #[inline] + pub fn fetch_all<'c, E>(self, executor: E) -> BoxFuture<'c, Result, Error>> where - E: RefExecutor<'e, Database = DB>, - 'q: 'e, + 'q: 'c, + E: 'c + Executor<'c, Database = DB>, + DB: 'c, + F: 'c, + O: 'c, { - // could be implemented in terms of `fetch()` but this avoids overhead from `try_stream!` - let mut cursor = executor.fetch_by_ref(self.query); - let mut mapper = self.mapper; - let val = cursor.next().await?; - val.map(|row| mapper.try_map_row(row)).transpose() + self.fetch(executor).try_collect().boxed() } - pub async fn fetch_one<'e, E>(self, executor: E) -> crate::Result + // FIXME: This is very close 1:1 with [`Executor::fetch_one`] + // noinspection DuplicatedCode + /// Execute the query and returns exactly one row. + pub fn fetch_one<'c, E>(self, executor: E) -> BoxFuture<'c, Result> where - E: RefExecutor<'e, Database = DB>, - 'q: 'e, + 'q: 'c, + E: 'c + Executor<'c, Database = DB>, + DB: 'c, + F: 'c, + O: 'c, { self.fetch_optional(executor) .and_then(|row| match row { - Some(row) => ready(Ok(row)), - None => ready(Err(crate::Error::RowNotFound)), + Some(row) => future::ok(row), + None => future::err(Error::RowNotFound), }) - .await + .boxed() } - pub async fn fetch_all<'e, E>(mut self, executor: E) -> crate::Result> + /// Execute the query and returns at most one row. + pub fn fetch_optional<'c, E>(self, executor: E) -> BoxFuture<'c, Result, Error>> where - E: RefExecutor<'e, Database = DB>, - 'q: 'e, + 'q: 'c, + E: 'c + Executor<'c, Database = DB>, + DB: 'c, + F: 'c, + O: 'c, { - let mut cursor = executor.fetch_by_ref(self.query); - let mut out = vec![]; - - while let Some(row) = cursor.next().await? { - out.push(self.mapper.try_map_row(row)?); - } - - Ok(out) - } -} - -// A (hopefully) temporary workaround for an internal compiler error (ICE) involving higher-ranked -// trait bounds (HRTBs), associated types and closures. -// -// See https://github.com/rust-lang/rust/issues/62529 - -pub trait TryMapRow { - type Output: Unpin; - - fn try_map_row(&mut self, row: ::Row) -> crate::Result; -} - -pub trait MapRow { - type Output: Unpin; - - fn map_row(&mut self, row: ::Row) -> Self::Output; -} - -// An adapter that implements [MapRow] in terms of [TryMapRow] -// Just ends up Ok wrapping it - -struct MapRowAdapter(F); - -impl TryMapRow for MapRowAdapter -where - O: Unpin, - F: MapRow, -{ - type Output = O; - - fn try_map_row(&mut self, row: ::Row) -> crate::Result { - Ok(self.0.map_row(row)) + Box::pin(async move { + let row = executor.fetch_optional(self.inner).await?; + if let Some(row) = row { + (self.mapper)(row).map(Some) + } else { + Ok(None) + } + }) } } /// Construct a raw SQL query that can be chained to bind parameters and executed. +#[inline] pub fn query(sql: &str) -> Query where DB: Database, diff --git a/sqlx-core/src/query_as.rs b/sqlx-core/src/query_as.rs index 3f188890..b57773e5 100644 --- a/sqlx-core/src/query_as.rs +++ b/sqlx-core/src/query_as.rs @@ -1,204 +1,151 @@ -use core::marker::PhantomData; +use std::marker::PhantomData; + +use async_stream::try_stream; +use either::Either; +use futures_core::stream::BoxStream; +use futures_util::{StreamExt, TryStreamExt}; use crate::arguments::Arguments; -use crate::database::Database; +use crate::database::{Database, HasArguments}; use crate::encode::Encode; -use crate::executor::Execute; -use crate::types::Type; +use crate::error::Error; +use crate::executor::{Execute, Executor}; +use crate::from_row::FromRow; +use crate::query::{query, Query}; -/// Raw SQL query with bind parameters, mapped to a concrete type -/// using [`FromRow`](trait.FromRow.html). Returned -/// by [`query_as`](fn.query_as.html). +/// Raw SQL query with bind parameters, mapped to a concrete type using [`FromRow`]. +/// Returned from [`query_as`]. #[must_use = "query must be executed to affect database"] -pub struct QueryAs<'q, DB, O> -where - DB: Database, -{ - query: &'q str, - arguments: ::Arguments, - database: PhantomData, +pub struct QueryAs<'q, DB: Database, O> { + pub(crate) inner: Query<'q, DB>, output: PhantomData, } -impl<'q, DB, O> QueryAs<'q, DB, O> -where - DB: Database, -{ - /// Bind a value for use with this SQL query. - #[inline] - pub fn bind(mut self, value: T) -> Self - where - T: Type, - T: Encode, - { - self.arguments.add(value); - self - } -} - impl<'q, DB, O: Send> Execute<'q, DB> for QueryAs<'q, DB, O> where DB: Database, { #[inline] - fn into_parts(self) -> (&'q str, Option<::Arguments>) { - (self.query, Some(self.arguments)) + fn query(&self) -> &'q str { + self.inner.query() } #[inline] + fn take_arguments(&mut self) -> Option<>::Arguments> { + self.inner.take_arguments() + } +} + +// FIXME: This is very close, nearly 1:1 with `Map` +// noinspection DuplicatedCode +impl<'q, DB, O> QueryAs<'q, DB, O> +where + DB: Database, + O: Send + Unpin + for<'r> FromRow<'r, DB::Row>, +{ + /// Bind a value for use with this SQL query. + /// + /// See [`Query::bind`](crate::query::Query::bind). + #[inline] + pub fn bind>(mut self, value: T) -> Self { + self.inner.arguments.add(value); + self + } + #[doc(hidden)] - fn query_string(&self) -> &'q str { - self.query + pub fn __bind_all(mut self, arguments: >::Arguments) -> Self { + self.inner.arguments = arguments; + self + } + + /// Execute the query and return the generated results as a stream. + pub fn fetch<'c, E>(self, executor: E) -> BoxStream<'c, Result> + where + 'q: 'c, + E: 'c + Executor<'c, Database = DB>, + DB: 'c, + O: 'c, + { + self.fetch_many(executor) + .try_filter_map(|step| async move { Ok(step.right()) }) + .boxed() + } + + /// Execute multiple queries and return the generated results as a stream + /// from each query, in a stream. + pub fn fetch_many<'c, E>(self, executor: E) -> BoxStream<'c, Result, Error>> + where + 'q: 'c, + E: 'c + Executor<'c, Database = DB>, + DB: 'c, + O: 'c, + { + Box::pin(try_stream! { + let mut s = executor.fetch_many(self.inner); + while let Some(v) = s.try_next().await? { + match v { + Either::Left(v) => yield Either::Left(v), + Either::Right(row) => { + let mapped = O::from_row(&row)?; + yield Either::Right(mapped); + } + } + } + }) + } + + /// Execute the query and return all the generated results, collected into a [`Vec`]. + #[inline] + pub async fn fetch_all<'c, E>(self, executor: E) -> Result, Error> + where + 'q: 'c, + E: 'c + Executor<'c, Database = DB>, + DB: 'c, + O: 'c, + { + self.fetch(executor).try_collect().await + } + + /// Execute the query and returns exactly one row. + pub async fn fetch_one<'c, E>(self, executor: E) -> Result + where + 'q: 'c, + E: 'c + Executor<'c, Database = DB>, + DB: 'c, + O: 'c, + { + self.fetch_optional(executor) + .await + .and_then(|row| row.ok_or(Error::RowNotFound)) + } + + /// Execute the query and returns at most one row. + pub async fn fetch_optional<'c, E>(self, executor: E) -> Result, Error> + where + 'q: 'c, + E: 'c + Executor<'c, Database = DB>, + DB: 'c, + O: 'c, + { + let row = executor.fetch_optional(self.inner).await?; + if let Some(row) = row { + O::from_row(&row).map(Some) + } else { + Ok(None) + } } } /// Construct a raw SQL query that is mapped to a concrete type /// using [`FromRow`](crate::row::FromRow). -/// -/// Returns [`QueryAs`]. +#[inline] pub fn query_as(sql: &str) -> QueryAs where DB: Database, + O: for<'r> FromRow<'r, DB::Row>, { QueryAs { - query: sql, - arguments: Default::default(), - database: PhantomData, + inner: query(sql), output: PhantomData, } } - -// We need database-specific QueryAs traits to work around: -// https://github.com/rust-lang/rust/issues/62529 - -// If for some reason we miss that issue being resolved in a _stable_ edition of -// rust, please open up a 100 issues and shout as loud as you can to remove -// this unseemly hack. - -#[allow(unused_macros)] -macro_rules! make_query_as { - ($name:ident, $db:ident, $row:ident) => { - pub trait $name<'q, O> { - fn fetch<'e, E>( - self, - executor: E, - ) -> futures_core::stream::BoxStream<'e, crate::Result> - where - E: 'e + Send + crate::executor::RefExecutor<'e, Database = $db>, - O: 'e + Send + Unpin + for<'c> crate::row::FromRow<'c, $row<'c>>, - 'q: 'e; - - fn fetch_all<'e, E>( - self, - executor: E, - ) -> futures_core::future::BoxFuture<'e, crate::Result>> - where - E: 'e + Send + crate::executor::RefExecutor<'e, Database = $db>, - O: 'e + Send + for<'c> crate::row::FromRow<'c, $row<'c>>, - 'q: 'e; - - fn fetch_one<'e, E>( - self, - executor: E, - ) -> futures_core::future::BoxFuture<'e, crate::Result> - where - E: 'e + Send + crate::executor::RefExecutor<'e, Database = $db>, - O: 'e + Send + for<'c> crate::row::FromRow<'c, $row<'c>>, - 'q: 'e; - - fn fetch_optional<'e, E>( - self, - executor: E, - ) -> futures_core::future::BoxFuture<'e, crate::Result>> - where - E: 'e + Send + crate::executor::RefExecutor<'e, Database = $db>, - O: 'e + Send + for<'c> crate::row::FromRow<'c, $row<'c>>, - 'q: 'e; - } - - impl<'q, O> $name<'q, O> for crate::query_as::QueryAs<'q, $db, O> { - fn fetch<'e, E>( - self, - executor: E, - ) -> futures_core::stream::BoxStream<'e, crate::Result> - where - E: 'e + Send + crate::executor::RefExecutor<'e, Database = $db>, - O: 'e + Send + Unpin + for<'c> crate::row::FromRow<'c, $row<'c>>, - 'q: 'e, - { - use crate::cursor::Cursor; - - Box::pin(async_stream::try_stream! { - let mut cursor = executor.fetch_by_ref(self); - - while let Some(row) = cursor.next().await? { - let obj = O::from_row(&row)?; - - yield obj; - } - }) - } - - fn fetch_optional<'e, E>( - self, - executor: E, - ) -> futures_core::future::BoxFuture<'e, crate::Result>> - where - E: 'e + Send + crate::executor::RefExecutor<'e, Database = $db>, - O: 'e + Send + for<'c> crate::row::FromRow<'c, $row<'c>>, - 'q: 'e, - { - use crate::cursor::Cursor; - - Box::pin(async move { - let mut cursor = executor.fetch_by_ref(self); - let row = cursor.next().await?; - - row.as_ref().map(O::from_row).transpose() - }) - } - - fn fetch_one<'e, E>( - self, - executor: E, - ) -> futures_core::future::BoxFuture<'e, crate::Result> - where - E: 'e + Send + crate::executor::RefExecutor<'e, Database = $db>, - O: 'e + Send + for<'c> crate::row::FromRow<'c, $row<'c>>, - 'q: 'e, - { - use futures_util::TryFutureExt; - - Box::pin(self.fetch_optional(executor).and_then(|row| match row { - Some(row) => futures_util::future::ready(Ok(row)), - None => futures_util::future::ready(Err(crate::Error::RowNotFound)), - })) - } - - fn fetch_all<'e, E>( - self, - executor: E, - ) -> futures_core::future::BoxFuture<'e, crate::Result>> - where - E: 'e + Send + crate::executor::RefExecutor<'e, Database = $db>, - O: 'e + Send + for<'c> crate::row::FromRow<'c, $row<'c>>, - 'q: 'e, - { - use crate::cursor::Cursor; - - Box::pin(async move { - let mut cursor = executor.fetch_by_ref(self); - let mut out = Vec::new(); - - while let Some(row) = cursor.next().await? { - let obj = O::from_row(&row)?; - - out.push(obj); - } - - Ok(out) - }) - } - } - }; -} diff --git a/sqlx-core/src/query_scalar.rs b/sqlx-core/src/query_scalar.rs new file mode 100644 index 00000000..fa8cab47 --- /dev/null +++ b/sqlx-core/src/query_scalar.rs @@ -0,0 +1,132 @@ +use either::Either; +use futures_core::stream::BoxStream; +use futures_util::{StreamExt, TryFutureExt, TryStreamExt}; + +use crate::arguments::Arguments; +use crate::database::{Database, HasArguments}; +use crate::encode::Encode; +use crate::error::Error; +use crate::executor::{Execute, Executor}; +use crate::from_row::FromRow; +use crate::query_as::{query_as, QueryAs}; + +/// Raw SQL query with bind parameters, mapped to a concrete type using [`FromRow`] on `(O,)`. +/// Returned from [`query_scalar`]. +#[must_use = "query must be executed to affect database"] +pub struct QueryScalar<'q, DB: Database, O> { + inner: QueryAs<'q, DB, (O,)>, +} + +impl<'q, DB, O: Send> Execute<'q, DB> for QueryScalar<'q, DB, O> +where + DB: Database, +{ + #[inline] + fn query(&self) -> &'q str { + self.inner.query() + } + + #[inline] + fn take_arguments(&mut self) -> Option<>::Arguments> { + self.inner.take_arguments() + } +} + +// FIXME: This is very close, nearly 1:1 with `Map` +// noinspection DuplicatedCode +impl<'q, DB, O> QueryScalar<'q, DB, O> +where + DB: Database, + O: Send + Unpin, + (O,): Send + Unpin + for<'r> FromRow<'r, DB::Row>, +{ + /// Bind a value for use with this SQL query. + /// + /// See [`Query::bind`](crate::query::Query::bind). + #[inline] + pub fn bind>(mut self, value: T) -> Self { + self.inner.inner.arguments.add(value); + self + } + + /// Execute the query and return the generated results as a stream. + #[inline] + pub fn fetch<'c, E>(self, executor: E) -> BoxStream<'c, Result> + where + 'q: 'c, + E: 'c + Executor<'c, Database = DB>, + DB: 'c, + O: 'c, + { + self.inner.fetch(executor).map_ok(|it| it.0).boxed() + } + + /// Execute multiple queries and return the generated results as a stream + /// from each query, in a stream. + #[inline] + pub fn fetch_many<'c, E>(self, executor: E) -> BoxStream<'c, Result, Error>> + where + 'q: 'c, + E: 'c + Executor<'c, Database = DB>, + DB: 'c, + O: 'c, + { + self.inner + .fetch_many(executor) + .map_ok(|v| v.map_right(|it| it.0)) + .boxed() + } + + /// Execute the query and return all the generated results, collected into a [`Vec`]. + #[inline] + pub async fn fetch_all<'c, E>(self, executor: E) -> Result, Error> + where + 'q: 'c, + E: 'c + Executor<'c, Database = DB>, + DB: 'c, + (O,): 'c, + { + self.inner + .fetch(executor) + .map_ok(|it| it.0) + .try_collect() + .await + } + + /// Execute the query and returns exactly one row. + #[inline] + pub async fn fetch_one<'c, E>(self, executor: E) -> Result + where + 'q: 'c, + E: 'c + Executor<'c, Database = DB>, + DB: 'c, + O: 'c, + { + self.inner.fetch_one(executor).map_ok(|it| it.0).await + } + + /// Execute the query and returns at most one row. + #[inline] + pub async fn fetch_optional<'c, E>(self, executor: E) -> Result, Error> + where + 'q: 'c, + E: 'c + Executor<'c, Database = DB>, + DB: 'c, + O: 'c, + { + Ok(self.inner.fetch_optional(executor).await?.map(|it| it.0)) + } +} + +/// Construct a raw SQL query that is mapped to a concrete type +/// using [`FromRow`](crate::row::FromRow) on `(O,)`. +#[inline] +pub fn query_scalar(sql: &str) -> QueryScalar +where + DB: Database, + (O,): for<'r> FromRow<'r, DB::Row>, +{ + QueryScalar { + inner: query_as(sql), + } +} diff --git a/sqlx-core/src/row.rs b/sqlx-core/src/row.rs index 0538fbd7..17a22a13 100644 --- a/sqlx-core/src/row.rs +++ b/sqlx-core/src/row.rs @@ -1,9 +1,9 @@ -//! Contains the `ColumnIndex`, `Row`, and `FromRow` traits. +use std::fmt::Debug; -use crate::database::Database; +use crate::database::{Database, HasValueRef}; use crate::decode::Decode; -use crate::types::{Type, TypeInfo}; -use crate::value::{HasRawValue, RawValue}; +use crate::error::{mismatched_types, Error}; +use crate::value::ValueRef; /// A type that can be used to index into a [`Row`]. /// @@ -16,33 +16,42 @@ use crate::value::{HasRawValue, RawValue}; /// [`Row`]: trait.Row.html /// [`get`]: trait.Row.html#method.get /// [`try_get`]: trait.Row.html#method.try_get -pub trait ColumnIndex<'c, R> -where - Self: private_column_index::Sealed, - R: Row<'c> + ?Sized, -{ +pub trait ColumnIndex: private_column_index::Sealed + Debug { /// Returns a valid positional index into the row, [`ColumnIndexOutOfBounds`], or, /// [`ColumnNotFound`]. /// /// [`ColumnNotFound`]: ../enum.Error.html#variant.ColumnNotFound /// [`ColumnIndexOutOfBounds`]: ../enum.Error.html#variant.ColumnIndexOutOfBounds - fn index(&self, row: &R) -> crate::Result; + fn index(&self, row: &R) -> Result; } -impl<'c, R, I> ColumnIndex<'c, R> for &'_ I +impl ColumnIndex for &'_ I where - R: Row<'c>, - I: ColumnIndex<'c, R> + ?Sized, + R: Row + ?Sized, + I: ColumnIndex + ?Sized, { #[inline] - fn index(&self, row: &R) -> crate::Result { + fn index(&self, row: &R) -> Result { (**self).index(row) } } +impl ColumnIndex for usize { + fn index(&self, row: &R) -> Result { + let len = row.len(); + + if *self >= len { + return Err(Error::ColumnIndexOutOfBounds { len, index: *self }); + } + + Ok(*self) + } +} + // Prevent users from implementing the `ColumnIndex` trait. mod private_column_index { pub trait Sealed {} + impl Sealed for usize {} impl Sealed for str {} impl Sealed for &'_ T where T: Sealed + ?Sized {} @@ -50,20 +59,12 @@ mod private_column_index { /// Represents a single row from the database. /// -/// Applications should not generally need to use this trait. Values of this trait are only -/// encountered when manually implementing [`FromRow`] (as opposed to deriving) or iterating -/// a [`Cursor`] (returned from [`Query::fetch`]). -/// /// This trait is sealed and cannot be implemented for types outside of SQLx. /// /// [`FromRow`]: crate::row::FromRow /// [`Cursor`]: crate::cursor::Cursor /// [`Query::fetch`]: crate::query::Query::fetch -pub trait Row<'c> -where - Self: private_row::Sealed + Unpin + Send + Sync, -{ - /// The `Database` this `Row` is implemented for. +pub trait Row: private_row::Sealed + Unpin + Send + Sync + 'static { type Database: Database; /// Returns `true` if this row has no columns. @@ -80,38 +81,36 @@ where /// A string index can be used to access a column by name and a `usize` index /// can be used to access a column by position. /// - /// ```rust,ignore - /// # let mut cursor = sqlx::query("SELECT id, name FROM users") - /// # .fetch(&mut conn); - /// # - /// # let row = cursor.next().await?.unwrap(); - /// # - /// let id: i32 = row.get("id"); // a column named "id" - /// let name: &str = row.get(1); // the second column in the result - /// ``` - /// /// # Panics + /// /// Panics if the column does not exist or its value cannot be decoded into the requested type. /// See [`try_get`](#method.try_get) for a non-panicking version. + /// #[inline] - fn get(&self, index: I) -> T + fn get<'r, T, I>(&'r self, index: I) -> T where - T: Type, - I: ColumnIndex<'c, Self>, - T: Decode<'c, Self::Database>, + I: ColumnIndex, + T: Decode<'r, Self::Database>, { self.try_get::(index).unwrap() } /// Index into the database row and decode a single value. /// - /// See [`try_get_unchecked`](#method.try_get_unchecked). + /// Unlike [`get`](#method.get), this method does not check that the type + /// being returned from the database is compatible with the Rust type and blindly tries + /// to decode the value. + /// + /// # Panics + /// + /// Panics if the column does not exist or its value cannot be decoded into the requested type. + /// See [`try_get_unchecked`](#method.try_get_unchecked) for a non-panicking version. + /// #[inline] - fn get_unchecked(&self, index: I) -> T + fn get_unchecked<'r, T, I>(&'r self, index: I) -> T where - T: Type, - I: ColumnIndex<'c, Self>, - T: Decode<'c, Self::Database>, + I: ColumnIndex, + T: Decode<'r, Self::Database>, { self.try_get_unchecked::(index).unwrap() } @@ -121,254 +120,91 @@ where /// A string index can be used to access a column by name and a `usize` index /// can be used to access a column by position. /// - /// ```rust,ignore - /// # let mut cursor = sqlx::query("SELECT id, name FROM users") - /// # .fetch(&mut conn); - /// # - /// # let row = cursor.next().await?.unwrap(); - /// # - /// let id: i32 = row.try_get("id")?; // a column named "id" - /// let name: &str = row.try_get(1)?; // the second column in the result - /// ``` - /// /// # Errors + /// /// * [`ColumnNotFound`] if the column by the given name was not found. /// * [`ColumnIndexOutOfBounds`] if the `usize` index was greater than the number of columns in the row. - /// * [`Decode`] if the value could not be decoded into the requested type. + /// * [`ColumnDecode`] if the value could not be decoded into the requested type. /// - /// [`Decode`]: crate::Error::Decode + /// [`ColumnDecode`]: crate::Error::ColumnDecode /// [`ColumnNotFound`]: crate::Error::ColumnNotFound /// [`ColumnIndexOutOfBounds`]: crate::Error::ColumnIndexOutOfBounds - fn try_get(&self, index: I) -> crate::Result + /// + fn try_get<'r, T, I>(&'r self, index: I) -> Result where - T: Type, - I: ColumnIndex<'c, Self>, - T: Decode<'c, Self::Database>, + I: ColumnIndex, + T: Decode<'r, Self::Database>, { - let value = self.try_get_raw(index)?; + let value = self.try_get_raw(&index)?; - if let Some(expected_ty) = value.type_info() { - // NOTE: If there is no type, the value is NULL. This is fine. If the user tries - // to get this into a non-Option we catch that elsewhere and report as - // UnexpectedNullError. - - if !expected_ty.compatible(&T::type_info()) { - return Err(crate::Error::mismatched_types::( - expected_ty, - )); + if !value.is_null() { + if let Some(actual_ty) = value.type_info() { + // NOTE: we opt-out of asserting the type equivalency of NULL because of the + // high false-positive rate (e.g., `NULL` in Postgres is `TEXT`). + if !T::accepts(&actual_ty) { + return Err(Error::ColumnDecode { + index: format!("{:?}", index), + source: mismatched_types::(&T::type_info(), &actual_ty), + }); + } } } - T::decode(value) + T::decode(value).map_err(|source| Error::ColumnDecode { + index: format!("{:?}", index), + source, + }) } /// Index into the database row and decode a single value. /// /// Unlike [`try_get`](#method.try_get), this method does not check that the type - /// being returned from the database is compatible with the Rust type and just blindly tries - /// to decode the value. An example of where this could be useful is decoding a Postgres - /// enumeration as a Rust string (instead of deriving a new Rust enum). + /// being returned from the database is compatible with the Rust type and blindly tries + /// to decode the value. + /// + /// # Errors + /// + /// * [`ColumnNotFound`] if the column by the given name was not found. + /// * [`ColumnIndexOutOfBounds`] if the `usize` index was greater than the number of columns in the row. + /// * [`ColumnDecode`] if the value could not be decoded into the requested type. + /// + /// [`ColumnDecode`]: crate::Error::ColumnDecode + /// [`ColumnNotFound`]: crate::Error::ColumnNotFound + /// [`ColumnIndexOutOfBounds`]: crate::Error::ColumnIndexOutOfBounds + /// #[inline] - fn try_get_unchecked(&self, index: I) -> crate::Result + fn try_get_unchecked<'r, T, I>(&'r self, index: I) -> Result where - T: Type, - I: ColumnIndex<'c, Self>, - T: Decode<'c, Self::Database>, + I: ColumnIndex, + T: Decode<'r, Self::Database>, { - self.try_get_raw(index).and_then(T::decode) + let value = self.try_get_raw(&index)?; + T::decode(value).map_err(|source| Error::ColumnDecode { + index: format!("{:?}", index), + source, + }) } - #[doc(hidden)] - fn try_get_raw( - &self, + /// Index into the database row and decode a single value. + /// + /// # Errors + /// + /// * [`ColumnNotFound`] if the column by the given name was not found. + /// * [`ColumnIndexOutOfBounds`] if the `usize` index was greater than the number of columns in the row. + /// + /// [`ColumnNotFound`]: crate::Error::ColumnNotFound + /// [`ColumnIndexOutOfBounds`]: crate::Error::ColumnIndexOutOfBounds + /// + // noinspection RsNeedlessLifetimes + fn try_get_raw<'r, I>( + &'r self, index: I, - ) -> crate::Result<>::RawValue> + ) -> Result<>::ValueRef, Error> where - I: ColumnIndex<'c, Self>; + I: ColumnIndex; } // Prevent users from implementing the `Row` trait. pub(crate) mod private_row { pub trait Sealed {} } - -/// Associate [`Database`] with a [`Row`] of a generic lifetime. -/// -/// --- -/// -/// The upcoming Rust feature, [Generic Associated Types], should obviate -/// the need for this trait. -/// -/// [Generic Associated Types]: https://www.google.com/search?q=generic+associated+types+rust&oq=generic+associated+types+rust&aqs=chrome..69i57j0l5.3327j0j7&sourceid=chrome&ie=UTF-8 -pub trait HasRow<'c> { - type Database: Database; - - /// The concrete `Row` implementation for this database. - type Row: Row<'c, Database = Self::Database>; -} - -/// A record that can be built from a row returned by the database. -/// -/// In order to use [`query_as`] the output type must implement `FromRow`. -/// -/// # Deriving -/// This trait can be automatically derived by SQLx for any struct. The generated implementation -/// will consist of a sequence of calls to [`Row::try_get`] using the name from each -/// struct field. -/// -/// ```rust,ignore -/// #[derive(sqlx::FromRow)] -/// struct User { -/// id: i32, -/// name: String, -/// } -/// ``` -/// -/// [`query_as`]: crate::query_as -/// [`Row::try_get`]: crate::row::Row::try_get -pub trait FromRow<'c, R> -where - Self: Sized, - R: Row<'c>, -{ - #[allow(missing_docs)] - fn from_row(row: &R) -> crate::Result; -} - -// Macros to help unify the internal implementations as a good chunk -// is very similar - -#[allow(unused_macros)] -macro_rules! impl_from_row_for_tuple { - ($db:ident, $r:ident; $( ($idx:tt) -> $T:ident );+;) => { - impl<'c, $($T,)+> crate::row::FromRow<'c, $r<'c>> for ($($T,)+) - where - $($T: 'c,)+ - $($T: crate::types::Type<$db>,)+ - $($T: crate::decode::Decode<'c, $db>,)+ - { - #[inline] - fn from_row(row: &$r<'c>) -> crate::Result { - use crate::row::Row; - - Ok(($(row.try_get($idx as usize)?,)+)) - } - } - }; -} - -#[allow(unused_macros)] -macro_rules! impl_from_row_for_tuples { - ($db:ident, $r:ident) => { - impl_from_row_for_tuple!($db, $r; - (0) -> T1; - ); - - impl_from_row_for_tuple!($db, $r; - (0) -> T1; - (1) -> T2; - ); - - impl_from_row_for_tuple!($db, $r; - (0) -> T1; - (1) -> T2; - (2) -> T3; - ); - - impl_from_row_for_tuple!($db, $r; - (0) -> T1; - (1) -> T2; - (2) -> T3; - (3) -> T4; - ); - - impl_from_row_for_tuple!($db, $r; - (0) -> T1; - (1) -> T2; - (2) -> T3; - (3) -> T4; - (4) -> T5; - ); - - impl_from_row_for_tuple!($db, $r; - (0) -> T1; - (1) -> T2; - (2) -> T3; - (3) -> T4; - (4) -> T5; - (5) -> T6; - ); - - impl_from_row_for_tuple!($db, $r; - (0) -> T1; - (1) -> T2; - (2) -> T3; - (3) -> T4; - (4) -> T5; - (5) -> T6; - (6) -> T7; - ); - - impl_from_row_for_tuple!($db, $r; - (0) -> T1; - (1) -> T2; - (2) -> T3; - (3) -> T4; - (4) -> T5; - (5) -> T6; - (6) -> T7; - (7) -> T8; - ); - - impl_from_row_for_tuple!($db, $r; - (0) -> T1; - (1) -> T2; - (2) -> T3; - (3) -> T4; - (4) -> T5; - (5) -> T6; - (6) -> T7; - (7) -> T8; - (8) -> T9; - ); - }; -} - -#[allow(unused_macros)] -macro_rules! impl_map_row_for_row { - ($DB:ident, $R:ident) => { - impl crate::query::MapRow<$DB> for F - where - F: for<'c> FnMut($R<'c>) -> O, - { - type Output = O; - - fn map_row(&mut self, row: $R) -> O { - (self)(row) - } - } - - impl crate::query::TryMapRow<$DB> for F - where - F: for<'c> FnMut($R<'c>) -> crate::Result, - { - type Output = O; - - fn try_map_row(&mut self, row: $R) -> crate::Result { - (self)(row) - } - } - }; -} - -#[allow(unused_macros)] -macro_rules! impl_from_row_for_row { - ($R:ident) => { - impl<'c> crate::row::FromRow<'c, $R<'c>> for $R<'c> { - #[inline] - fn from_row(row: $R<'c>) -> crate::Result { - Ok(row) - } - } - }; -} diff --git a/sqlx-core/src/runtime.rs b/sqlx-core/src/runtime.rs deleted file mode 100644 index 49e9c940..00000000 --- a/sqlx-core/src/runtime.rs +++ /dev/null @@ -1,34 +0,0 @@ -#![allow(unused_imports)] - -#[cfg(not(any(feature = "runtime-tokio", feature = "runtime-async-std")))] -compile_error!("one of 'runtime-async-std' or 'runtime-tokio' features must be enabled"); - -#[cfg(all(feature = "runtime-tokio", feature = "runtime-async-std"))] -compile_error!("only one of 'runtime-async-std' or 'runtime-tokio' features must be enabled"); - -#[cfg(feature = "runtime-async-std")] -pub(crate) use async_std::{ - fs, - future::timeout, - io::prelude::ReadExt as AsyncReadExt, - io::{Read as AsyncRead, Write as AsyncWrite}, - net::TcpStream, - task::sleep, - task::spawn, -}; - -#[cfg(all(feature = "runtime-async-std", feature = "postgres", unix))] -pub(crate) use async_std::os::unix::net::UnixStream; - -#[cfg(feature = "runtime-tokio")] -pub(crate) use tokio::{ - fs, - io::{AsyncRead, AsyncReadExt, AsyncWrite}, - net::TcpStream, - task::spawn, - time::delay_for as sleep, - time::timeout, -}; - -#[cfg(all(feature = "runtime-tokio", feature = "postgres", unix))] -pub(crate) use tokio::net::UnixStream; diff --git a/sqlx-core/src/transaction.rs b/sqlx-core/src/transaction.rs deleted file mode 100644 index 30002d21..00000000 --- a/sqlx-core/src/transaction.rs +++ /dev/null @@ -1,225 +0,0 @@ -use std::ops::{Deref, DerefMut}; - -use futures_core::future::BoxFuture; - -use crate::connection::Connection; -use crate::cursor::HasCursor; -use crate::database::Database; -use crate::describe::Describe; -use crate::executor::{Execute, Executor, RefExecutor}; -use crate::runtime::spawn; - -/// Represents an in-progress database transaction. -/// -/// A transaction ends with a call to [`commit`] or [`rollback`] in which the wrapped connection ( -/// or outer transaction) is returned. If neither are called before the transaction -/// goes out-of-scope, [`rollback`] is called. In other words, [`rollback`] is called on `drop` -/// if the transaction is still in-progress. -/// -/// ```rust,ignore -/// // Acquire a new connection and immediately begin a transaction -/// let mut tx = pool.begin().await?; -/// -/// sqlx::query("INSERT INTO articles (slug) VALUES ('this-is-a-slug')") -/// .execute(&mut tx) -/// // As we didn't fill in all the required fields in this INSERT, -/// // this statement will fail. Since we used `?`, this function -/// // will immediately return with the error which will cause -/// // this transaction to be rolled back. -/// .await?; -/// ``` -/// -/// [`commit`]: #method.commit -/// [`rollback`]: #method.rollback -// Transaction> -// Transaction -#[must_use = "transaction rolls back if not explicitly `.commit()`ed"] -pub struct Transaction -where - C: Connection, -{ - inner: Option, - depth: u32, -} - -impl Transaction -where - C: Connection, -{ - pub(crate) async fn new(depth: u32, mut inner: C) -> crate::Result { - if depth == 0 { - inner.execute("BEGIN").await?; - } else { - let stmt = format!("SAVEPOINT _sqlx_savepoint_{}", depth); - - inner.execute(&*stmt).await?; - } - - Ok(Self { - inner: Some(inner), - depth: depth + 1, - }) - } - - /// Creates a new save point in the current transaction and returns - /// a new `Transaction` object to manage its scope. - pub async fn begin(self) -> crate::Result>> { - Transaction::new(self.depth, self).await - } - - /// Commits the current transaction or save point. - /// Returns the inner connection or transaction. - pub async fn commit(mut self) -> crate::Result { - let mut inner = self.inner.take().expect(ERR_FINALIZED); - let depth = self.depth; - - if depth == 1 { - inner.execute("COMMIT").await?; - } else { - let stmt = format!("RELEASE SAVEPOINT _sqlx_savepoint_{}", depth - 1); - - inner.execute(&*stmt).await?; - } - - Ok(inner) - } - - /// Rollback the current transaction or save point. - /// Returns the inner connection or transaction. - pub async fn rollback(mut self) -> crate::Result { - let mut inner = self.inner.take().expect(ERR_FINALIZED); - let depth = self.depth; - - if depth == 1 { - inner.execute("ROLLBACK").await?; - } else { - let stmt = format!("ROLLBACK TO SAVEPOINT _sqlx_savepoint_{}", depth - 1); - - inner.execute(&*stmt).await?; - } - - Ok(inner) - } -} - -const ERR_FINALIZED: &str = "(bug) transaction already finalized"; - -impl Deref for Transaction -where - C: Connection, -{ - type Target = C; - - fn deref(&self) -> &Self::Target { - self.inner.as_ref().expect(ERR_FINALIZED) - } -} - -impl DerefMut for Transaction -where - C: Connection, -{ - fn deref_mut(&mut self) -> &mut Self::Target { - self.inner.as_mut().expect(ERR_FINALIZED) - } -} - -impl Connection for Transaction -where - C: Connection, -{ - // Close is equivalent to - fn close(mut self) -> BoxFuture<'static, crate::Result<()>> { - Box::pin(async move { - let mut inner = self.inner.take().expect(ERR_FINALIZED); - - if self.depth == 1 { - // This is the root transaction, call rollback - let res = inner.execute("ROLLBACK").await; - - // No matter the result of the above, call close - let _ = inner.close().await; - - // Now raise the error if there was one - res?; - } else { - // This is not the root transaction, forward to a nested - // transaction (to eventually call rollback) - inner.close().await? - } - - Ok(()) - }) - } - - #[inline] - fn ping(&mut self) -> BoxFuture<'_, crate::Result<()>> { - self.deref_mut().ping() - } -} - -impl Executor for Transaction -where - DB: Database, - C: Connection, -{ - type Database = C::Database; - - fn execute<'e, 'q: 'e, 'c: 'e, E: 'e>( - &'c mut self, - query: E, - ) -> BoxFuture<'e, crate::Result> - where - E: Execute<'q, Self::Database>, - { - (**self).execute(query) - } - - fn fetch<'e, 'q, E>(&'e mut self, query: E) -> >::Cursor - where - E: Execute<'q, Self::Database>, - { - (**self).fetch(query) - } - - #[doc(hidden)] - fn describe<'e, 'q, E: 'e>( - &'e mut self, - query: E, - ) -> BoxFuture<'e, crate::Result>> - where - E: Execute<'q, Self::Database>, - { - (**self).describe(query) - } -} - -impl<'e, DB, C> RefExecutor<'e> for &'e mut Transaction -where - DB: Database, - C: Connection, -{ - type Database = DB; - - fn fetch_by_ref<'q, E>(self, query: E) -> >::Cursor - where - E: Execute<'q, Self::Database>, - { - (**self).fetch(query) - } -} - -impl Drop for Transaction -where - C: Connection, -{ - fn drop(&mut self) { - if self.depth > 0 { - if let Some(inner) = self.inner.take() { - spawn(async move { - let _ = inner.close().await; - }); - } - } - } -} diff --git a/sqlx-core/src/type_info.rs b/sqlx-core/src/type_info.rs new file mode 100644 index 00000000..bf698251 --- /dev/null +++ b/sqlx-core/src/type_info.rs @@ -0,0 +1,3 @@ +use std::fmt::{Debug, Display}; + +pub trait TypeInfo: Debug + Display + Clone + PartialEq {} diff --git a/sqlx-core/src/types.rs b/sqlx-core/src/types.rs deleted file mode 100644 index c0383af9..00000000 --- a/sqlx-core/src/types.rs +++ /dev/null @@ -1,146 +0,0 @@ -//! Conversions between Rust and SQL types. -//! -//! To see how each SQL type maps to a Rust type, see the corresponding `types` module for each -//! database: -//! -//! * [PostgreSQL](../postgres/types/index.html) -//! * [MySQL](../mysql/types/index.html) -//! * [SQLite](../sqlite/types/index.html) -//! -//! Any external types that have had [`Type`] implemented for, are re-exported in this module -//! for convenience as downstream users need to use a compatible version of the external crate -//! to take advantage of the implementation. - -use std::fmt::{Debug, Display}; - -use crate::database::Database; - -#[cfg(feature = "uuid")] -#[cfg_attr(docsrs, doc(cfg(feature = "uuid")))] -pub use uuid::Uuid; - -#[cfg(feature = "chrono")] -#[cfg_attr(docsrs, doc(cfg(feature = "chrono")))] -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, OffsetDateTime, PrimitiveDateTime, Time, UtcOffset}; -} - -#[cfg(feature = "bigdecimal")] -#[cfg_attr(docsrs, doc(cfg(feature = "bigdecimal")))] -pub use bigdecimal::BigDecimal; - -#[cfg(feature = "ipnetwork")] -#[cfg_attr(docsrs, doc(cfg(feature = "ipnetwork")))] -pub mod ipnetwork { - pub use ipnetwork::{IpNetwork, Ipv4Network, Ipv6Network}; -} - -#[cfg(feature = "json")] -pub mod json { - use crate::database::Database; - use crate::decode::Decode; - use crate::encode::Encode; - use crate::value::HasRawValue; - use serde::{Deserialize, Serialize}; - use serde_json::value::RawValue as JsonRawValue; - use serde_json::Value as JsonValue; - use std::ops::Deref; - - #[cfg_attr(docsrs, doc(cfg(feature = "json")))] - #[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Default)] - #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] - pub struct Json(pub T); - - impl Deref for Json { - type Target = T; - - fn deref(&self) -> &Self::Target { - &self.0 - } - } - - impl AsRef for Json { - fn as_ref(&self) -> &T { - &self.0 - } - } - - impl Encode for JsonValue - where - for<'a> Json<&'a Self>: Encode, - DB: Database, - { - fn encode(&self, buf: &mut DB::RawBuffer) { - as Encode>::encode(&Json(self), buf) - } - } - - impl<'de, DB> Decode<'de, DB> for JsonValue - where - Json: Decode<'de, DB>, - DB: Database, - { - fn decode(value: >::RawValue) -> crate::Result { - as Decode>::decode(value).map(|item| item.0) - } - } - - // We don't have to implement Encode for JsonRawValue because that's covered by the default - // implementation for Encode - impl<'de, DB> Decode<'de, DB> for &'de JsonRawValue - where - Json: Decode<'de, DB>, - DB: Database, - { - fn decode(value: >::RawValue) -> crate::Result { - as Decode>::decode(value).map(|item| item.0) - } - } -} -#[cfg(feature = "json")] -pub use self::json::Json; - -pub trait TypeInfo: PartialEq + Debug + Display + Clone { - /// Compares type information to determine if `other` is compatible at the Rust level - /// with `self`. - fn compatible(&self, other: &Self) -> bool; -} - -/// Indicates that a SQL type is supported for a database. -pub trait Type -where - DB: Database, -{ - /// Returns the canonical type information on the database for the type `T`. - fn type_info() -> DB::TypeInfo; -} - -// For references to types in Rust, the underlying SQL type information -// is equivalent -impl Type for &'_ T -where - DB: Database, - T: Type, -{ - fn type_info() -> DB::TypeInfo { - >::type_info() - } -} - -// For optional types in Rust, the underlying SQL type information -// is equivalent -impl Type for Option -where - DB: Database, - T: Type, -{ - fn type_info() -> DB::TypeInfo { - >::type_info() - } -} diff --git a/sqlx-core/src/types/json.rs b/sqlx-core/src/types/json.rs new file mode 100644 index 00000000..567bd802 --- /dev/null +++ b/sqlx-core/src/types/json.rs @@ -0,0 +1,90 @@ +use std::ops::Deref; + +use serde_json::value::RawValue as JsonRawValue; +use serde_json::Value as JsonValue; + +use crate::database::{Database, HasArguments, HasValueRef}; +use crate::decode::Decode; +use crate::encode::{Encode, IsNull}; +use crate::error::BoxDynError; +use crate::types::Type; + +#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Default)] +pub struct Json(pub T); + +impl Deref for Json { + type Target = T; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl AsRef for Json { + fn as_ref(&self) -> &T { + &self.0 + } +} + +impl Type for JsonValue +where + Json: Type, + DB: Database, +{ + fn type_info() -> DB::TypeInfo { + as Type>::type_info() + } +} + +impl<'q, DB> Encode<'q, DB> for JsonValue +where + Self: Type, + for<'a> Json<&'a Self>: Encode<'q, DB>, + DB: Database, +{ + fn encode_by_ref(&self, buf: &mut >::Arguments) -> IsNull { + as Encode<'q, DB>>::encode(Json(self), buf) + } +} + +impl<'r, DB> Decode<'r, DB> for JsonValue +where + Self: Type, + Json: Decode<'r, DB>, + DB: Database, +{ + fn accepts(ty: &DB::TypeInfo) -> bool { + as Decode>::accepts(ty) + } + + fn decode(value: >::ValueRef) -> Result { + as Decode>::decode(value).map(|item| item.0) + } +} + +impl Type for JsonRawValue +where + for<'a> Json<&'a Self>: Type, + DB: Database, +{ + fn type_info() -> DB::TypeInfo { + as Type>::type_info() + } +} + +// We don't have to implement Encode for JsonRawValue because that's covered by the default +// implementation for Encode +impl<'r, DB> Decode<'r, DB> for &'r JsonRawValue +where + Self: Type, + Json: Decode<'r, DB>, + DB: Database, +{ + fn accepts(ty: &DB::TypeInfo) -> bool { + as Decode>::accepts(ty) + } + + fn decode(value: >::ValueRef) -> Result { + as Decode>::decode(value).map(|item| item.0) + } +} diff --git a/sqlx-core/src/types/mod.rs b/sqlx-core/src/types/mod.rs new file mode 100644 index 00000000..e6a9df0a --- /dev/null +++ b/sqlx-core/src/types/mod.rs @@ -0,0 +1,69 @@ +//! Conversions between Rust and SQL types. +//! +//! To see how each SQL type maps to a Rust type, see the corresponding `types` module for each +//! database: +//! +//! * [PostgreSQL](../postgres/types/index.html) +//! * [MySQL](../mysql/types/index.html) +//! * [SQLite](../sqlite/types/index.html) +//! +//! Any external types that have had [`Type`] implemented for, are re-exported in this module +//! for convenience as downstream users need to use a compatible version of the external crate +//! to take advantage of the implementation. + +use crate::database::Database; + +#[cfg(feature = "json")] +#[cfg_attr(docsrs, doc(cfg(feature = "json")))] +mod json; + +#[cfg(feature = "uuid")] +#[cfg_attr(docsrs, doc(cfg(feature = "uuid")))] +pub use uuid::Uuid; + +#[cfg(feature = "chrono")] +#[cfg_attr(docsrs, doc(cfg(feature = "chrono")))] +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, OffsetDateTime, PrimitiveDateTime, Time, UtcOffset}; +} + +#[cfg(feature = "bigdecimal")] +#[cfg_attr(docsrs, doc(cfg(feature = "bigdecimal")))] +pub use bigdecimal::BigDecimal; + +#[cfg(feature = "ipnetwork")] +#[cfg_attr(docsrs, doc(cfg(feature = "ipnetwork")))] +pub mod ipnetwork { + pub use ipnetwork::{IpNetwork, Ipv4Network, Ipv6Network}; +} + +#[cfg(feature = "json")] +pub use json::Json; + +/// Indicates that a SQL type is supported for a database. +pub trait Type { + /// Returns the canonical type information on the database for the type `T`. + fn type_info() -> DB::TypeInfo; +} + +// for references, the underlying SQL type is identical +impl, DB: Database> Type for &'_ T { + #[inline] + fn type_info() -> DB::TypeInfo { + >::type_info() + } +} + +// for optionals, the underlying SQL type is identical +impl, DB: Database> Type for Option { + #[inline] + fn type_info() -> DB::TypeInfo { + >::type_info() + } +} diff --git a/sqlx-core/src/url.rs b/sqlx-core/src/url.rs index 8015b9e1..8b137891 100644 --- a/sqlx-core/src/url.rs +++ b/sqlx-core/src/url.rs @@ -1,148 +1 @@ -use std::borrow::Cow; -use std::convert::{TryFrom, TryInto}; -#[derive(Debug)] -pub struct Url(url::Url); - -impl TryFrom for Url { - type Error = url::ParseError; - - fn try_from(value: String) -> Result { - (&value).try_into() - } -} - -impl<'s> TryFrom<&'s str> for Url { - type Error = url::ParseError; - - fn try_from(value: &'s str) -> Result { - Ok(Url(value.parse()?)) - } -} - -impl<'s> TryFrom<&'s String> for Url { - type Error = url::ParseError; - - fn try_from(value: &'s String) -> Result { - (value.as_str()).try_into() - } -} - -impl TryFrom for Url { - type Error = url::ParseError; - - fn try_from(value: url::Url) -> Result { - Ok(Url(value)) - } -} - -impl Url { - #[allow(dead_code)] - pub(crate) fn as_str(&self) -> &str { - self.0.as_str() - } - - pub fn host(&self) -> Option<&str> { - match self.0.host_str()? { - "" => None, - host => Some(host), - } - } - - pub fn port(&self, default: u16) -> u16 { - self.0.port().unwrap_or(default) - } - - pub fn username(&self) -> Option> { - let username = self.0.username(); - - if username.is_empty() { - None - } else { - Some( - percent_encoding::percent_decode_str(username) - .decode_utf8() - .expect("percent-encoded username contained non-UTF-8 bytes"), - ) - } - } - - pub fn password(&self) -> Option> { - match self.0.password() { - Some(s) => { - let decoded = percent_encoding::percent_decode_str(s); - - // FIXME: Handle error - Some( - decoded - .decode_utf8() - .expect("percent-encoded password contained non-UTF-8 bytes"), - ) - } - None => None, - } - } - - /// Undo URL percent-encoding and return [authority]path[query] - /// - /// Mostly a hack to fix special-character handling for SQLite as its connection string is a - /// file path and not _really_ a URL - pub fn path_decoded(&self) -> Cow { - // omit scheme (e.g. `sqlite://`, `mysql://`) - let url_str = &self.0.as_str()[self.0.scheme().len()..] - .trim_start_matches(':') - .trim_start_matches("//"); - - // decode - percent_encoding::percent_decode_str(url_str) - .decode_utf8() - .expect("percent-encoded path contained non-UTF-8 bytes") - } - - pub fn database(&self) -> Option<&str> { - let database = self.0.path().trim_start_matches('/'); - - if database.is_empty() { - None - } else { - Some(database) - } - } - - pub fn param(&self, key: &str) -> Option> { - self.0 - .query_pairs() - .find_map(|(key_, val)| if key == key_ { Some(val) } else { None }) - } -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn azure_connection_string_username_unencoded() { - let connection_string = - "postgres://username@servername:password@example.postgres.database.azure.com/db"; - - let url = Url::try_from(connection_string).expect("Failed to parse URL"); - - assert_eq!( - url.username().map(|u| u.to_string()), - Some(String::from("username@servername")) - ); - } - - #[test] - fn azure_connection_string_username_encoded() { - let connection_string = - "postgres://username%40servername:password@example.postgres.database.azure.com/db"; - - let url = Url::try_from(connection_string).expect("Failed to parse URL"); - - assert_eq!( - url.username().map(|u| u.to_string()), - Some(String::from("username@servername")) - ); - } -} diff --git a/sqlx-core/src/value.rs b/sqlx-core/src/value.rs index 70861776..b22f324c 100644 --- a/sqlx-core/src/value.rs +++ b/sqlx-core/src/value.rs @@ -1,23 +1,121 @@ -use crate::database::Database; +use std::borrow::Cow; -/// Associate [`Database`] with a `RawValue` of a generic lifetime. -/// -/// --- -/// -/// The upcoming Rust feature, [Generic Associated Types], should obviate -/// the need for this trait. -/// -/// [Generic Associated Types]: https://www.google.com/search?q=generic+associated+types+rust&oq=generic+associated+types+rust&aqs=chrome..69i57j0l5.3327j0j7&sourceid=chrome&ie=UTF-8 -pub trait HasRawValue<'c> { +use crate::database::{Database, HasValueRef}; +use crate::decode::Decode; +use crate::error::{mismatched_types, Error}; + +/// An owned value from the database. +pub trait Value { type Database: Database; - /// The Rust type used to hold a not-yet-decoded value that has just been - /// received from the database. - type RawValue: RawValue<'c, Database = Self::Database>; + /// Get this value as a reference. + fn as_ref(&self) -> >::ValueRef; + + /// Get the type information, if available, for this value. + /// + /// Some database implementations do not implement type deduction for + /// expressions (`SELECT 2 + 5`); and, this will return `None` in those cases. + fn type_info(&self) -> Option::TypeInfo>>; + + /// Returns `true` if the SQL value is `NULL`. + fn is_null(&self) -> bool; + + /// Decode this single value into the requested type. + /// + /// # Panics + /// + /// Panics if the value cannot be decoded into the requested type. + /// See [`try_decode`](#method.try_decode) for a non-panicking version. + /// + #[inline] + fn decode<'r, T>(&'r self) -> T + where + T: Decode<'r, Self::Database>, + { + self.try_decode::().unwrap() + } + + /// Decode this single value into the requested type. + /// + /// Unlike [`decode`](#method.decode), this method does not check that the type of this + /// value is compatible with the Rust type and blindly tries to decode the value. + /// + /// # Panics + /// + /// Panics if the value cannot be decoded into the requested type. + /// See [`try_decode_unchecked`](#method.try_decode_unchecked) for a non-panicking version. + /// + #[inline] + fn decode_unchecked<'r, T>(&'r self) -> T + where + T: Decode<'r, Self::Database>, + { + self.try_decode_unchecked::().unwrap() + } + + /// Decode this single value into the requested type. + /// + /// # Errors + /// + /// * [`Decode`] if the value could not be decoded into the requested type. + /// + /// [`Decode`]: crate::Error::Decode + /// + #[inline] + fn try_decode<'r, T>(&'r self) -> Result + where + T: Decode<'r, Self::Database>, + { + if !self.is_null() { + if let Some(actual_ty) = self.type_info() { + if !T::accepts(&actual_ty) { + return Err(Error::Decode(mismatched_types::( + &T::type_info(), + &actual_ty, + ))); + } + } + } + + self.try_decode_unchecked() + } + + /// Decode this single value into the requested type. + /// + /// Unlike [`try_decode`](#method.try_decode), this method does not check that the type of this + /// value is compatible with the Rust type and blindly tries to decode the value. + /// + /// # Errors + /// + /// * [`Decode`] if the value could not be decoded into the requested type. + /// + /// [`Decode`]: crate::Error::Decode + /// + #[inline] + fn try_decode_unchecked<'r, T>(&'r self) -> Result + where + T: Decode<'r, Self::Database>, + { + T::decode(self.as_ref()).map_err(Error::Decode) + } } -pub trait RawValue<'c> { +/// A reference to a single value from the database. +pub trait ValueRef<'r>: Sized { type Database: Database; - fn type_info(&self) -> Option<::TypeInfo>; + /// Creates an owned value from this value reference. + /// + /// This is just a reference increment in PostgreSQL and MySQL and thus is `O(1)`. In SQLite, + /// this is a copy. + fn to_owned(&self) -> ::Value; + + /// Get the type information, if available, for this value. + /// + /// Some database implementations do not implement type deduction for + /// expressions (`SELECT 2 + 5`); and, this will return `None` in those cases. + fn type_info(&self) -> Option::TypeInfo>>; + + /// Returns `true` if the SQL value is `NULL`. + fn is_null(&self) -> bool; } diff --git a/sqlx-rt/src/lib.rs b/sqlx-rt/src/lib.rs index 072fb181..8c9dd777 100644 --- a/sqlx-rt/src/lib.rs +++ b/sqlx-rt/src/lib.rs @@ -28,7 +28,7 @@ pub use native_tls; ))] pub use tokio::{ self, fs, io::AsyncRead, io::AsyncReadExt, io::AsyncWrite, io::AsyncWriteExt, net::TcpStream, - task::yield_now, + task::yield_now, time::delay_for as sleep, time::timeout, }; #[cfg(all( @@ -89,8 +89,9 @@ macro_rules! blocking { not(any(feature = "runtime-actix", feature = "runtime-tokio",)) ))] pub use async_std::{ - self, fs, io::prelude::ReadExt as AsyncReadExt, io::prelude::WriteExt as AsyncWriteExt, - io::Read as AsyncRead, io::Write as AsyncWrite, net::TcpStream, task::spawn, task::yield_now, + self, fs, future::timeout, io::prelude::ReadExt as AsyncReadExt, + io::prelude::WriteExt as AsyncWriteExt, io::Read as AsyncRead, io::Write as AsyncWrite, + net::TcpStream, task::sleep, task::spawn, task::yield_now, }; #[cfg(all(