Created
January 17, 2019 16:26
-
-
Save mpenick/1feeb0622d5b0192d709cd5c406eb392 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#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() { } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Great new API!