Skip to content

Commit

Permalink
Support for partial writing of const qualified objects (#1304)
Browse files Browse the repository at this point in the history
* test case for const-qualified partial write bug

* Using new hash method for partial writing

* Using new hash approach for reflectable
  • Loading branch information
stephenberry committed Sep 17, 2024
1 parent 4d2d008 commit ecf7910
Show file tree
Hide file tree
Showing 4 changed files with 153 additions and 98 deletions.
124 changes: 95 additions & 29 deletions include/glaze/core/refl.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -1639,15 +1639,19 @@ namespace glz::detail
return std::memchr(it, '"', size_t(end - it));
}
}


template <class T, auto HashInfo, hash_type Type>
struct decode_hash;

template <class T, auto HashInfo>
GLZ_ALWAYS_INLINE constexpr size_t decode_hash(auto&& it, auto&& end) noexcept
struct decode_hash<T, HashInfo, hash_type::unique_index>
{
constexpr auto type = HashInfo.type;
constexpr auto N = refl<T>.N;

using enum hash_type;
if constexpr (type == unique_index) {
static constexpr auto N = refl<T>.N;
static constexpr auto bsize = bucket_size(hash_type::unique_index, N);
static constexpr auto uindex = HashInfo.unique_index;

GLZ_ALWAYS_INLINE static constexpr size_t op(auto&& it, auto&& end) noexcept
{
if constexpr (HashInfo.sized_hash) {
const auto* c = quote_memchr<HashInfo.min_length>(it, end);
if (c) [[likely]] {
Expand All @@ -1657,7 +1661,6 @@ namespace glz::detail
}

const auto h = bitmix(uint16_t(it[HashInfo.unique_index]) | (uint16_t(n) << 8), HashInfo.seed);
static constexpr auto bsize = bucket_size(unique_index, N);
return HashInfo.table[h % bsize];
}
else [[unlikely]] {
Expand All @@ -1666,18 +1669,22 @@ namespace glz::detail
}
else {
if constexpr (N == 2) {
static constexpr auto uindex = HashInfo.unique_index;
if constexpr (uindex > 0) {
if ((it + uindex) >= end) [[unlikely]] {
return N; // error
}
}
// Avoids using a hash table
static constexpr auto first_key_char = refl<T>.keys[0][uindex];
return size_t(bool(it[uindex] ^ first_key_char));
if (std::is_constant_evaluated()) {
constexpr auto first_key_char = refl<T>.keys[0][uindex];
return size_t(bool(it[uindex] ^ first_key_char));
}
else {
static constexpr auto first_key_char = refl<T>.keys[0][uindex];
return size_t(bool(it[uindex] ^ first_key_char));
}
}
else {
static constexpr auto uindex = HashInfo.unique_index;
if constexpr (uindex > 0) {
if ((it + uindex) >= end) [[unlikely]] {
return N; // error
Expand All @@ -1687,48 +1694,103 @@ namespace glz::detail
}
}
}
else if constexpr (type == three_element_unique_index) {
static constexpr auto uindex = HashInfo.unique_index;
};

template <class T, auto HashInfo>
struct decode_hash<T, HashInfo, hash_type::three_element_unique_index>
{
static constexpr auto N = refl<T>.N;
static constexpr auto uindex = HashInfo.unique_index;

GLZ_ALWAYS_INLINE static constexpr size_t op(auto&& it, auto&& end) noexcept
{
if constexpr (uindex > 0) {
if ((it + uindex) >= end) [[unlikely]] {
return N; // error
}
}
// Avoids using a hash table
static constexpr auto first_key_char = refl<T>.keys[0][uindex];
return (uint8_t(it[uindex] ^ first_key_char) * HashInfo.seed) % 4;
if (std::is_constant_evaluated()) {
constexpr auto first_key_char = refl<T>.keys[0][uindex];
return (uint8_t(it[uindex] ^ first_key_char) * HashInfo.seed) % 4;
}
else {
static constexpr auto first_key_char = refl<T>.keys[0][uindex];
return (uint8_t(it[uindex] ^ first_key_char) * HashInfo.seed) % 4;
}
}
else if constexpr (type == front_hash) {
static constexpr auto bsize = bucket_size(front_hash, N);
};

template <class T, auto HashInfo>
struct decode_hash<T, HashInfo, hash_type::front_hash>
{
static constexpr auto N = refl<T>.N;
static constexpr auto bsize = bucket_size(hash_type::front_hash, N);

GLZ_ALWAYS_INLINE static constexpr size_t op(auto&& it, auto&& end) noexcept
{
if constexpr (HashInfo.front_hash_bytes == 2) {
if ((it + 2) >= end) [[unlikely]] {
return N; // error
}
uint16_t h;
std::memcpy(&h, it, 2);
if (std::is_constant_evaluated()) {
h = 0;
for (size_t i = 0; i < 2; ++i) {
h |= static_cast<uint16_t>(it[i]) << (8 * i);
}
}
else {
std::memcpy(&h, it, 2);
}
return HashInfo.table[bitmix(h, HashInfo.seed) % bsize];
}
else if constexpr (HashInfo.front_hash_bytes == 4) {
if ((it + 4) >= end) [[unlikely]] {
return N;
}
uint32_t h;
std::memcpy(&h, it, 4);
if (std::is_constant_evaluated()) {
h = 0;
for (size_t i = 0; i < 4; ++i) {
h |= static_cast<uint32_t>(it[i]) << (8 * i);
}
}
else {
std::memcpy(&h, it, 4);
}
return HashInfo.table[bitmix(h, HashInfo.seed) % bsize];
}
else if constexpr (HashInfo.front_hash_bytes == 8) {
if ((it + 8) >= end) [[unlikely]] {
return N;
}
uint64_t h;
std::memcpy(&h, it, 8);
if (std::is_constant_evaluated()) {
h = 0;
for (size_t i = 0; i < 8; ++i) {
h |= static_cast<uint64_t>(it[i]) << (8 * i);
}
}
else {
std::memcpy(&h, it, 8);
}
return HashInfo.table[rich_bitmix(h, HashInfo.seed) % bsize];
}
else {
static_assert(false_v<T>, "invalid hash algorithm");
}
}
else if constexpr (type == unique_per_length) {
};

template <class T, auto HashInfo>
struct decode_hash<T, HashInfo, hash_type::unique_per_length>
{
static constexpr auto N = refl<T>.N;
static constexpr auto bsize = bucket_size(hash_type::unique_per_length, N);

GLZ_ALWAYS_INLINE static constexpr size_t op(auto&& it, auto&& end) noexcept
{
const auto* c = quote_memchr<HashInfo.min_length>(it, end);
if (c) [[likely]] {
const auto n = uint8_t(static_cast<std::decay_t<decltype(it)>>(c) - it);
Expand All @@ -1737,29 +1799,33 @@ namespace glz::detail
return N; // error
}
const auto h = bitmix(uint16_t(it[pos]) | (uint16_t(n) << 8), HashInfo.seed);
static constexpr auto bsize = bucket_size(unique_per_length, N);
return HashInfo.table[h % bsize];
}
else [[unlikely]] {
return N;
}
}
else if constexpr (type == full_flat) {
};

template <class T, auto HashInfo>
struct decode_hash<T, HashInfo, hash_type::full_flat>
{
static constexpr auto N = refl<T>.N;
static constexpr auto bsize = bucket_size(hash_type::full_flat, N);

GLZ_ALWAYS_INLINE static constexpr size_t op(auto&& it, auto&& end) noexcept
{
const auto* c = quote_memchr<HashInfo.min_length>(it, end);
if (c) [[likely]] {
const auto n = uint8_t(static_cast<std::decay_t<decltype(it)>>(c) - it);
static constexpr auto bsize = bucket_size(full_flat, N);
const auto h = full_hash<HashInfo.min_length, HashInfo.max_length, HashInfo.seed>(it, n);
return HashInfo.table[h % bsize];
}
else [[unlikely]] {
return N;
}
}
else {
static_assert(false_v<T>, "invalid hash algorithm");
}
}
};
}

namespace glz
Expand Down
4 changes: 2 additions & 2 deletions include/glaze/json/read.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -274,7 +274,7 @@ namespace glz
decode_index<Opts, T, 0>(func, tuple, value, ctx, it, end);
}
else {
const auto index = decode_hash<T, HashInfo>(it, end);
const auto index = decode_hash<T, HashInfo, HashInfo.type>::op(it, end);

if (index >= N) [[unlikely]] {
if constexpr (Opts.error_on_unknown_keys) {
Expand Down Expand Up @@ -1241,7 +1241,7 @@ namespace glz
else {
static constexpr auto HashInfo = hash_info<T>;

const auto index = decode_hash<T, HashInfo>(it, end);
const auto index = decode_hash<T, HashInfo, HashInfo.type>::op(it, end);

if (index >= N) [[unlikely]] {
ctx.error = error_code::unexpected_enum;
Expand Down
90 changes: 23 additions & 67 deletions include/glaze/json/write.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -1544,83 +1544,39 @@ namespace glz
static constexpr auto num_members = refl<T>.N;

if constexpr ((num_members > 0) && (glaze_object_t<T> || reflectable<T>)) {
if constexpr (glaze_object_t<T>) {
invoke_table<N>([&]<size_t I>() {
if (bool(ctx.error)) [[unlikely]] {
return;
}
static constexpr auto HashInfo = hash_info<T>;

invoke_table<N>([&]<size_t I>() {
if (bool(ctx.error)) [[unlikely]] {
return;
}

static constexpr auto group = glz::get<I>(groups);
static constexpr auto group = glz::get<I>(groups);

static constexpr auto key = get<0>(group);
static constexpr auto quoted_key = join_v < chars<"\"">, key,
Opts.prettify ? chars<"\": "> : chars < "\":" >>
;
dump<quoted_key>(b, ix);
static constexpr auto key = get<0>(group);
static constexpr auto quoted_key = join_v < chars<"\"">, key,
Opts.prettify ? chars<"\": "> : chars < "\":" >>
;
dump<quoted_key>(b, ix);

static constexpr auto sub_partial = get<1>(group);
static constexpr auto frozen_map = make_map<T>();
static constexpr auto member_it = frozen_map.find(key);
static_assert(member_it != frozen_map.end(), "Invalid key passed to partial write");
static constexpr auto index = member_it->second.index();
static constexpr decltype(auto) member_ptr = get<index>(member_it->second);
static constexpr auto sub_partial = get<1>(group);
static constexpr auto index = decode_hash<T, HashInfo, HashInfo.type>::op(key.data(), key.data() + key.size());
static_assert(index < num_members, "Invalid key passed to partial write");
if constexpr (glaze_object_t<T>) {
static constexpr auto member = get<index>(refl<T>.values);

write_partial<json>::op<sub_partial, Opts>(get_member(value, member_ptr), ctx, b, ix);
write_partial<json>::op<sub_partial, Opts>(get_member(value, member), ctx, b, ix);
if constexpr (I != N - 1) {
write_object_entry_separator<Opts>(ctx, b, ix);
}
});
}
else {
#if ((defined _MSC_VER) && (!defined __clang__))
static thread_local auto cmap = make_map<T, Opts.use_hash_comparison>();
#else
static thread_local constinit auto cmap = make_map<T, Opts.use_hash_comparison>();
#endif
populate_map(value, cmap); // Function required for MSVC to build

static constexpr auto members = member_names<T>;

invoke_table<N>([&]<size_t I>() {
if (bool(ctx.error)) [[unlikely]] {
return;
}

static constexpr auto group = glz::get<I>(groups);

static constexpr auto key = get<0>(group);
constexpr auto mem_it = std::find(members.begin(), members.end(), key);
static_assert(mem_it != members.end(), "Invalid key passed to partial write");

static constexpr auto quoted_key = join_v < chars<"\"">, key,
Opts.prettify ? chars<"\": "> : chars < "\":" >>
;
dump<quoted_key>(b, ix);

static constexpr auto sub_partial = get<1>(group);
auto member_it = cmap.find(key); // we verified at compile time that this exists
std::visit(
[&](auto&& member_ptr) {
if constexpr (std::count(sub_partial.begin(), sub_partial.end(), "") > 0) {
detail::write<json>::op<Opts>(get_member(value, member_ptr), ctx, b, ix);
}
else {
decltype(auto) member = get_member(value, member_ptr);
using M = std::decay_t<decltype(member)>;
if constexpr (glaze_object_t<M> || writable_map_t<M> || reflectable<M>) {
write_partial<json>::op<sub_partial, Opts>(member, ctx, b, ix);
}
else {
detail::write<json>::op<Opts>(member, ctx, b, ix);
}
}
},
member_it->second);
}
else {
write_partial<json>::op<sub_partial, Opts>(get_member(value, get<index>(to_tuple(value))), ctx, b, ix);
if constexpr (I != N - 1) {
write_object_entry_separator<Opts>(ctx, b, ix);
}
});
}
}
});
}
else if constexpr (writable_map_t<T>) {
invoke_table<N>([&]<size_t I>() {
Expand Down
33 changes: 33 additions & 0 deletions tests/json_test/json_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -8110,6 +8110,19 @@ struct zoo_t
};
};

struct animals_reflection_t
{
std::string lion = "Lion";
std::string tiger = "Tiger";
std::string panda = "Panda";
};

struct zoo_reflection_t
{
animals_reflection_t animals{};
std::string name{"My Awesome Zoo"};
};

suite partial_write_tests = [] {
"partial write"_test = [] {
static constexpr auto partial = glz::json_ptrs("/name", "/animals/tiger");
Expand All @@ -8120,6 +8133,26 @@ suite partial_write_tests = [] {
expect(!ec);
expect(s == R"({"animals":{"tiger":"Tiger"},"name":"My Awesome Zoo"})") << s;
};

"partial write const qualified"_test = [] {
static constexpr auto partial = glz::json_ptrs("/name", "/animals/tiger");

const zoo_t obj{};
std::string s{};
const auto ec = glz::write_json<partial>(obj, s);
expect(!ec);
expect(s == R"({"animals":{"tiger":"Tiger"},"name":"My Awesome Zoo"})") << s;
};

"reflection partial write const qualified"_test = [] {
static constexpr auto partial = glz::json_ptrs("/name", "/animals/tiger");

const zoo_reflection_t obj{};
std::string s{};
const auto ec = glz::write_json<partial>(obj, s);
expect(!ec);
expect(s == R"({"animals":{"tiger":"Tiger"},"name":"My Awesome Zoo"})") << s;
};

"partial write with raw buffer"_test = [] {
static constexpr auto json_ptrs = glz::json_ptrs("/name");
Expand Down

0 comments on commit ecf7910

Please sign in to comment.