From 55fce39574b0346abce9f4ea0f17d8f13a79aba2 Mon Sep 17 00:00:00 2001 From: jsteemann Date: Mon, 20 Jun 2016 18:37:46 +0200 Subject: [PATCH] optimizations for neighbors queries --- arangod/Aql/OptimizerRules.cpp | 50 ++++++- arangod/Aql/TraversalBlock.cpp | 172 +++++++++++++++++++++- arangod/Aql/TraversalBlock.h | 12 ++ arangod/Aql/TraversalNode.cpp | 40 ++++- arangod/Aql/TraversalNode.h | 23 ++- arangod/Utils/Transaction.cpp | 8 + arangod/Utils/Transaction.h | 10 ++ arangod/VocBase/SingleServerTraverser.cpp | 3 +- arangod/VocBase/SingleServerTraverser.h | 7 - arangod/VocBase/Traverser.cpp | 4 +- arangod/VocBase/Traverser.h | 6 +- lib/Basics/StringRef.h | 171 ++++++++++++++++++--- 12 files changed, 465 insertions(+), 41 deletions(-) diff --git a/arangod/Aql/OptimizerRules.cpp b/arangod/Aql/OptimizerRules.cpp index 16f478ec0b..5947e0c2c4 100644 --- a/arangod/Aql/OptimizerRules.cpp +++ b/arangod/Aql/OptimizerRules.cpp @@ -3630,7 +3630,7 @@ void arangodb::aql::optimizeTraversalsRule(Optimizer* opt, auto outVariable = traversal->edgeOutVariable(); if (outVariable != nullptr && varsUsedLater.find(outVariable) == varsUsedLater.end()) { - // traversal vertex outVariable not used later + // traversal edge outVariable not used later traversal->setEdgeOutput(nullptr); modified = true; } @@ -3638,7 +3638,7 @@ void arangodb::aql::optimizeTraversalsRule(Optimizer* opt, outVariable = traversal->pathOutVariable(); if (outVariable != nullptr && varsUsedLater.find(outVariable) == varsUsedLater.end()) { - // traversal vertex outVariable not used later + // traversal path outVariable not used later traversal->setPathOutput(nullptr); modified = true; } @@ -3655,6 +3655,52 @@ void arangodb::aql::optimizeTraversalsRule(Optimizer* opt, n->walk(&finder); } } + + + // now check if we can use an optimized version of the neighbors search... + if (!arangodb::ServerState::instance()->isRunningInCluster()) { + for (auto const& n : tNodes) { + TraversalNode* traversal = static_cast(n); + + if (traversal->edgeOutVariable() != nullptr || + traversal->pathOutVariable() != nullptr) { + // traversal produces edges or paths + continue; + } + + if (traversal->maxDepth() > 100) { + // neighbors search is recursive... do not use recursive version if + // depth is potentially high + continue; + } + + if (!traversal->expressions()->empty()) { + // traversal has filter expressions + continue; + } + + if (!traversal->allDirectionsEqual()) { + // not all directions are equal + continue; + } + + TraversalOptions const* options = traversal->options(); + TRI_ASSERT(options != nullptr); + + if (options->uniqueVertices != traverser::TraverserOptions::GLOBAL || + options->uniqueEdges != traverser::TraverserOptions::NONE) { + // neighbors search is hard-coded to global vertex uniqueness + continue; + } + + if (!options->useBreadthFirst) { + continue; + } + + traversal->specializeToNeighborsSearch(); + modified = true; + } + } opt->addPlan(plan, rule, modified); } diff --git a/arangod/Aql/TraversalBlock.cpp b/arangod/Aql/TraversalBlock.cpp index 07b6d1e307..0eee017a99 100644 --- a/arangod/Aql/TraversalBlock.cpp +++ b/arangod/Aql/TraversalBlock.cpp @@ -28,7 +28,10 @@ #include "Aql/ExecutionPlan.h" #include "Aql/Functions.h" #include "Basics/ScopeGuard.h" +#include "Basics/StringRef.h" #include "Cluster/ClusterTraverser.h" +#include "Utils/OperationCursor.h" +#include "Utils/Transaction.h" #include "VocBase/SingleServerTraverser.h" #include "V8/v8-globals.h" @@ -261,6 +264,12 @@ int TraversalBlock::initializeCursor(AqlItemBlock* items, size_t pos) { /// @brief read more paths bool TraversalBlock::morePaths(size_t hint) { DEBUG_BEGIN_BLOCK(); + + TraversalNode const* planNode = static_cast(getPlanNode()); + if (planNode->_specializedNeighborsSearch) { + return false; + } + freeCaches(); _posInPaths = 0; if (!_traverser->hasMore()) { @@ -268,7 +277,7 @@ bool TraversalBlock::morePaths(size_t hint) { _engine->_stats.filtered += _traverser->getAndResetFilteredPaths(); return false; } - + if (usesVertexOutput()) { _vertices.reserve(hint); } @@ -315,6 +324,8 @@ bool TraversalBlock::morePaths(size_t hint) { /// @brief skip the next paths size_t TraversalBlock::skipPaths(size_t hint) { DEBUG_BEGIN_BLOCK(); + TRI_ASSERT(!static_cast(getPlanNode())->_specializedNeighborsSearch); + freeCaches(); _posInPaths = 0; if (!_traverser->hasMore()) { @@ -331,6 +342,9 @@ void TraversalBlock::initializePaths(AqlItemBlock const* items) { // No Initialization required. return; } + + TraversalNode const* planNode = static_cast(getPlanNode()); + if (!_useRegister) { if (!_usedConstant) { _usedConstant = true; @@ -341,22 +355,36 @@ void TraversalBlock::initializePaths(AqlItemBlock const* items) { "Only id strings or objects with " "_id are allowed"); } else { - _traverser->setStartVertex(_vertexId); + if (planNode->_specializedNeighborsSearch) { + // fetch neighbor nodes + neighbors(_vertexId); + } else { + _traverser->setStartVertex(_vertexId); + } } } } else { AqlValue const& in = items->getValueReference(_pos, _reg); if (in.isObject()) { try { - std::string idString = _trx->extractIdString(in.slice()); - _traverser->setStartVertex(idString); + if (planNode->_specializedNeighborsSearch) { + // fetch neighbor nodes + neighbors(_trx->extractIdString(in.slice())); + } else { + _traverser->setStartVertex(_trx->extractIdString(in.slice())); + } } catch (...) { // _id or _key not present... ignore this error and fall through } } else if (in.isString()) { _vertexId = in.slice().copyString(); - _traverser->setStartVertex(_vertexId); + if (planNode->_specializedNeighborsSearch) { + // fetch neighbor nodes + neighbors(_vertexId); + } else { + _traverser->setStartVertex(_vertexId); + } } else { _engine->getQuery()->registerWarning( TRI_ERROR_BAD_PARAMETER, "Invalid input for traversal: Only " @@ -511,3 +539,137 @@ size_t TraversalBlock::skipSome(size_t atLeast, size_t atMost) { return atMost; DEBUG_END_BLOCK(); } + +/// @brief optimized version of neighbors search, must properly implement this +void TraversalBlock::neighbors(std::string const& startVertex) { + std::unordered_set visited; + + std::vector result; + result.reserve(1000); + + TransactionBuilderLeaser builder(_trx); + builder->add(VPackValue(startVertex)); + + std::vector startVertices; + startVertices.emplace_back(builder->slice()); + visited.emplace(builder->slice()); + + TRI_edge_direction_e direction = TRI_EDGE_ANY; + std::string collectionName; + traverser::TraverserOptions const* options = _traverser->options(); + if (options->getCollection(0, collectionName, direction)) { + runNeighbors(startVertices, visited, result, direction, 1); + } + + TRI_doc_mptr_t mptr; + _vertices.clear(); + _vertices.reserve(result.size()); + for (auto const& it : result) { + VPackValueLength l; + char const* p = it.getString(l); + StringRef ref(p, l); + + size_t pos = ref.find('/'); + if (pos == std::string::npos) { + // invalid id + continue; + } + + int res = _trx->documentFastPathLocal(ref.substr(0, pos).toString(), ref.substr(pos + 1).toString(), &mptr); + + if (res != TRI_ERROR_NO_ERROR) { + continue; + } + + _vertices.emplace_back(AqlValue(mptr.vpack())); + } +} + +/// @brief worker for neighbors() function +void TraversalBlock::runNeighbors(std::vector const& startVertices, + std::unordered_set& visited, + std::vector& distinct, + TRI_edge_direction_e direction, + uint64_t depth) { + std::vector nextDepth; + bool initialized = false; + TraversalNode const* node = static_cast(getPlanNode()); + traverser::TraverserOptions const* options = _traverser->options(); + + TransactionBuilderLeaser builder(_trx); + + size_t const n = options->collectionCount(); + std::string collectionName; + Transaction::IndexHandle indexHandle; + + std::vector edges; + + for (auto const& startVertex : startVertices) { + for (size_t i = 0; i < n; ++i) { + builder->clear(); + + if (!options->getCollectionAndSearchValue(i, startVertex.copyString(), collectionName, indexHandle, *builder.builder())) { + TRI_ASSERT(false); + } + + std::shared_ptr cursor = _trx->indexScan(collectionName, + arangodb::Transaction::CursorType::INDEX, indexHandle, + builder->slice(), 0, UINT64_MAX, 1000, false); + + if (cursor->failed()) { + continue; + } + + edges.clear(); + while (cursor->hasMore()) { + cursor->getMoreMptr(edges, 1000); + + for (auto const& it : edges) { + VPackSlice edge(it->vpack()); + VPackSlice v; + if (direction == TRI_EDGE_IN) { + v = Transaction::extractFromFromDocument(edge); + } else { + v = Transaction::extractToFromDocument(edge); + } + + if (visited.find(v) == visited.end()) { + // we have not yet visited this vertex + if (depth >= node->minDepth()) { + distinct.emplace_back(v); + } + if (depth < node->maxDepth()) { + if (!initialized) { + nextDepth.reserve(64); + initialized = true; + } + nextDepth.emplace_back(v); + } + visited.emplace(v); + continue; + } else if (direction == TRI_EDGE_ANY) { + v = Transaction::extractToFromDocument(edge); + if (visited.find(v) == visited.end()) { + // we have not yet visited this vertex + if (depth >= node->minDepth()) { + distinct.emplace_back(v); + } + if (depth < node->maxDepth()) { + if (!initialized) { + nextDepth.reserve(64); + initialized = true; + } + nextDepth.emplace_back(v); + } + visited.emplace(v); + } + } + } + } + } + } + + if (!nextDepth.empty()) { + runNeighbors(nextDepth, visited, distinct, direction, depth + 1); + } +} diff --git a/arangod/Aql/TraversalBlock.h b/arangod/Aql/TraversalBlock.h index 03add97e21..b3fbb58a4f 100644 --- a/arangod/Aql/TraversalBlock.h +++ b/arangod/Aql/TraversalBlock.h @@ -26,6 +26,7 @@ #include "Aql/ExecutionBlock.h" #include "Aql/TraversalNode.h" +#include "Basics/VelocyPackHelper.h" #include "VocBase/Traverser.h" namespace arangodb { @@ -146,6 +147,17 @@ class TraversalBlock : public ExecutionBlock { /// @brief Executes the path-local filter expressions /// Also determines the context void executeFilterExpressions(); + + /// @brief optimized version of neighbors search, must properly implement this + void neighbors(std::string const& startVertex); + + /// @brief worker for neighbors() function + void runNeighbors(std::vector const& startVertices, + std::unordered_set& visited, + std::vector& distinct, + TRI_edge_direction_e direction, + uint64_t depth); + }; } // namespace arangodb::aql } // namespace arangodb diff --git a/arangod/Aql/TraversalNode.cpp b/arangod/Aql/TraversalNode.cpp index b3d6b2fe2b..e5f29b7efe 100644 --- a/arangod/Aql/TraversalNode.cpp +++ b/arangod/Aql/TraversalNode.cpp @@ -135,7 +135,8 @@ TraversalNode::TraversalNode(ExecutionPlan* plan, size_t id, _inVariable(nullptr), _graphObj(nullptr), _condition(nullptr), - _options(options) { + _options(options), + _specializedNeighborsSearch(false) { TRI_ASSERT(_vocbase != nullptr); TRI_ASSERT(direction != nullptr); @@ -268,7 +269,8 @@ TraversalNode::TraversalNode( _directions(directions), _graphObj(nullptr), _condition(nullptr), - _options(options) { + _options(options), + _specializedNeighborsSearch(false) { _graphJson = arangodb::basics::Json(arangodb::basics::Json::Array, edgeColls.size()); for (auto& it : edgeColls) { @@ -286,7 +288,8 @@ TraversalNode::TraversalNode(ExecutionPlan* plan, _pathOutVariable(nullptr), _inVariable(nullptr), _graphObj(nullptr), - _condition(nullptr) { + _condition(nullptr), + _specializedNeighborsSearch(false) { _minDepth = arangodb::basics::JsonHelper::stringUInt64(base.json(), "minDepth"); _maxDepth = @@ -454,6 +457,7 @@ TraversalNode::TraversalNode(ExecutionPlan* plan, _options = TraversalOptions(base); } + _specializedNeighborsSearch = arangodb::basics::JsonHelper::getBooleanValue(base.json(), "specializedNeighborsSearch", false); } int TraversalNode::checkIsOutVariable(size_t variableId) const { @@ -469,6 +473,30 @@ int TraversalNode::checkIsOutVariable(size_t variableId) const { return -1; } +/// @brief check if all directions are equal +bool TraversalNode::allDirectionsEqual() const { + if (_directions.empty()) { + // no directions! + return false; + } + size_t const n = _directions.size(); + TRI_edge_direction_e const expected = _directions[0]; + + for (size_t i = 1; i < n; ++i) { + if (_directions[i] != expected) { + return false; + } + } + return true; +} + +void TraversalNode::specializeToNeighborsSearch() { + TRI_ASSERT(allDirectionsEqual()); + TRI_ASSERT(!_directions.empty()); + + _specializedNeighborsSearch = true; +} + /// @brief toVelocyPack, for TraversalNode void TraversalNode::toVelocyPackHelper(arangodb::velocypack::Builder& nodes, bool verbose) const { @@ -539,6 +567,8 @@ void TraversalNode::toVelocyPackHelper(arangodb::velocypack::Builder& nodes, } } } + + nodes.add("specializedNeighborsSearch", VPackValue(_specializedNeighborsSearch)); nodes.add(VPackValue("traversalFlags")); _options.toVelocyPack(nodes); @@ -584,6 +614,10 @@ ExecutionNode* TraversalNode::clone(ExecutionPlan* plan, bool withDependencies, c->setPathOutput(pathOutVariable); } + if (_specializedNeighborsSearch) { + c->specializeToNeighborsSearch(); + } + cloneHelper(c, plan, withDependencies, withProperties); return static_cast(c); diff --git a/arangod/Aql/TraversalNode.h b/arangod/Aql/TraversalNode.h index 34ca63f9a7..ef1a2a4c9f 100644 --- a/arangod/Aql/TraversalNode.h +++ b/arangod/Aql/TraversalNode.h @@ -61,7 +61,7 @@ class SimpleTraverserExpression /// @brief class TraversalNode class TraversalNode : public ExecutionNode { friend class ExecutionBlock; - friend class TraversalCollectionBlock; + friend class TraversalBlock; friend class RedundantCalculationsReplacer; /// @brief constructor with a vocbase and a collection name @@ -134,6 +134,14 @@ class TraversalNode : public ExecutionNode { /// @brief getVariablesSetHere std::vector getVariablesSetHere() const override final { std::vector vars; + + size_t const numVars = + (_vertexOutVariable != nullptr ? 1 : 0) + + (_edgeOutVariable != nullptr ? 1 : 0) + + (_pathOutVariable != nullptr ? 1 : 0); + + vars.reserve(numVars); + if (_vertexOutVariable != nullptr) { vars.emplace_back(_vertexOutVariable); } @@ -211,6 +219,10 @@ class TraversalNode : public ExecutionNode { void storeSimpleExpression(bool isEdgeAccess, size_t indexAccess, AstNodeType comparisonType, AstNode const* varAccess, AstNode* compareToNode); + + bool allDirectionsEqual() const; + + void specializeToNeighborsSearch(); /// @brief Returns a regerence to the simple traverser expressions std::unordered_map< @@ -219,6 +231,11 @@ class TraversalNode : public ExecutionNode { return &_expressions; } + uint64_t minDepth() const { return _minDepth; } + uint64_t maxDepth() const { return _maxDepth; } + + TraversalOptions const* options() const { return &_options; } + private: /// @brief the database TRI_vocbase_t* _vocbase; @@ -238,7 +255,7 @@ class TraversalNode : public ExecutionNode { /// @brief input vertexId only used if _inVariable is unused std::string _vertexId; - /// @brief input graphJson only used for serialisation & info + /// @brief input graphJson only used for serialization & info arangodb::basics::Json _graphJson; /// @brief The minimal depth included in the result @@ -270,6 +287,8 @@ class TraversalNode : public ExecutionNode { /// @brief Options for traversals TraversalOptions _options; + + bool _specializedNeighborsSearch; }; } // namespace arangodb::aql diff --git a/arangod/Utils/Transaction.cpp b/arangod/Utils/Transaction.cpp index 11157283fe..4eb93df248 100644 --- a/arangod/Utils/Transaction.cpp +++ b/arangod/Utils/Transaction.cpp @@ -575,6 +575,10 @@ DocumentDitch* Transaction::orderDitch(TRI_voc_cid_t cid) { TRI_ASSERT(getStatus() == TRI_TRANSACTION_RUNNING || getStatus() == TRI_TRANSACTION_CREATED); + if (_ditchCache.cid == cid) { + return _ditchCache.ditch; + } + TRI_transaction_collection_t* trxCollection = TRI_GetCollectionTransaction(_trx, cid, TRI_TRANSACTION_READ); if (trxCollection == nullptr) { @@ -592,6 +596,10 @@ DocumentDitch* Transaction::orderDitch(TRI_voc_cid_t cid) { if (ditch == nullptr) { THROW_ARANGO_EXCEPTION(TRI_ERROR_OUT_OF_MEMORY); } + + _ditchCache.cid = cid; + _ditchCache.ditch = ditch; + return ditch; } diff --git a/arangod/Utils/Transaction.h b/arangod/Utils/Transaction.h index e84c9d78d9..1f07cf8dd0 100644 --- a/arangod/Utils/Transaction.h +++ b/arangod/Utils/Transaction.h @@ -955,6 +955,16 @@ class Transaction { ////////////////////////////////////////////////////////////////////////////// std::shared_ptr _transactionContext; + + ////////////////////////////////////////////////////////////////////////////// + /// @brief cache for last handed out DocumentDitch + ////////////////////////////////////////////////////////////////////////////// + + struct { + TRI_voc_cid_t cid = 0; + DocumentDitch* ditch = nullptr; + } + _ditchCache; public: ////////////////////////////////////////////////////////////////////////////// diff --git a/arangod/VocBase/SingleServerTraverser.cpp b/arangod/VocBase/SingleServerTraverser.cpp index b386a6465e..d222972c5b 100644 --- a/arangod/VocBase/SingleServerTraverser.cpp +++ b/arangod/VocBase/SingleServerTraverser.cpp @@ -465,7 +465,7 @@ void SingleServerTraverser::EdgeGetter::getAllEdges( if (!_traverser->edgeMatchesConditions(edge, depth)) { if (_opts.uniqueEdges == TraverserOptions::UniquenessLevel::GLOBAL) { // Insert a dummy to please the uniqueness - _traverser->_edges.emplace(id, nullptr); + _traverser->_edges.emplace(std::move(id), nullptr); } continue; } @@ -489,3 +489,4 @@ void SingleServerTraverser::EdgeGetter::getAllEdges( } } } + diff --git a/arangod/VocBase/SingleServerTraverser.h b/arangod/VocBase/SingleServerTraverser.h index 20debe7234..024da24c4b 100644 --- a/arangod/VocBase/SingleServerTraverser.h +++ b/arangod/VocBase/SingleServerTraverser.h @@ -111,13 +111,6 @@ class SingleServerTraverser final : public Traverser { SingleServerTraverser* _traverser; - ////////////////////////////////////////////////////////////////////////////// - /// @brief Cache for indexes. Maps collectionName to Index - ////////////////////////////////////////////////////////////////////////////// - - std::unordered_map> - _indexCache; - ////////////////////////////////////////////////////////////////////////////// /// @brief Traverser options ////////////////////////////////////////////////////////////////////////////// diff --git a/arangod/VocBase/Traverser.cpp b/arangod/VocBase/Traverser.cpp index caa7ed00f2..2b24916fa4 100644 --- a/arangod/VocBase/Traverser.cpp +++ b/arangod/VocBase/Traverser.cpp @@ -104,7 +104,7 @@ size_t arangodb::traverser::TraverserOptions::collectionCount () const { } bool arangodb::traverser::TraverserOptions::getCollection( - size_t const index, std::string& name, TRI_edge_direction_e& dir) const { + size_t index, std::string& name, TRI_edge_direction_e& dir) const { if (index >= _collections.size()) { // No more collections stop now return false; @@ -121,7 +121,7 @@ bool arangodb::traverser::TraverserOptions::getCollection( bool arangodb::traverser::TraverserOptions::getCollectionAndSearchValue( size_t index, std::string const& vertexId, std::string& name, - Transaction::IndexHandle& indexHandle, VPackBuilder& builder) { + Transaction::IndexHandle& indexHandle, VPackBuilder& builder) const { if (index >= _collections.size()) { // No more collections stop now return false; diff --git a/arangod/VocBase/Traverser.h b/arangod/VocBase/Traverser.h index 9295bc8bc8..e783593282 100644 --- a/arangod/VocBase/Traverser.h +++ b/arangod/VocBase/Traverser.h @@ -231,11 +231,11 @@ struct TraverserOptions { size_t collectionCount() const; - bool getCollection(size_t const, std::string&, TRI_edge_direction_e&) const; + bool getCollection(size_t, std::string&, TRI_edge_direction_e&) const; bool getCollectionAndSearchValue(size_t, std::string const&, std::string&, arangodb::Transaction::IndexHandle&, - arangodb::velocypack::Builder&); + arangodb::velocypack::Builder&) const; }; class Traverser { @@ -322,6 +322,8 @@ class Traverser { _readDocuments = 0; return tmp; } + + TraverserOptions const* options() { return &_opts; } ////////////////////////////////////////////////////////////////////////////// /// @brief Prune the current path prefix. Do not evaluate it any further. diff --git a/lib/Basics/StringRef.h b/lib/Basics/StringRef.h index 8ee5737ff0..87ec473151 100644 --- a/lib/Basics/StringRef.h +++ b/lib/Basics/StringRef.h @@ -27,47 +27,184 @@ #include "Basics/Common.h" #include "Basics/xxhash.h" +#include +#include + namespace arangodb { /// @brief a struct describing a C character array /// not responsible for memory management! -struct StringRef { - StringRef() : data(""), length(0) {} - explicit StringRef(std::string const& str) : data(str.c_str()), length(str.size()) {} - explicit StringRef(char const* data) : data(data), length(strlen(data)) {} - StringRef(char const* data, size_t length) : data(data), length(length) {} +class StringRef { + public: + /// @brief create an empty StringRef + StringRef() : _data(""), _length(0) {} - bool operator==(StringRef const& other) const { - return (length == other.length && memcmp(data, other.data, length) == 0); + /// @brief create a StringRef from an std::string + explicit StringRef(std::string const& str) : _data(str.c_str()), _length(str.size()) {} + + /// @brief create a StringRef from a null-terminated C string + explicit StringRef(char const* data) : _data(data), _length(strlen(data)) {} + + /// @brief create a StringRef from a VPack slice (must be of type String) + explicit StringRef(arangodb::velocypack::Slice const& slice) : StringRef() { + arangodb::velocypack::ValueLength l; + _data = slice.getString(l); + _length = l; } - bool operator==(std::string const& other) const { - return (length == other.size() && memcmp(data, other.c_str(), length) == 0); + /// @brief create a StringRef from a C string plus length + StringRef(char const* data, size_t length) : _data(data), _length(length) {} + + /// @brief create a StringRef from another StringRef + StringRef(StringRef const& other) + : _data(other._data), _length(other._length) {} + + /// @brief create a StringRef from another StringRef + StringRef& operator=(StringRef const& other) { + _data = other._data; + _length = other._length; + return *this; + } + + /// @brief create a StringRef from an std::string + StringRef& operator=(std::string const& other) { + _data = other.c_str(); + _length = other.size(); + return *this; + } + + /// @brief create a StringRef from a null-terminated C string + StringRef& operator=(char const* other) { + _data = other; + _length = strlen(other); + return *this; + } + + /// @brief create a StringRef from a VPack slice of type String + StringRef& operator=(arangodb::velocypack::Slice const& slice) { + arangodb::velocypack::ValueLength l; + _data = slice.getString(l); + _length = l; + return *this; } - char const* data; - size_t length; + size_t find(char c) const { + char const* p = static_cast(memchr(static_cast(_data), c, _length)); + + if (p == nullptr) { + return std::string::npos; + } + + return (p - _data); + } + + StringRef substr(size_t pos = 0, size_t count = std::string::npos) const { + if (pos >= _length) { + throw std::out_of_range("substr index out of bounds"); + } + if (count == std::string::npos || (count + pos >= _length)) { + count = _length - pos; + } + return StringRef(_data + pos, count); + } + + int compare(std::string const& other) const { + int res = memcmp(_data, other.c_str(), (std::min)(_length, other.size())); + if (res != 0) { + return res; + } + return (_length - other.size()); + } + + int compare(StringRef const& other) const { + int res = memcmp(_data, other._data, (std::min)(_length, other._length)); + if (res != 0) { + return res; + } + return (_length - other._length); + } + + inline std::string toString() const { + return std::string(_data, _length); + } + + inline bool empty() const { + return (_length == 0); + } + + char at(size_t index) const { + if (index >= _length) { + throw std::out_of_range("StringRef index out of bounds"); + } + return operator[](index); + } + + inline char const* begin() const { + return _data; + } + + inline char const* end() const { + return _data + _length; + } + + inline char front() const { return _data[0]; } + + inline char back() const { return _data[_length - 1]; } + + inline char operator[](size_t index) const noexcept { + return _data[index]; + } + + inline char const* data() const noexcept { + return _data; + } + + inline size_t size() const noexcept { + return _length; + } + + inline size_t length() const noexcept { + return _length; + } + + private: + char const* _data; + size_t _length; }; } +inline bool operator==(arangodb::StringRef const& lhs, arangodb::StringRef const& rhs) { + return (lhs.size() == rhs.size() && memcmp(lhs.data(), rhs.data(), lhs.size()) == 0); +} + +inline bool operator!=(arangodb::StringRef const& lhs, arangodb::StringRef const& rhs) { + return !(lhs == rhs); +} + +inline bool operator==(arangodb::StringRef const& lhs, std::string const& rhs) { + return (lhs.size() == rhs.size() && memcmp(lhs.data(), rhs.c_str(), lhs.size()) == 0); +} + +inline bool operator!=(arangodb::StringRef const& lhs, std::string const& rhs) { + return !(lhs == rhs); +} + namespace std { template <> struct hash { size_t operator()(arangodb::StringRef const& value) const noexcept { - return XXH64(value.data, value.length, 0xdeadbeef); + return XXH64(value.data(), value.size(), 0xdeadbeef); } }; template <> struct equal_to { bool operator()(arangodb::StringRef const& lhs, - arangodb::StringRef const& rhs) const { - if (lhs.length != rhs.length) { - return false; - } - return (memcmp(lhs.data, rhs.data, lhs.length) == 0); + arangodb::StringRef const& rhs) const noexcept { + return (lhs.size() == rhs.size() && + (memcmp(lhs.data(), rhs.data(), lhs.size()) == 0)); } };