Feedback request: a client library for HyperDex

Hi guys,

I'm a member of the HyperDex dev team and I have been developing a client binding in Rust. HyperDex is a distributed key-value store akin to Cassandra but with some novel features and better performance. We are developing this Rust binding because we really like how Rust champions performance as well as safety, the two traits that HyperDex also values.

However, since I'm relatively new to Rust, I'm not entirely sure if my API is idiomatic/ergonomic. Therefore, I'd really appreciate if someone could take a look at the following example and tell me if there are any ways to make the API better.

Link

Here is the project: GitHub - derekchiang/rust-hyperdex: A Rust binding for HyperDex You can find complete examples in src/test.rs. The README also links to the documentation, although due to link limits I can't post it here.

An Example

Let's first declare some constants:

static coord_addr: &'static str = "127.0.0.1:1982";

static space_name: &'static str = "contacts";

static space_desc: &'static str = "
space contacts
key username
attributes first, last, int age
subspace first, last
create 2 partitions
tolerate 2 failures";

In order, these constants specify the address the HyperDex coordinator is listening at, the name of the space we are using, and the description of the space. In HyperDex parlance, a space is like a table, whereas a description is like the schema of a table.

To create a space, we need to first create an Admin.

let admin = Admin::new(from_str(coord_addr).unwrap()).unwrap();

Then we create a space:

match admin.add_space(space_desc) {
    Ok(()) => (),
    Err(err) => panic!(err),  // deal with the error appropriately
};

Now we create a Client object that we will use to manipulate data.

let mut client = Client::new(from_str(coord_addr).unwrap()).unwrap();

Now we put some objects:

    match client.put(space_name, "derek", NewHyperObject!(
    "first", "Derek",
    "last", "Chiang",
    "age", 20,
)) {
    Ok(()) => (),
    Err(err) => panic!(err),
}

match client.put(space_name, "robert", NewHyperObject!(
    "first", "Robert",
    "last", "Escriva",
    "age", 25,
)) {
    Ok(()) => (),
    Err(err) => panic!(err),
}

Note that I have made an NewHyperObject! macro to facilitate creating objects. The actual low-level API looks like this:

let mut obj = HyperObject::new();
obj.insert("first", "Emin");
obj.insert("last", "Sirer");
obj.insert("age", 30);

Also, all APIs come in two flavors: synchronous and asynchronous. So far we have been using only synchronous functions, but here is how an async API looks like:

let fut = client.async_put(space_name, "emin", obj);

Which immediately returns fut, an object of type Future. When you are ready to check the result:

match fut.unwrap() {
    Ok(()) => (),
    Err(err) => panic!(err),
}

Now, to query all objects (HyperDex lets you query objects using secondary attributes, like age in this case) whose age attribute is less than or equal to 25:

let predicates = vec!(HyperPredicate::new("age", LESS_EQUAL, 25));
let res = client.search(space_name, predicates);

res is a channel that returns the objects that match the query, so we obtain the objects like so:

for obj_res in res.iter() {
    let obj = obj_res.unwrap();
    let name: Vec<u8> = obj.get("first").unwrap();
    let age: i64 = obj.get("age").unwrap();
    assert!(age <= 25);
}

Finally this is how we remove the space.

admin.remove_space(space_name).unwrap();

Wrap up

Thanks a lot for reading such a long post. Please let me know how you like the API and if you see any way to make it more idiomatic / ergonomic. I'd really appreciate any feedback! Feel free to shoot an email to derekchiang93@gmail.com if you want to reach me privately.

Best,
Derek

1 Like

This looks great! I'm new to Rust, but these bindings make it possible to use my favorite new database with my favorite new language. Keep up the good work!