diff --git a/arangod/Aql/ExecutionNode.cpp b/arangod/Aql/ExecutionNode.cpp index e0d5814507..c5f95185d2 100644 --- a/arangod/Aql/ExecutionNode.cpp +++ b/arangod/Aql/ExecutionNode.cpp @@ -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 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(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(); diff --git a/arangod/Aql/ExecutionNode.h b/arangod/Aql/ExecutionNode.h index cb3c2829b5..9e6423b5b5 100644 --- a/arangod/Aql/ExecutionNode.h +++ b/arangod/Aql/ExecutionNode.h @@ -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, diff --git a/arangod/Aql/ExecutionPlan.cpp b/arangod/Aql/ExecutionPlan.cpp index 548ea1d305..6847271f5a 100644 --- a/arangod/Aql/ExecutionPlan.cpp +++ b/arangod/Aql/ExecutionPlan.cpp @@ -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); diff --git a/arangod/Aql/Query.cpp b/arangod/Aql/Query.cpp index e415238dfe..7903374a70 100644 --- a/arangod/Aql/Query.cpp +++ b/arangod/Aql/Query.cpp @@ -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); diff --git a/js/common/modules/@arangodb/aql/explainer.js b/js/common/modules/@arangodb/aql/explainer.js index a955ec7a15..05ce51c577 100644 --- a/js/common/modules/@arangodb/aql/explainer.js +++ b/js/common/modules/@arangodb/aql/explainer.js @@ -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 { diff --git a/js/server/tests/shell/shell-collection-rocksdb-noncluster.js b/js/server/tests/shell/shell-collection-rocksdb-noncluster.js new file mode 100644 index 0000000000..95e81862ce --- /dev/null +++ b/js/server/tests/shell/shell-collection-rocksdb-noncluster.js @@ -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();