/////////////////////////////////////////////////////////////////////////////// /// DISCLAIMER /// /// Copyright 2014-2018 ArangoDB GmbH, Cologne, Germany /// Copyright 2004-2014 triAGENS GmbH, Cologne, Germany /// /// Licensed under the Apache License, Version 2.0 (the "License"); /// you may not use this file except in compliance with the License. /// You may obtain a copy of the License at /// /// http://www.apache.org/licenses/LICENSE-2.0 /// /// Unless required by applicable law or agreed to in writing, software /// distributed under the License is distributed on an "AS IS" BASIS, /// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. /// See the License for the specific language governing permissions and /// limitations under the License. /// /// Copyright holder is ArangoDB GmbH, Cologne, Germany /// /// @author Kaveh Vahedipour //////////////////////////////////////////////////////////////////////////////// #include "Node.h" #include "Store.h" #include "Basics/StringUtils.h" #include #include #include #include #include #include using namespace arangodb::consensus; using namespace arangodb::basics; const Node::Children Node::dummyChildren = Node::Children(); const Node Node::_dummyNode = Node("dumm-di-dumm"); /// @brief Split strings by separator inline static std::vector split(const std::string& str, char separator) { std::vector result; if (str.empty()) { return result; } std::regex reg("/+"); std::string key = std::regex_replace(str, reg, "/"); if (!key.empty() && key.front() == '/') { key.erase(0, 1); } if (!key.empty() && key.back() == '/') { key.pop_back(); } std::string::size_type p = 0; std::string::size_type q; while ((q = key.find(separator, p)) != std::string::npos) { result.emplace_back(key, p, q - p); p = q + 1; } result.emplace_back(key, p); result.erase(std::find_if(result.rbegin(), result.rend(), [](std::string const& s) -> bool { return !s.empty(); }).base(), result.end()); return result; } /// @brief Construct with node name Node::Node(std::string const& name) : _nodeName(name), _parent(nullptr), _store(nullptr), _vecBufDirty(true), _isArray(false) {} /// @brief Construct with node name in tree structure Node::Node(std::string const& name, Node* parent) : _nodeName(name), _parent(parent), _store(nullptr), _vecBufDirty(true), _isArray(false) {} /// @brief Construct for store Node::Node(std::string const& name, Store* store) : _nodeName(name), _parent(nullptr), _store(store), _vecBufDirty(true), _isArray(false) {} /// @brief Default dtor Node::~Node() {} /// @brief Get slice to value buffer Slice Node::slice() const { // Some array if (_isArray) { rebuildVecBuf(); return Slice(_vecBuf.data()); } // Some value if (!_value.empty()) { return Slice(_value.front().data()); } // Empty object return arangodb::velocypack::Slice::emptyObjectSlice(); } /// @brief Optimization, which avoids recreating of Builder for output if /// changes have not happened since last call void Node::rebuildVecBuf() const { if (_vecBufDirty) { // Dirty vector buffer Builder tmp; { VPackArrayBuilder t(&tmp); for (auto const& i : _value) { tmp.add(Slice(i.data())); } } _vecBuf = *tmp.steal(); _vecBufDirty = false; } } /// @brief Get name of this node std::string const& Node::name() const { return _nodeName; } /// @brief Get full path of this node std::string Node::uri() const { Node* par = _parent; std::stringstream path; std::deque names; names.push_front(name()); while (par != nullptr) { names.push_front(par->name()); par = par->_parent; } for (size_t i = 1; i < names.size(); ++i) { path << "/" << names.at(i); } return path.str(); } /// @brief Move constructor Node::Node(Node&& other) : _nodeName(std::move(other._nodeName)), _parent(nullptr), _store(nullptr), _children(std::move(other._children)), _ttl(std::move(other._ttl)), _value(std::move(other._value)), _vecBuf(std::move(other._vecBuf)), _vecBufDirty(std::move(other._vecBufDirty)), _isArray(std::move(other._isArray)) { // The _children map has been moved here, therefore we must // correct the _parent entry of all direct children: for (auto& child : _children) { child.second->_parent = this; } } /// @brief Copy constructor Node::Node(Node const& other) : _nodeName(other._nodeName), _parent(nullptr), _store(nullptr), _ttl(other._ttl), _value(other._value), _vecBuf(other._vecBuf), _vecBufDirty(other._vecBufDirty), _isArray(other._isArray) { for (auto const& p : other._children) { auto copy = std::make_shared(*p.second); copy->_parent = this; // new children have us as _parent! _children.insert(std::make_pair(p.first, copy)); } } /// @brief Assignment operator (slice) /// 1. remove any existing time to live entry /// 2. clear children map /// 3. copy from rhs buffer to my buffer /// @brief Must not copy _parent, _ttl, _observers Node& Node::operator=(VPackSlice const& slice) { removeTimeToLive(); _children.clear(); _value.clear(); if (slice.isArray()) { _isArray = true; _value.resize(slice.length()); for (size_t i = 0; i < slice.length(); ++i) { _value.at(i).append(reinterpret_cast(slice[i].begin()), slice[i].byteSize()); } } else { _isArray = false; _value.resize(1); _value.front().append(reinterpret_cast(slice.begin()), slice.byteSize()); } _vecBufDirty = true; return *this; } /// @brief Move operator Node& Node::operator=(Node&& rhs) { // 1. remove any existing time to live entry // 2. move children map over // 3. move value over // Must not move over rhs's _parent, _observers _nodeName = std::move(rhs._nodeName); _children = std::move(rhs._children); // The _children map has been moved here, therefore we must // correct the _parent entry of all direct children: for (auto& child : _children) { child.second->_parent = this; } _value = std::move(rhs._value); _vecBuf = std::move(rhs._vecBuf); _vecBufDirty = std::move(rhs._vecBufDirty); _isArray = std::move(rhs._isArray); _ttl = std::move(rhs._ttl); return *this; } /// Assignment operator Node& Node::operator=(Node const& rhs) { // 1. remove any existing time to live entry // 2. clear children map // 3. move from rhs to buffer pointer // Must not move rhs's _parent, _observers removeTimeToLive(); _nodeName = rhs._nodeName; _children.clear(); for (auto const& p : rhs._children) { auto copy = std::make_shared(*p.second); copy->_parent = this; // new child copy has us as _parent _children.insert(std::make_pair(p.first, copy)); } _value = rhs._value; _vecBuf = rhs._vecBuf; _vecBufDirty = rhs._vecBufDirty; _isArray = rhs._isArray; _ttl = rhs._ttl; return *this; } /// @brief Comparison with slice bool Node::operator==(VPackSlice const& rhs) const { if (rhs.isObject()) { // build object recursively, take ttl into account return VPackNormalizedCompare::equals(toBuilder().slice(), rhs); } else { return VPackNormalizedCompare::equals(slice(), rhs); } } /// @brief Comparison with slice bool Node::operator!=(VPackSlice const& rhs) const { return !(*this == rhs); } /// @brief Remove this node from store /// i.e. Remove this node as child of parent node if applicable bool Node::remove() { if (_parent == nullptr) { return false; } Node& parent = *_parent; return parent.removeChild(_nodeName); } /// @brief Remove child by name bool Node::removeChild(std::string const& key) { auto found = _children.find(key); if (found == _children.end()) { return false; } found->second->removeTimeToLive(); _children.erase(found); return true; } /// @brief Node type /// The check is if we are an array or a value. => LEAF. NODE else NodeType Node::type() const { return (_isArray || _value.size()) ? LEAF : NODE; } /// @brief lh-value at path vector Node& Node::operator()(std::vector const& pv) { Node* current = this; for (std::string const& key : pv) { auto& children = current->_children; auto child = children.find(key); if (child == children.end()) { current->_isArray = false; if (!current->_value.empty()) { current->_value.clear(); } auto const& node = std::make_shared(key, current); children.insert(Children::value_type(key, node)); current = node.get(); } else { current = child->second.get(); } } return *current; } /// @brief rh-value at path vector. Check if TTL has expired. Node const& Node::operator()(std::vector const& pv) const { Node const* current = this; for (std::string const& key : pv) { auto const& children = current->_children; auto const child = children.find(key); if (child == children.end() || (child->second->_ttl != std::chrono::system_clock::time_point() && child->second->_ttl < std::chrono::system_clock::now())) { throw StoreException(std::string("Node ") + uri() + "/" + key + " not found!"); } else { current = child->second.get(); } } return *current; } /// @brief lh-value at path Node& Node::operator()(std::string const& path) { return this->operator()(split(path, '/')); } /// @brief rh-value at path Node const& Node::operator()(std::string const& path) const { return this->operator()(split(path, '/')); } // Get method which always throws when not found: Node const& Node::get(std::string const& path) const { return this->operator()(path); } // lh-store Node const& Node::root() const { Node* par = _parent; Node const* tmp = this; while (par != nullptr) { tmp = par; par = par->_parent; } return *tmp; } // rh-store Node& Node::root() { Node* par = _parent; Node* tmp = this; while (par != nullptr) { tmp = par; par = par->_parent; } return *tmp; } Store& Node::store() { return *(root()._store); } Store const& Node::store() const { return *(root()._store); } Store* Node::getStore() { Node* par = _parent; Node* tmp = this; while (par != nullptr) { tmp = par; par = par->_parent; } return tmp->_store; // Can be nullptr if we are not in a Node that belongs // to a store. } // velocypack value type of this node ValueType Node::valueType() const { return slice().type(); } // file time to live entry for this node to now + millis bool Node::addTimeToLive(long millis) { auto tkey = std::chrono::system_clock::now() + std::chrono::milliseconds(millis); store().timeTable().insert(std::pair(tkey, uri())); _ttl = tkey; return true; } void Node::timeToLive(TimePoint const& ttl) { _ttl = ttl; } TimePoint const& Node::timeToLive() const { return _ttl; } // remove time to live entry for this node bool Node::removeTimeToLive() { Store* s = getStore(); // We could be in a Node that belongs to a store, // or in one that doesn't. if (s != nullptr) { s->removeTTL(uri()); } if (_ttl != std::chrono::system_clock::time_point()) { _ttl = std::chrono::system_clock::time_point(); } return true; } inline bool Node::observedBy(std::string const& url) const { auto ret = store().observerTable().equal_range(url); for (auto it = ret.first; it != ret.second; ++it) { if (it->second == uri()) { return true; } } return false; } namespace arangodb { namespace consensus { /// Set value template <> bool Node::handle(VPackSlice const& slice) { Slice val = slice.get("new"); if (val.isObject()) { if (val.hasKey("op")) { // No longer a keyword but a regular key "op" if (_children.find("op") == _children.end()) { _children["op"] = std::make_shared("op", this); } *(_children["op"]) = val.get("op"); } else { // Deeper down this->applies(val); } } else { *this = val; } if (slice.hasKey("ttl")) { VPackSlice ttl_v = slice.get("ttl"); if (ttl_v.isNumber()) { long ttl = 1000l * ((ttl_v.isDouble()) ? static_cast(slice.get("ttl").getNumber()) : static_cast(slice.get("ttl").getNumber())); addTimeToLive(ttl); } else { LOG_TOPIC("66da2", WARN, Logger::AGENCY) << "Non-number value assigned to ttl: " << ttl_v.toJson(); } } return true; } /// Increment integer value or set 1 template <> bool Node::handle(VPackSlice const& slice) { size_t inc = (slice.hasKey("step") && slice.get("step").isUInt()) ? slice.get("step").getUInt() : 1; Builder tmp; { VPackObjectBuilder t(&tmp); try { tmp.add("tmp", Value(this->slice().getInt() + inc)); } catch (std::exception const&) { tmp.add("tmp", Value(1)); } } *this = tmp.slice().get("tmp"); return true; } /// Decrement integer value or set -1 template <> bool Node::handle(VPackSlice const& slice) { Builder tmp; { VPackObjectBuilder t(&tmp); try { tmp.add("tmp", Value(this->slice().getInt() - 1)); } catch (std::exception const&) { tmp.add("tmp", Value(-1)); } } *this = tmp.slice().get("tmp"); return true; } /// Append element to array template <> bool Node::handle(VPackSlice const& slice) { if (!slice.hasKey("new")) { LOG_TOPIC("a9481", WARN, Logger::AGENCY) << "Operator push without new value: " << slice.toJson(); return false; } Builder tmp; { VPackArrayBuilder t(&tmp); if (this->slice().isArray()) { for (auto const& old : VPackArrayIterator(this->slice())) tmp.add(old); } tmp.add(slice.get("new")); } *this = tmp.slice(); return true; } /// Remove element from any place in array by value or position template <> bool Node::handle(VPackSlice const& slice) { bool haveVal = slice.hasKey("val"); bool havePos = slice.hasKey("pos"); if (!haveVal && !havePos) { LOG_TOPIC("b5eaa", WARN, Logger::AGENCY) << "Operator erase without value or position to be erased is illegal: " << slice.toJson(); return false; } else if (haveVal && havePos) { LOG_TOPIC("c2756", WARN, Logger::AGENCY) << "Operator erase with value and position to be erased is illegal: " << slice.toJson(); return false; } else if (havePos && (!slice.get("pos").isUInt() && !slice.get("pos").isSmallInt())) { LOG_TOPIC("d6648", WARN, Logger::AGENCY) << "Operator erase with non-positive integer position is illegal: " << slice.toJson(); } Builder tmp; { VPackArrayBuilder t(&tmp); if (this->slice().isArray()) { if (haveVal) { VPackSlice valToErase = slice.get("val"); for (auto const& old : VPackArrayIterator(this->slice())) { if (VelocyPackHelper::compare(old, valToErase, /*useUTF8*/ true) != 0) { tmp.add(old); } } } else { size_t pos = slice.get("pos").getNumber(); if (pos >= this->slice().length()) { return false; } size_t n = 0; for (const auto& old : VPackArrayIterator(this->slice())) { if (n != pos) { tmp.add(old); } ++n; } } } } *this = tmp.slice(); return true; } /// Replace element from any place in array by new value template <> bool Node::handle(VPackSlice const& slice) { if (!slice.hasKey("val")) { LOG_TOPIC("27763", WARN, Logger::AGENCY) << "Operator erase without value to be erased: " << slice.toJson(); return false; } if (!slice.hasKey("new")) { LOG_TOPIC("28331", WARN, Logger::AGENCY) << "Operator replace without new value: " << slice.toJson(); return false; } Builder tmp; { VPackArrayBuilder t(&tmp); if (this->slice().isArray()) { VPackSlice valToRepl = slice.get("val"); for (auto const& old : VPackArrayIterator(this->slice())) { if (VelocyPackHelper::compare(old, valToRepl, /*useUTF8*/ true) == 0) { tmp.add(slice.get("new")); } else { tmp.add(old); } } } } *this = tmp.slice(); return true; } /// Remove element from end of array. template <> bool Node::handle(VPackSlice const& slice) { Builder tmp; { VPackArrayBuilder t(&tmp); if (this->slice().isArray()) { VPackArrayIterator it(this->slice()); if (it.size() > 1) { size_t j = it.size() - 1; for (auto old : it) { tmp.add(old); if (--j == 0) break; } } } } *this = tmp.slice(); return true; } /// Prepend element to array template <> bool Node::handle(VPackSlice const& slice) { if (!slice.hasKey("new")) { LOG_TOPIC("5ecb0", WARN, Logger::AGENCY) << "Operator prepend without new value: " << slice.toJson(); return false; } Builder tmp; { VPackArrayBuilder t(&tmp); tmp.add(slice.get("new")); if (this->slice().isArray()) { for (auto const& old : VPackArrayIterator(this->slice())) tmp.add(old); } } *this = tmp.slice(); return true; } /// Remove element from front of array template <> bool Node::handle(VPackSlice const& slice) { Builder tmp; { VPackArrayBuilder t(&tmp); if (this->slice().isArray()) { // If a VPackArrayIterator it(this->slice()); bool first = true; for (auto const& old : it) { if (first) { first = false; } else { tmp.add(old); } } } } *this = tmp.slice(); return true; } /// Add observer for this node template <> bool Node::handle(VPackSlice const& slice) { if (!slice.hasKey("url")) return false; if (!slice.get("url").isString()) return false; std::string url(slice.get("url").copyString()), uri(this->uri()); // check if such entry exists if (!observedBy(url)) { store().observerTable().emplace(std::pair(url, uri)); store().observedTable().emplace(std::pair(uri, url)); return true; } return false; } /// Remove observer for this node template <> bool Node::handle(VPackSlice const& slice) { if (!slice.hasKey("url")) return false; if (!slice.get("url").isString()) return false; std::string url(slice.get("url").copyString()), uri(this->uri()); // delete in both cases a single entry (ensured above) // breaking the iterators is fine then auto ret = store().observerTable().equal_range(url); for (auto it = ret.first; it != ret.second; ++it) { if (it->second == uri) { store().observerTable().erase(it); break; } } ret = store().observedTable().equal_range(uri); for (auto it = ret.first; it != ret.second; ++it) { if (it->second == url) { store().observedTable().erase(it); return true; } } return false; } } // namespace consensus } // namespace arangodb bool Node::applieOp(VPackSlice const& slice) { std::string oper = slice.get("op").copyString(); if (oper == "delete") { if (_parent == nullptr) { // root node _children.clear(); _value.clear(); _vecBufDirty = true; // just in case there was an array return true; } else { return _parent->removeChild(_nodeName); } } else if (oper == "set") { // "op":"set" return handle(slice); } else if (oper == "increment") { // "op":"increment" return handle(slice); } else if (oper == "decrement") { // "op":"decrement" return handle(slice); } else if (oper == "push") { // "op":"push" return handle(slice); } else if (oper == "pop") { // "op":"pop" return handle(slice); } else if (oper == "prepend") { // "op":"prepend" return handle(slice); } else if (oper == "shift") { // "op":"shift" return handle(slice); } else if (oper == "observe") { // "op":"observe" return handle(slice); } else if (oper == "unobserve") { // "op":"unobserve" handle(slice); if (_children.empty() && _value.empty()) { if (_parent == nullptr) { // root node _children.clear(); _value.clear(); return true; } else { return _parent->removeChild(_nodeName); } } return true; } else if (oper == "erase") { // "op":"erase" return handle(slice); } else if (oper == "replace") { // "op":"replace" return handle(slice); } else { // "op" might not be a key word after all LOG_TOPIC("fb064", WARN, Logger::AGENCY) << "Keyword 'op' without known operation. Handling as regular key: \"" << oper << "\""; } return false; } // Apply slice to this node bool Node::applies(VPackSlice const& slice) { std::regex reg("/+"); clear(); if (slice.isObject()) { for (auto const& i : VPackObjectIterator(slice)) { std::string key = std::regex_replace(i.key.copyString(), reg, "/"); if (key.find('/') != std::string::npos) { (*this)(key).applies(i.value); } else { auto found = _children.find(key); if (found == _children.end()) { _children[key] = std::make_shared(key, this); } _children[key]->applies(i.value); } } } else { *this = slice; } return true; } void Node::toBuilder(Builder& builder, bool showHidden) const { typedef std::chrono::system_clock clock; try { if (type() == NODE) { VPackObjectBuilder guard(&builder); for (auto const& child : _children) { auto const& cptr = child.second; if ((cptr->_ttl != clock::time_point() && cptr->_ttl < clock::now()) || (child.first[0] == '.' && !showHidden)) { continue; } builder.add(VPackValue(child.first)); cptr->toBuilder(builder); } } else { if (!slice().isNone()) { builder.add(slice()); } } } catch (std::exception const& e) { LOG_TOPIC("44d99", ERR, Logger::AGENCY) << e.what() << " " << __FILE__ << __LINE__; } } // Print internals to ostream std::ostream& Node::print(std::ostream& o) const { Builder builder; toBuilder(builder); o << builder.toJson(); return o; } Node::Children& Node::children() { return _children; } Node::Children const& Node::children() const { return _children; } Builder Node::toBuilder() const { Builder builder; toBuilder(builder); return builder; } std::string Node::toJson() const { return toBuilder().toJson(); } Node const* Node::parent() const { return _parent; } std::vector Node::exists(std::vector const& rel) const { std::vector result; Node const* cur = this; for (auto const& sub : rel) { auto it = cur->children().find(sub); if (it != cur->children().end() && (it->second->_ttl == std::chrono::system_clock::time_point() || it->second->_ttl >= std::chrono::system_clock::now())) { cur = it->second.get(); result.push_back(sub); } else { break; } } return result; } std::vector Node::exists(std::string const& rel) const { return exists(split(rel, '/')); } bool Node::has(std::vector const& rel) const { return exists(rel).size() == rel.size(); } bool Node::has(std::string const& rel) const { return has(split(rel, '/')); } int64_t Node::getInt() const { if (type() == NODE) { throw StoreException("Must not convert NODE type to int"); } return slice().getNumber(); } uint64_t Node::getUInt() const { if (type() == NODE) { throw StoreException("Must not convert NODE type to unsigned int"); } return slice().getNumber(); } bool Node::getBool() const { if (type() == NODE) { throw StoreException("Must not convert NODE type to bool"); } return slice().getBool(); } bool Node::isBool() const { if (type() == NODE) { return false; } return slice().isBool(); } bool Node::isDouble() const { if (type() == NODE) { return false; } return slice().isDouble(); } bool Node::isString() const { if (type() == NODE) { return false; } return slice().isString(); } bool Node::isUInt() const { if (type() == NODE) { return false; } return slice().isUInt() || slice().isSmallInt(); } bool Node::isInt() const { if (type() == NODE) { return false; } return slice().isInt() || slice().isSmallInt(); } bool Node::isNumber() const { if (type() == NODE) { return false; } return slice().isNumber(); } double Node::getDouble() const { if (type() == NODE) { throw StoreException("Must not convert NODE type to double"); } return slice().getNumber(); } std::pair Node::hasAsNode(std::string const& url) const { // *this is bogus initializer std::pair fail_pair = {*this, false}; // retrieve node, throws if does not exist try { Node const& target(operator()(url)); std::pair good_pair = {target, true}; return good_pair; } catch (...) { // do nothing, fail_pair second already false LOG_TOPIC("3e591", DEBUG, Logger::SUPERVISION) << "hasAsNode had exception processing " << url; } // catch return fail_pair; } // hasAsNode std::pair Node::hasAsWritableNode(std::string const& url) { // *this is bogus initializer std::pair fail_pair = {*this, false}; // retrieve node, throws if does not exist try { Node& target(operator()(url)); std::pair good_pair = {target, true}; return good_pair; } catch (...) { // do nothing, fail_pair second already false LOG_TOPIC("027ed", DEBUG, Logger::SUPERVISION) << "hasAsWritableNode had exception processing " << url; } // catch return fail_pair; } // hasAsWritableNode std::pair Node::hasAsType(std::string const& url) const { std::pair ret_pair = {NODE, false}; // retrieve node, throws if does not exist try { Node const& target(operator()(url)); ret_pair.first = target.type(); ret_pair.second = true; } catch (...) { // do nothing, fail_pair second already false LOG_TOPIC("7f66c", DEBUG, Logger::SUPERVISION) << "hasAsType had exception processing " << url; } // catch return ret_pair; } // hasAsType std::pair Node::hasAsSlice(std::string const& url) const { // *this is bogus initializer std::pair ret_pair = {arangodb::velocypack::Slice::emptyObjectSlice(), false}; // retrieve node, throws if does not exist try { Node const& target(operator()(url)); ret_pair.first = target.slice(); ret_pair.second = true; } catch (...) { // do nothing, ret_pair second already false LOG_TOPIC("16f3d", TRACE, Logger::SUPERVISION) << "hasAsSlice had exception processing " << url; } // catch return ret_pair; } // hasAsSlice std::pair Node::hasAsUInt(std::string const& url) const { std::pair ret_pair(0, false); // retrieve node, throws if does not exist try { Node const& target(operator()(url)); if (target.isNumber()) { ret_pair.first = target.getUInt(); ret_pair.second = true; } } catch (...) { // do nothing, ret_pair second already false LOG_TOPIC("eaa6b", DEBUG, Logger::SUPERVISION) << "hasAsUInt had exception processing " << url; } // catch return ret_pair; } // hasAsUInt std::pair Node::hasAsBool(std::string const& url) const { std::pair ret_pair(false, false); // retrieve node, throws if does not exist try { Node const& target(operator()(url)); if (target.isBool()) { ret_pair.first = target.getBool(); ret_pair.second = true; } } catch (...) { // do nothing, ret_pair second already false LOG_TOPIC("99238", DEBUG, Logger::SUPERVISION) << "hasAsBool had exception processing " << url; } // catch return ret_pair; } // hasAsBool std::pair Node::hasAsString(std::string const& url) const { std::pair ret_pair; ret_pair.second = false; // retrieve node, throws if does not exist try { Node const& target(operator()(url)); if (target.isString()) { ret_pair.first = target.getString(); ret_pair.second = true; } } catch (...) { // do nothing, ret_pair second already false LOG_TOPIC("fd020", DEBUG, Logger::SUPERVISION) << "hasAsString had exception processing " << url; } // catch return ret_pair; } // hasAsString std::pair Node::hasAsChildren(std::string const& url) const { // retrieve node, throws if does not exist try { Node const& target(operator()(url)); return std::pair {target.children(), true}; } catch (...) { LOG_TOPIC("4e3cb", DEBUG, Logger::SUPERVISION) << "hasAsChildren had exception processing " << url; } // catch return std::pair {dummyChildren, false}; } // hasAsChildren std::pair Node::hasAsBuilder(std::string const& url, Builder& builder, bool showHidden) const { std::pair ret_pair(nullptr, false); // retrieve node, throws if does not exist try { Node const& target(operator()(url)); target.toBuilder(builder, showHidden); ret_pair.second = true; } catch (...) { // do nothing, ret_pair second already false LOG_TOPIC("fb685", DEBUG, Logger::SUPERVISION) << "hasAsBuilder(1) had exception processing " << url; } // catch return ret_pair; } // hasAsBuilder std::pair Node::hasAsBuilder(std::string const& url) const { Builder builder; std::pair ret_pair(builder, false); // retrieve node, throws if does not exist try { Node const& target(operator()(url)); target.toBuilder(builder); ret_pair.first = builder; // update ret_pair.second = true; } catch (...) { // do nothing, ret_pair second already false LOG_TOPIC("68a75", DEBUG, Logger::SUPERVISION) << "hasAsBuilder(2) had exception processing " << url; } // catch return ret_pair; } // hasAsBuilder std::pair Node::hasAsArray(std::string const& url) const { // *this is bogus initializer std::pair ret_pair = {arangodb::velocypack::Slice::emptyObjectSlice(), false}; // retrieve node, throws if does not exist try { Node const& target(operator()(url)); ret_pair.first = target.getArray(); ret_pair.second = true; } catch (...) { // do nothing, ret_pair second already false LOG_TOPIC("0a72b", DEBUG, Logger::SUPERVISION) << "hasAsArray had exception processing " << url; } // catch return ret_pair; } // hasAsArray std::string Node::getString() const { if (type() == NODE) { throw StoreException("Must not convert NODE type to string"); } return slice().copyString(); } Slice Node::getArray() const { if (type() == NODE) { throw StoreException("Must not convert NODE type to array"); } if (!_isArray) { throw StoreException("Not an array type"); } rebuildVecBuf(); return Slice(_vecBuf.data()); } void Node::clear() { _children.clear(); _ttl = std::chrono::system_clock::time_point(); _value.clear(); _vecBuf.clear(); _vecBufDirty = true; _isArray = false; }