1
0
Fork 0

slightly changed cost estimation for IndexRangeNode

This commit is contained in:
Jan Steemann 2015-01-07 15:11:42 +01:00
parent 2148e695f4
commit c591357dd3
4 changed files with 487 additions and 70 deletions

View File

@ -1251,7 +1251,7 @@ bool IndexRangeBlock::initRanges () {
for (size_t u = 0; u < _condition->at(s).size(); u++) { for (size_t u = 0; u < _condition->at(s).size(); u++) {
auto ri = _condition->at(s)[u]; auto ri = _condition->at(s)[u];
if (en->_index->fields[t].compare(ri._attr) == 0) { if (en->_index->fields[t].compare(ri._attr) == 0) {
prefix.at(s).insert(prefix.at(s).begin()+t, u); prefix.at(s).insert(prefix.at(s).begin() + t, u);
break; break;
} }
} }
@ -1909,7 +1909,7 @@ void IndexRangeBlock::getSkiplistIterator (IndexAndCondition const& ranges) {
Json parameters(Json::Array); Json parameters(Json::Array);
size_t i = 0; size_t i = 0;
for (; i < ranges.size(); i++) { for (; i < ranges.size(); i++) {
auto range = ranges[i]; auto const& range = ranges[i];
TRI_ASSERT(range.isConstant()); TRI_ASSERT(range.isConstant());
if (range.is1ValueRangeInfo()) { // it's an equality . . . if (range.is1ValueRangeInfo()) { // it's an equality . . .
parameters(range._lowConst.bound().copy()); parameters(range._lowConst.bound().copy());

View File

@ -462,8 +462,8 @@ void ExecutionNode::appendAsString (std::string& st, int indent) {
} }
//////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////
/// @brief inspect one index; only skiplist indices which match attrs in sequence. /// @brief inspect one index; only skiplist indexes which match attrs in sequence.
/// @returns a a qualification how good they match; /// @returns a qualification how good they match;
/// match->index==nullptr means no match at all. /// match->index==nullptr means no match at all.
//////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////
@ -741,7 +741,6 @@ struct RegisterPlanningDebugger : public WalkerWorker<ExecutionNode> {
//////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////
void ExecutionNode::planRegisters (ExecutionNode* super) { void ExecutionNode::planRegisters (ExecutionNode* super) {
// std::cout << triagens::arango::ServerState::instance()->getId() << ": PLAN REGISTERS\n";
// The super is only for the case of subqueries. // The super is only for the case of subqueries.
shared_ptr<RegisterPlan> v; shared_ptr<RegisterPlan> v;
if (super == nullptr) { if (super == nullptr) {
@ -1098,7 +1097,7 @@ size_t EnumerateCollectionNode::getUsableFieldsOfIndex (Index const* idx,
} }
//////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////
/// @brief get vector of indices with fields <attrs> /// @brief get vector of indexes with fields <attrs>
//////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////
// checks if a subset of <attrs> is a prefix of <idx->_fields> for every index // checks if a subset of <attrs> is a prefix of <idx->_fields> for every index
@ -1386,6 +1385,8 @@ ExecutionNode::IndexMatch IndexRangeNode::MatchesIndex (IndexMatchVec const& pat
//////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////
double IndexRangeNode::estimateCost (size_t& nrItems) const { double IndexRangeNode::estimateCost (size_t& nrItems) const {
static double const EqualityReductionFactor = 100.0;
size_t incoming = 0; size_t incoming = 0;
double const dependencyCost = _dependencies.at(0)->getCost(incoming); double const dependencyCost = _dependencies.at(0)->getCost(incoming);
size_t docCount = _collection->count(); size_t docCount = _collection->count();
@ -1393,22 +1394,33 @@ double IndexRangeNode::estimateCost (size_t& nrItems) const {
TRI_ASSERT(! _ranges.empty()); TRI_ASSERT(! _ranges.empty());
if (_index->type == TRI_IDX_TYPE_PRIMARY_INDEX) { if (_index->type == TRI_IDX_TYPE_PRIMARY_INDEX) {
nrItems = incoming; // always an equality lookup
nrItems = incoming * _ranges.size();
return dependencyCost + nrItems; return dependencyCost + nrItems;
} }
if (_index->type == TRI_IDX_TYPE_EDGE_INDEX) { if (_index->type == TRI_IDX_TYPE_EDGE_INDEX) {
nrItems = incoming * docCount / 1000; // always an equality lookup
nrItems = incoming * _ranges.size() * docCount / EqualityReductionFactor;
return dependencyCost + nrItems; return dependencyCost + nrItems;
} }
if (_index->type == TRI_IDX_TYPE_HASH_INDEX) { if (_index->type == TRI_IDX_TYPE_HASH_INDEX) {
// always an equality lookup
if (_index->unique) { if (_index->unique) {
nrItems = incoming; nrItems = incoming * _ranges.size();
return dependencyCost + nrItems; return dependencyCost + nrItems;
} }
nrItems = incoming * docCount / 1000;
return dependencyCost + nrItems; double cost = static_cast<double>(docCount) * incoming * _ranges.size();
// the more attributes are contained in the index, the more specific the lookup will be
for (auto const& x : _ranges.at(0)) {
cost /= EqualityReductionFactor;
}
nrItems = static_cast<size_t>(cost);
cost *= 0.9999995; // this is to prefer the hash index over skiplists if everything else is equal
return dependencyCost + cost;
} }
if (_index->type == TRI_IDX_TYPE_SKIPLIST_INDEX) { if (_index->type == TRI_IDX_TYPE_SKIPLIST_INDEX) {
@ -1420,56 +1432,78 @@ double IndexRangeNode::estimateCost (size_t& nrItems) const {
return dependencyCost + nrItems; return dependencyCost + nrItems;
} }
if (_index->unique && if (_index->unique) {
count == _index->fields.size()) { bool allEquality = true;
if (_ranges.at(0).back().is1ValueRangeInfo()) { for (auto const& x : _ranges) {
// check if we are using all indexed attributes in the query
if (x.size() != _index->fields.size()) {
allEquality = false;
break;
}
// check if this is an equality comparison
if (x.empty() || ! x.back().is1ValueRangeInfo()) {
allEquality = false;
break;
}
}
if (allEquality) {
// unique index, all attributes compared using eq (==) operator // unique index, all attributes compared using eq (==) operator
nrItems = incoming; nrItems = incoming * _ranges.size();
return dependencyCost + nrItems; return dependencyCost + nrItems;
} }
} }
double cost = static_cast<double>(docCount) * incoming; // build a total cost for the index usage by peeking into all ranges
for (auto x: _ranges.at(0)) { //only doing the 1-d case so far double totalCost = 0.0;
if (x.is1ValueRangeInfo()) {
// equality lookup for (auto const& x : _ranges) {
cost /= 100.0; double cost = static_cast<double>(docCount) * incoming;
continue;
for (auto const& y : x) { //only doing the 1-d case so far
if (y.is1ValueRangeInfo()) {
// equality lookup
cost /= EqualityReductionFactor;
continue;
}
bool hasLowerBound = false;
bool hasUpperBound = false;
if (y._lowConst.isDefined() || y._lows.size() > 0) {
hasLowerBound = true;
}
if (y._highConst.isDefined() || y._highs.size() > 0) {
hasUpperBound = true;
}
if (hasLowerBound && hasUpperBound) {
// both lower and upper bounds defined
cost /= 10.0;
}
else if (hasLowerBound || hasUpperBound) {
// either only low or high bound defined
cost /= 2.0;
}
// each bound (const and dynamic) counts!
size_t const numBounds = y._lows.size() +
y._highs.size() +
(y._lowConst.isDefined() ? 1 : 0) +
(y._highConst.isDefined() ? 1 : 0);
for (size_t j = 0; j < numBounds; ++j) {
// each dynamic bound again reduces the cost
cost *= 0.95;
}
} }
bool hasLowerBound = false; totalCost += cost;
bool hasUpperBound = false;
if (x._lowConst.isDefined() || x._lows.size() > 0) {
hasLowerBound = true;
}
if (x._highConst.isDefined() || x._highs.size() > 0) {
hasUpperBound = true;
}
if (hasLowerBound && hasUpperBound) {
// both lower and upper bounds defined
cost /= 10.0;
}
else if (hasLowerBound || hasUpperBound) {
// either only low or high bound defined
cost /= 2.0;
}
// each bound (const and dynamic) counts!
size_t const numBounds = x._lows.size() +
x._highs.size() +
(x._lowConst.isDefined() ? 1 : 0) +
(x._highConst.isDefined() ? 1 : 0);
for (size_t j = 0; j < numBounds; ++j) {
// each dynamic bound again reduces the cost
cost *= 0.95;
}
} }
nrItems = static_cast<size_t>(cost); nrItems = static_cast<size_t>(totalCost);
return dependencyCost + cost; return dependencyCost + totalCost;
} }
// no index // no index

View File

@ -1333,7 +1333,6 @@ class FilterToEnumCollFinder : public WalkerWorker<ExecutionNode> {
// results), for example // results), for example
// x.a == 1 || y.c == 2 || x.a == 3 // x.a == 1 || y.c == 2 || x.a == 3
if (_rangeInfoMapVec->isMapped(var->name)) { if (_rangeInfoMapVec->isMapped(var->name)) {
std::vector<size_t> const validPos = _rangeInfoMapVec->validPositions(var->name); std::vector<size_t> const validPos = _rangeInfoMapVec->validPositions(var->name);
// are any of the RangeInfoMaps in the vector valid? // are any of the RangeInfoMaps in the vector valid?
@ -1373,14 +1372,14 @@ class FilterToEnumCollFinder : public WalkerWorker<ExecutionNode> {
// index or == followed by a single <, >, >=, or <= // index or == followed by a single <, >, >=, or <=
// if a skip index in the order of the fields of the // if a skip index in the order of the fields of the
// index. // index.
auto idx = idxs.at(i); auto const idx = idxs.at(i);
TRI_ASSERT(idx != nullptr); TRI_ASSERT(idx != nullptr);
if (idx->type == TRI_IDX_TYPE_PRIMARY_INDEX) { if (idx->type == TRI_IDX_TYPE_PRIMARY_INDEX) {
for (size_t k = 0; k < validPos.size(); k++) { for (size_t k = 0; k < validPos.size(); k++) {
bool handled = false; bool handled = false;
auto map = _rangeInfoMapVec->find(var->name, validPos[k]); auto const map = _rangeInfoMapVec->find(var->name, validPos[k]);
auto range = map->find(std::string(TRI_VOC_ATTRIBUTE_ID)); auto range = map->find(std::string(TRI_VOC_ATTRIBUTE_ID));
if (range != map->end()) { if (range != map->end()) {
@ -1412,7 +1411,7 @@ class FilterToEnumCollFinder : public WalkerWorker<ExecutionNode> {
else if (idx->type == TRI_IDX_TYPE_HASH_INDEX) { else if (idx->type == TRI_IDX_TYPE_HASH_INDEX) {
//each valid orCondition should match every field of the given index //each valid orCondition should match every field of the given index
for (size_t k = 0; k < validPos.size() && !indexOrCondition.empty(); k++) { for (size_t k = 0; k < validPos.size() && !indexOrCondition.empty(); k++) {
auto map = _rangeInfoMapVec->find(var->name, validPos[k]); auto const map = _rangeInfoMapVec->find(var->name, validPos[k]);
for (size_t j = 0; j < idx->fields.size(); j++) { for (size_t j = 0; j < idx->fields.size(); j++) {
auto range = map->find(idx->fields[j]); auto range = map->find(idx->fields[j]);
@ -1429,7 +1428,7 @@ class FilterToEnumCollFinder : public WalkerWorker<ExecutionNode> {
else if (idx->type == TRI_IDX_TYPE_EDGE_INDEX) { else if (idx->type == TRI_IDX_TYPE_EDGE_INDEX) {
for (size_t k = 0; k < validPos.size(); k++) { for (size_t k = 0; k < validPos.size(); k++) {
bool handled = false; bool handled = false;
auto map = _rangeInfoMapVec->find(var->name, validPos[k]); auto const map = _rangeInfoMapVec->find(var->name, validPos[k]);
auto range = map->find(std::string(TRI_VOC_ATTRIBUTE_FROM)); auto range = map->find(std::string(TRI_VOC_ATTRIBUTE_FROM));
if (range != map->end()) { if (range != map->end()) {
if (! range->second.is1ValueRangeInfo()) { if (! range->second.is1ValueRangeInfo()) {
@ -1459,18 +1458,22 @@ class FilterToEnumCollFinder : public WalkerWorker<ExecutionNode> {
} }
else if (idx->type == TRI_IDX_TYPE_SKIPLIST_INDEX) { else if (idx->type == TRI_IDX_TYPE_SKIPLIST_INDEX) {
for (size_t k = 0; k < validPos.size(); k++) { for (size_t k = 0; k < validPos.size(); k++) {
auto map = _rangeInfoMapVec->find(var->name, validPos[k]); auto const map = _rangeInfoMapVec->find(var->name, validPos[k]);
size_t j = 0; // check if there is a range that contains the first index attribute
auto range = map->find(idx->fields[0]); auto range = map->find(idx->fields[0]);
if (range == map->end()) { if (range == map->end()) {
indexOrCondition.clear(); indexOrCondition.clear();
break; // not usable break; // not usable
} }
// insert the first index attribute
indexOrCondition.at(k).push_back(range->second); indexOrCondition.at(k).push_back(range->second);
// iterate over all index attributes from left to right
bool equality = range->second.is1ValueRangeInfo(); bool equality = range->second.is1ValueRangeInfo();
bool handled = false; bool handled = false;
size_t j = 0;
while (++j < prefixes.at(i) && equality) { while (++j < prefixes.at(i) && equality) {
range = map->find(idx->fields[j]); range = map->find(idx->fields[j]);
if (range == map->end()) { if (range == map->end()) {
@ -1481,6 +1484,7 @@ class FilterToEnumCollFinder : public WalkerWorker<ExecutionNode> {
indexOrCondition.at(k).push_back(range->second); indexOrCondition.at(k).push_back(range->second);
equality = equality && range->second.is1ValueRangeInfo(); equality = equality && range->second.is1ValueRangeInfo();
} }
if (handled) { if (handled) {
// exit the for loop, too. otherwise it will crash because // exit the for loop, too. otherwise it will crash because
// indexOrCondition is empty now // indexOrCondition is empty now

View File

@ -1,5 +1,5 @@
/*jshint strict: false, maxlen: 500 */ /*jshint strict: false, maxlen: 500 */
/*global require, assertTrue, assertEqual, assertNotEqual, AQL_EXPLAIN, AQL_EXECUTE */ /*global require, assertTrue, assertFalse, assertEqual, assertNotEqual, AQL_EXPLAIN, AQL_EXECUTE */
//////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////
/// @brief tests for index usage /// @brief tests for index usage
@ -544,7 +544,241 @@ function optimizerIndexesTestSuite () {
/// @brief test index usage /// @brief test index usage
//////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////
testIndexOr : function () { testSortWithMultipleIndexes : function () {
AQL_EXECUTE("FOR i IN " + c.name() + " UPDATE i WITH { value2: i.value } IN " + c.name());
// create multiple indexes
c.ensureHashIndex("value");
c.ensureHashIndex("value", "value2");
c.ensureSkiplist("value", "value2");
var query = "FOR i IN " + c.name() + " FILTER i.value == 1 SORT i.value RETURN i.value";
var plan = AQL_EXPLAIN(query).plan;
var nodeTypes = plan.nodes.map(function(node) {
return node.type;
});
assertNotEqual(-1, nodeTypes.indexOf("IndexRangeNode"), query);
assertEqual(-1, nodeTypes.indexOf("SortNode"), query);
var results = AQL_EXECUTE(query);
assertEqual([ 1 ], results.json, query);
assertEqual(0, results.stats.scannedFull);
assertEqual(1, results.stats.scannedIndex);
},
////////////////////////////////////////////////////////////////////////////////
/// @brief test index usage
////////////////////////////////////////////////////////////////////////////////
testSortWithMultipleIndexesAndRanges : function () {
AQL_EXECUTE("FOR i IN " + c.name() + " UPDATE i WITH { value2: i.value } IN " + c.name());
// create multiple indexes
c.ensureHashIndex("value");
c.ensureHashIndex("value", "value2");
c.ensureSkiplist("value", "value2");
var query = "FOR i IN " + c.name() + " FILTER i.value == 9 || i.value == 1 SORT i.value RETURN i.value";
var plan = AQL_EXPLAIN(query).plan;
var nodeTypes = plan.nodes.map(function(node) {
return node.type;
});
assertNotEqual(-1, nodeTypes.indexOf("IndexRangeNode"), query);
assertEqual(-1, nodeTypes.indexOf("SortNode"), query);
var results = AQL_EXECUTE(query);
assertEqual([ 1, 9 ], results.json, query);
assertEqual(0, results.stats.scannedFull);
assertEqual(2, results.stats.scannedIndex);
},
////////////////////////////////////////////////////////////////////////////////
/// @brief test index usage
////////////////////////////////////////////////////////////////////////////////
testMultiSortWithMultipleIndexes : function () {
AQL_EXECUTE("FOR i IN " + c.name() + " UPDATE i WITH { value2: i.value } IN " + c.name());
// create multiple indexes
c.ensureHashIndex("value");
c.ensureHashIndex("value", "value2");
c.ensureSkiplist("value", "value2");
var query = "FOR i IN " + c.name() + " FILTER i.value == 1 SORT i.value, i.value2 RETURN i.value";
var plan = AQL_EXPLAIN(query).plan;
var nodeTypes = plan.nodes.map(function(node) {
return node.type;
});
assertNotEqual(-1, nodeTypes.indexOf("IndexRangeNode"), query);
assertEqual(-1, nodeTypes.indexOf("SortNode"), query);
var results = AQL_EXECUTE(query);
assertEqual([ 1 ], results.json, query);
assertEqual(0, results.stats.scannedFull);
assertEqual(1, results.stats.scannedIndex);
},
////////////////////////////////////////////////////////////////////////////////
/// @brief test index usage
////////////////////////////////////////////////////////////////////////////////
testMultiSortWithMultipleIndexesAndRanges : function () {
AQL_EXECUTE("FOR i IN " + c.name() + " UPDATE i WITH { value2: i.value } IN " + c.name());
// create multiple indexes
c.ensureHashIndex("value");
c.ensureHashIndex("value", "value2");
c.ensureSkiplist("value", "value2");
var query = "FOR i IN " + c.name() + " FILTER i.value == 9 || i.value == 1 SORT i.value, i.value2 RETURN i.value";
var plan = AQL_EXPLAIN(query).plan;
var nodeTypes = plan.nodes.map(function(node) {
return node.type;
});
assertNotEqual(-1, nodeTypes.indexOf("IndexRangeNode"), query);
assertEqual(-1, nodeTypes.indexOf("SortNode"), query);
var results = AQL_EXECUTE(query);
assertEqual([ 1, 9 ], results.json, query);
assertEqual(0, results.stats.scannedFull);
assertEqual(2, results.stats.scannedIndex);
},
////////////////////////////////////////////////////////////////////////////////
/// @brief test index usage
////////////////////////////////////////////////////////////////////////////////
testIndexOrPrimary : function () {
var query = "FOR i IN " + c.name() + " FILTER i._key == 'test1' || i._key == 'test9' RETURN i.value";
var plan = AQL_EXPLAIN(query).plan;
var nodeTypes = plan.nodes.map(function(node) {
if (node.type === "IndexRangeNode") {
assertEqual("primary", node.index.type);
}
return node.type;
});
assertNotEqual(-1, nodeTypes.indexOf("IndexRangeNode"), query);
var results = AQL_EXECUTE(query);
assertEqual([ 1, 9 ], results.json.sort(), query);
assertEqual(2, results.stats.scannedIndex);
assertEqual(0, results.stats.scannedFull);
},
////////////////////////////////////////////////////////////////////////////////
/// @brief test index usage
////////////////////////////////////////////////////////////////////////////////
testIndexOrHash : function () {
c.ensureHashIndex("value");
var query = "FOR i IN " + c.name() + " FILTER i.value == 1 || i.value == 9 RETURN i.value";
var plan = AQL_EXPLAIN(query).plan;
var nodeTypes = plan.nodes.map(function(node) {
if (node.type === "IndexRangeNode") {
assertEqual("hash", node.index.type);
assertFalse(node.index.unique);
}
return node.type;
});
assertNotEqual(-1, nodeTypes.indexOf("IndexRangeNode"), query);
var results = AQL_EXECUTE(query);
assertEqual([ 1, 9 ], results.json.sort(), query);
assertEqual(2, results.stats.scannedIndex);
assertEqual(0, results.stats.scannedFull);
},
////////////////////////////////////////////////////////////////////////////////
/// @brief test index usage
////////////////////////////////////////////////////////////////////////////////
testIndexOrUniqueHash : function () {
c.ensureUniqueConstraint("value");
var query = "FOR i IN " + c.name() + " FILTER i.value == 1 || i.value == 9 RETURN i.value";
var plan = AQL_EXPLAIN(query).plan;
var nodeTypes = plan.nodes.map(function(node) {
if (node.type === "IndexRangeNode") {
assertEqual("hash", node.index.type);
assertTrue(node.index.unique);
}
return node.type;
});
assertNotEqual(-1, nodeTypes.indexOf("IndexRangeNode"), query);
var results = AQL_EXECUTE(query);
assertEqual([ 1, 9 ], results.json.sort(), query);
assertEqual(2, results.stats.scannedIndex);
assertEqual(0, results.stats.scannedFull);
},
////////////////////////////////////////////////////////////////////////////////
/// @brief test index usage
////////////////////////////////////////////////////////////////////////////////
testIndexOrSkiplist : function () {
var query = "FOR i IN " + c.name() + " FILTER i.value == 1 || i.value == 9 RETURN i.value";
var plan = AQL_EXPLAIN(query).plan;
var nodeTypes = plan.nodes.map(function(node) {
if (node.type === "IndexRangeNode") {
assertEqual("skiplist", node.index.type);
assertFalse(node.index.unique);
}
return node.type;
});
assertNotEqual(-1, nodeTypes.indexOf("IndexRangeNode"), query);
var results = AQL_EXECUTE(query);
assertEqual([ 1, 9 ], results.json.sort(), query);
assertEqual(2, results.stats.scannedIndex);
assertEqual(0, results.stats.scannedFull);
},
////////////////////////////////////////////////////////////////////////////////
/// @brief test index usage
////////////////////////////////////////////////////////////////////////////////
testIndexOrUniqueSkiplist : function () {
c.ensureUniqueSkiplist("value");
var query = "FOR i IN " + c.name() + " FILTER i.value == 1 || i.value == 9 RETURN i.value";
var plan = AQL_EXPLAIN(query).plan;
var nodeTypes = plan.nodes.map(function(node) {
if (node.type === "IndexRangeNode") {
assertEqual("skiplist", node.index.type);
assertTrue(node.index.unique);
}
return node.type;
});
assertNotEqual(-1, nodeTypes.indexOf("IndexRangeNode"), query);
var results = AQL_EXECUTE(query);
assertEqual([ 1, 9 ], results.json.sort(), query);
assertEqual(2, results.stats.scannedIndex);
assertEqual(0, results.stats.scannedFull);
},
////////////////////////////////////////////////////////////////////////////////
/// @brief test index usage
////////////////////////////////////////////////////////////////////////////////
testIndexOrNoIndex : function () {
c.ensureSkiplist("value2"); c.ensureSkiplist("value2");
AQL_EXECUTE("FOR i IN " + c.name() + " UPDATE i WITH { value2: i.value } IN " + c.name()); AQL_EXECUTE("FOR i IN " + c.name() + " UPDATE i WITH { value2: i.value } IN " + c.name());
@ -555,10 +789,7 @@ function optimizerIndexesTestSuite () {
return node.type; return node.type;
}); });
assertEqual("SingletonNode", nodeTypes[0], query);
assertEqual(-1, nodeTypes.indexOf("IndexRangeNode"), query); assertEqual(-1, nodeTypes.indexOf("IndexRangeNode"), query);
assertEqual(-1, nodeTypes.indexOf("SortNode"), query);
assertEqual("ReturnNode", nodeTypes[nodeTypes.length - 1], query);
var results = AQL_EXECUTE(query); var results = AQL_EXECUTE(query);
assertEqual([ 1 ], results.json, query); assertEqual([ 1 ], results.json, query);
@ -570,7 +801,158 @@ function optimizerIndexesTestSuite () {
/// @brief test index usage /// @brief test index usage
//////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////
testIndexAnd : function () { testIndexOrHashMultipleRanges : function () {
c.ensureHashIndex("value2", "value3");
AQL_EXECUTE("FOR i IN " + c.name() + " UPDATE i WITH { value2: i.value, value3: i.value } IN " + c.name());
var query = "FOR i IN " + c.name() + " FILTER (i.value2 == 2 && i.value3 == 2) || (i.value2 == 3 && i.value3 == 3) RETURN i.value2";
var plan = AQL_EXPLAIN(query).plan;
var nodeTypes = plan.nodes.map(function(node) {
if (node.type === "IndexRangeNode") {
assertEqual("hash", node.index.type);
assertFalse(node.index.unique);
assertEqual([ "value2", "value3" ], node.index.fields);
}
return node.type;
});
assertNotEqual(-1, nodeTypes.indexOf("IndexRangeNode"), query);
var results = AQL_EXECUTE(query);
assertEqual([ 2, 3 ], results.json.sort(), query);
assertEqual(2, results.stats.scannedIndex);
assertEqual(0, results.stats.scannedFull);
},
////////////////////////////////////////////////////////////////////////////////
/// @brief test index usage
////////////////////////////////////////////////////////////////////////////////
testIndexOrSkiplistMultipleRanges : function () {
c.ensureSkiplist("value2", "value3");
AQL_EXECUTE("FOR i IN " + c.name() + " UPDATE i WITH { value2: i.value, value3: i.value } IN " + c.name());
var query = "FOR i IN " + c.name() + " FILTER (i.value2 == 2 && i.value3 == 2) || (i.value2 == 3 && i.value3 == 3) RETURN i.value2";
var plan = AQL_EXPLAIN(query).plan;
var nodeTypes = plan.nodes.map(function(node) {
if (node.type === "IndexRangeNode") {
assertEqual("skiplist", node.index.type);
assertFalse(node.index.unique);
assertEqual([ "value2", "value3" ], node.index.fields);
}
return node.type;
});
assertNotEqual(-1, nodeTypes.indexOf("IndexRangeNode"), query);
var results = AQL_EXECUTE(query);
assertEqual([ 2, 3 ], results.json.sort(), query);
assertEqual(2, results.stats.scannedIndex);
assertEqual(0, results.stats.scannedFull);
},
////////////////////////////////////////////////////////////////////////////////
/// @brief test index usage
////////////////////////////////////////////////////////////////////////////////
testIndexOrHashMultipleRangesPartialNoIndex1 : function () {
c.ensureHashIndex("value2", "value3");
AQL_EXECUTE("FOR i IN " + c.name() + " UPDATE i WITH { value2: i.value, value3: i.value } IN " + c.name());
var query = "FOR i IN " + c.name() + " FILTER (i.value2 == 1 && i.value3 == 1) || (i.value2 == 2) RETURN i.value2";
var plan = AQL_EXPLAIN(query).plan;
var nodeTypes = plan.nodes.map(function(node) {
return node.type;
});
assertEqual(-1, nodeTypes.indexOf("IndexRangeNode"), query);
var results = AQL_EXECUTE(query);
assertEqual([ 1, 2 ], results.json.sort(), query);
assertEqual(0, results.stats.scannedIndex);
assertTrue(results.stats.scannedFull > 0);
},
////////////////////////////////////////////////////////////////////////////////
/// @brief test index usage
////////////////////////////////////////////////////////////////////////////////
testIndexOrHashMultipleRangesPartialNoIndex2 : function () {
c.ensureHashIndex("value2", "value3");
AQL_EXECUTE("FOR i IN " + c.name() + " UPDATE i WITH { value2: i.value, value3: i.value } IN " + c.name());
var query = "FOR i IN " + c.name() + " FILTER (i.value2 == 1 && i.value3 == 1) || (i.value3 == 2) RETURN i.value2";
var plan = AQL_EXPLAIN(query).plan;
var nodeTypes = plan.nodes.map(function(node) {
return node.type;
});
assertEqual(-1, nodeTypes.indexOf("IndexRangeNode"), query);
var results = AQL_EXECUTE(query);
assertEqual([ 1, 2 ], results.json.sort(), query);
assertEqual(0, results.stats.scannedIndex);
assertTrue(results.stats.scannedFull > 0);
},
////////////////////////////////////////////////////////////////////////////////
/// @brief test index usage
////////////////////////////////////////////////////////////////////////////////
/* TODO
testIndexOrSkiplistMultipleRangesPartialIndex : function () {
c.ensureSkiplist("value2", "value3");
AQL_EXECUTE("FOR i IN " + c.name() + " UPDATE i WITH { value2: i.value, value3: i.value } IN " + c.name());
var query = "FOR i IN " + c.name() + " FILTER (i.value2 == 1 && i.value3 == 1) || (i.value2 == 2) RETURN i.value2";
var plan = AQL_EXPLAIN(query).plan;
var nodeTypes = plan.nodes.map(function(node) {
if (node.type === "IndexRangeNode") {
assertEqual("skiplist", node.index.type);
assertFalse(node.index.unique);
assertEqual([ "value2", "value3" ], node.index.fields);
}
return node.type;
});
assertNotEqual(-1, nodeTypes.indexOf("IndexRangeNode"), query);
var results = AQL_EXECUTE(query);
assertEqual([ 1, 2 ], results.json.sort(), query);
assertEqual(2, results.stats.scannedIndex);
assertEqual(0, results.stats.scannedFull);
},
*/
////////////////////////////////////////////////////////////////////////////////
/// @brief test index usage
////////////////////////////////////////////////////////////////////////////////
testIndexOrSkiplistMultipleRangesPartialNoIndex : function () {
c.ensureSkiplist("value2", "value3");
AQL_EXECUTE("FOR i IN " + c.name() + " UPDATE i WITH { value2: i.value, value3: i.value } IN " + c.name());
var query = "FOR i IN " + c.name() + " FILTER (i.value2 == 1 && i.value3 == 1) || (i.value3 == 2) RETURN i.value2";
var plan = AQL_EXPLAIN(query).plan;
var nodeTypes = plan.nodes.map(function(node) {
return node.type;
});
assertEqual(-1, nodeTypes.indexOf("IndexRangeNode"), query);
var results = AQL_EXECUTE(query);
assertEqual([ 1, 2 ], results.json.sort(), query);
assertEqual(0, results.stats.scannedIndex);
assertTrue(results.stats.scannedFull > 0);
},
////////////////////////////////////////////////////////////////////////////////
/// @brief test index usage
////////////////////////////////////////////////////////////////////////////////
testIndexAndNoIndex : function () {
c.ensureSkiplist("value2"); c.ensureSkiplist("value2");
AQL_EXECUTE("FOR i IN " + c.name() + " UPDATE i WITH { value2: i.value } IN " + c.name()); AQL_EXECUTE("FOR i IN " + c.name() + " UPDATE i WITH { value2: i.value } IN " + c.name());
@ -581,16 +963,13 @@ function optimizerIndexesTestSuite () {
return node.type; return node.type;
}); });
assertEqual("SingletonNode", nodeTypes[0], query);
assertNotEqual(-1, nodeTypes.indexOf("IndexRangeNode"), query); assertNotEqual(-1, nodeTypes.indexOf("IndexRangeNode"), query);
assertEqual(-1, nodeTypes.indexOf("SortNode"), query);
assertEqual("ReturnNode", nodeTypes[nodeTypes.length - 1], query);
var results = AQL_EXECUTE(query); var results = AQL_EXECUTE(query);
assertEqual([ 1 ], results.json, query); assertEqual([ 1 ], results.json, query);
assertEqual(0, results.stats.scannedFull); assertEqual(0, results.stats.scannedFull);
assertTrue(results.stats.scannedIndex > 0); assertTrue(results.stats.scannedIndex > 0);
}, }
}; };
} }