// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. // This source code is licensed under the BSD-style license found in the // LICENSE file in the root directory of this source tree. An additional grant // of patent rights can be found in the PATENTS file in the same directory. #ifndef ROCKSDB_LITE #include "rocksdb/utilities/json_document.h" #ifndef __STDC_FORMAT_MACROS #define __STDC_FORMAT_MACROS #endif #include #include #include #include #include #include #include #include #include #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 void InitJSONDocument(std::unique_ptr* 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* 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(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(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(value)->val(); case fbson::FbsonType::T_Int16: return reinterpret_cast(value)->val(); case fbson::FbsonType::T_Int32: return reinterpret_cast(value)->val(); case fbson::FbsonType::T_Int64: return reinterpret_cast(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* 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(data->get())->getValue(); } void CreateObject(std::unique_ptr* 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(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(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: assert(false); default: assert(false); } return JSONDocument::kNull; } bool JSONDocument::Contains(const std::string& key) const { assert(IsObject()); auto objectVal = reinterpret_cast(value_); return objectVal->find(key.c_str()) != nullptr; } JSONDocument JSONDocument::operator[](const std::string& key) const { assert(IsObject()); auto objectVal = reinterpret_cast(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(value_); return ObjectNumElem(objectVal); } else if (IsArray()) { auto arrayVal = reinterpret_cast(value_); return arrayVal->numElem(); } assert(false); return 0; } JSONDocument JSONDocument::operator[](size_t i) const { assert(IsArray()); auto arrayVal = reinterpret_cast(value_); auto foundValue = arrayVal->get(static_cast(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(left); auto rightObject = reinterpret_cast(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(left); auto rightArr = reinterpret_cast(right); if (leftArr->numElem() != rightArr->numElem()) { return false; } for (int i = 0; i < static_cast(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(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(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(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(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