Exemple #1
0
/// @brief slice/clone chosen rows for a subset, this does a deep copy
/// of all entries
AqlItemBlock* AqlItemBlock::slice(std::vector<size_t>& chosen, size_t from,
                                  size_t to) const {
  TRI_ASSERT(from < to && to <= chosen.size());

  std::unordered_map<AqlValue, AqlValue> cache;
  cache.reserve((to - from) * _nrRegs / 4 + 1);

  auto res = std::make_unique<AqlItemBlock>(to - from, _nrRegs);

  for (size_t row = from; row < to; row++) {
    for (RegisterId col = 0; col < _nrRegs; col++) {
      AqlValue const& a(_data[chosen[row] * _nrRegs + col]);

      if (!a.isEmpty()) {
        auto it = cache.find(a);

        if (it == cache.end()) {
          AqlValue b = a.clone();
          try {
            res->setValue(row - from, col, b);
          } catch (...) {
            b.destroy();
          }
          cache.emplace(a, b);
        } else {
          res->setValue(row - from, col, it->second);
        }
      }
    }
  }

  return res.release();
}
Exemple #2
0
/// @brief slice/clone, this does a deep copy of all entries
AqlItemBlock* AqlItemBlock::slice(
    size_t row, std::unordered_set<RegisterId> const& registers) const {
  std::unordered_map<AqlValue, AqlValue> cache;

  auto res = std::make_unique<AqlItemBlock>(1, _nrRegs);

  for (RegisterId col = 0; col < _nrRegs; col++) {
    if (registers.find(col) == registers.end()) {
      continue;
    }

    AqlValue const& a(_data[row * _nrRegs + col]);

    if (!a.isEmpty()) {
      auto it = cache.find(a);

      if (it == cache.end()) {
        AqlValue b = a.clone();
        try {
          res->setValue(0, col, b);
        } catch (...) {
          b.destroy();
          throw;
        }
        cache.emplace(a, b);
      } else {
        res->setValue(0, col, it->second);
      }
    }
  }

  return res.release();
}
Exemple #3
0
/// @brief getSome
AqlItemBlock* GatherBlock::getSome(size_t atLeast, size_t atMost) {
  DEBUG_BEGIN_BLOCK();
  traceGetSomeBegin();
  if (_done) {
    traceGetSomeEnd(nullptr);
    return nullptr;
  }

  // the simple case . . .
  if (_isSimple) {
    auto res = _dependencies.at(_atDep)->getSome(atLeast, atMost);
    while (res == nullptr && _atDep < _dependencies.size() - 1) {
      _atDep++;
      res = _dependencies.at(_atDep)->getSome(atLeast, atMost);
    }
    if (res == nullptr) {
      _done = true;
    }
    traceGetSomeEnd(res);
    return res;
  }

  // the non-simple case . . .
  size_t available = 0;  // nr of available rows
  size_t index = 0;      // an index of a non-empty buffer

  // pull more blocks from dependencies . . .
  for (size_t i = 0; i < _dependencies.size(); i++) {
    if (_gatherBlockBuffer.at(i).empty()) {
      if (getBlock(i, atLeast, atMost)) {
        index = i;
        _gatherBlockPos.at(i) = std::make_pair(i, 0);
      }
    } else {
      index = i;
    }

    auto cur = _gatherBlockBuffer.at(i);
    if (!cur.empty()) {
      available += cur.at(0)->size() - _gatherBlockPos.at(i).second;
      for (size_t j = 1; j < cur.size(); j++) {
        available += cur.at(j)->size();
      }
    }
  }

  if (available == 0) {
    _done = true;
    traceGetSomeEnd(nullptr);
    return nullptr;
  }

  size_t toSend = (std::min)(available, atMost);  // nr rows in outgoing block

  // the following is similar to AqlItemBlock's slice method . . .
  std::unordered_map<AqlValue, AqlValue> cache;

  // comparison function
  OurLessThan ourLessThan(_trx, _gatherBlockBuffer, _sortRegisters);
  AqlItemBlock* example = _gatherBlockBuffer.at(index).front();
  size_t nrRegs = example->getNrRegs();

  auto res = std::make_unique<AqlItemBlock>(
      toSend, static_cast<arangodb::aql::RegisterId>(nrRegs));
  // automatically deleted if things go wrong

  for (size_t i = 0; i < toSend; i++) {
    // get the next smallest row from the buffer . . .
    std::pair<size_t, size_t> val = *(std::min_element(
        _gatherBlockPos.begin(), _gatherBlockPos.end(), ourLessThan));

    // copy the row in to the outgoing block . . .
    for (RegisterId col = 0; col < nrRegs; col++) {
      AqlValue const& x(
          _gatherBlockBuffer.at(val.first).front()->getValue(val.second, col));
      if (!x.isEmpty()) {
        auto it = cache.find(x);

        if (it == cache.end()) {
          AqlValue y = x.clone();
          try {
            res->setValue(i, col, y);
          } catch (...) {
            y.destroy();
            throw;
          }
          cache.emplace(x, y);
        } else {
          res->setValue(i, col, it->second);
        }
      }
    }

    // renew the _gatherBlockPos and clean up the buffer if necessary
    _gatherBlockPos.at(val.first).second++;
    if (_gatherBlockPos.at(val.first).second ==
        _gatherBlockBuffer.at(val.first).front()->size()) {
      AqlItemBlock* cur = _gatherBlockBuffer.at(val.first).front();
      delete cur;
      _gatherBlockBuffer.at(val.first).pop_front();
      _gatherBlockPos.at(val.first) = std::make_pair(val.first, 0);
    }
  }

  traceGetSomeEnd(res.get());
  return res.release();

  // cppcheck-suppress style
  DEBUG_END_BLOCK();
}
Exemple #4
0
AqlValue Expression::executeSimpleExpression (AstNode const* node,
                                              TRI_document_collection_t const** collection, 
                                              triagens::arango::AqlTransaction* trx,
                                              AqlItemBlock const* argv,
                                              size_t startPos,
                                              std::vector<Variable const*> const& vars,
                                              std::vector<RegisterId> const& regs,
                                              bool doCopy) {
  if (node->type == NODE_TYPE_ATTRIBUTE_ACCESS) {
    // object lookup, e.g. users.name
    TRI_ASSERT_EXPENSIVE(node->numMembers() == 1);

    auto member = node->getMemberUnchecked(0);
    auto name = static_cast<char const*>(node->getData());

    TRI_document_collection_t const* myCollection = nullptr;
    AqlValue result = executeSimpleExpression(member, &myCollection, trx, argv, startPos, vars, regs, false);

    auto j = result.extractObjectMember(trx, myCollection, name, true, _buffer);
    result.destroy();
    return AqlValue(new Json(TRI_UNKNOWN_MEM_ZONE, j.steal()));
  }
  
  else if (node->type == NODE_TYPE_INDEXED_ACCESS) {
    // array lookup, e.g. users[0]
    // note: it depends on the type of the value whether an array lookup or an object lookup is performed
    // for example, if the value is an object, then its elements might be accessed like this:
    // users['name'] or even users['0'] (as '0' is a valid attribute name, too)
    // if the value is an array, then string indexes might also be used and will be converted to integers, e.g.
    // users['0'] is the same as users[0], users['-2'] is the same as users[-2] etc.
    TRI_ASSERT(node->numMembers() == 2);

    auto member = node->getMember(0);
    auto index = node->getMember(1);

    TRI_document_collection_t const* myCollection = nullptr;
    AqlValue result = executeSimpleExpression(member, &myCollection, trx, argv, startPos, vars, regs, false);

    if (result.isArray()) {
      TRI_document_collection_t const* myCollection2 = nullptr;
      AqlValue indexResult = executeSimpleExpression(index, &myCollection2, trx, argv, startPos, vars, regs, false);

      if (indexResult.isNumber()) {
        auto j = result.extractArrayMember(trx, myCollection, indexResult.toInt64(), true);
        indexResult.destroy();
        result.destroy();
        return AqlValue(new Json(TRI_UNKNOWN_MEM_ZONE, j.steal()));
      }
      else if (indexResult.isString()) {
        auto&& value = indexResult.toString();
        indexResult.destroy();

        try {
          // stoll() might throw an exception if the string is not a number
          int64_t position = static_cast<int64_t>(std::stoll(value.c_str()));
          auto j = result.extractArrayMember(trx, myCollection, position, true);
          result.destroy();
          return AqlValue(new Json(TRI_UNKNOWN_MEM_ZONE, j.steal()));
        }
        catch (...) {
          // no number found. 
        }
      }
      else {
        indexResult.destroy();
      }
        
      // fall-through to returning null
    }
    else if (result.isObject()) {
      TRI_document_collection_t const* myCollection2 = nullptr;
      AqlValue indexResult = executeSimpleExpression(index, &myCollection2, trx, argv, startPos, vars, regs, false);

      if (indexResult.isNumber()) {
        auto&& indexString = std::to_string(indexResult.toInt64());
        auto j = result.extractObjectMember(trx, myCollection, indexString.c_str(), true, _buffer);
        indexResult.destroy();
        result.destroy();
        return AqlValue(new Json(TRI_UNKNOWN_MEM_ZONE, j.steal()));
      }
      else if (indexResult.isString()) {
        auto&& value = indexResult.toString();
        indexResult.destroy();

        auto j = result.extractObjectMember(trx, myCollection, value.c_str(), true, _buffer);
        result.destroy();
        return AqlValue(new Json(TRI_UNKNOWN_MEM_ZONE, j.steal()));
      }
      else {
        indexResult.destroy();
      }

      // fall-through to returning null
    }
    result.destroy();
      
    return AqlValue(new Json(TRI_UNKNOWN_MEM_ZONE, &NullJson, Json::NOFREE));
  }
  
  else if (node->type == NODE_TYPE_ARRAY) {
    if (node->isConstant()) {
      auto json = node->computeJson();

      if (json == nullptr) {
        THROW_ARANGO_EXCEPTION(TRI_ERROR_OUT_OF_MEMORY);
      }

      // we do not own the JSON but the node does!
      return AqlValue(new Json(TRI_UNKNOWN_MEM_ZONE, json, Json::NOFREE));
    }

    size_t const n = node->numMembers();
    std::unique_ptr<Json> array(new Json(Json::Array, n));

    for (size_t i = 0; i < n; ++i) {
      auto member = node->getMemberUnchecked(i);
      TRI_document_collection_t const* myCollection = nullptr;

      AqlValue result = executeSimpleExpression(member, &myCollection, trx, argv, startPos, vars, regs, false);
      array->add(result.toJson(trx, myCollection, true));
      result.destroy();
    }

    return AqlValue(array.release());
  }

  else if (node->type == NODE_TYPE_OBJECT) {
    if (node->isConstant()) {
      auto json = node->computeJson();

      if (json == nullptr) {
        THROW_ARANGO_EXCEPTION(TRI_ERROR_OUT_OF_MEMORY);
      }

      // we do not own the JSON but the node does!
      return AqlValue(new Json(TRI_UNKNOWN_MEM_ZONE, json, Json::NOFREE));
    }

    size_t const n = node->numMembers();
    std::unique_ptr<Json> object(new Json(Json::Object, n));

    for (size_t i = 0; i < n; ++i) {
      auto member = node->getMemberUnchecked(i);
      TRI_document_collection_t const* myCollection = nullptr;

      TRI_ASSERT(member->type == NODE_TYPE_OBJECT_ELEMENT);
      auto key = member->getStringValue();
      member = member->getMember(0);

      AqlValue result = executeSimpleExpression(member, &myCollection, trx, argv, startPos, vars, regs, false);
      object->set(key, result.toJson(trx, myCollection, true));
      result.destroy();
    }
    return AqlValue(object.release());
  }

  else if (node->type == NODE_TYPE_VALUE) {
    auto json = node->computeJson();

    if (json == nullptr) {
      THROW_ARANGO_EXCEPTION(TRI_ERROR_OUT_OF_MEMORY);
    }

    // we do not own the JSON but the node does!
    return AqlValue(new Json(TRI_UNKNOWN_MEM_ZONE, json, Json::NOFREE)); 
  }

  else if (node->type == NODE_TYPE_REFERENCE) {
    auto v = static_cast<Variable const*>(node->getData());

    {
      auto it = _variables.find(v);
      if (it != _variables.end()) {
        *collection = nullptr;
        return AqlValue(new Json(TRI_UNKNOWN_MEM_ZONE, TRI_CopyJson(TRI_UNKNOWN_MEM_ZONE, (*it).second))); //, Json::NOFREE));
      }
    }


    size_t i = 0;
    for (auto it = vars.begin(); it != vars.end(); ++it, ++i) {
      if ((*it)->name == v->name) {
        TRI_ASSERT(collection != nullptr);

        // save the collection info
        *collection = argv->getDocumentCollection(regs[i]); 

        if (doCopy) {
          return argv->getValueReference(startPos, regs[i]).clone();
        }
        
        // AqlValue.destroy() will be called for the returned value soon,
        // so we must not return the original AqlValue from the AqlItemBlock here 
        return argv->getValueReference(startPos, regs[i]).shallowClone();
      }
    }
    // fall-through to exception
  }
  
  else if (node->type == NODE_TYPE_FCALL) {
    // some functions have C++ handlers
    // check if the called function has one
    auto func = static_cast<Function*>(node->getData());
    TRI_ASSERT(func->implementation != nullptr);

    auto member = node->getMemberUnchecked(0);
    TRI_ASSERT(member->type == NODE_TYPE_ARRAY);

    size_t const n = member->numMembers();
    FunctionParameters parameters;
    parameters.reserve(n);

    try { 
      for (size_t i = 0; i < n; ++i) {
        TRI_document_collection_t const* myCollection = nullptr;
        auto arg = member->getMemberUnchecked(i);

        if (arg->type == NODE_TYPE_COLLECTION) {
          parameters.emplace_back(AqlValue(new Json(TRI_UNKNOWN_MEM_ZONE, arg->getStringValue(), arg->getStringLength())), nullptr);
        }
        else {
          auto value = executeSimpleExpression(arg, &myCollection, trx, argv, startPos, vars, regs, false);
          parameters.emplace_back(value, myCollection);
        }
      }

      auto res2 = func->implementation(_ast->query(), trx, parameters);

      for (auto& it : parameters) {
        it.first.destroy();
      }
      return res2;
    }
    catch (...) {
      // prevent leak and rethrow error
      for (auto& it : parameters) {
        it.first.destroy();
      }
      throw; 
    }
  }

  else if (node->type == NODE_TYPE_RANGE) {
    TRI_document_collection_t const* leftCollection = nullptr;
    TRI_document_collection_t const* rightCollection = nullptr;

    auto low = node->getMember(0);
    auto high = node->getMember(1);
    AqlValue resultLow  = executeSimpleExpression(low, &leftCollection, trx, argv, startPos, vars, regs, false);
    AqlValue resultHigh = executeSimpleExpression(high, &rightCollection, trx, argv, startPos, vars, regs, false);
    AqlValue res = AqlValue(resultLow.toInt64(), resultHigh.toInt64());
    resultLow.destroy();
    resultHigh.destroy();

    return res;
  }
  
  else if (node->type == NODE_TYPE_OPERATOR_UNARY_NOT) {
    TRI_document_collection_t const* myCollection = nullptr;
    AqlValue operand = executeSimpleExpression(node->getMember(0), &myCollection, trx, argv, startPos, vars, regs, false);
    
    bool const operandIsTrue = operand.isTrue();
    operand.destroy();
    return AqlValue(new Json(TRI_UNKNOWN_MEM_ZONE, operandIsTrue ? &FalseJson : &TrueJson, Json::NOFREE));
  }
  
  else if (node->type == NODE_TYPE_OPERATOR_BINARY_AND ||
           node->type == NODE_TYPE_OPERATOR_BINARY_OR) {
    TRI_document_collection_t const* leftCollection = nullptr;
    AqlValue left  = executeSimpleExpression(node->getMember(0), &leftCollection, trx, argv, startPos, vars, regs, true);
    TRI_document_collection_t const* rightCollection = nullptr;
    AqlValue right = executeSimpleExpression(node->getMember(1), &rightCollection, trx, argv, startPos, vars, regs, true);
    
    if (node->type == NODE_TYPE_OPERATOR_BINARY_AND) {
      // AND
      if (left.isTrue()) {
        // left is true => return right
        left.destroy();
        return right;
      }

      // left is false, return left
      right.destroy();
      return left;
    }
    else {
      // OR
      if (left.isTrue()) {
        // left is true => return left
        right.destroy();
        return left;
      }

      // left is false => return right
      left.destroy();
      return right;
    }
  }
  
  else if (node->type == NODE_TYPE_OPERATOR_BINARY_EQ ||
           node->type == NODE_TYPE_OPERATOR_BINARY_NE ||
           node->type == NODE_TYPE_OPERATOR_BINARY_LT ||
           node->type == NODE_TYPE_OPERATOR_BINARY_LE ||
           node->type == NODE_TYPE_OPERATOR_BINARY_GT ||
           node->type == NODE_TYPE_OPERATOR_BINARY_GE ||
           node->type == NODE_TYPE_OPERATOR_BINARY_IN ||
           node->type == NODE_TYPE_OPERATOR_BINARY_NIN) {
    TRI_document_collection_t const* leftCollection = nullptr;
    AqlValue left  = executeSimpleExpression(node->getMember(0), &leftCollection, trx, argv, startPos, vars, regs, false);
    TRI_document_collection_t const* rightCollection = nullptr;
    AqlValue right = executeSimpleExpression(node->getMember(1), &rightCollection, trx, argv, startPos, vars, regs, false);

    if (node->type == NODE_TYPE_OPERATOR_BINARY_IN ||
        node->type == NODE_TYPE_OPERATOR_BINARY_NIN) {
      // IN and NOT IN
      if (! right.isArray()) {
        // right operand must be a list, otherwise we return false
        left.destroy();
        right.destroy();
        // do not throw, but return "false" instead
        return AqlValue(new Json(TRI_UNKNOWN_MEM_ZONE, &FalseJson, Json::NOFREE));
      }
   
      bool result = findInArray(left, right, leftCollection, rightCollection, trx, node); 

      if (node->type == NODE_TYPE_OPERATOR_BINARY_NIN) {
        // revert the result in case of a NOT IN
        result = ! result;
      }
       
      left.destroy();
      right.destroy();
    
      return AqlValue(new triagens::basics::Json(result));
    }

    // all other comparison operators...

    // for equality and non-equality we can use a binary comparison
    bool compareUtf8 = (node->type != NODE_TYPE_OPERATOR_BINARY_EQ && node->type != NODE_TYPE_OPERATOR_BINARY_NE);

    int compareResult = AqlValue::Compare(trx, left, leftCollection, right, rightCollection, compareUtf8);
    left.destroy();
    right.destroy();

    if (node->type == NODE_TYPE_OPERATOR_BINARY_EQ) {
      return AqlValue(new Json(TRI_UNKNOWN_MEM_ZONE, (compareResult == 0) ? &TrueJson : &FalseJson, Json::NOFREE));
    }
    else if (node->type == NODE_TYPE_OPERATOR_BINARY_NE) {
      return AqlValue(new Json(TRI_UNKNOWN_MEM_ZONE, (compareResult != 0) ? &TrueJson : &FalseJson, Json::NOFREE));
    }
    else if (node->type == NODE_TYPE_OPERATOR_BINARY_LT) {
      return AqlValue(new Json(TRI_UNKNOWN_MEM_ZONE, (compareResult < 0) ? &TrueJson : &FalseJson, Json::NOFREE));
    }
    else if (node->type == NODE_TYPE_OPERATOR_BINARY_LE) {
      return AqlValue(new Json(TRI_UNKNOWN_MEM_ZONE, (compareResult <= 0) ? &TrueJson : &FalseJson, Json::NOFREE));
    }
    else if (node->type == NODE_TYPE_OPERATOR_BINARY_GT) {
      return AqlValue(new Json(TRI_UNKNOWN_MEM_ZONE, (compareResult > 0) ? &TrueJson : &FalseJson, Json::NOFREE));
    }
    else if (node->type == NODE_TYPE_OPERATOR_BINARY_GE) {
      return AqlValue(new Json(TRI_UNKNOWN_MEM_ZONE, (compareResult >= 0) ? &TrueJson : &FalseJson, Json::NOFREE));
    }
    // fall-through intentional
  }
  
  else if (node->type == NODE_TYPE_OPERATOR_TERNARY) {
    TRI_document_collection_t const* myCollection = nullptr;
    AqlValue condition  = executeSimpleExpression(node->getMember(0), &myCollection, trx, argv, startPos, vars, regs, false);

    bool const isTrue = condition.isTrue();
    condition.destroy();
    if (isTrue) {
      // return true part
      return executeSimpleExpression(node->getMember(1), &myCollection, trx, argv, startPos, vars, regs, true);
    }
    
    // return false part  
    return executeSimpleExpression(node->getMember(2), &myCollection, trx, argv, startPos, vars, regs, true);
  }

  else if (node->type == NODE_TYPE_EXPANSION) { 
    TRI_ASSERT(node->numMembers() == 5);

    // LIMIT
    int64_t offset = 0; 
    int64_t count  = INT64_MAX;

    auto limitNode = node->getMember(3);

    if (limitNode->type != NODE_TYPE_NOP) {
      TRI_document_collection_t const* subCollection = nullptr;
      AqlValue sub = executeSimpleExpression(limitNode->getMember(0), &subCollection, trx, argv, startPos, vars, regs, false);
      offset = sub.toInt64();
      sub.destroy();

      subCollection = nullptr;
      sub = executeSimpleExpression(limitNode->getMember(1), &subCollection, trx, argv, startPos, vars, regs, false);
      count = sub.toInt64();
      sub.destroy();
    }

    if (offset < 0 || count <= 0) {
      // no items to return... can already stop here
      return AqlValue(new triagens::basics::Json(triagens::basics::Json::Array));
    }

    // FILTER
    AstNode const* filterNode = node->getMember(2);

    if (filterNode->type == NODE_TYPE_NOP) {
      filterNode = nullptr;
    }
    else if (filterNode->isConstant()) {
      if (filterNode->isTrue()) { 
        // filter expression is always true
        filterNode = nullptr;
      }
      else {
        // filter expression is always false
        return AqlValue(new triagens::basics::Json(triagens::basics::Json::Array));
      }
    }

    auto iterator = node->getMember(0);
    auto variable = static_cast<Variable*>(iterator->getMember(0)->getData());
    auto levels = node->getIntValue(true);

    AqlValue value;

    if (levels > 1) {
      // flatten value...

      // generate a new temporary for the flattened array
      std::unique_ptr<Json> flattened(new Json(Json::Array));

      TRI_document_collection_t const* myCollection = nullptr;
      value = executeSimpleExpression(node->getMember(0), &myCollection, trx, argv, startPos, vars, regs, false);

      if (! value.isArray()) {
        // must cast value to array first
        FunctionParameters parameters{ std::make_pair(value, myCollection) };
        auto res = Functions::ToArray(_ast->query(), trx, parameters);

        // destroy old value and swap with function call result
        value.destroy();
        value = res;
      }

      std::function<void(TRI_json_t const*, int64_t)> flatten = [&] (TRI_json_t const* json, int64_t level) {
        if (! TRI_IsArrayJson(json)) {
          return;
        }

        size_t const n = TRI_LengthArrayJson(json);

        for (size_t i = 0; i < n; ++i) {
          auto item = static_cast<TRI_json_t const*>(TRI_AtVector(&json->_value._objects, i));
        
          bool const isArray = TRI_IsArrayJson(item);
 
          if (! isArray || level == levels) {
            flattened->add(TRI_CopyJson(TRI_UNKNOWN_MEM_ZONE, item));
          } 
          else if (isArray && level < levels) {
            flatten(item, level + 1);
          }
        }
      };

      auto subJson = value.toJson(trx, myCollection, false);
      flatten(subJson.json(), 1);
      value.destroy();

      value = AqlValue(flattened.release());
    }
    else {
      TRI_document_collection_t const* myCollection = nullptr;
      value = executeSimpleExpression(node->getMember(0), &myCollection, trx, argv, startPos, vars, regs, false);

      if (! value.isArray()) {
        // must cast value to array first
        FunctionParameters parameters{ std::make_pair(value, myCollection) };
        auto res = Functions::ToArray(_ast->query(), trx, parameters);

        // destroy old value and swap with function call result
        value.destroy();
        value = res;
      }
    }

    // RETURN
    // the default is to return array member unmodified 
    AstNode const* projectionNode = node->getMember(1);

    if (node->getMember(4)->type != NODE_TYPE_NOP) {
      // return projection
      projectionNode = node->getMember(4);
    }

    size_t const n = value.arraySize();
    std::unique_ptr<Json> array(new Json(Json::Array, n));

    for (size_t i = 0; i < n; ++i) {
      // TODO: check why we must copy the array member. will crash without copying!
      TRI_document_collection_t const* myCollection = nullptr;
      auto arrayItem = value.extractArrayMember(trx, myCollection, i, true);
      
      setVariable(variable, arrayItem.json());
 
      bool takeItem = true;

      if (filterNode != nullptr) {
        // have a filter
        TRI_document_collection_t const* subCollection = nullptr;
        AqlValue sub = executeSimpleExpression(filterNode, &subCollection, trx, argv, startPos, vars, regs, false);
        takeItem = sub.isTrue();
        sub.destroy();
      }

      if (takeItem && offset > 0) {
        // there is an offset in place
        --offset; 
        takeItem = false;
      }

      if (takeItem) {
        TRI_document_collection_t const* subCollection = nullptr;
        AqlValue sub = executeSimpleExpression(projectionNode, &subCollection, trx, argv, startPos, vars, regs, true);
        array->add(sub.toJson(trx, subCollection, true));
        sub.destroy();
      }

      clearVariable(variable);

      arrayItem.destroy();

      if (takeItem && count > 0) {
        // number of items to pick was restricted
        if (--count == 0) {
          // done
          break;
        }
      }
    }

    value.destroy();
    return AqlValue(array.release());
  }

  else if (node->type == NODE_TYPE_ITERATOR) { 
    TRI_ASSERT(node != nullptr);
    TRI_ASSERT(node->numMembers() == 2);

    // intentionally do not stringify node 0
    TRI_document_collection_t const* myCollection = nullptr;
    AqlValue value = executeSimpleExpression(node->getMember(1), &myCollection, trx, argv, startPos, vars, regs, true);
    return value;
  }

  std::string msg("unhandled type '");
  msg.append(node->getTypeString()); 
  msg.append("' in executeSimpleExpression()");
  THROW_ARANGO_EXCEPTION_MESSAGE(TRI_ERROR_INTERNAL, msg.c_str());
}