Skip to content

Instantly share code, notes, and snippets.

@mpenick
Created January 17, 2019 16:26
Show Gist options
  • Save mpenick/1feeb0622d5b0192d709cd5c406eb392 to your computer and use it in GitHub Desktop.
Save mpenick/1feeb0622d5b0192d709cd5c406eb392 to your computer and use it in GitHub Desktop.
#pragma clang diagnostic ignored "-Wold-style-cast"
/*
* _____ _____ _____
* / ____| _ _ /\ | __ \ |_ _|
* | | _| |_ _| |_ / \ | |__) | | |
* | | |_ _| |_ _| / /\ \ | ___/ | |
* | |____ |_| |_| / ____ \ | | _| |_
* \_____| /_/ \_\ |_| |_____|
*
* Preview
*
* AKA "50 ways to get the 'release_version'" (maybe not quite 50)
*
*
* Initial design goals:
*
* - Values semantics i.e. Limit pointers and no dealing with lifetimes
* - C++11 makes this efficent (move sematics)
*
* - Immutability
*
* - Reduce verbosity of common use cases
* - C++11 makes this possible (variadic templates)
*
* - Hide implementation details (where possible)
* - Opaque pointers to implementation of "core" objects
* - Limiting use of std library stuff.
* - But....still using `std::future` and `std::function` ¯\_(ツ)_/¯
* - Expose data layout where the "rubber meets the road"
*
* - Low cost, easy to use iterators, cursors (expose data layout)
* - This is pain point in the C API (it can be done better there too)
*
*/
#include <cassandra.h> // The old header
#include <stdio.h>
/**
* Where we're coming from... The C API "simple" example.
*
* (Hey! This doesn't look "simple")
*
* Lot's of pointers and dealing with lifetimes. Verbose.
*
*
* Can the C++ API do better?
*
* next;
*/
void simple_c() {
CassFuture* connect_future = NULL;
CassCluster* cluster = cass_cluster_new();
CassSession* session = cass_session_new();
cass_cluster_set_contact_points(cluster, "127.0.0.1");
connect_future = cass_session_connect(session, cluster);
if (cass_future_error_code(connect_future) == CASS_OK) {
const char* query = "SELECT release_version FROM system.local";
CassStatement* statement = cass_statement_new(query, 0);
CassFuture* result_future = cass_session_execute(session, statement);
if (cass_future_error_code(result_future) == CASS_OK) {
const CassResult* result = cass_future_get_result(result_future);
const CassRow* row = cass_result_first_row(result);
if (row) {
const CassValue* value = cass_row_get_column_by_name(row, "release_version");
const char* release_version;
size_t release_version_length;
cass_value_get_string(value, &release_version, &release_version_length);
printf("release_version: '%.*s'\n", (int)release_version_length,
release_version);
}
cass_result_free(result);
} else {
const char* message;
size_t message_length;
cass_future_error_message(result_future, &message, &message_length);
fprintf(stderr, "Unable to run query: '%.*s'\n", (int)message_length,
message);
}
cass_statement_free(statement);
cass_future_free(result_future);
} else {
const char* message;
size_t message_length;
cass_future_error_message(connect_future, &message, &message_length);
fprintf(stderr, "Unable to connect: '%.*s'\n", (int)message_length,
message);
}
// Look at all these frees!
cass_future_free(connect_future);
cass_cluster_free(cluster);
cass_session_free(session);
}
#include <datastax.hpp> // The new header
#include <iostream>
using namespace datastax;
using namespace datastax::values;
/**
* Yes. We can do "simple" much better in the new C++ API.
*
* Hey! No pointers, or dealing with lifetimes!
*
* (Destructors and method chaining. Oh yeah!)
*
*
* What if I told you we didn't have to build statements anymore?
*
* next;
*/
void simple_cpp() {
try {
Session session = SessionConfigBuilder("127.0.0.1")
.build().connect();
// Statements are immutable
Statement statement = SimpleStatementBuilder("SELECT release_version FROM system.local WHERE key = ?")
.bind(0, text("local")) // Bind using an index
.build();
ResultSet rs = session.execute(statement);
std::cout << "release_version: "
<< rs.first().column("release_version").as_string() << "\n";
} catch (Exception& ex) {
std::cout << "Error: \"" << ex.what() << "\"\n";
}
}
/**
* We can do "simple" even better.
*
* (C++11 variadic templates FTW, you don't even have to know what that means)
* (Zero cost, compiles out with optimization)
*
*
* What about parameters names?
*
* next;
*/
void simple_better_cpp() {
try {
Session session = SessionConfigBuilder("127.0.0.1")
.build().connect();
// Inline binding of values
ResultSet rs = session.execute("SELECT release_version FROM system.local WHERE key = ?",
text("local"));
std::cout << "release_version: "
<< rs.first().column("release_version").as_string() << "\n";
} catch (Exception& ex) {
std::cout << "Error: \"" << ex.what() << "\"\n";
}
}
/**
* That's supported too. Build a statement using a parameter name instead of an index.
*
*
* Builders are nice, but could this be done "inline"?
*
* next;
*/
void simple_by_name_cpp() {
try {
Session session = SessionConfigBuilder("127.0.0.1")
.build().connect();
Statement statement = SimpleStatementBuilder("SELECT release_version FROM system.local WHERE key = ?")
.bind("key", text("local")) // Use the name instead
.build();
ResultSet rs = session.execute(statement);
std::cout << "release_version: "
<< rs.first().column("release_version").as_string() << "\n";
} catch (Exception& ex) {
std::cout << "Error: \"" << ex.what() << "\"\n";
}
}
/**
* Yes. `named()` allows inline named parameters.
*
*
* What if we want to get more than one row?
*
* next;
*/
void simple_by_name_better_cpp() {
try {
Session session = SessionConfigBuilder("127.0.0.1")
.build().connect();
// Inline binding of values by name
ResultSet rs = session.execute("SELECT release_version FROM system.local WHERE key = ?",
named("key", text("local")));
std::cout << "release_version: "
<< rs.first().column("release_version").as_string() << "\n";
} catch (Exception& ex) {
std::cout << "Error: \"" << ex.what() << "\"\n";
}
}
/**
* Use iterators! Iterators can are allocation-less and transparent.
*
* (Use C++ style range `for` to iterate over result sets)
*
*
* What about query options? I only want the first 100 rows and I don't like
* keyspace in my query string.
*
* next;
*/
void simple_iterators_cpp() {
try {
Session session = SessionConfigBuilder("127.0.0.1")
.build().connect();
ResultSet rs = session.execute("SELECT release_version FROM system.peers");
// Iterator through every row and column
for (auto row : rs) {
for (auto column : row) {
std::cout << column.name() << ": " << column.as_string() << "\n";
}
}
// Iterator through every column
for (auto column : rs.columns()) {
std::cout << column.name() << ": " << column.as_string() << "\n";
}
} catch (Exception& ex) {
std::cout << "Error: \"" << ex.what() << "\"\n";
}
}
/**
* Back to the builder again to specify query options.
*
*
* Maybe this could be better too?
*
* next;
*/
void simple_query_options_cpp() {
try {
Session session = SessionConfigBuilder("127.0.0.1")
.build().connect();
// Build the statement with options
Statement statement = SimpleStatementBuilder("SELECT release_version FROM peers")
.with_keyspace("system")
.with_page_size(100)
.build();
ResultSet rs = session.execute(statement);
for (auto row : rs) {
for (auto column : row) {
std::cout << column.name() << ": " << column.as_string() << "\n";
}
}
} catch (Exception& ex) {
std::cout << "Error: \"" << ex.what() << "\"\n";
}
}
/**
* It can. Query options can allow be specified "inline" too!
*
*
* All these simple statements are pretty good, but what about prepared
* statements?
*
* next;
*/
void simple_query_options_better_cpp() {
try {
Session session = SessionConfigBuilder("127.0.0.1")
.build().connect();
// Execute using inline options
ResultSet rs = session.execute("SELECT release_version FROM peers",
page_size(100),
keyspace("system"));
for (auto row : rs) {
for (auto column : row) {
std::cout << column.name() << ": " << column.as_string() << "\n";
}
}
} catch (Exception& ex) {
std::cout << "Error: \"" << ex.what() << "\"\n";
}
}
/**
* Those are supported and allow inline parameters and options too!
*
*
* What about the asynchronous API?
*
* next;
*/
void simple_prepared_cpp() {
try {
Session session = SessionConfigBuilder("127.0.0.1")
.build().connect();
// Prepared statement (peers)
PreparedStatement prepared1 = session.prepare("SELECT release_version FROM peers");
// Execute prepared statement using inline options
ResultSet rs1 = session.execute(prepared1.bind(page_size(100),
keyspace("system")));
for (auto row : rs1) {
for (auto column : row) {
std::cout << column.name() << ": " << column.as_string() << "\n";
}
}
PreparedStatement prepared2
= session.prepare("SELECT release_version FROM system.local WHERE key = ?");
// Execute prepared statement using inline parameters
ResultSet rs2 = session.execute(prepared2.bind(text("local")));
std::cout << "release_version: "
<< rs2.first().column("release_version").as_string() << "\n";
} catch (Exception& ex) {
std::cout << "Error: \"" << ex.what() << "\"\n";
}
}
/**
* The future is now! "simple" done with `std::future`.
*
*
* What about do in async w/ callbacks?
*
* next;
*/
void simple_async() {
try {
Session session = SessionConfigBuilder("127.0.0.1")
.build().connect();
ResultSetFuture future1 = session.execute_async("SELECT release_version FROM system.local WHERE key = ?",
text("local"));
ResultSetFuture future2 = session.execute_async("SELECT release_version FROM system.local WHERE key = ?",
text("local"));
std::cout << "Look! I can do stuff whilst waiting for the query to finish!";
std::cout << "release_version: "
<< future1.get().first().column("release_version").as_string() << "\n";
std::cout << "release_version (nope it didn't change!): "
<< future2.get().first().column("release_version").as_string() << "\n";
} catch (Exception& ex) {
std::cout << "Error: \"" << ex.what() << "\"\n";
}
}
/**
* `std::function` unlocking the power of lambdas!
*
* (or whatever call method you want: functions pointer, pointer to method, functors)
*
*
* Can the connect be done async too?
*
* next;
*/
void simple_async_callback() {
try {
Session session = SessionConfigBuilder("127.0.0.1")
.build().connect();
std::promise<String> promise;
session.execute_async_callback("SELECT release_version FROM system.local WHERE key = ?",
[&promise] (Maybe<ResultSet> maybe) {
try {
ResultSet rs = maybe.get();
promise.set_value(rs.first().column("release_version").as_string());
} catch (Exception& ex) {
promise.set_exception(std::make_exception_ptr(ex));
}
}, "local");
std::cout << "release_version: " << promise.get_future().get() << "\n";
} catch (Exception& ex) {
std::cout << "Error: \"" << ex.what() << "\"\n";
}
}
/**
* Yes! Everything can be async.
*
*
* Are we done yet?
*
* next;
*/
void simple_full_async_callback() {
try {
std::promise<String> promise;
SessionConfigBuilder("127.0.0.1")
.build()
.connect_async_callback([&promise](Maybe<Session> maybe) {
try {
Session session = maybe.get();
session.execute_async_callback("SELECT release_version FROM system.local WHERE key = ?",
[&promise] (Maybe<ResultSet> maybe) {
try {
ResultSet rs = maybe.get();
promise.set_value(rs.first().column("release_version").as_string());
} catch (Exception& ex) {
promise.set_exception(std::make_exception_ptr(ex));
}
}, "local");
} catch (Exception& ex) {
promise.set_exception(std::make_exception_ptr(ex));
}
});
std::cout << "release_version: " << promise.get_future().get() << "\n";
} catch (Exception& ex) {
std::cout << "Error: \"" << ex.what() << "\"\n";
}
}
/*
*
* ______ _ _ _ _
* | ____| | | | | | | | |
* | |__ ___ ___ __| | | |__ __ _ ___ | | __ | |
* | __| / _ \ / _ \ / _` | | '_ \ / _` | / __| | |/ / | |
* | | | __/ | __/ | (_| | | |_) | | (_| | | (__ | < |_|
* |_| \___| \___| \__,_| |_.__/ \__,_| \___| |_|\_\ (_)
*
* Thanks!
*
* next;
*
* I wanted to make this brief. So maybe we cover more topics in the future?
*
* - Collections
* - Tuples and UDTs
* - Batches
* - Complex types (UUID, Date, Time, Duration, etc.)
* - Policies
* - ...
*
*/
int main() { }
@newkek
Copy link

newkek commented Jan 17, 2019

Great new API!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment