mirror of https://gitee.com/bigwinds/arangodb
Merge branch 'devel' of github.com:arangodb/arangodb into devel
This commit is contained in:
commit
2f2ecf5595
12
CHANGELOG
12
CHANGELOG
|
@ -79,7 +79,17 @@ v3.0.0 (XXXX-XX-XX)
|
||||||
now takes the form $name@$version.zip instead of simply "app.zip"
|
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
|
* the continuous replication applier will now prevent the master's WAL logfiles
|
||||||
|
|
|
@ -50,55 +50,6 @@ swagger:
|
||||||
@srcdir@/Documentation/DocuBlocks/Rest/ \
|
@srcdir@/Documentation/DocuBlocks/Rest/ \
|
||||||
) > @srcdir@/js/apps/system/_admin/aardvark/APP/api-docs.json
|
) > @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
|
## --SECTION-- END-OF-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
|
|
@ -447,18 +447,17 @@ Where to add new...
|
||||||
|
|
||||||
generate
|
generate
|
||||||
--------
|
--------
|
||||||
- `make examples` - on top level to generate Documentation/Examples
|
- `./scripts/generateExamples --onlyThisOne geoIndexSelect` will only produce one example - *geoIndexSelect*
|
||||||
- `make examples FILTER_EXAMPLE=geoIndexSelect` will only produce one example - *geoIndexSelect*
|
- `./scripts/generateExamples --onlyThisOne 'MOD.*'` will only produce the examples matching that regex; Note that
|
||||||
- `make examples FILTER_EXAMPLE='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.
|
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.
|
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:
|
- alternatively you can use generateExamples (i.e. on windows since the make target is not portable) like that:
|
||||||
`./scripts/generateExamples
|
`./scripts/generateExamples
|
||||||
--server.endpoint 'tcp://127.0.0.1:8529'
|
--server.endpoint 'tcp://127.0.0.1:8529'
|
||||||
--withPython 3rdParty/V8-4.3.61/third_party/python_26/python26.exe
|
--withPython 3rdParty/V8-4.3.61/third_party/python_26/python26.exe
|
||||||
--onlyThisOne 'MOD.*'`
|
--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
|
- `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
|
[the swagger editor](https://github.com/swagger-api/swagger-editor) to revalidate whether
|
||||||
*js/apps/system/_admin/aardvark/APP/api-docs.json* is accurate.
|
*js/apps/system/_admin/aardvark/APP/api-docs.json* is accurate.
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
source :rubygems
|
source :rubygems
|
||||||
|
|
||||||
gem "httparty", "~> 0.8.1"
|
gem "persistent_httparty"
|
||||||
gem "rspec", "~> 2.14.0"
|
gem "rspec", "~> 2.14.0"
|
||||||
gem "rspec-core", "~> 2.14.0"
|
gem "rspec-core", "~> 2.14.0"
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
# coding: utf-8
|
# coding: utf-8
|
||||||
|
|
||||||
require 'rubygems'
|
require 'rubygems'
|
||||||
require 'httparty'
|
require 'persistent_httparty'
|
||||||
require 'json'
|
require 'json'
|
||||||
require 'rspec'
|
require 'rspec'
|
||||||
require 'rspec/expectations'
|
require 'rspec/expectations'
|
||||||
|
@ -54,6 +54,7 @@ end
|
||||||
|
|
||||||
class ArangoDB
|
class ArangoDB
|
||||||
include HTTParty
|
include HTTParty
|
||||||
|
persistent_connection_adapter
|
||||||
|
|
||||||
if $ssl == '1'
|
if $ssl == '1'
|
||||||
base_uri "https://#{$address}"
|
base_uri "https://#{$address}"
|
||||||
|
|
|
@ -415,7 +415,13 @@ std::pair<bool, bool> Condition::findIndexes(
|
||||||
usedIndexes.emplace_back(sortIndex);
|
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;
|
canUseForFilter &= canUseIndex.first;
|
||||||
|
@ -450,6 +456,55 @@ bool Condition::indexSupportsSort(Index const* idx, Variable const* reference,
|
||||||
}
|
}
|
||||||
return false;
|
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
|
/// @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
|
// 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
|
// e.g. FILTER c.value1 == 1 && c.value2 == 42 SORT c.value1, c.value2
|
||||||
size_t coveredFields =
|
size_t coveredFields =
|
||||||
sortCondition->coveredAttributes(reference, idx->fields);
|
sortCondition->coveredAttributes(reference, idx->fields);
|
||||||
|
|
||||||
if (coveredFields == sortCondition->numAttributes() &&
|
if (coveredFields == sortCondition->numAttributes() &&
|
||||||
(idx->isSorted() ||
|
(idx->isSorted() ||
|
||||||
|
|
|
@ -294,6 +294,13 @@ class Condition {
|
||||||
std::vector<Index const*>&,
|
std::vector<Index const*>&,
|
||||||
SortCondition 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:
|
private:
|
||||||
//////////////////////////////////////////////////////////////////////////////
|
//////////////////////////////////////////////////////////////////////////////
|
||||||
/// @brief sort ORs for the same attribute so they are in ascending value
|
/// @brief sort ORs for the same attribute so they are in ascending value
|
||||||
|
|
|
@ -21,7 +21,7 @@
|
||||||
/// @author Michael Hackstein
|
/// @author Michael Hackstein
|
||||||
////////////////////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
#include "Aql/ConditionFinder.h"
|
#include "ConditionFinder.h"
|
||||||
#include "Aql/ExecutionPlan.h"
|
#include "Aql/ExecutionPlan.h"
|
||||||
#include "Aql/IndexNode.h"
|
#include "Aql/IndexNode.h"
|
||||||
#include "Aql/SortCondition.h"
|
#include "Aql/SortCondition.h"
|
||||||
|
@ -153,7 +153,7 @@ bool ConditionFinder::before(ExecutionNode* en) {
|
||||||
std::unique_ptr<SortCondition> sortCondition;
|
std::unique_ptr<SortCondition> sortCondition;
|
||||||
if (!en->isInInnerLoop()) {
|
if (!en->isInInnerLoop()) {
|
||||||
// we cannot optimize away a sort if we're in an inner loop ourselves
|
// 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 {
|
} else {
|
||||||
sortCondition.reset(new SortCondition);
|
sortCondition.reset(new SortCondition);
|
||||||
}
|
}
|
||||||
|
|
|
@ -38,6 +38,7 @@
|
||||||
#include "Aql/TraversalConditionFinder.h"
|
#include "Aql/TraversalConditionFinder.h"
|
||||||
#include "Aql/Variable.h"
|
#include "Aql/Variable.h"
|
||||||
#include "Aql/types.h"
|
#include "Aql/types.h"
|
||||||
|
#include "Basics/AttributeNameParser.h"
|
||||||
#include "Basics/json-utilities.h"
|
#include "Basics/json-utilities.h"
|
||||||
|
|
||||||
using namespace arangodb::aql;
|
using namespace arangodb::aql;
|
||||||
|
@ -1705,7 +1706,7 @@ struct SortToIndexNode final : public WalkerWorker<ExecutionNode> {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
SortCondition sortCondition(_sorts, _variableDefinitions);
|
SortCondition sortCondition(_sorts, std::vector<std::vector<arangodb::basics::AttributeName>>(), _variableDefinitions);
|
||||||
|
|
||||||
if (!sortCondition.isEmpty() && sortCondition.isOnlyAttributeAccess() &&
|
if (!sortCondition.isEmpty() && sortCondition.isOnlyAttributeAccess() &&
|
||||||
sortCondition.isUnidirectional()) {
|
sortCondition.isUnidirectional()) {
|
||||||
|
@ -1725,8 +1726,8 @@ struct SortToIndexNode final : public WalkerWorker<ExecutionNode> {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto numCovered =
|
size_t const numCovered =
|
||||||
sortCondition.coveredAttributes(outVariable, index->fields);
|
sortCondition.coveredAttributes(outVariable, index->fields);
|
||||||
|
|
||||||
if (numCovered == 0) {
|
if (numCovered == 0) {
|
||||||
continue;
|
continue;
|
||||||
|
@ -1734,14 +1735,15 @@ struct SortToIndexNode final : public WalkerWorker<ExecutionNode> {
|
||||||
|
|
||||||
double estimatedCost = 0.0;
|
double estimatedCost = 0.0;
|
||||||
if (!index->supportsSortCondition(
|
if (!index->supportsSortCondition(
|
||||||
&sortCondition, outVariable,
|
&sortCondition,
|
||||||
|
outVariable,
|
||||||
enumerateCollectionNode->collection()->count(),
|
enumerateCollectionNode->collection()->count(),
|
||||||
estimatedCost)) {
|
estimatedCost)) {
|
||||||
// should never happen
|
// should never happen
|
||||||
TRI_ASSERT(false);
|
TRI_ASSERT(false);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (bestIndex == nullptr || estimatedCost < bestCost) {
|
if (bestIndex == nullptr || estimatedCost < bestCost) {
|
||||||
bestIndex = index;
|
bestIndex = index;
|
||||||
bestCost = estimatedCost;
|
bestCost = estimatedCost;
|
||||||
|
@ -1790,6 +1792,10 @@ struct SortToIndexNode final : public WalkerWorker<ExecutionNode> {
|
||||||
|
|
||||||
auto const& indexes = indexNode->getIndexes();
|
auto const& indexes = indexNode->getIndexes();
|
||||||
auto cond = indexNode->condition();
|
auto cond = indexNode->condition();
|
||||||
|
TRI_ASSERT(cond != nullptr);
|
||||||
|
|
||||||
|
Variable const* outVariable = indexNode->outVariable();
|
||||||
|
TRI_ASSERT(outVariable != nullptr);
|
||||||
|
|
||||||
if (indexes.size() != 1) {
|
if (indexes.size() != 1) {
|
||||||
// can only use this index node if it uses exactly one index or multiple
|
// 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];
|
auto index = indexes[0];
|
||||||
bool handled = false;
|
bool handled = false;
|
||||||
|
|
||||||
SortCondition sortCondition(_sorts, _variableDefinitions);
|
SortCondition sortCondition(_sorts, cond->getConstAttributes(outVariable, !index->sparse), _variableDefinitions);
|
||||||
|
|
||||||
bool const isOnlyAttributeAccess =
|
bool const isOnlyAttributeAccess =
|
||||||
(!sortCondition.isEmpty() && sortCondition.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
|
// we have found a sort condition, which is unidirectional and in the same
|
||||||
// order as the IndexNode...
|
// order as the IndexNode...
|
||||||
// now check if the sort attributes match the ones of the index
|
// now check if the sort attributes match the ones of the index
|
||||||
Variable const* outVariable = indexNode->outVariable();
|
size_t const numCovered =
|
||||||
auto numCovered =
|
sortCondition.coveredAttributes(outVariable, index->fields);
|
||||||
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 condition is fully covered by index... now we can remove the
|
||||||
// sort node from the plan
|
// sort node from the plan
|
||||||
_plan->unlinkNode(_plan->getNodeById(_sortNode->id()));
|
_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
|
// now check if the index fields are the same as the sort condition
|
||||||
// fields
|
// fields
|
||||||
// e.g. FILTER c.value1 == 1 && c.value2 == 42 SORT c.value1, c.value2
|
// e.g. FILTER c.value1 == 1 && c.value2 == 42 SORT c.value1, c.value2
|
||||||
Variable const* outVariable = indexNode->outVariable();
|
size_t const numCovered =
|
||||||
size_t coveredFields =
|
sortCondition.coveredAttributes(outVariable, index->fields);
|
||||||
sortCondition.coveredAttributes(outVariable, index->fields);
|
|
||||||
|
|
||||||
if (coveredFields == sortCondition.numAttributes() &&
|
if (numCovered == sortCondition.numAttributes() &&
|
||||||
|
sortCondition.isUnidirectional() &&
|
||||||
(index->isSorted() ||
|
(index->isSorted() ||
|
||||||
index->fields.size() == sortCondition.numAttributes())) {
|
index->fields.size() == sortCondition.numAttributes())) {
|
||||||
// no need to sort
|
// no need to sort
|
||||||
|
|
|
@ -21,18 +21,34 @@
|
||||||
/// @author Jan Steemann
|
/// @author Jan Steemann
|
||||||
////////////////////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
#include "Aql/SortCondition.h"
|
#include "SortCondition.h"
|
||||||
#include "Aql/AstNode.h"
|
#include "Aql/AstNode.h"
|
||||||
|
#include "Basics/Logger.h"
|
||||||
|
|
||||||
using namespace arangodb::aql;
|
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
|
/// @brief create an empty condition
|
||||||
////////////////////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
SortCondition::SortCondition()
|
SortCondition::SortCondition()
|
||||||
: _expressions(),
|
: _fields(),
|
||||||
_fields(),
|
_constAttributes(),
|
||||||
_unidirectional(false),
|
_unidirectional(false),
|
||||||
_onlyAttributeAccess(false),
|
_onlyAttributeAccess(false),
|
||||||
_ascending(true) {}
|
_ascending(true) {}
|
||||||
|
@ -41,73 +57,22 @@ SortCondition::SortCondition()
|
||||||
/// @brief create the sort condition
|
/// @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(
|
SortCondition::SortCondition(
|
||||||
std::vector<std::pair<VariableId, bool>> const& sorts,
|
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)
|
std::unordered_map<VariableId, AstNode const*> const& variableDefinitions)
|
||||||
: _expressions(),
|
: _fields(),
|
||||||
|
_constAttributes(constAttributes),
|
||||||
_unidirectional(true),
|
_unidirectional(true),
|
||||||
_onlyAttributeAccess(true),
|
_onlyAttributeAccess(true),
|
||||||
_ascending(true) {
|
_ascending(true) {
|
||||||
|
|
||||||
|
bool foundDirection = false;
|
||||||
|
|
||||||
size_t const n = sorts.size();
|
size_t const n = sorts.size();
|
||||||
|
|
||||||
for (size_t i = 0; i < n; ++i) {
|
for (size_t i = 0; i < n; ++i) {
|
||||||
if (_unidirectional && i > 0 && sorts[i].second != sorts[i - 1].second) {
|
bool isConst = false; // const attribute?
|
||||||
_unidirectional = false;
|
|
||||||
}
|
|
||||||
if (i == 0) {
|
|
||||||
_ascending = sorts[i].second;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool handled = false;
|
bool handled = false;
|
||||||
auto variableId = sorts[i].first;
|
auto variableId = sorts[i].first;
|
||||||
|
|
||||||
|
@ -129,10 +94,30 @@ SortCondition::SortCondition(
|
||||||
|
|
||||||
_fields.emplace_back(std::make_pair(
|
_fields.emplace_back(std::make_pair(
|
||||||
static_cast<Variable const*>(node->getData()), fieldNames));
|
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) {
|
if (!handled) {
|
||||||
_fields.emplace_back(
|
_fields.emplace_back(
|
||||||
std::pair<Variable const*,
|
std::pair<Variable const*,
|
||||||
|
@ -161,40 +146,54 @@ size_t SortCondition::coveredAttributes(
|
||||||
Variable const* reference,
|
Variable const* reference,
|
||||||
std::vector<std::vector<arangodb::basics::AttributeName>> const&
|
std::vector<std::vector<arangodb::basics::AttributeName>> const&
|
||||||
indexAttributes) 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) {
|
for (size_t i = 0; i < n; /* no hoisting */) {
|
||||||
if (i >= _fields.size()) {
|
if (fieldsPosition >= _fields.size()) {
|
||||||
|
// done
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
auto const& field = _fields[fieldsPosition];
|
||||||
|
|
||||||
if (reference != _fields[i].first) {
|
// ...and check if the field is present in the index definition too
|
||||||
break;
|
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;
|
// no match
|
||||||
if (fieldNames.size() != indexAttributes[i].size()) {
|
bool isConstant = false;
|
||||||
// different attribute path
|
|
||||||
break;
|
if (IsContained(_constAttributes, indexAttributes[i])) {
|
||||||
|
// no field match, but a constant attribute
|
||||||
|
isConstant = true;
|
||||||
|
++i; // next index field
|
||||||
}
|
}
|
||||||
|
|
||||||
bool found = true;
|
if (!isConstant) {
|
||||||
for (size_t j = 0; j < indexAttributes[i].size(); ++j) {
|
if (IsContained(indexAttributes, field.second) &&
|
||||||
if (indexAttributes[i][j].shouldExpand ||
|
IsContained(_constAttributes, field.second)) {
|
||||||
fieldNames[j] != indexAttributes[i][j]) {
|
// no field match, but a constant attribute
|
||||||
// expanded attribute or different attribute
|
isConstant = true;
|
||||||
found = false;
|
++fieldsPosition;
|
||||||
break;
|
++numCovered;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!found) {
|
if (!isConstant) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
// same attribute
|
|
||||||
++numAttributes;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return numAttributes;
|
TRI_ASSERT(numCovered <= _fields.size());
|
||||||
|
return numCovered;
|
||||||
}
|
}
|
||||||
|
|
|
@ -47,13 +47,8 @@ class SortCondition {
|
||||||
/// @brief create the sort condition
|
/// @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&,
|
SortCondition(std::vector<std::pair<VariableId, bool>> const&,
|
||||||
|
std::vector<std::vector<arangodb::basics::AttributeName>> const&,
|
||||||
std::unordered_map<VariableId, AstNode const*> const&);
|
std::unordered_map<VariableId, AstNode const*> const&);
|
||||||
|
|
||||||
//////////////////////////////////////////////////////////////////////////////
|
//////////////////////////////////////////////////////////////////////////////
|
||||||
|
@ -115,11 +110,6 @@ class SortCondition {
|
||||||
std::vector<std::vector<arangodb::basics::AttributeName>> const&) const;
|
std::vector<std::vector<arangodb::basics::AttributeName>> const&) const;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
//////////////////////////////////////////////////////////////////////////////
|
|
||||||
/// @brief sort expressions
|
|
||||||
//////////////////////////////////////////////////////////////////////////////
|
|
||||||
|
|
||||||
std::vector<std::pair<AstNode const*, bool>> _expressions;
|
|
||||||
|
|
||||||
//////////////////////////////////////////////////////////////////////////////
|
//////////////////////////////////////////////////////////////////////////////
|
||||||
/// @brief fields used in the sort conditions
|
/// @brief fields used in the sort conditions
|
||||||
|
@ -127,6 +117,12 @@ class SortCondition {
|
||||||
|
|
||||||
std::vector<std::pair<Variable const*,
|
std::vector<std::pair<Variable const*,
|
||||||
std::vector<arangodb::basics::AttributeName>>> _fields;
|
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
|
/// @brief whether or not the sort is unidirectional
|
||||||
|
|
|
@ -170,7 +170,8 @@
|
||||||
var mount = validateMount(req);
|
var mount = validateMount(req);
|
||||||
var runTeardown = req.parameters.teardown;
|
var runTeardown = req.parameters.teardown;
|
||||||
var app = FoxxManager.uninstall(mount, {
|
var app = FoxxManager.uninstall(mount, {
|
||||||
teardown: runTeardown
|
teardown: runTeardown,
|
||||||
|
force: true
|
||||||
});
|
});
|
||||||
res.json({
|
res.json({
|
||||||
error: false,
|
error: false,
|
||||||
|
|
|
@ -698,7 +698,7 @@ function processQuery (query, explain) {
|
||||||
collectionVariables[node.outVariable.id] = node.collection;
|
collectionVariables[node.outVariable.id] = node.collection;
|
||||||
var types = [ ];
|
var types = [ ];
|
||||||
node.indexes.forEach(function (idx, i) {
|
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]) {
|
if (types.length === 0 || what !== types[types.length - 1]) {
|
||||||
types.push(what);
|
types.push(what);
|
||||||
}
|
}
|
||||||
|
|
|
@ -130,7 +130,7 @@ const optionsDefaults = {
|
||||||
"replication": false,
|
"replication": false,
|
||||||
"skipAql": false,
|
"skipAql": false,
|
||||||
"skipArangoB": false,
|
"skipArangoB": false,
|
||||||
"skipArangoBNonConnKeepAlive": false,
|
"skipArangoBNonConnKeepAlive": true,
|
||||||
"skipBoost": false,
|
"skipBoost": false,
|
||||||
"skipGeo": false,
|
"skipGeo": false,
|
||||||
"skipLogAnalysis": false,
|
"skipLogAnalysis": false,
|
||||||
|
@ -1807,7 +1807,6 @@ function filterTestcaseByOptions(testname, options, whichFilter) {
|
||||||
////////////////////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
let allTests = [
|
let allTests = [
|
||||||
"arangob",
|
|
||||||
"arangosh",
|
"arangosh",
|
||||||
"authentication",
|
"authentication",
|
||||||
"authentication_parameters",
|
"authentication_parameters",
|
||||||
|
@ -1822,7 +1821,8 @@ let allTests = [
|
||||||
"shell_server",
|
"shell_server",
|
||||||
"shell_server_aql",
|
"shell_server_aql",
|
||||||
"ssl_server",
|
"ssl_server",
|
||||||
"upgrade"
|
"upgrade",
|
||||||
|
"arangob"
|
||||||
];
|
];
|
||||||
|
|
||||||
////////////////////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
|
@ -697,7 +697,7 @@ function processQuery (query, explain) {
|
||||||
collectionVariables[node.outVariable.id] = node.collection;
|
collectionVariables[node.outVariable.id] = node.collection;
|
||||||
var types = [ ];
|
var types = [ ];
|
||||||
node.indexes.forEach(function (idx, i) {
|
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]) {
|
if (types.length === 0 || what !== types[types.length - 1]) {
|
||||||
types.push(what);
|
types.push(what);
|
||||||
}
|
}
|
||||||
|
|
|
@ -87,7 +87,8 @@ function Repository(collection, opts) {
|
||||||
|
|
||||||
EventEmitter.call(this);
|
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') {
|
if (EVENTS.indexOf(eventName) === -1 || typeof listener !== 'function') {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
|
@ -31,7 +31,6 @@
|
||||||
var jsunity = require("jsunity");
|
var jsunity = require("jsunity");
|
||||||
var db = require("@arangodb").db;
|
var db = require("@arangodb").db;
|
||||||
|
|
||||||
|
|
||||||
////////////////////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
/// @brief test suite
|
/// @brief test suite
|
||||||
////////////////////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
|
@ -309,9 +308,7 @@ function optimizerIndexesSortTestSuite () {
|
||||||
////////////////////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
testCannotUseHashIndexForSortIfConstRanges : function () {
|
testCannotUseHashIndexForSortIfConstRanges : function () {
|
||||||
AQL_EXECUTE("FOR i IN " + c.name() + " UPDATE i WITH { value2: i.value, value3: i.value } IN " + c.name());
|
c.ensureIndex({ type: "hash", fields: [ "value2", "value3" ] });
|
||||||
|
|
||||||
c.ensureHashIndex("value2", "value3");
|
|
||||||
|
|
||||||
var queries = [
|
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 ],
|
[ "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
|
/// @brief test index usage
|
||||||
////////////////////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
|
@ -437,17 +480,22 @@ function optimizerIndexesSortTestSuite () {
|
||||||
/// @brief test index usage
|
/// @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());
|
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");
|
c.ensureSkiplist("value2", "value3", "value4");
|
||||||
|
|
||||||
var queries = [
|
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 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 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 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 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 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.value4 DESC RETURN i.value2",
|
||||||
"FOR i IN " + c.name() + " FILTER i.value2 == 2 && i.value3 == 2 SORT i.value3 ASC 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("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
|
/// @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",
|
"FOR i IN " + c.name() + " FILTER i.value2 == 1 && i.value3 == null SORT i.value2 RETURN i.value2",
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|
||||||
queries.forEach(function(query) {
|
queries.forEach(function(query) {
|
||||||
var plan = AQL_EXPLAIN(query).plan;
|
var plan = AQL_EXPLAIN(query).plan;
|
||||||
var nodeTypes = plan.nodes.map(function(node) {
|
var nodeTypes = plan.nodes.map(function(node) {
|
||||||
|
|
|
@ -203,12 +203,15 @@ function optimizerRuleTestSuite () {
|
||||||
////////////////////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
testResults : function () {
|
testResults : function () {
|
||||||
var numbers = [ ], strings = [ ], reversed = [ ];
|
var groups = [ ], numbers = [ ], strings = [ ], reversed = [ ], i;
|
||||||
for (var i = 1; i <= 100; ++i) {
|
for (i = 1; i <= 100; ++i) {
|
||||||
numbers.push(i);
|
numbers.push(i);
|
||||||
strings.push("test" + i);
|
strings.push("test" + i);
|
||||||
reversed.push("test" + (101 - i));
|
reversed.push("test" + (101 - i));
|
||||||
}
|
}
|
||||||
|
for (i = 0; i < 10; ++i) {
|
||||||
|
groups.push(i);
|
||||||
|
}
|
||||||
|
|
||||||
var queries = [
|
var queries = [
|
||||||
[ "LET values = NOOPT(RANGE(1, 100)) FOR i IN 1..100 FILTER i IN values RETURN i", numbers ],
|
[ "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) 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 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 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) {
|
queries.forEach(function(query) {
|
||||||
|
|
|
@ -151,6 +151,57 @@ function optimizerRuleTestSuite() {
|
||||||
skiplist = null;
|
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
|
/// @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.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.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.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],
|
["FOR v IN " + colName + " SORT CONCAT(TO_STRING(v.a), \"lol\") RETURN [v.a]", true],
|
||||||
// TODO: limit blocks sort atm.
|
// TODO: limit blocks sort atm.
|
||||||
["FOR v IN " + colName + " FILTER v.a > 2 LIMIT 3 SORT v.a RETURN [v.a]", false],
|
["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);
|
hasIndexNode(XPresult);
|
||||||
|
|
||||||
// -> combined use-index-for-sort and use-index-range
|
// -> 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;
|
QResults[2] = AQL_EXECUTE(query, { }, paramIndexFromSort_IndexRange).json;
|
||||||
XPresult = AQL_EXPLAIN(query, { }, paramIndexFromSort_IndexRange);
|
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,
|
/// @brief test in detail that an index range fullfills everything the sort does,
|
||||||
// and thus the sort is removed.
|
// 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]";
|
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,
|
/// @brief test in detail that an index range fullfills everything the sort does,
|
||||||
// and thus the sort is removed; multi-dimensional indexes are utilized.
|
// 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 query = "FOR v IN " + colName + " FILTER v.a == 1 SORT v.a, v.b RETURN [v.a, v.b, v.c]";
|
||||||
var XPresult;
|
var XPresult;
|
||||||
|
|
|
@ -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;
|
||||||
|
}
|
|
@ -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);
|
||||||
|
});
|
||||||
|
}
|
Loading…
Reference in New Issue