mirror of https://gitee.com/bigwinds/arangodb
show AQL functions used in query in explain output (#5451)
This commit is contained in:
parent
cfdcdc5e9e
commit
a295eaa120
|
@ -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();
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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();
|
Loading…
Reference in New Issue