1
0
Fork 0

add cacheability info for explain

This commit is contained in:
jsteemann 2016-01-03 00:52:36 +01:00
parent 9eb51e9a20
commit c20cf66e93
8 changed files with 140 additions and 11 deletions

View File

@ -1007,7 +1007,11 @@ QueryResult Query::explain () {
it->planRegisters();
out.add(it->toJson(parser.ast(), TRI_UNKNOWN_MEM_ZONE, verbosePlans()));
}
result.json = out.steal();
// cacheability not available here
result.cached = false;
}
else {
// Now plan and all derived plans belong to the optimizer
@ -1017,6 +1021,10 @@ QueryResult Query::explain () {
bestPlan->findVarUsage();
bestPlan->planRegisters();
result.json = bestPlan->toJson(parser.ast(), TRI_UNKNOWN_MEM_ZONE, verbosePlans()).steal();
// cacheability
result.cached = (_queryString != nullptr && _queryLength > 0 &&
! _isModificationQuery && _warnings.empty() && _ast->root()->isCacheable());
}
_trx->commit();

View File

@ -1235,7 +1235,9 @@ static void JS_ExplainAql (const v8::FunctionCallbackInfo<v8::Value>& args) {
}
else {
result->Set(TRI_V8_ASCII_STRING("plan"), TRI_ObjectJson(isolate, queryResult.json));
result->Set(TRI_V8_ASCII_STRING("cacheable"), v8::Boolean::New(isolate, queryResult.cached));
}
if (queryResult.clusterplan != nullptr) {
result->Set(TRI_V8_ASCII_STRING("clusterplans"), TRI_ObjectJson(isolate, queryResult.clusterplan));
}

View File

@ -93,6 +93,10 @@ var ERRORS = require("internal").errors;
/// The result will also contain an attribute *warnings*, which is an array of
/// warnings that occurred during optimization or execution plan creation. Additionally,
/// a *stats* attribute is contained in the result with some optimizer statistics.
/// If *allPlans* is set to *false*, the result will contain an attribute *cacheable*
/// that states whether the query results can be cached on the server if the query
/// result cache were used. The *cacheable* attribute is not present when *allPlans*
/// is set to *true*.
///
/// Each plan in the result is a JSON object with the following attributes:
/// - *nodes*: the array of execution nodes of the plan. The array of available node types
@ -314,7 +318,8 @@ function post_api_explain (req, res) {
result = {
plan: result.plan,
warnings: result.warnings,
stats: result.stats
stats: result.stats,
cacheable: result.cacheable
};
}
actions.resultOk(req, res, actions.HTTP_OK, result);

View File

@ -148,7 +148,8 @@ ArangoStatement.prototype.explain = function (options) {
return {
plan: requestResult.plan,
warnings: requestResult.warnings,
stats: requestResult.stats
stats: requestResult.stats,
cacheable: requestResult.cacheable
};
}
};

View File

@ -126,7 +126,16 @@ function wrap (str, width) {
/* print query string */
function printQuery (query) {
'use strict';
stringBuilder.appendLine(section("Query string:"));
// restrict max length of printed query to avoid endless printing for
// very long query strings
var maxLength = 4096;
if (query.length > maxLength) {
stringBuilder.appendLine(section("Query string (truncated):"));
query = query.substr(0, maxLength / 2) + " ... " + query.substr(query.length - maxLength / 2);
}
else {
stringBuilder.appendLine(section("Query string:"));
}
stringBuilder.appendLine(" " + value(wrap(query, 100).replace(/\n+/g, "\n ", query)));
stringBuilder.appendLine();
}
@ -261,8 +270,7 @@ function printIndexes (indexes) {
}
}
/* print indexes used */
/* print traversal info */
function printTraversalDetails (traversals) {
'use strict';
if (traversals.length === 0) {
@ -408,7 +416,8 @@ function processQuery (query, explain) {
indexes = [ ],
traversalDetails = [],
modificationFlags,
isConst = true;
isConst = true,
currentNode = null;
var variableName = function (node) {
try {
@ -427,8 +436,26 @@ function processQuery (query, explain) {
return variable(node.name);
};
var addHint = function () { };
// uncomment this to show "style" hints
// var addHint = function (dst, currentNode, msg) {
// dst.push({ code: "Hint", message: "Node #" + currentNode + ": " + msg });
// };
var buildExpression = function (node) {
isConst = isConst && ([ "value", "object", "object element", "array" ].indexOf(node.type) !== -1);
if (node.type !== "attribute access" &&
node.hasOwnProperty("subNodes")) {
for (var i = 0; i < node.subNodes.length; ++i) {
if (node.subNodes[i].type === "reference" &&
collectionVariables.hasOwnProperty(node.subNodes[i].id)) {
addHint(explain.warnings, currentNode, "reference to collection document variable '" +
node.subNodes[i].name + "' used in potentially non-working way");
break;
}
}
}
switch (node.type) {
case "reference":
@ -453,6 +480,7 @@ function processQuery (query, explain) {
}
return variableName(node);
case "collection":
addHint(explain.warnings, currentNode, "using all documents from collection '" + node.name + "' in expression");
return collection(node.name) + " " + annotation("/* all collection documents */");
case "value":
return value(JSON.stringify(node.value));
@ -487,6 +515,21 @@ function processQuery (query, explain) {
case "array limit":
return buildExpression(node.subNodes[0]) + ", " + buildExpression(node.subNodes[1]);
case "attribute access":
if (node.subNodes[0].type === "reference" &&
collectionVariables.hasOwnProperty(node.subNodes[0].id)) {
// top-level attribute access
var collectionName = collectionVariables[node.subNodes[0].id],
collectionObject = db._collection(collectionName);
if (collectionObject !== null) {
var isEdgeCollection = (collectionObject.type() === 3),
isSystem = (node.name[0] === '_');
if ((isSystem && [ "_key", "_id", "_rev"].concat(isEdgeCollection ? [ "_from", "_to" ] : [ ]).indexOf(node.name) === -1) ||
(! isSystem && isEdgeCollection && [ "from", "to" ].indexOf(node.name) !== -1)) {
addHint(explain.warnings, currentNode, "reference to potentially non-existing attribute '" + node.name + "'");
}
}
}
return buildExpression(node.subNodes[0]) + "." + attribute(node.name);
case "indexed access":
return buildExpression(node.subNodes[0]) + "[" + buildExpression(node.subNodes[1]) + "]";
@ -810,6 +853,7 @@ function processQuery (query, explain) {
var preHandle = function (node) {
usedVariables = { };
currentNode = node.id;
isConst = true;
if (node.type === "SubqueryNode") {
subqueries.push(level);

View File

@ -147,7 +147,8 @@ ArangoStatement.prototype.explain = function (options) {
return {
plan: requestResult.plan,
warnings: requestResult.warnings,
stats: requestResult.stats
stats: requestResult.stats,
cacheable: requestResult.cacheable
};
}
};

View File

@ -125,7 +125,16 @@ function wrap (str, width) {
/* print query string */
function printQuery (query) {
'use strict';
stringBuilder.appendLine(section("Query string:"));
// restrict max length of printed query to avoid endless printing for
// very long query strings
var maxLength = 4096;
if (query.length > maxLength) {
stringBuilder.appendLine(section("Query string (truncated):"));
query = query.substr(0, maxLength / 2) + " ... " + query.substr(query.length - maxLength / 2);
}
else {
stringBuilder.appendLine(section("Query string:"));
}
stringBuilder.appendLine(" " + value(wrap(query, 100).replace(/\n+/g, "\n ", query)));
stringBuilder.appendLine();
}
@ -260,8 +269,7 @@ function printIndexes (indexes) {
}
}
/* print indexes used */
/* print traversal info */
function printTraversalDetails (traversals) {
'use strict';
if (traversals.length === 0) {
@ -407,7 +415,8 @@ function processQuery (query, explain) {
indexes = [ ],
traversalDetails = [],
modificationFlags,
isConst = true;
isConst = true,
currentNode = null;
var variableName = function (node) {
try {
@ -426,8 +435,26 @@ function processQuery (query, explain) {
return variable(node.name);
};
var addHint = function () { };
// uncomment this to show "style" hints
// var addHint = function (dst, currentNode, msg) {
// dst.push({ code: "Hint", message: "Node #" + currentNode + ": " + msg });
// };
var buildExpression = function (node) {
isConst = isConst && ([ "value", "object", "object element", "array" ].indexOf(node.type) !== -1);
if (node.type !== "attribute access" &&
node.hasOwnProperty("subNodes")) {
for (var i = 0; i < node.subNodes.length; ++i) {
if (node.subNodes[i].type === "reference" &&
collectionVariables.hasOwnProperty(node.subNodes[i].id)) {
addHint(explain.warnings, currentNode, "reference to collection document variable '" +
node.subNodes[i].name + "' used in potentially non-working way");
break;
}
}
}
switch (node.type) {
case "reference":
@ -452,6 +479,7 @@ function processQuery (query, explain) {
}
return variableName(node);
case "collection":
addHint(explain.warnings, currentNode, "using all documents from collection '" + node.name + "' in expression");
return collection(node.name) + " " + annotation("/* all collection documents */");
case "value":
return value(JSON.stringify(node.value));
@ -486,6 +514,21 @@ function processQuery (query, explain) {
case "array limit":
return buildExpression(node.subNodes[0]) + ", " + buildExpression(node.subNodes[1]);
case "attribute access":
if (node.subNodes[0].type === "reference" &&
collectionVariables.hasOwnProperty(node.subNodes[0].id)) {
// top-level attribute access
var collectionName = collectionVariables[node.subNodes[0].id],
collectionObject = db._collection(collectionName);
if (collectionObject !== null) {
var isEdgeCollection = (collectionObject.type() === 3),
isSystem = (node.name[0] === '_');
if ((isSystem && [ "_key", "_id", "_rev"].concat(isEdgeCollection ? [ "_from", "_to" ] : [ ]).indexOf(node.name) === -1) ||
(! isSystem && isEdgeCollection && [ "from", "to" ].indexOf(node.name) !== -1)) {
addHint(explain.warnings, currentNode, "reference to potentially non-existing attribute '" + node.name + "'");
}
}
}
return buildExpression(node.subNodes[0]) + "." + attribute(node.name);
case "indexed access":
return buildExpression(node.subNodes[0]) + "[" + buildExpression(node.subNodes[1]) + "]";
@ -809,6 +852,7 @@ function processQuery (query, explain) {
var preHandle = function (node) {
usedVariables = { };
currentNode = node.id;
isConst = true;
if (node.type === "SubqueryNode") {
subqueries.push(level);

View File

@ -252,6 +252,8 @@ function StatementSuite () {
assertTrue(plan.hasOwnProperty("collections"));
assertEqual([ ], plan.collections);
assertTrue(plan.hasOwnProperty("variables"));
assertTrue(result.hasOwnProperty("cacheable"));
assertTrue(result.cacheable);
},
////////////////////////////////////////////////////////////////////////////////
@ -275,6 +277,7 @@ function StatementSuite () {
assertTrue(plan.hasOwnProperty("collections"));
assertEqual([ ], plan.collections);
assertTrue(plan.hasOwnProperty("variables"));
assertFalse(result.hasOwnProperty("cacheable"));
},
////////////////////////////////////////////////////////////////////////////////
@ -298,6 +301,7 @@ function StatementSuite () {
assertTrue(plan.hasOwnProperty("collections"));
assertEqual([ ], plan.collections);
assertTrue(plan.hasOwnProperty("variables"));
assertFalse(result.hasOwnProperty("cacheable"));
},
////////////////////////////////////////////////////////////////////////////////
@ -370,6 +374,8 @@ function StatementSuite () {
assertTrue(plan.hasOwnProperty("collections"));
assertEqual([ ], plan.collections);
assertTrue(plan.hasOwnProperty("variables"));
assertTrue(result.hasOwnProperty("cacheable"));
assertTrue(result.cacheable);
},
@ -393,6 +399,24 @@ function StatementSuite () {
assertTrue(plan.hasOwnProperty("collections"));
assertEqual([ ], plan.collections);
assertTrue(plan.hasOwnProperty("variables"));
assertTrue(result.hasOwnProperty("cacheable"));
assertFalse(result.cacheable);
},
////////////////////////////////////////////////////////////////////////////////
/// @brief test non cacheable
////////////////////////////////////////////////////////////////////////////////
testExplainNoncacheable : function () {
var st = db._createStatement({ query : "RETURN RAND()" });
var result = st.explain();
assertEqual(0, result.warnings.length);
assertTrue(result.hasOwnProperty("plan"));
assertFalse(result.hasOwnProperty("plans"));
assertTrue(result.hasOwnProperty("cacheable"));
assertFalse(result.cacheable);
},
////////////////////////////////////////////////////////////////////////////////