├── .github
└── workflows
│ ├── build.yml
│ ├── deploy_page.yml
│ └── publish.yml
├── .gitignore
├── .rustfmt.toml
├── Cargo.toml
├── LICENSE
├── Makefile.toml
├── README.MD
├── cliff.toml
├── examples
├── mysql
│ ├── main.rs
│ └── migrations
│ │ ├── m0001_simple.rs
│ │ ├── m0002_with_parents.rs
│ │ ├── m0003_use_macros.rs
│ │ ├── m0004_complex_operation.rs
│ │ ├── m0005_reference_complex.rs
│ │ └── mod.rs
├── postgres
│ ├── main.rs
│ └── migrations
│ │ ├── m0001_simple.rs
│ │ ├── m0002_with_parents.rs
│ │ ├── m0003_use_macros.rs
│ │ ├── m0004_complex_operation.rs
│ │ ├── m0005_reference_complex.rs
│ │ └── mod.rs
└── sqlite
│ ├── main.rs
│ └── migrations
│ ├── m0001_simple.rs
│ ├── m0002_with_parents.rs
│ ├── m0003_use_macros.rs
│ ├── m0004_complex_operation.rs
│ ├── m0005_reference_complex.rs
│ └── mod.rs
└── src
├── cli.rs
├── error.rs
├── lib.rs
├── macros.rs
├── migration.rs
├── migrator
├── any.rs
├── mod.rs
├── mysql.rs
├── postgres.rs
├── sqlite.rs
└── tests.rs
└── operation.rs
/.github/workflows/build.yml:
--------------------------------------------------------------------------------
1 | name: Build
2 |
3 | on:
4 | push:
5 | branches:
6 | - master
7 | pull_request:
8 |
9 | jobs:
10 | build:
11 | name: Build
12 | strategy:
13 | matrix:
14 | include:
15 | - os: "ubuntu-latest"
16 | rust-version: "stable"
17 | - os: "macos-latest"
18 | rust-version: "stable"
19 | - os: "windows-latest"
20 | rust-version: "stable"
21 | - os: "ubuntu-latest"
22 | rust-version: "beta"
23 | - os: "ubuntu-latest"
24 | rust-version: "nightly"
25 | runs-on: ${{ matrix.os }}
26 | env:
27 | MAKE_FEATURES_FLAG: "--all-features"
28 | defaults:
29 | run:
30 | shell: bash
31 |
32 | steps:
33 | - uses: actions/checkout@v4
34 | - name: Setup rust toolchain
35 | uses: dtolnay/rust-toolchain@master
36 | with:
37 | toolchain: ${{ matrix.rust-version }}
38 | components: rustfmt, clippy
39 | - name: Install cargo make
40 | uses: davidB/rust-cargo-make@v1.10.0
41 | - name: Create env file
42 | uses: iamsauravsharma/create-dotenv@v3.0.0
43 | with:
44 | input-prefix: "MAKE_"
45 | - name: Run tests
46 | run: |
47 | cargo make --env-file=.env full
48 |
49 | run_example:
50 | name: Run Example
51 | runs-on: "ubuntu-latest"
52 | needs: build
53 | env:
54 | SQLITE_DATABASE_URL: "db.sqlite3"
55 | POSTGRES_DATABASE_URL: postgresql://postgres:postgres@127.0.0.1:5432/postgres
56 | MYSQL_DATABASE_URL: mysql://root:mysql@127.0.0.1:3306/default_db
57 | services:
58 | postgres:
59 | image: postgres
60 | env:
61 | POSTGRES_PASSWORD: postgres
62 | ports:
63 | - 5432:5432
64 | options: --health-cmd pg_isready --health-interval 10s --health-timeout 5s --health-retries 5
65 | mysql:
66 | image: mysql
67 | env:
68 | MYSQL_ROOT_PASSWORD: mysql
69 | MYSQL_DATABASE: default_db
70 | ports:
71 | - 3306:3306
72 | options: --health-cmd="mysqladmin ping" --health-interval=10s --health-timeout=5s --health-retries=3
73 |
74 | steps:
75 | - uses: actions/checkout@v4
76 | - name: Setup rust toolchain
77 | uses: dtolnay/rust-toolchain@nightly
78 | - name: Install cargo make
79 | uses: davidB/rust-cargo-make@v1
80 | - name: Print cli help message
81 | run: |
82 | cargo make run_postgres_example --help
83 | cargo make run_postgres_example apply --help
84 | cargo make run_postgres_example drop --help
85 | cargo make run_postgres_example list --help
86 | cargo make run_postgres_example revert --help
87 | - name: Run postgres example
88 | run: |
89 | cargo make run_postgres_example apply
90 | cargo make run_postgres_example list
91 | cargo make run_postgres_example revert --all --force
92 | cargo make run_postgres_example list
93 | cargo make run_postgres_example drop
94 | - name: Run sqlite example
95 | run: |
96 | touch db.sqlite3
97 | cargo make run_sqlite_example apply
98 | cargo make run_sqlite_example list
99 | cargo make run_sqlite_example revert --all --force
100 | cargo make run_sqlite_example list
101 | cargo make run_sqlite_example drop
102 | - name: Run mysql example
103 | run: |
104 | cargo make run_mysql_example apply
105 | cargo make run_mysql_example list
106 | cargo make run_mysql_example revert --all --force
107 | cargo make run_mysql_example list
108 | cargo make run_mysql_example drop
109 |
--------------------------------------------------------------------------------
/.github/workflows/deploy_page.yml:
--------------------------------------------------------------------------------
1 | name: Deploy github pages
2 |
3 | on:
4 | push:
5 | branches:
6 | - master
7 |
8 | permissions:
9 | contents: write
10 |
11 | jobs:
12 | deploy_page:
13 | name: Deploy Github page
14 | runs-on: ubuntu-latest
15 | env:
16 | MAKE_FEATURES_FLAG: "--all-features"
17 | steps:
18 | - uses: actions/checkout@v4
19 | - name: Setup rust toolchain
20 | uses: dtolnay/rust-toolchain@nightly
21 | - name: Install cargo make
22 | uses: davidB/rust-cargo-make@v1
23 | - name: Create env file
24 | uses: iamsauravsharma/create-dotenv@v3.0.0
25 | with:
26 | input-prefix: "MAKE_"
27 | - name: Generate documentation
28 | run: |
29 | cargo make --env-file=.env rustdoc
30 | - name: Generate index page
31 | run: |
32 | echo "" > target/doc/index.html
33 | - name: Deploy GitHub Page
34 | uses: JamesIves/github-pages-deploy-action@v4
35 | with:
36 | folder: target/doc
37 |
--------------------------------------------------------------------------------
/.github/workflows/publish.yml:
--------------------------------------------------------------------------------
1 | name: Publish
2 | on:
3 | push:
4 | tags:
5 | - "v*"
6 |
7 | permissions:
8 | contents: write
9 |
10 | jobs:
11 | publish_crate:
12 | name: Publish to crates.io
13 | runs-on: ubuntu-latest
14 |
15 | steps:
16 | - uses: actions/checkout@v4
17 | - name: Setup rust toolchain
18 | uses: dtolnay/rust-toolchain@stable
19 | - name: Publish to crates.io
20 | env:
21 | CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }}
22 | run: |
23 | cargo publish --all-features
24 |
25 | publish_release_note:
26 | name: Publish release note
27 | runs-on: ubuntu-latest
28 | needs: publish_crate
29 |
30 | steps:
31 | - uses: actions/checkout@v4
32 | with:
33 | fetch-depth: 0
34 | - name: Generate a changelog
35 | uses: orhun/git-cliff-action@v3
36 | id: git-cliff
37 | with:
38 | config: cliff.toml
39 | args: -vv --current --strip header
40 | env:
41 | OUTPUT: CHANGELOG.md
42 | - name: Create GitHub release
43 | uses: softprops/action-gh-release@v2
44 | with:
45 | body_path: ${{ steps.git-cliff.outputs.changelog }}
46 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Rust related
2 | /target
3 | /Cargo.lock
4 |
5 | # MacOS related
6 | .DS_Store
7 |
8 | # VS code
9 | .vscode
10 |
--------------------------------------------------------------------------------
/.rustfmt.toml:
--------------------------------------------------------------------------------
1 | combine_control_expr = false
2 | condense_wildcard_suffixes = true
3 | edition = "2024"
4 | error_on_line_overflow = true
5 | error_on_unformatted = true
6 | force_multiline_blocks = true
7 | format_code_in_doc_comments = true
8 | format_macro_matchers = true
9 | format_strings = true
10 | group_imports = "StdExternalCrate"
11 | imports_granularity = "Module"
12 | normalize_comments = true
13 | normalize_doc_attributes = true
14 | reorder_impl_items = true
15 | style_edition = "2024"
16 | unstable_features = true
17 | use_field_init_shorthand = true
18 | use_try_shorthand = true
19 | wrap_comments = true
20 |
--------------------------------------------------------------------------------
/Cargo.toml:
--------------------------------------------------------------------------------
1 | [package]
2 | name = "sqlx_migrator"
3 | version = "0.17.0"
4 | edition = "2024"
5 | authors = ["Saurav Sharma "]
6 | homepage = "https://github.com/iamsauravsharma/sqlx_migrator"
7 | repository = "https://github.com/iamsauravsharma/sqlx_migrator"
8 | description = "Migrator for writing sqlx migration using Rust instead of SQL"
9 | license = "MIT"
10 | readme = "README.MD"
11 | keywords = ["sqlx", "sqlx_migrations", "rust_migrations"]
12 | categories = ["database"]
13 |
14 | [dependencies]
15 | sqlx = { version = "0.8.0", default-features = false, features = ["macros"] }
16 | async-trait = "0.1.70"
17 | tracing = { version = "0.1.37" }
18 | thiserror = "2.0.0"
19 | clap = { version = "4.3.10", features = ["derive"], optional = true }
20 | crc32fast = { version = "1.3.2", optional = true }
21 |
22 | [dev-dependencies]
23 | tokio = { version = "1.34.0", features = ["rt-multi-thread", "macros"] }
24 | sqlx = { version = "0.8.0", features = ["runtime-tokio", "tls-rustls"] }
25 |
26 | [features]
27 | default = ["cli"]
28 | cli = ["dep:clap"]
29 | postgres = ["sqlx/postgres", "dep:crc32fast"]
30 | sqlite = ["sqlx/sqlite"]
31 | mysql = ["sqlx/mysql", "dep:crc32fast"]
32 | any = ["sqlx/any"]
33 |
34 | [[example]]
35 | name = "postgres"
36 | path = "examples/postgres/main.rs"
37 | required-features = ["postgres", "cli"]
38 |
39 | [[example]]
40 | name = "sqlite"
41 | path = "examples/sqlite/main.rs"
42 | required-features = ["sqlite", "cli"]
43 |
44 | [[example]]
45 | name = "mysql"
46 | path = "examples/mysql/main.rs"
47 | required-features = ["mysql", "cli"]
48 |
49 | [package.metadata.docs.rs]
50 | all-features = true
51 | rustdoc-args = ["--cfg", "docsrs"]
52 |
53 | [lints.rust]
54 | missing_docs = "warn"
55 | unreachable_pub = "warn"
56 | unused_crate_dependencies = "warn"
57 | unsafe_code = "deny"
58 |
59 | [lints.clippy]
60 | all = "deny"
61 | pedantic = "warn"
62 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2022 Saurav Sharma
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/Makefile.toml:
--------------------------------------------------------------------------------
1 | [env]
2 | RUST_BACKTRACE = "full"
3 |
4 | [tasks.clean]
5 | description = "Clean target directory"
6 | command = "cargo"
7 | args = ["clean"]
8 |
9 | [tasks.build]
10 | description = "Run cargo build"
11 | command = "cargo"
12 | args = ["build", "--workspace", "@@split(FEATURES_FLAG, )"]
13 |
14 | [tasks.fmt]
15 | condition = { channels = ["nightly"] }
16 | description = "Check whether rust code is properly formatted or not"
17 | command = "cargo"
18 | args = ["fmt", "--", "--check"]
19 |
20 | [tasks.clippy]
21 | condition = { channels = ["nightly"] }
22 | description = "Check if clippy return any warnings or error"
23 | command = "cargo"
24 | args = [
25 | "clippy",
26 | "--workspace",
27 | "@@split(FEATURES_FLAG, )",
28 | "--",
29 | "-D",
30 | "warnings",
31 | ]
32 |
33 | [tasks.test]
34 | description = "Run test"
35 | command = "cargo"
36 | args = ["test", "--workspace", "@@split(FEATURES_FLAG, )"]
37 |
38 | [tasks.doc]
39 | description = "Run doc"
40 | command = "cargo"
41 | args = ["doc", "--workspace", "--no-deps", "@@split(FEATURES_FLAG, )"]
42 |
43 | [tasks.rustdoc]
44 | description = "Run rustdoc"
45 | command = "cargo"
46 | args = ["rustdoc", "--all-features", "--", "--cfg", "docsrs"]
47 |
48 | [tasks.local]
49 | dependencies = ["fmt", "build", "clippy", "doc", "test"]
50 |
51 | [tasks.full]
52 | dependencies = ["clean", "local"]
53 |
54 | [tasks.run_postgres_example]
55 | description = "run postgres example"
56 | command = "cargo"
57 | args = ["run", "--example", "postgres", "--features", "postgres", "--", "${@}"]
58 |
59 | [tasks.run_sqlite_example]
60 | description = "run postgres example"
61 | command = "cargo"
62 | args = ["run", "--example", "sqlite", "--features", "sqlite", "--", "${@}"]
63 |
64 | [tasks.run_mysql_example]
65 | description = "run postgres example"
66 | command = "cargo"
67 | args = ["run", "--example", "mysql", "--features", "mysql", "--", "${@}"]
68 |
--------------------------------------------------------------------------------
/README.MD:
--------------------------------------------------------------------------------
1 | # SQLX migrator
2 |
3 | A Rust library for writing SQLX migrations using Rust instead of SQL.
4 |
5 | | License | Crates Version | Docs |
6 | | :--------------------------------------------: | :---------------------------------------: | :----------------------------------: |
7 | | [![License: MIT][license_badge]][license_link] | [![Crate][cratesio_badge]][cratesio_link] | [![Docs][docsrs_badge]][docsrs_link] |
8 |
9 | Supported Databases:
10 |
11 | - [x] PostgreSQL
12 | - [x] SQLite
13 | - [x] MySql
14 | - [x] Any
15 |
16 | ## Installation
17 |
18 | Add `sqlx_migrator` to your `Cargo.toml` with the appropriate database feature:
19 |
20 | ```toml
21 | sqlx_migrator = { version = "0.17.0", features=["postgres"] }
22 | ```
23 |
24 | OR
25 |
26 | ```toml
27 | sqlx_migrator = { version = "0.17.0", features=["mysql"] }
28 | ```
29 |
30 | OR
31 |
32 | ```toml
33 | sqlx_migrator = { version = "0.17.0", features=["sqlite"] }
34 | ```
35 |
36 | OR
37 |
38 | ```toml
39 | sqlx_migrator = { version = "0.17.0", features=[
40 | "any",
41 | # Plus any one of above database driver
42 | ] }
43 | ```
44 |
45 | # Usage
46 |
47 | To use `sqlx_migrator`, implement the `Operation` trait to define your migration logic. Here's an example using PostgreSQL:
48 |
49 | ```rust
50 | use sqlx_migrator::error::Error;
51 | use sqlx_migrator::operation::Operation;
52 |
53 | pub(crate) struct FirstOperation;
54 |
55 | #[async_trait::async_trait]
56 | impl Operation for FirstOperation {
57 | // Up function runs apply migration
58 | async fn up(&self, connection: &mut sqlx::PgConnection) -> Result<(), Error> {
59 | sqlx::query("CREATE TABLE sample (id INTEGER PRIMARY KEY, name TEXT)")
60 | // NOTE: if you want to use connection multiple times pass `&mut *connection`
61 | // as a parameter instead of `connection`
62 | .execute(connection)
63 | .await?;
64 | Ok(())
65 | }
66 |
67 | // down migration runs down migration
68 | async fn down(&self, connection: &mut sqlx::PgConnection) -> Result<(), Error> {
69 | sqlx::query("DROP TABLE sample").execute(connection).await?;
70 | Ok(())
71 | }
72 | }
73 | ```
74 | After defining your operations, you can create a migration:
75 |
76 | ```rust
77 | use sqlx_migrator::error::Error;
78 | use sqlx_migrator::migration::Migration;
79 | use sqlx_migrator::operation::Operation;
80 |
81 | pub(crate) struct FirstMigration;
82 |
83 | impl Migration for FirstMigration {
84 | // app where migration lies can be any value
85 | fn app(&self) -> &str {
86 | "main"
87 | }
88 |
89 | // name of migration
90 | // Combination of migration app and name must be unique to work properly expects for virtual migration
91 | fn name(&self) -> &str {
92 | "first_migration"
93 | }
94 |
95 | // Use the parent function to add parents of a migration.
96 | // If you cannot access or create the parent migration easily, you can also use
97 | // `(A,N) where A: AsRef, N: AsRef` where A is the app name
98 | // and N is the name of the migration.
99 | fn parents(&self) -> Vec>> {
100 | vec![]
101 | // vec![("main", "initial_migration"), AnotherInitialMigration]
102 | }
103 |
104 | // use operations function to add operation part of migration
105 | fn operations(&self) -> Vec>> {
106 | vec![Box::new(FirstOperation)]
107 | }
108 |
109 | // Migration trait also have multiple other function see docs for usage
110 | }
111 | ```
112 |
113 | This migration can be represented in a simpler form using macros:
114 | ```rust
115 | use sqlx_migrator::vec_box;
116 | sqlx_migrator::migration!(
117 | sqlx::Postgres,
118 | FirstMigration,
119 | "main",
120 | "first_migration",
121 | vec_box![],
122 | vec_box![FirstOperation]
123 | );
124 | // OR
125 | sqlx_migrator::postgres_migration!(
126 | FirstMigration,
127 | "main",
128 | "first_migration",
129 | vec_box![],
130 | vec_box![FirstOperation]
131 | );
132 | ```
133 |
134 | If your up and down queries are simple strings, you can simplify the implementation:
135 | ```rust
136 | sqlx_migrator::postgres_migration!(
137 | FirstMigration,
138 | "main",
139 | "first_migration",
140 | sqlx_migrator::vec_box![],
141 | sqlx_migrator::vec_box![
142 | (
143 | "CREATE TABLE sample (id INTEGER PRIMARY KEY, name TEXT)",
144 | "DROP TABLE sample"
145 | )
146 | ]
147 | );
148 | ```
149 |
150 | Finally, create a migrator to run your migrations:
151 |
152 | ```rust
153 | use sqlx_migrator::migrator::{Info, Migrate, Migrator};
154 | use sqlx::Postgres;
155 |
156 | #[tokio::main]
157 | async fn main() {
158 | let uri = std::env::var("DATABASE_URL").unwrap();
159 | let pool = sqlx::Pool::::connect(&uri).await.unwrap();
160 | let mut migrator = Migrator::default();
161 | // Adding migration can fail if another migration with same app and name and different values gets added
162 | // Adding migrations add its parents, replaces and not before as well
163 | migrator.add_migration(Box::new(FirstMigration)).unwrap();
164 | }
165 | ```
166 |
167 | # Running Migrations
168 |
169 | You can run migrations directly or integrate them into a CLI:
170 | ## Programmatic Execution
171 | ```rust
172 | use sqlx_migrator::migrator::Plan;
173 | let mut conn = pool.acquire().await?;
174 | // use apply all to apply all pending migration
175 | migrator.run(&mut *conn, Plan::apply_all()).await.unwrap();
176 | // or use revert all to revert all applied migrations
177 | migrator.run(&mut *conn, Plan::revert_all()).await.unwrap();
178 | // If you need to apply or revert to certain stage than see `Plan` docs
179 | ```
180 |
181 | ## CLI Integration
182 | To integrate sqlx_migrator into your CLI, you can either use the built-in
183 | `MigrationCommand` or extend your own CLI with migrator support. Below are
184 | examples of both approaches:
185 |
186 | #### Built-in Migration Command
187 |
188 | ```rust
189 | use sqlx_migrator::cli::MigrationCommand;
190 |
191 | MigrationCommand::parse_and_run(&mut *conn, Box::new(migrator)).await.unwrap();
192 | ```
193 |
194 | #### Extending Your Own CLI with Migrator Support
195 |
196 | ```rust
197 | #[derive(clap::Parser)]
198 | struct Cli {
199 | #[command(subcommand)]
200 | sub_command: CliSubcommand
201 | }
202 |
203 | #[derive(clap::Subcommand)]
204 | enum CliSubcommand {
205 | #[command()]
206 | Migrator(sqlx_migrator::cli::MigrationCommand)
207 | }
208 |
209 | impl Cli {
210 | async fn run() {
211 | let cli = Self::parse();
212 | // create connection
213 | match cli.sub_command {
214 | Migrator(m) => {
215 | m.run(&mut conn, Box::new(migrator)).await.unwrap()
216 | }
217 | }
218 | }
219 | }
220 | ```
221 |
222 | # Migrate from sqlx default sql based migration
223 |
224 | To migrate from sqlx sql based migration to rust migration the recommended approach
225 | is to rewrite your SQL migrations as Rust operations and migrations as explained above.
226 | After rewriting your SQL migrations, you need to mark them as applied without re-executing them.
227 | This step ensures that the migration state aligns with the existing database.
228 | There are two ways to perform a fake apply:
229 |
230 | #### Programmatic Fake Apply
231 |
232 | Use the fake option with the `Plan::apply_all()` function:
233 | ```rust
234 | use sqlx_migrator::migrator::Plan;
235 |
236 | migrator.run(&mut *conn, Plan::apply_all().fake(true)).await.unwrap();
237 | ```
238 |
239 | #### CLI-Based Fake Apply
240 | If you're using a CLI, use the --fake flag with the apply command: ` apply --fake`
241 |
242 | ### Note: Before writing any other migrations
243 |
244 | Before adding new migrations for future updates, ensure you complete the above steps to mark existing migrations as applied. Run the fake apply only once to align the migration state. After this, remove the `fake(true)` option or the `--fake` flag to allow new migrations to execute normally.
245 |
246 | By following these steps, you can seamlessly transition from SQLX SQL-based migrations to Rust migrations while maintaining an accurate migration state and ensuring compatibility for future updates.
247 |
248 | [license_badge]: https://img.shields.io/github/license/iamsauravsharma/sqlx_migrator.svg?style=for-the-badge
249 | [license_link]: LICENSE
250 | [cratesio_badge]: https://img.shields.io/crates/v/sqlx_migrator.svg?style=for-the-badge
251 | [cratesio_link]: https://crates.io/crates/sqlx_migrator
252 | [docsrs_badge]: https://img.shields.io/docsrs/sqlx_migrator/latest?style=for-the-badge
253 | [docsrs_link]: https://docs.rs/sqlx_migrator
254 |
--------------------------------------------------------------------------------
/cliff.toml:
--------------------------------------------------------------------------------
1 | # git-cliff ~ default configuration file
2 | # https://git-cliff.org/docs/configuration
3 | #
4 | # Lines starting with "#" are comments.
5 | # Configuration options are organized into tables and keys.
6 | # See documentation for more information on available options.
7 |
8 | [changelog]
9 | # changelog header
10 | header = """
11 | # Changelog\n
12 | All notable changes to this project will be documented in this file.\n
13 | """
14 | # template for the changelog body
15 | # https://tera.netlify.app/docs
16 | body = """
17 | {% if version %}\
18 | ## [{{ version | trim_start_matches(pat="v") }}] - {{ timestamp | date(format="%Y-%m-%d") }}
19 | {% else %}\
20 | ## [unreleased]
21 | {% endif %}\
22 | {% for group, commits in commits | group_by(attribute="group") %}
23 | ### {{ group | upper_first }}
24 | {% for commit in commits %}
25 | - {% if commit.breaking %}[**breaking**] {% endif %}{{ commit.message | upper_first }}\
26 | {% endfor %}
27 | {% endfor %}\n
28 | """
29 | # remove the leading and trailing whitespace from the template
30 | trim = true
31 | # changelog footer
32 | footer = """
33 |
34 | """
35 |
36 | [git]
37 | # parse the commits based on https://www.conventionalcommits.org
38 | conventional_commits = true
39 | # filter out the commits that are not conventional
40 | filter_unconventional = true
41 | # process each line of a commit as an individual commit
42 | split_commits = false
43 | # regex for preprocessing the commit messages
44 | commit_preprocessors = [
45 | # { pattern = '\((\w+\s)?#([0-9]+)\)', replace = "([#${2}](https://github.com/orhun/git-cliff/issues/${2}))"}, # replace issue numbers
46 | ]
47 | # regex for parsing and grouping commits
48 | commit_parsers = [
49 | { message = "^feat", group = "Features" },
50 | { message = "^fix", group = "Bug Fixes" },
51 | { message = "^doc", group = "Documentation" },
52 | { message = "^perf", group = "Performance" },
53 | { message = "^refactor", group = "Refactor" },
54 | { message = "^style", group = "Styling" },
55 | { message = "^test", group = "Testing" },
56 | { message = "^chore\\(release\\): prepare for", skip = true },
57 | { message = "^chore", group = "Miscellaneous Tasks" },
58 | { body = ".*security", group = "Security" },
59 | ]
60 | # protect breaking changes from being skipped due to matching a skipping commit_parser
61 | protect_breaking_commits = false
62 | # filter out the commits that are not matched by commit parsers
63 | filter_commits = false
64 | # glob pattern for matching git tags
65 | tag_pattern = "v[0-9]*"
66 | # regex for skipping tags
67 | skip_tags = "v0.1.0-beta.1"
68 | # regex for ignoring tags
69 | ignore_tags = ""
70 | # sort the tags topologically
71 | topo_order = false
72 | # sort the commits inside sections by oldest/newest order
73 | sort_commits = "oldest"
74 | # limit the number of commits included in the changelog.
75 | # limit_commits = 42
76 |
--------------------------------------------------------------------------------
/examples/mysql/main.rs:
--------------------------------------------------------------------------------
1 | #![expect(unused_crate_dependencies)]
2 | //! Example crate for mysql
3 | use sqlx::MySql;
4 | use sqlx_migrator::cli::MigrationCommand;
5 | use sqlx_migrator::migrator::{Info, Migrator};
6 |
7 | mod migrations;
8 |
9 | #[tokio::main]
10 | async fn main() {
11 | let uri = std::env::var("MYSQL_DATABASE_URL").unwrap();
12 | let pool = sqlx::Pool::::connect(&uri).await.unwrap();
13 | let mut migrator = Migrator::default();
14 | migrator.add_migrations(migrations::migrations()).unwrap();
15 | // There are two way to run migration. Either you can create cli as shown below
16 | let mut conn = pool.acquire().await.unwrap();
17 | MigrationCommand::parse_and_run(&mut *conn, Box::new(migrator))
18 | .await
19 | .unwrap();
20 | // Or you can directly use migrator run function instead of creating
21 | // cli
22 | // migrator
23 | // .run(&mut *conn, sqlx_migrator::migrator::Plan::apply_all())
24 | // .await
25 | // .unwrap();
26 | conn.close().await.unwrap();
27 | }
28 |
--------------------------------------------------------------------------------
/examples/mysql/migrations/m0001_simple.rs:
--------------------------------------------------------------------------------
1 | use sqlx::{MySql, MySqlConnection};
2 | use sqlx_migrator::error::Error;
3 | use sqlx_migrator::migration::Migration;
4 | use sqlx_migrator::operation::Operation;
5 |
6 | pub(crate) struct M0001Operation;
7 |
8 | #[async_trait::async_trait]
9 | impl Operation for M0001Operation {
10 | async fn up(&self, connection: &mut MySqlConnection) -> Result<(), Error> {
11 | sqlx::query("CREATE TABLE sample (id INTEGER PRIMARY KEY, name TEXT)")
12 | .execute(&mut *connection)
13 | .await?;
14 | Ok(())
15 | }
16 |
17 | async fn down(&self, connection: &mut MySqlConnection) -> Result<(), Error> {
18 | sqlx::query("DROP TABLE sample").execute(connection).await?;
19 | Ok(())
20 | }
21 | }
22 |
23 | pub(crate) struct M0001Migration;
24 |
25 | impl Migration for M0001Migration {
26 | fn app(&self) -> &'static str {
27 | "main"
28 | }
29 |
30 | fn name(&self) -> &'static str {
31 | "m0001_simple"
32 | }
33 |
34 | fn parents(&self) -> Vec>> {
35 | vec![]
36 | }
37 |
38 | fn operations(&self) -> Vec>> {
39 | vec![Box::new(M0001Operation)]
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/examples/mysql/migrations/m0002_with_parents.rs:
--------------------------------------------------------------------------------
1 | use sqlx::{MySql, MySqlConnection};
2 | use sqlx_migrator::error::Error;
3 | use sqlx_migrator::migration::Migration;
4 | use sqlx_migrator::operation::Operation;
5 |
6 | pub(crate) struct M0002Operation;
7 |
8 | #[async_trait::async_trait]
9 | impl Operation for M0002Operation {
10 | async fn up(&self, connection: &mut MySqlConnection) -> Result<(), Error> {
11 | sqlx::query("INSERT INTO sample (id, name) VALUES (99, 'Some text')")
12 | .execute(connection)
13 | .await?;
14 | Ok(())
15 | }
16 |
17 | async fn down(&self, connection: &mut MySqlConnection) -> Result<(), Error> {
18 | sqlx::query("DELETE FROM sample WHERE id = 99")
19 | .execute(connection)
20 | .await?;
21 | Ok(())
22 | }
23 | }
24 |
25 | pub(crate) struct M0002Migration;
26 |
27 | impl Migration for M0002Migration {
28 | fn app(&self) -> &'static str {
29 | "main"
30 | }
31 |
32 | fn name(&self) -> &'static str {
33 | "m0002_with_parents"
34 | }
35 |
36 | fn parents(&self) -> Vec>> {
37 | vec![Box::new(crate::migrations::m0001_simple::M0001Migration)]
38 | }
39 |
40 | fn operations(&self) -> Vec>> {
41 | vec![Box::new(M0002Operation)]
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/examples/mysql/migrations/m0003_use_macros.rs:
--------------------------------------------------------------------------------
1 | pub(crate) struct M0003Migration;
2 |
3 | sqlx_migrator::mysql_migration!(
4 | M0003Migration,
5 | "main",
6 | "m0003_use_macros",
7 | sqlx_migrator::vec_box![("main", "m0002_with_parents")],
8 | sqlx_migrator::vec_box![(
9 | "INSERT INTO sample (id, name) VALUES (999, 'Another text')",
10 | "DELETE FROM sample WHERE id = 999"
11 | )]
12 | );
13 |
--------------------------------------------------------------------------------
/examples/mysql/migrations/m0004_complex_operation.rs:
--------------------------------------------------------------------------------
1 | use sqlx::{MySql, MySqlConnection};
2 | use sqlx_migrator::error::Error;
3 | use sqlx_migrator::migration::Migration;
4 | use sqlx_migrator::operation::Operation;
5 |
6 | pub(crate) struct M0004Operation {
7 | id: i32,
8 | message: String,
9 | }
10 |
11 | #[async_trait::async_trait]
12 | impl Operation for M0004Operation {
13 | async fn up(&self, connection: &mut MySqlConnection) -> Result<(), Error> {
14 | sqlx::query("INSERT INTO sample (id, name) VALUES (?, ?)")
15 | .bind(self.id)
16 | .bind(&self.message)
17 | .execute(connection)
18 | .await?;
19 | Ok(())
20 | }
21 |
22 | async fn down(&self, connection: &mut MySqlConnection) -> Result<(), Error> {
23 | sqlx::query("DELETE FROM sample WHERE id = ?")
24 | .bind(self.id)
25 | .execute(connection)
26 | .await?;
27 | Ok(())
28 | }
29 | }
30 |
31 | pub(crate) struct M0004Migration {
32 | pub(crate) id: i32,
33 | pub(crate) message: String,
34 | }
35 |
36 | impl Migration for M0004Migration {
37 | fn app(&self) -> &'static str {
38 | "main"
39 | }
40 |
41 | fn name(&self) -> &'static str {
42 | "m0004_complex_operation"
43 | }
44 |
45 | fn parents(&self) -> Vec>> {
46 | vec![Box::new(
47 | crate::migrations::m0003_use_macros::M0003Migration,
48 | )]
49 | }
50 |
51 | fn operations(&self) -> Vec>> {
52 | vec![Box::new(M0004Operation {
53 | id: self.id,
54 | message: self.message.clone(),
55 | })]
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/examples/mysql/migrations/m0005_reference_complex.rs:
--------------------------------------------------------------------------------
1 | use sqlx::{MySql, MySqlConnection};
2 | use sqlx_migrator::error::Error;
3 | use sqlx_migrator::migration::Migration;
4 | use sqlx_migrator::operation::Operation;
5 |
6 | pub(crate) struct M0005Operation;
7 |
8 | #[async_trait::async_trait]
9 | impl Operation for M0005Operation {
10 | async fn up(&self, connection: &mut MySqlConnection) -> Result<(), Error> {
11 | sqlx::query("INSERT INTO sample (id, name) VALUES (888, 'complex')")
12 | .execute(connection)
13 | .await?;
14 | Ok(())
15 | }
16 |
17 | async fn down(&self, connection: &mut MySqlConnection) -> Result<(), Error> {
18 | sqlx::query("DELETE FROM sample WHERE id = 888")
19 | .execute(connection)
20 | .await?;
21 | Ok(())
22 | }
23 | }
24 |
25 | pub(crate) struct M0005Migration;
26 |
27 | impl Migration for M0005Migration {
28 | fn app(&self) -> &'static str {
29 | "main"
30 | }
31 |
32 | fn name(&self) -> &'static str {
33 | "m0005_reference_complex"
34 | }
35 |
36 | fn parents(&self) -> Vec>> {
37 | vec![Box::new(("main", "m0004_complex_operation"))]
38 | }
39 |
40 | fn operations(&self) -> Vec>> {
41 | vec![Box::new(M0005Operation)]
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/examples/mysql/migrations/mod.rs:
--------------------------------------------------------------------------------
1 | use sqlx::MySql;
2 | use sqlx_migrator::migration::Migration;
3 | use sqlx_migrator::vec_box;
4 |
5 | pub(crate) mod m0001_simple;
6 | pub(crate) mod m0002_with_parents;
7 | pub(crate) mod m0003_use_macros;
8 | pub(crate) mod m0004_complex_operation;
9 | pub(crate) mod m0005_reference_complex;
10 |
11 | pub(crate) fn migrations() -> Vec>> {
12 | vec_box![
13 | m0001_simple::M0001Migration,
14 | m0002_with_parents::M0002Migration,
15 | m0003_use_macros::M0003Migration,
16 | m0004_complex_operation::M0004Migration {
17 | id: 23,
18 | message: "Custom String".to_string()
19 | },
20 | m0005_reference_complex::M0005Migration,
21 | ]
22 | }
23 |
--------------------------------------------------------------------------------
/examples/postgres/main.rs:
--------------------------------------------------------------------------------
1 | #![expect(unused_crate_dependencies)]
2 | //! Example crate for postgres
3 | use sqlx::Postgres;
4 | use sqlx_migrator::cli::MigrationCommand;
5 | use sqlx_migrator::migrator::{Info, Migrator};
6 |
7 | mod migrations;
8 | #[tokio::main]
9 | async fn main() {
10 | let uri = std::env::var("POSTGRES_DATABASE_URL").unwrap();
11 | let pool = sqlx::Pool::::connect(&uri).await.unwrap();
12 | sqlx::query("CREATE SCHEMA IF NOT EXISTS random_schema_name")
13 | .execute(&pool)
14 | .await
15 | .unwrap();
16 | let mut migrator = Migrator::default()
17 | .set_table_prefix("prefix")
18 | .unwrap()
19 | .set_schema("random_schema_name")
20 | .unwrap();
21 | migrator.add_migrations(migrations::migrations()).unwrap();
22 | // There are two way to run migration. Either you can create cli as shown below
23 | let mut conn = pool.acquire().await.unwrap();
24 | MigrationCommand::parse_and_run(&mut *conn, Box::new(migrator))
25 | .await
26 | .unwrap();
27 | // Or you can directly use migrator run function instead of creating
28 | // cli
29 | // migrator
30 | // .run(&mut *conn, sqlx_migrator::migrator::Plan::apply_all())
31 | // .await
32 | // .unwrap();
33 | conn.close().await.unwrap();
34 | }
35 |
--------------------------------------------------------------------------------
/examples/postgres/migrations/m0001_simple.rs:
--------------------------------------------------------------------------------
1 | use sqlx::{PgConnection, Postgres};
2 | use sqlx_migrator::error::Error;
3 | use sqlx_migrator::migration::Migration;
4 | use sqlx_migrator::operation::Operation;
5 |
6 | pub(crate) struct M0001Operation;
7 |
8 | #[async_trait::async_trait]
9 | impl Operation for M0001Operation {
10 | async fn up(&self, connection: &mut PgConnection) -> Result<(), Error> {
11 | sqlx::query("CREATE TABLE sample (id INTEGER PRIMARY KEY, name TEXT)")
12 | .execute(connection)
13 | .await?;
14 | Ok(())
15 | }
16 |
17 | async fn down(&self, connection: &mut PgConnection) -> Result<(), Error> {
18 | sqlx::query("DROP TABLE sample").execute(connection).await?;
19 | Ok(())
20 | }
21 | }
22 |
23 | pub(crate) struct M0001Migration;
24 |
25 | impl Migration for M0001Migration {
26 | fn app(&self) -> &'static str {
27 | "main"
28 | }
29 |
30 | fn name(&self) -> &'static str {
31 | "m0001_simple"
32 | }
33 |
34 | fn parents(&self) -> Vec>> {
35 | vec![]
36 | }
37 |
38 | fn operations(&self) -> Vec>> {
39 | vec![Box::new(M0001Operation)]
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/examples/postgres/migrations/m0002_with_parents.rs:
--------------------------------------------------------------------------------
1 | use sqlx::{PgConnection, Postgres};
2 | use sqlx_migrator::error::Error;
3 | use sqlx_migrator::migration::Migration;
4 | use sqlx_migrator::operation::Operation;
5 |
6 | pub(crate) struct M0002Operation;
7 |
8 | #[async_trait::async_trait]
9 | impl Operation for M0002Operation {
10 | async fn up(&self, connection: &mut PgConnection) -> Result<(), Error> {
11 | sqlx::query("INSERT INTO sample (id, name) VALUES (99, 'Some text')")
12 | .execute(connection)
13 | .await?;
14 | Ok(())
15 | }
16 |
17 | async fn down(&self, connection: &mut PgConnection) -> Result<(), Error> {
18 | sqlx::query("DELETE FROM sample WHERE id = 99")
19 | .execute(connection)
20 | .await?;
21 | Ok(())
22 | }
23 | }
24 |
25 | pub(crate) struct M0002Migration;
26 |
27 | impl Migration for M0002Migration {
28 | fn app(&self) -> &'static str {
29 | "main"
30 | }
31 |
32 | fn name(&self) -> &'static str {
33 | "m0002_with_parents"
34 | }
35 |
36 | fn parents(&self) -> Vec>> {
37 | vec![Box::new(crate::migrations::m0001_simple::M0001Migration)]
38 | }
39 |
40 | fn operations(&self) -> Vec>> {
41 | vec![Box::new(M0002Operation)]
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/examples/postgres/migrations/m0003_use_macros.rs:
--------------------------------------------------------------------------------
1 | pub(crate) struct M0003Migration;
2 |
3 | sqlx_migrator::postgres_migration!(
4 | M0003Migration,
5 | "main",
6 | "m0003_use_macros",
7 | sqlx_migrator::vec_box![("main", "m0002_with_parents")],
8 | sqlx_migrator::vec_box![(
9 | "INSERT INTO sample (id, name) VALUES (999, 'Another text')",
10 | "DELETE FROM sample WHERE id = 999"
11 | )]
12 | );
13 |
--------------------------------------------------------------------------------
/examples/postgres/migrations/m0004_complex_operation.rs:
--------------------------------------------------------------------------------
1 | use sqlx::{PgConnection, Postgres};
2 | use sqlx_migrator::error::Error;
3 | use sqlx_migrator::migration::Migration;
4 | use sqlx_migrator::operation::Operation;
5 |
6 | pub(crate) struct M0004Operation {
7 | id: i32,
8 | message: String,
9 | }
10 |
11 | #[async_trait::async_trait]
12 | impl Operation for M0004Operation {
13 | async fn up(&self, connection: &mut PgConnection) -> Result<(), Error> {
14 | sqlx::query("INSERT INTO sample (id, name) VALUES ($1, $2)")
15 | .bind(self.id)
16 | .bind(&self.message)
17 | .execute(connection)
18 | .await?;
19 | Ok(())
20 | }
21 |
22 | async fn down(&self, connection: &mut PgConnection) -> Result<(), Error> {
23 | sqlx::query("DELETE FROM sample WHERE id = $1")
24 | .bind(self.id)
25 | .execute(connection)
26 | .await?;
27 | Ok(())
28 | }
29 | }
30 |
31 | pub(crate) struct M0004Migration {
32 | pub(crate) id: i32,
33 | pub(crate) message: String,
34 | }
35 |
36 | impl Migration for M0004Migration {
37 | fn app(&self) -> &'static str {
38 | "main"
39 | }
40 |
41 | fn name(&self) -> &'static str {
42 | "m0004_complex_operation"
43 | }
44 |
45 | fn parents(&self) -> Vec>> {
46 | vec![Box::new(
47 | crate::migrations::m0003_use_macros::M0003Migration,
48 | )]
49 | }
50 |
51 | fn operations(&self) -> Vec>> {
52 | vec![Box::new(M0004Operation {
53 | id: self.id,
54 | message: self.message.clone(),
55 | })]
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/examples/postgres/migrations/m0005_reference_complex.rs:
--------------------------------------------------------------------------------
1 | use sqlx::{PgConnection, Postgres};
2 | use sqlx_migrator::error::Error;
3 | use sqlx_migrator::migration::Migration;
4 | use sqlx_migrator::operation::Operation;
5 |
6 | pub(crate) struct M0005Operation;
7 |
8 | #[async_trait::async_trait]
9 | impl Operation for M0005Operation {
10 | async fn up(&self, connection: &mut PgConnection) -> Result<(), Error> {
11 | sqlx::query("INSERT INTO sample (id, name) VALUES (888, 'complex')")
12 | .execute(connection)
13 | .await?;
14 | Ok(())
15 | }
16 |
17 | async fn down(&self, connection: &mut PgConnection) -> Result<(), Error> {
18 | sqlx::query("DELETE FROM sample WHERE id = 888")
19 | .execute(connection)
20 | .await?;
21 | Ok(())
22 | }
23 | }
24 |
25 | pub(crate) struct M0005Migration;
26 |
27 | impl Migration for M0005Migration {
28 | fn app(&self) -> &'static str {
29 | "main"
30 | }
31 |
32 | fn name(&self) -> &'static str {
33 | "m0005_reference_complex"
34 | }
35 |
36 | fn parents(&self) -> Vec>> {
37 | vec![Box::new(("main", "m0004_complex_operation"))]
38 | }
39 |
40 | fn operations(&self) -> Vec>> {
41 | vec![Box::new(M0005Operation)]
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/examples/postgres/migrations/mod.rs:
--------------------------------------------------------------------------------
1 | use sqlx::Postgres;
2 | use sqlx_migrator::migration::Migration;
3 | use sqlx_migrator::vec_box;
4 |
5 | pub(crate) mod m0001_simple;
6 | pub(crate) mod m0002_with_parents;
7 | pub(crate) mod m0003_use_macros;
8 | pub(crate) mod m0004_complex_operation;
9 | pub(crate) mod m0005_reference_complex;
10 |
11 | pub(crate) fn migrations() -> Vec>> {
12 | vec_box![
13 | m0001_simple::M0001Migration,
14 | m0002_with_parents::M0002Migration,
15 | m0003_use_macros::M0003Migration,
16 | m0004_complex_operation::M0004Migration {
17 | id: 23,
18 | message: "Custom String".to_string()
19 | },
20 | m0005_reference_complex::M0005Migration,
21 | ]
22 | }
23 |
--------------------------------------------------------------------------------
/examples/sqlite/main.rs:
--------------------------------------------------------------------------------
1 | #![expect(unused_crate_dependencies)]
2 | //! Example crate for sqlite
3 | use sqlx::Sqlite;
4 | use sqlx_migrator::cli::MigrationCommand;
5 | use sqlx_migrator::migrator::{Info, Migrator};
6 |
7 | mod migrations;
8 | #[tokio::main]
9 | async fn main() {
10 | let uri = std::env::var("SQLITE_DATABASE_URL").unwrap();
11 | let pool = sqlx::Pool::::connect(&uri).await.unwrap();
12 | let mut migrator = Migrator::default();
13 | migrator.add_migrations(migrations::migrations()).unwrap();
14 | // There are two way to run migration. Either you can create cli as shown below
15 | let mut conn = pool.acquire().await.unwrap();
16 | MigrationCommand::parse_and_run(&mut *conn, Box::new(migrator))
17 | .await
18 | .unwrap();
19 | // Or you can directly use migrator run function instead of creating
20 | // cli
21 | // migrator
22 | // .run(&mut *conn, sqlx_migrator::migrator::Plan::apply_all())
23 | // .await
24 | // .unwrap();
25 | conn.close().await.unwrap();
26 | }
27 |
--------------------------------------------------------------------------------
/examples/sqlite/migrations/m0001_simple.rs:
--------------------------------------------------------------------------------
1 | use sqlx::{Sqlite, SqliteConnection};
2 | use sqlx_migrator::error::Error;
3 | use sqlx_migrator::migration::Migration;
4 | use sqlx_migrator::operation::Operation;
5 |
6 | pub(crate) struct M0001Operation;
7 |
8 | #[async_trait::async_trait]
9 | impl Operation for M0001Operation {
10 | async fn up(&self, connection: &mut SqliteConnection) -> Result<(), Error> {
11 | sqlx::query("CREATE TABLE sample (id INTEGER PRIMARY KEY, name TEXT)")
12 | .execute(connection)
13 | .await?;
14 | Ok(())
15 | }
16 |
17 | async fn down(&self, connection: &mut SqliteConnection) -> Result<(), Error> {
18 | sqlx::query("DROP TABLE sample").execute(connection).await?;
19 | Ok(())
20 | }
21 | }
22 |
23 | pub(crate) struct M0001Migration;
24 |
25 | impl Migration for M0001Migration {
26 | fn app(&self) -> &'static str {
27 | "main"
28 | }
29 |
30 | fn name(&self) -> &'static str {
31 | "m0001_simple"
32 | }
33 |
34 | fn parents(&self) -> Vec>> {
35 | vec![]
36 | }
37 |
38 | fn operations(&self) -> Vec>> {
39 | vec![Box::new(M0001Operation)]
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/examples/sqlite/migrations/m0002_with_parents.rs:
--------------------------------------------------------------------------------
1 | use sqlx::{Sqlite, SqliteConnection};
2 | use sqlx_migrator::error::Error;
3 | use sqlx_migrator::migration::Migration;
4 | use sqlx_migrator::operation::Operation;
5 |
6 | pub(crate) struct M0002Operation;
7 |
8 | #[async_trait::async_trait]
9 | impl Operation for M0002Operation {
10 | async fn up(&self, connection: &mut SqliteConnection) -> Result<(), Error> {
11 | sqlx::query("INSERT INTO sample (id, name) VALUES (99, 'Some text')")
12 | .execute(connection)
13 | .await?;
14 | Ok(())
15 | }
16 |
17 | async fn down(&self, connection: &mut SqliteConnection) -> Result<(), Error> {
18 | sqlx::query("DELETE FROM sample WHERE id = 99")
19 | .execute(connection)
20 | .await?;
21 | Ok(())
22 | }
23 | }
24 |
25 | pub(crate) struct M0002Migration;
26 |
27 | impl Migration for M0002Migration {
28 | fn app(&self) -> &'static str {
29 | "main"
30 | }
31 |
32 | fn name(&self) -> &'static str {
33 | "m0002_with_parents"
34 | }
35 |
36 | fn parents(&self) -> Vec>> {
37 | vec![Box::new(crate::migrations::m0001_simple::M0001Migration)]
38 | }
39 |
40 | fn operations(&self) -> Vec>> {
41 | vec![Box::new(M0002Operation)]
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/examples/sqlite/migrations/m0003_use_macros.rs:
--------------------------------------------------------------------------------
1 | pub(crate) struct M0003Migration;
2 |
3 | sqlx_migrator::sqlite_migration!(
4 | M0003Migration,
5 | "main",
6 | "m0003_use_macros",
7 | sqlx_migrator::vec_box![("main", "m0002_with_parents")],
8 | sqlx_migrator::vec_box![(
9 | "INSERT INTO sample (id, name) VALUES (999, 'Another text')",
10 | "DELETE FROM sample WHERE id = 999"
11 | )]
12 | );
13 |
--------------------------------------------------------------------------------
/examples/sqlite/migrations/m0004_complex_operation.rs:
--------------------------------------------------------------------------------
1 | use sqlx::{Sqlite, SqliteConnection};
2 | use sqlx_migrator::error::Error;
3 | use sqlx_migrator::migration::Migration;
4 | use sqlx_migrator::operation::Operation;
5 |
6 | pub(crate) struct M0004Operation {
7 | id: i32,
8 | message: String,
9 | }
10 |
11 | #[async_trait::async_trait]
12 | impl Operation for M0004Operation {
13 | async fn up(&self, connection: &mut SqliteConnection) -> Result<(), Error> {
14 | sqlx::query("INSERT INTO sample (id, name) VALUES (?, ?)")
15 | .bind(self.id)
16 | .bind(&self.message)
17 | .execute(connection)
18 | .await?;
19 | Ok(())
20 | }
21 |
22 | async fn down(&self, connection: &mut SqliteConnection) -> Result<(), Error> {
23 | sqlx::query("DELETE FROM sample WHERE id = ?")
24 | .bind(self.id)
25 | .execute(connection)
26 | .await?;
27 | Ok(())
28 | }
29 | }
30 |
31 | pub(crate) struct M0004Migration {
32 | pub(crate) id: i32,
33 | pub(crate) message: String,
34 | }
35 |
36 | impl Migration for M0004Migration {
37 | fn app(&self) -> &'static str {
38 | "main"
39 | }
40 |
41 | fn name(&self) -> &'static str {
42 | "m0004_complex_operation"
43 | }
44 |
45 | fn parents(&self) -> Vec>> {
46 | vec![Box::new(
47 | crate::migrations::m0003_use_macros::M0003Migration,
48 | )]
49 | }
50 |
51 | fn operations(&self) -> Vec>> {
52 | vec![Box::new(M0004Operation {
53 | id: self.id,
54 | message: self.message.clone(),
55 | })]
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/examples/sqlite/migrations/m0005_reference_complex.rs:
--------------------------------------------------------------------------------
1 | use sqlx::{Sqlite, SqliteConnection};
2 | use sqlx_migrator::error::Error;
3 | use sqlx_migrator::migration::Migration;
4 | use sqlx_migrator::operation::Operation;
5 |
6 | pub(crate) struct M0005Operation;
7 |
8 | #[async_trait::async_trait]
9 | impl Operation for M0005Operation {
10 | async fn up(&self, connection: &mut SqliteConnection) -> Result<(), Error> {
11 | sqlx::query("INSERT INTO sample (id, name) VALUES (888, 'complex')")
12 | .execute(connection)
13 | .await?;
14 | Ok(())
15 | }
16 |
17 | async fn down(&self, connection: &mut SqliteConnection) -> Result<(), Error> {
18 | sqlx::query("DELETE FROM sample WHERE id = 888")
19 | .execute(connection)
20 | .await?;
21 | Ok(())
22 | }
23 | }
24 |
25 | pub(crate) struct M0005Migration;
26 |
27 | impl Migration for M0005Migration {
28 | fn app(&self) -> &'static str {
29 | "main"
30 | }
31 |
32 | fn name(&self) -> &'static str {
33 | "m0005_reference_complex"
34 | }
35 |
36 | fn parents(&self) -> Vec>> {
37 | vec![Box::new(("main", "m0004_complex_operation"))]
38 | }
39 |
40 | fn operations(&self) -> Vec>> {
41 | vec![Box::new(M0005Operation)]
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/examples/sqlite/migrations/mod.rs:
--------------------------------------------------------------------------------
1 | use sqlx::Sqlite;
2 | use sqlx_migrator::migration::Migration;
3 | use sqlx_migrator::vec_box;
4 |
5 | pub(crate) mod m0001_simple;
6 | pub(crate) mod m0002_with_parents;
7 | pub(crate) mod m0003_use_macros;
8 | pub(crate) mod m0004_complex_operation;
9 | pub(crate) mod m0005_reference_complex;
10 |
11 | pub(crate) fn migrations() -> Vec>> {
12 | vec_box![
13 | m0001_simple::M0001Migration,
14 | m0002_with_parents::M0002Migration,
15 | m0003_use_macros::M0003Migration,
16 | m0004_complex_operation::M0004Migration {
17 | id: 23,
18 | message: "Custom String".to_string()
19 | },
20 | m0005_reference_complex::M0005Migration,
21 | ]
22 | }
23 |
--------------------------------------------------------------------------------
/src/cli.rs:
--------------------------------------------------------------------------------
1 | //! Module for creating and running cli with help of migrator
2 | //!
3 | //! CLI Command can directly used or extended
4 | //!
5 | //! For direct usage you can run `parse_and_run` function for `MigrationCommand`
6 | //!
7 | //! OR
8 | //!
9 | //! If you want to extend your own clap based cli then you can add migrator to
10 | //! sub command enum and then run migrator
11 | //! ```rust,no_run
12 | //! #[derive(clap::Parser)]
13 | //! struct Cli {
14 | //! #[command(subcommand)]
15 | //! sub_command: CliSubcommand,
16 | //! }
17 | //!
18 | //! #[derive(clap::Subcommand)]
19 | //! enum CliSubcommand {
20 | //! #[command()]
21 | //! Migrator(sqlx_migrator::cli::MigrationCommand),
22 | //! }
23 | //! ```
24 | use std::io::Write;
25 |
26 | use clap::{Parser, Subcommand};
27 | use sqlx::Database;
28 |
29 | use crate::error::Error;
30 | use crate::migrator::{Migrate, Plan};
31 |
32 | /// Migration command for performing rust based sqlx migrations
33 | #[derive(Parser, Debug)]
34 | pub struct MigrationCommand {
35 | #[command(subcommand)]
36 | sub_command: SubCommand,
37 | }
38 |
39 | impl MigrationCommand {
40 | /// Parse [`MigrationCommand`] and run migration command line interface
41 | ///
42 | /// # Errors
43 | /// If migration command fails to complete and raise some issue
44 | pub async fn parse_and_run(
45 | connection: &mut ::Connection,
46 | migrator: Box>,
47 | ) -> Result<(), Error>
48 | where
49 | DB: Database,
50 | {
51 | let migration_command = Self::parse();
52 | migration_command.run(connection, migrator).await
53 | }
54 |
55 | /// Run migration command line interface
56 | ///
57 | /// # Errors
58 | /// If migration command fails to complete and raise some issue
59 | pub async fn run(
60 | &self,
61 | connection: &mut ::Connection,
62 | migrator: Box>,
63 | ) -> Result<(), Error>
64 | where
65 | DB: Database,
66 | {
67 | self.sub_command
68 | .handle_subcommand(migrator, connection)
69 | .await?;
70 | Ok(())
71 | }
72 | }
73 |
74 | #[derive(Subcommand, Debug)]
75 | enum SubCommand {
76 | /// Apply migrations
77 | #[command()]
78 | Apply(Apply),
79 | /// Drop migration information table. Needs all migrations to be
80 | /// reverted else raises error
81 | #[command()]
82 | Drop,
83 | /// List migrations along with their status and time applied if migrations
84 | /// is already applied
85 | #[command()]
86 | List,
87 | /// Revert migrations
88 | #[command()]
89 | Revert(Revert),
90 | }
91 |
92 | impl SubCommand {
93 | async fn handle_subcommand(
94 | &self,
95 | migrator: Box>,
96 | connection: &mut ::Connection,
97 | ) -> Result<(), Error>
98 | where
99 | DB: Database,
100 | {
101 | match self {
102 | SubCommand::Apply(apply) => apply.run(connection, migrator).await?,
103 | SubCommand::Drop => drop_migrations(connection, migrator).await?,
104 | SubCommand::List => list_migrations(connection, migrator).await?,
105 | SubCommand::Revert(revert) => revert.run(connection, migrator).await?,
106 | }
107 | Ok(())
108 | }
109 | }
110 |
111 | async fn drop_migrations(
112 | connection: &mut ::Connection,
113 | migrator: Box>,
114 | ) -> Result<(), Error>
115 | where
116 | DB: Database,
117 | {
118 | migrator.ensure_migration_table_exists(connection).await?;
119 | if !migrator
120 | .fetch_applied_migration_from_db(connection)
121 | .await?
122 | .is_empty()
123 | {
124 | return Err(Error::AppliedMigrationExists);
125 | }
126 | migrator.drop_migration_table_if_exists(connection).await?;
127 | println!("Dropped migrations table");
128 | Ok(())
129 | }
130 |
131 | async fn list_migrations(
132 | connection: &mut ::Connection,
133 | migrator: Box>,
134 | ) -> Result<(), Error>
135 | where
136 | DB: Database,
137 | {
138 | let migration_plan = migrator.generate_migration_plan(connection, None).await?;
139 |
140 | let apply_plan = migrator
141 | .generate_migration_plan(connection, Some(&Plan::apply_all()))
142 | .await?;
143 | let applied_migrations = migrator.fetch_applied_migration_from_db(connection).await?;
144 |
145 | let widths = [5, 10, 50, 10, 40];
146 | let full_width = widths.iter().sum::() + widths.len() * 3;
147 |
148 | let first_width = widths[0];
149 | let second_width = widths[1];
150 | let third_width = widths[2];
151 | let fourth_width = widths[3];
152 | let fifth_width = widths[4];
153 |
154 | println!(
155 | "{:^first_width$} | {:^second_width$} | {:^third_width$} | {:^fourth_width$} | \
156 | {:^fifth_width$}",
157 | "ID", "App", "Name", "Status", "Applied time"
158 | );
159 |
160 | println!("{:^full_width$}", "-".repeat(full_width));
161 | for migration in migration_plan {
162 | let mut id = String::from("N/A");
163 | let mut status = "\u{2717}";
164 | let mut applied_time = String::from("N/A");
165 |
166 | let find_applied_migrations = applied_migrations
167 | .iter()
168 | .find(|&applied_migration| applied_migration == migration);
169 |
170 | if let Some(sqlx_migration) = find_applied_migrations {
171 | id = sqlx_migration.id().to_string();
172 | status = "\u{2713}";
173 | applied_time = sqlx_migration.applied_time().to_string();
174 | } else if !apply_plan.contains(&migration) {
175 | status = "\u{2194}";
176 | }
177 |
178 | println!(
179 | "{:^first_width$} | {:^second_width$} | {:^third_width$} | {:^fourth_width$} | \
180 | {:^fifth_width$}",
181 | id,
182 | migration.app(),
183 | migration.name(),
184 | status,
185 | applied_time
186 | );
187 | }
188 | Ok(())
189 | }
190 |
191 | #[derive(Parser, Debug)]
192 | #[expect(clippy::struct_excessive_bools)]
193 | struct Apply {
194 | /// App name up to which migration needs to be applied. If migration option
195 | /// is also present than only till migration is applied
196 | #[arg(long)]
197 | app: Option,
198 | /// Check for pending migration
199 | #[arg(long)]
200 | check: bool,
201 | /// Number of migration to apply. Conflicts with app args
202 | #[arg(long, conflicts_with = "app")]
203 | count: Option,
204 | /// Make migration applied without running migration operations
205 | #[arg(long)]
206 | fake: bool,
207 | /// Force run apply operation without asking question if migration is
208 | /// destructible
209 | #[arg(long)]
210 | force: bool,
211 | /// Apply migration till provided migration. Requires app options to be
212 | /// present
213 | #[arg(long, requires = "app")]
214 | migration: Option,
215 | /// Show plan
216 | #[arg(long)]
217 | plan: bool,
218 | }
219 | impl Apply {
220 | async fn run(
221 | &self,
222 | connection: &mut ::Connection,
223 | migrator: Box>,
224 | ) -> Result<(), Error>
225 | where
226 | DB: Database,
227 | {
228 | let plan;
229 | if let Some(count) = self.count {
230 | plan = Plan::apply_count(count);
231 | } else if let Some(app) = &self.app {
232 | plan = Plan::apply_name(app, &self.migration);
233 | } else {
234 | plan = Plan::apply_all();
235 | }
236 | let plan = plan.fake(self.fake);
237 | let migrations = migrator
238 | .generate_migration_plan(connection, Some(&plan))
239 | .await?;
240 | if self.check && !migrations.is_empty() {
241 | return Err(Error::PendingMigrationPresent);
242 | }
243 | if self.plan {
244 | if migrations.is_empty() {
245 | println!("No migration exists for applying");
246 | } else {
247 | let first_width = 10;
248 | let second_width = 50;
249 | let full_width = first_width + second_width + 3;
250 | println!("{:^first_width$} | {:^second_width$}", "App", "Name");
251 | println!("{:^full_width$}", "-".repeat(full_width));
252 | for migration in migrations {
253 | println!(
254 | "{:^first_width$} | {:^second_width$}",
255 | migration.app(),
256 | migration.name(),
257 | );
258 | }
259 | }
260 | } else {
261 | let destructible_migrations = migrations
262 | .iter()
263 | .filter(|m| m.operations().iter().any(|o| o.is_destructible()))
264 | .collect::>();
265 | if !self.force && !destructible_migrations.is_empty() && !self.fake {
266 | let mut input = String::new();
267 | println!(
268 | "Do you want to apply destructible migrations {} (y/N)",
269 | destructible_migrations.len()
270 | );
271 | for (position, migration) in destructible_migrations.iter().enumerate() {
272 | println!("{position}. {} : {}", migration.app(), migration.name());
273 | }
274 | std::io::stdout().flush()?;
275 | std::io::stdin().read_line(&mut input)?;
276 | let input_trimmed = input.trim().to_ascii_lowercase();
277 | // If answer is not y or yes then return
278 | if !["y", "yes"].contains(&input_trimmed.as_str()) {
279 | return Ok(());
280 | }
281 | }
282 | migrator.run(connection, &plan).await?;
283 | println!("Successfully applied migrations according to plan");
284 | }
285 | Ok(())
286 | }
287 | }
288 |
289 | #[derive(Parser, Debug)]
290 | #[expect(clippy::struct_excessive_bools)]
291 | struct Revert {
292 | /// Revert all migration. Conflicts with app args
293 | #[arg(long, conflicts_with = "app")]
294 | all: bool,
295 | /// Revert migration till app migrations is reverted. If it is present
296 | /// alongside migration options than only till migration is reverted
297 | #[arg(long)]
298 | app: Option,
299 | /// Number of migration to revert. Conflicts with all and app args
300 | #[arg(long, conflicts_with_all = ["all", "app"])]
301 | count: Option,
302 | /// Make migration reverted without running revert operation
303 | #[arg(long)]
304 | fake: bool,
305 | /// Force run revert operation without asking question
306 | #[arg(long)]
307 | force: bool,
308 | /// Revert migration till provided migration. Requires app options to be
309 | /// present
310 | #[arg(long, requires = "app")]
311 | migration: Option,
312 | /// Show plan
313 | #[arg(long)]
314 | plan: bool,
315 | }
316 | impl Revert {
317 | async fn run(
318 | &self,
319 | connection: &mut ::Connection,
320 | migrator: Box>,
321 | ) -> Result<(), Error>
322 | where
323 | DB: Database,
324 | {
325 | let plan;
326 | if let Some(count) = self.count {
327 | plan = Plan::revert_count(count);
328 | } else if let Some(app) = &self.app {
329 | plan = Plan::revert_name(app, &self.migration);
330 | } else if self.all {
331 | plan = Plan::revert_all();
332 | } else {
333 | plan = Plan::revert_count(1);
334 | }
335 | let plan = plan.fake(self.fake);
336 | let revert_migrations = migrator
337 | .generate_migration_plan(connection, Some(&plan))
338 | .await?;
339 |
340 | if self.plan {
341 | if revert_migrations.is_empty() {
342 | println!("No migration exists for reverting");
343 | } else {
344 | let first_width = 10;
345 | let second_width = 50;
346 | let full_width = first_width + second_width + 3;
347 | println!("{:^first_width$} | {:^second_width$}", "App", "Name");
348 | println!("{:^full_width$}", "-".repeat(full_width));
349 | for migration in revert_migrations {
350 | println!(
351 | "{:^first_width$} | {:^second_width$}",
352 | migration.app(),
353 | migration.name(),
354 | );
355 | }
356 | }
357 | } else {
358 | if !self.force && !revert_migrations.is_empty() && !self.fake {
359 | let mut input = String::new();
360 | println!(
361 | "Do you want to revert {} migrations (y/N)",
362 | revert_migrations.len()
363 | );
364 | for (position, migration) in revert_migrations.iter().enumerate() {
365 | println!("{position}. {} : {}", migration.app(), migration.name());
366 | }
367 | std::io::stdout().flush()?;
368 | std::io::stdin().read_line(&mut input)?;
369 | let input_trimmed = input.trim().to_ascii_lowercase();
370 | // If answer is not y or yes then return
371 | if !["y", "yes"].contains(&input_trimmed.as_str()) {
372 | return Ok(());
373 | }
374 | }
375 | migrator.run(connection, &plan).await?;
376 | println!("Successfully reverted migrations according to plan");
377 | }
378 | Ok(())
379 | }
380 | }
381 |
--------------------------------------------------------------------------------
/src/error.rs:
--------------------------------------------------------------------------------
1 | //! Module for library error
2 |
3 | /// Error enum to store different types of error
4 | #[derive(Debug, thiserror::Error)]
5 | #[non_exhaustive]
6 | pub enum Error {
7 | /// Error type created from error raised by sqlx
8 | #[error(transparent)]
9 | Sqlx(#[from] sqlx::Error),
10 | /// Error type created from error raised by box error
11 | #[error(transparent)]
12 | Box(#[from] Box),
13 | /// Error type created from error raised by std input output
14 | #[cfg(feature = "cli")]
15 | #[error(transparent)]
16 | StdIo(#[from] std::io::Error),
17 | /// Error generated during planning state
18 | #[error("plan error: {message}")]
19 | PlanError {
20 | /// Message for error
21 | message: String,
22 | },
23 | /// Error for irreversible operation
24 | #[error("operation is irreversible")]
25 | IrreversibleOperation,
26 | /// Error for pending migration present
27 | #[cfg(feature = "cli")]
28 | #[error("pending migration present")]
29 | PendingMigrationPresent,
30 | /// Error when applied migrations exists
31 | #[cfg(feature = "cli")]
32 | #[error("applied migrations exists. Revert all using revert subcommand")]
33 | AppliedMigrationExists,
34 | /// Error when unsupported database is used as any database
35 | #[error("database not supported")]
36 | UnsupportedDatabase,
37 | /// Error when table prefix is invalid
38 | #[error("table prefix name can only contain [a-z0-9_]")]
39 | InvalidTablePrefix,
40 | /// Error when passed schema name is invalid
41 | #[error("schema name can only contain [a-z0-9_] and begin with [a-z_]")]
42 | InvalidSchema,
43 | /// Error raised when two migration with same name are added and there value
44 | /// is not consistent
45 | #[error("migration for app: {app} with name: {name} consists of inconsistent values")]
46 | InconsistentMigration {
47 | /// Migration application name
48 | app: String,
49 | /// Migration name
50 | name: String,
51 | },
52 | /// Error raised when virtual migration is invalid virtual migration is
53 | /// invalid if it have any fields present expect app name and migration name
54 | #[error("invalid virtual migration")]
55 | InvalidVirtualMigration,
56 | }
57 |
--------------------------------------------------------------------------------
/src/lib.rs:
--------------------------------------------------------------------------------
1 | #![cfg_attr(docsrs, feature(doc_auto_cfg))]
2 |
3 | //! Library to create sqlx migration using rust code instead of sql.
4 | //!
5 | //! Check `README.MD` for more detailed information of how to use a crate
6 | //! and visit [`Operation`], [`Migration`] and [`Migrator`]
7 |
8 | #[cfg(feature = "cli")]
9 | pub use crate::cli::MigrationCommand;
10 | pub use crate::error::Error;
11 | pub use crate::migration::Migration;
12 | pub use crate::migrator::{Info, Migrate, Migrator, Plan};
13 | pub use crate::operation::Operation;
14 |
15 | #[cfg(feature = "cli")]
16 | pub mod cli;
17 | pub mod error;
18 | mod macros;
19 | pub mod migration;
20 | pub mod migrator;
21 | pub mod operation;
22 |
--------------------------------------------------------------------------------
/src/macros.rs:
--------------------------------------------------------------------------------
1 | /// Macro for vector of [`Box`]
2 | #[macro_export]
3 | macro_rules! vec_box {
4 | ($elem:expr; $n:expr) => (vec![Box::new($elem); $n]);
5 | ($($x:expr),*) => (vec![$(Box::new($x)),*]);
6 | ($($x:expr,)*) => (vec![$(Box::new($x)),*]);
7 | ($($x:expr,)*) => (sqlx_migrator::vec_box![$($x),*]);
8 | }
9 |
10 | /// Macro for implementing the [Migration](crate::migration::Migration) trait
11 | /// for the provided database.
12 | ///
13 | /// This macro will use current file name as name for migration
14 | ///
15 | /// This macro expects the following arguments:
16 | /// - `$db:ty`: the type of database
17 | /// - `$op:ty`: The type for which the migration is being implemented
18 | /// - `$app_name:literal`: Name of app to be used for app variable
19 | /// - `$migration_name:literal`: Name of app to be used for app variable
20 | /// - `$parents:expr`: List of parents migration.
21 | /// - `$operations:expr`: List of operations
22 | #[macro_export]
23 | macro_rules! migration {
24 | (
25 | $db:ty, $op:ty, $app_name:literal, $migration_name:literal, $parents:expr, $operations:expr
26 | ) => {
27 | impl sqlx_migrator::migration::Migration<$db> for $op {
28 | fn app(&self) -> &str {
29 | $app_name
30 | }
31 |
32 | fn name(&self) -> &str {
33 | $migration_name
34 | }
35 |
36 | fn parents(&self) -> Vec>> {
37 | $parents
38 | }
39 |
40 | fn operations(&self) -> Vec>> {
41 | $operations
42 | }
43 | }
44 | };
45 | }
46 |
47 | /// Macro for implementing the [`migration`] macro for the `Any`.
48 | ///
49 | /// This macro calls [`migration`] macro with db value already set as
50 | /// `sqlx::Any`
51 | #[macro_export]
52 | #[cfg(all(
53 | any(feature = "postgres", feature = "mysql", feature = "sqlite"),
54 | feature = "any"
55 | ))]
56 | macro_rules! any_migration {
57 | ($op:ty, $app_name:expr, $migration_name:expr, $parents:expr, $operations:expr) => {
58 | sqlx_migrator::migration!(
59 | sqlx::Any,
60 | $op,
61 | $app_name,
62 | $migration_name,
63 | $parents,
64 | $operations
65 | );
66 | };
67 | }
68 |
69 | /// Macro for implementing the [`migration`] macro for the `MySql`.
70 | ///
71 | /// This macro calls [`migration`] macro with db value already set as
72 | /// `sqlx::MySql`
73 | #[macro_export]
74 | #[cfg(feature = "mysql")]
75 | macro_rules! mysql_migration {
76 | ($op:ty, $app_name:expr, $migration_name:expr, $parents:expr, $operations:expr) => {
77 | sqlx_migrator::migration!(
78 | sqlx::MySql,
79 | $op,
80 | $app_name,
81 | $migration_name,
82 | $parents,
83 | $operations
84 | );
85 | };
86 | }
87 |
88 | /// Macro for implementing the [`migration`] macro for the `Postgres`.
89 | ///
90 | /// This macro calls [`migration`] macro with db value already set as
91 | /// `sqlx::Postgres`
92 | #[macro_export]
93 | #[cfg(feature = "postgres")]
94 | macro_rules! postgres_migration {
95 | ($op:ty, $app_name:expr, $migration_name:expr, $parents:expr, $operations:expr) => {
96 | sqlx_migrator::migration!(
97 | sqlx::Postgres,
98 | $op,
99 | $app_name,
100 | $migration_name,
101 | $parents,
102 | $operations
103 | );
104 | };
105 | }
106 |
107 | /// Macro for implementing the [`migration`] macro for the `Sqlite`.
108 | ///
109 | /// This macro calls [`migration`] macro with db value already set as
110 | /// `sqlx::Sqlite`
111 | #[macro_export]
112 | #[cfg(feature = "sqlite")]
113 | macro_rules! sqlite_migration {
114 | ($op:ty, $app_name:expr, $migration_name:expr, $parents:expr, $operations:expr) => {
115 | sqlx_migrator::migration!(
116 | sqlx::Sqlite,
117 | $op,
118 | $app_name,
119 | $migration_name,
120 | $parents,
121 | $operations
122 | );
123 | };
124 | }
125 |
--------------------------------------------------------------------------------
/src/migration.rs:
--------------------------------------------------------------------------------
1 | //! Module for defining the [`Migration`] trait, which represents a database
2 | //! migration.
3 | //!
4 | //! This module provides the necessary abstractions for defining migrations
5 | #![cfg_attr(
6 | feature = "sqlite",
7 | doc = r#"
8 | To create own implement migration trait for type
9 |
10 | ### Example
11 | ```rust,no_run
12 | use sqlx_migrator::error::Error;
13 | use sqlx_migrator::migration::Migration;
14 | use sqlx_migrator::operation::Operation;
15 | use sqlx::Sqlite;
16 |
17 | struct ExampleMigration;
18 |
19 | impl Migration for ExampleMigration {
20 | fn app(&self) -> &str {
21 | "example"
22 | }
23 |
24 | fn name(&self) -> &str {
25 | "first_migration"
26 | }
27 |
28 | fn parents(&self) -> Vec>> {
29 | vec![]
30 | }
31 |
32 | fn operations(&self) -> Vec>> {
33 | vec![]
34 | }
35 |
36 | fn replaces(&self) -> Vec>> {
37 | vec![]
38 | }
39 |
40 | fn run_before(&self) -> Vec>> {
41 | vec![]
42 | }
43 |
44 | fn is_atomic(&self) -> bool {
45 | true
46 | }
47 |
48 | fn is_virtual(&self) -> bool {
49 | false
50 | }
51 | }
52 | ```
53 | "#
54 | )]
55 |
56 | use std::hash::Hash;
57 |
58 | use crate::operation::Operation;
59 |
60 | /// Trait for defining database migration
61 | ///
62 | /// A migration represents a set of operations that can be applied to or
63 | /// reverted from a database. Each migration has an associated application name,
64 | /// migration name, and may depend on other migrations.
65 | ///
66 | /// Migrations can also replace existing migrations, enforce ordering with
67 | /// run before and parents, and control atomicity and virtualization.
68 | ///
69 | /// Migration trait is implemented for `(A,N) where A: AsRef, N:
70 | /// AsRef` where A is the app name and N is the name of the migration. You
71 | /// can use migration in this form in `parents`, `replaces` and `run_before` if
72 | /// you cannot reference migration or create migration easily
73 | pub trait Migration: Send + Sync {
74 | /// Returns the application name associated with the migration.
75 | /// This can be the name of the folder or library where the migration is
76 | /// located.
77 | ///
78 | /// This value is used in combination with the migration name to uniquely
79 | /// identify a migration.
80 | fn app(&self) -> &str;
81 |
82 | /// Returns the migration name, typically the file name without the
83 | /// extension.
84 | ///
85 | /// This value, together with the application name, is used to uniquely
86 | /// identify a migration and determine equality between migrations.
87 | fn name(&self) -> &str;
88 |
89 | /// Returns the list of parent migrations.
90 | ///
91 | /// Parent migrations must be applied before this migration can be applied.
92 | /// If no parent migrations are required, return an empty vector.
93 | fn parents(&self) -> Vec>>;
94 |
95 | /// Returns the operations associated with this migration.
96 | ///
97 | /// A migration can include multiple operations (e.g., create, drop) that
98 | /// are related.
99 | fn operations(&self) -> Vec>>;
100 |
101 | /// Returns the list of migrations that this migration replaces.
102 | ///
103 | /// If any of these migrations have been applied, this migration will not be
104 | /// applied. If not, it will either be applied or reverted in place of
105 | /// those migrations.
106 | ///
107 | /// The default implementation returns an empty vector.
108 | fn replaces(&self) -> Vec>> {
109 | vec![]
110 | }
111 |
112 | /// Returns the list of migrations that this migration must run before(when
113 | /// applying) or after (when reverting).
114 | ///
115 | /// This can be useful when a migration from another library needs to be
116 | /// applied after this migration or reverted before this migration.
117 | ///
118 | /// The default implementation returns an empty vector.
119 | fn run_before(&self) -> Vec>> {
120 | vec![]
121 | }
122 |
123 | /// Indicates whether the migration is atomic.
124 | /// By default, this function returns `true`, meaning the migration is
125 | /// atomic.
126 | ///
127 | /// If the migration is non-atomic, all its operations will be non-atomic as
128 | /// well. For migrations requiring mixed atomicity, it's recommended to
129 | /// split them into separate migrations, each handling atomic and
130 | /// non-atomic operations respectively.
131 | fn is_atomic(&self) -> bool {
132 | true
133 | }
134 |
135 | /// Indicates whether the migration is virtual.
136 | /// By default, this function returns `false`, meaning the migration is not
137 | /// virtual.
138 | ///
139 | /// A virtual migration serves as a reference to another migration with the
140 | /// same app and name. If the migration is virtual, all other methods
141 | /// are ignored expect its application name and its own name to check with
142 | /// non virtual migration so such non virtual migration can be used in its
143 | /// place.
144 | fn is_virtual(&self) -> bool {
145 | false
146 | }
147 | }
148 |
149 | impl PartialEq for dyn Migration {
150 | fn eq(&self, other: &Self) -> bool {
151 | self.app() == other.app() && self.name() == other.name()
152 | }
153 | }
154 |
155 | impl Eq for dyn Migration {}
156 |
157 | impl Hash for dyn Migration {
158 | fn hash(&self, state: &mut H) {
159 | self.app().hash(state);
160 | self.name().hash(state);
161 | }
162 | }
163 |
164 | impl Migration for (A, N)
165 | where
166 | A: AsRef + Send + Sync,
167 | N: AsRef + Send + Sync,
168 | {
169 | fn app(&self) -> &str {
170 | self.0.as_ref()
171 | }
172 |
173 | fn name(&self) -> &str {
174 | self.1.as_ref()
175 | }
176 |
177 | fn parents(&self) -> Vec>> {
178 | vec![]
179 | }
180 |
181 | fn operations(&self) -> Vec>> {
182 | vec![]
183 | }
184 |
185 | fn is_virtual(&self) -> bool {
186 | true
187 | }
188 | }
189 |
190 | /// Struct representing a migration row from the database.
191 | ///
192 | /// This struct corresponds to the id, app, name, and applied time fields in the
193 | /// database. It is used to list the migrations that have been applied.
194 | #[derive(sqlx::FromRow, Clone)]
195 | pub struct AppliedMigrationSqlRow {
196 | id: i32,
197 | app: String,
198 | name: String,
199 | applied_time: String,
200 | }
201 |
202 | impl AppliedMigrationSqlRow {
203 | #[cfg(test)]
204 | pub(crate) fn new(id: i32, app: &str, name: &str) -> Self {
205 | Self {
206 | id,
207 | app: app.to_string(),
208 | name: name.to_string(),
209 | applied_time: String::new(),
210 | }
211 | }
212 | }
213 |
214 | impl AppliedMigrationSqlRow {
215 | /// Return id value present on database
216 | #[must_use]
217 | pub fn id(&self) -> i32 {
218 | self.id
219 | }
220 |
221 | /// Return migration applied time
222 | #[must_use]
223 | pub fn applied_time(&self) -> &str {
224 | &self.applied_time
225 | }
226 | }
227 |
228 | impl PartialEq>> for AppliedMigrationSqlRow {
229 | fn eq(&self, other: &Box>) -> bool {
230 | self.app == other.app() && self.name == other.name()
231 | }
232 | }
233 |
--------------------------------------------------------------------------------
/src/migrator/any.rs:
--------------------------------------------------------------------------------
1 | #[cfg(feature = "mysql")]
2 | use sqlx::MySql;
3 | #[cfg(feature = "postgres")]
4 | use sqlx::Postgres;
5 | #[cfg(feature = "sqlite")]
6 | use sqlx::Sqlite;
7 | use sqlx::any::AnyArguments;
8 | use sqlx::{Any, Arguments, Database};
9 |
10 | #[cfg(feature = "mysql")]
11 | use super::mysql;
12 | #[cfg(feature = "postgres")]
13 | use super::postgres;
14 | #[cfg(feature = "sqlite")]
15 | use super::sqlite;
16 | use super::{DatabaseOperation, Migrator};
17 | use crate::error::Error;
18 | use crate::migration::{AppliedMigrationSqlRow, Migration};
19 |
20 | /// get database name
21 | async fn get_database_name(
22 | connection: &mut ::Connection,
23 | ) -> Result