/********************************************************************************
* *
* This file is part of IfcOpenShell. *
* *
* IfcOpenShell is free software: you can redistribute it and/or modify *
* it under the terms of the Lesser GNU General Public License as published by *
* the Free Software Foundation, either version 3.0 of the License, or *
* (at your option) any later version. *
* *
* IfcOpenShell is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
* Lesser GNU General Public License for more details. *
* *
* You should have received a copy of the Lesser GNU General Public License *
* along with this program. If not, see . *
* *
********************************************************************************/
#ifndef ROCKSDB_MAP_ADAPTER_H
#define ROCKSDB_MAP_ADAPTER_H
#ifdef WITH_ROCKSDB
#include
#include
#endif
#include
#include
#include
#include
#include
template
struct is_std_tuple : std::false_type {};
template
struct is_std_tuple> : std::true_type {};
// Serialization and deserialization primitives
template
struct DefaultCodec;
// @todo specialize for integral types in one go
// Specialization for size_t.
template <>
struct DefaultCodec {
std::string encode(const size_t& v) const {
std::string s(sizeof(v), 0);
memcpy(s.data(), &v, sizeof(v));
return s;
}
size_t decode(const std::string& s) const {
size_t v = 0;
// @todo take min of sizeof(v), len(s)
// @todo unify all serialization primitives
memcpy(&v, s.data(), sizeof(v));
return v;
}
};
// Specialization for uint32_t.
template <>
struct DefaultCodec {
std::string encode(const uint32_t& v) const {
std::string s(sizeof(v), 0);
memcpy(s.data(), &v, sizeof(v));
return s;
}
uint32_t decode(const std::string& s) const {
uint32_t v = 0;
// @todo take min of sizeof(v), len(s)
// @todo unify all serialization primitives
memcpy(&v, s.data(), sizeof(v));
return v;
}
};
// Specialization for std::vector.
template <>
struct DefaultCodec> {
std::string encode(const std::vector& vs) const {
std::string s(sizeof(uint32_t) * vs.size(), 0);
memcpy(s.data(), vs.data(), s.size());
return s;
}
std::vector decode(const std::string& s) const {
std::vector vs(s.size() / sizeof(uint32_t), 0);
memcpy(vs.data(), s.data(), s.size());
return vs;
}
};
// Specialization for std::string (identity)
template <>
struct DefaultCodec {
std::string encode(const std::string& v) const {
return v;
}
std::string decode(const std::string& s) const {
return s;
}
};
template
std::string key_to_string(const KeyT& key) {
if constexpr (std::is_same_v) {
return key;
} else {
return std::to_string(key);
}
}
// Convert from a string to a key. For non-string types, we assume numeric keys.
template ::value, int>::type = 0>
KeyT key_from_string(const std::string& s) {
// @todo tuples
if constexpr (std::is_same_v) {
return s;
} else if constexpr (std::is_integral_v) {
return static_cast(std::stoll(s));
} else {
static_assert(sizeof(KeyT) == 0, "key_from_string not implemented for this type");
}
}
template
std::string tuple_to_string_impl(const Tuple& t, std::index_sequence) {
std::ostringstream oss;
// Unpack the tuple; add a pipe before each element except the first.
((oss << (Is == 0 ? "" : "|") << std::to_string(std::get(t))), ...);
return oss.str();
}
template
std::string key_to_string(const std::tuple& key) {
return tuple_to_string_impl(key, std::index_sequence_for{});
}
// Helper: Convert a string token to the desired numeric type.
template
T convert_string(const std::string& token) {
if constexpr (std::is_integral_v) {
return static_cast(std::stoll(token));
} else if constexpr (std::is_floating_point_v) {
return static_cast(std::stod(token));
} else {
static_assert(sizeof(T) == 0, "convert_string not implemented for this type");
}
}
// Helper: Build a tuple from a vector of string tokens.
template
TupleT tuple_from_string_impl(const std::vector& tokens, std::index_sequence) {
return std::make_tuple(convert_string>(tokens[Is])...);
}
template ::value, int>::type = 0>
TupleT key_from_string(const std::string& s) {
std::vector tokens;
std::istringstream iss(s);
std::string token;
while (std::getline(iss, token, '|')) {
tokens.push_back(token);
}
if (tokens.size() != std::tuple_size::value) {
throw std::runtime_error("Invalid tuple format");
}
return tuple_from_string_impl(tokens, std::make_index_sequence::value>{});
}
// rocksdb_map_adapter: a std::map-like interface on a RocksDB keyspace with a given prefix.
// The mapped_type is templated and encoded/decoded via Codec.
template >
class rocksdb_map_adapter {
public:
using key_type = KeyT;
using mapped_type = MappedT;
using value_type = std::pair;
private:
rocksdb::DB* db_;
std::string prefix_;
Codec codec_;
public:
rocksdb_map_adapter(rocksdb::DB* db, const std::string& prefix)
: db_(db), prefix_(prefix), codec_(Codec{}) {}
class iterator {
public:
using value_type = std::pair;
using difference_type = std::ptrdiff_t;
using iterator_category = std::forward_iterator_tag;
using pointer = value_type*;
using reference = value_type&;
private:
rocksdb::DB* db_;
std::string prefix_;
Codec codec_;
// When it_ is nullptr, this iterator is at end.
std::unique_ptr it_;
mutable value_type cached_value_;
void check_valid() {
#ifdef WITH_ROCKSDB
if (!it_ || !it_->Valid() || !it_->key().starts_with(prefix_)) {
it_.reset();
}
#endif
}
public:
iterator() : db_(nullptr), prefix_(), codec_(Codec{}), it_(nullptr) {}
iterator(rocksdb::DB* db, const std::string& prefix,
std::unique_ptr iter, Codec codec = Codec{})
: db_(db), prefix_(prefix), codec_(codec), it_(std::move(iter))
{
check_valid();
}
iterator(const iterator& other)
: db_(other.db_), prefix_(other.prefix_), codec_(other.codec_)
{
#ifdef WITH_ROCKSDB
if (other.it_) {
std::string curr = other.it_->key().ToString();
it_.reset(db_->NewIterator(rocksdb::ReadOptions{}));
it_->Seek(curr);
if (!it_->Valid() || it_->key().ToString() != curr)
it_.reset();
}
#endif
}
iterator& operator=(const iterator& other) {
#ifdef WITH_ROCKSDB
if (this != &other) {
db_ = other.db_;
prefix_ = other.prefix_;
codec_ = other.codec_;
if (other.it_) {
std::string curr = other.it_->key().ToString();
it_.reset(db_->NewIterator(rocksdb::ReadOptions{}));
it_->Seek(curr);
if (!it_->Valid() || it_->key().ToString() != curr)
it_.reset();
} else {
it_.reset();
}
}
#endif
return *this;
}
value_type operator*() const {
#ifdef WITH_ROCKSDB
std::string full_key = it_->key().ToString();
std::string key_without_prefix = full_key.substr(prefix_.size());
std::string value_str = it_->value().ToString();
return { key_from_string(key_without_prefix), codec_.decode(value_str) };
#else
return cached_value_;
#endif
}
// operator-> uses a mutable cache to return a pointer to the current value.
value_type* operator->() const {
cached_value_ = **this;
return &cached_value_;
}
iterator& operator++() {
#if WITH_ROCKSDB
if (it_) {
it_->Next();
check_valid();
}
#endif
return *this;
}
iterator operator++(int) {
iterator tmp(*this);
++(*this);
return tmp;
}
bool operator==(const iterator& other) const {
#ifdef WITH_ROCKSDB
if (!it_ && !other.it_) return true;
if (it_ && other.it_)
return it_->key().ToString() == other.it_->key().ToString();
#endif
return false;
}
bool operator!=(const iterator& other) const {
return !(*this == other);
}
};
iterator begin() const {
#ifdef WITH_ROCKSDB
auto iter = std::unique_ptr(db_->NewIterator(rocksdb::ReadOptions{}));
iter->Seek(prefix_);
if (iter->Valid() && iter->key().starts_with(prefix_)) {
return iterator(db_, prefix_, std::move(iter), codec_);
}
#endif
return end();
}
iterator end() const {
return iterator();
}
iterator find(const key_type& key) const {
#ifdef WITH_ROCKSDB
std::string key_str = key_to_string(key);
std::string full_key = prefix_ + key_str;
auto iter = std::unique_ptr(db_->NewIterator(rocksdb::ReadOptions{}));
iter->Seek(full_key);
if (iter->Valid() && iter->key().ToString() == full_key)
return iterator(db_, prefix_, std::move(iter), codec_);
#endif
return end();
}
size_t erase(const key_type& key) {
#ifdef WITH_ROCKSDB
std::string key_str = key_to_string(key);
std::string full_key = prefix_ + key_str;
rocksdb::Status s = db_->Delete(rocksdb::WriteOptions{}, full_key);
return s.ok() ? 1 : 0;
#else
return 0;
#endif
}
std::pair insert(const value_type& val) {
#ifdef WITH_ROCKSDB
std::string key_str = key_to_string(val.first);
std::string full_key = prefix_ + key_str;
std::string existing;
rocksdb::Status s = db_->Get(rocksdb::ReadOptions{}, full_key, &existing);
if (s.ok()) {
// Key already exists.
return { find(val.first), false };
}
std::string encoded = codec_.encode(val.second);
s = db_->Put(rocksdb::WriteOptions{}, full_key, encoded);
if (!s.ok()) {
return { end(), false };
}
#endif
return { find(val.first), true };
}
};
#endif