bool DocumentAccessor::hasKey (std::string const& attribute) const {
  if (! isObject()) {
    return false;
  }

  if (_current == nullptr) {
    if (attribute.size() > 1 && attribute[0] == '_') {
      if (attribute == TRI_VOC_ATTRIBUTE_ID ||
          attribute == TRI_VOC_ATTRIBUTE_KEY ||
          attribute == TRI_VOC_ATTRIBUTE_REV) {
        return true;
      }
      
      if (TRI_IS_EDGE_MARKER(_mptr) &&
          (attribute == TRI_VOC_ATTRIBUTE_FROM ||
          attribute == TRI_VOC_ATTRIBUTE_TO)) {
        return true;
      }
    }

    auto shaper = _document->getShaper();

    TRI_shape_pid_t pid = shaper->lookupAttributePathByName(attribute.c_str());

    if (pid != 0) {
      return true;
    }

    return false;
  }

  return (TRI_LookupObjectJson(_current, attribute.c_str()) != nullptr);
}
void DocumentAccessor::lookupJsonAttribute (char const* name, size_t nameLength) {
  TRI_ASSERT(_current != nullptr);

  if (! isObject()) {
    setToNull();
    return;
  }

  TRI_json_t const* value = TRI_LookupObjectJson(_current, name);

  if (value == nullptr) {
    // attribute not found
    setToNull();
  }
  else {
    // found
    _current = value;
  }
}
uint64_t TRI_HashJsonByAttributes (TRI_json_t const* json,
                                   char const *attributes[],
                                   int nrAttributes,
                                   bool docComplete,
                                   int* error) {
    if (error != nullptr) {
        *error = TRI_ERROR_NO_ERROR;
    }
    uint64_t hash = TRI_FnvHashBlockInitial();
    if (TRI_IsObjectJson(json)) {
        for (int i = 0; i < nrAttributes; i++) {
            TRI_json_t const* subjson = TRI_LookupObjectJson(json, attributes[i]);

            if (subjson == nullptr && ! docComplete && error != nullptr) {
                *error = TRI_ERROR_CLUSTER_NOT_ALL_SHARDING_ATTRIBUTES_GIVEN;
            }
            hash = HashJsonRecursive(hash, subjson);
        }
    }
    return hash;
}
KeyGenerator::GeneratorType KeyGenerator::generatorType (TRI_json_t const* parameters) {
  if (! TRI_IsObjectJson(parameters)) {
    return KeyGenerator::TYPE_TRADITIONAL;
  }

  TRI_json_t const* type = TRI_LookupObjectJson(parameters, "type");

  if (! TRI_IsStringJson(type)) {
    return KeyGenerator::TYPE_TRADITIONAL;
  }

  char const* typeName = type->_value._string.data;

  if (TRI_CaseEqualString(typeName, TraditionalKeyGenerator::name().c_str())) {
    return KeyGenerator::TYPE_TRADITIONAL;
  }

  if (TRI_CaseEqualString(typeName, AutoIncrementKeyGenerator::name().c_str())) {
    return KeyGenerator::TYPE_AUTOINCREMENT;
  }

  // error
  return KeyGenerator::TYPE_UNKNOWN;
}
static TRI_json_t* MergeRecursive (TRI_memory_zone_t* zone,
                                   TRI_json_t const* lhs,
                                   TRI_json_t const* rhs,
                                   bool nullMeansRemove,
                                   bool mergeObjects) {
    TRI_ASSERT(lhs != nullptr);

    std::unique_ptr<TRI_json_t> result(TRI_CopyJson(zone, lhs));

    if (result == nullptr) {
        return nullptr;
    }

    auto r = result.get(); // shortcut variable

    size_t const n = TRI_LengthVector(&rhs->_value._objects);

    for (size_t i = 0; i < n; i += 2) {
        // enumerate all the replacement values
        auto key   = static_cast<TRI_json_t const*>(TRI_AtVector(&rhs->_value._objects, i));
        auto value = static_cast<TRI_json_t const*>(TRI_AtVector(&rhs->_value._objects, i + 1));

        if (value->_type == TRI_JSON_NULL && nullMeansRemove) {
            // replacement value is a null and we don't want to store nulls => delete attribute from the result
            TRI_DeleteObjectJson(zone, r, key->_value._string.data);
        }
        else {
            // replacement value is not a null or we want to store nulls
            TRI_json_t const* lhsValue = TRI_LookupObjectJson(lhs, key->_value._string.data);

            if (lhsValue == nullptr) {
                // existing array does not have the attribute => append new attribute
                if (value->_type == TRI_JSON_OBJECT && nullMeansRemove) {
                    TRI_json_t empty;
                    TRI_InitObjectJson(TRI_UNKNOWN_MEM_ZONE, &empty);
                    TRI_json_t* merged = MergeRecursive(zone, &empty, value, nullMeansRemove, mergeObjects);

                    if (merged == nullptr) {
                        return nullptr;
                    }

                    TRI_json_t* copy = TRI_CopyJson(zone, value);

                    if (copy == nullptr) {
                        return nullptr;
                    }

                    TRI_Insert3ObjectJson(zone, r, key->_value._string.data, copy);
                }
                else {
                    TRI_Insert3ObjectJson(zone, r, key->_value._string.data, TRI_CopyJson(zone, value));
                }
            }
            else {
                // existing array already has the attribute => replace attribute
                if (lhsValue->_type == TRI_JSON_OBJECT && value->_type == TRI_JSON_OBJECT && mergeObjects) {
                    TRI_json_t* merged = MergeRecursive(zone, lhsValue, value, nullMeansRemove, mergeObjects);

                    if (merged == nullptr) {
                        return nullptr;
                    }

                    TRI_ReplaceObjectJson(zone, r, key->_value._string.data, merged);
                    TRI_FreeJson(zone, merged);
                }
                else {
                    TRI_ReplaceObjectJson(zone, r, key->_value._string.data, value);
                }
            }
        }

    }

    return result.release();
}
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;
}
KeyGenerator* KeyGenerator::factory (TRI_json_t const* options) {
  KeyGenerator::GeneratorType type;

  bool const readOptions = TRI_IsObjectJson(options);

  if (readOptions) {
    type = generatorType(options);
  }
  else {
    type = TYPE_TRADITIONAL;
  }

  if (type == TYPE_UNKNOWN) {
    return nullptr;
  }

  bool allowUserKeys = true;

  if (readOptions) {
    TRI_json_t* option = TRI_LookupObjectJson(options, "allowUserKeys");

    if (TRI_IsBooleanJson(option)) {
      allowUserKeys = option->_value._boolean;
    }
  }

  if (type == TYPE_TRADITIONAL) {
    return new TraditionalKeyGenerator(allowUserKeys);
  }

  else if (type == TYPE_AUTOINCREMENT) {
    uint64_t offset = 0;
    uint64_t increment = 1;

    if (readOptions) {
      TRI_json_t* option;

      option = TRI_LookupObjectJson(options, "increment");

      if (TRI_IsNumberJson(option)) {
        if (option->_value._number <= 0.0) {
          // negative or 0 offset is not allowed
          return nullptr;
        }

        increment = static_cast<uint64_t>(option->_value._number);

        if (increment == 0 || increment >= (1ULL << 16)) {
          return nullptr;
        }
      }
    
      option = TRI_LookupObjectJson(options, "offset");

      if (TRI_IsNumberJson(option)) {
        if (option->_value._number < 0.0) {
          return nullptr;
        }
       
        offset = static_cast<uint64_t>(option->_value._number);

        if (offset >= UINT64_MAX) {
          return nullptr;
        }
      }
    }

    return new AutoIncrementKeyGenerator(allowUserKeys, offset, increment);
  }

  return nullptr;
}