Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Mapping UUID as Scylla Driver's type #7

Open
wants to merge 4 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions addon/include/nodepp/object-member-function.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,10 @@ namespace NodePP {
return Napi::Function::New(
env, [=](const Napi::CallbackInfo &info) { return (instance->*function)(info); });
}

template <typename ClassType, typename FunctionType>
Napi::Function StaticMemberFunction(Napi::Env env, ClassType *instance, FunctionType function) {
return Napi::Function::New(
env, [=](const Napi::CallbackInfo &info) { return (ClassType::function)(info); });
}
} // namespace NodePP
33 changes: 33 additions & 0 deletions addon/include/scylladb_wrapper/types/uuid.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
#pragma once

#include <napi.h>
#include <scylladb/cassandra.h>

namespace scylladb_wrapper::uuid {
class UUIDv4 : public Napi::ObjectWrap<UUIDv4> {
private:
uint64_t _part1;
uint64_t _part2;

Napi::FunctionReference *_constructor;

public:
// ------------- C++ -------------
static Napi::Function GetClass(Napi::Env env);
UUIDv4(const Napi::CallbackInfo &info);

static Napi::Object Wrap(Napi::Env env, uint64_t part1, uint64_t part2);

CassUuid to_cass_uuid() const;
static bool is_instance_of(const Napi::Object &object);

const std::string to_string_cpp() const;
// -------------------------------

// ------------- JS --------------
static Napi::Value random(const Napi::CallbackInfo &info);
static Napi::Value from_string(const Napi::CallbackInfo &info);
Napi::Value to_string(const Napi::CallbackInfo &info);
// -------------------------------
};
} // namespace scylladb_wrapper::uuid
2 changes: 2 additions & 0 deletions addon/src/main.cpp
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
#include <napi.h>

#include <scylladb_wrapper/cluster/cluster.hpp>
#include <scylladb_wrapper/types/uuid.hpp>

Napi::Object Init(Napi::Env env, Napi::Object exports) {
exports.Set("Cluster", scylladb_wrapper::cluster::Cluster::GetClass(env));
exports.Set("UUIDv4", scylladb_wrapper::uuid::UUIDv4::GetClass(env));
return exports;
}

Expand Down
95 changes: 93 additions & 2 deletions addon/src/scylladb_wrapper/cluster/session.cpp
Original file line number Diff line number Diff line change
@@ -1,8 +1,20 @@
#include <fmt/core.h>
#include <scylladb/cassandra.h>

#include <nodepp/object-member-function.hpp>
#include <nodepp/promise-worker.hpp>
#include <regex>
#include <scylladb_wrapper/cluster/session.hpp>
#include <scylladb_wrapper/types/uuid.hpp>

#define CHECK_CASS_ERROR(rc, type) \
do { \
if (rc != CASS_OK) { \
Napi::TypeError::New(env, fmt::format("Invalid operation on type {}", type)) \
.ThrowAsJavaScriptException(); \
return env.Null(); \
} \
} while (0)

namespace scylladb_wrapper::cluster {

Expand All @@ -14,7 +26,7 @@ namespace scylladb_wrapper::cluster {

session_object.Set(Napi::String::New(env, "executeSync"),
NodePP::MemberFunction(env, this, &Session::execute_sync));

session_object.Set(Napi::String::New(env, "setKeyspace"),
NodePP::MemberFunction(env, this, &Session::set_keyspace));

Expand Down Expand Up @@ -58,9 +70,64 @@ namespace scylladb_wrapper::cluster {

// Get the string from the first parameter
std::string query = info[0].As<Napi::String>().Utf8Value();
if (query.empty()) {
Napi::TypeError::New(env, "Query cannot be empty").ThrowAsJavaScriptException();
return env.Null();
}

// There's a second parameter for the query parameters, which is an Array of values
// We'll use this to bind the values to the query
// Check if there is a second parameter and if it's an array
Napi::Array params;
if (info.Length() == 2) {
if (!info[1].IsArray()) {
Napi::TypeError::New(env, "Expected a array for the second parameter")
.ThrowAsJavaScriptException();
return env.Null();
}

params = info[1].As<Napi::Array>();
}

CassStatement* statement = cass_statement_new(query.c_str(), params ? params.Length() : 0);

// Bind the values to the query
if (params) {
for (uint32_t i = 0; i < params.Length(); ++i) {
Napi::Value param = params.Get(i);

if (param.IsString()) {
std::string param_string = param.As<Napi::String>().Utf8Value();
CHECK_CASS_ERROR(cass_statement_bind_string(statement, i, param_string.c_str()),
"string");
} else if (param.IsNumber()) {
double param_number = param.As<Napi::Number>().DoubleValue();
CHECK_CASS_ERROR(cass_statement_bind_double(statement, i, param_number), "number");
} else if (param.IsBoolean()) {
bool param_bool = param.As<Napi::Boolean>().Value();
cass_bool_t cass_bool = param_bool ? cass_true : cass_false;
CHECK_CASS_ERROR(cass_statement_bind_bool(statement, i, cass_bool), "boolean");
} else if (param.IsObject()) {
using namespace scylladb_wrapper::uuid;
Napi::Object obj = param.As<Napi::Object>();

if (UUIDv4::is_instance_of(obj)) {
std::string uuid = Napi::ObjectWrap<UUIDv4>::Unwrap(obj)->to_string_cpp();

CassUuid cass_uuid;
CHECK_CASS_ERROR(cass_uuid_from_string(uuid.c_str(), &cass_uuid), "UUID");
CHECK_CASS_ERROR(cass_statement_bind_uuid(statement, i, cass_uuid), "UUID");
}

} else {
Napi::TypeError::New(env, "Unsupported parameter type").ThrowAsJavaScriptException();
return env.Null();
}
}
}

CassStatement* statement = cass_statement_new(query.c_str(), 0);
CassFuture* result_future = cass_session_execute(this->session, statement);

cass_statement_free(statement);

auto result_code = cass_future_error_code(result_future);
Expand All @@ -74,6 +141,9 @@ namespace scylladb_wrapper::cluster {

cass_result_free(result);
cass_future_free(result_future);

fmt::print("Query executed successfully\n");

return resultsArray;
} else {
const char* message;
Expand Down Expand Up @@ -120,6 +190,14 @@ namespace scylladb_wrapper::cluster {
auto type = cass_value_type(column_value);

switch (type) {
case CASS_VALUE_TYPE_ASCII: {
const char* ascii;
size_t ascii_length;
cass_value_get_string(column_value, &ascii, &ascii_length);
column.Set(Napi::String::New(env, column_name),
Napi::String::New(env, std::string(ascii, ascii_length)));
break;
}
case CASS_VALUE_TYPE_TEXT: {
const char* text;
size_t text_length;
Expand Down Expand Up @@ -170,4 +248,17 @@ namespace scylladb_wrapper::cluster {

return valuesArray;
}

void print_uuid(const CassUuid* uuid) {
std::uint32_t time_high = static_cast<std::uint32_t>(uuid->time_and_version >> 32);
std::uint16_t time_low = static_cast<std::uint16_t>(uuid->time_and_version >> 16);
std::uint16_t time_mid = static_cast<std::uint16_t>(uuid->time_and_version);
std::uint16_t clock_seq = static_cast<std::uint16_t>(uuid->clock_seq_and_node >> 48);
std::uint64_t node = uuid->clock_seq_and_node & 0x0000FFFFFFFFFFFF;

std::string uuid_str = fmt::format("{:08x}-{:04x}-{:04x}-{:04x}-{:012x}", time_high, time_mid,
time_low, clock_seq, node);

fmt::print("UUID: {}\n", uuid_str);
}
} // namespace scylladb_wrapper::cluster
152 changes: 152 additions & 0 deletions addon/src/scylladb_wrapper/types/uuid.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
#include <fmt/core.h>

#include <iomanip>
#include <iostream>
#include <random>
#include <scylladb_wrapper/types/uuid.hpp>
#include <sstream>

namespace scylladb_wrapper::uuid {
Napi::Function UUIDv4::GetClass(Napi::Env env) {
auto methods = {
StaticMethod("random", &UUIDv4::random),
StaticMethod("fromString", &UUIDv4::from_string),
InstanceMethod("toString", &UUIDv4::to_string),
};
Napi::Function func = DefineClass(env, "UUIDv4", methods);
Napi::FunctionReference* constructor = new Napi::FunctionReference();

// Create a persistent reference to the class constructor. This will allow
// a function called on a class prototype and a function
// called on instance of a class to be distinguished from each other.
*constructor = Napi::Persistent(func);

// supress destructor
constructor->SuppressDestruct();

// Store the constructor as the add-on instance data. This will allow this
// add-on to support multiple instances of itself running on multiple worker
// threads, as well as multiple instances of itself running in different
// contexts on the same thread.
//
// By default, the value set on the environment here will be destroyed when
// the add-on is unloaded using the `delete` operator, but it is also
// possible to supply a custom deleter.
env.SetInstanceData<Napi::FunctionReference>(constructor);

return func;
}

Napi::Object UUIDv4::Wrap(Napi::Env env, uint64_t part1, uint64_t part2) {
Napi::EscapableHandleScope scope(env);

Napi::FunctionReference* constructor = env.GetInstanceData<Napi::FunctionReference>();
Napi::Function ctor = constructor->Value();
Napi::Object obj = ctor.New({
Napi::BigInt::New(env, part1),
Napi::BigInt::New(env, part2),
});

obj.Set("uuidType", Napi::String::New(env, "UUIDv4"));

return scope.Escape(napi_value(obj)).ToObject();
}

UUIDv4::UUIDv4(const Napi::CallbackInfo& info) : Napi::ObjectWrap<UUIDv4>(info) {
Napi::Env env = info.Env();
Napi::HandleScope scope(env);

if (!info.IsConstructCall()) {
Napi::TypeError::New(env, "Use the new operator to create instances of this type.")
.ThrowAsJavaScriptException();
}

bool* lossless = new bool();
*lossless = false;
_part1 = info[0].As<Napi::BigInt>().Uint64Value(lossless);
_part2 = info[1].As<Napi::BigInt>().Uint64Value(lossless);

// Check if the third parameters if of the type of this ObjectWrap class <UUIDv4>
}

Napi::Value UUIDv4::random(const Napi::CallbackInfo& info) {
std::random_device rd;
std::mt19937_64 rng(rd());
std::uniform_int_distribution<uint64_t> dist;

auto _part1 = dist(rng);
auto _part2 = dist(rng);

// Set the version bits (0100) and the variant bits (10)
_part1 = (_part1 & 0xFFFFFFFFFFFF0FFFULL) | 0x0000000000004000ULL;
_part2 = (_part2 & 0x3FFFFFFFFFFFFFFFULL) | 0x8000000000000000ULL;

// TODO: This wrap method introduces a unnecessary wrap of data, we may create a ISSUE at
// node-addon-api to see if they can guide us to a better solution later. This new solution may
// not introduce breaking changes, so the users of this library will not be affected.
return Wrap(info.Env(), _part1, _part2);
}

Napi::Value UUIDv4::from_string(const Napi::CallbackInfo& info) {
std::string uuid_str = info[0].As<Napi::String>().Utf8Value();

if (uuid_str.size() != 36 || uuid_str[8] != '-' || uuid_str[13] != '-' || uuid_str[18] != '-'
|| uuid_str[23] != '-') {
throw std::invalid_argument("Invalid UUID string format.");
}

std::stringstream ss(uuid_str);
uint32_t p1a;
uint16_t p1b, p1c;
uint16_t p2a;
uint64_t p2b;

char dash;
ss >> std::hex >> p1a >> dash >> p1b >> dash >> p1c >> dash >> p2a >> dash >> p2b;

if (ss.fail()) {
throw std::invalid_argument("Invalid UUID string format.");
}

auto _part1 = (static_cast<uint64_t>(p1a) << 32) | (static_cast<uint64_t>(p1b) << 16) | p1c;
auto _part2 = (static_cast<uint64_t>(p2a) << 48) | p2b;

return Wrap(info.Env(), _part1, _part2);
}

Napi::Value UUIDv4::to_string(const Napi::CallbackInfo& info) {
std::ostringstream oss;

oss << std::hex << std::setw(8) << std::setfill('0') << ((_part1 >> 32) & 0xFFFFFFFFULL) << '-';
oss << std::hex << std::setw(4) << std::setfill('0') << ((_part1 >> 16) & 0xFFFFULL) << '-';
oss << std::hex << std::setw(4) << std::setfill('0') << (_part1 & 0xFFFFULL) << '-';
oss << std::hex << std::setw(4) << std::setfill('0') << ((_part2 >> 48) & 0xFFFFULL) << '-';
oss << std::hex << std::setw(12) << std::setfill('0') << (_part2 & 0xFFFFFFFFFFFFULL);

return Napi::String::New(info.Env(), oss.str());
}

const std::string UUIDv4::to_string_cpp() const {
std::ostringstream oss;

oss << std::hex << std::setw(8) << std::setfill('0') << ((_part1 >> 32) & 0xFFFFFFFFULL) << '-';
oss << std::hex << std::setw(4) << std::setfill('0') << ((_part1 >> 16) & 0xFFFFULL) << '-';
oss << std::hex << std::setw(4) << std::setfill('0') << (_part1 & 0xFFFFULL) << '-';
oss << std::hex << std::setw(4) << std::setfill('0') << ((_part2 >> 48) & 0xFFFFULL) << '-';
oss << std::hex << std::setw(12) << std::setfill('0') << (_part2 & 0xFFFFFFFFFFFFULL);

return oss.str();
}

CassUuid UUIDv4::to_cass_uuid() const {
CassUuid uuid;
uuid.time_and_version = _part1;
uuid.clock_seq_and_node = _part2;
return uuid;
}

bool UUIDv4::is_instance_of(const Napi::Object& object) {
return object.Has("uuidType")
&& object.Get("uuidType").As<Napi::String>().Utf8Value() == "UUIDv4";
}
} // namespace scylladb_wrapper::uuid
7 changes: 6 additions & 1 deletion binding.gyp
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,12 @@
],
"libraries": [ "<!@(./scripts/deps.sh --emit-libraries)", ],
"conditions": [
['OS!="win"', { 'cflags_cc+': [ '-std=c++20' ] }],
[
'OS!="win"',
{
'cflags_cc+': [ '-std=c++20' ]
}
],
]
}
]
Expand Down
Loading