doc_binary.cpp 6.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224
  1. // v1.11 Phase B — binary in-memory document storage backed by yyjson mut_doc.
  2. //
  3. // All yyjson includes are confined to this translation unit; the public
  4. // header forward-declares yyjson_mut_doc / yyjson_mut_val so callsites stay
  5. // yyjson-agnostic.
  6. #include "doc_binary.hpp"
  7. #include <yyjson.h>
  8. #include <cstdlib>
  9. #include <new>
  10. #include <string>
  11. #include <utility>
  12. namespace smartbotic::db::doc_binary {
  13. namespace {
  14. // Build a yyjson_mut_val for `j` inside `doc`. Returns nullptr on allocator
  15. // failure; the caller treats nullptr as bad_alloc.
  16. yyjson_mut_val* build_value(yyjson_mut_doc* doc, const nlohmann::json& j) {
  17. using value_t = nlohmann::detail::value_t;
  18. switch (j.type()) {
  19. case value_t::null:
  20. return yyjson_mut_null(doc);
  21. case value_t::boolean:
  22. return yyjson_mut_bool(doc, j.get<bool>());
  23. case value_t::number_integer:
  24. return yyjson_mut_sint(doc, j.get<int64_t>());
  25. case value_t::number_unsigned:
  26. return yyjson_mut_uint(doc, j.get<uint64_t>());
  27. case value_t::number_float:
  28. return yyjson_mut_real(doc, j.get<double>());
  29. case value_t::string: {
  30. const std::string& s = j.get_ref<const std::string&>();
  31. return yyjson_mut_strncpy(doc, s.data(), s.size());
  32. }
  33. case value_t::array: {
  34. yyjson_mut_val* arr = yyjson_mut_arr(doc);
  35. if (!arr) return nullptr;
  36. for (const auto& elem : j) {
  37. yyjson_mut_val* v = build_value(doc, elem);
  38. if (!v) return nullptr;
  39. if (!yyjson_mut_arr_add_val(arr, v)) return nullptr;
  40. }
  41. return arr;
  42. }
  43. case value_t::object: {
  44. yyjson_mut_val* obj = yyjson_mut_obj(doc);
  45. if (!obj) return nullptr;
  46. for (auto it = j.begin(); it != j.end(); ++it) {
  47. const std::string& key = it.key();
  48. yyjson_mut_val* k = yyjson_mut_strncpy(doc, key.data(), key.size());
  49. if (!k) return nullptr;
  50. yyjson_mut_val* v = build_value(doc, it.value());
  51. if (!v) return nullptr;
  52. if (!yyjson_mut_obj_add(obj, k, v)) return nullptr;
  53. }
  54. return obj;
  55. }
  56. case value_t::binary:
  57. // nlohmann's binary type has no direct JSON mapping; fall through
  58. // to null. No production callsite uses binary().
  59. return yyjson_mut_null(doc);
  60. case value_t::discarded:
  61. default:
  62. return yyjson_mut_null(doc);
  63. }
  64. }
  65. // Convert a yyjson_mut_val back into an nlohmann::json. Mirrors json_parse.cpp
  66. // but operates on the mut tree.
  67. nlohmann::json convert_to_nlohmann(yyjson_mut_val* v) {
  68. if (!v) {
  69. return nullptr;
  70. }
  71. switch (yyjson_mut_get_type(v)) {
  72. case YYJSON_TYPE_NULL:
  73. return nullptr;
  74. case YYJSON_TYPE_BOOL:
  75. return yyjson_mut_get_bool(v);
  76. case YYJSON_TYPE_NUM:
  77. switch (yyjson_mut_get_subtype(v)) {
  78. case YYJSON_SUBTYPE_UINT:
  79. return yyjson_mut_get_uint(v);
  80. case YYJSON_SUBTYPE_SINT:
  81. return yyjson_mut_get_sint(v);
  82. case YYJSON_SUBTYPE_REAL:
  83. default:
  84. return yyjson_mut_get_real(v);
  85. }
  86. case YYJSON_TYPE_STR:
  87. return std::string(yyjson_mut_get_str(v), yyjson_mut_get_len(v));
  88. case YYJSON_TYPE_ARR: {
  89. nlohmann::json out = nlohmann::json::array();
  90. size_t idx = 0, max = 0;
  91. yyjson_mut_val* elem = nullptr;
  92. yyjson_mut_arr_foreach(v, idx, max, elem) {
  93. out.push_back(convert_to_nlohmann(elem));
  94. }
  95. return out;
  96. }
  97. case YYJSON_TYPE_OBJ: {
  98. nlohmann::json out = nlohmann::json::object();
  99. size_t idx = 0, max = 0;
  100. yyjson_mut_val* key = nullptr;
  101. yyjson_mut_val* val = nullptr;
  102. yyjson_mut_obj_foreach(v, idx, max, key, val) {
  103. out[std::string(yyjson_mut_get_str(key), yyjson_mut_get_len(key))] =
  104. convert_to_nlohmann(val);
  105. }
  106. return out;
  107. }
  108. default:
  109. return nullptr;
  110. }
  111. }
  112. // RAII guard for the malloc'd buffer returned by yyjson_mut_write.
  113. struct WriteBuf {
  114. char* data = nullptr;
  115. ~WriteBuf() {
  116. if (data) {
  117. std::free(data);
  118. }
  119. }
  120. };
  121. } // namespace
  122. // ---------------- Doc ----------------
  123. Doc::Doc(yyjson_mut_doc* doc) : doc_(doc) {}
  124. Doc::Doc(Doc&& other) noexcept : doc_(other.doc_) {
  125. other.doc_ = nullptr;
  126. }
  127. Doc& Doc::operator=(Doc&& other) noexcept {
  128. if (this != &other) {
  129. if (doc_) {
  130. yyjson_mut_doc_free(doc_);
  131. }
  132. doc_ = other.doc_;
  133. other.doc_ = nullptr;
  134. }
  135. return *this;
  136. }
  137. Doc::~Doc() {
  138. if (doc_) {
  139. yyjson_mut_doc_free(doc_);
  140. doc_ = nullptr;
  141. }
  142. }
  143. bool Doc::empty() const noexcept {
  144. return doc_ == nullptr;
  145. }
  146. yyjson_mut_val* Doc::root() const noexcept {
  147. if (!doc_) return nullptr;
  148. return yyjson_mut_doc_get_root(doc_);
  149. }
  150. yyjson_mut_doc* Doc::raw() const noexcept {
  151. return doc_;
  152. }
  153. // ---------------- free functions ----------------
  154. Doc encode(const nlohmann::json& j) {
  155. yyjson_mut_doc* doc = yyjson_mut_doc_new(nullptr);
  156. if (!doc) {
  157. throw std::bad_alloc();
  158. }
  159. yyjson_mut_val* root = build_value(doc, j);
  160. if (!root) {
  161. yyjson_mut_doc_free(doc);
  162. throw std::bad_alloc();
  163. }
  164. yyjson_mut_doc_set_root(doc, root);
  165. return Doc(doc);
  166. }
  167. nlohmann::json decode(const Doc& doc) {
  168. if (doc.empty()) {
  169. return nlohmann::json(nullptr);
  170. }
  171. return convert_to_nlohmann(doc.root());
  172. }
  173. std::optional<nlohmann::json> get_field(const Doc& doc, std::string_view field_name) {
  174. if (doc.empty()) {
  175. return std::nullopt;
  176. }
  177. yyjson_mut_val* root = doc.root();
  178. if (!root || yyjson_mut_get_type(root) != YYJSON_TYPE_OBJ) {
  179. return std::nullopt;
  180. }
  181. // yyjson_mut_obj_getn does NOT require a null-terminated key — it takes
  182. // explicit length, so std::string_view's non-terminated buffer is safe.
  183. yyjson_mut_val* v = yyjson_mut_obj_getn(root, field_name.data(), field_name.size());
  184. if (!v) {
  185. return std::nullopt;
  186. }
  187. return convert_to_nlohmann(v);
  188. }
  189. std::string to_json_text(const Doc& doc) {
  190. if (doc.empty()) {
  191. return "null";
  192. }
  193. size_t len = 0;
  194. WriteBuf buf;
  195. buf.data = yyjson_mut_write(doc.raw(), 0, &len);
  196. if (!buf.data) {
  197. throw std::bad_alloc();
  198. }
  199. return std::string(buf.data, len);
  200. }
  201. } // namespace smartbotic::db::doc_binary