| // Copyright (c) 2011-present, Facebook, Inc. All rights reserved. |
| // This source code is licensed under both the GPLv2 (found in the |
| // COPYING file in the root directory) and Apache 2.0 License |
| // (found in the LICENSE.Apache file in the root directory). |
| #ifndef ROCKSDB_LITE |
| |
| #include "rocksdb/utilities/json_document.h" |
| |
| #ifndef __STDC_FORMAT_MACROS |
| #define __STDC_FORMAT_MACROS |
| #endif |
| |
| #include <assert.h> |
| #include <inttypes.h> |
| #include <string.h> |
| |
| #include <functional> |
| #include <limits> |
| #include <map> |
| #include <memory> |
| #include <string> |
| #include <vector> |
| |
| |
| #include "third-party/fbson/FbsonDocument.h" |
| #include "third-party/fbson/FbsonJsonParser.h" |
| #include "third-party/fbson/FbsonUtil.h" |
| #include "util/coding.h" |
| |
| using std::placeholders::_1; |
| |
| namespace { |
| |
| size_t ObjectNumElem(const fbson::ObjectVal& objectVal) { |
| size_t size = 0; |
| for (auto keyValuePair : objectVal) { |
| (void)keyValuePair; |
| ++size; |
| } |
| return size; |
| } |
| |
| template <typename Func> |
| void InitJSONDocument(std::unique_ptr<char[]>* data, |
| fbson::FbsonValue** value, |
| Func f) { |
| // TODO(stash): maybe add function to FbsonDocument to avoid creating array? |
| fbson::FbsonWriter writer; |
| bool res __attribute__((unused)) = writer.writeStartArray(); |
| assert(res); |
| uint32_t bytesWritten __attribute__((unused)) = f(writer); |
| assert(bytesWritten != 0); |
| res = writer.writeEndArray(); |
| assert(res); |
| char* buf = new char[writer.getOutput()->getSize()]; |
| memcpy(buf, writer.getOutput()->getBuffer(), writer.getOutput()->getSize()); |
| |
| *value = ((fbson::FbsonDocument *)buf)->getValue(); |
| assert((*value)->isArray()); |
| assert(((fbson::ArrayVal*)*value)->numElem() == 1); |
| *value = ((fbson::ArrayVal*)*value)->get(0); |
| data->reset(buf); |
| } |
| |
| void InitString(std::unique_ptr<char[]>* data, |
| fbson::FbsonValue** value, |
| const std::string& s) { |
| InitJSONDocument(data, value, std::bind( |
| [](fbson::FbsonWriter& writer, const std::string& str) -> uint32_t { |
| bool res __attribute__((unused)) = writer.writeStartString(); |
| assert(res); |
| auto bytesWritten = writer.writeString(str.c_str(), |
| static_cast<uint32_t>(str.length())); |
| res = writer.writeEndString(); |
| assert(res); |
| // If the string is empty, then bytesWritten == 0, and assert in |
| // InitJsonDocument will fail. |
| return bytesWritten + static_cast<uint32_t>(str.empty()); |
| }, |
| _1, s)); |
| } |
| |
| bool IsNumeric(fbson::FbsonValue* value) { |
| return value->isInt8() || value->isInt16() || |
| value->isInt32() || value->isInt64(); |
| } |
| |
| int64_t GetInt64ValFromFbsonNumericType(fbson::FbsonValue* value) { |
| switch (value->type()) { |
| case fbson::FbsonType::T_Int8: |
| return reinterpret_cast<fbson::Int8Val*>(value)->val(); |
| case fbson::FbsonType::T_Int16: |
| return reinterpret_cast<fbson::Int16Val*>(value)->val(); |
| case fbson::FbsonType::T_Int32: |
| return reinterpret_cast<fbson::Int32Val*>(value)->val(); |
| case fbson::FbsonType::T_Int64: |
| return reinterpret_cast<fbson::Int64Val*>(value)->val(); |
| default: |
| assert(false); |
| } |
| return 0; |
| } |
| |
| bool IsComparable(fbson::FbsonValue* left, fbson::FbsonValue* right) { |
| if (left->type() == right->type()) { |
| return true; |
| } |
| if (IsNumeric(left) && IsNumeric(right)) { |
| return true; |
| } |
| return false; |
| } |
| |
| void CreateArray(std::unique_ptr<char[]>* data, fbson::FbsonValue** value) { |
| fbson::FbsonWriter writer; |
| bool res __attribute__((unused)) = writer.writeStartArray(); |
| assert(res); |
| res = writer.writeEndArray(); |
| assert(res); |
| data->reset(new char[writer.getOutput()->getSize()]); |
| memcpy(data->get(), |
| writer.getOutput()->getBuffer(), |
| writer.getOutput()->getSize()); |
| *value = reinterpret_cast<fbson::FbsonDocument*>(data->get())->getValue(); |
| } |
| |
| void CreateObject(std::unique_ptr<char[]>* data, fbson::FbsonValue** value) { |
| fbson::FbsonWriter writer; |
| bool res __attribute__((unused)) = writer.writeStartObject(); |
| assert(res); |
| res = writer.writeEndObject(); |
| assert(res); |
| data->reset(new char[writer.getOutput()->getSize()]); |
| memcpy(data->get(), |
| writer.getOutput()->getBuffer(), |
| writer.getOutput()->getSize()); |
| *value = reinterpret_cast<fbson::FbsonDocument*>(data->get())->getValue(); |
| } |
| |
| } // namespace |
| |
| namespace rocksdb { |
| |
| |
| // TODO(stash): find smth easier |
| JSONDocument::JSONDocument() { |
| InitJSONDocument(&data_, |
| &value_, |
| std::bind(&fbson::FbsonWriter::writeNull, _1)); |
| } |
| |
| JSONDocument::JSONDocument(bool b) { |
| InitJSONDocument(&data_, |
| &value_, |
| std::bind(&fbson::FbsonWriter::writeBool, _1, b)); |
| } |
| |
| JSONDocument::JSONDocument(double d) { |
| InitJSONDocument(&data_, |
| &value_, |
| std::bind(&fbson::FbsonWriter::writeDouble, _1, d)); |
| } |
| |
| JSONDocument::JSONDocument(int8_t i) { |
| InitJSONDocument(&data_, |
| &value_, |
| std::bind(&fbson::FbsonWriter::writeInt8, _1, i)); |
| } |
| |
| JSONDocument::JSONDocument(int16_t i) { |
| InitJSONDocument(&data_, |
| &value_, |
| std::bind(&fbson::FbsonWriter::writeInt16, _1, i)); |
| } |
| |
| JSONDocument::JSONDocument(int32_t i) { |
| InitJSONDocument(&data_, |
| &value_, |
| std::bind(&fbson::FbsonWriter::writeInt32, _1, i)); |
| } |
| |
| JSONDocument::JSONDocument(int64_t i) { |
| InitJSONDocument(&data_, |
| &value_, |
| std::bind(&fbson::FbsonWriter::writeInt64, _1, i)); |
| } |
| |
| JSONDocument::JSONDocument(const std::string& s) { |
| InitString(&data_, &value_, s); |
| } |
| |
| JSONDocument::JSONDocument(const char* s) : JSONDocument(std::string(s)) { |
| } |
| |
| void JSONDocument::InitFromValue(const fbson::FbsonValue* val) { |
| data_.reset(new char[val->numPackedBytes()]); |
| memcpy(data_.get(), val, val->numPackedBytes()); |
| value_ = reinterpret_cast<fbson::FbsonValue*>(data_.get()); |
| } |
| |
| // Private constructor |
| JSONDocument::JSONDocument(fbson::FbsonValue* val, bool makeCopy) { |
| if (makeCopy) { |
| InitFromValue(val); |
| } else { |
| value_ = val; |
| } |
| } |
| |
| JSONDocument::JSONDocument(Type _type) { |
| // TODO(icanadi) make all of this better by using templates |
| switch (_type) { |
| case kNull: |
| InitJSONDocument(&data_, &value_, |
| std::bind(&fbson::FbsonWriter::writeNull, _1)); |
| break; |
| case kObject: |
| CreateObject(&data_, &value_); |
| break; |
| case kBool: |
| InitJSONDocument(&data_, &value_, |
| std::bind(&fbson::FbsonWriter::writeBool, _1, false)); |
| break; |
| case kDouble: |
| InitJSONDocument(&data_, &value_, |
| std::bind(&fbson::FbsonWriter::writeDouble, _1, 0.)); |
| break; |
| case kArray: |
| CreateArray(&data_, &value_); |
| break; |
| case kInt64: |
| InitJSONDocument(&data_, &value_, |
| std::bind(&fbson::FbsonWriter::writeInt64, _1, 0)); |
| break; |
| case kString: |
| InitString(&data_, &value_, ""); |
| break; |
| default: |
| assert(false); |
| } |
| } |
| |
| JSONDocument::JSONDocument(const JSONDocument& jsonDocument) { |
| if (jsonDocument.IsOwner()) { |
| InitFromValue(jsonDocument.value_); |
| } else { |
| value_ = jsonDocument.value_; |
| } |
| } |
| |
| JSONDocument::JSONDocument(JSONDocument&& jsonDocument) { |
| value_ = jsonDocument.value_; |
| data_.swap(jsonDocument.data_); |
| } |
| |
| JSONDocument& JSONDocument::operator=(JSONDocument jsonDocument) { |
| value_ = jsonDocument.value_; |
| data_.swap(jsonDocument.data_); |
| return *this; |
| } |
| |
| JSONDocument::Type JSONDocument::type() const { |
| switch (value_->type()) { |
| case fbson::FbsonType::T_Null: |
| return JSONDocument::kNull; |
| |
| case fbson::FbsonType::T_True: |
| case fbson::FbsonType::T_False: |
| return JSONDocument::kBool; |
| |
| case fbson::FbsonType::T_Int8: |
| case fbson::FbsonType::T_Int16: |
| case fbson::FbsonType::T_Int32: |
| case fbson::FbsonType::T_Int64: |
| return JSONDocument::kInt64; |
| |
| case fbson::FbsonType::T_Double: |
| return JSONDocument::kDouble; |
| |
| case fbson::FbsonType::T_String: |
| return JSONDocument::kString; |
| |
| case fbson::FbsonType::T_Object: |
| return JSONDocument::kObject; |
| |
| case fbson::FbsonType::T_Array: |
| return JSONDocument::kArray; |
| |
| case fbson::FbsonType::T_Binary: |
| default: |
| assert(false); |
| } |
| return JSONDocument::kNull; |
| } |
| |
| bool JSONDocument::Contains(const std::string& key) const { |
| assert(IsObject()); |
| auto objectVal = reinterpret_cast<fbson::ObjectVal*>(value_); |
| return objectVal->find(key.c_str()) != nullptr; |
| } |
| |
| JSONDocument JSONDocument::operator[](const std::string& key) const { |
| assert(IsObject()); |
| auto objectVal = reinterpret_cast<fbson::ObjectVal*>(value_); |
| auto foundValue = objectVal->find(key.c_str()); |
| assert(foundValue != nullptr); |
| // No need to save paths in const objects |
| JSONDocument ans(foundValue, false); |
| return ans; |
| } |
| |
| size_t JSONDocument::Count() const { |
| assert(IsObject() || IsArray()); |
| if (IsObject()) { |
| // TODO(stash): add to fbson? |
| const fbson::ObjectVal& objectVal = |
| *reinterpret_cast<fbson::ObjectVal*>(value_); |
| return ObjectNumElem(objectVal); |
| } else if (IsArray()) { |
| auto arrayVal = reinterpret_cast<fbson::ArrayVal*>(value_); |
| return arrayVal->numElem(); |
| } |
| assert(false); |
| return 0; |
| } |
| |
| JSONDocument JSONDocument::operator[](size_t i) const { |
| assert(IsArray()); |
| auto arrayVal = reinterpret_cast<fbson::ArrayVal*>(value_); |
| auto foundValue = arrayVal->get(static_cast<int>(i)); |
| JSONDocument ans(foundValue, false); |
| return ans; |
| } |
| |
| bool JSONDocument::IsNull() const { |
| return value_->isNull(); |
| } |
| |
| bool JSONDocument::IsArray() const { |
| return value_->isArray(); |
| } |
| |
| bool JSONDocument::IsBool() const { |
| return value_->isTrue() || value_->isFalse(); |
| } |
| |
| bool JSONDocument::IsDouble() const { |
| return value_->isDouble(); |
| } |
| |
| bool JSONDocument::IsInt64() const { |
| return value_->isInt8() || value_->isInt16() || |
| value_->isInt32() || value_->isInt64(); |
| } |
| |
| bool JSONDocument::IsObject() const { |
| return value_->isObject(); |
| } |
| |
| bool JSONDocument::IsString() const { |
| return value_->isString(); |
| } |
| |
| bool JSONDocument::GetBool() const { |
| assert(IsBool()); |
| return value_->isTrue(); |
| } |
| |
| double JSONDocument::GetDouble() const { |
| assert(IsDouble()); |
| return ((fbson::DoubleVal*)value_)->val(); |
| } |
| |
| int64_t JSONDocument::GetInt64() const { |
| assert(IsInt64()); |
| return GetInt64ValFromFbsonNumericType(value_); |
| } |
| |
| std::string JSONDocument::GetString() const { |
| assert(IsString()); |
| fbson::StringVal* stringVal = (fbson::StringVal*)value_; |
| return std::string(stringVal->getBlob(), stringVal->getBlobLen()); |
| } |
| |
| namespace { |
| |
| // FbsonValue can be int8, int16, int32, int64 |
| bool CompareNumeric(fbson::FbsonValue* left, fbson::FbsonValue* right) { |
| assert(IsNumeric(left) && IsNumeric(right)); |
| return GetInt64ValFromFbsonNumericType(left) == |
| GetInt64ValFromFbsonNumericType(right); |
| } |
| |
| bool CompareSimpleTypes(fbson::FbsonValue* left, fbson::FbsonValue* right) { |
| if (IsNumeric(left)) { |
| return CompareNumeric(left, right); |
| } |
| if (left->numPackedBytes() != right->numPackedBytes()) { |
| return false; |
| } |
| return memcmp(left, right, left->numPackedBytes()) == 0; |
| } |
| |
| bool CompareFbsonValue(fbson::FbsonValue* left, fbson::FbsonValue* right) { |
| if (!IsComparable(left, right)) { |
| return false; |
| } |
| |
| switch (left->type()) { |
| case fbson::FbsonType::T_True: |
| case fbson::FbsonType::T_False: |
| case fbson::FbsonType::T_Null: |
| return true; |
| case fbson::FbsonType::T_Int8: |
| case fbson::FbsonType::T_Int16: |
| case fbson::FbsonType::T_Int32: |
| case fbson::FbsonType::T_Int64: |
| return CompareNumeric(left, right); |
| case fbson::FbsonType::T_String: |
| case fbson::FbsonType::T_Double: |
| return CompareSimpleTypes(left, right); |
| case fbson::FbsonType::T_Object: |
| { |
| auto leftObject = reinterpret_cast<fbson::ObjectVal*>(left); |
| auto rightObject = reinterpret_cast<fbson::ObjectVal*>(right); |
| if (ObjectNumElem(*leftObject) != ObjectNumElem(*rightObject)) { |
| return false; |
| } |
| for (auto && keyValue : *leftObject) { |
| std::string str(keyValue.getKeyStr(), keyValue.klen()); |
| if (rightObject->find(str.c_str()) == nullptr) { |
| return false; |
| } |
| if (!CompareFbsonValue(keyValue.value(), |
| rightObject->find(str.c_str()))) { |
| return false; |
| } |
| } |
| return true; |
| } |
| case fbson::FbsonType::T_Array: |
| { |
| auto leftArr = reinterpret_cast<fbson::ArrayVal*>(left); |
| auto rightArr = reinterpret_cast<fbson::ArrayVal*>(right); |
| if (leftArr->numElem() != rightArr->numElem()) { |
| return false; |
| } |
| for (int i = 0; i < static_cast<int>(leftArr->numElem()); ++i) { |
| if (!CompareFbsonValue(leftArr->get(i), rightArr->get(i))) { |
| return false; |
| } |
| } |
| return true; |
| } |
| default: |
| assert(false); |
| } |
| return false; |
| } |
| |
| } // namespace |
| |
| bool JSONDocument::operator==(const JSONDocument& rhs) const { |
| return CompareFbsonValue(value_, rhs.value_); |
| } |
| |
| bool JSONDocument::operator!=(const JSONDocument& rhs) const { |
| return !(*this == rhs); |
| } |
| |
| JSONDocument JSONDocument::Copy() const { |
| return JSONDocument(value_, true); |
| } |
| |
| bool JSONDocument::IsOwner() const { |
| return data_.get() != nullptr; |
| } |
| |
| std::string JSONDocument::DebugString() const { |
| fbson::FbsonToJson fbsonToJson; |
| return fbsonToJson.json(value_); |
| } |
| |
| JSONDocument::ItemsIteratorGenerator JSONDocument::Items() const { |
| assert(IsObject()); |
| return ItemsIteratorGenerator(*(reinterpret_cast<fbson::ObjectVal*>(value_))); |
| } |
| |
| // TODO(icanadi) (perf) allocate objects with arena |
| JSONDocument* JSONDocument::ParseJSON(const char* json) { |
| fbson::FbsonJsonParser parser; |
| if (!parser.parse(json)) { |
| return nullptr; |
| } |
| |
| auto fbsonVal = fbson::FbsonDocument::createValue( |
| parser.getWriter().getOutput()->getBuffer(), |
| static_cast<uint32_t>(parser.getWriter().getOutput()->getSize())); |
| |
| if (fbsonVal == nullptr) { |
| return nullptr; |
| } |
| |
| return new JSONDocument(fbsonVal, true); |
| } |
| |
| void JSONDocument::Serialize(std::string* dst) const { |
| // first byte is reserved for header |
| // currently, header is only version number. that will help us provide |
| // backwards compatility. we might also store more information here if |
| // necessary |
| dst->push_back(kSerializationFormatVersion); |
| dst->push_back(FBSON_VER); |
| dst->append(reinterpret_cast<char*>(value_), value_->numPackedBytes()); |
| } |
| |
| const char JSONDocument::kSerializationFormatVersion = 2; |
| |
| JSONDocument* JSONDocument::Deserialize(const Slice& src) { |
| Slice input(src); |
| if (src.size() == 0) { |
| return nullptr; |
| } |
| char header = input[0]; |
| if (header == 1) { |
| assert(false); |
| } |
| input.remove_prefix(1); |
| auto value = fbson::FbsonDocument::createValue(input.data(), |
| static_cast<uint32_t>(input.size())); |
| if (value == nullptr) { |
| return nullptr; |
| } |
| |
| return new JSONDocument(value, true); |
| } |
| |
| class JSONDocument::const_item_iterator::Impl { |
| public: |
| typedef fbson::ObjectVal::const_iterator It; |
| |
| explicit Impl(It it) : it_(it) {} |
| |
| const char* getKeyStr() const { |
| return it_->getKeyStr(); |
| } |
| |
| uint8_t klen() const { |
| return it_->klen(); |
| } |
| |
| It& operator++() { |
| return ++it_; |
| } |
| |
| bool operator!=(const Impl& other) { |
| return it_ != other.it_; |
| } |
| |
| fbson::FbsonValue* value() const { |
| return it_->value(); |
| } |
| |
| private: |
| It it_; |
| }; |
| |
| JSONDocument::const_item_iterator::const_item_iterator(Impl* impl) |
| : it_(impl) {} |
| |
| JSONDocument::const_item_iterator::const_item_iterator(const_item_iterator&& a) |
| : it_(std::move(a.it_)) {} |
| |
| JSONDocument::const_item_iterator& |
| JSONDocument::const_item_iterator::operator++() { |
| ++(*it_); |
| return *this; |
| } |
| |
| bool JSONDocument::const_item_iterator::operator!=( |
| const const_item_iterator& other) { |
| return *it_ != *(other.it_); |
| } |
| |
| JSONDocument::const_item_iterator::~const_item_iterator() { |
| } |
| |
| JSONDocument::const_item_iterator::value_type |
| JSONDocument::const_item_iterator::operator*() { |
| return JSONDocument::const_item_iterator::value_type(std::string(it_->getKeyStr(), it_->klen()), |
| JSONDocument(it_->value(), false)); |
| } |
| |
| JSONDocument::ItemsIteratorGenerator::ItemsIteratorGenerator( |
| const fbson::ObjectVal& object) |
| : object_(object) {} |
| |
| JSONDocument::const_item_iterator |
| JSONDocument::ItemsIteratorGenerator::begin() const { |
| return const_item_iterator(new const_item_iterator::Impl(object_.begin())); |
| } |
| |
| JSONDocument::const_item_iterator |
| JSONDocument::ItemsIteratorGenerator::end() const { |
| return const_item_iterator(new const_item_iterator::Impl(object_.end())); |
| } |
| |
| } // namespace rocksdb |
| #endif // ROCKSDB_LITE |