예제 #1
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();
}
예제 #2
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();
}
예제 #3
0
void AggregatorMin::reduce(AqlValue const& cmpValue) {
  if (value.isEmpty() ||
      (!cmpValue.isNull(true) &&
       AqlValue::Compare(trx, value, cmpValue, true) > 0)) {
    // the value `null` itself will not be used in MIN() to compare lower than
    // e.g. value `false`
    value.destroy();
    value = cmpValue.clone();
  }
}
예제 #4
0
void AggregatorMax::reduce(AqlValue const& cmpValue) {
  if (value.isEmpty() ||
      AqlValue::Compare(trx, value, cmpValue, true) < 0) {
    value.destroy();
    value = cmpValue.clone();
  }
}
예제 #5
0
void AggregatorSum::reduce(AqlValue const& cmpValue) {
  if (!invalid) {
    if (cmpValue.isNull(true)) {
      // ignore `null` values here
      return;
    }
    if (cmpValue.isNumber()) {
      double const number = cmpValue.toDouble(trx);
      if (!std::isnan(number) && number != HUGE_VAL &&
          number != -HUGE_VAL) {
        sum += number;
        return;
      }
    }
  }

  invalid = true;
}
예제 #6
0
void AggregatorVarianceBase::reduce(AqlValue const& cmpValue) {
  if (!invalid) {
    if (cmpValue.isNull(true)) {
      // ignore `null` values here
      return;
    }
    if (cmpValue.isNumber()) {
      double const number = cmpValue.toDouble(trx);
      if (!std::isnan(number) && number != HUGE_VAL &&
          number != -HUGE_VAL) {
        double const delta = number - mean;
        ++count;
        mean += delta / count;
        sum += delta * (number - mean);
        return;
      }
    }
  }

  invalid = true;
}
예제 #7
0
int AqlValue::Compare (triagens::arango::AqlTransaction* trx,
                       AqlValue const& left,  
                       TRI_document_collection_t const* leftcoll,
                       AqlValue const& right, 
                       TRI_document_collection_t const* rightcoll,
                       bool compareUtf8) {
  if (left._type != right._type) {
    if (left._type == AqlValue::EMPTY) {
      return -1;
    }

    if (right._type == AqlValue::EMPTY) {
      return 1;
    }

    // JSON against x
    if (left._type == AqlValue::JSON && 
        (right._type == AqlValue::SHAPED ||
         right._type == AqlValue::RANGE ||
         right._type == AqlValue::DOCVEC)) {
        triagens::basics::Json rjson = right.toJson(trx, rightcoll, false);
      return TRI_CompareValuesJson(left._json->json(), rjson.json(), compareUtf8);
    }
    
    // SHAPED against x
    if (left._type == AqlValue::SHAPED) {
      triagens::basics::Json ljson = left.toJson(trx, leftcoll, false);

      if (right._type == AqlValue::JSON) {
        return TRI_CompareValuesJson(ljson.json(), right._json->json(), compareUtf8);
      }
      else if (right._type == AqlValue::RANGE ||
               right._type == AqlValue::DOCVEC) {
        triagens::basics::Json rjson = right.toJson(trx, rightcoll, false);
        return TRI_CompareValuesJson(ljson.json(), rjson.json(), compareUtf8);
      }
    }

    // RANGE against x
    if (left._type == AqlValue::RANGE) {
      triagens::basics::Json ljson = left.toJson(trx, leftcoll, false);

      if (right._type == AqlValue::JSON) {
        return TRI_CompareValuesJson(ljson.json(), right._json->json(), compareUtf8);
      }
      else if (right._type == AqlValue::SHAPED ||
               right._type == AqlValue::DOCVEC) {
        triagens::basics::Json rjson = right.toJson(trx, rightcoll, false);
        return TRI_CompareValuesJson(ljson.json(), rjson.json(), compareUtf8);
      }
    }
    
    // DOCVEC against x
    if (left._type == AqlValue::DOCVEC) {
      triagens::basics::Json ljson = left.toJson(trx, leftcoll, false);

      if (right._type == AqlValue::JSON) {
        return TRI_CompareValuesJson(ljson.json(), right._json->json(), compareUtf8);
      }
      else if (right._type == AqlValue::SHAPED ||
               right._type == AqlValue::RANGE) {
        triagens::basics::Json rjson = right.toJson(trx, rightcoll, false);
        return TRI_CompareValuesJson(ljson.json(), rjson.json(), compareUtf8);
      }
    }

    // No other comparisons are defined
    TRI_ASSERT(false);
  }

  // if we get here, types are equal

  switch (left._type) {
    case AqlValue::EMPTY: {
      return 0;
    }

    case AqlValue::JSON: {
      TRI_ASSERT(left._json != nullptr);
      TRI_ASSERT(right._json != nullptr);
      return TRI_CompareValuesJson(left._json->json(), right._json->json(), compareUtf8);
    }

    case AqlValue::SHAPED: {
      TRI_shaped_json_t l;
      TRI_shaped_json_t r;
      TRI_EXTRACT_SHAPED_JSON_MARKER(l, left._marker);
      TRI_EXTRACT_SHAPED_JSON_MARKER(r, right._marker);
                
      return TRI_CompareShapeTypes(nullptr, nullptr, &l, leftcoll->getShaper(), 
                                   nullptr, nullptr, &r, rightcoll->getShaper());
    }

    case AqlValue::DOCVEC: { 
      // use lexicographic ordering of AqlValues regardless of block,
      // DOCVECs have a single register coming from ReturnNode.
      size_t lblock = 0;
      size_t litem = 0;
      size_t rblock = 0;
      size_t ritem = 0;
      
      while (lblock < left._vector->size() && 
             rblock < right._vector->size()) {
        AqlValue lval = left._vector->at(lblock)->getValue(litem, 0);
        AqlValue rval = right._vector->at(rblock)->getValue(ritem, 0);

        int cmp = Compare(
          trx,
          lval, 
          left._vector->at(lblock)->getDocumentCollection(0),
          rval, 
          right._vector->at(rblock)->getDocumentCollection(0),
          compareUtf8
        );

        if (cmp != 0) {
          return cmp;
        }
        if (++litem == left._vector->size()) {
          litem = 0;
          lblock++;
        }
        if (++ritem == right._vector->size()) {
          ritem = 0;
          rblock++;
        }
      }

      if (lblock == left._vector->size() && 
          rblock == right._vector->size()){
        return 0;
      }

      return (lblock < left._vector->size() ? -1 : 1);
    }

    case AqlValue::RANGE: {
      if (left._range->_low < right._range->_low) {
        return -1;
      } 
      if (left._range->_low > right._range->_low) {
        return 1;
      } 
      if (left._range->_high < left._range->_high) {
        return -1;
      } 
      if (left._range->_high > left._range->_high) {
        return 1;
      }
      return 0;
    }

    default: {
      TRI_ASSERT(false);
      return 0;
    }
  }
}
예제 #8
0
QueryResultV8 Query::executeV8 (v8::Isolate* isolate, QueryRegistry* registry) {

  // Now start the execution:
  try {
    QueryResultV8 res = prepare(registry);
    if (res.code != TRI_ERROR_NO_ERROR) {
      return res;
    }

    uint32_t j = 0;
    QueryResultV8 result(TRI_ERROR_NO_ERROR);
    result.result  = v8::Array::New(isolate);
    triagens::basics::Json stats;

    AqlItemBlock* value;

    while (nullptr != (value = _engine->getSome(1, ExecutionBlock::DefaultBatchSize))) {
      auto doc = value->getDocumentCollection(0);
      size_t const n = value->size();
      // reserve space for n additional results at once
      /// json.reserve(n);
      
      for (size_t i = 0; i < n; ++i) {
        AqlValue val = value->getValue(i, 0);

        if (! val.isEmpty()) {
          result.result->Set(j++, val.toV8(isolate, _trx, doc)); 
        }
      }
      delete value;
    }

    stats = _engine->_stats.toJson();

    _trx->commit();
    
    cleanupPlanAndEngine(TRI_ERROR_NO_ERROR);

    enterState(FINALIZATION); 

    result.warnings = warningsToJson(TRI_UNKNOWN_MEM_ZONE);
    result.stats    = stats.steal(); 

    if (_profile != nullptr && profiling()) {
      result.profile = _profile->toJson(TRI_UNKNOWN_MEM_ZONE);
    }

    return result;
  }
  catch (triagens::basics::Exception const& ex) {
    cleanupPlanAndEngine(ex.code());
    return QueryResultV8(ex.code(), ex.message() + getStateString());
  }
  catch (std::bad_alloc const&) {
    cleanupPlanAndEngine(TRI_ERROR_OUT_OF_MEMORY);
    return QueryResultV8(TRI_ERROR_OUT_OF_MEMORY, TRI_errno_string(TRI_ERROR_OUT_OF_MEMORY) + getStateString());
  }
  catch (std::exception const& ex) {
    cleanupPlanAndEngine(TRI_ERROR_INTERNAL);
    return QueryResultV8(TRI_ERROR_INTERNAL, ex.what() + getStateString());
  }
  catch (...) {
    cleanupPlanAndEngine(TRI_ERROR_INTERNAL);
    return QueryResult(TRI_ERROR_INTERNAL, TRI_errno_string(TRI_ERROR_INTERNAL) + getStateString());
  }
}
예제 #9
0
/// @brief sendToClient: for each row of the incoming AqlItemBlock use the
/// attributes <shardKeys> of the Aql value <val> to determine to which shard
/// the row should be sent and return its clientId
size_t DistributeBlock::sendToClient(AqlItemBlock* cur) {
  DEBUG_BEGIN_BLOCK();

  // inspect cur in row _pos and check to which shard it should be sent . .
  AqlValue val = cur->getValueReference(_pos, _regId);

  VPackSlice input = val.slice();  // will throw when wrong type

  bool usedAlternativeRegId = false;

  if (input.isNull() && _alternativeRegId != ExecutionNode::MaxRegisterId) {
    // value is set, but null
    // check if there is a second input register available (UPSERT makes use of
    // two input registers,
    // one for the search document, the other for the insert document)
    val = cur->getValueReference(_pos, _alternativeRegId);

    input = val.slice();  // will throw when wrong type
    usedAlternativeRegId = true;
  }

  VPackSlice value = input;

  VPackBuilder builder;
  VPackBuilder builder2;

  bool hasCreatedKeyAttribute = false;

  if (input.isString() &&
      static_cast<DistributeNode const*>(_exeNode)
          ->_allowKeyConversionToObject) {
    builder.openObject();
    builder.add(StaticStrings::KeyString, input);
    builder.close();

    // clear the previous value
    cur->destroyValue(_pos, _regId);

    // overwrite with new value
    cur->setValue(_pos, _regId, AqlValue(builder));

    value = builder.slice();
    hasCreatedKeyAttribute = true;
  } else if (!input.isObject()) {
    THROW_ARANGO_EXCEPTION(TRI_ERROR_ARANGO_DOCUMENT_TYPE_INVALID);
  }

  TRI_ASSERT(value.isObject());

  if (static_cast<DistributeNode const*>(_exeNode)->_createKeys) {
    // we are responsible for creating keys if none present

    if (_usesDefaultSharding) {
      // the collection is sharded by _key...

      if (!hasCreatedKeyAttribute && !value.hasKey(StaticStrings::KeyString)) {
        // there is no _key attribute present, so we are responsible for
        // creating one
        VPackBuilder temp;
        temp.openObject();
        temp.add(StaticStrings::KeyString, VPackValue(createKey(value)));
        temp.close();

        builder2 = VPackCollection::merge(input, temp.slice(), true);

        // clear the previous value and overwrite with new value:
        if (usedAlternativeRegId) {
          cur->destroyValue(_pos, _alternativeRegId);
          cur->setValue(_pos, _alternativeRegId, AqlValue(builder2));
        } else {
          cur->destroyValue(_pos, _regId);
          cur->setValue(_pos, _regId, AqlValue(builder2));
        }
        value = builder2.slice();
      }
    } else {
      // the collection is not sharded by _key

      if (hasCreatedKeyAttribute || value.hasKey(StaticStrings::KeyString)) {
        // a _key was given, but user is not allowed to specify _key
        if (usedAlternativeRegId || !_allowSpecifiedKeys) {
          THROW_ARANGO_EXCEPTION(TRI_ERROR_CLUSTER_MUST_NOT_SPECIFY_KEY);
        }
      } else {
        VPackBuilder temp;
        temp.openObject();
        temp.add(StaticStrings::KeyString, VPackValue(createKey(value)));
        temp.close();

        builder2 = VPackCollection::merge(input, temp.slice(), true);

        // clear the previous value and overwrite with new value:
        if (usedAlternativeRegId) {
          cur->destroyValue(_pos, _alternativeRegId);
          cur->setValue(_pos, _alternativeRegId, AqlValue(builder2.slice()));
        } else {
          cur->destroyValue(_pos, _regId);
          cur->setValue(_pos, _regId, AqlValue(builder2.slice()));
        }
        value = builder2.slice();
      }
    }
  }

  std::string shardId;
  bool usesDefaultShardingAttributes;
  auto clusterInfo = arangodb::ClusterInfo::instance();
  auto collInfo = _collection->getCollection();

  int res = clusterInfo->getResponsibleShard(collInfo.get(), value, true,
      shardId, usesDefaultShardingAttributes);

  // std::cout << "SHARDID: " << shardId << "\n";

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

  TRI_ASSERT(!shardId.empty());

  return getClientId(shardId);

  // cppcheck-suppress style
  DEBUG_END_BLOCK();
}
예제 #10
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();
}
예제 #11
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());
}
예제 #12
0
bool Expression::findInArray (AqlValue const& left, 
                              AqlValue const& right, 
                              TRI_document_collection_t const* leftCollection, 
                              TRI_document_collection_t const* rightCollection,
                              triagens::arango::AqlTransaction* trx,
                              AstNode const* node) const {
  TRI_ASSERT_EXPENSIVE(right.isArray());
 
  size_t const n = right.arraySize();

  if (node->getMember(1)->isSorted()) {
    // node values are sorted. can use binary search
    size_t l = 0;
    size_t r = n - 1;

    while (true) {
      // determine midpoint
      size_t m = l + ((r - l) / 2);
      auto arrayItem = right.extractArrayMember(trx, rightCollection, m, false);
      AqlValue arrayItemValue(&arrayItem);

      int compareResult = AqlValue::Compare(trx, left, leftCollection, arrayItemValue, nullptr, false);

      if (compareResult == 0) {
        // item found in the list
        return true;
      }

      if (compareResult < 0) {
        if (m == 0) {
          // not found
          return false;
        }
        r = m - 1;
      }
      else {
        l = m + 1;
      }
      if (r < l) {
        return false;
      }
    }
  }
  else {
    // use linear search
    for (size_t i = 0; i < n; ++i) {
      // do not copy the list element we're looking at
      auto arrayItem = right.extractArrayMember(trx, rightCollection, i, false);
      AqlValue arrayItemValue(&arrayItem);

      int compareResult = AqlValue::Compare(trx, left, leftCollection, arrayItemValue, nullptr, false);

      if (compareResult == 0) {
        // item found in the list
        return true;
      }
    }
    
    return false;  
  }
} 
예제 #13
0
/// @brief 3-way comparison for AqlValue objects
int AqlValue::Compare(arangodb::AqlTransaction* trx, AqlValue const& left,
                       AqlValue const& right,
                       bool compareUtf8) {
  VPackOptions* options = trx->transactionContext()->getVPackOptions();

  AqlValue::AqlValueType const leftType = left.type();
  AqlValue::AqlValueType const rightType = right.type();

  if (leftType != rightType) {
    if (leftType == RANGE || rightType == RANGE || leftType == DOCVEC || rightType == DOCVEC) {
      // range|docvec against x
      VPackBuilder leftBuilder;
      left.toVelocyPack(trx, leftBuilder, false);

      VPackBuilder rightBuilder;
      right.toVelocyPack(trx, rightBuilder, false);
    
      return arangodb::basics::VelocyPackHelper::compare(leftBuilder.slice(), rightBuilder.slice(), compareUtf8, options);
    }
    // fall-through to other types intentional
  }

  // if we get here, types are equal or can be treated as being equal

  switch (leftType) {
    case VPACK_SLICE_POINTER:
    case VPACK_INLINE:
    case VPACK_MANAGED: {
      return arangodb::basics::VelocyPackHelper::compare(left.slice(), right.slice(), compareUtf8, options);
    }
    case DOCVEC: {
      // use lexicographic ordering of AqlValues regardless of block,
      // DOCVECs have a single register coming from ReturnNode.
      size_t lblock = 0;
      size_t litem = 0;
      size_t rblock = 0;
      size_t ritem = 0;
      size_t const lsize = left._data.docvec->size();
      size_t const rsize = right._data.docvec->size();

      if (lsize == 0 || rsize == 0) {
        if (lsize == rsize) {
          // both empty
          return 0;
        }
        return (lsize < rsize ? -1 : 1);
      }

      size_t lrows = left._data.docvec->at(0)->size();
      size_t rrows = right._data.docvec->at(0)->size();

      while (lblock < lsize && rblock < rsize) {
        AqlValue const& lval = left._data.docvec->at(lblock)->getValueReference(litem, 0);
        AqlValue const& rval = right._data.docvec->at(rblock)->getValueReference(ritem, 0);

        int cmp = Compare(trx, lval, rval, compareUtf8);

        if (cmp != 0) {
          return cmp;
        }
        if (++litem == lrows) {
          litem = 0;
          lblock++;
          if (lblock < lsize) {
            lrows = left._data.docvec->at(lblock)->size();
          }
        }
        if (++ritem == rrows) {
          ritem = 0;
          rblock++;
          if (rblock < rsize) {
            rrows = right._data.docvec->at(rblock)->size();
          }
        }
      }

      if (lblock == lsize && rblock == rsize) {
        // both blocks exhausted
        return 0;
      }

      return (lblock < lsize ? -1 : 1);
    }
    case RANGE: {
      if (left.range()->_low < right.range()->_low) {
        return -1;
      }
      if (left.range()->_low > right.range()->_low) {
        return 1;
      }
      if (left.range()->_high < right.range()->_high) {
        return -1;
      }
      if (left.range()->_high > right.range()->_high) {
        return 1;
      }
      return 0;
    }
  }

  return 0;
}