// v1.11 Phase B — binary in-memory document storage backed by yyjson mut_doc. // // All yyjson includes are confined to this translation unit; the public // header forward-declares yyjson_mut_doc / yyjson_mut_val so callsites stay // yyjson-agnostic. #include "doc_binary.hpp" #include #include #include #include #include namespace smartbotic::db::doc_binary { namespace { // Build a yyjson_mut_val for `j` inside `doc`. Returns nullptr on allocator // failure; the caller treats nullptr as bad_alloc. yyjson_mut_val* build_value(yyjson_mut_doc* doc, const nlohmann::json& j) { using value_t = nlohmann::detail::value_t; switch (j.type()) { case value_t::null: return yyjson_mut_null(doc); case value_t::boolean: return yyjson_mut_bool(doc, j.get()); case value_t::number_integer: return yyjson_mut_sint(doc, j.get()); case value_t::number_unsigned: return yyjson_mut_uint(doc, j.get()); case value_t::number_float: return yyjson_mut_real(doc, j.get()); case value_t::string: { const std::string& s = j.get_ref(); return yyjson_mut_strncpy(doc, s.data(), s.size()); } case value_t::array: { yyjson_mut_val* arr = yyjson_mut_arr(doc); if (!arr) return nullptr; for (const auto& elem : j) { yyjson_mut_val* v = build_value(doc, elem); if (!v) return nullptr; if (!yyjson_mut_arr_add_val(arr, v)) return nullptr; } return arr; } case value_t::object: { yyjson_mut_val* obj = yyjson_mut_obj(doc); if (!obj) return nullptr; for (auto it = j.begin(); it != j.end(); ++it) { const std::string& key = it.key(); yyjson_mut_val* k = yyjson_mut_strncpy(doc, key.data(), key.size()); if (!k) return nullptr; yyjson_mut_val* v = build_value(doc, it.value()); if (!v) return nullptr; if (!yyjson_mut_obj_add(obj, k, v)) return nullptr; } return obj; } case value_t::binary: // nlohmann's binary type has no direct JSON mapping; fall through // to null. No production callsite uses binary(). return yyjson_mut_null(doc); case value_t::discarded: default: return yyjson_mut_null(doc); } } // Convert a yyjson_mut_val back into an nlohmann::json. Mirrors json_parse.cpp // but operates on the mut tree. nlohmann::json convert_to_nlohmann(yyjson_mut_val* v) { if (!v) { return nullptr; } switch (yyjson_mut_get_type(v)) { case YYJSON_TYPE_NULL: return nullptr; case YYJSON_TYPE_BOOL: return yyjson_mut_get_bool(v); case YYJSON_TYPE_NUM: switch (yyjson_mut_get_subtype(v)) { case YYJSON_SUBTYPE_UINT: return yyjson_mut_get_uint(v); case YYJSON_SUBTYPE_SINT: return yyjson_mut_get_sint(v); case YYJSON_SUBTYPE_REAL: default: return yyjson_mut_get_real(v); } case YYJSON_TYPE_STR: return std::string(yyjson_mut_get_str(v), yyjson_mut_get_len(v)); case YYJSON_TYPE_ARR: { nlohmann::json out = nlohmann::json::array(); size_t idx = 0, max = 0; yyjson_mut_val* elem = nullptr; yyjson_mut_arr_foreach(v, idx, max, elem) { out.push_back(convert_to_nlohmann(elem)); } return out; } case YYJSON_TYPE_OBJ: { nlohmann::json out = nlohmann::json::object(); size_t idx = 0, max = 0; yyjson_mut_val* key = nullptr; yyjson_mut_val* val = nullptr; yyjson_mut_obj_foreach(v, idx, max, key, val) { out[std::string(yyjson_mut_get_str(key), yyjson_mut_get_len(key))] = convert_to_nlohmann(val); } return out; } default: return nullptr; } } // RAII guard for the malloc'd buffer returned by yyjson_mut_write. struct WriteBuf { char* data = nullptr; ~WriteBuf() { if (data) { std::free(data); } } }; } // namespace // ---------------- Doc ---------------- Doc::Doc(yyjson_mut_doc* doc) : doc_(doc) {} Doc::Doc(Doc&& other) noexcept : doc_(other.doc_) { other.doc_ = nullptr; } Doc& Doc::operator=(Doc&& other) noexcept { if (this != &other) { if (doc_) { yyjson_mut_doc_free(doc_); } doc_ = other.doc_; other.doc_ = nullptr; } return *this; } Doc::~Doc() { if (doc_) { yyjson_mut_doc_free(doc_); doc_ = nullptr; } } bool Doc::empty() const noexcept { return doc_ == nullptr; } yyjson_mut_val* Doc::root() const noexcept { if (!doc_) return nullptr; return yyjson_mut_doc_get_root(doc_); } yyjson_mut_doc* Doc::raw() const noexcept { return doc_; } // ---------------- free functions ---------------- Doc encode(const nlohmann::json& j) { yyjson_mut_doc* doc = yyjson_mut_doc_new(nullptr); if (!doc) { throw std::bad_alloc(); } yyjson_mut_val* root = build_value(doc, j); if (!root) { yyjson_mut_doc_free(doc); throw std::bad_alloc(); } yyjson_mut_doc_set_root(doc, root); return Doc(doc); } nlohmann::json decode(const Doc& doc) { if (doc.empty()) { return nlohmann::json(nullptr); } return convert_to_nlohmann(doc.root()); } std::optional get_field(const Doc& doc, std::string_view field_name) { if (doc.empty()) { return std::nullopt; } yyjson_mut_val* root = doc.root(); if (!root || yyjson_mut_get_type(root) != YYJSON_TYPE_OBJ) { return std::nullopt; } // yyjson_mut_obj_getn does NOT require a null-terminated key — it takes // explicit length, so std::string_view's non-terminated buffer is safe. yyjson_mut_val* v = yyjson_mut_obj_getn(root, field_name.data(), field_name.size()); if (!v) { return std::nullopt; } return convert_to_nlohmann(v); } std::string to_json_text(const Doc& doc) { if (doc.empty()) { return "null"; } size_t len = 0; WriteBuf buf; buf.data = yyjson_mut_write(doc.raw(), 0, &len); if (!buf.data) { throw std::bad_alloc(); } return std::string(buf.data, len); } } // namespace smartbotic::db::doc_binary