C++ Client

CedarDB is compatible with the PostgreSQL libpqxx driver.

Installing

Before demonstrating the connection to CedarDB, we need to get the correct dependencies . libpqxx uses the libpq library internally. On Debian or Ubuntu you can simply get the dev files from an apt repository.

sudo apt install libpqxx-dev

After finishing the client (see at the full program at the bottom of the program), we need to first compile our program with g++ and then execute it.

g++ -std=c++17 main.cpp -lpqxx -lpq -o CedarDBClient
./CedarDBClient

Connecting

Connect to CedarDB like this:

// The connection string
auto connectionString = "dbname=<dbname> user=<username> password=<password> host=localhost port=5432";
try {
    // Establishing a connection
    pqxx::connection connection(connectionString);
    if (!connection.is_open()) {
        cerr << "Can't connect!" << endl;
        return 1;
    }
} catch (const std::exception& e) {
    cerr << e.what() << std::endl;
    return 1;
}
return 0;

You now have an open connection to CedarDB that allows you to insert data or query the database.

Inserting Data

Let’s create a new table storing the log of a public chat channel:

// Create transaction
pqxx::work transaction(connection);

// Create table
auto createTable = "CREATE TABLE IF NOT EXISTS chatlog(userid integer, message text, ts timestamptz)"sv;
transaction.exec(createTable);
transaction.commit();

In the following, we first prepare a new insert statement, before we insert a new tuple using the connection instance:

// Create transaction
pqxx::work transaction(connection);

// Insert data
connection.prepare("insert", "INSERT INTO chatlog VALUES ($1 , $2, $3)");
string_view message = "(☞゚∀゚)☞"sv;
auto id = 0;
auto time = chrono::system_clock::to_time_t(chrono::system_clock::now());
std::stringstream ss;
ss << std::put_time(std::localtime(&time), "%Y-%m-%d %X%z");
transaction.exec_prepared("insert", id, message, ss);
transaction.commit();

Executing Queries

Let’s read back all data we have inserted previously:

// Create transaction
pqxx::work transaction(connection);

// Read data
auto readTable = "SELECT * FROM chatlog ORDER BY userid LIMIT 10"sv;
for (auto [id, message, time] : transaction.stream<int, string_view, string_view>(readTable)) {
    std::cout << id << "," << message << "," << time << "\n";
}
transaction.commit();

Bulk Loading

If you need to load a lot of data at once (e.g., for an initial import of your existing data set), inserting tuples one by one is too slow: jdbc has to do a full roundtrip to CedarDB and back for each single insert, making the whole loading process severely network latency bound, even on a local connection.

Use jdbc’s bulk loading feature instead:

// Create transaction
pqxx::work transaction(connection);

// Copy data
auto stream = pqxx::stream_to::table(transaction, {"chatlog"sv}, {"userid", "message", "ts"});
for (int i = 0; i < 100000; i++) {
    stream.write_values(i, "This is a message!", "2024-04-11 18:16:16.368000+00");
}
stream.complete();
transaction.commit();

This feature makes use of CedarDB’s Postgres-compatible COPY mode to bulk transmit all data, leading to significantly higher throughput:

LOG: 100000 rows (0.000012 s parsing, 0.000374 s compilation, 0.025395 s transmission, 0.016492 s execution)

Source Code

Open to show the complete sample code
#include <chrono>
#include <iostream>
#include <pqxx/pqxx>

using namespace std;

int main(int argc, char* argv[]) {
    auto connectionString = "dbname=<dbname> user=<username> password=<password> host=localhost port=5432";
    try {
        // Establishing a connection
        pqxx::connection connection(connectionString);
        if (!connection.is_open()) {
            cerr << "Can't connect!" << endl;
            return 1;
        }

        {
            // Create transaction
            pqxx::work transaction(connection);

            // Create table
            auto createTable = "CREATE TABLE IF NOT EXISTS chatlog(userid integer, message text, ts timestamptz)"sv;
            transaction.exec(createTable);
            transaction.commit();
        }
        {
            // Create transaction
            pqxx::work transaction(connection);

            // Insert data
            connection.prepare("insert", "INSERT INTO chatlog VALUES ($1 , $2, $3)");
            string_view message = "(☞゚∀゚)☞"sv;
            auto id = 0;
            auto time = chrono::system_clock::to_time_t(chrono::system_clock::now());
            std::stringstream ss;
            ss << std::put_time(std::localtime(&time), "%Y-%m-%d %X%z");
            transaction.exec_prepared("insert", id, message, ss);
            transaction.commit();
        }
        {
            // Create transaction
            pqxx::work transaction(connection);

            // Copy data
            auto stream = pqxx::stream_to::table(transaction, {"chatlog"sv}, {"userid", "message", "ts"});
            for (int i = 0; i < 100000; i++) {
                stream.write_values(i, "This is a message!", "2024-04-11 18:16:16.368000+00");
            }
            stream.complete();
            transaction.commit();
        }
        {
            // Create transaction
            pqxx::work transaction(connection);

            // Read data
            auto readTable = "SELECT * FROM chatlog ORDER BY userid LIMIT 10"sv;
            for (auto [id, message, time] : transaction.stream<int, string_view, string_view>(readTable)) {
                std::cout << id << "," << message << "," << time << "\n";
            }
            transaction.commit();
        }
    } catch (const std::exception& e) {
        cerr << e.what() << std::endl;
        return 1;
    }
    return 0;
}