From 7cda7692cf51aa952f0188277e9a511f61facace Mon Sep 17 00:00:00 2001 From: Goran Brkuljan Date: Thu, 19 Dec 2024 22:20:46 +0100 Subject: [PATCH 1/8] tests: initialization --- .github/workflows/build.yml | 54 +++++ Cargo.lock | 207 ++++-------------- charybdis-macros/Cargo.toml | 1 + charybdis-macros/src/model/consts/mod.rs | 1 - charybdis-macros/src/native/delete.rs | 33 ++- charybdis-macros/src/native/mod.rs | 1 - charybdis-macros/src/rules/delete.rs | 2 +- charybdis-macros/src/rules/find.rs | 2 +- charybdis-macros/src/rules/mod.rs | 1 - charybdis-macros/src/rules/partial.rs | 98 ++------- charybdis-macros/src/rules/update.rs | 2 +- .../src/traits/fields/hash_map.rs | 1 - charybdis-migrate/src/model/data.rs | 2 +- charybdis/Cargo.toml | 3 + charybdis/src/model.rs | 22 +- charybdis/src/operations.rs | 1 - charybdis/tests/integrations/common.rs | 21 ++ charybdis/tests/integrations/main.rs | 3 + charybdis/tests/integrations/models.rs | 33 +++ charybdis/tests/integrations/query.rs | 30 +++ test/docker-compose.yml | 79 +++++++ 21 files changed, 324 insertions(+), 273 deletions(-) create mode 100644 .github/workflows/build.yml create mode 100644 charybdis/tests/integrations/common.rs create mode 100644 charybdis/tests/integrations/main.rs create mode 100644 charybdis/tests/integrations/models.rs create mode 100644 charybdis/tests/integrations/query.rs create mode 100644 test/docker-compose.yml diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 0000000..c97099e --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,54 @@ +name: Test and Build +on: + push: + branches: + - main + pull_request: + branches: + - main +env: + FORCE_COLOR: 1 + CARGO_TERM_COLOR: always + RUST_BACKTRACE: full +jobs: + build: + runs-on: ubuntu-latest + timeout-minutes: 60 + steps: + - name: Checkout code + uses: actions/checkout@v4 + - name: Setup 3-node Scylla cluster + run: | + sudo sh -c "echo 2097152 >> /proc/sys/fs/aio-max-nr" + docker compose -f test/docker-compose.yml up -d --wait + - name: Cache Dependencies + uses: actions/cache@v4 + with: + path: | + ~/.cargo/bin + ~/.cargo/registry + ~/.cargo/git + target + key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }} + - name: Update rust toolchain + run: rustup update + - name: Print rustc version + run: rustc --version + - name: Print rustfmt version + run: cargo fmt --version + - name: Print clippy version + run: cargo clippy --version + - name: Format check + run: cargo fmt --verbose --all -- --check + - name: Clippy check + run: cargo clippy --verbose --all-targets --all-features + - name: Cargo check + run: cargo check --verbose --all-targets --all-features + - name: Build + run: cargo build --verbose --all-targets + - name: Install Charybdis Migration Tool + run: cargo install --path charybdis-migrate + - name: Run Charybdis Migration Tool + run: migrate --keyspace charybdis --host 127.0.0.1:9042 --drop-and-replace + - name: Run tests + run: cargo test --verbose --all-targets diff --git a/Cargo.lock b/Cargo.lock index f7b7be0..98e452b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -212,33 +212,33 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "charybdis" -version = "0.7.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b2a25dc45c6f68710903d18635ddd348a6b67ada9cd9c9a8f36bb2d242d129a" +version = "0.7.10" dependencies = [ "bigdecimal", - "charybdis_macros 0.7.2", + "charybdis-migrate", + "charybdis_macros 0.7.10", "chrono", "colored", "futures", - "num-bigint 0.4.4", - "scylla 0.13.1", + "scylla", "serde", "serde_json", + "tokio", "uuid", ] [[package]] name = "charybdis" version = "0.7.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c5bd195a7d47dc7aaa54ed6267320188aa8919ab222efdfeec2807755a311354" dependencies = [ "bigdecimal", - "charybdis-migrate", - "charybdis_macros 0.7.10", + "charybdis_macros 0.7.10 (registry+https://github.com/rust-lang/crates.io-index)", "chrono", "colored", "futures", - "scylla 0.15.1", + "scylla", "serde", "serde_json", "uuid", @@ -253,47 +253,45 @@ dependencies = [ "colored", "openssl", "regex", - "scylla 0.15.1", + "scylla", "strip-ansi-escapes", "tokio", ] [[package]] name = "charybdis_macros" -version = "0.7.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22275746377c3263a9154d22c19a08498e8bcf511407175cb20bc3de8b055646" +version = "0.7.10" dependencies = [ - "charybdis_parser 0.7.2", - "darling", + "charybdis 0.7.10 (registry+https://github.com/rust-lang/crates.io-index)", + "charybdis_parser 0.7.10", + "chrono", "proc-macro2", "quote", + "scylla", "syn", ] [[package]] name = "charybdis_macros" version = "0.7.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4cacdb3cc06a8f0632281196559491343342f1d10417f17b3a8199623fbedf31" dependencies = [ - "charybdis 0.7.2", - "charybdis_parser 0.7.10", + "charybdis_parser 0.7.10 (registry+https://github.com/rust-lang/crates.io-index)", "proc-macro2", "quote", - "scylla 0.15.1", "syn", ] [[package]] name = "charybdis_parser" -version = "0.7.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "80a6cb30fe0040056e65e175ce03ac7e9d9ffde308ba95ced1be938bbb170dbb" +version = "0.7.10" dependencies = [ "colored", "darling", "proc-macro2", "quote", - "scylla 0.13.1", + "scylla", "serde", "serde_json", "strum", @@ -305,12 +303,14 @@ dependencies = [ [[package]] name = "charybdis_parser" version = "0.7.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f0f1b8d925e5f82d58fd9abfa5137693ac3474c5e5840c1f1c832511a23ec56" dependencies = [ "colored", "darling", "proc-macro2", "quote", - "scylla 0.15.1", + "scylla", "serde", "serde_json", "strum", @@ -608,12 +608,6 @@ 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 = "histogram" version = "0.6.9" @@ -649,15 +643,6 @@ version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" -[[package]] -name = "itertools" -version = "0.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1c173a5686ce8bfa551b3563d0c2170bf24ca44da99c7ca4bfdab5418c3fe57" -dependencies = [ - "either", -] - [[package]] name = "itertools" version = "0.13.0" @@ -690,9 +675,9 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" [[package]] name = "libc" -version = "0.2.152" +version = "0.2.169" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "13e3bf6590cbc649f4d1a3eefc9d5d6eb746f5200ffb04e5e142700b8faa56e7" +checksum = "b5aba8db14291edd000dfcc4d620c7ebfb122c613afb886ca8803fa4e128a20a" [[package]] name = "libm" @@ -742,13 +727,13 @@ dependencies = [ [[package]] name = "mio" -version = "0.8.10" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f3d0b296e374a4e6f3c7b0a1f5a51d748a0d34c85e7dc48fc3fa9a87657fe09" +checksum = "2886843bf800fba2e3377cff24abf6379b4c4d5c6681eaf9ea5b0d15090450bd" dependencies = [ "libc", "wasi", - "windows-sys 0.48.0", + "windows-sys 0.52.0", ] [[package]] @@ -792,16 +777,6 @@ dependencies = [ "autocfg", ] -[[package]] -name = "num_cpus" -version = "1.16.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43" -dependencies = [ - "hermit-abi", - "libc", -] - [[package]] name = "object" version = "0.32.2" @@ -813,9 +788,9 @@ dependencies = [ [[package]] name = "once_cell" -version = "1.19.0" +version = "1.20.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" +checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775" [[package]] name = "openssl" @@ -1036,37 +1011,6 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" -[[package]] -name = "scylla" -version = "0.13.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b20b46cf4ea921ba41121ba9ddf933185cd830cbe2c4fa6272a6e274a6b7368d" -dependencies = [ - "arc-swap", - "async-trait", - "byteorder", - "bytes", - "chrono", - "dashmap", - "futures", - "hashbrown", - "histogram", - "itertools 0.11.0", - "lazy_static", - "lz4_flex", - "rand", - "rand_pcg", - "scylla-cql 0.2.1", - "scylla-macros 0.5.1", - "smallvec", - "snap", - "socket2", - "thiserror 1.0.56", - "tokio", - "tracing", - "uuid", -] - [[package]] name = "scylla" version = "0.15.1" @@ -1082,47 +1026,24 @@ dependencies = [ "futures", "hashbrown", "histogram", - "itertools 0.13.0", + "itertools", "lazy_static", "lz4_flex", "openssl", "rand", "rand_pcg", - "scylla-cql 0.4.1", - "scylla-macros 0.7.1", + "scylla-cql", + "scylla-macros", "smallvec", "snap", "socket2", - "thiserror 2.0.7", + "thiserror", "tokio", "tokio-openssl", "tracing", "uuid", ] -[[package]] -name = "scylla-cql" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "27ea3cd3ff5bf9d7db7a6d65c54cecf52f7c40b8e3e32c8c2d6da84d23776ea4" -dependencies = [ - "async-trait", - "bigdecimal", - "byteorder", - "bytes", - "chrono", - "lz4_flex", - "num-bigint 0.3.3", - "num-bigint 0.4.4", - "scylla-macros 0.5.1", - "secrecy 0.7.0", - "snap", - "thiserror 1.0.56", - "time", - "tokio", - "uuid", -] - [[package]] name = "scylla-cql" version = "0.4.1" @@ -1137,29 +1058,17 @@ dependencies = [ "lz4_flex", "num-bigint 0.3.3", "num-bigint 0.4.4", - "scylla-macros 0.7.1", - "secrecy 0.8.0", + "scylla-macros", + "secrecy", "snap", "stable_deref_trait", - "thiserror 2.0.7", + "thiserror", "time", "tokio", "uuid", "yoke", ] -[[package]] -name = "scylla-macros" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e50f3e2aec7ea9f495e029fb783eb34c64d26a8f2055e1d6b43d00e04d2fbda6" -dependencies = [ - "darling", - "proc-macro2", - "quote", - "syn", -] - [[package]] name = "scylla-macros" version = "0.7.1" @@ -1172,15 +1081,6 @@ dependencies = [ "syn", ] -[[package]] -name = "secrecy" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0673d6a6449f5e7d12a1caf424fd9363e2af3a4953023ed455e3c4beef4597c0" -dependencies = [ - "zeroize", -] - [[package]] name = "secrecy" version = "0.8.0" @@ -1329,33 +1229,13 @@ dependencies = [ "syn", ] -[[package]] -name = "thiserror" -version = "1.0.56" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d54378c645627613241d077a3a79db965db602882668f9136ac42af9ecb730ad" -dependencies = [ - "thiserror-impl 1.0.56", -] - [[package]] name = "thiserror" version = "2.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "93605438cbd668185516ab499d589afb7ee1859ea3d5fc8f6b0755e1c7443767" dependencies = [ - "thiserror-impl 2.0.7", -] - -[[package]] -name = "thiserror-impl" -version = "1.0.56" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa0faa943b50f3db30a20aa7e265dbc66076993efed8463e8de414e5d06d3471" -dependencies = [ - "proc-macro2", - "quote", - "syn", + "thiserror-impl", ] [[package]] @@ -1389,28 +1269,27 @@ checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3" [[package]] name = "tokio" -version = "1.38.0" +version = "1.42.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba4f4a02a7a80d6f274636f0aa95c7e383b912d41fe721a31f29e29698585a4a" +checksum = "5cec9b21b0450273377fc97bd4c33a8acffc8c996c987a7c5b319a0083707551" dependencies = [ "backtrace", "bytes", "libc", "mio", - "num_cpus", "parking_lot", "pin-project-lite", "signal-hook-registry", "socket2", "tokio-macros", - "windows-sys 0.48.0", + "windows-sys 0.52.0", ] [[package]] name = "tokio-macros" -version = "2.3.0" +version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f5ae998a069d4b5aba8ee9dad856af7d520c3699e6159b185c2acd48155d39a" +checksum = "693d596312e88961bc67d7f1f97af8a70227d9f90c31bba5806eec004978d752" dependencies = [ "proc-macro2", "quote", diff --git a/charybdis-macros/Cargo.toml b/charybdis-macros/Cargo.toml index efc816d..a62f9a0 100644 --- a/charybdis-macros/Cargo.toml +++ b/charybdis-macros/Cargo.toml @@ -20,3 +20,4 @@ quote = "1.0.36" [dev-dependencies] charybdis = "0.7.2" scylla = "0.15.1" +chrono = "0.4.38" \ No newline at end of file diff --git a/charybdis-macros/src/model/consts/mod.rs b/charybdis-macros/src/model/consts/mod.rs index 9f026dd..d4d1ff8 100644 --- a/charybdis-macros/src/model/consts/mod.rs +++ b/charybdis-macros/src/model/consts/mod.rs @@ -10,4 +10,3 @@ mod insert; mod model_name; mod update; - diff --git a/charybdis-macros/src/native/delete.rs b/charybdis-macros/src/native/delete.rs index 1db7725..426e410 100644 --- a/charybdis-macros/src/native/delete.rs +++ b/charybdis-macros/src/native/delete.rs @@ -11,18 +11,37 @@ const MAX_DELETE_BY_FUNCTIONS: usize = 3; /// for 3 keys generate additional function that deletes by partition key & partial clustering key /// Example: -/// ```ignore +/// ```rust +/// use charybdis::macros::charybdis_model; +/// use charybdis::errors::CharybdisError; +/// use charybdis::types::{Timestamp, Uuid}; +/// use scylla::CachingSession; +/// /// #[charybdis_model( /// table_name = users, /// partition_keys = [id], /// clustering_keys = [org_id, created_at], -/// global_secondary_indexes = [])] -/// pub struct UserOps {...} +/// global_secondary_indexes = [] +/// )] +/// pub struct User { +/// id: Uuid, +/// org_id: Uuid, +/// created_at: Timestamp, +/// } +/// impl User { +/// pub async fn delete_funs(db: &CachingSession) -> Result<(), CharybdisError> { +/// let id = Uuid::new_v4(); +/// let org_id = Uuid::new_v4(); +/// let created_at = chrono::Utc::now(); +/// +/// User::delete_by_id(id).execute(db).await?; +/// User::delete_by_id_and_org_id(id, org_id).execute(db).await?; +/// User::delete_by_id_and_org_id_and_created_at(id, org_id, created_at).execute(db).await?; +/// +/// Ok(()) +/// } +/// } /// ``` -/// we would have a functions: -/// ```ignore -/// User::delete_by_id_and_org_id(session: &Session, org_id: Uuid) -> Result, errors::CharybdisError> -/// User::delete_by_id_and_org_id_and_created_at(session: &Session, org_id: Uuid, created_at: Timestamp) -> Result, errors::CharybdisError> pub(crate) fn delete_by_primary_key_functions(ch_args: &CharybdisMacroArgs, fields: &CharybdisFields) -> TokenStream { let table_name = ch_args.table_name(); let partition_keys_len = fields.partition_key_fields.len(); diff --git a/charybdis-macros/src/native/mod.rs b/charybdis-macros/src/native/mod.rs index d702337..fb032f6 100644 --- a/charybdis-macros/src/native/mod.rs +++ b/charybdis-macros/src/native/mod.rs @@ -8,4 +8,3 @@ mod counter; mod delete; mod find; - diff --git a/charybdis-macros/src/rules/delete.rs b/charybdis-macros/src/rules/delete.rs index cfe6f18..3b1b2b7 100644 --- a/charybdis-macros/src/rules/delete.rs +++ b/charybdis-macros/src/rules/delete.rs @@ -2,8 +2,8 @@ use proc_macro2::{Ident, TokenStream}; use quote::quote; use syn::parse_str; -use charybdis_parser::traits::CharybdisMacroArgs; use charybdis_parser::traits::string::ToSnakeCase; +use charybdis_parser::traits::CharybdisMacroArgs; pub(crate) fn delete_model_query_rule(struct_name: &Ident, args: &CharybdisMacroArgs) -> TokenStream { let macro_name_str: String = format!("delete_{}_query", struct_name.to_string().to_snake_case()); diff --git a/charybdis-macros/src/rules/find.rs b/charybdis-macros/src/rules/find.rs index ffc9976..e7569e8 100644 --- a/charybdis-macros/src/rules/find.rs +++ b/charybdis-macros/src/rules/find.rs @@ -3,8 +3,8 @@ use quote::quote; use syn::parse_str; use charybdis_parser::fields::CharybdisFields; -use charybdis_parser::traits::CharybdisMacroArgs; use charybdis_parser::traits::string::ToSnakeCase; +use charybdis_parser::traits::CharybdisMacroArgs; use crate::traits::fields::FieldsQuery; diff --git a/charybdis-macros/src/rules/mod.rs b/charybdis-macros/src/rules/mod.rs index 911ac10..fcec776 100644 --- a/charybdis-macros/src/rules/mod.rs +++ b/charybdis-macros/src/rules/mod.rs @@ -7,4 +7,3 @@ mod delete; mod find; mod partial; mod update; - diff --git a/charybdis-macros/src/rules/partial.rs b/charybdis-macros/src/rules/partial.rs index 7a1ac13..6460020 100644 --- a/charybdis-macros/src/rules/partial.rs +++ b/charybdis-macros/src/rules/partial.rs @@ -16,13 +16,19 @@ use crate::traits::fields::{FieldHashMapString, ToIdents}; /// It is basically the same as base model struct, but with provided fields only. /// /// So, if we have a model User with some fields: -/// ```ignore -/// use charybdis::*; -/// use super::Address; +/// Example: +/// ```rust +/// use charybdis::macros::charybdis_model; +/// use charybdis::types::{Text, Timestamp, Uuid}; +/// use scylla::CachingSession; +/// /// #[charybdis_model( -/// table_name = "users", -/// partition_keys = ["id"], -/// clustering_keys = [])] +/// table_name = users, +/// partition_keys = [id], +/// clustering_keys = [], +/// global_secondary_indexes = [] +/// )] +/// #[derive(Default)] /// pub struct User { /// pub id: Uuid, /// pub username: Text, @@ -31,92 +37,28 @@ use crate::traits::fields::{FieldHashMapString, ToIdents}; /// pub email: Text, /// pub created_at: Timestamp, /// pub updated_at: Timestamp, -/// pub address: Address, /// } +/// partial_user!(UpdateUsernameUser, id, username, updated_at); /// ``` -/// It will generate a `partial_user!` macro that can be used to generate a partial users structs. -/// -/// ## Example generation: -/// ```ignore -/// partial_user!(PartialUser, id, username, email); -/// ``` -/// It will generate a struct with only those fields: -/// ```ignore -/// #[charybdis_model( -/// table_name = "users", -/// partition_keys = ["id"], -/// clustering_keys = [] -/// global_secondary_indexes = [])] -/// pub struct PartialUser { -/// pub id: Uuid, -/// pub username: Text, -/// pub email: Text, -/// } -///``` -/// And we can use it as a normal model struct. +/// it will generate a struct with `#[charybdis_model(...)]` declaration like this: /// -/// ```ignore -/// let mut partial_user = PartialUser {id, username, email}; -/// partial_user.insert().execute(&session).await?; -/// partial_user.find_by_primary_key().execute(&session).await?; /// ``` -/// -/// ## Example usage: -/// ```ignore -/// partial_user!(PartialUser, id, username, email); -/// -/// let mut partial_user = PartialUser { -/// id: Uuid::new_v4(), -/// username: "test".to_string(), -/// email: "test@gmail.com".to_string(), -/// }; -/// -/// println!("{:?}", partial_user); -///``` -///--- -/// -/// ### `#[charybdis_model]` declaration -/// It also appends `#[charybdis_model(...)]` declaration with clustering keys and secondary indexes -/// based on fields that are provided in partial_model struct. -/// -/// E.g. if we have model: -/// ```ignore -/// #[partial_model_generator] +/// use charybdis::macros::charybdis_model; +/// use charybdis::types::{Text, Timestamp, Uuid}; +/// use scylla::CachingSession; /// #[charybdis_model( /// table_name = users, /// partition_keys = [id], -/// clustering_keys = [created_at, updated_at], +/// clustering_keys = [], /// global_secondary_indexes = [] /// )] -/// pub struct User { +/// pub struct UpdateUsernameUser { /// pub id: Uuid, /// pub username: Text, -/// pub password: Text, -/// pub hashed_password: Text, -/// pub email: Text, -/// pub created_at: Timestamp, /// pub updated_at: Timestamp, /// } /// ``` -/// -/// and we use partial model macro: -/// ```ignore -/// partial_user!(UserOps, id, username, email, created_at); -/// ``` -/// it will generate a struct with `#[charybdis_model(...)]` declaration: -/// -/// ```ignore -/// #[charybdis_model( -/// table_name = users, -/// partition_keys = [id], -/// clustering_keys = [created_at], -/// global_secondary_indexes = [])] -/// pub struct UserOps {...} -/// ``` -/// Note that `updated_at` is not present in generated declaration. -/// However, all partition keys are required for db operations, so we can't have partial partition -/// keys. -/// +/// Note that partial_user! requires `Default` to be implemented for the model. pub(crate) fn partial_model_macro_generator( input: &DeriveInput, args: &CharybdisMacroArgs, diff --git a/charybdis-macros/src/rules/update.rs b/charybdis-macros/src/rules/update.rs index 174b8a5..d07d4aa 100644 --- a/charybdis-macros/src/rules/update.rs +++ b/charybdis-macros/src/rules/update.rs @@ -3,8 +3,8 @@ use quote::quote; use syn::parse_str; use charybdis_parser::fields::CharybdisFields; -use charybdis_parser::traits::CharybdisMacroArgs; use charybdis_parser::traits::string::ToSnakeCase; +use charybdis_parser::traits::CharybdisMacroArgs; use crate::traits::fields::FieldsQuery; diff --git a/charybdis-macros/src/traits/fields/hash_map.rs b/charybdis-macros/src/traits/fields/hash_map.rs index 2ba9199..16ca188 100644 --- a/charybdis-macros/src/traits/fields/hash_map.rs +++ b/charybdis-macros/src/traits/fields/hash_map.rs @@ -41,4 +41,3 @@ impl FieldHashMapString for Vec> { field_attributes.to_string().replace('\n', "") } } - diff --git a/charybdis-migrate/src/model/data.rs b/charybdis-migrate/src/model/data.rs index a503168..225e3c9 100644 --- a/charybdis-migrate/src/model/data.rs +++ b/charybdis-migrate/src/model/data.rs @@ -1,7 +1,7 @@ use charybdis_parser::schema::{IndexName, SchemaObject}; -use crate::model::ModelType; use crate::model::runner::INDEX_SUFFIX; +use crate::model::ModelType; type FieldName = String; type FieldType = String; diff --git a/charybdis/Cargo.toml b/charybdis/Cargo.toml index 1b746d8..9f2ce48 100644 --- a/charybdis/Cargo.toml +++ b/charybdis/Cargo.toml @@ -24,3 +24,6 @@ bigdecimal = { version = "0.4.3", features = ["serde"] } [features] migrate = ["charybdis-migrate"] + +[dev-dependencies] +tokio = "1.42.0" diff --git a/charybdis/src/model.rs b/charybdis/src/model.rs index 3fe2c94..30ad5d2 100644 --- a/charybdis/src/model.rs +++ b/charybdis/src/model.rs @@ -116,10 +116,8 @@ pub trait TableOptions { const TABLE_OPTIONS: &'static str; } -/// -/// In extension of partial_model!() in case you need native model in order to run calculations -/// or other operations, you can use `as_native` method: -/// ```rust ignore +/// In extension of partial_model!() in case you need to build native model you can use `as_native` method: +/// ```rust /// use charybdis::macros::charybdis_model; /// use charybdis::types::{Text, Timestamp, Uuid}; /// use charybdis::model::AsNative; @@ -145,18 +143,12 @@ pub trait TableOptions { /// /// partial_user!(UpdateUsernameUser, id, username); /// -/// let mut user = UpdateUsernameUser { -/// id: Uuid::new_v4(), -/// username: "updated_username".to_string(), -/// }; -/// -/// let native_user: User = user.as_native(); -/// -/// // action that requires native model -/// // authorize_user(&native_user); +/// impl UpdateUsernameUser { +/// pub fn native(&self) -> User { +/// self.as_native() +/// } +/// } /// ``` -/// Its automatically generated by `#[partial_model_generator]`. -/// pub trait AsNative { fn as_native(&self) -> T; } diff --git a/charybdis/src/operations.rs b/charybdis/src/operations.rs index edab179..fcc56ff 100644 --- a/charybdis/src/operations.rs +++ b/charybdis/src/operations.rs @@ -11,4 +11,3 @@ mod find; mod insert; mod new; mod update; - diff --git a/charybdis/tests/integrations/common.rs b/charybdis/tests/integrations/common.rs new file mode 100644 index 0000000..8fa2be0 --- /dev/null +++ b/charybdis/tests/integrations/common.rs @@ -0,0 +1,21 @@ +use scylla::{CachingSession, SessionBuilder}; +use tokio::sync::OnceCell; + +const NODE: &str = "127.0.0.1:9042"; +const KEYSPACE: &str = "charybdis"; +const CACHE_SIZE: usize = 1000; + +static ONCE: OnceCell = OnceCell::const_new(); + +pub async fn db_session() -> &'static CachingSession { + ONCE.get_or_init(|| async { + let db_session = SessionBuilder::new() + .known_node(NODE) + .use_keyspace(KEYSPACE, false) + .build() + .await + .expect("Unable to connect to scylla hosts"); + CachingSession::from(db_session, CACHE_SIZE) + }) + .await +} diff --git a/charybdis/tests/integrations/main.rs b/charybdis/tests/integrations/main.rs new file mode 100644 index 0000000..1007e43 --- /dev/null +++ b/charybdis/tests/integrations/main.rs @@ -0,0 +1,3 @@ +mod common; +mod models; +mod query; diff --git a/charybdis/tests/integrations/models.rs b/charybdis/tests/integrations/models.rs new file mode 100644 index 0000000..688ca2e --- /dev/null +++ b/charybdis/tests/integrations/models.rs @@ -0,0 +1,33 @@ +use charybdis::types::{Boolean, Text, Timestamp, Uuid}; +use charybdis_macros::{charybdis_model, charybdis_udt_model}; + +#[derive(Default, Clone)] +#[charybdis_udt_model(type_name = address)] +pub struct Address { + pub street: Text, + pub city: Text, + pub state: Text, + pub zip: Text, + pub country: Text, +} + +#[charybdis_model( + table_name = users, + partition_keys = [id], + clustering_keys = [], + global_secondary_indexes = [username, email], +)] +#[derive(Default)] +pub struct User { + pub id: Uuid, + pub username: Text, + pub email: Text, + pub password: Text, + pub first_name: Text, + pub last_name: Text, + pub bio: Option, + pub address: Option
, + pub is_confirmed: Boolean, + pub created_at: Timestamp, + pub updated_at: Timestamp, +} diff --git a/charybdis/tests/integrations/query.rs b/charybdis/tests/integrations/query.rs new file mode 100644 index 0000000..1849485 --- /dev/null +++ b/charybdis/tests/integrations/query.rs @@ -0,0 +1,30 @@ +use crate::common::db_session; +use crate::models::{Address, User}; +use charybdis::operations::Insert; + +#[tokio::test] +async fn create() { + let user = User { + id: uuid::Uuid::new_v4(), + username: "test".to_string(), + email: "homer@simpson.com".to_string(), + password: "Marge".to_string(), + first_name: "Homer".to_string(), + last_name: "Simpson".to_string(), + bio: Some("I like donuts".to_string()), + address: Some(Address { + street: "742 Evergreen Terrace".to_string(), + city: "Springfield".to_string(), + state: "Illinois".to_string(), + zip: "62701".to_string(), + country: "USA".to_string(), + }), + is_confirmed: true, + created_at: chrono::Utc::now(), + updated_at: chrono::Utc::now(), + }; + + let db_session = db_session().await; + + user.insert().execute(db_session).await.expect("Failed to insert user"); +} diff --git a/test/docker-compose.yml b/test/docker-compose.yml new file mode 100644 index 0000000..39f8513 --- /dev/null +++ b/test/docker-compose.yml @@ -0,0 +1,79 @@ +version: "3.7" + +networks: + public: + name: charybdis_public + driver: bridge + ipam: + driver: default + config: + - subnet: 172.42.0.0/16 +services: + scylla1: + image: scylladb/scylla + networks: + public: + ipv4_address: 172.42.0.2 + command: | + --rpc-address 172.42.0.2 + --listen-address 172.42.0.2 + --seeds 172.42.0.2 + --skip-wait-for-gossip-to-settle 0 + --ring-delay-ms 0 + --smp 2 + --memory 1G + healthcheck: + test: [ "CMD", "cqlsh", "scylla1", "-e", "select * from system.local" ] + interval: 5s + timeout: 5s + retries: 60 + init-keyspace: + image: scylladb/scylla + depends_on: + scylla1: + condition: service_healthy + command: [ "cqlsh", "scylla1", "-e", "CREATE KEYSPACE charybdis WITH replication = {'class': 'NetworkTopologyStrategy', 'replication_factor': 2};" ] + networks: + - public + scylla2: + image: scylladb/scylla + networks: + public: + ipv4_address: 172.42.0.3 + command: | + --rpc-address 172.42.0.3 + --listen-address 172.42.0.3 + --seeds 172.42.0.2 + --skip-wait-for-gossip-to-settle 0 + --ring-delay-ms 0 + --smp 2 + --memory 1G + healthcheck: + test: [ "CMD", "cqlsh", "scylla2", "-e", "select * from system.local" ] + interval: 5s + timeout: 5s + retries: 60 + depends_on: + scylla1: + condition: service_healthy + scylla3: + image: scylladb/scylla + networks: + public: + ipv4_address: 172.42.0.4 + command: | + --rpc-address 172.42.0.4 + --listen-address 172.42.0.4 + --seeds 172.42.0.2,172.42.0.3 + --skip-wait-for-gossip-to-settle 0 + --ring-delay-ms 0 + --smp 2 + --memory 1G + healthcheck: + test: [ "CMD", "cqlsh", "scylla3", "-e", "select * from system.local" ] + interval: 5s + timeout: 5s + retries: 60 + depends_on: + scylla2: + condition: service_healthy From c0a734f24d2898e317d6e5dc76e2cf5f8ef19849 Mon Sep 17 00:00:00 2001 From: Goran Brkuljan Date: Thu, 19 Dec 2024 23:12:05 +0100 Subject: [PATCH 2/8] tests: update keyspace creation in docker-compose --- .github/workflows/build.yml | 4 ++++ test/docker-compose.yml | 8 -------- 2 files changed, 4 insertions(+), 8 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index c97099e..ec01e3c 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -21,6 +21,10 @@ jobs: run: | sudo sh -c "echo 2097152 >> /proc/sys/fs/aio-max-nr" docker compose -f test/docker-compose.yml up -d --wait + - name: Create Keyspace + run: | + docker exec scylla1 cqlsh 127.0.0.1 -e \ + "CREATE KEYSPACE charybdis WITH replication = {'class': 'NetworkTopologyStrategy', 'replication_factor': 2};" - name: Cache Dependencies uses: actions/cache@v4 with: diff --git a/test/docker-compose.yml b/test/docker-compose.yml index 39f8513..861f5c0 100644 --- a/test/docker-compose.yml +++ b/test/docker-compose.yml @@ -27,14 +27,6 @@ services: interval: 5s timeout: 5s retries: 60 - init-keyspace: - image: scylladb/scylla - depends_on: - scylla1: - condition: service_healthy - command: [ "cqlsh", "scylla1", "-e", "CREATE KEYSPACE charybdis WITH replication = {'class': 'NetworkTopologyStrategy', 'replication_factor': 2};" ] - networks: - - public scylla2: image: scylladb/scylla networks: From 484ffc94bb0704fa1d36ea1648150ba471ee0d6b Mon Sep 17 00:00:00 2001 From: Goran Brkuljan Date: Fri, 20 Dec 2024 01:29:21 +0100 Subject: [PATCH 3/8] tests: update container names --- test/docker-compose.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/test/docker-compose.yml b/test/docker-compose.yml index 861f5c0..7e624ad 100644 --- a/test/docker-compose.yml +++ b/test/docker-compose.yml @@ -10,6 +10,7 @@ networks: - subnet: 172.42.0.0/16 services: scylla1: + container_name: scylla1 image: scylladb/scylla networks: public: @@ -28,6 +29,7 @@ services: timeout: 5s retries: 60 scylla2: + container_name: scylla2 image: scylladb/scylla networks: public: @@ -49,6 +51,7 @@ services: scylla1: condition: service_healthy scylla3: + container_name: scylla3 image: scylladb/scylla networks: public: From 4d1f03a8c06494a1870459ca21a06ff89d12b11a Mon Sep 17 00:00:00 2001 From: Goran Brkuljan Date: Fri, 20 Dec 2024 01:31:58 +0100 Subject: [PATCH 4/8] tests: remove ip from create keyspace command --- .github/workflows/build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index ec01e3c..9030d8b 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -23,7 +23,7 @@ jobs: docker compose -f test/docker-compose.yml up -d --wait - name: Create Keyspace run: | - docker exec scylla1 cqlsh 127.0.0.1 -e \ + docker exec scylla1 cqlsh -e \ "CREATE KEYSPACE charybdis WITH replication = {'class': 'NetworkTopologyStrategy', 'replication_factor': 2};" - name: Cache Dependencies uses: actions/cache@v4 From 8e15194be881391f64010ed2fcbbe7230266fd14 Mon Sep 17 00:00:00 2001 From: Goran Brkuljan Date: Fri, 20 Dec 2024 01:54:32 +0100 Subject: [PATCH 5/8] tests: expose container ports --- charybdis/tests/integrations/common.rs | 4 ++-- test/docker-compose.yml | 6 ++++++ 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/charybdis/tests/integrations/common.rs b/charybdis/tests/integrations/common.rs index 8fa2be0..343693e 100644 --- a/charybdis/tests/integrations/common.rs +++ b/charybdis/tests/integrations/common.rs @@ -1,7 +1,7 @@ use scylla::{CachingSession, SessionBuilder}; use tokio::sync::OnceCell; -const NODE: &str = "127.0.0.1:9042"; +const NODES: [&str; 3] = ["127.0.0.1:9042", "127.0.0.1:9043", "127.0.0.1:9044"]; const KEYSPACE: &str = "charybdis"; const CACHE_SIZE: usize = 1000; @@ -10,7 +10,7 @@ static ONCE: OnceCell = OnceCell::const_new(); pub async fn db_session() -> &'static CachingSession { ONCE.get_or_init(|| async { let db_session = SessionBuilder::new() - .known_node(NODE) + .known_nodes(NODES) .use_keyspace(KEYSPACE, false) .build() .await diff --git a/test/docker-compose.yml b/test/docker-compose.yml index 7e624ad..ca5fe07 100644 --- a/test/docker-compose.yml +++ b/test/docker-compose.yml @@ -15,6 +15,8 @@ services: networks: public: ipv4_address: 172.42.0.2 + ports: + - "9042:9042" command: | --rpc-address 172.42.0.2 --listen-address 172.42.0.2 @@ -34,6 +36,8 @@ services: networks: public: ipv4_address: 172.42.0.3 + ports: + - "9043:9042" command: | --rpc-address 172.42.0.3 --listen-address 172.42.0.3 @@ -56,6 +60,8 @@ services: networks: public: ipv4_address: 172.42.0.4 + ports: + - "9044:9042" command: | --rpc-address 172.42.0.4 --listen-address 172.42.0.4 From d98c95beaf8f498a0cd2423687b0840864dede89 Mon Sep 17 00:00:00 2001 From: Goran Brkuljan Date: Fri, 20 Dec 2024 05:05:08 +0100 Subject: [PATCH 6/8] update: add test cases --- charybdis/tests/integrations/common.rs | 22 +- charybdis/tests/integrations/main.rs | 2 +- charybdis/tests/integrations/model.rs | 293 +++++++++++++++++++++++++ charybdis/tests/integrations/models.rs | 33 --- charybdis/tests/integrations/query.rs | 172 +++++++++++++-- 5 files changed, 451 insertions(+), 71 deletions(-) create mode 100644 charybdis/tests/integrations/model.rs delete mode 100644 charybdis/tests/integrations/models.rs diff --git a/charybdis/tests/integrations/common.rs b/charybdis/tests/integrations/common.rs index 343693e..143657e 100644 --- a/charybdis/tests/integrations/common.rs +++ b/charybdis/tests/integrations/common.rs @@ -1,21 +1,15 @@ use scylla::{CachingSession, SessionBuilder}; -use tokio::sync::OnceCell; const NODES: [&str; 3] = ["127.0.0.1:9042", "127.0.0.1:9043", "127.0.0.1:9044"]; const KEYSPACE: &str = "charybdis"; const CACHE_SIZE: usize = 1000; -static ONCE: OnceCell = OnceCell::const_new(); - -pub async fn db_session() -> &'static CachingSession { - ONCE.get_or_init(|| async { - let db_session = SessionBuilder::new() - .known_nodes(NODES) - .use_keyspace(KEYSPACE, false) - .build() - .await - .expect("Unable to connect to scylla hosts"); - CachingSession::from(db_session, CACHE_SIZE) - }) - .await +pub async fn db_session() -> CachingSession { + let db_session = SessionBuilder::new() + .known_nodes(NODES) + .use_keyspace(KEYSPACE, false) + .build() + .await + .expect("Unable to connect to scylla hosts"); + CachingSession::from(db_session, CACHE_SIZE) } diff --git a/charybdis/tests/integrations/main.rs b/charybdis/tests/integrations/main.rs index 1007e43..9e3d6b7 100644 --- a/charybdis/tests/integrations/main.rs +++ b/charybdis/tests/integrations/main.rs @@ -1,3 +1,3 @@ mod common; -mod models; +mod model; mod query; diff --git a/charybdis/tests/integrations/model.rs b/charybdis/tests/integrations/model.rs new file mode 100644 index 0000000..d6b23e8 --- /dev/null +++ b/charybdis/tests/integrations/model.rs @@ -0,0 +1,293 @@ +use crate::common::db_session; +use charybdis::batch::ModelBatch; +use charybdis::errors::CharybdisError; +use charybdis::model::{BaseModel, Model}; +use charybdis::stream::CharybdisModelStream; +use charybdis::types::{Boolean, Int, Text, Uuid}; +use charybdis_macros::{charybdis_model, charybdis_udt_model, charybdis_view_model}; + +#[derive(Debug, Default, Clone, PartialEq)] +#[charybdis_udt_model(type_name = address)] +pub struct Address { + pub street: Text, + pub city: Text, + pub state: Text, + pub zip: Text, + pub country: Text, +} + +#[charybdis_model( + table_name = users, + partition_keys = [id], + clustering_keys = [], + global_secondary_indexes = [username], +)] +#[derive(Debug, Default, Clone, PartialEq)] +pub struct User { + pub id: Uuid, + pub username: Text, + pub password: Text, + pub email: Text, + pub first_name: Text, + pub last_name: Text, + pub bio: Option, + pub address: Option
, + pub is_confirmed: Boolean, +} + +partial_user!(UpdateUsernameUser, id, username); + +impl User { + pub async fn populate_sample_users() { + let db_session = db_session().await; + let users = (0..32) + .map(|i| { + let id = Uuid::new_v4(); + let mut new_user = User::homer(id); + new_user.username = format!("user_{}", i); + new_user.email = format!("user_{}@gmail.com", i); + + new_user + }) + .collect::>(); + + User::batch() + .chunked_insert(&db_session, &users, 100) + .await + .expect("Failed to insert users"); + } + + pub fn homer(id: Uuid) -> Self { + User { + id, + username: "test".to_string(), + password: "Marge".to_string(), + first_name: "Homer".to_string(), + email: "homer@simpson.com".to_string(), + last_name: "Simpson".to_string(), + bio: Some("I like donuts".to_string()), + address: Some(Address { + street: "742 Evergreen Terrace".to_string(), + city: "Springfield".to_string(), + state: "Illinois".to_string(), + zip: "62701".to_string(), + country: "USA".to_string(), + }), + is_confirmed: true, + } + } +} + +#[tokio::test] +async fn user_model_queries() { + assert_eq!(User::DB_MODEL_NAME, "users"); + assert_eq!( + User::FIND_ALL_QUERY, + "SELECT id, username, password, email, first_name, last_name, bio, address, is_confirmed FROM users" + ); + assert_eq!( + User::FIND_BY_PRIMARY_KEY_QUERY, + "SELECT id, username, password, email, \ + first_name, last_name, bio, address, is_confirmed FROM users WHERE id = ?" + ); + assert_eq!( + User::FIND_BY_PARTITION_KEY_QUERY, + "SELECT id, username, password, email, \ + first_name, last_name, bio, address, is_confirmed FROM users WHERE id = ?" + ); + assert_eq!( + User::FIND_FIRST_BY_PARTITION_KEY_QUERY, + "SELECT id, username, password, email, \ + first_name, last_name, bio, address, is_confirmed FROM users WHERE id = ? LIMIT 1" + ); + assert_eq!( + User::INSERT_QUERY, + "INSERT INTO users (id, username, password, email, first_name, last_name, bio, address, is_confirmed) \ + VALUES (:id, :username, :password, :email, :first_name, :last_name, :bio, :address, :is_confirmed)" + ); + assert_eq!( + User::INSERT_IF_NOT_EXIST_QUERY, + "INSERT INTO users (id, username, password, email, first_name, last_name, bio, address, is_confirmed) \ + VALUES (:id, :username, :password, :email, :first_name, :last_name, :bio, :address, :is_confirmed) \ + IF NOT EXISTS" + ); + assert_eq!( + User::UPDATE_QUERY, + "UPDATE users SET username = :username, password = :password, email = :email, first_name = :first_name, \ + last_name = :last_name, bio = :bio, address = :address, is_confirmed = :is_confirmed WHERE id = :id" + ); + assert_eq!(User::DELETE_QUERY, "DELETE FROM users WHERE id = ?"); + assert_eq!(User::DELETE_BY_PARTITION_KEY_QUERY, "DELETE FROM users WHERE id = ?"); +} + +#[charybdis_view_model( + table_name=user_by_email, + base_table=users, + partition_keys=[email], + clustering_keys=[id] +)] +pub struct UserByEmail { + pub id: Uuid, + pub email: Text, + pub username: Text, +} + +#[charybdis_model( + table_name = posts, + partition_keys = [category_id], + clustering_keys = [order_idx, title], + global_secondary_indexes = [author_id], + local_secondary_indexes = [title], +)] +#[derive(Debug, Default, Clone, PartialEq)] +pub struct Post { + pub category_id: Uuid, + pub order_idx: Int, + pub title: Text, + pub content: Text, + pub author_id: Uuid, +} + +impl Post { + pub async fn populate_sample_posts_per_partition(category_id: Uuid) { + let db_session = db_session().await; + let posts = (0..32) + .map(|i| Post { + category_id, + order_idx: i, + title: format!("Post {}", i), + content: "Lorem ipsum dolor sit amet".to_string(), + author_id: Uuid::new_v4(), + }) + .collect::>(); + + Post::batch() + .chunked_insert(&db_session, &posts, 100) + .await + .expect("Failed to insert posts"); + } +} + +#[tokio::test] +async fn post_model_queries() { + assert_eq!(Post::DB_MODEL_NAME, "posts"); + assert_eq!( + Post::FIND_ALL_QUERY, + "SELECT category_id, order_idx, title, content, author_id FROM posts" + ); + assert_eq!( + Post::FIND_BY_PRIMARY_KEY_QUERY, + "SELECT category_id, order_idx, title, content, author_id FROM posts \ + WHERE category_id = ? AND order_idx = ? AND title = ?" + ); + assert_eq!( + Post::FIND_BY_PARTITION_KEY_QUERY, + "SELECT category_id, order_idx, title, content, author_id FROM posts WHERE category_id = ?" + ); + assert_eq!( + Post::FIND_FIRST_BY_PARTITION_KEY_QUERY, + "SELECT category_id, order_idx, title, content, author_id FROM posts WHERE category_id = ? LIMIT 1" + ); + assert_eq!( + Post::INSERT_QUERY, + "INSERT INTO posts (category_id, order_idx, title, content, author_id) \ + VALUES (:category_id, :order_idx, :title, :content, :author_id)" + ); + assert_eq!( + Post::INSERT_IF_NOT_EXIST_QUERY, + "INSERT INTO posts (category_id, order_idx, title, content, author_id) \ + VALUES (:category_id, :order_idx, :title, :content, :author_id) \ + IF NOT EXISTS" + ); + assert_eq!( + Post::UPDATE_QUERY, + "UPDATE posts SET content = :content, author_id = :author_id \ + WHERE category_id = :category_id AND order_idx = :order_idx AND title = :title" + ); + assert_eq!( + Post::DELETE_QUERY, + "DELETE FROM posts WHERE category_id = ? AND order_idx = ? AND title = ?" + ); + assert_eq!( + Post::DELETE_BY_PARTITION_KEY_QUERY, + "DELETE FROM posts WHERE category_id = ?" + ); +} + +#[tokio::test] +async fn find_various() -> Result<(), CharybdisError> { + let category_id = Uuid::new_v4(); + let db_session = &db_session().await; + + Post::populate_sample_posts_per_partition(category_id).await; + + let posts: CharybdisModelStream = Post::find_by_category_id(category_id).execute(db_session).await?; + assert_eq!(posts.try_collect().await?.len(), 32); + + let posts: CharybdisModelStream = Post::find_by_category_id_and_order_idx(category_id, 1) + .execute(db_session) + .await?; + assert_eq!(posts.try_collect().await?.len(), 1); + + let posts: Post = Post::find_by_category_id_and_order_idx_and_title(category_id, 1, "Post 1".to_string()) + .execute(db_session) + .await?; + assert_eq!(posts.title, "Post 1"); + + let post: Post = Post::find_first_by_category_id(category_id).execute(db_session).await?; + assert_eq!(post.order_idx, 0); + + let post: Post = Post::find_first_by_category_id_and_order_idx(category_id, 1) + .execute(db_session) + .await?; + assert_eq!(post.title, "Post 1"); + + let maybe_post: Option = Post::maybe_find_first_by_category_id(Uuid::new_v4()) + .execute(db_session) + .await?; + + assert!(maybe_post.is_none()); + + let maybe_post: Option = Post::maybe_find_first_by_category_id_and_order_idx(category_id, 1) + .execute(db_session) + .await?; + assert!(maybe_post.is_some()); + + let maybe_post: Option = + Post::maybe_find_first_by_category_id_and_order_idx_and_title(category_id, 2, "Post 2".to_string()) + .execute(db_session) + .await?; + assert!(maybe_post.is_some()); + + // find by local secondary index + let posts: CharybdisModelStream = Post::find_by_category_id_and_title(category_id, "Post 2".to_string()) + .execute(db_session) + .await?; + assert!(posts.try_collect().await?.len() > 0); + + let post: Post = Post::find_first_by_category_id_and_title(category_id, "Post 2".to_string()) + .execute(db_session) + .await?; + assert_eq!(post.title, "Post 2"); + + let maybe_post: Option = Post::maybe_find_first_by_category_id_and_title(category_id, "Post 42".to_string()) + .execute(db_session) + .await?; + assert!(maybe_post.is_none()); + + let author_id = post.author_id; + + // find by global secondary index + let posts: CharybdisModelStream = Post::find_by_author_id(author_id).execute(db_session).await?; + assert!(posts.try_collect().await?.len() > 0); + + let post: Post = Post::find_first_by_author_id(author_id).execute(db_session).await?; + assert_eq!(post.author_id, author_id); + + let post: Option = Post::maybe_find_first_by_author_id(Uuid::new_v4()) + .execute(db_session) + .await?; + assert!(post.is_none()); + + Ok(()) +} diff --git a/charybdis/tests/integrations/models.rs b/charybdis/tests/integrations/models.rs deleted file mode 100644 index 688ca2e..0000000 --- a/charybdis/tests/integrations/models.rs +++ /dev/null @@ -1,33 +0,0 @@ -use charybdis::types::{Boolean, Text, Timestamp, Uuid}; -use charybdis_macros::{charybdis_model, charybdis_udt_model}; - -#[derive(Default, Clone)] -#[charybdis_udt_model(type_name = address)] -pub struct Address { - pub street: Text, - pub city: Text, - pub state: Text, - pub zip: Text, - pub country: Text, -} - -#[charybdis_model( - table_name = users, - partition_keys = [id], - clustering_keys = [], - global_secondary_indexes = [username, email], -)] -#[derive(Default)] -pub struct User { - pub id: Uuid, - pub username: Text, - pub email: Text, - pub password: Text, - pub first_name: Text, - pub last_name: Text, - pub bio: Option, - pub address: Option
, - pub is_confirmed: Boolean, - pub created_at: Timestamp, - pub updated_at: Timestamp, -} diff --git a/charybdis/tests/integrations/query.rs b/charybdis/tests/integrations/query.rs index 1849485..67a8d0d 100644 --- a/charybdis/tests/integrations/query.rs +++ b/charybdis/tests/integrations/query.rs @@ -1,30 +1,156 @@ use crate::common::db_session; -use crate::models::{Address, User}; -use charybdis::operations::Insert; +use crate::model::{Post, User}; +use charybdis::batch::ModelBatch; +use charybdis::errors::CharybdisError; +use charybdis::operations::{Delete, Find, Insert, Update}; +use charybdis::stream::CharybdisModelStream; +use scylla::statement::PagingStateResponse; #[tokio::test] -async fn create() { - let user = User { - id: uuid::Uuid::new_v4(), - username: "test".to_string(), - email: "homer@simpson.com".to_string(), - password: "Marge".to_string(), - first_name: "Homer".to_string(), - last_name: "Simpson".to_string(), - bio: Some("I like donuts".to_string()), - address: Some(Address { - street: "742 Evergreen Terrace".to_string(), - city: "Springfield".to_string(), - state: "Illinois".to_string(), - zip: "62701".to_string(), - country: "USA".to_string(), - }), - is_confirmed: true, - created_at: chrono::Utc::now(), - updated_at: chrono::Utc::now(), - }; +async fn model_mutation() { + let id = uuid::Uuid::new_v4(); + let new_user = User::homer(id); let db_session = db_session().await; - user.insert().execute(db_session).await.expect("Failed to insert user"); + new_user + .insert() + .execute(&db_session) + .await + .expect("Failed to insert user"); + + let mut user = User::find_by_id(id) + .execute(&db_session) + .await + .expect("Failed to find user"); + + assert_eq!(user, new_user); + + user.bio = Some("I like beer".to_string()); + + user.update().execute(&db_session).await.expect("Failed to update user"); + + let user = User::find_by_id(id) + .execute(&db_session) + .await + .expect("Failed to find user"); + + assert_eq!(user.bio, Some("I like beer".to_string())); + + user.delete().execute(&db_session).await.expect("Failed to delete user"); +} + +#[tokio::test] +async fn model_row() { + let id = uuid::Uuid::new_v4(); + let new_user = User::homer(id); + + let db_session = db_session().await; + + new_user + .insert() + .execute(&db_session) + .await + .expect("Failed to insert user"); + + let user = User::find_by_id(id) + .execute(&db_session) + .await + .expect("Failed to find user"); + + assert_eq!(user, new_user); + + user.delete().execute(&db_session).await.expect("Failed to delete user"); +} + +#[tokio::test] +async fn optional_model_row() { + let id = uuid::Uuid::new_v4(); + let db_session = db_session().await; + + let user = User::maybe_find_first_by_id(id) + .execute(&db_session) + .await + .expect("Failed to find user"); + + assert_eq!(user, None); +} + +#[tokio::test] +async fn model_stream() { + let db_session = db_session().await; + + User::populate_sample_users().await; + + let users: CharybdisModelStream = User::find_all() + .execute(&db_session) + .await + .expect("Failed to find users"); + let users_vec = users.try_collect().await.expect("Failed to collect users"); + + assert_eq!(users_vec.len(), 32); + + User::delete_batch() + .chunked_delete(&db_session, &users_vec, 100) + .await + .expect("Failed to delete users"); +} + +#[tokio::test] +async fn model_paged() { + let db_session = db_session().await; + let category_id = uuid::Uuid::new_v4(); + + Post::populate_sample_posts_per_partition(category_id).await; + + let (posts, paging_state_response) = Post::find_by_partition_key_value_paged((category_id,)) + .page_size(3) + .execute(&db_session) + .await + .expect("Failed to find posts"); + + let posts = posts + .collect::, CharybdisError>>() + .expect("Failed to collect posts"); + + assert_eq!(posts.len(), 3); + assert_eq!(posts[0].order_idx, 0); + assert_eq!(posts[1].order_idx, 1); + assert_eq!(posts[2].order_idx, 2); + assert!( + matches!(paging_state_response, PagingStateResponse::HasMorePages { .. }), + "Expected more pages, but got NoMorePages" + ); + + if let PagingStateResponse::HasMorePages { state } = paging_state_response { + let (next_page_posts, paging_state_response) = Post::find_by_partition_key_value_paged((category_id,)) + .page_size(30) // 32 is the total number of posts + .paging_state(state) + .execute(&db_session) + .await + .expect("Failed to find posts"); + let next_page_posts = next_page_posts + .collect::, CharybdisError>>() + .expect("Failed to collect posts"); + + assert_eq!(next_page_posts.len(), 29); + assert_eq!(next_page_posts[0].order_idx, 3); + assert_eq!(next_page_posts[1].order_idx, 4); + assert_eq!(next_page_posts[28].order_idx, 31); + + assert!( + matches!(paging_state_response, PagingStateResponse::NoMorePages), + "Expected no more pages, but got HasMorePages" + ); + + Post::delete_batch() + .chunked_delete(&db_session, &next_page_posts, 100) + .await + .expect("Failed to delete posts"); + } + + Post::delete_batch() + .chunked_delete(&db_session, &posts, 100) + .await + .expect("Failed to delete posts"); } From e05a2c4f284a82188ded1e15b60a8cce3c9c0faa Mon Sep 17 00:00:00 2001 From: Goran Brkuljan Date: Fri, 20 Dec 2024 05:11:40 +0100 Subject: [PATCH 7/8] update: force installation of migrate & add build badge --- .github/workflows/build.yml | 2 +- charybdis/README.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 9030d8b..1ff6cb1 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -51,7 +51,7 @@ jobs: - name: Build run: cargo build --verbose --all-targets - name: Install Charybdis Migration Tool - run: cargo install --path charybdis-migrate + run: cargo install --path charybdis-migrate --force - name: Run Charybdis Migration Tool run: migrate --keyspace charybdis --host 127.0.0.1:9042 --drop-and-replace - name: Run tests diff --git a/charybdis/README.md b/charybdis/README.md index fce38f4..d28314a 100644 --- a/charybdis/README.md +++ b/charybdis/README.md @@ -7,7 +7,7 @@ welcomed! [![License](https://img.shields.io/crates/l/charybdis)]() [![Docs.rs](https://docs.rs/charybdis/badge.svg)](https://docs.rs/charybdis) [![Discord](https://img.shields.io/discord/1247167793045176461?label=discord-server)](https://discord.gg/enDd57nNen) - +![Build](https://github.com/nodecosmos/charybdis/actions/workflows/build.yml/badge.svg)

scylla_logo cassandra_logo From a2f03fd138b3e89b1dffd3e22767cf1a99c728b1 Mon Sep 17 00:00:00 2001 From: Goran Brkuljan Date: Fri, 20 Dec 2024 05:17:36 +0100 Subject: [PATCH 8/8] update: test commands --- .github/workflows/build.yml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 1ff6cb1..47fef75 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -43,16 +43,16 @@ jobs: - name: Print clippy version run: cargo clippy --version - name: Format check - run: cargo fmt --verbose --all -- --check + run: cargo fmt --verbose -- --check - name: Clippy check - run: cargo clippy --verbose --all-targets --all-features + run: cargo clippy --verbose - name: Cargo check - run: cargo check --verbose --all-targets --all-features + run: cargo check --verbose - name: Build - run: cargo build --verbose --all-targets + run: cargo build --verbose - name: Install Charybdis Migration Tool run: cargo install --path charybdis-migrate --force - name: Run Charybdis Migration Tool run: migrate --keyspace charybdis --host 127.0.0.1:9042 --drop-and-replace - name: Run tests - run: cargo test --verbose --all-targets + run: cargo test --verbose