Compare commits

...

3 Commits

Author SHA1 Message Date
Dmytro Sydorchenko ddcb393200 fixed kv tests 2022-11-21 22:58:12 -05:00
Dmytro Sydorchenko f760a5fecd beta unordered_map with tests 2022-11-18 20:44:09 -05:00
larryk85 a820f587b1 copy of kv map from EOSIO 2022-11-18 20:44:09 -05:00
8 changed files with 1301 additions and 343 deletions
@@ -5,6 +5,7 @@
#pragma once
#include "action.hpp"
#include "../../core/eosio/print.hpp"
#include "map.hpp"
#include "multi_index.hpp"
#include "dispatcher.hpp"
#include "contract.hpp"
+557
View File
@@ -0,0 +1,557 @@
#pragma once
#include "../../core/eosio/context.hpp"
#include "../../core/eosio/datastream.hpp"
#include "../../core/eosio/name.hpp"
#include "../../core/eosio/varint.hpp"
#include "../../core/eosio/key_utils.hpp"
#include <algorithm>
#include <cctype>
#include <functional>
#include <string_view>
namespace eosio::kv {
namespace internal_use_do_not_use {
extern "C" {
__attribute__((eosio_wasm_import))
int64_t kv_erase(uint64_t contract, const char* key, uint32_t key_size);
__attribute__((eosio_wasm_import))
int64_t kv_set(uint64_t contract, const char* key, uint32_t key_size, const char* value, uint32_t value_size, uint64_t payer);
__attribute__((eosio_wasm_import))
bool kv_get(uint64_t contract, const char* key, uint32_t key_size, uint32_t& value_size);
__attribute__((eosio_wasm_import))
uint32_t kv_get_data(uint32_t offset, char* data, uint32_t data_size);
__attribute__((eosio_wasm_import))
uint32_t kv_it_create(uint64_t contract, const char* prefix, uint32_t size);
__attribute__((eosio_wasm_import))
void kv_it_destroy(uint32_t itr);
__attribute__((eosio_wasm_import))
int32_t kv_it_status(uint32_t itr);
__attribute__((eosio_wasm_import))
int32_t kv_it_compare(uint32_t itr_a, uint32_t itr_b);
__attribute__((eosio_wasm_import))
int32_t kv_it_key_compare(uint32_t itr, const char* key, uint32_t size);
__attribute__((eosio_wasm_import))
int32_t kv_it_move_to_end(uint32_t itr);
__attribute__((eosio_wasm_import))
int32_t kv_it_next(uint32_t itr, uint32_t& found_key_size, uint32_t& found_value_size);
__attribute__((eosio_wasm_import))
int32_t kv_it_prev(uint32_t itr, uint32_t& found_key_size, uint32_t& found_value_size);
__attribute__((eosio_wasm_import))
int32_t kv_it_lower_bound(uint32_t itr, const char* key, uint32_t size, uint32_t& found_key_size, uint32_t& found_value_size);
__attribute__((eosio_wasm_import))
int32_t kv_it_key(uint32_t itr, uint32_t offset, char* dest, uint32_t size, uint32_t& actual_size);
__attribute__((eosio_wasm_import))
int32_t kv_it_value(uint32_t itr, uint32_t offset, char* dest, uint32_t size, uint32_t& actual_size);
}
}
// tag used by some of the types to delineate overloads between key_type (std::string) and the map's key type
struct packed_tag {};
namespace detail {
static inline void increment_bytes(key_type& kt) {
// this will terminate without have to lengthen by one byte given the way prefixes are formed
// need to re-evaluate if that changes
for (std::size_t i=kt.size()-1; i >= 0; i--) {
uint16_t v = kt[i]+1;
kt[i] = v;
if (v <= 0xFF)
break;
}
}
struct packed_view {
packed_view() = default;
constexpr inline packed_view(char* p, std::size_t s)
: ptr(p), sz(s) {}
using iterator = char*;
using const_iterator = const char*;
constexpr inline char* data() { return ptr; }
constexpr inline const char* data() const { return ptr; }
constexpr inline std::size_t size() const { return sz; }
constexpr inline const_iterator begin() const { return data(); }
constexpr inline const_iterator end() const { return data() + size(); }
char* ptr;
std::size_t sz;
};
inline uint32_t itr_create(name contract, std::string_view prefix) {
return internal_use_do_not_use::kv_it_create(contract.value, prefix.data(), prefix.size());
}
inline void itr_destroy(uint32_t itr) { internal_use_do_not_use::kv_it_destroy(itr); }
inline int32_t itr_status(uint32_t itr) { return internal_use_do_not_use::kv_it_status(itr); }
inline int32_t itr_compare(uint32_t a, uint32_t b) { return internal_use_do_not_use::kv_it_compare(a, b); }
inline int32_t itr_key_compare(uint32_t itr, std::string_view k) { return internal_use_do_not_use::kv_it_key_compare(itr, k.data(), k.size()); }
inline int32_t itr_move_to_end(uint32_t itr) { return internal_use_do_not_use::kv_it_move_to_end(itr); }
inline int32_t itr_key(uint32_t itr, char* dest, uint32_t size, uint32_t& actual_size) { return internal_use_do_not_use::kv_it_key(itr, 0, dest, size, actual_size); }
inline int32_t itr_value(uint32_t itr, char* dest, uint32_t size, uint32_t& actual_size) { return internal_use_do_not_use::kv_it_value(itr, 0, dest, size, actual_size); }
inline int32_t itr_next(uint32_t itr, uint32_t& key_size, uint32_t& val_size) {
return internal_use_do_not_use::kv_it_next(itr, key_size, val_size);
}
inline int32_t itr_next(uint32_t itr) {
uint32_t k,v;
return internal_use_do_not_use::kv_it_next(itr, k, v);
}
inline int32_t itr_prev(uint32_t itr, uint32_t& key_size, uint32_t& val_size) {
return internal_use_do_not_use::kv_it_prev(itr, key_size, val_size);
}
inline int32_t itr_prev(uint32_t itr) {
uint32_t k,v;
return internal_use_do_not_use::kv_it_prev(itr, k, v);
}
inline int32_t itr_lower_bound(uint32_t itr, std::string_view k, uint32_t& key_size, uint32_t& val_size) {
return internal_use_do_not_use::kv_it_lower_bound(itr, k.data(), k.size(), key_size, val_size);
}
inline int32_t itr_lower_bound(uint32_t itr, std::string_view k) {
uint32_t _k,_v;
return internal_use_do_not_use::kv_it_lower_bound(itr, k.data(), k.size(), _k, _v);
}
inline int32_t itr_lower_bound(uint32_t itr) {
uint32_t _k,_v;
return internal_use_do_not_use::kv_it_lower_bound(itr, "", 0, _k, _v);
}
template <typename KV>
struct elem {
using key_t = typename KV::key_t;
using value_t = typename KV::value_t;
elem() = default;
constexpr inline elem(const key_t& k, value_t v, name p=current_context_contract())
: key(KV::full_key(k)), value(std::move(v)), payer(p) {}
inline elem(key_type k, value_t v, name p, packed_tag)
: key(std::move(k)), value(std::move(v)), payer(p) {}
constexpr inline bool operator==(const elem& e) const {
return std::tie(key, value) == std::tie(e.key, e.value);
}
key_type& first() { return key; }
const key_type& first() const { return key; }
value_t& second() { return value; }
const value_t& second() const { return value; }
key_type key;
value_t value;
name payer;
};
template <bool Reverse, typename KV>
struct iterator {
using elem_t = elem<KV>;
using value_t = typename KV::value_t;
constexpr static inline uint32_t invalidated_iterator = std::numeric_limits<uint32_t>::max();
enum class status { ok = 0, erased = -1, end = -2 };
template <status Stat>
constexpr inline static bool query_status(status stat) {
using namespace internal_use_do_not_use;
return Stat == stat;
}
template <status Stat>
constexpr inline static bool query_status(int32_t stat) {
using namespace internal_use_do_not_use;
return Stat == static_cast<status>(stat);
}
inline iterator(name owner)
: element(),
handle(itr_create(owner, {KV::prefix().data(), KV::prefix().size()})) {}
iterator(const iterator&) = delete;
iterator& operator=(const iterator&) = delete;
inline iterator(iterator&& o)
: element(o.element),
handle(std::exchange(o.handle, invalidated_iterator)),
current_status(o.current_status) {}
inline iterator(iterator<!Reverse, KV>&& o)
: element(o.element),
handle(std::exchange(o.handle, invalidated_iterator)),
current_status(o.current_status) {}
inline iterator& operator=(iterator&& o) {
element = o.element;
if (handle != invalidated_iterator)
itr_destroy(handle);
handle = std::exchange(o.handle, invalidated_iterator);
current_status = o.current_status;
return *this;
}
inline iterator& operator=(iterator<!Reverse, KV>&& o) {
element = o.element;
if (handle != invalidated_iterator)
itr_destroy(handle);
handle = std::exchange(o.handle, invalidated_iterator);
current_status = o.current_status;
return *this;
}
~iterator() {
if (handle != invalidated_iterator)
itr_destroy(handle);
}
inline bool is_valid() const { return query_status<status::ok>(current_status); }
iterator& seek_to_begin() {
current_status = static_cast<status>(itr_lower_bound(handle));
return *this;
}
iterator& seek_to_last() {
current_status = static_cast<status>(itr_move_to_end(handle));
current_status = static_cast<status>(itr_prev(handle));
return *this;
}
iterator& seek_to_end() {
current_status = static_cast<status>(itr_move_to_end(handle));
return *this;
}
iterator& lower_bound(const key_type& k) {
current_status = static_cast<status>(itr_lower_bound(handle, {k.data(), k.size()}));
return *this;
}
iterator& find(const key_type& k) {
lower_bound(k);
if (itr_key_compare(handle, {k.data(), k.size()}) != 0)
seek_to_end();
return *this;
}
inline elem_t& operator*() {
materialize();
return element;
}
inline const elem_t& operator*() const {
materialize();
return element;
}
inline elem_t* operator->() {
materialize();
return &element;
}
inline const elem_t* operator->() const {
materialize();
return &element;
}
iterator& operator++() {
if constexpr (Reverse) {
current_status = static_cast<status>(itr_prev(handle));
check(query_status<status::ok>(current_status), "incrementing past end or an erased iterator");
} else {
check(query_status<status::ok>(current_status), "incrementing past end or an erased iterator");
current_status = static_cast<status>(itr_next(handle));
}
if (query_status<status::end>(current_status))
seek_to_end();
return *this;
}
iterator& operator--() {
if constexpr (Reverse) {
check(query_status<status::ok>(current_status), "decrementing past end or an erased iterator");
current_status = static_cast<status>(itr_next(handle));
} else {
current_status = static_cast<status>(itr_prev(handle));
check(query_status<status::ok>(current_status), "decrementing past end or an erased iterator");
}
if (query_status<status::end>(current_status))
seek_to_end();
return *this;
}
inline bool operator==(const iterator& o) const {
// ignoring key_size and value_size as they shouldn't play a role in equality
return (std::tie(handle, current_status) == std::tie(o.handle, o.current_status)) ||
(query_status<status::end>(current_status) && query_status<status::end>(o.current_status)) ||
!itr_compare(handle, o.handle);
}
inline bool operator!=(const iterator& o) const { return !((*this) == o); }
void materialize() {
using namespace internal_use_do_not_use;
uint32_t sz;
itr_key(handle, nullptr, 0, sz);
element.key.resize(sz);
check(query_status<status::ok>(itr_key(handle, element.key.data(), element.key.size(), sz)), "failure getting key");
itr_value(handle, nullptr, 0, sz);
auto val_bytes = KV::get_tmp_buffer(sz);
auto status = itr_value(handle, val_bytes.data(), val_bytes.size(), sz);
check(query_status<status::ok>(status), "failure getting value");
unpack<value_t>(element.value, val_bytes.data(), val_bytes.size());
}
elem_t element;
uint32_t handle;
status current_status = status::ok;
};
} // namespace eosio::kv::detail
template <eosio::name::raw TableName, typename K, typename V, eosio::name::raw IndexName="map.index"_n>
class [[eosio::table]] map {
public:
constexpr static inline uint8_t magic = 1;
constexpr static inline name table_name = name{static_cast<uint64_t>(TableName)};
using key_t = K;
using value_t = V;
using self_t = map<TableName, K, V>;
static const key_type& prefix() {
static key_type prfx = eosio::detail::const_pack(table_name, index_name, magic);
return prfx;
}
static key_type full_key(const key_t& k) { return prefix() + convert_to_key(k); }
using elem_t = detail::elem<self_t>;
using iterator_t = detail::iterator<false, self_t>;
using reverse_iterator_t = detail::iterator<true, self_t>;
struct writable_wrapper {
writable_wrapper(key_type k, value_t v, name p, name o=current_context_contract())
: element(std::move(k), std::move(v), p, packed_tag{}), owner(o) {}
explicit operator value_t&() { return element.value; }
operator value_t() const { return element.value; }
writable_wrapper& operator=(const value_t& o) {
map{owner}.set(element.key, o, element.payer, packed_tag{});
element.value = o;
return *this;
}
writable_wrapper& operator=(value_t&& o) {
map{owner}.set(element.key, o, element.payer, packed_tag{});
element.value = std::move(o);
return *this;
}
elem_t element;
name owner;
};
inline map(name owner=current_context_contract())
: owner(owner) {}
/**
* Basic constructor of N elements. This will bill the implicit owner of the table by default.
*/
inline map(std::initializer_list<elem_t> l) {
for ( const auto& e : l ) {
set(e.key, e.value, e.payer, packed_tag{});
}
}
/**
* Basic constructor of N elements. This will bill the owner explicitly passed in.
*/
inline map(name owner, std::initializer_list<elem_t> l)
: owner(owner) {
for ( const auto& e : l ) {
set(e.key, e.value, e.payer, packed_tag{});
}
}
writable_wrapper operator[](const std::pair<key_t, name>& key_payer) {
auto v = get(key_payer.first);
if (std::get<0>(v))
return {std::get<1>(v), *std::get<0>(v), key_payer.second, owner};
set(std::get<1>(v), value_t{}, owner, packed_tag{});
return {std::move(std::get<1>(v)), value_t{}, key_payer.second, owner};
}
writable_wrapper operator[](const key_t& k) {
return (*this)[std::pair{k, owner}];
}
template <typename Key>
writable_wrapper at(Key&& k, name payer=current_context_contract()) {
auto v = get(std::forward<Key>(k));
check(std::get<0>(v), "key not found");
return {std::get<1>(v), *std::get<0>(v), payer, owner};
}
inline iterator_t begin() const {
iterator_t it = {owner};
it.seek_to_begin();
return it;
}
inline iterator_t end() const {
iterator_t it = {owner};
it.seek_to_end();
return it;
}
inline reverse_iterator_t rbegin() const {
reverse_iterator_t it = {owner};
it.seek_to_last();
return it;
}
inline reverse_iterator_t rend() const {
return end();
}
inline bool empty() const {
auto it = lower_bound("");
return it == end();
}
iterator_t find(const key_t& k) const {
auto fk = full_key(k);
iterator_t it = {owner};
it.find(fk);
return it;
}
void erase(const key_t& k) {
using namespace internal_use_do_not_use;
auto fk = full_key(k);
kv_erase(owner.value, fk.data(), fk.size());
}
iterator_t erase(const iterator_t& it) {
using namespace internal_use_do_not_use;
auto key = *it.key;
++it;
kv_erase(owner, key.data(), key.size());
return it;
}
inline iterator_t lower_bound(const key_t& k) const {
iterator_t it = {owner};
it.lower_bound(full_key(k));
return it;
}
inline iterator_t upper_bound(const key_t& k) const {
auto fk = full_key(k);
detail::increment_bytes(fk); // add '1' to the last byte, this should get us to the next value up
iterator_t it = {owner};
it.lower_bound(fk);
return it;
}
std::pair<iterator_t, iterator_t> equal_range(const key_t& k) const { return {lower_bound(k), upper_bound(k)}; }
std::vector<elem_t> ranged_slice(const key_t& l, const key_t& h) {
std::vector<elem_t> ret;
for (auto itr = lower_bound(l); itr != lower_bound(h); ++itr) {
ret.emplace_back(std::move(*itr));
}
return ret;
}
inline bool contains(const key_t& k) const {
using namespace internal_use_do_not_use;
uint32_t _vs;
auto fk = full_key(k);
return static_cast<bool>(kv_get(owner.value, fk.data(), fk.size(), _vs));
}
bool raw_write(const key_t& k, std::string_view bytes, name payer=current_context_contract()) const {
using namespace internal_use_do_not_use;
const auto& fk = full_key(k);
auto wrote = kv_set(owner.value, fk.data(), fk.size(), bytes.data(), bytes.size(), payer.value);
return wrote == bytes.size();
}
friend iterator_t;
protected:
constexpr static inline name index_name = name{IndexName};
writable_wrapper at(const key_type& bytes, name payer=current_context_contract()) {
auto v = get(bytes, packed_tag{});
check(std::get<0>(v), "key not found");
return {std::get<1>(v), *std::get<0>(v), payer, owner};
}
std::tuple<value_t*, key_type> get(key_type k, packed_tag) {
using namespace internal_use_do_not_use;
uint32_t sz;
if (!kv_get(owner.value, k.data(), k.size(), sz))
return {nullptr, std::move(k)};
auto val_bytes = get_tmp_buffer(sz);
check(kv_get_data(0, val_bytes.data(), val_bytes.size()) == val_bytes.size(), "kv get internal failure");
temp = unpack<value_t>(val_bytes.data(), val_bytes.size());
return {&temp, std::move(k)};
}
std::tuple<value_t*, key_type> get(const key_t& k) {
return get(full_key(k), packed_tag{});
}
inline bool set(const key_type& k, const value_t& v, name payer, packed_tag) const {
using namespace internal_use_do_not_use;
const auto& packed_value = pack_value(v);
auto wrote = kv_set(owner.value, k.data(), k.size(), packed_value.data(), packed_value.size(), payer.value);
return wrote == packed_value.size();
}
inline bool set(const key_t& k, const value_t& v, name payer) const {
return set(full_key(k), v, payer, packed_tag{});
}
static detail::packed_view get_tmp_buffer(std::size_t size_needed=0) {
constexpr std::size_t max_size = 512;
static char static_data[max_size];
static std::vector<char> dynamic_data = {0};
if (size_needed > max_size) {
if (dynamic_data.size() < size_needed)
dynamic_data.resize(size_needed);
return {dynamic_data.data(), size_needed};
} else {
return {&static_data[0], size_needed};
}
}
template <typename Value>
inline detail::packed_view pack_value(Value&& v) const {
auto pv = get_tmp_buffer(pack_size(v));
datastream<char*> ds(pv.data(), pv.size());
ds << std::forward<Value>(v);
return pv;
}
private:
name owner = current_context_contract();
value_t temp; // used for
};
} // namespace eosio::kv
@@ -0,0 +1,551 @@
#pragma once
#include "../../core/eosio/context.hpp"
#include "../../core/eosio/datastream.hpp"
#include "../../core/eosio/name.hpp"
#include "../../core/eosio/varint.hpp"
#include <cstddef>
#include <limits>
#include <algorithm>
#include <cctype>
#include <functional>
#include <string_view>
#include <unordered_map>
#include <iterator>
namespace eosio {
namespace detail {
template <typename Map>
struct elem {
using key_t = typename Map::key_t;
using value_t = typename Map::value_t;
using hash_key_t = typename Map::hash_key_t;
using hash_t = typename Map::hash_t;
using raw_iterator_t = typename Map::raw_iterator_t;
using record_t = typename Map::record_t;
constexpr static inline raw_iterator_t invalidated_iterator = std::numeric_limits<raw_iterator_t>::max();
elem() = default;
elem(const elem&) = default;
elem(elem&& e) = default;
elem& operator=(elem&&) = default;
elem(key_t k)
: hashed_key(hash_t{}(k)), key(std::move(k)) {}
elem(key_t k, value_t v)
: hashed_key(hash_t{}(k)), key(std::move(k)), value(std::move(v)) {}
elem(const hash_key_t hk, const key_t& k, value_t v)
: key(std::move(k)), hashed_key(hk), value(std::move(v)) {}
elem(raw_iterator_t it, const hash_key_t hk, const key_t& k, value_t v)
: raw_itr(it), key(std::move(k)), hashed_key(hk), value(std::move(v)) {}
void update(record_t&& r) {
key = std::move(r.first);
value = std::move(r.second);
}
inline bool has_iterator() const {
return raw_itr != invalidated_iterator;
}
key_t key;
hash_key_t hashed_key;
raw_iterator_t raw_itr = invalidated_iterator;
value_t value;
};
template <class Alloc>
struct buffer {
buffer(size_t n)
: size(n) {
data = alloc.allocate(size);
}
buffer(buffer&& buf)
: data(buf.data), size(buf.size) {
}
~buffer() {
alloc.deallocate(data, size);
}
Alloc alloc;
char* data;
size_t size;
};
template <typename T>
struct temp_alloc {
constexpr static std::size_t max_size = 512 / sizeof(T);
static T static_data[max_size];
static std::vector<T> dynamic_data;
// maintain std::allocator signature
T* allocate(size_t size_needed) {
if (size_needed > max_size) {
if (dynamic_data.size() < size_needed)
dynamic_data.resize(size_needed);
return dynamic_data.data();
} else {
return &static_data[0];
}
}
inline void deallocate(T*, size_t){}
};
template<typename T>
T temp_alloc<T>::static_data[temp_alloc<T>::max_size];
template<typename T>
std::vector<T> temp_alloc<T>::dynamic_data;
template <class Base>
struct quadric_probing_policy {
using key_t = typename Base::key_t;
using hash_key_t = typename Base::hash_key_t;
using raw_iterator_t = typename Base::raw_iterator_t;
using record_t = typename Base::record_t;
using bucket_t = record_t;
using buffer_t = typename Base::buffer_t;
using elem_t = typename Base::elem_t;
static constexpr inline raw_iterator_t invalidated_iterator = Base::elem_t::invalidated_iterator;
static inline bucket_t element_to_bucket(const elem_t& el) {
return {el.key, el.value};
}
static inline record_t get_record(const bucket_t& b) {
return b;
}
static inline hash_key_t next_hash(uint16_t collision_num, hash_key_t hash_key) {
if (!collision_num)
return hash_key;
auto inc = collision_num * collision_num;
if (hash_key > std::numeric_limits<hash_key_t>::max() - inc) {
return std::numeric_limits<hash_key_t>::max() - hash_key;
};
return hash_key + inc;
}
inline bucket_t extract_bucket(hash_key_t hashed_key, raw_iterator_t& raw_itr, const key_t& k) const {
return extract_bucket(hashed_key, raw_itr);
}
bucket_t extract_bucket(hash_key_t hashed_key, raw_iterator_t& raw_itr) const {
using namespace internal_use_do_not_use;
raw_itr = db_find_i64( owner.value, scope.value, Base::table_name.value, hashed_key );
if( raw_itr >= 0 ) {
return extract_bucket(raw_itr);
}
raw_itr = invalidated_iterator;
return {};
}
bucket_t extract_bucket(raw_iterator_t itr) const {
using namespace internal_use_do_not_use;
check(itr != invalidated_iterator, "invalid iterator passed");
uint32_t size = db_get_i64( itr, nullptr, 0 );
buffer_t buff(size);
auto wrote = db_get_i64( itr, buff.data, buff.size );
check(wrote == buff.size, "kv get internal failure");
return unpack<bucket_t>(buff.data, buff.size);
}
name owner;
name scope;
};
template <class Map>
struct iterator {
using elem_t = typename Map::elem_t;
using record_t = typename Map::record_t;
using bucket_t = typename Map::bucket_t;
using raw_iterator_t = typename Map::raw_iterator_t;
using iterator_category = std::forward_iterator_tag;
using value_type = typename elem_t::value_t;
using difference_type = size_t;
using pointer = record_t*;
using reference = record_t&;
using map_t = Map;
static constexpr inline raw_iterator_t invalidated_iterator = elem_t::invalidated_iterator;
inline iterator() : element(invalidated_iterator, {}, {}, {}) {}
inline iterator(raw_iterator_t itr, const map_t* m)
: element(itr, {}, {}, {}), map(m) {}
inline iterator(elem_t&& el, const map_t* m) : element(std::move(el)), map(m) {}
iterator(const iterator&) = default;
iterator& operator=(const iterator&) = default;
inline iterator(iterator&& o)
: element(o.element) {
}
inline iterator& operator=(iterator&& o) {
return const_cast<iterator&>(const_cast<const iterator&>(*this).operator=(std::forward<iterator&&>(o)));
}
inline const iterator& operator=(iterator&& o) const {
element = std::move(o.element);
map = o.map;
return *this;
}
~iterator() {}
inline elem_t& operator*() {
materialize();
return element;
}
inline const elem_t& operator*() const {
materialize();
return element;
}
inline elem_t* operator->() {
materialize();
return &element;
}
inline const elem_t* operator->() const {
materialize();
return &element;
}
iterator& operator++() const {
assert_valid();
uint64_t next_pk;
auto next_itr = internal_use_do_not_use::db_next_i64( element.raw_itr, &next_pk );
if( next_itr < 0 ) {
element.raw_itr = invalidated_iterator;
}
else {
element.raw_itr = next_itr;
}
return const_cast<iterator&>(*this);
}
inline iterator& operator++(int) const {
auto temp = *this;
++(*this);
return const_cast<iterator&>(temp);
}
inline bool operator==(const iterator& o) const {
return (element.raw_itr == o.element.raw_itr);
}
inline bool operator!=(const iterator& o) const { return !((*this) == o); }
void materialize() const {
assert_valid();
static bucket_t bucket_data;
bucket_data = map->collision_policy.extract_bucket(element.raw_itr);
element.update( map->collision_policy.get_record(bucket_data) );
}
// mutable for const_iterator. we do not consider increment and element change as non const operation
// the only methods where constness play role are dereference and pointer
mutable elem_t element;
mutable const map_t* map;
private:
inline void assert_valid() const {
eosio::check( element.raw_itr != invalidated_iterator, "cannot increment end iterator" );
}
};
template<class Map>
using const_iterator = const iterator<Map>;
} // namespace eosio::detail
template <eosio::name::raw TableName,
typename Key,
typename Value,
class Hash = std::hash<Key>,
template <typename> class CollisionPolicy = detail::quadric_probing_policy,
template <typename> class Allocator = detail::temp_alloc>
class unordered_map {
public:
constexpr static inline name table_name = name{static_cast<uint64_t>(TableName)};
static_assert( table_name && table_name.length() < 13, "multi_index does not support table names with a length greater than 12");
constexpr static uint16_t max_collisions = 255;
using key_t = Key;
using value_t = Value;
using record_t = std::pair<Key, Value>;
using hash_key_t = uint64_t;
using hash_t = Hash;
using raw_iterator_t = int32_t;
using allocator_t = Allocator<char>;
using buffer_t = detail::buffer<allocator_t>;
using self_t = unordered_map<TableName, Key, Value, Hash, CollisionPolicy, Allocator>;
using elem_t = detail::elem<self_t>;
using iterator_t = detail::iterator<self_t>;
using const_iterator_t = detail::const_iterator<self_t>;
using collision_policy_t = CollisionPolicy<self_t>;
using bucket_t = typename collision_policy_t::record_t;
struct writable_wrapper {
using unordered_map_t = self_t;
writable_wrapper(key_t k, value_t v, name p, unordered_map_t& m)
: element(std::move(k), std::move(v)), payer(p), map_ref(m) {}
writable_wrapper(elem_t el, name p, unordered_map_t& m)
: element(std::move(el)), payer(p), map_ref(m) {}
explicit operator value_t&() { return element.value; }
operator value_t() const { return element.value; }
writable_wrapper& operator=(const value_t& o) {
element.value = o;
map_ref.set(element, payer);
return *this;
}
writable_wrapper& operator=(value_t&& o) {
element.value = std::move(o);
map_ref.set(element, payer);
return *this;
}
bool operator == (const writable_wrapper& w) {
return std::tie(element.key, element.value) == std::tie(w.element.key, w.element.value);
}
bool operator == (const value_t& v) {
return element.value == v;
}
elem_t element;
name payer;
unordered_map_t& map_ref;
};
inline unordered_map(name o, name s)
: owner(o), scope(s), collision_policy{o, s} {}
/**
* Basic constructor of N elements.
*/
inline unordered_map(name o, name s, std::initializer_list<elem_t> l)
: owner(o), scope(s), collision_policy{o, s} {
for ( auto e : l ) {
set(e, owner);
}
}
writable_wrapper operator[](const std::pair<key_t, name>& key_payer) {
elem_t el{key_payer.first};
if (!get(el))
set(el, key_payer.second);
return {el, key_payer.second, *this};
}
writable_wrapper operator[](const key_t& k) {
return (*this)[{k, owner}];
}
writable_wrapper operator[](key_t&& k) {
return (*this)[{std::move(k), owner}];
}
writable_wrapper at(key_t&& k, name payer) {
elem_t el{std::forward<key_t>(k)};
check(get(el), "key not found");
return {el, payer, *this};
}
writable_wrapper at(key_t&& k) {
return at(std::forward<key_t&&>(k), owner);
}
writable_wrapper at(const key_t& k, name payer) {
elem_t el{k};
check(get(el), "key not found");
return {el, payer, *this};
}
writable_wrapper at(const key_t& k) {
return at(k, owner);
}
inline iterator_t begin() const {
return lower_bound_internal(std::numeric_limits<hash_key_t>::lowest());
}
inline const_iterator_t cbegin() const {
return begin();
}
inline iterator_t end() const {
return {};
}
inline const_iterator_t cend() const {
return end();
}
inline bool empty() const {
return cbegin() == cend();
}
inline size_t size() const {
return std::distance(begin(), end());
}
inline void clear() {
erase(begin(), end());
}
inline std::pair<iterator_t,bool> insert( record_t&& rec ) {
elem_t el{rec.key, rec.value};
bool updated = get(el);
set(el);
return {{el, this}, !updated};
}
template< class InputIt >
void insert( InputIt begin, InputIt end ) {
while (begin != end) {
elem_t el{begin.first, end.second};
get(el);// we need this in case of collisions
set(el);
++begin;
}
}
inline void insert( std::initializer_list<record_t> ilist ) {
insert( ilist.begin(), ilist.end() );
}
iterator_t find(const key_t& k) const {
elem_t el{k, owner};
return get(el) ? iterator_t{el, this} : end();
}
void erase(const key_t& k) {
using namespace internal_use_do_not_use;
elem_t el{k};
check(get(el), "no such key exist");
db_remove_i64(el.raw_itr);
}
iterator_t erase(const const_iterator_t& it) {
using namespace internal_use_do_not_use;
auto raw_itr = (*it).raw_itr;
++it;
db_remove_i64(raw_itr);
return const_cast<iterator_t&>(it);
}
inline iterator_t erase(iterator_t& it) {
return erase(it);
}
iterator_t erase(const const_iterator_t& begin, const const_iterator_t& end) {
while (begin != end) {
begin = erase(begin);
}
return const_cast<iterator_t&>(end);
}
inline iterator_t erase(iterator_t& begin, iterator_t& end) {
return erase(begin, end);
}
inline iterator_t lower_bound(const key_t& k) const {
return lower_bound_internal(hash_t{}(k));
}
protected:
// no need to expose method that takes raw hash
iterator_t lower_bound_internal(const hash_key_t& hk) const {
using namespace internal_use_do_not_use;
auto itr = db_lowerbound_i64( owner.value, scope.value, table_name.value, hk );
if( itr < 0 ) return end();
return {{itr, {}, {}, {}}, this};
}
public:
iterator_t upper_bound(const key_t& k) const {
//we need manually increment hash
auto hashed_key = hash_t{}(k);
auto next_hash = collision_policy.next_hash(1, hashed_key);
if (hashed_key > next_hash)
return end();
return lower_bound_internal( next_hash );
}
inline std::pair<iterator_t, iterator_t> equal_range(const key_t& k) {
return {lower_bound(k), upper_bound(k)};
}
inline std::pair<const_iterator_t, const_iterator_t> equal_range(const key_t& k) const {
return equal_range(k);
}
inline bool contains(const key_t& k) const {
elem_t el{k};
return get(el);
}
friend iterator_t;
protected:
bool get(elem_t& element) const {
uint16_t cur_collision = 0;
static bucket_t temp_bucket;
static record_t temp_record;
do {
//intential post increment to have zero for first call. in that case next_hash supposed to return the key unchanged
element.hashed_key = collision_policy.next_hash(cur_collision++, element.hashed_key);
temp_bucket = collision_policy.extract_bucket(element.hashed_key, element.raw_itr, element.key);
temp_record = collision_policy.get_record(temp_bucket);
// loop if collision
} while ( temp_record != record_t{} &&
temp_record.first != element.key &&
cur_collision < max_collisions );
bool found = temp_record != record_t{};
if (found)
element.value = std::move(temp_record.second);
return found;
}
inline void set(elem_t& el, name payer) const {
using namespace internal_use_do_not_use;
auto temp_el = el;
static bucket_t temp_bucket;
temp_bucket = collision_policy.element_to_bucket(el);
if (!el.has_iterator() && !get(el)) {
buffer_t buffer = pack_value(temp_bucket);
el.raw_itr = db_store_i64( scope.value, table_name.value, payer.value, el.hashed_key, buffer.data, buffer.size );
check(el.has_iterator(), "error inserting element");
}
else {
check(el.has_iterator(), "element can't be updated as it was not found");
buffer_t buffer = pack_value(temp_bucket);
db_update_i64( el.raw_itr, payer.value, buffer.data, buffer.size );
}
}
template <typename T>
inline buffer_t pack_value(T&& v) const {
buffer_t buffer(pack_size(v));
datastream<char*> ds(buffer.data, buffer.size);
ds << std::forward<T>(v);
return std::move(buffer);
}
private:
name owner;
name scope;
allocator_t alloc;
collision_policy_t collision_policy;
friend detail::iterator<self_t>;
};
} // namespace eosio
-343
View File
@@ -1,343 +0,0 @@
#pragma once
#include <deque>
#include <list>
#include <map>
#include <optional>
#include <set>
#include <tuple>
#include <utility>
#include <variant>
#include <vector>
#include <string>
#include <cstring>
#include "datastream.hpp"
#include "reflect.hpp"
namespace eosio {
namespace detail {
template<typename T>
constexpr bool has_bitwise_serialization() {
if constexpr (std::is_arithmetic_v<T>) {
return true;
} else if constexpr (std::is_enum_v<T>) {
static_assert(!std::is_convertible_v<T, std::underlying_type_t<T>>, "Serializing unscoped enum");
return true;
} else {
return false;
}
}
template <template <typename> class C, typename T>
constexpr bool is_ranged_type(C<T>) {
using type = std::decay_t<C<T>>;
return
std::is_same_v<std::vector<T>, type> ||
std::is_same_v<std::list<T>, type> ||
std::is_same_v<std::deque<T>, type> ||
std::is_same_v<std::set<T>, type>;
}
template <typename R, typename C>
auto member_pointer_type(R (C::*)) -> R;
template <typename R, typename C>
auto member_pointer_class(R (C::*)) -> C;
template <typename... Args>
constexpr inline std::size_t total_bytes_size() { return (sizeof(Args) + ...); }
// TODO rework the to_key and datastream logic to be constexpr/consteval friendly to get rid of this
template <std::size_t I, typename Arg, typename... Args>
inline void const_pack_helper(std::string& s, Arg&& arg, Args&&... args) {
std::memcpy(s.data()+I, &arg, sizeof(Arg));
if constexpr (std::is_integral_v<std::decay_t<Arg>> ||
std::is_same_v<std::decay_t<Arg>, eosio::name>) {
std::reverse(s.data()+I, s.data()+I+sizeof(Arg));
}
if constexpr (sizeof...(Args) > 0) {
return const_pack_helper<I+sizeof(Arg)>(s, std::forward<Args>(args)...);
}
}
// TODO rework the to_key and datastream logic to be constexpr/consteval friendly to get rid of this
template <typename... Args>
inline std::string const_pack(Args&&... args) {
std::string s;
s.resize(total_bytes_size<Args...>());
const_pack_helper<0>(s, std::forward<Args>(args)...);
return s;
}
} // namespace eosio::detail
using key_type = std::string;
// to_key defines a conversion from a type to a sequence of bytes whose lexicograpical
// ordering is the same as the ordering of the original type.
//
// For any two objects of type T, a and b:
//
// - key(a) < key(b) iff a < b
// - key(a) is not a prefix of key(b)
//
// Overloads of to_key for user-defined types can be found by Koenig (ADL) lookup.
//
// to_key is specialized for the following types
// - std::string and std::string_view
// - std::vector, std::list, std::deque
// - std::tuple
// - std::array
// - std::optional
// - std::variant
// - Arithmetic types
// - Scoped enumeration types
// - Reflected structs
// - All smart-contract related types defined by abieos
template <typename T, typename S>
void to_key(const T& obj, datastream<S>& stream);
template <int I, typename T, typename S>
void to_key_tuple(const T& obj, datastream<S>& stream) {
if constexpr (I < std::tuple_size_v<T>) {
to_key(std::get<I>(obj), stream);
to_key_tuple<I + 1>(obj, stream);
}
}
template <typename... Ts, typename S>
void to_key(const std::tuple<Ts...>& obj, datastream<S>& stream) {
to_key_tuple<0>(obj, stream);
}
template <typename T, std::size_t N, typename S>
void to_key(const std::array<T, N>& obj, datastream<S>& stream) {
for (const T& elem : obj) { to_key(elem, stream); }
}
template <typename T, typename S>
void to_key_optional(const bool* obj, datastream<S>& stream) {
if (obj == nullptr)
stream.write('\0');
else if (!*obj)
stream.write('\1');
else {
stream.write('\2');
}
}
template <typename T, typename S>
void to_key_optional(const T* obj, datastream<S>& stream) {
if constexpr (detail::has_bitwise_serialization<T>() && sizeof(T) == 1) {
if (obj == nullptr)
stream.write("\0", 2);
else {
char buf[1];
datastream<char*> tmp_stream(buf, 1);
to_key(*obj, tmp_stream);
stream.write(buf[0]);
if (buf[0] == '\0')
stream.write('\1');
}
} else {
if (obj) {
stream.write('\1');
to_key(*obj, stream);
} else {
stream.write('\0');
}
}
}
template <typename T, typename U, typename S>
void to_key(const std::pair<T, U>& obj, datastream<S>& stream) {
to_key(obj.first, stream);
to_key(obj.second, stream);
}
template <typename T, typename S>
void to_key_range(const T& obj, datastream<S>& stream) {
for (const auto& elem : obj) { to_key_optional(&elem, stream); }
to_key_optional((std::add_pointer_t<decltype(*std::begin(obj))>) nullptr, stream);
}
template <typename T, typename S>
auto to_key(const T& obj, datastream<S>& stream) -> std::enable_if_t<is_ranged_type(std::declval<T>()), void> {
to_key_range(obj, stream);
}
template <typename T, typename U, typename S>
void to_key(const std::map<T, U>& obj, datastream<S>& stream) {
to_key_range(obj, stream);
}
template <typename T, typename S>
void to_key(const std::optional<T>& obj, datastream<S>& stream) {
to_key_optional(obj ? &*obj : nullptr, stream);
}
// The first byte holds:
// 0-4 1's (number of additional bytes) 0 (terminator) bits
//
// The number is represented as big-endian using the low order
// bits of the first byte and all of the remaining bytes.
//
// Notes:
// - values must be encoded using the minimum number of bytes,
// as non-canonical representations will break the sort order.
template <typename S>
void to_key_varuint32(std::uint32_t obj, datastream<S>& stream) {
int num_bytes;
if (obj < 0x80u) {
num_bytes = 1;
} else if (obj < 0x4000u) {
num_bytes = 2;
} else if (obj < 0x200000u) {
num_bytes = 3;
} else if (obj < 0x10000000u) {
num_bytes = 4;
} else {
num_bytes = 5;
}
stream.write(
static_cast<char>(~(0xFFu >> (num_bytes - 1)) | (num_bytes == 5 ? 0 : (obj >> ((num_bytes - 1) * 8)))));
for (int i = num_bytes - 2; i >= 0; --i) { stream.write(static_cast<char>((obj >> i * 8) & 0xFFu)); }
}
// for non-negative values
// The first byte holds:
// 1 (signbit) 0-4 1's (number of additional bytes) 0 (terminator) bits
// The value is represented as big endian
// for negative values
// The first byte holds:
// 0 (signbit) 0-4 0's (number of additional bytes) 1 (terminator) bits
// The value is adjusted to be positive based on the range that can
// be represented with this number of bytes and then encoded as big endian.
//
// Notes:
// - negative values must sort before positive values
// - For negative value, numbers that need more bytes are smaller, hence
// the encoding of the width must be opposite the encoding used for
// non-negative values.
// - A 5-byte varint can represent values in $[-2^34, 2^34)$. In this case,
// the argument will be sign-extended.
template <typename S>
void to_key_varint32(std::int32_t obj, datastream<S>& stream) {
static_assert(std::is_same_v<S, void>, "to_key for varint32 has been temporarily disabled");
int num_bytes;
bool sign = (obj < 0);
if (obj < 0x40 && obj >= -0x40) {
num_bytes = 1;
} else if (obj < 0x2000 && obj >= -0x2000) {
num_bytes = 2;
} else if (obj < 0x100000 && obj >= -0x100000) {
num_bytes = 3;
} else if (obj < 0x08000000 && obj >= -0x08000000) {
num_bytes = 4;
} else {
num_bytes = 5;
}
unsigned char width_field;
if (sign) {
width_field = 0x80u >> num_bytes;
} else {
width_field = 0x80u | ~(0xFFu >> num_bytes);
}
auto uobj = static_cast<std::uint32_t>(obj);
unsigned char value_mask = (0xFFu >> (num_bytes + 1));
unsigned char high_byte = (num_bytes == 5 ? (sign ? 0xFF : 0) : (uobj >> ((num_bytes - 1) * 8)));
stream.write(width_field | (high_byte & value_mask));
for (int i = num_bytes - 2; i >= 0; --i) { stream.write(static_cast<char>((uobj >> i * 8) & 0xFFu)); }
}
template <typename... Ts, typename S>
void to_key(const std::variant<Ts...>& obj, datastream<S>& stream) {
to_key_varuint32(static_cast<uint32_t>(obj.index()), stream);
std::visit([&](const auto& item) { to_key(item, stream); }, obj);
}
template <std::size_t N, typename S>
void to_key(const char (&str)[N], datastream<S>& stream) {
to_key(std::string_view{str, N-1}, stream);
}
template <typename S>
void to_key(std::string_view obj, datastream<S>& stream) {
for (char ch : obj) {
stream.write(ch);
if (ch == '\0') {
stream.write('\1');
}
}
stream.write("\0", 2);
}
template <typename S>
void to_key(const std::string& obj, datastream<S>& stream) {
to_key(std::string_view(obj), stream);
}
template <typename S>
void to_key(bool obj, datastream<S>& stream) {
stream.write(static_cast<char>(obj ? 1 : 0));
}
template <typename UInt, typename T>
UInt float_to_key(T value) {
static_assert(sizeof(T) == sizeof(UInt), "Expected unsigned int of the same size");
UInt result;
std::memcpy(&result, &value, sizeof(T));
UInt signbit = (static_cast<UInt>(1) << (std::numeric_limits<UInt>::digits - 1));
UInt mask = 0;
if (result == signbit)
result = 0;
if (result & signbit)
mask = ~mask;
return result ^ (mask | signbit);
}
template <typename T, typename S>
void to_key(const T& obj, datastream<S>& stream) {
if constexpr (std::is_floating_point_v<T>) {
if constexpr (sizeof(T) == 4) {
to_key(float_to_key<uint32_t>(obj), stream);
} else {
static_assert(sizeof(T) == 8, "Unknown floating point type");
to_key(float_to_key<uint64_t>(obj), stream);
}
} else if constexpr (std::is_integral_v<T>) {
auto v = static_cast<std::make_unsigned_t<T>>(obj);
v -= static_cast<std::make_unsigned_t<T>>(std::numeric_limits<T>::min());
std::reverse(reinterpret_cast<char*>(&v), reinterpret_cast<char*>(&v + 1));
stream.write(&v, sizeof(v));
} else if constexpr (std::is_enum_v<T>) {
static_assert(!std::is_convertible_v<T, std::underlying_type_t<T>>, "Serializing unscoped enum");
to_key(static_cast<std::underlying_type_t<T>>(obj), stream);
} else {
using ts_meta = bluegrass::meta::meta_object<T>;
ts_meta::for_each_field(obj, [&](const auto& member) {
to_key(member, stream);
});
}
}
template <typename T>
void convert_to_key(const T& t, key_type& bin) {
datastream<size_t> ss;
to_key(t, ss);
auto orig_size = bin.size();
bin.resize(orig_size + ss.tellp());
datastream<char*> fbs(bin.data() + orig_size, ss.tellp());
to_key(t, fbs);
check( fbs.valid(), "Stream overrun" );
}
template <typename T>
key_type convert_to_key(const T& t) {
key_type result;
convert_to_key(t, result);
return result;
}
} // namespace eosio
+3
View File
@@ -22,5 +22,8 @@ namespace eosio::testing {
static std::vector<uint8_t> crypto_primitives_test_wasm() { return read_wasm("${CMAKE_BINARY_DIR}/../unit/test_contracts/crypto_primitives_tests.wasm"); }
static std::vector<char> crypto_primitives_test_abi() { return read_abi("${CMAKE_BINARY_DIR}/../unit/test_contracts/crypto_primitives_tests.abi"); }
static std::vector<uint8_t> kv_map_tests_wasm() { return read_wasm("${CMAKE_BINARY_DIR}/../unit/test_contracts/kv_map_tests.wasm"); }
static std::vector<char> kv_map_tests_abi() { return read_abi("${CMAKE_BINARY_DIR}/../unit/test_contracts/kv_map_tests.abi"); }
};
} //ns eosio::testing
+50
View File
@@ -0,0 +1,50 @@
#include <boost/test/data/test_case.hpp>
#include <boost/test/unit_test.hpp>
#include <eosio/chain/abi_serializer.hpp>
#include <eosio/testing/tester.hpp>
//#include <fc/variant_object.hpp>
#include <contracts.hpp>
using namespace eosio;
using namespace eosio::testing;
struct kv_tester {
kv_tester(std::vector<uint8_t> wasm, std::vector<char> abi) {
chain.create_accounts({"kvtest"_n});
chain.produce_block();
chain.set_code("kvtest"_n, wasm);
chain.set_abi("kvtest"_n, abi.data());
chain.produce_blocks();
}
void push_action(name act, std::string exception_msg="") {
if (exception_msg.empty()) {
chain.push_action("kvtest"_n, act, "kvtest"_n, {});
} else {
BOOST_CHECK_EXCEPTION(chain.push_action("kvtest"_n, act, "kvtest"_n, {}),
eosio_assert_message_exception,
eosio_assert_message_is(exception_msg));
}
}
tester chain;
};
BOOST_AUTO_TEST_SUITE(key_value_tests)
BOOST_AUTO_TEST_CASE(map_tests) try {
kv_tester t = {contracts::kv_map_tests_wasm(), contracts::kv_map_tests_abi()};
t.push_action("test"_n);
t.push_action("iter"_n);
t.push_action("erase"_n);
t.push_action("eraseexcp"_n, "key not found");
t.push_action("bounds"_n);
t.push_action("ranges"_n);
t.push_action("empty"_n);
} FC_LOG_AND_RETHROW()
BOOST_AUTO_TEST_SUITE_END()
+1
View File
@@ -7,6 +7,7 @@ add_contract(explicit_nested_tests explicit_nested_tests explicit_nested_tests.c
add_contract(transfer_contract transfer_contract transfer.cpp)
add_contract(minimal_tests minimal_tests minimal_tests.cpp)
add_contract(crypto_primitives_tests crypto_primitives_tests crypto_primitives_tests.cpp)
add_contract(kv_map_tests kv_map_tests kv_map_tests.cpp)
add_contract(capi_tests capi_tests capi/capi.c capi/action.c capi/chain.c capi/crypto.c capi/db.c capi/permission.c
capi/print.c capi/privileged.c capi/system.c capi/transaction.c)
+138
View File
@@ -0,0 +1,138 @@
#include <eosio/eosio.hpp>
#include <eosio/unordered_map.hpp>
struct test_record {
int pk;
float s;
std::string n;
};
class [[eosio::contract]] kv_map_tests : public eosio::contract {
public:
using contract::contract;
using testmap_t = eosio::unordered_map<"testmap"_n, int, float>;
using testmap2_t = eosio::unordered_map<"testmap2"_n, std::string, std::string>;
using testmap3_t = eosio::unordered_map<"testmap3"_n, int, float>;
[[eosio::action]]
void test() {
testmap_t t = { "kvtest"_n, "kvtest"_n, {{33, 23.4f}, {10, 13.44f}, {103, 334.3f}} };
auto p = t[33];
p = 102.23; // note here this will update the held value and do a db set
testmap_t t2{"kvtest"_n, "kvtest"_n};
eosio::check(p == 102.23f, "should be the same value");
eosio::check(p == t.at(33), "should be the same value");
auto it = t.begin();
auto& el = *it;
eosio::check(el.value == 13.44f, "should still be the same from before");
testmap2_t t3 = { "kvtest"_n, "kvtest"_n, {{"eosio", "fast"}, {"bit...", "hmm"}} };
auto it2 = t3.begin();
auto& el2 = *it2;
++it2;
++it2;
auto it3 = std::move(it2);
it2 = t3.end();
eosio::check(it2 == it3, "they should be at the end and pointing to the same thing");
eosio::check(it2 == t3.end(), "iterator should be at end");
eosio::check(it3 == t3.end(), "iterator should be at end");
}
[[eosio::action]]
void iter() {
testmap_t t = {"kvtest"_n, "kvtest"_n, {{34, 23.4f}, {11, 13.44f}, {104, 334.3f}, {5, 33.42f}} };
//called after test() and contains more data
std::map<int, float> test_vals = {{34, 23.4f}, {11, 13.44f}, {104, 334.3f}, {5, 33.42f}, {33, 102.23f}, {10, 13.44f}, {103, 334.3f}};
int i = 0;
// test that this will work with auto ranged for loop
for ( const auto& e : t ) {
++i;
eosio::check(test_vals.find(e.key)->second == e.value, "invalid value in iter test");
}
eosio::check(t.size() == 7, "size is wrong");
eosio::check(i == 7, "range based loop iterates less elements than there are in container");
}
[[eosio::action]]
void erase() {
testmap_t t("kvtest"_n, "kvtest"_n);
t.contains(34);
t.erase(34);
eosio::check(!t.contains(34), "should have erased a value");
}
[[eosio::action]]
void eraseexcp() {
testmap_t t("kvtest"_n, "kvtest"_n);
t.at(34); // this should cause an assertion
}
[[eosio::action]]
void bounds() {
testmap3_t t = {"kvtest"_n, "kvtest"_n, {{33, 10}, {10, 41.2f}, {11, 100.100f}, {2, 17.42f}}};
auto it = t.lower_bound(11);
eosio::check(it->key == 11, "should be pointing to 11");
it = t.lower_bound(31);
eosio::check(it->key == 33, "should be pointing to 33");
it = t.lower_bound(36);
eosio::check(it == t.end(), "should be pointing to end");
auto it2 = t.lower_bound(1);
eosio::check(it2->key == 2, "should be pointing to 2");
it = t.upper_bound(10);
eosio::check(it->key == 11, "should be pointing to 11");
it = t.upper_bound(33);
eosio::check(it == t.end(), "should be pointing to end");
}
[[eosio::action]]
void ranges() {
testmap3_t t = {"kvtest"_n, "kvtest"_n, {{17, 9.9f}}};
auto range = t.equal_range(16);
eosio::check(range.first->key == 17, "should be pointing to 17");
eosio::check(range.second->key == 17, "should be pointing to 17");
range = t.equal_range(1);
eosio::check(range.first->key == 2, "should be pointing to 2");
eosio::check(range.second->key == 2, "should be pointing to 2");
}
[[eosio::action]]
void empty() {
testmap_t t("kvtest"_n, "kvtest"_n);
eosio::check(!t.empty(), "should be not empty");
t.erase(t.begin(), t.end());
eosio::check(t.empty(), "should be empty");
}
};