#pragma once #include #include "common/expect.h" // monero/src #include "lmdb/error.h" // monero/src #include "lmdb/table.h" // monero/src #include "lmdb/util.h" // monero/src #include "wire/msgpack.h" namespace lmdb { //! Helper for grouping typical LMDB DBI options when key is fixed and value has msgpack component template struct msgpack_table : table { using key_type = K; using fixed_value_type = V1; using msgpack_value_type = V2; using value_type = std::pair; constexpr explicit msgpack_table(const char* name, unsigned flags = 0, MDB_cmp_func value_cmp = nullptr) noexcept : table{name, flags, &lmdb::less>, value_cmp} {} static expect get_key(MDB_val key) { if (key.mv_size != sizeof(key_type)) return {lmdb::error(MDB_BAD_VALSIZE)}; key_type out; std::memcpy(std::addressof(out), static_cast(key.mv_data), sizeof(out)); return out; } static expect make_value(const fixed_value_type& val1, const msgpack_value_type& val2) { epee::byte_stream initial; initial.write({reinterpret_cast(std::addressof(val1)), sizeof(val1)}); wire::msgpack_slice_writer dest{std::move(initial), true}; try { wire_write::bytes(dest, val2); } catch (const wire::exception& e) { return {e.code()}; } return epee::byte_slice{dest.take_sink()}; } /*! \tparam U must be same as `V`; used for sanity checking. \tparam F is the type within `U` that is being extracted. \tparam offset to `F` within `U`. \note If using `F` and `offset` to retrieve a specific field, use `MONERO_FIELD` macro in `src/lmdb/util.h` which calculates the offset automatically. \return Value of type `F` at `offset` within `value` which has type `U`. */ template static expect get_fixed_value(MDB_val value) noexcept { static_assert(std::is_same(), "bad MONERO_FIELD?"); static_assert(std::is_pod(), "F must be POD"); static_assert(sizeof(F) + offset <= sizeof(U), "bad field type and/or offset"); if (value.mv_size < sizeof(U)) return {lmdb::error(MDB_BAD_VALSIZE)}; F out; std::memcpy(std::addressof(out), static_cast(value.mv_data) + offset, sizeof(out)); return out; } static expect get_value(MDB_val value) noexcept { if (value.mv_size < sizeof(fixed_value_type)) return {lmdb::error(MDB_BAD_VALSIZE)}; std::pair out; std::memcpy(std::addressof(out.first), static_cast(value.mv_data), sizeof(out.first)); auto msgpack_bytes = lmdb::to_byte_span(value); msgpack_bytes.remove_prefix(sizeof(out.first)); const std::error_code error = wire::msgpack::from_bytes(epee::byte_slice{{msgpack_bytes}}, out.second); if (error) return error; return out; } //! Easier than doing another iterator .. for now :/ static expect>>> get_all(MDB_cursor& cur) { MDB_val key{}; MDB_val value{}; int err = mdb_cursor_get(&cur, &key, &value, MDB_FIRST); std::vector>> out; for ( ; /* for every key */ ; ) { if (err) { if (err != MDB_NOTFOUND) return {lmdb::error(err)}; break; } expect next_key = get_key(key); if (!next_key) return next_key.error(); out.emplace_back(std::move(*next_key), std::vector{}); for ( ; /* for every value at key */ ; ) { if (err) { if (err != MDB_NOTFOUND) return {lmdb::error(err)}; break; } expect next_value = get_value(value); if (!next_value) return next_value.error(); out.back().second.push_back(std::move(*next_value)); err = mdb_cursor_get(&cur, &key, &value, MDB_NEXT_DUP); } err = mdb_cursor_get(&cur, &key, &value, MDB_NEXT_NODUP); } // every key return out; } }; } // lmdb