├── .dockerignore ├── .github └── workflows │ ├── docker.yml │ └── rust.yml ├── .gitignore ├── Cargo.lock ├── Cargo.toml ├── Dockerfile ├── LICENSE ├── README.md └── src ├── adaptor.rs ├── adaptor ├── postgres.rs └── sqlite.rs ├── cli.rs ├── config.rs ├── config ├── postgres_params.rs └── sqlite_params.rs ├── display.rs ├── errors.rs ├── file_handler.rs ├── lib.rs ├── main.rs ├── match_maker.rs ├── migration.rs └── plan_builder.rs /.dockerignore: -------------------------------------------------------------------------------- 1 | .github 2 | .gitignore 3 | Dockerfile 4 | target -------------------------------------------------------------------------------- /.github/workflows/docker.yml: -------------------------------------------------------------------------------- 1 | name: Build and publish docker image 2 | 3 | on: 4 | release: 5 | types: [created] 6 | jobs: 7 | multi: 8 | runs-on: ubuntu-latest 9 | steps: 10 | - 11 | name: Checkout 12 | uses: actions/checkout@v2 13 | - name: Set output 14 | id: vars 15 | run: echo ::set-output name=tag::ghcr.io/${{ github.repository_owner }}/movine:${GITHUB_REF#refs/*/} 16 | - 17 | name: Set up QEMU 18 | uses: docker/setup-qemu-action@v1 19 | - 20 | name: Set up Docker Buildx 21 | uses: docker/setup-buildx-action@v1 22 | - 23 | name: Login to GitHub Container Registry 24 | uses: docker/login-action@v1 25 | with: 26 | registry: ghcr.io 27 | username: ${{ github.repository_owner }} 28 | password: ${{ secrets.CR_PAT }} 29 | - 30 | name: Cache Docker layers 31 | uses: actions/cache@v2 32 | with: 33 | path: /tmp/.buildx-cache 34 | key: ${{ runner.os }}-buildx-${{ github.sha }} 35 | restore-keys: | 36 | ${{ runner.os }}-buildx- 37 | - 38 | name: Build and push 39 | id: docker_build 40 | uses: docker/build-push-action@v2 41 | with: 42 | context: . 43 | file: ./Dockerfile 44 | push: true 45 | tags: ${{ steps.vars.outputs.tag }} -------------------------------------------------------------------------------- /.github/workflows/rust.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | push: 5 | branches: ['master'] 6 | pull_request: 7 | 8 | jobs: 9 | build: 10 | 11 | runs-on: ubuntu-latest 12 | 13 | steps: 14 | - uses: actions/checkout@v1 15 | - name: Install dependencies 16 | run: sudo apt-get install libsqlite3-dev 17 | - name: Format 18 | run: cargo fmt --all -- --check 19 | - name: Clippy 20 | run: cargo clippy --all-targets -- -D warnings 21 | - name: Build 22 | run: cargo build --verbose 23 | - name: Run tests 24 | run: cargo test --verbose 25 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | **/*.rs.bk 3 | notes.md 4 | testing/ 5 | -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 3 4 | 5 | [[package]] 6 | name = "aho-corasick" 7 | version = "0.7.20" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "cc936419f96fa211c1b9166887b38e5e40b19958e5b895be7c1f93adec7071ac" 10 | dependencies = [ 11 | "memchr", 12 | ] 13 | 14 | [[package]] 15 | name = "android_system_properties" 16 | version = "0.1.5" 17 | source = "registry+https://github.com/rust-lang/crates.io-index" 18 | checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" 19 | dependencies = [ 20 | "libc", 21 | ] 22 | 23 | [[package]] 24 | name = "ansi_term" 25 | version = "0.12.1" 26 | source = "registry+https://github.com/rust-lang/crates.io-index" 27 | checksum = "d52a9bb7ec0cf484c551830a7ce27bd20d67eac647e1befb56b0be4ee39a55d2" 28 | dependencies = [ 29 | "winapi", 30 | ] 31 | 32 | [[package]] 33 | name = "async-trait" 34 | version = "0.1.68" 35 | source = "registry+https://github.com/rust-lang/crates.io-index" 36 | checksum = "b9ccdd8f2a161be9bd5c023df56f1b2a0bd1d83872ae53b71a84a12c9bf6e842" 37 | dependencies = [ 38 | "proc-macro2", 39 | "quote", 40 | "syn 2.0.10", 41 | ] 42 | 43 | [[package]] 44 | name = "atty" 45 | version = "0.2.14" 46 | source = "registry+https://github.com/rust-lang/crates.io-index" 47 | checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" 48 | dependencies = [ 49 | "hermit-abi 0.1.19", 50 | "libc", 51 | "winapi", 52 | ] 53 | 54 | [[package]] 55 | name = "autocfg" 56 | version = "1.1.0" 57 | source = "registry+https://github.com/rust-lang/crates.io-index" 58 | checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" 59 | 60 | [[package]] 61 | name = "base64" 62 | version = "0.13.1" 63 | source = "registry+https://github.com/rust-lang/crates.io-index" 64 | checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" 65 | 66 | [[package]] 67 | name = "bitflags" 68 | version = "1.3.2" 69 | source = "registry+https://github.com/rust-lang/crates.io-index" 70 | checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" 71 | 72 | [[package]] 73 | name = "block-buffer" 74 | version = "0.10.4" 75 | source = "registry+https://github.com/rust-lang/crates.io-index" 76 | checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" 77 | dependencies = [ 78 | "generic-array", 79 | ] 80 | 81 | [[package]] 82 | name = "bumpalo" 83 | version = "3.12.0" 84 | source = "registry+https://github.com/rust-lang/crates.io-index" 85 | checksum = "0d261e256854913907f67ed06efbc3338dfe6179796deefc1ff763fc1aee5535" 86 | 87 | [[package]] 88 | name = "byteorder" 89 | version = "1.4.3" 90 | source = "registry+https://github.com/rust-lang/crates.io-index" 91 | checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" 92 | 93 | [[package]] 94 | name = "bytes" 95 | version = "1.4.0" 96 | source = "registry+https://github.com/rust-lang/crates.io-index" 97 | checksum = "89b2fd2a0dcf38d7971e2194b6b6eebab45ae01067456a7fd93d5547a61b70be" 98 | 99 | [[package]] 100 | name = "cc" 101 | version = "1.0.79" 102 | source = "registry+https://github.com/rust-lang/crates.io-index" 103 | checksum = "50d30906286121d95be3d479533b458f87493b30a4b5f79a607db8f5d11aa91f" 104 | 105 | [[package]] 106 | name = "cfg-if" 107 | version = "1.0.0" 108 | source = "registry+https://github.com/rust-lang/crates.io-index" 109 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" 110 | 111 | [[package]] 112 | name = "chrono" 113 | version = "0.4.24" 114 | source = "registry+https://github.com/rust-lang/crates.io-index" 115 | checksum = "4e3c5919066adf22df73762e50cffcde3a758f2a848b113b586d1f86728b673b" 116 | dependencies = [ 117 | "iana-time-zone", 118 | "js-sys", 119 | "num-integer", 120 | "num-traits", 121 | "time", 122 | "wasm-bindgen", 123 | "winapi", 124 | ] 125 | 126 | [[package]] 127 | name = "clap" 128 | version = "2.34.0" 129 | source = "registry+https://github.com/rust-lang/crates.io-index" 130 | checksum = "a0610544180c38b88101fecf2dd634b174a62eef6946f84dfc6a7127512b381c" 131 | dependencies = [ 132 | "ansi_term", 133 | "atty", 134 | "bitflags", 135 | "strsim", 136 | "textwrap", 137 | "unicode-width", 138 | "vec_map", 139 | ] 140 | 141 | [[package]] 142 | name = "codespan-reporting" 143 | version = "0.11.1" 144 | source = "registry+https://github.com/rust-lang/crates.io-index" 145 | checksum = "3538270d33cc669650c4b093848450d380def10c331d38c768e34cac80576e6e" 146 | dependencies = [ 147 | "termcolor", 148 | "unicode-width", 149 | ] 150 | 151 | [[package]] 152 | name = "core-foundation" 153 | version = "0.9.3" 154 | source = "registry+https://github.com/rust-lang/crates.io-index" 155 | checksum = "194a7a9e6de53fa55116934067c844d9d749312f75c6f6d0980e8c252f8c2146" 156 | dependencies = [ 157 | "core-foundation-sys", 158 | "libc", 159 | ] 160 | 161 | [[package]] 162 | name = "core-foundation-sys" 163 | version = "0.8.3" 164 | source = "registry+https://github.com/rust-lang/crates.io-index" 165 | checksum = "5827cebf4670468b8772dd191856768aedcb1b0278a04f989f7766351917b9dc" 166 | 167 | [[package]] 168 | name = "cpufeatures" 169 | version = "0.2.6" 170 | source = "registry+https://github.com/rust-lang/crates.io-index" 171 | checksum = "280a9f2d8b3a38871a3c8a46fb80db65e5e5ed97da80c4d08bf27fb63e35e181" 172 | dependencies = [ 173 | "libc", 174 | ] 175 | 176 | [[package]] 177 | name = "crypto-common" 178 | version = "0.1.6" 179 | source = "registry+https://github.com/rust-lang/crates.io-index" 180 | checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" 181 | dependencies = [ 182 | "generic-array", 183 | "typenum", 184 | ] 185 | 186 | [[package]] 187 | name = "cxx" 188 | version = "1.0.94" 189 | source = "registry+https://github.com/rust-lang/crates.io-index" 190 | checksum = "f61f1b6389c3fe1c316bf8a4dccc90a38208354b330925bce1f74a6c4756eb93" 191 | dependencies = [ 192 | "cc", 193 | "cxxbridge-flags", 194 | "cxxbridge-macro", 195 | "link-cplusplus", 196 | ] 197 | 198 | [[package]] 199 | name = "cxx-build" 200 | version = "1.0.94" 201 | source = "registry+https://github.com/rust-lang/crates.io-index" 202 | checksum = "12cee708e8962df2aeb38f594aae5d827c022b6460ac71a7a3e2c3c2aae5a07b" 203 | dependencies = [ 204 | "cc", 205 | "codespan-reporting", 206 | "once_cell", 207 | "proc-macro2", 208 | "quote", 209 | "scratch", 210 | "syn 2.0.10", 211 | ] 212 | 213 | [[package]] 214 | name = "cxxbridge-flags" 215 | version = "1.0.94" 216 | source = "registry+https://github.com/rust-lang/crates.io-index" 217 | checksum = "7944172ae7e4068c533afbb984114a56c46e9ccddda550499caa222902c7f7bb" 218 | 219 | [[package]] 220 | name = "cxxbridge-macro" 221 | version = "1.0.94" 222 | source = "registry+https://github.com/rust-lang/crates.io-index" 223 | checksum = "2345488264226bf682893e25de0769f3360aac9957980ec49361b083ddaa5bc5" 224 | dependencies = [ 225 | "proc-macro2", 226 | "quote", 227 | "syn 2.0.10", 228 | ] 229 | 230 | [[package]] 231 | name = "digest" 232 | version = "0.10.6" 233 | source = "registry+https://github.com/rust-lang/crates.io-index" 234 | checksum = "8168378f4e5023e7218c89c891c0fd8ecdb5e5e4f18cb78f38cf245dd021e76f" 235 | dependencies = [ 236 | "block-buffer", 237 | "crypto-common", 238 | "subtle", 239 | ] 240 | 241 | [[package]] 242 | name = "dotenv" 243 | version = "0.15.0" 244 | source = "registry+https://github.com/rust-lang/crates.io-index" 245 | checksum = "77c90badedccf4105eca100756a0b1289e191f6fcbdadd3cee1d2f614f97da8f" 246 | 247 | [[package]] 248 | name = "env_logger" 249 | version = "0.7.1" 250 | source = "registry+https://github.com/rust-lang/crates.io-index" 251 | checksum = "44533bbbb3bb3c1fa17d9f2e4e38bbbaf8396ba82193c4cb1b6445d711445d36" 252 | dependencies = [ 253 | "atty", 254 | "humantime", 255 | "log", 256 | "regex", 257 | "termcolor", 258 | ] 259 | 260 | [[package]] 261 | name = "envy" 262 | version = "0.4.2" 263 | source = "registry+https://github.com/rust-lang/crates.io-index" 264 | checksum = "3f47e0157f2cb54f5ae1bd371b30a2ae4311e1c028f575cd4e81de7353215965" 265 | dependencies = [ 266 | "serde", 267 | ] 268 | 269 | [[package]] 270 | name = "errno" 271 | version = "0.2.8" 272 | source = "registry+https://github.com/rust-lang/crates.io-index" 273 | checksum = "f639046355ee4f37944e44f60642c6f3a7efa3cf6b78c78a0d989a8ce6c396a1" 274 | dependencies = [ 275 | "errno-dragonfly", 276 | "libc", 277 | "winapi", 278 | ] 279 | 280 | [[package]] 281 | name = "errno-dragonfly" 282 | version = "0.1.2" 283 | source = "registry+https://github.com/rust-lang/crates.io-index" 284 | checksum = "aa68f1b12764fab894d2755d2518754e71b4fd80ecfb822714a1206c2aab39bf" 285 | dependencies = [ 286 | "cc", 287 | "libc", 288 | ] 289 | 290 | [[package]] 291 | name = "fallible-iterator" 292 | version = "0.2.0" 293 | source = "registry+https://github.com/rust-lang/crates.io-index" 294 | checksum = "4443176a9f2c162692bd3d352d745ef9413eec5782a80d8fd6f8a1ac692a07f7" 295 | 296 | [[package]] 297 | name = "fallible-streaming-iterator" 298 | version = "0.1.9" 299 | source = "registry+https://github.com/rust-lang/crates.io-index" 300 | checksum = "7360491ce676a36bf9bb3c56c1aa791658183a54d2744120f27285738d90465a" 301 | 302 | [[package]] 303 | name = "fastrand" 304 | version = "1.9.0" 305 | source = "registry+https://github.com/rust-lang/crates.io-index" 306 | checksum = "e51093e27b0797c359783294ca4f0a911c270184cb10f85783b118614a1501be" 307 | dependencies = [ 308 | "instant", 309 | ] 310 | 311 | [[package]] 312 | name = "foreign-types" 313 | version = "0.3.2" 314 | source = "registry+https://github.com/rust-lang/crates.io-index" 315 | checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" 316 | dependencies = [ 317 | "foreign-types-shared", 318 | ] 319 | 320 | [[package]] 321 | name = "foreign-types-shared" 322 | version = "0.1.1" 323 | source = "registry+https://github.com/rust-lang/crates.io-index" 324 | checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" 325 | 326 | [[package]] 327 | name = "futures" 328 | version = "0.3.27" 329 | source = "registry+https://github.com/rust-lang/crates.io-index" 330 | checksum = "531ac96c6ff5fd7c62263c5e3c67a603af4fcaee2e1a0ae5565ba3a11e69e549" 331 | dependencies = [ 332 | "futures-channel", 333 | "futures-core", 334 | "futures-executor", 335 | "futures-io", 336 | "futures-sink", 337 | "futures-task", 338 | "futures-util", 339 | ] 340 | 341 | [[package]] 342 | name = "futures-channel" 343 | version = "0.3.27" 344 | source = "registry+https://github.com/rust-lang/crates.io-index" 345 | checksum = "164713a5a0dcc3e7b4b1ed7d3b433cabc18025386f9339346e8daf15963cf7ac" 346 | dependencies = [ 347 | "futures-core", 348 | "futures-sink", 349 | ] 350 | 351 | [[package]] 352 | name = "futures-core" 353 | version = "0.3.27" 354 | source = "registry+https://github.com/rust-lang/crates.io-index" 355 | checksum = "86d7a0c1aa76363dac491de0ee99faf6941128376f1cf96f07db7603b7de69dd" 356 | 357 | [[package]] 358 | name = "futures-executor" 359 | version = "0.3.27" 360 | source = "registry+https://github.com/rust-lang/crates.io-index" 361 | checksum = "1997dd9df74cdac935c76252744c1ed5794fac083242ea4fe77ef3ed60ba0f83" 362 | dependencies = [ 363 | "futures-core", 364 | "futures-task", 365 | "futures-util", 366 | ] 367 | 368 | [[package]] 369 | name = "futures-io" 370 | version = "0.3.27" 371 | source = "registry+https://github.com/rust-lang/crates.io-index" 372 | checksum = "89d422fa3cbe3b40dca574ab087abb5bc98258ea57eea3fd6f1fa7162c778b91" 373 | 374 | [[package]] 375 | name = "futures-macro" 376 | version = "0.3.27" 377 | source = "registry+https://github.com/rust-lang/crates.io-index" 378 | checksum = "3eb14ed937631bd8b8b8977f2c198443447a8355b6e3ca599f38c975e5a963b6" 379 | dependencies = [ 380 | "proc-macro2", 381 | "quote", 382 | "syn 1.0.109", 383 | ] 384 | 385 | [[package]] 386 | name = "futures-sink" 387 | version = "0.3.27" 388 | source = "registry+https://github.com/rust-lang/crates.io-index" 389 | checksum = "ec93083a4aecafb2a80a885c9de1f0ccae9dbd32c2bb54b0c3a65690e0b8d2f2" 390 | 391 | [[package]] 392 | name = "futures-task" 393 | version = "0.3.27" 394 | source = "registry+https://github.com/rust-lang/crates.io-index" 395 | checksum = "fd65540d33b37b16542a0438c12e6aeead10d4ac5d05bd3f805b8f35ab592879" 396 | 397 | [[package]] 398 | name = "futures-util" 399 | version = "0.3.27" 400 | source = "registry+https://github.com/rust-lang/crates.io-index" 401 | checksum = "3ef6b17e481503ec85211fed8f39d1970f128935ca1f814cd32ac4a6842e84ab" 402 | dependencies = [ 403 | "futures-channel", 404 | "futures-core", 405 | "futures-io", 406 | "futures-macro", 407 | "futures-sink", 408 | "futures-task", 409 | "memchr", 410 | "pin-project-lite", 411 | "pin-utils", 412 | "slab", 413 | ] 414 | 415 | [[package]] 416 | name = "generic-array" 417 | version = "0.14.6" 418 | source = "registry+https://github.com/rust-lang/crates.io-index" 419 | checksum = "bff49e947297f3312447abdca79f45f4738097cc82b06e72054d2223f601f1b9" 420 | dependencies = [ 421 | "typenum", 422 | "version_check", 423 | ] 424 | 425 | [[package]] 426 | name = "getrandom" 427 | version = "0.2.8" 428 | source = "registry+https://github.com/rust-lang/crates.io-index" 429 | checksum = "c05aeb6a22b8f62540c194aac980f2115af067bfe15a0734d7277a768d396b31" 430 | dependencies = [ 431 | "cfg-if", 432 | "libc", 433 | "wasi 0.11.0+wasi-snapshot-preview1", 434 | ] 435 | 436 | [[package]] 437 | name = "heck" 438 | version = "0.3.3" 439 | source = "registry+https://github.com/rust-lang/crates.io-index" 440 | checksum = "6d621efb26863f0e9924c6ac577e8275e5e6b77455db64ffa6c65c904e9e132c" 441 | dependencies = [ 442 | "unicode-segmentation", 443 | ] 444 | 445 | [[package]] 446 | name = "hermit-abi" 447 | version = "0.1.19" 448 | source = "registry+https://github.com/rust-lang/crates.io-index" 449 | checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" 450 | dependencies = [ 451 | "libc", 452 | ] 453 | 454 | [[package]] 455 | name = "hermit-abi" 456 | version = "0.3.1" 457 | source = "registry+https://github.com/rust-lang/crates.io-index" 458 | checksum = "fed44880c466736ef9a5c5b5facefb5ed0785676d0c02d612db14e54f0d84286" 459 | 460 | [[package]] 461 | name = "hmac" 462 | version = "0.12.1" 463 | source = "registry+https://github.com/rust-lang/crates.io-index" 464 | checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" 465 | dependencies = [ 466 | "digest", 467 | ] 468 | 469 | [[package]] 470 | name = "humantime" 471 | version = "1.3.0" 472 | source = "registry+https://github.com/rust-lang/crates.io-index" 473 | checksum = "df004cfca50ef23c36850aaaa59ad52cc70d0e90243c3c7737a4dd32dc7a3c4f" 474 | dependencies = [ 475 | "quick-error", 476 | ] 477 | 478 | [[package]] 479 | name = "iana-time-zone" 480 | version = "0.1.54" 481 | source = "registry+https://github.com/rust-lang/crates.io-index" 482 | checksum = "0c17cc76786e99f8d2f055c11159e7f0091c42474dcc3189fbab96072e873e6d" 483 | dependencies = [ 484 | "android_system_properties", 485 | "core-foundation-sys", 486 | "iana-time-zone-haiku", 487 | "js-sys", 488 | "wasm-bindgen", 489 | "windows", 490 | ] 491 | 492 | [[package]] 493 | name = "iana-time-zone-haiku" 494 | version = "0.1.1" 495 | source = "registry+https://github.com/rust-lang/crates.io-index" 496 | checksum = "0703ae284fc167426161c2e3f1da3ea71d94b21bedbcc9494e92b28e334e3dca" 497 | dependencies = [ 498 | "cxx", 499 | "cxx-build", 500 | ] 501 | 502 | [[package]] 503 | name = "instant" 504 | version = "0.1.12" 505 | source = "registry+https://github.com/rust-lang/crates.io-index" 506 | checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c" 507 | dependencies = [ 508 | "cfg-if", 509 | ] 510 | 511 | [[package]] 512 | name = "io-lifetimes" 513 | version = "1.0.9" 514 | source = "registry+https://github.com/rust-lang/crates.io-index" 515 | checksum = "09270fd4fa1111bc614ed2246c7ef56239a3063d5be0d1ec3b589c505d400aeb" 516 | dependencies = [ 517 | "hermit-abi 0.3.1", 518 | "libc", 519 | "windows-sys 0.45.0", 520 | ] 521 | 522 | [[package]] 523 | name = "js-sys" 524 | version = "0.3.61" 525 | source = "registry+https://github.com/rust-lang/crates.io-index" 526 | checksum = "445dde2150c55e483f3d8416706b97ec8e8237c307e5b7b4b8dd15e6af2a0730" 527 | dependencies = [ 528 | "wasm-bindgen", 529 | ] 530 | 531 | [[package]] 532 | name = "lazy_static" 533 | version = "1.4.0" 534 | source = "registry+https://github.com/rust-lang/crates.io-index" 535 | checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" 536 | 537 | [[package]] 538 | name = "libc" 539 | version = "0.2.140" 540 | source = "registry+https://github.com/rust-lang/crates.io-index" 541 | checksum = "99227334921fae1a979cf0bfdfcc6b3e5ce376ef57e16fb6fb3ea2ed6095f80c" 542 | 543 | [[package]] 544 | name = "libsqlite3-sys" 545 | version = "0.18.0" 546 | source = "registry+https://github.com/rust-lang/crates.io-index" 547 | checksum = "1e704a02bcaecd4a08b93a23f6be59d0bd79cd161e0963e9499165a0a35df7bd" 548 | dependencies = [ 549 | "cc", 550 | "pkg-config", 551 | "vcpkg", 552 | ] 553 | 554 | [[package]] 555 | name = "link-cplusplus" 556 | version = "1.0.8" 557 | source = "registry+https://github.com/rust-lang/crates.io-index" 558 | checksum = "ecd207c9c713c34f95a097a5b029ac2ce6010530c7b49d7fea24d977dede04f5" 559 | dependencies = [ 560 | "cc", 561 | ] 562 | 563 | [[package]] 564 | name = "linked-hash-map" 565 | version = "0.5.6" 566 | source = "registry+https://github.com/rust-lang/crates.io-index" 567 | checksum = "0717cef1bc8b636c6e1c1bbdefc09e6322da8a9321966e8928ef80d20f7f770f" 568 | 569 | [[package]] 570 | name = "linux-raw-sys" 571 | version = "0.1.4" 572 | source = "registry+https://github.com/rust-lang/crates.io-index" 573 | checksum = "f051f77a7c8e6957c0696eac88f26b0117e54f52d3fc682ab19397a8812846a4" 574 | 575 | [[package]] 576 | name = "lock_api" 577 | version = "0.4.9" 578 | source = "registry+https://github.com/rust-lang/crates.io-index" 579 | checksum = "435011366fe56583b16cf956f9df0095b405b82d76425bc8981c0e22e60ec4df" 580 | dependencies = [ 581 | "autocfg", 582 | "scopeguard", 583 | ] 584 | 585 | [[package]] 586 | name = "log" 587 | version = "0.4.17" 588 | source = "registry+https://github.com/rust-lang/crates.io-index" 589 | checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e" 590 | dependencies = [ 591 | "cfg-if", 592 | ] 593 | 594 | [[package]] 595 | name = "lru-cache" 596 | version = "0.1.2" 597 | source = "registry+https://github.com/rust-lang/crates.io-index" 598 | checksum = "31e24f1ad8321ca0e8a1e0ac13f23cb668e6f5466c2c57319f6a5cf1cc8e3b1c" 599 | dependencies = [ 600 | "linked-hash-map", 601 | ] 602 | 603 | [[package]] 604 | name = "md-5" 605 | version = "0.10.5" 606 | source = "registry+https://github.com/rust-lang/crates.io-index" 607 | checksum = "6365506850d44bff6e2fbcb5176cf63650e48bd45ef2fe2665ae1570e0f4b9ca" 608 | dependencies = [ 609 | "digest", 610 | ] 611 | 612 | [[package]] 613 | name = "memchr" 614 | version = "2.5.0" 615 | source = "registry+https://github.com/rust-lang/crates.io-index" 616 | checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" 617 | 618 | [[package]] 619 | name = "mio" 620 | version = "0.8.6" 621 | source = "registry+https://github.com/rust-lang/crates.io-index" 622 | checksum = "5b9d9a46eff5b4ff64b45a9e316a6d1e0bc719ef429cbec4dc630684212bfdf9" 623 | dependencies = [ 624 | "libc", 625 | "log", 626 | "wasi 0.11.0+wasi-snapshot-preview1", 627 | "windows-sys 0.45.0", 628 | ] 629 | 630 | [[package]] 631 | name = "movine" 632 | version = "0.11.3" 633 | dependencies = [ 634 | "ansi_term", 635 | "chrono", 636 | "dotenv", 637 | "env_logger", 638 | "envy", 639 | "libsqlite3-sys", 640 | "log", 641 | "native-tls", 642 | "postgres", 643 | "postgres-native-tls", 644 | "rusqlite", 645 | "rustls", 646 | "serde", 647 | "structopt", 648 | "tokio-postgres-rustls", 649 | "toml", 650 | ] 651 | 652 | [[package]] 653 | name = "native-tls" 654 | version = "0.2.11" 655 | source = "registry+https://github.com/rust-lang/crates.io-index" 656 | checksum = "07226173c32f2926027b63cce4bcd8076c3552846cbe7925f3aaffeac0a3b92e" 657 | dependencies = [ 658 | "lazy_static", 659 | "libc", 660 | "log", 661 | "openssl", 662 | "openssl-probe", 663 | "openssl-sys", 664 | "schannel", 665 | "security-framework", 666 | "security-framework-sys", 667 | "tempfile", 668 | ] 669 | 670 | [[package]] 671 | name = "num-integer" 672 | version = "0.1.45" 673 | source = "registry+https://github.com/rust-lang/crates.io-index" 674 | checksum = "225d3389fb3509a24c93f5c29eb6bde2586b98d9f016636dff58d7c6f7569cd9" 675 | dependencies = [ 676 | "autocfg", 677 | "num-traits", 678 | ] 679 | 680 | [[package]] 681 | name = "num-traits" 682 | version = "0.2.15" 683 | source = "registry+https://github.com/rust-lang/crates.io-index" 684 | checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd" 685 | dependencies = [ 686 | "autocfg", 687 | ] 688 | 689 | [[package]] 690 | name = "once_cell" 691 | version = "1.17.1" 692 | source = "registry+https://github.com/rust-lang/crates.io-index" 693 | checksum = "b7e5500299e16ebb147ae15a00a942af264cf3688f47923b8fc2cd5858f23ad3" 694 | 695 | [[package]] 696 | name = "openssl" 697 | version = "0.10.48" 698 | source = "registry+https://github.com/rust-lang/crates.io-index" 699 | checksum = "518915b97df115dd36109bfa429a48b8f737bd05508cf9588977b599648926d2" 700 | dependencies = [ 701 | "bitflags", 702 | "cfg-if", 703 | "foreign-types", 704 | "libc", 705 | "once_cell", 706 | "openssl-macros", 707 | "openssl-sys", 708 | ] 709 | 710 | [[package]] 711 | name = "openssl-macros" 712 | version = "0.1.0" 713 | source = "registry+https://github.com/rust-lang/crates.io-index" 714 | checksum = "b501e44f11665960c7e7fcf062c7d96a14ade4aa98116c004b2e37b5be7d736c" 715 | dependencies = [ 716 | "proc-macro2", 717 | "quote", 718 | "syn 1.0.109", 719 | ] 720 | 721 | [[package]] 722 | name = "openssl-probe" 723 | version = "0.1.5" 724 | source = "registry+https://github.com/rust-lang/crates.io-index" 725 | checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" 726 | 727 | [[package]] 728 | name = "openssl-sys" 729 | version = "0.9.83" 730 | source = "registry+https://github.com/rust-lang/crates.io-index" 731 | checksum = "666416d899cf077260dac8698d60a60b435a46d57e82acb1be3d0dad87284e5b" 732 | dependencies = [ 733 | "autocfg", 734 | "cc", 735 | "libc", 736 | "pkg-config", 737 | "vcpkg", 738 | ] 739 | 740 | [[package]] 741 | name = "parking_lot" 742 | version = "0.12.1" 743 | source = "registry+https://github.com/rust-lang/crates.io-index" 744 | checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f" 745 | dependencies = [ 746 | "lock_api", 747 | "parking_lot_core", 748 | ] 749 | 750 | [[package]] 751 | name = "parking_lot_core" 752 | version = "0.9.7" 753 | source = "registry+https://github.com/rust-lang/crates.io-index" 754 | checksum = "9069cbb9f99e3a5083476ccb29ceb1de18b9118cafa53e90c9551235de2b9521" 755 | dependencies = [ 756 | "cfg-if", 757 | "libc", 758 | "redox_syscall", 759 | "smallvec", 760 | "windows-sys 0.45.0", 761 | ] 762 | 763 | [[package]] 764 | name = "percent-encoding" 765 | version = "2.2.0" 766 | source = "registry+https://github.com/rust-lang/crates.io-index" 767 | checksum = "478c572c3d73181ff3c2539045f6eb99e5491218eae919370993b890cdbdd98e" 768 | 769 | [[package]] 770 | name = "phf" 771 | version = "0.11.1" 772 | source = "registry+https://github.com/rust-lang/crates.io-index" 773 | checksum = "928c6535de93548188ef63bb7c4036bd415cd8f36ad25af44b9789b2ee72a48c" 774 | dependencies = [ 775 | "phf_shared", 776 | ] 777 | 778 | [[package]] 779 | name = "phf_shared" 780 | version = "0.11.1" 781 | source = "registry+https://github.com/rust-lang/crates.io-index" 782 | checksum = "e1fb5f6f826b772a8d4c0394209441e7d37cbbb967ae9c7e0e8134365c9ee676" 783 | dependencies = [ 784 | "siphasher", 785 | ] 786 | 787 | [[package]] 788 | name = "pin-project-lite" 789 | version = "0.2.9" 790 | source = "registry+https://github.com/rust-lang/crates.io-index" 791 | checksum = "e0a7ae3ac2f1173085d398531c705756c94a4c56843785df85a60c1a0afac116" 792 | 793 | [[package]] 794 | name = "pin-utils" 795 | version = "0.1.0" 796 | source = "registry+https://github.com/rust-lang/crates.io-index" 797 | checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" 798 | 799 | [[package]] 800 | name = "pkg-config" 801 | version = "0.3.26" 802 | source = "registry+https://github.com/rust-lang/crates.io-index" 803 | checksum = "6ac9a59f73473f1b8d852421e59e64809f025994837ef743615c6d0c5b305160" 804 | 805 | [[package]] 806 | name = "postgres" 807 | version = "0.19.4" 808 | source = "registry+https://github.com/rust-lang/crates.io-index" 809 | checksum = "960c214283ef8f0027974c03e9014517ced5db12f021a9abb66185a5751fab0a" 810 | dependencies = [ 811 | "bytes", 812 | "fallible-iterator", 813 | "futures-util", 814 | "log", 815 | "tokio", 816 | "tokio-postgres", 817 | ] 818 | 819 | [[package]] 820 | name = "postgres-native-tls" 821 | version = "0.5.0" 822 | source = "registry+https://github.com/rust-lang/crates.io-index" 823 | checksum = "2d442770e2b1e244bb5eb03b31c79b65bb2568f413b899eaba850fa945a65954" 824 | dependencies = [ 825 | "futures", 826 | "native-tls", 827 | "tokio", 828 | "tokio-native-tls", 829 | "tokio-postgres", 830 | ] 831 | 832 | [[package]] 833 | name = "postgres-protocol" 834 | version = "0.6.4" 835 | source = "registry+https://github.com/rust-lang/crates.io-index" 836 | checksum = "878c6cbf956e03af9aa8204b407b9cbf47c072164800aa918c516cd4b056c50c" 837 | dependencies = [ 838 | "base64", 839 | "byteorder", 840 | "bytes", 841 | "fallible-iterator", 842 | "hmac", 843 | "md-5", 844 | "memchr", 845 | "rand", 846 | "sha2", 847 | "stringprep", 848 | ] 849 | 850 | [[package]] 851 | name = "postgres-types" 852 | version = "0.2.4" 853 | source = "registry+https://github.com/rust-lang/crates.io-index" 854 | checksum = "73d946ec7d256b04dfadc4e6a3292324e6f417124750fc5c0950f981b703a0f1" 855 | dependencies = [ 856 | "bytes", 857 | "fallible-iterator", 858 | "postgres-protocol", 859 | ] 860 | 861 | [[package]] 862 | name = "ppv-lite86" 863 | version = "0.2.17" 864 | source = "registry+https://github.com/rust-lang/crates.io-index" 865 | checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" 866 | 867 | [[package]] 868 | name = "proc-macro-error" 869 | version = "1.0.4" 870 | source = "registry+https://github.com/rust-lang/crates.io-index" 871 | checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" 872 | dependencies = [ 873 | "proc-macro-error-attr", 874 | "proc-macro2", 875 | "quote", 876 | "syn 1.0.109", 877 | "version_check", 878 | ] 879 | 880 | [[package]] 881 | name = "proc-macro-error-attr" 882 | version = "1.0.4" 883 | source = "registry+https://github.com/rust-lang/crates.io-index" 884 | checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" 885 | dependencies = [ 886 | "proc-macro2", 887 | "quote", 888 | "version_check", 889 | ] 890 | 891 | [[package]] 892 | name = "proc-macro2" 893 | version = "1.0.54" 894 | source = "registry+https://github.com/rust-lang/crates.io-index" 895 | checksum = "e472a104799c74b514a57226160104aa483546de37e839ec50e3c2e41dd87534" 896 | dependencies = [ 897 | "unicode-ident", 898 | ] 899 | 900 | [[package]] 901 | name = "quick-error" 902 | version = "1.2.3" 903 | source = "registry+https://github.com/rust-lang/crates.io-index" 904 | checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0" 905 | 906 | [[package]] 907 | name = "quote" 908 | version = "1.0.26" 909 | source = "registry+https://github.com/rust-lang/crates.io-index" 910 | checksum = "4424af4bf778aae2051a77b60283332f386554255d722233d09fbfc7e30da2fc" 911 | dependencies = [ 912 | "proc-macro2", 913 | ] 914 | 915 | [[package]] 916 | name = "rand" 917 | version = "0.8.5" 918 | source = "registry+https://github.com/rust-lang/crates.io-index" 919 | checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" 920 | dependencies = [ 921 | "libc", 922 | "rand_chacha", 923 | "rand_core", 924 | ] 925 | 926 | [[package]] 927 | name = "rand_chacha" 928 | version = "0.3.1" 929 | source = "registry+https://github.com/rust-lang/crates.io-index" 930 | checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" 931 | dependencies = [ 932 | "ppv-lite86", 933 | "rand_core", 934 | ] 935 | 936 | [[package]] 937 | name = "rand_core" 938 | version = "0.6.4" 939 | source = "registry+https://github.com/rust-lang/crates.io-index" 940 | checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" 941 | dependencies = [ 942 | "getrandom", 943 | ] 944 | 945 | [[package]] 946 | name = "redox_syscall" 947 | version = "0.2.16" 948 | source = "registry+https://github.com/rust-lang/crates.io-index" 949 | checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a" 950 | dependencies = [ 951 | "bitflags", 952 | ] 953 | 954 | [[package]] 955 | name = "regex" 956 | version = "1.7.3" 957 | source = "registry+https://github.com/rust-lang/crates.io-index" 958 | checksum = "8b1f693b24f6ac912f4893ef08244d70b6067480d2f1a46e950c9691e6749d1d" 959 | dependencies = [ 960 | "aho-corasick", 961 | "memchr", 962 | "regex-syntax", 963 | ] 964 | 965 | [[package]] 966 | name = "regex-syntax" 967 | version = "0.6.29" 968 | source = "registry+https://github.com/rust-lang/crates.io-index" 969 | checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1" 970 | 971 | [[package]] 972 | name = "ring" 973 | version = "0.16.20" 974 | source = "registry+https://github.com/rust-lang/crates.io-index" 975 | checksum = "3053cf52e236a3ed746dfc745aa9cacf1b791d846bdaf412f60a8d7d6e17c8fc" 976 | dependencies = [ 977 | "cc", 978 | "libc", 979 | "once_cell", 980 | "spin", 981 | "untrusted", 982 | "web-sys", 983 | "winapi", 984 | ] 985 | 986 | [[package]] 987 | name = "rusqlite" 988 | version = "0.23.1" 989 | source = "registry+https://github.com/rust-lang/crates.io-index" 990 | checksum = "45d0fd62e1df63d254714e6cb40d0a0e82e7a1623e7a27f679d851af092ae58b" 991 | dependencies = [ 992 | "bitflags", 993 | "fallible-iterator", 994 | "fallible-streaming-iterator", 995 | "libsqlite3-sys", 996 | "lru-cache", 997 | "memchr", 998 | "smallvec", 999 | "time", 1000 | ] 1001 | 1002 | [[package]] 1003 | name = "rustix" 1004 | version = "0.36.11" 1005 | source = "registry+https://github.com/rust-lang/crates.io-index" 1006 | checksum = "db4165c9963ab29e422d6c26fbc1d37f15bace6b2810221f9d925023480fcf0e" 1007 | dependencies = [ 1008 | "bitflags", 1009 | "errno", 1010 | "io-lifetimes", 1011 | "libc", 1012 | "linux-raw-sys", 1013 | "windows-sys 0.45.0", 1014 | ] 1015 | 1016 | [[package]] 1017 | name = "rustls" 1018 | version = "0.19.1" 1019 | source = "registry+https://github.com/rust-lang/crates.io-index" 1020 | checksum = "35edb675feee39aec9c99fa5ff985081995a06d594114ae14cbe797ad7b7a6d7" 1021 | dependencies = [ 1022 | "base64", 1023 | "log", 1024 | "ring", 1025 | "sct", 1026 | "webpki", 1027 | ] 1028 | 1029 | [[package]] 1030 | name = "schannel" 1031 | version = "0.1.21" 1032 | source = "registry+https://github.com/rust-lang/crates.io-index" 1033 | checksum = "713cfb06c7059f3588fb8044c0fad1d09e3c01d225e25b9220dbfdcf16dbb1b3" 1034 | dependencies = [ 1035 | "windows-sys 0.42.0", 1036 | ] 1037 | 1038 | [[package]] 1039 | name = "scopeguard" 1040 | version = "1.1.0" 1041 | source = "registry+https://github.com/rust-lang/crates.io-index" 1042 | checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" 1043 | 1044 | [[package]] 1045 | name = "scratch" 1046 | version = "1.0.5" 1047 | source = "registry+https://github.com/rust-lang/crates.io-index" 1048 | checksum = "1792db035ce95be60c3f8853017b3999209281c24e2ba5bc8e59bf97a0c590c1" 1049 | 1050 | [[package]] 1051 | name = "sct" 1052 | version = "0.6.1" 1053 | source = "registry+https://github.com/rust-lang/crates.io-index" 1054 | checksum = "b362b83898e0e69f38515b82ee15aa80636befe47c3b6d3d89a911e78fc228ce" 1055 | dependencies = [ 1056 | "ring", 1057 | "untrusted", 1058 | ] 1059 | 1060 | [[package]] 1061 | name = "security-framework" 1062 | version = "2.8.2" 1063 | source = "registry+https://github.com/rust-lang/crates.io-index" 1064 | checksum = "a332be01508d814fed64bf28f798a146d73792121129962fdf335bb3c49a4254" 1065 | dependencies = [ 1066 | "bitflags", 1067 | "core-foundation", 1068 | "core-foundation-sys", 1069 | "libc", 1070 | "security-framework-sys", 1071 | ] 1072 | 1073 | [[package]] 1074 | name = "security-framework-sys" 1075 | version = "2.8.0" 1076 | source = "registry+https://github.com/rust-lang/crates.io-index" 1077 | checksum = "31c9bb296072e961fcbd8853511dd39c2d8be2deb1e17c6860b1d30732b323b4" 1078 | dependencies = [ 1079 | "core-foundation-sys", 1080 | "libc", 1081 | ] 1082 | 1083 | [[package]] 1084 | name = "serde" 1085 | version = "1.0.158" 1086 | source = "registry+https://github.com/rust-lang/crates.io-index" 1087 | checksum = "771d4d9c4163ee138805e12c710dd365e4f44be8be0503cb1bb9eb989425d9c9" 1088 | dependencies = [ 1089 | "serde_derive", 1090 | ] 1091 | 1092 | [[package]] 1093 | name = "serde_derive" 1094 | version = "1.0.158" 1095 | source = "registry+https://github.com/rust-lang/crates.io-index" 1096 | checksum = "e801c1712f48475582b7696ac71e0ca34ebb30e09338425384269d9717c62cad" 1097 | dependencies = [ 1098 | "proc-macro2", 1099 | "quote", 1100 | "syn 2.0.10", 1101 | ] 1102 | 1103 | [[package]] 1104 | name = "sha2" 1105 | version = "0.10.6" 1106 | source = "registry+https://github.com/rust-lang/crates.io-index" 1107 | checksum = "82e6b795fe2e3b1e845bafcb27aa35405c4d47cdfc92af5fc8d3002f76cebdc0" 1108 | dependencies = [ 1109 | "cfg-if", 1110 | "cpufeatures", 1111 | "digest", 1112 | ] 1113 | 1114 | [[package]] 1115 | name = "siphasher" 1116 | version = "0.3.10" 1117 | source = "registry+https://github.com/rust-lang/crates.io-index" 1118 | checksum = "7bd3e3206899af3f8b12af284fafc038cc1dc2b41d1b89dd17297221c5d225de" 1119 | 1120 | [[package]] 1121 | name = "slab" 1122 | version = "0.4.8" 1123 | source = "registry+https://github.com/rust-lang/crates.io-index" 1124 | checksum = "6528351c9bc8ab22353f9d776db39a20288e8d6c37ef8cfe3317cf875eecfc2d" 1125 | dependencies = [ 1126 | "autocfg", 1127 | ] 1128 | 1129 | [[package]] 1130 | name = "smallvec" 1131 | version = "1.10.0" 1132 | source = "registry+https://github.com/rust-lang/crates.io-index" 1133 | checksum = "a507befe795404456341dfab10cef66ead4c041f62b8b11bbb92bffe5d0953e0" 1134 | 1135 | [[package]] 1136 | name = "socket2" 1137 | version = "0.4.9" 1138 | source = "registry+https://github.com/rust-lang/crates.io-index" 1139 | checksum = "64a4a911eed85daf18834cfaa86a79b7d266ff93ff5ba14005426219480ed662" 1140 | dependencies = [ 1141 | "libc", 1142 | "winapi", 1143 | ] 1144 | 1145 | [[package]] 1146 | name = "spin" 1147 | version = "0.5.2" 1148 | source = "registry+https://github.com/rust-lang/crates.io-index" 1149 | checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d" 1150 | 1151 | [[package]] 1152 | name = "stringprep" 1153 | version = "0.1.2" 1154 | source = "registry+https://github.com/rust-lang/crates.io-index" 1155 | checksum = "8ee348cb74b87454fff4b551cbf727025810a004f88aeacae7f85b87f4e9a1c1" 1156 | dependencies = [ 1157 | "unicode-bidi", 1158 | "unicode-normalization", 1159 | ] 1160 | 1161 | [[package]] 1162 | name = "strsim" 1163 | version = "0.8.0" 1164 | source = "registry+https://github.com/rust-lang/crates.io-index" 1165 | checksum = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a" 1166 | 1167 | [[package]] 1168 | name = "structopt" 1169 | version = "0.3.26" 1170 | source = "registry+https://github.com/rust-lang/crates.io-index" 1171 | checksum = "0c6b5c64445ba8094a6ab0c3cd2ad323e07171012d9c98b0b15651daf1787a10" 1172 | dependencies = [ 1173 | "clap", 1174 | "lazy_static", 1175 | "structopt-derive", 1176 | ] 1177 | 1178 | [[package]] 1179 | name = "structopt-derive" 1180 | version = "0.4.18" 1181 | source = "registry+https://github.com/rust-lang/crates.io-index" 1182 | checksum = "dcb5ae327f9cc13b68763b5749770cb9e048a99bd9dfdfa58d0cf05d5f64afe0" 1183 | dependencies = [ 1184 | "heck", 1185 | "proc-macro-error", 1186 | "proc-macro2", 1187 | "quote", 1188 | "syn 1.0.109", 1189 | ] 1190 | 1191 | [[package]] 1192 | name = "subtle" 1193 | version = "2.4.1" 1194 | source = "registry+https://github.com/rust-lang/crates.io-index" 1195 | checksum = "6bdef32e8150c2a081110b42772ffe7d7c9032b606bc226c8260fd97e0976601" 1196 | 1197 | [[package]] 1198 | name = "syn" 1199 | version = "1.0.109" 1200 | source = "registry+https://github.com/rust-lang/crates.io-index" 1201 | checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" 1202 | dependencies = [ 1203 | "proc-macro2", 1204 | "quote", 1205 | "unicode-ident", 1206 | ] 1207 | 1208 | [[package]] 1209 | name = "syn" 1210 | version = "2.0.10" 1211 | source = "registry+https://github.com/rust-lang/crates.io-index" 1212 | checksum = "5aad1363ed6d37b84299588d62d3a7d95b5a5c2d9aad5c85609fda12afaa1f40" 1213 | dependencies = [ 1214 | "proc-macro2", 1215 | "quote", 1216 | "unicode-ident", 1217 | ] 1218 | 1219 | [[package]] 1220 | name = "tempfile" 1221 | version = "3.4.0" 1222 | source = "registry+https://github.com/rust-lang/crates.io-index" 1223 | checksum = "af18f7ae1acd354b992402e9ec5864359d693cd8a79dcbef59f76891701c1e95" 1224 | dependencies = [ 1225 | "cfg-if", 1226 | "fastrand", 1227 | "redox_syscall", 1228 | "rustix", 1229 | "windows-sys 0.42.0", 1230 | ] 1231 | 1232 | [[package]] 1233 | name = "termcolor" 1234 | version = "1.2.0" 1235 | source = "registry+https://github.com/rust-lang/crates.io-index" 1236 | checksum = "be55cf8942feac5c765c2c993422806843c9a9a45d4d5c407ad6dd2ea95eb9b6" 1237 | dependencies = [ 1238 | "winapi-util", 1239 | ] 1240 | 1241 | [[package]] 1242 | name = "textwrap" 1243 | version = "0.11.0" 1244 | source = "registry+https://github.com/rust-lang/crates.io-index" 1245 | checksum = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060" 1246 | dependencies = [ 1247 | "unicode-width", 1248 | ] 1249 | 1250 | [[package]] 1251 | name = "time" 1252 | version = "0.1.45" 1253 | source = "registry+https://github.com/rust-lang/crates.io-index" 1254 | checksum = "1b797afad3f312d1c66a56d11d0316f916356d11bd158fbc6ca6389ff6bf805a" 1255 | dependencies = [ 1256 | "libc", 1257 | "wasi 0.10.0+wasi-snapshot-preview1", 1258 | "winapi", 1259 | ] 1260 | 1261 | [[package]] 1262 | name = "tinyvec" 1263 | version = "1.6.0" 1264 | source = "registry+https://github.com/rust-lang/crates.io-index" 1265 | checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50" 1266 | dependencies = [ 1267 | "tinyvec_macros", 1268 | ] 1269 | 1270 | [[package]] 1271 | name = "tinyvec_macros" 1272 | version = "0.1.1" 1273 | source = "registry+https://github.com/rust-lang/crates.io-index" 1274 | checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" 1275 | 1276 | [[package]] 1277 | name = "tokio" 1278 | version = "1.26.0" 1279 | source = "registry+https://github.com/rust-lang/crates.io-index" 1280 | checksum = "03201d01c3c27a29c8a5cee5b55a93ddae1ccf6f08f65365c2c918f8c1b76f64" 1281 | dependencies = [ 1282 | "autocfg", 1283 | "bytes", 1284 | "libc", 1285 | "memchr", 1286 | "mio", 1287 | "pin-project-lite", 1288 | "socket2", 1289 | "windows-sys 0.45.0", 1290 | ] 1291 | 1292 | [[package]] 1293 | name = "tokio-native-tls" 1294 | version = "0.3.1" 1295 | source = "registry+https://github.com/rust-lang/crates.io-index" 1296 | checksum = "bbae76ab933c85776efabc971569dd6119c580d8f5d448769dec1764bf796ef2" 1297 | dependencies = [ 1298 | "native-tls", 1299 | "tokio", 1300 | ] 1301 | 1302 | [[package]] 1303 | name = "tokio-postgres" 1304 | version = "0.7.7" 1305 | source = "registry+https://github.com/rust-lang/crates.io-index" 1306 | checksum = "29a12c1b3e0704ae7dfc25562629798b29c72e6b1d0a681b6f29ab4ae5e7f7bf" 1307 | dependencies = [ 1308 | "async-trait", 1309 | "byteorder", 1310 | "bytes", 1311 | "fallible-iterator", 1312 | "futures-channel", 1313 | "futures-util", 1314 | "log", 1315 | "parking_lot", 1316 | "percent-encoding", 1317 | "phf", 1318 | "pin-project-lite", 1319 | "postgres-protocol", 1320 | "postgres-types", 1321 | "socket2", 1322 | "tokio", 1323 | "tokio-util", 1324 | ] 1325 | 1326 | [[package]] 1327 | name = "tokio-postgres-rustls" 1328 | version = "0.8.0" 1329 | source = "registry+https://github.com/rust-lang/crates.io-index" 1330 | checksum = "7bd8c37d8c23cb6ecdc32fc171bade4e9c7f1be65f693a17afbaad02091a0a19" 1331 | dependencies = [ 1332 | "futures", 1333 | "ring", 1334 | "rustls", 1335 | "tokio", 1336 | "tokio-postgres", 1337 | "tokio-rustls", 1338 | "webpki", 1339 | ] 1340 | 1341 | [[package]] 1342 | name = "tokio-rustls" 1343 | version = "0.22.0" 1344 | source = "registry+https://github.com/rust-lang/crates.io-index" 1345 | checksum = "bc6844de72e57df1980054b38be3a9f4702aba4858be64dd700181a8a6d0e1b6" 1346 | dependencies = [ 1347 | "rustls", 1348 | "tokio", 1349 | "webpki", 1350 | ] 1351 | 1352 | [[package]] 1353 | name = "tokio-util" 1354 | version = "0.7.7" 1355 | source = "registry+https://github.com/rust-lang/crates.io-index" 1356 | checksum = "5427d89453009325de0d8f342c9490009f76e999cb7672d77e46267448f7e6b2" 1357 | dependencies = [ 1358 | "bytes", 1359 | "futures-core", 1360 | "futures-sink", 1361 | "pin-project-lite", 1362 | "tokio", 1363 | "tracing", 1364 | ] 1365 | 1366 | [[package]] 1367 | name = "toml" 1368 | version = "0.5.11" 1369 | source = "registry+https://github.com/rust-lang/crates.io-index" 1370 | checksum = "f4f7f0dd8d50a853a531c426359045b1998f04219d88799810762cd4ad314234" 1371 | dependencies = [ 1372 | "serde", 1373 | ] 1374 | 1375 | [[package]] 1376 | name = "tracing" 1377 | version = "0.1.37" 1378 | source = "registry+https://github.com/rust-lang/crates.io-index" 1379 | checksum = "8ce8c33a8d48bd45d624a6e523445fd21ec13d3653cd51f681abf67418f54eb8" 1380 | dependencies = [ 1381 | "cfg-if", 1382 | "pin-project-lite", 1383 | "tracing-core", 1384 | ] 1385 | 1386 | [[package]] 1387 | name = "tracing-core" 1388 | version = "0.1.30" 1389 | source = "registry+https://github.com/rust-lang/crates.io-index" 1390 | checksum = "24eb03ba0eab1fd845050058ce5e616558e8f8d8fca633e6b163fe25c797213a" 1391 | dependencies = [ 1392 | "once_cell", 1393 | ] 1394 | 1395 | [[package]] 1396 | name = "typenum" 1397 | version = "1.16.0" 1398 | source = "registry+https://github.com/rust-lang/crates.io-index" 1399 | checksum = "497961ef93d974e23eb6f433eb5fe1b7930b659f06d12dec6fc44a8f554c0bba" 1400 | 1401 | [[package]] 1402 | name = "unicode-bidi" 1403 | version = "0.3.13" 1404 | source = "registry+https://github.com/rust-lang/crates.io-index" 1405 | checksum = "92888ba5573ff080736b3648696b70cafad7d250551175acbaa4e0385b3e1460" 1406 | 1407 | [[package]] 1408 | name = "unicode-ident" 1409 | version = "1.0.8" 1410 | source = "registry+https://github.com/rust-lang/crates.io-index" 1411 | checksum = "e5464a87b239f13a63a501f2701565754bae92d243d4bb7eb12f6d57d2269bf4" 1412 | 1413 | [[package]] 1414 | name = "unicode-normalization" 1415 | version = "0.1.22" 1416 | source = "registry+https://github.com/rust-lang/crates.io-index" 1417 | checksum = "5c5713f0fc4b5db668a2ac63cdb7bb4469d8c9fed047b1d0292cc7b0ce2ba921" 1418 | dependencies = [ 1419 | "tinyvec", 1420 | ] 1421 | 1422 | [[package]] 1423 | name = "unicode-segmentation" 1424 | version = "1.10.1" 1425 | source = "registry+https://github.com/rust-lang/crates.io-index" 1426 | checksum = "1dd624098567895118886609431a7c3b8f516e41d30e0643f03d94592a147e36" 1427 | 1428 | [[package]] 1429 | name = "unicode-width" 1430 | version = "0.1.10" 1431 | source = "registry+https://github.com/rust-lang/crates.io-index" 1432 | checksum = "c0edd1e5b14653f783770bce4a4dabb4a5108a5370a5f5d8cfe8710c361f6c8b" 1433 | 1434 | [[package]] 1435 | name = "untrusted" 1436 | version = "0.7.1" 1437 | source = "registry+https://github.com/rust-lang/crates.io-index" 1438 | checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a" 1439 | 1440 | [[package]] 1441 | name = "vcpkg" 1442 | version = "0.2.15" 1443 | source = "registry+https://github.com/rust-lang/crates.io-index" 1444 | checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" 1445 | 1446 | [[package]] 1447 | name = "vec_map" 1448 | version = "0.8.2" 1449 | source = "registry+https://github.com/rust-lang/crates.io-index" 1450 | checksum = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191" 1451 | 1452 | [[package]] 1453 | name = "version_check" 1454 | version = "0.9.4" 1455 | source = "registry+https://github.com/rust-lang/crates.io-index" 1456 | checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" 1457 | 1458 | [[package]] 1459 | name = "wasi" 1460 | version = "0.10.0+wasi-snapshot-preview1" 1461 | source = "registry+https://github.com/rust-lang/crates.io-index" 1462 | checksum = "1a143597ca7c7793eff794def352d41792a93c481eb1042423ff7ff72ba2c31f" 1463 | 1464 | [[package]] 1465 | name = "wasi" 1466 | version = "0.11.0+wasi-snapshot-preview1" 1467 | source = "registry+https://github.com/rust-lang/crates.io-index" 1468 | checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" 1469 | 1470 | [[package]] 1471 | name = "wasm-bindgen" 1472 | version = "0.2.84" 1473 | source = "registry+https://github.com/rust-lang/crates.io-index" 1474 | checksum = "31f8dcbc21f30d9b8f2ea926ecb58f6b91192c17e9d33594b3df58b2007ca53b" 1475 | dependencies = [ 1476 | "cfg-if", 1477 | "wasm-bindgen-macro", 1478 | ] 1479 | 1480 | [[package]] 1481 | name = "wasm-bindgen-backend" 1482 | version = "0.2.84" 1483 | source = "registry+https://github.com/rust-lang/crates.io-index" 1484 | checksum = "95ce90fd5bcc06af55a641a86428ee4229e44e07033963a2290a8e241607ccb9" 1485 | dependencies = [ 1486 | "bumpalo", 1487 | "log", 1488 | "once_cell", 1489 | "proc-macro2", 1490 | "quote", 1491 | "syn 1.0.109", 1492 | "wasm-bindgen-shared", 1493 | ] 1494 | 1495 | [[package]] 1496 | name = "wasm-bindgen-macro" 1497 | version = "0.2.84" 1498 | source = "registry+https://github.com/rust-lang/crates.io-index" 1499 | checksum = "4c21f77c0bedc37fd5dc21f897894a5ca01e7bb159884559461862ae90c0b4c5" 1500 | dependencies = [ 1501 | "quote", 1502 | "wasm-bindgen-macro-support", 1503 | ] 1504 | 1505 | [[package]] 1506 | name = "wasm-bindgen-macro-support" 1507 | version = "0.2.84" 1508 | source = "registry+https://github.com/rust-lang/crates.io-index" 1509 | checksum = "2aff81306fcac3c7515ad4e177f521b5c9a15f2b08f4e32d823066102f35a5f6" 1510 | dependencies = [ 1511 | "proc-macro2", 1512 | "quote", 1513 | "syn 1.0.109", 1514 | "wasm-bindgen-backend", 1515 | "wasm-bindgen-shared", 1516 | ] 1517 | 1518 | [[package]] 1519 | name = "wasm-bindgen-shared" 1520 | version = "0.2.84" 1521 | source = "registry+https://github.com/rust-lang/crates.io-index" 1522 | checksum = "0046fef7e28c3804e5e38bfa31ea2a0f73905319b677e57ebe37e49358989b5d" 1523 | 1524 | [[package]] 1525 | name = "web-sys" 1526 | version = "0.3.61" 1527 | source = "registry+https://github.com/rust-lang/crates.io-index" 1528 | checksum = "e33b99f4b23ba3eec1a53ac264e35a755f00e966e0065077d6027c0f575b0b97" 1529 | dependencies = [ 1530 | "js-sys", 1531 | "wasm-bindgen", 1532 | ] 1533 | 1534 | [[package]] 1535 | name = "webpki" 1536 | version = "0.21.4" 1537 | source = "registry+https://github.com/rust-lang/crates.io-index" 1538 | checksum = "b8e38c0608262c46d4a56202ebabdeb094cef7e560ca7a226c6bf055188aa4ea" 1539 | dependencies = [ 1540 | "ring", 1541 | "untrusted", 1542 | ] 1543 | 1544 | [[package]] 1545 | name = "winapi" 1546 | version = "0.3.9" 1547 | source = "registry+https://github.com/rust-lang/crates.io-index" 1548 | checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" 1549 | dependencies = [ 1550 | "winapi-i686-pc-windows-gnu", 1551 | "winapi-x86_64-pc-windows-gnu", 1552 | ] 1553 | 1554 | [[package]] 1555 | name = "winapi-i686-pc-windows-gnu" 1556 | version = "0.4.0" 1557 | source = "registry+https://github.com/rust-lang/crates.io-index" 1558 | checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" 1559 | 1560 | [[package]] 1561 | name = "winapi-util" 1562 | version = "0.1.5" 1563 | source = "registry+https://github.com/rust-lang/crates.io-index" 1564 | checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" 1565 | dependencies = [ 1566 | "winapi", 1567 | ] 1568 | 1569 | [[package]] 1570 | name = "winapi-x86_64-pc-windows-gnu" 1571 | version = "0.4.0" 1572 | source = "registry+https://github.com/rust-lang/crates.io-index" 1573 | checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" 1574 | 1575 | [[package]] 1576 | name = "windows" 1577 | version = "0.46.0" 1578 | source = "registry+https://github.com/rust-lang/crates.io-index" 1579 | checksum = "cdacb41e6a96a052c6cb63a144f24900236121c6f63f4f8219fef5977ecb0c25" 1580 | dependencies = [ 1581 | "windows-targets", 1582 | ] 1583 | 1584 | [[package]] 1585 | name = "windows-sys" 1586 | version = "0.42.0" 1587 | source = "registry+https://github.com/rust-lang/crates.io-index" 1588 | checksum = "5a3e1820f08b8513f676f7ab6c1f99ff312fb97b553d30ff4dd86f9f15728aa7" 1589 | dependencies = [ 1590 | "windows_aarch64_gnullvm", 1591 | "windows_aarch64_msvc", 1592 | "windows_i686_gnu", 1593 | "windows_i686_msvc", 1594 | "windows_x86_64_gnu", 1595 | "windows_x86_64_gnullvm", 1596 | "windows_x86_64_msvc", 1597 | ] 1598 | 1599 | [[package]] 1600 | name = "windows-sys" 1601 | version = "0.45.0" 1602 | source = "registry+https://github.com/rust-lang/crates.io-index" 1603 | checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0" 1604 | dependencies = [ 1605 | "windows-targets", 1606 | ] 1607 | 1608 | [[package]] 1609 | name = "windows-targets" 1610 | version = "0.42.2" 1611 | source = "registry+https://github.com/rust-lang/crates.io-index" 1612 | checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071" 1613 | dependencies = [ 1614 | "windows_aarch64_gnullvm", 1615 | "windows_aarch64_msvc", 1616 | "windows_i686_gnu", 1617 | "windows_i686_msvc", 1618 | "windows_x86_64_gnu", 1619 | "windows_x86_64_gnullvm", 1620 | "windows_x86_64_msvc", 1621 | ] 1622 | 1623 | [[package]] 1624 | name = "windows_aarch64_gnullvm" 1625 | version = "0.42.2" 1626 | source = "registry+https://github.com/rust-lang/crates.io-index" 1627 | checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" 1628 | 1629 | [[package]] 1630 | name = "windows_aarch64_msvc" 1631 | version = "0.42.2" 1632 | source = "registry+https://github.com/rust-lang/crates.io-index" 1633 | checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" 1634 | 1635 | [[package]] 1636 | name = "windows_i686_gnu" 1637 | version = "0.42.2" 1638 | source = "registry+https://github.com/rust-lang/crates.io-index" 1639 | checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" 1640 | 1641 | [[package]] 1642 | name = "windows_i686_msvc" 1643 | version = "0.42.2" 1644 | source = "registry+https://github.com/rust-lang/crates.io-index" 1645 | checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" 1646 | 1647 | [[package]] 1648 | name = "windows_x86_64_gnu" 1649 | version = "0.42.2" 1650 | source = "registry+https://github.com/rust-lang/crates.io-index" 1651 | checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" 1652 | 1653 | [[package]] 1654 | name = "windows_x86_64_gnullvm" 1655 | version = "0.42.2" 1656 | source = "registry+https://github.com/rust-lang/crates.io-index" 1657 | checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" 1658 | 1659 | [[package]] 1660 | name = "windows_x86_64_msvc" 1661 | version = "0.42.2" 1662 | source = "registry+https://github.com/rust-lang/crates.io-index" 1663 | checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" 1664 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "movine" 3 | license = "MIT" 4 | version = "0.11.4" 5 | authors = ["Byron Wasti "] 6 | homepage = "https://github.com/byronwasti/movine" 7 | repository = "https://github.com/byronwasti/movine" 8 | description = "A simple database migration manager" 9 | readme = "README.md" 10 | maintenance = { status = "actively-developed" } 11 | edition = "2018" 12 | keywords = ["migration", "database", "cli"] 13 | categories = ["command-line-utilities", "database"] 14 | 15 | [dependencies] 16 | postgres = "0.19.1" 17 | chrono = "0.4.11" 18 | dotenv = "0.15" 19 | envy = "0.4" 20 | structopt = "0.3.14" 21 | toml = "0.5.6" 22 | serde = { version = "1.0.106", features = ["derive"] } 23 | log = "0.4.8" 24 | rusqlite = "0.23.1" 25 | env_logger = "0.7.1" 26 | postgres-native-tls = { version = "0.5.0", optional = true } 27 | native-tls = { version = "0.2.7", optional = true } 28 | libsqlite3-sys = { version = "0.18.0", features = ["bundled"] } 29 | ansi_term = "0.12.1" 30 | rustls = { version = "0.19.0", optional = true } 31 | tokio-postgres-rustls = { version = "0.8.0", optional = true } 32 | 33 | [features] 34 | default = ["with-native-tls"] 35 | with-native-tls = ["native-tls", "postgres-native-tls"] 36 | with-rustls = ["rustls", "tokio-postgres-rustls"] 37 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | # You can override this `--build-arg BASE_IMAGE=...` to use different 2 | # version of Rust or OpenSSL. 3 | ARG BASE_IMAGE=ekidd/rust-musl-builder:1.47.0 4 | 5 | # Our first FROM statement declares the build environment. 6 | FROM ${BASE_IMAGE} AS builder 7 | 8 | # Add our source code. 9 | ADD --chown=rust:rust . ./ 10 | 11 | # Build our application. 12 | RUN cargo build --release 13 | 14 | # Now, we need to build our _real_ Docker container 15 | FROM alpine:3.12 16 | RUN apk --no-cache add ca-certificates 17 | COPY --from=builder \ 18 | /home/rust/src/target/x86_64-unknown-linux-musl/release/movine \ 19 | /usr/local/bin/ 20 | CMD /usr/local/bin/movine -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2019 Byron Wasti 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software is furnished to do so, 10 | 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, FITNESS 17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | *NOTE: This project is no longer actively maintained. It was originally a Proof-of-concept and I never got around to properly fixing it up, and don't plan to. Feel free to use at your own risk, and I'll still take a look every now and then to merge fixes.* 2 | 3 | # Movine 4 | ![Linux build status](https://github.com/byronwasti/movine/workflows/CI/badge.svg) 5 | [![Crates.io](https://img.shields.io/crates/v/movine.svg)](https://crates.io/crates/movine) 6 | 7 | Movine is a simple database migration manager that aims to be compatible with real-world migration work. Many migration managers get confused with complicated development strategies for migrations. Oftentimes migration managers do not warn you if the SQL saved in git differs from what was actually run on the database. Movine solves this issue by keeping track of the unique hashes for the `up.sql` and `down.sql` for each migration, and provides tools for fixing issues. This allows users to easily keep track of whether their local migration history matches the one on the database. 8 | 9 | This project is currently in early stages. 10 | 11 | Movine does *not* aim to be an ORM. Consider [diesel](http://diesel.rs/) instead if you want an ORM. 12 | 13 | ## Migration Concepts 14 | 15 | Movine keeps track of four different states of migrations on the database. There are the basic ones: 16 | 17 | - Applied: Found locally and applied to the database 18 | - Pending: Found locally and not applied to the database 19 | 20 | Then there are the more complicated ones, which Movine was specifically designed to handle: 21 | 22 | - Variant: Found locally but a different version is applied to the database 23 | - Divergent: Not found locally but applied to the database 24 | 25 | ## Short Asciinema Demo 26 | 27 | A 3.5 minute video showcasing the various tools Movine provides. 28 | 29 | [![asciicast](https://asciinema.org/a/337321.svg)](https://asciinema.org/a/337321) 30 | 31 | ## Configuration 32 | 33 | The first step to get started with Movine is to set the configuration. Configuration can be supplied either through a `movile.toml` file or environment variables: 34 | 35 | ### Using a Config File 36 | If Movine finds a config file named `movine.toml` it will use the parameters specified. 37 | 38 | ```toml 39 | [postgres] 40 | host = {host} 41 | database = {db} 42 | user = {username} 43 | password = {pass} 44 | port = {port} 45 | sslrootcert = {cert filename} 46 | 47 | ## Or use the Sqlite adaptor 48 | [sqlite] 49 | file={file} 50 | 51 | ## Or supply a database URL 52 | database_url={url_string} 53 | ``` 54 | 55 | *Note: SSLRootCert currently does not work when supplying a database_url.* 56 | *Note: You should only specify connection details for one database type, or Movine will implicitly choose one* 57 | 58 | ### Environment variables 59 | 60 | You can configure the PostgreSQL adaptor using the environment variables described in the [PostgreSQL documentation](https://www.postgresql.org/docs/current/libpq-envars.html). Specifically `PGHOST`, `PGPORT`, `PGDATABASE`, `PGUSER`, and `PGPASSWORD` and `PGSSLROOTCERT` are supported. 61 | 62 | You can configure the SQLite adaptor using an `SQLITE_FILE` environment variable. 63 | 64 | Finally, you can also supply a `DATABASE_URL` environment variable. 65 | 66 | *Note: SSLRootCert does not work when using a database URL.* 67 | 68 | Movine supports [`.env`](https://github.com/dotenv-rs/dotenv#usage) files as a source of configuration. 69 | 70 | ## Initializing 71 | 72 | Next, you can run the `init` command to set everything up, the `generate` command to create your first migration, and once those are written you can run `up` to apply them. 73 | ``` 74 | $ movine init 75 | $ tree migrations/ 76 | migrations/ 77 | └── 1970-01-01-000000_movine_init 78 | ├── down.sql 79 | └── up.sql 80 | 81 | 1 directory, 2 files 82 | $ movine generate create_new_table 83 | $ tree migrations/ 84 | migrations/ 85 | ├── 1970-01-01-000000_movine_init 86 | │   ├── down.sql 87 | │   └── up.sql 88 | └── 2019-03-17-163451_create_new_table 89 | ├── down.sql 90 | └── up.sql 91 | 92 | 2 directories, 4 files 93 | $ movine up 94 | $ movine status 95 | 2019-03-17 16:34:51 UTC - Applied 2019-03-17-163451_create_new_table 96 | 1970-01-01 00:00:00 UTC - Applied 1970-01-01-000000_movine_init 97 | ``` 98 | 99 | ## Commands 100 | There are a few commands that Movine uses, and all of them can be listed by using `--help` on the command line. 101 | 102 | ### Init 103 | 104 | The `init` command will run the initialization routine for Movine, which will create a table on the database to keep track of migrations and create a local migrations folder. 105 | ``` 106 | $ movine init 107 | $ ls 108 | migrations/ movine.toml 109 | $ tree migrations/ 110 | migrations/ 111 | └── 1970-01-01-000000_movine_init 112 | ├── down.sql 113 | └── up.sql 114 | 115 | 1 directory, 2 files 116 | $ psql $PARAMS -c "\d" 117 | List of relations 118 | Schema | Name | Type | Owner 119 | --------+--------------------------+----------+-------- 120 | public | movine_migrations | table | movine 121 | public | movine_migrations_id_seq | sequence | movine 122 | ``` 123 | 124 | ### Generate 125 | 126 | The `generate` command will generate a folder with the current date and the given name in the `migrations/` directory with blank `up.sql` and `down.sql` files. 127 | ``` 128 | $ movine generate create_new_table 129 | $ tree migrations/ 130 | migrations/ 131 | ├── 1970-01-01-000000_movine_init 132 | │   ├── down.sql 133 | │   └── up.sql 134 | └── 2019-03-17-163451_create_new_table 135 | ├── down.sql 136 | └── up.sql 137 | 138 | 2 directories, 4 files 139 | ``` 140 | 141 | ### Status 142 | 143 | The `status` command will tell you the current state of all migrations, both local and on the database. 144 | 145 | ``` 146 | $ movine status 147 | 2019-03-17 16:34:51 UTC - Pending 2019-03-17-163451_create_new_table 148 | 1970-01-01 00:00:00 UTC - Applied 1970-01-01-000000_movine_init 149 | ``` 150 | ### Up 151 | 152 | The `up` command will run all pending migrations. You can also run with the `-p` flag to show the migration plan without running it. This is true for all commands that modify the database and is useful for seeing if Movine will do what you expect. 153 | 154 | ``` 155 | $ movine up -p 156 | 1. Up - 2019-03-17-163451_create_new_table 157 | $ movine status 158 | 2019-03-17 16:34:51 UTC - Pending 2019-03-17-163451_create_new_table 159 | 1970-01-01 00:00:00 UTC - Applied 1970-01-01-000000_movine_init 160 | $ movine up 161 | $ movine status 162 | 2019-03-17 16:34:51 UTC - Applied 2019-03-17-163451_create_new_table 163 | 1970-01-01 00:00:00 UTC - Applied 1970-01-01-000000_movine_init 164 | ``` 165 | 166 | ### Down 167 | 168 | The `down` command will rollback the most recent migration. 169 | ``` 170 | $ movine down 171 | $ movine status 172 | 2019-03-17 16:34:51 UTC - Pending 2019-03-17-163451_create_new_table 173 | 1970-01-01 00:00:00 UTC - Applied 1970-01-01-000000_movine_init 174 | ``` 175 | 176 | ### Redo 177 | 178 | The `redo` command will rollback and then re-apply the most recent applied migration or variant migration. 179 | _Note: If the latest migration is `divergent` then redo will simply skip it. Be careful, and run `fix` if you want to fix `divergent` migrations._ 180 | ``` 181 | $ movine status 182 | 2019-03-17 16:34:51 UTC - Variant 2019-03-17-163451_create_new_table 183 | 1970-01-01 00:00:00 UTC - Applied 1970-01-01-000000_movine_init 184 | $ movine redo 185 | $ movine status 186 | 2019-03-17 16:34:51 UTC - Applied 2019-03-17-163451_create_new_table 187 | 1970-01-01 00:00:00 UTC - Applied 1970-01-01-000000_movine_init 188 | ``` 189 | 190 | ### Fix 191 | 192 | The `fix` command will rollback everything until there are no divergent or variant migrations, and then apply all migrations _except_ the migrations that were pending at the start. 193 | ``` 194 | $ movine status 195 | 2019-03-17 16:41:07 UTC - Pending 2019-03-17-164107_create_another_table 196 | 2019-03-17 16:40:59 UTC - Divergent 2019-03-17-164059_modify_table 197 | 2019-03-17 16:34:51 UTC - Variant 2019-03-17-163451_create_new_table 198 | 1970-01-01 00:00:00 UTC - Applied 1970-01-01-000000_movine_init 199 | $ movine fix 200 | $ movine status 201 | 2019-03-17 16:41:07 UTC - Pending 2019-03-17-164107_create_another_table 202 | 2019-03-17 16:34:51 UTC - Applied 2019-03-17-163451_create_new_table 203 | 1970-01-01 00:00:00 UTC - Applied 1970-01-01-000000_movine_init 204 | ``` 205 | 206 | ### Custom 207 | 208 | The `custom` command will allow you to specify your own migration strategy (in case Movine is not smart enough). *Note: this is currently not implemented* 209 | 210 | ## Library Usage 211 | *Note: While the `Movine` implementation is stable at this point, the `config` API may be in flux (specifically the helper functions). Please let me know any feedback!* 212 | 213 | Movine can be used as a library like so (using helper functions to load the database connection): 214 | ```rust 215 | use movine::{Movine, Config}; 216 | use movine::errors::Error; 217 | 218 | fn main() -> Result<(), Error> { 219 | let config = Config::load(&"movine.toml")?; 220 | let mut conn = config.into_sqlite_conn(); 221 | let mut movine = Movine::new(&mut conn); 222 | movine.up()?; 223 | Ok(()) 224 | } 225 | ``` 226 | 227 | Or if you already have a connection: 228 | ```rust 229 | use movine::{Movine, Config}; 230 | use movine::errors::Error; 231 | 232 | fn main() -> Result<(), Error> { 233 | // Same concept with a postgres connection! 234 | let mut conn = rusqlite::Connection::open("file.db")?; 235 | let mut movine = Movine::new(&mut conn); 236 | movine.up()?; 237 | Ok(()) 238 | } 239 | ``` 240 | 241 | ## Why you should use Movine 242 | 243 | - You accept the risks of pre-1.0 software 244 | - You want to write raw sql for your migrations 245 | - You have a shared database that has migrations developed by multiple developers 246 | - You want a migration management solution that works for the developers 247 | 248 | ## Why you should not use Movine 249 | 250 | - You want long battle-tested database migration manager 251 | - You want ORM integration (consider [diesel](http://diesel.rs/) instead) 252 | - You don't see value in keeping track of variant or divergent migrations 253 | 254 | -------------------------------------------------------------------------------- /src/adaptor.rs: -------------------------------------------------------------------------------- 1 | use crate::display; 2 | use crate::errors::Result; 3 | use crate::migration::Migration; 4 | use crate::plan_builder::Step; 5 | 6 | mod postgres; 7 | mod sqlite; 8 | 9 | pub trait DbAdaptor { 10 | fn init_up_sql(&self) -> &'static str; 11 | fn init_down_sql(&self) -> &'static str; 12 | fn load_migrations(&mut self) -> Result>; 13 | fn run_up_migration(&mut self, migration: &Migration) -> Result<()>; 14 | fn run_down_migration(&mut self, migration: &Migration) -> Result<()>; 15 | 16 | fn run_migration_plan(&mut self, plan: &[(Step, &Migration)]) -> Result<()> { 17 | for (step, migration) in plan { 18 | display::print_step(&(*step, migration)); 19 | match step { 20 | Step::Up => { 21 | self.run_up_migration(migration)?; 22 | } 23 | Step::Down => { 24 | if migration.is_reversable() { 25 | self.run_down_migration(migration)?; 26 | } 27 | } 28 | } 29 | } 30 | Ok(()) 31 | } 32 | } 33 | 34 | impl DbAdaptor for &'_ mut T { 35 | fn init_up_sql(&self) -> &'static str { 36 | (**self).init_up_sql() 37 | } 38 | 39 | fn init_down_sql(&self) -> &'static str { 40 | (**self).init_down_sql() 41 | } 42 | 43 | fn load_migrations(&mut self) -> Result> { 44 | (**self).load_migrations() 45 | } 46 | 47 | fn run_up_migration(&mut self, migration: &Migration) -> Result<()> { 48 | (**self).run_up_migration(migration) 49 | } 50 | 51 | fn run_down_migration(&mut self, migration: &Migration) -> Result<()> { 52 | (**self).run_down_migration(migration) 53 | } 54 | 55 | fn run_migration_plan(&mut self, plan: &[(Step, &Migration)]) -> Result<()> { 56 | (**self).run_migration_plan(plan) 57 | } 58 | } 59 | 60 | impl DbAdaptor for Box { 61 | fn init_up_sql(&self) -> &'static str { 62 | (**self).init_up_sql() 63 | } 64 | 65 | fn init_down_sql(&self) -> &'static str { 66 | (**self).init_down_sql() 67 | } 68 | 69 | fn load_migrations(&mut self) -> Result> { 70 | (**self).load_migrations() 71 | } 72 | 73 | fn run_up_migration(&mut self, migration: &Migration) -> Result<()> { 74 | (**self).run_up_migration(migration) 75 | } 76 | 77 | fn run_down_migration(&mut self, migration: &Migration) -> Result<()> { 78 | (**self).run_down_migration(migration) 79 | } 80 | 81 | fn run_migration_plan(&mut self, plan: &[(Step, &Migration)]) -> Result<()> { 82 | (**self).run_migration_plan(plan) 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /src/adaptor/postgres.rs: -------------------------------------------------------------------------------- 1 | use crate::adaptor::DbAdaptor; 2 | use crate::errors::{Error, Result}; 3 | use crate::migration::{Migration, MigrationBuilder}; 4 | 5 | impl DbAdaptor for postgres::Client { 6 | fn init_up_sql(&self) -> &'static str { 7 | INIT_UP_SQL 8 | } 9 | 10 | fn init_down_sql(&self) -> &'static str { 11 | INIT_DOWN_SQL 12 | } 13 | 14 | fn load_migrations(&mut self) -> Result> { 15 | let mut migrations = Vec::new(); 16 | let sql = " 17 | SELECT name, hash, down_sql 18 | FROM movine_migrations 19 | ORDER BY created_at DESC; 20 | "; 21 | let rows = self.query(sql, &[])?; 22 | for row in &rows { 23 | let name: String = row.get(0); 24 | let hash: String = row.get(1); 25 | let down_sql: String = row.get(2); 26 | let migration = MigrationBuilder::new() 27 | .compound_name(&name) 28 | .hash(&hash) 29 | .down_sql(&down_sql) 30 | .build()?; 31 | migrations.push(migration); 32 | } 33 | Ok(migrations) 34 | } 35 | 36 | fn run_up_migration(&mut self, migration: &Migration) -> Result<()> { 37 | let name = &migration.name; 38 | let hash = migration.hash.as_ref().ok_or(Error::BadMigration)?; 39 | let up_sql = migration.up_sql.as_ref().ok_or(Error::BadMigration)?; 40 | let empty_string = "".to_string(); 41 | let down_sql = migration.down_sql.as_ref().unwrap_or(&empty_string); 42 | 43 | let mut transaction = self.transaction()?; 44 | transaction.batch_execute(up_sql)?; 45 | transaction.execute(LOG_UP_MIGRATION, &[&name, &hash, &down_sql])?; 46 | transaction.commit()?; 47 | Ok(()) 48 | } 49 | 50 | fn run_down_migration(&mut self, migration: &Migration) -> Result<()> { 51 | let name = &migration.name; 52 | let down_sql = migration.down_sql.as_ref().ok_or(Error::BadMigration)?; 53 | 54 | let mut transaction = self.transaction()?; 55 | transaction.batch_execute(down_sql)?; 56 | transaction.execute(LOG_DOWN_MIGRATION, &[&name])?; 57 | transaction.commit()?; 58 | Ok(()) 59 | } 60 | } 61 | 62 | pub const LOG_UP_MIGRATION: &str = "\ 63 | INSERT INTO movine_migrations (name, hash, down_sql) 64 | VALUES ($1, $2, $3); 65 | "; 66 | 67 | pub const LOG_DOWN_MIGRATION: &str = "\ 68 | DELETE FROM movine_migrations 69 | WHERE name = $1; 70 | "; 71 | 72 | pub const INIT_UP_SQL: &str = "\ 73 | CREATE TABLE movine_migrations ( 74 | id SERIAL PRIMARY KEY, 75 | created_at TIMESTAMP DEFAULT now(), 76 | updated_at TIMESTAMP DEFAULT now(), 77 | name TEXT NOT NULL, 78 | hash TEXT NOT NULL, 79 | down_sql TEXT 80 | ); 81 | "; 82 | 83 | pub const INIT_DOWN_SQL: &str = "\ 84 | DROP TABLE movine_migrations; 85 | "; 86 | -------------------------------------------------------------------------------- /src/adaptor/sqlite.rs: -------------------------------------------------------------------------------- 1 | use crate::adaptor::DbAdaptor; 2 | use crate::errors::{Error, Result}; 3 | use crate::migration::{Migration, MigrationBuilder}; 4 | use rusqlite::{params, Connection}; 5 | 6 | impl DbAdaptor for Connection { 7 | fn init_up_sql(&self) -> &'static str { 8 | INIT_UP_SQL 9 | } 10 | 11 | fn init_down_sql(&self) -> &'static str { 12 | INIT_DOWN_SQL 13 | } 14 | 15 | fn load_migrations(&mut self) -> Result> { 16 | let mut migrations = Vec::new(); 17 | let sql = " 18 | SELECT name, hash, down_sql 19 | FROM movine_migrations 20 | ORDER BY created_at DESC; 21 | "; 22 | let mut stmt = self.prepare(sql)?; 23 | let rows: std::result::Result, _> = stmt 24 | .query_map(params![], |row| Ok((row.get(0)?, row.get(1)?, row.get(2)?)))? 25 | .collect(); 26 | let rows: Vec<(String, String, String)> = rows.unwrap(); 27 | 28 | for row in rows { 29 | let name: String = row.0; 30 | let hash: String = row.1; 31 | let down_sql: String = row.2; 32 | let migration = MigrationBuilder::new() 33 | .compound_name(&name) 34 | .hash(&hash) 35 | .down_sql(&down_sql) 36 | .build()?; 37 | migrations.push(migration); 38 | } 39 | Ok(migrations) 40 | } 41 | 42 | fn run_up_migration(&mut self, migration: &Migration) -> Result<()> { 43 | let name = &migration.name; 44 | let hash = migration.hash.as_ref().ok_or(Error::BadMigration)?; 45 | let up_sql = migration.up_sql.as_ref().ok_or(Error::BadMigration)?; 46 | let empty_string = "".to_string(); 47 | let down_sql = migration.down_sql.as_ref().unwrap_or(&empty_string); 48 | 49 | let transaction = self.transaction()?; 50 | transaction.execute_batch(up_sql)?; 51 | transaction.execute(LOG_UP_MIGRATION, &[&name, &hash, &down_sql])?; 52 | transaction.commit()?; 53 | Ok(()) 54 | } 55 | 56 | fn run_down_migration(&mut self, migration: &Migration) -> Result<()> { 57 | let name = &migration.name; 58 | let down_sql = migration.down_sql.as_ref().ok_or(Error::BadMigration)?; 59 | 60 | let transaction = self.transaction()?; 61 | transaction.execute_batch(down_sql)?; 62 | transaction.execute(LOG_DOWN_MIGRATION, &[&name])?; 63 | transaction.commit()?; 64 | Ok(()) 65 | } 66 | } 67 | 68 | pub const LOG_UP_MIGRATION: &str = "\ 69 | INSERT INTO movine_migrations (name, hash, down_sql) 70 | VALUES ($1, $2, $3); 71 | "; 72 | 73 | pub const LOG_DOWN_MIGRATION: &str = "\ 74 | DELETE FROM movine_migrations 75 | WHERE name = $1; 76 | "; 77 | 78 | pub const INIT_UP_SQL: &str = "\ 79 | CREATE TABLE movine_migrations ( 80 | id INTEGER PRIMARY KEY AUTOINCREMENT, 81 | created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, 82 | updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, 83 | name TEXT NOT NULL, 84 | hash TEXT NOT NULL, 85 | down_sql TEXT 86 | ); 87 | "; 88 | 89 | pub const INIT_DOWN_SQL: &str = "\ 90 | DROP TABLE movine_migrations; 91 | "; 92 | -------------------------------------------------------------------------------- /src/cli.rs: -------------------------------------------------------------------------------- 1 | use structopt::StructOpt; 2 | 3 | #[derive(Debug, StructOpt)] 4 | #[structopt(name = "movine", about = "the simple migration manager")] 5 | pub enum Opt { 6 | #[structopt(name = "status")] 7 | /// Get the status of migrations (applied, unapplied, mismatched). 8 | Status { 9 | #[structopt(short = "v", long = "verbose")] 10 | /// Run with verbose logging 11 | debug: bool, 12 | }, 13 | 14 | #[structopt(name = "up")] 15 | /// Run all pending migrations. 16 | Up { 17 | #[structopt(short = "n", long = "number")] 18 | /// Number of up or down migrations to run. 19 | number: Option, 20 | 21 | #[structopt(short = "p", long = "plan")] 22 | /// Do a dry run and show the migration plan. 23 | show_plan: bool, 24 | 25 | #[structopt(short = "s", long = "strict")] 26 | /// Error out on out-of-order pending migrations. 27 | strict: bool, 28 | 29 | #[structopt(short = "v", long = "verbose")] 30 | /// Run with verbose logging 31 | debug: bool, 32 | }, 33 | 34 | #[structopt(name = "down")] 35 | /// Rollback the latest migration. 36 | Down { 37 | #[structopt(short = "n", long = "number")] 38 | /// Number of up or down migrations to run. 39 | number: Option, 40 | 41 | #[structopt(short = "p", long = "plan")] 42 | /// Do a dry run and show the migration plan. 43 | show_plan: bool, 44 | 45 | #[structopt(short = "i", long = "ignore-divergent")] 46 | /// Ignore any divergent migrations. 47 | ignore_divergent: bool, 48 | 49 | #[structopt(short = "u", long = "ignore-unreversable")] 50 | /// Ignore any unreversable migrations. 51 | ignore_unreversable: bool, 52 | 53 | #[structopt(short = "v", long = "verbose")] 54 | /// Run with verbose logging 55 | debug: bool, 56 | }, 57 | 58 | #[structopt(name = "fix")] 59 | /// Rollback all divergent migrations and variant migrations, and then run all pending. 60 | Fix { 61 | #[structopt(short = "p", long = "plan")] 62 | /// Do a dry run and show the migration plan. 63 | show_plan: bool, 64 | 65 | #[structopt(short = "v", long = "verbose")] 66 | /// Run with verbose logging 67 | debug: bool, 68 | }, 69 | 70 | #[structopt(name = "redo")] 71 | /// Rollback the most recent migration and then run it. 72 | Redo { 73 | #[structopt(short = "n", long = "number")] 74 | /// Number of up or down migrations to run. 75 | number: Option, 76 | 77 | #[structopt(short = "p", long = "plan")] 78 | /// Do a dry run and show the migration plan. 79 | show_plan: bool, 80 | 81 | #[structopt(short = "i", long = "ignore-divergent")] 82 | /// Ignore any divergent migrations. 83 | ignore_divergent: bool, 84 | 85 | #[structopt(short = "u", long = "ignore-unreversable")] 86 | /// Ignore any unreversable migrations. 87 | ignore_unreversable: bool, 88 | 89 | #[structopt(short = "v", long = "verbose")] 90 | /// Run with verbose logging 91 | debug: bool, 92 | }, 93 | 94 | #[structopt(name = "custom")] 95 | /// [unimplemented] 96 | Custom { 97 | #[structopt(short = "p", long = "plan")] 98 | /// Do a dry run and show the migration plan. 99 | show_plan: bool, 100 | 101 | #[structopt(short = "v", long = "verbose")] 102 | /// Run with verbose logging 103 | debug: bool, 104 | 105 | plan: Vec, 106 | }, 107 | 108 | #[structopt(name = "generate")] 109 | /// Generate a migration with a given name. 110 | Generate { 111 | #[structopt(short = "v", long = "verbose")] 112 | /// Run with verbose logging 113 | debug: bool, 114 | 115 | name: String, 116 | }, 117 | 118 | #[structopt(name = "init")] 119 | /// Initialize the database and the local migration directory. 120 | Init { 121 | #[structopt(short = "v", long = "verbose")] 122 | /// Run with verbose logging 123 | debug: bool, 124 | }, 125 | } 126 | -------------------------------------------------------------------------------- /src/config.rs: -------------------------------------------------------------------------------- 1 | use crate::errors::{Error, Result}; 2 | use crate::DbAdaptor; 3 | use log::debug; 4 | #[cfg(feature = "with-native-tls")] 5 | use native_tls::{Certificate, TlsConnector}; 6 | #[cfg(feature = "with-native-tls")] 7 | use postgres_native_tls::MakeTlsConnector; 8 | #[cfg(feature = "with-rustls")] 9 | use rustls::ClientConfig; 10 | use serde::Deserialize; 11 | use std::convert::TryInto; 12 | #[cfg(feature = "with-native-tls")] 13 | use std::fs; 14 | use std::fs::File; 15 | use std::io::Read; 16 | #[cfg(feature = "with-rustls")] 17 | use tokio_postgres_rustls::MakeRustlsConnect; 18 | 19 | mod postgres_params; 20 | mod sqlite_params; 21 | 22 | pub use self::postgres_params::PostgresParams; 23 | use self::postgres_params::RawPostgresParams; 24 | use sqlite_params::RawSqliteParams; 25 | pub use sqlite_params::SqliteParams; 26 | 27 | #[derive(Debug, Default)] 28 | pub struct Config { 29 | pub postgres: Option, 30 | pub sqlite: Option, 31 | pub database_url: Option, 32 | } 33 | 34 | impl Config { 35 | pub fn load(file: &str) -> Result { 36 | let raw_config = RawConfig::load_file(file); 37 | let pg_env_params = RawPostgresParams::load_from_env(); 38 | let sqlite_env_params = RawSqliteParams::load_from_env(); 39 | let database_url = std::env::var("DATABASE_URL"); 40 | 41 | debug!( 42 | "Config information loaded: 43 | file: {:?} 44 | pg_env: {:?} 45 | sqlite_env: {:?} 46 | database_url: {:?}", 47 | &raw_config, &pg_env_params, &sqlite_env_params, &database_url 48 | ); 49 | 50 | if let Ok(database_url) = database_url { 51 | debug!("Using database_url provided."); 52 | return Ok(Config { 53 | database_url: Some(database_url), 54 | ..Self::default() 55 | }); 56 | } 57 | 58 | let raw_config = match raw_config { 59 | Ok(raw_config) => Some(raw_config), 60 | Err(Error::IoError(e)) if e.kind() == std::io::ErrorKind::NotFound => { 61 | debug!("Config file not found."); 62 | None 63 | } 64 | Err(e) => { 65 | return Err(e); 66 | } 67 | }; 68 | 69 | match raw_config { 70 | Some(RawConfig { 71 | postgres: Some(pg_params), 72 | .. 73 | }) => { 74 | debug!("Using postgres config-file params provided."); 75 | let all_params = [Ok(pg_params), pg_env_params]; 76 | let params: Vec<_> = all_params.iter().filter_map(|x| x.as_ref().ok()).collect(); 77 | let params: PostgresParams = (¶ms[..]).try_into()?; 78 | Ok(Self { 79 | postgres: Some(params), 80 | ..Self::default() 81 | }) 82 | } 83 | Some(RawConfig { 84 | sqlite: Some(sqlite_params), 85 | .. 86 | }) => { 87 | debug!("Using sqlite config-file params provided."); 88 | let all_params = [Ok(sqlite_params), sqlite_env_params]; 89 | let params: Vec<_> = all_params.iter().filter_map(|x| x.as_ref().ok()).collect(); 90 | let params = (¶ms[..]).try_into()?; 91 | Ok(Self { 92 | sqlite: Some(params), 93 | ..Self::default() 94 | }) 95 | } 96 | _ => match (pg_env_params, sqlite_env_params) { 97 | (Ok(pg_env_params), _) if pg_env_params.is_any() => { 98 | debug!("Using postgres env vars provided."); 99 | let params = [&pg_env_params]; 100 | let params = (¶ms[..]).try_into()?; 101 | Ok(Self { 102 | postgres: Some(params), 103 | ..Self::default() 104 | }) 105 | } 106 | (_, Ok(sqlite_env_params)) if sqlite_env_params.is_any() => { 107 | debug!("Using sqlite env vars provided."); 108 | let params = [&sqlite_env_params]; 109 | let params = (¶ms[..]).try_into()?; 110 | Ok(Self { 111 | sqlite: Some(params), 112 | ..Self::default() 113 | }) 114 | } 115 | _ => Err(Error::ConfigNotFound), 116 | }, 117 | } 118 | } 119 | 120 | pub fn into_pg_conn_from_url(self) -> Result { 121 | if let Some(ref url) = self.database_url { 122 | if url.starts_with("postgres") { 123 | let conn = postgres::Client::connect(url, postgres::NoTls)?; 124 | Ok(conn) 125 | } else { 126 | Err(Error::AdaptorNotFound) 127 | } 128 | } else { 129 | Err(Error::AdaptorNotFound) 130 | } 131 | } 132 | 133 | pub fn into_pg_conn_from_config(self) -> Result { 134 | if let Some(ref params) = self.postgres { 135 | let url = match params.password { 136 | Some(ref password) => format!( 137 | "postgresql://{user}:{password}@{host}:{port}/{database}", 138 | user = params.user, 139 | password = password, 140 | host = params.host, 141 | port = params.port, 142 | database = params.database, 143 | ), 144 | None => format!( 145 | "postgresql://{user}@{host}:{port}/{database}", 146 | user = params.user, 147 | host = params.host, 148 | port = params.port, 149 | database = params.database, 150 | ), 151 | }; 152 | let conn = if let Some(cert) = ¶ms.sslrootcert { 153 | build_tls_connection(&url, cert)? 154 | } else { 155 | postgres::Client::connect(&url, postgres::NoTls)? 156 | }; 157 | 158 | Ok(conn) 159 | } else { 160 | Err(Error::AdaptorNotFound) 161 | } 162 | } 163 | 164 | pub fn into_sqlite_conn(self) -> Result { 165 | if let Some(ref params) = self.sqlite { 166 | let conn = rusqlite::Connection::open(¶ms.file)?; 167 | Ok(conn) 168 | } else { 169 | Err(Error::AdaptorNotFound) 170 | } 171 | } 172 | 173 | pub fn into_db_adaptor(self) -> Result> { 174 | match self { 175 | Config { 176 | database_url: Some(_), 177 | .. 178 | } => Ok(Box::new(self.into_pg_conn_from_url()?)), 179 | Config { 180 | postgres: Some(_), .. 181 | } => Ok(Box::new(self.into_pg_conn_from_config()?)), 182 | Config { 183 | sqlite: Some(_), .. 184 | } => Ok(Box::new(self.into_sqlite_conn()?)), 185 | _ => Err(Error::AdaptorNotFound), 186 | } 187 | } 188 | } 189 | 190 | #[derive(Debug, Deserialize)] 191 | pub struct RawConfig { 192 | pub postgres: Option, 193 | pub sqlite: Option, 194 | } 195 | 196 | impl RawConfig { 197 | pub fn load_file(file: &str) -> Result { 198 | let mut file = File::open(file)?; 199 | let mut config = String::new(); 200 | file.read_to_string(&mut config)?; 201 | let config = toml::from_str(&config)?; 202 | Ok(config) 203 | } 204 | } 205 | 206 | #[cfg(feature = "with-native-tls")] 207 | fn build_tls_connection(url: &str, certificate: &str) -> Result { 208 | let cert = fs::read(certificate)?; 209 | let cert = Certificate::from_pem(&cert)?; 210 | let connector = TlsConnector::builder().add_root_certificate(cert).build()?; 211 | let tls = MakeTlsConnector::new(connector); 212 | Ok(postgres::Client::connect(url, tls)?) 213 | } 214 | 215 | #[cfg(feature = "with-rustls")] 216 | fn build_tls_connection(url: &str, certificate: &str) -> Result { 217 | use std::io::BufReader; 218 | 219 | let f = File::open(certificate)?; 220 | let mut reader = BufReader::new(f); 221 | let mut config = ClientConfig::new(); 222 | config 223 | .root_store 224 | .add_pem_file(&mut reader) 225 | .map_err(|_| Error::RustlsPemfileError)?; 226 | 227 | let tls = MakeRustlsConnect::new(config); 228 | Ok(postgres::Client::connect(&url, tls)?) 229 | } 230 | -------------------------------------------------------------------------------- /src/config/postgres_params.rs: -------------------------------------------------------------------------------- 1 | use crate::errors::{Error, Result}; 2 | use serde::Deserialize; 3 | use std::convert::TryFrom; 4 | 5 | #[derive(Debug, Clone)] 6 | pub struct PostgresParams { 7 | pub user: String, 8 | pub password: Option, 9 | pub host: String, 10 | pub database: String, 11 | pub port: i32, 12 | pub sslrootcert: Option, 13 | } 14 | 15 | impl TryFrom<&[&RawPostgresParams]> for PostgresParams { 16 | type Error = Error; 17 | 18 | fn try_from(value: &[&RawPostgresParams]) -> Result { 19 | let params = value 20 | .iter() 21 | .fold(RawPostgresParams::default(), |mut acc, x| { 22 | acc.user = x.user.to_owned().or(acc.user); 23 | acc.password = x.password.to_owned().or(acc.password); 24 | acc.host = x.host.to_owned().or(acc.host); 25 | acc.database = x.database.to_owned().or(acc.database); 26 | acc.port = x.port.to_owned().or(acc.port); 27 | acc.sslrootcert = x.sslrootcert.to_owned().or(acc.sslrootcert); 28 | acc 29 | }); 30 | 31 | match params { 32 | RawPostgresParams { 33 | user: Some(user), 34 | password, 35 | database: Some(database), 36 | host: Some(host), 37 | port: Some(port), 38 | sslrootcert, 39 | } => Ok(Self { 40 | user, 41 | password, 42 | host, 43 | database, 44 | port, 45 | sslrootcert, 46 | }), 47 | p => Err(Error::PgParamError { 48 | user: p.user.is_some(), 49 | password: p.password.is_some(), 50 | database: p.database.is_some(), 51 | host: p.host.is_some(), 52 | port: p.port.is_some(), 53 | }), 54 | } 55 | } 56 | } 57 | 58 | #[derive(Debug, Deserialize)] 59 | pub struct RawPostgresParams { 60 | pub user: Option, 61 | pub password: Option, 62 | pub host: Option, 63 | pub database: Option, 64 | pub port: Option, 65 | pub sslrootcert: Option, 66 | } 67 | 68 | impl RawPostgresParams { 69 | pub fn load_from_env() -> Result { 70 | let params = envy::prefixed("PG").from_env()?; 71 | Ok(params) 72 | } 73 | 74 | pub fn is_any(&self) -> bool { 75 | self.user.is_some() 76 | || self.password.is_some() 77 | || self.host.is_some() 78 | || self.database.is_some() 79 | || self.port.is_some() 80 | } 81 | } 82 | 83 | impl Default for RawPostgresParams { 84 | fn default() -> Self { 85 | Self { 86 | user: None, 87 | password: None, 88 | host: None, 89 | database: None, 90 | port: Some(5432), 91 | sslrootcert: None, 92 | } 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /src/config/sqlite_params.rs: -------------------------------------------------------------------------------- 1 | use crate::errors::{Error, Result}; 2 | use serde::Deserialize; //::{params, Connection, Result}; 3 | use std::convert::TryFrom; 4 | 5 | #[derive(Debug)] 6 | pub struct SqliteParams { 7 | pub file: String, 8 | } 9 | 10 | impl TryFrom<&[&RawSqliteParams]> for SqliteParams { 11 | type Error = Error; 12 | 13 | fn try_from(value: &[&RawSqliteParams]) -> Result { 14 | let params = value.iter().fold(RawSqliteParams::default(), |mut acc, x| { 15 | acc.file = x.file.to_owned().or(acc.file); 16 | acc 17 | }); 18 | 19 | match params { 20 | RawSqliteParams { file: Some(file) } => Ok(Self { file }), 21 | p => Err(Error::SqliteParamError { 22 | file: p.file.is_some(), 23 | }), 24 | } 25 | } 26 | } 27 | 28 | #[derive(Debug, Deserialize, Default)] 29 | pub struct RawSqliteParams { 30 | pub file: Option, 31 | } 32 | 33 | impl RawSqliteParams { 34 | pub fn load_from_env() -> Result { 35 | let params = envy::prefixed("SQLITE_").from_env()?; 36 | Ok(params) 37 | } 38 | 39 | pub fn is_any(&self) -> bool { 40 | self.file.is_some() 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/display.rs: -------------------------------------------------------------------------------- 1 | use crate::match_maker::Matching; 2 | use crate::migration::Migration; 3 | use crate::plan_builder::Step; 4 | use ansi_term::Color; 5 | use std::io::{self, Write}; 6 | 7 | const LIGHT_RED: u8 = 9; 8 | 9 | pub fn print_status(matchings: &[Matching]) { 10 | let stdout = io::stdout(); 11 | let mut handle = stdout.lock(); 12 | 13 | use Matching::*; 14 | for matching in matchings.iter().rev() { 15 | let reversable_str = if matching.is_reversable() { 16 | "".to_owned() 17 | } else { 18 | Color::Red.paint(" [unreversable]").to_string() 19 | }; 20 | 21 | let (color, status) = match matching { 22 | // Add spaces in front to make them all the same length 23 | Applied(_) => (Color::Green, " Applied"), 24 | Divergent(_) => (Color::Red, "Divergent"), 25 | Pending(_) => (Color::Yellow, " Pending"), 26 | Variant(_, _) => (Color::Fixed(LIGHT_RED), " Variant"), 27 | }; 28 | 29 | writeln!( 30 | handle, 31 | "{status}{reversable} - {name}", 32 | name = matching.get_name(), 33 | status = color.paint(status), 34 | reversable = reversable_str, 35 | ) 36 | .unwrap(); 37 | } 38 | } 39 | 40 | pub fn print_plan(plan: &[(Step, &Migration)]) { 41 | for step in plan.iter() { 42 | print_step(step); 43 | } 44 | } 45 | 46 | pub fn print_step((step, migration): &(Step, &Migration)) { 47 | use Step::*; 48 | if migration.is_reversable() || step == &Step::Up { 49 | let step = match step { 50 | // Add spaces in front to make them all the same length 51 | Up => " Up", 52 | Down => "Down", 53 | }; 54 | 55 | println!( 56 | "{step} - {name}", 57 | name = migration.name, 58 | step = Color::Green.paint(step), 59 | ); 60 | } else { 61 | println!( 62 | "{unreversable} - {name}", 63 | name = migration.name, 64 | unreversable = Color::Red.paint("Unreversable migration"), 65 | ); 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /src/errors.rs: -------------------------------------------------------------------------------- 1 | use libsqlite3_sys::Error as SqliteLibError; 2 | use libsqlite3_sys::ErrorCode as SqliteLibErrorCode; 3 | use postgres::error::Error as PostgresError; 4 | use rusqlite::Error as SqliteError; 5 | use std::error::Error as StdError; 6 | use std::fmt; 7 | use std::io; 8 | use toml::de::Error as TomlError; 9 | 10 | pub type Result = std::result::Result; 11 | 12 | pub enum Error { 13 | ConfigNotFound, 14 | PgParamError { 15 | user: bool, 16 | password: bool, 17 | database: bool, 18 | host: bool, 19 | port: bool, 20 | }, 21 | SqliteParamError { 22 | file: bool, 23 | }, 24 | BadMigration, 25 | Unknown, 26 | AdaptorNotFound, 27 | MigrationDirNotFound, 28 | DirtyMigrations, 29 | DivergentMigration, 30 | UnrollbackableMigration, 31 | IoError(io::Error), 32 | TomlError(TomlError), 33 | PgError(PostgresError), 34 | SqliteError(SqliteError), 35 | Envy(envy::Error), 36 | #[cfg(feature = "with-native-tls")] 37 | NativeTlsError(native_tls::Error), 38 | #[cfg(feature = "with-rustls")] 39 | RustlsError(rustls::TLSError), 40 | #[cfg(feature = "with-rustls")] 41 | RustlsPemfileError, 42 | } 43 | 44 | impl fmt::Debug for Error { 45 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 46 | use Error::*; 47 | match self { 48 | ConfigNotFound => write!(f, "`movine.toml` config file not found and no environment variables were found."), 49 | BadMigration => write!(f, "Error parsing migrations."), 50 | Unknown => write!(f, "Unknown error occurred"), 51 | AdaptorNotFound => write!(f, "Could not find adaptor"), 52 | MigrationDirNotFound => write!(f, "Could not find migration directory"), 53 | DirtyMigrations => write!(f, "More recent migrations exist in the database than the pending migrations. This is an error when run with --strict"), 54 | DivergentMigration => write!(f, "Divergent migration found. Run with --ignore-divergent to ignore divergent migrations."), 55 | UnrollbackableMigration => write!(f, "Can't rollback one of the migrations in the list. Consider changing your parameters or adding a `down.sql` migration."), 56 | IoError(e) => write!(f, "IO Error: {}", e), 57 | TomlError(e) => write!(f, "Unable to read config file: {}", e), 58 | PgError(e) => write!(f, "Error in Postgres: {}", e), 59 | SqliteError(e) => { 60 | match e { 61 | rusqlite::Error::SqliteFailure(SqliteLibError { code: SqliteLibErrorCode::APIMisuse, .. }, _) => { 62 | write!(f, "Error in Sqlite: {}.", e)?; 63 | write!(f, "\nThis is likely due to an invalid SQL statement, such as an empty UP or DOWN migration.") 64 | } 65 | _ => write!(f, "Error in Sqlite: {}", e), 66 | } 67 | } 68 | Envy(e) => write!(f, "Error in loading environment variables: {}", e), 69 | #[cfg(feature = "with-native-tls")] 70 | NativeTlsError(e) => write!(f, "Error in TLS: {}", e), 71 | #[cfg(feature = "with-rustls")] 72 | RustlsError(e) => write!(f, "Error in TLS: {}", e), 73 | #[cfg(feature = "with-rustls")] 74 | RustlsPemfileError => write!(f, "Error in TLS: could not add PEM file to store"), 75 | SqliteParamError { .. } => write!(f, "Unable to load Sqlite params. Make sure you have `file` defined in your `movine.toml` or SQLITE_FILE defined as an environment variable"), 76 | PgParamError { 77 | user, password, database, host, port 78 | } => { 79 | write!(f, "Unable to load Postgres params. Please ensure you have the following defined:\nUser: {}\nPassword: {}\nDatabase: {}\nHost: {}\nPort: {}", 80 | user, password, database, host, port) 81 | } 82 | } 83 | } 84 | } 85 | 86 | impl fmt::Display for Error { 87 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 88 | write!(f, "{:?}", self) 89 | } 90 | } 91 | 92 | impl From for Error { 93 | fn from(error: io::Error) -> Self { 94 | Error::IoError(error) 95 | } 96 | } 97 | 98 | impl From for Error { 99 | fn from(error: TomlError) -> Self { 100 | Error::TomlError(error) 101 | } 102 | } 103 | 104 | impl From for Error { 105 | fn from(error: PostgresError) -> Self { 106 | Error::PgError(error) 107 | } 108 | } 109 | 110 | impl From for Error { 111 | fn from(error: SqliteError) -> Self { 112 | Error::SqliteError(error) 113 | } 114 | } 115 | 116 | impl From for Error { 117 | fn from(error: envy::Error) -> Self { 118 | Error::Envy(error) 119 | } 120 | } 121 | 122 | #[cfg(feature = "with-native-tls")] 123 | impl From for Error { 124 | fn from(error: native_tls::Error) -> Self { 125 | Error::NativeTlsError(error) 126 | } 127 | } 128 | 129 | #[cfg(feature = "with-rustls")] 130 | impl From for Error { 131 | fn from(error: rustls::TLSError) -> Self { 132 | Error::RustlsError(error) 133 | } 134 | } 135 | 136 | // Implements std::Error for ease of use outside of Movine 137 | impl StdError for Error {} 138 | -------------------------------------------------------------------------------- /src/file_handler.rs: -------------------------------------------------------------------------------- 1 | use crate::errors::{Error, Result}; 2 | use crate::migration::{Migration, MigrationBuilder}; 3 | use std::fs; 4 | use std::fs::File; 5 | use std::io::Read; 6 | use std::io::Write; 7 | use std::path::PathBuf; 8 | 9 | pub struct FileHandler { 10 | migration_dir: PathBuf, 11 | } 12 | 13 | impl FileHandler { 14 | pub fn new(migration_dir: &str) -> Self { 15 | Self { 16 | migration_dir: migration_dir.into(), 17 | } 18 | } 19 | 20 | pub fn create_migration_directory(&self) -> Result<()> { 21 | let exists = self.migration_dir.exists(); 22 | if !exists { 23 | fs::create_dir(&self.migration_dir)?; 24 | } 25 | Ok(()) 26 | } 27 | 28 | pub fn write_migration(&self, migration: &Migration) -> Result<()> { 29 | let name = migration.name.clone().into(); 30 | let folder: PathBuf = [&self.migration_dir, &name].iter().collect(); 31 | fs::create_dir(&folder)?; 32 | 33 | let up_file: PathBuf = [&folder, &"up.sql".into()].iter().collect(); 34 | let mut up = File::create(up_file)?; 35 | 36 | let down_file: PathBuf = [&folder, &"down.sql".into()].iter().collect(); 37 | let mut down = File::create(down_file)?; 38 | 39 | if let Some(up_sql) = &migration.up_sql { 40 | up.write_all(up_sql.as_bytes())?; 41 | } 42 | if let Some(down_sql) = &migration.down_sql { 43 | down.write_all(down_sql.as_bytes())?; 44 | } 45 | Ok(()) 46 | } 47 | 48 | pub fn load_local_migrations(&self) -> Result> { 49 | let directory = match fs::read_dir(&self.migration_dir) { 50 | Ok(dir) => dir, 51 | Err(e) if e.kind() == std::io::ErrorKind::NotFound => { 52 | return Err(Error::MigrationDirNotFound); 53 | } 54 | Err(e) => { 55 | return Err(e.into()); 56 | } 57 | }; 58 | let mut migrations = Vec::new(); 59 | 60 | for entry in directory { 61 | let entry = entry?; 62 | let compound_name: String = entry.file_name().into_string().unwrap(); 63 | 64 | let mut up_path = entry.path(); 65 | let mut down_path = entry.path(); 66 | up_path.push("up.sql"); 67 | down_path.push("down.sql"); 68 | 69 | let mut file = File::open(up_path)?; 70 | let mut up_sql = String::new(); 71 | file.read_to_string(&mut up_sql)?; 72 | 73 | let mut file = File::open(down_path)?; 74 | let mut down_sql = String::new(); 75 | file.read_to_string(&mut down_sql)?; 76 | 77 | let migration = MigrationBuilder::new() 78 | .compound_name(&compound_name) 79 | .up_sql(&up_sql) 80 | .down_sql(&down_sql) 81 | .build()?; 82 | migrations.push(migration); 83 | } 84 | 85 | Ok(migrations) 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | //! Movine provides a library implementation for integration with codebases. This lets you easily 2 | //! run migrations at the startup of the application. 3 | //! 4 | //! # Example 5 | //! ``` 6 | //! use movine::{Movine, Config}; 7 | //! use movine::errors::Error; 8 | //! 9 | //! fn test() -> Result<(), Error> { 10 | //! std::env::set_var("SQLITE_FILE", ":memory:"); 11 | //! let config = Config::load(&"movine.toml")?; 12 | //! let mut conn = config.into_sqlite_conn()?; 13 | //! let mut movine = Movine::new(&mut conn); 14 | //! movine.up()?; 15 | //! Ok(()) 16 | //! } 17 | //! 18 | //! ``` 19 | //! Or if you want to provide your own connection 20 | //! 21 | //! ``` 22 | //! use movine::{Movine, Config}; 23 | //! use movine::errors::Error; 24 | //! 25 | //! fn test() -> Result<(), Error> { 26 | //! let mut conn = rusqlite::Connection::open(":memory:")?; 27 | //! let mut movine = Movine::new(&mut conn); 28 | //! movine.up()?; 29 | //! Ok(()) 30 | //! } 31 | //! 32 | //! ``` 33 | use chrono::prelude::*; 34 | 35 | #[macro_use] 36 | extern crate log; 37 | 38 | pub mod adaptor; 39 | pub mod config; 40 | mod display; 41 | pub mod errors; 42 | mod file_handler; 43 | mod match_maker; 44 | mod migration; 45 | mod plan_builder; 46 | 47 | pub use adaptor::DbAdaptor; 48 | pub use config::Config; 49 | use errors::{Error, Result}; 50 | use file_handler::FileHandler; 51 | use migration::MigrationBuilder; 52 | use plan_builder::PlanBuilder; 53 | 54 | pub struct Movine { 55 | adaptor: T, 56 | migration_dir: String, 57 | number: Option, 58 | show_plan: bool, 59 | ignore_divergent: bool, 60 | ignore_unreversable: bool, 61 | strict: bool, 62 | } 63 | 64 | impl Movine { 65 | pub fn new(adaptor: T) -> Self { 66 | Self { 67 | adaptor, 68 | migration_dir: "./migrations".into(), 69 | number: None, 70 | show_plan: false, 71 | ignore_divergent: false, 72 | ignore_unreversable: false, 73 | strict: false, 74 | } 75 | } 76 | 77 | pub fn set_migration_dir(&mut self, migration_dir: &str) -> &mut Self { 78 | self.migration_dir = migration_dir.into(); 79 | self 80 | } 81 | 82 | pub fn set_number(&mut self, number: Option) -> &mut Self { 83 | self.number = number; 84 | self 85 | } 86 | 87 | pub fn set_show_plan(&mut self, show_plan: bool) -> &mut Self { 88 | self.show_plan = show_plan; 89 | self 90 | } 91 | 92 | pub fn set_ignore_divergent(&mut self, ignore_divergent: bool) -> &mut Self { 93 | self.ignore_divergent = ignore_divergent; 94 | self 95 | } 96 | 97 | pub fn set_ignore_unreversable(&mut self, ignore_unreversable: bool) -> &mut Self { 98 | self.ignore_unreversable = ignore_unreversable; 99 | self 100 | } 101 | 102 | pub fn set_strict(&mut self, strict: bool) -> &mut Self { 103 | self.strict = strict; 104 | self 105 | } 106 | 107 | pub fn initialize(&mut self) -> Result<()> { 108 | let file_handler = FileHandler::new(&self.migration_dir); 109 | file_handler.create_migration_directory()?; 110 | let up_sql = self.adaptor.init_up_sql(); 111 | let down_sql = self.adaptor.init_down_sql(); 112 | 113 | let init_migration = MigrationBuilder::new() 114 | .name("movine_init") 115 | .date(Utc.timestamp_opt(0, 0).unwrap()) 116 | .up_sql(up_sql) 117 | .down_sql(down_sql) 118 | .build()?; 119 | 120 | match file_handler.write_migration(&init_migration) { 121 | Ok(_) => {} 122 | Err(Error::IoError(e)) if e.kind() == std::io::ErrorKind::AlreadyExists => {} 123 | x => x?, 124 | } 125 | 126 | // Can't just call to `up` function since we are unable to get 127 | // database migrations until we run this migration. 128 | let local_migrations = file_handler.load_local_migrations()?; 129 | let db_migrations = Vec::new(); 130 | let plan = PlanBuilder::new() 131 | .local_migrations(&local_migrations) 132 | .db_migrations(&db_migrations) 133 | .count(Some(1)) // Just want to run a single migration (the init one) 134 | .up()?; 135 | self.adaptor.run_migration_plan(&plan) 136 | } 137 | 138 | pub fn generate(&mut self, name: &str) -> Result<()> { 139 | let file_handler = FileHandler::new(&self.migration_dir); 140 | let new_migration = MigrationBuilder::new() 141 | .name(name) 142 | .date(Utc::now()) 143 | .build()?; 144 | file_handler.write_migration(&new_migration) 145 | } 146 | 147 | pub fn status(&mut self) -> Result<()> { 148 | let file_handler = FileHandler::new(&self.migration_dir); 149 | let local_migrations = file_handler.load_local_migrations()?; 150 | let db_migrations = self.adaptor.load_migrations()?; 151 | 152 | let status = PlanBuilder::new() 153 | .local_migrations(&local_migrations) 154 | .db_migrations(&db_migrations) 155 | .status()?; 156 | 157 | display::print_status(&status); 158 | Ok(()) 159 | } 160 | 161 | pub fn up(&mut self) -> Result<()> { 162 | let file_handler = FileHandler::new(&self.migration_dir); 163 | let local_migrations = file_handler.load_local_migrations()?; 164 | let db_migrations = self.adaptor.load_migrations()?; 165 | 166 | let plan = PlanBuilder::new() 167 | .local_migrations(&local_migrations) 168 | .db_migrations(&db_migrations) 169 | .count(self.number) 170 | .set_strict(self.strict) 171 | .up()?; 172 | 173 | if self.show_plan { 174 | display::print_plan(&plan); 175 | Ok(()) 176 | } else { 177 | self.adaptor.run_migration_plan(&plan) 178 | } 179 | } 180 | 181 | pub fn down(&mut self) -> Result<()> { 182 | let file_handler = FileHandler::new(&self.migration_dir); 183 | let local_migrations = file_handler.load_local_migrations()?; 184 | let db_migrations = self.adaptor.load_migrations()?; 185 | 186 | let plan = PlanBuilder::new() 187 | .local_migrations(&local_migrations) 188 | .db_migrations(&db_migrations) 189 | .count(self.number) 190 | .set_ignore_divergent(self.ignore_divergent) 191 | .set_ignore_unreversable(self.ignore_unreversable) 192 | .down()?; 193 | 194 | if self.show_plan { 195 | display::print_plan(&plan); 196 | Ok(()) 197 | } else { 198 | self.adaptor.run_migration_plan(&plan) 199 | } 200 | } 201 | 202 | pub fn fix(&mut self) -> Result<()> { 203 | let file_handler = FileHandler::new(&self.migration_dir); 204 | let local_migrations = file_handler.load_local_migrations()?; 205 | let db_migrations = self.adaptor.load_migrations()?; 206 | 207 | let plan = PlanBuilder::new() 208 | .local_migrations(&local_migrations) 209 | .db_migrations(&db_migrations) 210 | .fix()?; 211 | 212 | if self.show_plan { 213 | display::print_plan(&plan); 214 | Ok(()) 215 | } else { 216 | self.adaptor.run_migration_plan(&plan) 217 | } 218 | } 219 | 220 | pub fn redo(&mut self) -> Result<()> { 221 | let file_handler = FileHandler::new(&self.migration_dir); 222 | let local_migrations = file_handler.load_local_migrations()?; 223 | let db_migrations = self.adaptor.load_migrations()?; 224 | 225 | let plan = PlanBuilder::new() 226 | .local_migrations(&local_migrations) 227 | .db_migrations(&db_migrations) 228 | .count(self.number) 229 | .set_ignore_divergent(self.ignore_divergent) 230 | .set_ignore_unreversable(self.ignore_unreversable) 231 | .redo()?; 232 | 233 | if self.show_plan { 234 | display::print_plan(&plan); 235 | Ok(()) 236 | } else { 237 | self.adaptor.run_migration_plan(&plan) 238 | } 239 | } 240 | } 241 | -------------------------------------------------------------------------------- /src/main.rs: -------------------------------------------------------------------------------- 1 | use movine::config::Config; 2 | use movine::errors::Result; 3 | use movine::DbAdaptor; 4 | use movine::Movine; 5 | use structopt::StructOpt; 6 | 7 | mod cli; 8 | use cli::Opt; 9 | 10 | fn main() -> Result<()> { 11 | match Opt::from_args() { 12 | Opt::Init { debug } => { 13 | let mut movine = setup(debug)?; 14 | movine.initialize() 15 | } 16 | Opt::Generate { name, debug } => { 17 | let mut movine = setup(debug)?; 18 | movine.generate(&name) 19 | } 20 | Opt::Status { debug } => { 21 | let mut movine = setup(debug)?; 22 | movine.status() 23 | } 24 | Opt::Up { 25 | number, 26 | show_plan, 27 | debug, 28 | strict, 29 | } => { 30 | let mut movine = setup(debug)?; 31 | movine 32 | .set_number(number) 33 | .set_strict(strict) 34 | .set_show_plan(show_plan) 35 | .up() 36 | } 37 | Opt::Down { 38 | number, 39 | show_plan, 40 | ignore_divergent, 41 | ignore_unreversable, 42 | debug, 43 | } => { 44 | let mut movine = setup(debug)?; 45 | movine 46 | .set_number(number) 47 | .set_show_plan(show_plan) 48 | .set_ignore_divergent(ignore_divergent) 49 | .set_ignore_unreversable(ignore_unreversable) 50 | .down() 51 | } 52 | Opt::Redo { 53 | number, 54 | show_plan, 55 | ignore_divergent, 56 | ignore_unreversable, 57 | debug, 58 | } => { 59 | let mut movine = setup(debug)?; 60 | movine 61 | .set_number(number) 62 | .set_ignore_divergent(ignore_divergent) 63 | .set_ignore_unreversable(ignore_unreversable) 64 | .set_show_plan(show_plan) 65 | .redo() 66 | } 67 | Opt::Fix { show_plan, debug } => { 68 | let mut movine = setup(debug)?; 69 | movine.set_show_plan(show_plan).fix() 70 | } 71 | _ => unimplemented!(), 72 | } 73 | } 74 | 75 | fn setup(debug: bool) -> Result>> { 76 | dotenv::dotenv().ok(); 77 | env_logger::builder() 78 | .filter_level(if debug { 79 | log::LevelFilter::Debug 80 | } else { 81 | log::LevelFilter::Info 82 | }) 83 | .init(); 84 | 85 | let config = Config::load("movine.toml")?; 86 | let adaptor = config.into_db_adaptor()?; 87 | let movine = Movine::new(adaptor); 88 | Ok(movine) 89 | } 90 | -------------------------------------------------------------------------------- /src/match_maker.rs: -------------------------------------------------------------------------------- 1 | use crate::migration::Migration; 2 | use std::cmp::Ordering; 3 | use std::collections::HashMap; 4 | 5 | pub fn find_matches<'a>( 6 | local_migrations: &'a [Migration], 7 | db_migrations: &'a [Migration], 8 | ) -> Vec> { 9 | let mut matches = Vec::new(); 10 | let mut local_cmp: HashMap<&str, &Migration> = local_migrations 11 | .iter() 12 | .map(|x| (x.name.as_ref(), x)) 13 | .collect(); 14 | 15 | for m in db_migrations { 16 | let m_name: &str = m.name.as_ref(); 17 | if let Some((_, loc_m)) = local_cmp.remove_entry(m_name) { 18 | if loc_m.hash == m.hash { 19 | matches.push(Matching::Applied(loc_m)); 20 | } else { 21 | matches.push(Matching::Variant(loc_m, m)); 22 | } 23 | } else { 24 | matches.push(Matching::Divergent(m)); 25 | } 26 | } 27 | 28 | for loc_m in local_cmp.values() { 29 | matches.push(Matching::Pending(loc_m)); 30 | } 31 | 32 | matches 33 | } 34 | 35 | #[derive(Debug, Copy, Clone, PartialEq, Eq)] 36 | pub enum Matching<'a> { 37 | Applied(&'a Migration), 38 | Divergent(&'a Migration), 39 | Pending(&'a Migration), 40 | Variant(&'a Migration, &'a Migration), 41 | } 42 | 43 | impl<'a> Matching<'a> { 44 | pub fn get_name(&self) -> &'a str { 45 | use Matching::*; 46 | match self { 47 | Applied(x) | Divergent(x) | Pending(x) => &x.name, 48 | Variant(x, _) => &x.name, 49 | } 50 | } 51 | 52 | pub fn get_best_down_migration(&self) -> &'a Migration { 53 | use Matching::*; 54 | match self { 55 | Applied(x) | Pending(x) | Divergent(x) => x, 56 | Variant(x, y) => { 57 | if x.down_sql.is_some() { 58 | x 59 | } else { 60 | y 61 | } 62 | } 63 | } 64 | } 65 | 66 | pub fn get_local_migration(&self) -> Option<&'a Migration> { 67 | use Matching::*; 68 | match self { 69 | Applied(x) | Pending(x) | Variant(x, _) => Some(x), 70 | Divergent(_) => None, 71 | } 72 | } 73 | 74 | pub fn is_reversable(&self) -> bool { 75 | use Matching::*; 76 | match self { 77 | Applied(x) | Pending(x) | Divergent(x) => x.is_reversable(), 78 | Variant(x, y) => x.is_reversable() || y.is_reversable(), 79 | } 80 | } 81 | } 82 | 83 | impl Ord for Matching<'_> { 84 | fn cmp(&self, other: &Self) -> Ordering { 85 | let self_name = self.get_name(); 86 | let other_name = other.get_name(); 87 | self_name.cmp(other_name) 88 | } 89 | } 90 | 91 | impl PartialOrd for Matching<'_> { 92 | fn partial_cmp(&self, other: &Self) -> Option { 93 | Some(self.cmp(other)) 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /src/migration.rs: -------------------------------------------------------------------------------- 1 | use crate::errors::{Error, Result}; 2 | use chrono::prelude::*; 3 | use std::collections::hash_map::DefaultHasher; 4 | use std::hash::{Hash, Hasher}; 5 | 6 | #[derive(Debug, Eq, PartialEq)] 7 | pub struct Migration { 8 | pub name: String, 9 | pub up_sql: Option, 10 | pub down_sql: Option, 11 | pub hash: Option, 12 | } 13 | 14 | impl Migration { 15 | pub fn is_reversable(&self) -> bool { 16 | debug!("down_sql: {:?}", &self.down_sql); 17 | if let Some(sql) = &self.down_sql { 18 | !sql.is_empty() 19 | } else { 20 | false 21 | } 22 | } 23 | } 24 | 25 | pub struct MigrationBuilder { 26 | compound_name: Option, 27 | name: Option, 28 | date: Option>, 29 | up_sql: Option, 30 | down_sql: Option, 31 | hash: Option, 32 | } 33 | 34 | impl MigrationBuilder { 35 | pub fn new() -> Self { 36 | Self { 37 | compound_name: None, 38 | name: None, 39 | date: None, 40 | up_sql: None, 41 | down_sql: None, 42 | hash: None, 43 | } 44 | } 45 | 46 | pub fn compound_name<'a>(&'a mut self, compound_name: &str) -> &'a mut Self { 47 | self.compound_name = Some(compound_name.to_owned()); 48 | self 49 | } 50 | 51 | pub fn name<'a>(&'a mut self, name: &str) -> &'a mut Self { 52 | self.name = Some(name.to_owned()); 53 | self 54 | } 55 | 56 | pub fn date(&mut self, date: DateTime) -> &mut Self { 57 | self.date = Some(date.to_owned()); 58 | self 59 | } 60 | 61 | pub fn up_sql<'a>(&'a mut self, up_sql: &str) -> &'a mut Self { 62 | self.up_sql = Some(up_sql.to_owned()); 63 | self 64 | } 65 | 66 | pub fn down_sql<'a>(&'a mut self, down_sql: &str) -> &'a mut Self { 67 | self.down_sql = Some(down_sql.to_owned()); 68 | self 69 | } 70 | 71 | pub fn hash<'a>(&'a mut self, hash: &str) -> &'a mut Self { 72 | self.hash = Some(hash.to_owned()); 73 | self 74 | } 75 | 76 | pub fn build(&self) -> Result { 77 | // TODO: Clean up ownership a bit; we should be able to just take 78 | let name = if let Some(compound_name) = &self.compound_name { 79 | compound_name.to_owned() 80 | } else { 81 | let name = self.name.to_owned().ok_or(Error::BadMigration)?; 82 | let date = self.date.to_owned().ok_or(Error::BadMigration)?; 83 | let date = date.format("%Y-%m-%d-%H%M%S").to_string(); 84 | format!("{}_{}", date, name) 85 | }; 86 | 87 | let hash = match (&self.up_sql, &self.down_sql, &self.hash) { 88 | (_, _, Some(x)) => Some(x.to_owned()), 89 | (x, y, None) => { 90 | let mut hasher = DefaultHasher::new(); 91 | x.hash(&mut hasher); 92 | y.hash(&mut hasher); 93 | Some(format!("{:x}", hasher.finish())) 94 | } 95 | }; 96 | 97 | Ok(Migration { 98 | name, 99 | up_sql: self.up_sql.to_owned(), 100 | down_sql: self.down_sql.to_owned(), 101 | hash, 102 | }) 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /src/plan_builder.rs: -------------------------------------------------------------------------------- 1 | use crate::errors::{Error, Result}; 2 | use crate::match_maker::{self, Matching}; 3 | use crate::migration::Migration; 4 | 5 | pub type Plan<'a> = Vec<(Step, &'a Migration)>; 6 | 7 | #[derive(Debug, PartialEq, Eq, Copy, Clone)] 8 | pub enum Step { 9 | Up, 10 | Down, 11 | } 12 | 13 | pub struct PlanBuilder<'a> { 14 | local_migrations: Option<&'a [Migration]>, 15 | db_migrations: Option<&'a [Migration]>, 16 | count: Option, 17 | strict: bool, 18 | ignore_divergent: bool, 19 | ignore_unreversable: bool, 20 | } 21 | 22 | impl<'a> PlanBuilder<'a> { 23 | pub fn new() -> Self { 24 | Self { 25 | local_migrations: None, 26 | db_migrations: None, 27 | count: None, 28 | strict: false, 29 | ignore_divergent: false, 30 | ignore_unreversable: false, 31 | } 32 | } 33 | 34 | pub fn local_migrations(mut self, m: &'a [Migration]) -> Self { 35 | self.local_migrations = Some(m); 36 | self 37 | } 38 | 39 | pub fn db_migrations(mut self, m: &'a [Migration]) -> Self { 40 | self.db_migrations = Some(m); 41 | self 42 | } 43 | 44 | pub fn count(mut self, count: Option) -> Self { 45 | self.count = count; 46 | self 47 | } 48 | 49 | pub fn set_strict(mut self, strict: bool) -> Self { 50 | self.strict = strict; 51 | self 52 | } 53 | 54 | pub fn set_ignore_divergent(mut self, ignore: bool) -> Self { 55 | self.ignore_divergent = ignore; 56 | self 57 | } 58 | 59 | pub fn set_ignore_unreversable(mut self, ignore: bool) -> Self { 60 | self.ignore_unreversable = ignore; 61 | self 62 | } 63 | 64 | pub fn up(self) -> Result> { 65 | let mut dirty = false; 66 | let mut pending_found = false; 67 | let mut plan = Vec::new(); 68 | 69 | let matches = self.get_matches()?; 70 | for m in matches { 71 | match m { 72 | Matching::Pending(x) => { 73 | pending_found = true; 74 | if let Some(count) = self.count { 75 | if count == plan.len() { 76 | continue; 77 | } 78 | } 79 | 80 | let step = (Step::Up, x); 81 | plan.push(step); 82 | } 83 | _ => { 84 | if pending_found { 85 | dirty = true; 86 | } 87 | continue; 88 | } 89 | } 90 | } 91 | 92 | if self.strict && dirty { 93 | return Err(Error::DirtyMigrations); 94 | } 95 | 96 | Ok(plan) 97 | } 98 | 99 | pub fn down(self) -> Result> { 100 | let mut plan: Plan<'a> = Vec::new(); 101 | let matches = self.get_matches()?; 102 | 103 | // Note: get_matches() returns the migrations in date-order. 104 | // We want the most recently run, so we have to reverse the order. 105 | for m in matches.iter().rev() { 106 | match m { 107 | Matching::Divergent(x) => { 108 | if self.ignore_divergent { 109 | continue; 110 | } 111 | 112 | plan.push((Step::Down, x)); 113 | } 114 | Matching::Applied(_) | Matching::Variant(_, _) => { 115 | if m.is_reversable() { 116 | plan.push((Step::Down, m.get_best_down_migration())); 117 | } else if !self.ignore_unreversable { 118 | return Err(Error::UnrollbackableMigration); 119 | } 120 | } 121 | _ => {} 122 | } 123 | 124 | if let Some(count) = self.count { 125 | if count == plan.len() { 126 | break; 127 | } 128 | } else if plan.len() == 1 { 129 | break; 130 | } 131 | } 132 | 133 | Ok(plan) 134 | } 135 | 136 | pub fn fix(self) -> Result> { 137 | let matches = self.get_matches()?; 138 | 139 | let mut bad_migration_found = false; 140 | let mut rollback_plan_rev = Vec::new(); 141 | let mut rollup_plan = Vec::new(); 142 | for m in matches { 143 | match m { 144 | Matching::Divergent(x) => { 145 | bad_migration_found = true; 146 | if m.is_reversable() { 147 | rollback_plan_rev.push((Step::Down, x)); 148 | } else { 149 | return Err(Error::UnrollbackableMigration); 150 | } 151 | } 152 | Matching::Variant(_, _) => { 153 | bad_migration_found = true; 154 | let down = m.get_best_down_migration(); 155 | let up = m.get_local_migration().unwrap(); 156 | if m.is_reversable() { 157 | rollback_plan_rev.push((Step::Down, down)); 158 | rollup_plan.push((Step::Up, up)); 159 | } else { 160 | return Err(Error::UnrollbackableMigration); 161 | } 162 | } 163 | Matching::Applied(x) => { 164 | if bad_migration_found { 165 | if m.is_reversable() { 166 | rollback_plan_rev.push((Step::Down, x)); 167 | rollup_plan.push((Step::Up, x)); 168 | } else { 169 | return Err(Error::UnrollbackableMigration); 170 | } 171 | } 172 | } 173 | Matching::Pending(x) => { 174 | bad_migration_found = true; 175 | rollup_plan.push((Step::Up, x)); 176 | } 177 | } 178 | } 179 | 180 | let mut plan: Plan<'a> = rollback_plan_rev.drain(..).rev().collect(); 181 | plan.append(&mut rollup_plan); 182 | Ok(plan) 183 | } 184 | 185 | pub fn redo(self) -> Result> { 186 | let matches = self.get_matches()?; 187 | let mut rollback_plan: Plan<'a> = Vec::new(); 188 | let mut rollup_plan_rev: Plan<'a> = Vec::new(); 189 | 190 | // Note: get_matches() returns the migrations in date-order. 191 | // We want the most recently run, so we have to reverse the order. 192 | for m in matches.iter().rev() { 193 | match m { 194 | Matching::Divergent(_) => { 195 | if self.ignore_divergent { 196 | continue; 197 | } 198 | 199 | return Err(Error::DivergentMigration); 200 | } 201 | Matching::Applied(_) | Matching::Variant(_, _) => { 202 | if m.is_reversable() { 203 | rollback_plan.push((Step::Down, m.get_best_down_migration())); 204 | rollup_plan_rev.push((Step::Up, m.get_local_migration().unwrap())); 205 | } else if !self.ignore_unreversable { 206 | return Err(Error::UnrollbackableMigration); 207 | } 208 | } 209 | _ => {} 210 | } 211 | 212 | if let Some(count) = self.count { 213 | if count == rollback_plan.len() { 214 | break; 215 | } 216 | } else if rollback_plan.len() == 1 { 217 | break; 218 | } 219 | } 220 | 221 | let mut rollup_plan: Plan<'a> = rollup_plan_rev.drain(..).rev().collect(); 222 | let mut plan = rollback_plan; 223 | plan.append(&mut rollup_plan); 224 | Ok(plan) 225 | } 226 | 227 | pub fn status(self) -> Result>> { 228 | self.get_matches() 229 | } 230 | 231 | fn get_matches(&self) -> Result>> { 232 | if let (Some(local_migrations), Some(db_migrations)) = 233 | (self.local_migrations, self.db_migrations) 234 | { 235 | let mut matches = match_maker::find_matches(local_migrations, db_migrations); 236 | matches.sort(); 237 | Ok(matches) 238 | } else { 239 | Err(Error::Unknown) 240 | } 241 | } 242 | } 243 | 244 | #[cfg(test)] 245 | mod tests { 246 | use super::*; 247 | use crate::migration::Migration; 248 | 249 | // QoL impl 250 | impl Migration { 251 | fn new(name: &str) -> Self { 252 | Self { 253 | name: name.to_string(), 254 | up_sql: None, 255 | down_sql: Some("test".to_owned()), 256 | hash: None, 257 | } 258 | } 259 | 260 | fn new_with_hash(name: &str, hash: &str) -> Self { 261 | Self { 262 | name: name.to_string(), 263 | up_sql: None, 264 | down_sql: None, 265 | hash: Some(hash.to_string()), 266 | } 267 | } 268 | } 269 | 270 | #[test] 271 | /// Up should run pending migrations in-order. 272 | fn test_up_1() { 273 | let local = [Migration::new("test_1"), Migration::new("test_2")]; 274 | let db = []; 275 | let plan = PlanBuilder::new() 276 | .local_migrations(&local) 277 | .db_migrations(&db) 278 | .up() 279 | .unwrap(); 280 | assert_eq!(plan, [(Step::Up, &local[0]), (Step::Up, &local[1])]) 281 | } 282 | 283 | #[test] 284 | /// Up should run pending migrations even if divergent migrations exist. 285 | fn test_up_2() { 286 | let local = [Migration::new("test"), Migration::new("test_2")]; 287 | let db = [Migration::new("test"), Migration::new("test_3")]; 288 | let plan = PlanBuilder::new() 289 | .local_migrations(&local) 290 | .db_migrations(&db) 291 | .up() 292 | .unwrap(); 293 | assert_eq!(plan, [(Step::Up, &local[1])]) 294 | } 295 | 296 | #[test] 297 | /// Up should error with --strict if migrations are out-of-order. 298 | fn test_up_3() { 299 | let local = [Migration::new("test"), Migration::new("test_2")]; 300 | let db = [Migration::new("test"), Migration::new("test_3")]; 301 | let plan = PlanBuilder::new() 302 | .local_migrations(&local) 303 | .db_migrations(&db) 304 | .set_strict(true) 305 | .up(); 306 | assert!(plan.is_err()); 307 | let is_correct_error = matches!(plan.err().unwrap(), Error::DirtyMigrations); 308 | assert!(is_correct_error); 309 | } 310 | 311 | #[test] 312 | /// Down should rollback the most recent migration (divergent included by default) 313 | fn test_down_1() { 314 | let local = [Migration::new("test"), Migration::new("test_2")]; 315 | let db = [Migration::new("test"), Migration::new("test_3")]; 316 | let plan = PlanBuilder::new() 317 | .local_migrations(&local) 318 | .db_migrations(&db) 319 | .down() 320 | .unwrap(); 321 | assert_eq!(plan, [(Step::Down, &db[1])]) 322 | } 323 | 324 | #[test] 325 | /// Down should rollback the most recent migration (ignoring divergent) 326 | fn test_down_2() { 327 | let local = [Migration::new("test"), Migration::new("test_2")]; 328 | let db = [Migration::new("test"), Migration::new("test_3")]; 329 | let plan = PlanBuilder::new() 330 | .local_migrations(&local) 331 | .db_migrations(&db) 332 | .set_ignore_divergent(true) 333 | .down() 334 | .unwrap(); 335 | assert_eq!(plan, [(Step::Down, &local[0])]) 336 | } 337 | 338 | #[test] 339 | /// Fix should rollback all variant and divergent migrations, and then run pending migrations. 340 | fn test_fix_1() { 341 | let local = [ 342 | Migration::new("test_0"), 343 | Migration::new("test_1"), 344 | Migration::new("test_2"), 345 | ]; 346 | let db = [ 347 | Migration::new("test_0"), 348 | Migration::new_with_hash("test_1", "hash"), 349 | Migration::new_with_hash("test_2", "hash"), 350 | Migration::new("test_3"), 351 | ]; 352 | let plan = PlanBuilder::new() 353 | .local_migrations(&local) 354 | .db_migrations(&db) 355 | .fix() 356 | .unwrap(); 357 | assert_eq!( 358 | plan, 359 | [ 360 | (Step::Down, &db[3]), 361 | (Step::Down, &local[2]), 362 | (Step::Down, &local[1]), 363 | (Step::Up, &local[1]), 364 | (Step::Up, &local[2]), 365 | ] 366 | ) 367 | } 368 | 369 | #[test] 370 | /// Fix should rollback applied migrations if they are ahead of variant migrations. 371 | fn test_fix_2() { 372 | let local = [ 373 | Migration::new("test"), 374 | Migration::new("test_1"), 375 | Migration::new("test_2"), 376 | ]; 377 | let db = [ 378 | Migration::new("test"), 379 | Migration::new_with_hash("test_1", "hash"), 380 | Migration::new("test_2"), 381 | ]; 382 | let plan = PlanBuilder::new() 383 | .local_migrations(&local) 384 | .db_migrations(&db) 385 | .fix() 386 | .unwrap(); 387 | assert_eq!( 388 | plan, 389 | [ 390 | (Step::Down, &local[2]), 391 | (Step::Down, &local[1]), 392 | (Step::Up, &local[1]), 393 | (Step::Up, &local[2]), 394 | ] 395 | ) 396 | } 397 | 398 | #[test] 399 | /// Fix should rollback everything to a fully applied state and then roll back up, regardless 400 | /// of applied/variant/diverget migration orders. 401 | fn test_fix_3() { 402 | let local = [ 403 | Migration::new("test_0"), 404 | Migration::new("test_1"), 405 | Migration::new("test_2"), 406 | Migration::new("test_3"), 407 | Migration::new("test_4"), 408 | ]; 409 | let db = [ 410 | Migration::new("test_0"), 411 | Migration::new_with_hash("test_1", "hash"), 412 | Migration::new("test_2"), 413 | Migration::new("test_3b"), 414 | Migration::new("test_4"), 415 | ]; 416 | let actual = PlanBuilder::new() 417 | .local_migrations(&local) 418 | .db_migrations(&db) 419 | .fix() 420 | .unwrap(); 421 | let expected = [ 422 | (Step::Down, &local[4]), 423 | (Step::Down, &db[3]), 424 | (Step::Down, &local[2]), 425 | (Step::Down, &local[1]), 426 | (Step::Up, &local[1]), 427 | (Step::Up, &local[2]), 428 | (Step::Up, &local[3]), 429 | (Step::Up, &local[4]), 430 | ]; 431 | assert_eq!(actual, expected) 432 | } 433 | 434 | #[test] 435 | /// Fix should run pending migrations without problems. 436 | fn test_fix_4() { 437 | let local = [Migration::new("test_0"), Migration::new("test_1")]; 438 | let db = [Migration::new("test_0")]; 439 | let actual = PlanBuilder::new() 440 | .local_migrations(&local) 441 | .db_migrations(&db) 442 | .fix() 443 | .unwrap(); 444 | let expected = [(Step::Up, &local[1])]; 445 | assert_eq!(actual, expected) 446 | } 447 | 448 | #[test] 449 | /// Redo should fail if there is a divergent migration (and we are not ignoring them) 450 | fn test_redo_1() { 451 | let local = [Migration::new("test"), Migration::new("test_2")]; 452 | let db = [ 453 | Migration::new("test"), 454 | Migration::new_with_hash("test_2", "hash_1"), 455 | Migration::new("test_3"), 456 | ]; 457 | let plan = PlanBuilder::new() 458 | .local_migrations(&local) 459 | .db_migrations(&db) 460 | .count(Some(2)) 461 | .redo(); 462 | assert!(plan.is_err()); 463 | let is_correct_err = matches!(plan.err().unwrap(), Error::DivergentMigration); 464 | assert!(is_correct_err); 465 | } 466 | 467 | #[test] 468 | /// Redo should properly ignore divergent migrations 469 | fn test_redo_2() { 470 | let local = [ 471 | Migration::new("test_0"), 472 | Migration::new("test_1"), 473 | Migration::new("test_2"), 474 | ]; 475 | let db = [ 476 | Migration::new("test_0"), 477 | Migration::new("test_1"), 478 | Migration::new("test_2"), 479 | Migration::new("test_3"), 480 | ]; 481 | let plan = PlanBuilder::new() 482 | .local_migrations(&local) 483 | .db_migrations(&db) 484 | .count(Some(2)) 485 | .set_ignore_divergent(true) 486 | .redo() 487 | .unwrap(); 488 | assert_eq!( 489 | plan, 490 | [ 491 | (Step::Down, &local[2]), 492 | (Step::Down, &local[1]), 493 | (Step::Up, &local[1]), 494 | (Step::Up, &local[2]), 495 | ] 496 | ) 497 | } 498 | 499 | #[test] 500 | /// Redo should not care about variant migrations further than what we are redo'ing 501 | fn test_redo_3() { 502 | let local = [ 503 | Migration::new("test_0"), 504 | Migration::new("test_1"), 505 | Migration::new("test_2"), 506 | ]; 507 | let db = [ 508 | Migration::new("test_0"), 509 | Migration::new_with_hash("test_1", "hash_1"), 510 | Migration::new("test_2"), 511 | ]; 512 | let plan = PlanBuilder::new() 513 | .local_migrations(&local) 514 | .db_migrations(&db) 515 | .count(Some(1)) 516 | .redo() 517 | .unwrap(); 518 | assert_eq!(plan, [(Step::Down, &local[2]), (Step::Up, &local[2]),]) 519 | } 520 | 521 | #[test] 522 | /// Redo should properly rollback variant migrations 523 | fn test_redo_4() { 524 | let local = [ 525 | Migration::new("test_0"), 526 | Migration::new("test_1"), 527 | Migration::new("test_2"), 528 | ]; 529 | let db = [ 530 | Migration::new("test_0"), 531 | Migration::new_with_hash("test_1", "hash_1"), 532 | Migration::new("test_2"), 533 | ]; 534 | let plan = PlanBuilder::new() 535 | .local_migrations(&local) 536 | .db_migrations(&db) 537 | .count(Some(2)) 538 | .redo() 539 | .unwrap(); 540 | assert_eq!( 541 | plan, 542 | [ 543 | (Step::Down, &local[2]), 544 | (Step::Down, &local[1]), 545 | (Step::Up, &local[1]), 546 | (Step::Up, &local[2]), 547 | ] 548 | ) 549 | } 550 | } 551 | --------------------------------------------------------------------------------