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 c8cc0035e9..d99e2c8c6f 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 @@ -55,16 +68,16 @@ jobs: --target-dir target/beta/ check-minimal-versions: - name: Check build using minimal versions + name: Check build using direct minimal versions 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 rustup toolchain install nightly - - run: cargo +nightly generate-lockfile -Z minimal-versions + - run: cargo +nightly generate-lockfile -Z direct-minimal-versions - run: cargo build --all-features test: @@ -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 @@ -134,7 +147,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 @@ -153,12 +166,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 @@ -209,6 +227,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 @@ -220,7 +269,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 @@ -243,10 +292,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 @@ -264,10 +319,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 @@ -310,7 +371,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 @@ -323,14 +384,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 @@ -342,7 +441,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 @@ -352,15 +451,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 @@ -371,7 +483,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 @@ -379,10 +491,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 @@ -408,7 +537,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 @@ -421,17 +550,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 @@ -443,7 +621,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 @@ -453,15 +631,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 @@ -510,13 +701,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 559c2211d7..67ae6e0d91 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4,18 +4,18 @@ version = 4 [[package]] name = "addr2line" -version = "0.24.2" +version = "0.21.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dfbe277e56a376000877090da837660b4427aad530e3028d44e0bffe4f89a1c1" +checksum = "8a30b2e23b9e17a9f90641c7ab1549cd9b44f296d3ccbf309d2863cfe398a0cb" dependencies = [ "gimli", ] [[package]] -name = "adler2" -version = "2.0.1" +name = "adler" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" +checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" [[package]] name = "ahash" @@ -23,16 +23,16 @@ version = "0.7.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "891477e0c6a8957309ee5c45a6368af3ae14bb510732d2684ffa19af310920f9" dependencies = [ - "getrandom 0.2.16", + "getrandom 0.2.17", "once_cell", "version_check", ] [[package]] name = "aho-corasick" -version = "1.1.3" +version = "0.7.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" +checksum = "cc936419f96fa211c1b9166887b38e5e40b19958e5b895be7c1f93adec7071ac" dependencies = [ "memchr", ] @@ -75,9 +75,9 @@ dependencies = [ [[package]] name = "anstream" -version = "0.6.19" +version = "0.6.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "301af1932e46185686725e0fad2f8f2aa7da69dd70bf6ecc44d6b703844a3933" +checksum = "43d5b281e737544384e969a5ccad3f1cdd24b48086a0fc1b2a5262a26b8f4f4a" dependencies = [ "anstyle", "anstyle-parse", @@ -90,9 +90,9 @@ dependencies = [ [[package]] name = "anstyle" -version = "1.0.11" +version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "862ed96ca487e809f1c8e5a8447f6ee2cf102f846893800b20cebdf541fc6bbd" +checksum = "940b3a0ca603d1eade50a4846a2afffd5ef57a9feac2c0e2ec2e14f9ead76000" [[package]] name = "anstyle-parse" @@ -105,29 +105,29 @@ dependencies = [ [[package]] name = "anstyle-query" -version = "1.1.3" +version = "1.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c8bdeb6047d8983be085bab0ba1472e6dc604e7041dbf6fcd5e71523014fae9" +checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc" dependencies = [ - "windows-sys 0.59.0", + "windows-sys 0.61.2", ] [[package]] name = "anstyle-wincon" -version = "3.0.9" +version = "3.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "403f75924867bb1033c59fbf0797484329750cfbe3c4325cd33127941fabc882" +checksum = "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d" dependencies = [ "anstyle", "once_cell_polyfill", - "windows-sys 0.59.0", + "windows-sys 0.61.2", ] [[package]] name = "anyhow" -version = "1.0.98" +version = "1.0.58" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e16d2d3311acee920a9eb8d33b8cbc1787ce4a264e85f964c2404b969bdcd487" +checksum = "bb07d2053ccdbe10e2af2995a2f116c1330396493dc1269f6a91d0ae82e19704" [[package]] name = "argon2" @@ -167,7 +167,7 @@ dependencies = [ "anstyle", "bstr", "libc", - "predicates 3.1.3", + "predicates 3.1.4", "predicates-core", "predicates-tree", "wait-timeout", @@ -208,9 +208,9 @@ dependencies = [ [[package]] name = "async-executor" -version = "1.13.2" +version = "1.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bb812ffb58524bdd10860d7d974e2f01cc0950c2438a74ee5ec2e2280c6c4ffa" +checksum = "c96bf972d85afc50bf5ab8fe2d54d1586b4e0b46c97c50a0c9e71e2f7bcd812a" dependencies = [ "async-task", "concurrent-queue", @@ -222,9 +222,9 @@ dependencies = [ [[package]] name = "async-fs" -version = "2.1.2" +version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ebcd09b382f40fcd159c2d695175b2ae620ffa5f3bd6f664131efff4e8b9e04a" +checksum = "dd1f344136bad34df1f83a47f3fd7f2ab85d75cb8a940af4ccf6d482a84ea01b" dependencies = [ "async-lock", "blocking", @@ -273,7 +273,7 @@ dependencies = [ "futures-lite", "parking", "polling", - "rustix 1.0.7", + "rustix 1.1.4", "slab", "tracing", "windows-sys 0.59.0", @@ -281,11 +281,11 @@ dependencies = [ [[package]] name = "async-lock" -version = "3.4.0" +version = "3.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff6e472cdea888a4bd64f342f09b3f50e1886d32afe8df3d663c01140b811b18" +checksum = "290f7f2596bd5b78a9fec8088ccd89180d7f9f55b94b0576823bbbdc72ee8311" dependencies = [ - "event-listener 5.4.0", + "event-listener 5.2.0", "event-listener-strategy", "pin-project-lite", ] @@ -303,9 +303,9 @@ dependencies = [ [[package]] name = "async-process" -version = "2.3.1" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cde3f4e40e6021d7acffc90095cbd6dc54cb593903d1de5832f435eb274b85dc" +checksum = "fc50921ec0055cdd8a16de48773bfeec5c972598674347252c0399676be7da75" dependencies = [ "async-channel 2.5.0", "async-io", @@ -314,17 +314,16 @@ dependencies = [ "async-task", "blocking", "cfg-if", - "event-listener 5.4.0", + "event-listener 5.2.0", "futures-lite", - "rustix 1.0.7", - "tracing", + "rustix 1.1.4", ] [[package]] name = "async-signal" -version = "0.2.11" +version = "0.2.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d7605a4e50d4b06df3898d5a70bf5fde51ed9059b0434b73105193bc27acce0d" +checksum = "52b5aaafa020cf5053a01f2a60e8ff5dccf550f0f77ec54a4e47285ac2bab485" dependencies = [ "async-io", "async-lock", @@ -332,17 +331,17 @@ dependencies = [ "cfg-if", "futures-core", "futures-io", - "rustix 1.0.7", + "rustix 1.1.4", "signal-hook-registry", "slab", - "windows-sys 0.59.0", + "windows-sys 0.61.2", ] [[package]] name = "async-std" -version = "1.13.1" +version = "1.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "730294c1c08c2e0f85759590518f6333f0d5a0a766a27d519c1b244c3dfd8a24" +checksum = "c634475f29802fde2b8f0b505b1bd00dfe4df7d4a000f0b36f7671197d5c3615" dependencies = [ "async-attributes", "async-channel 1.9.0", @@ -373,13 +372,13 @@ checksum = "8b75356056920673b02621b35afd0f7dda9306d03c79a30f5c56c44cf256e3de" [[package]] name = "async-trait" -version = "0.1.88" +version = "0.1.43" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e539d3fca749fcee5236ab05e93a52867dd549cc157c8cb7f99595f3cedffdb5" +checksum = "b6287685011f026b98d26afd53251ad0101e856531b423eb2384265f7d4f5b01" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn 1.0.109", ] [[package]] @@ -429,9 +428,9 @@ checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" [[package]] name = "aws-lc-rs" -version = "1.13.1" +version = "1.16.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "93fcc8f365936c834db5514fc45aee5b1202d677e6b40e48468aaaa8183ca8c7" +checksum = "0ec6fb3fe69024a75fa7e1bfb48aa6cf59706a101658ea01bfd33b2b248a038f" dependencies = [ "aws-lc-sys", "zeroize", @@ -439,11 +438,10 @@ dependencies = [ [[package]] name = "aws-lc-sys" -version = "0.29.0" +version = "0.40.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61b1d86e7705efe1be1b569bab41d4fa1e14e220b60a160f78de2db687add079" +checksum = "f50037ee5e1e41e7b8f9d161680a725bd1626cb6f8c7e901f91f942850852fe7" dependencies = [ - "bindgen 0.69.5", "cc", "cmake", "dunce", @@ -452,62 +450,67 @@ dependencies = [ [[package]] name = "axum" -version = "0.5.17" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "acee9fd5073ab6b045a275b3e709c163dd36c90685219cb21804a147b58dba43" +checksum = "42f74c4d9a7a03b6dbcc61e711563da6b548bc8556d51708e8a172441ff3c4f1" dependencies = [ - "async-trait", "axum-core", "axum-macros", - "bitflags 1.3.2", "bytes", + "form_urlencoded", "futures-util", "http", "http-body", + "http-body-util", "hyper", + "hyper-util", "itoa", "matchit", "memchr", "mime", "percent-encoding", "pin-project-lite", + "rustversion", "serde", "serde_json", + "serde_path_to_error", "serde_urlencoded", "sync_wrapper", "tokio", - "tower", - "tower-http", + "tower 0.5.2", "tower-layer", "tower-service", + "tracing", ] [[package]] name = "axum-core" -version = "0.2.9" +version = "0.5.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37e5939e02c56fecd5c017c37df4238c0a839fa76b7f97acdd7efb804fd181cc" +checksum = "08c78f31d7b1291f7ee735c1c6780ccde7785daae9a9206026862dab7d8792d1" dependencies = [ - "async-trait", "bytes", - "futures-util", + "futures-core", "http", "http-body", + "http-body-util", "mime", + "pin-project-lite", + "sync_wrapper", "tower-layer", "tower-service", + "tracing", ] [[package]] name = "axum-macros" -version = "0.2.3" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6293dae2ec708e679da6736e857cf8532886ef258e92930f38279c12641628b8" +checksum = "7aa268c23bfbbd2c4363b9cd302a4f504fb2a9dfe7e3451d66f35dd392e20aca" dependencies = [ - "heck 0.4.1", "proc-macro2", "quote", - "syn 1.0.109", + "syn 2.0.87", ] [[package]] @@ -517,7 +520,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b62ddb9cb1ec0a098ad4bbf9344d0713fa193ae1a80af55febcff2627b6a00c1" dependencies = [ "futures-core", - "getrandom 0.2.16", + "getrandom 0.2.17", "instant", "pin-project-lite", "rand 0.8.5", @@ -526,17 +529,17 @@ dependencies = [ [[package]] name = "backtrace" -version = "0.3.75" +version = "0.3.71" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6806a6321ec58106fea15becdad98371e28d92ccbc7c8f1b3b6dd724fe8f1002" +checksum = "26b05800d2e817c8b3b4b54abd461726265fa9789ae34330622f2db9ee696f9d" dependencies = [ "addr2line", + "cc", "cfg-if", "libc", "miniz_oxide", "object", "rustc-demangle", - "windows-targets 0.52.6", ] [[package]] @@ -547,15 +550,15 @@ checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" [[package]] name = "base64" -version = "0.22.1" +version = "0.22.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" +checksum = "9475866fec1451be56a3c2400fd081ff546538961565ccb5b7142cbd22bc7a51" [[package]] name = "base64ct" -version = "1.8.0" +version = "1.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "55248b47b0caf0546f7988906588779981c43bb1bc9d0c44087278f80cdb44ba" +checksum = "2af50177e190e07a26ab74f8b1efbfe2ef87da2116221318cb1c2e82baf7de06" [[package]] name = "bigdecimal" @@ -570,11 +573,10 @@ dependencies = [ [[package]] name = "bigdecimal" -version = "0.4.8" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a22f228ab7a1b23027ccc6c350b72868017af7ea8356fbdf19f8d991c690013" +checksum = "5274a6b6e0ee020148397245b973e30163b7bffbc6d473613f850cb99888581e" dependencies = [ - "autocfg", "libm", "num-bigint", "num-integer", @@ -610,21 +612,18 @@ version = "0.69.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "271383c67ccabffb7381723dea0672a673f292304fcb45c01cc648c7a8d58088" dependencies = [ - "bitflags 2.9.1", + "bitflags 2.4.0", "cexpr", "clang-sys", "itertools 0.12.1", "lazy_static", "lazycell", - "log", - "prettyplease", "proc-macro2", "quote", "regex", "rustc-hash", "shlex", - "syn 2.0.104", - "which", + "syn 2.0.87", ] [[package]] @@ -641,9 +640,9 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bitflags" -version = "2.9.1" +version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b8e56985ec62d17e9c1001dc89c88ecd7dc08e47eba5ec7c29c7b5eeecde967" +checksum = "b4682ae6287fcf752ecaabbfcc7b6f9b72aa33933dc23a554d853aea8eea8635" dependencies = [ "serde", ] @@ -693,49 +692,51 @@ dependencies = [ [[package]] name = "borsh" -version = "1.5.7" +version = "1.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ad8646f98db542e39fc66e68a20b2144f6a732636df7c2354e74645faaa433ce" +checksum = "cfd1e3f8955a5d7de9fab72fc8373fade9fb8a703968cb200ae3dc6cf08e185a" dependencies = [ "borsh-derive", + "bytes", "cfg_aliases", ] [[package]] name = "borsh-derive" -version = "1.5.7" +version = "1.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fdd1d3c0c2f5833f22386f252fe8ed005c7f59fdcddeef025c01b4c3b9fd9ac3" +checksum = "bfcfdc083699101d5a7965e49925975f2f55060f94f9a05e7187be95d530ca59" dependencies = [ "once_cell", "proc-macro-crate", "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.87", ] [[package]] name = "bstr" -version = "1.12.0" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "234113d19d0d7d613b40e86fb654acf958910802bcceab913a4f9e7cda03b1a4" +checksum = "fca0852af221f458706eb0725c03e4ed6c46af9ac98e6a689d5e634215d594dd" dependencies = [ "memchr", + "once_cell", "regex-automata", "serde", ] [[package]] name = "bumpalo" -version = "3.19.0" +version = "3.20.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "46c5e41b57b8bba42a04676d81cb89e9ee8e859a1a66f80a5a72e1cb76b34d43" +checksum = "5d20789868f4b01b2f2caec9f5c4e0213b41e3e5702a50157d699ae31ced2fcb" [[package]] name = "bytecheck" -version = "0.6.12" +version = "0.6.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23cdc57ce23ac53c931e88a43d06d070a6fd142f2617be5855eb75efc9beb1c2" +checksum = "8b6372023ac861f6e6dc89c8344a8f398fb42aaba2b5dbc649ca0c0e9dbcb627" dependencies = [ "bytecheck_derive", "ptr_meta", @@ -755,46 +756,46 @@ dependencies = [ [[package]] name = "byteorder" -version = "1.5.0" +version = "1.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" +checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" [[package]] name = "bytes" -version = "1.10.1" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a" +checksum = "f0b3de4a0c5e67e16066a0715723abd91edc2f9001d09c46e1dca929351e130e" [[package]] name = "camino" -version = "1.1.10" +version = "1.1.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0da45bc31171d8d6960122e222a67740df867c1dd53b4d51caa297084c185cab" +checksum = "dd0b03af37dad7a14518b7691d81acb0f8222604ad3d1b02f6b4bed5188c0cd5" dependencies = [ "serde", ] [[package]] name = "cargo-platform" -version = "0.1.9" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e35af189006b9c0f00a064685c727031e3ed2d8020f7ba284d78cc2671bd36ea" +checksum = "122ec45a44b270afd1402f351b782c676b173e3c3fb28d86ff7ebfb4d86a4ee4" dependencies = [ "serde", ] [[package]] name = "cargo_metadata" -version = "0.18.1" +version = "0.23.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2d886547e41f740c616ae73108f6eb70afe6d940c7bc697cb30f13daec073037" +checksum = "ef987d17b0a113becdd19d3d0022d04d7ef41f9efe4f3fb63ac44ba61df3ade9" dependencies = [ "camino", "cargo-platform", "semver", "serde", "serde_json", - "thiserror 1.0.69", + "thiserror 2.0.17", ] [[package]] @@ -811,19 +812,20 @@ checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5" [[package]] name = "castaway" -version = "0.2.3" +version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0abae9be0aaf9ea96a3b1b8b1b55c602ca751eba1b1500220cea4ecbafe7c0d5" +checksum = "dec551ab6e7578819132c713a93c022a05d60159dc86e7a7050223577484c55a" dependencies = [ "rustversion", ] [[package]] name = "cc" -version = "1.2.29" +version = "1.2.61" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c1599538de2394445747c8cf7935946e3cc27e9625f889d979bfb2aaf569362" +checksum = "d16d90359e986641506914ba71350897565610e87ce0ad9e6f28569db3dd5c6d" dependencies = [ + "find-msvc-tools", "jobserver", "libc", "shlex", @@ -840,9 +842,9 @@ dependencies = [ [[package]] name = "cfg-if" -version = "1.0.1" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9555578bc9e57714c812a1f84e4fc5b4d21fcb063490c624de019f7464c91268" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "cfg_aliases" @@ -852,9 +854,9 @@ checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" [[package]] name = "chrono" -version = "0.4.41" +version = "0.4.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c469d952047f47f91b68d1cba3f10d63c11d73e4636f24f08daf0278abf01c4d" +checksum = "5bc015644b92d5890fab7489e49d21f879d5c990186827d42ec511919404f38b" dependencies = [ "android-tzdata", "iana-time-zone", @@ -862,7 +864,7 @@ dependencies = [ "num-traits", "serde", "wasm-bindgen", - "windows-link", + "windows-targets 0.52.6", ] [[package]] @@ -914,15 +916,15 @@ dependencies = [ "bitflags 1.3.2", "strsim 0.8.0", "textwrap", - "unicode-width 0.1.14", + "unicode-width", "vec_map", ] [[package]] name = "clap" -version = "4.5.40" +version = "4.4.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "40b6887a1d8685cebccf115538db5c0efe625ccac9696ad45c409d96566e910f" +checksum = "ac495e00dcec98c83465d5ad66c5c4fabd652fd6686e7c6269b117e729a6f17b" dependencies = [ "clap_builder", "clap_derive", @@ -930,58 +932,58 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.5.40" +version = "4.4.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e0c66c08ce9f0c698cbce5c0279d0bb6ac936d8674174fe48f736533b964f59e" +checksum = "c77ed9a32a62e6ca27175d00d29d05ca32e396ea1eb5fb01d8256b669cec7663" dependencies = [ "anstream", "anstyle", "clap_lex", - "strsim 0.11.1", - "terminal_size", + "strsim 0.10.0", + "terminal_size 0.3.0", ] [[package]] name = "clap_complete" -version = "4.5.54" +version = "4.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aad5b1b4de04fead402672b48897030eec1f3bfe1550776322f59f6d6e6a5677" +checksum = "7f6b5c519bab3ea61843a7923d074b04245624bb84a64a8c150f5deb014e388b" dependencies = [ - "clap 4.5.40", + "clap 4.4.7", ] [[package]] name = "clap_derive" -version = "4.5.40" +version = "4.4.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2c7947ae4cc3d851207c1adb5b5e260ff0cca11446b1d6d1423788e442257ce" +checksum = "cf9804afaaf59a91e75b022a30fb7229a7901f60c755489cc61c9b423b836442" dependencies = [ - "heck 0.5.0", + "heck 0.4.1", "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.87", ] [[package]] name = "clap_lex" -version = "0.7.5" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b94f61472cee1439c0b966b47e3aca9ae07e45d070759512cd390ea2bebc6675" +checksum = "702fc72eb24e5a1e48ce58027a675bc24edd52096d5397d4aea7c6dd9eca0bd1" [[package]] name = "cmake" -version = "0.1.54" +version = "0.1.58" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e7caa3f9de89ddbe2c607f4101924c5abec803763ae9534e4f4d7d8f84aa81f0" +checksum = "c0f78a02292a74a88ac736019ab962ece0bc380e3f977bf72e376c5d78ff0678" dependencies = [ "cc", ] [[package]] name = "color-eyre" -version = "0.6.5" +version = "0.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5920befb47832a6d61ee3a3a846565cfa39b331331e68a3b1d1116630f2f26d" +checksum = "55146f5e46f237f7423d74111267d4597b59b0dad0ffaf7303bce9945d843ad5" dependencies = [ "backtrace", "color-spantrace", @@ -994,9 +996,9 @@ dependencies = [ [[package]] name = "color-spantrace" -version = "0.3.0" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8b88ea9df13354b55bc7234ebcce36e6ef896aca2e42a15de9e10edce01b427" +checksum = "cd6be1b2a7e382e2b98b43b2adcca6bb0e465af0bdd38123873ae61eb17a72c2" dependencies = [ "once_cell", "owo-colors", @@ -1006,9 +1008,9 @@ dependencies = [ [[package]] name = "colorchoice" -version = "1.0.4" +version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75" +checksum = "1d07550c9036bf2ae0c684c4297d503f838287c83c53686d05370d0e139ae570" [[package]] name = "compact_str" @@ -1040,15 +1042,17 @@ checksum = "510ca239cf13b7f8d16a2b48f263de7b4f8c566f0af58d901031473c76afb1e3" [[package]] name = "console" -version = "0.15.11" +version = "0.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "054ccb5b10f9f2cbf51eb355ca1d05c2d279ce1804688d0db74b4733a5aeafd8" +checksum = "a28b32d32ca44b70c3e4acd7db1babf555fa026e385fb95f18028f88848b3c31" dependencies = [ "encode_unicode", "libc", "once_cell", - "unicode-width 0.2.1", - "windows-sys 0.59.0", + "regex", + "terminal_size 0.1.17", + "unicode-width", + "winapi", ] [[package]] @@ -1067,16 +1071,6 @@ dependencies = [ "libc", ] -[[package]] -name = "core-foundation" -version = "0.10.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2a6cd9ae233e7f62ba4e9353e81a88df7fc8a5987b8d445b4d90c879bd156f6" -dependencies = [ - "core-foundation-sys", - "libc", -] - [[package]] name = "core-foundation-sys" version = "0.8.7" @@ -1094,18 +1088,18 @@ dependencies = [ [[package]] name = "crc" -version = "3.3.0" +version = "3.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9710d3b3739c2e349eb44fe848ad0b7c8cb1e42bd87ee49371df2f7acaf3e675" +checksum = "53757d12b596c16c78b83458d732a5d1a17ab3f53f2f7412f6fb57cc8a140ab3" dependencies = [ "crc-catalog", ] [[package]] name = "crc-catalog" -version = "2.4.0" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "19d374276b40fb8bbdee95aef7c7fa6b5316ec764510eb64b8dd0e2ed0d7e7f5" +checksum = "217698eaf96b4a3f0bc4f3662aaa55bdf913cd54d7204591faa790070c6d0853" [[package]] name = "criterion" @@ -1116,7 +1110,7 @@ dependencies = [ "anes", "cast", "ciborium", - "clap 4.5.40", + "clap 4.4.7", "criterion-plot", "futures", "is-terminal", @@ -1166,10 +1160,11 @@ dependencies = [ [[package]] name = "crossbeam-queue" -version = "0.3.12" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0f58bbc28f91df819d0aa2a2c00cd19754769c2fad90579b3592b1c9ba7a3115" +checksum = "9b10ddc024425c88c2ad148c1b0fd53f4c6d38db9697c9f1588381212fa657c9" dependencies = [ + "cfg-if", "crossbeam-utils", ] @@ -1185,11 +1180,11 @@ version = "0.27.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f476fe445d41c9e991fd07515a6f463074b782242ccf4a5b7b1d1012e70824df" dependencies = [ - "bitflags 2.9.1", + "bitflags 2.4.0", "crossterm_winapi", "libc", "mio 0.8.11", - "parking_lot 0.12.4", + "parking_lot 0.12.5", "signal-hook", "signal-hook-mio", "winapi", @@ -1222,9 +1217,9 @@ dependencies = [ [[package]] name = "darling" -version = "0.20.11" +version = "0.20.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc7f46116c46ff9ab3eb1597a45688b6715c6e628b5c133e288e709a29bcb4ee" +checksum = "6f63b86c8a8826a49b8c21f08a2d07338eec8d900540f8630dc76284be802989" dependencies = [ "darling_core", "darling_macro", @@ -1232,27 +1227,27 @@ dependencies = [ [[package]] name = "darling_core" -version = "0.20.11" +version = "0.20.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d00b9596d185e565c2207a0b01f8bd1a135483d02d9b7b0a54b11da8d53412e" +checksum = "95133861a8032aaea082871032f5815eb9e98cef03fa916ab4500513994df9e5" dependencies = [ "fnv", "ident_case", "proc-macro2", "quote", "strsim 0.11.1", - "syn 2.0.104", + "syn 2.0.87", ] [[package]] name = "darling_macro" -version = "0.20.11" +version = "0.20.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc34b93ccb385b40dc71c6fceac4b2ad23662c7eeb248cf10d529b7e055b6ead" +checksum = "d336a2a514f6ccccaa3e09b02d41d35330c07ddf03a62165fcec10bb561c7806" dependencies = [ "darling_core", "quote", - "syn 2.0.104", + "syn 2.0.87", ] [[package]] @@ -1268,9 +1263,9 @@ dependencies = [ [[package]] name = "deranged" -version = "0.4.0" +version = "0.3.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c9e6a11ca8224451684bc0d7d5a7adbf8f2fd6887261a1cfc3c0432f9d4068e" +checksum = "b42b6fa04a440b495c8b04d0e71b707c585f83cb9cb28cf8cd0d976c315e31b4" dependencies = [ "powerfmt", "serde", @@ -1284,7 +1279,7 @@ checksum = "658bce805d770f407bc62102fca7c2c64ceef2fbcb2b8bd19d2765ce093980de" dependencies = [ "console", "shell-words", - "thiserror 1.0.69", + "thiserror 1.0.40", ] [[package]] @@ -1313,7 +1308,7 @@ checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.87", ] [[package]] @@ -1336,18 +1331,18 @@ checksum = "92773504d58c093f6de2459af4af33faa518c13451eb8f2b5698ed3d36e7c813" [[package]] name = "either" -version = "1.15.0" +version = "1.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" +checksum = "e78d4f1cc4ae33bbfc157ed5d5a5ef3bc29227303d595861deb238fcec4e9457" dependencies = [ "serde", ] [[package]] name = "encode_unicode" -version = "1.0.0" +version = "0.3.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34aa73646ffb006b8f5147f3dc182bd4bcb190227ce861fc4a4844bf8e3cb2c0" +checksum = "a357d28ed41a50f9c765dbfe56cbc04a64e53e5fc58ba79fbc34c10ef3df831f" [[package]] name = "encoding_rs" @@ -1375,14 +1370,14 @@ checksum = "67c78a4d8fdf9953a5c9d458f9efe940fd97a0cab0941c075a813ac594733827" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.87", ] [[package]] name = "env_filter" -version = "0.1.3" +version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "186e05a59d4c50738528153b83b0b0194d3a29507dfec16eccd4b342903397d0" +checksum = "1bf3c259d255ca70051b30e2e95b5446cdb8949ac4cd22c0d7fd634d89f568e2" dependencies = [ "log", "regex", @@ -1403,14 +1398,14 @@ dependencies = [ [[package]] name = "env_logger" -version = "0.11.8" +version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "13c863f0904021b108aa8b2f55046443e6b1ebde8fd4a15c399893aae4fa069f" +checksum = "9eeb342678d785662fd2514be38c459bb925f02b68dd2a3e0f21d7ef82d979dd" dependencies = [ "anstream", "anstyle", "env_filter", - "jiff", + "humantime", "log", ] @@ -1422,12 +1417,12 @@ checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" [[package]] name = "errno" -version = "0.3.13" +version = "0.3.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "778e2ac28f6c47af28e4907f13ffd1e1ddbd400980a9abd7c8df189bf578a5ad" +checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" dependencies = [ "libc", - "windows-sys 0.60.2", + "windows-sys 0.61.2", ] [[package]] @@ -1449,9 +1444,9 @@ checksum = "0206175f82b8d6bf6652ff7d71a1e27fd2e4efde587fd368662814d6ec1d9ce0" [[package]] name = "event-listener" -version = "5.4.0" +version = "5.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3492acde4c3fc54c845eaab3eed8bd00c7a7d881f78bfc801e43a93dec1331ae" +checksum = "2b5fb89194fa3cad959b833185b3063ba881dbfc7030680b314250779fb4cc91" dependencies = [ "concurrent-queue", "parking", @@ -1464,7 +1459,7 @@ version = "0.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8be9f3dfaaffdae2972880079a491a1a8bb7cbed0b8dd7a347f668b4150a3b93" dependencies = [ - "event-listener 5.4.0", + "event-listener 5.2.0", "pin-project-lite", ] @@ -1480,9 +1475,9 @@ dependencies = [ [[package]] name = "fastrand" -version = "2.3.0" +version = "2.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" +checksum = "9f1f227452a390804cdb637b74a86990f2a7d7ba4b7d5693aac9b4dd6defd8d6" [[package]] name = "filetime" @@ -1496,6 +1491,12 @@ dependencies = [ "windows-sys 0.59.0", ] +[[package]] +name = "find-msvc-tools" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582" + [[package]] name = "float-cmp" version = "0.9.0" @@ -1507,9 +1508,9 @@ dependencies = [ [[package]] name = "flume" -version = "0.11.1" +version = "0.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da0e4dd2a88388a1f4ccc7c9ce104604dab68d9f408dc34cd45823d5a9069095" +checksum = "5e139bc46ca777eb5efaf62df0ab8cc5fd400866427e56c68b22e414e53bd3be" dependencies = [ "futures-core", "futures-sink", @@ -1560,9 +1561,21 @@ dependencies = [ [[package]] name = "fragile" -version = "2.0.1" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7464c5c4a3f014d9b2ec4073650e5c06596f385060af740fc45ad5a19f959e8" +dependencies = [ + "fragile 2.1.0", +] + +[[package]] +name = "fragile" +version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28dd6caf6059519a65843af8fe2a3ae298b14b80179855aeb4adc2c1934ee619" +checksum = "8878864ba14bb86e818a412bfd6f18f9eabd4ec0f008a28e8f7eb61db532fcf9" +dependencies = [ + "futures-core", +] [[package]] name = "fs_extra" @@ -1578,9 +1591,9 @@ checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c" [[package]] name = "futures" -version = "0.3.31" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "65bc07b1a8bc7c85c5f2e110c476c7389b4554ba72af57d8445ea63a576b0876" +checksum = "8b147ee9d1f6d097cef9ce628cd2ee62288d963e16fb287bd9286455b241382d" dependencies = [ "futures-channel", "futures-core", @@ -1592,9 +1605,9 @@ dependencies = [ [[package]] name = "futures-channel" -version = "0.3.31" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" +checksum = "07bbe89c50d7a535e539b8c17bc0b49bdb77747034daa8087407d655f3f7cc1d" dependencies = [ "futures-core", "futures-sink", @@ -1602,15 +1615,15 @@ dependencies = [ [[package]] name = "futures-core" -version = "0.3.31" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" +checksum = "7e3450815272ef58cec6d564423f6e755e25379b217b0bc688e295ba24df6b1d" [[package]] name = "futures-executor" -version = "0.3.31" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e28d1d997f585e54aebc3f97d39e72338912123a67330d723fdbb564d646c9f" +checksum = "baf29c38818342a3b26b5b923639e7b1f4a61fc5e76102d4b1981c6dc7a7579d" dependencies = [ "futures-core", "futures-task", @@ -1625,20 +1638,20 @@ checksum = "1d930c203dd0b6ff06e0201a4a2fe9149b43c684fd4420555b26d21b1a02956f" dependencies = [ "futures-core", "lock_api", - "parking_lot 0.12.4", + "parking_lot 0.12.5", ] [[package]] name = "futures-io" -version = "0.3.31" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" +checksum = "cecba35d7ad927e23624b22ad55235f2239cfa44fd10428eecbeba6d6a717718" [[package]] name = "futures-lite" -version = "2.6.0" +version = "2.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f5edaec856126859abb19ed65f39e90fea3a9574b9707f13539acf4abf7eb532" +checksum = "f78e10609fe0e0b3f4157ffab1876319b5b0db102a2c60dc4626306dc46b44ad" dependencies = [ "fastrand", "futures-core", @@ -1649,32 +1662,32 @@ dependencies = [ [[package]] name = "futures-macro" -version = "0.3.31" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" +checksum = "e835b70203e41293343137df5c0664546da5745f82ec9b84d40be8336958447b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.87", ] [[package]] name = "futures-sink" -version = "0.3.31" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" +checksum = "c39754e157331b013978ec91992bde1ac089843443c49cbc7f46150b0fad0893" [[package]] name = "futures-task" -version = "0.3.31" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" +checksum = "037711b3d59c33004d3856fbdc83b99d4ff37a24768fa1be9ce3538a1cde4393" [[package]] name = "futures-util" -version = "0.3.31" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" +checksum = "389ca41296e6190b48053de0321d02a77f32f8a5d2461dd38762c0593805c6d6" dependencies = [ "futures-core", "futures-io", @@ -1683,15 +1696,14 @@ dependencies = [ "futures-task", "memchr", "pin-project-lite", - "pin-utils", "slab", ] [[package]] name = "generic-array" -version = "0.14.7" +version = "0.14.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +checksum = "501466ecc8a30d1d3b7fc9229b122b2ce8ed6e9d9223f1138d4babb253e51817" dependencies = [ "typenum", "version_check", @@ -1710,9 +1722,9 @@ dependencies = [ [[package]] name = "getrandom" -version = "0.2.16" +version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592" +checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0" dependencies = [ "cfg-if", "libc", @@ -1721,27 +1733,27 @@ dependencies = [ [[package]] name = "getrandom" -version = "0.3.3" +version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26145e563e54f2cadc477553f1ec5ee650b00862f0a58bcd12cbdc5f0ea2d2f4" +checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" dependencies = [ "cfg-if", "libc", "r-efi", - "wasi 0.14.2+wasi-0.2.4", + "wasip2", ] [[package]] name = "gimli" -version = "0.31.1" +version = "0.28.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" +checksum = "4271d37baee1b8c7e4b708028c57d816cf9d2434acb33a549475f78c181f6253" [[package]] name = "glob" -version = "0.3.2" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8d1add55171497b4705a648c6b583acafb01d58050a51727785f0b2c8e0a2b2" +checksum = "9b919933a397b79c37e33b77bb2aa3dc8eb6e165ad809e58ff75bc7db2e34574" [[package]] name = "gloo-timers" @@ -1757,12 +1769,13 @@ dependencies = [ [[package]] name = "half" -version = "2.6.0" +version = "2.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "459196ed295495a68f7d7fe1d84f6c4b7ff0e21fe3017b2f283c6fac3ad803c9" +checksum = "6ea2d84b969582b4b1864a92dc5d27cd2b77b622a8d79306834f1be5ba20d84b" dependencies = [ "cfg-if", "crunchy", + "zerocopy", ] [[package]] @@ -1776,9 +1789,15 @@ dependencies = [ [[package]] name = "hashbrown" -version = "0.15.4" +version = "0.14.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" + +[[package]] +name = "hashbrown" +version = "0.15.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5971ac85611da7067dbfcabef3c70ebb5606018acd9e2a3903a0da507521e0d5" +checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" dependencies = [ "allocator-api2", "equivalent", @@ -1840,18 +1859,18 @@ checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" [[package]] name = "hkdf" -version = "0.12.4" +version = "0.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b5f8eb2ad728638ea2c7d47a21db23b7b58a72ed6a38256b8a1849f15fbbdf7" +checksum = "94f41e9c77b6fc05b57497b960aad55942a9bbc5b20e1e623cf7fb1868f695d1" dependencies = [ "hmac", ] [[package]] name = "hmac" -version = "0.12.1" +version = "0.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" +checksum = "ddca131f3e7f2ce2df364b57949a9d47915cfbd35e46cfee355ccebbf794d6a2" dependencies = [ "digest", ] @@ -1867,31 +1886,36 @@ dependencies = [ [[package]] name = "http" -version = "0.2.12" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "601cbb57e577e2f5ef5be8e7b83f0f63994f25aa94d673e54a92d5c516d101f1" +checksum = "e3ba2a386d7f85a81f119ad7498ebe444d2e22c2af0b86b069416ace48b3311a" dependencies = [ "bytes", - "fnv", "itoa", ] [[package]] name = "http-body" -version = "0.4.6" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ceab25649e9960c0311ea418d17bee82c0dcec1bd053b5f9a66e265a693bed2" +checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" dependencies = [ "bytes", "http", - "pin-project-lite", ] [[package]] -name = "http-range-header" -version = "0.3.1" +name = "http-body-util" +version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "add0ab9360ddbd88cfeb3bd9574a1d85cfdfa14db10b3e21d3700dbc4328758f" +checksum = "41cb79eb393015dadd30fc252023adb0b2400a0caee0fa2a077e6e21a551e840" +dependencies = [ + "bytes", + "futures-util", + "http", + "http-body", + "pin-project-lite", +] [[package]] name = "httparse" @@ -1913,32 +1937,46 @@ checksum = "135b12329e5e3ce057a9f972339ea52bc954fe1e9358ef27f95e89716fbc5424" [[package]] name = "hyper" -version = "0.14.32" +version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41dfc780fdec9373c01bae43289ea34c972e40ee3c9f6b3c8801a35f35586ce7" +checksum = "6299f016b246a94207e63da54dbe807655bf9e00044f73ded42c3ac5305fbcca" dependencies = [ + "atomic-waker", "bytes", "futures-channel", "futures-core", - "futures-util", "http", "http-body", "httparse", "httpdate", "itoa", "pin-project-lite", - "socket2", + "smallvec", + "tokio", +] + +[[package]] +name = "hyper-util" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cde7055719c54e36e95e8719f95883f22072a48ede39db7fc17a4e1d5281e9b9" +dependencies = [ + "bytes", + "futures-util", + "http", + "http-body", + "hyper", + "pin-project-lite", "tokio", + "tower 0.4.13", "tower-service", - "tracing", - "want", ] [[package]] name = "iana-time-zone" -version = "0.1.63" +version = "0.1.65" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b0c919e5debc312ad217002b8048a17b7d83f80703865bbfcfebb0458b0b27d8" +checksum = "e31bc9ad994ba00e440a8aa5c9ef0ec67d5cb5e5cb0cc7f8b744a35b389cc470" dependencies = [ "android_system_properties", "core-foundation-sys", @@ -1960,9 +1998,9 @@ dependencies = [ [[package]] name = "icu_collections" -version = "2.0.0" +version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "200072f5d0e3614556f94a9930d5dc3e0662a652823904c3a75dc3b0af7fee47" +checksum = "4c6b649701667bbe825c3b7e6388cb521c23d88644678e83c0c4d0a621a34b43" dependencies = [ "displaydoc", "potential_utf", @@ -1973,9 +2011,9 @@ dependencies = [ [[package]] name = "icu_locale_core" -version = "2.0.0" +version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0cde2700ccaed3872079a65fb1a78f6c0a36c91570f28755dda67bc8f7d9f00a" +checksum = "edba7861004dd3714265b4db54a3c390e880ab658fec5f7db895fae2046b5bb6" dependencies = [ "displaydoc", "litemap", @@ -1986,11 +2024,10 @@ dependencies = [ [[package]] name = "icu_normalizer" -version = "2.0.0" +version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "436880e8e18df4d7bbc06d58432329d6458cc84531f7ac5f024e93deadb37979" +checksum = "5f6c8828b67bf8908d82127b2054ea1b4427ff0230ee9141c54251934ab1b599" dependencies = [ - "displaydoc", "icu_collections", "icu_normalizer_data", "icu_properties", @@ -2001,42 +2038,38 @@ dependencies = [ [[package]] name = "icu_normalizer_data" -version = "2.0.0" +version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "00210d6893afc98edb752b664b8890f0ef174c8adbb8d0be9710fa66fbbf72d3" +checksum = "7aedcccd01fc5fe81e6b489c15b247b8b0690feb23304303a9e560f37efc560a" [[package]] name = "icu_properties" -version = "2.0.1" +version = "2.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "016c619c1eeb94efb86809b015c58f479963de65bdb6253345c1a1276f22e32b" +checksum = "020bfc02fe870ec3a66d93e677ccca0562506e5872c650f893269e08615d74ec" dependencies = [ - "displaydoc", "icu_collections", "icu_locale_core", "icu_properties_data", "icu_provider", - "potential_utf", "zerotrie", "zerovec", ] [[package]] name = "icu_properties_data" -version = "2.0.1" +version = "2.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "298459143998310acd25ffe6810ed544932242d3f07083eee1084d83a71bd632" +checksum = "616c294cf8d725c6afcd8f55abc17c56464ef6211f9ed59cccffe534129c77af" [[package]] name = "icu_provider" -version = "2.0.0" +version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03c80da27b5f4187909049ee2d72f276f0d9f99a42c306bd0131ecfe04d8e5af" +checksum = "85962cf0ce02e1e0a629cc34e7ca3e373ce20dda4c4d7294bbd0bf1fdb59e614" dependencies = [ "displaydoc", "icu_locale_core", - "stable_deref_trait", - "tinystr", "writeable", "yoke", "zerofrom", @@ -2052,19 +2085,20 @@ checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" [[package]] name = "idna" -version = "0.4.0" +version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d20d6b07bfbc108882d88ed8e37d39636dcc260e15e30c45e6ba089610b917c" +checksum = "418a0a6fab821475f634efe3ccc45c013f742efe03d853e8d3355d5cb850ecf8" dependencies = [ + "matches", "unicode-bidi", "unicode-normalization", ] [[package]] name = "idna" -version = "1.0.3" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "686f825264d630750a544639377bae737628043f20d38bbc029e8f29ea968a7e" +checksum = "3b0875f23caa03898994f6ddc501886a45c7d3d62d04d2d90788d47be1b1e4de" dependencies = [ "idna_adapter", "smallvec", @@ -2081,17 +2115,11 @@ dependencies = [ "icu_properties", ] -[[package]] -name = "if_chain" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb56e1aa765b4b4f3aadfab769793b7087bb03a4ea4920644a6d238e2df5b9ed" - [[package]] name = "indenter" -version = "0.3.3" +version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce23b50ad8242c51a442f3ff322d56b02f08852c77e4c0b4d3fd684abc89c683" +checksum = "964de6e86d545b246d84badc0fef527924ace5134f30641c203ef52ba83f58d5" [[package]] name = "indexmap" @@ -2106,12 +2134,12 @@ dependencies = [ [[package]] name = "indexmap" -version = "2.10.0" +version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fe4cd85333e22411419a0bcae1297d25e58c9443848b11dc6a86fefe8c78a661" +checksum = "d5477fe2230a79769d8dc68e0eabf5437907c0457a5614a9e8dddb67f65eb65d" dependencies = [ "equivalent", - "hashbrown 0.15.4", + "hashbrown 0.14.5", ] [[package]] @@ -2123,22 +2151,11 @@ dependencies = [ "cfg-if", ] -[[package]] -name = "io-uring" -version = "0.7.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b86e202f00093dcba4275d4636b93ef9dd75d025ae560d2521b45ea28ab49013" -dependencies = [ - "bitflags 2.9.1", - "cfg-if", - "libc", -] - [[package]] name = "ipnet" -version = "2.11.0" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "469fb0b9cefa57e3ef31275ee7cacb78f2fdca44e4765491884a2b119d4eb130" +checksum = "47be2f14c678be2fdcab04ab1171db51b2762ce6f0a8ee87c8dd4a04ed216135" [[package]] name = "ipnetwork" @@ -2148,20 +2165,20 @@ checksum = "cf370abdafd54d13e54a620e8c3e1145f28e46cc9d704bc6d94414559df41763" [[package]] name = "is-terminal" -version = "0.4.16" +version = "0.4.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e04d7f318608d35d4b61ddd75cbdaee86b023ebe2bd5a66ee0915f0bf93095a9" +checksum = "3640c1c38b8e4e43584d8df18be5fc6b0aa314ce6ebf51b53313d4306cca8e46" dependencies = [ "hermit-abi 0.5.2", "libc", - "windows-sys 0.59.0", + "windows-sys 0.61.2", ] [[package]] name = "is_terminal_polyfill" -version = "1.70.1" +version = "1.70.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" +checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695" [[package]] name = "itertools" @@ -2192,50 +2209,28 @@ dependencies = [ [[package]] name = "itoa" -version = "1.0.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" - -[[package]] -name = "jiff" -version = "0.2.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be1f93b8b1eb69c77f24bbb0afdf66f54b632ee39af40ca21c4365a1d7347e49" -dependencies = [ - "jiff-static", - "log", - "portable-atomic", - "portable-atomic-util", - "serde", -] - -[[package]] -name = "jiff-static" -version = "0.2.15" +version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03343451ff899767262ec32146f6d559dd759fdadf42ff0e227c7c48f72594b4" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.104", -] +checksum = "fad582f4b9e86b6caa621cabeb0963332d92eea04729ab12892c2533951e6440" [[package]] name = "jobserver" -version = "0.1.33" +version = "0.1.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38f262f097c174adebe41eb73d66ae9c06b2844fb0da69969647bbddd9b0538a" +checksum = "9afb3de4395d6b3e67a780b6de64b51c978ecf11cb9a462c66be7d4ca9039d33" dependencies = [ - "getrandom 0.3.3", + "getrandom 0.3.4", "libc", ] [[package]] name = "js-sys" -version = "0.3.77" +version = "0.3.97" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1cfaf33c695fc6e08064efbc1f72ec937429614f25eef83af942d0e227c3a28f" +checksum = "a1840c94c045fbcf8ba2812c95db44499f7c64910a912551aaaa541decebcacf" dependencies = [ + "cfg-if", + "futures-util", "once_cell", "wasm-bindgen", ] @@ -2266,9 +2261,9 @@ checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55" [[package]] name = "libc" -version = "0.2.174" +version = "0.2.186" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1171693293099992e19cddea4e8b849964e9846f4acee11b3948bcc337be8776" +checksum = "68ab91017fe16c622486840e4c83c9a37afeff978bd239b5293d61ece587de66" [[package]] name = "libgssapi" @@ -2294,29 +2289,30 @@ dependencies = [ [[package]] name = "libloading" -version = "0.8.8" +version = "0.8.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07033963ba89ebaf1584d767badaa2e8fcec21aedea6b8c0346d487d49c28667" +checksum = "d7c4b02199fee7c5d21a5ae7d8cfa79a6ef5bb2fc834d6e9058e89c825efdc55" dependencies = [ "cfg-if", - "windows-targets 0.53.2", + "windows-link", ] [[package]] name = "libm" -version = "0.2.15" +version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f9fbbcab51052fe104eb5e5d351cf728d30a5be1fe14d9be8a3b097481fb97de" +checksum = "b6d2cec3eae94f9f509c767b45932f1ada8350c4bdb85af2fcab4a3c14807981" [[package]] name = "libredox" -version = "0.1.12" +version = "0.1.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d0b95e02c851351f877147b7deea7b1afb1df71b63aa5f8270716e0c5720616" +checksum = "e02f3bb43d335493c96bf3fd3a321600bf6bd07ed34bc64118e9293bdffea46c" dependencies = [ - "bitflags 2.9.1", + "bitflags 2.4.0", "libc", - "redox_syscall 0.7.0", + "plain", + "redox_syscall 0.7.4", ] [[package]] @@ -2339,31 +2335,30 @@ checksum = "d26c52dbd32dccf2d10cac7725f8eae5296885fb5703b261f7d0a0739ec807ab" [[package]] name = "linux-raw-sys" -version = "0.9.4" +version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd945864f07fe9f5371a27ad7b52a172b4b499999f1d97574c9fa68373937e12" +checksum = "32a66949e030da00e8c7d4434b251670a91556f4144941d37452769c25d58a53" [[package]] name = "litemap" -version = "0.8.0" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "241eaef5fd12c88705a01fc1066c48c4b36e0dd4377dcdc7ec3942cea7a69956" +checksum = "92daf443525c4cce67b150400bc2316076100ce0b3686209eb8cf3c31612e6f0" [[package]] name = "lock_api" -version = "0.4.13" +version = "0.4.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96936507f153605bddfcda068dd804796c84324ed2510809e5b2a624c81da765" +checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965" dependencies = [ - "autocfg", "scopeguard", ] [[package]] name = "log" -version = "0.4.27" +version = "0.4.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94" +checksum = "518ef76f2f87365916b142844c16d8fefd85039bc5699050210a7778ee1cd1de" dependencies = [ "value-bag", ] @@ -2374,32 +2369,37 @@ version = "0.12.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "234cf4f4a04dc1f57e24b96cc0cd600cf2af460d4161ac5ecdd0af8e1f3b2a38" dependencies = [ - "hashbrown 0.15.4", + "hashbrown 0.15.5", ] [[package]] name = "mac_address" -version = "1.1.8" +version = "1.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0aeb26bf5e836cc1c341c8106051b573f1766dfa05aa87f0b98be5e51b02303" +checksum = "4863ee94f19ed315bf3bc00299338d857d4b5bc856af375cc97d237382ad3856" dependencies = [ "nix", "winapi", ] +[[package]] +name = "matches" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2532096657941c2fea9c289d370a250971c689d4f143798ff67113ec042024a5" + [[package]] name = "matchit" -version = "0.5.0" +version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "73cbba799671b762df5a175adf59ce145165747bb891505c43d09aefbbf38beb" +checksum = "47e1ffaa40ddd1f3ed91f717a33c8c0ee23fff369e3aa8772b9605cc1d22f4c3" [[package]] name = "md-5" -version = "0.10.6" +version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d89e7ee0cfbedfc4da3340218492196241d89eefb6dab27de5df917a6d2e78cf" +checksum = "e6a38fc55c8bbc10058782919516f88826e70320db6d206aebc49611d24216ae" dependencies = [ - "cfg-if", "digest", ] @@ -2411,15 +2411,15 @@ checksum = "7e6bcd6433cff03a4bfc3d9834d504467db1f1cf6d0ea765d37d330249ed629d" [[package]] name = "memchr" -version = "2.7.5" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32a282da65faaf38286cf3be983213fcf1d2e2a58700e808f83f4ea9a4804bc0" +checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" [[package]] name = "memoffset" -version = "0.9.1" +version = "0.6.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "488016bfae457b036d996092f6cb448677611ce4449e970ceaf42695203f218a" +checksum = "5aa361d4faea93603064a027415f07bd8e1d5c88c9fbf68bf56a285428fd79ce" dependencies = [ "autocfg", ] @@ -2438,11 +2438,11 @@ checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" [[package]] name = "miniz_oxide" -version = "0.8.9" +version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316" +checksum = "b8a240ddb74feaf34a79a7add65a741f3167852fba007066dcac1ca548d89c08" dependencies = [ - "adler2", + "adler", ] [[package]] @@ -2459,24 +2459,24 @@ dependencies = [ [[package]] name = "mio" -version = "1.0.4" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78bed444cc8a2160f01cbcf811ef18cac863ad68ae8ca62092e8db51d51c761c" +checksum = "50b7e5b27aa02a74bac8c3f23f448f8d87ff11f92d3aac1a6ed369ee08cc56c1" dependencies = [ "libc", "wasi 0.11.1+wasi-snapshot-preview1", - "windows-sys 0.59.0", + "windows-sys 0.61.2", ] [[package]] name = "mockall" -version = "0.11.4" +version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c84490118f2ee2d74570d114f3d0493cbf02790df303d2707606c3e14e07c96" +checksum = "3d4d70639a72f972725db16350db56da68266ca368b2a1fe26724a903ad3d6b8" dependencies = [ "cfg-if", "downcast", - "fragile", + "fragile 1.2.2", "lazy_static", "mockall_derive", "predicates 2.1.5", @@ -2485,9 +2485,9 @@ dependencies = [ [[package]] name = "mockall_derive" -version = "0.11.4" +version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22ce75669015c4f47b289fd4d4f56e894e4c96003ffdf3ac51313126f94c6cbb" +checksum = "79ef208208a0dea3f72221e26e904cdc6db2e481d9ade89081ddd494f1dbaa6b" dependencies = [ "cfg-if", "proc-macro2", @@ -2497,30 +2497,31 @@ dependencies = [ [[package]] name = "native-tls" -version = "0.2.14" +version = "0.2.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87de3442987e9dbec73158d5c715e7ad9072fda936bb03d19d7fa10e00520f0e" +checksum = "fd7e2f3618557f980e0b17e8856252eee3c97fa12c54dff0ca290fb6266ca4a9" dependencies = [ + "lazy_static", "libc", "log", "openssl", "openssl-probe", "openssl-sys", "schannel", - "security-framework 2.11.1", + "security-framework", "security-framework-sys", "tempfile", ] [[package]] name = "nix" -version = "0.29.0" +version = "0.23.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "71e2746dc3a24dd78b3cfcb7be93368c6de9963d30f43a6a73998a9cf4b17b46" +checksum = "8f3790c00a0150112de0f4cd161e3d7fc4b2d8a5542ffc35f099a2562aecb35c" dependencies = [ - "bitflags 2.9.1", + "bitflags 1.3.2", + "cc", "cfg-if", - "cfg_aliases", "libc", "memoffset", ] @@ -2553,21 +2554,21 @@ dependencies = [ [[package]] name = "num-bigint" -version = "0.4.6" +version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9" +checksum = "f93ab6289c7b344a8a9f60f88d80aa20032336fe78da341afc91c8a2341fc75f" dependencies = [ + "autocfg", "num-integer", "num-traits", ] [[package]] name = "num-bigint-dig" -version = "0.8.4" +version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc84195820f291c7697304f3cbdadd1cb7199c0efc917ff5eafd71225c136151" +checksum = "e661dda6640fad38e827a6d4a310ff4763082116fe217f279885c97f511bb0b7" dependencies = [ - "byteorder", "lazy_static", "libm", "num-integer", @@ -2616,24 +2617,24 @@ dependencies = [ [[package]] name = "object" -version = "0.36.7" +version = "0.32.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62948e14d923ea95ea2c7c86c71013138b66525b86bdc08d2dcc262bdb497b87" +checksum = "a6a622008b6e321afc04970976f62ee297fdbaa6f95318ca343e3eebb9648441" dependencies = [ "memchr", ] [[package]] name = "once_cell" -version = "1.21.3" +version = "1.21.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" +checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" [[package]] name = "once_cell_polyfill" -version = "1.70.1" +version = "1.70.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4895175b425cb1f87721b59f0f286c2092bd4af812243672510e1ac53e2e0ad" +checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe" [[package]] name = "oorandom" @@ -2643,11 +2644,11 @@ checksum = "d6790f58c7ff633d8771f42965289203411a5e5c68388703c06e14f24770b41e" [[package]] name = "openssl" -version = "0.10.73" +version = "0.10.46" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8505734d46c8ab1e19a1dce3aef597ad87dcb4c37e7188231769bd6bd51cebf8" +checksum = "fd2523381e46256e40930512c7fd25562b9eae4812cb52078f155e87217c9d1e" dependencies = [ - "bitflags 2.9.1", + "bitflags 1.3.2", "cfg-if", "foreign-types", "libc", @@ -2664,7 +2665,7 @@ checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.87", ] [[package]] @@ -2675,18 +2676,18 @@ checksum = "d05e27ee213611ffe7d6348b942e8f942b37114c00cc03cec254295a4a17852e" [[package]] name = "openssl-src" -version = "300.5.1+3.5.1" +version = "300.6.0+3.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "735230c832b28c000e3bc117119e6466a663ec73506bc0a9907ea4187508e42a" +checksum = "a8e8cbfd3a4a8c8f089147fd7aaa33cf8c7450c4d09f8f80698a0cf093abeff4" dependencies = [ "cc", ] [[package]] name = "openssl-sys" -version = "0.9.109" +version = "0.9.114" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90096e2e47630d78b7d1c20952dc621f957103f8bc2c8359ec81290d75238571" +checksum = "13ce1245cd07fcc4cfdb438f7507b0c7e4f3849a69fd84d52374c66d83741bb6" dependencies = [ "cc", "libc", @@ -2703,9 +2704,9 @@ checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" [[package]] name = "owo-colors" -version = "4.2.2" +version = "3.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "48dd4f4a2c8405440fd0462561f0e5806bd0f77e86f51c761481bdd4018b545e" +checksum = "c1b04fb49957986fdce4d6ee7a65027d55d4b6d2265e5848bbb507b58ccfdb6f" [[package]] name = "parking" @@ -2726,12 +2727,12 @@ dependencies = [ [[package]] name = "parking_lot" -version = "0.12.4" +version = "0.12.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70d58bf43669b5795d1576d0641cfb6fbb2057bf629506267a92807158584a13" +checksum = "93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a" dependencies = [ "lock_api", - "parking_lot_core 0.9.11", + "parking_lot_core 0.9.12", ] [[package]] @@ -2750,15 +2751,15 @@ dependencies = [ [[package]] name = "parking_lot_core" -version = "0.9.11" +version = "0.9.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc838d2a56b5b1a6c25f55575dfc605fabb63bb2365f6c2353ef9159aa69e4a5" +checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1" dependencies = [ "cfg-if", "libc", - "redox_syscall 0.5.13", + "redox_syscall 0.5.18", "smallvec", - "windows-targets 0.52.6", + "windows-link", ] [[package]] @@ -2785,9 +2786,9 @@ dependencies = [ [[package]] name = "paste" -version = "1.0.15" +version = "1.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" +checksum = "0744126afe1a6dd7f394cb50a716dbe086cb06e255e53d8d0185d82828358fb5" [[package]] name = "peeking_take_while" @@ -2806,35 +2807,35 @@ dependencies = [ [[package]] name = "percent-encoding" -version = "2.3.1" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" +checksum = "9b2a4787296e9989611394c33f193f676704af1686e70b8f8033ab5ba9a35a94" [[package]] name = "pin-project" -version = "1.1.10" +version = "1.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "677f1add503faace112b9f1373e43e9e054bfdd22ff1a63c1bc485eaec6a6a8a" +checksum = "f1749c7ed4bcaf4c3d0a3efc28538844fb29bcdd7d2b67b2be7e20ba861ff517" dependencies = [ "pin-project-internal", ] [[package]] name = "pin-project-internal" -version = "1.1.10" +version = "1.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e918e4ff8c4549eb882f14b3a4bc8c8bc93de829416eacf579f1207a8fbf861" +checksum = "d9b20ed30f105399776b9c883e68e536ef602a16ae6f596d2c473591d6ad64c6" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.87", ] [[package]] name = "pin-project-lite" -version = "0.2.16" +version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" +checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd" [[package]] name = "pin-utils" @@ -2844,9 +2845,9 @@ checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" [[package]] name = "piper" -version = "0.2.4" +version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96c8c490f422ef9a4efd2cb5b42b76c8613d7e7dfc1caf667b8a3350a5acc066" +checksum = "c835479a4443ded371d6c535cbfd8d31ad92c5d23ae9770a61bc155e4992a3c1" dependencies = [ "atomic-waker", "fastrand", @@ -2876,9 +2877,15 @@ dependencies = [ [[package]] name = "pkg-config" -version = "0.3.32" +version = "0.3.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19f132c84eca552bf34cab8ec81f1c1dcc229b811638f9d283dceabe58c5569e" + +[[package]] +name = "plain" +version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" +checksum = "b4596b6d070b27117e987119b4dac604f3c58cfb0b191112e24771b2faeac1a6" [[package]] name = "plotters" @@ -2910,39 +2917,23 @@ dependencies = [ [[package]] name = "polling" -version = "3.8.0" +version = "3.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b53a684391ad002dd6a596ceb6c74fd004fdce75f4be2e3f615068abbea5fd50" +checksum = "5d0e4f59085d47d8241c88ead0f274e8a0cb551f3625263c05eb8dd897c34218" dependencies = [ "cfg-if", "concurrent-queue", "hermit-abi 0.5.2", "pin-project-lite", - "rustix 1.0.7", - "tracing", - "windows-sys 0.59.0", -] - -[[package]] -name = "portable-atomic" -version = "1.11.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f84267b20a16ea918e43c6a88433c2d54fa145c92a811b5b047ccbe153674483" - -[[package]] -name = "portable-atomic-util" -version = "0.2.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d8a2f0d8d040d7848a709caf78912debcc3f33ee4b3cac47d73d1e1069e83507" -dependencies = [ - "portable-atomic", + "rustix 1.1.4", + "windows-sys 0.61.2", ] [[package]] name = "potential_utf" -version = "0.1.2" +version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5a7c30837279ca13e7c867e9e40053bc68740f988cb07f7ca6df43cc734b585" +checksum = "b73949432f5e2a09657003c25bca5e19a0e9c84f8058ca374f49e0ebe605af77" dependencies = [ "zerovec", ] @@ -2978,9 +2969,9 @@ dependencies = [ [[package]] name = "predicates" -version = "3.1.3" +version = "3.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a5d19ee57562043d37e82899fade9a22ebab7be9cef5026b07fda9cdd4293573" +checksum = "ada8f2932f28a27ee7b70dd6c1c39ea0675c55a36879ab92f3a715eaa1e63cfe" dependencies = [ "anstyle", "difflib", @@ -2989,15 +2980,15 @@ dependencies = [ [[package]] name = "predicates-core" -version = "1.0.9" +version = "1.0.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "727e462b119fe9c93fd0eb1429a5f7647394014cf3c04ab2c0350eeb09095ffa" +checksum = "cad38746f3166b4031b1a0d39ad9f954dd291e7854fcc0eed52ee41a0b50d144" [[package]] name = "predicates-tree" -version = "1.0.12" +version = "1.0.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72dd2d6d381dfb73a193c7fca536518d7caee39fc8503f74e7dc0be0531b425c" +checksum = "d0de1b847b39c8131db0467e9df1ff60e6d0562ab8e9a16e568ad0fdb372e2f2" dependencies = [ "predicates-core", "termtree", @@ -3009,54 +3000,42 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c6fa0831dd7cc608c38a5e323422a0077678fa5744aa2be4ad91c4ece8eec8d5" -[[package]] -name = "prettyplease" -version = "0.2.35" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "061c1221631e079b26479d25bbf2275bfe5917ae8419cd7e34f13bfc2aa7539a" -dependencies = [ - "proc-macro2", - "syn 2.0.104", -] - [[package]] name = "proc-macro-crate" -version = "3.3.0" +version = "3.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "edce586971a4dfaa28950c6f18ed55e0406c1ab88bbce2c6f6293a7aaba73d35" +checksum = "8ecf48c7ca261d60b74ab1a7b20da18bede46776b2e55535cb958eb595c5fa7b" dependencies = [ "toml_edit", ] [[package]] -name = "proc-macro-error" -version = "1.0.4" +name = "proc-macro-error-attr2" +version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" +checksum = "96de42df36bb9bba5542fe9f1a054b8cc87e172759a1868aa05c1f3acc89dfc5" dependencies = [ - "proc-macro-error-attr", "proc-macro2", "quote", - "syn 1.0.109", - "version_check", ] [[package]] -name = "proc-macro-error-attr" -version = "1.0.4" +name = "proc-macro-error2" +version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" +checksum = "11ec05c52be0a07b08061f7dd003e7d7092e0472bc731b4af7bb1ef876109802" dependencies = [ + "proc-macro-error-attr2", "proc-macro2", "quote", - "version_check", + "syn 2.0.87", ] [[package]] name = "proc-macro2" -version = "1.0.95" +version = "1.0.83" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "02b3e5e68a3a1a02aad3ec490a98007cbc13c37cbe84a3cd7b8e406d76e7f778" +checksum = "0b33eb56c327dec362a9e55b3ad14f9d2f0904fb5a5b03b513ab5465399e9f43" dependencies = [ "unicode-ident", ] @@ -3083,9 +3062,9 @@ dependencies = [ [[package]] name = "quote" -version = "1.0.40" +version = "1.0.35" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d" +checksum = "291ec9ab5efd934aaf503a6466c5d5251535d108ee747472c3977cc5acc868ef" dependencies = [ "proc-macro2", ] @@ -3161,7 +3140,7 @@ version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" dependencies = [ - "getrandom 0.2.16", + "getrandom 0.2.17", ] [[package]] @@ -3188,7 +3167,7 @@ version = "0.27.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d16546c5b5962abf8ce6e2881e722b4e0ae3b6f1a08a26ae3573c55853ca68d3" dependencies = [ - "bitflags 2.9.1", + "bitflags 2.4.0", "cassowary", "compact_str", "crossterm", @@ -3200,14 +3179,14 @@ dependencies = [ "strum_macros", "unicode-segmentation", "unicode-truncate", - "unicode-width 0.1.14", + "unicode-width", ] [[package]] name = "rayon" -version = "1.10.0" +version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b418a60154510ca1a002a752ca9714984e21e4241e804d32555251faf8b78ffa" +checksum = "fb39b166781f92d482534ef4b4b1b2568f42613b53e5b6c160e24cfbfa30926d" dependencies = [ "either", "rayon-core", @@ -3215,9 +3194,9 @@ dependencies = [ [[package]] name = "rayon-core" -version = "1.12.1" +version = "1.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1465873a3dfdaa8ae7cb14b4383657caab0b3e8a0aa9ae8e04b044854c8dfce2" +checksum = "22e18b0f0062d30d4230b2e85ff77fdfe4326feb054b9783a3460d8435c8ab91" dependencies = [ "crossbeam-deque", "crossbeam-utils", @@ -3234,50 +3213,44 @@ dependencies = [ [[package]] name = "redox_syscall" -version = "0.5.13" +version = "0.5.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d04b7d0ee6b4a0207a0a7adb104d23ecb0b47d6beae7152d0fa34b692b29fd6" +checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" dependencies = [ - "bitflags 2.9.1", + "bitflags 2.4.0", ] [[package]] name = "redox_syscall" -version = "0.7.0" +version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49f3fe0889e69e2ae9e41f4d6c4c0181701d00e4697b356fb1f74173a5e0ee27" +checksum = "f450ad9c3b1da563fb6948a8e0fb0fb9269711c9c73d9ea1de5058c79c8d643a" dependencies = [ - "bitflags 2.9.1", + "bitflags 2.4.0", ] [[package]] name = "regex" -version = "1.11.1" +version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191" +checksum = "4c4eb3267174b8c6c2f654116623910a0fef09c4753f8dd83db29c48a0df988b" dependencies = [ "aho-corasick", "memchr", - "regex-automata", "regex-syntax", ] [[package]] name = "regex-automata" -version = "0.4.9" +version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908" -dependencies = [ - "aho-corasick", - "memchr", - "regex-syntax", -] +checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132" [[package]] name = "regex-syntax" -version = "0.8.5" +version = "0.6.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" +checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1" [[package]] name = "rend" @@ -3296,7 +3269,7 @@ checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7" dependencies = [ "cc", "cfg-if", - "getrandom 0.2.16", + "getrandom 0.2.17", "libc", "untrusted", "windows-sys 0.52.0", @@ -3304,13 +3277,12 @@ dependencies = [ [[package]] name = "rkyv" -version = "0.7.45" +version = "0.7.42" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9008cd6385b9e161d8229e1f6549dd23c3d022f132a2ea37ac3a10ac4935779b" +checksum = "0200c8230b013893c0b2d6213d6ec64ed2b9be2e0e016682b7224ff82cff5c58" dependencies = [ "bitvec", "bytecheck", - "bytes", "hashbrown 0.12.3", "ptr_meta", "rend", @@ -3322,9 +3294,9 @@ dependencies = [ [[package]] name = "rkyv_derive" -version = "0.7.45" +version = "0.7.46" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "503d1d27590a2b0a3a4ca4c94755aa2875657196ecbf401a42eff41d7de532c0" +checksum = "84d7b42d4b8d06048d3ac8db0eb31bcb942cbeb709f0b5f2b2ebde398d3038f5" dependencies = [ "proc-macro2", "quote", @@ -3333,29 +3305,30 @@ dependencies = [ [[package]] name = "rsa" -version = "0.9.8" +version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78928ac1ed176a5ca1d17e578a1825f3d81ca54cf41053a592584b020cfd691b" +checksum = "3dd2017d3e6d67384f301f8b06fbf4567afc576430a61624d845eb04d2b30a72" dependencies = [ + "byteorder", "const-oid", "digest", "num-bigint-dig", "num-integer", + "num-iter", "num-traits", "pkcs1", "pkcs8", "rand_core 0.6.4", "signature", - "spki", "subtle", "zeroize", ] [[package]] name = "rust_decimal" -version = "1.37.2" +version = "1.36.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b203a6425500a03e0919c42d3c47caca51e79f1132046626d2c8871c5092035d" +checksum = "b082d80e3e3cc52b2ed634388d436fe1f4de6af5786cc2de9ba9737527bdf555" dependencies = [ "arrayvec", "borsh", @@ -3369,9 +3342,9 @@ dependencies = [ [[package]] name = "rustc-demangle" -version = "0.1.25" +version = "0.1.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "989e6739f80c4ad5b13e0fd7fe89531180375b18520cc8c82080e4dc4035b84f" +checksum = "b50b8869d9fc858ce7266cce0194bd74df58b9d0e3f6df3a9fc8eb470d95c09d" [[package]] name = "rustc-hash" @@ -3385,7 +3358,7 @@ version = "0.38.44" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fdb5bc1ae2baa591800df16c9ca78619bf65c0488b41b96ccec5d11220d8c154" dependencies = [ - "bitflags 2.9.1", + "bitflags 2.4.0", "errno", "libc", "linux-raw-sys 0.4.15", @@ -3394,22 +3367,22 @@ dependencies = [ [[package]] name = "rustix" -version = "1.0.7" +version = "1.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c71e83d6afe7ff64890ec6b71d6a69bb8a610ab78ce364b3352876bb4c801266" +checksum = "b6fe4565b9518b83ef4f91bb47ce29620ca828bd32cb7e408f0062e9930ba190" dependencies = [ - "bitflags 2.9.1", + "bitflags 2.4.0", "errno", "libc", - "linux-raw-sys 0.9.4", - "windows-sys 0.59.0", + "linux-raw-sys 0.12.1", + "windows-sys 0.61.2", ] [[package]] name = "rustls" -version = "0.23.28" +version = "0.23.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7160e3e10bf4535308537f3c4e1641468cd0e485175d6163087c0393c7d46643" +checksum = "96bf61953b1bc045820a2b947e6e9771c58c8c4b15242425b03f783ede1b34fe" dependencies = [ "aws-lc-rs", "once_cell", @@ -3422,30 +3395,40 @@ dependencies = [ [[package]] name = "rustls-native-certs" -version = "0.8.1" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7fcff2dd52b58a8d98a70243663a0d234c4e2b79235637849d15913394a247d3" +checksum = "fcaf18a4f2be7326cd874a5fa579fae794320a0f388d365dca7e480e55f83f8a" dependencies = [ "openssl-probe", + "rustls-pemfile", "rustls-pki-types", "schannel", - "security-framework 3.2.0", + "security-framework", +] + +[[package]] +name = "rustls-pemfile" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dce314e5fee3f39953d46bb63bb8a46d40c2f8fb7cc5a3b6cab2bde9721d6e50" +dependencies = [ + "rustls-pki-types", ] [[package]] name = "rustls-pki-types" -version = "1.12.0" +version = "1.14.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "229a4a4c221013e7e1f1a043678c5cc39fe5171437c88fb47151a21e6f5b5c79" +checksum = "30a7197ae7eb376e574fe940d068c30fe0462554a3ddbe4eca7838e049c937a9" dependencies = [ "zeroize", ] [[package]] name = "rustls-webpki" -version = "0.103.3" +version = "0.103.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e4a72fe2bcf7a6ac6fd7d0b9e5cb68aeb7d4c0a0271730218b3e92d43b4eb435" +checksum = "61c429a8649f110dddef65e2a5ad240f747e85f7758a6bccc7e5777bd33f756e" dependencies = [ "aws-lc-rs", "ring", @@ -3455,15 +3438,15 @@ dependencies = [ [[package]] name = "rustversion" -version = "1.0.21" +version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a0d197bd2c9dc6e53b84da9556a69ba4cdfab8619eb41a8bd1cc2027a0f6b1d" +checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" [[package]] name = "ryu" -version = "1.0.20" +version = "1.0.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" +checksum = "9774ba4a74de5f7b1c1451ed6cd5285a32eddb5cccb8cc655a4e50009e06477f" [[package]] name = "same-file" @@ -3476,11 +3459,11 @@ dependencies = [ [[package]] name = "schannel" -version = "0.1.27" +version = "0.1.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f29ebaa345f945cec9fbbc532eb307f0fdad8161f281b6369539c8d84876b3d" +checksum = "91c1b7e4904c873ef0710c1f407dde2e6287de2bebc1bbbf7d430bb7cbffd939" dependencies = [ - "windows-sys 0.59.0", + "windows-sys 0.61.2", ] [[package]] @@ -3497,25 +3480,12 @@ checksum = "1c107b6f4780854c8b126e228ea8869f4d7b71260f962fefb57b996b8959ba6b" [[package]] name = "security-framework" -version = "2.11.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02" -dependencies = [ - "bitflags 2.9.1", - "core-foundation 0.9.4", - "core-foundation-sys", - "libc", - "security-framework-sys", -] - -[[package]] -name = "security-framework" -version = "3.2.0" +version = "2.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "271720403f46ca04f7ba6f55d438f8bd878d6b8ca0a1046e8228c4145bcbb316" +checksum = "770452e37cad93e0a50d5abc3990d2bc351c36d0328f86cefec2f2fb206eaef6" dependencies = [ - "bitflags 2.9.1", - "core-foundation 0.10.1", + "bitflags 1.3.2", + "core-foundation", "core-foundation-sys", "libc", "security-framework-sys", @@ -3523,9 +3493,9 @@ dependencies = [ [[package]] name = "security-framework-sys" -version = "2.14.0" +version = "2.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49db231d56a190491cb4aeda9527f1ad45345af50b0851622a7adb8c03b01c32" +checksum = "6ce2691df843ecc5d231c0b14ece2acc3efb62c0a398c7e1d875f3983ce020e3" dependencies = [ "core-foundation-sys", "libc", @@ -3557,14 +3527,14 @@ checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.87", ] [[package]] name = "serde_json" -version = "1.0.140" +version = "1.0.142" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "20068b6e96dc6c9bd23e01df8827e6c7e1f2fddd43c21810382803c136b99373" +checksum = "030fedb782600dcbd6f02d479bf0d817ac3bb40d644745b769d6a96bc3afc5a7" dependencies = [ "itoa", "memchr", @@ -3572,6 +3542,16 @@ dependencies = [ "serde", ] +[[package]] +name = "serde_path_to_error" +version = "0.1.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59fab13f937fa393d08645bf3a84bdfe86e296747b506ada67bb15f10f218b2a" +dependencies = [ + "itoa", + "serde", +] + [[package]] name = "serde_spanned" version = "0.6.9" @@ -3595,9 +3575,9 @@ dependencies = [ [[package]] name = "serde_with" -version = "2.3.3" +version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07ff71d2c147a7b57362cead5e22f772cd52f6ab31cfcd9edcd7f6aeb2a0afbe" +checksum = "89df7a26519371a3cce44fbb914c2819c84d9b897890987fa3ab096491cc0ea8" dependencies = [ "base64 0.13.1", "chrono", @@ -3618,14 +3598,14 @@ dependencies = [ "darling", "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.87", ] [[package]] name = "sha1" -version = "0.10.6" +version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba" +checksum = "c77f4e7f65455545c2153c1253d25056825e77ee2533f0e41deb65a93a34852f" dependencies = [ "cfg-if", "cpufeatures", @@ -3634,9 +3614,9 @@ dependencies = [ [[package]] name = "sha2" -version = "0.10.9" +version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" +checksum = "99c3bd8169c58782adad9290a9af5939994036b76187f7b4f0e6de91dbbfc0ec" dependencies = [ "cfg-if", "cpufeatures", @@ -3654,9 +3634,9 @@ dependencies = [ [[package]] name = "shell-words" -version = "1.1.0" +version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "24188a676b6ae68c3b2cb3a01be17fbf7240ce009799bb56d5b1409051e78fde" +checksum = "dc6fe69c597f9c37bfeeeeeb33da3530379845f10be461a66d16d03eca2ded77" [[package]] name = "shlex" @@ -3676,9 +3656,9 @@ dependencies = [ [[package]] name = "signal-hook-mio" -version = "0.2.4" +version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34db1a06d485c9142248b7a054f034b349b212551f3dfd19c94d45a754a217cd" +checksum = "b75a19a7a740b25bc7944bdee6172368f988763b744e3d4dfe753f6b4ece40cc" dependencies = [ "libc", "mio 0.8.11", @@ -3687,10 +3667,11 @@ dependencies = [ [[package]] name = "signal-hook-registry" -version = "1.4.5" +version = "1.4.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9203b8055f63a2a00e2f593bb0510367fe707d7ff1e5c872de2f537b339e5410" +checksum = "c4db69cba1110affc0e9f7bcd48bbf87b3f4fc7c61fc9155afd4c469eb3d6c1b" dependencies = [ + "errno", "libc", ] @@ -3712,24 +3693,24 @@ checksum = "e3a9fe34e3e7a50316060351f37187a3f546bce95496156754b601a5fa71b76e" [[package]] name = "slab" -version = "0.4.10" +version = "0.4.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "04dc19736151f35336d325007ac991178d504a119863a2fcb3758cdb5e52c50d" +checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5" [[package]] name = "smallvec" -version = "1.15.1" +version = "1.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" +checksum = "e6ecd384b10a64542d77071bd64bd7b231f4ed5940fba55e98c3de13824cf3d7" dependencies = [ "serde", ] [[package]] name = "smol" -version = "2.0.2" +version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a33bd3e260892199c3ccfc487c88b2da2265080acb316cd920da72fdfd7c599f" +checksum = "e635339259e51ef85ac7aa29a1cd991b957047507288697a690e80ab97d07cad" dependencies = [ "async-channel 2.5.0", "async-executor", @@ -3779,7 +3760,7 @@ dependencies = [ "async-std", "criterion", "dotenvy", - "env_logger 0.11.8", + "env_logger 0.11.0", "futures-util", "hex", "libsqlite3-sys", @@ -3811,7 +3792,7 @@ dependencies = [ "backoff", "cargo_metadata", "chrono", - "clap 4.5.40", + "clap 4.4.7", "clap_complete", "console", "dialoguer", @@ -3835,8 +3816,8 @@ dependencies = [ "async-io", "async-std", "async-task", - "base64 0.22.1", - "bigdecimal 0.4.8", + "base64 0.22.0", + "bigdecimal 0.4.0", "bit-vec", "bstr", "bytes", @@ -3845,14 +3826,14 @@ dependencies = [ "crc", "crossbeam-queue", "either", - "event-listener 5.4.0", + "event-listener 5.2.0", "futures-core", "futures-intrusive", "futures-io", "futures-util", "hashbrown 0.16.0", "hashlink", - "indexmap 2.10.0", + "indexmap 2.0.0", "ipnet", "ipnetwork", "log", @@ -3873,7 +3854,7 @@ dependencies = [ "time", "tokio", "tokio-stream", - "toml", + "toml 0.8.16", "tracing", "url", "uuid", @@ -3885,7 +3866,7 @@ name = "sqlx-example-mssql-todos" version = "0.1.0" dependencies = [ "anyhow", - "clap 4.5.40", + "clap 4.4.7", "dotenvy", "sqlx", "tokio", @@ -3896,7 +3877,7 @@ name = "sqlx-example-mysql-todos" version = "0.1.0" dependencies = [ "anyhow", - "clap 4.5.40", + "clap 4.4.7", "sqlx", "tokio", ] @@ -3909,6 +3890,7 @@ dependencies = [ "argon2 0.4.1", "axum", "dotenvy", + "http-body-util", "rand 0.8.5", "regex", "serde", @@ -3918,7 +3900,7 @@ dependencies = [ "thiserror 2.0.17", "time", "tokio", - "tower", + "tower 0.5.2", "tracing", "uuid", "validator", @@ -3932,7 +3914,7 @@ dependencies = [ "ratatui", "sqlx", "tokio", - "unicode-width 0.1.14", + "unicode-width", ] [[package]] @@ -3950,7 +3932,7 @@ name = "sqlx-example-postgres-json" version = "0.1.0" dependencies = [ "anyhow", - "clap 4.5.40", + "clap 4.4.7", "dotenvy", "serde", "serde_json", @@ -3973,7 +3955,7 @@ version = "0.1.0" dependencies = [ "anyhow", "async-trait", - "clap 4.5.40", + "clap 4.4.7", "dotenvy", "mockall", "sqlx", @@ -4004,7 +3986,7 @@ dependencies = [ "rand 0.8.5", "serde", "sqlx", - "thiserror 1.0.69", + "thiserror 1.0.40", "time", "tokio", "uuid", @@ -4045,7 +4027,7 @@ dependencies = [ "rand 0.8.5", "serde", "sqlx", - "thiserror 1.0.69", + "thiserror 1.0.40", "time", "tokio", "uuid", @@ -4102,7 +4084,7 @@ name = "sqlx-example-postgres-todos" version = "0.1.0" dependencies = [ "anyhow", - "clap 4.5.40", + "clap 4.4.7", "dotenvy", "sqlx", "tokio", @@ -4125,12 +4107,21 @@ dependencies = [ "tokio", ] +[[package]] +name = "sqlx-example-sqlite-serialize" +version = "0.1.0" +dependencies = [ + "anyhow", + "sqlx", + "tokio", +] + [[package]] name = "sqlx-example-sqlite-todos" version = "0.1.0" dependencies = [ "anyhow", - "clap 4.5.40", + "clap 4.4.7", "sqlx", "tokio", ] @@ -4143,7 +4134,7 @@ dependencies = [ "quote", "sqlx-core", "sqlx-macros-core", - "syn 2.0.104", + "syn 2.0.87", ] [[package]] @@ -4168,7 +4159,7 @@ dependencies = [ "sqlx-mysql", "sqlx-postgres", "sqlx-sqlite", - "syn 2.0.104", + "syn 2.0.87", "thiserror 2.0.17", "tokio", "url", @@ -4179,7 +4170,7 @@ name = "sqlx-mssql" version = "0.9.0-alpha.1" dependencies = [ "async-std", - "bigdecimal 0.4.8", + "bigdecimal 0.4.0", "bytes", "chrono", "dotenvy", @@ -4206,10 +4197,8 @@ dependencies = [ name = "sqlx-mysql" version = "0.9.0-alpha.1" dependencies = [ - "atoi", - "base64 0.22.1", - "bigdecimal 0.4.8", - "bitflags 2.9.1", + "bigdecimal 0.4.0", + "bitflags 2.4.0", "byteorder", "bytes", "chrono", @@ -4217,18 +4206,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 0.8.5", "rsa", @@ -4236,10 +4217,8 @@ dependencies = [ "serde", "sha1", "sha2", - "smallvec", "sqlx", "sqlx-core", - "stringprep", "thiserror 2.0.17", "time", "tracing", @@ -4251,10 +4230,10 @@ name = "sqlx-postgres" version = "0.9.0-alpha.1" dependencies = [ "atoi", - "base64 0.22.1", - "bigdecimal 0.4.8", + "base64 0.22.0", + "bigdecimal 0.4.0", "bit-vec", - "bitflags 2.9.1", + "bitflags 2.4.0", "byteorder", "chrono", "crc", @@ -4323,7 +4302,7 @@ version = "0.1.0" dependencies = [ "anyhow", "dotenvy", - "env_logger 0.11.8", + "env_logger 0.11.0", "sqlx", ] @@ -4334,14 +4313,14 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d904e7009df136af5297832a3ace3370cd14ff1546a232f4f185036c2736fcac" dependencies = [ "quote", - "syn 2.0.104", + "syn 2.0.87", ] [[package]] name = "stable_deref_trait" -version = "1.2.0" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" +checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" [[package]] name = "static_assertions" @@ -4351,13 +4330,12 @@ checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" [[package]] name = "stringprep" -version = "0.1.5" +version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b4df3d392d81bd458a8a621b8bffbd2302a12ffe288a9d931670948749463b1" +checksum = "8ee348cb74b87454fff4b551cbf727025810a004f88aeacae7f85b87f4e9a1c1" dependencies = [ "unicode-bidi", "unicode-normalization", - "unicode-properties", ] [[package]] @@ -4366,6 +4344,12 @@ version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a" +[[package]] +name = "strsim" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" + [[package]] name = "strsim" version = "0.11.1" @@ -4391,7 +4375,7 @@ dependencies = [ "proc-macro2", "quote", "rustversion", - "syn 2.0.104", + "syn 2.0.87", ] [[package]] @@ -4413,9 +4397,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.104" +version = "2.0.87" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "17b6f705963418cdb9927482fa304bc562ece2fdd4f616084c50b7023b435a40" +checksum = "25aa4ce346d03a6dcd68dd8b4010bcb74e54e62c90c573f394c46eae99aba32d" dependencies = [ "proc-macro2", "quote", @@ -4424,9 +4408,9 @@ dependencies = [ [[package]] name = "sync_wrapper" -version = "0.1.2" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2047c6ded9c721764247e62cd3b03c09ffc529b2ba5b10ec482ae507a4a70160" +checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263" [[package]] name = "synstructure" @@ -4436,7 +4420,7 @@ checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.87", ] [[package]] @@ -4445,23 +4429,16 @@ version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" -[[package]] -name = "target-triple" -version = "0.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ac9aa371f599d22256307c24a9d748c041e548cbf599f35d890f9d365361790" - [[package]] name = "tempfile" -version = "3.20.0" +version = "3.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e8a64e3985349f2441a1a9ef0b853f869006c3855f2cda6862a94d26ebb9d6a1" +checksum = "85b77fafb263dd9d05cbeac119526425676db3784113aa9295c88498cbf8bff1" dependencies = [ + "cfg-if", "fastrand", - "getrandom 0.3.3", - "once_cell", - "rustix 1.0.7", - "windows-sys 0.59.0", + "rustix 0.38.44", + "windows-sys 0.52.0", ] [[package]] @@ -4475,12 +4452,22 @@ dependencies = [ [[package]] name = "terminal_size" -version = "0.4.2" +version = "0.1.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "45c6481c4829e4cc63825e62c49186a34538b7b2750b73b266581ffb612fb5ed" +checksum = "633c1a546cee861a1a6d0dc69ebeca693bf4296661ba7852b9d21d159e0506df" dependencies = [ - "rustix 1.0.7", - "windows-sys 0.59.0", + "libc", + "winapi", +] + +[[package]] +name = "terminal_size" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "21bebf2b7c9e0a515f6e0f8c51dc0f8e4696391e6f1ff30379559f8365fb0df7" +dependencies = [ + "rustix 0.38.44", + "windows-sys 0.48.0", ] [[package]] @@ -4495,16 +4482,16 @@ version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060" dependencies = [ - "unicode-width 0.1.14", + "unicode-width", ] [[package]] name = "thiserror" -version = "1.0.69" +version = "1.0.40" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" +checksum = "978c9a314bd8dc99be594bc3c175faaa9794be04a5a5e153caba6915336cebac" dependencies = [ - "thiserror-impl 1.0.69", + "thiserror-impl 1.0.40", ] [[package]] @@ -4518,13 +4505,13 @@ dependencies = [ [[package]] name = "thiserror-impl" -version = "1.0.69" +version = "1.0.40" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" +checksum = "f9456a42c5b0d803c8cd86e73dd7cc9edd429499f37a3550d286d5e86720569f" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.87", ] [[package]] @@ -4535,7 +4522,7 @@ checksum = "3ff15c8ecd7de3849db632e14d18d2571fa09dfc5ed93479bc4485c7a517c913" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.87", ] [[package]] @@ -4569,7 +4556,7 @@ dependencies = [ "pin-project-lite", "pretty-hex", "rust_decimal", - "thiserror 1.0.69", + "thiserror 1.0.40", "time", "tracing", "uuid", @@ -4578,9 +4565,9 @@ dependencies = [ [[package]] name = "time" -version = "0.3.41" +version = "0.3.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a7619e19bc266e0f9c5e6686659d394bc57973859340060a69221e57dbc0c40" +checksum = "35e7868883861bd0e56d9ac6efcaaca0d6d5d82a2a7ec8209ff492c07cf37b21" dependencies = [ "deranged", "itoa", @@ -4593,15 +4580,15 @@ dependencies = [ [[package]] name = "time-core" -version = "0.1.4" +version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c9e9a38711f559d9e3ce1cdb06dd7c5b8ea546bc90052da6d06bb76da74bb07c" +checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3" [[package]] name = "time-macros" -version = "0.2.22" +version = "0.2.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3526739392ec93fd8b359c8e98514cb3e8e021beb4e5f597b00a0221f8ed8a49" +checksum = "2834e6017e3e5e4b9834939793b282bc03b37a3336245fa820e35e233e2a85de" dependencies = [ "num-conv", "time-core", @@ -4609,9 +4596,9 @@ dependencies = [ [[package]] name = "tinystr" -version = "0.8.1" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d4f6d1145dcb577acf783d4e601bc1d76a13337bb54e6233add580b07344c8b" +checksum = "42d3e9c45c09de15d06dd8acf5f4e0e399e85927b7f00711024eb7ae10fa4869" dependencies = [ "displaydoc", "zerovec", @@ -4629,9 +4616,9 @@ dependencies = [ [[package]] name = "tinyvec" -version = "1.9.0" +version = "1.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09b3661f17e86524eccd4371ab0429194e0d7c008abb45f7a7495b1719463c71" +checksum = "3e61e67053d25a4e82c844e8424039d9745781b3fc4f32b8d55ed50f5f667ef3" dependencies = [ "tinyvec_macros", ] @@ -4644,19 +4631,17 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" -version = "1.46.1" +version = "1.43.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0cc3a2344dafbe23a245241fe8b09735b521110d30fcefbbd5feb1797ca35d17" +checksum = "333f1ce734dbc263af1106964dba1f8c993a91d1857910fb542d45179c3d3da5" dependencies = [ "backtrace", "bytes", - "io-uring", "libc", - "mio 1.0.4", - "parking_lot 0.12.4", + "mio 1.2.0", + "parking_lot 0.12.5", "pin-project-lite", "signal-hook-registry", - "slab", "socket2", "tokio-macros", "windows-sys 0.52.0", @@ -4670,14 +4655,14 @@ checksum = "6e06d43f1345a3bcd39f6a56dbb7dcab2ba47e68e8ac134855e7e2bdbaf8cab8" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.87", ] [[package]] name = "tokio-stream" -version = "0.1.17" +version = "0.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eca58d7bba4a75707817a2c44174253f9236b2d5fbd055602e9d5c07c139a047" +checksum = "50145484efff8818b5ccd256697f36863f587da82cf8b409c53adf1e840798e3" dependencies = [ "futures-core", "pin-project-lite", @@ -4686,9 +4671,9 @@ dependencies = [ [[package]] name = "tokio-util" -version = "0.7.18" +version = "0.7.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ae9cec805b01e8fc3fd2fe289f89149a9b66dd16786abd8b19cfa7b48cb0098" +checksum = "d7fcaa8d55a2bdd6b83ace262b016eca0d79ee02818c5c1bcdf0305114081078" dependencies = [ "bytes", "futures-core", @@ -4700,9 +4685,18 @@ dependencies = [ [[package]] name = "toml" -version = "0.8.23" +version = "0.5.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4f7f0dd8d50a853a531c426359045b1998f04219d88799810762cd4ad314234" +dependencies = [ + "serde", +] + +[[package]] +name = "toml" +version = "0.8.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc1beb996b9d83529a9e75c17a1686767d148d70663143c7854d8b4a09ced362" +checksum = "81967dd0dd2c1ab0bc3468bd7caecc32b8a4aa47d0c8c695d8c2b2108168d62c" dependencies = [ "serde", "serde_spanned", @@ -4721,24 +4715,17 @@ dependencies = [ [[package]] name = "toml_edit" -version = "0.22.27" +version = "0.22.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41fe8c660ae4257887cf66394862d21dbca4a6ddd26f04a3560410406a2f819a" +checksum = "583c44c02ad26b0c3f3066fe629275e50627026c51ac2e595cca4c230ce1ce1d" dependencies = [ - "indexmap 2.10.0", + "indexmap 2.0.0", "serde", "serde_spanned", "toml_datetime", - "toml_write", "winnow", ] -[[package]] -name = "toml_write" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d99f8c9a7727884afe522e9bd5edbfc91a3312b36a77b5fb8926e4c31a41801" - [[package]] name = "tower" version = "0.4.13" @@ -4752,26 +4739,22 @@ dependencies = [ "tokio", "tower-layer", "tower-service", - "tracing", ] [[package]] -name = "tower-http" -version = "0.3.5" +name = "tower" +version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f873044bf02dd1e8239e9c1293ea39dad76dc594ec16185d0a1bf31d8dc8d858" +checksum = "d039ad9159c98b70ecfd540b2573b97f7f52c3e8d9f8ad57a24b916a536975f9" dependencies = [ - "bitflags 1.3.2", - "bytes", "futures-core", "futures-util", - "http", - "http-body", - "http-range-header", "pin-project-lite", - "tower", + "sync_wrapper", + "tokio", "tower-layer", "tower-service", + "tracing", ] [[package]] @@ -4788,10 +4771,11 @@ checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" [[package]] name = "tracing" -version = "0.1.41" +version = "0.1.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0" +checksum = "8ce8c33a8d48bd45d624a6e523445fd21ec13d3653cd51f681abf67418f54eb8" dependencies = [ + "cfg-if", "log", "pin-project-lite", "tracing-attributes", @@ -4800,20 +4784,20 @@ dependencies = [ [[package]] name = "tracing-attributes" -version = "0.1.30" +version = "0.1.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81383ab64e72a7a8b8e13130c49e3dab29def6d0c7d76a03087b3cf71c5c6903" +checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.87", ] [[package]] name = "tracing-core" -version = "0.1.34" +version = "0.1.36" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9d12581f227e93f094d3af2ae690a574abb8a2b9b7a96e7cfe9647b2b617678" +checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a" dependencies = [ "once_cell", "valuable", @@ -4854,32 +4838,25 @@ dependencies = [ "tracing-log", ] -[[package]] -name = "try-lock" -version = "0.2.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" - [[package]] name = "trybuild" -version = "1.0.105" +version = "1.0.53" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1c9bf9513a2f4aeef5fdac8677d7d349c79fdbcc03b9c86da6e9d254f1e43be2" +checksum = "9d664de8ea7e531ad4c0f5a834f20b8cb2b8e6dfe88d05796ee7887518ed67b9" dependencies = [ "glob", + "lazy_static", "serde", - "serde_derive", "serde_json", - "target-triple", "termcolor", - "toml", + "toml 0.5.11", ] [[package]] name = "typenum" -version = "1.18.0" +version = "1.20.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1dccffe3ce07af9386bfd29e80c0ab1a8205a2fc34e4bcd40364df902cfa8f3f" +checksum = "40ce102ab67701b8526c123c1bab5cbe42d7040ccfd0f64af1a385808d2f43de" [[package]] name = "unicode-bidi" @@ -4889,30 +4866,24 @@ checksum = "5c1cb5db39152898a79168971543b1cb5020dff7fe43c8dc468b0885f5e29df5" [[package]] name = "unicode-ident" -version = "1.0.18" +version = "1.0.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" +checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" [[package]] name = "unicode-normalization" -version = "0.1.24" +version = "0.1.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5033c97c4262335cded6d6fc3e5c18ab755e1a3dc96376350f3d8e9f009ad956" +checksum = "5fd4f6878c9cb28d874b009da9e8d183b5abc80117c40bbd187a1fde336be6e8" dependencies = [ "tinyvec", ] -[[package]] -name = "unicode-properties" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e70f2a8b45122e719eb623c01822704c4e0907e7e426a05927e1a1cfff5b75d0" - [[package]] name = "unicode-segmentation" -version = "1.12.0" +version = "1.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493" +checksum = "9629274872b2bfaf8d66f5f15725007f635594914870f65218920345aa11aa8c" [[package]] name = "unicode-truncate" @@ -4922,20 +4893,14 @@ checksum = "b3644627a5af5fa321c95b9b235a72fd24cd29c648c2c379431e6628655627bf" dependencies = [ "itertools 0.13.0", "unicode-segmentation", - "unicode-width 0.1.14", + "unicode-width", ] [[package]] name = "unicode-width" -version = "0.1.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af" - -[[package]] -name = "unicode-width" -version = "0.2.1" +version = "0.1.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4a1a07cc7db3810833284e8d372ccdc6da29741639ecc70c9ec107df0fa6154c" +checksum = "0336d538f7abc86d282a4189614dfaa90810dfc2c6f6427eaf88e16311dd225d" [[package]] name = "untrusted" @@ -4945,12 +4910,13 @@ checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" [[package]] name = "url" -version = "2.5.4" +version = "2.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32f8b686cadd1473f4bd0117a5d28d36b1ade384ea9b5069a1c40aefed7fda60" +checksum = "a507c383b2d33b5fc35d1861e77e6b383d158b2da5e14fe51b83dfedf6fd578c" dependencies = [ "form_urlencoded", - "idna 1.0.3", + "idna 0.2.3", + "matches", "percent-encoding", ] @@ -4968,23 +4934,21 @@ checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" [[package]] name = "uuid" -version = "1.17.0" +version = "1.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3cf4199d1e5d15ddd86a694e4d0dffa9c323ce759fea589f00fef9d81cc1931d" +checksum = "b3758f5e68192bb96cc8f9b7e2c2cfdabb435499a28499a42f8f984092adad4b" dependencies = [ - "js-sys", "serde", - "wasm-bindgen", ] [[package]] name = "validator" -version = "0.16.1" +version = "0.20.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b92f40481c04ff1f4f61f304d61793c7b56ff76ac1469f1beb199b1445b253bd" +checksum = "43fb22e1a008ece370ce08a3e9e4447a910e92621bb49b85d6e48a45397e7cfa" dependencies = [ - "idna 0.4.0", - "lazy_static", + "idna 1.1.0", + "once_cell", "regex", "serde", "serde_derive", @@ -4995,28 +4959,16 @@ dependencies = [ [[package]] name = "validator_derive" -version = "0.16.0" +version = "0.20.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc44ca3088bb3ba384d9aecf40c6a23a676ce23e09bdaca2073d99c207f864af" +checksum = "b7df16e474ef958526d1205f6dda359fdfab79d9aa6d54bafcb92dcd07673dca" dependencies = [ - "if_chain", - "lazy_static", - "proc-macro-error", + "darling", + "once_cell", + "proc-macro-error2", "proc-macro2", "quote", - "regex", - "syn 1.0.109", - "validator_types", -] - -[[package]] -name = "validator_types" -version = "0.16.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "111abfe30072511849c5910134e8baf8dc05de4c0e5903d681cbd5c9c4d611e3" -dependencies = [ - "proc-macro2", - "syn 1.0.109", + "syn 2.0.87", ] [[package]] @@ -5027,9 +4979,9 @@ checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65" [[package]] name = "value-bag" -version = "1.11.1" +version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "943ce29a8a743eb10d6082545d861b24f9d1b160b7d741e0f2cdf726bec909c5" +checksum = "7ba6f5989077681266825251a52748b8c1d8a4ad098cc37e440103d0ea717fc0" [[package]] name = "vcpkg" @@ -5068,15 +5020,6 @@ dependencies = [ "winapi-util", ] -[[package]] -name = "want" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e" -dependencies = [ - "try-lock", -] - [[package]] name = "wasi" version = "0.9.0+wasi-snapshot-preview1" @@ -5090,58 +5033,42 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" [[package]] -name = "wasi" -version = "0.14.2+wasi-0.2.4" +name = "wasip2" +version = "1.0.3+wasi-0.2.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9683f9a5a998d873c0d21fcbe3c083009670149a8fab228644b8bd36b2c48cb3" +checksum = "20064672db26d7cdc89c7798c48a0fdfac8213434a1186e5ef29fd560ae223d6" dependencies = [ - "wit-bindgen-rt", + "wit-bindgen", ] [[package]] name = "wasm-bindgen" -version = "0.2.100" +version = "0.2.120" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1edc8929d7499fc4e8f0be2262a241556cfc54a0bea223790e71446f2aab1ef5" +checksum = "df52b6d9b87e0c74c9edfa1eb2d9bf85e5d63515474513aa50fa181b3c4f5db1" dependencies = [ "cfg-if", "once_cell", "rustversion", "wasm-bindgen-macro", -] - -[[package]] -name = "wasm-bindgen-backend" -version = "0.2.100" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2f0a0651a5c2bc21487bde11ee802ccaf4c51935d0d3d42a6101f98161700bc6" -dependencies = [ - "bumpalo", - "log", - "proc-macro2", - "quote", - "syn 2.0.104", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-futures" -version = "0.4.50" +version = "0.4.70" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "555d470ec0bc3bb57890405e5d4322cc9ea83cebb085523ced7be4144dac1e61" +checksum = "af934872acec734c2d80e6617bbb5ff4f12b052dd8e6332b0817bce889516084" dependencies = [ - "cfg-if", "js-sys", - "once_cell", "wasm-bindgen", - "web-sys", ] [[package]] name = "wasm-bindgen-macro" -version = "0.2.100" +version = "0.2.120" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7fe63fc6d09ed3792bd0897b314f53de8e16568c2b3f7982f468c0bf9bd0b407" +checksum = "78b1041f495fb322e64aca85f5756b2172e35cd459376e67f2a6c9dffcedb103" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -5149,31 +5076,31 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.100" +version = "0.2.120" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de" +checksum = "9dcd0ff20416988a18ac686d4d4d0f6aae9ebf08a389ff5d29012b05af2a1b41" dependencies = [ + "bumpalo", "proc-macro2", "quote", - "syn 2.0.104", - "wasm-bindgen-backend", + "syn 2.0.87", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" -version = "0.2.100" +version = "0.2.120" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a05d73b933a847d6cccdda8f838a22ff101ad9bf93e33684f39c1f5f0eece3d" +checksum = "49757b3c82ebf16c57d69365a142940b384176c24df52a087fb748e2085359ea" dependencies = [ "unicode-ident", ] [[package]] name = "web-sys" -version = "0.3.77" +version = "0.3.97" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33b6dd2ef9186f1f2072e409e99cd22a975331a6b3591b12c764e0e55c60d5d2" +checksum = "2eadbac71025cd7b0834f20d1fe8472e8495821b4e9801eb0a60bd1f19827602" dependencies = [ "js-sys", "wasm-bindgen", @@ -5181,9 +5108,9 @@ dependencies = [ [[package]] name = "webpki-roots" -version = "1.0.1" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8782dd5a41a24eed3a4f40b606249b3e236ca61adf1f25ea4d45c73de122b502" +checksum = "2853738d1cc4f2da3a225c18ec6c3721abb31961096e9dbf5ab35fa88b19cfdb" dependencies = [ "rustls-pki-types", ] @@ -5227,11 +5154,11 @@ checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" [[package]] name = "winapi-util" -version = "0.1.9" +version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" +checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" dependencies = [ - "windows-sys 0.59.0", + "windows-sys 0.61.2", ] [[package]] @@ -5255,9 +5182,9 @@ dependencies = [ [[package]] name = "windows-core" -version = "0.61.2" +version = "0.62.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0fdd3ddb90610c7638aa2b3a3ab2904fb9e5cdbecc643ddb3647212781c4ae3" +checksum = "b8e83a14d34d0623b51dce9581199302a221863196a1dde71a7663a4c2be9deb" dependencies = [ "windows-implement", "windows-interface", @@ -5268,46 +5195,46 @@ dependencies = [ [[package]] name = "windows-implement" -version = "0.60.0" +version = "0.60.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a47fddd13af08290e67f4acabf4b459f647552718f683a7b415d290ac744a836" +checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.87", ] [[package]] name = "windows-interface" -version = "0.59.1" +version = "0.59.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd9211b69f8dcdfa817bfd14bf1c97c9188afa36f4750130fcdf3f400eca9fa8" +checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.87", ] [[package]] name = "windows-link" -version = "0.1.3" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e6ad25900d524eaabdbbb96d20b4311e1e7ae1699af4fb28c17ae66c80d798a" +checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" [[package]] name = "windows-result" -version = "0.3.4" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56f42bd332cc6c8eac5af113fc0c1fd6a8fd2aa08a0119358686e5160d0586c6" +checksum = "7781fa89eaf60850ac3d2da7af8e5242a5ea78d1a11c49bf2910bb5a73853eb5" dependencies = [ "windows-link", ] [[package]] name = "windows-strings" -version = "0.4.2" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56e6c93f3a0c3b36176cb1327a4958a0353d5d166c2a35cb268ace15e91d3b57" +checksum = "7837d08f69c77cf6b07689544538e017c1bfcf57e34b4c0ff58e6c2cd3b37091" dependencies = [ "windows-link", ] @@ -5341,11 +5268,11 @@ dependencies = [ [[package]] name = "windows-sys" -version = "0.60.2" +version = "0.61.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb" +checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" dependencies = [ - "windows-targets 0.53.2", + "windows-link", ] [[package]] @@ -5372,29 +5299,13 @@ dependencies = [ "windows_aarch64_gnullvm 0.52.6", "windows_aarch64_msvc 0.52.6", "windows_i686_gnu 0.52.6", - "windows_i686_gnullvm 0.52.6", + "windows_i686_gnullvm", "windows_i686_msvc 0.52.6", "windows_x86_64_gnu 0.52.6", "windows_x86_64_gnullvm 0.52.6", "windows_x86_64_msvc 0.52.6", ] -[[package]] -name = "windows-targets" -version = "0.53.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c66f69fcc9ce11da9966ddb31a40968cad001c5bedeb5c2b82ede4253ab48aef" -dependencies = [ - "windows_aarch64_gnullvm 0.53.0", - "windows_aarch64_msvc 0.53.0", - "windows_i686_gnu 0.53.0", - "windows_i686_gnullvm 0.53.0", - "windows_i686_msvc 0.53.0", - "windows_x86_64_gnu 0.53.0", - "windows_x86_64_gnullvm 0.53.0", - "windows_x86_64_msvc 0.53.0", -] - [[package]] name = "windows_aarch64_gnullvm" version = "0.48.5" @@ -5407,12 +5318,6 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" -[[package]] -name = "windows_aarch64_gnullvm" -version = "0.53.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "86b8d5f90ddd19cb4a147a5fa63ca848db3df085e25fee3cc10b39b6eebae764" - [[package]] name = "windows_aarch64_msvc" version = "0.48.5" @@ -5425,12 +5330,6 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" -[[package]] -name = "windows_aarch64_msvc" -version = "0.53.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c7651a1f62a11b8cbd5e0d42526e55f2c99886c77e007179efff86c2b137e66c" - [[package]] name = "windows_i686_gnu" version = "0.48.5" @@ -5443,24 +5342,12 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" -[[package]] -name = "windows_i686_gnu" -version = "0.53.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1dc67659d35f387f5f6c479dc4e28f1d4bb90ddd1a5d3da2e5d97b42d6272c3" - [[package]] name = "windows_i686_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" -[[package]] -name = "windows_i686_gnullvm" -version = "0.53.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ce6ccbdedbf6d6354471319e781c0dfef054c81fbc7cf83f338a4296c0cae11" - [[package]] name = "windows_i686_msvc" version = "0.48.5" @@ -5473,12 +5360,6 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" -[[package]] -name = "windows_i686_msvc" -version = "0.53.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "581fee95406bb13382d2f65cd4a908ca7b1e4c2f1917f143ba16efe98a589b5d" - [[package]] name = "windows_x86_64_gnu" version = "0.48.5" @@ -5491,12 +5372,6 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" -[[package]] -name = "windows_x86_64_gnu" -version = "0.53.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2e55b5ac9ea33f2fc1716d1742db15574fd6fc8dadc51caab1c16a3d3b4190ba" - [[package]] name = "windows_x86_64_gnullvm" version = "0.48.5" @@ -5509,12 +5384,6 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" -[[package]] -name = "windows_x86_64_gnullvm" -version = "0.53.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0a6e035dd0599267ce1ee132e51c27dd29437f63325753051e71dd9e42406c57" - [[package]] name = "windows_x86_64_msvc" version = "0.48.5" @@ -5527,35 +5396,26 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" -[[package]] -name = "windows_x86_64_msvc" -version = "0.53.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "271414315aff87387382ec3d271b52d7ae78726f5d44ac98b4f4030c91880486" - [[package]] name = "winnow" -version = "0.7.11" +version = "0.6.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "74c7b26e3480b707944fc872477815d29a8e429d2f93a1ce000f5fa84a15cbcd" +checksum = "1e90edd2ac1aa278a5c4599b1d89cf03074b610800f866d4026dc199d7929a28" dependencies = [ "memchr", ] [[package]] -name = "wit-bindgen-rt" -version = "0.39.0" +name = "wit-bindgen" +version = "0.57.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6f42320e61fe2cfd34354ecb597f86f413484a798ba44a8ca1165c58d42da6c1" -dependencies = [ - "bitflags 2.9.1", -] +checksum = "1ebf944e87a7c253233ad6766e082e3cd714b5d03812acc24c318f549614536e" [[package]] name = "writeable" -version = "0.6.1" +version = "0.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea2f10b9bb0928dfb1b42b65e1f9e36f7f54dbdf08457afefb38afcdec4fa2bb" +checksum = "1ffae5123b2d3fc086436f8834ae3ab053a283cfac8fe0a0b8eaae044768a4c4" [[package]] name = "wyz" @@ -5568,11 +5428,10 @@ dependencies = [ [[package]] name = "yoke" -version = "0.8.0" +version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f41bb01b8226ef4bfd589436a297c53d118f65921786300e427be8d487695cc" +checksum = "72d6e5c6afb84d73944e5cedb052c4680d5657337201555f9f2a16b7406d4954" dependencies = [ - "serde", "stable_deref_trait", "yoke-derive", "zerofrom", @@ -5580,41 +5439,41 @@ dependencies = [ [[package]] name = "yoke-derive" -version = "0.8.0" +version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38da3c9736e16c5d3c8c597a9aaa5d1fa565d0532ae05e27c24aa62fb32c0ab6" +checksum = "b659052874eb698efe5b9e8cf382204678a0086ebf46982b79d6ca3182927e5d" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.87", "synstructure", ] [[package]] name = "zerocopy" -version = "0.8.26" +version = "0.8.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1039dd0d3c310cf05de012d8a39ff557cb0d23087fd44cad61df08fc31907a2f" +checksum = "0894878a5fa3edfd6da3f88c4805f4c8558e2b996227a3d864f47fe11e38282c" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.8.26" +version = "0.8.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ecf5b4cc5364572d7f4c329661bcc82724222973f2cab6f050a4e5c22f75181" +checksum = "88d2b8d9c68ad2b9e4340d7832716a4d21a22a1154777ad56ea55c51a9cf3831" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.87", ] [[package]] name = "zerofrom" -version = "0.1.6" +version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50cc42e0333e05660c3587f3bf9d0478688e15d870fab3346451ce7f8c9fbea5" +checksum = "69faa1f2a1ea75661980b013019ed6687ed0e83d069bc1114e2cc74c6c04c4df" dependencies = [ "zerofrom-derive", ] @@ -5627,21 +5486,21 @@ checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.87", "synstructure", ] [[package]] name = "zeroize" -version = "1.8.1" +version = "1.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde" +checksum = "b97154e67e32c85465826e8bcc1c59429aaaf107c1e4a9e53c8d8ccd5eff88d0" [[package]] name = "zerotrie" -version = "0.2.2" +version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "36f0bbd478583f79edad978b407914f61b2972f5af6fa089686016be8f9af595" +checksum = "2a59c17a5562d507e4b54960e8569ebee33bee890c70aa3fe7b97e85a9fd7851" dependencies = [ "displaydoc", "yoke", @@ -5650,9 +5509,9 @@ dependencies = [ [[package]] name = "zerovec" -version = "0.11.2" +version = "0.11.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4a05eb080e015ba39cc9e23bbe5e7fb04d5fb040350f99f34e338d5fdd294428" +checksum = "6c28719294829477f525be0186d13efa9a3c602f7ec202ca9e353d310fb9a002" dependencies = [ "yoke", "zerofrom", @@ -5661,11 +5520,11 @@ dependencies = [ [[package]] name = "zerovec-derive" -version = "0.11.1" +version = "0.11.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b96237efa0c878c64bd89c436f661be4e46b2f3eff1ebb976f7ef2321d2f58f" +checksum = "eadce39539ca5cb3985590102671f2567e659fca9666581ad3411d59207951f3" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.87", ] diff --git a/Cargo.toml b/Cargo.toml index f663e04f09..50e553ca05 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -26,6 +26,7 @@ members = [ "examples/postgres/transaction", "examples/sqlite/todos", "examples/sqlite/extension", + "examples/sqlite/serialize", ] [workspace.package] @@ -123,6 +124,7 @@ any = ["sqlx-core/any", "sqlx-mssql?/any", "sqlx-mysql?/any", "sqlx-postgres?/an mssql = ["sqlx-mssql", "sqlx-macros?/mssql"] 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 @@ -172,7 +174,7 @@ sqlx-macros = { version = "=0.9.0-alpha.1", path = "sqlx-macros" } # Driver crates sqlx-mssql = { version = "=0.9.0-alpha.1", path = "sqlx-mssql" } -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" } @@ -187,13 +189,13 @@ chrono = { version = "0.4.34", default-features = false, features = ["std", "clo ipnet = "2.3.0" ipnetwork = "0.21.1" mac_address = "1.1.5" -rust_decimal = { version = "1.26.1", default-features = false, features = ["std"] } -time = { version = "0.3.36", features = ["formatting", "parsing", "macros"] } -uuid = "1.1.2" +rust_decimal = { version = "1.36.0", default-features = false, features = ["std"] } +time = { version = "0.3.37", features = ["formatting", "parsing", "macros"] } +uuid = "1.12.1" # Common utility crates cfg-if = "1.0.0" -dotenvy = { version = "0.15.0", default-features = false } +dotenvy = { version = "0.15.7", default-features = false } thiserror = { version = "2.0.17", default-features = false, features = ["std"] } # Runtimes @@ -210,7 +212,7 @@ version = "2.0" default-features = false [workspace.dependencies.tokio] -version = "1" +version = "1.25.0" features = ["time", "net", "sync", "fs", "io-util", "rt"] default-features = false @@ -219,25 +221,25 @@ sqlx-core = { workspace = true, features = ["migrate"] } sqlx-macros = { workspace = true, optional = true } sqlx-mssql = { 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 } [dev-dependencies] -anyhow = "1.0.52" -time_ = { version = "0.3.2", package = "time" } -futures-util = { version = "0.3.19", default-features = false, features = ["alloc"] } +anyhow = "1.0.58" +time_ = { version = "0.3.37", package = "time" } +futures-util = { version = "0.3.32", default-features = false, features = ["alloc"] } env_logger = "0.11" async-std = { workspace = true, features = ["attributes"] } -tokio = { version = "1.15.0", features = ["full"] } -dotenvy = "0.15.0" +tokio = { version = "1.25.0", features = ["full"] } +dotenvy = "0.15.7" trybuild = "1.0.53" sqlx-test = { path = "./sqlx-test" } paste = "1.0.6" -serde = { version = "1.0.132", features = ["derive"] } -serde_json = "1.0.73" +serde = { version = "1.0.219", features = ["derive"] } +serde_json = "1.0.142" url = "2.2.2" -rand = "0.8.4" +rand = "0.8.5" rand_xoshiro = "0.6.0" hex = "0.4.3" tempfile = "3.10.1" diff --git a/README.md b/README.md index f1e53cdced..ff98f45350 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. @@ -331,7 +334,7 @@ use futures_util::TryStreamExt; use sqlx::Row; let mut rows = sqlx::query("SELECT * FROM users WHERE email = ?") - .bind(email) + .bind("user@example.com") .fetch(&mut conn); while let Some(row) = rows.try_next().await? { @@ -355,8 +358,8 @@ let mut stream = sqlx::query("SELECT * FROM users") struct User { name: String, id: i64 } let mut stream = sqlx::query_as::<_, User>("SELECT * FROM users WHERE email = ? OR name = ?") - .bind(user_email) - .bind(user_name) + .bind("user@example.com") + .bind("example_username") .fetch(&mut conn); ``` @@ -400,13 +403,13 @@ Differences from `query()`: queries against; the database does not have to contain any data but must be the same kind (MySQL, Postgres, etc.) and have the same schema as the database you will be connecting to at runtime. - For convenience, you can use [a `.env` file][dotenv]1 to set DATABASE_URL so that you don't have to pass it every time: + For convenience, you can use [a `.env` file][dotenvy]1 to set DATABASE_URL so that you don't have to pass it every time: ``` DATABASE_URL=mysql://localhost/my_database ``` -[dotenv]: https://github.com/dotenv-rs/dotenv#examples +[dotenvy]: https://github.com/allan2/dotenvy?tab=readme-ov-file#what-is-an-environment-file The biggest downside to `query!()` is that the output type cannot be named (due to Rust not officially supporting anonymous records). To address that, there is a `query_as!()` macro that is diff --git a/examples/mysql/todos/Cargo.toml b/examples/mysql/todos/Cargo.toml index db8c677980..b82657ed49 100644 --- a/examples/mysql/todos/Cargo.toml +++ b/examples/mysql/todos/Cargo.toml @@ -5,7 +5,7 @@ edition = "2021" workspace = "../../../" [dependencies] -anyhow = "1.0" -sqlx = { path = "../../../", features = [ "mysql", "runtime-tokio", "tls-native-tls" ] } -clap = { version = "4", features = ["derive"] } -tokio = { version = "1.20.0", features = ["rt", "macros"]} +anyhow = "1.0.58" +sqlx = { path = "../../../", features = [ "mysql", "mysql-rsa", "runtime-tokio", "tls-native-tls" ] } +clap = { version = "4.4.7", features = ["derive"] } +tokio = { version = "1.25.0", features = ["rt", "macros"]} diff --git a/examples/postgres/axum-social-with-tests/Cargo.toml b/examples/postgres/axum-social-with-tests/Cargo.toml index 05257a617c..e54b1db717 100644 --- a/examples/postgres/axum-social-with-tests/Cargo.toml +++ b/examples/postgres/axum-social-with-tests/Cargo.toml @@ -7,26 +7,27 @@ edition = "2021" [dependencies] # Primary crates -axum = { version = "0.5.13", features = ["macros"] } +axum = { version = "0.8", features = ["macros"] } sqlx = { path = "../../../", features = [ "runtime-tokio", "tls-rustls-ring", "postgres", "time", "uuid" ] } -tokio = { version = "1.20.1", features = ["rt-multi-thread", "macros"] } +tokio = { version = "1.25.0", features = ["rt-multi-thread", "macros"] } # Important secondary crates argon2 = "0.4.1" rand = "0.8.5" regex = "1.6.0" -serde = "1.0.140" +serde = "1.0.219" serde_with = { version = "2.0.0", features = ["time_0_3"] } -time = "0.3.11" -uuid = { version = "1.1.2", features = ["serde"] } -validator = { version = "0.16.0", features = ["derive"] } +time = "0.3.37" +uuid = { version = "1.12.1", features = ["serde"] } +validator = { version = "0.20.0", features = ["derive"] } # Auxilliary crates anyhow = "1.0.58" -dotenvy = "0.15.1" -thiserror = "2.0.0" -tracing = "0.1.35" +dotenvy = "0.15.7" +thiserror = "2.0.17" +tracing = "0.1.37" [dev-dependencies] -serde_json = "1.0.82" -tower = "0.4.13" +http-body-util = "0.1" +serde_json = "1.0.142" +tower = "0.5.2" diff --git a/examples/postgres/axum-social-with-tests/src/http/mod.rs b/examples/postgres/axum-social-with-tests/src/http/mod.rs index a871a93d7e..2a9297cbef 100644 --- a/examples/postgres/axum-social-with-tests/src/http/mod.rs +++ b/examples/postgres/axum-social-with-tests/src/http/mod.rs @@ -1,5 +1,5 @@ use anyhow::Context; -use axum::{Extension, Router}; +use axum::Router; use sqlx::PgPool; mod error; @@ -15,12 +15,12 @@ pub fn app(db: PgPool) -> Router { Router::new() .merge(user::router()) .merge(post::router()) - .layer(Extension(db)) + .with_state(db) } pub async fn serve(db: PgPool) -> anyhow::Result<()> { - axum::Server::bind(&"0.0.0.0:8080".parse().unwrap()) - .serve(app(db).into_make_service()) + let listener = tokio::net::TcpListener::bind("0.0.0.0:8080").await?; + axum::serve(listener, app(db)) .await .context("failed to serve API") } diff --git a/examples/postgres/axum-social-with-tests/src/http/post/comment.rs b/examples/postgres/axum-social-with-tests/src/http/post/comment.rs index 630dedaa21..d54cb473d4 100644 --- a/examples/postgres/axum-social-with-tests/src/http/post/comment.rs +++ b/examples/postgres/axum-social-with-tests/src/http/post/comment.rs @@ -1,5 +1,5 @@ -use axum::extract::Path; -use axum::{Extension, Json, Router}; +use axum::extract::{Path, State}; +use axum::{Json, Router}; use axum::routing::get; @@ -15,9 +15,9 @@ use crate::http::Result; use time::format_description::well_known::Rfc3339; use uuid::Uuid; -pub fn router() -> Router { +pub fn router() -> Router { Router::new().route( - "/v1/post/:postId/comment", + "/v1/post/{postId}/comment", get(get_post_comments).post(create_post_comment), ) } @@ -44,7 +44,7 @@ struct Comment { // #[axum::debug_handler] // very useful! async fn create_post_comment( - db: Extension, + db: State, Path(post_id): Path, Json(req): Json, ) -> Result> { @@ -76,7 +76,7 @@ async fn create_post_comment( /// Returns comments in ascending chronological order. async fn get_post_comments( - db: Extension, + db: State, Path(post_id): Path, ) -> Result>> { // Note: normally you'd want to put a `LIMIT` on this as well, diff --git a/examples/postgres/axum-social-with-tests/src/http/post/mod.rs b/examples/postgres/axum-social-with-tests/src/http/post/mod.rs index 09c2fa44bb..4d2021e0f4 100644 --- a/examples/postgres/axum-social-with-tests/src/http/post/mod.rs +++ b/examples/postgres/axum-social-with-tests/src/http/post/mod.rs @@ -1,4 +1,5 @@ -use axum::{Extension, Json, Router}; +use axum::extract::State; +use axum::{Json, Router}; use axum::routing::get; @@ -16,7 +17,7 @@ use uuid::Uuid; mod comment; -pub fn router() -> Router { +pub fn router() -> Router { Router::new() .route("/v1/post", get(get_posts).post(create_post)) .merge(comment::router()) @@ -43,10 +44,7 @@ struct Post { } // #[axum::debug_handler] // very useful! -async fn create_post( - db: Extension, - Json(req): Json, -) -> Result> { +async fn create_post(db: State, Json(req): Json) -> Result> { req.validate()?; let user_id = req.auth.verify(&*db).await?; @@ -73,7 +71,7 @@ async fn create_post( } /// Returns posts in descending chronological order. -async fn get_posts(db: Extension) -> Result>> { +async fn get_posts(db: State) -> Result>> { // Note: normally you'd want to put a `LIMIT` on this as well, // though that would also necessitate implementing pagination. let posts = sqlx::query_as!( diff --git a/examples/postgres/axum-social-with-tests/src/http/user.rs b/examples/postgres/axum-social-with-tests/src/http/user.rs index cf4db77cdc..7d8267ca3e 100644 --- a/examples/postgres/axum-social-with-tests/src/http/user.rs +++ b/examples/postgres/axum-social-with-tests/src/http/user.rs @@ -1,5 +1,6 @@ +use axum::extract::State; use axum::http::StatusCode; -use axum::{routing::post, Extension, Json, Router}; +use axum::{routing::post, Json, Router}; use rand::Rng; use regex::Regex; use std::{sync::LazyLock, time::Duration}; @@ -13,7 +14,7 @@ use crate::http::{Error, Result}; pub type UserId = Uuid; -pub fn router() -> Router { +pub fn router() -> Router { Router::new().route("/v1/user", post(create_user)) } @@ -24,7 +25,7 @@ static USERNAME_REGEX: LazyLock = LazyLock::new(|| Regex::new(r"^[0-9A-Za #[derive(Deserialize, Validate)] #[serde(rename_all = "camelCase")] pub struct UserAuth { - #[validate(length(min = 3, max = 16), regex = "USERNAME_REGEX")] + #[validate(length(min = 3, max = 16), regex(path = USERNAME_REGEX))] username: String, #[validate(length(min = 8, max = 32))] password: String, @@ -32,7 +33,7 @@ pub struct UserAuth { // WARNING: this API has none of the checks that a normal user signup flow implements, // such as email or phone verification. -async fn create_user(db: Extension, Json(req): Json) -> Result { +async fn create_user(db: State, Json(req): Json) -> Result { req.validate()?; let UserAuth { username, password } = req; diff --git a/examples/postgres/axum-social-with-tests/tests/common.rs b/examples/postgres/axum-social-with-tests/tests/common.rs index 2b7f169ae9..64da67091f 100644 --- a/examples/postgres/axum-social-with-tests/tests/common.rs +++ b/examples/postgres/axum-social-with-tests/tests/common.rs @@ -1,10 +1,11 @@ // This is imported by different tests that use different functions. #![allow(dead_code)] -use axum::body::{Body, BoxBody, HttpBody}; +use axum::body::Body; use axum::http::header::CONTENT_TYPE; use axum::http::{request, Request}; use axum::response::Response; +use http_body_util::BodyExt; use time::format_description::well_known::Rfc3339; use time::OffsetDateTime; use uuid::Uuid; @@ -27,7 +28,7 @@ impl RequestBuilderExt for request::Builder { } } -pub async fn response_json(resp: &mut Response) -> serde_json::Value { +pub async fn response_json(resp: &mut Response) -> serde_json::Value { assert_eq!( resp.headers() .get(CONTENT_TYPE) @@ -35,15 +36,11 @@ pub async fn response_json(resp: &mut Response) -> serde_json::Value { "application/json" ); - let body = resp.body_mut(); - - let mut bytes = Vec::new(); - - while let Some(res) = body.data().await { - let chunk = res.expect("error reading response body"); - - bytes.extend_from_slice(&chunk[..]); - } + let bytes = resp + .collect() + .await + .expect("error reading response body") + .to_bytes(); serde_json::from_slice(&bytes).expect("failed to read response body as json") } diff --git a/examples/postgres/chat/Cargo.toml b/examples/postgres/chat/Cargo.toml index ecacff7269..00142cfd8c 100644 --- a/examples/postgres/chat/Cargo.toml +++ b/examples/postgres/chat/Cargo.toml @@ -6,7 +6,7 @@ workspace = "../../../" [dependencies] sqlx = { path = "../../../", features = [ "postgres", "runtime-tokio", "tls-native-tls" ] } -tokio = { version = "1.20.0", features = [ "rt-multi-thread", "macros" ] } +tokio = { version = "1.25.0", features = [ "rt-multi-thread", "macros" ] } ratatui = "0.27.0" crossterm = "0.27.0" -unicode-width = "0.1" +unicode-width = "0.1.13" diff --git a/examples/postgres/files/Cargo.toml b/examples/postgres/files/Cargo.toml index 5b9ac6bd49..823c3576e6 100644 --- a/examples/postgres/files/Cargo.toml +++ b/examples/postgres/files/Cargo.toml @@ -6,7 +6,7 @@ edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -anyhow = "1.0" +anyhow = "1.0.58" sqlx = { path = "../../../", features = [ "postgres", "runtime-tokio", "tls-native-tls" ] } -tokio = { version = "1.20.0", features = ["rt", "macros"]} -dotenvy = "0.15.0" +tokio = { version = "1.25.0", features = ["rt", "macros"]} +dotenvy = "0.15.7" diff --git a/examples/postgres/json/Cargo.toml b/examples/postgres/json/Cargo.toml index edca041d68..cb9e2f8b49 100644 --- a/examples/postgres/json/Cargo.toml +++ b/examples/postgres/json/Cargo.toml @@ -5,10 +5,10 @@ edition = "2021" workspace = "../../../" [dependencies] -anyhow = "1.0" -dotenvy = "0.15.0" -serde = { version = "1", features = ["derive"] } -serde_json = "1" +anyhow = "1.0.58" +dotenvy = "0.15.7" +serde = { version = "1.0.219", features = ["derive"] } +serde_json = "1.0.142" sqlx = { path = "../../../", features = [ "runtime-tokio", "postgres", "json" ] } -clap = { version = "4", features = ["derive"] } -tokio = { version = "1.20.0", features = ["rt", "macros"]} +clap = { version = "4.4.7", features = ["derive"] } +tokio = { version = "1.25.0", features = ["rt", "macros"]} diff --git a/examples/postgres/listen/Cargo.toml b/examples/postgres/listen/Cargo.toml index 636e8c380b..e575fab3e9 100644 --- a/examples/postgres/listen/Cargo.toml +++ b/examples/postgres/listen/Cargo.toml @@ -6,5 +6,5 @@ workspace = "../../../" [dependencies] sqlx = { path = "../../../", features = [ "runtime-tokio", "postgres" ] } -futures-util = { version = "0.3.1", default-features = false } -tokio = { version = "1.20.0", features = ["rt-multi-thread", "macros", "time"]} +futures-util = { version = "0.3.32", default-features = false } +tokio = { version = "1.25.0", features = ["rt-multi-thread", "macros", "time"]} diff --git a/examples/postgres/mockable-todos/Cargo.toml b/examples/postgres/mockable-todos/Cargo.toml index 59bb800224..4860d74c0e 100644 --- a/examples/postgres/mockable-todos/Cargo.toml +++ b/examples/postgres/mockable-todos/Cargo.toml @@ -5,10 +5,10 @@ edition = "2021" workspace = "../../../" [dependencies] -anyhow = "1.0" +anyhow = "1.0.58" sqlx = { path = "../../../", features = [ "postgres", "runtime-tokio", "tls-native-tls" ] } -clap = { version = "4", features = ["derive"] } -tokio = { version = "1.20.0", features = ["rt", "macros"]} -dotenvy = "0.15.0" -async-trait = "0.1.41" +clap = { version = "4.4.7", features = ["derive"] } +tokio = { version = "1.25.0", features = ["rt", "macros"]} +dotenvy = "0.15.7" +async-trait = "0.1.43" mockall = "0.11" diff --git a/examples/postgres/multi-database/Cargo.toml b/examples/postgres/multi-database/Cargo.toml index c5e01621b8..e0a2dd5a32 100644 --- a/examples/postgres/multi-database/Cargo.toml +++ b/examples/postgres/multi-database/Cargo.toml @@ -9,7 +9,7 @@ categories.workspace = true authors.workspace = true [dependencies] -tokio = { version = "1", features = ["rt-multi-thread", "macros"] } +tokio = { version = "1.25.0", features = ["rt-multi-thread", "macros"] } color-eyre = "0.6.3" dotenvy = "0.15.7" diff --git a/examples/postgres/multi-database/accounts/Cargo.toml b/examples/postgres/multi-database/accounts/Cargo.toml index f7c04ca8b4..43fafa2e3d 100644 --- a/examples/postgres/multi-database/accounts/Cargo.toml +++ b/examples/postgres/multi-database/accounts/Cargo.toml @@ -5,18 +5,18 @@ edition = "2021" [dependencies] sqlx = { workspace = true, features = ["postgres", "time", "uuid", "macros", "sqlx-toml"] } -tokio = { version = "1", features = ["rt", "sync"] } +tokio = { version = "1.25.0", features = ["rt", "sync"] } argon2 = { version = "0.5.3", features = ["password-hash"] } password-hash = { version = "0.5", features = ["std"] } -uuid = { version = "1", features = ["serde"] } -thiserror = "1" -rand = "0.8" +uuid = { version = "1.12.1", features = ["serde"] } +thiserror = "1.0.40" +rand = "0.8.5" time = { version = "0.3.37", features = ["serde"] } -serde = { version = "1.0.218", features = ["derive"] } +serde = { version = "1.0.219", features = ["derive"] } [dev-dependencies] sqlx = { workspace = true, features = ["runtime-tokio"] } diff --git a/examples/postgres/multi-tenant/Cargo.toml b/examples/postgres/multi-tenant/Cargo.toml index a219cce2b8..3ece57ad6e 100644 --- a/examples/postgres/multi-tenant/Cargo.toml +++ b/examples/postgres/multi-tenant/Cargo.toml @@ -9,7 +9,7 @@ categories.workspace = true authors.workspace = true [dependencies] -tokio = { version = "1", features = ["rt-multi-thread", "macros"] } +tokio = { version = "1.25.0", features = ["rt-multi-thread", "macros"] } color-eyre = "0.6.3" dotenvy = "0.15.7" diff --git a/examples/postgres/multi-tenant/accounts/Cargo.toml b/examples/postgres/multi-tenant/accounts/Cargo.toml index 40c365c607..64465157c8 100644 --- a/examples/postgres/multi-tenant/accounts/Cargo.toml +++ b/examples/postgres/multi-tenant/accounts/Cargo.toml @@ -4,18 +4,18 @@ version = "0.1.0" edition = "2021" [dependencies] -tokio = { version = "1", features = ["rt", "sync"] } +tokio = { version = "1.25.0", features = ["rt", "sync"] } argon2 = { version = "0.5.3", features = ["password-hash"] } password-hash = { version = "0.5", features = ["std"] } -uuid = { version = "1", features = ["serde"] } -thiserror = "1" -rand = "0.8" +uuid = { version = "1.12.1", features = ["serde"] } +thiserror = "1.0.40" +rand = "0.8.5" time = { version = "0.3.37", features = ["serde"] } -serde = { version = "1.0.218", features = ["derive"] } +serde = { version = "1.0.219", features = ["derive"] } [dependencies.sqlx] # version = "0.9.0" diff --git a/examples/postgres/preferred-crates/Cargo.toml b/examples/postgres/preferred-crates/Cargo.toml index cb975e30df..2972a8203c 100644 --- a/examples/postgres/preferred-crates/Cargo.toml +++ b/examples/postgres/preferred-crates/Cargo.toml @@ -11,10 +11,10 @@ authors.workspace = true [dependencies] dotenvy.workspace = true -anyhow = "1" -chrono = "0.4" -serde = { version = "1", features = ["derive"] } -uuid = { version = "1", features = ["serde"] } +anyhow = "1.0.58" +chrono = "0.4.34" +serde = { version = "1.0.219", features = ["derive"] } +uuid = { version = "1.12.1", features = ["serde"] } [dependencies.tokio] workspace = true diff --git a/examples/postgres/preferred-crates/uses-rust-decimal/Cargo.toml b/examples/postgres/preferred-crates/uses-rust-decimal/Cargo.toml index 13c409ac84..09ca470caf 100644 --- a/examples/postgres/preferred-crates/uses-rust-decimal/Cargo.toml +++ b/examples/postgres/preferred-crates/uses-rust-decimal/Cargo.toml @@ -9,9 +9,9 @@ categories.workspace = true authors.workspace = true [dependencies] -chrono = "0.4" -rust_decimal = "1" -uuid = "1" +chrono = "0.4.34" +rust_decimal = "1.36.0" +uuid = "1.12.1" [dependencies.sqlx] workspace = true diff --git a/examples/postgres/preferred-crates/uses-time/Cargo.toml b/examples/postgres/preferred-crates/uses-time/Cargo.toml index 1dfb1dab7f..a0c0768034 100644 --- a/examples/postgres/preferred-crates/uses-time/Cargo.toml +++ b/examples/postgres/preferred-crates/uses-time/Cargo.toml @@ -9,9 +9,9 @@ categories.workspace = true authors.workspace = true [dependencies] -serde = "1" -time = "0.3" -uuid = "1" +serde = "1.0.219" +time = "0.3.37" +uuid = "1.12.1" [dependencies.sqlx] workspace = true diff --git a/examples/postgres/todos/Cargo.toml b/examples/postgres/todos/Cargo.toml index 0696ea57d6..30a24d09d2 100644 --- a/examples/postgres/todos/Cargo.toml +++ b/examples/postgres/todos/Cargo.toml @@ -5,8 +5,8 @@ edition = "2018" workspace = "../../../" [dependencies] -anyhow = "1.0" +anyhow = "1.0.58" sqlx = { path = "../../../", features = [ "postgres", "runtime-tokio", "tls-native-tls" ] } -clap = { version = "4", features = ["derive"] } -tokio = { version = "1.20.0", features = ["rt", "macros"]} -dotenvy = "0.15.0" +clap = { version = "4.4.7", features = ["derive"] } +tokio = { version = "1.25.0", features = ["rt", "macros"]} +dotenvy = "0.15.7" diff --git a/examples/postgres/transaction/Cargo.toml b/examples/postgres/transaction/Cargo.toml index b6bd4e7cad..6370044781 100644 --- a/examples/postgres/transaction/Cargo.toml +++ b/examples/postgres/transaction/Cargo.toml @@ -6,4 +6,4 @@ workspace = "../../../" [dependencies] sqlx = { path = "../../../", features = [ "postgres", "runtime-tokio", "tls-native-tls" ] } -tokio = { version = "1.20.0", features = ["rt-multi-thread", "macros"]} +tokio = { version = "1.25.0", features = ["rt-multi-thread", "macros"]} diff --git a/examples/sqlite/extension/Cargo.toml b/examples/sqlite/extension/Cargo.toml index fa2042e343..3ee7d2b493 100644 --- a/examples/sqlite/extension/Cargo.toml +++ b/examples/sqlite/extension/Cargo.toml @@ -10,8 +10,8 @@ authors.workspace = true [dependencies] sqlx = { path = "../../../", features = [ "sqlite", "runtime-tokio", "tls-native-tls", "sqlx-toml"] } -tokio = { version = "1.20.0", features = ["rt", "macros"]} -anyhow = "1.0" +tokio = { version = "1.25.0", features = ["rt", "macros"]} +anyhow = "1.0.58" [lints] workspace = true diff --git a/examples/sqlite/extension/download-extension.sh b/examples/sqlite/extension/download-extension.sh index ce7f23a486..83829b3ae9 100755 --- a/examples/sqlite/extension/download-extension.sh +++ b/examples/sqlite/extension/download-extension.sh @@ -6,4 +6,8 @@ # directory on the library search path, either by using the system # package manager or by compiling and installing it yourself. -mkdir /tmp/sqlite3-lib && wget -O /tmp/sqlite3-lib/ipaddr.so https://github.com/nalgeon/sqlean/releases/download/0.15.2/ipaddr.so +mkdir /tmp/sqlite3-lib && \ + wget -O /tmp/sqlean-linux-x64.zip https://github.com/nalgeon/sqlean/releases/download/0.28.0/sqlean-linux-x64.zip && \ + unzip /tmp/sqlean-linux-x64.zip -d /tmp/sqlite3-lib && \ + mv /tmp/sqlite3-lib/uuid.so /tmp/sqlite3-lib/uuid_renamed.so && \ + rm /tmp/sqlean-linux-x64.zip diff --git a/examples/sqlite/extension/migrations/20251115215857_uuid.sql b/examples/sqlite/extension/migrations/20251115215857_uuid.sql new file mode 100644 index 0000000000..c0a8673b4b --- /dev/null +++ b/examples/sqlite/extension/migrations/20251115215857_uuid.sql @@ -0,0 +1,10 @@ +create table uuids (uuid text); + +-- The `uuid4` function is provided by the +-- [uuid](https://github.com/nalgeon/sqlean/blob/main/docs/uuid.md) +-- sqlite extension, and so this migration can not run if that +-- extension is not loaded. +insert into uuids (uuid) values + (uuid4()), + (uuid4()), + (uuid4()); diff --git a/examples/sqlite/extension/sqlx.toml b/examples/sqlite/extension/sqlx.toml index 7c67dd160e..ada65b1a25 100644 --- a/examples/sqlite/extension/sqlx.toml +++ b/examples/sqlite/extension/sqlx.toml @@ -1,4 +1,4 @@ -[common.drivers.sqlite] +[drivers.sqlite] # Including the full path to the extension is somewhat unusual, # because normally an extension will be installed in a standard # directory which is part of the library search path. If that were the @@ -9,4 +9,7 @@ # * Provide the full path the the extension, as seen below. # * Add the non-standard location to the library search path, which on # Linux means adding it to the LD_LIBRARY_PATH environment variable. -unsafe-load-extensions = ["/tmp/sqlite3-lib/ipaddr"] +unsafe-load-extensions = [ + "/tmp/sqlite3-lib/ipaddr", + { path = "/tmp/sqlite3-lib/uuid_renamed", entrypoint = "sqlite3_uuid_init" }, +] diff --git a/examples/sqlite/extension/src/main.rs b/examples/sqlite/extension/src/main.rs index ee859c55b8..15a5b01ab8 100644 --- a/examples/sqlite/extension/src/main.rs +++ b/examples/sqlite/extension/src/main.rs @@ -7,24 +7,32 @@ use sqlx::{ #[tokio::main(flavor = "current_thread")] async fn main() -> anyhow::Result<()> { - let opts = SqliteConnectOptions::from_str(&std::env::var("DATABASE_URL")?)? - // The sqlx.toml file controls loading extensions for the CLI - // and for the query checking macros, *not* for the - // application while it's running. Thus, if we want the - // extension to be available during program execution, we need - // to load it. - // - // Note that while in this case the extension path is the same - // when checking the program (sqlx.toml) and when running it - // (here), this is not required. The runtime environment can - // be entirely different from the development one. - // - // The extension can be described with a full path, as seen - // here, but in many cases that will not be necessary. As long - // as the extension is installed in a directory on the library - // search path, it is sufficient to just provide the extension - // name, like "ipaddr" - .extension("/tmp/sqlite3-lib/ipaddr"); + let opts = SqliteConnectOptions::from_str(&std::env::var("DATABASE_URL")?)?; + // The sqlx.toml file controls loading extensions for the CLI + // and for the query checking macros, *not* for the + // application while it's running. Thus, if we want the + // extension to be available during program execution, we need + // to load it. + // + // Note that while in this case the extension paths are the + // same when checking the program (sqlx.toml) and when running + // it (here), this is not required. The runtime environment + // can be entirely different from the development one. + // + // The extension can be described with a full path, as seen + // here, but in many cases that will not be necessary. As long + // as the extension is installed in a directory on the library + // search path, it is sufficient to just provide the extension + // name, like "ipaddr" + let opts = unsafe { opts.extension("/tmp/sqlite3-lib/ipaddr") }; + // The entrypoint for an extension is usually inferred as + // `sqlite3_extension_init` or `sqlite3_X_init` where X is the + // lowercase, ASCII-only equivalent of the filename. For the + // extension below, this would be `sqlite3_uuidrenamed_init`. + // The entrypoint can instead be explicitly provided. + let opts = unsafe { + opts.extension_with_entrypoint("/tmp/sqlite3-lib/uuid_renamed", "sqlite3_uuid_init") + }; let db = SqlitePool::connect_with(opts).await?; @@ -41,7 +49,11 @@ async fn main() -> anyhow::Result<()> { .execute(&db) .await?; - println!("Query which requires the extension was successfully executed."); + query!("insert into uuids (uuid) values (uuid4())") + .execute(&db) + .await?; + + println!("Queries which require the extensions were successfully executed."); Ok(()) } diff --git a/examples/sqlite/serialize/Cargo.toml b/examples/sqlite/serialize/Cargo.toml new file mode 100644 index 0000000000..580d8bcef2 --- /dev/null +++ b/examples/sqlite/serialize/Cargo.toml @@ -0,0 +1,10 @@ +[package] +name = "sqlx-example-sqlite-serialize" +version = "0.1.0" +edition = "2024" +workspace = "../../../" + +[dependencies] +anyhow = "1.0.58" +sqlx = { path = "../../../", features = ["sqlite", "sqlite-deserialize", "runtime-tokio"] } +tokio = { version = "1.25.0", features = ["rt", "macros"] } diff --git a/examples/sqlite/serialize/src/main.rs b/examples/sqlite/serialize/src/main.rs new file mode 100644 index 0000000000..f3c0b709b5 --- /dev/null +++ b/examples/sqlite/serialize/src/main.rs @@ -0,0 +1,31 @@ +use sqlx::sqlite::SqliteOwnedBuf; +use sqlx::{Connection, SqliteConnection}; + +#[tokio::main(flavor = "current_thread")] +async fn main() -> anyhow::Result<()> { + let mut conn = SqliteConnection::connect("sqlite::memory:").await?; + + sqlx::raw_sql( + "create table notes(id integer primary key, body text not null); + insert into notes(body) values ('hello'), ('world');", + ) + .execute(&mut conn) + .await?; + + let snapshot: SqliteOwnedBuf = conn.serialize(None).await?; + let bytes: &[u8] = snapshot.as_ref(); + conn.close().await?; + + let owned = SqliteOwnedBuf::try_from(bytes)?; + let mut restored = SqliteConnection::connect("sqlite::memory:").await?; + restored.deserialize(None, owned, false).await?; + + let rows = sqlx::query_as::<_, (i64, String)>("select id, body from notes order by id") + .fetch_all(&mut restored) + .await?; + + assert_eq!(rows.len(), 2); + assert_eq!(rows[0].1, "hello"); + assert_eq!(rows[1].1, "world"); + Ok(()) +} diff --git a/examples/sqlite/todos/Cargo.toml b/examples/sqlite/todos/Cargo.toml index 544ca2d8df..712a653bed 100644 --- a/examples/sqlite/todos/Cargo.toml +++ b/examples/sqlite/todos/Cargo.toml @@ -5,7 +5,7 @@ edition = "2018" workspace = "../../../" [dependencies] -anyhow = "1.0" +anyhow = "1.0.58" sqlx = { path = "../../../", features = [ "sqlite", "runtime-tokio", "tls-native-tls" ] } -clap = { version = "4", features = ["derive"] } -tokio = { version = "1.20.0", features = ["rt", "macros"]} +clap = { version = "4.4.7", features = ["derive"] } +tokio = { version = "1.25.0", features = ["rt", "macros"]} diff --git a/sqlx-cli/Cargo.toml b/sqlx-cli/Cargo.toml index a18366f982..f283ba747b 100644 --- a/sqlx-cli/Cargo.toml +++ b/sqlx-cli/Cargo.toml @@ -26,20 +26,20 @@ name = "cargo-sqlx" path = "src/bin/cargo-sqlx.rs" [dependencies] -dotenvy = "0.15.0" -tokio = { version = "1.15.0", features = ["macros", "rt", "rt-multi-thread", "signal"] } -futures-util = { version = "0.3.19", features = ["alloc"] } -clap = { version = "4.3.10", features = ["derive", "env", "wrap_help"] } +dotenvy = "0.15.7" +tokio = { version = "1.25.0", features = ["macros", "rt", "rt-multi-thread", "signal"] } +futures-util = { version = "0.3.32", features = ["alloc"] } +clap = { version = "4.4.7", features = ["derive", "env", "wrap_help"] } clap_complete = { version = "4.3.1", optional = true } -chrono = { version = "0.4.19", default-features = false, features = ["clock"] } -anyhow = "1.0.52" +chrono = { version = "0.4.34", default-features = false, features = ["clock"] } +anyhow = "1.0.58" console = "0.15.0" dialoguer = { version = "0.11", default-features = false } -serde_json = "1.0.73" +serde_json = "1.0.142" glob = "0.3.0" openssl = { version = "0.10.46", optional = true } -cargo_metadata = "0.18.1" -filetime = "0.2" +cargo_metadata = "0.23.1" +filetime = "0.2.25" backoff = { version = "0.4.0", features = ["futures", "tokio"] } @@ -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-cli/src/database.rs b/sqlx-cli/src/database.rs index eaba46eed9..44d58eecca 100644 --- a/sqlx-cli/src/database.rs +++ b/sqlx-cli/src/database.rs @@ -62,7 +62,16 @@ pub async fn setup( connect_opts: &ConnectOpts, ) -> anyhow::Result<()> { create(connect_opts).await?; - migrate::run(config, migration_source, connect_opts, false, false, None).await + migrate::run( + config, + migration_source, + connect_opts, + false, + false, + None, + false, + ) + .await } async fn ask_to_continue_drop(db_url: String) -> bool { diff --git a/sqlx-cli/src/lib.rs b/sqlx-cli/src/lib.rs index 7a2e41b16f..9abd689e96 100644 --- a/sqlx-cli/src/lib.rs +++ b/sqlx-cli/src/lib.rs @@ -27,7 +27,7 @@ use futures_util::TryFutureExt; use sqlx::AnyConnection; use tokio::{select, signal}; -use crate::opt::{Command, ConnectOpts, DatabaseCommand, MigrateCommand}; +use crate::opt::{Command, ConnectOpts, DatabaseCommand, MigrateCommand, OverrideCommand}; pub mod database; pub mod metadata; @@ -99,6 +99,7 @@ async fn do_run(opt: Opt) -> anyhow::Result<()> { dry_run, *ignore_missing, target_version, + false, ) .await? } @@ -124,6 +125,30 @@ async fn do_run(opt: Opt) -> anyhow::Result<()> { ) .await? } + MigrateCommand::Override { command } => match command { + OverrideCommand::Skip { + source, + config, + mut connect_opts, + dry_run, + ignore_missing, + target_version, + } => { + let config = config.load_config().await?; + connect_opts.populate_db_url(&config)?; + + migrate::run( + &config, + &source, + &connect_opts, + dry_run, + *ignore_missing, + target_version, + true, + ) + .await? + } + }, MigrateCommand::Info { source, config, diff --git a/sqlx-cli/src/metadata.rs b/sqlx-cli/src/metadata.rs index e90d9c66c3..38afa9ba14 100644 --- a/sqlx-cli/src/metadata.rs +++ b/sqlx-cli/src/metadata.rs @@ -9,6 +9,7 @@ use std::{ use anyhow::Context; use cargo_metadata::{ Metadata as CargoMetadata, Package as MetadataPackage, PackageId as MetadataId, + PackageName as MetadataPackageName, }; /// The minimal amount of package information we care about @@ -17,13 +18,13 @@ use cargo_metadata::{ /// are used to trigger recompiles of packages within the workspace #[derive(Debug)] pub struct Package { - name: String, + name: MetadataPackageName, src_paths: Vec, } impl Package { pub fn name(&self) -> &str { - &self.name + self.name.as_str() } pub fn src_paths(&self) -> &[PathBuf] { diff --git a/sqlx-cli/src/migrate.rs b/sqlx-cli/src/migrate.rs index 926e264032..b26a9f46a3 100644 --- a/sqlx-cli/src/migrate.rs +++ b/sqlx-cli/src/migrate.rs @@ -220,6 +220,7 @@ pub async fn run( dry_run: bool, ignore_missing: bool, target_version: Option, + skip: bool, ) -> anyhow::Result<()> { let migrator = migration_source.resolve(config).await?; @@ -277,18 +278,23 @@ pub async fn run( } } None => { - let skip = + let exceeds_target = target_version.is_some_and(|target_version| migration.version > target_version); - let elapsed = if dry_run || skip { + let elapsed = if dry_run || exceeds_target { + Duration::new(0, 0) + } else if skip { + conn.skip(config.migrate.table_name(), migration).await?; Duration::new(0, 0) } else { conn.apply(config.migrate.table_name(), migration).await? }; - let text = if skip { + let text = if exceeds_target { "Skipped" } else if dry_run { "Can apply" + } else if skip { + "Skipped on request" } else { "Applied" }; diff --git a/sqlx-cli/src/opt.rs b/sqlx-cli/src/opt.rs index cb09bc2ff5..48ec0207ba 100644 --- a/sqlx-cli/src/opt.rs +++ b/sqlx-cli/src/opt.rs @@ -247,6 +247,12 @@ pub enum MigrateCommand { target_version: Option, }, + /// Override migration state, potentially dangerous operations. + Override { + #[clap(subcommand)] + command: OverrideCommand, + }, + /// Revert the latest migration with a down file. Revert { #[clap(flatten)] @@ -300,6 +306,33 @@ pub enum MigrateCommand { }, } +#[derive(clap::Subcommand, Debug)] +pub enum OverrideCommand { + /// Skip all pending migrations without running them. + Skip { + #[clap(flatten)] + source: MigrationSourceOpt, + + #[clap(flatten)] + config: ConfigOpt, + + #[clap(flatten)] + connect_opts: ConnectOpts, + + /// List all the migrations to be skipped without marking them as applied. + #[clap(long)] + dry_run: bool, + + #[clap(flatten)] + ignore_missing: IgnoreMissing, + + /// Apply migrations up to the specified version. If unspecified, apply all + /// pending migrations. If already at the target version, then no-op. + #[clap(long)] + target_version: Option, + }, +} + #[derive(Args, Debug)] pub struct AddMigrationOpts { pub description: String, diff --git a/sqlx-cli/src/prepare.rs b/sqlx-cli/src/prepare.rs index 9f3fc67da4..f3688add2a 100644 --- a/sqlx-cli/src/prepare.rs +++ b/sqlx-cli/src/prepare.rs @@ -161,7 +161,7 @@ fn run_prepare_step(ctx: &PrepareCtx, cache_dir: &Path) -> anyhow::Result<()> { let tmp_dir = ctx.metadata.target_directory().join("sqlx-tmp"); fs::create_dir_all(&tmp_dir).context(format!( "Failed to create temporary query cache directory: {:?}", - cache_dir + tmp_dir ))?; // Only delete sqlx-*.json files to avoid accidentally deleting any user data. diff --git a/sqlx-cli/tests/common/mod.rs b/sqlx-cli/tests/common/mod.rs index 66e7924859..e3bd9f6e83 100644 --- a/sqlx-cli/tests/common/mod.rs +++ b/sqlx-cli/tests/common/mod.rs @@ -13,6 +13,22 @@ pub struct TestDatabase { pub config_path: Option, } +pub enum MigrateCommand { + Run, + Revert, + Skip, +} + +impl AsRef for MigrateCommand { + fn as_ref(&self) -> &str { + match self { + MigrateCommand::Run => "run", + MigrateCommand::Revert => "revert", + MigrateCommand::Skip => "override skip", + } + } +} + impl TestDatabase { pub fn new(name: &str, migrations: &str) -> Self { // Note: only set when _building_ @@ -58,19 +74,17 @@ impl TestDatabase { format!("sqlite://{}", self.file_path.display()) } - pub fn run_migration(&self, revert: bool, version: Option, dry_run: bool) -> Assert { + pub fn run_migration( + &self, + migrate_command: MigrateCommand, + version: Option, + dry_run: bool, + ) -> Assert { let mut command = Command::cargo_bin("sqlx").unwrap(); command - .args([ - "migrate", - match revert { - true => "revert", - false => "run", - }, - "--database-url", - &self.connection_string(), - "--source", - ]) + .arg("migrate") + .args(migrate_command.as_ref().split_whitespace()) + .args(["--database-url", &self.connection_string(), "--source"]) .arg(&self.migrations_path); if let Some(config_path) = &self.config_path { diff --git a/sqlx-cli/tests/migrate.rs b/sqlx-cli/tests/migrate.rs index f33ee5eb0e..002aa4da96 100644 --- a/sqlx-cli/tests/migrate.rs +++ b/sqlx-cli/tests/migrate.rs @@ -1,5 +1,6 @@ mod common; +use common::MigrateCommand; use common::TestDatabase; #[tokio::test] @@ -14,7 +15,7 @@ async fn run_reversible_migrations() { // Without --target-version specified.k { let db = TestDatabase::new("run_reversible_latest", "migrations_reversible"); - db.run_migration(false, None, false).success(); + db.run_migration(MigrateCommand::Run, None, false).success(); assert_eq!(db.applied_migrations().await, all_migrations); } // With --target-version specified. @@ -22,17 +23,17 @@ async fn run_reversible_migrations() { let db = TestDatabase::new("run_reversible_latest_explicit", "migrations_reversible"); // Move to latest, explicitly specified. - db.run_migration(false, Some(20230501000000), false) + db.run_migration(MigrateCommand::Run, Some(20230501000000), false) .success(); assert_eq!(db.applied_migrations().await, all_migrations); // Move to latest when we're already at the latest. - db.run_migration(false, Some(20230501000000), false) + db.run_migration(MigrateCommand::Run, Some(20230501000000), false) .success(); assert_eq!(db.applied_migrations().await, all_migrations); // Upgrade to an old version. - db.run_migration(false, Some(20230301000000), false) + db.run_migration(MigrateCommand::Run, Some(20230301000000), false) .failure(); assert_eq!(db.applied_migrations().await, all_migrations); } @@ -41,26 +42,26 @@ async fn run_reversible_migrations() { let db = TestDatabase::new("run_reversible_incremental", "migrations_reversible"); // First version - db.run_migration(false, Some(20230101000000), false) + db.run_migration(MigrateCommand::Run, Some(20230101000000), false) .success(); assert_eq!(db.applied_migrations().await, vec![20230101000000]); // Dry run upgrade to latest. - db.run_migration(false, None, true).success(); + db.run_migration(MigrateCommand::Run, None, true).success(); assert_eq!(db.applied_migrations().await, vec![20230101000000]); // Dry run upgrade + 2 - db.run_migration(false, Some(20230301000000), true) + db.run_migration(MigrateCommand::Run, Some(20230301000000), true) .success(); assert_eq!(db.applied_migrations().await, vec![20230101000000]); // Upgrade to non-existent version. - db.run_migration(false, Some(20230901000000999), false) + db.run_migration(MigrateCommand::Run, Some(20230901000000999), false) .failure(); assert_eq!(db.applied_migrations().await, vec![20230101000000]); // Upgrade + 1 - db.run_migration(false, Some(20230201000000), false) + db.run_migration(MigrateCommand::Run, Some(20230201000000), false) .success(); assert_eq!( db.applied_migrations().await, @@ -68,7 +69,7 @@ async fn run_reversible_migrations() { ); // Upgrade + 2 - db.run_migration(false, Some(20230401000000), false) + db.run_migration(MigrateCommand::Run, Some(20230401000000), false) .success(); assert_eq!(db.applied_migrations().await, all_migrations[..4]); } @@ -87,65 +88,150 @@ async fn revert_migrations() { // Without --target-version { let db = TestDatabase::new("revert_incremental", "migrations_reversible"); - db.run_migration(false, None, false).success(); + db.run_migration(MigrateCommand::Run, None, false).success(); // Dry-run - db.run_migration(true, None, true).success(); + db.run_migration(MigrateCommand::Revert, None, true) + .success(); assert_eq!(db.applied_migrations().await, all_migrations); // Downgrade one - db.run_migration(true, None, false).success(); + db.run_migration(MigrateCommand::Revert, None, false) + .success(); assert_eq!(db.applied_migrations().await, all_migrations[..4]); // Downgrade one - db.run_migration(true, None, false).success(); + db.run_migration(MigrateCommand::Revert, None, false) + .success(); assert_eq!(db.applied_migrations().await, all_migrations[..3]); } // With --target-version { let db = TestDatabase::new("revert_incremental", "migrations_reversible"); - db.run_migration(false, None, false).success(); + db.run_migration(MigrateCommand::Run, None, false).success(); // Dry-run downgrade to version 3. - db.run_migration(true, Some(20230301000000), true).success(); + db.run_migration(MigrateCommand::Revert, Some(20230301000000), true) + .success(); assert_eq!(db.applied_migrations().await, all_migrations); // Downgrade to version 3. - db.run_migration(true, Some(20230301000000), false) + db.run_migration(MigrateCommand::Revert, Some(20230301000000), false) .success(); assert_eq!(db.applied_migrations().await, all_migrations[..3]); // Try downgrading to the same version. - db.run_migration(true, Some(20230301000000), false) + db.run_migration(MigrateCommand::Revert, Some(20230301000000), false) .success(); assert_eq!(db.applied_migrations().await, all_migrations[..3]); // Try downgrading to a newer version. - db.run_migration(true, Some(20230401000000), false) + db.run_migration(MigrateCommand::Revert, Some(20230401000000), false) .failure(); assert_eq!(db.applied_migrations().await, all_migrations[..3]); // Try downgrading to a non-existent version. - db.run_migration(true, Some(9999), false).failure(); + db.run_migration(MigrateCommand::Revert, Some(9999), false) + .failure(); assert_eq!(db.applied_migrations().await, all_migrations[..3]); // Ensure we can still upgrade - db.run_migration(false, Some(20230401000000), false) + db.run_migration(MigrateCommand::Run, Some(20230401000000), false) .success(); assert_eq!(db.applied_migrations().await, all_migrations[..4]); // Downgrade to zero. - db.run_migration(true, Some(0), false).success(); + db.run_migration(MigrateCommand::Revert, Some(0), false) + .success(); assert_eq!(db.applied_migrations().await, Vec::::new()); } } +#[tokio::test] +async fn skip_reversible_migrations() { + let all_migrations: Vec = vec![ + 20230101000000, + 20230201000000, + 20230301000000, + 20230401000000, + 20230501000000, + ]; + // Without --target-version specified. + { + let db = TestDatabase::new("migrate_skip_reversible_latest", "migrations_reversible"); + db.run_migration(MigrateCommand::Skip, None, false) + .success(); + assert_eq!(db.applied_migrations().await, all_migrations); + } + // With --target-version specified. + { + let db = TestDatabase::new( + "migrate_skip_reversible_latest_explicit", + "migrations_reversible", + ); + + // Move to latest, explicitly specified. + db.run_migration(MigrateCommand::Run, Some(20230501000000), false) + .success(); + assert_eq!(db.applied_migrations().await, all_migrations); + + // Skip to latest when we're already at the latest. + db.run_migration(MigrateCommand::Skip, Some(20230501000000), false) + .success(); + assert_eq!(db.applied_migrations().await, all_migrations); + + // Upgrade to an old version. + db.run_migration(MigrateCommand::Skip, Some(20230301000000), false) + .failure(); + assert_eq!(db.applied_migrations().await, all_migrations); + } + // With --target-version, incrementally upgrade. + { + let db = TestDatabase::new( + "migrate_skip_reversible_incremental", + "migrations_reversible", + ); + + // Run first version + db.run_migration(MigrateCommand::Run, Some(20230101000000), false) + .success(); + assert_eq!(db.applied_migrations().await, vec![20230101000000]); + + // Skip and dry run upgrade to latest. + db.run_migration(MigrateCommand::Skip, None, true).success(); + assert_eq!(db.applied_migrations().await, vec![20230101000000]); + + // Skip and dry run upgrade + 2 + db.run_migration(MigrateCommand::Skip, Some(20230301000000), true) + .success(); + assert_eq!(db.applied_migrations().await, vec![20230101000000]); + + // Skip to to non-existent version. + db.run_migration(MigrateCommand::Skip, Some(20230901000000999), false) + .failure(); + assert_eq!(db.applied_migrations().await, vec![20230101000000]); + + // Upgrade + 1 + db.run_migration(MigrateCommand::Run, Some(20230201000000), false) + .success(); + assert_eq!( + db.applied_migrations().await, + vec![20230101000000, 20230201000000] + ); + + // Skip + 2 + db.run_migration(MigrateCommand::Skip, Some(20230401000000), false) + .success(); + assert_eq!(db.applied_migrations().await, all_migrations[..4]); + } +} + #[tokio::test] async fn ignored_chars() { let mut db = TestDatabase::new("ignored-chars", "ignored-chars/LF"); db.config_path = Some("tests/ignored-chars/sqlx.toml".into()); - db.run_migration(false, None, false).success(); + db.run_migration(MigrateCommand::Run, None, false).success(); db.set_migrations("ignored-chars/CRLF"); @@ -155,13 +241,19 @@ async fn ignored_chars() { db.migrate_info().success().stdout(expected_info); // Running migration should be a no-op - db.run_migration(false, None, false).success().stdout(""); + db.run_migration(MigrateCommand::Run, None, false) + .success() + .stdout(""); db.set_migrations("ignored-chars/BOM"); db.migrate_info().success().stdout(expected_info); - db.run_migration(false, None, false).success().stdout(""); + db.run_migration(MigrateCommand::Run, None, false) + .success() + .stdout(""); db.set_migrations("ignored-chars/oops-all-tabs"); db.migrate_info().success().stdout(expected_info); - db.run_migration(false, None, false).success().stdout(""); + db.run_migration(MigrateCommand::Run, None, false) + .success() + .stdout(""); } diff --git a/sqlx-core/Cargo.toml b/sqlx-core/Cargo.toml index 9ccb441e45..c935f5dcc2 100644 --- a/sqlx-core/Cargo.toml +++ b/sqlx-core/Cargo.toml @@ -76,28 +76,28 @@ async-io = { version = "2.4.1", optional = true } async-task = { version = "4.7.1", optional = true } base64 = { version = "0.22.0", default-features = false, features = ["std"] } -bytes = "1.1.0" +bytes = "1.2.0" cfg-if = { workspace = true } crc = { version = "3", optional = true } crossbeam-queue = "0.3.2" either = "1.6.1" -futures-core = { version = "0.3.19", default-features = false } -futures-io = "0.3.24" +futures-core = { version = "0.3.32", default-features = false } +futures-io = "0.3.32" futures-intrusive = "0.5.0" -futures-util = { version = "0.3.19", default-features = false, features = ["alloc", "sink", "io"] } +futures-util = { version = "0.3.32", default-features = false, features = ["alloc", "sink", "io"] } log = { version = "0.4.18", default-features = false } -memchr = { version = "2.4.1", default-features = false } -percent-encoding = "2.1.0" -serde = { version = "1.0.132", features = ["derive", "rc"], optional = true } -serde_json = { version = "1.0.73", features = ["raw_value"], optional = true } +memchr = { version = "2.5.0", default-features = false } +percent-encoding = "2.3.0" +serde = { version = "1.0.219", features = ["derive", "rc"], optional = true } +serde_json = { version = "1.0.142", features = ["raw_value"], optional = true } toml = { version = "0.8.16", optional = true } sha2 = { version = "0.10.0", default-features = false, optional = true } #sqlformat = "0.2.0" tokio-stream = { version = "0.1.8", features = ["fs"], optional = true } tracing = { version = "0.1.37", features = ["log"] } -smallvec = "1.7.0" +smallvec = "1.13.1" url = { version = "2.2.2" } -bstr = { version = "1.0", default-features = false, features = ["std"], optional = true } +bstr = { version = "1.0.1", default-features = false, features = ["std"], optional = true } hashlink = "0.11.0" indexmap = "2.0" event-listener = "5.2.0" @@ -106,7 +106,7 @@ hashbrown = "0.16.0" thiserror.workspace = true [dev-dependencies] -tokio = { version = "1", features = ["rt"] } +tokio = { version = "1.25.0", features = ["rt"] } [dev-dependencies.sqlx] # FIXME: https://github.com/rust-lang/cargo/issues/15622 diff --git a/sqlx-core/src/any/migrate.rs b/sqlx-core/src/any/migrate.rs index 7a894c68bc..deca501cb7 100644 --- a/sqlx-core/src/any/migrate.rs +++ b/sqlx-core/src/any/migrate.rs @@ -99,4 +99,12 @@ impl Migrate for AnyConnection { ) -> BoxFuture<'e, Result> { Box::pin(async { self.get_migrate()?.revert(table_name, migration).await }) } + + fn skip<'e>( + &'e mut self, + table_name: &'e str, + migration: &'e Migration, + ) -> BoxFuture<'e, Result<(), MigrateError>> { + Box::pin(async { self.get_migrate()?.skip(table_name, migration).await }) + } } diff --git a/sqlx-core/src/column.rs b/sqlx-core/src/column.rs index 132e7b0346..e9b6aec813 100644 --- a/sqlx-core/src/column.rs +++ b/sqlx-core/src/column.rs @@ -84,6 +84,11 @@ impl ColumnOrigin { /// This trait is implemented for strings which are used to look up a column by name, and for /// `usize` which is used as a positional index into the row. /// +/// *Caution*: The column index may differ between a [`Statement`] and a [`Row`] returned by the +/// statement. This can happen with some databases if, for example, the schema changes between +/// prepare and execute or if the database does not provide column information when the statement +/// is prepared. +/// /// [`Row`]: crate::row::Row /// [`Statement`]: crate::statement::Statement /// [`get`]: crate::row::Row::get diff --git a/sqlx-core/src/config/drivers.rs b/sqlx-core/src/config/drivers.rs index 5019f1f9f6..c3416541cd 100644 --- a/sqlx-core/src/config/drivers.rs +++ b/sqlx-core/src/config/drivers.rs @@ -102,7 +102,19 @@ pub struct SqliteConfig { /// [common.drivers.sqlite] /// unsafe-load-extensions = ["uuid", "vsv"] /// ``` - pub unsafe_load_extensions: Vec, + pub unsafe_load_extensions: Vec, +} + +/// Extension for the SQLite database driver. +#[derive(Debug, PartialEq)] +#[cfg_attr( + feature = "sqlx-toml", + derive(serde::Deserialize), + serde(untagged, deny_unknown_fields) +)] +pub enum SqliteExtension { + Path(String), + PathWithEntrypoint { path: String, entrypoint: String }, } /// Configuration for external database drivers. diff --git a/sqlx-core/src/config/reference.toml b/sqlx-core/src/config/reference.toml index 00f0af9285..618ac23c91 100644 --- a/sqlx-core/src/config/reference.toml +++ b/sqlx-core/src/config/reference.toml @@ -44,7 +44,10 @@ database-url-var = "FOO_DATABASE_URL" # It is not possible to provide a truly safe version of this API. # # Use this field with care, and only load extensions that you trust. -unsafe-load-extensions = ["uuid", "vsv"] +unsafe-load-extensions = [ + "uuid", + { path = "vsv_renamed", entrypoint = "sqlite3_vsv_init" }, +] # Configure external drivers in macros and sqlx-cli. # @@ -90,8 +93,8 @@ numeric = "rust_decimal" # or not. They only override the inner type used. [macros.type-overrides] # Override a built-in type (map all `UUID` columns to `crate::types::MyUuid`) -# Note: currently, the case of the type name MUST match. -# Built-in types are spelled in all-uppercase to match SQL convention. +# Note: currently, the case of the type name MUST match. +# Built-in types are spelled in all-uppercase to match SQL convention. 'UUID' = "crate::types::MyUuid" # Support an external or custom wrapper type (e.g. from the `isn` Postgres extension) diff --git a/sqlx-core/src/config/tests.rs b/sqlx-core/src/config/tests.rs index 91e877afb6..d01b859e46 100644 --- a/sqlx-core/src/config/tests.rs +++ b/sqlx-core/src/config/tests.rs @@ -18,7 +18,16 @@ fn assert_common_config(config: &config::common::Config) { } fn assert_drivers_config(config: &config::drivers::Config) { - assert_eq!(config.sqlite.unsafe_load_extensions, ["uuid", "vsv"]); + assert_eq!( + config.sqlite.unsafe_load_extensions, + vec![ + config::drivers::SqliteExtension::Path("uuid".to_string()), + config::drivers::SqliteExtension::PathWithEntrypoint { + path: "vsv_renamed".to_string(), + entrypoint: "sqlite3_vsv_init".to_string() + } + ] + ); #[derive(Debug, Eq, PartialEq, serde::Deserialize)] #[serde(rename_all = "kebab-case")] diff --git a/sqlx-core/src/migrate/error.rs b/sqlx-core/src/migrate/error.rs index a04243963a..3c08f57301 100644 --- a/sqlx-core/src/migrate/error.rs +++ b/sqlx-core/src/migrate/error.rs @@ -42,4 +42,7 @@ pub enum MigrateError { #[error("database driver does not support creation of schemas at migrate time: {0}")] CreateSchemasNotSupported(String), + + #[error("database driver does not support skipping migrations")] + SkipNotSupported(), } diff --git a/sqlx-core/src/migrate/migrate.rs b/sqlx-core/src/migrate/migrate.rs index a3c365b807..de4bbc5f9d 100644 --- a/sqlx-core/src/migrate/migrate.rs +++ b/sqlx-core/src/migrate/migrate.rs @@ -78,4 +78,13 @@ pub trait Migrate { table_name: &'e str, migration: &'e Migration, ) -> BoxFuture<'e, Result>; + + // insert new row to [_migrations] table without running SQL from migration + fn skip<'e>( + &'e mut self, + _table_name: &'e str, + _migration: &'e Migration, + ) -> BoxFuture<'e, Result<(), MigrateError>> { + Box::pin(async move { Err(MigrateError::SkipNotSupported()) }) + } } diff --git a/sqlx-core/src/migrate/migration.rs b/sqlx-core/src/migrate/migration.rs index 79721d244d..a434ac9684 100644 --- a/sqlx-core/src/migrate/migration.rs +++ b/sqlx-core/src/migrate/migration.rs @@ -1,5 +1,6 @@ use sha2::{Digest, Sha384}; use std::borrow::Cow; +use std::cmp::Ordering; use crate::sql_str::SqlStr; @@ -15,6 +16,28 @@ pub struct Migration { pub no_tx: bool, } +impl PartialEq for Migration { + fn eq(&self, other: &Self) -> bool { + self.version == other.version && self.migration_type == other.migration_type + } +} + +impl Eq for Migration {} + +impl PartialOrd for Migration { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + } +} + +impl Ord for Migration { + fn cmp(&self, other: &Self) -> Ordering { + self.version + .cmp(&other.version) + .then_with(|| self.migration_type.cmp(&other.migration_type)) + } +} + impl Migration { pub fn new( version: i64, diff --git a/sqlx-core/src/migrate/migration_type.rs b/sqlx-core/src/migrate/migration_type.rs index 350ddb3f27..107cdd246c 100644 --- a/sqlx-core/src/migrate/migration_type.rs +++ b/sqlx-core/src/migrate/migration_type.rs @@ -1,7 +1,7 @@ use super::Migrator; /// Migration Type represents the type of migration -#[derive(Debug, Copy, Clone, PartialEq)] +#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord)] pub enum MigrationType { /// Simple migration are single file migrations with no up / down queries Simple, diff --git a/sqlx-core/src/migrate/migrator.rs b/sqlx-core/src/migrate/migrator.rs index 53295c92d0..c64b0f211b 100644 --- a/sqlx-core/src/migrate/migrator.rs +++ b/sqlx-core/src/migrate/migrator.rs @@ -87,8 +87,7 @@ impl Migrator { /// let m = Migrator::with_migrations(migrations); /// ``` pub fn with_migrations(mut migrations: Vec) -> Self { - // Ensure that we are sorted by version in ascending order. - migrations.sort_by_key(|m| m.version); + migrations.sort(); Self { migrations: Cow::Owned(migrations), ..Self::DEFAULT @@ -180,7 +179,7 @@ impl Migrator { ::Target: Migrate, { let mut conn = migrator.acquire().await?; - self.run_direct(None, &mut *conn).await + self.run_direct(None, &mut *conn, false).await } pub async fn run_to<'a, A>(&self, target: i64, migrator: A) -> Result<(), MigrateError> @@ -189,12 +188,48 @@ impl Migrator { ::Target: Migrate, { let mut conn = migrator.acquire().await?; - self.run_direct(Some(target), &mut *conn).await + self.run_direct(Some(target), &mut *conn, false).await + } + + /// Skip any pending migrations until a specific version against the database; + /// Additionally validate previously applied migrations against the current migration + /// source to detect accidental changes in previously-applied migrations. + /// + /// Skipping entails not executing the SQL of the migrations, but marking them as + /// applied in the [_migrations] table. + /// + /// # Examples + /// + /// ```rust,no_run + /// # use sqlx::migrate::MigrateError; + /// # fn main() -> Result<(), MigrateError> { + /// # 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 = SqlitePoolOptions::new().connect("sqlite::memory:").await?; + /// m.skip(&pool, Some(17)).await + /// # }) + /// # } + /// ``` + pub async fn skip<'a, A>(&self, migrator: A, target: Option) -> Result<(), MigrateError> + where + A: Acquire<'a>, + ::Target: Migrate, + { + let mut conn = migrator.acquire().await?; + self.run_direct(target, &mut *conn, true).await } // Getting around the annoying "implementation of `Acquire` is not general enough" error #[doc(hidden)] - pub async fn run_direct(&self, target: Option, conn: &mut C) -> Result<(), MigrateError> + pub async fn run_direct( + &self, + target: Option, + conn: &mut C, + skip: bool, + ) -> Result<(), MigrateError> where C: Migrate, { @@ -241,7 +276,11 @@ impl Migrator { } } None => { - conn.apply(&self.table_name, migration).await?; + if skip { + conn.skip(&self.table_name, migration).await?; + } else { + conn.apply(&self.table_name, migration).await?; + } } } } diff --git a/sqlx-core/src/migrate/source.rs b/sqlx-core/src/migrate/source.rs index 4648e53f1e..10fc7c7b8e 100644 --- a/sqlx-core/src/migrate/source.rs +++ b/sqlx-core/src/migrate/source.rs @@ -248,8 +248,7 @@ pub fn resolve_blocking_with_config( )); } - // Ensure that we are sorted by version in ascending order. - migrations.sort_by_key(|(m, _)| m.version); + migrations.sort(); Ok(migrations) } diff --git a/sqlx-core/src/net/tls/util.rs b/sqlx-core/src/net/tls/util.rs index 42e11f31be..99aa6384c6 100644 --- a/sqlx-core/src/net/tls/util.rs +++ b/sqlx-core/src/net/tls/util.rs @@ -2,7 +2,7 @@ use crate::net::Socket; use std::future; use std::io::{self, Read, Write}; -use std::task::{ready, Context, Poll}; +use std::task::{Context, Poll}; pub struct StdSocket { pub socket: S, @@ -19,20 +19,35 @@ impl StdSocket { } } + /// Returns `Ready` if a previously blocked read _or_ write may now proceed. + /// + /// If both a read and a write were attempted, to avoid deadlocks this returns `Ready` + /// when _either_ direction is ready, not necessarily both. pub fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll> { - if self.wants_write { - ready!(self.socket.poll_write_ready(cx))?; + // Return `Ready` without waiting if the caller hasn't tried to do I/O in either direction. + let mut ready = !(self.wants_read || self.wants_write); + + if self.wants_write && self.socket.poll_write_ready(cx)?.is_ready() { self.wants_write = false; + ready |= true; } - if self.wants_read { - ready!(self.socket.poll_read_ready(cx))?; + if self.wants_read && self.socket.poll_read_ready(cx)?.is_ready() { self.wants_read = false; + ready |= true; } - Poll::Ready(Ok(())) + if ready { + Poll::Ready(Ok(())) + } else { + Poll::Pending + } } + /// Returns successfully if a previously blocked read _or_ write may now proceed. + /// + /// If both a read and a write were attempted, to avoid deadlocks this returns when _either_ + /// direction is ready, not necessarily both. pub async fn ready(&mut self) -> io::Result<()> { future::poll_fn(|cx| self.poll_ready(cx)).await } diff --git a/sqlx-core/src/testing/mod.rs b/sqlx-core/src/testing/mod.rs index 17022b4652..379fac690e 100644 --- a/sqlx-core/src/testing/mod.rs +++ b/sqlx-core/src/testing/mod.rs @@ -257,7 +257,7 @@ async fn setup_test_db( if let Some(migrator) = args.migrator { migrator - .run_direct(None, &mut conn) + .run_direct(None, &mut conn, false) .await .expect("failed to apply migrations"); } diff --git a/sqlx-macros-core/Cargo.toml b/sqlx-macros-core/Cargo.toml index bafaa3bf79..75502484a8 100644 --- a/sqlx-macros-core/Cargo.toml +++ b/sqlx-macros-core/Cargo.toml @@ -34,6 +34,7 @@ sqlx-toml = ["sqlx-core/sqlx-toml", "sqlx-sqlite?/sqlx-toml"] # database mssql = ["sqlx-mssql"] mysql = ["sqlx-mysql"] +mysql-rsa = ["mysql", "sqlx-mysql/rsa"] postgres = ["sqlx-postgres"] sqlite = ["_sqlite", "sqlx-sqlite/bundled"] sqlite-unbundled = ["_sqlite", "sqlx-sqlite/unbundled"] @@ -57,7 +58,7 @@ uuid = ["sqlx-core/uuid", "sqlx-mssql?/uuid", "sqlx-mysql?/uuid", "sqlx-postgres [dependencies] sqlx-core = { workspace = true, features = ["offline"] } sqlx-mssql = { workspace = true, features = ["offline", "migrate"], optional = true } -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 } @@ -73,12 +74,12 @@ thiserror = { workspace = true, optional = true } hex = { version = "0.4.3" } heck = { version = "0.5" } either = "1.6.1" -proc-macro2 = { version = "1.0.79", default-features = false } -serde = { version = "1.0.132", features = ["derive"] } -serde_json = { version = "1.0.73" } +proc-macro2 = { version = "1.0.83", default-features = false } +serde = { version = "1.0.219", features = ["derive"] } +serde_json = { version = "1.0.142" } sha2 = { version = "0.10.0" } -syn = { version = "2.0.52", default-features = false, features = ["full", "derive", "parsing", "printing", "clone-impls"] } -quote = { version = "1.0.26", default-features = false } +syn = { version = "2.0.87", default-features = false, features = ["full", "derive", "parsing", "printing", "clone-impls"] } +quote = { version = "1.0.35", default-features = false } url = { version = "2.2.2" } [lints.rust.unexpected_cfgs] diff --git a/sqlx-macros-core/clippy.toml b/sqlx-macros-core/clippy.toml index f303803661..0c0f4fe996 100644 --- a/sqlx-macros-core/clippy.toml +++ b/sqlx-macros-core/clippy.toml @@ -1,3 +1,3 @@ [[disallowed-methods]] path = "std::env::var" -reason = "use `crate::env()` instead, which optionally calls `proc_macro::tracked_env::var()`" +reason = "use `crate::env()` instead, which optionally calls `proc_macro::tracked::env_var()`" diff --git a/sqlx-macros-core/src/lib.rs b/sqlx-macros-core/src/lib.rs index 55a6f4be25..61e0fc0729 100644 --- a/sqlx-macros-core/src/lib.rs +++ b/sqlx-macros-core/src/lib.rs @@ -16,9 +16,12 @@ #![cfg_attr( any(sqlx_macros_unstable, procmacro2_semver_exempt), - feature(track_path) + feature(proc_macro_tracked_path, proc_macro_tracked_env) )] +#[cfg(any(sqlx_macros_unstable, procmacro2_semver_exempt))] +extern crate proc_macro; + use cfg_if::cfg_if; use std::path::PathBuf; @@ -98,7 +101,7 @@ pub fn env_opt(var: &str) -> Result> { use std::env::VarError; #[cfg(any(sqlx_macros_unstable, procmacro2_semver_exempt))] - let res: Result = proc_macro::tracked_env::var(var); + let res: Result = proc_macro::tracked::env_var(var); #[cfg(not(any(sqlx_macros_unstable, procmacro2_semver_exempt)))] let res: Result = std::env::var(var); diff --git a/sqlx-macros-core/src/migrate.rs b/sqlx-macros-core/src/migrate.rs index b855703c22..6ef1582093 100644 --- a/sqlx-macros-core/src/migrate.rs +++ b/sqlx-macros-core/src/migrate.rs @@ -1,6 +1,3 @@ -#[cfg(any(sqlx_macros_unstable, procmacro2_semver_exempt))] -extern crate proc_macro; - use std::path::{Path, PathBuf}; use proc_macro2::{Span, TokenStream}; @@ -132,7 +129,7 @@ pub fn expand_with_path(config: &Config, path: &Path) -> crate::Result( + &'e mut self, + table_name: &'e str, + migration: &'e Migration, + ) -> BoxFuture<'e, Result<(), MigrateError>> { + Box::pin(async move { + let ident = escape_table_name(table_name); + // language=TSQL + let _ = query(AssertSqlSafe(format!( + r#" + INSERT INTO {ident} ( version, description, success, checksum, execution_time ) + VALUES ( @p1, @p2, 1, @p3, -1 ) + "# + ))) + .bind(migration.version) + .bind(&*migration.description) + .bind(&*migration.checksum) + .execute(self) + .await?; + + Ok(()) + }) + } } async fn execute_migration( diff --git a/sqlx-mysql/Cargo.toml b/sqlx-mysql/Cargo.toml index c7afc236c6..519cf735e8 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"] } +futures-core = { version = "0.3.32", default-features = false } +futures-util = { version = "0.3.32", 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" +digest = { version = "0.10.7", default-features = false, features = ["std"] } +rand = { version = "0.8.5", 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,26 +47,19 @@ 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 } +bitflags = { version = "2.4", default-features = false } byteorder = { version = "1.4.3", default-features = false, features = ["std"] } -bytes = "1.1.0" +bytes = "1.2.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" +percent-encoding = "2.3.0" tracing = { version = "0.1.37", features = ["log"] } dotenvy.workspace = true thiserror.workspace = true -serde = { version = "1.0.144", optional = true } +serde = { version = "1.0.219", optional = true } [dev-dependencies] # FIXME: https://github.com/rust-lang/cargo/issues/15622 diff --git a/sqlx-mysql/src/any.rs b/sqlx-mysql/src/any.rs index b0950e0b41..57c895826f 100644 --- a/sqlx-mysql/src/any.rs +++ b/sqlx-mysql/src/any.rs @@ -16,6 +16,7 @@ use sqlx_core::database::Database; use sqlx_core::executor::Executor; use sqlx_core::sql_str::SqlStr; use sqlx_core::transaction::TransactionManager; +use sqlx_core::types::Type; use std::{future, pin::pin}; sqlx_core::declare_driver_with_optional_migrate!(DRIVER = MySql); @@ -164,13 +165,9 @@ impl<'a> TryFrom<&'a MySqlTypeInfo> for AnyTypeInfo { 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 - } + // Checks for any applicable type and compatible collations + _ if >::compatible(type_info) => AnyTypeInfoKind::Text, + _ if <[u8] as Type>::compatible(type_info) => AnyTypeInfoKind::Blob, _ => { return Err(sqlx_core::Error::AnyDriverError( format!("Any driver does not support MySql type {type_info:?}").into(), diff --git a/sqlx-mysql/src/connection/auth.rs b/sqlx-mysql/src/connection/auth.rs index 613f8e702f..cd6d9324c3 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; @@ -46,10 +44,12 @@ impl AuthPlugin { match self { AuthPlugin::CachingSha2Password if packet[0] == 0x01 => { match packet[1] { - // AUTH_OK - 0x03 => Ok(true), + // fast_auth_success — the server still sends a trailing + // OK_Packet, so yield back to the handshake loop and let + // it consume the OK on the next iteration. + 0x03 => Ok(false), - // AUTH_CONTINUE + // perform_full_authentication 0x04 => { let payload = encrypt_rsa(stream, 0x02, password, nonce).await?; @@ -60,7 +60,7 @@ impl AuthPlugin { } v => { - Err(err_protocol!("unexpected result from fast authentication 0x{:x} when expecting 0x03 (AUTH_OK) or 0x04 (AUTH_CONTINUE)", v)) + Err(err_protocol!("unexpected result from fast authentication 0x{:x} when expecting 0x03 (fast_auth_success) or 0x04 (perform_full_authentication)", v)) } } } @@ -106,8 +106,9 @@ fn scramble_sha256( password: &str, nonce: &Chain, ) -> GenericArray::OutputSize> { - // XOR(SHA256(password), SHA256(seed, SHA256(SHA256(password)))) - // https://mariadb.com/kb/en/caching_sha2_password-authentication-plugin/#sha-2-encrypted-password + // XOR(SHA256(password), SHA256(SHA256(SHA256(password)), seed)) + // Order matches the server-side verification in MySQL's sha2_password + // (generate_sha2_scramble): stage2 digest first, then the nonce. let mut ctx = Sha256::new(); ctx.update(password); @@ -118,9 +119,9 @@ fn scramble_sha256( let pw_hash_hash = ctx.finalize_reset(); + ctx.update(pw_hash_hash); ctx.update(nonce.first_ref()); ctx.update(nonce.last_ref()); - ctx.update(pw_hash_hash); let pw_seed_hash_hash = ctx.finalize(); @@ -161,10 +162,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 +183,72 @@ 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}; + + use super::Error; + + 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; - // This takes advantage of the knowledge that we know - // we are receiving a PKCS#8 RSA Public Key at all - // times from MySQL + 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(), + )) + } +} - RsaPublicKey::from_public_key_pem(pem).map_err(Error::protocol) +#[cfg(test)] +mod tests { + use super::*; + use bytes::Buf; + use sha2::{Digest, Sha256}; + + // Regression test for https://github.com/launchbadge/sqlx/issues/4244: + // caching_sha2_password fast-auth requires the client scramble to be + // invertible by the server as XOR(scramble, SHA256(stage2 || nonce)) == stage1, + // where stage1 = SHA256(password) and stage2 = SHA256(stage1). + #[test] + fn scramble_sha256_is_invertible_by_server() { + let password = "my_pwd"; + let nonce_a = Bytes::from_static(b"0123456789"); + let nonce_b = Bytes::from_static(&[0xAB; 10]); + let nonce = nonce_a.clone().chain(nonce_b.clone()); + + let mut scramble = scramble_sha256(password, &nonce); + + let stage1 = Sha256::digest(password.as_bytes()); + let stage2 = Sha256::digest(stage1); + + let mut h = Sha256::new(); + h.update(stage2); + h.update(&nonce_a); + h.update(&nonce_b); + let xor_pad = h.finalize(); + + xor_eq(&mut scramble, &xor_pad); + assert_eq!(&scramble[..], &stage1[..]); + } } diff --git a/sqlx-mysql/src/connection/executor.rs b/sqlx-mysql/src/connection/executor.rs index e331acf013..ee59d03d0a 100644 --- a/sqlx-mysql/src/connection/executor.rs +++ b/sqlx-mysql/src/connection/executor.rs @@ -120,7 +120,7 @@ impl MySqlConnection { // to re-use this memory freely between result sets let mut columns = Arc::new(Vec::new()); - let (mut column_names, format, mut needs_metadata) = if let Some(arguments) = arguments { + let format = if let Some(arguments) = arguments { if persistent && self.inner.cache_statement.is_enabled() { let (id, metadata) = self .get_or_prepare_statement(sql) @@ -144,7 +144,7 @@ impl MySqlConnection { }) .await?; - (metadata.column_names, MySqlValueFormat::Binary, false) + MySqlValueFormat::Binary } else { let (id, metadata) = self .prepare_statement(sql) @@ -170,13 +170,13 @@ impl MySqlConnection { self.inner.stream.send_packet(StmtClose { statement: id }).await?; - (metadata.column_names, MySqlValueFormat::Binary, false) + MySqlValueFormat::Binary } } else { // https://dev.mysql.com/doc/internals/en/com-query.html self.inner.stream.send_packet(Query(sql)).await?; - (Arc::default(), MySqlValueFormat::Text, true) + MySqlValueFormat::Text }; loop { @@ -216,15 +216,9 @@ impl MySqlConnection { let num_columns = usize::try_from(num_columns) .map_err(|_| err_protocol!("column count overflows usize: {num_columns}"))?; - if needs_metadata { - column_names = Arc::new(recv_result_metadata(&mut self.inner.stream, num_columns, Arc::make_mut(&mut columns)).await?); - } else { - // next time we hit here, it'll be a new result set and we'll need the - // full metadata - needs_metadata = true; - - recv_result_columns(&mut self.inner.stream, num_columns, Arc::make_mut(&mut columns)).await?; - } + // Always reload column names, even for prepared statements (the schema + // may change between PREPARE and EXECUTE). + let column_names = Arc::new(recv_result_metadata(&mut self.inner.stream, num_columns, Arc::make_mut(&mut columns)).await?); // finally, there will be none or many result-rows loop { @@ -395,25 +389,6 @@ impl<'c> Executor<'c> for &'c mut MySqlConnection { } } -async fn recv_result_columns( - stream: &mut MySqlStream, - num_columns: usize, - columns: &mut Vec, -) -> Result<(), Error> { - columns.clear(); - columns.reserve(num_columns); - - for ordinal in 0..num_columns { - columns.push(recv_next_result_column(&stream.recv().await?, ordinal)?); - } - - if num_columns > 0 { - stream.maybe_recv_eof().await?; - } - - Ok(()) -} - fn recv_next_result_column(def: &ColumnDefinition, ordinal: usize) -> Result { // if the alias is empty, use the alias // only then use the name diff --git a/sqlx-mysql/src/migrate.rs b/sqlx-mysql/src/migrate.rs index 7424dfe27f..54cf1cf660 100644 --- a/sqlx-mysql/src/migrate.rs +++ b/sqlx-mysql/src/migrate.rs @@ -308,6 +308,29 @@ CREATE TABLE IF NOT EXISTS {table_name} ( Ok(elapsed) }) } + + fn skip<'e>( + &'e mut self, + table_name: &'e str, + migration: &'e Migration, + ) -> BoxFuture<'e, Result<(), MigrateError>> { + Box::pin(async move { + // language=MySQL + let _ = query(AssertSqlSafe(format!( + r#" + INSERT INTO {table_name} ( version, description, success, checksum, execution_time ) + VALUES ( ?, ?, TRUE, ?, -1 ) + "# + ))) + .bind(migration.version) + .bind(&*migration.description) + .bind(&*migration.checksum) + .execute(self) + .await?; + + Ok(()) + }) + } } async fn current_database(conn: &mut MySqlConnection) -> Result { diff --git a/sqlx-postgres/Cargo.toml b/sqlx-postgres/Cargo.toml index 2943049f0b..df45ab33ff 100644 --- a/sqlx-postgres/Cargo.toml +++ b/sqlx-postgres/Cargo.toml @@ -28,16 +28,16 @@ uuid = ["dep:uuid", "sqlx-core/uuid"] [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-util = { version = "0.3.19", default-features = false, features = ["alloc", "sink", "io"] } +futures-channel = { version = "0.3.32", default-features = false, features = ["sink", "alloc", "std"] } +futures-core = { version = "0.3.32", default-features = false } +futures-util = { version = "0.3.32", 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, features = ["reset"]} md-5 = { version = "0.10.0", default-features = false } -rand = { version = "0.8.4", default-features = false, features = ["std", "std_rng"] } +rand = { version = "0.8.5", default-features = false, features = ["std", "std_rng"] } sha2 = { version = "0.10.0", default-features = false } # Type Integrations (versions inherited from `[workspace.dependencies]`) @@ -54,14 +54,14 @@ 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 } +bitflags = { version = "2.4", default-features = false } byteorder = { version = "1.4.3", default-features = false, features = ["std"] } hex = "0.4.3" -itoa = "1.0.1" +itoa = "1.0.5" log = "0.4.18" -memchr = { version = "2.4.1", default-features = false } +memchr = { version = "2.5.0", default-features = false } num-bigint = { version = "0.4.3", optional = true } -smallvec = { version = "1.7.0" } +smallvec = { version = "1.13.1" } stringprep = "0.1.2" tracing = { version = "0.1.37", features = ["log"] } whoami = { version = "2.0.2", default-features = false } @@ -69,8 +69,8 @@ whoami = { version = "2.0.2", default-features = false } dotenvy.workspace = true thiserror.workspace = true -serde = { version = "1.0.144", optional = true, features = ["derive"] } -serde_json = { version = "1.0.85", optional = true, features = ["raw_value"] } +serde = { version = "1.0.219", optional = true, features = ["derive"] } +serde_json = { version = "1.0.142", optional = true, features = ["raw_value"] } [dependencies.sqlx-core] workspace = true diff --git a/sqlx-postgres/src/advisory_lock.rs b/sqlx-postgres/src/advisory_lock.rs index 84cad2bfdd..979aca06ce 100644 --- a/sqlx-postgres/src/advisory_lock.rs +++ b/sqlx-postgres/src/advisory_lock.rs @@ -3,6 +3,8 @@ use crate::Either; use crate::PgConnection; use hkdf::Hkdf; use sha2::Sha256; +use sqlx_core::executor::Executor; +use sqlx_core::sql_str::SqlSafeStr; use std::ops::{Deref, DerefMut}; use std::sync::Arc; use std::sync::OnceLock; @@ -199,27 +201,36 @@ impl PgAdvisoryLock { /// See [Postgres' documentation for the Advisory Lock Functions][advisory-funcs] for details. /// /// [advisory-funcs]: https://www.postgresql.org/docs/current/functions-admin.html#FUNCTIONS-ADVISORY-LOCKS + /// + /// # Cancel Safety + /// + /// This method is cancel safe. If the future is dropped before the query completes, a + /// `pg_advisory_unlock()` call is queued and run the next time the connection is used. pub async fn acquire>( &self, mut conn: C, ) -> Result> { + let query = match &self.key { + PgAdvisoryLockKey::BigInt(_) => "SELECT pg_advisory_lock($1)", + PgAdvisoryLockKey::IntPair(_, _) => "SELECT pg_advisory_lock($1, $2)", + }; + + let stmt = conn.as_mut().prepare(query.into_sql_str()).await?; + let query = crate::query::query_statement(&stmt); + + // We're wrapping the connection in a `PgAdvisoryLockGuard` early here on purpose. If this + // future is dropped, the lock will be released in the drop impl. + let mut guard = PgAdvisoryLockGuard::new(self.clone(), conn); + let conn = guard.conn.as_mut().unwrap(); + match &self.key { - PgAdvisoryLockKey::BigInt(key) => { - crate::query::query("SELECT pg_advisory_lock($1)") - .bind(key) - .execute(conn.as_mut()) - .await?; - } - PgAdvisoryLockKey::IntPair(key1, key2) => { - crate::query::query("SELECT pg_advisory_lock($1, $2)") - .bind(key1) - .bind(key2) - .execute(conn.as_mut()) - .await?; - } + PgAdvisoryLockKey::BigInt(key) => query.bind(key), + PgAdvisoryLockKey::IntPair(key1, key2) => query.bind(key1).bind(key2), } + .execute(conn.as_mut()) + .await?; - Ok(PgAdvisoryLockGuard::new(self.clone(), conn)) + Ok(guard) } /// Acquires an exclusive lock using `pg_try_advisory_lock()`, returning immediately @@ -242,6 +253,12 @@ impl PgAdvisoryLock { /// See [Postgres' documentation for the Advisory Lock Functions][advisory-funcs] for details. /// /// [advisory-funcs]: https://www.postgresql.org/docs/current/functions-admin.html#FUNCTIONS-ADVISORY-LOCKS + /// + /// # Cancel Safety + /// + /// This method is **not** cancel safe. If the future is dropped while the query is in-flight, + /// it is not possible to know whether the lock was acquired, so it cannot be safely released. + /// The lock may remain held until the connection is closed. pub async fn try_acquire>( &self, mut conn: C, diff --git a/sqlx-postgres/src/arguments.rs b/sqlx-postgres/src/arguments.rs index c0db982c7d..90cdd85057 100644 --- a/sqlx-postgres/src/arguments.rs +++ b/sqlx-postgres/src/arguments.rs @@ -4,11 +4,9 @@ use std::sync::Arc; use crate::encode::{Encode, IsNull}; use crate::error::Error; -use crate::ext::ustr::UStr; use crate::types::Type; use crate::{PgConnection, PgTypeInfo, Postgres}; -use crate::type_info::PgArrayOf; pub(crate) use sqlx_core::arguments::Arguments; use sqlx_core::error::BoxDynError; @@ -43,13 +41,10 @@ pub struct PgArgumentBuffer { // This is done for Records and Arrays as the OID is needed well before we are in an async // function and can just ask postgres. // - type_holes: Vec<(usize, HoleKind)>, // Vec<{ offset, type_name }> -} - -#[derive(Debug, Clone)] -enum HoleKind { - Type { name: UStr }, - Array(Arc), + hole_offsets: Vec, + // Separate vecator so that we don't have to generify or duplicate the logic in + // `PgConnection::resolve_types()`. + hole_types: Vec, } #[derive(Clone)] @@ -114,7 +109,8 @@ impl PgArguments { ) -> Result<(), Error> { let PgArgumentBuffer { ref patches, - ref type_holes, + ref hole_types, + ref hole_offsets, ref mut buffer, .. } = self.buffer; @@ -126,12 +122,10 @@ impl PgArguments { (patch.callback)(buf, ty); } - for (offset, kind) in type_holes { - let oid = match kind { - HoleKind::Type { name } => conn.fetch_type_id_by_name(name).await?, - HoleKind::Array(array) => conn.fetch_array_type_id(array).await?, - }; - buffer[*offset..(*offset + 4)].copy_from_slice(&oid.0.to_be_bytes()); + let resolved_holes = conn.resolve_types(hole_types).await?; + + for (&offset, oid) in hole_offsets.iter().zip(resolved_holes) { + buffer[offset..][..4].copy_from_slice(&oid.0.to_be_bytes()); } Ok(()) @@ -195,8 +189,8 @@ impl PgArgumentBuffer { } // Adds a callback to be invoked later when we know the parameter type - #[allow(dead_code)] - pub(crate) fn patch(&mut self, callback: F) + #[cfg_attr(not(feature = "json"), expect(dead_code))] + pub(crate) fn patch_with(&mut self, callback: F) where F: Fn(&mut [u8], &PgTypeInfo) + 'static + Send + Sync, { @@ -212,23 +206,12 @@ impl PgArgumentBuffer { // Extends the inner buffer by enough space to have an OID // Remembers where the OID goes and type name for the OID - pub(crate) fn patch_type_by_name(&mut self, type_name: &UStr) { + pub(crate) fn push_hole(&mut self, type_info: PgTypeInfo) { let offset = self.len(); self.extend_from_slice(&0_u32.to_be_bytes()); - self.type_holes.push(( - offset, - HoleKind::Type { - name: type_name.clone(), - }, - )); - } - - pub(crate) fn patch_array_type(&mut self, array: Arc) { - let offset = self.len(); - - self.extend_from_slice(&0_u32.to_be_bytes()); - self.type_holes.push((offset, HoleKind::Array(array))); + self.hole_offsets.push(offset); + self.hole_types.push(type_info); } fn snapshot(&self) -> PgArgumentBufferSnapshot { @@ -236,14 +219,15 @@ impl PgArgumentBuffer { buffer, count, patches, - type_holes, + hole_offsets, + .. } = self; PgArgumentBufferSnapshot { buffer_length: buffer.len(), count: *count, patches_length: patches.len(), - type_holes_length: type_holes.len(), + type_holes_length: hole_offsets.len(), } } @@ -259,7 +243,8 @@ impl PgArgumentBuffer { self.buffer.truncate(buffer_length); self.count = count; self.patches.truncate(patches_length); - self.type_holes.truncate(type_holes_length); + self.hole_offsets.truncate(type_holes_length); + self.hole_types.truncate(type_holes_length); } } diff --git a/sqlx-postgres/src/bind_iter.rs b/sqlx-postgres/src/bind_iter.rs index 0f44f19e3d..c0cb54f2b0 100644 --- a/sqlx-postgres/src/bind_iter.rs +++ b/sqlx-postgres/src/bind_iter.rs @@ -1,4 +1,4 @@ -use crate::{type_info::PgType, PgArgumentBuffer, PgHasArrayType, PgTypeInfo, Postgres}; +use crate::{PgArgumentBuffer, PgHasArrayType, PgTypeInfo, Postgres}; use core::cell::Cell; use sqlx_core::{ database::Database, @@ -95,13 +95,10 @@ where buf.extend(&1_i32.to_be_bytes()); // number of dimensions buf.extend(&0_i32.to_be_bytes()); // flags - match type_info.0 { - PgType::DeclareWithName(name) => buf.patch_type_by_name(&name), - PgType::DeclareArrayOf(array) => buf.patch_array_type(array), - - ty => { - buf.extend(&ty.oid().0.to_be_bytes()); - } + if let Some(oid) = type_info.oid() { + buf.extend(oid.0.to_be_bytes()); + } else { + buf.push_hole(type_info); } let len_start = buf.len(); diff --git a/sqlx-postgres/src/connection/establish.rs b/sqlx-postgres/src/connection/establish.rs index 634b71de4b..3c2f516533 100644 --- a/sqlx-postgres/src/connection/establish.rs +++ b/sqlx-postgres/src/connection/establish.rs @@ -148,7 +148,7 @@ impl PgConnection { cache_type_oid: HashMap::new(), cache_type_info: HashMap::new(), cache_elem_type_to_array: HashMap::new(), - cache_table_to_column_names: HashMap::new(), + cache_table_data: HashMap::new(), log_settings: options.log_settings.clone(), }), }) diff --git a/sqlx-postgres/src/connection/executor.rs b/sqlx-postgres/src/connection/executor.rs index a221dc3489..e0f4c3d44a 100644 --- a/sqlx-postgres/src/connection/executor.rs +++ b/sqlx-postgres/src/connection/executor.rs @@ -4,7 +4,7 @@ use crate::io::{PortalId, StatementId}; use crate::logger::QueryLogger; use crate::message::{ self, BackendMessageFormat, Bind, Close, CommandComplete, DataRow, ParameterDescription, Parse, - ParseComplete, Query, RowDescription, + ParseComplete, RowDescription, }; use crate::statement::PgStatementMetadata; use crate::{ @@ -23,10 +23,10 @@ use std::{pin::pin, sync::Arc}; async fn prepare( conn: &mut PgConnection, sql: &str, - parameters: &[PgTypeInfo], + arg_types: &[PgTypeInfo], metadata: Option>, persistent: bool, - fetch_column_origin: bool, + resolve_column_origin: bool, ) -> Result<(StatementId, Arc), Error> { let id = if persistent { let id = conn.inner.next_statement_id; @@ -39,12 +39,7 @@ async fn prepare( // build a list of type OIDs to send to the database in the PARSE command // we have not yet started the query sequence, so we are *safe* to cleanly make // additional queries here to get any missing OIDs - - let mut param_types = Vec::with_capacity(parameters.len()); - - for ty in parameters { - param_types.push(conn.resolve_type_id(&ty.0).await?); - } + let param_types = conn.resolve_types(arg_types).await?; // flush and wait until we are re-ready conn.wait_until_ready().await?; @@ -79,26 +74,20 @@ async fn prepare( } else { let parameters = recv_desc_params(conn).await?; - let rows = recv_desc_rows(conn).await?; + let row_desc = recv_desc_rows(conn).await?; // each SYNC produces one READY FOR QUERY conn.recv_ready_for_query().await?; - let parameters = conn.handle_parameter_description(parameters).await?; - - let (columns, column_names) = conn - .handle_row_description(rows, true, fetch_column_origin) + let metadata = conn + .resolve_statement_metadata::(Some(parameters), row_desc, resolve_column_origin) .await?; // ensure that if we did fetch custom data, we wait until we are fully ready before // continuing conn.wait_until_ready().await?; - Arc::new(PgStatementMetadata { - parameters, - columns, - column_names: Arc::new(column_names), - }) + metadata }; Ok((id, metadata)) @@ -176,7 +165,7 @@ impl PgConnection { // optional metadata that was provided by the user, this means they are reusing // a statement object metadata: Option>, - fetch_column_origin: bool, + resolve_column_origin: bool, ) -> Result<(StatementId, Arc), Error> { if let Some(statement) = self.inner.cache_statement.get_mut(sql) { return Ok((*statement).clone()); @@ -188,7 +177,7 @@ impl PgConnection { parameters, metadata, persistent, - fetch_column_origin, + resolve_column_origin, ) .await?; @@ -292,8 +281,7 @@ impl PgConnection { PgValueFormat::Binary } else { // Query will trigger a ReadyForQuery - self.inner.stream.write_msg(Query(sql))?; - self.inner.pending_ready_for_query_count += 1; + self.queue_simple_query(sql)?; // metadata starts out as "nothing" metadata = Arc::new(PgStatementMetadata::default()); @@ -343,17 +331,15 @@ impl PgConnection { // incomplete query execution has finished BackendMessageFormat::PortalSuspended => {} + // indicates that a *new* set of rows are about to be returned BackendMessageFormat::RowDescription => { - // indicates that a *new* set of rows are about to be returned - let (columns, column_names) = self - .handle_row_description(Some(message.decode()?), false, false) - .await?; - - metadata = Arc::new(PgStatementMetadata { - column_names: Arc::new(column_names), - columns, - parameters: Vec::default(), - }); + let new_metadata = self.resolve_statement_metadata::( + None, + Some(message.decode()?), + false, + ).await?; + + metadata = new_metadata; } BackendMessageFormat::DataRow => { diff --git a/sqlx-postgres/src/connection/mod.rs b/sqlx-postgres/src/connection/mod.rs index d5db20ad05..d594585b6c 100644 --- a/sqlx-postgres/src/connection/mod.rs +++ b/sqlx-postgres/src/connection/mod.rs @@ -66,7 +66,7 @@ pub struct PgConnectionInner { cache_type_info: HashMap, cache_type_oid: HashMap, cache_elem_type_to_array: HashMap, - cache_table_to_column_names: HashMap, + cache_table_data: HashMap, // number of ReadyForQuery messages that we are currently expecting pub(crate) pending_ready_for_query_count: usize, @@ -78,7 +78,7 @@ pub struct PgConnectionInner { log_settings: LogSettings, } -pub(crate) struct TableColumns { +pub(crate) struct TableData { table_name: Arc, /// Attribute number -> name. columns: BTreeMap>, diff --git a/sqlx-postgres/src/connection/resolve.rs b/sqlx-postgres/src/connection/resolve.rs index 182d39ea36..e47c318926 100644 --- a/sqlx-postgres/src/connection/resolve.rs +++ b/sqlx-postgres/src/connection/resolve.rs @@ -1,15 +1,596 @@ -use crate::connection::TableColumns; +use crate::connection::TableData; use crate::error::Error; use crate::ext::ustr::UStr; use crate::message::{ParameterDescription, RowDescription}; -use crate::query_as::query_as; -use crate::query_scalar::query_scalar; -use crate::type_info::{PgArrayOf, PgCustomType, PgType, PgTypeKind}; +use crate::statement::PgStatementMetadata; +use crate::type_info::{PgCustomType, PgType, PgTypeKind}; use crate::types::Oid; -use crate::HashMap; +use crate::{HashMap, PgRow, PgValueRef, Postgres}; use crate::{PgColumn, PgConnection, PgTypeInfo}; use sqlx_core::column::{ColumnOrigin, TableColumn}; +use sqlx_core::decode::Decode; +use sqlx_core::error::BoxDynError; +use sqlx_core::from_row::FromRow; +use sqlx_core::raw_sql::raw_sql; +use sqlx_core::row::Row; +use sqlx_core::sql_str::AssertSqlSafe; +use sqlx_core::types::Type; +use std::collections::{BTreeMap, VecDeque}; +use std::fmt::Display; +use std::mem; +use std::ops::ControlFlow; use std::sync::Arc; +// NOTE: we should only use raw queries in this module because this may occur in the middle +// of an existing extended query flow. Additionally, some third-party implementations don't +// support named prepared statements, so to execute these statements with the extended query flow, +// we'd have to replace the unnamed prepared statement which is already the one the user wanted +// to execute. This means we'd have to immediately re-prepare it, adding an extra round trip. + +impl PgConnection { + pub(super) async fn resolve_statement_metadata( + &mut self, + param_desc: Option, + row_desc: Option, + resolve_column_origin: bool, + ) -> Result, Error> { + let param_types = param_desc.map_or_else(Default::default, |desc| desc.types); + + let fields = row_desc.map_or_else(Default::default, |desc| desc.fields); + + if QUERIES_ALLOWED { + let mut type_resolver = TypeResolver::default(); + let mut column_resolver = ColumnResolver::default(); + + for ty in ¶m_types { + if self.try_oid_to_type(*ty).is_none() { + type_resolver.push_type("NULL", ty.0); + } + } + + for field in &fields { + if self.try_oid_to_type(field.data_type_id).is_none() { + type_resolver.push_type("NULL", field.data_type_id.0); + } + + if let (Some(relation_oid), Some(attribute_no)) = + (field.relation_id, field.relation_attribute_no) + { + if resolve_column_origin && !self.has_table_column(relation_oid, attribute_no) { + column_resolver.push_column(relation_oid, attribute_no); + } + } + } + + // No-op if `.push_type()` was not called + type_resolver.fill_cache(self).await?; + + // No-op if `.push_column()` was not called + column_resolver.fill_cache(self).await?; + } + + let mut parameters = Vec::with_capacity(param_types.len()); + + for ty in param_types { + if let Some(type_info) = self.try_oid_to_type(ty) { + parameters.push(type_info); + } else { + parameters.push(PgTypeInfo(PgType::DeclareWithOid(ty))); + } + } + + let mut columns = Vec::with_capacity(fields.len()); + let mut column_names = HashMap::with_capacity(fields.len()); + + for field in fields { + let name = UStr::from(field.name); + let ordinal = columns.len(); + + let type_info = self + .try_oid_to_type(field.data_type_id) + .unwrap_or(PgTypeInfo(PgType::DeclareWithOid(field.data_type_id))); + + let origin = field.relation_id.zip(field.relation_attribute_no).map_or( + ColumnOrigin::Expression, + |(relation_oid, attribue_no)| { + self.try_table_column(relation_oid, attribue_no) + .map_or(ColumnOrigin::Unknown, ColumnOrigin::Table) + }, + ); + + columns.push(PgColumn { + ordinal, + name: name.clone(), + type_info, + origin, + relation_id: field.relation_id, + relation_attribute_no: field.relation_attribute_no, + }); + + column_names.insert(name, ordinal); + } + + Ok(Arc::new(PgStatementMetadata { + columns, + column_names: column_names.into(), + parameters, + })) + } + + fn try_table_column(&self, relation_oid: Oid, attribute_no: i16) -> Option { + let table_columns = self.inner.cache_table_data.get(&relation_oid)?; + + let column = table_columns.columns.get(&attribute_no)?; + + Some(TableColumn { + table: table_columns.table_name.clone(), + name: column.clone(), + }) + } + + fn has_table_column(&self, relation_oid: Oid, attribute_no: i16) -> bool { + self.inner + .cache_table_data + .get(&relation_oid) + .is_some_and(|data| data.columns.contains_key(&attribute_no)) + } + + pub(crate) async fn resolve_types(&mut self, types: &[PgTypeInfo]) -> Result, Error> { + let mut oids = Vec::with_capacity(types.len()); + + let mut unresolved_types = types.iter().peekable(); + + // Eagerly try to resolve types, stopping at the first unresolved type + while let Some(ty) = unresolved_types.peek() { + let Some(oid) = self.try_type_to_oid(ty) else { + break; + }; + + oids.push(oid); + unresolved_types.next(); + } + + // Fast-path: all types resolved + if oids.len() == types.len() { + return Ok(oids); + } + + let mut resolver = TypeResolver::default(); + + for ty in unresolved_types.clone() { + // Skip over subsequent types that are already resolved + if self.try_type_to_oid(ty).is_some() { + continue; + } + + if let PgType::DeclareArrayOf(array_of) = &ty.0 { + // Eagerly bring the element type into cache for array types declared by-name + resolver.push_type( + format_args!("E'{}'", array_of.elem_name), + format_args!("to_regtype(E'{}')", array_of.elem_name), + ); + } + + resolver.push_type( + // `escape_default()` should produce a valid SQL string literal + // https://doc.rust-lang.org/stable/std/primitive.char.html#method.escape_default + // https://www.postgresql.org/docs/current/sql-syntax-lexical.html#SQL-SYNTAX-STRINGS-ESCAPE + format_args!("E'{}'", ty.name().escape_default()), + // `to_regtype()` evaluates to `NULL` if the type does not exist, + // instead of throwing an exception like `''::regtype` does. + format_args!("to_regtype(E'{}')::oid", ty.name().escape_default()), + ); + } + + resolver.fill_cache(self).await?; + + for ty in unresolved_types { + oids.push( + self.try_type_to_oid(ty) + .ok_or_else(|| Error::TypeNotFound { + type_name: ty.name().to_string(), + })?, + ); + } + + Ok(oids) + } + + pub(crate) fn try_type_to_oid(&self, ty: &PgTypeInfo) -> Option { + if let Some(oid) = ty.try_oid() { + return Some(oid); + } + + match &ty.0 { + PgType::DeclareWithName(name) => self.inner.cache_type_oid.get(name).copied(), + PgType::DeclareArrayOf(array) => { + let typelem = self.inner.cache_type_oid.get(&array.elem_name).copied()?; + self.inner.cache_elem_type_to_array.get(&typelem).copied() + } + // `.try_oid()` should return `Some()` or it should be covered here + _ => unreachable!("(bug) OID should be resolvable for type {ty:?}"), + } + } + + fn try_oid_to_type(&self, oid: Oid) -> Option { + PgTypeInfo::try_from_oid(oid).or_else(|| self.inner.cache_type_info.get(&oid).cloned()) + } + + fn try_cache_type(&mut self, ty: &TypeResolverRow) -> Result, Error> { + if self.try_oid_to_type(ty.oid).is_some() { + // We hit this code path because one of these names didn't resolve, + // cache them both. + self.inner + .cache_type_oid + .insert(UStr::new(&ty.catalog_name), ty.oid); + self.inner + .cache_type_oid + .insert(UStr::new(&ty.pretty_name), ty.oid); + + if let Some(original_name) = &ty.original_name { + self.inner + .cache_type_oid + .insert(UStr::new(original_name), ty.oid); + } + + if let Some(elem_oid) = ty.typelem { + if self.try_oid_to_type(elem_oid).is_some() { + self.inner.cache_elem_type_to_array.insert(elem_oid, ty.oid); + } else { + return Ok(ControlFlow::Break(elem_oid)); + } + } + + return Ok(ControlFlow::Continue(())); + } + + if self.inner.cache_type_info.contains_key(&ty.oid) { + return Ok(ControlFlow::Continue(())); + } + + let custom_type_kind = match (ty.typtype, ty.typcategory) { + (TypType::Domain, _) => { + let typbasetype = ty.typbasetype.ok_or_else(|| { + err_protocol!( + "type category is listed as domain, but no base type was found: {ty:?}" + ) + })?; + + let Some(base_type) = self.try_oid_to_type(typbasetype) else { + return Ok(ControlFlow::Break(typbasetype)); + }; + + PgTypeKind::Domain(base_type) + } + + (TypType::Base, TypCategory::Array) => { + let typelem = ty.typelem.ok_or_else(|| { + err_protocol!( + "type category is listed as array, but no element type was found: {ty:?}" + ) + })?; + + let Some(elem_type) = self.try_oid_to_type(typelem) else { + return Ok(ControlFlow::Break(typelem)); + }; + + self.inner.cache_elem_type_to_array.insert(typelem, ty.oid); + + PgTypeKind::Array(elem_type) + } + + (TypType::Pseudo, _) => PgTypeKind::Pseudo, + + (TypType::Range, _) => { + let rngsubtype = ty.rngsubtype.ok_or_else(|| { + err_protocol!( + "type category is listed as range, but no subtype was found: {ty:?}" + ) + })?; + + let Some(sub_type) = self.try_oid_to_type(rngsubtype) else { + return Ok(ControlFlow::Break(rngsubtype)); + }; + + PgTypeKind::Range(sub_type) + } + + (TypType::Enum, _) => PgTypeKind::Enum(ty.enum_labels.iter().cloned().collect()), + + (TypType::Composite, _) => { + let mut attributes = Vec::with_capacity(ty.record_attributes.len()); + + for (name, oid) in &ty.record_attributes { + let Some(attribute_type) = self.try_oid_to_type(*oid) else { + return Ok(ControlFlow::Break(*oid)); + }; + + attributes.push((name.clone(), attribute_type)); + } + + PgTypeKind::Composite(attributes.into()) + } + + _ => PgTypeKind::Simple, + }; + + let typname = UStr::new(&ty.pretty_name); + + self.inner + .cache_type_oid + .entry_ref(&typname) + .or_insert(ty.oid); + + if ty.pretty_name != ty.catalog_name { + self.inner + .cache_type_oid + .entry(UStr::new(&ty.catalog_name)) + .or_insert(ty.oid); + } + + if let Some(original_name) = &ty.original_name { + self.inner + .cache_type_oid + .entry(UStr::new(original_name)) + .or_insert(ty.oid); + } + + self.inner.cache_type_info.entry(ty.oid).or_insert_with(|| { + PgTypeInfo(PgType::Custom(Arc::new(PgCustomType { + kind: custom_type_kind, + name: typname.clone(), + oid: ty.oid, + }))) + }); + + Ok(ControlFlow::Continue(())) + } +} + +#[derive(Default)] +struct TypeResolver { + query: String, +} + +impl TypeResolver { + fn push_type(&mut self, original_name: impl Display, oid_expr: impl Display) { + use std::fmt::Write; + + tracing::trace!(%original_name, %oid_expr, "push_type"); + + // Lazily push the preamble to `self.query` so we don't allocate in the fast path + // (all types already known) + if self.query.is_empty() { + write!( + &mut self.query, + // Postgres 13 would return `0` instead of `NULL` for `typelem`, `typbasetype` + "SELECT pg_type.oid,\n\ + pg_type.oid::regtype::text pretty_name,\n\ + typname catalog_name,\n\ + original_name,\n\ + typtype,\n\ + typcategory,\n\ + NULLIF(typelem, 0::oid) typelem,\n\ + NULLIF(typbasetype, 0::oid) typbasetype,\n\ + rngsubtype,\n\ + COALESCE(\ + (SELECT array_agg(enumlabel) FROM (SELECT *\n\ + FROM pg_catalog.pg_enum\n\ + WHERE enumtypid = pg_type.oid\n\ + ORDER BY enumsortorder) labels),\n\ + '{{}}') enum_labels,\n\ + COALESCE(\n\ + (SELECT array_agg((attname, atttypid)) FROM (SELECT *\n\ + FROM pg_catalog.pg_attribute\n\ + WHERE attrelid = pg_type.typrelid\n\ + AND NOT attisdropped\n\ + AND attnum > 0\n\ + ORDER BY attnum) attributes),\n\ + '{{}}') record_attributes\n\ + FROM (SELECT DISTINCT ON(lookup_oid) original_name, lookup_oid\n\ + FROM (VALUES ({original_name}, {oid_expr})" + ) + .expect("error writing type expression to query string") + } else { + write!(&mut self.query, ", ({original_name}, {oid_expr})") + .expect("error writing type expression to query string") + } + } + + async fn fill_cache(&mut self, conn: &mut PgConnection) -> Result<(), Error> { + let mut missing_dependencies = HashMap::>::new(); + + // Iteratively resolve types until all are resolved, or we hit a dead-end. + // We statically cap the number of iterations in case we somehow encounter a circular type + // dependency, which I *assume* Postgres should forbid. + for _ in 0..64 { + if self.query.is_empty() { + break; + } + + // * Cancel-safety + // * Makes this type reusable if we want to for whatever reason + // * Avoids an allocation when converting to `SqlStr` + let mut query = mem::take(&mut self.query); + query.push_str( + ") lookup_inner(original_name, lookup_oid)\n\ + ORDER BY lookup_oid) type_lookup\n\ + INNER JOIN pg_catalog.pg_type ON type_lookup.lookup_oid = pg_type.oid\n\ + LEFT JOIN pg_catalog.pg_range ON pg_type.oid = pg_range.rngtypid", + ); + + tracing::trace!(query, "fill_cache"); + + let types = raw_sql(AssertSqlSafe(query)).fetch_all(&mut *conn).await?; + + 'outer: for row in types { + let mut type_row = TypeResolverRow::from_row(&row)?; + + tracing::trace!("type_row: {type_row:?}"); + + let mut resolved_dependencies = VecDeque::new(); + + loop { + if let ControlFlow::Break(missing_oid) = conn.try_cache_type(&type_row)? { + tracing::trace!( + ty_name = type_row.catalog_name, + missing_oid = missing_oid.0, + "type missing dependency" + ); + + missing_dependencies + .entry(missing_oid) + .or_default() + .push(type_row); + + self.push_type("NULL", missing_oid.0); + + continue 'outer; + } + + resolved_dependencies.extend( + missing_dependencies + .remove(&type_row.oid) + .unwrap_or_default(), + ); + + // Iteratively mark existing dependencies as resolved + if let Some(next_row) = resolved_dependencies.pop_back() { + tracing::trace!( + resolved_oid = type_row.oid.0, + ty_name = next_row.catalog_name, + "resolved dependency" + ); + + type_row = next_row + } else { + break; + } + } + } + } + + if !missing_dependencies.is_empty() { + return Err(Error::Protocol(format!( + "unable to resolve type OIDs: {:?}", + missing_dependencies.keys() + ))); + } + + Ok(()) + } +} + +#[derive(Debug)] +struct TypeResolverRow { + oid: Oid, + // Most of the time, these are the same but not necessarily for arrays + pretty_name: String, + catalog_name: String, + original_name: Option, + typtype: TypType, + typcategory: TypCategory, + typelem: Option, + typbasetype: Option, + rngsubtype: Option, + enum_labels: Vec, + record_attributes: Vec<(String, Oid)>, +} + +// Can't use `#[derive(FromRow)]` here +impl<'r> FromRow<'r, PgRow> for TypeResolverRow { + fn from_row(row: &'r PgRow) -> Result { + Ok(Self { + oid: row.try_get("oid")?, + pretty_name: row.try_get("pretty_name")?, + catalog_name: row.try_get("catalog_name")?, + original_name: row.try_get("original_name")?, + typtype: row.try_get("typtype")?, + typcategory: row.try_get("typcategory")?, + typelem: row.try_get("typelem")?, + typbasetype: row.try_get("typbasetype")?, + rngsubtype: row.try_get("rngsubtype")?, + enum_labels: row.try_get("enum_labels")?, + record_attributes: row.try_get("record_attributes")?, + }) + } +} + +#[derive(Default)] +struct ColumnResolver { + query: String, +} + +impl ColumnResolver { + fn push_column(&mut self, table_oid: Oid, attribute_no: i16) { + use std::fmt::Write; + + if self.query.is_empty() { + write!( + self.query, + // Postgres 13 does not accept `(attnum,attname)` without `ROW` + "SELECT\n\ + attrelid table_oid,\n\ + attrelid::regclass::text table_name,\n\ + array_agg(ROW(attnum, attname)) AS columns\n\ + FROM (VALUES ({}, {attribute_no})", + table_oid.0, + ) + .expect("writing to a `String` should be infallible") + } else { + write!(self.query, ", ({}, {attribute_no})", table_oid.0) + .expect("writing to a `String` should be infallible") + } + } + + async fn fill_cache(&mut self, conn: &mut PgConnection) -> Result<(), Error> { + if self.query.is_empty() { + return Ok(()); + } + + let mut query = mem::take(&mut self.query); + query.push_str( + ") lookup(table_oid, attribute_num)\n\ + INNER JOIN pg_catalog.pg_attribute ON lookup.table_oid = attrelid AND lookup.attribute_num = attnum\n\ + GROUP BY attrelid" + ); + + let rows = raw_sql(AssertSqlSafe(query)).fetch_all(&mut *conn).await?; + + for row in rows { + let row = ColumnResolverRow::from_row(&row)?; + + let table_columns = conn + .inner + .cache_table_data + .entry(row.table_oid) + .or_insert_with(|| TableData { + table_name: row.table_name.clone(), + columns: BTreeMap::new(), + }); + + table_columns.columns.extend(row.columns); + } + + Ok(()) + } +} + +#[derive(Debug)] +struct ColumnResolverRow { + table_oid: Oid, + table_name: Arc, + columns: Vec<(i16, Arc)>, +} + +impl<'r> FromRow<'r, PgRow> for ColumnResolverRow { + fn from_row(row: &'r PgRow) -> Result { + Ok(Self { + table_oid: row.try_get("table_oid")?, + table_name: row.try_get("table_name")?, + columns: row.try_get("columns")?, + }) + } +} /// Describes the type of the `pg_type.typtype` column /// @@ -25,10 +606,10 @@ enum TypType { } impl TryFrom for TypType { - type Error = (); + type Error = String; fn try_from(t: i8) -> Result { - let t = u8::try_from(t).or(Err(()))?; + let t = u8::try_from(t).map_err(|_| format!("unknown type code {t}"))?; let t = match t { b'b' => Self::Base, @@ -37,12 +618,24 @@ impl TryFrom for TypType { b'e' => Self::Enum, b'p' => Self::Pseudo, b'r' => Self::Range, - _ => return Err(()), + _ => return Err(format!("unknown type code {t}")), }; Ok(t) } } +impl<'r> Decode<'r, Postgres> for TypType { + fn decode(value: PgValueRef<'r>) -> Result { + Ok(i8::decode(value)?.try_into()?) + } +} + +impl Type for TypType { + fn type_info() -> PgTypeInfo { + PgTypeInfo(PgType::Char) + } +} + /// Describes the type of the `pg_type.typcategory` column /// /// See @@ -66,10 +659,10 @@ enum TypCategory { } impl TryFrom for TypCategory { - type Error = (); + type Error = String; fn try_from(c: i8) -> Result { - let c = u8::try_from(c).or(Err(()))?; + let c = u8::try_from(c).map_err(|_| format!("invalid category code {c}"))?; let c = match c { b'A' => Self::Array, @@ -87,399 +680,20 @@ impl TryFrom for TypCategory { b'U' => Self::User, b'V' => Self::BitString, b'X' => Self::Unknown, - _ => return Err(()), + _ => return Err(format!("invalid category code {c}")), }; Ok(c) } } -impl PgConnection { - pub(super) async fn handle_row_description( - &mut self, - desc: Option, - fetch_type_info: bool, - fetch_column_description: bool, - ) -> Result<(Vec, HashMap), Error> { - let mut columns = Vec::new(); - let mut column_names = HashMap::new(); - - let desc = if let Some(desc) = desc { - desc - } else { - // no rows - return Ok((columns, column_names)); - }; - - columns.reserve(desc.fields.len()); - column_names.reserve(desc.fields.len()); - - for (index, field) in desc.fields.into_iter().enumerate() { - let name = UStr::from(field.name); - - let type_info = self - .maybe_fetch_type_info_by_oid(field.data_type_id, fetch_type_info) - .await?; - - let origin = if let (Some(relation_oid), Some(attribute_no)) = - (field.relation_id, field.relation_attribute_no) - { - self.maybe_fetch_column_origin(relation_oid, attribute_no, fetch_column_description) - .await? - } else { - ColumnOrigin::Expression - }; - - let column = PgColumn { - ordinal: index, - name: name.clone(), - type_info, - relation_id: field.relation_id, - relation_attribute_no: field.relation_attribute_no, - origin, - }; - - columns.push(column); - column_names.insert(name, index); - } - - Ok((columns, column_names)) - } - - pub(super) async fn handle_parameter_description( - &mut self, - desc: ParameterDescription, - ) -> Result, Error> { - let mut params = Vec::with_capacity(desc.types.len()); - - for ty in desc.types { - params.push(self.maybe_fetch_type_info_by_oid(ty, true).await?); - } - - Ok(params) - } - - async fn maybe_fetch_type_info_by_oid( - &mut self, - oid: Oid, - should_fetch: bool, - ) -> Result { - // first we check if this is a built-in type - // in the average application, the vast majority of checks should flow through this - if let Some(info) = PgTypeInfo::try_from_oid(oid) { - return Ok(info); - } - - // next we check a local cache for user-defined type names <-> object id - if let Some(info) = self.inner.cache_type_info.get(&oid) { - return Ok(info.clone()); - } - - // fallback to asking the database directly for a type name - if should_fetch { - // we're boxing this future here so we can use async recursion - let info = Box::pin(async { self.fetch_type_by_oid(oid).await }).await?; - - // cache the type name <-> oid relationship in a paired hashmap - // so we don't come down this road again - self.inner.cache_type_info.insert(oid, info.clone()); - self.inner - .cache_type_oid - .insert(info.0.name().to_string().into(), oid); - - Ok(info) - } else { - // we are not in a place that *can* run a query - // this generally means we are in the middle of another query - // this _should_ only happen for complex types sent through the TEXT protocol - // we're open to ideas to correct this.. but it'd probably be more efficient to figure - // out a way to "prime" the type cache for connections rather than make this - // fallback work correctly for complex user-defined types for the TEXT protocol - Ok(PgTypeInfo(PgType::DeclareWithOid(oid))) - } +impl<'r> Decode<'r, Postgres> for TypCategory { + fn decode(value: PgValueRef<'r>) -> Result { + Ok(i8::decode(value)?.try_into()?) } +} - async fn maybe_fetch_column_origin( - &mut self, - relation_id: Oid, - attribute_no: i16, - should_fetch: bool, - ) -> Result { - if let Some(origin) = self - .inner - .cache_table_to_column_names - .get(&relation_id) - .and_then(|table_columns| { - let column_name = table_columns.columns.get(&attribute_no).cloned()?; - - Some(ColumnOrigin::Table(TableColumn { - table: table_columns.table_name.clone(), - name: column_name, - })) - }) - { - return Ok(origin); - } - - if !should_fetch { - return Ok(ColumnOrigin::Unknown); - } - - // Looking up the table name _may_ end up being redundant, - // but the round-trip to the server is by far the most expensive part anyway. - let Some((table_name, column_name)): Option<(String, String)> = query_as( - // language=PostgreSQL - "SELECT $1::oid::regclass::text, attname \ - FROM pg_catalog.pg_attribute \ - WHERE attrelid = $1 AND attnum = $2", - ) - .bind(relation_id) - .bind(attribute_no) - .fetch_optional(&mut *self) - .await? - else { - // The column/table doesn't exist anymore for whatever reason. - return Ok(ColumnOrigin::Unknown); - }; - - let table_columns = self - .inner - .cache_table_to_column_names - .entry(relation_id) - .or_insert_with(|| TableColumns { - table_name: table_name.into(), - columns: Default::default(), - }); - - let column_name = table_columns - .columns - .entry(attribute_no) - .or_insert(column_name.into()); - - Ok(ColumnOrigin::Table(TableColumn { - table: table_columns.table_name.clone(), - name: Arc::clone(column_name), - })) - } - - async fn fetch_type_by_oid(&mut self, oid: Oid) -> Result { - let (name, typ_type, category, relation_id, element, base_type): ( - String, - i8, - i8, - Oid, - Oid, - Oid, - ) = query_as( - // Converting the OID to `regtype` and then `text` will give us the name that - // the type will need to be found at by search_path. - "SELECT oid::regtype::text, \ - typtype, \ - typcategory, \ - typrelid, \ - typelem, \ - typbasetype \ - FROM pg_catalog.pg_type \ - WHERE oid = $1", - ) - .bind(oid) - .fetch_one(&mut *self) - .await?; - - let typ_type = TypType::try_from(typ_type); - let category = TypCategory::try_from(category); - - match (typ_type, category) { - (Ok(TypType::Domain), _) => self.fetch_domain_by_oid(oid, base_type, name).await, - - (Ok(TypType::Base), Ok(TypCategory::Array)) => { - Ok(PgTypeInfo(PgType::Custom(Arc::new(PgCustomType { - kind: PgTypeKind::Array( - self.maybe_fetch_type_info_by_oid(element, true).await?, - ), - name: name.into(), - oid, - })))) - } - - (Ok(TypType::Pseudo), Ok(TypCategory::Pseudo)) => { - Ok(PgTypeInfo(PgType::Custom(Arc::new(PgCustomType { - kind: PgTypeKind::Pseudo, - name: name.into(), - oid, - })))) - } - - (Ok(TypType::Range), Ok(TypCategory::Range)) => { - self.fetch_range_by_oid(oid, name).await - } - - (Ok(TypType::Enum), Ok(TypCategory::Enum)) => self.fetch_enum_by_oid(oid, name).await, - - (Ok(TypType::Composite), Ok(TypCategory::Composite)) => { - self.fetch_composite_by_oid(oid, relation_id, name).await - } - - _ => Ok(PgTypeInfo(PgType::Custom(Arc::new(PgCustomType { - kind: PgTypeKind::Simple, - name: name.into(), - oid, - })))), - } - } - - async fn fetch_enum_by_oid(&mut self, oid: Oid, name: String) -> Result { - let variants: Vec = query_scalar( - r#" -SELECT enumlabel -FROM pg_catalog.pg_enum -WHERE enumtypid = $1 -ORDER BY enumsortorder - "#, - ) - .bind(oid) - .fetch_all(self) - .await?; - - Ok(PgTypeInfo(PgType::Custom(Arc::new(PgCustomType { - oid, - name: name.into(), - kind: PgTypeKind::Enum(Arc::from(variants)), - })))) - } - - async fn fetch_composite_by_oid( - &mut self, - oid: Oid, - relation_id: Oid, - name: String, - ) -> Result { - let raw_fields: Vec<(String, Oid)> = query_as( - r#" -SELECT attname, atttypid -FROM pg_catalog.pg_attribute -WHERE attrelid = $1 -AND NOT attisdropped -AND attnum > 0 -ORDER BY attnum - "#, - ) - .bind(relation_id) - .fetch_all(&mut *self) - .await?; - - let mut fields = Vec::new(); - - for (field_name, field_oid) in raw_fields.into_iter() { - let field_type = self.maybe_fetch_type_info_by_oid(field_oid, true).await?; - - fields.push((field_name, field_type)); - } - - Ok(PgTypeInfo(PgType::Custom(Arc::new(PgCustomType { - oid, - name: name.into(), - kind: PgTypeKind::Composite(Arc::from(fields)), - })))) - } - - async fn fetch_domain_by_oid( - &mut self, - oid: Oid, - base_type: Oid, - name: String, - ) -> Result { - let base_type = self.maybe_fetch_type_info_by_oid(base_type, true).await?; - - Ok(PgTypeInfo(PgType::Custom(Arc::new(PgCustomType { - oid, - name: name.into(), - kind: PgTypeKind::Domain(base_type), - })))) - } - - async fn fetch_range_by_oid(&mut self, oid: Oid, name: String) -> Result { - let element_oid: Oid = query_scalar( - r#" -SELECT rngsubtype -FROM pg_catalog.pg_range -WHERE rngtypid = $1 - "#, - ) - .bind(oid) - .fetch_one(&mut *self) - .await?; - - let element = self.maybe_fetch_type_info_by_oid(element_oid, true).await?; - - Ok(PgTypeInfo(PgType::Custom(Arc::new(PgCustomType { - kind: PgTypeKind::Range(element), - name: name.into(), - oid, - })))) - } - - pub(crate) async fn resolve_type_id(&mut self, ty: &PgType) -> Result { - if let Some(oid) = ty.try_oid() { - return Ok(oid); - } - - match ty { - PgType::DeclareWithName(name) => self.fetch_type_id_by_name(name).await, - PgType::DeclareArrayOf(array) => self.fetch_array_type_id(array).await, - // `.try_oid()` should return `Some()` or it should be covered here - _ => unreachable!("(bug) OID should be resolvable for type {ty:?}"), - } - } - - pub(crate) async fn fetch_type_id_by_name(&mut self, name: &str) -> Result { - if let Some(oid) = self.inner.cache_type_oid.get(name) { - return Ok(*oid); - } - - // language=SQL - let (oid,): (Oid,) = query_as("SELECT $1::regtype::oid") - .bind(name) - .fetch_optional(&mut *self) - .await? - .ok_or_else(|| Error::TypeNotFound { - type_name: name.into(), - })?; - - self.inner - .cache_type_oid - .insert(name.to_string().into(), oid); - Ok(oid) - } - - pub(crate) async fn fetch_array_type_id(&mut self, array: &PgArrayOf) -> Result { - if let Some(oid) = self - .inner - .cache_type_oid - .get(&array.elem_name) - .and_then(|elem_oid| self.inner.cache_elem_type_to_array.get(elem_oid)) - { - return Ok(*oid); - } - - // language=SQL - let (elem_oid, array_oid): (Oid, Oid) = - query_as("SELECT oid, typarray FROM pg_catalog.pg_type WHERE oid = $1::regtype::oid") - .bind(&*array.elem_name) - .fetch_optional(&mut *self) - .await? - .ok_or_else(|| Error::TypeNotFound { - type_name: array.name.to_string(), - })?; - - // Avoids copying `elem_name` until necessary - self.inner - .cache_type_oid - .entry_ref(&array.elem_name) - .insert(elem_oid); - self.inner - .cache_elem_type_to_array - .insert(elem_oid, array_oid); - - Ok(array_oid) +impl Type for TypCategory { + fn type_info() -> PgTypeInfo { + PgTypeInfo(PgType::Char) } } diff --git a/sqlx-postgres/src/connection/sasl.rs b/sqlx-postgres/src/connection/sasl.rs index 94fdfc689f..7245e8ee49 100644 --- a/sqlx-postgres/src/connection/sasl.rs +++ b/sqlx-postgres/src/connection/sasl.rs @@ -56,8 +56,11 @@ pub(crate) async fn authenticate( let username = format!("{}={}", USERNAME_ATTR, options.username); let username = match saslprep(&username) { Ok(v) => v, - // TODO(danielakhterov): Remove panic when we have proper support for configuration errors - Err(_) => panic!("Failed to saslprep username"), + Err(error) => { + return Err(Error::Configuration( + format!("Failed to saslprep username: {:?}", error).into(), + )) + } }; // nonce = "r=" c-nonce [s-nonce] ;; Second part provided by server. @@ -86,13 +89,19 @@ pub(crate) async fn authenticate( } }; + // Normalize(password): + let password = options.password.as_deref().unwrap_or_default(); + let password = match saslprep(password) { + Ok(v) => v, + Err(error) => { + return Err(Error::Configuration( + format!("Failed to saslprep password: {:?}", error).into(), + )) + } + }; + // SaltedPassword := Hi(Normalize(password), salt, i) - let salted_password = hi( - options.password.as_deref().unwrap_or_default(), - &cont.salt, - cont.iterations, - ) - .await?; + let salted_password = hi(&password, &cont.salt, cont.iterations).await?; // ClientKey := HMAC(SaltedPassword, "Client Key") let mut mac = Hmac::::new_from_slice(&salted_password).map_err(Error::protocol)?; diff --git a/sqlx-postgres/src/error.rs b/sqlx-postgres/src/error.rs index 7b5a03f2b3..7f787a4e5e 100644 --- a/sqlx-postgres/src/error.rs +++ b/sqlx-postgres/src/error.rs @@ -156,7 +156,11 @@ impl Debug for PgDatabaseError { impl Display for PgDatabaseError { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { - f.write_str(self.message()) + f.write_str(self.message())?; + if let Some(line) = self.line() { + write!(f, " at line {line}")?; + } + Ok(()) } } diff --git a/sqlx-postgres/src/listener.rs b/sqlx-postgres/src/listener.rs index 9c439db854..f9b9b98b1f 100644 --- a/sqlx-postgres/src/listener.rs +++ b/sqlx-postgres/src/listener.rs @@ -36,6 +36,7 @@ pub struct PgListener { } /// An asynchronous notification from Postgres. +#[derive(Clone)] pub struct PgNotification(Notification); impl PgListener { @@ -335,7 +336,7 @@ impl PgListener { /// /// This is helpful if you want to retrieve all buffered notifications and process them in batches. pub fn next_buffered(&mut self) -> Option { - if let Ok(Some(notification)) = self.buffer_rx.try_next() { + if let Ok(notification) = self.buffer_rx.try_recv() { Some(PgNotification(notification)) } else { None diff --git a/sqlx-postgres/src/message/notification.rs b/sqlx-postgres/src/message/notification.rs index 7bf029839c..8eafcc27f2 100644 --- a/sqlx-postgres/src/message/notification.rs +++ b/sqlx-postgres/src/message/notification.rs @@ -4,7 +4,7 @@ use crate::error::Error; use crate::io::BufExt; use crate::message::{BackendMessage, BackendMessageFormat}; -#[derive(Debug)] +#[derive(Debug, Clone)] pub struct Notification { pub(crate) process_id: u32, pub(crate) channel: Bytes, diff --git a/sqlx-postgres/src/migrate.rs b/sqlx-postgres/src/migrate.rs index f4e55a36e6..4afa2046c9 100644 --- a/sqlx-postgres/src/migrate.rs +++ b/sqlx-postgres/src/migrate.rs @@ -288,6 +288,28 @@ CREATE TABLE IF NOT EXISTS {table_name} ( Ok(elapsed) }) } + + fn skip<'e>( + &'e mut self, + table_name: &'e str, + migration: &'e Migration, + ) -> BoxFuture<'e, Result<(), MigrateError>> { + Box::pin(async move { + // language=SQL + let _ = query(AssertSqlSafe(format!( + r#" + INSERT INTO {table_name} ( version, description, success, checksum, execution_time ) + VALUES ( $1, $2, TRUE, $3, -1 ) + "# + ))) + .bind(migration.version) + .bind(&*migration.description) + .bind(&*migration.checksum) + .execute(self) + .await?; + Ok(()) + }) + } } async fn execute_migration( diff --git a/sqlx-postgres/src/options/pgpass.rs b/sqlx-postgres/src/options/pgpass.rs index ca904fc751..b8e0e4847f 100644 --- a/sqlx-postgres/src/options/pgpass.rs +++ b/sqlx-postgres/src/options/pgpass.rs @@ -4,6 +4,12 @@ use std::fs::File; use std::io::{BufRead, BufReader}; use std::path::PathBuf; +#[derive(Debug, Clone, thiserror::Error, PartialEq, Eq)] +pub enum PGPassLineParseError { + #[error("Unexpected end of line")] + UnexpectedEOL, +} + /// try to load a password from the various pgpass file locations pub fn load_password( host: &str, @@ -30,7 +36,7 @@ pub fn load_password( etcetera::base_strategy::Windows::new() .ok() - .map(|basedirs| basedirs.data_dir().join("postgres").join("pgpass.conf")) + .map(|basedirs| basedirs.data_dir().join("postgresql").join("pgpass.conf")) }; load_password_from_file(default_file?, host, port, username, database) } @@ -114,8 +120,12 @@ fn load_password_from_reader( } else { // try to load password from line trim_newline(&mut line); - if let Some(password) = load_password_from_line(&line, host, port, username, database) { - return Some(password); + match load_password_from_line(&line, host, port, username, database) { + Err(err) => { + tracing::warn!(line = line, "Malformed line in pgpass file: {err}"); + } + Ok(Some(password)) => return Some(password), + Ok(None) => (), } } @@ -132,45 +142,57 @@ fn load_password_from_line( port: u16, username: &str, database: Option<&str>, -) -> Option { - let whole_line = line; - +) -> Result, PGPassLineParseError> { // Pgpass line ordering: hostname, port, database, username, password // See: https://www.postgresql.org/docs/9.3/libpq-pgpass.html - match line.trim_start().chars().next() { - None | Some('#') => None, - _ => { - matches_next_field(whole_line, &mut line, host)?; - matches_next_field(whole_line, &mut line, &port.to_string())?; - matches_next_field(whole_line, &mut line, database.unwrap_or_default())?; - matches_next_field(whole_line, &mut line, username)?; - Some(line.to_owned()) - } + + if let None | Some('#') = line.trim_end().chars().next() { + return Ok(None); + } + + let line_matches = matches_next_field(&mut line, host)? + && matches_next_field(&mut line, &port.to_string())? + && matches_next_field(&mut line, database.unwrap_or_default())? + && matches_next_field(&mut line, username)?; + + if !line_matches { + return Ok(None); } + + Ok(Some(unescape_password(line))) } -/// check if the next field matches the provided value -fn matches_next_field(whole_line: &str, line: &mut &str, value: &str) -> Option<()> { - let field = find_next_field(line); - match field { - Some(field) => { - if field == "*" || field == value { - Some(()) - } else { - None +/// Unescape occurrences of `:` and `\` in the given password’s. +fn unescape_password(password_escaped: &str) -> String { + let mut result = String::new(); + + let mut it = password_escaped.chars(); + while let Some(char) = it.next() { + if char != '\\' { + result.push(char); + } else if let Some(c) = it.next() { + if c != ':' && c != '\\' { + tracing::warn!("Superfluous escape in pgpass file"); } - } - None => { - tracing::warn!(line = whole_line, "Malformed line in pgpass file"); - None + result.push(c); + } else { + tracing::warn!("Superfluous escape at EOL in pgpass file"); } } + + result +} + +/// check if the next field matches the provided value +fn matches_next_field(line: &mut &str, value: &str) -> Result { + let field = find_next_field(line)?; + Ok(field == "*" || field == value) } /// extract the next value from a line in a pgpass file /// /// `line` will get updated to point behind the field and delimiter -fn find_next_field<'a>(line: &mut &'a str) -> Option> { +fn find_next_field<'a>(line: &mut &'a str) -> Result, PGPassLineParseError> { let mut escaping = false; let mut escaped_string = None; let mut last_added = 0; @@ -183,9 +205,9 @@ fn find_next_field<'a>(line: &mut &'a str) -> Option> { if let Some(mut escaped_string) = escaped_string { escaped_string += &field[last_added..]; - return Some(Cow::Owned(escaped_string)); + return Ok(Cow::Owned(escaped_string)); } else { - return Some(Cow::Borrowed(field)); + return Ok(Cow::Borrowed(field)); } } else if c == '\\' { let s = escaped_string.get_or_insert_with(String::new); @@ -199,66 +221,73 @@ fn find_next_field<'a>(line: &mut &'a str) -> Option> { escaping = !escaping; last_added = idx + 1; } else { + if escaping && c != '\\' && c != ':' { + tracing::warn!("Superfluous escape in in pgpass file"); + } escaping = false; } } - None + Err(PGPassLineParseError::UnexpectedEOL) } #[cfg(test)] mod tests { - use super::{find_next_field, load_password_from_line, load_password_from_reader}; + use super::*; use std::borrow::Cow; #[test] fn test_find_next_field() { - fn test_case<'a>(mut input: &'a str, result: Option>, rest: &str) { + fn test_case<'a>( + mut input: &'a str, + result: Result, PGPassLineParseError>, + rest: &str, + ) { assert_eq!(find_next_field(&mut input), result); assert_eq!(input, rest); } // normal field - test_case("foo:bar:baz", Some(Cow::Borrowed("foo")), "bar:baz"); + test_case("foo:bar:baz", Ok(Cow::Borrowed("foo")), "bar:baz"); // \ escaped test_case( "foo\\\\:bar:baz", - Some(Cow::Owned("foo\\".to_owned())), + Ok(Cow::Owned("foo\\".to_owned())), "bar:baz", ); // : escaped test_case( "foo\\::bar:baz", - Some(Cow::Owned("foo:".to_owned())), + Ok(Cow::Owned("foo:".to_owned())), "bar:baz", ); // unnecessary escape test_case( "foo\\a:bar:baz", - Some(Cow::Owned("fooa".to_owned())), + Ok(Cow::Owned("fooa".to_owned())), "bar:baz", ); // other text after escape test_case( "foo\\\\a:bar:baz", - Some(Cow::Owned("foo\\a".to_owned())), + Ok(Cow::Owned("foo\\a".to_owned())), "bar:baz", ); // double escape test_case( "foo\\\\\\\\a:bar:baz", - Some(Cow::Owned("foo\\\\a".to_owned())), + Ok(Cow::Owned("foo\\\\a".to_owned())), "bar:baz", ); // utf8 support - test_case("🦀:bar:baz", Some(Cow::Borrowed("🦀")), "bar:baz"); + test_case("🦀:bar:baz", Ok(Cow::Borrowed("🦀")), "bar:baz"); // missing delimiter (eof) - test_case("foo", None, "foo"); + test_case("foo", Err(PGPassLineParseError::UnexpectedEOL), "foo"); // missing delimiter after escape - test_case("foo\\:", None, "foo\\:"); + test_case("foo\\:", Err(PGPassLineParseError::UnexpectedEOL), "foo\\:"); // missing delimiter after unused trailing escape - test_case("foo\\", None, "foo\\"); + test_case("foo\\", Err(PGPassLineParseError::UnexpectedEOL), "foo\\"); } #[test] @@ -270,19 +299,19 @@ mod tests { "localhost", 5432, "foo", - Some("bar") + Some("bar"), ), - Some("baz".to_owned()) + Ok(Some("baz".to_owned())) ); // wildcard assert_eq!( load_password_from_line("*:5432:bar:foo:baz", "localhost", 5432, "foo", Some("bar")), - Some("baz".to_owned()) + Ok(Some("baz".to_owned())) ); // accept wildcard with missing db assert_eq!( load_password_from_line("localhost:5432:*:foo:baz", "localhost", 5432, "foo", None), - Some("baz".to_owned()) + Ok(Some("baz".to_owned())) ); // doesn't match @@ -294,7 +323,7 @@ mod tests { "foo", Some("bar") ), - None + Ok(None) ); // malformed entry assert_eq!( @@ -305,7 +334,32 @@ mod tests { "foo", Some("bar") ), - None + Err(PGPassLineParseError::UnexpectedEOL) + ); + // Password with trailing whitespace + assert_eq!( + load_password_from_line("*:*:*:*:baz ", "localhost", 5432, "foo", Some("bar")), + Ok(Some("baz ".to_owned())) + ); + // Password with escaped colon + assert_eq!( + load_password_from_line("*:*:*:*:ba\\:z", "localhost", 5432, "foo", Some("bar")), + Ok(Some("ba:z".to_owned())) + ); + // Password with escaped backslash + assert_eq!( + load_password_from_line("*:*:*:*:ba\\\\z", "localhost", 5432, "foo", Some("bar")), + Ok(Some("ba\\z".to_owned())) + ); + // Password with superfluous escape + assert_eq!( + load_password_from_line("*:*:*:*:ba\\z", "localhost", 5432, "foo", Some("bar")), + Ok(Some("baz".to_owned())) + ); + // Password with trailing escape + assert_eq!( + load_password_from_line("*:*:*:*:baz\\", "localhost", 5432, "foo", Some("bar")), + Ok(Some("baz".to_owned())) ); } diff --git a/sqlx-postgres/src/types/geometry/path.rs b/sqlx-postgres/src/types/geometry/path.rs index 4f99e7e983..858e8ddcb1 100644 --- a/sqlx-postgres/src/types/geometry/path.rs +++ b/sqlx-postgres/src/types/geometry/path.rs @@ -131,7 +131,7 @@ impl PgPath { .into()); } - if bytes.len() % BYTE_WIDTH * 2 != 0 { + if bytes.len() % (BYTE_WIDTH * 2) != 0 { return Err(format!( "data length not divisible by pairs of {BYTE_WIDTH}: {}", bytes.len() diff --git a/sqlx-postgres/src/types/geometry/polygon.rs b/sqlx-postgres/src/types/geometry/polygon.rs index 5ca97477ea..0932d468f2 100644 --- a/sqlx-postgres/src/types/geometry/polygon.rs +++ b/sqlx-postgres/src/types/geometry/polygon.rs @@ -128,7 +128,7 @@ impl PgPolygon { .into()); } - if bytes.len() % BYTE_WIDTH * 2 != 0 { + if bytes.len() % (BYTE_WIDTH * 2) != 0 { return Err(format!( "data length not divisible by pairs of {BYTE_WIDTH}: {}", bytes.len() diff --git a/sqlx-postgres/src/types/json.rs b/sqlx-postgres/src/types/json.rs index 32f886c781..d575c1df03 100644 --- a/sqlx-postgres/src/types/json.rs +++ b/sqlx-postgres/src/types/json.rs @@ -61,7 +61,7 @@ where fn encode_by_ref(&self, buf: &mut PgArgumentBuffer) -> Result { // we have a tiny amount of dynamic behavior depending if we are resolved to be JSON // instead of JSONB - buf.patch(|buf, ty: &PgTypeInfo| { + buf.patch_with(|buf, ty: &PgTypeInfo| { if *ty == PgTypeInfo::JSON || *ty == PgTypeInfo::JSON_ARRAY { buf[0] = b' '; } @@ -85,11 +85,12 @@ where let mut buf = value.as_bytes()?; if value.format() == PgValueFormat::Binary && value.type_info == PgTypeInfo::JSONB { - assert_eq!( - buf[0], 1, - "unsupported JSONB format version {}; please open an issue", - buf[0] - ); + // Check JSONB version byte - PostgreSQL currently only supports version 1 + if buf[0] != 1 { + return Err( + format!("unsupported JSONB format version {} (expected 1)", buf[0]).into(), + ); + } buf = &buf[1..]; } diff --git a/sqlx-postgres/src/types/record.rs b/sqlx-postgres/src/types/record.rs index a5410caadd..2ad2480066 100644 --- a/sqlx-postgres/src/types/record.rs +++ b/sqlx-postgres/src/types/record.rs @@ -41,13 +41,10 @@ impl<'a> PgRecordEncoder<'a> { { let ty = value.produces().unwrap_or_else(T::type_info); - match ty.0 { - // push a hole for this type ID - // to be filled in on query execution - PgType::DeclareWithName(name) => self.buf.patch_type_by_name(&name), - PgType::DeclareArrayOf(array) => self.buf.patch_array_type(array), - // write type id - pg_type => self.buf.extend(&pg_type.oid().0.to_be_bytes()), + if let Some(oid) = ty.oid() { + self.buf.extend(oid.0.to_be_bytes()) + } else { + self.buf.push_hole(ty); } self.buf.encode(value)?; diff --git a/sqlx-sqlite/Cargo.toml b/sqlx-sqlite/Cargo.toml index e1b4c7edc4..59bd247019 100644 --- a/sqlx-sqlite/Cargo.toml +++ b/sqlx-sqlite/Cargo.toml @@ -56,7 +56,7 @@ _unstable-docs = [ [dependencies.libsqlite3-sys] # See `sqlx-sqlite/src/lib.rs` for details. -version = ">=0.30.0, <0.37.0" +version = ">=0.30.1, <0.37.0" default-features = false features = [ "pkg-config", @@ -64,22 +64,22 @@ features = [ ] [dependencies] -futures-core = { version = "0.3.19", default-features = false } -futures-channel = { version = "0.3.19", default-features = false, features = ["sink", "alloc", "std"] } +futures-core = { version = "0.3.32", default-features = false } +futures-channel = { version = "0.3.32", default-features = false, features = ["sink", "alloc", "std"] } # used by the SQLite worker thread to block on the async mutex that locks the database handle -futures-executor = { version = "0.3.19" } +futures-executor = { version = "0.3.32" } futures-intrusive = "0.5.0" -futures-util = { version = "0.3.19", default-features = false, features = ["alloc", "sink"] } +futures-util = { version = "0.3.32", default-features = false, features = ["alloc", "sink"] } chrono = { workspace = true, optional = true } time = { workspace = true, optional = true } uuid = { workspace = true, optional = true } url = { version = "2.2.2" } -percent-encoding = "2.1.0" +percent-encoding = "2.3.0" form_urlencoded = "1.2.2" -flume = { version = "0.11.0", default-features = false, features = ["async"] } +flume = { version = "0.12.0", default-features = false, features = ["async"] } atoi = "2.0" @@ -88,8 +88,8 @@ tracing = { version = "0.1.37", features = ["log"] } thiserror.workspace = true -serde = { version = "1.0.145", features = ["derive"], optional = true } -regex = { version = "1.5.5", optional = true } +serde = { version = "1.0.219", features = ["derive"], optional = true } +regex = { version = "1.6.0", optional = true } [dependencies.sqlx-core] workspace = true diff --git a/sqlx-sqlite/src/connection/collation.rs b/sqlx-sqlite/src/connection/collation.rs index e7422138bc..229e780adf 100644 --- a/sqlx-sqlite/src/connection/collation.rs +++ b/sqlx-sqlite/src/connection/collation.rs @@ -3,7 +3,6 @@ use std::ffi::CString; use std::fmt::{self, Debug, Formatter}; use std::os::raw::{c_int, c_void}; use std::slice; -use std::str::from_utf8_unchecked; use std::sync::Arc; use libsqlite3_sys::{sqlite3_create_collation_v2, SQLITE_OK, SQLITE_UTF8}; @@ -137,15 +136,19 @@ where let right_len = usize::try_from(right_len) .unwrap_or_else(|_| panic!("right_len out of range: {right_len}")); + // SQLite explicitly documents that invalid UTF-8 may be passed into + // application-defined collating sequences. The safe `Fn(&str, &str)` + // signature exposed to users must never observe invalid UTF-8, so + // lossily coerce the raw bytes here. let s1 = { let c_slice = slice::from_raw_parts(left_ptr as *const u8, left_len); - from_utf8_unchecked(c_slice) + String::from_utf8_lossy(c_slice) }; let s2 = { let c_slice = slice::from_raw_parts(right_ptr as *const u8, right_len); - from_utf8_unchecked(c_slice) + String::from_utf8_lossy(c_slice) }; - let t = (*boxed_f)(s1, s2); + let t = (*boxed_f)(&s1, &s2); match t { Ordering::Less => -1, diff --git a/sqlx-sqlite/src/connection/explain.rs b/sqlx-sqlite/src/connection/explain.rs index edd65ece49..550f21557e 100644 --- a/sqlx-sqlite/src/connection/explain.rs +++ b/sqlx-sqlite/src/connection/explain.rs @@ -43,6 +43,7 @@ const OP_IDX_GE: &str = "IdxGE"; const OP_IDX_GT: &str = "IdxGT"; const OP_IDX_LE: &str = "IdxLE"; const OP_IDX_LT: &str = "IdxLT"; +const OP_IDX_ROWID: &str = "IdxRowid"; const OP_IF: &str = "If"; const OP_IF_NO_HOPE: &str = "IfNoHope"; const OP_IF_NOT: &str = "IfNot"; @@ -361,7 +362,9 @@ fn opcode_to_type(op: &str) -> DataType { OP_REAL => DataType::Float, OP_BLOB => DataType::Blob, OP_AND | OP_OR => DataType::Bool, - OP_NEWROWID | OP_ROWID | OP_COUNT | OP_INT64 | OP_INTEGER => DataType::Integer, + OP_NEWROWID | OP_IDX_ROWID | OP_ROWID | OP_COUNT | OP_INT64 | OP_INTEGER => { + DataType::Integer + } OP_STRING8 => DataType::Text, OP_COLUMN | _ => DataType::Null, } @@ -676,7 +679,7 @@ pub(super) fn explain( //nobranch if maybe not null let might_not_branch = match state.mem.r.get(&p1) { - Some(r_p1) => !matches!(r_p1.map_to_datatype(), DataType::Null), + Some(r_p1) => r_p1.map_to_nullable() != Some(false), _ => false, }; @@ -1379,7 +1382,8 @@ pub(super) fn explain( state.mem.r.insert(p2, RegDataType::Int(p1)); } - OP_BLOB | OP_COUNT | OP_REAL | OP_STRING8 | OP_ROWID | OP_NEWROWID => { + OP_BLOB | OP_COUNT | OP_REAL | OP_STRING8 | OP_ROWID | OP_IDX_ROWID + | OP_NEWROWID => { // r[p2] = state.mem.r.insert( p2, @@ -1778,3 +1782,63 @@ fn test_root_block_columns_has_types() { ); } } + +#[test] +fn test_explain() { + use crate::SqliteConnectOptions; + use std::str::FromStr; + let conn_options = SqliteConnectOptions::from_str("sqlite::memory:").unwrap(); + let mut conn = super::EstablishParams::from_options(&conn_options) + .unwrap() + .establish() + .unwrap(); + + assert!(execute::iter( + &mut conn, + r"CREATE TABLE an_alias(a INTEGER PRIMARY KEY);", + None, + false + ) + .unwrap() + .next() + .is_some()); + + assert!(execute::iter( + &mut conn, + r"CREATE TABLE not_an_alias(a INT PRIMARY KEY);", + None, + false + ) + .unwrap() + .next() + .is_some()); + + assert!( + if let Ok((ty, nullable)) = explain(&mut conn, "SELECT * FROM an_alias") { + ty == [SqliteTypeInfo(DataType::Integer)] && nullable == [Some(false)] + } else { + false + } + ); + assert!( + if let Ok((ty, nullable)) = explain(&mut conn, "SELECT * FROM not_an_alias") { + ty == [SqliteTypeInfo(DataType::Integer)] && nullable == [Some(true)] + } else { + false + } + ); + assert!( + if let Ok((ty, nullable)) = explain(&mut conn, "SELECT rowid FROM an_alias") { + ty == [SqliteTypeInfo(DataType::Integer)] && nullable == [Some(false)] + } else { + false + } + ); + assert!( + if let Ok((ty, nullable)) = explain(&mut conn, "SELECT rowid FROM not_an_alias") { + ty == [SqliteTypeInfo(DataType::Integer)] && nullable == [Some(false)] + } else { + false + } + ); +} diff --git a/sqlx-sqlite/src/error.rs b/sqlx-sqlite/src/error.rs index b4373d7a07..5510e25d97 100644 --- a/sqlx-sqlite/src/error.rs +++ b/sqlx-sqlite/src/error.rs @@ -39,7 +39,7 @@ impl SqliteError { let msg = sqlite3_errmsg(handle); debug_assert!(!msg.is_null()); - str::from_utf8_unchecked(CStr::from_ptr(msg).to_bytes()).to_owned() + String::from_utf8_lossy(CStr::from_ptr(msg).to_bytes()).into_owned() }; Some(Self { diff --git a/sqlx-sqlite/src/migrate.rs b/sqlx-sqlite/src/migrate.rs index dd9611b873..5864023891 100644 --- a/sqlx-sqlite/src/migrate.rs +++ b/sqlx-sqlite/src/migrate.rs @@ -221,6 +221,29 @@ CREATE TABLE IF NOT EXISTS {table_name} ( Ok(elapsed) }) } + + fn skip<'e>( + &'e mut self, + table_name: &'e str, + migration: &'e Migration, + ) -> BoxFuture<'e, Result<(), MigrateError>> { + Box::pin(async move { + // language=SQLite + let _ = query(AssertSqlSafe(format!( + r#" + INSERT INTO {table_name} ( version, description, success, checksum, execution_time ) + VALUES ( ?1, ?2, TRUE, ?3, -1 ) + "# + ))) + .bind(migration.version) + .bind(&*migration.description) + .bind(&*migration.checksum) + .execute(self) + .await?; + + Ok(()) + }) + } } async fn execute_migration( diff --git a/sqlx-sqlite/src/options/mod.rs b/sqlx-sqlite/src/options/mod.rs index b2849f243c..0cafc4d08c 100644 --- a/sqlx-sqlite/src/options/mod.rs +++ b/sqlx-sqlite/src/options/mod.rs @@ -511,7 +511,7 @@ impl SqliteConnectOptions { /// .extension("vsv") /// .extension("mod_spatialite"); /// } - /// + /// /// # Ok(options) /// # } /// ``` @@ -646,7 +646,15 @@ impl SqliteConnectOptions { #[cfg(feature = "load-extension")] for extension in &config.unsafe_load_extensions { // SAFETY: the documentation warns the user about loading extensions - self = unsafe { self.extension(extension.clone()) }; + match extension { + config::drivers::SqliteExtension::Path(path) => { + self = unsafe { self.extension(path.clone()) } + } + config::drivers::SqliteExtension::PathWithEntrypoint { path, entrypoint } => { + self = + unsafe { self.extension_with_entrypoint(path.clone(), entrypoint.clone()) } + } + } } #[cfg(not(feature = "load-extension"))] diff --git a/sqlx-sqlite/src/regexp.rs b/sqlx-sqlite/src/regexp.rs index eb14fffc77..b525992d9d 100644 --- a/sqlx-sqlite/src/regexp.rs +++ b/sqlx-sqlite/src/regexp.rs @@ -136,8 +136,11 @@ unsafe fn get_regex_from_arg( Some(regex) } -/// Get a text reference of the value of `arg`. If this value is not a string value, an error is printed and `None` is -/// returned. +/// Get a text reference of the value of `arg`. Returns `None` for NULL values. +/// +/// For non-NULL values, `sqlite3_value_text()` is called directly, which lets SQLite +/// coerce INTEGER, REAL, and BLOB values to their text representation. This matches +/// the coercion behavior documented at . /// /// The returned `&str` is valid for lifetime `'a` which can be determined by the caller. This lifetime should **not** /// outlive `ctx`. @@ -146,20 +149,19 @@ unsafe fn get_text_from_arg<'a>( arg: *mut ffi::sqlite3_value, ) -> Option<&'a str> { let ty = ffi::sqlite3_value_type(arg); - if ty == ffi::SQLITE_TEXT { - let ptr = ffi::sqlite3_value_text(arg); - let len = ffi::sqlite3_value_bytes(arg); - let slice = std::slice::from_raw_parts(ptr.cast(), len as usize); - match std::str::from_utf8(slice) { - Ok(result) => Some(result), - Err(e) => { - log::error!("Incoming text is not valid UTF8: {e:?}"); - ffi::sqlite3_result_error_code(ctx, ffi::SQLITE_CONSTRAINT_FUNCTION); - None - } + if ty == ffi::SQLITE_NULL { + return None; + } + let ptr = ffi::sqlite3_value_text(arg); + let len = ffi::sqlite3_value_bytes(arg); + let slice = std::slice::from_raw_parts(ptr.cast(), len as usize); + match std::str::from_utf8(slice) { + Ok(result) => Some(result), + Err(e) => { + log::error!("Incoming text is not valid UTF8: {e:?}"); + ffi::sqlite3_result_error_code(ctx, ffi::SQLITE_CONSTRAINT_FUNCTION); + None } - } else { - None } } @@ -222,6 +224,52 @@ mod tests { assert!(result.is_empty()); } + #[sqlx::test] + async fn test_regexp_coerces_non_text_values() { + let mut conn = crate::SqliteConnectOptions::from_str("sqlite://:memory:") + .unwrap() + .with_regexp() + .connect() + .await + .unwrap(); + + // INTEGER coercion + let result: Option = sqlx::query_scalar("SELECT 123 REGEXP '23'") + .fetch_one(&mut conn) + .await + .unwrap(); + assert_eq!(result, Some(1)); + + // REAL coercion + let result: Option = sqlx::query_scalar("SELECT 12.5 REGEXP '12\\.5'") + .fetch_one(&mut conn) + .await + .unwrap(); + assert_eq!(result, Some(1)); + + // INTEGER column + sqlx::query("CREATE TABLE int_test (x INTEGER NOT NULL)") + .execute(&mut conn) + .await + .unwrap(); + sqlx::query("INSERT INTO int_test VALUES (123), (45)") + .execute(&mut conn) + .await + .unwrap(); + let rows: Vec = sqlx::query_scalar("SELECT x FROM int_test WHERE x REGEXP '23'") + .fetch_all(&mut conn) + .await + .unwrap(); + assert_eq!(rows, vec![123]); + + // NULL should return NULL, not match + let result: Option = sqlx::query_scalar("SELECT NULL REGEXP '.*'") + .fetch_one(&mut conn) + .await + .unwrap(); + assert_eq!(result, None); + } + #[sqlx::test] async fn test_invalid_regexp_should_fail() { let mut conn = test_db().await; diff --git a/sqlx-sqlite/src/statement/handle.rs b/sqlx-sqlite/src/statement/handle.rs index 7985ff9d36..c78ce98414 100644 --- a/sqlx-sqlite/src/statement/handle.rs +++ b/sqlx-sqlite/src/statement/handle.rs @@ -21,7 +21,7 @@ use std::os::raw::{c_char, c_int}; use std::ptr; use std::ptr::NonNull; use std::slice::from_raw_parts; -use std::str::{from_utf8, from_utf8_unchecked}; +use std::str::from_utf8; use std::sync::Arc; #[derive(Debug)] @@ -77,7 +77,8 @@ impl StatementHandle { let raw = sqlite3_sql(self.0.as_ptr()); debug_assert!(!raw.is_null()); - from_utf8_unchecked(CStr::from_ptr(raw).to_bytes()) + from_utf8(CStr::from_ptr(raw).to_bytes()) + .expect("sqlite3_sql() returned non-UTF-8 string") } } @@ -107,7 +108,8 @@ impl StatementHandle { let name = sqlite3_column_name(self.0.as_ptr(), check_col_idx!(index)); debug_assert!(!name.is_null()); - from_utf8_unchecked(CStr::from_ptr(name).to_bytes()) + from_utf8(CStr::from_ptr(name).to_bytes()) + .expect("sqlite3_column_name() returned non-UTF-8 column name") } } @@ -139,7 +141,10 @@ impl StatementHandle { let db_name = sqlite3_column_database_name(self.0.as_ptr(), check_col_idx!(index)); if !db_name.is_null() { - Some(from_utf8_unchecked(CStr::from_ptr(db_name).to_bytes())) + Some( + from_utf8(CStr::from_ptr(db_name).to_bytes()) + .expect("sqlite3_column_database_name() returned non-UTF-8 string"), + ) } else { None } @@ -151,7 +156,10 @@ impl StatementHandle { let table_name = sqlite3_column_table_name(self.0.as_ptr(), check_col_idx!(index)); if !table_name.is_null() { - Some(from_utf8_unchecked(CStr::from_ptr(table_name).to_bytes())) + Some( + from_utf8(CStr::from_ptr(table_name).to_bytes()) + .expect("sqlite3_column_table_name() returned non-UTF-8 string"), + ) } else { None } @@ -163,7 +171,10 @@ impl StatementHandle { let origin_name = sqlite3_column_origin_name(self.0.as_ptr(), check_col_idx!(index)); if !origin_name.is_null() { - Some(from_utf8_unchecked(CStr::from_ptr(origin_name).to_bytes())) + Some( + from_utf8(CStr::from_ptr(origin_name).to_bytes()) + .expect("sqlite3_column_origin_name() returned non-UTF-8 string"), + ) } else { None } @@ -191,17 +202,23 @@ impl StatementHandle { return None; } - let decl = from_utf8_unchecked(CStr::from_ptr(decl).to_bytes()); + let decl = from_utf8(CStr::from_ptr(decl).to_bytes()) + .expect("sqlite3_column_decltype() returned non-UTF-8 string"); let ty: DataType = decl.parse().ok()?; Some(SqliteTypeInfo(ty)) } } + /// Use sqlite3_column_metadata to determine if a specific column is nullable. + /// + /// Returns None in the case of INTEGER PRIMARY KEYs + /// This is because this column is an alias to rowid if the table does not use a compound + /// primary key. In this case the row is not nullable, and the output of + /// sqlite3_column_metadata may be incorrect. pub(crate) fn column_nullable(&self, index: usize) -> Result, Error> { unsafe { let index = check_col_idx!(index); - // https://sqlite.org/c3ref/column_database_name.html // // ### Note @@ -212,12 +229,13 @@ impl StatementHandle { let db_name = sqlite3_column_database_name(self.0.as_ptr(), index); let table_name = sqlite3_column_table_name(self.0.as_ptr(), index); let origin_name = sqlite3_column_origin_name(self.0.as_ptr(), index); - if db_name.is_null() || table_name.is_null() || origin_name.is_null() { return Ok(None); } let mut not_null: c_int = 0; + let mut datatype: *const c_char = ptr::null(); + let mut primary_key: c_int = 0; // https://sqlite.org/c3ref/table_column_metadata.html let status = sqlite3_table_column_metadata( @@ -225,11 +243,11 @@ impl StatementHandle { db_name, table_name, origin_name, + &mut datatype, // function docs state to provide NULL for return values you don't care about ptr::null_mut(), - ptr::null_mut(), &mut not_null, - ptr::null_mut(), + &mut primary_key, ptr::null_mut(), ); @@ -245,7 +263,19 @@ impl StatementHandle { return Err(SqliteError::new(self.db_handle()).into()); } - Ok(Some(not_null == 0)) + let datatype = CStr::from_ptr(datatype); + + Ok( + if primary_key != 0 + && datatype + .to_bytes() + .eq_ignore_ascii_case("integer".as_bytes()) + { + None + } else { + Some(not_null == 0) + }, + ) } } @@ -267,7 +297,10 @@ impl StatementHandle { return None; } - Some(from_utf8_unchecked(CStr::from_ptr(name).to_bytes())) + Some( + from_utf8(CStr::from_ptr(name).to_bytes()) + .expect("sqlite3_bind_parameter_name() returned non-UTF-8 string"), + ) } } diff --git a/sqlx-test/Cargo.toml b/sqlx-test/Cargo.toml index 32a341adcb..47a17ca0fb 100644 --- a/sqlx-test/Cargo.toml +++ b/sqlx-test/Cargo.toml @@ -8,8 +8,8 @@ rust-version.workspace = true [dependencies] sqlx = { default-features = false, path = ".." } env_logger = "0.11" -dotenvy = "0.15.0" -anyhow = "1.0.26" +dotenvy = "0.15.7" +anyhow = "1.0.58" [lints] workspace = true 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 0069dbf99d..bc49804de2 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 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 a6fc025a8a..ce3eb05511 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/mssql/migrate.rs b/tests/mssql/migrate.rs index b76a2a4eb1..dedb7e64a5 100644 --- a/tests/mssql/migrate.rs +++ b/tests/mssql/migrate.rs @@ -66,6 +66,51 @@ async fn reversible(mut conn: PoolConnection) -> anyhow::Result<()> { Ok(()) } +#[sqlx::test(migrations = false)] +async fn skip(mut conn: PoolConnection) -> anyhow::Result<()> { + clean_up(&mut conn).await?; + let migrator = Migrator::new(Path::new("tests/mssql/migrations_reversible")).await?; + + // get to the state of after the first migration manually + let sql = include_str!("migrations_reversible/20220721124650_add_table.up.sql"); + let statements: Vec<&str> = sql.split(';').filter(|s| !s.trim().is_empty()).collect(); + for statement in statements { + conn.execute(statement).await?; + } + + // skip first migration + migrator.skip(&mut conn, Some(20220721124650)).await?; + + // check outcome + let res: i64 = conn + .fetch_one("SELECT some_payload FROM migrations_reversible_test") + .await? + .get(0); + assert_eq!(res, 100); + + // run remaining migration + migrator.run(&mut conn).await?; + + // check outcome + let res: i64 = conn + .fetch_one("SELECT some_payload FROM migrations_reversible_test") + .await? + .get(0); + assert_eq!(res, 101); + + // roll back one version + migrator.undo(&mut conn, 20220721124650).await?; + + // check outcome + let res: i64 = conn + .fetch_one("SELECT some_payload FROM migrations_reversible_test") + .await? + .get(0); + assert_eq!(res, 100); + + Ok(()) +} + /// Ensure that we have a clean initial state. async fn clean_up(conn: &mut MssqlConnection) -> anyhow::Result<()> { conn.execute( 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/mysql/migrate.rs b/tests/mysql/migrate.rs index 97caa38005..4a0759c81c 100644 --- a/tests/mysql/migrate.rs +++ b/tests/mysql/migrate.rs @@ -66,6 +66,51 @@ async fn reversible(mut conn: PoolConnection) -> anyhow::Result<()> { Ok(()) } +#[sqlx::test(migrations = false)] +async fn skip(mut conn: PoolConnection) -> anyhow::Result<()> { + clean_up(&mut conn).await?; + let migrator = Migrator::new(Path::new("tests/mysql/migrations_reversible")).await?; + + // get to the state of after the first migration manually + let sql = include_str!("migrations_reversible/20220721124650_add_table.up.sql"); + let statements: Vec<&str> = sql.split(';').filter(|s| !s.trim().is_empty()).collect(); + for statement in statements { + conn.execute(statement).await?; + } + + // skip first migration + migrator.skip(&mut conn, Some(20220721124650)).await?; + + // check outcome + let res: i64 = conn + .fetch_one("SELECT some_payload FROM migrations_reversible_test") + .await? + .get(0); + assert_eq!(res, 100); + + // run remaining migration + migrator.run(&mut conn).await?; + + // check outcome + let res: i64 = conn + .fetch_one("SELECT some_payload FROM migrations_reversible_test") + .await? + .get(0); + assert_eq!(res, 101); + + // roll back one version + migrator.undo(&mut conn, 20220721124650).await?; + + // check outcome + let res: i64 = conn + .fetch_one("SELECT some_payload FROM migrations_reversible_test") + .await? + .get(0); + assert_eq!(res, 100); + + Ok(()) +} + /// Ensure that we have a clean initial state. async fn clean_up(conn: &mut MySqlConnection) -> anyhow::Result<()> { conn.execute("DROP TABLE migrations_simple_test").await.ok(); diff --git a/tests/mysql/mysql.rs b/tests/mysql/mysql.rs index 5d6a5ef233..5374e651c8 100644 --- a/tests/mysql/mysql.rs +++ b/tests/mysql/mysql.rs @@ -3,6 +3,7 @@ use futures_util::TryStreamExt; use sqlx::mysql::{MySql, MySqlConnection, MySqlPool, MySqlPoolOptions, MySqlRow}; use sqlx::{Column, Connection, Executor, Row, SqlSafeStr, Statement, TypeInfo}; use sqlx_core::connection::ConnectOptions; +use sqlx_core::types::Type; use sqlx_mysql::MySqlConnectOptions; use sqlx_test::{new, setup_if_needed}; use std::env; @@ -636,3 +637,93 @@ async fn issue_3200() -> anyhow::Result<()> { Ok(()) } + +#[cfg(mariadb)] +#[sqlx_macros::test] +async fn it_can_name_columns_issue_2206() -> anyhow::Result<()> { + let mut conn = new::().await?; + + sqlx::raw_sql( + "\ + CREATE TABLE IF NOT EXISTS issue_2206 + ( + `id` BIGINT AUTO_INCREMENT, + `name` VARCHAR(128) NOT NULL, + PRIMARY KEY (id) + ); + ", + ) + .execute(&mut conn) + .await?; + + let row = sqlx::query("INSERT INTO issue_2206 (name) VALUES (?) RETURNING *") + .bind("Alice") + .fetch_one(&mut conn) + .await?; + let _id: i64 = row.get("id"); + let name: String = row.get("name"); + + assert_eq!(&name, "Alice"); + + Ok(()) +} + +#[cfg(feature = "any")] +#[sqlx_macros::test] +async fn any_blob_conversions() -> anyhow::Result<()> { + use sqlx::Any; + use sqlx::Decode; + use sqlx::ValueRef; + + sqlx::any::install_default_drivers(); + + let mut conn = new::().await?; + + sqlx::raw_sql( + r#" + CREATE TEMPORARY TABLE any_blob_conversions( + id INTEGER PRIMARY KEY, + regular_text TEXT NOT NULL, + text_binary TEXT COLLATE "utf8mb4_bin" NOT NULL, + binary_blob BLOB NOT NULL + ); + + INSERT INTO any_blob_conversions(id, regular_text, text_binary, binary_blob) + VALUES (1, 'Hello, world!', 'Lorem ipsum dolor sit amet', X'01020304DEADBEEF'); + "#, + ) + .execute(&mut conn) + .await?; + + let row = sqlx::query("SELECT * FROM any_blob_conversions") + .fetch_one(&mut conn) + .await?; + + let id = row.try_get_raw("id")?; + let regular_text = row.try_get_raw("regular_text")?; + let text_binary = row.try_get_raw("text_binary")?; + let binary_blob = row.try_get_raw("binary_blob")?; + + assert_eq!(*id.type_info(), >::type_info()); + assert_eq!(>::decode(id).unwrap(), 1); + + assert_eq!(*regular_text.type_info(), >::type_info()); + assert_eq!( + <&str as Decode>::decode(regular_text).unwrap(), + "Hello, world!" + ); + + assert_eq!(*text_binary.type_info(), >::type_info()); + assert_eq!( + <&str as Decode>::decode(text_binary).unwrap(), + "Lorem ipsum dolor sit amet" + ); + + assert_eq!(*binary_blob.type_info(), <[u8] as Type>::type_info()); + assert_eq!( + <&[u8] as Decode>::decode(binary_blob).unwrap(), + [0x01, 0x02, 0x03, 0x04, 0xDE, 0xAD, 0xBE, 0xEF], + ); + + Ok(()) +} diff --git a/tests/postgres/migrate.rs b/tests/postgres/migrate.rs index 636dffe860..c08e24c731 100644 --- a/tests/postgres/migrate.rs +++ b/tests/postgres/migrate.rs @@ -66,6 +66,51 @@ async fn reversible(mut conn: PoolConnection) -> anyhow::Result<()> { Ok(()) } +#[sqlx::test(migrations = false)] +async fn skip(mut conn: PoolConnection) -> anyhow::Result<()> { + clean_up(&mut conn).await?; + let migrator = Migrator::new(Path::new("tests/postgres/migrations_reversible")).await?; + + // get to the state of after the first migration manually + let sql = include_str!("migrations_reversible/20220721124650_add_table.up.sql"); + let statements: Vec<&str> = sql.split(';').filter(|s| !s.trim().is_empty()).collect(); + for statement in statements { + conn.execute(statement).await?; + } + + // skip first migration + migrator.skip(&mut conn, Some(20220721124650)).await?; + + // check outcome + let res: i64 = conn + .fetch_one("SELECT some_payload FROM migrations_reversible_test") + .await? + .get(0); + assert_eq!(res, 100); + + // run remaining migration + migrator.run(&mut conn).await?; + + // check outcome + let res: i64 = conn + .fetch_one("SELECT some_payload FROM migrations_reversible_test") + .await? + .get(0); + assert_eq!(res, 101); + + // roll back one version + migrator.undo(&mut conn, 20220721124650).await?; + + // check outcome + let res: i64 = conn + .fetch_one("SELECT some_payload FROM migrations_reversible_test") + .await? + .get(0); + assert_eq!(res, 100); + + Ok(()) +} + #[sqlx::test(migrations = false)] async fn no_tx(mut conn: PoolConnection) -> anyhow::Result<()> { clean_up(&mut conn).await?; diff --git a/tests/postgres/postgres.rs b/tests/postgres/postgres.rs index 06adf0ca7f..126771565a 100644 --- a/tests/postgres/postgres.rs +++ b/tests/postgres/postgres.rs @@ -2086,6 +2086,7 @@ async fn test_issue_3052() { } #[sqlx_macros::test] +#[cfg(feature = "chrono")] async fn test_bind_iter() -> anyhow::Result<()> { use sqlx::postgres::PgBindIterExt; use sqlx::types::chrono::{DateTime, Utc}; diff --git a/tests/sqlite/describe.rs b/tests/sqlite/describe.rs index 4c0768a5e2..49bdbd35e7 100644 --- a/tests/sqlite/describe.rs +++ b/tests/sqlite/describe.rs @@ -591,6 +591,50 @@ async fn it_describes_table_order_by() -> anyhow::Result<()> { Ok(()) } +// Regression test for https://github.com/launchbadge/sqlx/issues/4147 +// ORDER BY + LIMIT routes data through an ephemeral sorter table; +// the NOT NULL constraint must survive the round-trip. +#[sqlx_macros::test] +async fn it_describes_order_by_with_limit() -> anyhow::Result<()> { + let mut conn = new::().await?; + + let info = conn + .describe("SELECT text FROM tweet ORDER BY id DESC LIMIT 10".into_sql_str()) + .await?; + assert_eq!(info.column(0).type_info().name(), "TEXT"); + assert_eq!( + info.nullable(0), + Some(false), + "NOT NULL column should stay NOT NULL with ORDER BY + LIMIT" + ); + + let info = conn + .describe("SELECT text, is_sent FROM tweet ORDER BY id DESC LIMIT 10000".into_sql_str()) + .await?; + assert_eq!( + info.nullable(0), + Some(false), + "text should be NOT NULL with ORDER BY DESC + large LIMIT" + ); + assert_eq!( + info.nullable(1), + Some(false), + "is_sent should be NOT NULL with ORDER BY DESC + large LIMIT" + ); + + // nullable column must remain nullable + let info = conn + .describe("SELECT owner_id FROM tweet ORDER BY id DESC LIMIT 10".into_sql_str()) + .await?; + assert_eq!( + info.nullable(0), + Some(true), + "nullable column should stay nullable with ORDER BY + LIMIT" + ); + + Ok(()) +} + #[sqlx_macros::test] async fn it_describes_union() -> anyhow::Result<()> { async fn assert_union_described( diff --git a/tests/sqlite/migrate.rs b/tests/sqlite/migrate.rs index ed5835225c..9b5dc38bfa 100644 --- a/tests/sqlite/migrate.rs +++ b/tests/sqlite/migrate.rs @@ -66,6 +66,51 @@ async fn reversible(mut conn: PoolConnection) -> anyhow::Result<()> { Ok(()) } +#[sqlx::test(migrations = false)] +async fn skip(mut conn: PoolConnection) -> anyhow::Result<()> { + clean_up(&mut conn).await?; + let migrator = Migrator::new(Path::new("tests/sqlite/migrations_reversible")).await?; + + // get to the state of after the first migration manually + let sql = include_str!("migrations_reversible/20220721124650_add_table.up.sql"); + let statements: Vec<&str> = sql.split(';').filter(|s| !s.trim().is_empty()).collect(); + for statement in statements { + conn.execute(statement).await?; + } + + // skip first migration + migrator.skip(&mut conn, Some(20220721124650)).await?; + + // check outcome + let res: i64 = conn + .fetch_one("SELECT some_payload FROM migrations_reversible_test") + .await? + .get(0); + assert_eq!(res, 100); + + // run remaining migration + migrator.run(&mut conn).await?; + + // check outcome + let res: i64 = conn + .fetch_one("SELECT some_payload FROM migrations_reversible_test") + .await? + .get(0); + assert_eq!(res, 101); + + // roll back one version + migrator.undo(&mut conn, 20220721124650).await?; + + // check outcome + let res: i64 = conn + .fetch_one("SELECT some_payload FROM migrations_reversible_test") + .await? + .get(0); + assert_eq!(res, 100); + + Ok(()) +} + #[sqlx::test(migrations = false)] async fn no_tx(mut conn: PoolConnection) -> anyhow::Result<()> { clean_up(&mut conn).await?; 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