namespace ceph::libfdb {
+/*JFW:
database::database()
{
std::call_once(ceph::libfdb::detail::database_system::fdb_was_initialized, ceph::libfdb::detail::database_system::initialize_fdb);
if(nullptr != fdb_handle) {
fdb_database_destroy(fdb_handle), fdb_handle = nullptr;
}
-}
+}*/
[[nodiscard]] bool transaction::commit()
{
return true;
}
-} // namespace ceph::libfdb
+} // namespace ceph::libfdb
namespace ceph::libfdb::detail {
+// JFW: challenge: how to access ceph::libfdb::from at the right time w/o this being in here?
std::pair<std::string, std::string> to_decoded_kv_pair(const FDBKeyValue kv)
{
std::pair<std::string, std::string> r;
return r;
}
-} // namespace ceph::libfdb::detail
+} // namespace ceph::libfdb::detail
// Ceph uses libfmt rather than <format>:
#include <fmt/format.h>
+#include <map> // JFW: remove after lifting (and, indeed, remove to see what you need to lift!)
+
#include <tuple>
#include <mutex>
#include <memory>
// Should we commit after the (possibly) mutating operation?
enum struct commit_after_op { commit, no_commit };
-// JFW: it may be with messing with error_code here, but I don't see a ton of utility in that at the moment:
struct libfdb_exception final : std::runtime_error
{
using std::runtime_error::runtime_error;
friend class transaction;
};
-/* Analogs to work with key ranges:
-JFW: this is TODO and requires a bit of thought-- for now, these types will
-at least keep us from "stepping over our own feet" in terms of API design, and
-be a sensible default:
-#define FDB_KEYSEL_LAST_LESS_THAN(k, l) k, l, 0, 0
-#define FDB_KEYSEL_LAST_LESS_OR_EQUAL(k, l) k, l, 1, 0
-#define FDB_KEYSEL_FIRST_GREATER_THAN(k, l) k, l, 1, 1
-#define FDB_KEYSEL_FIRST_GREATER_OR_EQUAL(k, l) k, l, 0, 1
-
-enum struct key_selector { first_gt, first_gteq, last_lt, last_lteq };
-
-JFW: add Concepts-- keytype should be <= orderable, copyable, ... mostly the <= comp is important;
-the restriction to string_view is artificial
-*/
+// May be good to revisit my earlier idea of doing this with references/string_view:
struct select final
{
std::string begin_key, end_key;
namespace ceph::libfdb::detail {
-// I tried fancier versions of this that wrapped the function and forwarded the parameters; unfortunately,
-// FDB sometimes likes to use macros, which wind up making that scheme less orthogonal than it should be--
-// which is unfortunate. This isn't as pretty, but "works with everything":
-[[maybe_unused]] inline auto check_fdb_result(const fdb_error_t result)
+constexpr auto as_fdb_span(const char *s)
{
- if(0 != result)
- throw libfdb_exception(result);
+ // Sorry this is tied to the encoding (zpp_bits), but it "just is" for now...
+ return std::span<const std::uint8_t>((const std::uint8_t *)s, std::strlen(s));
+}
- return result;
+constexpr auto as_fdb_span(std::string_view sv)
+{
+ return std::span<const std::uint8_t>((const std::uint8_t *)sv.data(), sv.size());
}
-inline bool commit(ceph::libfdb::transaction_handle& txn);
+} // namespace ceph::libfdb::detail
+
+namespace ceph::libfdb::detail {
+
+std::pair<std::string, std::string> to_decoded_kv_pair(const FDBKeyValue kv);
+
+struct maybe_commit;
} // namespace ceph::libfdb::detail
namespace ceph::libfdb::detail {
-constexpr auto as_fdb_span(std::string_view sv)
-{
- return std::span<const std::uint8_t>((const std::uint8_t *)sv.data(), sv.size());
-}
+// The global DB state and management thread:
+// JFW: more user hooks that go into FDB system possible here
+class database_system final
+{
+ database_system() = delete;
-}
+ private:
+ static inline bool was_initialized = false;
+
+ static inline std::once_flag fdb_was_initialized;
+ static inline std::jthread fdb_network_thread;
+
+ static inline void initialize_fdb()
+ {
+ // This must be called before ANY other API function:
+ if(fdb_error_t r = fdb_select_api_version(FDB_API_VERSION); 0 != r)
+ throw libfdb_exception(r);
+
+ // Zero or more calls to this may now be made:
+ // fdb_error_t fdb_network_set_option(FDBNetworkOption option, uint8_t const *value, int value_length)
+
+ // This must be called before any other API function (besides >= 0 calls to fdb_network_set_option()):
+ if(fdb_error_t r = fdb_setup_network(); 0 != r)
+ throw libfdb_exception(r);
+
+ // Launch network thread:
+ fdb_network_thread = std::jthread { &fdb_run_network };
+
+ // Okie-dokie, we're all set:
+ was_initialized = true;
+ }
+
+ public:
+ static bool& initialized() { return was_initialized; }
+
+ public:
+ static inline void shutdown_fdb()
+ {
+ using namespace std::chrono_literals;
+
+ if(not initialized()) {
+ return;
+ }
+
+ // shut down network and database:
+ if(int r = fdb_stop_network(); 0 != r)
+ {
+ // JFW: in this case, we likely don't want to throw from our dtor, but we may
+ // have something to log.
+ // fmt::println("database::shutdown_fdb() error {}", r);
+ }
+
+ std::this_thread::sleep_for(10ms);
+
+ if(fdb_network_thread.joinable()) {
+ fdb_network_thread.join();
+ }
+ }
+
+ private:
+ friend struct ceph::libfdb::database;
+};
+
+} // namespace ceph::libfdb::detail
namespace ceph::libfdb {
FDBDatabase *fdb_handle = nullptr;
public:
- database(const std::filesystem::path cluster_file_path);
- database();
+ database()
+ {
+ std::call_once(ceph::libfdb::detail::database_system::fdb_was_initialized, ceph::libfdb::detail::database_system::initialize_fdb);
+
+ if(fdb_error_t r = fdb_create_database(nullptr, &fdb_handle); 0 != r)
+ throw libfdb_exception(r);
- ~database();
+ // may now set database options:
+ ; // JFW
+ }
+
+ ~database()
+ {
+//JFW: move to smartptr
+ if(nullptr != fdb_handle) {
+ fdb_database_destroy(fdb_handle), fdb_handle = nullptr;
+ }
+ }
public:
operator bool() { return nullptr != raw_handle(); }
{
database_handle dbh;
-//JFW: FDBTransaction *txn_handle = nullptr;
std::unique_ptr<FDBTransaction, decltype(&fdb_transaction_destroy)> txn_ptr;
private:
FDBTransaction *create_transaction() {
- FDBTransaction *txn_p = nullptr;
+ FDBTransaction *txn_p = nullptr; // JFW: *or* should it be angherror to create over extant txn?
if(fdb_error_t r = fdb_database_create_transaction(dbh->raw_handle(), &txn_p); 0 != r) {
throw libfdb_exception(r);
}
return txn_p;
}
+/*JFW: rework into a coherent mechanism--
void establish() {
destroy(), txn_ptr.reset(create_transaction());
}
if(not this) {
throw ceph::libfdb::libfdb_exception("inactive transaction");
}
-
-/* if(not this) {
+ if(not this) {
establish();
- }*/
- }
+ }
+ }*/
+
+ private:
+ inline bool get_single_value_from_transaction(const std::span<const std::uint8_t>& key, std::invocable<std::span<const std::uint8_t>> auto&& write_output);
+ inline bool get_value_range_from_transaction(std::span<const std::uint8_t> begin_key, std::span<const std::uint8_t> end_key, auto out_iter);
+ inline future_value get_range_future_from_transaction(std::span<const std::uint8_t> begin_key, std::span<const std::uint8_t> end_key);
public:
transaction(database_handle& dbh)
{}
public:
+ // I vacillate between considering this ok, or not even a good idea...
operator bool() { return dbh && nullptr != raw_handle(); }
public:
private:
void set(std::span<const std::uint8_t> k, std::span<const std::uint8_t> v) {
- maybe_vivify();
+ // maybe_vivify();
fdb_transaction_set(raw_handle(),
(const uint8_t*)k.data(), k.size(),
(const uint8_t*)v.data(), v.size());
}
+ // The output requirement to std::string is a bit artificial, and should be revisited:
+ // Satisfying std::output_iterator<std::string, ??> appears to be trickier than it looks-- so don't be
+ // misled by my poor specification here, please; I will fix this!!:
+ bool get(std::span<const std::uint8_t> begin_key, std::span<const std::uint8_t> end_key, auto out_iter) {
+ return ceph::libfdb::transaction::get_value_range_from_transaction(begin_key, end_key, out_iter);
+ }
+
+ bool get(std::span<const std::uint8_t> k, std::invocable<std::span<const std::uint8_t>> auto& val_collector) {
+ return get_single_value_from_transaction(k, val_collector);
+ }
+
void erase(std::span<const std::uint8_t> k) {
- maybe_vivify();
+// maybe_vivify();
fdb_transaction_clear(raw_handle(),
(const std::uint8_t *)k.data(), k.size());
}
void erase(const ceph::libfdb::concepts::selector auto& key_range) {
- maybe_vivify();
+ // maybe_vivify();
fdb_transaction_clear_range(raw_handle(),
(const uint8_t *)key_range.begin_key.data(), key_range.begin_key.size(),
(const uint8_t *)key_range.end_key.data(), key_range.end_key.size());
}
- private:
+ bool key_exists(std::string_view k) {
+ return get_single_value_from_transaction(detail::as_fdb_span(k), [](auto) {});
+ }
+
bool commit();
void destroy() { txn_ptr.reset(); }
private:
- friend inline bool get(ceph::libfdb::transaction_handle, const auto&, auto&, const commit_after_op);
- friend inline bool get(ceph::libfdb::transaction_handle, const ceph::libfdb::select&, auto, const commit_after_op);
+ friend transaction_handle make_transaction(database_handle dbh);
+
+ private:
+ friend inline void set(transaction_handle, const char*, const char*, commit_after_op);
+ friend inline void set(std::span<const unsigned char>, std::span<const unsigned char>);
+ friend inline void set(std::span<const std::uint8_t>, std::span<const std::uint8_t>);
+ friend inline void set(transaction_handle, const std::string_view, const auto&, const commit_after_op);
+ friend inline void set(transaction_handle, std::input_iterator auto, std::input_iterator auto, const commit_after_op);
- template <typename K, typename V>
- friend inline void set(transaction_handle h, const K& k, const V& v, const commit_after_op commit_after);
+ // Clearly, this could use some work-- the trick is disambiguating the iterators, do-able but it will take a little work:
+ friend inline void set(transaction_handle txn, std::map<std::string, std::string>::const_iterator b, std::map<std::string, std::string>::const_iterator e, const commit_after_op commit_after);
- template <std::input_iterator PairIter>
- friend inline void set(transaction_handle h, PairIter b, PairIter e, const commit_after_op commit_after);
- friend inline void erase(ceph::libfdb::transaction_handle, std::string_view, const commit_after_op); // JFW: should adjust back to auto
+// JFW: needs lifting
+ friend inline bool get(ceph::libfdb::transaction_handle, std::string_view, auto&, const commit_after_op);
+ friend inline bool get(ceph::libfdb::transaction_handle, const ceph::libfdb::select&, auto, const commit_after_op);
+
+ friend inline void erase(ceph::libfdb::transaction_handle, std::string_view, const commit_after_op);
friend inline void erase(ceph::libfdb::transaction_handle, const ceph::libfdb::select&, const commit_after_op);
+ friend inline bool key_exists(transaction_handle txn, std::string_view k, const commit_after_op commit_after);
- private:
+ // JFW: std::remove_cvref():
friend inline bool commit(transaction_handle& txn);
+ friend inline bool commit(transaction_handle txn);
- private:
- friend transaction_handle make_transaction(database_handle dbh);
+ // private:
+ friend struct ceph::libfdb::detail::maybe_commit;
};
} // namespace ceph::libfdb
-namespace ceph::libfdb::detail {
-
-// The global DB state and management thread:
-// JFW: there are some more user hooks that go go in here
-class database_system final
-{
- database_system() = delete;
-
- private:
- static inline bool was_initialized = false;
-
- static inline std::once_flag fdb_was_initialized;
- static inline std::jthread fdb_network_thread;
-
- static inline void initialize_fdb()
- {
- // This must be called before ANY other API function:
- if(fdb_error_t r = fdb_select_api_version(FDB_API_VERSION); 0 != r)
- throw libfdb_exception(r);
-
- // Zero or more calls to this may now be made:
- // fdb_error_t fdb_network_set_option(FDBNetworkOption option, uint8_t const *value, int value_length)
-
- // This must be called before any other API function (besides >= 0 calls to fdb_network_set_option()):
- if(fdb_error_t r = fdb_setup_network(); 0 != r)
- throw libfdb_exception(r);
-
- // Launch network thread:
- fdb_network_thread = std::jthread { &fdb_run_network };
-
- // Okie-dokie, we're all set:
- was_initialized = true;
- }
-
- public:
- static bool& initialized() { return was_initialized; }
-
- public:
- static inline void shutdown_fdb()
- {
- using namespace std::chrono_literals;
-
- if(not initialized()) {
- return;
- }
-
- // shut down network and database:
- if(int r = fdb_stop_network(); 0 != r)
- {
- // JFW: in this case, we likely don't want to throw from our dtor, but we may
- // have something to log.
- // fmt::println("database::shutdown_fdb() error {}", r);
- }
-
- std::this_thread::sleep_for(10ms);
-
- if(fdb_network_thread.joinable()) {
- fdb_network_thread.join();
- }
- }
-
- private:
- friend struct ceph::libfdb::database;
-};
-
-} // namespace ceph::libfdb::detail
-
namespace ceph::libfdb {
inline void shutdown_libfdb()
} // namespace ceph::libfdb
-// This is where the touchier FDB interfacing is-- hopefully any bugs will be easy
-// to correct by at least having this in "sorta" one place:
-namespace ceph::libfdb::detail {
-
-inline bool get_value_range_from_transaction(transaction_handle& txn, std::span<const std::uint8_t> begin_key, std::span<const std::uint8_t> end_key, auto out_iter);
-
-inline bool get_single_value_from_transaction(transaction_handle& txn, const std::span<const std::uint8_t>& key, std::invocable<std::span<const std::uint8_t>> auto& write_output);
-
-inline future_value get_range_future_from_transaction(transaction_handle& txn, std::span<const std::uint8_t> begin_key, std::span<const std::uint8_t> end_key);
-
-std::pair<std::string, std::string> to_decoded_kv_pair(const FDBKeyValue kv);
-/*
-{
- std::pair<std::string, std::string> r;
-
- r.first.assign((const char *)kv.key, static_cast<std::string::size_type>(kv.key_length));
-
- ceph::libfdb::from::convert(std::span<const std::uint8_t>(kv.value, kv.value_length), r.second);
-
- return r;
-}*/
-
// JFW: TODO: consolidate this with get_value_from_transaction()! See details there.
-// JFW: (it's a bit trickier than it looks at first...)
-inline bool get_value_range_from_transaction(transaction_handle& txn, std::span<const std::uint8_t> begin_key, std::span<const std::uint8_t> end_key, auto out_iter)
+// JFW: (it's a bit trickier than it looks at first-- I need to separate it into more components.)
+inline bool ceph::libfdb::transaction::get_value_range_from_transaction(std::span<const std::uint8_t> begin_key, std::span<const std::uint8_t> end_key, auto out_iter)
{
- if(nullptr == txn) {
- throw libfdb_exception("missing transaction");
- }
-
const fdb_bool_t is_snapshot = false;
const FDBKeyValue *out_kvs = nullptr;
int out_count = 0; // updated by FDB's read
fdb_bool_t out_more = false; // true if there's more to read
- future_value fv = get_range_future_from_transaction(txn, begin_key, end_key);
+ future_value fv = get_range_future_from_transaction(begin_key, end_key);
if(fdb_error_t r = fdb_future_block_until_ready(fv.raw_handle()); 0 != r) {
throw libfdb_exception(r);
if(fdb_error_t r = fdb_future_get_keyvalue_array(fv.raw_handle(), &out_kvs, &out_count, &out_more); 0 != r) {
- auto fv2 = future_value(fdb_transaction_on_error(txn->raw_handle(), r));
+ auto fv2 = future_value(fdb_transaction_on_error(raw_handle(), r));
if(fdb_error_t r2 = fdb_future_block_until_ready(fv2.raw_handle()); 0 != r2) {
throw libfdb_exception(r);
// JFW: This probably needs to go back into base.h;
// JFW: I think there's a simpler and also-"approved" way to do this-- I'll look at it later, getting rid
// of the strangeness and complexity here would be good:
-inline bool get_single_value_from_transaction(transaction_handle& txn, const std::span<const std::uint8_t>& key, std::invocable<std::span<const std::uint8_t>> auto& write_output)
+inline bool ceph::libfdb::transaction::get_single_value_from_transaction(const std::span<const std::uint8_t>& key, std::invocable<std::span<const std::uint8_t>> auto&& write_output)
{
fdb_bool_t key_was_found = false;
fdb_bool_t is_snapshot = false;
const uint8_t *out_buffer = nullptr;
int out_len = 0;
- future_value fv = fdb_transaction_get(txn->raw_handle(), (const uint8_t *)key.data(), key.size(), is_snapshot);
+ ceph::libfdb::future_value fv = fdb_transaction_get(raw_handle(), (const uint8_t *)key.data(), key.size(), is_snapshot);
// AWAIT the FUTURE:
if(fdb_error_t r = fdb_future_block_until_ready(fv.raw_handle()); 0 != r) {
}
if(fdb_error_t r = fdb_future_get_value(fv.raw_handle(), &key_was_found, &out_buffer, &out_len); 0 != r) {
- auto fv2 = future_value(fdb_transaction_on_error(txn->raw_handle(), r));
+ auto fv2 = future_value(fdb_transaction_on_error(raw_handle(), r));
if(fdb_error_t r2 = fdb_future_block_until_ready(fv2.raw_handle()); 0 != r2) {
throw libfdb_exception(r2);
*/
// JFW: TODO: implement/expose remaining features (key selectors, batching and chunking, etc.):
-inline future_value get_range_future_from_transaction(transaction_handle& txn, std::span<const std::uint8_t> begin_key, std::span<const std::uint8_t> end_key)
+inline ceph::libfdb::future_value ceph::libfdb::transaction::get_range_future_from_transaction(std::span<const std::uint8_t> begin_key, std::span<const std::uint8_t> end_key)
{
// By default, give (begin, end]; not much in the C API documentation about selector details, the Python docs
// have a bit more information:
int iterations = 1; // should start at one, is incremented when streaming_mode_t::iterator is enabled, else ignored
return fdb_transaction_get_range(
- txn->raw_handle(),
+ raw_handle(),
(const uint8_t *)begin_key.data(), begin_key.size(), // the reference-point key (begin_key)
begin_or_eq,
begin_offset,
);
}
-} // namespace ceph::libfdb::detail
-
-
#endif
#include "base.h"
#include "conversion.h"
+#include <map> // JFW: REMOVEME when iterators are properly lifted
+
#include <span>
#include <cstdint>
#include <concepts>
#include <iterator>
+#include <exception>
#include <algorithm>
#include <functional>
-#include <fmt/format.h> // JFW:
+/* JFW: needs tinkering
+namespace ceph::libfdb::detail {
+
+template <typename FnT>
+auto commit_wrapper(FnT& f, ceph::libfdb::transaction_handle&& txn, const commit_after_op&& commit_after, auto&& ...params)
+{
+ auto r = f(txn, std::forward(params...));
+
+ if(commit_after_op::commit == commit_after) {
+ // Perhaps a tri-state return is the better long-term path?
+ if(false == ceph::libfdb::detail::commit(txn))
+ throw ceph::libfdb::libfdb_exception("transaction commit failed");
+ }
+
+ return r;
+}
+
+} // namespace ceph::libfdb::detail */
namespace ceph::libfdb {
return std::make_shared<transaction>(dbh);
}
-// Note only rarely is a direct call to this requireda.
+// Note only rarely is a direct call to this.
// On false, the client should retry the transaction:
[[nodiscard]] inline bool commit(transaction_handle& txn)
{
namespace ceph::libfdb::detail {
+/* Equivalence with FDBStreamingMode:
+ *
+FDB_STREAMING_MODE_ITERATOR
+
+The caller is implementing an iterator (most likely in a binding to a higher level language). The amount of data returned depends on the value of the iteration parameter to fdb_transaction_get_range().
+
+FDB_STREAMING_MODE_SMALL
+
+Data is returned in small batches (not much more expensive than reading individual key-value pairs).
+
+FDB_STREAMING_MODE_MEDIUM
+
+Data is returned in batches between _SMALL and _LARGE.
+
+FDB_STREAMING_MODE_LARGE
+
+Data is returned in batches large enough to be, in a high-concurrency environment, nearly as efficient as possible. If the caller does not need the entire range, some disk and network bandwidth may be wasted. The batch size may be still be too small to allow a single client to get high throughput from the database.
+
+FDB_STREAMING_MODE_SERIAL
+
+Data is returned in batches large enough that an individual client can get reasonable read bandwidth from the database. If the caller does not need the entire range, considerable disk and network bandwidth may be wasted.
+
+FDB_STREAMING_MODE_WANT_ALL
+
+The caller intends to consume the entire range and would like it all transferred as early as possible.
+
+FDB_STREAMING_MODE_EXACT
+
+The caller has passed a specific row limit and wants that many rows delivered in a single batch.
+
+enum struct streaming_mode_t : int {
+ iterator = FDB_STREAMING_MODE_ITERATOR,
+ small = FDB_STREAMING_MODE_SMALL,
+ medium = FDB_STREAMING_MODE_MEDIUM,
+ large = FDB_STREAMING_MODE_LARGE,
+ serial = FDB_STREAMING_MODE_SERIAL,
+ all = FDB_STREAMING_MODE_WANT_ALL,
+3F exact = FDB_STREAMING_MODE_EXACT
+};
+
+...these are not defined in terms of int or enum as far as I can tell, needs more exploring.
+*/
// The alternatives were "spanlike" or even "Span-ish", but that was a little /too/ cute; thanks
// to Adam Emerson for the coinage:
auto ptr_and_sz(const auto& spanoid)
};
}
-} // namespace ceph::libfdb::detail
+// RAII wrapper:
+struct maybe_commit final
+{
+ transaction_handle& txn;
-namespace ceph::libfdb::detail {
+ commit_after_op commit_after;
-/* JFW:
-// Used iternally to get values from futures:
-struct transaction_value_result
-{
- fdb_bool_t key_was_found = false;
-
- fdb_bool_t is_snapshot = false;
+ maybe_commit(transaction_handle& txn_, const commit_after_op commit_after_)
+ : txn(txn_), commit_after(commit_after_)
+ {}
+
+ ~maybe_commit()
+ {
+ if(std::uncaught_exceptions() || commit_after_op::commit != commit_after) {
+ return;
+ }
+
+ txn->commit();
+ }
+};
+
+} // namespace ceph::libfdb::detail
- // Kept here to extend the future's (and data within it) lifetime:
- future_value fv = nullptr;
+// JFW: these can maybe go away (or move?):
- // JFW: this can be a variant; the data lives only as long as the future_value lives:
- // By the time we have an instance of transaction_value_result(), this must have already
- // been provided by the appropriate FDB function:
- const std::span<const std::uint8_t> out_data;
-};*/
+namespace ceph::libfdb::detail {
// Core dispatch from internal DB value to external concrete value:
-void reify_value(const uint8_t *buffer, const size_t buffer_size, auto& target)
+[[deprecated]] void reify_value(const uint8_t *buffer, const size_t buffer_size, auto& target)
{
return ceph::libfdb::from::convert(std::span { buffer, buffer_size }, target);
}
-// Grab a future and its related result from a request:
-inline bool get_single_value_from_transaction(transaction_handle& txn, const std::span<const std::uint8_t>& key, std::invocable<std::span<const std::uint8_t>> auto& write_output);
+} // namespace ceph::libfdb::detail */
-inline future_value get_range_future_from_transaction(transaction_handle& txn, std::span<const std::uint8_t> begin_key, std::span<const std::uint8_t> end_key);
+namespace ceph::libfdb {
-} // namespace ceph::libfdb::detail
+inline void set(transaction_handle txn, std::string_view k, const auto& v, const commit_after_op commit_after)
+{
+ detail::maybe_commit mc(txn, commit_after);
-namespace ceph::libfdb {
+ return txn->set(detail::as_fdb_span(k), ceph::libfdb::to::convert(v));
+}
-template <typename K, typename V>
-inline void set(transaction_handle txn, const K& k, const V& v, const commit_after_op commit_after)
+inline void set(transaction_handle txn, std::string_view k, const auto& v)
{
- txn->set(detail::as_fdb_span(k), ceph::libfdb::to::convert(v));
-
- if(commit_after_op::no_commit == commit_after) {
- return;
- }
+ return set(txn, k, v, commit_after_op::no_commit);
+}
- txn->commit();
+inline void set(database_handle dbh, std::string_view k, const auto& v)
+{
+ return set(make_transaction(dbh), k, v, commit_after_op::commit);
}
-template <std::input_iterator PairIter>
-inline void set(transaction_handle txn, PairIter b, PairIter e, const commit_after_op commit_after)
+// JFW: this needs lifting on the iterator types:
+inline void set(transaction_handle txn, std::map<std::string, std::string>::const_iterator b, std::map<std::string, std::string>::const_iterator e, const commit_after_op commit_after)
{
+ detail::maybe_commit mc(txn, commit_after);
+
std::for_each(b, e, [&txn](const auto& kv) {
- txn->set(detail::as_fdb_span(kv.first), ceph::libfdb::to::convert(kv.second));
- });
+ txn->set(detail::as_fdb_span(kv.first), ceph::libfdb::to::convert(kv.second));
+ });
+}
- if(commit_after_op::no_commit == commit_after) {
- return;
- }
+// JFW: this is a glaring embarassment in the interface-- ok for now as I'm just trying to get things to
+// work for the prototype, but I've /got/ to find another way to do this.
+inline void set(transaction_handle txn, const char *k, const char *v, ceph::libfdb::commit_after_op commit_after)
+{
+ // Since we've got a static extent key, we need to be sure we convert data appropriately:
+ return txn->set(detail::as_fdb_span(k), detail::as_fdb_span(v));
+}
- txn->commit();
+//JFW: this likely needs disambiguation of iterator vs. const char *
+inline void set(database_handle dbh, auto b, auto e)
+{
+ return set(make_transaction(dbh), b, e, commit_after_op::commit);
}
+} // namespace ceph::libfdb
+
+namespace ceph::libfdb {
+
// erase() is clear() in FDB parlance:
-//
-// "[R]emove all keys (if any) which are lexicographically greater than or equal to the given begin key and
-// lexicographically less than the given end_key."
-//
-// JFW: See note about some current key-type limitations (we will reinvestigate when the prototype is
-// starting to work, there's no hard need for the string type limitation, it's just most straightforward
-// to deal with):
inline void erase(ceph::libfdb::transaction_handle txn, const ceph::libfdb::select& key_range, const commit_after_op commit_after)
{
- txn->erase(key_range);
+ detail::maybe_commit mc(txn, commit_after);
- if(commit_after_op::commit == commit_after) {
- txn->commit();
- }
+ return txn->erase(key_range);
+}
+
+inline void erase(ceph::libfdb::transaction_handle txn, const ceph::libfdb::select& key_range)
+{
+ return erase(txn, key_range, commit_after_op::no_commit);
+}
+
+inline void erase(ceph::libfdb::database_handle dbh, const ceph::libfdb::select& key_range)
+{
+ return erase(ceph::libfdb::make_transaction(dbh), key_range, commit_after_op::commit);
}
inline void erase(ceph::libfdb::transaction_handle txn, std::string_view k, const commit_after_op commit_after)
{
- txn->erase(detail::as_fdb_span(k));
+ detail::maybe_commit mc(txn, commit_after);
- if(commit_after_op::no_commit == commit_after) {
- return;
- }
+ return txn->erase(detail::as_fdb_span(k));
+}
- txn->commit();
+inline void erase(ceph::libfdb::transaction_handle txn, std::string_view k)
+{
+ return erase(txn, k, commit_after_op::commit);
}
-inline bool get(ceph::libfdb::transaction_handle txn, const ceph::libfdb::select& key_range, auto out_iter, const commit_after_op commit_after)
+inline void erase(ceph::libfdb::database_handle dbh, std::string_view k)
{
- auto r = ceph::libfdb::detail::get_value_range_from_transaction(txn, detail::as_fdb_span(key_range.begin_key), detail::as_fdb_span(key_range.end_key), out_iter);
+ return erase(make_transaction(dbh), k);
+}
- if(commit_after_op::commit == commit_after) {
- txn->commit();
- }
+} // namespace ceph::libfdb
- return r;
+namespace ceph::libfdb {
+
+// get() with selector:
+// JFW: Satisfying output_iterator is not as straightforward as it appears, I need to look at this mechanism again:
+inline bool get(ceph::libfdb::transaction_handle txn, const ceph::libfdb::select& key_range, auto out_iter, const ceph::libfdb::commit_after_op commit_after)
+{
+ detail::maybe_commit mc(txn, commit_after);
+
+ return txn->get(detail::as_fdb_span(key_range.begin_key), detail::as_fdb_span(key_range.end_key), out_iter);
}
-inline bool get(ceph::libfdb::transaction_handle txn, const auto& key, auto& out_value, const commit_after_op commit_after)
+inline bool get(ceph::libfdb::transaction_handle txn, const ceph::libfdb::select& key_range, auto out_iter)
{
- auto vc = detail::value_collector(out_value);
+ return get(txn, key_range, out_iter, commit_after_op::no_commit);
+}
- if(false == ceph::libfdb::detail::get_single_value_from_transaction(txn, detail::as_fdb_span(key), vc))
- return false;
+inline bool get(ceph::libfdb::database_handle dbh, const ceph::libfdb::select& key_range, auto out_iter)
+{
+ return get(ceph::libfdb::make_transaction(dbh), key_range, out_iter, ceph::libfdb::commit_after_op::commit);
+}
- if(commit_after_op::commit == commit_after) {
- txn->commit();
- }
+inline bool get(ceph::libfdb::transaction_handle txn, std::string_view key, auto& out_value, const commit_after_op commit_after)
+{
+ detail::maybe_commit mc(txn, commit_after);
+
+ auto vc = detail::value_collector(out_value);
+
+ return txn->get(detail::as_fdb_span(key), vc);
+}
+
+inline bool get(ceph::libfdb::transaction_handle txn, std::string_view key, auto& out_value)
+{
+ return get(txn, key, out_value, commit_after_op::no_commit);
+}
- return true;
+inline bool get(ceph::libfdb::database_handle dbh, std::string_view key, auto& out_value)
+{
+ return get(ceph::libfdb::make_transaction(dbh), key, out_value, commit_after_op::commit);
}
// The user can provide an immediate conversion function (no function_ref until C++26):
-inline bool get(ceph::libfdb::transaction_handle txn, auto& key, std::invocable auto fn, const commit_after_op commit_after)
+// JFW: std::remove_cvref_t
+inline bool get(ceph::libfdb::transaction_handle txn, std::string_view key, auto&& fn, const commit_after_op commit_after)
{
- if(!ceph::libfdb::detail::get_single_value_from_transaction(txn, detail::as_fdb_span(key), fn))
- return false;
+ detail::maybe_commit mc(txn, commit_after);
- if(commit_after_op::commit == commit_after) {
- txn->commit();
- }
+ return txn->get(detail::as_fdb_span(key), fn);
+}
- return true;
+inline bool get(ceph::libfdb::transaction_handle txn, std::string_view key, auto&& fn)
+{
+ return get(txn, key, fn, commit_after_op::commit);
}
-template <typename K>
-inline bool key_exists(transaction_handle txn, K k)
+inline bool get(ceph::libfdb::database_handle dbh, std::string_view key, auto&& fn)
{
- auto bit_bucket = [](auto) {};
+ return get(ceph::libfdb::make_transaction(dbh), key, fn, commit_after_op::commit);
+}
+
+} // namespace ceph::libfdb
+
+namespace ceph::libfdb {
+
+// Does a key exist?
+inline bool key_exists(transaction_handle txn, std::string_view k, const commit_after_op commit_after)
+{
+ detail::maybe_commit mc(txn, commit_after);
- return ceph::libfdb::detail::get_single_value_from_transaction(txn, detail::as_fdb_span(k), bit_bucket);
+ return txn->key_exists(k);
+}
+
+inline bool key_exists(transaction_handle txn, std::string_view k)
+{
+ return key_exists(txn, k, commit_after_op::no_commit);
+}
+
+inline bool key_exists(database_handle dbh, std::string_view k)
+{
+ return key_exists(ceph::libfdb::make_transaction(dbh), k, commit_after_op::commit);
}
} // namespace ceph::libfdb
zpp::bits::out out(out_data);
// zpp::bits won't write a size if we start with a fixed size array:
+ // (see dynamic_extent):
if constexpr (not std::is_array_v<decltype(from)>)
- out(from);
+ out(from).or_throw();
else
out(std::span(from, std::size(from))).or_throw();
return key_counter(lfdb::make_transaction(dbh), selector, _);
}
-
// Note that the generated keys are ONE based, not zero:
inline std::map<std::string, std::string> make_monotonic_kvs(const int N)
{
lfdb::erase(lfdb::make_transaction(dbh), k, lfdb::commit_after_op::commit);
// Now, we shouldn't find anything:
- CHECK_FALSE(lfdb::key_exists(lfdb::make_transaction(dbh), k));
+ CHECK_FALSE(lfdb::key_exists(lfdb::make_transaction(dbh), k, lfdb::commit_after_op::no_commit));
// Write the key:
lfdb::set(lfdb::make_transaction(dbh), k, v, lfdb::commit_after_op::commit);
// ...it should magically be there!
- CHECK(lfdb::key_exists(lfdb::make_transaction(dbh), k));
+ CHECK(lfdb::key_exists(lfdb::make_transaction(dbh), k, lfdb::commit_after_op::no_commit));
// ...and now it should be gone again:
lfdb::erase(lfdb::make_transaction(dbh), k, lfdb::commit_after_op::commit);
- CHECK_FALSE(lfdb::key_exists(lfdb::make_transaction(dbh), k));
+ CHECK_FALSE(lfdb::key_exists(lfdb::make_transaction(dbh), k, lfdb::commit_after_op::no_commit));
}
}
// From the "database" point of view, the structure is now that we have a single
// key pointing (p) to an associative array, e.g. map<p, map<k, v>>:
-
- lfdb::set(lfdb::make_transaction(dbh), "key", kvs, lfdb::commit_after_op::commit);
+lfdb::set(lfdb::make_transaction(dbh), "key", kvs, lfdb::commit_after_op::commit);
std::map<std::string, std::string> out_kvs;
CHECK(pearl_msg == out_kvs["pearl"]);
}
+SCENARIO("implicit transactions", "[fdb][rgw]")
+{
+ // If you pass a dbh or tenant, a transaction should be constructed and
+ //if not otherwise specified handled "appropriately".
+ //
+ //The rules are:
+ // 1) if passed a transaction and NO specific handler, the transaction is used and NOT committed on success;
+ // 2) if passed a handle and a handler, the transaction is used and treated accordingly (potentially dangerous);
+ // 3) if passed a handle and no handler, a transaction is created AND committed on success;
+ //...need to test with range operations, etc..
+
+ janitor j;
+
+ auto dbh = lfdb::make_database();
+
+// A no-no because of zpp_bits-- we can work around it, but...
+// const auto k = "hi", v = "there";
+ std::string_view k = "hi", v = "there";
+
+ CAPTURE(k);
+ CAPTURE(v);
+
+ SECTION("implicitly create and complete transactions") {
+
+ REQUIRE_FALSE(lfdb::key_exists(dbh, k));
+ CHECK_NOTHROW(lfdb::set(dbh, k, v));
+ CHECK(lfdb::key_exists(dbh, k));
+
+ std::string ov;
+ CHECK(lfdb::get(dbh, k, ov));
+
+ CAPTURE(ov);
+
+ REQUIRE(v == ov);
+
+ CHECK_NOTHROW(lfdb::erase(dbh, k));
+ REQUIRE_FALSE(lfdb::key_exists(dbh, k));
+
+ REQUIRE_FALSE(lfdb::get(dbh, k, ov));
+ }
+
+ SECTION("implicitly create and complete transactions-- selection operations") {
+ // With an implicit transaction, transactions should commit by default:
+ const auto selector = lfdb::select { make_key(0), make_key(20) };
+
+ const auto kvs = make_monotonic_kvs(20);
+
+ CHECK_NOTHROW(lfdb::set(dbh, begin(kvs), end(kvs)));
+
+ lfdb::erase(dbh, lfdb::select { make_key(1), make_key(6) });
+ CHECK(15 == key_count(selector));
+
+ CHECK_FALSE(lfdb::key_exists(dbh, "key_03"));
+ }
+
+ SECTION("test behavior with shared transaction") {
+
+ SECTION("write in uncommitted transaction") {
+ // With a shared transaction, transactions should NOT commit on API calls by default:
+ auto txn = lfdb::make_transaction(dbh);
+
+ lfdb::set(txn, "Herman", "Hollerith");
+
+ // Key exists with respect to this transaction:
+ CHECK(lfdb::key_exists(txn, "Herman"));
+
+ lfdb::set(txn, "John", "Backus");
+
+ // Key exists with respect to this transaction:
+ CHECK(lfdb::key_exists(txn, "John"));
+
+ // transaction is abandoned
+ }
+
+ // These were only set in the abandoned transaction:
+ CHECK_FALSE(lfdb::key_exists(dbh, "Herman"));
+ CHECK_FALSE(lfdb::key_exists(dbh, "John"));
+ }
+}
+
TEST_CASE("fdb misc", "[fdb]")
{
SECTION("ptr_and_sz()") {