doc_binary.cpp 7.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245
  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(const Doc& other) {
  138. if (other.doc_) {
  139. doc_ = yyjson_mut_doc_new(nullptr);
  140. if (!doc_) {
  141. throw std::bad_alloc();
  142. }
  143. yyjson_mut_val* root = yyjson_mut_val_mut_copy(doc_, other.root());
  144. if (root) {
  145. yyjson_mut_doc_set_root(doc_, root);
  146. }
  147. }
  148. }
  149. Doc& Doc::operator=(const Doc& other) {
  150. if (this != &other) {
  151. Doc tmp(other);
  152. std::swap(doc_, tmp.doc_);
  153. }
  154. return *this;
  155. }
  156. Doc::~Doc() {
  157. if (doc_) {
  158. yyjson_mut_doc_free(doc_);
  159. doc_ = nullptr;
  160. }
  161. }
  162. bool Doc::empty() const noexcept {
  163. return doc_ == nullptr;
  164. }
  165. yyjson_mut_val* Doc::root() const noexcept {
  166. if (!doc_) return nullptr;
  167. return yyjson_mut_doc_get_root(doc_);
  168. }
  169. yyjson_mut_doc* Doc::raw() const noexcept {
  170. return doc_;
  171. }
  172. // ---------------- free functions ----------------
  173. Doc encode(const nlohmann::json& j) {
  174. yyjson_mut_doc* doc = yyjson_mut_doc_new(nullptr);
  175. if (!doc) {
  176. throw std::bad_alloc();
  177. }
  178. yyjson_mut_val* root = build_value(doc, j);
  179. if (!root) {
  180. yyjson_mut_doc_free(doc);
  181. throw std::bad_alloc();
  182. }
  183. yyjson_mut_doc_set_root(doc, root);
  184. return Doc(doc);
  185. }
  186. nlohmann::json decode(const Doc& doc) {
  187. if (doc.empty()) {
  188. return nlohmann::json(nullptr);
  189. }
  190. return convert_to_nlohmann(doc.root());
  191. }
  192. std::optional<nlohmann::json> get_field(const Doc& doc, std::string_view field_name) {
  193. if (doc.empty()) {
  194. return std::nullopt;
  195. }
  196. yyjson_mut_val* root = doc.root();
  197. if (!root || yyjson_mut_get_type(root) != YYJSON_TYPE_OBJ) {
  198. return std::nullopt;
  199. }
  200. // yyjson_mut_obj_getn does NOT require a null-terminated key — it takes
  201. // explicit length, so std::string_view's non-terminated buffer is safe.
  202. yyjson_mut_val* v = yyjson_mut_obj_getn(root, field_name.data(), field_name.size());
  203. if (!v) {
  204. return std::nullopt;
  205. }
  206. return convert_to_nlohmann(v);
  207. }
  208. std::string to_json_text(const Doc& doc) {
  209. if (doc.empty()) {
  210. return "null";
  211. }
  212. size_t len = 0;
  213. WriteBuf buf;
  214. buf.data = yyjson_mut_write(doc.raw(), 0, &len);
  215. if (!buf.data) {
  216. throw std::bad_alloc();
  217. }
  218. return std::string(buf.data, len);
  219. }
  220. } // namespace smartbotic::db::doc_binary