1
0
Fork 0

Merge branch 'aql-feature-lazy-index' of https://github.com/triAGENS/ArangoDB into devel

This commit is contained in:
Jan Steemann 2014-11-13 18:04:57 +01:00
commit 5fda62b725
14 changed files with 1486 additions and 268 deletions

View File

@ -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 \

View File

@ -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);
}

View File

@ -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

View File

@ -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
// -----------------------------------------------------------------------------

View File

@ -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*);
}
}

View File

@ -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();

View File

@ -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;
};
// -----------------------------------------------------------------------------

View File

@ -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,

View File

@ -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

View File

@ -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--\\|/// @\\}\\)"

View File

@ -188,6 +188,8 @@ namespace triagens {
int replaceOrWithIn (Optimizer*, ExecutionPlan*, Optimizer::Rule const*);
int removeRedundantOr (Optimizer*, ExecutionPlan*, Optimizer::Rule const*);
} // namespace aql
} // namespace triagens

View File

@ -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();

View File

@ -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 () {

View File

@ -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;
}