├── .cargo └── mutants.toml ├── .github ├── CODEOWNERS ├── dependabot.yml └── workflows │ ├── ci.yml │ └── security.yml ├── .gitignore ├── CHANGELOG.md ├── Cargo.lock ├── Cargo.toml ├── LICENSE.txt ├── README.md ├── clippy.toml ├── examples ├── async │ ├── Cargo.toml │ └── src │ │ └── main.rs ├── friend_car.sql ├── from-directory │ ├── Cargo.toml │ ├── build.rs │ ├── migrations │ │ ├── 01-friend_car │ │ │ └── up.sql │ │ ├── 02-add_birthday_column │ │ │ └── up.sql │ │ └── 03-add_animal_table │ │ │ ├── down.sql │ │ │ └── up.sql │ └── src │ │ └── main.rs └── simple │ ├── Cargo.toml │ └── src │ └── main.rs ├── rusqlite_migration ├── Cargo.toml ├── README.md ├── benches │ ├── 100_migrations │ │ ├── 1-comment │ │ │ ├── down.sql │ │ │ └── up.sql │ │ ├── 10-comment │ │ │ ├── down.sql │ │ │ └── up.sql │ │ ├── 100-comment │ │ │ ├── down.sql │ │ │ └── up.sql │ │ ├── 11-comment │ │ │ ├── down.sql │ │ │ └── up.sql │ │ ├── 12-comment │ │ │ ├── down.sql │ │ │ └── up.sql │ │ ├── 13-comment │ │ │ ├── down.sql │ │ │ └── up.sql │ │ ├── 14-comment │ │ │ ├── down.sql │ │ │ └── up.sql │ │ ├── 15-comment │ │ │ ├── down.sql │ │ │ └── up.sql │ │ ├── 16-comment │ │ │ ├── down.sql │ │ │ └── up.sql │ │ ├── 17-comment │ │ │ ├── down.sql │ │ │ └── up.sql │ │ ├── 18-comment │ │ │ ├── down.sql │ │ │ └── up.sql │ │ ├── 19-comment │ │ │ ├── down.sql │ │ │ └── up.sql │ │ ├── 2-comment │ │ │ ├── down.sql │ │ │ └── up.sql │ │ ├── 20-comment │ │ │ ├── down.sql │ │ │ └── up.sql │ │ ├── 21-comment │ │ │ ├── down.sql │ │ │ └── up.sql │ │ ├── 22-comment │ │ │ ├── down.sql │ │ │ └── up.sql │ │ ├── 23-comment │ │ │ ├── down.sql │ │ │ └── up.sql │ │ ├── 24-comment │ │ │ ├── down.sql │ │ │ └── up.sql │ │ ├── 25-comment │ │ │ ├── down.sql │ │ │ └── up.sql │ │ ├── 26-comment │ │ │ ├── down.sql │ │ │ └── up.sql │ │ ├── 27-comment │ │ │ ├── down.sql │ │ │ └── up.sql │ │ ├── 28-comment │ │ │ ├── down.sql │ │ │ └── up.sql │ │ ├── 29-comment │ │ │ ├── down.sql │ │ │ └── up.sql │ │ ├── 3-comment │ │ │ ├── down.sql │ │ │ └── up.sql │ │ ├── 30-comment │ │ │ ├── down.sql │ │ │ └── up.sql │ │ ├── 31-comment │ │ │ ├── down.sql │ │ │ └── up.sql │ │ ├── 32-comment │ │ │ ├── down.sql │ │ │ └── up.sql │ │ ├── 33-comment │ │ │ ├── down.sql │ │ │ └── up.sql │ │ ├── 34-comment │ │ │ ├── down.sql │ │ │ └── up.sql │ │ ├── 35-comment │ │ │ ├── down.sql │ │ │ └── up.sql │ │ ├── 36-comment │ │ │ ├── down.sql │ │ │ └── up.sql │ │ ├── 37-comment │ │ │ ├── down.sql │ │ │ └── up.sql │ │ ├── 38-comment │ │ │ ├── down.sql │ │ │ └── up.sql │ │ ├── 39-comment │ │ │ ├── down.sql │ │ │ └── up.sql │ │ ├── 4-comment │ │ │ ├── down.sql │ │ │ └── up.sql │ │ ├── 40-comment │ │ │ ├── down.sql │ │ │ └── up.sql │ │ ├── 41-comment │ │ │ ├── down.sql │ │ │ └── up.sql │ │ ├── 42-comment │ │ │ ├── down.sql │ │ │ └── up.sql │ │ ├── 43-comment │ │ │ ├── down.sql │ │ │ └── up.sql │ │ ├── 44-comment │ │ │ ├── down.sql │ │ │ └── up.sql │ │ ├── 45-comment │ │ │ ├── down.sql │ │ │ └── up.sql │ │ ├── 46-comment │ │ │ ├── down.sql │ │ │ └── up.sql │ │ ├── 47-comment │ │ │ ├── down.sql │ │ │ └── up.sql │ │ ├── 48-comment │ │ │ ├── down.sql │ │ │ └── up.sql │ │ ├── 49-comment │ │ │ ├── down.sql │ │ │ └── up.sql │ │ ├── 5-comment │ │ │ ├── down.sql │ │ │ └── up.sql │ │ ├── 50-comment │ │ │ ├── down.sql │ │ │ └── up.sql │ │ ├── 51-comment │ │ │ ├── down.sql │ │ │ └── up.sql │ │ ├── 52-comment │ │ │ ├── down.sql │ │ │ └── up.sql │ │ ├── 53-comment │ │ │ ├── down.sql │ │ │ └── up.sql │ │ ├── 54-comment │ │ │ ├── down.sql │ │ │ └── up.sql │ │ ├── 55-comment │ │ │ ├── down.sql │ │ │ └── up.sql │ │ ├── 56-comment │ │ │ ├── down.sql │ │ │ └── up.sql │ │ ├── 57-comment │ │ │ ├── down.sql │ │ │ └── up.sql │ │ ├── 58-comment │ │ │ ├── down.sql │ │ │ └── up.sql │ │ ├── 59-comment │ │ │ ├── down.sql │ │ │ └── up.sql │ │ ├── 6-comment │ │ │ ├── down.sql │ │ │ └── up.sql │ │ ├── 60-comment │ │ │ ├── down.sql │ │ │ └── up.sql │ │ ├── 61-comment │ │ │ ├── down.sql │ │ │ └── up.sql │ │ ├── 62-comment │ │ │ ├── down.sql │ │ │ └── up.sql │ │ ├── 63-comment │ │ │ ├── down.sql │ │ │ └── up.sql │ │ ├── 64-comment │ │ │ ├── down.sql │ │ │ └── up.sql │ │ ├── 65-comment │ │ │ ├── down.sql │ │ │ └── up.sql │ │ ├── 66-comment │ │ │ ├── down.sql │ │ │ └── up.sql │ │ ├── 67-comment │ │ │ ├── down.sql │ │ │ └── up.sql │ │ ├── 68-comment │ │ │ ├── down.sql │ │ │ └── up.sql │ │ ├── 69-comment │ │ │ ├── down.sql │ │ │ └── up.sql │ │ ├── 7-comment │ │ │ ├── down.sql │ │ │ └── up.sql │ │ ├── 70-comment │ │ │ ├── down.sql │ │ │ └── up.sql │ │ ├── 71-comment │ │ │ ├── down.sql │ │ │ └── up.sql │ │ ├── 72-comment │ │ │ ├── down.sql │ │ │ └── up.sql │ │ ├── 73-comment │ │ │ ├── down.sql │ │ │ └── up.sql │ │ ├── 74-comment │ │ │ ├── down.sql │ │ │ └── up.sql │ │ ├── 75-comment │ │ │ ├── down.sql │ │ │ └── up.sql │ │ ├── 76-comment │ │ │ ├── down.sql │ │ │ └── up.sql │ │ ├── 77-comment │ │ │ ├── down.sql │ │ │ └── up.sql │ │ ├── 78-comment │ │ │ ├── down.sql │ │ │ └── up.sql │ │ ├── 79-comment │ │ │ ├── down.sql │ │ │ └── up.sql │ │ ├── 8-comment │ │ │ ├── down.sql │ │ │ └── up.sql │ │ ├── 80-comment │ │ │ ├── down.sql │ │ │ └── up.sql │ │ ├── 81-comment │ │ │ ├── down.sql │ │ │ └── up.sql │ │ ├── 82-comment │ │ │ ├── down.sql │ │ │ └── up.sql │ │ ├── 83-comment │ │ │ ├── down.sql │ │ │ └── up.sql │ │ ├── 84-comment │ │ │ ├── down.sql │ │ │ └── up.sql │ │ ├── 85-comment │ │ │ ├── down.sql │ │ │ └── up.sql │ │ ├── 86-comment │ │ │ ├── down.sql │ │ │ └── up.sql │ │ ├── 87-comment │ │ │ ├── down.sql │ │ │ └── up.sql │ │ ├── 88-comment │ │ │ ├── down.sql │ │ │ └── up.sql │ │ ├── 89-comment │ │ │ ├── down.sql │ │ │ └── up.sql │ │ ├── 9-comment │ │ │ ├── down.sql │ │ │ └── up.sql │ │ ├── 90-comment │ │ │ ├── down.sql │ │ │ └── up.sql │ │ ├── 91-comment │ │ │ ├── down.sql │ │ │ └── up.sql │ │ ├── 92-comment │ │ │ ├── down.sql │ │ │ └── up.sql │ │ ├── 93-comment │ │ │ ├── down.sql │ │ │ └── up.sql │ │ ├── 94-comment │ │ │ ├── down.sql │ │ │ └── up.sql │ │ ├── 95-comment │ │ │ ├── down.sql │ │ │ └── up.sql │ │ ├── 96-comment │ │ │ ├── down.sql │ │ │ └── up.sql │ │ ├── 97-comment │ │ │ ├── down.sql │ │ │ └── up.sql │ │ ├── 98-comment │ │ │ ├── down.sql │ │ │ └── up.sql │ │ └── 99-comment │ │ │ ├── down.sql │ │ │ └── up.sql │ ├── 10_migrations │ │ ├── 1-comment │ │ │ ├── down.sql │ │ │ └── up.sql │ │ ├── 10-comment │ │ │ ├── down.sql │ │ │ └── up.sql │ │ ├── 2-comment │ │ │ ├── down.sql │ │ │ └── up.sql │ │ ├── 3-comment │ │ │ ├── down.sql │ │ │ └── up.sql │ │ ├── 4-comment │ │ │ ├── down.sql │ │ │ └── up.sql │ │ ├── 5-comment │ │ │ ├── down.sql │ │ │ └── up.sql │ │ ├── 6-comment │ │ │ ├── down.sql │ │ │ └── up.sql │ │ ├── 7-comment │ │ │ ├── down.sql │ │ │ └── up.sql │ │ ├── 8-comment │ │ │ ├── down.sql │ │ │ └── up.sql │ │ └── 9-comment │ │ │ ├── down.sql │ │ │ └── up.sql │ ├── README.md │ ├── criterion.rs │ ├── from_directory.rs │ ├── gen_migrations.sh │ └── iai.rs ├── build.rs └── src │ ├── builder.rs │ ├── errors.rs │ ├── errors │ ├── snapshots │ │ ├── rusqlite_migration__errors__tests__error_display__file_load.snap │ │ ├── rusqlite_migration__errors__tests__error_display__foreign_key_check.snap │ │ ├── rusqlite_migration__errors__tests__error_display__hook.snap │ │ ├── rusqlite_migration__errors__tests__error_display__invalid_user_version.snap │ │ ├── rusqlite_migration__errors__tests__error_display__migration_definition.snap │ │ ├── rusqlite_migration__errors__tests__error_display__rusqlite_error.snap │ │ ├── rusqlite_migration__errors__tests__error_display__specified_schema_version.snap │ │ ├── rusqlite_migration__errors__tests__error_display__too_high_schema_version.snap │ │ ├── rusqlite_migration__errors__tests__error_display__unrecognized.snap │ │ ├── rusqlite_migration__errors__tests__error_source_number_file_load.snap │ │ ├── rusqlite_migration__errors__tests__error_source_number_foreign_key_check.snap │ │ ├── rusqlite_migration__errors__tests__error_source_number_hook.snap │ │ ├── rusqlite_migration__errors__tests__error_source_number_invalid_user_version.snap │ │ ├── rusqlite_migration__errors__tests__error_source_number_migration_definition.snap │ │ ├── rusqlite_migration__errors__tests__error_source_number_rusqlite_error.snap │ │ ├── rusqlite_migration__errors__tests__error_source_number_specified_schema_version.snap │ │ ├── rusqlite_migration__errors__tests__error_source_number_too_high_schema_version.snap │ │ └── rusqlite_migration__errors__tests__error_source_number_unrecognized.snap │ └── tests.rs │ ├── lib.rs │ ├── loader.rs │ └── tests │ ├── builder.rs │ ├── core.rs │ ├── helpers.rs │ ├── mod.rs │ └── snapshots │ ├── rusqlite_migration__tests__builder__len_builder.snap │ ├── rusqlite_migration__tests__builder__valid_index.snap │ ├── rusqlite_migration__tests__core__all_valid_down_test.snap │ ├── rusqlite_migration__tests__core__all_valid_up_test.snap │ ├── rusqlite_migration__tests__core__everything.snap │ ├── rusqlite_migration__tests__core__everything_alt.snap │ ├── rusqlite_migration__tests__core__everything_compact_debug.snap │ ├── rusqlite_migration__tests__core__everything_debug.snap │ ├── rusqlite_migration__tests__core__invalid_fk_check_test.snap │ ├── rusqlite_migration__tests__core__migration_hook_debug.snap │ ├── rusqlite_migration__tests__core__read_only_db_all_valid.snap │ ├── rusqlite_migration__tests__core__up_down.snap │ ├── rusqlite_migration__tests__core__up_down_alt.snap │ ├── rusqlite_migration__tests__core__up_down_fk.snap │ ├── rusqlite_migration__tests__core__up_down_fk_alt.snap │ ├── rusqlite_migration__tests__core__up_only.snap │ ├── rusqlite_migration__tests__core__up_only_alt.snap │ └── rusqlite_migration__tests__core__user_version_error.snap └── rusqlite_migration_tests ├── Cargo.toml └── tests ├── from_directory_test.rs ├── integration_test.rs ├── lib.rs ├── migrations ├── bad_migration_id │ └── a-friend_car │ │ └── up.sql ├── empty_dir │ └── .gitkeep ├── invalid_utf8 │ └── 01-invalid_utf8 │ │ └── up.sql ├── just_down │ └── 01-friend_car │ │ └── down.sql ├── missing_migration_id │ └── friend_car │ │ └── up.sql ├── multiple │ ├── 01-friend_car │ │ └── up.sql │ └── 01-friend_car2 │ │ └── up.sql ├── non_consecutive │ ├── 01-friend_car │ │ └── up.sql │ ├── 03-add_animal_table │ │ ├── down.sql │ │ └── up.sql │ └── 04-add_birthday_column │ │ └── up.sql ├── too_large_migration_id │ └── 18446744073709551616-friend_car │ │ └── up.sql └── zero_as_id │ └── 00-friend_car │ └── up.sql ├── migrations_builder_from_iterator_test.rs ├── migrations_builder_test.rs ├── multiline_test.rs └── up_and_down.rs /.cargo/mutants.toml: -------------------------------------------------------------------------------- 1 | additional_cargo_args = ["--all-features"] 2 | -------------------------------------------------------------------------------- /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | * @cljoly 2 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: cargo 4 | directory: "/" 5 | schedule: 6 | interval: daily 7 | open-pull-requests-limit: 10 8 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | on: 2 | # Those checks only run against code changes 3 | - push 4 | 5 | name: Continuous integration 6 | 7 | env: 8 | MSRV: 1.84.0 9 | CARGO_TERM_COLOR: always 10 | # Useful for cargo insta 11 | CI: true 12 | 13 | jobs: 14 | check: 15 | name: Check 16 | runs-on: ubuntu-24.04 17 | steps: 18 | - uses: actions/checkout@v3 19 | - uses: actions-rs/toolchain@v1 20 | with: 21 | profile: minimal 22 | toolchain: stable 23 | override: true 24 | - uses: actions-rs/cargo@v1 25 | with: 26 | command: check 27 | args: --locked 28 | 29 | test_features_matrix: 30 | name: Cargo test 31 | strategy: 32 | matrix: 33 | args: 34 | - "" 35 | - "--all-features" 36 | - "--features from-directory" 37 | runs-on: ubuntu-24.04 38 | steps: 39 | - uses: actions/checkout@v3 40 | - name: Install libsqlite 41 | run: sudo apt-get install -y libsqlite3-dev 42 | - uses: actions-rs/toolchain@v1 43 | with: 44 | profile: minimal 45 | toolchain: stable 46 | override: true 47 | - uses: actions-rs/cargo@v1 48 | with: 49 | command: test 50 | args: --locked ${{ matrix.args }} 51 | 52 | min_rust_version: 53 | name: Check with minimal Rust version 54 | runs-on: ubuntu-24.04 55 | steps: 56 | - uses: actions/checkout@v3 57 | - name: Install libsqlite 58 | run: sudo apt-get install -y libsqlite3-dev 59 | - uses: actions-rs/toolchain@v1 60 | with: 61 | profile: minimal 62 | toolchain: ${{ env.MSRV }} 63 | override: true 64 | - uses: actions-rs/cargo@v1 65 | with: 66 | command: test 67 | args: --locked 68 | 69 | doc: 70 | name: Documentation 71 | runs-on: ubuntu-24.04 72 | steps: 73 | - uses: actions/checkout@v3 74 | - uses: actions-rs/toolchain@v1 75 | with: 76 | profile: minimal 77 | toolchain: stable 78 | - run: RUSTDOCFLAGS="-D warnings" cargo --locked doc --all-features --no-deps --lib 79 | 80 | fmt: 81 | name: Rustfmt 82 | runs-on: ubuntu-24.04 83 | steps: 84 | - uses: actions/checkout@v3 85 | - uses: actions-rs/toolchain@v1 86 | with: 87 | profile: minimal 88 | toolchain: stable 89 | override: true 90 | - run: rustup component add rustfmt 91 | - uses: actions-rs/cargo@v1 92 | with: 93 | command: fmt 94 | args: --all -- --check 95 | 96 | mutation-tests: 97 | name: Mutation tests 98 | runs-on: ubuntu-24.04 99 | strategy: 100 | matrix: 101 | shard: [0, 1, 2, 3] 102 | steps: 103 | - uses: actions/checkout@v3 104 | - name: Install libsqlite 105 | run: sudo apt-get install -y libsqlite3-dev 106 | - uses: actions-rs/toolchain@v1 107 | with: 108 | profile: minimal 109 | toolchain: stable 110 | override: true 111 | - uses: Swatinem/rust-cache@v2 112 | - run: cargo install --locked cargo-mutants 113 | - uses: actions-rs/cargo@v1 114 | with: 115 | command: mutants 116 | args: -p rusqlite_migration --colors=always --no-shuffle -vV --shard ${{ matrix.shard }}/4 117 | - name: Archive results 118 | uses: actions/upload-artifact@v4 119 | if: failure() 120 | with: 121 | name: mutation-report 122 | path: mutants.out 123 | 124 | clippy_all: 125 | name: Clippy (all code) 126 | runs-on: ubuntu-24.04 127 | steps: 128 | - uses: actions/checkout@v3 129 | - uses: actions-rs/toolchain@v1 130 | with: 131 | profile: minimal 132 | toolchain: stable 133 | override: true 134 | - uses: Swatinem/rust-cache@v2 135 | - run: rustup component add clippy 136 | - uses: actions-rs/cargo@v1 137 | with: 138 | command: clippy 139 | args: --locked --all-features -- -D warnings 140 | 141 | clippy_main: 142 | name: Clippy (main package) 143 | runs-on: ubuntu-24.04 144 | steps: 145 | - uses: actions/checkout@v3 146 | - uses: actions-rs/toolchain@v1 147 | with: 148 | profile: minimal 149 | toolchain: stable 150 | override: true 151 | - uses: Swatinem/rust-cache@v2 152 | - run: rustup component add clippy 153 | - uses: actions-rs/cargo@v1 154 | with: 155 | command: clippy 156 | # More stringent requirements 157 | args: --locked --all-features -p rusqlite_migration -- -D warnings -D clippy::missing_errors_doc -D clippy::missing_panics_doc -D clippy::doc_link_with_quotes -D clippy::doc_markdown 158 | 159 | coverage: 160 | name: Code coverage 161 | runs-on: ubuntu-24.04 162 | steps: 163 | - uses: actions/checkout@v3 164 | - name: Install libsqlite 165 | run: sudo apt-get install -y libsqlite3-dev 166 | - uses: actions-rs/toolchain@v1 167 | with: 168 | profile: minimal 169 | toolchain: stable 170 | override: true 171 | - uses: Swatinem/rust-cache@v2 172 | - run: cargo install --locked cargo-tarpaulin 173 | - name: Generate code coverage 174 | uses: actions-rs/cargo@v1 175 | with: 176 | command: tarpaulin 177 | args: --locked --all-features --run-types AllTargets --workspace --timeout 120 --out Lcov --exclude-files '**/benches/*' 178 | - name: Upload coverage to Coveralls 179 | uses: coverallsapp/github-action@v1 180 | with: 181 | github-token: ${{ secrets.GITHUB_TOKEN }} 182 | path-to-lcov: lcov.info 183 | -------------------------------------------------------------------------------- /.github/workflows/security.yml: -------------------------------------------------------------------------------- 1 | name: Security audit 2 | on: 3 | schedule: 4 | - cron: '0 0 * * *' 5 | jobs: 6 | audit: 7 | runs-on: ubuntu-latest 8 | steps: 9 | - uses: actions/checkout@v2 10 | - uses: actions-rs/audit-check@v1 11 | with: 12 | token: ${{ secrets.GITHUB_TOKEN }} 13 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | mutants.out 2 | mutants.out.old 3 | 4 | **/target 5 | 6 | # Output of some examples 7 | my_db.db3 8 | my_db.wal 9 | my_db.shm 10 | 11 | # Local IDE & editor files 12 | .idea/ 13 | 14 | # Tarpaulin & code coverage artefacts 15 | lcov.info 16 | tarpaulin-report.html 17 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | 11 | 12 | 13 | # Changelog 14 | 15 | 16 | 19 | 20 | ## Version 2.2.0 Beta 1 21 | 22 | ### Features 23 | 24 | - Implement the `Display` trait for `M`. This makes it easier to print errors pertaining to a particular migration (this feature is planned for the future, in the context of more extensive migration checks) 25 | 26 | ### Dependencies 27 | 28 | Rusqlite was updated from 0.35.0 to 0.36.0. 29 | Please see [the release notes for 0.36.0](https://github.com/rusqlite/rusqlite/releases/tag/v0.36.0). 30 | 31 | ### Other 32 | 33 | - Update development dependencies 34 | - Improve tests to cover more cases, in particular around downward migrations 35 | - Add docs.rs link to Cargo metadata 36 | - Fix clippy warning in rust 1.87.0 37 | 38 | ## Version 2.1.0 39 | 40 | ### Dependencies 41 | 42 | Rusqlite was updated from 0.34.0 to 0.34.0. 43 | Please see [the release notes for 0.35.0](https://github.com/rusqlite/rusqlite/releases/tag/v0.35.0). 44 | 45 | ## Version 2.0.0 46 | 47 | ### Breaking changes 48 | 49 | #### Remove the `alpha-async-tokio-rusqlite` Feature 50 | 51 | As the name of the feature suggest, we have had experimental support for async using tokio for a while now. Supporting that feature has been quite a big burden, introducing some duplicated code in the `AsyncMigrations` struct in particular, as well as a whole set of very similar tests. Plus the benefit of async is limited here, because everything gets executed in a blocking fashion in sqlite anyway. 52 | 53 | It turns out that we don’t need the async support in rusqlite_migration for folks to use async libraries. For instance, with tokio-rusqlite, you can define migrations like in the sync context and run: 54 | ```rust 55 | async_conn 56 | .call_unwrap(|conn| MIGRATIONS.to_latest(conn)) 57 | .await?; 58 | ``` 59 | 60 | See [the updated async example](https://github.com/cljoly/rusqlite_migration/blob/master/examples/async/src/main.rs) for details, in particular why it’s fine to call [a method](https://docs.rs/tokio-rusqlite/0.6.0/tokio_rusqlite/struct.Connection.html#method.call_unwrap) with unwrap in its name. 61 | 62 | #### Make the Builder `Finalizer` Method Not Generic 63 | 64 | On a related note, now that we have removed the `AsyncMigrations` (see the section right above) struct, we only have `Migrations` so there is no need for the `MigrationsBuilder.finalize` method to be generic. Thus we removed the generic argument. To update your code, you can just do this: 65 | ```diff 66 | - .finalize::()); 67 | + .finalize()); 68 | ``` 69 | #### Remove `Migrations::new_iter` 70 | 71 | This function has been deprecated for a while now, remove it as a part of the major version bump. You can use the standard `FromIter` trait implementation instead. 72 | 73 | ### Behavior Change 74 | 75 | * When the [user version field](https://www.sqlite.org/fileformat.html#user_version_number) is altered by other code in your application, we are now returning an explicit error (`Error::InvalidUserVersion`) when this can be detected. Previously, the library would silently misbehave. 76 | 77 | ### Features 78 | 79 | - Add the new [`Migrations::from_slice`](https://docs.rs/rusqlite_migration/2.0.0-beta.1/rusqlite_migration/struct.Migrations.html#method.from_slice) constructor, which is `const` and takes a slice, so that it can be constructed in global constant, without using `LazyLock` or similar. Internally, this is possible because we now use a [`Cow`](https://doc.rust-lang.org/std/borrow/enum.Cow.html) structure to hold migrations. 80 | - Add [`Migrations::pending_migrations`](https://docs.rs/rusqlite_migration/2.0.0-beta.1/rusqlite_migration/struct.Migrations.html#method.pending_migrations) which returns the number of migrations that would be applied. This is mostly useful to take a backup of the database prior to applying migrations (and do nothing if no migrations will be applied). 81 | 82 | 83 | ### Dependencies 84 | 85 | Rusqlite was updated from 0.32.1 to 0.34.0. 86 | Please see [the release notes for 0.34.0](https://github.com/rusqlite/rusqlite/releases/tag/v0.34.0) and 87 | [the release notes for 0.33.0](https://github.com/rusqlite/rusqlite/releases/tag/v0.33.0). 88 | Tokio Rusqlite was removed as a dependency. 89 | 90 | ### Minimum Rust Version 91 | 92 | Rust 1.84. 93 | 94 | Moving forward, we expect to keep this aligned with rusqlite itself, now that it has a [policy](https://github.com/rusqlite/rusqlite?tab=readme-ov-file#minimum-supported-rust-version-msrv) (introduced in [october 2024](https://github.com/rusqlite/rusqlite/pull/1576)). 95 | 96 | ## Version 2.0.0 Beta 1 97 | 98 | ### Features 99 | 100 | - Add the new [`Migrations::from_slice`](https://docs.rs/rusqlite_migration/2.0.0-beta.1/rusqlite_migration/struct.Migrations.html#method.from_slice) constructor, which is `const` and takes a slice, so that it can be constructed in global constant, without using `LazyLock` or similar. Internally, this is possible because we now use a [`Cow`](https://doc.rust-lang.org/std/borrow/enum.Cow.html) structure to hold migrations. 101 | - Add [`Migrations::pending_migrations`](https://docs.rs/rusqlite_migration/2.0.0-beta.1/rusqlite_migration/struct.Migrations.html#method.pending_migrations) which returns the number of migrations that would be applied. This is mostly useful to take a backup of the database prior to applying migrations (and do nothing if no migrations will be applied). 102 | 103 | ## Version 2.0.0 Alpha 1 104 | 105 | ### Breaking changes 106 | 107 | #### Remove the `alpha-async-tokio-rusqlite` Feature 108 | 109 | As the name of the feature suggest, we have had experimental support for async using tokio for a while now. Supporting that feature has been quite a big burden, introducing some duplicated code in the `AsyncMigrations` struct in particular, as well as a whole set of very similar tests. Plus the benefit of async is limited here, because everything gets executed in a blocking fashion in sqlite anyway. 110 | 111 | It turns out that we don’t need the async support in rusqlite_migration for folks to use async libraries. For instance, with tokio-rusqlite, you can define migrations like in the sync context and run: 112 | ```rust 113 | async_conn 114 | .call_unwrap(|conn| MIGRATIONS.to_latest(conn)) 115 | .await?; 116 | ``` 117 | 118 | See [the updated async example](https://github.com/cljoly/rusqlite_migration/blob/master/examples/async/src/main.rs) for details, in particular why it’s fine to call [a method](https://docs.rs/tokio-rusqlite/0.6.0/tokio_rusqlite/struct.Connection.html#method.call_unwrap) with unwrap in its name. 119 | 120 | #### Make the Builder `Finalizer` Method Not Generic 121 | 122 | On a related note, now that we have removed the `AsyncMigrations` (see the section right above) struct, we only have `Migrations` so there is no need for the `MigrationsBuilder.finalize` method to be generic. Thus we removed the generic argument. To update your code, you can just do this: 123 | ```diff 124 | - .finalize::()); 125 | + .finalize()); 126 | ``` 127 | #### Remove `Migrations::new_iter` 128 | 129 | This function has been deprecated for a while now, remove it as a part of the major version bump. You can use the standard `FromIter` trait implementation instead. 130 | 131 | ### Behavior Change 132 | 133 | * When the [user version field](https://www.sqlite.org/fileformat.html#user_version_number) is altered by other code in your application, we are now returning an explicit error (`Error::InvalidUserVersion`) when this can be detected. Previously, the library would silently misbehave. 134 | 135 | ### Dependencies 136 | 137 | Rusqlite was updated from 0.32.1 to 0.34.0. 138 | Please see [the release notes for 0.34.0](https://github.com/rusqlite/rusqlite/releases/tag/v0.34.0) and 139 | [the release notes for 0.33.0](https://github.com/rusqlite/rusqlite/releases/tag/v0.33.0). 140 | Tokio Rusqlite was removed as a dependency. 141 | 142 | ### Features 143 | 144 | - `Migrations::new` is now `const` 145 | 146 | ### Minimum Rust Version 147 | 148 | Rust 1.84. 149 | 150 | Moving forward, we expect to keep this aligned with rusqlite itself, now that it has a [policy](https://github.com/rusqlite/rusqlite?tab=readme-ov-file#minimum-supported-rust-version-msrv) (introduced in [october 2024](https://github.com/rusqlite/rusqlite/pull/1576)). 151 | 152 | ## Version 1.3.1 153 | 154 | The only change is a fix to the deps.rs badge in the documentation. 155 | 156 | ## Version 1.3.0 157 | 158 | The code of this version is identical to [Version 1.3.0 Beta 1](#version-130-beta-1) 159 | 160 | Rusqlite was updated from 0.31.0 to 0.32.1. 161 | Please see [the release notes for 0.32.0](https://github.com/rusqlite/rusqlite/releases/tag/v0.32.0) and 162 | [for 0.32.1](https://github.com/rusqlite/rusqlite/releases/tag/v0.32.1). 163 | Tokio Rusqlite was updated from 0.5.1 to 0.6.0. 164 | Please see the [release notes](https://github.com/programatik29/tokio-rusqlite/releases/tag/v0.6.0). 165 | 166 | ### Minimum Rust Version 167 | 168 | Rust 1.77 169 | 170 | ### Documentation 171 | 172 | Various documentation improvements and clarification. In particular, call out that if a rusqlite error is encountered during a migration, the next migrations in the list are not applied. 173 | 174 | ### Other 175 | 176 | - Apply minor or patch updates to the dependencies 177 | - Update development dependencies 178 | - Make CI testing more reproducible by forcing the use of Cargo.lock 179 | 180 | ## Version 1.3.0 Beta 1 181 | 182 | This reintroduces the async features temporarily removed from [Version 1.3.0 Alpha-Without-Tokio 1](#version-130-alpha-without-tokio-1) 183 | 184 | Rusqlite was updated from 0.31.0 to 0.32.1. 185 | Please see [the release notes for 0.32.0](https://github.com/rusqlite/rusqlite/releases/tag/v0.32.0) and 186 | [for 0.32.1](https://github.com/rusqlite/rusqlite/releases/tag/v0.32.1). 187 | Tokio Rusqlite was updated from 0.5.1 to 0.6.0. 188 | Please see the [release notes](https://github.com/programatik29/tokio-rusqlite/releases/tag/v0.6.0). 189 | 190 | ### Minimum Rust Version 191 | 192 | Rust 1.77 193 | 194 | ### Documentation 195 | 196 | Various documentation improvements and clarification. In particular, call out that if a rusqlite error is encountered during a migration, the next migrations in the list are not applied. 197 | 198 | ### Other 199 | 200 | - Apply minor or patch updates to the dependencies 201 | - Update development dependencies 202 | - Make CI testing more reproducible by forcing the use of Cargo.lock 203 | 204 | 205 | ## Version 1.3.0 Alpha-Without-Tokio 1 206 | 207 | ### Major Changes 208 | 209 | This is an alpha version to start integrating rusqlite 0.32.1. Unfortunately, at this time, tokio-rusqlite is did not update to rusqlite 0.32.1. So we are temporarily removing the async features, while we figure out a way to bring them back. **To be clear, we intend to support the async features going forward, this is a temporary change in a specifically tagged version**. 210 | 211 | Rusqlite was updated from 0.31.0 to 0.32.1. Please see [the release notes for 0.32.0](https://github.com/rusqlite/rusqlite/releases/tag/v0.32.0) and 212 | [for 0.32.1](https://github.com/rusqlite/rusqlite/releases/tag/v0.32.1) 213 | 214 | ### Minimum Rust Version 215 | 216 | Rust 1.77 217 | 218 | ### Documentation 219 | 220 | Various documentation improvements and clarification. In particular, call out that if a rusqlite error is encountered during a migration, the next migrations in the list are not applied. 221 | 222 | ### Other 223 | 224 | - Apply minor or patch updates to the dependencies 225 | - Update development dependencies 226 | - Make CI testing more reproducible by forcing the use of Cargo.lock 227 | 228 | ## Version 1.2.0 229 | 230 | *Same code as version 1.2.0-beta.1* 231 | 232 | ### Documentation 233 | 234 | - Improved the badges a little bit 235 | 236 | ## Version 1.2.0 Beta 1 237 | 238 | Small release, mainly to update dependencies. 239 | 240 | ### Minimum Rust Version 241 | 242 | Now using edition 2021, but the minimum rust version is still 1.70 243 | 244 | ### New Features 245 | 246 | No new features. 247 | 248 | ### Other 249 | 250 | - Update rusqlite to 0.31 251 | - Update various development dependencies 252 | - Improve CI build time 253 | - Impove documentation 254 | - Fix some broken examples 255 | 256 | ### See also 257 | 258 | Rusqlite was updated from 0.30 to 0.31. Please see [its release notes](https://github.com/rusqlite/rusqlite/releases/tag/v0.31.0) 259 | 260 | 261 | ## Version 1.1.0 262 | 263 | *Same code as version 1.1.0-beta.1* 264 | 265 | ### Minimum Rust Version 266 | 267 | Rust 1.70 268 | 269 | ### New Features 270 | 271 | * Support for tokio-rusqlite behind the feature named `alpha-async-tokio-rusqlite`thanks to [@czocher](https://github.com/czocher). See [the example](https://github.com/cljoly/rusqlite_migration/tree/c54951d22691432fbfd511cc68f1c5b8a2306737/examples/async). This feature is alpha, meaning that compatibility in future minor versions is not guaranteed. 272 | * Create migrations from directories holding SQL files thanks to [@czocher](https://github.com/czocher). See [the example](https://github.com/cljoly/rusqlite_migration/tree/af4da527ff75e3b8c089d2300cab7fbe66096411/examples/from-directory). 273 | * Add up/down hooks to run custom Rust code during migrations ([PR](https://github.com/cljoly/rusqlite_migration/pull/28) thanks to [@matze](https://github.com/matze)) 274 | * Add foreign_key_check method to migrations ([PR](https://github.com/cljoly/rusqlite_migration/pull/20) thanks to [@Jokler](https://github.com/Jokler)) 275 | * Make `Migration` functions const ([PR](https://github.com/cljoly/rusqlite_migration/pull/19) thanks to [@fkaa](https://github.com/fkaa)) 276 | * Make `Migrations` serializable (using the Debug serializer) with [insta](https://insta.rs). 277 | 278 | ### Depreciation 279 | 280 | * Mark `Migrations::from_iter` as deprecated 281 | 282 | ### Other 283 | 284 | * Documentation improvements 285 | * Repository metadata improvements 286 | * Code quality improvements 287 | * Introduce cargo mutants & fix bugs found 288 | * Clippy warning fixes and other linter improvements 289 | * Report on test coverage & improve test coverage 290 | * Add benchmarks 291 | * Made errors returned more precise 292 | * Updated dependencies 293 | 294 | ### See also 295 | 296 | Rusqlite was updated from 0.29.0 to 0.30.0. Please see [its release notes](https://github.com/rusqlite/rusqlite/releases/tag/v0.30.0) 297 | 298 | ## Version 1.1.0 Beta 1 299 | 300 | **⚠️ The APIs exposed in this version may be unstable.** 301 | 302 | Summing up all the changes from the previous Alpha versions. 303 | 304 | ### Minimum Rust Version 305 | 306 | Rust 1.70 307 | 308 | ### New Features 309 | 310 | * Support for tokio-rusqlite behind the feature named `alpha-async-tokio-rusqlite`thanks to [@czocher](https://github.com/czocher). See [the example](https://github.com/cljoly/rusqlite_migration/tree/c54951d22691432fbfd511cc68f1c5b8a2306737/examples/async). This feature is alpha, meaning that compatibility in future minor versions is not guaranteed. 311 | * Create migrations from directories holding SQL files thanks to [@czocher](https://github.com/czocher). See [the example](https://github.com/cljoly/rusqlite_migration/tree/af4da527ff75e3b8c089d2300cab7fbe66096411/examples/from-directory). 312 | * Add up/down hooks to run custom Rust code during migrations ([PR](https://github.com/cljoly/rusqlite_migration/pull/28) thanks to [@matze](https://github.com/matze)) 313 | * Add foreign_key_check method to migrations ([PR](https://github.com/cljoly/rusqlite_migration/pull/20) thanks to [@Jokler](https://github.com/Jokler)) 314 | * Make `Migration` functions const ([PR](https://github.com/cljoly/rusqlite_migration/pull/19) thanks to [@fkaa](https://github.com/fkaa)) 315 | * Make `Migrations` serializable (using the Debug serializer) with [insta](https://insta.rs). 316 | 317 | ### Depreciation 318 | 319 | * Mark `Migrations::from_iter` as deprecated 320 | 321 | ### Other 322 | 323 | * Documentation improvements 324 | * Repository metadata improvements 325 | * Code quality improvements 326 | * Introduce cargo mutants & fix bugs found 327 | * Clippy warning fixes and other linter improvements 328 | * Report on test coverage & improve test coverage 329 | * Add benchmarks 330 | * Made errors returned more precise 331 | * Updated dependencies 332 | 333 | ### See also 334 | 335 | Rusqlite was updated from 0.29.0 to 0.30.0. Please see [its release notes](https://github.com/rusqlite/rusqlite/releases/tag/v0.30.0) 336 | 337 | ## Version 1.1.0 Alpha 2 338 | 339 | **⚠️ The APIs exposed in this version may be unstable.** 340 | 341 | ### Minimum Rust Version 342 | 343 | Rust 1.64 344 | 345 | ### New Features 346 | 347 | * Create migrations from directories holding SQL files. See [the example](https://github.com/cljoly/rusqlite_migration/tree/af4da527ff75e3b8c089d2300cab7fbe66096411/examples/from-directory). 348 | 349 | ### Depreciation 350 | 351 | * Mark `Migrations::from_iter` as deprecated 352 | 353 | ### Other 354 | 355 | * Documentation improvements 356 | * Code quality improvements 357 | * Introduce cargo mutants & fix bugs found 358 | * Clippy warning fixes 359 | * Report on test coverage & improve test coverage 360 | * Add benchmarks 361 | * Made errors returned more precise 362 | * Update dependencies 363 | 364 | ## Version 1.1.0 Alpha 1 365 | 366 | **⚠️ The APIs exposed in this version may be unstable.** 367 | 368 | ### Minimum Rust Version 369 | 370 | Rust 1.61 371 | 372 | ### New Features 373 | 374 | * Add up/down hooks to run custom Rust code during migrations ([PR](https://github.com/cljoly/rusqlite_migration/pull/28) thanks to [@matze](https://github.com/matze)) 375 | * The purpose of this release is to get feedback on the new API. Please feel free to comment on [this discussion](https://github.com/cljoly/rusqlite_migration/discussions/36)! 376 | * Add foreign_key_check method to migrations ([PR](https://github.com/cljoly/rusqlite_migration/pull/20) thanks to [@Jokler](https://github.com/Jokler)) 377 | * Please beware of the [follow up work needed on this](https://github.com/cljoly/rusqlite_migration/issues/4#issuecomment-1166363260) 378 | * Make `Migration` functions const ([PR](https://github.com/cljoly/rusqlite_migration/pull/19) thanks to [@fkaa](https://github.com/fkaa)) 379 | 380 | ### Other 381 | 382 | * CI improvements 383 | * Linter improvements 384 | * Repository metadata improvements 385 | * Documentation improvements 386 | * Dev dependencies update (not dependencies of the library when used in another crate) 387 | 388 | ## Version 1.0.2 389 | 390 | ### Bug fix 391 | 392 | * fix: adapt to rusqlite 0.29 and tighten dependency requirements for rusqlite (see [this discussion](https://github.com/cljoly/rusqlite_migration/issues/68#issuecomment-1485795284)) 393 | 394 | ## Version 1.0.1 395 | 396 | ### Bug Fix 397 | 398 | * fix: error instead of panicking on higher migration level (see commit ad57d92d1677420eb81c4e25635be1884f9b7ce7) 399 | 400 | ### Other 401 | 402 | * Documentation improvements 403 | 404 | ## Version 1.0.0 405 | 406 | ### Breaking changes 407 | 408 | * Remove deprecated symbols (`Migrations.latest`, `SchemaVersionError::MigrateToLowerNotSupported`) 409 | 410 | ### Other 411 | 412 | * Documentation improvements 413 | 414 | ## Version 0.5.1 415 | 416 | ### Potentially Breaking Changes 417 | - Update the `rusqlite` crate (to protect agaisnt [RUSTSEC-2020-0014](https://rustsec.org/advisories/RUSTSEC-2020-0014.html)) 418 | 419 | ### Other 420 | - Improve the documentation 421 | 422 | ## Version 0.5.0 423 | 424 | - Update the `env_logger` dependency 425 | - Improve the documentation 426 | 427 | ## Version 0.4.1 / 0.4.2 428 | 429 | - Update documentation 430 | 431 | ## Version 0.4.0 432 | 433 | ### New features 434 | 435 | - Add downward migrations, i.e. migrations to go to past schema version of the database. Thanks @MightyPork! 436 | - Unsafe code is now forbidden. 437 | 438 | ### Breaking changes 439 | 440 | - Rename `latest` to `to_latest`. The old symbol is deprecated and will be removed eventually. 441 | - An error is now returned when a migration is attempted while no migrations exist. 442 | 443 | ### Other 444 | 445 | - Improve general rust API documentation. 446 | - Generate parts of the readme based on rust comments, for increased consistency with the docs.rs content. 447 | - Various refactoring and clean-ups. 448 | 449 | ## Version 0.3.1 450 | 451 | Fix in readme, for crates.io 452 | 453 | ## Version 0.3 454 | 455 | ### New features 456 | 457 | - Multi line sql statements like: 458 | ``` 459 | M::up(r#" 460 | CREATE TABLE t1(a, b); 461 | CREATE TABLE t2(a, b); 462 | "#) 463 | ``` 464 | are now fully supported 465 | 466 | ### Other 467 | 468 | - Various doc & CI improvements 469 | - Fix a case of failure with silent errors. 470 | 471 | 472 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace.package] 2 | authors = ["Clément Joly "] 3 | categories = ["database"] 4 | description = "Simple schema migration library for rusqlite using user_version instead of an SQL table to maintain the current schema version." 5 | homepage = "https://cj.rs/rusqlite_migration" 6 | keywords = ["rusqlite", "sqlite", "user_version", "database", "migration"] 7 | license = "Apache-2.0" 8 | repository = "https://github.com/cljoly/rusqlite_migration" 9 | documentation = "https://docs.rs/rusqlite_migration/" 10 | rust-version = "1.84" 11 | version = "2.2.0-beta.1" 12 | 13 | [workspace] 14 | members = [ 15 | "rusqlite_migration", 16 | "rusqlite_migration_tests", 17 | "examples/*", 18 | ] 19 | # https://doc.rust-lang.org/cargo/reference/resolver.html#feature-resolver-version-2 20 | resolver = "2" 21 | 22 | [workspace.lints.rust] 23 | unsafe_code = "forbid" 24 | missing_docs = "warn" 25 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | APPENDIX: How to apply the Apache License to your work. 180 | 181 | To apply the Apache License to your work, attach the following 182 | boilerplate notice, with the fields enclosed by brackets "[]" 183 | replaced with your own identifying information. (Don't include 184 | the brackets!) The text should be enclosed in the appropriate 185 | comment syntax for the file format. We also recommend that a 186 | file or class name and description of purpose be included on the 187 | same "printed page" as the copyright notice for easier 188 | identification within third-party archives. 189 | 190 | Copyright [yyyy] [name of copyright owner] 191 | 192 | Licensed under the Apache License, Version 2.0 (the "License"); 193 | you may not use this file except in compliance with the License. 194 | You may obtain a copy of the License at 195 | 196 | http://www.apache.org/licenses/LICENSE-2.0 197 | 198 | Unless required by applicable law or agreed to in writing, software 199 | distributed under the License is distributed on an "AS IS" BASIS, 200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 201 | See the License for the specific language governing permissions and 202 | limitations under the License. 203 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 14 | 15 | 16 |
17 | 18 | # Rusqlite Migration 19 | 20 | 21 | 22 | 23 | 30 | 31 | [![docs.rs](https://img.shields.io/docsrs/rusqlite_migration)][docs] 32 | [![Crates.io](https://img.shields.io/crates/v/rusqlite_migration)][cio] 33 | [![Changelog](https://img.shields.io/badge/-Changelog-purple)][changelog] 34 | [![unsafe forbidden](https://img.shields.io/badge/unsafe-forbidden-success.svg)][safety-dance] 35 | [![dependency status](https://deps.rs/crate/rusqlite_migration/latest/status.svg)][deps] 36 | [![Coveralls](https://img.shields.io/coverallsCoverage/github/cljoly/rusqlite_migration)][coveralls] 37 | 38 | 41 |
42 | 45 | 46 | Rusqlite Migration is a performant and simple schema migration library for [rusqlite](https://crates.io/crates/rusqlite). 47 | 48 | * **Performance**: 49 | * *Fast database opening*: to keep track of the current migration state, most tools create one or more tables in the database. These tables require parsing by SQLite and are queried with SQL statements. This library uses the [`user_version`][uv] value instead. It’s much lighter as it is just an integer at a [fixed offset][uv_offset] in the SQLite file. 50 | * *Fast compilation*: this crate is very small and does not use macros to define the migrations. 51 | * **Simplicity**: this crate strives for simplicity. Just define a set of SQL statements as strings in your Rust code. Add more SQL statements over time as needed. No external CLI required. Additionally, rusqlite_migration works especially well with other small libraries complementing rusqlite, like [serde_rusqlite][]. 52 | 53 | ## Example 54 | 55 | Here, we define SQL statements to run with [`Migrations::new()`][migrations_new] and run these (if necessary) with [`Migrations::to_latest()`][migrations_to_latest]. 56 | 57 | [migrations_new]: https://docs.rs/rusqlite_migration/latest/rusqlite_migration/struct.Migrations.html#method.new 58 | [migrations_to_latest]: https://docs.rs/rusqlite_migration/latest/rusqlite_migration/struct.Migrations.html#method.to_latest 59 | 60 | ``` rust 61 | use rusqlite::{params, Connection}; 62 | use rusqlite_migration::{Migrations, M}; 63 | 64 | // 1️⃣ Define migrations 65 | const MIGRATION_SLICE: &[M<'_>] = &[ 66 | M::up("CREATE TABLE friend(name TEXT NOT NULL);"), 67 | // In the future, add more migrations here: 68 | //M::up("ALTER TABLE friend ADD COLUMN email TEXT;"), 69 | ]; 70 | const MIGRATIONS: Migrations<'_> = Migrations::from_slice(MIGRATION_SLICE); 71 | 72 | fn main() { 73 | let mut conn = Connection::open_in_memory().unwrap(); 74 | 75 | // Apply some PRAGMA, often better to do it outside of migrations 76 | conn.pragma_update_and_check(None, "journal_mode", &"WAL", |_| Ok(())) 77 | .unwrap(); 78 | 79 | // 2️⃣ Update the database schema, atomically 80 | MIGRATIONS.to_latest(&mut conn).unwrap(); 81 | 82 | // 3️⃣ Use the database 🥳 83 | conn.execute("INSERT INTO friend (name) VALUES (?1)", params!["John"]) 84 | .unwrap(); 85 | } 86 | ``` 87 | 88 | Please see the [examples](https://github.com/cljoly/rusqlite_migrate/tree/master/examples) folder for more, in particular: 89 | - migrations with multiple SQL statements (using for instance `r#"…"` or `include_str!(…)`) 90 | - migrations defined [from a directory][from_dir] with SQL files 91 | - migrations to [previous versions (downward migrations)][generic_example] 92 | - migrations [when using `async`][quick_start_async] 93 | 94 | I’ve also made a [cheatsheet of SQLite pragma for improved performance and consistency][cheat]. 95 | 96 | ### Built-in tests 97 | 98 | To test that the migrations are working, you can add this in your test module: 99 | 100 | ``` rust 101 | #[test] 102 | fn migrations_test() { 103 | assert!(MIGRATIONS.validate().is_ok()); 104 | } 105 | ``` 106 | 107 | The migrations object is also suitable for serialisation with [insta][], using the `Debug` serialisation. You can store a snapshot of your migrations like this: 108 | 109 | ```rust 110 | #[test] 111 | fn migrations_insta_snapshot() { 112 | let migrations = Migrations::new(vec![ 113 | // ... 114 | ]); 115 | insta::assert_debug_snapshot!(migrations); 116 | } 117 | ``` 118 | 119 | [insta]: https://insta.rs/ 120 | 121 | ## Optional Features 122 | 123 | Rusqlite_migration provides several [Cargo features][cargo_features]. They are: 124 | 125 | * `from-directory`: enable loading migrations from *.sql files in a given directory 126 | 127 | [cargo_features]: https://doc.rust-lang.org/cargo/reference/manifest.html#the-features-section 128 | 129 | ## Active Users 130 | 131 | 136 | 137 | [![Crates.io Downloads](https://img.shields.io/crates/d/rusqlite_migration?style=social)][cio] [![Crates.io Downloads (recent)](https://img.shields.io/crates/dr/rusqlite_migration?style=social)][cio] 138 | 139 | 144 | 145 | This crate is actively used in a number of projects. You can find up-to-date list of those on: 146 | 147 | * [crates.io][cio_reverse] / [lib.rs][lrs_reverse] 148 | * [GitHub’s list of dependent repositories][gh_reverse] 149 | 150 | A number of contributors are also reporting issues as they arise, another indicator of active use. 151 | 152 | ## Minimum Supported Rust Version (MSRV) 153 | 154 | This crate extends rusqlite and as such is tightly integrated with it. Thus, it supports the [same MSRV][msrv] as rusqlite. At the time of writing, this means: 155 | 156 | > Latest stable Rust version at the time of release. It might compile with older versions. 157 | 158 | ## Limits 159 | 160 | 1. Since this crate uses the [`user_version`][uv_offset] field, if your program or any other library changes it, this library will behave in an unspecified way: it may return an error, apply the wrong set of migrations, do nothing at all... 161 | 162 | 1. The [`user_version`][uv_offset] field is effectively a i32, so there is a theoretical limit (about two billion) on the number of migrations that can be applied by this library. You are likely to hit memory limits well before that though, so in practice, you can think of the number of migrations as limitless. And you would need to create 10 000 new migrations, every day, for over 5 centuries, before getting close to the limit. 163 | 164 | ## Contributing 165 | 166 | Contributions (documentation or code improvements in particular) are welcome, see [contributing][]! 167 | 168 | We use various tools for testing that you may find helpful to install locally (e.g. to fix failing CI checks): 169 | * [cargo-insta][] 170 | * [cargo-mutants][] 171 | 172 | ## Acknowledgments 173 | 174 | I would like to thank all the contributors, as well as the authors of the dependencies this crate uses. 175 | 176 | Thanks to [Migadu](https://www.migadu.com/) for offering a discounted service to support this project. It is not an endorsement by Migadu though. 177 | 178 | [deps]: https://deps.rs/crate/rusqlite_migration 179 | [changelog]: https://cj.rs/rusqlite_migration/changelog 180 | [coveralls]: https://coveralls.io/github/cljoly/rusqlite_migration 181 | [safety-dance]: https://github.com/rust-secure-code/safety-dance/ 182 | [cio]: https://crates.io/crates/rusqlite_migration 183 | [cio_reverse]: https://crates.io/crates/rusqlite_migration/reverse_dependencies 184 | [lrs_reverse]: https://lib.rs/crates/rusqlite_migration/rev 185 | [gh_reverse]: https://github.com/cljoly/rusqlite_migration/network/dependents?dependent_type=REPOSITORY 186 | [contributing]: https://cj.rs/docs/contribute/ 187 | [diesel_migrations]: https://crates.io/crates/diesel_migrations 188 | [pgfine]: https://crates.io/crates/pgfine 189 | [movine]: https://crates.io/crates/movine 190 | [uv]: https://sqlite.org/pragma.html#pragma_user_version 191 | [uv_offset]: https://www.sqlite.org/fileformat.html#user_version_number 192 | [serde_rusqlite]: https://crates.io/crates/serde_rusqlite 193 | [cargo-insta]: https://crates.io/crates/cargo-insta 194 | [cargo-mutants]: https://mutants.rs/installation.html 195 | [cheat]: https://cj.rs/blog/sqlite-pragma-cheatsheet-for-performance-and-consistency/ 196 | [docs]: https://docs.rs/rusqlite_migration 197 | [msrv]: https://github.com/rusqlite/rusqlite?tab=readme-ov-file#minimum-supported-rust-version-msrv 198 | [from_dir]: https://github.com/cljoly/rusqlite_migration/tree/master/examples/from-directory 199 | [generic_example]: https://github.com/cljoly/rusqlite_migration/blob/master/examples/simple/src/main.rs 200 | [quick_start_async]: https://github.com/cljoly/rusqlite_migration/blob/master/examples/async/src/main.rs 201 | -------------------------------------------------------------------------------- /clippy.toml: -------------------------------------------------------------------------------- 1 | doc-valid-idents = ["SQLite", "rusqlite_migration", "serde_rusqlite", ".."] 2 | -------------------------------------------------------------------------------- /examples/async/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "example-async" 3 | version = "0.1.0" 4 | edition = "2018" 5 | publish = false 6 | 7 | [dependencies] 8 | log = "0.4" 9 | simple-logging = "2.0.2" 10 | env_logger = "0.11" 11 | anyhow = "1" 12 | mktemp = "0.5" 13 | tokio-rusqlite-new = "=0.10.0" 14 | tokio = { version = "1.45.1", features = ["full"] } 15 | 16 | [dependencies.rusqlite_migration] 17 | path = "../../rusqlite_migration" 18 | 19 | [dependencies.rusqlite] 20 | version = "0.36.0" 21 | default-features = false 22 | features = [] 23 | -------------------------------------------------------------------------------- /examples/async/src/main.rs: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | // Copyright Clément Joly and contributors. 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | 16 | use anyhow::Result; 17 | use rusqlite::params; 18 | use rusqlite_migration::{Migrations, M}; 19 | use tokio_rusqlite_new::Connection; 20 | 21 | /// The general idea with this example is to use [`Connection::call`][call] and 22 | /// [`Connection::call_unwrap`][call_unwrap] to run the migration in a sync context. 23 | /// 24 | /// [call]: https://docs.rs/tokio-rusqlite/0.6.0/tokio_rusqlite/struct.Connection.html#method.call 25 | /// [call_unwrap]: https://docs.rs/tokio-rusqlite/0.6.0/tokio_rusqlite/struct.Connection.html#method.call_unwrap 26 | 27 | // Test that migrations are working 28 | #[cfg(test)] 29 | mod tests { 30 | use super::*; 31 | 32 | // Validating that migrations are correctly defined. It is enough to test in the sync context, 33 | // because under the hood, tokio_rusqlite executes the migrations in a sync context anyway. 34 | #[test] 35 | fn migrations_test() { 36 | assert!(MIGRATIONS.validate().is_ok()); 37 | } 38 | } 39 | 40 | // Define migrations. These are applied atomically. 41 | const MIGRATION_ARRAY: &[M] = &[ 42 | M::up(include_str!("../../friend_car.sql")), 43 | // PRAGMA are better applied outside of migrations, see below for details. 44 | M::up( 45 | r#" 46 | ALTER TABLE friend ADD COLUMN birthday TEXT; 47 | ALTER TABLE friend ADD COLUMN comment TEXT; 48 | "#, 49 | ), 50 | // This migration can be reverted 51 | M::up("CREATE TABLE animal(name TEXT);").down("DROP TABLE animal;"), 52 | // In the future, if the need to change the schema arises, put 53 | // migrations here, like so: 54 | // M::up("CREATE INDEX UX_friend_email ON friend(email);"), 55 | // M::up("CREATE INDEX UX_friend_name ON friend(name);"), 56 | ]; 57 | const MIGRATIONS: Migrations = Migrations::from_slice(MIGRATION_ARRAY); 58 | 59 | pub async fn init_db() -> Result { 60 | let async_conn = Connection::open("./my_db.db3").await?; 61 | 62 | // Update the database schema, atomically 63 | // Using `call_unwrap` is appropriate because we have just opened the connection before and 64 | // this function still returns any error returned inside of it. In other words, according to 65 | // the [documentation][docs], it only panics if there is an issue with the connection, not if 66 | // an error is returned by the closure. 67 | // 68 | // [docs]: https://docs.rs/tokio-rusqlite/0.6.0/tokio_rusqlite/struct.Connection.html#method.call_unwrap 69 | async_conn 70 | .call_unwrap(|conn| MIGRATIONS.to_latest(conn)) 71 | .await?; 72 | 73 | Ok(async_conn) 74 | } 75 | 76 | #[tokio::main] 77 | async fn main() -> Result<()> { 78 | env_logger::Builder::from_env(env_logger::Env::default().default_filter_or("trace")).init(); 79 | 80 | let async_conn = init_db().await.unwrap(); 81 | 82 | // Apply some PRAGMA. These are often better applied outside of migrations, as some needs to be 83 | // executed for each connection (like `foreign_keys`) or to be executed outside transactions 84 | // (`journal_mode` is a noop in a transaction). 85 | async_conn 86 | .call::<_, _, rusqlite::Error>(|conn| Ok(conn.pragma_update(None, "journal_mode", "WAL"))) 87 | .await 88 | .unwrap() 89 | .unwrap(); 90 | async_conn 91 | .call::<_, _, rusqlite::Error>(|conn| Ok(conn.pragma_update(None, "foreign_keys", "ON"))) 92 | .await 93 | .unwrap() 94 | .unwrap(); 95 | 96 | // Use the db 🥳 97 | async_conn 98 | .call::<_, _, rusqlite::Error>(|conn| { 99 | Ok(conn.execute( 100 | "INSERT INTO friend (name, birthday) VALUES (?1, ?2)", 101 | params!["John", "1970-01-01"], 102 | )) 103 | }) 104 | .await 105 | .unwrap() 106 | .unwrap(); 107 | 108 | async_conn 109 | .call::<_, _, rusqlite::Error>(|conn| { 110 | Ok(conn.execute("INSERT INTO animal (name) VALUES (?1)", params!["dog"])) 111 | }) 112 | .await 113 | .unwrap() 114 | .unwrap(); 115 | 116 | // We can revert to the last migration 117 | // Let’s also demonstrate using `Connection::call` instead of `Connection::call_unwrap`. 118 | // Notice how in effect, we have handle two `Result`s, one for the errors happening when we try 119 | // to use the connection and the other one when applying the migration proper. 120 | async_conn 121 | .call::<_, _, rusqlite::Error>(|conn| Ok(MIGRATIONS.to_version(conn, 2))) 122 | .await??; 123 | 124 | // The table was removed 125 | async_conn 126 | .call::<_, _, rusqlite::Error>(|conn| { 127 | Ok(conn.execute("INSERT INTO animal (name) VALUES (?1)", params!["cat"])) 128 | }) 129 | .await 130 | .unwrap_err(); 131 | 132 | Ok(()) 133 | } 134 | -------------------------------------------------------------------------------- /examples/friend_car.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE friend( 2 | friend_id INTEGER PRIMARY KEY, 3 | name TEXT NOT NULL, 4 | email TEXT UNIQUE, 5 | phone TEXT UNIQUE, 6 | picture BLOB 7 | ); 8 | 9 | CREATE TABLE car( 10 | registration_plate TEXT PRIMARY KEY, 11 | cost REAL NOT NULL, 12 | bought_on TEXT NOT NULL 13 | ); 14 | -------------------------------------------------------------------------------- /examples/from-directory/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "example-from-directory" 3 | version = "0.1.0" 4 | edition = "2018" 5 | publish = false 6 | 7 | [dependencies] 8 | log = "0.4" 9 | simple-logging = "2.0.2" 10 | env_logger = "0.11" 11 | anyhow = "1" 12 | mktemp = "0.5" 13 | include_dir = "0.7.4" 14 | 15 | [dependencies.rusqlite_migration] 16 | path = "../../rusqlite_migration" 17 | features = ["from-directory"] 18 | 19 | [dependencies.rusqlite] 20 | version = "0.36.0" 21 | default-features = false 22 | features = [] 23 | -------------------------------------------------------------------------------- /examples/from-directory/build.rs: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | // Copyright Clément Joly and contributors. 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | 16 | fn main() { 17 | println!("cargo:rerun-if-changed=migrations/"); 18 | } 19 | -------------------------------------------------------------------------------- /examples/from-directory/migrations/01-friend_car/up.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE friend( 2 | friend_id INTEGER PRIMARY KEY, 3 | name TEXT NOT NULL, 4 | email TEXT UNIQUE, 5 | phone TEXT UNIQUE, 6 | picture BLOB 7 | ); 8 | 9 | CREATE TABLE car( 10 | registration_plate TEXT PRIMARY KEY, 11 | cost REAL NOT NULL, 12 | bought_on TEXT NOT NULL 13 | ); 14 | -------------------------------------------------------------------------------- /examples/from-directory/migrations/02-add_birthday_column/up.sql: -------------------------------------------------------------------------------- 1 | ALTER TABLE friend ADD COLUMN birthday TEXT; 2 | ALTER TABLE friend ADD COLUMN comment TEXT; -------------------------------------------------------------------------------- /examples/from-directory/migrations/03-add_animal_table/down.sql: -------------------------------------------------------------------------------- 1 | DROP TABLE animal; -------------------------------------------------------------------------------- /examples/from-directory/migrations/03-add_animal_table/up.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE animal(name TEXT); -------------------------------------------------------------------------------- /examples/from-directory/src/main.rs: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | // Copyright Clément Joly and contributors. 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | 16 | use std::sync::LazyLock; 17 | 18 | use anyhow::Result; 19 | use include_dir::{include_dir, Dir}; 20 | use rusqlite::{params, Connection}; 21 | use rusqlite_migration::Migrations; 22 | 23 | static MIGRATIONS_DIR: Dir = include_dir!("$CARGO_MANIFEST_DIR/migrations"); 24 | 25 | // Define migrations. These are applied atomically. 26 | static MIGRATIONS: LazyLock> = 27 | LazyLock::new(|| Migrations::from_directory(&MIGRATIONS_DIR).unwrap()); 28 | 29 | pub fn init_db() -> Result { 30 | let mut conn = Connection::open("./my_db.db3")?; 31 | 32 | // Update the database schema, atomically 33 | MIGRATIONS.to_latest(&mut conn)?; 34 | 35 | Ok(conn) 36 | } 37 | 38 | pub fn main() { 39 | env_logger::Builder::from_env(env_logger::Env::default().default_filter_or("trace")).init(); 40 | 41 | let mut conn = init_db().unwrap(); 42 | 43 | // Apply some PRAGMA. These are often better applied outside of migrations, as some needs to be 44 | // executed for each connection (like `foreign_keys`) or to be executed outside transactions 45 | // (`journal_mode` is a noop in a transaction). 46 | conn.pragma_update(None, "journal_mode", "WAL").unwrap(); 47 | conn.pragma_update(None, "foreign_keys", "ON").unwrap(); 48 | 49 | // Use the db 🥳 50 | conn.execute( 51 | "INSERT INTO friend (name, birthday) VALUES (?1, ?2)", 52 | params!["John", "1970-01-01"], 53 | ) 54 | .unwrap(); 55 | 56 | conn.execute("INSERT INTO animal (name) VALUES (?1)", params!["dog"]) 57 | .unwrap(); 58 | 59 | // If we want to revert the last migration 60 | MIGRATIONS.to_version(&mut conn, 2).unwrap(); 61 | 62 | // The table was removed 63 | conn.execute("INSERT INTO animal (name) VALUES (?1)", params!["cat"]) 64 | .unwrap_err(); 65 | } 66 | 67 | // Test that migrations are working 68 | #[cfg(test)] 69 | mod tests { 70 | use super::*; 71 | 72 | #[test] 73 | fn migrations_test() { 74 | assert!(MIGRATIONS.validate().is_ok()); 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /examples/simple/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "example-simple" 3 | version = "0.1.0" 4 | edition = "2018" 5 | publish = false 6 | 7 | [dependencies] 8 | rusqlite_migration = { path = "../../rusqlite_migration" } 9 | log = "0.4" 10 | simple-logging = "2.0.2" 11 | env_logger = "0.11" 12 | anyhow = "1" 13 | mktemp = "0.5" 14 | 15 | [dependencies.rusqlite] 16 | version = "0.36.0" 17 | features = ["extra_check"] # A realistic use case 18 | -------------------------------------------------------------------------------- /examples/simple/src/main.rs: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | // Copyright Clément Joly and contributors. 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | 16 | use anyhow::Result; 17 | use rusqlite::{params, Connection}; 18 | use rusqlite_migration::{Migrations, M}; 19 | 20 | // Test that migrations are working 21 | #[cfg(test)] 22 | mod tests { 23 | use super::*; 24 | 25 | #[test] 26 | fn migrations_test() { 27 | assert!(MIGRATIONS.validate().is_ok()); 28 | } 29 | } 30 | 31 | // Define migrations. These are applied atomically. 32 | const MIGRATION_ARRAY: &[M] = &[ 33 | M::up(include_str!("../../friend_car.sql")), 34 | // PRAGMA are better applied outside of migrations, see below for details. 35 | M::up( 36 | r#" 37 | ALTER TABLE friend ADD COLUMN birthday TEXT; 38 | ALTER TABLE friend ADD COLUMN comment TEXT; 39 | "#, 40 | ), 41 | // This migration can be reverted 42 | M::up("CREATE TABLE animal(name TEXT);").down("DROP TABLE animal;"), 43 | // In the future, if the need to change the schema arises, put 44 | // migrations here, like so: 45 | // M::up("CREATE INDEX UX_friend_email ON friend(email);"), 46 | // M::up("CREATE INDEX UX_friend_name ON friend(name);"), 47 | ]; 48 | const MIGRATIONS: Migrations = Migrations::from_slice(MIGRATION_ARRAY); 49 | 50 | pub fn init_db() -> Result { 51 | let mut conn = Connection::open("./my_db.db3")?; 52 | 53 | // Update the database schema, atomically 54 | MIGRATIONS.to_latest(&mut conn)?; 55 | 56 | Ok(conn) 57 | } 58 | 59 | pub fn main() { 60 | env_logger::Builder::from_env(env_logger::Env::default().default_filter_or("trace")).init(); 61 | 62 | let mut conn = init_db().unwrap(); 63 | 64 | // Apply some PRAGMA. These are often better applied outside of migrations, as some needs to be 65 | // executed for each connection (like `foreign_keys`) or to be executed outside transactions 66 | // (`journal_mode` is a noop in a transaction). 67 | conn.pragma_update(None, "journal_mode", "WAL").unwrap(); 68 | conn.pragma_update(None, "foreign_keys", "ON").unwrap(); 69 | 70 | // Use the db 🥳 71 | conn.execute( 72 | "INSERT INTO friend (name, birthday) VALUES (?1, ?2)", 73 | params!["John", "1970-01-01"], 74 | ) 75 | .unwrap(); 76 | 77 | conn.execute("INSERT INTO animal (name) VALUES (?1)", params!["dog"]) 78 | .unwrap(); 79 | 80 | // If we want to revert the last migration 81 | MIGRATIONS.to_version(&mut conn, 2).unwrap(); 82 | 83 | // The table was removed 84 | conn.execute("INSERT INTO animal (name) VALUES (?1)", params!["cat"]) 85 | .unwrap_err(); 86 | } 87 | -------------------------------------------------------------------------------- /rusqlite_migration/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | edition = "2021" 3 | name = "rusqlite_migration" 4 | readme = "README.md" 5 | 6 | authors.workspace = true 7 | categories.workspace = true 8 | description.workspace = true 9 | homepage.workspace = true 10 | keywords.workspace = true 11 | license.workspace = true 12 | repository.workspace = true 13 | rust-version.workspace = true 14 | version.workspace = true 15 | 16 | # Locally, run: 17 | # RUSTDOCFLAGS="--cfg docsrs" cargo +nightly doc --all-features 18 | [package.metadata.docs.rs] 19 | # Document all features 20 | all-features = true 21 | rustdoc-args = ["--cfg", "docsrs"] 22 | 23 | [lints] 24 | workspace = true 25 | 26 | [features] 27 | default = [] 28 | 29 | ### Enable loading migrations from *.sql files in a given directory 30 | from-directory = ["dep:include_dir"] 31 | 32 | [dependencies] 33 | include_dir = { version = "0.7.4", optional = true } 34 | log = "0.4" 35 | 36 | [dependencies.rusqlite] 37 | version = "0.36.0" 38 | default-features = false 39 | features = [] 40 | 41 | [dev-dependencies] 42 | anyhow = "1" 43 | env_logger = "0.11" 44 | iai = "0.1" 45 | insta = "1.43.1" 46 | mktemp = "0.5" 47 | mutants = "0.0.3" 48 | simple-logging = "2.0.2" 49 | 50 | [dev-dependencies.criterion] 51 | version = "0.6.0" 52 | features = ["html_reports", "cargo_bench_support"] 53 | 54 | [[bench]] 55 | name = "criterion" 56 | harness = false 57 | 58 | [[bench]] 59 | name = "iai" 60 | harness = false 61 | -------------------------------------------------------------------------------- /rusqlite_migration/README.md: -------------------------------------------------------------------------------- 1 | ../README.md -------------------------------------------------------------------------------- /rusqlite_migration/benches/100_migrations/1-comment/down.sql: -------------------------------------------------------------------------------- 1 | DROP TABLE t1; 2 | -------------------------------------------------------------------------------- /rusqlite_migration/benches/100_migrations/1-comment/up.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE t1(a, b, c); 2 | -------------------------------------------------------------------------------- /rusqlite_migration/benches/100_migrations/10-comment/down.sql: -------------------------------------------------------------------------------- 1 | DROP TABLE t10; 2 | -------------------------------------------------------------------------------- /rusqlite_migration/benches/100_migrations/10-comment/up.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE t10(a, b, c); 2 | -------------------------------------------------------------------------------- /rusqlite_migration/benches/100_migrations/100-comment/down.sql: -------------------------------------------------------------------------------- 1 | DROP TABLE t100; 2 | -------------------------------------------------------------------------------- /rusqlite_migration/benches/100_migrations/100-comment/up.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE t100(a, b, c); 2 | -------------------------------------------------------------------------------- /rusqlite_migration/benches/100_migrations/11-comment/down.sql: -------------------------------------------------------------------------------- 1 | DROP TABLE t11; 2 | -------------------------------------------------------------------------------- /rusqlite_migration/benches/100_migrations/11-comment/up.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE t11(a, b, c); 2 | -------------------------------------------------------------------------------- /rusqlite_migration/benches/100_migrations/12-comment/down.sql: -------------------------------------------------------------------------------- 1 | DROP TABLE t12; 2 | -------------------------------------------------------------------------------- /rusqlite_migration/benches/100_migrations/12-comment/up.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE t12(a, b, c); 2 | -------------------------------------------------------------------------------- /rusqlite_migration/benches/100_migrations/13-comment/down.sql: -------------------------------------------------------------------------------- 1 | DROP TABLE t13; 2 | -------------------------------------------------------------------------------- /rusqlite_migration/benches/100_migrations/13-comment/up.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE t13(a, b, c); 2 | -------------------------------------------------------------------------------- /rusqlite_migration/benches/100_migrations/14-comment/down.sql: -------------------------------------------------------------------------------- 1 | DROP TABLE t14; 2 | -------------------------------------------------------------------------------- /rusqlite_migration/benches/100_migrations/14-comment/up.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE t14(a, b, c); 2 | -------------------------------------------------------------------------------- /rusqlite_migration/benches/100_migrations/15-comment/down.sql: -------------------------------------------------------------------------------- 1 | DROP TABLE t15; 2 | -------------------------------------------------------------------------------- /rusqlite_migration/benches/100_migrations/15-comment/up.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE t15(a, b, c); 2 | -------------------------------------------------------------------------------- /rusqlite_migration/benches/100_migrations/16-comment/down.sql: -------------------------------------------------------------------------------- 1 | DROP TABLE t16; 2 | -------------------------------------------------------------------------------- /rusqlite_migration/benches/100_migrations/16-comment/up.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE t16(a, b, c); 2 | -------------------------------------------------------------------------------- /rusqlite_migration/benches/100_migrations/17-comment/down.sql: -------------------------------------------------------------------------------- 1 | DROP TABLE t17; 2 | -------------------------------------------------------------------------------- /rusqlite_migration/benches/100_migrations/17-comment/up.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE t17(a, b, c); 2 | -------------------------------------------------------------------------------- /rusqlite_migration/benches/100_migrations/18-comment/down.sql: -------------------------------------------------------------------------------- 1 | DROP TABLE t18; 2 | -------------------------------------------------------------------------------- /rusqlite_migration/benches/100_migrations/18-comment/up.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE t18(a, b, c); 2 | -------------------------------------------------------------------------------- /rusqlite_migration/benches/100_migrations/19-comment/down.sql: -------------------------------------------------------------------------------- 1 | DROP TABLE t19; 2 | -------------------------------------------------------------------------------- /rusqlite_migration/benches/100_migrations/19-comment/up.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE t19(a, b, c); 2 | -------------------------------------------------------------------------------- /rusqlite_migration/benches/100_migrations/2-comment/down.sql: -------------------------------------------------------------------------------- 1 | DROP TABLE t2; 2 | -------------------------------------------------------------------------------- /rusqlite_migration/benches/100_migrations/2-comment/up.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE t2(a, b, c); 2 | -------------------------------------------------------------------------------- /rusqlite_migration/benches/100_migrations/20-comment/down.sql: -------------------------------------------------------------------------------- 1 | DROP TABLE t20; 2 | -------------------------------------------------------------------------------- /rusqlite_migration/benches/100_migrations/20-comment/up.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE t20(a, b, c); 2 | -------------------------------------------------------------------------------- /rusqlite_migration/benches/100_migrations/21-comment/down.sql: -------------------------------------------------------------------------------- 1 | DROP TABLE t21; 2 | -------------------------------------------------------------------------------- /rusqlite_migration/benches/100_migrations/21-comment/up.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE t21(a, b, c); 2 | -------------------------------------------------------------------------------- /rusqlite_migration/benches/100_migrations/22-comment/down.sql: -------------------------------------------------------------------------------- 1 | DROP TABLE t22; 2 | -------------------------------------------------------------------------------- /rusqlite_migration/benches/100_migrations/22-comment/up.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE t22(a, b, c); 2 | -------------------------------------------------------------------------------- /rusqlite_migration/benches/100_migrations/23-comment/down.sql: -------------------------------------------------------------------------------- 1 | DROP TABLE t23; 2 | -------------------------------------------------------------------------------- /rusqlite_migration/benches/100_migrations/23-comment/up.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE t23(a, b, c); 2 | -------------------------------------------------------------------------------- /rusqlite_migration/benches/100_migrations/24-comment/down.sql: -------------------------------------------------------------------------------- 1 | DROP TABLE t24; 2 | -------------------------------------------------------------------------------- /rusqlite_migration/benches/100_migrations/24-comment/up.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE t24(a, b, c); 2 | -------------------------------------------------------------------------------- /rusqlite_migration/benches/100_migrations/25-comment/down.sql: -------------------------------------------------------------------------------- 1 | DROP TABLE t25; 2 | -------------------------------------------------------------------------------- /rusqlite_migration/benches/100_migrations/25-comment/up.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE t25(a, b, c); 2 | -------------------------------------------------------------------------------- /rusqlite_migration/benches/100_migrations/26-comment/down.sql: -------------------------------------------------------------------------------- 1 | DROP TABLE t26; 2 | -------------------------------------------------------------------------------- /rusqlite_migration/benches/100_migrations/26-comment/up.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE t26(a, b, c); 2 | -------------------------------------------------------------------------------- /rusqlite_migration/benches/100_migrations/27-comment/down.sql: -------------------------------------------------------------------------------- 1 | DROP TABLE t27; 2 | -------------------------------------------------------------------------------- /rusqlite_migration/benches/100_migrations/27-comment/up.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE t27(a, b, c); 2 | -------------------------------------------------------------------------------- /rusqlite_migration/benches/100_migrations/28-comment/down.sql: -------------------------------------------------------------------------------- 1 | DROP TABLE t28; 2 | -------------------------------------------------------------------------------- /rusqlite_migration/benches/100_migrations/28-comment/up.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE t28(a, b, c); 2 | -------------------------------------------------------------------------------- /rusqlite_migration/benches/100_migrations/29-comment/down.sql: -------------------------------------------------------------------------------- 1 | DROP TABLE t29; 2 | -------------------------------------------------------------------------------- /rusqlite_migration/benches/100_migrations/29-comment/up.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE t29(a, b, c); 2 | -------------------------------------------------------------------------------- /rusqlite_migration/benches/100_migrations/3-comment/down.sql: -------------------------------------------------------------------------------- 1 | DROP TABLE t3; 2 | -------------------------------------------------------------------------------- /rusqlite_migration/benches/100_migrations/3-comment/up.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE t3(a, b, c); 2 | -------------------------------------------------------------------------------- /rusqlite_migration/benches/100_migrations/30-comment/down.sql: -------------------------------------------------------------------------------- 1 | DROP TABLE t30; 2 | -------------------------------------------------------------------------------- /rusqlite_migration/benches/100_migrations/30-comment/up.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE t30(a, b, c); 2 | -------------------------------------------------------------------------------- /rusqlite_migration/benches/100_migrations/31-comment/down.sql: -------------------------------------------------------------------------------- 1 | DROP TABLE t31; 2 | -------------------------------------------------------------------------------- /rusqlite_migration/benches/100_migrations/31-comment/up.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE t31(a, b, c); 2 | -------------------------------------------------------------------------------- /rusqlite_migration/benches/100_migrations/32-comment/down.sql: -------------------------------------------------------------------------------- 1 | DROP TABLE t32; 2 | -------------------------------------------------------------------------------- /rusqlite_migration/benches/100_migrations/32-comment/up.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE t32(a, b, c); 2 | -------------------------------------------------------------------------------- /rusqlite_migration/benches/100_migrations/33-comment/down.sql: -------------------------------------------------------------------------------- 1 | DROP TABLE t33; 2 | -------------------------------------------------------------------------------- /rusqlite_migration/benches/100_migrations/33-comment/up.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE t33(a, b, c); 2 | -------------------------------------------------------------------------------- /rusqlite_migration/benches/100_migrations/34-comment/down.sql: -------------------------------------------------------------------------------- 1 | DROP TABLE t34; 2 | -------------------------------------------------------------------------------- /rusqlite_migration/benches/100_migrations/34-comment/up.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE t34(a, b, c); 2 | -------------------------------------------------------------------------------- /rusqlite_migration/benches/100_migrations/35-comment/down.sql: -------------------------------------------------------------------------------- 1 | DROP TABLE t35; 2 | -------------------------------------------------------------------------------- /rusqlite_migration/benches/100_migrations/35-comment/up.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE t35(a, b, c); 2 | -------------------------------------------------------------------------------- /rusqlite_migration/benches/100_migrations/36-comment/down.sql: -------------------------------------------------------------------------------- 1 | DROP TABLE t36; 2 | -------------------------------------------------------------------------------- /rusqlite_migration/benches/100_migrations/36-comment/up.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE t36(a, b, c); 2 | -------------------------------------------------------------------------------- /rusqlite_migration/benches/100_migrations/37-comment/down.sql: -------------------------------------------------------------------------------- 1 | DROP TABLE t37; 2 | -------------------------------------------------------------------------------- /rusqlite_migration/benches/100_migrations/37-comment/up.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE t37(a, b, c); 2 | -------------------------------------------------------------------------------- /rusqlite_migration/benches/100_migrations/38-comment/down.sql: -------------------------------------------------------------------------------- 1 | DROP TABLE t38; 2 | -------------------------------------------------------------------------------- /rusqlite_migration/benches/100_migrations/38-comment/up.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE t38(a, b, c); 2 | -------------------------------------------------------------------------------- /rusqlite_migration/benches/100_migrations/39-comment/down.sql: -------------------------------------------------------------------------------- 1 | DROP TABLE t39; 2 | -------------------------------------------------------------------------------- /rusqlite_migration/benches/100_migrations/39-comment/up.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE t39(a, b, c); 2 | -------------------------------------------------------------------------------- /rusqlite_migration/benches/100_migrations/4-comment/down.sql: -------------------------------------------------------------------------------- 1 | DROP TABLE t4; 2 | -------------------------------------------------------------------------------- /rusqlite_migration/benches/100_migrations/4-comment/up.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE t4(a, b, c); 2 | -------------------------------------------------------------------------------- /rusqlite_migration/benches/100_migrations/40-comment/down.sql: -------------------------------------------------------------------------------- 1 | DROP TABLE t40; 2 | -------------------------------------------------------------------------------- /rusqlite_migration/benches/100_migrations/40-comment/up.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE t40(a, b, c); 2 | -------------------------------------------------------------------------------- /rusqlite_migration/benches/100_migrations/41-comment/down.sql: -------------------------------------------------------------------------------- 1 | DROP TABLE t41; 2 | -------------------------------------------------------------------------------- /rusqlite_migration/benches/100_migrations/41-comment/up.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE t41(a, b, c); 2 | -------------------------------------------------------------------------------- /rusqlite_migration/benches/100_migrations/42-comment/down.sql: -------------------------------------------------------------------------------- 1 | DROP TABLE t42; 2 | -------------------------------------------------------------------------------- /rusqlite_migration/benches/100_migrations/42-comment/up.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE t42(a, b, c); 2 | -------------------------------------------------------------------------------- /rusqlite_migration/benches/100_migrations/43-comment/down.sql: -------------------------------------------------------------------------------- 1 | DROP TABLE t43; 2 | -------------------------------------------------------------------------------- /rusqlite_migration/benches/100_migrations/43-comment/up.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE t43(a, b, c); 2 | -------------------------------------------------------------------------------- /rusqlite_migration/benches/100_migrations/44-comment/down.sql: -------------------------------------------------------------------------------- 1 | DROP TABLE t44; 2 | -------------------------------------------------------------------------------- /rusqlite_migration/benches/100_migrations/44-comment/up.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE t44(a, b, c); 2 | -------------------------------------------------------------------------------- /rusqlite_migration/benches/100_migrations/45-comment/down.sql: -------------------------------------------------------------------------------- 1 | DROP TABLE t45; 2 | -------------------------------------------------------------------------------- /rusqlite_migration/benches/100_migrations/45-comment/up.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE t45(a, b, c); 2 | -------------------------------------------------------------------------------- /rusqlite_migration/benches/100_migrations/46-comment/down.sql: -------------------------------------------------------------------------------- 1 | DROP TABLE t46; 2 | -------------------------------------------------------------------------------- /rusqlite_migration/benches/100_migrations/46-comment/up.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE t46(a, b, c); 2 | -------------------------------------------------------------------------------- /rusqlite_migration/benches/100_migrations/47-comment/down.sql: -------------------------------------------------------------------------------- 1 | DROP TABLE t47; 2 | -------------------------------------------------------------------------------- /rusqlite_migration/benches/100_migrations/47-comment/up.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE t47(a, b, c); 2 | -------------------------------------------------------------------------------- /rusqlite_migration/benches/100_migrations/48-comment/down.sql: -------------------------------------------------------------------------------- 1 | DROP TABLE t48; 2 | -------------------------------------------------------------------------------- /rusqlite_migration/benches/100_migrations/48-comment/up.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE t48(a, b, c); 2 | -------------------------------------------------------------------------------- /rusqlite_migration/benches/100_migrations/49-comment/down.sql: -------------------------------------------------------------------------------- 1 | DROP TABLE t49; 2 | -------------------------------------------------------------------------------- /rusqlite_migration/benches/100_migrations/49-comment/up.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE t49(a, b, c); 2 | -------------------------------------------------------------------------------- /rusqlite_migration/benches/100_migrations/5-comment/down.sql: -------------------------------------------------------------------------------- 1 | DROP TABLE t5; 2 | -------------------------------------------------------------------------------- /rusqlite_migration/benches/100_migrations/5-comment/up.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE t5(a, b, c); 2 | -------------------------------------------------------------------------------- /rusqlite_migration/benches/100_migrations/50-comment/down.sql: -------------------------------------------------------------------------------- 1 | DROP TABLE t50; 2 | -------------------------------------------------------------------------------- /rusqlite_migration/benches/100_migrations/50-comment/up.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE t50(a, b, c); 2 | -------------------------------------------------------------------------------- /rusqlite_migration/benches/100_migrations/51-comment/down.sql: -------------------------------------------------------------------------------- 1 | DROP TABLE t51; 2 | -------------------------------------------------------------------------------- /rusqlite_migration/benches/100_migrations/51-comment/up.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE t51(a, b, c); 2 | -------------------------------------------------------------------------------- /rusqlite_migration/benches/100_migrations/52-comment/down.sql: -------------------------------------------------------------------------------- 1 | DROP TABLE t52; 2 | -------------------------------------------------------------------------------- /rusqlite_migration/benches/100_migrations/52-comment/up.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE t52(a, b, c); 2 | -------------------------------------------------------------------------------- /rusqlite_migration/benches/100_migrations/53-comment/down.sql: -------------------------------------------------------------------------------- 1 | DROP TABLE t53; 2 | -------------------------------------------------------------------------------- /rusqlite_migration/benches/100_migrations/53-comment/up.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE t53(a, b, c); 2 | -------------------------------------------------------------------------------- /rusqlite_migration/benches/100_migrations/54-comment/down.sql: -------------------------------------------------------------------------------- 1 | DROP TABLE t54; 2 | -------------------------------------------------------------------------------- /rusqlite_migration/benches/100_migrations/54-comment/up.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE t54(a, b, c); 2 | -------------------------------------------------------------------------------- /rusqlite_migration/benches/100_migrations/55-comment/down.sql: -------------------------------------------------------------------------------- 1 | DROP TABLE t55; 2 | -------------------------------------------------------------------------------- /rusqlite_migration/benches/100_migrations/55-comment/up.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE t55(a, b, c); 2 | -------------------------------------------------------------------------------- /rusqlite_migration/benches/100_migrations/56-comment/down.sql: -------------------------------------------------------------------------------- 1 | DROP TABLE t56; 2 | -------------------------------------------------------------------------------- /rusqlite_migration/benches/100_migrations/56-comment/up.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE t56(a, b, c); 2 | -------------------------------------------------------------------------------- /rusqlite_migration/benches/100_migrations/57-comment/down.sql: -------------------------------------------------------------------------------- 1 | DROP TABLE t57; 2 | -------------------------------------------------------------------------------- /rusqlite_migration/benches/100_migrations/57-comment/up.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE t57(a, b, c); 2 | -------------------------------------------------------------------------------- /rusqlite_migration/benches/100_migrations/58-comment/down.sql: -------------------------------------------------------------------------------- 1 | DROP TABLE t58; 2 | -------------------------------------------------------------------------------- /rusqlite_migration/benches/100_migrations/58-comment/up.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE t58(a, b, c); 2 | -------------------------------------------------------------------------------- /rusqlite_migration/benches/100_migrations/59-comment/down.sql: -------------------------------------------------------------------------------- 1 | DROP TABLE t59; 2 | -------------------------------------------------------------------------------- /rusqlite_migration/benches/100_migrations/59-comment/up.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE t59(a, b, c); 2 | -------------------------------------------------------------------------------- /rusqlite_migration/benches/100_migrations/6-comment/down.sql: -------------------------------------------------------------------------------- 1 | DROP TABLE t6; 2 | -------------------------------------------------------------------------------- /rusqlite_migration/benches/100_migrations/6-comment/up.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE t6(a, b, c); 2 | -------------------------------------------------------------------------------- /rusqlite_migration/benches/100_migrations/60-comment/down.sql: -------------------------------------------------------------------------------- 1 | DROP TABLE t60; 2 | -------------------------------------------------------------------------------- /rusqlite_migration/benches/100_migrations/60-comment/up.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE t60(a, b, c); 2 | -------------------------------------------------------------------------------- /rusqlite_migration/benches/100_migrations/61-comment/down.sql: -------------------------------------------------------------------------------- 1 | DROP TABLE t61; 2 | -------------------------------------------------------------------------------- /rusqlite_migration/benches/100_migrations/61-comment/up.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE t61(a, b, c); 2 | -------------------------------------------------------------------------------- /rusqlite_migration/benches/100_migrations/62-comment/down.sql: -------------------------------------------------------------------------------- 1 | DROP TABLE t62; 2 | -------------------------------------------------------------------------------- /rusqlite_migration/benches/100_migrations/62-comment/up.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE t62(a, b, c); 2 | -------------------------------------------------------------------------------- /rusqlite_migration/benches/100_migrations/63-comment/down.sql: -------------------------------------------------------------------------------- 1 | DROP TABLE t63; 2 | -------------------------------------------------------------------------------- /rusqlite_migration/benches/100_migrations/63-comment/up.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE t63(a, b, c); 2 | -------------------------------------------------------------------------------- /rusqlite_migration/benches/100_migrations/64-comment/down.sql: -------------------------------------------------------------------------------- 1 | DROP TABLE t64; 2 | -------------------------------------------------------------------------------- /rusqlite_migration/benches/100_migrations/64-comment/up.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE t64(a, b, c); 2 | -------------------------------------------------------------------------------- /rusqlite_migration/benches/100_migrations/65-comment/down.sql: -------------------------------------------------------------------------------- 1 | DROP TABLE t65; 2 | -------------------------------------------------------------------------------- /rusqlite_migration/benches/100_migrations/65-comment/up.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE t65(a, b, c); 2 | -------------------------------------------------------------------------------- /rusqlite_migration/benches/100_migrations/66-comment/down.sql: -------------------------------------------------------------------------------- 1 | DROP TABLE t66; 2 | -------------------------------------------------------------------------------- /rusqlite_migration/benches/100_migrations/66-comment/up.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE t66(a, b, c); 2 | -------------------------------------------------------------------------------- /rusqlite_migration/benches/100_migrations/67-comment/down.sql: -------------------------------------------------------------------------------- 1 | DROP TABLE t67; 2 | -------------------------------------------------------------------------------- /rusqlite_migration/benches/100_migrations/67-comment/up.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE t67(a, b, c); 2 | -------------------------------------------------------------------------------- /rusqlite_migration/benches/100_migrations/68-comment/down.sql: -------------------------------------------------------------------------------- 1 | DROP TABLE t68; 2 | -------------------------------------------------------------------------------- /rusqlite_migration/benches/100_migrations/68-comment/up.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE t68(a, b, c); 2 | -------------------------------------------------------------------------------- /rusqlite_migration/benches/100_migrations/69-comment/down.sql: -------------------------------------------------------------------------------- 1 | DROP TABLE t69; 2 | -------------------------------------------------------------------------------- /rusqlite_migration/benches/100_migrations/69-comment/up.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE t69(a, b, c); 2 | -------------------------------------------------------------------------------- /rusqlite_migration/benches/100_migrations/7-comment/down.sql: -------------------------------------------------------------------------------- 1 | DROP TABLE t7; 2 | -------------------------------------------------------------------------------- /rusqlite_migration/benches/100_migrations/7-comment/up.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE t7(a, b, c); 2 | -------------------------------------------------------------------------------- /rusqlite_migration/benches/100_migrations/70-comment/down.sql: -------------------------------------------------------------------------------- 1 | DROP TABLE t70; 2 | -------------------------------------------------------------------------------- /rusqlite_migration/benches/100_migrations/70-comment/up.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE t70(a, b, c); 2 | -------------------------------------------------------------------------------- /rusqlite_migration/benches/100_migrations/71-comment/down.sql: -------------------------------------------------------------------------------- 1 | DROP TABLE t71; 2 | -------------------------------------------------------------------------------- /rusqlite_migration/benches/100_migrations/71-comment/up.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE t71(a, b, c); 2 | -------------------------------------------------------------------------------- /rusqlite_migration/benches/100_migrations/72-comment/down.sql: -------------------------------------------------------------------------------- 1 | DROP TABLE t72; 2 | -------------------------------------------------------------------------------- /rusqlite_migration/benches/100_migrations/72-comment/up.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE t72(a, b, c); 2 | -------------------------------------------------------------------------------- /rusqlite_migration/benches/100_migrations/73-comment/down.sql: -------------------------------------------------------------------------------- 1 | DROP TABLE t73; 2 | -------------------------------------------------------------------------------- /rusqlite_migration/benches/100_migrations/73-comment/up.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE t73(a, b, c); 2 | -------------------------------------------------------------------------------- /rusqlite_migration/benches/100_migrations/74-comment/down.sql: -------------------------------------------------------------------------------- 1 | DROP TABLE t74; 2 | -------------------------------------------------------------------------------- /rusqlite_migration/benches/100_migrations/74-comment/up.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE t74(a, b, c); 2 | -------------------------------------------------------------------------------- /rusqlite_migration/benches/100_migrations/75-comment/down.sql: -------------------------------------------------------------------------------- 1 | DROP TABLE t75; 2 | -------------------------------------------------------------------------------- /rusqlite_migration/benches/100_migrations/75-comment/up.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE t75(a, b, c); 2 | -------------------------------------------------------------------------------- /rusqlite_migration/benches/100_migrations/76-comment/down.sql: -------------------------------------------------------------------------------- 1 | DROP TABLE t76; 2 | -------------------------------------------------------------------------------- /rusqlite_migration/benches/100_migrations/76-comment/up.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE t76(a, b, c); 2 | -------------------------------------------------------------------------------- /rusqlite_migration/benches/100_migrations/77-comment/down.sql: -------------------------------------------------------------------------------- 1 | DROP TABLE t77; 2 | -------------------------------------------------------------------------------- /rusqlite_migration/benches/100_migrations/77-comment/up.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE t77(a, b, c); 2 | -------------------------------------------------------------------------------- /rusqlite_migration/benches/100_migrations/78-comment/down.sql: -------------------------------------------------------------------------------- 1 | DROP TABLE t78; 2 | -------------------------------------------------------------------------------- /rusqlite_migration/benches/100_migrations/78-comment/up.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE t78(a, b, c); 2 | -------------------------------------------------------------------------------- /rusqlite_migration/benches/100_migrations/79-comment/down.sql: -------------------------------------------------------------------------------- 1 | DROP TABLE t79; 2 | -------------------------------------------------------------------------------- /rusqlite_migration/benches/100_migrations/79-comment/up.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE t79(a, b, c); 2 | -------------------------------------------------------------------------------- /rusqlite_migration/benches/100_migrations/8-comment/down.sql: -------------------------------------------------------------------------------- 1 | DROP TABLE t8; 2 | -------------------------------------------------------------------------------- /rusqlite_migration/benches/100_migrations/8-comment/up.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE t8(a, b, c); 2 | -------------------------------------------------------------------------------- /rusqlite_migration/benches/100_migrations/80-comment/down.sql: -------------------------------------------------------------------------------- 1 | DROP TABLE t80; 2 | -------------------------------------------------------------------------------- /rusqlite_migration/benches/100_migrations/80-comment/up.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE t80(a, b, c); 2 | -------------------------------------------------------------------------------- /rusqlite_migration/benches/100_migrations/81-comment/down.sql: -------------------------------------------------------------------------------- 1 | DROP TABLE t81; 2 | -------------------------------------------------------------------------------- /rusqlite_migration/benches/100_migrations/81-comment/up.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE t81(a, b, c); 2 | -------------------------------------------------------------------------------- /rusqlite_migration/benches/100_migrations/82-comment/down.sql: -------------------------------------------------------------------------------- 1 | DROP TABLE t82; 2 | -------------------------------------------------------------------------------- /rusqlite_migration/benches/100_migrations/82-comment/up.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE t82(a, b, c); 2 | -------------------------------------------------------------------------------- /rusqlite_migration/benches/100_migrations/83-comment/down.sql: -------------------------------------------------------------------------------- 1 | DROP TABLE t83; 2 | -------------------------------------------------------------------------------- /rusqlite_migration/benches/100_migrations/83-comment/up.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE t83(a, b, c); 2 | -------------------------------------------------------------------------------- /rusqlite_migration/benches/100_migrations/84-comment/down.sql: -------------------------------------------------------------------------------- 1 | DROP TABLE t84; 2 | -------------------------------------------------------------------------------- /rusqlite_migration/benches/100_migrations/84-comment/up.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE t84(a, b, c); 2 | -------------------------------------------------------------------------------- /rusqlite_migration/benches/100_migrations/85-comment/down.sql: -------------------------------------------------------------------------------- 1 | DROP TABLE t85; 2 | -------------------------------------------------------------------------------- /rusqlite_migration/benches/100_migrations/85-comment/up.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE t85(a, b, c); 2 | -------------------------------------------------------------------------------- /rusqlite_migration/benches/100_migrations/86-comment/down.sql: -------------------------------------------------------------------------------- 1 | DROP TABLE t86; 2 | -------------------------------------------------------------------------------- /rusqlite_migration/benches/100_migrations/86-comment/up.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE t86(a, b, c); 2 | -------------------------------------------------------------------------------- /rusqlite_migration/benches/100_migrations/87-comment/down.sql: -------------------------------------------------------------------------------- 1 | DROP TABLE t87; 2 | -------------------------------------------------------------------------------- /rusqlite_migration/benches/100_migrations/87-comment/up.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE t87(a, b, c); 2 | -------------------------------------------------------------------------------- /rusqlite_migration/benches/100_migrations/88-comment/down.sql: -------------------------------------------------------------------------------- 1 | DROP TABLE t88; 2 | -------------------------------------------------------------------------------- /rusqlite_migration/benches/100_migrations/88-comment/up.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE t88(a, b, c); 2 | -------------------------------------------------------------------------------- /rusqlite_migration/benches/100_migrations/89-comment/down.sql: -------------------------------------------------------------------------------- 1 | DROP TABLE t89; 2 | -------------------------------------------------------------------------------- /rusqlite_migration/benches/100_migrations/89-comment/up.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE t89(a, b, c); 2 | -------------------------------------------------------------------------------- /rusqlite_migration/benches/100_migrations/9-comment/down.sql: -------------------------------------------------------------------------------- 1 | DROP TABLE t9; 2 | -------------------------------------------------------------------------------- /rusqlite_migration/benches/100_migrations/9-comment/up.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE t9(a, b, c); 2 | -------------------------------------------------------------------------------- /rusqlite_migration/benches/100_migrations/90-comment/down.sql: -------------------------------------------------------------------------------- 1 | DROP TABLE t90; 2 | -------------------------------------------------------------------------------- /rusqlite_migration/benches/100_migrations/90-comment/up.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE t90(a, b, c); 2 | -------------------------------------------------------------------------------- /rusqlite_migration/benches/100_migrations/91-comment/down.sql: -------------------------------------------------------------------------------- 1 | DROP TABLE t91; 2 | -------------------------------------------------------------------------------- /rusqlite_migration/benches/100_migrations/91-comment/up.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE t91(a, b, c); 2 | -------------------------------------------------------------------------------- /rusqlite_migration/benches/100_migrations/92-comment/down.sql: -------------------------------------------------------------------------------- 1 | DROP TABLE t92; 2 | -------------------------------------------------------------------------------- /rusqlite_migration/benches/100_migrations/92-comment/up.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE t92(a, b, c); 2 | -------------------------------------------------------------------------------- /rusqlite_migration/benches/100_migrations/93-comment/down.sql: -------------------------------------------------------------------------------- 1 | DROP TABLE t93; 2 | -------------------------------------------------------------------------------- /rusqlite_migration/benches/100_migrations/93-comment/up.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE t93(a, b, c); 2 | -------------------------------------------------------------------------------- /rusqlite_migration/benches/100_migrations/94-comment/down.sql: -------------------------------------------------------------------------------- 1 | DROP TABLE t94; 2 | -------------------------------------------------------------------------------- /rusqlite_migration/benches/100_migrations/94-comment/up.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE t94(a, b, c); 2 | -------------------------------------------------------------------------------- /rusqlite_migration/benches/100_migrations/95-comment/down.sql: -------------------------------------------------------------------------------- 1 | DROP TABLE t95; 2 | -------------------------------------------------------------------------------- /rusqlite_migration/benches/100_migrations/95-comment/up.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE t95(a, b, c); 2 | -------------------------------------------------------------------------------- /rusqlite_migration/benches/100_migrations/96-comment/down.sql: -------------------------------------------------------------------------------- 1 | DROP TABLE t96; 2 | -------------------------------------------------------------------------------- /rusqlite_migration/benches/100_migrations/96-comment/up.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE t96(a, b, c); 2 | -------------------------------------------------------------------------------- /rusqlite_migration/benches/100_migrations/97-comment/down.sql: -------------------------------------------------------------------------------- 1 | DROP TABLE t97; 2 | -------------------------------------------------------------------------------- /rusqlite_migration/benches/100_migrations/97-comment/up.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE t97(a, b, c); 2 | -------------------------------------------------------------------------------- /rusqlite_migration/benches/100_migrations/98-comment/down.sql: -------------------------------------------------------------------------------- 1 | DROP TABLE t98; 2 | -------------------------------------------------------------------------------- /rusqlite_migration/benches/100_migrations/98-comment/up.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE t98(a, b, c); 2 | -------------------------------------------------------------------------------- /rusqlite_migration/benches/100_migrations/99-comment/down.sql: -------------------------------------------------------------------------------- 1 | DROP TABLE t99; 2 | -------------------------------------------------------------------------------- /rusqlite_migration/benches/100_migrations/99-comment/up.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE t99(a, b, c); 2 | -------------------------------------------------------------------------------- /rusqlite_migration/benches/10_migrations/1-comment/down.sql: -------------------------------------------------------------------------------- 1 | DROP TABLE t1; 2 | -------------------------------------------------------------------------------- /rusqlite_migration/benches/10_migrations/1-comment/up.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE t1(a, b, c); 2 | -------------------------------------------------------------------------------- /rusqlite_migration/benches/10_migrations/10-comment/down.sql: -------------------------------------------------------------------------------- 1 | DROP TABLE t10; 2 | -------------------------------------------------------------------------------- /rusqlite_migration/benches/10_migrations/10-comment/up.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE t10(a, b, c); 2 | -------------------------------------------------------------------------------- /rusqlite_migration/benches/10_migrations/2-comment/down.sql: -------------------------------------------------------------------------------- 1 | DROP TABLE t2; 2 | -------------------------------------------------------------------------------- /rusqlite_migration/benches/10_migrations/2-comment/up.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE t2(a, b, c); 2 | -------------------------------------------------------------------------------- /rusqlite_migration/benches/10_migrations/3-comment/down.sql: -------------------------------------------------------------------------------- 1 | DROP TABLE t3; 2 | -------------------------------------------------------------------------------- /rusqlite_migration/benches/10_migrations/3-comment/up.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE t3(a, b, c); 2 | -------------------------------------------------------------------------------- /rusqlite_migration/benches/10_migrations/4-comment/down.sql: -------------------------------------------------------------------------------- 1 | DROP TABLE t4; 2 | -------------------------------------------------------------------------------- /rusqlite_migration/benches/10_migrations/4-comment/up.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE t4(a, b, c); 2 | -------------------------------------------------------------------------------- /rusqlite_migration/benches/10_migrations/5-comment/down.sql: -------------------------------------------------------------------------------- 1 | DROP TABLE t5; 2 | -------------------------------------------------------------------------------- /rusqlite_migration/benches/10_migrations/5-comment/up.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE t5(a, b, c); 2 | -------------------------------------------------------------------------------- /rusqlite_migration/benches/10_migrations/6-comment/down.sql: -------------------------------------------------------------------------------- 1 | DROP TABLE t6; 2 | -------------------------------------------------------------------------------- /rusqlite_migration/benches/10_migrations/6-comment/up.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE t6(a, b, c); 2 | -------------------------------------------------------------------------------- /rusqlite_migration/benches/10_migrations/7-comment/down.sql: -------------------------------------------------------------------------------- 1 | DROP TABLE t7; 2 | -------------------------------------------------------------------------------- /rusqlite_migration/benches/10_migrations/7-comment/up.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE t7(a, b, c); 2 | -------------------------------------------------------------------------------- /rusqlite_migration/benches/10_migrations/8-comment/down.sql: -------------------------------------------------------------------------------- 1 | DROP TABLE t8; 2 | -------------------------------------------------------------------------------- /rusqlite_migration/benches/10_migrations/8-comment/up.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE t8(a, b, c); 2 | -------------------------------------------------------------------------------- /rusqlite_migration/benches/10_migrations/9-comment/down.sql: -------------------------------------------------------------------------------- 1 | DROP TABLE t9; 2 | -------------------------------------------------------------------------------- /rusqlite_migration/benches/10_migrations/9-comment/up.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE t9(a, b, c); 2 | -------------------------------------------------------------------------------- /rusqlite_migration/benches/README.md: -------------------------------------------------------------------------------- 1 | # Alpha Benchmarks 2 | 3 | This is a collection of alpha-quality benchmarks. This is a work in progress. They are a bit quirky still and have the following gotchas: 4 | 5 | * Criterion benchmark are quite often noisy and have outliers (even on a quiet computer) 6 | 7 | ## Criterion 8 | 9 | Just run 10 | ``` 11 | cargo bench --bench criterion 12 | ``` 13 | 14 | The migration benchmark use perf counters, so if you get a permission denied error, run (on GNU/Linux): 15 | 16 | ``` bash 17 | echo 1 | sudo tee /proc/sys/kernel/perf_event_paranoid 18 | ``` 19 | 20 | ## Iai 21 | 22 | Just run 23 | ``` 24 | cargo bench --bench iai 25 | ``` 26 | 27 | ## Why Both Criterion and Iai? 28 | 29 | > Comparison with Criterion-rs 30 | > 31 | > I intend Iai to be a complement to Criterion-rs, not a competitor. The two projects measure different things in different ways and have different pros, cons, and limitations, so for most projects the best approach is to use both. 32 | 33 | From https://bheisler.github.io/criterion.rs/book/iai/comparison.html 34 | 35 | -------------------------------------------------------------------------------- /rusqlite_migration/benches/criterion.rs: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | // Copyright Clément Joly and contributors. 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | 16 | //! Benchmarks using criterion 17 | 18 | use criterion::criterion_main; 19 | 20 | #[cfg(feature = "from-directory")] 21 | mod from_directory; 22 | 23 | #[cfg(not(feature = "from-directory"))] 24 | mod from_directory { 25 | pub fn create() { 26 | () 27 | } 28 | } 29 | 30 | criterion_main!(from_directory::create); 31 | -------------------------------------------------------------------------------- /rusqlite_migration/benches/from_directory.rs: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | // Copyright Clément Joly and contributors. 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | 16 | use criterion::{criterion_group, Criterion}; 17 | use include_dir::{include_dir, Dir}; 18 | 19 | use rusqlite_migration::Migrations; 20 | 21 | static MIGRATIONS_DIR_10: Dir = include_dir!("$CARGO_MANIFEST_DIR/benches/10_migrations"); 22 | static MIGRATIONS_DIR_100: Dir = include_dir!("$CARGO_MANIFEST_DIR/benches/100_migrations"); 23 | 24 | // Iai 25 | #[allow(dead_code)] 26 | pub fn small() { 27 | Migrations::from_directory(&MIGRATIONS_DIR_10).unwrap(); 28 | } 29 | 30 | #[allow(dead_code)] 31 | pub fn big() { 32 | Migrations::from_directory(&MIGRATIONS_DIR_100).unwrap(); 33 | } 34 | 35 | // Criterion 36 | #[allow(dead_code)] 37 | pub fn create_bench(c: &mut Criterion) { 38 | c.bench_function("from_directory_small", |b| { 39 | b.iter_with_large_drop(|| Migrations::from_directory(&MIGRATIONS_DIR_10).unwrap()) 40 | }); 41 | 42 | c.bench_function("from_directory_big", |b| { 43 | b.iter_with_large_drop(|| Migrations::from_directory(&MIGRATIONS_DIR_100).unwrap()) 44 | }); 45 | } 46 | 47 | criterion_group!(create, create_bench); 48 | -------------------------------------------------------------------------------- /rusqlite_migration/benches/gen_migrations.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -o errexit 4 | set -o nounset 5 | set -o pipefail 6 | if [[ "${TRACE-0}" == "1" ]]; then 7 | set -o xtrace 8 | fi 9 | if [[ "${1-}" =~ ^-*h(elp)?$ ]]; then 10 | echo 'Usage: ./gen_migrations.sh TODO Implement usage 11 | 12 | This is an awesome bash script to make your life better. 13 | 14 | ' 15 | exit 16 | fi 17 | cd "$(dirname "$0")" 18 | 19 | if test "$1" == ""; then 20 | echo "Please give the directory that will hold migrations as a first argument" 21 | exit 1 22 | fi 23 | 24 | mkdir "$1" 25 | cd "$1" 26 | 27 | if test "$2" == ""; then 28 | echo "Please give the number of migrations as a second argument" 29 | exit 2 30 | fi 31 | nbr_migrations="$2" 32 | 33 | for i in $(seq 1 $nbr_migrations); 34 | do 35 | dir="${i}-comment" 36 | mkdir "$dir" 37 | echo "CREATE TABLE t${i}(a, b, c);" >"$dir"/up.sql 38 | echo "DROP TABLE t${i};" >"$dir"/down.sql 39 | done 40 | -------------------------------------------------------------------------------- /rusqlite_migration/benches/iai.rs: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | // Copyright Clément Joly and contributors. 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | 16 | //! Why criterion and iai? It’s actually recommended: 17 | //! https://bheisler.github.io/criterion.rs/book/iai/comparison.html 18 | 19 | use std::iter::FromIterator; 20 | 21 | use iai::black_box; 22 | use rusqlite::Connection; 23 | use rusqlite_migration::{Migrations, M}; 24 | 25 | fn upward(i: u64) { 26 | let sql_migrations = (0..=i) 27 | .map(|i| { 28 | ( 29 | format!("CREATE TABLE t{i}(a, b, c);"), 30 | format!("DROP TABLE t{i};"), 31 | ) 32 | }) 33 | .collect::>(); 34 | let migrations = 35 | Migrations::from_iter(sql_migrations.iter().enumerate().map(|(i, (up, down))| { 36 | let m = M::up(up).down(down); 37 | if i % 500 == 0 { 38 | m.foreign_key_check() 39 | } else { 40 | m 41 | } 42 | })); 43 | 44 | let mut conn = Connection::open_in_memory().unwrap(); 45 | 46 | migrations.to_latest(&mut conn).unwrap(); 47 | } 48 | 49 | fn upward_migration_short() { 50 | upward(black_box(10)) 51 | } 52 | 53 | fn upward_migration_long() { 54 | upward(black_box(100)) 55 | } 56 | 57 | #[cfg(feature = "from-directory")] 58 | mod from_directory; 59 | 60 | #[cfg(feature = "from-directory")] 61 | fn from_directory_small() { 62 | from_directory::small() 63 | } 64 | 65 | #[cfg(not(feature = "from-directory"))] 66 | fn from_directory_small() { 67 | () 68 | } 69 | 70 | #[cfg(feature = "from-directory")] 71 | fn from_directory_big() { 72 | from_directory::big() 73 | } 74 | 75 | #[cfg(not(feature = "from-directory"))] 76 | fn from_directory_big() { 77 | () 78 | } 79 | 80 | iai::main!( 81 | upward_migration_short, 82 | upward_migration_long, 83 | from_directory_small, 84 | from_directory_big, 85 | ); 86 | -------------------------------------------------------------------------------- /rusqlite_migration/build.rs: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | // Copyright Clément Joly and contributors. 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | 16 | //! Insert the readme as documentation of the crate 17 | 18 | use std::{ 19 | env, 20 | error::Error, 21 | fs::{read_to_string, File}, 22 | io::{self, BufWriter, Write}, 23 | }; 24 | 25 | fn main() -> Result<(), Box> { 26 | let readme_path = env::var("CARGO_PKG_README")?; 27 | println!("cargo:rerun-if-changed={readme_path}"); 28 | 29 | let out_dir = env::var("OUT_DIR")?; 30 | let readme_for_rustdoc = File::create(format!("{out_dir}/readme_for_rustdoc.md"))?; 31 | let mut out = BufWriter::new(readme_for_rustdoc); 32 | 33 | let readme = read_to_string(readme_path)?; 34 | readme 35 | .lines() 36 | .skip_while(|line| line != &"") 37 | .skip(1) // Discard the pattern line 38 | .filter(|line| *line != "") // Known unclosed div because we don’t start from the top 39 | .try_fold(0, |lines_written, line| -> Result { 40 | writeln!(out, "{}", line)?; 41 | Ok(lines_written + 1) 42 | }) 43 | .map(|lines_written| { 44 | println!("Wrote {lines_written} lines from the README to the rustdoc."); 45 | assert!( 46 | lines_written > 70, 47 | "the size of the documentation produced from the README.md file is suspiciously small" 48 | ) 49 | }) 50 | ?; 51 | 52 | Ok(()) 53 | } 54 | -------------------------------------------------------------------------------- /rusqlite_migration/src/builder.rs: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | // Copyright Clément Joly and contributors. 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | 16 | use std::{iter::FromIterator, mem::take}; 17 | 18 | use include_dir::Dir; 19 | 20 | use crate::{loader::from_directory, MigrationHook, Result, M}; 21 | 22 | /// Allows to build a `Vec>` with additional edits. 23 | #[derive(Default, Debug)] 24 | pub struct MigrationsBuilder<'u> { 25 | migrations: Vec>>, 26 | } 27 | 28 | impl<'u> MigrationsBuilder<'u> { 29 | /// Creates a set of migrations from a given directory by scanning subdirectories with a specified name pattern. 30 | /// The migrations are loaded and stored in the binary. 31 | /// 32 | /// See the [`crate::Migrations::from_directory`] method for additional information regarding the directory structure. 33 | /// 34 | /// # Example 35 | /// 36 | /// ``` 37 | /// use rusqlite_migration::{Migrations, MigrationsBuilder}; 38 | /// use include_dir::{Dir, include_dir}; 39 | /// 40 | /// static MIGRATION_DIR: Dir = include_dir!("$CARGO_MANIFEST_DIR/../examples/from-directory/migrations"); 41 | /// let migrations: Migrations = MigrationsBuilder::from_directory(&MIGRATION_DIR).unwrap().finalize(); 42 | /// ``` 43 | /// 44 | /// # Errors 45 | /// 46 | /// Returns [`crate::Error::FileLoad`] in case the subdirectory names are incorrect, 47 | /// or don't contain at least a valid `up.sql` file. 48 | #[cfg_attr(test, mutants::skip)] // Tested at a high level 49 | pub fn from_directory(dir: &'static Dir<'static>) -> Result { 50 | Ok(Self { 51 | migrations: from_directory(dir)?, 52 | }) 53 | } 54 | 55 | /// Allows to edit a migration with a given `id`. 56 | /// 57 | /// # Panics 58 | /// 59 | /// Panics if no migration with the `id` provided exists. 60 | #[must_use] 61 | pub fn edit(mut self, id: usize, f: impl Fn(M) -> M) -> Self { 62 | if id < 1 { 63 | panic!("id cannot be equal to 0"); 64 | } 65 | self.migrations[id - 1] = take(&mut self.migrations[id - 1]).map(f); 66 | self 67 | } 68 | 69 | /// Finalizes the builder and creates a [`crate::Migrations`]. 70 | pub fn finalize(mut self) -> crate::Migrations<'u> { 71 | self.migrations.drain(..).flatten().collect() 72 | } 73 | } 74 | 75 | impl<'u> FromIterator> for MigrationsBuilder<'u> { 76 | fn from_iter>>(iter: T) -> Self { 77 | Self { 78 | migrations: Vec::from_iter(iter.into_iter().map(Some)), 79 | } 80 | } 81 | } 82 | 83 | impl M<'_> { 84 | /// Replace the `up_hook` in the given migration with the provided one. 85 | /// 86 | /// # Warning 87 | /// 88 | /// Use [`M::up_with_hook`] instead if you're creating a new migration. 89 | /// This method is meant for editing existing transactions 90 | /// when using the [`MigrationsBuilder`]. 91 | pub fn set_up_hook(mut self, hook: impl MigrationHook + 'static) -> Self { 92 | self.up_hook = Some(hook.clone_box()); 93 | self 94 | } 95 | 96 | /// Replace the `down_hook` in the given migration with the provided one. 97 | /// 98 | /// # Warning 99 | /// 100 | /// Use [`M::down_with_hook`] instead if you're creating a new migration. 101 | /// This method is meant for editing existing transactions 102 | /// when using the [`MigrationsBuilder`]. 103 | pub fn set_down_hook(mut self, hook: impl MigrationHook + 'static) -> Self { 104 | self.down_hook = Some(hook.clone_box()); 105 | self 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /rusqlite_migration/src/errors.rs: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | // Copyright Clément Joly and contributors. 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | 16 | //! Custom error types 17 | 18 | use std::fmt; 19 | 20 | use crate::SchemaVersion; 21 | 22 | /// A typedef of the result returned by many methods. 23 | pub type Result = std::result::Result; 24 | 25 | /// Enum listing possible errors. 26 | #[derive(Debug)] 27 | #[allow(clippy::enum_variant_names)] 28 | #[non_exhaustive] 29 | pub enum Error { 30 | /// Rusqlite error, query may indicate the attempted SQL query 31 | RusqliteError { 32 | /// SQL query that caused the error 33 | query: String, 34 | /// Error returned by rusqlite 35 | err: rusqlite::Error, 36 | }, 37 | /// Error with the specified schema version 38 | SpecifiedSchemaVersion(SchemaVersionError), 39 | /// Invalid [user version field](https://www.sqlite.org/fileformat.html#user_version_number) in 40 | /// the SQLite database. The field was likely altered by another program or library. 41 | InvalidUserVersion, 42 | /// Something wrong with migration definitions 43 | MigrationDefinition(MigrationDefinitionError), 44 | /// The foreign key check failed 45 | ForeignKeyCheck(Vec), 46 | /// Error returned by the migration hook 47 | Hook(String), 48 | /// Error returned when loading migrations from directory 49 | FileLoad(String), 50 | /// An unknown error occurred. *Note*: such errors are not comparable between one another, 51 | /// much like NaN for floats. 52 | Unrecognized(Box), 53 | } 54 | 55 | impl PartialEq for Error { 56 | fn eq(&self, other: &Self) -> bool { 57 | match (self, other) { 58 | ( 59 | Self::RusqliteError { query: q1, err: e1 }, 60 | Self::RusqliteError { query: q2, err: e2 }, 61 | ) => q1 == q2 && e1 == e2, 62 | (Self::SpecifiedSchemaVersion(a), Self::SpecifiedSchemaVersion(b)) => a == b, 63 | (Self::MigrationDefinition(a), Self::MigrationDefinition(b)) => a == b, 64 | (Self::ForeignKeyCheck(e1), Self::ForeignKeyCheck(e2)) => e1 == e2, 65 | (Self::Hook(a), Self::Hook(b)) => a == b, 66 | (Self::FileLoad(a), Self::FileLoad(b)) => a == b, 67 | // This makes Unrecognized errors behave like NaN (where NaN != NaN) 68 | (Self::Unrecognized(_), Self::Unrecognized(_)) => false, 69 | // Fallback to comparing enum variants 70 | _ => core::mem::discriminant(self) == core::mem::discriminant(other), 71 | } 72 | } 73 | } 74 | 75 | impl Error { 76 | /// Associate the SQL request that caused the error 77 | #[must_use] 78 | pub fn with_sql(e: rusqlite::Error, sql: &str) -> Error { 79 | Error::RusqliteError { 80 | query: String::from(sql), 81 | err: e, 82 | } 83 | } 84 | } 85 | 86 | impl fmt::Display for Error { 87 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 88 | match self { 89 | Error::SpecifiedSchemaVersion(e) => write!(f, "rusqlite_migrate error: {e}"), 90 | // TODO Format the error with fmt instead of debug 91 | _ => write!(f, "rusqlite_migrate error: {self:?}"), 92 | } 93 | } 94 | } 95 | 96 | impl std::error::Error for Error { 97 | fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { 98 | match self { 99 | Error::RusqliteError { query: _, err } => Some(err), 100 | Error::SpecifiedSchemaVersion(e) => Some(e), 101 | Error::MigrationDefinition(e) => Some(e), 102 | Error::ForeignKeyCheck(vec) => Some(vec.first()?), 103 | Error::Unrecognized(ref e) => Some(&**e), 104 | Error::Hook(_) | Error::FileLoad(_) | Error::InvalidUserVersion => None, 105 | } 106 | } 107 | } 108 | 109 | impl From for Error { 110 | fn from(e: rusqlite::Error) -> Error { 111 | Error::RusqliteError { 112 | query: String::new(), 113 | err: e, 114 | } 115 | } 116 | } 117 | 118 | /// Errors related to schema versions 119 | #[derive(Debug, PartialEq, Eq, Clone, Copy)] 120 | #[allow(clippy::enum_variant_names)] 121 | #[non_exhaustive] 122 | pub enum SchemaVersionError { 123 | /// Attempt to migrate to a version out of range for the supplied migrations 124 | TargetVersionOutOfRange { 125 | /// The attempt to migrate to this version caused the error 126 | specified: SchemaVersion, 127 | /// Highest version defined in the migration set 128 | highest: SchemaVersion, 129 | }, 130 | /// Schema version is so high that it is unsupported (higher than [`crate::MIGRATIONS_MAX`]) 131 | TooHigh, 132 | } 133 | 134 | impl fmt::Display for SchemaVersionError { 135 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 136 | match self { 137 | SchemaVersionError::TargetVersionOutOfRange { specified, highest } => { 138 | write!(f, "Attempt to migrate to version {specified}, which is higher than the highest version currently supported, {highest}.") 139 | } 140 | SchemaVersionError::TooHigh => { 141 | write!(f, "Attempt to use a schema version higher than supported.") 142 | } 143 | } 144 | } 145 | } 146 | 147 | impl std::error::Error for SchemaVersionError {} 148 | 149 | /// Errors related to schema versions 150 | #[derive(Debug, PartialEq, Eq, Clone, Copy)] 151 | #[allow(clippy::enum_variant_names)] 152 | #[non_exhaustive] 153 | pub enum MigrationDefinitionError { 154 | /// Migration has no down version 155 | DownNotDefined { 156 | /// Index of the migration that caused the error 157 | migration_index: usize, 158 | }, 159 | /// Attempt to migrate when no migrations are defined 160 | NoMigrationsDefined, 161 | /// Attempt to migrate when the database is currently at a higher migration level (see ) 162 | DatabaseTooFarAhead, 163 | } 164 | 165 | impl fmt::Display for MigrationDefinitionError { 166 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 167 | match self { 168 | MigrationDefinitionError::DownNotDefined { migration_index } => { 169 | write!( 170 | f, 171 | "Migration {} (version {} -> {}) cannot be reverted", 172 | migration_index, 173 | migration_index, 174 | migration_index + 1 175 | ) 176 | } 177 | MigrationDefinitionError::NoMigrationsDefined => { 178 | write!(f, "Attempt to migrate with no migrations defined") 179 | } 180 | MigrationDefinitionError::DatabaseTooFarAhead => { 181 | write!( 182 | f, 183 | "Attempt to migrate a database with a migration number that is too high" 184 | ) 185 | } 186 | } 187 | } 188 | } 189 | 190 | impl std::error::Error for MigrationDefinitionError {} 191 | 192 | /// Error caused by a foreign key check 193 | #[derive(Debug, PartialEq, Eq, Clone)] 194 | pub struct ForeignKeyCheckError { 195 | pub(super) table: String, 196 | pub(super) rowid: i64, 197 | pub(super) parent: String, 198 | pub(super) fkid: i64, 199 | } 200 | 201 | impl fmt::Display for ForeignKeyCheckError { 202 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 203 | write!( 204 | f, 205 | "Foreign key check found row with id {} in table '{}' missing from table '{}' \ 206 | but required by foreign key with id {}", 207 | self.rowid, self.table, self.parent, self.fkid 208 | ) 209 | } 210 | } 211 | 212 | impl std::error::Error for ForeignKeyCheckError {} 213 | 214 | /// Error enum with rusqlite or hook-specified errors. 215 | #[derive(Debug, PartialEq)] 216 | #[allow(clippy::enum_variant_names)] 217 | #[non_exhaustive] 218 | pub enum HookError { 219 | /// Rusqlite error, query may indicate the attempted SQL query 220 | RusqliteError(rusqlite::Error), 221 | /// Error returned by the hook 222 | Hook(String), 223 | } 224 | 225 | impl From for HookError { 226 | fn from(e: rusqlite::Error) -> HookError { 227 | HookError::RusqliteError(e) 228 | } 229 | } 230 | 231 | impl From for Error { 232 | fn from(e: HookError) -> Error { 233 | match e { 234 | HookError::RusqliteError(err) => Error::with_sql(err, ""), 235 | HookError::Hook(s) => Error::Hook(s), 236 | } 237 | } 238 | } 239 | 240 | /// A typedef of the result returned by hooks. 241 | pub type HookResult = std::result::Result<(), E>; 242 | 243 | #[cfg(test)] 244 | mod tests; 245 | -------------------------------------------------------------------------------- /rusqlite_migration/src/errors/snapshots/rusqlite_migration__errors__tests__error_display__file_load.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: rusqlite_migration/src/errors/tests.rs 3 | expression: e 4 | snapshot_kind: text 5 | --- 6 | rusqlite_migrate error: FileLoad("file causing problem") 7 | -------------------------------------------------------------------------------- /rusqlite_migration/src/errors/snapshots/rusqlite_migration__errors__tests__error_display__foreign_key_check.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: rusqlite_migration/src/errors/tests.rs 3 | expression: e 4 | snapshot_kind: text 5 | --- 6 | rusqlite_migrate error: ForeignKeyCheck([ForeignKeyCheckError { table: "t1", rowid: 1, parent: "t2", fkid: 2 }, ForeignKeyCheckError { table: "t3", rowid: 2, parent: "t4", fkid: 3 }]) 7 | -------------------------------------------------------------------------------- /rusqlite_migration/src/errors/snapshots/rusqlite_migration__errors__tests__error_display__hook.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: rusqlite_migration/src/errors/tests.rs 3 | expression: e 4 | snapshot_kind: text 5 | --- 6 | rusqlite_migrate error: Hook("in hook") 7 | -------------------------------------------------------------------------------- /rusqlite_migration/src/errors/snapshots/rusqlite_migration__errors__tests__error_display__invalid_user_version.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: rusqlite_migration/src/errors/tests.rs 3 | expression: e 4 | snapshot_kind: text 5 | --- 6 | rusqlite_migrate error: InvalidUserVersion 7 | -------------------------------------------------------------------------------- /rusqlite_migration/src/errors/snapshots/rusqlite_migration__errors__tests__error_display__migration_definition.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: rusqlite_migration/src/errors/tests.rs 3 | expression: e 4 | snapshot_kind: text 5 | --- 6 | rusqlite_migrate error: MigrationDefinition(NoMigrationsDefined) 7 | -------------------------------------------------------------------------------- /rusqlite_migration/src/errors/snapshots/rusqlite_migration__errors__tests__error_display__rusqlite_error.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: rusqlite_migration/src/errors/tests.rs 3 | expression: e 4 | snapshot_kind: text 5 | --- 6 | rusqlite_migrate error: RusqliteError { query: "SELECT * FROM table42;", err: InvalidQuery } 7 | -------------------------------------------------------------------------------- /rusqlite_migration/src/errors/snapshots/rusqlite_migration__errors__tests__error_display__specified_schema_version.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: rusqlite_migration/src/errors/tests.rs 3 | expression: e 4 | snapshot_kind: text 5 | --- 6 | rusqlite_migrate error: Attempt to migrate to version 0 (no version set), which is higher than the highest version currently supported, 0 (no version set). 7 | -------------------------------------------------------------------------------- /rusqlite_migration/src/errors/snapshots/rusqlite_migration__errors__tests__error_display__too_high_schema_version.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: rusqlite_migration/src/errors/tests.rs 3 | expression: e 4 | snapshot_kind: text 5 | --- 6 | rusqlite_migrate error: Attempt to use a schema version higher than supported. 7 | -------------------------------------------------------------------------------- /rusqlite_migration/src/errors/snapshots/rusqlite_migration__errors__tests__error_display__unrecognized.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: rusqlite_migration/src/errors/tests.rs 3 | expression: e 4 | snapshot_kind: text 5 | --- 6 | rusqlite_migrate error: Unrecognized(Hook("unknown")) 7 | -------------------------------------------------------------------------------- /rusqlite_migration/src/errors/snapshots/rusqlite_migration__errors__tests__error_source_number_file_load.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: rusqlite_migration/src/errors/tests.rs 3 | expression: e.source() 4 | snapshot_kind: text 5 | --- 6 | None 7 | -------------------------------------------------------------------------------- /rusqlite_migration/src/errors/snapshots/rusqlite_migration__errors__tests__error_source_number_foreign_key_check.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: rusqlite_migration/src/errors/tests.rs 3 | expression: e.source() 4 | snapshot_kind: text 5 | --- 6 | Some( 7 | ForeignKeyCheckError { 8 | table: "t1", 9 | rowid: 1, 10 | parent: "t2", 11 | fkid: 2, 12 | }, 13 | ) 14 | -------------------------------------------------------------------------------- /rusqlite_migration/src/errors/snapshots/rusqlite_migration__errors__tests__error_source_number_hook.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: rusqlite_migration/src/errors/tests.rs 3 | expression: e.source() 4 | snapshot_kind: text 5 | --- 6 | None 7 | -------------------------------------------------------------------------------- /rusqlite_migration/src/errors/snapshots/rusqlite_migration__errors__tests__error_source_number_invalid_user_version.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: rusqlite_migration/src/errors/tests.rs 3 | expression: e.source() 4 | snapshot_kind: text 5 | --- 6 | None 7 | -------------------------------------------------------------------------------- /rusqlite_migration/src/errors/snapshots/rusqlite_migration__errors__tests__error_source_number_migration_definition.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: rusqlite_migration/src/errors/tests.rs 3 | expression: e.source() 4 | snapshot_kind: text 5 | --- 6 | Some( 7 | NoMigrationsDefined, 8 | ) 9 | -------------------------------------------------------------------------------- /rusqlite_migration/src/errors/snapshots/rusqlite_migration__errors__tests__error_source_number_rusqlite_error.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: rusqlite_migration/src/errors/tests.rs 3 | expression: e.source() 4 | snapshot_kind: text 5 | --- 6 | Some( 7 | InvalidQuery, 8 | ) 9 | -------------------------------------------------------------------------------- /rusqlite_migration/src/errors/snapshots/rusqlite_migration__errors__tests__error_source_number_specified_schema_version.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: rusqlite_migration/src/errors/tests.rs 3 | expression: e.source() 4 | snapshot_kind: text 5 | --- 6 | Some( 7 | TargetVersionOutOfRange { 8 | specified: NoneSet, 9 | highest: NoneSet, 10 | }, 11 | ) 12 | -------------------------------------------------------------------------------- /rusqlite_migration/src/errors/snapshots/rusqlite_migration__errors__tests__error_source_number_too_high_schema_version.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: rusqlite_migration/src/errors/tests.rs 3 | expression: e.source() 4 | snapshot_kind: text 5 | --- 6 | Some( 7 | TooHigh, 8 | ) 9 | -------------------------------------------------------------------------------- /rusqlite_migration/src/errors/snapshots/rusqlite_migration__errors__tests__error_source_number_unrecognized.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: rusqlite_migration/src/errors/tests.rs 3 | expression: e.source() 4 | snapshot_kind: text 5 | --- 6 | Some( 7 | Hook( 8 | "unknown", 9 | ), 10 | ) 11 | -------------------------------------------------------------------------------- /rusqlite_migration/src/errors/tests.rs: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | // Copyright Clément Joly and contributors. 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | 16 | use std::num::NonZeroUsize; 17 | 18 | use super::*; 19 | 20 | fn all_errors() -> Vec<(&'static str, crate::Error)> { 21 | use crate::Error::*; 22 | use crate::ForeignKeyCheckError; 23 | use crate::MigrationDefinitionError; 24 | use crate::SchemaVersion; 25 | use crate::SchemaVersionError; 26 | 27 | vec![ 28 | ( 29 | "rusqlite_error", 30 | RusqliteError { 31 | query: "SELECT * FROM table42;".to_owned(), 32 | err: rusqlite::Error::InvalidQuery, 33 | }, 34 | ), 35 | ( 36 | "specified_schema_version", 37 | SpecifiedSchemaVersion(SchemaVersionError::TargetVersionOutOfRange { 38 | specified: SchemaVersion::NoneSet, 39 | highest: SchemaVersion::NoneSet, 40 | }), 41 | ), 42 | ( 43 | "too_high_schema_version", 44 | SpecifiedSchemaVersion(SchemaVersionError::TooHigh), 45 | ), 46 | ("invalid_user_version", InvalidUserVersion), 47 | ( 48 | "migration_definition", 49 | MigrationDefinition(MigrationDefinitionError::NoMigrationsDefined), 50 | ), 51 | ( 52 | "foreign_key_check", 53 | ForeignKeyCheck(vec![ 54 | ForeignKeyCheckError { 55 | table: "t1".to_owned(), 56 | rowid: 1, 57 | parent: "t2".to_owned(), 58 | fkid: 2, 59 | }, 60 | ForeignKeyCheckError { 61 | table: "t3".to_owned(), 62 | rowid: 2, 63 | parent: "t4".to_owned(), 64 | fkid: 3, 65 | }, 66 | ]), 67 | ), 68 | ("hook", Hook("in hook".to_owned())), 69 | ("file_load", FileLoad("file causing problem".to_owned())), 70 | ( 71 | "unrecognized", 72 | Unrecognized(Box::new(Hook("unknown".to_owned()))), 73 | ), 74 | ] 75 | } 76 | 77 | // We should be able to convert rusqlite errors transparently 78 | #[test] 79 | fn test_rusqlite_error_conversion() { 80 | assert!(matches!( 81 | Error::from(rusqlite::Error::MultipleStatement), 82 | Error::RusqliteError { query: _, err: _ } 83 | )); 84 | 85 | let hook_error = HookError::from(rusqlite::Error::MultipleStatement); 86 | assert!(matches!(&hook_error, &HookError::RusqliteError(_))); 87 | assert!(matches!( 88 | Error::from(hook_error), 89 | Error::RusqliteError { query: _, err: _ }, 90 | )); 91 | } 92 | 93 | // Check that Unrecognized errors correctly implement PartialEq, namely that 94 | // > a != b if and only if !(a == b). 95 | // from https://doc.rust-lang.org/std/cmp/trait.PartialEq.html 96 | #[test] 97 | fn test_unrecognized_errors() { 98 | let u1 = Error::Unrecognized(Box::new(Error::Hook(String::new()))); 99 | let u2 = Error::Unrecognized(Box::new(Error::Hook(String::new()))); 100 | let u3 = Error::Unrecognized(Box::new(Error::Hook("1".to_owned()))); 101 | let u4 = Error::Unrecognized(Box::new(Error::Hook(String::new()))); 102 | let u5 = Error::FileLoad("1".to_owned()); 103 | let u6 = Error::Unrecognized(Box::new(Error::Hook(String::new()))); 104 | 105 | for (e1, e2) in &[(u1, u2), (u3, u4), (u5, u6)] { 106 | assert!(e1 != e2); 107 | assert!(!(e1 == e2)); 108 | } 109 | } 110 | 111 | #[test] 112 | // Errors on specified schema versions should be equal if and only if all versions are 113 | // equal 114 | fn test_specified_schema_version_error() { 115 | assert_eq!( 116 | Error::SpecifiedSchemaVersion(SchemaVersionError::TargetVersionOutOfRange { 117 | specified: SchemaVersion::Outside(NonZeroUsize::new(10).unwrap()), 118 | highest: SchemaVersion::Inside(NonZeroUsize::new(4).unwrap()), 119 | }), 120 | Error::SpecifiedSchemaVersion(SchemaVersionError::TargetVersionOutOfRange { 121 | specified: SchemaVersion::Outside(NonZeroUsize::new(10).unwrap()), 122 | highest: SchemaVersion::Inside(NonZeroUsize::new(4).unwrap()), 123 | }), 124 | ); 125 | assert_ne!( 126 | Error::SpecifiedSchemaVersion(SchemaVersionError::TargetVersionOutOfRange { 127 | specified: SchemaVersion::Outside(NonZeroUsize::new(9).unwrap()), 128 | highest: SchemaVersion::Inside(NonZeroUsize::new(4).unwrap()), 129 | }), 130 | Error::SpecifiedSchemaVersion(SchemaVersionError::TargetVersionOutOfRange { 131 | specified: SchemaVersion::Outside(NonZeroUsize::new(10).unwrap()), 132 | highest: SchemaVersion::Inside(NonZeroUsize::new(4).unwrap()), 133 | }), 134 | ); 135 | } 136 | 137 | // Two errors with different queries or errors should be considered different 138 | #[test] 139 | fn test_rusqlite_error_query() { 140 | assert_ne!( 141 | Error::RusqliteError { 142 | query: "SELECTTT".to_owned(), 143 | err: rusqlite::Error::InvalidQuery 144 | }, 145 | Error::RusqliteError { 146 | query: "SSSELECT".to_owned(), 147 | err: rusqlite::Error::InvalidQuery 148 | } 149 | ); 150 | assert_ne!( 151 | Error::RusqliteError { 152 | query: "SELECT".to_owned(), 153 | err: rusqlite::Error::MultipleStatement 154 | }, 155 | Error::RusqliteError { 156 | query: "SELECT".to_owned(), 157 | err: rusqlite::Error::InvalidQuery 158 | } 159 | ) 160 | } 161 | 162 | // Two errors with different file load errors should be considered different 163 | #[test] 164 | fn test_rusqlite_error_file_load() { 165 | assert_ne!( 166 | Error::FileLoad("s1".to_owned()), 167 | Error::FileLoad("s2".to_owned()) 168 | ) 169 | } 170 | 171 | // Two errors with different foreign key checks should be considered different 172 | #[test] 173 | fn test_rusqlite_error_fkc() { 174 | assert_ne!( 175 | Error::ForeignKeyCheck(vec![ForeignKeyCheckError { 176 | table: "t1".to_owned(), 177 | rowid: 1, 178 | parent: "t2".to_owned(), 179 | fkid: 3 180 | }]), 181 | Error::ForeignKeyCheck(vec![ForeignKeyCheckError { 182 | table: "t1".to_owned(), 183 | rowid: 3, 184 | parent: "t2".to_owned(), 185 | fkid: 3 186 | }]), 187 | ) 188 | } 189 | 190 | // Hook error conversion preserves the message 191 | #[test] 192 | fn test_hook_conversion_msg() { 193 | let msg = String::from("some error encountered in the hook"); 194 | let hook_error = HookError::Hook(msg.clone()); 195 | 196 | assert_eq!(Error::from(hook_error), Error::Hook(msg)) 197 | } 198 | 199 | #[test] 200 | fn test_schema_version_error_display() { 201 | let err = SchemaVersionError::TargetVersionOutOfRange { 202 | specified: SchemaVersion::NoneSet, 203 | highest: SchemaVersion::NoneSet, 204 | }; 205 | assert_eq!("Attempt to migrate to version 0 (no version set), which is higher than the highest version currently supported, 0 (no version set).", format!("{err}")) 206 | } 207 | 208 | #[test] 209 | fn test_foreign_key_check_error_display() { 210 | let err = ForeignKeyCheckError { 211 | table: "a".to_string(), 212 | rowid: 1, 213 | parent: "b".to_string(), 214 | fkid: 2, 215 | }; 216 | assert_eq!("Foreign key check found row with id 1 in table 'a' missing from table 'b' but required by foreign key with id 2", format!("{err}")) 217 | } 218 | 219 | #[test] 220 | fn test_migration_definition_error_display() { 221 | let err = MigrationDefinitionError::DownNotDefined { migration_index: 1 }; 222 | assert_eq!( 223 | "Migration 1 (version 1 -> 2) cannot be reverted", 224 | format!("{err}") 225 | ); 226 | 227 | let err = MigrationDefinitionError::DatabaseTooFarAhead; 228 | assert_eq!( 229 | "Attempt to migrate a database with a migration number that is too high", 230 | format!("{err}") 231 | ); 232 | 233 | let err = MigrationDefinitionError::NoMigrationsDefined; 234 | assert_eq!( 235 | "Attempt to migrate with no migrations defined", 236 | format!("{err}") 237 | ) 238 | } 239 | 240 | #[test] 241 | fn test_error_display() { 242 | for (name, e) in all_errors() { 243 | insta::assert_snapshot!(format!("error_display__{name}"), e); 244 | } 245 | } 246 | 247 | #[test] 248 | fn test_error_source() { 249 | use std::error::Error; 250 | 251 | for (name, e) in all_errors() { 252 | // For API stability reasons (if that changes, we must change the major version) 253 | insta::assert_debug_snapshot!(format!("error_source_number_{name}"), e.source()); 254 | } 255 | } 256 | 257 | #[test] 258 | fn schema_version_partial_display_test() { 259 | assert_eq!("0 (no version set)", format!("{}", SchemaVersion::NoneSet)); 260 | assert_eq!( 261 | "1 (inside)", 262 | format!("{}", SchemaVersion::Inside(NonZeroUsize::new(1).unwrap())) 263 | ); 264 | assert_eq!( 265 | "32 (inside)", 266 | format!("{}", SchemaVersion::Inside(NonZeroUsize::new(32).unwrap())) 267 | ); 268 | assert_eq!( 269 | "1 (outside)", 270 | format!("{}", SchemaVersion::Outside(NonZeroUsize::new(1).unwrap())) 271 | ); 272 | assert_eq!( 273 | "32 (outside)", 274 | format!("{}", SchemaVersion::Outside(NonZeroUsize::new(32).unwrap())) 275 | ); 276 | } 277 | 278 | #[test] 279 | fn error_test_source() { 280 | let err = Error::RusqliteError { 281 | query: String::new(), 282 | err: rusqlite::Error::InvalidQuery, 283 | }; 284 | assert_eq!( 285 | std::error::Error::source(&err) 286 | .and_then(|e| e.downcast_ref::()) 287 | .unwrap(), 288 | &rusqlite::Error::InvalidQuery 289 | ); 290 | 291 | let err = Error::SpecifiedSchemaVersion(SchemaVersionError::TargetVersionOutOfRange { 292 | specified: SchemaVersion::NoneSet, 293 | highest: SchemaVersion::NoneSet, 294 | }); 295 | assert_eq!( 296 | std::error::Error::source(&err) 297 | .and_then(|e| e.downcast_ref::()) 298 | .unwrap(), 299 | &SchemaVersionError::TargetVersionOutOfRange { 300 | specified: SchemaVersion::NoneSet, 301 | highest: SchemaVersion::NoneSet 302 | } 303 | ); 304 | 305 | let err = Error::MigrationDefinition(MigrationDefinitionError::NoMigrationsDefined); 306 | assert_eq!( 307 | std::error::Error::source(&err) 308 | .and_then(|e| e.downcast_ref::()) 309 | .unwrap(), 310 | &MigrationDefinitionError::NoMigrationsDefined 311 | ); 312 | 313 | let err = Error::ForeignKeyCheck(vec![ForeignKeyCheckError { 314 | table: String::new(), 315 | rowid: 1i64, 316 | parent: String::new(), 317 | fkid: 1i64, 318 | }]); 319 | assert_eq!( 320 | std::error::Error::source(&err) 321 | .and_then(|e| e.downcast_ref::()) 322 | .unwrap(), 323 | &ForeignKeyCheckError { 324 | table: String::new(), 325 | rowid: 1i64, 326 | parent: String::new(), 327 | fkid: 1i64, 328 | } 329 | ); 330 | 331 | let err = Error::Hook(String::new()); 332 | assert!(std::error::Error::source(&err).is_none()); 333 | 334 | let err = Error::FileLoad(String::new()); 335 | assert!(std::error::Error::source(&err).is_none()); 336 | } 337 | 338 | #[test] 339 | // Test cases where enum comparison is not enouh for error equality 340 | fn test_migration_enum_eq_not_enough() { 341 | let mde1 = MigrationDefinitionError::DownNotDefined { migration_index: 3 }; 342 | let mde2 = MigrationDefinitionError::DownNotDefined { migration_index: 5 }; 343 | assert_ne!( 344 | Error::MigrationDefinition(mde1), 345 | Error::MigrationDefinition(mde2), 346 | ); 347 | 348 | let fce = ForeignKeyCheckError { 349 | table: String::from("t1"), 350 | rowid: 23, 351 | parent: String::from("t2"), 352 | fkid: 42, 353 | }; 354 | assert_eq!( 355 | Error::ForeignKeyCheck(vec![fce.clone()]), 356 | Error::ForeignKeyCheck(vec![fce.clone()]) 357 | ); 358 | assert_ne!( 359 | Error::ForeignKeyCheck(vec![fce.clone()]), 360 | Error::ForeignKeyCheck(vec![ForeignKeyCheckError { 361 | fkid: 32, 362 | ..fce.clone() 363 | }]) 364 | ); 365 | assert_ne!( 366 | Error::ForeignKeyCheck(vec![fce.clone()]), 367 | Error::ForeignKeyCheck(vec![]) 368 | ); 369 | 370 | assert_ne!( 371 | Error::Hook(String::from("auie")), 372 | Error::Hook(String::default()) 373 | ) 374 | } 375 | -------------------------------------------------------------------------------- /rusqlite_migration/src/loader.rs: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | // Copyright Clément Joly and contributors. 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | 16 | use std::{convert::TryFrom, num::NonZeroUsize}; 17 | 18 | use crate::{Error, Result, M}; 19 | use include_dir::Dir; 20 | 21 | #[derive(Debug, Clone)] 22 | struct MigrationFile { 23 | id: NonZeroUsize, 24 | name: &'static str, 25 | up: &'static str, 26 | down: Option<&'static str>, 27 | } 28 | 29 | fn get_name(value: &'static Dir<'static>) -> Result<&'static str> { 30 | value 31 | .path() 32 | .file_name() 33 | .and_then(|name| name.to_str()) 34 | .ok_or(Error::FileLoad(format!( 35 | "Could not extract file name from {:?}", 36 | value.path() 37 | ))) 38 | } 39 | 40 | #[cfg_attr(test, mutants::skip)] // Tested at a high level 41 | fn get_migrations( 42 | name: &'static str, 43 | value: &'static Dir<'static>, 44 | ) -> Result<(&'static str, Option<&'static str>)> { 45 | let up = value 46 | .files() 47 | .find(|f| f.path().ends_with("up.sql")) 48 | .ok_or(Error::FileLoad(format!( 49 | "Missing upward migration file for migration {name}" 50 | )))? 51 | .contents_utf8() 52 | .ok_or(Error::FileLoad(format!( 53 | "Could not load contents from {name}/up.sql" 54 | )))?; 55 | 56 | let down = value 57 | .files() 58 | .find(|f| f.path().ends_with("down.sql")) 59 | .map(|down| { 60 | down.contents_utf8().ok_or(Error::FileLoad(format!( 61 | "Could not load contents from {name}/down.sql" 62 | ))) 63 | }) 64 | .transpose()?; 65 | 66 | Ok((up, down)) 67 | } 68 | 69 | fn get_id(file_name: &'static str) -> Result { 70 | file_name 71 | .split_once('-') 72 | .ok_or(Error::FileLoad(format!( 73 | "Could not extract migration id from file name {file_name}" 74 | )))? 75 | .0 76 | .parse::() 77 | .map_err(|e| { 78 | Error::FileLoad(format!( 79 | "Could not parse migration id from file name {file_name} as usize: {e}" 80 | )) 81 | }) 82 | .and_then(|v| { 83 | NonZeroUsize::new(v).ok_or(Error::FileLoad(format!( 84 | "{file_name} has an incorrect migration id: migration id cannot be 0" 85 | ))) 86 | }) 87 | } 88 | 89 | impl TryFrom<&'static Dir<'static>> for MigrationFile { 90 | type Error = Error; 91 | 92 | fn try_from(value: &'static Dir<'static>) -> std::result::Result { 93 | let name = get_name(value)?; 94 | let (up, down) = get_migrations(name, value)?; 95 | let id = get_id(name)?; 96 | 97 | Ok(MigrationFile { id, name, up, down }) 98 | } 99 | } 100 | 101 | impl From<&MigrationFile> for M<'_> { 102 | fn from(value: &MigrationFile) -> Self { 103 | M::up(value.up) 104 | .comment(value.name) 105 | .down(value.down.unwrap_or_default()) 106 | } 107 | } 108 | 109 | #[cfg_attr(test, mutants::skip)] // Tested at a high level 110 | pub(crate) fn from_directory(dir: &'static Dir<'static>) -> Result>>> { 111 | let mut migrations: Vec> = vec![None; dir.dirs().count()]; 112 | 113 | for dir in dir.dirs() { 114 | let migration_file = MigrationFile::try_from(dir)?; 115 | 116 | let id = usize::from(migration_file.id) - 1; 117 | 118 | if migrations.len() <= id { 119 | return Err(Error::FileLoad( 120 | "Migration ids must be consecutive numbers".to_string(), 121 | )); 122 | } 123 | 124 | if migrations[id].is_some() { 125 | return Err(Error::FileLoad(format!( 126 | "Multiple migrations detected for migration id: {}", 127 | migration_file.id 128 | ))); 129 | } 130 | 131 | migrations[id] = Some((&migration_file).into()); 132 | } 133 | 134 | if migrations.iter().all(|m| m.is_none()) { 135 | return Err(Error::FileLoad( 136 | "Directory does not contain any migration files".to_string(), 137 | )); 138 | } 139 | 140 | if migrations.iter().any(|m| m.is_none()) { 141 | return Err(Error::FileLoad( 142 | "Migration ids must be consecutive numbers".to_string(), 143 | )); 144 | } 145 | 146 | // The values are returned in the order of the keys, i.e. of IDs 147 | Ok(migrations) 148 | } 149 | -------------------------------------------------------------------------------- /rusqlite_migration/src/tests/builder.rs: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | // Copyright Clément Joly and contributors. 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | 16 | use std::{iter::FromIterator, num::NonZeroUsize}; 17 | 18 | use rusqlite::Connection; 19 | 20 | use crate::{Migrations, MigrationsBuilder, SchemaVersion, M}; 21 | 22 | #[test] 23 | #[should_panic] 24 | fn test_non_existing_index() { 25 | let ms = vec![M::up("CREATE TABLE t(a);")]; 26 | 27 | let _ = MigrationsBuilder::from_iter(ms.clone()).edit(100, move |m| m); 28 | } 29 | 30 | #[test] 31 | #[should_panic] 32 | fn test_0_index() { 33 | let ms = vec![M::up("CREATE TABLE t(a);")]; 34 | 35 | let _ = MigrationsBuilder::from_iter(ms).edit(0, move |m| m); 36 | } 37 | 38 | #[test] 39 | fn test_valid_index() { 40 | let ms = vec![M::up("CREATE TABLE t1(a);"), M::up("CREATE TABLE t2(a);")]; 41 | 42 | insta::assert_debug_snapshot!(MigrationsBuilder::from_iter(ms) 43 | .edit(1, move |m| m.down("DROP TABLE t1;")) 44 | .edit(2, move |m| m.down("DROP TABLE t2;")) 45 | .finalize()); 46 | } 47 | 48 | #[test] 49 | fn test_len_builder() { 50 | let mut conn = Connection::open_in_memory().unwrap(); 51 | // Define migrations 52 | let ms = vec![ 53 | M::up("CREATE TABLE friend(name TEXT);"), 54 | M::up("ALTER TABLE friend ADD COLUMN birthday TEXT;"), 55 | ]; 56 | 57 | { 58 | let builder = MigrationsBuilder::from_iter(ms); 59 | 60 | let migrations: Migrations = builder.finalize(); 61 | 62 | migrations.to_latest(&mut conn).unwrap(); 63 | 64 | insta::assert_debug_snapshot!(migrations); 65 | assert_eq!(migrations.ms.len(), 2); 66 | assert_eq!( 67 | Ok(SchemaVersion::Inside(NonZeroUsize::new(2).unwrap())), 68 | migrations.current_version(&conn) 69 | ); 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /rusqlite_migration/src/tests/helpers.rs: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | // Copyright Clément Joly and contributors. 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | 16 | use crate::M; 17 | 18 | use rusqlite::Connection; 19 | 20 | /// Attempt to set the user version in the whole possible range of values (even negative ones or 21 | /// values that don’t fit in the underlying 4 bytes field) 22 | pub fn raw_set_user_version(conn: &mut Connection, version: isize) { 23 | conn.pragma_update(None, "user_version", version).unwrap() 24 | } 25 | 26 | pub fn m_valid0_up() -> M<'static> { 27 | M::up("CREATE TABLE m1(a, b); CREATE TABLE m2(a, b, c);") 28 | } 29 | pub fn m_valid0_down() -> M<'static> { 30 | m_valid0_up().down("DROP TABLE m1; DROP TABLE m2;") 31 | } 32 | pub fn m_valid10_up() -> M<'static> { 33 | M::up("CREATE TABLE t1(a, b);") 34 | } 35 | pub fn m_valid10_down() -> M<'static> { 36 | m_valid10_up().down("DROP TABLE t1;") 37 | } 38 | 39 | pub fn m_valid11_down() -> M<'static> { 40 | m_valid11_up().down("ALTER TABLE t1 RENAME COLUMN c TO b;") 41 | } 42 | 43 | pub fn m_valid20_down() -> M<'static> { 44 | m_valid20_up().down("DROP TABLE t2;") 45 | } 46 | 47 | pub fn m_valid21_down() -> M<'static> { 48 | m_valid21_up().down("ALTER TABLE t2 DROP COLUMN a;") 49 | } 50 | pub fn m_valid11_up() -> M<'static> { 51 | M::up("ALTER TABLE t1 RENAME COLUMN b TO c;") 52 | } 53 | pub fn m_valid20_up() -> M<'static> { 54 | M::up("CREATE TABLE t2(b);") 55 | } 56 | pub fn m_valid21_up() -> M<'static> { 57 | M::up("ALTER TABLE t2 ADD COLUMN a;") 58 | } 59 | 60 | pub fn m_valid_fk_up() -> M<'static> { 61 | M::up( 62 | r#" 63 | CREATE TABLE fk1(a PRIMARY KEY); 64 | CREATE TABLE fk2( 65 | a, 66 | FOREIGN KEY(a) REFERENCES fk1(a) 67 | ); 68 | INSERT INTO fk1 (a) VALUES ('foo'); 69 | INSERT INTO fk2 (a) VALUES ('foo'); 70 | "#, 71 | ) 72 | .foreign_key_check() 73 | } 74 | 75 | pub fn m_valid_fk_down() -> M<'static> { 76 | m_valid_fk_up().down("DELETE FROM fk2; DELETE FROM fk1; DROP TABLE fk2; DROP TABLE fk1;") 77 | } 78 | 79 | // All valid upward Ms in the right order 80 | pub fn all_valid_up() -> Vec> { 81 | vec![ 82 | m_valid0_up(), 83 | m_valid10_up(), 84 | m_valid11_up(), 85 | m_valid20_up(), 86 | m_valid21_up(), 87 | m_valid_fk_up(), 88 | ] 89 | } 90 | 91 | // All valid Ms in the right order 92 | pub fn all_valid_down() -> Vec> { 93 | vec![ 94 | m_valid0_down(), 95 | m_valid10_down(), 96 | m_valid11_down(), 97 | m_valid20_down(), 98 | m_valid21_down(), 99 | m_valid_fk_down(), 100 | ] 101 | } 102 | 103 | #[test] 104 | fn all_valid_consistent() { 105 | let all_up = all_valid_up(); 106 | let all_down = all_valid_down(); 107 | 108 | assert_eq!(all_up.len(), all_down.len()); 109 | assert_ne!(all_up.len(), 0); 110 | 111 | for i in 0..all_up.len() { 112 | let M { 113 | up: left_up, 114 | up_hook: _, 115 | down: left_down, 116 | down_hook: _, 117 | foreign_key_check: left_foreign_key_check, 118 | comment: left_comment, 119 | } = all_up[i]; 120 | let M { 121 | up: right_up, 122 | up_hook: _, 123 | down: right_down, 124 | down_hook: _, 125 | foreign_key_check: right_foreign_key_check, 126 | comment: right_comment, 127 | } = all_down[i]; 128 | 129 | assert_eq!(left_up, right_up); 130 | assert_eq!(left_foreign_key_check, right_foreign_key_check); 131 | assert_eq!(left_comment, right_comment); 132 | 133 | assert!(left_down.is_none()); 134 | assert!(right_down.is_some()); 135 | } 136 | } 137 | 138 | pub fn m_invalid0() -> M<'static> { 139 | M::up("CREATE TABLE table3()") 140 | } 141 | pub fn m_invalid1() -> M<'static> { 142 | M::up("something invalid") 143 | } 144 | 145 | pub fn m_invalid_fk() -> M<'static> { 146 | M::up( 147 | r#" 148 | CREATE TABLE fk1(a PRIMARY KEY); 149 | CREATE TABLE fk2( 150 | a, 151 | FOREIGN KEY(a) REFERENCES fk1(a) 152 | ); 153 | INSERT INTO fk2 (a) VALUES ('foo'); 154 | INSERT INTO fk2 (a) VALUES ('bar'); 155 | "#, 156 | ) 157 | .foreign_key_check() 158 | } 159 | 160 | pub fn m_invalid_fk_down() -> M<'static> { 161 | M::up( 162 | r#" 163 | CREATE TABLE fk1(a PRIMARY KEY); 164 | CREATE TABLE fk2( 165 | a, 166 | FOREIGN KEY(a) REFERENCES fk1(a) 167 | ); 168 | INSERT INTO fk1 (a) VALUES ('foo'); 169 | INSERT INTO fk2 (a) VALUES ('foo'); 170 | "#, 171 | ) 172 | .foreign_key_check() 173 | .down("DROP TABLE fk1;") 174 | } 175 | -------------------------------------------------------------------------------- /rusqlite_migration/src/tests/mod.rs: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | // Copyright Clément Joly and contributors. 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | 16 | // For feature "from-directory" 17 | mod builder; 18 | 19 | mod core; 20 | mod helpers; 21 | -------------------------------------------------------------------------------- /rusqlite_migration/src/tests/snapshots/rusqlite_migration__tests__builder__len_builder.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: rusqlite_migration/src/tests/builder.rs 3 | expression: migrations 4 | snapshot_kind: text 5 | --- 6 | Migrations { 7 | ms: [ 8 | M { 9 | up: "CREATE TABLE friend(name TEXT);", 10 | up_hook: None, 11 | down: None, 12 | down_hook: None, 13 | foreign_key_check: false, 14 | comment: None, 15 | }, 16 | M { 17 | up: "ALTER TABLE friend ADD COLUMN birthday TEXT;", 18 | up_hook: None, 19 | down: None, 20 | down_hook: None, 21 | foreign_key_check: false, 22 | comment: None, 23 | }, 24 | ], 25 | } 26 | -------------------------------------------------------------------------------- /rusqlite_migration/src/tests/snapshots/rusqlite_migration__tests__builder__valid_index.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: rusqlite_migration/src/tests/builder.rs 3 | expression: "MigrationsBuilder::from_iter(ms).edit(1, move |m|\nm.down(\"DROP TABLE t1;\")).edit(2, move |m|\nm.down(\"DROP TABLE t2;\")).finalize()" 4 | snapshot_kind: text 5 | --- 6 | Migrations { 7 | ms: [ 8 | M { 9 | up: "CREATE TABLE t1(a);", 10 | up_hook: None, 11 | down: Some( 12 | "DROP TABLE t1;", 13 | ), 14 | down_hook: None, 15 | foreign_key_check: false, 16 | comment: None, 17 | }, 18 | M { 19 | up: "CREATE TABLE t2(a);", 20 | up_hook: None, 21 | down: Some( 22 | "DROP TABLE t2;", 23 | ), 24 | down_hook: None, 25 | foreign_key_check: false, 26 | comment: None, 27 | }, 28 | ], 29 | } 30 | -------------------------------------------------------------------------------- /rusqlite_migration/src/tests/snapshots/rusqlite_migration__tests__core__all_valid_down_test.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: rusqlite_migration/src/tests/core.rs 3 | expression: migrations 4 | snapshot_kind: text 5 | --- 6 | Migrations { 7 | ms: [ 8 | M { 9 | up: "CREATE TABLE m1(a, b); CREATE TABLE m2(a, b, c);", 10 | up_hook: None, 11 | down: Some( 12 | "DROP TABLE m1; DROP TABLE m2;", 13 | ), 14 | down_hook: None, 15 | foreign_key_check: false, 16 | comment: None, 17 | }, 18 | M { 19 | up: "CREATE TABLE t1(a, b);", 20 | up_hook: None, 21 | down: Some( 22 | "DROP TABLE t1;", 23 | ), 24 | down_hook: None, 25 | foreign_key_check: false, 26 | comment: None, 27 | }, 28 | M { 29 | up: "ALTER TABLE t1 RENAME COLUMN b TO c;", 30 | up_hook: None, 31 | down: Some( 32 | "ALTER TABLE t1 RENAME COLUMN c TO b;", 33 | ), 34 | down_hook: None, 35 | foreign_key_check: false, 36 | comment: None, 37 | }, 38 | M { 39 | up: "CREATE TABLE t2(b);", 40 | up_hook: None, 41 | down: Some( 42 | "DROP TABLE t2;", 43 | ), 44 | down_hook: None, 45 | foreign_key_check: false, 46 | comment: None, 47 | }, 48 | M { 49 | up: "ALTER TABLE t2 ADD COLUMN a;", 50 | up_hook: None, 51 | down: Some( 52 | "ALTER TABLE t2 DROP COLUMN a;", 53 | ), 54 | down_hook: None, 55 | foreign_key_check: false, 56 | comment: None, 57 | }, 58 | M { 59 | up: "\n CREATE TABLE fk1(a PRIMARY KEY);\n CREATE TABLE fk2(\n a,\n FOREIGN KEY(a) REFERENCES fk1(a)\n );\n INSERT INTO fk1 (a) VALUES ('foo');\n INSERT INTO fk2 (a) VALUES ('foo');\n ", 60 | up_hook: None, 61 | down: Some( 62 | "DELETE FROM fk2; DELETE FROM fk1; DROP TABLE fk2; DROP TABLE fk1;", 63 | ), 64 | down_hook: None, 65 | foreign_key_check: true, 66 | comment: None, 67 | }, 68 | ], 69 | } 70 | -------------------------------------------------------------------------------- /rusqlite_migration/src/tests/snapshots/rusqlite_migration__tests__core__all_valid_up_test.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: rusqlite_migration/src/tests/core.rs 3 | expression: migrations 4 | snapshot_kind: text 5 | --- 6 | Migrations { 7 | ms: [ 8 | M { 9 | up: "CREATE TABLE m1(a, b); CREATE TABLE m2(a, b, c);", 10 | up_hook: None, 11 | down: None, 12 | down_hook: None, 13 | foreign_key_check: false, 14 | comment: None, 15 | }, 16 | M { 17 | up: "CREATE TABLE t1(a, b);", 18 | up_hook: None, 19 | down: None, 20 | down_hook: None, 21 | foreign_key_check: false, 22 | comment: None, 23 | }, 24 | M { 25 | up: "ALTER TABLE t1 RENAME COLUMN b TO c;", 26 | up_hook: None, 27 | down: None, 28 | down_hook: None, 29 | foreign_key_check: false, 30 | comment: None, 31 | }, 32 | M { 33 | up: "CREATE TABLE t2(b);", 34 | up_hook: None, 35 | down: None, 36 | down_hook: None, 37 | foreign_key_check: false, 38 | comment: None, 39 | }, 40 | M { 41 | up: "ALTER TABLE t2 ADD COLUMN a;", 42 | up_hook: None, 43 | down: None, 44 | down_hook: None, 45 | foreign_key_check: false, 46 | comment: None, 47 | }, 48 | M { 49 | up: "\n CREATE TABLE fk1(a PRIMARY KEY);\n CREATE TABLE fk2(\n a,\n FOREIGN KEY(a) REFERENCES fk1(a)\n );\n INSERT INTO fk1 (a) VALUES ('foo');\n INSERT INTO fk2 (a) VALUES ('foo');\n ", 50 | up_hook: None, 51 | down: None, 52 | down_hook: None, 53 | foreign_key_check: true, 54 | comment: None, 55 | }, 56 | ], 57 | } 58 | -------------------------------------------------------------------------------- /rusqlite_migration/src/tests/snapshots/rusqlite_migration__tests__core__everything.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: rusqlite_migration/src/tests/core.rs 3 | expression: everything 4 | snapshot_kind: text 5 | --- 6 | M(up: "UP", up hook, down: "DOWN", down hook, foreign key check, comment: "Comment, likely a filename in practice!") 7 | -------------------------------------------------------------------------------- /rusqlite_migration/src/tests/snapshots/rusqlite_migration__tests__core__everything_alt.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: rusqlite_migration/src/tests/core.rs 3 | expression: "format!(\"{everything:#}\")" 4 | snapshot_kind: text 5 | --- 6 | M( 7 | up: "UP", 8 | up hook, 9 | down: "DOWN", 10 | down hook, 11 | foreign key check, 12 | comment: "Comment, likely a filename in practice!" 13 | ) 14 | -------------------------------------------------------------------------------- /rusqlite_migration/src/tests/snapshots/rusqlite_migration__tests__core__everything_compact_debug.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: rusqlite_migration/src/tests/core.rs 3 | expression: everything 4 | snapshot_kind: text 5 | --- 6 | M { up: "UP", up_hook: Some(MigrationHook()), down: Some("DOWN"), down_hook: Some(MigrationHook()), foreign_key_check: true, comment: Some("Comment, likely a filename in practice!") } 7 | -------------------------------------------------------------------------------- /rusqlite_migration/src/tests/snapshots/rusqlite_migration__tests__core__everything_debug.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: rusqlite_migration/src/tests/core.rs 3 | expression: everything 4 | snapshot_kind: text 5 | --- 6 | M { 7 | up: "UP", 8 | up_hook: Some( 9 | MigrationHook(), 10 | ), 11 | down: Some( 12 | "DOWN", 13 | ), 14 | down_hook: Some( 15 | MigrationHook(), 16 | ), 17 | foreign_key_check: true, 18 | comment: Some( 19 | "Comment, likely a filename in practice!", 20 | ), 21 | } 22 | -------------------------------------------------------------------------------- /rusqlite_migration/src/tests/snapshots/rusqlite_migration__tests__core__invalid_fk_check_test.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: rusqlite_migration/src/tests/core.rs 3 | expression: migrations.validate() 4 | snapshot_kind: text 5 | --- 6 | Err( 7 | ForeignKeyCheck( 8 | [ 9 | ForeignKeyCheckError { 10 | table: "fk2", 11 | rowid: 1, 12 | parent: "fk1", 13 | fkid: 0, 14 | }, 15 | ForeignKeyCheckError { 16 | table: "fk2", 17 | rowid: 2, 18 | parent: "fk1", 19 | fkid: 0, 20 | }, 21 | ], 22 | ), 23 | ) 24 | -------------------------------------------------------------------------------- /rusqlite_migration/src/tests/snapshots/rusqlite_migration__tests__core__migration_hook_debug.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: rusqlite_migration/src/tests/core.rs 3 | expression: m 4 | snapshot_kind: text 5 | --- 6 | M { 7 | up: "", 8 | up_hook: Some( 9 | MigrationHook(), 10 | ), 11 | down: None, 12 | down_hook: None, 13 | foreign_key_check: false, 14 | comment: None, 15 | } 16 | -------------------------------------------------------------------------------- /rusqlite_migration/src/tests/snapshots/rusqlite_migration__tests__core__read_only_db_all_valid.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: rusqlite_migration/src/tests/core.rs 3 | expression: e 4 | snapshot_kind: text 5 | --- 6 | Err( 7 | RusqliteError { 8 | query: "CREATE TABLE m1(a, b); CREATE TABLE m2(a, b, c);", 9 | err: SqliteFailure( 10 | Error { 11 | code: ReadOnly, 12 | extended_code: 8, 13 | }, 14 | Some( 15 | "attempt to write a readonly database", 16 | ), 17 | ), 18 | }, 19 | ) 20 | -------------------------------------------------------------------------------- /rusqlite_migration/src/tests/snapshots/rusqlite_migration__tests__core__up_down.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: rusqlite_migration/src/tests/core.rs 3 | expression: m_valid0_down() 4 | snapshot_kind: text 5 | --- 6 | M(up: "CREATE TABLE m1(a, b); CREATE TABLE m2(a, b, c);", down: "DROP TABLE m1; DROP TABLE m2;") 7 | -------------------------------------------------------------------------------- /rusqlite_migration/src/tests/snapshots/rusqlite_migration__tests__core__up_down_alt.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: rusqlite_migration/src/tests/core.rs 3 | expression: "format!(\"{:#}\", m_valid0_down())" 4 | snapshot_kind: text 5 | --- 6 | M( 7 | up: "CREATE TABLE m1(a, b); CREATE TABLE m2(a, b, c);", 8 | down: "DROP TABLE m1; DROP TABLE m2;" 9 | ) 10 | -------------------------------------------------------------------------------- /rusqlite_migration/src/tests/snapshots/rusqlite_migration__tests__core__up_down_fk.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: rusqlite_migration/src/tests/core.rs 3 | expression: m_valid_fk_down() 4 | snapshot_kind: text 5 | --- 6 | M(up: " 7 | CREATE TABLE fk1(a PRIMARY KEY); 8 | CREATE TABLE fk2( 9 | a, 10 | FOREIGN KEY(a) REFERENCES fk1(a) 11 | ); 12 | INSERT INTO fk1 (a) VALUES ('foo'); 13 | INSERT INTO fk2 (a) VALUES ('foo'); 14 | ", down: "DELETE FROM fk2; DELETE FROM fk1; DROP TABLE fk2; DROP TABLE fk1;", foreign key check) 15 | -------------------------------------------------------------------------------- /rusqlite_migration/src/tests/snapshots/rusqlite_migration__tests__core__up_down_fk_alt.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: rusqlite_migration/src/tests/core.rs 3 | expression: "format!(\"{:#}\", m_valid_fk_down())" 4 | snapshot_kind: text 5 | --- 6 | M( 7 | up: " 8 | CREATE TABLE fk1(a PRIMARY KEY); 9 | CREATE TABLE fk2( 10 | a, 11 | FOREIGN KEY(a) REFERENCES fk1(a) 12 | ); 13 | INSERT INTO fk1 (a) VALUES ('foo'); 14 | INSERT INTO fk2 (a) VALUES ('foo'); 15 | ", 16 | down: "DELETE FROM fk2; DELETE FROM fk1; DROP TABLE fk2; DROP TABLE fk1;", 17 | foreign key check 18 | ) 19 | -------------------------------------------------------------------------------- /rusqlite_migration/src/tests/snapshots/rusqlite_migration__tests__core__up_only.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: rusqlite_migration/src/tests/core.rs 3 | expression: m_valid0_up() 4 | snapshot_kind: text 5 | --- 6 | M(up: "CREATE TABLE m1(a, b); CREATE TABLE m2(a, b, c);") 7 | -------------------------------------------------------------------------------- /rusqlite_migration/src/tests/snapshots/rusqlite_migration__tests__core__up_only_alt.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: rusqlite_migration/src/tests/core.rs 3 | expression: "format!(\"{:#}\", m_valid0_up())" 4 | snapshot_kind: text 5 | --- 6 | M( 7 | up: "CREATE TABLE m1(a, b); CREATE TABLE m2(a, b, c);" 8 | ) 9 | -------------------------------------------------------------------------------- /rusqlite_migration/src/tests/snapshots/rusqlite_migration__tests__core__user_version_error.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: rusqlite_migration/src/tests/core.rs 3 | expression: e 4 | snapshot_kind: text 5 | --- 6 | Err( 7 | RusqliteError { 8 | query: "PRAGMA user_version = 1; -- Approximate query", 9 | err: SqliteFailure( 10 | Error { 11 | code: ReadOnly, 12 | extended_code: 8, 13 | }, 14 | Some( 15 | "attempt to write a readonly database", 16 | ), 17 | ), 18 | }, 19 | ) 20 | -------------------------------------------------------------------------------- /rusqlite_migration_tests/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "rusqlite_migration_tests" 3 | edition = "2018" 4 | readme = "README.md" 5 | publish = false 6 | 7 | authors.workspace = true 8 | categories.workspace = true 9 | description.workspace = true 10 | homepage.workspace = true 11 | keywords.workspace = true 12 | license.workspace = true 13 | repository.workspace = true 14 | rust-version.workspace = true 15 | version.workspace = true 16 | 17 | [dependencies] 18 | log = "0.4" 19 | 20 | [dependencies.rusqlite_migration] 21 | path = "../rusqlite_migration" 22 | features = ["from-directory"] 23 | 24 | [dependencies.rusqlite] 25 | version = "0.36.0" 26 | features = ["extra_check"] 27 | 28 | [dev-dependencies] 29 | simple-logging = "2.0.2" 30 | env_logger = "0.11" 31 | anyhow = "1" 32 | mktemp = "0.5" 33 | include_dir = "0.7.4" 34 | 35 | [[test]] 36 | name = "integration_tests" 37 | path = "tests/lib.rs" 38 | harness = true 39 | -------------------------------------------------------------------------------- /rusqlite_migration_tests/tests/from_directory_test.rs: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | // Copyright Clément Joly and contributors. 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | 16 | use std::num::NonZeroUsize; 17 | 18 | use include_dir::{include_dir, Dir}; 19 | 20 | use rusqlite::{params, Connection}; 21 | use rusqlite_migration::{Error, Migrations, SchemaVersion}; 22 | 23 | static MIGRATIONS_DIR: Dir = 24 | include_dir!("$CARGO_MANIFEST_DIR/../examples/from-directory/migrations"); 25 | static JUST_DOWN: Dir = include_dir!("$CARGO_MANIFEST_DIR/tests/migrations/just_down"); 26 | static EMPTY_DIR: Dir = include_dir!("$CARGO_MANIFEST_DIR/tests/migrations/empty_dir"); 27 | static NON_CONSECUTIVE: Dir = include_dir!("$CARGO_MANIFEST_DIR/tests/migrations/non_consecutive"); 28 | static MULTIPLE: Dir = include_dir!("$CARGO_MANIFEST_DIR/tests/migrations/multiple"); 29 | static ZERO_AS_ID: Dir = include_dir!("$CARGO_MANIFEST_DIR/tests/migrations/zero_as_id"); 30 | static INVALID_UTF8: Dir = include_dir!("$CARGO_MANIFEST_DIR/tests/migrations/invalid_utf8"); 31 | static TOO_LARGE_MIGRATION_ID: Dir = 32 | include_dir!("$CARGO_MANIFEST_DIR/tests/migrations/too_large_migration_id"); 33 | static BAD_MIGRATION_ID: Dir = 34 | include_dir!("$CARGO_MANIFEST_DIR/tests/migrations/bad_migration_id"); 35 | static MISSING_MIGRATION_ID: Dir = 36 | include_dir!("$CARGO_MANIFEST_DIR/tests/migrations/missing_migration_id"); 37 | 38 | #[test] 39 | fn main_test() { 40 | let mut conn = Connection::open_in_memory().unwrap(); 41 | { 42 | let migrations = Migrations::from_directory(&MIGRATIONS_DIR).unwrap(); 43 | 44 | migrations.to_latest(&mut conn).unwrap(); 45 | 46 | assert_eq!( 47 | Ok(SchemaVersion::Inside(NonZeroUsize::new(3).unwrap())), 48 | migrations.current_version(&conn) 49 | ); 50 | 51 | conn.execute( 52 | "INSERT INTO friend (name, birthday) VALUES (?1, ?2)", 53 | params!["John", "1970-01-01"], 54 | ) 55 | .unwrap(); 56 | } 57 | } 58 | 59 | #[test] 60 | fn utf8_test() { 61 | let migrations = Migrations::from_directory(&INVALID_UTF8); 62 | assert_eq!( 63 | Error::FileLoad("Could not load contents from 01-invalid_utf8/up.sql".to_string()), 64 | migrations.unwrap_err() 65 | ) 66 | } 67 | 68 | #[test] 69 | fn missing_id_test() { 70 | let migrations = Migrations::from_directory(&MISSING_MIGRATION_ID); 71 | assert_eq!( 72 | Error::FileLoad("Could not extract migration id from file name friend_car".to_string()), 73 | migrations.unwrap_err() 74 | ) 75 | } 76 | 77 | #[test] 78 | fn bad_migration_id() { 79 | let migrations = Migrations::from_directory(&BAD_MIGRATION_ID); 80 | assert_eq!( 81 | Error::FileLoad("Could not parse migration id from file name a-friend_car as usize: invalid digit found in string".to_string()), 82 | migrations.unwrap_err() 83 | ) 84 | } 85 | 86 | #[test] 87 | fn too_large_migration_id() { 88 | let migrations = Migrations::from_directory(&TOO_LARGE_MIGRATION_ID); 89 | assert_eq!( 90 | Error::FileLoad("Could not parse migration id from file name 18446744073709551616-friend_car as usize: number too large to fit in target type".to_string()), 91 | migrations.unwrap_err() 92 | ) 93 | } 94 | 95 | #[test] 96 | fn zero_as_id() { 97 | let migrations = Migrations::from_directory(&ZERO_AS_ID); 98 | assert_eq!( 99 | Error::FileLoad( 100 | "00-friend_car has an incorrect migration id: migration id cannot be 0".to_string() 101 | ), 102 | migrations.unwrap_err() 103 | ) 104 | } 105 | 106 | #[test] 107 | fn just_down() { 108 | let migrations = Migrations::from_directory(&JUST_DOWN); 109 | assert_eq!( 110 | Error::FileLoad("Missing upward migration file for migration 01-friend_car".to_string()), 111 | migrations.unwrap_err() 112 | ) 113 | } 114 | 115 | #[test] 116 | fn multiple_up() { 117 | let migrations = Migrations::from_directory(&MULTIPLE); 118 | assert_eq!( 119 | Error::FileLoad("Multiple migrations detected for migration id: 1".to_string()), 120 | migrations.unwrap_err() 121 | ) 122 | } 123 | 124 | #[test] 125 | fn empty_dir() { 126 | let migrations = Migrations::from_directory(&EMPTY_DIR); 127 | assert_eq!( 128 | Error::FileLoad("Directory does not contain any migration files".to_string()), 129 | migrations.unwrap_err() 130 | ) 131 | } 132 | 133 | #[test] 134 | fn non_consecutive() { 135 | let migrations = Migrations::from_directory(&NON_CONSECUTIVE); 136 | assert_eq!( 137 | Error::FileLoad("Migration ids must be consecutive numbers".to_string()), 138 | migrations.unwrap_err() 139 | ) 140 | } 141 | 142 | #[test] 143 | // Ensure that we have a healthy mix of files with an end of line (EOL) at the end and of files 144 | // without. 145 | fn eol_does_not_matter_test() { 146 | let no_eol = 147 | include_str!("../../examples/from-directory/migrations/02-add_birthday_column/up.sql"); 148 | let eol = include_str!("../../examples/from-directory/migrations/01-friend_car/up.sql"); 149 | 150 | assert_ne!(no_eol.chars().last().unwrap(), '\n'); 151 | assert_eq!(eol.chars().last().unwrap(), '\n'); 152 | } 153 | -------------------------------------------------------------------------------- /rusqlite_migration_tests/tests/integration_test.rs: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | // Copyright Clément Joly and contributors. 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | 16 | use std::num::NonZeroUsize; 17 | 18 | use rusqlite::{params, Connection}; 19 | use rusqlite_migration::{Migrations, SchemaVersion, M}; 20 | 21 | #[test] 22 | fn main_test() { 23 | let mut conn = Connection::open_in_memory().unwrap(); 24 | // Define migrations 25 | let mut ms = vec![ 26 | M::up("CREATE TABLE t(a);"), 27 | M::up(include_str!("../../examples/friend_car.sql")), 28 | M::up("ALTER TABLE friend ADD COLUMN birthday TEXT;"), 29 | ]; 30 | 31 | { 32 | let migrations = Migrations::new(ms.clone()); 33 | migrations.to_latest(&mut conn).unwrap(); 34 | 35 | assert_eq!( 36 | Ok(SchemaVersion::Inside(NonZeroUsize::new(3).unwrap())), 37 | migrations.current_version(&conn) 38 | ); 39 | 40 | conn.execute( 41 | "INSERT INTO friend (name, birthday) VALUES (?1, ?2)", 42 | params!["John", "1970-01-01"], 43 | ) 44 | .unwrap(); 45 | } 46 | 47 | // Later, we add things to the schema 48 | ms.push(M::up("CREATE INDEX UX_friend_email ON friend(email);")); 49 | ms.push(M::up("ALTER TABLE friend RENAME COLUMN birthday TO birth;")); 50 | 51 | { 52 | let migrations = Migrations::new(ms.clone()); 53 | migrations.to_latest(&mut conn).unwrap(); 54 | 55 | assert_eq!( 56 | Ok(SchemaVersion::Inside(NonZeroUsize::new(5).unwrap())), 57 | migrations.current_version(&conn) 58 | ); 59 | 60 | conn.execute( 61 | "INSERT INTO friend (name, birth) VALUES (?1, ?2)", 62 | params!["Alice", "2000-01-01"], 63 | ) 64 | .unwrap(); 65 | } 66 | 67 | // Later still 68 | ms.push(M::up("DROP INDEX UX_friend_email;")); 69 | 70 | { 71 | let migrations = Migrations::new(ms.clone()); 72 | migrations.to_latest(&mut conn).unwrap(); 73 | 74 | assert_eq!( 75 | Ok(SchemaVersion::Inside(NonZeroUsize::new(6).unwrap())), 76 | migrations.current_version(&conn) 77 | ); 78 | 79 | conn.execute( 80 | "INSERT INTO friend (name, birth) VALUES (?1, ?2)", 81 | params!["Alice", "2000-01-01"], 82 | ) 83 | .unwrap(); 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /rusqlite_migration_tests/tests/lib.rs: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | // Copyright Clément Joly and contributors. 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | 16 | mod from_directory_test; 17 | mod integration_test; 18 | mod migrations_builder_from_iterator_test; 19 | mod migrations_builder_test; 20 | mod multiline_test; 21 | mod up_and_down; 22 | -------------------------------------------------------------------------------- /rusqlite_migration_tests/tests/migrations/bad_migration_id/a-friend_car/up.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE friend( 2 | friend_id INTEGER PRIMARY KEY, 3 | name TEXT NOT NULL, 4 | email TEXT UNIQUE, 5 | phone TEXT UNIQUE, 6 | picture BLOB 7 | ); 8 | 9 | CREATE TABLE car( 10 | registration_plate TEXT PRIMARY KEY, 11 | cost REAL NOT NULL, 12 | bought_on TEXT NOT NULL 13 | ); 14 | -------------------------------------------------------------------------------- /rusqlite_migration_tests/tests/migrations/empty_dir/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cljoly/rusqlite_migration/a8fd7c89f442758b0d9d5e23c7150572a60d7195/rusqlite_migration_tests/tests/migrations/empty_dir/.gitkeep -------------------------------------------------------------------------------- /rusqlite_migration_tests/tests/migrations/invalid_utf8/01-invalid_utf8/up.sql: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cljoly/rusqlite_migration/a8fd7c89f442758b0d9d5e23c7150572a60d7195/rusqlite_migration_tests/tests/migrations/invalid_utf8/01-invalid_utf8/up.sql -------------------------------------------------------------------------------- /rusqlite_migration_tests/tests/migrations/just_down/01-friend_car/down.sql: -------------------------------------------------------------------------------- 1 | DROP TABLE TEST; 2 | -------------------------------------------------------------------------------- /rusqlite_migration_tests/tests/migrations/missing_migration_id/friend_car/up.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE friend( 2 | friend_id INTEGER PRIMARY KEY, 3 | name TEXT NOT NULL, 4 | email TEXT UNIQUE, 5 | phone TEXT UNIQUE, 6 | picture BLOB 7 | ); 8 | 9 | CREATE TABLE car( 10 | registration_plate TEXT PRIMARY KEY, 11 | cost REAL NOT NULL, 12 | bought_on TEXT NOT NULL 13 | ); 14 | -------------------------------------------------------------------------------- /rusqlite_migration_tests/tests/migrations/multiple/01-friend_car/up.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE friend( 2 | friend_id INTEGER PRIMARY KEY, 3 | name TEXT NOT NULL, 4 | email TEXT UNIQUE, 5 | phone TEXT UNIQUE, 6 | picture BLOB 7 | ); 8 | 9 | CREATE TABLE car( 10 | registration_plate TEXT PRIMARY KEY, 11 | cost REAL NOT NULL, 12 | bought_on TEXT NOT NULL 13 | ); 14 | -------------------------------------------------------------------------------- /rusqlite_migration_tests/tests/migrations/multiple/01-friend_car2/up.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE friend( 2 | friend_id INTEGER PRIMARY KEY, 3 | name TEXT NOT NULL, 4 | email TEXT UNIQUE, 5 | phone TEXT UNIQUE, 6 | picture BLOB 7 | ); 8 | 9 | CREATE TABLE car( 10 | registration_plate TEXT PRIMARY KEY, 11 | cost REAL NOT NULL, 12 | bought_on TEXT NOT NULL 13 | ); 14 | -------------------------------------------------------------------------------- /rusqlite_migration_tests/tests/migrations/non_consecutive/01-friend_car/up.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE friend( 2 | friend_id INTEGER PRIMARY KEY, 3 | name TEXT NOT NULL, 4 | email TEXT UNIQUE, 5 | phone TEXT UNIQUE, 6 | picture BLOB 7 | ); 8 | 9 | CREATE TABLE car( 10 | registration_plate TEXT PRIMARY KEY, 11 | cost REAL NOT NULL, 12 | bought_on TEXT NOT NULL 13 | ); 14 | -------------------------------------------------------------------------------- /rusqlite_migration_tests/tests/migrations/non_consecutive/03-add_animal_table/down.sql: -------------------------------------------------------------------------------- 1 | DROP TABLE animal; -------------------------------------------------------------------------------- /rusqlite_migration_tests/tests/migrations/non_consecutive/03-add_animal_table/up.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE animal(name TEXT); -------------------------------------------------------------------------------- /rusqlite_migration_tests/tests/migrations/non_consecutive/04-add_birthday_column/up.sql: -------------------------------------------------------------------------------- 1 | ALTER TABLE friend ADD COLUMN birthday TEXT; 2 | ALTER TABLE friend ADD COLUMN comment TEXT; -------------------------------------------------------------------------------- /rusqlite_migration_tests/tests/migrations/too_large_migration_id/18446744073709551616-friend_car/up.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE friend( 2 | friend_id INTEGER PRIMARY KEY, 3 | name TEXT NOT NULL, 4 | email TEXT UNIQUE, 5 | phone TEXT UNIQUE, 6 | picture BLOB 7 | ); 8 | 9 | CREATE TABLE car( 10 | registration_plate TEXT PRIMARY KEY, 11 | cost REAL NOT NULL, 12 | bought_on TEXT NOT NULL 13 | ); 14 | -------------------------------------------------------------------------------- /rusqlite_migration_tests/tests/migrations/zero_as_id/00-friend_car/up.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE friend( 2 | friend_id INTEGER PRIMARY KEY, 3 | name TEXT NOT NULL, 4 | email TEXT UNIQUE, 5 | phone TEXT UNIQUE, 6 | picture BLOB 7 | ); 8 | 9 | CREATE TABLE car( 10 | registration_plate TEXT PRIMARY KEY, 11 | cost REAL NOT NULL, 12 | bought_on TEXT NOT NULL 13 | ); 14 | -------------------------------------------------------------------------------- /rusqlite_migration_tests/tests/migrations_builder_from_iterator_test.rs: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | // Copyright Clément Joly and contributors. 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | 16 | use std::{iter::FromIterator, num::NonZeroUsize}; 17 | 18 | use rusqlite::{params, Connection, Transaction}; 19 | use rusqlite_migration::{Migrations, MigrationsBuilder, SchemaVersion, M}; 20 | 21 | #[test] 22 | fn main_test() { 23 | let mut conn = Connection::open_in_memory().unwrap(); 24 | // Define migrations 25 | let ms = vec![ 26 | M::up("CREATE TABLE t(a);"), 27 | M::up(include_str!("../../examples/friend_car.sql")), 28 | M::up("ALTER TABLE friend ADD COLUMN birthday TEXT;"), 29 | ]; 30 | 31 | { 32 | let builder = MigrationsBuilder::from_iter(ms); 33 | 34 | let migrations: Migrations = builder 35 | .edit(1, move |m| m.set_down_hook(move |_tx: &Transaction| Ok(()))) 36 | .edit(1, move |m| m.set_up_hook(move |_tx: &Transaction| Ok(()))) 37 | .finalize(); 38 | 39 | migrations.to_latest(&mut conn).unwrap(); 40 | 41 | assert_eq!( 42 | Ok(SchemaVersion::Inside(NonZeroUsize::new(3).unwrap())), 43 | migrations.current_version(&conn) 44 | ); 45 | 46 | conn.execute( 47 | "INSERT INTO friend (name, birthday) VALUES (?1, ?2)", 48 | params!["John", "1970-01-01"], 49 | ) 50 | .unwrap(); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /rusqlite_migration_tests/tests/migrations_builder_test.rs: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | // Copyright Clément Joly and contributors. 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | 16 | use std::num::NonZeroUsize; 17 | 18 | use include_dir::{include_dir, Dir}; 19 | 20 | use rusqlite::{params, Connection, Transaction}; 21 | use rusqlite_migration::{Migrations, MigrationsBuilder, SchemaVersion}; 22 | 23 | static MIGRATIONS_DIR: Dir = 24 | include_dir!("$CARGO_MANIFEST_DIR/../examples/from-directory/migrations"); 25 | 26 | #[test] 27 | fn main_test() { 28 | let mut conn = Connection::open_in_memory().unwrap(); 29 | // Define migrations 30 | 31 | { 32 | let builder = MigrationsBuilder::from_directory(&MIGRATIONS_DIR).unwrap(); 33 | 34 | let migrations: Migrations = builder 35 | .edit(1, move |m| m.set_down_hook(move |_tx: &Transaction| Ok(()))) 36 | .edit(1, move |m| m.set_up_hook(move |_tx: &Transaction| Ok(()))) 37 | .finalize(); 38 | 39 | migrations.to_latest(&mut conn).unwrap(); 40 | 41 | assert_eq!( 42 | Ok(SchemaVersion::Inside(NonZeroUsize::new(3).unwrap())), 43 | migrations.current_version(&conn) 44 | ); 45 | 46 | conn.execute( 47 | "INSERT INTO friend (name, birthday) VALUES (?1, ?2)", 48 | params!["John", "1970-01-01"], 49 | ) 50 | .unwrap(); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /rusqlite_migration_tests/tests/multiline_test.rs: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | // Copyright Clément Joly and contributors. 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | 16 | use std::num::NonZeroUsize; 17 | 18 | use rusqlite::{params, Connection}; 19 | use rusqlite_migration::{Migrations, SchemaVersion, M}; 20 | 21 | #[test] 22 | fn main_test() { 23 | let db_file = mktemp::Temp::new_file().unwrap(); 24 | // Define a multiline migration 25 | let mut ms = vec![M::up( 26 | r#" 27 | CREATE TABLE friend (name TEXT PRIMARY KEY, email TEXT) WITHOUT ROWID; 28 | ALTER TABLE friend ADD COLUMN birthday TEXT; 29 | ALTER TABLE friend ADD COLUMN notes TEXT; 30 | "#, 31 | )]; 32 | 33 | { 34 | let mut conn = Connection::open(&db_file).unwrap(); 35 | 36 | let migrations = Migrations::new(ms.clone()); 37 | migrations.to_latest(&mut conn).unwrap(); 38 | 39 | conn.pragma_update_and_check(None, "journal_mode", "WAL", |r| { 40 | match r.get::<_, String>(0) { 41 | Ok(v) if v.to_lowercase() == "wal" => Ok(()), 42 | val => panic!("unexpected journal_mode after setting it: {:?}", val), 43 | } 44 | }) 45 | .unwrap(); 46 | conn.pragma_update(None, "foreign_keys", "ON").unwrap(); 47 | 48 | assert_eq!( 49 | Ok(SchemaVersion::Inside(NonZeroUsize::new(ms.len()).unwrap())), 50 | migrations.current_version(&conn) 51 | ); 52 | 53 | conn.execute( 54 | "INSERT INTO friend (name, birthday, notes) VALUES (?1, ?2, ?3)", 55 | params!["John", "1970-01-01", "fun fact: ..."], 56 | ) 57 | .unwrap(); 58 | 59 | conn.query_row("SELECT * FROM pragma_journal_mode", [], |row| { 60 | assert_eq!(row.get::<_, String>(0), Ok(String::from("wal"))); 61 | Ok(()) 62 | }) 63 | .unwrap(); 64 | 65 | conn.query_row("SELECT * FROM pragma_foreign_keys", [], |row| { 66 | assert_eq!(row.get::<_, bool>(0), Ok(true)); 67 | Ok(()) 68 | }) 69 | .unwrap(); 70 | } 71 | 72 | // Using a new connection to ensure the pragma were taken into account 73 | { 74 | let conn = Connection::open(&db_file).unwrap(); 75 | 76 | conn.query_row("SELECT * FROM pragma_journal_mode", [], |row| { 77 | assert_eq!(row.get::<_, String>(0), Ok(String::from("wal"))); 78 | Ok(()) 79 | }) 80 | .unwrap(); 81 | 82 | conn.execute( 83 | "INSERT INTO friend (name, birthday) VALUES (?1, ?2)", 84 | params!["Anna", "1971-11-11"], 85 | ) 86 | .unwrap(); 87 | } 88 | 89 | // Later, we add things to the schema 90 | ms.push(M::up("CREATE INDEX UX_friend_email ON friend(email)")); 91 | ms.push(M::up("ALTER TABLE friend RENAME COLUMN birthday TO birth;")); 92 | 93 | { 94 | let mut conn = Connection::open(&db_file).unwrap(); 95 | 96 | let migrations = Migrations::new(ms.clone()); 97 | migrations.to_latest(&mut conn).unwrap(); 98 | 99 | assert_eq!( 100 | Ok(SchemaVersion::Inside(NonZeroUsize::new(3).unwrap())), 101 | migrations.current_version(&conn) 102 | ); 103 | 104 | conn.execute( 105 | "INSERT INTO friend (name, birth) VALUES (?1, ?2)", 106 | params!["Alice", "2000-01-01"], 107 | ) 108 | .unwrap(); 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /rusqlite_migration_tests/tests/up_and_down.rs: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | // Copyright Clément Joly and contributors. 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | 16 | use std::num::NonZeroUsize; 17 | 18 | use rusqlite::{params, Connection}; 19 | use rusqlite_migration::{MigrationDefinitionError, Migrations, SchemaVersion, M}; 20 | 21 | #[test] 22 | fn main_test() { 23 | let ms = vec![ 24 | // 0 25 | M::up("CREATE TABLE animals (id INTEGER PRIMARY KEY, name TEXT);") 26 | .down("DROP TABLE animals;"), 27 | // 1 28 | M::up("CREATE TABLE food (id INTEGER PRIMARY KEY, name TEXT);").down("DROP TABLE food;"), 29 | // 2 30 | M::up("CREATE TABLE animal_food (animal_id INTEGER, food_id INTEGER);") 31 | .down("DROP TABLE animal_food;"), 32 | // 3 33 | M::up("CREATE TABLE habitats (id INTEGER PRIMARY KEY, name TEXT);") 34 | .down("DROP TABLE habitats;"), 35 | // 4 36 | M::up("CREATE TABLE animal_habitat (animal_id INTEGER, habitat_id INTEGER);") 37 | .down("DROP TABLE animal_habitat;"), 38 | ]; 39 | 40 | { 41 | let mut conn = Connection::open_in_memory().unwrap(); 42 | 43 | let migrations = Migrations::new(ms.clone()); 44 | 45 | assert_eq!( 46 | Ok(SchemaVersion::NoneSet), 47 | migrations.current_version(&conn) 48 | ); 49 | 50 | migrations.to_version(&mut conn, 1).unwrap(); 51 | 52 | conn.execute("INSERT INTO animals (name) VALUES (?1)", params!["Dog"]) 53 | .unwrap(); 54 | 55 | assert_eq!( 56 | Ok(SchemaVersion::Inside(NonZeroUsize::new(1).unwrap())), 57 | migrations.current_version(&conn) 58 | ); 59 | 60 | // go back 61 | migrations.to_version(&mut conn, 0).unwrap(); 62 | 63 | // the table is gone now 64 | let _ = conn 65 | .execute("INSERT INTO animals (name) VALUES (?1)", params!["Dog"]) 66 | .unwrap_err(); 67 | 68 | assert_eq!( 69 | Ok(SchemaVersion::NoneSet), 70 | migrations.current_version(&conn) 71 | ); 72 | } 73 | 74 | // Multiple steps 75 | { 76 | let mut conn = Connection::open_in_memory().unwrap(); 77 | 78 | let migrations = Migrations::new(ms.clone()); 79 | 80 | assert_eq!( 81 | Ok(SchemaVersion::NoneSet), 82 | migrations.current_version(&conn) 83 | ); 84 | 85 | // Bad version, this should not change the DB 86 | assert!(migrations.to_version(&mut conn, 6).is_err()); 87 | 88 | assert_eq!( 89 | Ok(SchemaVersion::NoneSet), 90 | migrations.current_version(&conn) 91 | ); 92 | 93 | migrations.to_version(&mut conn, 5).unwrap(); 94 | 95 | conn.execute( 96 | "INSERT INTO habitats (id, name) VALUES (?1, ?2)", 97 | params![0, "Forest"], 98 | ) 99 | .unwrap(); 100 | 101 | conn.execute( 102 | "INSERT INTO animals (id, name) VALUES (?1, ?2)", 103 | params![15, "Fox"], 104 | ) 105 | .unwrap(); 106 | 107 | conn.execute( 108 | "INSERT INTO animal_habitat (animal_id, habitat_id) VALUES (?1, ?2)", 109 | params![15, 0], 110 | ) 111 | .unwrap(); 112 | 113 | // go back 114 | migrations.to_version(&mut conn, 3).unwrap(); 115 | 116 | // the table is gone now 117 | assert!(conn 118 | .execute("INSERT INTO habitats (name) VALUES (?1)", params!["Beach"],) 119 | .is_err()); 120 | 121 | conn.execute("INSERT INTO food (name) VALUES (?1)", params!["Cheese"]) 122 | .unwrap(); 123 | 124 | conn.execute("INSERT INTO animals (name) VALUES (?1)", params!["Mouse"]) 125 | .unwrap(); 126 | 127 | conn.execute( 128 | "INSERT INTO animal_food (animal_id, food_id) VALUES (?1, ?2)", 129 | params![1, 0], 130 | ) 131 | .unwrap(); 132 | } 133 | } 134 | 135 | #[test] 136 | fn test_errors() { 137 | let ms = vec![ 138 | // 0 139 | M::up("CREATE TABLE animals (id INTEGER, name TEXT);").down("DROP TABLE animals;"), 140 | // 1 141 | M::up("CREATE TABLE food (id INTEGER, name TEXT);"), // no down!!! 142 | // 2 143 | M::up("CREATE TABLE animal_food (animal_id INTEGER, food_id INTEGER);") 144 | .down("DROP TABLE animal_food;"), 145 | ]; 146 | 147 | { 148 | let mut conn = Connection::open_in_memory().unwrap(); 149 | 150 | let migrations = Migrations::new(ms.clone()); 151 | 152 | migrations.to_latest(&mut conn).unwrap(); 153 | // Successful even on the second run (the most common case once migrations have been 154 | // applied) 155 | migrations.to_latest(&mut conn).unwrap(); 156 | 157 | assert_eq!( 158 | Ok(SchemaVersion::Inside(NonZeroUsize::new(3).unwrap())), 159 | migrations.current_version(&conn) 160 | ); 161 | 162 | conn.execute("INSERT INTO animals (name) VALUES (?1)", params!["Dog"]) 163 | .unwrap(); 164 | 165 | // go back 166 | assert!(migrations.to_version(&mut conn, 0).is_err()); // oops 167 | 168 | assert_eq!( 169 | Ok(SchemaVersion::Inside(NonZeroUsize::new(3).unwrap())), 170 | migrations.current_version(&conn) 171 | ); 172 | 173 | // one is fine 174 | assert!(migrations.to_version(&mut conn, 2).is_ok()); 175 | 176 | // boom 177 | assert_eq!( 178 | Err(rusqlite_migration::Error::MigrationDefinition( 179 | MigrationDefinitionError::DownNotDefined { migration_index: 1 } 180 | )), 181 | migrations.to_version(&mut conn, 1) 182 | ); 183 | } 184 | } 185 | --------------------------------------------------------------------------------