1
0
Fork 0

Merge branch 'devel' of github.com:arangodb/arangodb into devel

This commit is contained in:
hkernbach 2016-02-10 12:15:33 +01:00
commit 2f2ecf5595
22 changed files with 590 additions and 190 deletions

View File

@ -79,7 +79,17 @@ v3.0.0 (XXXX-XX-XX)
now takes the form $name@$version.zip instead of simply "app.zip"
v2.8.2 (XXXX-XX-XX)
v2.8.3 (XXXX-XX-XX)
-------------------
* Foxx Model event listeners defined on the model are now correctly invoked by
the Repository methods (issue #1665)
* Deleting a Foxx service in the frontend should now always succeed even if the
files no longer exist on the file system (issue #1358)
v2.8.2 (2016-02-09)
-------------------
* the continuous replication applier will now prevent the master's WAL logfiles

View File

@ -50,55 +50,6 @@ swagger:
@srcdir@/Documentation/DocuBlocks/Rest/ \
) > @srcdir@/js/apps/system/_admin/aardvark/APP/api-docs.json
## -----------------------------------------------------------------------------
## --SECTION-- EXAMPLES
## -----------------------------------------------------------------------------
################################################################################
### @brief generate examples
################################################################################
.PHONY: examples
examples:
@rm -f /tmp/arangodb.examples
rm -rf @srcdir@/Documentation/Books/ppbooks
python @srcdir@/Documentation/Scripts/generateExamples.py \
--outputDir @builddir@/Documentation/Examples \
--onlyThisOne "$(FILTER_EXAMPLE)" \
--outputFile /tmp/arangosh.examples.js \
--arangoshSetup @srcdir@/Documentation/Examples/setup-arangosh.js \
@srcdir@/Documentation/DocuBlocks \
@srcdir@/Documentation/Books/Users
if test -z "$(server.endpoint)"; then \
$(MAKE) start-server PID=$(PID) \
SERVER_START="--server.endpoint tcp://$(VOCHOST):$(VOCPORT) --server.disable-authentication true" \
PROTO=http ;\
@builddir@/bin/arangosh \
-s \
-c @srcdir@/etc/relative/arangosh.conf \
--server.password "" \
--server.endpoint tcp://$(VOCHOST):$(VOCPORT) \
--javascript.execute /tmp/arangosh.examples.js ;\
else \
@builddir@/bin/arangosh \
-s \
-c @srcdir@/etc/relative/arangosh.conf \
--server.password "" \
--server.endpoint $(server.endpoint) \
--javascript.execute /tmp/arangosh.examples.js ;\
fi
examples-inspect-file:
@echo "generating /tmp/allExamples.html"
@(printf "<html><head></head><body><pre>\n"; \
for thisExample in Documentation/Examples/*.generated; do \
printf "<hr>\n<h3>$$thisExample</h3>\n"; \
cat $$thisExample; \
done; \
printf "</pre></body></html>") > /tmp/allExamples.html
## -----------------------------------------------------------------------------
## --SECTION-- END-OF-FILE
## -----------------------------------------------------------------------------

View File

@ -0,0 +1,8 @@
#!/bin/sh
echo "generating /tmp/allExamples.html"
(printf "<html><head></head><body><pre>\n";
for thisExample in Documentation/Examples/*.generated; do
printf "<hr>\n<h3>$thisExample</h3>\n";
cat $thisExample;
done;
printf "</pre></body></html>") > /tmp/allExamples.html

View File

@ -447,18 +447,17 @@ Where to add new...
generate
--------
- `make examples` - on top level to generate Documentation/Examples
- `make examples FILTER_EXAMPLE=geoIndexSelect` will only produce one example - *geoIndexSelect*
- `make examples FILTER_EXAMPLE='MOD.*'` will only produce the examples matching that regex; Note that
- `./scripts/generateExamples --onlyThisOne geoIndexSelect` will only produce one example - *geoIndexSelect*
- `./scripts/generateExamples --onlyThisOne 'MOD.*'` will only produce the examples matching that regex; Note that
examples with enumerations in their name may base on others in their series - so you should generate the whole group.
- `make examples server.endpoint=tcp://127.0.0.1:8529` will utilize an existing arangod instead of starting a new one.
- `./scripts/generateExamples --server.endpoint tcp://127.0.0.1:8529` will utilize an existing arangod instead of starting a new one.
this does seriously cut down the execution time.
- alternatively you can use generateExamples (i.e. on windows since the make target is not portable) like that:
`./scripts/generateExamples
--server.endpoint 'tcp://127.0.0.1:8529'
--withPython 3rdParty/V8-4.3.61/third_party/python_26/python26.exe
--onlyThisOne 'MOD.*'`
- `make examples-inspect-file` generates a file where you can inspect all examples for readability.
- `./Documentation/Scripts/allExamples.sh` generates a file where you can inspect all examples for readability.
- `make swagger` - on top level to generate the documentation interactively with the server; you may use
[the swagger editor](https://github.com/swagger-api/swagger-editor) to revalidate whether
*js/apps/system/_admin/aardvark/APP/api-docs.json* is accurate.

View File

@ -1,5 +1,5 @@
source :rubygems
gem "httparty", "~> 0.8.1"
gem "persistent_httparty"
gem "rspec", "~> 2.14.0"
gem "rspec-core", "~> 2.14.0"

View File

@ -1,7 +1,7 @@
# coding: utf-8
require 'rubygems'
require 'httparty'
require 'persistent_httparty'
require 'json'
require 'rspec'
require 'rspec/expectations'
@ -54,6 +54,7 @@ end
class ArangoDB
include HTTParty
persistent_connection_adapter
if $ssl == '1'
base_uri "https://#{$address}"

View File

@ -415,7 +415,13 @@ std::pair<bool, bool> Condition::findIndexes(
usedIndexes.emplace_back(sortIndex);
}
return std::make_pair(false, true);
TRI_ASSERT(usedIndexes.size() == 1);
if (usedIndexes.back()->sparse) {
// cannot use a sparse index for sorting alone
usedIndexes.clear();
}
return std::make_pair(false, !usedIndexes.empty());
}
canUseForFilter &= canUseIndex.first;
@ -450,6 +456,55 @@ bool Condition::indexSupportsSort(Index const* idx, Variable const* reference,
}
return false;
}
////////////////////////////////////////////////////////////////////////////////
/// @brief get the attributes for a sub-condition that are const
/// (i.e. compared with equality)
////////////////////////////////////////////////////////////////////////////////
std::vector<std::vector<arangodb::basics::AttributeName>> Condition::getConstAttributes (Variable const* reference,
bool includeNull) {
std::vector<std::vector<arangodb::basics::AttributeName>> result;
if (_root == nullptr) {
return result;
}
size_t n = _root->numMembers();
if (n != 1) {
return result;
}
AstNode const* node = _root->getMember(0);
n = node->numMembers();
for (size_t i = 0; i < n; ++i) {
auto member = node->getMember(i);
if (member->type == NODE_TYPE_OPERATOR_BINARY_EQ) {
std::pair<Variable const*, std::vector<arangodb::basics::AttributeName>> parts;
auto lhs = member->getMember(0);
auto rhs = member->getMember(1);
if (lhs->isAttributeAccessForVariable(parts) &&
parts.first == reference) {
if (includeNull || (rhs->isConstant() && !rhs->isNullValue())) {
result.emplace_back(std::move(parts.second));
}
}
else if (rhs->isAttributeAccessForVariable(parts) &&
parts.first == reference) {
if (includeNull || (lhs->isConstant() && !lhs->isNullValue())) {
result.emplace_back(std::move(parts.second));
}
}
}
}
return result;
}
////////////////////////////////////////////////////////////////////////////////
/// @brief finds the best index that can match this single node
@ -517,7 +572,7 @@ std::pair<bool, bool> Condition::findIndexForAndNode(
// now check if the index fields are the same as the sort condition fields
// e.g. FILTER c.value1 == 1 && c.value2 == 42 SORT c.value1, c.value2
size_t coveredFields =
sortCondition->coveredAttributes(reference, idx->fields);
sortCondition->coveredAttributes(reference, idx->fields);
if (coveredFields == sortCondition->numAttributes() &&
(idx->isSorted() ||

View File

@ -294,6 +294,13 @@ class Condition {
std::vector<Index const*>&,
SortCondition const*);
//////////////////////////////////////////////////////////////////////////////
/// @brief get the attributes for a sub-condition that are const
/// (i.e. compared with equality)
//////////////////////////////////////////////////////////////////////////////
std::vector<std::vector<arangodb::basics::AttributeName>> getConstAttributes (Variable const*, bool);
private:
//////////////////////////////////////////////////////////////////////////////
/// @brief sort ORs for the same attribute so they are in ascending value

View File

@ -21,7 +21,7 @@
/// @author Michael Hackstein
////////////////////////////////////////////////////////////////////////////////
#include "Aql/ConditionFinder.h"
#include "ConditionFinder.h"
#include "Aql/ExecutionPlan.h"
#include "Aql/IndexNode.h"
#include "Aql/SortCondition.h"
@ -153,7 +153,7 @@ bool ConditionFinder::before(ExecutionNode* en) {
std::unique_ptr<SortCondition> sortCondition;
if (!en->isInInnerLoop()) {
// we cannot optimize away a sort if we're in an inner loop ourselves
sortCondition.reset(new SortCondition(_sorts, _variableDefinitions));
sortCondition.reset(new SortCondition(_sorts, condition->getConstAttributes(node->outVariable(), false), _variableDefinitions));
} else {
sortCondition.reset(new SortCondition);
}

View File

@ -38,6 +38,7 @@
#include "Aql/TraversalConditionFinder.h"
#include "Aql/Variable.h"
#include "Aql/types.h"
#include "Basics/AttributeNameParser.h"
#include "Basics/json-utilities.h"
using namespace arangodb::aql;
@ -1705,7 +1706,7 @@ struct SortToIndexNode final : public WalkerWorker<ExecutionNode> {
return true;
}
SortCondition sortCondition(_sorts, _variableDefinitions);
SortCondition sortCondition(_sorts, std::vector<std::vector<arangodb::basics::AttributeName>>(), _variableDefinitions);
if (!sortCondition.isEmpty() && sortCondition.isOnlyAttributeAccess() &&
sortCondition.isUnidirectional()) {
@ -1725,8 +1726,8 @@ struct SortToIndexNode final : public WalkerWorker<ExecutionNode> {
continue;
}
auto numCovered =
sortCondition.coveredAttributes(outVariable, index->fields);
size_t const numCovered =
sortCondition.coveredAttributes(outVariable, index->fields);
if (numCovered == 0) {
continue;
@ -1734,14 +1735,15 @@ struct SortToIndexNode final : public WalkerWorker<ExecutionNode> {
double estimatedCost = 0.0;
if (!index->supportsSortCondition(
&sortCondition, outVariable,
&sortCondition,
outVariable,
enumerateCollectionNode->collection()->count(),
estimatedCost)) {
// should never happen
TRI_ASSERT(false);
continue;
}
if (bestIndex == nullptr || estimatedCost < bestCost) {
bestIndex = index;
bestCost = estimatedCost;
@ -1790,6 +1792,10 @@ struct SortToIndexNode final : public WalkerWorker<ExecutionNode> {
auto const& indexes = indexNode->getIndexes();
auto cond = indexNode->condition();
TRI_ASSERT(cond != nullptr);
Variable const* outVariable = indexNode->outVariable();
TRI_ASSERT(outVariable != nullptr);
if (indexes.size() != 1) {
// can only use this index node if it uses exactly one index or multiple
@ -1824,7 +1830,7 @@ struct SortToIndexNode final : public WalkerWorker<ExecutionNode> {
auto index = indexes[0];
bool handled = false;
SortCondition sortCondition(_sorts, _variableDefinitions);
SortCondition sortCondition(_sorts, cond->getConstAttributes(outVariable, !index->sparse), _variableDefinitions);
bool const isOnlyAttributeAccess =
(!sortCondition.isEmpty() && sortCondition.isOnlyAttributeAccess());
@ -1835,11 +1841,10 @@ struct SortToIndexNode final : public WalkerWorker<ExecutionNode> {
// we have found a sort condition, which is unidirectional and in the same
// order as the IndexNode...
// now check if the sort attributes match the ones of the index
Variable const* outVariable = indexNode->outVariable();
auto numCovered =
sortCondition.coveredAttributes(outVariable, index->fields);
size_t const numCovered =
sortCondition.coveredAttributes(outVariable, index->fields);
if (numCovered == sortCondition.numAttributes()) {
if (numCovered >= sortCondition.numAttributes()) {
// sort condition is fully covered by index... now we can remove the
// sort node from the plan
_plan->unlinkNode(_plan->getNodeById(_sortNode->id()));
@ -1862,11 +1867,11 @@ struct SortToIndexNode final : public WalkerWorker<ExecutionNode> {
// now check if the index fields are the same as the sort condition
// fields
// e.g. FILTER c.value1 == 1 && c.value2 == 42 SORT c.value1, c.value2
Variable const* outVariable = indexNode->outVariable();
size_t coveredFields =
sortCondition.coveredAttributes(outVariable, index->fields);
size_t const numCovered =
sortCondition.coveredAttributes(outVariable, index->fields);
if (coveredFields == sortCondition.numAttributes() &&
if (numCovered == sortCondition.numAttributes() &&
sortCondition.isUnidirectional() &&
(index->isSorted() ||
index->fields.size() == sortCondition.numAttributes())) {
// no need to sort

View File

@ -21,18 +21,34 @@
/// @author Jan Steemann
////////////////////////////////////////////////////////////////////////////////
#include "Aql/SortCondition.h"
#include "SortCondition.h"
#include "Aql/AstNode.h"
#include "Basics/Logger.h"
using namespace arangodb::aql;
////////////////////////////////////////////////////////////////////////////////
/// @brief whether or not an attribute is contained in a vector
////////////////////////////////////////////////////////////////////////////////
static bool IsContained (std::vector<std::vector<arangodb::basics::AttributeName>> const& attributes,
std::vector<arangodb::basics::AttributeName> const& attribute) {
for (auto const& it : attributes) {
if (arangodb::basics::AttributeName::isIdentical(it, attribute, false)) {
return true;
}
}
return false;
}
////////////////////////////////////////////////////////////////////////////////
/// @brief create an empty condition
////////////////////////////////////////////////////////////////////////////////
SortCondition::SortCondition()
: _expressions(),
_fields(),
: _fields(),
_constAttributes(),
_unidirectional(false),
_onlyAttributeAccess(false),
_ascending(true) {}
@ -41,73 +57,22 @@ SortCondition::SortCondition()
/// @brief create the sort condition
////////////////////////////////////////////////////////////////////////////////
SortCondition::SortCondition(
std::vector<std::pair<AstNode const*, bool>> const& expressions)
: _expressions(expressions),
_fields(),
_unidirectional(true),
_onlyAttributeAccess(true),
_ascending(true) {
size_t const n = _expressions.size();
for (size_t i = 0; i < n; ++i) {
if (_unidirectional && i > 0 &&
_expressions[i].second != _expressions[i - 1].second) {
_unidirectional = false;
}
bool handled = false;
auto node = _expressions[i].first;
if (node != nullptr && node->type == NODE_TYPE_ATTRIBUTE_ACCESS) {
std::vector<arangodb::basics::AttributeName> fieldNames;
while (node->type == NODE_TYPE_ATTRIBUTE_ACCESS) {
fieldNames.emplace_back(
arangodb::basics::AttributeName(node->getStringValue()));
node = node->getMember(0);
}
if (node->type == NODE_TYPE_REFERENCE) {
handled = true;
_fields.emplace_back(std::make_pair(
static_cast<Variable const*>(node->getData()), fieldNames));
}
}
if (!handled) {
_fields.emplace_back(
std::pair<Variable const*,
std::vector<arangodb::basics::AttributeName>>());
_onlyAttributeAccess = false;
}
}
if (n == 0) {
_onlyAttributeAccess = false;
}
}
////////////////////////////////////////////////////////////////////////////////
/// @brief create the sort condition
////////////////////////////////////////////////////////////////////////////////
SortCondition::SortCondition(
std::vector<std::pair<VariableId, bool>> const& sorts,
std::vector<std::vector<arangodb::basics::AttributeName>> const& constAttributes,
std::unordered_map<VariableId, AstNode const*> const& variableDefinitions)
: _expressions(),
: _fields(),
_constAttributes(constAttributes),
_unidirectional(true),
_onlyAttributeAccess(true),
_ascending(true) {
bool foundDirection = false;
size_t const n = sorts.size();
for (size_t i = 0; i < n; ++i) {
if (_unidirectional && i > 0 && sorts[i].second != sorts[i - 1].second) {
_unidirectional = false;
}
if (i == 0) {
_ascending = sorts[i].second;
}
bool isConst = false; // const attribute?
bool handled = false;
auto variableId = sorts[i].first;
@ -129,10 +94,30 @@ SortCondition::SortCondition(
_fields.emplace_back(std::make_pair(
static_cast<Variable const*>(node->getData()), fieldNames));
for (auto const& it2 : constAttributes) {
if (it2 == fieldNames) {
// const attribute
isConst = true;
break;
}
}
}
}
}
if (!isConst) {
// const attributes can be ignored for sorting
if (!foundDirection) {
// first attribute that we found
foundDirection = true;
_ascending = sorts[i].second;
}
else if (_unidirectional && sorts[i].second != _ascending) {
_unidirectional = false;
}
}
if (!handled) {
_fields.emplace_back(
std::pair<Variable const*,
@ -161,40 +146,54 @@ size_t SortCondition::coveredAttributes(
Variable const* reference,
std::vector<std::vector<arangodb::basics::AttributeName>> const&
indexAttributes) const {
size_t numAttributes = 0;
size_t numCovered = 0;
size_t fieldsPosition = 0;
// iterate over all fields of the index definition
size_t const n = indexAttributes.size();
for (size_t i = 0; i < indexAttributes.size(); ++i) {
if (i >= _fields.size()) {
for (size_t i = 0; i < n; /* no hoisting */) {
if (fieldsPosition >= _fields.size()) {
// done
break;
}
auto const& field = _fields[fieldsPosition];
if (reference != _fields[i].first) {
break;
// ...and check if the field is present in the index definition too
if (reference == field.first &&
arangodb::basics::AttributeName::isIdentical(field.second, indexAttributes[i], false)) {
// field match
++fieldsPosition;
++numCovered;
++i; // next index field
continue;
}
auto const& fieldNames = _fields[i].second;
if (fieldNames.size() != indexAttributes[i].size()) {
// different attribute path
break;
// no match
bool isConstant = false;
if (IsContained(_constAttributes, indexAttributes[i])) {
// no field match, but a constant attribute
isConstant = true;
++i; // next index field
}
bool found = true;
for (size_t j = 0; j < indexAttributes[i].size(); ++j) {
if (indexAttributes[i][j].shouldExpand ||
fieldNames[j] != indexAttributes[i][j]) {
// expanded attribute or different attribute
found = false;
break;
if (!isConstant) {
if (IsContained(indexAttributes, field.second) &&
IsContained(_constAttributes, field.second)) {
// no field match, but a constant attribute
isConstant = true;
++fieldsPosition;
++numCovered;
}
}
if (!found) {
if (!isConstant) {
break;
}
// same attribute
++numAttributes;
}
return numAttributes;
TRI_ASSERT(numCovered <= _fields.size());
return numCovered;
}

View File

@ -47,13 +47,8 @@ class SortCondition {
/// @brief create the sort condition
//////////////////////////////////////////////////////////////////////////////
explicit SortCondition(std::vector<std::pair<AstNode const*, bool>> const&);
//////////////////////////////////////////////////////////////////////////////
/// @brief create the sort condition
//////////////////////////////////////////////////////////////////////////////
SortCondition(std::vector<std::pair<VariableId, bool>> const&,
std::vector<std::vector<arangodb::basics::AttributeName>> const&,
std::unordered_map<VariableId, AstNode const*> const&);
//////////////////////////////////////////////////////////////////////////////
@ -115,11 +110,6 @@ class SortCondition {
std::vector<std::vector<arangodb::basics::AttributeName>> const&) const;
private:
//////////////////////////////////////////////////////////////////////////////
/// @brief sort expressions
//////////////////////////////////////////////////////////////////////////////
std::vector<std::pair<AstNode const*, bool>> _expressions;
//////////////////////////////////////////////////////////////////////////////
/// @brief fields used in the sort conditions
@ -127,6 +117,12 @@ class SortCondition {
std::vector<std::pair<Variable const*,
std::vector<arangodb::basics::AttributeName>>> _fields;
//////////////////////////////////////////////////////////////////////////////
/// @brief const attributes
//////////////////////////////////////////////////////////////////////////////
std::vector<std::vector<arangodb::basics::AttributeName>> const _constAttributes;
//////////////////////////////////////////////////////////////////////////////
/// @brief whether or not the sort is unidirectional

View File

@ -170,7 +170,8 @@
var mount = validateMount(req);
var runTeardown = req.parameters.teardown;
var app = FoxxManager.uninstall(mount, {
teardown: runTeardown
teardown: runTeardown,
force: true
});
res.json({
error: false,

View File

@ -698,7 +698,7 @@ function processQuery (query, explain) {
collectionVariables[node.outVariable.id] = node.collection;
var types = [ ];
node.indexes.forEach(function (idx, i) {
var what = (idx.reverse ? "reverse " : "") + idx.type + " index scan";
var what = (node.reverse ? "reverse " : "") + idx.type + " index scan";
if (types.length === 0 || what !== types[types.length - 1]) {
types.push(what);
}

View File

@ -130,7 +130,7 @@ const optionsDefaults = {
"replication": false,
"skipAql": false,
"skipArangoB": false,
"skipArangoBNonConnKeepAlive": false,
"skipArangoBNonConnKeepAlive": true,
"skipBoost": false,
"skipGeo": false,
"skipLogAnalysis": false,
@ -1807,7 +1807,6 @@ function filterTestcaseByOptions(testname, options, whichFilter) {
////////////////////////////////////////////////////////////////////////////////
let allTests = [
"arangob",
"arangosh",
"authentication",
"authentication_parameters",
@ -1822,7 +1821,8 @@ let allTests = [
"shell_server",
"shell_server_aql",
"ssl_server",
"upgrade"
"upgrade",
"arangob"
];
////////////////////////////////////////////////////////////////////////////////

View File

@ -697,7 +697,7 @@ function processQuery (query, explain) {
collectionVariables[node.outVariable.id] = node.collection;
var types = [ ];
node.indexes.forEach(function (idx, i) {
var what = (idx.reverse ? "reverse " : "") + idx.type + " index scan";
var what = (node.reverse ? "reverse " : "") + idx.type + " index scan";
if (types.length === 0 || what !== types[types.length - 1]) {
types.push(what);
}

View File

@ -87,7 +87,8 @@ function Repository(collection, opts) {
EventEmitter.call(this);
_.each(this.model, function (listener, eventName) {
Object.keys(this.model).forEach(function (eventName) {
const listener = this.model[eventName];
if (EVENTS.indexOf(eventName) === -1 || typeof listener !== 'function') {
return;
}

View File

@ -31,7 +31,6 @@
var jsunity = require("jsunity");
var db = require("@arangodb").db;
////////////////////////////////////////////////////////////////////////////////
/// @brief test suite
////////////////////////////////////////////////////////////////////////////////
@ -309,9 +308,7 @@ function optimizerIndexesSortTestSuite () {
////////////////////////////////////////////////////////////////////////////////
testCannotUseHashIndexForSortIfConstRanges : function () {
AQL_EXECUTE("FOR i IN " + c.name() + " UPDATE i WITH { value2: i.value, value3: i.value } IN " + c.name());
c.ensureHashIndex("value2", "value3");
c.ensureIndex({ type: "hash", fields: [ "value2", "value3" ] });
var queries = [
[ "FOR i IN " + c.name() + " FILTER i.value2 == 2 && i.value3 == 2 SORT i.value2 ASC, i.value3 ASC RETURN i.value2", false ],
@ -339,6 +336,52 @@ function optimizerIndexesSortTestSuite () {
});
},
////////////////////////////////////////////////////////////////////////////////
/// @brief test index usage
////////////////////////////////////////////////////////////////////////////////
testCannotUseHashIndexForSortIfConstRangesMore : function () {
c.ensureIndex({ type: "hash", fields: [ "value2", "value3", "value4" ] });
var queries = [
[ "FOR i IN " + c.name() + " FILTER i.value2 == 2 SORT i.value3 ASC RETURN i.value2", false ],
[ "FOR i IN " + c.name() + " FILTER i.value2 == 2 SORT i.value3 DESC RETURN i.value2", false ],
[ "FOR i IN " + c.name() + " FILTER i.value2 == 2 SORT i.value3 ASC, i.value4 ASC RETURN i.value2", false ],
[ "FOR i IN " + c.name() + " FILTER i.value2 == 2 SORT i.value3 DESC, i.value4 DESC RETURN i.value2", false ],
[ "FOR i IN " + c.name() + " FILTER i.value2 == 2 && i.value3 == 2 SORT i.value4 ASC RETURN i.value2", false ],
[ "FOR i IN " + c.name() + " FILTER i.value2 == 2 && i.value3 == 2 SORT i.value4 DESC RETURN i.value2", false ],
[ "FOR i IN " + c.name() + " FILTER i.value2 == 2 && i.value3 == 2 SORT i.value3 ASC RETURN i.value2", false ],
[ "FOR i IN " + c.name() + " FILTER i.value2 == 2 && i.value3 == 2 SORT i.value3 DESC RETURN i.value2", false ],
[ "FOR i IN " + c.name() + " FILTER i.value2 == 2 && i.value3 == 2 SORT i.value3 ASC, i.value4 ASC RETURN i.value2", false ],
[ "FOR i IN " + c.name() + " FILTER i.value2 == 2 && i.value3 == 2 SORT i.value3 DESC, i.value4 DESC RETURN i.value2" ,false ],
[ "FOR i IN " + c.name() + " FILTER i.value2 == 2 && i.value3 == 2 && i.value4 == 2 SORT i.value3 ASC RETURN i.value2", false ],
[ "FOR i IN " + c.name() + " FILTER i.value2 == 2 && i.value3 == 2 && i.value4 == 2 SORT i.value3 DESC RETURN i.value2", false ],
[ "FOR i IN " + c.name() + " FILTER i.value2 == 2 && i.value3 == 2 && i.value4 == 2 SORT i.value3 ASC, i.value4 ASC RETURN i.value2", false ],
[ "FOR i IN " + c.name() + " FILTER i.value2 == 2 && i.value3 == 2 && i.value4 == 2 SORT i.value3 DESC, i.value4 DESC RETURN i.value2", false ],
[ "FOR i IN " + c.name() + " FILTER i.value2 == 2 && i.value3 == 2 && i.value4 == 2 SORT i.value4 ASC RETURN i.value2", false ],
[ "FOR i IN " + c.name() + " FILTER i.value2 == 2 && i.value3 == 2 && i.value4 == 2 SORT i.value4 DESC RETURN i.value2", false ],
[ "FOR i IN " + c.name() + " FILTER i.value2 == 2 && i.value3 == 2 && i.value4 == 2 SORT i.value2 ASC, i.value3 ASC, i.value4 ASC RETURN i.value2", true ],
[ "FOR i IN " + c.name() + " FILTER i.value2 == 2 && i.value3 == 2 && i.value4 == 2 SORT i.value2 DESC, i.value3 DESC, i.value4 DESC RETURN i.value2", true ],
[ "FOR i IN " + c.name() + " FILTER i.value2 == 2 && i.value3 == 2 && i.value4 == 2 SORT i.value2 ASC, i.value3 ASC, i.value4 DESC RETURN i.value2", true ]
];
queries.forEach(function(query) {
var plan = AQL_EXPLAIN(query[0]).plan;
var nodeTypes = plan.nodes.map(function(node) {
return node.type;
});
if (query[1]) {
assertEqual(-1, nodeTypes.indexOf("SortNode"), query[0]);
}
else {
assertNotEqual(-1, nodeTypes.indexOf("SortNode"), query[0]);
}
});
},
////////////////////////////////////////////////////////////////////////////////
/// @brief test index usage
////////////////////////////////////////////////////////////////////////////////
@ -437,17 +480,22 @@ function optimizerIndexesSortTestSuite () {
/// @brief test index usage
////////////////////////////////////////////////////////////////////////////////
testCannotUseSkiplistIndexForSortIfConstRanges : function () {
testCanUseSkiplistIndexForSortIfConstRanges : function () {
AQL_EXECUTE("FOR i IN " + c.name() + " UPDATE i WITH { value2: i.value, value3: i.value, value4: i.value } IN " + c.name());
c.ensureSkiplist("value2", "value3", "value4");
var queries = [
"FOR i IN " + c.name() + " FILTER i.value2 == 2 SORT i.value2 ASC RETURN i.value2",
"FOR i IN " + c.name() + " FILTER i.value2 == 2 SORT i.value2 DESC RETURN i.value2",
"FOR i IN " + c.name() + " FILTER i.value2 == 2 SORT i.value2 ASC, i.value3 ASC RETURN i.value2",
"FOR i IN " + c.name() + " FILTER i.value2 == 2 SORT i.value2 ASC, i.value3 DESC RETURN i.value2",
"FOR i IN " + c.name() + " FILTER i.value2 == 2 SORT i.value3 ASC RETURN i.value2",
"FOR i IN " + c.name() + " FILTER i.value2 == 2 SORT i.value3 DESC RETURN i.value2",
"FOR i IN " + c.name() + " FILTER i.value2 == 2 SORT i.value3 ASC, i.value4 ASC RETURN i.value2",
"FOR i IN " + c.name() + " FILTER i.value2 == 2 SORT i.value3 DESC, i.value4 DESC RETURN i.value2",
"FOR i IN " + c.name() + " FILTER i.value2 == 2 && i.value3 == 2 SORT i.value4 ASC RETURN i.value2",
"FOR i IN " + c.name() + " FILTER i.value2 == 2 && i.value3 == 2 SORT i.value4 DESC RETURN i.value2",
"FOR i IN " + c.name() + " FILTER i.value2 == 2 && i.value3 == 2 SORT i.value3 ASC RETURN i.value2",
@ -470,10 +518,33 @@ function optimizerIndexesSortTestSuite () {
});
assertNotEqual(-1, nodeTypes.indexOf("IndexNode"), query);
assertNotEqual(-1, nodeTypes.indexOf("SortNode"), query);
assertEqual(-1, nodeTypes.indexOf("SortNode"), query);
});
},
////////////////////////////////////////////////////////////////////////////////
/// @brief test index usage
////////////////////////////////////////////////////////////////////////////////
testCannotUseSkiplistIndexForSortIfConstRanges : function () {
c.ensureSkiplist("value2", "value3", "value4");
var queries = [
"FOR i IN " + c.name() + " FILTER i.value2 == 2 SORT i.value3 ASC, i.value4 DESC RETURN i.value2",
"FOR i IN " + c.name() + " FILTER i.value2 == 2 SORT i.value2 ASC, i.value4 ASC RETURN i.value2",
"FOR i IN " + c.name() + " FILTER i.value2 == 2 SORT i.value2 ASC, i.value3 ASC, i.value4 DESC RETURN i.value2",
"FOR i IN " + c.name() + " FILTER i.value2 == 2 SORT i.value2 ASC, i.value3 ASC, i.value4 DESC RETURN i.value2"
];
queries.forEach(function(query) {
var plan = AQL_EXPLAIN(query).plan;
var nodeTypes = plan.nodes.map(function(node) {
return node.type;
});
assertNotEqual(-1, nodeTypes.indexOf("SortNode"), query);
});
},
////////////////////////////////////////////////////////////////////////////////
/// @brief test index usage
@ -750,6 +821,7 @@ function optimizerIndexesSortTestSuite () {
"FOR i IN " + c.name() + " FILTER i.value2 == 1 && i.value3 == null SORT i.value2 RETURN i.value2",
];
queries.forEach(function(query) {
var plan = AQL_EXPLAIN(query).plan;
var nodeTypes = plan.nodes.map(function(node) {

View File

@ -203,12 +203,15 @@ function optimizerRuleTestSuite () {
////////////////////////////////////////////////////////////////////////////////
testResults : function () {
var numbers = [ ], strings = [ ], reversed = [ ];
for (var i = 1; i <= 100; ++i) {
var groups = [ ], numbers = [ ], strings = [ ], reversed = [ ], i;
for (i = 1; i <= 100; ++i) {
numbers.push(i);
strings.push("test" + i);
reversed.push("test" + (101 - i));
}
for (i = 0; i < 10; ++i) {
groups.push(i);
}
var queries = [
[ "LET values = NOOPT(RANGE(1, 100)) FOR i IN 1..100 FILTER i IN values RETURN i", numbers ],
@ -223,7 +226,8 @@ function optimizerRuleTestSuite () {
[ "LET values = NOOPT(" + JSON.stringify(reversed) + ") FOR i IN 1..100 FILTER CONCAT('test', i) IN values RETURN i", numbers ],
[ "LET values = NOOPT(" + JSON.stringify(reversed) + ") FOR i IN 1..100 FILTER CONCAT('test', i) NOT IN values RETURN i", [ ] ],
[ "LET values = NOOPT(" + JSON.stringify(reversed) + ") FOR i IN 1..100 FILTER i IN values RETURN i", [ ] ],
[ "LET values = NOOPT(" + JSON.stringify(reversed) + ") FOR i IN 1..100 FILTER i NOT IN values RETURN i", numbers ]
[ "LET values = NOOPT(" + JSON.stringify(reversed) + ") FOR i IN 1..100 FILTER i NOT IN values RETURN i", numbers ],
[ "LET values = NOOPT(" + JSON.stringify(numbers) + ") FOR i IN 1..100 FILTER i IN values COLLECT group = i % 10 RETURN group", groups ]
];
queries.forEach(function(query) {

View File

@ -151,6 +151,57 @@ function optimizerRuleTestSuite() {
skiplist = null;
},
testRuleOptimizeWhenEqComparison : function () {
// skiplist: a, b
// skiplist: d
// hash: c
// hash: y,z
skiplist.ensureIndex({ type: "hash", fields: [ "y", "z" ], unique: false });
var queries = [
[ "FOR v IN " + colName + " FILTER v.u == 1 SORT v.u RETURN 1", false ],
[ "FOR v IN " + colName + " FILTER v.c == 1 SORT v.c RETURN 1", true ],
[ "FOR v IN " + colName + " FILTER v.c == 1 SORT v.z RETURN 1", false ],
[ "FOR v IN " + colName + " FILTER v.c == 1 SORT v.f RETURN 1", false ],
[ "FOR v IN " + colName + " FILTER v.y == 1 SORT v.z RETURN 1", false ],
[ "FOR v IN " + colName + " FILTER v.y == 1 SORT v.y RETURN 1", false ],
[ "FOR v IN " + colName + " FILTER v.z == 1 SORT v.y RETURN 1", false ],
[ "FOR v IN " + colName + " FILTER v.z == 1 SORT v.z RETURN 1", false ],
[ "FOR v IN " + colName + " FILTER v.y == 1 && v.z == 1 SORT v.y RETURN 1", false ],
[ "FOR v IN " + colName + " FILTER v.y == 1 && v.z == 1 SORT v.z RETURN 1", false ],
[ "FOR v IN " + colName + " FILTER v.y == 1 && v.z == 1 SORT v.y, v.z RETURN 1", true ],
[ "FOR v IN " + colName + " FILTER v.y == 1 && v.z == 1 SORT v.z, v.y RETURN 1", false ], // not supported yet
[ "FOR v IN " + colName + " FILTER v.d == 1 SORT v.d RETURN 1", true ],
[ "FOR v IN " + colName + " FILTER v.d == 1 && v.e == 1 SORT v.d RETURN 1", true ],
[ "FOR v IN " + colName + " FILTER v.d == 1 SORT v.e RETURN 1", false ],
[ "FOR v IN " + colName + " FILTER v.a == 1 SORT v.a, v.b RETURN 1", true ],
[ "FOR v IN " + colName + " FILTER v.a == 1 && v.b == 1 SORT v.a, v.b RETURN 1", true ],
[ "FOR v IN " + colName + " FILTER v.a == 1 SORT v.a RETURN 1", true ],
[ "FOR v IN " + colName + " FILTER v.a == 1 SORT v.a, v.b RETURN 1", true ],
[ "FOR v IN " + colName + " FILTER v.a == 1 SORT v.b RETURN 1", true ],
[ "FOR v IN " + colName + " FILTER v.a == 1 && v.b == 1 SORT v.b RETURN 1", true ],
[ "FOR v IN " + colName + " FILTER v.b == 1 SORT v.a, v.b RETURN 1", true ],
[ "FOR v IN " + colName + " FILTER v.b == 1 SORT v.b RETURN 1", false ],
[ "FOR v IN " + colName + " FILTER v.b == 1 SORT v.b, v.a RETURN 1", false ],
[ "FOR v IN " + colName + " FILTER v.a == 1 && v.b == 1 SORT v.b, v.a RETURN 1", false ],
[ "FOR v IN " + colName + " FILTER v.a == 1 && v.b == 1 SORT v.a, v.b RETURN 1", true ],
[ "FOR v IN " + colName + " FILTER v.a == 1 && v.b == 1 SORT v.a, v.c RETURN 1", false ],
[ "FOR v IN " + colName + " FILTER v.a == 1 && v.b == 1 SORT v.b, v.a RETURN 1", false ]
];
queries.forEach(function(query) {
var result = AQL_EXPLAIN(query[0]);
if (query[1]) {
assertNotEqual(-1, removeAlwaysOnClusterRules(result.plan.rules).indexOf(ruleName), query[0]);
hasNoSortNode(result);
}
else {
assertEqual(-1, removeAlwaysOnClusterRules(result.plan.rules).indexOf(ruleName), query[0]);
hasSortNode(result);
}
});
},
////////////////////////////////////////////////////////////////////////////////
/// @brief test that rule has no effect
////////////////////////////////////////////////////////////////////////////////
@ -162,7 +213,7 @@ function optimizerRuleTestSuite() {
["FOR v IN " + colName + " SORT v.c RETURN [v.a, v.b]", true],
["FOR v IN " + colName + " SORT v.b, v.a RETURN [v.a]", true],
["FOR v IN " + colName + " SORT v.c RETURN [v.a, v.b]", true],
["FOR v IN " + colName + " SORT v.a + 1 RETURN [v.a]", false],// this will throw...
["FOR v IN " + colName + " SORT v.a + 1 RETURN [v.a]", false],
["FOR v IN " + colName + " SORT CONCAT(TO_STRING(v.a), \"lol\") RETURN [v.a]", true],
// TODO: limit blocks sort atm.
["FOR v IN " + colName + " FILTER v.a > 2 LIMIT 3 SORT v.a RETURN [v.a]", false],
@ -371,7 +422,7 @@ function optimizerRuleTestSuite() {
hasIndexNode(XPresult);
// -> combined use-index-for-sort and use-index-range
// use-index-range superseedes use-index-for-sort
// use-index-range supersedes use-index-for-sort
QResults[2] = AQL_EXECUTE(query, { }, paramIndexFromSort_IndexRange).json;
XPresult = AQL_EXPLAIN(query, { }, paramIndexFromSort_IndexRange);
@ -417,7 +468,8 @@ function optimizerRuleTestSuite() {
/// @brief test in detail that an index range fullfills everything the sort does,
// and thus the sort is removed.
////////////////////////////////////////////////////////////////////////////////
testRangeSuperseedsSort: function () {
testRangeSupersedesSort: function () {
var query = "FOR v IN " + colName + " FILTER v.a == 1 SORT v.a RETURN [v.a, v.b, v.c]";
@ -496,7 +548,8 @@ function optimizerRuleTestSuite() {
/// @brief test in detail that an index range fullfills everything the sort does,
// and thus the sort is removed; multi-dimensional indexes are utilized.
////////////////////////////////////////////////////////////////////////////////
testRangeSuperseedsSort2: function () {
testRangeSupersedesSort2: function () {
var query = "FOR v IN " + colName + " FILTER v.a == 1 SORT v.a, v.b RETURN [v.a, v.b, v.c]";
var XPresult;

View File

@ -0,0 +1,123 @@
/*global describe, it, beforeEach */
'use strict';
////////////////////////////////////////////////////////////////////////////////
/// @brief Spec for foxx repository
///
/// @file
///
/// DISCLAIMER
///
/// Copyright 2016 ArangoDB 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 ArangoDB GmbH, Cologne, Germany
///
/// @author Alan Plum
/// @author Copyright 2016, ArangoDB GmbH, Cologne, Germany
////////////////////////////////////////////////////////////////////////////////
const expect = require('chai').expect;
const sinon = require('sinon');
const FoxxRepository = require('@arangodb/foxx/repository').Repository;
const FoxxModel = require('@arangodb/foxx/model').Model;
describe('Repository Events', function () {
let collection, Model;
beforeEach(function () {
Model = FoxxModel.extend({}, {});
collection = {};
});
it('should emit beforeCreate and afterCreate events when creating the model', function () {
var model = prepInstance(Model, 'Create');
collection.type = sinon.stub().returns(2);
collection.save = sinon.stub();
var repository = new FoxxRepository(collection, {model: Model});
expect(repository.save(model)).to.equal(model);
expect(model.get('beforeCalled')).to.equal(true);
expect(model.get('afterCalled')).to.equal(true);
});
it('should emit beforeSave and afterSave events when creating the model', function () {
var model = prepInstance(Model, 'Save');
collection.type = sinon.stub().returns(2);
collection.save = sinon.stub();
var repository = new FoxxRepository(collection, {model: Model});
expect(repository.save(model)).to.equal(model);
expect(model.get('beforeCalled')).to.equal(true);
expect(model.get('afterCalled')).to.equal(true);
});
it('should emit beforeUpdate and afterUpdate events when updating the model', function () {
var newData = {newAttribute: 'test'};
var model = prepInstance(Model, 'Update', newData);
collection.type = sinon.stub().returns(2);
collection.update = sinon.stub();
var repository = new FoxxRepository(collection, {model: Model});
expect(repository.update(model, newData)).to.equal(model);
expect(model.get('beforeCalled')).to.equal(true);
expect(model.get('afterCalled')).to.equal(true);
});
it('should emit beforeSave and afterSave events when updating the model', function () {
var newData = {newAttribute: 'test'};
var model = prepInstance(Model, 'Save', newData);
collection.type = sinon.stub().returns(2);
collection.update = sinon.stub();
var repository = new FoxxRepository(collection, {model: Model});
expect(repository.update(model, newData)).to.equal(model);
expect(model.get('beforeCalled')).to.equal(true);
expect(model.get('afterCalled')).to.equal(true);
});
it('should emit beforeRemove and afterRemove events when removing the model', function () {
var model = prepInstance(Model, 'Remove');
collection.type = sinon.stub().returns(2);
collection.remove = sinon.stub();
var repository = new FoxxRepository(collection, {model: Model});
repository.remove(model);
expect(model.get('beforeCalled')).to.equal(true);
expect(model.get('afterCalled')).to.equal(true);
});
});
function prepInstance(Model, ev, dataToReceive) {
let model;
const random = String(Math.floor(Math.random() * 1000));
Model['before' + ev] = function (instance, data) {
expect(instance).to.equal(model);
expect(data).to.equal(dataToReceive);
instance.set('random', random);
instance.set('beforeCalled', true);
};
Model['after' + ev] = function (instance, data) {
expect(instance).to.equal(model);
expect(data).to.equal(dataToReceive);
instance.set('afterCalled', true);
expect(instance.get('beforeCalled')).to.equal(true);
expect(instance.get('random')).to.equal(random);
};
model = new Model({
random: '',
beforeCalled: false,
afterCalled: false
});
return model;
}

View File

@ -0,0 +1,115 @@
/*global describe, it, beforeEach */
'use strict';
////////////////////////////////////////////////////////////////////////////////
/// @brief Spec for foxx repository
///
/// @file
///
/// DISCLAIMER
///
/// Copyright 2015-2016 ArangoDB 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 ArangoDB GmbH, Cologne, Germany
///
/// @author Alan Plum
/// @author Copyright 2015, ArangoDB GmbH, Cologne, Germany
////////////////////////////////////////////////////////////////////////////////
const expect = require('chai').expect;
const sinon = require('sinon');
const FoxxRepository = require('@arangodb/foxx/repository').Repository;
const Model = require('@arangodb/foxx/model').Model;
describe('Model Events', function () {
let collection, instance, repository;
beforeEach(function () {
collection = {
type: sinon.stub().returns(2)
};
instance = new Model();
repository = new FoxxRepository(collection, {model: Model, random: '', beforeCalled: false, afterCalled: false});
});
it('should be possible to subscribe and emit events', function () {
expect(repository.on).to.be.a('function');
expect(repository.emit).to.be.a('function');
});
it('should emit beforeCreate and afterCreate events when creating the model', function () {
collection.save = sinon.stub();
addHooks(repository, instance, 'Create');
expect(repository.save(instance)).to.equal(instance);
expect(repository.beforeCalled).to.equal(true);
expect(repository.afterCalled).to.equal(true);
});
it('should emit beforeSave and afterSave events when creating the model', function () {
collection.save = sinon.stub();
addHooks(repository, instance, 'Save');
expect(repository.save(instance)).to.equal(instance);
expect(repository.beforeCalled).to.equal(true);
expect(repository.afterCalled).to.equal(true);
});
it('should emit beforeUpdate and afterUpdate events when updating the model', function () {
var newData = {newAttribute: 'test'};
collection.update = sinon.stub();
addHooks(repository, instance, 'Update', newData);
expect(repository.update(instance, newData)).to.equal(instance);
expect(repository.beforeCalled).to.equal(true);
expect(repository.afterCalled).to.equal(true);
});
it('should emit beforeSave and afterSave events when updating the model', function () {
var newData = {newAttribute: 'test'};
collection.update = sinon.stub();
addHooks(repository, instance, 'Save', newData);
expect(repository.update(instance, newData)).to.equal(instance);
expect(repository.beforeCalled).to.equal(true);
expect(repository.afterCalled).to.equal(true);
});
it('should emit beforeRemove and afterRemove events when removing the model', function () {
collection.remove = sinon.stub();
addHooks(repository, instance, 'Remove');
repository.remove(instance);
expect(repository.beforeCalled).to.equal(true);
expect(repository.afterCalled).to.equal(true);
});
});
function addHooks(repo, model, ev, dataToReceive) {
const random = String(Math.floor(Math.random() * 1000));
repo.on('before' + ev, function (self, data) {
expect(this).to.equal(repo);
expect(self).to.equal(model);
expect(data).to.equal(dataToReceive);
this.random = random;
this.beforeCalled = true;
});
repo.on('after' + ev, function (self, data) {
expect(this).to.equal(repo);
expect(self).to.equal(model);
expect(data).to.equal(dataToReceive);
this.afterCalled = true;
expect(this.beforeCalled).to.equal(true);
expect(this.random).to.equal(random);
});
}