shardmap 0.3.2

Sharded embedded in-memory map with optional cache, protocol, and server internals
Documentation
mod common;

use shardmap::protocol::Frame;

use common::{TestHarness, command, distinct_keys_for_shards};

#[tokio::test(flavor = "multi_thread")]
async fn basic_commands_work() {
    let harness = TestHarness::new(false);

    let ping = harness.engine.execute(command(&[b"PING"])).await.unwrap();
    assert_eq!(ping, Frame::SimpleString("PONG".into()));

    let set = harness
        .engine
        .execute(command(&[b"SET", b"alpha", b"one"]))
        .await
        .unwrap();
    assert_eq!(set, Frame::SimpleString("OK".into()));

    let get = harness
        .engine
        .execute(command(&[b"GET", b"alpha"]))
        .await
        .unwrap();
    assert_eq!(get, Frame::BlobString(b"one".to_vec()));

    let exists = harness
        .engine
        .execute(command(&[b"EXISTS", b"alpha", b"missing"]))
        .await
        .unwrap();
    assert_eq!(exists, Frame::Integer(1));

    let expire = harness
        .engine
        .execute(command(&[b"EXPIRE", b"alpha", b"1"]))
        .await
        .unwrap();
    assert_eq!(expire, Frame::Integer(1));

    let ttl = harness
        .engine
        .execute(command(&[b"TTL", b"alpha"]))
        .await
        .unwrap();
    match ttl {
        Frame::Integer(value) => assert!(value >= 0),
        other => panic!("unexpected ttl frame: {other:?}"),
    }

    tokio::time::sleep(std::time::Duration::from_millis(1_100)).await;
    let expired = harness
        .engine
        .execute(command(&[b"GET", b"alpha"]))
        .await
        .unwrap();
    assert_eq!(expired, Frame::Null);

    harness.shutdown().await;
}

#[tokio::test(flavor = "multi_thread")]
async fn mset_and_mget_preserve_order_across_shards() {
    let harness = TestHarness::new(false);
    let keys = distinct_keys_for_shards(harness.config.shard_count);

    let mut owned_parts = vec![b"MSET".to_vec()];
    for (index, key) in keys.iter().enumerate() {
        owned_parts.push(key.clone());
        owned_parts.push(format!("value-{index}").into_bytes());
    }
    let parts = owned_parts.iter().map(Vec::as_slice).collect::<Vec<_>>();

    let set = harness.engine.execute(command(&parts)).await.unwrap();
    assert_eq!(set, Frame::SimpleString("OK".into()));

    let mut owned_get_parts = vec![b"MGET".to_vec()];
    for key in &keys {
        owned_get_parts.push(key.clone());
    }
    let get_parts = owned_get_parts
        .iter()
        .map(Vec::as_slice)
        .collect::<Vec<_>>();

    let values = harness.engine.execute(command(&get_parts)).await.unwrap();
    match values {
        Frame::Array(items) => {
            assert_eq!(items.len(), keys.len());
            for (index, item) in items.into_iter().enumerate() {
                assert_eq!(
                    item,
                    Frame::BlobString(format!("value-{index}").into_bytes())
                );
            }
        }
        other => panic!("unexpected mget response: {other:?}"),
    }

    let stats = harness.engine.execute(command(&[b"STATS"])).await.unwrap();
    match stats {
        Frame::BlobString(body) => {
            let text = String::from_utf8(body).unwrap();
            assert!(text.contains("\"shard_count\""));
        }
        other => panic!("unexpected stats response: {other:?}"),
    }

    harness.shutdown().await;
}