diff --git a/.cargo/config.toml b/.cargo/config.toml new file mode 100644 index 00000000..2a8b890d --- /dev/null +++ b/.cargo/config.toml @@ -0,0 +1,2 @@ +[env] +LIBSQLITE3_FLAGS = { value = "-DSQLITE_ENABLE_MATH_FUNCTIONS", force = true } diff --git a/.github/workflows/cargo-publish.yaml b/.github/workflows/cargo-publish.yaml index 92947a86..8d19a76e 100644 --- a/.github/workflows/cargo-publish.yaml +++ b/.github/workflows/cargo-publish.yaml @@ -14,7 +14,7 @@ jobs: uses: actions/checkout@v3 - name: Setup toolchain - - uses: actions-rs/toolchain@v1 + uses: actions-rs/toolchain@v1 with: toolchain: stable diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index ad75d79f..704fd971 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -6,7 +6,8 @@ jobs: name: CI is green runs-on: ubuntu-latest needs: - - cargo-fmt-clippy-and-test-macros-and-cli + - cargo-fmt-clippy + - test-macros-and-cli - test-sqlite - test-postgres - test-tokio-postgres @@ -17,35 +18,59 @@ jobs: steps: - run: exit 0 - cargo-fmt-clippy-and-test-macros-and-cli: - name: Cargo fmt/clippy/test-macros-and-cli - runs-on: ${{ matrix.os }} - strategy: - matrix: - os: [windows-latest, ubuntu-latest] - rust: [stable, nightly, 1.56.0] + set-rust-versions: + runs-on: ubuntu-latest + outputs: + versions: ${{ steps.set-versions.outputs.versions }} + steps: + - name: checkout repo + uses: actions/checkout@v2 + - id: set-versions + run: | + MSRV=$(grep -oP 'rust-version\s*=\s*"\K[^"]+' ./Cargo.toml) + echo "versions=['stable', 'nightly', '$MSRV']" >> $GITHUB_OUTPUT + + cargo-fmt-clippy: + name: Cargo fmt and clippy + runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 - uses: actions-rs/toolchain@v1 with: - toolchain: ${{ matrix.rust }} + toolchain: beta - run: rustup self update - run: rustup component add clippy - run: rustup component add rustfmt - run: cargo fmt --all -- --check + - run: cargo clippy --all-targets --all-features + + test-macros-and-cli: + name: test-macros-and-cli + needs: set-rust-versions + strategy: + matrix: + rust: ${{ fromJson(needs.set-rust-versions.outputs.versions) }} + os: [windows-latest, ubuntu-latest] + + runs-on: ${{ matrix.os }} + steps: + - uses: actions/checkout@v2 + - uses: actions-rs/toolchain@v1 + with: + toolchain: ${{ matrix.rust }} + - run: rustup self update - run: cd refinery_core && cargo test --all-features -- --test-threads 1 - run: cd refinery && cargo build --all-features - - run: cd refinery_macros && cargo clippy - - run: cd refinery_cli && cargo clippy - - run: cd refinery_macros && cargo test + - run: cd refinery_macros && cargo test --all-features - run: cd refinery_cli && cargo test test-sqlite: name: Test Sqlite + needs: set-rust-versions runs-on: ubuntu-latest strategy: - matrix: - rust: [stable, nightly, 1.65.0] + matrix: + rust: ${{ fromJson(needs.set-rust-versions.outputs.versions) }} steps: - uses: actions/checkout@v2 - uses: actions-rs/toolchain@v1 @@ -56,10 +81,11 @@ jobs: test-postgres: name: Test postgres + needs: set-rust-versions runs-on: ubuntu-latest strategy: - matrix: - rust: [stable, nightly, 1.56.0] + matrix: + rust: ${{ fromJson(needs.set-rust-versions.outputs.versions) }} services: postgres: image: postgres:9.6.13-alpine @@ -71,14 +97,16 @@ jobs: with: toolchain: ${{ matrix.rust }} - run: cargo install --path ./refinery_cli --no-default-features --features=postgresql - - run: cd refinery && cargo test --features postgres --test postgres -- --test-threads 1 + - run: cd refinery && cargo test --features postgres-tls --test postgres -- --test-threads 1 + - run: cd refinery && cargo test --features postgres-tls,int8-versions --test postgres -- --test-threads 1 test-tokio-postgres: name: Test tokio-postgres - runs-on: ubuntu-latest + needs: set-rust-versions strategy: - matrix: - rust: [stable, nightly, 1.56.0] + matrix: + rust: ${{ fromJson(needs.set-rust-versions.outputs.versions) }} + runs-on: ubuntu-latest services: postgres: image: postgres:9.6.13-alpine @@ -89,14 +117,16 @@ jobs: - uses: actions-rs/toolchain@v1 with: toolchain: ${{ matrix.rust }} - - run: cd refinery && cargo test --features tokio-postgres --test tokio_postgres -- --test-threads 1 + - run: cd refinery && cargo test --features tokio-postgres-tls --test tokio_postgres -- --test-threads 1 + - run: cd refinery && cargo test --features tokio-postgres-rustls --test tokio_postgres -- --test-threads 1 test-mysql: name: Test mysql + needs: set-rust-versions runs-on: ubuntu-latest strategy: - matrix: - rust: [stable, nightly, 1.56.0] + matrix: + rust: ${{ fromJson(needs.set-rust-versions.outputs.versions) }} services: postgres: image: mysql:latest @@ -117,10 +147,11 @@ jobs: test-mysql-async: name: Test mysql-async + needs: set-rust-versions runs-on: ubuntu-latest strategy: - matrix: - rust: [stable, nightly, 1.56.0] + matrix: + rust: ${{ fromJson(needs.set-rust-versions.outputs.versions) }} services: postgres: image: mysql:latest @@ -140,20 +171,17 @@ jobs: test-tiberius: name: Test tiberius + needs: set-rust-versions runs-on: ubuntu-latest strategy: - matrix: - rust: [stable, nightly, 1.56.0] - services: - mssql: - image: mcr.microsoft.com/mssql/server:2017-latest - ports: - - 1433:1433 - env: - ACCEPT_EULA: yes - SA_PASSWORD: Passw0rd + matrix: + rust: ${{ fromJson(needs.set-rust-versions.outputs.versions) }} steps: - uses: actions/checkout@v2 + - uses: potatoqualitee/mssqlsuite@v1.8 + with: + install: sqlengine, sqlpackage + sa-password: Passw0rd - uses: actions-rs/toolchain@v1 with: toolchain: ${{ matrix.rust }} @@ -173,17 +201,10 @@ jobs: toolchain: stable - run: cd refinery && cargo rustdoc --all-features -- -D rustdoc::broken_intra_doc_links - audit: - name: cargo-audit + cargo-deny: runs-on: ubuntu-latest steps: - - name: Checkout - uses: actions/checkout@v1 - - - name: Install Rust - # actions-rs/audit seems to be unmaintained and it's output doesn't give interverted tree dependencies - uses: actions-rs/toolchain@v1 - with: - toolchain: stable - - run: cargo install cargo-audit - - run: cargo audit + - uses: actions/checkout@v5 + - uses: EmbarkStudios/cargo-deny-action@v2 + with: + command: check advisories bans licenses sources diff --git a/.github/workflows/stale.yml b/.github/workflows/stale.yml new file mode 100644 index 00000000..71c2556c --- /dev/null +++ b/.github/workflows/stale.yml @@ -0,0 +1,17 @@ +name: Close stale issues + +on: + schedule: + - cron: '0 0 * * *' # runs daily at midnight + +jobs: + stale: + runs-on: ubuntu-latest + steps: + - uses: actions/stale@v10 + with: + days-before-stale: 30 + days-before-close: 7 + stale-issue-message: 'This issue has been automatically marked as stale due to inactivity.' + close-issue-message: 'Closing this issue due to prolonged inactivity.' + exempt-issue-labels: 'keep-open' diff --git a/.gitignore b/.gitignore index b1954ea8..afe783d8 100644 --- a/.gitignore +++ b/.gitignore @@ -8,4 +8,3 @@ # This is refinery specific things Refinery.toml target/ -Cargo.lock diff --git a/CHANGELOG.md b/CHANGELOG.md index ec482b65..8b52454c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,9 +4,85 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). -## [0.8.12] - 2024-01-22 +## [0.9.2] - 2026-06-09 +### Added +- Support for `rustls` as a TLS backend for `tokio-postgres` via the new `tokio-postgres-rustls` feature. [#426](https://github.com/rust-db/refinery/pull/426) +### Changed +- Make TLS opt-in for `postgres` and `tokio-postgres` features. The `postgres` and `tokio-postgres` features no longer pull in `native-tls` unconditionally. Use `postgres-tls` and `tokio-postgres-tls` features for TLS support. [#408](https://github.com/rust-db/refinery/pull/408) +- Support `rusqlite` 0.39.x. [#425](https://github.com/rust-db/refinery/pull/425) +- Support `mysql` up to 0.28.x and `mysql_async` up to 0.37.x. [#436](https://github.com/rust-db/refinery/pull/436) +- Update `toml` dependency to 1.1.2. [#430](https://github.com/rust-db/refinery/pull/430) + +## [0.9.1] - 2026-04-15 +### Changed +- Support `rusqlite` 0.38.x. [#410](https://github.com/rust-db/refinery/pull/410) +### Fixed +- Avoid warning logs for nested directories while scanning migrations. [#421](https://github.com/rust-db/refinery/pull/421) + +## [0.9.0] - 2025-01-06 +### Added +- Support for TLS in postgres/tokio-postgres using native-tls. [#353](https://github.com/rust-db/refinery/pull/353) +- Support for int8 migration versions via new `int8-versions` feature. [#330](https://github.com/rust-db/refinery/pull/330) +- Derive Serialize for Migration if serde is enabled. [#395](https://github.com/rust-db/refinery/pull/395) + +### Changed +- Update migrate Transaction and AsyncTransaction execute functions to avoid double iteration. [#393](https://github.com/rust-db/refinery/pull/393) +- Update `mysql_async` to support up until version 0.36. [#400](https://github.com/rust-db/refinery/pull/400) +- Update `rusqlite` to support up until version 0.37. [#389](https://github.com/rust-db/refinery/pull/389), [#390](https://github.com/rust-db/refinery/pull/390) +- Update `thiserror` to version 2. [#372](https://github.com/rust-db/refinery/pull/372) +- Update MSRV (Minimum Supported Rust Version). [#401](https://github.com/rust-db/refinery/pull/401), [#362](https://github.com/rust-db/refinery/pull/362) +- Fix unused warnings [#403](https://github.com/rust-db/refinery/pull/401) + + +### Fixed +- Fix logging output for async and sync migrate functions. [#378](https://github.com/rust-db/refinery/pull/378) + +## [0.8.16] - 2024-02-21 +### Fixed +- Revert [#346](https://github.com/rust-db/refinery/pull/346) as it breaks Semver, save it for a minor release in the future. + +## [0.8.15] - **YANKED** - 2024-02-18 + +### Added +- Make a query overridable. [#358](https://github.com/rust-db/refinery/pull/358) +- Enable math functions for SQLite. [#335](https://github.com/rust-db/refinery/pull/335) +- Add serde subfeature to tiberius-config. [#334](https://github.com/rust-db/refinery/pull/334) + +### Changed +- Update `mysql` version to allow `26`, [#365](https://github.com/rust-db/refinery/pull/365) +- Update `mysql_async` to allow `0.35`, [#359](https://github.com/rust-db/refinery/pull/359) +- Update `rusqlite` to allow `0.33`, [#361](https://github.com/rust-db/refinery/pull/361) +- Update migrate Transaction and AsyncTransaction execute functions to avoid double iteration when calling migrate with the grouped option active. [#346](https://github.com/rust-db/refinery/pull/346) +- Update overall dependencies. [#340](https://github.com/rust-db/refinery/pull/340) + +### Fixed +- Fix misleading CLI arguments descriptions [#336](https://github.com/rust-db/refinery/pull/336) + + +## [0.8.14] - 2024-04-03 + +### Added +- Add new utility function, `load_sql_migrations` that enables dynamic migration discovery where embedding is not desirable. [#313](https://github.com/rust-db/refinery/pull/313) +- Add an enum `EmbeddedMigration` with all the migrations applied, [#312](https://github.com/rust-db/refinery/pull/312) + ### Changed +- Make serde, toml deps optional, [#310](https://github.com/rust-db/refinery/pull/310) +- Make `Migration::applied` `pub`, [#321](https://github.com/rust-db/refinery/pull/321) +- Update `rusqlite` to allow `0.31`, [#316](https://github.com/rust-db/refinery/pull/316) + +### Fixed +- Fix bug in get_last_applied_migration when refinery's schema history table is not default, [#313](https://github.com/rust-db/refinery/pull/313) +- Fix newline handling for Windows in database configuration setup, [#320](https://github.com/rust-db/refinery/pull/320) + +## [0.8.13] - 2024-03-29 + +- YANKED: due to release mismatch, see [#323](https://github.com/rust-db/refinery/issues/323). + +## [0.8.12] - 2024-01-22 +### Added - Add Iterable method, [#296](https://github.com/rust-db/refinery/pull/296) + +### Changed - Update `mysql` to allow `24`, [#292](https://github.com/rust-db/refinery/pull/292) - Update `mysql_async` to allow `0.33`, [#292](https://github.com/rust-db/refinery/pull/292) - Update `rusqlite` to allow `0.30`, [#300](https://github.com/rust-db/refinery/pull/300) diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 00000000..0d9efd31 --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,3368 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "addr2line" +version = "0.25.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b5d307320b3181d6d7954e663bd7c774a838b8220fe0593c86d9fb09f498b4b" +dependencies = [ + "gimli", +] + +[[package]] +name = "adler2" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" + +[[package]] +name = "aho-corasick" +version = "1.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301" +dependencies = [ + "memchr", +] + +[[package]] +name = "allocator-api2" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" + +[[package]] +name = "anstream" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "824a212faf96e9acacdbd09febd34438f8f711fb84e09a8916013cd7815ca28d" +dependencies = [ + "anstyle", + "anstyle-parse", + "anstyle-query", + "anstyle-wincon", + "colorchoice", + "is_terminal_polyfill", + "utf8parse", +] + +[[package]] +name = "anstyle" +version = "1.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "940b3a0ca603d1eade50a4846a2afffd5ef57a9feac2c0e2ec2e14f9ead76000" + +[[package]] +name = "anstyle-parse" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52ce7f38b242319f7cabaa6813055467063ecdc9d355bbb4ce0c68908cd8130e" +dependencies = [ + "utf8parse", +] + +[[package]] +name = "anstyle-query" +version = "1.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc" +dependencies = [ + "windows-sys 0.61.2", +] + +[[package]] +name = "anstyle-wincon" +version = "3.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d" +dependencies = [ + "anstyle", + "once_cell_polyfill", + "windows-sys 0.61.2", +] + +[[package]] +name = "anyhow" +version = "1.0.102" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c" + +[[package]] +name = "assert_cmd" +version = "2.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39bae1d3fa576f7c6519514180a72559268dd7d1fe104070956cb687bc6673bd" +dependencies = [ + "anstyle", + "bstr", + "libc", + "predicates", + "predicates-core", + "predicates-tree", + "wait-timeout", +] + +[[package]] +name = "async-trait" +version = "0.1.89" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9035ad2d096bed7955a320ee7e2230574d28fd3c3a0f186cbea1ff3c7eed5dbb" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "asynchronous-codec" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4057f2c32adbb2fc158e22fb38433c8e9bbf76b75a4732c7c0cbaf695fb65568" +dependencies = [ + "bytes", + "futures-sink", + "futures-util", + "memchr", + "pin-project-lite", +] + +[[package]] +name = "autocfg" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" + +[[package]] +name = "backtrace" +version = "0.3.76" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb531853791a215d7c62a30daf0dde835f381ab5de4589cfe7c649d2cbe92bd6" +dependencies = [ + "addr2line", + "cfg-if", + "libc", + "miniz_oxide", + "object", + "rustc-demangle", + "windows-link", +] + +[[package]] +name = "barrel" +version = "0.6.6-alpha.0" +source = "git+https://github.com/jxs/barrel#0221f547be69ebca584da750c6b19a8582ce9548" + +[[package]] +name = "barrel" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad9e605929a6964efbec5ac0884bd0fe93f12a3b1eb271f52c251316640c68d9" + +[[package]] +name = "base64" +version = "0.22.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" + +[[package]] +name = "base64ct" +version = "1.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2af50177e190e07a26ab74f8b1efbfe2ef87da2116221318cb1c2e82baf7de06" + +[[package]] +name = "bitflags" +version = "2.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4512299f36f043ab09a583e57bceb5a5aab7a73db1805848e8fef3c9e8c78b3" + +[[package]] +name = "block-buffer" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +dependencies = [ + "generic-array", +] + +[[package]] +name = "block-buffer" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cdd35008169921d80bc60d3d0ab416eecb028c4cd653352907921d95084790be" +dependencies = [ + "hybrid-array", +] + +[[package]] +name = "bstr" +version = "1.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "63044e1ae8e69f3b5a92c736ca6269b8d12fa7efe39bf34ddb06d102cf0e2cab" +dependencies = [ + "memchr", + "regex-automata", + "serde", +] + +[[package]] +name = "btoi" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9dd6407f73a9b8b6162d8a2ef999fe6afd7cc15902ebf42c5cd296addf17e0ad" +dependencies = [ + "num-traits", +] + +[[package]] +name = "bufstream" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "40e38929add23cdf8a366df9b0e088953150724bcbe5fc330b0d8eb3b328eec8" + +[[package]] +name = "bumpalo" +version = "3.20.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d20789868f4b01b2f2caec9f5c4e0213b41e3e5702a50157d699ae31ced2fcb" + +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + +[[package]] +name = "bytes" +version = "1.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e748733b7cbc798e1434b6ac524f0c1ff2ab456fe201501e6497c8417a4fc33" + +[[package]] +name = "cc" +version = "1.2.60" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43c5703da9466b66a946814e1adf53ea2c90f10063b86290cc9eb67ce3478a20" +dependencies = [ + "find-msvc-tools", + "shlex", +] + +[[package]] +name = "cfg-if" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" + +[[package]] +name = "chacha20" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f8d983286843e49675a4b7a2d174efe136dc93a18d69130dd18198a6c167601" +dependencies = [ + "cfg-if", + "cpufeatures 0.3.0", + "rand_core 0.10.1", +] + +[[package]] +name = "clap" +version = "4.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ddb117e43bbf7dacf0a4190fef4d345b9bad68dfc649cb349e7d17d28428e51" +dependencies = [ + "clap_builder", + "clap_derive", +] + +[[package]] +name = "clap_builder" +version = "4.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "714a53001bf66416adb0e2ef5ac857140e7dc3a0c48fb28b2f10762fc4b5069f" +dependencies = [ + "anstream", + "anstyle", + "clap_lex", + "strsim", +] + +[[package]] +name = "clap_derive" +version = "4.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2ce8604710f6733aa641a2b3731eaa1e8b3d9973d5e3565da11800813f997a9" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "clap_lex" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8d4a3bb8b1e0c1050499d1815f5ab16d04f0959b233085fb31653fbfc9d98f9" + +[[package]] +name = "cmov" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f88a43d011fc4a6876cb7344703e297c71dda42494fee094d5f7c76bf13f746" + +[[package]] +name = "colorchoice" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d07550c9036bf2ae0c684c4297d503f838287c83c53686d05370d0e139ae570" + +[[package]] +name = "connection-string" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "510ca239cf13b7f8d16a2b48f263de7b4f8c566f0af58d901031473c76afb1e3" + +[[package]] +name = "const-oid" +version = "0.9.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" + +[[package]] +name = "const-oid" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6ef517f0926dd24a1582492c791b6a4818a4d94e789a334894aa15b0d12f55c" + +[[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" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" + +[[package]] +name = "cpufeatures" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" +dependencies = [ + "libc", +] + +[[package]] +name = "cpufeatures" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b2a41393f66f16b0823bb79094d54ac5fbd34ab292ddafb9a0456ac9f87d201" +dependencies = [ + "libc", +] + +[[package]] +name = "crc32fast" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9481c1c90cbf2ac953f07c8d4a58aa3945c425b7185c9154d67a65e4230da511" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "crossbeam-queue" +version = "0.3.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f58bbc28f91df819d0aa2a2c00cd19754769c2fad90579b3592b1c9ba7a3115" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" + +[[package]] +name = "crypto-common" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78c8292055d1c1df0cce5d180393dc8cce0abec0a7102adb6c7b1eef6016d60a" +dependencies = [ + "generic-array", + "typenum", +] + +[[package]] +name = "crypto-common" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77727bb15fa921304124b128af125e7e3b968275d1b108b379190264f4423710" +dependencies = [ + "hybrid-array", +] + +[[package]] +name = "ctutils" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d5515a3834141de9eafb9717ad39eea8247b5674e6066c404e8c4b365d2a29e" +dependencies = [ + "cmov", +] + +[[package]] +name = "der" +version = "0.7.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7c1832837b905bbfb5101e07cc24c8deddf52f93225eee6ead5f4d63d53ddcb" +dependencies = [ + "const-oid 0.9.6", + "der_derive", + "flagset", + "zeroize", +] + +[[package]] +name = "der_derive" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8034092389675178f570469e6c3b0465d3d30b4505c294a6550db47f3c17ad18" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "deranged" +version = "0.5.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7cd812cc2bc1d69d4764bd80df88b4317eaef9e773c75226407d9bc0876b211c" +dependencies = [ + "powerfmt", + "serde_core", +] + +[[package]] +name = "derive_utils" +version = "0.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "362f47930db19fe7735f527e6595e4900316b893ebf6d48ad3d31be928d57dd6" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "difflib" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6184e33543162437515c2e2b48714794e37845ec9851711914eec9d308f6ebe8" + +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer 0.10.4", + "crypto-common 0.1.7", +] + +[[package]] +name = "digest" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4850db49bf08e663084f7fb5c87d202ef91a3907271aff24a94eb97ff039153c" +dependencies = [ + "block-buffer 0.12.0", + "const-oid 0.10.2", + "crypto-common 0.2.1", + "ctutils", +] + +[[package]] +name = "displaydoc" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "encoding_rs" +version = "0.8.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75030f3c4f45dafd7586dd6780965a8c7e8e285a5ecb86713e63a79c5b2766f3" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "enumflags2" +version = "0.7.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1027f7680c853e056ebcec683615fb6fbbc07dbaa13b4d5d9442b146ded4ecef" +dependencies = [ + "enumflags2_derive", +] + +[[package]] +name = "enumflags2_derive" +version = "0.7.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67c78a4d8fdf9953a5c9d458f9efe940fd97a0cab0941c075a813ac594733827" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "env_filter" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32e90c2accc4b07a8456ea0debdc2e7587bdd890680d71173a15d4ae604f6eef" +dependencies = [ + "log", + "regex", +] + +[[package]] +name = "env_logger" +version = "0.11.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0621c04f2196ac3f488dd583365b9c09be011a4ab8b9f37248ffcc8f6198b56a" +dependencies = [ + "anstream", + "anstyle", + "env_filter", + "jiff", + "log", +] + +[[package]] +name = "equivalent" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" + +[[package]] +name = "errno" +version = "0.3.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" +dependencies = [ + "libc", + "windows-sys 0.61.2", +] + +[[package]] +name = "fallible-iterator" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4443176a9f2c162692bd3d352d745ef9413eec5782a80d8fd6f8a1ac692a07f7" + +[[package]] +name = "fallible-iterator" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2acce4a10f12dc2fb14a218589d4f1f62ef011b2d0cc4b3cb1bba8e94da14649" + +[[package]] +name = "fallible-streaming-iterator" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7360491ce676a36bf9bb3c56c1aa791658183a54d2744120f27285738d90465a" + +[[package]] +name = "fastrand" +version = "2.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f1f227452a390804cdb637b74a86990f2a7d7ba4b7d5693aac9b4dd6defd8d6" + +[[package]] +name = "find-msvc-tools" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582" + +[[package]] +name = "flagset" +version = "0.4.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7ac824320a75a52197e8f2d787f6a38b6718bb6897a35142d749af3c0e8f4fe" + +[[package]] +name = "flate2" +version = "1.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "843fba2746e448b37e26a819579957415c8cef339bf08564fe8b7ddbd959573c" +dependencies = [ + "crc32fast", + "libz-sys", + "miniz_oxide", +] + +[[package]] +name = "float-cmp" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b09cf3155332e944990140d967ff5eceb70df778b34f77d8075db46e4704e6d8" +dependencies = [ + "num-traits", +] + +[[package]] +name = "foldhash" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" + +[[package]] +name = "foldhash" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77ce24cb58228fbb8aa041425bb1050850ac19177686ea6e0f41a70416f56fdb" + +[[package]] +name = "foreign-types" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" +dependencies = [ + "foreign-types-shared", +] + +[[package]] +name = "foreign-types-shared" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" + +[[package]] +name = "form_urlencoded" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb4cb245038516f5f85277875cdaa4f7d2c9a0fa0468de06ed190163b1581fcf" +dependencies = [ + "percent-encoding", +] + +[[package]] +name = "futures" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b147ee9d1f6d097cef9ce628cd2ee62288d963e16fb287bd9286455b241382d" +dependencies = [ + "futures-channel", + "futures-core", + "futures-executor", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-channel" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07bbe89c50d7a535e539b8c17bc0b49bdb77747034daa8087407d655f3f7cc1d" +dependencies = [ + "futures-core", + "futures-sink", +] + +[[package]] +name = "futures-core" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e3450815272ef58cec6d564423f6e755e25379b217b0bc688e295ba24df6b1d" + +[[package]] +name = "futures-executor" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf29c38818342a3b26b5b923639e7b1f4a61fc5e76102d4b1981c6dc7a7579d" +dependencies = [ + "futures-core", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-io" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cecba35d7ad927e23624b22ad55235f2239cfa44fd10428eecbeba6d6a717718" + +[[package]] +name = "futures-macro" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e835b70203e41293343137df5c0664546da5745f82ec9b84d40be8336958447b" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "futures-sink" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c39754e157331b013978ec91992bde1ac089843443c49cbc7f46150b0fad0893" + +[[package]] +name = "futures-task" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "037711b3d59c33004d3856fbdc83b99d4ff37a24768fa1be9ce3538a1cde4393" + +[[package]] +name = "futures-util" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "389ca41296e6190b48053de0321d02a77f32f8a5d2461dd38762c0593805c6d6" +dependencies = [ + "futures-channel", + "futures-core", + "futures-io", + "futures-macro", + "futures-sink", + "futures-task", + "memchr", + "pin-project-lite", + "slab", +] + +[[package]] +name = "generic-array" +version = "0.14.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +dependencies = [ + "typenum", + "version_check", +] + +[[package]] +name = "getrandom" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0" +dependencies = [ + "cfg-if", + "libc", + "wasi 0.11.1+wasi-snapshot-preview1", +] + +[[package]] +name = "getrandom" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" +dependencies = [ + "cfg-if", + "libc", + "r-efi 5.3.0", + "wasip2", +] + +[[package]] +name = "getrandom" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0de51e6874e94e7bf76d726fc5d13ba782deca734ff60d5bb2fb2607c7406555" +dependencies = [ + "cfg-if", + "libc", + "r-efi 6.0.0", + "rand_core 0.10.1", + "wasip2", + "wasip3", +] + +[[package]] +name = "gimli" +version = "0.32.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e629b9b98ef3dd8afe6ca2bd0f89306cec16d43d907889945bc5d6687f2f13c7" + +[[package]] +name = "hashbrown" +version = "0.15.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" +dependencies = [ + "foldhash 0.1.5", +] + +[[package]] +name = "hashbrown" +version = "0.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100" +dependencies = [ + "allocator-api2", + "equivalent", + "foldhash 0.2.0", +] + +[[package]] +name = "hashbrown" +version = "0.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4f467dd6dccf739c208452f8014c75c18bb8301b050ad1cfb27153803edb0f51" + +[[package]] +name = "hashlink" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea0b22561a9c04a7cb1a302c013e0259cd3b4bb619f145b32f72b8b4bcbed230" +dependencies = [ + "hashbrown 0.16.1", +] + +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + +[[package]] +name = "hmac" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6303bc9732ae41b04cb554b844a762b4115a61bfaa81e3e83050991eeb56863f" +dependencies = [ + "digest 0.11.2", +] + +[[package]] +name = "human-panic" +version = "2.0.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2815d0c49773c6e0a9753960b3d0e50b822e48e08c77bcb4780be8474c9cc3d" +dependencies = [ + "anstream", + "anstyle", + "backtrace", + "serde", + "serde_derive", + "sysinfo", + "toml 1.1.2+spec-1.1.0", + "uuid", +] + +[[package]] +name = "hybrid-array" +version = "0.4.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3944cf8cf766b40e2a1a333ee5e9b563f854d5fa49d6a8ca2764e97c6eddb214" +dependencies = [ + "typenum", +] + +[[package]] +name = "icu_collections" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2984d1cd16c883d7935b9e07e44071dca8d917fd52ecc02c04d5fa0b5a3f191c" +dependencies = [ + "displaydoc", + "potential_utf", + "utf8_iter", + "yoke", + "zerofrom", + "zerovec", +] + +[[package]] +name = "icu_locale_core" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92219b62b3e2b4d88ac5119f8904c10f8f61bf7e95b640d25ba3075e6cac2c29" +dependencies = [ + "displaydoc", + "litemap", + "tinystr", + "writeable", + "zerovec", +] + +[[package]] +name = "icu_normalizer" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c56e5ee99d6e3d33bd91c5d85458b6005a22140021cc324cea84dd0e72cff3b4" +dependencies = [ + "icu_collections", + "icu_normalizer_data", + "icu_properties", + "icu_provider", + "smallvec", + "zerovec", +] + +[[package]] +name = "icu_normalizer_data" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da3be0ae77ea334f4da67c12f149704f19f81d1adf7c51cf482943e84a2bad38" + +[[package]] +name = "icu_properties" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bee3b67d0ea5c2cca5003417989af8996f8604e34fb9ddf96208a033901e70de" +dependencies = [ + "icu_collections", + "icu_locale_core", + "icu_properties_data", + "icu_provider", + "zerotrie", + "zerovec", +] + +[[package]] +name = "icu_properties_data" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e2bbb201e0c04f7b4b3e14382af113e17ba4f63e2c9d2ee626b720cbce54a14" + +[[package]] +name = "icu_provider" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "139c4cf31c8b5f33d7e199446eff9c1e02decfc2f0eec2c8d71f65befa45b421" +dependencies = [ + "displaydoc", + "icu_locale_core", + "writeable", + "yoke", + "zerofrom", + "zerotrie", + "zerovec", +] + +[[package]] +name = "id-arena" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d3067d79b975e8844ca9eb072e16b31c3c1c36928edf9c6789548c524d0d954" + +[[package]] +name = "idna" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b0875f23caa03898994f6ddc501886a45c7d3d62d04d2d90788d47be1b1e4de" +dependencies = [ + "idna_adapter", + "smallvec", + "utf8_iter", +] + +[[package]] +name = "idna_adapter" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3acae9609540aa318d1bc588455225fb2085b9ed0c4f6bd0d9d5bcd86f1a0344" +dependencies = [ + "icu_normalizer", + "icu_properties", +] + +[[package]] +name = "indexmap" +version = "2.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d466e9454f08e4a911e14806c24e16fba1b4c121d1ea474396f396069cf949d9" +dependencies = [ + "equivalent", + "hashbrown 0.17.0", + "serde", + "serde_core", +] + +[[package]] +name = "io-enum" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7de9008599afe8527a8c9d70423437363b321649161e98473f433de802d76107" +dependencies = [ + "derive_utils", +] + +[[package]] +name = "is_terminal_polyfill" +version = "1.70.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695" + +[[package]] +name = "itoa" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" + +[[package]] +name = "jiff" +version = "0.2.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a3546dc96b6d42c5f24902af9e2538e82e39ad350b0c766eb3fbf2d8f3d8359" +dependencies = [ + "jiff-static", + "log", + "portable-atomic", + "portable-atomic-util", + "serde_core", +] + +[[package]] +name = "jiff-static" +version = "0.2.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a8c8b344124222efd714b73bb41f8b5120b27a7cc1c75593a6ff768d9d05aa4" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "js-sys" +version = "0.3.95" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2964e92d1d9dc3364cae4d718d93f227e3abb088e747d92e0395bfdedf1c12ca" +dependencies = [ + "once_cell", + "wasm-bindgen", +] + +[[package]] +name = "keyed_priority_queue" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ee7893dab2e44ae5f9d0173f26ff4aa327c10b01b06a72b52dd9405b628640d" +dependencies = [ + "indexmap", +] + +[[package]] +name = "leb128fmt" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2" + +[[package]] +name = "libc" +version = "0.2.185" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52ff2c0fe9bc6cb6b14a0592c2ff4fa9ceb83eea9db979b0487cd054946a2b8f" + +[[package]] +name = "libredox" +version = "0.1.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e02f3bb43d335493c96bf3fd3a321600bf6bd07ed34bc64118e9293bdffea46c" +dependencies = [ + "libc", +] + +[[package]] +name = "libsqlite3-sys" +version = "0.37.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1f111c8c41e7c61a49cd34e44c7619462967221a6443b0ec299e0ac30cfb9b1" +dependencies = [ + "cc", + "pkg-config", + "vcpkg", +] + +[[package]] +name = "libz-sys" +version = "1.1.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc3a226e576f50782b3305c5ccf458698f92798987f551c6a02efe8276721e22" +dependencies = [ + "cc", + "pkg-config", + "vcpkg", +] + +[[package]] +name = "linux-raw-sys" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a66949e030da00e8c7d4434b251670a91556f4144941d37452769c25d58a53" + +[[package]] +name = "litemap" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92daf443525c4cce67b150400bc2316076100ce0b3686209eb8cf3c31612e6f0" + +[[package]] +name = "lock_api" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965" +dependencies = [ + "scopeguard", +] + +[[package]] +name = "log" +version = "0.4.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" + +[[package]] +name = "lru" +version = "0.12.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "234cf4f4a04dc1f57e24b96cc0cd600cf2af460d4161ac5ecdd0af8e1f3b2a38" + +[[package]] +name = "lru" +version = "0.16.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f66e8d5d03f609abc3a39e6f08e4164ebf1447a732906d39eb9b99b7919ef39" +dependencies = [ + "hashbrown 0.16.1", +] + +[[package]] +name = "md-5" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69b6441f590336821bb897fb28fc622898ccceb1d6cea3fde5ea86b090c4de98" +dependencies = [ + "cfg-if", + "digest 0.11.2", +] + +[[package]] +name = "memchr" +version = "2.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79" + +[[package]] +name = "miniz_oxide" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316" +dependencies = [ + "adler2", + "simd-adler32", +] + +[[package]] +name = "mio" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50b7e5b27aa02a74bac8c3f23f448f8d87ff11f92d3aac1a6ed369ee08cc56c1" +dependencies = [ + "libc", + "wasi 0.11.1+wasi-snapshot-preview1", + "windows-sys 0.61.2", +] + +[[package]] +name = "mysql" +version = "26.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce2510a735f601bab18202b07ea0a197bd1d130d3a5ce2edf4577d225f0c3ee4" +dependencies = [ + "bufstream", + "bytes", + "crossbeam-queue", + "crossbeam-utils", + "flate2", + "io-enum", + "libc", + "lru 0.12.5", + "mysql_common", + "named_pipe", + "pem", + "percent-encoding", + "socket2 0.5.10", + "twox-hash", + "url", +] + +[[package]] +name = "mysql_async" +version = "0.36.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d1d9585dc9058886ff3a1f48a23024dd1d054264dee7c5ae0e4bd640c953bee5" +dependencies = [ + "bytes", + "crossbeam-queue", + "flate2", + "futures-core", + "futures-sink", + "futures-util", + "keyed_priority_queue", + "lru 0.16.4", + "mysql_common", + "pem", + "percent-encoding", + "rand 0.9.4", + "serde", + "serde_json", + "socket2 0.5.10", + "thiserror 2.0.18", + "tokio", + "tokio-util", + "twox-hash", + "url", +] + +[[package]] +name = "mysql_common" +version = "0.35.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fbb9f371618ce723f095c61fbcdc36e8936956d2b62832f9c7648689b338e052" +dependencies = [ + "base64", + "bitflags", + "btoi", + "byteorder", + "bytes", + "crc32fast", + "flate2", + "getrandom 0.3.4", + "num-bigint", + "num-traits", + "regex", + "saturating", + "serde", + "serde_json", + "sha1", + "sha2 0.10.9", + "thiserror 2.0.18", + "uuid", +] + +[[package]] +name = "named_pipe" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad9c443cce91fc3e12f017290db75dde490d685cdaaf508d7159d7cf41f0eb2b" +dependencies = [ + "winapi", +] + +[[package]] +name = "native-tls" +version = "0.2.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "465500e14ea162429d264d44189adc38b199b62b1c21eea9f69e4b73cb03bbf2" +dependencies = [ + "libc", + "log", + "openssl", + "openssl-probe", + "openssl-sys", + "schannel", + "security-framework", + "security-framework-sys", + "tempfile", +] + +[[package]] +name = "normalize-line-endings" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61807f77802ff30975e01f4f071c8ba10c022052f98b3294119f3e615d13e5be" + +[[package]] +name = "ntapi" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3b335231dfd352ffb0f8017f3b6027a4917f7df785ea2143d8af2adc66980ae" +dependencies = [ + "winapi", +] + +[[package]] +name = "num-bigint" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9" +dependencies = [ + "num-integer", + "num-traits", +] + +[[package]] +name = "num-conv" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6673768db2d862beb9b39a78fdcb1a69439615d5794a1be50caa9bc92c81967" + +[[package]] +name = "num-integer" +version = "0.1.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" +dependencies = [ + "num-traits", +] + +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", +] + +[[package]] +name = "objc2-core-foundation" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a180dd8642fa45cdb7dd721cd4c11b1cadd4929ce112ebd8b9f5803cc79d536" +dependencies = [ + "bitflags", +] + +[[package]] +name = "objc2-io-kit" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33fafba39597d6dc1fb709123dfa8289d39406734be322956a69f0931c73bb15" +dependencies = [ + "libc", + "objc2-core-foundation", +] + +[[package]] +name = "objc2-system-configuration" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7216bd11cbda54ccabcab84d523dc93b858ec75ecfb3a7d89513fa22464da396" +dependencies = [ + "objc2-core-foundation", +] + +[[package]] +name = "object" +version = "0.37.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff76201f031d8863c38aa7f905eca4f53abbfa15f609db4277d44cd8938f33fe" +dependencies = [ + "memchr", +] + +[[package]] +name = "once_cell" +version = "1.21.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" + +[[package]] +name = "once_cell_polyfill" +version = "1.70.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe" + +[[package]] +name = "openssl" +version = "0.10.80" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a45fa2aa886c42762255da344f0a0d313e254066c46aad76f300c3d3da62d967" +dependencies = [ + "bitflags", + "cfg-if", + "foreign-types", + "libc", + "openssl-macros", + "openssl-sys", +] + +[[package]] +name = "openssl-macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "openssl-probe" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c87def4c32ab89d880effc9e097653c8da5d6ef28e6b539d313baaacfbafcbe" + +[[package]] +name = "openssl-sys" +version = "0.9.116" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f28a22dc7140cda5f096e5e7724a6962ca81a7f8bfd2979f9b18c11af56318c4" +dependencies = [ + "cc", + "libc", + "pkg-config", + "vcpkg", +] + +[[package]] +name = "parking_lot" +version = "0.12.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "smallvec", + "windows-link", +] + +[[package]] +name = "pem" +version = "3.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d30c53c26bc5b31a98cd02d20f25a7c8567146caf63ed593a9d87b2775291be" +dependencies = [ + "base64", + "serde_core", +] + +[[package]] +name = "percent-encoding" +version = "2.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" + +[[package]] +name = "phf" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1562dc717473dbaa4c1f85a36410e03c047b2e7df7f45ee938fbef64ae7fadf" +dependencies = [ + "phf_shared", + "serde", +] + +[[package]] +name = "phf_shared" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e57fef6bc5981e38c2ce2d63bfa546861309f875b8a75f092d1d54ae2d64f266" +dependencies = [ + "siphasher", +] + +[[package]] +name = "pin-project-lite" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd" + +[[package]] +name = "pkg-config" +version = "0.3.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19f132c84eca552bf34cab8ec81f1c1dcc229b811638f9d283dceabe58c5569e" + +[[package]] +name = "portable-atomic" +version = "1.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c33a9471896f1c69cecef8d20cbe2f7accd12527ce60845ff44c153bb2a21b49" + +[[package]] +name = "portable-atomic-util" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2a106d1259c23fac8e543272398ae0e3c0b8d33c88ed73d0cc71b0f1d902618" +dependencies = [ + "portable-atomic", +] + +[[package]] +name = "postgres" +version = "0.19.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aacf632d0554ff75f58183694f41dc8999c8a3a43a386994d0ec2d034f1dfbe1" +dependencies = [ + "bytes", + "fallible-iterator 0.2.0", + "futures-util", + "log", + "tokio", + "tokio-postgres", +] + +[[package]] +name = "postgres-native-tls" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fef4de47bb81477e0c3deaf153a1b10ae176484713ff1640969f4cb96b653ebc" +dependencies = [ + "native-tls", + "tokio", + "tokio-native-tls", + "tokio-postgres", +] + +[[package]] +name = "postgres-protocol" +version = "0.6.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56201207dac53e2f38e848e31b4b91616a6bb6e0c7205b77718994a7f49e70fc" +dependencies = [ + "base64", + "byteorder", + "bytes", + "fallible-iterator 0.2.0", + "hmac", + "md-5", + "memchr", + "rand 0.10.1", + "sha2 0.11.0", + "stringprep", +] + +[[package]] +name = "postgres-types" +version = "0.2.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8dc729a129e682e8d24170cd30ae1aa01b336b096cbb56df6d534ffec133d186" +dependencies = [ + "bytes", + "fallible-iterator 0.2.0", + "postgres-protocol", +] + +[[package]] +name = "potential_utf" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0103b1cef7ec0cf76490e969665504990193874ea05c85ff9bab8b911d0a0564" +dependencies = [ + "zerovec", +] + +[[package]] +name = "powerfmt" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" + +[[package]] +name = "ppv-lite86" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" +dependencies = [ + "zerocopy", +] + +[[package]] +name = "predicates" +version = "3.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ada8f2932f28a27ee7b70dd6c1c39ea0675c55a36879ab92f3a715eaa1e63cfe" +dependencies = [ + "anstyle", + "difflib", + "float-cmp", + "normalize-line-endings", + "predicates-core", + "regex", +] + +[[package]] +name = "predicates-core" +version = "1.0.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cad38746f3166b4031b1a0d39ad9f954dd291e7854fcc0eed52ee41a0b50d144" + +[[package]] +name = "predicates-tree" +version = "1.0.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0de1b847b39c8131db0467e9df1ff60e6d0562ab8e9a16e568ad0fdb372e2f2" +dependencies = [ + "predicates-core", + "termtree", +] + +[[package]] +name = "pretty-hex" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6fa0831dd7cc608c38a5e323422a0077678fa5744aa2be4ad91c4ece8eec8d5" + +[[package]] +name = "prettyplease" +version = "0.2.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b" +dependencies = [ + "proc-macro2", + "syn", +] + +[[package]] +name = "proc-macro2" +version = "1.0.106" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "r-efi" +version = "5.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" + +[[package]] +name = "r-efi" +version = "6.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf" + +[[package]] +name = "rand" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44c5af06bb1b7d3216d91932aed5265164bf384dc89cd6ba05cf59a35f5f76ea" +dependencies = [ + "rand_chacha", + "rand_core 0.9.5", +] + +[[package]] +name = "rand" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2e8e8bcc7961af1fdac401278c6a831614941f6164ee3bf4ce61b7edb162207" +dependencies = [ + "chacha20", + "getrandom 0.4.2", + "rand_core 0.10.1", +] + +[[package]] +name = "rand_chacha" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" +dependencies = [ + "ppv-lite86", + "rand_core 0.9.5", +] + +[[package]] +name = "rand_core" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76afc826de14238e6e8c374ddcc1fa19e374fd8dd986b0d2af0d02377261d83c" +dependencies = [ + "getrandom 0.3.4", +] + +[[package]] +name = "rand_core" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "63b8176103e19a2643978565ca18b50549f6101881c443590420e4dc998a3c69" + +[[package]] +name = "redox_syscall" +version = "0.5.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" +dependencies = [ + "bitflags", +] + +[[package]] +name = "refinery" +version = "0.9.0" +dependencies = [ + "assert_cmd", + "barrel 0.6.6-alpha.0", + "futures", + "predicates", + "refinery-core", + "refinery-macros", + "rustls", + "tempfile", + "time", + "tokio", + "tokio-util", +] + +[[package]] +name = "refinery-core" +version = "0.9.0" +dependencies = [ + "async-trait", + "barrel 0.6.6-alpha.0", + "cfg-if", + "futures", + "log", + "mysql", + "mysql_async", + "native-tls", + "postgres", + "postgres-native-tls", + "regex", + "rusqlite", + "rustls", + "rustls-native-certs", + "serde", + "siphasher", + "tempfile", + "thiserror 2.0.18", + "tiberius", + "time", + "tokio", + "tokio-postgres", + "tokio-postgres-rustls", + "tokio-util", + "toml 1.1.2+spec-1.1.0", + "url", + "walkdir", +] + +[[package]] +name = "refinery-examples" +version = "0.8.12" +dependencies = [ + "barrel 0.7.0", + "env_logger", + "log", + "refinery", + "rusqlite", +] + +[[package]] +name = "refinery-macros" +version = "0.9.0" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "refinery-core", + "regex", + "syn", + "tempfile", +] + +[[package]] +name = "refinery_cli" +version = "0.9.0" +dependencies = [ + "anyhow", + "assert_cmd", + "cfg-if", + "clap", + "env_logger", + "human-panic", + "log", + "predicates", + "refinery-core", + "regex", + "tokio", + "toml 0.8.23", + "walkdir", +] + +[[package]] +name = "regex" +version = "1.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e10754a14b9137dd7b1e3e5b0493cc9171fdd105e0ab477f51b72e7f3ac0e276" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e1dd4122fc1595e8162618945476892eefca7b88c52820e74af6262213cae8f" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.8.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc897dd8d9e8bd1ed8cdad82b5966c3e0ecae09fb1907d58efaa013543185d0a" + +[[package]] +name = "ring" +version = "0.17.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7" +dependencies = [ + "cc", + "cfg-if", + "getrandom 0.2.17", + "libc", + "untrusted", + "windows-sys 0.52.0", +] + +[[package]] +name = "rsqlite-vfs" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8a1f2315036ef6b1fbacd1972e8ee7688030b0a2121edfc2a6550febd41574d" +dependencies = [ + "hashbrown 0.16.1", + "thiserror 2.0.18", +] + +[[package]] +name = "rusqlite" +version = "0.39.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a0d2b0146dd9661bf67bb107c0bb2a55064d556eeb3fc314151b957f313bcd4e" +dependencies = [ + "bitflags", + "fallible-iterator 0.3.0", + "fallible-streaming-iterator", + "hashlink", + "libsqlite3-sys", + "smallvec", + "sqlite-wasm-rs", +] + +[[package]] +name = "rustc-demangle" +version = "0.1.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b50b8869d9fc858ce7266cce0194bd74df58b9d0e3f6df3a9fc8eb470d95c09d" + +[[package]] +name = "rustix" +version = "1.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6fe4565b9518b83ef4f91bb47ce29620ca828bd32cb7e408f0062e9930ba190" +dependencies = [ + "bitflags", + "errno", + "libc", + "linux-raw-sys", + "windows-sys 0.61.2", +] + +[[package]] +name = "rustls" +version = "0.23.38" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69f9466fb2c14ea04357e91413efb882e2a6d4a406e625449bc0a5d360d53a21" +dependencies = [ + "once_cell", + "ring", + "rustls-pki-types", + "rustls-webpki", + "subtle", + "zeroize", +] + +[[package]] +name = "rustls-native-certs" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "612460d5f7bea540c490b2b6395d8e34a953e52b491accd6c86c8164c5932a63" +dependencies = [ + "openssl-probe", + "rustls-pki-types", + "schannel", + "security-framework", +] + +[[package]] +name = "rustls-pki-types" +version = "1.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be040f8b0a225e40375822a563fa9524378b9d63112f53e19ffff34df5d33fdd" +dependencies = [ + "zeroize", +] + +[[package]] +name = "rustls-webpki" +version = "0.103.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61c429a8649f110dddef65e2a5ad240f747e85f7758a6bccc7e5777bd33f756e" +dependencies = [ + "ring", + "rustls-pki-types", + "untrusted", +] + +[[package]] +name = "rustversion" +version = "1.0.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" + +[[package]] +name = "same-file" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "saturating" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ece8e78b2f38ec51c51f5d475df0a7187ba5111b2a28bdc761ee05b075d40a71" + +[[package]] +name = "schannel" +version = "0.1.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91c1b7e4904c873ef0710c1f407dde2e6287de2bebc1bbbf7d430bb7cbffd939" +dependencies = [ + "windows-sys 0.61.2", +] + +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[package]] +name = "security-framework" +version = "3.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7f4bc775c73d9a02cde8bf7b2ec4c9d12743edf609006c7facc23998404cd1d" +dependencies = [ + "bitflags", + "core-foundation", + "core-foundation-sys", + "libc", + "security-framework-sys", +] + +[[package]] +name = "security-framework-sys" +version = "2.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ce2691df843ecc5d231c0b14ece2acc3efb62c0a398c7e1d875f3983ce020e3" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "semver" +version = "1.0.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a7852d02fc848982e0c167ef163aaff9cd91dc640ba85e263cb1ce46fae51cd" + +[[package]] +name = "serde" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" +dependencies = [ + "serde_core", + "serde_derive", +] + +[[package]] +name = "serde_core" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_json" +version = "1.0.149" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "83fc039473c5595ace860d8c4fafa220ff474b3fc6bfdb4293327f1a37e94d86" +dependencies = [ + "itoa", + "memchr", + "serde", + "serde_core", + "zmij", +] + +[[package]] +name = "serde_spanned" +version = "0.6.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf41e0cfaf7226dca15e8197172c295a782857fcb97fad1808a166870dee75a3" +dependencies = [ + "serde", +] + +[[package]] +name = "serde_spanned" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6662b5879511e06e8999a8a235d848113e942c9124f211511b16466ee2995f26" +dependencies = [ + "serde_core", +] + +[[package]] +name = "sha1" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba" +dependencies = [ + "cfg-if", + "cpufeatures 0.2.17", + "digest 0.10.7", +] + +[[package]] +name = "sha2" +version = "0.10.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" +dependencies = [ + "cfg-if", + "cpufeatures 0.2.17", + "digest 0.10.7", +] + +[[package]] +name = "sha2" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "446ba717509524cb3f22f17ecc096f10f4822d76ab5c0b9822c5f9c284e825f4" +dependencies = [ + "cfg-if", + "cpufeatures 0.3.0", + "digest 0.11.2", +] + +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + +[[package]] +name = "signal-hook-registry" +version = "1.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4db69cba1110affc0e9f7bcd48bbf87b3f4fc7c61fc9155afd4c469eb3d6c1b" +dependencies = [ + "errno", + "libc", +] + +[[package]] +name = "simd-adler32" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "703d5c7ef118737c72f1af64ad2f6f8c5e1921f818cdcb97b8fe6fc69bf66214" + +[[package]] +name = "siphasher" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2aa850e253778c88a04c3d7323b043aeda9d3e30d5971937c1855769763678e" + +[[package]] +name = "slab" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5" + +[[package]] +name = "smallvec" +version = "1.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" + +[[package]] +name = "socket2" +version = "0.5.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e22376abed350d73dd1cd119b57ffccad95b4e585a7cda43e286245ce23c0678" +dependencies = [ + "libc", + "windows-sys 0.52.0", +] + +[[package]] +name = "socket2" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a766e1110788c36f4fa1c2b71b387a7815aa65f88ce0229841826633d93723e" +dependencies = [ + "libc", + "windows-sys 0.61.2", +] + +[[package]] +name = "spki" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d91ed6c858b01f942cd56b37a94b3e0a1798290327d1236e4d9cf4eaca44d29d" +dependencies = [ + "base64ct", + "der", +] + +[[package]] +name = "sqlite-wasm-rs" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b2c760607300407ddeaee518acf28c795661b7108c75421303dbefb237d3a36" +dependencies = [ + "cc", + "js-sys", + "rsqlite-vfs", + "wasm-bindgen", +] + +[[package]] +name = "stable_deref_trait" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" + +[[package]] +name = "stringprep" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b4df3d392d81bd458a8a621b8bffbd2302a12ffe288a9d931670948749463b1" +dependencies = [ + "unicode-bidi", + "unicode-normalization", + "unicode-properties", +] + +[[package]] +name = "strsim" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" + +[[package]] +name = "subtle" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" + +[[package]] +name = "syn" +version = "2.0.117" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "synstructure" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "sysinfo" +version = "0.38.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92ab6a2f8bfe508deb3c6406578252e491d299cbbf3bc0529ecc3313aee4a52f" +dependencies = [ + "libc", + "memchr", + "ntapi", + "objc2-core-foundation", + "objc2-io-kit", + "windows", +] + +[[package]] +name = "tempfile" +version = "3.27.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32497e9a4c7b38532efcdebeef879707aa9f794296a4f0244f6f69e9bc8574bd" +dependencies = [ + "fastrand", + "getrandom 0.4.2", + "once_cell", + "rustix", + "windows-sys 0.61.2", +] + +[[package]] +name = "termtree" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f50febec83f5ee1df3015341d8bd429f2d1cc62bcba7ea2076759d315084683" + +[[package]] +name = "thiserror" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" +dependencies = [ + "thiserror-impl 1.0.69", +] + +[[package]] +name = "thiserror" +version = "2.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4" +dependencies = [ + "thiserror-impl 2.0.18", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "thiserror-impl" +version = "2.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tiberius" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1446cb4198848d1562301a3340424b4f425ef79f35ef9ee034769a9dd92c10d" +dependencies = [ + "async-trait", + "asynchronous-codec", + "byteorder", + "bytes", + "connection-string", + "encoding_rs", + "enumflags2", + "futures-util", + "num-traits", + "once_cell", + "pin-project-lite", + "pretty-hex", + "thiserror 1.0.69", + "tracing", + "uuid", +] + +[[package]] +name = "time" +version = "0.3.47" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "743bd48c283afc0388f9b8827b976905fb217ad9e647fae3a379a9283c4def2c" +dependencies = [ + "deranged", + "itoa", + "num-conv", + "powerfmt", + "serde_core", + "time-core", + "time-macros", +] + +[[package]] +name = "time-core" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7694e1cfe791f8d31026952abf09c69ca6f6fa4e1a1229e18988f06a04a12dca" + +[[package]] +name = "time-macros" +version = "0.2.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e70e4c5a0e0a8a4823ad65dfe1a6930e4f4d756dcd9dd7939022b5e8c501215" +dependencies = [ + "num-conv", + "time-core", +] + +[[package]] +name = "tinystr" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8323304221c2a851516f22236c5722a72eaa19749016521d6dff0824447d96d" +dependencies = [ + "displaydoc", + "zerovec", +] + +[[package]] +name = "tinyvec" +version = "1.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3e61e67053d25a4e82c844e8424039d9745781b3fc4f32b8d55ed50f5f667ef3" +dependencies = [ + "tinyvec_macros", +] + +[[package]] +name = "tinyvec_macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" + +[[package]] +name = "tls_codec" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0de2e01245e2bb89d6f05801c564fa27624dbd7b1846859876c7dad82e90bf6b" +dependencies = [ + "tls_codec_derive", + "zeroize", +] + +[[package]] +name = "tls_codec_derive" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d2e76690929402faae40aebdda620a2c0e25dd6d3b9afe48867dfd95991f4bd" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tokio" +version = "1.52.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b67dee974fe86fd92cc45b7a95fdd2f99a36a6d7b0d431a231178d3d670bbcc6" +dependencies = [ + "bytes", + "libc", + "mio", + "parking_lot", + "pin-project-lite", + "signal-hook-registry", + "socket2 0.6.3", + "tokio-macros", + "windows-sys 0.61.2", +] + +[[package]] +name = "tokio-macros" +version = "2.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "385a6cb71ab9ab790c5fe8d67f1645e6c450a7ce006a33de03daa956cf70a496" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tokio-native-tls" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbae76ab933c85776efabc971569dd6119c580d8f5d448769dec1764bf796ef2" +dependencies = [ + "native-tls", + "tokio", +] + +[[package]] +name = "tokio-postgres" +version = "0.7.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4dd8df5ef180f6364759a6f00f7aadda4fbbac86cdee37480826a6ff9f3574ce" +dependencies = [ + "async-trait", + "byteorder", + "bytes", + "fallible-iterator 0.2.0", + "futures-channel", + "futures-util", + "log", + "parking_lot", + "percent-encoding", + "phf", + "pin-project-lite", + "postgres-protocol", + "postgres-types", + "rand 0.10.1", + "socket2 0.6.3", + "tokio", + "tokio-util", + "whoami", +] + +[[package]] +name = "tokio-postgres-rustls" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "27d684bad428a0f2481f42241f821db42c54e2dc81d8c00db8536c506b0a0144" +dependencies = [ + "const-oid 0.9.6", + "ring", + "rustls", + "tokio", + "tokio-postgres", + "tokio-rustls", + "x509-cert", +] + +[[package]] +name = "tokio-rustls" +version = "0.26.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1729aa945f29d91ba541258c8df89027d5792d85a8841fb65e8bf0f4ede4ef61" +dependencies = [ + "rustls", + "tokio", +] + +[[package]] +name = "tokio-util" +version = "0.7.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ae9cec805b01e8fc3fd2fe289f89149a9b66dd16786abd8b19cfa7b48cb0098" +dependencies = [ + "bytes", + "futures-core", + "futures-io", + "futures-sink", + "pin-project-lite", + "tokio", +] + +[[package]] +name = "toml" +version = "0.8.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc1beb996b9d83529a9e75c17a1686767d148d70663143c7854d8b4a09ced362" +dependencies = [ + "serde", + "serde_spanned 0.6.9", + "toml_datetime 0.6.11", + "toml_edit", +] + +[[package]] +name = "toml" +version = "1.1.2+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81f3d15e84cbcd896376e6730314d59fb5a87f31e4b038454184435cd57defee" +dependencies = [ + "indexmap", + "serde_core", + "serde_spanned 1.1.1", + "toml_datetime 1.1.1+spec-1.1.0", + "toml_parser", + "toml_writer", + "winnow 1.0.3", +] + +[[package]] +name = "toml_datetime" +version = "0.6.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22cddaf88f4fbc13c51aebbf5f8eceb5c7c5a9da2ac40a13519eb5b0a0e8f11c" +dependencies = [ + "serde", +] + +[[package]] +name = "toml_datetime" +version = "1.1.1+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3165f65f62e28e0115a00b2ebdd37eb6f3b641855f9d636d3cd4103767159ad7" +dependencies = [ + "serde_core", +] + +[[package]] +name = "toml_edit" +version = "0.22.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41fe8c660ae4257887cf66394862d21dbca4a6ddd26f04a3560410406a2f819a" +dependencies = [ + "indexmap", + "serde", + "serde_spanned 0.6.9", + "toml_datetime 0.6.11", + "toml_write", + "winnow 0.7.15", +] + +[[package]] +name = "toml_parser" +version = "1.1.2+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2abe9b86193656635d2411dc43050282ca48aa31c2451210f4202550afb7526" +dependencies = [ + "winnow 1.0.3", +] + +[[package]] +name = "toml_write" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d99f8c9a7727884afe522e9bd5edbfc91a3312b36a77b5fb8926e4c31a41801" + +[[package]] +name = "toml_writer" +version = "1.1.1+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "756daf9b1013ebe47a8776667b466417e2d4c5679d441c26230efd9ef78692db" + +[[package]] +name = "tracing" +version = "0.1.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100" +dependencies = [ + "log", + "pin-project-lite", + "tracing-attributes", + "tracing-core", +] + +[[package]] +name = "tracing-attributes" +version = "0.1.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tracing-core" +version = "0.1.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a" +dependencies = [ + "once_cell", +] + +[[package]] +name = "twox-hash" +version = "2.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ea3136b675547379c4bd395ca6b938e5ad3c3d20fad76e7fe85f9e0d011419c" + +[[package]] +name = "typenum" +version = "1.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "40ce102ab67701b8526c123c1bab5cbe42d7040ccfd0f64af1a385808d2f43de" + +[[package]] +name = "unicode-bidi" +version = "0.3.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c1cb5db39152898a79168971543b1cb5020dff7fe43c8dc468b0885f5e29df5" + +[[package]] +name = "unicode-ident" +version = "1.0.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" + +[[package]] +name = "unicode-normalization" +version = "0.1.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5fd4f6878c9cb28d874b009da9e8d183b5abc80117c40bbd187a1fde336be6e8" +dependencies = [ + "tinyvec", +] + +[[package]] +name = "unicode-properties" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7df058c713841ad818f1dc5d3fd88063241cc61f49f5fbea4b951e8cf5a8d71d" + +[[package]] +name = "unicode-xid" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" + +[[package]] +name = "untrusted" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" + +[[package]] +name = "url" +version = "2.5.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff67a8a4397373c3ef660812acab3268222035010ab8680ec4215f38ba3d0eed" +dependencies = [ + "form_urlencoded", + "idna", + "percent-encoding", + "serde", +] + +[[package]] +name = "utf8_iter" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" + +[[package]] +name = "utf8parse" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" + +[[package]] +name = "uuid" +version = "1.23.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ddd74a9687298c6858e9b88ec8935ec45d22e8fd5e6394fa1bd4e99a87789c76" +dependencies = [ + "getrandom 0.4.2", + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "vcpkg" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" + +[[package]] +name = "version_check" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" + +[[package]] +name = "wait-timeout" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ac3b126d3914f9849036f826e054cbabdc8519970b8998ddaf3b5bd3c65f11" +dependencies = [ + "libc", +] + +[[package]] +name = "walkdir" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" +dependencies = [ + "same-file", + "winapi-util", +] + +[[package]] +name = "wasi" +version = "0.11.1+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" + +[[package]] +name = "wasi" +version = "0.14.7+wasi-0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "883478de20367e224c0090af9cf5f9fa85bed63a95c1abf3afc5c083ebc06e8c" +dependencies = [ + "wasip2", +] + +[[package]] +name = "wasip2" +version = "1.0.3+wasi-0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "20064672db26d7cdc89c7798c48a0fdfac8213434a1186e5ef29fd560ae223d6" +dependencies = [ + "wit-bindgen 0.57.1", +] + +[[package]] +name = "wasip3" +version = "0.4.0+wasi-0.3.0-rc-2026-01-06" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5428f8bf88ea5ddc08faddef2ac4a67e390b88186c703ce6dbd955e1c145aca5" +dependencies = [ + "wit-bindgen 0.51.0", +] + +[[package]] +name = "wasite" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "66fe902b4a6b8028a753d5424909b764ccf79b7a209eac9bf97e59cda9f71a42" +dependencies = [ + "wasi 0.14.7+wasi-0.2.4", +] + +[[package]] +name = "wasm-bindgen" +version = "0.2.118" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bf938a0bacb0469e83c1e148908bd7d5a6010354cf4fb73279b7447422e3a89" +dependencies = [ + "cfg-if", + "once_cell", + "rustversion", + "wasm-bindgen-macro", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.118" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eeff24f84126c0ec2db7a449f0c2ec963c6a49efe0698c4242929da037ca28ed" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.118" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d08065faf983b2b80a79fd87d8254c409281cf7de75fc4b773019824196c904" +dependencies = [ + "bumpalo", + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.118" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5fd04d9e306f1907bd13c6361b5c6bfc7b3b3c095ed3f8a9246390f8dbdee129" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "wasm-encoder" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "990065f2fe63003fe337b932cfb5e3b80e0b4d0f5ff650e6985b1048f62c8319" +dependencies = [ + "leb128fmt", + "wasmparser", +] + +[[package]] +name = "wasm-metadata" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb0e353e6a2fbdc176932bbaab493762eb1255a7900fe0fea1a2f96c296cc909" +dependencies = [ + "anyhow", + "indexmap", + "wasm-encoder", + "wasmparser", +] + +[[package]] +name = "wasmparser" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47b807c72e1bac69382b3a6fb3dbe8ea4c0ed87ff5629b8685ae6b9a611028fe" +dependencies = [ + "bitflags", + "hashbrown 0.15.5", + "indexmap", + "semver", +] + +[[package]] +name = "web-sys" +version = "0.3.95" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4f2dfbb17949fa2088e5d39408c48368947b86f7834484e87b73de55bc14d97d" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "whoami" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6a5b12f9df4f978d2cfdb1bd3bac52433f44393342d7ee9c25f5a1c14c0f45d" +dependencies = [ + "libc", + "libredox", + "objc2-system-configuration", + "wasite", + "web-sys", +] + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-util" +version = "0.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" +dependencies = [ + "windows-sys 0.61.2", +] + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "windows" +version = "0.62.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "527fadee13e0c05939a6a05d5bd6eec6cd2e3dbd648b9f8e447c6518133d8580" +dependencies = [ + "windows-collections", + "windows-core", + "windows-future", + "windows-numerics", +] + +[[package]] +name = "windows-collections" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23b2d95af1a8a14a3c7367e1ed4fc9c20e0a26e79551b1454d72583c97cc6610" +dependencies = [ + "windows-core", +] + +[[package]] +name = "windows-core" +version = "0.62.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8e83a14d34d0623b51dce9581199302a221863196a1dde71a7663a4c2be9deb" +dependencies = [ + "windows-implement", + "windows-interface", + "windows-link", + "windows-result", + "windows-strings", +] + +[[package]] +name = "windows-future" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1d6f90251fe18a279739e78025bd6ddc52a7e22f921070ccdc67dde84c605cb" +dependencies = [ + "windows-core", + "windows-link", + "windows-threading", +] + +[[package]] +name = "windows-implement" +version = "0.60.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "windows-interface" +version = "0.59.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "windows-link" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" + +[[package]] +name = "windows-numerics" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e2e40844ac143cdb44aead537bbf727de9b044e107a0f1220392177d15b0f26" +dependencies = [ + "windows-core", + "windows-link", +] + +[[package]] +name = "windows-result" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7781fa89eaf60850ac3d2da7af8e5242a5ea78d1a11c49bf2910bb5a73853eb5" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-strings" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7837d08f69c77cf6b07689544538e017c1bfcf57e34b4c0ff58e6c2cd3b37091" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-sys" +version = "0.61.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-targets" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +dependencies = [ + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_gnullvm", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", +] + +[[package]] +name = "windows-threading" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3949bd5b99cafdf1c7ca86b43ca564028dfe27d66958f2470940f73d86d75b37" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" + +[[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_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" + +[[package]] +name = "winnow" +version = "0.7.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df79d97927682d2fd8adb29682d1140b343be4ac0f08fd68b7765d9c059d3945" +dependencies = [ + "memchr", +] + +[[package]] +name = "winnow" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0592e1c9d151f854e6fd382574c3a0855250e1d9b2f99d9281c6e6391af352f1" + +[[package]] +name = "wit-bindgen" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7249219f66ced02969388cf2bb044a09756a083d0fab1e566056b04d9fbcaa5" +dependencies = [ + "wit-bindgen-rust-macro", +] + +[[package]] +name = "wit-bindgen" +version = "0.57.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ebf944e87a7c253233ad6766e082e3cd714b5d03812acc24c318f549614536e" + +[[package]] +name = "wit-bindgen-core" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea61de684c3ea68cb082b7a88508a8b27fcc8b797d738bfc99a82facf1d752dc" +dependencies = [ + "anyhow", + "heck", + "wit-parser", +] + +[[package]] +name = "wit-bindgen-rust" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7c566e0f4b284dd6561c786d9cb0142da491f46a9fbed79ea69cdad5db17f21" +dependencies = [ + "anyhow", + "heck", + "indexmap", + "prettyplease", + "syn", + "wasm-metadata", + "wit-bindgen-core", + "wit-component", +] + +[[package]] +name = "wit-bindgen-rust-macro" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c0f9bfd77e6a48eccf51359e3ae77140a7f50b1e2ebfe62422d8afdaffab17a" +dependencies = [ + "anyhow", + "prettyplease", + "proc-macro2", + "quote", + "syn", + "wit-bindgen-core", + "wit-bindgen-rust", +] + +[[package]] +name = "wit-component" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d66ea20e9553b30172b5e831994e35fbde2d165325bec84fc43dbf6f4eb9cb2" +dependencies = [ + "anyhow", + "bitflags", + "indexmap", + "log", + "serde", + "serde_derive", + "serde_json", + "wasm-encoder", + "wasm-metadata", + "wasmparser", + "wit-parser", +] + +[[package]] +name = "wit-parser" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ecc8ac4bc1dc3381b7f59c34f00b67e18f910c2c0f50015669dde7def656a736" +dependencies = [ + "anyhow", + "id-arena", + "indexmap", + "log", + "semver", + "serde", + "serde_derive", + "serde_json", + "unicode-xid", + "wasmparser", +] + +[[package]] +name = "writeable" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ffae5123b2d3fc086436f8834ae3ab053a283cfac8fe0a0b8eaae044768a4c4" + +[[package]] +name = "x509-cert" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1301e935010a701ae5f8655edc0ad17c44bad3ac5ce8c39185f75453b720ae94" +dependencies = [ + "const-oid 0.9.6", + "der", + "spki", + "tls_codec", +] + +[[package]] +name = "yoke" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "abe8c5fda708d9ca3df187cae8bfb9ceda00dd96231bed36e445a1a48e66f9ca" +dependencies = [ + "stable_deref_trait", + "yoke-derive", + "zerofrom", +] + +[[package]] +name = "yoke-derive" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "de844c262c8848816172cef550288e7dc6c7b7814b4ee56b3e1553f275f1858e" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "synstructure", +] + +[[package]] +name = "zerocopy" +version = "0.8.48" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eed437bf9d6692032087e337407a86f04cd8d6a16a37199ed57949d415bd68e9" +dependencies = [ + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.8.48" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70e3cd084b1788766f53af483dd21f93881ff30d7320490ec3ef7526d203bad4" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "zerofrom" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69faa1f2a1ea75661980b013019ed6687ed0e83d069bc1114e2cc74c6c04c4df" +dependencies = [ + "zerofrom-derive", +] + +[[package]] +name = "zerofrom-derive" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11532158c46691caf0f2593ea8358fed6bbf68a0315e80aae9bd41fbade684a1" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "synstructure", +] + +[[package]] +name = "zeroize" +version = "1.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b97154e67e32c85465826e8bcc1c59429aaaf107c1e4a9e53c8d8ccd5eff88d0" +dependencies = [ + "zeroize_derive", +] + +[[package]] +name = "zeroize_derive" +version = "1.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85a5b4158499876c763cb03bc4e49185d3cccbabb15b33c627f7884f43db852e" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "zerotrie" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f9152d31db0792fa83f70fb2f83148effb5c1f5b8c7686c3459e361d9bc20bf" +dependencies = [ + "displaydoc", + "yoke", + "zerofrom", +] + +[[package]] +name = "zerovec" +version = "0.11.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90f911cbc359ab6af17377d242225f4d75119aec87ea711a880987b18cd7b239" +dependencies = [ + "yoke", + "zerofrom", + "zerovec-derive", +] + +[[package]] +name = "zerovec-derive" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "625dc425cab0dca6dc3c3319506e6593dcb08a9f387ea3b284dbd52a92c40555" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "zmij" +version = "1.0.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa" diff --git a/Cargo.toml b/Cargo.toml index c23c3fab..b7334254 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,7 +1,16 @@ [workspace] +resolver = "2" members = [ "refinery", "refinery_cli", "refinery_core", - "refinery_macros" + "refinery_macros", + "examples", ] + +[workspace.package] +rust-version = "1.85" + +[profile.release] +codegen-units = 1 +lto = true diff --git a/README.md b/README.md index 6f91bbfd..12cd571e 100644 --- a/README.md +++ b/README.md @@ -35,6 +35,13 @@ If you are using a driver that is not yet supported, namely [`SQLx`](https://git - Migrations, both .sql files and Rust modules must be named in the format `[U|V]{1}__{2}.sql` or `[U|V]{1}__{2}.rs`, where `{1}` represents the migration version and `{2}` the name. - Migrations can be run either by embedding them in your Rust code with `embed_migrations` macro, or via [refinery_cli]. +NOTE: + +- By default, migration version numbers are restricted to `i32` (signed, 32-bit integers). +- If you enable the `int8-versions` feature, this restriction is lifted to being able to use `i64`s for your migration version numbers. + Bear in mind that this feature must be enabled *before* you start using refinery on a given database. + Migrating an existing database's `refinery_schema_history` table to use `int8` versions **will break the checksums on all previously-applied migrations**. + ### Example: Library ```rust,no_run use rusqlite::Connection; @@ -50,19 +57,13 @@ fn main() { } ``` -For more library examples, refer to the [`examples`](examples). - +For more library examples, refer to the [`examples`](https://github.com/rust-db/refinery/tree/main/examples). ### Example: CLI -NOTE: - -- Contiguous (adjacent) migration version numbers are restricted to `u32` (unsigned, 32-bit integers). -- Non-contiguous (not adjacent) migration version numbers are restricted to `u32` (unsigned, 32-bit integers). - ```bash export DATABASE_URL="postgres://postgres:secret@localhost:5432/your-db" pushd migrations - # Runs ./src/V1__*.rs or ./src/V1__*.sql + # Runs ./src/V1__*.rs or ./src/V1__*.sql refinery migrate -e DATABASE_URL -p ./src -t 1 popd ``` @@ -75,6 +76,13 @@ let client = conn.deref_mut().deref_mut(); let report = embedded::migrations::runner().run_async(client).await?; ``` +### Example: bb8 + +```rust +let mut client = pool.dedicated_connection().await?; +let report = embedded::migrations::runner().run_async(&mut client).await?; +``` + ### Non-contiguous VS Contiguous migrations Depending on how your project/team has been structured will define whether you want to use contiguous (adjacent) migrations `V{1}__{2}.[sql|rs]` or non-contiguous (not adjacent) migrations `U{1}__{2}.[sql|rs]`. @@ -90,21 +98,25 @@ This would stop developer 1's migration from ever running if you were using cont refinery works by creating a table that keeps all the applied migrations' versions and their metadata. When you [run](https://docs.rs/refinery/latest/refinery/struct.Runner.html#method.run) the migrations `Runner`, refinery compares the applied migrations with the ones to be applied, checking for [divergent](https://docs.rs/refinery/latest/refinery/struct.Runner.html#method.set_abort_divergent) and [missing](https://docs.rs/refinery/latest/refinery/struct.Runner.html#method.set_abort_missing) and executing unapplied migrations.\ By default, refinery runs each migration in a single transaction. Alternatively, you can also configure refinery to wrap the entire execution of all migrations in a single transaction by setting [set_grouped](https://docs.rs/refinery/latest/refinery/struct.Runner.html#method.set_grouped) to true. +The rust crate intentionally ignores new migration files until your sourcecode is rebuild. This prevents accidental migrations and altering the database schema without any code changes. We can also bake the migrations into the binary, so no additional files are needed when deployed. ### Rollback refinery's design was based on [flyway](https://flywaydb.org/) and so, it shares its earlier [philosophy](https://web.archive.org/web/20191226033347/https://flywaydb.org/documentation/command/undo#important-notes) on undo/rollback migrations. Flyway has since changed it's opinion but refinery hasn't. To undo/rollback a migration, you have to generate a new one and write specifically what you want to undo. -## MSRV +## Support for Additional Database Drivers -refinery aims to support stable Rust, the previous Rust version, and nightly. +While initially it seemed beneficial to support as many aditional drivers as possible in this repo, with the current bandwidth available by the maintainers it's preferable to create them and maintain them on external repositories (see [here](https://github.com/rust-db/refinery/pull/264#issuecomment-1419198667) for context). + +Notable external database drivers: -## Async +- [Klickhouse](https://github.com/Protryon/klickhouse) ([Clickhouse](https://clickhouse.tech/docs/en/) database driver with refinery support) -Starting with version 0.2 refinery supports [tokio-postgres](https://crates.io/crates/tokio-postgres), [`mysql_async`](https://crates.io/crates/mysql_async) -and [Tiberius](https://github.com/prisma/tiberius) -For Rusqlite, the best way to run migrations in an async context is to run them inside tokio's [`spawn_blocking`](https://docs.rs/tokio/1.10.0/tokio/task/fn.spawn_blocking.html) for example. + +## MSRV + +refinery aims to support stable Rust, the previous Rust version, and nightly. ## Contributing diff --git a/deny.toml b/deny.toml new file mode 100644 index 00000000..fdcce230 --- /dev/null +++ b/deny.toml @@ -0,0 +1,115 @@ +# This section is considered when running `cargo deny check advisories` +# More documentation for the advisories section can be found here: +# https://embarkstudios.github.io/cargo-deny/checks/advisories/cfg.html +[advisories] +# Version of the advisory config. See https://github.com/EmbarkStudios/cargo-deny/pull/611 +version = 2 +# The path where the advisory database is cloned/fetched into +db-path = "~/cargo/advisory-db" +# The url of the advisory database to use +db-urls = ["https://github.com/rustsec/advisory-db"] +# The lint level for crates that have been yanked from their source registry +yanked = "warn" +# A list of advisory IDs to ignore. Note that ignored advisories will still +# output a note when they are encountered. +ignore = [] +# Threshold for security vulnerabilities, any vulnerability with a CVSS score +# lower than the range specified will be ignored. Note that ignored advisories +# will still output a note when they are encountered. +# * None - CVSS Score 0.0 +# * Low - CVSS Score 0.1 - 3.9 +# * Medium - CVSS Score 4.0 - 6.9 +# * High - CVSS Score 7.0 - 8.9 +# * Critical - CVSS Score 9.0 - 10.0 +#severity-threshold = + +# This section is considered when running `cargo deny check licenses` +# More documentation for the licenses section can be found here: +# https://embarkstudios.github.io/cargo-deny/checks/licenses/cfg.html +[licenses] +# Version of the license config. See https://github.com/EmbarkStudios/cargo-deny/pull/611 +version = 2 +# List of explicitly allowed licenses +# See https://spdx.org/licenses/ for list of possible licenses +# [possible values: any SPDX 3.11 short identifier (+ optional exception)]. +allow = [ + "Apache-2.0 WITH LLVM-exception", + "Apache-2.0", + "BSD-2-Clause", + "BSD-3-Clause", + "ISC", + "MIT", + "Unlicense", + "Unicode-3.0", + "Zlib", +] +# The confidence threshold for detecting a license from license text. +# The higher the value, the more closely the license text must be to the +# canonical license text of a valid SPDX license file. +# [possible values: any between 0.0 and 1.0]. +confidence-threshold = 0.8 +# Allow 1 or more licenses on a per-crate basis, so that particular licenses +# aren't accepted for every possible crate as with the normal allow list +exceptions = [] + +# Some crates don't have (easily) machine readable licensing information, +# adding a clarification entry for it allows you to manually specify the +# licensing information +[[licenses.clarify]] +name = "ring" +expression = "ISC AND MIT AND OpenSSL" +license-files = [{ path = "LICENSE", hash = 0xbd0eed23 }] + +[licenses.private] +# If true, ignores workspace crates that aren't published, or are only +# published to private registries +ignore = false +# One or more private registries that you might publish crates to, if a crate +# is only published to private registries, and ignore is true, the crate will +# not have its license(s) checked +#registries = [ +#] + +# This section is considered when running `cargo deny check bans`. +# More documentation about the 'bans' section can be found here: +# https://embarkstudios.github.io/cargo-deny/checks/bans/cfg.html +[bans] +# Lint level for when multiple versions of the same crate are detected +multiple-versions = "warn" +# The graph highlighting used when creating dotgraphs for crates +# with multiple versions +# * lowest-version - The path to the lowest versioned duplicate is highlighted +# * simplest-path - The path to the version with the fewest edges is highlighted +# * all - Both lowest-version and simplest-path are used +highlight = "all" +# List of crates that are allowed. Use with care! +#allow = [ +#] +# List of crates to deny +#deny = [ +#] +# Certain crates/versions that will be skipped when doing duplicate detection. +#skip = [ +#] +# Similarly to `skip` allows you to skip certain crates during duplicate +# detection. Unlike skip, it also includes the entire tree of transitive +# dependencies starting at the specified crate, up to a certain depth, which is +# by default infinite +#skip-tree = [ +#] + +# This section is considered when running `cargo deny check sources`. +# More documentation about the 'sources' section can be found here: +# https://embarkstudios.github.io/cargo-deny/checks/sources/cfg.html +[sources] +# Lint level for what to happen when a crate from a crate registry that is not +# in the allow list is encountered +unknown-registry = "deny" +# Lint level for what to happen when a crate from a git repository that is not +# in the allow list is encountered +unknown-git = "deny" +# List of URLs for allowed crate registries. Defaults to the crates.io index +# if not specified. If it is specified but empty, no registries are allowed. +allow-registry = ["https://github.com/rust-lang/crates.io-index"] +# List of URLs for allowed Git repositories +allow-git = ["https://github.com/jxs/barrel"] diff --git a/examples/Cargo.toml b/examples/Cargo.toml new file mode 100644 index 00000000..e70f1d92 --- /dev/null +++ b/examples/Cargo.toml @@ -0,0 +1,21 @@ +[package] +name = "refinery-examples" +version = "0.8.12" +authors = ["Katharina Fey ", "João Oliveira "] +description = "Minimal Refinery usage example" +license = "MIT OR Apache-2.0" +documentation = "https://docs.rs/refinery/" +repository = "https://github.com/rust-db/refinery" +edition = "2021" +publish = false + +[features] +enums = ["refinery/enums"] +int8-versions = ["refinery/int8-versions"] + +[dependencies] +refinery = { path = "../refinery", features = ["rusqlite"] } +rusqlite = "0.39" +barrel = { version = "0.7", features = ["sqlite3"] } +log = "0.4" +env_logger = "0.11" diff --git a/examples/main.rs b/examples/main.rs deleted file mode 100644 index 6d4948bd..00000000 --- a/examples/main.rs +++ /dev/null @@ -1,18 +0,0 @@ -use rusqlite::Connection; - -mod embedded { - use refinery::embed_migrations; - embed_migrations!("refinery/examples/embedded/migrations"); -} - -fn main() { - let mut conn = Connection::open_in_memory().unwrap(); - - // run all migrations in one go - embedded::migrations::runner().run(&mut conn).unwrap(); - - // or create an iterator over migrations as they run - for migration in embedded::migrations::runner().run_iter(&mut conn) { - info!("Got a migration: {}", migration.expect("migration failed!")); - } -} diff --git a/examples/src/main.rs b/examples/src/main.rs new file mode 100644 index 00000000..7b650639 --- /dev/null +++ b/examples/src/main.rs @@ -0,0 +1,41 @@ +use log::info; +use refinery::Migration; +use rusqlite::Connection; + +refinery::embed_migrations!("migrations"); + +fn main() { + env_logger::init(); + + let mut conn = Connection::open_in_memory().unwrap(); + + let use_iteration = std::env::args().any(|a| a.to_lowercase().eq("--iterate")); + + if use_iteration { + // create an iterator over migrations as they run + for migration in migrations::runner().run_iter(&mut conn) { + process_migration(migration.expect("Migration failed!")); + } + } else { + // or run all migrations in one go + migrations::runner().run(&mut conn).unwrap(); + } +} + +fn process_migration(migration: Migration) { + #[cfg(not(feature = "enums"))] + { + // run something after each migration + info!("Post-processing a migration: {}", migration) + } + + #[cfg(feature = "enums")] + { + // or with the `enums` feature enabled, match against migrations to run specific post-migration steps + use migrations::EmbeddedMigration; + match migration.into() { + EmbeddedMigration::Initial(m) => info!("V{}: Initialized the database!", m.version()), + m => info!("Got a migration: {:?}", m), + } + } +} diff --git a/refinery/Cargo.toml b/refinery/Cargo.toml index 0a054d4c..7471624c 100644 --- a/refinery/Cargo.toml +++ b/refinery/Cargo.toml @@ -1,7 +1,6 @@ [package] name = "refinery" -version = "0.8.12" -rust-version = "1.56" +version = "0.9.2" authors = ["Katharina Fey ", "João Oliveira "] license = "MIT" description = "Powerful SQL migration toolkit for Rust" @@ -14,20 +13,26 @@ edition = "2018" [features] default = ["toml"] -rusqlite-bundled = ["refinery-core/rusqlite-bundled"] -rusqlite = ["refinery-core/rusqlite"] +config = ["refinery-core/config"] +rusqlite-bundled = ["refinery-core/rusqlite-bundled", "config"] +rusqlite = ["refinery-core/rusqlite", "config"] +postgres-tls = ["refinery-core/postgres-tls", "config"] postgres = ["refinery-core/postgres"] -mysql = ["refinery-core/mysql"] +mysql = ["refinery-core/mysql", "config"] +tokio-postgres-tls = ["tokio-postgres", "refinery-core/tokio-postgres-tls"] tokio-postgres = ["refinery-core/tokio-postgres"] -mysql_async = ["refinery-core/mysql_async"] -tiberius = ["refinery-core/tiberius"] -tiberius-config = ["refinery-core/tiberius", "refinery-core/tiberius-config"] +tokio-postgres-rustls = ["tokio-postgres", "refinery-core/tokio-postgres-rustls"] +mysql_async = ["refinery-core/mysql_async", "config"] +tiberius = ["refinery-core/tiberius", "config"] +tiberius-config = ["refinery-core/tiberius", "refinery-core/tiberius-config", "config"] serde = ["refinery-core/serde"] toml = ["refinery-core/toml"] +enums = ["refinery-macros/enums"] +int8-versions = ["refinery-core/int8-versions", "refinery-macros/int8-versions"] [dependencies] -refinery-core = { version = "0.8.12", path = "../refinery_core" } -refinery-macros = { version = "0.8.12", path = "../refinery_macros" } +refinery-core = { version = "0.9.2", path = "../refinery_core", default-features = false } +refinery-macros = { version = "0.9.2", path = "../refinery_macros" } [dev-dependencies] barrel = { git = "https://github.com/jxs/barrel", features = ["sqlite3", "pg", "mysql", "mssql"] } @@ -38,3 +43,4 @@ tempfile = "3" time = "0.3.5" tokio-util = { version = "0.7.7", features = ["compat"] } tokio = { version = "1.9.0", features = ["full"] } +rustls = { version = "0.23", default-features = false, features = ["ring"] } diff --git a/refinery/src/lib.rs b/refinery/src/lib.rs index a3a4d780..569e612a 100644 --- a/refinery/src/lib.rs +++ b/refinery/src/lib.rs @@ -31,8 +31,11 @@ embedded::migrations::runner().run(&mut conn).unwrap(); for more examples refer to the [examples](https://github.com/rust-db/refinery/tree/master/examples) */ +#[cfg(feature = "config")] pub use refinery_core::config; -pub use refinery_core::{error, Error, Migration, Report, Runner, Target}; +pub use refinery_core::{ + error, load_sql_migrations, Error, Migration, Report, Runner, SchemaVersion, Target, +}; #[doc(hidden)] pub use refinery_core::{AsyncMigrate, Migrate}; pub use refinery_macros::embed_migrations; diff --git a/refinery/tests/migrations_int8/U20240504090241__initial.rs b/refinery/tests/migrations_int8/U20240504090241__initial.rs new file mode 100644 index 00000000..3a84e506 --- /dev/null +++ b/refinery/tests/migrations_int8/U20240504090241__initial.rs @@ -0,0 +1,15 @@ +use barrel::{types, Migration}; + +use crate::Sql; + +pub fn migration() -> String { + let mut m = Migration::new(); + + m.create_table("persons", |t| { + t.add_column("id", types::primary()); + t.add_column("name", types::varchar(255)); + t.add_column("city", types::varchar(255)); + }); + + m.make::() +} diff --git a/refinery/tests/migrations_int8/U20240504090301__add_cars_and_motos_table.sql b/refinery/tests/migrations_int8/U20240504090301__add_cars_and_motos_table.sql new file mode 100644 index 00000000..13390796 --- /dev/null +++ b/refinery/tests/migrations_int8/U20240504090301__add_cars_and_motos_table.sql @@ -0,0 +1,8 @@ +CREATE TABLE cars ( + id int, + name varchar(255) +); +CREATE TABLE motos ( + id int, + name varchar(255) +); diff --git a/refinery/tests/migrations_int8/U20240504090322__add_brand_to_cars_table.sql b/refinery/tests/migrations_int8/U20240504090322__add_brand_to_cars_table.sql new file mode 100644 index 00000000..c689a6aa --- /dev/null +++ b/refinery/tests/migrations_int8/U20240504090322__add_brand_to_cars_table.sql @@ -0,0 +1,2 @@ +ALTER TABLE cars +ADD brand varchar(255); diff --git a/refinery/tests/migrations_int8/U20240504090343__add_year_to_motos_table.rs b/refinery/tests/migrations_int8/U20240504090343__add_year_to_motos_table.rs new file mode 100644 index 00000000..57623df9 --- /dev/null +++ b/refinery/tests/migrations_int8/U20240504090343__add_year_to_motos_table.rs @@ -0,0 +1,13 @@ +use barrel::{types, Migration}; + +use crate::Sql; + +pub fn migration() -> String { + let mut m = Migration::new(); + + m.change_table("motos", |t| { + t.add_column("brand", types::varchar(255).nullable(true)); + }); + + m.make::() +} diff --git a/refinery/tests/mysql.rs b/refinery/tests/mysql.rs index 0bfdf971..de2a25d6 100644 --- a/refinery/tests/mysql.rs +++ b/refinery/tests/mysql.rs @@ -78,7 +78,7 @@ mod mysql { where T: FnOnce() + std::panic::UnwindSafe, { - let result = std::panic::catch_unwind(|| test()); + let result = std::panic::catch_unwind(test); clean_database(); @@ -125,8 +125,7 @@ mod mysql { let mut conn = pool.get_conn().unwrap(); embedded::migrations::runner().run(&mut conn).unwrap(); for row in format!( - "SELECT table_name FROM information_schema.tables WHERE table_name='{}'", - DEFAULT_TABLE_NAME + "SELECT table_name FROM information_schema.tables WHERE table_name='{DEFAULT_TABLE_NAME}'", ) .run(conn) .unwrap() @@ -150,8 +149,7 @@ mod mysql { .unwrap(); for row in format!( - "SELECT table_name FROM information_schema.tables WHERE table_name='{}'", - DEFAULT_TABLE_NAME + "SELECT table_name FROM information_schema.tables WHERE table_name='{DEFAULT_TABLE_NAME}'", ) .run(conn) .unwrap() @@ -767,7 +765,7 @@ mod mysql { // cli only finds .sql migration files run_test(|| { Command::new("refinery") - .args(&[ + .args([ "migrate", "-c", "tests/mysql_refinery.toml", diff --git a/refinery/tests/mysql_async.rs b/refinery/tests/mysql_async.rs index 4b3e9cff..a9069b7c 100644 --- a/refinery/tests/mysql_async.rs +++ b/refinery/tests/mysql_async.rs @@ -94,9 +94,8 @@ mod mysql_async { .await .unwrap(); - conn.query(&format!( - "SELECT table_name FROM information_schema.tables WHERE table_name='{}'", - DEFAULT_TABLE_NAME + conn.query(format!( + "SELECT table_name FROM information_schema.tables WHERE table_name='{DEFAULT_TABLE_NAME}'", )) .await .unwrap() @@ -122,9 +121,8 @@ mod mysql_async { .unwrap(); let result = conn - .query(&format!( - "SELECT table_name FROM information_schema.tables WHERE table_name='{}'", - DEFAULT_TABLE_NAME + .query(format!( + "SELECT table_name FROM information_schema.tables WHERE table_name='{DEFAULT_TABLE_NAME}'", )) .await .unwrap(); diff --git a/refinery/tests/postgres.rs b/refinery/tests/postgres.rs index 39321ed8..00793694 100644 --- a/refinery/tests/postgres.rs +++ b/refinery/tests/postgres.rs @@ -4,14 +4,13 @@ use barrel::backend::Pg as Sql; mod postgres { use assert_cmd::prelude::*; use predicates::str::contains; + use refinery::config::ConfigDbType; use refinery::{ - config::{Config, ConfigDbType}, - embed_migrations, - error::Kind, - Migrate, Migration, Runner, Target, + config::Config, embed_migrations, error::Kind, Migrate, Migration, Runner, Target, }; use refinery_core::postgres::{Client, NoTls}; use std::process::Command; + use std::str::FromStr; use time::OffsetDateTime; const DEFAULT_TABLE_NAME: &str = "refinery_schema_history"; @@ -31,6 +30,16 @@ mod postgres { embed_migrations!("./tests/migrations_missing"); } + #[cfg(feature = "int8-versions")] + mod int8 { + use refinery::embed_migrations; + embed_migrations!("./tests/migrations_int8"); + } + + fn db_uri() -> String { + std::env::var("DB_URI").unwrap_or("postgres://postgres@localhost:5432/postgres".to_string()) + } + fn get_migrations() -> Vec { embed_migrations!("./tests/migrations"); @@ -64,36 +73,30 @@ mod postgres { vec![migration1, migration2, migration3, migration4, migration5] } - fn clean_database() { - let mut client = - Client::connect("postgres://postgres@localhost:5432/template1", NoTls).unwrap(); + fn prep_database() { + let mut client = Client::connect(&db_uri(), NoTls).unwrap(); client - .execute( - "SELECT pg_terminate_backend(pid) FROM pg_stat_activity WHERE datname='postgres'", - &[], - ) + .execute("DROP SCHEMA IF EXISTS public CASCADE", &[]) + .unwrap(); + client + .execute("CREATE SCHEMA IF NOT EXISTS public", &[]) .unwrap(); - client.execute("DROP DATABASE POSTGRES", &[]).unwrap(); - client.execute("CREATE DATABASE POSTGRES", &[]).unwrap(); } fn run_test(test: T) where - T: FnOnce() + std::panic::UnwindSafe, + T: FnOnce(), { - let result = std::panic::catch_unwind(|| test()); - - clean_database(); + prep_database(); - assert!(result.is_ok()) + test(); } #[test] fn report_contains_applied_migrations() { run_test(|| { - let mut client = - Client::connect("postgres://postgres@localhost:5432/postgres", NoTls).unwrap(); + let mut client = Client::connect(&db_uri(), NoTls).unwrap(); let report = embedded::migrations::runner().run(&mut client).unwrap(); @@ -122,14 +125,12 @@ mod postgres { #[test] fn creates_migration_table() { run_test(|| { - let mut client = - Client::connect("postgres://postgres@localhost:5432/postgres", NoTls).unwrap(); + let mut client = Client::connect(&db_uri(), NoTls).unwrap(); embedded::migrations::runner().run(&mut client).unwrap(); for row in &client .query( &format!( - "SELECT table_name FROM information_schema.tables WHERE table_name='{}'", - DEFAULT_TABLE_NAME + "SELECT table_name FROM information_schema.tables WHERE table_name='{DEFAULT_TABLE_NAME}'", ), &[], ) @@ -144,8 +145,7 @@ mod postgres { #[test] fn creates_migration_table_grouped_transaction() { run_test(|| { - let mut client = - Client::connect("postgres://postgres@localhost:5432/postgres", NoTls).unwrap(); + let mut client = Client::connect(&db_uri(), NoTls).unwrap(); embedded::migrations::runner() .set_grouped(true) @@ -155,8 +155,7 @@ mod postgres { for row in &client .query( &format!( - "SELECT table_name FROM information_schema.tables WHERE table_name='{}'", - DEFAULT_TABLE_NAME + "SELECT table_name FROM information_schema.tables WHERE table_name='{DEFAULT_TABLE_NAME}'", ), &[], ) @@ -171,8 +170,7 @@ mod postgres { #[test] fn applies_migration() { run_test(|| { - let mut client = - Client::connect("postgres://postgres@localhost:5432/postgres", NoTls).unwrap(); + let mut client = Client::connect(&db_uri(), NoTls).unwrap(); embedded::migrations::runner().run(&mut client).unwrap(); client .execute( @@ -189,11 +187,41 @@ mod postgres { }); } + #[test] + #[cfg(feature = "int8-versions")] + fn applies_migration_int8() { + run_test(|| { + let mut client = Client::connect(&db_uri(), NoTls).unwrap(); + let report = int8::migrations::runner().run(&mut client).unwrap(); + + let applied_migrations = report.applied_migrations(); + + assert_eq!(4, applied_migrations.len()); + + assert_eq!(20240504090241, applied_migrations[0].version()); + assert_eq!(20240504090301, applied_migrations[1].version()); + assert_eq!(20240504090322, applied_migrations[2].version()); + assert_eq!(20240504090343, applied_migrations[3].version()); + + client + .execute( + "INSERT INTO persons (name, city) VALUES ($1, $2)", + &[&"John Legend", &"New York"], + ) + .unwrap(); + for row in &client.query("SELECT name, city FROM persons", &[]).unwrap() { + let name: String = row.get(0); + let city: String = row.get(1); + assert_eq!("John Legend", name); + assert_eq!("New York", city); + } + }); + } + #[test] fn applies_migration_grouped_transaction() { run_test(|| { - let mut client = - Client::connect("postgres://postgres@localhost:5432/postgres", NoTls).unwrap(); + let mut client = Client::connect(&db_uri(), NoTls).unwrap(); embedded::migrations::runner() .set_grouped(false) @@ -218,8 +246,7 @@ mod postgres { #[test] fn updates_schema_history() { run_test(|| { - let mut client = - Client::connect("postgres://postgres@localhost:5432/postgres", NoTls).unwrap(); + let mut client = Client::connect(&db_uri(), NoTls).unwrap(); embedded::migrations::runner().run(&mut client).unwrap(); @@ -239,8 +266,7 @@ mod postgres { #[test] fn updates_schema_history_grouped_transaction() { run_test(|| { - let mut client = - Client::connect("postgres://postgres@localhost:5432/postgres", NoTls).unwrap(); + let mut client = Client::connect(&db_uri(), NoTls).unwrap(); embedded::migrations::runner() .set_grouped(false) @@ -262,13 +288,12 @@ mod postgres { #[test] fn updates_to_last_working_if_not_grouped() { run_test(|| { - let mut client = - Client::connect("postgres://postgres@localhost:5432/postgres", NoTls).unwrap(); + let mut client = Client::connect(&db_uri(), NoTls).unwrap(); let result = broken::migrations::runner().run(&mut client); assert!(result.is_err()); - println!("CURRENT: {:?}", result); + println!("CURRENT: {result:?}"); let current = client .get_last_applied_migration(DEFAULT_TABLE_NAME) @@ -292,23 +317,29 @@ mod postgres { assert_eq!("initial", migrations[0].name()); assert_eq!("add_cars_table", applied_migrations[1].name()); + #[cfg(not(feature = "int8-versions"))] assert_eq!(2959965718684201605, applied_migrations[0].checksum()); + #[cfg(feature = "int8-versions")] + assert_eq!(13938959368620441626, applied_migrations[0].checksum()); + + #[cfg(not(feature = "int8-versions"))] assert_eq!(8238603820526370208, applied_migrations[1].checksum()); + #[cfg(feature = "int8-versions")] + assert_eq!(5394706226941044339, applied_migrations[1].checksum()); }); } #[test] fn doesnt_update_to_last_working_if_grouped() { run_test(|| { - let mut client = - Client::connect("postgres://postgres@localhost:5432/postgres", NoTls).unwrap(); + let mut client = Client::connect(&db_uri(), NoTls).unwrap(); let result = broken::migrations::runner() .set_grouped(true) .run(&mut client); assert!(result.is_err()); - println!("CURRENT: {:?}", result); + println!("CURRENT: {result:?}"); let query = &client .query("SELECT version FROM refinery_schema_history", &[]) @@ -320,8 +351,7 @@ mod postgres { #[test] fn gets_applied_migrations() { run_test(|| { - let mut client = - Client::connect("postgres://postgres@localhost:5432/postgres", NoTls).unwrap(); + let mut client = Client::connect(&db_uri(), NoTls).unwrap(); embedded::migrations::runner().run(&mut client).unwrap(); @@ -349,8 +379,7 @@ mod postgres { #[test] fn applies_new_migration() { run_test(|| { - let mut client = - Client::connect("postgres://postgres@localhost:5432/postgres", NoTls).unwrap(); + let mut client = Client::connect(&db_uri(), NoTls).unwrap(); embedded::migrations::runner().run(&mut client).unwrap(); @@ -381,8 +410,7 @@ mod postgres { #[test] fn migrates_to_target_migration() { run_test(|| { - let mut client = - Client::connect("postgres://postgres@localhost:5432/postgres", NoTls).unwrap(); + let mut client = Client::connect(&db_uri(), NoTls).unwrap(); let report = embedded::migrations::runner() .set_target(Target::Version(3)) @@ -417,8 +445,7 @@ mod postgres { #[test] fn migrates_to_target_migration_grouped() { run_test(|| { - let mut client = - Client::connect("postgres://postgres@localhost:5432/postgres", NoTls).unwrap(); + let mut client = Client::connect(&db_uri(), NoTls).unwrap(); let report = embedded::migrations::runner() .set_target(Target::Version(3)) @@ -454,8 +481,7 @@ mod postgres { #[test] fn aborts_on_missing_migration_on_filesystem() { run_test(|| { - let mut client = - Client::connect("postgres://postgres@localhost:5432/postgres", NoTls).unwrap(); + let mut client = Client::connect(&db_uri(), NoTls).unwrap(); embedded::migrations::runner().run(&mut client).unwrap(); @@ -488,8 +514,7 @@ mod postgres { #[test] fn aborts_on_divergent_migration() { run_test(|| { - let mut client = - Client::connect("postgres://postgres@localhost:5432/postgres", NoTls).unwrap(); + let mut client = Client::connect(&db_uri(), NoTls).unwrap(); embedded::migrations::runner().run(&mut client).unwrap(); @@ -523,8 +548,7 @@ mod postgres { #[test] fn aborts_on_missing_migration_on_database() { run_test(|| { - let mut client = - Client::connect("postgres://postgres@localhost:5432/postgres", NoTls).unwrap(); + let mut client = Client::connect(&db_uri(), NoTls).unwrap(); missing::migrations::runner().run(&mut client).unwrap(); @@ -568,11 +592,7 @@ mod postgres { #[test] fn migrates_from_config() { run_test(|| { - let mut config = Config::new(ConfigDbType::Postgres) - .set_db_name("postgres") - .set_db_user("postgres") - .set_db_host("localhost") - .set_db_port("5432"); + let mut config = Config::from_str(&db_uri()).unwrap(); let migrations = get_migrations(); let runner = Runner::new(&migrations) @@ -608,11 +628,7 @@ mod postgres { #[test] fn migrate_from_config_report_contains_migrations() { run_test(|| { - let mut config = Config::new(ConfigDbType::Postgres) - .set_db_name("postgres") - .set_db_user("postgres") - .set_db_host("localhost") - .set_db_port("5432"); + let mut config = Config::from_str(&db_uri()).unwrap(); let migrations = get_migrations(); let runner = Runner::new(&migrations) @@ -648,11 +664,7 @@ mod postgres { #[test] fn migrate_from_config_report_returns_last_applied_migration() { run_test(|| { - let mut config = Config::new(ConfigDbType::Postgres) - .set_db_name("postgres") - .set_db_user("postgres") - .set_db_host("localhost") - .set_db_port("5432"); + let mut config = Config::from_str(&db_uri()).unwrap(); let migrations = get_migrations(); let runner = Runner::new(&migrations) @@ -677,8 +689,7 @@ mod postgres { #[test] fn doesnt_run_migrations_if_fake() { run_test(|| { - let mut client = - Client::connect("postgres://postgres@localhost:5432/postgres", NoTls).unwrap(); + let mut client = Client::connect(&db_uri(), NoTls).unwrap(); let report = embedded::migrations::runner() .set_target(Target::Fake) @@ -712,8 +723,7 @@ mod postgres { #[test] fn doesnt_run_migrations_if_fake_version() { run_test(|| { - let mut client = - Client::connect("postgres://postgres@localhost:5432/postgres", NoTls).unwrap(); + let mut client = Client::connect(&db_uri(), NoTls).unwrap(); let report = embedded::migrations::runner() .set_target(Target::FakeVersion(2)) @@ -748,7 +758,7 @@ mod postgres { fn migrates_from_cli() { run_test(|| { Command::new("refinery") - .args(&[ + .args([ "migrate", "-c", "tests/postgres_refinery.toml", @@ -761,4 +771,38 @@ mod postgres { .stdout(contains("applying migration: V3__add_brand_to_cars_table")); }) } + + #[test] + fn migrates_with_tls_enabled() { + run_test(|| { + let mut config = Config::new(ConfigDbType::Postgres) + .set_db_name("postgres") + .set_db_user("postgres") + .set_db_host("localhost") + .set_db_port("5432") + .set_use_tls(true); + + let migrations = get_migrations(); + let runner = Runner::new(&migrations) + .set_grouped(false) + .set_abort_divergent(true) + .set_abort_missing(true); + + let report = runner.run(&mut config).unwrap(); + + let applied_migrations = report.applied_migrations(); + assert_eq!(5, applied_migrations.len()); + + let last_migration = runner + .get_last_applied_migration(&mut config) + .unwrap() + .unwrap(); + + assert_eq!(5, last_migration.version()); + assert_eq!(migrations[4].name(), last_migration.name()); + assert_eq!(migrations[4].checksum(), last_migration.checksum()); + + assert!(config.use_tls()); + }); + } } diff --git a/refinery/tests/rusqlite.rs b/refinery/tests/rusqlite.rs index 27e818c3..f7807d69 100644 --- a/refinery/tests/rusqlite.rs +++ b/refinery/tests/rusqlite.rs @@ -40,7 +40,7 @@ mod rusqlite { let filepath = "tests/db.sql"; File::create(filepath).unwrap(); - let result = std::panic::catch_unwind(|| test()); + let result = std::panic::catch_unwind(test); fs::remove_file(filepath).unwrap(); @@ -141,8 +141,7 @@ mod rusqlite { let table_name: String = conn .query_row( &format!( - "SELECT name FROM sqlite_master WHERE type='table' AND name='{}'", - DEFAULT_TABLE_NAME + "SELECT name FROM sqlite_master WHERE type='table' AND name='{DEFAULT_TABLE_NAME}'", ), [], |row| row.get(0), @@ -161,8 +160,7 @@ mod rusqlite { let table_name: String = conn .query_row( &format!( - "SELECT name FROM sqlite_master WHERE type='table' AND name='{}'", - DEFAULT_TABLE_NAME + "SELECT name FROM sqlite_master WHERE type='table' AND name='{DEFAULT_TABLE_NAME}'", ), [], |row| row.get(0), @@ -181,8 +179,7 @@ mod rusqlite { let table_name: String = conn .query_row( &format!( - "SELECT name FROM sqlite_master WHERE type='table' AND name='{}'", - DEFAULT_TABLE_NAME + "SELECT name FROM sqlite_master WHERE type='table' AND name='{DEFAULT_TABLE_NAME}'", ), [], |row| row.get(0), @@ -199,7 +196,7 @@ mod rusqlite { conn.execute( "INSERT INTO persons (name, city) VALUES (?, ?)", - &["John Legend", "New York"], + ["John Legend", "New York"], ) .unwrap(); let (name, city): (String, String) = conn @@ -222,7 +219,7 @@ mod rusqlite { conn.execute( "INSERT INTO persons (name, city) VALUES (?, ?)", - &["John Legend", "New York"], + ["John Legend", "New York"], ) .unwrap(); let (name, city): (String, String) = conn @@ -245,7 +242,7 @@ mod rusqlite { conn.execute( "INSERT INTO persons (name, city) VALUES (?, ?)", - &["John Legend", "New York"], + ["John Legend", "New York"], ) .unwrap(); let (name, city): (String, String) = conn @@ -831,7 +828,7 @@ mod rusqlite { fn migrates_from_cli() { run_test(|| { Command::new("refinery") - .args(&[ + .args([ "migrate", "-c", "tests/sqlite_refinery.toml", diff --git a/refinery/tests/tiberius.rs b/refinery/tests/tiberius.rs index f9011404..e6dcb8d4 100644 --- a/refinery/tests/tiberius.rs +++ b/refinery/tests/tiberius.rs @@ -1,6 +1,6 @@ use barrel::backend::MsSql as Sql; -#[cfg(all(feature = "tiberius-config"))] +#[cfg(feature = "tiberius-config")] mod tiberius { use assert_cmd::prelude::*; use futures::FutureExt; @@ -16,7 +16,7 @@ mod tiberius { use time::OffsetDateTime; use tokio_util::compat::TokioAsyncWriteCompatExt; - const CONFIG: &'static str = "mssql://SA:Passw0rd@localhost:1433/refinery_test?trust_cert=true"; + const CONFIG: &str = "mssql://SA:Passw0rd@localhost:1433/refinery_test?trust_cert=true"; const DEFAULT_TABLE_NAME: &str = "refinery_schema_history"; fn get_migrations() -> Vec { @@ -292,8 +292,7 @@ mod tiberius { let row = client .simple_query(&format!( - "SELECT table_name FROM information_schema.tables WHERE table_name='{}'", - DEFAULT_TABLE_NAME + "SELECT table_name FROM information_schema.tables WHERE table_name='{DEFAULT_TABLE_NAME}'", )) .await .unwrap() @@ -334,8 +333,7 @@ mod tiberius { let row = client .simple_query(&format!( - "SELECT table_name FROM information_schema.tables WHERE table_name='{}'", - DEFAULT_TABLE_NAME + "SELECT table_name FROM information_schema.tables WHERE table_name='{DEFAULT_TABLE_NAME}'", )) .await .unwrap() @@ -1076,7 +1074,7 @@ mod tiberius { async fn migrates_from_cli() { run_test(async { Command::new("refinery") - .args(&[ + .args([ "migrate", "-c", "tests/tiberius_refinery.toml", diff --git a/refinery/tests/tokio_postgres.rs b/refinery/tests/tokio_postgres.rs index 2f7f7fc6..71f9827c 100644 --- a/refinery/tests/tokio_postgres.rs +++ b/refinery/tests/tokio_postgres.rs @@ -3,19 +3,33 @@ use barrel::backend::Pg as Sql; #[cfg(feature = "tokio-postgres")] mod tokio_postgres { use futures::FutureExt; - use refinery::{ - config::{Config, ConfigDbType}, - embed_migrations, - error::Kind, - AsyncMigrate, Migration, Runner, Target, - }; + use refinery::{embed_migrations, error::Kind, AsyncMigrate, Migration, Runner, Target}; + use refinery_core::config::Config; use refinery_core::tokio_postgres; use refinery_core::tokio_postgres::NoTls; use std::panic::AssertUnwindSafe; + use std::str::FromStr; use time::OffsetDateTime; const DEFAULT_TABLE_NAME: &str = "refinery_schema_history"; + fn db_url(db: &str) -> String { + let base = std::env::var("POSTGRES_URL") + .unwrap_or_else(|_| "postgres://postgres@localhost:5432".to_string()); + // Skip past "://" then find the first '/' which separates authority from path + let after_scheme = base.find("://").map(|i| i + 3).unwrap_or(0); + let base = if let Some(pos) = base[after_scheme..].find('/') { + base[..after_scheme + pos].to_string() + } else { + base + }; + format!("{base}/{db}") + } + + fn db_config(db: &str) -> Config { + Config::from_str(&db_url(db)).unwrap() + } + fn get_migrations() -> Vec { embed_migrations!("./tests/migrations"); @@ -71,10 +85,9 @@ mod tokio_postgres { } async fn clean_database() { - let (client, connection) = - tokio_postgres::connect("postgres://postgres@localhost:5432/template1", NoTls) - .await - .unwrap(); + let (client, connection) = tokio_postgres::connect(db_url("template1").as_str(), NoTls) + .await + .unwrap(); tokio::spawn(async move { connection.await.unwrap(); @@ -99,7 +112,7 @@ mod tokio_postgres { async fn report_contains_applied_migrations() { run_test(async { let (mut client, connection) = - tokio_postgres::connect("postgres://postgres@localhost:5432/postgres", NoTls) + tokio_postgres::connect(db_url("postgres").as_str(), NoTls) .await .unwrap(); @@ -139,7 +152,7 @@ mod tokio_postgres { async fn creates_migration_table() { run_test(async { let (mut client, connection) = - tokio_postgres::connect("postgres://postgres@localhost:5432/postgres", NoTls) + tokio_postgres::connect(db_url("postgres").as_str(), NoTls) .await .unwrap(); @@ -155,8 +168,7 @@ mod tokio_postgres { let rows = client .query( &format!( - "SELECT table_name FROM information_schema.tables WHERE table_name='{}'", - DEFAULT_TABLE_NAME + "SELECT table_name FROM information_schema.tables WHERE table_name='{DEFAULT_TABLE_NAME}'", ), &[], ) @@ -175,7 +187,7 @@ mod tokio_postgres { async fn creates_migration_table_grouped_migrations() { run_test(async { let (mut client, connection) = - tokio_postgres::connect("postgres://postgres@localhost:5432/postgres", NoTls) + tokio_postgres::connect(db_url("postgres").as_str(), NoTls) .await .unwrap(); @@ -192,8 +204,7 @@ mod tokio_postgres { let rows = client .query( &format!( - "SELECT table_name FROM information_schema.tables WHERE table_name='{}'", - DEFAULT_TABLE_NAME + "SELECT table_name FROM information_schema.tables WHERE table_name='{DEFAULT_TABLE_NAME}'", ), &[], ) @@ -212,7 +223,7 @@ mod tokio_postgres { async fn applies_migration() { run_test(async { let (mut client, connection) = - tokio_postgres::connect("postgres://postgres@localhost:5432/postgres", NoTls) + tokio_postgres::connect(db_url("postgres").as_str(), NoTls) .await .unwrap(); @@ -251,7 +262,7 @@ mod tokio_postgres { async fn applies_migration_grouped() { run_test(async { let (mut client, connection) = - tokio_postgres::connect("postgres://postgres@localhost:5432/postgres", NoTls) + tokio_postgres::connect(db_url("postgres").as_str(), NoTls) .await .unwrap(); @@ -291,7 +302,7 @@ mod tokio_postgres { async fn updates_schema_history() { run_test(async { let (mut client, connection) = - tokio_postgres::connect("postgres://postgres@localhost:5432/postgres", NoTls) + tokio_postgres::connect(db_url("postgres").as_str(), NoTls) .await .unwrap(); @@ -323,7 +334,7 @@ mod tokio_postgres { async fn updates_schema_history_grouped() { run_test(async { let (mut client, connection) = - tokio_postgres::connect("postgres://postgres@localhost:5432/postgres", NoTls) + tokio_postgres::connect(db_url("postgres").as_str(), NoTls) .await .unwrap(); @@ -356,7 +367,7 @@ mod tokio_postgres { async fn updates_to_last_working_if_not_grouped() { run_test(async { let (mut client, connection) = - tokio_postgres::connect("postgres://postgres@localhost:5432/postgres", NoTls) + tokio_postgres::connect(db_url("postgres").as_str(), NoTls) .await .unwrap(); @@ -400,7 +411,7 @@ mod tokio_postgres { async fn doesnt_update_to_last_working_if_grouped() { run_test(async { let (mut client, connection) = - tokio_postgres::connect("postgres://postgres@localhost:5432/postgres", NoTls) + tokio_postgres::connect(db_url("postgres").as_str(), NoTls) .await .unwrap(); @@ -429,7 +440,7 @@ mod tokio_postgres { async fn gets_applied_migrations() { run_test(async { let (mut client, connection) = - tokio_postgres::connect("postgres://postgres@localhost:5432/postgres", NoTls) + tokio_postgres::connect(db_url("postgres").as_str(), NoTls) .await .unwrap(); @@ -471,7 +482,7 @@ mod tokio_postgres { async fn applies_new_migration() { run_test(async { let (mut client, connection) = - tokio_postgres::connect("postgres://postgres@localhost:5432/postgres", NoTls) + tokio_postgres::connect(db_url("postgres").as_str(), NoTls) .await .unwrap(); @@ -514,7 +525,7 @@ mod tokio_postgres { async fn migrates_to_target_migration() { run_test(async { let (mut client, connection) = - tokio_postgres::connect("postgres://postgres@localhost:5432/postgres", NoTls) + tokio_postgres::connect(db_url("postgres").as_str(), NoTls) .await .unwrap(); @@ -559,7 +570,7 @@ mod tokio_postgres { async fn migrates_to_target_migration_grouped() { run_test(async { let (mut client, connection) = - tokio_postgres::connect("postgres://postgres@localhost:5432/postgres", NoTls) + tokio_postgres::connect(db_url("postgres").as_str(), NoTls) .await .unwrap(); @@ -605,7 +616,7 @@ mod tokio_postgres { async fn aborts_on_missing_migration_on_filesystem() { run_test(async { let (mut client, connection) = - tokio_postgres::connect("postgres://postgres@localhost:5432/postgres", NoTls) + tokio_postgres::connect(db_url("postgres").as_str(), NoTls) .await .unwrap(); @@ -650,7 +661,7 @@ mod tokio_postgres { async fn aborts_on_divergent_migration() { run_test(async { let (mut client, connection) = - tokio_postgres::connect("postgres://postgres@localhost:5432/postgres", NoTls) + tokio_postgres::connect(db_url("postgres").as_str(), NoTls) .await .unwrap(); @@ -697,7 +708,7 @@ mod tokio_postgres { async fn aborts_on_missing_migration_on_database() { run_test(async { let (mut client, connection) = - tokio_postgres::connect("postgres://postgres@localhost:5432/postgres", NoTls) + tokio_postgres::connect(db_url("postgres").as_str(), NoTls) .await .unwrap(); @@ -753,11 +764,7 @@ mod tokio_postgres { #[tokio::test] async fn migrates_from_config() { run_test(async { - let mut config = Config::new(ConfigDbType::Postgres) - .set_db_name("postgres") - .set_db_user("postgres") - .set_db_host("localhost") - .set_db_port("5432"); + let mut config = db_config("postgres"); let migrations = get_migrations(); let runner = Runner::new(&migrations) @@ -797,11 +804,7 @@ mod tokio_postgres { #[tokio::test] async fn migrate_from_config_report_contains_migrations() { run_test(async { - let mut config = Config::new(ConfigDbType::Postgres) - .set_db_name("postgres") - .set_db_user("postgres") - .set_db_host("localhost") - .set_db_port("5432"); + let mut config = db_config("postgres"); let migrations = get_migrations(); let runner = Runner::new(&migrations) @@ -838,11 +841,7 @@ mod tokio_postgres { #[tokio::test] async fn migrate_from_config_report_returns_last_applied_migration() { run_test(async { - let mut config = Config::new(ConfigDbType::Postgres) - .set_db_name("postgres") - .set_db_user("postgres") - .set_db_host("localhost") - .set_db_port("5432"); + let mut config = db_config("postgres"); let migrations = get_migrations(); let runner = Runner::new(&migrations) @@ -870,7 +869,7 @@ mod tokio_postgres { async fn doesnt_run_migrations_if_fake() { run_test(async { let (mut client, connection) = - tokio_postgres::connect("postgres://postgres@localhost:5432/postgres", NoTls) + tokio_postgres::connect(db_url("postgres").as_str(), NoTls) .await .unwrap(); @@ -914,7 +913,7 @@ mod tokio_postgres { async fn doesnt_run_migrations_if_fake_version() { run_test(async { let (mut client, connection) = - tokio_postgres::connect("postgres://postgres@localhost:5432/postgres", NoTls) + tokio_postgres::connect(db_url("postgres").as_str(), NoTls) .await .unwrap(); @@ -953,4 +952,70 @@ mod tokio_postgres { }) .await; } + + #[cfg(feature = "tokio-postgres-tls")] + #[tokio::test] + async fn migrates_with_tls_enabled() { + run_test(async { + let mut config = + Config::from_str(&format!("{}?sslmode=require", db_url("postgres"))).unwrap(); + + let migrations = get_migrations(); + let runner = Runner::new(&migrations) + .set_grouped(false) + .set_abort_divergent(true) + .set_abort_missing(true); + + let report = runner.run_async(&mut config).await.unwrap(); + + let applied_migrations = report.applied_migrations(); + assert_eq!(5, applied_migrations.len()); + + let last_migration = runner + .get_last_applied_migration_async(&mut config) + .await + .unwrap() + .unwrap(); + + assert_eq!(5, last_migration.version()); + assert_eq!(migrations[4].name(), last_migration.name()); + assert_eq!(migrations[4].checksum(), last_migration.checksum()); + + assert!(config.use_tls()); + }) + .await; + } + + #[cfg(feature = "tokio-postgres-rustls")] + #[tokio::test] + async fn migrates_with_rustls_enabled() { + run_test(async { + let mut config = + Config::from_str(&format!("{}?sslmode=require", db_url("postgres"))).unwrap(); + + let migrations = get_migrations(); + let runner = Runner::new(&migrations) + .set_grouped(false) + .set_abort_divergent(true) + .set_abort_missing(true); + + let report = runner.run_async(&mut config).await.unwrap(); + + let applied_migrations = report.applied_migrations(); + assert_eq!(5, applied_migrations.len()); + + let last_migration = runner + .get_last_applied_migration_async(&mut config) + .await + .unwrap() + .unwrap(); + + assert_eq!(5, last_migration.version()); + assert_eq!(migrations[4].name(), last_migration.name()); + assert_eq!(migrations[4].checksum(), last_migration.checksum()); + + assert!(config.use_tls()); + }) + .await; + } } diff --git a/refinery_cli/Cargo.toml b/refinery_cli/Cargo.toml index 99b3eac5..a153cc8d 100644 --- a/refinery_cli/Cargo.toml +++ b/refinery_cli/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "refinery_cli" -version = "0.8.12" +version = "0.9.2" authors = ["Katharina Fey ", "João Oliveira "] license = "MIT OR Apache-2.0" description = "Provides the CLI for the Refinery crate" @@ -16,18 +16,19 @@ path = "src/main.rs" [features] default = ["mysql", "postgresql", "sqlite-bundled", "mssql"] -postgresql = ["refinery-core/postgres"] +postgresql = ["refinery-core/postgres-tls"] mysql = ["refinery-core/mysql"] sqlite = ["refinery-core/rusqlite"] sqlite-bundled = ["sqlite", "refinery-core/rusqlite-bundled"] mssql = ["refinery-core/tiberius-config", "tokio"] +int8-versions = ["refinery-core/int8-versions"] [dependencies] -refinery-core = { version = "0.8.12", path = "../refinery_core", default-features = false, features = ["toml"] } +refinery-core = { version = "0.9.2", path = "../refinery_core", default-features = false, features = ["toml", "config"] } clap = { version = "4", features = ["derive"] } -human-panic = "1.1.3" +human-panic = "2" toml = "0.8" -env_logger = "0.10" +env_logger = "0.11" log = "0.4" anyhow = "1" regex = "1" diff --git a/refinery_cli/src/cli.rs b/refinery_cli/src/cli.rs index fd188266..e394b911 100644 --- a/refinery_cli/src/cli.rs +++ b/refinery_cli/src/cli.rs @@ -4,6 +4,8 @@ use std::path::PathBuf; use clap::{Args, Parser}; +use refinery_core::SchemaVersion; + #[derive(Parser)] #[clap(version)] pub enum Cli { @@ -38,17 +40,17 @@ pub struct MigrateArgs { /// Migrate to the specified target version #[clap(short)] - pub target: Option, + pub target: Option, /// Set migration table name #[clap(long, default_value = "refinery_schema_history")] pub table_name: String, - /// Migrate even if divergent migrations are found + /// Should abort if divergent migrations are found #[clap(short)] pub divergent: bool, - /// Migrate even if missing migrations are found + /// Should abort if missing migrations are found #[clap(short)] pub missing: bool, } diff --git a/refinery_cli/src/migrate.rs b/refinery_cli/src/migrate.rs index f35c5d64..cd9557a4 100644 --- a/refinery_cli/src/migrate.rs +++ b/refinery_cli/src/migrate.rs @@ -3,7 +3,7 @@ use std::path::Path; use anyhow::Context; use refinery_core::{ config::{Config, ConfigDbType}, - find_migration_files, Migration, MigrationType, Runner, Target, + find_migration_files, Migration, MigrationType, SchemaVersion, Target, }; use crate::cli::MigrateArgs; @@ -19,17 +19,17 @@ pub fn handle_migration_command(args: MigrateArgs) -> anyhow::Result<()> { args.env_var.as_deref(), &args.path, &args.table_name, - )?; - Ok(()) + ) } +#[allow(clippy::too_many_arguments)] fn run_migrations( config_location: &Path, grouped: bool, divergent: bool, missing: bool, fake: bool, - target: Option, + target: Option, env_var_opt: Option<&str>, path: &Path, table_name: &str, @@ -72,7 +72,7 @@ fn run_migrations( .context("Can't start tokio runtime")?; runtime.block_on(async { - Runner::new(&migrations) + refinery_core::Runner::new(&migrations) .set_grouped(grouped) .set_target(target) .set_abort_divergent(divergent) @@ -89,7 +89,7 @@ fn run_migrations( _db_type @ (ConfigDbType::Mysql | ConfigDbType::Postgres | ConfigDbType::Sqlite) => { cfg_if::cfg_if! { if #[cfg(any(feature = "mysql", feature = "postgresql", feature = "sqlite"))] { - Runner::new(&migrations) + refinery_core::Runner::new(&migrations) .set_grouped(grouped) .set_abort_divergent(divergent) .set_abort_missing(missing) @@ -102,7 +102,6 @@ fn run_migrations( } } }; - Ok(()) } diff --git a/refinery_cli/src/setup.rs b/refinery_cli/src/setup.rs index aa9783bd..6429df71 100644 --- a/refinery_cli/src/setup.rs +++ b/refinery_cli/src/setup.rs @@ -35,7 +35,7 @@ fn get_config_from_input() -> Result { let mut db_type = String::new(); io::stdin().read_line(&mut db_type)?; - let db_type = match db_type.as_str().trim() { + let db_type = match db_type.trim() { "1" => ConfigDbType::Mysql, "2" => ConfigDbType::Postgres, "3" => ConfigDbType::Sqlite, @@ -51,8 +51,6 @@ fn get_config_from_input() -> Result { io::stdout().flush()?; let mut db_path = String::new(); io::stdin().read_line(&mut db_path)?; - //remove \n - db_path.pop(); config = config.set_db_path(db_path.trim()); return Ok(config); } else { @@ -61,40 +59,45 @@ fn get_config_from_input() -> Result { } } - print!("Enter database host: "); - io::stdout().flush()?; - let mut db_host = String::new(); - io::stdin().read_line(&mut db_host)?; - db_host.pop(); - config = config.set_db_host(&db_host); + cfg_if::cfg_if! { + if #[cfg(any( + feature = "mysql", + feature = "mssql", + feature = "postgresql", + ))]{ + print!("Enter database host: "); + io::stdout().flush()?; + let mut db_host = String::new(); + io::stdin().read_line(&mut db_host)?; + config = config.set_db_host(db_host.trim()); - print!("Enter database port: "); - io::stdout().flush()?; - let mut db_port = String::new(); - io::stdin().read_line(&mut db_port)?; - db_port.pop(); - config = config.set_db_port(&db_port); + print!("Enter database port: "); + io::stdout().flush()?; + let mut db_port = String::new(); + io::stdin().read_line(&mut db_port)?; + config = config.set_db_port(db_port.trim()); - print!("Enter database username: "); - io::stdout().flush()?; - let mut db_user = String::new(); - io::stdin().read_line(&mut db_user)?; - db_user.pop(); - config = config.set_db_user(&db_user); + print!("Enter database username: "); + io::stdout().flush()?; + let mut db_user = String::new(); + io::stdin().read_line(&mut db_user)?; + config = config.set_db_user(db_user.trim()); - print!("Enter database password: "); - io::stdout().flush()?; - let mut db_pass = String::new(); - io::stdin().read_line(&mut db_pass)?; - db_pass.pop(); - config = config.set_db_pass(&db_pass); + print!("Enter database password: "); + io::stdout().flush()?; + let mut db_pass = String::new(); + io::stdin().read_line(&mut db_pass)?; + config = config.set_db_pass(db_pass.trim()); - print!("Enter database name: "); - io::stdout().flush()?; - let mut db_name = String::new(); - io::stdin().read_line(&mut db_name)?; - db_name.pop(); - config = config.set_db_name(&db_name); + print!("Enter database name: "); + io::stdout().flush()?; + let mut db_name = String::new(); + io::stdin().read_line(&mut db_name)?; + config = config.set_db_name(db_name.trim()); - Ok(config) + Ok(config) + } else { + Ok(config) + } + } } diff --git a/refinery_cli/tests/cli.rs b/refinery_cli/tests/cli.rs index 2ddd3e7b..502a6adf 100644 --- a/refinery_cli/tests/cli.rs +++ b/refinery_cli/tests/cli.rs @@ -6,14 +6,15 @@ mod cli { // `refinery` with no args should exit with a non-zero code. #[test] fn cli_no_args() { - Command::cargo_bin("refinery").unwrap().assert().failure(); + Command::new(assert_cmd::cargo_bin!("refinery")) + .assert() + .failure(); } #[test] fn cli_version() { - Command::cargo_bin("refinery") - .unwrap() - .args(&["-V"]) + Command::new(assert_cmd::cargo_bin!("refinery")) + .arg("-V") .assert() .stdout(contains(env!("CARGO_PKG_VERSION"))); } @@ -21,9 +22,8 @@ mod cli { // `refinery migrate` with no args should exit with a non-zero code. #[test] fn migrate_no_args() { - Command::cargo_bin("refinery") - .unwrap() - .args(&["migrate"]) + Command::new(assert_cmd::cargo_bin!("refinery")) + .arg("migrate") .assert() .failure(); } diff --git a/refinery_core/Cargo.toml b/refinery_core/Cargo.toml index 0bd2ea05..51edba25 100644 --- a/refinery_core/Cargo.toml +++ b/refinery_core/Cargo.toml @@ -1,22 +1,30 @@ [package] name = "refinery-core" -version = "0.8.12" +version = "0.9.2" authors = ["Katharina Fey ", "João Oliveira "] description = "This crate should not be used directly, it is internally related to Refinery" license = "MIT OR Apache-2.0" documentation = "https://docs.rs/refinery/" repository = "https://github.com/rust-db/refinery" -edition = "2018" +edition = "2021" [features] default = [] -rusqlite-bundled = ["rusqlite", "rusqlite/bundled"] -tiberius = ["dep:tiberius", "futures", "tokio", "tokio/net"] -tiberius-config = ["tiberius", "tokio", "tokio-util"] -tokio-postgres = ["dep:tokio-postgres", "tokio", "tokio/rt"] -mysql_async = ["dep:mysql_async"] -serde = ["dep:serde"] +config = [] +mysql_async = ["dep:mysql_async", "config"] +postgres = [ "dep:postgres" ] +postgres-tls = ["postgres", "dep:postgres-native-tls", "tls", "config"] +rusqlite = ["dep:rusqlite", "config"] +rusqlite-bundled = ["rusqlite", "rusqlite/bundled", "config"] +serde = ["dep:serde", "time/serde"] +tiberius = ["dep:tiberius", "dep:futures", "dep:tokio", "tokio/net"] +tiberius-config = ["tiberius", "dep:tokio-util", "dep:serde", "config"] +tls = ["dep:native-tls"] +tokio-postgres = ["dep:tokio-postgres", "dep:tokio", "tokio/rt"] +tokio-postgres-tls = ["tokio-postgres", "postgres-tls", "config"] toml = ["serde", "dep:toml"] +int8-versions = [] +tokio-postgres-rustls = ["dep:tokio-postgres-rustls", "dep:rustls", "dep:rustls-native-certs", "tokio-postgres", "config"] [dependencies] async-trait = "0.1" @@ -24,23 +32,28 @@ cfg-if = "1.0" log = "0.4" regex = "1" siphasher = "1.0" -thiserror = "1" +thiserror = "2" url = "2.0" walkdir = "2.3.1" # allow multiple versions of the same dependency if API is similar -rusqlite = { version = ">= 0.23, <= 0.30", optional = true } +rusqlite = { version = ">= 0.23, <= 0.39", optional = true } postgres = { version = ">=0.17, <= 0.19", optional = true } +native-tls = { version = "0.2", optional = true } +postgres-native-tls = { version = "0.5", optional = true } tokio-postgres = { version = ">= 0.5, <= 0.7", optional = true } -mysql = { version = ">= 21.0.0, <= 24", optional = true, default-features = false, features = ["minimal"] } -mysql_async = { version = ">= 0.28, <= 0.33", optional = true, default-features = false, features = ["minimal"] } +mysql = { version = ">= 21.0.0, <= 28", optional = true, default-features = false, features = ["minimal"] } +mysql_async = { version = ">= 0.28, <= 0.37", optional = true, default-features = false, features = ["minimal"] } tiberius = { version = ">= 0.7, <= 0.12", optional = true, default-features = false } tokio = { version = "1.0", optional = true } futures = { version = "0.3.16", optional = true, features = ["async-await"] } tokio-util = { version = "0.7.7", features = ["compat"], optional = true } time = { version = "0.3.5", features = ["parsing", "formatting"] } serde = { version = "1", features = ["derive"], optional = true } -toml = { version = "0.8.8", optional = true } +toml = { version = "1.1.2", optional = true } +rustls = { version = "0.23", default-features = false, optional = true } +rustls-native-certs = { version = "0.8", optional = true } +tokio-postgres-rustls = { version = "0.13", optional = true } [dev-dependencies] barrel = { git = "https://github.com/jxs/barrel", features = ["sqlite3", "pg", "mysql", "mssql"] } diff --git a/refinery_core/src/config.rs b/refinery_core/src/config.rs index 2f3b1e63..ca1b0101 100644 --- a/refinery_core/src/config.rs +++ b/refinery_core/src/config.rs @@ -1,8 +1,12 @@ use crate::error::Kind; use crate::Error; +#[cfg(any( + feature = "postgres", + feature = "tokio-postgres", + feature = "tiberius-config" +))] +use std::borrow::Cow; use std::convert::TryFrom; -use std::fs; -use std::path::{Path, PathBuf}; use std::str::FromStr; use url::Url; @@ -27,17 +31,7 @@ impl Config { /// create a new config instance pub fn new(db_type: ConfigDbType) -> Config { Config { - main: Main { - db_type, - db_path: None, - db_host: None, - db_port: None, - db_user: None, - db_pass: None, - db_name: None, - #[cfg(feature = "tiberius-config")] - trust_cert: false, - }, + main: Main::new(db_type), } } @@ -45,32 +39,38 @@ impl Config { pub fn from_env_var(name: &str) -> Result { let value = std::env::var(name).map_err(|_| { Error::new( - Kind::ConfigError(format!("Couldn't find {} environment variable", name)), + Kind::ConfigError(format!("Couldn't find {name} environment variable")), None, ) })?; Config::from_str(&value) } + pub fn db_type(&self) -> ConfigDbType { + self.main.db_type + } + /// create a new Config instance from a config file located on the file system #[cfg(feature = "toml")] - pub fn from_file_location>(location: T) -> Result { + pub fn from_file_location>(location: T) -> Result { let file = std::fs::read_to_string(&location).map_err(|err| { Error::new( - Kind::ConfigError(format!("could not open config file, {}", err)), + Kind::ConfigError(format!("could not open config file, {err}")), None, ) })?; - let mut config: Config = toml::from_str(&file).map_err(|err| { + let config: Config = toml::from_str(&file).map_err(|err| { Error::new( - Kind::ConfigError(format!("could not parse config file, {}", err)), + Kind::ConfigError(format!("could not parse config file, {err}")), None, ) })?; //replace relative path with canonical path in case of Sqlite db + #[cfg(feature = "rusqlite")] if config.main.db_type == ConfigDbType::Sqlite { + let mut config = config; let mut config_db_path = config.main.db_path.ok_or_else(|| { Error::new( Kind::ConfigError("field path must be present for Sqlite database type".into()), @@ -86,52 +86,38 @@ impl Config { .unwrap_or(&std::env::current_dir().unwrap()) .to_path_buf(); - config_db_dir = fs::canonicalize(config_db_dir).unwrap(); + config_db_dir = std::fs::canonicalize(config_db_dir).unwrap(); config_db_path = config_db_dir.join(&config_db_path) } let config_db_path = config_db_path.canonicalize().map_err(|err| { Error::new( - Kind::ConfigError(format!("invalid sqlite db path, {}", err)), + Kind::ConfigError(format!("invalid sqlite db path, {err}")), None, ) })?; - config.main.db_path = Some(config_db_path); - } - - Ok(config) - } - - cfg_if::cfg_if! { - if #[cfg(feature = "rusqlite")] { - pub(crate) fn db_path(&self) -> Option<&Path> { - self.main.db_path.as_deref() - } - pub fn set_db_path(self, db_path: &str) -> Config { - Config { - main: Main { - db_path: Some(db_path.into()), - ..self.main - }, - } - } + return Ok(config); } - } - cfg_if::cfg_if! { - if #[cfg(feature = "tiberius-config")] { - pub fn set_trust_cert(&mut self) { - self.main.trust_cert = true; - } - } + Ok(config) } - pub fn db_type(&self) -> ConfigDbType { - self.main.db_type + #[cfg(feature = "tiberius-config")] + pub fn set_trust_cert(&mut self) { + self.main.trust_cert = true; } +} +#[cfg(any( + feature = "mysql", + feature = "postgres", + feature = "tokio-postgres", + feature = "mysql_async", + feature = "tiberius-config" +))] +impl Config { pub fn db_host(&self) -> Option<&str> { self.main.db_host.as_deref() } @@ -186,6 +172,38 @@ impl Config { } } +#[cfg(feature = "rusqlite")] +impl Config { + pub(crate) fn db_path(&self) -> Option<&std::path::Path> { + self.main.db_path.as_deref() + } + + pub fn set_db_path(self, db_path: &str) -> Config { + Config { + main: Main { + db_path: Some(db_path.into()), + ..self.main + }, + } + } +} + +#[cfg(any(feature = "postgres", feature = "tokio-postgres"))] +impl Config { + pub fn use_tls(&self) -> bool { + self.main.use_tls + } + + pub fn set_use_tls(self, use_tls: bool) -> Config { + Config { + main: Main { + use_tls, + ..self.main + }, + } + } +} + impl TryFrom for Config { type Error = Error; @@ -204,29 +222,10 @@ impl TryFrom for Config { } }; - cfg_if::cfg_if! { - if #[cfg(feature = "tiberius-config")] { - use std::{borrow::Cow, collections::HashMap}; - let query_params = url - .query_pairs() - .collect::, Cow<'_, str>>>(); - - let trust_cert = query_params. - get("trust_cert") - .unwrap_or(&Cow::Borrowed("false")) - .parse::() - .map_err(|_| { - Error::new( - Kind::ConfigError("Invalid trust_cert value, please use true/false".into()), - None, - ) - })?; - } - } - Ok(Self { main: Main { db_type, + #[cfg(feature = "rusqlite")] db_path: Some( url.as_str()[url.scheme().len()..] .trim_start_matches(':') @@ -234,13 +233,78 @@ impl TryFrom for Config { .to_string() .into(), ), + #[cfg(any( + feature = "mysql", + feature = "postgres", + feature = "tokio-postgres", + feature = "mysql_async", + feature = "tiberius-config" + ))] db_host: url.host_str().map(|r| r.to_string()), + #[cfg(any( + feature = "mysql", + feature = "postgres", + feature = "tokio-postgres", + feature = "mysql_async", + feature = "tiberius-config" + ))] db_port: url.port().map(|r| r.to_string()), + #[cfg(any( + feature = "mysql", + feature = "postgres", + feature = "tokio-postgres", + feature = "mysql_async", + feature = "tiberius-config" + ))] db_user: Some(url.username().to_string()), + #[cfg(any( + feature = "mysql", + feature = "postgres", + feature = "tokio-postgres", + feature = "mysql_async", + feature = "tiberius-config" + ))] db_pass: url.password().map(|r| r.to_string()), + #[cfg(any( + feature = "mysql", + feature = "postgres", + feature = "tokio-postgres", + feature = "mysql_async", + feature = "tiberius-config" + ))] db_name: Some(url.path().trim_start_matches('/').to_string()), + #[cfg(any(feature = "postgres", feature = "tokio-postgres"))] + use_tls: match url + .query_pairs() + .collect::, Cow<'_, str>>>() + .get("sslmode") + { + Some(Cow::Borrowed("require")) => true, + Some(Cow::Borrowed("disable")) | None => false, + _ => { + return Err(Error::new( + Kind::ConfigError( + "Invalid sslmode value, please use disable/require".into(), + ), + None, + )) + } + }, #[cfg(feature = "tiberius-config")] - trust_cert, + trust_cert: url + .query_pairs() + .collect::, Cow<'_, str>>>() + .get("trust_cert") + .unwrap_or(&Cow::Borrowed("false")) + .parse::() + .map_err(|_| { + Error::new( + Kind::ConfigError( + "Invalid trust_cert value, please use true/false".into(), + ), + None, + ) + })?, }, }) } @@ -253,7 +317,7 @@ impl FromStr for Config { fn from_str(url_str: &str) -> Result { let url = Url::parse(url_str).map_err(|_| { Error::new( - Kind::ConfigError(format!("Couldn't parse the string '{}' as a URL", url_str)), + Kind::ConfigError(format!("Couldn't parse the string '{url_str}' as a URL")), None, ) })?; @@ -265,22 +329,115 @@ impl FromStr for Config { #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] struct Main { db_type: ConfigDbType, - db_path: Option, + #[cfg(feature = "rusqlite")] + db_path: Option, + #[cfg(any( + feature = "mysql", + feature = "postgres", + feature = "tokio-postgres", + feature = "mysql_async", + feature = "tiberius-config" + ))] db_host: Option, + #[cfg(any( + feature = "mysql", + feature = "postgres", + feature = "tokio-postgres", + feature = "mysql_async", + feature = "tiberius-config" + ))] db_port: Option, + #[cfg(any( + feature = "mysql", + feature = "postgres", + feature = "tokio-postgres", + feature = "mysql_async", + feature = "tiberius-config" + ))] db_user: Option, + #[cfg(any( + feature = "mysql", + feature = "postgres", + feature = "tokio-postgres", + feature = "mysql_async", + feature = "tiberius-config" + ))] db_pass: Option, + #[cfg(any( + feature = "mysql", + feature = "postgres", + feature = "tokio-postgres", + feature = "mysql_async", + feature = "tiberius-config" + ))] db_name: Option, + #[cfg(any(feature = "postgres", feature = "tokio-postgres"))] + #[cfg_attr(feature = "serde", serde(default))] + use_tls: bool, #[cfg(feature = "tiberius-config")] - #[serde(default)] + #[cfg_attr(feature = "serde", serde(default))] trust_cert: bool, } +impl Main { + fn new(db_type: ConfigDbType) -> Self { + Main { + db_type, + #[cfg(feature = "rusqlite")] + db_path: None, + #[cfg(any( + feature = "mysql", + feature = "postgres", + feature = "tokio-postgres", + feature = "mysql_async", + feature = "tiberius-config" + ))] + db_host: None, + #[cfg(any( + feature = "mysql", + feature = "postgres", + feature = "tokio-postgres", + feature = "mysql_async", + feature = "tiberius-config" + ))] + db_port: None, + #[cfg(any( + feature = "mysql", + feature = "postgres", + feature = "tokio-postgres", + feature = "mysql_async", + feature = "tiberius-config" + ))] + db_user: None, + #[cfg(any( + feature = "mysql", + feature = "postgres", + feature = "tokio-postgres", + feature = "mysql_async", + feature = "tiberius-config" + ))] + db_pass: None, + #[cfg(any( + feature = "mysql", + feature = "postgres", + feature = "tokio-postgres", + feature = "mysql_async", + feature = "tiberius-config" + ))] + db_name: None, + #[cfg(any(feature = "postgres", feature = "tokio-postgres"))] + use_tls: false, + #[cfg(feature = "tiberius-config")] + trust_cert: false, + } + } +} + #[cfg(any( feature = "mysql", feature = "postgres", feature = "tokio-postgres", - feature = "mysql_async" + feature = "mysql_async", ))] pub(crate) fn build_db_url(name: &str, config: &Config) -> String { let mut url: String = name.to_string() + "://"; @@ -307,52 +464,58 @@ pub(crate) fn build_db_url(name: &str, config: &Config) -> String { url } -cfg_if::cfg_if! { - if #[cfg(feature = "tiberius-config")] { - use tiberius::{AuthMethod, Config as TConfig}; - - impl TryFrom<&Config> for TConfig { - type Error=Error; - - fn try_from(config: &Config) -> Result { - let mut tconfig = TConfig::new(); - if let Some(host) = &config.main.db_host { - tconfig.host(host); - } +#[cfg(feature = "tiberius-config")] +impl TryFrom<&Config> for tiberius::Config { + type Error = Error; - if let Some(port) = &config.main.db_port { - let port = port.parse().map_err(|_| Error::new( - Kind::ConfigError(format!("Couldn't parse value {} as mssql port", port)), - None, - ))?; - tconfig.port(port); - } + fn try_from(config: &Config) -> Result { + let mut tconfig = tiberius::Config::new(); + if let Some(host) = &config.main.db_host { + tconfig.host(host); + } - if let Some(db) = &config.main.db_name { - tconfig.database(db); - } + if let Some(port) = &config.main.db_port { + let port = port.parse().map_err(|_| { + Error::new( + Kind::ConfigError(format!("Couldn't parse value {port} as mssql port")), + None, + ) + })?; + tconfig.port(port); + } - let user = config.main.db_user.as_deref().unwrap_or(""); - let pass = config.main.db_pass.as_deref().unwrap_or(""); + if let Some(db) = &config.main.db_name { + tconfig.database(db); + } - if config.main.trust_cert { - tconfig.trust_cert(); - } - tconfig.authentication(AuthMethod::sql_server(&user, &pass)); + let user = config.main.db_user.as_deref().unwrap_or(""); + let pass = config.main.db_pass.as_deref().unwrap_or(""); - Ok(tconfig) - } + if config.main.trust_cert { + tconfig.trust_cert(); } + tconfig.authentication(tiberius::AuthMethod::sql_server(user, pass)); + + Ok(tconfig) } } #[cfg(test)] mod tests { - use super::{build_db_url, Config, Kind}; + use super::{Config, Kind}; use std::io::Write; use std::str::FromStr; + #[cfg(any( + feature = "mysql", + feature = "postgres", + feature = "tokio-postgres", + feature = "mysql_async" + ))] + use super::build_db_url; + #[test] + #[cfg(feature = "toml")] fn returns_config_error_from_invalid_config_location() { let config = Config::from_file_location("invalid_path").unwrap_err(); match config.kind() { @@ -362,6 +525,7 @@ mod tests { } #[test] + #[cfg(feature = "toml")] fn returns_config_error_from_invalid_toml_file() { let config = "[<$% db_type = \"Sqlite\" \n"; @@ -376,6 +540,7 @@ mod tests { } #[test] + #[cfg(all(feature = "toml", feature = "rusqlite"))] fn returns_config_error_from_sqlite_with_missing_path() { let config = "[main] \n db_type = \"Sqlite\" \n"; @@ -392,6 +557,7 @@ mod tests { } #[test] + #[cfg(all(feature = "toml", feature = "rusqlite"))] fn builds_sqlite_path_from_relative_path() { let db_file = tempfile::NamedTempFile::new_in(".").unwrap(); @@ -415,6 +581,15 @@ mod tests { } #[test] + #[cfg(all( + feature = "toml", + any( + feature = "mysql", + feature = "postgres", + feature = "tokio-postgres", + feature = "mysql_async" + ) + ))] fn builds_db_url() { let config = "[main] \n db_type = \"Postgres\" \n @@ -433,12 +608,18 @@ mod tests { } #[test] + #[cfg(any( + feature = "mysql", + feature = "postgres", + feature = "tokio-postgres", + feature = "mysql_async" + ))] fn builds_db_env_var() { std::env::set_var( - "DATABASE_URL", + "TEST_DATABASE_URL", "postgres://root:1234@localhost:5432/refinery", ); - let config = Config::from_env_var("DATABASE_URL").unwrap(); + let config = Config::from_env_var("TEST_DATABASE_URL").unwrap(); assert_eq!( "postgres://root:1234@localhost:5432/refinery", build_db_url("postgres", &config) @@ -446,6 +627,12 @@ mod tests { } #[test] + #[cfg(any( + feature = "mysql", + feature = "postgres", + feature = "tokio-postgres", + feature = "mysql_async" + ))] fn builds_from_str() { let config = Config::from_str("postgres://root:1234@localhost:5432/refinery").unwrap(); assert_eq!( @@ -454,10 +641,49 @@ mod tests { ); } + #[cfg(any(feature = "postgres", feature = "tokio-postgres"))] + #[test] + fn builds_from_sslmode_str() { + use crate::config::ConfigDbType; + + let config_disable = + Config::from_str("postgres://root:1234@localhost:5432/refinery?sslmode=disable") + .unwrap(); + assert!(!config_disable.use_tls()); + + let config_require = + Config::from_str("postgres://root:1234@localhost:5432/refinery?sslmode=require") + .unwrap(); + assert!(config_require.use_tls()); + + // Verify that manually created config matches parsed URL config + let manual_config_disable = Config::new(ConfigDbType::Postgres) + .set_db_user("root") + .set_db_pass("1234") + .set_db_host("localhost") + .set_db_port("5432") + .set_db_name("refinery") + .set_use_tls(false); + assert_eq!(config_disable.use_tls(), manual_config_disable.use_tls()); + + let manual_config_require = Config::new(ConfigDbType::Postgres) + .set_db_user("root") + .set_db_pass("1234") + .set_db_host("localhost") + .set_db_port("5432") + .set_db_name("refinery") + .set_use_tls(true); + assert_eq!(config_require.use_tls(), manual_config_require.use_tls()); + + let config = + Config::from_str("postgres://root:1234@localhost:5432/refinery?sslmode=invalidvalue"); + assert!(config.is_err()); + } + #[test] fn builds_db_env_var_failure() { - std::env::set_var("DATABASE_URL", "this_is_not_a_url"); - let config = Config::from_env_var("DATABASE_URL"); + std::env::set_var("TEST_DATABASE_URL_INVALID", "this_is_not_a_url"); + let config = Config::from_env_var("TEST_DATABASE_URL_INVALID"); assert!(config.is_err()); } } diff --git a/refinery_core/src/drivers/config.rs b/refinery_core/src/drivers/config.rs index 92a00582..d7d7192d 100644 --- a/refinery_core/src/drivers/config.rs +++ b/refinery_core/src/drivers/config.rs @@ -1,16 +1,21 @@ +use crate::config::Config; +use crate::traits::r#async::{AsyncQuery, AsyncTransaction}; +use crate::traits::sync::{Query, Transaction}; +use crate::Migration; #[cfg(any( feature = "mysql", feature = "postgres", + feature = "rusqlite", feature = "tokio-postgres", - feature = "mysql_async" + feature = "mysql_async", + feature = "tiberius-config" ))] -use crate::config::build_db_url; -use crate::config::{Config, ConfigDbType}; -use crate::error::WrapMigrationError; -use crate::traits::r#async::{AsyncQuery, AsyncTransaction}; -use crate::traits::sync::{Query, Transaction}; -use crate::traits::{GET_APPLIED_MIGRATIONS_QUERY, GET_LAST_APPLIED_MIGRATION_QUERY}; -use crate::{Error, Migration, Report, Target}; +use crate::{ + config::ConfigDbType, + error::WrapMigrationError, + traits::{GET_APPLIED_MIGRATIONS_QUERY, GET_LAST_APPLIED_MIGRATION_QUERY}, + Error, Report, Target, +}; use async_trait::async_trait; use std::convert::Infallible; @@ -18,7 +23,10 @@ use std::convert::Infallible; impl Transaction for Config { type Error = Infallible; - fn execute(&mut self, _queries: &[&str]) -> Result { + fn execute<'a, T: Iterator>( + &mut self, + _queries: T, + ) -> Result { Ok(0) } } @@ -33,7 +41,10 @@ impl Query> for Config { impl AsyncTransaction for Config { type Error = Infallible; - async fn execute(&mut self, _queries: &[&str]) -> Result { + async fn execute<'a, T: Iterator + Send>( + &mut self, + _queries: T, + ) -> Result { Ok(0) } } @@ -49,13 +60,15 @@ impl AsyncQuery> for Config { } // this is written as macro so that we don't have to deal with type signatures #[cfg(any(feature = "mysql", feature = "postgres", feature = "rusqlite"))] +#[allow(clippy::redundant_closure_call)] macro_rules! with_connection { ($config:ident, $op: expr) => { + #[allow(clippy::redundant_closure_call)] match $config.db_type() { ConfigDbType::Mysql => { cfg_if::cfg_if! { if #[cfg(feature = "mysql")] { - let url = build_db_url("mysql", &$config); + let url = crate::config::build_db_url("mysql", &$config); let opts = mysql::Opts::from_url(&url).migration_err("could not parse url", None)?; let conn = mysql::Conn::new(opts).migration_err("could not connect to database", None)?; $op(conn) @@ -79,8 +92,31 @@ macro_rules! with_connection { ConfigDbType::Postgres => { cfg_if::cfg_if! { if #[cfg(feature = "postgres")] { - let path = build_db_url("postgresql", &$config); - let conn = postgres::Client::connect(path.as_str(), postgres::NoTls).migration_err("could not connect to database", None)?; + let path = crate::config::build_db_url("postgresql", &$config); + + let conn; + cfg_if::cfg_if! { + if #[cfg(feature = "tls")] { + if $config.use_tls() { + let connector = native_tls::TlsConnector::new().unwrap(); + let connector = postgres_native_tls::MakeTlsConnector::new(connector); + conn = postgres::Client::connect(path.as_str(), connector).migration_err("could not connect to database", None)?; + } else { + conn = postgres::Client::connect(path.as_str(), postgres::NoTls).migration_err("could not connect to database", None)?; + } + } else if #[cfg(feature = "tokio-postgres-rustls")] { + if $config.use_tls() { + panic!("tokio-postgres-rustls only supports the async tokio-postgres driver; enable the 'tls' feature to use TLS with the sync postgres driver"); + } + conn = postgres::Client::connect(path.as_str(), postgres::NoTls).migration_err("could not connect to database", None)?; + } else { + if $config.use_tls() { + panic!("TLS was requested but neither 'tls' nor 'tokio-postgres-rustls' feature is enabled in refinery-core"); + } + conn = postgres::Client::connect(path.as_str(), postgres::NoTls).migration_err("could not connect to database", None)?; + } + } + $op(conn) } else { panic!("tried to migrate from config for a postgresql database, but feature postgres not enabled!"); @@ -101,11 +137,12 @@ macro_rules! with_connection { ))] macro_rules! with_connection_async { ($config: ident, $op: expr) => { + #[allow(clippy::redundant_closure_call)] match $config.db_type() { ConfigDbType::Mysql => { cfg_if::cfg_if! { if #[cfg(feature = "mysql_async")] { - let url = build_db_url("mysql", $config); + let url = crate::config::build_db_url("mysql", $config); let pool = mysql_async::Pool::from_url(&url).migration_err("could not connect to the database", None)?; $op(pool).await } else { @@ -119,14 +156,69 @@ macro_rules! with_connection_async { ConfigDbType::Postgres => { cfg_if::cfg_if! { if #[cfg(feature = "tokio-postgres")] { - let path = build_db_url("postgresql", $config); - let (client, connection ) = tokio_postgres::connect(path.as_str(), tokio_postgres::NoTls).await.migration_err("could not connect to database", None)?; - tokio::spawn(async move { - if let Err(e) = connection.await { - eprintln!("connection error: {}", e); + let path = crate::config::build_db_url("postgresql", $config); + cfg_if::cfg_if! { + if #[cfg(feature = "tls")] { + if $config.use_tls() { + let connector = native_tls::TlsConnector::new().unwrap(); + let connector = postgres_native_tls::MakeTlsConnector::new(connector); + let (client, connection) = tokio_postgres::connect(path.as_str(), connector).await.migration_err("could not connect to database", None)?; + tokio::spawn(async move { + if let Err(e) = connection.await { + eprintln!("connection error: {}", e); + } + }); + $op(client).await + } else { + let (client, connection) = tokio_postgres::connect(path.as_str(), tokio_postgres::NoTls).await.migration_err("could not connect to database", None)?; + tokio::spawn(async move { + if let Err(e) = connection.await { + eprintln!("connection error: {}", e); + } + }); + $op(client).await + } + } else if #[cfg(feature = "tokio-postgres-rustls")] { + if $config.use_tls() { + let native_certs = rustls_native_certs::load_native_certs(); + for err in &native_certs.errors { + log::warn!("Failed to load native TLS certificate: {err}"); + } + let mut root_store = rustls::RootCertStore::empty(); + root_store.add_parsable_certificates(native_certs.certs); + let tls_config = rustls::ClientConfig::builder() + .with_root_certificates(root_store) + .with_no_client_auth(); + let tls = tokio_postgres_rustls::MakeRustlsConnect::new(tls_config); + let (client, connection) = tokio_postgres::connect(path.as_str(), tls).await.migration_err("could not connect to database", None)?; + tokio::spawn(async move { + if let Err(e) = connection.await { + eprintln!("connection error: {}", e); + } + }); + $op(client).await + } else { + let (client, connection) = tokio_postgres::connect(path.as_str(), tokio_postgres::NoTls).await.migration_err("could not connect to database", None)?; + tokio::spawn(async move { + if let Err(e) = connection.await { + eprintln!("connection error: {}", e); + } + }); + $op(client).await + } + } else { + if $config.use_tls() { + panic!("TLS was requested but neither 'tls' nor 'tokio-postgres-rustls' feature is enabled in refinery-core"); + } + let (client, connection) = tokio_postgres::connect(path.as_str(), tokio_postgres::NoTls).await.migration_err("could not connect to database", None)?; + tokio::spawn(async move { + if let Err(e) = connection.await { + eprintln!("connection error: {}", e); + } + }); + $op(client).await } - }); - $op(client).await + } } else { panic!("tried to migrate async from config for a postgresql database, but tokio-postgres was not enabled!"); } diff --git a/refinery_core/src/drivers/mod.rs b/refinery_core/src/drivers/mod.rs index 867d4c4d..12688c67 100644 --- a/refinery_core/src/drivers/mod.rs +++ b/refinery_core/src/drivers/mod.rs @@ -16,4 +16,5 @@ pub mod mysql; #[cfg(feature = "tiberius")] pub mod tiberius; +#[cfg(feature = "config")] mod config; diff --git a/refinery_core/src/drivers/mysql.rs b/refinery_core/src/drivers/mysql.rs index 33148d94..c2947152 100644 --- a/refinery_core/src/drivers/mysql.rs +++ b/refinery_core/src/drivers/mysql.rs @@ -43,10 +43,13 @@ fn query_applied_migrations( impl Transaction for Conn { type Error = MError; - fn execute(&mut self, queries: &[&str]) -> Result { + fn execute<'a, T: Iterator>( + &mut self, + queries: T, + ) -> Result { let mut transaction = self.start_transaction(get_tx_opts())?; let mut count = 0; - for query in queries.iter() { + for query in queries { transaction.query_iter(query)?; count += 1; } @@ -58,11 +61,14 @@ impl Transaction for Conn { impl Transaction for PooledConn { type Error = MError; - fn execute(&mut self, queries: &[&str]) -> Result { + fn execute<'a, T: Iterator>( + &mut self, + queries: T, + ) -> Result { let mut transaction = self.start_transaction(get_tx_opts())?; let mut count = 0; - for query in queries.iter() { + for query in queries { transaction.query_iter(query)?; count += 1; } diff --git a/refinery_core/src/drivers/mysql_async.rs b/refinery_core/src/drivers/mysql_async.rs index 101ab0ab..675b96f9 100644 --- a/refinery_core/src/drivers/mysql_async.rs +++ b/refinery_core/src/drivers/mysql_async.rs @@ -1,4 +1,5 @@ use crate::traits::r#async::{AsyncMigrate, AsyncQuery, AsyncTransaction}; +use crate::util::SchemaVersion; use crate::Migration; use async_trait::async_trait; use mysql_async::{ @@ -16,7 +17,7 @@ async fn query_applied_migrations<'a>( let applied = result .into_iter() .map(|row| { - let (version, name, applied_on, checksum): (i32, String, String, String) = + let (version, name, applied_on, checksum): (SchemaVersion, String, String, String) = mysql_async::from_row(row); // Safe to call unwrap, as we stored it in RFC3339 format on the database @@ -39,7 +40,10 @@ async fn query_applied_migrations<'a>( impl AsyncTransaction for Pool { type Error = MError; - async fn execute(&mut self, queries: &[&str]) -> Result { + async fn execute<'a, T: Iterator + Send>( + &mut self, + queries: T, + ) -> Result { let mut conn = self.get_conn().await?; let mut options = TxOpts::new(); options.with_isolation_level(Some(IsolationLevel::ReadCommitted)); @@ -47,7 +51,7 @@ impl AsyncTransaction for Pool { let mut transaction = conn.start_transaction(options).await?; let mut count = 0; for query in queries { - transaction.query_drop(*query).await?; + transaction.query_drop(query).await?; count += 1; } transaction.commit().await?; diff --git a/refinery_core/src/drivers/postgres.rs b/refinery_core/src/drivers/postgres.rs index 3d177509..fa4d5b9e 100644 --- a/refinery_core/src/drivers/postgres.rs +++ b/refinery_core/src/drivers/postgres.rs @@ -33,10 +33,13 @@ fn query_applied_migrations( impl Transaction for PgClient { type Error = PgError; - fn execute(&mut self, queries: &[&str]) -> Result { + fn execute<'a, T: Iterator>( + &mut self, + queries: T, + ) -> Result { let mut transaction = PgClient::transaction(self)?; let mut count = 0; - for query in queries.iter() { + for query in queries { PgTransaction::batch_execute(&mut transaction, query)?; count += 1; } diff --git a/refinery_core/src/drivers/rusqlite.rs b/refinery_core/src/drivers/rusqlite.rs index 9547ba48..9ee4ced9 100644 --- a/refinery_core/src/drivers/rusqlite.rs +++ b/refinery_core/src/drivers/rusqlite.rs @@ -32,10 +32,13 @@ fn query_applied_migrations( impl Transaction for RqlConnection { type Error = RqlError; - fn execute(&mut self, queries: &[&str]) -> Result { + fn execute<'a, T: Iterator>( + &mut self, + queries: T, + ) -> Result { let transaction = self.transaction()?; let mut count = 0; - for query in queries.iter() { + for query in queries { transaction.execute_batch(query)?; count += 1; } diff --git a/refinery_core/src/drivers/tiberius.rs b/refinery_core/src/drivers/tiberius.rs index 117c6344..4095f1b7 100644 --- a/refinery_core/src/drivers/tiberius.rs +++ b/refinery_core/src/drivers/tiberius.rs @@ -1,4 +1,5 @@ use crate::traits::r#async::{AsyncMigrate, AsyncQuery, AsyncTransaction}; +use crate::util::SchemaVersion; use crate::Migration; use async_trait::async_trait; @@ -19,7 +20,7 @@ async fn query_applied_migrations( // Unfortunately too many unwraps as `Row::get` maps to Option instead of T while let Some(item) = rows.try_next().await? { if let QueryItem::Row(row) = item { - let version = row.get::(0).unwrap(); + let version = row.get::(0).unwrap(); let applied_on: &str = row.get::<&str, usize>(2).unwrap(); // Safe to call unwrap, as we stored it in RFC3339 format on the database let applied_on = OffsetDateTime::parse(applied_on, &Rfc3339).unwrap(); @@ -46,13 +47,16 @@ where { type Error = Error; - async fn execute(&mut self, queries: &[&str]) -> Result { + async fn execute<'a, T: Iterator + Send>( + &mut self, + queries: T, + ) -> Result { // Tiberius doesn't support transactions, see https://github.com/prisma/tiberius/issues/28 self.simple_query("BEGIN TRAN T1;").await?; let mut count = 0; for query in queries { // Drop the returning `QueryStream<'a>` to avoid compiler complaning regarding lifetimes - if let Err(err) = self.simple_query(*query).await.map(drop) { + if let Err(err) = self.simple_query(query).await.map(drop) { if let Err(err) = self.simple_query("ROLLBACK TRAN T1").await { log::error!("could not ROLLBACK transaction, {}", err); } @@ -85,15 +89,14 @@ where { fn assert_migrations_table_query(migration_table_name: &str) -> String { format!( - "IF NOT EXISTS(SELECT 1 FROM sys.Tables WHERE Name = N'{table_name}') + "IF NOT EXISTS(SELECT 1 FROM sys.Tables WHERE Name = N'{migration_table_name}') BEGIN - CREATE TABLE {table_name}( + CREATE TABLE {migration_table_name}( version INT PRIMARY KEY, name VARCHAR(255), applied_on VARCHAR(255), checksum VARCHAR(255)); - END", - table_name = migration_table_name + END" ) } } diff --git a/refinery_core/src/drivers/tokio_postgres.rs b/refinery_core/src/drivers/tokio_postgres.rs index e8feaf7a..346cfd7c 100644 --- a/refinery_core/src/drivers/tokio_postgres.rs +++ b/refinery_core/src/drivers/tokio_postgres.rs @@ -35,11 +35,14 @@ async fn query_applied_migrations( impl AsyncTransaction for Client { type Error = PgError; - async fn execute(&mut self, queries: &[&str]) -> Result { + async fn execute<'a, T: Iterator + Send>( + &mut self, + queries: T, + ) -> Result { let transaction = self.transaction().await?; let mut count = 0; for query in queries { - transaction.batch_execute(*query).await?; + transaction.batch_execute(query).await?; count += 1; } transaction.commit().await?; diff --git a/refinery_core/src/error.rs b/refinery_core/src/error.rs index 3e865c1e..2b1b4661 100644 --- a/refinery_core/src/error.rs +++ b/refinery_core/src/error.rs @@ -6,14 +6,17 @@ use thiserror::Error as TError; /// An Error occurred during a migration cycle #[derive(Debug)] pub struct Error { - kind: Kind, + kind: Box, report: Option, } impl Error { /// Instantiate a new Error pub(crate) fn new(kind: Kind, report: Option) -> Error { - Error { kind, report } + Error { + kind: Box::new(kind), + report, + } } /// Return the Report of the migration cycle if any @@ -66,6 +69,9 @@ pub enum Kind { /// An Error from an underlying database connection Error #[error("`{0}`, `{1}`")] Connection(String, #[source] Box), + /// An Error from an invalid migration file (not UTF-8 etc) + #[error("invalid migration file at path {0}, {1}")] + InvalidMigrationFile(PathBuf, std::io::Error), } // Helper trait for adding custom messages and applied migrations to Connection error's. @@ -85,7 +91,7 @@ where match self { Ok(report) => Ok(report), Err(err) => Err(Error { - kind: Kind::Connection(msg.into(), Box::new(err)), + kind: Box::new(Kind::Connection(msg.into(), Box::new(err))), report: applied_migrations.map(|am| Report::new(am.to_vec())), }), } diff --git a/refinery_core/src/lib.rs b/refinery_core/src/lib.rs index 35d02a47..a6230f84 100644 --- a/refinery_core/src/lib.rs +++ b/refinery_core/src/lib.rs @@ -1,3 +1,4 @@ +#[cfg(feature = "config")] pub mod config; mod drivers; pub mod error; @@ -9,7 +10,9 @@ pub use crate::error::Error; pub use crate::runner::{Migration, Report, Runner, Target}; pub use crate::traits::r#async::AsyncMigrate; pub use crate::traits::sync::Migrate; -pub use crate::util::{find_migration_files, MigrationType}; +pub use crate::util::{ + find_migration_files, load_sql_migrations, parse_migration_name, MigrationType, SchemaVersion, +}; #[cfg(feature = "rusqlite")] pub use rusqlite; diff --git a/refinery_core/src/runner.rs b/refinery_core/src/runner.rs index 87cd00df..adf34d10 100644 --- a/refinery_core/src/runner.rs +++ b/refinery_core/src/runner.rs @@ -1,4 +1,3 @@ -use regex::Regex; use siphasher::sip::SipHasher13; use time::OffsetDateTime; @@ -7,21 +6,15 @@ use std::cmp::Ordering; use std::collections::VecDeque; use std::fmt; use std::hash::{Hash, Hasher}; -use std::sync::OnceLock; -use crate::error::Kind; use crate::traits::{sync::migrate as sync_migrate, DEFAULT_MIGRATION_TABLE_NAME}; +use crate::util::{parse_migration_name, SchemaVersion}; use crate::{AsyncMigrate, Error, Migrate}; use std::fmt::Formatter; -// regex used to match file names -pub fn file_match_re() -> &'static Regex { - static RE: OnceLock = OnceLock::new(); - RE.get_or_init(|| Regex::new(r"^([U|V])(\d+(?:\.\d+)?)__(\w+)").unwrap()) -} - /// An enum set that represents the type of the Migration #[derive(Clone, PartialEq)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub enum Type { Versioned, Unversioned, @@ -33,7 +26,7 @@ impl fmt::Display for Type { Type::Versioned => "V", Type::Unversioned => "U", }; - write!(f, "{}", version_type) + write!(f, "{version_type}") } } @@ -43,7 +36,7 @@ impl fmt::Debug for Type { Type::Versioned => "Versioned", Type::Unversioned => "Unversioned", }; - write!(f, "{}", version_type) + write!(f, "{version_type}") } } @@ -51,14 +44,15 @@ impl fmt::Debug for Type { #[derive(Clone, Copy, Debug)] pub enum Target { Latest, - Version(u32), + Version(SchemaVersion), Fake, - FakeVersion(u32), + FakeVersion(SchemaVersion), } // an Enum set that represents the state of the migration: Applied on the database, // or Unapplied yet to be applied on the database #[derive(Clone, Debug)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] enum State { Applied, Unapplied, @@ -70,11 +64,12 @@ enum State { /// /// [`embed_migrations!`]: macro.embed_migrations.html #[derive(Clone, Debug)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub struct Migration { state: State, name: String, checksum: u64, - version: i32, + version: SchemaVersion, prefix: Type, sql: Option, applied_on: Option, @@ -84,20 +79,7 @@ impl Migration { /// Create an unapplied migration, name and version are parsed from the input_name, /// which must be named in the format (U|V){1}__{2}.rs where {1} represents the migration version and {2} the name. pub fn unapplied(input_name: &str, sql: &str) -> Result { - let captures = file_match_re() - .captures(input_name) - .filter(|caps| caps.len() == 4) - .ok_or_else(|| Error::new(Kind::InvalidName, None))?; - let version: i32 = captures[2] - .parse() - .map_err(|_| Error::new(Kind::InvalidVersion, None))?; - - let name: String = (&captures[3]).into(); - let prefix = match &captures[1] { - "V" => Type::Versioned, - "U" => Type::Unversioned, - _ => unreachable!(), - }; + let (prefix, version, name) = parse_migration_name(input_name)?; // Previously, `std::collections::hash_map::DefaultHasher` was used // to calculate the checksum and the implementation at that time @@ -125,8 +107,8 @@ impl Migration { } // Create a migration from an applied migration on the database - pub(crate) fn applied( - version: i32, + pub fn applied( + version: SchemaVersion, name: String, applied_on: OffsetDateTime, checksum: u64, @@ -144,7 +126,7 @@ impl Migration { } // convert the Unapplied into an Applied Migration - pub(crate) fn set_applied(&mut self) { + pub fn set_applied(&mut self) { self.applied_on = Some(OffsetDateTime::now_utc()); self.state = State::Applied; } @@ -155,8 +137,8 @@ impl Migration { } /// Get the Migration version - pub fn version(&self) -> u32 { - self.version as u32 + pub fn version(&self) -> SchemaVersion { + self.version } /// Get the Prefix @@ -225,7 +207,7 @@ pub struct Report { impl Report { /// Instantiate a new Report - pub(crate) fn new(applied_migrations: Vec) -> Report { + pub fn new(applied_migrations: Vec) -> Report { Report { applied_migrations } } diff --git a/refinery_core/src/traits/async.rs b/refinery_core/src/traits/async.rs index 75621449..a0305d2e 100644 --- a/refinery_core/src/traits/async.rs +++ b/refinery_core/src/traits/async.rs @@ -1,7 +1,7 @@ use crate::error::WrapMigrationError; use crate::traits::{ - insert_migration_query, verify_migrations, ASSERT_MIGRATIONS_TABLE_QUERY, - GET_APPLIED_MIGRATIONS_QUERY, GET_LAST_APPLIED_MIGRATION_QUERY, + insert_migration_query, verify_migrations, GET_APPLIED_MIGRATIONS_QUERY, + GET_LAST_APPLIED_MIGRATION_QUERY, }; use crate::{Error, Migration, Report, Target}; @@ -12,7 +12,10 @@ use std::string::ToString; pub trait AsyncTransaction { type Error: std::error::Error + Send + Sync + 'static; - async fn execute(&mut self, query: &[&str]) -> Result; + async fn execute<'a, T: Iterator + Send>( + &mut self, + queries: T, + ) -> Result; } #[async_trait] @@ -43,13 +46,16 @@ async fn migrate( migration.set_applied(); let update_query = insert_migration_query(&migration, migration_table_name); transaction - .execute(&[ - migration.sql().as_ref().expect("sql must be Some!"), - &update_query, - ]) + .execute( + [ + migration.sql().as_ref().expect("sql must be Some!"), + update_query.as_str(), + ] + .into_iter(), + ) .await .migration_err( - &format!("error applying migration {}", migration), + &format!("error applying migration {migration}"), Some(&applied_migrations), )?; applied_migrations.push(migration); @@ -91,9 +97,13 @@ async fn migrate_grouped( log::info!("not going to apply any migration as fake flag is enabled"); } Target::Latest | Target::Version(_) => { + let migrations_display = applied_migrations + .iter() + .map(ToString::to_string) + .collect::>() + .join("\n"); log::info!( - "going to apply batch migrations in single transaction: {:#?}", - applied_migrations.iter().map(ToString::to_string) + "going to apply batch migrations in single transaction:\n{migrations_display}" ); } }; @@ -105,10 +115,8 @@ async fn migrate_grouped( ); } - let refs: Vec<&str> = grouped_migrations.iter().map(AsRef::as_ref).collect(); - transaction - .execute(refs.as_ref()) + .execute(grouped_migrations.iter().map(AsRef::as_ref)) .await .migration_err("error applying migrations", None)?; @@ -122,7 +130,15 @@ where { // Needed cause some database vendors like Mssql have a non sql standard way of checking the migrations table fn assert_migrations_table_query(migration_table_name: &str) -> String { - ASSERT_MIGRATIONS_TABLE_QUERY.replace("%MIGRATION_TABLE_NAME%", migration_table_name) + super::assert_migrations_table_query(migration_table_name) + } + + fn get_last_applied_migration_query(migration_table_name: &str) -> String { + GET_LAST_APPLIED_MIGRATION_QUERY.replace("%MIGRATION_TABLE_NAME%", migration_table_name) + } + + fn get_applied_migrations_query(migration_table_name: &str) -> String { + GET_APPLIED_MIGRATIONS_QUERY.replace("%MIGRATION_TABLE_NAME%", migration_table_name) } async fn get_last_applied_migration( @@ -130,10 +146,7 @@ where migration_table_name: &str, ) -> Result, Error> { let mut migrations = self - .query( - &GET_LAST_APPLIED_MIGRATION_QUERY - .replace("%MIGRATION_TABLE_NAME%", migration_table_name), - ) + .query(Self::get_last_applied_migration_query(migration_table_name).as_ref()) .await .migration_err("error getting last applied migration", None)?; @@ -145,10 +158,7 @@ where migration_table_name: &str, ) -> Result, Error> { let migrations = self - .query( - &GET_APPLIED_MIGRATIONS_QUERY - .replace("%MIGRATION_TABLE_NAME%", migration_table_name), - ) + .query(Self::get_applied_migrations_query(migration_table_name).as_ref()) .await .migration_err("error getting applied migrations", None)?; @@ -164,15 +174,14 @@ where target: Target, migration_table_name: &str, ) -> Result { - self.execute(&[&Self::assert_migrations_table_query(migration_table_name)]) - .await - .migration_err("error asserting migrations table", None)?; + self.execute( + [Self::assert_migrations_table_query(migration_table_name).as_ref()].into_iter(), + ) + .await + .migration_err("error asserting migrations table", None)?; let applied_migrations = self - .query( - &GET_APPLIED_MIGRATIONS_QUERY - .replace("%MIGRATION_TABLE_NAME%", migration_table_name), - ) + .get_applied_migrations(migration_table_name) .await .migration_err("error getting current schema version", None)?; diff --git a/refinery_core/src/traits/mod.rs b/refinery_core/src/traits/mod.rs index 9d79a5d1..6bda4142 100644 --- a/refinery_core/src/traits/mod.rs +++ b/refinery_core/src/traits/mod.rs @@ -4,6 +4,7 @@ pub mod r#async; pub mod sync; use crate::runner::Type; +use crate::util::SchemaVersion; use crate::{error::Kind, Error, Migration}; // Verifies applied and to be applied migrations returning Error if: @@ -49,10 +50,10 @@ pub(crate) fn verify_migrations( } } - let current: i32 = match applied.last() { + let current: SchemaVersion = match applied.last() { Some(last) => { log::info!("current version: {}", last.version()); - last.version() as i32 + last.version() as SchemaVersion } None => { log::info!("schema history table is empty, going to apply all migrations"); @@ -73,7 +74,7 @@ pub(crate) fn verify_migrations( if to_be_applied.contains(&migration) { return Err(Error::new(Kind::RepeatedVersion(migration), None)); } else if migration.prefix() == &Type::Versioned - && current >= migration.version() as i32 + && current >= migration.version() as SchemaVersion { if abort_missing { return Err(Error::new(Kind::MissingVersion(migration), None)); @@ -105,17 +106,30 @@ pub(crate) fn insert_migration_query(migration: &Migration, migration_table_name pub(crate) const ASSERT_MIGRATIONS_TABLE_QUERY: &str = "CREATE TABLE IF NOT EXISTS %MIGRATION_TABLE_NAME%( - version INT4 PRIMARY KEY, + version %VERSION_TYPE% PRIMARY KEY, name VARCHAR(255), applied_on VARCHAR(255), checksum VARCHAR(255));"; +pub(crate) fn assert_migrations_table_query(migration_table_name: &str) -> String { + ASSERT_MIGRATIONS_TABLE_QUERY + .replace("%MIGRATION_TABLE_NAME%", migration_table_name) + .replace( + "%VERSION_TYPE%", + if cfg!(feature = "int8-versions") { + "int8" + } else { + "int4" + }, + ) +} + pub(crate) const GET_APPLIED_MIGRATIONS_QUERY: &str = "SELECT version, name, applied_on, checksum \ FROM %MIGRATION_TABLE_NAME% ORDER BY version ASC;"; pub(crate) const GET_LAST_APPLIED_MIGRATION_QUERY: &str = "SELECT version, name, applied_on, checksum - FROM %MIGRATION_TABLE_NAME% WHERE version=(SELECT MAX(version) from refinery_schema_history)"; + FROM %MIGRATION_TABLE_NAME% WHERE version=(SELECT MAX(version) from %MIGRATION_TABLE_NAME%)"; pub(crate) const DEFAULT_MIGRATION_TABLE_NAME: &str = "refinery_schema_history"; diff --git a/refinery_core/src/traits/sync.rs b/refinery_core/src/traits/sync.rs index 58238c0d..f7ad2a65 100644 --- a/refinery_core/src/traits/sync.rs +++ b/refinery_core/src/traits/sync.rs @@ -1,14 +1,17 @@ use crate::error::WrapMigrationError; use crate::traits::{ - insert_migration_query, verify_migrations, ASSERT_MIGRATIONS_TABLE_QUERY, - GET_APPLIED_MIGRATIONS_QUERY, GET_LAST_APPLIED_MIGRATION_QUERY, + insert_migration_query, verify_migrations, GET_APPLIED_MIGRATIONS_QUERY, + GET_LAST_APPLIED_MIGRATION_QUERY, }; use crate::{Error, Migration, Report, Target}; pub trait Transaction { type Error: std::error::Error + Send + Sync + 'static; - fn execute(&mut self, queries: &[&str]) -> Result; + fn execute<'a, T: Iterator>( + &mut self, + queries: T, + ) -> Result; } pub trait Query: Transaction { @@ -36,7 +39,6 @@ pub fn migrate( } } - log::info!("applying migration: {}", migration); migration.set_applied(); let insert_migration = insert_migration_query(&migration, migration_table_name); let migration_sql = migration.sql().expect("sql must be Some!").to_string(); @@ -51,33 +53,48 @@ pub fn migrate( match (target, batched) { (Target::Fake | Target::FakeVersion(_), _) => { - log::info!("not going to apply any migration as fake flag is enabled"); + log::info!("not going to apply any migration as fake flag is enabled."); } (Target::Latest | Target::Version(_), true) => { log::info!( - "going to apply batch migrations in single transaction: {:#?}", - applied_migrations.iter().map(ToString::to_string) + "going to batch apply {} migrations in single transaction.", + applied_migrations.len() ); } (Target::Latest | Target::Version(_), false) => { log::info!( - "preparing to apply {} migrations: {:#?}", + "going to apply {} migrations in multiple transactions.", applied_migrations.len(), - applied_migrations.iter().map(ToString::to_string) ); } }; - let refs: Vec<&str> = migration_batch.iter().map(AsRef::as_ref).collect(); + let refs = migration_batch.iter().map(AsRef::as_ref); if batched { + let migrations_display = applied_migrations + .iter() + .map(ToString::to_string) + .collect::>() + .join("\n"); + log::info!("going to apply batch migrations in single transaction:\n{migrations_display}"); transaction - .execute(refs.as_ref()) + .execute(refs) .migration_err("error applying migrations", None)?; } else { - for (i, update) in refs.iter().enumerate() { + for (i, update) in refs.enumerate() { + // first iteration is pair so we know the following even in the iteration index + // marks the previous (pair) migration as completed. + let applying_migration = i % 2 == 0; + let current_migration = &applied_migrations[i / 2]; + if applying_migration { + log::info!("applying migration: {current_migration} ..."); + } else { + // Writing the migration state to the db. + log::debug!("applied migration: {current_migration} writing state to db."); + } transaction - .execute(&[update]) + .execute([update].into_iter()) .migration_err("error applying update", Some(&applied_migrations[0..i / 2]))?; } } @@ -89,13 +106,26 @@ pub trait Migrate: Query> where Self: Sized, { + // Needed cause some database vendors like Mssql have a non sql standard way of checking the migrations table + fn assert_migrations_table_query(migration_table_name: &str) -> String { + super::assert_migrations_table_query(migration_table_name) + } + + fn get_last_applied_migration_query(migration_table_name: &str) -> String { + GET_LAST_APPLIED_MIGRATION_QUERY.replace("%MIGRATION_TABLE_NAME%", migration_table_name) + } + + fn get_applied_migrations_query(migration_table_name: &str) -> String { + GET_APPLIED_MIGRATIONS_QUERY.replace("%MIGRATION_TABLE_NAME%", migration_table_name) + } + fn assert_migrations_table(&mut self, migration_table_name: &str) -> Result { // Needed cause some database vendors like Mssql have a non sql standard way of checking the migrations table, - // thou on this case it's just to be consistent with the async trait `AsyncMigrate` - self.execute(&[ASSERT_MIGRATIONS_TABLE_QUERY - .replace("%MIGRATION_TABLE_NAME%", migration_table_name) - .as_str()]) - .migration_err("error asserting migrations table", None) + // though on this case it's just to be consistent with the async trait `AsyncMigrate` + self.execute( + [Self::assert_migrations_table_query(migration_table_name).as_ref()].into_iter(), + ) + .migration_err("error asserting migrations table", None) } fn get_last_applied_migration( @@ -103,10 +133,7 @@ where migration_table_name: &str, ) -> Result, Error> { let mut migrations = self - .query( - &GET_LAST_APPLIED_MIGRATION_QUERY - .replace("%MIGRATION_TABLE_NAME%", migration_table_name), - ) + .query(Self::get_last_applied_migration_query(migration_table_name).as_str()) .migration_err("error getting last applied migration", None)?; Ok(migrations.pop()) @@ -117,10 +144,7 @@ where migration_table_name: &str, ) -> Result, Error> { let migrations = self - .query( - &GET_APPLIED_MIGRATIONS_QUERY - .replace("%MIGRATION_TABLE_NAME%", migration_table_name), - ) + .query(Self::get_applied_migrations_query(migration_table_name).as_str()) .migration_err("error getting applied migrations", None)?; Ok(migrations) diff --git a/refinery_core/src/util.rs b/refinery_core/src/util.rs index 5992aab3..d19f167f 100644 --- a/refinery_core/src/util.rs +++ b/refinery_core/src/util.rs @@ -1,9 +1,37 @@ use crate::error::{Error, Kind}; +use crate::runner::Type; +use crate::Migration; use regex::Regex; use std::ffi::OsStr; use std::path::{Path, PathBuf}; +use std::sync::OnceLock; use walkdir::{DirEntry, WalkDir}; +#[cfg(not(feature = "int8-versions"))] +pub type SchemaVersion = i32; +#[cfg(feature = "int8-versions")] +pub type SchemaVersion = i64; + +const STEM_RE: &str = r"^([U|V])(\d+(?:\.\d+)?)__(\w+)"; + +/// Matches the stem of a migration file. +fn file_stem_re() -> &'static Regex { + static RE: OnceLock = OnceLock::new(); + RE.get_or_init(|| Regex::new(STEM_RE).unwrap()) +} + +/// Matches the stem + extension of a SQL migration file. +fn file_re_sql() -> &'static Regex { + static RE: OnceLock = OnceLock::new(); + RE.get_or_init(|| Regex::new([STEM_RE, r"\.sql$"].concat().as_str()).unwrap()) +} + +/// Matches the stem + extension of any migration file. +fn file_re_all() -> &'static Regex { + static RE: OnceLock = OnceLock::new(); + RE.get_or_init(|| Regex::new([STEM_RE, r"\.(rs|sql)$"].concat().as_str()).unwrap()) +} + /// enum containing the migration types used to search for migrations /// either just .sql files or both .sql and .rs pub enum MigrationType { @@ -12,16 +40,34 @@ pub enum MigrationType { } impl MigrationType { - fn file_match_re(&self) -> Regex { - let ext = match self { - MigrationType::All => "(rs|sql)", - MigrationType::Sql => "sql", - }; - let re_str = format!(r"^(U|V)(\d+(?:\.\d+)?)__(\w+)\.{}$", ext); - Regex::new(re_str.as_str()).unwrap() + fn file_match_re(&self) -> &'static Regex { + match self { + MigrationType::All => file_re_all(), + MigrationType::Sql => file_re_sql(), + } } } +/// Parse a migration filename stem into a prefix, version, and name. +pub fn parse_migration_name(name: &str) -> Result<(Type, SchemaVersion, String), Error> { + let captures = file_stem_re() + .captures(name) + .filter(|caps| caps.len() == 4) + .ok_or_else(|| Error::new(Kind::InvalidName, None))?; + let version: SchemaVersion = captures[2] + .parse() + .map_err(|_| Error::new(Kind::InvalidVersion, None))?; + + let name: String = (&captures[3]).into(); + let prefix = match &captures[1] { + "V" => Type::Versioned, + "U" => Type::Unversioned, + _ => unreachable!(), + }; + + Ok((prefix, version, name)) +} + /// find migrations on file system recursively across directories given a location and [MigrationType] pub fn find_migration_files( location: impl AsRef, @@ -41,8 +87,12 @@ pub fn find_migration_files( .filter_map(Result::ok) .map(DirEntry::into_path) // filter by migration file regex - .filter( - move |entry| match entry.file_name().and_then(OsStr::to_str) { + .filter(move |entry| { + if entry.is_dir() { + return false; + } + + match entry.file_name().and_then(OsStr::to_str) { Some(file_name) if re.is_match(file_name) => true, Some(file_name) => { log::warn!( @@ -52,15 +102,47 @@ pub fn find_migration_files( false } None => false, - }, - ); + } + }); Ok(file_paths) } +/// Loads SQL migrations from a path. This enables dynamic migration discovery, as opposed to +/// embedding. The resulting collection is ordered by version. +pub fn load_sql_migrations(location: impl AsRef) -> Result, Error> { + let migration_files = find_migration_files(location, MigrationType::Sql)?; + + let mut migrations = vec![]; + + for path in migration_files { + let sql = std::fs::read_to_string(path.as_path()).map_err(|e| { + let path = path.to_owned(); + let kind = match e.kind() { + std::io::ErrorKind::NotFound => Kind::InvalidMigrationPath(path, e), + _ => Kind::InvalidMigrationFile(path, e), + }; + + Error::new(kind, None) + })?; + + //safe to call unwrap as find_migration_filenames returns canonical paths + let filename = path + .file_stem() + .and_then(|file| file.to_os_string().into_string().ok()) + .unwrap(); + + let migration = Migration::unapplied(&filename, &sql)?; + migrations.push(migration); + } + + migrations.sort(); + Ok(migrations) +} + #[cfg(test)] mod tests { - use super::{find_migration_files, MigrationType}; + use super::{find_migration_files, load_sql_migrations, MigrationType}; use std::fs; use std::path::PathBuf; use tempfile::TempDir; @@ -89,9 +171,9 @@ mod tests { let migrations_dir = tmp_dir.path().join("migrations"); fs::create_dir(&migrations_dir).unwrap(); let sql1 = migrations_dir.join("V1first.rs"); - fs::File::create(&sql1).unwrap(); + fs::File::create(sql1).unwrap(); let sql2 = migrations_dir.join("V2second.rs"); - fs::File::create(&sql2).unwrap(); + fs::File::create(sql2).unwrap(); let mut mods = find_migration_files(migrations_dir, MigrationType::All).unwrap(); assert!(mods.next().is_none()); @@ -115,6 +197,28 @@ mod tests { assert_eq!(sql2.canonicalize().unwrap(), mods[1]); } + #[test] + fn finds_sql_migrations_in_nested_directories() { + let tmp_dir = TempDir::new().unwrap(); + let migrations_dir = tmp_dir.path().join("migrations"); + let nested_dir = migrations_dir.join("foo"); + fs::create_dir(&migrations_dir).unwrap(); + fs::create_dir(&nested_dir).unwrap(); + + let sql1 = nested_dir.join("V1__first.sql"); + let sql2 = nested_dir.join("V2__second.sql"); + fs::File::create(&sql1).unwrap(); + fs::File::create(&sql2).unwrap(); + + let mut mods: Vec = find_migration_files(migrations_dir, MigrationType::All) + .unwrap() + .collect(); + mods.sort(); + + assert_eq!(sql1.canonicalize().unwrap(), mods[0]); + assert_eq!(sql2.canonicalize().unwrap(), mods[1]); + } + #[test] fn finds_unversioned_migrations() { let tmp_dir = TempDir::new().unwrap(); @@ -139,11 +243,29 @@ mod tests { let migrations_dir = tmp_dir.path().join("migrations"); fs::create_dir(&migrations_dir).unwrap(); let sql1 = migrations_dir.join("V1first.sql"); - fs::File::create(&sql1).unwrap(); + fs::File::create(sql1).unwrap(); let sql2 = migrations_dir.join("V2second.sql"); - fs::File::create(&sql2).unwrap(); + fs::File::create(sql2).unwrap(); let mut mods = find_migration_files(migrations_dir, MigrationType::All).unwrap(); assert!(mods.next().is_none()); } + + #[test] + fn loads_migrations_from_path() { + let tmp_dir = TempDir::new().unwrap(); + let migrations_dir = tmp_dir.path().join("migrations"); + fs::create_dir(&migrations_dir).unwrap(); + let sql1 = migrations_dir.join("V1__first.sql"); + fs::File::create(&sql1).unwrap(); + let sql2 = migrations_dir.join("V2__second.sql"); + fs::File::create(&sql2).unwrap(); + let rs3 = migrations_dir.join("V3__third.rs"); + fs::File::create(&rs3).unwrap(); + + let migrations = load_sql_migrations(migrations_dir).unwrap(); + assert_eq!(migrations.len(), 2); + assert_eq!(&migrations[0].to_string(), "V1__first"); + assert_eq!(&migrations[1].to_string(), "V2__second"); + } } diff --git a/refinery_macros/Cargo.toml b/refinery_macros/Cargo.toml index 647836b8..1aadb11a 100644 --- a/refinery_macros/Cargo.toml +++ b/refinery_macros/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "refinery-macros" -version = "0.8.12" +version = "0.9.2" authors = ["Katharina Fey ", "João Oliveira "] description = "This crate should not be used directly, it is internally related to Refinery" license = "MIT OR Apache-2.0" @@ -8,15 +8,20 @@ documentation = "https://docs.rs/refinery/" repository = "https://github.com/rust-db/refinery" edition = "2018" +[features] +enums = ["dep:heck"] +int8-versions = ["refinery-core/int8-versions"] + [lib] proc-macro = true [dependencies] -refinery-core = { version = "0.8.12", path = "../refinery_core" } +refinery-core = { version = "0.9.2", path = "../refinery_core" } quote = "1" syn = "2" proc-macro2 = "1" regex = "1" +heck = { version = "0.5", optional = true } [dev-dependencies] tempfile = "3" diff --git a/refinery_macros/src/lib.rs b/refinery_macros/src/lib.rs index 34586fb7..cd790cc1 100644 --- a/refinery_macros/src/lib.rs +++ b/refinery_macros/src/lib.rs @@ -1,6 +1,8 @@ //! Contains Refinery macros that are used to import and embed migration files. #![recursion_limit = "128"] +#[cfg(feature = "enums")] +use heck::ToUpperCamelCase; use proc_macro::TokenStream; use proc_macro2::{Span as Span2, TokenStream as TokenStream2}; use quote::quote; @@ -18,7 +20,7 @@ pub(crate) fn crate_root() -> PathBuf { fn migration_fn_quoted(_migrations: Vec) -> TokenStream2 { let result = quote! { - use refinery::{Migration, Runner}; + use refinery::{Migration, Runner, SchemaVersion}; pub fn runner() -> Runner { let quoted_migrations: Vec<(&str, String)> = vec![#(#_migrations),*]; let mut migrations: Vec = Vec::new(); @@ -31,6 +33,56 @@ fn migration_fn_quoted(_migrations: Vec) -> TokenStream2 { result } +#[cfg(feature = "enums")] +fn migration_enum_quoted(migration_names: &[impl AsRef]) -> TokenStream2 { + use refinery_core::SchemaVersion; + + let mut variants = Vec::new(); + let mut discriminants = Vec::new(); + + for m in migration_names { + let m = m.as_ref(); + let (_, version, name) = refinery_core::parse_migration_name(m) + .unwrap_or_else(|e| panic!("Couldn't parse migration filename '{}': {:?}", m, e)); + let version: SchemaVersion = version; + let variant = Ident::new(name.to_upper_camel_case().as_str(), Span2::call_site()); + variants.push(quote! { #variant(Migration) = #version }); + discriminants.push(quote! { #version => Self::#variant(migration) }); + } + discriminants.push(quote! { v => panic!("Invalid migration version '{}'", v) }); + + #[cfg(feature = "int8-versions")] + let embedded = quote! { + #[repr(i64)] + #[derive(Debug)] + pub enum EmbeddedMigration { + #(#variants),* + } + }; + + #[cfg(not(feature = "int8-versions"))] + let embedded = quote! { + #[repr(i32)] + #[derive(Debug)] + pub enum EmbeddedMigration { + #(#variants),* + } + }; + + quote! { + + #embedded + + impl From for EmbeddedMigration { + fn from(migration: Migration) -> Self { + match migration.version() as SchemaVersion { + #(#discriminants),* + } + } + } + } +} + /// Interpret Rust or SQL migrations and inserts a function called runner that when called returns a [`Runner`] instance with the collected migration modules. /// /// When called without arguments `embed_migrations` searches for migration files on a directory called `migrations` at the root level of your crate. @@ -56,6 +108,7 @@ pub fn embed_migrations(input: TokenStream) -> TokenStream { let mut migrations_mods = Vec::new(); let mut _migrations = Vec::new(); + let mut migration_filenames = Vec::new(); for migration in migration_files { // safe to call unwrap as find_migration_filenames returns canonical paths @@ -65,6 +118,7 @@ pub fn embed_migrations(input: TokenStream) -> TokenStream { .unwrap(); let path = migration.display().to_string(); let extension = migration.extension().unwrap(); + migration_filenames.push(filename.clone()); if extension == "sql" { _migrations.push(quote! {(#filename, include_str!(#path).to_string())}); @@ -85,10 +139,16 @@ pub fn embed_migrations(input: TokenStream) -> TokenStream { } let fnq = migration_fn_quoted(_migrations); + #[cfg(feature = "enums")] + let enums = migration_enum_quoted(migration_filenames.as_slice()); + #[cfg(not(feature = "enums"))] + let enums = quote!(); + (quote! { pub mod migrations { #(#migrations_mods)* #fnq + #enums } }) .into() @@ -98,11 +158,55 @@ pub fn embed_migrations(input: TokenStream) -> TokenStream { mod tests { use super::{migration_fn_quoted, quote}; + #[cfg(all(feature = "enums", feature = "int8-versions"))] + #[test] + fn test_enum_fn_i8() { + let expected = concat! { + "# [repr (i64)] ", + "# [derive (Debug)] ", + "pub enum EmbeddedMigration { ", + "Foo (Migration) = 1i64 , ", + "BarBaz (Migration) = 3i64 ", + "} ", + "impl From < Migration > for EmbeddedMigration { ", + "fn from (migration : Migration) -> Self { ", + "match migration . version () as SchemaVersion { ", + "1i64 => Self :: Foo (migration) , ", + "3i64 => Self :: BarBaz (migration) , ", + "v => panic ! (\"Invalid migration version '{}'\" , v) ", + "} } }" + }; + let enums = super::migration_enum_quoted(&["V1__foo", "U3__barBAZ"]).to_string(); + assert_eq!(expected, enums); + } + + #[cfg(all(feature = "enums", not(feature = "int8-versions")))] + #[test] + fn test_enum_fn() { + let expected = concat! { + "# [repr (i32)] ", + "# [derive (Debug)] ", + "pub enum EmbeddedMigration { ", + "Foo (Migration) = 1i32 , ", + "BarBaz (Migration) = 3i32 ", + "} ", + "impl From < Migration > for EmbeddedMigration { ", + "fn from (migration : Migration) -> Self { ", + "match migration . version () as SchemaVersion { ", + "1i32 => Self :: Foo (migration) , ", + "3i32 => Self :: BarBaz (migration) , ", + "v => panic ! (\"Invalid migration version '{}'\" , v) ", + "} } }" + }; + let enums = super::migration_enum_quoted(&["V1__foo", "U3__barBAZ"]).to_string(); + assert_eq!(expected, enums); + } + #[test] fn test_quote_fn() { let migs = vec![quote!("V1__first", "valid_sql_file")]; let expected = concat! { - "use refinery :: { Migration , Runner } ; ", + "use refinery :: { Migration , Runner , SchemaVersion } ; ", "pub fn runner () -> Runner { ", "let quoted_migrations : Vec < (& str , String) > = vec ! [\"V1__first\" , \"valid_sql_file\"] ; ", "let mut migrations : Vec < Migration > = Vec :: new () ; ",