#pragma once #include "data/cassandra/Types.hpp" #include "data/cassandra/impl/Collection.hpp" #include "data/cassandra/impl/ManagedObject.hpp" #include "data/cassandra/impl/Tuple.hpp" #include "util/UnsupportedType.hpp" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace data::cassandra::impl { class Statement : public ManagedObject { static constexpr auto kDeleter = [](CassStatement* ptr) { cass_statement_free(ptr); }; public: /** * @brief Construct a new statement with optionally provided arguments. * * Note: it's up to the user to make sure the bound parameters match * the format of the query (e.g. amount of '?' matches count of args). */ template explicit Statement(std::string_view query, Args&&... args) : ManagedObject{cass_statement_new_n(query.data(), query.size(), sizeof...(args)), kDeleter} { // TODO: figure out how to set consistency level in config // NOTE: Keyspace doesn't support QUORUM at write level // cass_statement_set_consistency(*this, CASS_CONSISTENCY_LOCAL_QUORUM); cass_statement_set_is_idempotent(*this, cass_true); bind(std::forward(args)...); } /* implicit */ Statement(CassStatement* ptr) : ManagedObject{ptr, kDeleter} { // cass_statement_set_consistency(*this, CASS_CONSISTENCY_LOCAL_QUORUM); cass_statement_set_is_idempotent(*this, cass_true); } /** * @brief Binds the given arguments to the statement. * * @param args Arguments to bind */ template void bind(Args&&... args) const { std::size_t idx = 0; // NOLINT(misc-const-correctness) (this->bindAt(idx++, std::forward(args)), ...); } /** * @brief Binds an argument to a specific index. * * @param idx The index of the argument * @param value The value to bind it to */ template void bindAt(std::size_t const idx, Type&& value) const { using std::to_string; auto throwErrorIfNeeded = [idx](CassError rc, std::string_view label) { if (rc != CASS_OK) { throw std::logic_error( fmt::format("[{}] at idx {}: {}", label, idx, cass_error_desc(rc)) ); } }; auto bindBytes = [this, idx](auto const* data, size_t size) { return cass_statement_bind_bytes( *this, idx, static_cast(data), size ); }; using DecayedType = std::decay_t; using UCharVectorType = std::vector; using UintTupleType = std::tuple; using UintByteTupleType = std::tuple; using ByteVectorType = std::vector; if constexpr ( std::is_same_v || std::is_same_v ) { auto const rc = bindBytes(value.data(), value.size()); throwErrorIfNeeded(rc, "Bind ripple::base_uint"); } else if constexpr (std::is_same_v) { auto const rc = bindBytes(value.data(), value.size()); throwErrorIfNeeded(rc, "Bind ripple::AccountID"); } else if constexpr (std::is_same_v) { auto const rc = bindBytes(value.data(), value.size()); throwErrorIfNeeded(rc, "Bind vector"); } else if constexpr (std::is_convertible_v) { // reinterpret_cast is needed here :'( auto const rc = bindBytes(reinterpret_cast(value.data()), value.size()); throwErrorIfNeeded(rc, "Bind string (as bytes)"); } else if constexpr (std::is_convertible_v) { auto const rc = cass_statement_bind_string_n(*this, idx, value.text.c_str(), value.text.size()); throwErrorIfNeeded(rc, "Bind string (as TEXT)"); } else if constexpr ( std::is_same_v || std::is_same_v ) { auto const rc = cass_statement_bind_tuple(*this, idx, Tuple{std::forward(value)}); throwErrorIfNeeded(rc, "Bind tuple or "); } else if constexpr (std::is_same_v) { auto const rc = cass_statement_bind_collection(*this, idx, Collection{std::forward(value)}); throwErrorIfNeeded(rc, "Bind collection"); } else if constexpr (std::is_same_v) { auto const rc = cass_statement_bind_bool(*this, idx, value ? cass_true : cass_false); throwErrorIfNeeded(rc, "Bind bool"); } else if constexpr (std::is_same_v) { auto const rc = cass_statement_bind_int32(*this, idx, value.limit); throwErrorIfNeeded(rc, "Bind limit (int32)"); } else if constexpr (std::is_convertible_v) { auto const uuidStr = boost::uuids::to_string(value); CassUuid cassUuid; auto rc = cass_uuid_from_string(uuidStr.c_str(), &cassUuid); throwErrorIfNeeded(rc, "CassUuid from string"); rc = cass_statement_bind_uuid(*this, idx, cassUuid); throwErrorIfNeeded(rc, "Bind boost::uuid"); // clio only uses bigint (int64_t) so we convert any incoming type } else if constexpr (std::is_convertible_v) { auto const rc = cass_statement_bind_int64(*this, idx, value); throwErrorIfNeeded(rc, "Bind int64"); } else { // type not supported for binding static_assert(util::Unsupported); } } }; /** * @brief Represents a prepared statement on the DB side. * * This is used to produce Statement objects that can be executed. */ class PreparedStatement : public ManagedObject { static constexpr auto kDeleter = [](CassPrepared const* ptr) { cass_prepared_free(ptr); }; public: /* implicit */ PreparedStatement(CassPrepared const* ptr) : ManagedObject{ptr, kDeleter} { } /** * @brief Bind the given arguments and produce a ready to execute Statement. * * @param args The arguments to bind * @return A bound and ready to execute Statement object */ template [[nodiscard]] Statement bind(Args&&... args) const { Statement statement = cass_prepared_bind(*this); statement.bind(std::forward(args)...); return statement; } }; } // namespace data::cassandra::impl