mirror of https://gitee.com/bigwinds/arangodb
Merge branch 'aql2' of https://github.com/triAGENS/ArangoDB into aql2
This commit is contained in:
commit
7b3ee1e452
|
@ -110,7 +110,21 @@ void AqlItemBlock::shrink (size_t nrItems) {
|
|||
// erase all stored values in the region that we freed
|
||||
for (size_t i = nrItems; i < _nrItems; ++i) {
|
||||
for (RegisterId j = 0; j < _nrRegs; ++j) {
|
||||
eraseValue(i, j);
|
||||
AqlValue& a(_data[_nrRegs * i + j]);
|
||||
if (! a.isEmpty()) {
|
||||
auto it = _valueCount.find(a);
|
||||
if (it != _valueCount.end()) {
|
||||
if (--it->second == 0) {
|
||||
a.destroy();
|
||||
try {
|
||||
_valueCount.erase(it);
|
||||
}
|
||||
catch (...) {
|
||||
}
|
||||
}
|
||||
}
|
||||
a.erase();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -131,12 +145,11 @@ void AqlItemBlock::clearRegisters (std::unordered_set<RegisterId>& toClear) {
|
|||
auto it = _valueCount.find(a);
|
||||
if (it != _valueCount.end()) {
|
||||
if (--it->second == 0) {
|
||||
a.destroy();
|
||||
try {
|
||||
_valueCount.erase(it);
|
||||
}
|
||||
catch (...) {
|
||||
it->second++;
|
||||
throw;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -130,8 +130,6 @@ namespace triagens {
|
|||
_valueCount.erase(it);
|
||||
}
|
||||
catch (...) {
|
||||
it->second++;
|
||||
throw;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -444,7 +444,7 @@ Json AqlValue::toJson (AQL_TRANSACTION_V8* trx,
|
|||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
/// @brief extract an attribute value from the AqlValue
|
||||
/// this will return null if the value is not an array
|
||||
/// this will return an empty Json if the value is not an array
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
Json AqlValue::extractArrayMember (AQL_TRANSACTION_V8* trx,
|
||||
|
|
|
@ -1023,7 +1023,7 @@ void IndexRangeBlock::readSkiplistIndex () {
|
|||
skiplistOperator = TRI_CreateIndexOperator(TRI_EQ_INDEX_OPERATOR, nullptr,
|
||||
nullptr, parameters.copy().steal(), shaper, nullptr, i, nullptr);
|
||||
}
|
||||
if (range._low._undefined) {
|
||||
if (!range._low._undefined) {
|
||||
auto op = range._low.toIndexOperator(false, parameters.copy(), shaper);
|
||||
if (skiplistOperator != nullptr) {
|
||||
skiplistOperator = TRI_CreateIndexOperator(TRI_AND_INDEX_OPERATOR,
|
||||
|
@ -1033,7 +1033,7 @@ void IndexRangeBlock::readSkiplistIndex () {
|
|||
skiplistOperator = op;
|
||||
}
|
||||
}
|
||||
if (range._high._undefined) {
|
||||
if (!range._high._undefined) {
|
||||
auto op = range._high.toIndexOperator(true, parameters.copy(), shaper);
|
||||
if (skiplistOperator != nullptr) {
|
||||
skiplistOperator = TRI_CreateIndexOperator(TRI_AND_INDEX_OPERATOR,
|
||||
|
|
|
@ -263,7 +263,7 @@ ExecutionNode::CompareIndex (TRI_index_t* idx,
|
|||
return match;
|
||||
}
|
||||
|
||||
match.fullmatch = idx->_fields._length == attrs.size();
|
||||
match.fullmatch = idx->_fields._length >= attrs.size();
|
||||
|
||||
size_t interestingCount = 0;
|
||||
size_t j = 0;
|
||||
|
@ -583,11 +583,12 @@ void IndexRangeNode::toJsonHelper (triagens::basics::Json& nodes,
|
|||
TRI_json_t* idxJson = _index->json(_index);
|
||||
if (idxJson != nullptr) {
|
||||
try {
|
||||
json.set("index", Json(TRI_UNKNOWN_MEM_ZONE, TRI_CopyJson(TRI_UNKNOWN_MEM_ZONE, idxJson)));
|
||||
TRI_json_t* copy = TRI_CopyJson(TRI_UNKNOWN_MEM_ZONE, idxJson);
|
||||
json.set("index", Json(TRI_UNKNOWN_MEM_ZONE, copy));
|
||||
}
|
||||
catch (...) {
|
||||
}
|
||||
TRI_Free(TRI_CORE_MEM_ZONE, idxJson);
|
||||
TRI_FreeJson(TRI_CORE_MEM_ZONE, idxJson);
|
||||
}
|
||||
|
||||
// And add it:
|
||||
|
|
|
@ -107,6 +107,7 @@ namespace triagens {
|
|||
ExecutionNode (size_t id)
|
||||
: _id(id),
|
||||
_estimatedCost(0.0),
|
||||
_estimatedCostSet(false),
|
||||
_varUsageValid(false) {
|
||||
}
|
||||
|
||||
|
@ -353,8 +354,9 @@ namespace triagens {
|
|||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
double getCost () {
|
||||
if (_estimatedCost == 0.0) {
|
||||
if (! _estimatedCostSet) {
|
||||
_estimatedCost = estimateCost();
|
||||
_estimatedCostSet = true;
|
||||
TRI_ASSERT(_estimatedCost >= 0.0);
|
||||
}
|
||||
return _estimatedCost;
|
||||
|
@ -517,10 +519,12 @@ namespace triagens {
|
|||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
/// @brief _estimatedCost = 0 if uninitialised and otherwise stores the result
|
||||
/// of estimateCost()
|
||||
/// of estimateCost(), the bool indicates if the cost has been set, it starts
|
||||
/// out as false
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
double _estimatedCost;
|
||||
bool _estimatedCostSet;
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
/// @brief _varsUsedLater and _varsValid, the former contains those
|
||||
|
@ -1424,23 +1428,31 @@ namespace triagens {
|
|||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
struct SortInformation {
|
||||
|
||||
enum Match {
|
||||
unequal,
|
||||
weSupersede,
|
||||
otherSupersedes,
|
||||
allEqual
|
||||
};
|
||||
|
||||
std::vector<std::tuple<ExecutionNode const*, std::string, bool>> criteria;
|
||||
bool isValid = true;
|
||||
bool isComplex = false;
|
||||
|
||||
bool isCoveredBy (SortInformation const& other) {
|
||||
Match isCoveredBy (SortInformation const& other) {
|
||||
if (! isValid || ! other.isValid) {
|
||||
return false;
|
||||
return unequal;
|
||||
}
|
||||
|
||||
if (isComplex) {
|
||||
return false;
|
||||
return unequal;
|
||||
}
|
||||
|
||||
size_t const n = criteria.size();
|
||||
for (size_t i = 0; i < n; ++i) {
|
||||
if (other.criteria.size() < i) {
|
||||
return false;
|
||||
if (other.criteria.size() <= i) {
|
||||
return otherSupersedes;
|
||||
}
|
||||
|
||||
auto ours = criteria[i];
|
||||
|
@ -1448,16 +1460,18 @@ namespace triagens {
|
|||
|
||||
if (std::get<2>(ours) != std::get<2>(theirs)) {
|
||||
// sort order is different
|
||||
return false;
|
||||
return unequal;
|
||||
}
|
||||
|
||||
if (std::get<1>(ours) != std::get<1>(theirs)) {
|
||||
// sort criterion is different
|
||||
return false;
|
||||
return unequal;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
if (other.criteria.size() > n)
|
||||
return weSupersede;
|
||||
else
|
||||
return allEqual;
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -1525,7 +1539,12 @@ namespace triagens {
|
|||
|
||||
double estimateCost () {
|
||||
double depCost = _dependencies.at(0)->getCost();
|
||||
return log(depCost) * depCost;
|
||||
if (depCost <= 2.0) {
|
||||
return depCost;
|
||||
}
|
||||
else {
|
||||
return log(depCost) * depCost;
|
||||
}
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
|
|
@ -184,7 +184,8 @@ void Expression::analyzeExpression () {
|
|||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
/// @brief execute an expression of type SIMPLE
|
||||
/// @brief execute an expression of type SIMPLE, the convention is that
|
||||
/// the resulting AqlValue will be destroyed outside eventually
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
AqlValue Expression::executeSimpleExpression (AstNode const* node,
|
||||
|
@ -207,6 +208,7 @@ AqlValue Expression::executeSimpleExpression (AstNode const* node,
|
|||
AqlValue result = executeSimpleExpression(member, &myCollection, trx, docColls, argv, startPos, vars, regs);
|
||||
|
||||
auto j = result.extractArrayMember(trx, myCollection, name);
|
||||
result.destroy();
|
||||
return AqlValue(new Json(TRI_UNKNOWN_MEM_ZONE, j.steal()));
|
||||
}
|
||||
|
||||
|
@ -228,6 +230,7 @@ AqlValue Expression::executeSimpleExpression (AstNode const* node,
|
|||
if (result.isList()) {
|
||||
if (index->isNumericValue()) {
|
||||
auto j = result.extractListMember(trx, myCollection, index->getIntValue());
|
||||
result.destroy();
|
||||
return AqlValue(new Json(TRI_UNKNOWN_MEM_ZONE, j.steal()));
|
||||
}
|
||||
else if (index->isStringValue()) {
|
||||
|
@ -238,6 +241,7 @@ AqlValue Expression::executeSimpleExpression (AstNode const* node,
|
|||
// stoll() might throw an exception if the string is not a number
|
||||
int64_t position = static_cast<int64_t>(std::stoll(p));
|
||||
auto j = result.extractListMember(trx, myCollection, position);
|
||||
result.destroy();
|
||||
return AqlValue(new Json(TRI_UNKNOWN_MEM_ZONE, j.steal()));
|
||||
}
|
||||
catch (...) {
|
||||
|
@ -250,6 +254,7 @@ AqlValue Expression::executeSimpleExpression (AstNode const* node,
|
|||
if (index->isNumericValue()) {
|
||||
std::string const indexString = std::to_string(index->getIntValue());
|
||||
auto j = result.extractArrayMember(trx, myCollection, indexString.c_str());
|
||||
result.destroy();
|
||||
return AqlValue(new Json(TRI_UNKNOWN_MEM_ZONE, j.steal()));
|
||||
}
|
||||
else if (index->isStringValue()) {
|
||||
|
@ -257,10 +262,12 @@ AqlValue Expression::executeSimpleExpression (AstNode const* node,
|
|||
TRI_ASSERT(p != nullptr);
|
||||
|
||||
auto j = result.extractArrayMember(trx, myCollection, p);
|
||||
result.destroy();
|
||||
return AqlValue(new Json(TRI_UNKNOWN_MEM_ZONE, j.steal()));
|
||||
}
|
||||
// fall-through to returning null
|
||||
}
|
||||
result.destroy();
|
||||
|
||||
return AqlValue(new Json(Json::Null));
|
||||
}
|
||||
|
@ -276,6 +283,7 @@ AqlValue Expression::executeSimpleExpression (AstNode const* node,
|
|||
|
||||
AqlValue result = executeSimpleExpression(member, &myCollection, trx, docColls, argv, startPos, vars, regs);
|
||||
list->add(result.toJson(trx, myCollection));
|
||||
result.destroy();
|
||||
}
|
||||
return AqlValue(list);
|
||||
}
|
||||
|
@ -300,6 +308,7 @@ AqlValue Expression::executeSimpleExpression (AstNode const* node,
|
|||
|
||||
AqlValue result = executeSimpleExpression(member, &myCollection, trx, docColls, argv, startPos, vars, regs);
|
||||
resultArray->set(key, result.toJson(trx, myCollection));
|
||||
result.destroy();
|
||||
}
|
||||
return AqlValue(resultArray);
|
||||
}
|
||||
|
@ -319,7 +328,7 @@ AqlValue Expression::executeSimpleExpression (AstNode const* node,
|
|||
|
||||
// save the collection info
|
||||
*collection = docColls[regs[i]];
|
||||
return argv[startPos + regs[i]];
|
||||
return argv[startPos + regs[i]].clone();
|
||||
}
|
||||
}
|
||||
// fall-through to exception
|
||||
|
@ -344,7 +353,9 @@ AqlValue Expression::executeSimpleExpression (AstNode const* node,
|
|||
auto member = node->getMember(0);
|
||||
AqlValue result = executeSimpleExpression(member, &myCollection, trx, docColls, argv, startPos, vars, regs);
|
||||
|
||||
return func->implementation(trx, myCollection, result);
|
||||
auto res2 = func->implementation(trx, myCollection, result);
|
||||
result.destroy();
|
||||
return res2;
|
||||
}
|
||||
|
||||
THROW_ARANGO_EXCEPTION_MESSAGE(TRI_ERROR_INTERNAL, "unhandled type in simple expression");
|
||||
|
|
|
@ -69,7 +69,14 @@ int triagens::aql::removeRedundantSorts (Optimizer* opt,
|
|||
// we found another sort. now check if they are compatible!
|
||||
auto other = static_cast<SortNode*>(current)->getSortInformation(plan);
|
||||
|
||||
if (sortInfo.isCoveredBy(other)) {
|
||||
switch (sortInfo.isCoveredBy(other)) {
|
||||
case triagens::aql::SortInformation::unequal:
|
||||
break;
|
||||
case triagens::aql::SortInformation::otherSupersedes:
|
||||
toUnlink.insert(current);
|
||||
break;
|
||||
case triagens::aql::SortInformation::weSupersede:
|
||||
case triagens::aql::SortInformation::allEqual:
|
||||
// the sort at the start of the pipeline makes the sort at the end
|
||||
// superfluous, so we'll remove it
|
||||
toUnlink.insert(n);
|
||||
|
@ -645,6 +652,12 @@ class FilterToEnumCollFinder : public WalkerWorker<ExecutionNode> {
|
|||
buildRangeInfo(node->getMember(0), enumCollVar, attr);
|
||||
buildRangeInfo(node->getMember(1), enumCollVar, attr);
|
||||
}
|
||||
/* TODO: or isn't implemented yet.
|
||||
if (node->type == NODE_TYPE_OPERATOR_BINARY_OR) {
|
||||
buildRangeInfo(node->getMember(0), enumCollVar, attr);
|
||||
buildRangeInfo(node->getMember(1), enumCollVar, attr);
|
||||
}
|
||||
*/
|
||||
attr = "";
|
||||
enumCollVar = "";
|
||||
return;
|
||||
|
@ -787,13 +800,13 @@ public:
|
|||
/// @brief removes the sortNode and its referenced Calculationnodes from the plan.
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
void removeSortNodeFromPlan (ExecutionPlan *newPlan) {
|
||||
newPlan->unlinkNode(newPlan->getNodeById(sortNodeID));
|
||||
|
||||
for (auto idToRemove = _sortNodeData.begin();
|
||||
idToRemove != _sortNodeData.end();
|
||||
++idToRemove) {
|
||||
newPlan->unlinkNode(newPlan->getNodeById((*idToRemove)->calculationNodeID));
|
||||
}
|
||||
|
||||
newPlan->unlinkNode(newPlan->getNodeById(sortNodeID));
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -817,7 +830,7 @@ class sortToIndexNode : public WalkerWorker<ExecutionNode> {
|
|||
_plan(plan),
|
||||
_sortNode(Node),
|
||||
_level(level) {
|
||||
planModified = true;
|
||||
planModified = false;
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
|
|
@ -178,15 +178,35 @@ void Parser::registerParseError (int errorCode,
|
|||
// extract the query string part where the error happened
|
||||
std::string const region(_query->extractRegion(line, column));
|
||||
|
||||
// create a neat pointer to the location of the error.
|
||||
auto arrowpointer = (char*) malloc (sizeof(char) * (column + 10) );
|
||||
size_t i;
|
||||
for (i = 0; i < (size_t) column; i++) {
|
||||
arrowpointer[i] = ' ';
|
||||
}
|
||||
if (i > 0) {
|
||||
i --;
|
||||
arrowpointer[i++] = '^';
|
||||
}
|
||||
|
||||
arrowpointer[i++] = '^';
|
||||
arrowpointer[i++] = '^';
|
||||
arrowpointer[i++] = '\0';
|
||||
|
||||
|
||||
// note: line numbers reported by bison/flex start at 1, columns start at 0
|
||||
char buffer[512];
|
||||
snprintf(buffer,
|
||||
sizeof(buffer),
|
||||
"%s near '%s' at position %d:%d",
|
||||
"%s near '%s' at position %d:%d:\n%s\n%s\n",
|
||||
data,
|
||||
region.c_str(),
|
||||
line,
|
||||
column + 1);
|
||||
column + 1,
|
||||
_query->queryString(),
|
||||
arrowpointer);
|
||||
|
||||
free(arrowpointer);
|
||||
|
||||
registerError(errorCode, buffer);
|
||||
}
|
||||
|
|
|
@ -84,11 +84,13 @@ namespace triagens {
|
|||
void assign (AstNode const* bound, bool include) {
|
||||
_include = include;
|
||||
_bound = Json(TRI_UNKNOWN_MEM_ZONE, bound->toJson(TRI_UNKNOWN_MEM_ZONE, true));
|
||||
_undefined = false;
|
||||
}
|
||||
|
||||
void assign (RangeInfoBound copy) {
|
||||
_include = copy._include;
|
||||
_bound = copy._bound;
|
||||
_undefined = false;
|
||||
}
|
||||
|
||||
Json toJson () const {
|
||||
|
@ -154,7 +156,7 @@ namespace triagens {
|
|||
RangeInfo () : _valid(false), _undefined(true) {}
|
||||
|
||||
RangeInfo (basics::Json const& json) :
|
||||
_var(basics::JsonHelper::checkAndGetStringValue(json.json(), "var")),
|
||||
_var(basics::JsonHelper::checkAndGetStringValue(json.json(), "variable")),
|
||||
_attr(basics::JsonHelper::checkAndGetStringValue(json.json(), "attr")),
|
||||
_valid(basics::JsonHelper::checkAndGetBooleanValue(json.json(), "valid")),
|
||||
_undefined(false) {
|
||||
|
@ -178,7 +180,7 @@ namespace triagens {
|
|||
|
||||
Json toJson () {
|
||||
Json item(basics::Json::Array);
|
||||
item("var", Json(_var))("attr", Json(_attr));
|
||||
item("variable", Json(_var))("attr", Json(_attr));
|
||||
if(!_low._undefined){
|
||||
item("low", _low.toJson());
|
||||
}
|
||||
|
@ -254,7 +256,7 @@ namespace triagens {
|
|||
for (auto x : _ranges) {
|
||||
for (auto y: x.second){
|
||||
Json item(Json::Array);
|
||||
item("var", Json(x.first))
|
||||
item("variable", Json(x.first))
|
||||
("attribute name", Json(y.first))
|
||||
("range info", y.second.toJson());
|
||||
list(item);
|
||||
|
|
|
@ -425,10 +425,51 @@ function getCompactPlan (explainResult) {
|
|||
return out;
|
||||
}
|
||||
|
||||
function findExecutionNodes(plan, nodetype) {
|
||||
var matches = [];
|
||||
plan.plan.nodes.forEach(function(node) {
|
||||
if (node.type === nodetype) {
|
||||
|
||||
matches.push(node);
|
||||
}
|
||||
else if (node.type === "SubqueryNode") {
|
||||
var subPlan = {"plan" : node.subquery};
|
||||
matches = matches.concat(findExecutionNodes(subPlan, nodetype));
|
||||
}
|
||||
});
|
||||
return matches;
|
||||
}
|
||||
|
||||
function findReferencedNodes(plan, testNode) {
|
||||
var matches = [];
|
||||
if (testNode.elements) {
|
||||
testNode.elements.forEach(function(element) {
|
||||
plan.plan.nodes.forEach(function(node) {
|
||||
if (node.hasOwnProperty("outVariable") &&
|
||||
node.outVariable.id ===
|
||||
element.inVariable.id) {
|
||||
matches.push(node);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
else {
|
||||
plan.plan.nodes.forEach(function(node) {
|
||||
if (node.outVariable.id === testNode.inVariable.id) {
|
||||
matches.push(node);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return matches;
|
||||
}
|
||||
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// --SECTION-- module exports
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
exports.isEqual = isEqual;
|
||||
exports.getParseResults = getParseResults;
|
||||
exports.assertParseError = assertParseError;
|
||||
exports.getQueryExplanation = getQueryExplanation;
|
||||
|
@ -441,6 +482,8 @@ exports.assertQueryError = assertQueryError;
|
|||
exports.assertQueryError2 = assertQueryError2;
|
||||
exports.getLinearizedPlan = getLinearizedPlan;
|
||||
exports.getCompactPlan = getCompactPlan;
|
||||
exports.findExecutionNodes = findExecutionNodes;
|
||||
exports.findReferencedNodes = findReferencedNodes;
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// --SECTION-- END-OF-FILE
|
||||
|
|
|
@ -37,7 +37,10 @@ var assertQueryError = helper.assertQueryError2;
|
|||
|
||||
function optimizerRuleTestSuite () {
|
||||
var ruleName = "move-calculations-up";
|
||||
|
||||
// various choices to control the optimizer:
|
||||
var paramNone = { optimizer: { rules: [ "-all" ] } };
|
||||
var paramMCU = { optimizer: { rules: [ "-all", "+" + ruleName ] } };
|
||||
var paramNoMCU = { optimizer: { rules: [ "+all", "-" + ruleName ] } };
|
||||
return {
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
@ -66,7 +69,7 @@ function optimizerRuleTestSuite () {
|
|||
];
|
||||
|
||||
queries.forEach(function(query) {
|
||||
var result = AQL_EXPLAIN(query, { }, { optimizer: { rules: [ "-all" ] } });
|
||||
var result = AQL_EXPLAIN(query, { }, paramNone);
|
||||
assertEqual([ ], result.plan.rules);
|
||||
});
|
||||
},
|
||||
|
@ -84,7 +87,7 @@ function optimizerRuleTestSuite () {
|
|||
];
|
||||
|
||||
queries.forEach(function(query) {
|
||||
var result = AQL_EXPLAIN(query, { }, { optimizer: { rules: [ "-all", "+" + ruleName ] } });
|
||||
var result = AQL_EXPLAIN(query, { }, paramMCU);
|
||||
assertEqual([ ], result.plan.rules, query);
|
||||
});
|
||||
},
|
||||
|
@ -104,7 +107,7 @@ function optimizerRuleTestSuite () {
|
|||
];
|
||||
|
||||
queries.forEach(function(query) {
|
||||
var result = AQL_EXPLAIN(query, { }, { optimizer: { rules: [ "-all", "+" + ruleName ] } });
|
||||
var result = AQL_EXPLAIN(query, { }, paramMCU);
|
||||
assertEqual([ ruleName ], result.plan.rules);
|
||||
});
|
||||
},
|
||||
|
@ -120,7 +123,7 @@ function optimizerRuleTestSuite () {
|
|||
];
|
||||
|
||||
plans.forEach(function(plan) {
|
||||
var result = AQL_EXPLAIN(plan[0], { }, { optimizer: { rules: [ "-all", "+" + ruleName ] } });
|
||||
var result = AQL_EXPLAIN(plan[0], { }, paramMCU);
|
||||
assertEqual([ ruleName ], result.plan.rules, plan[0]);
|
||||
assertEqual(plan[1], helper.getCompactPlan(result).map(function(node) { return node.type; }), plan[0]);
|
||||
});
|
||||
|
@ -139,11 +142,11 @@ function optimizerRuleTestSuite () {
|
|||
];
|
||||
|
||||
queries.forEach(function(query) {
|
||||
var planDisabled = AQL_EXPLAIN(query[0], { }, { optimizer: { rules: [ "+all", "-" + ruleName ] } });
|
||||
var planEnabled = AQL_EXPLAIN(query[0], { }, { optimizer: { rules: [ "-all", "+" + ruleName ] } });
|
||||
var planDisabled = AQL_EXPLAIN(query[0], { }, paramNoMCU);
|
||||
var planEnabled = AQL_EXPLAIN(query[0], { }, paramMCU);
|
||||
|
||||
var resultDisabled = AQL_EXECUTE(query[0], { }, { optimizer: { rules: [ "+all", "-" + ruleName ] } });
|
||||
var resultEnabled = AQL_EXECUTE(query[0], { }, { optimizer: { rules: [ "-all", "+" + ruleName ] } });
|
||||
var resultDisabled = AQL_EXECUTE(query[0], { }, paramNoMCU);
|
||||
var resultEnabled = AQL_EXECUTE(query[0], { }, paramMCU);
|
||||
|
||||
assertTrue(planDisabled.plan.rules.indexOf(ruleName) === -1, query[0]);
|
||||
assertTrue(planEnabled.plan.rules.indexOf(ruleName) !== -1, query[0]);
|
||||
|
|
|
@ -0,0 +1,392 @@
|
|||
/*jslint indent: 2, nomen: true, maxlen: 200, sloppy: true, vars: true, white: true, plusplus: true */
|
||||
/*global require, exports, assertTrue, assertEqual, AQL_EXECUTE, AQL_EXPLAIN, fail, loopmax */
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
/// @brief tests for optimizer rules
|
||||
///
|
||||
/// @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 Jan Steemann
|
||||
/// @author Copyright 2012, triAGENS GmbH, Cologne, Germany
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
var jsunity = require("jsunity");
|
||||
var errors = require("internal").errors;
|
||||
var helper = require("org/arangodb/aql-helper");
|
||||
var getQueryResults = helper.getQueryResults2;
|
||||
var assertQueryError = helper.assertQueryError2;
|
||||
var isEqual = helper.isEqual;
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
/// @brief test suite
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
function optimizerRuleTestSuite () {
|
||||
var ruleName = "remove-unnecessary-calculations";
|
||||
// various choices to control the optimizer:
|
||||
var paramNone = { optimizer: { rules: [ "-all" ] } };
|
||||
var paramMCU = { optimizer: { rules: [ "-all", "+" + ruleName ] } };
|
||||
var paramNoMCU = { optimizer: { rules: [ "+all", "-" + ruleName ] } };
|
||||
return {
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
/// @brief set up
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
setUp : function () {
|
||||
},
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
/// @brief tear down
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
tearDown : function () {
|
||||
},
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
/// @brief test that rule has no effect when explicitly disabled
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
testRuleDisabled : function () {
|
||||
var queries = [
|
||||
"FOR a IN 1 RETURN a + 1",
|
||||
"FOR a IN 1 RETURN a + 0",
|
||||
"FOR a IN 1 RETURN a - 1",
|
||||
"FOR a IN 1 RETURN a * 2",
|
||||
"FOR a IN 4 RETURN a / 2",
|
||||
"FOR a IN 4 RETURN a % 3",
|
||||
"FOR a IN 4 RETURN a == 8",
|
||||
"FOR a IN 4 RETURN 2",
|
||||
"FOR a IN [1, 2, 3, 4, 5, 6] RETURN SLICE(a, 4, 1)",
|
||||
"FOR a IN 17.33 RETURN FLOOR(a)",
|
||||
"FOR a IN 17.33 RETURN CEIL(a)",
|
||||
"FOR a IN 17.33 RETURN FLOOR(a + 3)",
|
||||
"FOR a IN 17.33 RETURN CEIL(a + 3)",
|
||||
"FOR a IN 17.33 RETURN FLOOR(a / 77)",
|
||||
"FOR a IN 17.33 RETURN CEIL(a / 77)",
|
||||
"FOR a IN -12 RETURN TO_BOOL(a)",
|
||||
"FOR a IN \"-12\" RETURN TO_NUMBER(a)",
|
||||
"FOR a IN \"-12\" RETURN IS_NUMBER(a)",
|
||||
"FOR a IN \"-12\" RETURN TO_LIST(a)",
|
||||
"FOR a IN { \"a\" : null, \"b\" : -63, \"c\" : [ 1, 2 ], \"d\": { \"a\" : \"b\" } } RETURN TO_LIST(a)",
|
||||
"FOR a IN -12 RETURN ABS(a)",
|
||||
"FOR a IN -12 RETURN ABS(a + 17)",
|
||||
"FOR a IN 17.33 RETURN ROUND(a)",
|
||||
"FOR a IN 17.33 RETURN SQRT(a)",
|
||||
"FOR a IN -17.33 RETURN SQRT(a)",
|
||||
"FOR a IN CHAR_LENGTH('äöボカド名üÄÖÜß') return a + 1",
|
||||
"FOR a IN 7 return a..12",
|
||||
"FOR a IN [1, 7, 3, 12] RETURN AVERAGE(a)",
|
||||
"FOR a IN [1, 7, 3, 12] RETURN MEDIAN(a)",
|
||||
"FOR a IN [1, 7, 3, 12] RETURN MAX(a)",
|
||||
"FOR a IN [1, 7, 3, 12] RETURN SUM(a)",
|
||||
"FOR a IN [1, 7, 3, 12, null] RETURN NOT_NULL(a)",
|
||||
"FOR a IN [1, 7, 3, 12, null] RETURN FIRST_LIST(a)",
|
||||
"FOR a IN [null, \"not a doc!\"] RETURN FIRST_DOCUMENT(a)",
|
||||
"FOR a IN [0.75, 0.8] RETURN VARIANCE_SAMPLE(a)",
|
||||
"FOR a IN [0.75, 0.8] RETURN STDDEV_POPULATION(a)",
|
||||
"FOR a IN 5 RETURN DATE_DAYOFWEEK(a)",
|
||||
"FOR a IN 5 RETURN DATE_MONTH(a)",
|
||||
"FOR a IN 5 RETURN DATE_DAY(a)",
|
||||
"FOR a IN 5 RETURN DATE_HOUR(a)",
|
||||
"FOR a IN 5 RETURN DATE_MINUTE(a)",
|
||||
"FOR a IN 5 RETURN DATE_SECOND(a)",
|
||||
"FOR a IN 5 RETURN DATE_MILLISECOND(a)",
|
||||
"FOR a IN 5 RETURN DATE_TIMESTAMP(a)",
|
||||
"FOR a IN 5 RETURN DATE_ISO8601(a)",
|
||||
"FOR a IN 1975 RETURN DATE_YEAR(a)",
|
||||
"LET a = SLEEP(2) RETURN 1"
|
||||
];
|
||||
|
||||
queries.forEach(function(query) {
|
||||
var result = AQL_EXPLAIN(query, { }, paramNone);
|
||||
assertEqual([ ], result.plan.rules);
|
||||
});
|
||||
},
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
/// @brief test that rule has no effect
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
testRuleNoEffect : function () {
|
||||
var queries = [
|
||||
"FOR a IN 1 RETURN a + 1",
|
||||
"FOR a IN 1 RETURN a + 0",
|
||||
"FOR a IN 1 RETURN a - 1",
|
||||
"FOR a IN 1 RETURN a * 2",
|
||||
"FOR a IN 4 RETURN a / 2",
|
||||
"FOR a IN 4 RETURN a % 3",
|
||||
"FOR a IN 4 RETURN a == 8",
|
||||
"FOR a IN 4 RETURN 2",
|
||||
"FOR a IN [1, 2, 3, 4, 5, 6] RETURN SLICE(a, 4, 1)",
|
||||
"FOR a IN 17.33 RETURN FLOOR(a)",
|
||||
"FOR a IN 17.33 RETURN CEIL(a)",
|
||||
"FOR a IN 17.33 RETURN FLOOR(a + 3)",
|
||||
"FOR a IN 17.33 RETURN CEIL(a + 3)",
|
||||
"FOR a IN 17.33 RETURN FLOOR(a / 77)",
|
||||
"FOR a IN 17.33 RETURN CEIL(a / 77)",
|
||||
"FOR a IN -12 RETURN TO_BOOL(a)",
|
||||
"FOR a IN \"-12\" RETURN TO_NUMBER(a)",
|
||||
"FOR a IN \"-12\" RETURN IS_NUMBER(a)",
|
||||
"FOR a IN \"-12\" RETURN TO_LIST(a)",
|
||||
"FOR a IN { \"a\" : null, \"b\" : -63, \"c\" : [ 1, 2 ], \"d\": { \"a\" : \"b\" } } RETURN TO_LIST(a)",
|
||||
"FOR a IN -12 RETURN ABS(a)",
|
||||
"FOR a IN -12 RETURN ABS(a + 17)",
|
||||
"FOR a IN 17.33 RETURN ROUND(a)",
|
||||
"FOR a IN 17.33 RETURN SQRT(a)",
|
||||
"FOR a IN -17.33 RETURN SQRT(a)",
|
||||
"FOR a IN CHAR_LENGTH('äöボカド名üÄÖÜß') return a + 1",
|
||||
"FOR a IN 7 return a..12",
|
||||
"FOR a IN [1, 7, 3, 12] RETURN AVERAGE(a)",
|
||||
"FOR a IN [1, 7, 3, 12] RETURN MEDIAN(a)",
|
||||
"FOR a IN [1, 7, 3, 12] RETURN MAX(a)",
|
||||
"FOR a IN [1, 7, 3, 12] RETURN SUM(a)",
|
||||
"FOR a IN [1, 7, 3, 12, null] RETURN NOT_NULL(a)",
|
||||
"FOR a IN [1, 7, 3, 12, null] RETURN FIRST_LIST(a)",
|
||||
"FOR a IN [null, \"not a doc!\"] RETURN FIRST_DOCUMENT(a)",
|
||||
"FOR a IN [0.75, 0.8] RETURN VARIANCE_SAMPLE(a)",
|
||||
"FOR a IN [0.75, 0.8] RETURN STDDEV_POPULATION(a)",
|
||||
"FOR a IN 5 RETURN DATE_DAYOFWEEK(a)",
|
||||
"FOR a IN 5 RETURN DATE_MONTH(a)",
|
||||
"FOR a IN 5 RETURN DATE_DAY(a)",
|
||||
"FOR a IN 5 RETURN DATE_HOUR(a)",
|
||||
"FOR a IN 5 RETURN DATE_MINUTE(a)",
|
||||
"FOR a IN 5 RETURN DATE_SECOND(a)",
|
||||
"FOR a IN 5 RETURN DATE_MILLISECOND(a)",
|
||||
"FOR a IN 5 RETURN DATE_TIMESTAMP(a)",
|
||||
"FOR a IN 5 RETURN DATE_ISO8601(a)",
|
||||
"FOR a IN 1975 RETURN DATE_YEAR(a)",
|
||||
"LET a = SLEEP(2) RETURN 1"
|
||||
];
|
||||
|
||||
queries.forEach(function(query) {
|
||||
var result = AQL_EXPLAIN(query, { }, paramMCU);
|
||||
assertEqual([ ], result.plan.rules, query);
|
||||
});
|
||||
},
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
/// @brief test that rule has an effect
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
testRuleHasEffect : function () {
|
||||
var queries = [
|
||||
"LET a = 1 RETURN a + 1",
|
||||
"LET a = 1 RETURN a + 0",
|
||||
"LET a = 1 RETURN a - 1",
|
||||
"LET a = 1 RETURN a * 2",
|
||||
"LET a = 4 RETURN a / 2",
|
||||
"LET a = 4 RETURN a % 3",
|
||||
"LET a = 4 RETURN a == 8",
|
||||
"LET a = 4 RETURN 2",
|
||||
"LET a = [1, 2, 3, 4, 5, 6] RETURN SLICE(a, 4, 1)",
|
||||
"LET a = 17.33 RETURN FLOOR(a)",
|
||||
"LET a = 17.33 RETURN CEIL(a)",
|
||||
"LET a = 17.33 RETURN FLOOR(a + 3)",
|
||||
"LET a = 17.33 RETURN CEIL(a + 3)",
|
||||
"LET a = 17.33 RETURN FLOOR(a / 77)",
|
||||
"LET a = 17.33 RETURN CEIL(a / 77)",
|
||||
"LET a = -12 RETURN TO_BOOL(a)",
|
||||
"LET a = \"-12\" RETURN TO_NUMBER(a)",
|
||||
"LET a = \"-12\" RETURN IS_NUMBER(a)",
|
||||
"LET a = \"-12\" RETURN TO_LIST(a)",
|
||||
"LET a = { \"a\" : null, \"b\" : -63, \"c\" : [ 1, 2 ], \"d\": { \"a\" : \"b\" } } RETURN TO_LIST(a)",
|
||||
"LET a = -12 RETURN ABS(a)",
|
||||
"LET a = -12 RETURN ABS(a + 17)",
|
||||
"LET a = 17.33 RETURN ROUND(a)",
|
||||
"LET a = 17.33 RETURN SQRT(a)",
|
||||
"LET a = -17.33 RETURN SQRT(a)",
|
||||
"LET a = CHAR_LENGTH('äöボカド名üÄÖÜß') return a + 1",
|
||||
"LET a = 7 return a..12",
|
||||
"LET a = 1 LET b = 2 RETURN [ a, b ]",
|
||||
"LET a = [1, 7, 3, 12] RETURN AVERAGE(a)",
|
||||
"LET a = [1, 7, 3, 12] RETURN MEDIAN(a)",
|
||||
"LET a = [1, 7, 3, 12] RETURN MAX(a)",
|
||||
"LET a = [1, 7, 3, 12] RETURN SUM(a)",
|
||||
"LET a = [1, 7, 3, 12, null] RETURN NOT_NULL(a)",
|
||||
"LET a = [1, 7, 3, 12, null] RETURN FIRST_LIST(a)",
|
||||
"LET a = [null, \"not a doc!\"] RETURN FIRST_DOCUMENT(a)",
|
||||
"LET a = [0.75, 0.8] RETURN VARIANCE_SAMPLE(a)",
|
||||
"LET a = [0.75, 0.8] RETURN STDDEV_POPULATION(a)",
|
||||
"LET a = 5 RETURN DATE_DAYOFWEEK(a)",
|
||||
"LET a = 5 RETURN DATE_MONTH(a)",
|
||||
"LET a = 5 RETURN DATE_DAY(a)",
|
||||
"LET a = 5 RETURN DATE_HOUR(a)",
|
||||
"LET a = 5 RETURN DATE_MINUTE(a)",
|
||||
"LET a = 5 RETURN DATE_SECOND(a)",
|
||||
"LET a = 5 RETURN DATE_MILLISECOND(a)",
|
||||
"LET a = 5 RETURN DATE_TIMESTAMP(a)",
|
||||
"LET a = 5 RETURN DATE_ISO8601(a)",
|
||||
"LET a = 1975 RETURN DATE_YEAR(a)",
|
||||
"FOR i IN 1..10 LET a = 1 FILTER i == a RETURN i"
|
||||
// "FOR i IN 1..10 LET a = i + 1 FILTER i != a RETURN i"
|
||||
];
|
||||
|
||||
queries.forEach(function(query) {
|
||||
var result = AQL_EXPLAIN(query, { }, paramMCU);
|
||||
//require("internal").print(result);
|
||||
assertEqual([ ruleName ], result.plan.rules);
|
||||
});
|
||||
},
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
/// @brief test generated plans
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
|
||||
testPlans : function () {
|
||||
var plans = [
|
||||
["LET a = 1 RETURN a + 1", ["SingletonNode", "CalculationNode", "ReturnNode"]],
|
||||
["LET a = 1 RETURN a + 0", ["SingletonNode", "CalculationNode", "ReturnNode"]],
|
||||
["LET a = 1 RETURN a - 1", ["SingletonNode", "CalculationNode", "ReturnNode"]],
|
||||
["LET a = 1 RETURN a * 2", ["SingletonNode", "CalculationNode", "ReturnNode"]],
|
||||
["LET a = 4 RETURN a / 2", ["SingletonNode", "CalculationNode", "ReturnNode"]],
|
||||
["LET a = 4 RETURN a % 3", ["SingletonNode", "CalculationNode", "ReturnNode"]],
|
||||
["LET a = 4 RETURN a == 8", ["SingletonNode", "CalculationNode", "ReturnNode"]],
|
||||
["LET a = 4 RETURN 2", ["SingletonNode", "CalculationNode", "ReturnNode"]],
|
||||
["LET a = [1, 2, 3, 4, 5, 6] RETURN SLICE(a, 4, 1)", ["SingletonNode", "CalculationNode", "ReturnNode"]],
|
||||
["LET a = 17.33 RETURN FLOOR(a)", ["SingletonNode", "CalculationNode", "ReturnNode"]],
|
||||
["LET a = 17.33 RETURN CEIL(a)", ["SingletonNode", "CalculationNode", "ReturnNode"]],
|
||||
["LET a = 17.33 RETURN FLOOR(a + 3)", ["SingletonNode", "CalculationNode", "ReturnNode"]],
|
||||
["LET a = 17.33 RETURN CEIL(a + 3)", ["SingletonNode", "CalculationNode", "ReturnNode"]],
|
||||
["LET a = 17.33 RETURN FLOOR(a / 77)", ["SingletonNode", "CalculationNode", "ReturnNode"]],
|
||||
["LET a = 17.33 RETURN CEIL(a / 77)", ["SingletonNode", "CalculationNode", "ReturnNode"]],
|
||||
["LET a = -12 RETURN TO_BOOL(a)", ["SingletonNode", "CalculationNode", "ReturnNode"]],
|
||||
["LET a = \"-12\" RETURN TO_NUMBER(a)", ["SingletonNode", "CalculationNode", "ReturnNode"]],
|
||||
["LET a = \"-12\" RETURN IS_NUMBER(a)", ["SingletonNode", "CalculationNode", "ReturnNode"]],
|
||||
["LET a = \"-12\" RETURN TO_LIST(a)", ["SingletonNode", "CalculationNode", "ReturnNode"]],
|
||||
["LET a = { \"a\" : null, \"b\" : -63, \"c\" : [ 1, 2 ], \"d\": { \"a\" : \"b\" } } RETURN TO_LIST(a)", ["SingletonNode", "CalculationNode", "ReturnNode"]],
|
||||
["LET a = -12 RETURN ABS(a)", ["SingletonNode", "CalculationNode", "ReturnNode"]],
|
||||
["LET a = -12 RETURN ABS(a + 17)", ["SingletonNode", "CalculationNode", "ReturnNode"]],
|
||||
["LET a = 17.33 RETURN ROUND(a)", ["SingletonNode", "CalculationNode", "ReturnNode"]],
|
||||
["LET a = 17.33 RETURN SQRT(a)", ["SingletonNode", "CalculationNode", "ReturnNode"]],
|
||||
["LET a = -17.33 RETURN SQRT(a)", ["SingletonNode", "CalculationNode", "ReturnNode"]],
|
||||
["LET a = CHAR_LENGTH('äöボカド名üÄÖÜß') return a + 1", ["SingletonNode", "CalculationNode", "ReturnNode"]],
|
||||
["LET a = 7 return a..12", ["SingletonNode", "CalculationNode", "ReturnNode"]],
|
||||
["LET a = 1 LET b = 2 RETURN [ a, b ]", ["SingletonNode", "CalculationNode", "ReturnNode"]],
|
||||
["LET a = [1, 7, 3, 12] RETURN AVERAGE(a)", ["SingletonNode", "CalculationNode", "ReturnNode"]],
|
||||
["LET a = [1, 7, 3, 12] RETURN MEDIAN(a)", ["SingletonNode", "CalculationNode", "ReturnNode"]],
|
||||
["LET a = [1, 7, 3, 12] RETURN MAX(a)", ["SingletonNode", "CalculationNode", "ReturnNode"]],
|
||||
["LET a = [1, 7, 3, 12] RETURN SUM(a)", ["SingletonNode", "CalculationNode", "ReturnNode"]],
|
||||
["LET a = [1, 7, 3, 12, null] RETURN NOT_NULL(a)", ["SingletonNode", "CalculationNode", "ReturnNode"]],
|
||||
["LET a = [1, 7, 3, 12, null] RETURN FIRST_LIST(a)", ["SingletonNode", "CalculationNode", "ReturnNode"]],
|
||||
["LET a = [null, \"not a doc!\"] RETURN FIRST_DOCUMENT(a)", ["SingletonNode", "CalculationNode", "ReturnNode"]],
|
||||
["LET a = [0.75, 0.8] RETURN VARIANCE_SAMPLE(a)", ["SingletonNode", "CalculationNode", "ReturnNode"]],
|
||||
["LET a = [0.75, 0.8] RETURN STDDEV_POPULATION(a)", ["SingletonNode", "CalculationNode", "ReturnNode"]],
|
||||
["LET a = 5 RETURN DATE_DAYOFWEEK(a)", ["SingletonNode", "CalculationNode", "ReturnNode"]],
|
||||
["LET a = 5 RETURN DATE_MONTH(a)", ["SingletonNode", "CalculationNode", "ReturnNode"]],
|
||||
["LET a = 5 RETURN DATE_DAY(a)", ["SingletonNode", "CalculationNode", "ReturnNode"]],
|
||||
["LET a = 5 RETURN DATE_HOUR(a)", ["SingletonNode", "CalculationNode", "ReturnNode"]],
|
||||
["LET a = 5 RETURN DATE_MINUTE(a)", ["SingletonNode", "CalculationNode", "ReturnNode"]],
|
||||
["LET a = 5 RETURN DATE_SECOND(a)", ["SingletonNode", "CalculationNode", "ReturnNode"]],
|
||||
["LET a = 5 RETURN DATE_MILLISECOND(a)", ["SingletonNode", "CalculationNode", "ReturnNode"]],
|
||||
["LET a = 5 RETURN DATE_TIMESTAMP(a)", ["SingletonNode", "CalculationNode", "ReturnNode"]],
|
||||
["LET a = 5 RETURN DATE_ISO8601(a)", ["SingletonNode", "CalculationNode", "ReturnNode"]],
|
||||
["LET a = 1975 RETURN DATE_YEAR(a)", ["SingletonNode", "CalculationNode", "ReturnNode"]],
|
||||
["FOR i IN 1..10 LET a = 1 FILTER i == a RETURN i", ["SingletonNode", "CalculationNode", "EnumerateListNode", "CalculationNode", "FilterNode", "ReturnNode" ]]
|
||||
];
|
||||
|
||||
plans.forEach(function(plan) {
|
||||
var result = AQL_EXPLAIN(plan[0], { }, paramMCU);
|
||||
assertEqual([ ruleName ], result.plan.rules, plan[0]);
|
||||
//require("internal").print(helper.getCompactPlan(result).map(function(node) { return node.type; }));
|
||||
assertEqual(plan[1], helper.getCompactPlan(result).map(function(node) { return node.type; }), plan[0]);
|
||||
});
|
||||
},
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
/// @brief test results
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
testResults : function () {
|
||||
var queries = [
|
||||
["LET a = 1 RETURN a + 1", ["SingletonNode", "CalculationNode", "ReturnNode"]],
|
||||
["LET a = 1 RETURN a + 0", ["SingletonNode", "CalculationNode", "ReturnNode"]],
|
||||
["LET a = 1 RETURN a - 1", ["SingletonNode", "CalculationNode", "ReturnNode"]],
|
||||
["LET a = 1 RETURN a * 2", ["SingletonNode", "CalculationNode", "ReturnNode"]],
|
||||
["LET a = 4 RETURN a / 2", ["SingletonNode", "CalculationNode", "ReturnNode"]],
|
||||
["LET a = 4 RETURN a % 3", ["SingletonNode", "CalculationNode", "ReturnNode"]],
|
||||
["LET a = 4 RETURN a == 8", ["SingletonNode", "CalculationNode", "ReturnNode"]],
|
||||
["LET a = 4 RETURN 2", ["SingletonNode", "CalculationNode", "ReturnNode"]],
|
||||
["LET a = [1, 2, 3, 4, 5, 6] RETURN SLICE(a, 4, 1)", ["SingletonNode", "CalculationNode", "ReturnNode"]],
|
||||
["LET a = 17.33 RETURN FLOOR(a)", ["SingletonNode", "CalculationNode", "ReturnNode"]],
|
||||
["LET a = 17.33 RETURN CEIL(a)", ["SingletonNode", "CalculationNode", "ReturnNode"]],
|
||||
["LET a = 17.33 RETURN FLOOR(a + 3)", ["SingletonNode", "CalculationNode", "ReturnNode"]],
|
||||
["LET a = 17.33 RETURN CEIL(a + 3)", ["SingletonNode", "CalculationNode", "ReturnNode"]],
|
||||
["LET a = 17.33 RETURN FLOOR(a / 77)", ["SingletonNode", "CalculationNode", "ReturnNode"]],
|
||||
["LET a = 17.33 RETURN CEIL(a / 77)", ["SingletonNode", "CalculationNode", "ReturnNode"]],
|
||||
["LET a = -12 RETURN TO_BOOL(a)", ["SingletonNode", "CalculationNode", "ReturnNode"]],
|
||||
["LET a = \"-12\" RETURN TO_NUMBER(a)", ["SingletonNode", "CalculationNode", "ReturnNode"]],
|
||||
["LET a = \"-12\" RETURN IS_NUMBER(a)", ["SingletonNode", "CalculationNode", "ReturnNode"]],
|
||||
["LET a = \"-12\" RETURN TO_LIST(a)", ["SingletonNode", "CalculationNode", "ReturnNode"]],
|
||||
["LET a = { \"a\" : null, \"b\" : -63, \"c\" : [ 1, 2 ], \"d\": { \"a\" : \"b\" } } RETURN TO_LIST(a)", ["SingletonNode", "CalculationNode", "ReturnNode"]],
|
||||
["LET a = -12 RETURN ABS(a)", ["SingletonNode", "CalculationNode", "ReturnNode"]],
|
||||
["LET a = -12 RETURN ABS(a + 17)", ["SingletonNode", "CalculationNode", "ReturnNode"]],
|
||||
["LET a = 17.33 RETURN ROUND(a)", ["SingletonNode", "CalculationNode", "ReturnNode"]],
|
||||
["LET a = 17.33 RETURN SQRT(a)", ["SingletonNode", "CalculationNode", "ReturnNode"]],
|
||||
["LET a = -17.33 RETURN SQRT(a)", ["SingletonNode", "CalculationNode", "ReturnNode"]],
|
||||
["LET a = CHAR_LENGTH('äöボカド名üÄÖÜß') return a + 1", ["SingletonNode", "CalculationNode", "ReturnNode"]],
|
||||
["LET a = 7 return a..12", ["SingletonNode", "CalculationNode", "ReturnNode"]],
|
||||
["LET a = 1 LET b = 2 RETURN [ a, b ]", ["SingletonNode", "CalculationNode", "ReturnNode"]],
|
||||
["LET a = [1, 7, 3, 12] RETURN AVERAGE(a)", ["SingletonNode", "CalculationNode", "ReturnNode"]],
|
||||
["LET a = [1, 7, 3, 12] RETURN MEDIAN(a)", ["SingletonNode", "CalculationNode", "ReturnNode"]],
|
||||
["LET a = [1, 7, 3, 12] RETURN MAX(a)", ["SingletonNode", "CalculationNode", "ReturnNode"]],
|
||||
["LET a = [1, 7, 3, 12] RETURN SUM(a)", ["SingletonNode", "CalculationNode", "ReturnNode"]],
|
||||
["LET a = [1, 7, 3, 12, null] RETURN NOT_NULL(a)", ["SingletonNode", "CalculationNode", "ReturnNode"]],
|
||||
["LET a = [1, 7, 3, 12, null] RETURN FIRST_LIST(a)", ["SingletonNode", "CalculationNode", "ReturnNode"]],
|
||||
["LET a = [null, \"not a doc!\"] RETURN FIRST_DOCUMENT(a)", ["SingletonNode", "CalculationNode", "ReturnNode"]],
|
||||
["LET a = [0.75, 0.8] RETURN VARIANCE_SAMPLE(a)", ["SingletonNode", "CalculationNode", "ReturnNode"]],
|
||||
["LET a = [0.75, 0.8] RETURN STDDEV_POPULATION(a)", ["SingletonNode", "CalculationNode", "ReturnNode"]],
|
||||
["LET a = 5 RETURN DATE_DAYOFWEEK(a)", ["SingletonNode", "CalculationNode", "ReturnNode"]],
|
||||
["LET a = 5 RETURN DATE_MONTH(a)", ["SingletonNode", "CalculationNode", "ReturnNode"]],
|
||||
["LET a = 5 RETURN DATE_DAY(a)", ["SingletonNode", "CalculationNode", "ReturnNode"]],
|
||||
["LET a = 5 RETURN DATE_HOUR(a)", ["SingletonNode", "CalculationNode", "ReturnNode"]],
|
||||
["LET a = 5 RETURN DATE_MINUTE(a)", ["SingletonNode", "CalculationNode", "ReturnNode"]],
|
||||
["LET a = 5 RETURN DATE_SECOND(a)", ["SingletonNode", "CalculationNode", "ReturnNode"]],
|
||||
["LET a = 5 RETURN DATE_MILLISECOND(a)", ["SingletonNode", "CalculationNode", "ReturnNode"]],
|
||||
["LET a = 5 RETURN DATE_TIMESTAMP(a)", ["SingletonNode", "CalculationNode", "ReturnNode"]],
|
||||
["LET a = 5 RETURN DATE_ISO8601(a)", ["SingletonNode", "CalculationNode", "ReturnNode"]],
|
||||
["LET a = 1975 RETURN DATE_YEAR(a)", ["SingletonNode", "CalculationNode", "ReturnNode"]],
|
||||
["FOR i IN 1..10 LET a = 1 FILTER i == a RETURN i", ["SingletonNode", "CalculationNode", "EnumerateListNode", "CalculationNode", "FilterNode", "ReturnNode" ]]
|
||||
];
|
||||
|
||||
queries.forEach(function(query) {
|
||||
var resultDisabled = AQL_EXECUTE(query[0], { }, paramNoMCU).json;
|
||||
var resultEnabled = AQL_EXECUTE(query[0], { }, paramMCU).json;
|
||||
|
||||
assertTrue(isEqual(resultDisabled, resultEnabled), query[0]);
|
||||
});
|
||||
}
|
||||
|
||||
};
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
/// @brief executes the test suite
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
jsunity.run(optimizerRuleTestSuite);
|
||||
|
||||
return jsunity.done();
|
||||
|
||||
// Local Variables:
|
||||
// mode: outline-minor
|
||||
// outline-regexp: "^\\(/// @brief\\|/// @addtogroup\\|// --SECTION--\\|/// @page\\|/// @}\\)"
|
||||
// End:
|
|
@ -0,0 +1,760 @@
|
|||
/*jslint indent: 2, nomen: true, maxlen: 200, sloppy: true, vars: true, white: true, plusplus: true */
|
||||
/*global require, exports, assertTrue, assertEqual, AQL_EXECUTE, AQL_EXPLAIN, fail, loopmax */
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
/// @brief tests for optimizer rules
|
||||
///
|
||||
/// @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 Jan Steemann
|
||||
/// @author Copyright 2012, triAGENS GmbH, Cologne, Germany
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
var internal = require("internal");
|
||||
var jsunity = require("jsunity");
|
||||
var errors = require("internal").errors;
|
||||
var helper = require("org/arangodb/aql-helper");
|
||||
var getQueryResults = helper.getQueryResults2;
|
||||
var assertQueryError = helper.assertQueryError2;
|
||||
var isEqual = helper.isEqual;
|
||||
var findExecutionNodes = helper.findExecutionNodes;
|
||||
var findReferencedNodes = helper.findReferencedNodes;
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
/// @brief test suite
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
function optimizerRuleTestSuite() {
|
||||
var ruleName = "use-index-for-sort";
|
||||
var secondRuleName = "use-index-range";
|
||||
var thirdRuleName = "remove-redundant-sorts";
|
||||
var colName = "UnitTestsAqlOptimizer" + ruleName.replace(/-/g, "_");
|
||||
var colNameOther = colName + "_XX";
|
||||
|
||||
// various choices to control the optimizer:
|
||||
var paramNone = { optimizer: { rules: [ "-all" ] } };
|
||||
var paramIFS = { optimizer: { rules: [ "-all", "+" + ruleName ] } };
|
||||
var paramIR = { optimizer: { rules: [ "-all", "+" + secondRuleName ] } };
|
||||
var paramRS = { optimizer: { rules: [ "-all", "+" + thirdRuleName ] } };
|
||||
var paramBoth = { optimizer: { rules: [ "-all", "+" + ruleName, "+" + secondRuleName ] } };
|
||||
|
||||
var skiplist;
|
||||
var skiplist2;
|
||||
var sortArray = function (l, r) {
|
||||
if (l[0] !== r[0]) {
|
||||
return l[0] < r[0] ? -1 : 1;
|
||||
}
|
||||
if (l[1] !== r[1]) {
|
||||
return l[1] < r[1] ? -1 : 1;
|
||||
}
|
||||
return 0;
|
||||
};
|
||||
var hasSortNode = function (plan) {
|
||||
assertEqual(findExecutionNodes(plan, "SortNode").length, 1, "Has SortNode");
|
||||
};
|
||||
var hasNoSortNode = function (plan) {
|
||||
assertEqual(findExecutionNodes(plan, "SortNode").length, 0, "Has NO SortNode");
|
||||
};
|
||||
var hasCalculationNodes = function (plan, countXPect) {
|
||||
assertEqual(findExecutionNodes(plan, "CalculationNode").length,
|
||||
countXPect,
|
||||
"Has " + countXPect + "CalculationNode");
|
||||
};
|
||||
var hasNoCalculationNode = function (plan) {
|
||||
assertEqual(findExecutionNodes(plan, "CalculationNode").length, 0, "Has NO CalculationNode");
|
||||
};
|
||||
var hasIndexRangeNode_WithRanges = function (plan, haveRanges) {
|
||||
var rn = findExecutionNodes(plan, "IndexRangeNode");
|
||||
assertEqual(rn.length, 1, "Has IndexRangeNode");
|
||||
if (haveRanges) {
|
||||
assertTrue(rn[0].ranges.length > 0, "Have IndexRangeNode with ranges");
|
||||
}
|
||||
else {
|
||||
assertEqual(rn[0].ranges.length, 0, "Have IndexRangeNode with NO ranges");
|
||||
}
|
||||
};
|
||||
var getRangeAttributes = function (plan) {
|
||||
var rn = findExecutionNodes(plan, "IndexRangeNode");
|
||||
assertEqual(rn.length, 1, "Has IndexRangeNode");
|
||||
assertTrue(rn[0].ranges.length > 0, "Have IndexRangeNode with ranges");
|
||||
return rn[0].ranges;
|
||||
};
|
||||
var getRangeAttribute = function (rangeAttributes, varcmp, attrcmp, getNth) {
|
||||
var ret = {};
|
||||
rangeAttributes.forEach(function compare(oneRA) {
|
||||
if ( (oneRA.variable === varcmp) &&
|
||||
(oneRA.attr === attrcmp)) {
|
||||
getNth --;
|
||||
if (getNth === 0) {
|
||||
ret = oneRA;
|
||||
}
|
||||
}
|
||||
|
||||
});
|
||||
return ret;
|
||||
};
|
||||
var isNodeType = function(node, type) {
|
||||
assertEqual(node.type, type, "check whether this node is of type "+type);
|
||||
};
|
||||
|
||||
return {
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
/// @brief set up
|
||||
// Datastructure:
|
||||
// - double index on (a,b)/(f,g) for tests with these
|
||||
// - single column index on d/j to test sort behaviour without sub-columns
|
||||
// - non-indexed columns c/h to sort without indices.
|
||||
// - non-skiplist indexed columns e/j to check whether its not selecting them.
|
||||
// - join column 'joinme' to intersect both tables.
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
setUp : function () {
|
||||
var loopto;
|
||||
if (typeof loopmax === 'undefined') {
|
||||
loopto = 10;
|
||||
}
|
||||
else {
|
||||
loopto = loopmax;
|
||||
}
|
||||
/// require("internal").print("loopto: " + loopto + "\n");
|
||||
|
||||
internal.db._drop(colName);
|
||||
skiplist = internal.db._create(colName);
|
||||
var i, j;
|
||||
for (j = 1; j <= loopto; ++j) {
|
||||
for (i = 1; i <= loopto; ++i) {
|
||||
skiplist.save({ "a" : i, "b": j , "c": j, "d": i, "e": i, "joinme" : "aoeu " + j});
|
||||
}
|
||||
skiplist.save( { "a" : i, "c": j, "d": i, "e": i, "joinme" : "aoeu " + j});
|
||||
skiplist.save( { "c": j, "joinme" : "aoeu " + j});
|
||||
}
|
||||
|
||||
skiplist.ensureSkiplist("a", "b");
|
||||
skiplist.ensureSkiplist("d");
|
||||
skiplist.ensureIndex({ type: "hash", fields: [ "c" ], unique: false });
|
||||
|
||||
skiplist2 = internal.db._create(colNameOther);
|
||||
for (j = 1; j <= loopto; ++j) {
|
||||
for (i = 1; i <= loopto; ++i) {
|
||||
skiplist2.save({ "f" : i, "g": j , "h": j, "i": i, "j": i, "joinme" : "aoeu " + j});
|
||||
}
|
||||
skiplist2.save( { "f" : i, "g": j, "i": i, "j": i, "joinme" : "aoeu " + j});
|
||||
skiplist2.save( { "h": j, "joinme" : "aoeu " + j});
|
||||
}
|
||||
skiplist2.ensureSkiplist("f", "g");
|
||||
skiplist2.ensureSkiplist("i");
|
||||
skiplist2.ensureIndex({ type: "hash", fields: [ "h" ], unique: false });
|
||||
},
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
/// @brief tear down
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
tearDown : function () {
|
||||
internal.db._drop(colName);
|
||||
internal.db._drop(colNameOther);
|
||||
skiplist = null;
|
||||
},
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
/// @brief test that rule has no effect when explicitly disabled
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
testRuleDisabled : function () {
|
||||
/*
|
||||
var queries = [
|
||||
"LET a = 1 FOR i IN 1..10 FILTER a == 1 RETURN i",
|
||||
"FOR i IN 1..10 LET a = 25 RETURN i",
|
||||
"FOR i IN 1..10 LET a = 1 LET b = 2 LET c = 3 FILTER i > 3 LET d = 1 RETURN i"
|
||||
];
|
||||
|
||||
queries.forEach(function(query) {
|
||||
var result = AQL_EXPLAIN(query, { }, { optimizer: { rules: [ "-all" ] } });
|
||||
assertEqual([ ], result.plan.rules);
|
||||
});
|
||||
*/
|
||||
},
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
/// @brief test that rule has no effect
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
testRuleNoEffect : function () {
|
||||
/*
|
||||
var queries = [
|
||||
|
||||
"FOR v IN " + colName + " SORT v.c RETURN [v.a, v.b]",
|
||||
// todo: we use an index anyways right now. "FOR v IN " + colName + " SORT v.a DESC RETURN [v.a, v.b]",// currently only ASC supported.
|
||||
"FOR v IN " + colName + " SORT v.b, v.a RETURN [v.a, v.b]",
|
||||
"FOR v IN " + colName + " SORT v.c RETURN [v.a, v.b]",
|
||||
"FOR v IN " + colName + " SORT v.a + 1 RETURN [v.a, v.b]",
|
||||
"FOR v IN " + colName + " SORT CONCAT(TO_STRING(v.a), \"lol\") RETURN [v.a, v.b]",
|
||||
"FOR v IN " + colName + " FILTER v.a > 2 LIMIT 3 SORT v.a RETURN [v.a, v.b] ", // TODO: limit blocks sort atm.
|
||||
"FOR v IN " + colName + " FOR w IN " + colNameOther + " SORT v.a RETURN [v.a, v.b]"
|
||||
];
|
||||
|
||||
queries.forEach(function(query) {
|
||||
|
||||
// require("internal").print(query);
|
||||
var result = AQL_EXPLAIN(query, { }, paramIFS);
|
||||
// require("internal").print(result);
|
||||
assertEqual([], result.plan.rules, query);
|
||||
});
|
||||
*/
|
||||
},
|
||||
|
||||
/*
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
/// @brief test that rule has an effect
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
testRuleHasEffect : function () {
|
||||
|
||||
var queries = [
|
||||
|
||||
"FOR v IN " + colName + " SORT v.d DESC RETURN [v.d]",// currently only ASC supported, but we use the index range anyways. todo: this may change.
|
||||
"FOR v IN " + colName + " SORT v.d FILTER v.a > 2 LIMIT 3 RETURN [v.d] ",
|
||||
"FOR v IN " + colName + " FOR w IN 1..10 SORT v.d RETURN [v.d]",
|
||||
|
||||
"FOR v IN " + colName + " LET x = (FOR w IN " + colNameOther + " RETURN w.f ) SORT v.a RETURN [v.a]"
|
||||
];
|
||||
var QResults = [];
|
||||
var i = 0;
|
||||
queries.forEach(function(query) {
|
||||
|
||||
//require("internal").print(query);
|
||||
var result = AQL_EXPLAIN(query, { }, paramIFS);
|
||||
assertEqual([ ruleName ], result.plan.rules, query);
|
||||
QResults[0] = AQL_EXECUTE(query, { }, paramNone).json;
|
||||
QResults[1] = AQL_EXECUTE(query, { }, paramIFS ).json;
|
||||
// require("internal").print(result);
|
||||
|
||||
assertTrue(isEqual(QResults[0], QResults[1]), "Result " + i + " is Equal?");
|
||||
i++;
|
||||
});
|
||||
|
||||
},
|
||||
|
||||
|
||||
testRuleHasEffectButSortsStill : function () {
|
||||
|
||||
var queries = [
|
||||
"FOR v IN " + colName + " FILTER v.a == 1 SORT v.a, v.c RETURN [v.a, v.b, v.c]",
|
||||
"FOR v IN " + colName + " LET x = (FOR w IN " + colNameOther + " SORT w.j, w.h RETURN w.f ) SORT v.a RETURN [v.a]"
|
||||
];
|
||||
var QResults = [];
|
||||
var i = 0;
|
||||
queries.forEach(function(query) {
|
||||
// require("internal").print(query);
|
||||
var result = AQL_EXPLAIN(query, { }, paramIFS);
|
||||
// require("internal").print(result);
|
||||
assertEqual([ ruleName ], result.plan.rules);
|
||||
hasIndexRangeNode_WithRanges(result, false);
|
||||
hasSortNode(result);
|
||||
QResults[0] = AQL_EXECUTE(query, { }, paramNone).json;
|
||||
QResults[1] = AQL_EXECUTE(query, { }, paramIFS ).json;
|
||||
assertTrue(isEqual(QResults[0], QResults[1]), "Result " + i + " is Equal?");
|
||||
i++;
|
||||
});
|
||||
|
||||
},
|
||||
*/
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
/// @brief test generated plans
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
/*
|
||||
testPlans : function () {
|
||||
var plans = [
|
||||
|
||||
[ "FOR i IN 1..10 LET a = 1 RETURN i", [ "SingletonNode", "CalculationNode", "CalculationNode", "EnumerateListNode", "ReturnNode" ] ],
|
||||
[ "FOR i IN 1..10 FILTER i == 1 LET a = 1 RETURN i", [ "SingletonNode", "CalculationNode", "CalculationNode", "EnumerateListNode", "CalculationNode", "FilterNode", "ReturnNode" ] ]
|
||||
];
|
||||
|
||||
plans.forEach(function(plan) {
|
||||
var result = AQL_EXPLAIN(plan[0], { }, paramIFS);
|
||||
assertEqual([ ruleName ], result.plan.rules, plan[0]);
|
||||
assertEqual(plan[1], helper.getCompactPlan(result).map(function(node) { return node.type; }), plan[0]);
|
||||
});
|
||||
},
|
||||
*/
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
/// @brief test results
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
/*
|
||||
testResults : function () {
|
||||
var queries = [
|
||||
[ "FOR i IN 1..10 LET a = 1 RETURN i", [ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 ] ],
|
||||
[ "FOR i IN 1..10 LET a = 1 RETURN a", [ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 ] ],
|
||||
[ "FOR i IN 1..10 FILTER i == 1 LET a = 1 RETURN i", [ 1 ] ],
|
||||
[ "FOR i IN 1..10 FILTER i == 1 LET a = 1 RETURN a", [ 1 ] ],
|
||||
];
|
||||
|
||||
queries.forEach(function(query) {
|
||||
var planDisabled = AQL_EXPLAIN(query[0], { }, { optimizer: { rules: [ "+all", "-" + ruleName ] } });
|
||||
var planEnabled = AQL_EXPLAIN(query[0], { }, paramIFS);
|
||||
|
||||
var resultDisabled = AQL_EXECUTE(query[0], { }, { optimizer: { rules: [ "+all", "-" + ruleName ] } });
|
||||
var resultEnabled = AQL_EXECUTE(query[0], { }, paramIFS);
|
||||
|
||||
assertTrue(planDisabled.plan.rules.indexOf(ruleName) === -1, query[0]);
|
||||
assertTrue(planEnabled.plan.rules.indexOf(ruleName) !== -1, query[0]);
|
||||
|
||||
assertEqual(resultDisabled.json, query[1], query[0]);
|
||||
assertEqual(resultEnabled.json, query[1], query[0]);
|
||||
});
|
||||
},
|
||||
*/
|
||||
/*
|
||||
// -----------------------------------------------------------------------------
|
||||
// --SECTION-- sortToIndexRange
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
/// @brief this sort is replaceable by an index.
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
testSortIndexable: function () {
|
||||
|
||||
var query = "FOR v IN " + colName + " SORT v.a RETURN [v.a, v.b]";
|
||||
|
||||
var XPresult;
|
||||
var QResults=[];
|
||||
|
||||
// we have to re-sort here, because of the index has one more sort criteria.
|
||||
QResults[0] = AQL_EXECUTE(query, { }, paramNone).json.sort(sortArray);
|
||||
|
||||
// -> use-index-for-sort alone.
|
||||
XPresult = AQL_EXPLAIN(query, { }, paramNone);
|
||||
|
||||
XPresult = AQL_EXPLAIN(query, { }, paramIFS);
|
||||
QResults[1] = AQL_EXECUTE(query, { }, paramIFS).json;
|
||||
// our rule should have been applied.
|
||||
assertEqual([ ruleName ], XPresult.plan.rules);
|
||||
// The sortnode and its calculation node should have been removed.
|
||||
hasNoSortNode(XPresult);
|
||||
hasCalculationNodes(XPresult, 1);
|
||||
// The IndexRangeNode created by this rule is simple; it shouldn't have ranges.
|
||||
hasIndexRangeNode_WithRanges(XPresult, false);
|
||||
|
||||
assertTrue(isEqual(QResults[0], QResults[1]), "Query results are equal?");
|
||||
|
||||
},
|
||||
|
||||
|
||||
|
||||
testSortMoreThanIndexed: function () {
|
||||
|
||||
var query = "FOR v IN " + colName + " FILTER v.a == 1 SORT v.a, v.c RETURN [v.a, v.b, v.c]";
|
||||
// no index can be used for v.c -> sort has to remain in place!
|
||||
var XPresult;
|
||||
var QResults=[];
|
||||
var i;
|
||||
|
||||
// the index we will compare to sorts by a & b, so we need to re-sort the result here to accomplish similarity.
|
||||
QResults[0] = AQL_EXECUTE(query, { }, paramNone).json.sort(sortArray);
|
||||
|
||||
|
||||
// -> use-index-for-sort alone.
|
||||
QResults[1] = AQL_EXECUTE(query, { }, paramIFS).json;
|
||||
XPresult = AQL_EXPLAIN(query, { }, paramIFS);
|
||||
// our rule should be there.
|
||||
assertEqual([ ruleName ], XPresult.plan.rules);
|
||||
// The sortnode and its calculation node should have been removed.
|
||||
// require("internal").print(XPresult);
|
||||
|
||||
hasSortNode(XPresult);
|
||||
hasCalculationNodes(XPresult, 4);
|
||||
// The IndexRangeNode created by this rule is simple; it shouldn't have ranges.
|
||||
hasIndexRangeNode_WithRanges(XPresult, false);
|
||||
|
||||
// -> combined use-index-for-sort and use-index-range
|
||||
// use-index-range superseedes use-index-for-sort
|
||||
QResults[2] = AQL_EXECUTE(query, { }, paramBoth).json;
|
||||
XPresult = AQL_EXPLAIN(query, { }, paramBoth);
|
||||
|
||||
assertEqual([ secondRuleName ], XPresult.plan.rules.sort());
|
||||
// The sortnode and its calculation node should not have been removed.
|
||||
hasSortNode(XPresult);
|
||||
hasCalculationNodes(XPresult, 4);
|
||||
// The IndexRangeNode created by this rule should be more clever, it knows the ranges.
|
||||
hasIndexRangeNode_WithRanges(XPresult, true);
|
||||
|
||||
// -> use-index-range alone.
|
||||
QResults[3] = AQL_EXECUTE(query, { }, paramIR).json;
|
||||
XPresult = AQL_EXPLAIN(query, { }, paramIR);
|
||||
assertEqual([ secondRuleName ], XPresult.plan.rules);
|
||||
// the sortnode and its calculation node should be there.
|
||||
|
||||
hasSortNode(XPresult);
|
||||
hasCalculationNodes(XPresult,4);
|
||||
// we should be able to find exactly one sortnode property - its a Calculation node.
|
||||
var sortProperty = findReferencedNodes(XPresult, findExecutionNodes(XPresult, "SortNode")[0]);
|
||||
assertEqual(sortProperty.length, 2);
|
||||
assertEqual(sortProperty[0].type, "CalculationNode");
|
||||
assertEqual(sortProperty[1].type, "CalculationNode");
|
||||
// The IndexRangeNode created by this rule should be more clever, it knows the ranges.
|
||||
hasIndexRangeNode_WithRanges(XPresult, true);
|
||||
|
||||
for (i = 1; i < 4; i++) {
|
||||
assertTrue(isEqual(QResults[0], QResults[i]), "Result " + i + " is Equal?");
|
||||
}
|
||||
},
|
||||
|
||||
testRangeSuperseedsSort: function () {
|
||||
|
||||
var query = "FOR v IN " + colName + " FILTER v.a == 1 SORT v.a RETURN [v.a, v.b, v.c]";
|
||||
|
||||
var XPresult;
|
||||
var QResults=[];
|
||||
var i;
|
||||
|
||||
// the index we will compare to sorts by a & b, so we need to re-sort the result here to accomplish similarity.
|
||||
QResults[0] = AQL_EXECUTE(query, { }, paramNone).json.sort(sortArray);
|
||||
|
||||
// -> use-index-for-sort alone.
|
||||
QResults[1] = AQL_EXECUTE(query, { }, paramIFS).json;
|
||||
XPresult = AQL_EXPLAIN(query, { }, paramIFS);
|
||||
// our rule should be there.
|
||||
assertEqual([ ruleName ], XPresult.plan.rules);
|
||||
// The sortnode and its calculation node should have been removed.
|
||||
hasNoSortNode(XPresult);
|
||||
hasCalculationNodes(XPresult, 2);
|
||||
// The IndexRangeNode created by this rule is simple; it shouldn't have ranges.
|
||||
hasIndexRangeNode_WithRanges(XPresult, false);
|
||||
|
||||
// -> combined use-index-for-sort and use-index-range
|
||||
QResults[2] = AQL_EXECUTE(query, { }, paramBoth).json;
|
||||
XPresult = AQL_EXPLAIN(query, { }, paramBoth);
|
||||
assertEqual([ secondRuleName, ruleName ].sort(), XPresult.plan.rules.sort());
|
||||
// The sortnode and its calculation node should have been removed.
|
||||
hasNoSortNode(XPresult);
|
||||
hasCalculationNodes(XPresult, 2);
|
||||
// The IndexRangeNode created by this rule should be more clever, it knows the ranges.
|
||||
hasIndexRangeNode_WithRanges(XPresult, true);
|
||||
|
||||
// -> use-index-range alone.
|
||||
QResults[3] = AQL_EXECUTE(query, { }, paramIR).json;
|
||||
XPresult = AQL_EXPLAIN(query, { }, paramIR);
|
||||
assertEqual([ secondRuleName ], XPresult.plan.rules);
|
||||
// the sortnode and its calculation node should be there.
|
||||
hasSortNode(XPresult);
|
||||
hasCalculationNodes(XPresult, 3);
|
||||
// we should be able to find exactly one sortnode property - its a Calculation node.
|
||||
var sortProperty = findReferencedNodes(XPresult, findExecutionNodes(XPresult, "SortNode")[0]);
|
||||
assertEqual(sortProperty.length, 1);
|
||||
isNodeType(sortProperty[0], "CalculationNode");
|
||||
// The IndexRangeNode created by this rule should be more clever, it knows the ranges.
|
||||
hasIndexRangeNode_WithRanges(XPresult, true);
|
||||
|
||||
|
||||
for (i = 1; i < 4; i++) {
|
||||
assertTrue(isEqual(QResults[0], QResults[i]), "Result " + i + " is Equal?");
|
||||
}
|
||||
|
||||
},
|
||||
|
||||
*/
|
||||
// -----------------------------------------------------------------------------
|
||||
// --SECTION-- toIndexRange
|
||||
// -----------------------------------------------------------------------------
|
||||
/*
|
||||
testRangeEquals: function () {
|
||||
|
||||
var query = "FOR v IN " + colName + " FILTER v.a == 1 RETURN [v.a, v.b, v.c]";
|
||||
|
||||
var XPresult;
|
||||
var QResults=[];
|
||||
var i;
|
||||
|
||||
// the index we will compare to sorts by a & b, so we need to re-sort the result here to accomplish similarity.
|
||||
QResults[0] = AQL_EXECUTE(query, { }, paramNone).json;
|
||||
|
||||
// -> use-index-range alone.
|
||||
QResults[1] = AQL_EXECUTE(query, { }, paramIR).json;
|
||||
XPresult = AQL_EXPLAIN(query, { }, paramIR);
|
||||
assertEqual([ secondRuleName ], XPresult.plan.rules);
|
||||
// the sortnode and its calculation node should be there.
|
||||
hasCalculationNodes(XPresult, 2);
|
||||
|
||||
// The IndexRangeNode created by this rule should be more clever, it knows the ranges.
|
||||
var RAs = getRangeAttributes(XPresult);
|
||||
//require("internal").print(RAs);
|
||||
var first = getRangeAttribute(RAs, "v", "a", 1);
|
||||
|
||||
//require("internal").print(first);
|
||||
assertEqual(first.low.bound.vType, "int", "Type is int");
|
||||
assertEqual(first.high.bound.vType, "int", "Type is int");
|
||||
assertEqual(first.low.bound.value, first.high.bound.value);
|
||||
|
||||
for (i = 1; i < 2; i++) {
|
||||
assertTrue(isEqual(QResults[0].sort(sortArray), QResults[i]), "Result " + i + " is Equal?");
|
||||
}
|
||||
|
||||
},
|
||||
|
||||
|
||||
testRangeLessThen: function () {
|
||||
var query = "FOR v IN " + colName + " FILTER v.a < 5 RETURN [v.a, v.b]";
|
||||
|
||||
var XPresult;
|
||||
var QResults=[];
|
||||
|
||||
// the index we will compare to sorts by a & b, so we need to re-sort the result here to accomplish similarity.
|
||||
QResults[0] = AQL_EXECUTE(query, { }, paramNone).json.sort(sortArray);
|
||||
|
||||
// -> use-index-range alone.
|
||||
QResults[1] = AQL_EXECUTE(query, { }, paramIR).json;
|
||||
|
||||
XPresult = AQL_EXPLAIN(query, { }, paramIR);
|
||||
assertEqual([ secondRuleName ], XPresult.plan.rules);
|
||||
// the sortnode and its calculation node should be there.
|
||||
hasCalculationNodes(XPresult, 2);
|
||||
|
||||
// The IndexRangeNode created by this rule should be more clever, it knows the ranges.
|
||||
var RAs = getRangeAttributes(XPresult);
|
||||
var first = getRangeAttribute(RAs, "v", "a", 1);
|
||||
assertEqual(first.high.bound.vType, "int", "Type is int");
|
||||
assertEqual(first.high.bound.value, 5, "proper value was set");
|
||||
|
||||
assertTrue(isEqual(QResults[0], QResults[1]), "Results are Equal?");
|
||||
},
|
||||
*/
|
||||
testRangeGreaterThen: function () {
|
||||
/*
|
||||
TODO: can skiplist do a range?
|
||||
var query = "FOR v IN " + colName + " FILTER v.a < 5 RETURN [v.a, v.b, v.c]";
|
||||
var query = "FOR v IN " + colName + " FILTER v.a > 1 && v.a < 5 RETURN [v.a, v.b, v.c]";
|
||||
var query = "FOR v IN " + colName + " FILTER v.c > 1 && v.c < 5 RETURN [v.a, v.b, v.c]";
|
||||
*/
|
||||
var query = "FOR v IN " + colName + " FILTER v.a > 5 RETURN [v.a, v.b]";
|
||||
/*
|
||||
var XPresult;
|
||||
var QResults=[];
|
||||
|
||||
// the index we will compare to sorts by a & b, so we need to re-sort the result here to accomplish similarity.
|
||||
QResults[0] = AQL_EXECUTE(query, { }, paramNone).json.sort(sortArray);
|
||||
|
||||
// -> use-index-range alone.
|
||||
QResults[1] = AQL_EXECUTE(query, { }, paramIR).json;
|
||||
|
||||
XPresult = AQL_EXPLAIN(query, { }, paramIR);
|
||||
assertEqual([ secondRuleName ], XPresult.plan.rules);
|
||||
// the sortnode and its calculation node should be there.
|
||||
hasCalculationNodes(XPresult, 2);
|
||||
|
||||
// The IndexRangeNode created by this rule should be more clever, it knows the ranges.
|
||||
var RAs = getRangeAttributes(XPresult);
|
||||
// require("internal").print(RAs);
|
||||
var first = getRangeAttribute(RAs, "v", "a", 1);
|
||||
|
||||
// require("internal").print(first);
|
||||
assertEqual(first.low.bound.vType, "int", "Type is int");
|
||||
assertEqual(first.low.bound.value, 5, "proper value was set");
|
||||
|
||||
assertTrue(isEqual(QResults[0], QResults[1]), "Results are Equal?");
|
||||
*/
|
||||
},
|
||||
|
||||
testRangeBandpass: function () {
|
||||
var query = "FOR v IN " + colName + " FILTER v.a > 4 && v.a < 10 RETURN [v.a, v.b]";
|
||||
/*
|
||||
var XPresult;
|
||||
var QResults=[];
|
||||
|
||||
// the index we will compare to sorts by a & b, so we need to re-sort the result here to accomplish similarity.
|
||||
QResults[0] = AQL_EXECUTE(query, { }, paramNone).json.sort(sortArray);
|
||||
|
||||
// -> use-index-range alone.
|
||||
QResults[1] = AQL_EXECUTE(query, { }, paramIR).json;
|
||||
|
||||
XPresult = AQL_EXPLAIN(query, { }, paramIR);
|
||||
assertEqual([ secondRuleName ], XPresult.plan.rules);
|
||||
// the sortnode and its calculation node should be there.
|
||||
hasCalculationNodes(XPresult, 2);
|
||||
|
||||
// The IndexRangeNode created by this rule should be more clever, it knows the ranges.
|
||||
var RAs = getRangeAttributes(XPresult);
|
||||
// require("internal").print(RAs);
|
||||
var first = getRangeAttribute(RAs, "v", "a", 1);
|
||||
|
||||
// require("internal").print(first);
|
||||
assertEqual(first.low.bound.vType, "int", "Type is int");
|
||||
assertEqual(first.low.bound.value, 4, "proper value was set");
|
||||
assertEqual(first.high.bound.vType, "int", "Type is int");
|
||||
assertEqual(first.high.bound.value, 10, "proper value was set");
|
||||
|
||||
assertTrue(isEqual(QResults[0], QResults[1]), "Results are Equal?");
|
||||
*/
|
||||
},
|
||||
|
||||
testRangeBandpassInvalid: function () {
|
||||
/* TODO: this doesn't do anything. should it simply flush that range since its empty? or even raise?
|
||||
var query = "FOR v IN " + colName + " FILTER v.a > 7 && v.a < 4 RETURN [v.a, v.b]";
|
||||
|
||||
var XPresult;
|
||||
var QResults=[];
|
||||
|
||||
// the index we will compare to sorts by a & b, so we need to re-sort the result here to accomplish similarity.
|
||||
QResults[0] = AQL_EXECUTE(query, { }, paramNone).json.sort(sortArray);
|
||||
|
||||
// -> use-index-range alone.
|
||||
QResults[1] = AQL_EXECUTE(query, { }, paramIR).json;
|
||||
|
||||
|
||||
XPresult = AQL_EXPLAIN(query, { }, paramIR);
|
||||
require("internal").print(XPresult);
|
||||
assertEqual([ secondRuleName ], XPresult.plan.rules);
|
||||
// the sortnode and its calculation node should be there.
|
||||
hasCalculationNodes(XPresult, 2);
|
||||
|
||||
// The IndexRangeNode created by this rule should be more clever, it knows the ranges.
|
||||
var RAs = getRangeAttributes(XPresult);
|
||||
// require("internal").print(RAs);
|
||||
var first = getRangeAttribute(RAs, "v", "a", 1);
|
||||
|
||||
require("internal").print(first);
|
||||
// require("internal").print(first);
|
||||
assertEqual(first.low.bound.vType, "int", "Type is int");
|
||||
assertEqual(first.low.bound.value, 4, "proper value was set");
|
||||
assertEqual(first.high.bound.vType, "int", "Type is int");
|
||||
assertEqual(first.high.bound.value, 10, "proper value was set");
|
||||
|
||||
assertTrue(isEqual(QResults[0], QResults[1]), "Results are Equal?");
|
||||
*/
|
||||
},
|
||||
|
||||
|
||||
testRangeBandstop: function () {
|
||||
/* TODO: OR isn't implemented
|
||||
var query = "FOR v IN " + colName + " FILTER v.a < 5 || v.a > 10 RETURN [v.a, v.b]";
|
||||
|
||||
var XPresult;
|
||||
var QResults=[];
|
||||
|
||||
// the index we will compare to sorts by a & b, so we need to re-sort the result here to accomplish similarity.
|
||||
QResults[0] = AQL_EXECUTE(query, { }, paramNone).json.sort(sortArray);
|
||||
|
||||
// -> use-index-range alone.
|
||||
QResults[1] = AQL_EXECUTE(query, { }, paramIR).json;
|
||||
|
||||
XPresult = AQL_EXPLAIN(query, { }, paramIR);
|
||||
assertEqual([ secondRuleName ], XPresult.plan.rules);
|
||||
// the sortnode and its calculation node should be there.
|
||||
hasCalculationNodes(XPresult, 2);
|
||||
|
||||
// The IndexRangeNode created by this rule should be more clever, it knows the ranges.
|
||||
var RAs = getRangeAttributes(XPresult);
|
||||
require("internal").print(RAs);
|
||||
var first = getRangeAttribute(RAs, "v", "a", 1);
|
||||
|
||||
require("internal").print(first);
|
||||
assertEqual(first.low.bound.vType, "int", "Type is int");
|
||||
assertEqual(first.low.bound.value, 5, "proper value was set");
|
||||
|
||||
require("internal").print(QResults[0]);
|
||||
require("internal").print(QResults[1]);
|
||||
assertTrue(isEqual(QResults[0], QResults[1]), "Results are Equal?");
|
||||
*/
|
||||
},
|
||||
|
||||
testMultiRangeBandpass: function () {
|
||||
/* TODO: OR isn't implemented
|
||||
var query = "FOR v IN " + colName + " FILTER ((v.a > 3 && v.a < 5) || (v.a > 4 && v.a < 7)) RETURN [v.a, v.b]";
|
||||
|
||||
var XPresult;
|
||||
var QResults=[];
|
||||
|
||||
// the index we will compare to sorts by a & b, so we need to re-sort the result here to accomplish similarity.
|
||||
QResults[0] = AQL_EXECUTE(query, { }, paramNone).json.sort(sortArray);
|
||||
|
||||
// -> use-index-range alone.
|
||||
QResults[1] = AQL_EXECUTE(query, { }, paramIR).json;
|
||||
|
||||
XPresult = AQL_EXPLAIN(query, { }, paramIR);
|
||||
assertEqual([ secondRuleName ], XPresult.plan.rules);
|
||||
// the sortnode and its calculation node should be there.
|
||||
hasCalculationNodes(XPresult, 2);
|
||||
|
||||
// The IndexRangeNode created by this rule should be more clever, it knows the ranges.
|
||||
var RAs = getRangeAttributes(XPresult);
|
||||
// require("internal").print(RAs);
|
||||
var first = getRangeAttribute(RAs, "v", "a", 1);
|
||||
|
||||
// require("internal").print(first);
|
||||
assertEqual(first.low.bound.vType, "int", "Type is int");
|
||||
assertEqual(first.low.bound.value, 4, "proper value was set");
|
||||
assertEqual(first.high.bound.vType, "int", "Type is int");
|
||||
assertEqual(first.high.bound.value, 10, "proper value was set");
|
||||
|
||||
assertTrue(isEqual(QResults[0], QResults[1]), "Results are Equal?");
|
||||
*/
|
||||
},
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// --SECTION-- toIndexRange
|
||||
// -----------------------------------------------------------------------------
|
||||
testDSRuleHasEffect : function () {
|
||||
|
||||
var queries = [
|
||||
|
||||
"FOR v IN " + colName + " SORT v.c SORT v.c RETURN [v.a, v.b]",
|
||||
"FOR v IN " + colName + " SORT v.c SORT v.c , v.d RETURN [v.a, v.b]",
|
||||
"FOR v IN " + colName + " SORT v.c, v.d SORT v.c RETURN [v.a, v.b]",
|
||||
"FOR v IN " + colName + " SORT v.c SORT v.c, v.d SORT v.c RETURN [v.a, v.b]",
|
||||
"FOR v IN " + colName + " SORT v.c, v.d SORT v.c SORT v.c, v.d SORT v.c RETURN [v.a, v.b]",
|
||||
|
||||
"FOR v IN " + colName + " SORT v.c FILTER v.c > 3 SORT v.c RETURN [v.a, v.b]"
|
||||
/*
|
||||
// todo: we use an index anyways right now. "FOR v IN " + colName + " SORT v.a DESC RETURN [v.a, v.b]",// currently only ASC supported.
|
||||
"FOR v IN " + colName + " SORT v.b, v.a RETURN [v.a, v.b]",
|
||||
"FOR v IN " + colName + " SORT v.c RETURN [v.a, v.b]",
|
||||
"FOR v IN " + colName + " SORT v.a + 1 RETURN [v.a, v.b]",
|
||||
"FOR v IN " + colName + " SORT CONCAT(TO_STRING(v.a), \"lol\") RETURN [v.a, v.b]",
|
||||
"FOR v IN " + colName + " FILTER v.a > 2 LIMIT 3 SORT v.a RETURN [v.a, v.b] ", // TODO: limit blocks sort atm.
|
||||
"FOR v IN " + colName + " FOR w IN " + colNameOther + " SORT v.a RETURN [v.a, v.b]"
|
||||
*/
|
||||
];
|
||||
|
||||
queries.forEach(function(query) {
|
||||
|
||||
// require("internal").print(query);
|
||||
var result = AQL_EXPLAIN(query, { }, paramRS);
|
||||
// require("internal").print(result);
|
||||
assertEqual([thirdRuleName], result.plan.rules, query);
|
||||
});
|
||||
}
|
||||
//*/
|
||||
};
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
/// @brief executes the test suite
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
jsunity.run(optimizerRuleTestSuite);
|
||||
|
||||
return jsunity.done();
|
||||
|
||||
// Local Variables:
|
||||
// mode: outline-minor
|
||||
// outline-regexp: "^\\(/// @brief\\|/// @addtogroup\\|// --SECTION--\\|/// @page\\|/// @}\\)"
|
||||
// End:
|
|
@ -186,6 +186,10 @@ bool ReadlineShell::close() {
|
|||
|
||||
bool res = writeHistory();
|
||||
|
||||
clear_history();
|
||||
HIST_ENTRY** hist = history_list();
|
||||
free(hist);
|
||||
|
||||
#ifndef __APPLE__
|
||||
// reset state of the terminal to what it was before readline()
|
||||
rl_cleanup_after_signal();
|
||||
|
@ -225,7 +229,8 @@ void ReadlineShell::addHistory(char const* str) {
|
|||
if (current_history()) {
|
||||
do {
|
||||
if (strcmp(current_history()->line, str) == 0) {
|
||||
remove_history(where_history());
|
||||
HIST_ENTRY* e = remove_history(where_history());
|
||||
free_history_entry(e);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue