/*jshint strict: false */ /*global assertTrue, assertEqual, fail, AQL_EXECUTE, AQL_PARSE, AQL_EXPLAIN, AQL_EXECUTEJSON */ //////////////////////////////////////////////////////////////////////////////// /// @brief aql test helper functions /// /// @file /// /// DISCLAIMER /// /// Copyright 2011-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 2013, triAGENS GmbH, Cologne, Germany //////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////// /// @brief normalize a single row result //////////////////////////////////////////////////////////////////////////////// function normalizeRow (row, recursive) { if (row !== null && typeof row === 'object' && ! Array.isArray(row)) { var keys = Object.keys(row); keys.sort(); var i, n = keys.length, out = { }; for (i = 0; i < n; ++i) { var key = keys[i]; if (key[0] !== '_') { out[key] = row[key]; } } return out; } if (recursive && Array.isArray(row)) { row = row.map(normalizeRow); } return row; } //////////////////////////////////////////////////////////////////////////////// /// @brief return the parse results for a query //////////////////////////////////////////////////////////////////////////////// function getParseResults (query) { return AQL_PARSE(query); } //////////////////////////////////////////////////////////////////////////////// /// @brief assert a specific error code when parsing a query //////////////////////////////////////////////////////////////////////////////// function assertParseError (errorCode, query) { try { getParseResults(query); fail(); } catch (e) { assertTrue(e.errorNum !== undefined, "unexpected error format"); assertEqual(errorCode, e.errorNum, "unexpected error code (" + e.errorMessage + "): "); } } //////////////////////////////////////////////////////////////////////////////// /// @brief return the results of a query explanation //////////////////////////////////////////////////////////////////////////////// function getQueryExplanation (query, bindVars) { return AQL_EXPLAIN(query, bindVars); } //////////////////////////////////////////////////////////////////////////////// /// @brief return the results of a modify-query //////////////////////////////////////////////////////////////////////////////// function getModifyQueryResults (query, bindVars) { var queryResult = AQL_EXECUTE(query, bindVars); return queryResult.stats; } //////////////////////////////////////////////////////////////////////////////// /// @brief return the results of a modify-query //////////////////////////////////////////////////////////////////////////////// function getModifyQueryResultsRaw (query, bindVars) { var queryResult = AQL_EXECUTE(query, bindVars); return queryResult; } //////////////////////////////////////////////////////////////////////////////// /// @brief return the results of a query, version //////////////////////////////////////////////////////////////////////////////// function getRawQueryResults (query, bindVars) { var queryResult = AQL_EXECUTE(query, bindVars, { count: true, batchSize : 3000 }); return queryResult.json; } //////////////////////////////////////////////////////////////////////////////// /// @brief return the results of a query in a normalized way //////////////////////////////////////////////////////////////////////////////// function getQueryResults (query, bindVars, recursive) { var result = getRawQueryResults(query, bindVars); if (Array.isArray(result)) { result = result.map(function (row) { return normalizeRow(row, recursive); }); } return result; } function typeName (value) { if (value === null) { return "null"; } if (value === undefined) { return "undefined"; } if (Array.isArray(value)) { return "array"; } var type = typeof value; if (type === "object") { return "object"; } if (type === "string") { return "string"; } if (type === "boolean") { return "boolean"; } if (type === "number") { return "number"; } throw "unknown variable type"; } function isEqual (lhs, rhs) { var ltype = typeName(lhs), rtype = typeName(rhs), i; if (ltype !== rtype) { return false; } if (ltype === "null" || ltype === "undefined") { return true; } if (ltype === "array") { if (lhs.length !== rhs.length) { return false; } for (i = 0; i < lhs.length; ++i) { if (! isEqual(lhs[i], rhs[i])) { return false; } } return true; } if (ltype === "object") { var lkeys = Object.keys(lhs), rkeys = Object.keys(rhs); if (lkeys.length !== rkeys.length) { return false; } for (i = 0; i < lkeys.length; ++i) { var key = lkeys[i]; if (! isEqual(lhs[key], rhs[key])) { return false; } } return true; } if (ltype === "boolean") { return (lhs === rhs); } if (ltype === "string") { return (lhs === rhs); } if (ltype === "number") { if (isNaN(lhs)) { return isNaN(rhs); } if (! isFinite(lhs)) { return (lhs === rhs); } return (lhs.toFixed(10) === rhs.toFixed(10)); } throw "unknown variable type"; } //////////////////////////////////////////////////////////////////////////////// /// @brief assert a specific error code when running a query //////////////////////////////////////////////////////////////////////////////// function assertQueryError (errorCode, query, bindVars) { try { getQueryResults(query, bindVars); fail(); } catch (e) { assertTrue(e.errorNum !== undefined, "unexpected error format while calling [" + query + "]"); assertEqual(errorCode, e.errorNum, "unexpected error code (" + e.errorMessage + " while executing: '" + query + "' expecting: " + errorCode + "): "); } } //////////////////////////////////////////////////////////////////////////////// /// @brief assert a specific warning running a query //////////////////////////////////////////////////////////////////////////////// function assertQueryWarningAndNull (errorCode, query, bindVars) { var result = AQL_EXECUTE(query, bindVars), i, found = { }; for (i = 0; i < result.warnings.length; ++i) { found[result.warnings[i].code] = true; } assertTrue(found[errorCode]); assertEqual([ null ], result.json); } //////////////////////////////////////////////////////////////////////////////// /// @brief assert a specific warning running a query //////////////////////////////////////////////////////////////////////////////// function assertQueryWarningAndFalse (errorCode, query, bindVars) { var result = AQL_EXECUTE(query, bindVars), i, found = { }; for (i = 0; i < result.warnings.length; ++i) { found[result.warnings[i].code] = true; } assertTrue(found[errorCode]); assertEqual([ false ], result.json); } //////////////////////////////////////////////////////////////////////////////// /// @brief get a linearized version of an execution plan //////////////////////////////////////////////////////////////////////////////// function getLinearizedPlan (explainResult) { var nodes = explainResult.plan.nodes, i; var lookup = { }, deps = { }; for (i = 0; i < nodes.length; ++i) { var node = nodes[i]; lookup[node.id] = node; var dependency = -1; if (node.dependencies.length > 0) { dependency = node.dependencies[0]; } deps[dependency] = node.id; } var current = -1; var out = [ ]; while (true) { if (! deps.hasOwnProperty(current)) { break; } var n = lookup[deps[current]]; current = n.id; out.push(n); } return out; } function getCompactPlan (explainResult) { var out = [ ]; function buildExpression (node) { var out = node.type; if (node.hasOwnProperty("name")) { out += "[" + node.name + "]"; } if (node.hasOwnProperty("value")) { out += "[" + node.value + "]"; } if (Array.isArray(node.subNodes)) { out += "("; node.subNodes.forEach(function (node, i) { if (i > 0) { out += ", "; } out += buildExpression(node); }); out += ")"; } return out; } getLinearizedPlan(explainResult).forEach(function (node) { var data = { type: node.type }; if (node.expression) { data.expression = buildExpression(node.expression); } if (node.outVariable) { data.outVariable = node.outVariable.name; } out.push(data); }); return out; } function findExecutionNodes(plan, nodetype) { var matches = []; var what = plan; if (plan.hasOwnProperty("plan")) { what = plan.plan; } what.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; } function getQueryMultiplePlansAndExecutions (query, bindVars, testObject, debug) { var printYaml = function (plan) { require("internal").print(require("js-yaml").safeDump(plan)); }; var i; var plans = []; var allPlans = []; var results = []; var resetTest = false; var paramNone = { optimizer: { rules: [ "-all" ]}, verbosePlans: true}; var paramAllPlans = { allPlans : true, verbosePlans: true}; if (testObject !== undefined) { resetTest = true; } if (debug === undefined) { debug = false; } // first fetch the unmodified version if (debug) { require("internal").print("Analyzing Query unoptimized: " + query); } plans[0] = AQL_EXPLAIN(query, bindVars, paramNone); // then all of the ones permuted by by the optimizer. if (debug) { require("internal").print("Unoptimized Plan (0):"); printYaml(plans [0]); } allPlans = AQL_EXPLAIN(query, bindVars, paramAllPlans); for (i = 0; i < allPlans.plans.length; i++) { if (debug) { require("internal").print("Optimized Plan [" + (i + 1) + "]:"); printYaml(allPlans.plans [i]); } plans[i + 1] = { plan: allPlans.plans[i] }; } // Now execute each of these variations. for (i = 0; i < plans.length; i++) { if (debug) { require("internal").print("Executing Plan No: " + i + "\n"); } if (resetTest) { if (debug) { require("internal").print("\nFLUSHING\n"); } testObject.tearDown(); testObject.setUp(); if (debug) { require("internal").print("\n" + i + " FLUSH DONE\n"); } } results[i] = AQL_EXECUTEJSON(plans[i].plan, paramNone); // ignore these statistics for comparisons delete results[i].stats.scannedFull; delete results[i].stats.scannedIndex; delete results[i].stats.filtered; delete results[i].stats.executionTime; if (debug) { require("internal").print("\n" + i + " DONE\n"); } } if (debug) { require("internal").print("done\n"); } return {'plans': plans, 'results': results}; } function removeAlwaysOnClusterRules (rules) { var pos; var copy = []; for (pos = 0; pos < rules.length; pos++) { if (rules[pos] !== "scatter-in-cluster" && rules[pos] !== "distribute-in-cluster") { copy.push(rules[pos]); } } return copy; } function removeClusterNodes (nodeTypes) { return nodeTypes.filter(function(nodeType) { return ([ "ScatterNode", "GatherNode", "DistributeNode", "RemoteNode" ].indexOf(nodeType) === -1); }); } exports.isEqual = isEqual; exports.getParseResults = getParseResults; exports.assertParseError = assertParseError; exports.getQueryExplanation = getQueryExplanation; exports.getModifyQueryResults = getModifyQueryResults; exports.getModifyQueryResultsRaw = getModifyQueryResultsRaw; exports.getRawQueryResults = getRawQueryResults; exports.getQueryResults = getQueryResults; exports.assertQueryError = assertQueryError; exports.assertQueryWarningAndNull = assertQueryWarningAndNull; exports.assertQueryWarningAndFalse = assertQueryWarningAndFalse; exports.getLinearizedPlan = getLinearizedPlan; exports.getCompactPlan = getCompactPlan; exports.findExecutionNodes = findExecutionNodes; exports.findReferencedNodes = findReferencedNodes; exports.getQueryMultiplePlansAndExecutions = getQueryMultiplePlansAndExecutions; exports.removeAlwaysOnClusterRules = removeAlwaysOnClusterRules; exports.removeClusterNodes = removeClusterNodes;