text:
using namespace arangodb;

/// @brief the _key attribute, which, when used in an index, will implictly make
/// it unique
static std::vector<arangodb::basics::AttributeName> const KeyAttribute{
    arangodb::basics::AttributeName("_key", false)};

// .............................................................................
// recall for all of the following comparison functions:
//
// left < right  return -1
// left > right  return  1
// left == right return  0
//
// furthermore:
//
// the following order is currently defined for placing an order on documents
// undef < null < boolean < number < strings < lists < hash arrays
// note: undefined will be treated as NULL pointer not NULL JSON OBJECT
// within each type class we have the following order
// boolean: false < true
// number: natural order
// strings: lexicographical
// lists: lexicographically and within each slot according to these rules.
// ...........................................................................

RocksDBVPackUniqueIndexIterator::RocksDBVPackUniqueIndexIterator(
    LogicalCollection* collection, transaction::Methods* trx,
    ManagedDocumentResult* mmdr, arangodb::RocksDBVPackIndex const* index,
    VPackSlice const& indexValues)
    : IndexIterator(collection, trx, mmdr, index),
      _index(index),
      _cmp(index->comparator()),
      _key(trx),
      _done(false) {
  TRI_ASSERT(index->columnFamily() == RocksDBColumnFamily::vpack());
  _key->constructUniqueVPackIndexValue(index->objectId(), indexValues);
}

/// @brief Reset the cursor
void RocksDBVPackUniqueIndexIterator::reset() {
  TRI_ASSERT(_trx->state()->isRunning());

  _done = false;
}

bool RocksDBVPackUniqueIndexIterator::next(LocalDocumentIdCallback const& cb, size_t limit) {
  TRI_ASSERT(_trx->state()->isRunning());

  if (limit == 0 || _done) {
    // already looked up something
    return false;
  }

  _done = true;

  auto value = RocksDBValue::Empty(RocksDBEntryType::PrimaryIndexValue);
  RocksDBMethods* mthds = RocksDBTransactionState::toMethods(_trx);
  arangodb::Result r = mthds->Get(_index->columnFamily(), _key.ref(), value.buffer());

  if (r.ok()) {
    cb(LocalDocumentId(RocksDBValue::revisionId(*value.buffer())));
  }

  // there is at most one element, so we are done now
  return false;
}

RocksDBVPackIndexIterator::RocksDBVPackIndexIterator(
    LogicalCollection* collection, transaction::Methods* trx,
    ManagedDocumentResult* mmdr, arangodb::RocksDBVPackIndex const* index,
    bool reverse, RocksDBKeyBounds&& bounds)
    : IndexIterator(collection, trx, mmdr, index),
      _index(index),
      _cmp(index->comparator()),
      _reverse(reverse),
      _bounds(std::move(bounds)) {
  TRI_ASSERT(index->columnFamily() == RocksDBColumnFamily::vpack());

  RocksDBMethods* mthds = RocksDBTransactionState::toMethods(trx);
  rocksdb::ReadOptions options = mthds->readOptions();
  if (!reverse) {
    // we need to have a pointer to a slice for the upper bound
    // so we need to assign the slice to an instance variable here
    _upperBound = _bounds.end();
    options.iterate_upper_bound = &_upperBound;
  }

  TRI_ASSERT(options.prefix_same_as_start);
  _iterator = mthds->NewIterator(options, index->columnFamily());
  if (reverse) {
    _iterator->SeekForPrev(_bounds.end());
  } else {
    _iterator->Seek(_bounds.start());
  }
}

/// @brief Reset the cursor
void RocksDBVPackIndexIterator::reset() {
  TRI_ASSERT(_trx->state()->isRunning());

  if (_reverse) {
    _iterator->SeekForPrev(_bounds.end());
  } else {
    _iterator->Seek(_bounds.start());
  }
}

bool RocksDBVPackIndexIterator::outOfRange() const {
  TRI_ASSERT(_trx->state()->isRunning());
  if (_reverse) {
    return (_cmp->Compare(_iterator->key(), _bounds.start()) < 0);
  } else {
    return (_cmp->Compare(_iterator->key(), _bounds.end()) > 0);
  }
}

bool RocksDBVPackIndexIterator::next(LocalDocumentIdCallback const& cb, size_t limit) {
  TRI_ASSERT(_trx->state()->isRunning());

  if (limit == 0 || !_iterator->Valid() || outOfRange()) {
    // No limit no data, or we are actually done. The last call should have
    // returned false
    TRI_ASSERT(limit > 0);  // Someone called with limit == 0. Api broken
    return false;
  }

  while (limit > 0) {
    TRI_ASSERT(_index->objectId() == RocksDBKey::objectId(_iterator->key()));

    LocalDocumentId const documentId(
        _index->_unique
            ? RocksDBValue::revisionId(_iterator->value())
            : RocksDBKey::revisionId(_bounds.type(), _iterator->key()));
    cb(documentId);

    --limit;
    if (_reverse) {
      _iterator->Prev();
    } else {
      _iterator->Next();
    }

    if (!_iterator->Valid() || outOfRange()) {
      return false;
    }
  }

  return true;
}

uint64_t RocksDBVPackIndex::HashForKey(const rocksdb::Slice& key) {
  // NOTE: This function needs to use the same hashing on the
  // indexed VPack as the initial inserter does
  VPackSlice tmp = RocksDBKey::indexedVPack(key);
  return tmp.normalizedHash();
}

/// @brief create the index
RocksDBVPackIndex::RocksDBVPackIndex(TRI_idx_iid_t iid,
                                     arangodb::LogicalCollection* collection,
                                     arangodb::velocypack::Slice const& info)
    : RocksDBIndex(iid, collection, info, RocksDBColumnFamily::vpack(), false),
      _deduplicate(arangodb::basics::VelocyPackHelper::getBooleanValue(
          info, "deduplicate", true)),
      _useExpansion(false),
      _allowPartialIndex(true),
      _estimator(nullptr) {
  TRI_ASSERT(_cf == RocksDBColumnFamily::vpack());

  if (!_unique && !ServerState::instance()->isCoordinator()) {
    // We activate the estimator for all non unique-indexes.
    // And only on DBServers
    _estimator = std::make_unique<RocksDBCuckooIndexEstimator<uint64_t>>(
        RocksDBIndex::ESTIMATOR_SIZE);
    TRI_ASSERT(_estimator != nullptr);
  }
  TRI_ASSERT(!_fields.empty());

  TRI_ASSERT(iid != 0);

  fillPaths(_paths, _expanding);

  for (auto const& it : _fields) {
    if (TRI_AttributeNamesHaveExpansion(it)) {
      _useExpansion = true;
      break;
    }
  }
}

/// @brief destroy the index
RocksDBVPackIndex::~RocksDBVPackIndex() {}

double RocksDBVPackIndex::selectivityEstimateLocal(
    arangodb::StringRef const*) const {
  TRI_ASSERT(!ServerState::instance()->isCoordinator());
  TRI_ASSERT(!_unique);
  TRI_ASSERT(_estimator != nullptr);
  return _estimator->computeEstimate();
}

/// @brief return a VelocyPack representation of the index
void RocksDBVPackIndex::toVelocyPack(VPackBuilder& builder, bool withFigures,
                                     bool forPersistence) const {
  TRI_ASSERT(builder.isOpenArray() || builder.isEmpty());
  builder.openObject();
  RocksDBIndex::toVelocyPack(builder, withFigures, forPersistence);
  builder.add("unique", VPackValue(_unique));
  builder.add("sparse", VPackValue(_sparse));
  builder.add("deduplicate", VPackValue(_deduplicate));
  builder.close();
}

/// @brief whether or not the index is implicitly unique
/// this can be the case if the index is not declared as unique,
/// but contains a unique attribute such as _key
bool RocksDBVPackIndex::implicitlyUnique() const {
  if (_unique) {
    // a unique index is always unique
    return true;
  }
  if (_useExpansion) {
    // when an expansion such as a[*] is used, the index may not be unique,
    // even if it contains attributes that are guaranteed to be unique
    return false;
  }

  for (auto const& it : _fields) {
    // if _key is contained in the index fields definition, then the index is
    // implicitly unique
    if (it == KeyAttribute) {
      return true;
    }
  }

  // _key not contained
  return false;
}

/// @brief helper function to insert a document into any index type
/// Should result in an elements vector filled with the new index entries
/// uses the _unique field to determine the kind of key structure
int RocksDBVPackIndex::fillElement(VPackBuilder& leased,
                                   LocalDocumentId const& documentId,
                                   VPackSlice const& doc,
                                   std::vector<RocksDBKey>& elements,
                                   std::vector<uint64_t>& hashes) {
  if (doc.isNone()) {
    LOG_TOPIC(ERR, arangodb::Logger::FIXME)
        << "encountered invalid marker with slice of type None";
    return TRI_ERROR_INTERNAL;
  }

  TRI_IF_FAILURE("FillElementIllegalSlice") { return TRI_ERROR_INTERNAL; }

  TRI_ASSERT(leased.isEmpty());
  if (!_useExpansion) {
    // fast path for inserts... no array elements used
    leased.openArray();

    size_t const n = _paths.size();
    for (size_t i = 0; i < n; ++i) {
      TRI_ASSERT(!_paths[i].empty());

      VPackSlice slice = doc.get(_paths[i]);
      if (slice.isNone() || slice.isNull()) {
        // attribute not found
        if (_sparse) {
          // if sparse we do not have to index, this is indicated by result
          // being shorter than n
          return TRI_ERROR_NO_ERROR;
        }
        // null, note that this will be copied later!
        leased.add(VPackSlice::nullSlice());
      } else {
        leased.add(slice);
      }
    }
    leased.close();

    TRI_IF_FAILURE("FillElementOOM") { return TRI_ERROR_OUT_OF_MEMORY; }
    TRI_IF_FAILURE("FillElementOOM2") {
      THROW_ARANGO_EXCEPTION(TRI_ERROR_OUT_OF_MEMORY);
    }

    if (_unique) {
      // Unique VPack index values are stored as follows:
      // - Key: 7 + 8-byte object ID of index + VPack array with index
      // value(s) + separator (NUL) byte
      // - Value: primary key
      RocksDBKey key;
      key.constructUniqueVPackIndexValue(_objectId, leased.slice());
      elements.emplace_back(std::move(key));
    } else {
      // Non-unique VPack index values are stored as follows:
      // - Key: 6 + 8-byte object ID of index + VPack array with index
      // value(s) + revisionID
      // - Value: empty
      RocksDBKey key;
      key.constructVPackIndexValue(_objectId, leased.slice(), documentId.id());
      elements.emplace_back(std::move(key));
      hashes.push_back(leased.slice().normalizedHash());
    }
  } else {
    // other path for handling array elements, too

    std::vector<VPackSlice> sliceStack;
    try {
      buildIndexValues(leased, documentId, doc, 0, elements, sliceStack, hashes);
    } catch (arangodb::basics::Exception const& ex) {
      return ex.code();
    } catch (std::bad_alloc const&) {
      return TRI_ERROR_OUT_OF_MEMORY;
    } catch (...) {
      // unknown error
      return TRI_ERROR_INTERNAL;
    }
  }

  return TRI_ERROR_NO_ERROR;
}

void RocksDBVPackIndex::addIndexValue(VPackBuilder& leased,
                                      LocalDocumentId const& documentId,
                                      VPackSlice const& document,
                                      std::vector<RocksDBKey>& elements,
                                      std::vector<VPackSlice>& sliceStack,
                                      std::vector<uint64_t>& hashes) {
  leased.clear();
  leased.openArray(true);  // unindexed
  for (VPackSlice const& s : sliceStack) {
    leased.add(s);
  }
  leased.close();

  if (_unique) {
    // Unique VPack index values are stored as follows:
    // - Key: 7 + 8-byte object ID of index + VPack array with index value(s)
    // - Value: primary key
    RocksDBKey key;
    key.constructUniqueVPackIndexValue(_objectId, leased.slice());
    elements.emplace_back(std::move(key));
  } else {
    // Non-unique VPack index values are stored as follows:
    // - Key: 6 + 8-byte object ID of index + VPack array with index value(s)
    // + primary key
    // - Value: empty
    RocksDBKey key;
    key.constructVPackIndexValue(_objectId, leased.slice(), documentId.id());
    elements.emplace_back(std::move(key));
    hashes.push_back(leased.slice().normalizedHash());
  }
}

/// @brief helper function to create a set of index combinations to insert
void RocksDBVPackIndex::buildIndexValues(VPackBuilder& leased,
                                         LocalDocumentId const& documentId,
                                         VPackSlice const doc, size_t level,
                                         std::vector<RocksDBKey>& elements,
                                         std::vector<VPackSlice>& sliceStack,
                                         std::vector<uint64_t>& hashes) {
  // Invariant: level == sliceStack.size()

  // Stop the recursion:
  if (level == _paths.size()) {
    addIndexValue(leased, documentId, doc, elements, sliceStack, hashes);
    return;
  }

  if (_expanding[level] == -1) {  // the trivial, non-expanding case
    VPackSlice slice = doc.get(_paths[level]);
    if (slice.isNone() || slice.isNull()) {
      if (_sparse) {
        return;
      }
      sliceStack.emplace_back(arangodb::basics::VelocyPackHelper::NullValue());
    } else {
      sliceStack.emplace_back(slice);
    }
    buildIndexValues(leased, documentId, doc, level + 1, elements, sliceStack,
                     hashes);
    sliceStack.pop_back();
    return;
  }

  // Finally, the complex case, where we have to expand one entry.
  // Note again that at most one step in the attribute path can be
  // an array step. Furthermore, if _allowPartialIndex is true and
  // anything goes wrong with this attribute path, we have to bottom out
  // with None values to be able to use the index for a prefix match.

  // Trivial case to bottom out with Illegal types.
  VPackSlice illegalSlice = arangodb::basics::VelocyPackHelper::IllegalValue();

  auto finishWithNones = [&]() -> void {
    if (!_allowPartialIndex || level == 0) {
      return;
    }
    for (size_t i = level; i < _paths.size(); i++) {
      sliceStack.emplace_back(illegalSlice);
    }
    addIndexValue(leased, documentId, doc, elements, sliceStack, hashes);
    for (size_t i = level; i < _paths.size(); i++) {
      sliceStack.pop_back();
    }
  };
  size_t const n = _paths[level].size();
  // We have 0 <= _expanding[level] < n.
  VPackSlice current(doc);
  for (size_t i = 0; i <= static_cast<size_t>(_expanding[level]); i++) {
    if (!current.isObject()) {
      finishWithNones();
      return;
    }
    current = current.get(_paths[level][i]);
    if (current.isNone()) {
      finishWithNones();
      return;
    }
  }
  // Now the expansion:
  if (!current.isArray() || current.length() == 0) {
    finishWithNones();
    return;
  }

  std::unordered_set<VPackSlice, arangodb::basics::VelocyPackHelper::VPackHash,
                     arangodb::basics::VelocyPackHelper::VPackEqual>
      seen(2, arangodb::basics::VelocyPackHelper::VPackHash(),
           arangodb::basics::VelocyPackHelper::VPackEqual());

  auto moveOn = [&](VPackSlice something) -> void {
    auto it = seen.find(something);
    if (it == seen.end()) {
      seen.insert(something);
      sliceStack.emplace_back(something);
      buildIndexValues(leased, documentId, doc, level + 1, elements, sliceStack,
                       hashes);
      sliceStack.pop_back();
    } else if (_unique && !_deduplicate) {
      THROW_ARANGO_EXCEPTION(TRI_ERROR_ARANGO_UNIQUE_CONSTRAINT_VIOLATED);
    }
  };
  for (auto const& member : VPackArrayIterator(current)) {
    VPackSlice current2(member);
    bool doneNull = false;
    for (size_t i = _expanding[level] + 1; i < n; i++) {
      if (!current2.isObject()) {
        if (!_sparse) {
          moveOn(arangodb::basics::VelocyPackHelper::NullValue());
        }
        doneNull = true;
        break;
      }
      current2 = current2.get(_paths[level][i]);
      if (current2.isNone()) {
        if (!_sparse) {
          moveOn(arangodb::basics::VelocyPackHelper::NullValue());
        }
        doneNull = true;
        break;
      }
    }
    if (!doneNull) {
      moveOn(current2);
    }
    // Finally, if, because of sparsity, we have not inserted anything by now,
    // we need to play the above trick with None because of the above
    // mentioned
    // reasons:
    if (seen.empty()) {
      finishWithNones();
    }
  }
}

/// @brief helper function to transform AttributeNames into strings.
void RocksDBVPackIndex::fillPaths(std::vector<std::vector<std::string>>& paths,
                                  std::vector<int>& expanding) {
  paths.clear();
  expanding.clear();
  for (std::vector<arangodb::basics::AttributeName> const& list : _fields) {
    paths.emplace_back();
    std::vector<std::string>& interior(paths.back());
    int expands = -1;
    int count = 0;
    for (auto const& att : list) {
      interior.emplace_back(att.name);
      if (att.shouldExpand) {
        expands = count;
      }
      ++count;
    }
    expanding.emplace_back(expands);
  }
}

/// @brief inserts a document into the index
Result RocksDBVPackIndex::insertInternal(transaction::Methods* trx,
                                         RocksDBMethods* mthds,
                                         LocalDocumentId const& documentId,
                                         VPackSlice const& doc) {
  std::vector<RocksDBKey> elements;
  std::vector<uint64_t> hashes;
  int res = TRI_ERROR_NO_ERROR;
  {
    // rethrow all types of exceptions from here...
    transaction::BuilderLeaser leased(trx);
    res = fillElement(*(leased.get()), documentId, doc, elements, hashes);
  }
  if (res != TRI_ERROR_NO_ERROR) {
    return IndexResult(res, this);
  }

  // now we are going to construct the value to insert into rocksdb
  // unique indexes have a different key structure
  RocksDBValue value = _unique ? RocksDBValue::UniqueVPackIndexValue(documentId.id())
                               : RocksDBValue::VPackIndexValue();

  size_t const count = elements.size();
  for (size_t i = 0; i < count; ++i) {
    RocksDBKey& key = elements[i];
    if (_unique) {
      RocksDBValue existing =
          RocksDBValue::Empty(RocksDBEntryType::UniqueVPackIndexValue);
      if (mthds->Exists(_cf, key)) {
        res = TRI_ERROR_ARANGO_UNIQUE_CONSTRAINT_VIOLATED;
      }
    }

    if (res == TRI_ERROR_NO_ERROR) {
      arangodb::Result r =
          mthds->Put(_cf, key, value.string(), rocksutils::index);
      if (!r.ok()) {
        res = r.errorNumber();
      }
    }

    if (res != TRI_ERROR_NO_ERROR) {
      for (size_t j = 0; j < i; ++j) {
        mthds->Delete(_cf, elements[j]);
      }

      if (res == TRI_ERROR_ARANGO_UNIQUE_CONSTRAINT_VIOLATED && !_unique) {
        // We ignore unique_constraint violated if we are not unique
        res = TRI_ERROR_NO_ERROR;
        // TODO: remove this? seems dangerous...
      }
      break;
    }
  }

  if (res == TRI_ERROR_NO_ERROR) {
    for (auto& it : hashes) {
      // The estimator is only useful if we are in a non-unique indexes
      TRI_ASSERT(!_unique);
      _estimator->insert(it);
    }
  }

  return IndexResult(res, this);
}

Result RocksDBVPackIndex::updateInternal(transaction::Methods* trx,
                                         RocksDBMethods* mthds,
                                         LocalDocumentId const& oldDocumentId,
                                         arangodb::velocypack::Slice const& oldDoc,
                                         LocalDocumentId const& newDocumentId,
                                         velocypack::Slice const& newDoc) {

  if (!_unique || _useExpansion) {
    // only unique index supports in-place updates
    // lets also not handle the complex case of expanded arrays
    return RocksDBIndex::updateInternal(trx, mthds, oldDocumentId, oldDoc,
                                        newDocumentId, newDoc);
  } else {

    bool equal = true;
    for (size_t i = 0; i < _paths.size(); ++i) {
      TRI_ASSERT(!_paths[i].empty());
      VPackSlice oldSlice = oldDoc.get(_paths[i]);
      VPackSlice newSlice = newDoc.get(_paths[i]);
      if ((oldSlice.isNone() || oldSlice.isNull()) &&
          (newSlice.isNone() || newSlice.isNull())) {
        // attribute not found
        if (_sparse) {
          // if sparse we do not have to index, this is indicated by result
          // being shorter than n
          return TRI_ERROR_NO_ERROR;
        }
      } else if (basics::VelocyPackHelper::compare(oldSlice, newSlice, true)) {
        equal = false;
        break;
      }
    }
    if (!equal) {
      // we can only use in-place updates if no indexed attributes changed
      return RocksDBIndex::updateInternal(trx, mthds, oldDocumentId, oldDoc,
                                          newDocumentId, newDoc);
    }

    // more expansive method to
    std::vector<RocksDBKey> elements;
    std::vector<uint64_t> hashes;
    int res = TRI_ERROR_NO_ERROR;
    {
      // rethrow all types of exceptions from here...
      transaction::BuilderLeaser leased(trx);
      res = fillElement(*(leased.get()), newDocumentId, newDoc, elements, hashes);
    }
    if (res != TRI_ERROR_NO_ERROR) {
      return IndexResult(res, this);
    }

    RocksDBValue value = RocksDBValue::UniqueVPackIndexValue(newDocumentId.id());
    size_t const count = elements.size();
    for (size_t i = 0; i < count; ++i) {
      RocksDBKey& key = elements[i];
      if (res == TRI_ERROR_NO_ERROR) {
        arangodb::Result r =
        mthds->Put(_cf, key, value.string(), rocksutils::index);
        if (!r.ok()) {
          res = r.errorNumber();
        }
      }
      // fix the inserts again
      if (res != TRI_ERROR_NO_ERROR) {
        for (size_t j = 0; j < i; ++j) {
          mthds->Delete(_cf, elements[j]);
        }
        break;
      }
    }

    return res;
  }
}

/// @brief removes a document from the index
Result RocksDBVPackIndex::removeInternal(transaction::Methods* trx,
                                         RocksDBMethods* mthds,
                                         LocalDocumentId const& documentId,
                                         VPackSlice const& doc) {
  std::vector<RocksDBKey> elements;
  std::vector<uint64_t> hashes;
  int res = TRI_ERROR_NO_ERROR;
  {
    // rethrow all types of exceptions from here...
    transaction::BuilderLeaser leased(trx);
    res = fillElement(*(leased.get()), documentId, doc, elements, hashes);
  }
  if (res != TRI_ERROR_NO_ERROR) {
    return IndexResult(res, this);
  }

  size_t const count = elements.size();
  for (size_t i = 0; i < count; ++i) {
    arangodb::Result r = mthds->Delete(_cf, elements[i]);
    if (!r.ok()) {
      res = r.errorNumber();
    }
  }

  if (res == TRI_ERROR_NO_ERROR) {
    for (auto& it : hashes) {
      // The estimator is only useful if we are in a non-unique indexes
      TRI_ASSERT(!_unique);
      _estimator->remove(it);
    }
  }

  return IndexResult(res, this);
}

/// @brief attempts to locate an entry in the index
/// Warning: who ever calls this function is responsible for destroying
/// the RocksDBVPackIndexIterator* results
IndexIterator* RocksDBVPackIndex::lookup(
    transaction::Methods* trx, ManagedDocumentResult* mmdr,
    VPackSlice const searchValues, bool reverse) const {
  TRI_ASSERT(searchValues.isArray());
  TRI_ASSERT(searchValues.length() <= _fields.size());

  VPackBuilder leftSearch;

  VPackSlice lastNonEq;
  leftSearch.openArray();
  for (auto const& it : VPackArrayIterator(searchValues)) {
    TRI_ASSERT(it.isObject());
    VPackSlice eq = it.get(StaticStrings::IndexEq);
    if (eq.isNone()) {
      lastNonEq = it;
      break;
    }
    leftSearch.add(eq);
  }

  if (lastNonEq.isNone() && _unique && searchValues.length() == _fields.size()) {
    leftSearch.close();

    return new RocksDBVPackUniqueIndexIterator(_collection, trx, mmdr, this, leftSearch.slice());
  }

  VPackSlice leftBorder;
  VPackSlice rightBorder;

  VPackBuilder rightSearch;

  if (lastNonEq.isNone()) {
    // We only have equality!
    rightSearch = leftSearch;

    leftSearch.add(VPackSlice::minKeySlice());
    leftSearch.close();

    rightSearch.add(VPackSlice::maxKeySlice());
    rightSearch.close();

    leftBorder = leftSearch.slice();
    rightBorder = rightSearch.slice();
  } else {
    // Copy rightSearch = leftSearch for right border
    rightSearch = leftSearch;

    // Define Lower-Bound
    VPackSlice lastLeft = lastNonEq.get(StaticStrings::IndexGe);
    if (!lastLeft.isNone()) {
      TRI_ASSERT(!lastNonEq.hasKey(StaticStrings::IndexGt));
      leftSearch.add(lastLeft);
      leftSearch.add(VPackSlice::minKeySlice());
      leftSearch.close();
      VPackSlice search = leftSearch.slice();
      leftBorder = search;
    } else {
      lastLeft = lastNonEq.get(StaticStrings::IndexGt);
      if (!lastLeft.isNone()) {
        leftSearch.add(lastLeft);
        leftSearch.add(VPackSlice::maxKeySlice());
        leftSearch.close();
        VPackSlice search = leftSearch.slice();
        leftBorder = search;
      } else {
        // No lower bound set default to (null <= x)
        leftSearch.add(VPackSlice::minKeySlice());
        leftSearch.close();
        VPackSlice search = leftSearch.slice();
        leftBorder = search;
      }
    }

    // Define upper-bound
    VPackSlice lastRight = lastNonEq.get(StaticStrings::IndexLe);
    if (!lastRight.isNone()) {
      TRI_ASSERT(!lastNonEq.hasKey(StaticStrings::IndexLt));
      rightSearch.add(lastRight);
      rightSearch.add(VPackSlice::maxKeySlice());
      rightSearch.close();
      VPackSlice search = rightSearch.slice();
      rightBorder = search;
    } else {
      lastRight = lastNonEq.get(StaticStrings::IndexLt);
      if (!lastRight.isNone()) {
        rightSearch.add(lastRight);
        rightSearch.add(VPackSlice::minKeySlice());
        rightSearch.close();
        VPackSlice search = rightSearch.slice();
        rightBorder = search;
      } else {
        // No upper bound set default to (x <= INFINITY)
        rightSearch.add(VPackSlice::maxKeySlice());
        rightSearch.close();
        VPackSlice search = rightSearch.slice();
        rightBorder = search;
      }
    }
  }

  RocksDBKeyBounds bounds = _unique ? RocksDBKeyBounds::UniqueVPackIndex(
                                          _objectId, leftBorder, rightBorder)
                                    : RocksDBKeyBounds::VPackIndex(
                                          _objectId, leftBorder, rightBorder);

  return new RocksDBVPackIndexIterator(_collection, trx, mmdr, this, reverse, std::move(bounds));
}

bool RocksDBVPackIndex::accessFitsIndex(
    arangodb::aql::AstNode const* access, arangodb::aql::AstNode const* other,
    arangodb::aql::AstNode const* op, arangodb::aql::Variable const* reference,
    std::unordered_map<size_t, std::vector<arangodb::aql::AstNode const*>>&
        found,
    std::unordered_set<std::string>& nonNullAttributes,
    bool isExecution) const {
  if (!canUseConditionPart(access, other, op, reference, nonNullAttributes,
                           isExecution)) {
    return false;
  }

  arangodb::aql::AstNode const* what = access;
  std::pair<arangodb::aql::Variable const*,
            std::vector<arangodb::basics::AttributeName>>
      attributeData;

  if (op->type != arangodb::aql::NODE_TYPE_OPERATOR_BINARY_IN) {
    if (!what->isAttributeAccessForVariable(attributeData) ||
        attributeData.first != reference) {
      // this access is not referencing this collection
      return false;
    }
    if (arangodb::basics::TRI_AttributeNamesHaveExpansion(
            attributeData.second)) {
      // doc.value[*] == 'value'
      return false;
    }
    if (isAttributeExpanded(attributeData.second)) {
      // doc.value == 'value' (with an array index)
      return false;
    }
  } else {
    // ok, we do have an IN here... check if it's something like 'value' IN
    // doc.value[*]
    TRI_ASSERT(op->type == arangodb::aql::NODE_TYPE_OPERATOR_BINARY_IN);
    bool canUse = false;

    if (what->isAttributeAccessForVariable(attributeData) &&
        attributeData.first == reference &&
        !arangodb::basics::TRI_AttributeNamesHaveExpansion(
            attributeData.second) &&
        attributeMatches(attributeData.second)) {
      // doc.value IN 'value'
      // can use this index
      canUse = true;
    } else {
      // check for  'value' IN doc.value  AND  'value' IN doc.value[*]
      what = other;
      if (what->isAttributeAccessForVariable(attributeData) &&
          attributeData.first == reference &&
          isAttributeExpanded(attributeData.second) &&
          attributeMatches(attributeData.second)) {
        canUse = true;
      }
    }

    if (!canUse) {
      return false;
    }
  }

  std::vector<arangodb::basics::AttributeName> const& fieldNames =
      attributeData.second;

  for (size_t i = 0; i < _fields.size(); ++i) {
    if (_fields[i].size() != fieldNames.size()) {
      // attribute path length differs
      continue;
    }

    if (this->isAttributeExpanded(i) &&
        op->type != arangodb::aql::NODE_TYPE_OPERATOR_BINARY_IN) {
      // If this attribute is correct or not, it could only serve for IN
      continue;
    }

    bool match = arangodb::basics::AttributeName::isIdentical(_fields[i],
                                                              fieldNames, true);

    if (match) {
      // mark ith attribute as being covered
      auto it = found.find(i);

      if (it == found.end()) {
        found.emplace(i, std::vector<arangodb::aql::AstNode const*>{op});
      } else {
        (*it).second.emplace_back(op);
      }
      TRI_IF_FAILURE("PersistentIndex::accessFitsIndex") {
        THROW_ARANGO_EXCEPTION(TRI_ERROR_DEBUG);
      }
      TRI_IF_FAILURE("SkiplistIndex::accessFitsIndex") {
        THROW_ARANGO_EXCEPTION(TRI_ERROR_DEBUG);
      }
      TRI_IF_FAILURE("HashIndex::accessFitsIndex") {
        THROW_ARANGO_EXCEPTION(TRI_ERROR_DEBUG);
      }

      return true;
    }
  }

  return false;
}

void RocksDBVPackIndex::matchAttributes(
    arangodb::aql::AstNode const* node,
    arangodb::aql::Variable const* reference,
    std::unordered_map<size_t, std::vector<arangodb::aql::AstNode const*>>&
        found,
    size_t& values, std::unordered_set<std::string>& nonNullAttributes,
    bool isExecution) const {
  for (size_t i = 0; i < node->numMembers(); ++i) {
    auto op = node->getMember(i);

    switch (op->type) {
      case arangodb::aql::NODE_TYPE_OPERATOR_BINARY_EQ:
      case arangodb::aql::NODE_TYPE_OPERATOR_BINARY_LT:
      case arangodb::aql::NODE_TYPE_OPERATOR_BINARY_LE:
      case arangodb::aql::NODE_TYPE_OPERATOR_BINARY_GT:
      case arangodb::aql::NODE_TYPE_OPERATOR_BINARY_GE:
        TRI_ASSERT(op->numMembers() == 2);
        accessFitsIndex(op->getMember(0), op->getMember(1), op, reference,
                        found, nonNullAttributes, isExecution);
        accessFitsIndex(op->getMember(1), op->getMember(0), op, reference,
                        found, nonNullAttributes, isExecution);
        break;

      case arangodb::aql::NODE_TYPE_OPERATOR_BINARY_IN:
        if (accessFitsIndex(op->getMember(0), op->getMember(1), op, reference,
                            found, nonNullAttributes, isExecution)) {
          size_t av = SimpleAttributeEqualityMatcher::estimateNumberOfArrayMembers(op->getMember(1));
          if (av > 1) {
            // attr IN [ a, b, c ]  =>  this will produce multiple items, so
            // count them!
            values += av - 1;
          }
        }
        break;

      default:
        break;
    }
  }
}

bool RocksDBVPackIndex::supportsFilterCondition(
    arangodb::aql::AstNode const* node,
    arangodb::aql::Variable const* reference, size_t itemsInIndex,
    size_t& estimatedItems, double& estimatedCost) const {
  // mmfiles failure point compat
  if (this->type() == Index::TRI_IDX_TYPE_HASH_INDEX) {
    TRI_IF_FAILURE("SimpleAttributeMatcher::accessFitsIndex") {
      THROW_ARANGO_EXCEPTION(TRI_ERROR_DEBUG);
    }
  }

  std::unordered_map<size_t, std::vector<arangodb::aql::AstNode const*>> found;
  std::unordered_set<std::string> nonNullAttributes;
  size_t values = 0;
  matchAttributes(node, reference, found, values, nonNullAttributes, false);

  bool lastContainsEquality = true;
  size_t attributesCovered = 0;
  size_t attributesCoveredByEquality = 0;
  double equalityReductionFactor = 20.0;
  estimatedCost = static_cast<double>(itemsInIndex);

  for (size_t i = 0; i < _fields.size(); ++i) {
    auto it = found.find(i);

    if (it == found.end()) {
      // index attribute not covered by condition
      break;
    }

    // check if the current condition contains an equality condition
    auto const& nodes = (*it).second;
    bool containsEquality = false;
    for (size_t j = 0; j < nodes.size(); ++j) {
      if (nodes[j]->type == arangodb::aql::NODE_TYPE_OPERATOR_BINARY_EQ ||
          nodes[j]->type == arangodb::aql::NODE_TYPE_OPERATOR_BINARY_IN) {
        containsEquality = true;
        break;
      }
    }

    if (!lastContainsEquality) {
      // unsupported condition. must abort
      break;
    }

    ++attributesCovered;
    if (containsEquality) {
      ++attributesCoveredByEquality;
      estimatedCost /= equalityReductionFactor;

      // decrease the effect of the equality reduction factor
      equalityReductionFactor *= 0.25;
      if (equalityReductionFactor < 2.0) {
        // equalityReductionFactor shouldn't get too low
        equalityReductionFactor = 2.0;
      }
    } else {
      // quick estimate for the potential reductions caused by the conditions
      if (nodes.size() >= 2) {
        // at least two (non-equality) conditions. probably a range with lower
        // and upper bound defined
        estimatedCost /= 7.5;
      } else {
        // one (non-equality). this is either a lower or a higher bound
        estimatedCost /= 2.0;
      }
    }

    lastContainsEquality = containsEquality;
  }

  if (values == 0) {
    values = 1;
  }

  if (attributesCoveredByEquality == _fields.size() && unique()) {
    // index is unique and condition covers all attributes by equality
    if (itemsInIndex == 0) {
      estimatedItems = 0;
      estimatedCost = 0.0;
      return true;
    }
    estimatedItems = values;
    estimatedCost = 0.995 * values;
    if (values > 0) {
      if (useCache()) {
        estimatedCost = static_cast<double>(estimatedItems * values);
      } else {
        estimatedCost =
             std::max(static_cast<double>(1),
                       std::log2(static_cast<double>(itemsInIndex)) * values);
      }
    }
    // cost is already low... now slightly prioritize unique indexes
    estimatedCost *= 0.995 - 0.05 * (_fields.size() - 1);
    return true;
  }

  if (attributesCovered > 0 &&
      (!_sparse || attributesCovered == _fields.size())) {
    // if the condition contains at least one index attribute and is not
    // sparse,
    // or the index is sparse and all attributes are covered by the condition,
    // then it can be used (note: additional checks for condition parts in
    // sparse indexes are contained in Index::canUseConditionPart)
    estimatedItems = static_cast<size_t>((std::max)(
        static_cast<size_t>(estimatedCost * values), static_cast<size_t>(1)));

    // check if the index has a selectivity estimate ready
    if (attributesCoveredByEquality == _fields.size()) {
      StringRef ignore;
      double estimate = this->selectivityEstimate(&ignore);
      if (estimate > 0.0) {
        estimatedItems = static_cast<size_t>(1.0 / estimate);
      }
    }
    if (itemsInIndex == 0) {
      estimatedCost = 0.0;
    } else {
      if (useCache()) {
        estimatedCost = static_cast<double>(estimatedItems * values);
      } else {
        estimatedCost =
            (std::max)(static_cast<double>(1),
                       std::log2(static_cast<double>(itemsInIndex)) * values);
      }
    }
    return true;
  }

  // index does not help for this condition
  estimatedItems = itemsInIndex;
  estimatedCost = static_cast<double>(estimatedItems);
  return false;
}

bool RocksDBVPackIndex::supportsSortCondition(
    arangodb::aql::SortCondition const* sortCondition,
    arangodb::aql::Variable const* reference, size_t itemsInIndex,
    double& estimatedCost, size_t& coveredAttributes) const {
  TRI_ASSERT(sortCondition != nullptr);

  if (!_sparse) {
    // only non-sparse indexes can be used for sorting
    if (!_useExpansion && sortCondition->isUnidirectional() &&
        sortCondition->isOnlyAttributeAccess()) {
      coveredAttributes = sortCondition->coveredAttributes(reference, _fields);

      if (coveredAttributes >= sortCondition->numAttributes()) {
        // sort is fully covered by index. no additional sort costs!
        // forward iteration does not have high costs
        estimatedCost = itemsInIndex * 0.001;
        if (sortCondition->isDescending()) {
          // reverse iteration has higher costs than forward iteration
          estimatedCost *= 4;
        }
        return true;
      } else if (coveredAttributes > 0) {
        estimatedCost = (itemsInIndex / coveredAttributes) *
                        std::log2(static_cast<double>(itemsInIndex));
        if (sortCondition->isAscending()) {
          // reverse iteration is more expensive
          estimatedCost *= 4;
        }
        return true;
      }
    }
  }

  coveredAttributes = 0;
  // by default no sort conditions are supported
  if (itemsInIndex > 0) {
    estimatedCost = itemsInIndex * std::log2(static_cast<double>(itemsInIndex));
    // slightly penalize this type of index against other indexes which
    // are in memory
    estimatedCost *= 1.05;
  } else {
    estimatedCost = 0.0;
  }
  return false;
}

IndexIterator* RocksDBVPackIndex::iteratorForCondition(
    transaction::Methods* trx, ManagedDocumentResult* mmdr,
    arangodb::aql::AstNode const* node,
    arangodb::aql::Variable const* reference, bool reverse) {
  VPackBuilder searchValues;
  searchValues.openArray();
  bool needNormalize = false;
  if (node == nullptr) {
    // We only use this index for sort. Empty searchValue
    VPackArrayBuilder guard(&searchValues);

    TRI_IF_FAILURE("PersistentIndex::noSortIterator") {
      THROW_ARANGO_EXCEPTION(TRI_ERROR_DEBUG);
    }
    TRI_IF_FAILURE("SkiplistIndex::noSortIterator") {
      THROW_ARANGO_EXCEPTION(TRI_ERROR_DEBUG);
    }
    TRI_IF_FAILURE("HashIndex::noSortIterator") {
      THROW_ARANGO_EXCEPTION(TRI_ERROR_DEBUG);
    }

  } else {
    // Create the search Values for the lookup
    VPackArrayBuilder guard(&searchValues);

    std::unordered_map<size_t, std::vector<arangodb::aql::AstNode const*>>
        found;
    std::unordered_set<std::string> nonNullAttributes;
    size_t unused = 0;
    matchAttributes(node, reference, found, unused, nonNullAttributes, true);

    // found contains all attributes that are relevant for this node.
    // It might be less than fields().
    //
    // Handle the first attributes. They can only be == or IN and only
    // one node per attribute

    auto getValueAccess = [&](arangodb::aql::AstNode const* comp,
                              arangodb::aql::AstNode const*& access,
                              arangodb::aql::AstNode const*& value) -> bool {
      access = comp->getMember(0);
      value = comp->getMember(1);
      std::pair<arangodb::aql::Variable const*,
                std::vector<arangodb::basics::AttributeName>>
          paramPair;
      if (!(access->isAttributeAccessForVariable(paramPair) &&
            paramPair.first == reference)) {
        access = comp->getMember(1);
        value = comp->getMember(0);
        if (!(access->isAttributeAccessForVariable(paramPair) &&
              paramPair.first == reference)) {
          // Both side do not have a correct AttributeAccess, this should not
          // happen and indicates
          // an error in the optimizer
          TRI_ASSERT(false);
        }
        return true;
      }
      return false;
    };

    size_t usedFields = 0;
    for (; usedFields < _fields.size(); ++usedFields) {
      auto it = found.find(usedFields);
      if (it == found.end()) {
        // We are either done
        // or this is a range.
        // Continue with more complicated loop
        break;
      }

      auto comp = it->second[0];
      TRI_ASSERT(comp->numMembers() == 2);
      arangodb::aql::AstNode const* access = nullptr;
      arangodb::aql::AstNode const* value = nullptr;
      getValueAccess(comp, access, value);
      // We found an access for this field

      if (comp->type == arangodb::aql::NODE_TYPE_OPERATOR_BINARY_EQ) {
        searchValues.openObject();
        searchValues.add(VPackValue(StaticStrings::IndexEq));
        TRI_IF_FAILURE("PersistentIndex::permutationEQ") {
          THROW_ARANGO_EXCEPTION(TRI_ERROR_DEBUG);
        }
        TRI_IF_FAILURE("SkiplistIndex::permutationEQ") {
          THROW_ARANGO_EXCEPTION(TRI_ERROR_DEBUG);
        }
        TRI_IF_FAILURE("HashIndex::permutationEQ") {
          THROW_ARANGO_EXCEPTION(TRI_ERROR_DEBUG);
        }
      } else if (comp->type == arangodb::aql::NODE_TYPE_OPERATOR_BINARY_IN) {
        if (isAttributeExpanded(usedFields)) {
          searchValues.openObject();
          searchValues.add(VPackValue(StaticStrings::IndexEq));
          TRI_IF_FAILURE("PersistentIndex::permutationArrayIN") {
            THROW_ARANGO_EXCEPTION(TRI_ERROR_DEBUG);
          }
          TRI_IF_FAILURE("SkiplistIndex::permutationArrayIN") {
            THROW_ARANGO_EXCEPTION(TRI_ERROR_DEBUG);
          }
          TRI_IF_FAILURE("HashIndex::permutationArrayIN") {
            THROW_ARANGO_EXCEPTION(TRI_ERROR_DEBUG);
          }
        } else {
          needNormalize = true;
          searchValues.openObject();
          searchValues.add(VPackValue(StaticStrings::IndexIn));
        }
      } else {
        // This is a one-sided range
        break;
      }
      // We have to add the value always, the key was added before
      value->toVelocyPackValue(searchValues);
      searchValues.close();
    }

    // Now handle the next element, which might be a range
    if (usedFields < _fields.size()) {
      auto it = found.find(usedFields);
      if (it != found.end()) {
        auto rangeConditions = it->second;
        TRI_ASSERT(rangeConditions.size() <= 2);

        VPackObjectBuilder searchElement(&searchValues);
        for (auto& comp : rangeConditions) {
          TRI_ASSERT(comp->numMembers() == 2);
          arangodb::aql::AstNode const* access = nullptr;
          arangodb::aql::AstNode const* value = nullptr;
          bool isReverseOrder = getValueAccess(comp, access, value);
          // Add the key
          switch (comp->type) {
            case arangodb::aql::NODE_TYPE_OPERATOR_BINARY_LT:
              if (isReverseOrder) {
                searchValues.add(VPackValue(StaticStrings::IndexGt));
              } else {
                searchValues.add(VPackValue(StaticStrings::IndexLt));
              }
              break;
            case arangodb::aql::NODE_TYPE_OPERATOR_BINARY_LE:
              if (isReverseOrder) {
                searchValues.add(VPackValue(StaticStrings::IndexGe));
              } else {
                searchValues.add(VPackValue(StaticStrings::IndexLe));
              }
              break;
            case arangodb::aql::NODE_TYPE_OPERATOR_BINARY_GT:
              if (isReverseOrder) {
                searchValues.add(VPackValue(StaticStrings::IndexLt));
              } else {
                searchValues.add(VPackValue(StaticStrings::IndexGt));
              }
              break;
            case arangodb::aql::NODE_TYPE_OPERATOR_BINARY_GE:
              if (isReverseOrder) {
                searchValues.add(VPackValue(StaticStrings::IndexLe));
              } else {
                searchValues.add(VPackValue(StaticStrings::IndexGe));
              }
              break;
            default:
              // unsupported right now. Should have been rejected by
              // supportsFilterCondition
              TRI_ASSERT(false);
              return new EmptyIndexIterator(_collection, trx, mmdr, this);
          }
          value->toVelocyPackValue(searchValues);
        }
      }
    }
  }
  searchValues.close();

  TRI_IF_FAILURE("PersistentIndex::noIterator") {
    THROW_ARANGO_EXCEPTION(TRI_ERROR_DEBUG);
  }
  TRI_IF_FAILURE("SkiplistIndex::noIterator") {
    THROW_ARANGO_EXCEPTION(TRI_ERROR_DEBUG);
  }
  TRI_IF_FAILURE("HashIndex::noIterator") {
    THROW_ARANGO_EXCEPTION(TRI_ERROR_DEBUG);
  }

  if (needNormalize) {
    VPackBuilder expandedSearchValues;
    expandInSearchValues(searchValues.slice(), expandedSearchValues);
    VPackSlice expandedSlice = expandedSearchValues.slice();
    std::vector<IndexIterator*> iterators;
    try {
      for (auto const& val : VPackArrayIterator(expandedSlice)) {
        auto iterator = lookup(trx, mmdr, val, reverse);
        try {
          iterators.push_back(iterator);
        } catch (...) {
          // avoid leak
          delete iterator;
          throw;
        }
      }
      if (reverse) {
        std::reverse(iterators.begin(), iterators.end());
      }
    } catch (...) {
      for (auto& it : iterators) {
        delete it;
      }
      throw;
    }
    return new MultiIndexIterator(_collection, trx, mmdr, this, iterators);
  }

  VPackSlice searchSlice = searchValues.slice();
  TRI_ASSERT(searchSlice.length() == 1);
  searchSlice = searchSlice.at(0);
  return lookup(trx, mmdr, searchSlice, reverse);
}

/// @brief specializes the condition for use with the index
arangodb::aql::AstNode* RocksDBVPackIndex::specializeCondition(
    arangodb::aql::AstNode* node,
    arangodb::aql::Variable const* reference) const {
  // mmfiles failure compat
  if (this->type() == Index::TRI_IDX_TYPE_HASH_INDEX) {
    TRI_IF_FAILURE("SimpleAttributeMatcher::specializeAllChildrenEQ") {
      THROW_ARANGO_EXCEPTION(TRI_ERROR_DEBUG);
    }
    TRI_IF_FAILURE("SimpleAttributeMatcher::specializeAllChildrenIN") {
      THROW_ARANGO_EXCEPTION(TRI_ERROR_DEBUG);
    }
  }

  std::unordered_map<size_t, std::vector<arangodb::aql::AstNode const*>> found;
  std::unordered_set<std::string> nonNullAttributes;
  size_t values = 0;
  matchAttributes(node, reference, found, values, nonNullAttributes, false);

  std::vector<arangodb::aql::AstNode const*> children;
  bool lastContainsEquality = true;

  for (size_t i = 0; i < _fields.size(); ++i) {
    auto it = found.find(i);

    if (it == found.end()) {
      // index attribute not covered by condition
      break;
    }

    // check if the current condition contains an equality condition
    auto& nodes = (*it).second;
    bool containsEquality = false;
    for (size_t j = 0; j < nodes.size(); ++j) {
      if (nodes[j]->type == arangodb::aql::NODE_TYPE_OPERATOR_BINARY_EQ ||
          nodes[j]->type == arangodb::aql::NODE_TYPE_OPERATOR_BINARY_IN) {
        containsEquality = true;
        break;
      }
    }

    if (!lastContainsEquality) {
      // unsupported condition. must abort
      break;
    }

    std::sort(nodes.begin(), nodes.end(),
              [](arangodb::aql::AstNode const* lhs,
                 arangodb::aql::AstNode const* rhs) -> bool {
                return sortWeight(lhs) < sortWeight(rhs);
              });

    lastContainsEquality = containsEquality;
    std::unordered_set<int> operatorsFound;
    for (auto& it : nodes) {
      // do not let duplicate or related operators pass
      if (isDuplicateOperator(it, operatorsFound)) {
        continue;
      }

      operatorsFound.emplace(static_cast<int>(it->type));
      children.emplace_back(it);
    }
  }

  while (node->numMembers() > 0) {
    node->removeMemberUnchecked(0);
  }

  for (auto& it : children) {
    node->addMember(it);
  }
  return node;
}

bool RocksDBVPackIndex::isDuplicateOperator(
    arangodb::aql::AstNode const* node,
    std::unordered_set<int> const& operatorsFound) const {
  auto type = node->type;
  if (operatorsFound.find(static_cast<int>(type)) != operatorsFound.end()) {
    // duplicate operator
    return true;
  }

  if (operatorsFound.find(
          static_cast<int>(arangodb::aql::NODE_TYPE_OPERATOR_BINARY_EQ)) !=
          operatorsFound.end() ||
      operatorsFound.find(
          static_cast<int>(arangodb::aql::NODE_TYPE_OPERATOR_BINARY_IN)) !=
          operatorsFound.end()) {
    return true;
  }

  bool duplicate = false;
  switch (type) {
    case arangodb::aql::NODE_TYPE_OPERATOR_BINARY_LT:
      duplicate = operatorsFound.find(static_cast<int>(
                      arangodb::aql::NODE_TYPE_OPERATOR_BINARY_LE)) !=
                  operatorsFound.end();
      break;
    case arangodb::aql::NODE_TYPE_OPERATOR_BINARY_LE:
      duplicate = operatorsFound.find(static_cast<int>(
                      arangodb::aql::NODE_TYPE_OPERATOR_BINARY_LT)) !=
                  operatorsFound.end();
      break;
    case arangodb::aql::NODE_TYPE_OPERATOR_BINARY_GT:
      duplicate = operatorsFound.find(static_cast<int>(
                      arangodb::aql::NODE_TYPE_OPERATOR_BINARY_GE)) !=
                  operatorsFound.end();
      break;
    case arangodb::aql::NODE_TYPE_OPERATOR_BINARY_GE:
      duplicate = operatorsFound.find(static_cast<int>(
                      arangodb::aql::NODE_TYPE_OPERATOR_BINARY_GT)) !=
                  operatorsFound.end();
      break;
    case arangodb::aql::NODE_TYPE_OPERATOR_BINARY_EQ:
      duplicate = operatorsFound.find(static_cast<int>(
                      arangodb::aql::NODE_TYPE_OPERATOR_BINARY_IN)) !=
                  operatorsFound.end();
      break;
    case arangodb::aql::NODE_TYPE_OPERATOR_BINARY_IN:
      duplicate = operatorsFound.find(static_cast<int>(
                      arangodb::aql::NODE_TYPE_OPERATOR_BINARY_EQ)) !=
                  operatorsFound.end();
      break;
    default: {
      // ignore
    }
  }

  return duplicate;
}

void RocksDBVPackIndex::serializeEstimate(std::string& output) const {
  TRI_ASSERT(!ServerState::instance()->isCoordinator());
  if (!_unique) {
    TRI_ASSERT(_estimator != nullptr);
    _estimator->serialize(output);
  }
}

bool RocksDBVPackIndex::deserializeEstimate(RocksDBCounterManager* mgr) {
  if (_unique || ServerState::instance()->isCoordinator()) {
    return true;
  }
  // We simply drop the current estimator and steal the one from recovery
  // We are than save for resizing issues in our _estimator format
  // and will use the old size.

  TRI_ASSERT(mgr != nullptr);
  auto tmp = mgr->stealIndexEstimator(_objectId);
  if (tmp == nullptr) {
    // We expected to receive a stored index estimate, however we got none.
    // We use the freshly created estimator but have to recompute it.
    return false;
  }
  _estimator.swap(tmp);
  TRI_ASSERT(_estimator != nullptr);
  return true;
}

void RocksDBVPackIndex::recalculateEstimates() {
  if (ServerState::instance()->isCoordinator()) {
    return;
  }
  if (unique()) {
    return;
  }
  TRI_ASSERT(_estimator != nullptr);
  _estimator->clear();

  RocksDBKeyBounds bounds = getBounds();
  rocksutils::iterateBounds(bounds,
                            [this](rocksdb::Iterator* it) {
                              uint64_t hash =
                                  RocksDBVPackIndex::HashForKey(it->key());
                              _estimator->insert(hash);
                            },
                            bounds.columnFamily());
}

Result RocksDBVPackIndex::postprocessRemove(transaction::Methods* trx,
                                            rocksdb::Slice const& key,
                                            rocksdb::Slice const& value) {
  if (!unique()) {
    uint64_t hash = RocksDBVPackIndex::HashForKey(key);
    _estimator->remove(hash);
  }
  return {TRI_ERROR_NO_ERROR};
}

redpajama_set_name: RedPajamaGithub


The nursing shortage in the current U.S. economy has created strong demand for certified nursing assistants. Browse CNA jobs by state and check back again soon for new job listings. The job listings displayed on our site are drawn from various sources including job search engines and health care employers. The job listings are constantly updated and listed in order of most recently posted jobs first.
Hospitals, nursing homes, assisted living facilities, long term care centers and other medical facilities are looking to hire skilled nursing professionals nation wide. Many current nurses are entering retirement and creating CNA job openings for the next generation. Take advantage of current opportunities and find the CNA job that you are looking for.


Buying Safe for Home with Consideration of this 9 Things in Mind is not about any design or interior design, but this is some interesting point to know all of us when thing came to our hard earned valuables.
No one wants to think about the possibility of coming home to find all of their most precious processions missing. Unfortunately, whether the cause is burglary, fire, or a flood, it can happen to anyone. These occurrences are particularly scary because there isn't any warning beforehand.
Luckily, there is a way to protect your family. buying safe for home is a simple way to make sure that your valuables are protected, no matter what.
Now the question is where to start. There are a lot of safes on the market. Before you start shopping, review the questions below. They'll help you focus on your needs and leave you feeling assured that you'll find the right fit.
Before you can start thinking about what kind of safe you'd like to buy, you first need to take stock of which processions are most important to you. Ask yourself: If you were to lose everything at this very moment, what would you be most upset to find missing.
If you're unsure, focus on things that are irreplaceable. Start with paperwork: Identifying documents like birth records and passports are a good bet, as is legal paperwork like a will or deeds to your property. You might also store records related to your investments, debts, or insurance policies.
Beyond all those papers, you could store expensive pieces of jewelry or an external hard drive. Parents of young children could also consider using a safe to keep prescription medication and other danger substances out of their little ones' reach.
i) Is The Safe Accredited?
Don't just take a sales person's word on safety. The majority of home safes will have an accreditation rating somewhere on their exterior. Accreditation by Underwriters Laboratories (UL) have grown to be the industry standard. UL tests safes against a variety of conditions – fire, burglary, – and gives them a rating based on how well and how long they withstand the trial.
Check Glass and Concrete contracting in Curucura House at Buenos Aires, Argentina.
ii) How Much Space Do You Need?
While it may seem self-explanatory, take measurements before going shopping. Most home safes are extremely heavy and tricky to move, so the last thing you want is to bring one home only to find that it doesn't fit where you want it. Even worse, imagine if it fit in the space, but wasn't big enough for all your valuables.
The exterior measurements are pretty easy. You need to account for the height, width, and depth. You'll also want to measure some of your door frames and stairwells, so that you know you'll be able to move the safe into place easily.
For the interior measurements, gather your items together and start by measuring the height, width, and depth. Since most safe interiors are measured in cubic feet, you should to multiply those three numbers and divide that total by 1728 to get an accurate number for shopping. Don't be afraid to buy a bigger safe than you currently need. Sooner or later, you'll appreciate the room to grow.
iii) Which Type Of Safe Should You Choose?
Wall Safes: Wall safes may remind you of sleek spy flicks, but as cool as they seem, they can't support that much weight. They work best when storing paperwork or photographs.
Floor Safes: Floor safes are the most bulky option, but they will give you the most flexibility. Feel free to store your heavier items like jewelry, hard drives, or precious metals.
Weapons Safes: Keep in mind that regular wall and floor safes are not suited for storing weapons. Specialty weapons safes are fitted with extra locks, pry-proof metal, and locking bolt technology to minimize risk of accidents.
There's no law saying limiting you to one safe. If you have a variety of processions that need protection, consider buying safe for home more than one safe rather than trying to force them all into one area.
iv) What Type Of Protection Do You Want?
Unfortunately, not all safes are created equal. Some offer will offer lots of protection against fire but fall down in the face of home invaders or vice versa. Before you purchase a home safe, think about what type of protection you need most.
Keep in mind that, according to Consumer Reports, fire is the most common cause of asset loss, followed by burglary, and then water damage.
Composite safes, or safes that offer some degree of protection against multiple types of invasion, are available. However, the levels to which the provide protection may vary greatly. Weigh each protection standard before making your decision.
v) Which Kind Of Lock Do You Want?
When it comes to locks, you have two options: Dial or Digital. Each has it's advantages and drawbacks, however, so take a minute to think about which kind best suits your needs before making a lasting purchase.
Dial Locks: These will remind you of your locker in school. They're opened rotating the dial right and left until you input your combination. This type of safe is seen as most reliable because there isn't a possibility for an electronic outage or failure. But, in the event you ever need to change the combination, you'll need to contact a professional.
Digital Locks: These are the kind of locks that are used in hotel safes. They open by putting a pin number into a keypad. This type of lock is easier to open than a combination and allows you to access your belongings quickly. However, they do take batteries, which need to be replaced regularly in order to lock the safe effectively.
vi) How Much Can You Spend?
It goes without saying that budget should be a consideration before any big home purchase, but especially when looking into buying safe for home safe. This is one situation where additional costs can add up fast, so be sure to set a price point before heading to the store.
Of course, there is the cost of the safe itself. A basic model is not likely to break the bank. However, be prepared to encounter upgrades such as higher quality steel, extra locks, or additional bolting. Folks who love origination might want pay a little extra for a safe fitted with interior shelving.
After you've decided on the model, there are often a few additional costs to consider. Some companies will offer a guarantee or warranty on their products, which can be used to cover the cost of stolen valuables, if needed. Since safes are so difficult to move, you may have to pay an additional installation cost to put yours in place.
vii) Will Your Safe Serve You?
Despite all of the other valid concerns on this list, our last tip might be the most important. After you've heard about all a safe's different features and verified that it will fit your most precious belongings, take a second to step back from the sales pitch. Ask yourself if you can picture your family using that safe on a regular basis.
After all, buying safe for home a high-end piece of safe-keeping technology won't matter if you forget to put your important documents into it. Make sure that what you are purchasing is functional for your lifestyle, even if that means choosing a less flashy model.
Then, once you get your purchase home and installed, make sure to actually use it. Force yourself to gather those special items and move them into your new home. Remember, it takes 28 days to make a habit stick, so if you're in the habit of retrieving those items regularly, do your best to stick to your new routine for the first month.
viii) Where Will You Keep Your Safe?
Believe it or not, the place where you keep could make all the difference in whether or not your valuables. Take a second to think about where you are planning on putting the safe in your own home.
We're willing to bet that your mind automatically went to the master bedroom. The folks at Casoro Jewelry Safes say that the master bedroom closet is the most popular location. In their opinion, the upside to this location is easy access to your assets. However, a common counter argument is that thieves are more likely to search the bedroom for expensive items.
Ultimately, it's all a matter of your personality. Are you the type who's likely to be deterred from wearing your fine jewelry because it's not at your fingertips? Or, are you someone who constantly worries over the possibility of a break in. If you find the master bedroom to be too much of a target, you could always consider your basement, home office, or even a crawl space under the stairs.
ix) Can Your Safe Be Anchored Down?
One element that is often considered crucial to a safe's success is its weight. The theory is that a heavy safe will be harder to escape with, in the event that a thief is not able to open it outright. All that is true, but do you know what's even harder to carry than a safe that's full of steel? One that has been bolted to the ground.
Most reputable safe companies work under the assumption that you are going to take this step. They design their safes to have four holes in the bottom, one at each corner. As you shop, look for those holes as an indication of quality.
As for how to actually bolt the safe down, that part is pretty easy. Simply place the safe where you want it to stay. Then, use a masonry bit drill to drive bolts into your flooring where each of the prefabricated holes have been placed. However, if your room is carpeted, its wise to cut out patches first so that that the fibers do not unwind.


The Selma blanket box boasts a traditional design with a modern touch. This blanket box has a hand painted white carcas with a wax treated lid, allowing for the beauty of the natural wood to shine through - a stunning combination. Featuring a half lift-up hinged lid, this blanket box also has an inner tray and is the ideal storage solution for any room.


text:
GEO-Mobile Radio Interface (GEO stands for Geostationary Earth Orbit), better known as GMR, is an ETSI standard for satellite phones. The GMR standard is derived from the 3GPP-family terrestrial digital cellular standards and supports access to GSM/UMTS core networks. It is used by ACeS, ICO, Inmarsat, SkyTerra, TerreStar and Thuraya.

There are two widely deployed variants of GMR, both heavily modeled after GSM
 GMR-1: The first version of the standard and that has evolved over time into 3 different revisions:
 GMR-1: The basic circuit switched model, more or less corresponding to what GSM Phase 2 is, and using exactly the same core network infrastructure.
 GmPRS: Adding support for packet data. The equivalent of GPRS in the GSM world. Still connected to a 'Gb' style core network.
 GMR-1 3G: Adds support for some new channel types, but the most important changes are in the core network, adding interoperability with UMTS core network components. Contrary to the classic cell network where UMTS and GSM have a radically different air-interface, GMR-1 3G is still very similar to GMR-1 on the Layer 1 side.
 GMR-2: Which is not an evolution of GMR-1 but rather a concurrent standard that has been developed by another group of companies.

GMR-1 is the technology used by Thuraya.  GMR-1 3G is the technology used for TerreStar and SkyTerra.
GMR-2 is used by Inmarsat iSatPhonePro.

GMR was developed by TIA and ETSI.

Air Interface Ciphers
Versions of standard and cipher used:
 GMR-1 - GEO mobile radio 1  (Thuraya). Based on 4 LFSR registers.
 GMR-2 - GEO mobile radio 2  (InmarSat iSatPhonePro). Based on byte operations and 2 S-Boxes from DES.

References

External links
Creonic
ETSI
ETSI Publications Download Area
Don't trust satellite phones – The GMR-1 and GMR-2 ciphers have been broken

Satellite telephony

redpajama_set_name: RedPajamaWikipedia


The information for supporting a specific test object method.
If no specific function is assigned to support a given test object method, UFT calls a function with the same name as the test object method to run the step.
file_name string optional The file system path to the file containing the function. If no path is specified, the default file name defined in the Control\Settings element is used.
You can specify a path relative to the <UFT installation folder>\dat\Extensibility\Web\Toolkits\<Toolkit name> folder.
function string optional The name of the implementation function.
name string required The name of the test object method.


Welcome to the Department of Speech-Language Pathology.
Speech-language pathology is an exciting and worthy profession that provides service to communicatively impaired individuals and their families.
The W offers an undergraduate, pre-professional degree and a Master of Science degree in Speech-Language Pathology. Additionally, the Program operates the Speech and Hearing Center which provides speech, language and hearing services to the public and allows students to learn in a "hands-on" environment. These programs are committed to teaching a future generation of speech-language pathologists and offering exemplary evaluation and therapy services to clients.
Please take the time to fully explore what the Speech-Language Pathology Program at The W has to offer. Although this Website has been designed to answer your questions, please feel free to contact our office at (662) 329-7270 for more information.


text:
Critical remarks on Thomas Nagel
A confused thinker. In other words, a philosopher.
[From 2009.] I'm reading Thomas Nagel's famous book The View From Nowhere (1986). Nagel seems more sensible than most contemporary philosophers. For example, "The reductionist program that dominates current work in the philosophy of mind [by which he means functionalism, physicalism, etc.] is completely misguided, because it is based on the groundless assumption that a particular conception of objective reality [namely the 'physical' conception] is exhaustive of what there is." In other words, unlike Daniel Dennett and his gang of fools, he admits that sensations and other 'inner' states exist, and that humans aren't robots.
Nagel's thoughts remind me of how sensible my 'solution' was to the mind-body problem. The point is that a single substance -- neural stuff -- has, in its activity, two different ways of being: 'physical' and 'mental' (our mental experience). The nervous system itself is physical, composed of physical things you can point to (neurons, etc.). But its electrical and chemical processes can be 'considered' in two ways: serially and 'holistically.' On the one hand, you can think of neural activity as involving millions of electrical bursts, chemical discharges, etc., all interacting in an instant to give rise to a sensation or a thought. It's a series of interrelated physical events. On the other hand, you can think of all these events not serially but holistically, from the perspective of the 'whole' that emerges from their nearly instantaneous interactions. This emergent whole is the mental experience. It isn't 'located' anywhere -- except maybe vaguely and somewhat metaphorically, like 'in the hand' or 'in the head' -- it isn't a physical thing you can point to or that has a precise spatiotemporal location (comparable to that of a neuron, say); because of its emergent nature, it's just the sort of thing of which you can't predicate an exact location. So the mental experience isn't really 'in' the brain (as a neuron is), although in a loose, metaphorical sense maybe you can say it is. –That's the basis of the solution to the mind-body problem. It doesn't cease to be a problem, because we still have this mysterious phenomenon of emergence that will never be fully understood, but at least now we understand what it is we don't understand. Before, it was just a big muddle. I've reduced the problem to a specific phenomenon of biology.
More broadly, of course, the 'mind,' including all its unconscious aspects and cognitive structures and mathematical computations and so forth, is 'emergent' from the brain.
Nagel's common sense has limits. In the context of a discussion of the self he suggests that possibly "I am my brain." He thinks that's a reasonable hypothesis. I -- this consciousness -- I am my....brain! This consciousness is the same thing as its....nervous system! Wow. It doesn't even occur to him that the self might be self-consciousness. (A hypothesis that has to be embellished, yes.)
Later on, though, he shows that, for an academic, he has a deep mind: he shares my fascination with the intuitive distinction between the 'essential' me, or I, and the particular person Chris Wright, who is just like all other people, not at all different except for the fact that it is through him that I exist. In thought I can, in a sense, separate myself from Chris, hover above the city and look at all the people walking around, one of whom is Chris -- who, as it happens, is me! From one perspective, I could have been any of those other people, but I'm this guy, this particular body and personality and so on. Sometimes when I'm in the train surrounded by people I actually think about that: I look at everyone else, all their faces, and then look at the reflection in the window and see another face -- and I realize it's mine! But it looks no different from anyone else's! It isn't particularly special, it isn't glowing -- but it should be, because it has a unique connection to this self. This (seemingly) essential self (essential to itself, that is). This essential self, which is sort of 'outside' the world, appears to have an accidental connection to the body and personality of Chris Wright, who is a being in the world no different from all others. Nagel even uses the same terminology I used in my undergraduate senior thesis, but reverses it: whereas I called the particular body and mind I happen to 'occupy' the objective self, which is opposed to my 'true,' most intimate self, my (sort of) outside-the-world self that inhabits a particular body and mind, he calls this 'true' self the objective self (because it can consider the world more objectively, 'stepping outside' the particular perspective of Chris Wright or Thomas Nagel). His analysis doesn't go as far as mine, though.[1] I went on to argue that the 'true' self Nagel is talking about is self-consciousness (not the brain), and then I speculated about how self-consciousness (the I, the 'active' self, the 'subjective self') -- which, in its purity, is a sort of abstract thing, contentless, 'universal' -- manages to identify itself with the objective self (as I called it), i.e. the particular body and mind it 'occupies.' Of course this language is somewhat metaphorical: I shouldn't say it, as though self-consciousness is a single entity, but rather 'the continuously present (albeit implicitly present) thought of "consciousness of consciousness."' I also tried to describe the composition of the objective self: for example, self-consciousness 'immediately' identifies itself more closely with other thoughts than with the body, so it seems as though all these thoughts I'm having right now (and always) in my consciousness constitute the essential me, the self, whereas in fact the 'essential' me is only the abstracted thought 'consciousness of this consciousness.' (But that's also my least particular aspect, the aspect I share with everyone else.[2]) Needless to say, this phenomenological stuff is one of the most difficult areas of philosophy, so my account wasn't satisfactory.
By the way, it isn't just because I'm an intellectual and preoccupied with thoughts that I say the self identifies itself more closely with thoughts than with the body. This is true of everyone, as shown by the fact that people intuitively understand the mind/body distinction and that they often see their bodies as 'rebellious,' somewhat foreign things they have to control, take care of, etc.
Despite all this, however, it remains true 'on another level' that the mind-body dualism is specious. We are physical beings; consciousness is not a different substance but an emergent expression of neural activity.
Throughout his book, Nagel keeps referring to the 'objective self,' that which can separate itself from the perspective of the body and mind to which it is 'attached,' can step outside particular perspectives, can advance in knowledge by reflecting on itself and its conditions of existence, can doubt the significance of life by considering the smallness of each individual, etc. But Nagel can't account for it; it's a mystery to him. It doesn't even occur to him that the so-called objective self is, more or less, as Sartre and other phenomenologists have argued, just self-consciousness. Self-consciousness is what's responsible for the continuous tension that Nagel is obsessed with between the subjective and the objective -- being occupied in one's own perspective and looking at oneself externally, from the perspective of an other. Yes, this tension is essential to what it means to be human -- which is to say that self-consciousness is essential to humanity. Not a terribly original insight. But Nagel doesn't even rise to this insight, because the 'objective self' is a mystery to him.
On another subject, however, he reveals his common sense: he thinks that skepticism about the physical world is irrefutable. "The possibility of skepticism is built into our ordinary thoughts, in virtue of the realism that they automatically assume and their pretensions to go beyond experience." He's also right to think that the problem of free will is a hopeless muddle, for more than one reason. For example, the idea of freedom or autonomy is itself unintelligible. One reason, which he doesn't discuss, is that the self is largely an illusion. We don't even exist, at least not in the way we incoherently think we do. How can it be the case that I determine all my actions if I am nothing but self-consciousness (roughly speaking)? Consciousness of consciousness can't will anything; all it can do is be aware of itself (aware of everything going on in consciousness).[3] But let's ignore this reason and talk about ourselves as we ordinarily do. Years ago I wrote that, although we have reasons for doing any given act, at some point the reasons break off and we just make a 'leap' -- we just do the act. We could consider reasons for or against it forever, but sooner or later we have to give up the weighing of reasons and act. This fact seemed significant to me, but I didn't really know why. Nagel has helped me see one reason for its importance: it shows that intentional explanations (which presuppose the idea of freedom, as opposed to causal explanations, which are deterministic) can only go so far. You can ask somebody why he acted the way he did, and he'll list the reasons. But then you ask why he found those reasons more compelling than all the reasons against the act, and all he can say is, "I don't know, I just did." From his intentional perspective, there's no explanation for why those reasons motivated him.[4] But obviously there has to be some explanation, because they did, after all, motivate him. Clearly, then, the answer will take us "outside the domain of subjective normative reasons and into the domain of formative causes of my character or personality" -- or, on another level, neural processes. Only causes, not reasons, can explain why I'm motivated by some set of reasons. In other words, my values are explained not by reasons I have for holding them but by causes of my holding them. (Admittedly, I can be led to value some particular thing on the basis of reasons, but my valuing those reasons is not itself explainable by further reasons. In fact, the valorization of logic itself is not explainable by reasons (which would be question-begging); it's only causally explainable.) Thus, no act can be explained solely on the assumption of autonomy or freedom.[5]
Unfortunately we can't escape our sense of freedom and self-initiative, so....there you go. We're at a dead end. Complete freedom doesn't make sense, but we can't help believing that we're totally free, so we just have to accept the dilemma and move on with our lives. Or maybe it's better to say that total freedom makes some sense but not really: "We feel that in acting we ought to be able to determine not only our choices but the inner conditions of those choices [such as our basic values, or our preferences in a given moment], provided we step far enough outside ourselves. ....Yet the logical goal of these ambitions is incoherent, for to be really free we would have to act from a standpoint completely outside ourselves, choosing everything about ourselves, including all our principles of choice -- creating ourselves from nothing, so to speak." This is exactly what I said in my senior thesis that my professors didn't understand, that complete freedom is incoherent for these very reasons.[6]
By the way, it's also a total mystery how my 'will' could cause a given act or thought or bodily movement. How does my arm move when I will it to? It's like magic. We don't know how these things happen, but postulating a magical 'will' doesn't seem particularly fruitful. The explanation will be biological, i.e. deterministic.
Am I justified in condemning William Calley for the massacre at My Lai? Leaving aside questions about free will and responsibility and all that, it's true that he doesn't exemplify certain values we hold -- in his murders he was quite the opposite of them -- which means that from the standpoint of those values he is bad. That's a fact: he is not or was not good at living according to those values. But is (was) he morally bad? Well, insofar as we call those values 'moral' ones, yes, he was morally bad -- or at least, what he did was morally wrong. And in the moment of doing it, or at that time of his life, he was morally bad. He was not a good person from the perspective of values that we call 'morality' -- or, rather, if those values define what it is to be a good person, than he was a bad person. By definition.[7] None of this can be contested. What can be contested are more ambitious claims than that he simply didn't exemplify certain values, etc. Our inclination is to condemn him in a more 'absolute' way than all this; we want to get rid of the relativistic clause 'according to the values of such and such' and instead say that he was just bad. A bad person, period. Absolutely bad, bad in his 'essence,' so to speak, such that in describing him you could say, "He weighed 200 pounds, he was a lieutenant, six feet tall, and bad. Contemptible, repulsive." Stalin and Hitler are even better examples, since they expose our intuition with perfect clarity. But as I've argued sufficiently in the past, this absolutist value-judgment is not meaningful. Nevertheless, we want to condemn him in a stronger way than I outlined a moment ago. Such a condemnation would be based on the presupposition that he was free in his actions, that alternatives were available to him and he chose not to take them. He callously disregarded human life -- he freely and knowingly killed innocent old men, women and children -- which says a lot about his character. The 'mild' kind of condemnation above is somewhat 'external' in its nature, as when you judge that your cat is a bad pet. You're not condemning your cat's character; you're only saying that, as a matter of fact, probably for reasons out of its control, it isn't a good pet. But Calley is a human being and so presumably has free will. He is therefore immoral in a stronger sense: he's responsible for acting contrary to certain values, the most important values in life. The intensity of our condemnation is thus justified by two factors: the presumption that he's responsible, and the fact that the norms he flouted were more important than any others. A third factor is probably also relevant: we assume he knew, on some level, that what he was doing was the worst thing a person could do. Or maybe this presumption of his moral knowledge is included in, or is an extension of, the presumption that he's responsible for his actions, since full responsibility means he was perfectly aware of what he was doing.
But how meaningful is this stronger sort of condemnation? Insofar as it tends toward the 'absolute' condemnation I mentioned above, not very. But what other kind of condemnation is there, aside from the weak kind I discussed initially? There is, of course, the subjective aversion and disgust embodied in the judgment, but these feelings are not what I'm talking about. I'm searching for a judgment that has cognitive content. But there isn't one. The only strong (i.e., adequate-to-our-disgust) meaning of our condemnation is that in some sense we banish Calley from the human community for his apparently complete lack of respect for our most important values, as shown by his willful violation of them.
But how willful is it? How much responsibility does (did) he have? Maybe he lacked a moral sense. Maybe his time in Vietnam had desensitized him to violence and horror, or had destroyed his respect for human life. He had had it drilled into him by his superiors and others to think of Vietnamese peasants not as people but as the enemy -- a thing. Maybe he had also already encountered so many massacres that he had lost his moral bearings, had lost his sense of who he was, etc. For many men, Vietnam meant absolute existential disorientation. To an extent, all this would, perhaps, erode Calley's responsibility for his actions. Moreover, as we saw above, he couldn't really have had control over his not valuing human life, because he didn't choose not to value it. His attitudes were caused, not chosen. And his actions were at least strongly conditioned by his attitudes. If we keep going on in this way, he becomes less and less responsible for his acts, which means that our condemnation of him loses more and more of its justification, or its force. We can still deplore what he did, but we start to see him in a somewhat less condemnatory way.[8]
Or even in the case of somebody like Jeffrey Dahmer.... 'He is what he is.' Maybe he could have chosen not to kill and eat those men, but why would he have, given his values and desires? People act on the basis of their values, which in general are not subject to their control. Even if they were, why would somebody choose to change his values? He would have to do so on the basis of some other value, which in turn would not be something he had chosen.
And yet we can't help thinking, despite all this (and more -- for instance, the determinism of the brain), that people are basically free, responsible for everything they do. Calley chose to act as he did, he had alternatives, he didn't have to throw a baby into a ditch and shoot it. And that's true, in fact....but it's not the whole story....
There are a lot of similarities between Nagel's arguments and my old thoughts from college. Like his argument that prejudiced, narrow-minded and irrational people are less free than people without those traits, and that we can increase our freedom by cultivating reason, etc. Not terribly original ideas, though.
According to Nagel,
Berkeley claimed that [the truth of the idea that to exist is to be perceived] became evident if we tried to form the idea of an unperceived object. It turns out to be impossible, he said, because as soon as we try to think, for example, of an unperceived tree, we find that all we can do is to call up a perceptual image of a tree, and that is not unperceived.
It would be generally recognized now that this argument involves the mistake of confusing perceptual imagination as the vehicle of thought with a perceptual experience as part of the object of thought. Even if I employ a visual image to think about the tree, that does not mean I am thinking about a visual impression of the tree, any more than if I draw a tree, I am drawing a drawing of a tree.
A similar mistake would be to argue that we cannot form the thought of something that no one is actually thinking about or the conception of something that no one is conceiving of. Clearly we can think and talk about the possible state of affairs in which no one is thinking or talking about Bishop Berkeley. The fact that we must talk about Berkeley to talk about the situation in which he is not being talked about doesn't make that situation either inexpressible or impossible.
Berkeley may indeed have made the mistake Nagel accuses him of, but the argument in the first paragraph above can be interpreted in another way: it can mean that when we try to think of a world independently of how it appears to us -- how it is 'in itself' -- all we can call to our minds is the world as it appears to us. We can't imagine what it 'looks like' independently of what it looks like to us. Obviously. I made the point years ago: when we try to think of the world as it is in itself even on the most 'basic' level (beneath ordinary appearances), we find ourselves picturing cells or molecules or things like that, things that have appeared to us in photographs or artists' renditions or whatever. We can't escape the perspective of a subject viewing an object, even when we try to imagine an object as it is in itself without a subject looking at it. Where I disagree with Berkeley is in thinking that this fact doesn't mean there is no external world. Our inability to imagine an external world 'in its contours' or 'in its details' -- i.e., as it appears! -- without adopting the perspective of 'being-appeared-to' doesn't entail that there are no things external to minds. It entails only itself, i.e. that we can't imagine (visualize) them (as they are 'when we're not looking at them').
Meanwhile, Nagel's argument that objective values exist, values "independent of our beliefs and inclinations," is silly and confused. At one point he says that "to dispense with [objective values] is too radical a denial of appearances." He goes on: "If I have a severe headache, the headache seems to me to be not merely unpleasant, but a bad thing. Not only do I dislike it, but I think I have a reason to try to get rid of it. [Um, yes you do, namely that you dislike it!] It is barely conceivable that this might be an illusion, but if the idea of a bad thing makes sense at all, it need not be an illusion, and the true explanation of my impression may be the simplest one, namely that headaches are bad, and not just unwelcome to the people who have them." What the hell? Headaches are bad, yes, insofar as they're painful: i.e., they're bad to the person experiencing them, because we value not being in pain. Their badness is relative to our desire not to experience pain. What would it even mean to say that headaches are 'objectively bad'?
Regarding moral values, the most that can be said is that they aren't mere subjective preferences with no independent basis at all; instead, there is a foundation for our common values in human biology. It is likely in our biology to (have a tendency to) think that the kinds of things Stalin and Hitler did were bad or wrong; and other things being equal -- i.e., if social relations haven't warped a person's mind so much that he has lost touch with humans' natural morality -- we will so condemn Stalin and Hitler. Their acts were literally unnatural. So in a sense, yes, there is an objective foundation of morality, viz. nature, in that nature predisposes us to act and value in certain ways.
Nagel is right, furthermore, that one can say that there are degrees of objectivity in value-judgments. One's morality can perhaps be called more 'objective' to the extent that one takes into consideration other people's viewpoints and values. But this doesn't mean that certain values are objective(ly justified), because there is no 'view from nowhere' in the ethical realm (or any other). There are only perspectives, desires -- desires and perspectives that determine what is seen as valuable. It's incredible that Nagel thinks there is a view from nowhere, a perspectiveless perspective, and that it is reached simply by stepping outside one's own 'subjective,' 'personal' viewpoint and considering things more 'objectively,' as an 'objective self.'
The more I read of the book, the more I see it's riddled with confusions.
But Nagel is right to reject Aristotle's and Plato's positions on morality. He distinguishes between the moral life and the good life: loosely speaking, in the first, one partially sacrifices one's own 'selfish' interests to those of others, while in the second, one's own interests take priority. (But it isn't ruled out that morality can be a component of the good life.) Aristotle more or less defined morality in terms of the good life -- which doesn't mean, however, that the two ideas are equivalent. "The test of moral principles is their contribution, either instrumental or constitutive, to the good life as a whole; but since we are social beings, this may entail some of the familiar moral virtues." Nagel rejects this because morality has its source in the claims of other people, which can't "be strictly limited by their capacity to be accommodated within a good individual life." For instance, it would be more moral for me to donate $100 to some charity that would use it to save children from starvation than to spend $100 on a meal in an expensive restaurant, even though fine dining might be considered a component of the good life. In a sense, living well (living the good life, which can be defined in various ways) is immoral when you know that other people are miserable, starving, etc. So Aristotle is wrong. Morality can't be substantially reduced to the good life.
Plato, on the other hand, defined the good life in terms of the moral life. That is, by far the most important thing in the former is the latter. But Nagel argues that "there is much more to us, and therefore much more to what is good and bad for us, than what is directly involved in morality." We have many different capacities and desires, and sacrificing most of them to the imperative to improve others' lives leads to the impoverishment of our own life. Thus, unlike Plato and Aristotle, Nagel thinks that the good life and the moral life (or at least the most moral life) can conflict. This is surely right.
Nietzsche thought that the good life overrides the moral life. I don't agree with Nagel that this position is simply wrong in the way that Plato's and Aristotle's are;[9] all you can say is that it's morally reprehensible. If you're happy living a life that has no concern for morality, then fine, that's your choice. Actually, if you accept that morality plays some role in the good life because it is in human nature to care for others -- and so to 'fulfill' yourself as a human, to live a good human life, you have to have some concern for others -- then a pure Nietzschean position that totally rejects morality in favor of the 'good life' isn't possible, since morality plays at least some part in the good (human) life. The opposite position is that the moral life overrides the good life. I think this, too, is neither right nor wrong; whether you accept it is a matter of choice, the values that you hold. My own position is that neither the moral life nor the good life should 'override' the other; we simply have to balance them as best we can. Try to be a full human being, which involves living morally but not obsessively morally (because then you'll neglect other sides of your personality, which will atrophy). But if you care far more about living 'right' than living 'well' and are willing to sacrifice your own interests to those of others, then do so; I'll respect you very much.
Interestingly, though, morality isn't only an other-oriented thing. In a broader sense, concepts like integrity, honesty and autonomy are relevant to it. This is more like a self-oriented morality; it implies that you can act immorally, at least in some loose way, with respect to yourself. And that makes sense, since you're a human being like all others. It seems as though this kind of morality has an even closer connection to the good life than other-oriented morality does (insofar as the two can be distinguished).
Anyway, while I don't know why Nagel's book is so famous, I won't deny it has some value. Parts of it are mildly thought-provoking.
[1] (After reading more, I see that his ideas and mine are quite different.)
[2] Enter: mysticism, John Donne's 17th Meditation, etc.
[3] This self-awareness is in fact what explains our perception of free will, as I've said before. In being of itself, self-consciousness cannot escape the impression that it is self-causing, that it is responsible for all of 'its' (or 'my') acts and thoughts -- the acts and thoughts that are registered in consciousness.
[4] [Well, he could give more basic reasons for why those reasons motivated him, but then you'd ask why those reasons motivated him. At some point, he'd have no more reasons to give.]
[5] [More accurately, in a narrow sense, yes, acts can be explained on that assumption. But in order to explain why particular reasons had motivational force, you have to go beyond that assumption, to a deeper, causal level.]
[6] This is an example of the snobbery of most academics. They take something more seriously if it's said by a respected colleague than by a student, in which case they might well dismiss it or ridicule it. On the other hand, maybe I shouldn't pick on academics; nearly every professional in an elite institution is like this. People are assholes.
[7] Again, free will isn't necessarily presupposed by any of this, just as it isn't presupposed when you say that your pet cat is a bad pet for whatever reasons.
[8] But that doesn't have to stop us -- after reading in detail about what he did -- from being horrified at him, perhaps thinking of him as a cautionary tale about what can happen to people in war. And even if in some sense he wasn't completely 'free' in his choice to do what he did, we can still think of him as something dangerous that has to be locked up.
[9] Of course, you can define morality and the good life however you want, and different people will have different understandings. I just mean that if you judge by common intuitions, Plato's and Aristotle's definitions are inadequate.

redpajama_set_name: RedPajamaCommonCrawl


Abu Dhabi is renowned for offering up city breaks packed with heaps of glamour, culture, breathtaking landmarks and some pretty diverse activities.
However when there's so much on offer, trying to narrow it down for your holiday can feel like a somewhat overwhelming task.
To give you a helping hand, we've done the leg work and picked out some of the top attractions you won't want to miss from the spectacular Sheikh Zayed Grand Mosque to the newly-opened Louvre Abu Dhabi museum.
We've also searched out some of the best places where you can find cheap Abu Dhabi city breaks, whether you're after a package or just looking for budget-friendly accommodation.
The largest mosque in the UAE, the Sheikh Zayed Grand Mosque has long been wowing visitors thanks to its intricate architecture, breathtaking domes, reflective pools and the iconic prayer hall which is home to the world's biggest chandelier and carpet.
Open to the public, entrance is free and there are guided tours if you want to make sure you see all the highlights.
The ultimate playground for thrill-seekers, this theme park boasts everything from Ferrari driving experiences to Formula Rossa, the world's fastest rollercoaster (it reaches a a speed of 0 to 240 km/h in 4.9 seconds).
Located on the city's Yas Island, where you'll find plenty more water parks and entertainment, the park is a must-visit for those who want a burst of action in the itinerary. Travelling with kids? Don't worry, there are some more gentle attractions for little ones.
Tickets start from AED295 (approx £61) for single day tickets.
If you've got some extra time to play around with during your trip, it's well worth looking into a guided tour or safari of this incredible desert which sits approximately 93 miles southwest of the city.
There are various trips whether you're after sand-bashing on the shifting dunes, sand boarding, or want to enjoy traditional evening shows and an Arabic buffet beneath the stars.
The museum opened in November 2017 and is already proving a firm hit with visitors thanks to the incredible collections of artefacts in its 9,200sqm of galleries.
There's plenty on loan from notable French museums including Musee du Louvre, Musee d'Orsay, and Centre Pompidou.
If you're after a day on golden sandy shores, then Saadiyat's public beach is a must-visit. For those who don't like to laze by the water, there's plenty on from beach yoga classes to water sports including sailing.
You'll also find some hotels and resorts with their own private stretches of beach, for those who want to escape the crowds.
Want to stay on the island? Booking.com has a handy round-up of the best hotels to suit a wide array of budgets.
If you grew up obsessed with the likes of Batman, Superman and Bugs Bunny, then get ready for a serious dose of nostalgia at this new indoor theme park.
Divided into six separate lands from Gotham City to Cartoon Junction, there's fun to be had for everyone from superhero thrill-seekers to kids who just want to see Jerry give Tom his comeuppance.
We went exploring the new theme park to find out just what visitors can expect – including the best rides you won't want to miss while you're there.
Flight and hotel packages: Expedia and TravelSupermarket can be good sources if you're flexible on your dates of travel, while Thomas Cook offers a host of Abu Dhabi holidays.
Budget-friendly hotels: Lastminute.com has Abu Dhabi hotels from £41 per night, while TripAdvisor has a handy round-up of the top 10 cheap hotels based on travellers' reviews.
Cheap flights: British Airways can be a good source for cheap flights with prices from £297pp, not to mention it often hosts sales where you could snap up a bargain. If you're not fussed about flying direct, it's worth comparing prices on Skyscanner where you could bag fares from £257pp.