diff --git a/.github/workflows/examples.yml b/.github/workflows/examples.yml index 5e90588619..970afa7b37 100644 --- a/.github/workflows/examples.yml +++ b/.github/workflows/examples.yml @@ -28,7 +28,7 @@ jobs: -p sqlx-cli --release --no-default-features - --features mysql,postgres,sqlite,sqlx-toml + --features mysql,postgres,sqlite,sqlx-toml,mysql-rsa - uses: actions/upload-artifact@v4 with: diff --git a/.github/workflows/sqlx-cli.yml b/.github/workflows/sqlx-cli.yml index 5686cb5059..95c7a3edfc 100644 --- a/.github/workflows/sqlx-cli.yml +++ b/.github/workflows/sqlx-cli.yml @@ -14,7 +14,7 @@ jobs: timeout-minutes: 30 steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - name: Setup Rust run: | @@ -51,7 +51,7 @@ jobs: timeout-minutes: 30 steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - name: Setup Rust run: rustup show active-toolchain || rustup toolchain install @@ -76,7 +76,7 @@ jobs: timeout-minutes: 30 steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - name: Setup Rust run: rustup show active-toolchain || rustup toolchain install @@ -156,7 +156,7 @@ jobs: timeout-minutes: 30 steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - name: Setup Rust run: rustup show active-toolchain || rustup toolchain install @@ -228,7 +228,7 @@ jobs: timeout-minutes: 30 steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - name: Setup Rust run: rustup show active-toolchain || rustup toolchain install @@ -322,7 +322,7 @@ jobs: timeout-minutes: 30 steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - name: Setup Rust run: | diff --git a/.github/workflows/sqlx.yml b/.github/workflows/sqlx.yml index b2f81b75ad..394b9efdd3 100644 --- a/.github/workflows/sqlx.yml +++ b/.github/workflows/sqlx.yml @@ -7,13 +7,23 @@ on: - main - "*-dev" +env: + MYSQL_ISOLATED_TESTS: | + it_can_handle_split_packets + rustsec_2024_0363 + PG_ISOLATED_TESTS: | + test_pg_copy_chunked + rustsec_2024_0363 + SQLITE_ISOLATED_TESTS: | + rustsec_2024_0363 + jobs: format: name: Format runs-on: ubuntu-24.04 timeout-minutes: 15 steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - run: rustup component add rustfmt - run: cargo fmt --all -- --check @@ -23,11 +33,14 @@ jobs: strategy: matrix: # Note: because `async-std` is deprecated, we only check it in a single job to save CI time. - runtime: [ async-std, async-global-executor, smol, tokio ] + runtime: [ async-global-executor, smol, tokio ] tls: [ native-tls, rustls, none ] + include: + - runtime: async-std + tls: rustls timeout-minutes: 30 steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 # Swatinem/rust-cache recommends setting up the rust toolchain first because it's used in cache keys - name: Setup Rust @@ -59,7 +72,7 @@ jobs: runs-on: ubuntu-24.04 timeout-minutes: 30 steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - name: Setup Rust run: | rustup show active-toolchain || rustup toolchain install @@ -72,7 +85,7 @@ jobs: runs-on: ubuntu-24.04 timeout-minutes: 30 steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 # https://blog.rust-lang.org/2025/03/02/Rustup-1.28.0.html - name: Setup Rust @@ -128,7 +141,7 @@ jobs: needs: check timeout-minutes: 30 steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - run: mkdir /tmp/sqlite3-lib && wget -O /tmp/sqlite3-lib/ipaddr.so https://github.com/nalgeon/sqlean/releases/download/0.15.2/ipaddr.so @@ -147,12 +160,17 @@ jobs: # Create data dir for offline mode - run: mkdir .sqlx - - run: > - cargo test - --no-default-features - --features any,macros,migrate,${{ matrix.linking }},_unstable-all-types,runtime-${{ matrix.runtime }},${{ matrix.linking == 'sqlite' && 'sqlite-preupdate-hook' || ''}} - -- - --test-threads=1 + - run: | + SKIP_ARGS=() + for test in $SQLITE_ISOLATED_TESTS; do + SKIP_ARGS+=(--skip "$test") + done + cargo test \ + --no-default-features \ + --features any,macros,migrate,${{ matrix.linking }},_unstable-all-types,runtime-${{ matrix.runtime }},${{ matrix.linking == 'sqlite' && 'sqlite-preupdate-hook' || ''}} \ + -- \ + "${SKIP_ARGS[@]}" \ + --test-threads=1 env: DATABASE_URL: sqlite:tests/sqlite/sqlite.db SQLX_OFFLINE_DIR: .sqlx @@ -203,6 +221,37 @@ jobs: RUSTFLAGS: --cfg sqlite_ipaddr LD_LIBRARY_PATH: /tmp/sqlite3-lib + sqlite-isolated: + name: SQLite Isolated + runs-on: ubuntu-24.04 + needs: check + timeout-minutes: 20 + steps: + - uses: actions/checkout@v5 + + - run: mkdir /tmp/sqlite3-lib && wget -O /tmp/sqlite3-lib/ipaddr.so https://github.com/nalgeon/sqlean/releases/download/0.15.2/ipaddr.so + + # https://blog.rust-lang.org/2025/03/02/Rustup-1.28.0.html + - name: Setup Rust + run: rustup show active-toolchain || rustup toolchain install + + - uses: Swatinem/rust-cache@v2 + + - run: | + for test in $SQLITE_ISOLATED_TESTS; do + cargo test \ + --no-default-features \ + --features sqlite,macros,runtime-tokio \ + --test sqlite-rustsec \ + -- \ + --exact "$test" \ + --test-threads=1 + done + env: + DATABASE_URL: sqlite:tests/sqlite/sqlite.db + RUSTFLAGS: --cfg sqlite_ipaddr --cfg sqlite_test_sqlcipher + LD_LIBRARY_PATH: /tmp/sqlite3-lib + postgres: name: Postgres runs-on: ubuntu-24.04 @@ -214,7 +263,7 @@ jobs: needs: check timeout-minutes: 30 steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - name: Setup Rust run: rustup show active-toolchain || rustup toolchain install @@ -237,10 +286,16 @@ jobs: # Create data dir for offline mode - run: mkdir .sqlx - - run: > - cargo test - --no-default-features - --features any,postgres,macros,migrate,_unstable-all-types,runtime-${{ matrix.runtime }},tls-${{ matrix.tls }} + - run: | + SKIP_ARGS=() + for test in $PG_ISOLATED_TESTS; do + SKIP_ARGS+=(--skip "$test") + done + cargo test \ + --no-default-features \ + --features any,postgres,macros,migrate,_unstable-all-types,runtime-${{ matrix.runtime }},tls-${{ matrix.tls }} \ + -- \ + "${SKIP_ARGS[@]}" env: DATABASE_URL: postgres://postgres:password@localhost:5432/sqlx SQLX_OFFLINE_DIR: .sqlx @@ -258,10 +313,16 @@ jobs: RUSTFLAGS: -D warnings --cfg postgres="${{ matrix.postgres }}" - if: matrix.tls != 'none' - run: > - cargo test - --no-default-features - --features any,postgres,macros,migrate,_unstable-all-types,runtime-${{ matrix.runtime }},tls-${{ matrix.tls }} + run: | + SKIP_ARGS=() + for test in $PG_ISOLATED_TESTS; do + SKIP_ARGS+=(--skip "$test") + done + cargo test \ + --no-default-features \ + --features any,postgres,macros,migrate,_unstable-all-types,runtime-${{ matrix.runtime }},tls-${{ matrix.tls }} \ + -- \ + "${SKIP_ARGS[@]}" env: DATABASE_URL: postgres://postgres:password@localhost:5432/sqlx?sslmode=verify-ca&sslrootcert=.%2Ftests%2Fcerts%2Fca.crt SQLX_OFFLINE_DIR: .sqlx @@ -304,7 +365,7 @@ jobs: needs: check timeout-minutes: 30 steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - name: Setup Rust run: rustup show active-toolchain || rustup toolchain install @@ -317,14 +378,52 @@ jobs: - run: | docker exec postgres_${{ matrix.postgres }}_client_ssl bash -c "until pg_isready; do sleep 1; done" - - run: > - cargo test - --no-default-features - --features any,postgres,macros,_unstable-all-types,runtime-${{ matrix.runtime }},tls-${{ matrix.tls }} + - run: | + SKIP_ARGS=() + for test in $PG_ISOLATED_TESTS; do + SKIP_ARGS+=(--skip "$test") + done + cargo test \ + --no-default-features \ + --features any,postgres,macros,_unstable-all-types,runtime-${{ matrix.runtime }},tls-${{ matrix.tls }} \ + -- \ + "${SKIP_ARGS[@]}" env: DATABASE_URL: postgres://postgres@localhost:5432/sqlx?sslmode=verify-ca&sslrootcert=.%2Ftests%2Fcerts%2Fca.crt&sslkey=.%2Ftests%2Fcerts%2Fkeys%2Fclient.key&sslcert=.%2Ftests%2Fcerts%2Fclient.crt RUSTFLAGS: -D warnings --cfg postgres="${{ matrix.postgres }}" + postgres-isolated: + name: Postgres Isolated + runs-on: ubuntu-24.04 + needs: check + timeout-minutes: 20 + steps: + - uses: actions/checkout@v5 + + - name: Setup Rust + run: rustup show active-toolchain || rustup toolchain install + + - uses: Swatinem/rust-cache@v2 + + - run: | + docker compose -f tests/docker-compose.yml run -d -p 5432:5432 --name postgres_17 postgres_17 + docker exec postgres_17 bash -c "until pg_isready; do sleep 1; done" + + # Run isolated Postgres tests to avoid stalling the main job. + - run: | + for test in $PG_ISOLATED_TESTS; do + cargo test \ + --no-default-features \ + --features any,postgres,macros,_unstable-all-types,runtime-tokio,tls-none \ + --test postgres \ + -- \ + --exact "$test" \ + --test-threads=1 + done + env: + DATABASE_URL: postgres://postgres:password@localhost:5432/sqlx + RUSTFLAGS: -D warnings --cfg postgres="17" + mysql: name: MySQL runs-on: ubuntu-24.04 @@ -336,7 +435,7 @@ jobs: needs: check timeout-minutes: 30 steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - name: Setup Rust run: rustup show active-toolchain || rustup toolchain install @@ -346,15 +445,28 @@ jobs: - run: cargo build --features mysql,_unstable-all-types,runtime-${{ matrix.runtime }},tls-${{ matrix.tls }} - run: docker compose -f tests/docker-compose.yml run -d -p 3306:3306 --name mysql_${{ matrix.mysql }} mysql_${{ matrix.mysql }} - - run: sleep 60 + - name: Wait for MySQL + run: | + docker exec mysql_${{ matrix.mysql }} bash -c ' + until (command -v mysqladmin >/dev/null && mysqladmin ping -uroot -ppassword --silent) || \ + (command -v mariadb-admin >/dev/null && mariadb-admin ping -uroot -ppassword --silent); do + sleep 2 + done + ' # Create data dir for offline mode - run: mkdir .sqlx - - run: > - cargo test - --no-default-features - --features any,mysql,macros,migrate,_unstable-all-types,runtime-${{ matrix.runtime }},tls-${{ matrix.tls }} + - run: | + SKIP_ARGS=() + for test in $MYSQL_ISOLATED_TESTS; do + SKIP_ARGS+=(--skip "$test") + done + cargo test \ + --no-default-features \ + --features any,mysql,mysql-rsa,macros,migrate,_unstable-all-types,runtime-${{ matrix.runtime }},tls-${{ matrix.tls }} \ + -- \ + "${SKIP_ARGS[@]}" env: DATABASE_URL: mysql://root:password@localhost:3306/sqlx?ssl-mode=disabled SQLX_OFFLINE_DIR: .sqlx @@ -365,7 +477,7 @@ jobs: cargo test --test mysql-test-attr --no-default-features - --features any,mysql,macros,migrate,_unstable-all-types,runtime-${{ matrix.runtime }},tls-${{ matrix.tls }} + --features any,mysql,mysql-rsa,macros,migrate,_unstable-all-types,runtime-${{ matrix.runtime }},tls-${{ matrix.tls }} env: DATABASE_URL: mysql://root:password@localhost:3306/sqlx?ssl-mode=disabled SQLX_OFFLINE_DIR: .sqlx @@ -373,10 +485,27 @@ jobs: # MySQL 5.7 supports TLS but not TLSv1.3 as required by RusTLS. - if: ${{ !(matrix.mysql == '5_7' && matrix.tls == 'rustls') }} + run: | + SKIP_ARGS=() + for test in $MYSQL_ISOLATED_TESTS; do + SKIP_ARGS+=(--skip "$test") + done + cargo test \ + --no-default-features \ + --features any,mysql,${{ matrix.tls == 'none' && 'mysql-rsa,' || '' }}macros,migrate,_unstable-all-types,runtime-${{ matrix.runtime }},tls-${{ matrix.tls }} \ + -- \ + "${SKIP_ARGS[@]}" + env: + DATABASE_URL: mysql://root:password@localhost:3306/sqlx + SQLX_OFFLINE_DIR: .sqlx + RUSTFLAGS: --cfg mysql_${{ matrix.mysql }} + + # Minimal coverage for mysql-rsa with TLS enabled; RSA should be inert when TLS is used. + - if: ${{ matrix.mysql == '8' && matrix.runtime == 'tokio' && matrix.tls == 'native-tls' }} run: > cargo test --no-default-features - --features any,mysql,macros,migrate,_unstable-all-types,runtime-${{ matrix.runtime }},tls-${{ matrix.tls }} + --features any,mysql,mysql-rsa,macros,migrate,_unstable-all-types,runtime-${{ matrix.runtime }},tls-${{ matrix.tls }} env: DATABASE_URL: mysql://root:password@localhost:3306/sqlx SQLX_OFFLINE_DIR: .sqlx @@ -402,7 +531,7 @@ jobs: cargo test --no-default-features --test mysql-macros - --features any,mysql,macros,migrate,_unstable-all-types,runtime-${{ matrix.runtime }},tls-${{ matrix.tls }} + --features any,mysql,${{ matrix.tls == 'none' && 'mysql-rsa,' || '' }}macros,migrate,_unstable-all-types,runtime-${{ matrix.runtime }},tls-${{ matrix.tls }} env: DATABASE_URL: mysql://root:password@localhost:3306/sqlx SQLX_OFFLINE: true @@ -415,17 +544,66 @@ jobs: run: | docker stop mysql_${{ matrix.mysql }} docker compose -f tests/docker-compose.yml run -d -p 3306:3306 --name mysql_${{ matrix.mysql }}_client_ssl mysql_${{ matrix.mysql }}_client_ssl - sleep 60 + docker exec mysql_${{ matrix.mysql }}_client_ssl bash -c ' + until (command -v mysqladmin >/dev/null && mysqladmin ping -uroot --silent) || \ + (command -v mariadb-admin >/dev/null && mariadb-admin ping -uroot --silent); do + sleep 2 + done + ' - if: ${{ matrix.tls != 'none' }} - run: > - cargo test - --no-default-features - --features any,mysql,macros,migrate,_unstable-all-types,runtime-${{ matrix.runtime }},tls-${{ matrix.tls }} + run: | + SKIP_ARGS=() + for test in $MYSQL_ISOLATED_TESTS; do + SKIP_ARGS+=(--skip "$test") + done + cargo test \ + --no-default-features \ + --features any,mysql,macros,migrate,_unstable-all-types,runtime-${{ matrix.runtime }},tls-${{ matrix.tls }} \ + -- \ + "${SKIP_ARGS[@]}" env: DATABASE_URL: mysql://root@localhost:3306/sqlx?sslmode=verify_ca&ssl-ca=.%2Ftests%2Fcerts%2Fca.crt&ssl-key=.%2Ftests%2Fcerts%2Fkeys%2Fclient.key&ssl-cert=.%2Ftests%2Fcerts%2Fclient.crt RUSTFLAGS: --cfg mysql_${{ matrix.mysql }} + mysql-isolated: + name: MySQL Isolated + runs-on: ubuntu-24.04 + needs: check + timeout-minutes: 20 + steps: + - uses: actions/checkout@v5 + + - name: Setup Rust + run: rustup show active-toolchain || rustup toolchain install + + - uses: Swatinem/rust-cache@v2 + + - run: docker compose -f tests/docker-compose.yml run -d -p 3306:3306 --name mysql_8 mysql_8 + - name: Wait for MySQL + run: | + docker exec mysql_8 bash -c ' + until (command -v mysqladmin >/dev/null && mysqladmin ping -uroot -ppassword --silent) || \ + (command -v mariadb-admin >/dev/null && mariadb-admin ping -uroot -ppassword --silent); do + sleep 2 + done + ' + + # Run isolated MySQL tests to avoid stalling the main job. + - run: | + for test in $MYSQL_ISOLATED_TESTS; do + cargo test \ + --no-default-features \ + --features any,mysql,mysql-rsa,macros,migrate,_unstable-all-types,runtime-tokio,tls-none \ + --test mysql \ + -- \ + --exact "$test" \ + --test-threads=1 + done + env: + DATABASE_URL: mysql://root:password@localhost:3306/sqlx?ssl-mode=disabled + RUSTFLAGS: --cfg mysql_8 + mariadb: name: MariaDB runs-on: ubuntu-24.04 @@ -437,7 +615,7 @@ jobs: needs: check timeout-minutes: 30 steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - name: Setup Rust run: rustup show active-toolchain || rustup toolchain install @@ -447,15 +625,28 @@ jobs: - run: cargo build --features mysql,_unstable-all-types,runtime-${{ matrix.runtime }},tls-${{ matrix.tls }} - run: docker compose -f tests/docker-compose.yml run -d -p 3306:3306 --name mariadb_${{ matrix.mariadb }} mariadb_${{ matrix.mariadb }} - - run: sleep 30 + - name: Wait for MariaDB + run: | + docker exec mariadb_${{ matrix.mariadb }} bash -c ' + until (command -v mysqladmin >/dev/null && mysqladmin ping -uroot -ppassword --silent) || \ + (command -v mariadb-admin >/dev/null && mariadb-admin ping -uroot -ppassword --silent); do + sleep 2 + done + ' # Create data dir for offline mode - run: mkdir .sqlx - - run: > - cargo test - --no-default-features - --features any,mysql,macros,migrate,_unstable-all-types,runtime-${{ matrix.runtime }},tls-${{ matrix.tls }} + - run: | + SKIP_ARGS=() + for test in $MYSQL_ISOLATED_TESTS; do + SKIP_ARGS+=(--skip "$test") + done + cargo test \ + --no-default-features \ + --features any,mysql,macros,migrate,_unstable-all-types,runtime-${{ matrix.runtime }},tls-${{ matrix.tls }} \ + -- \ + "${SKIP_ARGS[@]}" env: DATABASE_URL: mysql://root:password@localhost:3306/sqlx SQLX_OFFLINE_DIR: .sqlx @@ -504,13 +695,24 @@ jobs: run: | docker stop mariadb_${{ matrix.mariadb }} docker compose -f tests/docker-compose.yml run -d -p 3306:3306 --name mariadb_${{ matrix.mariadb }}_client_ssl mariadb_${{ matrix.mariadb }}_client_ssl - sleep 60 + docker exec mariadb_${{ matrix.mariadb }}_client_ssl bash -c ' + until (command -v mysqladmin >/dev/null && mysqladmin ping -uroot --silent) || \ + (command -v mariadb-admin >/dev/null && mariadb-admin ping -uroot --silent); do + sleep 2 + done + ' - if: ${{ matrix.tls != 'none' }} - run: > - cargo test - --no-default-features - --features any,mysql,macros,migrate,_unstable-all-types,runtime-${{ matrix.runtime }},tls-${{ matrix.tls }} + run: | + SKIP_ARGS=() + for test in $MYSQL_ISOLATED_TESTS; do + SKIP_ARGS+=(--skip "$test") + done + cargo test \ + --no-default-features \ + --features any,mysql,macros,migrate,_unstable-all-types,runtime-${{ matrix.runtime }},tls-${{ matrix.tls }} \ + -- \ + "${SKIP_ARGS[@]}" env: DATABASE_URL: mysql://root@localhost:3306/sqlx?sslmode=verify_ca&ssl-ca=.%2Ftests%2Fcerts%2Fca.crt&ssl-key=.%2Ftests%2Fcerts%2Fkeys%2Fclient.key&ssl-cert=.%2Ftests%2Fcerts%2Fclient.crt RUSTFLAGS: --cfg mariadb="${{ matrix.mariadb }}" diff --git a/Cargo.lock b/Cargo.lock index 236039f0ff..c912472b28 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1180,12 +1180,12 @@ dependencies = [ [[package]] name = "deranged" -version = "0.4.0" +version = "0.5.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c9e6a11ca8224451684bc0d7d5a7adbf8f2fd6887261a1cfc3c0432f9d4068e" +checksum = "7cd812cc2bc1d69d4764bd80df88b4317eaef9e773c75226407d9bc0876b211c" dependencies = [ "powerfmt", - "serde", + "serde_core", ] [[package]] @@ -3271,18 +3271,28 @@ dependencies = [ [[package]] name = "serde" -version = "1.0.219" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" +dependencies = [ + "serde_core", + "serde_derive", +] + +[[package]] +name = "serde_core" +version = "1.0.228" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6" +checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.219" +version = "1.0.228" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00" +checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" dependencies = [ "proc-macro2", "quote", @@ -3894,8 +3904,6 @@ dependencies = [ name = "sqlx-mysql" version = "0.9.0-alpha.1" dependencies = [ - "atoi", - "base64 0.22.1", "bigdecimal", "bitflags 2.9.1", "byteorder", @@ -3905,18 +3913,10 @@ dependencies = [ "digest", "dotenvy", "either", - "futures-channel", "futures-core", - "futures-io", "futures-util", "generic-array", - "hex", - "hkdf", - "hmac", - "itoa", "log", - "md-5", - "memchr", "percent-encoding", "rand", "rsa", @@ -3924,10 +3924,8 @@ dependencies = [ "serde", "sha1", "sha2", - "smallvec", "sqlx", "sqlx-core", - "stringprep", "thiserror 2.0.17", "time", "tracing", @@ -4222,30 +4220,30 @@ dependencies = [ [[package]] name = "time" -version = "0.3.41" +version = "0.3.45" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a7619e19bc266e0f9c5e6686659d394bc57973859340060a69221e57dbc0c40" +checksum = "f9e442fc33d7fdb45aa9bfeb312c095964abdf596f7567261062b2a7107aaabd" dependencies = [ "deranged", "itoa", "num-conv", "powerfmt", - "serde", + "serde_core", "time-core", "time-macros", ] [[package]] name = "time-core" -version = "0.1.4" +version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c9e9a38711f559d9e3ce1cdb06dd7c5b8ea546bc90052da6d06bb76da74bb07c" +checksum = "8b36ee98fd31ec7426d599183e8fe26932a8dc1fb76ddb6214d05493377d34ca" [[package]] name = "time-macros" -version = "0.2.22" +version = "0.2.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3526739392ec93fd8b359c8e98514cb3e8e021beb4e5f597b00a0221f8ed8a49" +checksum = "71e552d1249bf61ac2a52db88179fd0673def1e1ad8243a00d9ec9ed71fee3dd" dependencies = [ "num-conv", "time-core", diff --git a/Cargo.toml b/Cargo.toml index c88ab231e2..9aef69d47e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -120,6 +120,7 @@ _sqlite = [] 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"] +mysql-rsa = ["mysql", "sqlx-mysql/rsa", "sqlx-macros?/mysql-rsa"] sqlite = ["sqlite-bundled", "sqlite-deserialize", "sqlite-load-extension", "sqlite-unlock-notify"] # SQLite base features @@ -168,7 +169,7 @@ sqlx-macros-core = { version = "=0.9.0-alpha.1", path = "sqlx-macros-core" } sqlx-macros = { version = "=0.9.0-alpha.1", path = "sqlx-macros" } # Driver crates -sqlx-mysql = { version = "=0.9.0-alpha.1", path = "sqlx-mysql" } +sqlx-mysql = { version = "=0.9.0-alpha.1", path = "sqlx-mysql", default-features = false } sqlx-postgres = { version = "=0.9.0-alpha.1", path = "sqlx-postgres" } sqlx-sqlite = { version = "=0.9.0-alpha.1", path = "sqlx-sqlite" } @@ -214,7 +215,7 @@ default-features = false sqlx-core = { workspace = true, features = ["migrate"] } sqlx-macros = { workspace = true, optional = true } -sqlx-mysql = { workspace = true, optional = true } +sqlx-mysql = { workspace = true, optional = true, default-features = false } sqlx-postgres = { workspace = true, optional = true } sqlx-sqlite = { workspace = true, optional = true } diff --git a/README.md b/README.md index f1e53cdced..e711e4f254 100644 --- a/README.md +++ b/README.md @@ -172,10 +172,13 @@ be removed in the future. - `tls-native-tls`: Use the `native-tls` TLS backend (OpenSSL on *nix, SChannel on Windows, Secure Transport on macOS). - `tls-rustls`: Use the `rustls` TLS backend (cross-platform backend, only supports TLS 1.2 and 1.3). +- `tls-rustls-aws-lc-rs`: Use the `rustls` TLS backend with `aws-lc-rs`. - `postgres`: Add support for the Postgres database server. - `mysql`: Add support for the MySQL/MariaDB database server. +- Note: RSA auth without TLS requires `mysql-rsa` (not enabled by `mysql`). +- `mysql-rsa`: Enable RSA password encryption for `caching_sha2_password`/`sha256_password` when TLS is off. Only enable it if you must connect without TLS to servers that require RSA auth. Prefer using TLS. - `mssql`: Add support for the MSSQL database server. diff --git a/examples/mysql/todos/Cargo.toml b/examples/mysql/todos/Cargo.toml index db8c677980..5606a0fca1 100644 --- a/examples/mysql/todos/Cargo.toml +++ b/examples/mysql/todos/Cargo.toml @@ -6,6 +6,6 @@ workspace = "../../../" [dependencies] anyhow = "1.0" -sqlx = { path = "../../../", features = [ "mysql", "runtime-tokio", "tls-native-tls" ] } +sqlx = { path = "../../../", features = [ "mysql", "mysql-rsa", "runtime-tokio", "tls-native-tls" ] } clap = { version = "4", features = ["derive"] } tokio = { version = "1.20.0", features = ["rt", "macros"]} diff --git a/sqlx-cli/Cargo.toml b/sqlx-cli/Cargo.toml index a18366f982..32ba4d8a20 100644 --- a/sqlx-cli/Cargo.toml +++ b/sqlx-cli/Cargo.toml @@ -61,6 +61,7 @@ native-tls = ["sqlx/tls-native-tls"] # databases mysql = ["sqlx/mysql"] +mysql-rsa = ["sqlx/mysql-rsa"] postgres = ["sqlx/postgres"] sqlite = ["sqlx/sqlite", "_sqlite"] sqlite-unbundled = ["sqlx/sqlite-unbundled", "_sqlite"] diff --git a/sqlx-cli/README.md b/sqlx-cli/README.md index b20461b8fd..ae575d5b5f 100644 --- a/sqlx-cli/README.md +++ b/sqlx-cli/README.md @@ -22,8 +22,14 @@ $ cargo install sqlx-cli --no-default-features --features rustls # only for sqlite and use the system sqlite library $ cargo install sqlx-cli --no-default-features --features sqlite-unbundled + +# if you connect to MySQL/MariaDB without TLS and the server requires RSA auth +$ cargo install sqlx-cli --features mysql-rsa ``` +Add `mysql-rsa` only for non-TLS MySQL/MariaDB connections that use +`caching_sha2_password` or `sha256_password`. If you use TLS, it is not needed. + ## Usage All commands require that a database url is provided. This can be done either with the `--database-url` command line option or by setting `DATABASE_URL`, either in the environment or in a `.env` file diff --git a/sqlx-macros-core/Cargo.toml b/sqlx-macros-core/Cargo.toml index 8702555086..6a9d96d1d9 100644 --- a/sqlx-macros-core/Cargo.toml +++ b/sqlx-macros-core/Cargo.toml @@ -33,6 +33,7 @@ sqlx-toml = ["sqlx-core/sqlx-toml", "sqlx-sqlite?/sqlx-toml"] # database mysql = ["sqlx-mysql"] +mysql-rsa = ["mysql", "sqlx-mysql/rsa"] postgres = ["sqlx-postgres"] sqlite = ["_sqlite", "sqlx-sqlite/bundled"] sqlite-unbundled = ["_sqlite", "sqlx-sqlite/unbundled"] @@ -55,7 +56,7 @@ uuid = ["sqlx-core/uuid", "sqlx-mysql?/uuid", "sqlx-postgres?/uuid", "sqlx-sqlit [dependencies] sqlx-core = { workspace = true, features = ["offline"] } -sqlx-mysql = { workspace = true, features = ["offline", "migrate"], optional = true } +sqlx-mysql = { workspace = true, features = ["offline", "migrate"], optional = true, default-features = false } sqlx-postgres = { workspace = true, features = ["offline", "migrate"], optional = true } sqlx-sqlite = { workspace = true, features = ["offline", "migrate"], optional = true } diff --git a/sqlx-macros/Cargo.toml b/sqlx-macros/Cargo.toml index 95954d72ef..98d4e8b775 100644 --- a/sqlx-macros/Cargo.toml +++ b/sqlx-macros/Cargo.toml @@ -34,6 +34,7 @@ sqlx-toml = ["sqlx-macros-core/sqlx-toml"] # database mysql = ["sqlx-macros-core/mysql"] +mysql-rsa = ["sqlx-macros-core/mysql-rsa"] postgres = ["sqlx-macros-core/postgres"] sqlite = ["sqlx-macros-core/sqlite"] sqlite-unbundled = ["sqlx-macros-core/sqlite-unbundled"] diff --git a/sqlx-mysql/Cargo.toml b/sqlx-mysql/Cargo.toml index c7afc236c6..42f0c9938f 100644 --- a/sqlx-mysql/Cargo.toml +++ b/sqlx-mysql/Cargo.toml @@ -10,10 +10,12 @@ repository.workspace = true rust-version.workspace = true [features] +default = [] json = ["sqlx-core/json", "serde"] any = ["sqlx-core/any"] offline = ["sqlx-core/offline", "serde/derive", "bitflags/serde"] migrate = ["sqlx-core/migrate"] +rsa = ["dep:rand", "dep:rsa"] # Type Integration features bigdecimal = ["dep:bigdecimal", "sqlx-core/bigdecimal"] @@ -26,19 +28,14 @@ uuid = ["dep:uuid", "sqlx-core/uuid"] sqlx-core = { workspace = true } # 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.9" +rand = { version = "0.8.4", default-features = false, features = ["std", "std_rng"], optional = true } +rsa = { version = "0.9", optional = true } sha1 = { version = "0.10.1", default-features = false } sha2 = { version = "0.10.0", default-features = false } @@ -50,20 +47,13 @@ time = { workspace = true, optional = true } uuid = { workspace = true, optional = true } # Misc -atoi = "2.0" -base64 = { version = "0.22.0", default-features = false, features = ["std"] } bitflags = { version = "2", default-features = false } byteorder = { version = "1.4.3", default-features = false, features = ["std"] } bytes = "1.1.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.18" -memchr = { version = "2.4.1", default-features = false } percent-encoding = "2.1.0" -smallvec = "1.7.0" -stringprep = "0.1.2" tracing = { version = "0.1.37", features = ["log"] } dotenvy.workspace = true diff --git a/sqlx-mysql/src/connection/auth.rs b/sqlx-mysql/src/connection/auth.rs index 613f8e702f..0c6a4bf997 100644 --- a/sqlx-mysql/src/connection/auth.rs +++ b/sqlx-mysql/src/connection/auth.rs @@ -2,8 +2,6 @@ use bytes::buf::Chain; use bytes::Bytes; use digest::{Digest, OutputSizeUser}; use generic_array::GenericArray; -use rand::thread_rng; -use rsa::{pkcs8::DecodePublicKey, Oaep, RsaPublicKey}; use sha1::Sha1; use sha2::Sha256; @@ -161,10 +159,7 @@ async fn encrypt_rsa<'s>( xor_eq(&mut pass, &nonce); // client sends an RSA encrypted password - let pkey = parse_rsa_pub_key(rsa_pub_key)?; - let padding = Oaep::new::(); - pkey.encrypt(&mut thread_rng(), padding, &pass[..]) - .map_err(Error::protocol) + rsa_backend::encrypt(rsa_pub_key, &pass) } // XOR(x, y) @@ -185,13 +180,39 @@ fn to_asciz(s: &str) -> Vec { z.into_bytes() } -// https://docs.rs/rsa/0.3.0/rsa/struct.RSAPublicKey.html?search=#example-1 -fn parse_rsa_pub_key(key: &[u8]) -> Result { - let pem = std::str::from_utf8(key).map_err(Error::protocol)?; +#[cfg(feature = "rsa")] +mod rsa_backend { + use rand::thread_rng; + use rsa::{pkcs8::DecodePublicKey, Oaep, RsaPublicKey}; - // This takes advantage of the knowledge that we know - // we are receiving a PKCS#8 RSA Public Key at all - // times from MySQL + use super::Error; - RsaPublicKey::from_public_key_pem(pem).map_err(Error::protocol) + pub(super) fn encrypt(rsa_pub_key: &[u8], pass: &[u8]) -> Result, Error> { + let pkey = parse_rsa_pub_key(rsa_pub_key)?; + let padding = Oaep::new::(); + pkey.encrypt(&mut thread_rng(), padding, pass) + .map_err(Error::protocol) + } + + // https://docs.rs/rsa/0.3.0/rsa/struct.RSAPublicKey.html?search=#example-1 + fn parse_rsa_pub_key(key: &[u8]) -> Result { + let pem = std::str::from_utf8(key).map_err(Error::protocol)?; + + // This takes advantage of the knowledge that we know + // we are receiving a PKCS#8 RSA Public Key at all + // times from MySQL + + RsaPublicKey::from_public_key_pem(pem).map_err(Error::protocol) + } +} + +#[cfg(not(feature = "rsa"))] +mod rsa_backend { + use super::Error; + + pub(super) fn encrypt(_rsa_pub_key: &[u8], _pass: &[u8]) -> Result, Error> { + Err(Error::Configuration( + "RSA auth backend disabled; enable feature `mysql-rsa` (or `rsa` if using sqlx-mysql directly) or use TLS.".into(), + )) + } } diff --git a/tests/README.md b/tests/README.md index bc2dc2327c..019f4c5d4d 100644 --- a/tests/README.md +++ b/tests/README.md @@ -5,14 +5,55 @@ SQLx uses docker to run many compatible database systems for integration testing $ docker run hello-world -Start the databases with `docker-compose` before running tests: +Start the databases with `docker compose` (or `docker-compose`) before running tests: - $ docker-compose up + $ docker compose up -d -Run all tests against all supported databases using: +Run clippy for the check matrix: + + $ ./x.py --clippy + +This runs only the check/clippy matrix and skips unit and integration tests. + +For the full test matrix, run all tests against all supported databases using: $ ./x.py +### Limiting the Matrix + +The full matrix (runtimes, TLS backends, and DB versions) is large. Use the filters in `x.py` to keep runs small. + +List all targets (tags): + + $ ./x.py --list-targets + +Run by prefix (uses `tag.startswith`): + + $ ./x.py --target sqlite_tokio + $ ./x.py --target postgres_17_tokio + $ ./x.py --target mysql_8 + $ ./x.py --target mariadb_10_11 + +Note: integration tags do not include TLS, so a target like `postgres_17_tokio` +still runs all `TLS_VARIANTS`. To limit TLS locally, edit `TLS_VARIANTS` (and +`CHECK_TLS` for the check phase). + +Run exactly one target: + + $ ./x.py --target-exact mysql_8_client_ssl_no_password_tokio + +Run only one integration test binary: + + $ ./x.py --test sqlite + $ ./x.py --test any + +Pass extra args to cargo: + + $ ./x.py -- --nocapture + +To shrink the matrix globally, edit the lists at the top of `tests/x.py`: +`CHECK_TLS`, `TLS_VARIANTS`, `POSTGRES_VERSIONS`, `MYSQL_VERSIONS`, `MARIADB_VERSIONS`. + If you see test failures, or want to run a more specific set of tests against a specific database, you can specify both the features to be tests and the DATABASE_URL. e.g. - $ DATABASE_URL=mysql://root:password@127.0.0.1:49183/sqlx cargo test --no-default-features --features macros,offline,any,all-types,mysql,runtime-async-std-native-tls + $ DATABASE_URL=mysql://root:password@127.0.0.1:49183/sqlx cargo test --no-default-features --features macros,offline,any,all-types,mysql,runtime-async-std-native-tls \ No newline at end of file diff --git a/tests/any/any.rs b/tests/any/any.rs index 71c561cadb..2c57a237ec 100644 --- a/tests/any/any.rs +++ b/tests/any/any.rs @@ -155,12 +155,14 @@ async fn it_can_query_by_string_args() -> sqlx::Result<()> { let ref tuple = ("Hello, world!".to_string(),); #[cfg(feature = "postgres")] - const SQL: &str = - "SELECT 'Hello, world!' as string where 'Hello, world!' in ($1, $2, $3, $4, $5, $6, $7)"; + const SQL: &str = "SELECT 'Hello, world!' \ + FROM (SELECT 1) AS t \ + WHERE 'Hello, world!' IN ($1, $2, $3, $4, $5, $6, $7)"; #[cfg(not(feature = "postgres"))] - const SQL: &str = - "SELECT 'Hello, world!' as string where 'Hello, world!' in (?, ?, ?, ?, ?, ?, ?)"; + const SQL: &str = "SELECT 'Hello, world!' \ + FROM (SELECT 1) AS t \ + WHERE 'Hello, world!' IN (?, ?, ?, ?, ?, ?, ?)"; { let query = sqlx::query(SQL) diff --git a/tests/certs/README.md b/tests/certs/README.md index add100625b..78bc0bc87a 100644 --- a/tests/certs/README.md +++ b/tests/certs/README.md @@ -14,6 +14,12 @@ These certificates should be valid until the year 2035. RusTLS requires TLS certificates to be x509v3. OpenSSL 3.2 and up create v3 certificates by default. +### MySQL 5.7 (RSA) + +The default test certificates in this directory use Ed25519, which MySQL 5.7 +cannot load. We keep a separate RSA CA/client/server set under +`tests/certs/rsa` and use it only for the MySQL 5.7 client-SSL targets. + ## (Re)generating When generating certificates, OpenSSL prompts for a number of fields: diff --git a/tests/certs/rsa/ca.crt b/tests/certs/rsa/ca.crt new file mode 100644 index 0000000000..bafecf8bfa --- /dev/null +++ b/tests/certs/rsa/ca.crt @@ -0,0 +1,21 @@ +-----BEGIN CERTIFICATE----- +MIIDizCCAnOgAwIBAgIUYWAIZEOv172fTkUC1LZvQbdmUcYwDQYJKoZIhvcNAQEL +BQAwVTELMAkGA1UEBhMCdXMxEzARBgNVBAgMCmNhbGlmb3JuaWExEDAOBgNVBAoM +B1NRTHgucnMxHzAdBgNVBAMMFlNRTHggTXlTUUwgNS43IFRlc3QgQ0EwHhcNMjYw +MTE3MjA0OTQzWhcNMzYwMTE1MjA0OTQzWjBVMQswCQYDVQQGEwJ1czETMBEGA1UE +CAwKY2FsaWZvcm5pYTEQMA4GA1UECgwHU1FMeC5yczEfMB0GA1UEAwwWU1FMeCBN +eVNRTCA1LjcgVGVzdCBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEB +AJlTKGk0hm+kuaX7AXFPt7z3EgkAakqEF7zpz6oyKohgQ/favYUgFW36A27VCpLs +eUhzRvlYUaLjdbMiSZMcDLyFMPQysFR6XFmB/loUppEhWGUCY/2qbUlnCd3jj9vB +qSuJ4KkVHo9HMeivLhAxEiFQ60iYgQ1dC+cOXxXckDUAqr1ZRVCwpkqo5VKyi5Ft +2jo2I8q20pAbnPteQJcWwF4zdKD989WoToZyFEsHGSNJvx7qsXnbbQV9ZfVD2gQO +3ac8rO746lq/Mv4hvX7UmRE3N/wrJvsE6wkHLOGwqs79AlWfUR/7PDxf2Qolaujf +e7ZSWVmvsG4YsHLYbFgt0rECAwEAAaNTMFEwHQYDVR0OBBYEFOJi5+EmSMZYTpnK +IEdk7FyA6RsUMB8GA1UdIwQYMBaAFOJi5+EmSMZYTpnKIEdk7FyA6RsUMA8GA1Ud +EwEB/wQFMAMBAf8wDQYJKoZIhvcNAQELBQADggEBABiC7AG9C8HmMEHEFgK0Vu96 +JKnWrObjINR07VfxLNpQ8lCSTdxVj6f4gNA4rfWuoai5+9us/nVQEHWwul7OZM5s +nrrh6G4xF6gWETpCj7Psro33tA3P9gjYanf9pBA8cEnjSN9mZdR+EToNkWwqYa25 +KnNUSV8dAmOGWkFinqQh1buR5BL55muffKL91rDtsbjVuS+FKfXUF6RR7+MwHuGz +KSuK5jN2lXuXryyuCLqKdEP5Hzi453M1EbSnbFPNnNihVsO7IEbyrvsrdDoo2KwQ +kZsWZxdT7CnVkVVUDrgJTql4suDYdcJkY/wAm8lPDf+oHyEBEXoNCmW31pKu6o8= +-----END CERTIFICATE----- diff --git a/tests/certs/rsa/client.crt b/tests/certs/rsa/client.crt new file mode 100644 index 0000000000..b18a9268ef --- /dev/null +++ b/tests/certs/rsa/client.crt @@ -0,0 +1,19 @@ +-----BEGIN CERTIFICATE----- +MIIDJzCCAg8CFAxjCSTLziRQ3tsnEl9kRYWx+mDmMA0GCSqGSIb3DQEBCwUAMFUx +CzAJBgNVBAYTAnVzMRMwEQYDVQQIDApjYWxpZm9ybmlhMRAwDgYDVQQKDAdTUUx4 +LnJzMR8wHQYDVQQDDBZTUUx4IE15U1FMIDUuNyBUZXN0IENBMB4XDTI2MDExNzIw +NTAxMFoXDTM2MDExNTIwNTAxMFowSzELMAkGA1UEBhMCdXMxEzARBgNVBAgMCmNh +bGlmb3JuaWExEDAOBgNVBAoMB1NRTHgucnMxFTATBgNVBAMMDG15c3FsLWNsaWVu +dDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALMvA+vqxPuTVtX/9uFH +Up7Wkx5iaFBiC2x3wTLZX1WavknU/aw8jYQabgwVbiCaCvjoncPtQt1abtzBvPbL +HpXC6h91D0hpMabzr8HyRutPQ09MK2OkIQvIJZPFu8CKbDdSsD2uqgSEjvAeFxKv +v1JdL24ha+hoojTnG+Of5qzPgIKeuQrUa5lvB0RVpxxs86tjENZXsqgYVtCD3i9u +ne6DGcTn0sMZTjQiHll0HGbTPEOY0CzIp+xoAOrTfVXeAVhi+B90U31q371X70+H +qa6R8KmsJ4f+qWn26yJoZacLEcO97IPZLxo+zEegDSDIWfiCDb8o+JGdjpMDmsvS +XIECAwEAATANBgkqhkiG9w0BAQsFAAOCAQEAOBdK+gBtdy8hAF313FCBCNAhe5eF +jSVUt0B+sknqXh5W2cx8J0cfyiL2/HgID5t4d1JaNOUt+3Pv2XDWgp8zQ7Tqob+j +Rp0M//IuBAJgyBkN6E+Xok+4sIX8pRJ0fYPzHPU7LbnCcKb6tc5MnY6wjtLM1I8F +ayCwXIEdXcsvPey98kWnmwJu4QHpjBkvAs6NEGWbW2ZLdm0URdAUdBlgv/sSNnV9 +4UTHVvjk1/aZOC2BTtcNvLO+8qXRkeWy/YMEFKMWtcF19uXOS39jTtjeRnyNhosy +g6yFsDbcEU+TnVtRA0lji+DsRJ3JBLT6UKmIY45tMYMDqlDm77MwLcH1sw== +-----END CERTIFICATE----- diff --git a/tests/certs/rsa/keys/ca.key b/tests/certs/rsa/keys/ca.key new file mode 100644 index 0000000000..e8ac8881a8 --- /dev/null +++ b/tests/certs/rsa/keys/ca.key @@ -0,0 +1,28 @@ +-----BEGIN PRIVATE KEY----- +MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCZUyhpNIZvpLml ++wFxT7e89xIJAGpKhBe86c+qMiqIYEP32r2FIBVt+gNu1QqS7HlIc0b5WFGi43Wz +IkmTHAy8hTD0MrBUelxZgf5aFKaRIVhlAmP9qm1JZwnd44/bwakrieCpFR6PRzHo +ry4QMRIhUOtImIENXQvnDl8V3JA1AKq9WUVQsKZKqOVSsouRbdo6NiPKttKQG5z7 +XkCXFsBeM3Sg/fPVqE6GchRLBxkjSb8e6rF5220FfWX1Q9oEDt2nPKzu+OpavzL+ +Ib1+1JkRNzf8Kyb7BOsJByzhsKrO/QJVn1Ef+zw8X9kKJWro33u2UllZr7BuGLBy +2GxYLdKxAgMBAAECggEAHNJYfNpOUStOaKiT/1hkaiWpor6MvIAzNCRhkJVIkIVE +EZHxYVaEIL3IKmvqxm6kZ92foFydT/jhFbDi0sAJluCUsLrckazEsmCwzv8lxo9V +nftCj5sbWxp+7NKLptwzMEeFT1N0gKt58ssHZizLQy8CY42jaL8ubxsw/ZuOEiBI +4bz+tr17UWxA08K4jmxM+un+wUSAa1X6+j8DqqdvAyGXoHRoyweQQ8gC87c7pIal +GMIk+Bk1izILR2SdgoOdLiTASpYQEr6x3/+A/ZGixlD6tlM2twuKWD3LuGhDCZ/V +I0+3MQxO8PPKdb3DaB5ReRyVyuNTpaH7SxKnbV9vzwKBgQDQzWTxnsEDf2zCfsO+ +yDntH9tTigI+WMt6nVX4lV3jy6fnqa+6zPuYxes6RLRMkJZMud1S3b35FNW9lQHd +CYxS4eRO1DnrRWFgj8VGJQ97d4wqSg5YLoB/de/h6JN7C2SctsmCXm9qL0L5wXrx +DS5ZWLe//gKIDPU8m8yMEvbkjwKBgQC7+31p/Lgu39DqhiRCrbwMShOgu9JqTlmN +LroJJ2eZA931gnK3wysWlHpJBu6+/ObippEW9IJ+NRecYHe/jMyL1fCCm6iCUu6o +E8s2u8lzltOQSskC6PBn9p/kM3mmC+mhlA2qFYoInT+2OMZhbFup96fb1Yc5FmPR +/k0QKE/0vwKBgQDP97OSANgn3rP56H6YuB8R8gfm5e+kH5bTkn/9bvAsIj0jPVx9 +RwtVN9Q5nhKiq+Q3mWw6zAcaXskg4ZgQiyELsFhQt4rUra72mVwYqHMKO6EMweQV +qoNr8JCzxo2WIVvdxyVfxyVbcqVX04DbNJC0huvFu37T+WwNKPSLk5v7OwKBgCis +TYJ1L9TUkHtt8sKKnLl7/as1eF2P/khR5+a7I+szrv7D7tZb4CLOlXbfjSC9z6cS +qynwVZvBGQ64wLAtYsSO0a8wxtEL6J9tSPbawsfDxprd04hRplKYRhg2GwgWY8KW +Ki624lriyzo+Jo5Fx7+K2kLyfIOZmJeDEmGAl2w5AoGALtFWXrHFNuHqFAk7oRBk +m6IgHrWf4SAH13fbReROk3nAhZwRBUIoFBSBJA8i0z3kjg01lm3rk64h5SwB7WIg +As+x0QYVlQifR7Lv0kQbtN55GsQYYyj4DUPTqVvZXSm88yzJ0TqFWLd5RdhFZviC +ZPF8adFIzEAnRXoEH6d98Og= +-----END PRIVATE KEY----- diff --git a/tests/certs/rsa/keys/client.key b/tests/certs/rsa/keys/client.key new file mode 100644 index 0000000000..1591d585f8 --- /dev/null +++ b/tests/certs/rsa/keys/client.key @@ -0,0 +1,28 @@ +-----BEGIN PRIVATE KEY----- +MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQCzLwPr6sT7k1bV +//bhR1Ke1pMeYmhQYgtsd8Ey2V9Vmr5J1P2sPI2EGm4MFW4gmgr46J3D7ULdWm7c +wbz2yx6VwuofdQ9IaTGm86/B8kbrT0NPTCtjpCELyCWTxbvAimw3UrA9rqoEhI7w +HhcSr79SXS9uIWvoaKI05xvjn+asz4CCnrkK1GuZbwdEVaccbPOrYxDWV7KoGFbQ +g94vbp3ugxnE59LDGU40Ih5ZdBxm0zxDmNAsyKfsaADq031V3gFYYvgfdFN9at+9 +V+9Ph6mukfCprCeH/qlp9usiaGWnCxHDveyD2S8aPsxHoA0gyFn4gg2/KPiRnY6T +A5rL0lyBAgMBAAECggEABX/ZnvZMgyMCh0XBlyh0qdkQqt4RMPiszswCtEOuhM2E +Li2kdu33IM2u3B3MQ30JIlFvGG/CC1JNgnkCCpEMzPmcNe09QbKLHLk5YpXocr4g +m+C6mMIDHRBQqThoZjdHGv3cauOoVDIGejOWytd1dX9mrB81535zMuNSrqnL/O94 +0lmIn3iIwqGY0Hcc1Oed4GnOp0mSbjQfJ9SrBNTYS0BrbnYeO0B+E0W6nc/AfTRw +JLri5zWl+iQpiKjtvu9ABmLMeI5NrFzsZaUOPnTaJ3XKAr5tUWfB/pnjWf07JhRZ +5z97qjx1UTcjNiy/xCS0BzHm11FlOviBtNLF9NFEAQKBgQDnl4Pxg4Fmo/aCDL4E +so/YMfXJEH1qaGmtaI8pwsWtvlTQeflFYvNNZqSihe+YzkdrOWdIqBQS5vyjS/pl +yDhUU9ZUNWrtJBgo7bPHLKXP5t2asfvvHEp7YPZH6WWsJW2P8cslWmyu9lHIO9S5 +rdcTHfd+pKTK5fVZGCBsxWEXfwKBgQDGEX7pADH4xjBfbWt4KCJH1Yoy+p+8ftkt +/nN4KS+0P6dTne9Dmekt/wrX4n0qrJLYTzCO0LU7KDOgD7K/3JMwqgIYSYnFSEPt +LNaSKXhRhazgOhr55YfR4WGNfixApgrkICwB2iKqnkSZCu0wIqogr1BWE1vj84I7 +WNzcxKaL/wKBgQDEeEeZJkUq/EJuRb0WYx2g/ZFUB8c99GJimGeLuA7XvLZbPn74 +HF/n9AILVrDS43y3PDWg7+ZHuuns5tIAcwFGmPEk80RI9ewBHNb9S6VHYMXzLLdc +PJX7YWDN1PVKO15dVXVPtQyqyZDL2+Y1t4LUVwHV0Ht1He0srkkjvbcGpQKBgQCS +c5lNGzHX6mMWDEfsfnBqgQBAlYPK0lgvY/dpH7sAIhjNAPhLGeCKfAw+eF9oUFX7 +zwHud2+poB4b+b+Hkcbbsrj90FIoJzjig8bcKAGo9ZhP62bK4+a7T1TcVDDQVHW1 +G/yuGeaMFZ5PMv8SGm+E31wdaQ8Gy6S90QTt0BH9bQKBgFuOaNc+ByJl6zLpxoXI +Xtdd0jtzIwMHz6XOi+fn1BaCaadB+WWKbp8Si6WnkHXAckFIRLt6n86Pb4v79kVB +GKY/saNvtv8FWx9IfKCc/utrTm27J+OhAQhvChrrb4kGpbm8iBqX1mNstDcaKS5x +I6RrWqv3aLoo0Gxvy01sRaC0 +-----END PRIVATE KEY----- diff --git a/tests/certs/rsa/keys/server.key b/tests/certs/rsa/keys/server.key new file mode 100644 index 0000000000..8d7a8e5419 --- /dev/null +++ b/tests/certs/rsa/keys/server.key @@ -0,0 +1,28 @@ +-----BEGIN PRIVATE KEY----- +MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQDQMXlx6SQejGgE +viUQMUb40KkFixRo0FlHb4mONLPbMZ+TCNGt1OziLe2pPJnnfJPdq3miKza2iuxX +sB8e9wL2XVra3HtD1qlbl12vkPx8DHO2NNh3BYFgDij9CNZNAaD2O0IjDtBkhcv+ +yOYUoEnehtu4KyoiYcZbD1iASxJtiyK6fNWIWFRxYR+AF6t0BKEQKSivbiXtkawc +asXxelC8nupIwijgEms3CRabyDnMkJnWJ3wPuTDzQr7g3xW9NQ/rkKfqnw96rQJV +K1OxLr3c4D7/7T5Dt6glJijghCqgUrc2I8Z1cEdDWEJ2rED4a9CztW5nTenfc9pb +hw/yTue5AgMBAAECggEABa4/PZxSdy/BKVWiXNfJXiyRsL7ny0iEZ8fLdC5sNL2H +g1YCOaOQZJyt9M2Rt1B4V5Fa7TxlCWlO6QFwsfIPD2ztQmucb0Bjfdt/wQn8LQAp +JUY5x1Kb7M8/b1BkdN9dNobCp4Kl7UOTf76DJ6TW1dFucURNPvkAa2zmucEX+JY+ +ug4ZvKY7EH7vV5Hr96tfCz2oCcbtJn3NBODE0Nq6FYxzZsEMsMmAFDDutncBZVQl +3K77te2v2UussYuFqae0t2+VUADJ6Jc3oNHWKAvGG0zSa/W4yAFVc5feJzD4I/qD +vEVZOQRkUmjcDZwI5mGgwFdSf/I8c/HFh71W1H/1MQKBgQDzoWntgSU8TNfhj2s1 +bq+hCxOs9ytuH7TALd4DoqGTr7y79d0PCYUCOHHY7cXdVUTIRgyshAKHkiqsmE1w +M3CsyU1uPvp0zIqIHeddjdwy8rNrrvlQ0KaUE/KElSfXhjPtE5TdFm1xkrCJBW9W +TLbcwklXzyWLt778QMOyeSXWEQKBgQDaw3cKcjpxQdqrZzbU71B+69E7yNMU2Jnb +B/9A2xPlb4omkFdqi0naEENySClPCeXR0E9v6+hq87pzx6JlfYu22CJk5f78lUpS +dbZQYaSQPKO5Unik5oPJHY6oA5iYYAaZxxEj9GsAMHWNV27FVHPoz5J9Z40Hm12X +YDt7/UqvKQKBgFqUUrvY3i0zLLhSCDwPcQDhC2mtY9pHs34YD4kueABewD7pxEyI +74jJz5olnQETaMVFNgUV95LMB02wOmpS1buIBF/OznOKcJ7270RbL9lJXufUYCFp +0eUQHYSpp+x7muaz9w7T/dDSBwyKlsBxOTOOkJIzE/SEVl+W/KtoW2bhAoGBAKUx +72WbBpjZ4teGNHitUrrVNoYPy521RtGIg28lQCwEg21FmE1ja1xY5aWZ6l++GKbM +x/+7RCHndMfTW8WJ/YQQSECrEVcJITuNmiOu6EbnE7dxGJtlWuT3Be/H72Y5NSLQ +mRfujRJyhYI7IPGwKWsHvBYoqO2ynAUgbSrfBZOpAoGAPUTE0jjRchngnR9hdfQZ +ztwXq2vpfaQd8vyYiNkihNGu3h4MVGvm86zw7288AMwd2ArEYVccqcqM9KdPulee +WcPxKHV3dQirldwlk2ZErUhFRDO3BORwChm4uIkjN9yfOOaX4kOixllTMTVb19WX +SE6T72rWH84Kev/aPsHFMao= +-----END PRIVATE KEY----- diff --git a/tests/certs/rsa/server.crt b/tests/certs/rsa/server.crt new file mode 100644 index 0000000000..7ebc7f7454 --- /dev/null +++ b/tests/certs/rsa/server.crt @@ -0,0 +1,21 @@ +-----BEGIN CERTIFICATE----- +MIIDfzCCAmegAwIBAgIUDGMJJMvOJFDe2ycSX2RFhbH6YOUwDQYJKoZIhvcNAQEL +BQAwVTELMAkGA1UEBhMCdXMxEzARBgNVBAgMCmNhbGlmb3JuaWExEDAOBgNVBAoM +B1NRTHgucnMxHzAdBgNVBAMMFlNRTHggTXlTUUwgNS43IFRlc3QgQ0EwHhcNMjYw +MTE3MjA0OTU2WhcNMzYwMTE1MjA0OTU2WjBGMQswCQYDVQQGEwJ1czETMBEGA1UE +CAwKY2FsaWZvcm5pYTEQMA4GA1UECgwHU1FMeC5yczEQMA4GA1UEAwwHc3FseC5y +czCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBANAxeXHpJB6MaAS+JRAx +RvjQqQWLFGjQWUdviY40s9sxn5MI0a3U7OIt7ak8med8k92reaIrNraK7FewHx73 +AvZdWtrce0PWqVuXXa+Q/HwMc7Y02HcFgWAOKP0I1k0BoPY7QiMO0GSFy/7I5hSg +Sd6G27grKiJhxlsPWIBLEm2LIrp81YhYVHFhH4AXq3QEoRApKK9uJe2RrBxqxfF6 +ULye6kjCKOASazcJFpvIOcyQmdYnfA+5MPNCvuDfFb01D+uQp+qfD3qtAlUrU7Eu +vdzgPv/tPkO3qCUmKOCEKqBStzYjxnVwR0NYQnasQPhr0LO1bmdN6d9z2luHD/JO +57kCAwEAAaNWMFQwEgYDVR0RBAswCYIHc3FseC5yczAdBgNVHQ4EFgQU+AloROBZ +cbZ3TX5lZGxB6E8IF+EwHwYDVR0jBBgwFoAU4mLn4SZIxlhOmcogR2TsXIDpGxQw +DQYJKoZIhvcNAQELBQADggEBADS2doCZzWhSaqHjycgs4KkmCd3rORNL+U6QbPTk +5fjPvO0Ni6kXOYfCnyhocelyhc3dKt8Kvkusqg/fNmeRAj3BNo4cKlsnycdCezvg +vZBxVB9DnV2NcCmFRN4uKSqGszJ5mtCHbgWxJWnP+48qpWVUNi0dZSL7SYRr/eou +3WancEOJ3OkUAZssUNSNloXkhpyzvFkLd1btLCSnyGpE2pACfpX5JiRrX/qkRKaM +IP2l5s/5QDRhRde85PvKqWCQkzIh5wR5qwZfKx/n0ZjtEWDvu0fzdpFdgag03Q2L +vZZ8+9RqwQ1E8KB6dLQjZSbi71UbUqVN97535w9oYPOLY8k= +-----END CERTIFICATE----- diff --git a/tests/docker-compose.yml b/tests/docker-compose.yml index c2ccdabef6..e03eeeea67 100644 --- a/tests/docker-compose.yml +++ b/tests/docker-compose.yml @@ -47,6 +47,8 @@ services: dockerfile: mysql/Dockerfile args: IMAGE: mysql:5.7 + SSL_CERT_DIR: certs/rsa + SSL_KEY_DIR: certs/rsa/keys volumes: - "./mysql/setup.sql:/docker-entrypoint-initdb.d/setup.sql:z" ports: diff --git a/tests/docker.py b/tests/docker.py index 5e8c74fb1f..6d7c2663b0 100644 --- a/tests/docker.py +++ b/tests/docker.py @@ -12,6 +12,14 @@ # start database server and return a URL to use to connect +def docker_compose_command(): + if shutil.which("docker-compose"): + return ["docker-compose"] + if shutil.which("docker"): + return ["docker", "compose"] + return None + + def start_database(driver, database, cwd): if driver == "sqlite": database = path.join(cwd, database) @@ -22,8 +30,13 @@ def start_database(driver, database, cwd): # short-circuit for sqlite return f"sqlite://{path.join(cwd, new_database)}?mode=rwc" + compose_cmd = docker_compose_command() + if compose_cmd is None: + raise FileNotFoundError("docker-compose or docker compose not found") + + compose_args = [*compose_cmd, "-p", "sqlx"] res = subprocess.run( - ["docker-compose", "up", "-d", driver], + [*compose_args, "up", "-d", driver], stdout=subprocess.PIPE, stderr=subprocess.PIPE, cwd=dir_tests, @@ -35,6 +48,21 @@ def start_database(driver, database, cwd): if b"done" in res.stderr: time.sleep(30) + res = subprocess.run( + [*compose_args, "ps", "-q", driver], + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + cwd=dir_tests, + ) + + if res.returncode != 0: + print(res.stderr, file=sys.stderr) + raise RuntimeError(f"failed to resolve container for {driver}") + + container_id = res.stdout.strip().decode() + if not container_id: + raise RuntimeError(f"no container found for {driver}") + # determine appropriate port for driver if driver.startswith("mysql") or driver.startswith("mariadb"): port = 3306 @@ -46,9 +74,9 @@ def start_database(driver, database, cwd): raise NotImplementedError # find port + format_arg = f"{{{{(index (index .NetworkSettings.Ports \"{port}/tcp\") 0).HostPort}}}}" res = subprocess.run( - ["docker", "inspect", f"-f='{{{{(index (index .NetworkSettings.Ports \"{port}/tcp\") 0).HostPort}}}}'", - f"sqlx_{driver}_1"], + ["docker", "inspect", "-f", format_arg, container_id], stdout=subprocess.PIPE, stderr=subprocess.PIPE, cwd=dir_tests, @@ -57,18 +85,23 @@ def start_database(driver, database, cwd): if res.returncode != 0: print(res.stderr, file=sys.stderr) - port = int(res.stdout[1:-2].decode()) + port = int(res.stdout.decode().strip()) # need additional permissions to connect to MySQL when using SSL - res = subprocess.run( - ["docker", "exec", f"sqlx_{driver}_1", "mysql", "-u", "root", "-e", "GRANT ALL PRIVILEGES ON *.* TO 'root' WITH GRANT OPTION;"], - stdout=subprocess.PIPE, - stderr=subprocess.PIPE, - cwd=dir_tests, - ) - - if res.returncode != 0: - print(res.stderr, file=sys.stderr) + if driver.startswith("mysql") or driver.startswith("mariadb"): + mysql_args = ["docker", "exec", container_id, "mysql", "-u", "root"] + if not driver.endswith("client_ssl"): + mysql_args.append("-ppassword") + mysql_args.extend(["-e", "GRANT ALL PRIVILEGES ON *.* TO 'root' WITH GRANT OPTION;"]) + res = subprocess.run( + mysql_args, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + cwd=dir_tests, + ) + + if res.returncode != 0: + print(res.stderr, file=sys.stderr) # do not set password in URL if authenticating using SSL key file if driver.endswith("client_ssl"): diff --git a/tests/mysql/Dockerfile b/tests/mysql/Dockerfile index b80618230f..46eddd8f6f 100644 --- a/tests/mysql/Dockerfile +++ b/tests/mysql/Dockerfile @@ -1,11 +1,15 @@ ARG IMAGE FROM ${IMAGE} +# Allow using alternate cert sets for older server images (e.g. MySQL 5.7). +ARG SSL_CERT_DIR=certs +ARG SSL_KEY_DIR=certs/keys + # Copy SSL certificate (and key) -COPY certs/server.crt /etc/mysql/ssl/server.crt -COPY certs/ca.crt /etc/mysql/ssl/ca.crt -COPY certs/keys/server.key /etc/mysql/ssl/server.key -COPY mysql/my.cnf /etc/mysql/my.cnf +COPY ${SSL_CERT_DIR}/server.crt /etc/mysql/ssl/server.crt +COPY ${SSL_CERT_DIR}/ca.crt /etc/mysql/ssl/ca.crt +COPY ${SSL_KEY_DIR}/server.key /etc/mysql/ssl/server.key +COPY mysql/my.cnf /etc/mysql/conf.d/sqlx-ssl.cnf # Fix permissions RUN chown mysql:mysql /etc/mysql/ssl/server.crt /etc/mysql/ssl/server.key diff --git a/tests/mysql/error.rs b/tests/mysql/error.rs index f75e9513a6..bd2cbb9ec0 100644 --- a/tests/mysql/error.rs +++ b/tests/mysql/error.rs @@ -1,6 +1,26 @@ use sqlx::{error::ErrorKind, mysql::MySql, Connection, Error}; use sqlx_test::new; +fn mysql_supports_check_constraints(version: &str) -> bool { + if version.contains("MariaDB") { + return true; + } + + let numeric = match version.split(|c| c == '-' || c == ' ').next() { + Some(numeric) => numeric, + None => return false, + }; + let mut parts = numeric.split('.'); + let major: u64 = match parts.next().and_then(|part| part.parse().ok()) { + Some(major) => major, + None => return false, + }; + let minor: u64 = parts.next().unwrap_or("0").parse().unwrap_or_default(); + let patch: u64 = parts.next().unwrap_or("0").parse().unwrap_or_default(); + + (major, minor, patch) >= (8, 0, 16) +} + #[sqlx_macros::test] async fn it_fails_with_unique_violation() -> anyhow::Result<()> { let mut conn = new::().await?; @@ -62,6 +82,13 @@ async fn it_fails_with_check_violation() -> anyhow::Result<()> { let mut conn = new::().await?; let mut tx = conn.begin().await?; + let version: String = sqlx::query_scalar("SELECT VERSION()") + .fetch_one(&mut *tx) + .await?; + if !mysql_supports_check_constraints(&version) { + return Ok(()); + } + let res: Result<_, sqlx::Error> = sqlx::query("INSERT INTO products VALUES (1, 'Product 1', 0);") .execute(&mut *tx) diff --git a/tests/x.py b/tests/x.py index e1308f2fa4..66130e6f99 100755 --- a/tests/x.py +++ b/tests/x.py @@ -15,15 +15,25 @@ parser.add_argument("-e", "--target-exact") parser.add_argument("-l", "--list-targets", action="store_true") parser.add_argument("--test") +parser.add_argument("--clippy", action="store_true") argv, unknown = parser.parse_known_args() +_list_targets_seen = set() + # base dir of sqlx workspace dir_workspace = os.path.dirname(os.path.dirname(os.path.realpath(__file__))) # dir of tests dir_tests = os.path.join(dir_workspace, "tests") +RUNTIMES = ["async-std", "async-global-executor", "smol", "tokio"] +CHECK_TLS = ["native-tls", "rustls", "rustls-ring", "rustls-aws-lc-rs", "none"] +TLS_VARIANTS = ["native-tls", "rustls-ring", "rustls-aws-lc-rs", "none"] +POSTGRES_VERSIONS = ["17", "16", "15", "14", "13"] +MYSQL_VERSIONS = ["8", "5_7"] +MARIADB_VERSIONS = ["verylatest", "11_8", "11_4", "10_11", "10_6"] + def maybe_fetch_sqlite_extension(): """ @@ -55,10 +65,37 @@ def maybe_fetch_sqlite_extension(): return filename.split(".")[0] +def required_feature_for_test(test_name): + for feature in ["postgres", "mysql", "sqlite", "any"]: + if test_name.startswith(feature): + return feature + return None + + +def extract_features(command): + tokens = command.split(" ") + for i, token in enumerate(tokens): + if token == "--features" and i + 1 < len(tokens): + return set(tokens[i + 1].split(",")) + return None + + +def core_tls_features(tls): + if tls == "rustls": + return ["_tls-rustls-ring-webpki"] + if tls == "rustls-ring": + return ["_tls-rustls-ring-webpki", "_tls-rustls-ring-native-roots"] + if tls == "rustls-aws-lc-rs": + return ["_tls-rustls-aws-lc-rs"] + return [f"_tls-{tls}"] + + def run(command, comment=None, env=None, service=None, tag=None, args=None, database_url_args=None): if argv.list_targets: if tag: - print(f"{tag}") + if tag not in _list_targets_seen: + print(f"{tag}") + _list_targets_seen.add(tag) return @@ -100,7 +137,17 @@ def run(command, comment=None, env=None, service=None, tag=None, args=None, data command_args = [] if argv.test: - command_args.extend(["--test", argv.test]) + if command.startswith("cargo c") or command.startswith("cargo check") or command.startswith("cargo clippy"): + return + if "--manifest-path" in command: + return + required = required_feature_for_test(argv.test) + if required is not None: + features = extract_features(command) + if features is None or (required not in features and "all-databases" not in features): + return + if command.startswith("cargo test"): + command_args.extend(["--test", argv.test]) if unknown: command_args.extend(["--", *unknown]) @@ -124,6 +171,17 @@ def run(command, comment=None, env=None, service=None, tag=None, args=None, data sys.exit(res.returncode) +def postgres_env(version): + env = {} + rustflags = os.environ.get("RUSTFLAGS", "").strip() + version_flag = f'--cfg postgres="{version}"' + if rustflags: + env["RUSTFLAGS"] = f"{rustflags} {version_flag}" + else: + env["RUSTFLAGS"] = version_flag + return env + + # before we start, we clean previous profile data # keeping these around can cause weird errors for path in glob(os.path.join(os.path.dirname(__file__), "target/**/*.gc*"), recursive=True): @@ -133,120 +191,190 @@ def run(command, comment=None, env=None, service=None, tag=None, args=None, data # check # -for runtime in ["async-std", "tokio"]: - for tls in ["native-tls", "rustls", "none"]: +CHECK_CMD = "cargo clippy" if argv.clippy else "cargo c" + +for runtime in RUNTIMES: + for tls in CHECK_TLS: run( - f"cargo c --no-default-features --features all-databases,_unstable-all-types,macros,runtime-{runtime},tls-{tls}", - comment="check with async-std", - tag=f"check_{runtime}_{tls}" + f"{CHECK_CMD} --no-default-features --features all-databases,_unstable-all-types,macros,sqlite-preupdate-hook,runtime-{runtime},tls-{tls}", + comment=f"check {runtime} {tls}", + tag=f"check_{runtime}_{tls}", ) +if argv.clippy: + sys.exit(0) + # # unit test # -for runtime in ["async-std", "tokio"]: - for tls in ["native-tls", "rustls", "none"]: +for runtime in RUNTIMES: + for tls in TLS_VARIANTS: + core_features = [ + "json", + "offline", + "migrate", + "sqlx-toml", + f"_rt-{runtime}", + *core_tls_features(tls), + ] run( - f"cargo test --no-default-features --manifest-path sqlx-core/Cargo.toml --features json,offline,migrate,_rt-{runtime},_tls-{tls}", - comment="unit test core", - tag=f"unit_{runtime}_{tls}" + "cargo test --no-default-features --manifest-path sqlx-core/Cargo.toml " + f"--features {','.join(core_features)}", + comment=f"unit test core {runtime} {tls}", + tag=f"unit_{runtime}_{tls}", ) +run( + "cargo test -p sqlx-mysql --no-default-features --features rsa --lib", + comment="unit test sqlx-mysql rsa", + tag="unit_mysql_rsa", +) + # # integration tests # -for runtime in ["async-std", "tokio"]: - for tls in ["native-tls", "rustls", "none"]: - +for runtime in RUNTIMES: + for tls in TLS_VARIANTS: # # sqlite # run( - f"cargo test --no-default-features --features any,sqlite,macros,_unstable-all-types,runtime-{runtime},tls-{tls}", - comment=f"test sqlite", + f"cargo test --no-default-features " + f"--features any,sqlite,macros,migrate,sqlite-preupdate-hook,_unstable-all-types,runtime-{runtime},tls-{tls}", + comment="test sqlite", + env={"RUST_TEST_THREADS": "1"}, service="sqlite", - tag=f"sqlite" if runtime == "async-std" else f"sqlite_{runtime}", + tag=f"sqlite_{runtime}", ) # # postgres # - for version in ["17", "16", "15", "14", "13"]: + for version in POSTGRES_VERSIONS: run( - f"cargo test --no-default-features --features any,postgres,macros,_unstable-all-types,runtime-{runtime},tls-{tls}", + f"cargo test --no-default-features " + f"--features any,postgres,macros,migrate,_unstable-all-types,runtime-{runtime},tls-{tls}", comment=f"test postgres {version}", + env=postgres_env(version), service=f"postgres_{version}", - tag=f"postgres_{version}" if runtime == "async-std" else f"postgres_{version}_{runtime}", + tag=f"postgres_{version}_{runtime}", ) if tls != "none": ## +ssl run( - f"cargo test --no-default-features --features any,postgres,macros,_unstable-all-types,runtime-{runtime},tls-{tls}", + f"cargo test --no-default-features " + f"--features any,postgres,macros,migrate,_unstable-all-types,runtime-{runtime},tls-{tls}", comment=f"test postgres {version} ssl", database_url_args="sslmode=verify-ca&sslrootcert=.%2Ftests%2Fcerts%2Fca.crt", + env=postgres_env(version), service=f"postgres_{version}", - tag=f"postgres_{version}_ssl" if runtime == "async-std" else f"postgres_{version}_ssl_{runtime}", + tag=f"postgres_{version}_ssl_{runtime}", ) ## +client-ssl run( - f"cargo test --no-default-features --features any,postgres,macros,_unstable-all-types,runtime-{runtime},tls-{tls}", + f"cargo test --no-default-features " + f"--features any,postgres,macros,migrate,_unstable-all-types,runtime-{runtime},tls-{tls}", comment=f"test postgres {version}_client_ssl no-password", - database_url_args="sslmode=verify-ca&sslrootcert=.%2Ftests%2Fcerts%2Fca.crt&sslkey=%2Ftests%2Fcerts%2Fkeys%2Fclient.key&sslcert=.%2Ftests%2Fcerts%2Fclient.crt", + database_url_args="sslmode=verify-ca&sslrootcert=.%2Ftests%2Fcerts%2Fca.crt&sslkey=.%2Ftests%2Fcerts%2Fkeys%2Fclient.key&sslcert=.%2Ftests%2Fcerts%2Fclient.crt", + env=postgres_env(version), service=f"postgres_{version}_client_ssl", - tag=f"postgres_{version}_client_ssl_no_password" if runtime == "async-std" else f"postgres_{version}_client_ssl_no_password_{runtime}", + tag=f"postgres_{version}_client_ssl_no_password_{runtime}", ) # # mysql # - for version in ["8", "5_7"]: + for version in MYSQL_VERSIONS: + base_features = f"any,mysql,macros,migrate,_unstable-all-types,runtime-{runtime},tls-{tls}" + rsa_features = f"any,mysql,mysql-rsa,macros,migrate,_unstable-all-types,runtime-{runtime},tls-{tls}" + features = rsa_features if tls == "none" else base_features + base_url_args = "ssl-mode=disabled" if tls == "none" else "ssl-mode=required" + client_ssl_ca = ".%2Ftests%2Fcerts%2Fca.crt" + client_ssl_key = ".%2Ftests%2Fcerts%2Fkeys%2Fclient.key" + client_ssl_cert = ".%2Ftests%2Fcerts%2Fclient.crt" + if version == "5_7": + # MySQL 5.7 cannot load Ed25519 certs; use the RSA set for client-SSL targets. + client_ssl_ca = ".%2Ftests%2Fcerts%2Frsa%2Fca.crt" + client_ssl_key = ".%2Ftests%2Fcerts%2Frsa%2Fkeys%2Fclient.key" + client_ssl_cert = ".%2Ftests%2Fcerts%2Frsa%2Fclient.crt" + client_ssl_args = ( + f"sslmode=verify_ca&ssl-ca={client_ssl_ca}" + f"&ssl-key={client_ssl_key}&ssl-cert={client_ssl_cert}" + ) + # Since docker mysql 5.7 using yaSSL(It only supports TLSv1.1), avoid running when using rustls. # https://github.com/docker-library/mysql/issues/567 - if not(version == "5_7" and tls == "rustls"): + # only run when using native-tls + if not (version == "5_7" and tls in ["rustls-ring", "rustls-aws-lc-rs"]): run( - f"cargo test --no-default-features --features any,mysql,macros,_unstable-all-types,runtime-{runtime},tls-{tls}", + f"cargo test --no-default-features --features {features}", comment=f"test mysql {version}", + database_url_args=base_url_args, service=f"mysql_{version}", - tag=f"mysql_{version}" if runtime == "async-std" else f"mysql_{version}_{runtime}", + tag=f"mysql_{version}_{runtime}", ) - ## +client-ssl - if tls != "none" and not(version == "5_7" and tls == "rustls"): + ## +client-ssl + if tls != "none" and not (version == "5_7" and tls in ["rustls-ring", "rustls-aws-lc-rs"]): + run( + f"cargo test --no-default-features --features {base_features}", + comment=f"test mysql {version}_client_ssl no-password", + database_url_args=client_ssl_args, + service=f"mysql_{version}_client_ssl", + tag=f"mysql_{version}_client_ssl_no_password_{runtime}", + ) + + if tls == "native-tls" and runtime == "tokio" and version == "8": run( - f"cargo test --no-default-features --features any,mysql,macros,_unstable-all-types,runtime-{runtime},tls-{tls}", - comment=f"test mysql {version}_client_ssl no-password", - database_url_args="sslmode=verify_ca&ssl-ca=.%2Ftests%2Fcerts%2Fca.crt&ssl-key=.%2Ftests%2Fcerts%2Fkeys%2Fclient.key&ssl-cert=.%2Ftests%2Fcerts%2Fclient.crt", - service=f"mysql_{version}_client_ssl", - tag=f"mysql_{version}_client_ssl_no_password" if runtime == "async-std" else f"mysql_{version}_client_ssl_no_password_{runtime}", + f"cargo test --no-default-features --features {rsa_features}", + comment=f"test mysql {version} tls with rsa", + database_url_args="ssl-mode=required", + service=f"mysql_{version}", + tag=f"mysql_{version}_tls_rsa_{runtime}", ) # # mariadb # - for version in ["verylatest", "10_11", "10_6", "10_5", "10_4"]: + for version in MARIADB_VERSIONS: + base_features = f"any,mysql,macros,migrate,_unstable-all-types,runtime-{runtime},tls-{tls}" + rsa_features = f"any,mysql,mysql-rsa,macros,migrate,_unstable-all-types,runtime-{runtime},tls-{tls}" + features = rsa_features if tls == "none" else base_features + base_url_args = "ssl-mode=disabled" if tls == "none" else "ssl-mode=required" + run( - f"cargo test --no-default-features --features any,mysql,macros,_unstable-all-types,runtime-{runtime},tls-{tls}", + f"cargo test --no-default-features --features {features}", comment=f"test mariadb {version}", + database_url_args=base_url_args, service=f"mariadb_{version}", - tag=f"mariadb_{version}" if runtime == "async-std" else f"mariadb_{version}_{runtime}", + tag=f"mariadb_{version}_{runtime}", ) ## +client-ssl if tls != "none": run( - f"cargo test --no-default-features --features any,mysql,macros,_unstable-all-types,runtime-{runtime},tls-{tls}", + f"cargo test --no-default-features --features {base_features}", comment=f"test mariadb {version}_client_ssl no-password", database_url_args="sslmode=verify_ca&ssl-ca=.%2Ftests%2Fcerts%2Fca.crt&ssl-key=%2Ftests%2Fcerts%2Fkeys%2Fclient.key&ssl-cert=.%2Ftests%2Fcerts%2Fclient.crt", service=f"mariadb_{version}_client_ssl", - tag=f"mariadb_{version}_client_ssl_no_password" if runtime == "async-std" else f"mariadb_{version}_client_ssl_no_password_{runtime}", + tag=f"mariadb_{version}_client_ssl_no_password_{runtime}", + ) + + if tls == "native-tls" and runtime == "tokio" and version == "10_11": + run( + f"cargo test --no-default-features --features {rsa_features}", + comment=f"test mariadb {version} tls with rsa", + database_url_args="ssl-mode=required", + service=f"mariadb_{version}", + tag=f"mariadb_{version}_tls_rsa_{runtime}", ) # TODO: Use [grcov] if available