C++ Bindings
Yokan provides comprehensive header-only C++ bindings that wrap the C API with modern C++ idioms, RAII resource management, and exception handling.
Overview
The C++ bindings are located in include/yokan/cxx/ and provide C++ classes
equivalent to the C opaque pointers:
yokan::Client- Wrapper foryk_client_tyokan::Database- Wrapper foryk_database_handle_tyokan::Collection- Wrapper for document collectionsyokan::Exception- C++ exception for error handling
Warning
Some C++ functions have parameters in a different order than their C equivalents. In particular, functions that take a mode have this mode as the last parameter to allow C++ optional parameters.
Quick Start
Here’s a minimal example using the C++ API:
/*
* (C) 2024 The University of Chicago
*
* See COPYRIGHT in top-level directory.
*/
#include <thallium.hpp>
#include <yokan/cxx/client.hpp>
#include <yokan/cxx/database.hpp>
#include <iostream>
#include <string>
namespace tl = thallium;
int main() {
try {
// Initialize Thallium engine
tl::engine engine("na+sm", THALLIUM_CLIENT_MODE);
// Create a client using the Margo instance from Thallium
yokan::Client client(engine.get_margo_instance());
// Look up the server address
tl::endpoint server_ep = engine.lookup("na+sm://localhost:1234");
// Connect to a database using the resolved address
yokan::Database db = client.makeDatabaseHandle(
server_ep.get_addr(), 42);
// Put a value
std::string key = "greeting";
std::string value = "Hello, Yokan!";
db.put(key.data(), key.size(), value.data(), value.size());
// Get the value back
std::vector<char> buffer(256);
size_t vsize = buffer.size();
db.get(key.data(), key.size(), buffer.data(), &vsize);
std::string retrieved(buffer.data(), vsize);
std::cout << "Retrieved: " << retrieved << std::endl;
} catch(const yokan::Exception& ex) {
std::cerr << "Error: " << ex.what() << std::endl;
return 1;
}
return 0;
}
This demonstrates:
Automatic resource management (RAII)
Exception-based error handling
Clean, modern C++ API
Client and Database Handles
Creating Clients and Databases
/*
* (C) 2024 The University of Chicago
*
* See COPYRIGHT in top-level directory.
*/
#include <thallium.hpp>
#include <yokan/cxx/client.hpp>
#include <yokan/cxx/database.hpp>
#include <iostream>
namespace tl = thallium;
int main() {
try {
// Initialize Thallium engine
tl::engine engine("na+sm", THALLIUM_CLIENT_MODE);
// Create a client using the Margo instance from Thallium
yokan::Client client(engine.get_margo_instance());
std::cout << "Client created" << std::endl;
// Look up the server address
tl::endpoint server_ep = engine.lookup("na+sm://localhost:1234");
// Connect to a database by address and provider ID
yokan::Database db = client.makeDatabaseHandle(
server_ep.get_addr(), // Server address (hg_addr_t)
42, // Provider ID
true // Check validity
);
std::cout << "Database handle created" << std::endl;
// The client and database are reference counted
// They'll be automatically cleaned up when they go out of scope
// Copy the database handle (increments reference count)
yokan::Database db_copy = db;
std::cout << "Database handle copied" << std::endl;
// Move the database handle (transfers ownership)
yokan::Database db_moved = std::move(db_copy);
std::cout << "Database handle moved" << std::endl;
// db_copy is now invalid, db_moved and db are valid
// All will be cleaned up automatically
} catch(const yokan::Exception& ex) {
std::cerr << "Error: " << ex.what() << std::endl;
return 1;
}
return 0;
}
The yokan::Client and yokan::Database classes:
Use RAII for automatic cleanup
Are copyable (reference counted)
Are movable for efficient transfer
Throw
yokan::Exceptionon errors
Basic Operations
Put, Get, Exists, and Erase
/*
* (C) 2024 The University of Chicago
*
* See COPYRIGHT in top-level directory.
*/
#include <thallium.hpp>
#include <yokan/cxx/database.hpp>
#include <yokan/cxx/client.hpp>
#include <iostream>
#include <string>
#include <vector>
namespace tl = thallium;
int main(int argc, char** argv) {
if(argc != 3) {
std::cerr << "Usage: " << argv[0] << " <server_addr> <provider_id>" << std::endl;
return 1;
}
try {
// Initialize Thallium engine
tl::engine engine("na+sm", THALLIUM_CLIENT_MODE);
// Create a client using the Margo instance from Thallium
yokan::Client client(engine.get_margo_instance());
// Look up the server address
tl::endpoint server_ep = engine.lookup(argv[1]);
// Create database handle
yokan::Database db = client.makeDatabaseHandle(
server_ep.get_addr(), std::atoi(argv[2]));
// Put operation
std::string key = "user:1001";
std::string value = "Alice Johnson";
db.put(key.data(), key.size(), value.data(), value.size());
std::cout << "Stored: " << key << " = " << value << std::endl;
// Get operation
std::vector<char> buffer(256);
size_t vsize = buffer.size();
db.get(key.data(), key.size(), buffer.data(), &vsize);
std::string retrieved(buffer.data(), vsize);
std::cout << "Retrieved: " << retrieved << std::endl;
// Exists operation
bool exists = db.exists(key.data(), key.size());
std::cout << "Key exists: " << (exists ? "yes" : "no") << std::endl;
// Length operation
size_t length = db.length(key.data(), key.size());
std::cout << "Value length: " << length << " bytes" << std::endl;
// Erase operation
db.erase(key.data(), key.size());
std::cout << "Key erased" << std::endl;
// Verify erasure
bool exists_after = db.exists(key.data(), key.size());
std::cout << "Key exists after erase: " << (exists_after ? "yes" : "no") << std::endl;
} catch(const yokan::Exception& ex) {
std::cerr << "Error: " << ex.what() << std::endl;
return 1;
}
return 0;
}
All basic operations:
Accept
const void*andsize_tfor binary dataSupport strings through
.data()and.size()Take an optional mode parameter (defaults to
YOKAN_MODE_DEFAULT)Throw
yokan::Exceptionon errors
Batch Operations
Multi-key Operations
For efficiency, use batch operations when working with multiple keys:
/*
* (C) 2024 The University of Chicago
*
* See COPYRIGHT in top-level directory.
*/
#include <thallium.hpp>
#include <yokan/cxx/database.hpp>
#include <yokan/cxx/client.hpp>
#include <iostream>
#include <vector>
#include <string>
namespace tl = thallium;
int main(int argc, char** argv) {
if(argc != 3) {
std::cerr << "Usage: " << argv[0] << " <server_addr> <provider_id>" << std::endl;
return 1;
}
try {
// Initialize Thallium engine
tl::engine engine("na+sm", THALLIUM_CLIENT_MODE);
// Create a client using the Margo instance from Thallium
yokan::Client client(engine.get_margo_instance());
// Look up the server address
tl::endpoint server_ep = engine.lookup(argv[1]);
// Create database handle
yokan::Database db = client.makeDatabaseHandle(
server_ep.get_addr(), std::atoi(argv[2]));
// Prepare multiple key/value pairs
std::vector<std::string> keys = {"user:1", "user:2", "user:3"};
std::vector<std::string> values = {"Alice", "Bob", "Carol"};
// Convert to raw pointers for batch API
std::vector<const void*> key_ptrs;
std::vector<size_t> key_sizes;
std::vector<const void*> value_ptrs;
std::vector<size_t> value_sizes;
for(const auto& k : keys) {
key_ptrs.push_back(k.data());
key_sizes.push_back(k.size());
}
for(const auto& v : values) {
value_ptrs.push_back(v.data());
value_sizes.push_back(v.size());
}
// Put multiple key/value pairs at once
db.putMulti(keys.size(),
key_ptrs.data(), key_sizes.data(),
value_ptrs.data(), value_sizes.data());
std::cout << "Stored " << keys.size() << " key/value pairs" << std::endl;
// Get multiple values at once
std::vector<std::vector<char>> buffers(keys.size(), std::vector<char>(256));
std::vector<void*> buffer_ptrs;
std::vector<size_t> buffer_sizes;
for(auto& buf : buffers) {
buffer_ptrs.push_back(buf.data());
buffer_sizes.push_back(buf.size());
}
db.getMulti(keys.size(),
key_ptrs.data(), key_sizes.data(),
buffer_ptrs.data(), buffer_sizes.data());
std::cout << "Retrieved values:" << std::endl;
for(size_t i = 0; i < keys.size(); i++) {
std::string value(buffers[i].data(), buffer_sizes[i]);
std::cout << " " << keys[i] << " = " << value << std::endl;
}
// Check existence of multiple keys
std::vector<bool> existence = db.existsMulti(keys.size(),
key_ptrs.data(), key_sizes.data());
std::cout << "Existence check:" << std::endl;
for(size_t i = 0; i < keys.size(); i++) {
std::cout << " " << keys[i] << ": " << (existence[i] ? "exists" : "missing") << std::endl;
}
// Erase multiple keys
db.eraseMulti(keys.size(), key_ptrs.data(), key_sizes.data());
std::cout << "Erased " << keys.size() << " keys" << std::endl;
} catch(const yokan::Exception& ex) {
std::cerr << "Error: " << ex.what() << std::endl;
return 1;
}
return 0;
}
Batch operations reduce network round-trips and improve throughput significantly.
String and Vector Helpers
The C++ API provides convenient helpers for working with std::string and
std::vector:
/*
* (C) 2024 The University of Chicago
*
* See COPYRIGHT in top-level directory.
*/
#include <thallium.hpp>
#include <yokan/cxx/database.hpp>
#include <yokan/cxx/client.hpp>
#include <iostream>
#include <string>
#include <vector>
namespace tl = thallium;
int main(int argc, char** argv) {
if(argc != 3) {
std::cerr << "Usage: " << argv[0] << " <server_addr> <provider_id>" << std::endl;
return 1;
}
try {
tl::engine engine("na+sm", THALLIUM_CLIENT_MODE);
yokan::Client client(engine.get_margo_instance());
tl::endpoint server_ep = engine.lookup(argv[1]);
yokan::Database db = client.makeDatabaseHandle(
server_ep.get_addr(), std::atoi(argv[2]));
// Working with std::string directly
std::string key = "user:1001";
std::string value = "Alice Johnson";
// Put using string helper
db.put(key.data(), key.size(), value.data(), value.size());
// Get with automatic sizing using std::vector
std::vector<char> result;
// First get the size
size_t vsize = db.length(key.data(), key.size());
// Resize vector and get data
result.resize(vsize);
db.get(key.data(), key.size(), result.data(), &vsize);
// Convert to string
std::string retrieved(result.data(), vsize);
std::cout << "Retrieved: " << retrieved << std::endl;
// Working with multiple keys using vectors
std::vector<std::string> keys = {"user:1001", "user:1002", "user:1003"};
std::vector<std::string> values = {"Alice", "Bob", "Carol"};
// Convert to pointer/size arrays for batch operations
std::vector<const void*> key_ptrs;
std::vector<size_t> key_sizes;
std::vector<const void*> val_ptrs;
std::vector<size_t> val_sizes;
for(const auto& k : keys) {
key_ptrs.push_back(k.data());
key_sizes.push_back(k.size());
}
for(const auto& v : values) {
val_ptrs.push_back(v.data());
val_sizes.push_back(v.size());
}
// Batch put
db.putMulti(keys.size(), key_ptrs.data(), key_sizes.data(),
val_ptrs.data(), val_sizes.data());
std::cout << "Stored " << keys.size() << " key-value pairs" << std::endl;
} catch(const yokan::Exception& ex) {
std::cerr << "Error: " << ex.what() << std::endl;
return 1;
}
return 0;
}
List Operations
The C++ API provides wrappers for list operations that work with callbacks:
/*
* (C) 2024 The University of Chicago
*
* See COPYRIGHT in top-level directory.
*/
#include <thallium.hpp>
#include <yokan/cxx/database.hpp>
#include <yokan/cxx/client.hpp>
#include <iostream>
#include <string>
#include <vector>
namespace tl = thallium;
int main(int argc, char** argv) {
if(argc != 3) {
std::cerr << "Usage: " << argv[0] << " <server_addr> <provider_id>" << std::endl;
return 1;
}
try {
tl::engine engine("na+sm", THALLIUM_CLIENT_MODE);
yokan::Client client(engine.get_margo_instance());
tl::endpoint server_ep = engine.lookup(argv[1]);
yokan::Database db = client.makeDatabaseHandle(
server_ep.get_addr(), std::atoi(argv[2]));
// Insert test data
std::vector<std::string> keys = {"user:001", "user:002", "user:003", "user:004"};
std::vector<std::string> values = {"Alice", "Bob", "Carol", "Dave"};
for(size_t i = 0; i < keys.size(); i++) {
db.put(keys[i].data(), keys[i].size(),
values[i].data(), values[i].size());
}
std::cout << "Inserted " << keys.size() << " records" << std::endl;
// List key-value pairs using iter
std::string from_key = "user:";
std::string filter = "";
std::cout << "\nListing key-value pairs:" << std::endl;
// Create callback using C++ lambda
auto keyval_callback = [](size_t index, const void* key, size_t ksize,
const void* val, size_t vsize) -> yk_return_t {
std::string k(static_cast<const char*>(key), ksize);
std::string v(static_cast<const char*>(val), vsize);
std::cout << " [" << index << "] " << k << " = " << v << std::endl;
return YOKAN_SUCCESS;
};
db.iter(from_key.data(), from_key.size(),
filter.data(), filter.size(),
0, /* count = 0 means all */
keyval_callback);
std::cout << "\nIteration completed successfully!" << std::endl;
} catch(const yokan::Exception& ex) {
std::cerr << "Error: " << ex.what() << std::endl;
return 1;
}
return 0;
}
Alternatively, use the packed variants for lower-level control:
/*
* (C) 2024 The University of Chicago
*
* See COPYRIGHT in top-level directory.
*/
#include <thallium.hpp>
#include <yokan/cxx/database.hpp>
#include <yokan/cxx/client.hpp>
#include <iostream>
#include <string>
#include <vector>
namespace tl = thallium;
int main(int argc, char** argv) {
if(argc != 3) {
std::cerr << "Usage: " << argv[0] << " <server_addr> <provider_id>" << std::endl;
return 1;
}
try {
tl::engine engine("na+sm", THALLIUM_CLIENT_MODE);
yokan::Client client(engine.get_margo_instance());
tl::endpoint server_ep = engine.lookup(argv[1]);
yokan::Database db = client.makeDatabaseHandle(
server_ep.get_addr(), std::atoi(argv[2]));
// Insert test data
for(int i = 1; i <= 5; i++) {
std::string key = "item:" + std::to_string(i);
std::string value = "value" + std::to_string(i);
db.put(key.data(), key.size(), value.data(), value.size());
}
// List keys using packed format
std::string from_key = "item:";
std::string filter = "";
// Allocate buffer for packed keys
std::vector<char> packed_keys(1024);
std::vector<size_t> key_sizes(10);
size_t count = 10;
db.listKeysPacked(from_key.data(), from_key.size(),
filter.data(), filter.size(),
count,
packed_keys.data(), packed_keys.size(),
key_sizes.data());
std::cout << "Listed " << count << " keys:" << std::endl;
// Parse packed keys
size_t offset = 0;
for(size_t i = 0; i < count; i++) {
std::string key(packed_keys.data() + offset, key_sizes[i]);
std::cout << " " << key << std::endl;
offset += key_sizes[i];
}
// List key-value pairs using packed format
std::vector<char> packed_vals(1024);
std::vector<size_t> val_sizes(10);
count = 10;
db.listKeyValsPacked(from_key.data(), from_key.size(),
filter.data(), filter.size(),
count,
packed_keys.data(), packed_keys.size(),
key_sizes.data(),
packed_vals.data(), packed_vals.size(),
val_sizes.data());
std::cout << "\nListed " << count << " key-value pairs:" << std::endl;
// Parse packed key-value pairs
size_t key_offset = 0;
size_t val_offset = 0;
for(size_t i = 0; i < count; i++) {
std::string key(packed_keys.data() + key_offset, key_sizes[i]);
std::string value(packed_vals.data() + val_offset, val_sizes[i]);
std::cout << " " << key << " = " << value << std::endl;
key_offset += key_sizes[i];
val_offset += val_sizes[i];
}
} catch(const yokan::Exception& ex) {
std::cerr << "Error: " << ex.what() << std::endl;
return 1;
}
return 0;
}
Working with Modes
Modes modify operation semantics and can be combined using bitwise OR:
/*
* (C) 2024 The University of Chicago
*
* See COPYRIGHT in top-level directory.
*/
#include <thallium.hpp>
#include <yokan/cxx/database.hpp>
#include <yokan/cxx/client.hpp>
#include <iostream>
#include <string>
namespace tl = thallium;
int main(int argc, char** argv) {
if(argc != 3) {
std::cerr << "Usage: " << argv[0] << " <server_addr> <provider_id>" << std::endl;
return 1;
}
try {
// Initialize Thallium engine
tl::engine engine("na+sm", THALLIUM_CLIENT_MODE);
// Create a client using the Margo instance from Thallium
yokan::Client client(engine.get_margo_instance());
// Look up the server address
tl::endpoint server_ep = engine.lookup(argv[1]);
// Create database handle
yokan::Database db = client.makeDatabaseHandle(
server_ep.get_addr(), std::atoi(argv[2]));
// APPEND mode: Append to existing values
std::string log_key = "application_log";
std::string entry1 = "Entry 1\n";
std::string entry2 = "Entry 2\n";
std::string entry3 = "Entry 3\n";
db.put(log_key.data(), log_key.size(), entry1.data(), entry1.size());
db.put(log_key.data(), log_key.size(), entry2.data(), entry2.size(),
YOKAN_MODE_APPEND);
db.put(log_key.data(), log_key.size(), entry3.data(), entry3.size(),
YOKAN_MODE_APPEND);
std::vector<char> log_buffer(1024);
size_t log_size = log_buffer.size();
db.get(log_key.data(), log_key.size(), log_buffer.data(), &log_size);
std::string log(log_buffer.data(), log_size);
std::cout << "Appended log:\n" << log << std::endl;
// CONSUME mode: Get and erase atomically
std::string task_key = "pending_task";
std::string task_value = "Process data";
db.put(task_key.data(), task_key.size(), task_value.data(), task_value.size());
std::vector<char> task_buffer(256);
size_t task_size = task_buffer.size();
db.get(task_key.data(), task_key.size(), task_buffer.data(), &task_size,
YOKAN_MODE_CONSUME);
std::string task(task_buffer.data(), task_size);
std::cout << "Consumed task: " << task << std::endl;
bool still_exists = db.exists(task_key.data(), task_key.size());
std::cout << "Task still exists: " << (still_exists ? "yes" : "no") << std::endl;
// NEW_ONLY mode: Only put if key doesn't exist
std::string counter_key = "counter";
std::string initial_value = "1";
db.put(counter_key.data(), counter_key.size(),
initial_value.data(), initial_value.size(),
YOKAN_MODE_NEW_ONLY);
std::cout << "Initial counter set" << std::endl;
try {
std::string new_value = "2";
db.put(counter_key.data(), counter_key.size(),
new_value.data(), new_value.size(),
YOKAN_MODE_NEW_ONLY);
std::cout << "Second put shouldn't succeed!" << std::endl;
} catch(const yokan::Exception& ex) {
std::cout << "Expected: Can't overwrite with NEW_ONLY" << std::endl;
}
// Combining modes
std::string multi_mode_key = "combined";
std::string multi_value = "test";
db.put(multi_mode_key.data(), multi_mode_key.size(),
multi_value.data(), multi_value.size());
std::vector<char> combined_buffer(256);
size_t combined_size = combined_buffer.size();
db.get(multi_mode_key.data(), multi_mode_key.size(),
combined_buffer.data(), &combined_size,
YOKAN_MODE_CONSUME | YOKAN_MODE_NO_RDMA);
std::cout << "Used combined modes: CONSUME | NO_RDMA" << std::endl;
} catch(const yokan::Exception& ex) {
std::cerr << "Error: " << ex.what() << std::endl;
return 1;
}
return 0;
}
Available modes include:
YOKAN_MODE_DEFAULT- Default behaviorYOKAN_MODE_INCLUSIVE- Include lower bound in listsYOKAN_MODE_APPEND- Append to valuesYOKAN_MODE_CONSUME- Get and erase atomicallyYOKAN_MODE_WAIT- Wait for keys to appearYOKAN_MODE_NOTIFY- Notify waiting clientsYOKAN_MODE_NEW_ONLY- Only put if key doesn’t existYOKAN_MODE_EXIST_ONLY- Only put if key existsYOKAN_MODE_NO_RDMA- Disable RDMA for small data
Exception Handling
The C++ API throws yokan::Exception for all errors:
/*
* (C) 2024 The University of Chicago
*
* See COPYRIGHT in top-level directory.
*/
#include <thallium.hpp>
#include <yokan/cxx/database.hpp>
#include <yokan/cxx/client.hpp>
#include <iostream>
#include <string>
namespace tl = thallium;
int main(int argc, char** argv) {
if(argc != 3) {
std::cerr << "Usage: " << argv[0] << " <server_addr> <provider_id>" << std::endl;
return 1;
}
try {
// Initialize Thallium engine
tl::engine engine("na+sm", THALLIUM_CLIENT_MODE);
// Create a client using the Margo instance from Thallium
yokan::Client client(engine.get_margo_instance());
// Look up the server address
tl::endpoint server_ep = engine.lookup(argv[1]);
// Create database handle
yokan::Database db = client.makeDatabaseHandle(
server_ep.get_addr(), std::atoi(argv[2]));
// Example 1: Handling missing keys
std::cout << "=== Example 1: Missing key ===" << std::endl;
try {
std::string key = "nonexistent";
std::vector<char> buffer(256);
size_t size = buffer.size();
db.get(key.data(), key.size(), buffer.data(), &size);
} catch(const yokan::Exception& ex) {
std::cout << "Expected error - key not found: " << ex.what() << std::endl;
}
// Example 2: Safe get with existence check
std::cout << "\n=== Example 2: Safe get ===" << std::endl;
std::string key = "safe_key";
if(db.exists(key.data(), key.size())) {
std::vector<char> buffer(256);
size_t size = buffer.size();
db.get(key.data(), key.size(), buffer.data(), &size);
std::string value(buffer.data(), size);
std::cout << "Value: " << value << std::endl;
} else {
std::cout << "Key doesn't exist, using default" << std::endl;
}
// Example 3: Exception information
std::cout << "\n=== Example 3: Exception details ===" << std::endl;
try {
std::string missing_key = "another_missing_key";
std::vector<char> buffer(256);
size_t size = buffer.size();
db.get(missing_key.data(), missing_key.size(), buffer.data(), &size);
} catch(const yokan::Exception& ex) {
std::cout << "Exception message: " << ex.what() << std::endl;
// The exception message contains error details
}
// Example 4: RAII ensures cleanup even with exceptions
std::cout << "\n=== Example 4: RAII cleanup ===" << std::endl;
{
yokan::Database scoped_db = client.makeDatabaseHandle(
server_ep.get_addr(), std::atoi(argv[2]));
try {
// Even if this throws...
std::string key = "will_fail";
std::vector<char> buffer(256);
size_t size = buffer.size();
scoped_db.get(key.data(), key.size(), buffer.data(), &size);
} catch(const yokan::Exception&) {
// ...the database handle is still cleaned up
std::cout << "Exception caught, but resources are safe" << std::endl;
}
// scoped_db is automatically cleaned up here
}
std::cout << "Scoped database handle cleaned up" << std::endl;
// Example 5: Multiple operations with error handling
std::cout << "\n=== Example 5: Batch error handling ===" << std::endl;
std::vector<std::string> keys = {"key1", "key2", "key3"};
for(const auto& k : keys) {
try {
std::vector<char> buffer(256);
size_t size = buffer.size();
db.get(k.data(), k.size(), buffer.data(), &size);
std::string value(buffer.data(), size);
std::cout << k << " = " << value << std::endl;
} catch(const yokan::Exception&) {
std::cout << k << " = <not found>" << std::endl;
}
}
std::cout << "\n=== All examples completed ===" << std::endl;
} catch(const yokan::Exception& ex) {
std::cerr << "Fatal error: " << ex.what() << std::endl;
return 1;
}
return 0;
}
Best practices:
Wrap operations in try/catch blocks
Use RAII for automatic cleanup
Check backend capabilities before using advanced modes
Handle missing keys gracefully
RAII and Resource Management
Copy and Move Semantics
The C++ classes use reference counting for safe copying:
/*
* (C) 2024 The University of Chicago
*
* See COPYRIGHT in top-level directory.
*/
#include <thallium.hpp>
#include <yokan/cxx/database.hpp>
#include <yokan/cxx/client.hpp>
#include <iostream>
#include <memory>
namespace tl = thallium;
void demonstrateCopying(yokan::Database db) {
// Database handle is copied (reference counted)
// Original handle remains valid
std::string key = "copy_test";
std::string value = "copied";
db.put(key.data(), key.size(), value.data(), value.size());
std::cout << "Operation in copied handle successful" << std::endl;
// db is destroyed here, but reference count decremented
}
yokan::Database demonstrateMoving(yokan::Client& client, hg_addr_t addr, uint16_t provider_id) {
// Create database handle
yokan::Database db = client.makeDatabaseHandle(addr, provider_id);
// Return by value uses move semantics
return db;
// Original db is moved, not copied
}
int main(int argc, char** argv) {
if(argc != 3) {
std::cerr << "Usage: " << argv[0] << " <server_addr> <provider_id>" << std::endl;
return 1;
}
try {
tl::engine engine("na+sm", THALLIUM_CLIENT_MODE);
yokan::Client client(engine.get_margo_instance());
tl::endpoint server_ep = engine.lookup(argv[1]);
{
// Create database handle - RAII ensures cleanup
yokan::Database db = client.makeDatabaseHandle(
server_ep.get_addr(), std::atoi(argv[2]));
// Demonstrate copying
yokan::Database db_copy = db; // Reference counted copy
demonstrateCopying(db_copy);
// Both db and db_copy are still valid
std::cout << "Original handle still valid" << std::endl;
// Demonstrate moving
yokan::Database db_moved = demonstrateMoving(
client, server_ep.get_addr(), std::atoi(argv[2]));
std::string key = "move_test";
std::string value = "moved";
db_moved.put(key.data(), key.size(), value.data(), value.size());
// Using smart pointers for dynamic allocation
auto db_ptr = std::make_unique<yokan::Database>(
client.makeDatabaseHandle(server_ep.get_addr(), std::atoi(argv[2])));
key = "smart_ptr_test";
value = "smart pointer";
db_ptr->put(key.data(), key.size(), value.data(), value.size());
// All resources will be automatically cleaned up when going out of scope
// No need for explicit cleanup calls
}
std::cout << "All resources automatically cleaned up" << std::endl;
} catch(const yokan::Exception& ex) {
std::cerr << "Error: " << ex.what() << std::endl;
return 1;
}
return 0;
}
Resources are automatically cleaned up when the last reference is destroyed, preventing leaks and simplifying code.
Advanced Patterns
Custom Memory Management
For high-performance applications, preallocate buffers:
/*
* (C) 2024 The University of Chicago
*
* See COPYRIGHT in top-level directory.
*/
#include <thallium.hpp>
#include <yokan/cxx/database.hpp>
#include <yokan/cxx/client.hpp>
#include <iostream>
#include <vector>
#include <string>
namespace tl = thallium;
int main(int argc, char** argv) {
if(argc != 3) {
std::cerr << "Usage: " << argv[0] << " <server_addr> <provider_id>" << std::endl;
return 1;
}
try {
tl::engine engine("na+sm", THALLIUM_CLIENT_MODE);
yokan::Client client(engine.get_margo_instance());
tl::endpoint server_ep = engine.lookup(argv[1]);
yokan::Database db = client.makeDatabaseHandle(
server_ep.get_addr(), std::atoi(argv[2]));
// Strategy 1: Preallocate buffer for known size
std::string key = "large_value";
// Get the value size first
size_t value_size = db.length(key.data(), key.size());
// Preallocate exact size
std::vector<char> value_buffer(value_size);
db.get(key.data(), key.size(), value_buffer.data(), &value_size);
std::cout << "Retrieved " << value_size << " bytes" << std::endl;
// Strategy 2: Reuse buffer for multiple gets
std::vector<char> reusable_buffer(4096); // 4KB buffer
std::vector<std::string> keys_to_fetch = {"user:1", "user:2", "user:3"};
for(const auto& k : keys_to_fetch) {
size_t vsize = reusable_buffer.size();
db.get(k.data(), k.size(), reusable_buffer.data(), &vsize);
std::string value(reusable_buffer.data(), vsize);
std::cout << k << " = " << value << std::endl;
}
// Strategy 3: Batch operations with preallocated arrays
const size_t batch_size = 100;
std::vector<const void*> key_ptrs(batch_size);
std::vector<size_t> key_sizes(batch_size);
std::vector<void*> val_ptrs(batch_size);
std::vector<size_t> val_sizes(batch_size);
// Preallocate value buffers
std::vector<std::vector<char>> value_buffers(batch_size);
for(size_t i = 0; i < batch_size; i++) {
value_buffers[i].resize(256); // 256 bytes per value
val_ptrs[i] = value_buffers[i].data();
val_sizes[i] = value_buffers[i].size();
}
// Prepare keys
std::vector<std::string> batch_keys(batch_size);
for(size_t i = 0; i < batch_size; i++) {
batch_keys[i] = "batch:" + std::to_string(i);
key_ptrs[i] = batch_keys[i].data();
key_sizes[i] = batch_keys[i].size();
}
// Batch get with preallocated memory
db.getMulti(batch_size, key_ptrs.data(), key_sizes.data(),
val_ptrs.data(), val_sizes.data());
std::cout << "Fetched " << batch_size << " values with preallocated buffers" << std::endl;
// Strategy 4: Memory pool for high-frequency operations
struct MemoryPool {
std::vector<char> buffer;
size_t used = 0;
MemoryPool(size_t size) : buffer(size) {}
char* allocate(size_t n) {
if(used + n > buffer.size()) {
throw std::runtime_error("Pool exhausted");
}
char* ptr = buffer.data() + used;
used += n;
return ptr;
}
void reset() { used = 0; }
};
MemoryPool pool(10240); // 10KB pool
for(int i = 0; i < 10; i++) {
std::string k = "pool:" + std::to_string(i);
size_t vsize = 512;
char* buf = pool.allocate(vsize);
db.get(k.data(), k.size(), buf, &vsize);
std::string value(buf, vsize);
std::cout << "Fetched from pool: " << value << std::endl;
}
pool.reset(); // Reuse pool
} catch(const yokan::Exception& ex) {
std::cerr << "Error: " << ex.what() << std::endl;
return 1;
} catch(const std::exception& ex) {
std::cerr << "Error: " << ex.what() << std::endl;
return 1;
}
return 0;
}
Binary Data Handling
Working with binary (non-text) data:
/*
* (C) 2024 The University of Chicago
*
* See COPYRIGHT in top-level directory.
*/
#include <thallium.hpp>
#include <yokan/cxx/database.hpp>
#include <yokan/cxx/client.hpp>
#include <iostream>
#include <vector>
#include <cstring>
#include <cstdint>
namespace tl = thallium;
// Example struct for binary data
struct UserRecord {
uint64_t id;
char name[32];
double balance;
uint32_t age;
};
int main(int argc, char** argv) {
if(argc != 3) {
std::cerr << "Usage: " << argv[0] << " <server_addr> <provider_id>" << std::endl;
return 1;
}
try {
tl::engine engine("na+sm", THALLIUM_CLIENT_MODE);
yokan::Client client(engine.get_margo_instance());
tl::endpoint server_ep = engine.lookup(argv[1]);
yokan::Database db = client.makeDatabaseHandle(
server_ep.get_addr(), std::atoi(argv[2]));
// Store binary struct
UserRecord user;
user.id = 12345;
std::strncpy(user.name, "Alice Johnson", sizeof(user.name));
user.balance = 1234.56;
user.age = 30;
std::string key = "user:12345";
db.put(key.data(), key.size(), &user, sizeof(UserRecord));
std::cout << "Stored binary record" << std::endl;
// Retrieve binary struct
UserRecord retrieved_user;
size_t vsize = sizeof(UserRecord);
db.get(key.data(), key.size(), &retrieved_user, &vsize);
std::cout << "Retrieved user:" << std::endl;
std::cout << " ID: " << retrieved_user.id << std::endl;
std::cout << " Name: " << retrieved_user.name << std::endl;
std::cout << " Balance: $" << retrieved_user.balance << std::endl;
std::cout << " Age: " << retrieved_user.age << std::endl;
// Store binary blob (e.g., image data)
std::vector<uint8_t> blob_data(1024);
for(size_t i = 0; i < blob_data.size(); i++) {
blob_data[i] = static_cast<uint8_t>(i % 256);
}
std::string blob_key = "image:001";
db.put(blob_key.data(), blob_key.size(),
blob_data.data(), blob_data.size());
std::cout << "\nStored binary blob of " << blob_data.size() << " bytes" << std::endl;
// Retrieve blob
std::vector<uint8_t> retrieved_blob(1024);
size_t blob_size = retrieved_blob.size();
db.get(blob_key.data(), blob_key.size(),
retrieved_blob.data(), &blob_size);
// Verify data
bool match = (blob_data == retrieved_blob);
std::cout << "Binary data matches: " << (match ? "yes" : "no") << std::endl;
// Store array of integers
std::vector<int32_t> int_array = {10, 20, 30, 40, 50};
std::string array_key = "array:001";
db.put(array_key.data(), array_key.size(),
int_array.data(), int_array.size() * sizeof(int32_t));
// Retrieve array
std::vector<int32_t> retrieved_array(5);
size_t array_size = retrieved_array.size() * sizeof(int32_t);
db.get(array_key.data(), array_key.size(),
retrieved_array.data(), &array_size);
std::cout << "\nRetrieved array: ";
for(const auto& val : retrieved_array) {
std::cout << val << " ";
}
std::cout << std::endl;
} catch(const yokan::Exception& ex) {
std::cerr << "Error: " << ex.what() << std::endl;
return 1;
}
return 0;
}
Template-Based Wrappers
Create type-safe wrappers using templates:
/*
* (C) 2024 The University of Chicago
*
* See COPYRIGHT in top-level directory.
*/
#include <thallium.hpp>
#include <yokan/cxx/database.hpp>
#include <yokan/cxx/client.hpp>
#include <iostream>
#include <string>
#include <sstream>
#include <type_traits>
namespace tl = thallium;
// Type-safe wrapper for Yokan database
template<typename KeyType, typename ValueType>
class TypedDatabase {
private:
yokan::Database db;
// Serialize key to binary
template<typename T>
std::vector<char> serialize(const T& obj) {
if constexpr (std::is_same_v<T, std::string>) {
return std::vector<char>(obj.begin(), obj.end());
} else {
std::vector<char> buffer(sizeof(T));
std::memcpy(buffer.data(), &obj, sizeof(T));
return buffer;
}
}
// Deserialize value from binary
template<typename T>
T deserialize(const std::vector<char>& data) {
if constexpr (std::is_same_v<T, std::string>) {
return std::string(data.begin(), data.end());
} else {
T obj;
std::memcpy(&obj, data.data(), sizeof(T));
return obj;
}
}
public:
TypedDatabase(yokan::Database db) : db(std::move(db)) {}
void put(const KeyType& key, const ValueType& value) {
auto key_data = serialize(key);
auto val_data = serialize(value);
db.put(key_data.data(), key_data.size(),
val_data.data(), val_data.size());
}
ValueType get(const KeyType& key) {
auto key_data = serialize(key);
// Get value size first
size_t vsize = db.length(key_data.data(), key_data.size());
// Allocate and get value
std::vector<char> val_data(vsize);
db.get(key_data.data(), key_data.size(), val_data.data(), &vsize);
return deserialize<ValueType>(val_data);
}
bool exists(const KeyType& key) {
auto key_data = serialize(key);
return db.exists(key_data.data(), key_data.size());
}
void erase(const KeyType& key) {
auto key_data = serialize(key);
db.erase(key_data.data(), key_data.size());
}
};
int main(int argc, char** argv) {
if(argc != 3) {
std::cerr << "Usage: " << argv[0] << " <server_addr> <provider_id>" << std::endl;
return 1;
}
try {
tl::engine engine("na+sm", THALLIUM_CLIENT_MODE);
yokan::Client client(engine.get_margo_instance());
tl::endpoint server_ep = engine.lookup(argv[1]);
yokan::Database db = client.makeDatabaseHandle(
server_ep.get_addr(), std::atoi(argv[2]));
// Create typed database wrapper for string->string
TypedDatabase<std::string, std::string> string_db(db);
string_db.put("name", "Alice");
std::string name = string_db.get("name");
std::cout << "Name: " << name << std::endl;
// Create typed database wrapper for int->double
TypedDatabase<int, double> numeric_db(db);
numeric_db.put(42, 3.14159);
double value = numeric_db.get(42);
std::cout << "Value for key 42: " << value << std::endl;
// Check existence
bool exists = numeric_db.exists(42);
std::cout << "Key 42 exists: " << (exists ? "yes" : "no") << std::endl;
// Erase
numeric_db.erase(42);
exists = numeric_db.exists(42);
std::cout << "Key 42 exists after erase: " << (exists ? "yes" : "no") << std::endl;
} catch(const yokan::Exception& ex) {
std::cerr << "Error: " << ex.what() << std::endl;
return 1;
}
return 0;
}
Migration API
The C++ API provides a clean interface for database migration:
/*
* (C) 2024 The University of Chicago
*
* See COPYRIGHT in top-level directory.
*/
#include <thallium.hpp>
#include <yokan/cxx/database.hpp>
#include <yokan/cxx/client.hpp>
#include <iostream>
#include <string>
#include <vector>
namespace tl = thallium;
int main(int argc, char** argv) {
if(argc != 5) {
std::cerr << "Usage: " << argv[0]
<< " <source_addr> <source_provider_id>"
<< " <dest_addr> <dest_provider_id>" << std::endl;
return 1;
}
try {
tl::engine engine("na+sm", THALLIUM_CLIENT_MODE);
yokan::Client client(engine.get_margo_instance());
// Connect to source database
tl::endpoint source_ep = engine.lookup(argv[1]);
yokan::Database source_db = client.makeDatabaseHandle(
source_ep.get_addr(), std::atoi(argv[2]));
// Connect to destination database
tl::endpoint dest_ep = engine.lookup(argv[3]);
yokan::Database dest_db = client.makeDatabaseHandle(
dest_ep.get_addr(), std::atoi(argv[4]));
// Populate source database with test data
std::cout << "Populating source database..." << std::endl;
for(int i = 0; i < 10; i++) {
std::string key = "key:" + std::to_string(i);
std::string value = "value:" + std::to_string(i);
source_db.put(key.data(), key.size(), value.data(), value.size());
}
// Manual migration using list and put operations
std::cout << "\nStarting manual migration..." << std::endl;
size_t total_migrated = 0;
// Use iter to iterate and copy
auto migration_callback = [&](size_t index, const void* key, size_t ksize,
const void* val, size_t vsize) -> yk_return_t {
// Put key-value pair into destination database
dest_db.put(key, ksize, val, vsize);
total_migrated++;
return YOKAN_SUCCESS;
};
// Migrate all keys (empty prefix means all keys)
std::string prefix = "";
std::string filter = "";
source_db.iter(prefix.data(), prefix.size(),
filter.data(), filter.size(),
0, // count = 0 means all
migration_callback);
std::cout << "Migrated " << total_migrated << " key-value pairs" << std::endl;
// Verify migration by checking destination database
std::cout << "\nVerifying migration..." << std::endl;
for(int i = 0; i < 10; i++) {
std::string key = "key:" + std::to_string(i);
// Check if key exists in destination
bool exists = dest_db.exists(key.data(), key.size());
if(exists) {
// Get value from destination
std::vector<char> buffer(256);
size_t vsize = buffer.size();
dest_db.get(key.data(), key.size(), buffer.data(), &vsize);
std::string value(buffer.data(), vsize);
std::cout << " " << key << " = " << value << std::endl;
} else {
std::cout << " " << key << " NOT FOUND!" << std::endl;
}
}
std::cout << "\nMigration verification complete!" << std::endl;
// Note: For production use with large databases, consider:
// 1. Batch operations for better performance
// 2. Error handling and retry logic
// 3. Progress tracking
// 4. Using REMI for provider-level migration (requires server access)
} catch(const yokan::Exception& ex) {
std::cerr << "Error: " << ex.what() << std::endl;
return 1;
}
return 0;
}
See Database migration for detailed migration documentation.
Document Collections
Work with JSON documents using the Collection API:
/*
* (C) 2024 The University of Chicago
*
* See COPYRIGHT in top-level directory.
*/
#include <thallium.hpp>
#include <yokan/cxx/database.hpp>
#include <yokan/cxx/client.hpp>
#include <yokan/cxx/collection.hpp>
#include <iostream>
#include <string>
#include <vector>
namespace tl = thallium;
int main(int argc, char** argv) {
if(argc != 3) {
std::cerr << "Usage: " << argv[0] << " <server_addr> <provider_id>" << std::endl;
return 1;
}
try {
tl::engine engine("na+sm", THALLIUM_CLIENT_MODE);
yokan::Client client(engine.get_margo_instance());
tl::endpoint server_ep = engine.lookup(argv[1]);
uint16_t provider_id = std::atoi(argv[2]);
// Create database handle
yokan::Database db = client.makeDatabaseHandle(
server_ep.get_addr(), provider_id);
// Create a collection
// Note: Collection constructor takes name first, then Database object
std::string collection_name = "users";
yokan::Collection collection(collection_name.c_str(), db);
std::cout << "Working with collection: " << collection_name << std::endl;
// Prepare JSON documents
std::vector<std::string> documents = {
R"({"name": "Alice Johnson", "age": 30, "email": "alice@example.com", "role": "engineer"})",
R"({"name": "Bob Smith", "age": 35, "email": "bob@example.com", "role": "manager"})",
R"({"name": "Carol White", "age": 28, "email": "carol@example.com", "role": "engineer"})"
};
// Prepare IDs for storing
std::vector<yk_id_t> ids = {1001, 1002, 1003};
// Prepare pointers for storeMulti
std::vector<const void*> doc_ptrs;
std::vector<size_t> doc_sizes;
for(const auto& doc : documents) {
doc_ptrs.push_back(doc.data());
doc_sizes.push_back(doc.size());
}
// Store multiple documents with explicit IDs
collection.storeMulti(documents.size(),
doc_ptrs.data(),
doc_sizes.data(),
ids.data());
std::cout << "\nStored " << documents.size() << " documents" << std::endl;
for(size_t i = 0; i < ids.size(); i++) {
std::cout << " ID " << ids[i] << std::endl;
}
// Load a document by ID
yk_id_t id_to_load = 1001;
std::vector<char> buffer(512);
size_t doc_size = buffer.size();
collection.load(id_to_load, buffer.data(), &doc_size);
std::string retrieved_doc(buffer.data(), doc_size);
std::cout << "\nRetrieved document " << id_to_load << ":" << std::endl;
std::cout << retrieved_doc << std::endl;
// Get document length
size_t length = collection.length(id_to_load);
std::cout << "\nDocument " << id_to_load << " length: " << length << " bytes" << std::endl;
// Update a document
std::string updated_doc = R"({"name": "Alice Johnson", "age": 31, "email": "alice.j@example.com", "role": "senior engineer"})";
collection.update(id_to_load, updated_doc.data(), updated_doc.size());
std::cout << "\nUpdated document " << id_to_load << std::endl;
// Load updated document
doc_size = buffer.size();
collection.load(id_to_load, buffer.data(), &doc_size);
std::string updated_retrieved(buffer.data(), doc_size);
std::cout << "Updated document content: " << updated_retrieved << std::endl;
// Erase a document
yk_id_t id_to_erase = 1002;
collection.erase(id_to_erase);
std::cout << "\nErased document " << id_to_erase << std::endl;
// List documents using iter with C++ callback
std::cout << "\nListing all remaining documents:" << std::endl;
auto callback = [](size_t index, yk_id_t id, const void* doc, size_t size) -> yk_return_t {
std::string document(static_cast<const char*>(doc), size);
std::cout << " [" << index << "] ID " << id << ": " << document << std::endl;
return YOKAN_SUCCESS;
};
yk_id_t start_id = 0; // Start from beginning
std::string filter = ""; // No filter
collection.iter(start_id,
filter.data(), filter.size(),
0, // max = 0 means all
callback);
std::cout << "\nCollection operations completed successfully!" << std::endl;
} catch(const yokan::Exception& ex) {
std::cerr << "Error: " << ex.what() << std::endl;
return 1;
}
return 0;
}
Collections provide:
JSON document storage
Document IDs
Batch operations
Query capabilities
Performance Considerations
Use batch operations to reduce network round-trips
Preallocate buffers for get operations when sizes are known
Use move semantics to avoid unnecessary copying
Choose appropriate backends based on workload characteristics
Use NO_RDMA mode for small key/value pairs
Consider pool configuration for concurrent operations
Profile your application to identify bottlenecks
Comparison with C API
C++ Advantages:
RAII automatic cleanup
Exception-based error handling
Type safety
Modern C++ idioms
Easier to use correctly
When to use C API:
C-only environments
ABI stability requirements
Specific performance needs
Library interoperability
Integration Examples
With Thallium
The C++ bindings work seamlessly with Thallium:
/*
* (C) 2024 The University of Chicago
*
* See COPYRIGHT in top-level directory.
*/
#include <thallium.hpp>
#include <thallium/serialization/stl/string.hpp>
#include <yokan/cxx/database.hpp>
#include <yokan/cxx/client.hpp>
#include <iostream>
#include <string>
namespace tl = thallium;
// Custom RPC that uses Yokan
void store_user(const tl::request& req, yokan::Database& db,
const std::string& username, const std::string& email) {
try {
std::string key = "user:" + username;
db.put(key.data(), key.size(), email.data(), email.size());
std::cout << "Stored user: " << username << " -> " << email << std::endl;
req.respond(true);
} catch(const yokan::Exception& ex) {
std::cerr << "Yokan error: " << ex.what() << std::endl;
req.respond(false);
}
}
void get_user(const tl::request& req, yokan::Database& db,
const std::string& username) {
try {
std::string key = "user:" + username;
// Get email
std::vector<char> buffer(256);
size_t vsize = buffer.size();
db.get(key.data(), key.size(), buffer.data(), &vsize);
std::string email(buffer.data(), vsize);
std::cout << "Retrieved user: " << username << " -> " << email << std::endl;
req.respond(email);
} catch(const yokan::Exception& ex) {
std::cerr << "Yokan error: " << ex.what() << std::endl;
req.respond(std::string(""));
}
}
int main(int argc, char** argv) {
if(argc != 3) {
std::cerr << "Usage: " << argv[0] << " <protocol> <provider_id>" << std::endl;
return 1;
}
try {
// Initialize Thallium engine in server mode
tl::engine engine(argv[1], THALLIUM_SERVER_MODE);
// Create Yokan client using Thallium's Margo instance
yokan::Client client(engine.get_margo_instance());
// Create database handle (connecting to local provider)
yokan::Database db = client.makeDatabaseHandle(
engine.self().get_addr(), std::atoi(argv[2]));
std::cout << "Server running at " << engine.self() << std::endl;
// Register Thallium RPCs that use Yokan
engine.define("store_user",
[&db](const tl::request& req, const std::string& username,
const std::string& email) {
store_user(req, db, username, email);
});
engine.define("get_user",
[&db](const tl::request& req, const std::string& username) {
get_user(req, db, username);
});
// Enable remote shutdown
engine.enable_remote_shutdown();
// Wait for finalize
engine.wait_for_finalize();
} catch(const yokan::Exception& ex) {
std::cerr << "Yokan error: " << ex.what() << std::endl;
return 1;
} catch(const std::exception& ex) {
std::cerr << "Error: " << ex.what() << std::endl;
return 1;
}
return 0;
}
With Bedrock
Using Yokan with Bedrock’s C++ API:
/*
* (C) 2024 The University of Chicago
*
* See COPYRIGHT in top-level directory.
*/
#include <thallium.hpp>
#include <bedrock/Client.hpp>
#include <yokan/cxx/database.hpp>
#include <yokan/cxx/client.hpp>
#include <iostream>
#include <string>
namespace tl = thallium;
int main(int argc, char** argv) {
if(argc != 2) {
std::cerr << "Usage: " << argv[0] << " <bedrock_config.json>" << std::endl;
return 1;
}
try {
// Initialize Thallium engine
tl::engine engine("na+sm", THALLIUM_CLIENT_MODE);
// Create Bedrock client
bedrock::Client bedrock_client(engine);
// Load Bedrock configuration
std::string config_file = argv[1];
std::cout << "Loading Bedrock configuration from: " << config_file << std::endl;
// Query Bedrock for Yokan provider information
// (In a real application, you would parse the config or use Bedrock API
// to discover providers. This is a simplified example.)
// For this example, assume we know the server address and provider ID
std::string server_addr = "na+sm://12345-0";
uint16_t provider_id = 1;
// Create Yokan client
yokan::Client yokan_client(engine.get_margo_instance());
// Look up server
tl::endpoint server_ep = engine.lookup(server_addr);
// Create database handle
yokan::Database db = yokan_client.makeDatabaseHandle(
server_ep.get_addr(), provider_id);
std::cout << "Connected to Yokan provider via Bedrock" << std::endl;
// Use Yokan database
std::string key = "bedrock:test";
std::string value = "Yokan + Bedrock integration";
db.put(key.data(), key.size(), value.data(), value.size());
std::cout << "Stored: " << key << " = " << value << std::endl;
// Retrieve value
std::vector<char> buffer(256);
size_t vsize = buffer.size();
db.get(key.data(), key.size(), buffer.data(), &vsize);
std::string retrieved(buffer.data(), vsize);
std::cout << "Retrieved: " << retrieved << std::endl;
// Example Bedrock configuration (for reference):
std::cout << "\nExample Bedrock configuration snippet:" << std::endl;
std::cout << R"({
"libraries": {
"yokan": "libyokan-bedrock-module.so"
},
"providers": [
{
"name": "my_yokan_db",
"type": "yokan",
"provider_id": 1,
"config": {
"database": {
"type": "map"
}
}
}
]
})" << std::endl;
} catch(const yokan::Exception& ex) {
std::cerr << "Yokan error: " << ex.what() << std::endl;
return 1;
} catch(const std::exception& ex) {
std::cerr << "Error: " << ex.what() << std::endl;
return 1;
}
return 0;
}
See 02_advanced_setup for Bedrock configuration details.
Building Applications
CMake Integration
Use pkg-config to find Yokan:
cmake_minimum_required(VERSION 3.10)
project(my_yokan_app CXX)
set(CMAKE_CXX_STANDARD 14)
find_package (yokan REQUIRED)
add_executable(my_app main.cpp)
target_link_libraries(my_app yokan::client yokan::server)
Manual Compilation
g++ -std=c++14 my_app.cpp $(pkg-config --cflags --libs yokan-client) -o my_app