11 unstable releases (3 breaking)

0.4.4 Oct 3, 2024
0.4.3 Oct 3, 2024
0.4.2 Sep 23, 2024
0.3.2 Sep 19, 2024
0.1.1 Sep 3, 2024

#349 in Database implementations

Download history 261/week @ 2024-08-29 1651/week @ 2024-09-05 1920/week @ 2024-09-12 1867/week @ 2024-09-19 1382/week @ 2024-09-26 1812/week @ 2024-10-03 607/week @ 2024-10-10

5,953 downloads per month
Used in 6 crates (3 directly)

BUSL-1.1

200KB
4K SLoC

SurrealCS Client

The SurrealCS client is a simple key-value store client that can be used to interact with the SurrealCS server.

Connections

The connection between the client and the server consists of a pool of TCP connections. Lets say we want to create a pool of 3 connections, we can do this with the following code:

use surrealcs::router::create_connection_pool;

let ids: Vec<usize> = create_connection_pool("127.0.0.1:8080", Some(3)).await.unwrap();

The create_connection_pool function returns a Vec<usize> which is the indexes of the connection in the pool. This index is not really needed, as allocation is done automatically, but it can be useful for debugging purposes. If the number of connections is not specified, the default number of connections will be the number of cores on the machine.

We can also create a single connection with the following code:

use surrealcs::router::{send_to_router, create_connection};

let id = create_connection("127.0.0.1:8080", send_to_router).await.unwrap();

The create_connection function returns a usize which is the index of the connection in the pool. This index is not really needed, as allocation is done automatically, but it can be useful for debugging purposes. The send_to_router is a function that sends to the global connection allocator. You can also create your own function that handles the sending to a connection allocator. However, you must also manage your own connection pool. This can be useful for testing purposes or if you wanted to have different connection pools to different servers.

Strict Transactions

Strict transactions are when the functions available to the transaction handle are restricted based on the state of the transaction. We can also exploit the Rust compiler to check the state of the transaction, meaning that we can strict a function input of a transaction that only has a particular state if needed by the following:

fn some_function(transaction: &mut Transaction<NotStarted>) {
    // Do something
}
fn another_function(transaction: &mut Transaction<InProgress>) {
    // Do something
}
fn yet_another_function(transaction: &mut Transaction<Committed>) {
    // Do something
}
fn final_function(transaction: &mut Transaction<RolledBack>) {
    // Do something
}

We can start a transaction with the following code:

use surrealcs_kernel::messages::server::{
    interface::ServerTransactionMessage,
    kv_operations::{MessageDel, MessageGet, MessagePut},
    message::{KeyValueOperationType, Transaction as TransactionMessage};
};
use surrealcs::transactions::interface::interface::{BridgeHandle, Transaction};

let transaction: Transaction<NotStarted> = Transaction::new().await.unwrap();

// begin a transaction with a put
let message = ServerTransactionMessage::Put(MessagePut {
    key: b"key".to_vec(),
    value: b"value".to_vec(),
});
// returns a Transaction<InProgress>
let (message, mut transaction) = transaction.begin::<BridgeHandle>(message).await.unwrap();
match outcome {
    ServerTransactionMessage::EmptyResponse => {},
    _ => panic!("should have been a put"),
};

// send a get message
let message = ServerTransactionMessage::Get(MessageGet {
    key: b"key".to_vec(),
});
// returns a Transaction<InProgress>
let outcome = transaction.send::<BridgeHandle>(message).await.unwrap();
let outcome = match outcome {
    ServerTransactionMessage::ResponseGet(outcome) => outcome,
    _ => panic!("should have been a put"),
};

// commit with a get transaction
let message = ServerTransactionMessage::Get(MessageGet {
    key: b"key".to_vec(),
});
// returns a Transaction<Committed>
// We also have an empty_commit function that can be used to commit a transaction without 
// any additional messages
let (outcome, _transaction) = transaction.commit::<BridgeHandle>(message).await.unwrap();

It must be noted that we utilize a BridgeHandle when sending messages to our transaction actor associated with the handle. This handle facilitates the sending of the message to the actor and receiving the response from the actor. We have a handle to allow users to implement their own handles if they wish to do so. The handle also helps with unit testing to see how the transaction handle handles multiple edgecases. If you want to implement your own handle, you can do so by implement the surrealcs::transactions::interface::bridge::SendToTransactionClientActor for a struct. The SendToTransactionClientActor does not reference the state of the struct implementing the trait.

Relaxed Transactions

Relaxed transactions are built on top of strict transactions. Please read the strict transactions section before reading this section.

Relaxed transactions are transactions that allow the user to send any message and the state does not change. To create a relaxed transaction, we can do the following:

use surrealcs::transactions::interface::interface::{
    Transaction,
    Any
};

let transaction: Transaction<Any> = Transaction::new().await.unwrap();

You can then perform a range of operations on the transaction without the state changing. This is useful for legacy systems that are not designed to support strict transactions.

Dependencies

~7–14MB
~159K SLoC