1
0
Fork 0

show AQL functions used in query in explain output (#5451)

This commit is contained in:
Jan 2018-05-24 21:17:51 +02:00 committed by GitHub
parent cfdcdc5e9e
commit a295eaa120
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 182 additions and 7 deletions

View File

@ -33,6 +33,7 @@
#include "Aql/ExecutionPlan.h"
#include "Aql/EnumerateCollectionBlock.h"
#include "Aql/EnumerateListBlock.h"
#include "Aql/Function.h"
#include "Aql/IndexNode.h"
#include "Aql/ModificationNodes.h"
#include "Aql/Query.h"
@ -1569,6 +1570,45 @@ void CalculationNode::toVelocyPackHelper(VPackBuilder& nodes, unsigned flags) co
}
nodes.add("expressionType", VPackValue(_expression->typeString()));
if ((flags & SERIALIZE_FUNCTIONS) &&
_expression->node() != nullptr) {
auto root = _expression->node();
if (root != nullptr) {
// enumerate all used functions, but report each function only once
std::unordered_set<std::string> functionsSeen;
nodes.add("functions", VPackValue(VPackValueType::Array));
Ast::traverseReadOnly(root, [&functionsSeen, &nodes](AstNode const* node) -> bool {
if (node->type == NODE_TYPE_FCALL) {
auto func = static_cast<Function const*>(node->getData());
if (functionsSeen.emplace(func->name).second) {
// built-in function, not seen before
nodes.openObject();
nodes.add("name", VPackValue(func->name));
nodes.add("isDeterministic", VPackValue(func->isDeterministic));
nodes.add("canRunOnDBServer", VPackValue(func->canRunOnDBServer));
nodes.add("usesV8", VPackValue(func->implementation == nullptr || (func->condition && !func->condition())));
nodes.close();
}
} else if (node->type == NODE_TYPE_FCALL_USER) {
auto func = node->getString();
if (functionsSeen.emplace(func).second) {
// user defined function, not seen before
nodes.openObject();
nodes.add("name", VPackValue(func));
nodes.add("isDeterministic", VPackValue(false));
nodes.add("canRunOnDBServer", VPackValue(false));
nodes.add("usesV8", VPackValue(true));
nodes.close();
}
}
return true;
});
nodes.close();
}
}
// And close it
nodes.close();

View File

@ -343,8 +343,10 @@ class ExecutionNode {
static constexpr unsigned SERIALIZE_PARENTS = 1;
/// include estimate cost (used in the explainer)
static constexpr unsigned SERIALIZE_ESTIMATES = 1 << 1;
/// Print all ExecutionNode information required in cluster snippets
/// print all ExecutionNode information required in cluster snippets
static constexpr unsigned SERIALIZE_DETAILS = 1 << 2;
/// include additional function info for explain
static constexpr unsigned SERIALIZE_FUNCTIONS = 1 << 3;
/// @brief toVelocyPack, export an ExecutionNode to VelocyPack
void toVelocyPack(arangodb::velocypack::Builder&, unsigned flags,

View File

@ -370,7 +370,8 @@ void ExecutionPlan::toVelocyPack(VPackBuilder& builder, Ast* ast,
unsigned flags = ExecutionNode::SERIALIZE_ESTIMATES;
if (verbose) {
flags |= ExecutionNode::SERIALIZE_PARENTS |
ExecutionNode::SERIALIZE_DETAILS;
ExecutionNode::SERIALIZE_DETAILS |
ExecutionNode::SERIALIZE_FUNCTIONS;
}
// keeps top level of built object open
_root->toVelocyPack(builder, flags, true);

View File

@ -907,7 +907,7 @@ QueryResult Query::explain() {
try {
init();
enterState(QueryExecutionState::ValueType::PARSING);
Parser parser(this);
parser.parse(true);
@ -983,7 +983,8 @@ QueryResult Query::explain() {
!_isModificationQuery && _warnings.empty() &&
_ast->root()->isCacheable());
}
// technically no need to commit, as we are only explaining here
auto commitResult = _trx->commit();
if (commitResult.fail()) {
THROW_ARANGO_EXCEPTION(commitResult);

View File

@ -162,10 +162,10 @@ function printQuery (query) {
// very long query strings
var maxLength = 4096;
if (query.length > maxLength) {
stringBuilder.appendLine(section('Query string (truncated):'));
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(section('Query String:'));
}
stringBuilder.appendLine(' ' + value(wrap(query, 100).replace(/\n+/g, '\n ', query)));
stringBuilder.appendLine();
@ -194,6 +194,7 @@ function printModificationFlags (flags) {
/* print optimizer rules */
function printRules (rules) {
'use strict';
stringBuilder.appendLine(section('Optimization rules applied:'));
if (rules.length === 0) {
stringBuilder.appendLine(' ' + value('none'));
@ -346,6 +347,47 @@ function printIndexes (indexes) {
}
}
function printFunctions (functions) {
'use strict';
let funcArray = [];
Object.keys(functions).forEach(function(f) {
funcArray.push(functions[f]);
});
if (funcArray.length === 0) {
return;
}
stringBuilder.appendLine(section('Functions used:'));
let maxNameLen = String('Name').length;
let maxDeterministicLen = String('Deterministic').length;
let maxV8Len = String('Uses V8').length;
funcArray.forEach(function (f) {
let l = String(f.name).length;
if (l > maxNameLen) {
maxNameLen = l;
}
});
let line = ' ' +
header('Name') + pad(1 + maxNameLen - 'Name'.length) + ' ' +
header('Deterministic') + pad(1 + maxDeterministicLen - 'Deterministic'.length) + ' ' +
header('Uses V8') + pad(1 + maxV8Len - 'Uses V8'.length);
stringBuilder.appendLine(line);
for (var i = 0; i < funcArray.length; ++i) {
let deterministic = String(funcArray[i].isDeterministic);
let usesV8 = String(funcArray[i].usesV8);
line = ' ' +
variable(funcArray[i].name) + pad(1 + maxNameLen - funcArray[i].name.length) + ' ' +
value(deterministic) + pad(1 + maxDeterministicLen - deterministic.length) + ' ' +
value(usesV8) + pad(1 + maxV8Len - usesV8.length);
stringBuilder.appendLine(line);
}
}
/* print traversal info */
function printTraversalDetails (traversals) {
'use strict';
@ -663,6 +705,7 @@ function processQuery (query, explain) {
indexes = [],
traversalDetails = [],
shortestPathDetails = [],
functions = [],
modificationFlags,
isConst = true,
currentNode = null;
@ -1186,6 +1229,9 @@ function processQuery (query, explain) {
}
return rc;
case 'CalculationNode':
(node.functions || []).forEach(function(f) {
functions[f.name] = f;
});
return keyword('LET') + ' ' + variableName(node.outVariable) + ' = ' + buildExpression(node.expression) + ' ' + annotation('/* ' + node.expressionType + ' expression */');
case 'FilterNode':
return keyword('FILTER') + ' ' + variableName(node.inVariable);
@ -1375,6 +1421,7 @@ function processQuery (query, explain) {
};
printQuery(query);
stringBuilder.appendLine(section('Execution plan:'));
var line = ' ' +
@ -1413,9 +1460,11 @@ function processQuery (query, explain) {
stringBuilder.appendLine();
printIndexes(indexes);
printFunctions(functions);
printTraversalDetails(traversalDetails);
printShortestPathDetails(shortestPathDetails);
stringBuilder.appendLine();
printRules(plan.rules);
printModificationFlags(modificationFlags);
printWarnings(explain.warnings);
@ -1439,13 +1488,14 @@ function explain(data, options, shouldPrint) {
options = data.options;
}
options = options || { };
options.verbosePlans = true;
setColors(options.colors === undefined ? true : options.colors);
stringBuilder.clearOutput();
let stmt = db._createStatement(data);
let result = stmt.explain(options); // TODO why is this there ?
processQuery(data.query, result);
if (shouldPrint === undefined || shouldPrint) {
print(stringBuilder.getOutput());
} else {

View File

@ -0,0 +1,81 @@
/*jshint globalstrict:false, strict:false */
/*global assertEqual, assertNull, fail */
////////////////////////////////////////////////////////////////////////////////
/// @brief test the collection interface
///
/// @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 Dr. Frank Celler
/// @author Copyright 2012, triAGENS GmbH, Cologne, Germany
////////////////////////////////////////////////////////////////////////////////
var jsunity = require("jsunity");
var arangodb = require("@arangodb");
var internal = require("internal");
var ArangoCollection = arangodb.ArangoCollection;
var db = arangodb.db;
var ERRORS = arangodb.errors;
function CollectionSuite() {
let cn = "example";
return {
setUp: function() {
db._drop(cn);
},
tearDown: function() {
db._drop(cn);
},
testCreateWithInvalidIndexes1 : function () {
try {
db._create(cn, { indexes: [{ id: "1", type: "edge", fields: ["_from"] }] });
fail();
} catch (err) {
assertEqual(ERRORS.ERROR_INTERNAL.code, err.errorNum);
}
assertNull(db._collection(cn));
},
testCreateWithInvalidIndexes2 : function () {
let cn = "example";
db._drop(cn);
try {
db._create(cn, { indexes: [{ id: "1234", type: "hash", fields: ["a"] }] });
fail();
} catch (err) {
assertEqual(ERRORS.ERROR_INTERNAL.code, err.errorNum);
}
assertNull(db._collection(cn));
}
};
}
jsunity.run(CollectionSuite);
return jsunity.done();