mirror of https://gitee.com/bigwinds/arangodb
Merge branch 'aql-feature-lazy-index' of https://github.com/triAGENS/ArangoDB into devel
This commit is contained in:
commit
5fda62b725
|
@ -557,11 +557,12 @@ SHELL_SERVER_AQL = @top_srcdir@/js/server/tests/aql-arithmetic.js \
|
|||
@top_srcdir@/js/server/tests/aql-optimizer-rule-move-calculations-up.js \
|
||||
@top_srcdir@/js/server/tests/aql-optimizer-rule-move-filters-up.js \
|
||||
@top_srcdir@/js/server/tests/aql-optimizer-rule-remove-redundant-calculations.js \
|
||||
@top_srcdir@/js/server/tests/aql-optimizer-rule-remove-redundant-or.js \
|
||||
@top_srcdir@/js/server/tests/aql-optimizer-rule-remove-redundant-sorts.js \
|
||||
@top_srcdir@/js/server/tests/aql-optimizer-rule-remove-unnecessary-calculations.js \
|
||||
@top_srcdir@/js/server/tests/aql-optimizer-rule-remove-unnecessary-filters.js \
|
||||
@top_srcdir@/js/server/tests/aql-optimizer-rule-use-index-for-sort.js \
|
||||
@top_srcdir@/js/server/tests/aql-optimizer-rule-replace-or-with-in.js \
|
||||
@top_srcdir@/js/server/tests/aql-optimizer-rule-use-index-for-sort.js \
|
||||
@top_srcdir@/js/server/tests/aql-parse.js \
|
||||
@top_srcdir@/js/server/tests/aql-primary-index-noncluster.js \
|
||||
@top_srcdir@/js/server/tests/aql-queries-collection.js \
|
||||
|
|
|
@ -81,7 +81,7 @@ AstNode const Ast::EmptyStringNode{ "", VALUE_TYPE_STRING };
|
|||
/// @brief inverse comparison operators
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
std::unordered_map<int, AstNodeType> const Ast::ReverseOperators{
|
||||
std::unordered_map<int, AstNodeType> const Ast::NegatedOperators{
|
||||
{ static_cast<int>(NODE_TYPE_OPERATOR_BINARY_EQ), NODE_TYPE_OPERATOR_BINARY_NE },
|
||||
{ static_cast<int>(NODE_TYPE_OPERATOR_BINARY_NE), NODE_TYPE_OPERATOR_BINARY_EQ },
|
||||
{ static_cast<int>(NODE_TYPE_OPERATOR_BINARY_GT), NODE_TYPE_OPERATOR_BINARY_LE },
|
||||
|
@ -92,6 +92,18 @@ std::unordered_map<int, AstNodeType> const Ast::ReverseOperators{
|
|||
{ static_cast<int>(NODE_TYPE_OPERATOR_BINARY_NIN), NODE_TYPE_OPERATOR_BINARY_IN }
|
||||
};
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
/// @brief reverse comparison operators
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
std::unordered_map<int, AstNodeType> const Ast::ReversedOperators{
|
||||
{ static_cast<int>(NODE_TYPE_OPERATOR_BINARY_EQ), NODE_TYPE_OPERATOR_BINARY_EQ },
|
||||
{ static_cast<int>(NODE_TYPE_OPERATOR_BINARY_GT), NODE_TYPE_OPERATOR_BINARY_LT },
|
||||
{ static_cast<int>(NODE_TYPE_OPERATOR_BINARY_GE), NODE_TYPE_OPERATOR_BINARY_LE },
|
||||
{ static_cast<int>(NODE_TYPE_OPERATOR_BINARY_LT), NODE_TYPE_OPERATOR_BINARY_GT },
|
||||
{ static_cast<int>(NODE_TYPE_OPERATOR_BINARY_LE), NODE_TYPE_OPERATOR_BINARY_GE }
|
||||
};
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// --SECTION-- constructors / destructors
|
||||
// -----------------------------------------------------------------------------
|
||||
|
@ -1088,6 +1100,19 @@ AstNode* Ast::clone (AstNode const* node) {
|
|||
return copy;
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
/// @brief get the reversed operator for a comparison operator
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
AstNodeType Ast::ReverseOperator (AstNodeType type) {
|
||||
auto it = ReversedOperators.find(static_cast<int>(type));
|
||||
if (it == ReversedOperators.end()) {
|
||||
THROW_ARANGO_EXCEPTION_MESSAGE(TRI_ERROR_INTERNAL, "invalid node type for inversed operator");
|
||||
}
|
||||
|
||||
return (*it).second;
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// --SECTION-- private methods
|
||||
// -----------------------------------------------------------------------------
|
||||
|
@ -1227,8 +1252,8 @@ AstNode* Ast::optimizeNotExpression (AstNode* node) {
|
|||
auto lhs = operand->getMember(0);
|
||||
auto rhs = operand->getMember(1);
|
||||
|
||||
auto it = ReverseOperators.find(static_cast<int>(operand->type));
|
||||
TRI_ASSERT(it != ReverseOperators.end());
|
||||
auto it = NegatedOperators.find(static_cast<int>(operand->type));
|
||||
TRI_ASSERT(it != NegatedOperators.end());
|
||||
|
||||
return createNodeBinaryOperator((*it).second, lhs, rhs);
|
||||
}
|
||||
|
|
|
@ -392,13 +392,13 @@ namespace triagens {
|
|||
/// @brief create an AST null value node
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
AstNode* createNodeValueNull ();
|
||||
static AstNode* createNodeValueNull ();
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
/// @brief create an AST bool value node
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
AstNode* createNodeValueBool (bool);
|
||||
static AstNode* createNodeValueBool (bool);
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
/// @brief create an AST int value node
|
||||
|
@ -489,6 +489,12 @@ namespace triagens {
|
|||
|
||||
AstNode* clone (AstNode const*);
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
/// @brief get the reversed operator for a comparison operator
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
static AstNodeType ReverseOperator (AstNodeType);
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// --SECTION-- private methods
|
||||
// -----------------------------------------------------------------------------
|
||||
|
@ -630,11 +636,16 @@ namespace triagens {
|
|||
public:
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
/// @brief inverse comparison operators
|
||||
/// @brief negated comparison operators
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
static std::unordered_map<int, AstNodeType> const ReverseOperators;
|
||||
static std::unordered_map<int, AstNodeType> const NegatedOperators;
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
/// @brief reverse comparison operators
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
static std::unordered_map<int, AstNodeType> const ReversedOperators;
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// --SECTION-- private variables
|
||||
|
|
|
@ -132,12 +132,74 @@ std::unordered_map<int, std::string const> const AstNode::valueTypeNames{
|
|||
// -----------------------------------------------------------------------------
|
||||
// --SECTION-- static helper functions
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
/// @brief resolve an attribute access
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
static AstNode const* ResolveAttribute (AstNode const* node) {
|
||||
TRI_ASSERT(node != nullptr);
|
||||
TRI_ASSERT(node->type == NODE_TYPE_ATTRIBUTE_ACCESS);
|
||||
|
||||
std::vector<std::string> attributeNames;
|
||||
|
||||
while (node->type == NODE_TYPE_ATTRIBUTE_ACCESS) {
|
||||
char const* attributeName = node->getStringValue();
|
||||
|
||||
TRI_ASSERT(attributeName != nullptr);
|
||||
attributeNames.push_back(attributeName);
|
||||
node = node->getMember(0);
|
||||
}
|
||||
|
||||
while (true) {
|
||||
TRI_ASSERT(! attributeNames.empty());
|
||||
|
||||
TRI_ASSERT(node->type == NODE_TYPE_VALUE ||
|
||||
node->type == NODE_TYPE_LIST ||
|
||||
node->type == NODE_TYPE_ARRAY);
|
||||
|
||||
bool found = false;
|
||||
|
||||
if (node->type == NODE_TYPE_ARRAY) {
|
||||
char const* attributeName = attributeNames.back().c_str();
|
||||
attributeNames.pop_back();
|
||||
|
||||
size_t const n = node->numMembers();
|
||||
for (size_t i = 0; i < n; ++i) {
|
||||
auto member = node->getMember(i);
|
||||
|
||||
if (member != nullptr && strcmp(member->getStringValue(), attributeName) == 0) {
|
||||
// found the attribute
|
||||
node = member->getMember(0);
|
||||
if (attributeNames.empty()) {
|
||||
// we found what we looked for
|
||||
return node;
|
||||
}
|
||||
else {
|
||||
// we found the correct attribute but there is now an attribute access on the result
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (! found) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// attribute not found or non-array
|
||||
return Ast::createNodeValueNull();
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
/// @brief get the node type for inter-node comparisons
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
static TRI_json_type_e GetNodeCompareType (AstNode const* node) {
|
||||
TRI_ASSERT(node != nullptr);
|
||||
|
||||
if (node->type == NODE_TYPE_VALUE) {
|
||||
switch (node->value.type) {
|
||||
case VALUE_TYPE_NULL:
|
||||
|
@ -158,7 +220,11 @@ static TRI_json_type_e GetNodeCompareType (AstNode const* node) {
|
|||
else if (node->type == NODE_TYPE_ARRAY) {
|
||||
return TRI_JSON_ARRAY;
|
||||
}
|
||||
|
||||
// we should never get here
|
||||
TRI_ASSERT(false);
|
||||
|
||||
// return null in case assertions are turned off
|
||||
return TRI_JSON_NULL;
|
||||
}
|
||||
|
||||
|
@ -166,7 +232,15 @@ static TRI_json_type_e GetNodeCompareType (AstNode const* node) {
|
|||
/// @brief compare two nodes
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
static int CompareNodes (AstNode const* lhs, AstNode const* rhs) {
|
||||
int triagens::aql::CompareAstNodes (AstNode const* lhs,
|
||||
AstNode const* rhs) {
|
||||
if (lhs->type == NODE_TYPE_ATTRIBUTE_ACCESS) {
|
||||
lhs = ResolveAttribute(lhs);
|
||||
}
|
||||
if (rhs->type == NODE_TYPE_ATTRIBUTE_ACCESS) {
|
||||
rhs = ResolveAttribute(rhs);
|
||||
}
|
||||
|
||||
auto lType = GetNodeCompareType(lhs);
|
||||
auto rType = GetNodeCompareType(rhs);
|
||||
|
||||
|
@ -195,7 +269,7 @@ static int CompareNodes (AstNode const* lhs, AstNode const* rhs) {
|
|||
size_t const n = ((numLhs > numRhs) ? numRhs : numLhs);
|
||||
|
||||
for (size_t i = 0; i < n; ++i) {
|
||||
int res = CompareNodes(lhs->getMember(i), rhs->getMember(i));
|
||||
int res = triagens::aql::CompareAstNodes(lhs->getMember(i), rhs->getMember(i));
|
||||
if (res != 0) {
|
||||
return res;
|
||||
}
|
||||
|
@ -492,7 +566,7 @@ void AstNode::sort () {
|
|||
auto const l = static_cast<AstNode const*>(lhs);
|
||||
auto const r = static_cast<AstNode const*>(rhs);
|
||||
|
||||
return (CompareNodes(l, r) < 0);
|
||||
return (triagens::aql::CompareAstNodes(l, r) < 0);
|
||||
});
|
||||
|
||||
setFlag(FLAG_SORTED);
|
||||
|
@ -621,6 +695,23 @@ TRI_json_t* AstNode::toJsonValue (TRI_memory_zone_t* zone) const {
|
|||
return array;
|
||||
}
|
||||
|
||||
if (type == NODE_TYPE_ATTRIBUTE_ACCESS) {
|
||||
TRI_json_t* j = getMember(0)->toJsonValue(zone);
|
||||
|
||||
if (j != nullptr) {
|
||||
if (TRI_IsArrayJson(j)) {
|
||||
TRI_json_t* v = TRI_LookupArrayJson(j, getStringValue());
|
||||
if (v != nullptr) {
|
||||
TRI_json_t* copy = TRI_CopyJson(zone, v);
|
||||
TRI_FreeJson(zone, j);
|
||||
return copy;
|
||||
}
|
||||
}
|
||||
TRI_FreeJson(zone, j);
|
||||
return TRI_CreateNullJson(zone);
|
||||
}
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
|
@ -1099,10 +1190,12 @@ bool AstNode::isSimple () const {
|
|||
|
||||
bool AstNode::isConstant () const {
|
||||
if (hasFlag(FLAG_CONSTANT)) {
|
||||
TRI_ASSERT(! hasFlag(FLAG_DYNAMIC));
|
||||
// fast track exit
|
||||
return true;
|
||||
}
|
||||
if (hasFlag(FLAG_DYNAMIC)) {
|
||||
TRI_ASSERT(! hasFlag(FLAG_CONSTANT));
|
||||
// fast track exit
|
||||
return false;
|
||||
}
|
||||
|
@ -1154,6 +1247,13 @@ bool AstNode::isConstant () const {
|
|||
return true;
|
||||
}
|
||||
|
||||
if (type == NODE_TYPE_ATTRIBUTE_ACCESS) {
|
||||
if (getMember(0)->isConstant()) {
|
||||
setFlag(FLAG_CONSTANT);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
setFlag(FLAG_DYNAMIC);
|
||||
return false;
|
||||
}
|
||||
|
@ -1505,6 +1605,12 @@ void AstNode::stringify (triagens::basics::StringBuffer* buffer,
|
|||
THROW_ARANGO_EXCEPTION_MESSAGE(TRI_ERROR_INTERNAL, message);
|
||||
}
|
||||
|
||||
std::string AstNode::toString () const {
|
||||
triagens::basics::StringBuffer buffer(TRI_UNKNOWN_MEM_ZONE);
|
||||
stringify(&buffer, false);
|
||||
return std::string(buffer.c_str(), buffer.length());
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// --SECTION-- private methods
|
||||
// -----------------------------------------------------------------------------
|
||||
|
|
|
@ -648,6 +648,9 @@ namespace triagens {
|
|||
|
||||
void stringify (triagens::basics::StringBuffer*,
|
||||
bool) const;
|
||||
|
||||
std::string toString () const;
|
||||
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// --SECTION-- private methods
|
||||
|
@ -705,6 +708,7 @@ namespace triagens {
|
|||
|
||||
};
|
||||
|
||||
int CompareAstNodes (AstNode const*, AstNode const*);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -409,7 +409,7 @@ size_t ExecutionBlock::skipSome (size_t atLeast, size_t atMost) {
|
|||
bool ExecutionBlock::skip (size_t number) {
|
||||
size_t skipped = skipSome(number, number);
|
||||
size_t nr = skipped;
|
||||
while ( nr != 0 && skipped < number ){
|
||||
while (nr != 0 && skipped < number) {
|
||||
nr = skipSome(number - skipped, number - skipped);
|
||||
skipped += nr;
|
||||
}
|
||||
|
@ -473,8 +473,7 @@ int ExecutionBlock::getOrSkipSome (size_t atLeast,
|
|||
return TRI_ERROR_NO_ERROR;
|
||||
}
|
||||
else {
|
||||
if (! getBlock(atLeast - skipped,
|
||||
(std::max)(atMost - skipped, DefaultBatchSize))) {
|
||||
if (! getBlock(atLeast - skipped, atMost - skipped)) {
|
||||
_done = true;
|
||||
break; // must still put things in the result from the collector . . .
|
||||
}
|
||||
|
@ -486,7 +485,7 @@ int ExecutionBlock::getOrSkipSome (size_t atLeast,
|
|||
|
||||
if (cur->size() - _pos > atMost - skipped) {
|
||||
// The current block is too large for atMost:
|
||||
if (! skipping){
|
||||
if (! skipping) {
|
||||
unique_ptr<AqlItemBlock> more(cur->slice(_pos, _pos + (atMost - skipped)));
|
||||
collector.push_back(more.get());
|
||||
more.release(); // do not delete it!
|
||||
|
@ -497,7 +496,7 @@ int ExecutionBlock::getOrSkipSome (size_t atLeast,
|
|||
else if (_pos > 0) {
|
||||
// The current block fits into our result, but it is already
|
||||
// half-eaten:
|
||||
if(! skipping){
|
||||
if (! skipping) {
|
||||
unique_ptr<AqlItemBlock> more(cur->slice(_pos, cur->size()));
|
||||
collector.push_back(more.get());
|
||||
more.release();
|
||||
|
@ -580,7 +579,7 @@ int SingletonBlock::shutdown (int errorCode) {
|
|||
}
|
||||
|
||||
int SingletonBlock::getOrSkipSome (size_t, // atLeast,
|
||||
size_t, // atMost,
|
||||
size_t atMost, // atMost,
|
||||
bool skipping,
|
||||
AqlItemBlock*& result,
|
||||
size_t& skipped) {
|
||||
|
@ -591,7 +590,7 @@ int SingletonBlock::getOrSkipSome (size_t, // atLeast,
|
|||
return TRI_ERROR_NO_ERROR;
|
||||
}
|
||||
|
||||
if(! skipping){
|
||||
if (! skipping) {
|
||||
result = new AqlItemBlock(1, getPlanNode()->getRegisterPlan()->nrRegs[getPlanNode()->getDepth()]);
|
||||
try {
|
||||
if (_inputRegisterValues != nullptr) {
|
||||
|
@ -849,9 +848,12 @@ IndexRangeBlock::IndexRangeBlock (ExecutionEngine* engine,
|
|||
: ExecutionBlock(engine, en),
|
||||
_collection(en->collection()),
|
||||
_posInDocs(0),
|
||||
_allBoundsConstant(true) {
|
||||
_allBoundsConstant(true),
|
||||
_skiplistIterator(nullptr),
|
||||
_condition(&en->_ranges),
|
||||
_freeCondition(false) {
|
||||
|
||||
std::vector<std::vector<RangeInfo>> const& orRanges = en->_ranges;
|
||||
std::vector<std::vector<RangeInfo>> const& orRanges = en->_ranges;//TODO replace this with _condition
|
||||
TRI_ASSERT(en->_index != nullptr);
|
||||
|
||||
TRI_ASSERT(orRanges.size() == 1); // OR expressions not yet implemented
|
||||
|
@ -868,6 +870,10 @@ IndexRangeBlock::~IndexRangeBlock () {
|
|||
delete e;
|
||||
}
|
||||
_allVariableBoundExpressions.clear();
|
||||
|
||||
if (_freeCondition && _condition != nullptr) {
|
||||
delete _condition;
|
||||
}
|
||||
}
|
||||
|
||||
int IndexRangeBlock::initialize () {
|
||||
|
@ -931,38 +937,38 @@ int IndexRangeBlock::initialize () {
|
|||
throw;
|
||||
}
|
||||
}
|
||||
else { // _allBoundsConstant
|
||||
readIndex();
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
bool IndexRangeBlock::readIndex () {
|
||||
// This is either called from initialize if all bounds are constant,
|
||||
// in this case it is never called again. If there is at least one
|
||||
// variable bound, then readIndex is called once for every item coming
|
||||
// in from our dependency. In that case, it is guaranteed that
|
||||
// _buffer is not empty, in particular _buffer.front() is defined
|
||||
// _pos points to a position in _buffer.front()
|
||||
// Therefore, we can use the register values in _buffer.front() in row
|
||||
// _pos to evaluate the variable bounds.
|
||||
|
||||
if (_documents.empty()) {
|
||||
_documents.reserve(DefaultBatchSize);
|
||||
}
|
||||
else {
|
||||
_documents.clear();
|
||||
}
|
||||
// init the index for reading, this should be called once per new incoming
|
||||
// block!
|
||||
//
|
||||
// This is either called every time we get a new incoming block.
|
||||
// If all the bounds are constant, then in the case of hash, primary or edges
|
||||
// indexes it does nothing. In the case of a skiplist index, it creates a
|
||||
// skiplistIterator which is used by readIndex. If at least one bound is
|
||||
// variable, then this this also evaluates the IndexOrCondition required to
|
||||
// determine the values of the bounds.
|
||||
//
|
||||
// It is guaranteed that
|
||||
// _buffer is not empty, in particular _buffer.front() is defined
|
||||
// _pos points to a position in _buffer.front()
|
||||
// Therefore, we can use the register values in _buffer.front() in row
|
||||
// _pos to evaluate the variable bounds.
|
||||
|
||||
bool IndexRangeBlock::initIndex () {
|
||||
ENTER_BLOCK
|
||||
_flag = true;
|
||||
auto en = static_cast<IndexRangeNode const*>(getPlanNode());
|
||||
IndexOrCondition const* condition = &en->_ranges;
|
||||
freeCondition();
|
||||
_condition = &en->_ranges;
|
||||
_freeCondition = false;
|
||||
|
||||
TRI_ASSERT(en->_index != nullptr);
|
||||
|
||||
std::unique_ptr<IndexOrCondition> newCondition;
|
||||
|
||||
// Find out about the actual values for the bounds in the variable bound case:
|
||||
if (! _allBoundsConstant) {
|
||||
std::unique_ptr<IndexOrCondition> newCondition;
|
||||
// The following are needed to evaluate expressions with local data from
|
||||
// the current incoming item:
|
||||
AqlItemBlock* cur = _buffer.front();
|
||||
|
@ -1050,30 +1056,87 @@ bool IndexRangeBlock::readIndex () {
|
|||
|
||||
newCondition.get()->at(0).push_back(actualRange);
|
||||
}
|
||||
|
||||
condition = newCondition.get();
|
||||
|
||||
freeCondition();
|
||||
_condition = newCondition.release();
|
||||
_freeCondition = true;
|
||||
}
|
||||
|
||||
|
||||
if (en->_index->type == TRI_IDX_TYPE_PRIMARY_INDEX) {
|
||||
readPrimaryIndex(*condition);
|
||||
return true; //no initialization here!
|
||||
}
|
||||
else if (en->_index->type == TRI_IDX_TYPE_HASH_INDEX) {
|
||||
readHashIndex(*condition);
|
||||
return true; //no initialization here!
|
||||
}
|
||||
else if (en->_index->type == TRI_IDX_TYPE_SKIPLIST_INDEX) {
|
||||
readSkiplistIndex(*condition);
|
||||
if (en->_index->type == TRI_IDX_TYPE_SKIPLIST_INDEX) {
|
||||
initSkiplistIndex(*_condition);
|
||||
return (_skiplistIterator != nullptr);
|
||||
}
|
||||
else if (en->_index->type == TRI_IDX_TYPE_EDGE_INDEX) {
|
||||
readEdgeIndex(*condition);
|
||||
return true; //no initialization here!
|
||||
}
|
||||
|
||||
THROW_ARANGO_EXCEPTION_MESSAGE(TRI_ERROR_INTERNAL, "unexpected index type");
|
||||
LEAVE_BLOCK;
|
||||
}
|
||||
|
||||
void IndexRangeBlock::freeCondition () {
|
||||
if (_condition != nullptr && _freeCondition) {
|
||||
delete _condition;
|
||||
_condition = nullptr;
|
||||
_freeCondition = false;
|
||||
}
|
||||
}
|
||||
|
||||
// this is called every time everything in _documents has been passed on
|
||||
|
||||
bool IndexRangeBlock::readIndex (size_t atMost) {
|
||||
ENTER_BLOCK;
|
||||
// this is called every time we want more in _documents.
|
||||
// For non-skiplist indexes (currently hash, primary, edge), this
|
||||
// only reads the index once, and never again (although there might be
|
||||
// multiple calls to this function). For skiplists indexes, initIndex creates
|
||||
// a skiplistIterator and readIndex just reads from the iterator until it is
|
||||
// done. Then initIndex is read again and so on. This is to avoid reading the
|
||||
// entire index when we only want a small number of documents.
|
||||
|
||||
if (_documents.empty()) {
|
||||
_documents.reserve(atMost);
|
||||
}
|
||||
else {
|
||||
_documents.clear();
|
||||
}
|
||||
|
||||
auto en = static_cast<IndexRangeNode const*>(getPlanNode());
|
||||
|
||||
if (en->_index->type == TRI_IDX_TYPE_PRIMARY_INDEX) {
|
||||
if (_flag) {
|
||||
readPrimaryIndex(*_condition);
|
||||
}
|
||||
}
|
||||
else if (en->_index->type == TRI_IDX_TYPE_HASH_INDEX) {
|
||||
if (_flag) {
|
||||
readHashIndex(*_condition);
|
||||
}
|
||||
}
|
||||
else if (en->_index->type == TRI_IDX_TYPE_SKIPLIST_INDEX) {
|
||||
readSkiplistIndex(atMost);
|
||||
}
|
||||
else if (en->_index->type == TRI_IDX_TYPE_EDGE_INDEX) {
|
||||
if (_flag) {
|
||||
readEdgeIndex(*_condition);
|
||||
}
|
||||
}
|
||||
else {
|
||||
TRI_ASSERT(false);
|
||||
}
|
||||
|
||||
return (!_documents.empty());
|
||||
_flag = false;
|
||||
return (! _documents.empty());
|
||||
LEAVE_BLOCK;
|
||||
}
|
||||
|
||||
int IndexRangeBlock::initializeCursor (AqlItemBlock* items, size_t pos) {
|
||||
ENTER_BLOCK;
|
||||
int res = ExecutionBlock::initializeCursor(items, pos);
|
||||
if (res != TRI_ERROR_NO_ERROR) {
|
||||
return res;
|
||||
|
@ -1081,19 +1144,17 @@ int IndexRangeBlock::initializeCursor (AqlItemBlock* items, size_t pos) {
|
|||
_pos = 0;
|
||||
_posInDocs = 0;
|
||||
|
||||
if (_allBoundsConstant && _documents.size() == 0) {
|
||||
_done = true;
|
||||
}
|
||||
|
||||
return TRI_ERROR_NO_ERROR;
|
||||
LEAVE_BLOCK;
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
/// @brief getSome
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
AqlItemBlock* IndexRangeBlock::getSome (size_t, // atLeast
|
||||
AqlItemBlock* IndexRangeBlock::getSome (size_t atLeast,
|
||||
size_t atMost) {
|
||||
ENTER_BLOCK;
|
||||
if (_done) {
|
||||
return nullptr;
|
||||
}
|
||||
|
@ -1107,18 +1168,44 @@ AqlItemBlock* IndexRangeBlock::getSome (size_t, // atLeast
|
|||
// try again!
|
||||
|
||||
if (_buffer.empty()) {
|
||||
if (! ExecutionBlock::getBlock(DefaultBatchSize, DefaultBatchSize)) {
|
||||
if (! ExecutionBlock::getBlock(DefaultBatchSize, DefaultBatchSize)
|
||||
|| (! initIndex())) {
|
||||
_done = true;
|
||||
return nullptr;
|
||||
}
|
||||
_pos = 0; // this is in the first block
|
||||
|
||||
// This is a new item, so let's read the index if bounds are variable:
|
||||
if (! _allBoundsConstant) {
|
||||
readIndex();
|
||||
}
|
||||
|
||||
// This is a new item, so let's read the index (it is already
|
||||
// initialised).
|
||||
readIndex(atMost);
|
||||
_posInDocs = 0; // position in _documents . . .
|
||||
}
|
||||
else if (_posInDocs >= _documents.size()) {
|
||||
// we have exhausted our local documents buffer,
|
||||
|
||||
_posInDocs = 0;
|
||||
AqlItemBlock* cur = _buffer.front();
|
||||
|
||||
if (! readIndex(atMost)) { //no more output from this version of the index
|
||||
if (++_pos >= cur->size()) {
|
||||
_buffer.pop_front(); // does not throw
|
||||
delete cur;
|
||||
_pos = 0;
|
||||
}
|
||||
if (_buffer.empty()) {
|
||||
if (! ExecutionBlock::getBlock(DefaultBatchSize, DefaultBatchSize) ) {
|
||||
_done = true;
|
||||
return nullptr;
|
||||
}
|
||||
_pos = 0; // this is in the first block
|
||||
}
|
||||
|
||||
if(! initIndex()) {
|
||||
_done = true;
|
||||
return nullptr;
|
||||
}
|
||||
readIndex(atMost);
|
||||
}
|
||||
}
|
||||
|
||||
// If we get here, we do have _buffer.front() and _pos points into it
|
||||
|
@ -1130,7 +1217,8 @@ AqlItemBlock* IndexRangeBlock::getSome (size_t, // atLeast
|
|||
|
||||
if (toSend > 0) {
|
||||
|
||||
res.reset(new AqlItemBlock(toSend, getPlanNode()->getRegisterPlan()->nrRegs[getPlanNode()->getDepth()]));
|
||||
res.reset(new AqlItemBlock(toSend,
|
||||
getPlanNode()->getRegisterPlan()->nrRegs[getPlanNode()->getDepth()]));
|
||||
|
||||
// automatically freed should we throw
|
||||
TRI_ASSERT(curRegs <= res->getNrRegs());
|
||||
|
@ -1139,7 +1227,8 @@ AqlItemBlock* IndexRangeBlock::getSome (size_t, // atLeast
|
|||
inheritRegisters(cur, res.get(), _pos);
|
||||
|
||||
// set our collection for our output register
|
||||
res->setDocumentCollection(static_cast<triagens::aql::RegisterId>(curRegs), _trx->documentCollection(_collection->cid()));
|
||||
res->setDocumentCollection(static_cast<triagens::aql::RegisterId>(curRegs),
|
||||
_trx->documentCollection(_collection->cid()));
|
||||
|
||||
for (size_t j = 0; j < toSend; j++) {
|
||||
if (j > 0) {
|
||||
|
@ -1161,32 +1250,13 @@ AqlItemBlock* IndexRangeBlock::getSome (size_t, // atLeast
|
|||
}
|
||||
}
|
||||
|
||||
// Advance read position:
|
||||
if (_posInDocs >= _documents.size()) {
|
||||
// we have exhausted our local documents buffer,
|
||||
|
||||
_posInDocs = 0;
|
||||
|
||||
if (++_pos >= cur->size()) {
|
||||
_buffer.pop_front(); // does not throw
|
||||
delete cur;
|
||||
_pos = 0;
|
||||
}
|
||||
|
||||
// let's read the index if bounds are variable:
|
||||
if (! _buffer.empty() && ! _allBoundsConstant) {
|
||||
readIndex();
|
||||
}
|
||||
// If _buffer is empty, then we will fetch a new block in the next call
|
||||
// and then read the index.
|
||||
|
||||
}
|
||||
}
|
||||
while (res.get() == nullptr);
|
||||
|
||||
// Clear out registers no longer needed later:
|
||||
clearRegisters(res.get());
|
||||
return res.release();
|
||||
LEAVE_BLOCK;
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
@ -1202,19 +1272,17 @@ size_t IndexRangeBlock::skipSome (size_t atLeast,
|
|||
|
||||
size_t skipped = 0;
|
||||
|
||||
while (skipped < atLeast ){
|
||||
while (skipped < atLeast) {
|
||||
if (_buffer.empty()) {
|
||||
if (! ExecutionBlock::getBlock(DefaultBatchSize, DefaultBatchSize)) {
|
||||
if (! ExecutionBlock::getBlock(DefaultBatchSize, DefaultBatchSize)
|
||||
|| (! initIndex())) {
|
||||
_done = true;
|
||||
return skipped;
|
||||
}
|
||||
_pos = 0; // this is in the first block
|
||||
|
||||
// This is a new item, so let's read the index if bounds are variable:
|
||||
if (! _allBoundsConstant) {
|
||||
readIndex();
|
||||
}
|
||||
|
||||
readIndex(atMost);
|
||||
_posInDocs = 0; // position in _documents . . .
|
||||
}
|
||||
|
||||
|
@ -1238,15 +1306,18 @@ size_t IndexRangeBlock::skipSome (size_t atLeast,
|
|||
}
|
||||
|
||||
// let's read the index if bounds are variable:
|
||||
if (! _buffer.empty() && ! _allBoundsConstant) {
|
||||
readIndex();
|
||||
if (! _buffer.empty()) {
|
||||
if(! initIndex()) {
|
||||
_done = true;
|
||||
return skipped;
|
||||
}
|
||||
readIndex(atMost);
|
||||
}
|
||||
_posInDocs = 0;
|
||||
|
||||
// If _buffer is empty, then we will fetch a new block in the next round
|
||||
// and then read the index.
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return skipped;
|
||||
|
@ -1257,6 +1328,7 @@ size_t IndexRangeBlock::skipSome (size_t atLeast,
|
|||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
void IndexRangeBlock::readPrimaryIndex (IndexOrCondition const& ranges) {
|
||||
ENTER_BLOCK;
|
||||
TRI_primary_index_t* primaryIndex = &(_collection->documentCollection()->_primaryIndex);
|
||||
|
||||
std::string key;
|
||||
|
@ -1310,9 +1382,10 @@ void IndexRangeBlock::readPrimaryIndex (IndexOrCondition const& ranges) {
|
|||
|
||||
auto found = static_cast<TRI_doc_mptr_t const*>(TRI_LookupByKeyPrimaryIndex(primaryIndex, key.c_str()));
|
||||
if (found != nullptr) {
|
||||
_documents.push_back(*found);
|
||||
_documents.emplace_back(*found);
|
||||
}
|
||||
}
|
||||
LEAVE_BLOCK;
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
@ -1320,11 +1393,12 @@ void IndexRangeBlock::readPrimaryIndex (IndexOrCondition const& ranges) {
|
|||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
void IndexRangeBlock::readHashIndex (IndexOrCondition const& ranges) {
|
||||
ENTER_BLOCK;
|
||||
auto en = static_cast<IndexRangeNode const*>(getPlanNode());
|
||||
TRI_index_t* idx = en->_index->data;
|
||||
TRI_ASSERT(idx != nullptr);
|
||||
TRI_hash_index_t* hashIndex = (TRI_hash_index_t*) idx;
|
||||
|
||||
|
||||
TRI_shaper_t* shaper = _collection->documentCollection()->getShaper();
|
||||
TRI_ASSERT(shaper != nullptr);
|
||||
|
||||
|
@ -1352,7 +1426,7 @@ void IndexRangeBlock::readHashIndex (IndexOrCondition const& ranges) {
|
|||
|
||||
searchValue._length = n;
|
||||
|
||||
for (size_t i = 0; i < n; ++i) {
|
||||
for (size_t i = 0; i < n; ++i) {
|
||||
TRI_shape_pid_t pid = *(static_cast<TRI_shape_pid_t*>(TRI_AtVector(&hashIndex->_paths, i)));
|
||||
TRI_ASSERT(pid != 0);
|
||||
|
||||
|
@ -1364,21 +1438,20 @@ void IndexRangeBlock::readHashIndex (IndexOrCondition const& ranges) {
|
|||
// here x->_low->_bound = x->_high->_bound
|
||||
searchValue._values[i] = *shaped;
|
||||
TRI_Free(shaper->_memoryZone, shaped);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
};
|
||||
|
||||
setupSearchValue();
|
||||
TRI_index_result_t list = TRI_LookupHashIndex(idx, &searchValue);
|
||||
|
||||
destroySearchValue();
|
||||
|
||||
size_t const n = list._length;
|
||||
try {
|
||||
for (size_t i = 0; i < n; ++i) {
|
||||
_documents.push_back(*(list._documents[i]));
|
||||
_documents.emplace_back(*(list._documents[i]));
|
||||
}
|
||||
|
||||
_engine->_stats.scannedIndex += static_cast<int64_t>(n);
|
||||
|
@ -1388,6 +1461,7 @@ void IndexRangeBlock::readHashIndex (IndexOrCondition const& ranges) {
|
|||
TRI_DestroyIndexResult(&list);
|
||||
throw;
|
||||
}
|
||||
LEAVE_BLOCK;
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
@ -1395,6 +1469,7 @@ void IndexRangeBlock::readHashIndex (IndexOrCondition const& ranges) {
|
|||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
void IndexRangeBlock::readEdgeIndex (IndexOrCondition const& ranges) {
|
||||
ENTER_BLOCK;
|
||||
TRI_document_collection_t* document = _collection->documentCollection();
|
||||
|
||||
std::string key;
|
||||
|
@ -1435,12 +1510,13 @@ void IndexRangeBlock::readEdgeIndex (IndexOrCondition const& ranges) {
|
|||
// silently ignore all errors due to wrong _from / _to specifications
|
||||
auto&& result = TRI_LookupEdgesDocumentCollection(document, direction, documentCid, (TRI_voc_key_t) documentKey.c_str());
|
||||
for (auto it : result) {
|
||||
_documents.push_back((it));
|
||||
_documents.emplace_back(it);
|
||||
}
|
||||
|
||||
_engine->_stats.scannedIndex += static_cast<int64_t>(result.size());
|
||||
}
|
||||
}
|
||||
LEAVE_BLOCK;
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
@ -1479,14 +1555,17 @@ void IndexRangeBlock::readEdgeIndex (IndexOrCondition const& ranges) {
|
|||
// (i.e. the 1 in x.c >= 1) cannot be lists or arrays.
|
||||
//
|
||||
|
||||
void IndexRangeBlock::readSkiplistIndex (IndexOrCondition const& ranges) {
|
||||
void IndexRangeBlock::initSkiplistIndex (IndexOrCondition const& ranges) {
|
||||
ENTER_BLOCK;
|
||||
TRI_ASSERT(_skiplistIterator == nullptr);
|
||||
|
||||
auto en = static_cast<IndexRangeNode const*>(getPlanNode());
|
||||
TRI_index_t* idx = en->_index->data;
|
||||
TRI_ASSERT(idx != nullptr);
|
||||
|
||||
|
||||
TRI_shaper_t* shaper = _collection->documentCollection()->getShaper();
|
||||
TRI_ASSERT(shaper != nullptr);
|
||||
|
||||
|
||||
TRI_index_operator_t* skiplistOperator = nullptr;
|
||||
|
||||
Json parameters(Json::List);
|
||||
|
@ -1542,12 +1621,12 @@ void IndexRangeBlock::readSkiplistIndex (IndexOrCondition const& ranges) {
|
|||
}
|
||||
}
|
||||
|
||||
TRI_skiplist_iterator_t* skiplistIterator = TRI_LookupSkiplistIndex(idx, skiplistOperator, en->_reverse);
|
||||
_skiplistIterator = TRI_LookupSkiplistIndex(idx, skiplistOperator, en->_reverse);
|
||||
if (skiplistOperator != nullptr) {
|
||||
TRI_FreeIndexOperator(skiplistOperator);
|
||||
}
|
||||
|
||||
if (skiplistIterator == nullptr) {
|
||||
|
||||
if (_skiplistIterator == nullptr) {
|
||||
int res = TRI_errno();
|
||||
if (res == TRI_RESULT_ELEMENT_NOT_FOUND) {
|
||||
return;
|
||||
|
@ -1555,23 +1634,38 @@ void IndexRangeBlock::readSkiplistIndex (IndexOrCondition const& ranges) {
|
|||
|
||||
THROW_ARANGO_EXCEPTION(TRI_ERROR_ARANGO_NO_INDEX);
|
||||
}
|
||||
LEAVE_BLOCK;
|
||||
}
|
||||
|
||||
void IndexRangeBlock::readSkiplistIndex (size_t atMost) {
|
||||
ENTER_BLOCK;
|
||||
if (_skiplistIterator == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
while (true) {
|
||||
TRI_skiplist_index_element_t* indexElement = skiplistIterator->next(skiplistIterator);
|
||||
size_t nrSent = 0;
|
||||
TRI_skiplist_index_element_t* indexElement;
|
||||
while (nrSent < atMost) {
|
||||
indexElement = _skiplistIterator->next(_skiplistIterator);
|
||||
|
||||
if (indexElement == nullptr) {
|
||||
break;
|
||||
}
|
||||
_documents.push_back(*(indexElement->_document));
|
||||
_documents.emplace_back(*(indexElement->_document));
|
||||
++nrSent;
|
||||
++_engine->_stats.scannedIndex;
|
||||
}
|
||||
TRI_FreeSkiplistIterator(skiplistIterator);
|
||||
if (indexElement == nullptr) {
|
||||
TRI_FreeSkiplistIterator(_skiplistIterator);
|
||||
_skiplistIterator = nullptr;
|
||||
}
|
||||
}
|
||||
catch (...) {
|
||||
TRI_FreeSkiplistIterator(skiplistIterator);
|
||||
TRI_FreeSkiplistIterator(_skiplistIterator);
|
||||
throw;
|
||||
}
|
||||
LEAVE_BLOCK;
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
|
@ -1584,7 +1678,7 @@ EnumerateListBlock::EnumerateListBlock (ExecutionEngine* engine,
|
|||
_inVarRegId(ExecutionNode::MaxRegisterId) {
|
||||
|
||||
auto it = en->getRegisterPlan()->varInfo.find(en->_inVariable->id);
|
||||
if (it == en->getRegisterPlan()->varInfo.end()){
|
||||
if (it == en->getRegisterPlan()->varInfo.end()) {
|
||||
THROW_ARANGO_EXCEPTION_MESSAGE(TRI_ERROR_INTERNAL, "variable not found");
|
||||
}
|
||||
_inVarRegId = (*it).second.registerId;
|
||||
|
@ -1729,7 +1823,7 @@ AqlItemBlock* EnumerateListBlock::getSome (size_t, size_t atMost) {
|
|||
_thisblock = 0;
|
||||
_seen = 0;
|
||||
// advance read position in the current block . . .
|
||||
if (++_pos == cur->size() ) {
|
||||
if (++_pos == cur->size()) {
|
||||
delete cur;
|
||||
_buffer.pop_front(); // does not throw
|
||||
_pos = 0;
|
||||
|
@ -1751,7 +1845,7 @@ size_t EnumerateListBlock::skipSome (size_t atLeast, size_t atMost) {
|
|||
|
||||
size_t skipped = 0;
|
||||
|
||||
while ( skipped < atLeast ) {
|
||||
while (skipped < atLeast) {
|
||||
if (_buffer.empty()) {
|
||||
if (! ExecutionBlock::getBlock(DefaultBatchSize, DefaultBatchSize)) {
|
||||
_done = true;
|
||||
|
@ -1783,7 +1877,7 @@ size_t EnumerateListBlock::skipSome (size_t atLeast, size_t atMost) {
|
|||
}
|
||||
|
||||
case AqlValue::DOCVEC: {
|
||||
if( _index == 0) { // this is a (maybe) new DOCVEC
|
||||
if (_index == 0) { // this is a (maybe) new DOCVEC
|
||||
_DOCVECsize = 0;
|
||||
//we require the total number of items
|
||||
for (size_t i = 0; i < inVarReg._vector->size(); i++) {
|
||||
|
@ -1837,7 +1931,7 @@ AqlValue EnumerateListBlock::getAqlValue (AqlValue inVarReg) {
|
|||
case AqlValue::DOCVEC: { // incoming doc vec has a single column
|
||||
AqlValue out = inVarReg._vector->at(_thisblock)->getValue(_index -
|
||||
_seen, 0).clone();
|
||||
if(++_index == (inVarReg._vector->at(_thisblock)->size() + _seen)){
|
||||
if (++_index == (inVarReg._vector->at(_thisblock)->size() + _seen)) {
|
||||
_seen += inVarReg._vector->at(_thisblock)->size();
|
||||
_thisblock++;
|
||||
}
|
||||
|
@ -1979,7 +2073,7 @@ AqlItemBlock* CalculationBlock::getSome (size_t atLeast,
|
|||
size_t atMost) {
|
||||
|
||||
unique_ptr<AqlItemBlock> res(ExecutionBlock::getSomeWithoutRegisterClearout(
|
||||
atLeast, atMost));
|
||||
DefaultBatchSize, DefaultBatchSize));
|
||||
|
||||
if (res.get() == nullptr) {
|
||||
return nullptr;
|
||||
|
@ -2227,7 +2321,7 @@ int FilterBlock::getOrSkipSome (size_t atLeast,
|
|||
try {
|
||||
result = AqlItemBlock::concatenate(collector);
|
||||
}
|
||||
catch (...){
|
||||
catch (...) {
|
||||
for (auto x : collector) {
|
||||
delete x;
|
||||
}
|
||||
|
@ -2396,7 +2490,7 @@ int AggregateBlock::getOrSkipSome (size_t atLeast,
|
|||
|
||||
if (newGroup) {
|
||||
if (! _currentGroup.groupValues[0].isEmpty()) {
|
||||
if(! skipping){
|
||||
if (! skipping) {
|
||||
// need to emit the current group first
|
||||
emitGroup(cur, res.get(), skipped);
|
||||
}
|
||||
|
@ -2443,7 +2537,7 @@ int AggregateBlock::getOrSkipSome (size_t atLeast,
|
|||
// no more input. we're done
|
||||
try {
|
||||
// emit last buffered group
|
||||
if(! skipping){
|
||||
if (! skipping) {
|
||||
emitGroup(cur, res.get(), skipped);
|
||||
++skipped;
|
||||
TRI_ASSERT(skipped > 0);
|
||||
|
@ -2474,7 +2568,7 @@ int AggregateBlock::getOrSkipSome (size_t atLeast,
|
|||
}
|
||||
}
|
||||
|
||||
if(! skipping){
|
||||
if (! skipping) {
|
||||
TRI_ASSERT(skipped > 0);
|
||||
res->shrink(skipped);
|
||||
}
|
||||
|
@ -3517,7 +3611,7 @@ int GatherBlock::initializeCursor (AqlItemBlock* items, size_t pos) {
|
|||
return res;
|
||||
}
|
||||
|
||||
if (!_isSimple) {
|
||||
if (! _isSimple) {
|
||||
for (std::deque<AqlItemBlock*>& x : _gatherBlockBuffer) {
|
||||
for (AqlItemBlock* y: x) {
|
||||
delete y;
|
||||
|
@ -3589,13 +3683,13 @@ bool GatherBlock::hasMore () {
|
|||
|
||||
if (_isSimple) {
|
||||
for (size_t i = 0; i < _dependencies.size(); i++) {
|
||||
if(_dependencies.at(i)->hasMore()) {
|
||||
if (_dependencies.at(i)->hasMore()) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
for (size_t i = 0; i < _gatherBlockBuffer.size(); i++){
|
||||
for (size_t i = 0; i < _gatherBlockBuffer.size(); i++) {
|
||||
if (! _gatherBlockBuffer.at(i).empty()) {
|
||||
return true;
|
||||
}
|
||||
|
@ -3893,7 +3987,7 @@ BlockWithClients::BlockWithClients (ExecutionEngine* engine,
|
|||
: ExecutionBlock(engine, ep),
|
||||
_nrClients(shardIds.size()),
|
||||
_ignoreInitCursor(false),
|
||||
_ignoreShutdown(false){
|
||||
_ignoreShutdown(false) {
|
||||
|
||||
_shardIdMap.reserve(_nrClients);
|
||||
for (size_t i = 0; i < _nrClients; i++) {
|
||||
|
@ -4170,7 +4264,7 @@ int ScatterBlock::getOrSkipSomeForShard (size_t atLeast,
|
|||
|
||||
skipped = (std::min)(available, atMost); //nr rows in outgoing block
|
||||
|
||||
if (! skipping){
|
||||
if (! skipping) {
|
||||
result = _buffer.at(pos.first)->slice(pos.second, pos.second + skipped);
|
||||
}
|
||||
|
||||
|
@ -4343,7 +4437,7 @@ int DistributeBlock::getOrSkipSomeForShard (size_t atLeast,
|
|||
skipped = (std::min)(buf.size(), atMost);
|
||||
|
||||
if (skipping) {
|
||||
for (size_t i = 0; i < skipped; i++){
|
||||
for (size_t i = 0; i < skipped; i++) {
|
||||
buf.pop_front();
|
||||
}
|
||||
freeCollector();
|
||||
|
|
|
@ -562,11 +562,24 @@ namespace triagens {
|
|||
|
||||
private:
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
/// @brief free _condition if it belongs to us
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
void freeCondition ();
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
/// @brief continue fetching of documents
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
bool readIndex ();
|
||||
bool readIndex (size_t atMost);
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
/// @brief set up the index for reading. This should be called once per incoming
|
||||
/// block.
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
bool initIndex ();
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
/// @brief read using the primary index
|
||||
|
@ -584,7 +597,13 @@ namespace triagens {
|
|||
/// @brief read using a skiplist index
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
void readSkiplistIndex (IndexOrCondition const&);
|
||||
void readSkiplistIndex (size_t atMost);
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
/// @brief this tries to create a skiplistIterator to read from the index.
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
void initSkiplistIndex (IndexOrCondition const&);
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
/// @brief read using a hash index
|
||||
|
@ -633,7 +652,7 @@ namespace triagens {
|
|||
////////////////////////////////////////////////////////////////////////////////
|
||||
/// @brief _inVars, a vector containing for each expression above
|
||||
/// a vector of Variable*, used to execute the expression
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
/////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
std::vector<std::vector<Variable*>> _inVars;
|
||||
|
||||
|
@ -644,6 +663,39 @@ namespace triagens {
|
|||
|
||||
std::vector<std::vector<RegisterId>> _inRegs;
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
/// @brief _skiplistIterator: holds the skiplist iterator found using
|
||||
/// initSkiplistIndex (if any) so that it can be read in chunks and not
|
||||
/// necessarily all at once.
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
TRI_skiplist_iterator_t* _skiplistIterator;
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
/// @brief _condition: holds the IndexOrCondition for the current incoming block,
|
||||
/// this is just the _ranges member of the plan node if _allBoundsConstant
|
||||
/// otherwise it is reevaluated every time initIndex is called, i.e. once per
|
||||
/// incoming block.
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
IndexOrCondition const* _condition;
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
/// @brief _flag: since readIndex for primary, hash, edges indexes reads the
|
||||
/// whole index, this is <true> if initIndex has been called but readIndex has
|
||||
/// not been called, otherwise it is <false> to avoid rereading the entire index
|
||||
/// with successive calls to readIndex.
|
||||
//////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
bool _flag;
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
/// @brief _freeCondition: whether or not the _condition is owned by the
|
||||
/// IndexRangeBlock and must be freed
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
bool _freeCondition;
|
||||
|
||||
};
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
|
|
|
@ -459,11 +459,17 @@ void Optimizer::setupRules () {
|
|||
//////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
// try to replace simple OR conditions with IN
|
||||
registerRule("replace-OR-with-IN",
|
||||
registerRule("replace-or-with-in",
|
||||
replaceOrWithIn,
|
||||
replaceOrWithIn_pass6,
|
||||
true);
|
||||
|
||||
// try to remove redundant OR conditions
|
||||
registerRule("remove-redundant-or",
|
||||
removeRedundantOr,
|
||||
removeRedundantOr_pass6,
|
||||
true);
|
||||
|
||||
// try to find a filter after an enumerate collection and find an index . . .
|
||||
registerRule("use-index-range",
|
||||
useIndexRange,
|
||||
|
@ -475,10 +481,10 @@ void Optimizer::setupRules () {
|
|||
useIndexForSort,
|
||||
useIndexForSort_pass6,
|
||||
true);
|
||||
|
||||
#if 0
|
||||
|
||||
#if 0
|
||||
// try to remove filters which are covered by index ranges
|
||||
// rule seems to work, but tests are still missing
|
||||
// TODO: rule seems to work, but tests are still missing
|
||||
registerRule("remove-filter-covered-by-index",
|
||||
removeFiltersCoveredByIndex,
|
||||
removeFiltersCoveredByIndex_pass6,
|
||||
|
|
|
@ -134,14 +134,17 @@ namespace triagens {
|
|||
// replace simple OR conditions with IN
|
||||
replaceOrWithIn_pass6 = 810,
|
||||
|
||||
// remove redundant OR conditions
|
||||
removeRedundantOr_pass6 = 820,
|
||||
|
||||
// try to find a filter after an enumerate collection and find an index . . .
|
||||
useIndexRange_pass6 = 820,
|
||||
useIndexRange_pass6 = 830,
|
||||
|
||||
// try to find sort blocks which are superseeded by indexes
|
||||
useIndexForSort_pass6 = 830,
|
||||
useIndexForSort_pass6 = 840,
|
||||
|
||||
// try to remove filters covered by index ranges
|
||||
removeFiltersCoveredByIndex_pass6 = 840,
|
||||
removeFiltersCoveredByIndex_pass6 = 850,
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////
|
||||
/// "Pass 10": final transformations for the cluster
|
||||
|
|
|
@ -1556,9 +1556,7 @@ int triagens::aql::useIndexForSort (Optimizer* opt,
|
|||
return TRI_ERROR_NO_ERROR;
|
||||
}
|
||||
|
||||
#if 0
|
||||
// TODO: finish rule and test it
|
||||
|
||||
struct FilterCondition {
|
||||
std::string variableName;
|
||||
std::string attributeName;
|
||||
|
@ -1640,19 +1638,17 @@ struct FilterCondition {
|
|||
lhs = node->getMember(1);
|
||||
rhs = node->getMember(0);
|
||||
|
||||
auto it = Ast::ReverseOperators.find(static_cast<int>(node->type));
|
||||
TRI_ASSERT(it != Ast::ReverseOperators.end());
|
||||
|
||||
op = (*it).second;
|
||||
op = Ast::ReverseOperator(node->type);
|
||||
}
|
||||
|
||||
if (found) {
|
||||
TRI_ASSERT(lhs->type == NODE_TYPE_VALUE);
|
||||
TRI_ASSERT(rhs->type == NODE_TYPE_ATTRIBUTE_ACCESS);
|
||||
|
||||
std::function<void(AstNode const*)> buildName = [&] (AstNode const* node) -> void {
|
||||
std::function<void(AstNode const*, std::string&, std::string&)> buildName =
|
||||
[&] (AstNode const* node, std::string& variableName, std::string& attributeName) -> void {
|
||||
if (node->type == NODE_TYPE_ATTRIBUTE_ACCESS) {
|
||||
buildName(node->getMember(0));
|
||||
buildName(node->getMember(0), variableName, attributeName);
|
||||
|
||||
if (! attributeName.empty()) {
|
||||
attributeName.push_back('.');
|
||||
|
@ -1667,7 +1663,9 @@ struct FilterCondition {
|
|||
};
|
||||
|
||||
if (attributeName.empty()) {
|
||||
buildName(rhs);
|
||||
TRI_ASSERT(! variableName.empty());
|
||||
|
||||
buildName(rhs, variableName, attributeName);
|
||||
if (op == NODE_TYPE_OPERATOR_BINARY_EQ ||
|
||||
op == NODE_TYPE_OPERATOR_BINARY_NE) {
|
||||
lowInclusive = true;
|
||||
|
@ -1694,10 +1692,19 @@ struct FilterCondition {
|
|||
|
||||
return true;
|
||||
}
|
||||
// else if (attributeName == std::string(buffer.c_str(), buffer.length())) {
|
||||
// same attribute
|
||||
// TODO
|
||||
// }
|
||||
else {
|
||||
// already have collected something, now check if the next condition
|
||||
// is for the same variable / attribute
|
||||
std::string compareVariableName;
|
||||
std::string compareAttributeName;
|
||||
buildName(rhs, compareVariableName, compareAttributeName);
|
||||
|
||||
if (variableName == compareVariableName &&
|
||||
attributeName == compareAttributeName) {
|
||||
// same attribute
|
||||
// TODO
|
||||
}
|
||||
}
|
||||
|
||||
// fall-through
|
||||
}
|
||||
|
@ -1717,7 +1724,6 @@ struct FilterCondition {
|
|||
|
||||
};
|
||||
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
/// @brief try to remove filters which are covered by indexes
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
@ -1736,7 +1742,9 @@ int triagens::aql::removeFiltersCoveredByIndex (Optimizer* opt,
|
|||
// auto outVar = cn->getVariablesSetHere();
|
||||
|
||||
auto setter = plan->getVarSetBy(inVar[0]->id);
|
||||
TRI_ASSERT(setter != nullptr);
|
||||
if (setter == nullptr) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (setter->getType() != EN::CALCULATION) {
|
||||
continue;
|
||||
|
@ -1753,9 +1761,6 @@ int triagens::aql::removeFiltersCoveredByIndex (Optimizer* opt,
|
|||
while (current != nullptr) {
|
||||
if (current->getType() == EN::INDEX_RANGE) {
|
||||
// found an index range, now check if the expression is covered by the index
|
||||
auto variable = static_cast<IndexRangeNode const*>(current)->outVariable();
|
||||
TRI_ASSERT(variable != nullptr);
|
||||
|
||||
auto const& ranges = static_cast<IndexRangeNode const*>(current)->ranges();
|
||||
|
||||
// TODO: this is not prepared for OR conditions
|
||||
|
@ -1796,7 +1801,6 @@ int triagens::aql::removeFiltersCoveredByIndex (Optimizer* opt,
|
|||
|
||||
return TRI_ERROR_NO_ERROR;
|
||||
}
|
||||
#endif
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
/// @brief helper to compute lots of permutation tuples
|
||||
|
@ -2594,22 +2598,121 @@ int triagens::aql::undistributeRemoveAfterEnumColl (Optimizer* opt,
|
|||
return TRI_ERROR_NO_ERROR;
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
/// @brief auxilliary struct for finding common nodes in OR conditions
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
struct CommonNodeFinder {
|
||||
std::vector<AstNode const*> possibleNodes;
|
||||
|
||||
bool find (AstNode const* node,
|
||||
AstNodeType condition,
|
||||
AstNode const*& commonNode,
|
||||
std::string& commonName ) {
|
||||
|
||||
if (node->type == NODE_TYPE_OPERATOR_BINARY_OR) {
|
||||
return (find(node->getMember(0), condition, commonNode, commonName)
|
||||
&& find(node->getMember(1), condition, commonNode, commonName));
|
||||
}
|
||||
|
||||
if (node->type == NODE_TYPE_VALUE) {
|
||||
possibleNodes.clear();
|
||||
return true;
|
||||
}
|
||||
|
||||
if (node->type == condition
|
||||
|| (condition != NODE_TYPE_OPERATOR_BINARY_EQ
|
||||
&& ( node->type == NODE_TYPE_OPERATOR_BINARY_LE
|
||||
|| node->type == NODE_TYPE_OPERATOR_BINARY_LT
|
||||
|| node->type == NODE_TYPE_OPERATOR_BINARY_GE
|
||||
|| node->type == NODE_TYPE_OPERATOR_BINARY_GT ))) {
|
||||
|
||||
auto lhs = node->getMember(0);
|
||||
auto rhs = node->getMember(1);
|
||||
|
||||
if (lhs->isConstant()) {
|
||||
commonNode = rhs;
|
||||
commonName = commonNode->toString();
|
||||
possibleNodes.clear();
|
||||
return true;
|
||||
}
|
||||
|
||||
if (rhs->isConstant()) {
|
||||
commonNode = lhs;
|
||||
commonName = commonNode->toString();
|
||||
possibleNodes.clear();
|
||||
return true;
|
||||
}
|
||||
|
||||
if (rhs->type == NODE_TYPE_FCALL ||
|
||||
rhs->type == NODE_TYPE_FCALL_USER ||
|
||||
rhs->type == NODE_TYPE_REFERENCE) {
|
||||
commonNode = lhs;
|
||||
commonName = commonNode->toString();
|
||||
possibleNodes.clear();
|
||||
return true;
|
||||
}
|
||||
|
||||
if (lhs->type == NODE_TYPE_FCALL ||
|
||||
lhs->type == NODE_TYPE_FCALL_USER ||
|
||||
lhs->type == NODE_TYPE_REFERENCE) {
|
||||
commonNode = rhs;
|
||||
commonName = commonNode->toString();
|
||||
possibleNodes.clear();
|
||||
return true;
|
||||
}
|
||||
|
||||
if (lhs->type == NODE_TYPE_ATTRIBUTE_ACCESS ||
|
||||
lhs->type == NODE_TYPE_INDEXED_ACCESS) {
|
||||
if (possibleNodes.size() == 2) {
|
||||
for (size_t i = 0; i < 2; i++) {
|
||||
if (lhs->toString() == possibleNodes[i]->toString()) {
|
||||
commonNode = possibleNodes[i];
|
||||
commonName = commonNode->toString();
|
||||
possibleNodes.clear();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
// don't return, must consider the other side of the condition
|
||||
}
|
||||
else {
|
||||
possibleNodes.push_back(lhs);
|
||||
}
|
||||
}
|
||||
if (rhs->type == NODE_TYPE_ATTRIBUTE_ACCESS ||
|
||||
rhs->type == NODE_TYPE_INDEXED_ACCESS) {
|
||||
if (possibleNodes.size() == 2) {
|
||||
for (size_t i = 0; i < 2; i++) {
|
||||
if (rhs->toString() == possibleNodes[i]->toString()) {
|
||||
commonNode = possibleNodes[i];
|
||||
commonName = commonNode->toString();
|
||||
possibleNodes.clear();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
else {
|
||||
possibleNodes.push_back(rhs);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
possibleNodes.clear();
|
||||
return (! commonName.empty());
|
||||
}
|
||||
};
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
/// @brief auxilliary struct for the OR-to-IN conversion
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
struct OrToInConverter {
|
||||
AstNode const* variableNode;
|
||||
std::string variableName;
|
||||
std::vector<AstNode const*> valueNodes;
|
||||
std::vector<AstNode const*> possibleNodes;
|
||||
std::vector<AstNode const*> orConditions;
|
||||
|
||||
std::string getString (AstNode const* node) {
|
||||
triagens::basics::StringBuffer buffer(TRI_UNKNOWN_MEM_ZONE);
|
||||
node->stringify(&buffer, false);
|
||||
return std::string(buffer.c_str(), buffer.length());
|
||||
}
|
||||
std::vector<AstNode const*> valueNodes;
|
||||
CommonNodeFinder finder;
|
||||
AstNode const* commonNode = nullptr;
|
||||
std::string commonName;
|
||||
|
||||
AstNode* buildInExpression (Ast* ast) {
|
||||
// the list of comparison values
|
||||
|
@ -2620,115 +2723,37 @@ struct OrToInConverter {
|
|||
|
||||
// return a new IN operator node
|
||||
return ast->createNodeBinaryOperator(NODE_TYPE_OPERATOR_BINARY_IN,
|
||||
variableNode->clone(ast),
|
||||
commonNode->clone(ast),
|
||||
list);
|
||||
}
|
||||
|
||||
bool flattenOr (AstNode const* node) {
|
||||
if (node->type == NODE_TYPE_OPERATOR_BINARY_OR) {
|
||||
return (flattenOr(node->getMember(0)) && flattenOr(node->getMember(1)));
|
||||
}
|
||||
if (node->type == NODE_TYPE_OPERATOR_BINARY_EQ) {
|
||||
orConditions.push_back(node);
|
||||
return true;
|
||||
}
|
||||
if (node->type == NODE_TYPE_VALUE) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool findCommonNode (AstNode const* node) {
|
||||
|
||||
if (! flattenOr(node)) {
|
||||
return false;
|
||||
}
|
||||
TRI_ASSERT(orConditions.size() > 1);
|
||||
|
||||
for (AstNode const* n: orConditions) {
|
||||
|
||||
auto lhs = n->getMember(0);
|
||||
auto rhs = n->getMember(1);
|
||||
|
||||
if (lhs->isConstant()) {
|
||||
variableNode = rhs;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (rhs->isConstant()) {
|
||||
variableNode = lhs;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (rhs->type == NODE_TYPE_FCALL ||
|
||||
rhs->type == NODE_TYPE_FCALL_USER ||
|
||||
rhs->type == NODE_TYPE_REFERENCE) {
|
||||
variableNode = lhs;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (lhs->type == NODE_TYPE_FCALL ||
|
||||
lhs->type == NODE_TYPE_FCALL_USER ||
|
||||
lhs->type == NODE_TYPE_REFERENCE) {
|
||||
variableNode = rhs;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (lhs->type == NODE_TYPE_ATTRIBUTE_ACCESS ||
|
||||
lhs->type == NODE_TYPE_INDEXED_ACCESS) {
|
||||
if (possibleNodes.size() == 2) {
|
||||
for (size_t i = 0; i < 2; i++) {
|
||||
if (getString(lhs) == getString(possibleNodes[i])) {
|
||||
variableNode = possibleNodes[i];
|
||||
variableName = getString(variableNode);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
possibleNodes.push_back(lhs);
|
||||
}
|
||||
}
|
||||
if (rhs->type == NODE_TYPE_ATTRIBUTE_ACCESS ||
|
||||
rhs->type == NODE_TYPE_INDEXED_ACCESS) {
|
||||
if (possibleNodes.size() == 2) {
|
||||
for (size_t i = 0; i < 2; i++) {
|
||||
if (getString(rhs) == getString(possibleNodes[i])) {
|
||||
variableNode = possibleNodes[i];
|
||||
variableName = getString(variableNode);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
else {
|
||||
possibleNodes.push_back(rhs);
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool canConvertExpression (AstNode const* node) {
|
||||
if(finder.find(node, NODE_TYPE_OPERATOR_BINARY_EQ, commonNode, commonName)){
|
||||
return canConvertExpressionWalker(node);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool canConvertExpressionWalker (AstNode const* node) {
|
||||
if (node->type == NODE_TYPE_OPERATOR_BINARY_OR) {
|
||||
return (canConvertExpression(node->getMember(0)) &&
|
||||
canConvertExpression(node->getMember(1)));
|
||||
return (canConvertExpressionWalker(node->getMember(0)) &&
|
||||
canConvertExpressionWalker(node->getMember(1)));
|
||||
}
|
||||
|
||||
if (node->type == NODE_TYPE_OPERATOR_BINARY_EQ) {
|
||||
auto lhs = node->getMember(0);
|
||||
auto rhs = node->getMember(1);
|
||||
|
||||
if (canConvertExpression(rhs) && ! canConvertExpression(lhs)) {
|
||||
if (canConvertExpressionWalker(rhs) && ! canConvertExpressionWalker(lhs)) {
|
||||
valueNodes.push_back(lhs);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (canConvertExpression(lhs) && ! canConvertExpression(rhs)) {
|
||||
if (canConvertExpressionWalker(lhs) && ! canConvertExpressionWalker(rhs)) {
|
||||
valueNodes.push_back(rhs);
|
||||
return true;
|
||||
}
|
||||
// if canConvertExpression(lhs) and canConvertExpression(rhs), then one of
|
||||
// if canConvertExpressionWalker(lhs) and canConvertExpressionWalker(rhs), then one of
|
||||
// the equalities in the OR statement is of the form x == x
|
||||
// fall-through intentional
|
||||
}
|
||||
|
@ -2736,8 +2761,7 @@ struct OrToInConverter {
|
|||
node->type == NODE_TYPE_ATTRIBUTE_ACCESS ||
|
||||
node->type == NODE_TYPE_INDEXED_ACCESS) {
|
||||
// get a string representation of the node for comparisons
|
||||
std::string nodeString = getString(node);
|
||||
return nodeString == getString(variableNode);
|
||||
return (node->toString() == commonName);
|
||||
} else if (node->isBoolValue()) {
|
||||
return true;
|
||||
}
|
||||
|
@ -2784,8 +2808,7 @@ int triagens::aql::replaceOrWithIn (Optimizer* opt,
|
|||
}
|
||||
|
||||
OrToInConverter converter;
|
||||
if (converter.findCommonNode(cn->expression()->node())
|
||||
&& converter.canConvertExpression(cn->expression()->node())) {
|
||||
if (converter.canConvertExpression(cn->expression()->node())) {
|
||||
Expression* expr = nullptr;
|
||||
ExecutionNode* newNode = nullptr;
|
||||
auto inNode = converter.buildInExpression(plan->getAst());
|
||||
|
@ -2821,6 +2844,199 @@ int triagens::aql::replaceOrWithIn (Optimizer* opt,
|
|||
LEAVE_BLOCK;
|
||||
}
|
||||
|
||||
struct RemoveRedundantOr {
|
||||
AstNode const* bestValue = nullptr;
|
||||
AstNodeType comparison;
|
||||
bool inclusive;
|
||||
bool isComparisonSet = false;
|
||||
CommonNodeFinder finder;
|
||||
AstNode const* commonNode = nullptr;
|
||||
std::string commonName;
|
||||
|
||||
AstNode* createReplacementNode (Ast* ast) {
|
||||
TRI_ASSERT(commonNode != nullptr);
|
||||
TRI_ASSERT(bestValue != nullptr);
|
||||
TRI_ASSERT(isComparisonSet == true);
|
||||
return ast->createNodeBinaryOperator(comparison, commonNode->clone(ast),
|
||||
bestValue);
|
||||
}
|
||||
|
||||
bool isInclusiveBound (AstNodeType type) {
|
||||
return (type == NODE_TYPE_OPERATOR_BINARY_GE || type == NODE_TYPE_OPERATOR_BINARY_LE);
|
||||
}
|
||||
|
||||
int isCompatibleBound (AstNodeType type, AstNode const* value) {
|
||||
|
||||
if ((comparison == NODE_TYPE_OPERATOR_BINARY_LE
|
||||
|| comparison == NODE_TYPE_OPERATOR_BINARY_LT) &&
|
||||
(type == NODE_TYPE_OPERATOR_BINARY_LE
|
||||
|| type == NODE_TYPE_OPERATOR_BINARY_LT)) {
|
||||
return -1; //high bound
|
||||
}
|
||||
else if ((comparison == NODE_TYPE_OPERATOR_BINARY_GE
|
||||
|| comparison == NODE_TYPE_OPERATOR_BINARY_GT) &&
|
||||
(type == NODE_TYPE_OPERATOR_BINARY_GE
|
||||
|| type == NODE_TYPE_OPERATOR_BINARY_GT)) {
|
||||
return 1; //low bound
|
||||
}
|
||||
return 0; //incompatible bounds
|
||||
}
|
||||
|
||||
// returns false if the existing value is better and true if the input value is
|
||||
// better
|
||||
bool compareBounds(AstNodeType type, AstNode const* value, int lowhigh) {
|
||||
|
||||
int cmp = CompareAstNodes(bestValue, value);
|
||||
|
||||
if (cmp == 0 && (isInclusiveBound(comparison) != isInclusiveBound(type))) {
|
||||
return (isInclusiveBound(type) ? true : false);
|
||||
}
|
||||
return (cmp * lowhigh == 1);
|
||||
}
|
||||
|
||||
bool hasRedundantCondition (AstNode const* node) {
|
||||
if (finder.find(node, NODE_TYPE_OPERATOR_BINARY_LT, commonNode, commonName)) {
|
||||
return hasRedundantConditionWalker(node);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool hasRedundantConditionWalker (AstNode const* node) {
|
||||
AstNodeType type = node->type;
|
||||
|
||||
if (type == NODE_TYPE_OPERATOR_BINARY_OR) {
|
||||
return (hasRedundantConditionWalker(node->getMember(0)) &&
|
||||
hasRedundantConditionWalker(node->getMember(1)));
|
||||
}
|
||||
|
||||
if (type == NODE_TYPE_OPERATOR_BINARY_LE
|
||||
|| type == NODE_TYPE_OPERATOR_BINARY_LT
|
||||
|| type == NODE_TYPE_OPERATOR_BINARY_GE
|
||||
|| type == NODE_TYPE_OPERATOR_BINARY_GT) {
|
||||
|
||||
auto lhs = node->getMember(0);
|
||||
auto rhs = node->getMember(1);
|
||||
|
||||
if (hasRedundantConditionWalker(rhs)
|
||||
&& ! hasRedundantConditionWalker(lhs)
|
||||
&& lhs->isConstant()) {
|
||||
|
||||
if (! isComparisonSet) {
|
||||
comparison = Ast::ReverseOperator(type);
|
||||
bestValue = lhs;
|
||||
isComparisonSet = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
int lowhigh = isCompatibleBound(Ast::ReverseOperator(type), lhs);
|
||||
if (lowhigh == 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (compareBounds(type, lhs, lowhigh)) {
|
||||
comparison = Ast::ReverseOperator(type);
|
||||
bestValue = lhs;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
if (hasRedundantConditionWalker(lhs)
|
||||
&& ! hasRedundantConditionWalker(rhs)
|
||||
&& rhs->isConstant()) {
|
||||
if (! isComparisonSet) {
|
||||
comparison = type;
|
||||
bestValue = rhs;
|
||||
isComparisonSet = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
int lowhigh = isCompatibleBound(type, rhs);
|
||||
if (lowhigh == 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (compareBounds(type, rhs, lowhigh)) {
|
||||
comparison = type;
|
||||
bestValue = rhs;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
// if hasRedundantConditionWalker(lhs) and
|
||||
// hasRedundantConditionWalker(rhs), then one of the conditions in the OR
|
||||
// statement is of the form x == x fall-through intentional
|
||||
}
|
||||
else if (type == NODE_TYPE_REFERENCE ||
|
||||
type == NODE_TYPE_ATTRIBUTE_ACCESS ||
|
||||
type == NODE_TYPE_INDEXED_ACCESS) {
|
||||
// get a string representation of the node for comparisons
|
||||
return (node->toString() == commonName);
|
||||
}
|
||||
else if (node->isBoolValue()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
int triagens::aql::removeRedundantOr (Optimizer* opt,
|
||||
ExecutionPlan* plan,
|
||||
Optimizer::Rule const* rule) {
|
||||
ENTER_BLOCK;
|
||||
std::vector<ExecutionNode*> nodes
|
||||
= plan->findNodesOfType(EN::FILTER, true);
|
||||
|
||||
bool modified = false;
|
||||
for (auto n : nodes) {
|
||||
auto deps = n->getDependencies();
|
||||
TRI_ASSERT(deps.size() == 1);
|
||||
if (deps[0]->getType() != EN::CALCULATION) {
|
||||
continue;
|
||||
}
|
||||
|
||||
auto fn = static_cast<FilterNode*>(n);
|
||||
auto cn = static_cast<CalculationNode*>(deps[0]);
|
||||
|
||||
auto inVar = fn->getVariablesUsedHere();
|
||||
auto outVar = cn->getVariablesSetHere();
|
||||
|
||||
if (outVar.size() != 1 || outVar[0]->id != inVar[0]->id) {
|
||||
continue;
|
||||
}
|
||||
if (cn->expression()->node()->type != NODE_TYPE_OPERATOR_BINARY_OR) {
|
||||
continue;
|
||||
}
|
||||
|
||||
RemoveRedundantOr remover;
|
||||
if (remover.hasRedundantCondition(cn->expression()->node())) {
|
||||
Expression* expr = nullptr;
|
||||
ExecutionNode* newNode = nullptr;
|
||||
auto astNode = remover.createReplacementNode(plan->getAst());
|
||||
|
||||
expr = new Expression(plan->getAst(), astNode);
|
||||
|
||||
try {
|
||||
newNode = new CalculationNode(plan, plan->nextId(), expr, outVar[0]);
|
||||
}
|
||||
catch (...) {
|
||||
delete expr;
|
||||
throw;
|
||||
}
|
||||
|
||||
plan->registerNode(newNode);
|
||||
plan->replaceNode(cn, newNode);
|
||||
modified = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (modified) {
|
||||
plan->findVarUsage();
|
||||
}
|
||||
opt->addPlan(plan, rule->level, modified);
|
||||
|
||||
return TRI_ERROR_NO_ERROR;
|
||||
LEAVE_BLOCK;
|
||||
}
|
||||
|
||||
// Local Variables:
|
||||
// mode: outline-minor
|
||||
// outline-regexp: "^\\(/// @brief\\|/// {@inheritDoc}\\|/// @addtogroup\\|// --SECTION--\\|/// @\\}\\)"
|
||||
|
|
|
@ -188,6 +188,8 @@ namespace triagens {
|
|||
|
||||
int replaceOrWithIn (Optimizer*, ExecutionPlan*, Optimizer::Rule const*);
|
||||
|
||||
int removeRedundantOr (Optimizer*, ExecutionPlan*, Optimizer::Rule const*);
|
||||
|
||||
} // namespace aql
|
||||
} // namespace triagens
|
||||
|
||||
|
|
|
@ -0,0 +1,692 @@
|
|||
/*jshint strict: false, maxlen: 500 */
|
||||
/*global require, assertEqual, assertTrue, AQL_EXPLAIN, AQL_EXECUTE */
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
/// @brief tests for Ahuacatl, skiplist index queries
|
||||
///
|
||||
/// @file
|
||||
///
|
||||
/// DISCLAIMER
|
||||
///
|
||||
/// Copyright 2010-2012 triagens GmbH, Cologne, Germany
|
||||
///
|
||||
/// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
/// you may not use this file except in compliance with the License.
|
||||
/// You may obtain a copy of the License at
|
||||
///
|
||||
/// http://www.apache.org/licenses/LICENSE-2.0
|
||||
///
|
||||
/// Unless required by applicable law or agreed to in writing, software
|
||||
/// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
/// See the License for the specific language governing permissions and
|
||||
/// limitations under the License.
|
||||
///
|
||||
/// Copyright holder is triAGENS GmbH, Cologne, Germany
|
||||
///
|
||||
/// @author
|
||||
/// @author Copyright 2014, triAGENS GmbH, Cologne, Germany
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// TODO add some test which don't use number values!
|
||||
|
||||
var jsunity = require("jsunity");
|
||||
var helper = require("org/arangodb/aql-helper");
|
||||
var getQueryResults = helper.getQueryResults;
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
/// @brief test suite
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
function NewAqlRemoveRedundantORTestSuite () {
|
||||
var ruleName = "remove-redundant-or";
|
||||
|
||||
var isRuleUsed = function (query, params) {
|
||||
var result = AQL_EXPLAIN(query, params, { optimizer: { rules: [ "-all", "+" + ruleName ] } });
|
||||
assertTrue(result.plan.rules.indexOf(ruleName) !== -1, query);
|
||||
result = AQL_EXPLAIN(query, params, { optimizer: { rules: [ "-all" ] } });
|
||||
assertTrue(result.plan.rules.indexOf(ruleName) === -1, query);
|
||||
};
|
||||
|
||||
var ruleIsNotUsed = function (query, params) {
|
||||
var result = AQL_EXPLAIN(query, params, { optimizer: { rules: [ "-all", "+" + ruleName ] } });
|
||||
assertTrue(result.plan.rules.indexOf(ruleName) === -1, query);
|
||||
};
|
||||
|
||||
var executeWithRule = function (query, params) {
|
||||
return AQL_EXECUTE(query, params, { optimizer: { rules: [ "-all", "+" + ruleName ] } }).json;
|
||||
};
|
||||
|
||||
var executeWithoutRule = function (query, params) {
|
||||
return AQL_EXECUTE(query, params, { optimizer: { rules: [ "-all" ] } }).json;
|
||||
};
|
||||
|
||||
return {
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
/// @brief set up
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
setUp : function () {
|
||||
},
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
/// @brief tear down
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
tearDown : function () {
|
||||
},
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
/// @brief test the rule fires for actual values
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
testFiresGtGt1 : function () {
|
||||
var query = "FOR i IN [ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 ] "
|
||||
+ " FILTER i > 1 || i > 2 RETURN i";
|
||||
|
||||
isRuleUsed(query, {});
|
||||
|
||||
var expected = [ 2, 3, 4, 5, 6, 7, 8, 9, 10 ];
|
||||
var actual = getQueryResults(query);
|
||||
assertEqual(expected, actual);
|
||||
assertEqual(executeWithRule(query, {}), executeWithoutRule(query, {}));
|
||||
},
|
||||
|
||||
testFiresGtLt1 : function () {
|
||||
var query = "FOR i IN [ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 ] FILTER i > 1 || 2 < i RETURN i";
|
||||
|
||||
isRuleUsed(query, {});
|
||||
|
||||
var expected = [ 2, 3, 4, 5, 6, 7, 8, 9, 10 ];
|
||||
var actual = getQueryResults(query);
|
||||
assertEqual(expected, actual);
|
||||
assertEqual(executeWithRule(query, {}), executeWithoutRule(query, {}));
|
||||
},
|
||||
|
||||
testFiresLtLt1 : function () {
|
||||
var query = "FOR i IN [ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 ] FILTER 1 < i || 2 < i RETURN i";
|
||||
|
||||
isRuleUsed(query, {});
|
||||
|
||||
var expected = [ 2, 3, 4, 5, 6, 7, 8, 9, 10 ];
|
||||
var actual = getQueryResults(query);
|
||||
assertEqual(expected, actual);
|
||||
assertEqual(executeWithRule(query, {}), executeWithoutRule(query, {}));
|
||||
},
|
||||
|
||||
testFiresLtGt1 : function () {
|
||||
var query = "FOR i IN [ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 ] FILTER 1 < i || i > 2 RETURN i";
|
||||
|
||||
isRuleUsed(query, {});
|
||||
|
||||
var expected = [ 2, 3, 4, 5, 6, 7, 8, 9, 10 ];
|
||||
var actual = getQueryResults(query);
|
||||
assertEqual(expected, actual);
|
||||
assertEqual(executeWithRule(query, {}), executeWithoutRule(query, {}));
|
||||
},
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
testFiresGtGe1 : function () {
|
||||
var query = "FOR i IN [ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 ] "
|
||||
+ " FILTER i > 1 || i >= 2 RETURN i";
|
||||
|
||||
isRuleUsed(query, {});
|
||||
|
||||
var expected = [ 2, 3, 4, 5, 6, 7, 8, 9, 10 ];
|
||||
var actual = getQueryResults(query);
|
||||
assertEqual(expected, actual);
|
||||
assertEqual(executeWithRule(query, {}), executeWithoutRule(query, {}));
|
||||
},
|
||||
|
||||
testFiresGtLe1 : function () {
|
||||
var query = "FOR i IN [ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 ] "
|
||||
+ " FILTER i > 1 || 2 <= i RETURN i";
|
||||
|
||||
isRuleUsed(query, {});
|
||||
|
||||
var expected = [ 2, 3, 4, 5, 6, 7, 8, 9, 10 ];
|
||||
var actual = getQueryResults(query);
|
||||
assertEqual(expected, actual);
|
||||
assertEqual(executeWithRule(query, {}), executeWithoutRule(query, {}));
|
||||
},
|
||||
|
||||
testFiresLtLe1 : function () {
|
||||
var query = "FOR i IN [ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 ] "
|
||||
+ " FILTER 1 < i || 2 <= i RETURN i";
|
||||
|
||||
isRuleUsed(query, {});
|
||||
|
||||
var expected = [ 2, 3, 4, 5, 6, 7, 8, 9, 10 ];
|
||||
var actual = getQueryResults(query);
|
||||
assertEqual(expected, actual);
|
||||
assertEqual(executeWithRule(query, {}), executeWithoutRule(query, {}));
|
||||
},
|
||||
|
||||
testFiresLtGe1 : function () {
|
||||
var query = "FOR i IN [ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 ] "
|
||||
+ " FILTER 1 < i || i >= 2 RETURN i";
|
||||
|
||||
isRuleUsed(query, {});
|
||||
|
||||
var expected = [ 2, 3, 4, 5, 6, 7, 8, 9, 10 ];
|
||||
var actual = getQueryResults(query);
|
||||
assertEqual(expected, actual);
|
||||
assertEqual(executeWithRule(query, {}), executeWithoutRule(query, {}));
|
||||
},
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
testFiresGeGt1 : function () {
|
||||
var query = "FOR i IN [ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 ] "
|
||||
+ " FILTER i >= 1 || i > 2 RETURN i";
|
||||
|
||||
isRuleUsed(query, {});
|
||||
|
||||
var expected = [ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 ];
|
||||
var actual = getQueryResults(query);
|
||||
assertEqual(expected, actual);
|
||||
assertEqual(executeWithRule(query, {}), executeWithoutRule(query, {}));
|
||||
},
|
||||
|
||||
testFiresGeLt1 : function () {
|
||||
var query = "FOR i IN [ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 ] "
|
||||
+ " FILTER i >= 1 || 2 < i RETURN i";
|
||||
|
||||
isRuleUsed(query, {});
|
||||
|
||||
var expected = [ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 ];
|
||||
var actual = getQueryResults(query);
|
||||
assertEqual(expected, actual);
|
||||
assertEqual(executeWithRule(query, {}), executeWithoutRule(query, {}));
|
||||
},
|
||||
|
||||
testFiresLeLt1 : function () {
|
||||
var query = "FOR i IN [ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 ] "
|
||||
+ " FILTER 1 <= i || 2 < i RETURN i";
|
||||
|
||||
isRuleUsed(query, {});
|
||||
|
||||
var expected = [ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 ];
|
||||
var actual = getQueryResults(query);
|
||||
assertEqual(expected, actual);
|
||||
assertEqual(executeWithRule(query, {}), executeWithoutRule(query, {}));
|
||||
},
|
||||
|
||||
testFiresLeGt1 : function () {
|
||||
var query = "FOR i IN [ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 ] "
|
||||
+ " FILTER 1 <= i || i > 2 RETURN i";
|
||||
|
||||
isRuleUsed(query, {});
|
||||
|
||||
var expected = [ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 ];
|
||||
var actual = getQueryResults(query);
|
||||
assertEqual(expected, actual);
|
||||
assertEqual(executeWithRule(query, {}), executeWithoutRule(query, {}));
|
||||
},
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
testFiresGeGe1 : function () {
|
||||
var query = "FOR i IN [ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 ] "
|
||||
+ " FILTER i >= 1 || i >= 2 RETURN i";
|
||||
|
||||
isRuleUsed(query, {});
|
||||
|
||||
var expected = [ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 ];
|
||||
var actual = getQueryResults(query);
|
||||
assertEqual(expected, actual);
|
||||
assertEqual(executeWithRule(query, {}), executeWithoutRule(query, {}));
|
||||
},
|
||||
|
||||
testFiresGeLe1 : function () {
|
||||
var query = "FOR i IN [ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 ] "
|
||||
+ " FILTER i >= 1 || 2 <= i RETURN i";
|
||||
|
||||
isRuleUsed(query, {});
|
||||
|
||||
var expected = [ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 ];
|
||||
var actual = getQueryResults(query);
|
||||
assertEqual(expected, actual);
|
||||
assertEqual(executeWithRule(query, {}), executeWithoutRule(query, {}));
|
||||
},
|
||||
|
||||
testFiresLeLe1 : function () {
|
||||
var query = "FOR i IN [ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 ] "
|
||||
+ " FILTER 1 <= i || 2 <= i RETURN i";
|
||||
|
||||
isRuleUsed(query, {});
|
||||
|
||||
var expected = [ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 ];
|
||||
var actual = getQueryResults(query);
|
||||
assertEqual(expected, actual);
|
||||
assertEqual(executeWithRule(query, {}), executeWithoutRule(query, {}));
|
||||
},
|
||||
|
||||
testFiresLeGe1 : function () {
|
||||
var query = "FOR i IN [ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 ] "
|
||||
+ " FILTER 1 <= i || i >= 2 RETURN i";
|
||||
|
||||
isRuleUsed(query, {});
|
||||
|
||||
var expected = [ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 ];
|
||||
var actual = getQueryResults(query);
|
||||
assertEqual(expected, actual);
|
||||
assertEqual(executeWithRule(query, {}), executeWithoutRule(query, {}));
|
||||
},
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
testFiresGtGt2 : function () {
|
||||
var query = "FOR i IN [ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 ] "
|
||||
+ " FILTER i > 1 || i > 1 RETURN i";
|
||||
|
||||
isRuleUsed(query, {});
|
||||
|
||||
var expected = [ 2, 3, 4, 5, 6, 7, 8, 9, 10 ];
|
||||
var actual = getQueryResults(query);
|
||||
assertEqual(expected, actual);
|
||||
assertEqual(executeWithRule(query, {}), executeWithoutRule(query, {}));
|
||||
},
|
||||
|
||||
testFiresGtLt2 : function () {
|
||||
var query = "FOR i IN [ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 ] FILTER i > 1 || 1 < i RETURN i";
|
||||
|
||||
isRuleUsed(query, {});
|
||||
|
||||
var expected = [ 2, 3, 4, 5, 6, 7, 8, 9, 10 ];
|
||||
var actual = getQueryResults(query);
|
||||
assertEqual(expected, actual);
|
||||
assertEqual(executeWithRule(query, {}), executeWithoutRule(query, {}));
|
||||
},
|
||||
|
||||
testFiresLtLt2 : function () {
|
||||
var query = "FOR i IN [ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 ] FILTER 1 < i || 1 < i RETURN i";
|
||||
|
||||
isRuleUsed(query, {});
|
||||
|
||||
var expected = [ 2, 3, 4, 5, 6, 7, 8, 9, 10 ];
|
||||
var actual = getQueryResults(query);
|
||||
assertEqual(expected, actual);
|
||||
assertEqual(executeWithRule(query, {}), executeWithoutRule(query, {}));
|
||||
},
|
||||
|
||||
testFiresLtGt2 : function () {
|
||||
var query = "FOR i IN [ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 ] FILTER 1 < i || i > 1 RETURN i";
|
||||
|
||||
isRuleUsed(query, {});
|
||||
|
||||
var expected = [ 2, 3, 4, 5, 6, 7, 8, 9, 10 ];
|
||||
var actual = getQueryResults(query);
|
||||
assertEqual(expected, actual);
|
||||
assertEqual(executeWithRule(query, {}), executeWithoutRule(query, {}));
|
||||
},
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
testFiresGtGe2 : function () {
|
||||
var query = "FOR i IN [ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 ] "
|
||||
+ " FILTER i > 1 || i >= 1 RETURN i";
|
||||
|
||||
isRuleUsed(query, {});
|
||||
|
||||
var expected = [ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 ];
|
||||
var actual = getQueryResults(query);
|
||||
assertEqual(expected, actual);
|
||||
assertEqual(executeWithRule(query, {}), executeWithoutRule(query, {}));
|
||||
},
|
||||
|
||||
testFiresGtLe2 : function () {
|
||||
var query = "FOR i IN [ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 ] "
|
||||
+ " FILTER i > 1 || 1 <= i RETURN i";
|
||||
|
||||
isRuleUsed(query, {});
|
||||
|
||||
var expected = [ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 ];
|
||||
var actual = getQueryResults(query);
|
||||
assertEqual(expected, actual);
|
||||
assertEqual(executeWithRule(query, {}), executeWithoutRule(query, {}));
|
||||
},
|
||||
|
||||
testFiresLtLe2 : function () {
|
||||
var query = "FOR i IN [ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 ] FILTER 1 < i || 1 <= i RETURN i";
|
||||
|
||||
isRuleUsed(query, {});
|
||||
|
||||
var expected = [ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 ];
|
||||
var actual = getQueryResults(query);
|
||||
assertEqual(expected, actual);
|
||||
assertEqual(executeWithRule(query, {}), executeWithoutRule(query, {}));
|
||||
},
|
||||
|
||||
testFiresLtGe2 : function () {
|
||||
var query = "FOR i IN [ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 ] "
|
||||
+ " FILTER 1 < i || i >= 1 RETURN i";
|
||||
|
||||
isRuleUsed(query, {});
|
||||
|
||||
var expected = [ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 ];
|
||||
var actual = getQueryResults(query);
|
||||
assertEqual(expected, actual);
|
||||
assertEqual(executeWithRule(query, {}), executeWithoutRule(query, {}));
|
||||
},
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
testFiresGeGt2 : function () {
|
||||
var query = "FOR i IN [ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 ] "
|
||||
+ " FILTER i >= 1 || i > 1 RETURN i";
|
||||
|
||||
isRuleUsed(query, {});
|
||||
|
||||
var expected = [ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 ];
|
||||
var actual = getQueryResults(query);
|
||||
assertEqual(expected, actual);
|
||||
assertEqual(executeWithRule(query, {}), executeWithoutRule(query, {}));
|
||||
},
|
||||
|
||||
testFiresGeLt2 : function () {
|
||||
var query = "FOR i IN [ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 ] "
|
||||
+ " FILTER i >= 1 || 1 < i RETURN i";
|
||||
|
||||
isRuleUsed(query, {});
|
||||
|
||||
var expected = [ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 ];
|
||||
var actual = getQueryResults(query);
|
||||
assertEqual(expected, actual);
|
||||
assertEqual(executeWithRule(query, {}), executeWithoutRule(query, {}));
|
||||
},
|
||||
|
||||
testFiresLeLt2 : function () {
|
||||
var query = "FOR i IN [ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 ] "
|
||||
+ " FILTER 1 <= i || 1 < i RETURN i";
|
||||
|
||||
isRuleUsed(query, {});
|
||||
|
||||
var expected = [ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 ];
|
||||
var actual = getQueryResults(query);
|
||||
assertEqual(expected, actual);
|
||||
assertEqual(executeWithRule(query, {}), executeWithoutRule(query, {}));
|
||||
},
|
||||
|
||||
testFiresLeGt2 : function () {
|
||||
var query = "FOR i IN [ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 ] "
|
||||
+ " FILTER 1 <= i || i > 1 RETURN i";
|
||||
|
||||
isRuleUsed(query, {});
|
||||
|
||||
var expected = [ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 ];
|
||||
var actual = getQueryResults(query);
|
||||
assertEqual(expected, actual);
|
||||
assertEqual(executeWithRule(query, {}), executeWithoutRule(query, {}));
|
||||
},
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
testFiresGeGe2 : function () {
|
||||
var query = "FOR i IN [ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 ] "
|
||||
+ " FILTER i >= 1 || i >= 1 RETURN i";
|
||||
|
||||
isRuleUsed(query, {});
|
||||
|
||||
var expected = [ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 ];
|
||||
var actual = getQueryResults(query);
|
||||
assertEqual(expected, actual);
|
||||
assertEqual(executeWithRule(query, {}), executeWithoutRule(query, {}));
|
||||
},
|
||||
|
||||
testFiresGeLe2 : function () {
|
||||
var query = "FOR i IN [ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 ] "
|
||||
+ " FILTER i >= 1 || 1 <= i RETURN i";
|
||||
|
||||
isRuleUsed(query, {});
|
||||
|
||||
var expected = [ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 ];
|
||||
var actual = getQueryResults(query);
|
||||
assertEqual(expected, actual);
|
||||
assertEqual(executeWithRule(query, {}), executeWithoutRule(query, {}));
|
||||
},
|
||||
|
||||
testFiresLeLe2 : function () {
|
||||
var query = "FOR i IN [ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 ] "
|
||||
+ " FILTER 1 <= i || 1 <= i RETURN i";
|
||||
|
||||
isRuleUsed(query, {});
|
||||
|
||||
var expected = [ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 ];
|
||||
var actual = getQueryResults(query);
|
||||
assertEqual(expected, actual);
|
||||
assertEqual(executeWithRule(query, {}), executeWithoutRule(query, {}));
|
||||
},
|
||||
|
||||
testFiresLeGe2 : function () {
|
||||
var query = "FOR i IN [ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 ] "
|
||||
+ " FILTER 1 <= i || i >= 1 RETURN i";
|
||||
|
||||
isRuleUsed(query, {});
|
||||
|
||||
var expected = [ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 ];
|
||||
var actual = getQueryResults(query);
|
||||
assertEqual(expected, actual);
|
||||
assertEqual(executeWithRule(query, {}), executeWithoutRule(query, {}));
|
||||
},
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
testDudGtGt1 : function () {
|
||||
var query = "FOR i IN [ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 ] "
|
||||
+ " FILTER i > 1 || 2 > i RETURN i";
|
||||
|
||||
ruleIsNotUsed(query, {});
|
||||
},
|
||||
|
||||
testDudGtLt1 : function () {
|
||||
var query = "FOR i IN [ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 ] FILTER i > 1 || i < 2 RETURN i";
|
||||
|
||||
ruleIsNotUsed(query, {});
|
||||
},
|
||||
|
||||
testDudLtLt1 : function () {
|
||||
var query = "FOR i IN [ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 ] FILTER 1 < i || i < 2 RETURN i";
|
||||
|
||||
ruleIsNotUsed(query, {});
|
||||
},
|
||||
|
||||
testDudLtGt1 : function () {
|
||||
var query = "FOR i IN [ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 ] FILTER 1 < i || 2 > i RETURN i";
|
||||
|
||||
ruleIsNotUsed(query, {});
|
||||
},
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
testDudGtGe1 : function () {
|
||||
var query = "FOR i IN [ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 ] "
|
||||
+ " FILTER i > 1 || 2 >= i RETURN i";
|
||||
|
||||
ruleIsNotUsed(query, {});
|
||||
},
|
||||
|
||||
testDudGtLe1 : function () {
|
||||
var query = "FOR i IN [ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 ] "
|
||||
+ " FILTER i > 1 || i <= 2 RETURN i";
|
||||
|
||||
ruleIsNotUsed(query, {});
|
||||
},
|
||||
|
||||
testDudLtLe1 : function () {
|
||||
var query = "FOR i IN [ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 ] "
|
||||
+ " FILTER 1 < i || i <= 2 RETURN i";
|
||||
|
||||
ruleIsNotUsed(query, {});
|
||||
},
|
||||
|
||||
testDudLtGe1 : function () {
|
||||
var query = "FOR i IN [ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 ] "
|
||||
+ " FILTER 1 < i || 2 >= i RETURN i";
|
||||
|
||||
ruleIsNotUsed(query, {});
|
||||
},
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
testDudGeGt1 : function () {
|
||||
var query = "FOR i IN [ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 ] "
|
||||
+ " FILTER i >= 1 || 2 > i RETURN i";
|
||||
ruleIsNotUsed(query, {});
|
||||
},
|
||||
|
||||
testDudGeLt1 : function () {
|
||||
var query = "FOR i IN [ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 ] "
|
||||
+ " FILTER i >= 1 || i < 2 RETURN i";
|
||||
ruleIsNotUsed(query, {});
|
||||
},
|
||||
|
||||
testDudLeLt1 : function () {
|
||||
var query = "FOR i IN [ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 ] "
|
||||
+ " FILTER 1 <= i || i < 2 RETURN i";
|
||||
ruleIsNotUsed(query, {});
|
||||
},
|
||||
|
||||
testDudLeGt1 : function () {
|
||||
var query = "FOR i IN [ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 ] "
|
||||
+ " FILTER 1 <= i || 2 > i RETURN i";
|
||||
|
||||
ruleIsNotUsed(query, {});
|
||||
},
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
testDudGeGe1 : function () {
|
||||
var query = "FOR i IN [ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 ] "
|
||||
+ " FILTER i >= 1 || 2 >= i RETURN i";
|
||||
ruleIsNotUsed(query, {});
|
||||
},
|
||||
|
||||
testDudGeLe1 : function () {
|
||||
var query = "FOR i IN [ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 ] "
|
||||
+ " FILTER i >= 1 || i <= 2 RETURN i";
|
||||
ruleIsNotUsed(query, {});
|
||||
},
|
||||
|
||||
testDudLeLe1 : function () {
|
||||
var query = "FOR i IN [ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 ] "
|
||||
+ " FILTER 1 <= i || i <= 2 RETURN i";
|
||||
ruleIsNotUsed(query, {});
|
||||
},
|
||||
|
||||
testDudLeGe1 : function () {
|
||||
var query = "FOR i IN [ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 ] "
|
||||
+ " FILTER 1 <= i || 2 >= i RETURN i";
|
||||
|
||||
ruleIsNotUsed(query, {});
|
||||
},
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
testDudGtGt2 : function () {
|
||||
var query = "FOR i IN [ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 ] "
|
||||
+ " FILTER i > 1 || i < 1 RETURN i";
|
||||
ruleIsNotUsed(query, {});
|
||||
},
|
||||
|
||||
testDudGtLt2 : function () {
|
||||
var query = "FOR i IN [ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 ] FILTER i > 1 || 1 > i RETURN i";
|
||||
ruleIsNotUsed(query, {});
|
||||
},
|
||||
|
||||
testDudLtLt2 : function () {
|
||||
var query = "FOR i IN [ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 ] FILTER 1 < i || 1 > i RETURN i";
|
||||
ruleIsNotUsed(query, {});
|
||||
},
|
||||
|
||||
testDudLtGt2 : function () {
|
||||
var query = "FOR i IN [ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 ] FILTER 1 < i || i < 1 RETURN i";
|
||||
ruleIsNotUsed(query, {});
|
||||
},
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
testDudGtGe2 : function () {
|
||||
var query = "FOR i IN [ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 ] "
|
||||
+ " FILTER i > 1 || 1 >= i RETURN i";
|
||||
ruleIsNotUsed(query, {});
|
||||
},
|
||||
|
||||
testDudGtLe2 : function () {
|
||||
var query = "FOR i IN [ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 ] "
|
||||
+ " FILTER i > 1 || i <= 1 RETURN i";
|
||||
ruleIsNotUsed(query, {});
|
||||
},
|
||||
|
||||
testDudLtLe2 : function () {
|
||||
var query = "FOR i IN [ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 ] FILTER 1 < i || i <= 1 RETURN i";
|
||||
ruleIsNotUsed(query, {});
|
||||
},
|
||||
|
||||
testDudLtGe2 : function () {
|
||||
var query = "FOR i IN [ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 ] "
|
||||
+ " FILTER 1 < i || 1 >= i RETURN i";
|
||||
ruleIsNotUsed(query, {});
|
||||
},
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
testDudGeGt2 : function () {
|
||||
var query = "FOR i IN [ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 ] "
|
||||
+ " FILTER i >= 1 || 1 > i RETURN i";
|
||||
ruleIsNotUsed(query, {});
|
||||
},
|
||||
|
||||
testDudGeLt2 : function () {
|
||||
var query = "FOR i IN [ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 ] "
|
||||
+ " FILTER i >= 1 || i < 1 RETURN i";
|
||||
ruleIsNotUsed(query, {});
|
||||
},
|
||||
|
||||
testDudLeLt2 : function () {
|
||||
var query = "FOR i IN [ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 ] "
|
||||
+ " FILTER 1 <= i || i < 1 RETURN i";
|
||||
ruleIsNotUsed(query, {});
|
||||
},
|
||||
|
||||
testDudLeGt2 : function () {
|
||||
var query = "FOR i IN [ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 ] "
|
||||
+ " FILTER 1 <= i || 1 > i RETURN i";
|
||||
ruleIsNotUsed(query, {});
|
||||
},
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
testDudGeGe2 : function () {
|
||||
var query = "FOR i IN [ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 ] "
|
||||
+ " FILTER i >= 1 || 1 >= i RETURN i";
|
||||
ruleIsNotUsed(query, {});
|
||||
},
|
||||
|
||||
testDudGeLe2 : function () {
|
||||
var query = "FOR i IN [ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 ] "
|
||||
+ " FILTER i >= 1 || i <= 1 RETURN i";
|
||||
ruleIsNotUsed(query, {});
|
||||
},
|
||||
|
||||
testDudLeLe2 : function () {
|
||||
var query = "FOR i IN [ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 ] "
|
||||
+ " FILTER 1 <= i || i <= 1 RETURN i";
|
||||
ruleIsNotUsed(query, {});
|
||||
},
|
||||
|
||||
testDudLeGe2 : function () {
|
||||
var query = "FOR i IN [ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 ] "
|
||||
+ " FILTER 1 <= i || 1 >= i RETURN i";
|
||||
ruleIsNotUsed(query, {});
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
jsunity.run(NewAqlRemoveRedundantORTestSuite);
|
||||
|
||||
return jsunity.done();
|
||||
|
|
@ -39,7 +39,7 @@ var getQueryResults = helper.getQueryResults;
|
|||
|
||||
function NewAqlReplaceORWithINTestSuite () {
|
||||
var replace;
|
||||
var ruleName = "replace-OR-with-IN";
|
||||
var ruleName = "replace-or-with-in";
|
||||
|
||||
var isRuleUsed = function (query, params) {
|
||||
var result = AQL_EXPLAIN(query, params, { optimizer: { rules: [ "-all", "+" + ruleName ] } });
|
||||
|
@ -389,16 +389,19 @@ function NewAqlReplaceORWithINTestSuite () {
|
|||
|
||||
},
|
||||
|
||||
testFiresCommonConstant: function () {
|
||||
testDudCommonConstant1: function () {
|
||||
var query = "LET x = {a:@a} FOR v IN " + replace.name()
|
||||
+ " FILTER x.a == v.value || x.a == v._key RETURN v._key";
|
||||
|
||||
var key = replace.any()._key;
|
||||
isRuleUsed(query, {a: key});
|
||||
|
||||
var actual = getQueryResults(query, {a: key});
|
||||
assertEqual(key, actual.toString());
|
||||
ruleIsNotUsed(query, {a: key});
|
||||
},
|
||||
|
||||
testDudCommonConstant2: function () {
|
||||
var query = "LET x = {a:1} FOR v IN " + replace.name()
|
||||
+ " FILTER x.a == v.value || x.a == v._key RETURN v._key";
|
||||
|
||||
ruleIsNotUsed(query, {});
|
||||
},
|
||||
|
||||
testDudAlwaysTrue: function () {
|
||||
|
|
|
@ -203,9 +203,12 @@ int TRI_CompareValuesJson (TRI_json_t const* lhs,
|
|||
return 1;
|
||||
}
|
||||
|
||||
TRI_ASSERT(lWeight == rWeight);
|
||||
|
||||
// lhs and rhs have equal weights
|
||||
if (lhs == nullptr) {
|
||||
// both lhs and rhs are NULL, so they are equal
|
||||
TRI_ASSERT(rhs == nullptr);
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue