int TRI_LookupByKeyHashArrayMulti (TRI_hash_array_multi_t const* array,
                                   TRI_index_search_value_t const* key,
                                   std::vector<TRI_doc_mptr_copy_t>& result) {
  TRI_ASSERT_EXPENSIVE(array->_nrUsed < array->_nrAlloc);

  uint64_t const n = array->_nrAlloc;
  uint64_t i, k;

  i = k = HashKey(array, key) % n;
  
  for (; i < n && array->_table[i]._document != nullptr && ! IsEqualKeyElement(array, key, &array->_table[i]); ++i);
  if (i == n) {
    for (i = 0; i < k && array->_table[i]._document != nullptr && ! IsEqualKeyElement(array, key, &array->_table[i]); ++i);
  }

  TRI_ASSERT_EXPENSIVE(i < n);

  if (array->_table[i]._document != nullptr) {
    // add the element itself
    result.emplace_back(*(array->_table[i]._document));

    // add the overflow elements
    auto current = array->_table[i]._next;
    while (current != nullptr) {
      result.emplace_back(*(current->_document));
      current = current->_next;
    }
  }

  return TRI_ERROR_NO_ERROR;
}
void* TRI_LookupByKeyPrimaryIndex (TRI_primary_index_t* idx,
                                   char const* key) {
  if (idx->_nrUsed == 0) {
    return nullptr;
  }

  // compute the hash
  uint64_t const hash = TRI_HashKeyPrimaryIndex(key);
  uint64_t const n = idx->_nrAlloc;
  uint64_t i, k;

  i = k = hash % n;

  TRI_ASSERT_EXPENSIVE(n > 0);

  // search the table
  for (; i < n && idx->_table[i] != nullptr && IsDifferentHashElement(key, hash, idx->_table[i]); ++i);
  if (i == n) {
    for (i = 0; i < k && idx->_table[i] != nullptr && IsDifferentHashElement(key, hash, idx->_table[i]); ++i);
  }

  TRI_ASSERT_EXPENSIVE(i < n);

  // return whatever we found
  return idx->_table[i];
}
static void DestroyElement (TRI_hash_array_multi_t* array,
                            TRI_hash_index_element_multi_t* element) {
  TRI_ASSERT_EXPENSIVE(element != nullptr);
  TRI_ASSERT_EXPENSIVE(element->_document != nullptr);

  if (element->_subObjects != nullptr) {
    TRI_Free(TRI_UNKNOWN_MEM_ZONE, element->_subObjects);
  }
  element->_document = nullptr;
  element->_subObjects = nullptr;
  element->_next = nullptr;
}
static bool IsEqualKeyElement (TRI_hash_array_multi_t const* array,
                               TRI_index_search_value_t const* left,
                               TRI_hash_index_element_multi_t const* right) {
  TRI_ASSERT_EXPENSIVE(right->_document != nullptr);

  for (size_t j = 0;  j < array->_numFields;  ++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;
}
int TRI_LookupByKeyHashArrayMulti (TRI_hash_array_multi_t const* array,
                                   TRI_index_search_value_t const* key,
                                   std::vector<TRI_doc_mptr_copy_t>& result,
                                   TRI_hash_index_element_multi_t*& next,
                                   size_t batchSize) {
  size_t const initialSize = result.size();
  TRI_ASSERT_EXPENSIVE(array->_nrUsed < array->_nrAlloc);
  TRI_ASSERT(batchSize > 0);

  if (next == nullptr) {
    // no previous state. start at the beginning
    uint64_t const n = array->_nrAlloc;
    uint64_t i, k;

    i = k = HashKey(array, key) % n;
  
    for (; i < n && array->_table[i]._document != nullptr && ! IsEqualKeyElement(array, key, &array->_table[i]); ++i);
    if (i == n) {
      for (i = 0; i < k && array->_table[i]._document != nullptr && ! IsEqualKeyElement(array, key, &array->_table[i]); ++i);
    }

    TRI_ASSERT_EXPENSIVE(i < n);

    if (array->_table[i]._document != nullptr) {
      result.emplace_back(*(array->_table[i]._document));
    }
    next = array->_table[i]._next;
  }
  
  if (next != nullptr) {
    // we already had a state
    size_t total = result.size() - initialSize;

    while (next != nullptr && total < batchSize) {
      result.emplace_back(*(next->_document));
      next = next->_next;
      ++total;
    }
  }
    
  return TRI_ERROR_NO_ERROR;
}
int TRI_InsertKeyPrimaryIndex (TRI_primary_index_t* idx,
                               TRI_doc_mptr_t const* header,
                               void const** found) {
  *found = nullptr;

  if (idx->_nrAlloc < 2 * idx->_nrUsed) {
    // check for out-of-memory
    if (! ResizePrimaryIndex(idx, (uint64_t) (2 * idx->_nrAlloc + 1), false)) {
      return TRI_ERROR_OUT_OF_MEMORY;
    }
  }

  uint64_t const n = idx->_nrAlloc;
  uint64_t i, k;

  TRI_ASSERT_EXPENSIVE(n > 0);

  i = k = header->_hash % n;

  for (; i < n && idx->_table[i] != nullptr && IsDifferentKeyElement(header, idx->_table[i]); ++i);
  if (i == n) {
    for (i = 0; i < k && idx->_table[i] != nullptr && IsDifferentKeyElement(header, idx->_table[i]); ++i);
  }

  TRI_ASSERT_EXPENSIVE(i < n);

  void* old = idx->_table[i];

  // if we found an element, return
  if (old != nullptr) {
    *found = old;

    return TRI_ERROR_NO_ERROR;
  }

  // add a new element to the associative idx
  idx->_table[i] = (void*) header;
  ++idx->_nrUsed;

  return TRI_ERROR_NO_ERROR;
}
static int ResizeHashArray (TRI_hash_array_multi_t* array,
                            uint64_t targetSize,
                            bool allowShrink) {
  if (array->_nrAlloc >= targetSize && ! allowShrink) {
    return TRI_ERROR_NO_ERROR;
  }

  TRI_hash_index_element_multi_t* oldTable    = array->_table;
  TRI_hash_index_element_multi_t* oldTablePtr = array->_tablePtr;
  uint64_t oldAlloc = array->_nrAlloc;

  TRI_ASSERT(targetSize > 0);

  int res = AllocateTable(array, targetSize);

  if (res != TRI_ERROR_NO_ERROR) {
    return res;
  }

  if (array->_nrUsed > 0) {
    uint64_t const n = array->_nrAlloc;

    for (uint64_t j = 0; j < oldAlloc; j++) {
      TRI_hash_index_element_multi_t* element = &oldTable[j];

      if (element->_document != nullptr) {
        uint64_t i, k;
        i = k = HashElement(array, element) % n;

        for (; i < n && array->_table[i]._document != nullptr; ++i);
        if (i == n) {
          for (i = 0; i < k && array->_table[i]._document != nullptr; ++i);
        }

        TRI_ASSERT_EXPENSIVE(i < n);

        // ...........................................................................
        // add a new element to the associative array
        // memcpy ok here since are simply moving array items internally
        // ...........................................................................

        memcpy(&array->_table[i], element, TableEntrySize());
      }
    }
  }

  TRI_Free(TRI_UNKNOWN_MEM_ZONE, oldTablePtr);

  return TRI_ERROR_NO_ERROR;
}
TRI_vector_pointer_t TRI_LookupByKeyHashArrayMulti (TRI_hash_array_multi_t const* array,
                                                    TRI_index_search_value_t const* key) {
  TRI_ASSERT_EXPENSIVE(array->_nrUsed < array->_nrAlloc);

  // ...........................................................................
  // initialise the vector which will hold the result if any
  // ...........................................................................

  TRI_vector_pointer_t result;
  TRI_InitVectorPointer(&result, TRI_UNKNOWN_MEM_ZONE);

  uint64_t const n = array->_nrAlloc;
  uint64_t i, k;

  i = k = HashKey(array, key) % n;
  
  for (; i < n && array->_table[i]._document != nullptr && ! IsEqualKeyElement(array, key, &array->_table[i]); ++i);
  if (i == n) {
    for (i = 0; i < k && array->_table[i]._document != nullptr && ! IsEqualKeyElement(array, key, &array->_table[i]); ++i);
  }

  TRI_ASSERT_EXPENSIVE(i < n);

  if (array->_table[i]._document != nullptr) {
    // add the element itself
    TRI_PushBackVectorPointer(&result, array->_table[i]._document);

    // add the overflow elements
    auto current = array->_table[i]._next;
    while (current != nullptr) {
      TRI_PushBackVectorPointer(&result, current->_document);
      current = current->_next;
    }
  }

  return result;
}
static bool ResizePrimaryIndex (TRI_primary_index_t* idx,
                                uint64_t targetSize,
                                bool allowShrink) {
  TRI_ASSERT(targetSize > 0);

  if (idx->_nrAlloc >= targetSize && ! allowShrink) {
    return true;
  }

  void** oldTable = idx->_table;

  idx->_table = static_cast<void**>(TRI_Allocate(TRI_UNKNOWN_MEM_ZONE, (size_t) (targetSize * sizeof(void*)), true));

  if (idx->_table == nullptr) {
    idx->_table = oldTable;

    return false;
  }

  if (idx->_nrUsed > 0) {
    uint64_t const oldAlloc = idx->_nrAlloc;

    // table is already cleared by allocate, now copy old data
    for (uint64_t j = 0; j < oldAlloc; j++) {
      TRI_doc_mptr_t const* element = static_cast<TRI_doc_mptr_t const*>(oldTable[j]);

      if (element != nullptr) {
        uint64_t const hash = element->_hash;
        uint64_t i, k;

        i = k = hash % targetSize;

        for (; i < targetSize && idx->_table[i] != nullptr; ++i);
        if (i == targetSize) {
          for (i = 0; i < k && idx->_table[i] != nullptr; ++i);
        }

        TRI_ASSERT_EXPENSIVE(i < targetSize);

        idx->_table[i] = (void*) element;
      }
    }
  }

  TRI_Free(TRI_UNKNOWN_MEM_ZONE, oldTable);
  idx->_nrAlloc = targetSize;

  return true;
}
void* TRI_RemoveKeyPrimaryIndex (TRI_primary_index_t* idx,
                                 char const* key) {
  uint64_t const hash = TRI_HashKeyPrimaryIndex(key);
  uint64_t const n = idx->_nrAlloc;
  uint64_t i, k;

  i = k = hash % n;

  // search the table
  for (; i < n && idx->_table[i] != nullptr && IsDifferentHashElement(key, hash, idx->_table[i]); ++i);
  if (i == n) {
    for (i = 0; i < k && idx->_table[i] != nullptr && IsDifferentHashElement(key, hash, idx->_table[i]); ++i);
  }

  TRI_ASSERT_EXPENSIVE(i < n);

  // if we did not find such an item return false
  if (idx->_table[i] == nullptr) {
    return nullptr;
  }

  // remove item
  void* old = idx->_table[i];
  idx->_table[i] = nullptr;
  idx->_nrUsed--;

  // and now check the following places for items to move here
  k = TRI_IncModU64(i, n);

  while (idx->_table[k] != nullptr) {
    uint64_t j = (static_cast<TRI_doc_mptr_t const*>(idx->_table[k])->_hash) % n;

    if ((i < k && ! (i < j && j <= k)) || (k < i && ! (i < j || j <= k))) {
      idx->_table[i] = idx->_table[k];
      idx->_table[k] = nullptr;
      i = k;
    }

    k = TRI_IncModU64(k, n);
  }

  if (idx->_nrUsed == 0) {
    ResizePrimaryIndex(idx, InitialSize(), true);
  }

  // return success
  return old;
}
int TRI_RemoveElementHashArrayMulti (TRI_hash_array_multi_t* array,
                                     TRI_index_search_value_t const* key,
                                     TRI_hash_index_element_multi_t* element) {
  uint64_t const n = array->_nrAlloc;
  uint64_t i, k;

  i = k = HashKey(array, key) % n;

  for (; i < n && array->_table[i]._document != nullptr && ! IsEqualKeyElement(array, key, &array->_table[i]); ++i);
  if (i == n) {
    for (i = 0; i < k && array->_table[i]._document != nullptr && ! IsEqualKeyElement(array, key, &array->_table[i]); ++i);
  }

  TRI_ASSERT_EXPENSIVE(i < n);

  TRI_hash_index_element_multi_t* arrayElement = &array->_table[i];

  bool found = (arrayElement->_document != nullptr);

  if (! found) {
    return TRI_RESULT_ELEMENT_NOT_FOUND;
  }
    
  if (arrayElement->_document != element->_document) {
    // look in the overflow list for the sought document
    auto next = &(arrayElement->_next);
    while (*next != nullptr) {
      if ((*next)->_document == element->_document) {
        auto ptr = (*next)->_next;
        DestroyElement(array, *next);
        ReturnToFreelist(array, *next);
        *next = ptr;

        return TRI_ERROR_NO_ERROR;
      }
      next = &((*next)->_next);
    }

    return TRI_RESULT_ELEMENT_NOT_FOUND;
  }

  // the element itself is the document to remove
  TRI_ASSERT(arrayElement->_document == element->_document);
  
  if (arrayElement->_next != nullptr) {
    auto next = arrayElement->_next;

    // destroy our own data first, otherwise we'll leak
    TRI_ASSERT(arrayElement->_subObjects != nullptr);
    TRI_Free(TRI_UNKNOWN_MEM_ZONE, arrayElement->_subObjects);

    // copy data from first overflow element into ourselves
    arrayElement->_document   = next->_document;
    arrayElement->_subObjects = next->_subObjects;
    arrayElement->_next       = next->_next;
 
    // and remove the first overflow element
    next->_subObjects = nullptr;
    DestroyElement(array, next);
    ReturnToFreelist(array, next);
        
    return TRI_ERROR_NO_ERROR;
  }

  TRI_ASSERT(arrayElement->_next == nullptr);

  DestroyElement(array, arrayElement);
  array->_nrUsed--;

  // ...........................................................................
  // and now check the following places for items to move here
  // ...........................................................................

  k = TRI_IncModU64(i, n);

  while (array->_table[k]._document != nullptr) {
    uint64_t j = HashElement(array, &array->_table[k]) % n;

    if ((i < k && ! (i < j && j <= k)) || (k < i && ! (i < j || j <= k))) {
      array->_table[i] = array->_table[k];
      array->_table[k]._document   = nullptr;
      array->_table[k]._next       = nullptr;
      array->_table[k]._subObjects = nullptr;
      i = k;
    }

    k = TRI_IncModU64(k, n);
  }

  if (array->_nrUsed == 0) {
    TRI_ASSERT(array->_nrOverflowUsed == 0);
    ResizeHashArray(array, InitialSize(), true);
  }

  return TRI_ERROR_NO_ERROR;
}
int TRI_InsertElementHashArrayMulti (TRI_hash_array_multi_t* array,
                                     TRI_index_search_value_t const* key,
                                     TRI_hash_index_element_multi_t* element,
                                     bool isRollback) {
  if (! CheckResize(array)) {
    return TRI_ERROR_OUT_OF_MEMORY;
  }

  uint64_t const n = array->_nrAlloc;
  uint64_t i, k;

  i = k = HashKey(array, key) % n;

  for (; i < n && array->_table[i]._document != nullptr && ! IsEqualKeyElement(array, key, &array->_table[i]); ++i);
  if (i == n) {
    for (i = 0; i < k && array->_table[i]._document != nullptr && ! IsEqualKeyElement(array, key, &array->_table[i]); ++i);
  }

  TRI_ASSERT_EXPENSIVE(i < n);

  TRI_hash_index_element_multi_t* arrayElement = &array->_table[i];

  // ...........................................................................
  // If we found an element, return. While we allow duplicate entries in the
  // hash table, we do not allow duplicate elements. Elements would refer to the
  // (for example) an actual row in memory. This is different from the
  // TRI_InsertElementMultiArray function below where we only have keys to
  // differentiate between elements.
  // ...........................................................................

  bool found = (arrayElement->_document != nullptr);

  if (found) {
    if (isRollback) {
      if (arrayElement->_document == element->_document) {
        DestroyElement(array, element);

        return TRI_RESULT_ELEMENT_EXISTS;
      }

      auto current = arrayElement->_next;
      while (current != nullptr) {
        if (current->_document == element->_document) {
          DestroyElement(array, element);

          return TRI_RESULT_ELEMENT_EXISTS;
        }
        current = current->_next;
      }
    }

    auto ptr = GetFromFreelist(array);

    if (ptr == nullptr) {
      return TRI_ERROR_OUT_OF_MEMORY;
    }

    // link our element at the list head
    ptr->_document   = element->_document;
    ptr->_subObjects = element->_subObjects;
    ptr->_next       = arrayElement->_next;
    arrayElement->_next = ptr;
          
    // it is ok to destroy the element here, because we have copied its internal before!
    element->_subObjects = nullptr;
    DestroyElement(array, element);

    return TRI_ERROR_NO_ERROR;
  }
  
  TRI_ASSERT(arrayElement->_next == nullptr);

  // not found in list, now insert
  element->_next = nullptr;
  *arrayElement  = *element;
  array->_nrUsed++;
  
  TRI_ASSERT(arrayElement->_next == nullptr);

  return TRI_ERROR_NO_ERROR;
}
int TRI_CompareValuesJson (TRI_json_t const* lhs,
                           TRI_json_t const* rhs,
                           bool useUTF8) {
    // note: both lhs and rhs may be NULL!
    {
        int lWeight = TypeWeight(lhs);
        int rWeight = TypeWeight(rhs);

        if (lWeight < rWeight) {
            return -1;
        }

        if (lWeight > rWeight) {
            return 1;
        }

        TRI_ASSERT_EXPENSIVE(lWeight == rWeight);
    }

    // lhs and rhs have equal weights

    if (lhs == nullptr || rhs == nullptr) {
        // either lhs or rhs is a nullptr. we cannot be sure here that both are nullptrs.
        // there can also exist the situation that lhs is a nullptr and rhs is a JSON null value
        // (or vice versa). Anyway, the compare value is the same for both,
        return 0;
    }

    switch (lhs->_type) {
    case TRI_JSON_UNUSED:
    case TRI_JSON_NULL: {
        return 0; // null == null;
    }

    case TRI_JSON_BOOLEAN: {
        if (lhs->_value._boolean == rhs->_value._boolean) {
            return 0;
        }

        if (! lhs->_value._boolean && rhs->_value._boolean) {
            return -1;
        }

        return 1;
    }

    case TRI_JSON_NUMBER: {
        if (lhs->_value._number == rhs->_value._number) {
            return 0;
        }

        if (lhs->_value._number < rhs->_value._number) {
            return -1;
        }

        return 1;
    }

    case TRI_JSON_STRING:
    case TRI_JSON_STRING_REFERENCE: {
        // same for STRING and STRING_REFERENCE
        TRI_ASSERT(lhs->_value._string.data != nullptr);
        TRI_ASSERT(rhs->_value._string.data != nullptr);

        int res;
        size_t const nl = lhs->_value._string.length - 1;
        size_t const nr = rhs->_value._string.length - 1;
        if (useUTF8) {
            res = TRI_compare_utf8(lhs->_value._string.data,
                                   nl,
                                   rhs->_value._string.data,
                                   nr);
        }
        else {
            // beware of strings containing NUL bytes
            size_t len = nl < nr ? nl : nr;
            res = memcmp(lhs->_value._string.data, rhs->_value._string.data, len);
        }
        if (res < 0) {
            return -1;
        }
        else if (res > 0) {
            return 1;
        }
        // res == 0
        if (nl == nr) {
            return 0;
        }
        // res == 0, but different string lengths
        return nl < nr ? -1 : 1;
    }

    case TRI_JSON_ARRAY: {
        size_t const nl = TRI_LengthVector(&lhs->_value._objects);
        size_t const nr = TRI_LengthVector(&rhs->_value._objects);
        size_t n;

        if (nl > nr) {
            n = nl;
        }
        else {
            n = nr;
        }

        for (size_t i = 0; i < n; ++i) {
            auto lhsValue = (i >= nl) ? nullptr : static_cast<TRI_json_t const*>(TRI_AtVector(&lhs->_value._objects, i));
            auto rhsValue = (i >= nr) ? nullptr : static_cast<TRI_json_t const*>(TRI_AtVector(&rhs->_value._objects, i));

            int result = TRI_CompareValuesJson(lhsValue, rhsValue, useUTF8);

            if (result != 0) {
                return result;
            }
        }

        return 0;
    }

    case TRI_JSON_OBJECT: {
        TRI_ASSERT(lhs->_type == TRI_JSON_OBJECT);
        TRI_ASSERT(rhs->_type == TRI_JSON_OBJECT);

        std::unique_ptr<TRI_json_t> keys(GetMergedKeyArray(lhs, rhs));

        if (keys != nullptr) {
            auto json = keys.get();
            size_t const n = TRI_LengthVector(&json->_value._objects);

            for (size_t i = 0; i < n; ++i) {
                auto keyElement = static_cast<TRI_json_t const*>(TRI_AtVector(&json->_value._objects, i));
                TRI_ASSERT(TRI_IsStringJson(keyElement));

                TRI_json_t const* lhsValue = TRI_LookupObjectJson(lhs, keyElement->_value._string.data); // may be NULL
                TRI_json_t const* rhsValue = TRI_LookupObjectJson(rhs, keyElement->_value._string.data); // may be NULL

                int result = TRI_CompareValuesJson(lhsValue, rhsValue, useUTF8);

                if (result != 0) {
                    return result;
                }
            }
        }
        // fall-through to returning 0
    }

    }

    return 0;
}