charybdis 1.1.0

High-Performance ORM for ScyllaDB
Documentation
use crate::common::db_session;
use crate::custom_fields::AddressTypeCustomField;
use crate::model::{Post, User, SAMPLE_MODEL_COUNT};
use charybdis::batch::ModelBatch;
use charybdis::errors::CharybdisError;
use charybdis::operations::{Delete, Find, Insert, Update};
use charybdis::scylla::PagingStateResponse;
use charybdis::stream::CharybdisModelStream;

#[tokio::test]
async fn model_mutation() {
    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 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.address.as_mut().expect("homer should have address").addr_type = AddressTypeCustomField::HomeAddress;
    let tag = ("Second Key".to_string(), "Second Value".to_string());
    user.user_extra_data.user_tags.push(tag.clone());

    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()));
    assert_eq!(
        user.address.as_ref().expect("homer should have address").addr_type,
        AddressTypeCustomField::HomeAddress
    );
    assert_eq!(user.user_extra_data.user_tags.last().cloned(), Some(tag));

    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> = 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(), SAMPLE_MODEL_COUNT);

    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, None).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::<Result<Vec<Post>, 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::<Result<Vec<Post>, 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");
}

#[tokio::test]
async fn model_multi_field_paged() {
    let db_session = db_session().await;
    let category_id = uuid::Uuid::new_v4();
    let author_id = uuid::Uuid::new_v4();
    let repeated_order_idx = 42;

    let posts_to_insert = (0..5)
        .map(|i| Post {
            category_id,
            order_idx: repeated_order_idx,
            title: format!("Paged Post {i}"),
            content: format!("Paged content {i}"),
            author_id,
        })
        .collect::<Vec<Post>>();

    Post::batch()
        .chunked_insert(&db_session, &posts_to_insert, 100)
        .await
        .expect("Failed to insert paged posts");

    let (first_page_iter, paging_state_response) =
        Post::find_by_category_id_and_order_idx_paged(category_id, repeated_order_idx)
            .page_size(2)
            .execute(&db_session)
            .await
            .expect("Failed to fetch first page of posts");

    let first_page = first_page_iter
        .collect::<Result<Vec<Post>, CharybdisError>>()
        .expect("Failed to collect first paged posts");

    assert_eq!(first_page.len(), 2);
    assert!(first_page.iter().all(|post| post.order_idx == repeated_order_idx));
    assert!(
        matches!(paging_state_response, PagingStateResponse::HasMorePages { .. }),
        "Expected paged query to have additional pages"
    );

    let mut collected_posts = first_page.clone();

    if let PagingStateResponse::HasMorePages { state } = paging_state_response {
        let (next_page_iter, next_paging_state_response) =
            Post::find_by_category_id_and_order_idx_paged(category_id, repeated_order_idx)
                .page_size(10)
                .paging_state(state)
                .execute(&db_session)
                .await
                .expect("Failed to fetch second page of posts");

        let next_page = next_page_iter
            .collect::<Result<Vec<Post>, CharybdisError>>()
            .expect("Failed to collect second paged posts");

        assert_eq!(next_page.len(), 3);
        assert!(next_page.iter().all(|post| post.order_idx == repeated_order_idx));
        assert!(
            matches!(next_paging_state_response, PagingStateResponse::NoMorePages),
            "Expected paged query to finish after the second call"
        );

        collected_posts.extend(next_page);
    } else {
        panic!("Expected additional pages for multi-field paged query");
    }

    assert_eq!(collected_posts.len(), posts_to_insert.len());

    Post::delete_batch()
        .chunked_delete(&db_session, &posts_to_insert, 100)
        .await
        .expect("Failed to delete paged posts");
}