From 545c16c4542baaed19ade620f9414f74ec76253d Mon Sep 17 00:00:00 2001 From: Gabriel Maeztu Date: Sat, 24 Aug 2024 19:11:31 +0200 Subject: [PATCH] Add more subcommands and format code Signed-off-by: Gabriel de Maeztu --- Cargo.lock | 1483 +++++++++++++++++++++++++++++++++++--- Cargo.toml | 1 + README.md | 14 +- rust-toolchain.toml | 6 + src/deploy_log.rs | 206 ++++++ src/lib.rs | 292 ++++++-- src/main.rs | 345 ++++++++- src/reference.rs | 28 +- src/relational_object.rs | 40 + src/utils/mod.rs | 39 +- src/utils/topsort.rs | 34 +- 11 files changed, 2238 insertions(+), 250 deletions(-) create mode 100644 rust-toolchain.toml create mode 100644 src/relational_object.rs diff --git a/Cargo.lock b/Cargo.lock index 350698d..aad001c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -17,6 +17,18 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" +[[package]] +name = "ahash" +version = "0.8.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e89da841a80418a9b391ebaea17f5c112ffaaa96f621d2c285b5174da76b9011" +dependencies = [ + "cfg-if", + "once_cell", + "version_check", + "zerocopy", +] + [[package]] name = "aho-corasick" version = "1.1.3" @@ -26,6 +38,12 @@ dependencies = [ "memchr", ] +[[package]] +name = "allocator-api2" +version = "0.2.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c6cb57a04249c6480766f7f7cef5467412af1490f8d1e243141daddada3264f" + [[package]] name = "anstream" version = "0.6.13" @@ -74,6 +92,15 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "atoi" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f28d99ec8bfea296261ca1af174f24225171fea9664ba9003cbebee704810528" +dependencies = [ + "num-traits", +] + [[package]] name = "atty" version = "0.2.14" @@ -106,12 +133,48 @@ dependencies = [ "rustc-demangle", ] +[[package]] +name = "base64" +version = "0.22.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" + +[[package]] +name = "base64ct" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b" + [[package]] name = "bitflags" version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" +[[package]] +name = "bitflags" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de" +dependencies = [ + "serde", +] + +[[package]] +name = "block-buffer" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +dependencies = [ + "generic-array", +] + +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + [[package]] name = "bytes" version = "1.6.0" @@ -120,9 +183,12 @@ checksum = "514de17de45fdb8dc022b1a7975556c53c86f9f0aa5f534b98977b171857c2c9" [[package]] name = "cc" -version = "1.0.92" +version = "1.1.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2678b2e3449475e95b0aa6f9b506a28e61b3dc8996592b983695e8ebb58a8b41" +checksum = "50d2eb3cd3d1bf4529e31c215ee6f93ec5a3d536d9f578f93d9d33ee19562932" +dependencies = [ + "shlex", +] [[package]] name = "cfg-if" @@ -163,6 +229,108 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7" +[[package]] +name = "concurrent-queue" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ca0197aee26d1ae37445ee532fefce43251d24cc7c166799f4d46817f1d3973" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "const-oid" +version = "0.9.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" + +[[package]] +name = "cpufeatures" +version = "0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53fe5e26ff1b7aef8bca9c6080520cfb8d9333c7568e1829cef191a9723e5504" +dependencies = [ + "libc", +] + +[[package]] +name = "crc" +version = "3.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69e6e4d7b33a94f0991c26729976b10ebde1d34c3ee82408fb536164fa10d636" +dependencies = [ + "crc-catalog", +] + +[[package]] +name = "crc-catalog" +version = "2.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19d374276b40fb8bbdee95aef7c7fa6b5316ec764510eb64b8dd0e2ed0d7e7f5" + +[[package]] +name = "crossbeam-queue" +version = "0.3.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df0346b5d5e76ac2fe4e327c5fd1118d6be7c51dfb18f9b7922923f287471e35" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22ec99545bb0ed0ea7bb9b8e1e9122ea386ff8a48c0922e43f36d45ab09e0e80" + +[[package]] +name = "crypto-common" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" +dependencies = [ + "generic-array", + "typenum", +] + +[[package]] +name = "der" +version = "0.7.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f55bf8e7b65898637379c1b74eb1551107c8294ed26d855ceb9fd1a09cfc9bc0" +dependencies = [ + "const-oid", + "pem-rfc7468", + "zeroize", +] + +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer", + "const-oid", + "crypto-common", + "subtle", +] + +[[package]] +name = "dotenvy" +version = "0.15.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1aaf95b3e5c8f23aa320147307562d361db0ae0d51242340f558153b4eb2439b" + +[[package]] +name = "either" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0" +dependencies = [ + "serde", +] + [[package]] name = "env_logger" version = "0.8.4" @@ -176,6 +344,163 @@ dependencies = [ "termcolor", ] +[[package]] +name = "equivalent" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" + +[[package]] +name = "errno" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "534c5cf6194dfab3db3242765c03bbe257cf92f22b38f6bc0c58d59108a820ba" +dependencies = [ + "libc", + "windows-sys 0.52.0", +] + +[[package]] +name = "etcetera" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "136d1b5283a1ab77bd9257427ffd09d8667ced0570b6f938942bc7568ed5b943" +dependencies = [ + "cfg-if", + "home", + "windows-sys 0.48.0", +] + +[[package]] +name = "event-listener" +version = "5.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6032be9bd27023a771701cc49f9f053c751055f71efb2e0ae5c15809093675ba" +dependencies = [ + "concurrent-queue", + "parking", + "pin-project-lite", +] + +[[package]] +name = "fastrand" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8c02a5121d4ea3eb16a80748c74f5549a5665e4c21333c6098f283870fbdea6" + +[[package]] +name = "flume" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55ac459de2512911e4b674ce33cf20befaba382d05b62b008afc1c8b57cbf181" +dependencies = [ + "futures-core", + "futures-sink", + "spin", +] + +[[package]] +name = "form_urlencoded" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456" +dependencies = [ + "percent-encoding", +] + +[[package]] +name = "futures-channel" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eac8f7d7865dcb88bd4373ab671c8cf4508703796caa2b1985a9ca867b3fcb78" +dependencies = [ + "futures-core", + "futures-sink", +] + +[[package]] +name = "futures-core" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dfc6580bb841c5a68e9ef15c77ccc837b40a7504914d52e47b8b0e9bbda25a1d" + +[[package]] +name = "futures-executor" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a576fc72ae164fca6b9db127eaa9a9dda0d61316034f33a0a0d4eda41f02b01d" +dependencies = [ + "futures-core", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-intrusive" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d930c203dd0b6ff06e0201a4a2fe9149b43c684fd4420555b26d21b1a02956f" +dependencies = [ + "futures-core", + "lock_api", + "parking_lot", +] + +[[package]] +name = "futures-io" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a44623e20b9681a318efdd71c299b6b222ed6f231972bfe2f224ebad6311f0c1" + +[[package]] +name = "futures-sink" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fb8e00e87438d937621c1c6269e53f536c14d3fbd6a042bb24879e57d474fb5" + +[[package]] +name = "futures-task" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38d84fa142264698cdce1a9f9172cf383a0c82de1bddcf3092901442c4097004" + +[[package]] +name = "futures-util" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d6401deb83407ab3da39eba7e33987a73c3df0c82b4bb5813ee871c19c41d48" +dependencies = [ + "futures-core", + "futures-io", + "futures-sink", + "futures-task", + "memchr", + "pin-project-lite", + "pin-utils", + "slab", +] + +[[package]] +name = "generic-array" +version = "0.14.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +dependencies = [ + "typenum", + "version_check", +] + +[[package]] +name = "getrandom" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94b22e06ecb0110981051723910cbf0b5f5e09a2062dd7663334ee79a9d1286c" +dependencies = [ + "cfg-if", + "libc", + "wasi", +] + [[package]] name = "gimli" version = "0.28.1" @@ -188,6 +513,31 @@ version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" +[[package]] +name = "hashbrown" +version = "0.14.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" +dependencies = [ + "ahash", + "allocator-api2", +] + +[[package]] +name = "hashlink" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ba4ff7128dee98c7dc9794b6a411377e1404dba1c97deb8d1a55297bd25d8af" +dependencies = [ + "hashbrown 0.14.5", +] + +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + [[package]] name = "hermit-abi" version = "0.1.19" @@ -203,12 +553,55 @@ version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024" +[[package]] +name = "hex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" + +[[package]] +name = "hkdf" +version = "0.12.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b5f8eb2ad728638ea2c7d47a21db23b7b58a72ed6a38256b8a1849f15fbbdf7" +dependencies = [ + "hmac", +] + +[[package]] +name = "hmac" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" +dependencies = [ + "digest", +] + +[[package]] +name = "home" +version = "0.5.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3d1354bf6b7235cb4a0576c2619fd4ed18183f689b12b006a0ee7329eeff9a5" +dependencies = [ + "windows-sys 0.52.0", +] + [[package]] name = "humantime" version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" +[[package]] +name = "idna" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "634d9b1461af396cad843f47fdba5597a4f9e6ddd4bfb6ff5d85028c25cb12f6" +dependencies = [ + "unicode-bidi", + "unicode-normalization", +] + [[package]] name = "indexmap" version = "1.9.3" @@ -216,7 +609,32 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" dependencies = [ "autocfg", - "hashbrown", + "hashbrown 0.12.3", +] + +[[package]] +name = "indexmap" +version = "2.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93ead53efc7ea8ed3cfb0c79fc8023fbb782a5432b52830b6518941cebe6505c" +dependencies = [ + "equivalent", + "hashbrown 0.14.5", +] + +[[package]] +name = "itoa" +version = "1.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" + +[[package]] +name = "lazy_static" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" +dependencies = [ + "spin", ] [[package]] @@ -225,6 +643,29 @@ version = "0.2.153" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c198f91728a82281a64e1f4f9eeb25d82cb32a5de251c6bd1b5154d63a8e7bd" +[[package]] +name = "libm" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ec2a862134d2a7d32d7983ddcdd1c4923530833c9f2ea1a44fc5fa473989058" + +[[package]] +name = "libsqlite3-sys" +version = "0.30.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e99fb7a497b1e3339bc746195567ed8d3e24945ecd636e3619d20b9de9e9149" +dependencies = [ + "cc", + "pkg-config", + "vcpkg", +] + +[[package]] +name = "linux-raw-sys" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89" + [[package]] name = "lock_api" version = "0.4.11" @@ -241,12 +682,28 @@ version = "0.4.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "90ed8c1e510134f979dbc4f070f87d4313098b704861a105fe34231c70a3901c" +[[package]] +name = "md-5" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d89e7ee0cfbedfc4da3340218492196241d89eefb6dab27de5df917a6d2e78cf" +dependencies = [ + "cfg-if", + "digest", +] + [[package]] name = "memchr" version = "2.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6c8640c5d730cb13ebd907d8d04b52f55ac9a2eec55b440c8892f40d56c76c1d" +[[package]] +name = "minimal-lexical" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" + [[package]] name = "miniz_oxide" version = "0.7.2" @@ -268,7 +725,64 @@ dependencies = [ ] [[package]] -name = "num_cpus" +name = "nom" +version = "7.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" +dependencies = [ + "memchr", + "minimal-lexical", +] + +[[package]] +name = "num-bigint-dig" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc84195820f291c7697304f3cbdadd1cb7199c0efc917ff5eafd71225c136151" +dependencies = [ + "byteorder", + "lazy_static", + "libm", + "num-integer", + "num-iter", + "num-traits", + "rand", + "smallvec", + "zeroize", +] + +[[package]] +name = "num-integer" +version = "0.1.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" +dependencies = [ + "num-traits", +] + +[[package]] +name = "num-iter" +version = "0.1.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1429034a0490724d0075ebb2bc9e875d6503c3cf69e235a8941aa757d83ef5bf" +dependencies = [ + "autocfg", + "num-integer", + "num-traits", +] + +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", + "libm", +] + +[[package]] +name = "num_cpus" version = "1.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43" @@ -286,169 +800,701 @@ dependencies = [ "memchr", ] +[[package]] +name = "once_cell" +version = "1.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" + [[package]] name = "oxigration" version = "0.1.0" dependencies = [ "clap", "env_logger", - "indexmap", + "indexmap 1.9.3", "log", "sqlparser", + "sqlx", "tokio", "walkdir", ] [[package]] -name = "parking_lot" -version = "0.12.1" +name = "parking" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb813b8af86854136c6922af0598d719255ecb2179515e6e7730d468f05c9cae" + +[[package]] +name = "parking_lot" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c42a9226546d68acdd9c0a280d17ce19bfe27a46bf68784e4066115788d008e" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "smallvec", + "windows-targets 0.48.5", +] + +[[package]] +name = "paste" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" + +[[package]] +name = "pem-rfc7468" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88b39c9bfcfc231068454382784bb460aae594343fb030d46e9f50a645418412" +dependencies = [ + "base64ct", +] + +[[package]] +name = "percent-encoding" +version = "2.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" + +[[package]] +name = "pin-project-lite" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bda66fc9667c18cb2758a2ac84d1167245054bcf85d5d1aaa6923f45801bdd02" + +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + +[[package]] +name = "pkcs1" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8ffb9f10fa047879315e6625af03c164b16962a5368d724ed16323b68ace47f" +dependencies = [ + "der", + "pkcs8", + "spki", +] + +[[package]] +name = "pkcs8" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f950b2377845cebe5cf8b5165cb3cc1a5e0fa5cfa3e1f7f55707d8fd82e0a7b7" +dependencies = [ + "der", + "spki", +] + +[[package]] +name = "pkg-config" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d231b230927b5e4ad203db57bbcbee2802f6bce620b1e4a9024a07d94e2907ec" + +[[package]] +name = "ppv-lite86" +version = "0.2.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77957b295656769bb8ad2b6a6b09d897d94f05c41b069aede1fcdaa675eaea04" +dependencies = [ + "zerocopy", +] + +[[package]] +name = "proc-macro2" +version = "1.0.79" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e835ff2298f5721608eb1a980ecaee1aef2c132bf95ecc026a11b7bf3c01c02e" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "libc", + "rand_chacha", + "rand_core", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom", +] + +[[package]] +name = "redox_syscall" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4722d768eff46b75989dd134e5c353f0d6296e5aaa3132e776cbdb56be7731aa" +dependencies = [ + "bitflags 1.3.2", +] + +[[package]] +name = "regex" +version = "1.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c117dbdfde9c8308975b6a18d71f3f385c89461f7b3fb054288ecf2a2058ba4c" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "86b83b8b9847f9bf95ef68afb0b8e6cdb80f498442f5179a29fad448fcc1eaea" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "adad44e29e4c806119491a7f06f03de4d1af22c3a680dd47f1e6e179439d1f56" + +[[package]] +name = "ring" +version = "0.17.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c17fa4cb658e3583423e915b9f3acc01cceaee1860e33d59ebae66adc3a2dc0d" +dependencies = [ + "cc", + "cfg-if", + "getrandom", + "libc", + "spin", + "untrusted", + "windows-sys 0.52.0", +] + +[[package]] +name = "rsa" +version = "0.9.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d0e5124fcb30e76a7e79bfee683a2746db83784b86289f6251b54b7950a0dfc" +dependencies = [ + "const-oid", + "digest", + "num-bigint-dig", + "num-integer", + "num-traits", + "pkcs1", + "pkcs8", + "rand_core", + "signature", + "spki", + "subtle", + "zeroize", +] + +[[package]] +name = "rustc-demangle" +version = "0.1.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76" + +[[package]] +name = "rustix" +version = "0.38.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70dc5ec042f7a43c4a73241207cecc9873a06d45debb38b329f8541d85c2730f" +dependencies = [ + "bitflags 2.6.0", + "errno", + "libc", + "linux-raw-sys", + "windows-sys 0.52.0", +] + +[[package]] +name = "rustls" +version = "0.23.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c58f8c84392efc0a126acce10fa59ff7b3d2ac06ab451a33f2741989b806b044" +dependencies = [ + "once_cell", + "ring", + "rustls-pki-types", + "rustls-webpki", + "subtle", + "zeroize", +] + +[[package]] +name = "rustls-pemfile" +version = "2.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "196fe16b00e106300d3e45ecfcb764fa292a535d7326a29a5875c579c7417425" +dependencies = [ + "base64", + "rustls-pki-types", +] + +[[package]] +name = "rustls-pki-types" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc0a2ce646f8655401bb81e7927b812614bd5d91dbc968696be50603510fcaf0" + +[[package]] +name = "rustls-webpki" +version = "0.102.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e6b52d4fda176fd835fdc55a835d4a89b8499cad995885a21149d5ad62f852e" +dependencies = [ + "ring", + "rustls-pki-types", + "untrusted", +] + +[[package]] +name = "ryu" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" + +[[package]] +name = "same-file" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[package]] +name = "serde" +version = "1.0.209" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "99fce0ffe7310761ca6bf9faf5115afbc19688edd00171d81b1bb1b116c63e09" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.209" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5831b979fd7b5439637af1752d535ff49f4860c0f341d1baeb6faf0f4242170" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_json" +version = "1.0.127" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8043c06d9f82bd7271361ed64f415fe5e12a77fdb52e573e7f06a516dea329ad" +dependencies = [ + "itoa", + "memchr", + "ryu", + "serde", +] + +[[package]] +name = "serde_urlencoded" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" +dependencies = [ + "form_urlencoded", + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "sha1" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + +[[package]] +name = "sha2" +version = "0.10.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + +[[package]] +name = "signal-hook-registry" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8229b473baa5980ac72ef434c4415e70c4b5e71b423043adb4ba059f89c99a1" +dependencies = [ + "libc", +] + +[[package]] +name = "signature" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f" +checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de" dependencies = [ - "lock_api", - "parking_lot_core", + "digest", + "rand_core", ] [[package]] -name = "parking_lot_core" -version = "0.9.9" +name = "slab" +version = "0.4.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c42a9226546d68acdd9c0a280d17ce19bfe27a46bf68784e4066115788d008e" +checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" dependencies = [ - "cfg-if", - "libc", - "redox_syscall", - "smallvec", - "windows-targets 0.48.5", + "autocfg", ] [[package]] -name = "pin-project-lite" -version = "0.2.14" +name = "smallvec" +version = "1.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bda66fc9667c18cb2758a2ac84d1167245054bcf85d5d1aaa6923f45801bdd02" +checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" +dependencies = [ + "serde", +] [[package]] -name = "proc-macro2" -version = "1.0.79" +name = "socket2" +version = "0.5.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e835ff2298f5721608eb1a980ecaee1aef2c132bf95ecc026a11b7bf3c01c02e" +checksum = "05ffd9c0a93b7543e062e759284fcf5f5e3b098501104bfbdde4d404db792871" dependencies = [ - "unicode-ident", + "libc", + "windows-sys 0.52.0", ] [[package]] -name = "quote" -version = "1.0.36" +name = "spin" +version = "0.9.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7" +checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" dependencies = [ - "proc-macro2", + "lock_api", ] [[package]] -name = "redox_syscall" -version = "0.4.1" +name = "spki" +version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4722d768eff46b75989dd134e5c353f0d6296e5aaa3132e776cbdb56be7731aa" +checksum = "d91ed6c858b01f942cd56b37a94b3e0a1798290327d1236e4d9cf4eaca44d29d" dependencies = [ - "bitflags", + "base64ct", + "der", ] [[package]] -name = "regex" -version = "1.10.4" +name = "sqlformat" +version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c117dbdfde9c8308975b6a18d71f3f385c89461f7b3fb054288ecf2a2058ba4c" +checksum = "f895e3734318cc55f1fe66258926c9b910c124d47520339efecbb6c59cec7c1f" dependencies = [ - "aho-corasick", - "memchr", - "regex-automata", - "regex-syntax", + "nom", + "unicode_categories", ] [[package]] -name = "regex-automata" -version = "0.4.6" +name = "sqlparser" +version = "0.50.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "86b83b8b9847f9bf95ef68afb0b8e6cdb80f498442f5179a29fad448fcc1eaea" +checksum = "b2e5b515a2bd5168426033e9efbfd05500114833916f1d5c268f938b4ee130ac" dependencies = [ - "aho-corasick", - "memchr", - "regex-syntax", + "log", + "sqlparser_derive", ] [[package]] -name = "regex-syntax" -version = "0.8.3" +name = "sqlparser_derive" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "adad44e29e4c806119491a7f06f03de4d1af22c3a680dd47f1e6e179439d1f56" +checksum = "01b2e185515564f15375f593fb966b5718bc624ba77fe49fa4616ad619690554" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] [[package]] -name = "rustc-demangle" -version = "0.1.23" +name = "sqlx" +version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76" +checksum = "fcfa89bea9500db4a0d038513d7a060566bfc51d46d1c014847049a45cce85e8" +dependencies = [ + "sqlx-core", + "sqlx-macros", + "sqlx-mysql", + "sqlx-postgres", + "sqlx-sqlite", +] [[package]] -name = "same-file" -version = "1.0.6" +name = "sqlx-core" +version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" +checksum = "d06e2f2bd861719b1f3f0c7dbe1d80c30bf59e76cf019f07d9014ed7eefb8e08" dependencies = [ - "winapi-util", + "atoi", + "byteorder", + "bytes", + "crc", + "crossbeam-queue", + "either", + "event-listener", + "futures-channel", + "futures-core", + "futures-intrusive", + "futures-io", + "futures-util", + "hashbrown 0.14.5", + "hashlink", + "hex", + "indexmap 2.4.0", + "log", + "memchr", + "once_cell", + "paste", + "percent-encoding", + "rustls", + "rustls-pemfile", + "serde", + "serde_json", + "sha2", + "smallvec", + "sqlformat", + "thiserror", + "tokio", + "tokio-stream", + "tracing", + "url", + "webpki-roots", ] [[package]] -name = "scopeguard" -version = "1.2.0" +name = "sqlx-macros" +version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" +checksum = "2f998a9defdbd48ed005a89362bd40dd2117502f15294f61c8d47034107dbbdc" +dependencies = [ + "proc-macro2", + "quote", + "sqlx-core", + "sqlx-macros-core", + "syn", +] [[package]] -name = "signal-hook-registry" -version = "1.4.1" +name = "sqlx-macros-core" +version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d8229b473baa5980ac72ef434c4415e70c4b5e71b423043adb4ba059f89c99a1" +checksum = "3d100558134176a2629d46cec0c8891ba0be8910f7896abfdb75ef4ab6f4e7ce" dependencies = [ - "libc", + "dotenvy", + "either", + "heck", + "hex", + "once_cell", + "proc-macro2", + "quote", + "serde", + "serde_json", + "sha2", + "sqlx-core", + "sqlx-mysql", + "sqlx-postgres", + "sqlx-sqlite", + "syn", + "tempfile", + "tokio", + "url", ] [[package]] -name = "smallvec" -version = "1.13.2" +name = "sqlx-mysql" +version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" +checksum = "936cac0ab331b14cb3921c62156d913e4c15b74fb6ec0f3146bd4ef6e4fb3c12" +dependencies = [ + "atoi", + "base64", + "bitflags 2.6.0", + "byteorder", + "bytes", + "crc", + "digest", + "dotenvy", + "either", + "futures-channel", + "futures-core", + "futures-io", + "futures-util", + "generic-array", + "hex", + "hkdf", + "hmac", + "itoa", + "log", + "md-5", + "memchr", + "once_cell", + "percent-encoding", + "rand", + "rsa", + "serde", + "sha1", + "sha2", + "smallvec", + "sqlx-core", + "stringprep", + "thiserror", + "tracing", + "whoami", +] [[package]] -name = "socket2" -version = "0.5.6" +name = "sqlx-postgres" +version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05ffd9c0a93b7543e062e759284fcf5f5e3b098501104bfbdde4d404db792871" +checksum = "9734dbce698c67ecf67c442f768a5e90a49b2a4d61a9f1d59f73874bd4cf0710" dependencies = [ - "libc", - "windows-sys 0.52.0", + "atoi", + "base64", + "bitflags 2.6.0", + "byteorder", + "crc", + "dotenvy", + "etcetera", + "futures-channel", + "futures-core", + "futures-io", + "futures-util", + "hex", + "hkdf", + "hmac", + "home", + "itoa", + "log", + "md-5", + "memchr", + "once_cell", + "rand", + "serde", + "serde_json", + "sha2", + "smallvec", + "sqlx-core", + "stringprep", + "thiserror", + "tracing", + "whoami", ] [[package]] -name = "sqlparser" -version = "0.50.0" +name = "sqlx-sqlite" +version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2e5b515a2bd5168426033e9efbfd05500114833916f1d5c268f938b4ee130ac" +checksum = "a75b419c3c1b1697833dd927bdc4c6545a620bc1bbafabd44e1efbe9afcd337e" dependencies = [ + "atoi", + "flume", + "futures-channel", + "futures-core", + "futures-executor", + "futures-intrusive", + "futures-util", + "libsqlite3-sys", "log", - "sqlparser_derive", + "percent-encoding", + "serde", + "serde_urlencoded", + "sqlx-core", + "tracing", + "url", ] [[package]] -name = "sqlparser_derive" -version = "0.2.2" +name = "stringprep" +version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "01b2e185515564f15375f593fb966b5718bc624ba77fe49fa4616ad619690554" +checksum = "7b4df3d392d81bd458a8a621b8bffbd2302a12ffe288a9d931670948749463b1" dependencies = [ - "proc-macro2", - "quote", - "syn", + "unicode-bidi", + "unicode-normalization", + "unicode-properties", ] [[package]] @@ -457,6 +1503,12 @@ version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" +[[package]] +name = "subtle" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" + [[package]] name = "syn" version = "2.0.58" @@ -468,6 +1520,19 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "tempfile" +version = "3.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04cbcdd0c794ebb0d4cf35e88edd2f7d2c4c3e9a5a6dab322839b321c6a87a64" +dependencies = [ + "cfg-if", + "fastrand", + "once_cell", + "rustix", + "windows-sys 0.59.0", +] + [[package]] name = "termcolor" version = "1.4.1" @@ -477,6 +1542,41 @@ dependencies = [ "winapi-util", ] +[[package]] +name = "thiserror" +version = "1.0.63" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0342370b38b6a11b6cc11d6a805569958d54cfa061a29969c3b5ce2ea405724" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.63" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4558b58466b9ad7ca0f102865eccc95938dca1a74a856f2b57b6629050da261" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tinyvec" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "445e881f4f6d382d5f27c034e25eb92edd7c784ceab92a0937db7f2e9471b938" +dependencies = [ + "tinyvec_macros", +] + +[[package]] +name = "tinyvec_macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" + [[package]] name = "tokio" version = "1.37.0" @@ -507,18 +1607,123 @@ dependencies = [ "syn", ] +[[package]] +name = "tokio-stream" +version = "0.1.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "267ac89e0bec6e691e5813911606935d77c476ff49024f98abcea3e7b15e37af" +dependencies = [ + "futures-core", + "pin-project-lite", + "tokio", +] + +[[package]] +name = "tracing" +version = "0.1.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef" +dependencies = [ + "log", + "pin-project-lite", + "tracing-attributes", + "tracing-core", +] + +[[package]] +name = "tracing-attributes" +version = "0.1.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tracing-core" +version = "0.1.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54" +dependencies = [ + "once_cell", +] + +[[package]] +name = "typenum" +version = "1.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" + +[[package]] +name = "unicode-bidi" +version = "0.3.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08f95100a766bf4f8f28f90d77e0a5461bbdb219042e7679bebe79004fed8d75" + [[package]] name = "unicode-ident" version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" +[[package]] +name = "unicode-normalization" +version = "0.1.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a56d1686db2308d901306f92a263857ef59ea39678a5458e7cb17f01415101f5" +dependencies = [ + "tinyvec", +] + +[[package]] +name = "unicode-properties" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52ea75f83c0137a9b98608359a5f1af8144876eb67bcb1ce837368e906a9f524" + +[[package]] +name = "unicode_categories" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39ec24b3121d976906ece63c9daad25b85969647682eee313cb5779fdd69e14e" + +[[package]] +name = "untrusted" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" + +[[package]] +name = "url" +version = "2.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22784dbdf76fdde8af1aeda5622b546b422b6fc585325248a2bf9f5e41e94d6c" +dependencies = [ + "form_urlencoded", + "idna", + "percent-encoding", +] + [[package]] name = "utf8parse" version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a" +[[package]] +name = "vcpkg" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" + +[[package]] +name = "version_check" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" + [[package]] name = "walkdir" version = "2.5.0" @@ -535,6 +1740,31 @@ version = "0.11.0+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" +[[package]] +name = "wasite" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8dad83b4f25e74f184f64c43b150b91efe7647395b42289f38e50566d82855b" + +[[package]] +name = "webpki-roots" +version = "0.26.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd7c23921eeb1713a4e851530e9b9756e4fb0e89978582942612524cf09f01cd" +dependencies = [ + "rustls-pki-types", +] + +[[package]] +name = "whoami" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a44ab49fad634e88f55bf8f9bb3abd2f27d7204172a112c7c9987e01c1c94ea9" +dependencies = [ + "redox_syscall", + "wasite", +] + [[package]] name = "winapi" version = "0.3.9" @@ -581,7 +1811,16 @@ version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" dependencies = [ - "windows-targets 0.52.4", + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-sys" +version = "0.59.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" +dependencies = [ + "windows-targets 0.52.6", ] [[package]] @@ -601,17 +1840,18 @@ dependencies = [ [[package]] name = "windows-targets" -version = "0.52.4" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7dd37b7e5ab9018759f893a1952c9420d060016fc19a472b4bb20d1bdd694d1b" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" dependencies = [ - "windows_aarch64_gnullvm 0.52.4", - "windows_aarch64_msvc 0.52.4", - "windows_i686_gnu 0.52.4", - "windows_i686_msvc 0.52.4", - "windows_x86_64_gnu 0.52.4", - "windows_x86_64_gnullvm 0.52.4", - "windows_x86_64_msvc 0.52.4", + "windows_aarch64_gnullvm 0.52.6", + "windows_aarch64_msvc 0.52.6", + "windows_i686_gnu 0.52.6", + "windows_i686_gnullvm", + "windows_i686_msvc 0.52.6", + "windows_x86_64_gnu 0.52.6", + "windows_x86_64_gnullvm 0.52.6", + "windows_x86_64_msvc 0.52.6", ] [[package]] @@ -622,9 +1862,9 @@ checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" [[package]] name = "windows_aarch64_gnullvm" -version = "0.52.4" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bcf46cf4c365c6f2d1cc93ce535f2c8b244591df96ceee75d8e83deb70a9cac9" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" [[package]] name = "windows_aarch64_msvc" @@ -634,9 +1874,9 @@ checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" [[package]] name = "windows_aarch64_msvc" -version = "0.52.4" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da9f259dd3bcf6990b55bffd094c4f7235817ba4ceebde8e6d11cd0c5633b675" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" [[package]] name = "windows_i686_gnu" @@ -646,9 +1886,15 @@ checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" [[package]] name = "windows_i686_gnu" -version = "0.52.4" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b474d8268f99e0995f25b9f095bc7434632601028cf86590aea5c8a5cb7801d3" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" [[package]] name = "windows_i686_msvc" @@ -658,9 +1904,9 @@ checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" [[package]] name = "windows_i686_msvc" -version = "0.52.4" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1515e9a29e5bed743cb4415a9ecf5dfca648ce85ee42e15873c3cd8610ff8e02" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" [[package]] name = "windows_x86_64_gnu" @@ -670,9 +1916,9 @@ checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" [[package]] name = "windows_x86_64_gnu" -version = "0.52.4" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5eee091590e89cc02ad514ffe3ead9eb6b660aedca2183455434b93546371a03" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" [[package]] name = "windows_x86_64_gnullvm" @@ -682,9 +1928,9 @@ checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" [[package]] name = "windows_x86_64_gnullvm" -version = "0.52.4" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77ca79f2451b49fa9e2af39f0747fe999fcda4f5e241b2898624dca97a1f2177" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" [[package]] name = "windows_x86_64_msvc" @@ -694,6 +1940,33 @@ checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" [[package]] name = "windows_x86_64_msvc" -version = "0.52.4" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" + +[[package]] +name = "zerocopy" +version = "0.7.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0" +dependencies = [ + "byteorder", + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.7.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "zeroize" +version = "1.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32b752e52a2da0ddfbdbcc6fceadfeede4c939ed16d13e648833a61dfb611ed8" +checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde" diff --git a/Cargo.toml b/Cargo.toml index 2aa1609..a67fa68 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,6 +11,7 @@ env_logger = "0.8.4" indexmap = "1.9.3" log = "0.4" sqlparser = { version = "0.50.0", features = ["visitor"] } +sqlx = { version = "0.8", features = ["runtime-tokio", "postgres", "mysql", "sqlite", "tls-rustls-ring"] } tokio = { version = "1.0", features = ["full"] } walkdir = "2.3" diff --git a/README.md b/README.md index 3b31a9a..22e072e 100644 --- a/README.md +++ b/README.md @@ -1,15 +1,15 @@ -# Oxigration: SQL Migration Manager +# Oxigration: DBMS Schema Migration Manager -Oxigration is a SQL migration manager designed to implement database schemas based on individual files defining SQL objects organized in directories. This tool simplifies the process of managing and applying database schema changes across different environments. +Oxigration is a tool for managing database schema migrations, where a DBMS (Database Management System) organizes and maintains the structure of data, and a schema defines the blueprint of how data is stored and accessed. This tool simplifies the process of managing and applying database schema changes across different environments. **Features** -- Takes DBMS objects represented in files and migrates the DBMS -- Schemas are represented as directories -- Each object type is stored in a directory within the schema directory with its object types -- Tracks schema version and applied migrations -- Supports multiple database environments +- Migrate the DBMS by applying SQL changes accurately + - Takes DBMS objects represented in files and migrates the DBMS + - Tracks schema version and applied migrations - Generate DBMS objects source code from the DBMS +- Supports multiple database environments and conditional logic +- Schemas are directories, with each DBMS object type in its own subdirectory **Example Directory Layout** diff --git a/rust-toolchain.toml b/rust-toolchain.toml new file mode 100644 index 0000000..0ca0b74 --- /dev/null +++ b/rust-toolchain.toml @@ -0,0 +1,6 @@ +# NOTE: this does NOT indicate a Minimum Supported Rust Version (MSRV) of SQLx. +# We reserve the right to increase this version at any time without considering it to be a breaking change. +# See the answer in FAQ.md for details. +[toolchain] +channel = "1.80" +profile = "minimal" \ No newline at end of file diff --git a/src/deploy_log.rs b/src/deploy_log.rs index e69de29..0519bb8 100644 --- a/src/deploy_log.rs +++ b/src/deploy_log.rs @@ -0,0 +1,206 @@ +use indexmap::IndexMap; +use sqlx::{query, query_scalar, AnyPool, Executor, Row}; +use std::error::Error; +use std::sync::atomic::Ordering; + +use crate::relational_object::DatabaseObject; +use crate::utils::{format_query_with_schema, SCHEMA_SUPPORT}; + +/// This function initializes the deploy log and the configuration settings in the database. +/// It performs the following steps: +/// +/// 1. Creates a deploy schema if it does not already exist. +/// 2. Creates a `deploy_log` table to keep track of all the changes that have been applied to the database. +/// 3. Creates a `deploy_log_config` table to store configuration settings related to the deployment process. +/// 4. Inserts the initial configuration settings into the `deploy_log_config` table. +/// +/// The `deploy_log` table is crucial for tracking which changes have been applied to the database, ensuring that +/// changes are not reapplied, and enabling rollback functionality. The `deploy_log_config` table stores settings +/// that can influence the deployment process, such as environment-specific configurations. +/// +/// # Arguments +/// +/// * `connection_string` - A string slice that holds the connection string to the target database. +/// +/// # Returns +/// +/// This function returns a `Result`: +/// * `Ok(true)` if the deploy log is successfully initialized. +/// * `Err(Box)` if there is an error during the initialization process. +/// +/// # Errors +/// +/// This function will return an error if: +/// * There is an issue connecting to the database. +/// * There is an error executing the SQL statements to create the schema, tables, or insert the configuration settings. +pub async fn init_deploy_log(connection_string: &str) -> Result> { + let pool = AnyPool::connect(connection_string).await?; + + // Check if the database is SQLite + let is_sqlite = connection_string.starts_with("sqlite"); + + if !is_sqlite { + // Check if the database supports schemas + let supports_schemas: bool = query_scalar( + "SELECT EXISTS (SELECT 1 FROM information_schema.schemata WHERE schema_name = 'information_schema');" + ) + .fetch_one(&pool) + .await?; + + SCHEMA_SUPPORT.store(supports_schemas, Ordering::Relaxed); + + if supports_schemas { + // Create oxigration schema if it does not exist + pool.execute("CREATE SCHEMA IF NOT EXISTS oxigration;") + .await?; + } + } + + // Create deploy_log table if it does not exist + pool.execute( + &*format_query_with_schema( + "CREATE TABLE IF NOT EXISTS {schema_prefix}deploy_log ( + id INTEGER PRIMARY KEY, + change_name TEXT NOT NULL, + applied_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP + );", + ) + .to_string(), + ) + .await?; + + // Create deploy_log_config table if it does not exist + let query = format_query_with_schema( + "CREATE TABLE IF NOT EXISTS {schema_prefix}deploy_log_config ( + key TEXT PRIMARY KEY, + value TEXT NOT NULL + );", + ) + .to_string(); + pool.execute(&*query).await?; + + Ok(true) +} + +/// The function checks if the deploy log exists in the database by checking the deploy_log table and that there is content in the deploy_log table. +/// This is crucial for determining if a rollback is possible. The function follows these steps: +/// +/// 1. It verifies if the `deploy_log` table exists in the database. +/// 2. It checks if there are any entries in the `deploy_log` table. +/// 3. If the `deploy_log` table exists and has entries, it indicates that rollback is possible. +/// 4. If the `deploy_log` table does not exist or has no entries, rollback is not possible. +/// +/// The function returns a boolean indicating if the deploy log exists and has content. +/// +/// # Arguments +/// +/// * `connection_string` - A string slice that holds the connection string to the target database. +/// +/// # Returns +/// +/// This function returns a `Result`: +/// * `Ok(true)` if the deploy log exists and has content. +/// * `Ok(false)` if the deploy log does not exist or has no content. +/// * `Err(Box)` if there is an error during the check. +/// +/// # Errors +/// +/// This function will return an error if: +/// * There is an issue connecting to the database. +/// * There is an error executing the query to check the `deploy_log` table. +pub async fn check_deploy_log_in_db(connection_string: &str) -> Result> { + let pool = AnyPool::connect(connection_string).await?; + + // Check if the deploy_log table exists + let table_exists: bool = query_scalar( + "SELECT EXISTS (SELECT table_name FROM information_schema.tables WHERE table_schema = 'oxigration' AND table_name = 'deploy_log');" + ) + .fetch_one(&pool) + .await?; + + if !table_exists { + return Ok(false); + } + + // Check if the deploy_log table has entries + let log_has_entries: bool = + query_scalar("SELECT EXISTS (SELECT 1 FROM oxigration.deploy_log LIMIT 1);") + .fetch_one(&pool) + .await?; + + Ok(log_has_entries) +} + +/// The function reads the deploy log from the database +/// Returns an indexmap of DatabaseObject +pub async fn read_deploy_log( + connection_string: &str, +) -> Result, Box> { + if !check_deploy_log_in_db(connection_string).await? { + return Err("Deploy log does not exist in the database".into()); + } + + let pool = AnyPool::connect(connection_string).await?; + let mut deploy_log = IndexMap::new(); + + let rows = query("SELECT change_name FROM oxigration.deploy_log;") + .fetch_all(&pool) + .await?; + + for _ in rows { + // let change_name: String = row.try_get("change_name")?; + // Assuming DatabaseObject can be created from change_name + // let db_object = DatabaseObject::new(change_name.clone(), /* other required args */); + // deploy_log.insert(change_name, db_object); + } + + Ok(deploy_log) +} + +#[cfg(test)] +mod tests { + use super::*; + use sqlx::AnyPool; + + #[tokio::test] + async fn test_init_deploy_log() -> Result<(), Box> { + // Install the default drivers + sqlx::any::install_default_drivers(); + + // Use an in-memory SQLite database for testing + let connection_string = "postgresql://postgres@0.0.0.0/postgres"; + let pool = AnyPool::connect(connection_string).await?; + + // Initialize the deploy log + let result = init_deploy_log(connection_string).await?; + assert!(result, "Initialization should return true"); + + // Verify the oxigration schema exists (only if not SQLite) + if !connection_string.starts_with("sqlite") { + let schema_exists: bool = query_scalar( + "SELECT EXISTS (SELECT schema_name FROM information_schema.schemata WHERE schema_name = 'oxigration');" + ) + .fetch_one(&pool) + .await?; + assert!(schema_exists, "oxigration schema should exist"); + } + + // Verify the deploy_log table exists + let table_exists: bool = query_scalar( + "SELECT EXISTS (SELECT table_name FROM information_schema.tables WHERE table_name = 'deploy_log');" + ) + .fetch_one(&pool) + .await?; + assert!(table_exists, "deploy_log table should exist"); + + // Verify the deploy_log_config table exists + let config_table_exists: bool = query_scalar( + "SELECT EXISTS (SELECT table_name FROM information_schema.tables WHERE table_name = 'deploy_log_config');" + ) + .fetch_one(&pool) + .await?; + assert!(config_table_exists, "deploy_log_config table should exist"); + + Ok(()) + } +} diff --git a/src/lib.rs b/src/lib.rs index b18fcd6..fbae59a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,85 +1,231 @@ +mod deploy_log; mod reference; +mod relational_object; mod utils; -use reference::read_desired_state; -use std::collections::{HashSet,HashMap}; - -use sqlparser::ast::Statement; -use sqlparser::dialect::GenericDialect; -use sqlparser::parser::Parser; - -/// Represents a SQL object with its properties and dependencies. -#[derive(Debug, Clone)] -pub struct RelationalObject { - pub schema_name: String, - pub object_type: String, - pub object_name: String, - pub object_definition: Vec, - pub dependencies: HashSet, - pub properties: HashMap, -} -impl RelationalObject { - /// Creates a new SqlObject with the given parameters. - pub fn new( - schema_name: String, - object_type: String, - object_name: String, - object_definition: Vec, - dependencies: HashSet, - properties: HashMap, - ) -> Self { - RelationalObject { - schema_name, - object_type, - object_name, - object_definition, - dependencies, - properties - } +use deploy_log::{check_deploy_log_in_db, init_deploy_log, read_deploy_log}; +use log::{error, info}; +use reference::reference; +use relational_object::DatabaseObject; +use std::env; +use std::path::Path; + +/// Performs pre-migration checks to ensure the base directory exists, the target database is reachable, +/// the environment variable `ENV` is set correctly, the target database matches the environment, and +/// rollback is possible by verifying the existence of the deploy log in the database. +/// +/// # Arguments +/// +/// * `base_dir` - A string slice that holds the path to the base directory containing the source code. +/// * `connection_string` - A string slice that holds the connection string to the target database. +/// +/// # Returns +/// +/// This function returns a `Result`: +/// * `Ok(())` if all checks pass. +/// * `Err(Box)` if any check fails. +/// +/// # Errors +/// +/// This function will return an error if: +/// * The base directory does not exist. +/// * The target database is not reachable. +/// * The environment variable `ENV` is not set correctly. +/// * The target database does not match the environment. +/// * Rollback is not possible because the deploy log does not exist in the database. +async fn environment_checks( + base_dir: &str, + connection_string: &str, +) -> Result<(), Box> { + // The `install_default_drivers` function is typically used to install the default SQLx drivers for database connections. + sqlx::any::install_default_drivers(); + + // Check if the base_dir exists + if !Path::new(base_dir).exists() { + error!("Base directory does not exist: {}", base_dir); + return Err("Base directory does not exist".into()); + } else { + info!("Base directory exists: {}", base_dir); } - /// Adds a dependency to the SqlObject. - pub fn add_dependency(&mut self, dependency: String) { - self.dependencies.insert(dependency); + // Check if the target DB is reachable + // This is a placeholder check. Replace with actual DB connection check. + let db_reachable = true; // replace with actual check + if !db_reachable { + error!("Target database is not reachable"); + return Err("Target database is not reachable".into()); + } else { + info!("Target database is reachable"); } - /// Adds a property to the SqlObject. - pub fn add_property(&mut self, key: String, value: String) { - self.properties.insert(key, value); + // Check if the environment variables are set (DEV, TEST, PROD) + let env = env::var("ENV").unwrap_or_else(|_| "DEV".to_string()); + if !["DEV", "TEST", "PROD"].contains(&env.as_str()) { + error!("Environment variable ENV is not set correctly"); + return Err("Environment variable ENV is not set correctly".into()); + } else { + info!("Environment variable ENV is set to: {}", env); } + + // Check if the target DB is the correct one (DEV, TEST, PROD) + // TODO: Store in the database the environment type (DEV, TEST, PROD) and check the env against this setting + let correct_db = true; // replace with actual check + if !correct_db { + error!( + "Target database is not the correct one for the environment: {}", + env + ); + return Err("Target database is not the correct one for the environment".into()); + } else { + info!("Target database is correct for the environment: {}", env); + } + + // Check if rollback is possible (if the deploy log exists in the database) + let deploy_log_exists = check_deploy_log_in_db(connection_string).await?; + if !deploy_log_exists { + error!("Rollback is not possible, deploy log does not exist in the database"); + return Err("Rollback is not possible, deploy log does not exist in the database".into()); + } else { + info!("Rollback is possible, deploy log exists in the database"); + } + + Ok(()) +} + +/// This function initializes the deploy log and the configuration settings in the target database. +/// +/// It performs the following steps: +/// 1. Connects to the target database using the provided connection string. +/// 2. If the database supports schemas, it creates the `oxigration` schema if it does not already exist. +/// 3. Creates the `deploy_log` table if it does not already exist. This table is used to keep track of all the changes that have been applied to the database. +/// 4. Creates the `deploy_log_config` table if it does not already exist. This table is used to store configuration settings related to the deployment process. +/// 5. Inserts initial configuration settings into the `deploy_log_config` table if they do not already exist. +/// +/// The `deploy_log` table is crucial for tracking which changes have been applied to the database, ensuring that changes are not reapplied, and enabling rollback functionality. +/// The `deploy_log_config` table stores settings that can influence the deployment process, such as environment-specific configurations. +/// +/// # Arguments +/// +/// * `base_dir` - A string slice that holds the path to the base directory containing the source code. +/// * `connection_string` - A string slice that holds the connection string to the target database. +/// +/// # Returns +/// +/// This function returns a `Result`: +/// * `Ok(())` if the initialization is successful. +/// * `Err(Box)` if any error occurs during the initialization process. +/// +/// # Errors +/// +/// This function will return an error if: +/// * There is an issue connecting to the database. +/// * There is an error executing the SQL statements to create the schema, tables, or insert the configuration settings. +/// * The base directory does not exist. +/// * The target database is not reachable. +/// * The environment variable `ENV` is not set correctly. +/// * The target database does not match the environment. +/// * Rollback is not possible because the deploy log does not exist in the database. +pub async fn init(connection_string: &str) -> Result<(), Box> { + // The `install_default_drivers` function is typically used to install the default SQLx drivers for database connections. + sqlx::any::install_default_drivers(); + init_deploy_log(connection_string).await?; + Ok(()) } -pub async fn migrate(base_dir: &str, _connection_string: &str) -> Result<(), Box> { - - // Perform checks - // Check if the base_dir exists - // Check if the tartget DB is reachable - // Check if the environment variables are set (DEV, TEST, PROD) - // Check if the target DB is the correct one (DEV, TEST, PROD) - // Check if rollback is possible (if the deploy log exists) - // Read the changes from the source code - let _desired_state = read_desired_state(base_dir)?; - - // Read changes from the deploy log in the target database - // Compute the changeset between the source code and the deploy log - // Apply changes to the target database - // Apply changes to the deploy log - // Disconnect from the DB - - // Example of parsing SQL using sqlparser-rs - let dialect = GenericDialect {}; // or use a specific dialect - let sql = "SELECT * FROM table"; // replace with actual SQL - let statements = Parser::parse_sql(&dialect, sql)?; - - // Example usage of parsed statements - let _desired_state = RelationalObject::new( - "schema".to_string(), - "type".to_string(), - "name".to_string(), - statements, - HashSet::new(), - HashMap::new(), - ); +/// Migrates the database schema based on the source code in the specified base directory. +/// +/// This function performs the following steps: +/// 1. Pre-migration checks: +/// - Verifies if the base directory exists. +/// - Checks if the target database is reachable. +/// - Ensures the environment variable `ENV` is set correctly (DEV, TEST, PROD). +/// - Confirms that the target database matches the environment. +/// - Checks if rollback is possible by verifying the existence of the deploy log in the database. +/// 2. Reads and processes the desired schema and changes from the source code in the base directory. +/// 3. Reads changes from the deploy log in the target database. +/// 4. Computes the changeset between the source code and the deploy log. +/// 5. Applies changes to the target database. +/// 6. Updates the deploy log to reflect the new state of the environment. +/// 7. Disconnects from the database. +/// +/// # Arguments +/// +/// * `base_dir` - A string slice that holds the path to the base directory containing the source code. +/// * `_connection_string` - A string slice that holds the connection string to the target database. +/// +/// # Returns +/// +/// This function returns a `Result`: +/// * `Ok(())` if the migration is successful. +/// * `Err(Box)` if any error occurs during the migration process. +/// +/// # Errors +/// +/// This function will return an error if: +/// * The pre-migration checks fails. +/// * Any other error occurs during the migration process. +pub async fn migrate( + base_dir: &str, + connection_string: &str, +) -> Result<(), Box> { + // Pre-migration checks + environment_checks(base_dir, connection_string).await?; + + // Step 0: Read and process the desired schema and changes from the source code in base_dir. + // This step involves parsing the SQL files, processing them, and storing the information in memory. It parses the SQL inside each file and builds a graph representation of each database object, its modifications over time, and other dependencies. + // The information from the AST tree is used to build a graph where all the other database objects that have a dependency on that object are stated with a relationship. + // TODO: With table CREATE statements, it rewrites the initial schema based on all the ALTERS that the table might have along all the files, creating a new CREATE statement that includes all the changes. + let _reference_source_code = reference(base_dir)?; + + // Step 1: Read changes from the deploy log in the target database + // This step involves reading the deploy log to understand the current state of the environment. + let _deploy_log = read_deploy_log(connection_string).await?; + + // Step 2: Compute the changeset between the source code and the deploy log + // This step compares the changes in the source code with the entries in the deploy log. + // let changeset = compute_changeset(&_reference_source_code, &deploy_log)?; + + // Step 3: Apply changes to the target database + // This step involves executing the necessary SQL commands or other database modifications. + // apply_changes_to_db(&changeset, _connection_string).await?; + + // Step 4: Apply changes to the deploy log + // After successfully applying the changes, update the deploy log to reflect the new state of the environment. + // update_deploy_log(&changeset, _connection_string).await?; + + // Step 5: Disconnect from the DB + // Ensure that the database connection is properly closed. + // disconnect_from_db(_connection_string).await?; Ok(()) -} \ No newline at end of file +} + +/// This function generates the source code for the schema from the target database and stores it in the specified base directory. +/// +/// # Arguments +/// +/// * `base_dir` - A string slice that holds the path to the base directory where the generated source code will be stored. +/// * `_connection_string` - A string slice that holds the connection string to the target database. +/// +/// # Returns +/// +/// This function returns a `Result` type, which is: +/// +/// * `Ok(())` if the operation was successful. +/// * `Err` if there was an error during the operation. +/// +/// # Steps +/// +/// 1. Read the schema from the target database. +/// 2. Generate the source code for the schema. +/// 3. Store the generated source code in the specified base directory. +pub async fn generate( + base_dir: &str, + connection_string: &str, +) -> Result<(), Box> { + // Read the schema from the target database + // Generate the source code for the schema + // Store the source code in the base_dir + environment_checks(base_dir, connection_string).await?; + Ok(()) +} diff --git a/src/main.rs b/src/main.rs index 14a69c9..ced7c79 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,33 +1,336 @@ -use oxigration::migrate; -use env_logger; use clap::{Arg, Command}; +use env_logger; +use oxigration::{generate, init, migrate}; use tokio; +/// Oxigration: DBMS Schema Migration Manager +/// +/// Oxigration is a tool for managing database schema migrations, where a DBMS (Database Management System) organizes and maintains the structure of data, and a schema defines the blueprint of how data is stored and accessed. This tool simplifies the process of managing and applying database schema changes across different environments. +/// +/// **Features** +/// +/// - Migrate the DBMS by applying SQL changes accurately +/// - Takes DBMS objects represented in files and migrates the DBMS +/// - Tracks schema version and applied migrations +/// - Generate DBMS objects source code from the DBMS +/// - Supports multiple database environments and conditional logic +/// - Schemas are directories, with each DBMS object type in its own subdirectory +/// +/// **Example Directory Layout** +/// +/// ```sh +/// my_schema/ +/// ├── function +/// │ ├── func1.sql +/// │ └── func_with_overload.sql +/// ├── sequence +/// │ ├── regular_sequence.sql +/// ├── sp +/// │ └── sp1.sql +/// ├── table +/// │ ├── table_a_multicol_pk.sql +/// │ ├── table_b_with_fk.sql +/// ├── usertype +/// │ └── usertype1.sql +/// └── view +/// └── view1.sql +/// ``` #[tokio::main] async fn main() { env_logger::init(); let matches = Command::new("oxigration") - .arg( - Arg::new("dir") - .short('d') - .long("dir") - .value_name("DIR") - .default_value("schemas/") + .about("Oxigration: DBMS Schema Migration Manager") + .subcommand_required(true) + .arg_required_else_help(true) + .subcommand( + Command::new("init") + .about("Initialize the database schema") + .arg( + Arg::new("connection") + .short('c') + .long("connection") + .default_value("postgresql://postgres@0.0.0.0.0/postgres") + .help("Database connection string"), + ), ) - .arg( - Arg::new("connection") - .short('c') - .long("connection") - .value_name("STRING") - .default_value("sqlite:///memory") + .subcommand( + Command::new("generate") + .about("Generate DBMS objects source code from the DBMS") + .arg( + Arg::new("dir") + .short('d') + .long("dir") + .default_value("schemas/") + .help("Directory to store generated schemas"), + ) + .arg( + Arg::new("connection") + .short('c') + .long("connection") + .default_value("postgresql://postgres@0.0.0.0.0/postgres") + .help("Database connection string"), + ), + ) + .subcommand( + Command::new("migrate") + .about("Migrate the DBMS by applying SQL changes accurately") + .arg( + Arg::new("dir") + .short('d') + .long("dir") + .default_value("schemas/") + .help("Directory containing schema files"), + ) + .arg( + Arg::new("connection") + .short('c') + .long("connection") + .default_value("postgresql://postgres@0.0.0.0.0/postgres") + .help("Database connection string"), + ), ) .get_matches(); - let base_dir = matches.get_one::("dir").unwrap().as_str(); - let connection =matches.get_one::("connection").unwrap().as_str(); - if let Err(e) = migrate(base_dir, connection).await { - eprintln!("Error during migration: {}", e); - } else { - println!("Migration completed successfully"); + match matches.subcommand() { + Some(("init", sub_matches)) => { + let connection = sub_matches + .get_one::("connection") + .unwrap() + .as_str(); + if let Err(e) = init(connection).await { + eprintln!("Error during initialization: {}", e); + } else { + println!("Initialization completed successfully"); + } + } + Some(("generate", sub_matches)) => { + let base_dir = sub_matches.get_one::("dir").unwrap().as_str(); + let connection = sub_matches + .get_one::("connection") + .unwrap() + .as_str(); + if let Err(e) = generate(base_dir, connection).await { + eprintln!("Error during generation: {}", e); + } else { + println!("Generation completed successfully"); + } + } + Some(("migrate", sub_matches)) => { + let base_dir = sub_matches.get_one::("dir").unwrap().as_str(); + let connection = sub_matches + .get_one::("connection") + .unwrap() + .as_str(); + if let Err(e) = migrate(base_dir, connection).await { + eprintln!("Error during migration: {}", e); + } else { + println!("Migration completed successfully"); + } + } + _ => unreachable!(), + } +} + +#[cfg(test)] +mod tests { + use super::*; + use clap::CommandFactory; + + #[test] + fn test_cli_init() { + let cmd = Command::new("oxigration") + .subcommand_required(true) + .arg_required_else_help(true) + .subcommand( + Command::new("init").arg( + Arg::new("connection") + .short('c') + .long("connection") + .default_value("postgresql://postgres@0.0.0.0.0/postgres"), + ), + ) + .subcommand( + Command::new("generate") + .arg( + Arg::new("dir") + .short('d') + .long("dir") + .default_value("schemas/"), + ) + .arg( + Arg::new("connection") + .short('c') + .long("connection") + .default_value("postgresql://postgres@0.0.0.0.0/postgres"), + ), + ) + .subcommand( + Command::new("migrate") + .arg( + Arg::new("dir") + .short('d') + .long("dir") + .default_value("schemas/"), + ) + .arg( + Arg::new("connection") + .short('c') + .long("connection") + .default_value("postgresql://postgres@0.0.0.0.0/postgres"), + ), + ); + + let matches = cmd.try_get_matches_from(vec![ + "oxigration", + "init", + "-c", + "postgresql://test@localhost/test", + ]); + + assert!(matches.is_ok()); + let matches = matches.unwrap(); + assert_eq!(matches.subcommand_name(), Some("init")); + if let Some(sub_matches) = matches.subcommand_matches("init") { + assert_eq!( + sub_matches.get_one::("connection").unwrap(), + "postgresql://test@localhost/test" + ); + } + } + + #[test] + fn test_cli_generate() { + let cmd = Command::new("oxigration") + .subcommand_required(true) + .arg_required_else_help(true) + .subcommand( + Command::new("init").arg( + Arg::new("connection") + .short('c') + .long("connection") + .default_value("postgresql://postgres@0.0.0.0.0/postgres"), + ), + ) + .subcommand( + Command::new("generate") + .arg( + Arg::new("dir") + .short('d') + .long("dir") + .default_value("schemas/"), + ) + .arg( + Arg::new("connection") + .short('c') + .long("connection") + .default_value("postgresql://postgres@0.0.0.0.0/postgres"), + ), + ) + .subcommand( + Command::new("migrate") + .arg( + Arg::new("dir") + .short('d') + .long("dir") + .default_value("schemas/"), + ) + .arg( + Arg::new("connection") + .short('c') + .long("connection") + .default_value("postgresql://postgres@0.0.0.0.0/postgres"), + ), + ); + + let matches = cmd.try_get_matches_from(vec![ + "oxigration", + "generate", + "-d", + "test_schemas/", + "-c", + "postgresql://test@localhost/test", + ]); + + assert!(matches.is_ok()); + let matches = matches.unwrap(); + assert_eq!(matches.subcommand_name(), Some("generate")); + if let Some(sub_matches) = matches.subcommand_matches("generate") { + assert_eq!( + sub_matches.get_one::("dir").unwrap(), + "test_schemas/" + ); + assert_eq!( + sub_matches.get_one::("connection").unwrap(), + "postgresql://test@localhost/test" + ); + } + } + + #[test] + fn test_cli_migrate() { + let cmd = Command::new("oxigration") + .subcommand_required(true) + .arg_required_else_help(true) + .subcommand( + Command::new("init").arg( + Arg::new("connection") + .short('c') + .long("connection") + .default_value("postgresql://postgres@0.0.0.0.0/postgres"), + ), + ) + .subcommand( + Command::new("generate") + .arg( + Arg::new("dir") + .short('d') + .long("dir") + .default_value("schemas/"), + ) + .arg( + Arg::new("connection") + .short('c') + .long("connection") + .default_value("postgresql://postgres@0.0.0.0.0/postgres"), + ), + ) + .subcommand( + Command::new("migrate") + .arg( + Arg::new("dir") + .short('d') + .long("dir") + .default_value("schemas/"), + ) + .arg( + Arg::new("connection") + .short('c') + .long("connection") + .default_value("postgresql://postgres@0.0.0.0.0/postgres"), + ), + ); + + let matches = cmd.try_get_matches_from(vec![ + "oxigration", + "migrate", + "-d", + "test_schemas/", + "-c", + "postgresql://test@localhost/test", + ]); + + assert!(matches.is_ok()); + let matches = matches.unwrap(); + assert_eq!(matches.subcommand_name(), Some("migrate")); + if let Some(sub_matches) = matches.subcommand_matches("migrate") { + assert_eq!( + sub_matches.get_one::("dir").unwrap(), + "test_schemas/" + ); + assert_eq!( + sub_matches.get_one::("connection").unwrap(), + "postgresql://test@localhost/test" + ); + } } -} \ No newline at end of file +} diff --git a/src/reference.rs b/src/reference.rs index 71bc3b6..cce232d 100644 --- a/src/reference.rs +++ b/src/reference.rs @@ -1,16 +1,16 @@ -use crate::RelationalObject; use crate::utils::topsort::topo_sort; +use crate::DatabaseObject; +use core::ops::ControlFlow; use indexmap::IndexMap; +use sqlparser::ast::{ObjectName, Statement, Visitor}; use sqlparser::dialect::GenericDialect; use sqlparser::parser::Parser; -use sqlparser::ast::{Visitor, ObjectName, Statement}; use std::collections::{HashMap, HashSet}; use std::error::Error; use std::fs::File; use std::io::Read; use std::path::Path; use walkdir::WalkDir; -use core::ops::ControlFlow; /// Reads and processes a directory containing multiple subdirectories, each representing a type of /// database object. @@ -77,10 +77,8 @@ use core::ops::ControlFlow; /// } /// } /// ``` -pub fn read_desired_state( - base_dir: &str, -) -> Result, Box> { - let mut object_info: IndexMap = IndexMap::new(); +pub fn reference(base_dir: &str) -> Result, Box> { + let mut object_info: IndexMap = IndexMap::new(); log::debug!("Reading desired state from {}", base_dir); for entry in WalkDir::new(base_dir).into_iter().filter_map(|e| e.ok()) { @@ -147,7 +145,7 @@ fn build_relational_object( object_type: &str, contents: &str, stmt: Option<&Stmt>, -) -> Result> { +) -> Result> { let dialect = GenericDialect {}; let parsed_content = Parser::parse_sql(&dialect, &stmt.map_or(contents, |s| &s.value))?; let first_object = parsed_content @@ -186,7 +184,7 @@ fn build_relational_object( let dependencies = stmt.map_or_else(HashSet::new, |s| s.dependencies.clone()); let properties = stmt.map_or_else(HashMap::new, |s| s.properties.clone()); - Ok(RelationalObject::new( + Ok(DatabaseObject::new( schema_name.to_string(), object_type.to_string(), key, @@ -339,8 +337,8 @@ fn parse_change_stmts( /// # Errors /// Returns an error if a cycle is detected in the dependencies. fn determine_execution_order( - object_info: &IndexMap, -) -> Result, Box> { + object_info: &IndexMap, +) -> Result, Box> { let mut edges = Vec::new(); for (key, obj) in object_info { @@ -349,8 +347,7 @@ fn determine_execution_order( } } - let order = topo_sort(&edges) - .map_err(|_| "Cycle detected in dependencies")?; + let order = topo_sort(&edges).map_err(|_| "Cycle detected in dependencies")?; let execution_order: Vec = order.into_iter().map(|s| s.to_string()).collect(); @@ -445,7 +442,8 @@ mod tests { #[test] fn test_parse_change_stmts_without_end_delimiters_and_one_statements() { - let content = "CREATE PROCEDURE sp1() LANGUAGE plpgsql AS $$ DECLARE val INTEGER; END $$; \n\nGO"; + let content = + "CREATE PROCEDURE sp1() LANGUAGE plpgsql AS $$ DECLARE val INTEGER; END $$; \n\nGO"; let parsed_stmts = parse_change_stmts(content, "//// CHANGE", "GO", "name"); assert_eq!(parsed_stmts.len(), 1); assert!(parsed_stmts.contains_key("root0")); @@ -459,4 +457,4 @@ mod tests { assert!(parsed_stmts.contains_key("root0")); assert!(parsed_stmts.contains_key("root1")); } -} \ No newline at end of file +} diff --git a/src/relational_object.rs b/src/relational_object.rs new file mode 100644 index 0000000..4a13b2f --- /dev/null +++ b/src/relational_object.rs @@ -0,0 +1,40 @@ +use sqlparser::ast::Statement; +use std::collections::{HashMap, HashSet}; + +#[derive(Debug, Clone)] +pub struct DatabaseObject { + pub schema_name: String, + pub object_type: String, + pub object_name: String, + pub object_definition: Vec, + pub dependencies: HashSet, + pub properties: HashMap, +} + +impl DatabaseObject { + pub fn new( + schema_name: String, + object_type: String, + object_name: String, + object_definition: Vec, + dependencies: HashSet, + properties: HashMap, + ) -> Self { + DatabaseObject { + schema_name, + object_type, + object_name, + object_definition, + dependencies, + properties, + } + } + + pub fn add_dependency(&mut self, dependency: String) { + self.dependencies.insert(dependency); + } + + pub fn add_property(&mut self, key: String, value: String) { + self.properties.insert(key, value); + } +} diff --git a/src/utils/mod.rs b/src/utils/mod.rs index dc849ae..2d942e9 100644 --- a/src/utils/mod.rs +++ b/src/utils/mod.rs @@ -1 +1,38 @@ -pub mod topsort; \ No newline at end of file +pub mod topsort; + +use std::sync::atomic::{AtomicBool, Ordering}; + +pub static SCHEMA_SUPPORT: AtomicBool = AtomicBool::new(false); + +/// Formats a query template by replacing the `{schema_prefix}` placeholder with the appropriate schema prefix. +/// +/// This function is useful for dynamically generating SQL queries that need to be compatible with databases +/// that may or may not support schemas. If schema support is enabled, the `{schema_prefix}` placeholder in the +/// query template will be replaced with the schema name (e.g., "oxigration."). If schema support is not enabled, +/// the placeholder will be replaced with an empty string. +/// +/// # Arguments +/// +/// * `query_template` - A string slice that holds the SQL query template containing the `{schema_prefix}` placeholder. +/// +/// # Returns +/// +/// This function returns a `String`: +/// * The formatted query with the `{schema_prefix}` placeholder replaced by the appropriate schema prefix. +/// +/// # Example +/// +/// ``` +/// let query_template = "SELECT * FROM {schema_prefix}deploy_log;"; +/// let formatted_query = crate::deploy_log::format_query_with_schema(query_template); +/// // If schema support is enabled, `formatted_query` will be "SELECT * FROM oxigration.deploy_log;" +/// // If schema support is not enabled, `formatted_query` will be "SELECT * FROM deploy_log;" +/// ``` +pub fn format_query_with_schema(query_template: &str) -> String { + let schema_prefix = if SCHEMA_SUPPORT.load(Ordering::Relaxed) { + "oxigration." + } else { + "" + }; + query_template.replace("{schema_prefix}", schema_prefix) +} diff --git a/src/utils/topsort.rs b/src/utils/topsort.rs index 8174d58..f3f4691 100644 --- a/src/utils/topsort.rs +++ b/src/utils/topsort.rs @@ -12,32 +12,7 @@ type TopoSortResult = Result, TopologicalSortError>; /// Given a directed graph represented as a list of edges (source, destination), /// this function uses Kahn's algorithm to return a topological sort of the graph /// or detect if there's a cycle. -/// -/// # Example -/// -/// ``` -/// use crate::utils::topsort::{topo_sort, TopologicalSortError}; -/// -/// let graph = vec![(1, 2), (1, 3), (2, 3), (3, 4), (4, 5), (5, 6), (6, 7)]; -/// let sort = topo_sort(&graph); -/// assert!(sort.is_ok()); -/// let sort = sort.unwrap(); -/// assert_eq!(sort, vec![1, 2, 3, 4, 5, 6, 7]); -/// ``` -/// -/// # Cycle Detection -/// -/// ``` -/// use crate::utils::topsort::{topo_sort, TopologicalSortError}; -/// -/// let graph = vec![(1, 2), (2, 3), (3, 4), (4, 5), (4, 2)]; -/// let sort = topo_sort(&graph); -/// assert!(sort.is_err()); -/// assert_eq!(sort.err().unwrap(), TopologicalSortError::CycleDetected); -/// ``` -pub fn topo_sort( - edges: &Vec<(Node, Node)>, -) -> TopoSortResult { +pub fn topo_sort(edges: &Vec<(Node, Node)>) -> TopoSortResult { // Step 1: Initialize data structures let mut edges_by_source: HashMap> = HashMap::new(); let mut incoming_edges_count: HashMap = HashMap::new(); @@ -45,7 +20,10 @@ pub fn topo_sort( // Step 2: Build the graph and count incoming edges for each node for (source, destination) in edges { incoming_edges_count.entry(*source).or_insert(0); - edges_by_source.entry(*source).or_default().push(*destination); + edges_by_source + .entry(*source) + .or_default() + .push(*destination); *incoming_edges_count.entry(*destination).or_insert(0) += 1; } @@ -139,4 +117,4 @@ mod tests { assert!(sort.is_err()); assert_eq!(sort.err().unwrap(), TopologicalSortError::CycleDetected); } -} \ No newline at end of file +}