diff --git a/.github/workflows/sqlx.yml b/.github/workflows/sqlx.yml index d5e8692c1e..35750698cc 100644 --- a/.github/workflows/sqlx.yml +++ b/.github/workflows/sqlx.yml @@ -33,8 +33,8 @@ jobs: runs-on: ubuntu-20.04 strategy: matrix: - runtime: [async-std, tokio, actix] - tls: [native-tls, rustls] + runtime: [async-std, tokio] + tls: [native-tls, rustls, none] steps: - uses: actions/checkout@v2 @@ -54,22 +54,22 @@ jobs: args: > --manifest-path sqlx-core/Cargo.toml --no-default-features - --features offline,all-databases,all-types,migrate,runtime-${{ matrix.runtime }}-${{ matrix.tls }} + --features json,offline,migrate,_rt-${{ matrix.runtime }},_tls-${{ matrix.tls }} - uses: actions-rs/cargo@v1 with: command: check args: > --no-default-features - --features offline,all-databases,all-types,migrate,runtime-${{ matrix.runtime }}-${{ matrix.tls }},macros + --features all-databases,_unstable-all-types,runtime-${{ matrix.runtime }},tls-${{ matrix.tls }},macros test: name: Unit Test runs-on: ubuntu-20.04 strategy: matrix: - runtime: [async-std, tokio, actix] - tls: [native-tls, rustls] + runtime: [async-std, tokio] + tls: [native-tls, rustls, none] steps: - uses: actions/checkout@v2 @@ -88,7 +88,7 @@ jobs: command: test args: > --manifest-path sqlx-core/Cargo.toml - --features offline,all-databases,all-types,runtime-${{ matrix.runtime }}-${{ matrix.tls }} + --features json,_rt-${{ matrix.runtime }},_tls-${{ matrix.tls }} cli: name: CLI Binaries @@ -139,8 +139,7 @@ jobs: runs-on: ubuntu-20.04 strategy: matrix: - runtime: [async-std, tokio, actix] - tls: [native-tls, rustls] + runtime: [async-std, tokio] needs: check steps: - uses: actions/checkout@v2 @@ -157,16 +156,18 @@ jobs: with: key: ${{ runner.os }}-sqlite-${{ matrix.runtime }}-${{ matrix.tls }} + - run: echo "using ${DATABASE_URL}" + - uses: actions-rs/cargo@v1 with: command: test args: > --no-default-features - --features any,macros,migrate,sqlite,all-types,runtime-${{ matrix.runtime }}-${{ matrix.tls }} + --features any,macros,sqlite,_unstable-all-types,runtime-${{ matrix.runtime }} -- --test-threads=1 env: - DATABASE_URL: sqlite://tests/sqlite/sqlite.db + DATABASE_URL: sqlite:tests/sqlite/sqlite.db RUSTFLAGS: --cfg sqlite_ipaddr LD_LIBRARY_PATH: /tmp/sqlite3-lib @@ -176,8 +177,8 @@ jobs: strategy: matrix: postgres: [14, 10] - runtime: [async-std, tokio, actix] - tls: [native-tls, rustls] + runtime: [async-std, tokio] + tls: [native-tls, rustls, none] needs: check steps: - uses: actions/checkout@v2 @@ -200,7 +201,7 @@ jobs: with: command: build args: > - --features postgres,all-types,runtime-${{ matrix.runtime }}-${{ matrix.tls }} + --features postgres,_unstable-all-types,runtime-${{ matrix.runtime }},tls-${{ matrix.tls }} - run: | docker-compose -f tests/docker-compose.yml run -d -p 5432:5432 --name postgres_${{ matrix.postgres }} postgres_${{ matrix.postgres }} @@ -211,7 +212,7 @@ jobs: command: test args: > --no-default-features - --features any,postgres,macros,all-types,runtime-${{ matrix.runtime }}-${{ matrix.tls }} + --features any,postgres,macros,_unstable-all-types,runtime-${{ matrix.runtime }},tls-${{ matrix.tls }} env: DATABASE_URL: postgres://postgres:password@localhost:5432/sqlx # FIXME: needed to disable `ltree` tests in Postgres 9.6 @@ -219,11 +220,12 @@ jobs: RUSTFLAGS: --cfg postgres_${{ matrix.postgres }} - uses: actions-rs/cargo@v1 + if: matrix.tls != 'none' with: command: test args: > --no-default-features - --features any,postgres,macros,migrate,all-types,runtime-${{ matrix.runtime }}-${{ matrix.tls }} + --features any,postgres,macros,_unstable-all-types,runtime-${{ matrix.runtime }},tls-${{ matrix.tls }} env: DATABASE_URL: postgres://postgres:password@localhost:5432/sqlx?sslmode=verify-ca&sslrootcert=.%2Ftests%2Fcerts%2Fca.crt # FIXME: needed to disable `ltree` tests in Postgres 9.6 @@ -236,8 +238,8 @@ jobs: strategy: matrix: mysql: [8, 5_7] - runtime: [async-std, tokio, actix] - tls: [native-tls, rustls] + runtime: [async-std, tokio] + tls: [native-tls, rustls, none] needs: check steps: - uses: actions/checkout@v2 @@ -256,7 +258,7 @@ jobs: with: command: build args: > - --features mysql,all-types,runtime-${{ matrix.runtime }}-${{ matrix.tls }} + --features mysql,_unstable-all-types,runtime-${{ matrix.runtime }},tls-${{ matrix.tls }} - run: docker-compose -f tests/docker-compose.yml run -d -p 3306:3306 mysql_${{ matrix.mysql }} - run: sleep 60 @@ -266,7 +268,7 @@ jobs: command: test args: > --no-default-features - --features any,mysql,macros,migrate,all-types,runtime-${{ matrix.runtime }}-${{ matrix.tls }} + --features any,mysql,macros,_unstable-all-types,runtime-${{ matrix.runtime }},tls-${{ matrix.tls }} env: DATABASE_URL: mysql://root:password@localhost:3306/sqlx?ssl-mode=disabled @@ -277,7 +279,7 @@ jobs: command: test args: > --no-default-features - --features any,mysql,macros,migrate,all-types,runtime-${{ matrix.runtime }}-${{ matrix.tls }} + --features any,mysql,macros,_unstable-all-types,runtime-${{ matrix.runtime }},tls-${{ matrix.tls }} env: DATABASE_URL: mysql://root:password@localhost:3306/sqlx @@ -287,8 +289,8 @@ jobs: strategy: matrix: mariadb: [10_6, 10_3] - runtime: [async-std, tokio, actix] - tls: [native-tls, rustls] + runtime: [async-std, tokio] + tls: [native-tls, rustls, none] needs: check steps: - uses: actions/checkout@v2 @@ -307,7 +309,7 @@ jobs: with: command: build args: > - --features mysql,all-types,runtime-${{ matrix.runtime }}-${{ matrix.tls }} + --features mysql,_unstable-all-types,runtime-${{ matrix.runtime }},tls-${{ matrix.tls }} - run: docker-compose -f tests/docker-compose.yml run -d -p 3306:3306 mariadb_${{ matrix.mariadb }} - run: sleep 30 @@ -317,46 +319,6 @@ jobs: command: test args: > --no-default-features - --features any,mysql,macros,migrate,all-types,runtime-${{ matrix.runtime }}-${{ matrix.tls }} + --features any,mysql,macros,_unstable-all-types,runtime-${{ matrix.runtime }},tls-${{ matrix.tls }} env: DATABASE_URL: mysql://root:password@localhost:3306/sqlx - - mssql: - name: MSSQL - runs-on: ubuntu-20.04 - strategy: - matrix: - mssql: [2019, 2017] - runtime: [async-std, tokio, actix] - tls: [native-tls, rustls] - needs: check - steps: - - uses: actions/checkout@v2 - - - uses: actions-rs/toolchain@v1 - with: - profile: minimal - toolchain: stable - override: true - - - uses: Swatinem/rust-cache@v1 - with: - key: ${{ runner.os }}-mssql-${{ matrix.runtime }}-${{ matrix.tls }}-${{ hashFiles('**/Cargo.lock') }} - - - uses: actions-rs/cargo@v1 - with: - command: build - args: > - --features mssql,all-types,runtime-${{ matrix.runtime }}-${{ matrix.tls }} - - - run: docker-compose -f tests/docker-compose.yml run -d -p 1433:1433 mssql_${{ matrix.mssql }} - - run: sleep 80 # MSSQL takes a "bit" to startup - - - uses: actions-rs/cargo@v1 - with: - command: test - args: > - --no-default-features - --features any,mssql,macros,migrate,all-types,runtime-${{ matrix.runtime }}-${{ matrix.tls }} - env: - DATABASE_URL: mssql://sa:Password123!@localhost/sqlx diff --git a/Cargo.lock b/Cargo.lock index 39f428ef48..b9233fcb0d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -15,13 +15,22 @@ dependencies = [ [[package]] name = "aho-corasick" -version = "0.7.18" +version = "0.7.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e37cfd5e7657ada45f742d6e99ca5788580b5c529dc78faf11ece6dc702656f" +checksum = "cc936419f96fa211c1b9166887b38e5e40b19958e5b895be7c1f93adec7071ac" dependencies = [ "memchr", ] +[[package]] +name = "android_system_properties" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" +dependencies = [ + "libc", +] + [[package]] name = "ansi_term" version = "0.12.1" @@ -33,9 +42,9 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.58" +version = "1.0.68" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bb07d2053ccdbe10e2af2995a2f116c1330396493dc1269f6a91d0ae82e19704" +checksum = "2cb2f989d18dd141ab8ae82f64d1a8cdd37e0840f73a406896cf5e99502fab61" [[package]] name = "argon2" @@ -66,9 +75,9 @@ dependencies = [ [[package]] name = "async-channel" -version = "1.6.1" +version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2114d64672151c0c5eaa5e131ec84a74f06e1e559830dabba01ca30605d66319" +checksum = "cf46fee83e5ccffc220104713af3292ff9bc7c64c7de289f66dae8e38d826833" dependencies = [ "concurrent-queue", "event-listener", @@ -77,23 +86,23 @@ dependencies = [ [[package]] name = "async-executor" -version = "1.4.1" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "871f9bb5e0a22eeb7e8cf16641feb87c9dc67032ccf8ff49e772eb9941d3a965" +checksum = "17adb73da160dfb475c183343c8cccd80721ea5a605d3eb57125f0a7b7a92d0b" dependencies = [ + "async-lock", "async-task", "concurrent-queue", "fastrand", "futures-lite", - "once_cell", "slab", ] [[package]] name = "async-global-executor" -version = "2.2.0" +version = "2.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5262ed948da60dd8956c6c5aca4d4163593dddb7b32d73267c93dab7b2e98940" +checksum = "f1b6f5d7df27bd294849f8eec66ecfc63d11814df7a4f5d74168a2394467b776" dependencies = [ "async-channel", "async-executor", @@ -101,65 +110,37 @@ dependencies = [ "async-lock", "blocking", "futures-lite", - "num_cpus", "once_cell", ] [[package]] name = "async-io" -version = "1.7.0" +version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5e18f61464ae81cde0a23e713ae8fd299580c54d697a35820cfd0625b8b0e07" +checksum = "8c374dda1ed3e7d8f0d9ba58715f924862c63eae6849c92d3a18e7fbde9e2794" dependencies = [ + "async-lock", + "autocfg", "concurrent-queue", "futures-lite", "libc", "log", - "once_cell", "parking", "polling", "slab", "socket2", "waker-fn", - "winapi", + "windows-sys", ] [[package]] name = "async-lock" -version = "2.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e97a171d191782fba31bb902b14ad94e24a68145032b7eedf871ab0bc0d077b6" -dependencies = [ - "event-listener", -] - -[[package]] -name = "async-native-tls" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d57d4cec3c647232e1094dc013546c0b33ce785d8aeb251e1f20dfaf8a9a13fe" -dependencies = [ - "futures-util", - "native-tls", - "thiserror", - "url", -] - -[[package]] -name = "async-process" -version = "1.4.0" +version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf2c06e30a24e8c78a3987d07f0930edf76ef35e027e7bdb063fccafdad1f60c" +checksum = "c8101efe8695a6c17e02911402145357e718ac92d3ff88ae8419e84b1707b685" dependencies = [ - "async-io", - "blocking", - "cfg-if", "event-listener", "futures-lite", - "libc", - "once_cell", - "signal-hook", - "winapi", ] [[package]] @@ -173,7 +154,6 @@ dependencies = [ "async-global-executor", "async-io", "async-lock", - "async-process", "crossbeam-utils", "futures-channel", "futures-core", @@ -198,9 +178,9 @@ checksum = "7a40729d2133846d9ed0ea60a8b9541bccddab49cd30f0715a1da672fe9a2524" [[package]] name = "async-trait" -version = "0.1.56" +version = "0.1.64" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96cf8829f67d2eab0b2dfa42c5d0ef737e0724e4a82b01b3e292456202b19716" +checksum = "1cd7fce9ba8c3c042128ce72d8b2ddbf3a05747efb67ea0313c635e10bda47a2" dependencies = [ "proc-macro2", "quote", @@ -218,9 +198,9 @@ dependencies = [ [[package]] name = "atomic-waker" -version = "1.0.0" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "065374052e7df7ee4047b1160cca5e1467a12351a40b3da123c870ba0b8eda2a" +checksum = "debc29dde2e69f9e47506b525f639ed42300fc014a3e007832592448fa8e4599" [[package]] name = "atty" @@ -228,7 +208,7 @@ version = "0.2.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" dependencies = [ - "hermit-abi", + "hermit-abi 0.1.19", "libc", "winapi", ] @@ -241,9 +221,9 @@ checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" [[package]] name = "axum" -version = "0.5.13" +version = "0.5.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6b9496f0c1d1afb7a2af4338bbe1d969cddfead41d87a9fb3aaa6d0bbc7af648" +checksum = "acee9fd5073ab6b045a275b3e709c163dd36c90685219cb21804a147b58dba43" dependencies = [ "async-trait", "axum-core", @@ -254,7 +234,7 @@ dependencies = [ "http", "http-body", "hyper", - "itoa 1.0.2", + "itoa 1.0.5", "matchit", "memchr", "mime", @@ -273,9 +253,9 @@ dependencies = [ [[package]] name = "axum-core" -version = "0.2.7" +version = "0.2.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e4f44a0e6200e9d11a1cdc989e4b358f6e3d354fbf48478f345a17f4e43f8635" +checksum = "37e5939e02c56fecd5c017c37df4238c0a839fa76b7f97acdd7efb804fd181cc" dependencies = [ "async-trait", "bytes", @@ -283,6 +263,8 @@ dependencies = [ "http", "http-body", "mime", + "tower-layer", + "tower-service", ] [[package]] @@ -313,15 +295,21 @@ dependencies = [ [[package]] name = "base64" -version = "0.13.0" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" + +[[package]] +name = "base64" +version = "0.21.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "904dfeac50f3cdaba28fc6f57fdcddb75f49ed61346676a78c4ffe55877802fd" +checksum = "a4a4ddaa51a5bc52a6948f74c06d20aaaddb71924eab79b8c97a8c556e942d6a" [[package]] name = "base64ct" -version = "1.5.1" +version = "1.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3bdca834647821e0b13d9539a8634eb62d3501b6b6c2cec1722786ee6671b851" +checksum = "b645a089122eccb6111b4f81cbc1a49f5900ac4666bb93ac027feaecf15607bf" [[package]] name = "bigdecimal" @@ -348,34 +336,79 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "blake2" -version = "0.10.4" +version = "0.10.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9cf849ee05b2ee5fba5e36f97ff8ec2533916700fc0758d40d92136a42f3388" +checksum = "46502ad458c9a52b69d4d4d32775c788b7a1b85e8bc9d482d92250fc0e3f8efe" dependencies = [ "digest", ] [[package]] name = "block-buffer" -version = "0.10.2" +version = "0.10.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0bf7fe51849ea569fd452f37822f606a5cabb684dc918707a0193fd4664ff324" +checksum = "69cce20737498f97b993470a6e536b8523f0af7892a4f928cceb1ac5e52ebe7e" dependencies = [ "generic-array", ] [[package]] name = "blocking" -version = "1.2.0" +version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c6ccb65d468978a086b69884437ded69a90faab3bbe6e67f242173ea728acccc" +checksum = "3c67b173a56acffd6d2326fb7ab938ba0b00a71480e14902b2591c87bc5741e8" dependencies = [ "async-channel", + "async-lock", "async-task", "atomic-waker", "fastrand", "futures-lite", - "once_cell", +] + +[[package]] +name = "borsh" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "15bf3650200d8bffa99015595e10f1fbd17de07abbc25bb067da79e769939bfa" +dependencies = [ + "borsh-derive", + "hashbrown 0.11.2", +] + +[[package]] +name = "borsh-derive" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6441c552f230375d18e3cc377677914d2ca2b0d36e52129fe15450a2dce46775" +dependencies = [ + "borsh-derive-internal", + "borsh-schema-derive-internal", + "proc-macro-crate", + "proc-macro2", + "syn", +] + +[[package]] +name = "borsh-derive-internal" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5449c28a7b352f2d1e592a8a28bf139bc71afb0764a14f3c02500935d8c44065" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "borsh-schema-derive-internal" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cdbd5696d8bfa21d53d9fe39a714a18538bad11492a42d066dbbc395fb1951c0" +dependencies = [ + "proc-macro2", + "quote", + "syn", ] [[package]] @@ -392,9 +425,30 @@ dependencies = [ [[package]] name = "bumpalo" -version = "3.10.0" +version = "3.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d261e256854913907f67ed06efbc3338dfe6179796deefc1ff763fc1aee5535" + +[[package]] +name = "bytecheck" +version = "0.6.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d11cac2c12b5adc6570dad2ee1b87eff4955dac476fe12d81e5fdd352e52406f" +dependencies = [ + "bytecheck_derive", + "ptr_meta", +] + +[[package]] +name = "bytecheck_derive" +version = "0.6.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37ccbd214614c6783386c1af30caf03192f17891059cecc394b4fb119e363de3" +checksum = "13e576ebe98e605500b3c8041bb888e966653577172df6dd97398714eb30b9bf" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] [[package]] name = "byteorder" @@ -404,21 +458,15 @@ checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" [[package]] name = "bytes" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4872d67bab6358e59559027aa3b9157c53d9358c51423c17554809a8858e0f8" - -[[package]] -name = "cache-padded" -version = "1.2.0" +version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1db59621ec70f09c5e9b597b220c7a2b43611f4710dc03ceb8748637775692c" +checksum = "dfb24e866b15a1af2a1b663f10c6b6b8f397a84aadb828f12e5b289ec23a3a3c" [[package]] name = "camino" -version = "1.0.9" +version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "869119e97797867fd90f5e22af7d0bd274bd4635ebb9eb68c04f3f513ae6c412" +checksum = "c77df041dc383319cc661b428b6961a005db4d6808d5e12536931b1ca9556055" dependencies = [ "serde", ] @@ -445,15 +493,6 @@ dependencies = [ "serde_json", ] -[[package]] -name = "cast" -version = "0.2.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c24dab4283a142afa2fdca129b80ad2c6284e073930f964c3a1293c225ee39a" -dependencies = [ - "rustc_version", -] - [[package]] name = "cast" version = "0.3.0" @@ -462,12 +501,9 @@ checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5" [[package]] name = "cc" -version = "1.0.73" +version = "1.0.78" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2fff2a6927b3bb87f9595d67196a70493f627687a71d87a0d692242c33f58c11" -dependencies = [ - "jobserver", -] +checksum = "a20104e2335ce8a659d6dd92a51a767a0c062599c73b343fd152cb401e828c3d" [[package]] name = "cfg-if" @@ -477,15 +513,17 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "chrono" -version = "0.4.19" +version = "0.4.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "670ad68c9088c2a963aaa298cb369688cf3f9465ce5e2d4ca10e6e0098a1ce73" +checksum = "16b0a3d9ed01224b22057780a37bb8c5dbfe1be8ba48678e7bf57ec4b385411f" dependencies = [ - "libc", + "iana-time-zone", + "js-sys", "num-integer", "num-traits", "serde", - "time 0.1.44", + "time 0.1.45", + "wasm-bindgen", "winapi", ] @@ -506,9 +544,9 @@ dependencies = [ [[package]] name = "clap" -version = "3.2.10" +version = "3.2.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "69c5a7f9997be616e47f0577ee38c91decb33392c5be4866494f34cdf329a9aa" +checksum = "71655c45cb9845d3270c9d6df84ebe72b4dad3c2ba3f7023ad47c144e4e473a5" dependencies = [ "atty", "bitflags", @@ -518,14 +556,14 @@ dependencies = [ "once_cell", "strsim 0.10.0", "termcolor", - "textwrap 0.15.0", + "textwrap 0.16.0", ] [[package]] name = "clap_derive" -version = "3.2.7" +version = "3.2.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "759bf187376e1afa7b85b959e6a664a3e7a95203415dba952ad19139e798f902" +checksum = "ea0c8bce528c4be4da13ea6fead8965e95b6073585a2f05204bd8f4119f82a65" dependencies = [ "heck 0.4.0", "proc-macro-error", @@ -545,37 +583,45 @@ dependencies = [ [[package]] name = "clipboard-win" -version = "4.4.1" +version = "4.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2f3e1238132dc01f081e1cbb9dace14e5ef4c3a51ee244bd982275fb514605db" +checksum = "7191c27c2357d9b7ef96baac1773290d4ca63b24205b82a3fd8a0637afcf0362" dependencies = [ "error-code", "str-buf", "winapi", ] +[[package]] +name = "codespan-reporting" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3538270d33cc669650c4b093848450d380def10c331d38c768e34cac80576e6e" +dependencies = [ + "termcolor", + "unicode-width", +] + [[package]] name = "concurrent-queue" -version = "1.2.2" +version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "30ed07550be01594c6026cff2a1d7fe9c8f683caa798e12b68694ac9e88286a3" +checksum = "c278839b831783b70278b14df4d45e1beb1aad306c07bb796637de9a0e323e8e" dependencies = [ - "cache-padded", + "crossbeam-utils", ] [[package]] name = "console" -version = "0.15.0" +version = "0.15.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a28b32d32ca44b70c3e4acd7db1babf555fa026e385fb95f18028f88848b3c31" +checksum = "c3d79fbe8970a77e3e34151cc13d3b3e248aa0faaecb9f6091fa07ebefe5ad60" dependencies = [ "encode_unicode", + "lazy_static", "libc", - "once_cell", - "regex", - "terminal_size", "unicode-width", - "winapi", + "windows-sys", ] [[package]] @@ -602,9 +648,9 @@ checksum = "5827cebf4670468b8772dd191856768aedcb1b0278a04f989f7766351917b9dc" [[package]] name = "cpufeatures" -version = "0.2.2" +version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "59a6001667ab124aebae2a495118e11d30984c3a653e99d86d58971708cf5e4b" +checksum = "28d997bd5e24a5928dd43e46dc529867e207907fe0b239c3477d924f7f2ca320" dependencies = [ "libc", ] @@ -620,9 +666,9 @@ dependencies = [ [[package]] name = "crc-catalog" -version = "2.1.0" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2d0165d2900ae6778e36e80bbc4da3b5eefccee9ba939761f9c2882a5d9af3ff" +checksum = "9cace84e55f07e7301bae1c519df89cdad8cc3cd868413d3fdbdeca9ff3db484" [[package]] name = "criterion" @@ -631,7 +677,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b01d6de93b2b6c65e17c634a26653a29d107b3c98c607c765bf38d041531cd8f" dependencies = [ "atty", - "cast 0.3.0", + "cast", "clap 2.34.0", "criterion-plot", "csv", @@ -652,19 +698,19 @@ dependencies = [ [[package]] name = "criterion-plot" -version = "0.4.4" +version = "0.4.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d00996de9f2f7559f7f4dc286073197f83e92256a59ed395f9aac01fe717da57" +checksum = "2673cc8207403546f45f5fd319a974b1e6983ad1a3ee7e6041650013be041876" dependencies = [ - "cast 0.2.7", + "cast", "itertools", ] [[package]] name = "crossbeam-channel" -version = "0.5.5" +version = "0.5.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c02a4d71819009c192cf4872265391563fd6a84c81ff2c0f2a7026ca4c1d85c" +checksum = "c2dd04ddaf88237dc3b8d8f9a3c1004b506b54b3313403944054d23c0870c521" dependencies = [ "cfg-if", "crossbeam-utils", @@ -672,9 +718,9 @@ dependencies = [ [[package]] name = "crossbeam-deque" -version = "0.8.1" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6455c0ca19f0d2fbf751b908d5c55c1f5cbc65e03c4225427254b46890bdde1e" +checksum = "715e8152b692bba2d374b53d4875445368fdf21a94751410af607a5ac677d1fc" dependencies = [ "cfg-if", "crossbeam-epoch", @@ -683,23 +729,22 @@ dependencies = [ [[package]] name = "crossbeam-epoch" -version = "0.9.9" +version = "0.9.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07db9d94cbd326813772c968ccd25999e5f8ae22f4f8d1b11effa37ef6ce281d" +checksum = "01a9af1f4c2ef74bb8aa1f7e19706bc72d03598c8a570bb5de72243c7a9d9d5a" dependencies = [ "autocfg", "cfg-if", "crossbeam-utils", - "memoffset", - "once_cell", + "memoffset 0.7.1", "scopeguard", ] [[package]] name = "crossbeam-queue" -version = "0.3.5" +version = "0.3.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f25d8400f4a7a5778f0e4e52384a48cbd9b5c495d110786187fc750075277a2" +checksum = "d1cfb3ea8a53f37c40dea2c7bedcbd88bdfae54f5e2175d6ecaff1c988353add" dependencies = [ "cfg-if", "crossbeam-utils", @@ -707,12 +752,11 @@ dependencies = [ [[package]] name = "crossbeam-utils" -version = "0.8.10" +version = "0.8.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d82ee10ce34d7bc12c2122495e7593a9c41347ecdd64185af4ecf72cb1a7f83" +checksum = "4fb766fa798726286dbbb842f174001dab8abc7b627a1dd86e0b7222a95d929f" dependencies = [ "cfg-if", - "once_cell", ] [[package]] @@ -727,9 +771,9 @@ dependencies = [ [[package]] name = "crypto-common" -version = "0.1.5" +version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2ccfd8c0ee4cce11e45b3fd6f9d5e69e0cc62912aa6a0cb1bf4617b0eba5a12f" +checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" dependencies = [ "generic-array", "typenum", @@ -759,19 +803,63 @@ dependencies = [ [[package]] name = "ctor" -version = "0.1.22" +version = "0.1.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d2301688392eb071b0bf1a37be05c469d3cc4dbbd95df672fe28ab021e6a096" +dependencies = [ + "quote", + "syn", +] + +[[package]] +name = "cxx" +version = "1.0.87" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f877be4f7c9f246b183111634f75baa039715e3f46ce860677d3b19a69fb229c" +checksum = "b61a7545f753a88bcbe0a70de1fcc0221e10bfc752f576754fa91e663db1622e" dependencies = [ + "cc", + "cxxbridge-flags", + "cxxbridge-macro", + "link-cplusplus", +] + +[[package]] +name = "cxx-build" +version = "1.0.87" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f464457d494b5ed6905c63b0c4704842aba319084a0a3561cdc1359536b53200" +dependencies = [ + "cc", + "codespan-reporting", + "once_cell", + "proc-macro2", + "quote", + "scratch", + "syn", +] + +[[package]] +name = "cxxbridge-flags" +version = "1.0.87" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43c7119ce3a3701ed81aca8410b9acf6fc399d2629d057b87e2efa4e63a3aaea" + +[[package]] +name = "cxxbridge-macro" +version = "1.0.87" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "65e07508b90551e610910fa648a1878991d367064997a596135b86df30daf07e" +dependencies = [ + "proc-macro2", "quote", "syn", ] [[package]] name = "darling" -version = "0.14.1" +version = "0.14.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4529658bdda7fd6769b8614be250cdcfc3aeb0ee72fe66f9e41e5e5eb73eac02" +checksum = "b0dd3cd20dc6b5a876612a6e5accfe7f3dd883db6d07acfbf14c128f61550dfa" dependencies = [ "darling_core", "darling_macro", @@ -779,9 +867,9 @@ dependencies = [ [[package]] name = "darling_core" -version = "0.14.1" +version = "0.14.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "649c91bc01e8b1eac09fb91e8dbc7d517684ca6be8ebc75bb9cafc894f9fdb6f" +checksum = "a784d2ccaf7c98501746bf0be29b2022ba41fd62a2e622af997a03e9f972859f" dependencies = [ "fnv", "ident_case", @@ -793,9 +881,9 @@ dependencies = [ [[package]] name = "darling_macro" -version = "0.14.1" +version = "0.14.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ddfc69c5bfcbd2fc09a0f38451d2daf0e372e367986a83906d1b0dbc88134fb5" +checksum = "7618812407e9402654622dd402b0a89dff9ba93badd6540781526117b92aab7e" dependencies = [ "darling_core", "quote", @@ -821,9 +909,9 @@ checksum = "6184e33543162437515c2e2b48714794e37845ec9851711914eec9d308f6ebe8" [[package]] name = "digest" -version = "0.10.3" +version = "0.10.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2fb860ca6fafa5552fb6d0e816a69c8e49f0908bf524e30a90d97c85892d506" +checksum = "8168378f4e5023e7218c89c891c0fd8ecdb5e5e4f18cb78f38cf245dd021e76f" dependencies = [ "block-buffer", "crypto-common", @@ -873,12 +961,9 @@ dependencies = [ [[package]] name = "dotenvy" -version = "0.15.1" +version = "0.15.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e851a83c30366fd01d75b913588e95e74a1705c1ecc5d58b1f8e1a6d556525f" -dependencies = [ - "dirs", -] +checksum = "03d8c417d7a8cb362e0c37e5d815f5eb7c37f79ff93707329d5a194e42e54ca0" [[package]] name = "downcast" @@ -888,9 +973,9 @@ checksum = "1435fa1053d8b2fbbe9be7e97eca7f33d37b28409959813daefc1446a14247f1" [[package]] name = "either" -version = "1.7.0" +version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f107b87b6afc2a64fd13cac55fe06d6c8859f12d4b14cbcdd2c67d0976781be" +checksum = "90e5c1c8368803113bf0c9584fc495a58b86dc8a29edbf8fe877d21d9507e797" dependencies = [ "serde", ] @@ -918,9 +1003,9 @@ checksum = "c34f04666d835ff5d62e058c3995147c06f42fe86ff053337632bca83e42702d" [[package]] name = "env_logger" -version = "0.9.0" +version = "0.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b2cf0344971ee6c64c31be0d530793fba457d322dfec2810c453d0ef228f9c3" +checksum = "a12e6657c4c97ebab115a42dcee77225f7f482cdd841cf7088c657a42e9e00e7" dependencies = [ "atty", "humantime", @@ -962,24 +1047,24 @@ dependencies = [ [[package]] name = "event-listener" -version = "2.5.2" +version = "2.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77f3309417938f28bf8228fcff79a4a37103981e3e186d2ccd19c74b38f4eb71" +checksum = "0206175f82b8d6bf6652ff7d71a1e27fd2e4efde587fd368662814d6ec1d9ce0" [[package]] name = "fastrand" -version = "1.7.0" +version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3fcf0cee53519c866c09b5de1f6c56ff9d647101f81c1964fa632e148896cdf" +checksum = "a7a407cfaa3385c4ae6b23e84623d48c2798d06e3e6a1878f7f59f17b3f86499" dependencies = [ "instant", ] [[package]] name = "fd-lock" -version = "3.0.6" +version = "3.0.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e11dcc7e4d79a8c89b9ab4c6f5c30b1fc4a83c420792da3542fd31179ed5f517" +checksum = "28c0190ff0bd3b28bfdd4d0cf9f92faa12880fb0b8ae2054723dd6c76a4efd42" dependencies = [ "cfg-if", "rustix", @@ -998,9 +1083,9 @@ dependencies = [ [[package]] name = "filetime" -version = "0.2.17" +version = "0.2.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e94a7bbaa59354bc20dd75b67f23e2797b4490e9d6928203fb105c79e448c86c" +checksum = "4e884668cd0c7480504233e951174ddc3b382f7c2666e3b7310b5c4e7b0c37f9" dependencies = [ "cfg-if", "libc", @@ -1019,14 +1104,14 @@ dependencies = [ [[package]] name = "flume" -version = "0.10.13" +version = "0.10.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ceeb589a3157cac0ab8cc585feb749bd2cea5cb55a6ee802ad72d9fd38303da" +checksum = "1657b4441c3403d9f7b3409e47575237dac27b1b5726df654a6ecbf92f0f7577" dependencies = [ "futures-core", "futures-sink", "pin-project", - "spin 0.9.3", + "spin 0.9.4", ] [[package]] @@ -1052,19 +1137,18 @@ checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" [[package]] name = "form_urlencoded" -version = "1.0.1" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5fc25a87fa4fd2094bffb06925852034d90a17f0d1e05197d4956d3555752191" +checksum = "a9c384f161156f5260c24a097c56119f9be8c798586aecc13afbcbe7b7e26bf8" dependencies = [ - "matches", "percent-encoding", ] [[package]] name = "fragile" -version = "1.2.1" +version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85dcb89d2b10c5f6133de2efd8c11959ce9dbb46a2f7a4cab208c4eeda6ce1ab" +checksum = "6c2141d6d6c8512188a7891b4b01590a45f6dac67afb4f255c4124dbb86d4eaa" [[package]] name = "fuchsia-cprng" @@ -1074,9 +1158,9 @@ checksum = "a06f77d526c1a601b7c4cdd98f54b5eaabffc14d5f2f0296febdc7f357c6d3ba" [[package]] name = "futures" -version = "0.3.21" +version = "0.3.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f73fe65f54d1e12b726f517d3e2135ca3125a437b6d998caf1962961f7172d9e" +checksum = "38390104763dc37a5145a53c29c63c1290b5d316d6086ec32c293f6736051bb0" dependencies = [ "futures-channel", "futures-core", @@ -1089,9 +1173,9 @@ dependencies = [ [[package]] name = "futures-channel" -version = "0.3.21" +version = "0.3.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3083ce4b914124575708913bca19bfe887522d6e2e6d0952943f5eac4a74010" +checksum = "52ba265a92256105f45b719605a571ffe2d1f0fea3807304b522c1d778f79eed" dependencies = [ "futures-core", "futures-sink", @@ -1099,15 +1183,15 @@ dependencies = [ [[package]] name = "futures-core" -version = "0.3.21" +version = "0.3.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c09fd04b7e4073ac7156a9539b57a484a8ea920f79c7c675d05d289ab6110d3" +checksum = "04909a7a7e4633ae6c4a9ab280aeb86da1236243a77b694a49eacd659a4bd3ac" [[package]] name = "futures-executor" -version = "0.3.21" +version = "0.3.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9420b90cfa29e327d0429f19be13e7ddb68fa1cccb09d65e5706b8c7a749b8a6" +checksum = "7acc85df6714c176ab5edf386123fafe217be88c0840ec11f199441134a074e2" dependencies = [ "futures-core", "futures-task", @@ -1116,9 +1200,9 @@ dependencies = [ [[package]] name = "futures-intrusive" -version = "0.4.0" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62007592ac46aa7c2b6416f7deb9a8a8f63a01e0f1d6e1787d5630170db2b63e" +checksum = "a604f7a68fbf8103337523b1fadc8ade7361ee3f112f7c680ad179651616aed5" dependencies = [ "futures-core", "lock_api", @@ -1127,9 +1211,9 @@ dependencies = [ [[package]] name = "futures-io" -version = "0.3.21" +version = "0.3.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc4045962a5a5e935ee2fdedaa4e08284547402885ab326734432bed5d12966b" +checksum = "00f5fb52a06bdcadeb54e8d3671f8888a39697dcb0b81b23b55174030427f4eb" [[package]] name = "futures-lite" @@ -1148,43 +1232,32 @@ dependencies = [ [[package]] name = "futures-macro" -version = "0.3.21" +version = "0.3.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33c1e13800337f4d4d7a316bf45a567dbcb6ffe087f16424852d97e97a91f512" +checksum = "bdfb8ce053d86b91919aad980c220b1fb8401a9394410e1c289ed7e66b61835d" dependencies = [ "proc-macro2", "quote", "syn", ] -[[package]] -name = "futures-rustls" -version = "0.22.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e01fe9932a224b72b45336d96040aa86386d674a31d0af27d800ea7bc8ca97fe" -dependencies = [ - "futures-io", - "rustls", - "webpki", -] - [[package]] name = "futures-sink" -version = "0.3.21" +version = "0.3.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "21163e139fa306126e6eedaf49ecdb4588f939600f0b1e770f4205ee4b7fa868" +checksum = "39c15cf1a4aa79df40f1bb462fb39676d0ad9e366c2a33b590d7c66f4f81fcf9" [[package]] name = "futures-task" -version = "0.3.21" +version = "0.3.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57c66a976bf5909d801bbef33416c41372779507e7a6b3a5e25e4749c58f776a" +checksum = "2ffb393ac5d9a6eaa9d3fdf37ae2776656b706e200c8e16b1bdb227f5198e6ea" [[package]] name = "futures-util" -version = "0.3.21" +version = "0.3.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d8b7abd5d659d9b90c8cba917f6ec750a74e2dc23902ef9cd4cc8c8b22e6036a" +checksum = "197676987abd2f9cadff84926f410af1c183608d36641465df73ae8211dc65d6" dependencies = [ "futures-channel", "futures-core", @@ -1200,9 +1273,9 @@ dependencies = [ [[package]] name = "generic-array" -version = "0.14.5" +version = "0.14.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd48d33ec7f05fbfa152300fdad764757cbded343c1aa1cff2fbaf4134851803" +checksum = "bff49e947297f3312447abdca79f45f4738097cc82b06e72054d2223f601f1b9" dependencies = [ "typenum", "version_check", @@ -1210,39 +1283,26 @@ dependencies = [ [[package]] name = "getrandom" -version = "0.2.7" +version = "0.2.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4eb1a864a501629691edf6c15a593b7a51eebaa1e8468e9ddc623de7c9b58ec6" +checksum = "c05aeb6a22b8f62540c194aac980f2115af067bfe15a0734d7277a768d396b31" dependencies = [ "cfg-if", "libc", "wasi 0.11.0+wasi-snapshot-preview1", ] -[[package]] -name = "git2" -version = "0.14.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d0155506aab710a86160ddb504a480d2964d7ab5b9e62419be69e0032bc5931c" -dependencies = [ - "bitflags", - "libc", - "libgit2-sys", - "log", - "url", -] - [[package]] name = "glob" -version = "0.3.0" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b919933a397b79c37e33b77bb2aa3dc8eb6e165ad809e58ff75bc7db2e34574" +checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b" [[package]] name = "gloo-timers" -version = "0.2.4" +version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5fb7d06c1c8cc2a29bee7ec961009a0b2caa0793ee4900c2ffb348734ba1c8f9" +checksum = "9b995a66bb87bebce9a0f4a95aed01daca4872c050bfcb21653361c03bc35e5c" dependencies = [ "futures-channel", "futures-core", @@ -1258,20 +1318,29 @@ checksum = "eabb4a44450da02c90444cf74558da904edde8fb4e9035a9a6a4e15445af0bd7" [[package]] name = "hashbrown" -version = "0.12.2" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab5ef0d4909ef3724cc8cce6ccc8572c5c817592e9285f5464f8e86f8bd3726e" +dependencies = [ + "ahash", +] + +[[package]] +name = "hashbrown" +version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "607c8a29735385251a339424dd462993c0fed8fa09d378f259377df08c126022" +checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" dependencies = [ "ahash", ] [[package]] name = "hashlink" -version = "0.8.0" +version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d452c155cb93fecdfb02a73dd57b5d8e442c2063bd7aac72f1bc5e4263a43086" +checksum = "69fe1fcf8b4278d860ad0548329f892a3631fb63f82574df68275f34cdbe0ffa" dependencies = [ - "hashbrown", + "hashbrown 0.12.3", ] [[package]] @@ -1301,6 +1370,15 @@ dependencies = [ "libc", ] +[[package]] +name = "hermit-abi" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee512640fe35acbfb4bb779db6f0d80704c2cacfa2e39b601ef3e3f47d1ae4c7" +dependencies = [ + "libc", +] + [[package]] name = "hex" version = "0.4.3" @@ -1333,7 +1411,7 @@ checksum = "75f43d41e26995c17e71ee126451dd3941010b0514a81a9d11f3b341debc2399" dependencies = [ "bytes", "fnv", - "itoa 1.0.2", + "itoa 1.0.5", ] [[package]] @@ -1355,9 +1433,9 @@ checksum = "0bfe8eed0a9285ef776bb792479ea3834e8b94e13d615c2f66d03dd50a435a29" [[package]] name = "httparse" -version = "1.7.1" +version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "496ce29bb5a52785b44e0f7ca2847ae0bb839c9bd28f69acac9b99d461c0c04c" +checksum = "d897f394bad6a705d5f4104762e116a75639e470d80901eed05a860a95cb1904" [[package]] name = "httpdate" @@ -1373,9 +1451,9 @@ checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" [[package]] name = "hyper" -version = "0.14.20" +version = "0.14.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "02c929dc5c39e335a03c405292728118860721b10190d98c2a0f0efd5baafbac" +checksum = "034711faac9d2166cb1baf1a2fb0b60b1f277f8492fd72176c17f3515e1abd3c" dependencies = [ "bytes", "futures-channel", @@ -1385,7 +1463,7 @@ dependencies = [ "http-body", "httparse", "httpdate", - "itoa 1.0.2", + "itoa 1.0.5", "pin-project-lite", "socket2", "tokio", @@ -1394,6 +1472,30 @@ dependencies = [ "want", ] +[[package]] +name = "iana-time-zone" +version = "0.1.53" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "64c122667b287044802d6ce17ee2ddf13207ed924c712de9a66a5814d5b64765" +dependencies = [ + "android_system_properties", + "core-foundation-sys", + "iana-time-zone-haiku", + "js-sys", + "wasm-bindgen", + "winapi", +] + +[[package]] +name = "iana-time-zone-haiku" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0703ae284fc167426161c2e3f1da3ea71d94b21bedbcc9494e92b28e334e3dca" +dependencies = [ + "cxx", + "cxx-build", +] + [[package]] name = "ident_case" version = "1.0.1" @@ -1411,6 +1513,16 @@ dependencies = [ "unicode-normalization", ] +[[package]] +name = "idna" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e14ddfc70884202db2244c223200c204c2bda1bc6e0998d11b5e024d657209e6" +dependencies = [ + "unicode-bidi", + "unicode-normalization", +] + [[package]] name = "if_chain" version = "1.0.2" @@ -1419,12 +1531,12 @@ checksum = "cb56e1aa765b4b4f3aadfab769793b7087bb03a4ea4920644a6d238e2df5b9ed" [[package]] name = "indexmap" -version = "1.9.1" +version = "1.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "10a35a97730320ffe8e2d410b5d3b69279b98d2c14bdb8b70ea89ecf7888d41e" +checksum = "1885e79c1fc4b10f0e172c475f458b7f7b93061064d98c3293e98c5ba0c8b399" dependencies = [ "autocfg", - "hashbrown", + "hashbrown 0.12.3", "serde", ] @@ -1439,21 +1551,28 @@ dependencies = [ [[package]] name = "io-lifetimes" -version = "0.7.2" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "24c3f4eff5495aee4c0399d7b6a0dc2b6e81be84242ffbfcf253ebacccc1d0cb" +checksum = "e7d6c6f8c91b4b9ed43484ad1a938e393caf35960fce7f82a040497207bd8e9e" +dependencies = [ + "libc", + "windows-sys", +] [[package]] name = "ipnetwork" -version = "0.19.0" +version = "0.20.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f84f1612606f3753f205a4e9a2efd6fe5b4c573a6269b2cc6c3003d44a0d127" +checksum = "bf466541e9d546596ee94f9f69590f89473455f88372423e0008fc1a7daf100e" +dependencies = [ + "serde", +] [[package]] name = "itertools" -version = "0.10.3" +version = "0.10.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a9a9d19fa1e79b6215ff29b9d6880b706147f16e9b1dbb1e4e5947b5b02bc5e3" +checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473" dependencies = [ "either", ] @@ -1466,24 +1585,15 @@ checksum = "b71991ff56294aa922b450139ee08b3bfc70982c6b2c7562771375cf73542dd4" [[package]] name = "itoa" -version = "1.0.2" +version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "112c678d4050afce233f4f2852bb2eb519230b3cf12f33585275537d7e41578d" - -[[package]] -name = "jobserver" -version = "0.1.24" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af25a77299a7f711a01975c35a6a424eb6862092cc2d6c72c4ed6cbc56dfc1fa" -dependencies = [ - "libc", -] +checksum = "fad582f4b9e86b6caa621cabeb0963332d92eea04729ab12892c2533951e6440" [[package]] name = "js-sys" -version = "0.3.58" +version = "0.3.60" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3fac17f7123a73ca62df411b1bf727ccc805daa070338fda671c86dac1bdc27" +checksum = "49409df3e3bf0856b916e2ceaca09ee28e6871cf7d9ce97a692cacfdb2a25a47" dependencies = [ "wasm-bindgen", ] @@ -1522,33 +1632,21 @@ dependencies = [ [[package]] name = "libc" -version = "0.2.126" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "349d5a591cd28b49e1d1037471617a32ddcda5731b99419008085f72d5a53836" - -[[package]] -name = "libgit2-sys" -version = "0.13.4+1.4.2" +version = "0.2.139" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d0fa6563431ede25f5cc7f6d803c6afbc1c5d3ad3d4925d12c882bf2b526f5d1" -dependencies = [ - "cc", - "libc", - "libz-sys", - "pkg-config", -] +checksum = "201de327520df007757c1f0adce6e827fe8562fbc28bfd9c15571c66ca1f5f79" [[package]] name = "libm" -version = "0.2.2" +version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33a33a362ce288760ec6a508b94caaec573ae7d3bbbd91b87aa0bad4456839db" +checksum = "348108ab3fba42ec82ff6e9564fc4ca0247bdccdc68dd8af9764bbc79c3c8ffb" [[package]] name = "libsqlite3-sys" -version = "0.25.1" +version = "0.25.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f0455f2c1bc9a7caa792907026e469c1d91761fb0ea37cbb16427c77280cf35" +checksum = "29f835d03d717946d28b1d1ed632eb6f0e24a299388ee623d0c23118d3e8a7fa" dependencies = [ "cc", "pkg-config", @@ -1556,28 +1654,25 @@ dependencies = [ ] [[package]] -name = "libz-sys" -version = "1.1.8" +name = "link-cplusplus" +version = "1.0.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9702761c3935f8cc2f101793272e202c72b99da8f4224a19ddcf1279a6450bbf" +checksum = "ecd207c9c713c34f95a097a5b029ac2ce6010530c7b49d7fea24d977dede04f5" dependencies = [ "cc", - "libc", - "pkg-config", - "vcpkg", ] [[package]] name = "linux-raw-sys" -version = "0.0.46" +version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d4d2456c373231a208ad294c33dc5bff30051eafd954cd4caae83a712b12854d" +checksum = "f051f77a7c8e6957c0696eac88f26b0117e54f52d3fc682ab19397a8812846a4" [[package]] name = "lock_api" -version = "0.4.7" +version = "0.4.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "327fa5b6a6940e4699ec49a9beae1ea4845c6bab9314e4f84ac68742139d8c53" +checksum = "435011366fe56583b16cf956f9df0095b405b82d76425bc8981c0e22e60ec4df" dependencies = [ "autocfg", "scopeguard", @@ -1595,9 +1690,9 @@ dependencies = [ [[package]] name = "mac_address" -version = "1.1.3" +version = "1.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df1d1bc1084549d60725ccc53a2bfa07f67fe4689fda07b05a36531f2988104a" +checksum = "b238e3235c8382b7653c6408ed1b08dd379bdb9fdf990fb0bbae3db2cc0ae963" dependencies = [ "nix", "winapi", @@ -1605,9 +1700,9 @@ dependencies = [ [[package]] name = "matches" -version = "0.1.9" +version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a3e378b66a060d48947b590737b30a1be76706c8dd7b8ba0f2fe3989c68a853f" +checksum = "2532096657941c2fea9c289d370a250971c689d4f143798ff67113ec042024a5" [[package]] name = "matchit" @@ -1617,9 +1712,9 @@ checksum = "73cbba799671b762df5a175adf59ce145165747bb891505c43d09aefbbf38beb" [[package]] name = "md-5" -version = "0.10.1" +version = "0.10.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "658646b21e0b72f7866c7038ab086d3d5e1cd6271f060fd37defb241949d0582" +checksum = "6365506850d44bff6e2fbcb5176cf63650e48bd45ef2fe2665ae1570e0f4b9ca" dependencies = [ "digest", ] @@ -1639,6 +1734,15 @@ dependencies = [ "autocfg", ] +[[package]] +name = "memoffset" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5de893c32cde5f383baa4c04c5d6dbdd735cfd4a794b0debdb2bb1b421da5ff4" +dependencies = [ + "autocfg", +] + [[package]] name = "mime" version = "0.3.16" @@ -1653,9 +1757,9 @@ checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" [[package]] name = "mio" -version = "0.8.4" +version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57ee1c23c7c63b0c9250c339ffdc69255f110b298b901b9f6c82547b7b87caaf" +checksum = "e5d732bc30207a6423068df043e3d02e0735b155ad7ce1a6f76fe2baa5b158de" dependencies = [ "libc", "log", @@ -1665,9 +1769,9 @@ dependencies = [ [[package]] name = "mockall" -version = "0.11.1" +version = "0.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5641e476bbaf592a3939a7485fa079f427b4db21407d5ebfd5bba4e07a1f6f4c" +checksum = "50e4a1c770583dac7ab5e2f6c139153b783a53a1bbee9729613f193e59828326" dependencies = [ "cfg-if", "downcast", @@ -1680,9 +1784,9 @@ dependencies = [ [[package]] name = "mockall_derive" -version = "0.11.1" +version = "0.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "262d56735932ee0240d515656e5a7667af3af2a5b0af4da558c4cff2b2aeb0c7" +checksum = "832663583d5fa284ca8810bf7015e46c9fff9622d3cf34bd1eea5003fec06dd0" dependencies = [ "cfg-if", "proc-macro2", @@ -1692,9 +1796,9 @@ dependencies = [ [[package]] name = "native-tls" -version = "0.2.10" +version = "0.2.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd7e2f3618557f980e0b17e8856252eee3c97fa12c54dff0ca290fb6266ca4a9" +checksum = "07226173c32f2926027b63cce4bcd8076c3552846cbe7925f3aaffeac0a3b92e" dependencies = [ "lazy_static", "libc", @@ -1719,22 +1823,22 @@ dependencies = [ [[package]] name = "nix" -version = "0.23.1" +version = "0.23.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f866317acbd3a240710c63f065ffb1e4fd466259045ccb504130b7f668f35c6" +checksum = "8f3790c00a0150112de0f4cd161e3d7fc4b2d8a5542ffc35f099a2562aecb35c" dependencies = [ "bitflags", "cc", "cfg-if", "libc", - "memoffset", + "memoffset 0.6.5", ] [[package]] name = "nom" -version = "7.1.1" +version = "7.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8903e5a29a317527874d0402f867152a3d21c908bb0b933e416c65e301d4c36" +checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" dependencies = [ "memchr", "minimal-lexical", @@ -1759,9 +1863,9 @@ dependencies = [ [[package]] name = "num-bigint-dig" -version = "0.8.1" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "566d173b2f9406afbc5510a90925d5a2cd80cae4605631f1212303df265de011" +checksum = "2399c9463abc5f909349d8aa9ba080e0b88b3ce2885389b60b993f39b1a56905" dependencies = [ "byteorder", "lazy_static", @@ -1807,28 +1911,19 @@ dependencies = [ [[package]] name = "num_cpus" -version = "1.13.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "19e64526ebdee182341572e50e9ad03965aa510cd94427a4549448f285e957a1" -dependencies = [ - "hermit-abi", - "libc", -] - -[[package]] -name = "num_threads" -version = "0.1.6" +version = "1.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2819ce041d2ee131036f4fc9d6ae7ae125a3a40e97ba64d04fe799ad9dabbb44" +checksum = "0fac9e2da13b5eb447a6ce3d392f23a29d8694bff781bf03a16cd9ac8697593b" dependencies = [ + "hermit-abi 0.2.6", "libc", ] [[package]] name = "once_cell" -version = "1.13.0" +version = "1.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "18a6dbe30758c9f83eb00cbea4ac95966305f5a7772f3f42ebfc7fc7eddbd8e1" +checksum = "6f61fba1741ea2b3d6a1e3178721804bb716a68a6aeba1149b5d52e3d464ea66" [[package]] name = "oorandom" @@ -1838,9 +1933,9 @@ checksum = "0ab1bc2a289d34bd04a330323ac98a1b4bc82c9d9fcb1e66b63caa84da26b575" [[package]] name = "openssl" -version = "0.10.41" +version = "0.10.45" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "618febf65336490dfcf20b73f885f5651a0c89c64c2d4a8c3662585a70bf5bd0" +checksum = "b102428fd03bc5edf97f62620f7298614c45cedf287c271e7ed450bbaf83f2e1" dependencies = [ "bitflags", "cfg-if", @@ -1870,18 +1965,18 @@ checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" [[package]] name = "openssl-src" -version = "111.22.0+1.1.1q" +version = "111.24.0+1.1.1s" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f31f0d509d1c1ae9cada2f9539ff8f37933831fd5098879e482aa687d659853" +checksum = "3498f259dab01178c6228c6b00dcef0ed2a2d5e20d648c017861227773ea4abd" dependencies = [ "cc", ] [[package]] name = "openssl-sys" -version = "0.9.75" +version = "0.9.80" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5f9bd0c2710541a3cda73d6f9ac4f1b240de4ae261065d309dbe73d9dceb42f" +checksum = "23bbbf7854cd45b83958ebe919f0e8e516793727652e27fda10a8384cfc790b7" dependencies = [ "autocfg", "cc", @@ -1893,9 +1988,9 @@ dependencies = [ [[package]] name = "os_str_bytes" -version = "6.1.0" +version = "6.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "21326818e99cfe6ce1e524c2a805c189a99b5ae555a35d19f9a284b427d86afa" +checksum = "9b7820b9daea5457c9f21c69448905d723fbd21136ccf521748f23fd49e723ee" [[package]] name = "parking" @@ -1911,7 +2006,7 @@ checksum = "7d17b78036a60663b797adeaee46f5c9dfebb86948d1255007a1d6be0271ff99" dependencies = [ "instant", "lock_api", - "parking_lot_core 0.8.5", + "parking_lot_core 0.8.6", ] [[package]] @@ -1921,14 +2016,14 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f" dependencies = [ "lock_api", - "parking_lot_core 0.9.3", + "parking_lot_core 0.9.6", ] [[package]] name = "parking_lot_core" -version = "0.8.5" +version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d76e8e1493bcac0d2766c42737f34458f1c8c50c0d23bcb24ea953affb273216" +checksum = "60a2cfe6f0ad2bfc16aefa463b497d5c7a5ecd44a23efa72aa342d90177356dc" dependencies = [ "cfg-if", "instant", @@ -1940,9 +2035,9 @@ dependencies = [ [[package]] name = "parking_lot_core" -version = "0.9.3" +version = "0.9.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09a279cbf25cb0757810394fbc1e359949b59e348145c643a939a525692e6929" +checksum = "ba1ef8814b5c993410bb3adfad7a5ed269563e4a2f90c41f5d85be7fb47133bf" dependencies = [ "cfg-if", "libc", @@ -1958,15 +2053,15 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7676374caaee8a325c9e7a2ae557f216c5563a171d6997b0ef8a65af35147700" dependencies = [ "base64ct", - "rand_core 0.6.3", + "rand_core 0.6.4", "subtle", ] [[package]] name = "paste" -version = "1.0.7" +version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c520e05135d6e763148b6426a837e239041653ba7becd2e538c076c738025fc" +checksum = "d01a5bd0424d00070b0098dd17ebca6f961a959dead1dbcbbbc1d1cd8d3deeba" [[package]] name = "pem-rfc7468" @@ -1979,24 +2074,24 @@ dependencies = [ [[package]] name = "percent-encoding" -version = "2.1.0" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d4fd5641d01c8f18a23da7b6fe29298ff4b55afcccdf78973b24cf3175fee32e" +checksum = "478c572c3d73181ff3c2539045f6eb99e5491218eae919370993b890cdbdd98e" [[package]] name = "pin-project" -version = "1.0.11" +version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78203e83c48cffbe01e4a2d35d566ca4de445d79a85372fc64e378bfc812a260" +checksum = "ad29a609b6bcd67fee905812e544992d216af9d755757c05ed2d0e15a74c6ecc" dependencies = [ "pin-project-internal", ] [[package]] name = "pin-project-internal" -version = "1.0.11" +version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "710faf75e1b33345361201d36d04e98ac1ed8909151a017ed384700836104c74" +checksum = "069bdb1e05adc7a8990dce9cc75370895fbe4e3d58b9b73bf1aee56359344a55" dependencies = [ "proc-macro2", "quote", @@ -2039,15 +2134,15 @@ dependencies = [ [[package]] name = "pkg-config" -version = "0.3.25" +version = "0.3.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1df8c4ec4b0627e53bdf214615ad287367e482558cf84b109250b37464dc03ae" +checksum = "6ac9a59f73473f1b8d852421e59e64809f025994837ef743615c6d0c5b305160" [[package]] name = "plotters" -version = "0.3.2" +version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9428003b84df1496fb9d6eeee9c5f8145cb41ca375eb0dad204328888832811f" +checksum = "2538b639e642295546c50fcd545198c9d64ee2a38620a628724a3b266d5fbf97" dependencies = [ "num-traits", "plotters-backend", @@ -2064,37 +2159,38 @@ checksum = "193228616381fecdc1224c62e96946dfbc73ff4384fba576e052ff8c1bea8142" [[package]] name = "plotters-svg" -version = "0.3.2" +version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e0918736323d1baff32ee0eade54984f6f201ad7e97d5cfb5d6ab4a358529615" +checksum = "f9a81d2759aae1dae668f783c308bc5c8ebd191ff4184aaa1b37f65a6ae5a56f" dependencies = [ "plotters-backend", ] [[package]] name = "polling" -version = "2.2.0" +version = "2.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "685404d509889fade3e86fe3a5803bca2ec09b0c0778d5ada6ec8bf7a8de5259" +checksum = "22122d5ec4f9fe1b3916419b76be1e80bcb93f618d071d2edf841b137b2a2bd6" dependencies = [ + "autocfg", "cfg-if", "libc", "log", "wepoll-ffi", - "winapi", + "windows-sys", ] [[package]] name = "ppv-lite86" -version = "0.2.16" +version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eb9f9e6e233e5c4a35559a617bf40a4ec447db2e84c20b55a6f83167b7e57872" +checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" [[package]] name = "predicates" -version = "2.1.1" +version = "2.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a5aab5be6e4732b473071984b3164dbbfb7a3674d30ea5ff44410b6bcd960c3c" +checksum = "59230a63c37f3e18569bdb90e4a89cbf5bf8b06fea0b84e65ea10cc4df47addd" dependencies = [ "difflib", "float-cmp", @@ -2106,20 +2202,29 @@ dependencies = [ [[package]] name = "predicates-core" -version = "1.0.3" +version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da1c2388b1513e1b605fcec39a95e0a9e8ef088f71443ef37099fa9ae6673fcb" +checksum = "72f883590242d3c6fc5bf50299011695fa6590c2c70eac95ee1bdb9a733ad1a2" [[package]] name = "predicates-tree" -version = "1.0.5" +version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4d86de6de25020a36c6d3643a86d9a6a9f552107c0559c60ea03551b5e16c032" +checksum = "54ff541861505aabf6ea722d2131ee980b8276e10a1297b94e896dd8b621850d" dependencies = [ "predicates-core", "termtree", ] +[[package]] +name = "proc-macro-crate" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d6ea3c4595b96363c13943497db34af4460fb474a95c43f4446ad341b8c9785" +dependencies = [ + "toml", +] + [[package]] name = "proc-macro-error" version = "1.0.4" @@ -2146,9 +2251,9 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.40" +version = "1.0.50" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd96a1e8ed2596c337f8eae5f24924ec83f5ad5ab21ea8e455d3566c69fbcaf7" +checksum = "6ef7d57beacfaf2d8aee5937dab7b7f28de3cb8b1828479bb5de2a7106f2bae2" dependencies = [ "unicode-ident", ] @@ -2162,11 +2267,31 @@ dependencies = [ "rustyline", ] +[[package]] +name = "ptr_meta" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0738ccf7ea06b608c10564b31debd4f5bc5e197fc8bfe088f68ae5ce81e7a4f1" +dependencies = [ + "ptr_meta_derive", +] + +[[package]] +name = "ptr_meta_derive" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "16b845dbfca988fa33db069c0e230574d15a3088f147a87b64c7589eb662c9ac" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "quote" -version = "1.0.20" +version = "1.0.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3bcdf212e9776fbcb2d23ab029360416bb1706b1aea2d1a5ba002727cbcab804" +checksum = "8856d8364d252a14d474036ea1358d63c9e6965c8e5c1885c18f73d70bff9c7b" dependencies = [ "proc-macro2", ] @@ -2202,7 +2327,7 @@ checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" dependencies = [ "libc", "rand_chacha", - "rand_core 0.6.3", + "rand_core 0.6.4", ] [[package]] @@ -2212,7 +2337,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" dependencies = [ "ppv-lite86", - "rand_core 0.6.3", + "rand_core 0.6.4", ] [[package]] @@ -2232,9 +2357,9 @@ checksum = "9c33a3c44ca05fa6f1807d8e6743f3824e8509beca625669633be0acbdf509dc" [[package]] name = "rand_core" -version = "0.6.3" +version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d34f1408f55294453790c48b2f1ebbb1c5b4b7563eb1f418bcfcfdbb06ebb4e7" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" dependencies = [ "getrandom", ] @@ -2245,26 +2370,24 @@ version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6f97cdb2a36ed4183de61b2f824cc45c9f1037f28afe0a322e9fff4c108b5aaa" dependencies = [ - "rand_core 0.6.3", + "rand_core 0.6.4", ] [[package]] name = "rayon" -version = "1.5.3" +version = "1.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd99e5772ead8baa5215278c9b15bf92087709e9c1b2d1f97cdb5a183c933a7d" +checksum = "6db3a213adf02b3bcfd2d3846bb41cb22857d131789e01df434fb7e7bc0759b7" dependencies = [ - "autocfg", - "crossbeam-deque", "either", "rayon-core", ] [[package]] name = "rayon-core" -version = "1.9.3" +version = "1.10.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "258bcdb5ac6dad48491bb2992db6b7cf74878b0384908af124823d118c99683f" +checksum = "356a0625f1954f730c0201cdab48611198dc6ce21f4acff55089b5a78e6e835b" dependencies = [ "crossbeam-channel", "crossbeam-deque", @@ -2283,9 +2406,9 @@ dependencies = [ [[package]] name = "redox_syscall" -version = "0.2.13" +version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62f25bc4c7e55e0b0b7a1d43fb893f4fa1361d0abe38b9ce4f323c2adfe6ef42" +checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a" dependencies = [ "bitflags", ] @@ -2303,9 +2426,9 @@ dependencies = [ [[package]] name = "regex" -version = "1.6.0" +version = "1.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c4eb3267174b8c6c2f654116623910a0fef09c4753f8dd83db29c48a0df988b" +checksum = "48aaa5748ba571fb95cd2c85c09f629215d3a6ece942baa100950af03a34f733" dependencies = [ "aho-corasick", "memchr", @@ -2320,9 +2443,9 @@ checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132" [[package]] name = "regex-syntax" -version = "0.6.27" +version = "0.6.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a3f87b73ce11b1619a3c6332f45341e0047173771e8b8b73f87bfeefb7b56244" +checksum = "456c603be3e8d448b072f410900c09faf164fbce2d480456f50eea6e25f9c848" [[package]] name = "remove_dir_all" @@ -2346,6 +2469,15 @@ dependencies = [ "winapi", ] +[[package]] +name = "rend" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "79af64b4b6362ffba04eef3a4e10829718a4896dac19daa741851c86781edf95" +dependencies = [ + "bytecheck", +] + [[package]] name = "ring" version = "0.16.20" @@ -2361,6 +2493,31 @@ dependencies = [ "winapi", ] +[[package]] +name = "rkyv" +version = "0.7.39" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cec2b3485b07d96ddfd3134767b8a447b45ea4eb91448d0a35180ec0ffd5ed15" +dependencies = [ + "bytecheck", + "hashbrown 0.12.3", + "ptr_meta", + "rend", + "rkyv_derive", + "seahash", +] + +[[package]] +name = "rkyv_derive" +version = "0.7.39" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6eaedadc88b53e36dd32d940ed21ae4d850d5916f2581526921f553a72ac34c4" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "rsa" version = "0.6.1" @@ -2375,7 +2532,7 @@ dependencies = [ "num-traits", "pkcs1", "pkcs8", - "rand_core 0.6.3", + "rand_core 0.6.4", "smallvec", "subtle", "zeroize", @@ -2383,29 +2540,27 @@ dependencies = [ [[package]] name = "rust_decimal" -version = "1.25.0" +version = "1.28.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34a3bb58e85333f1ab191bf979104b586ebd77475bc6681882825f4532dfe87c" +checksum = "7fe32e8c89834541077a5c5bbe5691aa69324361e27e6aeb3552a737db4a70c8" dependencies = [ "arrayvec", + "borsh", + "bytecheck", + "byteorder", + "bytes", "num-traits", + "rand 0.8.5", + "rkyv", "serde", -] - -[[package]] -name = "rustc_version" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366" -dependencies = [ - "semver", + "serde_json", ] [[package]] name = "rustix" -version = "0.35.7" +version = "0.36.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d51cc38aa10f6bbb377ed28197aa052aa4e2b762c22be9d3153d01822587e787" +checksum = "d4fdebc4b395b7fbb9ab11e462e20ed9051e7b16e42d24042c776eca0ac81b03" dependencies = [ "bitflags", "errno", @@ -2417,9 +2572,9 @@ dependencies = [ [[package]] name = "rustls" -version = "0.20.6" +version = "0.20.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5aab8ee6c7097ed6057f43c187a62418d0c05a4bd5f18b3571db50ee0f9ce033" +checksum = "fff78fc74d175294f4e83b28343315ffcfb114b156f0185e9741cb5570f50e2f" dependencies = [ "log", "ring", @@ -2429,11 +2584,11 @@ dependencies = [ [[package]] name = "rustls-pemfile" -version = "1.0.0" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e7522c9de787ff061458fe9a829dc790a3f5b22dc571694fc5883f448b94d9a9" +checksum = "d194b56d58803a43635bdc398cd17e383d6f71f9182b9a192c127ca42494a59b" dependencies = [ - "base64", + "base64 0.21.0", ] [[package]] @@ -2462,9 +2617,9 @@ dependencies = [ [[package]] name = "ryu" -version = "1.0.10" +version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f3f6f92acf49d1b98f7a81226834412ada05458b7364277387724a237f062695" +checksum = "7b4b9743ed687d4b4bcedf9ff5eaa7398495ae14e61cba0a295704edbc7decde" [[package]] name = "same-file" @@ -2477,11 +2632,10 @@ dependencies = [ [[package]] name = "schannel" -version = "0.1.20" +version = "0.1.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "88d6731146462ea25d9244b2ed5fd1d716d25c52e4d54aa4fb0f3c4e9854dbe2" +checksum = "713cfb06c7059f3588fb8044c0fad1d09e3c01d225e25b9220dbfdcf16dbb1b3" dependencies = [ - "lazy_static", "windows-sys", ] @@ -2491,6 +2645,12 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" +[[package]] +name = "scratch" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ddccb15bcce173023b3fedd9436f882a0739b8dfb45e4f6b6002bee5929f61b2" + [[package]] name = "sct" version = "0.7.0" @@ -2501,11 +2661,17 @@ dependencies = [ "untrusted", ] +[[package]] +name = "seahash" +version = "4.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1c107b6f4780854c8b126e228ea8869f4d7b71260f962fefb57b996b8959ba6b" + [[package]] name = "security-framework" -version = "2.6.1" +version = "2.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2dc14f172faf8a0194a3aded622712b0de276821addc574fa54fc0a1167e10dc" +checksum = "645926f31b250a2dca3c232496c2d898d91036e45ca0e97e0e2390c54e11be36" dependencies = [ "bitflags", "core-foundation", @@ -2516,9 +2682,9 @@ dependencies = [ [[package]] name = "security-framework-sys" -version = "2.6.1" +version = "2.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0160a13a177a45bfb43ce71c01580998474f556ad854dcbca936dd2841a5c556" +checksum = "31c9bb296072e961fcbd8853511dd39c2d8be2deb1e17c6860b1d30732b323b4" dependencies = [ "core-foundation-sys", "libc", @@ -2526,18 +2692,18 @@ dependencies = [ [[package]] name = "semver" -version = "1.0.12" +version = "1.0.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2333e6df6d6598f2b1974829f853c2b4c5f4a6e503c10af918081aa6f8564e1" +checksum = "58bc9567378fc7690d6b2addae4e60ac2eeea07becb2c64b9f218b53865cba2a" dependencies = [ "serde", ] [[package]] name = "serde" -version = "1.0.140" +version = "1.0.152" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc855a42c7967b7c369eb5860f7164ef1f6f81c20c7cc1141f2a604e18723b03" +checksum = "bb7d1f0d3021d347a83e556fc4683dea2ea09d87bccdf88ff5c12545d89d5efb" dependencies = [ "serde_derive", ] @@ -2554,9 +2720,9 @@ dependencies = [ [[package]] name = "serde_derive" -version = "1.0.140" +version = "1.0.152" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6f2122636b9fe3b81f1cb25099fcf2d3f542cdb1d45940d56c713158884a05da" +checksum = "af487d118eecd09402d70a5d72551860e788df87b464af30e5ea6a38c75c541e" dependencies = [ "proc-macro2", "quote", @@ -2565,11 +2731,11 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.82" +version = "1.0.91" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "82c2c1fdcd807d1098552c5b9a36e425e42e9fbd7c6a37a8425f390f781f7fa7" +checksum = "877c235533714907a8c2464236f5c4b2a17262ef1bd71f38f35ea592c8da6883" dependencies = [ - "itoa 1.0.2", + "itoa 1.0.5", "ryu", "serde", ] @@ -2581,32 +2747,32 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" dependencies = [ "form_urlencoded", - "itoa 1.0.2", + "itoa 1.0.5", "ryu", "serde", ] [[package]] name = "serde_with" -version = "2.0.0" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89df7a26519371a3cce44fbb914c2819c84d9b897890987fa3ab096491cc0ea8" +checksum = "30d904179146de381af4c93d3af6ca4984b3152db687dacb9c3c35e86f39809c" dependencies = [ - "base64", + "base64 0.13.1", "chrono", "hex", "indexmap", "serde", "serde_json", "serde_with_macros", - "time 0.3.11", + "time 0.3.17", ] [[package]] name = "serde_with_macros" -version = "2.0.0" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "de337f322382fcdfbb21a014f7c224ee041a23785651db67b9827403178f698f" +checksum = "a1966009f3c05f095697c537312f5415d1e3ed31ce0a56942bac4c771c5c335e" dependencies = [ "darling", "proc-macro2", @@ -2616,9 +2782,9 @@ dependencies = [ [[package]] name = "sha1" -version = "0.10.1" +version = "0.10.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c77f4e7f65455545c2153c1253d25056825e77ee2533f0e41deb65a93a34852f" +checksum = "f04293dc80c3993519f2d7f6f511707ee7094fe0c6d3406feb330cdb3540eba3" dependencies = [ "cfg-if", "cpufeatures", @@ -2627,25 +2793,15 @@ dependencies = [ [[package]] name = "sha2" -version = "0.10.2" +version = "0.10.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "55deaec60f81eefe3cce0dc50bda92d6d8e88f2a27df7c5033b42afeb1ed2676" +checksum = "82e6b795fe2e3b1e845bafcb27aa35405c4d47cdfc92af5fc8d3002f76cebdc0" dependencies = [ "cfg-if", "cpufeatures", "digest", ] -[[package]] -name = "signal-hook" -version = "0.3.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a253b5e89e2698464fc26b545c9edceb338e18a89effeeecfea192c3025be29d" -dependencies = [ - "libc", - "signal-hook-registry", -] - [[package]] name = "signal-hook-registry" version = "1.4.0" @@ -2657,21 +2813,24 @@ dependencies = [ [[package]] name = "slab" -version = "0.4.6" +version = "0.4.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eb703cfe953bccee95685111adeedb76fabe4e97549a58d16f03ea7b9367bb32" +checksum = "4614a76b2a8be0058caa9dbbaf66d988527d86d003c11a94fbd335d7661edcef" +dependencies = [ + "autocfg", +] [[package]] name = "smallvec" -version = "1.9.0" +version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2fd0db749597d91ff862fd1d55ea87f7855a744a8425a64695b6fca237d1dad1" +checksum = "a507befe795404456341dfab10cef66ead4c041f62b8b11bbb92bffe5d0953e0" [[package]] name = "socket2" -version = "0.4.4" +version = "0.4.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "66d72b759436ae32898a2af0a14218dbf55efde3feeb170eb623637db85ee1e0" +checksum = "02e2d2db9033d13a1567121ddd7a095ee144db4e1ca1b1bda3419bc0da294ebd" dependencies = [ "libc", "winapi", @@ -2685,9 +2844,9 @@ checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d" [[package]] name = "spin" -version = "0.9.3" +version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c530c2b0d0bf8b69304b39fe2001993e267461948b890cd037d8ad4293fa1a0d" +checksum = "7f6002a767bff9e83f8eeecf883ecb8011875a21ae8da43bffb817a57e78cc09" dependencies = [ "lock_api", ] @@ -2704,9 +2863,9 @@ dependencies = [ [[package]] name = "sqlformat" -version = "0.2.0" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f87e292b4291f154971a43c3774364e2cbcaec599d3f5bf6fa9d122885dbc38a" +checksum = "0c12bc9199d1db8234678b7051747c07f517cdcf019262d1847b94ec8b1aee3e" dependencies = [ "itertools", "nom", @@ -2731,10 +2890,12 @@ dependencies = [ "serde_json", "sqlx-core", "sqlx-macros", - "sqlx-rt", + "sqlx-mysql", + "sqlx-postgres", + "sqlx-sqlite", "sqlx-test", "tempdir", - "time 0.3.11", + "time 0.3.17", "tokio", "trybuild", "url", @@ -2749,7 +2910,6 @@ dependencies = [ "dotenvy", "once_cell", "sqlx", - "sqlx-rt", ] [[package]] @@ -2761,7 +2921,7 @@ dependencies = [ "backoff", "cargo_metadata", "chrono", - "clap 3.2.10", + "clap 3.2.23", "console", "dotenvy", "filetime", @@ -2782,8 +2942,9 @@ name = "sqlx-core" version = "0.6.2" dependencies = [ "ahash", + "async-io", + "async-std", "atoi", - "base64", "bigdecimal", "bit-vec", "bitflags", @@ -2794,37 +2955,28 @@ dependencies = [ "crc", "crossbeam-queue", "digest", - "dirs", "dotenvy", "either", "encoding_rs", "event-listener", - "flume", "futures-channel", "futures-core", - "futures-executor", "futures-intrusive", + "futures-io", "futures-util", "generic-array", - "git2", "hashlink", "hex", - "hkdf", - "hmac", "indexmap", "ipnetwork", - "itoa 1.0.2", - "libc", - "libsqlite3-sys", "log", "mac_address", - "md-5", "memchr", + "native-tls", "num-bigint", "once_cell", "paste", "percent-encoding", - "rand 0.8.5", "regex", "rsa", "rust_decimal", @@ -2837,16 +2989,13 @@ dependencies = [ "smallvec", "sqlformat", "sqlx", - "sqlx-rt", - "stringprep", "thiserror", - "time 0.3.11", + "time 0.3.17", "tokio", "tokio-stream", "url", "uuid", "webpki-roots", - "whoami", ] [[package]] @@ -2876,7 +3025,7 @@ dependencies = [ "serde_with", "sqlx", "thiserror", - "time 0.3.11", + "time 0.3.17", "tokio", "tower", "tracing", @@ -2943,6 +3092,18 @@ dependencies = [ name = "sqlx-macros" version = "0.6.2" dependencies = [ + "proc-macro2", + "quote", + "sqlx-core", + "sqlx-macros-core", + "syn", +] + +[[package]] +name = "sqlx-macros-core" +version = "0.6.2" +dependencies = [ + "async-std", "dotenvy", "either", "heck 0.4.0", @@ -2954,23 +3115,124 @@ dependencies = [ "serde_json", "sha2", "sqlx-core", - "sqlx-rt", + "sqlx-mysql", + "sqlx-postgres", + "sqlx-sqlite", "syn", + "tokio", "url", ] [[package]] -name = "sqlx-rt" +name = "sqlx-mysql" version = "0.6.2" dependencies = [ - "async-native-tls", - "async-std", - "futures-rustls", - "native-tls", + "atoi", + "base64 0.13.1", + "bigdecimal", + "bitflags", + "byteorder", + "bytes", + "chrono", + "crc", + "digest", + "dirs", + "dotenvy", + "either", + "futures-channel", + "futures-core", + "futures-io", + "futures-util", + "generic-array", + "hex", + "hkdf", + "hmac", + "itoa 1.0.5", + "log", + "md-5", + "memchr", "once_cell", - "tokio", - "tokio-native-tls", - "tokio-rustls", + "percent-encoding", + "rand 0.8.5", + "rsa", + "rust_decimal", + "serde", + "sha1", + "sha2", + "smallvec", + "sqlx-core", + "stringprep", + "thiserror", + "time 0.3.17", + "uuid", + "whoami", +] + +[[package]] +name = "sqlx-postgres" +version = "0.6.2" +dependencies = [ + "atoi", + "base64 0.13.1", + "bigdecimal", + "bit-vec", + "bitflags", + "byteorder", + "chrono", + "crc", + "dirs", + "dotenvy", + "futures-channel", + "futures-core", + "futures-io", + "futures-util", + "hex", + "hkdf", + "hmac", + "ipnetwork", + "itoa 1.0.5", + "log", + "mac_address", + "md-5", + "memchr", + "num-bigint", + "once_cell", + "rand 0.8.5", + "rust_decimal", + "serde", + "serde_json", + "sha1", + "sha2", + "smallvec", + "sqlx-core", + "stringprep", + "thiserror", + "time 0.3.17", + "uuid", + "whoami", +] + +[[package]] +name = "sqlx-sqlite" +version = "0.6.2" +dependencies = [ + "atoi", + "bitflags", + "chrono", + "flume", + "futures-channel", + "futures-core", + "futures-executor", + "futures-intrusive", + "futures-util", + "libsqlite3-sys", + "log", + "percent-encoding", + "serde", + "sqlx-core", + "time 0.3.17", + "url", + "uuid", ] [[package]] @@ -3045,9 +3307,9 @@ checksum = "6bdef32e8150c2a081110b42772ffe7d7c9032b606bc226c8260fd97e0976601" [[package]] name = "syn" -version = "1.0.98" +version = "1.0.107" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c50aef8a904de4c23c788f104b7dddc7d6f79c647c7c8ce4cc8f73eb0ca773dd" +checksum = "1f4064b5b16e03ae50984a5a8ed5d4f8803e6bc1fd170a3cda91a1be4b18e3f5" dependencies = [ "proc-macro2", "quote", @@ -3056,9 +3318,9 @@ dependencies = [ [[package]] name = "sync_wrapper" -version = "0.1.1" +version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "20518fe4a4c9acf048008599e464deb21beeae3d3578418951a189c235a7a9a8" +checksum = "2047c6ded9c721764247e62cd3b03c09ffc529b2ba5b10ec482ae507a4a70160" [[package]] name = "tempdir" @@ -3086,28 +3348,18 @@ dependencies = [ [[package]] name = "termcolor" -version = "1.1.3" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bab24d30b911b2376f3a13cc2cd443142f0c81dda04c118693e35b3835757755" +checksum = "be55cf8942feac5c765c2c993422806843c9a9a45d4d5c407ad6dd2ea95eb9b6" dependencies = [ "winapi-util", ] -[[package]] -name = "terminal_size" -version = "0.1.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "633c1a546cee861a1a6d0dc69ebeca693bf4296661ba7852b9d21d159e0506df" -dependencies = [ - "libc", - "winapi", -] - [[package]] name = "termtree" -version = "0.2.4" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "507e9898683b6c43a9aa55b64259b721b52ba226e0f3779137e50ad114a4c90b" +checksum = "95059e91184749cb66be6dc994f67f182b6d897cb3df74a5bf66b5e709295fd8" [[package]] name = "textwrap" @@ -3120,24 +3372,24 @@ dependencies = [ [[package]] name = "textwrap" -version = "0.15.0" +version = "0.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1141d4d61095b28419e22cb0bbf02755f5e54e0526f97f1e3d1d160e60885fb" +checksum = "222a222a5bfe1bba4a77b45ec488a741b3cb8872e5e499451fd7d0129c9c7c3d" [[package]] name = "thiserror" -version = "1.0.31" +version = "1.0.38" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd829fe32373d27f76265620b5309d0340cb8550f523c1dda251d6298069069a" +checksum = "6a9cd18aa97d5c45c6603caea1da6628790b37f7a34b6ca89522331c5180fed0" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.31" +version = "1.0.38" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0396bc89e626244658bef819e22d0cc459e795a5ebe878e6ec336d1674a8d79a" +checksum = "1fb327af4685e4d03fa8cbcf1716380da910eeb2bb8be417e7f9fd3fb164f36f" dependencies = [ "proc-macro2", "quote", @@ -3146,9 +3398,9 @@ dependencies = [ [[package]] name = "time" -version = "0.1.44" +version = "0.1.45" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6db9e6914ab8b1ae1c260a4ae7a49b6c5611b40328a735b21862567685e73255" +checksum = "1b797afad3f312d1c66a56d11d0316f916356d11bd158fbc6ca6389ff6bf805a" dependencies = [ "libc", "wasi 0.10.0+wasi-snapshot-preview1", @@ -3157,22 +3409,30 @@ dependencies = [ [[package]] name = "time" -version = "0.3.11" +version = "0.3.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72c91f41dcb2f096c05f0873d667dceec1087ce5bcf984ec8ffb19acddbb3217" +checksum = "a561bf4617eebd33bca6434b988f39ed798e527f51a1e797d0ee4f61c0a38376" dependencies = [ - "itoa 1.0.2", - "libc", - "num_threads", + "itoa 1.0.5", "serde", + "time-core", "time-macros", ] +[[package]] +name = "time-core" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e153e1f1acaef8acc537e68b44906d2db6436e2b35ac2c6b42640fff91f00fd" + [[package]] name = "time-macros" -version = "0.2.4" +version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42657b1a6f4d817cda8e7a0ace261fe0cc946cf3a80314390b22cc61ae080792" +checksum = "d967f99f534ca7e495c575c62638eebc2898a8c84c119b89e250477bc4ba16b2" +dependencies = [ + "time-core", +] [[package]] name = "tinytemplate" @@ -3201,9 +3461,9 @@ checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c" [[package]] name = "tokio" -version = "1.20.1" +version = "1.24.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a8325f63a7d4774dd041e363b2409ed1c5cbbd0f867795e661df066b2b0a581" +checksum = "597a12a59981d9e3c38d216785b0c37399f6e415e8d0712047620f189371b0bb" dependencies = [ "autocfg", "bytes", @@ -3211,52 +3471,30 @@ dependencies = [ "memchr", "mio", "num_cpus", - "once_cell", "parking_lot 0.12.1", "pin-project-lite", "signal-hook-registry", "socket2", "tokio-macros", - "winapi", + "windows-sys", ] [[package]] name = "tokio-macros" -version = "1.8.0" +version = "1.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9724f9a975fb987ef7a3cd9be0350edcbe130698af5b8f7a631e23d42d052484" +checksum = "d266c00fde287f55d3f1c3e96c500c362a2b8c695076ec180f27918820bc6df8" dependencies = [ "proc-macro2", "quote", "syn", ] -[[package]] -name = "tokio-native-tls" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f7d995660bd2b7f8c1568414c1126076c13fbb725c40112dc0120b78eb9b717b" -dependencies = [ - "native-tls", - "tokio", -] - -[[package]] -name = "tokio-rustls" -version = "0.23.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c43ee83903113e03984cb9e5cebe6c04a5116269e900e3ddba8f068a62adda59" -dependencies = [ - "rustls", - "tokio", - "webpki", -] - [[package]] name = "tokio-stream" -version = "0.1.9" +version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df54d54117d6fdc4e4fea40fe1e4e566b3505700e148a6827e59b34b0d2600d9" +checksum = "d660770404473ccd7bc9f8b28494a811bc18542b915c0855c51e8f419d5223ce" dependencies = [ "futures-core", "pin-project-lite", @@ -3265,9 +3503,9 @@ dependencies = [ [[package]] name = "toml" -version = "0.5.9" +version = "0.5.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8d82e1a7758622a465f8cee077614c73484dac5b836c02ff6a40d5d1010324d7" +checksum = "f4f7f0dd8d50a853a531c426359045b1998f04219d88799810762cd4ad314234" dependencies = [ "serde", ] @@ -3290,9 +3528,9 @@ dependencies = [ [[package]] name = "tower-http" -version = "0.3.4" +version = "0.3.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c530c8675c1dbf98facee631536fa116b5fb6382d7dd6dc1b118d970eafe3ba" +checksum = "f873044bf02dd1e8239e9c1293ea39dad76dc594ec16185d0a1bf31d8dc8d858" dependencies = [ "bitflags", "bytes", @@ -3309,9 +3547,9 @@ dependencies = [ [[package]] name = "tower-layer" -version = "0.3.1" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "343bc9466d3fe6b0f960ef45960509f84480bf4fd96f92901afe7ff3df9d3a62" +checksum = "c20c8dbed6283a09604c3e69b4b7eeb54e298b8a600d4d5ecb5ad39de609f1d0" [[package]] name = "tower-service" @@ -3321,9 +3559,9 @@ checksum = "b6bc1c9ce2b5135ac7f93c72918fc37feb872bdc6a5533a8b85eb4b86bfdae52" [[package]] name = "tracing" -version = "0.1.35" +version = "0.1.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a400e31aa60b9d44a52a8ee0343b5b18566b03a8321e0d321f695cf56e940160" +checksum = "8ce8c33a8d48bd45d624a6e523445fd21ec13d3653cd51f681abf67418f54eb8" dependencies = [ "cfg-if", "log", @@ -3334,9 +3572,9 @@ dependencies = [ [[package]] name = "tracing-attributes" -version = "0.1.22" +version = "0.1.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "11c75893af559bc8e10716548bdef5cb2b983f8e637db9d0e15126b61b484ee2" +checksum = "4017f8f45139870ca7e672686113917c71c7a6e02d4924eda67186083c03081a" dependencies = [ "proc-macro2", "quote", @@ -3345,24 +3583,24 @@ dependencies = [ [[package]] name = "tracing-core" -version = "0.1.28" +version = "0.1.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b7358be39f2f274f322d2aaed611acc57f382e8eb1e5b48cb9ae30933495ce7" +checksum = "24eb03ba0eab1fd845050058ce5e616558e8f8d8fca633e6b163fe25c797213a" dependencies = [ "once_cell", ] [[package]] name = "try-lock" -version = "0.2.3" +version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "59547bce71d9c38b83d9c0e92b6066c4253371f15005def0c30d9657f50c7642" +checksum = "3528ecfd12c466c6f163363caf2d02a71161dd5e1cc6ae7b34207ea2d42d81ed" [[package]] name = "trybuild" -version = "1.0.63" +version = "1.0.76" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "764b9e244b482a9b81bde596aa37aa6f1347bf8007adab25e59f901b32b4e0a0" +checksum = "6ed2c57956f91546d4d33614265a85d55c8e1ab91484853a10335894786d7db6" dependencies = [ "glob", "once_cell", @@ -3375,42 +3613,42 @@ dependencies = [ [[package]] name = "typenum" -version = "1.15.0" +version = "1.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dcf81ac59edc17cc8697ff311e8f5ef2d99fcbd9817b34cec66f90b6c3dfd987" +checksum = "497961ef93d974e23eb6f433eb5fe1b7930b659f06d12dec6fc44a8f554c0bba" [[package]] name = "unicode-bidi" -version = "0.3.8" +version = "0.3.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "099b7128301d285f79ddd55b9a83d5e6b9e97c92e0ea0daebee7263e932de992" +checksum = "d54675592c1dbefd78cbd98db9bacd89886e1ca50692a0692baefffdeb92dd58" [[package]] name = "unicode-ident" -version = "1.0.1" +version = "1.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5bd2fe26506023ed7b5e1e315add59d6f584c621d037f9368fea9cfb988f368c" +checksum = "84a22b9f218b40614adcb3f4ff08b703773ad44fa9423e4e0d346d5db86e4ebc" [[package]] name = "unicode-normalization" -version = "0.1.21" +version = "0.1.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "854cbdc4f7bc6ae19c820d44abdc3277ac3e1b2b93db20a636825d9322fb60e6" +checksum = "5c5713f0fc4b5db668a2ac63cdb7bb4469d8c9fed047b1d0292cc7b0ce2ba921" dependencies = [ "tinyvec", ] [[package]] name = "unicode-segmentation" -version = "1.9.0" +version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e8820f5d777f6224dc4be3632222971ac30164d4a258d595640799554ebfd99" +checksum = "0fdbf052a0783de01e944a6ce7a8cb939e295b1e7be835a1112c3b9a7f047a5a" [[package]] name = "unicode-width" -version = "0.1.9" +version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3ed742d4ea2bd1176e236172c8429aaf54486e7ac098db29ffe6529e0ce50973" +checksum = "c0edd1e5b14653f783770bce4a4dabb4a5108a5370a5f5d8cfe8710c361f6c8b" [[package]] name = "unicode_categories" @@ -3426,13 +3664,12 @@ checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a" [[package]] name = "url" -version = "2.2.2" +version = "2.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a507c383b2d33b5fc35d1861e77e6b383d158b2da5e14fe51b83dfedf6fd578c" +checksum = "0d68c799ae75762b8c3fe375feb6600ef5602c883c5d21eb51c09f22b83c4643" dependencies = [ "form_urlencoded", - "idna", - "matches", + "idna 0.3.0", "percent-encoding", ] @@ -3444,9 +3681,9 @@ checksum = "936e4b492acfd135421d8dca4b1aa80a7bfc26e702ef3af710e0752684df5372" [[package]] name = "uuid" -version = "1.1.2" +version = "1.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd6469f4314d5f1ffec476e05f17cc9a78bc7a27a6a857842170bdf8d6f98d2f" +checksum = "422ee0de9031b5b948b97a8fc04e3aa35230001a722ddd27943e0be31564ce4c" dependencies = [ "serde", ] @@ -3457,7 +3694,7 @@ version = "0.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32ad5bf234c7d3ad1042e5252b7eddb2c4669ee23f32c7dd0e9b7705f07ef591" dependencies = [ - "idna", + "idna 0.2.3", "lazy_static", "regex", "serde", @@ -3562,9 +3799,9 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] name = "wasm-bindgen" -version = "0.2.81" +version = "0.2.83" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7c53b543413a17a202f4be280a7e5c62a1c69345f5de525ee64f8cfdbc954994" +checksum = "eaf9f5aceeec8be17c128b2e93e031fb8a4d469bb9c4ae2d7dc1888b26887268" dependencies = [ "cfg-if", "wasm-bindgen-macro", @@ -3572,13 +3809,13 @@ dependencies = [ [[package]] name = "wasm-bindgen-backend" -version = "0.2.81" +version = "0.2.83" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5491a68ab4500fa6b4d726bd67408630c3dbe9c4fe7bda16d5c82a1fd8c7340a" +checksum = "4c8ffb332579b0557b52d268b91feab8df3615f265d5270fec2a8c95b17c1142" dependencies = [ "bumpalo", - "lazy_static", "log", + "once_cell", "proc-macro2", "quote", "syn", @@ -3587,9 +3824,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-futures" -version = "0.4.31" +version = "0.4.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "de9a9cec1733468a8c657e57fa2413d2ae2c0129b95e87c5b72b8ace4d13f31f" +checksum = "23639446165ca5a5de86ae1d8896b737ae80319560fbaa4c2887b7da6e7ebd7d" dependencies = [ "cfg-if", "js-sys", @@ -3599,9 +3836,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.81" +version = "0.2.83" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c441e177922bc58f1e12c022624b6216378e5febc2f0533e41ba443d505b80aa" +checksum = "052be0f94026e6cbc75cdefc9bae13fd6052cdcaf532fa6c45e7ae33a1e6c810" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -3609,9 +3846,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.81" +version = "0.2.83" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d94ac45fcf608c1f45ef53e748d35660f168490c10b23704c7779ab8f5c3048" +checksum = "07bc0c051dc5f23e307b13285f9d75df86bfdf816c5721e573dec1f9b8aa193c" dependencies = [ "proc-macro2", "quote", @@ -3622,15 +3859,15 @@ dependencies = [ [[package]] name = "wasm-bindgen-shared" -version = "0.2.81" +version = "0.2.83" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a89911bd99e5f3659ec4acf9c4d93b0a90fe4a2a11f15328472058edc5261be" +checksum = "1c38c045535d93ec4f0b4defec448e4291638ee608530863b1e2ba115d4fff7f" [[package]] name = "web-sys" -version = "0.3.58" +version = "0.3.60" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2fed94beee57daf8dd7d51f2b15dc2bcde92d7a72304cdf662a4371008b71b90" +checksum = "bcda906d8be16e728fd5adc5b729afad4e444e106ab28cd1c7256e54fa61510f" dependencies = [ "js-sys", "wasm-bindgen", @@ -3648,9 +3885,9 @@ dependencies = [ [[package]] name = "webpki-roots" -version = "0.22.4" +version = "0.22.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1c760f0d366a6c24a02ed7816e23e691f5d92291f94d15e836006fd11b04daf" +checksum = "b6c71e40d7d2c34a5106301fb632274ca37242cd0c9d3e64dbece371a40a2d87" dependencies = [ "webpki", ] @@ -3666,9 +3903,9 @@ dependencies = [ [[package]] name = "whoami" -version = "1.2.1" +version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "524b58fa5a20a2fb3014dd6358b70e6579692a56ef6fce928834e488f42f65e8" +checksum = "45dbc71f0cdca27dc261a9bd37ddec174e4a0af2b900b890f378460f745426e3" dependencies = [ "wasm-bindgen", "web-sys", @@ -3707,49 +3944,63 @@ checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" [[package]] name = "windows-sys" -version = "0.36.1" +version = "0.42.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea04155a16a59f9eab786fe12a4a450e75cdb175f9e0d80da1e17db09f55b8d2" +checksum = "5a3e1820f08b8513f676f7ab6c1f99ff312fb97b553d30ff4dd86f9f15728aa7" dependencies = [ + "windows_aarch64_gnullvm", "windows_aarch64_msvc", "windows_i686_gnu", "windows_i686_msvc", "windows_x86_64_gnu", + "windows_x86_64_gnullvm", "windows_x86_64_msvc", ] +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.42.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c9864e83243fdec7fc9c5444389dcbbfd258f745e7853198f365e3c4968a608" + [[package]] name = "windows_aarch64_msvc" -version = "0.36.1" +version = "0.42.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9bb8c3fd39ade2d67e9874ac4f3db21f0d710bee00fe7cab16949ec184eeaa47" +checksum = "4c8b1b673ffc16c47a9ff48570a9d85e25d265735c503681332589af6253c6c7" [[package]] name = "windows_i686_gnu" -version = "0.36.1" +version = "0.42.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "180e6ccf01daf4c426b846dfc66db1fc518f074baa793aa7d9b9aaeffad6a3b6" +checksum = "de3887528ad530ba7bdbb1faa8275ec7a1155a45ffa57c37993960277145d640" [[package]] name = "windows_i686_msvc" -version = "0.36.1" +version = "0.42.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2e7917148b2812d1eeafaeb22a97e4813dfa60a3f8f78ebe204bcc88f12f024" +checksum = "bf4d1122317eddd6ff351aa852118a2418ad4214e6613a50e0191f7004372605" [[package]] name = "windows_x86_64_gnu" -version = "0.36.1" +version = "0.42.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1040f221285e17ebccbc2591ffdc2d44ee1f9186324dd3e84e99ac68d699c45" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.42.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4dcd171b8776c41b97521e5da127a2d86ad280114807d0b2ab1e462bc764d9e1" +checksum = "628bfdf232daa22b0d64fdb62b09fcc36bb01f05a3939e20ab73aaf9470d0463" [[package]] name = "windows_x86_64_msvc" -version = "0.36.1" +version = "0.42.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c811ca4a8c853ef420abd8592ba53ddbbac90410fab6903b3e79972a631f7680" +checksum = "447660ad36a13288b1db4d4248e857b510e8c3a225c822ba4fb748c0aafecffd" [[package]] name = "zeroize" -version = "1.5.6" +version = "1.5.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "20b578acffd8516a6c3f2a1bdefc1ec37e547bb4e0fb8b6b01a4cafc886b4442" +checksum = "c394b5bd0c6f669e7275d9c20aa90ae064cb22e75a1cad54e1b34088034b149f" diff --git a/Cargo.toml b/Cargo.toml index ebe489cecd..d90ffd375a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -2,11 +2,14 @@ members = [ ".", "sqlx-core", - "sqlx-rt", "sqlx-macros", + "sqlx-macros-core", "sqlx-test", "sqlx-cli", "sqlx-bench", + "sqlx-mysql", + "sqlx-postgres", + "sqlx-sqlite", "examples/mysql/todos", "examples/postgres/axum-social-with-tests", "examples/postgres/files", @@ -18,15 +21,11 @@ members = [ "examples/sqlite/todos", ] -[package] -name = "sqlx" +[workspace.package] version = "0.6.2" license = "MIT OR Apache-2.0" -readme = "README.md" -repository = "https://github.com/launchbadge/sqlx" -documentation = "https://docs.rs/sqlx" -description = "🧰 The Rust SQL Toolkit. An async, pure Rust SQL crate featuring compile-time checked queries without a DSL. Supports PostgreSQL, MySQL, and SQLite." edition = "2021" +repository = "https://github.com/launchbadge/sqlx" keywords = ["database", "async", "postgres", "mysql", "sqlite"] categories = ["database", "asynchronous"] authors = [ @@ -36,28 +35,31 @@ authors = [ "Daniel Akhterov ", ] +[package] +name = "sqlx" +readme = "README.md" +documentation = "https://docs.rs/sqlx" +description = "🧰 The Rust SQL Toolkit. An async, pure Rust SQL crate featuring compile-time checked queries without a DSL. Supports PostgreSQL, MySQL, and SQLite." +version.workspace = true +license.workspace = true +edition.workspace = true +authors.workspace = true +repository.workspace = true + [package.metadata.docs.rs] features = ["all", "runtime-tokio-native-tls"] rustdoc-args = ["--cfg", "docsrs"] [features] -default = ["macros", "migrate"] +default = ["any", "macros", "migrate", "json"] macros = ["sqlx-macros"] -migrate = ["sqlx-macros/migrate", "sqlx-core/migrate"] - -# [deprecated] TLS is not possible to disable due to it being conditional on multiple features -# Hopefully Cargo can handle this in the future -tls = [] - -# offline building support in `sqlx-macros` -offline = ["sqlx-macros/offline", "sqlx-core/offline"] +migrate = ["sqlx-core/migrate", "sqlx-macros?/migrate", "sqlx-mysql?/migrate", "sqlx-postgres?/migrate", "sqlx-sqlite?/migrate"] # intended mainly for CI and docs -all = ["tls", "all-databases", "all-types"] -all-databases = ["mysql", "sqlite", "postgres", "mssql", "any"] -all-types = [ +all-databases = ["mysql", "sqlite", "postgres", "any"] +_unstable-all-types = [ "bigdecimal", - "decimal", + "rust_decimal", "json", "time", "chrono", @@ -65,69 +67,89 @@ all-types = [ "mac_address", "uuid", "bit-vec", - "bstr", - "git2", ] -# previous runtimes, available as features for error messages better than just -# "feature doesn't exist" -runtime-actix = [] -runtime-async-std = [] -runtime-tokio = [] - -# actual runtimes -runtime-actix-native-tls = ["runtime-tokio-native-tls"] -runtime-async-std-native-tls = [ - "sqlx-core/runtime-async-std-native-tls", - "sqlx-macros/runtime-async-std-native-tls", - "_rt-async-std", -] -runtime-tokio-native-tls = [ - "sqlx-core/runtime-tokio-native-tls", - "sqlx-macros/runtime-tokio-native-tls", - "_rt-tokio", -] +# Base runtime features without TLS +runtime-async-std = ["_rt-async-std", "sqlx-core/_rt-async-std", "sqlx-macros/_rt-async-std"] +runtime-tokio = ["_rt-tokio", "sqlx-core/_rt-tokio", "sqlx-macros/_rt-tokio"] -runtime-actix-rustls = ["runtime-tokio-rustls"] -runtime-async-std-rustls = [ - "sqlx-core/runtime-async-std-rustls", - "sqlx-macros/runtime-async-std-rustls", - "_rt-async-std", -] -runtime-tokio-rustls = [ - "sqlx-core/runtime-tokio-rustls", - "sqlx-macros/runtime-tokio-rustls", - "_rt-tokio", -] +# TLS features +tls-native-tls = ["sqlx-core/_tls-native-tls", "sqlx-macros/_tls-native-tls"] +tls-rustls = ["sqlx-core/_tls-rustls", "sqlx-macros/_tls-rustls"] + +# No-op feature used by the workflows to compile without TLS enabled. Not meant for general use. +tls-none = [] + +# Legacy Runtime + TLS features + +runtime-async-std-native-tls = ["runtime-async-std", "tls-native-tls"] +runtime-async-std-rustls = ["runtime-async-std", "tls-rustls"] + +runtime-tokio-native-tls = ["runtime-tokio", "tls-native-tls"] +runtime-tokio-rustls = ["runtime-tokio", "tls-rustls"] # for conditional compilation _rt-async-std = [] _rt-tokio = [] # database -any = ["sqlx-core/any"] -postgres = ["sqlx-core/postgres", "sqlx-macros/postgres"] -mysql = ["sqlx-core/mysql", "sqlx-macros/mysql"] -sqlite = ["sqlx-core/sqlite", "sqlx-macros/sqlite"] -mssql = ["sqlx-core/mssql", "sqlx-macros/mssql"] +any = ["sqlx-core/any", "sqlx-mysql?/any", "sqlx-postgres?/any", "sqlx-sqlite?/any"] +postgres = ["sqlx-postgres", "sqlx-macros?/postgres"] +mysql = ["sqlx-mysql", "sqlx-macros?/mysql"] +sqlite = ["sqlx-sqlite", "sqlx-macros?/sqlite"] # types -bigdecimal = ["sqlx-core/bigdecimal", "sqlx-macros/bigdecimal"] -decimal = ["sqlx-core/decimal", "sqlx-macros/decimal"] -chrono = ["sqlx-core/chrono", "sqlx-macros/chrono"] -ipnetwork = ["sqlx-core/ipnetwork", "sqlx-macros/ipnetwork"] -mac_address = ["sqlx-core/mac_address", "sqlx-macros/mac_address"] -uuid = ["sqlx-core/uuid", "sqlx-macros/uuid"] -json = ["sqlx-core/json", "sqlx-macros/json"] -time = ["sqlx-core/time", "sqlx-macros/time"] -bit-vec = ["sqlx-core/bit-vec", "sqlx-macros/bit-vec"] -bstr = ["sqlx-core/bstr"] -git2 = ["sqlx-core/git2"] +json = ["sqlx-macros?/json", "sqlx-mysql?/json", "sqlx-postgres?/json", "sqlx-sqlite?/json"] + +bigdecimal = ["sqlx-core/bigdecimal", "sqlx-macros?/bigdecimal", "sqlx-mysql?/bigdecimal", "sqlx-postgres?/bigdecimal"] +bit-vec = ["sqlx-core/bit-vec", "sqlx-macros?/bit-vec", "sqlx-postgres?/bit-vec"] +chrono = ["sqlx-core/chrono", "sqlx-macros?/chrono", "sqlx-mysql?/chrono", "sqlx-postgres?/chrono", "sqlx-sqlite?/chrono"] +ipnetwork = ["sqlx-core/ipnetwork", "sqlx-macros?/ipnetwork", "sqlx-postgres?/ipnetwork"] +mac_address = ["sqlx-core/mac_address", "sqlx-macros?/mac_address", "sqlx-postgres?/mac_address"] +rust_decimal = ["sqlx-core/rust_decimal", "sqlx-macros?/rust_decimal", "sqlx-mysql?/rust_decimal", "sqlx-postgres?/rust_decimal"] +time = ["sqlx-core/time", "sqlx-macros?/time", "sqlx-mysql?/time", "sqlx-postgres?/time", "sqlx-sqlite?/time"] +uuid = ["sqlx-core/uuid", "sqlx-macros?/uuid", "sqlx-mysql?/uuid", "sqlx-postgres?/uuid", "sqlx-sqlite?/uuid"] + +[workspace.dependencies] +# Driver crates +sqlx-mysql = { version = "=0.6.2", path = "sqlx-mysql" } +sqlx-postgres = { version = "=0.6.2", path = "sqlx-postgres" } +sqlx-sqlite = { version = "=0.6.2", path = "sqlx-sqlite" } + +# Facade crate (for reference from sqlx-cli) +sqlx = {version = "=0.6.2", path = "." } + +# Common type integrations shared by multiple driver crates. +# These are optional unless enabled in a workspace crate. +bigdecimal = "0.3.0" +bit-vec = "0.6.3" +chrono = "0.4.22" +ipnetwork = "0.20.0" +mac_address = "1.1.3" +rust_decimal = "1.26.1" +time = { version = "0.3.14", features = ["formatting", "parsing", "macros"] } +uuid = "1.1.2" + +# Common utility crates +dotenvy = { version = "0.15.0", default-features = false } + +# Runtimes +[workspace.dependencies.async-std] +version = "1" + +[workspace.dependencies.tokio] +version = "1" +features = ["time", "net", "sync", "fs", "io-util", "rt"] +default-features = false [dependencies] -sqlx-core = { version = "0.6.2", path = "sqlx-core", default-features = false } +sqlx-core = { version = "0.6.2", path = "sqlx-core", features = ["offline", "migrate"], default-features = false } sqlx-macros = { version = "0.6.2", path = "sqlx-macros", default-features = false, optional = true } +sqlx-mysql = { workspace = true, optional = true } +sqlx-postgres = { workspace = true, optional = true } +sqlx-sqlite = { workspace = true, optional = true } + [dev-dependencies] anyhow = "1.0.52" time_ = { version = "0.3.2", package = "time" } @@ -137,7 +159,6 @@ async-std = { version = "1.10.0", features = ["attributes"] } tokio = { version = "1.15.0", features = ["full"] } dotenvy = "0.15.0" trybuild = "1.0.53" -sqlx-rt = { path = "./sqlx-rt" } sqlx-test = { path = "./sqlx-test" } paste = "1.0.6" serde = { version = "1.0.132", features = ["derive"] } @@ -203,7 +224,7 @@ path = "tests/sqlite/derives.rs" required-features = ["sqlite", "macros"] [[test]] -name = "sqlcipher" +name = "sqlite-sqlcipher" path = "tests/sqlite/sqlcipher.rs" required-features = ["sqlite"] @@ -289,27 +310,3 @@ required-features = ["postgres", "macros", "migrate"] name = "postgres-migrate" path = "tests/postgres/migrate.rs" required-features = ["postgres", "macros", "migrate"] - -# -# Microsoft SQL Server (MSSQL) -# - -[[test]] -name = "mssql" -path = "tests/mssql/mssql.rs" -required-features = ["mssql"] - -[[test]] -name = "mssql-types" -path = "tests/mssql/types.rs" -required-features = ["mssql"] - -[[test]] -name = "mssql-describe" -path = "tests/mssql/describe.rs" -required-features = ["mssql"] - -[[test]] -name = "mssql-macros" -path = "tests/mssql/macros.rs" -required-features = ["mssql", "macros"] diff --git a/README.md b/README.md index 7c7b66a237..0c2499d231 100644 --- a/README.md +++ b/README.md @@ -123,28 +123,57 @@ SQLx is compatible with the [`async-std`], [`tokio`] and [`actix`] runtimes; and ```toml # Cargo.toml [dependencies] +# PICK ONE OF THE FOLLOWING: + +# tokio (no TLS) +sqlx = { version = "0.6", features = [ "runtime-tokio" ] } +# tokio + native-tls +sqlx = { version = "0.6", features = [ "runtime-tokio", "tls-native" ] } # tokio + rustls -sqlx = { version = "0.6", features = [ "runtime-tokio-rustls" ] } +sqlx = { version = "0.6", features = [ "runtime-tokio", "tls-rustls" ] } + +# async-std (no TLS) +sqlx = { version = "0.6", features = [ "runtime-async-std" ] } # async-std + native-tls -sqlx = { version = "0.6", features = [ "runtime-async-std-native-tls" ] } +sqlx = { version = "0.6", features = [ "runtime-async-std", "tls-native" ] } +# async-std + rustls +sqlx = { version = "0.6", features = [ "runtime-async-std", "tls-rustls" ] } ``` -The runtime and TLS backend not being separate feature sets to select is a workaround for a [Cargo issue](https://github.com/rust-lang/cargo/issues/3494). - #### Cargo Feature Flags +For backwards-compatibility reasons, the runtime and TLS features can either be chosen together as a single feature, +or separately. + +For forward-compatibility, you should use the separate runtime and TLS features as the combination features may +be removed in the future. + +- `runtime-async-std`: Use the `async-std` runtime without enabling a TLS backend. + - `runtime-async-std-native-tls`: Use the `async-std` runtime and `native-tls` TLS backend. - `runtime-async-std-rustls`: Use the `async-std` runtime and `rustls` TLS backend. +- `runtime-tokio`: Use the `tokio` runtime without enabling a TLS backend. + - `runtime-tokio-native-tls`: Use the `tokio` runtime and `native-tls` TLS backend. - `runtime-tokio-rustls`: Use the `tokio` runtime and `rustls` TLS backend. +- `runtime-actix`: Use the `actix` runtime without enabling a TLS backend. + - `runtime-actix-native-tls`: Use the `actix` runtime and `native-tls` TLS backend. - `runtime-actix-rustls`: Use the `actix` runtime and `rustls` TLS backend. + - Actix-web is fully compatible with Tokio and so a separate runtime feature is no longer needed. + The above three features exist only for backwards compatibility, and are in fact merely aliases to their + `runtime-tokio` counterparts. + +- `tls-native`: Use the `native-tls` TLS backend (OpenSSL on *nix, SChannel on Windows, Secure Transport on macOS). + +- `tls-rustls`: Use the `rustls` TLS backend (crossplatform backend, only supports TLS 1.2 and 1.3). + - `postgres`: Add support for the Postgres database server. - `mysql`: Add support for the MySQL/MariaDB database server. @@ -177,8 +206,6 @@ sqlx = { version = "0.6", features = [ "runtime-async-std-native-tls" ] } - `json`: Add support for `JSON` and `JSONB` (in postgres) using the `serde_json` crate. -- `tls`: Add support for TLS connections. - - `offline`: Enables building the macros in offline mode when a live database is not available (such as CI). - Requires `sqlx-cli` installed to use. See [sqlx-cli/README.md][readme-offline]. diff --git a/examples/postgres/files/Cargo.toml b/examples/postgres/files/Cargo.toml index 2fca037cbc..30317e4118 100644 --- a/examples/postgres/files/Cargo.toml +++ b/examples/postgres/files/Cargo.toml @@ -7,6 +7,6 @@ edition = "2021" [dependencies] anyhow = "1.0" -sqlx = { path = "../../../", features = ["postgres", "offline", "runtime-tokio-native-tls"] } +sqlx = { path = "../../../", features = ["postgres", "runtime-tokio-native-tls"] } tokio = { version = "1.20.0", features = ["macros"]} dotenvy = "0.15.0" diff --git a/examples/postgres/listen/Cargo.toml b/examples/postgres/listen/Cargo.toml index 06fd046248..7fa814c27e 100644 --- a/examples/postgres/listen/Cargo.toml +++ b/examples/postgres/listen/Cargo.toml @@ -5,6 +5,6 @@ edition = "2021" workspace = "../../../" [dependencies] -sqlx = { path = "../../../", features = [ "postgres", "tls" ] } +sqlx = { path = "../../../", features = [ "postgres" ] } futures = "0.3.1" tokio = { version = "1.20.0", features = ["macros"]} diff --git a/examples/postgres/mockable-todos/Cargo.toml b/examples/postgres/mockable-todos/Cargo.toml index b2a5004895..58e6237f37 100644 --- a/examples/postgres/mockable-todos/Cargo.toml +++ b/examples/postgres/mockable-todos/Cargo.toml @@ -7,7 +7,7 @@ workspace = "../../../" [dependencies] anyhow = "1.0" futures = "0.3" -sqlx = { path = "../../../", features = ["postgres", "offline", "runtime-tokio-native-tls"] } +sqlx = { path = "../../../", features = ["postgres", "runtime-tokio-native-tls"] } structopt = "0.3" tokio = { version = "1.20.0", features = ["macros"]} dotenvy = "0.15.0" diff --git a/examples/postgres/todos/Cargo.toml b/examples/postgres/todos/Cargo.toml index a00ab31825..18f87d87f2 100644 --- a/examples/postgres/todos/Cargo.toml +++ b/examples/postgres/todos/Cargo.toml @@ -7,7 +7,7 @@ workspace = "../../../" [dependencies] anyhow = "1.0" futures = "0.3" -sqlx = { path = "../../../", features = ["postgres", "offline", "runtime-tokio-native-tls"] } +sqlx = { path = "../../../", features = ["postgres", "runtime-tokio-native-tls"] } structopt = "0.3" tokio = { version = "1.20.0", features = ["macros"]} dotenvy = "0.15.0" diff --git a/examples/postgres/transaction/Cargo.toml b/examples/postgres/transaction/Cargo.toml index b9be8c1d01..b0e59ef75c 100644 --- a/examples/postgres/transaction/Cargo.toml +++ b/examples/postgres/transaction/Cargo.toml @@ -5,6 +5,6 @@ edition = "2021" workspace = "../../../" [dependencies] -sqlx = { path = "../../../", features = [ "postgres", "tls", "runtime-tokio-native-tls" ] } +sqlx = { path = "../../../", features = [ "postgres", "runtime-tokio-native-tls" ] } futures = "0.3.1" tokio = { version = "1.20.0", features = ["macros"]} diff --git a/sqlx-bench/Cargo.toml b/sqlx-bench/Cargo.toml index 58504423ee..5fcb35068a 100644 --- a/sqlx-bench/Cargo.toml +++ b/sqlx-bench/Cargo.toml @@ -9,21 +9,17 @@ publish = false runtime-actix-native-tls = ["runtime-tokio-native-tls"] runtime-async-std-native-tls = [ "sqlx/runtime-async-std-native-tls", - "sqlx-rt/runtime-async-std-native-tls", ] runtime-tokio-native-tls = [ "sqlx/runtime-tokio-native-tls", - "sqlx-rt/runtime-tokio-native-tls", ] runtime-actix-rustls = ["runtime-tokio-rustls"] runtime-async-std-rustls = [ "sqlx/runtime-async-std-rustls", - "sqlx-rt/runtime-async-std-rustls", ] runtime-tokio-rustls = [ "sqlx/runtime-tokio-rustls", - "sqlx-rt/runtime-tokio-rustls", ] postgres = ["sqlx/postgres"] @@ -34,7 +30,6 @@ criterion = "0.3.3" dotenvy = "0.15.0" once_cell = "1.4" sqlx = { version = "0.6", path = "../", default-features = false, features = ["macros"] } -sqlx-rt = { version = "0.6", path = "../sqlx-rt", default-features = false } chrono = "0.4.19" diff --git a/sqlx-bench/benches/pg_pool.rs b/sqlx-bench/benches/pg_pool.rs index 0e4f2e78d5..4fdce3b6b6 100644 --- a/sqlx-bench/benches/pg_pool.rs +++ b/sqlx-bench/benches/pg_pool.rs @@ -23,7 +23,7 @@ fn bench_pgpool_acquire(c: &mut Criterion) { } fn do_bench_acquire(b: &mut Bencher, concurrent: u32, fair: bool) { - let pool = sqlx_rt::block_on( + let pool = sqlx::__rt::block_on( PgPoolOptions::new() // we don't want timeouts because we want to see how the pool degrades .acquire_timeout(Duration::from_secs(3600)) @@ -41,8 +41,8 @@ fn do_bench_acquire(b: &mut Bencher, concurrent: u32, fair: bool) { for _ in 0..concurrent { let pool = pool.clone(); - sqlx_rt::enter_runtime(|| { - sqlx_rt::spawn(async move { + sqlx::__rt::enter_runtime(|| { + sqlx::__rt::spawn(async move { while !pool.is_closed() { let conn = match pool.acquire().await { Ok(conn) => conn, @@ -51,7 +51,7 @@ fn do_bench_acquire(b: &mut Bencher, concurrent: u32, fair: bool) { }; // pretend we're using the connection - sqlx_rt::sleep(Duration::from_micros(500)).await; + sqlx::__rt::sleep(Duration::from_micros(500)).await; drop(criterion::black_box(conn)); } }) @@ -59,7 +59,7 @@ fn do_bench_acquire(b: &mut Bencher, concurrent: u32, fair: bool) { } b.iter_custom(|iters| { - sqlx_rt::block_on(async { + sqlx::__rt::block_on(async { // take the start time inside the future to make sure we only count once it's running let start = Instant::now(); for _ in 0..iters { @@ -73,7 +73,7 @@ fn do_bench_acquire(b: &mut Bencher, concurrent: u32, fair: bool) { }) }); - sqlx_rt::block_on(pool.close()); + sqlx::__rt::block_on(pool.close()); } criterion_group!(pg_pool, bench_pgpool_acquire); diff --git a/sqlx-bench/benches/sqlite_fetch_all.rs b/sqlx-bench/benches/sqlite_fetch_all.rs index 690b1ddd9a..ba6f6f71d5 100644 --- a/sqlx-bench/benches/sqlite_fetch_all.rs +++ b/sqlx-bench/benches/sqlite_fetch_all.rs @@ -8,7 +8,7 @@ struct Test { } fn main() -> sqlx::Result<()> { - sqlx_rt::block_on(async { + sqlx::__rt::block_on(async { let mut conn = sqlx::SqliteConnection::connect("sqlite://test.db?mode=rwc").await?; let delete_sql = "DROP TABLE IF EXISTS test"; conn.execute(delete_sql).await?; diff --git a/sqlx-cli/Cargo.toml b/sqlx-cli/Cargo.toml index f0e8eb8a45..3dce2f0173 100644 --- a/sqlx-cli/Cargo.toml +++ b/sqlx-cli/Cargo.toml @@ -27,10 +27,9 @@ path = "src/bin/cargo-sqlx.rs" [dependencies] dotenvy = "0.15.0" tokio = { version = "1.15.0", features = ["macros", "rt", "rt-multi-thread"] } -sqlx = { version = "0.6.2", path = "..", default-features = false, features = [ +sqlx = { workspace = true, default-features = false, features = [ "migrate", "any", - "offline", ] } futures = "0.3.19" clap = { version = "3.1.0", features = ["derive", "env"] } @@ -52,14 +51,14 @@ filetime = "0.2" backoff = { version = "0.4.0", features = ["futures", "tokio"] } [features] -default = ["postgres", "sqlite", "mysql", "native-tls"] +#default = ["postgres", "sqlite", "mysql", "native-tls"] rustls = ["sqlx/runtime-tokio-rustls"] native-tls = ["sqlx/runtime-tokio-native-tls"] # databases -mysql = ["sqlx/mysql"] -postgres = ["sqlx/postgres"] -sqlite = ["sqlx/sqlite"] +#mysql = ["sqlx/mysql"] +#postgres = ["sqlx/postgres"] +#sqlite = ["sqlx/sqlite"] # workaround for musl + openssl issues openssl-vendored = ["openssl/vendored"] diff --git a/sqlx-cli/src/lib.rs b/sqlx-cli/src/lib.rs index b190ce4fbb..696391b85d 100644 --- a/sqlx-cli/src/lib.rs +++ b/sqlx-cli/src/lib.rs @@ -23,42 +23,24 @@ pub async fn run(opt: Opt) -> Result<()> { source, description, reversible, - } => migrate::add(source.resolve(&migrate.source), &description, reversible).await?, + } => migrate::add(&source, &description, reversible).await?, MigrateCommand::Run { source, dry_run, ignore_missing, connect_opts, - } => { - migrate::run( - source.resolve(&migrate.source), - &connect_opts, - dry_run, - *ignore_missing, - ) - .await? - } + } => migrate::run(&source, &connect_opts, dry_run, *ignore_missing).await?, MigrateCommand::Revert { source, dry_run, ignore_missing, connect_opts, - } => { - migrate::revert( - source.resolve(&migrate.source), - &connect_opts, - dry_run, - *ignore_missing, - ) - .await? - } + } => migrate::revert(&source, &connect_opts, dry_run, *ignore_missing).await?, MigrateCommand::Info { source, connect_opts, - } => migrate::info(source.resolve(&migrate.source), &connect_opts).await?, - MigrateCommand::BuildScript { source, force } => { - migrate::build_script(source.resolve(&migrate.source), force)? - } + } => migrate::info(&source, &connect_opts).await?, + MigrateCommand::BuildScript { source, force } => migrate::build_script(&source, force)?, }, Command::Database(database) => match database.command { @@ -98,6 +80,8 @@ pub async fn run(opt: Opt) -> Result<()> { /// Attempt to connect to the database server, retrying up to `ops.connect_timeout`. async fn connect(opts: &ConnectOpts) -> sqlx::Result { + sqlx::any::install_default_drivers(); + retry_connect_errors(opts, AnyConnection::connect).await } diff --git a/sqlx-cli/src/opt.rs b/sqlx-cli/src/opt.rs index 838fe6b48e..f89ccc0c28 100644 --- a/sqlx-cli/src/opt.rs +++ b/sqlx-cli/src/opt.rs @@ -94,11 +94,6 @@ pub enum DatabaseCommand { /// Group of commands for creating and running migrations. #[derive(Parser, Debug)] pub struct MigrateOpt { - /// Path to folder containing migrations. - /// Warning: deprecated, use --source - #[clap(long, default_value = "migrations")] - pub source: String, - #[clap(subcommand)] pub command: MigrateCommand, } @@ -111,7 +106,7 @@ pub enum MigrateCommand { description: String, #[clap(flatten)] - source: SourceOverride, + source: Source, /// If true, creates a pair of up and down migration files with same version /// else creates a single sql file @@ -122,7 +117,7 @@ pub enum MigrateCommand { /// Run all pending migrations. Run { #[clap(flatten)] - source: SourceOverride, + source: Source, /// List all the migrations to be run without applying #[clap(long)] @@ -138,7 +133,7 @@ pub enum MigrateCommand { /// Revert the latest migration with a down file. Revert { #[clap(flatten)] - source: SourceOverride, + source: Source, /// List the migration to be reverted without applying #[clap(long)] @@ -154,7 +149,7 @@ pub enum MigrateCommand { /// List all available migrations. Info { #[clap(flatten)] - source: SourceOverride, + source: Source, #[clap(flatten)] connect_opts: ConnectOpts, @@ -165,7 +160,7 @@ pub enum MigrateCommand { /// Must be run in a Cargo project root. BuildScript { #[clap(flatten)] - source: SourceOverride, + source: Source, /// Overwrite the build script if it already exists. #[clap(long)] @@ -189,27 +184,6 @@ impl Deref for Source { } } -/// Argument for overriding migration scripts source. -// Note: once `MigrateOpt.source` is removed, usage can be replaced with `Source`. -#[derive(Args, Debug)] -pub struct SourceOverride { - /// Path to folder containing migrations [default: migrations] - #[clap(long)] - source: Option, -} - -impl SourceOverride { - /// Override command's `source` flag value with subcommand's - /// `source` flag value when provided. - #[inline] - pub(super) fn resolve<'a>(&'a self, source: &'a str) -> &'a str { - match self.source { - Some(ref source) => source, - None => source, - } - } -} - /// Argument for the database URL. #[derive(Args, Debug)] pub struct ConnectOpts { diff --git a/sqlx-cli/src/prepare.rs b/sqlx-cli/src/prepare.rs index f934ed3c52..31c804434d 100644 --- a/sqlx-cli/src/prepare.rs +++ b/sqlx-cli/src/prepare.rs @@ -29,12 +29,10 @@ pub async fn run( merge: bool, cargo_args: Vec, ) -> anyhow::Result<()> { - // Ensure the database server is available. - crate::connect(connect_opts).await?.close().await?; + let db = check_backend_and_get_name(connect_opts).await?; let url = &connect_opts.database_url; - let db_kind = get_db_kind(url)?; let data = run_prepare_step(url, merge, cargo_args)?; if data.is_empty() { @@ -48,10 +46,7 @@ pub async fn run( BufWriter::new( File::create("sqlx-data.json").context("failed to create/open `sqlx-data.json`")?, ), - &DataFile { - db: db_kind.to_owned(), - data, - }, + &DataFile { db, data }, ) .context("failed to write to `sqlx-data.json`")?; @@ -68,12 +63,10 @@ pub async fn check( merge: bool, cargo_args: Vec, ) -> anyhow::Result<()> { - // Ensure the database server is available. - crate::connect(connect_opts).await?.close().await?; + let db = check_backend_and_get_name(connect_opts).await?; let url = &connect_opts.database_url; - let db_kind = get_db_kind(url)?; let data = run_prepare_step(url, merge, cargo_args)?; let data_file = File::open("sqlx-data.json").context( @@ -85,11 +78,11 @@ pub async fn check( data: saved_data, } = serde_json::from_reader(BufReader::new(data_file))?; - if db_kind != expected_db { + if db != expected_db { bail!( "saved prepare data is for {}, not {} (inferred from `DATABASE_URL`)", expected_db, - db_kind + db ) } @@ -317,23 +310,14 @@ fn minimal_project_recompile_action(metadata: &Metadata) -> anyhow::Result anyhow::Result<&'static str> { - let options = AnyConnectOptions::from_str(&url)?; - - // these should match the values of `DatabaseExt::NAME` in `sqlx-macros` - match options.kind() { - #[cfg(feature = "postgres")] - AnyKind::Postgres => Ok("PostgreSQL"), - - #[cfg(feature = "mysql")] - AnyKind::MySql => Ok("MySQL"), - - #[cfg(feature = "sqlite")] - AnyKind::Sqlite => Ok("SQLite"), - - #[cfg(feature = "mssql")] - AnyKind::Mssql => Ok("MSSQL"), - } +/// Ensure the database server is available. +/// +/// Returns the `Database::NAME` of the backend on success. +async fn check_backend_and_get_name(opts: &ConnectOpts) -> anyhow::Result { + let conn = crate::connect(opts).await?; + let db = conn.backend_name().to_string(); + conn.close().await?; + Ok(db) } #[cfg(test)] diff --git a/sqlx-core/Cargo.toml b/sqlx-core/Cargo.toml index 38ee9fbd61..8aa4080287 100644 --- a/sqlx-core/Cargo.toml +++ b/sqlx-core/Cargo.toml @@ -1,116 +1,59 @@ [package] name = "sqlx-core" -version = "0.6.2" -repository = "https://github.com/launchbadge/sqlx" description = "Core of SQLx, the rust SQL toolkit. Not intended to be used directly." -license = "MIT OR Apache-2.0" -edition = "2021" -authors = [ - "Ryan Leckey ", - "Austin Bonander ", - "Chloe Ross ", - "Daniel Akhterov ", -] +version.workspace = true +license.workspace = true +edition.workspace = true +authors.workspace = true +repository.workspace = true [package.metadata.docs.rs] -features = ["all-databases", "all-types", "offline", "runtime-tokio-native-tls"] +features = ["offline", "runtime-tokio-native-tls"] [features] -default = ["migrate"] +default = [] migrate = ["sha2", "crc"] -# databases -all-databases = ["postgres", "mysql", "sqlite", "mssql", "any"] -postgres = [ - "md-5", - "sha2", - "base64", - "sha1", - "rand", - "hmac", - "futures-channel/sink", - "futures-util/sink", - "json", - "dirs", - "whoami", - "hkdf" -] -mysql = [ - "sha1", - "sha2", - "generic-array", - "num-bigint", - "digest", - "rand", - "rsa", -] -sqlite = ["libsqlite3-sys", "futures-executor", "flume"] -mssql = ["uuid", "encoding_rs", "regex"] any = [] -# types -all-types = [ - "chrono", - "time", - "bigdecimal", - "decimal", - "ipnetwork", - "mac_address", - "json", - "uuid", - "bit-vec", -] -bigdecimal = ["bigdecimal_", "num-bigint"] -decimal = ["rust_decimal", "num-bigint"] json = ["serde", "serde_json"] -# runtimes -runtime-actix-native-tls = ["runtime-tokio-native-tls"] -runtime-async-std-native-tls = [ - "sqlx-rt/runtime-async-std-native-tls", - "sqlx/runtime-async-std-native-tls", - "_tls-native-tls", - "_rt-async-std", -] -runtime-tokio-native-tls = [ - "sqlx-rt/runtime-tokio-native-tls", - "sqlx/runtime-tokio-native-tls", - "_tls-native-tls", - "_rt-tokio", -] - -runtime-actix-rustls = ['runtime-tokio-rustls'] -runtime-async-std-rustls = [ - "sqlx-rt/runtime-async-std-rustls", - "sqlx/runtime-async-std-rustls", - "_tls-rustls", - "_rt-async-std", -] -runtime-tokio-rustls = [ - "sqlx-rt/runtime-tokio-rustls", - "sqlx/runtime-tokio-rustls", - "_tls-rustls", - "_rt-tokio" -] - # for conditional compilation -_rt-async-std = [] -_rt-tokio = ["tokio-stream"] -_tls-native-tls = [] +_rt-async-std = ["async-std", "async-io"] +_rt-tokio = ["tokio", "tokio-stream"] +_tls-native-tls = ["native-tls"] _tls-rustls = ["rustls", "rustls-pemfile", "webpki-roots"] +_tls-none = [] # support offline/decoupled building (enables serialization of `Describe`) offline = ["serde", "either/serde"] [dependencies] +# Runtimes +async-std = { workspace = true, optional = true } +tokio = { workspace = true, optional = true } + +# TLS +native-tls = { version = "0.2.10", optional = true } + +rustls = { version = "0.20.6", features = ["dangerous_configuration"], optional = true } +rustls-pemfile = { version = "1.0", optional = true } +webpki-roots = { version = "0.22.0", optional = true } + +# Type Integrations +bit-vec = { workspace = true, optional = true } +bigdecimal = { workspace = true, optional = true } +rust_decimal = { workspace = true, optional = true } +time = { workspace = true, optional = true } +ipnetwork = { workspace = true, optional = true } +mac_address = { workspace = true, optional = true } +uuid = { workspace = true, optional = true } + +async-io = { version = "1.9.0", optional = true } paste = "1.0.6" ahash = "0.7.6" atoi = "1.0" -sqlx-rt = { path = "../sqlx-rt", version = "0.6.2" } -base64 = { version = "0.13.0", default-features = false, optional = true, features = ["std"] } -bigdecimal_ = { version = "0.3.0", optional = true, package = "bigdecimal" } -rust_decimal = { version = "1.19.0", optional = true } -bit-vec = { version = "0.6.3", optional = true } + bitflags = { version = "1.3.2", default-features = false } bytes = "1.1.0" byteorder = { version = "1.4.3", default-features = false, features = ["std"] } @@ -118,65 +61,41 @@ chrono = { version = "0.4.19", default-features = false, features = ["clock"], o crc = { version = "3", optional = true } crossbeam-queue = "0.3.2" digest = { version = "0.10.0", default-features = false, optional = true, features = ["std"] } -dirs = { version = "4.0.0", optional = true } encoding_rs = { version = "0.8.30", optional = true } either = "1.6.1" futures-channel = { version = "0.3.19", default-features = false, features = ["sink", "alloc", "std"] } futures-core = { version = "0.3.19", default-features = false } +futures-io = "0.3.24" futures-intrusive = "0.4.0" -futures-util = { version = "0.3.19", default-features = false, features = ["alloc", "sink"] } -# used by the SQLite worker thread to block on the async mutex that locks the database handle -futures-executor = { version = "0.3.19", optional = true } -flume = { version = "0.10.9", optional = true, default-features = false, features = ["async"] } +futures-util = { version = "0.3.19", default-features = false, features = ["alloc", "sink", "io"] } generic-array = { version = "0.14.4", default-features = false, optional = true } hex = "0.4.3" -hmac = { version = "0.12.0", default-features = false, optional = true } -itoa = "1.0.1" -ipnetwork = { version = "0.19.0", default-features = false, optional = true } -mac_address = { version = "1.1.2", default-features = false, optional = true } -libc = "0.2.112" -libsqlite3-sys = { version = "0.25.1", optional = true, default-features = false, features = [ - "pkg-config", - "vcpkg", - "bundled", - "unlock_notify" -] } + log = { version = "0.4.14", default-features = false } -md-5 = { version = "0.10.0", default-features = false, optional = true } memchr = { version = "2.4.1", default-features = false } num-bigint = { version = "0.4.0", default-features = false, optional = true, features = ["std"] } once_cell = "1.9.0" percent-encoding = "2.1.0" -rand = { version = "0.8.4", default-features = false, optional = true, features = ["std", "std_rng"] } regex = { version = "1.5.5", optional = true } rsa = { version = "0.6.0", optional = true } -rustls = { version = "0.20.1", features = ["dangerous_configuration"], optional = true } -rustls-pemfile = { version = "1.0", optional = true } serde = { version = "1.0.132", features = ["derive", "rc"], optional = true } serde_json = { version = "1.0.73", features = ["raw_value"], optional = true } sha1 = { version = "0.10.1", default-features = false, optional = true } sha2 = { version = "0.10.0", default-features = false, optional = true } sqlformat = "0.2.0" thiserror = "1.0.30" -time = { version = "0.3.2", features = ["macros", "formatting", "parsing"], optional = true } tokio-stream = { version = "0.1.8", features = ["fs"], optional = true } smallvec = "1.7.0" url = { version = "2.2.2", default-features = false } -uuid = { version = "1.0", default-features = false, optional = true, features = ["std"] } -webpki-roots = { version = "0.22.0", optional = true } -whoami = { version = "1.2.1", optional = true } -stringprep = "0.1.2" bstr = { version = "0.2.17", default-features = false, features = ["std"], optional = true } -git2 = { version = "0.14", default-features = false, optional = true } hashlink = "0.8.0" # NOTE: *must* remain below 1.7.0 to allow users to avoid the `ahash` cyclic dependency problem by pinning the version # https://github.com/tkaitchuck/aHash/issues/95#issuecomment-874150078 indexmap = "1.6.0" -hkdf = { version = "0.12.0", optional = true } event-listener = "2.5.2" dotenvy = "0.15" [dev-dependencies] -sqlx = { version = "0.6.2", path = "..", features = ["postgres", "sqlite", "mysql"] } +sqlx = { version = "0.6.2", path = "..", features = ["postgres", "sqlite", "mysql", "migrate"] } tokio = { version = "1", features = ["rt"] } diff --git a/sqlx-core/src/acquire.rs b/sqlx-core/src/acquire.rs index 6d8b2d10ed..3bd7698b07 100644 --- a/sqlx-core/src/acquire.rs +++ b/sqlx-core/src/acquire.rs @@ -1,6 +1,7 @@ use crate::database::Database; use crate::error::Error; use crate::pool::{MaybePoolConnection, Pool, PoolConnection}; + use crate::transaction::Transaction; use futures_core::future::BoxFuture; use std::ops::{Deref, DerefMut}; @@ -97,18 +98,18 @@ impl<'a, DB: Database> Acquire<'a> for &'_ Pool { } } -#[allow(unused_macros)] +#[macro_export] macro_rules! impl_acquire { ($DB:ident, $C:ident) => { - impl<'c> crate::acquire::Acquire<'c> for &'c mut $C { + impl<'c> $crate::acquire::Acquire<'c> for &'c mut $C { type Database = $DB; - type Connection = &'c mut <$DB as crate::database::Database>::Connection; + type Connection = &'c mut <$DB as $crate::database::Database>::Connection; #[inline] fn acquire( self, - ) -> futures_core::future::BoxFuture<'c, Result> + ) -> futures_core::future::BoxFuture<'c, Result> { Box::pin(futures_util::future::ok(self)) } @@ -118,59 +119,9 @@ macro_rules! impl_acquire { self, ) -> futures_core::future::BoxFuture< 'c, - Result, crate::error::Error>, - > { - crate::transaction::Transaction::begin(self) - } - } - - impl<'c> crate::acquire::Acquire<'c> for &'c mut crate::pool::PoolConnection<$DB> { - type Database = $DB; - - type Connection = &'c mut <$DB as crate::database::Database>::Connection; - - #[inline] - fn acquire( - self, - ) -> futures_core::future::BoxFuture<'c, Result> - { - Box::pin(futures_util::future::ok(&mut **self)) - } - - #[inline] - fn begin( - self, - ) -> futures_core::future::BoxFuture< - 'c, - Result, crate::error::Error>, - > { - crate::transaction::Transaction::begin(&mut **self) - } - } - - impl<'c, 't> crate::acquire::Acquire<'t> - for &'t mut crate::transaction::Transaction<'c, $DB> - { - type Database = $DB; - - type Connection = &'t mut <$DB as crate::database::Database>::Connection; - - #[inline] - fn acquire( - self, - ) -> futures_core::future::BoxFuture<'t, Result> - { - Box::pin(futures_util::future::ok(&mut **self)) - } - - #[inline] - fn begin( - self, - ) -> futures_core::future::BoxFuture< - 't, - Result, crate::error::Error>, + Result<$crate::transaction::Transaction<'c, $DB>, $crate::error::Error>, > { - crate::transaction::Transaction::begin(&mut **self) + $crate::transaction::Transaction::begin(self) } } }; diff --git a/sqlx-core/src/any/arguments.rs b/sqlx-core/src/any/arguments.rs index 41b0b72946..84195b5c2c 100644 --- a/sqlx-core/src/any/arguments.rs +++ b/sqlx-core/src/any/arguments.rs @@ -1,133 +1,72 @@ -use crate::any::Any; +use crate::any::value::AnyValueKind; +use crate::any::{Any, AnyValueRef}; use crate::arguments::Arguments; use crate::encode::Encode; use crate::types::Type; +use std::borrow::Cow; +use std::marker::PhantomData; -#[derive(Default)] pub struct AnyArguments<'q> { - values: Vec + Send + 'q>>, + #[doc(hidden)] + pub values: AnyArgumentBuffer<'q>, } impl<'q> Arguments<'q> for AnyArguments<'q> { type Database = Any; fn reserve(&mut self, additional: usize, _size: usize) { - self.values.reserve(additional); + self.values.0.reserve(additional); } fn add(&mut self, value: T) where T: 'q + Send + Encode<'q, Self::Database> + Type, { - self.values.push(Box::new(value)); + value.encode(&mut self.values); } } -pub struct AnyArgumentBuffer<'q>(pub(crate) AnyArgumentBufferKind<'q>); +pub struct AnyArgumentBuffer<'q>(#[doc(hidden)] pub Vec>); -pub(crate) enum AnyArgumentBufferKind<'q> { - #[cfg(feature = "postgres")] - Postgres( - crate::postgres::PgArguments, - std::marker::PhantomData<&'q ()>, - ), - - #[cfg(feature = "mysql")] - MySql( - crate::mysql::MySqlArguments, - std::marker::PhantomData<&'q ()>, - ), - - #[cfg(feature = "sqlite")] - Sqlite(crate::sqlite::SqliteArguments<'q>), - - #[cfg(feature = "mssql")] - Mssql( - crate::mssql::MssqlArguments, - std::marker::PhantomData<&'q ()>, - ), -} - -// control flow inferred type bounds would be fun -// the compiler should know the branch is totally unreachable - -#[cfg(feature = "sqlite")] -#[allow(irrefutable_let_patterns)] -impl<'q> From> for crate::sqlite::SqliteArguments<'q> { - fn from(args: AnyArguments<'q>) -> Self { - let mut buf = AnyArgumentBuffer(AnyArgumentBufferKind::Sqlite(Default::default())); - - for value in args.values { - let _ = value.encode_by_ref(&mut buf); - } - - if let AnyArgumentBufferKind::Sqlite(args) = buf.0 { - args - } else { - unreachable!() +impl<'q> Default for AnyArguments<'q> { + fn default() -> Self { + AnyArguments { + values: AnyArgumentBuffer(vec![]), } } } -#[cfg(feature = "mysql")] -#[allow(irrefutable_let_patterns)] -impl<'q> From> for crate::mysql::MySqlArguments { - fn from(args: AnyArguments<'q>) -> Self { - let mut buf = AnyArgumentBuffer(AnyArgumentBufferKind::MySql( - Default::default(), - std::marker::PhantomData, - )); - - for value in args.values { - let _ = value.encode_by_ref(&mut buf); - } - - if let AnyArgumentBufferKind::MySql(args, _) = buf.0 { - args - } else { - unreachable!() - } - } -} - -#[cfg(feature = "mssql")] -#[allow(irrefutable_let_patterns)] -impl<'q> From> for crate::mssql::MssqlArguments { - fn from(args: AnyArguments<'q>) -> Self { - let mut buf = AnyArgumentBuffer(AnyArgumentBufferKind::Mssql( - Default::default(), - std::marker::PhantomData, - )); - - for value in args.values { - let _ = value.encode_by_ref(&mut buf); - } - - if let AnyArgumentBufferKind::Mssql(args, _) = buf.0 { - args - } else { - unreachable!() - } - } -} - -#[cfg(feature = "postgres")] -#[allow(irrefutable_let_patterns)] -impl<'q> From> for crate::postgres::PgArguments { - fn from(args: AnyArguments<'q>) -> Self { - let mut buf = AnyArgumentBuffer(AnyArgumentBufferKind::Postgres( - Default::default(), - std::marker::PhantomData, - )); - - for value in args.values { - let _ = value.encode_by_ref(&mut buf); +impl<'q> AnyArguments<'q> { + #[doc(hidden)] + pub fn convert_to<'a, A: Arguments<'a>>(&'a self) -> A + where + 'q: 'a, + Option: Type + Encode<'a, A::Database>, + bool: Type + Encode<'a, A::Database>, + i16: Type + Encode<'a, A::Database>, + i32: Type + Encode<'a, A::Database>, + i64: Type + Encode<'a, A::Database>, + f32: Type + Encode<'a, A::Database>, + f64: Type + Encode<'a, A::Database>, + &'a str: Type + Encode<'a, A::Database>, + &'a [u8]: Type + Encode<'a, A::Database>, + { + let mut out = A::default(); + + for arg in &self.values.0 { + match arg { + AnyValueKind::Null => out.add(Option::::None), + AnyValueKind::Bool(b) => out.add(b), + AnyValueKind::SmallInt(i) => out.add(i), + AnyValueKind::Integer(i) => out.add(i), + AnyValueKind::BigInt(i) => out.add(i), + AnyValueKind::Real(r) => out.add(r), + AnyValueKind::Double(d) => out.add(d), + AnyValueKind::Text(t) => out.add(&**t), + AnyValueKind::Blob(b) => out.add(&**b), + } } - if let AnyArgumentBufferKind::Postgres(args, _) = buf.0 { - args - } else { - unreachable!() - } + out } } diff --git a/sqlx-core/src/any/column.rs b/sqlx-core/src/any/column.rs index 22049033a8..ffe24ab0fd 100644 --- a/sqlx-core/src/any/column.rs +++ b/sqlx-core/src/any/column.rs @@ -1,443 +1,31 @@ -use crate::any::{Any, AnyTypeInfo}; +use crate::any::{Any, AnyTypeInfo, AnyValue}; use crate::column::{Column, ColumnIndex}; - -#[cfg(feature = "postgres")] -use crate::postgres::{PgColumn, PgRow, PgStatement}; - -#[cfg(feature = "mysql")] -use crate::mysql::{MySqlColumn, MySqlRow, MySqlStatement}; - -#[cfg(feature = "sqlite")] -use crate::sqlite::{SqliteColumn, SqliteRow, SqliteStatement}; - -#[cfg(feature = "mssql")] -use crate::mssql::{MssqlColumn, MssqlRow, MssqlStatement}; +use crate::ext::ustr::UStr; #[derive(Debug, Clone)] pub struct AnyColumn { - pub(crate) kind: AnyColumnKind, - pub(crate) type_info: AnyTypeInfo, -} + // NOTE: these fields are semver-exempt. See crate root docs for details. + #[doc(hidden)] + pub ordinal: usize, -impl crate::column::private_column::Sealed for AnyColumn {} + #[doc(hidden)] + pub name: UStr, -#[derive(Debug, Clone)] -pub(crate) enum AnyColumnKind { - #[cfg(feature = "postgres")] - Postgres(PgColumn), - - #[cfg(feature = "mysql")] - MySql(MySqlColumn), - - #[cfg(feature = "sqlite")] - Sqlite(SqliteColumn), - - #[cfg(feature = "mssql")] - Mssql(MssqlColumn), + #[doc(hidden)] + pub type_info: AnyTypeInfo, } - impl Column for AnyColumn { type Database = Any; fn ordinal(&self) -> usize { - match &self.kind { - #[cfg(feature = "postgres")] - AnyColumnKind::Postgres(row) => row.ordinal(), - - #[cfg(feature = "mysql")] - AnyColumnKind::MySql(row) => row.ordinal(), - - #[cfg(feature = "sqlite")] - AnyColumnKind::Sqlite(row) => row.ordinal(), - - #[cfg(feature = "mssql")] - AnyColumnKind::Mssql(row) => row.ordinal(), - } + self.ordinal } fn name(&self) -> &str { - match &self.kind { - #[cfg(feature = "postgres")] - AnyColumnKind::Postgres(row) => row.name(), - - #[cfg(feature = "mysql")] - AnyColumnKind::MySql(row) => row.name(), - - #[cfg(feature = "sqlite")] - AnyColumnKind::Sqlite(row) => row.name(), - - #[cfg(feature = "mssql")] - AnyColumnKind::Mssql(row) => row.name(), - } + &self.name } fn type_info(&self) -> &AnyTypeInfo { &self.type_info } } - -// FIXME: Find a nice way to auto-generate the below or petition Rust to add support for #[cfg] -// to trait bounds - -// all 4 - -#[cfg(all( - feature = "postgres", - feature = "mysql", - feature = "mssql", - feature = "sqlite" -))] -pub trait AnyColumnIndex: - ColumnIndex - + for<'q> ColumnIndex> - + ColumnIndex - + for<'q> ColumnIndex> - + ColumnIndex - + for<'q> ColumnIndex> - + ColumnIndex - + for<'q> ColumnIndex> -{ -} - -#[cfg(all( - feature = "postgres", - feature = "mysql", - feature = "mssql", - feature = "sqlite" -))] -impl AnyColumnIndex for I where - I: ColumnIndex - + for<'q> ColumnIndex> - + ColumnIndex - + for<'q> ColumnIndex> - + ColumnIndex - + for<'q> ColumnIndex> - + ColumnIndex - + for<'q> ColumnIndex> -{ -} - -// only 3 (4) - -#[cfg(all( - not(feature = "mssql"), - all(feature = "postgres", feature = "mysql", feature = "sqlite") -))] -pub trait AnyColumnIndex: - ColumnIndex - + for<'q> ColumnIndex> - + ColumnIndex - + for<'q> ColumnIndex> - + ColumnIndex - + for<'q> ColumnIndex> -{ -} - -#[cfg(all( - not(feature = "mssql"), - all(feature = "postgres", feature = "mysql", feature = "sqlite") -))] -impl AnyColumnIndex for I where - I: ColumnIndex - + for<'q> ColumnIndex> - + ColumnIndex - + for<'q> ColumnIndex> - + ColumnIndex - + for<'q> ColumnIndex> -{ -} - -#[cfg(all( - not(feature = "mysql"), - all(feature = "postgres", feature = "mssql", feature = "sqlite") -))] -pub trait AnyColumnIndex: - ColumnIndex - + for<'q> ColumnIndex> - + ColumnIndex - + for<'q> ColumnIndex> - + ColumnIndex - + for<'q> ColumnIndex> -{ -} - -#[cfg(all( - not(feature = "mysql"), - all(feature = "postgres", feature = "mssql", feature = "sqlite") -))] -impl AnyColumnIndex for I where - I: ColumnIndex - + for<'q> ColumnIndex> - + ColumnIndex - + for<'q> ColumnIndex> - + ColumnIndex - + for<'q> ColumnIndex> -{ -} - -#[cfg(all( - not(feature = "sqlite"), - all(feature = "postgres", feature = "mysql", feature = "mssql") -))] -pub trait AnyColumnIndex: - ColumnIndex - + for<'q> ColumnIndex> - + ColumnIndex - + for<'q> ColumnIndex> - + ColumnIndex - + for<'q> ColumnIndex> -{ -} - -#[cfg(all( - not(feature = "sqlite"), - all(feature = "postgres", feature = "mysql", feature = "mssql") -))] -impl AnyColumnIndex for I where - I: ColumnIndex - + for<'q> ColumnIndex> - + ColumnIndex - + for<'q> ColumnIndex> - + ColumnIndex - + for<'q> ColumnIndex> -{ -} - -#[cfg(all( - not(feature = "postgres"), - all(feature = "sqlite", feature = "mysql", feature = "mssql") -))] -pub trait AnyColumnIndex: - ColumnIndex - + for<'q> ColumnIndex> - + ColumnIndex - + for<'q> ColumnIndex> - + ColumnIndex - + for<'q> ColumnIndex> -{ -} - -#[cfg(all( - not(feature = "postgres"), - all(feature = "sqlite", feature = "mysql", feature = "mssql") -))] -impl AnyColumnIndex for I where - I: ColumnIndex - + for<'q> ColumnIndex> - + ColumnIndex - + for<'q> ColumnIndex> - + ColumnIndex - + for<'q> ColumnIndex> -{ -} - -// only 2 (6) - -#[cfg(all( - not(any(feature = "mssql", feature = "sqlite")), - all(feature = "postgres", feature = "mysql") -))] -pub trait AnyColumnIndex: - ColumnIndex - + for<'q> ColumnIndex> - + ColumnIndex - + for<'q> ColumnIndex> -{ -} - -#[cfg(all( - not(any(feature = "mssql", feature = "sqlite")), - all(feature = "postgres", feature = "mysql") -))] -impl AnyColumnIndex for I where - I: ColumnIndex - + for<'q> ColumnIndex> - + ColumnIndex - + for<'q> ColumnIndex> -{ -} - -#[cfg(all( - not(any(feature = "mysql", feature = "sqlite")), - all(feature = "postgres", feature = "mssql") -))] -pub trait AnyColumnIndex: - ColumnIndex - + for<'q> ColumnIndex> - + ColumnIndex - + for<'q> ColumnIndex> -{ -} - -#[cfg(all( - not(any(feature = "mysql", feature = "sqlite")), - all(feature = "postgres", feature = "mssql") -))] -impl AnyColumnIndex for I where - I: ColumnIndex - + for<'q> ColumnIndex> - + ColumnIndex - + for<'q> ColumnIndex> -{ -} - -#[cfg(all( - not(any(feature = "mysql", feature = "mssql")), - all(feature = "postgres", feature = "sqlite") -))] -pub trait AnyColumnIndex: - ColumnIndex - + for<'q> ColumnIndex> - + ColumnIndex - + for<'q> ColumnIndex> -{ -} - -#[cfg(all( - not(any(feature = "mysql", feature = "mssql")), - all(feature = "postgres", feature = "sqlite") -))] -impl AnyColumnIndex for I where - I: ColumnIndex - + for<'q> ColumnIndex> - + ColumnIndex - + for<'q> ColumnIndex> -{ -} - -#[cfg(all( - not(any(feature = "postgres", feature = "sqlite")), - all(feature = "mssql", feature = "mysql") -))] -pub trait AnyColumnIndex: - ColumnIndex - + for<'q> ColumnIndex> - + ColumnIndex - + for<'q> ColumnIndex> -{ -} - -#[cfg(all( - not(any(feature = "postgres", feature = "sqlite")), - all(feature = "mssql", feature = "mysql") -))] -impl AnyColumnIndex for I where - I: ColumnIndex - + for<'q> ColumnIndex> - + ColumnIndex - + for<'q> ColumnIndex> -{ -} - -#[cfg(all( - not(any(feature = "postgres", feature = "mysql")), - all(feature = "mssql", feature = "sqlite") -))] -pub trait AnyColumnIndex: - ColumnIndex - + for<'q> ColumnIndex> - + ColumnIndex - + for<'q> ColumnIndex> -{ -} - -#[cfg(all( - not(any(feature = "postgres", feature = "mysql")), - all(feature = "mssql", feature = "sqlite") -))] -impl AnyColumnIndex for I where - I: ColumnIndex - + for<'q> ColumnIndex> - + ColumnIndex - + for<'q> ColumnIndex> -{ -} - -#[cfg(all( - not(any(feature = "postgres", feature = "mssql")), - all(feature = "mysql", feature = "sqlite") -))] -pub trait AnyColumnIndex: - ColumnIndex - + for<'q> ColumnIndex> - + ColumnIndex - + for<'q> ColumnIndex> -{ -} - -#[cfg(all( - not(any(feature = "postgres", feature = "mssql")), - all(feature = "mysql", feature = "sqlite") -))] -impl AnyColumnIndex for I where - I: ColumnIndex - + for<'q> ColumnIndex> - + ColumnIndex - + for<'q> ColumnIndex> -{ -} - -// only 1 (4) - -#[cfg(all( - not(any(feature = "mysql", feature = "mssql", feature = "sqlite")), - feature = "postgres" -))] -pub trait AnyColumnIndex: ColumnIndex + for<'q> ColumnIndex> {} - -#[cfg(all( - not(any(feature = "mysql", feature = "mssql", feature = "sqlite")), - feature = "postgres" -))] -impl AnyColumnIndex for I where - I: ColumnIndex + for<'q> ColumnIndex> -{ -} - -#[cfg(all( - not(any(feature = "postgres", feature = "mssql", feature = "sqlite")), - feature = "mysql" -))] -pub trait AnyColumnIndex: ColumnIndex + for<'q> ColumnIndex> {} - -#[cfg(all( - not(any(feature = "postgres", feature = "mssql", feature = "sqlite")), - feature = "mysql" -))] -impl AnyColumnIndex for I where - I: ColumnIndex + for<'q> ColumnIndex> -{ -} - -#[cfg(all( - not(any(feature = "mysql", feature = "postgres", feature = "sqlite")), - feature = "mssql" -))] -pub trait AnyColumnIndex: ColumnIndex + for<'q> ColumnIndex> {} - -#[cfg(all( - not(any(feature = "mysql", feature = "postgres", feature = "sqlite")), - feature = "mssql" -))] -impl AnyColumnIndex for I where - I: ColumnIndex + for<'q> ColumnIndex> -{ -} - -#[cfg(all( - not(any(feature = "mysql", feature = "mssql", feature = "postgres")), - feature = "sqlite" -))] -pub trait AnyColumnIndex: - ColumnIndex + for<'q> ColumnIndex> -{ -} - -#[cfg(all( - not(any(feature = "mysql", feature = "mssql", feature = "postgres")), - feature = "sqlite" -))] -impl AnyColumnIndex for I where - I: ColumnIndex + for<'q> ColumnIndex> -{ -} diff --git a/sqlx-core/src/any/connection/backend.rs b/sqlx-core/src/any/connection/backend.rs new file mode 100644 index 0000000000..cd8fec29d1 --- /dev/null +++ b/sqlx-core/src/any/connection/backend.rs @@ -0,0 +1,87 @@ +use crate::any::{Any, AnyArguments, AnyQueryResult, AnyRow, AnyStatement, AnyTypeInfo}; +use crate::describe::Describe; +use crate::transaction::Transaction; +use either::Either; +use futures_core::future::BoxFuture; +use futures_core::stream::BoxStream; +use std::fmt::Debug; + +pub trait AnyConnectionBackend: std::any::Any + Debug + Send + 'static { + /// The backend name. + fn name(&self) -> &str; + + /// 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 backend + /// will be faster at cleaning up resources. + fn close(self: Box) -> BoxFuture<'static, crate::Result<()>>; + + /// Immediately close the connection without sending a graceful shutdown. + /// + /// This should still at least send a TCP `FIN` frame to let the server know we're dying. + #[doc(hidden)] + fn close_hard(self: Box) -> BoxFuture<'static, crate::Result<()>>; + + /// Checks if a connection to the database is still valid. + fn ping(&mut self) -> BoxFuture<'_, crate::Result<()>>; + + /// Begin a new transaction or establish a savepoint within the active transaction. + /// + /// Returns a [`Transaction`] for controlling and tracking the new transaction. + fn begin(&mut self) -> BoxFuture<'_, crate::Result<()>>; + + fn commit(&mut self) -> BoxFuture<'_, crate::Result<()>>; + + fn rollback(&mut self) -> BoxFuture<'_, crate::Result<()>>; + + fn start_rollback(&mut self); + + /// The number of statements currently cached in the connection. + fn cached_statements_size(&self) -> usize { + 0 + } + + /// Removes all statements from the cache, closing them on the server if + /// needed. + fn clear_cached_statements(&mut self) -> BoxFuture<'_, crate::Result<()>> { + Box::pin(async move { Ok(()) }) + } + + #[doc(hidden)] + fn flush(&mut self) -> BoxFuture<'_, crate::Result<()>>; + + #[doc(hidden)] + fn should_flush(&self) -> bool; + + #[cfg(feature = "migrate")] + fn as_migrate(&mut self) -> crate::Result<&mut (dyn crate::migrate::Migrate + Send + 'static)> { + Err(crate::Error::Configuration( + format!( + "{} driver does not support migrations or `migrate` feature was not enabled", + self.name() + ) + .into(), + )) + } + + fn fetch_many<'q>( + &'q mut self, + query: &'q str, + arguments: Option>, + ) -> BoxStream<'q, crate::Result>>; + + fn fetch_optional<'q>( + &'q mut self, + query: &'q str, + arguments: Option>, + ) -> BoxFuture<'q, crate::Result>>; + + fn prepare_with<'c, 'q: 'c>( + &'c mut self, + sql: &'q str, + parameters: &[AnyTypeInfo], + ) -> BoxFuture<'c, crate::Result>>; + + fn describe<'q>(&'q mut self, sql: &'q str) -> BoxFuture<'q, crate::Result>>; +} diff --git a/sqlx-core/src/any/connection/establish.rs b/sqlx-core/src/any/connection/establish.rs index 290a499cdd..dac9ab093d 100644 --- a/sqlx-core/src/any/connection/establish.rs +++ b/sqlx-core/src/any/connection/establish.rs @@ -1,40 +1,11 @@ -use crate::any::connection::AnyConnectionKind; -use crate::any::options::{AnyConnectOptions, AnyConnectOptionsKind}; +use crate::any::options::AnyConnectOptions; use crate::any::AnyConnection; use crate::connection::Connection; use crate::error::Error; impl AnyConnection { pub(crate) async fn establish(options: &AnyConnectOptions) -> Result { - match &options.0 { - #[cfg(feature = "mysql")] - AnyConnectOptionsKind::MySql(options) => { - crate::mysql::MySqlConnection::connect_with(options) - .await - .map(AnyConnectionKind::MySql) - } - - #[cfg(feature = "postgres")] - AnyConnectOptionsKind::Postgres(options) => { - crate::postgres::PgConnection::connect_with(options) - .await - .map(AnyConnectionKind::Postgres) - } - - #[cfg(feature = "sqlite")] - AnyConnectOptionsKind::Sqlite(options) => { - crate::sqlite::SqliteConnection::connect_with(options) - .await - .map(AnyConnectionKind::Sqlite) - } - - #[cfg(feature = "mssql")] - AnyConnectOptionsKind::Mssql(options) => { - crate::mssql::MssqlConnection::connect_with(options) - .await - .map(AnyConnectionKind::Mssql) - } - } - .map(AnyConnection) + let driver = crate::any::driver::from_url(&options.database_url)?; + (driver.connect)(options).await } } diff --git a/sqlx-core/src/any/connection/executor.rs b/sqlx-core/src/any/connection/executor.rs index 560d818010..292abbba64 100644 --- a/sqlx-core/src/any/connection/executor.rs +++ b/sqlx-core/src/any/connection/executor.rs @@ -1,4 +1,3 @@ -use crate::any::connection::AnyConnectionKind; use crate::any::{ Any, AnyColumn, AnyConnection, AnyQueryResult, AnyRow, AnyStatement, AnyTypeInfo, }; @@ -20,36 +19,10 @@ impl<'c> Executor<'c> for &'c mut AnyConnection { ) -> BoxStream<'e, Result, Error>> where 'c: 'e, - E: Execute<'q, Self::Database>, + E: Execute<'q, Any>, { let arguments = query.take_arguments(); - let query = query.sql(); - - match &mut self.0 { - #[cfg(feature = "postgres")] - AnyConnectionKind::Postgres(conn) => conn - .fetch_many((query, arguments.map(Into::into))) - .map_ok(|v| v.map_right(Into::into).map_left(Into::into)) - .boxed(), - - #[cfg(feature = "mysql")] - AnyConnectionKind::MySql(conn) => conn - .fetch_many((query, arguments.map(Into::into))) - .map_ok(|v| v.map_right(Into::into).map_left(Into::into)) - .boxed(), - - #[cfg(feature = "sqlite")] - AnyConnectionKind::Sqlite(conn) => conn - .fetch_many((query, arguments.map(Into::into))) - .map_ok(|v| v.map_right(Into::into).map_left(Into::into)) - .boxed(), - - #[cfg(feature = "mssql")] - AnyConnectionKind::Mssql(conn) => conn - .fetch_many((query, arguments.map(Into::into))) - .map_ok(|v| v.map_right(Into::into).map_left(Into::into)) - .boxed(), - } + self.backend.fetch_many(query.sql(), arguments) } fn fetch_optional<'e, 'q: 'e, E: 'q>( @@ -61,61 +34,18 @@ impl<'c> Executor<'c> for &'c mut AnyConnection { E: Execute<'q, Self::Database>, { let arguments = query.take_arguments(); - let query = query.sql(); - - Box::pin(async move { - Ok(match &mut self.0 { - #[cfg(feature = "postgres")] - AnyConnectionKind::Postgres(conn) => conn - .fetch_optional((query, arguments.map(Into::into))) - .await? - .map(Into::into), - - #[cfg(feature = "mysql")] - AnyConnectionKind::MySql(conn) => conn - .fetch_optional((query, arguments.map(Into::into))) - .await? - .map(Into::into), - - #[cfg(feature = "sqlite")] - AnyConnectionKind::Sqlite(conn) => conn - .fetch_optional((query, arguments.map(Into::into))) - .await? - .map(Into::into), - - #[cfg(feature = "mssql")] - AnyConnectionKind::Mssql(conn) => conn - .fetch_optional((query, arguments.map(Into::into))) - .await? - .map(Into::into), - }) - }) + self.backend.fetch_optional(query.sql(), arguments) } fn prepare_with<'e, 'q: 'e>( self, sql: &'q str, - _parameters: &[AnyTypeInfo], + parameters: &[AnyTypeInfo], ) -> BoxFuture<'e, Result, Error>> where 'c: 'e, { - Box::pin(async move { - Ok(match &mut self.0 { - // To match other databases here, we explicitly ignore the parameter types - #[cfg(feature = "postgres")] - AnyConnectionKind::Postgres(conn) => conn.prepare(sql).await.map(Into::into)?, - - #[cfg(feature = "mysql")] - AnyConnectionKind::MySql(conn) => conn.prepare(sql).await.map(Into::into)?, - - #[cfg(feature = "sqlite")] - AnyConnectionKind::Sqlite(conn) => conn.prepare(sql).await.map(Into::into)?, - - #[cfg(feature = "mssql")] - AnyConnectionKind::Mssql(conn) => conn.prepare(sql).await.map(Into::into)?, - }) - }) + self.backend.prepare_with(sql, parameters) } fn describe<'e, 'q: 'e>( @@ -125,21 +55,7 @@ impl<'c> Executor<'c> for &'c mut AnyConnection { where 'c: 'e, { - Box::pin(async move { - Ok(match &mut self.0 { - #[cfg(feature = "postgres")] - AnyConnectionKind::Postgres(conn) => conn.describe(sql).await.map(map_describe)?, - - #[cfg(feature = "mysql")] - AnyConnectionKind::MySql(conn) => conn.describe(sql).await.map(map_describe)?, - - #[cfg(feature = "sqlite")] - AnyConnectionKind::Sqlite(conn) => conn.describe(sql).await.map(map_describe)?, - - #[cfg(feature = "mssql")] - AnyConnectionKind::Mssql(conn) => conn.describe(sql).await.map(map_describe)?, - }) - }) + self.backend.describe(sql) } } diff --git a/sqlx-core/src/any/connection/mod.rs b/sqlx-core/src/any/connection/mod.rs index f057551db5..9d05f8e827 100644 --- a/sqlx-core/src/any/connection/mod.rs +++ b/sqlx-core/src/any/connection/mod.rs @@ -1,22 +1,17 @@ use futures_core::future::BoxFuture; +use std::marker::PhantomData; +use url::Url; use crate::any::{Any, AnyConnectOptions, AnyKind}; -use crate::connection::Connection; +use crate::connection::{ConnectOptions, Connection}; use crate::error::Error; -#[cfg(feature = "postgres")] -use crate::postgres; +use crate::database::Database; +pub use backend::AnyConnectionBackend; -#[cfg(feature = "sqlite")] -use crate::sqlite; - -#[cfg(feature = "mssql")] -use crate::mssql; - -#[cfg(feature = "mysql")] -use crate::mysql; use crate::transaction::Transaction; +mod backend; mod establish; mod executor; @@ -30,89 +25,41 @@ mod executor; /// sqlite://a.sqlite /// ``` #[derive(Debug)] -pub struct AnyConnection(pub(super) AnyConnectionKind); - -#[derive(Debug)] -// Used internally in `sqlx-macros` -#[doc(hidden)] -pub enum AnyConnectionKind { - #[cfg(feature = "postgres")] - Postgres(postgres::PgConnection), - - #[cfg(feature = "mssql")] - Mssql(mssql::MssqlConnection), - - #[cfg(feature = "mysql")] - MySql(mysql::MySqlConnection), - - #[cfg(feature = "sqlite")] - Sqlite(sqlite::SqliteConnection), -} - -impl AnyConnectionKind { - pub fn kind(&self) -> AnyKind { - match self { - #[cfg(feature = "postgres")] - AnyConnectionKind::Postgres(_) => AnyKind::Postgres, - - #[cfg(feature = "mysql")] - AnyConnectionKind::MySql(_) => AnyKind::MySql, - - #[cfg(feature = "sqlite")] - AnyConnectionKind::Sqlite(_) => AnyKind::Sqlite, - - #[cfg(feature = "mssql")] - AnyConnectionKind::Mssql(_) => AnyKind::Mssql, - } - } +pub struct AnyConnection { + pub(crate) backend: Box, } impl AnyConnection { - pub fn kind(&self) -> AnyKind { - self.0.kind() + /// Returns the name of the database backend in use (e.g. PostgreSQL, MySQL, SQLite, etc.) + pub fn backend_name(&self) -> &str { + self.backend.name() } - // Used internally in `sqlx-macros` - #[doc(hidden)] - pub fn private_get_mut(&mut self) -> &mut AnyConnectionKind { - &mut self.0 - } -} - -macro_rules! delegate_to { - ($self:ident.$method:ident($($arg:ident),*)) => { - match &$self.0 { - #[cfg(feature = "postgres")] - AnyConnectionKind::Postgres(conn) => conn.$method($($arg),*), - - #[cfg(feature = "mysql")] - AnyConnectionKind::MySql(conn) => conn.$method($($arg),*), - - #[cfg(feature = "sqlite")] - AnyConnectionKind::Sqlite(conn) => conn.$method($($arg),*), - - #[cfg(feature = "mssql")] - AnyConnectionKind::Mssql(conn) => conn.$method($($arg),*), - } - }; -} - -macro_rules! delegate_to_mut { - ($self:ident.$method:ident($($arg:ident),*)) => { - match &mut $self.0 { - #[cfg(feature = "postgres")] - AnyConnectionKind::Postgres(conn) => conn.$method($($arg),*), + pub(crate) fn connect( + options: &AnyConnectOptions, + ) -> BoxFuture<'_, crate::Result> + where + DB::Connection: AnyConnectionBackend, + ::Options: + for<'a> TryFrom<&'a AnyConnectOptions, Error = Error>, + { + let res = TryFrom::try_from(options); - #[cfg(feature = "mysql")] - AnyConnectionKind::MySql(conn) => conn.$method($($arg),*), + Box::pin(async { + let options: ::Options = res?; - #[cfg(feature = "sqlite")] - AnyConnectionKind::Sqlite(conn) => conn.$method($($arg),*), + Ok(AnyConnection { + backend: Box::new(options.connect().await?), + }) + }) + } - #[cfg(feature = "mssql")] - AnyConnectionKind::Mssql(conn) => conn.$method($($arg),*), - } - }; + #[cfg(feature = "migrate")] + pub(crate) fn get_migrate( + &mut self, + ) -> crate::Result<&mut (dyn crate::migrate::Migrate + Send + 'static)> { + self.backend.as_migrate() + } } impl Connection for AnyConnection { @@ -121,39 +68,15 @@ impl Connection for AnyConnection { type Options = AnyConnectOptions; fn close(self) -> BoxFuture<'static, Result<(), Error>> { - match self.0 { - #[cfg(feature = "postgres")] - AnyConnectionKind::Postgres(conn) => conn.close(), - - #[cfg(feature = "mysql")] - AnyConnectionKind::MySql(conn) => conn.close(), - - #[cfg(feature = "sqlite")] - AnyConnectionKind::Sqlite(conn) => conn.close(), - - #[cfg(feature = "mssql")] - AnyConnectionKind::Mssql(conn) => conn.close(), - } + self.backend.close() } fn close_hard(self) -> BoxFuture<'static, Result<(), Error>> { - match self.0 { - #[cfg(feature = "postgres")] - AnyConnectionKind::Postgres(conn) => conn.close_hard(), - - #[cfg(feature = "mysql")] - AnyConnectionKind::MySql(conn) => conn.close_hard(), - - #[cfg(feature = "sqlite")] - AnyConnectionKind::Sqlite(conn) => conn.close_hard(), - - #[cfg(feature = "mssql")] - AnyConnectionKind::Mssql(conn) => conn.close_hard(), - } + self.backend.close() } fn ping(&mut self) -> BoxFuture<'_, Result<(), Error>> { - delegate_to_mut!(self.ping()) + self.backend.ping() } fn begin(&mut self) -> BoxFuture<'_, Result, Error>> @@ -164,74 +87,20 @@ impl Connection for AnyConnection { } fn cached_statements_size(&self) -> usize { - match &self.0 { - #[cfg(feature = "postgres")] - AnyConnectionKind::Postgres(conn) => conn.cached_statements_size(), - - #[cfg(feature = "mysql")] - AnyConnectionKind::MySql(conn) => conn.cached_statements_size(), - - #[cfg(feature = "sqlite")] - AnyConnectionKind::Sqlite(conn) => conn.cached_statements_size(), - - // no cache - #[cfg(feature = "mssql")] - AnyConnectionKind::Mssql(_) => 0, - } + self.backend.cached_statements_size() } - fn clear_cached_statements(&mut self) -> BoxFuture<'_, Result<(), Error>> { - match &mut self.0 { - #[cfg(feature = "postgres")] - AnyConnectionKind::Postgres(conn) => conn.clear_cached_statements(), - - #[cfg(feature = "mysql")] - AnyConnectionKind::MySql(conn) => conn.clear_cached_statements(), - - #[cfg(feature = "sqlite")] - AnyConnectionKind::Sqlite(conn) => conn.clear_cached_statements(), - - // no cache - #[cfg(feature = "mssql")] - AnyConnectionKind::Mssql(_) => Box::pin(futures_util::future::ok(())), - } + fn clear_cached_statements(&mut self) -> BoxFuture<'_, crate::Result<()>> { + self.backend.clear_cached_statements() } #[doc(hidden)] fn flush(&mut self) -> BoxFuture<'_, Result<(), Error>> { - delegate_to_mut!(self.flush()) + self.backend.flush() } #[doc(hidden)] fn should_flush(&self) -> bool { - delegate_to!(self.should_flush()) - } -} - -#[cfg(feature = "postgres")] -impl From for AnyConnection { - fn from(conn: postgres::PgConnection) -> Self { - AnyConnection(AnyConnectionKind::Postgres(conn)) - } -} - -#[cfg(feature = "mssql")] -impl From for AnyConnection { - fn from(conn: mssql::MssqlConnection) -> Self { - AnyConnection(AnyConnectionKind::Mssql(conn)) - } -} - -#[cfg(feature = "mysql")] -impl From for AnyConnection { - fn from(conn: mysql::MySqlConnection) -> Self { - AnyConnection(AnyConnectionKind::MySql(conn)) - } -} - -#[cfg(feature = "sqlite")] -impl From for AnyConnection { - fn from(conn: sqlite::SqliteConnection) -> Self { - AnyConnection(AnyConnectionKind::Sqlite(conn)) + self.backend.should_flush() } } diff --git a/sqlx-core/src/any/database.rs b/sqlx-core/src/any/database.rs index 6e79c400c6..a7b784e47d 100644 --- a/sqlx-core/src/any/database.rs +++ b/sqlx-core/src/any/database.rs @@ -3,6 +3,7 @@ use crate::any::{ AnyStatement, AnyTransactionManager, AnyTypeInfo, AnyValue, AnyValueRef, }; use crate::database::{Database, HasArguments, HasStatement, HasStatementCache, HasValueRef}; +use std::marker::PhantomData; /// Opaque database driver. Capable of being used in place of any SQLx database driver. The actual /// driver used will be selected at runtime, from the connection url. @@ -23,6 +24,9 @@ impl Database for Any { type TypeInfo = AnyTypeInfo; type Value = AnyValue; + const NAME: &'static str = "Any"; + + const URL_SCHEMES: &'static [&'static str] = &[]; } impl<'r> HasValueRef<'r> for Any { diff --git a/sqlx-core/src/any/decode.rs b/sqlx-core/src/any/decode.rs deleted file mode 100644 index 28d1872f6e..0000000000 --- a/sqlx-core/src/any/decode.rs +++ /dev/null @@ -1,363 +0,0 @@ -use crate::decode::Decode; -use crate::types::Type; - -#[cfg(feature = "postgres")] -use crate::postgres::Postgres; - -#[cfg(feature = "mysql")] -use crate::mysql::MySql; - -#[cfg(feature = "mssql")] -use crate::mssql::Mssql; - -#[cfg(feature = "sqlite")] -use crate::sqlite::Sqlite; - -// Implements Decode for any T where T supports Decode for any database that has support currently -// compiled into SQLx -macro_rules! impl_any_decode { - ($ty:ty) => { - impl<'r> crate::decode::Decode<'r, crate::any::Any> for $ty - where - $ty: crate::any::AnyDecode<'r>, - { - fn decode( - value: crate::any::AnyValueRef<'r>, - ) -> Result { - match value.kind { - #[cfg(feature = "mysql")] - crate::any::value::AnyValueRefKind::MySql(value) => { - <$ty as crate::decode::Decode<'r, crate::mysql::MySql>>::decode(value) - } - - #[cfg(feature = "sqlite")] - crate::any::value::AnyValueRefKind::Sqlite(value) => { - <$ty as crate::decode::Decode<'r, crate::sqlite::Sqlite>>::decode(value) - } - - #[cfg(feature = "mssql")] - crate::any::value::AnyValueRefKind::Mssql(value) => { - <$ty as crate::decode::Decode<'r, crate::mssql::Mssql>>::decode(value) - } - - #[cfg(feature = "postgres")] - crate::any::value::AnyValueRefKind::Postgres(value) => { - <$ty as crate::decode::Decode<'r, crate::postgres::Postgres>>::decode(value) - } - } - } - } - }; -} - -// FIXME: Find a nice way to auto-generate the below or petition Rust to add support for #[cfg] -// to trait bounds - -// all 4 - -#[cfg(all( - feature = "postgres", - feature = "mysql", - feature = "mssql", - feature = "sqlite" -))] -pub trait AnyDecode<'r>: - Decode<'r, Postgres> - + Type - + Decode<'r, MySql> - + Type - + Decode<'r, Mssql> - + Type - + Decode<'r, Sqlite> - + Type -{ -} - -#[cfg(all( - feature = "postgres", - feature = "mysql", - feature = "mssql", - feature = "sqlite" -))] -impl<'r, T> AnyDecode<'r> for T where - T: Decode<'r, Postgres> - + Type - + Decode<'r, MySql> - + Type - + Decode<'r, Mssql> - + Type - + Decode<'r, Sqlite> - + Type -{ -} - -// only 3 (4) - -#[cfg(all( - not(feature = "mssql"), - all(feature = "postgres", feature = "mysql", feature = "sqlite") -))] -pub trait AnyDecode<'r>: - Decode<'r, Postgres> - + Type - + Decode<'r, MySql> - + Type - + Decode<'r, Sqlite> - + Type -{ -} - -#[cfg(all( - not(feature = "mssql"), - all(feature = "postgres", feature = "mysql", feature = "sqlite") -))] -impl<'r, T> AnyDecode<'r> for T where - T: Decode<'r, Postgres> - + Type - + Decode<'r, MySql> - + Type - + Decode<'r, Sqlite> - + Type -{ -} - -#[cfg(all( - not(feature = "mysql"), - all(feature = "postgres", feature = "mssql", feature = "sqlite") -))] -pub trait AnyDecode<'r>: - Decode<'r, Postgres> - + Type - + Decode<'r, Mssql> - + Type - + Decode<'r, Sqlite> - + Type -{ -} - -#[cfg(all( - not(feature = "mysql"), - all(feature = "postgres", feature = "mssql", feature = "sqlite") -))] -impl<'r, T> AnyDecode<'r> for T where - T: Decode<'r, Postgres> - + Type - + Decode<'r, Mssql> - + Type - + Decode<'r, Sqlite> - + Type -{ -} - -#[cfg(all( - not(feature = "sqlite"), - all(feature = "postgres", feature = "mysql", feature = "mssql") -))] -pub trait AnyDecode<'r>: - Decode<'r, Postgres> - + Type - + Decode<'r, MySql> - + Type - + Decode<'r, Mssql> - + Type -{ -} - -#[cfg(all( - not(feature = "sqlite"), - all(feature = "postgres", feature = "mysql", feature = "mssql") -))] -impl<'r, T> AnyDecode<'r> for T where - T: Decode<'r, Postgres> - + Type - + Decode<'r, MySql> - + Type - + Decode<'r, Mssql> - + Type -{ -} - -#[cfg(all( - not(feature = "postgres"), - all(feature = "sqlite", feature = "mysql", feature = "mssql") -))] -pub trait AnyDecode<'r>: - Decode<'r, Sqlite> - + Type - + Decode<'r, MySql> - + Type - + Decode<'r, Mssql> - + Type -{ -} - -#[cfg(all( - not(feature = "postgres"), - all(feature = "sqlite", feature = "mysql", feature = "mssql") -))] -impl<'r, T> AnyDecode<'r> for T where - T: Decode<'r, Sqlite> - + Type - + Decode<'r, MySql> - + Type - + Decode<'r, Mssql> - + Type -{ -} - -// only 2 (6) - -#[cfg(all( - not(any(feature = "mssql", feature = "sqlite")), - all(feature = "postgres", feature = "mysql") -))] -pub trait AnyDecode<'r>: - Decode<'r, Postgres> + Type + Decode<'r, MySql> + Type -{ -} - -#[cfg(all( - not(any(feature = "mssql", feature = "sqlite")), - all(feature = "postgres", feature = "mysql") -))] -impl<'r, T> AnyDecode<'r> for T where - T: Decode<'r, Postgres> + Type + Decode<'r, MySql> + Type -{ -} - -#[cfg(all( - not(any(feature = "mysql", feature = "sqlite")), - all(feature = "postgres", feature = "mssql") -))] -pub trait AnyDecode<'r>: - Decode<'r, Postgres> + Type + Decode<'r, Mssql> + Type -{ -} - -#[cfg(all( - not(any(feature = "mysql", feature = "sqlite")), - all(feature = "postgres", feature = "mssql") -))] -impl<'r, T> AnyDecode<'r> for T where - T: Decode<'r, Postgres> + Type + Decode<'r, Mssql> + Type -{ -} - -#[cfg(all( - not(any(feature = "mysql", feature = "mssql")), - all(feature = "postgres", feature = "sqlite") -))] -pub trait AnyDecode<'r>: - Decode<'r, Postgres> + Type + Decode<'r, Sqlite> + Type -{ -} - -#[cfg(all( - not(any(feature = "mysql", feature = "mssql")), - all(feature = "postgres", feature = "sqlite") -))] -impl<'r, T> AnyDecode<'r> for T where - T: Decode<'r, Postgres> + Type + Decode<'r, Sqlite> + Type -{ -} - -#[cfg(all( - not(any(feature = "postgres", feature = "sqlite")), - all(feature = "mssql", feature = "mysql") -))] -pub trait AnyDecode<'r>: Decode<'r, Mssql> + Type + Decode<'r, MySql> + Type {} - -#[cfg(all( - not(any(feature = "postgres", feature = "sqlite")), - all(feature = "mssql", feature = "mysql") -))] -impl<'r, T> AnyDecode<'r> for T where - T: Decode<'r, Mssql> + Type + Decode<'r, MySql> + Type -{ -} - -#[cfg(all( - not(any(feature = "postgres", feature = "mysql")), - all(feature = "mssql", feature = "sqlite") -))] -pub trait AnyDecode<'r>: - Decode<'r, Mssql> + Type + Decode<'r, Sqlite> + Type -{ -} - -#[cfg(all( - not(any(feature = "postgres", feature = "mysql")), - all(feature = "mssql", feature = "sqlite") -))] -impl<'r, T> AnyDecode<'r> for T where - T: Decode<'r, Mssql> + Type + Decode<'r, Sqlite> + Type -{ -} - -#[cfg(all( - not(any(feature = "postgres", feature = "mssql")), - all(feature = "mysql", feature = "sqlite") -))] -pub trait AnyDecode<'r>: - Decode<'r, MySql> + Type + Decode<'r, Sqlite> + Type -{ -} - -#[cfg(all( - not(any(feature = "postgres", feature = "mssql")), - all(feature = "mysql", feature = "sqlite") -))] -impl<'r, T> AnyDecode<'r> for T where - T: Decode<'r, MySql> + Type + Decode<'r, Sqlite> + Type -{ -} - -// only 1 (4) - -#[cfg(all( - not(any(feature = "mysql", feature = "mssql", feature = "sqlite")), - feature = "postgres" -))] -pub trait AnyDecode<'r>: Decode<'r, Postgres> + Type {} - -#[cfg(all( - not(any(feature = "mysql", feature = "mssql", feature = "sqlite")), - feature = "postgres" -))] -impl<'r, T> AnyDecode<'r> for T where T: Decode<'r, Postgres> + Type {} - -#[cfg(all( - not(any(feature = "postgres", feature = "mssql", feature = "sqlite")), - feature = "mysql" -))] -pub trait AnyDecode<'r>: Decode<'r, MySql> + Type {} - -#[cfg(all( - not(any(feature = "postgres", feature = "mssql", feature = "sqlite")), - feature = "mysql" -))] -impl<'r, T> AnyDecode<'r> for T where T: Decode<'r, MySql> + Type {} - -#[cfg(all( - not(any(feature = "mysql", feature = "postgres", feature = "sqlite")), - feature = "mssql" -))] -pub trait AnyDecode<'r>: Decode<'r, Mssql> + Type {} - -#[cfg(all( - not(any(feature = "mysql", feature = "postgres", feature = "sqlite")), - feature = "mssql" -))] -impl<'r, T> AnyDecode<'r> for T where T: Decode<'r, Mssql> + Type {} - -#[cfg(all( - not(any(feature = "mysql", feature = "mssql", feature = "postgres")), - feature = "sqlite" -))] -pub trait AnyDecode<'r>: Decode<'r, Sqlite> + Type {} - -#[cfg(all( - not(any(feature = "mysql", feature = "mssql", feature = "postgres")), - feature = "sqlite" -))] -impl<'r, T> AnyDecode<'r> for T where T: Decode<'r, Sqlite> + Type {} diff --git a/sqlx-core/src/any/driver.rs b/sqlx-core/src/any/driver.rs new file mode 100644 index 0000000000..6770ba7aa8 --- /dev/null +++ b/sqlx-core/src/any/driver.rs @@ -0,0 +1,153 @@ +use crate::any::connection::AnyConnectionBackend; +use crate::any::{ + Any, AnyArguments, AnyConnectOptions, AnyConnection, AnyQueryResult, AnyRow, AnyStatement, + AnyTypeInfo, +}; +use crate::common::DebugFn; +use crate::connection::Connection; +use crate::database::Database; +use crate::describe::Describe; +use crate::error::BoxDynError; +use crate::transaction::Transaction; +use crate::Error; +use either::Either; +use futures_core::future::BoxFuture; +use futures_core::stream::BoxStream; +use once_cell::sync::OnceCell; +use std::fmt::{Debug, Formatter}; +use std::marker::PhantomData; +use url::Url; + +static DRIVERS: OnceCell<&'static [AnyDriver]> = OnceCell::new(); + +#[macro_export] +macro_rules! declare_driver_with_optional_migrate { + ($name:ident = $db:path) => { + #[cfg(feature = "migrate")] + pub const $name: $crate::any::driver::AnyDriver = + $crate::any::driver::AnyDriver::with_migrate::<$db>(); + + #[cfg(not(feature = "migrate"))] + pub const $name: $crate::any::driver::AnyDriver = + $crate::any::driver::AnyDriver::without_migrate::<$db>(); + }; +} + +#[non_exhaustive] +pub struct AnyDriver { + pub(crate) name: &'static str, + pub(crate) url_schemes: &'static [&'static str], + pub(crate) connect: + DebugFn BoxFuture<'_, crate::Result>>, + pub(crate) migrate_database: Option, +} + +impl AnyDriver { + pub const fn without_migrate() -> Self + where + DB::Connection: AnyConnectionBackend, + ::Options: + for<'a> TryFrom<&'a AnyConnectOptions, Error = Error>, + { + Self { + name: DB::NAME, + url_schemes: DB::URL_SCHEMES, + connect: DebugFn(AnyConnection::connect::), + migrate_database: None, + } + } + + #[cfg(not(feature = "migrate"))] + pub const fn with_migrate() -> Self + where + DB::Connection: AnyConnectionBackend, + ::Options: + for<'a> TryFrom<&'a AnyConnectOptions, Error = Error>, + { + Self::without_migrate::() + } + + #[cfg(feature = "migrate")] + pub const fn with_migrate() -> Self + where + DB::Connection: AnyConnectionBackend, + ::Options: + for<'a> TryFrom<&'a AnyConnectOptions, Error = Error>, + { + Self { + migrate_database: Some(AnyMigrateDatabase { + create_database: DebugFn(DB::create_database), + database_exists: DebugFn(DB::database_exists), + drop_database: DebugFn(DB::drop_database), + }), + ..Self::without_migrate::() + } + } + + pub fn get_migrate_database(&self) -> crate::Result<&AnyMigrateDatabase> { + self.migrate_database.as_ref() + .ok_or_else(|| Error::Configuration(format!("{} driver does not support migrations or the `migrate` feature was not enabled for it", self.name).into())) + } +} + +impl Debug for AnyDriver { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + f.debug_struct("AnyDriver") + .field("name", &self.name) + .field("url_schemes", &self.url_schemes) + .finish() + } +} + +pub struct AnyMigrateDatabase { + create_database: DebugFn BoxFuture<'_, crate::Result<()>>>, + database_exists: DebugFn BoxFuture<'_, crate::Result>>, + drop_database: DebugFn BoxFuture<'_, crate::Result<()>>>, +} + +impl AnyMigrateDatabase { + pub fn create_database<'a>(&self, url: &'a str) -> BoxFuture<'a, crate::Result<()>> { + (self.create_database)(url) + } + + pub fn database_exists<'a>(&self, url: &'a str) -> BoxFuture<'a, crate::Result> { + (self.database_exists)(url) + } + + pub fn drop_database<'a>(&self, url: &'a str) -> BoxFuture<'a, crate::Result<()>> { + (self.drop_database)(url) + } +} + +/// Install the list of drivers for [`AnyConnection`] to use. +/// +/// Must be called before an `AnyConnection` or `AnyPool` can be connected. +/// +/// ### Errors +/// If called more than once. +pub fn install_drivers( + drivers: &'static [AnyDriver], +) -> Result<(), Box> { + DRIVERS + .set(drivers) + .map_err(|_| "drivers already installed".into()) +} + +pub(crate) fn from_url_str(url: &str) -> crate::Result<&'static AnyDriver> { + from_url(&url.parse().map_err(Error::config)?) +} + +pub(crate) fn from_url(url: &Url) -> crate::Result<&'static AnyDriver> { + let scheme = url.scheme(); + + let drivers: &[AnyDriver] = DRIVERS + .get() + .expect("No drivers installed. Please see the documentation in `sqlx::any` for details."); + + drivers + .iter() + .find(|driver| driver.url_schemes.contains(&url.scheme())) + .ok_or_else(|| { + Error::Configuration(format!("no driver found for URL scheme {:?}", scheme).into()) + }) +} diff --git a/sqlx-core/src/any/encode.rs b/sqlx-core/src/any/encode.rs deleted file mode 100644 index edde3bcd70..0000000000 --- a/sqlx-core/src/any/encode.rs +++ /dev/null @@ -1,361 +0,0 @@ -use crate::encode::Encode; -use crate::types::Type; - -#[cfg(feature = "postgres")] -use crate::postgres::Postgres; - -#[cfg(feature = "mysql")] -use crate::mysql::MySql; - -#[cfg(feature = "mssql")] -use crate::mssql::Mssql; - -#[cfg(feature = "sqlite")] -use crate::sqlite::Sqlite; - -// Implements Encode for any T where T supports Encode for any database that has support currently -// compiled into SQLx -macro_rules! impl_any_encode { - ($ty:ty) => { - impl<'q> crate::encode::Encode<'q, crate::any::Any> for $ty - where - $ty: crate::any::AnyEncode<'q>, - { - fn encode_by_ref( - &self, - buf: &mut crate::any::AnyArgumentBuffer<'q>, - ) -> crate::encode::IsNull { - match &mut buf.0 { - #[cfg(feature = "postgres")] - crate::any::arguments::AnyArgumentBufferKind::Postgres(args, _) => { - args.add(self) - } - - #[cfg(feature = "mysql")] - crate::any::arguments::AnyArgumentBufferKind::MySql(args, _) => args.add(self), - - #[cfg(feature = "mssql")] - crate::any::arguments::AnyArgumentBufferKind::Mssql(args, _) => args.add(self), - - #[cfg(feature = "sqlite")] - crate::any::arguments::AnyArgumentBufferKind::Sqlite(args) => args.add(self), - } - - // unused - crate::encode::IsNull::No - } - } - }; -} - -// FIXME: Find a nice way to auto-generate the below or petition Rust to add support for #[cfg] -// to trait bounds - -// all 4 - -#[cfg(all( - feature = "postgres", - feature = "mysql", - feature = "mssql", - feature = "sqlite" -))] -pub trait AnyEncode<'q>: - Encode<'q, Postgres> - + Type - + Encode<'q, MySql> - + Type - + Encode<'q, Mssql> - + Type - + Encode<'q, Sqlite> - + Type -{ -} - -#[cfg(all( - feature = "postgres", - feature = "mysql", - feature = "mssql", - feature = "sqlite" -))] -impl<'q, T> AnyEncode<'q> for T where - T: Encode<'q, Postgres> - + Type - + Encode<'q, MySql> - + Type - + Encode<'q, Mssql> - + Type - + Encode<'q, Sqlite> - + Type -{ -} - -// only 3 (4) - -#[cfg(all( - not(feature = "mssql"), - all(feature = "postgres", feature = "mysql", feature = "sqlite") -))] -pub trait AnyEncode<'q>: - Encode<'q, Postgres> - + Type - + Encode<'q, MySql> - + Type - + Encode<'q, Sqlite> - + Type -{ -} - -#[cfg(all( - not(feature = "mssql"), - all(feature = "postgres", feature = "mysql", feature = "sqlite") -))] -impl<'q, T> AnyEncode<'q> for T where - T: Encode<'q, Postgres> - + Type - + Encode<'q, MySql> - + Type - + Encode<'q, Sqlite> - + Type -{ -} - -#[cfg(all( - not(feature = "mysql"), - all(feature = "postgres", feature = "mssql", feature = "sqlite") -))] -pub trait AnyEncode<'q>: - Encode<'q, Postgres> - + Type - + Encode<'q, Mssql> - + Type - + Encode<'q, Sqlite> - + Type -{ -} - -#[cfg(all( - not(feature = "mysql"), - all(feature = "postgres", feature = "mssql", feature = "sqlite") -))] -impl<'q, T> AnyEncode<'q> for T where - T: Encode<'q, Postgres> - + Type - + Encode<'q, Mssql> - + Type - + Encode<'q, Sqlite> - + Type -{ -} - -#[cfg(all( - not(feature = "sqlite"), - all(feature = "postgres", feature = "mysql", feature = "mssql") -))] -pub trait AnyEncode<'q>: - Encode<'q, Postgres> - + Type - + Encode<'q, MySql> - + Type - + Encode<'q, Mssql> - + Type -{ -} - -#[cfg(all( - not(feature = "sqlite"), - all(feature = "postgres", feature = "mysql", feature = "mssql") -))] -impl<'q, T> AnyEncode<'q> for T where - T: Encode<'q, Postgres> - + Type - + Encode<'q, MySql> - + Type - + Encode<'q, Mssql> - + Type -{ -} - -#[cfg(all( - not(feature = "postgres"), - all(feature = "sqlite", feature = "mysql", feature = "mssql") -))] -pub trait AnyEncode<'q>: - Encode<'q, Sqlite> - + Type - + Encode<'q, MySql> - + Type - + Encode<'q, Mssql> - + Type -{ -} - -#[cfg(all( - not(feature = "postgres"), - all(feature = "sqlite", feature = "mysql", feature = "mssql") -))] -impl<'q, T> AnyEncode<'q> for T where - T: Encode<'q, Sqlite> - + Type - + Encode<'q, MySql> - + Type - + Encode<'q, Mssql> - + Type -{ -} - -// only 2 (6) - -#[cfg(all( - not(any(feature = "mssql", feature = "sqlite")), - all(feature = "postgres", feature = "mysql") -))] -pub trait AnyEncode<'q>: - Encode<'q, Postgres> + Type + Encode<'q, MySql> + Type -{ -} - -#[cfg(all( - not(any(feature = "mssql", feature = "sqlite")), - all(feature = "postgres", feature = "mysql") -))] -impl<'q, T> AnyEncode<'q> for T where - T: Encode<'q, Postgres> + Type + Encode<'q, MySql> + Type -{ -} - -#[cfg(all( - not(any(feature = "mysql", feature = "sqlite")), - all(feature = "postgres", feature = "mssql") -))] -pub trait AnyEncode<'q>: - Encode<'q, Postgres> + Type + Encode<'q, Mssql> + Type -{ -} - -#[cfg(all( - not(any(feature = "mysql", feature = "sqlite")), - all(feature = "postgres", feature = "mssql") -))] -impl<'q, T> AnyEncode<'q> for T where - T: Encode<'q, Postgres> + Type + Encode<'q, Mssql> + Type -{ -} - -#[cfg(all( - not(any(feature = "mysql", feature = "mssql")), - all(feature = "postgres", feature = "sqlite") -))] -pub trait AnyEncode<'q>: - Encode<'q, Postgres> + Type + Encode<'q, Sqlite> + Type -{ -} - -#[cfg(all( - not(any(feature = "mysql", feature = "mssql")), - all(feature = "postgres", feature = "sqlite") -))] -impl<'q, T> AnyEncode<'q> for T where - T: Encode<'q, Postgres> + Type + Encode<'q, Sqlite> + Type -{ -} - -#[cfg(all( - not(any(feature = "postgres", feature = "sqlite")), - all(feature = "mssql", feature = "mysql") -))] -pub trait AnyEncode<'q>: Encode<'q, Mssql> + Type + Encode<'q, MySql> + Type {} - -#[cfg(all( - not(any(feature = "postgres", feature = "sqlite")), - all(feature = "mssql", feature = "mysql") -))] -impl<'q, T> AnyEncode<'q> for T where - T: Encode<'q, Mssql> + Type + Encode<'q, MySql> + Type -{ -} - -#[cfg(all( - not(any(feature = "postgres", feature = "mysql")), - all(feature = "mssql", feature = "sqlite") -))] -pub trait AnyEncode<'q>: - Encode<'q, Mssql> + Type + Encode<'q, Sqlite> + Type -{ -} - -#[cfg(all( - not(any(feature = "postgres", feature = "mysql")), - all(feature = "mssql", feature = "sqlite") -))] -impl<'q, T> AnyEncode<'q> for T where - T: Encode<'q, Mssql> + Type + Encode<'q, Sqlite> + Type -{ -} - -#[cfg(all( - not(any(feature = "postgres", feature = "mssql")), - all(feature = "mysql", feature = "sqlite") -))] -pub trait AnyEncode<'q>: - Encode<'q, MySql> + Type + Encode<'q, Sqlite> + Type -{ -} - -#[cfg(all( - not(any(feature = "postgres", feature = "mssql")), - all(feature = "mysql", feature = "sqlite") -))] -impl<'q, T> AnyEncode<'q> for T where - T: Encode<'q, MySql> + Type + Encode<'q, Sqlite> + Type -{ -} - -// only 1 (4) - -#[cfg(all( - not(any(feature = "mysql", feature = "mssql", feature = "sqlite")), - feature = "postgres" -))] -pub trait AnyEncode<'q>: Encode<'q, Postgres> + Type {} - -#[cfg(all( - not(any(feature = "mysql", feature = "mssql", feature = "sqlite")), - feature = "postgres" -))] -impl<'q, T> AnyEncode<'q> for T where T: Encode<'q, Postgres> + Type {} - -#[cfg(all( - not(any(feature = "postgres", feature = "mssql", feature = "sqlite")), - feature = "mysql" -))] -pub trait AnyEncode<'q>: Encode<'q, MySql> + Type {} - -#[cfg(all( - not(any(feature = "postgres", feature = "mssql", feature = "sqlite")), - feature = "mysql" -))] -impl<'q, T> AnyEncode<'q> for T where T: Encode<'q, MySql> + Type {} - -#[cfg(all( - not(any(feature = "mysql", feature = "postgres", feature = "sqlite")), - feature = "mssql" -))] -pub trait AnyEncode<'q>: Encode<'q, Mssql> + Type {} - -#[cfg(all( - not(any(feature = "mysql", feature = "postgres", feature = "sqlite")), - feature = "mssql" -))] -impl<'q, T> AnyEncode<'q> for T where T: Encode<'q, Mssql> + Type {} - -#[cfg(all( - not(any(feature = "mysql", feature = "mssql", feature = "postgres")), - feature = "sqlite" -))] -pub trait AnyEncode<'q>: Encode<'q, Sqlite> + Type {} - -#[cfg(all( - not(any(feature = "mysql", feature = "mssql", feature = "postgres")), - feature = "sqlite" -))] -impl<'q, T> AnyEncode<'q> for T where T: Encode<'q, Sqlite> + Type {} diff --git a/sqlx-core/src/any/migrate.rs b/sqlx-core/src/any/migrate.rs index ba73be926a..59a65e22ac 100644 --- a/sqlx-core/src/any/migrate.rs +++ b/sqlx-core/src/any/migrate.rs @@ -1,4 +1,4 @@ -use crate::any::connection::AnyConnectionKind; +use crate::any::driver; use crate::any::kind::AnyKind; use crate::any::{Any, AnyConnection}; use crate::error::Error; @@ -9,224 +9,67 @@ use std::time::Duration; impl MigrateDatabase for Any { fn create_database(url: &str) -> BoxFuture<'_, Result<(), Error>> { - Box::pin(async move { - match AnyKind::from_str(url)? { - #[cfg(feature = "postgres")] - AnyKind::Postgres => crate::postgres::Postgres::create_database(url).await, - - #[cfg(feature = "sqlite")] - AnyKind::Sqlite => crate::sqlite::Sqlite::create_database(url).await, - - #[cfg(feature = "mysql")] - AnyKind::MySql => crate::mysql::MySql::create_database(url).await, - - #[cfg(feature = "mssql")] - AnyKind::Mssql => unimplemented!(), - } + Box::pin(async { + driver::from_url_str(url)? + .get_migrate_database()? + .create_database(url) + .await }) } fn database_exists(url: &str) -> BoxFuture<'_, Result> { - Box::pin(async move { - match AnyKind::from_str(url)? { - #[cfg(feature = "postgres")] - AnyKind::Postgres => crate::postgres::Postgres::database_exists(url).await, - - #[cfg(feature = "sqlite")] - AnyKind::Sqlite => crate::sqlite::Sqlite::database_exists(url).await, - - #[cfg(feature = "mysql")] - AnyKind::MySql => crate::mysql::MySql::database_exists(url).await, - - #[cfg(feature = "mssql")] - AnyKind::Mssql => unimplemented!(), - } + Box::pin(async { + driver::from_url_str(url)? + .get_migrate_database()? + .database_exists(url) + .await }) } fn drop_database(url: &str) -> BoxFuture<'_, Result<(), Error>> { - Box::pin(async move { - match AnyKind::from_str(url)? { - #[cfg(feature = "postgres")] - AnyKind::Postgres => crate::postgres::Postgres::drop_database(url).await, - - #[cfg(feature = "sqlite")] - AnyKind::Sqlite => crate::sqlite::Sqlite::drop_database(url).await, - - #[cfg(feature = "mysql")] - AnyKind::MySql => crate::mysql::MySql::drop_database(url).await, - - #[cfg(feature = "mssql")] - AnyKind::Mssql => unimplemented!(), - } + Box::pin(async { + driver::from_url_str(url)? + .get_migrate_database()? + .drop_database(url) + .await }) } } impl Migrate for AnyConnection { fn ensure_migrations_table(&mut self) -> BoxFuture<'_, Result<(), MigrateError>> { - match &mut self.0 { - #[cfg(feature = "postgres")] - AnyConnectionKind::Postgres(conn) => conn.ensure_migrations_table(), - - #[cfg(feature = "sqlite")] - AnyConnectionKind::Sqlite(conn) => conn.ensure_migrations_table(), - - #[cfg(feature = "mysql")] - AnyConnectionKind::MySql(conn) => conn.ensure_migrations_table(), - - #[cfg(feature = "mssql")] - AnyConnectionKind::Mssql(_conn) => unimplemented!(), - } - } - - #[allow(deprecated)] - fn version(&mut self) -> BoxFuture<'_, Result, MigrateError>> { - match &mut self.0 { - #[cfg(feature = "postgres")] - AnyConnectionKind::Postgres(conn) => conn.version(), - - #[cfg(feature = "sqlite")] - AnyConnectionKind::Sqlite(conn) => conn.version(), - - #[cfg(feature = "mysql")] - AnyConnectionKind::MySql(conn) => conn.version(), - - #[cfg(feature = "mssql")] - AnyConnectionKind::Mssql(_conn) => unimplemented!(), - } + Box::pin(async { self.get_migrate()?.ensure_migrations_table().await }) } fn dirty_version(&mut self) -> BoxFuture<'_, Result, MigrateError>> { - match &mut self.0 { - #[cfg(feature = "postgres")] - AnyConnectionKind::Postgres(conn) => conn.dirty_version(), - - #[cfg(feature = "sqlite")] - AnyConnectionKind::Sqlite(conn) => conn.dirty_version(), - - #[cfg(feature = "mysql")] - AnyConnectionKind::MySql(conn) => conn.dirty_version(), - - #[cfg(feature = "mssql")] - AnyConnectionKind::Mssql(_conn) => unimplemented!(), - } - } - - #[allow(deprecated)] - fn validate<'e: 'm, 'm>( - &'e mut self, - migration: &'m Migration, - ) -> BoxFuture<'m, Result<(), MigrateError>> { - match &mut self.0 { - #[cfg(feature = "postgres")] - AnyConnectionKind::Postgres(conn) => conn.validate(migration), - - #[cfg(feature = "sqlite")] - AnyConnectionKind::Sqlite(conn) => conn.validate(migration), - - #[cfg(feature = "mysql")] - AnyConnectionKind::MySql(conn) => conn.validate(migration), - - #[cfg(feature = "mssql")] - AnyConnectionKind::Mssql(_conn) => { - let _ = migration; - unimplemented!() - } - } + Box::pin(async { self.get_migrate()?.dirty_version().await }) } fn list_applied_migrations( &mut self, ) -> BoxFuture<'_, Result, MigrateError>> { - match &mut self.0 { - #[cfg(feature = "postgres")] - AnyConnectionKind::Postgres(conn) => conn.list_applied_migrations(), - - #[cfg(feature = "sqlite")] - AnyConnectionKind::Sqlite(conn) => conn.list_applied_migrations(), - - #[cfg(feature = "mysql")] - AnyConnectionKind::MySql(conn) => conn.list_applied_migrations(), - - #[cfg(feature = "mssql")] - AnyConnectionKind::Mssql(_conn) => unimplemented!(), - } + Box::pin(async { self.get_migrate()?.list_applied_migrations().await }) } fn lock(&mut self) -> BoxFuture<'_, Result<(), MigrateError>> { - match &mut self.0 { - #[cfg(feature = "postgres")] - AnyConnectionKind::Postgres(conn) => conn.lock(), - - #[cfg(feature = "sqlite")] - AnyConnectionKind::Sqlite(conn) => conn.lock(), - - #[cfg(feature = "mysql")] - AnyConnectionKind::MySql(conn) => conn.lock(), - - #[cfg(feature = "mssql")] - AnyConnectionKind::Mssql(_conn) => unimplemented!(), - } + Box::pin(async { self.get_migrate()?.lock().await }) } fn unlock(&mut self) -> BoxFuture<'_, Result<(), MigrateError>> { - match &mut self.0 { - #[cfg(feature = "postgres")] - AnyConnectionKind::Postgres(conn) => conn.unlock(), - - #[cfg(feature = "sqlite")] - AnyConnectionKind::Sqlite(conn) => conn.unlock(), - - #[cfg(feature = "mysql")] - AnyConnectionKind::MySql(conn) => conn.unlock(), - - #[cfg(feature = "mssql")] - AnyConnectionKind::Mssql(_conn) => unimplemented!(), - } + Box::pin(async { self.get_migrate()?.unlock().await }) } fn apply<'e: 'm, 'm>( &'e mut self, migration: &'m Migration, ) -> BoxFuture<'m, Result> { - match &mut self.0 { - #[cfg(feature = "postgres")] - AnyConnectionKind::Postgres(conn) => conn.apply(migration), - - #[cfg(feature = "sqlite")] - AnyConnectionKind::Sqlite(conn) => conn.apply(migration), - - #[cfg(feature = "mysql")] - AnyConnectionKind::MySql(conn) => conn.apply(migration), - - #[cfg(feature = "mssql")] - AnyConnectionKind::Mssql(_conn) => { - let _ = migration; - unimplemented!() - } - } + Box::pin(async { self.get_migrate()?.apply(migration).await }) } fn revert<'e: 'm, 'm>( &'e mut self, migration: &'m Migration, ) -> BoxFuture<'m, Result> { - match &mut self.0 { - #[cfg(feature = "postgres")] - AnyConnectionKind::Postgres(conn) => conn.revert(migration), - - #[cfg(feature = "sqlite")] - AnyConnectionKind::Sqlite(conn) => conn.revert(migration), - - #[cfg(feature = "mysql")] - AnyConnectionKind::MySql(conn) => conn.revert(migration), - - #[cfg(feature = "mssql")] - AnyConnectionKind::Mssql(_conn) => { - let _ = migration; - unimplemented!() - } - } + Box::pin(async { self.get_migrate()?.revert(migration).await }) } } diff --git a/sqlx-core/src/any/mod.rs b/sqlx-core/src/any/mod.rs index bb07016051..6a499070fc 100644 --- a/sqlx-core/src/any/mod.rs +++ b/sqlx-core/src/any/mod.rs @@ -1,16 +1,10 @@ -//! Generic database driver with the specific driver selected at runtime. - +//! **SEE DOCUMENTATION BEFORE USE**. Generic database driver with the specific driver selected at runtime. +//! +//! The underlying database drivers are chosen at runtime from the list set via +//! [`install_drivers`][self::driver::install_drivers). Any use of `AnyConnection` or `AnyPool` +//! without this will panic. use crate::executor::Executor; -#[macro_use] -mod decode; - -#[macro_use] -mod encode; - -#[macro_use] -mod r#type; - mod arguments; pub(crate) mod column; mod connection; @@ -26,27 +20,31 @@ pub(crate) mod type_info; pub mod types; pub(crate) mod value; +pub mod driver; + #[cfg(feature = "migrate")] mod migrate; pub use arguments::{AnyArgumentBuffer, AnyArguments}; -pub use column::{AnyColumn, AnyColumnIndex}; +pub use column::AnyColumn; pub use connection::AnyConnection; // Used internally in `sqlx-macros` -#[doc(hidden)] -pub use connection::AnyConnectionKind; + +use crate::encode::Encode; +pub use connection::AnyConnectionBackend; pub use database::Any; -pub use decode::AnyDecode; -pub use encode::AnyEncode; pub use kind::AnyKind; pub use options::AnyConnectOptions; pub use query_result::AnyQueryResult; pub use row::AnyRow; pub use statement::AnyStatement; pub use transaction::AnyTransactionManager; -pub use type_info::AnyTypeInfo; +pub use type_info::{AnyTypeInfo, AnyTypeInfoKind}; pub use value::{AnyValue, AnyValueRef}; +#[doc(hidden)] +pub use value::AnyValueKind; + pub type AnyPool = crate::pool::Pool; pub type AnyPoolOptions = crate::pool::PoolOptions; @@ -57,34 +55,25 @@ impl<'c, T: Executor<'c, Database = Any>> AnyExecutor<'c> for T {} // NOTE: required due to the lack of lazy normalization impl_into_arguments_for_arguments!(AnyArguments<'q>); -impl_executor_for_pool_connection!(Any, AnyConnection, AnyRow); -impl_executor_for_transaction!(Any, AnyRow); +// impl_executor_for_pool_connection!(Any, AnyConnection, AnyRow); +// impl_executor_for_transaction!(Any, AnyRow); impl_acquire!(Any, AnyConnection); impl_column_index_for_row!(AnyRow); impl_column_index_for_statement!(AnyStatement); -impl_into_maybe_pool!(Any, AnyConnection); +// impl_into_maybe_pool!(Any, AnyConnection); // required because some databases have a different handling of NULL -impl<'q, T> crate::encode::Encode<'q, Any> for Option +impl<'q, T> Encode<'q, Any> for Option where - T: AnyEncode<'q> + 'q, + T: Encode<'q, Any> + 'q, { fn encode_by_ref(&self, buf: &mut AnyArgumentBuffer<'q>) -> crate::encode::IsNull { - match &mut buf.0 { - #[cfg(feature = "postgres")] - arguments::AnyArgumentBufferKind::Postgres(args, _) => args.add(self), - - #[cfg(feature = "mysql")] - arguments::AnyArgumentBufferKind::MySql(args, _) => args.add(self), - - #[cfg(feature = "mssql")] - arguments::AnyArgumentBufferKind::Mssql(args, _) => args.add(self), - - #[cfg(feature = "sqlite")] - arguments::AnyArgumentBufferKind::Sqlite(args) => args.add(self), + if let Some(value) = self { + value.encode_by_ref(buf); + crate::encode::IsNull::No + } else { + buf.0.push(AnyValueKind::Null); + crate::encode::IsNull::Yes } - - // unused - crate::encode::IsNull::No } } diff --git a/sqlx-core/src/any/options.rs b/sqlx-core/src/any/options.rs index 3e81198b1b..08361eaeeb 100644 --- a/sqlx-core/src/any/options.rs +++ b/sqlx-core/src/any/options.rs @@ -1,23 +1,13 @@ use crate::any::AnyConnection; -use crate::connection::ConnectOptions; +use crate::connection::{ConnectOptions, LogSettings}; use crate::error::Error; use futures_core::future::BoxFuture; use log::LevelFilter; use std::str::FromStr; use std::time::Duration; - -#[cfg(feature = "postgres")] -use crate::postgres::PgConnectOptions; - -#[cfg(feature = "mysql")] -use crate::mysql::MySqlConnectOptions; - -#[cfg(feature = "sqlite")] -use crate::sqlite::SqliteConnectOptions; +use url::Url; use crate::any::kind::AnyKind; -#[cfg(feature = "mssql")] -use crate::mssql::MssqlConnectOptions; /// Opaque options for connecting to a database. These may only be constructed by parsing from /// a connection url. @@ -27,210 +17,47 @@ use crate::mssql::MssqlConnectOptions; /// mysql://root:password@localhost/database /// ``` #[derive(Debug, Clone)] -pub struct AnyConnectOptions(pub(crate) AnyConnectOptionsKind); - -impl AnyConnectOptions { - pub fn kind(&self) -> AnyKind { - match &self.0 { - #[cfg(feature = "postgres")] - AnyConnectOptionsKind::Postgres(_) => AnyKind::Postgres, - - #[cfg(feature = "mysql")] - AnyConnectOptionsKind::MySql(_) => AnyKind::MySql, - - #[cfg(feature = "sqlite")] - AnyConnectOptionsKind::Sqlite(_) => AnyKind::Sqlite, - - #[cfg(feature = "mssql")] - AnyConnectOptionsKind::Mssql(_) => AnyKind::Mssql, - } - } -} - -macro_rules! try_from_any_connect_options_to { - ($to:ty, $kind:path, $name:expr) => { - impl TryFrom for $to { - type Error = Error; - - fn try_from(value: AnyConnectOptions) -> Result { - #[allow(irrefutable_let_patterns)] - if let $kind(connect_options) = value.0 { - Ok(connect_options) - } else { - Err(Error::Configuration( - format!("Not {} typed AnyConnectOptions", $name).into(), - )) - } - } - } - - impl AnyConnectOptions { - paste::item! { - pub fn [< as_ $name >] (&self) -> Option<&$to> { - #[allow(irrefutable_let_patterns)] - if let $kind(ref connect_options) = self.0 { - Some(connect_options) - } else { - None - } - } - - pub fn [< as_ $name _mut >] (&mut self) -> Option<&mut $to> { - #[allow(irrefutable_let_patterns)] - if let $kind(ref mut connect_options) = self.0 { - Some(connect_options) - } else { - None - } - } - } - } - }; -} - -#[cfg(feature = "postgres")] -try_from_any_connect_options_to!( - PgConnectOptions, - AnyConnectOptionsKind::Postgres, - "postgres" -); - -#[cfg(feature = "mysql")] -try_from_any_connect_options_to!(MySqlConnectOptions, AnyConnectOptionsKind::MySql, "mysql"); - -#[cfg(feature = "sqlite")] -try_from_any_connect_options_to!( - SqliteConnectOptions, - AnyConnectOptionsKind::Sqlite, - "sqlite" -); - -#[cfg(feature = "mssql")] -try_from_any_connect_options_to!(MssqlConnectOptions, AnyConnectOptionsKind::Mssql, "mssql"); - -#[derive(Debug, Clone)] -pub(crate) enum AnyConnectOptionsKind { - #[cfg(feature = "postgres")] - Postgres(PgConnectOptions), - - #[cfg(feature = "mysql")] - MySql(MySqlConnectOptions), - - #[cfg(feature = "sqlite")] - Sqlite(SqliteConnectOptions), - - #[cfg(feature = "mssql")] - Mssql(MssqlConnectOptions), +#[non_exhaustive] +pub struct AnyConnectOptions { + pub database_url: Url, + pub log_settings: LogSettings, } - -#[cfg(feature = "postgres")] -impl From for AnyConnectOptions { - fn from(options: PgConnectOptions) -> Self { - Self(AnyConnectOptionsKind::Postgres(options)) - } -} - -#[cfg(feature = "mysql")] -impl From for AnyConnectOptions { - fn from(options: MySqlConnectOptions) -> Self { - Self(AnyConnectOptionsKind::MySql(options)) - } -} - -#[cfg(feature = "sqlite")] -impl From for AnyConnectOptions { - fn from(options: SqliteConnectOptions) -> Self { - Self(AnyConnectOptionsKind::Sqlite(options)) - } -} - -#[cfg(feature = "mssql")] -impl From for AnyConnectOptions { - fn from(options: MssqlConnectOptions) -> Self { - Self(AnyConnectOptionsKind::Mssql(options)) - } -} - impl FromStr for AnyConnectOptions { type Err = Error; fn from_str(url: &str) -> Result { - match AnyKind::from_str(url)? { - #[cfg(feature = "postgres")] - AnyKind::Postgres => { - PgConnectOptions::from_str(url).map(AnyConnectOptionsKind::Postgres) - } - - #[cfg(feature = "mysql")] - AnyKind::MySql => MySqlConnectOptions::from_str(url).map(AnyConnectOptionsKind::MySql), - - #[cfg(feature = "sqlite")] - AnyKind::Sqlite => { - SqliteConnectOptions::from_str(url).map(AnyConnectOptionsKind::Sqlite) - } - - #[cfg(feature = "mssql")] - AnyKind::Mssql => MssqlConnectOptions::from_str(url).map(AnyConnectOptionsKind::Mssql), - } - .map(AnyConnectOptions) + Ok(AnyConnectOptions { + database_url: url + .parse::() + .map_err(|e| Error::Configuration(e.into()))?, + log_settings: LogSettings::default(), + }) } } impl ConnectOptions for AnyConnectOptions { type Connection = AnyConnection; + fn from_url(url: &Url) -> Result { + Ok(AnyConnectOptions { + database_url: url.clone(), + log_settings: LogSettings::default(), + }) + } + #[inline] fn connect(&self) -> BoxFuture<'_, Result> { Box::pin(AnyConnection::establish(self)) } fn log_statements(&mut self, level: LevelFilter) -> &mut Self { - match &mut self.0 { - #[cfg(feature = "postgres")] - AnyConnectOptionsKind::Postgres(o) => { - o.log_statements(level); - } - - #[cfg(feature = "mysql")] - AnyConnectOptionsKind::MySql(o) => { - o.log_statements(level); - } - - #[cfg(feature = "sqlite")] - AnyConnectOptionsKind::Sqlite(o) => { - o.log_statements(level); - } - - #[cfg(feature = "mssql")] - AnyConnectOptionsKind::Mssql(o) => { - o.log_statements(level); - } - }; + self.log_settings.statements_level = level; self } fn log_slow_statements(&mut self, level: LevelFilter, duration: Duration) -> &mut Self { - match &mut self.0 { - #[cfg(feature = "postgres")] - AnyConnectOptionsKind::Postgres(o) => { - o.log_slow_statements(level, duration); - } - - #[cfg(feature = "mysql")] - AnyConnectOptionsKind::MySql(o) => { - o.log_slow_statements(level, duration); - } - - #[cfg(feature = "sqlite")] - AnyConnectOptionsKind::Sqlite(o) => { - o.log_slow_statements(level, duration); - } - - #[cfg(feature = "mssql")] - AnyConnectOptionsKind::Mssql(o) => { - o.log_slow_statements(level, duration); - } - }; + self.log_settings.slow_statements_level = level; + self.log_settings.slow_statements_duration = duration; self } } diff --git a/sqlx-core/src/any/query_result.rs b/sqlx-core/src/any/query_result.rs index b101fbf8b1..f594e06224 100644 --- a/sqlx-core/src/any/query_result.rs +++ b/sqlx-core/src/any/query_result.rs @@ -2,8 +2,10 @@ use std::iter::{Extend, IntoIterator}; #[derive(Debug, Default)] pub struct AnyQueryResult { - pub(crate) rows_affected: u64, - pub(crate) last_insert_id: Option, + #[doc(hidden)] + pub rows_affected: u64, + #[doc(hidden)] + pub last_insert_id: Option, } impl AnyQueryResult { diff --git a/sqlx-core/src/any/row.rs b/sqlx-core/src/any/row.rs index b48f07b585..3af4657028 100644 --- a/sqlx-core/src/any/row.rs +++ b/sqlx-core/src/any/row.rs @@ -1,45 +1,25 @@ use crate::any::error::mismatched_types; -use crate::any::{Any, AnyColumn, AnyColumnIndex}; -use crate::column::ColumnIndex; -use crate::database::HasValueRef; +use crate::any::{Any, AnyColumn, AnyTypeInfo, AnyTypeInfoKind, AnyValue, AnyValueKind}; +use crate::column::{Column, ColumnIndex}; +use crate::database::{Database, HasValueRef}; use crate::decode::Decode; use crate::error::Error; +use crate::ext::ustr::UStr; use crate::row::Row; use crate::type_info::TypeInfo; use crate::types::Type; -use crate::value::ValueRef; - -#[cfg(feature = "postgres")] -use crate::postgres::PgRow; - -#[cfg(feature = "mysql")] -use crate::mysql::MySqlRow; - -#[cfg(feature = "sqlite")] -use crate::sqlite::SqliteRow; - -#[cfg(feature = "mssql")] -use crate::mssql::MssqlRow; +use crate::value::{Value, ValueRef}; +use std::borrow::Cow; +use std::sync::Arc; +#[derive(Clone)] pub struct AnyRow { - pub(crate) kind: AnyRowKind, - pub(crate) columns: Vec, -} - -impl crate::row::private_row::Sealed for AnyRow {} - -pub(crate) enum AnyRowKind { - #[cfg(feature = "postgres")] - Postgres(PgRow), - - #[cfg(feature = "mysql")] - MySql(MySqlRow), - - #[cfg(feature = "sqlite")] - Sqlite(SqliteRow), - - #[cfg(feature = "mssql")] - Mssql(MssqlRow), + #[doc(hidden)] + pub column_names: Arc>, + #[doc(hidden)] + pub columns: Vec, + #[doc(hidden)] + pub values: Vec, } impl Row for AnyRow { @@ -57,20 +37,14 @@ impl Row for AnyRow { I: ColumnIndex, { let index = index.index(self)?; - - match &self.kind { - #[cfg(feature = "postgres")] - AnyRowKind::Postgres(row) => row.try_get_raw(index).map(Into::into), - - #[cfg(feature = "mysql")] - AnyRowKind::MySql(row) => row.try_get_raw(index).map(Into::into), - - #[cfg(feature = "sqlite")] - AnyRowKind::Sqlite(row) => row.try_get_raw(index).map(Into::into), - - #[cfg(feature = "mssql")] - AnyRowKind::Mssql(row) => row.try_get_raw(index).map(Into::into), - } + Ok(self + .values + .get(index) + .ok_or_else(|| Error::ColumnIndexOutOfBounds { + index, + len: self.columns.len(), + })? + .as_ref()) } fn try_get<'r, T, I>(&'r self, index: I) -> Result @@ -93,23 +67,82 @@ impl Row for AnyRow { } } -impl<'i> ColumnIndex for &'i str -where - &'i str: AnyColumnIndex, -{ +impl<'i> ColumnIndex for &'i str { fn index(&self, row: &AnyRow) -> Result { - match &row.kind { - #[cfg(feature = "postgres")] - AnyRowKind::Postgres(row) => self.index(row), - - #[cfg(feature = "mysql")] - AnyRowKind::MySql(row) => self.index(row), - - #[cfg(feature = "sqlite")] - AnyRowKind::Sqlite(row) => self.index(row), + row.column_names + .get(*self) + .copied() + .ok_or_else(|| Error::ColumnNotFound(self.to_string())) + } +} - #[cfg(feature = "mssql")] - AnyRowKind::Mssql(row) => self.index(row), +impl AnyRow { + // This is not a `TryFrom` impl because trait impls are easy for users to accidentally + // become reliant upon, even if hidden, but we want to be able to change the bounds + // on this function as the `Any` driver gains support for more types. + // + // Also `column_names` needs to be passed by the driver to avoid making deep copies. + #[doc(hidden)] + pub fn map_from<'a, R: Row>( + row: &'a R, + column_names: Arc>, + ) -> Result + where + usize: ColumnIndex, + AnyTypeInfo: for<'b> TryFrom<&'b ::TypeInfo, Error = Error>, + AnyColumn: for<'b> TryFrom<&'b ::Column, Error = Error>, + bool: Type + Decode<'a, R::Database>, + i16: Type + Decode<'a, R::Database>, + i32: Type + Decode<'a, R::Database>, + i64: Type + Decode<'a, R::Database>, + f32: Type + Decode<'a, R::Database>, + f64: Type + Decode<'a, R::Database>, + String: Type + Decode<'a, R::Database>, + Vec: Type + Decode<'a, R::Database>, + { + let mut row_out = AnyRow { + column_names, + columns: Vec::with_capacity(row.columns().len()), + values: Vec::with_capacity(row.columns().len()), + }; + + for col in row.columns() { + let i = col.ordinal(); + + let any_col = AnyColumn::try_from(col)?; + + let value = row.try_get_raw(i)?; + + // Map based on the _value_ type info, not the column type info. + let type_info = + AnyTypeInfo::try_from(&value.type_info()).map_err(|e| Error::ColumnDecode { + index: col.ordinal().to_string(), + source: e.into(), + })?; + + let value_kind = match type_info.kind { + _ if value.is_null() => AnyValueKind::Null, + AnyTypeInfoKind::Null => AnyValueKind::Null, + AnyTypeInfoKind::Bool => AnyValueKind::Bool(decode(value)?), + AnyTypeInfoKind::SmallInt => AnyValueKind::SmallInt(decode(value)?), + AnyTypeInfoKind::Integer => AnyValueKind::Integer(decode(value)?), + AnyTypeInfoKind::BigInt => AnyValueKind::BigInt(decode(value)?), + AnyTypeInfoKind::Real => AnyValueKind::Real(decode(value)?), + AnyTypeInfoKind::Double => AnyValueKind::Double(decode(value)?), + AnyTypeInfoKind::Blob => AnyValueKind::Blob(decode::<_, Vec>(value)?.into()), + AnyTypeInfoKind::Text => AnyValueKind::Text(decode::<_, String>(value)?.into()), + }; + + row_out.columns.push(any_col); + row_out.values.push(AnyValue { kind: value_kind }); } + + Ok(row_out) } } + +fn decode<'r, DB: Database, T: Decode<'r, DB>>( + valueref: >::ValueRef, +) -> crate::Result { + Decode::decode(valueref).map_err(Error::decode) +} diff --git a/sqlx-core/src/any/statement.rs b/sqlx-core/src/any/statement.rs index 0c283c2e5e..01ea06011e 100644 --- a/sqlx-core/src/any/statement.rs +++ b/sqlx-core/src/any/statement.rs @@ -1,5 +1,6 @@ -use crate::any::{Any, AnyArguments, AnyColumn, AnyColumnIndex, AnyTypeInfo}; +use crate::any::{Any, AnyArguments, AnyColumn, AnyTypeInfo}; use crate::column::ColumnIndex; +use crate::database::{Database, HasStatement}; use crate::error::Error; use crate::ext::ustr::UStr; use crate::statement::Statement; @@ -9,10 +10,14 @@ use std::borrow::Cow; use std::sync::Arc; pub struct AnyStatement<'q> { - pub(crate) sql: Cow<'q, str>, - pub(crate) parameters: Option, usize>>, - pub(crate) column_names: Arc>, - pub(crate) columns: Vec, + #[doc(hidden)] + pub sql: Cow<'q, str>, + #[doc(hidden)] + pub parameters: Option, usize>>, + #[doc(hidden)] + pub column_names: Arc>, + #[doc(hidden)] + pub columns: Vec, } impl<'q> Statement<'q> for AnyStatement<'q> { @@ -46,10 +51,7 @@ impl<'q> Statement<'q> for AnyStatement<'q> { impl_statement_query!(AnyArguments<'_>); } -impl<'i> ColumnIndex> for &'i str -where - &'i str: AnyColumnIndex, -{ +impl<'i> ColumnIndex> for &'i str { fn index(&self, statement: &AnyStatement<'_>) -> Result { statement .column_names @@ -58,3 +60,41 @@ where .map(|v| *v) } } + +impl<'q> AnyStatement<'q> { + #[doc(hidden)] + pub fn try_from_statement( + query: &'q str, + statement: &S, + column_names: Arc>, + ) -> crate::Result + where + S: Statement<'q>, + AnyTypeInfo: for<'a> TryFrom<&'a ::TypeInfo, Error = Error>, + AnyColumn: for<'a> TryFrom<&'a ::Column, Error = Error>, + { + let parameters = match statement.parameters() { + Some(Either::Left(parameters)) => Some(Either::Left( + parameters + .iter() + .map(AnyTypeInfo::try_from) + .collect::, _>>()?, + )), + Some(Either::Right(count)) => Some(Either::Right(count)), + None => None, + }; + + let columns = statement + .columns() + .iter() + .map(AnyColumn::try_from) + .collect::, _>>()?; + + Ok(Self { + sql: query.into(), + columns, + column_names, + parameters, + }) + } +} diff --git a/sqlx-core/src/any/transaction.rs b/sqlx-core/src/any/transaction.rs index 248e25847c..215110eaec 100644 --- a/sqlx-core/src/any/transaction.rs +++ b/sqlx-core/src/any/transaction.rs @@ -1,6 +1,5 @@ use futures_util::future::BoxFuture; -use crate::any::connection::AnyConnectionKind; use crate::any::{Any, AnyConnection}; use crate::database::Database; use crate::error::Error; @@ -12,98 +11,18 @@ impl TransactionManager for AnyTransactionManager { type Database = Any; fn begin(conn: &mut AnyConnection) -> BoxFuture<'_, Result<(), Error>> { - match &mut conn.0 { - #[cfg(feature = "postgres")] - AnyConnectionKind::Postgres(conn) => { - ::TransactionManager::begin(conn) - } - - #[cfg(feature = "mysql")] - AnyConnectionKind::MySql(conn) => { - ::TransactionManager::begin(conn) - } - - #[cfg(feature = "sqlite")] - AnyConnectionKind::Sqlite(conn) => { - ::TransactionManager::begin(conn) - } - - #[cfg(feature = "mssql")] - AnyConnectionKind::Mssql(conn) => { - ::TransactionManager::begin(conn) - } - } + conn.backend.begin() } fn commit(conn: &mut AnyConnection) -> BoxFuture<'_, Result<(), Error>> { - match &mut conn.0 { - #[cfg(feature = "postgres")] - AnyConnectionKind::Postgres(conn) => { - ::TransactionManager::commit(conn) - } - - #[cfg(feature = "mysql")] - AnyConnectionKind::MySql(conn) => { - ::TransactionManager::commit(conn) - } - - #[cfg(feature = "sqlite")] - AnyConnectionKind::Sqlite(conn) => { - ::TransactionManager::commit(conn) - } - - #[cfg(feature = "mssql")] - AnyConnectionKind::Mssql(conn) => { - ::TransactionManager::commit(conn) - } - } + conn.backend.commit() } fn rollback(conn: &mut AnyConnection) -> BoxFuture<'_, Result<(), Error>> { - match &mut conn.0 { - #[cfg(feature = "postgres")] - AnyConnectionKind::Postgres(conn) => { - ::TransactionManager::rollback(conn) - } - - #[cfg(feature = "mysql")] - AnyConnectionKind::MySql(conn) => { - ::TransactionManager::rollback(conn) - } - - #[cfg(feature = "sqlite")] - AnyConnectionKind::Sqlite(conn) => { - ::TransactionManager::rollback(conn) - } - - #[cfg(feature = "mssql")] - AnyConnectionKind::Mssql(conn) => { - ::TransactionManager::rollback(conn) - } - } + conn.backend.rollback() } fn start_rollback(conn: &mut AnyConnection) { - match &mut conn.0 { - #[cfg(feature = "postgres")] - AnyConnectionKind::Postgres(conn) => { - ::TransactionManager::start_rollback(conn) - } - - #[cfg(feature = "mysql")] - AnyConnectionKind::MySql(conn) => { - ::TransactionManager::start_rollback(conn) - } - - #[cfg(feature = "sqlite")] - AnyConnectionKind::Sqlite(conn) => { - ::TransactionManager::start_rollback(conn) - } - - #[cfg(feature = "mssql")] - AnyConnectionKind::Mssql(conn) => { - ::TransactionManager::start_rollback(conn) - } - } + conn.backend.start_rollback() } } diff --git a/sqlx-core/src/any/type.rs b/sqlx-core/src/any/type.rs deleted file mode 100644 index 3df4136b65..0000000000 --- a/sqlx-core/src/any/type.rs +++ /dev/null @@ -1,40 +0,0 @@ -// Type is required by the bounds of the [`Row`] and [`Arguments`] trait but its been overridden in -// AnyRow and AnyArguments to not use this implementation; but instead, delegate to the -// database-specific implementation. -// -// The other use of this trait is for compile-time verification which is not feasible to support -// for the [`Any`] driver. -macro_rules! impl_any_type { - ($ty:ty) => { - impl crate::types::Type for $ty { - fn type_info() -> crate::any::AnyTypeInfo { - // FIXME: nicer panic explaining why this isn't possible - unimplemented!() - } - - fn compatible(ty: &crate::any::AnyTypeInfo) -> bool { - match &ty.0 { - #[cfg(feature = "postgres")] - crate::any::type_info::AnyTypeInfoKind::Postgres(ty) => { - <$ty as crate::types::Type>::compatible(&ty) - } - - #[cfg(feature = "mysql")] - crate::any::type_info::AnyTypeInfoKind::MySql(ty) => { - <$ty as crate::types::Type>::compatible(&ty) - } - - #[cfg(feature = "sqlite")] - crate::any::type_info::AnyTypeInfoKind::Sqlite(ty) => { - <$ty as crate::types::Type>::compatible(&ty) - } - - #[cfg(feature = "mssql")] - crate::any::type_info::AnyTypeInfoKind::Mssql(ty) => { - <$ty as crate::types::Type>::compatible(&ty) - } - } - } - } - }; -} diff --git a/sqlx-core/src/any/type_info.rs b/sqlx-core/src/any/type_info.rs index 3d3c914f16..a7eec4a953 100644 --- a/sqlx-core/src/any/type_info.rs +++ b/sqlx-core/src/any/type_info.rs @@ -2,84 +2,63 @@ use std::fmt::{self, Display, Formatter}; use crate::type_info::TypeInfo; -#[cfg(feature = "postgres")] -use crate::postgres::PgTypeInfo; - -#[cfg(feature = "mysql")] -use crate::mysql::MySqlTypeInfo; - -#[cfg(feature = "sqlite")] -use crate::sqlite::SqliteTypeInfo; - -#[cfg(feature = "mssql")] -use crate::mssql::MssqlTypeInfo; +use AnyTypeInfoKind::*; #[derive(Debug, Clone, PartialEq)] -pub struct AnyTypeInfo(pub(crate) AnyTypeInfoKind); - -#[derive(Debug, Clone, PartialEq)] -pub(crate) enum AnyTypeInfoKind { - #[cfg(feature = "postgres")] - Postgres(PgTypeInfo), - - #[cfg(feature = "mysql")] - MySql(MySqlTypeInfo), +pub struct AnyTypeInfo { + #[doc(hidden)] + pub kind: AnyTypeInfoKind, +} - #[cfg(feature = "sqlite")] - Sqlite(SqliteTypeInfo), +impl AnyTypeInfo { + pub fn kind(&self) -> AnyTypeInfoKind { + self.kind + } +} - #[cfg(feature = "mssql")] - Mssql(MssqlTypeInfo), +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum AnyTypeInfoKind { + Null, + Bool, + SmallInt, + Integer, + BigInt, + Real, + Double, + Text, + Blob, } impl TypeInfo for AnyTypeInfo { fn is_null(&self) -> bool { - match &self.0 { - #[cfg(feature = "postgres")] - AnyTypeInfoKind::Postgres(ty) => ty.is_null(), - - #[cfg(feature = "mysql")] - AnyTypeInfoKind::MySql(ty) => ty.is_null(), - - #[cfg(feature = "sqlite")] - AnyTypeInfoKind::Sqlite(ty) => ty.is_null(), - - #[cfg(feature = "mssql")] - AnyTypeInfoKind::Mssql(ty) => ty.is_null(), - } + false } fn name(&self) -> &str { - match &self.0 { - #[cfg(feature = "postgres")] - AnyTypeInfoKind::Postgres(ty) => ty.name(), - - #[cfg(feature = "mysql")] - AnyTypeInfoKind::MySql(ty) => ty.name(), - - #[cfg(feature = "sqlite")] - AnyTypeInfoKind::Sqlite(ty) => ty.name(), - - #[cfg(feature = "mssql")] - AnyTypeInfoKind::Mssql(ty) => ty.name(), + use AnyTypeInfoKind::*; + + match self.kind { + Bool => "BOOLEAN", + SmallInt => "SMALLINT", + Integer => "INTEGER", + BigInt => "BIGINT", + Real => "REAL", + Double => "DOUBLE", + Text => "TEXT", + Blob => "BLOB", + Null => "NULL", } } } impl Display for AnyTypeInfo { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { - match &self.0 { - #[cfg(feature = "postgres")] - AnyTypeInfoKind::Postgres(ty) => ty.fmt(f), - - #[cfg(feature = "mysql")] - AnyTypeInfoKind::MySql(ty) => ty.fmt(f), - - #[cfg(feature = "sqlite")] - AnyTypeInfoKind::Sqlite(ty) => ty.fmt(f), + f.write_str(self.name()) + } +} - #[cfg(feature = "mssql")] - AnyTypeInfoKind::Mssql(ty) => ty.fmt(f), - } +impl AnyTypeInfoKind { + pub fn is_integer(&self) -> bool { + matches!(self, SmallInt | Integer | BigInt) } } diff --git a/sqlx-core/src/any/types.rs b/sqlx-core/src/any/types.rs deleted file mode 100644 index ace9e2bd4d..0000000000 --- a/sqlx-core/src/any/types.rs +++ /dev/null @@ -1,195 +0,0 @@ -//! Conversions between Rust and standard **SQL** types. -//! -//! # Types -//! -//! | Rust type | SQL type(s) | -//! |---------------------------------------|------------------------------------------------------| -//! | `bool` | BOOLEAN | -//! | `i16` | SMALLINT | -//! | `i32` | INT | -//! | `i64` | BIGINT | -//! | `f32` | FLOAT | -//! | `f64` | DOUBLE | -//! | `&str`, [`String`] | VARCHAR, CHAR, TEXT | -//! -//! # Nullable -//! -//! In addition, `Option` is supported where `T` implements `Type`. An `Option` represents -//! a potentially `NULL` value from SQL. -//! - -// Type - -impl_any_type!(bool); - -impl_any_type!(i16); -impl_any_type!(i32); -impl_any_type!(i64); - -impl_any_type!(f32); -impl_any_type!(f64); - -impl_any_type!(str); -impl_any_type!(String); - -// Encode - -impl_any_encode!(bool); - -impl_any_encode!(i16); -impl_any_encode!(i32); -impl_any_encode!(i64); - -impl_any_encode!(f32); -impl_any_encode!(f64); - -impl_any_encode!(&'q str); -impl_any_encode!(String); - -// Decode - -impl_any_decode!(bool); - -impl_any_decode!(i16); -impl_any_decode!(i32); -impl_any_decode!(i64); - -impl_any_decode!(f32); -impl_any_decode!(f64); - -impl_any_decode!(&'r str); -impl_any_decode!(String); - -// Conversions for Blob SQL types -// Type -#[cfg(all( - any(feature = "mysql", feature = "sqlite", feature = "postgres"), - not(feature = "mssql") -))] -impl_any_type!([u8]); -#[cfg(all( - any(feature = "mysql", feature = "sqlite", feature = "postgres"), - not(feature = "mssql") -))] -impl_any_type!(Vec); - -// Encode -#[cfg(all( - any(feature = "mysql", feature = "sqlite", feature = "postgres"), - not(feature = "mssql") -))] -impl_any_encode!(&'q [u8]); -#[cfg(all( - any(feature = "mysql", feature = "sqlite", feature = "postgres"), - not(feature = "mssql") -))] -impl_any_encode!(Vec); - -// Decode -#[cfg(all( - any(feature = "mysql", feature = "sqlite", feature = "postgres"), - not(feature = "mssql") -))] -impl_any_decode!(&'r [u8]); -#[cfg(all( - any(feature = "mysql", feature = "sqlite", feature = "postgres"), - not(feature = "mssql") -))] -impl_any_decode!(Vec); - -// Conversions for Time SQL types -// Type -#[cfg(all( - feature = "chrono", - any(feature = "mysql", feature = "sqlite", feature = "postgres"), - not(feature = "mssql") -))] -impl_any_type!(chrono::NaiveDate); -#[cfg(all( - feature = "chrono", - any(feature = "mysql", feature = "sqlite", feature = "postgres"), - not(feature = "mssql") -))] -impl_any_type!(chrono::NaiveTime); -#[cfg(all( - feature = "chrono", - any(feature = "mysql", feature = "sqlite", feature = "postgres"), - not(feature = "mssql") -))] -impl_any_type!(chrono::NaiveDateTime); -#[cfg(all( - feature = "chrono", - any(feature = "mysql", feature = "sqlite", feature = "postgres"), - not(feature = "mssql") -))] -impl_any_type!(chrono::DateTime); -#[cfg(all( - feature = "chrono", - any(feature = "sqlite", feature = "postgres", feature = "mysql"), - not(feature = "mssql") -))] -impl_any_type!(chrono::DateTime); - -// Encode -#[cfg(all( - feature = "chrono", - any(feature = "mysql", feature = "sqlite", feature = "postgres"), - not(feature = "mssql") -))] -impl_any_encode!(chrono::NaiveDate); -#[cfg(all( - feature = "chrono", - any(feature = "mysql", feature = "sqlite", feature = "postgres"), - not(feature = "mssql") -))] -impl_any_encode!(chrono::NaiveTime); -#[cfg(all( - feature = "chrono", - any(feature = "mysql", feature = "sqlite", feature = "postgres"), - not(feature = "mssql") -))] -impl_any_encode!(chrono::NaiveDateTime); -#[cfg(all( - feature = "chrono", - any(feature = "mysql", feature = "sqlite", feature = "postgres"), - not(feature = "mssql") -))] -impl_any_encode!(chrono::DateTime); -#[cfg(all( - feature = "chrono", - any(feature = "sqlite", feature = "postgres", feature = "mysql"), - not(feature = "mssql") -))] -impl_any_encode!(chrono::DateTime); - -// Decode -#[cfg(all( - feature = "chrono", - any(feature = "mysql", feature = "sqlite", feature = "postgres"), - not(feature = "mssql") -))] -impl_any_decode!(chrono::NaiveDate); -#[cfg(all( - feature = "chrono", - any(feature = "mysql", feature = "sqlite", feature = "postgres"), - not(feature = "mssql") -))] -impl_any_decode!(chrono::NaiveTime); -#[cfg(all( - feature = "chrono", - any(feature = "mysql", feature = "sqlite", feature = "postgres"), - not(feature = "mssql") -))] -impl_any_decode!(chrono::NaiveDateTime); -#[cfg(all( - feature = "chrono", - any(feature = "mysql", feature = "sqlite", feature = "postgres"), - not(feature = "mssql") -))] -impl_any_decode!(chrono::DateTime); -#[cfg(all( - feature = "chrono", - any(feature = "sqlite", feature = "postgres", feature = "mysql"), - not(feature = "mssql") -))] -impl_any_decode!(chrono::DateTime); diff --git a/sqlx-core/src/any/types/blob.rs b/sqlx-core/src/any/types/blob.rs new file mode 100644 index 0000000000..80d9602694 --- /dev/null +++ b/sqlx-core/src/any/types/blob.rs @@ -0,0 +1,58 @@ +use crate::any::{Any, AnyTypeInfo, AnyTypeInfoKind, AnyValueKind}; +use crate::database::{HasArguments, HasValueRef}; +use crate::decode::Decode; +use crate::encode::{Encode, IsNull}; +use crate::error::BoxDynError; +use crate::types::Type; +use std::borrow::Cow; + +impl Type for [u8] { + fn type_info() -> AnyTypeInfo { + AnyTypeInfo { + kind: AnyTypeInfoKind::Blob, + } + } +} + +impl<'q> Encode<'q, Any> for &'q [u8] { + fn encode_by_ref(&self, buf: &mut >::ArgumentBuffer) -> IsNull { + buf.0.push(AnyValueKind::Blob((*self).into())); + IsNull::No + } +} + +impl<'r> Decode<'r, Any> for &'r [u8] { + fn decode(value: >::ValueRef) -> Result { + match value.kind { + AnyValueKind::Blob(Cow::Borrowed(blob)) => Ok(blob), + // This shouldn't happen in practice, it means the user got an `AnyValueRef` + // constructed from an owned `Vec` which shouldn't be allowed by the API. + AnyValueKind::Blob(Cow::Owned(_text)) => { + panic!("attempting to return a borrow that outlives its buffer") + } + other => other.unexpected(), + } + } +} + +impl Type for Vec { + fn type_info() -> AnyTypeInfo { + <[u8] as Type>::type_info() + } +} + +impl<'q> Encode<'q, Any> for Vec { + fn encode_by_ref(&self, buf: &mut >::ArgumentBuffer) -> IsNull { + buf.0.push(AnyValueKind::Blob(Cow::Owned(self.clone()))); + IsNull::No + } +} + +impl<'r> Decode<'r, Any> for Vec { + fn decode(value: >::ValueRef) -> Result { + match value.kind { + AnyValueKind::Blob(blob) => Ok(blob.into_owned()), + other => other.unexpected(), + } + } +} diff --git a/sqlx-core/src/any/types/bool.rs b/sqlx-core/src/any/types/bool.rs new file mode 100644 index 0000000000..1f2a05c91a --- /dev/null +++ b/sqlx-core/src/any/types/bool.rs @@ -0,0 +1,30 @@ +use crate::any::{Any, AnyTypeInfo, AnyTypeInfoKind, AnyValueKind}; +use crate::database::{HasArguments, HasValueRef}; +use crate::decode::Decode; +use crate::encode::{Encode, IsNull}; +use crate::error::BoxDynError; +use crate::types::Type; + +impl Type for bool { + fn type_info() -> AnyTypeInfo { + AnyTypeInfo { + kind: AnyTypeInfoKind::Bool, + } + } +} + +impl<'q> Encode<'q, Any> for bool { + fn encode_by_ref(&self, buf: &mut >::ArgumentBuffer) -> IsNull { + buf.0.push(AnyValueKind::Bool(*self)); + IsNull::No + } +} + +impl<'r> Decode<'r, Any> for bool { + fn decode(value: >::ValueRef) -> Result { + match value.kind { + AnyValueKind::Bool(b) => Ok(b), + other => other.unexpected(), + } + } +} diff --git a/sqlx-core/src/any/types/float.rs b/sqlx-core/src/any/types/float.rs new file mode 100644 index 0000000000..8daa5a6aad --- /dev/null +++ b/sqlx-core/src/any/types/float.rs @@ -0,0 +1,56 @@ +use crate::any::{Any, AnyArgumentBuffer, AnyTypeInfo, AnyTypeInfoKind, AnyValueKind, AnyValueRef}; +use crate::database::{HasArguments, HasValueRef}; +use crate::decode::Decode; +use crate::encode::{Encode, IsNull}; +use crate::error::BoxDynError; +use crate::types::Type; + +impl Type for f32 { + fn type_info() -> AnyTypeInfo { + AnyTypeInfo { + kind: AnyTypeInfoKind::Real, + } + } +} + +impl<'q> Encode<'q, Any> for f32 { + fn encode_by_ref(&self, buf: &mut AnyArgumentBuffer) -> IsNull { + buf.0.push(AnyValueKind::Real(*self)); + IsNull::No + } +} + +impl<'r> Decode<'r, Any> for f32 { + fn decode(value: AnyValueRef<'r>) -> Result { + match value.kind { + AnyValueKind::Real(r) => Ok(r), + other => other.unexpected(), + } + } +} + +impl Type for f64 { + fn type_info() -> AnyTypeInfo { + AnyTypeInfo { + kind: AnyTypeInfoKind::Double, + } + } +} + +impl<'q> Encode<'q, Any> for f64 { + fn encode_by_ref(&self, buf: &mut >::ArgumentBuffer) -> IsNull { + buf.0.push(AnyValueKind::Double(*self)); + IsNull::No + } +} + +impl<'r> Decode<'r, Any> for f64 { + fn decode(value: >::ValueRef) -> Result { + match value.kind { + // Widening is safe + AnyValueKind::Real(r) => Ok(r as f64), + AnyValueKind::Double(d) => Ok(d), + other => other.unexpected(), + } + } +} diff --git a/sqlx-core/src/any/types/int.rs b/sqlx-core/src/any/types/int.rs new file mode 100644 index 0000000000..d3dfdc136e --- /dev/null +++ b/sqlx-core/src/any/types/int.rs @@ -0,0 +1,81 @@ +use crate::any::{Any, AnyTypeInfo, AnyTypeInfoKind, AnyValueKind}; +use crate::database::{HasArguments, HasValueRef}; +use crate::decode::Decode; +use crate::encode::{Encode, IsNull}; +use crate::error::BoxDynError; +use crate::types::Type; + +impl Type for i16 { + fn type_info() -> AnyTypeInfo { + AnyTypeInfo { + kind: AnyTypeInfoKind::SmallInt, + } + } + + fn compatible(ty: &AnyTypeInfo) -> bool { + ty.kind().is_integer() + } +} + +impl<'q> Encode<'q, Any> for i16 { + fn encode_by_ref(&self, buf: &mut >::ArgumentBuffer) -> IsNull { + buf.0.push(AnyValueKind::SmallInt(*self)); + IsNull::No + } +} + +impl<'r> Decode<'r, Any> for i16 { + fn decode(value: >::ValueRef) -> Result { + value.kind.try_integer() + } +} + +impl Type for i32 { + fn type_info() -> AnyTypeInfo { + AnyTypeInfo { + kind: AnyTypeInfoKind::Integer, + } + } + + fn compatible(ty: &AnyTypeInfo) -> bool { + ty.kind().is_integer() + } +} + +impl<'q> Encode<'q, Any> for i32 { + fn encode_by_ref(&self, buf: &mut >::ArgumentBuffer) -> IsNull { + buf.0.push(AnyValueKind::Integer(*self)); + IsNull::No + } +} + +impl<'r> Decode<'r, Any> for i32 { + fn decode(value: >::ValueRef) -> Result { + value.kind.try_integer() + } +} + +impl Type for i64 { + fn type_info() -> AnyTypeInfo { + AnyTypeInfo { + kind: AnyTypeInfoKind::BigInt, + } + } + + fn compatible(ty: &AnyTypeInfo) -> bool { + ty.kind().is_integer() + } +} + +impl<'q> Encode<'q, Any> for i64 { + fn encode_by_ref(&self, buf: &mut >::ArgumentBuffer) -> IsNull { + buf.0.push(AnyValueKind::BigInt(*self)); + IsNull::No + } +} + +impl<'r> Decode<'r, Any> for i64 { + fn decode(value: >::ValueRef) -> Result { + value.kind.try_integer() + } +} diff --git a/sqlx-core/src/any/types/mod.rs b/sqlx-core/src/any/types/mod.rs new file mode 100644 index 0000000000..9d0be52a9b --- /dev/null +++ b/sqlx-core/src/any/types/mod.rs @@ -0,0 +1,58 @@ +//! Conversions between Rust and standard **SQL** types. +//! +//! # Types +//! +//! | Rust type | SQL type(s) | +//! |---------------------------------------|------------------------------------------------------| +//! | `bool` | BOOLEAN | +//! | `i16` | SMALLINT | +//! | `i32` | INT | +//! | `i64` | BIGINT | +//! | `f32` | FLOAT | +//! | `f64` | DOUBLE | +//! | `&str`, [`String`] | VARCHAR, CHAR, TEXT | +//! +//! # Nullable +//! +//! In addition, `Option` is supported where `T` implements `Type`. An `Option` represents +//! a potentially `NULL` value from SQL. + +use crate::any::type_info::AnyTypeInfoKind; +use crate::any::value::AnyValueKind; +use crate::any::{Any, AnyTypeInfo, AnyValueRef}; +use crate::database::{HasArguments, HasValueRef}; +use crate::decode::Decode; +use crate::encode::{Encode, IsNull}; +use crate::error::BoxDynError; +use crate::types::Type; +use std::borrow::Cow; + +mod blob; +mod bool; +mod float; +mod int; +mod str; + +#[test] +fn test_type_impls() { + fn has_type() + where + T: Type, + for<'a> T: Encode<'a, Any>, + for<'a> T: Decode<'a, Any>, + { + } + + has_type::(); + + has_type::(); + has_type::(); + has_type::(); + + has_type::(); + has_type::(); + + // These imply that there are also impls for the equivalent slice types. + has_type::>(); + has_type::(); +} diff --git a/sqlx-core/src/any/types/str.rs b/sqlx-core/src/any/types/str.rs new file mode 100644 index 0000000000..4f519ddbc3 --- /dev/null +++ b/sqlx-core/src/any/types/str.rs @@ -0,0 +1,66 @@ +use crate::any::types::str; +use crate::any::{Any, AnyTypeInfo, AnyTypeInfoKind, AnyValueKind}; +use crate::database::{HasArguments, HasValueRef}; +use crate::decode::Decode; +use crate::encode::{Encode, IsNull}; +use crate::error::BoxDynError; +use crate::types::Type; +use std::borrow::Cow; + +impl Type for str { + fn type_info() -> AnyTypeInfo { + AnyTypeInfo { + kind: AnyTypeInfoKind::Text, + } + } +} + +impl<'a> Encode<'a, Any> for &'a str { + fn encode(self, buf: &mut >::ArgumentBuffer) -> IsNull + where + Self: Sized, + { + buf.0.push(AnyValueKind::Text(self.into())); + IsNull::No + } + + fn encode_by_ref(&self, buf: &mut >::ArgumentBuffer) -> IsNull { + (*self).encode(buf) + } +} + +impl<'a> Decode<'a, Any> for &'a str { + fn decode(value: >::ValueRef) -> Result { + match value.kind { + AnyValueKind::Text(Cow::Borrowed(text)) => Ok(text), + // This shouldn't happen in practice, it means the user got an `AnyValueRef` + // constructed from an owned `String` which shouldn't be allowed by the API. + AnyValueKind::Text(Cow::Owned(_text)) => { + panic!("attempting to return a borrow that outlives its buffer") + } + other => other.unexpected(), + } + } +} + +impl Type for String { + fn type_info() -> AnyTypeInfo { + >::type_info() + } +} + +impl<'q> Encode<'q, Any> for String { + fn encode_by_ref(&self, buf: &mut >::ArgumentBuffer) -> IsNull { + buf.0.push(AnyValueKind::Text(Cow::Owned(self.clone()))); + IsNull::No + } +} + +impl<'r> Decode<'r, Any> for String { + fn decode(value: >::ValueRef) -> Result { + match value.kind { + AnyValueKind::Text(text) => Ok(text.into_owned()), + other => other.unexpected(), + } + } +} diff --git a/sqlx-core/src/any/value.rs b/sqlx-core/src/any/value.rs index 73dd01fdcf..6c1e1eecc2 100644 --- a/sqlx-core/src/any/value.rs +++ b/sqlx-core/src/any/value.rs @@ -1,155 +1,131 @@ use std::borrow::Cow; +use std::marker::PhantomData; +use std::num::TryFromIntError; use crate::any::error::mismatched_types; -use crate::any::{Any, AnyTypeInfo}; -use crate::database::HasValueRef; +use crate::any::{Any, AnyTypeInfo, AnyTypeInfoKind}; +use crate::database::{Database, HasValueRef}; use crate::decode::Decode; -use crate::error::Error; +use crate::error::{BoxDynError, Error}; +use crate::io::Encode; use crate::type_info::TypeInfo; use crate::types::Type; use crate::value::{Value, ValueRef}; -#[cfg(feature = "postgres")] -use crate::postgres::{PgValue, PgValueRef}; - -#[cfg(feature = "mysql")] -use crate::mysql::{MySqlValue, MySqlValueRef}; - -#[cfg(feature = "sqlite")] -use crate::sqlite::{SqliteValue, SqliteValueRef}; - -#[cfg(feature = "mssql")] -use crate::mssql::{MssqlValue, MssqlValueRef}; - -pub struct AnyValue { - pub(crate) kind: AnyValueKind, - pub(crate) type_info: AnyTypeInfo, +#[derive(Clone, Debug)] +#[non_exhaustive] +pub enum AnyValueKind<'a> { + Null, + Bool(bool), + SmallInt(i16), + Integer(i32), + BigInt(i64), + Real(f32), + Double(f64), + Text(Cow<'a, str>), + Blob(Cow<'a, [u8]>), } -pub(crate) enum AnyValueKind { - #[cfg(feature = "postgres")] - Postgres(PgValue), - - #[cfg(feature = "mysql")] - MySql(MySqlValue), +impl AnyValueKind<'_> { + fn type_info(&self) -> AnyTypeInfo { + AnyTypeInfo { + kind: match self { + AnyValueKind::Null => AnyTypeInfoKind::Null, + AnyValueKind::Bool(_) => AnyTypeInfoKind::Bool, + AnyValueKind::SmallInt(_) => AnyTypeInfoKind::SmallInt, + AnyValueKind::Integer(_) => AnyTypeInfoKind::Integer, + AnyValueKind::BigInt(_) => AnyTypeInfoKind::BigInt, + AnyValueKind::Real(_) => AnyTypeInfoKind::Real, + AnyValueKind::Double(_) => AnyTypeInfoKind::Double, + AnyValueKind::Text(_) => AnyTypeInfoKind::Text, + AnyValueKind::Blob(_) => AnyTypeInfoKind::Blob, + }, + } + } - #[cfg(feature = "sqlite")] - Sqlite(SqliteValue), + pub(in crate::any) fn unexpected>(&self) -> Result { + Err(format!("expected {}, got {:?}", Expected::type_info(), self).into()) + } - #[cfg(feature = "mssql")] - Mssql(MssqlValue), + pub(in crate::any) fn try_integer(&self) -> Result + where + T: Type + TryFrom + TryFrom + TryFrom, + BoxDynError: From<>::Error>, + BoxDynError: From<>::Error>, + BoxDynError: From<>::Error>, + { + Ok(match self { + AnyValueKind::SmallInt(i) => (*i).try_into()?, + AnyValueKind::Integer(i) => (*i).try_into()?, + AnyValueKind::BigInt(i) => (*i).try_into()?, + _ => return self.unexpected(), + }) + } } -pub struct AnyValueRef<'r> { - pub(crate) kind: AnyValueRefKind<'r>, - pub(crate) type_info: AnyTypeInfo, +#[derive(Clone, Debug)] +pub struct AnyValue { + #[doc(hidden)] + pub kind: AnyValueKind<'static>, } -pub(crate) enum AnyValueRefKind<'r> { - #[cfg(feature = "postgres")] - Postgres(PgValueRef<'r>), - - #[cfg(feature = "mysql")] - MySql(MySqlValueRef<'r>), - - #[cfg(feature = "sqlite")] - Sqlite(SqliteValueRef<'r>), - - #[cfg(feature = "mssql")] - Mssql(MssqlValueRef<'r>), +#[derive(Clone, Debug)] +pub struct AnyValueRef<'a> { + pub(crate) kind: AnyValueKind<'a>, } impl Value for AnyValue { type Database = Any; fn as_ref(&self) -> >::ValueRef { - match &self.kind { - #[cfg(feature = "postgres")] - AnyValueKind::Postgres(value) => value.as_ref().into(), - - #[cfg(feature = "mysql")] - AnyValueKind::MySql(value) => value.as_ref().into(), - - #[cfg(feature = "sqlite")] - AnyValueKind::Sqlite(value) => value.as_ref().into(), - - #[cfg(feature = "mssql")] - AnyValueKind::Mssql(value) => value.as_ref().into(), + AnyValueRef { + kind: match &self.kind { + AnyValueKind::Null => AnyValueKind::Null, + AnyValueKind::Bool(b) => AnyValueKind::Bool(*b), + AnyValueKind::SmallInt(i) => AnyValueKind::SmallInt(*i), + AnyValueKind::Integer(i) => AnyValueKind::Integer(*i), + AnyValueKind::BigInt(i) => AnyValueKind::BigInt(*i), + AnyValueKind::Real(r) => AnyValueKind::Real(*r), + AnyValueKind::Double(d) => AnyValueKind::Double(*d), + AnyValueKind::Text(t) => AnyValueKind::Text(Cow::Borrowed(t)), + AnyValueKind::Blob(b) => AnyValueKind::Blob(Cow::Borrowed(b)), + }, } } - fn type_info(&self) -> Cow<'_, AnyTypeInfo> { - Cow::Borrowed(&self.type_info) + fn type_info(&self) -> Cow<'_, ::TypeInfo> { + Cow::Owned(self.kind.type_info()) } fn is_null(&self) -> bool { - match &self.kind { - #[cfg(feature = "postgres")] - AnyValueKind::Postgres(value) => value.is_null(), - - #[cfg(feature = "mysql")] - AnyValueKind::MySql(value) => value.is_null(), - - #[cfg(feature = "sqlite")] - AnyValueKind::Sqlite(value) => value.is_null(), - - #[cfg(feature = "mssql")] - AnyValueKind::Mssql(value) => value.is_null(), - } - } - - fn try_decode<'r, T>(&'r self) -> Result - where - T: Decode<'r, Self::Database> + Type, - { - if !self.is_null() { - let ty = self.type_info(); - - if !ty.is_null() && !T::compatible(&ty) { - return Err(Error::Decode(mismatched_types::(&ty))); - } - } - - self.try_decode_unchecked() + false } } -impl<'r> ValueRef<'r> for AnyValueRef<'r> { +impl<'a> ValueRef<'a> for AnyValueRef<'a> { type Database = Any; - fn to_owned(&self) -> AnyValue { - match &self.kind { - #[cfg(feature = "postgres")] - AnyValueRefKind::Postgres(value) => ValueRef::to_owned(value).into(), - - #[cfg(feature = "mysql")] - AnyValueRefKind::MySql(value) => ValueRef::to_owned(value).into(), - - #[cfg(feature = "sqlite")] - AnyValueRefKind::Sqlite(value) => ValueRef::to_owned(value).into(), - - #[cfg(feature = "mssql")] - AnyValueRefKind::Mssql(value) => ValueRef::to_owned(value).into(), + fn to_owned(&self) -> ::Value { + AnyValue { + kind: match &self.kind { + AnyValueKind::Null => AnyValueKind::Null, + AnyValueKind::Bool(b) => AnyValueKind::Bool(*b), + AnyValueKind::SmallInt(i) => AnyValueKind::SmallInt(*i), + AnyValueKind::Integer(i) => AnyValueKind::Integer(*i), + AnyValueKind::BigInt(i) => AnyValueKind::BigInt(*i), + AnyValueKind::Real(r) => AnyValueKind::Real(*r), + AnyValueKind::Double(d) => AnyValueKind::Double(*d), + AnyValueKind::Text(t) => AnyValueKind::Text(Cow::Owned(t.to_string())), + AnyValueKind::Blob(b) => AnyValueKind::Blob(Cow::Owned(b.to_vec())), + }, } } - fn type_info(&self) -> Cow<'_, AnyTypeInfo> { - Cow::Borrowed(&self.type_info) + fn type_info(&self) -> Cow<'_, ::TypeInfo> { + Cow::Owned(self.kind.type_info()) } fn is_null(&self) -> bool { - match &self.kind { - #[cfg(feature = "postgres")] - AnyValueRefKind::Postgres(value) => value.is_null(), - - #[cfg(feature = "mysql")] - AnyValueRefKind::MySql(value) => value.is_null(), - - #[cfg(feature = "sqlite")] - AnyValueRefKind::Sqlite(value) => value.is_null(), - - #[cfg(feature = "mssql")] - AnyValueRefKind::Mssql(value) => value.is_null(), - } + false } } diff --git a/sqlx-core/src/arguments.rs b/sqlx-core/src/arguments.rs index 46acd3f76b..c952d7df9f 100644 --- a/sqlx-core/src/arguments.rs +++ b/sqlx-core/src/arguments.rs @@ -28,13 +28,13 @@ pub trait IntoArguments<'q, DB: HasArguments<'q>>: Sized + Send { } // NOTE: required due to lack of lazy normalization -#[allow(unused_macros)] +#[macro_export] macro_rules! impl_into_arguments_for_arguments { ($Arguments:path) => { impl<'q> - crate::arguments::IntoArguments< + $crate::arguments::IntoArguments< 'q, - <$Arguments as crate::arguments::Arguments<'q>>::Database, + <$Arguments as $crate::arguments::Arguments<'q>>::Database, > for $Arguments { fn into_arguments(self) -> $Arguments { diff --git a/sqlx-core/src/column.rs b/sqlx-core/src/column.rs index e670e3b4cd..db552324e5 100644 --- a/sqlx-core/src/column.rs +++ b/sqlx-core/src/column.rs @@ -1,8 +1,9 @@ use crate::database::Database; use crate::error::Error; + use std::fmt::Debug; -pub trait Column: private_column::Sealed + 'static + Send + Sync + Debug { +pub trait Column: 'static + Send + Sync + Debug { type Database: Database; /// Gets the column ordinal. @@ -21,11 +22,6 @@ pub trait Column: private_column::Sealed + 'static + Send + Sync + Debug { fn type_info(&self) -> &::TypeInfo; } -// Prevent users from implementing the `Row` trait. -pub(crate) mod private_column { - pub trait Sealed {} -} - /// A type that can be used to index into a [`Row`] or [`Statement`]. /// /// The [`get`] and [`try_get`] methods of [`Row`] accept any type that implements `ColumnIndex`. @@ -39,7 +35,7 @@ pub(crate) mod private_column { /// [`get`]: crate::row::Row::get /// [`try_get`]: crate::row::Row::try_get /// -pub trait ColumnIndex: private_column_index::Sealed + Debug { +pub trait ColumnIndex: Debug { /// Returns a valid positional index into the row or statement, [`ColumnIndexOutOfBounds`], or, /// [`ColumnNotFound`]. /// @@ -55,14 +51,15 @@ impl + ?Sized> ColumnIndex for &'_ I { } } +#[macro_export] macro_rules! impl_column_index_for_row { ($R:ident) => { - impl crate::column::ColumnIndex<$R> for usize { - fn index(&self, row: &$R) -> Result { - let len = crate::row::Row::len(row); + impl $crate::column::ColumnIndex<$R> for usize { + fn index(&self, row: &$R) -> Result { + let len = $crate::row::Row::len(row); if *self >= len { - return Err(crate::error::Error::ColumnIndexOutOfBounds { len, index: *self }); + return Err($crate::error::Error::ColumnIndexOutOfBounds { len, index: *self }); } Ok(*self) @@ -71,14 +68,15 @@ macro_rules! impl_column_index_for_row { }; } +#[macro_export] macro_rules! impl_column_index_for_statement { ($S:ident) => { - impl crate::column::ColumnIndex<$S<'_>> for usize { - fn index(&self, statement: &$S<'_>) -> Result { - let len = crate::statement::Statement::columns(statement).len(); + impl $crate::column::ColumnIndex<$S<'_>> for usize { + fn index(&self, statement: &$S<'_>) -> Result { + let len = $crate::statement::Statement::columns(statement).len(); if *self >= len { - return Err(crate::error::Error::ColumnIndexOutOfBounds { len, index: *self }); + return Err($crate::error::Error::ColumnIndexOutOfBounds { len, index: *self }); } Ok(*self) @@ -86,12 +84,3 @@ macro_rules! impl_column_index_for_statement { } }; } - -// 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 {} -} diff --git a/sqlx-core/src/common/mod.rs b/sqlx-core/src/common/mod.rs index 63ed52815b..794007155f 100644 --- a/sqlx-core/src/common/mod.rs +++ b/sqlx-core/src/common/mod.rs @@ -1,11 +1,11 @@ mod statement_cache; -pub(crate) use statement_cache::StatementCache; +pub use statement_cache::StatementCache; use std::fmt::{Debug, Formatter}; use std::ops::{Deref, DerefMut}; /// A wrapper for `Fn`s that provides a debug impl that just says "Function" -pub(crate) struct DebugFn(pub F); +pub struct DebugFn(pub F); impl Deref for DebugFn { type Target = F; diff --git a/sqlx-core/src/common/statement_cache.rs b/sqlx-core/src/common/statement_cache.rs index dcf48c9465..b189b8abbb 100644 --- a/sqlx-core/src/common/statement_cache.rs +++ b/sqlx-core/src/common/statement_cache.rs @@ -49,7 +49,6 @@ impl StatementCache { } /// Clear all cached statements from the cache. - #[cfg(feature = "sqlite")] pub fn clear(&mut self) { self.inner.clear(); } diff --git a/sqlx-core/src/connection.rs b/sqlx-core/src/connection.rs index 80469ea3e5..829e4bb599 100644 --- a/sqlx-core/src/connection.rs +++ b/sqlx-core/src/connection.rs @@ -1,11 +1,13 @@ use crate::database::{Database, HasStatementCache}; use crate::error::Error; + use crate::transaction::Transaction; use futures_core::future::BoxFuture; use log::LevelFilter; use std::fmt::Debug; use std::str::FromStr; use std::time::Duration; +use url::Url; /// Represents a single database connection. pub trait Connection: Send { @@ -44,15 +46,12 @@ pub trait Connection: Send { /// # Example /// /// ```rust - /// use sqlx_core::connection::Connection; - /// use sqlx_core::error::Error; - /// use sqlx_core::executor::Executor; - /// use sqlx_core::postgres::{PgConnection, PgRow}; - /// use sqlx_core::query::query; + /// use sqlx::postgres::{PgConnection, PgRow}; + /// use sqlx::Connection; /// - /// # pub async fn _f(conn: &mut PgConnection) -> Result, Error> { - /// conn.transaction(|conn|Box::pin(async move { - /// query("select * from ..").fetch_all(conn).await + /// # pub async fn _f(conn: &mut PgConnection) -> sqlx::Result> { + /// conn.transaction(|txn| Box::pin(async move { + /// sqlx::query("select * from ..").fetch_all(&mut **txn).await /// })).await /// # } /// ``` @@ -132,10 +131,11 @@ pub trait Connection: Send { } #[derive(Clone, Debug)] -pub(crate) struct LogSettings { - pub(crate) statements_level: LevelFilter, - pub(crate) slow_statements_level: LevelFilter, - pub(crate) slow_statements_duration: Duration, +#[non_exhaustive] +pub struct LogSettings { + pub statements_level: LevelFilter, + pub slow_statements_level: LevelFilter, + pub slow_statements_duration: Duration, } impl Default for LogSettings { @@ -149,10 +149,10 @@ impl Default for LogSettings { } impl LogSettings { - pub(crate) fn log_statements(&mut self, level: LevelFilter) { + pub fn log_statements(&mut self, level: LevelFilter) { self.statements_level = level; } - pub(crate) fn log_slow_statements(&mut self, level: LevelFilter, duration: Duration) { + pub fn log_slow_statements(&mut self, level: LevelFilter, duration: Duration) { self.slow_statements_level = level; self.slow_statements_duration = duration; } @@ -161,6 +161,9 @@ impl LogSettings { pub trait ConnectOptions: 'static + Send + Sync + FromStr + Debug + Clone { type Connection: Connection + ?Sized; + /// Parse the `ConnectOptions` from a URL. + fn from_url(url: &Url) -> Result; + /// Establish a new database connection with the options specified by `self`. fn connect(&self) -> BoxFuture<'_, Result> where diff --git a/sqlx-core/src/database.rs b/sqlx-core/src/database.rs index 4484f3315a..be62924077 100644 --- a/sqlx-core/src/database.rs +++ b/sqlx-core/src/database.rs @@ -59,6 +59,7 @@ use crate::arguments::Arguments; use crate::column::Column; use crate::connection::Connection; use crate::row::Row; + use crate::statement::Statement; use crate::transaction::TransactionManager; use crate::type_info::TypeInfo; @@ -98,6 +99,12 @@ pub trait Database: /// 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; + + /// The display name for this database driver. + const NAME: &'static str; + + /// The schemes for database URLs that should match this driver. + const URL_SCHEMES: &'static [&'static str]; } /// Associate [`Database`] with a [`ValueRef`](crate::value::ValueRef) of a generic lifetime. diff --git a/sqlx-core/src/decode.rs b/sqlx-core/src/decode.rs index 40b85e0e2b..e913668e07 100644 --- a/sqlx-core/src/decode.rs +++ b/sqlx-core/src/decode.rs @@ -2,6 +2,7 @@ use crate::database::{Database, HasValueRef}; use crate::error::BoxDynError; + use crate::value::ValueRef; /// A type that can be decoded from the database. diff --git a/sqlx-core/src/describe.rs b/sqlx-core/src/describe.rs index 91e6242b73..83dc6a4abc 100644 --- a/sqlx-core/src/describe.rs +++ b/sqlx-core/src/describe.rs @@ -19,9 +19,9 @@ use std::convert::identity; )] #[doc(hidden)] pub struct Describe { - pub(crate) columns: Vec, - pub(crate) parameters: Option, usize>>, - pub(crate) nullable: Vec>, + pub columns: Vec, + pub parameters: Option, usize>>, + pub nullable: Vec>, } impl Describe { @@ -54,3 +54,50 @@ impl Describe { self.nullable.get(column).copied().and_then(identity) } } + +#[cfg(feature = "any")] +impl Describe { + #[doc(hidden)] + pub fn try_into_any(self) -> crate::Result> + where + crate::any::AnyColumn: for<'a> TryFrom<&'a DB::Column, Error = crate::Error>, + crate::any::AnyTypeInfo: for<'a> TryFrom<&'a DB::TypeInfo, Error = crate::Error>, + { + use crate::any::AnyTypeInfo; + use std::convert::TryFrom; + + let columns = self + .columns + .iter() + .map(crate::any::AnyColumn::try_from) + .collect::, _>>()?; + + let parameters = match self.parameters { + Some(Either::Left(parameters)) => Some(Either::Left( + parameters + .iter() + .enumerate() + .map(|(i, type_info)| { + AnyTypeInfo::try_from(type_info).map_err(|_| { + crate::Error::AnyDriverError( + format!( + "Any driver does not support type {} of parameter {}", + type_info, i + ) + .into(), + ) + }) + }) + .collect::, _>>()?, + )), + Some(Either::Right(count)) => Some(Either::Right(count)), + None => None, + }; + + Ok(Describe { + columns, + parameters, + nullable: self.nullable, + }) + } +} diff --git a/sqlx-core/src/encode.rs b/sqlx-core/src/encode.rs index a734d0eb9a..b1848bebc4 100644 --- a/sqlx-core/src/encode.rs +++ b/sqlx-core/src/encode.rs @@ -70,15 +70,15 @@ where } } -#[allow(unused_macros)] +#[macro_export] macro_rules! impl_encode_for_option { ($DB:ident) => { - impl<'q, T> crate::encode::Encode<'q, $DB> for Option + impl<'q, T> $crate::encode::Encode<'q, $DB> for Option where - T: crate::encode::Encode<'q, $DB> + crate::types::Type<$DB> + 'q, + T: $crate::encode::Encode<'q, $DB> + $crate::types::Type<$DB> + 'q, { #[inline] - fn produces(&self) -> Option<<$DB as crate::database::Database>::TypeInfo> { + fn produces(&self) -> Option<<$DB as $crate::database::Database>::TypeInfo> { if let Some(v) = self { v.produces() } else { @@ -89,30 +89,30 @@ macro_rules! impl_encode_for_option { #[inline] fn encode( self, - buf: &mut <$DB as crate::database::HasArguments<'q>>::ArgumentBuffer, - ) -> crate::encode::IsNull { + buf: &mut <$DB as $crate::database::HasArguments<'q>>::ArgumentBuffer, + ) -> $crate::encode::IsNull { if let Some(v) = self { v.encode(buf) } else { - crate::encode::IsNull::Yes + $crate::encode::IsNull::Yes } } #[inline] fn encode_by_ref( &self, - buf: &mut <$DB as crate::database::HasArguments<'q>>::ArgumentBuffer, - ) -> crate::encode::IsNull { + buf: &mut <$DB as $crate::database::HasArguments<'q>>::ArgumentBuffer, + ) -> $crate::encode::IsNull { if let Some(v) = self { v.encode_by_ref(buf) } else { - crate::encode::IsNull::Yes + $crate::encode::IsNull::Yes } } #[inline] fn size_hint(&self) -> usize { - self.as_ref().map_or(0, crate::encode::Encode::size_hint) + self.as_ref().map_or(0, $crate::encode::Encode::size_hint) } } }; diff --git a/sqlx-core/src/error.rs b/sqlx-core/src/error.rs index cc8ff523a6..a4283c1053 100644 --- a/sqlx-core/src/error.rs +++ b/sqlx-core/src/error.rs @@ -5,14 +5,14 @@ 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::type_info::TypeInfo; use crate::types::Type; /// A specialized `Result` type for SQLx. -pub type Result = StdResult; +pub type Result = ::std::result::Result; // Convenience type alias for usage within SQLx. // Do not make this type public. @@ -82,6 +82,10 @@ pub enum Error { #[error("error occurred while decoding: {0}")] Decode(#[source] BoxDynError), + /// Error occurred within the `Any` driver mapping to/from the native driver. + #[error("error in Any driver mapping: {0}")] + AnyDriverError(#[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. /// @@ -122,20 +126,30 @@ impl Error { } } - #[allow(dead_code)] + #[doc(hidden)] #[inline] - pub(crate) fn protocol(err: impl Display) -> Self { + pub fn protocol(err: impl Display) -> Self { Error::Protocol(err.to_string()) } - #[allow(dead_code)] + #[doc(hidden)] #[inline] - pub(crate) fn config(err: impl StdError + Send + Sync + 'static) -> Self { + pub fn config(err: impl StdError + Send + Sync + 'static) -> Self { Error::Configuration(err.into()) } + + pub(crate) fn tls(err: impl Into>) -> Self { + Error::Tls(err.into()) + } + + #[doc(hidden)] + #[inline] + pub fn decode(err: impl Into>) -> Self { + Error::Decode(err.into()) + } } -pub(crate) fn mismatched_types>(ty: &DB::TypeInfo) -> BoxDynError { +pub fn mismatched_types>(ty: &DB::TypeInfo) -> BoxDynError { // TODO: `#name` only produces `TINYINT` but perhaps we want to show `TINYINT(1)` format!( "mismatched types; Rust type `{}` (as SQL type `{}`) is not compatible with SQL type `{}`", @@ -223,7 +237,7 @@ impl dyn DatabaseError { /// Downcast this generic database error to a specific database error type. #[inline] - pub fn try_downcast(self: Box) -> StdResult, Box> { + pub fn try_downcast(self: Box) -> Result, Box> { if self.as_error().is::() { Ok(self.into_error().downcast().unwrap()) } else { @@ -250,15 +264,8 @@ impl From for Error { } } -#[cfg(feature = "_tls-native-tls")] -impl From for Error { - #[inline] - fn from(error: sqlx_rt::native_tls::Error) -> Self { - Error::Tls(Box::new(error)) - } -} - -// Format an error message as a `Protocol` error +/// Format an error message as a `Protocol` error +#[macro_export] macro_rules! err_protocol { ($expr:expr) => { $crate::error::Error::Protocol($expr.into()) diff --git a/sqlx-core/src/executor.rs b/sqlx-core/src/executor.rs index 2b0e27c219..769a7aede4 100644 --- a/sqlx-core/src/executor.rs +++ b/sqlx-core/src/executor.rs @@ -1,6 +1,7 @@ use crate::database::{Database, HasArguments, HasStatement}; use crate::describe::Describe; use crate::error::Error; + use either::Either; use futures_core::future::BoxFuture; use futures_core::stream::BoxStream; diff --git a/sqlx-core/src/ext/async_stream.rs b/sqlx-core/src/ext/async_stream.rs index 54c0d0e3de..c08c0d9c74 100644 --- a/sqlx-core/src/ext/async_stream.rs +++ b/sqlx-core/src/ext/async_stream.rs @@ -58,6 +58,7 @@ impl<'a, T> Stream for TryAsyncStream<'a, T> { } } +#[macro_export] macro_rules! try_stream { ($($block:tt)*) => { crate::ext::async_stream::TryAsyncStream::new(move |mut sender| async move { diff --git a/sqlx-core/src/ext/ustr.rs b/sqlx-core/src/ext/ustr.rs index 734c1f74f2..c7fe366b59 100644 --- a/sqlx-core/src/ext/ustr.rs +++ b/sqlx-core/src/ext/ustr.rs @@ -14,8 +14,7 @@ pub enum UStr { } impl UStr { - #[allow(dead_code)] - pub(crate) fn new(s: &str) -> Self { + pub fn new(s: &str) -> Self { UStr::Shared(Arc::from(s.to_owned())) } } diff --git a/sqlx-core/src/fs.rs b/sqlx-core/src/fs.rs new file mode 100644 index 0000000000..0993cbeec6 --- /dev/null +++ b/sqlx-core/src/fs.rs @@ -0,0 +1,96 @@ +use std::ffi::OsString; +use std::fs::Metadata; +use std::io; +use std::path::{Path, PathBuf}; + +use crate::rt; + +pub struct ReadDir { + inner: Option, +} + +pub struct DirEntry { + pub path: PathBuf, + pub file_name: OsString, + pub metadata: Metadata, +} + +// Filesystem operations are generally not capable of being non-blocking +// so Tokio and async-std don't bother; they just send the work to a blocking thread pool. +// +// We save on code duplication here by just implementing the same strategy ourselves +// using the runtime's `spawn_blocking()` primitive. + +pub async fn read>(path: P) -> io::Result> { + let path = PathBuf::from(path.as_ref()); + rt::spawn_blocking(move || std::fs::read(path)).await +} + +pub async fn read_to_string>(path: P) -> io::Result { + let path = PathBuf::from(path.as_ref()); + rt::spawn_blocking(move || std::fs::read_to_string(path)).await +} + +pub async fn create_dir_all>(path: P) -> io::Result<()> { + let path = PathBuf::from(path.as_ref()); + rt::spawn_blocking(move || std::fs::create_dir_all(path)).await +} + +pub async fn remove_file>(path: P) -> io::Result<()> { + let path = PathBuf::from(path.as_ref()); + rt::spawn_blocking(move || std::fs::remove_file(path)).await +} + +pub async fn remove_dir>(path: P) -> io::Result<()> { + let path = PathBuf::from(path.as_ref()); + rt::spawn_blocking(move || std::fs::remove_dir(path)).await +} + +pub async fn remove_dir_all>(path: P) -> io::Result<()> { + let path = PathBuf::from(path.as_ref()); + rt::spawn_blocking(move || std::fs::remove_dir_all(path)).await +} + +pub async fn read_dir(path: PathBuf) -> io::Result { + let read_dir = rt::spawn_blocking(move || std::fs::read_dir(path)).await?; + + Ok(ReadDir { + inner: Some(read_dir), + }) +} + +impl ReadDir { + pub async fn next(&mut self) -> io::Result> { + if let Some(mut read_dir) = self.inner.take() { + let maybe = rt::spawn_blocking(move || { + let entry = read_dir.next().transpose()?; + + entry + .map(|entry| -> io::Result<_> { + Ok(( + read_dir, + DirEntry { + path: entry.path(), + file_name: entry.file_name(), + // We always want the metadata as well so might as well fetch + // it in the same blocking call. + metadata: entry.metadata()?, + }, + )) + }) + .transpose() + }) + .await?; + + match maybe { + Some((read_dir, entry)) => { + self.inner = Some(read_dir); + Ok(Some(entry)) + } + None => Ok(None), + } + } else { + Ok(None) + } + } +} diff --git a/sqlx-core/src/io/buf_stream.rs b/sqlx-core/src/io/buf_stream.rs index 8f376cbfb0..a21cfe288d 100644 --- a/sqlx-core/src/io/buf_stream.rs +++ b/sqlx-core/src/io/buf_stream.rs @@ -103,59 +103,3 @@ where &mut self.stream } } - -// Holds a buffer which has been temporarily extended, so that -// we can read into it. Automatically shrinks the buffer back -// down if the read is cancelled. -struct BufTruncator<'a> { - buf: &'a mut BytesMut, - filled_len: usize, -} - -impl<'a> BufTruncator<'a> { - fn new(buf: &'a mut BytesMut) -> Self { - let filled_len = buf.len(); - Self { buf, filled_len } - } - fn reserve(&mut self, space: usize) { - self.buf.resize(self.filled_len + space, 0); - } - async fn read(&mut self, stream: &mut S) -> Result { - let n = stream.read(&mut self.buf[self.filled_len..]).await?; - self.filled_len += n; - Ok(n) - } - fn is_full(&self) -> bool { - self.filled_len >= self.buf.len() - } -} - -impl Drop for BufTruncator<'_> { - fn drop(&mut self) { - self.buf.truncate(self.filled_len); - } -} - -async fn read_raw_into( - stream: &mut S, - buf: &mut BytesMut, - cnt: usize, -) -> Result<(), Error> { - let mut buf = BufTruncator::new(buf); - buf.reserve(cnt); - - while !buf.is_full() { - let n = buf.read(stream).await?; - - if n == 0 { - // 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()); - } - } - - Ok(()) -} diff --git a/sqlx-core/src/io/mod.rs b/sqlx-core/src/io/mod.rs index f994965400..84a09d7c3b 100644 --- a/sqlx-core/src/io/mod.rs +++ b/sqlx-core/src/io/mod.rs @@ -1,12 +1,26 @@ mod buf; mod buf_mut; -mod buf_stream; +// mod buf_stream; mod decode; mod encode; -mod write_and_flush; +mod read_buf; +// mod write_and_flush; pub use buf::BufExt; pub use buf_mut::BufMutExt; -pub use buf_stream::BufStream; +//pub use buf_stream::BufStream; pub use decode::Decode; pub use encode::Encode; +pub use read_buf::ReadBuf; + +#[cfg(not(feature = "_rt-tokio"))] +pub use futures_io::AsyncRead; + +#[cfg(feature = "_rt-tokio")] +pub use tokio::io::AsyncRead; + +#[cfg(not(feature = "_rt-tokio"))] +pub use futures_util::io::AsyncReadExt; + +#[cfg(feature = "_rt-tokio")] +pub use tokio::io::AsyncReadExt; diff --git a/sqlx-core/src/io/read_buf.rs b/sqlx-core/src/io/read_buf.rs new file mode 100644 index 0000000000..c32f37befd --- /dev/null +++ b/sqlx-core/src/io/read_buf.rs @@ -0,0 +1,35 @@ +use bytes::{BufMut, BytesMut}; + +/// An extension for [`BufMut`] for getting a writeable buffer in safe code. +pub trait ReadBuf: BufMut { + /// Get the full capacity of this buffer as a safely initialized slice. + fn init_mut(&mut self) -> &mut [u8]; +} + +impl ReadBuf for &'_ mut [u8] { + #[inline(always)] + fn init_mut(&mut self) -> &mut [u8] { + self + } +} + +impl ReadBuf for BytesMut { + #[inline(always)] + fn init_mut(&mut self) -> &mut [u8] { + // `self.remaining_mut()` returns `usize::MAX - self.len()` + let remaining = self.capacity() - self.len(); + + // I'm hoping for most uses that this operation is elided by the optimizer. + self.put_bytes(0, remaining); + + self + } +} + +#[test] +fn test_read_buf_bytes_mut() { + let mut buf = BytesMut::with_capacity(8); + buf.put_u32(0x12345678); + + assert_eq!(buf.init_mut(), [0x12, 0x34, 0x56, 0x78, 0, 0, 0, 0]); +} diff --git a/sqlx-core/src/lib.rs b/sqlx-core/src/lib.rs index 83e0203b93..468242e9e6 100644 --- a/sqlx-core/src/lib.rs +++ b/sqlx-core/src/lib.rs @@ -1,26 +1,31 @@ //! Core of SQLx, the rust SQL toolkit. -//! Not intended to be used directly. +//! +//! ### Note: Semver Exempt API +//! The API of this crate is not meant for general use and does *not* follow Semantic Versioning. +//! The only crate that follows Semantic Versioning in the project is the `sqlx` crate itself. +//! If you are building a custom SQLx driver, you should pin an exact version for `sqlx-core` to +//! avoid breakages: +//! +//! ```toml +//! sqlx-core = { version = "=0.6.2" } +//! ``` +//! +//! And then make releases in lockstep with `sqlx-core`. We recommend all driver crates, in-tree +//! or otherwise, use the same version numbers as `sqlx-core` to avoid confusion. #![recursion_limit = "512"] #![warn(future_incompatible, rust_2018_idioms)] #![allow(clippy::needless_doctest_main, clippy::type_complexity)] // See `clippy.toml` at the workspace root #![deny(clippy::disallowed_method)] -// +// The only unsafe code in SQLx is that necessary to interact with native APIs like with SQLite, +// and that can live in its own separate driver crate. +#![forbid(unsafe_code)] // 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))] - -#[cfg(feature = "bigdecimal")] -extern crate bigdecimal_ as bigdecimal; #[macro_use] -mod ext; +pub mod ext; #[macro_use] pub mod error; @@ -57,58 +62,60 @@ pub mod column; #[macro_use] pub mod statement; -mod common; -pub use either::Either; +pub mod common; pub mod database; pub mod describe; pub mod executor; pub mod from_row; -mod io; -mod logger; -mod net; +pub mod fs; +pub mod io; +pub mod logger; +pub mod net; pub mod query_as; pub mod query_builder; pub mod query_scalar; pub mod row; +pub mod rt; +pub mod sync; pub mod type_info; pub mod value; #[cfg(feature = "migrate")] pub mod migrate; -#[cfg(all( - any( - feature = "postgres", - feature = "mysql", - feature = "mssql", - feature = "sqlite" - ), - feature = "any" -))] +#[cfg(feature = "any")] pub mod any; -#[cfg(feature = "postgres")] -#[cfg_attr(docsrs, doc(cfg(feature = "postgres")))] -pub mod postgres; - -#[cfg(feature = "sqlite")] -#[cfg_attr(docsrs, doc(cfg(feature = "sqlite")))] -pub mod sqlite; - -#[cfg(feature = "mysql")] -#[cfg_attr(docsrs, doc(cfg(feature = "mysql")))] -pub mod mysql; - -#[cfg(feature = "mssql")] -#[cfg_attr(docsrs, doc(cfg(feature = "mssql")))] -pub mod mssql; - // Implements test support with automatic DB management. #[cfg(feature = "migrate")] pub mod testing; -pub use sqlx_rt::test_block_on; +pub use error::{Error, Result}; /// sqlx uses ahash for increased performance, at the cost of reduced DoS resistance. -use ahash::AHashMap as HashMap; +pub use ahash::AHashMap as HashMap; +pub use either::Either; +pub use indexmap::IndexMap; +pub use percent_encoding; +pub use smallvec::SmallVec; +pub use url::{self, Url}; + +pub use bytes; + //type HashMap = std::collections::HashMap; + +/// Helper module to get drivers compiling again that used to be in this crate, +/// to avoid having to replace tons of `use crate::<...>` imports. +/// +/// This module can be glob-imported and should not clash with any modules a driver +/// would want to implement itself. +pub mod driver_prelude { + pub use crate::{ + acquire, common, decode, describe, encode, executor, ext, from_row, fs, io, logger, net, + pool, query, query_as, query_builder, query_scalar, rt, sync, + }; + + pub use crate::error::{Error, Result}; + pub use crate::HashMap; + pub use either::Either; +} diff --git a/sqlx-core/src/logger.rs b/sqlx-core/src/logger.rs index f26bb85019..ec2175233d 100644 --- a/sqlx-core/src/logger.rs +++ b/sqlx-core/src/logger.rs @@ -1,13 +1,9 @@ use crate::connection::LogSettings; -#[cfg(feature = "sqlite")] -use std::collections::HashSet; -#[cfg(feature = "sqlite")] -use std::fmt::Debug; -#[cfg(feature = "sqlite")] -use std::hash::Hash; use std::time::Instant; -pub(crate) struct QueryLogger<'q> { +pub use sqlformat; + +pub struct QueryLogger<'q> { sql: &'q str, rows_returned: u64, rows_affected: u64, @@ -16,7 +12,7 @@ pub(crate) struct QueryLogger<'q> { } impl<'q> QueryLogger<'q> { - pub(crate) fn new(sql: &'q str, settings: LogSettings) -> Self { + pub fn new(sql: &'q str, settings: LogSettings) -> Self { Self { sql, rows_returned: 0, @@ -26,15 +22,15 @@ impl<'q> QueryLogger<'q> { } } - pub(crate) fn increment_rows_returned(&mut self) { + pub fn increment_rows_returned(&mut self) { self.rows_returned += 1; } - pub(crate) fn increase_rows_affected(&mut self, n: u64) { + pub fn increase_rows_affected(&mut self, n: u64) { self.rows_affected += n; } - pub(crate) fn finish(&self) { + pub fn finish(&self) { let elapsed = self.start.elapsed(); let lvl = if elapsed >= self.settings.slow_statements_duration { @@ -84,94 +80,7 @@ impl<'q> Drop for QueryLogger<'q> { } } -#[cfg(feature = "sqlite")] -pub(crate) struct QueryPlanLogger<'q, O: Debug + Hash + Eq, R: Debug, P: Debug> { - sql: &'q str, - unknown_operations: HashSet, - results: Vec, - program: &'q [P], - settings: LogSettings, -} - -#[cfg(feature = "sqlite")] -impl<'q, O: Debug + Hash + Eq, R: Debug, P: Debug> QueryPlanLogger<'q, O, R, P> { - pub(crate) fn new(sql: &'q str, program: &'q [P], settings: LogSettings) -> Self { - Self { - sql, - unknown_operations: HashSet::new(), - results: Vec::new(), - program, - settings, - } - } - - pub(crate) fn log_enabled(&self) -> bool { - if let Some(_lvl) = self - .settings - .statements_level - .to_level() - .filter(|lvl| log::log_enabled!(target: "sqlx::explain", *lvl)) - { - return true; - } else { - return false; - } - } - - pub(crate) fn add_result(&mut self, result: R) { - self.results.push(result); - } - - pub(crate) fn add_unknown_operation(&mut self, operation: O) { - self.unknown_operations.insert(operation); - } - - pub(crate) fn finish(&self) { - let lvl = self.settings.statements_level; - - if let Some(lvl) = lvl - .to_level() - .filter(|lvl| log::log_enabled!(target: "sqlx::explain", *lvl)) - { - let mut summary = parse_query_summary(&self.sql); - - let sql = if summary != self.sql { - summary.push_str(" …"); - format!( - "\n\n{}\n", - sqlformat::format( - &self.sql, - &sqlformat::QueryParams::None, - sqlformat::FormatOptions::default() - ) - ) - } else { - String::new() - }; - - log::logger().log( - &log::Record::builder() - .args(format_args!( - "{}; program:{:?}, unknown_operations:{:?}, results: {:?}{}", - summary, self.program, self.unknown_operations, self.results, sql - )) - .level(lvl) - .module_path_static(Some("sqlx::explain")) - .target("sqlx::explain") - .build(), - ); - } - } -} - -#[cfg(feature = "sqlite")] -impl<'q, O: Debug + Hash + Eq, R: Debug, P: Debug> Drop for QueryPlanLogger<'q, O, R, P> { - fn drop(&mut self) { - self.finish(); - } -} - -fn parse_query_summary(sql: &str) -> String { +pub fn parse_query_summary(sql: &str) -> String { // For now, just take the first 4 words sql.split_whitespace() .take(4) diff --git a/sqlx-core/src/migrate/migrate.rs b/sqlx-core/src/migrate/migrate.rs index 6f43e97d35..1ea64f503e 100644 --- a/sqlx-core/src/migrate/migrate.rs +++ b/sqlx-core/src/migrate/migrate.rs @@ -27,19 +27,6 @@ pub trait Migrate { // "dirty" means there is a partially applied migration that failed. fn dirty_version(&mut self) -> BoxFuture<'_, Result, MigrateError>>; - // Return the current version and if the database is "dirty". - // "dirty" means there is a partially applied migration that failed. - #[deprecated] - fn version(&mut self) -> BoxFuture<'_, Result, MigrateError>>; - - // validate the migration - // checks that it does exist on the database and that the checksum matches - #[deprecated] - fn validate<'e: 'm, 'm>( - &'e mut self, - migration: &'m Migration, - ) -> BoxFuture<'m, Result<(), MigrateError>>; - // Return the ordered list of applied migrations fn list_applied_migrations( &mut self, diff --git a/sqlx-core/src/migrate/migrator.rs b/sqlx-core/src/migrate/migrator.rs index f120e3e1ca..585d6d9d86 100644 --- a/sqlx-core/src/migrate/migrator.rs +++ b/sqlx-core/src/migrate/migrator.rs @@ -6,6 +6,7 @@ use std::ops::Deref; use std::slice; #[derive(Debug)] +#[doc(hidden)] pub struct Migrator { pub migrations: Cow<'static, [Migration]>, pub ignore_missing: bool, @@ -39,7 +40,7 @@ impl Migrator { /// ```rust,no_run /// # use sqlx_core::migrate::MigrateError; /// # fn main() -> Result<(), MigrateError> { - /// # sqlx_rt::block_on(async move { + /// # sqlx::__rt::test_block_on(async move { /// # use sqlx_core::migrate::Migrator; /// use std::path::Path; /// @@ -91,13 +92,14 @@ impl Migrator { /// # Examples /// /// ```rust,no_run - /// # use sqlx_core::migrate::MigrateError; - /// # #[cfg(feature = "sqlite")] + /// # use sqlx::migrate::MigrateError; /// # fn main() -> Result<(), MigrateError> { - /// # sqlx_rt::block_on(async move { - /// # use sqlx_core::migrate::Migrator; + /// # sqlx::__rt::test_block_on(async move { + /// use sqlx::migrate::Migrator; + /// use sqlx::sqlite::SqlitePoolOptions; + /// /// let m = Migrator::new(std::path::Path::new("./migrations")).await?; - /// let pool = sqlx_core::sqlite::SqlitePoolOptions::new().connect("sqlite::memory:").await?; + /// let pool = SqlitePoolOptions::new().connect("sqlite::memory:").await?; /// m.run(&pool).await /// # }) /// # } @@ -170,13 +172,14 @@ impl Migrator { /// # Examples /// /// ```rust,no_run - /// # use sqlx_core::migrate::MigrateError; - /// # #[cfg(feature = "sqlite")] + /// # use sqlx::migrate::MigrateError; /// # fn main() -> Result<(), MigrateError> { - /// # sqlx_rt::block_on(async move { - /// # use sqlx_core::migrate::Migrator; + /// # sqlx::__rt::test_block_on(async move { + /// use sqlx::migrate::Migrator; + /// use sqlx::sqlite::SqlitePoolOptions; + /// /// let m = Migrator::new(std::path::Path::new("./migrations")).await?; - /// let pool = sqlx_core::sqlite::SqlitePoolOptions::new().connect("sqlite::memory:").await?; + /// let pool = SqlitePoolOptions::new().connect("sqlite::memory:").await?; /// m.undo(&pool, 4).await /// # }) /// # } diff --git a/sqlx-core/src/migrate/source.rs b/sqlx-core/src/migrate/source.rs index cd0cdca39d..609f4fdeaa 100644 --- a/sqlx-core/src/migrate/source.rs +++ b/sqlx-core/src/migrate/source.rs @@ -1,8 +1,8 @@ use crate::error::BoxDynError; +use crate::fs; use crate::migrate::{Migration, MigrationType}; use futures_core::future::BoxFuture; -use futures_util::TryStreamExt; -use sqlx_rt::fs; + use std::borrow::Cow; use std::fmt::Debug; use std::path::{Path, PathBuf}; @@ -20,21 +20,16 @@ pub trait MigrationSource<'s>: Debug { impl<'s> MigrationSource<'s> for &'s Path { fn resolve(self) -> BoxFuture<'s, Result, BoxDynError>> { Box::pin(async move { - #[allow(unused_mut)] let mut s = fs::read_dir(self.canonicalize()?).await?; let mut migrations = Vec::new(); - #[cfg(feature = "_rt-tokio")] - let mut s = tokio_stream::wrappers::ReadDirStream::new(s); - - while let Some(entry) = s.try_next().await? { - if !entry.metadata().await?.is_file() { + while let Some(entry) = s.next().await? { + if !entry.metadata.is_file() { // not a file; ignore continue; } - let file_name = entry.file_name(); - let file_name = file_name.to_string_lossy(); + let file_name = entry.file_name.to_string_lossy(); let parts = file_name.splitn(2, '_').collect::>(); @@ -52,7 +47,7 @@ impl<'s> MigrationSource<'s> for &'s Path { .replace('_', " ") .to_owned(); - let sql = fs::read_to_string(&entry.path()).await?; + let sql = fs::read_to_string(&entry.path).await?; migrations.push(Migration::new( version, diff --git a/sqlx-core/src/mssql/arguments.rs b/sqlx-core/src/mssql/arguments.rs deleted file mode 100644 index 123e10b18a..0000000000 --- a/sqlx-core/src/mssql/arguments.rs +++ /dev/null @@ -1,118 +0,0 @@ -use crate::arguments::Arguments; -use crate::encode::Encode; -use crate::mssql::database::Mssql; -use crate::mssql::io::MssqlBufMutExt; -use crate::mssql::protocol::rpc::StatusFlags; -use crate::types::Type; - -#[derive(Default, Clone)] -pub struct MssqlArguments { - // next ordinal to be used when formatting a positional parameter name - pub(crate) ordinal: usize, - // temporary string buffer used to format parameter names - name: String, - pub(crate) data: Vec, - pub(crate) declarations: String, -} - -impl MssqlArguments { - pub(crate) fn add_named<'q, T: Encode<'q, Mssql> + Type>( - &mut self, - name: &str, - value: T, - ) { - let ty = value.produces().unwrap_or_else(T::type_info); - - let mut ty_name = String::new(); - ty.0.fmt(&mut ty_name); - - self.data.put_b_varchar(name); // [ParamName] - self.data.push(0); // [StatusFlags] - - ty.0.put(&mut self.data); // [TYPE_INFO] - ty.0.put_value(&mut self.data, value); // [ParamLenData] - } - - pub(crate) fn add_unnamed<'q, T: Encode<'q, Mssql> + Type>(&mut self, value: T) { - self.add_named("", value); - } - - pub(crate) fn declare<'q, T: Encode<'q, Mssql> + Type>( - &mut self, - name: &str, - initial_value: T, - ) { - let ty = initial_value.produces().unwrap_or_else(T::type_info); - - let mut ty_name = String::new(); - ty.0.fmt(&mut ty_name); - - self.data.put_b_varchar(name); // [ParamName] - self.data.push(StatusFlags::BY_REF_VALUE.bits()); // [StatusFlags] - - ty.0.put(&mut self.data); // [TYPE_INFO] - ty.0.put_value(&mut self.data, initial_value); // [ParamLenData] - } - - pub(crate) fn append(&mut self, arguments: &mut MssqlArguments) { - self.ordinal += arguments.ordinal; - self.data.append(&mut arguments.data); - } - - pub(crate) fn add<'q, T>(&mut self, value: T) - where - T: Encode<'q, Mssql> + Type, - { - let ty = value.produces().unwrap_or_else(T::type_info); - - // produce an ordinal parameter name - // @p1, @p2, ... @pN - - self.name.clear(); - self.name.push_str("@p"); - - self.ordinal += 1; - self.name.push_str(itoa::Buffer::new().format(self.ordinal)); - - let MssqlArguments { - ref name, - ref mut declarations, - ref mut data, - .. - } = self; - - // add this to our variable declaration list - // @p1 int, @p2 nvarchar(10), ... - - if !declarations.is_empty() { - declarations.push_str(","); - } - - declarations.push_str(name); - declarations.push(' '); - ty.0.fmt(declarations); - - // write out the parameter - - data.put_b_varchar(name); // [ParamName] - data.push(0); // [StatusFlags] - - ty.0.put(data); // [TYPE_INFO] - ty.0.put_value(data, value); // [ParamLenData] - } -} - -impl<'q> Arguments<'q> for MssqlArguments { - type Database = Mssql; - - fn reserve(&mut self, _additional: usize, size: usize) { - self.data.reserve(size + 10); // est. 4 chars for name, 1 for status, 1 for TYPE_INFO - } - - fn add(&mut self, value: T) - where - T: 'q + Encode<'q, Self::Database> + Type, - { - self.add(value) - } -} diff --git a/sqlx-core/src/mssql/column.rs b/sqlx-core/src/mssql/column.rs deleted file mode 100644 index a6bdbe823c..0000000000 --- a/sqlx-core/src/mssql/column.rs +++ /dev/null @@ -1,53 +0,0 @@ -use crate::column::Column; -use crate::ext::ustr::UStr; -use crate::mssql::protocol::col_meta_data::{ColumnData, Flags}; -use crate::mssql::{Mssql, MssqlTypeInfo}; - -#[derive(Debug, Clone)] -#[cfg_attr(feature = "offline", derive(serde::Serialize, serde::Deserialize))] -pub struct MssqlColumn { - pub(crate) ordinal: usize, - pub(crate) name: UStr, - pub(crate) type_info: MssqlTypeInfo, - pub(crate) flags: Flags, -} - -impl crate::column::private_column::Sealed for MssqlColumn {} - -impl MssqlColumn { - pub(crate) fn new(meta: ColumnData, ordinal: usize) -> Self { - Self { - name: UStr::from(meta.col_name), - type_info: MssqlTypeInfo(meta.type_info), - ordinal, - flags: meta.flags, - } - } -} - -impl Column for MssqlColumn { - type Database = Mssql; - - fn ordinal(&self) -> usize { - self.ordinal - } - - fn name(&self) -> &str { - &*self.name - } - - fn type_info(&self) -> &MssqlTypeInfo { - &self.type_info - } -} - -#[cfg(feature = "any")] -impl From for crate::any::AnyColumn { - #[inline] - fn from(column: MssqlColumn) -> Self { - crate::any::AnyColumn { - type_info: column.type_info.clone().into(), - kind: crate::any::column::AnyColumnKind::Mssql(column), - } - } -} diff --git a/sqlx-core/src/mssql/connection/establish.rs b/sqlx-core/src/mssql/connection/establish.rs deleted file mode 100644 index 9b2d826149..0000000000 --- a/sqlx-core/src/mssql/connection/establish.rs +++ /dev/null @@ -1,88 +0,0 @@ -use crate::common::StatementCache; -use crate::error::Error; -use crate::io::Decode; -use crate::mssql::connection::stream::MssqlStream; -use crate::mssql::protocol::login::Login7; -use crate::mssql::protocol::message::Message; -use crate::mssql::protocol::packet::PacketType; -use crate::mssql::protocol::pre_login::{Encrypt, PreLogin, Version}; -use crate::mssql::{MssqlConnectOptions, MssqlConnection}; - -impl MssqlConnection { - pub(crate) async fn establish(options: &MssqlConnectOptions) -> Result { - let mut stream: MssqlStream = MssqlStream::connect(options).await?; - - // Send PRELOGIN to set up the context for login. The server should immediately - // respond with a PRELOGIN message of its own. - - // TODO: Encryption - // TODO: Send the version of SQLx over - - stream.write_packet( - PacketType::PreLogin, - PreLogin { - version: Version::default(), - encryption: Encrypt::NOT_SUPPORTED, - - ..Default::default() - }, - ); - - stream.flush().await?; - - let (_, packet) = stream.recv_packet().await?; - let _ = PreLogin::decode(packet)?; - - // LOGIN7 defines the authentication rules for use between client and server - - stream.write_packet( - PacketType::Tds7Login, - Login7 { - // FIXME: use a version constant - version: 0x74000004, // SQL Server 2012 - SQL Server 2019 - client_program_version: 0, - client_pid: 0, - packet_size: 4096, - hostname: "", - username: &options.username, - password: options.password.as_deref().unwrap_or_default(), - app_name: "", - server_name: "", - client_interface_name: "", - language: "", - database: &*options.database, - client_id: [0; 6], - }, - ); - - stream.flush().await?; - - loop { - // NOTE: we should receive an [Error] message if something goes wrong, otherwise, - // all messages are mostly informational (ENVCHANGE, INFO, LOGINACK) - - match stream.recv_message().await? { - Message::LoginAck(_) => { - // indicates that the login was successful - // no action is needed, we are just going to keep waiting till we hit - } - - Message::Done(_) => { - break; - } - - _ => {} - } - } - - // FIXME: Do we need to expose the capacity count here? It's not tied to - // server-side resources but just .prepare() calls which return - // client-side data. - - Ok(Self { - stream, - cache_statement: StatementCache::new(1024), - log_settings: options.log_settings.clone(), - }) - } -} diff --git a/sqlx-core/src/mssql/connection/executor.rs b/sqlx-core/src/mssql/connection/executor.rs deleted file mode 100644 index 0c8a2f064d..0000000000 --- a/sqlx-core/src/mssql/connection/executor.rs +++ /dev/null @@ -1,198 +0,0 @@ -use crate::describe::Describe; -use crate::error::Error; -use crate::executor::{Execute, Executor}; -use crate::logger::QueryLogger; -use crate::mssql::connection::prepare::prepare; -use crate::mssql::protocol::col_meta_data::Flags; -use crate::mssql::protocol::done::Status; -use crate::mssql::protocol::message::Message; -use crate::mssql::protocol::packet::PacketType; -use crate::mssql::protocol::rpc::{OptionFlags, Procedure, RpcRequest}; -use crate::mssql::protocol::sql_batch::SqlBatch; -use crate::mssql::{ - Mssql, MssqlArguments, MssqlConnection, MssqlQueryResult, MssqlRow, MssqlStatement, - MssqlTypeInfo, -}; -use either::Either; -use futures_core::future::BoxFuture; -use futures_core::stream::BoxStream; -use futures_util::TryStreamExt; -use std::borrow::Cow; -use std::sync::Arc; - -impl MssqlConnection { - async fn run(&mut self, query: &str, arguments: Option) -> Result<(), Error> { - self.stream.wait_until_ready().await?; - self.stream.pending_done_count += 1; - - if let Some(mut arguments) = arguments { - let proc = Either::Right(Procedure::ExecuteSql); - let mut proc_args = MssqlArguments::default(); - - // SQL - proc_args.add_unnamed(query); - - if !arguments.data.is_empty() { - // Declarations - // NAME TYPE, NAME TYPE, ... - proc_args.add_unnamed(&*arguments.declarations); - - // Add the list of SQL parameters _after_ our RPC parameters - proc_args.append(&mut arguments); - } - - self.stream.write_packet( - PacketType::Rpc, - RpcRequest { - transaction_descriptor: self.stream.transaction_descriptor, - arguments: &proc_args, - procedure: proc, - options: OptionFlags::empty(), - }, - ); - } else { - self.stream.write_packet( - PacketType::SqlBatch, - SqlBatch { - transaction_descriptor: self.stream.transaction_descriptor, - sql: query, - }, - ); - } - - self.stream.flush().await?; - - Ok(()) - } -} - -impl<'c> Executor<'c> for &'c mut MssqlConnection { - type Database = Mssql; - - fn fetch_many<'e, 'q: 'e, E: 'q>( - self, - mut query: E, - ) -> BoxStream<'e, Result, Error>> - where - 'c: 'e, - E: Execute<'q, Self::Database>, - { - let sql = query.sql(); - let arguments = query.take_arguments(); - let mut logger = QueryLogger::new(sql, self.log_settings.clone()); - - Box::pin(try_stream! { - self.run(sql, arguments).await?; - - loop { - let message = self.stream.recv_message().await?; - - match message { - Message::Row(row) => { - let columns = Arc::clone(&self.stream.columns); - let column_names = Arc::clone(&self.stream.column_names); - - logger.increment_rows_returned(); - - r#yield!(Either::Right(MssqlRow { row, column_names, columns })); - } - - Message::Done(done) | Message::DoneProc(done) => { - if !done.status.contains(Status::DONE_MORE) { - self.stream.handle_done(&done); - } - - if done.status.contains(Status::DONE_COUNT) { - let rows_affected = done.affected_rows; - logger.increase_rows_affected(rows_affected); - r#yield!(Either::Left(MssqlQueryResult { - rows_affected, - })); - } - - if !done.status.contains(Status::DONE_MORE) { - break; - } - } - - Message::DoneInProc(done) => { - if done.status.contains(Status::DONE_COUNT) { - let rows_affected = done.affected_rows; - logger.increase_rows_affected(rows_affected); - r#yield!(Either::Left(MssqlQueryResult { - rows_affected, - })); - } - } - - _ => {} - } - } - - Ok(()) - }) - } - - fn fetch_optional<'e, 'q: 'e, E: 'q>( - self, - query: E, - ) -> BoxFuture<'e, Result, Error>> - where - 'c: 'e, - E: Execute<'q, Self::Database>, - { - let mut s = self.fetch_many(query); - - Box::pin(async move { - while let Some(v) = s.try_next().await? { - if let Either::Right(r) = v { - return Ok(Some(r)); - } - } - - Ok(None) - }) - } - - fn prepare_with<'e, 'q: 'e>( - self, - sql: &'q str, - _parameters: &[MssqlTypeInfo], - ) -> BoxFuture<'e, Result, Error>> - where - 'c: 'e, - { - Box::pin(async move { - let metadata = prepare(self, sql).await?; - - Ok(MssqlStatement { - sql: Cow::Borrowed(sql), - metadata, - }) - }) - } - - fn describe<'e, 'q: 'e>( - self, - sql: &'q str, - ) -> BoxFuture<'e, Result, Error>> - where - 'c: 'e, - { - Box::pin(async move { - let metadata = prepare(self, sql).await?; - - let mut nullable = Vec::with_capacity(metadata.columns.len()); - - for col in metadata.columns.iter() { - nullable.push(Some(col.flags.contains(Flags::NULLABLE))); - } - - Ok(Describe { - nullable, - columns: (metadata.columns).clone(), - parameters: None, - }) - }) - } -} diff --git a/sqlx-core/src/mssql/connection/mod.rs b/sqlx-core/src/mssql/connection/mod.rs deleted file mode 100644 index 8585f7cf99..0000000000 --- a/sqlx-core/src/mssql/connection/mod.rs +++ /dev/null @@ -1,83 +0,0 @@ -use crate::common::StatementCache; -use crate::connection::{Connection, LogSettings}; -use crate::error::Error; -use crate::executor::Executor; -use crate::mssql::connection::stream::MssqlStream; -use crate::mssql::statement::MssqlStatementMetadata; -use crate::mssql::{Mssql, MssqlConnectOptions}; -use crate::transaction::Transaction; -use futures_core::future::BoxFuture; -use futures_util::{FutureExt, TryFutureExt}; -use std::fmt::{self, Debug, Formatter}; -use std::sync::Arc; - -mod establish; -mod executor; -mod prepare; -mod stream; - -pub struct MssqlConnection { - pub(crate) stream: MssqlStream, - pub(crate) cache_statement: StatementCache>, - log_settings: LogSettings, -} - -impl Debug for MssqlConnection { - fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { - f.debug_struct("MssqlConnection").finish() - } -} - -impl Connection for MssqlConnection { - type Database = Mssql; - - type Options = MssqlConnectOptions; - - #[allow(unused_mut)] - fn close(mut self) -> BoxFuture<'static, Result<(), Error>> { - // NOTE: there does not seem to be a clean shutdown packet to send to MSSQL - - #[cfg(feature = "_rt-async-std")] - { - use std::future::ready; - use std::net::Shutdown; - - ready(self.stream.shutdown(Shutdown::Both).map_err(Into::into)).boxed() - } - - #[cfg(feature = "_rt-tokio")] - { - use sqlx_rt::AsyncWriteExt; - - // FIXME: This is equivalent to Shutdown::Write, not Shutdown::Both like above - // https://docs.rs/tokio/1.0.1/tokio/io/trait.AsyncWriteExt.html#method.shutdown - async move { self.stream.shutdown().await.map_err(Into::into) }.boxed() - } - } - - fn close_hard(self) -> BoxFuture<'static, Result<(), Error>> { - self.close() - } - - fn ping(&mut self) -> BoxFuture<'_, Result<(), Error>> { - // NOTE: we do not use `SELECT 1` as that *could* interact with any ongoing transactions - self.execute("/* SQLx ping */").map_ok(|_| ()).boxed() - } - - fn begin(&mut self) -> BoxFuture<'_, Result, Error>> - where - Self: Sized, - { - Transaction::begin(self) - } - - #[doc(hidden)] - fn flush(&mut self) -> BoxFuture<'_, Result<(), Error>> { - self.stream.wait_until_ready().boxed() - } - - #[doc(hidden)] - fn should_flush(&self) -> bool { - !self.stream.wbuf.is_empty() - } -} diff --git a/sqlx-core/src/mssql/connection/prepare.rs b/sqlx-core/src/mssql/connection/prepare.rs deleted file mode 100644 index ef96dadc5f..0000000000 --- a/sqlx-core/src/mssql/connection/prepare.rs +++ /dev/null @@ -1,142 +0,0 @@ -use crate::decode::Decode; -use crate::error::Error; -use crate::mssql::protocol::done::Status; -use crate::mssql::protocol::message::Message; -use crate::mssql::protocol::packet::PacketType; -use crate::mssql::protocol::rpc::{OptionFlags, Procedure, RpcRequest}; -use crate::mssql::statement::MssqlStatementMetadata; -use crate::mssql::{Mssql, MssqlArguments, MssqlConnection, MssqlTypeInfo, MssqlValueRef}; -use either::Either; -use once_cell::sync::Lazy; -use regex::Regex; -use std::sync::Arc; - -pub(crate) async fn prepare( - conn: &mut MssqlConnection, - sql: &str, -) -> Result, Error> { - if let Some(metadata) = conn.cache_statement.get_mut(sql) { - return Ok(metadata.clone()); - } - - // NOTE: this does not support unicode identifiers; as we don't even support - // named parameters (yet) this is probably fine, for now - - static PARAMS_RE: Lazy = Lazy::new(|| Regex::new(r"@p[[:alnum:]]+").unwrap()); - - let mut params = String::new(); - - for m in PARAMS_RE.captures_iter(sql) { - if !params.is_empty() { - params.push_str(","); - } - - params.push_str(&m[0]); - - // NOTE: this means that a query! of `SELECT @p1` will have the macros believe - // it will return nvarchar(1); this is a greater issue with `query!` that we - // we need to circle back to. This doesn't happen much in practice however. - params.push_str(" nvarchar(1)"); - } - - let params = if params.is_empty() { - None - } else { - Some(&*params) - }; - - let mut args = MssqlArguments::default(); - - args.declare("", 0_i32); - args.add_unnamed(params); - args.add_unnamed(sql); - args.add_unnamed(0x0001_i32); // 1 = SEND_METADATA - - conn.stream.write_packet( - PacketType::Rpc, - RpcRequest { - transaction_descriptor: conn.stream.transaction_descriptor, - arguments: &args, - // [sp_prepare] will emit the column meta data - // small issue is that we need to declare all the used placeholders with a "fallback" type - // we currently use regex to collect them; false positives are *okay* but false - // negatives would break the query - procedure: Either::Right(Procedure::Prepare), - options: OptionFlags::empty(), - }, - ); - - conn.stream.flush().await?; - conn.stream.wait_until_ready().await?; - conn.stream.pending_done_count += 1; - - let mut id: Option = None; - - loop { - let message = conn.stream.recv_message().await?; - - match message { - Message::DoneProc(done) | Message::Done(done) => { - if !done.status.contains(Status::DONE_MORE) { - // done with prepare - conn.stream.handle_done(&done); - break; - } - } - - Message::ReturnValue(rv) => { - id = >::decode(MssqlValueRef { - data: rv.value.as_ref(), - type_info: MssqlTypeInfo(rv.type_info), - }) - .ok(); - } - - _ => {} - } - } - - if let Some(id) = id { - let mut args = MssqlArguments::default(); - args.add_unnamed(id); - - conn.stream.write_packet( - PacketType::Rpc, - RpcRequest { - transaction_descriptor: conn.stream.transaction_descriptor, - arguments: &args, - procedure: Either::Right(Procedure::Unprepare), - options: OptionFlags::empty(), - }, - ); - - conn.stream.flush().await?; - conn.stream.wait_until_ready().await?; - conn.stream.pending_done_count += 1; - - loop { - let message = conn.stream.recv_message().await?; - - match message { - Message::DoneProc(done) | Message::Done(done) => { - if !done.status.contains(Status::DONE_MORE) { - // done with unprepare - conn.stream.handle_done(&done); - break; - } - } - - _ => {} - } - } - } - - let metadata = Arc::new(MssqlStatementMetadata { - columns: conn.stream.columns.as_ref().clone(), - column_names: conn.stream.column_names.as_ref().clone(), - }); - - conn.cache_statement.insert(sql, metadata.clone()); - - Ok(metadata) -} diff --git a/sqlx-core/src/mssql/connection/stream.rs b/sqlx-core/src/mssql/connection/stream.rs deleted file mode 100644 index 1ce061d508..0000000000 --- a/sqlx-core/src/mssql/connection/stream.rs +++ /dev/null @@ -1,236 +0,0 @@ -use std::ops::{Deref, DerefMut}; - -use bytes::{Bytes, BytesMut}; -use sqlx_rt::TcpStream; - -use crate::error::Error; -use crate::ext::ustr::UStr; -use crate::io::{BufStream, Encode}; -use crate::mssql::protocol::col_meta_data::ColMetaData; -use crate::mssql::protocol::done::{Done, Status as DoneStatus}; -use crate::mssql::protocol::env_change::EnvChange; -use crate::mssql::protocol::error::Error as ProtocolError; -use crate::mssql::protocol::info::Info; -use crate::mssql::protocol::login_ack::LoginAck; -use crate::mssql::protocol::message::{Message, MessageType}; -use crate::mssql::protocol::order::Order; -use crate::mssql::protocol::packet::{PacketHeader, PacketType, Status}; -use crate::mssql::protocol::return_status::ReturnStatus; -use crate::mssql::protocol::return_value::ReturnValue; -use crate::mssql::protocol::row::Row; -use crate::mssql::{MssqlColumn, MssqlConnectOptions, MssqlDatabaseError}; -use crate::net::MaybeTlsStream; -use crate::HashMap; -use std::sync::Arc; - -pub(crate) struct MssqlStream { - inner: BufStream>, - - // how many Done (or Error) we are currently waiting for - pub(crate) pending_done_count: usize, - - // current transaction descriptor - // set from ENVCHANGE on `BEGIN` and reset to `0` on a ROLLBACK - pub(crate) transaction_descriptor: u64, - pub(crate) transaction_depth: usize, - - // current TabularResult from the server that we are iterating over - response: Option<(PacketHeader, Bytes)>, - - // most recent column data from ColMetaData - // we need to store this as its needed when decoding - pub(crate) columns: Arc>, - pub(crate) column_names: Arc>, -} - -impl MssqlStream { - pub(super) async fn connect(options: &MssqlConnectOptions) -> Result { - let inner = BufStream::new(MaybeTlsStream::Raw( - TcpStream::connect((&*options.host, options.port)).await?, - )); - - Ok(Self { - inner, - columns: Default::default(), - column_names: Default::default(), - response: None, - pending_done_count: 0, - transaction_descriptor: 0, - transaction_depth: 0, - }) - } - - // writes the packet out to the write buffer - // will (eventually) handle packet chunking - pub(crate) fn write_packet<'en, T: Encode<'en>>(&mut self, ty: PacketType, payload: T) { - // TODO: Support packet chunking for large packet sizes - // We likely need to double-buffer the writes so we know to chunk - - // write out the packet header, leaving room for setting the packet length later - - let mut len_offset = 0; - - self.inner.write_with( - PacketHeader { - r#type: ty, - status: Status::END_OF_MESSAGE, - length: 0, - server_process_id: 0, - packet_id: 1, - }, - &mut len_offset, - ); - - // write out the payload - self.inner.write(payload); - - // overwrite the packet length now that we know it - let len = self.inner.wbuf.len(); - self.inner.wbuf[len_offset..(len_offset + 2)].copy_from_slice(&(len as u16).to_be_bytes()); - } - - // receive the next packet from the database - // blocks until a packet is available - pub(super) async fn recv_packet(&mut self) -> Result<(PacketHeader, Bytes), Error> { - let mut header: PacketHeader = self.inner.read(8).await?; - - // NOTE: From what I can tell, the response type from the server should ~always~ - // be TabularResult. Here we expect that and die otherwise. - if !matches!(header.r#type, PacketType::TabularResult) { - return Err(err_protocol!( - "received unexpected packet: {:?}", - header.r#type - )); - } - - let mut payload = BytesMut::new(); - - loop { - self.inner - .read_raw_into(&mut payload, (header.length - 8) as usize) - .await?; - - if header.status.contains(Status::END_OF_MESSAGE) { - break; - } - - header = self.inner.read(8).await?; - } - - Ok((header, payload.freeze())) - } - - // receive the next ~message~ - // TDS communicates in streams of packets that are themselves streams of messages - pub(super) async fn recv_message(&mut self) -> Result { - loop { - while self.response.as_ref().map_or(false, |r| !r.1.is_empty()) { - let buf = if let Some((_, buf)) = self.response.as_mut() { - buf - } else { - // this shouldn't be reachable but just nope out - // and head to refill our buffer - break; - }; - - let ty = MessageType::get(buf)?; - - let message = match ty { - MessageType::EnvChange => { - match EnvChange::get(buf)? { - EnvChange::BeginTransaction(desc) => { - self.transaction_descriptor = desc; - } - - EnvChange::CommitTransaction(_) | EnvChange::RollbackTransaction(_) => { - self.transaction_descriptor = 0; - } - - _ => {} - } - - continue; - } - - MessageType::Info => { - let _ = Info::get(buf)?; - continue; - } - - MessageType::Row => Message::Row(Row::get(buf, false, &self.columns)?), - MessageType::NbcRow => Message::Row(Row::get(buf, true, &self.columns)?), - MessageType::LoginAck => Message::LoginAck(LoginAck::get(buf)?), - MessageType::ReturnStatus => Message::ReturnStatus(ReturnStatus::get(buf)?), - MessageType::ReturnValue => Message::ReturnValue(ReturnValue::get(buf)?), - MessageType::Done => Message::Done(Done::get(buf)?), - MessageType::DoneInProc => Message::DoneInProc(Done::get(buf)?), - MessageType::DoneProc => Message::DoneProc(Done::get(buf)?), - MessageType::Order => Message::Order(Order::get(buf)?), - - MessageType::Error => { - let error = ProtocolError::get(buf)?; - return self.handle_error(error); - } - - MessageType::ColMetaData => { - // NOTE: there isn't anything to return as the data gets - // consumed by the stream for use in subsequent Row decoding - ColMetaData::get( - buf, - Arc::make_mut(&mut self.columns), - Arc::make_mut(&mut self.column_names), - )?; - continue; - } - }; - - return Ok(message); - } - - // no packet from the server to iterate (or its empty); fill our buffer - self.response = Some(self.recv_packet().await?); - } - } - - pub(crate) fn handle_done(&mut self, _done: &Done) { - self.pending_done_count -= 1; - } - - pub(crate) fn handle_error(&mut self, error: ProtocolError) -> Result { - // NOTE: [error] is sent IN ADDITION TO [done] - Err(MssqlDatabaseError(error).into()) - } - - pub(crate) async fn wait_until_ready(&mut self) -> Result<(), Error> { - if !self.wbuf.is_empty() { - self.flush().await?; - } - - while self.pending_done_count > 0 { - let message = self.recv_message().await?; - - if let Message::DoneProc(done) | Message::Done(done) = message { - if !done.status.contains(DoneStatus::DONE_MORE) { - // finished RPC procedure *OR* SQL batch - self.handle_done(&done); - } - } - } - - Ok(()) - } -} - -impl Deref for MssqlStream { - type Target = BufStream>; - - fn deref(&self) -> &Self::Target { - &self.inner - } -} - -impl DerefMut for MssqlStream { - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.inner - } -} diff --git a/sqlx-core/src/mssql/database.rs b/sqlx-core/src/mssql/database.rs deleted file mode 100644 index addc41633e..0000000000 --- a/sqlx-core/src/mssql/database.rs +++ /dev/null @@ -1,45 +0,0 @@ -use crate::database::{Database, HasArguments, HasStatement, HasValueRef}; -use crate::mssql::{ - MssqlArguments, MssqlColumn, MssqlConnection, MssqlQueryResult, MssqlRow, MssqlStatement, - MssqlTransactionManager, MssqlTypeInfo, MssqlValue, MssqlValueRef, -}; - -/// MSSQL database driver. -#[derive(Debug)] -pub struct Mssql; - -impl Database for Mssql { - type Connection = MssqlConnection; - - type TransactionManager = MssqlTransactionManager; - - type Row = MssqlRow; - - type QueryResult = MssqlQueryResult; - - type Column = MssqlColumn; - - type TypeInfo = MssqlTypeInfo; - - type Value = MssqlValue; -} - -impl<'r> HasValueRef<'r> for Mssql { - type Database = Mssql; - - type ValueRef = MssqlValueRef<'r>; -} - -impl<'q> HasStatement<'q> for Mssql { - type Database = Mssql; - - type Statement = MssqlStatement<'q>; -} - -impl HasArguments<'_> for Mssql { - type Database = Mssql; - - type Arguments = MssqlArguments; - - type ArgumentBuffer = Vec; -} diff --git a/sqlx-core/src/mssql/error.rs b/sqlx-core/src/mssql/error.rs deleted file mode 100644 index 41d7152569..0000000000 --- a/sqlx-core/src/mssql/error.rs +++ /dev/null @@ -1,52 +0,0 @@ -use std::error::Error as StdError; -use std::fmt::{self, Debug, Display, Formatter}; - -use crate::error::DatabaseError; -use crate::mssql::protocol::error::Error; - -/// An error returned from the MSSQL database. -pub struct MssqlDatabaseError(pub(crate) Error); - -impl Debug for MssqlDatabaseError { - fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { - f.debug_struct("MssqlDatabaseError") - .field("message", &self.0.message) - .field("number", &self.0.number) - .field("state", &self.0.state) - .field("class", &self.0.class) - .field("server", &self.0.server) - .field("procedure", &self.0.procedure) - .field("line", &self.0.line) - .finish() - } -} - -impl Display for MssqlDatabaseError { - fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { - f.pad(self.message()) - } -} - -impl StdError for MssqlDatabaseError {} - -impl DatabaseError for MssqlDatabaseError { - #[inline] - fn message(&self) -> &str { - &self.0.message - } - - #[doc(hidden)] - fn as_error(&self) -> &(dyn StdError + Send + Sync + 'static) { - self - } - - #[doc(hidden)] - fn as_error_mut(&mut self) -> &mut (dyn StdError + Send + Sync + 'static) { - self - } - - #[doc(hidden)] - fn into_error(self: Box) -> Box { - self - } -} diff --git a/sqlx-core/src/mssql/io/buf.rs b/sqlx-core/src/mssql/io/buf.rs deleted file mode 100644 index 03e7c97fa1..0000000000 --- a/sqlx-core/src/mssql/io/buf.rs +++ /dev/null @@ -1,43 +0,0 @@ -use bytes::{Buf, Bytes}; - -use crate::error::Error; -use crate::io::BufExt; - -pub trait MssqlBufExt: Buf { - fn get_utf16_str(&mut self, n: usize) -> Result; - - fn get_b_varchar(&mut self) -> Result; - - fn get_us_varchar(&mut self) -> Result; - - fn get_b_varbyte(&mut self) -> Bytes; -} - -impl MssqlBufExt for Bytes { - fn get_utf16_str(&mut self, mut n: usize) -> Result { - let mut raw = Vec::with_capacity(n * 2); - - while n > 0 { - let ch = self.get_u16_le(); - raw.push(ch); - n -= 1; - } - - String::from_utf16(&raw).map_err(Error::protocol) - } - - fn get_b_varchar(&mut self) -> Result { - let size = self.get_u8(); - self.get_utf16_str(size as usize) - } - - fn get_us_varchar(&mut self) -> Result { - let size = self.get_u16_le(); - self.get_utf16_str(size as usize) - } - - fn get_b_varbyte(&mut self) -> Bytes { - let size = self.get_u8(); - self.get_bytes(size as usize) - } -} diff --git a/sqlx-core/src/mssql/io/buf_mut.rs b/sqlx-core/src/mssql/io/buf_mut.rs deleted file mode 100644 index 01cc757b9f..0000000000 --- a/sqlx-core/src/mssql/io/buf_mut.rs +++ /dev/null @@ -1,18 +0,0 @@ -pub trait MssqlBufMutExt { - fn put_b_varchar(&mut self, s: &str); - fn put_utf16_str(&mut self, s: &str); -} - -impl MssqlBufMutExt for Vec { - fn put_utf16_str(&mut self, s: &str) { - let mut enc = s.encode_utf16(); - while let Some(ch) = enc.next() { - self.extend_from_slice(&ch.to_le_bytes()); - } - } - - fn put_b_varchar(&mut self, s: &str) { - self.extend(&(s.len() as u8).to_le_bytes()); - self.put_utf16_str(s); - } -} diff --git a/sqlx-core/src/mssql/io/mod.rs b/sqlx-core/src/mssql/io/mod.rs deleted file mode 100644 index 1ebd185b55..0000000000 --- a/sqlx-core/src/mssql/io/mod.rs +++ /dev/null @@ -1,5 +0,0 @@ -mod buf; -mod buf_mut; - -pub(crate) use buf::MssqlBufExt; -pub(crate) use buf_mut::MssqlBufMutExt; diff --git a/sqlx-core/src/mssql/mod.rs b/sqlx-core/src/mssql/mod.rs deleted file mode 100644 index e3354620cd..0000000000 --- a/sqlx-core/src/mssql/mod.rs +++ /dev/null @@ -1,51 +0,0 @@ -//! Microsoft SQL (MSSQL) database driver. - -use crate::executor::Executor; - -mod arguments; -mod column; -mod connection; -mod database; -mod error; -mod io; -mod options; -mod protocol; -mod query_result; -mod row; -mod statement; -mod transaction; -mod type_info; -pub mod types; -mod value; - -pub use arguments::MssqlArguments; -pub use column::MssqlColumn; -pub use connection::MssqlConnection; -pub use database::Mssql; -pub use error::MssqlDatabaseError; -pub use options::MssqlConnectOptions; -pub use query_result::MssqlQueryResult; -pub use row::MssqlRow; -pub use statement::MssqlStatement; -pub use transaction::MssqlTransactionManager; -pub use type_info::MssqlTypeInfo; -pub use value::{MssqlValue, MssqlValueRef}; - -/// An alias for [`Pool`][crate::pool::Pool], specialized for MSSQL. -pub type MssqlPool = crate::pool::Pool; - -/// An alias for [`PoolOptions`][crate::pool::PoolOptions], specialized for MSSQL. -pub type MssqlPoolOptions = crate::pool::PoolOptions; - -/// An alias for [`Executor<'_, Database = Mssql>`][Executor]. -pub trait MssqlExecutor<'c>: Executor<'c, Database = Mssql> {} -impl<'c, T: Executor<'c, Database = Mssql>> MssqlExecutor<'c> for T {} - -// NOTE: required due to the lack of lazy normalization -impl_into_arguments_for_arguments!(MssqlArguments); -impl_executor_for_pool_connection!(Mssql, MssqlConnection, MssqlRow); -impl_executor_for_transaction!(Mssql, MssqlRow); -impl_acquire!(Mssql, MssqlConnection); -impl_column_index_for_row!(MssqlRow); -impl_column_index_for_statement!(MssqlStatement); -impl_into_maybe_pool!(Mssql, MssqlConnection); diff --git a/sqlx-core/src/mssql/options/connect.rs b/sqlx-core/src/mssql/options/connect.rs deleted file mode 100644 index 991a8b86e0..0000000000 --- a/sqlx-core/src/mssql/options/connect.rs +++ /dev/null @@ -1,27 +0,0 @@ -use crate::connection::ConnectOptions; -use crate::error::Error; -use crate::mssql::{MssqlConnectOptions, MssqlConnection}; -use futures_core::future::BoxFuture; -use log::LevelFilter; -use std::time::Duration; - -impl ConnectOptions for MssqlConnectOptions { - type Connection = MssqlConnection; - - fn connect(&self) -> BoxFuture<'_, Result> - where - Self::Connection: Sized, - { - Box::pin(MssqlConnection::establish(self)) - } - - fn log_statements(&mut self, level: LevelFilter) -> &mut Self { - self.log_settings.log_statements(level); - self - } - - fn log_slow_statements(&mut self, level: LevelFilter, duration: Duration) -> &mut Self { - self.log_settings.log_slow_statements(level, duration); - self - } -} diff --git a/sqlx-core/src/mssql/options/mod.rs b/sqlx-core/src/mssql/options/mod.rs deleted file mode 100644 index 8a68fdbffc..0000000000 --- a/sqlx-core/src/mssql/options/mod.rs +++ /dev/null @@ -1,58 +0,0 @@ -use crate::connection::LogSettings; - -mod connect; -mod parse; - -#[derive(Debug, Clone)] -pub struct MssqlConnectOptions { - pub(crate) host: String, - pub(crate) port: u16, - pub(crate) username: String, - pub(crate) database: String, - pub(crate) password: Option, - pub(crate) log_settings: LogSettings, -} - -impl Default for MssqlConnectOptions { - fn default() -> Self { - Self::new() - } -} - -impl MssqlConnectOptions { - pub fn new() -> Self { - Self { - port: 1433, - host: String::from("localhost"), - database: String::from("master"), - username: String::from("sa"), - password: None, - log_settings: Default::default(), - } - } - - pub fn host(mut self, host: &str) -> Self { - self.host = host.to_owned(); - self - } - - pub fn port(mut self, port: u16) -> Self { - self.port = port; - self - } - - pub fn username(mut self, username: &str) -> Self { - self.username = username.to_owned(); - self - } - - pub fn password(mut self, password: &str) -> Self { - self.password = Some(password.to_owned()); - self - } - - pub fn database(mut self, database: &str) -> Self { - self.database = database.to_owned(); - self - } -} diff --git a/sqlx-core/src/mssql/options/parse.rs b/sqlx-core/src/mssql/options/parse.rs deleted file mode 100644 index 65c8d2a4ac..0000000000 --- a/sqlx-core/src/mssql/options/parse.rs +++ /dev/null @@ -1,62 +0,0 @@ -use crate::error::Error; -use crate::mssql::MssqlConnectOptions; -use percent_encoding::percent_decode_str; -use std::str::FromStr; -use url::Url; - -impl FromStr for MssqlConnectOptions { - type Err = Error; - - fn from_str(s: &str) -> Result { - let url: Url = s.parse().map_err(Error::config)?; - let mut options = Self::new(); - - if let Some(host) = url.host_str() { - options = options.host(host); - } - - if let Some(port) = url.port() { - options = options.port(port); - } - - let username = url.username(); - if !username.is_empty() { - options = options.username( - &*percent_decode_str(username) - .decode_utf8() - .map_err(Error::config)?, - ); - } - - if let Some(password) = url.password() { - options = options.password( - &*percent_decode_str(password) - .decode_utf8() - .map_err(Error::config)?, - ); - } - - let path = url.path().trim_start_matches('/'); - if !path.is_empty() { - options = options.database(path); - } - - Ok(options) - } -} - -#[test] -fn it_parses_username_with_at_sign_correctly() { - let url = "mysql://user@hostname:password@hostname:5432/database"; - let opts = MssqlConnectOptions::from_str(url).unwrap(); - - assert_eq!("user@hostname", &opts.username); -} - -#[test] -fn it_parses_password_with_non_ascii_chars_correctly() { - let url = "mysql://username:p@ssw0rd@hostname:5432/database"; - let opts = MssqlConnectOptions::from_str(url).unwrap(); - - assert_eq!(Some("p@ssw0rd".into()), opts.password); -} diff --git a/sqlx-core/src/mssql/protocol/col_meta_data.rs b/sqlx-core/src/mssql/protocol/col_meta_data.rs deleted file mode 100644 index a3eaa301f2..0000000000 --- a/sqlx-core/src/mssql/protocol/col_meta_data.rs +++ /dev/null @@ -1,133 +0,0 @@ -use bitflags::bitflags; -use bytes::{Buf, Bytes}; - -use crate::error::Error; -use crate::ext::ustr::UStr; -use crate::mssql::io::MssqlBufExt; -use crate::mssql::protocol::type_info::TypeInfo; -use crate::mssql::MssqlColumn; -use crate::HashMap; - -#[derive(Debug)] -pub(crate) struct ColMetaData; - -#[derive(Debug)] -pub(crate) struct ColumnData { - // The user type ID of the data type of the column. Depending on the TDS version that is used, - // valid values are 0x0000 or 0x00000000, with the exceptions of data type - // TIMESTAMP (0x0050 or 0x00000050) and alias types (greater than 0x00FF or 0x000000FF). - #[allow(dead_code)] - pub(crate) user_type: u32, - - pub(crate) flags: Flags, - pub(crate) type_info: TypeInfo, - - // TODO: pub(crate) table_name: Option>, - // TODO: crypto_meta_data: Option, - - // The column name. It contains the column name length and column name. - pub(crate) col_name: String, -} - -bitflags! { - #[cfg_attr(feature = "offline", derive(serde::Serialize, serde::Deserialize))] - pub struct Flags: u16 { - // Its value is 1 if the column is nullable. - const NULLABLE = 0x0001; - - // Set to 1 for string columns with binary collation and always for the XML data type. - // Set to 0 otherwise. - const CASE_SEN = 0x0002; - - // usUpdateable is a 2-bit field. Its value is 0 if column is read-only, 1 if column is - // read/write and2 if updateable is unknown. - const UPDATEABLE1 = 0x0004; - const UPDATEABLE2 = 0x0008; - - // Its value is 1 if the column is an identity column. - const IDENITTY = 0x0010; - - // Its value is 1 if the column is a COMPUTED column. - const COMPUTED = 0x0020; - - // Its value is 1 if the column is a fixed-length common language runtime - // user-defined type (CLR UDT). - const FIXED_LEN_CLR_TYPE = 0x0100; - - // fSparseColumnSet, introduced in TDSversion 7.3.B, is a bit flag. Its value is 1 if the - // column is the special XML column for the sparse column set. For information about using - // column sets, see [MSDN-ColSets] - const SPARSE_COLUMN_SET = 0x0200; - - // Its value is 1 if the column is encrypted transparently and - // has to be decrypted to view the plaintext value. This flag is valid when the column - // encryption feature is negotiated between client and server and is turned on. - const ENCRYPTED = 0x0400; - - // Its value is 1 if the column is part of a hidden primary key created to support a - // T-SQL SELECT statement containing FOR BROWSE. - const HIDDEN = 0x0800; - - // Its value is 1 if the column is part of a primary key for the row - // and the T-SQL SELECT statement contains FOR BROWSE. - const KEY = 0x1000; - - // Its value is 1 if it is unknown whether the column might be nullable. - const NULLABLE_UNKNOWN = 0x2000; - } -} - -impl ColMetaData { - pub(crate) fn get( - buf: &mut Bytes, - columns: &mut Vec, - column_names: &mut HashMap, - ) -> Result<(), Error> { - columns.clear(); - column_names.clear(); - - let mut count = buf.get_u16_le(); - let mut ordinal = 0; - - if count == 0xffff { - // In the event that the client requested no metadata to be returned, the value of - // Count will be 0xFFFF. This has the same effect on Count as a - // zero value (for example, no ColumnData is sent). - count = 0; - } else { - columns.reserve(count as usize); - } - - while count > 0 { - let col = MssqlColumn::new(ColumnData::get(buf)?, ordinal); - - column_names.insert(col.name.clone(), ordinal); - columns.push(col); - - count -= 1; - ordinal += 1; - } - - Ok(()) - } -} - -impl ColumnData { - fn get(buf: &mut Bytes) -> Result { - let user_type = buf.get_u32_le(); - let flags = Flags::from_bits_truncate(buf.get_u16_le()); - let type_info = TypeInfo::get(buf)?; - - // TODO: table_name - // TODO: crypto_meta_data - - let name = buf.get_b_varchar()?; - - Ok(Self { - user_type, - flags, - type_info, - col_name: name, - }) - } -} diff --git a/sqlx-core/src/mssql/protocol/done.rs b/sqlx-core/src/mssql/protocol/done.rs deleted file mode 100644 index 740232a0c3..0000000000 --- a/sqlx-core/src/mssql/protocol/done.rs +++ /dev/null @@ -1,55 +0,0 @@ -use bitflags::bitflags; -use bytes::{Buf, Bytes}; - -use crate::error::Error; - -#[derive(Debug)] -pub(crate) struct Done { - pub(crate) status: Status, - - // The token of the current SQL statement. The token value is provided and controlled by the - // application layer, which utilizes TDS. The TDS layer does not evaluate the value. - #[allow(dead_code)] - cursor_command: u16, - - // The count of rows that were affected by the SQL statement. The value of DoneRowCount is - // valid if the value of Status includes DONE_COUNT. - pub(crate) affected_rows: u64, // NOTE: u32 before TDS 7.2 -} - -impl Done { - pub(crate) fn get(buf: &mut Bytes) -> Result { - let status = Status::from_bits_truncate(buf.get_u16_le()); - let cursor_command = buf.get_u16_le(); - let affected_rows = buf.get_u64_le(); - - Ok(Self { - affected_rows, - status, - cursor_command, - }) - } -} - -bitflags! { - pub struct Status: u16 { - // This DONEINPROC message is not the final DONE/DONEPROC/DONEINPROC message in - // the response; more data streams are to follow. - const DONE_MORE = 0x0001; - - // An error occurred on the current SQL statement or execution of a stored procedure was - // interrupted. A preceding ERROR token SHOULD be sent when this bit is set. - const DONE_ERROR = 0x0002; - - // A transaction is in progress. - const DONE_INXACT = 0x0004; - - // The DoneRowCount value is valid. This is used to distinguish between a valid value of 0 - // for DoneRowCount or just an initialized variable. - const DONE_COUNT = 0x0010; - - // Used in place of DONE_ERROR when an error occurred on the current SQL statement that is - // severe enough to require the result set, if any, to be discarded. - const DONE_SRVERROR = 0x0100; - } -} diff --git a/sqlx-core/src/mssql/protocol/env_change.rs b/sqlx-core/src/mssql/protocol/env_change.rs deleted file mode 100644 index 2806736ff6..0000000000 --- a/sqlx-core/src/mssql/protocol/env_change.rs +++ /dev/null @@ -1,65 +0,0 @@ -use bytes::{Buf, Bytes}; - -use crate::error::Error; -use crate::mssql::io::MssqlBufExt; - -#[derive(Debug)] -#[allow(dead_code)] -pub(crate) enum EnvChange { - Database(String), - Language(String), - CharacterSet(String), - PacketSize(String), - UnicodeDataSortingLocalId(String), - UnicodeDataSortingComparisonFlags(String), - SqlCollation(Bytes), - - // TDS 7.2+ - BeginTransaction(u64), - CommitTransaction(u64), - RollbackTransaction(u64), - EnlistDtcTransaction, - DefectTransaction, - RealTimeLogShipping, - PromoteTransaction, - TransactionManagerAddress, - TransactionEnded, - ResetConnectionCompletionAck, - LoginRequestUserNameAck, - - // TDS 7.4+ - RoutingInformation, -} - -impl EnvChange { - pub(crate) fn get(buf: &mut Bytes) -> Result { - let len = buf.get_u16_le(); - let ty = buf.get_u8(); - let mut data = buf.split_to((len - 1) as usize); - - Ok(match ty { - 1 => EnvChange::Database(data.get_b_varchar()?), - 2 => EnvChange::Language(data.get_b_varchar()?), - 3 => EnvChange::CharacterSet(data.get_b_varchar()?), - 4 => EnvChange::PacketSize(data.get_b_varchar()?), - 5 => EnvChange::UnicodeDataSortingLocalId(data.get_b_varchar()?), - 6 => EnvChange::UnicodeDataSortingComparisonFlags(data.get_b_varchar()?), - 7 => EnvChange::SqlCollation(data.get_b_varbyte()), - 8 => EnvChange::BeginTransaction(data.get_b_varbyte().get_u64_le()), - - 9 => { - let _ = data.get_u8(); - EnvChange::CommitTransaction(data.get_u64_le()) - } - - 10 => { - let _ = data.get_u8(); - EnvChange::RollbackTransaction(data.get_u64_le()) - } - - _ => { - return Err(err_protocol!("unexpected value {} for ENVCHANGE Type", ty)); - } - }) - } -} diff --git a/sqlx-core/src/mssql/protocol/error.rs b/sqlx-core/src/mssql/protocol/error.rs deleted file mode 100644 index 003311b66c..0000000000 --- a/sqlx-core/src/mssql/protocol/error.rs +++ /dev/null @@ -1,54 +0,0 @@ -use crate::mssql::io::MssqlBufExt; -use bytes::{Buf, Bytes}; - -#[derive(Debug)] -pub(crate) struct Error { - // The error number - pub(crate) number: i32, - - // The error state, used as a modifier to the error number. - pub(crate) state: u8, - - // The class (severity) of the error. A class of less than 10 indicates - // an informational message. - pub(crate) class: u8, - - // The message text length and message text using US_VARCHAR format. - pub(crate) message: String, - - // The server name length and server name using B_VARCHAR format - pub(crate) server: String, - - // The stored procedure name length and the stored procedure name using B_VARCHAR format - pub(crate) procedure: String, - - // The line number in the SQL batch or stored procedure that caused the error. Line numbers - // begin at 1. If the line number is not applicable to the message, the - // value of LineNumber is 0. - pub(crate) line: i32, -} - -impl Error { - pub(crate) fn get(buf: &mut Bytes) -> Result { - let len = buf.get_u16_le(); - let mut data = buf.split_to(len as usize); - - let number = data.get_i32_le(); - let state = data.get_u8(); - let class = data.get_u8(); - let message = data.get_us_varchar()?; - let server = data.get_b_varchar()?; - let procedure = data.get_b_varchar()?; - let line = data.get_i32_le(); - - Ok(Self { - number, - state, - class, - message, - server, - procedure, - line, - }) - } -} diff --git a/sqlx-core/src/mssql/protocol/header.rs b/sqlx-core/src/mssql/protocol/header.rs deleted file mode 100644 index 8f2f2f7e03..0000000000 --- a/sqlx-core/src/mssql/protocol/header.rs +++ /dev/null @@ -1,45 +0,0 @@ -use crate::io::Encode; - -pub(crate) struct AllHeaders<'a>(pub(crate) &'a [Header]); - -impl Encode<'_> for AllHeaders<'_> { - fn encode_with(&self, buf: &mut Vec, _: ()) { - let offset = buf.len(); - buf.resize(buf.len() + 4, 0); - - for header in self.0 { - header.encode_with(buf, ()); - } - - let len = buf.len() - offset; - buf[offset..(offset + 4)].copy_from_slice(&(len as u32).to_le_bytes()); - } -} - -pub(crate) enum Header { - TransactionDescriptor { - // number of requests currently active on the connection - outstanding_request_count: u32, - - // for each connection, a number that uniquely identifies the transaction with which the - // request is associated; initially generated by the server when a new transaction is - // created and returned to the client as part of the ENVCHANGE token stream - transaction_descriptor: u64, - }, -} - -impl Encode<'_> for Header { - fn encode_with(&self, buf: &mut Vec, _: ()) { - match self { - Header::TransactionDescriptor { - outstanding_request_count, - transaction_descriptor, - } => { - buf.extend(&18_u32.to_le_bytes()); // [HeaderLength] 4 + 2 + 8 + 4 - buf.extend(&2_u16.to_le_bytes()); // [HeaderType] - buf.extend(&transaction_descriptor.to_le_bytes()); - buf.extend(&outstanding_request_count.to_le_bytes()); - } - } - } -} diff --git a/sqlx-core/src/mssql/protocol/info.rs b/sqlx-core/src/mssql/protocol/info.rs deleted file mode 100644 index 5b305f6381..0000000000 --- a/sqlx-core/src/mssql/protocol/info.rs +++ /dev/null @@ -1,59 +0,0 @@ -use bytes::{Buf, Bytes}; - -use crate::error::Error; -use crate::mssql::io::MssqlBufExt; - -#[allow(dead_code)] -#[derive(Debug)] -pub(crate) struct Info { - pub(crate) number: u32, - pub(crate) state: u8, - pub(crate) class: u8, - pub(crate) message: String, - pub(crate) server: String, - pub(crate) procedure: String, - pub(crate) line: u32, -} - -impl Info { - pub(crate) fn get(buf: &mut Bytes) -> Result { - let len = buf.get_u16_le(); - let mut data = buf.split_to(len as usize); - - let number = data.get_u32_le(); - let state = data.get_u8(); - let class = data.get_u8(); - let message = data.get_us_varchar()?; - let server = data.get_b_varchar()?; - let procedure = data.get_b_varchar()?; - let line = data.get_u32_le(); - - Ok(Self { - number, - state, - class, - message, - server, - procedure, - line, - }) - } -} - -#[test] -fn test_get() { - #[rustfmt::skip] - let mut buf = Bytes::from_static(&[ - 0x74, 0, 0x47, 0x16, 0, 0, 1, 0, 0x27, 0, 0x43, 0, 0x68, 0, 0x61, 0, 0x6e, 0, 0x67, 0, 0x65, 0, 0x64, 0, 0x20, 0, 0x6c, 0, 0x61, 0, 0x6e, 0, 0x67, 0, 0x75, 0, 0x61, 0, 0x67, 0, 0x65, 0, 0x20, 0, 0x73, 0, 0x65, 0, 0x74, 0, 0x74, 0, 0x69, 0, 0x6e, 0, 0x67, 0, 0x20, 0, 0x74, 0, 0x6f, 0, 0x20, 0, 0x75, 0, 0x73, 0, 0x5f, 0, 0x65, 0, 0x6e, 0, 0x67, 0, 0x6c, 0, 0x69, 0, 0x73, 0, 0x68, 0, 0x2e, 0, 0xc, 0x61, 0, 0x62, 0, 0x64, 0, 0x30, 0, 0x62, 0, 0x36, 0, 0x37, 0, 0x62, 0, 0x64, 0, 0x34, 0, 0x39, 0, 0x33, 0, 0, 1, 0, 0, 0, 0xad, 0x36, 0, 1, 0x74, 0, 0, 4, 0x16, 0x4d, 0, 0x69, 0, 0x63, 0, 0x72, 0, 0x6f, 0, 0x73, 0, 0x6f, 0, 0x66, 0, 0x74, 0, 0x20, 0, 0x53, 0, 0x51, 0, 0x4c, 0, 0x20, 0, 0x53, 0, 0x65, 0, 0x72, 0, 0x76, 0, 0x65, 0, 0x72, 0, 0, 0, 0, 0, 0xf, 0, 0x10, 0x7f, 0xe3, 0x13, 0, 4, 4, 0x34, 0, 0x30, 0, 0x39, 0, 0x36, 0, 4, 0x34, 0, 0x30, 0, 0x39, 0, 0x36, 0, 0xfd, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 - ]); - - let info = Info::get(&mut buf).unwrap(); - - assert_eq!(info.number, 5703); - assert_eq!(info.state, 1); - assert_eq!(info.class, 0); - assert_eq!(info.message, "Changed language setting to us_english."); - assert_eq!(info.server, "abd0b67bd493"); - assert_eq!(info.procedure, ""); - assert_eq!(info.line, 1); -} diff --git a/sqlx-core/src/mssql/protocol/login.rs b/sqlx-core/src/mssql/protocol/login.rs deleted file mode 100644 index a001e00acc..0000000000 --- a/sqlx-core/src/mssql/protocol/login.rs +++ /dev/null @@ -1,243 +0,0 @@ -use crate::io::Encode; -use crate::mssql::io::MssqlBufMutExt; - -#[derive(Debug)] -pub struct Login7<'a> { - pub version: u32, - pub packet_size: u32, - pub client_program_version: u32, - pub client_pid: u32, - pub hostname: &'a str, - pub username: &'a str, - pub password: &'a str, - pub app_name: &'a str, - pub server_name: &'a str, - pub client_interface_name: &'a str, - pub language: &'a str, - pub database: &'a str, - pub client_id: [u8; 6], -} - -impl Encode<'_> for Login7<'_> { - fn encode_with(&self, buf: &mut Vec, _: ()) { - // [Length] The total length of the LOGIN7 structure. - let beg = buf.len(); - buf.extend(&0_u32.to_le_bytes()); - - // [TDSVersion] The highest TDS version supported by the client. - buf.extend(&self.version.to_le_bytes()); - - // [PacketSize] The packet size being requested by the client. - buf.extend(&self.packet_size.to_le_bytes()); - - // [ClientProgVer] The version of the **interface** library. - buf.extend(&self.client_program_version.to_le_bytes()); - - // [ClientPID] The process ID of the client application. - buf.extend(&self.client_pid.to_le_bytes()); - - // [ConnectionID] The connection ID of the primary server. - buf.extend(&0_u32.to_le_bytes()); - - // [OptionFlags1] - // 7 | SET_LANG_ON (1) – Require a warning message for a language choice statement - // 6 | INIT_DB_FATAL (1) – Fail to change to initial database should be fatal - // 5 | USE_DB_ON (1) – Require a warning message for a db change statement - // 4 | DUMPLOAD_OFF (0) - // 3-2 | FLOAT_IEEE_754 (0) - // 1 | CHARSET_ASCII (0) - // 0 | ORDER_X86 (0) - buf.push(0b11_10_00_00); - - // [OptionsFlags2] - // 6 | INTEGRATED_SECURITY_OFF (0) - // 5-4 | USER_NORMAL (0) - // 3 | - // 2 | - // 1 | ODBC_ON (1) - // 0 | INIT_LANG_FATAL (1) - buf.push(0b00_00_00_11); - - // [TypeFlags] - // 2 | - // 1 | OLEDB_OFF (0) - // 0 | SQL_DFLT (0) - buf.push(0); - - // [OptionFlags3] - // 4 | - // 3 | - // 2 | - // 1 | - // 0 | - buf.push(0); - - // [ClientTimeZone] This field is not used and can be set to zero. - buf.extend(&0_u32.to_le_bytes()); - - // [ClientLanguageCodeIdentifier] The language code identifier (LCID) value for - // the client collation. - buf.extend(&0_u32.to_le_bytes()); - - // [OffsetLength] pre-allocate a space for all offset, length pairs - let mut offsets = buf.len(); - buf.resize(buf.len() + 58, 0); - - // [Hostname] The client machine name - write_str(buf, &mut offsets, beg, self.hostname); - - // [UserName] The client user ID - write_str(buf, &mut offsets, beg, self.username); - - // [Password] The password supplied by the client - let password_start = buf.len(); - write_str(buf, &mut offsets, beg, self.password); - - // Before submitting a password from the client to the server, for every byte in the - // password buffer starting with the position pointed to by ibPassword or - // ibChangePassword, the client SHOULD first swap the four high bits with - // the four low bits and then do a bit-XOR with 0xA5 (10100101). - for i in password_start..buf.len() { - let b = buf[i]; - buf[i] = ((b << 4) & 0xf0 | (b >> 4) & 0x0f) ^ 0xa5; - } - - // [AppName] The client application name - write_str(buf, &mut offsets, beg, self.app_name); - - // [ServerName] The server name - write_str(buf, &mut offsets, beg, self.server_name); - - // [Extension] Points to an extension block. - // TODO: Implement to get FeatureExt which should let us use UTF-8 - write_offset(buf, &mut offsets, beg); - offsets += 2; - - // [CltIntName] The interface library name - write_str(buf, &mut offsets, beg, self.client_interface_name); - - // [Language] The initial language (overrides the user IDs language) - write_str(buf, &mut offsets, beg, self.language); - - // [Database] The initial database (overrides the user IDs database) - write_str(buf, &mut offsets, beg, self.database); - - // [ClientID] The unique client ID. Can be all zero. - buf[offsets..(offsets + 6)].copy_from_slice(&self.client_id); - offsets += 6; - - // [SSPI] SSPI data - write_offset(buf, &mut offsets, beg); - offsets += 2; - - // [AtchDBFile] The file name for a database that is to be attached - write_offset(buf, &mut offsets, beg); - offsets += 2; - - // [ChangePassword] New password for the specified login - write_offset(buf, &mut offsets, beg); - - // Establish the length of the entire structure - let len = buf.len(); - buf[beg..beg + 4].copy_from_slice(&((len - beg) as u32).to_le_bytes()); - } -} - -fn write_offset(buf: &mut Vec, offsets: &mut usize, beg: usize) { - // The offset must be relative to the beginning of the packet payload, after - // the packet header - - let offset = buf.len() - beg; - buf[*offsets..(*offsets + 2)].copy_from_slice(&(offset as u16).to_le_bytes()); - - *offsets += 2; -} - -fn write_str(buf: &mut Vec, offsets: &mut usize, beg: usize, s: &str) { - // Write the offset - write_offset(buf, offsets, beg); - - // Write the length, in UCS-2 characters - buf[*offsets..(*offsets + 2)].copy_from_slice(&(s.len() as u16).to_le_bytes()); - *offsets += 2; - - // Encode the character sequence as UCS-2 (precursor to UTF16-LE) - buf.put_utf16_str(s); -} - -#[test] -fn test_encode_login() { - let mut buf = Vec::new(); - - let login = Login7 { - version: 0x72090002, - client_program_version: 0x07_00_00_00, - client_pid: 0x0100, - packet_size: 0x1000, - hostname: "skostov1", - username: "sa", - password: "", - app_name: "OSQL-32", - server_name: "", - client_interface_name: "ODBC", - language: "", - database: "", - client_id: [0x00, 0x50, 0x8B, 0xE2, 0xB7, 0x8F], - }; - - // Adapted from v20191101 of MS-TDS - #[rustfmt::skip] - let expected = vec![ - // Packet Header - /* 0x10, 0x01, 0x00, 0x90, 0x00, 0x00, 0x01, 0x00, */ - - 0x88, 0x00, 0x00, 0x00, // Length - 0x02, 0x00, 0x09, 0x72, // TDS Version = SQL Server 2005 - 0x00, 0x10, 0x00, 0x00, // Packet Size = 1048576 or 1 Mi - 0x00, 0x00, 0x00, 0x07, // Client Program Version = 7 - 0x00, 0x01, 0x00, 0x00, // Client PID = 0x01_00_00 - 0x00, 0x00, 0x00, 0x00, // Connection ID - 0xE0, // [OptionFlags1] 0b1110_0000 - 0x03, // [OptionFlags2] 0b0000_0011 - 0x00, // [TypeFlags] - 0x00, // [OptionFlags3] - 0x00, 0x00, 0x00, 0x00, // [ClientTimeZone] - 0x00, 0x00, 0x00, 0x00, // [ClientLCID] - 0x5E, 0x00, // [ibHostName] - 0x08, 0x00, // [cchHostName] - 0x6E, 0x00, // [ibUserName] - 0x02, 0x00, // [cchUserName] - 0x72, 0x00, // [ibPassword] - 0x00, 0x00, // [cchPassword] - 0x72, 0x00, // [ibAppName] - 0x07, 0x00, // [cchAppName] - 0x80, 0x00, // [ibServerName] - 0x00, 0x00, // [cchServerName] - 0x80, 0x00, // [ibUnused] - 0x00, 0x00, // [cbUnused] - 0x80, 0x00, // [ibCltIntName] - 0x04, 0x00, // [cchCltIntName] - 0x88, 0x00, // [ibLanguage] - 0x00, 0x00, // [cchLanguage] - 0x88, 0x00, // [ibDatabase] - 0x00, 0x00, // [chDatabase] - 0x00, 0x50, 0x8B, // [ClientID] - 0xE2, 0xB7, 0x8F, - 0x88, 0x00, // [ibSSPI] - 0x00, 0x00, // [cchSSPI] - 0x88, 0x00, // [ibAtchDBFile] - 0x00, 0x00, // [cchAtchDBFile] - 0x88, 0x00, // [ibChangePassword] - 0x00, 0x00, // [cchChangePassword] - 0x00, 0x00, 0x00, 0x00, // [cbSSPILong] - 0x73, 0x00, 0x6B, 0x00, 0x6F, 0x00, 0x73, 0x00, 0x74, 0x00, // [Data] - 0x6F, 0x00, 0x76, 0x00, 0x31, 0x00, 0x73, 0x00, 0x61, 0x00, - 0x4F, 0x00, 0x53, 0x00, 0x51, 0x00, 0x4C, 0x00, 0x2D, 0x00, - 0x33, 0x00, 0x32, 0x00, 0x4F, 0x00, 0x44, 0x00, 0x42, 0x00, - 0x43, 0x00, - ]; - - login.encode(&mut buf); - - assert_eq!(expected, buf); -} diff --git a/sqlx-core/src/mssql/protocol/login_ack.rs b/sqlx-core/src/mssql/protocol/login_ack.rs deleted file mode 100644 index 7944474b46..0000000000 --- a/sqlx-core/src/mssql/protocol/login_ack.rs +++ /dev/null @@ -1,60 +0,0 @@ -use bytes::{Buf, Bytes}; - -use crate::error::Error; -use crate::mssql::io::MssqlBufExt; -use crate::mssql::protocol::pre_login::Version; - -#[allow(dead_code)] -#[derive(Debug)] -pub(crate) struct LoginAck { - pub(crate) interface: u8, - pub(crate) tds_version: u32, - pub(crate) program_name: String, - pub(crate) program_version: Version, -} - -impl LoginAck { - pub(crate) fn get(buf: &mut Bytes) -> Result { - let len = buf.get_u16_le(); - let mut data = buf.split_to(len as usize); - - let interface = data.get_u8(); - let tds_version = data.get_u32_le(); - let program_name = data.get_b_varchar()?; - let program_version_major = data.get_u8(); - let program_version_minor = data.get_u8(); - let program_version_build = data.get_u16(); - - Ok(Self { - interface, - tds_version, - program_name, - program_version: Version { - major: program_version_major, - minor: program_version_minor, - build: program_version_build, - sub_build: 0, - }, - }) - } -} - -#[test] -fn test_get() { - #[rustfmt::skip] - let mut buf = Bytes::from_static(&[ - 0x36, 0, 1, 0x74, 0, 0, 4, 0x16, 0x4d, 0, 0x69, 0, 0x63, 0, 0x72, 0, 0x6f, 0, 0x73, 0, 0x6f, 0, 0x66, 0, 0x74, 0, 0x20, 0, 0x53, 0, 51, 0, 0x4c, 0, 0x20, 0, 0x53, 0, 0x65, 0, 0x72, 0, 0x76, 0, 0x65, 0, 0x72, 0, 0, 0, 0, 0, 0xf, 0, 0x10, 0x7f, 0xe3, 0x13, 0, 4, 4, 0x34, 0, 0x30, 0, 0x39, 0, 0x36, 0, 4, 0x34, 0, 0x30, 0, 0x39, 0, 0x36, 0, 0xfd, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 - ]); - - let login_ack = LoginAck::get(&mut buf).unwrap(); - - assert_eq!(login_ack.interface, 1); - assert_eq!(login_ack.tds_version, 67108980); - - assert_eq!(login_ack.program_version.major, 15); - assert_eq!(login_ack.program_version.minor, 0); - assert_eq!(login_ack.program_version.build, 4223); - assert_eq!(login_ack.program_version.sub_build, 0); - - assert_eq!(login_ack.program_name, "Microsoft S3L Server\0\0"); -} diff --git a/sqlx-core/src/mssql/protocol/message.rs b/sqlx-core/src/mssql/protocol/message.rs deleted file mode 100644 index 5299a2ad9f..0000000000 --- a/sqlx-core/src/mssql/protocol/message.rs +++ /dev/null @@ -1,64 +0,0 @@ -use bytes::{Buf, Bytes}; - -use crate::mssql::protocol::done::Done; -use crate::mssql::protocol::login_ack::LoginAck; -use crate::mssql::protocol::order::Order; -use crate::mssql::protocol::return_status::ReturnStatus; -use crate::mssql::protocol::return_value::ReturnValue; -use crate::mssql::protocol::row::Row; - -#[derive(Debug)] -pub(crate) enum Message { - LoginAck(LoginAck), - Done(Done), - DoneInProc(Done), - DoneProc(Done), - Row(Row), - ReturnStatus(ReturnStatus), - ReturnValue(ReturnValue), - Order(Order), -} - -#[derive(Debug)] -pub(crate) enum MessageType { - Info, - LoginAck, - EnvChange, - Done, - DoneProc, - DoneInProc, - Row, - NbcRow, - Error, - ColMetaData, - ReturnStatus, - ReturnValue, - Order, -} - -impl MessageType { - pub(crate) fn get(buf: &mut Bytes) -> Result { - Ok(match buf.get_u8() { - 0x81 => MessageType::ColMetaData, - 0xaa => MessageType::Error, - 0xab => MessageType::Info, - 0xac => MessageType::ReturnValue, - 0xad => MessageType::LoginAck, - 0xd1 => MessageType::Row, - 0xd2 => MessageType::NbcRow, - 0xe3 => MessageType::EnvChange, - 0x79 => MessageType::ReturnStatus, - 0xa9 => MessageType::Order, - 0xfd => MessageType::Done, - 0xfe => MessageType::DoneProc, - 0xff => MessageType::DoneInProc, - - ty => { - return Err(err_protocol!( - "unknown value `0x{:02x?}` for message type in token stream", - ty - )); - } - }) - } -} diff --git a/sqlx-core/src/mssql/protocol/mod.rs b/sqlx-core/src/mssql/protocol/mod.rs deleted file mode 100644 index eb8741234b..0000000000 --- a/sqlx-core/src/mssql/protocol/mod.rs +++ /dev/null @@ -1,18 +0,0 @@ -pub(crate) mod col_meta_data; -pub(crate) mod done; -pub(crate) mod env_change; -pub(crate) mod error; -pub(crate) mod header; -pub(crate) mod info; -pub(crate) mod login; -pub(crate) mod login_ack; -pub(crate) mod message; -pub(crate) mod order; -pub(crate) mod packet; -pub(crate) mod pre_login; -pub(crate) mod return_status; -pub(crate) mod return_value; -pub(crate) mod row; -pub(crate) mod rpc; -pub(crate) mod sql_batch; -pub(crate) mod type_info; diff --git a/sqlx-core/src/mssql/protocol/order.rs b/sqlx-core/src/mssql/protocol/order.rs deleted file mode 100644 index d3171a3269..0000000000 --- a/sqlx-core/src/mssql/protocol/order.rs +++ /dev/null @@ -1,18 +0,0 @@ -use bytes::{Buf, Bytes}; - -use crate::error::Error; - -#[derive(Debug)] -pub(crate) struct Order { - #[allow(dead_code)] - columns: Bytes, -} - -impl Order { - pub(crate) fn get(buf: &mut Bytes) -> Result { - let len = buf.get_u16_le(); - let columns = buf.split_to(len as usize); - - Ok(Self { columns }) - } -} diff --git a/sqlx-core/src/mssql/protocol/packet.rs b/sqlx-core/src/mssql/protocol/packet.rs deleted file mode 100644 index eb99de3f0b..0000000000 --- a/sqlx-core/src/mssql/protocol/packet.rs +++ /dev/null @@ -1,138 +0,0 @@ -use bitflags::bitflags; -use bytes::{Buf, Bytes}; - -use crate::error::Error; -use crate::io::{Decode, Encode}; - -#[derive(Debug)] -pub(crate) struct PacketHeader { - // Type defines the type of message. Type is a 1-byte unsigned char. - pub(crate) r#type: PacketType, - - // Status is a bit field used to indicate the message state. Status is a 1-byte unsigned char. - pub(crate) status: Status, - - // Length is the size of the packet including the 8 bytes in the packet header. - pub(crate) length: u16, - - // The process ID on the server, corresponding to the current connection. - pub(crate) server_process_id: u16, - - // Packet ID is used for numbering message packets that contain data in addition to the packet - // header. Packet ID is a 1-byte, unsigned char. Each time packet data is sent, the value of - // PacketID is incremented by 1, modulo 256. This allows the receiver to track the sequence - // of TDS packets for a given message. This value is currently ignored. - pub(crate) packet_id: u8, -} - -impl<'s> Encode<'s, &'s mut usize> for PacketHeader { - fn encode_with(&self, buf: &mut Vec, offset: &'s mut usize) { - buf.push(self.r#type as u8); - buf.push(self.status.bits()); - - *offset = buf.len(); - buf.extend(&self.length.to_be_bytes()); - - buf.extend(&self.server_process_id.to_be_bytes()); - buf.push(self.packet_id); - - // window, unused - buf.push(0); - } -} - -impl Decode<'_> for PacketHeader { - fn decode_with(mut buf: Bytes, _: ()) -> Result { - Ok(Self { - r#type: PacketType::get(buf.get_u8())?, - status: Status::from_bits_truncate(buf.get_u8()), - length: buf.get_u16(), - server_process_id: buf.get_u16(), - packet_id: buf.get_u8(), - }) - } -} - -#[derive(Debug, Copy, PartialEq, Clone)] -pub(crate) enum PacketType { - // Pre-login. Should always be #18 unless we decide to try and support pre 7.0 TDS - PreTds7Login = 2, - PreLogin = 18, - - SqlBatch = 1, - Rpc = 3, - AttentionSignal = 6, - BulkLoadData = 7, - FederatedAuthToken = 8, - TransactionManagerRequest = 14, - Tds7Login = 16, - Sspi = 17, - - TabularResult = 4, -} - -impl PacketType { - pub fn get(value: u8) -> Result { - Ok(match value { - 1 => PacketType::SqlBatch, - 2 => PacketType::PreTds7Login, - 3 => PacketType::Rpc, - 4 => PacketType::TabularResult, - 6 => PacketType::AttentionSignal, - 7 => PacketType::BulkLoadData, - 8 => PacketType::FederatedAuthToken, - 14 => PacketType::TransactionManagerRequest, - 16 => PacketType::Tds7Login, - 17 => PacketType::Sspi, - 18 => PacketType::PreLogin, - - ty => { - return Err(err_protocol!("unknown packet type: {}", ty)); - } - }) - } -} - -// Status is a bit field used to indicate the message state. Status is a 1-byte unsigned char. -// The following Status bit flags are defined. -bitflags! { - pub(crate) struct Status: u8 { - // "Normal" message. - const NORMAL = 0x00; - - // End of message (EOM). The packet is the last packet in the whole request. - const END_OF_MESSAGE = 0x01; - - // (From client to server) Ignore this event (0x01 MUST also be set). - const IGNORE_EVENT = 0x02; - - // RESETCONNECTION - // - // (Introduced in TDS 7.1) - // - // (From client to server) Reset this connection - // before processing event. Only set for event types Batch, RPC, or Transaction Manager - // request. If clients want to set this bit, it MUST be part of the first packet of the - // message. This signals the server to clean up the environment state of the connection - // back to the default environment setting, effectively simulating a logout and a - // subsequent login, and provides server support for connection pooling. This bit SHOULD - // be ignored if it is set in a packet that is not the first packet of the message. - // - // This status bit MUST NOT be set in conjunction with the RESETCONNECTIONSKIPTRAN bit. - // Distributed transactions and isolation levels will not be reset. - const RESET_CONN = 0x08; - - // RESETCONNECTIONSKIPTRAN - // - // (Introduced in TDS 7.3) - // - // (From client to server) Reset the - // connection before processing event but do not modify the transaction state (the - // state will remain the same before and after the reset). The transaction in the - // session can be a local transaction that is started from the session or it can - // be a distributed transaction in which the session is enlisted. This status bit - // MUST NOT be set in conjunction with the RESETCONNECTION bit. - // Otherwise identical to RESETCONNECTION. - const RESET_CONN_SKIP_TRAN = 0x10; - } -} diff --git a/sqlx-core/src/mssql/protocol/pre_login.rs b/sqlx-core/src/mssql/protocol/pre_login.rs deleted file mode 100644 index 7a4b1f1740..0000000000 --- a/sqlx-core/src/mssql/protocol/pre_login.rs +++ /dev/null @@ -1,311 +0,0 @@ -use std::fmt::{self, Display, Formatter}; - -use bitflags::bitflags; -use bytes::{Buf, Bytes}; -use uuid::Uuid; - -use crate::error::Error; -use crate::io::{Decode, Encode}; - -/// A message sent by the client to set up context for login. The server responds to a client -/// `PRELOGIN` message with a message of packet header type `0x04` and the packet data -/// containing a `PRELOGIN` structure. -#[derive(Debug, Default)] -pub(crate) struct PreLogin<'a> { - pub(crate) version: Version, - pub(crate) encryption: Encrypt, - pub(crate) instance: Option<&'a str>, - pub(crate) thread_id: Option, - pub(crate) trace_id: Option, - pub(crate) multiple_active_result_sets: Option, -} - -impl<'de> Decode<'de> for PreLogin<'de> { - fn decode_with(buf: Bytes, _: ()) -> Result { - let mut version = None; - let mut encryption = None; - - // TODO: Decode the remainder of the structure - // let mut instance = None; - // let mut thread_id = None; - // let mut trace_id = None; - // let mut multiple_active_result_sets = None; - - let mut offsets = buf.clone(); - - loop { - let token = offsets.get_u8(); - - match PreLoginOptionToken::get(token) { - Some(token) => { - let offset = offsets.get_u16() as usize; - let size = offsets.get_u16() as usize; - let mut data = &buf[offset..offset + size]; - - match token { - PreLoginOptionToken::Version => { - let major = data.get_u8(); - let minor = data.get_u8(); - let build = data.get_u16(); - let sub_build = data.get_u16(); - - version = Some(Version { - major, - minor, - build, - sub_build, - }); - } - - PreLoginOptionToken::Encryption => { - encryption = Some(Encrypt::from_bits_truncate(data.get_u8())); - } - - tok => todo!("{:?}", tok), - } - } - - None if token == 0xff => { - break; - } - - None => { - return Err(err_protocol!( - "PRELOGIN: unexpected login option token: 0x{:02?}", - token - ) - .into()); - } - } - } - - let version = - version.ok_or(err_protocol!("PRELOGIN: missing required `version` option"))?; - - let encryption = encryption.ok_or(err_protocol!( - "PRELOGIN: missing required `encryption` option" - ))?; - - Ok(Self { - version, - encryption, - - ..Default::default() - }) - } -} - -impl Encode<'_> for PreLogin<'_> { - fn encode_with(&self, buf: &mut Vec, _: ()) { - use PreLoginOptionToken::*; - - // NOTE: Packet headers are written in MssqlStream::write - - // Rules - // PRELOGIN = (*PRELOGIN_OPTION *PL_OPTION_DATA) / SSL_PAYLOAD - // PRELOGIN_OPTION = (PL_OPTION_TOKEN PL_OFFSET PL_OPTION_LENGTH) / TERMINATOR - - // Count the number of set options - let num_options = 2 - + self.instance.map_or(0, |_| 1) - + self.thread_id.map_or(0, |_| 1) - + self.trace_id.as_ref().map_or(0, |_| 1) - + self.multiple_active_result_sets.map_or(0, |_| 1); - - // Calculate the length of the option offset block. Each block is 5 bytes and it ends in - // a 1 byte terminator. - let len_offsets = (num_options * 5) + 1; - let mut offsets = buf.len() as usize; - let mut offset = len_offsets as u16; - - // Reserve a chunk for the offset block and set the final terminator - buf.resize(buf.len() + len_offsets, 0); - let end_offsets = buf.len() - 1; - buf[end_offsets] = 0xff; - - // NOTE: VERSION is a required token, and it MUST be the first token. - Version.put(buf, &mut offsets, &mut offset, 6); - self.version.encode(buf); - - Encryption.put(buf, &mut offsets, &mut offset, 1); - buf.push(self.encryption.bits()); - - if let Some(name) = self.instance { - Instance.put(buf, &mut offsets, &mut offset, name.len() as u16 + 1); - buf.extend_from_slice(name.as_bytes()); - buf.push(b'\0'); - } - - if let Some(id) = self.thread_id { - ThreadId.put(buf, &mut offsets, &mut offset, 4); - buf.extend_from_slice(&id.to_le_bytes()); - } - - if let Some(trace) = &self.trace_id { - ThreadId.put(buf, &mut offsets, &mut offset, 36); - buf.extend_from_slice(trace.connection_id.as_bytes()); - buf.extend_from_slice(trace.activity_id.as_bytes()); - buf.extend_from_slice(&trace.activity_seq.to_be_bytes()); - } - - if let Some(mars) = &self.multiple_active_result_sets { - MultipleActiveResultSets.put(buf, &mut offsets, &mut offset, 1); - buf.push(*mars as u8); - } - } -} - -// token value representing the option (PL_OPTION_TOKEN) -#[derive(Debug, Copy, Clone)] -#[repr(u8)] -enum PreLoginOptionToken { - Version = 0x00, - Encryption = 0x01, - Instance = 0x02, - ThreadId = 0x03, - - // Multiple Active Result Sets (MARS) - MultipleActiveResultSets = 0x04, - - TraceId = 0x05, -} - -impl PreLoginOptionToken { - fn put(self, buf: &mut Vec, pos: &mut usize, offset: &mut u16, len: u16) { - buf[*pos] = self as u8; - *pos += 1; - - buf[*pos..(*pos + 2)].copy_from_slice(&offset.to_be_bytes()); - *pos += 2; - - buf[*pos..(*pos + 2)].copy_from_slice(&len.to_be_bytes()); - *pos += 2; - - *offset += len; - } - - fn get(b: u8) -> Option { - Some(match b { - 0x00 => PreLoginOptionToken::Version, - 0x01 => PreLoginOptionToken::Encryption, - 0x02 => PreLoginOptionToken::Instance, - 0x03 => PreLoginOptionToken::ThreadId, - 0x04 => PreLoginOptionToken::MultipleActiveResultSets, - 0x05 => PreLoginOptionToken::TraceId, - - _ => { - return None; - } - }) - } -} - -#[derive(Debug)] -pub(crate) struct TraceId { - // client application trace ID (GUID_CONNID) - pub(crate) connection_id: Uuid, - - // client application activity ID (GUID_ActivityID) - pub(crate) activity_id: Uuid, - - // client application activity sequence (ActivitySequence) - pub(crate) activity_seq: u32, -} - -// Version of the sender (UL_VERSION) -#[derive(Debug, Default)] -pub(crate) struct Version { - pub(crate) major: u8, - pub(crate) minor: u8, - pub(crate) build: u16, - - // Sub-build number of the sender (US_SUBBUILD) - pub(crate) sub_build: u16, -} - -impl Version { - fn encode(&self, buf: &mut Vec) { - buf.push(self.major); - buf.push(self.minor); - buf.extend(&self.build.to_be_bytes()); - buf.extend(&self.sub_build.to_be_bytes()); - } -} - -impl Display for Version { - fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { - write!(f, "v{}.{}.{}", self.major, self.minor, self.build) - } -} - -bitflags! { - /// During the Pre-Login handshake, the client and the server negotiate the - /// wire encryption to be used. - #[derive(Default)] - pub(crate) struct Encrypt: u8 { - /// Encryption is available but on. - const ON = 0x01; - - /// Encryption is not available. - const NOT_SUPPORTED = 0x02; - - /// Encryption is required. - const REQUIRED = 0x03; - - /// The client certificate should be used to authenticate - /// the user in place of a user/password. - const CLIENT_CERT = 0x80; - } -} - -#[test] -fn test_encode_pre_login() { - let mut buf = Vec::new(); - - let pre_login = PreLogin { - version: Version { - major: 9, - minor: 0, - build: 0, - sub_build: 0, - }, - encryption: Encrypt::ON, - instance: Some(""), - thread_id: Some(0x00000DB8), - multiple_active_result_sets: Some(true), - - ..Default::default() - }; - - // From v20191101 of MS-TDS documentation - #[rustfmt::skip] - let expected = vec![ - 0x00, 0x00, 0x1A, 0x00, 0x06, 0x01, 0x00, 0x20, 0x00, 0x01, 0x02, 0x00, 0x21, 0x00, - 0x01, 0x03, 0x00, 0x22, 0x00, 0x04, 0x04, 0x00, 0x26, 0x00, 0x01, 0xFF, 0x09, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0xB8, 0x0D, 0x00, 0x00, 0x01 - ]; - - pre_login.encode(&mut buf); - - assert_eq!(expected, buf); -} - -#[test] -fn test_decode_pre_login() { - #[rustfmt::skip] - let buffer = Bytes::from_static(&[ - 0, 0, 11, 0, 6, 1, 0, 17, 0, 1, 255, - 14, 0, 12, 209, 0, 0, 0, - ]); - - let pre_login = PreLogin::decode(buffer).unwrap(); - - // v14.0.3281 - assert_eq!(pre_login.version.major, 14); - assert_eq!(pre_login.version.minor, 0); - assert_eq!(pre_login.version.build, 3281); - assert_eq!(pre_login.version.sub_build, 0); - - // ENCRYPT_OFF - assert_eq!(pre_login.encryption.bits(), 0); -} diff --git a/sqlx-core/src/mssql/protocol/return_status.rs b/sqlx-core/src/mssql/protocol/return_status.rs deleted file mode 100644 index 806ceb0fd7..0000000000 --- a/sqlx-core/src/mssql/protocol/return_status.rs +++ /dev/null @@ -1,17 +0,0 @@ -use bytes::{Buf, Bytes}; - -use crate::error::Error; - -#[derive(Debug)] -pub(crate) struct ReturnStatus { - #[allow(dead_code)] - value: i32, -} - -impl ReturnStatus { - pub(crate) fn get(buf: &mut Bytes) -> Result { - let value = buf.get_i32_le(); - - Ok(Self { value }) - } -} diff --git a/sqlx-core/src/mssql/protocol/return_value.rs b/sqlx-core/src/mssql/protocol/return_value.rs deleted file mode 100644 index 1e7d331e21..0000000000 --- a/sqlx-core/src/mssql/protocol/return_value.rs +++ /dev/null @@ -1,77 +0,0 @@ -use bitflags::bitflags; -use bytes::{Buf, Bytes}; - -use crate::error::Error; -use crate::mssql::io::MssqlBufExt; -use crate::mssql::protocol::col_meta_data::Flags; -#[cfg(test)] -use crate::mssql::protocol::type_info::DataType; -use crate::mssql::protocol::type_info::TypeInfo; - -#[allow(dead_code)] -#[derive(Debug)] -pub(crate) struct ReturnValue { - param_ordinal: u16, - param_name: String, - status: ReturnValueStatus, - user_type: u32, - flags: Flags, - pub(crate) type_info: TypeInfo, - pub(crate) value: Option, -} - -bitflags! { - pub(crate) struct ReturnValueStatus: u8 { - // If ReturnValue corresponds to OUTPUT parameter of a stored procedure invocation - const OUTPUT_PARAM = 0x01; - - // If ReturnValue corresponds to return value of User Defined Function. - const USER_DEFINED = 0x02; - } -} - -impl ReturnValue { - pub(crate) fn get(buf: &mut Bytes) -> Result { - let ordinal = buf.get_u16_le(); - let name = buf.get_b_varchar()?; - let status = ReturnValueStatus::from_bits_truncate(buf.get_u8()); - let user_type = buf.get_u32_le(); - let flags = Flags::from_bits_truncate(buf.get_u16_le()); - let type_info = TypeInfo::get(buf)?; - let value = type_info.get_value(buf); - - Ok(Self { - param_ordinal: ordinal, - param_name: name, - status, - user_type, - flags, - type_info, - value, - }) - } -} - -#[test] -fn test_get() { - #[rustfmt::skip] - let mut buf = Bytes::from_static(&[ - 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0x26, 4, 4, 1, 0, 0, 0, 0xfe, 0, 0, 0xe0, 0, 0, 0, 0, 0, 0, 0, 0, 0 - ]); - - let return_value = ReturnValue::get(&mut buf).unwrap(); - - assert_eq!(return_value.param_ordinal, 0); - assert_eq!(return_value.param_name, ""); - assert_eq!( - return_value.status, - ReturnValueStatus::from_bits_truncate(1) - ); - assert_eq!(return_value.user_type, 0); - assert_eq!(return_value.flags, Flags::from_bits_truncate(0)); - assert_eq!(return_value.type_info, TypeInfo::new(DataType::IntN, 4)); - assert_eq!( - return_value.value, - Some(Bytes::from_static(&[0x01, 0, 0, 0])) - ); -} diff --git a/sqlx-core/src/mssql/protocol/row.rs b/sqlx-core/src/mssql/protocol/row.rs deleted file mode 100644 index 1094019ab2..0000000000 --- a/sqlx-core/src/mssql/protocol/row.rs +++ /dev/null @@ -1,44 +0,0 @@ -use bytes::Bytes; - -use crate::error::Error; -use crate::io::BufExt; -use crate::mssql::{MssqlColumn, MssqlTypeInfo}; - -#[derive(Debug)] -pub(crate) struct Row { - pub(crate) column_types: Vec, - pub(crate) values: Vec>, -} - -impl Row { - pub(crate) fn get( - buf: &mut Bytes, - nullable: bool, - columns: &[MssqlColumn], - ) -> Result { - let mut values = Vec::with_capacity(columns.len()); - let mut column_types = Vec::with_capacity(columns.len()); - - let nulls = if nullable { - buf.get_bytes((columns.len() + 7) / 8) - } else { - Bytes::from_static(b"") - }; - - for (i, column) in columns.iter().enumerate() { - column_types.push(column.type_info.clone()); - - if !(column.type_info.0.is_null() || (nullable && (nulls[i / 8] & (1 << (i % 8))) != 0)) - { - values.push(column.type_info.0.get_value(buf)); - } else { - values.push(None); - } - } - - Ok(Self { - values, - column_types, - }) - } -} diff --git a/sqlx-core/src/mssql/protocol/rpc.rs b/sqlx-core/src/mssql/protocol/rpc.rs deleted file mode 100644 index d4f8e7200b..0000000000 --- a/sqlx-core/src/mssql/protocol/rpc.rs +++ /dev/null @@ -1,93 +0,0 @@ -use bitflags::bitflags; -use either::Either; - -use crate::io::Encode; -use crate::mssql::io::MssqlBufMutExt; -use crate::mssql::protocol::header::{AllHeaders, Header}; -use crate::mssql::MssqlArguments; - -pub(crate) struct RpcRequest<'a> { - pub(crate) transaction_descriptor: u64, - - // the procedure can be encoded as a u16 of a built-in or the name for a custom one - pub(crate) procedure: Either<&'a str, Procedure>, - pub(crate) options: OptionFlags, - pub(crate) arguments: &'a MssqlArguments, -} - -#[derive(Debug, Copy, Clone)] -#[repr(u16)] -#[allow(dead_code)] -pub(crate) enum Procedure { - Cursor = 1, - CursorOpen = 2, - CursorPrepare = 3, - CursorExecute = 4, - CursorPrepareExecute = 5, - CursorUnprepare = 6, - CursorFetch = 7, - CursorOption = 8, - CursorClose = 9, - ExecuteSql = 10, - Prepare = 11, - Execute = 12, - PrepareExecute = 13, - PrepareExecuteRpc = 14, - Unprepare = 15, -} - -bitflags! { - pub(crate) struct OptionFlags: u16 { - const WITH_RECOMPILE = 1; - - // The server sends NoMetaData only if fNoMetadata is set to 1 in the request - const NO_META_DATA = 2; - - // 1 if the metadata has not changed from the previous call and the server SHOULD reuse - // its cached metadata (the metadata MUST still be sent). - const REUSE_META_DATA = 4; - } -} - -bitflags! { - pub(crate) struct StatusFlags: u8 { - // if the parameter is passed by reference (OUTPUT parameter) or - // 0 if parameter is passed by value - const BY_REF_VALUE = 1; - - // 1 if the parameter being passed is to be the default value - const DEFAULT_VALUE = 2; - - // 1 if the parameter that is being passed is encrypted. This flag is valid - // only when the column encryption feature is negotiated by client and server - // and is turned on - const ENCRYPTED = 8; - } -} - -impl Encode<'_> for RpcRequest<'_> { - fn encode_with(&self, buf: &mut Vec, _: ()) { - AllHeaders(&[Header::TransactionDescriptor { - outstanding_request_count: 1, - transaction_descriptor: self.transaction_descriptor, - }]) - .encode(buf); - - match &self.procedure { - Either::Left(name) => { - buf.extend(&(name.len() as u16).to_le_bytes()); - buf.put_utf16_str(name); - } - - Either::Right(id) => { - buf.extend(&(0xffff_u16).to_le_bytes()); - buf.extend(&(*id as u16).to_le_bytes()); - } - } - - buf.extend(&self.options.bits.to_le_bytes()); - buf.extend(&self.arguments.data); - } -} - -// TODO: Test serialization of this? diff --git a/sqlx-core/src/mssql/protocol/sql_batch.rs b/sqlx-core/src/mssql/protocol/sql_batch.rs deleted file mode 100644 index a1fe8a9b5f..0000000000 --- a/sqlx-core/src/mssql/protocol/sql_batch.rs +++ /dev/null @@ -1,22 +0,0 @@ -use crate::io::Encode; -use crate::mssql::io::MssqlBufMutExt; -use crate::mssql::protocol::header::{AllHeaders, Header}; - -#[derive(Debug)] -pub(crate) struct SqlBatch<'a> { - pub(crate) transaction_descriptor: u64, - pub(crate) sql: &'a str, -} - -impl Encode<'_> for SqlBatch<'_> { - fn encode_with(&self, buf: &mut Vec, _: ()) { - AllHeaders(&[Header::TransactionDescriptor { - outstanding_request_count: 1, - transaction_descriptor: self.transaction_descriptor, - }]) - .encode(buf); - - // SQLText - buf.put_utf16_str(self.sql); - } -} diff --git a/sqlx-core/src/mssql/protocol/type_info.rs b/sqlx-core/src/mssql/protocol/type_info.rs deleted file mode 100644 index d5136db285..0000000000 --- a/sqlx-core/src/mssql/protocol/type_info.rs +++ /dev/null @@ -1,673 +0,0 @@ -use bitflags::bitflags; -use bytes::{Buf, Bytes}; -use encoding_rs::Encoding; - -use crate::encode::{Encode, IsNull}; -use crate::error::Error; -use crate::mssql::Mssql; - -bitflags! { - #[cfg_attr(feature = "offline", derive(serde::Serialize, serde::Deserialize))] - pub(crate) struct CollationFlags: u8 { - const IGNORE_CASE = (1 << 0); - const IGNORE_ACCENT = (1 << 1); - const IGNORE_WIDTH = (1 << 2); - const IGNORE_KANA = (1 << 3); - const BINARY = (1 << 4); - const BINARY2 = (1 << 5); - } -} - -#[derive(Debug, PartialEq, Eq, Clone, Copy)] -#[cfg_attr(feature = "offline", derive(serde::Serialize, serde::Deserialize))] -pub(crate) struct Collation { - pub(crate) locale: u32, - pub(crate) flags: CollationFlags, - pub(crate) sort: u8, - pub(crate) version: u8, -} - -#[derive(Debug, PartialEq, Eq, Clone, Copy)] -#[cfg_attr(feature = "offline", derive(serde::Serialize, serde::Deserialize))] -#[repr(u8)] -pub(crate) enum DataType { - // fixed-length data types - // https://docs.microsoft.com/en-us/openspecs/sql_server_protocols/ms-sstds/d33ef17b-7e53-4380-ad11-2ba42c8dda8d - Null = 0x1f, - TinyInt = 0x30, - Bit = 0x32, - SmallInt = 0x34, - Int = 0x38, - SmallDateTime = 0x3a, - Real = 0x3b, - Money = 0x3c, - DateTime = 0x3d, - Float = 0x3e, - SmallMoney = 0x7a, - BigInt = 0x7f, - - // variable-length data types - // https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-tds/ce3183a6-9d89-47e8-a02f-de5a1a1303de - - // byte length - Guid = 0x24, - IntN = 0x26, - Decimal = 0x37, // legacy - Numeric = 0x3f, // legacy - BitN = 0x68, - DecimalN = 0x6a, - NumericN = 0x6c, - FloatN = 0x6d, - MoneyN = 0x6e, - DateTimeN = 0x6f, - DateN = 0x28, - TimeN = 0x29, - DateTime2N = 0x2a, - DateTimeOffsetN = 0x2b, - Char = 0x2f, // legacy - VarChar = 0x27, // legacy - Binary = 0x2d, // legacy - VarBinary = 0x25, // legacy - - // short length - BigVarBinary = 0xa5, - BigVarChar = 0xa7, - BigBinary = 0xad, - BigChar = 0xaf, - NVarChar = 0xe7, - NChar = 0xef, - Xml = 0xf1, - UserDefined = 0xf0, - - // long length - Text = 0x23, - Image = 0x22, - NText = 0x63, - Variant = 0x62, -} - -// http://msdn.microsoft.com/en-us/library/dd358284.aspx -#[derive(Debug, Clone, Eq, PartialEq)] -#[cfg_attr(feature = "offline", derive(serde::Serialize, serde::Deserialize))] -pub(crate) struct TypeInfo { - pub(crate) ty: DataType, - pub(crate) size: u32, - pub(crate) scale: u8, - pub(crate) precision: u8, - pub(crate) collation: Option, -} - -impl TypeInfo { - pub(crate) const fn new(ty: DataType, size: u32) -> Self { - Self { - ty, - size, - scale: 0, - precision: 0, - collation: None, - } - } - - pub(crate) fn encoding(&self) -> Result<&'static Encoding, Error> { - match self.ty { - DataType::NChar | DataType::NVarChar => Ok(encoding_rs::UTF_16LE), - - DataType::VarChar | DataType::Char | DataType::BigChar | DataType::BigVarChar => { - // unwrap: impossible to unwrap here, collation will be set - Ok(match self.collation.unwrap().locale { - // This is the Western encoding for Windows. It is an extension of ISO-8859-1, - // which is known as Latin 1. - 0x0409 => encoding_rs::WINDOWS_1252, - - locale => { - return Err(err_protocol!("unsupported locale 0x{:?}", locale)); - } - }) - } - - _ => { - // default to UTF-8 for anything - // else coming in here - Ok(encoding_rs::UTF_8) - } - } - } - - // reads a TYPE_INFO from the buffer - pub(crate) fn get(buf: &mut Bytes) -> Result { - let ty = DataType::get(buf)?; - - Ok(match ty { - DataType::Null => Self::new(ty, 0), - - DataType::TinyInt | DataType::Bit => Self::new(ty, 1), - - DataType::SmallInt => Self::new(ty, 2), - - DataType::Int | DataType::SmallDateTime | DataType::Real | DataType::SmallMoney => { - Self::new(ty, 4) - } - - DataType::BigInt | DataType::Money | DataType::DateTime | DataType::Float => { - Self::new(ty, 8) - } - - DataType::DateN => Self::new(ty, 3), - - DataType::TimeN | DataType::DateTime2N | DataType::DateTimeOffsetN => { - let scale = buf.get_u8(); - - let mut size = match scale { - 0 | 1 | 2 => 3, - 3 | 4 => 4, - 5 | 6 | 7 => 5, - - scale => { - return Err(err_protocol!("invalid scale {} for type {:?}", scale, ty)); - } - }; - - match ty { - DataType::DateTime2N => { - size += 3; - } - - DataType::DateTimeOffsetN => { - size += 5; - } - - _ => {} - } - - Self { - scale, - size, - ty, - precision: 0, - collation: None, - } - } - - DataType::Guid - | DataType::IntN - | DataType::BitN - | DataType::FloatN - | DataType::MoneyN - | DataType::DateTimeN - | DataType::Char - | DataType::VarChar - | DataType::Binary - | DataType::VarBinary => Self::new(ty, buf.get_u8() as u32), - - DataType::Decimal | DataType::Numeric | DataType::DecimalN | DataType::NumericN => { - let size = buf.get_u8() as u32; - let precision = buf.get_u8(); - let scale = buf.get_u8(); - - Self { - size, - precision, - scale, - ty, - collation: None, - } - } - - DataType::BigVarBinary | DataType::BigBinary => Self::new(ty, buf.get_u16_le() as u32), - - DataType::BigVarChar | DataType::BigChar | DataType::NVarChar | DataType::NChar => { - let size = buf.get_u16_le() as u32; - let collation = Collation::get(buf); - - Self { - ty, - size, - collation: Some(collation), - scale: 0, - precision: 0, - } - } - - _ => { - return Err(err_protocol!("unsupported data type {:?}", ty)); - } - }) - } - - // writes a TYPE_INFO to the buffer - pub(crate) fn put(&self, buf: &mut Vec) { - buf.push(self.ty as u8); - - match self.ty { - DataType::Null - | DataType::TinyInt - | DataType::Bit - | DataType::SmallInt - | DataType::Int - | DataType::SmallDateTime - | DataType::Real - | DataType::SmallMoney - | DataType::BigInt - | DataType::Money - | DataType::DateTime - | DataType::Float - | DataType::DateN => { - // nothing to do - } - - DataType::TimeN | DataType::DateTime2N | DataType::DateTimeOffsetN => { - buf.push(self.scale); - } - - DataType::Guid - | DataType::IntN - | DataType::BitN - | DataType::FloatN - | DataType::MoneyN - | DataType::DateTimeN - | DataType::Char - | DataType::VarChar - | DataType::Binary - | DataType::VarBinary => { - buf.push(self.size as u8); - } - - DataType::Decimal | DataType::Numeric | DataType::DecimalN | DataType::NumericN => { - buf.push(self.size as u8); - buf.push(self.precision); - buf.push(self.scale); - } - - DataType::BigVarBinary | DataType::BigBinary => { - buf.extend(&(self.size as u16).to_le_bytes()); - } - - DataType::BigVarChar | DataType::BigChar | DataType::NVarChar | DataType::NChar => { - buf.extend(&(self.size as u16).to_le_bytes()); - - if let Some(collation) = &self.collation { - collation.put(buf); - } else { - buf.extend(&0_u32.to_le_bytes()); - buf.push(0); - } - } - - _ => { - unimplemented!("unsupported data type {:?}", self.ty); - } - } - } - - pub(crate) fn is_null(&self) -> bool { - matches!(self.ty, DataType::Null) - } - - pub(crate) fn get_value(&self, buf: &mut Bytes) -> Option { - match self.ty { - DataType::Null - | DataType::TinyInt - | DataType::Bit - | DataType::SmallInt - | DataType::Int - | DataType::SmallDateTime - | DataType::Real - | DataType::Money - | DataType::DateTime - | DataType::Float - | DataType::SmallMoney - | DataType::BigInt => Some(buf.split_to(self.size as usize)), - - DataType::Guid - | DataType::IntN - | DataType::Decimal - | DataType::Numeric - | DataType::BitN - | DataType::DecimalN - | DataType::NumericN - | DataType::FloatN - | DataType::MoneyN - | DataType::DateTimeN - | DataType::DateN - | DataType::TimeN - | DataType::DateTime2N - | DataType::DateTimeOffsetN => { - let size = buf.get_u8(); - - if size == 0 || size == 0xFF { - None - } else { - Some(buf.split_to(size as usize)) - } - } - - DataType::Char | DataType::VarChar | DataType::Binary | DataType::VarBinary => { - let size = buf.get_u8(); - - if size == 0xFF { - None - } else { - Some(buf.split_to(size as usize)) - } - } - - DataType::BigVarBinary - | DataType::BigVarChar - | DataType::BigBinary - | DataType::BigChar - | DataType::NVarChar - | DataType::NChar - | DataType::Xml - | DataType::UserDefined => { - let size = buf.get_u16_le(); - - if size == 0xFF_FF { - None - } else { - Some(buf.split_to(size as usize)) - } - } - - DataType::Text | DataType::Image | DataType::NText | DataType::Variant => { - let size = buf.get_u32_le(); - - if size == 0xFFFF_FFFF { - None - } else { - Some(buf.split_to(size as usize)) - } - } - } - } - - pub(crate) fn put_value<'q, T: Encode<'q, Mssql>>(&self, buf: &mut Vec, value: T) { - match self.ty { - DataType::Null - | DataType::TinyInt - | DataType::Bit - | DataType::SmallInt - | DataType::Int - | DataType::SmallDateTime - | DataType::Real - | DataType::Money - | DataType::DateTime - | DataType::Float - | DataType::SmallMoney - | DataType::BigInt => { - self.put_fixed_value(buf, value); - } - - DataType::Guid - | DataType::IntN - | DataType::Decimal - | DataType::Numeric - | DataType::BitN - | DataType::DecimalN - | DataType::NumericN - | DataType::FloatN - | DataType::MoneyN - | DataType::DateTimeN - | DataType::DateN - | DataType::TimeN - | DataType::DateTime2N - | DataType::DateTimeOffsetN - | DataType::Char - | DataType::VarChar - | DataType::Binary - | DataType::VarBinary => { - self.put_byte_len_value(buf, value); - } - - DataType::BigVarBinary - | DataType::BigVarChar - | DataType::BigBinary - | DataType::BigChar - | DataType::NVarChar - | DataType::NChar - | DataType::Xml - | DataType::UserDefined => { - self.put_short_len_value(buf, value); - } - - DataType::Text | DataType::Image | DataType::NText | DataType::Variant => { - self.put_long_len_value(buf, value); - } - } - } - - pub(crate) fn put_fixed_value<'q, T: Encode<'q, Mssql>>(&self, buf: &mut Vec, value: T) { - let _ = value.encode(buf); - } - - pub(crate) fn put_byte_len_value<'q, T: Encode<'q, Mssql>>(&self, buf: &mut Vec, value: T) { - let offset = buf.len(); - buf.push(0); - - let size = if let IsNull::Yes = value.encode(buf) { - 0xFF - } else { - (buf.len() - offset - 1) as u8 - }; - - buf[offset] = size; - } - - pub(crate) fn put_short_len_value<'q, T: Encode<'q, Mssql>>( - &self, - buf: &mut Vec, - value: T, - ) { - let offset = buf.len(); - buf.extend(&0_u16.to_le_bytes()); - - let size = if let IsNull::Yes = value.encode(buf) { - 0xFFFF - } else { - (buf.len() - offset - 2) as u16 - }; - - buf[offset..(offset + 2)].copy_from_slice(&size.to_le_bytes()); - } - - pub(crate) fn put_long_len_value<'q, T: Encode<'q, Mssql>>(&self, buf: &mut Vec, value: T) { - let offset = buf.len(); - buf.extend(&0_u32.to_le_bytes()); - - let size = if let IsNull::Yes = value.encode(buf) { - 0xFFFF_FFFF - } else { - (buf.len() - offset - 4) as u32 - }; - - buf[offset..(offset + 4)].copy_from_slice(&size.to_le_bytes()); - } - - pub(crate) fn name(&self) -> &'static str { - match self.ty { - DataType::Null => "NULL", - DataType::TinyInt => "TINYINT", - DataType::SmallInt => "SMALLINT", - DataType::Int => "INT", - DataType::BigInt => "BIGINT", - DataType::Real => "REAL", - DataType::Float => "FLOAT", - - DataType::IntN => match self.size { - 1 => "TINYINT", - 2 => "SMALLINT", - 4 => "INT", - 8 => "BIGINT", - - n => unreachable!("invalid size {} for int", n), - }, - - DataType::FloatN => match self.size { - 4 => "REAL", - 8 => "FLOAT", - - n => unreachable!("invalid size {} for float", n), - }, - - DataType::VarChar => "VARCHAR", - DataType::NVarChar => "NVARCHAR", - DataType::BigVarChar => "BIGVARCHAR", - DataType::Char => "CHAR", - DataType::BigChar => "BIGCHAR", - DataType::NChar => "NCHAR", - - _ => unimplemented!("name: unsupported data type {:?}", self.ty), - } - } - - pub(crate) fn fmt(&self, s: &mut String) { - match self.ty { - DataType::Null => s.push_str("nvarchar(1)"), - DataType::TinyInt => s.push_str("tinyint"), - DataType::SmallInt => s.push_str("smallint"), - DataType::Int => s.push_str("int"), - DataType::BigInt => s.push_str("bigint"), - DataType::Real => s.push_str("real"), - DataType::Float => s.push_str("float"), - DataType::Bit => s.push_str("bit"), - - DataType::IntN => s.push_str(match self.size { - 1 => "tinyint", - 2 => "smallint", - 4 => "int", - 8 => "bigint", - - n => unreachable!("invalid size {} for int", n), - }), - - DataType::FloatN => s.push_str(match self.size { - 4 => "real", - 8 => "float", - - n => unreachable!("invalid size {} for float", n), - }), - - DataType::VarChar - | DataType::NVarChar - | DataType::BigVarChar - | DataType::Char - | DataType::BigChar - | DataType::NChar => { - // name - s.push_str(match self.ty { - DataType::VarChar => "varchar", - DataType::NVarChar => "nvarchar", - DataType::BigVarChar => "bigvarchar", - DataType::Char => "char", - DataType::BigChar => "bigchar", - DataType::NChar => "nchar", - - _ => unreachable!(), - }); - - // size - if self.size < 8000 && self.size > 0 { - s.push_str("("); - s.push_str(itoa::Buffer::new().format(self.size)); - s.push_str(")"); - } else { - s.push_str("(max)"); - } - } - - DataType::BitN => { - s.push_str("bit"); - } - - _ => unimplemented!("fmt: unsupported data type {:?}", self.ty), - } - } -} - -impl DataType { - pub(crate) fn get(buf: &mut Bytes) -> Result { - Ok(match buf.get_u8() { - 0x1f => DataType::Null, - 0x30 => DataType::TinyInt, - 0x32 => DataType::Bit, - 0x34 => DataType::SmallInt, - 0x38 => DataType::Int, - 0x3a => DataType::SmallDateTime, - 0x3b => DataType::Real, - 0x3c => DataType::Money, - 0x3d => DataType::DateTime, - 0x3e => DataType::Float, - 0x7a => DataType::SmallMoney, - 0x7f => DataType::BigInt, - 0x24 => DataType::Guid, - 0x26 => DataType::IntN, - 0x37 => DataType::Decimal, - 0x3f => DataType::Numeric, - 0x68 => DataType::BitN, - 0x6a => DataType::DecimalN, - 0x6c => DataType::NumericN, - 0x6d => DataType::FloatN, - 0x6e => DataType::MoneyN, - 0x6f => DataType::DateTimeN, - 0x28 => DataType::DateN, - 0x29 => DataType::TimeN, - 0x2a => DataType::DateTime2N, - 0x2b => DataType::DateTimeOffsetN, - 0x2f => DataType::Char, - 0x27 => DataType::VarChar, - 0x2d => DataType::Binary, - 0x25 => DataType::VarBinary, - 0xa5 => DataType::BigVarBinary, - 0xa7 => DataType::BigVarChar, - 0xad => DataType::BigBinary, - 0xaf => DataType::BigChar, - 0xe7 => DataType::NVarChar, - 0xef => DataType::NChar, - 0xf1 => DataType::Xml, - 0xf0 => DataType::UserDefined, - 0x23 => DataType::Text, - 0x22 => DataType::Image, - 0x63 => DataType::NText, - 0x62 => DataType::Variant, - - ty => { - return Err(err_protocol!("unknown data type 0x{:02x}", ty)); - } - }) - } -} - -impl Collation { - pub(crate) fn get(buf: &mut Bytes) -> Collation { - let locale_sort_version = buf.get_u32_le(); - let locale = locale_sort_version & 0xfffff; - let flags = CollationFlags::from_bits_truncate(((locale_sort_version >> 20) & 0xFF) as u8); - let version = (locale_sort_version >> 28) as u8; - let sort = buf.get_u8(); - - Collation { - locale, - flags, - sort, - version, - } - } - - pub(crate) fn put(&self, buf: &mut Vec) { - let locale_sort_version = - self.locale | ((self.flags.bits() as u32) << 20) | ((self.version as u32) << 28); - - buf.extend(&locale_sort_version.to_le_bytes()); - buf.push(self.sort); - } -} - -#[test] -fn test_get() { - #[rustfmt::skip] - let mut buf = Bytes::from_static(&[ - 0x26, 4, 4, 1, 0, 0, 0, 0xfe, 0, 0, 0xe0, 0, 0, 0, 0, 0, 0, 0, 0, 0 - ]); - - let type_info = TypeInfo::get(&mut buf).unwrap(); - assert_eq!(type_info, TypeInfo::new(DataType::IntN, 4)); -} diff --git a/sqlx-core/src/mssql/query_result.rs b/sqlx-core/src/mssql/query_result.rs deleted file mode 100644 index 2e31b4a86e..0000000000 --- a/sqlx-core/src/mssql/query_result.rs +++ /dev/null @@ -1,30 +0,0 @@ -use std::iter::{Extend, IntoIterator}; - -#[derive(Debug, Default)] -pub struct MssqlQueryResult { - pub(super) rows_affected: u64, -} - -impl MssqlQueryResult { - pub fn rows_affected(&self) -> u64 { - self.rows_affected - } -} - -impl Extend for MssqlQueryResult { - fn extend>(&mut self, iter: T) { - for elem in iter { - self.rows_affected += elem.rows_affected; - } - } -} - -#[cfg(feature = "any")] -impl From for crate::any::AnyQueryResult { - fn from(done: MssqlQueryResult) -> Self { - crate::any::AnyQueryResult { - rows_affected: done.rows_affected, - last_insert_id: None, - } - } -} diff --git a/sqlx-core/src/mssql/row.rs b/sqlx-core/src/mssql/row.rs deleted file mode 100644 index 08f3ec6395..0000000000 --- a/sqlx-core/src/mssql/row.rs +++ /dev/null @@ -1,57 +0,0 @@ -use crate::column::ColumnIndex; -use crate::error::Error; -use crate::ext::ustr::UStr; -use crate::mssql::protocol::row::Row as ProtocolRow; -use crate::mssql::{Mssql, MssqlColumn, MssqlValueRef}; -use crate::row::Row; -use crate::HashMap; -use std::sync::Arc; - -pub struct MssqlRow { - pub(crate) row: ProtocolRow, - pub(crate) columns: Arc>, - pub(crate) column_names: Arc>, -} - -impl crate::row::private_row::Sealed for MssqlRow {} - -impl Row for MssqlRow { - type Database = Mssql; - - fn columns(&self) -> &[MssqlColumn] { - &*self.columns - } - - fn try_get_raw(&self, index: I) -> Result, Error> - where - I: ColumnIndex, - { - let index = index.index(self)?; - let value = MssqlValueRef { - data: self.row.values[index].as_ref(), - type_info: self.row.column_types[index].clone(), - }; - - Ok(value) - } -} - -impl ColumnIndex for &'_ str { - fn index(&self, row: &MssqlRow) -> Result { - row.column_names - .get(*self) - .ok_or_else(|| Error::ColumnNotFound((*self).into())) - .map(|v| *v) - } -} - -#[cfg(feature = "any")] -impl From for crate::any::AnyRow { - #[inline] - fn from(row: MssqlRow) -> Self { - crate::any::AnyRow { - columns: row.columns.iter().map(|col| col.clone().into()).collect(), - kind: crate::any::row::AnyRowKind::Mssql(row), - } - } -} diff --git a/sqlx-core/src/mssql/statement.rs b/sqlx-core/src/mssql/statement.rs deleted file mode 100644 index 3bba4906bb..0000000000 --- a/sqlx-core/src/mssql/statement.rs +++ /dev/null @@ -1,75 +0,0 @@ -use crate::column::ColumnIndex; -use crate::error::Error; -use crate::ext::ustr::UStr; -use crate::mssql::{Mssql, MssqlArguments, MssqlColumn, MssqlTypeInfo}; -use crate::statement::Statement; -use crate::HashMap; -use either::Either; -use std::borrow::Cow; -use std::sync::Arc; - -#[derive(Debug, Clone)] -pub struct MssqlStatement<'q> { - pub(crate) sql: Cow<'q, str>, - pub(crate) metadata: Arc, -} - -#[derive(Debug, Default, Clone)] -pub(crate) struct MssqlStatementMetadata { - pub(crate) columns: Vec, - pub(crate) column_names: HashMap, -} - -impl<'q> Statement<'q> for MssqlStatement<'q> { - type Database = Mssql; - - fn to_owned(&self) -> MssqlStatement<'static> { - MssqlStatement::<'static> { - sql: Cow::Owned(self.sql.clone().into_owned()), - metadata: self.metadata.clone(), - } - } - - fn sql(&self) -> &str { - &self.sql - } - - fn parameters(&self) -> Option> { - None - } - - fn columns(&self) -> &[MssqlColumn] { - &self.metadata.columns - } - - impl_statement_query!(MssqlArguments); -} - -impl ColumnIndex> for &'_ str { - fn index(&self, statement: &MssqlStatement<'_>) -> Result { - statement - .metadata - .column_names - .get(*self) - .ok_or_else(|| Error::ColumnNotFound((*self).into())) - .map(|v| *v) - } -} - -#[cfg(feature = "any")] -impl<'q> From> for crate::any::AnyStatement<'q> { - #[inline] - fn from(statement: MssqlStatement<'q>) -> Self { - crate::any::AnyStatement::<'q> { - columns: statement - .metadata - .columns - .iter() - .map(|col| col.clone().into()) - .collect(), - column_names: std::sync::Arc::new(statement.metadata.column_names.clone()), - parameters: None, - sql: statement.sql, - } - } -} diff --git a/sqlx-core/src/mssql/transaction.rs b/sqlx-core/src/mssql/transaction.rs deleted file mode 100644 index e5402fe602..0000000000 --- a/sqlx-core/src/mssql/transaction.rs +++ /dev/null @@ -1,94 +0,0 @@ -use std::borrow::Cow; - -use futures_core::future::BoxFuture; - -use crate::error::Error; -use crate::executor::Executor; -use crate::mssql::protocol::packet::PacketType; -use crate::mssql::protocol::sql_batch::SqlBatch; -use crate::mssql::{Mssql, MssqlConnection}; -use crate::transaction::TransactionManager; - -/// Implementation of [`TransactionManager`] for MSSQL. -pub struct MssqlTransactionManager; - -impl TransactionManager for MssqlTransactionManager { - type Database = Mssql; - - fn begin(conn: &mut MssqlConnection) -> BoxFuture<'_, Result<(), Error>> { - Box::pin(async move { - let depth = conn.stream.transaction_depth; - - let query = if depth == 0 { - Cow::Borrowed("BEGIN TRAN ") - } else { - Cow::Owned(format!("SAVE TRAN _sqlx_savepoint_{}", depth)) - }; - - conn.execute(&*query).await?; - conn.stream.transaction_depth = depth + 1; - - Ok(()) - }) - } - - fn commit(conn: &mut MssqlConnection) -> BoxFuture<'_, Result<(), Error>> { - Box::pin(async move { - let depth = conn.stream.transaction_depth; - - if depth > 0 { - if depth == 1 { - // savepoints are not released in MSSQL - conn.execute("COMMIT TRAN").await?; - } - - conn.stream.transaction_depth = depth - 1; - } - - Ok(()) - }) - } - - fn rollback(conn: &mut MssqlConnection) -> BoxFuture<'_, Result<(), Error>> { - Box::pin(async move { - let depth = conn.stream.transaction_depth; - - if depth > 0 { - let query = if depth == 1 { - Cow::Borrowed("ROLLBACK TRAN") - } else { - Cow::Owned(format!("ROLLBACK TRAN _sqlx_savepoint_{}", depth - 1)) - }; - - conn.execute(&*query).await?; - conn.stream.transaction_depth = depth - 1; - } - - Ok(()) - }) - } - - fn start_rollback(conn: &mut MssqlConnection) { - let depth = conn.stream.transaction_depth; - - if depth > 0 { - let query = if depth == 1 { - Cow::Borrowed("ROLLBACK TRAN") - } else { - Cow::Owned(format!("ROLLBACK TRAN _sqlx_savepoint_{}", depth - 1)) - }; - - conn.stream.pending_done_count += 1; - - conn.stream.write_packet( - PacketType::SqlBatch, - SqlBatch { - transaction_descriptor: conn.stream.transaction_descriptor, - sql: &*query, - }, - ); - - conn.stream.transaction_depth = depth - 1; - } - } -} diff --git a/sqlx-core/src/mssql/type_info.rs b/sqlx-core/src/mssql/type_info.rs deleted file mode 100644 index 6721380f61..0000000000 --- a/sqlx-core/src/mssql/type_info.rs +++ /dev/null @@ -1,32 +0,0 @@ -use std::fmt::{self, Display, Formatter}; - -use crate::mssql::protocol::type_info::{DataType, TypeInfo as ProtocolTypeInfo}; -use crate::type_info::TypeInfo; - -#[derive(Debug, Clone, PartialEq, Eq)] -#[cfg_attr(feature = "offline", derive(serde::Serialize, serde::Deserialize))] -pub struct MssqlTypeInfo(pub(crate) ProtocolTypeInfo); - -impl TypeInfo for MssqlTypeInfo { - fn is_null(&self) -> bool { - matches!(self.0.ty, DataType::Null) - } - - fn name(&self) -> &str { - self.0.name() - } -} - -impl Display for MssqlTypeInfo { - fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { - f.pad(self.name()) - } -} - -#[cfg(feature = "any")] -impl From for crate::any::AnyTypeInfo { - #[inline] - fn from(ty: MssqlTypeInfo) -> Self { - crate::any::AnyTypeInfo(crate::any::type_info::AnyTypeInfoKind::Mssql(ty)) - } -} diff --git a/sqlx-core/src/mssql/types/bool.rs b/sqlx-core/src/mssql/types/bool.rs deleted file mode 100644 index 5e247fd294..0000000000 --- a/sqlx-core/src/mssql/types/bool.rs +++ /dev/null @@ -1,30 +0,0 @@ -use crate::decode::Decode; -use crate::encode::{Encode, IsNull}; -use crate::error::BoxDynError; -use crate::mssql::protocol::type_info::{DataType, TypeInfo}; -use crate::mssql::{Mssql, MssqlTypeInfo, MssqlValueRef}; -use crate::types::Type; - -impl Type for bool { - fn type_info() -> MssqlTypeInfo { - MssqlTypeInfo(TypeInfo::new(DataType::BitN, 1)) - } - - fn compatible(ty: &MssqlTypeInfo) -> bool { - matches!(ty.0.ty, DataType::Bit | DataType::BitN) - } -} - -impl Encode<'_, Mssql> for bool { - fn encode_by_ref(&self, buf: &mut Vec) -> IsNull { - buf.push(if *self { 1 } else { 0 }); - - IsNull::No - } -} - -impl Decode<'_, Mssql> for bool { - fn decode(value: MssqlValueRef<'_>) -> Result { - Ok(value.as_bytes()?[0] == 1) - } -} diff --git a/sqlx-core/src/mssql/types/float.rs b/sqlx-core/src/mssql/types/float.rs deleted file mode 100644 index c4e2c337b9..0000000000 --- a/sqlx-core/src/mssql/types/float.rs +++ /dev/null @@ -1,56 +0,0 @@ -use byteorder::{ByteOrder, LittleEndian}; - -use crate::decode::Decode; -use crate::encode::{Encode, IsNull}; -use crate::error::BoxDynError; -use crate::mssql::protocol::type_info::{DataType, TypeInfo}; -use crate::mssql::{Mssql, MssqlTypeInfo, MssqlValueRef}; -use crate::types::Type; - -impl Type for f32 { - fn type_info() -> MssqlTypeInfo { - MssqlTypeInfo(TypeInfo::new(DataType::FloatN, 4)) - } - - fn compatible(ty: &MssqlTypeInfo) -> bool { - matches!(ty.0.ty, DataType::Real | DataType::FloatN) && ty.0.size == 4 - } -} - -impl Encode<'_, Mssql> for f32 { - fn encode_by_ref(&self, buf: &mut Vec) -> IsNull { - buf.extend(&self.to_le_bytes()); - - IsNull::No - } -} - -impl Decode<'_, Mssql> for f32 { - fn decode(value: MssqlValueRef<'_>) -> Result { - Ok(LittleEndian::read_f32(value.as_bytes()?)) - } -} - -impl Type for f64 { - fn type_info() -> MssqlTypeInfo { - MssqlTypeInfo(TypeInfo::new(DataType::FloatN, 8)) - } - - fn compatible(ty: &MssqlTypeInfo) -> bool { - matches!(ty.0.ty, DataType::Float | DataType::FloatN) && ty.0.size == 8 - } -} - -impl Encode<'_, Mssql> for f64 { - fn encode_by_ref(&self, buf: &mut Vec) -> IsNull { - buf.extend(&self.to_le_bytes()); - - IsNull::No - } -} - -impl Decode<'_, Mssql> for f64 { - fn decode(value: MssqlValueRef<'_>) -> Result { - Ok(LittleEndian::read_f64(value.as_bytes()?)) - } -} diff --git a/sqlx-core/src/mssql/types/int.rs b/sqlx-core/src/mssql/types/int.rs deleted file mode 100644 index 1734e127e4..0000000000 --- a/sqlx-core/src/mssql/types/int.rs +++ /dev/null @@ -1,104 +0,0 @@ -use byteorder::{ByteOrder, LittleEndian}; - -use crate::decode::Decode; -use crate::encode::{Encode, IsNull}; -use crate::error::BoxDynError; -use crate::mssql::protocol::type_info::{DataType, TypeInfo}; -use crate::mssql::{Mssql, MssqlTypeInfo, MssqlValueRef}; -use crate::types::Type; - -impl Type for i8 { - fn type_info() -> MssqlTypeInfo { - MssqlTypeInfo(TypeInfo::new(DataType::IntN, 1)) - } - - fn compatible(ty: &MssqlTypeInfo) -> bool { - matches!(ty.0.ty, DataType::TinyInt | DataType::IntN) && ty.0.size == 1 - } -} - -impl Encode<'_, Mssql> for i8 { - fn encode_by_ref(&self, buf: &mut Vec) -> IsNull { - buf.extend(&self.to_le_bytes()); - - IsNull::No - } -} - -impl Decode<'_, Mssql> for i8 { - fn decode(value: MssqlValueRef<'_>) -> Result { - Ok(value.as_bytes()?[0] as i8) - } -} - -impl Type for i16 { - fn type_info() -> MssqlTypeInfo { - MssqlTypeInfo(TypeInfo::new(DataType::IntN, 2)) - } - - fn compatible(ty: &MssqlTypeInfo) -> bool { - matches!(ty.0.ty, DataType::SmallInt | DataType::IntN) && ty.0.size == 2 - } -} - -impl Encode<'_, Mssql> for i16 { - fn encode_by_ref(&self, buf: &mut Vec) -> IsNull { - buf.extend(&self.to_le_bytes()); - - IsNull::No - } -} - -impl Decode<'_, Mssql> for i16 { - fn decode(value: MssqlValueRef<'_>) -> Result { - Ok(LittleEndian::read_i16(value.as_bytes()?)) - } -} - -impl Type for i32 { - fn type_info() -> MssqlTypeInfo { - MssqlTypeInfo(TypeInfo::new(DataType::IntN, 4)) - } - - fn compatible(ty: &MssqlTypeInfo) -> bool { - matches!(ty.0.ty, DataType::Int | DataType::IntN) && ty.0.size == 4 - } -} - -impl Encode<'_, Mssql> for i32 { - fn encode_by_ref(&self, buf: &mut Vec) -> IsNull { - buf.extend(&self.to_le_bytes()); - - IsNull::No - } -} - -impl Decode<'_, Mssql> for i32 { - fn decode(value: MssqlValueRef<'_>) -> Result { - Ok(LittleEndian::read_i32(value.as_bytes()?)) - } -} - -impl Type for i64 { - fn type_info() -> MssqlTypeInfo { - MssqlTypeInfo(TypeInfo::new(DataType::IntN, 8)) - } - - fn compatible(ty: &MssqlTypeInfo) -> bool { - matches!(ty.0.ty, DataType::BigInt | DataType::IntN) && ty.0.size == 8 - } -} - -impl Encode<'_, Mssql> for i64 { - fn encode_by_ref(&self, buf: &mut Vec) -> IsNull { - buf.extend(&self.to_le_bytes()); - - IsNull::No - } -} - -impl Decode<'_, Mssql> for i64 { - fn decode(value: MssqlValueRef<'_>) -> Result { - Ok(LittleEndian::read_i64(value.as_bytes()?)) - } -} diff --git a/sqlx-core/src/mssql/types/mod.rs b/sqlx-core/src/mssql/types/mod.rs deleted file mode 100644 index 0d90e375b4..0000000000 --- a/sqlx-core/src/mssql/types/mod.rs +++ /dev/null @@ -1,40 +0,0 @@ -use crate::encode::{Encode, IsNull}; -use crate::mssql::protocol::type_info::{DataType, TypeInfo}; -use crate::mssql::{Mssql, MssqlTypeInfo}; - -mod bool; -mod float; -mod int; -mod str; -mod uint; - -impl<'q, T: 'q + Encode<'q, Mssql>> Encode<'q, Mssql> for Option { - fn encode(self, buf: &mut Vec) -> IsNull { - if let Some(v) = self { - v.encode(buf) - } else { - IsNull::Yes - } - } - - fn encode_by_ref(&self, buf: &mut Vec) -> IsNull { - if let Some(v) = self { - v.encode_by_ref(buf) - } else { - IsNull::Yes - } - } - - fn produces(&self) -> Option { - if let Some(v) = self { - v.produces() - } else { - // MSSQL requires a special NULL type ID - Some(MssqlTypeInfo(TypeInfo::new(DataType::Null, 0))) - } - } - - fn size_hint(&self) -> usize { - self.as_ref().map_or(0, Encode::size_hint) - } -} diff --git a/sqlx-core/src/mssql/types/str.rs b/sqlx-core/src/mssql/types/str.rs deleted file mode 100644 index 2f4ae9f99b..0000000000 --- a/sqlx-core/src/mssql/types/str.rs +++ /dev/null @@ -1,124 +0,0 @@ -use crate::decode::Decode; -use crate::encode::{Encode, IsNull}; -use crate::error::BoxDynError; -use crate::mssql::io::MssqlBufMutExt; -use crate::mssql::protocol::type_info::{Collation, CollationFlags, DataType, TypeInfo}; -use crate::mssql::{Mssql, MssqlTypeInfo, MssqlValueRef}; -use crate::types::Type; -use std::borrow::Cow; - -impl Type for str { - fn type_info() -> MssqlTypeInfo { - MssqlTypeInfo(TypeInfo::new(DataType::NVarChar, 0)) - } - - fn compatible(ty: &MssqlTypeInfo) -> bool { - matches!( - ty.0.ty, - DataType::NVarChar - | DataType::NChar - | DataType::BigVarChar - | DataType::VarChar - | DataType::BigChar - | DataType::Char - ) - } -} - -impl Type for String { - fn type_info() -> MssqlTypeInfo { - >::type_info() - } - - fn compatible(ty: &MssqlTypeInfo) -> bool { - >::compatible(ty) - } -} - -impl Encode<'_, Mssql> for &'_ str { - fn produces(&self) -> Option { - // an empty string needs to be encoded as `nvarchar(2)` - Some(MssqlTypeInfo(TypeInfo { - ty: DataType::NVarChar, - size: ((self.len() * 2) as u32).max(2), - scale: 0, - precision: 0, - collation: Some(Collation { - locale: 1033, - flags: CollationFlags::IGNORE_CASE - | CollationFlags::IGNORE_WIDTH - | CollationFlags::IGNORE_KANA, - sort: 52, - version: 0, - }), - })) - } - - fn encode_by_ref(&self, buf: &mut Vec) -> IsNull { - buf.put_utf16_str(self); - - IsNull::No - } -} - -impl Encode<'_, Mssql> for String { - fn produces(&self) -> Option { - <&str as Encode>::produces(&self.as_str()) - } - - fn encode_by_ref(&self, buf: &mut Vec) -> IsNull { - <&str as Encode>::encode_by_ref(&self.as_str(), buf) - } -} - -impl Decode<'_, Mssql> for String { - fn decode(value: MssqlValueRef<'_>) -> Result { - Ok(value - .type_info - .0 - .encoding()? - .decode_without_bom_handling(value.as_bytes()?) - .0 - .into_owned()) - } -} - -impl Type for Cow<'_, str> { - fn type_info() -> MssqlTypeInfo { - <&str as Type>::type_info() - } - - fn compatible(ty: &MssqlTypeInfo) -> bool { - <&str as Type>::compatible(ty) - } -} - -impl Encode<'_, Mssql> for Cow<'_, str> { - fn produces(&self) -> Option { - match self { - Cow::Borrowed(str) => <&str as Encode>::produces(str), - Cow::Owned(str) => <&str as Encode>::produces(&(str.as_ref())), - } - } - - fn encode_by_ref(&self, buf: &mut Vec) -> IsNull { - match self { - Cow::Borrowed(str) => <&str as Encode>::encode_by_ref(str, buf), - Cow::Owned(str) => <&str as Encode>::encode_by_ref(&(str.as_ref()), buf), - } - } -} - -impl<'r> Decode<'r, Mssql> for Cow<'r, str> { - fn decode(value: MssqlValueRef<'r>) -> Result { - Ok(Cow::Owned( - value - .type_info - .0 - .encoding()? - .decode_without_bom_handling(value.as_bytes()?) - .0 - .into_owned(), - )) - } -} diff --git a/sqlx-core/src/mssql/types/uint.rs b/sqlx-core/src/mssql/types/uint.rs deleted file mode 100644 index a36cae3a6c..0000000000 --- a/sqlx-core/src/mssql/types/uint.rs +++ /dev/null @@ -1,30 +0,0 @@ -use crate::decode::Decode; -use crate::encode::{Encode, IsNull}; -use crate::error::BoxDynError; -use crate::mssql::protocol::type_info::{DataType, TypeInfo}; -use crate::mssql::{Mssql, MssqlTypeInfo, MssqlValueRef}; -use crate::types::Type; - -impl Type for u8 { - fn type_info() -> MssqlTypeInfo { - MssqlTypeInfo(TypeInfo::new(DataType::IntN, 1)) - } - - fn compatible(ty: &MssqlTypeInfo) -> bool { - matches!(ty.0.ty, DataType::TinyInt | DataType::IntN) && ty.0.size == 1 - } -} - -impl Encode<'_, Mssql> for u8 { - fn encode_by_ref(&self, buf: &mut Vec) -> IsNull { - buf.extend(&self.to_le_bytes()); - - IsNull::No - } -} - -impl Decode<'_, Mssql> for u8 { - fn decode(value: MssqlValueRef<'_>) -> Result { - Ok(value.as_bytes()?[0] as u8) - } -} diff --git a/sqlx-core/src/mssql/value.rs b/sqlx-core/src/mssql/value.rs deleted file mode 100644 index 61b2a0f3a6..0000000000 --- a/sqlx-core/src/mssql/value.rs +++ /dev/null @@ -1,88 +0,0 @@ -use crate::error::{BoxDynError, UnexpectedNullError}; -use crate::mssql::{Mssql, MssqlTypeInfo}; -use crate::value::{Value, ValueRef}; -use bytes::Bytes; -use std::borrow::Cow; - -/// Implementation of [`ValueRef`] for MSSQL. -#[derive(Clone)] -pub struct MssqlValueRef<'r> { - pub(crate) type_info: MssqlTypeInfo, - pub(crate) data: Option<&'r Bytes>, -} - -impl<'r> MssqlValueRef<'r> { - pub(crate) fn as_bytes(&self) -> Result<&'r [u8], BoxDynError> { - match &self.data { - Some(v) => Ok(v), - None => Err(UnexpectedNullError.into()), - } - } -} - -impl ValueRef<'_> for MssqlValueRef<'_> { - type Database = Mssql; - - fn to_owned(&self) -> MssqlValue { - MssqlValue { - data: self.data.cloned(), - type_info: self.type_info.clone(), - } - } - - fn type_info(&self) -> Cow<'_, MssqlTypeInfo> { - Cow::Borrowed(&self.type_info) - } - - fn is_null(&self) -> bool { - self.data.is_none() || self.type_info.0.is_null() - } -} - -#[cfg(feature = "any")] -impl<'r> From> for crate::any::AnyValueRef<'r> { - #[inline] - fn from(value: MssqlValueRef<'r>) -> Self { - crate::any::AnyValueRef { - type_info: value.type_info.clone().into(), - kind: crate::any::value::AnyValueRefKind::Mssql(value), - } - } -} - -/// Implementation of [`Value`] for MSSQL. -#[derive(Clone)] -pub struct MssqlValue { - pub(crate) type_info: MssqlTypeInfo, - pub(crate) data: Option, -} - -impl Value for MssqlValue { - type Database = Mssql; - - fn as_ref(&self) -> MssqlValueRef<'_> { - MssqlValueRef { - data: self.data.as_ref(), - type_info: self.type_info.clone(), - } - } - - fn type_info(&self) -> Cow<'_, MssqlTypeInfo> { - Cow::Borrowed(&self.type_info) - } - - fn is_null(&self) -> bool { - self.data.is_none() || self.type_info.0.is_null() - } -} - -#[cfg(feature = "any")] -impl From for crate::any::AnyValue { - #[inline] - fn from(value: MssqlValue) -> Self { - crate::any::AnyValue { - type_info: value.type_info.clone().into(), - kind: crate::any::value::AnyValueKind::Mssql(value), - } - } -} diff --git a/sqlx-core/src/mysql/connection/tls.rs b/sqlx-core/src/mysql/connection/tls.rs deleted file mode 100644 index 468b638fa8..0000000000 --- a/sqlx-core/src/mysql/connection/tls.rs +++ /dev/null @@ -1,60 +0,0 @@ -use crate::error::Error; -use crate::mysql::connection::MySqlStream; -use crate::mysql::protocol::connect::SslRequest; -use crate::mysql::protocol::Capabilities; -use crate::mysql::{MySqlConnectOptions, MySqlSslMode}; - -pub(super) async fn maybe_upgrade( - stream: &mut MySqlStream, - options: &MySqlConnectOptions, -) -> Result<(), Error> { - // https://www.postgresql.org/docs/12/libpq-ssl.html#LIBPQ-SSL-SSLMODE-STATEMENTS - match options.ssl_mode { - MySqlSslMode::Disabled => {} - - MySqlSslMode::Preferred => { - // try upgrade, but its okay if we fail - upgrade(stream, options).await?; - } - - MySqlSslMode::Required | MySqlSslMode::VerifyIdentity | MySqlSslMode::VerifyCa => { - if !upgrade(stream, options).await? { - // upgrade failed, die - return Err(Error::Tls("server does not support TLS".into())); - } - } - } - - Ok(()) -} - -async fn upgrade(stream: &mut MySqlStream, options: &MySqlConnectOptions) -> Result { - if !stream.capabilities.contains(Capabilities::SSL) { - // server does not support TLS - return Ok(false); - } - - stream.write_packet(SslRequest { - max_packet_size: super::MAX_PACKET_SIZE, - collation: stream.collation as u8, - }); - - stream.flush().await?; - - let accept_invalid_certs = !matches!( - options.ssl_mode, - MySqlSslMode::VerifyCa | MySqlSslMode::VerifyIdentity - ); - let accept_invalid_host_names = !matches!(options.ssl_mode, MySqlSslMode::VerifyIdentity); - - stream - .upgrade( - &options.host, - accept_invalid_certs, - accept_invalid_host_names, - options.ssl_ca.as_ref(), - ) - .await?; - - Ok(true) -} diff --git a/sqlx-core/src/mysql/types/json.rs b/sqlx-core/src/mysql/types/json.rs deleted file mode 100644 index 86dce1ad8c..0000000000 --- a/sqlx-core/src/mysql/types/json.rs +++ /dev/null @@ -1,48 +0,0 @@ -use serde::{Deserialize, Serialize}; - -use crate::decode::Decode; -use crate::encode::{Encode, IsNull}; -use crate::error::BoxDynError; -use crate::mysql::protocol::text::ColumnType; -use crate::mysql::{MySql, MySqlTypeInfo, MySqlValueRef}; -use crate::types::{Json, Type}; - -impl Type for Json { - fn type_info() -> MySqlTypeInfo { - // MySql uses the `CHAR` type to pass JSON data from and to the client - // NOTE: This is forwards-compatible with MySQL v8+ as CHAR is a common transmission format - // and has nothing to do with the native storage ability of MySQL v8+ - MySqlTypeInfo::binary(ColumnType::String) - } - - fn compatible(ty: &MySqlTypeInfo) -> bool { - ty.r#type == ColumnType::Json - || <&str as Type>::compatible(ty) - || <&[u8] as Type>::compatible(ty) - } -} - -impl Encode<'_, MySql> for Json -where - T: Serialize, -{ - fn encode_by_ref(&self, buf: &mut Vec) -> IsNull { - let json_string_value = - serde_json::to_string(&self.0).expect("serde_json failed to convert to string"); - - <&str as Encode>::encode(json_string_value.as_str(), buf) - } -} - -impl<'r, T> Decode<'r, MySql> for Json -where - T: 'r + Deserialize<'r>, -{ - fn decode(value: MySqlValueRef<'r>) -> Result { - let string_value = <&str as Decode>::decode(value)?; - - serde_json::from_str(&string_value) - .map(Json) - .map_err(Into::into) - } -} diff --git a/sqlx-core/src/net/mod.rs b/sqlx-core/src/net/mod.rs index 429c5f6c44..3c75f32c92 100644 --- a/sqlx-core/src/net/mod.rs +++ b/sqlx-core/src/net/mod.rs @@ -1,17 +1,4 @@ mod socket; -mod tls; +pub mod tls; -pub use socket::Socket; -pub use tls::{CertificateInput, MaybeTlsStream}; - -#[cfg(feature = "_rt-async-std")] -type PollReadBuf<'a> = [u8]; - -#[cfg(feature = "_rt-tokio")] -type PollReadBuf<'a> = sqlx_rt::ReadBuf<'a>; - -#[cfg(feature = "_rt-async-std")] -type PollReadOut = usize; - -#[cfg(feature = "_rt-tokio")] -type PollReadOut = (); +pub use socket::{connect_tcp, connect_uds, BufferedSocket, Socket, SocketIntoBox, WithSocket}; diff --git a/sqlx-core/src/net/socket.rs b/sqlx-core/src/net/socket.rs deleted file mode 100644 index 622a1a22ce..0000000000 --- a/sqlx-core/src/net/socket.rs +++ /dev/null @@ -1,134 +0,0 @@ -#![allow(dead_code)] - -use std::io; -use std::net::SocketAddr; -use std::path::Path; -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 { - pub async fn connect_tcp(host: &str, port: u16) -> io::Result { - // Trim square brackets from host if it's an IPv6 address as the `url` crate doesn't do that. - TcpStream::connect((host.trim_matches(|c| c == '[' || c == ']'), port)) - .await - .map(Socket::Tcp) - } - - #[cfg(unix)] - pub async fn connect_uds(path: impl AsRef) -> io::Result { - sqlx_rt::UnixStream::connect(path.as_ref()) - .await - .map(Socket::Unix) - } - - pub fn local_addr(&self) -> Option { - match self { - Self::Tcp(tcp) => tcp.local_addr().ok(), - #[cfg(unix)] - Self::Unix(_) => None, - } - } - - #[cfg(not(unix))] - pub async fn connect_uds(_: impl AsRef) -> io::Result { - Err(io::Error::new( - io::ErrorKind::Other, - "Unix domain sockets are not supported outside Unix platforms.", - )) - } - - pub async fn shutdown(&mut self) -> io::Result<()> { - #[cfg(feature = "_rt-async-std")] - { - use std::net::Shutdown; - - match self { - Socket::Tcp(s) => s.shutdown(Shutdown::Both), - - #[cfg(unix)] - Socket::Unix(s) => s.shutdown(Shutdown::Both), - } - } - - #[cfg(feature = "_rt-tokio")] - { - use sqlx_rt::AsyncWriteExt; - - match self { - Socket::Tcp(s) => s.shutdown().await, - - #[cfg(unix)] - Socket::Unix(s) => s.shutdown().await, - } - } - } -} - -impl AsyncRead for Socket { - fn poll_read( - mut self: Pin<&mut Self>, - cx: &mut Context<'_>, - buf: &mut super::PollReadBuf<'_>, - ) -> 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), - } - } -} - -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(feature = "_rt-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 = "_rt-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), - } - } -} diff --git a/sqlx-core/src/net/socket/buffered.rs b/sqlx-core/src/net/socket/buffered.rs new file mode 100644 index 0000000000..dc05c87863 --- /dev/null +++ b/sqlx-core/src/net/socket/buffered.rs @@ -0,0 +1,234 @@ +use crate::net::Socket; +use bytes::BytesMut; +use std::io; + +use crate::error::Error; + +use crate::io::{Decode, Encode}; + +// Tokio, async-std, and std all use this as the default capacity for their buffered I/O. +const DEFAULT_BUF_SIZE: usize = 8192; + +pub struct BufferedSocket { + socket: S, + write_buf: WriteBuffer, + read_buf: ReadBuffer, +} + +pub struct WriteBuffer { + buf: Vec, + bytes_written: usize, + bytes_flushed: usize, +} + +pub struct ReadBuffer { + read: BytesMut, + available: BytesMut, +} + +impl BufferedSocket { + pub fn new(socket: S) -> Self + where + S: Sized, + { + BufferedSocket { + socket, + write_buf: WriteBuffer { + buf: Vec::with_capacity(DEFAULT_BUF_SIZE), + bytes_written: 0, + bytes_flushed: 0, + }, + read_buf: ReadBuffer { + read: BytesMut::new(), + available: BytesMut::with_capacity(DEFAULT_BUF_SIZE), + }, + } + } + + pub async fn read_buffered(&mut self, len: usize) -> io::Result { + while self.read_buf.read.len() < len { + self.read_buf.reserve(len); + + let read = self.socket.read(&mut self.read_buf.available).await?; + + if read == 0 { + return Err(io::Error::new( + io::ErrorKind::UnexpectedEof, + format!( + "expected to read {} bytes, got {} bytes at EOF", + len, + self.read_buf.read.len() + ), + )); + } + + self.read_buf.advance(read); + } + + Ok(self.read_buf.drain(len)) + } + + pub fn write_buffer(&self) -> &WriteBuffer { + &self.write_buf + } + + pub fn write_buffer_mut(&mut self) -> &mut WriteBuffer { + &mut self.write_buf + } + + pub async fn read<'de, T>(&mut self, byte_len: usize) -> Result + where + T: Decode<'de, ()>, + { + self.read_with(byte_len, ()).await + } + + pub async fn read_with<'de, T, C>(&mut self, byte_len: usize, context: C) -> Result + where + T: Decode<'de, C>, + { + T::decode_with(self.read_buffered(byte_len).await?.freeze(), context) + } + + pub fn write<'en, T>(&mut self, value: T) + where + T: Encode<'en, ()>, + { + self.write_with(value, ()) + } + + pub fn write_with<'en, T, C>(&mut self, value: T, context: C) + where + T: Encode<'en, C>, + { + value.encode_with(self.write_buf.buf_mut(), context); + self.write_buf.bytes_written = self.write_buf.buf.len(); + self.write_buf.sanity_check(); + } + + pub async fn flush(&mut self) -> io::Result<()> { + while !self.write_buf.is_empty() { + let written = self.socket.write(self.write_buf.get()).await?; + self.write_buf.consume(written); + self.write_buf.sanity_check(); + } + + self.socket.flush().await?; + + Ok(()) + } + + pub async fn shutdown(&mut self) -> io::Result<()> { + self.flush().await?; + self.socket.shutdown().await + } + + pub fn into_inner(self) -> S { + self.socket + } + + pub fn boxed(self) -> BufferedSocket> { + BufferedSocket { + socket: Box::new(self.socket), + write_buf: self.write_buf, + read_buf: self.read_buf, + } + } +} + +impl WriteBuffer { + fn sanity_check(&self) { + assert_ne!(self.buf.capacity(), 0); + assert!(self.bytes_written <= self.buf.len()); + assert!(self.bytes_flushed <= self.bytes_written); + } + + pub fn buf_mut(&mut self) -> &mut Vec { + self.buf.truncate(self.bytes_written); + self.sanity_check(); + &mut self.buf + } + + pub fn init_remaining_mut(&mut self) -> &mut [u8] { + self.buf.resize(self.buf.capacity(), 0); + self.sanity_check(); + &mut self.buf[self.bytes_written..] + } + + pub fn put_slice(&mut self, slice: &[u8]) { + // If we already have an initialized area that can fit the slice, + // don't change `self.buf.len()` + if let Some(dest) = self.buf[self.bytes_written..].get_mut(..slice.len()) { + dest.copy_from_slice(slice); + } else { + self.buf.truncate(self.bytes_written); + self.buf.extend_from_slice(slice); + } + + self.sanity_check(); + } + + pub fn advance(&mut self, amt: usize) { + let new_bytes_written = self + .bytes_written + .checked_add(amt) + .expect("self.bytes_written + amt overflowed"); + + assert!(new_bytes_written <= self.buf.len()); + + self.bytes_written = new_bytes_written; + + self.sanity_check(); + } + + pub fn is_empty(&self) -> bool { + self.bytes_flushed >= self.bytes_written + } + + pub fn is_full(&self) -> bool { + self.bytes_written == self.buf.len() + } + + pub fn get(&self) -> &[u8] { + &self.buf[self.bytes_flushed..self.bytes_written] + } + + pub fn get_mut(&mut self) -> &mut [u8] { + &mut self.buf[self.bytes_flushed..self.bytes_written] + } + + fn consume(&mut self, amt: usize) { + let new_bytes_flushed = self + .bytes_flushed + .checked_add(amt) + .expect("self.bytes_flushed + amt overflowed"); + + assert!(new_bytes_flushed <= self.bytes_written); + + self.bytes_flushed = new_bytes_flushed; + + if self.bytes_flushed == self.bytes_written { + // Reset cursors to zero if we've consumed the whole buffer + self.bytes_flushed = 0; + self.bytes_written = 0; + } + + self.sanity_check(); + } +} + +impl ReadBuffer { + fn reserve(&mut self, amt: usize) { + if let Some(additional) = amt.checked_sub(self.available.capacity()) { + self.available.reserve(additional); + } + } + + fn advance(&mut self, amt: usize) { + self.read.unsplit(self.available.split_to(amt)); + } + + fn drain(&mut self, amt: usize) -> BytesMut { + self.read.split_to(amt) + } +} diff --git a/sqlx-core/src/net/socket/mod.rs b/sqlx-core/src/net/socket/mod.rs new file mode 100644 index 0000000000..bcfa7f0616 --- /dev/null +++ b/sqlx-core/src/net/socket/mod.rs @@ -0,0 +1,260 @@ +use std::future::Future; +use std::io; +use std::path::Path; +use std::pin::Pin; +use std::task::{Context, Poll}; + +use bytes::BufMut; +use futures_core::ready; + +pub use buffered::{BufferedSocket, WriteBuffer}; + +use crate::io::ReadBuf; + +mod buffered; + +pub trait Socket: Send + Sync + Unpin + 'static { + fn try_read(&mut self, buf: &mut dyn ReadBuf) -> io::Result; + + fn try_write(&mut self, buf: &[u8]) -> io::Result; + + fn poll_read_ready(&mut self, cx: &mut Context<'_>) -> Poll>; + + fn poll_write_ready(&mut self, cx: &mut Context<'_>) -> Poll>; + + fn poll_flush(&mut self, _cx: &mut Context<'_>) -> Poll> { + // `flush()` is a no-op for TCP/UDS + Poll::Ready(Ok(())) + } + + fn poll_shutdown(&mut self, cx: &mut Context<'_>) -> Poll>; + + fn read<'a, B: ReadBuf>(&'a mut self, buf: &'a mut B) -> Read<'a, Self, B> + where + Self: Sized, + { + Read { socket: self, buf } + } + + fn write<'a>(&'a mut self, buf: &'a [u8]) -> Write<'a, Self> + where + Self: Sized, + { + Write { socket: self, buf } + } + + fn flush(&mut self) -> Flush<'_, Self> + where + Self: Sized, + { + Flush { socket: self } + } + + fn shutdown(&mut self) -> Shutdown<'_, Self> + where + Self: Sized, + { + Shutdown { socket: self } + } +} + +pub struct Read<'a, S: ?Sized, B> { + socket: &'a mut S, + buf: &'a mut B, +} + +impl<'a, S: ?Sized, B> Future for Read<'a, S, B> +where + S: Socket, + B: ReadBuf, +{ + type Output = io::Result; + + fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + let this = &mut *self; + + while this.buf.has_remaining_mut() { + match this.socket.try_read(&mut *this.buf) { + Err(e) if e.kind() == io::ErrorKind::WouldBlock => { + ready!(this.socket.poll_read_ready(cx))?; + } + ready => return Poll::Ready(ready), + } + } + + Poll::Ready(Ok(0)) + } +} + +pub struct Write<'a, S: ?Sized> { + socket: &'a mut S, + buf: &'a [u8], +} + +impl<'a, S: ?Sized> Future for Write<'a, S> +where + S: Socket, +{ + type Output = io::Result; + + fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + let this = &mut *self; + + while !this.buf.is_empty() { + match this.socket.try_write(&mut this.buf) { + Err(e) if e.kind() == io::ErrorKind::WouldBlock => { + ready!(this.socket.poll_write_ready(cx))?; + } + ready => return Poll::Ready(ready), + } + } + + Poll::Ready(Ok(0)) + } +} + +pub struct Flush<'a, S: ?Sized> { + socket: &'a mut S, +} + +impl<'a, S: Socket + ?Sized> Future for Flush<'a, S> { + type Output = io::Result<()>; + + fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + self.socket.poll_flush(cx) + } +} + +pub struct Shutdown<'a, S: ?Sized> { + socket: &'a mut S, +} + +impl<'a, S: ?Sized> Future for Shutdown<'a, S> +where + S: Socket, +{ + type Output = io::Result<()>; + + fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + self.socket.poll_shutdown(cx) + } +} + +pub trait WithSocket { + type Output; + + fn with_socket(self, socket: S) -> Self::Output; +} + +pub struct SocketIntoBox; + +impl WithSocket for SocketIntoBox { + type Output = Box; + + fn with_socket(self, socket: S) -> Self::Output { + Box::new(socket) + } +} + +impl Socket for Box { + fn try_read(&mut self, buf: &mut dyn ReadBuf) -> io::Result { + (**self).try_read(buf) + } + + fn try_write(&mut self, buf: &[u8]) -> io::Result { + (**self).try_write(buf) + } + + fn poll_read_ready(&mut self, cx: &mut Context<'_>) -> Poll> { + (**self).poll_read_ready(cx) + } + + fn poll_write_ready(&mut self, cx: &mut Context<'_>) -> Poll> { + (**self).poll_write_ready(cx) + } + + fn poll_shutdown(&mut self, cx: &mut Context<'_>) -> Poll> { + (**self).poll_shutdown(cx) + } +} + +pub async fn connect_tcp( + host: &str, + port: u16, + with_socket: Ws, +) -> crate::Result { + // IPv6 addresses in URLs will be wrapped in brackets and the `url` crate doesn't trim those. + let host = host.trim_matches(&['[', ']'][..]); + + #[cfg(feature = "_rt-tokio")] + if crate::rt::rt_tokio::available() { + use tokio::net::TcpStream; + + let stream = TcpStream::connect((host, port)).await?; + + return Ok(with_socket.with_socket(stream)); + } + + #[cfg(feature = "_rt-async-std")] + { + use async_io::Async; + use async_std::net::ToSocketAddrs; + use std::net::TcpStream; + + let socket_addr = (host, port) + .to_socket_addrs() + .await? + .next() + .expect("BUG: to_socket_addrs() should have returned at least one result"); + + let stream = Async::::connect(socket_addr).await?; + + return Ok(with_socket.with_socket(stream)); + } + + #[cfg(not(feature = "_rt-async-std"))] + { + crate::rt::missing_rt((host, port, with_socket)) + } +} + +/// Connect a Unix Domain Socket at the given path. +/// +/// Returns an error if Unix Domain Sockets are not supported on this platform. +pub async fn connect_uds, Ws: WithSocket>( + path: P, + with_socket: Ws, +) -> crate::Result { + #[cfg(not(unix))] + { + return Err(io::Error::new( + io::ErrorKind::Unsupported, + "Unix domain sockets are not supported on this platform", + ) + .into()); + } + + #[cfg(all(unix, feature = "_rt-tokio"))] + if crate::rt::rt_tokio::available() { + use tokio::net::UnixStream; + + let stream = UnixStream::connect(path).await?; + + return Ok(with_socket.with_socket(stream)); + } + + #[cfg(all(unix, feature = "_rt-async-std"))] + { + use async_io::Async; + use std::os::unix::net::UnixStream; + + let stream = Async::::connect(path).await?; + + return Ok(with_socket.with_socket(stream)); + } + + #[cfg(not(feature = "_rt-async-std"))] + { + crate::rt::missing_rt((path, with_socket)) + } +} diff --git a/sqlx-core/src/net/tls/mod.rs b/sqlx-core/src/net/tls/mod.rs index 85e5dda7c1..3fae0ecaed 100644 --- a/sqlx-core/src/net/tls/mod.rs +++ b/sqlx-core/src/net/tls/mod.rs @@ -1,15 +1,18 @@ #![allow(dead_code)] -use std::io; -use std::ops::{Deref, DerefMut}; use std::path::PathBuf; -use std::pin::Pin; -use std::task::{Context, Poll}; - -use sqlx_rt::{AsyncRead, AsyncWrite, TlsStream}; use crate::error::Error; -use std::mem::replace; +use crate::net::socket::WithSocket; +use crate::net::Socket; + +#[cfg(feature = "_tls-rustls")] +mod tls_rustls; + +#[cfg(feature = "_tls-native-tls")] +mod tls_native_tls; + +mod util; /// X.509 Certificate input, either a file path or a PEM encoded inline certificate(s). #[derive(Clone, Debug)] @@ -36,7 +39,7 @@ impl From for CertificateInput { impl CertificateInput { async fn data(&self) -> Result, std::io::Error> { - use sqlx_rt::fs; + use crate::fs; match self { CertificateInput::Inline(v) => Ok(v.clone()), CertificateInput::File(path) => fs::read(path).await, @@ -53,210 +56,46 @@ impl std::fmt::Display for CertificateInput { } } -#[cfg(feature = "_tls-rustls")] -mod rustls; - -pub enum MaybeTlsStream -where - S: AsyncRead + AsyncWrite + Unpin, -{ - Raw(S), - Tls(TlsStream), - Upgrading, +pub struct TlsConfig<'a> { + pub accept_invalid_certs: bool, + pub accept_invalid_hostnames: bool, + pub hostname: &'a str, + pub root_cert_path: Option<&'a CertificateInput>, } -impl MaybeTlsStream +pub async fn handshake( + socket: S, + config: TlsConfig<'_>, + with_socket: Ws, +) -> crate::Result where - S: AsyncRead + AsyncWrite + Unpin, + S: Socket, + Ws: WithSocket, { - #[inline] - pub fn is_tls(&self) -> bool { - matches!(self, Self::Tls(_)) - } - - pub async fn upgrade( - &mut self, - host: &str, - accept_invalid_certs: bool, - accept_invalid_hostnames: bool, - root_cert_path: Option<&CertificateInput>, - ) -> Result<(), Error> { - let connector = configure_tls_connector( - accept_invalid_certs, - accept_invalid_hostnames, - root_cert_path, - ) - .await?; - - 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())); - } - }; - - #[cfg(feature = "_tls-rustls")] - let host = ::rustls::ServerName::try_from(host).map_err(|err| Error::Tls(err.into()))?; - - *self = MaybeTlsStream::Tls(connector.connect(host, stream).await?); - - Ok(()) - } -} - -#[cfg(feature = "_tls-native-tls")] -async fn configure_tls_connector( - accept_invalid_certs: bool, - accept_invalid_hostnames: bool, - root_cert_path: Option<&CertificateInput>, -) -> Result { - use sqlx_rt::native_tls::{Certificate, TlsConnector}; - - let mut builder = TlsConnector::builder(); - builder - .danger_accept_invalid_certs(accept_invalid_certs) - .danger_accept_invalid_hostnames(accept_invalid_hostnames); - - if !accept_invalid_certs { - if let Some(ca) = root_cert_path { - let data = ca.data().await?; - let cert = Certificate::from_pem(&data)?; - - builder.add_root_certificate(cert); - } - } + #[cfg(feature = "_tls-native-tls")] + return Ok(with_socket.with_socket(tls_native_tls::handshake(socket, config).await?)); - #[cfg(not(feature = "_rt-async-std"))] - let connector = builder.build()?.into(); + #[cfg(feature = "_tls-rustls")] + return Ok(with_socket.with_socket(tls_rustls::handshake(socket, config).await?)); - #[cfg(feature = "_rt-async-std")] - let connector = builder.into(); - - Ok(connector) -} - -#[cfg(feature = "_tls-rustls")] -use self::rustls::configure_tls_connector; - -impl AsyncRead for MaybeTlsStream -where - S: Unpin + AsyncWrite + AsyncRead, -{ - fn poll_read( - mut self: Pin<&mut Self>, - cx: &mut Context<'_>, - buf: &mut super::PollReadBuf<'_>, - ) -> 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(not(any(feature = "_tls-native-tls", feature = "_tls-rustls")))] + { + drop((socket, config, with_socket)); + panic!("one of the `runtime-*-native-tls` or `runtime-*-rustls` features must be enabled") } } -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(feature = "_rt-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 = "_rt-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())), - } - } +pub fn available() -> bool { + cfg!(any(feature = "_tls-native-tls", feature = "_tls-rustls")) } -impl Deref for MaybeTlsStream -where - S: Unpin + AsyncWrite + AsyncRead, -{ - type Target = S; - - fn deref(&self) -> &Self::Target { - match self { - MaybeTlsStream::Raw(s) => s, - - #[cfg(feature = "_tls-rustls")] - MaybeTlsStream::Tls(s) => s.get_ref().0, - - #[cfg(all(feature = "_rt-async-std", feature = "_tls-native-tls"))] - MaybeTlsStream::Tls(s) => s.get_ref(), - - #[cfg(all(not(feature = "_rt-async-std"), feature = "_tls-native-tls"))] - MaybeTlsStream::Tls(s) => s.get_ref().get_ref().get_ref(), - - MaybeTlsStream::Upgrading => { - panic!("{}", io::Error::from(io::ErrorKind::ConnectionAborted)) - } - } +pub fn error_if_unavailable() -> crate::Result<()> { + if !available() { + return Err(Error::tls( + "TLS upgrade required by connect options \ + but SQLx was built without TLS support enabled", + )); } -} -impl DerefMut for MaybeTlsStream -where - S: Unpin + AsyncWrite + AsyncRead, -{ - fn deref_mut(&mut self) -> &mut Self::Target { - match self { - MaybeTlsStream::Raw(s) => s, - - #[cfg(feature = "_tls-rustls")] - MaybeTlsStream::Tls(s) => s.get_mut().0, - - #[cfg(all(feature = "_rt-async-std", feature = "_tls-native-tls"))] - MaybeTlsStream::Tls(s) => s.get_mut(), - - #[cfg(all(not(feature = "_rt-async-std"), feature = "_tls-native-tls"))] - MaybeTlsStream::Tls(s) => s.get_mut().get_mut().get_mut(), - - MaybeTlsStream::Upgrading => { - panic!("{}", io::Error::from(io::ErrorKind::ConnectionAborted)) - } - } - } + Ok(()) } diff --git a/sqlx-core/src/net/tls/rustls.rs b/sqlx-core/src/net/tls/rustls.rs deleted file mode 100644 index 2ad958b0d2..0000000000 --- a/sqlx-core/src/net/tls/rustls.rs +++ /dev/null @@ -1,108 +0,0 @@ -use crate::net::CertificateInput; -use rustls::{ - client::{ServerCertVerified, ServerCertVerifier, WebPkiVerifier}, - ClientConfig, Error as TlsError, OwnedTrustAnchor, RootCertStore, ServerName, -}; -use std::io::Cursor; -use std::sync::Arc; -use std::time::SystemTime; - -use crate::error::Error; - -pub async fn configure_tls_connector( - accept_invalid_certs: bool, - accept_invalid_hostnames: bool, - root_cert_path: Option<&CertificateInput>, -) -> Result { - let config = ClientConfig::builder().with_safe_defaults(); - - let config = if accept_invalid_certs { - config - .with_custom_certificate_verifier(Arc::new(DummyTlsVerifier)) - .with_no_client_auth() - } else { - let mut cert_store = RootCertStore::empty(); - cert_store.add_server_trust_anchors(webpki_roots::TLS_SERVER_ROOTS.0.iter().map(|ta| { - OwnedTrustAnchor::from_subject_spki_name_constraints( - ta.subject, - ta.spki, - ta.name_constraints, - ) - })); - - if let Some(ca) = root_cert_path { - let data = ca.data().await?; - let mut cursor = Cursor::new(data); - - for cert in rustls_pemfile::certs(&mut cursor) - .map_err(|_| Error::Tls(format!("Invalid certificate {}", ca).into()))? - { - cert_store - .add(&rustls::Certificate(cert)) - .map_err(|err| Error::Tls(err.into()))?; - } - } - - if accept_invalid_hostnames { - let verifier = WebPkiVerifier::new(cert_store, None); - - config - .with_custom_certificate_verifier(Arc::new(NoHostnameTlsVerifier { verifier })) - .with_no_client_auth() - } else { - config - .with_root_certificates(cert_store) - .with_no_client_auth() - } - }; - - Ok(Arc::new(config).into()) -} - -struct DummyTlsVerifier; - -impl ServerCertVerifier for DummyTlsVerifier { - fn verify_server_cert( - &self, - _end_entity: &rustls::Certificate, - _intermediates: &[rustls::Certificate], - _server_name: &ServerName, - _scts: &mut dyn Iterator, - _ocsp_response: &[u8], - _now: SystemTime, - ) -> Result { - Ok(ServerCertVerified::assertion()) - } -} - -pub struct NoHostnameTlsVerifier { - verifier: WebPkiVerifier, -} - -impl ServerCertVerifier for NoHostnameTlsVerifier { - fn verify_server_cert( - &self, - end_entity: &rustls::Certificate, - intermediates: &[rustls::Certificate], - server_name: &ServerName, - scts: &mut dyn Iterator, - ocsp_response: &[u8], - now: SystemTime, - ) -> Result { - match self.verifier.verify_server_cert( - end_entity, - intermediates, - server_name, - scts, - ocsp_response, - now, - ) { - Err(TlsError::InvalidCertificateData(reason)) - if reason.contains("CertNotValidForName") => - { - Ok(ServerCertVerified::assertion()) - } - res => res, - } - } -} diff --git a/sqlx-core/src/net/tls/tls_native_tls.rs b/sqlx-core/src/net/tls/tls_native_tls.rs new file mode 100644 index 0000000000..4b0e3085d4 --- /dev/null +++ b/sqlx-core/src/net/tls/tls_native_tls.rs @@ -0,0 +1,75 @@ +use std::io::{self, Read, Write}; + +use crate::io::ReadBuf; +use crate::net::tls::util::StdSocket; +use crate::net::tls::TlsConfig; +use crate::net::Socket; +use crate::Error; + +use native_tls::HandshakeError; +use std::task::{Context, Poll}; + +pub struct NativeTlsSocket { + stream: native_tls::TlsStream>, +} + +impl Socket for NativeTlsSocket { + fn try_read(&mut self, buf: &mut dyn ReadBuf) -> io::Result { + self.stream.read(buf.init_mut()) + } + + fn try_write(&mut self, buf: &[u8]) -> io::Result { + self.stream.write(buf) + } + + fn poll_read_ready(&mut self, cx: &mut Context<'_>) -> Poll> { + self.stream.get_mut().poll_ready(cx) + } + + fn poll_write_ready(&mut self, cx: &mut Context<'_>) -> Poll> { + self.stream.get_mut().poll_ready(cx) + } + + fn poll_shutdown(&mut self, cx: &mut Context<'_>) -> Poll> { + match self.stream.shutdown() { + Err(e) if e.kind() == io::ErrorKind::WouldBlock => self.stream.get_mut().poll_ready(cx), + ready => Poll::Ready(ready), + } + } +} + +pub async fn handshake( + socket: S, + config: TlsConfig<'_>, +) -> crate::Result> { + let mut builder = native_tls::TlsConnector::builder(); + + builder + .danger_accept_invalid_certs(config.accept_invalid_certs) + .danger_accept_invalid_hostnames(config.accept_invalid_hostnames); + + if let Some(root_cert_path) = config.root_cert_path { + let data = root_cert_path.data().await?; + builder.add_root_certificate(native_tls::Certificate::from_pem(&data).map_err(Error::tls)?); + } + + let connector = builder.build().map_err(Error::tls)?; + + let mut mid_handshake = match connector.connect(config.hostname, StdSocket::new(socket)) { + Ok(tls_stream) => return Ok(NativeTlsSocket { stream: tls_stream }), + Err(HandshakeError::Failure(e)) => return Err(Error::tls(e)), + Err(HandshakeError::WouldBlock(mid_handshake)) => mid_handshake, + }; + + loop { + mid_handshake.get_mut().ready().await?; + + match mid_handshake.handshake() { + Ok(tls_stream) => return Ok(NativeTlsSocket { stream: tls_stream }), + Err(HandshakeError::Failure(e)) => return Err(Error::tls(e)), + Err(HandshakeError::WouldBlock(mid_handshake_)) => { + mid_handshake = mid_handshake_; + } + } + } +} diff --git a/sqlx-core/src/net/tls/tls_rustls.rs b/sqlx-core/src/net/tls/tls_rustls.rs new file mode 100644 index 0000000000..230e03527f --- /dev/null +++ b/sqlx-core/src/net/tls/tls_rustls.rs @@ -0,0 +1,184 @@ +use futures_util::future; +use std::io; +use std::io::{Cursor, Read, Write}; +use std::sync::Arc; +use std::task::{Context, Poll}; +use std::time::SystemTime; + +use rustls::{ + client::{ServerCertVerified, ServerCertVerifier, WebPkiVerifier}, + ClientConfig, ClientConnection, Error as TlsError, OwnedTrustAnchor, RootCertStore, ServerName, +}; + +use crate::error::Error; +use crate::io::ReadBuf; +use crate::net::tls::util::StdSocket; +use crate::net::tls::TlsConfig; +use crate::net::Socket; + +pub struct RustlsSocket { + inner: StdSocket, + state: ClientConnection, + close_notify_sent: bool, +} + +impl RustlsSocket { + fn poll_complete_io(&mut self, cx: &mut Context<'_>) -> Poll> { + loop { + match self.state.complete_io(&mut self.inner) { + Err(e) if e.kind() == io::ErrorKind::WouldBlock => { + futures_util::ready!(self.inner.poll_ready(cx))?; + } + ready => return Poll::Ready(ready.map(|_| ())), + } + } + } + + async fn complete_io(&mut self) -> io::Result<()> { + future::poll_fn(|cx| self.poll_complete_io(cx)).await + } +} + +impl Socket for RustlsSocket { + fn try_read(&mut self, buf: &mut dyn ReadBuf) -> io::Result { + self.state.reader().read(buf.init_mut()) + } + + fn try_write(&mut self, buf: &[u8]) -> io::Result { + match self.state.writer().write(buf) { + // Returns a zero-length write when the buffer is full. + Ok(0) => Err(io::ErrorKind::WouldBlock.into()), + other => return other, + } + } + + fn poll_read_ready(&mut self, cx: &mut Context<'_>) -> Poll> { + self.poll_complete_io(cx) + } + + fn poll_write_ready(&mut self, cx: &mut Context<'_>) -> Poll> { + self.poll_complete_io(cx) + } + + fn poll_flush(&mut self, cx: &mut Context<'_>) -> Poll> { + self.poll_complete_io(cx) + } + + fn poll_shutdown(&mut self, cx: &mut Context<'_>) -> Poll> { + if !self.close_notify_sent { + self.state.send_close_notify(); + self.close_notify_sent = true; + } + + futures_util::ready!(self.poll_complete_io(cx))?; + self.inner.socket.poll_shutdown(cx) + } +} + +pub async fn handshake(socket: S, tls_config: TlsConfig<'_>) -> Result, Error> +where + S: Socket, +{ + let config = ClientConfig::builder().with_safe_defaults(); + + let config = if tls_config.accept_invalid_certs { + config + .with_custom_certificate_verifier(Arc::new(DummyTlsVerifier)) + .with_no_client_auth() + } else { + let mut cert_store = RootCertStore::empty(); + cert_store.add_server_trust_anchors(webpki_roots::TLS_SERVER_ROOTS.0.iter().map(|ta| { + OwnedTrustAnchor::from_subject_spki_name_constraints( + ta.subject, + ta.spki, + ta.name_constraints, + ) + })); + + if let Some(ca) = tls_config.root_cert_path { + let data = ca.data().await?; + let mut cursor = Cursor::new(data); + + for cert in rustls_pemfile::certs(&mut cursor) + .map_err(|_| Error::Tls(format!("Invalid certificate {}", ca).into()))? + { + cert_store + .add(&rustls::Certificate(cert)) + .map_err(|err| Error::Tls(err.into()))?; + } + } + + if tls_config.accept_invalid_hostnames { + let verifier = WebPkiVerifier::new(cert_store, None); + + config + .with_custom_certificate_verifier(Arc::new(NoHostnameTlsVerifier { verifier })) + .with_no_client_auth() + } else { + config + .with_root_certificates(cert_store) + .with_no_client_auth() + } + }; + + let host = rustls::ServerName::try_from(tls_config.hostname).map_err(Error::tls)?; + + let mut socket = RustlsSocket { + inner: StdSocket::new(socket), + state: ClientConnection::new(Arc::new(config), host).map_err(Error::tls)?, + close_notify_sent: false, + }; + + // Performs the TLS handshake or bails + socket.complete_io().await?; + + Ok(socket) +} + +struct DummyTlsVerifier; + +impl ServerCertVerifier for DummyTlsVerifier { + fn verify_server_cert( + &self, + _end_entity: &rustls::Certificate, + _intermediates: &[rustls::Certificate], + _server_name: &ServerName, + _scts: &mut dyn Iterator, + _ocsp_response: &[u8], + _now: SystemTime, + ) -> Result { + Ok(ServerCertVerified::assertion()) + } +} + +pub struct NoHostnameTlsVerifier { + verifier: WebPkiVerifier, +} + +impl ServerCertVerifier for NoHostnameTlsVerifier { + fn verify_server_cert( + &self, + end_entity: &rustls::Certificate, + intermediates: &[rustls::Certificate], + server_name: &ServerName, + scts: &mut dyn Iterator, + ocsp_response: &[u8], + now: SystemTime, + ) -> Result { + match self.verifier.verify_server_cert( + end_entity, + intermediates, + server_name, + scts, + ocsp_response, + now, + ) { + Err(TlsError::InvalidCertificateData(reason)) + if reason.contains("CertNotValidForName") => + { + Ok(ServerCertVerified::assertion()) + } + res => res, + } + } +} diff --git a/sqlx-core/src/net/tls/util.rs b/sqlx-core/src/net/tls/util.rs new file mode 100644 index 0000000000..02a16ef5e1 --- /dev/null +++ b/sqlx-core/src/net/tls/util.rs @@ -0,0 +1,65 @@ +use crate::net::Socket; + +use std::io::{self, Read, Write}; +use std::task::{Context, Poll}; + +use futures_core::ready; +use futures_util::future; + +pub struct StdSocket { + pub socket: S, + wants_read: bool, + wants_write: bool, +} + +impl StdSocket { + pub fn new(socket: S) -> Self { + Self { + socket, + wants_read: false, + wants_write: false, + } + } + + pub fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll> { + if self.wants_write { + ready!(self.socket.poll_write_ready(cx))?; + self.wants_write = false; + } + + if self.wants_read { + ready!(self.socket.poll_read_ready(cx))?; + self.wants_read = false; + } + + Poll::Ready(Ok(())) + } + + pub async fn ready(&mut self) -> io::Result<()> { + future::poll_fn(|cx| self.poll_ready(cx)).await + } +} + +impl Read for StdSocket { + fn read(&mut self, mut buf: &mut [u8]) -> io::Result { + self.wants_read = true; + let read = self.socket.try_read(&mut buf)?; + self.wants_read = false; + + Ok(read) + } +} + +impl Write for StdSocket { + fn write(&mut self, buf: &[u8]) -> io::Result { + self.wants_write = true; + let written = self.socket.try_write(buf)?; + self.wants_write = false; + Ok(written) + } + + fn flush(&mut self) -> io::Result<()> { + // NOTE: TCP sockets and unix sockets are both no-ops for flushes + Ok(()) + } +} diff --git a/sqlx-core/src/pool/connection.rs b/sqlx-core/src/pool/connection.rs index 9c61547cbe..8f1eded132 100644 --- a/sqlx-core/src/pool/connection.rs +++ b/sqlx-core/src/pool/connection.rs @@ -3,7 +3,7 @@ use std::ops::{Deref, DerefMut}; use std::sync::Arc; use std::time::{Duration, Instant}; -use futures_intrusive::sync::SemaphoreReleaser; +use crate::sync::AsyncSemaphoreReleaser; use crate::connection::Connection; use crate::database::Database; @@ -105,7 +105,8 @@ impl PoolConnection { /// Test the connection to make sure it is still live before returning it to the pool. /// /// This effectively runs the drop handler eagerly instead of spawning a task to do it. - pub(crate) fn return_to_pool(&mut self) -> impl Future + Send + 'static { + #[doc(hidden)] + pub fn return_to_pool(&mut self) -> impl Future + Send + 'static { // float the connection in the pool before we move into the task // in case the returned `Future` isn't executed, like if it's spawned into a dying runtime // https://github.com/launchbadge/sqlx/issues/1396 @@ -129,18 +130,31 @@ impl PoolConnection { } } +impl<'c, DB: Database> crate::acquire::Acquire<'c> for &'c mut PoolConnection { + type Database = DB; + + type Connection = &'c mut ::Connection; + + #[inline] + fn acquire(self) -> futures_core::future::BoxFuture<'c, Result> { + Box::pin(futures_util::future::ok(&mut **self)) + } + + #[inline] + fn begin( + self, + ) -> futures_core::future::BoxFuture<'c, Result, Error>> + { + crate::transaction::Transaction::begin(&mut **self) + } +} + /// Returns the connection to the [`Pool`][crate::pool::Pool] it was checked-out from. impl Drop for PoolConnection { fn drop(&mut self) { // We still need to spawn a task to maintain `min_connections`. if self.live.is_some() || self.pool.options.min_connections > 0 { - #[cfg(not(feature = "_rt-async-std"))] - if let Ok(handle) = sqlx_rt::Handle::try_current() { - handle.spawn(self.return_to_pool()); - } - - #[cfg(feature = "_rt-async-std")] - sqlx_rt::spawn(self.return_to_pool()); + crate::rt::spawn(self.return_to_pool()); } } } @@ -288,7 +302,7 @@ impl Floating> { pub fn from_idle( idle: Idle, pool: Arc>, - permit: SemaphoreReleaser<'_>, + permit: AsyncSemaphoreReleaser<'_>, ) -> Self { Self { inner: idle, diff --git a/sqlx-core/src/pool/executor.rs b/sqlx-core/src/pool/executor.rs index 97c8905bab..c5c7acdd39 100644 --- a/sqlx-core/src/pool/executor.rs +++ b/sqlx-core/src/pool/executor.rs @@ -69,72 +69,74 @@ where } } -// NOTE: required due to lack of lazy normalization -#[allow(unused_macros)] -macro_rules! impl_executor_for_pool_connection { - ($DB:ident, $C:ident, $R:ident) => { - impl<'c> crate::executor::Executor<'c> for &'c mut crate::pool::PoolConnection<$DB> { - type Database = $DB; - - #[inline] - fn fetch_many<'e, 'q: 'e, E: 'q>( - self, - query: E, - ) -> futures_core::stream::BoxStream< - 'e, - Result< - either::Either<<$DB as crate::database::Database>::QueryResult, $R>, - crate::error::Error, - >, - > - where - 'c: 'e, - E: crate::executor::Execute<'q, $DB>, - { - (**self).fetch_many(query) - } - - #[inline] - fn fetch_optional<'e, 'q: 'e, E: 'q>( - self, - query: E, - ) -> futures_core::future::BoxFuture<'e, Result, crate::error::Error>> - where - 'c: 'e, - E: crate::executor::Execute<'q, $DB>, - { - (**self).fetch_optional(query) - } - - #[inline] - fn prepare_with<'e, 'q: 'e>( - self, - sql: &'q str, - parameters: &'e [<$DB as crate::database::Database>::TypeInfo], - ) -> futures_core::future::BoxFuture< - 'e, - Result<<$DB as crate::database::HasStatement<'q>>::Statement, crate::error::Error>, - > - where - 'c: 'e, - { - (**self).prepare_with(sql, parameters) - } - - #[doc(hidden)] - #[inline] - fn describe<'e, 'q: 'e>( - self, - sql: &'q str, - ) -> futures_core::future::BoxFuture< - 'e, - Result, crate::error::Error>, - > - where - 'c: 'e, - { - (**self).describe(sql) - } - } - }; -} +// Causes an overflow when evaluating `&mut DB::Connection: Executor`. +// +// +// impl<'c, DB: Database> crate::executor::Executor<'c> for &'c mut crate::pool::PoolConnection +// where +// &'c mut DB::Connection: Executor<'c, Database = DB>, +// { +// type Database = DB; +// +// +// +// #[inline] +// fn fetch_many<'e, 'q: 'e, E: 'q>( +// self, +// query: E, +// ) -> futures_core::stream::BoxStream< +// 'e, +// Result< +// either::Either<::QueryResult, DB::Row>, +// crate::error::Error, +// >, +// > +// where +// 'c: 'e, +// E: crate::executor::Execute<'q, DB>, +// { +// (**self).fetch_many(query) +// } +// +// #[inline] +// fn fetch_optional<'e, 'q: 'e, E: 'q>( +// self, +// query: E, +// ) -> futures_core::future::BoxFuture<'e, Result, crate::error::Error>> +// where +// 'c: 'e, +// E: crate::executor::Execute<'q, DB>, +// { +// (**self).fetch_optional(query) +// } +// +// #[inline] +// fn prepare_with<'e, 'q: 'e>( +// self, +// sql: &'q str, +// parameters: &'e [::TypeInfo], +// ) -> futures_core::future::BoxFuture< +// 'e, +// Result<>::Statement, crate::error::Error>, +// > +// where +// 'c: 'e, +// { +// (**self).prepare_with(sql, parameters) +// } +// +// #[doc(hidden)] +// #[inline] +// fn describe<'e, 'q: 'e>( +// self, +// sql: &'q str, +// ) -> futures_core::future::BoxFuture< +// 'e, +// Result, crate::error::Error>, +// > +// where +// 'c: 'e, +// { +// (**self).describe(sql) +// } +// } diff --git a/sqlx-core/src/pool/inner.rs b/sqlx-core/src/pool/inner.rs index 2a48ac6347..d2a4b7e800 100644 --- a/sqlx-core/src/pool/inner.rs +++ b/sqlx-core/src/pool/inner.rs @@ -6,7 +6,7 @@ use crate::error::Error; use crate::pool::{deadline_as_timeout, CloseEvent, Pool, PoolOptions}; use crossbeam_queue::ArrayQueue; -use futures_intrusive::sync::{Semaphore, SemaphoreReleaser}; +use crate::sync::{AsyncSemaphore, AsyncSemaphoreReleaser}; use std::cmp; use std::future::Future; @@ -22,7 +22,7 @@ use std::time::{Duration, Instant}; pub(crate) struct PoolInner { pub(super) connect_options: RwLock::Options>>, pub(super) idle_conns: ArrayQueue>, - pub(super) semaphore: Semaphore, + pub(super) semaphore: AsyncSemaphore, pub(super) size: AtomicU32, pub(super) num_idle: AtomicUsize, is_closed: AtomicBool, @@ -49,7 +49,7 @@ impl PoolInner { let pool = Self { connect_options: RwLock::new(Arc::new(connect_options)), idle_conns: ArrayQueue::new(capacity), - semaphore: Semaphore::new(options.fair, semaphore_capacity), + semaphore: AsyncSemaphore::new(options.fair, semaphore_capacity), size: AtomicU32::new(0), num_idle: AtomicUsize::new(0), is_closed: AtomicBool::new(false), @@ -86,7 +86,7 @@ impl PoolInner { self.on_closed.notify(usize::MAX); async move { - for permits in 1..=self.options.max_connections as usize { + for permits in 1..=self.options.max_connections { // Close any currently idle connections in the pool. while let Some(idle) = self.idle_conns.pop() { let _ = idle.live.float((*self).clone()).close().await; @@ -112,7 +112,7 @@ impl PoolInner { /// /// If we steal a permit from the parent but *don't* open a connection, /// it should be returned to the parent. - async fn acquire_permit<'a>(self: &'a Arc) -> Result, Error> { + async fn acquire_permit<'a>(self: &'a Arc) -> Result, Error> { let parent = self .parent() // If we're already at the max size, we shouldn't try to steal from the parent. @@ -182,8 +182,8 @@ impl PoolInner { fn pop_idle<'a>( self: &'a Arc, - permit: SemaphoreReleaser<'a>, - ) -> Result>, SemaphoreReleaser<'a>> { + permit: AsyncSemaphoreReleaser<'a>, + ) -> Result>, AsyncSemaphoreReleaser<'a>> { if let Some(idle) = self.idle_conns.pop() { self.num_idle.fetch_sub(1, Ordering::AcqRel); Ok(Floating::from_idle(idle, (*self).clone(), permit)) @@ -211,8 +211,8 @@ impl PoolInner { /// Try to atomically increment the pool size for a new connection. pub(super) fn try_increment_size<'a>( self: &'a Arc, - permit: SemaphoreReleaser<'a>, - ) -> Result, SemaphoreReleaser<'a>> { + permit: AsyncSemaphoreReleaser<'a>, + ) -> Result, AsyncSemaphoreReleaser<'a>> { match self .size .fetch_update(Ordering::AcqRel, Ordering::Acquire, |size| { @@ -233,7 +233,7 @@ impl PoolInner { let deadline = Instant::now() + self.options.acquire_timeout; - sqlx_rt::timeout( + crate::rt::timeout( self.options.acquire_timeout, async { loop { @@ -263,7 +263,7 @@ impl PoolInner { // If so, we're likely in the current-thread runtime if it's Tokio // and so we should yield to let any spawned release_to_pool() tasks // execute. - sqlx_rt::yield_now().await; + crate::rt::yield_now().await; continue; } }; @@ -302,7 +302,7 @@ impl PoolInner { // result here is `Result, TimeoutError>` // if this block does not return, sleep for the backoff timeout and try again - match sqlx_rt::timeout(timeout, connect_options.connect()).await { + match crate::rt::timeout(timeout, connect_options.connect()).await { // successfully established connection Ok(Ok(mut raw)) => { // See comment on `PoolOptions::after_connect` @@ -346,7 +346,7 @@ impl PoolInner { // If the connection is refused, wait in exponentially // increasing steps for the server to come up, // capped by a factor of the remaining time until the deadline - sqlx_rt::sleep(backoff).await; + crate::rt::sleep(backoff).await; backoff = cmp::min(backoff * 2, max_backoff); } } @@ -475,7 +475,7 @@ fn spawn_maintenance_tasks(pool: &Arc>) { (None, None) => { if pool.options.min_connections > 0 { - sqlx_rt::spawn(async move { + crate::rt::spawn(async move { pool.min_connections_maintenance(None).await; }); } @@ -484,7 +484,7 @@ fn spawn_maintenance_tasks(pool: &Arc>) { } }; - sqlx_rt::spawn(async move { + crate::rt::spawn(async move { // Immediately cancel this task if the pool is closed. let _ = pool .close_event() @@ -496,9 +496,9 @@ fn spawn_maintenance_tasks(pool: &Arc>) { if let Some(duration) = next_run.checked_duration_since(Instant::now()) { // `async-std` doesn't have a `sleep_until()` - sqlx_rt::sleep(duration).await; + crate::rt::sleep(duration).await; } else { - sqlx_rt::yield_now().await; + crate::rt::yield_now().await; } // Don't run the reaper right away. @@ -552,7 +552,7 @@ impl DecrementSizeGuard { } } - pub fn from_permit(pool: Arc>, mut permit: SemaphoreReleaser<'_>) -> Self { + pub fn from_permit(pool: Arc>, permit: AsyncSemaphoreReleaser<'_>) -> Self { // here we effectively take ownership of the permit permit.disarm(); Self::new_permit(pool) diff --git a/sqlx-core/src/pool/maybe.rs b/sqlx-core/src/pool/maybe.rs index 43c7f3457d..f9f16c41a5 100644 --- a/sqlx-core/src/pool/maybe.rs +++ b/sqlx-core/src/pool/maybe.rs @@ -2,7 +2,7 @@ use crate::database::Database; use crate::pool::PoolConnection; use std::ops::{Deref, DerefMut}; -pub(crate) enum MaybePoolConnection<'c, DB: Database> { +pub enum MaybePoolConnection<'c, DB: Database> { #[allow(dead_code)] Connection(&'c mut DB::Connection), PoolConnection(PoolConnection), @@ -30,21 +30,14 @@ impl<'c, DB: Database> DerefMut for MaybePoolConnection<'c, DB> { } } -#[allow(unused_macros)] -macro_rules! impl_into_maybe_pool { - ($DB:ident, $C:ident) => { - impl<'c> From> - for crate::pool::MaybePoolConnection<'c, $DB> - { - fn from(v: crate::pool::PoolConnection<$DB>) -> Self { - crate::pool::MaybePoolConnection::PoolConnection(v) - } - } +impl<'c, DB: Database> From> for MaybePoolConnection<'c, DB> { + fn from(v: PoolConnection) -> Self { + MaybePoolConnection::PoolConnection(v) + } +} - impl<'c> From<&'c mut $C> for crate::pool::MaybePoolConnection<'c, $DB> { - fn from(v: &'c mut $C) -> Self { - crate::pool::MaybePoolConnection::Connection(v) - } - } - }; +impl<'c, DB: Database> From<&'c mut DB::Connection> for MaybePoolConnection<'c, DB> { + fn from(v: &'c mut DB::Connection) -> Self { + MaybePoolConnection::Connection(v) + } } diff --git a/sqlx-core/src/pool/mod.rs b/sqlx-core/src/pool/mod.rs index 562bfdf36a..236116caca 100644 --- a/sqlx-core/src/pool/mod.rs +++ b/sqlx-core/src/pool/mod.rs @@ -84,16 +84,18 @@ use std::time::{Duration, Instant}; mod executor; #[macro_use] -mod maybe; +pub mod maybe; mod connection; mod inner; mod options; pub use self::connection::PoolConnection; -pub(crate) use self::maybe::MaybePoolConnection; pub use self::options::{PoolConnectionMetadata, PoolOptions}; +#[doc(hidden)] +pub use self::maybe::MaybePoolConnection; + /// An asynchronous pool of SQLx database connections. /// /// Create a pool with [Pool::connect] or [Pool::connect_with] and then call [Pool::acquire] diff --git a/sqlx-core/src/postgres/connection/tls.rs b/sqlx-core/src/postgres/connection/tls.rs deleted file mode 100644 index 0c780f401a..0000000000 --- a/sqlx-core/src/postgres/connection/tls.rs +++ /dev/null @@ -1,78 +0,0 @@ -use bytes::Bytes; - -use crate::error::Error; -use crate::postgres::connection::stream::PgStream; -use crate::postgres::message::SslRequest; -use crate::postgres::{PgConnectOptions, PgSslMode}; - -pub(super) async fn maybe_upgrade( - stream: &mut PgStream, - options: &PgConnectOptions, -) -> Result<(), Error> { - // https://www.postgresql.org/docs/12/libpq-ssl.html#LIBPQ-SSL-SSLMODE-STATEMENTS - match options.ssl_mode { - // FIXME: Implement ALLOW - PgSslMode::Allow | PgSslMode::Disable => {} - - PgSslMode::Prefer => { - // try upgrade, but its okay if we fail - upgrade(stream, options).await?; - } - - PgSslMode::Require | PgSslMode::VerifyFull | PgSslMode::VerifyCa => { - if !upgrade(stream, options).await? { - // upgrade failed, die - return Err(Error::Tls("server does not support TLS".into())); - } - } - } - - Ok(()) -} - -async fn upgrade(stream: &mut PgStream, options: &PgConnectOptions) -> Result { - // https://www.postgresql.org/docs/current/protocol-flow.html#id-1.10.5.7.11 - - // To initiate an SSL-encrypted connection, the frontend initially sends an - // SSLRequest message rather than a StartupMessage - - stream.send(SslRequest).await?; - - // The server then responds with a single byte containing S or N, indicating that - // it is willing or unwilling to perform SSL, respectively. - - match stream.read::(1).await?[0] { - b'S' => { - // The server is ready and willing to accept an SSL connection - } - - b'N' => { - // The server is _unwilling_ to perform SSL - return Ok(false); - } - - other => { - return Err(err_protocol!( - "unexpected response from SSLRequest: 0x{:02x}", - other - )); - } - } - - let accept_invalid_certs = !matches!( - options.ssl_mode, - PgSslMode::VerifyCa | PgSslMode::VerifyFull - ); - let accept_invalid_hostnames = !matches!(options.ssl_mode, PgSslMode::VerifyFull); - - stream - .upgrade( - &options.host, - accept_invalid_certs, - accept_invalid_hostnames, - options.ssl_root_cert.as_ref(), - ) - .await?; - - Ok(true) -} diff --git a/sqlx-core/src/query.rs b/sqlx-core/src/query.rs index b3e30dc52c..2d7a1c9b15 100644 --- a/sqlx-core/src/query.rs +++ b/sqlx-core/src/query.rs @@ -395,7 +395,7 @@ where } // Make a SQL query from a statement. -pub(crate) fn query_statement<'q, DB>( +pub fn query_statement<'q, DB>( statement: &'q >::Statement, ) -> Query<'q, DB, >::Arguments> where @@ -410,7 +410,7 @@ where } // Make a SQL query from a statement, with the given arguments. -pub(crate) fn query_statement_with<'q, DB, A>( +pub fn query_statement_with<'q, DB, A>( statement: &'q >::Statement, arguments: A, ) -> Query<'q, DB, A> diff --git a/sqlx-core/src/query_as.rs b/sqlx-core/src/query_as.rs index 7a15150cbc..43a611cf65 100644 --- a/sqlx-core/src/query_as.rs +++ b/sqlx-core/src/query_as.rs @@ -195,7 +195,7 @@ where } // Make a SQL query from a statement, that is mapped to a concrete type. -pub(crate) fn query_statement_as<'q, DB, O>( +pub fn query_statement_as<'q, DB, O>( statement: &'q >::Statement, ) -> QueryAs<'q, DB, O, >::Arguments> where @@ -209,7 +209,7 @@ where } // Make a SQL query from a statement, with the given arguments, that is mapped to a concrete type. -pub(crate) fn query_statement_as_with<'q, DB, O, A>( +pub fn query_statement_as_with<'q, DB, O, A>( statement: &'q >::Statement, arguments: A, ) -> QueryAs<'q, DB, O, A> diff --git a/sqlx-core/src/query_builder.rs b/sqlx-core/src/query_builder.rs index 82e2c3070f..10dd4d7e9f 100644 --- a/sqlx-core/src/query_builder.rs +++ b/sqlx-core/src/query_builder.rs @@ -541,7 +541,7 @@ where } } -#[cfg(test)] +#[cfg(all(test, feature = "postgres"))] mod test { use crate::postgres::Postgres; diff --git a/sqlx-core/src/query_scalar.rs b/sqlx-core/src/query_scalar.rs index 19a78287bc..197c527e56 100644 --- a/sqlx-core/src/query_scalar.rs +++ b/sqlx-core/src/query_scalar.rs @@ -188,7 +188,7 @@ where } // Make a SQL query from a statement, that is mapped to a concrete value. -pub(crate) fn query_statement_scalar<'q, DB, O>( +pub fn query_statement_scalar<'q, DB, O>( statement: &'q >::Statement, ) -> QueryScalar<'q, DB, O, >::Arguments> where @@ -201,7 +201,7 @@ where } // Make a SQL query from a statement, with the given arguments, that is mapped to a concrete value. -pub(crate) fn query_statement_scalar_with<'q, DB, O, A>( +pub fn query_statement_scalar_with<'q, DB, O, A>( statement: &'q >::Statement, arguments: A, ) -> QueryScalar<'q, DB, O, A> diff --git a/sqlx-core/src/row.rs b/sqlx-core/src/row.rs index 9376d13c0b..91181390de 100644 --- a/sqlx-core/src/row.rs +++ b/sqlx-core/src/row.rs @@ -2,6 +2,7 @@ use crate::column::ColumnIndex; use crate::database::{Database, HasValueRef}; use crate::decode::Decode; use crate::error::{mismatched_types, Error}; + use crate::type_info::TypeInfo; use crate::types::Type; use crate::value::ValueRef; @@ -12,7 +13,7 @@ use crate::value::ValueRef; /// /// [`FromRow`]: crate::row::FromRow /// [`Query::fetch`]: crate::query::Query::fetch -pub trait Row: private_row::Sealed + Unpin + Send + Sync + 'static { +pub trait Row: Unpin + Send + Sync + 'static { type Database: Database; /// Returns `true` if this row has no columns. @@ -179,8 +180,3 @@ pub trait Row: private_row::Sealed + Unpin + Send + Sync + 'static { where I: ColumnIndex; } - -// Prevent users from implementing the `Row` trait. -pub(crate) mod private_row { - pub trait Sealed {} -} diff --git a/sqlx-core/src/rt/mod.rs b/sqlx-core/src/rt/mod.rs new file mode 100644 index 0000000000..dd74652175 --- /dev/null +++ b/sqlx-core/src/rt/mod.rs @@ -0,0 +1,168 @@ +use std::future::Future; +use std::marker::PhantomData; +use std::pin::Pin; +use std::task::{Context, Poll}; +use std::time::Duration; + +#[cfg(feature = "_rt-async-std")] +pub mod rt_async_std; + +#[cfg(feature = "_rt-tokio")] +pub mod rt_tokio; + +#[derive(Debug, thiserror::Error)] +#[error("operation timed out")] +pub struct TimeoutError(()); + +pub enum JoinHandle { + #[cfg(feature = "_rt-async-std")] + AsyncStd(async_std::task::JoinHandle), + #[cfg(feature = "_rt-tokio")] + Tokio(tokio::task::JoinHandle), + // `PhantomData` requires `T: Unpin` + _Phantom(PhantomData T>), +} + +#[track_caller] +pub async fn timeout(duration: Duration, f: F) -> Result { + #[cfg(feature = "_rt-tokio")] + if rt_tokio::available() { + return tokio::time::timeout(duration, f) + .await + .map_err(|_| TimeoutError(())); + } + + #[cfg(feature = "_rt-async-std")] + { + return async_std::future::timeout(duration, f) + .await + .map_err(|_| TimeoutError(())); + } + + #[cfg(not(feature = "_rt-async-std"))] + missing_rt((duration, f)) +} + +#[track_caller] +pub async fn sleep(duration: Duration) { + #[cfg(feature = "_rt-tokio")] + if rt_tokio::available() { + return tokio::time::sleep(duration).await; + } + + #[cfg(feature = "_rt-async-std")] + { + return async_std::task::sleep(duration).await; + } + + #[cfg(not(feature = "_rt-async-std"))] + missing_rt(duration) +} + +#[track_caller] +pub fn spawn(fut: F) -> JoinHandle +where + F: Future + Send + 'static, + F::Output: Send + 'static, +{ + #[cfg(feature = "_rt-tokio")] + if let Ok(handle) = tokio::runtime::Handle::try_current() { + return JoinHandle::Tokio(handle.spawn(fut)); + } + + #[cfg(feature = "_rt-async-std")] + { + return JoinHandle::AsyncStd(async_std::task::spawn(fut)); + } + + #[cfg(not(feature = "_rt-async-std"))] + missing_rt(fut) +} + +#[track_caller] +pub fn spawn_blocking(f: F) -> JoinHandle +where + F: FnOnce() -> R + Send + 'static, + R: Send + 'static, +{ + #[cfg(feature = "_rt-tokio")] + if let Ok(handle) = tokio::runtime::Handle::try_current() { + return JoinHandle::Tokio(handle.spawn_blocking(f)); + } + + #[cfg(feature = "_rt-async-std")] + { + return JoinHandle::AsyncStd(async_std::task::spawn_blocking(f)); + } + + #[cfg(not(feature = "_rt-async-std"))] + missing_rt(f) +} + +#[track_caller] +pub async fn yield_now() { + #[cfg(feature = "_rt-tokio")] + if rt_tokio::available() { + return tokio::task::yield_now().await; + } + + #[cfg(feature = "_rt-async-std")] + { + return async_std::task::yield_now().await; + } + + #[cfg(not(feature = "_rt-async-std"))] + missing_rt(()) +} + +#[track_caller] +pub fn test_block_on(f: F) -> F::Output { + #[cfg(feature = "_rt-tokio")] + { + return tokio::runtime::Builder::new_current_thread() + .enable_all() + .build() + .expect("failed to start Tokio runtime") + .block_on(f); + } + + #[cfg(all(feature = "_rt-async-std", not(feature = "_rt-tokio")))] + { + return async_std::task::block_on(f); + } + + #[cfg(not(any(feature = "_rt-async-std", feature = "_rt-tokio")))] + { + drop(f); + panic!("at least one of the `runtime-*` features must be enabled") + } +} + +#[track_caller] +pub fn missing_rt(_unused: T) -> ! { + if cfg!(feature = "_rt-tokio") { + panic!("this functionality requires a Tokio context") + } + + panic!("either the `runtime-async-std` or `runtime-tokio` feature must be enabled") +} + +impl Future for JoinHandle { + type Output = T; + + #[track_caller] + fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + match &mut *self { + #[cfg(feature = "_rt-async-std")] + Self::AsyncStd(handle) => Pin::new(handle).poll(cx), + #[cfg(feature = "_rt-tokio")] + Self::Tokio(handle) => Pin::new(handle) + .poll(cx) + .map(|res| res.expect("spawned task panicked")), + Self::_Phantom(_) => { + let _ = cx; + unreachable!("runtime should have been checked on spawn") + } + } + } +} diff --git a/sqlx-core/src/rt/rt_async_std/mod.rs b/sqlx-core/src/rt/rt_async_std/mod.rs new file mode 100644 index 0000000000..b6d40b922b --- /dev/null +++ b/sqlx-core/src/rt/rt_async_std/mod.rs @@ -0,0 +1 @@ +mod socket; diff --git a/sqlx-core/src/rt/rt_async_std/socket.rs b/sqlx-core/src/rt/rt_async_std/socket.rs new file mode 100644 index 0000000000..2d66d70c76 --- /dev/null +++ b/sqlx-core/src/rt/rt_async_std/socket.rs @@ -0,0 +1,55 @@ +use crate::net::Socket; + +use std::io; +use std::io::{Read, Write}; +use std::net::{Shutdown, TcpStream}; + +use std::task::{Context, Poll}; + +use crate::io::ReadBuf; +use async_io::Async; + +impl Socket for Async { + fn try_read(&mut self, buf: &mut dyn ReadBuf) -> io::Result { + self.get_mut().read(buf.init_mut()) + } + + fn try_write(&mut self, buf: &[u8]) -> io::Result { + self.get_mut().write(buf) + } + + fn poll_read_ready(&mut self, cx: &mut Context<'_>) -> Poll> { + self.poll_readable(cx) + } + + fn poll_write_ready(&mut self, cx: &mut Context<'_>) -> Poll> { + self.poll_writable(cx) + } + + fn poll_shutdown(&mut self, _cx: &mut Context<'_>) -> Poll> { + Poll::Ready(self.get_mut().shutdown(Shutdown::Both)) + } +} + +#[cfg(unix)] +impl Socket for Async { + fn try_read(&mut self, buf: &mut dyn ReadBuf) -> io::Result { + self.get_mut().read(buf.init_mut()) + } + + fn try_write(&mut self, buf: &[u8]) -> io::Result { + self.get_mut().write(buf) + } + + fn poll_read_ready(&mut self, cx: &mut Context<'_>) -> Poll> { + self.poll_readable(cx) + } + + fn poll_write_ready(&mut self, cx: &mut Context<'_>) -> Poll> { + self.poll_writable(cx) + } + + fn poll_shutdown(&mut self, _cx: &mut Context<'_>) -> Poll> { + Poll::Ready(self.get_mut().shutdown(Shutdown::Both)) + } +} diff --git a/sqlx-core/src/rt/rt_tokio/mod.rs b/sqlx-core/src/rt/rt_tokio/mod.rs new file mode 100644 index 0000000000..ce699456db --- /dev/null +++ b/sqlx-core/src/rt/rt_tokio/mod.rs @@ -0,0 +1,5 @@ +mod socket; + +pub fn available() -> bool { + tokio::runtime::Handle::try_current().is_ok() +} diff --git a/sqlx-core/src/rt/rt_tokio/socket.rs b/sqlx-core/src/rt/rt_tokio/socket.rs new file mode 100644 index 0000000000..bb57cbfde9 --- /dev/null +++ b/sqlx-core/src/rt/rt_tokio/socket.rs @@ -0,0 +1,55 @@ +use std::io; +use std::pin::Pin; +use std::task::{Context, Poll}; + +use tokio::io::AsyncWrite; +use tokio::net::TcpStream; + +use crate::io::ReadBuf; +use crate::net::Socket; + +impl Socket for TcpStream { + fn try_read(&mut self, mut buf: &mut dyn ReadBuf) -> io::Result { + // Requires `&mut impl BufMut` + self.try_read_buf(&mut buf) + } + + fn try_write(&mut self, buf: &[u8]) -> io::Result { + (*self).try_write(buf) + } + + fn poll_read_ready(&mut self, cx: &mut Context<'_>) -> Poll> { + (*self).poll_read_ready(cx) + } + + fn poll_write_ready(&mut self, cx: &mut Context<'_>) -> Poll> { + (*self).poll_write_ready(cx) + } + + fn poll_shutdown(&mut self, cx: &mut Context<'_>) -> Poll> { + Pin::new(self).poll_shutdown(cx) + } +} + +#[cfg(unix)] +impl Socket for tokio::net::UnixStream { + fn try_read(&mut self, mut buf: &mut dyn ReadBuf) -> io::Result { + self.try_read_buf(&mut buf) + } + + fn try_write(&mut self, buf: &[u8]) -> io::Result { + (*self).try_write(buf) + } + + fn poll_read_ready(&mut self, cx: &mut Context<'_>) -> Poll> { + (*self).poll_read_ready(cx) + } + + fn poll_write_ready(&mut self, cx: &mut Context<'_>) -> Poll> { + (*self).poll_write_ready(cx) + } + + fn poll_shutdown(&mut self, cx: &mut Context<'_>) -> Poll> { + Pin::new(self).poll_shutdown(cx) + } +} diff --git a/sqlx-core/src/statement.rs b/sqlx-core/src/statement.rs index 1260fa46da..e857a9e8c5 100644 --- a/sqlx-core/src/statement.rs +++ b/sqlx-core/src/statement.rs @@ -88,85 +88,86 @@ pub trait Statement<'q>: Send + Sync { A: IntoArguments<'s, Self::Database>; } +#[macro_export] macro_rules! impl_statement_query { ($A:ty) => { #[inline] - fn query(&self) -> crate::query::Query<'_, Self::Database, $A> { - crate::query::query_statement(self) + fn query(&self) -> $crate::query::Query<'_, Self::Database, $A> { + $crate::query::query_statement(self) } #[inline] - fn query_with<'s, A>(&'s self, arguments: A) -> crate::query::Query<'s, Self::Database, A> + fn query_with<'s, A>(&'s self, arguments: A) -> $crate::query::Query<'s, Self::Database, A> where - A: crate::arguments::IntoArguments<'s, Self::Database>, + A: $crate::arguments::IntoArguments<'s, Self::Database>, { - crate::query::query_statement_with(self, arguments) + $crate::query::query_statement_with(self, arguments) } #[inline] fn query_as( &self, - ) -> crate::query_as::QueryAs< + ) -> $crate::query_as::QueryAs< '_, Self::Database, O, - >::Arguments, + >::Arguments, > where - O: for<'r> crate::from_row::FromRow< + O: for<'r> $crate::from_row::FromRow< 'r, - ::Row, + ::Row, >, { - crate::query_as::query_statement_as(self) + $crate::query_as::query_statement_as(self) } #[inline] fn query_as_with<'s, O, A>( &'s self, arguments: A, - ) -> crate::query_as::QueryAs<'s, Self::Database, O, A> + ) -> $crate::query_as::QueryAs<'s, Self::Database, O, A> where - O: for<'r> crate::from_row::FromRow< + O: for<'r> $crate::from_row::FromRow< 'r, - ::Row, + ::Row, >, - A: crate::arguments::IntoArguments<'s, Self::Database>, + A: $crate::arguments::IntoArguments<'s, Self::Database>, { - crate::query_as::query_statement_as_with(self, arguments) + $crate::query_as::query_statement_as_with(self, arguments) } #[inline] fn query_scalar( &self, - ) -> crate::query_scalar::QueryScalar< + ) -> $crate::query_scalar::QueryScalar< '_, Self::Database, O, - >::Arguments, + >::Arguments, > where - (O,): for<'r> crate::from_row::FromRow< + (O,): for<'r> $crate::from_row::FromRow< 'r, - ::Row, + ::Row, >, { - crate::query_scalar::query_statement_scalar(self) + $crate::query_scalar::query_statement_scalar(self) } #[inline] fn query_scalar_with<'s, O, A>( &'s self, arguments: A, - ) -> crate::query_scalar::QueryScalar<'s, Self::Database, O, A> + ) -> $crate::query_scalar::QueryScalar<'s, Self::Database, O, A> where - (O,): for<'r> crate::from_row::FromRow< + (O,): for<'r> $crate::from_row::FromRow< 'r, - ::Row, + ::Row, >, - A: crate::arguments::IntoArguments<'s, Self::Database>, + A: $crate::arguments::IntoArguments<'s, Self::Database>, { - crate::query_scalar::query_statement_scalar_with(self, arguments) + $crate::query_scalar::query_statement_scalar_with(self, arguments) } }; } diff --git a/sqlx-core/src/sync.rs b/sqlx-core/src/sync.rs new file mode 100644 index 0000000000..bc6aae7f96 --- /dev/null +++ b/sqlx-core/src/sync.rs @@ -0,0 +1,145 @@ +// For types with identical signatures that don't require runtime support, +// we can just arbitrarily pick one to use based on what's enabled. +// +// We'll generally lean towards Tokio's types as those are more featureful +// (including `tokio-console` support) and more widely deployed. + +#[cfg(all(feature = "_rt-async-std", not(feature = "_rt-tokio")))] +pub use async_std::sync::{Mutex as AsyncMutex, MutexGuard as AsyncMutexGuard}; + +#[cfg(feature = "_rt-tokio")] +pub use tokio::sync::{Mutex as AsyncMutex, MutexGuard as AsyncMutexGuard}; + +pub struct AsyncSemaphore { + // We use the semaphore from futures-intrusive as the one from async-std + // is missing the ability to add arbitrary permits, and is not guaranteed to be fair: + // * https://github.com/smol-rs/async-lock/issues/22 + // * https://github.com/smol-rs/async-lock/issues/23 + // + // We're on the look-out for a replacement, however, as futures-intrusive is not maintained + // and there are some soundness concerns (although it turns out any intrusive future is unsound + // in MIRI due to the necessitated mutable aliasing): + // https://github.com/launchbadge/sqlx/issues/1668 + #[cfg(all(feature = "_rt-async-std", not(feature = "_rt-tokio")))] + inner: futures_intrusive::sync::Semaphore, + + #[cfg(feature = "_rt-tokio")] + inner: tokio::sync::Semaphore, +} + +impl AsyncSemaphore { + #[track_caller] + pub fn new(fair: bool, permits: usize) -> Self { + if cfg!(not(any(feature = "_rt-async-std", feature = "_rt-tokio"))) { + crate::rt::missing_rt((fair, permits)); + } + + AsyncSemaphore { + #[cfg(all(feature = "_rt-async-std", not(feature = "_rt-tokio")))] + inner: futures_intrusive::sync::Semaphore::new(fair, permits), + #[cfg(feature = "_rt-tokio")] + inner: { + debug_assert!(fair, "Tokio only has fair permits"); + tokio::sync::Semaphore::new(permits) + }, + } + } + + pub fn permits(&self) -> usize { + #[cfg(all(feature = "_rt-async-std", not(feature = "_rt-tokio")))] + return self.inner.permits(); + + #[cfg(feature = "_rt-tokio")] + return self.inner.available_permits(); + + #[cfg(not(any(feature = "_rt-async-std", feature = "_rt-tokio")))] + crate::rt::missing_rt(()) + } + + pub async fn acquire(&self, permits: u32) -> AsyncSemaphoreReleaser<'_> { + #[cfg(all(feature = "_rt-async-std", not(feature = "_rt-tokio")))] + return AsyncSemaphoreReleaser { + inner: self.inner.acquire(permits as usize).await, + }; + + #[cfg(feature = "_rt-tokio")] + return AsyncSemaphoreReleaser { + inner: self + .inner + // Weird quirk: `tokio::sync::Semaphore` mostly uses `usize` for permit counts, + // but `u32` for this and `try_acquire_many()`. + .acquire_many(permits) + .await + .expect("BUG: we do not expose the `.close()` method"), + }; + + #[cfg(not(any(feature = "_rt-async-std", feature = "_rt-tokio")))] + crate::rt::missing_rt(permits) + } + + pub fn try_acquire(&self, permits: u32) -> Option> { + #[cfg(all(feature = "_rt-async-std", not(feature = "_rt-tokio")))] + return Some(AsyncSemaphoreReleaser { + inner: self.inner.try_acquire(permits as usize)?, + }); + + #[cfg(feature = "_rt-tokio")] + return Some(AsyncSemaphoreReleaser { + inner: self.inner.try_acquire_many(permits).ok()?, + }); + + #[cfg(not(any(feature = "_rt-async-std", feature = "_rt-tokio")))] + crate::rt::missing_rt(permits) + } + + pub fn release(&self, permits: usize) { + #[cfg(all(feature = "_rt-async-std", not(feature = "_rt-tokio")))] + return self.inner.release(permits); + + #[cfg(feature = "_rt-tokio")] + return self.inner.add_permits(permits); + + #[cfg(not(any(feature = "_rt-async-std", feature = "_rt-tokio")))] + crate::rt::missing_rt(permits) + } +} + +pub struct AsyncSemaphoreReleaser<'a> { + // We use the semaphore from futures-intrusive as the one from async-std + // is missing the ability to add arbitrary permits, and is not guaranteed to be fair: + // * https://github.com/smol-rs/async-lock/issues/22 + // * https://github.com/smol-rs/async-lock/issues/23 + // + // We're on the look-out for a replacement, however, as futures-intrusive is not maintained + // and there are some soundness concerns (although it turns out any intrusive future is unsound + // in MIRI due to the necessitated mutable aliasing): + // https://github.com/launchbadge/sqlx/issues/1668 + #[cfg(all(feature = "_rt-async-std", not(feature = "_rt-tokio")))] + inner: futures_intrusive::sync::SemaphoreReleaser<'a>, + + #[cfg(feature = "_rt-tokio")] + inner: tokio::sync::SemaphorePermit<'a>, + + #[cfg(not(any(feature = "_rt-async-std", feature = "_rt-tokio")))] + _phantom: std::marker::PhantomData<&'a ()>, +} + +impl AsyncSemaphoreReleaser<'_> { + pub fn disarm(self) { + #[cfg(all(feature = "_rt-async-std", not(feature = "_rt-tokio")))] + { + let mut this = self; + this.inner.disarm(); + return; + } + + #[cfg(feature = "_rt-tokio")] + { + self.inner.forget(); + return; + } + + #[cfg(not(any(feature = "_rt-async-std", feature = "_rt-tokio")))] + crate::rt::missing_rt(()) + } +} diff --git a/sqlx-core/src/testing/mod.rs b/sqlx-core/src/testing/mod.rs index 5183c914a9..1725f9eca1 100644 --- a/sqlx-core/src/testing/mod.rs +++ b/sqlx-core/src/testing/mod.rs @@ -4,7 +4,6 @@ use std::time::Duration; use futures_core::future::BoxFuture; pub use fixtures::FixtureSnapshot; -use sqlx_rt::test_block_on; use crate::connection::{ConnectOptions, Connection}; use crate::database::Database; @@ -135,7 +134,7 @@ where args.fixtures.is_empty(), "fixtures cannot be applied for a bare function" ); - test_block_on(self()) + crate::rt::test_block_on(self()) } } @@ -187,7 +186,7 @@ where let res = test_fn(pool.clone()).await; - let close_timed_out = sqlx_rt::timeout(Duration::from_secs(10), pool.close()) + let close_timed_out = crate::rt::timeout(Duration::from_secs(10), pool.close()) .await .is_err(); @@ -208,7 +207,7 @@ where Fut: Future, Fut::Output: TestTermination, { - test_block_on(async move { + crate::rt::test_block_on(async move { let test_context = DB::test_context(&args) .await .expect("failed to connect to setup test database"); diff --git a/sqlx-core/src/transaction.rs b/sqlx-core/src/transaction.rs index 76d5ac85ba..10498ca1ba 100644 --- a/sqlx-core/src/transaction.rs +++ b/sqlx-core/src/transaction.rs @@ -6,6 +6,7 @@ use futures_core::future::BoxFuture; use crate::database::Database; use crate::error::Error; +use crate::executor::Executor; use crate::pool::MaybePoolConnection; /// Generic management of database transactions. @@ -62,7 +63,8 @@ impl<'c, DB> Transaction<'c, DB> where DB: Database, { - pub(crate) fn begin( + #[doc(hidden)] + pub fn begin( conn: impl Into>, ) -> BoxFuture<'c, Result> { let mut conn = conn.into(); @@ -94,76 +96,75 @@ where } } -// NOTE: required due to lack of lazy normalization -#[allow(unused_macros)] -macro_rules! impl_executor_for_transaction { - ($DB:ident, $Row:ident) => { - impl<'c, 't> crate::executor::Executor<'t> - for &'t mut crate::transaction::Transaction<'c, $DB> - { - type Database = $DB; - - fn fetch_many<'e, 'q: 'e, E: 'q>( - self, - query: E, - ) -> futures_core::stream::BoxStream< - 'e, - Result< - either::Either<<$DB as crate::database::Database>::QueryResult, $Row>, - crate::error::Error, - >, - > - where - 't: 'e, - E: crate::executor::Execute<'q, Self::Database>, - { - (&mut **self).fetch_many(query) - } - - fn fetch_optional<'e, 'q: 'e, E: 'q>( - self, - query: E, - ) -> futures_core::future::BoxFuture<'e, Result, crate::error::Error>> - where - 't: 'e, - E: crate::executor::Execute<'q, Self::Database>, - { - (&mut **self).fetch_optional(query) - } - - fn prepare_with<'e, 'q: 'e>( - self, - sql: &'q str, - parameters: &'e [::TypeInfo], - ) -> futures_core::future::BoxFuture< - 'e, - Result< - >::Statement, - crate::error::Error, - >, - > - where - 't: 'e, - { - (&mut **self).prepare_with(sql, parameters) - } - - #[doc(hidden)] - fn describe<'e, 'q: 'e>( - self, - query: &'q str, - ) -> futures_core::future::BoxFuture< - 'e, - Result, crate::error::Error>, - > - where - 't: 'e, - { - (&mut **self).describe(query) - } - } - }; -} +// NOTE: fails to compile due to lack of lazy normalization +// impl<'c, 't, DB: Database> crate::executor::Executor<'t> +// for &'t mut crate::transaction::Transaction<'c, DB> +// where +// &'c mut DB::Connection: Executor<'c, Database = DB>, +// { +// type Database = DB; +// +// +// +// fn fetch_many<'e, 'q: 'e, E: 'q>( +// self, +// query: E, +// ) -> futures_core::stream::BoxStream< +// 'e, +// Result< +// crate::Either<::QueryResult, DB::Row>, +// crate::error::Error, +// >, +// > +// where +// 't: 'e, +// E: crate::executor::Execute<'q, Self::Database>, +// { +// (&mut **self).fetch_many(query) +// } +// +// fn fetch_optional<'e, 'q: 'e, E: 'q>( +// self, +// query: E, +// ) -> futures_core::future::BoxFuture<'e, Result, crate::error::Error>> +// where +// 't: 'e, +// E: crate::executor::Execute<'q, Self::Database>, +// { +// (&mut **self).fetch_optional(query) +// } +// +// fn prepare_with<'e, 'q: 'e>( +// self, +// sql: &'q str, +// parameters: &'e [::TypeInfo], +// ) -> futures_core::future::BoxFuture< +// 'e, +// Result< +// >::Statement, +// crate::error::Error, +// >, +// > +// where +// 't: 'e, +// { +// (&mut **self).prepare_with(sql, parameters) +// } +// +// #[doc(hidden)] +// fn describe<'e, 'q: 'e>( +// self, +// query: &'q str, +// ) -> futures_core::future::BoxFuture< +// 'e, +// Result, crate::error::Error>, +// > +// where +// 't: 'e, +// { +// (&mut **self).describe(query) +// } +// } impl<'c, DB> Debug for Transaction<'c, DB> where @@ -197,6 +198,22 @@ where } } +impl<'c, 't, DB: Database> crate::acquire::Acquire<'t> for &'t mut Transaction<'c, DB> { + type Database = DB; + + type Connection = &'t mut ::Connection; + + #[inline] + fn acquire(self) -> BoxFuture<'t, Result> { + Box::pin(futures_util::future::ok(&mut **self)) + } + + #[inline] + fn begin(self) -> BoxFuture<'t, Result, Error>> { + Transaction::begin(&mut **self) + } +} + impl<'c, DB> Drop for Transaction<'c, DB> where DB: Database, @@ -214,8 +231,7 @@ where } } -#[allow(dead_code)] -pub(crate) fn begin_ansi_transaction_sql(depth: usize) -> Cow<'static, str> { +pub fn begin_ansi_transaction_sql(depth: usize) -> Cow<'static, str> { if depth == 0 { Cow::Borrowed("BEGIN") } else { @@ -223,8 +239,7 @@ pub(crate) fn begin_ansi_transaction_sql(depth: usize) -> Cow<'static, str> { } } -#[allow(dead_code)] -pub(crate) fn commit_ansi_transaction_sql(depth: usize) -> Cow<'static, str> { +pub fn commit_ansi_transaction_sql(depth: usize) -> Cow<'static, str> { if depth == 1 { Cow::Borrowed("COMMIT") } else { @@ -232,8 +247,7 @@ pub(crate) fn commit_ansi_transaction_sql(depth: usize) -> Cow<'static, str> { } } -#[allow(dead_code)] -pub(crate) fn rollback_ansi_transaction_sql(depth: usize) -> Cow<'static, str> { +pub fn rollback_ansi_transaction_sql(depth: usize) -> Cow<'static, str> { if depth == 1 { Cow::Borrowed("ROLLBACK") } else { diff --git a/sqlx-core/src/types/git2.rs b/sqlx-core/src/types/git2.rs deleted file mode 100644 index ca40598243..0000000000 --- a/sqlx-core/src/types/git2.rs +++ /dev/null @@ -1,43 +0,0 @@ -/// Conversions between `git2::Oid` and SQL types. -use crate::database::{Database, HasArguments, HasValueRef}; -use crate::decode::Decode; -use crate::encode::{Encode, IsNull}; -use crate::error::BoxDynError; -use crate::types::Type; - -#[doc(no_inline)] -pub use git2::Oid; - -impl Type for Oid -where - DB: Database, - [u8]: Type, -{ - fn type_info() -> DB::TypeInfo { - <&[u8] as Type>::type_info() - } - - fn compatible(ty: &DB::TypeInfo) -> bool { - <&[u8] as Type>::compatible(ty) - } -} - -impl<'r, DB> Decode<'r, DB> for Oid -where - DB: Database, - &'r [u8]: Decode<'r, DB>, -{ - fn decode(value: >::ValueRef) -> Result { - <&[u8] as Decode>::decode(value).and_then(|bytes| Ok(Oid::from_bytes(bytes)?)) - } -} - -impl<'q, DB: Database> Encode<'q, DB> for Oid -where - DB: Database, - Vec: Encode<'q, DB>, -{ - fn encode_by_ref(&self, buf: &mut >::ArgumentBuffer) -> IsNull { - as Encode>::encode(self.as_bytes().to_vec(), buf) - } -} diff --git a/sqlx-core/src/types/json.rs b/sqlx-core/src/types/json.rs index 5ba87b7122..ad324f8a3d 100644 --- a/sqlx-core/src/types/json.rs +++ b/sqlx-core/src/types/json.rs @@ -1,5 +1,6 @@ use std::ops::{Deref, DerefMut}; +use serde::de::DeserializeOwned; use serde::{Deserialize, Serialize}; pub use serde_json::value::RawValue as JsonRawValue; pub use serde_json::Value as JsonValue; @@ -83,6 +84,39 @@ impl AsMut for Json { } } +const JSON_SERIALIZE_ERR: &str = "failed to encode value as JSON; the most likely cause is \ + attempting to serialize a map with a non-string key type"; + +// UNSTABLE: for driver use only! +#[doc(hidden)] +impl Json { + pub fn encode_to_string(&self) -> String { + // Encoding is supposed to be infallible so we don't have much choice but to panic here. + // However, I believe that's the right thing to do anyway as an object being unable + // to serialize to JSON is likely due to a bug or a malformed datastructure. + serde_json::to_string(self).expect(JSON_SERIALIZE_ERR) + } + + pub fn encode_to(&self, buf: &mut Vec) { + serde_json::to_writer(buf, self).expect(JSON_SERIALIZE_ERR) + } +} + +// UNSTABLE: for driver use only! +#[doc(hidden)] +impl<'a, T: 'a> Json +where + T: Deserialize<'a>, +{ + pub fn decode_from_string(s: &'a str) -> Result { + serde_json::from_str(s).map_err(Into::into) + } + + pub fn decode_from_bytes(bytes: &'a [u8]) -> Result { + serde_json::from_slice(bytes).map_err(Into::into) + } +} + impl Type for JsonValue where Json: Type, diff --git a/sqlx-core/src/types/mod.rs b/sqlx-core/src/types/mod.rs index 0610c67ad6..2c592e24b4 100644 --- a/sqlx-core/src/types/mod.rs +++ b/sqlx-core/src/types/mod.rs @@ -24,10 +24,6 @@ use crate::database::Database; #[cfg_attr(docsrs, doc(cfg(feature = "bstr")))] pub mod bstr; -#[cfg(feature = "git2")] -#[cfg_attr(docsrs, doc(cfg(feature = "git2")))] -pub mod git2; - #[cfg(feature = "json")] #[cfg_attr(docsrs, doc(cfg(feature = "json")))] mod json; @@ -63,8 +59,8 @@ pub mod time { #[doc(no_inline)] pub use bigdecimal::BigDecimal; -#[cfg(feature = "decimal")] -#[cfg_attr(docsrs, doc(cfg(feature = "decimal")))] +#[cfg(feature = "rust_decimal")] +#[cfg_attr(docsrs, doc(cfg(feature = "rust_decimal")))] #[doc(no_inline)] pub use rust_decimal::Decimal; diff --git a/sqlx-macros-core/Cargo.toml b/sqlx-macros-core/Cargo.toml new file mode 100644 index 0000000000..60bef71502 --- /dev/null +++ b/sqlx-macros-core/Cargo.toml @@ -0,0 +1,60 @@ +[package] +name = "sqlx-macros-core" +description = "Macro support core for SQLx, the Rust SQL toolkit. Not intended to be used directly." +version.workspace = true +license.workspace = true +edition.workspace = true +authors.workspace = true +repository.workspace = true + +[features] +# for conditional compilation +_rt-async-std = ["async-std", "sqlx-core/_rt-async-std"] +_rt-tokio = ["tokio", "sqlx-core/_rt-tokio"] + +_tls-native-tls = ["sqlx-core/_tls-native-tls"] +_tls-rustls = ["sqlx-core/_tls-rustls"] + +# SQLx features +migrate = ["sqlx-core/migrate"] + +# database +mysql = ["sqlx-mysql"] +postgres = ["sqlx-postgres"] +sqlite = ["sqlx-sqlite"] + +# type integrations +json = ["sqlx-core/json", "sqlx-mysql?/json", "sqlx-sqlite?/json"] + +bigdecimal = ["sqlx-core/bigdecimal", "sqlx-mysql?/bigdecimal", "sqlx-postgres?/bigdecimal"] +bit-vec = ["sqlx-core/bit-vec", "sqlx-postgres?/bit-vec"] +chrono = ["sqlx-core/chrono", "sqlx-mysql?/chrono", "sqlx-postgres?/chrono", "sqlx-sqlite?/chrono"] +ipnetwork = ["sqlx-core/ipnetwork", "sqlx-postgres?/ipnetwork"] +mac_address = ["sqlx-core/mac_address", "sqlx-postgres?/mac_address"] +rust_decimal = ["sqlx-core/rust_decimal", "sqlx-mysql?/rust_decimal", "sqlx-postgres?/rust_decimal"] +time = ["sqlx-core/time", "sqlx-mysql?/time", "sqlx-postgres?/time", "sqlx-sqlite?/time"] +uuid = ["sqlx-core/uuid", "sqlx-mysql?/uuid", "sqlx-postgres?/uuid", "sqlx-sqlite?/uuid"] + +[dependencies] +sqlx-core = { version = "0.6.2", default-features = false, features = ["offline"], path = "../sqlx-core" } +sqlx-mysql = { workspace = true, features = ["offline", "migrate"], optional = true } +sqlx-postgres = { workspace = true, features = ["offline", "migrate"], optional = true } +sqlx-sqlite = { workspace = true, features = ["offline", "migrate"], optional = true } + +async-std = { workspace = true, optional = true } +tokio = { workspace = true, optional = true } + +dotenvy = { workspace = true } + +hex = { version = "0.4.3" } +heck = { version = "0.4", features = ["unicode"] } +either = "1.6.1" +once_cell = "1.9.0" +proc-macro2 = { version = "1.0.36", default-features = false } +serde = { version = "1.0.132", features = ["derive"] } +serde_json = { version = "1.0.73" } +sha2 = { version = "0.10.0" } +syn = { version = "1.0.84", default-features = false, features = ["full", "derive", "parsing", "printing", "clone-impls"] } +quote = { version = "1.0.14", default-features = false } +url = { version = "2.2.2", default-features = false } + diff --git a/sqlx-macros/src/common.rs b/sqlx-macros-core/src/common.rs similarity index 100% rename from sqlx-macros/src/common.rs rename to sqlx-macros-core/src/common.rs diff --git a/sqlx-macros/src/database/mod.rs b/sqlx-macros-core/src/database/mod.rs similarity index 52% rename from sqlx-macros/src/database/mod.rs rename to sqlx-macros-core/src/database/mod.rs index cbc0c5dd61..0a4d3ed9cc 100644 --- a/sqlx-macros/src/database/mod.rs +++ b/sqlx-macros-core/src/database/mod.rs @@ -1,4 +1,13 @@ +use std::collections::hash_map; +use std::collections::HashMap; +use std::sync::Mutex; + +use once_cell::sync::Lazy; + +use sqlx_core::connection::Connection; use sqlx_core::database::Database; +use sqlx_core::describe::Describe; +use sqlx_core::executor::Executor; #[derive(PartialEq, Eq)] #[allow(dead_code)] @@ -10,7 +19,6 @@ pub enum ParamChecking { pub trait DatabaseExt: Database { const DATABASE_PATH: &'static str; const ROW_PATH: &'static str; - const NAME: &'static str; const PARAM_CHECKING: ParamChecking; @@ -27,6 +35,41 @@ pub trait DatabaseExt: Database { fn return_type_for_id(id: &Self::TypeInfo) -> Option<&'static str>; fn get_feature_gate(info: &Self::TypeInfo) -> Option<&'static str>; + + fn describe_blocking(query: &str, database_url: &str) -> sqlx_core::Result>; +} + +pub struct CachingDescribeBlocking { + connections: Lazy>>, +} + +impl CachingDescribeBlocking { + pub const fn new() -> Self { + CachingDescribeBlocking { + connections: Lazy::new(|| Mutex::new(HashMap::new())), + } + } + + pub fn describe(&self, query: &str, database_url: &str) -> sqlx_core::Result> + where + for<'a> &'a mut DB::Connection: Executor<'a, Database = DB>, + { + crate::block_on(async { + let mut cache = self + .connections + .lock() + .expect("previous panic in describe call"); + + let conn = match cache.entry(database_url.to_string()) { + hash_map::Entry::Occupied(hit) => hit.into_mut(), + hash_map::Entry::Vacant(mut miss) => { + miss.insert(DB::Connection::connect(&database_url).await?) + } + }; + + conn.describe(query).await + }) + } } macro_rules! impl_database_ext { @@ -36,14 +79,13 @@ macro_rules! impl_database_ext { }, ParamChecking::$param_checking:ident, feature-types: $ty_info:ident => $get_gate:expr, - row = $row:path, - name = $db_name:literal + row: $row:path, + $(describe-blocking: $describe:path,)? ) => { impl $crate::database::DatabaseExt for $database { const DATABASE_PATH: &'static str = stringify!($database); const ROW_PATH: &'static str = stringify!($row); const PARAM_CHECKING: $crate::database::ParamChecking = $crate::database::ParamChecking::$param_checking; - const NAME: &'static str = $db_name; fn param_type_for_id(info: &Self::TypeInfo) -> Option<&'static str> { match () { @@ -76,10 +118,36 @@ macro_rules! impl_database_ext { fn get_feature_gate($ty_info: &Self::TypeInfo) -> Option<&'static str> { $get_gate } + + impl_describe_blocking!($database, $($describe)?); } } } +macro_rules! impl_describe_blocking { + ($database:path $(,)?) => { + fn describe_blocking( + query: &str, + database_url: &str, + ) -> sqlx_core::Result> { + use $crate::database::CachingDescribeBlocking; + + // This can't be a provided method because the `static` can't reference `Self`. + static CACHE: CachingDescribeBlocking<$database> = CachingDescribeBlocking::new(); + + CACHE.describe(query, database_url) + } + }; + ($database:path, $describe:path) => { + fn describe_blocking( + query: &str, + database_url: &str, + ) -> sqlx_core::Result> { + $describe(query, database_url) + } + }; +} + macro_rules! input_ty { ($ty:ty, $input:ty) => { stringify!($input) @@ -98,5 +166,15 @@ mod mysql; #[cfg(feature = "sqlite")] mod sqlite; -#[cfg(feature = "mssql")] -mod mssql; +mod fake_sqlx { + pub use sqlx_core::*; + + #[cfg(feature = "mysql")] + pub use sqlx_mysql as mysql; + + #[cfg(feature = "postgres")] + pub use sqlx_postgres as postgres; + + #[cfg(feature = "sqlite")] + pub use sqlx_sqlite as sqlite; +} diff --git a/sqlx-macros/src/database/mysql.rs b/sqlx-macros-core/src/database/mysql.rs similarity index 92% rename from sqlx-macros/src/database/mysql.rs rename to sqlx-macros-core/src/database/mysql.rs index b5504c32df..3fbd0af495 100644 --- a/sqlx-macros/src/database/mysql.rs +++ b/sqlx-macros-core/src/database/mysql.rs @@ -1,4 +1,4 @@ -use sqlx_core as sqlx; +use super::fake_sqlx as sqlx; impl_database_ext! { sqlx::mysql::MySql { @@ -47,7 +47,7 @@ impl_database_ext! { #[cfg(feature = "bigdecimal")] sqlx::types::BigDecimal, - #[cfg(feature = "decimal")] + #[cfg(feature = "rust_decimal")] sqlx::types::Decimal, #[cfg(feature = "json")] @@ -55,6 +55,5 @@ impl_database_ext! { }, ParamChecking::Weak, feature-types: info => info.__type_feature_gate(), - row = sqlx::mysql::MySqlRow, - name = "MySQL" + row: sqlx::mysql::MySqlRow, } diff --git a/sqlx-macros/src/database/postgres.rs b/sqlx-macros-core/src/database/postgres.rs similarity index 98% rename from sqlx-macros/src/database/postgres.rs rename to sqlx-macros-core/src/database/postgres.rs index 57335430f2..2c2d461f06 100644 --- a/sqlx-macros/src/database/postgres.rs +++ b/sqlx-macros-core/src/database/postgres.rs @@ -1,4 +1,4 @@ -use sqlx_core as sqlx; +use super::fake_sqlx as sqlx; impl_database_ext! { sqlx::postgres::Postgres { @@ -169,7 +169,7 @@ impl_database_ext! { Vec> | &[sqlx::postgres::types::PgRange], - #[cfg(feature = "decimal")] + #[cfg(feature = "rust_decimal")] Vec> | &[sqlx::postgres::types::PgRange], @@ -203,6 +203,5 @@ impl_database_ext! { }, ParamChecking::Strong, feature-types: info => info.__type_feature_gate(), - row = sqlx::postgres::PgRow, - name = "PostgreSQL" + row: sqlx::postgres::PgRow, } diff --git a/sqlx-macros/src/database/sqlite.rs b/sqlx-macros-core/src/database/sqlite.rs similarity index 55% rename from sqlx-macros/src/database/sqlite.rs rename to sqlx-macros-core/src/database/sqlite.rs index aaa7f2c0a8..a2ae664f68 100644 --- a/sqlx-macros/src/database/sqlite.rs +++ b/sqlx-macros-core/src/database/sqlite.rs @@ -1,4 +1,4 @@ -use sqlx_core as sqlx; +use super::fake_sqlx as sqlx; // f32 is not included below as REAL represents a floating point value // stored as an 8-byte IEEE floating point number @@ -17,9 +17,20 @@ impl_database_ext! { #[cfg(feature = "chrono")] sqlx::types::chrono::DateTime | sqlx::types::chrono::DateTime<_>, + + #[cfg(feature = "time")] + sqlx::types::time::PrimitiveDateTime, + + #[cfg(feature = "time")] + sqlx::types::time::OffsetDateTime, + + #[cfg(feature = "uuid")] + sqlx::types::Uuid, }, ParamChecking::Weak, feature-types: _info => None, - row = sqlx::sqlite::SqliteRow, - name = "SQLite" + row: sqlx::sqlite::SqliteRow, + // Since proc-macros don't benefit from async, we can make a describe call directly + // which also ensures that the database is closed afterwards, regardless of errors. + describe-blocking: sqlx_sqlite::describe_blocking, } diff --git a/sqlx-macros/src/derives/attributes.rs b/sqlx-macros-core/src/derives/attributes.rs similarity index 89% rename from sqlx-macros/src/derives/attributes.rs rename to sqlx-macros-core/src/derives/attributes.rs index 85a853f27d..664e2301a6 100644 --- a/sqlx-macros/src/derives/attributes.rs +++ b/sqlx-macros-core/src/derives/attributes.rs @@ -31,21 +31,12 @@ macro_rules! try_set { pub struct TypeName { pub val: String, pub span: Span, - /// Whether the old sqlx(rename) syntax was used instead of sqlx(type_name) - pub deprecated_rename: bool, } impl TypeName { pub fn get(&self) -> TokenStream { let val = &self.val; - if self.deprecated_rename { - quote_spanned!(self.span=> { - ::sqlx::_rename(); - #val - }) - } else { - quote! { #val } - } + quote! { #val } } } @@ -125,23 +116,6 @@ pub fn parse_container_attributes(input: &[Attribute]) -> syn::Result { - try_set!( - type_name, - TypeName { - val: val.value(), - span: value.span(), - deprecated_rename: true }, value ) diff --git a/sqlx-macros/src/derives/decode.rs b/sqlx-macros-core/src/derives/decode.rs similarity index 100% rename from sqlx-macros/src/derives/decode.rs rename to sqlx-macros-core/src/derives/decode.rs diff --git a/sqlx-macros/src/derives/encode.rs b/sqlx-macros-core/src/derives/encode.rs similarity index 100% rename from sqlx-macros/src/derives/encode.rs rename to sqlx-macros-core/src/derives/encode.rs diff --git a/sqlx-macros/src/derives/mod.rs b/sqlx-macros-core/src/derives/mod.rs similarity index 78% rename from sqlx-macros/src/derives/mod.rs rename to sqlx-macros-core/src/derives/mod.rs index a1711ba0dc..45e8d521c4 100644 --- a/sqlx-macros/src/derives/mod.rs +++ b/sqlx-macros-core/src/derives/mod.rs @@ -4,10 +4,10 @@ mod encode; mod row; mod r#type; -pub(crate) use decode::expand_derive_decode; -pub(crate) use encode::expand_derive_encode; -pub(crate) use r#type::expand_derive_type; -pub(crate) use row::expand_derive_from_row; +pub use decode::expand_derive_decode; +pub use encode::expand_derive_encode; +pub use r#type::expand_derive_type; +pub use row::expand_derive_from_row; use self::attributes::RenameAll; use heck::{ToKebabCase, ToLowerCamelCase, ToShoutySnakeCase, ToSnakeCase, ToUpperCamelCase}; @@ -15,7 +15,7 @@ use proc_macro2::TokenStream; use std::iter::FromIterator; use syn::DeriveInput; -pub(crate) fn expand_derive_type_encode_decode(input: &DeriveInput) -> syn::Result { +pub fn expand_derive_type_encode_decode(input: &DeriveInput) -> syn::Result { let encode_tts = expand_derive_encode(input)?; let decode_tts = expand_derive_decode(input)?; let type_tts = expand_derive_type(input)?; diff --git a/sqlx-macros/src/derives/row.rs b/sqlx-macros-core/src/derives/row.rs similarity index 100% rename from sqlx-macros/src/derives/row.rs rename to sqlx-macros-core/src/derives/row.rs diff --git a/sqlx-macros/src/derives/type.rs b/sqlx-macros-core/src/derives/type.rs similarity index 100% rename from sqlx-macros/src/derives/type.rs rename to sqlx-macros-core/src/derives/type.rs diff --git a/sqlx-macros-core/src/lib.rs b/sqlx-macros-core/src/lib.rs new file mode 100644 index 0000000000..8327ed427b --- /dev/null +++ b/sqlx-macros-core/src/lib.rs @@ -0,0 +1,71 @@ +//! Support crate for SQLx's proc macros. +//! +//! ### Note: Semver Exempt API +//! The API of this crate is not meant for general use and does *not* follow Semantic Versioning. +//! The only crate that follows Semantic Versioning in the project is the `sqlx` crate itself. +//! If you are building a custom SQLx driver, you should pin an exact version of this and +//! `sqlx-core` to avoid breakages: +//! +//! ```toml +//! sqlx-core = "=0.6.2" +//! sqlx-macros-core = "=0.6.2" +//! ``` +//! +//! And then make releases in lockstep with `sqlx-core`. We recommend all driver crates, in-tree +//! or otherwise, use the same version numbers as `sqlx-core` to avoid confusion. + +use once_cell::sync::Lazy; + +use crate::query::QueryDriver; + +pub type Error = Box; + +pub type Result = std::result::Result; + +mod common; +mod database; + +pub mod derives; +pub mod query; + +// The compiler gives misleading help messages about `#[cfg(test)]` when this is just named `test`. +pub mod test_attr; + +#[cfg(feature = "migrate")] +pub mod migrate; + +pub const FOSS_DRIVERS: &[QueryDriver] = &[ + #[cfg(feature = "mysql")] + QueryDriver::new::(), + #[cfg(feature = "postgres")] + QueryDriver::new::(), + #[cfg(feature = "sqlite")] + QueryDriver::new::(), +]; + +pub fn block_on(f: F) -> F::Output +where + F: std::future::Future, +{ + #[cfg(feature = "_rt-tokio")] + { + use tokio::runtime::{self, Runtime}; + + // We need a single, persistent Tokio runtime since we're caching connections, + // otherwise we'll get "IO driver has terminated" errors. + static TOKIO_RT: Lazy = Lazy::new(|| { + runtime::Builder::new_current_thread() + .enable_all() + .build() + .expect("failed to start Tokio runtime") + }); + + return TOKIO_RT.block_on(f); + } + + #[cfg(all(feature = "_rt-async-std", not(feature = "tokio")))] + return async_std::task::block_on(f); + + #[cfg(not(any(feature = "_rt-async-std", feature = "tokio")))] + sqlx_core::rt::missing_rt(f) +} diff --git a/sqlx-macros/src/migrate.rs b/sqlx-macros-core/src/migrate.rs similarity index 98% rename from sqlx-macros/src/migrate.rs rename to sqlx-macros-core/src/migrate.rs index 58d3ef4501..ac93b0442b 100644 --- a/sqlx-macros/src/migrate.rs +++ b/sqlx-macros-core/src/migrate.rs @@ -57,7 +57,7 @@ impl ToTokens for QuotedMigration { } // mostly copied from sqlx-core/src/migrate/source.rs -pub(crate) fn expand_migrator_from_lit_dir(dir: LitStr) -> crate::Result { +pub fn expand_migrator_from_lit_dir(dir: LitStr) -> crate::Result { expand_migrator_from_dir(&dir.value(), dir.span()) } diff --git a/sqlx-macros/src/query/args.rs b/sqlx-macros-core/src/query/args.rs similarity index 100% rename from sqlx-macros/src/query/args.rs rename to sqlx-macros-core/src/query/args.rs diff --git a/sqlx-macros-core/src/query/data.rs b/sqlx-macros-core/src/query/data.rs new file mode 100644 index 0000000000..4c2075bd7c --- /dev/null +++ b/sqlx-macros-core/src/query/data.rs @@ -0,0 +1,192 @@ +use std::collections::BTreeMap; +use std::fs::{self, File}; +use std::io::BufWriter; +use std::path::{Path, PathBuf}; +use std::sync::Mutex; + +use once_cell::sync::Lazy; +use proc_macro2::Span; + +use sqlx_core::database::Database; +use sqlx_core::describe::Describe; +use sqlx_core::executor::Executor; + +use crate::database::DatabaseExt; + +#[derive(serde::Deserialize, serde::Serialize)] +#[serde(bound( + serialize = "Describe: serde::Serialize", + deserialize = "Describe: serde::de::DeserializeOwned" +))] +#[derive(Debug)] +pub struct QueryData { + #[allow(dead_code)] + pub(super) query: String, + pub(super) describe: Describe, + pub(super) hash: String, +} + +impl QueryData { + pub async fn from_db( + conn: impl Executor<'_, Database = DB>, + query: &str, + ) -> crate::Result { + Ok(Self::from_describe(query, conn.describe(query).await?)) + } + + pub fn from_describe(query: &str, describe: Describe) -> Self { + QueryData { + query: query.into(), + describe, + hash: hash_string(query), + } + } +} + +static OFFLINE_DATA_CACHE: Lazy>> = + Lazy::new(|| Mutex::new(BTreeMap::new())); + +#[derive(serde::Deserialize)] +struct BaseQuery { + query: String, + describe: serde_json::Value, +} + +#[derive(serde::Deserialize)] +struct OfflineData { + db: String, + #[serde(flatten)] + hash_to_query: BTreeMap, +} + +impl OfflineData { + fn get_query_from_hash(&self, hash: &str) -> Option { + self.hash_to_query.get(hash).map(|base_query| DynQueryData { + db_name: self.db.clone(), + query: base_query.query.to_owned(), + describe: base_query.describe.to_owned(), + hash: hash.to_owned(), + }) + } +} + +#[derive(serde::Deserialize)] +pub struct DynQueryData { + #[serde(skip)] + pub db_name: String, + pub query: String, + pub describe: serde_json::Value, + #[serde(skip)] + pub hash: String, +} + +impl DynQueryData { + /// Find and deserialize the data table for this query from a shared `sqlx-data.json` + /// file. The expected structure is a JSON map keyed by the SHA-256 hash of queries in hex. + pub fn from_data_file(path: impl AsRef, query: &str) -> crate::Result { + let path = path.as_ref(); + + let query_data = { + let mut cache = OFFLINE_DATA_CACHE + .lock() + // Just reset the cache on error + .unwrap_or_else(|posion_err| { + let mut guard = posion_err.into_inner(); + *guard = BTreeMap::new(); + guard + }); + + if !cache.contains_key(path) { + let offline_data_contents = fs::read_to_string(path) + .map_err(|e| format!("failed to read path {}: {}", path.display(), e))?; + let offline_data: OfflineData = serde_json::from_str(&offline_data_contents)?; + let _ = cache.insert(path.to_owned(), offline_data); + } + + let offline_data = cache + .get(path) + .expect("Missing data should have just been added"); + + let query_hash = hash_string(query); + let query_data = offline_data + .get_query_from_hash(&query_hash) + .ok_or_else(|| format!("failed to find data for query {}", query))?; + + if query != query_data.query { + return Err(format!( + "hash collision for stored queries:\n{:?}\n{:?}", + query, query_data.query + ) + .into()); + } + + query_data + }; + + #[cfg(procmacr2_semver_exempt)] + { + let path = path.as_ref().canonicalize()?; + let path = path.to_str().ok_or_else(|| { + format!( + "sqlx-data.json path cannot be represented as a string: {:?}", + path + ) + })?; + + proc_macro::tracked_path::path(path); + } + + Ok(query_data) + } +} + +impl QueryData +where + Describe: serde::Serialize + serde::de::DeserializeOwned, +{ + pub fn from_dyn_data(dyn_data: DynQueryData) -> crate::Result { + assert!(!dyn_data.db_name.is_empty()); + assert!(!dyn_data.hash.is_empty()); + + if DB::NAME == dyn_data.db_name { + let describe: Describe = serde_json::from_value(dyn_data.describe)?; + Ok(QueryData { + query: dyn_data.query, + describe, + hash: dyn_data.hash, + }) + } else { + Err(format!( + "expected query data for {}, got data for {}", + DB::NAME, + dyn_data.db_name + ) + .into()) + } + } + + pub fn save_in(&self, dir: impl AsRef, input_span: Span) -> crate::Result<()> { + // we save under the hash of the span representation because that should be unique + // per invocation + let path = dir.as_ref().join(format!( + "query-{}.json", + hash_string(&format!("{:?}", input_span)) + )); + + serde_json::to_writer_pretty( + BufWriter::new( + File::create(&path) + .map_err(|e| format!("failed to open path {}: {}", path.display(), e))?, + ), + self, + ) + .map_err(Into::into) + } +} + +pub fn hash_string(query: &str) -> String { + // picked `sha2` because it's already in the dependency tree for both MySQL and Postgres + use sha2::{Digest, Sha256}; + + hex::encode(Sha256::digest(query.as_bytes())) +} diff --git a/sqlx-macros/src/query/input.rs b/sqlx-macros-core/src/query/input.rs similarity index 98% rename from sqlx-macros/src/query/input.rs rename to sqlx-macros-core/src/query/input.rs index f3bce4a333..4711c65078 100644 --- a/sqlx-macros/src/query/input.rs +++ b/sqlx-macros-core/src/query/input.rs @@ -10,7 +10,6 @@ use syn::{ExprArray, Type}; pub struct QueryMacroInput { pub(super) sql: String, - #[cfg_attr(not(feature = "offline"), allow(dead_code))] pub(super) src_span: Span, pub(super) record_type: RecordType, diff --git a/sqlx-macros/src/query/mod.rs b/sqlx-macros-core/src/query/mod.rs similarity index 58% rename from sqlx-macros/src/query/mod.rs rename to sqlx-macros-core/src/query/mod.rs index 44a540be8e..f61c3b3d2c 100644 --- a/sqlx-macros/src/query/mod.rs +++ b/sqlx-macros-core/src/query/mod.rs @@ -1,7 +1,6 @@ use std::collections::BTreeMap; use std::path::PathBuf; use std::str::FromStr; -#[cfg(feature = "offline")] use std::sync::{Arc, Mutex}; use once_cell::sync::Lazy; @@ -13,32 +12,74 @@ use quote::{format_ident, quote}; use sqlx_core::connection::Connection; use sqlx_core::database::Database; use sqlx_core::{column::Column, describe::Describe, type_info::TypeInfo}; -use sqlx_rt::{block_on, AsyncMutex}; use crate::database::DatabaseExt; -use crate::query::data::QueryData; +use crate::query::data::{DynQueryData, QueryData}; use crate::query::input::RecordType; use either::Either; +use url::Url; mod args; mod data; mod input; mod output; +#[derive(Copy, Clone)] +pub struct QueryDriver { + db_name: &'static str, + url_schemes: &'static [&'static str], + expand: fn(QueryMacroInput, QueryDataSource) -> crate::Result, +} + +impl QueryDriver { + pub const fn new() -> Self + where + Describe: serde::Serialize + serde::de::DeserializeOwned, + { + QueryDriver { + db_name: DB::NAME, + url_schemes: DB::URL_SCHEMES, + expand: expand_with::, + } + } +} +pub enum QueryDataSource<'a> { + Live { + database_url: &'a str, + database_url_parsed: Url, + }, + Cached(DynQueryData), +} + +impl<'a> QueryDataSource<'a> { + pub fn live(database_url: &'a str) -> crate::Result { + Ok(QueryDataSource::Live { + database_url, + database_url_parsed: database_url.parse()?, + }) + } + + pub fn matches_driver(&self, driver: &QueryDriver) -> bool { + match self { + Self::Live { + database_url_parsed, + .. + } => driver.url_schemes.contains(&database_url_parsed.scheme()), + Self::Cached(dyn_data) => dyn_data.db_name == driver.db_name, + } + } +} + struct Metadata { #[allow(unused)] manifest_dir: PathBuf, offline: bool, database_url: Option, - #[cfg(feature = "offline")] package_name: String, - #[cfg(feature = "offline")] target_dir: PathBuf, - #[cfg(feature = "offline")] workspace_root: Arc>>, } -#[cfg(feature = "offline")] impl Metadata { pub fn workspace_root(&self) -> PathBuf { let mut root = self.workspace_root.lock().unwrap(); @@ -76,12 +117,10 @@ static METADATA: Lazy = Lazy::new(|| { .expect("`CARGO_MANIFEST_DIR` must be set") .into(); - #[cfg(feature = "offline")] let package_name: String = env("CARGO_PKG_NAME") .expect("`CARGO_PKG_NAME` must be set") .into(); - #[cfg(feature = "offline")] let target_dir = env("CARGO_TARGET_DIR").map_or_else(|_| "target".into(), |dir| dir.into()); // If a .env file exists at CARGO_MANIFEST_DIR, load environment variables from this, @@ -116,196 +155,98 @@ static METADATA: Lazy = Lazy::new(|| { manifest_dir, offline, database_url, - #[cfg(feature = "offline")] package_name, - #[cfg(feature = "offline")] target_dir, - #[cfg(feature = "offline")] workspace_root: Arc::new(Mutex::new(None)), } }); -pub fn expand_input(input: QueryMacroInput) -> crate::Result { - match &*METADATA { - #[cfg(not(any( - feature = "postgres", - feature = "mysql", - feature = "mssql", - feature = "sqlite" - )))] - Metadata { - offline: false, - database_url: Some(db_url), - .. - } => Err( - "At least one of the features ['postgres', 'mysql', 'mssql', 'sqlite'] must be enabled \ - to get information directly from a database" - .into(), - ), - - #[cfg(any( - feature = "postgres", - feature = "mysql", - feature = "mssql", - feature = "sqlite" - ))] +pub fn expand_input<'a>( + input: QueryMacroInput, + drivers: impl IntoIterator, +) -> crate::Result { + let data_source = match &*METADATA { Metadata { offline: false, database_url: Some(db_url), .. - } => expand_from_db(input, &db_url), + } => QueryDataSource::live(db_url)?, - #[cfg(feature = "offline")] _ => { let data_file_path = METADATA.manifest_dir.join("sqlx-data.json"); - if data_file_path.exists() { - expand_from_file(input, data_file_path) + let data_file_path = if data_file_path.exists() { + data_file_path } else { let workspace_data_file_path = METADATA.workspace_root().join("sqlx-data.json"); if workspace_data_file_path.exists() { - expand_from_file(input, workspace_data_file_path) + workspace_data_file_path } else { - Err( + return Err( "`DATABASE_URL` must be set, or `cargo sqlx prepare` must have been run \ and sqlx-data.json must exist, to use query macros" .into(), - ) + ); } - } + }; + + QueryDataSource::Cached(DynQueryData::from_data_file(&data_file_path, &input.sql)?) } + }; - #[cfg(not(feature = "offline"))] - Metadata { offline: true, .. } => { - Err("The cargo feature `offline` has to be enabled to use `SQLX_OFFLINE`".into()) + for driver in drivers { + if data_source.matches_driver(&driver) { + return (driver.expand)(input, data_source); } + } - #[cfg(not(feature = "offline"))] - Metadata { - offline: false, - database_url: None, + match data_source { + QueryDataSource::Live { + database_url_parsed, .. - } => Err("`DATABASE_URL` must be set to use query macros".into()), + } => Err(format!( + "no database driver found matching URL scheme {:?}; the corresponding Cargo feature may need to be enabled", + database_url_parsed.scheme() + ).into()), + QueryDataSource::Cached(data) => { + Err(format!( + "found cached data for database {:?} but no matching driver; the corresponding Cargo feature may need to be enabled", + data.db_name + ).into()) + } } } -#[cfg(any( - feature = "postgres", - feature = "mysql", - feature = "mssql", - feature = "sqlite" -))] -fn expand_from_db(input: QueryMacroInput, db_url: &str) -> crate::Result { - use sqlx_core::any::{AnyConnectOptions, AnyConnection}; - - let connect_opts = AnyConnectOptions::from_str(db_url)?; - - // SQLite is not used in the connection cache due to issues with newly created - // databases seemingly being locked for several seconds when journaling is off. This - // isn't a huge issue since the intent of the connection cache was to make connections - // to remote databases much faster. Relevant links: - // - https://github.com/launchbadge/sqlx/pull/1782#issuecomment-1089226716 - // - https://github.com/launchbadge/sqlx/issues/1929 - #[cfg(feature = "sqlite")] - if let Some(sqlite_opts) = connect_opts.as_sqlite() { - // Since proc-macros don't benefit from async, we can make a describe call directly - // which also ensures that the database is closed afterwards, regardless of errors. - let describe = sqlx_core::sqlite::describe_blocking(sqlite_opts, &input.sql)?; - let data = QueryData::from_describe(&input.sql, describe); - return expand_with_data(input, data, false); - } - - block_on(async { - static CONNECTION_CACHE: Lazy>> = - Lazy::new(|| AsyncMutex::new(BTreeMap::new())); - - let mut cache = CONNECTION_CACHE.lock().await; - - if !cache.contains_key(db_url) { - let conn = AnyConnection::connect_with(&connect_opts).await?; - let _ = cache.insert(db_url.to_owned(), conn); - } - - let conn_item = cache.get_mut(db_url).expect("Item was just inserted"); - match conn_item.private_get_mut() { - #[cfg(feature = "postgres")] - sqlx_core::any::AnyConnectionKind::Postgres(conn) => { - let data = QueryData::from_db(conn, &input.sql).await?; - expand_with_data(input, data, false) - } - #[cfg(feature = "mssql")] - sqlx_core::any::AnyConnectionKind::Mssql(conn) => { - let data = QueryData::from_db(conn, &input.sql).await?; - expand_with_data(input, data, false) - } - #[cfg(feature = "mysql")] - sqlx_core::any::AnyConnectionKind::MySql(conn) => { - let data = QueryData::from_db(conn, &input.sql).await?; - expand_with_data(input, data, false) - } - // Variants depend on feature flags - #[allow(unreachable_patterns)] - item => { - return Err(format!("Missing expansion needed for: {:?}", item).into()); - } +fn expand_with( + input: QueryMacroInput, + data_source: QueryDataSource, +) -> crate::Result +where + Describe: DescribeExt, +{ + let (query_data, offline): (QueryData, bool) = match data_source { + QueryDataSource::Cached(dyn_data) => (QueryData::from_dyn_data(dyn_data)?, true), + QueryDataSource::Live { database_url, .. } => { + let describe = DB::describe_blocking(&input.sql, &database_url)?; + (QueryData::from_describe(&input.sql, describe), false) } - }) -} + }; -#[cfg(feature = "offline")] -pub fn expand_from_file(input: QueryMacroInput, file: PathBuf) -> crate::Result { - use data::offline::DynQueryData; - - let query_data = DynQueryData::from_data_file(file, &input.sql)?; - assert!(!query_data.db_name.is_empty()); - - match &*query_data.db_name { - #[cfg(feature = "postgres")] - sqlx_core::postgres::Postgres::NAME => expand_with_data( - input, - QueryData::::from_dyn_data(query_data)?, - true, - ), - #[cfg(feature = "mysql")] - sqlx_core::mysql::MySql::NAME => expand_with_data( - input, - QueryData::::from_dyn_data(query_data)?, - true, - ), - #[cfg(feature = "sqlite")] - sqlx_core::sqlite::Sqlite::NAME => expand_with_data( - input, - QueryData::::from_dyn_data(query_data)?, - true, - ), - _ => Err(format!( - "found query data for {} but the feature for that database was not enabled", - query_data.db_name - ) - .into()), - } + expand_with_data(input, query_data, offline) } // marker trait for `Describe` that lets us conditionally require it to be `Serialize + Deserialize` -#[cfg(feature = "offline")] trait DescribeExt: serde::Serialize + serde::de::DeserializeOwned {} -#[cfg(feature = "offline")] impl DescribeExt for Describe where Describe: serde::Serialize + serde::de::DeserializeOwned { } -#[cfg(not(feature = "offline"))] -trait DescribeExt {} - -#[cfg(not(feature = "offline"))] -impl DescribeExt for Describe {} - fn expand_with_data( input: QueryMacroInput, data: QueryData, - #[allow(unused_variables)] offline: bool, + offline: bool, ) -> crate::Result where Describe: DescribeExt, @@ -409,7 +350,6 @@ where // Store query metadata only if offline support is enabled but the current build is online. // If the build is offline, the cache is our input so it's pointless to also write data for it. - #[cfg(feature = "offline")] if !offline { // Use a separate sub-directory for each crate in a workspace. This avoids a race condition // where `prepare` can pull in queries from multiple crates if they happen to be generated diff --git a/sqlx-macros/src/query/output.rs b/sqlx-macros-core/src/query/output.rs similarity index 100% rename from sqlx-macros/src/query/output.rs rename to sqlx-macros-core/src/query/output.rs diff --git a/sqlx-macros/src/test_attr.rs b/sqlx-macros-core/src/test_attr.rs similarity index 100% rename from sqlx-macros/src/test_attr.rs rename to sqlx-macros-core/src/test_attr.rs diff --git a/sqlx-macros/Cargo.toml b/sqlx-macros/Cargo.toml index 14ecbc7735..6a2232fb59 100644 --- a/sqlx-macros/Cargo.toml +++ b/sqlx-macros/Cargo.toml @@ -1,85 +1,47 @@ [package] name = "sqlx-macros" -version = "0.6.2" -repository = "https://github.com/launchbadge/sqlx" description = "Macros for SQLx, the rust SQL toolkit. Not intended to be used directly." -license = "MIT OR Apache-2.0" -edition = "2021" -authors = [ - "Ryan Leckey ", - "Austin Bonander ", - "Chloe Ross ", - "Daniel Akhterov ", -] # daniel@launchbadge.com +version.workspace = true +license.workspace = true +edition.workspace = true +authors.workspace = true +repository.workspace = true [lib] proc-macro = true [features] -default = ["runtime-tokio-native-tls", "migrate"] -migrate = ["sha2", "sqlx-core/migrate"] - -# runtimes -runtime-actix-native-tls = ["runtime-tokio-native-tls"] -runtime-async-std-native-tls = [ - "sqlx-core/runtime-async-std-native-tls", - "sqlx-rt/runtime-async-std-native-tls", - "_rt-async-std", -] -runtime-tokio-native-tls = [ - "sqlx-core/runtime-tokio-native-tls", - "sqlx-rt/runtime-tokio-native-tls", - "_rt-tokio", -] - -runtime-actix-rustls = ["runtime-tokio-rustls"] -runtime-async-std-rustls = [ - "sqlx-core/runtime-async-std-rustls", - "sqlx-rt/runtime-async-std-rustls", - "_rt-async-std", -] -runtime-tokio-rustls = [ - "sqlx-core/runtime-tokio-rustls", - "sqlx-rt/runtime-tokio-rustls", - "_rt-tokio", -] # for conditional compilation -_rt-async-std = [] -_rt-tokio = [] +_rt-async-std = ["sqlx-macros-core/_rt-async-std"] +_rt-tokio = ["sqlx-macros-core/_rt-tokio"] -# offline building support -offline = ["sqlx-core/offline", "hex", "serde", "serde_json", "sha2"] +_tls-native-tls = ["sqlx-macros-core/_tls-native-tls"] +_tls-rustls = ["sqlx-macros-core/_tls-rustls"] + +# SQLx features +migrate = ["sqlx-macros-core/migrate"] # database -mysql = ["sqlx-core/mysql"] -postgres = ["sqlx-core/postgres"] -sqlite = ["sqlx-core/sqlite"] -mssql = ["sqlx-core/mssql"] +mysql = ["sqlx-macros-core/mysql"] +postgres = ["sqlx-macros-core/postgres"] +sqlite = ["sqlx-macros-core/sqlite"] # type -bigdecimal = ["sqlx-core/bigdecimal"] -decimal = ["sqlx-core/decimal"] -chrono = ["sqlx-core/chrono"] -time = ["sqlx-core/time"] -ipnetwork = ["sqlx-core/ipnetwork"] -mac_address = ["sqlx-core/mac_address"] -uuid = ["sqlx-core/uuid"] -bit-vec = ["sqlx-core/bit-vec"] -json = ["sqlx-core/json", "serde_json"] +bigdecimal = ["sqlx-macros-core/bigdecimal"] +bit-vec = ["sqlx-macros-core/bit-vec"] +chrono = ["sqlx-macros-core/chrono"] +ipnetwork = ["sqlx-macros-core/ipnetwork"] +mac_address = ["sqlx-macros-core/mac_address"] +rust_decimal = ["sqlx-macros-core/rust_decimal"] +time = ["sqlx-macros-core/time"] +uuid = ["sqlx-macros-core/uuid"] +json = ["sqlx-macros-core/json"] [dependencies] -dotenvy = { version = "0.15.0", default-features = false } -hex = { version = "0.4.3", optional = true } -heck = { version = "0.4", features = ["unicode"] } -either = "1.6.1" -once_cell = "1.9.0" +sqlx-core = { version = "=0.6.2", default-features = false, features = ["any"], path = "../sqlx-core" } +sqlx-macros-core = { version = "=0.6.2", path = "../sqlx-macros-core" } + proc-macro2 = { version = "1.0.36", default-features = false } -sqlx-core = { version = "0.6.2", default-features = false, features = ["any"], path = "../sqlx-core" } -sqlx-rt = { version = "0.6.2", default-features = false, path = "../sqlx-rt" } -serde = { version = "1.0.132", features = ["derive"], optional = true } -serde_json = { version = "1.0.73", optional = true } -sha2 = { version = "0.10.0", optional = true } syn = { version = "1.0.84", default-features = false, features = ["full"] } quote = { version = "1.0.14", default-features = false } -url = { version = "2.2.2", default-features = false } diff --git a/sqlx-macros/src/database/mssql.rs b/sqlx-macros/src/database/mssql.rs deleted file mode 100644 index f0ab6107e2..0000000000 --- a/sqlx-macros/src/database/mssql.rs +++ /dev/null @@ -1,18 +0,0 @@ -use sqlx_core as sqlx; - -impl_database_ext! { - sqlx::mssql::Mssql { - bool, - i8, - i16, - i32, - i64, - f32, - f64, - String, - }, - ParamChecking::Weak, - feature-types: _info => None, - row = sqlx::mssql::MssqlRow, - name = "MSSQL" -} diff --git a/sqlx-macros/src/lib.rs b/sqlx-macros/src/lib.rs index c858b204dd..229c1030b1 100644 --- a/sqlx-macros/src/lib.rs +++ b/sqlx-macros/src/lib.rs @@ -1,37 +1,14 @@ -#![cfg_attr( - not(any(feature = "postgres", feature = "mysql", feature = "offline")), - allow(dead_code, unused_macros, unused_imports) -)] -#![cfg_attr( - any(sqlx_macros_unstable, procmacro2_semver_exempt), - feature(track_path, proc_macro_tracked_env) -)] -extern crate proc_macro; - use proc_macro::TokenStream; use quote::quote; -type Error = Box; - -type Result = std::result::Result; - -mod common; -mod database; -mod derives; -mod query; - -// The compiler gives misleading help messages about `#[cfg(test)]` when this is just named `test`. -mod test_attr; - -#[cfg(feature = "migrate")] -mod migrate; +use sqlx_macros_core::*; #[proc_macro] pub fn expand_query(input: TokenStream) -> TokenStream { let input = syn::parse_macro_input!(input as query::QueryMacroInput); - match query::expand_input(input) { + match query::expand_input(input, FOSS_DRIVERS) { Ok(ts) => ts.into(), Err(e) => { if let Some(parse_err) = e.downcast_ref::() { diff --git a/sqlx-macros/src/query/data.rs b/sqlx-macros/src/query/data.rs deleted file mode 100644 index 682950be41..0000000000 --- a/sqlx-macros/src/query/data.rs +++ /dev/null @@ -1,202 +0,0 @@ -use sqlx_core::database::Database; -use sqlx_core::describe::Describe; -use sqlx_core::executor::Executor; - -#[cfg_attr(feature = "offline", derive(serde::Deserialize, serde::Serialize))] -#[cfg_attr( - feature = "offline", - serde(bound( - serialize = "Describe: serde::Serialize", - deserialize = "Describe: serde::de::DeserializeOwned" - )) -)] -#[derive(Debug)] -pub struct QueryData { - #[allow(dead_code)] - pub(super) query: String, - pub(super) describe: Describe, - #[cfg(feature = "offline")] - pub(super) hash: String, -} - -impl QueryData { - pub async fn from_db( - conn: impl Executor<'_, Database = DB>, - query: &str, - ) -> crate::Result { - Ok(Self::from_describe(query, conn.describe(query).await?)) - } - - pub fn from_describe(query: &str, describe: Describe) -> Self { - QueryData { - query: query.into(), - describe, - #[cfg(feature = "offline")] - hash: offline::hash_string(query), - } - } -} - -#[cfg(feature = "offline")] -pub mod offline { - use super::QueryData; - use crate::database::DatabaseExt; - - use std::collections::BTreeMap; - use std::fs::{self, File}; - use std::io::BufWriter; - use std::path::{Path, PathBuf}; - use std::sync::Mutex; - - use once_cell::sync::Lazy; - use proc_macro2::Span; - use sqlx_core::describe::Describe; - - static OFFLINE_DATA_CACHE: Lazy>> = - Lazy::new(|| Mutex::new(BTreeMap::new())); - - #[derive(serde::Deserialize)] - struct BaseQuery { - query: String, - describe: serde_json::Value, - } - - #[derive(serde::Deserialize)] - struct OfflineData { - db: String, - #[serde(flatten)] - hash_to_query: BTreeMap, - } - - impl OfflineData { - fn get_query_from_hash(&self, hash: &str) -> Option { - self.hash_to_query.get(hash).map(|base_query| DynQueryData { - db_name: self.db.clone(), - query: base_query.query.to_owned(), - describe: base_query.describe.to_owned(), - hash: hash.to_owned(), - }) - } - } - - #[derive(serde::Deserialize)] - pub struct DynQueryData { - #[serde(skip)] - pub db_name: String, - pub query: String, - pub describe: serde_json::Value, - #[serde(skip)] - pub hash: String, - } - - impl DynQueryData { - /// Find and deserialize the data table for this query from a shared `sqlx-data.json` - /// file. The expected structure is a JSON map keyed by the SHA-256 hash of queries in hex. - pub fn from_data_file(path: impl AsRef, query: &str) -> crate::Result { - let path = path.as_ref(); - - let query_data = { - let mut cache = OFFLINE_DATA_CACHE - .lock() - // Just reset the cache on error - .unwrap_or_else(|posion_err| { - let mut guard = posion_err.into_inner(); - *guard = BTreeMap::new(); - guard - }); - - if !cache.contains_key(path) { - let offline_data_contents = fs::read_to_string(path) - .map_err(|e| format!("failed to read path {}: {}", path.display(), e))?; - let offline_data: OfflineData = serde_json::from_str(&offline_data_contents)?; - let _ = cache.insert(path.to_owned(), offline_data); - } - - let offline_data = cache - .get(path) - .expect("Missing data should have just been added"); - - let query_hash = hash_string(query); - let query_data = offline_data - .get_query_from_hash(&query_hash) - .ok_or_else(|| format!("failed to find data for query {}", query))?; - - if query != query_data.query { - return Err(format!( - "hash collision for stored queries:\n{:?}\n{:?}", - query, query_data.query - ) - .into()); - } - - query_data - }; - - #[cfg(procmacr2_semver_exempt)] - { - let path = path.as_ref().canonicalize()?; - let path = path.to_str().ok_or_else(|| { - format!( - "sqlx-data.json path cannot be represented as a string: {:?}", - path - ) - })?; - - proc_macro::tracked_path::path(path); - } - - Ok(query_data) - } - } - - impl QueryData - where - Describe: serde::Serialize + serde::de::DeserializeOwned, - { - pub fn from_dyn_data(dyn_data: DynQueryData) -> crate::Result { - assert!(!dyn_data.db_name.is_empty()); - assert!(!dyn_data.hash.is_empty()); - - if DB::NAME == dyn_data.db_name { - let describe: Describe = serde_json::from_value(dyn_data.describe)?; - Ok(QueryData { - query: dyn_data.query, - describe, - hash: dyn_data.hash, - }) - } else { - Err(format!( - "expected query data for {}, got data for {}", - DB::NAME, - dyn_data.db_name - ) - .into()) - } - } - - pub fn save_in(&self, dir: impl AsRef, input_span: Span) -> crate::Result<()> { - // we save under the hash of the span representation because that should be unique - // per invocation - let path = dir.as_ref().join(format!( - "query-{}.json", - hash_string(&format!("{:?}", input_span)) - )); - - serde_json::to_writer_pretty( - BufWriter::new( - File::create(&path) - .map_err(|e| format!("failed to open path {}: {}", path.display(), e))?, - ), - self, - ) - .map_err(Into::into) - } - } - - pub fn hash_string(query: &str) -> String { - // picked `sha2` because it's already in the dependency tree for both MySQL and Postgres - use sha2::{Digest, Sha256}; - - hex::encode(Sha256::digest(query.as_bytes())) - } -} diff --git a/sqlx-mysql/Cargo.toml b/sqlx-mysql/Cargo.toml new file mode 100644 index 0000000000..5a1713bc49 --- /dev/null +++ b/sqlx-mysql/Cargo.toml @@ -0,0 +1,69 @@ +[package] +name = "sqlx-mysql" +readme = "README.md" +documentation = "https://docs.rs/sqlx" +description = "MySQL driver implementation for SQLx. Not for direct use; see the `sqlx` crate for details." +version.workspace = true +license.workspace = true +edition.workspace = true +authors.workspace = true +repository.workspace = true +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[features] +json = ["sqlx-core/json", "serde"] +any = ["sqlx-core/any"] +offline = ["sqlx-core/offline", "serde/derive"] +migrate = ["sqlx-core/migrate"] + +[dependencies] +# Futures crates +futures-channel = { version = "0.3.19", default-features = false, features = ["sink", "alloc", "std"] } +futures-core = { version = "0.3.19", default-features = false } +futures-io = "0.3.24" +futures-util = { version = "0.3.19", default-features = false, features = ["alloc", "sink", "io"] } + +# Cryptographic Primitives +crc = "3.0.0" +digest = { version = "0.10.0", default-features = false, features = ["std"] } +hkdf = "0.12.0" +hmac = { version = "0.12.0", default-features = false } +md-5 = { version = "0.10.0", default-features = false } +rand = { version = "0.8.4", default-features = false, features = ["std", "std_rng"] } +rsa = "0.6.0" +sha1 = { version = "0.10.1", default-features = false } +sha2 = { version = "0.10.0", default-features = false } + +# Type Integrations (versions inherited from `[workspace.dependencies]`) +bigdecimal = { workspace = true, optional = true } +chrono = { workspace = true, optional = true } +rust_decimal = { workspace = true, optional = true } +time = { workspace = true, optional = true } +uuid = { workspace = true, optional = true } + +# Misc +atoi = "1.0" +base64 = { version = "0.13.0", default-features = false, features = ["std"] } +bitflags = { version = "1.3.2", default-features = false } +byteorder = { version = "1.4.3", default-features = false, features = ["std"] } +bytes = "1.1.0" +dotenvy = "0.15.5" +dirs = "4.0.0" +either = "1.6.1" +generic-array = { version = "0.14.4", default-features = false } +hex = "0.4.3" +itoa = "1.0.1" +log = "0.4.17" +memchr = { version = "2.4.1", default-features = false } +once_cell = "1.9.0" +percent-encoding = "2.1.0" +smallvec = "1.7.0" +stringprep = "0.1.2" +thiserror = "1.0.35" +whoami = "1.2.1" + +serde = { version = "1.0.144", optional = true } + +[dependencies.sqlx-core] +version = "=0.6.2" +path = "../sqlx-core" diff --git a/sqlx-mysql/src/any.rs b/sqlx-mysql/src/any.rs new file mode 100644 index 0000000000..632abace23 --- /dev/null +++ b/sqlx-mysql/src/any.rs @@ -0,0 +1,194 @@ +use crate::protocol::text::ColumnType; +use crate::{ + MySql, MySqlColumn, MySqlConnectOptions, MySqlConnection, MySqlQueryResult, MySqlRow, + MySqlTransactionManager, MySqlTypeInfo, +}; +use either::Either; +use futures_core::future::BoxFuture; +use futures_core::stream::BoxStream; +use futures_util::{StreamExt, TryFutureExt, TryStreamExt}; +use sqlx_core::any::driver::AnyDriver; +use sqlx_core::any::{ + Any, AnyArguments, AnyColumn, AnyConnectOptions, AnyConnectionBackend, AnyQueryResult, AnyRow, + AnyStatement, AnyTypeInfo, AnyTypeInfoKind, +}; +use sqlx_core::connection::Connection; +use sqlx_core::database::Database; +use sqlx_core::describe::Describe; +use sqlx_core::executor::Executor; +use sqlx_core::transaction::TransactionManager; +use std::borrow::Cow; + +sqlx_core::declare_driver_with_optional_migrate!(DRIVER = MySql); + +impl AnyConnectionBackend for MySqlConnection { + fn name(&self) -> &str { + ::NAME + } + + fn close(self: Box) -> BoxFuture<'static, sqlx_core::Result<()>> { + Connection::close(*self) + } + + fn close_hard(self: Box) -> BoxFuture<'static, sqlx_core::Result<()>> { + Connection::close_hard(*self) + } + + fn ping(&mut self) -> BoxFuture<'_, sqlx_core::Result<()>> { + Connection::ping(self) + } + + fn begin(&mut self) -> BoxFuture<'_, sqlx_core::Result<()>> { + MySqlTransactionManager::begin(self) + } + + fn commit(&mut self) -> BoxFuture<'_, sqlx_core::Result<()>> { + MySqlTransactionManager::commit(self) + } + + fn rollback(&mut self) -> BoxFuture<'_, sqlx_core::Result<()>> { + MySqlTransactionManager::rollback(self) + } + + fn start_rollback(&mut self) { + MySqlTransactionManager::start_rollback(self) + } + + fn flush(&mut self) -> BoxFuture<'_, sqlx_core::Result<()>> { + Connection::flush(self) + } + + fn should_flush(&self) -> bool { + Connection::should_flush(self) + } + + fn fetch_many<'q>( + &'q mut self, + query: &'q str, + arguments: Option>, + ) -> BoxStream<'q, sqlx_core::Result>> { + let persistent = arguments.is_some(); + let args = arguments.as_ref().map(AnyArguments::convert_to); + + Box::pin( + self.run(query, args, persistent) + .try_flatten_stream() + .map(|res| { + Ok(match res? { + Either::Left(result) => Either::Left(map_result(result)), + Either::Right(row) => Either::Right(AnyRow::try_from(&row)?), + }) + }), + ) + } + + fn fetch_optional<'q>( + &'q mut self, + query: &'q str, + arguments: Option>, + ) -> BoxFuture<'q, sqlx_core::Result>> { + let persistent = arguments.is_some(); + let args = arguments.as_ref().map(AnyArguments::convert_to); + + Box::pin(async move { + let mut stream = self.run(query, args, persistent).await?; + futures_util::pin_mut!(stream); + + if let Some(Either::Right(row)) = stream.try_next().await? { + return Ok(Some(AnyRow::try_from(&row)?)); + } + + Ok(None) + }) + } + + fn prepare_with<'c, 'q: 'c>( + &'c mut self, + sql: &'q str, + _parameters: &[AnyTypeInfo], + ) -> BoxFuture<'c, sqlx_core::Result>> { + Box::pin(async move { + let statement = Executor::prepare_with(self, sql, &[]).await?; + AnyStatement::try_from_statement( + sql, + &statement, + statement.metadata.column_names.clone(), + ) + }) + } + + fn describe<'q>(&'q mut self, sql: &'q str) -> BoxFuture<'q, sqlx_core::Result>> { + Box::pin(async move { + let describe = Executor::describe(self, sql).await?; + describe.try_into_any() + }) + } +} + +impl<'a> TryFrom<&'a MySqlTypeInfo> for AnyTypeInfo { + type Error = sqlx_core::Error; + + fn try_from(type_info: &'a MySqlTypeInfo) -> Result { + Ok(AnyTypeInfo { + kind: match &type_info.r#type { + ColumnType::Null => AnyTypeInfoKind::Null, + ColumnType::Short => AnyTypeInfoKind::SmallInt, + ColumnType::Long => AnyTypeInfoKind::Integer, + ColumnType::LongLong => AnyTypeInfoKind::BigInt, + ColumnType::Float => AnyTypeInfoKind::Real, + ColumnType::Double => AnyTypeInfoKind::Double, + ColumnType::Blob + | ColumnType::TinyBlob + | ColumnType::MediumBlob + | ColumnType::LongBlob => AnyTypeInfoKind::Blob, + ColumnType::String | ColumnType::VarString | ColumnType::VarChar => { + AnyTypeInfoKind::Text + } + _ => { + return Err(sqlx_core::Error::AnyDriverError( + format!("Any driver does not support MySql type {:?}", type_info).into(), + )) + } + }, + }) + } +} + +impl<'a> TryFrom<&'a MySqlColumn> for AnyColumn { + type Error = sqlx_core::Error; + + fn try_from(column: &'a MySqlColumn) -> Result { + let type_info = AnyTypeInfo::try_from(&column.type_info)?; + + Ok(AnyColumn { + ordinal: column.ordinal, + name: column.name.clone(), + type_info, + }) + } +} + +impl<'a> TryFrom<&'a MySqlRow> for AnyRow { + type Error = sqlx_core::Error; + + fn try_from(row: &'a MySqlRow) -> Result { + AnyRow::map_from(row, row.column_names.clone()) + } +} + +impl<'a> TryFrom<&'a AnyConnectOptions> for MySqlConnectOptions { + type Error = sqlx_core::Error; + + fn try_from(any_opts: &'a AnyConnectOptions) -> Result { + let mut opts = Self::parse_from_url(&any_opts.database_url)?; + opts.log_settings = any_opts.log_settings.clone(); + Ok(opts) + } +} + +fn map_result(result: MySqlQueryResult) -> AnyQueryResult { + AnyQueryResult { + rows_affected: result.rows_affected, + last_insert_id: Some(result.last_insert_id as i64), + } +} diff --git a/sqlx-core/src/mysql/arguments.rs b/sqlx-mysql/src/arguments.rs similarity index 94% rename from sqlx-core/src/mysql/arguments.rs rename to sqlx-mysql/src/arguments.rs index 3d8dcce86f..3731ea24d3 100644 --- a/sqlx-core/src/mysql/arguments.rs +++ b/sqlx-mysql/src/arguments.rs @@ -1,7 +1,7 @@ -use crate::arguments::Arguments; use crate::encode::{Encode, IsNull}; -use crate::mysql::{MySql, MySqlTypeInfo}; use crate::types::Type; +use crate::{MySql, MySqlTypeInfo}; +pub(crate) use sqlx_core::arguments::*; /// Implementation of [`Arguments`] for MySQL. #[derive(Debug, Default, Clone)] diff --git a/sqlx-core/src/mysql/collation.rs b/sqlx-mysql/src/collation.rs similarity index 100% rename from sqlx-core/src/mysql/collation.rs rename to sqlx-mysql/src/collation.rs diff --git a/sqlx-core/src/mysql/column.rs b/sqlx-mysql/src/column.rs similarity index 55% rename from sqlx-core/src/mysql/column.rs rename to sqlx-mysql/src/column.rs index ecbe8ca774..5220eae092 100644 --- a/sqlx-core/src/mysql/column.rs +++ b/sqlx-mysql/src/column.rs @@ -1,7 +1,7 @@ -use crate::column::Column; use crate::ext::ustr::UStr; -use crate::mysql::protocol::text::ColumnFlags; -use crate::mysql::{MySql, MySqlTypeInfo}; +use crate::protocol::text::ColumnFlags; +use crate::{MySql, MySqlTypeInfo}; +pub(crate) use sqlx_core::column::*; #[derive(Debug, Clone)] #[cfg_attr(feature = "offline", derive(serde::Serialize, serde::Deserialize))] @@ -14,8 +14,6 @@ pub struct MySqlColumn { pub(crate) flags: Option, } -impl crate::column::private_column::Sealed for MySqlColumn {} - impl Column for MySqlColumn { type Database = MySql; @@ -31,14 +29,3 @@ impl Column for MySqlColumn { &self.type_info } } - -#[cfg(feature = "any")] -impl From for crate::any::AnyColumn { - #[inline] - fn from(column: MySqlColumn) -> Self { - crate::any::AnyColumn { - type_info: column.type_info.clone().into(), - kind: crate::any::column::AnyColumnKind::MySql(column), - } - } -} diff --git a/sqlx-core/src/mysql/connection/auth.rs b/sqlx-mysql/src/connection/auth.rs similarity index 97% rename from sqlx-core/src/mysql/connection/auth.rs rename to sqlx-mysql/src/connection/auth.rs index 237fd55288..514bb102d8 100644 --- a/sqlx-core/src/mysql/connection/auth.rs +++ b/sqlx-mysql/src/connection/auth.rs @@ -7,10 +7,10 @@ use rsa::{pkcs8::DecodePublicKey, PaddingScheme, PublicKey, RsaPublicKey}; use sha1::Sha1; use sha2::Sha256; +use crate::connection::stream::MySqlStream; use crate::error::Error; -use crate::mysql::connection::stream::MySqlStream; -use crate::mysql::protocol::auth::AuthPlugin; -use crate::mysql::protocol::Packet; +use crate::protocol::auth::AuthPlugin; +use crate::protocol::Packet; impl AuthPlugin { pub(super) async fn scramble( @@ -131,7 +131,7 @@ async fn encrypt_rsa<'s>( ) -> Result, Error> { // https://mariadb.com/kb/en/caching_sha2_password-authentication-plugin/ - if stream.is_tls() { + if stream.is_tls { // If in a TLS stream, send the password directly in clear text return Ok(to_asciz(password)); } diff --git a/sqlx-core/src/mysql/connection/establish.rs b/sqlx-mysql/src/connection/establish.rs similarity index 63% rename from sqlx-core/src/mysql/connection/establish.rs rename to sqlx-mysql/src/connection/establish.rs index 5352b1a10c..75838c57f5 100644 --- a/sqlx-core/src/mysql/connection/establish.rs +++ b/sqlx-mysql/src/connection/establish.rs @@ -1,26 +1,77 @@ use bytes::buf::Buf; use bytes::Bytes; +use futures_core::future::BoxFuture; +use crate::collation::{CharSet, Collation}; use crate::common::StatementCache; +use crate::connection::{tls, MySqlStream, MAX_PACKET_SIZE}; use crate::error::Error; -use crate::mysql::connection::{tls, MySqlStream, MAX_PACKET_SIZE}; -use crate::mysql::protocol::connect::{ +use crate::net::{Socket, WithSocket}; +use crate::protocol::connect::{ AuthSwitchRequest, AuthSwitchResponse, Handshake, HandshakeResponse, }; -use crate::mysql::protocol::Capabilities; -use crate::mysql::{MySqlConnectOptions, MySqlConnection, MySqlSslMode}; +use crate::protocol::Capabilities; +use crate::{MySqlConnectOptions, MySqlConnection}; impl MySqlConnection { pub(crate) async fn establish(options: &MySqlConnectOptions) -> Result { - let mut stream: MySqlStream = MySqlStream::connect(options).await?; + let do_handshake = DoHandshake::new(options)?; - // https://dev.mysql.com/doc/dev/mysql-server/8.0.12/page_protocol_connection_phase.html + let handshake = match &options.socket { + Some(path) => crate::net::connect_uds(path, do_handshake).await?, + None => crate::net::connect_tcp(&options.host, options.port, do_handshake).await?, + }; + + let stream = handshake.await?; + + Ok(Self { + stream, + transaction_depth: 0, + cache_statement: StatementCache::new(options.statement_cache_capacity), + log_settings: options.log_settings.clone(), + }) + } +} + +struct DoHandshake<'a> { + options: &'a MySqlConnectOptions, + charset: CharSet, + collation: Collation, +} + +impl<'a> DoHandshake<'a> { + fn new(options: &'a MySqlConnectOptions) -> Result { + let charset: CharSet = options.charset.parse()?; + let collation: Collation = options + .collation + .as_deref() + .map(|collation| collation.parse()) + .transpose()? + .unwrap_or_else(|| charset.default_collation()); + + Ok(Self { + options, + charset, + collation, + }) + } + + async fn do_handshake(self, socket: S) -> Result { + let DoHandshake { + options, + charset, + collation, + } = self; + + let mut stream = MySqlStream::with_socket(charset, collation, options, socket); + + // https://dev.mysql.com/doc/internals/en/connection-phase.html // https://mariadb.com/kb/en/connection/ let handshake: Handshake = stream.recv_packet().await?.decode()?; let mut plugin = handshake.auth_plugin; - let mut nonce = handshake.auth_plugin_data; + let nonce = handshake.auth_plugin_data; // FIXME: server version parse is a bit ugly // expecting MAJOR.MINOR.PATCH @@ -54,39 +105,7 @@ impl MySqlConnection { stream.capabilities &= handshake.server_capabilities; stream.capabilities |= Capabilities::PROTOCOL_41; - if matches!(options.ssl_mode, MySqlSslMode::Disabled) { - // remove the SSL capability if SSL has been explicitly disabled - stream.capabilities.remove(Capabilities::SSL); - } - - // Upgrade to TLS if we were asked to and the server supports it - - #[cfg(feature = "_tls-rustls")] - { - // To aid in debugging: https://github.com/rustls/rustls/issues/893 - - let local_addr = stream.local_addr(); - - match tls::maybe_upgrade(&mut stream, options).await { - Ok(()) => (), - #[cfg(feature = "_tls-rustls")] - Err(Error::Io(ioe)) => { - if let Some(&rustls::Error::CorruptMessage) = - ioe.get_ref().and_then(|e| e.downcast_ref()) - { - log::trace!("got corrupt message on socket {:?}", local_addr); - } - - return Err(Error::Io(ioe)); - } - Err(e) => return Err(e), - } - } - - #[cfg(not(feature = "_tls-rustls"))] - { - tls::maybe_upgrade(&mut stream, options).await? - } + let mut stream = tls::maybe_upgrade(stream, self.options).await?; let auth_response = if let (Some(plugin), Some(password)) = (plugin, &options.password) { Some(plugin.scramble(&mut stream, password, &nonce).await?) @@ -118,7 +137,7 @@ impl MySqlConnection { let switch: AuthSwitchRequest = packet.decode()?; plugin = Some(switch.plugin); - nonce = switch.data.chain(Bytes::new()); + let nonce = switch.data.chain(Bytes::new()); let response = switch .plugin @@ -140,7 +159,7 @@ impl MySqlConnection { break; } - // plugin signaled to continue authentication + // plugin signaled to continue authentication } else { return Err(err_protocol!( "unexpected packet 0x{:02x} during authentication", @@ -151,11 +170,14 @@ impl MySqlConnection { } } - Ok(Self { - stream, - transaction_depth: 0, - cache_statement: StatementCache::new(options.statement_cache_capacity), - log_settings: options.log_settings.clone(), - }) + Ok(stream) + } +} + +impl<'a> WithSocket for DoHandshake<'a> { + type Output = BoxFuture<'a, Result>; + + fn with_socket(self, socket: S) -> Self::Output { + Box::pin(self.do_handshake(socket)) } } diff --git a/sqlx-core/src/mysql/connection/executor.rs b/sqlx-mysql/src/connection/executor.rs similarity index 96% rename from sqlx-core/src/mysql/connection/executor.rs rename to sqlx-mysql/src/connection/executor.rs index 8d73794f57..b668c67396 100644 --- a/sqlx-core/src/mysql/connection/executor.rs +++ b/sqlx-mysql/src/connection/executor.rs @@ -1,22 +1,22 @@ use super::MySqlStream; +use crate::connection::stream::Waiting; use crate::describe::Describe; use crate::error::Error; use crate::executor::{Execute, Executor}; use crate::ext::ustr::UStr; +use crate::io::MySqlBufExt; use crate::logger::QueryLogger; -use crate::mysql::connection::stream::Waiting; -use crate::mysql::io::MySqlBufExt; -use crate::mysql::protocol::response::Status; -use crate::mysql::protocol::statement::{ +use crate::protocol::response::Status; +use crate::protocol::statement::{ BinaryRow, Execute as StatementExecute, Prepare, PrepareOk, StmtClose, }; -use crate::mysql::protocol::text::{ColumnDefinition, ColumnFlags, Query, TextRow}; -use crate::mysql::statement::{MySqlStatement, MySqlStatementMetadata}; -use crate::mysql::{ +use crate::protocol::text::{ColumnDefinition, ColumnFlags, Query, TextRow}; +use crate::statement::{MySqlStatement, MySqlStatementMetadata}; +use crate::HashMap; +use crate::{ MySql, MySqlArguments, MySqlColumn, MySqlConnection, MySqlQueryResult, MySqlRow, MySqlTypeInfo, MySqlValueFormat, }; -use crate::HashMap; use either::Either; use futures_core::future::BoxFuture; use futures_core::stream::BoxStream; @@ -83,7 +83,7 @@ impl MySqlConnection { } #[allow(clippy::needless_lifetimes)] - async fn run<'e, 'c: 'e, 'q: 'e>( + pub(crate) async fn run<'e, 'c: 'e, 'q: 'e>( &'c mut self, sql: &'q str, arguments: Option, diff --git a/sqlx-core/src/mysql/connection/mod.rs b/sqlx-mysql/src/connection/mod.rs similarity index 90% rename from sqlx-core/src/mysql/connection/mod.rs rename to sqlx-mysql/src/connection/mod.rs index 1f87eaa918..98144fc0d6 100644 --- a/sqlx-core/src/mysql/connection/mod.rs +++ b/sqlx-mysql/src/connection/mod.rs @@ -1,14 +1,17 @@ +use std::fmt::{self, Debug, Formatter}; + +use futures_core::future::BoxFuture; +use futures_util::FutureExt; +pub(crate) use sqlx_core::connection::*; +pub(crate) use stream::{MySqlStream, Waiting}; + use crate::common::StatementCache; -use crate::connection::{Connection, LogSettings}; use crate::error::Error; -use crate::mysql::protocol::statement::StmtClose; -use crate::mysql::protocol::text::{Ping, Quit}; -use crate::mysql::statement::MySqlStatementMetadata; -use crate::mysql::{MySql, MySqlConnectOptions}; +use crate::protocol::statement::StmtClose; +use crate::protocol::text::{Ping, Quit}; +use crate::statement::MySqlStatementMetadata; use crate::transaction::Transaction; -use futures_core::future::BoxFuture; -use futures_util::FutureExt; -use std::fmt::{self, Debug, Formatter}; +use crate::{MySql, MySqlConnectOptions}; mod auth; mod establish; @@ -16,8 +19,6 @@ mod executor; mod stream; mod tls; -pub(crate) use stream::{MySqlStream, Waiting}; - const MAX_PACKET_SIZE: u32 = 1024; /// A connection to a MySQL database. @@ -98,7 +99,7 @@ impl Connection for MySqlConnection { #[doc(hidden)] fn should_flush(&self) -> bool { - !self.stream.wbuf.is_empty() + !self.stream.write_buffer().is_empty() } fn begin(&mut self) -> BoxFuture<'_, Result, Error>> diff --git a/sqlx-core/src/mysql/connection/stream.rs b/sqlx-mysql/src/connection/stream.rs similarity index 75% rename from sqlx-core/src/mysql/connection/stream.rs rename to sqlx-mysql/src/connection/stream.rs index dd9a1235b8..e42c15d9e5 100644 --- a/sqlx-core/src/mysql/connection/stream.rs +++ b/sqlx-mysql/src/connection/stream.rs @@ -3,23 +3,25 @@ use std::ops::{Deref, DerefMut}; use bytes::{Buf, Bytes}; +use crate::collation::{CharSet, Collation}; use crate::error::Error; -use crate::io::{BufStream, Decode, Encode}; -use crate::mysql::collation::{CharSet, Collation}; -use crate::mysql::io::MySqlBufExt; -use crate::mysql::protocol::response::{EofPacket, ErrPacket, OkPacket, Status}; -use crate::mysql::protocol::{Capabilities, Packet}; -use crate::mysql::{MySqlConnectOptions, MySqlDatabaseError}; -use crate::net::{MaybeTlsStream, Socket}; - -pub struct MySqlStream { - stream: BufStream>, +use crate::io::MySqlBufExt; +use crate::io::{Decode, Encode}; +use crate::net::{BufferedSocket, Socket}; +use crate::protocol::response::{EofPacket, ErrPacket, OkPacket, Status}; +use crate::protocol::{Capabilities, Packet}; +use crate::{MySqlConnectOptions, MySqlDatabaseError}; + +pub struct MySqlStream> { + // Wrapping the socket in `Box` allows us to unsize in-place. + pub(crate) socket: BufferedSocket, pub(crate) server_version: (u16, u16, u16), pub(super) capabilities: Capabilities, pub(crate) sequence_id: u8, pub(crate) waiting: VecDeque, pub(crate) charset: CharSet, pub(crate) collation: Collation, + pub(crate) is_tls: bool, } #[derive(Debug, PartialEq, Eq)] @@ -31,21 +33,13 @@ pub(crate) enum Waiting { Row, } -impl MySqlStream { - pub(super) async fn connect(options: &MySqlConnectOptions) -> Result { - let charset: CharSet = options.charset.parse()?; - let collation: Collation = options - .collation - .as_deref() - .map(|collation| collation.parse()) - .transpose()? - .unwrap_or_else(|| charset.default_collation()); - - let socket = match options.socket { - Some(ref path) => Socket::connect_uds(path).await?, - None => Socket::connect_tcp(&options.host, options.port).await?, - }; - +impl MySqlStream { + pub(crate) fn with_socket( + charset: CharSet, + collation: Collation, + options: &MySqlConnectOptions, + socket: S, + ) -> Self { let mut capabilities = Capabilities::PROTOCOL_41 | Capabilities::IGNORE_SPACE | Capabilities::DEPRECATE_EOF @@ -63,20 +57,21 @@ impl MySqlStream { capabilities |= Capabilities::CONNECT_WITH_DB; } - Ok(Self { + Self { waiting: VecDeque::new(), capabilities, server_version: (0, 0, 0), sequence_id: 0, collation, charset, - stream: BufStream::new(MaybeTlsStream::Raw(socket)), - }) + socket: BufferedSocket::new(socket), + is_tls: false, + } } pub(crate) async fn wait_until_ready(&mut self) -> Result<(), Error> { - if !self.stream.wbuf.is_empty() { - self.stream.flush().await?; + if !self.socket.write_buffer().is_empty() { + self.socket.flush().await?; } while !self.waiting.is_empty() { @@ -119,14 +114,15 @@ impl MySqlStream { { self.sequence_id = 0; self.write_packet(payload); - self.flush().await + self.flush().await?; + Ok(()) } pub(crate) fn write_packet<'en, T>(&mut self, payload: T) where T: Encode<'en, Capabilities>, { - self.stream + self.socket .write_with(Packet(payload), (self.capabilities, &mut self.sequence_id)); } @@ -136,14 +132,14 @@ impl MySqlStream { // https://dev.mysql.com/doc/dev/mysql-server/8.0.12/page_protocol_basic_packets.html // https://mariadb.com/kb/en/library/0-packet/#standard-packet - let mut header: Bytes = self.stream.read(4).await?; + let mut header: Bytes = self.socket.read(4).await?; let packet_size = header.get_uint_le(3) as usize; let sequence_id = header.get_u8(); self.sequence_id = sequence_id.wrapping_add(1); - let payload: Bytes = self.stream.read(packet_size).await?; + let payload: Bytes = self.socket.read(packet_size).await?; // TODO: packet compression // TODO: packet joining @@ -195,18 +191,31 @@ impl MySqlStream { Ok(()) } + + pub fn boxed_socket(self) -> MySqlStream { + MySqlStream { + socket: self.socket.boxed(), + server_version: self.server_version, + capabilities: self.capabilities, + sequence_id: self.sequence_id, + waiting: self.waiting, + charset: self.charset, + collation: self.collation, + is_tls: self.is_tls, + } + } } -impl Deref for MySqlStream { - type Target = BufStream>; +impl Deref for MySqlStream { + type Target = BufferedSocket; fn deref(&self) -> &Self::Target { - &self.stream + &self.socket } } -impl DerefMut for MySqlStream { +impl DerefMut for MySqlStream { fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.stream + &mut self.socket } } diff --git a/sqlx-mysql/src/connection/tls.rs b/sqlx-mysql/src/connection/tls.rs new file mode 100644 index 0000000000..883fbcadad --- /dev/null +++ b/sqlx-mysql/src/connection/tls.rs @@ -0,0 +1,107 @@ +use crate::collation::{CharSet, Collation}; +use crate::connection::{MySqlStream, Waiting}; +use crate::error::Error; +use crate::net::tls::TlsConfig; +use crate::net::{tls, BufferedSocket, Socket, WithSocket}; +use crate::protocol::connect::SslRequest; +use crate::protocol::Capabilities; +use crate::{MySqlConnectOptions, MySqlSslMode}; +use std::collections::VecDeque; + +struct MapStream { + server_version: (u16, u16, u16), + capabilities: Capabilities, + sequence_id: u8, + waiting: VecDeque, + charset: CharSet, + collation: Collation, +} + +pub(super) async fn maybe_upgrade( + mut stream: MySqlStream, + options: &MySqlConnectOptions, +) -> Result { + let server_supports_tls = stream.capabilities.contains(Capabilities::SSL); + + if matches!(options.ssl_mode, MySqlSslMode::Disabled) || !tls::available() { + // remove the SSL capability if SSL has been explicitly disabled + stream.capabilities.remove(Capabilities::SSL); + } + + // https://www.postgresql.org/docs/12/libpq-ssl.html#LIBPQ-SSL-SSLMODE-STATEMENTS + match options.ssl_mode { + MySqlSslMode::Disabled => return Ok(stream.boxed_socket()), + + MySqlSslMode::Preferred => { + if !tls::available() { + // Client doesn't support TLS + log::debug!("not performing TLS upgrade: TLS support not compiled in"); + return Ok(stream.boxed_socket()); + } + + if !server_supports_tls { + // Server doesn't support TLS + log::debug!("not performing TLS upgrade: unsupported by server"); + return Ok(stream.boxed_socket()); + } + } + + MySqlSslMode::Required | MySqlSslMode::VerifyIdentity | MySqlSslMode::VerifyCa => { + tls::error_if_unavailable()?; + + if !server_supports_tls { + // upgrade failed, die + return Err(Error::Tls("server does not support TLS".into())); + } + } + } + + let tls_config = TlsConfig { + accept_invalid_certs: !matches!( + options.ssl_mode, + MySqlSslMode::VerifyCa | MySqlSslMode::VerifyIdentity + ), + accept_invalid_hostnames: !matches!(options.ssl_mode, MySqlSslMode::VerifyIdentity), + hostname: &options.host, + root_cert_path: options.ssl_ca.as_ref(), + }; + + // Request TLS upgrade + stream.write_packet(SslRequest { + max_packet_size: super::MAX_PACKET_SIZE, + collation: stream.collation as u8, + }); + + stream.flush().await?; + + tls::handshake( + stream.socket.into_inner(), + tls_config, + MapStream { + server_version: stream.server_version, + capabilities: stream.capabilities, + sequence_id: stream.sequence_id, + waiting: stream.waiting, + charset: stream.charset, + collation: stream.collation, + }, + ) + .await +} + +impl WithSocket for MapStream { + type Output = MySqlStream; + + fn with_socket(self, socket: S) -> Self::Output { + MySqlStream { + socket: BufferedSocket::new(Box::new(socket)), + server_version: self.server_version, + capabilities: self.capabilities, + sequence_id: self.sequence_id, + waiting: self.waiting, + charset: self.charset, + collation: self.collation, + is_tls: true, + } + } +} diff --git a/sqlx-core/src/mysql/database.rs b/sqlx-mysql/src/database.rs similarity index 75% rename from sqlx-core/src/mysql/database.rs rename to sqlx-mysql/src/database.rs index 8e70ce5363..08040d2783 100644 --- a/sqlx-core/src/mysql/database.rs +++ b/sqlx-mysql/src/database.rs @@ -1,9 +1,11 @@ -use crate::database::{Database, HasArguments, HasStatement, HasStatementCache, HasValueRef}; -use crate::mysql::value::{MySqlValue, MySqlValueRef}; -use crate::mysql::{ +use crate::value::{MySqlValue, MySqlValueRef}; +use crate::{ MySqlArguments, MySqlColumn, MySqlConnection, MySqlQueryResult, MySqlRow, MySqlStatement, MySqlTransactionManager, MySqlTypeInfo, }; +pub(crate) use sqlx_core::database::{ + Database, HasArguments, HasStatement, HasStatementCache, HasValueRef, +}; /// MySQL database driver. #[derive(Debug)] @@ -23,6 +25,10 @@ impl Database for MySql { type TypeInfo = MySqlTypeInfo; type Value = MySqlValue; + + const NAME: &'static str = "MySQL"; + + const URL_SCHEMES: &'static [&'static str] = &["mysql", "mariadb"]; } impl<'r> HasValueRef<'r> for MySql { diff --git a/sqlx-core/src/mysql/error.rs b/sqlx-mysql/src/error.rs similarity index 81% rename from sqlx-core/src/mysql/error.rs rename to sqlx-mysql/src/error.rs index 3f769c7bc0..0717b49f76 100644 --- a/sqlx-core/src/mysql/error.rs +++ b/sqlx-mysql/src/error.rs @@ -1,9 +1,11 @@ -use std::error::Error; +use std::error::Error as StdError; use std::fmt::{self, Debug, Display, Formatter}; -use crate::error::DatabaseError; -use crate::mysql::protocol::response::ErrPacket; -use smallvec::alloc::borrow::Cow; +use crate::protocol::response::ErrPacket; + +use std::borrow::Cow; + +pub(crate) use sqlx_core::error::*; /// An error returned from the MySQL database. pub struct MySqlDatabaseError(pub(super) ErrPacket); @@ -49,7 +51,7 @@ impl Display for MySqlDatabaseError { } } -impl Error for MySqlDatabaseError {} +impl StdError for MySqlDatabaseError {} impl DatabaseError for MySqlDatabaseError { #[inline] @@ -63,17 +65,17 @@ impl DatabaseError for MySqlDatabaseError { } #[doc(hidden)] - fn as_error(&self) -> &(dyn Error + Send + Sync + 'static) { + fn as_error(&self) -> &(dyn StdError + Send + Sync + 'static) { self } #[doc(hidden)] - fn as_error_mut(&mut self) -> &mut (dyn Error + Send + Sync + 'static) { + fn as_error_mut(&mut self) -> &mut (dyn StdError + Send + Sync + 'static) { self } #[doc(hidden)] - fn into_error(self: Box) -> Box { + fn into_error(self: Box) -> Box { self } } diff --git a/sqlx-core/src/mysql/io/buf.rs b/sqlx-mysql/src/io/buf.rs similarity index 100% rename from sqlx-core/src/mysql/io/buf.rs rename to sqlx-mysql/src/io/buf.rs diff --git a/sqlx-core/src/mysql/io/buf_mut.rs b/sqlx-mysql/src/io/buf_mut.rs similarity index 100% rename from sqlx-core/src/mysql/io/buf_mut.rs rename to sqlx-mysql/src/io/buf_mut.rs diff --git a/sqlx-core/src/mysql/io/mod.rs b/sqlx-mysql/src/io/mod.rs similarity index 70% rename from sqlx-core/src/mysql/io/mod.rs rename to sqlx-mysql/src/io/mod.rs index fafc91454e..cf48e8ab76 100644 --- a/sqlx-core/src/mysql/io/mod.rs +++ b/sqlx-mysql/src/io/mod.rs @@ -3,3 +3,5 @@ mod buf_mut; pub use buf::MySqlBufExt; pub use buf_mut::MySqlBufMutExt; + +pub(crate) use sqlx_core::io::*; diff --git a/sqlx-core/src/mysql/mod.rs b/sqlx-mysql/src/lib.rs similarity index 90% rename from sqlx-core/src/mysql/mod.rs rename to sqlx-mysql/src/lib.rs index 874b893bda..a8582f07eb 100644 --- a/sqlx-core/src/mysql/mod.rs +++ b/sqlx-mysql/src/lib.rs @@ -1,7 +1,15 @@ //! **MySQL** database driver. +#[macro_use] +extern crate sqlx_core; + use crate::executor::Executor; +pub(crate) use sqlx_core::driver_prelude::*; + +#[cfg(feature = "any")] +pub mod any; + mod arguments; mod collation; mod column; @@ -50,12 +58,9 @@ impl<'c, T: Executor<'c, Database = MySql>> MySqlExecutor<'c> for T {} // NOTE: required due to the lack of lazy normalization impl_into_arguments_for_arguments!(MySqlArguments); -impl_executor_for_pool_connection!(MySql, MySqlConnection, MySqlRow); -impl_executor_for_transaction!(MySql, MySqlRow); impl_acquire!(MySql, MySqlConnection); impl_column_index_for_row!(MySqlRow); impl_column_index_for_statement!(MySqlStatement); -impl_into_maybe_pool!(MySql, MySqlConnection); // required because some databases have a different handling of NULL impl_encode_for_option!(MySql); diff --git a/sqlx-core/src/mysql/migrate.rs b/sqlx-mysql/src/migrate.rs similarity index 86% rename from sqlx-core/src/mysql/migrate.rs rename to sqlx-mysql/src/migrate.rs index d17fbfb59c..0e08226ec5 100644 --- a/sqlx-core/src/mysql/migrate.rs +++ b/sqlx-mysql/src/migrate.rs @@ -1,17 +1,17 @@ +use std::str::FromStr; +use std::time::Duration; +use std::time::Instant; + +use futures_core::future::BoxFuture; +pub(crate) use sqlx_core::migrate::*; + use crate::connection::{ConnectOptions, Connection}; use crate::error::Error; use crate::executor::Executor; -use crate::migrate::MigrateError; -use crate::migrate::{AppliedMigration, Migration}; -use crate::migrate::{Migrate, MigrateDatabase}; -use crate::mysql::{MySql, MySqlConnectOptions, MySqlConnection}; use crate::query::query; use crate::query_as::query_as; use crate::query_scalar::query_scalar; -use futures_core::future::BoxFuture; -use std::str::FromStr; -use std::time::Duration; -use std::time::Instant; +use crate::{MySql, MySqlConnectOptions, MySqlConnection}; fn parse_for_maintenance(url: &str) -> Result<(MySqlConnectOptions, String), Error> { let mut options = MySqlConnectOptions::from_str(url)?; @@ -96,19 +96,6 @@ CREATE TABLE IF NOT EXISTS _sqlx_migrations ( }) } - fn version(&mut self) -> BoxFuture<'_, Result, MigrateError>> { - Box::pin(async move { - // language=SQL - let row = query_as( - "SELECT version, NOT success FROM _sqlx_migrations ORDER BY version DESC LIMIT 1", - ) - .fetch_optional(self) - .await?; - - Ok(row) - }) - } - fn dirty_version(&mut self) -> BoxFuture<'_, Result, MigrateError>> { Box::pin(async move { // language=SQL @@ -180,30 +167,6 @@ CREATE TABLE IF NOT EXISTS _sqlx_migrations ( }) } - fn validate<'e: 'm, 'm>( - &'e mut self, - migration: &'m Migration, - ) -> BoxFuture<'m, Result<(), MigrateError>> { - Box::pin(async move { - // language=SQL - let checksum: Option> = - query_scalar("SELECT checksum FROM _sqlx_migrations WHERE version = ?") - .bind(migration.version) - .fetch_optional(self) - .await?; - - if let Some(checksum) = checksum { - return if checksum == &*migration.checksum { - Ok(()) - } else { - Err(MigrateError::VersionMismatch(migration.version)) - }; - } else { - Err(MigrateError::VersionMissing(migration.version)) - } - }) - } - fn apply<'e: 'm, 'm>( &'e mut self, migration: &'m Migration, @@ -233,7 +196,7 @@ CREATE TABLE IF NOT EXISTS _sqlx_migrations ( .bind(migration.version) .bind(&*migration.description) .bind(&*migration.checksum) - .execute(&mut tx) + .execute(&mut *tx) .await?; let _ = tx.execute(&*migration.sql).await?; @@ -247,7 +210,7 @@ CREATE TABLE IF NOT EXISTS _sqlx_migrations ( "#, ) .bind(migration.version) - .execute(&mut tx) + .execute(&mut *tx) .await?; tx.commit().await?; @@ -299,7 +262,7 @@ CREATE TABLE IF NOT EXISTS _sqlx_migrations ( "#, ) .bind(migration.version) - .execute(&mut tx) + .execute(&mut *tx) .await?; tx.execute(&*migration.sql).await?; @@ -307,7 +270,7 @@ CREATE TABLE IF NOT EXISTS _sqlx_migrations ( // language=SQL let _ = query(r#"DELETE FROM _sqlx_migrations WHERE version = ?"#) .bind(migration.version) - .execute(&mut tx) + .execute(&mut *tx) .await?; tx.commit().await?; diff --git a/sqlx-core/src/mysql/options/connect.rs b/sqlx-mysql/src/options/connect.rs similarity index 94% rename from sqlx-core/src/mysql/options/connect.rs rename to sqlx-mysql/src/options/connect.rs index fd59dbbdf1..0e07e8331c 100644 --- a/sqlx-core/src/mysql/options/connect.rs +++ b/sqlx-mysql/src/options/connect.rs @@ -1,14 +1,19 @@ use crate::connection::ConnectOptions; use crate::error::Error; use crate::executor::Executor; -use crate::mysql::{MySqlConnectOptions, MySqlConnection}; +use crate::{MySqlConnectOptions, MySqlConnection}; use futures_core::future::BoxFuture; use log::LevelFilter; +use sqlx_core::Url; use std::time::Duration; impl ConnectOptions for MySqlConnectOptions { type Connection = MySqlConnection; + fn from_url(url: &Url) -> Result { + Self::parse_from_url(url) + } + fn connect(&self) -> BoxFuture<'_, Result> where Self::Connection: Sized, diff --git a/sqlx-core/src/mysql/options/mod.rs b/sqlx-mysql/src/options/mod.rs similarity index 97% rename from sqlx-core/src/mysql/options/mod.rs rename to sqlx-mysql/src/options/mod.rs index 5d152c3869..d0959579d1 100644 --- a/sqlx-core/src/mysql/options/mod.rs +++ b/sqlx-mysql/src/options/mod.rs @@ -4,7 +4,7 @@ mod connect; mod parse; mod ssl_mode; -use crate::{connection::LogSettings, net::CertificateInput}; +use crate::{connection::LogSettings, net::tls::CertificateInput}; pub use ssl_mode::MySqlSslMode; /// Options and flags which can be used to configure a MySQL connection. @@ -35,8 +35,8 @@ pub use ssl_mode::MySqlSslMode; /// # use sqlx_core::mysql::{MySqlConnectOptions, MySqlConnection, MySqlSslMode}; /// # /// # fn main() { -/// # #[cfg(feature = "_rt-async-std")] -/// # sqlx_rt::async_std::task::block_on::<_, Result<(), Error>>(async move { +/// # #[cfg(feature = "_rt")] +/// # sqlx::__rt::test_block_on(async move { /// // URL connection string /// let conn = MySqlConnection::connect("mysql://root:password@localhost/db").await?; /// @@ -47,7 +47,7 @@ pub use ssl_mode::MySqlSslMode; /// .password("password") /// .database("db") /// .connect().await?; -/// # Ok(()) +/// # Result::<(), Error>::Ok(()) /// # }).unwrap(); /// # } /// ``` diff --git a/sqlx-core/src/mysql/options/parse.rs b/sqlx-mysql/src/options/parse.rs similarity index 92% rename from sqlx-core/src/mysql/options/parse.rs rename to sqlx-mysql/src/options/parse.rs index 0ce32b5d71..854a95c427 100644 --- a/sqlx-core/src/mysql/options/parse.rs +++ b/sqlx-mysql/src/options/parse.rs @@ -1,14 +1,14 @@ -use crate::error::Error; -use crate::mysql::MySqlConnectOptions; -use percent_encoding::percent_decode_str; use std::str::FromStr; -use url::Url; -impl FromStr for MySqlConnectOptions { - type Err = Error; +use percent_encoding::percent_decode_str; +use sqlx_core::Url; - fn from_str(s: &str) -> Result { - let url: Url = s.parse().map_err(Error::config)?; +use crate::error::Error; + +use super::MySqlConnectOptions; + +impl MySqlConnectOptions { + pub(crate) fn parse_from_url(url: &Url) -> Result { let mut options = Self::new(); if let Some(host) = url.host_str() { @@ -76,6 +76,15 @@ impl FromStr for MySqlConnectOptions { } } +impl FromStr for MySqlConnectOptions { + type Err = Error; + + fn from_str(s: &str) -> Result { + let url: Url = s.parse().map_err(Error::config)?; + Self::parse_from_url(&url) + } +} + #[test] fn it_parses_username_with_at_sign_correctly() { let url = "mysql://user@hostname:password@hostname:5432/database"; diff --git a/sqlx-core/src/mysql/options/ssl_mode.rs b/sqlx-mysql/src/options/ssl_mode.rs similarity index 100% rename from sqlx-core/src/mysql/options/ssl_mode.rs rename to sqlx-mysql/src/options/ssl_mode.rs diff --git a/sqlx-core/src/mysql/protocol/auth.rs b/sqlx-mysql/src/protocol/auth.rs similarity index 100% rename from sqlx-core/src/mysql/protocol/auth.rs rename to sqlx-mysql/src/protocol/auth.rs diff --git a/sqlx-core/src/mysql/protocol/capabilities.rs b/sqlx-mysql/src/protocol/capabilities.rs similarity index 100% rename from sqlx-core/src/mysql/protocol/capabilities.rs rename to sqlx-mysql/src/protocol/capabilities.rs diff --git a/sqlx-core/src/mysql/protocol/connect/auth_switch.rs b/sqlx-mysql/src/protocol/connect/auth_switch.rs similarity index 93% rename from sqlx-core/src/mysql/protocol/connect/auth_switch.rs rename to sqlx-mysql/src/protocol/connect/auth_switch.rs index 757411f56b..0c25a42500 100644 --- a/sqlx-core/src/mysql/protocol/connect/auth_switch.rs +++ b/sqlx-mysql/src/protocol/connect/auth_switch.rs @@ -3,8 +3,8 @@ use bytes::{Buf, Bytes}; use crate::error::Error; use crate::io::Encode; use crate::io::{BufExt, Decode}; -use crate::mysql::protocol::auth::AuthPlugin; -use crate::mysql::protocol::Capabilities; +use crate::protocol::auth::AuthPlugin; +use crate::protocol::Capabilities; // https://dev.mysql.com/doc/dev/mysql-server/8.0.12/page_protocol_connection_phase_packets_protocol_auth_switch_request.html diff --git a/sqlx-core/src/mysql/protocol/connect/handshake.rs b/sqlx-mysql/src/protocol/connect/handshake.rs similarity index 98% rename from sqlx-core/src/mysql/protocol/connect/handshake.rs rename to sqlx-mysql/src/protocol/connect/handshake.rs index e0a4497bbd..3904d9a94a 100644 --- a/sqlx-core/src/mysql/protocol/connect/handshake.rs +++ b/sqlx-mysql/src/protocol/connect/handshake.rs @@ -3,9 +3,9 @@ use bytes::{Buf, Bytes}; use crate::error::Error; use crate::io::{BufExt, Decode}; -use crate::mysql::protocol::auth::AuthPlugin; -use crate::mysql::protocol::response::Status; -use crate::mysql::protocol::Capabilities; +use crate::protocol::auth::AuthPlugin; +use crate::protocol::response::Status; +use crate::protocol::Capabilities; // https://dev.mysql.com/doc/internals/en/connection-phase-packets.html#packet-Protocol::Handshake // https://mariadb.com/kb/en/connection/#initial-handshake-packet diff --git a/sqlx-core/src/mysql/protocol/connect/handshake_response.rs b/sqlx-mysql/src/protocol/connect/handshake_response.rs similarity index 92% rename from sqlx-core/src/mysql/protocol/connect/handshake_response.rs rename to sqlx-mysql/src/protocol/connect/handshake_response.rs index 22933006e3..c5d982e441 100644 --- a/sqlx-core/src/mysql/protocol/connect/handshake_response.rs +++ b/sqlx-mysql/src/protocol/connect/handshake_response.rs @@ -1,8 +1,8 @@ +use crate::io::MySqlBufMutExt; use crate::io::{BufMutExt, Encode}; -use crate::mysql::io::MySqlBufMutExt; -use crate::mysql::protocol::auth::AuthPlugin; -use crate::mysql::protocol::connect::ssl_request::SslRequest; -use crate::mysql::protocol::Capabilities; +use crate::protocol::auth::AuthPlugin; +use crate::protocol::connect::ssl_request::SslRequest; +use crate::protocol::Capabilities; // https://dev.mysql.com/doc/internals/en/connection-phase-packets.html#packet-Protocol::HandshakeResponse // https://mariadb.com/kb/en/connection/#client-handshake-response diff --git a/sqlx-core/src/mysql/protocol/connect/mod.rs b/sqlx-mysql/src/protocol/connect/mod.rs similarity index 100% rename from sqlx-core/src/mysql/protocol/connect/mod.rs rename to sqlx-mysql/src/protocol/connect/mod.rs diff --git a/sqlx-core/src/mysql/protocol/connect/ssl_request.rs b/sqlx-mysql/src/protocol/connect/ssl_request.rs similarity index 95% rename from sqlx-core/src/mysql/protocol/connect/ssl_request.rs rename to sqlx-mysql/src/protocol/connect/ssl_request.rs index 5f0c2d8946..c7d501db24 100644 --- a/sqlx-core/src/mysql/protocol/connect/ssl_request.rs +++ b/sqlx-mysql/src/protocol/connect/ssl_request.rs @@ -1,5 +1,5 @@ use crate::io::Encode; -use crate::mysql::protocol::Capabilities; +use crate::protocol::Capabilities; // https://dev.mysql.com/doc/dev/mysql-server/8.0.12/page_protocol_connection_phase_packets_protocol_handshake_response.html // https://dev.mysql.com/doc/internals/en/connection-phase-packets.html#packet-Protocol::SSLRequest diff --git a/sqlx-core/src/mysql/protocol/mod.rs b/sqlx-mysql/src/protocol/mod.rs similarity index 100% rename from sqlx-core/src/mysql/protocol/mod.rs rename to sqlx-mysql/src/protocol/mod.rs diff --git a/sqlx-core/src/mysql/protocol/packet.rs b/sqlx-mysql/src/protocol/packet.rs similarity index 95% rename from sqlx-core/src/mysql/protocol/packet.rs rename to sqlx-mysql/src/protocol/packet.rs index 8c49fcc335..2d40ba8e77 100644 --- a/sqlx-core/src/mysql/protocol/packet.rs +++ b/sqlx-mysql/src/protocol/packet.rs @@ -4,8 +4,8 @@ use bytes::Bytes; use crate::error::Error; use crate::io::{Decode, Encode}; -use crate::mysql::protocol::response::{EofPacket, OkPacket}; -use crate::mysql::protocol::Capabilities; +use crate::protocol::response::{EofPacket, OkPacket}; +use crate::protocol::Capabilities; #[derive(Debug)] pub struct Packet(pub(crate) T); diff --git a/sqlx-core/src/mysql/protocol/response/eof.rs b/sqlx-mysql/src/protocol/response/eof.rs similarity index 90% rename from sqlx-core/src/mysql/protocol/response/eof.rs rename to sqlx-mysql/src/protocol/response/eof.rs index 756c370a62..5dc9f104c7 100644 --- a/sqlx-core/src/mysql/protocol/response/eof.rs +++ b/sqlx-mysql/src/protocol/response/eof.rs @@ -2,8 +2,8 @@ use bytes::{Buf, Bytes}; use crate::error::Error; use crate::io::Decode; -use crate::mysql::protocol::response::Status; -use crate::mysql::protocol::Capabilities; +use crate::protocol::response::Status; +use crate::protocol::Capabilities; /// Marks the end of a result set, returning status and warnings. /// diff --git a/sqlx-core/src/mysql/protocol/response/err.rs b/sqlx-mysql/src/protocol/response/err.rs similarity index 98% rename from sqlx-core/src/mysql/protocol/response/err.rs rename to sqlx-mysql/src/protocol/response/err.rs index e365a8e0a7..344247b888 100644 --- a/sqlx-core/src/mysql/protocol/response/err.rs +++ b/sqlx-mysql/src/protocol/response/err.rs @@ -2,7 +2,7 @@ use bytes::{Buf, Bytes}; use crate::error::Error; use crate::io::{BufExt, Decode}; -use crate::mysql::protocol::Capabilities; +use crate::protocol::Capabilities; // https://dev.mysql.com/doc/dev/mysql-server/8.0.12/page_protocol_basic_err_packet.html // https://mariadb.com/kb/en/err_packet/ diff --git a/sqlx-core/src/mysql/protocol/response/mod.rs b/sqlx-mysql/src/protocol/response/mod.rs similarity index 100% rename from sqlx-core/src/mysql/protocol/response/mod.rs rename to sqlx-mysql/src/protocol/response/mod.rs diff --git a/sqlx-core/src/mysql/protocol/response/ok.rs b/sqlx-mysql/src/protocol/response/ok.rs similarity index 94% rename from sqlx-core/src/mysql/protocol/response/ok.rs rename to sqlx-mysql/src/protocol/response/ok.rs index 8ad607b79a..0eada2e8ff 100644 --- a/sqlx-core/src/mysql/protocol/response/ok.rs +++ b/sqlx-mysql/src/protocol/response/ok.rs @@ -2,8 +2,8 @@ use bytes::{Buf, Bytes}; use crate::error::Error; use crate::io::Decode; -use crate::mysql::io::MySqlBufExt; -use crate::mysql::protocol::response::Status; +use crate::io::MySqlBufExt; +use crate::protocol::response::Status; /// Indicates successful completion of a previous command sent by the client. #[derive(Debug)] diff --git a/sqlx-core/src/mysql/protocol/response/status.rs b/sqlx-mysql/src/protocol/response/status.rs similarity index 100% rename from sqlx-core/src/mysql/protocol/response/status.rs rename to sqlx-mysql/src/protocol/response/status.rs diff --git a/sqlx-core/src/mysql/protocol/row.rs b/sqlx-mysql/src/protocol/row.rs similarity index 100% rename from sqlx-core/src/mysql/protocol/row.rs rename to sqlx-mysql/src/protocol/row.rs diff --git a/sqlx-core/src/mysql/protocol/statement/execute.rs b/sqlx-mysql/src/protocol/statement/execute.rs similarity index 89% rename from sqlx-core/src/mysql/protocol/statement/execute.rs rename to sqlx-mysql/src/protocol/statement/execute.rs index 47d7e2089a..8bd367e25e 100644 --- a/sqlx-core/src/mysql/protocol/statement/execute.rs +++ b/sqlx-mysql/src/protocol/statement/execute.rs @@ -1,7 +1,7 @@ use crate::io::Encode; -use crate::mysql::protocol::text::ColumnFlags; -use crate::mysql::protocol::Capabilities; -use crate::mysql::MySqlArguments; +use crate::protocol::text::ColumnFlags; +use crate::protocol::Capabilities; +use crate::MySqlArguments; // https://dev.mysql.com/doc/dev/mysql-server/8.0.12/page_protocol_com_stmt_execute.html diff --git a/sqlx-core/src/mysql/protocol/statement/mod.rs b/sqlx-mysql/src/protocol/statement/mod.rs similarity index 100% rename from sqlx-core/src/mysql/protocol/statement/mod.rs rename to sqlx-mysql/src/protocol/statement/mod.rs diff --git a/sqlx-core/src/mysql/protocol/statement/prepare.rs b/sqlx-mysql/src/protocol/statement/prepare.rs similarity index 89% rename from sqlx-core/src/mysql/protocol/statement/prepare.rs rename to sqlx-mysql/src/protocol/statement/prepare.rs index 325b29fafd..106e177475 100644 --- a/sqlx-core/src/mysql/protocol/statement/prepare.rs +++ b/sqlx-mysql/src/protocol/statement/prepare.rs @@ -1,5 +1,5 @@ use crate::io::Encode; -use crate::mysql::protocol::Capabilities; +use crate::protocol::Capabilities; // https://dev.mysql.com/doc/internals/en/com-stmt-prepare.html#packet-COM_STMT_PREPARE diff --git a/sqlx-core/src/mysql/protocol/statement/prepare_ok.rs b/sqlx-mysql/src/protocol/statement/prepare_ok.rs similarity index 96% rename from sqlx-core/src/mysql/protocol/statement/prepare_ok.rs rename to sqlx-mysql/src/protocol/statement/prepare_ok.rs index 0aae29098a..1048a6bbd9 100644 --- a/sqlx-core/src/mysql/protocol/statement/prepare_ok.rs +++ b/sqlx-mysql/src/protocol/statement/prepare_ok.rs @@ -2,7 +2,7 @@ use bytes::{Buf, Bytes}; use crate::error::Error; use crate::io::Decode; -use crate::mysql::protocol::Capabilities; +use crate::protocol::Capabilities; // https://dev.mysql.com/doc/internals/en/com-stmt-prepare-response.html#packet-COM_STMT_PREPARE_OK diff --git a/sqlx-core/src/mysql/protocol/statement/row.rs b/sqlx-mysql/src/protocol/statement/row.rs similarity index 95% rename from sqlx-core/src/mysql/protocol/statement/row.rs rename to sqlx-mysql/src/protocol/statement/row.rs index b3760de9a3..bf518f491f 100644 --- a/sqlx-core/src/mysql/protocol/statement/row.rs +++ b/sqlx-mysql/src/protocol/statement/row.rs @@ -1,11 +1,11 @@ use bytes::{Buf, Bytes}; use crate::error::Error; +use crate::io::MySqlBufExt; use crate::io::{BufExt, Decode}; -use crate::mysql::io::MySqlBufExt; -use crate::mysql::protocol::text::ColumnType; -use crate::mysql::protocol::Row; -use crate::mysql::MySqlColumn; +use crate::protocol::text::ColumnType; +use crate::protocol::Row; +use crate::MySqlColumn; // https://dev.mysql.com/doc/internals/en/binary-protocol-resultset-row.html#packet-ProtocolBinary::ResultsetRow // https://dev.mysql.com/doc/internals/en/binary-protocol-value.html diff --git a/sqlx-core/src/mysql/protocol/statement/stmt_close.rs b/sqlx-mysql/src/protocol/statement/stmt_close.rs similarity index 89% rename from sqlx-core/src/mysql/protocol/statement/stmt_close.rs rename to sqlx-mysql/src/protocol/statement/stmt_close.rs index 13f095f9b5..150a57e2c6 100644 --- a/sqlx-core/src/mysql/protocol/statement/stmt_close.rs +++ b/sqlx-mysql/src/protocol/statement/stmt_close.rs @@ -1,5 +1,5 @@ use crate::io::Encode; -use crate::mysql::protocol::Capabilities; +use crate::protocol::Capabilities; // https://dev.mysql.com/doc/internals/en/com-stmt-close.html diff --git a/sqlx-core/src/mysql/protocol/text/column.rs b/sqlx-mysql/src/protocol/text/column.rs similarity index 99% rename from sqlx-core/src/mysql/protocol/text/column.rs rename to sqlx-mysql/src/protocol/text/column.rs index 3ef42fd207..baa0c670b3 100644 --- a/sqlx-core/src/mysql/protocol/text/column.rs +++ b/sqlx-mysql/src/protocol/text/column.rs @@ -5,8 +5,8 @@ use bytes::{Buf, Bytes}; use crate::error::Error; use crate::io::Decode; -use crate::mysql::io::MySqlBufExt; -use crate::mysql::protocol::Capabilities; +use crate::io::MySqlBufExt; +use crate::protocol::Capabilities; // https://dev.mysql.com/doc/dev/mysql-server/8.0.12/group__group__cs__column__definition__flags.html diff --git a/sqlx-core/src/mysql/protocol/text/mod.rs b/sqlx-mysql/src/protocol/text/mod.rs similarity index 100% rename from sqlx-core/src/mysql/protocol/text/mod.rs rename to sqlx-mysql/src/protocol/text/mod.rs diff --git a/sqlx-core/src/mysql/protocol/text/ping.rs b/sqlx-mysql/src/protocol/text/ping.rs similarity index 86% rename from sqlx-core/src/mysql/protocol/text/ping.rs rename to sqlx-mysql/src/protocol/text/ping.rs index ad3b08844a..217a826477 100644 --- a/sqlx-core/src/mysql/protocol/text/ping.rs +++ b/sqlx-mysql/src/protocol/text/ping.rs @@ -1,5 +1,5 @@ use crate::io::Encode; -use crate::mysql::protocol::Capabilities; +use crate::protocol::Capabilities; // https://dev.mysql.com/doc/internals/en/com-ping.html diff --git a/sqlx-core/src/mysql/protocol/text/query.rs b/sqlx-mysql/src/protocol/text/query.rs similarity index 89% rename from sqlx-core/src/mysql/protocol/text/query.rs rename to sqlx-mysql/src/protocol/text/query.rs index 15edeb6ffe..caca9e46bf 100644 --- a/sqlx-core/src/mysql/protocol/text/query.rs +++ b/sqlx-mysql/src/protocol/text/query.rs @@ -1,5 +1,5 @@ use crate::io::Encode; -use crate::mysql::protocol::Capabilities; +use crate::protocol::Capabilities; // https://dev.mysql.com/doc/internals/en/com-query.html diff --git a/sqlx-core/src/mysql/protocol/text/quit.rs b/sqlx-mysql/src/protocol/text/quit.rs similarity index 86% rename from sqlx-core/src/mysql/protocol/text/quit.rs rename to sqlx-mysql/src/protocol/text/quit.rs index 86a8c49a6d..e4f6525f20 100644 --- a/sqlx-core/src/mysql/protocol/text/quit.rs +++ b/sqlx-mysql/src/protocol/text/quit.rs @@ -1,5 +1,5 @@ use crate::io::Encode; -use crate::mysql::protocol::Capabilities; +use crate::protocol::Capabilities; // https://dev.mysql.com/doc/internals/en/com-quit.html diff --git a/sqlx-core/src/mysql/protocol/text/row.rs b/sqlx-mysql/src/protocol/text/row.rs similarity index 89% rename from sqlx-core/src/mysql/protocol/text/row.rs rename to sqlx-mysql/src/protocol/text/row.rs index 17d32f10b7..852c47604e 100644 --- a/sqlx-core/src/mysql/protocol/text/row.rs +++ b/sqlx-mysql/src/protocol/text/row.rs @@ -1,10 +1,10 @@ use bytes::{Buf, Bytes}; +use crate::column::MySqlColumn; use crate::error::Error; use crate::io::Decode; -use crate::mysql::io::MySqlBufExt; -use crate::mysql::protocol::Row; -use crate::mysql::MySqlColumn; +use crate::io::MySqlBufExt; +use crate::protocol::Row; #[derive(Debug)] pub(crate) struct TextRow(pub(crate) Row); diff --git a/sqlx-core/src/mysql/query_result.rs b/sqlx-mysql/src/query_result.rs similarity index 68% rename from sqlx-core/src/mysql/query_result.rs rename to sqlx-mysql/src/query_result.rs index 41dcdbc1bb..9951b26acb 100644 --- a/sqlx-core/src/mysql/query_result.rs +++ b/sqlx-mysql/src/query_result.rs @@ -24,13 +24,3 @@ impl Extend for MySqlQueryResult { } } } - -#[cfg(feature = "any")] -impl From for crate::any::AnyQueryResult { - fn from(done: MySqlQueryResult) -> Self { - crate::any::AnyQueryResult { - rows_affected: done.rows_affected, - last_insert_id: Some(done.last_insert_id as i64), - } - } -} diff --git a/sqlx-core/src/mysql/row.rs b/sqlx-mysql/src/row.rs similarity index 72% rename from sqlx-core/src/mysql/row.rs rename to sqlx-mysql/src/row.rs index f910ded68d..c354d23d10 100644 --- a/sqlx-core/src/mysql/row.rs +++ b/sqlx-mysql/src/row.rs @@ -1,10 +1,12 @@ +use std::sync::Arc; + +pub(crate) use sqlx_core::row::*; + use crate::column::ColumnIndex; use crate::error::Error; use crate::ext::ustr::UStr; -use crate::mysql::{protocol, MySql, MySqlColumn, MySqlValueFormat, MySqlValueRef}; -use crate::row::Row; use crate::HashMap; -use std::sync::Arc; +use crate::{protocol, MySql, MySqlColumn, MySqlValueFormat, MySqlValueRef}; /// Implementation of [`Row`] for MySQL. #[derive(Debug)] @@ -15,8 +17,6 @@ pub struct MySqlRow { pub(crate) column_names: Arc>, } -impl crate::row::private_row::Sealed for MySqlRow {} - impl Row for MySqlRow { type Database = MySql; @@ -49,15 +49,3 @@ impl ColumnIndex for &'_ str { .map(|v| *v) } } - -#[cfg(feature = "any")] -impl From for crate::any::AnyRow { - #[inline] - fn from(row: MySqlRow) -> Self { - crate::any::AnyRow { - columns: row.columns.iter().map(|col| col.clone().into()).collect(), - - kind: crate::any::row::AnyRowKind::MySql(row), - } - } -} diff --git a/sqlx-core/src/mysql/statement.rs b/sqlx-mysql/src/statement.rs similarity index 69% rename from sqlx-core/src/mysql/statement.rs rename to sqlx-mysql/src/statement.rs index b6de92fa49..83db3af02c 100644 --- a/sqlx-core/src/mysql/statement.rs +++ b/sqlx-mysql/src/statement.rs @@ -2,13 +2,14 @@ use super::MySqlColumn; use crate::column::ColumnIndex; use crate::error::Error; use crate::ext::ustr::UStr; -use crate::mysql::{MySql, MySqlArguments, MySqlTypeInfo}; -use crate::statement::Statement; use crate::HashMap; +use crate::{MySql, MySqlArguments, MySqlTypeInfo}; use either::Either; use std::borrow::Cow; use std::sync::Arc; +pub(crate) use sqlx_core::statement::*; + #[derive(Debug, Clone)] pub struct MySqlStatement<'q> { pub(crate) sql: Cow<'q, str>, @@ -57,21 +58,3 @@ impl ColumnIndex> for &'_ str { .map(|v| *v) } } - -#[cfg(feature = "any")] -impl<'q> From> for crate::any::AnyStatement<'q> { - #[inline] - fn from(statement: MySqlStatement<'q>) -> Self { - crate::any::AnyStatement::<'q> { - columns: statement - .metadata - .columns - .iter() - .map(|col| col.clone().into()) - .collect(), - column_names: statement.metadata.column_names, - parameters: Some(Either::Right(statement.metadata.parameters)), - sql: statement.sql, - } - } -} diff --git a/sqlx-core/src/mysql/testing/mod.rs b/sqlx-mysql/src/testing/mod.rs similarity index 96% rename from sqlx-core/src/mysql/testing/mod.rs rename to sqlx-mysql/src/testing/mod.rs index c31c868dee..3355614525 100644 --- a/sqlx-core/src/mysql/testing/mod.rs +++ b/sqlx-mysql/src/testing/mod.rs @@ -12,12 +12,13 @@ use crate::connection::Connection; use crate::error::Error; use crate::executor::Executor; -use crate::mysql::{MySql, MySqlConnectOptions, MySqlConnection}; use crate::pool::{Pool, PoolOptions}; use crate::query::query; use crate::query_builder::QueryBuilder; use crate::query_scalar::query_scalar; -use crate::testing::{FixtureSnapshot, TestArgs, TestContext, TestSupport}; +use crate::{MySql, MySqlConnectOptions, MySqlConnection}; + +pub(crate) use sqlx_core::testing::*; // Using a blocking `OnceCell` here because the critical sections are short. static MASTER_POOL: OnceCell> = OnceCell::new(); @@ -47,7 +48,7 @@ impl TestSupport for MySql { query("delete from _sqlx_test_databases where db_id = ?") .bind(&db_id) - .execute(&mut conn) + .execute(&mut *conn) .await?; Ok(()) @@ -129,12 +130,12 @@ async fn test_context(args: &TestArgs) -> Result, Error> { query("insert into _sqlx_test_databases(test_path) values (?)") .bind(&args.test_path) - .execute(&mut conn) + .execute(&mut *conn) .await?; // MySQL doesn't have `INSERT ... RETURNING` let new_db_id: u64 = query_scalar("select last_insert_id()") - .fetch_one(&mut conn) + .fetch_one(&mut *conn) .await?; let new_db_name = db_name(new_db_id); diff --git a/sqlx-core/src/mysql/transaction.rs b/sqlx-mysql/src/transaction.rs similarity index 86% rename from sqlx-core/src/mysql/transaction.rs rename to sqlx-mysql/src/transaction.rs index 97cb121d0e..731bdb5750 100644 --- a/sqlx-core/src/mysql/transaction.rs +++ b/sqlx-mysql/src/transaction.rs @@ -1,14 +1,12 @@ use futures_core::future::BoxFuture; +use crate::connection::Waiting; use crate::error::Error; use crate::executor::Executor; -use crate::mysql::connection::Waiting; -use crate::mysql::protocol::text::Query; -use crate::mysql::{MySql, MySqlConnection}; -use crate::transaction::{ - begin_ansi_transaction_sql, commit_ansi_transaction_sql, rollback_ansi_transaction_sql, - TransactionManager, -}; +use crate::protocol::text::Query; +use crate::{MySql, MySqlConnection}; + +pub(crate) use sqlx_core::transaction::*; /// Implementation of [`TransactionManager`] for MySQL. pub struct MySqlTransactionManager; diff --git a/sqlx-core/src/mysql/type_info.rs b/sqlx-mysql/src/type_info.rs similarity index 89% rename from sqlx-core/src/mysql/type_info.rs rename to sqlx-mysql/src/type_info.rs index ac4df0ab2e..50e8b4172c 100644 --- a/sqlx-core/src/mysql/type_info.rs +++ b/sqlx-mysql/src/type_info.rs @@ -1,7 +1,8 @@ use std::fmt::{self, Display, Formatter}; -use crate::mysql::protocol::text::{ColumnDefinition, ColumnFlags, ColumnType}; -use crate::type_info::TypeInfo; +pub(crate) use sqlx_core::type_info::*; + +use crate::protocol::text::{ColumnDefinition, ColumnFlags, ColumnType}; /// Type information for a MySql type. #[derive(Debug, Clone)] @@ -112,11 +113,3 @@ impl PartialEq for MySqlTypeInfo { } impl Eq for MySqlTypeInfo {} - -#[cfg(feature = "any")] -impl From for crate::any::AnyTypeInfo { - #[inline] - fn from(ty: MySqlTypeInfo) -> Self { - crate::any::AnyTypeInfo(crate::any::type_info::AnyTypeInfoKind::MySql(ty)) - } -} diff --git a/sqlx-core/src/mysql/types/bigdecimal.rs b/sqlx-mysql/src/types/bigdecimal.rs similarity index 81% rename from sqlx-core/src/mysql/types/bigdecimal.rs rename to sqlx-mysql/src/types/bigdecimal.rs index 088e9cbffd..d763bb7f47 100644 --- a/sqlx-core/src/mysql/types/bigdecimal.rs +++ b/sqlx-mysql/src/types/bigdecimal.rs @@ -3,10 +3,10 @@ use bigdecimal::BigDecimal; use crate::decode::Decode; use crate::encode::{Encode, IsNull}; use crate::error::BoxDynError; -use crate::mysql::io::MySqlBufMutExt; -use crate::mysql::protocol::text::ColumnType; -use crate::mysql::{MySql, MySqlTypeInfo, MySqlValueRef}; +use crate::io::MySqlBufMutExt; +use crate::protocol::text::ColumnType; use crate::types::Type; +use crate::{MySql, MySqlTypeInfo, MySqlValueRef}; impl Type for BigDecimal { fn type_info() -> MySqlTypeInfo { diff --git a/sqlx-core/src/mysql/types/bool.rs b/sqlx-mysql/src/types/bool.rs similarity index 98% rename from sqlx-core/src/mysql/types/bool.rs rename to sqlx-mysql/src/types/bool.rs index 2d52a6838f..92793cdf92 100644 --- a/sqlx-core/src/mysql/types/bool.rs +++ b/sqlx-mysql/src/types/bool.rs @@ -1,11 +1,11 @@ use crate::decode::Decode; use crate::encode::{Encode, IsNull}; use crate::error::BoxDynError; -use crate::mysql::{ +use crate::types::Type; +use crate::{ protocol::text::{ColumnFlags, ColumnType}, MySql, MySqlTypeInfo, MySqlValueRef, }; -use crate::types::Type; impl Type for bool { fn type_info() -> MySqlTypeInfo { diff --git a/sqlx-core/src/mysql/types/bytes.rs b/sqlx-mysql/src/types/bytes.rs similarity index 91% rename from sqlx-core/src/mysql/types/bytes.rs rename to sqlx-mysql/src/types/bytes.rs index 1426425a23..fe5587b60f 100644 --- a/sqlx-core/src/mysql/types/bytes.rs +++ b/sqlx-mysql/src/types/bytes.rs @@ -1,10 +1,10 @@ use crate::decode::Decode; use crate::encode::{Encode, IsNull}; use crate::error::BoxDynError; -use crate::mysql::io::MySqlBufMutExt; -use crate::mysql::protocol::text::ColumnType; -use crate::mysql::{MySql, MySqlTypeInfo, MySqlValueRef}; +use crate::io::MySqlBufMutExt; +use crate::protocol::text::ColumnType; use crate::types::Type; +use crate::{MySql, MySqlTypeInfo, MySqlValueRef}; impl Type for [u8] { fn type_info() -> MySqlTypeInfo { diff --git a/sqlx-core/src/mysql/types/chrono.rs b/sqlx-mysql/src/types/chrono.rs similarity index 98% rename from sqlx-core/src/mysql/types/chrono.rs rename to sqlx-mysql/src/types/chrono.rs index 254d66987e..4059b1ef2b 100644 --- a/sqlx-core/src/mysql/types/chrono.rs +++ b/sqlx-mysql/src/types/chrono.rs @@ -4,10 +4,10 @@ use chrono::{DateTime, Datelike, Local, NaiveDate, NaiveDateTime, NaiveTime, Tim use crate::decode::Decode; use crate::encode::{Encode, IsNull}; use crate::error::{BoxDynError, UnexpectedNullError}; -use crate::mysql::protocol::text::ColumnType; -use crate::mysql::type_info::MySqlTypeInfo; -use crate::mysql::{MySql, MySqlValueFormat, MySqlValueRef}; +use crate::protocol::text::ColumnType; +use crate::type_info::MySqlTypeInfo; use crate::types::Type; +use crate::{MySql, MySqlValueFormat, MySqlValueRef}; impl Type for DateTime { fn type_info() -> MySqlTypeInfo { diff --git a/sqlx-core/src/mysql/types/float.rs b/sqlx-mysql/src/types/float.rs similarity index 94% rename from sqlx-core/src/mysql/types/float.rs rename to sqlx-mysql/src/types/float.rs index 07a76895ab..6df7c6cec3 100644 --- a/sqlx-core/src/mysql/types/float.rs +++ b/sqlx-mysql/src/types/float.rs @@ -3,9 +3,9 @@ use byteorder::{ByteOrder, LittleEndian}; use crate::decode::Decode; use crate::encode::{Encode, IsNull}; use crate::error::BoxDynError; -use crate::mysql::protocol::text::ColumnType; -use crate::mysql::{MySql, MySqlTypeInfo, MySqlValueFormat, MySqlValueRef}; +use crate::protocol::text::ColumnType; use crate::types::Type; +use crate::{MySql, MySqlTypeInfo, MySqlValueFormat, MySqlValueRef}; fn real_compatible(ty: &MySqlTypeInfo) -> bool { matches!(ty.r#type, ColumnType::Float | ColumnType::Double) diff --git a/sqlx-core/src/mysql/types/int.rs b/sqlx-mysql/src/types/int.rs similarity index 95% rename from sqlx-core/src/mysql/types/int.rs rename to sqlx-mysql/src/types/int.rs index f1ef3bc458..18a64f3155 100644 --- a/sqlx-core/src/mysql/types/int.rs +++ b/sqlx-mysql/src/types/int.rs @@ -3,9 +3,9 @@ use byteorder::{ByteOrder, LittleEndian}; use crate::decode::Decode; use crate::encode::{Encode, IsNull}; use crate::error::BoxDynError; -use crate::mysql::protocol::text::{ColumnFlags, ColumnType}; -use crate::mysql::{MySql, MySqlTypeInfo, MySqlValueFormat, MySqlValueRef}; +use crate::protocol::text::{ColumnFlags, ColumnType}; use crate::types::Type; +use crate::{MySql, MySqlTypeInfo, MySqlValueFormat, MySqlValueRef}; fn int_compatible(ty: &MySqlTypeInfo) -> bool { matches!( diff --git a/sqlx-mysql/src/types/json.rs b/sqlx-mysql/src/types/json.rs new file mode 100644 index 0000000000..b83ba83bcc --- /dev/null +++ b/sqlx-mysql/src/types/json.rs @@ -0,0 +1,68 @@ +use serde::{Deserialize, Serialize}; + +use crate::decode::Decode; +use crate::encode::{Encode, IsNull}; +use crate::error::BoxDynError; +use crate::protocol::text::ColumnType; +use crate::types::{Json, Type}; +use crate::{MySql, MySqlTypeInfo, MySqlValueRef}; + +impl Type for Json { + fn type_info() -> MySqlTypeInfo { + // MySql uses the `CHAR` type to pass JSON data from and to the client + // NOTE: This is forwards-compatible with MySQL v8+ as CHAR is a common transmission format + // and has nothing to do with the native storage ability of MySQL v8+ + MySqlTypeInfo::binary(ColumnType::String) + } + + fn compatible(ty: &MySqlTypeInfo) -> bool { + ty.r#type == ColumnType::Json + || <&str as Type>::compatible(ty) + || <&[u8] as Type>::compatible(ty) + } +} + +impl Encode<'_, MySql> for Json +where + T: Serialize, +{ + fn encode_by_ref(&self, buf: &mut Vec) -> IsNull { + // Encode JSON as a length-prefixed string. + // + // The previous implementation encoded into an intermediate buffer to get the final length. + // This is because the length prefix for the string is itself length-encoded, so we have + // to know the length first before we can start encoding in the buffer... or do we? + // + // The docs suggest that the integer length-encoding doesn't actually enforce a range on + // the value itself as long as it fits in the chosen encoding, so why not just choose + // the full length encoding to begin with? Then we can just reserve the space up-front + // and encode directly into the buffer. + // + // If someone is storing a JSON value it's likely large enough that the overhead of using + // the full-length integer encoding doesn't really matter. And if it's so large it overflows + // a `u64` then the process is likely to run OOM during the encoding process first anyway. + + let lenenc_start = buf.len(); + + buf.extend_from_slice(&[0u8; 9]); + + let encode_start = buf.len(); + self.encode_to(buf); + let encoded_len = (buf.len() - encode_start) as u64; + + // This prefix indicates that the following 8 bytes are a little-endian integer. + buf[lenenc_start] = 0xFE; + buf[lenenc_start + 1..][..8].copy_from_slice(&encoded_len.to_le_bytes()); + + IsNull::No + } +} + +impl<'r, T> Decode<'r, MySql> for Json +where + T: 'r + Deserialize<'r>, +{ + fn decode(value: MySqlValueRef<'r>) -> Result { + Json::decode_from_string(value.as_str()?) + } +} diff --git a/sqlx-core/src/mysql/types/mod.rs b/sqlx-mysql/src/types/mod.rs similarity index 98% rename from sqlx-core/src/mysql/types/mod.rs rename to sqlx-mysql/src/types/mod.rs index cf07f7d7b5..6c2466ae5f 100644 --- a/sqlx-core/src/mysql/types/mod.rs +++ b/sqlx-mysql/src/types/mod.rs @@ -78,7 +78,8 @@ //! //! In addition, `Option` is supported where `T` implements `Type`. An `Option` represents //! a potentially `NULL` value from MySQL. -//! + +pub(crate) use sqlx_core::types::*; mod bool; mod bytes; @@ -87,11 +88,14 @@ mod int; mod str; mod uint; +#[cfg(feature = "json")] +mod json; + #[cfg(feature = "bigdecimal")] mod bigdecimal; -#[cfg(feature = "decimal")] -mod decimal; +#[cfg(feature = "rust_decimal")] +mod rust_decimal; #[cfg(feature = "chrono")] mod chrono; @@ -101,6 +105,3 @@ mod time; #[cfg(feature = "uuid")] mod uuid; - -#[cfg(feature = "json")] -mod json; diff --git a/sqlx-core/src/mysql/types/decimal.rs b/sqlx-mysql/src/types/rust_decimal.rs similarity index 81% rename from sqlx-core/src/mysql/types/decimal.rs rename to sqlx-mysql/src/types/rust_decimal.rs index 0826b6bbd9..fa387596f4 100644 --- a/sqlx-core/src/mysql/types/decimal.rs +++ b/sqlx-mysql/src/types/rust_decimal.rs @@ -3,10 +3,10 @@ use rust_decimal::Decimal; use crate::decode::Decode; use crate::encode::{Encode, IsNull}; use crate::error::BoxDynError; -use crate::mysql::io::MySqlBufMutExt; -use crate::mysql::protocol::text::ColumnType; -use crate::mysql::{MySql, MySqlTypeInfo, MySqlValueRef}; +use crate::io::MySqlBufMutExt; +use crate::protocol::text::ColumnType; use crate::types::Type; +use crate::{MySql, MySqlTypeInfo, MySqlValueRef}; impl Type for Decimal { fn type_info() -> MySqlTypeInfo { diff --git a/sqlx-core/src/mysql/types/str.rs b/sqlx-mysql/src/types/str.rs similarity index 95% rename from sqlx-core/src/mysql/types/str.rs rename to sqlx-mysql/src/types/str.rs index e222019702..1da303684a 100644 --- a/sqlx-core/src/mysql/types/str.rs +++ b/sqlx-mysql/src/types/str.rs @@ -1,10 +1,10 @@ use crate::decode::Decode; use crate::encode::{Encode, IsNull}; use crate::error::BoxDynError; -use crate::mysql::io::MySqlBufMutExt; -use crate::mysql::protocol::text::{ColumnFlags, ColumnType}; -use crate::mysql::{MySql, MySqlTypeInfo, MySqlValueRef}; +use crate::io::MySqlBufMutExt; +use crate::protocol::text::{ColumnFlags, ColumnType}; use crate::types::Type; +use crate::{MySql, MySqlTypeInfo, MySqlValueRef}; use std::borrow::Cow; const COLLATE_UTF8_GENERAL_CI: u16 = 33; diff --git a/sqlx-core/src/mysql/types/time.rs b/sqlx-mysql/src/types/time.rs similarity index 98% rename from sqlx-core/src/mysql/types/time.rs rename to sqlx-mysql/src/types/time.rs index 638e68fcac..98d7b0fde8 100644 --- a/sqlx-core/src/mysql/types/time.rs +++ b/sqlx-mysql/src/types/time.rs @@ -6,10 +6,10 @@ use time::{Date, OffsetDateTime, PrimitiveDateTime, Time, UtcOffset}; use crate::decode::Decode; use crate::encode::{Encode, IsNull}; use crate::error::{BoxDynError, UnexpectedNullError}; -use crate::mysql::protocol::text::ColumnType; -use crate::mysql::type_info::MySqlTypeInfo; -use crate::mysql::{MySql, MySqlValueFormat, MySqlValueRef}; +use crate::protocol::text::ColumnType; +use crate::type_info::MySqlTypeInfo; use crate::types::Type; +use crate::{MySql, MySqlValueFormat, MySqlValueRef}; impl Type for OffsetDateTime { fn type_info() -> MySqlTypeInfo { diff --git a/sqlx-core/src/mysql/types/uint.rs b/sqlx-mysql/src/types/uint.rs similarity index 96% rename from sqlx-core/src/mysql/types/uint.rs rename to sqlx-mysql/src/types/uint.rs index b66baca450..4731f0e433 100644 --- a/sqlx-core/src/mysql/types/uint.rs +++ b/sqlx-mysql/src/types/uint.rs @@ -1,9 +1,9 @@ use crate::decode::Decode; use crate::encode::{Encode, IsNull}; use crate::error::BoxDynError; -use crate::mysql::protocol::text::{ColumnFlags, ColumnType}; -use crate::mysql::{MySql, MySqlTypeInfo, MySqlValueFormat, MySqlValueRef}; +use crate::protocol::text::{ColumnFlags, ColumnType}; use crate::types::Type; +use crate::{MySql, MySqlTypeInfo, MySqlValueFormat, MySqlValueRef}; use byteorder::{ByteOrder, LittleEndian}; fn uint_type_info(ty: ColumnType) -> MySqlTypeInfo { diff --git a/sqlx-core/src/mysql/types/uuid.rs b/sqlx-mysql/src/types/uuid.rs similarity index 94% rename from sqlx-core/src/mysql/types/uuid.rs rename to sqlx-mysql/src/types/uuid.rs index 114405db22..4c351e7b33 100644 --- a/sqlx-core/src/mysql/types/uuid.rs +++ b/sqlx-mysql/src/types/uuid.rs @@ -3,9 +3,9 @@ use uuid::{fmt::Hyphenated, Uuid}; use crate::decode::Decode; use crate::encode::{Encode, IsNull}; use crate::error::BoxDynError; -use crate::mysql::io::MySqlBufMutExt; -use crate::mysql::{MySql, MySqlTypeInfo, MySqlValueRef}; +use crate::io::MySqlBufMutExt; use crate::types::Type; +use crate::{MySql, MySqlTypeInfo, MySqlValueRef}; impl Type for Uuid { fn type_info() -> MySqlTypeInfo { diff --git a/sqlx-core/src/mysql/value.rs b/sqlx-mysql/src/value.rs similarity index 77% rename from sqlx-core/src/mysql/value.rs rename to sqlx-mysql/src/value.rs index 58cf69cdbf..2725597efb 100644 --- a/sqlx-core/src/mysql/value.rs +++ b/sqlx-mysql/src/value.rs @@ -1,11 +1,13 @@ -use crate::error::{BoxDynError, UnexpectedNullError}; -use crate::mysql::protocol::text::ColumnType; -use crate::mysql::{MySql, MySqlTypeInfo}; -use crate::value::{Value, ValueRef}; -use bytes::Bytes; use std::borrow::Cow; use std::str::from_utf8; +use bytes::Bytes; +pub(crate) use sqlx_core::value::*; + +use crate::error::{BoxDynError, UnexpectedNullError}; +use crate::protocol::text::ColumnType; +use crate::{MySql, MySqlTypeInfo}; + #[derive(Debug, Clone, Copy)] #[repr(u8)] pub enum MySqlValueFormat { @@ -97,28 +99,6 @@ impl<'r> ValueRef<'r> for MySqlValueRef<'r> { } } -#[cfg(feature = "any")] -impl<'r> From> for crate::any::AnyValueRef<'r> { - #[inline] - fn from(value: MySqlValueRef<'r>) -> Self { - crate::any::AnyValueRef { - type_info: value.type_info.clone().into(), - kind: crate::any::value::AnyValueRefKind::MySql(value), - } - } -} - -#[cfg(feature = "any")] -impl From for crate::any::AnyValue { - #[inline] - fn from(value: MySqlValue) -> Self { - crate::any::AnyValue { - type_info: value.type_info.clone().into(), - kind: crate::any::value::AnyValueKind::MySql(value), - } - } -} - fn is_null(value: Option<&[u8]>, ty: &MySqlTypeInfo) -> bool { if let Some(value) = value { // zero dates and date times should be treated the same as NULL diff --git a/sqlx-postgres/Cargo.toml b/sqlx-postgres/Cargo.toml new file mode 100644 index 0000000000..0c9bf4b5d1 --- /dev/null +++ b/sqlx-postgres/Cargo.toml @@ -0,0 +1,73 @@ +[package] +name = "sqlx-postgres" +readme = "README.md" +documentation = "https://docs.rs/sqlx" +description = "PostgreSQL driver implementation for SQLx. Not for direct use; see the `sqlx` crate for details." +version.workspace = true +license.workspace = true +edition.workspace = true +authors.workspace = true +repository.workspace = true +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[features] +any = ["sqlx-core/any"] +json = ["sqlx-core/json"] +migrate = ["sqlx-core/migrate"] +offline = ["sqlx-core/offline"] + +# Type integration features which require additional dependencies +rust_decimal = ["dep:rust_decimal", "dep:num-bigint"] + +[dependencies] +# Futures crates +futures-channel = { version = "0.3.19", default-features = false, features = ["sink", "alloc", "std"] } +futures-core = { version = "0.3.19", default-features = false } +futures-io = "0.3.24" +futures-util = { version = "0.3.19", default-features = false, features = ["alloc", "sink", "io"] } + +# Cryptographic Primitives +crc = "3.0.0" +hkdf = "0.12.0" +hmac = { version = "0.12.0", default-features = false } +md-5 = { version = "0.10.0", default-features = false } +rand = { version = "0.8.4", default-features = false, features = ["std", "std_rng"] } +sha1 = { version = "0.10.1", default-features = false } +sha2 = { version = "0.10.0", default-features = false } + +# Type Integrations (versions inherited from `[workspace.dependencies]`) +bigdecimal = { workspace = true, optional = true } +bit-vec = { workspace = true, optional = true } +chrono = { workspace = true, optional = true } +ipnetwork = { workspace = true, optional = true } +mac_address = { workspace = true, optional = true } +rust_decimal = { workspace = true, optional = true } +time = { workspace = true, optional = true } +uuid = { workspace = true, optional = true } + +# Misc +atoi = "1.0" +base64 = { version = "0.13.0", default-features = false, features = ["std"] } +bitflags = { version = "1.3.2", default-features = false } +byteorder = { version = "1.4.3", default-features = false, features = ["std"] } +dirs = "4.0.0" +dotenvy = { workspace = true } +hex = "0.4.3" +itoa = "1.0.1" +log = "0.4.17" +memchr = { version = "2.4.1", default-features = false } +num-bigint = { version = "0.4.3", optional = true } +once_cell = "1.9.0" +smallvec = "1.7.0" +stringprep = "0.1.2" +thiserror = "1.0.35" +whoami = "1.2.1" + +serde = { version = "1.0.144", features = ["derive"] } +serde_json = { version = "1.0.85", features = ["raw_value"] } + +[dependencies.sqlx-core] +version = "=0.6.2" +path = "../sqlx-core" +# We use JSON in the driver implementation itself so there's no reason not to enable it here. +features = ["json"] diff --git a/sqlx-core/src/postgres/advisory_lock.rs b/sqlx-postgres/src/advisory_lock.rs similarity index 98% rename from sqlx-core/src/postgres/advisory_lock.rs rename to sqlx-postgres/src/advisory_lock.rs index ce807484a6..32e3d742ae 100644 --- a/sqlx-core/src/postgres/advisory_lock.rs +++ b/sqlx-postgres/src/advisory_lock.rs @@ -1,6 +1,6 @@ use crate::error::Result; -use crate::postgres::PgConnection; use crate::Either; +use crate::PgConnection; use hkdf::Hkdf; use once_cell::sync::OnceCell; use sha2::Sha256; @@ -72,7 +72,7 @@ pub enum PgAdvisoryLockKey { /// ### Note: Release-on-drop is not immediate! /// On drop, this guard queues a `pg_advisory_unlock()` call on the connection which will be /// flushed to the server the next time it is used, or when it is returned to -/// a [`PgPool`][crate::postgres::PgPool] in the case of +/// a [`PgPool`][crate::PgPool] in the case of /// [`PoolConnection`][crate::pool::PoolConnection]. /// /// This means the lock is not actually released as soon as the guard is dropped. To ensure the @@ -190,7 +190,7 @@ impl PgAdvisoryLock { /// /// The returned guard queues a `pg_advisory_unlock()` call on the connection when dropped, /// which will be executed the next time the connection is used, or when returned to a - /// [`PgPool`][crate::postgres::PgPool] in the case of `PoolConnection`. + /// [`PgPool`][crate::PgPool] in the case of `PoolConnection`. /// /// Postgres allows a single connection to acquire a given lock more than once without releasing /// it first, so in that sense the lock is re-entrant. However, the number of unlock operations @@ -233,7 +233,7 @@ impl PgAdvisoryLock { /// /// The returned guard queues a `pg_advisory_unlock()` call on the connection when dropped, /// which will be executed the next time the connection is used, or when returned to a - /// [`PgPool`][crate::postgres::PgPool] in the case of `PoolConnection`. + /// [`PgPool`][crate::PgPool] in the case of `PoolConnection`. /// /// Postgres allows a single connection to acquire a given lock more than once without releasing /// it first, so in that sense the lock is re-entrant. However, the number of unlock operations @@ -406,7 +406,7 @@ impl<'lock, C: AsMut> AsMut for PgAdvisoryLockGuard< } /// Queues a `pg_advisory_unlock()` call on the wrapped connection which will be flushed -/// to the server the next time it is used, or when it is returned to [`PgPool`][crate::postgres::PgPool] +/// to the server the next time it is used, or when it is returned to [`PgPool`][crate::PgPool] /// in the case of [`PoolConnection`][crate::pool::PoolConnection]. impl<'lock, C: AsMut> Drop for PgAdvisoryLockGuard<'lock, C> { fn drop(&mut self) { diff --git a/sqlx-postgres/src/any.rs b/sqlx-postgres/src/any.rs new file mode 100644 index 0000000000..5703c10795 --- /dev/null +++ b/sqlx-postgres/src/any.rs @@ -0,0 +1,233 @@ +use crate::{ + Either, PgArguments, PgColumn, PgConnectOptions, PgConnection, PgQueryResult, PgRow, + PgTransactionManager, PgTypeInfo, Postgres, +}; +use futures_core::future::BoxFuture; +use futures_core::stream::BoxStream; +use futures_util::{StreamExt, TryFutureExt, TryStreamExt}; +use std::borrow::Cow; +use std::sync::Arc; + +pub use sqlx_core::any::*; + +use crate::type_info::PgType; +use sqlx_core::any::driver::AnyDriver; +use sqlx_core::column::Column; +use sqlx_core::connection::Connection; +use sqlx_core::database::Database; +use sqlx_core::describe::Describe; +use sqlx_core::executor::Executor; +use sqlx_core::ext::ustr::UStr; +use sqlx_core::row::Row; +use sqlx_core::statement::Statement; +use sqlx_core::transaction::TransactionManager; + +sqlx_core::declare_driver_with_optional_migrate!(DRIVER = Postgres); + +impl AnyConnectionBackend for PgConnection { + fn name(&self) -> &str { + ::NAME + } + + fn close(self: Box) -> BoxFuture<'static, sqlx_core::Result<()>> { + Connection::close(*self) + } + + fn close_hard(self: Box) -> BoxFuture<'static, sqlx_core::Result<()>> { + Connection::close_hard(*self) + } + + fn ping(&mut self) -> BoxFuture<'_, sqlx_core::Result<()>> { + Connection::ping(self) + } + + fn begin(&mut self) -> BoxFuture<'_, sqlx_core::Result<()>> { + PgTransactionManager::begin(self) + } + + fn commit(&mut self) -> BoxFuture<'_, sqlx_core::Result<()>> { + PgTransactionManager::commit(self) + } + + fn rollback(&mut self) -> BoxFuture<'_, sqlx_core::Result<()>> { + PgTransactionManager::rollback(self) + } + + fn start_rollback(&mut self) { + PgTransactionManager::start_rollback(self) + } + + fn flush(&mut self) -> BoxFuture<'_, sqlx_core::Result<()>> { + Connection::flush(self) + } + + fn should_flush(&self) -> bool { + Connection::should_flush(self) + } + + fn fetch_many<'q>( + &'q mut self, + query: &'q str, + arguments: Option>, + ) -> BoxStream<'q, sqlx_core::Result>> { + let persistent = arguments.is_some(); + let args = arguments.as_ref().map(AnyArguments::convert_to); + + Box::pin( + self.run(query, args, 0, persistent, None) + .try_flatten_stream() + .map( + move |res: sqlx_core::Result>| match res? { + Either::Left(result) => Ok(Either::Left(map_result(result))), + Either::Right(row) => Ok(Either::Right(AnyRow::try_from(&row)?)), + }, + ), + ) + } + + fn fetch_optional<'q>( + &'q mut self, + query: &'q str, + arguments: Option>, + ) -> BoxFuture<'q, sqlx_core::Result>> { + let persistent = arguments.is_some(); + let args = arguments.as_ref().map(AnyArguments::convert_to); + + Box::pin(async move { + let mut stream = self.run(query, args, 1, persistent, None).await?; + futures_util::pin_mut!(stream); + + if let Some(Either::Right(row)) = stream.try_next().await? { + return Ok(Some(AnyRow::try_from(&row)?)); + } + + Ok(None) + }) + } + + fn prepare_with<'c, 'q: 'c>( + &'c mut self, + sql: &'q str, + _parameters: &[AnyTypeInfo], + ) -> BoxFuture<'c, sqlx_core::Result>> { + Box::pin(async move { + let statement = Executor::prepare_with(self, sql, &[]).await?; + AnyStatement::try_from_statement( + sql, + &statement, + statement.metadata.column_names.clone(), + ) + }) + } + + fn describe<'q>(&'q mut self, sql: &'q str) -> BoxFuture<'q, sqlx_core::Result>> { + Box::pin(async move { + let describe = Executor::describe(self, sql).await?; + + let columns = describe + .columns + .iter() + .map(AnyColumn::try_from) + .collect::, _>>()?; + + let parameters = match describe.parameters { + Some(Either::Left(parameters)) => Some(Either::Left( + parameters + .iter() + .enumerate() + .map(|(i, type_info)| { + AnyTypeInfo::try_from(type_info).map_err(|_| { + sqlx_core::Error::AnyDriverError( + format!( + "Any driver does not support type {} of parameter {}", + type_info, i + ) + .into(), + ) + }) + }) + .collect::, _>>()?, + )), + Some(Either::Right(count)) => Some(Either::Right(count)), + None => None, + }; + + Ok(Describe { + columns, + parameters, + nullable: describe.nullable, + }) + }) + } +} + +impl<'a> TryFrom<&'a PgTypeInfo> for AnyTypeInfo { + type Error = sqlx_core::Error; + + fn try_from(pg_type: &'a PgTypeInfo) -> Result { + Ok(AnyTypeInfo { + kind: match &pg_type.0 { + PgType::Void => AnyTypeInfoKind::Null, + PgType::Int2 => AnyTypeInfoKind::SmallInt, + PgType::Int4 => AnyTypeInfoKind::Integer, + PgType::Int8 => AnyTypeInfoKind::BigInt, + PgType::Float4 => AnyTypeInfoKind::Real, + PgType::Float8 => AnyTypeInfoKind::Double, + PgType::Bytea => AnyTypeInfoKind::Blob, + PgType::Text => AnyTypeInfoKind::Text, + _ => { + return Err(sqlx_core::Error::AnyDriverError( + format!( + "Any driver does not support the Postgres type {:?}", + pg_type + ) + .into(), + )) + } + }, + }) + } +} + +impl<'a> TryFrom<&'a PgColumn> for AnyColumn { + type Error = sqlx_core::Error; + + fn try_from(col: &'a PgColumn) -> Result { + let type_info = + AnyTypeInfo::try_from(&col.type_info).map_err(|e| sqlx_core::Error::ColumnDecode { + index: col.name.to_string(), + source: e.into(), + })?; + + Ok(AnyColumn { + ordinal: col.ordinal, + name: col.name.clone(), + type_info, + }) + } +} + +impl<'a> TryFrom<&'a PgRow> for AnyRow { + type Error = sqlx_core::Error; + + fn try_from(row: &'a PgRow) -> Result { + AnyRow::map_from(row, row.metadata.column_names.clone()) + } +} + +impl<'a> TryFrom<&'a AnyConnectOptions> for PgConnectOptions { + type Error = sqlx_core::Error; + + fn try_from(value: &'a AnyConnectOptions) -> Result { + let mut opts = PgConnectOptions::parse_from_url(&value.database_url)?; + opts.log_settings = value.log_settings.clone(); + Ok(opts) + } +} + +fn map_result(res: PgQueryResult) -> AnyQueryResult { + AnyQueryResult { + rows_affected: res.rows_affected(), + last_insert_id: None, + } +} diff --git a/sqlx-core/src/postgres/arguments.rs b/sqlx-postgres/src/arguments.rs similarity index 98% rename from sqlx-core/src/postgres/arguments.rs rename to sqlx-postgres/src/arguments.rs index 4ca51b5032..40e1c11e34 100644 --- a/sqlx-core/src/postgres/arguments.rs +++ b/sqlx-postgres/src/arguments.rs @@ -1,12 +1,13 @@ use std::fmt::{self, Write}; use std::ops::{Deref, DerefMut}; -use crate::arguments::Arguments; use crate::encode::{Encode, IsNull}; use crate::error::Error; use crate::ext::ustr::UStr; -use crate::postgres::{PgConnection, PgTypeInfo, Postgres}; use crate::types::Type; +use crate::{PgConnection, PgTypeInfo, Postgres}; + +pub(crate) use sqlx_core::arguments::Arguments; // TODO: buf.patch(|| ...) is a poor name, can we think of a better name? Maybe `buf.lazy(||)` ? // TODO: Extend the patch system to support dynamic lengths diff --git a/sqlx-core/src/postgres/column.rs b/sqlx-postgres/src/column.rs similarity index 61% rename from sqlx-core/src/postgres/column.rs rename to sqlx-postgres/src/column.rs index 559fd6947a..9d309d8671 100644 --- a/sqlx-core/src/postgres/column.rs +++ b/sqlx-postgres/src/column.rs @@ -1,6 +1,7 @@ -use crate::column::Column; use crate::ext::ustr::UStr; -use crate::postgres::{PgTypeInfo, Postgres}; +use crate::{PgTypeInfo, Postgres}; + +pub(crate) use sqlx_core::column::{Column, ColumnIndex}; #[derive(Debug, Clone)] #[cfg_attr(feature = "offline", derive(serde::Serialize, serde::Deserialize))] @@ -14,8 +15,6 @@ pub struct PgColumn { pub(crate) relation_attribute_no: Option, } -impl crate::column::private_column::Sealed for PgColumn {} - impl Column for PgColumn { type Database = Postgres; @@ -31,14 +30,3 @@ impl Column for PgColumn { &self.type_info } } - -#[cfg(feature = "any")] -impl From for crate::any::AnyColumn { - #[inline] - fn from(column: PgColumn) -> Self { - crate::any::AnyColumn { - type_info: column.type_info.clone().into(), - kind: crate::any::column::AnyColumnKind::Postgres(column), - } - } -} diff --git a/sqlx-core/src/postgres/connection/describe.rs b/sqlx-postgres/src/connection/describe.rs similarity index 98% rename from sqlx-core/src/postgres/connection/describe.rs rename to sqlx-postgres/src/connection/describe.rs index dda7ada4a8..6fd584f25f 100644 --- a/sqlx-core/src/postgres/connection/describe.rs +++ b/sqlx-postgres/src/connection/describe.rs @@ -1,14 +1,14 @@ use crate::error::Error; use crate::ext::ustr::UStr; -use crate::postgres::message::{ParameterDescription, RowDescription}; -use crate::postgres::statement::PgStatementMetadata; -use crate::postgres::type_info::{PgCustomType, PgType, PgTypeKind}; -use crate::postgres::types::Oid; -use crate::postgres::{PgArguments, PgColumn, PgConnection, PgTypeInfo}; +use crate::message::{ParameterDescription, RowDescription}; use crate::query_as::query_as; use crate::query_scalar::{query_scalar, query_scalar_with}; +use crate::statement::PgStatementMetadata; +use crate::type_info::{PgCustomType, PgType, PgTypeKind}; use crate::types::Json; +use crate::types::Oid; use crate::HashMap; +use crate::{PgArguments, PgColumn, PgConnection, PgTypeInfo}; use futures_core::future::BoxFuture; use std::fmt::Write; use std::sync::Arc; diff --git a/sqlx-core/src/postgres/connection/establish.rs b/sqlx-postgres/src/connection/establish.rs similarity index 95% rename from sqlx-core/src/postgres/connection/establish.rs rename to sqlx-postgres/src/connection/establish.rs index cd163c5039..9f5008f9ed 100644 --- a/sqlx-core/src/postgres/connection/establish.rs +++ b/sqlx-postgres/src/connection/establish.rs @@ -1,24 +1,22 @@ use crate::HashMap; use crate::common::StatementCache; +use crate::connection::{sasl, stream::PgStream}; use crate::error::Error; use crate::io::Decode; -use crate::postgres::connection::{sasl, stream::PgStream, tls}; -use crate::postgres::message::{ +use crate::message::{ Authentication, BackendKeyData, MessageFormat, Password, ReadyForQuery, Startup, }; -use crate::postgres::types::Oid; -use crate::postgres::{PgConnectOptions, PgConnection}; +use crate::types::Oid; +use crate::{PgConnectOptions, PgConnection}; // https://www.postgresql.org/docs/current/protocol-flow.html#id-1.10.5.7.3 // https://www.postgresql.org/docs/current/protocol-flow.html#id-1.10.5.7.11 impl PgConnection { pub(crate) async fn establish(options: &PgConnectOptions) -> Result { - let mut stream = PgStream::connect(options).await?; - // Upgrade to TLS if we were asked to and the server supports it - tls::maybe_upgrade(&mut stream, options).await?; + let mut stream = PgStream::connect(options).await?; // To begin a session, a frontend opens a connection to the server // and sends a startup message. diff --git a/sqlx-core/src/postgres/connection/executor.rs b/sqlx-postgres/src/connection/executor.rs similarity index 97% rename from sqlx-core/src/postgres/connection/executor.rs rename to sqlx-postgres/src/connection/executor.rs index ad8e794c30..67ffa16ac9 100644 --- a/sqlx-core/src/postgres/connection/executor.rs +++ b/sqlx-postgres/src/connection/executor.rs @@ -2,22 +2,22 @@ use crate::describe::Describe; use crate::error::Error; use crate::executor::{Execute, Executor}; use crate::logger::QueryLogger; -use crate::postgres::message::{ +use crate::message::{ self, Bind, Close, CommandComplete, DataRow, MessageFormat, ParameterDescription, Parse, Query, RowDescription, }; -use crate::postgres::statement::PgStatementMetadata; -use crate::postgres::type_info::PgType; -use crate::postgres::types::Oid; -use crate::postgres::{ +use crate::statement::PgStatementMetadata; +use crate::type_info::PgType; +use crate::types::Oid; +use crate::{ statement::PgStatement, PgArguments, PgConnection, PgQueryResult, PgRow, PgTypeInfo, PgValueFormat, Postgres, }; -use either::Either; use futures_core::future::BoxFuture; use futures_core::stream::BoxStream; use futures_core::Stream; use futures_util::{pin_mut, TryStreamExt}; +use sqlx_core::Either; use std::{borrow::Cow, sync::Arc}; async fn prepare( @@ -93,7 +93,7 @@ async fn prepare( Arc::new(PgStatementMetadata { parameters, columns, - column_names, + column_names: Arc::new(column_names), }) }; @@ -191,7 +191,7 @@ impl PgConnection { Ok(statement) } - async fn run<'e, 'c: 'e, 'q: 'e>( + pub(crate) async fn run<'e, 'c: 'e, 'q: 'e>( &'c mut self, query: &'q str, arguments: Option, @@ -317,7 +317,7 @@ impl PgConnection { .await?; metadata = Arc::new(PgStatementMetadata { - column_names, + column_names: Arc::new(column_names), columns, parameters: Vec::default(), }); diff --git a/sqlx-core/src/postgres/connection/mod.rs b/sqlx-postgres/src/connection/mod.rs similarity index 93% rename from sqlx-core/src/postgres/connection/mod.rs rename to sqlx-postgres/src/connection/mod.rs index 325b565c3b..a021e4875e 100644 --- a/sqlx-core/src/postgres/connection/mod.rs +++ b/sqlx-postgres/src/connection/mod.rs @@ -6,17 +6,18 @@ use futures_core::future::BoxFuture; use futures_util::FutureExt; use crate::common::StatementCache; -use crate::connection::{Connection, LogSettings}; use crate::error::Error; use crate::ext::ustr::UStr; use crate::io::Decode; -use crate::postgres::message::{ +use crate::message::{ Close, Message, MessageFormat, Query, ReadyForQuery, Terminate, TransactionStatus, }; -use crate::postgres::statement::PgStatementMetadata; -use crate::postgres::types::Oid; -use crate::postgres::{PgConnectOptions, PgTypeInfo, Postgres}; +use crate::statement::PgStatementMetadata; use crate::transaction::Transaction; +use crate::types::Oid; +use crate::{PgConnectOptions, PgTypeInfo, Postgres}; + +pub(crate) use sqlx_core::connection::*; pub use self::stream::PgStream; @@ -72,8 +73,8 @@ impl PgConnection { } // will return when the connection is ready for another query - pub(in crate::postgres) async fn wait_until_ready(&mut self) -> Result<(), Error> { - if !self.stream.wbuf.is_empty() { + pub(crate) async fn wait_until_ready(&mut self) -> Result<(), Error> { + if !self.stream.write_buffer_mut().is_empty() { self.stream.flush().await?; } @@ -203,6 +204,6 @@ impl Connection for PgConnection { #[doc(hidden)] fn should_flush(&self) -> bool { - !self.stream.wbuf.is_empty() + !self.stream.write_buffer().is_empty() } } diff --git a/sqlx-core/src/postgres/connection/sasl.rs b/sqlx-postgres/src/connection/sasl.rs similarity index 98% rename from sqlx-core/src/postgres/connection/sasl.rs rename to sqlx-postgres/src/connection/sasl.rs index 51d23b748f..4e8be0ae93 100644 --- a/sqlx-core/src/postgres/connection/sasl.rs +++ b/sqlx-postgres/src/connection/sasl.rs @@ -1,9 +1,9 @@ +use crate::connection::stream::PgStream; use crate::error::Error; -use crate::postgres::connection::stream::PgStream; -use crate::postgres::message::{ +use crate::message::{ Authentication, AuthenticationSasl, MessageFormat, SaslInitialResponse, SaslResponse, }; -use crate::postgres::PgConnectOptions; +use crate::PgConnectOptions; use hmac::{Hmac, Mac}; use rand::Rng; use sha2::{Digest, Sha256}; diff --git a/sqlx-core/src/postgres/connection/stream.rs b/sqlx-postgres/src/connection/stream.rs similarity index 89% rename from sqlx-core/src/postgres/connection/stream.rs rename to sqlx-postgres/src/connection/stream.rs index 59b5289b8e..96e33a3150 100644 --- a/sqlx-core/src/postgres/connection/stream.rs +++ b/sqlx-postgres/src/connection/stream.rs @@ -2,16 +2,17 @@ use std::collections::BTreeMap; use std::ops::{Deref, DerefMut}; use std::str::FromStr; -use bytes::{Buf, Bytes}; use futures_channel::mpsc::UnboundedSender; use futures_util::SinkExt; use log::Level; +use sqlx_core::bytes::{Buf, Bytes}; +use crate::connection::tls::MaybeUpgradeTls; use crate::error::Error; -use crate::io::{BufStream, Decode, Encode}; -use crate::net::{MaybeTlsStream, Socket}; -use crate::postgres::message::{Message, MessageFormat, Notice, Notification, ParameterStatus}; -use crate::postgres::{PgConnectOptions, PgDatabaseError, PgSeverity}; +use crate::io::{Decode, Encode}; +use crate::message::{Message, MessageFormat, Notice, Notification, ParameterStatus}; +use crate::net::{self, BufferedSocket, Socket}; +use crate::{PgConnectOptions, PgDatabaseError, PgSeverity}; // the stream is a separate type from the connection to uphold the invariant where an instantiated // [PgConnection] is a **valid** connection to postgres @@ -23,7 +24,9 @@ use crate::postgres::{PgConnectOptions, PgDatabaseError, PgSeverity}; // is fully prepared to receive queries pub struct PgStream { - inner: BufStream>, + // A trait object is okay here as the buffering amortizes the overhead of both the dynamic + // function call as well as the syscall. + inner: BufferedSocket>, // buffer of unreceived notification messages from `PUBLISH` // this is set when creating a PgListener and only written to if that listener is @@ -37,15 +40,15 @@ pub struct PgStream { impl PgStream { pub(super) async fn connect(options: &PgConnectOptions) -> Result { - let socket = match options.fetch_socket() { - Some(ref path) => Socket::connect_uds(path).await?, - None => Socket::connect_tcp(&options.host, options.port).await?, + let socket_future = match options.fetch_socket() { + Some(ref path) => net::connect_uds(path, MaybeUpgradeTls(options)).await?, + None => net::connect_tcp(&options.host, options.port, MaybeUpgradeTls(options)).await?, }; - let inner = BufStream::new(MaybeTlsStream::Raw(socket)); + let socket = socket_future.await?; Ok(Self { - inner, + inner: BufferedSocket::new(socket), notifications: None, parameter_statuses: BTreeMap::default(), server_version_num: None, @@ -57,7 +60,8 @@ impl PgStream { T: Encode<'en>, { self.write(message); - self.flush().await + self.flush().await?; + Ok(()) } // Expect a specific type and format @@ -171,7 +175,7 @@ impl PgStream { } impl Deref for PgStream { - type Target = BufStream>; + type Target = BufferedSocket>; #[inline] fn deref(&self) -> &Self::Target { diff --git a/sqlx-postgres/src/connection/tls.rs b/sqlx-postgres/src/connection/tls.rs new file mode 100644 index 0000000000..b9e40d90dc --- /dev/null +++ b/sqlx-postgres/src/connection/tls.rs @@ -0,0 +1,100 @@ +use futures_core::future::BoxFuture; + +use crate::error::Error; +use crate::net::tls::{self, TlsConfig}; +use crate::net::{Socket, SocketIntoBox, WithSocket}; + +use crate::message::SslRequest; +use crate::{PgConnectOptions, PgSslMode}; + +pub struct MaybeUpgradeTls<'a>(pub &'a PgConnectOptions); + +impl<'a> WithSocket for MaybeUpgradeTls<'a> { + type Output = BoxFuture<'a, crate::Result>>; + + fn with_socket(self, socket: S) -> Self::Output { + Box::pin(maybe_upgrade(socket, self.0)) + } +} + +async fn maybe_upgrade( + mut socket: S, + options: &PgConnectOptions, +) -> Result, Error> { + // https://www.postgresql.org/docs/12/libpq-ssl.html#LIBPQ-SSL-SSLMODE-STATEMENTS + match options.ssl_mode { + // FIXME: Implement ALLOW + PgSslMode::Allow | PgSslMode::Disable => return Ok(Box::new(socket)), + + PgSslMode::Prefer => { + if !tls::available() { + return Ok(Box::new(socket)); + } + + // try upgrade, but its okay if we fail + if !request_upgrade(&mut socket, options).await? { + return Ok(Box::new(socket)); + } + } + + PgSslMode::Require | PgSslMode::VerifyFull | PgSslMode::VerifyCa => { + tls::error_if_unavailable()?; + + if !request_upgrade(&mut socket, options).await? { + // upgrade failed, die + return Err(Error::Tls("server does not support TLS".into())); + } + } + } + + let accept_invalid_certs = !matches!( + options.ssl_mode, + PgSslMode::VerifyCa | PgSslMode::VerifyFull + ); + let accept_invalid_hostnames = !matches!(options.ssl_mode, PgSslMode::VerifyFull); + + let config = TlsConfig { + accept_invalid_certs, + accept_invalid_hostnames, + hostname: &options.host, + root_cert_path: options.ssl_root_cert.as_ref(), + }; + + tls::handshake(socket, config, SocketIntoBox).await +} + +async fn request_upgrade( + socket: &mut impl Socket, + _options: &PgConnectOptions, +) -> Result { + // https://www.postgresql.org/docs/current/protocol-flow.html#id-1.10.5.7.11 + + // To initiate an SSL-encrypted connection, the frontend initially sends an + // SSLRequest message rather than a StartupMessage + + socket.write(SslRequest::BYTES).await?; + + // The server then responds with a single byte containing S or N, indicating that + // it is willing or unwilling to perform SSL, respectively. + + let mut response = [0u8]; + + socket.read(&mut &mut response[..]).await?; + + match response[0] { + b'S' => { + // The server is ready and willing to accept an SSL connection + Ok(true) + } + + b'N' => { + // The server is _unwilling_ to perform SSL + Ok(false) + } + + other => Err(err_protocol!( + "unexpected response from SSLRequest: 0x{:02x}", + other + )), + } +} diff --git a/sqlx-core/src/postgres/copy.rs b/sqlx-postgres/src/copy.rs similarity index 79% rename from sqlx-core/src/postgres/copy.rs rename to sqlx-postgres/src/copy.rs index 0bad775085..46bb5fe658 100644 --- a/sqlx-core/src/postgres/copy.rs +++ b/sqlx-postgres/src/copy.rs @@ -1,16 +1,19 @@ +use futures_core::future::BoxFuture; +use std::borrow::Cow; +use std::ops::{Deref, DerefMut}; + +use futures_core::stream::BoxStream; +use sqlx_core::bytes::{BufMut, Bytes}; + +use crate::connection::PgConnection; use crate::error::{Error, Result}; use crate::ext::async_stream::TryAsyncStream; -use crate::pool::{Pool, PoolConnection}; -use crate::postgres::connection::PgConnection; -use crate::postgres::message::{ +use crate::io::{AsyncRead, AsyncReadExt}; +use crate::message::{ CommandComplete, CopyData, CopyDone, CopyFail, CopyResponse, MessageFormat, Query, }; -use crate::postgres::Postgres; -use bytes::{BufMut, Bytes}; -use futures_core::stream::BoxStream; -use smallvec::alloc::borrow::Cow; -use sqlx_rt::{AsyncRead, AsyncReadExt, AsyncWriteExt}; -use std::ops::{Deref, DerefMut}; +use crate::pool::{Pool, PoolConnection}; +use crate::Postgres; impl PgConnection { /// Issue a `COPY FROM STDIN` statement and transition the connection to streaming data @@ -58,7 +61,11 @@ impl PgConnection { } } -impl Pool { +/// Implements methods for directly executing `COPY FROM/TO STDOUT` on a [`PgPool`]. +/// +/// This is a replacement for the inherent methods on `PgPool` which could not exist +/// once the Postgres driver was moved out into its own crate. +pub trait PgPoolCopyExt { /// Issue a `COPY FROM STDIN` statement and begin streaming data to Postgres. /// This is a more efficient way to import data into Postgres as compared to /// `INSERT` but requires one of a few specific data formats (text/CSV/binary). @@ -74,9 +81,10 @@ impl Pool { /// ### Note /// [PgCopyIn::finish] or [PgCopyIn::abort] *must* be called when finished or the connection /// will return an error the next time it is used. - pub async fn copy_in_raw(&self, statement: &str) -> Result>> { - PgCopyIn::begin(self.acquire().await?, statement).await - } + fn copy_in_raw<'a>( + &'a self, + statement: &'a str, + ) -> BoxFuture<'a, Result>>>; /// Issue a `COPY TO STDOUT` statement and begin streaming data /// from Postgres. This is a more efficient way to export data from Postgres but @@ -97,8 +105,25 @@ impl Pool { /// /// Command examples and accepted formats for `COPY` data are shown here: /// https://www.postgresql.org/docs/current/sql-copy.html - pub async fn copy_out_raw(&self, statement: &str) -> Result>> { - pg_begin_copy_out(self.acquire().await?, statement).await + fn copy_out_raw<'a>( + &'a self, + statement: &'a str, + ) -> BoxFuture<'a, Result>>>; +} + +impl PgPoolCopyExt for Pool { + fn copy_in_raw<'a>( + &'a self, + statement: &'a str, + ) -> BoxFuture<'a, Result>>> { + Box::pin(async { PgCopyIn::begin(self.acquire().await?, statement).await }) + } + + fn copy_out_raw<'a>( + &'a self, + statement: &'a str, + ) -> BoxFuture<'a, Result>>> { + Box::pin(async { pg_begin_copy_out(self.acquire().await?, statement).await }) } } @@ -172,8 +197,16 @@ impl> PgCopyIn { /// /// `source` will be read to the end. /// - /// ### Note + /// ### Note: Completion Step Required /// You must still call either [Self::finish] or [Self::abort] to complete the process. + /// + /// ### Note: Runtime Features + /// This method uses the `AsyncRead` trait which is re-exported from either Tokio or `async-std` + /// depending on which runtime feature is used. + /// + /// The runtime features _used_ to be mutually exclusive, but are no longer. + /// If both `runtime-async-std` and `runtime-tokio` features are enabled, the Tokio version + /// takes precedent. pub async fn read_from(&mut self, mut source: impl AsyncRead + Unpin) -> Result<&mut Self> { // this is a separate guard from WriteAndFlush so we can reuse the buffer without zeroing struct BufGuard<'s>(&'s mut Vec); @@ -189,46 +222,34 @@ impl> PgCopyIn { // flush any existing messages in the buffer and clear it conn.stream.flush().await?; - { - let buf_stream = &mut *conn.stream; - let stream = &mut buf_stream.stream; - - // ensures the buffer isn't left in an inconsistent state - let mut guard = BufGuard(&mut buf_stream.wbuf); - - let buf: &mut Vec = &mut guard.0; - buf.push(b'd'); // CopyData format code - buf.resize(5, 0); // reserve space for the length - - loop { - let read = match () { - // Tokio lets us read into the buffer without zeroing first - #[cfg(feature = "runtime-tokio")] - _ if buf.len() != buf.capacity() => { - // in case we have some data in the buffer, which can occur - // if the previous write did not fill the buffer - buf.truncate(5); - source.read_buf(buf).await? - } - _ => { - // should be a no-op unless len != capacity - buf.resize(buf.capacity(), 0); - source.read(&mut buf[5..]).await? - } - }; + loop { + let buf = conn.stream.write_buffer_mut(); + + // CopyData format code and reserved space for length + buf.put_slice(b"d\0\0\0\0"); + + let read = match () { + // Tokio lets us read into the buffer without zeroing first + #[cfg(feature = "_rt-tokio")] + _ => source.read_buf(buf.buf_mut()).await?, + #[cfg(not(feature = "_rt-tokio"))] + _ => source.read(buf.init_remaining_mut()).await?, + }; + + if read == 0 { + // This will end up sending an empty `CopyData` packet but that should be fine. + break; + } - if read == 0 { - break; - } + buf.advance(read); - let read32 = u32::try_from(read) - .map_err(|_| err_protocol!("number of bytes read exceeds 2^32: {}", read))?; + // Write the length + let read32 = u32::try_from(read) + .map_err(|_| err_protocol!("number of bytes read exceeds 2^32: {}", read))?; - (&mut buf[1..]).put_u32(read32 + 4); + (&mut buf.get_mut()[1..]).put_u32(read32 + 4); - stream.write_all(&buf[..read + 5]).await?; - stream.flush().await?; - } + conn.stream.flush().await?; } Ok(self) diff --git a/sqlx-core/src/postgres/database.rs b/sqlx-postgres/src/database.rs similarity index 72% rename from sqlx-core/src/postgres/database.rs rename to sqlx-postgres/src/database.rs index 192d5a6480..c082aafe5f 100644 --- a/sqlx-core/src/postgres/database.rs +++ b/sqlx-postgres/src/database.rs @@ -1,11 +1,14 @@ -use crate::database::{Database, HasArguments, HasStatement, HasStatementCache, HasValueRef}; -use crate::postgres::arguments::PgArgumentBuffer; -use crate::postgres::value::{PgValue, PgValueRef}; -use crate::postgres::{ +use crate::arguments::PgArgumentBuffer; +use crate::value::{PgValue, PgValueRef}; +use crate::{ PgArguments, PgColumn, PgConnection, PgQueryResult, PgRow, PgStatement, PgTransactionManager, PgTypeInfo, }; +pub(crate) use sqlx_core::database::{ + Database, HasArguments, HasStatement, HasStatementCache, HasValueRef, +}; + /// PostgreSQL database driver. #[derive(Debug)] pub struct Postgres; @@ -24,6 +27,10 @@ impl Database for Postgres { type TypeInfo = PgTypeInfo; type Value = PgValue; + + const NAME: &'static str = "PostgreSQL"; + + const URL_SCHEMES: &'static [&'static str] = &["postgres", "postgresql"]; } impl<'r> HasValueRef<'r> for Postgres { diff --git a/sqlx-core/src/postgres/error.rs b/sqlx-postgres/src/error.rs similarity index 94% rename from sqlx-core/src/postgres/error.rs rename to sqlx-postgres/src/error.rs index bffccba3a6..931685a4e0 100644 --- a/sqlx-core/src/postgres/error.rs +++ b/sqlx-postgres/src/error.rs @@ -1,11 +1,12 @@ -use std::error::Error; +use std::error::Error as StdError; use std::fmt::{self, Debug, Display, Formatter}; use atoi::atoi; use smallvec::alloc::borrow::Cow; -use crate::error::DatabaseError; -use crate::postgres::message::{Notice, PgSeverity}; +pub(crate) use sqlx_core::error::*; + +use crate::message::{Notice, PgSeverity}; /// An error returned from the PostgreSQL database. pub struct PgDatabaseError(pub(crate) Notice); @@ -159,7 +160,7 @@ impl Display for PgDatabaseError { } } -impl Error for PgDatabaseError {} +impl StdError for PgDatabaseError {} impl DatabaseError for PgDatabaseError { fn message(&self) -> &str { @@ -171,17 +172,17 @@ impl DatabaseError for PgDatabaseError { } #[doc(hidden)] - fn as_error(&self) -> &(dyn Error + Send + Sync + 'static) { + fn as_error(&self) -> &(dyn StdError + Send + Sync + 'static) { self } #[doc(hidden)] - fn as_error_mut(&mut self) -> &mut (dyn Error + Send + Sync + 'static) { + fn as_error_mut(&mut self) -> &mut (dyn StdError + Send + Sync + 'static) { self } #[doc(hidden)] - fn into_error(self: Box) -> Box { + fn into_error(self: Box) -> BoxDynError { self } diff --git a/sqlx-core/src/postgres/io/buf_mut.rs b/sqlx-postgres/src/io/buf_mut.rs similarity index 97% rename from sqlx-core/src/postgres/io/buf_mut.rs rename to sqlx-postgres/src/io/buf_mut.rs index d0b710293d..b5688f3bf3 100644 --- a/sqlx-core/src/postgres/io/buf_mut.rs +++ b/sqlx-postgres/src/io/buf_mut.rs @@ -1,4 +1,4 @@ -use crate::postgres::types::Oid; +use crate::types::Oid; pub trait PgBufMutExt { fn put_length_prefixed(&mut self, f: F) diff --git a/sqlx-core/src/postgres/io/mod.rs b/sqlx-postgres/src/io/mod.rs similarity index 56% rename from sqlx-core/src/postgres/io/mod.rs rename to sqlx-postgres/src/io/mod.rs index 37988b6d4f..1a6d070257 100644 --- a/sqlx-core/src/postgres/io/mod.rs +++ b/sqlx-postgres/src/io/mod.rs @@ -1,3 +1,5 @@ mod buf_mut; pub use buf_mut::PgBufMutExt; + +pub(crate) use sqlx_core::io::*; diff --git a/sqlx-core/src/postgres/mod.rs b/sqlx-postgres/src/lib.rs similarity index 91% rename from sqlx-core/src/postgres/mod.rs rename to sqlx-postgres/src/lib.rs index 00abc9c967..5b9d5804b2 100644 --- a/sqlx-core/src/postgres/mod.rs +++ b/sqlx-postgres/src/lib.rs @@ -1,5 +1,8 @@ //! **PostgreSQL** database driver. +#[macro_use] +extern crate sqlx_core; + use crate::executor::Executor; mod advisory_lock; @@ -21,12 +24,17 @@ mod type_info; pub mod types; mod value; +#[cfg(feature = "any")] +pub mod any; + #[cfg(feature = "migrate")] mod migrate; #[cfg(feature = "migrate")] mod testing; +pub(crate) use sqlx_core::driver_prelude::*; + pub use advisory_lock::{PgAdvisoryLock, PgAdvisoryLockGuard, PgAdvisoryLockKey}; pub use arguments::{PgArgumentBuffer, PgArguments}; pub use column::PgColumn; @@ -56,10 +64,7 @@ pub trait PgExecutor<'c>: Executor<'c, Database = Postgres> {} impl<'c, T: Executor<'c, Database = Postgres>> PgExecutor<'c> for T {} impl_into_arguments_for_arguments!(PgArguments); -impl_executor_for_pool_connection!(Postgres, PgConnection, PgRow); -impl_executor_for_transaction!(Postgres, PgRow); impl_acquire!(Postgres, PgConnection); impl_column_index_for_row!(PgRow); impl_column_index_for_statement!(PgStatement); -impl_into_maybe_pool!(Postgres, PgConnection); impl_encode_for_option!(Postgres); diff --git a/sqlx-core/src/postgres/listener.rs b/sqlx-postgres/src/listener.rs similarity index 95% rename from sqlx-core/src/postgres/listener.rs rename to sqlx-postgres/src/listener.rs index 1432ae6c06..e09f4c5203 100644 --- a/sqlx-core/src/postgres/listener.rs +++ b/sqlx-postgres/src/listener.rs @@ -2,19 +2,19 @@ use std::fmt::{self, Debug}; use std::io; use std::str::from_utf8; -use either::Either; use futures_channel::mpsc; use futures_core::future::BoxFuture; use futures_core::stream::{BoxStream, Stream}; use futures_util::{FutureExt, StreamExt, TryStreamExt}; +use sqlx_core::Either; use crate::describe::Describe; use crate::error::Error; use crate::executor::{Execute, Executor}; +use crate::message::{MessageFormat, Notification}; use crate::pool::PoolOptions; use crate::pool::{Pool, PoolConnection}; -use crate::postgres::message::{MessageFormat, Notification}; -use crate::postgres::{PgConnection, PgQueryResult, PgRow, PgStatement, PgTypeInfo, Postgres}; +use crate::{PgConnection, PgQueryResult, PgRow, PgStatement, PgTypeInfo, Postgres}; /// A stream of asynchronous notifications from Postgres. /// @@ -191,8 +191,8 @@ impl PgListener { /// # use sqlx_core::postgres::PgListener; /// # use sqlx_core::error::Error; /// # - /// # #[cfg(feature = "_rt-async-std")] - /// # sqlx_rt::block_on::<_, Result<(), Error>>(async move { + /// # #[cfg(feature = "_rt")] + /// # sqlx::__rt::test_block_on(async move { /// # let mut listener = PgListener::connect("postgres:// ...").await?; /// loop { /// // ask for next notification, re-connecting (transparently) if needed @@ -200,7 +200,7 @@ impl PgListener { /// /// // handle notification, do something interesting /// } - /// # Ok(()) + /// # Result::<(), Error>::Ok(()) /// # }).unwrap(); /// ``` pub async fn recv(&mut self) -> Result { @@ -222,8 +222,8 @@ impl PgListener { /// # use sqlx_core::postgres::PgListener; /// # use sqlx_core::error::Error; /// # - /// # #[cfg(feature = "_rt-async-std")] - /// # sqlx_rt::block_on::<_, Result<(), Error>>(async move { + /// # #[cfg(feature = "_rt")] + /// # sqlx::__rt::test_block_on(async move { /// # let mut listener = PgListener::connect("postgres:// ...").await?; /// loop { /// // start handling notifications, connecting if needed @@ -233,7 +233,7 @@ impl PgListener { /// /// // connection lost, do something interesting /// } - /// # Ok(()) + /// # Result::<(), Error>::Ok(()) /// # }).unwrap(); /// ``` pub async fn try_recv(&mut self) -> Result, Error> { @@ -321,13 +321,7 @@ impl Drop for PgListener { }; // Unregister any listeners before returning the connection to the pool. - #[cfg(not(feature = "_rt-async-std"))] - if let Ok(handle) = sqlx_rt::Handle::try_current() { - handle.spawn(fut); - } - - #[cfg(feature = "_rt-async-std")] - sqlx_rt::spawn(fut); + crate::rt::spawn(fut); } } } diff --git a/sqlx-core/src/postgres/message/authentication.rs b/sqlx-postgres/src/message/authentication.rs similarity index 99% rename from sqlx-core/src/postgres/message/authentication.rs rename to sqlx-postgres/src/message/authentication.rs index 47625acfd1..d1f4c658dd 100644 --- a/sqlx-core/src/postgres/message/authentication.rs +++ b/sqlx-postgres/src/message/authentication.rs @@ -1,7 +1,7 @@ use std::str::from_utf8; -use bytes::{Buf, Bytes}; use memchr::memchr; +use sqlx_core::bytes::{Buf, Bytes}; use crate::error::Error; use crate::io::Decode; diff --git a/sqlx-core/src/postgres/message/backend_key_data.rs b/sqlx-postgres/src/message/backend_key_data.rs similarity index 97% rename from sqlx-core/src/postgres/message/backend_key_data.rs rename to sqlx-postgres/src/message/backend_key_data.rs index 03ae6413e2..d89df65fb0 100644 --- a/sqlx-core/src/postgres/message/backend_key_data.rs +++ b/sqlx-postgres/src/message/backend_key_data.rs @@ -1,5 +1,5 @@ use byteorder::{BigEndian, ByteOrder}; -use bytes::Bytes; +use sqlx_core::bytes::Bytes; use crate::error::Error; use crate::io::Decode; diff --git a/sqlx-core/src/postgres/message/bind.rs b/sqlx-postgres/src/message/bind.rs similarity index 95% rename from sqlx-core/src/postgres/message/bind.rs rename to sqlx-postgres/src/message/bind.rs index 6c5dcf78c8..b8db9679bb 100644 --- a/sqlx-core/src/postgres/message/bind.rs +++ b/sqlx-postgres/src/message/bind.rs @@ -1,7 +1,7 @@ use crate::io::Encode; -use crate::postgres::io::PgBufMutExt; -use crate::postgres::types::Oid; -use crate::postgres::PgValueFormat; +use crate::io::PgBufMutExt; +use crate::types::Oid; +use crate::PgValueFormat; #[derive(Debug)] pub struct Bind<'a> { diff --git a/sqlx-core/src/postgres/message/close.rs b/sqlx-postgres/src/message/close.rs similarity index 91% rename from sqlx-core/src/postgres/message/close.rs rename to sqlx-postgres/src/message/close.rs index ed5de761c8..0ffa638c0b 100644 --- a/sqlx-core/src/postgres/message/close.rs +++ b/sqlx-postgres/src/message/close.rs @@ -1,6 +1,6 @@ use crate::io::Encode; -use crate::postgres::io::PgBufMutExt; -use crate::postgres::types::Oid; +use crate::io::PgBufMutExt; +use crate::types::Oid; const CLOSE_PORTAL: u8 = b'P'; const CLOSE_STATEMENT: u8 = b'S'; diff --git a/sqlx-core/src/postgres/message/command_complete.rs b/sqlx-postgres/src/message/command_complete.rs similarity index 98% rename from sqlx-core/src/postgres/message/command_complete.rs rename to sqlx-postgres/src/message/command_complete.rs index 87ed557264..c2c8e1580e 100644 --- a/sqlx-core/src/postgres/message/command_complete.rs +++ b/sqlx-postgres/src/message/command_complete.rs @@ -1,6 +1,6 @@ use atoi::atoi; -use bytes::Bytes; use memchr::memrchr; +use sqlx_core::bytes::Bytes; use crate::error::Error; use crate::io::Decode; diff --git a/sqlx-core/src/postgres/message/copy.rs b/sqlx-postgres/src/message/copy.rs similarity index 98% rename from sqlx-core/src/postgres/message/copy.rs rename to sqlx-postgres/src/message/copy.rs index 58553d431b..db0e7398cf 100644 --- a/sqlx-core/src/postgres/message/copy.rs +++ b/sqlx-postgres/src/message/copy.rs @@ -1,6 +1,6 @@ use crate::error::Result; use crate::io::{BufExt, BufMutExt, Decode, Encode}; -use bytes::{Buf, BufMut, Bytes}; +use sqlx_core::bytes::{Buf, BufMut, Bytes}; use std::ops::Deref; /// The same structure is sent for both `CopyInResponse` and `CopyOutResponse` diff --git a/sqlx-core/src/postgres/message/data_row.rs b/sqlx-postgres/src/message/data_row.rs similarity index 99% rename from sqlx-core/src/postgres/message/data_row.rs rename to sqlx-postgres/src/message/data_row.rs index 853b4d2f41..3e08d22f20 100644 --- a/sqlx-core/src/postgres/message/data_row.rs +++ b/sqlx-postgres/src/message/data_row.rs @@ -1,7 +1,7 @@ use std::ops::Range; use byteorder::{BigEndian, ByteOrder}; -use bytes::Bytes; +use sqlx_core::bytes::Bytes; use crate::error::Error; use crate::io::Decode; diff --git a/sqlx-core/src/postgres/message/describe.rs b/sqlx-postgres/src/message/describe.rs similarity index 97% rename from sqlx-core/src/postgres/message/describe.rs rename to sqlx-postgres/src/message/describe.rs index ea919d5021..382f6e70f5 100644 --- a/sqlx-core/src/postgres/message/describe.rs +++ b/sqlx-postgres/src/message/describe.rs @@ -1,6 +1,6 @@ use crate::io::Encode; -use crate::postgres::io::PgBufMutExt; -use crate::postgres::types::Oid; +use crate::io::PgBufMutExt; +use crate::types::Oid; const DESCRIBE_PORTAL: u8 = b'P'; const DESCRIBE_STATEMENT: u8 = b'S'; diff --git a/sqlx-core/src/postgres/message/execute.rs b/sqlx-postgres/src/message/execute.rs similarity index 92% rename from sqlx-core/src/postgres/message/execute.rs rename to sqlx-postgres/src/message/execute.rs index dc70ced914..3550ae7824 100644 --- a/sqlx-core/src/postgres/message/execute.rs +++ b/sqlx-postgres/src/message/execute.rs @@ -1,6 +1,6 @@ use crate::io::Encode; -use crate::postgres::io::PgBufMutExt; -use crate::postgres::types::Oid; +use crate::io::PgBufMutExt; +use crate::types::Oid; pub struct Execute { /// The id of the portal to execute (`None` selects the unnamed portal). diff --git a/sqlx-core/src/postgres/message/flush.rs b/sqlx-postgres/src/message/flush.rs similarity index 100% rename from sqlx-core/src/postgres/message/flush.rs rename to sqlx-postgres/src/message/flush.rs diff --git a/sqlx-core/src/postgres/message/mod.rs b/sqlx-postgres/src/message/mod.rs similarity index 99% rename from sqlx-core/src/postgres/message/mod.rs rename to sqlx-postgres/src/message/mod.rs index 1261bff339..6e7daad239 100644 --- a/sqlx-core/src/postgres/message/mod.rs +++ b/sqlx-postgres/src/message/mod.rs @@ -1,4 +1,4 @@ -use bytes::Bytes; +use sqlx_core::bytes::Bytes; use crate::error::Error; use crate::io::Decode; diff --git a/sqlx-core/src/postgres/message/notification.rs b/sqlx-postgres/src/message/notification.rs similarity index 96% rename from sqlx-core/src/postgres/message/notification.rs rename to sqlx-postgres/src/message/notification.rs index 207b8f306b..34303908ac 100644 --- a/sqlx-core/src/postgres/message/notification.rs +++ b/sqlx-postgres/src/message/notification.rs @@ -1,4 +1,4 @@ -use bytes::{Buf, Bytes}; +use sqlx_core::bytes::{Buf, Bytes}; use crate::error::Error; use crate::io::{BufExt, Decode}; diff --git a/sqlx-core/src/postgres/message/parameter_description.rs b/sqlx-postgres/src/message/parameter_description.rs similarity index 95% rename from sqlx-core/src/postgres/message/parameter_description.rs rename to sqlx-postgres/src/message/parameter_description.rs index e6640a73bd..8d525d05c5 100644 --- a/sqlx-core/src/postgres/message/parameter_description.rs +++ b/sqlx-postgres/src/message/parameter_description.rs @@ -1,9 +1,9 @@ -use bytes::{Buf, Bytes}; use smallvec::SmallVec; +use sqlx_core::bytes::{Buf, Bytes}; use crate::error::Error; use crate::io::Decode; -use crate::postgres::types::Oid; +use crate::types::Oid; #[derive(Debug)] pub struct ParameterDescription { diff --git a/sqlx-core/src/postgres/message/parameter_status.rs b/sqlx-postgres/src/message/parameter_status.rs similarity index 98% rename from sqlx-core/src/postgres/message/parameter_status.rs rename to sqlx-postgres/src/message/parameter_status.rs index ffd0ef1b60..37abe4e38e 100644 --- a/sqlx-core/src/postgres/message/parameter_status.rs +++ b/sqlx-postgres/src/message/parameter_status.rs @@ -1,4 +1,4 @@ -use bytes::Bytes; +use sqlx_core::bytes::Bytes; use crate::error::Error; use crate::io::{BufExt, Decode}; diff --git a/sqlx-core/src/postgres/message/parse.rs b/sqlx-postgres/src/message/parse.rs similarity index 95% rename from sqlx-core/src/postgres/message/parse.rs rename to sqlx-postgres/src/message/parse.rs index 5734ae4120..bf5e3363bf 100644 --- a/sqlx-core/src/postgres/message/parse.rs +++ b/sqlx-postgres/src/message/parse.rs @@ -1,8 +1,8 @@ use std::i16; +use crate::io::PgBufMutExt; use crate::io::{BufMutExt, Encode}; -use crate::postgres::io::PgBufMutExt; -use crate::postgres::types::Oid; +use crate::types::Oid; #[derive(Debug)] pub struct Parse<'a> { diff --git a/sqlx-core/src/postgres/message/password.rs b/sqlx-postgres/src/message/password.rs similarity index 98% rename from sqlx-core/src/postgres/message/password.rs rename to sqlx-postgres/src/message/password.rs index 8b0a8d66ae..ba8b5ac68e 100644 --- a/sqlx-core/src/postgres/message/password.rs +++ b/sqlx-postgres/src/message/password.rs @@ -2,8 +2,8 @@ use std::fmt::Write; use md5::{Digest, Md5}; +use crate::io::PgBufMutExt; use crate::io::{BufMutExt, Encode}; -use crate::postgres::io::PgBufMutExt; #[derive(Debug)] pub enum Password<'a> { diff --git a/sqlx-core/src/postgres/message/query.rs b/sqlx-postgres/src/message/query.rs similarity index 100% rename from sqlx-core/src/postgres/message/query.rs rename to sqlx-postgres/src/message/query.rs diff --git a/sqlx-core/src/postgres/message/ready_for_query.rs b/sqlx-postgres/src/message/ready_for_query.rs similarity index 97% rename from sqlx-core/src/postgres/message/ready_for_query.rs rename to sqlx-postgres/src/message/ready_for_query.rs index 791d01b207..21e6540d01 100644 --- a/sqlx-core/src/postgres/message/ready_for_query.rs +++ b/sqlx-postgres/src/message/ready_for_query.rs @@ -1,4 +1,4 @@ -use bytes::Bytes; +use sqlx_core::bytes::Bytes; use crate::error::Error; use crate::io::Decode; diff --git a/sqlx-core/src/postgres/message/response.rs b/sqlx-postgres/src/message/response.rs similarity index 99% rename from sqlx-core/src/postgres/message/response.rs rename to sqlx-postgres/src/message/response.rs index ccc81846ac..bb79ddd46d 100644 --- a/sqlx-core/src/postgres/message/response.rs +++ b/sqlx-postgres/src/message/response.rs @@ -1,7 +1,7 @@ use std::str::from_utf8; -use bytes::Bytes; use memchr::memchr; +use sqlx_core::bytes::Bytes; use crate::error::Error; use crate::io::Decode; diff --git a/sqlx-core/src/postgres/message/row_description.rs b/sqlx-postgres/src/message/row_description.rs similarity index 97% rename from sqlx-core/src/postgres/message/row_description.rs rename to sqlx-postgres/src/message/row_description.rs index ff25ea9373..7146ee8375 100644 --- a/sqlx-core/src/postgres/message/row_description.rs +++ b/sqlx-postgres/src/message/row_description.rs @@ -1,8 +1,8 @@ -use bytes::{Buf, Bytes}; +use sqlx_core::bytes::{Buf, Bytes}; use crate::error::Error; use crate::io::{BufExt, Decode}; -use crate::postgres::types::Oid; +use crate::types::Oid; #[derive(Debug)] pub struct RowDescription { diff --git a/sqlx-core/src/postgres/message/sasl.rs b/sqlx-postgres/src/message/sasl.rs similarity index 96% rename from sqlx-core/src/postgres/message/sasl.rs rename to sqlx-postgres/src/message/sasl.rs index df8b618ab1..77d0bf8dfe 100644 --- a/sqlx-core/src/postgres/message/sasl.rs +++ b/sqlx-postgres/src/message/sasl.rs @@ -1,5 +1,5 @@ +use crate::io::PgBufMutExt; use crate::io::{BufMutExt, Encode}; -use crate::postgres::io::PgBufMutExt; pub struct SaslInitialResponse<'a> { pub response: &'a str, diff --git a/sqlx-core/src/postgres/message/ssl_request.rs b/sqlx-postgres/src/message/ssl_request.rs similarity index 74% rename from sqlx-core/src/postgres/message/ssl_request.rs rename to sqlx-postgres/src/message/ssl_request.rs index 7740c0be6e..fa57faf064 100644 --- a/sqlx-core/src/postgres/message/ssl_request.rs +++ b/sqlx-postgres/src/message/ssl_request.rs @@ -2,6 +2,10 @@ use crate::io::Encode; pub struct SslRequest; +impl SslRequest { + pub const BYTES: &'static [u8] = b"\x00\x00\x00\x08\x04\xd2\x16/"; +} + impl Encode<'_> for SslRequest { #[inline] fn encode_with(&self, buf: &mut Vec, _: ()) { @@ -12,10 +16,8 @@ impl Encode<'_> for SslRequest { #[test] fn test_encode_ssl_request() { - const EXPECTED: &[u8] = b"\x00\x00\x00\x08\x04\xd2\x16/"; - let mut buf = Vec::new(); SslRequest.encode(&mut buf); - assert_eq!(buf, EXPECTED); + assert_eq!(buf, SslRequest::BYTES); } diff --git a/sqlx-core/src/postgres/message/startup.rs b/sqlx-postgres/src/message/startup.rs similarity index 98% rename from sqlx-core/src/postgres/message/startup.rs rename to sqlx-postgres/src/message/startup.rs index 34f6fa4e97..838695843f 100644 --- a/sqlx-core/src/postgres/message/startup.rs +++ b/sqlx-postgres/src/message/startup.rs @@ -1,5 +1,5 @@ +use crate::io::PgBufMutExt; use crate::io::{BufMutExt, Encode}; -use crate::postgres::io::PgBufMutExt; // To begin a session, a frontend opens a connection to the server and sends a startup message. // This message includes the names of the user and of the database the user wants to connect to; diff --git a/sqlx-core/src/postgres/message/sync.rs b/sqlx-postgres/src/message/sync.rs similarity index 100% rename from sqlx-core/src/postgres/message/sync.rs rename to sqlx-postgres/src/message/sync.rs diff --git a/sqlx-core/src/postgres/message/terminate.rs b/sqlx-postgres/src/message/terminate.rs similarity index 100% rename from sqlx-core/src/postgres/message/terminate.rs rename to sqlx-postgres/src/message/terminate.rs diff --git a/sqlx-core/src/postgres/migrate.rs b/sqlx-postgres/src/migrate.rs similarity index 85% rename from sqlx-core/src/postgres/migrate.rs rename to sqlx-postgres/src/migrate.rs index 183b193b51..de4103bfcc 100644 --- a/sqlx-core/src/postgres/migrate.rs +++ b/sqlx-postgres/src/migrate.rs @@ -1,17 +1,20 @@ +use std::str::FromStr; +use std::time::Duration; +use std::time::Instant; + +use futures_core::future::BoxFuture; + +pub(crate) use sqlx_core::migrate::MigrateError; +pub(crate) use sqlx_core::migrate::{AppliedMigration, Migration}; +pub(crate) use sqlx_core::migrate::{Migrate, MigrateDatabase}; + use crate::connection::{ConnectOptions, Connection}; use crate::error::Error; use crate::executor::Executor; -use crate::migrate::MigrateError; -use crate::migrate::{AppliedMigration, Migration}; -use crate::migrate::{Migrate, MigrateDatabase}; -use crate::postgres::{PgConnectOptions, PgConnection, Postgres}; use crate::query::query; use crate::query_as::query_as; use crate::query_scalar::query_scalar; -use futures_core::future::BoxFuture; -use std::str::FromStr; -use std::time::Duration; -use std::time::Instant; +use crate::{PgConnectOptions, PgConnection, Postgres}; fn parse_for_maintenance(url: &str) -> Result<(PgConnectOptions, String), Error> { let mut options = PgConnectOptions::from_str(url)?; @@ -106,19 +109,6 @@ CREATE TABLE IF NOT EXISTS _sqlx_migrations ( }) } - fn version(&mut self) -> BoxFuture<'_, Result, MigrateError>> { - Box::pin(async move { - // language=SQL - let row = query_as( - "SELECT version, NOT success FROM _sqlx_migrations ORDER BY version DESC LIMIT 1", - ) - .fetch_optional(self) - .await?; - - Ok(row) - }) - } - fn dirty_version(&mut self) -> BoxFuture<'_, Result, MigrateError>> { Box::pin(async move { // language=SQL @@ -190,30 +180,6 @@ CREATE TABLE IF NOT EXISTS _sqlx_migrations ( }) } - fn validate<'e: 'm, 'm>( - &'e mut self, - migration: &'m Migration, - ) -> BoxFuture<'m, Result<(), MigrateError>> { - Box::pin(async move { - // language=SQL - let checksum: Option> = - query_scalar("SELECT checksum FROM _sqlx_migrations WHERE version = $1") - .bind(migration.version) - .fetch_optional(self) - .await?; - - if let Some(checksum) = checksum { - return if checksum == &*migration.checksum { - Ok(()) - } else { - Err(MigrateError::VersionMismatch(migration.version)) - }; - } else { - Err(MigrateError::VersionMissing(migration.version)) - } - }) - } - fn apply<'e: 'm, 'm>( &'e mut self, migration: &'m Migration, @@ -239,7 +205,7 @@ CREATE TABLE IF NOT EXISTS _sqlx_migrations ( .bind(migration.version) .bind(&*migration.description) .bind(&*migration.checksum) - .execute(&mut tx) + .execute(&mut *tx) .await?; tx.commit().await?; @@ -282,7 +248,7 @@ CREATE TABLE IF NOT EXISTS _sqlx_migrations ( // language=SQL let _ = query(r#"DELETE FROM _sqlx_migrations WHERE version = $1"#) .bind(migration.version) - .execute(&mut tx) + .execute(&mut *tx) .await?; tx.commit().await?; diff --git a/sqlx-core/src/postgres/options/connect.rs b/sqlx-postgres/src/options/connect.rs similarity index 82% rename from sqlx-core/src/postgres/options/connect.rs rename to sqlx-postgres/src/options/connect.rs index 5c98598dd6..295768311b 100644 --- a/sqlx-core/src/postgres/options/connect.rs +++ b/sqlx-postgres/src/options/connect.rs @@ -1,13 +1,18 @@ use crate::connection::ConnectOptions; use crate::error::Error; -use crate::postgres::{PgConnectOptions, PgConnection}; +use crate::{PgConnectOptions, PgConnection}; use futures_core::future::BoxFuture; use log::LevelFilter; +use sqlx_core::Url; use std::time::Duration; impl ConnectOptions for PgConnectOptions { type Connection = PgConnection; + fn from_url(url: &Url) -> Result { + Self::parse_from_url(url) + } + fn connect(&self) -> BoxFuture<'_, Result> where Self::Connection: Sized, diff --git a/sqlx-core/src/postgres/options/mod.rs b/sqlx-postgres/src/options/mod.rs similarity index 98% rename from sqlx-core/src/postgres/options/mod.rs rename to sqlx-postgres/src/options/mod.rs index e870da0943..9bca6629df 100644 --- a/sqlx-core/src/postgres/options/mod.rs +++ b/sqlx-postgres/src/options/mod.rs @@ -3,12 +3,14 @@ use std::env::var; use std::fmt::{Display, Write}; use std::path::{Path, PathBuf}; +pub use ssl_mode::PgSslMode; + +use crate::{connection::LogSettings, net::tls::CertificateInput}; + mod connect; mod parse; mod pgpass; mod ssl_mode; -use crate::{connection::LogSettings, net::CertificateInput}; -pub use ssl_mode::PgSslMode; /// Options and flags which can be used to configure a PostgreSQL connection. /// @@ -58,8 +60,8 @@ pub use ssl_mode::PgSslMode; /// # use sqlx_core::postgres::{PgConnectOptions, PgConnection, PgSslMode}; /// # /// # fn main() { -/// # #[cfg(feature = "_rt-async-std")] -/// # sqlx_rt::async_std::task::block_on::<_, Result<(), Error>>(async move { +/// # #[cfg(feature = "_rt")] +/// # sqlx::__rt::test_block_on(async move { /// // URL connection string /// let conn = PgConnection::connect("postgres://localhost/mydb").await?; /// @@ -71,7 +73,7 @@ pub use ssl_mode::PgSslMode; /// .password("secret-password") /// .ssl_mode(PgSslMode::Require) /// .connect().await?; -/// # Ok(()) +/// # Result::<(), Error>::Ok(()) /// # }).unwrap(); /// # } /// ``` diff --git a/sqlx-core/src/postgres/options/parse.rs b/sqlx-postgres/src/options/parse.rs similarity index 96% rename from sqlx-core/src/postgres/options/parse.rs rename to sqlx-postgres/src/options/parse.rs index 65d2baeb49..501e5b52c2 100644 --- a/sqlx-core/src/postgres/options/parse.rs +++ b/sqlx-postgres/src/options/parse.rs @@ -1,16 +1,12 @@ use crate::error::Error; -use crate::postgres::PgConnectOptions; -use percent_encoding::percent_decode_str; +use crate::PgConnectOptions; +use sqlx_core::percent_encoding::percent_decode_str; +use sqlx_core::Url; use std::net::IpAddr; use std::str::FromStr; -use url::Url; - -impl FromStr for PgConnectOptions { - type Err = Error; - - fn from_str(s: &str) -> Result { - let url: Url = s.parse().map_err(Error::config)?; +impl PgConnectOptions { + pub(crate) fn parse_from_url(url: &Url) -> Result { let mut options = Self::new_without_pgpass(); if let Some(host) = url.host_str() { @@ -110,6 +106,16 @@ impl FromStr for PgConnectOptions { } } +impl FromStr for PgConnectOptions { + type Err = Error; + + fn from_str(s: &str) -> Result { + let url: Url = s.parse().map_err(Error::config)?; + + Self::parse_from_url(&url) + } +} + #[test] fn it_parses_socket_correctly_from_parameter() { let url = "postgres:///?host=/var/run/postgres/"; diff --git a/sqlx-core/src/postgres/options/pgpass.rs b/sqlx-postgres/src/options/pgpass.rs similarity index 100% rename from sqlx-core/src/postgres/options/pgpass.rs rename to sqlx-postgres/src/options/pgpass.rs diff --git a/sqlx-core/src/postgres/options/ssl_mode.rs b/sqlx-postgres/src/options/ssl_mode.rs similarity index 100% rename from sqlx-core/src/postgres/options/ssl_mode.rs rename to sqlx-postgres/src/options/ssl_mode.rs diff --git a/sqlx-core/src/postgres/query_result.rs b/sqlx-postgres/src/query_result.rs similarity index 100% rename from sqlx-core/src/postgres/query_result.rs rename to sqlx-postgres/src/query_result.rs diff --git a/sqlx-core/src/postgres/row.rs b/sqlx-postgres/src/row.rs similarity index 62% rename from sqlx-core/src/postgres/row.rs rename to sqlx-postgres/src/row.rs index 1b7bdf7e38..865d8a991b 100644 --- a/sqlx-core/src/postgres/row.rs +++ b/sqlx-postgres/src/row.rs @@ -1,12 +1,13 @@ use crate::column::ColumnIndex; use crate::error::Error; -use crate::postgres::message::DataRow; -use crate::postgres::statement::PgStatementMetadata; -use crate::postgres::value::PgValueFormat; -use crate::postgres::{PgColumn, PgValueRef, Postgres}; -use crate::row::Row; +use crate::message::DataRow; +use crate::statement::PgStatementMetadata; +use crate::value::PgValueFormat; +use crate::{PgColumn, PgValueRef, Postgres}; use std::sync::Arc; +pub(crate) use sqlx_core::row::Row; + /// Implementation of [`Row`] for PostgreSQL. pub struct PgRow { pub(crate) data: DataRow, @@ -14,8 +15,6 @@ pub struct PgRow { pub(crate) metadata: Arc, } -impl crate::row::private_row::Sealed for PgRow {} - impl Row for PgRow { type Database = Postgres; @@ -49,20 +48,3 @@ impl ColumnIndex for &'_ str { .map(|v| *v) } } - -#[cfg(feature = "any")] -impl From for crate::any::AnyRow { - #[inline] - fn from(row: PgRow) -> Self { - crate::any::AnyRow { - columns: row - .metadata - .columns - .iter() - .map(|col| col.clone().into()) - .collect(), - - kind: crate::any::row::AnyRowKind::Postgres(row), - } - } -} diff --git a/sqlx-core/src/postgres/statement.rs b/sqlx-postgres/src/statement.rs similarity index 54% rename from sqlx-core/src/postgres/statement.rs rename to sqlx-postgres/src/statement.rs index 4c01b91563..5c83a50043 100644 --- a/sqlx-core/src/postgres/statement.rs +++ b/sqlx-postgres/src/statement.rs @@ -2,13 +2,13 @@ use super::{PgColumn, PgTypeInfo}; use crate::column::ColumnIndex; use crate::error::Error; use crate::ext::ustr::UStr; -use crate::postgres::{PgArguments, Postgres}; -use crate::statement::Statement; -use crate::HashMap; -use either::Either; +use crate::{PgArguments, Postgres}; use std::borrow::Cow; use std::sync::Arc; +pub(crate) use sqlx_core::statement::Statement; +use sqlx_core::{Either, HashMap}; + #[derive(Debug, Clone)] pub struct PgStatement<'q> { pub(crate) sql: Cow<'q, str>, @@ -18,7 +18,9 @@ pub struct PgStatement<'q> { #[derive(Debug, Default)] pub(crate) struct PgStatementMetadata { pub(crate) columns: Vec, - pub(crate) column_names: HashMap, + // This `Arc` is not redundant; it's used to avoid deep-copying this map for the `Any` backend. + // See `sqlx-postgres/src/any.rs` + pub(crate) column_names: Arc>, pub(crate) parameters: Vec, } @@ -58,27 +60,27 @@ impl ColumnIndex> for &'_ str { } } -#[cfg(feature = "any")] -impl<'q> From> for crate::any::AnyStatement<'q> { - #[inline] - fn from(statement: PgStatement<'q>) -> Self { - crate::any::AnyStatement::<'q> { - columns: statement - .metadata - .columns - .iter() - .map(|col| col.clone().into()) - .collect(), - column_names: std::sync::Arc::new(statement.metadata.column_names.clone()), - parameters: Some(Either::Left( - statement - .metadata - .parameters - .iter() - .map(|ty| ty.clone().into()) - .collect(), - )), - sql: statement.sql, - } - } -} +// #[cfg(feature = "any")] +// impl<'q> From> for crate::any::AnyStatement<'q> { +// #[inline] +// fn from(statement: PgStatement<'q>) -> Self { +// crate::any::AnyStatement::<'q> { +// columns: statement +// .metadata +// .columns +// .iter() +// .map(|col| col.clone().into()) +// .collect(), +// column_names: statement.metadata.column_names.clone(), +// parameters: Some(Either::Left( +// statement +// .metadata +// .parameters +// .iter() +// .map(|ty| ty.clone().into()) +// .collect(), +// )), +// sql: statement.sql, +// } +// } +// } diff --git a/sqlx-core/src/postgres/testing/mod.rs b/sqlx-postgres/src/testing/mod.rs similarity index 97% rename from sqlx-core/src/postgres/testing/mod.rs rename to sqlx-postgres/src/testing/mod.rs index b566725a51..06c6af39d3 100644 --- a/sqlx-core/src/postgres/testing/mod.rs +++ b/sqlx-postgres/src/testing/mod.rs @@ -13,10 +13,11 @@ use crate::connection::Connection; use crate::error::Error; use crate::executor::Executor; use crate::pool::{Pool, PoolOptions}; -use crate::postgres::{PgConnectOptions, PgConnection, Postgres}; use crate::query::query; use crate::query_scalar::query_scalar; -use crate::testing::{FixtureSnapshot, TestArgs, TestContext, TestSupport}; +use crate::{PgConnectOptions, PgConnection, Postgres}; + +pub(crate) use sqlx_core::testing::*; // Using a blocking `OnceCell` here because the critical sections are short. static MASTER_POOL: OnceCell> = OnceCell::new(); @@ -44,7 +45,7 @@ impl TestSupport for Postgres { query("delete from _sqlx_test.databases where db_name = $1") .bind(&db_name) - .execute(&mut conn) + .execute(&mut *conn) .await?; Ok(()) @@ -145,7 +146,7 @@ async fn test_context(args: &TestArgs) -> Result, Error> { "#, ) .bind(&args.test_path) - .fetch_one(&mut conn) + .fetch_one(&mut *conn) .await?; conn.execute(&format!("create database {:?}", new_db_name)[..]) diff --git a/sqlx-core/src/postgres/transaction.rs b/sqlx-postgres/src/transaction.rs similarity index 88% rename from sqlx-core/src/postgres/transaction.rs rename to sqlx-postgres/src/transaction.rs index c65e772099..045dda704b 100644 --- a/sqlx-core/src/postgres/transaction.rs +++ b/sqlx-postgres/src/transaction.rs @@ -2,11 +2,10 @@ use futures_core::future::BoxFuture; use crate::error::Error; use crate::executor::Executor; -use crate::postgres::{PgConnection, Postgres}; -use crate::transaction::{ - begin_ansi_transaction_sql, commit_ansi_transaction_sql, rollback_ansi_transaction_sql, - TransactionManager, -}; + +use crate::{PgConnection, Postgres}; + +pub(crate) use sqlx_core::transaction::*; /// Implementation of [`TransactionManager`] for PostgreSQL. pub struct PgTransactionManager; diff --git a/sqlx-core/src/postgres/type_info.rs b/sqlx-postgres/src/type_info.rs similarity index 99% rename from sqlx-core/src/postgres/type_info.rs rename to sqlx-postgres/src/type_info.rs index c6a96b58ee..ae211d0d3a 100644 --- a/sqlx-core/src/postgres/type_info.rs +++ b/sqlx-postgres/src/type_info.rs @@ -6,8 +6,9 @@ use std::ops::Deref; use std::sync::Arc; use crate::ext::ustr::UStr; -use crate::postgres::types::Oid; -use crate::type_info::TypeInfo; +use crate::types::Oid; + +pub(crate) use sqlx_core::type_info::TypeInfo; /// Type information for a PostgreSQL type. #[derive(Debug, Clone, PartialEq)] @@ -1136,11 +1137,3 @@ impl PartialEq for PgType { } } } - -#[cfg(feature = "any")] -impl From for crate::any::AnyTypeInfo { - #[inline] - fn from(ty: PgTypeInfo) -> Self { - crate::any::AnyTypeInfo(crate::any::type_info::AnyTypeInfoKind::Postgres(ty)) - } -} diff --git a/sqlx-core/src/postgres/types/array.rs b/sqlx-postgres/src/types/array.rs similarity index 97% rename from sqlx-core/src/postgres/types/array.rs rename to sqlx-postgres/src/types/array.rs index bd07861173..0ae06240cd 100644 --- a/sqlx-core/src/postgres/types/array.rs +++ b/sqlx-postgres/src/types/array.rs @@ -1,13 +1,13 @@ -use bytes::Buf; +use sqlx_core::bytes::Buf; use std::borrow::Cow; use crate::decode::Decode; use crate::encode::{Encode, IsNull}; use crate::error::BoxDynError; -use crate::postgres::type_info::PgType; -use crate::postgres::types::Oid; -use crate::postgres::{PgArgumentBuffer, PgTypeInfo, PgValueFormat, PgValueRef, Postgres}; +use crate::type_info::PgType; +use crate::types::Oid; use crate::types::Type; +use crate::{PgArgumentBuffer, PgTypeInfo, PgValueFormat, PgValueRef, Postgres}; pub trait PgHasArrayType { fn array_type_info() -> PgTypeInfo; diff --git a/sqlx-core/src/postgres/types/bigdecimal.rs b/sqlx-postgres/src/types/bigdecimal.rs similarity index 98% rename from sqlx-core/src/postgres/types/bigdecimal.rs rename to sqlx-postgres/src/types/bigdecimal.rs index 709909d6cb..8240bb0d27 100644 --- a/sqlx-core/src/postgres/types/bigdecimal.rs +++ b/sqlx-postgres/src/types/bigdecimal.rs @@ -6,11 +6,9 @@ use num_bigint::{BigInt, Sign}; use crate::decode::Decode; use crate::encode::{Encode, IsNull}; use crate::error::BoxDynError; -use crate::postgres::types::numeric::{PgNumeric, PgNumericSign}; -use crate::postgres::{ - PgArgumentBuffer, PgHasArrayType, PgTypeInfo, PgValueFormat, PgValueRef, Postgres, -}; +use crate::types::numeric::{PgNumeric, PgNumericSign}; use crate::types::Type; +use crate::{PgArgumentBuffer, PgHasArrayType, PgTypeInfo, PgValueFormat, PgValueRef, Postgres}; impl Type for BigDecimal { fn type_info() -> PgTypeInfo { diff --git a/sqlx-core/src/postgres/types/bit_vec.rs b/sqlx-postgres/src/types/bit_vec.rs similarity index 96% rename from sqlx-core/src/postgres/types/bit_vec.rs rename to sqlx-postgres/src/types/bit_vec.rs index 902ab12674..e1ad3fb50a 100644 --- a/sqlx-core/src/postgres/types/bit_vec.rs +++ b/sqlx-postgres/src/types/bit_vec.rs @@ -2,11 +2,11 @@ use crate::{ decode::Decode, encode::{Encode, IsNull}, error::BoxDynError, - postgres::{PgArgumentBuffer, PgHasArrayType, PgTypeInfo, PgValueFormat, PgValueRef, Postgres}, types::Type, + PgArgumentBuffer, PgHasArrayType, PgTypeInfo, PgValueFormat, PgValueRef, Postgres, }; use bit_vec::BitVec; -use bytes::Buf; +use sqlx_core::bytes::Buf; use std::{io, mem}; impl Type for BitVec { diff --git a/sqlx-core/src/postgres/types/bool.rs b/sqlx-postgres/src/types/bool.rs similarity index 89% rename from sqlx-core/src/postgres/types/bool.rs rename to sqlx-postgres/src/types/bool.rs index 30a7297b54..e55470bfb5 100644 --- a/sqlx-core/src/postgres/types/bool.rs +++ b/sqlx-postgres/src/types/bool.rs @@ -1,10 +1,8 @@ use crate::decode::Decode; use crate::encode::{Encode, IsNull}; use crate::error::BoxDynError; -use crate::postgres::{ - PgArgumentBuffer, PgHasArrayType, PgTypeInfo, PgValueFormat, PgValueRef, Postgres, -}; use crate::types::Type; +use crate::{PgArgumentBuffer, PgHasArrayType, PgTypeInfo, PgValueFormat, PgValueRef, Postgres}; impl Type for bool { fn type_info() -> PgTypeInfo { diff --git a/sqlx-core/src/postgres/types/bytes.rs b/sqlx-postgres/src/types/bytes.rs similarity index 95% rename from sqlx-core/src/postgres/types/bytes.rs rename to sqlx-postgres/src/types/bytes.rs index 6325c4376d..c36247bfb6 100644 --- a/sqlx-core/src/postgres/types/bytes.rs +++ b/sqlx-postgres/src/types/bytes.rs @@ -1,10 +1,8 @@ use crate::decode::Decode; use crate::encode::{Encode, IsNull}; use crate::error::BoxDynError; -use crate::postgres::{ - PgArgumentBuffer, PgHasArrayType, PgTypeInfo, PgValueFormat, PgValueRef, Postgres, -}; use crate::types::Type; +use crate::{PgArgumentBuffer, PgHasArrayType, PgTypeInfo, PgValueFormat, PgValueRef, Postgres}; impl PgHasArrayType for u8 { fn array_type_info() -> PgTypeInfo { diff --git a/sqlx-core/src/postgres/types/chrono/date.rs b/sqlx-postgres/src/types/chrono/date.rs similarity index 91% rename from sqlx-core/src/postgres/types/chrono/date.rs rename to sqlx-postgres/src/types/chrono/date.rs index 68ee1d48dc..c02d6b8513 100644 --- a/sqlx-core/src/postgres/types/chrono/date.rs +++ b/sqlx-postgres/src/types/chrono/date.rs @@ -1,10 +1,8 @@ use crate::decode::Decode; use crate::encode::{Encode, IsNull}; use crate::error::BoxDynError; -use crate::postgres::{ - PgArgumentBuffer, PgHasArrayType, PgTypeInfo, PgValueFormat, PgValueRef, Postgres, -}; use crate::types::Type; +use crate::{PgArgumentBuffer, PgHasArrayType, PgTypeInfo, PgValueFormat, PgValueRef, Postgres}; use chrono::{Duration, NaiveDate}; use std::mem; diff --git a/sqlx-core/src/postgres/types/chrono/datetime.rs b/sqlx-postgres/src/types/chrono/datetime.rs similarity index 96% rename from sqlx-core/src/postgres/types/chrono/datetime.rs rename to sqlx-postgres/src/types/chrono/datetime.rs index ff0fdff4c2..a8740bdbb6 100644 --- a/sqlx-core/src/postgres/types/chrono/datetime.rs +++ b/sqlx-postgres/src/types/chrono/datetime.rs @@ -1,10 +1,8 @@ use crate::decode::Decode; use crate::encode::{Encode, IsNull}; use crate::error::BoxDynError; -use crate::postgres::{ - PgArgumentBuffer, PgHasArrayType, PgTypeInfo, PgValueFormat, PgValueRef, Postgres, -}; use crate::types::Type; +use crate::{PgArgumentBuffer, PgHasArrayType, PgTypeInfo, PgValueFormat, PgValueRef, Postgres}; use chrono::{ DateTime, Duration, FixedOffset, Local, NaiveDate, NaiveDateTime, Offset, TimeZone, Utc, }; diff --git a/sqlx-core/src/postgres/types/chrono/mod.rs b/sqlx-postgres/src/types/chrono/mod.rs similarity index 100% rename from sqlx-core/src/postgres/types/chrono/mod.rs rename to sqlx-postgres/src/types/chrono/mod.rs diff --git a/sqlx-core/src/postgres/types/chrono/time.rs b/sqlx-postgres/src/types/chrono/time.rs similarity index 92% rename from sqlx-core/src/postgres/types/chrono/time.rs rename to sqlx-postgres/src/types/chrono/time.rs index e21550033a..b354b8d9f2 100644 --- a/sqlx-core/src/postgres/types/chrono/time.rs +++ b/sqlx-postgres/src/types/chrono/time.rs @@ -1,10 +1,8 @@ use crate::decode::Decode; use crate::encode::{Encode, IsNull}; use crate::error::BoxDynError; -use crate::postgres::{ - PgArgumentBuffer, PgHasArrayType, PgTypeInfo, PgValueFormat, PgValueRef, Postgres, -}; use crate::types::Type; +use crate::{PgArgumentBuffer, PgHasArrayType, PgTypeInfo, PgValueFormat, PgValueRef, Postgres}; use chrono::{Duration, NaiveTime}; use std::mem; diff --git a/sqlx-core/src/postgres/types/float.rs b/sqlx-postgres/src/types/float.rs similarity index 93% rename from sqlx-core/src/postgres/types/float.rs rename to sqlx-postgres/src/types/float.rs index ca5789ebce..2cb258d916 100644 --- a/sqlx-core/src/postgres/types/float.rs +++ b/sqlx-postgres/src/types/float.rs @@ -3,10 +3,8 @@ use byteorder::{BigEndian, ByteOrder}; use crate::decode::Decode; use crate::encode::{Encode, IsNull}; use crate::error::BoxDynError; -use crate::postgres::{ - PgArgumentBuffer, PgHasArrayType, PgTypeInfo, PgValueFormat, PgValueRef, Postgres, -}; use crate::types::Type; +use crate::{PgArgumentBuffer, PgHasArrayType, PgTypeInfo, PgValueFormat, PgValueRef, Postgres}; impl Type for f32 { fn type_info() -> PgTypeInfo { diff --git a/sqlx-core/src/postgres/types/int.rs b/sqlx-postgres/src/types/int.rs similarity index 96% rename from sqlx-core/src/postgres/types/int.rs rename to sqlx-postgres/src/types/int.rs index e721bab143..0e2c73b0db 100644 --- a/sqlx-core/src/postgres/types/int.rs +++ b/sqlx-postgres/src/types/int.rs @@ -3,10 +3,8 @@ use byteorder::{BigEndian, ByteOrder}; use crate::decode::Decode; use crate::encode::{Encode, IsNull}; use crate::error::BoxDynError; -use crate::postgres::{ - PgArgumentBuffer, PgHasArrayType, PgTypeInfo, PgValueFormat, PgValueRef, Postgres, -}; use crate::types::Type; +use crate::{PgArgumentBuffer, PgHasArrayType, PgTypeInfo, PgValueFormat, PgValueRef, Postgres}; impl Type for i8 { fn type_info() -> PgTypeInfo { diff --git a/sqlx-core/src/postgres/types/interval.rs b/sqlx-postgres/src/types/interval.rs similarity index 98% rename from sqlx-core/src/postgres/types/interval.rs rename to sqlx-postgres/src/types/interval.rs index 39719cab0d..03f99c332a 100644 --- a/sqlx-core/src/postgres/types/interval.rs +++ b/sqlx-postgres/src/types/interval.rs @@ -5,10 +5,8 @@ use byteorder::{NetworkEndian, ReadBytesExt}; use crate::decode::Decode; use crate::encode::{Encode, IsNull}; use crate::error::BoxDynError; -use crate::postgres::{ - PgArgumentBuffer, PgHasArrayType, PgTypeInfo, PgValueFormat, PgValueRef, Postgres, -}; use crate::types::Type; +use crate::{PgArgumentBuffer, PgHasArrayType, PgTypeInfo, PgValueFormat, PgValueRef, Postgres}; // `PgInterval` is available for direct access to the INTERVAL type diff --git a/sqlx-core/src/postgres/types/ipaddr.rs b/sqlx-postgres/src/types/ipaddr.rs similarity index 93% rename from sqlx-core/src/postgres/types/ipaddr.rs rename to sqlx-postgres/src/types/ipaddr.rs index 56fedc5cf1..98898ebca5 100644 --- a/sqlx-core/src/postgres/types/ipaddr.rs +++ b/sqlx-postgres/src/types/ipaddr.rs @@ -5,8 +5,8 @@ use ipnetwork::IpNetwork; use crate::decode::Decode; use crate::encode::{Encode, IsNull}; use crate::error::BoxDynError; -use crate::postgres::{PgArgumentBuffer, PgHasArrayType, PgTypeInfo, PgValueRef, Postgres}; use crate::types::Type; +use crate::{PgArgumentBuffer, PgHasArrayType, PgTypeInfo, PgValueRef, Postgres}; impl Type for IpAddr where diff --git a/sqlx-core/src/postgres/types/ipnetwork.rs b/sqlx-postgres/src/types/ipnetwork.rs similarity index 90% rename from sqlx-core/src/postgres/types/ipnetwork.rs rename to sqlx-postgres/src/types/ipnetwork.rs index 73c776bc60..e4d15ff681 100644 --- a/sqlx-core/src/postgres/types/ipnetwork.rs +++ b/sqlx-postgres/src/types/ipnetwork.rs @@ -5,26 +5,15 @@ use ipnetwork::{IpNetwork, Ipv4Network, Ipv6Network}; use crate::decode::Decode; use crate::encode::{Encode, IsNull}; use crate::error::BoxDynError; -use crate::postgres::{ - PgArgumentBuffer, PgHasArrayType, PgTypeInfo, PgValueFormat, PgValueRef, Postgres, -}; use crate::types::Type; - -// https://github.com/rust-lang/rust/search?q=AF_INET&unscoped_q=AF_INET - -#[cfg(windows)] -const AF_INET: u8 = 2; - -#[cfg(not(any(unix, windows)))] -const AF_INET: u8 = 0; - -#[cfg(unix)] -const AF_INET: u8 = libc::AF_INET as u8; +use crate::{PgArgumentBuffer, PgHasArrayType, PgTypeInfo, PgValueFormat, PgValueRef, Postgres}; // https://github.com/postgres/postgres/blob/574925bfd0a8175f6e161936ea11d9695677ba09/src/include/utils/inet.h#L39 -const PGSQL_AF_INET: u8 = AF_INET; -const PGSQL_AF_INET6: u8 = AF_INET + 1; +// Technically this is a magic number here but it doesn't make sense to drag in the whole of `libc` +// just for one constant. +const PGSQL_AF_INET: u8 = 2; // AF_INET +const PGSQL_AF_INET6: u8 = PGSQL_AF_INET + 1; impl Type for IpNetwork { fn type_info() -> PgTypeInfo { diff --git a/sqlx-core/src/postgres/types/json.rs b/sqlx-postgres/src/types/json.rs similarity index 93% rename from sqlx-core/src/postgres/types/json.rs rename to sqlx-postgres/src/types/json.rs index 523cd29705..60ebe41385 100644 --- a/sqlx-core/src/postgres/types/json.rs +++ b/sqlx-postgres/src/types/json.rs @@ -1,14 +1,12 @@ use crate::decode::Decode; use crate::encode::{Encode, IsNull}; use crate::error::BoxDynError; -use crate::postgres::types::array_compatible; -use crate::postgres::{ - PgArgumentBuffer, PgHasArrayType, PgTypeInfo, PgValueFormat, PgValueRef, Postgres, -}; -use crate::types::{Json, Type}; +use crate::types::array_compatible; +use crate::{PgArgumentBuffer, PgHasArrayType, PgTypeInfo, PgValueFormat, PgValueRef, Postgres}; use serde::{Deserialize, Serialize}; use serde_json::value::RawValue as JsonRawValue; use serde_json::Value as JsonValue; +pub(crate) use sqlx_core::types::{Json, Type}; // diff --git a/sqlx-core/src/postgres/types/lquery.rs b/sqlx-postgres/src/types/lquery.rs similarity index 98% rename from sqlx-core/src/postgres/types/lquery.rs rename to sqlx-postgres/src/types/lquery.rs index ddb05bd43e..c7995c2e9c 100644 --- a/sqlx-core/src/postgres/types/lquery.rs +++ b/sqlx-postgres/src/types/lquery.rs @@ -1,15 +1,15 @@ use crate::decode::Decode; use crate::encode::{Encode, IsNull}; use crate::error::BoxDynError; -use crate::postgres::{PgArgumentBuffer, PgTypeInfo, PgValueFormat, PgValueRef, Postgres}; use crate::types::Type; +use crate::{PgArgumentBuffer, PgTypeInfo, PgValueFormat, PgValueRef, Postgres}; use bitflags::bitflags; use std::fmt::{self, Display, Formatter}; use std::io::Write; use std::ops::Deref; use std::str::FromStr; -use crate::postgres::types::ltree::{PgLTreeLabel, PgLTreeParseError}; +use crate::types::ltree::{PgLTreeLabel, PgLTreeParseError}; /// Represents lquery specific errors #[derive(Debug, thiserror::Error)] diff --git a/sqlx-core/src/postgres/types/ltree.rs b/sqlx-postgres/src/types/ltree.rs similarity index 97% rename from sqlx-core/src/postgres/types/ltree.rs rename to sqlx-postgres/src/types/ltree.rs index a1e7c32563..db6296cd35 100644 --- a/sqlx-core/src/postgres/types/ltree.rs +++ b/sqlx-postgres/src/types/ltree.rs @@ -1,10 +1,8 @@ use crate::decode::Decode; use crate::encode::{Encode, IsNull}; use crate::error::BoxDynError; -use crate::postgres::{ - PgArgumentBuffer, PgHasArrayType, PgTypeInfo, PgValueFormat, PgValueRef, Postgres, -}; use crate::types::Type; +use crate::{PgArgumentBuffer, PgHasArrayType, PgTypeInfo, PgValueFormat, PgValueRef, Postgres}; use std::fmt::{self, Display, Formatter}; use std::io::Write; use std::ops::Deref; diff --git a/sqlx-core/src/postgres/types/mac_address.rs b/sqlx-postgres/src/types/mac_address.rs similarity index 91% rename from sqlx-core/src/postgres/types/mac_address.rs rename to sqlx-postgres/src/types/mac_address.rs index f12e9f8324..8038159c82 100644 --- a/sqlx-core/src/postgres/types/mac_address.rs +++ b/sqlx-postgres/src/types/mac_address.rs @@ -3,10 +3,8 @@ use mac_address::MacAddress; use crate::decode::Decode; use crate::encode::{Encode, IsNull}; use crate::error::BoxDynError; -use crate::postgres::{ - PgArgumentBuffer, PgHasArrayType, PgTypeInfo, PgValueFormat, PgValueRef, Postgres, -}; use crate::types::Type; +use crate::{PgArgumentBuffer, PgHasArrayType, PgTypeInfo, PgValueFormat, PgValueRef, Postgres}; impl Type for MacAddress { fn type_info() -> PgTypeInfo { diff --git a/sqlx-core/src/postgres/types/mod.rs b/sqlx-postgres/src/types/mod.rs similarity index 93% rename from sqlx-core/src/postgres/types/mod.rs rename to sqlx-postgres/src/types/mod.rs index ef6e960f57..8749fe28ba 100644 --- a/sqlx-core/src/postgres/types/mod.rs +++ b/sqlx-postgres/src/types/mod.rs @@ -13,10 +13,12 @@ //! | `f64` | DOUBLE PRECISION, FLOAT8 | //! | `&str`, [`String`] | VARCHAR, CHAR(N), TEXT, NAME | //! | `&[u8]`, `Vec` | BYTEA | +//! | `()` | VOID | //! | [`PgInterval`] | INTERVAL | //! | [`PgRange`](PgRange) | INT8RANGE, INT4RANGE, TSRANGE, TSTZRANGE, DATERANGE, NUMRANGE | //! | [`PgMoney`] | MONEY | -//! +//! | [`PgLTree`] | LTREE | +//! | [`PgLQuery`] | LQUERY | //! //! ### [`bigdecimal`](https://crates.io/crates/bigdecimal) //! Requires the `bigdecimal` Cargo feature flag. @@ -25,8 +27,8 @@ //! |---------------------------------------|------------------------------------------------------| //! | `bigdecimal::BigDecimal` | NUMERIC | //! -//! ### [`decimal`](https://crates.io/crates/rust_decimal) -//! Requires the `decimal` Cargo feature flag. +//! ### [`rust_decimal`](https://crates.io/crates/rust_decimal) +//! Requires the `rust_decimal` Cargo feature flag. //! //! | Rust type | Postgres type(s) | //! |---------------------------------------|------------------------------------------------------| @@ -165,9 +167,10 @@ //! ``` //! -use crate::postgres::type_info::PgTypeKind; -use crate::postgres::{PgTypeInfo, Postgres}; -use crate::types::Type; +use crate::type_info::PgTypeKind; +use crate::{PgTypeInfo, Postgres}; + +pub(crate) use sqlx_core::types::{Json, Type}; mod array; mod bool; @@ -177,6 +180,8 @@ mod int; mod interval; mod lquery; mod ltree; +// Not behind a Cargo feature because we require JSON in the driver implementation. +mod json; mod money; mod oid; mod range; @@ -194,8 +199,8 @@ mod bigdecimal; #[cfg(any(feature = "bigdecimal", feature = "decimal"))] mod numeric; -#[cfg(feature = "decimal")] -mod decimal; +#[cfg(feature = "rust_decimal")] +mod rust_decimal; #[cfg(feature = "chrono")] mod chrono; @@ -206,9 +211,6 @@ mod time; #[cfg(feature = "uuid")] mod uuid; -#[cfg(feature = "json")] -mod json; - #[cfg(feature = "ipnetwork")] mod ipnetwork; diff --git a/sqlx-core/src/postgres/types/money.rs b/sqlx-postgres/src/types/money.rs similarity index 99% rename from sqlx-core/src/postgres/types/money.rs rename to sqlx-postgres/src/types/money.rs index a03bb796ea..60c6323737 100644 --- a/sqlx-core/src/postgres/types/money.rs +++ b/sqlx-postgres/src/types/money.rs @@ -2,8 +2,8 @@ use crate::{ decode::Decode, encode::{Encode, IsNull}, error::BoxDynError, - postgres::{PgArgumentBuffer, PgHasArrayType, PgTypeInfo, PgValueFormat, PgValueRef, Postgres}, types::Type, + {PgArgumentBuffer, PgHasArrayType, PgTypeInfo, PgValueFormat, PgValueRef, Postgres}, }; use byteorder::{BigEndian, ByteOrder}; use std::{ diff --git a/sqlx-core/src/postgres/types/numeric.rs b/sqlx-postgres/src/types/numeric.rs similarity index 98% rename from sqlx-core/src/postgres/types/numeric.rs rename to sqlx-postgres/src/types/numeric.rs index a83a771fda..659699c262 100644 --- a/sqlx-core/src/postgres/types/numeric.rs +++ b/sqlx-postgres/src/types/numeric.rs @@ -1,7 +1,7 @@ -use bytes::Buf; +use sqlx_core::bytes::Buf; use crate::error::BoxDynError; -use crate::postgres::PgArgumentBuffer; +use crate::PgArgumentBuffer; /// Represents a `NUMERIC` value in the **Postgres** wire protocol. #[derive(Debug, PartialEq, Eq)] diff --git a/sqlx-core/src/postgres/types/oid.rs b/sqlx-postgres/src/types/oid.rs similarity index 90% rename from sqlx-core/src/postgres/types/oid.rs rename to sqlx-postgres/src/types/oid.rs index 74d8e3b68b..9841b6034d 100644 --- a/sqlx-core/src/postgres/types/oid.rs +++ b/sqlx-postgres/src/types/oid.rs @@ -1,14 +1,11 @@ use byteorder::{BigEndian, ByteOrder}; -#[cfg(feature = "serde")] use serde::{de::Deserializer, ser::Serializer, Deserialize, Serialize}; use crate::decode::Decode; use crate::encode::{Encode, IsNull}; use crate::error::BoxDynError; -use crate::postgres::{ - PgArgumentBuffer, PgHasArrayType, PgTypeInfo, PgValueFormat, PgValueRef, Postgres, -}; use crate::types::Type; +use crate::{PgArgumentBuffer, PgHasArrayType, PgTypeInfo, PgValueFormat, PgValueRef, Postgres}; /// The PostgreSQL [`OID`] type stores an object identifier, /// used internally by PostgreSQL as primary keys for various system tables. @@ -55,7 +52,6 @@ impl Decode<'_, Postgres> for Oid { } } -#[cfg(feature = "serde")] impl Serialize for Oid { fn serialize(&self, serializer: S) -> Result where @@ -65,7 +61,6 @@ impl Serialize for Oid { } } -#[cfg(feature = "serde")] impl<'de> Deserialize<'de> for Oid { fn deserialize(deserializer: D) -> Result where diff --git a/sqlx-core/src/postgres/types/range.rs b/sqlx-postgres/src/types/range.rs similarity index 98% rename from sqlx-core/src/postgres/types/range.rs rename to sqlx-postgres/src/types/range.rs index fab2ddfc84..9bd0f9b399 100644 --- a/sqlx-core/src/postgres/types/range.rs +++ b/sqlx-postgres/src/types/range.rs @@ -2,16 +2,14 @@ use std::fmt::{self, Debug, Display, Formatter}; use std::ops::{Bound, Range, RangeBounds, RangeFrom, RangeInclusive, RangeTo, RangeToInclusive}; use bitflags::bitflags; -use bytes::Buf; +use sqlx_core::bytes::Buf; use crate::decode::Decode; use crate::encode::{Encode, IsNull}; use crate::error::BoxDynError; -use crate::postgres::type_info::PgTypeKind; -use crate::postgres::{ - PgArgumentBuffer, PgHasArrayType, PgTypeInfo, PgValueFormat, PgValueRef, Postgres, -}; +use crate::type_info::PgTypeKind; use crate::types::Type; +use crate::{PgArgumentBuffer, PgHasArrayType, PgTypeInfo, PgValueFormat, PgValueRef, Postgres}; // https://github.com/postgres/postgres/blob/2f48ede080f42b97b594fb14102c82ca1001b80c/src/include/utils/rangetypes.h#L35-L44 bitflags! { @@ -240,7 +238,7 @@ impl PgHasArrayType for PgRange { } } -#[cfg(feature = "decimal")] +#[cfg(feature = "rust_decimal")] impl PgHasArrayType for PgRange { fn array_type_info() -> PgTypeInfo { PgTypeInfo::NUM_RANGE_ARRAY diff --git a/sqlx-core/src/postgres/types/record.rs b/sqlx-postgres/src/types/record.rs similarity index 96% rename from sqlx-core/src/postgres/types/record.rs rename to sqlx-postgres/src/types/record.rs index 352bdb72b3..18dc395bf0 100644 --- a/sqlx-core/src/postgres/types/record.rs +++ b/sqlx-postgres/src/types/record.rs @@ -1,13 +1,13 @@ -use bytes::Buf; +use sqlx_core::bytes::Buf; use crate::decode::Decode; use crate::encode::Encode; use crate::error::{mismatched_types, BoxDynError}; -use crate::postgres::type_info::{PgType, PgTypeKind}; -use crate::postgres::types::Oid; -use crate::postgres::{PgArgumentBuffer, PgTypeInfo, PgValueFormat, PgValueRef, Postgres}; use crate::type_info::TypeInfo; +use crate::type_info::{PgType, PgTypeKind}; +use crate::types::Oid; use crate::types::Type; +use crate::{PgArgumentBuffer, PgTypeInfo, PgValueFormat, PgValueRef, Postgres}; #[doc(hidden)] pub struct PgRecordEncoder<'a> { diff --git a/sqlx-core/src/postgres/types/decimal.rs b/sqlx-postgres/src/types/rust_decimal.rs similarity index 98% rename from sqlx-core/src/postgres/types/decimal.rs rename to sqlx-postgres/src/types/rust_decimal.rs index 2fd7bff861..7447564c69 100644 --- a/sqlx-core/src/postgres/types/decimal.rs +++ b/sqlx-postgres/src/types/rust_decimal.rs @@ -7,11 +7,9 @@ use rust_decimal::{ use crate::decode::Decode; use crate::encode::{Encode, IsNull}; use crate::error::BoxDynError; -use crate::postgres::types::numeric::{PgNumeric, PgNumericSign}; -use crate::postgres::{ - PgArgumentBuffer, PgHasArrayType, PgTypeInfo, PgValueFormat, PgValueRef, Postgres, -}; +use crate::types::numeric::{PgNumeric, PgNumericSign}; use crate::types::Type; +use crate::{PgArgumentBuffer, PgHasArrayType, PgTypeInfo, PgValueFormat, PgValueRef, Postgres}; impl Type for Decimal { fn type_info() -> PgTypeInfo { diff --git a/sqlx-core/src/postgres/types/str.rs b/sqlx-postgres/src/types/str.rs similarity index 95% rename from sqlx-core/src/postgres/types/str.rs rename to sqlx-postgres/src/types/str.rs index 76e4a71a67..53dda1f446 100644 --- a/sqlx-core/src/postgres/types/str.rs +++ b/sqlx-postgres/src/types/str.rs @@ -1,9 +1,9 @@ use crate::decode::Decode; use crate::encode::{Encode, IsNull}; use crate::error::BoxDynError; -use crate::postgres::types::array_compatible; -use crate::postgres::{PgArgumentBuffer, PgHasArrayType, PgTypeInfo, PgValueRef, Postgres}; +use crate::types::array_compatible; use crate::types::Type; +use crate::{PgArgumentBuffer, PgHasArrayType, PgTypeInfo, PgValueRef, Postgres}; use std::borrow::Cow; impl Type for str { diff --git a/sqlx-core/src/postgres/types/time/date.rs b/sqlx-postgres/src/types/time/date.rs similarity index 89% rename from sqlx-core/src/postgres/types/time/date.rs rename to sqlx-postgres/src/types/time/date.rs index 2763d9a1d6..a320afd721 100644 --- a/sqlx-core/src/postgres/types/time/date.rs +++ b/sqlx-postgres/src/types/time/date.rs @@ -1,11 +1,9 @@ use crate::decode::Decode; use crate::encode::{Encode, IsNull}; use crate::error::BoxDynError; -use crate::postgres::types::time::PG_EPOCH; -use crate::postgres::{ - PgArgumentBuffer, PgHasArrayType, PgTypeInfo, PgValueFormat, PgValueRef, Postgres, -}; +use crate::types::time::PG_EPOCH; use crate::types::Type; +use crate::{PgArgumentBuffer, PgHasArrayType, PgTypeInfo, PgValueFormat, PgValueRef, Postgres}; use std::mem; use time::macros::format_description; use time::{Date, Duration}; diff --git a/sqlx-core/src/postgres/types/time/datetime.rs b/sqlx-postgres/src/types/time/datetime.rs similarity index 95% rename from sqlx-core/src/postgres/types/time/datetime.rs rename to sqlx-postgres/src/types/time/datetime.rs index fc7bc928ad..2d794e5931 100644 --- a/sqlx-core/src/postgres/types/time/datetime.rs +++ b/sqlx-postgres/src/types/time/datetime.rs @@ -1,11 +1,9 @@ use crate::decode::Decode; use crate::encode::{Encode, IsNull}; use crate::error::BoxDynError; -use crate::postgres::types::time::PG_EPOCH; -use crate::postgres::{ - PgArgumentBuffer, PgHasArrayType, PgTypeInfo, PgValueFormat, PgValueRef, Postgres, -}; +use crate::types::time::PG_EPOCH; use crate::types::Type; +use crate::{PgArgumentBuffer, PgHasArrayType, PgTypeInfo, PgValueFormat, PgValueRef, Postgres}; use std::borrow::Cow; use std::mem; use time::macros::format_description; diff --git a/sqlx-core/src/postgres/types/time/mod.rs b/sqlx-postgres/src/types/time/mod.rs similarity index 100% rename from sqlx-core/src/postgres/types/time/mod.rs rename to sqlx-postgres/src/types/time/mod.rs diff --git a/sqlx-core/src/postgres/types/time/time.rs b/sqlx-postgres/src/types/time/time.rs similarity index 92% rename from sqlx-core/src/postgres/types/time/time.rs rename to sqlx-postgres/src/types/time/time.rs index ff25586975..fcd40145bc 100644 --- a/sqlx-core/src/postgres/types/time/time.rs +++ b/sqlx-postgres/src/types/time/time.rs @@ -1,10 +1,8 @@ use crate::decode::Decode; use crate::encode::{Encode, IsNull}; use crate::error::BoxDynError; -use crate::postgres::{ - PgArgumentBuffer, PgHasArrayType, PgTypeInfo, PgValueFormat, PgValueRef, Postgres, -}; use crate::types::Type; +use crate::{PgArgumentBuffer, PgHasArrayType, PgTypeInfo, PgValueFormat, PgValueRef, Postgres}; use std::mem; use time::macros::format_description; use time::{Duration, Time}; diff --git a/sqlx-core/src/postgres/types/time_tz.rs b/sqlx-postgres/src/types/time_tz.rs similarity index 92% rename from sqlx-core/src/postgres/types/time_tz.rs rename to sqlx-postgres/src/types/time_tz.rs index caba64cb7f..b3acf7244b 100644 --- a/sqlx-core/src/postgres/types/time_tz.rs +++ b/sqlx-postgres/src/types/time_tz.rs @@ -1,8 +1,8 @@ use crate::decode::Decode; use crate::encode::{Encode, IsNull}; use crate::error::BoxDynError; -use crate::postgres::{PgArgumentBuffer, PgTypeInfo, PgValueFormat, PgValueRef, Postgres}; use crate::types::Type; +use crate::{PgArgumentBuffer, PgHasArrayType, PgTypeInfo, PgValueFormat, PgValueRef, Postgres}; use byteorder::{BigEndian, ReadBytesExt}; use std::io::Cursor; use std::mem; @@ -33,20 +33,8 @@ pub struct PgTimeTz