//////////////////////////////////////////////////////////////////////////////// /// @brief hash index /// /// @file /// /// DISCLAIMER /// /// Copyright 2014 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 Dr. Frank Celler /// @author Copyright 2014, ArangoDB GmbH, Cologne, Germany /// @author Copyright 2011-2013, triAGENS GmbH, Cologne, Germany //////////////////////////////////////////////////////////////////////////////// #include "HashIndex.h" #include "Aql/Ast.h" #include "Aql/AstNode.h" #include "Aql/SortCondition.h" #include "Basics/Exceptions.h" #include "Indexes/SimpleAttributeEqualityMatcher.h" #include "VocBase/transaction.h" #include "VocBase/VocShaper.h" using namespace triagens::arango; // ----------------------------------------------------------------------------- // --SECTION-- private functions // ----------------------------------------------------------------------------- //////////////////////////////////////////////////////////////////////////////// /// @brief Frees an index element //////////////////////////////////////////////////////////////////////////////// static void FreeElement(TRI_index_element_t* element) { TRI_index_element_t::free(element); } //////////////////////////////////////////////////////////////////////////////// /// @brief determines if two elements are equal //////////////////////////////////////////////////////////////////////////////// static bool IsEqualElementElement (TRI_index_element_t const* left, TRI_index_element_t const* right) { return left->document() == right->document(); } //////////////////////////////////////////////////////////////////////////////// /// @brief given a key generates a hash integer //////////////////////////////////////////////////////////////////////////////// static uint64_t HashKey (TRI_hash_index_search_value_t const* key) { uint64_t hash = 0x0123456789abcdef; for (size_t j = 0; j < key->_length; ++j) { // ignore the sid for hashing hash = fasthash64(key->_values[j]._data.data, key->_values[j]._data.length, hash); } return hash; } //////////////////////////////////////////////////////////////////////////////// /// @brief determines if a key corresponds to an element //////////////////////////////////////////////////////////////////////////////// static bool IsEqualKeyElement (TRI_hash_index_search_value_t const* left, TRI_index_element_t const* right) { TRI_ASSERT_EXPENSIVE(right->document() != nullptr); for (size_t j = 0; j < left->_length; ++j) { TRI_shaped_json_t* leftJson = &left->_values[j]; TRI_shaped_sub_t* rightSub = &right->subObjects()[j]; if (leftJson->_sid != rightSub->_sid) { return false; } auto length = leftJson->_data.length; char const* rightData; size_t rightLength; TRI_InspectShapedSub(rightSub, right->document(), rightData, rightLength); if (length != rightLength) { return false; } if (length > 0 && memcmp(leftJson->_data.data, rightData, length) != 0) { return false; } } return true; } static bool IsEqualKeyElementHash (TRI_hash_index_search_value_t const* left, uint64_t const hash, // Has been computed but is not used here TRI_index_element_t const* right) { return IsEqualKeyElement(left, right); } // ----------------------------------------------------------------------------- // --SECTION-- class HashIndexIterator // ----------------------------------------------------------------------------- // ----------------------------------------------------------------------------- // --SECTION-- public methods // ----------------------------------------------------------------------------- TRI_doc_mptr_t* HashIndexIterator::next () { while (true) { if (_posInBuffer >= _buffer.size()) { if (_position >= _keys.size()) { // we're at the end of the lookup values return nullptr; } // We have to refill the buffer _buffer.clear(); _posInBuffer = 0; int res = _index->lookup(_trx, _keys[_position++], _buffer); if (res != TRI_ERROR_NO_ERROR) { THROW_ARANGO_EXCEPTION(res); } } if (! _buffer.empty()) { // found something return _buffer.at(_posInBuffer++); } } } void HashIndexIterator::reset () { _buffer.clear(); _position = 0; _posInBuffer = 0; } // ----------------------------------------------------------------------------- // --SECTION-- class HashIndex::UniqueArray // ----------------------------------------------------------------------------- //////////////////////////////////////////////////////////////////////////////// /// @brief create the unique array //////////////////////////////////////////////////////////////////////////////// HashIndex::UniqueArray::UniqueArray (TRI_HashArray_t* hashArray, HashElementFunc* hashElement, IsEqualElementElementByKey* isEqualElElByKey) : _hashArray(hashArray), _hashElement(hashElement), _isEqualElElByKey(isEqualElElByKey) { TRI_ASSERT(_hashArray != nullptr); TRI_ASSERT(_hashElement != nullptr); TRI_ASSERT(_isEqualElElByKey != nullptr); } //////////////////////////////////////////////////////////////////////////////// /// @brief destroy the unique array //////////////////////////////////////////////////////////////////////////////// HashIndex::UniqueArray::~UniqueArray () { if (_hashArray != nullptr) { _hashArray->invokeOnAllElements(FreeElement); } delete _hashArray; delete _hashElement; delete _isEqualElElByKey; } // ----------------------------------------------------------------------------- // --SECTION-- class HashIndex::MultiArray // ----------------------------------------------------------------------------- //////////////////////////////////////////////////////////////////////////////// /// @brief create the multi array //////////////////////////////////////////////////////////////////////////////// HashIndex::MultiArray::MultiArray (TRI_HashArrayMulti_t* hashArray, HashElementFunc* hashElement, IsEqualElementElementByKey* isEqualElElByKey) : _hashArray(hashArray), _hashElement(hashElement), _isEqualElElByKey(isEqualElElByKey) { TRI_ASSERT(_hashArray != nullptr); TRI_ASSERT(_hashElement != nullptr); TRI_ASSERT(_isEqualElElByKey != nullptr); } //////////////////////////////////////////////////////////////////////////////// /// @brief destroy the multi array //////////////////////////////////////////////////////////////////////////////// HashIndex::MultiArray::~MultiArray () { if (_hashArray != nullptr) { _hashArray->invokeOnAllElements(FreeElement); } delete _hashArray; delete _hashElement; delete _isEqualElElByKey; } // ----------------------------------------------------------------------------- // --SECTION-- struct TRI_hash_index_search_value_t // ----------------------------------------------------------------------------- //////////////////////////////////////////////////////////////////////////////// /// @brief create an index search value //////////////////////////////////////////////////////////////////////////////// TRI_hash_index_search_value_t::TRI_hash_index_search_value_t () : _length(0), _values(nullptr) { } TRI_hash_index_search_value_t::~TRI_hash_index_search_value_t () { destroy(); } void TRI_hash_index_search_value_t::reserve (size_t n) { TRI_ASSERT(_values == nullptr); _values = static_cast(TRI_Allocate(TRI_UNKNOWN_MEM_ZONE, n * sizeof(TRI_shaped_json_t), true)); if (_values == nullptr) { THROW_ARANGO_EXCEPTION(TRI_ERROR_OUT_OF_MEMORY); } _length = n; } void TRI_hash_index_search_value_t::destroy () { if (_values != nullptr) { for (size_t i = 0; i < _length; ++i) { TRI_DestroyShapedJson(TRI_UNKNOWN_MEM_ZONE, &_values[i]); } TRI_Free(TRI_UNKNOWN_MEM_ZONE, _values); _values = nullptr; } } // ----------------------------------------------------------------------------- // --SECTION-- class HashIndex // ----------------------------------------------------------------------------- // ----------------------------------------------------------------------------- // --SECTION-- constructors and destructors // ----------------------------------------------------------------------------- //////////////////////////////////////////////////////////////////////////////// /// @brief create the index //////////////////////////////////////////////////////////////////////////////// HashIndex::HashIndex (TRI_idx_iid_t iid, TRI_document_collection_t* collection, std::vector> const& fields, bool unique, bool sparse) : PathBasedIndex(iid, collection, fields, unique, sparse, false), _uniqueArray(nullptr) { uint32_t indexBuckets = 1; if (collection != nullptr) { // document is a nullptr in the coordinator case indexBuckets = collection->_info._indexBuckets; } std::unique_ptr func(new HashElementFunc(_paths.size())); std::unique_ptr compare(new IsEqualElementElementByKey(_paths.size())); if (unique) { std::unique_ptr array(new TRI_HashArray_t(HashKey, *(func.get()), IsEqualKeyElementHash, IsEqualElementElement, *(compare.get()), indexBuckets, [] () -> std::string { return "unique hash-array"; })); _uniqueArray = new HashIndex::UniqueArray(array.get(), func.get(), compare.get()); array.release(); } else { _multiArray = nullptr; std::unique_ptr array(new TRI_HashArrayMulti_t(HashKey, *(func.get()), IsEqualKeyElement, IsEqualElementElement, *(compare.get()), indexBuckets, 64, [] () -> std::string { return "multi hash-array"; })); _multiArray = new HashIndex::MultiArray(array.get(), func.get(), compare.get()); array.release(); } compare.release(); func.release(); } //////////////////////////////////////////////////////////////////////////////// /// @brief create an index stub with a hard-coded selectivity estimate /// this is used in the cluster coordinator case //////////////////////////////////////////////////////////////////////////////// HashIndex::HashIndex (TRI_json_t const* json) : PathBasedIndex(json, false), _uniqueArray(nullptr) { } //////////////////////////////////////////////////////////////////////////////// /// @brief destroys the index //////////////////////////////////////////////////////////////////////////////// HashIndex::~HashIndex () { if (_unique) { delete _uniqueArray; } else { delete _multiArray; } } // ----------------------------------------------------------------------------- // --SECTION-- public methods // ----------------------------------------------------------------------------- //////////////////////////////////////////////////////////////////////////////// /// @brief returns a selectivity estimate for the index //////////////////////////////////////////////////////////////////////////////// double HashIndex::selectivityEstimate () const { if (_unique) { return 1.0; } if (_multiArray == nullptr) { // use hard-coded selectivity estimate in case of cluster coordinator return _selectivityEstimate; } double estimate = _multiArray->_hashArray->selectivity(); TRI_ASSERT(estimate >= 0.0 && estimate <= 1.00001); // floating-point tolerance return estimate; } //////////////////////////////////////////////////////////////////////////////// /// @brief returns the index memory usage //////////////////////////////////////////////////////////////////////////////// size_t HashIndex::memory () const { if (_unique) { return static_cast(elementSize() * _uniqueArray->_hashArray->size() + _uniqueArray->_hashArray->memoryUsage()); } return static_cast(elementSize() * _multiArray->_hashArray->size() + _multiArray->_hashArray->memoryUsage()); } //////////////////////////////////////////////////////////////////////////////// /// @brief return a JSON representation of the index //////////////////////////////////////////////////////////////////////////////// triagens::basics::Json HashIndex::toJson (TRI_memory_zone_t* zone, bool withFigures) const { auto json = Index::toJson(zone, withFigures); json("unique", triagens::basics::Json(zone, _unique)) ("sparse", triagens::basics::Json(zone, _sparse)); return json; } //////////////////////////////////////////////////////////////////////////////// /// @brief return a JSON representation of the index figures //////////////////////////////////////////////////////////////////////////////// triagens::basics::Json HashIndex::toJsonFigures (TRI_memory_zone_t* zone) const { triagens::basics::Json json(zone, triagens::basics::Json::Object); json("memory", triagens::basics::Json(static_cast(memory()))); if (_unique) { _uniqueArray->_hashArray->appendToJson(zone, json); } else { _multiArray->_hashArray->appendToJson(zone, json); } return json; } int HashIndex::insert (triagens::arango::Transaction* trx, TRI_doc_mptr_t const* doc, bool isRollback) { if (_unique) { return insertUnique(trx, doc, isRollback); } return insertMulti(trx, doc, isRollback); } //////////////////////////////////////////////////////////////////////////////// /// @brief removes an entry from the hash array part of the hash index //////////////////////////////////////////////////////////////////////////////// int HashIndex::remove (triagens::arango::Transaction* trx, TRI_doc_mptr_t const* doc, bool isRollback) { if (_unique) { return removeUnique(trx, doc, isRollback); } return removeMulti(trx, doc, isRollback); } int HashIndex::batchInsert (triagens::arango::Transaction* trx, std::vector const* documents, size_t numThreads) { if (_unique) { return batchInsertUnique(trx, documents, numThreads); } return batchInsertMulti(trx, documents, numThreads); } //////////////////////////////////////////////////////////////////////////////// /// @brief provides a size hint for the hash index //////////////////////////////////////////////////////////////////////////////// int HashIndex::sizeHint (triagens::arango::Transaction* trx, size_t size) { if (_sparse) { // for sparse indexes, we assume that we will have less index entries // than if the index would be fully populated size /= 5; } if (_unique) { return _uniqueArray->_hashArray->resize(size); } else { return _multiArray->_hashArray->resize(trx, size); } } //////////////////////////////////////////////////////////////////////////////// /// @brief locates entries in the hash index given shaped json objects //////////////////////////////////////////////////////////////////////////////// int HashIndex::lookup (triagens::arango::Transaction* trx, TRI_hash_index_search_value_t* searchValue, std::vector& documents) const { if (_unique) { TRI_index_element_t* found = _uniqueArray->_hashArray->findByKey(searchValue); if (found != nullptr) { // unique hash index: maximum number is 1 documents.emplace_back(found->document()); } return TRI_ERROR_NO_ERROR; } std::vector* results = nullptr; try { results = _multiArray->_hashArray->lookupByKey(trx, searchValue); } catch (...) { return TRI_ERROR_OUT_OF_MEMORY; } if (results != nullptr) { try { for (size_t i = 0; i < results->size(); i++) { documents.emplace_back((*results)[i]->document()); } delete results; } catch (...) { delete results; return TRI_ERROR_OUT_OF_MEMORY; } } return TRI_ERROR_NO_ERROR; } //////////////////////////////////////////////////////////////////////////////// /// @brief locates entries in the hash index given shaped json objects //////////////////////////////////////////////////////////////////////////////// int HashIndex::lookup (triagens::arango::Transaction* trx, TRI_hash_index_search_value_t* searchValue, std::vector& documents, TRI_index_element_t*& next, size_t batchSize) const { if (_unique) { next = nullptr; TRI_index_element_t* found = _uniqueArray->_hashArray->findByKey(searchValue); if (found != nullptr) { // unique hash index: maximum number is 1 documents.emplace_back(*(found->document())); } return TRI_ERROR_NO_ERROR; } std::vector* results = nullptr; if (next == nullptr) { try { results = _multiArray->_hashArray->lookupByKey(trx, searchValue, batchSize); } catch (...) { return TRI_ERROR_OUT_OF_MEMORY; } } else { try { results = _multiArray->_hashArray->lookupByKeyContinue(trx, next, batchSize); } catch (...) { return TRI_ERROR_OUT_OF_MEMORY; } } if (results != nullptr) { if (results->size() > 0) { next = results->back(); // for continuation the next time try { for (size_t i = 0; i < results->size(); i++) { documents.emplace_back(*((*results)[i]->document())); } } catch (...) { delete results; return TRI_ERROR_OUT_OF_MEMORY; } } else { next = nullptr; } delete results; } else { next = nullptr; } return TRI_ERROR_NO_ERROR; } // ----------------------------------------------------------------------------- // --SECTION-- private methods // ----------------------------------------------------------------------------- int HashIndex::insertUnique (triagens::arango::Transaction* trx, TRI_doc_mptr_t const* doc, bool isRollback) { std::vector elements; int res = fillElement(elements, doc); if (res != TRI_ERROR_NO_ERROR) { for (auto& it : elements) { // free all elements to prevent leak FreeElement(it); } return res; } auto work = [this] (TRI_index_element_t* element, bool isRollback) -> int { TRI_IF_FAILURE("InsertHashIndex") { return TRI_ERROR_DEBUG; } return _uniqueArray->_hashArray->insert(element); }; size_t const n = elements.size(); for (size_t i = 0; i < n; ++i) { auto hashElement = elements[i]; res = work(hashElement, isRollback); if (res != TRI_ERROR_NO_ERROR) { for (size_t j = i; j < n; ++j) { // Free all elements that are not yet in the index FreeElement(elements[j]); } // Already indexed elements will be removed by the rollback break; } } return res; } int HashIndex::batchInsertUnique (triagens::arango::Transaction* trx, std::vector const* documents, size_t numThreads) { std::vector elements; elements.reserve(documents->size()); for (auto& doc : *documents) { int res = fillElement(elements, doc); if (res != TRI_ERROR_NO_ERROR) { for (auto& it : elements) { // free all elements to prevent leak FreeElement(it); } return res; } } int res = _uniqueArray->_hashArray->batchInsert(&elements, numThreads); if (res != TRI_ERROR_NO_ERROR) { // TODO check leaks for (auto& it : elements) { // free all elements to prevent leak FreeElement(it); } } return res; } int HashIndex::insertMulti (triagens::arango::Transaction* trx, TRI_doc_mptr_t const* doc, bool isRollback) { std::vector elements; int res = fillElement(elements, doc); if (res != TRI_ERROR_NO_ERROR) { for (auto& hashElement : elements) { FreeElement(hashElement); } return res; } auto work = [this, trx] (TRI_index_element_t*& element, bool isRollback) { TRI_IF_FAILURE("InsertHashIndex") { THROW_ARANGO_EXCEPTION(TRI_ERROR_DEBUG); } TRI_index_element_t* found = _multiArray->_hashArray->insert(trx, element, false, true); if (found != nullptr) { // already got the exact same index entry. now free our local element... FreeElement(element); // we're not responsible for this element anymore element = nullptr; } }; size_t const n = elements.size(); for (size_t i = 0; i < n; ++i) { auto hashElement = elements[i]; try { work(hashElement, isRollback); } catch (triagens::basics::Exception const& ex) { res = ex.code(); } catch (...) { res = TRI_ERROR_OUT_OF_MEMORY; } if (res != TRI_ERROR_NO_ERROR) { for (size_t j = i; j < n; ++j) { // Free all elements that are not yet in the index FreeElement(elements[j]); } for (size_t j = 0; j < i; ++j) { // Remove all allready indexed elements and free them if (elements[j] != nullptr) { removeMultiElement(trx, elements[j], isRollback); } } return res; } } return TRI_ERROR_NO_ERROR; } int HashIndex::batchInsertMulti (triagens::arango::Transaction* trx, std::vector const* documents, size_t numThreads) { std::vector elements; for (auto& doc : *documents) { int res = fillElement(elements, doc); if (res != TRI_ERROR_NO_ERROR) { // Filling the elements failed for some reason. Assume loading as failed for (auto& el : elements) { // Free all elements that are not yet in the index FreeElement(el); } return res; } } return _multiArray->_hashArray->batchInsert(trx, &elements, numThreads); } int HashIndex::removeUniqueElement (triagens::arango::Transaction* trx, TRI_index_element_t* element, bool isRollback) { TRI_IF_FAILURE("RemoveHashIndex") { return TRI_ERROR_DEBUG; } TRI_index_element_t* old = _uniqueArray->_hashArray->remove(element); // this might happen when rolling back if (old == nullptr) { if (isRollback) { return TRI_ERROR_NO_ERROR; } else { return TRI_ERROR_INTERNAL; } } FreeElement(old); return TRI_ERROR_NO_ERROR; } int HashIndex::removeUnique (triagens::arango::Transaction* trx, TRI_doc_mptr_t const* doc, bool isRollback) { std::vector elements; int res = fillElement(elements, doc); if (res != TRI_ERROR_NO_ERROR) { for (auto& hashElement : elements) { FreeElement(hashElement); } return res; } for (auto& hashElement : elements) { res = removeUniqueElement(trx, hashElement, isRollback); FreeElement(hashElement); } return res; } int HashIndex::removeMultiElement (triagens::arango::Transaction* trx, TRI_index_element_t* element, bool isRollback) { TRI_IF_FAILURE("RemoveHashIndex") { return TRI_ERROR_DEBUG; } TRI_index_element_t* old = _multiArray->_hashArray->remove(trx, element); if (old == nullptr) { // not found if (isRollback) { // ignore in this case, because it can happen return TRI_ERROR_NO_ERROR; } else { return TRI_ERROR_INTERNAL; } } FreeElement(old); return TRI_ERROR_NO_ERROR; } int HashIndex::removeMulti (triagens::arango::Transaction* trx, TRI_doc_mptr_t const* doc, bool isRollback) { std::vector elements; int res = fillElement(elements, doc); if (res != TRI_ERROR_NO_ERROR) { for (auto& hashElement : elements) { FreeElement(hashElement); } } for (auto& hashElement : elements) { res = removeMultiElement(trx, hashElement, isRollback); FreeElement(hashElement); } return res; } //////////////////////////////////////////////////////////////////////////////// /// @brief checks whether the index supports the condition //////////////////////////////////////////////////////////////////////////////// bool HashIndex::supportsFilterCondition (triagens::aql::AstNode const* node, triagens::aql::Variable const* reference, size_t itemsInIndex, size_t& estimatedItems, double& estimatedCost) const { SimpleAttributeEqualityMatcher matcher(fields()); return matcher.matchAll(this, node, reference, itemsInIndex, estimatedItems, estimatedCost); } //////////////////////////////////////////////////////////////////////////////// /// @brief creates an IndexIterator for the given Condition //////////////////////////////////////////////////////////////////////////////// IndexIterator* HashIndex::iteratorForCondition (triagens::arango::Transaction* trx, IndexIteratorContext* context, triagens::aql::Ast* ast, triagens::aql::AstNode const* node, triagens::aql::Variable const* reference, bool reverse) const { TRI_ASSERT(node->type == aql::NODE_TYPE_OPERATOR_NARY_AND); SimpleAttributeEqualityMatcher matcher(fields()); size_t const n = _fields.size(); TRI_ASSERT(node->numMembers() == n); // initialize permutations std::vector permutationStates; permutationStates.reserve(n); size_t maxPermutations = 1; std::pair> paramPair; for (size_t i = 0; i < n; ++i) { auto comp = node->getMemberUnchecked(i); auto attrNode = comp->getMember(0); auto valNode = comp->getMember(1); paramPair.first = nullptr; paramPair.second.clear(); if (! attrNode->isAttributeAccessForVariable(paramPair) || paramPair.first != reference) { attrNode = comp->getMember(1); valNode = comp->getMember(0); if (! attrNode->isAttributeAccessForVariable(paramPair) || paramPair.first != reference) { return nullptr; } } size_t attributePosition = SIZE_MAX; for (size_t j = 0; j < _fields.size(); ++j) { if (triagens::basics::AttributeName::isIdentical(_fields[j], paramPair.second, true)) { attributePosition = j; break; } } if (attributePosition == SIZE_MAX) { // index attribute not found in condition. this is a severe error THROW_ARANGO_EXCEPTION(TRI_ERROR_INTERNAL); } triagens::aql::AstNodeType type = comp->type; if (comp->type == aql::NODE_TYPE_OPERATOR_BINARY_EQ) { permutationStates.emplace_back(PermutationState(type, valNode, attributePosition, 1)); TRI_IF_FAILURE("HashIndex::permutationEQ") { THROW_ARANGO_EXCEPTION(TRI_ERROR_DEBUG); } } else if (comp->type == aql::NODE_TYPE_OPERATOR_BINARY_IN) { if (isAttributeExpanded(attributePosition)) { type = aql::NODE_TYPE_OPERATOR_BINARY_EQ; permutationStates.emplace_back(PermutationState(type, valNode, attributePosition, 1)); TRI_IF_FAILURE("HashIndex::permutationArrayIN") { THROW_ARANGO_EXCEPTION(TRI_ERROR_DEBUG); } } else { if (valNode->numMembers() == 0) { return nullptr; } permutationStates.emplace_back(PermutationState(type, valNode, attributePosition, valNode->numMembers())); TRI_IF_FAILURE("HashIndex::permutationIN") { THROW_ARANGO_EXCEPTION(TRI_ERROR_DEBUG); } maxPermutations *= valNode->numMembers(); } } } if (permutationStates.empty()) { // can only be caused by empty IN lists return nullptr; } std::vector searchValues; searchValues.reserve(maxPermutations); // create all permutations auto shaper = _collection->getShaper(); size_t current = 0; bool done = false; while (! done) { std::unique_ptr searchValue(new TRI_hash_index_search_value_t); searchValue->reserve(n); bool valid = true; for (size_t i = 0; i < n; ++i) { auto& state = permutationStates[i]; std::unique_ptr json(state.getValue()->toJsonValue(TRI_UNKNOWN_MEM_ZONE)); if (json == nullptr) { valid = false; break; } auto shaped = TRI_ShapedJsonJson(shaper, json.get(), false); if (shaped == nullptr) { // no such shape exists. this means we won't find this value and can go on with the next permutation valid = false; break; } searchValue->_values[state.attributePosition] = *shaped; TRI_Free(shaper->memoryZone(), shaped); } if (valid) { searchValues.push_back(searchValue.get()); searchValue.release(); } // now permute while (true) { if (++permutationStates[current].current < permutationStates[current].n) { current = 0; // abort inner iteration break; } permutationStates[current].current = 0; if (++current >= n) { done = true; break; } // next inner iteration } } TRI_ASSERT(searchValues.size() <= maxPermutations); // Create the iterator TRI_IF_FAILURE("HashIndex::noIterator") { THROW_ARANGO_EXCEPTION(TRI_ERROR_DEBUG); } return new HashIndexIterator(trx, this, searchValues); } //////////////////////////////////////////////////////////////////////////////// /// @brief specializes the condition for use with the index //////////////////////////////////////////////////////////////////////////////// triagens::aql::AstNode* HashIndex::specializeCondition (triagens::aql::AstNode* node, triagens::aql::Variable const* reference) const { SimpleAttributeEqualityMatcher matcher(fields()); return matcher.specializeAll(this, node, reference); } // ----------------------------------------------------------------------------- // --SECTION-- END-OF-FILE // ----------------------------------------------------------------------------- // Local Variables: // mode: outline-minor // outline-regexp: "/// @brief\\|/// {@inheritDoc}\\|/// @page\\|// --SECTION--\\|/// @\\}" // End: