mirror of
https://github.com/XRPLF/clio.git
synced 2026-04-29 15:37:53 +00:00
188 lines
7.8 KiB
C++
188 lines
7.8 KiB
C++
//------------------------------------------------------------------------------
|
|
/*
|
|
This file is part of clio: https://github.com/XRPLF/clio
|
|
Copyright (c) 2023, the clio developers.
|
|
|
|
Permission to use, copy, modify, and distribute this software for any
|
|
purpose with or without fee is hereby granted, provided that the above
|
|
copyright notice and this permission notice appear in all copies.
|
|
|
|
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
|
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
|
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
|
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
|
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
|
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
|
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
|
*/
|
|
//==============================================================================
|
|
|
|
#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 <boost/uuid/uuid.hpp>
|
|
#include <boost/uuid/uuid_io.hpp>
|
|
#include <cassandra.h>
|
|
#include <fmt/format.h>
|
|
#include <xrpl/basics/base_uint.h>
|
|
#include <xrpl/protocol/AccountID.h>
|
|
#include <xrpl/protocol/STAccount.h>
|
|
|
|
#include <cstddef>
|
|
#include <cstdint>
|
|
#include <stdexcept>
|
|
#include <string>
|
|
#include <string_view>
|
|
#include <tuple>
|
|
#include <type_traits>
|
|
#include <vector>
|
|
|
|
namespace data::cassandra::impl {
|
|
|
|
class Statement : public ManagedObject<CassStatement> {
|
|
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 <typename... Args>
|
|
explicit Statement(std::string_view query, Args&&... args)
|
|
: ManagedObject{cass_statement_new_n(query.data(), query.size(), sizeof...(args)), kDELETER}
|
|
{
|
|
cass_statement_set_consistency(*this, CASS_CONSISTENCY_LOCAL_QUORUM);
|
|
cass_statement_set_is_idempotent(*this, cass_true);
|
|
bind<Args...>(std::forward<Args>(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 <typename... Args>
|
|
void
|
|
bind(Args&&... args) const
|
|
{
|
|
std::size_t idx = 0; // NOLINT(misc-const-correctness)
|
|
(this->bindAt<Args>(idx++, std::forward<Args>(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 <typename Type>
|
|
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<cass_byte_t const*>(data), size);
|
|
};
|
|
|
|
using DecayedType = std::decay_t<Type>;
|
|
using UCharVectorType = std::vector<unsigned char>;
|
|
using UintTupleType = std::tuple<uint32_t, uint32_t>;
|
|
using UintByteTupleType = std::tuple<uint32_t, ripple::uint256>;
|
|
using ByteVectorType = std::vector<ripple::uint256>;
|
|
|
|
if constexpr (std::is_same_v<DecayedType, ripple::uint256> || std::is_same_v<DecayedType, ripple::uint192>) {
|
|
auto const rc = bindBytes(value.data(), value.size());
|
|
throwErrorIfNeeded(rc, "Bind ripple::base_uint");
|
|
} else if constexpr (std::is_same_v<DecayedType, ripple::AccountID>) {
|
|
auto const rc = bindBytes(value.data(), value.size());
|
|
throwErrorIfNeeded(rc, "Bind ripple::AccountID");
|
|
} else if constexpr (std::is_same_v<DecayedType, UCharVectorType>) {
|
|
auto const rc = bindBytes(value.data(), value.size());
|
|
throwErrorIfNeeded(rc, "Bind vector<unsigned char>");
|
|
} else if constexpr (std::is_convertible_v<DecayedType, std::string>) {
|
|
// reinterpret_cast is needed here :'(
|
|
auto const rc = bindBytes(reinterpret_cast<unsigned char const*>(value.data()), value.size());
|
|
throwErrorIfNeeded(rc, "Bind string (as bytes)");
|
|
} else if constexpr (std::is_convertible_v<DecayedType, Text>) {
|
|
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<DecayedType, UintTupleType> ||
|
|
std::is_same_v<DecayedType, UintByteTupleType>) {
|
|
auto const rc = cass_statement_bind_tuple(*this, idx, Tuple{std::forward<Type>(value)});
|
|
throwErrorIfNeeded(rc, "Bind tuple<uint32, uint32> or <uint32_t, ripple::uint256>");
|
|
} else if constexpr (std::is_same_v<DecayedType, ByteVectorType>) {
|
|
auto const rc = cass_statement_bind_collection(*this, idx, Collection{std::forward<Type>(value)});
|
|
throwErrorIfNeeded(rc, "Bind collection");
|
|
} else if constexpr (std::is_same_v<DecayedType, bool>) {
|
|
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<DecayedType, Limit>) {
|
|
auto const rc = cass_statement_bind_int32(*this, idx, value.limit);
|
|
throwErrorIfNeeded(rc, "Bind limit (int32)");
|
|
} else if constexpr (std::is_convertible_v<DecayedType, boost::uuids::uuid>) {
|
|
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<DecayedType, int64_t>) {
|
|
auto const rc = cass_statement_bind_int64(*this, idx, value);
|
|
throwErrorIfNeeded(rc, "Bind int64");
|
|
} else {
|
|
// type not supported for binding
|
|
static_assert(util::Unsupported<DecayedType>);
|
|
}
|
|
}
|
|
};
|
|
|
|
/**
|
|
* @brief Represents a prepared statement on the DB side.
|
|
*
|
|
* This is used to produce Statement objects that can be executed.
|
|
*/
|
|
class PreparedStatement : public ManagedObject<CassPrepared const> {
|
|
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 <typename... Args>
|
|
Statement
|
|
bind(Args&&... args) const
|
|
{
|
|
Statement statement = cass_prepared_bind(*this);
|
|
statement.bind<Args...>(std::forward<Args>(args)...);
|
|
return statement;
|
|
}
|
|
};
|
|
|
|
} // namespace data::cassandra::impl
|