diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..409fc30 --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +/target +/entity-update_derive/target +/entity-changes-ctl/target +.idea +/Cargo.lock diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..65a31f1 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,68 @@ +# CLAUDE.md + +This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. + +## Project Overview + +This is a Rust library called `entity-changes` that provides entity change tracking functionality for MySQL databases using SQLx. The library supports tracking entity modifications with actors, sessions, and change sets, along with procedural macros for automatic derivation of entity tracking capabilities. + +## Architecture + +### Workspace Structure +- **Main library** (`src/`): Core entity change tracking functionality +- **entity-update_derive** (`entity-update_derive/`): Procedural macro crate for deriving Entity and Update traits +- **entity-changes-ctl** (`entity-changes-ctl/`): Command-line utility for the library + +### Core Components +- **models.rs**: Defines `EntityChange` struct and `Action` enum for tracking entity modifications +- **repositories.rs**: Database query functions for creating and retrieving entity changes by various criteria (ID, entity, session, actor, change set) +- **value.rs**: Type-safe value system supporting MySQL types (integers, strings, UUIDs, dates, etc.) with `Value` enum and `Updater` for SQL operations +- **condition.rs**: Query condition system with `Condition` enum supporting SQL operations (Equal, Like, In, And, Or, etc.) +- **helpers.rs**: Utility functions and macros for the library + +### Features +- `derive`: Enables procedural macro support for Entity and Update traits +- `static-validation`: Enables static SQLx validation during compilation + +## Development Commands + +### Building +```bash +cargo build +``` + +### Testing +```bash +cargo test +``` + +### Building with all features +```bash +cargo build --all-features +``` + +### Working with workspace members +```bash +# Build specific workspace member +cargo build -p entity-update_derive +cargo build -p entity-changes-ctl + +# Test specific workspace member +cargo test -p entity-changes +``` + +### Releasing +The project uses a Makefile for tagging releases: +```bash +make tag +``` +This creates a git tag based on the version in Cargo.toml and pushes it to the remote repository. + +## Important Notes + +- The library is designed specifically for MySQL databases via SQLx +- All entity changes include metadata: actor_id, session_id, change_set_id, and timestamps +- The `new_value` field stores JSON data for tracking actual changes +- Procedural macros are optional and gated behind the "derive" feature +- The library uses UUIDs for all entity identifiers +- Query conditions support both simple field-value pairs and complex nested And/Or logic \ No newline at end of file diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..e4e6a28 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,21 @@ +[package] +name = "entity-changes" +version = "0.1.37" +edition = "2021" + +[dependencies] +entity-update_derive = { path = "entity-update_derive", optional = true } +sqlx = { version = "0.8", features = ["runtime-tokio", "mysql", "uuid", "chrono", "json"] } +serde_json = "1.0" +uuid = { version = "1", features = ["v4"]} + +[workspace] +members = [ + "entity-update_derive", + "entity-changes-ctl" +] + +[features] +default = [] +derive = ["dep:entity-update_derive"] +static-validation = ["entity-update_derive?/static-validation"] \ No newline at end of file diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..05071dd --- /dev/null +++ b/Makefile @@ -0,0 +1,6 @@ +TAG ?= $(shell cargo pkgid --offline | cut -d\# -f2 | cut -d: -f2) + +tag: # Tag current release + #git commit -am "Release $(TAG)" + #git push + git tag "v$(TAG)" -a -m "Release $(TAG)" && git push --tags diff --git a/entity-changes-ctl/Cargo.lock b/entity-changes-ctl/Cargo.lock new file mode 100644 index 0000000..dbb8c5d --- /dev/null +++ b/entity-changes-ctl/Cargo.lock @@ -0,0 +1,2003 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "addr2line" +version = "0.22.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e4503c46a5c0c7844e948c9a4d6acd9f50cccb4de1c48eb9e291ea17470c678" +dependencies = [ + "gimli", +] + +[[package]] +name = "adler" +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 = "allocator-api2" +version = "0.2.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c6cb57a04249c6480766f7f7cef5467412af1490f8d1e243141daddada3264f" + +[[package]] +name = "android-tzdata" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0" + +[[package]] +name = "android_system_properties" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" +dependencies = [ + "libc", +] + +[[package]] +name = "anstream" +version = "0.6.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "64e15c1ab1f89faffbf04a634d5e1962e9074f2741eef6d97f3c4e322426d526" +dependencies = [ + "anstyle", + "anstyle-parse", + "anstyle-query", + "anstyle-wincon", + "colorchoice", + "is_terminal_polyfill", + "utf8parse", +] + +[[package]] +name = "anstyle" +version = "1.0.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bec1de6f59aedf83baf9ff929c98f2ad654b97c9510f4e70cf6f661d49fd5b1" + +[[package]] +name = "anstyle-parse" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eb47de1e80c2b463c735db5b217a0ddc39d612e7ac9e2e96a5aed1f57616c1cb" +dependencies = [ + "utf8parse", +] + +[[package]] +name = "anstyle-query" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d36fc52c7f6c869915e99412912f22093507da8d9e942ceaf66fe4b7c14422a" +dependencies = [ + "windows-sys 0.52.0", +] + +[[package]] +name = "anstyle-wincon" +version = "3.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5bf74e1b6e971609db8ca7a9ce79fd5768ab6ae46441c572e46cf596f59e57f8" +dependencies = [ + "anstyle", + "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 = "autocfg" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c4b4d0bd25bd0b74681c0ad21497610ce1b7c91b1022cd21c80c6fbdd9476b0" + +[[package]] +name = "backtrace" +version = "0.3.73" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5cc23269a4f8976d0a4d2e7109211a419fe30e8d88d677cd60b6bc79c5732e0a" +dependencies = [ + "addr2line", + "cc", + "cfg-if", + "libc", + "miniz_oxide", + "object", + "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 = "bumpalo" +version = "3.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c" + +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + +[[package]] +name = "bytes" +version = "1.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8318a53db07bb3f8dca91a600466bdb3f2eaadeedfdbcf02e1accbad9271ba50" + +[[package]] +name = "cc" +version = "1.1.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57b6a275aa2903740dc87da01c62040406b8812552e97129a63ea8850a17c6e6" +dependencies = [ + "shlex", +] + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "chrono" +version = "0.4.38" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a21f936df1771bf62b77f047b726c4625ff2e8aa607c01ec06e5a05bd8463401" +dependencies = [ + "android-tzdata", + "iana-time-zone", + "js-sys", + "num-traits", + "wasm-bindgen", + "windows-targets 0.52.6", +] + +[[package]] +name = "clap" +version = "4.5.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed6719fffa43d0d87e5fd8caeab59be1554fb028cd30edc88fc4369b17971019" +dependencies = [ + "clap_builder", + "clap_derive", +] + +[[package]] +name = "clap_builder" +version = "4.5.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "216aec2b177652e3846684cbfe25c9964d18ec45234f0f5da5157b207ed1aab6" +dependencies = [ + "anstream", + "anstyle", + "clap_lex", + "strsim", +] + +[[package]] +name = "clap_derive" +version = "4.5.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "501d359d5f3dcaf6ecdeee48833ae73ec6e42723a1e52419c79abf9507eec0a0" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "clap_lex" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1462739cb27611015575c0c11df5df7601141071f07518d56fcc1be504cbec97" + +[[package]] +name = "colorchoice" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3fd119d74b830634cea2a0f58bbd0d54540518a14397557951e79340abc28c0" + +[[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 = "core-foundation" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "core-foundation-sys" +version = "0.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" + +[[package]] +name = "cpufeatures" +version = "0.2.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51e852e6dc9a5bed1fae92dd2375037bf2b768725bf3be87811edee3249d09ad" +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 = "dotenv" +version = "0.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77c90badedccf4105eca100756a0b1289e191f6fcbdadd3cee1d2f614f97da8f" + +[[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 = "entity-ctl" +version = "0.1.0" +dependencies = [ + "chrono", + "clap", + "dotenv", + "sqlx", + "tokio", + "url", + "uuid", +] + +[[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 = "foreign-types" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" +dependencies = [ + "foreign-types-shared", +] + +[[package]] +name = "foreign-types-shared" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" + +[[package]] +name = "form_urlencoded" +version = "1.2.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.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" +dependencies = [ + "cfg-if", + "libc", + "wasi", +] + +[[package]] +name = "gimli" +version = "0.29.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "40ecd4077b5ae9fd2e9e169b102c6c330d0605168eb0e8bf79952b256dbefffd" + +[[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", +] + +[[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.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 = "iana-time-zone" +version = "0.1.60" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7ffbb5a1b541ea2561f8c41c087286cc091e21e556a4f09a8f6cbf17b69b141" +dependencies = [ + "android_system_properties", + "core-foundation-sys", + "iana-time-zone-haiku", + "js-sys", + "wasm-bindgen", + "windows-core", +] + +[[package]] +name = "iana-time-zone-haiku" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" +dependencies = [ + "cc", +] + +[[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 = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68b900aa2f7301e21c36462b170ee99994de34dff39a4a6a528e80e7376d07e5" +dependencies = [ + "equivalent", + "hashbrown", +] + +[[package]] +name = "is_terminal_polyfill" +version = "1.70.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" + +[[package]] +name = "itoa" +version = "1.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" + +[[package]] +name = "js-sys" +version = "0.3.70" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1868808506b929d7b0cfa8f75951347aa71bb21144b7791bae35d9bccfcfe37a" +dependencies = [ + "wasm-bindgen", +] + +[[package]] +name = "lazy_static" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" +dependencies = [ + "spin", +] + +[[package]] +name = "libc" +version = "0.2.158" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8adc4bb1803a324070e64a98ae98f38934d91957a99cfb3a43dcbc01bc56439" + +[[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.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17" +dependencies = [ + "autocfg", + "scopeguard", +] + +[[package]] +name = "log" +version = "0.4.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" + +[[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.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" + +[[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.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8a240ddb74feaf34a79a7add65a741f3167852fba007066dcac1ca548d89c08" +dependencies = [ + "adler", +] + +[[package]] +name = "mio" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "80e04d1dcff3aae0704555fe5fee3bcfaf3d1fdf8a7e521d5b9d2b42acb52cec" +dependencies = [ + "hermit-abi", + "libc", + "wasi", + "windows-sys 0.52.0", +] + +[[package]] +name = "native-tls" +version = "0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8614eb2c83d59d1c8cc974dd3f920198647674a0a035e1af1fa58707e317466" +dependencies = [ + "libc", + "log", + "openssl", + "openssl-probe", + "openssl-sys", + "schannel", + "security-framework", + "security-framework-sys", + "tempfile", +] + +[[package]] +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 = "object" +version = "0.36.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "084f1a5821ac4c651660a94a7153d27ac9d8a53736203f58b31945ded098070a" +dependencies = [ + "memchr", +] + +[[package]] +name = "once_cell" +version = "1.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" + +[[package]] +name = "openssl" +version = "0.10.66" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9529f4786b70a3e8c61e11179af17ab6188ad8d0ded78c5529441ed39d4bd9c1" +dependencies = [ + "bitflags 2.6.0", + "cfg-if", + "foreign-types", + "libc", + "once_cell", + "openssl-macros", + "openssl-sys", +] + +[[package]] +name = "openssl-macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "openssl-probe" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" + +[[package]] +name = "openssl-sys" +version = "0.9.103" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f9e8deee91df40a943c71b917e5874b951d32a802526c85721ce3b776c929d6" +dependencies = [ + "cc", + "libc", + "pkg-config", + "vcpkg", +] + +[[package]] +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.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1bf18183cf54e8d6059647fc3063646a1801cf30896933ec2311622cc4b9a27" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall 0.5.3", + "smallvec", + "windows-targets 0.52.6", +] + +[[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.86" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e719e8df665df0d1c8fbfd238015744736151d4445ec0836b8e628aae103b77" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af" +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 = "redox_syscall" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a908a6e00f1fdd0dfd9c0eb08ce85126f6d8bbda50017e74bc4a4b7d4a926a4" +dependencies = [ + "bitflags 2.6.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.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" + +[[package]] +name = "rustix" +version = "0.38.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a85d50532239da68e9addb745ba38ff4612a242c1c7ceea689c4bc7c2f43c36f" +dependencies = [ + "bitflags 2.6.0", + "errno", + "libc", + "linux-raw-sys", + "windows-sys 0.52.0", +] + +[[package]] +name = "ryu" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" + +[[package]] +name = "schannel" +version = "0.1.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fbc91545643bcf3a0bbb6569265615222618bdf33ce4ffbbd13c4bbd4c093534" +dependencies = [ + "windows-sys 0.52.0", +] + +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[package]] +name = "security-framework" +version = "2.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02" +dependencies = [ + "bitflags 2.6.0", + "core-foundation", + "core-foundation-sys", + "libc", + "security-framework-sys", +] + +[[package]] +name = "security-framework-sys" +version = "2.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75da29fe9b9b08fe9d6b22b5b4bcbc75d8db3aa31e639aa56bb62e9d46bfceaf" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[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 = "signature" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de" +dependencies = [ + "digest", + "rand_core", +] + +[[package]] +name = "slab" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" +dependencies = [ + "autocfg", +] + +[[package]] +name = "smallvec" +version = "1.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" +dependencies = [ + "serde", +] + +[[package]] +name = "socket2" +version = "0.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce305eb0b4296696835b71df73eb912e0f1ffd2556a501fcede6e0c50349191c" +dependencies = [ + "libc", + "windows-sys 0.52.0", +] + +[[package]] +name = "spin" +version = "0.9.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" +dependencies = [ + "lock_api", +] + +[[package]] +name = "spki" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d91ed6c858b01f942cd56b37a94b3e0a1798290327d1236e4d9cf4eaca44d29d" +dependencies = [ + "base64ct", + "der", +] + +[[package]] +name = "sqlformat" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f895e3734318cc55f1fe66258926c9b910c124d47520339efecbb6c59cec7c1f" +dependencies = [ + "nom", + "unicode_categories", +] + +[[package]] +name = "sqlx" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fcfa89bea9500db4a0d038513d7a060566bfc51d46d1c014847049a45cce85e8" +dependencies = [ + "sqlx-core", + "sqlx-macros", + "sqlx-mysql", + "sqlx-postgres", + "sqlx-sqlite", +] + +[[package]] +name = "sqlx-core" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d06e2f2bd861719b1f3f0c7dbe1d80c30bf59e76cf019f07d9014ed7eefb8e08" +dependencies = [ + "atoi", + "byteorder", + "bytes", + "crc", + "crossbeam-queue", + "either", + "event-listener", + "futures-channel", + "futures-core", + "futures-intrusive", + "futures-io", + "futures-util", + "hashbrown", + "hashlink", + "hex", + "indexmap", + "log", + "memchr", + "native-tls", + "once_cell", + "paste", + "percent-encoding", + "serde", + "serde_json", + "sha2", + "smallvec", + "sqlformat", + "thiserror", + "tokio", + "tokio-stream", + "tracing", + "url", +] + +[[package]] +name = "sqlx-macros" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f998a9defdbd48ed005a89362bd40dd2117502f15294f61c8d47034107dbbdc" +dependencies = [ + "proc-macro2", + "quote", + "sqlx-core", + "sqlx-macros-core", + "syn", +] + +[[package]] +name = "sqlx-macros-core" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d100558134176a2629d46cec0c8891ba0be8910f7896abfdb75ef4ab6f4e7ce" +dependencies = [ + "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 = "sqlx-mysql" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +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 = "sqlx-postgres" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9734dbce698c67ecf67c442f768a5e90a49b2a4d61a9f1d59f73874bd4cf0710" +dependencies = [ + "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 = "sqlx-sqlite" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a75b419c3c1b1697833dd927bdc4c6545a620bc1bbafabd44e1efbe9afcd337e" +dependencies = [ + "atoi", + "flume", + "futures-channel", + "futures-core", + "futures-executor", + "futures-intrusive", + "futures-util", + "libsqlite3-sys", + "log", + "percent-encoding", + "serde", + "serde_urlencoded", + "sqlx-core", + "tracing", + "url", +] + +[[package]] +name = "stringprep" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b4df3d392d81bd458a8a621b8bffbd2302a12ffe288a9d931670948749463b1" +dependencies = [ + "unicode-bidi", + "unicode-normalization", + "unicode-properties", +] + +[[package]] +name = "strsim" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" + +[[package]] +name = "subtle" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" + +[[package]] +name = "syn" +version = "2.0.77" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f35bcdf61fd8e7be6caf75f429fdca8beb3ed76584befb503b1569faee373ed" +dependencies = [ + "proc-macro2", + "quote", + "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 = "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.40.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2b070231665d27ad9ec9b8df639893f46727666c6767db40317fbe920a5d998" +dependencies = [ + "backtrace", + "bytes", + "libc", + "mio", + "pin-project-lite", + "socket2", + "tokio-macros", + "windows-sys 0.52.0", +] + +[[package]] +name = "tokio-macros" +version = "2.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "693d596312e88961bc67d7f1f97af8a70227d9f90c31bba5806eec004978d752" +dependencies = [ + "proc-macro2", + "quote", + "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 = "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.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" + +[[package]] +name = "uuid" +version = "1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81dfa00651efa65069b0b6b651f4aaa31ba9e3c3ce0137aaad053604ee7e0314" +dependencies = [ + "getrandom", +] + +[[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 = "wasi" +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 = "wasm-bindgen" +version = "0.2.93" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a82edfc16a6c469f5f44dc7b571814045d60404b55a0ee849f9bcfa2e63dd9b5" +dependencies = [ + "cfg-if", + "once_cell", + "wasm-bindgen-macro", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.93" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9de396da306523044d3302746f1208fa71d7532227f15e347e2d93e4145dd77b" +dependencies = [ + "bumpalo", + "log", + "once_cell", + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.93" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "585c4c91a46b072c92e908d99cb1dcdf95c5218eeb6f3bf1efa991ee7a68cccf" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.93" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "afc340c74d9005395cf9dd098506f7f44e38f2b4a21c6aaacf9a105ea5e1e836" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.93" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c62a0a307cb4a311d3a07867860911ca130c3494e8c2719593806c08bc5d0484" + +[[package]] +name = "whoami" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a44ab49fad634e88f55bf8f9bb3abd2f27d7204172a112c7c9987e01c1c94ea9" +dependencies = [ + "redox_syscall 0.4.1", + "wasite", +] + +[[package]] +name = "windows-core" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9" +dependencies = [ + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-sys" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" +dependencies = [ + "windows-targets 0.48.5", +] + +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "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]] +name = "windows-targets" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" +dependencies = [ + "windows_aarch64_gnullvm 0.48.5", + "windows_aarch64_msvc 0.48.5", + "windows_i686_gnu 0.48.5", + "windows_i686_msvc 0.48.5", + "windows_x86_64_gnu 0.48.5", + "windows_x86_64_gnullvm 0.48.5", + "windows_x86_64_msvc 0.48.5", +] + +[[package]] +name = "windows-targets" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +dependencies = [ + "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]] +name = "windows_aarch64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" + +[[package]] +name = "windows_i686_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" + +[[package]] +name = "windows_i686_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" + +[[package]] +name = "windows_x86_64_msvc" +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 = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde" diff --git a/entity-changes-ctl/Cargo.toml b/entity-changes-ctl/Cargo.toml new file mode 100644 index 0000000..469a68e --- /dev/null +++ b/entity-changes-ctl/Cargo.toml @@ -0,0 +1,13 @@ +[package] +name = "entity-changes-ctl" +version = "0.1.0" +edition = "2021" + +[dependencies] +uuid = { version = "1", features = ["v4"] } +chrono = "0.4" +dotenv = "0.15" +sqlx = { version = "0.8", features = ["mysql", "runtime-tokio-native-tls"] } +tokio = { version = "1", features = ["rt", "macros", "time", "net", "rt-multi-thread"] } +clap = { version = "4.1", features = ["derive"] } +url = "2" \ No newline at end of file diff --git a/entity-changes-ctl/scripts/create-ddl.sql b/entity-changes-ctl/scripts/create-ddl.sql new file mode 100644 index 0000000..3a7215f --- /dev/null +++ b/entity-changes-ctl/scripts/create-ddl.sql @@ -0,0 +1,6 @@ +CREATE TABLE entity_changes_metadata ( + table_name VARCHAR(255) PRIMARY KEY, + is_auditable BOOLEAN NOT NULL DEFAULT TRUE +); + +-- INSERT INTO entity_changes_metadata (table_name) VALUES ('my_table'); \ No newline at end of file diff --git a/entity-changes-ctl/scripts/drop-tables.down.sql b/entity-changes-ctl/scripts/drop-tables.down.sql new file mode 100644 index 0000000..e4b543f --- /dev/null +++ b/entity-changes-ctl/scripts/drop-tables.down.sql @@ -0,0 +1,36 @@ +DELIMITER $$ + +CREATE PROCEDURE DropEntityChangeTables() +BEGIN + DECLARE done INT DEFAULT 0; + DECLARE table_name VARCHAR(255); + DECLARE drop_stmt VARCHAR(512); + + DECLARE cur CURSOR FOR + SELECT CONCAT('DROP TABLE IF EXISTS entity_changes_', table_name) + FROM entity_changes_metadata + WHERE is_auditable = TRUE; + + DECLARE CONTINUE HANDLER FOR NOT FOUND SET done = 1; + + OPEN cur; + + read_loop: LOOP + FETCH cur INTO drop_stmt; + IF done THEN + LEAVE read_loop; + END IF; + + SET @s = drop_stmt; + PREPARE stmt FROM @s; + EXECUTE stmt; + DEALLOCATE PREPARE stmt; + END LOOP; + + CLOSE cur; +END$$ + +DELIMITER ; + + +CALL DropEntityChangeTables(); \ No newline at end of file diff --git a/entity-changes-ctl/src/main.rs b/entity-changes-ctl/src/main.rs new file mode 100644 index 0000000..a800197 --- /dev/null +++ b/entity-changes-ctl/src/main.rs @@ -0,0 +1,127 @@ +use clap::Parser; +use dotenv::dotenv; +use sqlx::{mysql::MySqlPool, Row}; +use std::env; +use url::Url; + +/// Command line arguments +#[derive(Parser, Debug)] +#[command(author, version, about, long_about = None)] +struct Args { + /// The name of the schema to operate on + #[arg(long, short)] + schema_name: Option, + + /// The database URL, optionally provided. Defaults to the DATABASE_URL environment variable. + #[arg(long, short)] + db_url: Option, + + #[arg(long, short)] + env: Option, + + #[arg(long)] + delete: bool, +} + +#[tokio::main] +async fn main() -> Result<(), sqlx::Error> { + // Parse command-line arguments + let args = Args::parse(); + + // Load environment variables from .env file + if let Some(env) = args.env { + dotenv::from_filename(env).ok(); + } else { + dotenv().ok(); + } + + // Use provided --db-url or fallback to the DATABASE_URL from .env + let database_url = args + .db_url + .unwrap_or_else(|| env::var("DATABASE_URL").expect("DATABASE_URL must be set")); + + // Connect to the TiDB database + let pool = MySqlPool::connect(&database_url).await?; + + // Use the schema name from command line arguments + let schema_name = args.schema_name.unwrap_or_else(|| { + let parsed_url = Url::parse(&database_url).expect("Failed to parse database URL"); + parsed_url + .path_segments() + .and_then(|segments| segments.last()) + .map(|db_name| db_name.to_string()) + .expect("No schema (database) name found in the database URL") + }); + + // Find all tables with 'actor_id' and 'session_id' columns in the specified schema, + // excluding tables that start with 'entity_changes_' + let tables: Vec = sqlx::query( + "SELECT table_name FROM entity_changes_metadata WHERE is_auditable = TRUE" + ) + .fetch_all(&pool) + .await? + .iter() + .map(|row| row.get::("table_name")) + .collect(); + + // Iterate over each table and create an entity_changes table and triggers + for table_name in tables { + // Create the corresponding entity_changes table + println!("table_name: {}", table_name); + if args.delete { + let entity_changes_table = format!("entity_changes_{}", table_name); + println!("delete table: {}", entity_changes_table); + sqlx::query(&format!( + "DROP TABLE IF EXISTS {}.{}", + schema_name, entity_changes_table + )).execute(&pool).await?; + } else { + let entity_changes_table = format!("entity_changes_{}", table_name); + println!("create table: {}", entity_changes_table); + sqlx::query(&format!( + "CREATE TABLE IF NOT EXISTS {}.{} ( + id BINARY(16) PRIMARY KEY /* T! CLUSTERED */, + entity_id BINARY(16) NOT NULL, + action ENUM('insert', 'update', 'delete', 'restore', 'hard-delete') NOT NULL, + changed_at BIGINT NOT NULL, + actor_id BINARY(16) NOT NULL, + session_id BINARY(16) NOT NULL, + change_set_id BINARY(16) NOT NULL, + new_value JSON + );", + schema_name, entity_changes_table, + )).execute(&pool).await?; + + println!("create index _ entity_id"); + sqlx::query(&format!( + "CREATE INDEX IF NOT EXISTS idx_{}_entity_id ON {} (entity_id);", + entity_changes_table, entity_changes_table, + )).execute(&pool).await?; + + println!("create index _ change_set_id"); + sqlx::query(&format!( + "CREATE INDEX IF NOT EXISTS idx_{}_change_set_id ON {} (change_set_id);", + entity_changes_table, entity_changes_table, + )).execute(&pool).await?; + + println!("create index _ session_id"); + sqlx::query(&format!( + "CREATE INDEX IF NOT EXISTS idx_{}_session_id ON {} (session_id);", + entity_changes_table, entity_changes_table, + )).execute(&pool).await?; + + println!("create index _ actor_id"); + sqlx::query(&format!( + "CREATE INDEX IF NOT EXISTS idx_{}_actor_id ON {} (actor_id);", + entity_changes_table, entity_changes_table, + )).execute(&pool).await?; + + println!("create index _ entity_id_actor_id"); + sqlx::query(&format!( + "CREATE INDEX IF NOT EXISTS idx_{}_entity_id_actor_id ON {} (entity_id, actor_id);", + entity_changes_table, entity_changes_table, + )).execute(&pool).await?; + } + } + Ok(()) +} diff --git a/entity-update_derive/Cargo.lock b/entity-update_derive/Cargo.lock new file mode 100644 index 0000000..9736c39 --- /dev/null +++ b/entity-update_derive/Cargo.lock @@ -0,0 +1,1487 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[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 = "allocator-api2" +version = "0.2.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c6cb57a04249c6480766f7f7cef5467412af1490f8d1e243141daddada3264f" + +[[package]] +name = "atoi" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f28d99ec8bfea296261ca1af174f24225171fea9664ba9003cbebee704810528" +dependencies = [ + "num-traits", +] + +[[package]] +name = "autocfg" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c4b4d0bd25bd0b74681c0ad21497610ce1b7c91b1022cd21c80c6fbdd9476b0" + +[[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.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8318a53db07bb3f8dca91a600466bdb3f2eaadeedfdbcf02e1accbad9271ba50" + +[[package]] +name = "cc" +version = "1.1.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57b6a275aa2903740dc87da01c62040406b8812552e97129a63ea8850a17c6e6" +dependencies = [ + "shlex", +] + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[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.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51e852e6dc9a5bed1fae92dd2375037bf2b768725bf3be87811edee3249d09ad" +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 = "entity_utils_derive" +version = "0.1.0" +dependencies = [ + "proc-macro2", + "quote", + "sqlx", + "syn", +] + +[[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.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" +dependencies = [ + "cfg-if", + "libc", + "wasi", +] + +[[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", +] + +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + +[[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 = "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 = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68b900aa2f7301e21c36462b170ee99994de34dff39a4a6a528e80e7376d07e5" +dependencies = [ + "equivalent", + "hashbrown", +] + +[[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]] +name = "libc" +version = "0.2.158" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8adc4bb1803a324070e64a98ae98f38934d91957a99cfb3a43dcbc01bc56439" + +[[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.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17" +dependencies = [ + "autocfg", + "scopeguard", +] + +[[package]] +name = "log" +version = "0.4.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" + +[[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.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" + +[[package]] +name = "minimal-lexical" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" + +[[package]] +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 = "once_cell" +version = "1.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" + +[[package]] +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.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1bf18183cf54e8d6059647fc3063646a1801cf30896933ec2311622cc4b9a27" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall 0.5.3", + "smallvec", + "windows-targets 0.52.6", +] + +[[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.86" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e719e8df665df0d1c8fbfd238015744736151d4445ec0836b8e628aae103b77" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af" +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 = "redox_syscall" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a908a6e00f1fdd0dfd9c0eb08ce85126f6d8bbda50017e74bc4a4b7d4a926a4" +dependencies = [ + "bitflags 2.6.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 = "rustix" +version = "0.38.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a85d50532239da68e9addb745ba38ff4612a242c1c7ceea689c4bc7c2f43c36f" +dependencies = [ + "bitflags 2.6.0", + "errno", + "libc", + "linux-raw-sys", + "windows-sys 0.52.0", +] + +[[package]] +name = "ryu" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" + +[[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 = "signature" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de" +dependencies = [ + "digest", + "rand_core", +] + +[[package]] +name = "slab" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" +dependencies = [ + "autocfg", +] + +[[package]] +name = "smallvec" +version = "1.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" +dependencies = [ + "serde", +] + +[[package]] +name = "spin" +version = "0.9.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" +dependencies = [ + "lock_api", +] + +[[package]] +name = "spki" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d91ed6c858b01f942cd56b37a94b3e0a1798290327d1236e4d9cf4eaca44d29d" +dependencies = [ + "base64ct", + "der", +] + +[[package]] +name = "sqlformat" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f895e3734318cc55f1fe66258926c9b910c124d47520339efecbb6c59cec7c1f" +dependencies = [ + "nom", + "unicode_categories", +] + +[[package]] +name = "sqlx" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fcfa89bea9500db4a0d038513d7a060566bfc51d46d1c014847049a45cce85e8" +dependencies = [ + "sqlx-core", + "sqlx-macros", + "sqlx-mysql", + "sqlx-postgres", + "sqlx-sqlite", +] + +[[package]] +name = "sqlx-core" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d06e2f2bd861719b1f3f0c7dbe1d80c30bf59e76cf019f07d9014ed7eefb8e08" +dependencies = [ + "atoi", + "byteorder", + "bytes", + "crc", + "crossbeam-queue", + "either", + "event-listener", + "futures-channel", + "futures-core", + "futures-intrusive", + "futures-io", + "futures-util", + "hashbrown", + "hashlink", + "hex", + "indexmap", + "log", + "memchr", + "once_cell", + "paste", + "percent-encoding", + "serde", + "serde_json", + "sha2", + "smallvec", + "sqlformat", + "thiserror", + "tracing", + "url", +] + +[[package]] +name = "sqlx-macros" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f998a9defdbd48ed005a89362bd40dd2117502f15294f61c8d47034107dbbdc" +dependencies = [ + "proc-macro2", + "quote", + "sqlx-core", + "sqlx-macros-core", + "syn", +] + +[[package]] +name = "sqlx-macros-core" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d100558134176a2629d46cec0c8891ba0be8910f7896abfdb75ef4ab6f4e7ce" +dependencies = [ + "dotenvy", + "either", + "heck", + "hex", + "once_cell", + "proc-macro2", + "quote", + "serde", + "serde_json", + "sha2", + "sqlx-core", + "sqlx-mysql", + "sqlx-postgres", + "sqlx-sqlite", + "syn", + "tempfile", + "url", +] + +[[package]] +name = "sqlx-mysql" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +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 = "sqlx-postgres" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9734dbce698c67ecf67c442f768a5e90a49b2a4d61a9f1d59f73874bd4cf0710" +dependencies = [ + "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 = "sqlx-sqlite" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a75b419c3c1b1697833dd927bdc4c6545a620bc1bbafabd44e1efbe9afcd337e" +dependencies = [ + "atoi", + "flume", + "futures-channel", + "futures-core", + "futures-executor", + "futures-intrusive", + "futures-util", + "libsqlite3-sys", + "log", + "percent-encoding", + "serde", + "serde_urlencoded", + "sqlx-core", + "tracing", + "url", +] + +[[package]] +name = "stringprep" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b4df3d392d81bd458a8a621b8bffbd2302a12ffe288a9d931670948749463b1" +dependencies = [ + "unicode-bidi", + "unicode-normalization", + "unicode-properties", +] + +[[package]] +name = "subtle" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" + +[[package]] +name = "syn" +version = "2.0.77" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f35bcdf61fd8e7be6caf75f429fdca8beb3ed76584befb503b1569faee373ed" +dependencies = [ + "proc-macro2", + "quote", + "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 = "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 = "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 = "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 = "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 = "wasi" +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 = "whoami" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a44ab49fad634e88f55bf8f9bb3abd2f27d7204172a112c7c9987e01c1c94ea9" +dependencies = [ + "redox_syscall 0.4.1", + "wasite", +] + +[[package]] +name = "windows-sys" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" +dependencies = [ + "windows-targets 0.48.5", +] + +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "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]] +name = "windows-targets" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" +dependencies = [ + "windows_aarch64_gnullvm 0.48.5", + "windows_aarch64_msvc 0.48.5", + "windows_i686_gnu 0.48.5", + "windows_i686_msvc 0.48.5", + "windows_x86_64_gnu 0.48.5", + "windows_x86_64_gnullvm 0.48.5", + "windows_x86_64_msvc 0.48.5", +] + +[[package]] +name = "windows-targets" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +dependencies = [ + "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]] +name = "windows_aarch64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" + +[[package]] +name = "windows_i686_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" + +[[package]] +name = "windows_i686_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" + +[[package]] +name = "windows_x86_64_msvc" +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 = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde" diff --git a/entity-update_derive/Cargo.toml b/entity-update_derive/Cargo.toml new file mode 100644 index 0000000..848e0e4 --- /dev/null +++ b/entity-update_derive/Cargo.toml @@ -0,0 +1,19 @@ +[package] +name = "entity-update_derive" +version = "0.1.6" +edition = "2021" + + +[dependencies] +syn = "2.0" +quote = "1.0" +proc-macro2 = "1.0" +sqlx = { version = "0.8", features = ["macros"] } +futures = "0.3" + +[features] +default = [] +static-validation = [] + +[lib] +proc-macro = true diff --git a/entity-update_derive/License.md b/entity-update_derive/License.md new file mode 100644 index 0000000..8cac5f0 --- /dev/null +++ b/entity-update_derive/License.md @@ -0,0 +1,47 @@ +# Nandie Software Proprietary License + +Copyright (c) 2024 Nandie Software. All rights reserved. + +## License Grant + +Nandie Software grants you a non-exclusive, non-transferable, revocable license to use the Update Macro for SQLx ("the Software") solely for your internal business purposes, subject to the terms and conditions of this license. + +## Restrictions + +You may not: +1. Modify, adapt, alter, translate, or create derivative works of the Software. +2. Reverse engineer, decompile, disassemble, or otherwise attempt to derive the source code of the Software. +3. Remove, alter, or obscure any proprietary notices on the Software. +4. Use the Software for any unlawful purpose. +5. Sublicense, rent, lease, loan, or distribute the Software to any third party. +6. Use the Software to create a competing product. + +## Ownership + +Nandie Software retains all right, title, and interest in and to the Software, including all intellectual property rights therein. + +## Termination + +This license is effective until terminated. Nandie Software may terminate this license at any time if you fail to comply with any term of this license. Upon termination, you must cease all use of the Software and destroy all copies. + +## Warranty Disclaimer + +THE SOFTWARE IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND. NANDIE SOFTWARE DISCLAIMS ALL WARRANTIES, WHETHER EXPRESS, IMPLIED, OR STATUTORY, INCLUDING WITHOUT LIMITATION ANY WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, AND NON-INFRINGEMENT. + +## Limitation of Liability + +IN NO EVENT SHALL NANDIE SOFTWARE BE LIABLE FOR ANY SPECIAL, INCIDENTAL, INDIRECT, OR CONSEQUENTIAL DAMAGES WHATSOEVER ARISING OUT OF OR RELATED TO YOUR USE OR INABILITY TO USE THE SOFTWARE. + +## Governing Law + +This license shall be governed by and construed in accordance with the laws of [Insert Jurisdiction], without regard to its conflict of law provisions. + +## Contact Information + +If you have any questions about this license, please contact: + +Nandie Software +Bryanston, Johannesburg +michael@nandie.com + +By using the Software, you acknowledge that you have read this license, understand it, and agree to be bound by its terms and conditions. \ No newline at end of file diff --git a/entity-update_derive/ReadMe.md b/entity-update_derive/ReadMe.md new file mode 100644 index 0000000..ad057ab --- /dev/null +++ b/entity-update_derive/ReadMe.md @@ -0,0 +1,218 @@ +# Update Macro for SQLx + +## Overview + +This derive macro provides a convenient way to generate update forms and related methods for database entities using SQLx. It's designed to work with MySQL databases and provides flexibility in naming conventions for tables and fields. Additionally, it now supports the use of `id`, `code`, or any custom primary key field through the `primary_key` attribute. + +## Features + +- Generates an update form struct with optional fields +- Creates methods for generating SQL update statements +- Implements a builder pattern for setting update values +- Provides methods for comparing the update form with both the original model and the database record, returning JSON objects +- Allows custom table names and field names through optional attributes +- Supports specifying custom primary keys with the `primary_key` attribute +- Generates a method to get the table name for use in SQL queries +- Includes an `initial_diff` method to get a JSON representation of all fields in the model +- Supports database transactions for atomic operations +- Optimized for better performance with large structs + +## Installation + +Add the following to your `Cargo.toml`: + +```toml +[dependencies] +entity_update_derive = { path = "path/to/entity_update_derive" } +sqlx = { version = "0.8", features = ["runtime-tokio-rustls", "mysql", "json"] } +serde_json = "1.0" +tokio = { version = "1", features = ["full"] } +``` + +## Usage + +### Basic Usage with Default Primary Key (`id` or `code`) + +```rust +use entity_update_derive::Update; +use sqlx::FromRow; + +#[derive(Update, FromRow, Debug)] +struct User { + id: i32, + name: String, + email: Option, +} +``` + +This will generate a `UserUpdateForm` struct and associated methods, with `id` as the primary key. If your struct uses `code` as the primary key, the macro will automatically detect and use it. + +### Specifying a Custom Primary Key + +You can specify a custom primary key field using the `primary_key` attribute: + +```rust +#[derive(Update, FromRow, Debug)] +struct User { + #[primary_key] + code: String, + name: String, + email: Option, +} +``` + +### Custom Table Name + +You can specify a custom table name using the `table_name` attribute: + +```rust +#[derive(Update, FromRow, Debug)] +#[table_name("customers")] +struct User { + id: i32, + name: String, + email: Option, +} +``` + +### Custom Field Names + +You can specify custom field names for the database using the `rename` attribute: + +```rust +#[derive(Update, FromRow, Debug)] +struct User { + id: i32, + #[rename("user_name")] + name: String, + #[rename("user_email")] + email: Option, +} +``` + +### Using the Generated Update Form with Transactions + +```rust +use sqlx::mysql::MySqlPool; +use entity_update_derive::Update; +use sqlx::FromRow; + +#[derive(Update, FromRow, Debug)] +struct User { + id: i32, + name: String, + email: Option, +} + +#[tokio::main] +async fn main() -> Result<(), sqlx::Error> { + let pool = MySqlPool::connect("mysql://username:password@localhost/database_name").await?; + + let form = User::update_form() + .with_name("new_username".to_string()) + .with_email(Some("new_email@example.com".to_string())); + + let user_id = 1; + + let mut tx = pool.begin().await?; + + form.execute_update(&user_id, &mut tx).await?; + let diff = form.transactional_db_diff(&user_id, &mut tx).await?; + println!("Diff: {:?}", diff); + + tx.commit().await?; + + println!("Update completed successfully"); + + Ok(()) +} +``` + +## Generated Methods + +### `update_form()` + +Creates a new instance of the update form. + +```rust +let form = User::update_form(); +``` + +### `with_field_name()` + +Builder method for each field in the struct. + +```rust +let form = User::update_form() + .with_name("New Name") + .with_email(Some("new_email@example.com")); +``` + +### `update_stmt()` + +Generates the SQL SET clause for the update statement. + +```rust +let stmt = form.update_stmt(); +``` + +### `bind_values()` + +Binds the update values to a SQLx query. + +```rust +let query = sqlx::query("UPDATE users SET ..."); +let query = form.bind_values(query); +``` + +### `model_diff()` + +Compares the update form with an instance of the original struct and returns a JSON object. + +```rust +let diff: serde_json::Value = form.model_diff(&original_user); +``` + +### `db_diff()` + +Compares the update form with the current database record and returns a JSON object. + +```rust +let diff: serde_json::Value = form.db_diff(&user_id, &pool).await?; +``` + +### `table_name()` + +Returns the table name as a static string. + +```rust +let table_name = UserUpdateForm::table_name(); +``` + +### `initial_diff()` + +Returns a JSON object containing all fields of the original model and their current values. + +```rust +let user = User { + id: 1, + name: "John Doe".to_string(), + email: Some("john@example.com".to_string()), +}; +let initial_diff: serde_json::Value = user.initial_diff(); +``` + +## Notes + +- This macro now supports custom primary key fields using the `primary_key` attribute. If not specified, it defaults to checking for an `id` or `code` field. +- The macro assumes you're using MySQL. Modifications may be needed for other database types. +- Error handling is minimal in these examples. In a production environment, you should implement proper error handling and logging. +- The `model_diff()`, `db_diff()`, and `initial_diff()` methods return `serde_json::Value` objects, which are easy to work with when dealing with JSON data. + +## Contributing + +Contributions are welcome! Please feel free to submit a Pull Request. + +## License + +This project is licensed under the Nandie Software Proprietary License. See the LICENSE file in the project root for the full license text. diff --git a/entity-update_derive/src/lib.rs b/entity-update_derive/src/lib.rs new file mode 100644 index 0000000..d07eeac --- /dev/null +++ b/entity-update_derive/src/lib.rs @@ -0,0 +1,1031 @@ +mod string_utils; +extern crate proc_macro; + +use proc_macro::TokenStream; +use proc_macro2::{Ident, TokenStream as TokenStream2}; +use quote::{quote, format_ident}; +use syn::{parse_macro_input, DeriveInput, Data, LitStr, Type, ImplGenerics, TypeGenerics, WhereClause}; + +use crate::string_utils::{pluralize, to_snake_case}; + + +struct EntityField { + ident: Ident, + db_name: String, + ty: Type, + needs_type_annotation: bool, + type_override: Option, + is_primary_key: bool, + is_version_field: bool, +} + +/// Parse a string attribute that can be either: +/// - `#[attr("value")]` (Meta::List style) +/// - `#[attr = "value"]` (Meta::NameValue style) +fn parse_string_attr(attr: &syn::Attribute) -> Option { + match &attr.meta { + syn::Meta::List(_) => { + // #[attr("value")] style + attr.parse_args::().ok().map(|lit| lit.value()) + } + syn::Meta::NameValue(nv) => { + // #[attr = "value"] style + if let syn::Expr::Lit(syn::ExprLit { lit: syn::Lit::Str(lit), .. }) = &nv.value { + Some(lit.value()) + } else { + None + } + } + _ => None, + } +} + +// Support both Update and Entity attributes +#[proc_macro_derive(Update, attributes(rename, table_name, primary_key, field_type))] +pub fn derive_update(input: TokenStream) -> TokenStream { + derive_entity_internal(input) +} + +#[proc_macro_derive(Entity, attributes(rename, table_name, primary_key, version, field_type))] +pub fn derive_entity(input: TokenStream) -> TokenStream { + derive_entity_internal(input) +} +fn derive_entity_internal(input: TokenStream) -> TokenStream { + let input = parse_macro_input!(input as DeriveInput); + let name = &input.ident; + let update_form_name = format_ident!("{}UpdateForm", name); + + let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl(); + let table_name = get_table_name(&input); + let fields = parse_fields(&input); + + let primary_key = fields.iter() + .find(|f| f.is_primary_key) + .or_else(|| fields.iter().find(|f| f.ident == "id" || f.ident == "code")) + .expect("Struct must have a primary key field, either explicitly specified or named 'id' or 'code'"); + + let (has_created_at, has_updated_at) = check_timestamp_fields(&fields); + let version_field = fields.iter() + .find(|f| f.is_version_field) + .or_else(|| fields.iter().find(|&f| is_version_field(f))); + + + // Generate all implementations + let insert_impl = generate_insert_impl(&name, &table_name, primary_key, &fields, has_created_at, has_updated_at, &impl_generics, &ty_generics, &where_clause); + let get_impl = generate_get_impl(&name, &table_name, primary_key, version_field, &fields, &impl_generics, &ty_generics, &where_clause); + let update_impl = generate_update_impl(&name, &update_form_name, &table_name, &fields, primary_key, version_field, &impl_generics, &ty_generics, &where_clause); + let diff_impl = generate_diff_impl(&name, &update_form_name, &fields, primary_key, version_field, &impl_generics, &ty_generics, &where_clause); + + let pk_type = &primary_key.ty; + let pk_field_name = &primary_key.ident; + + quote! { + #insert_impl + #get_impl + #update_impl + #diff_impl + + impl #impl_generics #name #ty_generics #where_clause { + pub const fn table_name() -> &'static str { + #table_name + } + + pub fn entity_key(#pk_field_name: &#pk_type) -> String { + format!("/entities/{}/{}", #table_name, #pk_field_name) + } + + pub fn entity_changes_table_name() -> String { + format!("entity_changes_{}", #table_name) + } + } + }.into() +} +fn get_table_name(input: &DeriveInput) -> String { + input.attrs.iter() + .find_map(|attr| { + if attr.path().is_ident("table_name") { + parse_string_attr(attr) + } else { + None + } + }) + .unwrap_or_else(|| to_snake_case(&input.ident.to_string())) +} + +fn parse_fields(input: &DeriveInput) -> Vec { + match &input.data { + Data::Struct(data_struct) => { + data_struct.fields.iter().map(|field| { + let ident = field.ident.as_ref().unwrap().clone(); + let db_name = field.attrs.iter() + .find_map(|attr| { + if attr.path().is_ident("rename") { + parse_string_attr(attr) + } else { + None + } + }) + .unwrap_or_else(|| ident.to_string()); + + let type_override = field.attrs.iter() + .find_map(|attr| { + if attr.path().is_ident("field_type") { + parse_string_attr(attr) + } else { + None + } + }); + + let needs_type_annotation = type_override.is_some() || { + matches!(&field.ty, syn::Type::Path(p) if { + let type_str = quote!(#p).to_string(); + type_str.contains("Uuid") || type_str.contains("bool") + }) + }; + + let is_primary_key = field.attrs.iter() + .any(|attr| attr.path().is_ident("primary_key")); + let is_version_field = field.attrs.iter() + .any(|attr| attr.path().is_ident("version")); + + EntityField { + ident, + db_name, + ty: field.ty.clone(), + needs_type_annotation, + type_override, + is_primary_key, + is_version_field, + } + }).collect() + } + _ => panic!("Entity can only be derived for structs"), + } +} + +fn check_timestamp_fields(fields: &[EntityField]) -> (bool, bool) { + let has_created_at = fields.iter() + .any(|f| f.ident == "created_at" && matches!(&f.ty, Type::Path(p) if p.path.is_ident("i64"))); + + let has_updated_at = fields.iter() + .any(|f| f.ident == "updated_at" && matches!(&f.ty, Type::Path(p) if p.path.is_ident("i64"))); + + (has_created_at, has_updated_at) +} + +fn is_version_field(f: &EntityField) -> bool { + f.ident == "version" && matches!(&f.ty, Type::Path(p) if p.path.is_ident("u64") || + p.path.is_ident("u32") || p.path.is_ident("i64") || p.path.is_ident("i32")) +} + +// Generate the insert implementation +fn generate_insert_impl( + name: &Ident, + table_name: &str, + primary_key: &EntityField, + fields: &[EntityField], + _has_created_at: bool, + _has_updated_at: bool, + impl_generics: &ImplGenerics, + ty_generics: &TypeGenerics, + where_clause: &Option<&WhereClause>, +) -> TokenStream2 { + let db_names: Vec<_> = fields.iter().map(|f| &f.db_name).collect(); + + let insert_stmt = format!( + "INSERT INTO `{}` ({}) VALUES ({})", + table_name, + db_names.iter().map(|s| s.as_str()).collect::>().join(", "), + std::iter::repeat("?").take(db_names.len()).collect::>().join(", ") + ); + + let bindings: Vec<_> = fields.iter().map(|f| { + let ident = &f.ident; + /* match ident.to_string().as_str() { + "created_at" | "updated_at" if has_created_at || has_updated_at => { + quote! { chrono::Utc::now().timestamp_millis() } + } + _ => quote! { &self.#ident } + } */ + quote! { &self.#ident } + }).collect(); + + let pk_field = &primary_key.ident; + let pk_type = &primary_key.ty; + + // Rest of function remains the same + quote! { + impl #impl_generics #name #ty_generics #where_clause { + pub async fn insert<'a, E>(&self, executor: E) -> Result<#pk_type, sqlx::Error> + where + E: sqlx::Executor<'a, Database=sqlx::MySql>, + { + let result = sqlx::query(#insert_stmt) + #(.bind(#bindings))* + .execute(executor) + .await?; + + Ok(self.#pk_field.clone()) + } + } + } +} + +fn get_type_string(field: &EntityField) -> String { + if field.needs_type_annotation { + if let Some(override_type) = &field.type_override { + override_type.clone() + } else { + let ty = &field.ty; + let type_str = quote!(#ty).to_string(); + + // Remove all whitespace + let clean_type = type_str.replace(" ", ""); + + if clean_type.starts_with("Option<") && clean_type.ends_with(">") { + // Extract inner type between < and > + clean_type[7..clean_type.len()-1].to_string() + } else { + type_str + } + } + } else { + String::new() + } +} + +// Generate the get implementations +fn generate_get_impl( + name: &Ident, + table_name: &str, + primary_key: &EntityField, + version_field: Option<&EntityField>, + fields: &[EntityField], + impl_generics: &ImplGenerics, + ty_generics: &TypeGenerics, + where_clause: &Option<&WhereClause>, +) -> TokenStream2 { + let select_fields = fields.iter().map(|f| { + if f.needs_type_annotation { + let type_str = get_type_string(f); + format!("{} as \"{}: {}\"", f.db_name, f.ident, type_str) + } else { + f.db_name.clone() + } + }); + + let pk_field_name = primary_key.ident.to_string(); + let multi_pk_field_name = Some(pluralize(&pk_field_name)) + .filter(|x| x != &pk_field_name) + .unwrap_or_else(|| format!("{}_list", pk_field_name)); + + let get_by_func = format_ident!("get_by_{}", pk_field_name); + let multi_get_by_func = format_ident!("get_by_{}", multi_pk_field_name); + let pk_field = &primary_key.ident; + let pk_type = &primary_key.ty; + let pk_field_name = primary_key.ident.to_string(); + let pk_db_field_name = &primary_key.db_name; + + let select_stmt = format!( + r#"SELECT DISTINCT {} FROM `{}` WHERE {} = ?"#, + select_fields.clone().collect::>().join(", "), + table_name, pk_db_field_name + ); + + let new_fields = select_fields.clone().collect::>(); + let select_fields_str = new_fields.iter() + .filter_map(|e| e.split(" ") + .next()).collect::>().join(", "); + + let select_field_list = select_fields.clone().collect::>(); + // let field_list = fields.iter().map(|f| f.db_name.clone()).collect::>(); + // println!("cargo:warning=select_fields for {} -> : {:?}", name.to_string(), select_fields.clone().collect::>().join(", ")); + // println!("cargo:warning=select_stmt for {} -> : {:?}", name.to_string(), select_stmt); + + // Generate the get_version function if version_field exists + let get_version_impl = if let Some(vfield) = version_field { + let version_field_type = &vfield.ty; + let version_db_name = &vfield.db_name; + + quote! { + pub async fn get_version<'a, E>(executor: E, #pk_field: &#pk_type) -> Result, sqlx::Error> + where + E: sqlx::Executor<'a, Database=sqlx::MySql>, + { + let query = format!( + r#"SELECT DISTINCT {} FROM `{}` WHERE {} = ?"#, + #version_db_name, + Self::table_name(), + #pk_db_field_name + ); + + let result = sqlx::query_scalar(&query) + .bind(#pk_field) + .fetch_optional(executor) + .await?; + Ok(result) + } + + pub async fn get_versions<'a, E>(executor: E, keys: &Vec<#pk_type>) -> Result<::std::collections::HashMap<#pk_type, #version_field_type>, sqlx::Error> + where + E: sqlx::Executor<'a, Database=sqlx::MySql>, + { + if keys.is_empty() { + return Ok(::std::collections::HashMap::new()); + } + use sqlx::Row; + + let placeholders = std::iter::repeat("?").take(keys.len()).collect::>().join(","); + let query = format!( + r#"SELECT DISTINCT {}, {} FROM `{}` WHERE {} IN ({})"#, + #pk_db_field_name, + #version_db_name, + Self::table_name(), + #pk_db_field_name, + placeholders + ); + + let mut query_builder = sqlx::query(&query); + for key in keys { + query_builder = query_builder.bind(key); + } + + let rows = query_builder + .fetch_all(executor) + .await?; + + let mut result = ::std::collections::HashMap::with_capacity(rows.len()); + for row in rows { + let key: #pk_type = row.try_get(0)?; + let version: #version_field_type = row.try_get(1)?; + result.insert(key, version); + } + + Ok(result) + } + } + + + } else { + // If no version field, generate empty implementation + quote! {} + }; + + // Check if static-validation feature is enabled at macro expansion time + let use_static_validation = cfg!(feature = "static-validation"); + + let get_by_impl = if use_static_validation { + quote! { + pub async fn #get_by_func<'a, E>(executor: E, #pk_field: &#pk_type) -> Result, sqlx::Error> + where + E: sqlx::Executor<'a, Database=sqlx::MySql>, + { + let result = sqlx::query_as!( + Self, + #select_stmt, + #pk_field + ) + .fetch_optional(executor) + .await?; + + Ok(result) + } + + pub async fn get_by_primary_key<'a, E>(executor: E, #pk_field: &#pk_type) -> Result, sqlx::Error> + where + E: sqlx::Executor<'a, Database=sqlx::MySql>, + { + let result = sqlx::query_as!( + Self, + #select_stmt, + #pk_field + ) + .fetch_optional(executor) + .await?; + + Ok(result) + } + } + } else { + let field_list = fields.iter().map(|f| f.db_name.clone()).collect::>(); + let select_stmt = format!( + r#"SELECT DISTINCT {} FROM `{}` WHERE {} = ?"#, + field_list.join(","), + table_name, pk_db_field_name + ); + + quote! { + pub async fn #get_by_func<'a, E>(executor: E, #pk_field: &#pk_type) -> Result, sqlx::Error> + where + E: sqlx::Executor<'a, Database=sqlx::MySql>, + { + let result = sqlx::query_as::<_, Self>(#select_stmt) + .bind(#pk_field) + .fetch_optional(executor) + .await?; + + Ok(result) + } + + pub async fn get_by_primary_key<'a, E>(executor: E, #pk_field: &#pk_type) -> Result, sqlx::Error> + where + E: sqlx::Executor<'a, Database=sqlx::MySql>, + { + let result = sqlx::query_as::<_, Self>(#select_stmt) + .bind(#pk_field) + .fetch_optional(executor) + .await?; + + Ok(result) + } + } + }; + + quote! { + + impl #impl_generics #name #ty_generics #where_clause { + + #get_by_impl + + pub const fn primary_key_field() -> &'static str { + #pk_field_name + } + + pub const fn primary_key_db_field() -> &'static str { + #pk_db_field_name + } + + pub fn primary_key(&self) -> &#pk_type { + &self.#pk_field + } + + pub fn select_fields() -> Vec<&'static str> { + vec![#(#select_field_list),*] + } + + pub async fn #multi_get_by_func<'a, E>(executor: E, ids: &[#pk_type]) -> Result, sqlx::Error> + where + E: sqlx::Executor<'a, Database=sqlx::MySql>, + { + if ids.is_empty() { + return Ok(vec![]); + } + + let placeholders = (0..ids.len()) + .map(|_| "?") + .collect::>() + .join(","); + + let query = format!( + r#"SELECT DISTINCT {} FROM `{}` WHERE {} IN ({})"#, + #select_fields_str, + Self::table_name(), + #pk_db_field_name, + placeholders + ); + + let mut q = sqlx::query_as::<_, Self>(&query); + for id in ids { + q = q.bind(id); + } + + q.fetch_all(executor).await + } + + #get_version_impl + + pub async fn find<'a, E>( + executor: E, + conditions: Vec<::entity_changes::prelude::Condition<'a>>, + index: Option<&str>, + ) -> Result, sqlx::Error> + where + E: sqlx::Executor<'a, Database=sqlx::MySql>, + { + Self::find_ordered_with_limit(executor, conditions, index, Vec::new(), None).await + } + pub async fn find_ordered<'a, E>( + executor: E, + conditions: Vec<::entity_changes::prelude::Condition<'a>>, + index: Option<&str>, + order_by: Vec<(&str, bool)>, + ) -> Result, sqlx::Error> + where + E: sqlx::Executor<'a, Database=sqlx::MySql>, + { + Self::find_ordered_with_limit(executor, conditions, index, order_by, None).await + } + + pub async fn find_one<'a, E>( + executor: E, + conditions: Vec<::entity_changes::prelude::Condition<'a>>, + index: Option<&str>, + ) -> Result, sqlx::Error> + where + E: sqlx::Executor<'a, Database=sqlx::MySql>, + { + let found = Self::find_ordered_with_limit(executor, conditions, index, Vec::new(), Some((0, 1))).await?; + Ok(found.into_iter().next()) + } + + pub async fn find_ordered_with_limit<'a, E>( + executor: E, + conditions: Vec<::entity_changes::prelude::Condition<'a>>, + index: Option<&str>, + order_by: Vec<(&str, bool)>, + offset_limit: Option<(u32, u32)>, + ) -> Result, sqlx::Error> + where + E: sqlx::Executor<'a, Database=sqlx::MySql>, + { + use ::entity_changes::prelude::{Condition, Value, bind_as_values}; + + let (where_conditions, values) = Condition::build_where_clause(&conditions); + let where_clause = if !where_conditions.is_empty() { + format!("WHERE {}", where_conditions) + } else { + String::new() + }; + + let index_clause = index + .map(|idx| format!("USE INDEX ({})", idx)) + .unwrap_or_default(); + + //Filter order_by fields to only those managed + let fields = Self::select_fields().into_iter().collect::<::std::collections::HashSet<_>>(); + let order_by = order_by.iter() + .filter(|(field, _)| fields.contains(field)) + .collect::>(); + + let order_by_clause = if !order_by.is_empty() { + let order_by_str = order_by.iter() + .map(|(field, asc)| format!("{} {}", field, if *asc { "ASC" } else { "DESC" })) + .collect::>() + .join(", "); + format!("ORDER BY {}", order_by_str) + } else { + String::new() + }; + + let query = format!( + r#"SELECT DISTINCT {} FROM {} {} {} {} {}"#, + #select_fields_str, + #table_name, + index_clause, + where_clause, + order_by_clause, + offset_limit.map(|(offset, limit)| format!("LIMIT {}, {}", offset, limit)).unwrap_or_default(), + ); + + let mut db_query = sqlx::query_as(&query); + + // Bind values to the query + let results = match bind_as_values(db_query, &values) + .fetch_all(executor) + .await { + Ok(results) => results, + Err(err) => { + tracing::error!(r#"Error executing + Query: {} + Error => {:?} + "#, query, err); + return Err(err); + } + }; + + Ok(results) + } + + pub async fn count<'a, E>( + executor: E, + conditions: Vec<::entity_changes::prelude::Condition<'a>>, + index: Option<&str>, + ) -> Result + where + E: sqlx::Executor<'a, Database=sqlx::MySql>, + { + use ::entity_changes::prelude::{Condition, Value, bind_scalar_values}; + + let (where_conditions, values) = Condition::build_where_clause(&conditions); + let where_clause = if !where_conditions.is_empty() { + format!("WHERE {}", where_conditions) + } else { + String::new() + }; + + let index_clause = index + .map(|idx| format!("USE INDEX ({})", idx)) + .unwrap_or_default(); + + let query = format!( + r#"SELECT CAST(COUNT({}) AS UNSIGNED) FROM {} {} {}"#, + #pk_db_field_name, + #table_name, + index_clause, + where_clause, + ); + + let mut db_query = sqlx::query_scalar::<_, u64>(&query); + + // Bind values to the query + let count = match bind_scalar_values(db_query, &values) + .fetch_optional(executor) + .await { + Ok(count) => count.unwrap_or(0), + Err(err) => { + tracing::error!(r#"Error executing + Query: {} + Error => {:?} + "#, query, err); + return Err(err); + } + }; + + Ok(count) + } + } + } +} +fn generate_update_impl( + name: &Ident, + update_form_name: &Ident, + table_name: &str, + fields: &[EntityField], + primary_key: &EntityField, + version_field: Option<&EntityField>, + impl_generics: &ImplGenerics, + ty_generics: &TypeGenerics, + where_clause: &Option<&WhereClause>, +) -> TokenStream2 { + let update_fields: Vec<_> = fields.iter() + .filter(|f| f.ident != primary_key.ident && f.ident != "created_at" && + version_field.as_ref().map(|vf| f.ident != vf.ident).unwrap_or(true)) + .collect(); + + let field_idents: Vec<_> = update_fields.iter().map(|f| &f.ident).collect(); + let field_types: Vec<_> = update_fields.iter().map(|f| &f.ty).collect(); + let db_names: Vec<_> = update_fields.iter().map(|f| &f.db_name).collect(); + + let setter_methods: Vec<_> = update_fields.iter().map(|field| { + let method_name = format_ident!("set_{}", field.ident); + let field_type = &field.ty; + let field_ident = &field.ident; + quote! { + pub fn #method_name(&mut self, value: T) -> () + where + T: Into<#field_type>, + { + self.#field_ident = Some(value.into()); + } + } + }).collect(); + + let builder_methods = update_fields.iter().map(|field| { + let method_name = format_ident!("with_{}", field.ident); + let field_type = &field.ty; + let field_ident = &field.ident; + quote! { + pub fn #method_name(mut self, value: T) -> Self + where + T: Into<#field_type>, + { + self.#field_ident = Some(value.into()); + self + } + } + }); + + // This code should replace the version_increment code in generate_update_impl + let version_increment = if let Some(vfield) = version_field { + let version_db_name = &vfield.db_name; + let version_type = &vfield.ty; + + // Handle different integer types with appropriate max values for wrapping + match version_type { + Type::Path(type_path) if type_path.path.is_ident("u32") => { + // u32 max: 4,294,967,295 + quote! { + parts.push(format!("{} = IF({} = 4294967295, 0, {} + 1)", #version_db_name, #version_db_name, #version_db_name)); + } + }, + Type::Path(type_path) if type_path.path.is_ident("u64") => { + // u64 max: 18,446,744,073,709,551,615 + quote! { + parts.push(format!("{} = IF({} = 18446744073709551615, 0, {} + 1)", #version_db_name, #version_db_name, #version_db_name)); + } + }, + Type::Path(type_path) if type_path.path.is_ident("i32") => { + // i32 max: 2,147,483,647 + quote! { + parts.push(format!("{} = IF({} = 2147483647, 0, {} + 1)", #version_db_name, #version_db_name, #version_db_name)); + } + }, + Type::Path(type_path) if type_path.path.is_ident("i64") => { + // i64 max: 9,223,372,036,854,775,807 + quote! { + parts.push(format!("{} = IF({} = 9223372036854775807, 0, {} + 1)", #version_db_name, #version_db_name, #version_db_name)); + } + }, + _ => { + // Default increment for any other type + quote! { + parts.push(format!("{} = {} + 1", #version_db_name, #version_db_name)); + } + } + } + } else { + quote! {} + }; + + quote! { + #[derive(Default)] + pub struct #update_form_name #ty_generics #where_clause { + #(pub #field_idents: Option<#field_types>,)* + } + + impl #impl_generics #name #ty_generics #where_clause { + pub fn update_form() -> #update_form_name { + #update_form_name::default() + } + } + + impl #impl_generics #update_form_name #ty_generics #where_clause { + pub fn new() -> Self { + Self::default() + } + + #(#setter_methods)* + + #(#builder_methods)* + + pub fn update_stmt(&self) -> String { + let mut parts = Vec::new(); + #( + if self.#field_idents.is_some() { + parts.push(format!("{} = ?", #db_names)); + } + )* + + #version_increment + + parts.join(", ") + } + + pub fn bind_form_values<'q>(&'q self, mut query: sqlx::query::Query<'q, sqlx::MySql, sqlx::mysql::MySqlArguments>) + -> sqlx::query::Query<'q, sqlx::MySql, sqlx::mysql::MySqlArguments> + { + #( + if let Some(ref value) = self.#field_idents { + query = query.bind(value); + } + )* + query + } + + pub const fn table_name() -> &'static str { + #table_name + } + } + } +} + +fn generate_diff_impl( + name: &Ident, + update_form_name: &Ident, + fields: &[EntityField], + primary_key: &EntityField, + version_field: Option<&EntityField>, + impl_generics: &ImplGenerics, + ty_generics: &TypeGenerics, + where_clause: &Option<&WhereClause>, +) -> TokenStream2 { + let update_fields: Vec<_> = fields.iter() + .filter(|f| f.ident != primary_key.ident && f.ident != "created_at" && + version_field.as_ref().map(|vf| f.ident != vf.ident).unwrap_or(true)) + .collect(); + + let field_idents: Vec<_> = update_fields.iter().map(|f| &f.ident).collect(); + let db_names: Vec<_> = update_fields.iter().map(|f| &f.db_name).collect(); + let field_types: Vec<_> = update_fields.iter().map(|f| &f.ty).collect(); + let bind_form_values_trait = format_ident!("{}BindFormValues", name); + let bind_form_values_func = format_ident!("{}", to_snake_case(&name.to_string())); + + let pk_field = primary_key.ident.clone(); + let pk_type = primary_key.ty.clone(); + let pk_db_name = primary_key.db_name.clone(); + + let multi_pk_field= format_ident!("{}", pluralize(&pk_field.to_string())); + let update_by_func = format_ident!("update_by_{}", pk_field); + let multi_update_by_func = format_ident!("update_by_{}", multi_pk_field); + + quote! { + impl #impl_generics #update_form_name #ty_generics #where_clause { + pub fn model_diff(&self, other: &#name #ty_generics) -> serde_json::Value { + let mut changes = serde_json::Map::new(); + #( + if let Some(ref value) = &self.#field_idents { + if &other.#field_idents != value { + changes.insert(#db_names.to_string(), serde_json::json!(value)); + } + } + )* + serde_json::Value::Object(changes) + } + + pub fn diff_modify(&mut self, model: &#name #ty_generics) -> serde_json::Value { + let mut changes = serde_json::Map::new(); + #( + if let Some(ref value) = self.#field_idents { + if &model.#field_idents == value { + self.#field_idents = None; + } else { + changes.insert(#db_names.to_string(), serde_json::json!(value)); + } + } + )* + serde_json::Value::Object(changes) + } + + pub async fn db_diff<'q>(&self, #pk_field: &#pk_type, executor: impl sqlx::MySqlExecutor<'q>) -> Result { + use sqlx::Row; + let fields_to_fetch: Vec = vec![ + #( + if self.#field_idents.is_some() { + #db_names.to_string() + } else { + String::new() + } + ),* + ].into_iter().filter(|s| !s.is_empty()).collect(); + + if fields_to_fetch.is_empty() { + return Ok(serde_json::json!({})); + } + + let query = format!( + "SELECT DISTINCT {} FROM {} WHERE {} = ?", + fields_to_fetch.join(", "), + Self::table_name(), #pk_db_name, + ); + + let row = sqlx::query(&query) + .bind(#pk_field) + .fetch_one(executor) + .await?; + + let mut changes = serde_json::Map::new(); + #( + if let Some(value) = &self.#field_idents { + if fields_to_fetch.contains(&#db_names.to_string()) { + let db_value: #field_types = row.try_get(#db_names)?; + if *value != db_value { + changes.insert(#db_names.to_string(), serde_json::json!(value)); + } + } + } + )* + Ok(serde_json::Value::Object(changes)) + } + } + + impl #impl_generics #name #ty_generics #where_clause { + pub fn to_update_form(&self) -> #update_form_name #ty_generics { + #update_form_name { + #(#field_idents: Some(self.#field_idents.clone()),)* + } + } + + pub fn initial_diff(&self) -> serde_json::Value { + let mut map = serde_json::Map::new(); + #( + map.insert(#db_names.to_string(), serde_json::to_value(&self.#field_idents).unwrap_or(serde_json::Value::Null)); + )* + serde_json::Value::Object(map) + } + } + + + pub trait #bind_form_values_trait<'q, 'f> + where 'f: 'q { + fn #bind_form_values_func(self, form: &'f #update_form_name) -> Self; + } + + impl<'q, 'f> #bind_form_values_trait<'q, 'f> for sqlx::query::Query<'q, sqlx::MySql, sqlx::mysql::MySqlArguments> + where 'f: 'q { + fn #bind_form_values_func(self, form: &'f #update_form_name) -> Self { + form.bind_form_values(self) + } + } + + impl #impl_generics #name #ty_generics #where_clause { + pub async fn update<'a, E>(&self, executor: E, form: #update_form_name) -> Result<(), sqlx::Error> + where + E: sqlx::Executor<'a, Database=sqlx::MySql>, + { + let query_str = format!( + r#" + UPDATE `{}` + SET {} + WHERE {} = ? + "#, + Self::table_name(), + form.update_stmt(), + #pk_db_name + ); + + let _q = match sqlx::query(&query_str) + .#bind_form_values_func(&form) + .bind(&self.#pk_field) + .execute(executor) + .await { + Ok(q) => q, + Err(err) => { + tracing::error!(r#"Error updating entity: {:?} + Query String: {} + "#, err, query_str); + return Err(err); + } + }; + + Ok(()) + } + + pub async fn #update_by_func<'a, E>(executor: E, #pk_field: &#pk_type, form: #update_form_name) -> Result<(), sqlx::Error> + where + E: sqlx::Executor<'a, Database=sqlx::MySql>, + { + let query_str = format!( + r#" + UPDATE `{}` + SET {} + WHERE {} = ? + "#, + Self::table_name(), + form.update_stmt(), + #pk_db_name + ); + + let _q = match sqlx::query(&query_str) + .#bind_form_values_func(&form) + .bind(#pk_field) + .execute(executor) + .await { + Ok(q) => q, + Err(err) => { + tracing::error!(r#"Error updating entity: {:?} + Query String: {} + "#, err, query_str); + return Err(err); + } + }; + + Ok(()) + } + + pub async fn #multi_update_by_func<'a, E>(executor: E, #multi_pk_field: &Vec<#pk_type>, form: #update_form_name) -> Result<(), sqlx::Error> + where + E: sqlx::Executor<'a, Database=sqlx::MySql>, + { + if #multi_pk_field.is_empty() { + return Ok(()); + } + + let placeholders = (0..#multi_pk_field.len()) + .map(|_| "?") + .collect::>() + .join(","); + + let query_str = format!( + r#" + UPDATE `{}` + SET {} + WHERE {} IN ({}) + "#, + Self::table_name(), + form.update_stmt(), + #pk_db_name, placeholders, + ); + + let mut q = sqlx::query(&query_str) + .#bind_form_values_func(&form); + + for #pk_field in #multi_pk_field { + q = q.bind(#pk_field); + } + + match q.execute(executor) + .await { + Ok(_q) => (), + Err(err) => { + tracing::error!(r#"Error updating entity: {:?} + Query String: {} + "#, err, query_str); + return Err(err); + } + }; + + Ok(()) + } + } + } +} \ No newline at end of file diff --git a/entity-update_derive/src/string_utils.rs b/entity-update_derive/src/string_utils.rs new file mode 100644 index 0000000..78f4257 --- /dev/null +++ b/entity-update_derive/src/string_utils.rs @@ -0,0 +1,158 @@ +pub(crate) fn to_snake_case(name: &str) -> String { + let mut result = String::with_capacity(name.len() + 4); + let mut chars = name.chars().peekable(); + + while let Some(current) = chars.next() { + if let Some(_next) = chars.peek() { + if current.is_uppercase() { + if !result.is_empty() { + result.push('_'); + } + result.push(current.to_lowercase().next().unwrap()); + } else { + result.push(current); + } + } else { + // Last character + result.push(current.to_lowercase().next().unwrap()); + } + } + + pluralize(&result) +} +pub(crate) fn pluralize(word: &str) -> String { + if word.is_empty() { + return String::new(); + } + + // Handle possessives and existing plurals + if word.ends_with("'s") || word.ends_with("'") || + word.ends_with("s's") || word.ends_with("s'") { + return word.to_string(); + } + + // Compound words (handle last word) + if word.contains(" of ") { + let parts: Vec<&str> = word.split(" of ").collect(); + return format!("{} of {}", pluralize(parts[0]), parts[1]); + } + + // Compound words with hyphens + if word.contains('-') { + let parts: Vec<&str> = word.split('-').collect(); + return format!("{}-{}", + pluralize(parts[0]), + parts[1..].join("-") + ); + } + + // Invariant words (same singular and plural) + match word.to_lowercase().as_str() { + "sheep" | "deer" | "moose" | "swine" | "buffalo" | "fish" | "trout" | + "salmon" | "pike" | "aircraft" | "series" | "species" | "means" | + "crossroads" | "swiss" | "portuguese" | "vietnamese" | "japanese" | + "chinese" | "chassis" | "corps" | "headquarters" | "diabetes" | + "news" | "odds" | "innings" => return word.to_string(), + _ => {} + } + + // Irregular plurals + match word.to_lowercase().as_str() { + // People + "person" => "people", + "man" => "men", + "woman" => "women", + "child" => "children", + "tooth" => "teeth", + "foot" => "feet", + "mouse" => "mice", + "louse" => "lice", + "goose" => "geese", + "ox" => "oxen", + + // Latin/Greek endings + "alumnus" => "alumni", + "alga" => "algae", + "larva" => "larvae", + "vertex" => "vertices", + "index" => "indices", + "matrix" => "matrices", + "criterion" => "criteria", + "phenomenon" => "phenomena", + "datum" => "data", + "medium" => "media", + "analysis" => "analyses", + "thesis" => "theses", + "crisis" => "crises", + "appendix" => "appendices", + "stimulus" => "stimuli", + "radius" => "radii", + "axis" => "axes", + "hypothesis" => "hypotheses", + "basis" => "bases", + "diagnosis" => "diagnoses", + "formula" => "formulae", + "fungus" => "fungi", + "nucleus" => "nuclei", + "syllabus" => "syllabi", + "focus" => "foci", + "cactus" => "cacti", + "bacterium" => "bacteria", + "curriculum" => "curricula", + "memorandum" => "memoranda", + "millennium" => "millennia", + + _ => return apply_general_rules(word), + }.to_string() +} + +fn apply_general_rules(word: &str) -> String { + // Words ending in -o + if word.ends_with('o') { + match word.to_lowercase().as_str() { + // -o → -oes + w if matches!(w, "hero" | "potato" | "tomato" | "echo" | + "tornado" | "torpedo" | "veto" | "mosquito" | + "volcano" | "buffalo" | "domino" | "embargo") => { + return format!("{}es", word); + } + // -o → -os + _ => return format!("{}s", word), + } + } + + // Words ending in -f or -fe + if word.ends_with('f') { + match word.to_lowercase().as_str() { + w if matches!(w, "roof" | "belief" | "chief" | "reef") => { + return format!("{}s", word); + } + _ => return format!("{}ves", &word[..word.len() - 1]), + } + } + if word.ends_with("fe") { + return format!("{}ves", &word[..word.len() - 2]); + } + + // Words ending in -y + if word.ends_with('y') { + let chars: Vec = word.chars().collect(); + if chars.len() > 1 { + let before_y = chars[chars.len() - 2]; + if !matches!(before_y, 'a' | 'e' | 'i' | 'o' | 'u') { + return format!("{}ies", &word[..word.len() - 1]); + } + } + return format!("{}s", word); + } + + // Words ending in sibilants (-s, -ss, -sh, -ch, -x, -z) + if word.ends_with('s') || word.ends_with("ss") || + word.ends_with("sh") || word.ends_with("ch") || + word.ends_with('x') || word.ends_with('z') { + return format!("{}es", word); + } + + // Default case: add 's' + format!("{}s", word) +} \ No newline at end of file diff --git a/scripts/tag-version.sh b/scripts/tag-version.sh new file mode 100755 index 0000000..7bf9228 --- /dev/null +++ b/scripts/tag-version.sh @@ -0,0 +1,18 @@ +#!/bin/bash + +# Fetch the version from Cargo.toml +version=$(grep '^version =' Cargo.toml | sed -E 's/version = "(.*)"/\1/') + +# Check if the version is retrieved +if [ -z "$version" ]; then + echo "Version not found in Cargo.toml" + exit 1 +fi + +# Create a git tag with the version +git tag -a "v$version" -m "Version $version" + +# Push the tag to the remote repository +git push origin "v$version" + +echo "Tagged the current commit with version v$version" \ No newline at end of file diff --git a/src/condition.rs b/src/condition.rs new file mode 100644 index 0000000..ba56699 --- /dev/null +++ b/src/condition.rs @@ -0,0 +1,176 @@ +use crate::prelude::Value; + +#[derive(Clone, Debug)] +pub enum Condition<'a> { + Equal(&'a str, Value), + NotEqual(&'a str, Value), + GreaterThan(&'a str, Value), + GreaterThanOrEqual(&'a str, Value), + LessThan(&'a str, Value), + LessThanOrEqual(&'a str, Value), + Like(&'a str, Value), + ILike(&'a str, Value), + NotLike(&'a str, Value), + In(&'a str, Vec), + NotIn(&'a str, Vec), + IsNull(&'a str), + IsNotNull(&'a str), + And(Vec>), + Or(Vec>), +} + +impl<'a, T: Into> From<(&'a str, T)> for Condition<'a> { + fn from((field, value): (&'a str, T)) -> Self { + Condition::Equal(field, value.into()) + } +} + +#[macro_export] +macro_rules! condition_or { + ($x:expr) => ( + $crate::prelude::Condition::Or(vec![<$crate::prelude::Condition<'_>>::from($x)]) + ); + ($($x:expr),+ $(,)?) => ( + $crate::prelude::Condition::Or(vec![$(<$crate::prelude::Condition<'_>>::from($x)),+]) + ); +} + +#[macro_export] +macro_rules! condition_and { + ($x:expr) => ( + $crate::prelude::Condition::And(vec![<$crate::prelude::Condition<'_>>::from($x)]) + ); + ($($x:expr),+ $(,)?) => ( + $crate::prelude::Condition::And(vec![$(<$crate::prelude::Condition<'_>>::from($x)),+]) + ); +} + +#[macro_export] +macro_rules! conditions { + () => { + vec![] + }; + ($x:expr) => { + vec![<$crate::prelude::Condition<'_>>::from($x)] + }; + ($($x:expr),+ $(,)?) => { + vec![$(<$crate::prelude::Condition<'_>>::from($x)),+] + }; +} + +pub trait ConditionOps { + type Output; + fn gt(self, rhs: Rhs) -> Self::Output; + fn ge(self, rhs: Rhs) -> Self::Output; + fn lt(self, rhs: Rhs) -> Self::Output; + fn le(self, rhs: Rhs) -> Self::Output; + fn eq(self, rhs: Rhs) -> Self::Output; + fn ne(self, rhs: Rhs) -> Self::Output; +} + +// For types that can be converted into Value +impl, V: Into> ConditionOps for T { + type Output = Condition<'static>; + + fn gt(self, rhs: V) -> Self::Output { + Condition::GreaterThan(self.into(), rhs.into()) + } + + fn ge(self, rhs: V) -> Self::Output { + Condition::GreaterThanOrEqual(self.into(), rhs.into()) + } + + fn lt(self, rhs: V) -> Self::Output { + Condition::LessThan(self.into(), rhs.into()) + } + + fn le(self, rhs: V) -> Self::Output { + Condition::LessThanOrEqual(self.into(), rhs.into()) + } + + fn eq(self, rhs: V) -> Self::Output { + Condition::Equal(self.into(), rhs.into()) + } + + fn ne(self, rhs: V) -> Self::Output { + Condition::NotEqual(self.into(), rhs.into()) + } +} + + +impl Condition<'_> { + pub fn build_where_clause(conditions: &[Condition]) -> (String, Vec) { + let mut values = Vec::new(); + let conditions: Vec = conditions + .iter() + .map(|condition| { + match condition { + Condition::Equal(field, value) => { + values.push(value.clone()); + format!("{} = ?", field) + } + Condition::NotEqual(field, value) => { + values.push(value.clone()); + format!("{} != ?", field) + } + Condition::GreaterThan(field, value) => { + values.push(value.clone()); + format!("{} > ?", field) + } + Condition::GreaterThanOrEqual(field, value) => { + values.push(value.clone()); + format!("{} >= ?", field) + } + Condition::LessThan(field, value) => { + values.push(value.clone()); + format!("{} < ?", field) + } + Condition::LessThanOrEqual(field, value) => { + values.push(value.clone()); + format!("{} <= ?", field) + } + Condition::Like(field, value) => { + values.push(value.clone()); + format!("{} LIKE ?", field) + } + Condition::ILike(field, value) => { + values.push(value.clone()); + format!("{} ILIKE ?", field) + } + Condition::NotLike(field, value) => { + values.push(value.clone()); + format!("{} NOT LIKE ?", field) + } + Condition::In(field, value_vec) => { + let placeholders = vec!["?"; value_vec.len()].join(", "); + values.extend(value_vec.clone()); + format!("{} IN ({})", field, placeholders) + } + Condition::NotIn(field, value_vec) => { + let placeholders = vec!["?"; value_vec.len()].join(", "); + values.extend(value_vec.clone()); + format!("{} NOT IN ({})", field, placeholders) + } + Condition::IsNull(field) => { + format!("{} IS NULL", field) + } + Condition::IsNotNull(field) => { + format!("{} IS NOT NULL", field) + } + Condition::And(nested_conditions) => { + let (nested_clause, nested_values) = Self::build_where_clause(nested_conditions); + values.extend(nested_values); + format!("({})", nested_clause) + } + Condition::Or(nested_conditions) => { + let (nested_clause, nested_values) = Self::build_where_clause(nested_conditions); + values.extend(nested_values); + format!("({})", nested_clause.split(" AND ").collect::>().join(" OR ")) + } + } + }) + .collect(); + + (conditions.join(" AND "), values) + } +} \ No newline at end of file diff --git a/src/helpers.rs b/src/helpers.rs new file mode 100644 index 0000000..ceab0c2 --- /dev/null +++ b/src/helpers.rs @@ -0,0 +1,29 @@ +#[macro_export] +macro_rules! update_entity_func { + ($form_type:ident, $func_name:ident) => { // Changed $form_type:ty to $form_type:ident + pub async fn $func_name<'a, E>(executor: E, id: &Uuid, form: $form_type) -> Result<(), RepositoryError> + where + E: sqlx::Executor<'a, Database=sqlx::MySql>, + { + //If the section exists, we update it + let result = sqlx::query( + format!(r#" + UPDATE {} + SET {} + WHERE id = ? + "#, + $form_type::table_name(), + form.update_stmt()).as_str()) + .bind_form_values(form) + .bind(id) + .execute(executor) + .await; + + if let Err(err) = result { + tracing::error!("Error updating entity: {:?}", err); + return Err(RepositoryError::from(err)); + } + Ok(()) + } + }; +} \ No newline at end of file diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..5f3af95 --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,20 @@ +pub mod models; +pub mod repositories; +mod helpers; +mod value; +mod condition; + +// Re-export the entity_update_derive module on feature flag +#[cfg(feature = "derive")] +pub use entity_update_derive::{Entity, Update}; + +pub mod prelude { + pub use crate::value::*; + pub use crate::condition::*; + pub use crate::{condition_or, condition_and, conditions, update_entity_func}; + pub use crate::{condition_or as or, condition_and as and}; + pub use crate::values; + + #[cfg(feature = "derive")] + pub use entity_update_derive::{Entity, Update}; +} \ No newline at end of file diff --git a/src/models.rs b/src/models.rs new file mode 100644 index 0000000..42a0c9d --- /dev/null +++ b/src/models.rs @@ -0,0 +1,53 @@ +use std::fmt::Display; +use sqlx::FromRow; +use uuid::Uuid; +use serde_json::Value; + +#[derive(Debug, FromRow)] +pub struct EntityChange { + pub id: Uuid, + pub entity_id: Uuid, + pub action: String, + pub changed_at: i64, + pub actor_id: Uuid, + pub session_id: Uuid, + pub change_set_id: Uuid, + pub new_value: Option, +} + +#[derive(Debug)] +pub enum Action { + Insert, + Update, + Delete, + Restore, + HardDelete, + Unknown(String), +} + +impl From for Action { + fn from(value: String) -> Self { + match &value.to_lowercase()[..] { + "insert" => Action::Insert, + "update" => Action::Update, + "delete" => Action::Delete, + "restore" => Action::Restore, + "hard-delete" => Action::HardDelete, + _ => Action::Unknown(value), + } + } +} + +impl Display for Action { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let str = match self { + Action::Insert => "insert".to_string(), + Action::Update => "update".to_string(), + Action::Delete => "delete".to_string(), + Action::Restore => "restore".to_string(), + Action::HardDelete => "hard-delete".to_string(), + Action::Unknown(value) => value.to_string(), + }; + write!(f, "{}", str) + } +} \ No newline at end of file diff --git a/src/repositories.rs b/src/repositories.rs new file mode 100644 index 0000000..3040cfc --- /dev/null +++ b/src/repositories.rs @@ -0,0 +1,176 @@ +use sqlx::{Error, MySqlExecutor}; +use uuid::Uuid; +use crate::models::EntityChange; + + +pub async fn create_entity_change<'q>( + conn: impl MySqlExecutor<'q>, + table_name: &str, + change: &EntityChange, +) -> Result<(), sqlx::Error> { + let query = format!( + r#"INSERT INTO `{}` ( + id, entity_id, action, changed_at, actor_id, + session_id, change_set_id, new_value) + VALUES (?, ?, ?, ?, ?, ?, ?, ?)"#, + table_name); + + sqlx::query(&query) + .bind(&change.id) + .bind(&change.entity_id) + .bind(&change.action) + .bind(&change.changed_at) + .bind(&change.actor_id) + .bind(&change.session_id) + .bind(&change.change_set_id) + .bind(&change.new_value) + .execute(conn) + .await?; + + Ok(()) +} + +pub async fn get_entity_changes_by_id<'q>( + conn: impl MySqlExecutor<'q>, + table_name: &str, id: &Uuid) -> Result, Error> { + let query = format!( + r#"SELECT + id, + entity_id, + action, + changed_at, + actor_id, + session_id, + change_set_id, + new_value + FROM `{}` WHERE id = ?"#, + table_name); + + let changes = sqlx::query_as::<_, EntityChange>(&query) + .bind(id) + .fetch_all(conn) + .await?; + + Ok(changes) +} + +pub async fn get_entity_changes_by_entity<'q>( + conn: impl MySqlExecutor<'q>, + table_name: &str, entity_id: &Uuid) -> Result, Error> { + let query = format!( + r#"SELECT + id, + entity_id, + action, + changed_at, + actor_id, + session_id, + change_set_id, + new_value + FROM `{}` WHERE entity_id = ?"#, + table_name); + + let changes = sqlx::query_as::<_, EntityChange>(&query) + .bind(entity_id) + .fetch_all(conn) + .await?; + + Ok(changes) +} + +pub async fn get_entity_changes_session<'q>( + conn: impl MySqlExecutor<'q>, + table_name: &str, session_id: &Uuid, +) -> Result, Error>{ + let query = format!( + r#"SELECT + id, + entity_id, + action, + changed_at, + actor_id, + session_id, + change_set_id, + new_value + FROM `{}` WHERE session_id = ?"#, + table_name); + + let changes = sqlx::query_as::<_, EntityChange>(&query) + .bind(session_id) + .fetch_all(conn) + .await?; + + Ok(changes) +} + +pub async fn get_entity_changes_actor<'q>( + conn: impl MySqlExecutor<'q>, + table_name: &str, actor_id: &Uuid) -> Result, Error>{ + let query = format!( + r#"SELECT + id, + entity_id, + action, + changed_at, + actor_id, + session_id, + change_set_id, + new_value + FROM `{}` WHERE actor_id = ?"#, + table_name); + + let changes = sqlx::query_as::<_, EntityChange>(&query) + .bind(actor_id) + .fetch_all(conn) + .await?; + + Ok(changes) +} + +pub async fn get_entity_changes_by_change_set<'q>( + conn: impl MySqlExecutor<'q>, table_name: &str, change_set_id: &Uuid) -> Result, Error> +{ + let query = format!( + r#"SELECT + id, + entity_id, + action, + changed_at, + actor_id, + session_id, + change_set_id, + new_value + FROM `{}` WHERE change_set_id = ?"#, + table_name); + + let changes = sqlx::query_as::<_, EntityChange>(&query) + .bind(change_set_id) + .fetch_all(conn) + .await?; + + Ok(changes) +} + +pub async fn get_entity_changes_by_entity_and_actor<'q>( + conn: impl MySqlExecutor<'q>, table_name: &str, entity_id: &Uuid, actor_id: &Uuid) -> Result, Error>{ + let query = format!( + r#"SELECT + id, + entity_id, + action, + changed_at, + actor_id, + session_id, + change_set_id, + new_value + FROM `{}` WHERE entity_id = ? AND actor_id = ?"#, + table_name); + + let changes = sqlx::query_as::<_, EntityChange>(&query) + .bind(entity_id) + .bind(actor_id) + .fetch_all(conn) + .await?; + + Ok(changes) +} \ No newline at end of file diff --git a/src/value.rs b/src/value.rs new file mode 100644 index 0000000..5621405 --- /dev/null +++ b/src/value.rs @@ -0,0 +1,208 @@ +use sqlx::MySql; +use sqlx::mysql::MySqlArguments; +use sqlx::query::{Query, QueryAs, QueryScalar}; +use sqlx::types::chrono::{NaiveDate, NaiveDateTime}; + +pub type DB = MySql; +#[derive(Clone, Debug)] +pub enum Value { + Int8(i8), + Uint8(u8), + Int16(i16), + Uint16(u16), + Int32(i32), + Uint32(u32), + Int64(i64), + Uint64(u64), + VecU8(Vec), + String(String), + Bool(bool), + Uuid(uuid::Uuid), + NaiveDate(NaiveDate), + NaiveDateTime(NaiveDateTime), +} + +pub enum Updater<'a> { + Set(&'a str, Value), + Increment(&'a str, Value), + Decrement(&'a str, Value), +} +#[deprecated(since = "0.1.0", note = "Please use Value instead")] +pub type SqlValue = Value; + +macro_rules! bind_value { + ($query:expr, $value: expr) => {{ + let query = match $value { + Value::Int8(v) => $query.bind(v), + Value::Uint8(v) => $query.bind(v), + Value::Int16(v) => $query.bind(v), + Value::Uint16(v) => $query.bind(v), + Value::Int32(v) => $query.bind(v), + Value::Uint32(v) => $query.bind(v), + Value::Int64(v) => $query.bind(v), + Value::Uint64(v) => $query.bind(v), + Value::VecU8(v) => $query.bind(v), + Value::String(v) => $query.bind(v), + Value::Bool(v) => $query.bind(v), + Value::Uuid(v) => $query.bind(v), + Value::NaiveDate(v) => $query.bind(v), + Value::NaiveDateTime(v) => $query.bind(v), + }; + query + }}; +} + +pub fn bind_values<'q>(query: Query<'q, DB, MySqlArguments>, values: &'q [Value]) -> Query<'q, DB, MySqlArguments> { + let mut query = query; + for value in values { + query = bind_value!(query, value); + } + query +} + + +pub fn bind_as_values<'q, O>(query: QueryAs<'q, DB, O, MySqlArguments>, values: &'q [Value]) -> QueryAs<'q, DB, O, MySqlArguments> { + values.into_iter().fold(query, |query, value| { + bind_value!(query, value) + }) +} + +pub fn bind_scalar_values<'q, O>(query: QueryScalar<'q, DB, O, MySqlArguments>, values: &'q [Value]) -> QueryScalar<'q, DB, O, MySqlArguments> { + let mut query = query; + for value in values { + query = bind_value!(query, value); + } + query +} + +#[inline] +pub fn query_fields(fields: Vec<&str>) -> String { + fields.iter().filter_map(|e| e.split(" ").next()) + .collect::>().join(", ") +} + +impl From for Value { + fn from(value: String) -> Self { + Value::String(value) + } +} + +impl From for Value { + fn from(value: i32) -> Self { + Value::Int32(value) + } +} + +impl From for Value { + fn from(value: i64) -> Self { + Value::Int64(value) + } +} + +impl From for Value { + fn from(value: bool) -> Self { + Value::Bool(value) + } +} + +impl From for Value { + fn from(value: uuid::Uuid) -> Self { + Value::Uuid(value) + } +} + +impl From<&str> for Value { + fn from(value: &str) -> Self { + Value::String(value.to_string()) + } +} + +impl From<&i32> for Value { + fn from(value: &i32) -> Self { + Value::Int32(*value) + } +} + +impl From<&i64> for Value { + fn from(value: &i64) -> Self { + Value::Int64(*value) + } +} + +impl From<&bool> for Value { + fn from(value: &bool) -> Self { + Value::Bool(*value) + } +} + +impl From<&uuid::Uuid> for Value { + fn from(value: &uuid::Uuid) -> Self { + Value::Uuid(*value) + } +} + +impl From for Value { + fn from(value: NaiveDate) -> Self { + Value::NaiveDate(value) + } +} + +impl From for Value { + fn from(value: NaiveDateTime) -> Self { + Value::NaiveDateTime(value) + } +} + +pub trait BindValues<'q> { + type Output; + + fn bind_values(self, values: &'q [Value]) -> Self::Output; +} + +impl<'q> BindValues<'q> for Query<'q, DB, MySqlArguments> { + type Output = Query<'q, DB, MySqlArguments>; + + fn bind_values(self, values: &'q [Value]) -> Self::Output { + let mut query = self; + for value in values { + query = bind_value!(query, value); + } + query + } +} + +impl<'q, O> BindValues<'q> for QueryAs<'q, DB, O, MySqlArguments> { + type Output = QueryAs<'q, DB, O, MySqlArguments>; + + fn bind_values(self, values: &'q [Value]) -> Self::Output { + values.into_iter().fold(self, |query, value| { + bind_value!(query, value) + }) + } +} + +impl<'q, O> BindValues<'q> for QueryScalar<'q, DB, O, MySqlArguments> { + type Output = QueryScalar<'q, DB, O, MySqlArguments>; + + fn bind_values(self, values: &'q [Value]) -> Self::Output { + let mut query = self; + for value in values { + query = bind_value!(query, value); + } + query + } +} + + +#[macro_export] +macro_rules! values { + () => { + vec![] + }; + ($x:expr) => { + vec![<$crate::prelude::Value>::from($x)] + }; + ($($x:expr),+ $(,)?) => { + vec![$(<$crate::prelude::Value>::from($x)),+] + }; +}