1
0
Fork 0

make use of sortedness of rocksdb primary index (#7788)

This commit is contained in:
Jan Christoph Uhde 2019-01-11 09:40:22 +01:00 committed by Jan
parent 63f5379115
commit f65853e30f
22 changed files with 1320 additions and 293 deletions

View File

@ -1,6 +1,19 @@
devel
-----
* RocksDB primary index can now be used by the optimizer to optimize queries
that use `_key` or `_id` in SORT and FILTER conditions.
* the web UI will now by default show the documents of a collection lexicographically
sorted by their `_key` values.
Previous versions of ArangoDB tried to interpret `_key` values as numeric values if
possible and sorted by these. That previous sort strategy never used an index and
could have caused unnecessary overhead. The new version will now use an index for
sorting for the RocksDB engine, but may change the order in which documents are
shown in the web UI (e.g. now a `_key` value of "10" will be shown before a `_key`
value of "9").
* fixed known issue #445: ArangoSearch ignores `_id` attribute even if `includeAllFields`
is set to `true`.
@ -8,9 +21,8 @@ devel
* upgraded bundled curl library to version 7.63
* fix issue #7900: Bind values of `null` are not replaced by
empty string anymore, when toggling between json and table
view in the web-ui.
* fix issue #7900: Bind values of `null` are not replaced by empty string anymore,
when toggling between JSON and table view in the web UI.
* Use base64url to encode and decode JWT parts.

View File

@ -5,6 +5,14 @@ The following list shows in detail which features have been added or improved in
ArangoDB 3.5. ArangoDB 3.5 also contains several bug fixes that are not listed
here.
Customer Relevant
-----------------
* The optimizer can now make use of the sorted-ness of primary indexes if the
RocksDB engine is used. This means the primary index can be utilized for
sorting by `_key` or `_id` attribute as well as for range queries (note that
the document key is still a string).
Internal
--------

View File

@ -6,6 +6,15 @@ upgrading to ArangoDB 3.5, and adjust any client programs if necessary.
The following incompatible changes have been made in ArangoDB 3.5:
UI
--
Primary index keys will now always be sorted in lexicographical order as keys are
strings. An exception for values representing numerical values has been removed
when shown in the UI. Therefore a key with value "10" will be displayed before
a key having "9" as value.
AQL
---
@ -34,4 +43,3 @@ undefined.
This change is about making queries as the above fail with a parse error, as an
unknown variable `key1` is accessed here, avoiding the undefined behavior. This is
also in line with what the documentation states about variable invalidation.

View File

@ -1,6 +1,5 @@
////////////////////////////////////////////////////////////////////////////////
/// DISCLAIMER
///
/// Copyright 2014-2016 ArangoDB GmbH, Cologne, Germany
/// Copyright 2004-2014 triAGENS GmbH, Cologne, Germany
///
@ -175,7 +174,7 @@ std::unordered_map<int, std::string const> const AstNode::ValueTypeNames{
namespace {
/// @brief quick translation array from an AST node value type to a VPack type
static std::array<VPackValueType, 5> const valueTypes{{
std::array<VPackValueType, 5> const valueTypes{{
VPackValueType::Null, // VALUE_TYPE_NULL = 0,
VPackValueType::Bool, // VALUE_TYPE_BOOL = 1,
VPackValueType::Int, // VALUE_TYPE_INT = 2,
@ -195,7 +194,29 @@ static_assert(AstNodeValueType::VALUE_TYPE_STRING == 4,
"incorrect ast node value types");
/// @brief get the node type for inter-node comparisons
static VPackValueType getNodeCompareType(AstNode const* node) {
inline int valueTypeOrder(VPackValueType type) {
switch (type) {
case VPackValueType::Null:
return 0;
case VPackValueType::Bool:
return 1;
case VPackValueType::Int:
case VPackValueType::Double:
return 2;
case VPackValueType::String:
case VPackValueType::Custom: // _id
return 3;
case VPackValueType::Array:
return 4;
case VPackValueType::Object:
return 5;
default:
return 0; // null
}
}
/// @brief get the node type for inter-node comparisons
VPackValueType getNodeCompareType(AstNode const* node) {
TRI_ASSERT(node != nullptr);
if (node->type == NODE_TYPE_VALUE) {
@ -215,7 +236,7 @@ static VPackValueType getNodeCompareType(AstNode const* node) {
return VPackValueType::Null;
}
static inline int compareDoubleValues(double lhs, double rhs) {
inline int compareDoubleValues(double lhs, double rhs) {
if (arangodb::almostEquals(lhs, rhs)) {
return 0;
}
@ -258,7 +279,7 @@ int arangodb::aql::CompareAstNodes(AstNode const* lhs, AstNode const* rhs, bool
static_cast<double>(rhs->getIntValue()));
}
int diff = static_cast<int>(lType) - static_cast<int>(rType);
int diff = valueTypeOrder(lType) - valueTypeOrder(rType);
TRI_ASSERT(diff != 0);
@ -816,28 +837,31 @@ uint64_t AstNode::hashValue(uint64_t hash) const noexcept {
/// @brief dump the node (for debugging purposes)
#ifdef ARANGODB_ENABLE_MAINTAINER_MODE
void AstNode::dump(int level) const {
std::ostream& AstNode::toStream(std::ostream& os, int level) const {
for (int i = 0; i < level; ++i) {
std::cout << " ";
os << " ";
}
std::cout << "- " << getTypeString();
os << "- " << getTypeString();
if (type == NODE_TYPE_VALUE || type == NODE_TYPE_ARRAY) {
std::cout << ": " << toVelocyPackValue().get()->toJson();
os << ": " << toVelocyPackValue().get()->toJson();
} else if (type == NODE_TYPE_ATTRIBUTE_ACCESS) {
std::cout << ": " << getString();
os << ": " << getString();
} else if (type == NODE_TYPE_REFERENCE) {
std::cout << ": " << static_cast<Variable const*>(getData())->name;
os << ": " << static_cast<Variable const*>(getData())->name;
}
std::cout << "\n";
os << "\n";
size_t const n = numMembers();
for (size_t i = 0; i < n; ++i) {
auto sub = getMemberUnchecked(i);
sub->dump(level + 1);
sub->toStream(os, level + 1);
}
return os;
}
void AstNode::dump(int indent) const { toStream(std::cout, indent); }
#endif
/// @brief compute the value for a constant value node

View File

@ -262,6 +262,7 @@ struct AstNode {
/// @brief dump the node (for debugging purposes)
#ifdef ARANGODB_ENABLE_MAINTAINER_MODE
std::ostream& toStream(std::ostream& os, int indent) const;
void dump(int indent) const;
#endif

View File

@ -156,7 +156,7 @@ arangodb::aql::AstNode* IndexBlock::makeUnique(arangodb::aql::AstNode* node) con
bool isSorted = false;
bool isSparse = false;
auto unused = trx->getIndexFeatures(_indexes[_currentIndex], isSorted, isSparse);
if (isSparse) {
if (isSparse || isSorted) {
// the index is sorted. we need to use SORTED_UNIQUE to get the
// result back in index order
return ast->createNodeFunctionCall(TRI_CHAR_LENGTH_PAIR("SORTED_UNIQUE"), array);

View File

@ -149,7 +149,8 @@ bool ClusterIndex::isSorted() const {
return _indexType == Index::TRI_IDX_TYPE_SKIPLIST_INDEX ||
_indexType == Index::TRI_IDX_TYPE_PERSISTENT_INDEX;
} else if (_engineType == ClusterEngineType::RocksDBEngine) {
return _indexType == Index::TRI_IDX_TYPE_EDGE_INDEX ||
return _indexType == Index::TRI_IDX_TYPE_PRIMARY_INDEX ||
_indexType == Index::TRI_IDX_TYPE_EDGE_INDEX ||
_indexType == Index::TRI_IDX_TYPE_HASH_INDEX ||
_indexType == Index::TRI_IDX_TYPE_SKIPLIST_INDEX ||
_indexType == Index::TRI_IDX_TYPE_PERSISTENT_INDEX ||
@ -209,6 +210,17 @@ bool ClusterIndex::supportsFilterCondition(
size_t itemsInIndex, size_t& estimatedItems, double& estimatedCost) const {
switch (_indexType) {
case TRI_IDX_TYPE_PRIMARY_INDEX: {
if (_engineType == ClusterEngineType::RocksDBEngine) {
std::unordered_map<size_t, std::vector<arangodb::aql::AstNode const*>> found;
std::unordered_set<std::string> nonNullAttributes;
std::size_t values = 0;
SkiplistIndexAttributeMatcher::matchAttributes(this, node, reference, found,
values, nonNullAttributes,
/*skip evaluation (during execution)*/ false);
estimatedItems = values;
return !found.empty();
}
// MMFiles et al
SimpleAttributeEqualityMatcher matcher(PrimaryIndexAttributes);
return matcher.matchOne(this, node, reference, itemsInIndex, estimatedItems, estimatedCost);
}
@ -274,6 +286,15 @@ bool ClusterIndex::supportsSortCondition(arangodb::aql::SortCondition const* sor
size_t& coveredAttributes) const {
switch (_indexType) {
case TRI_IDX_TYPE_PRIMARY_INDEX:
if (_engineType == ClusterEngineType::MMFilesEngine) {
return Index::supportsSortCondition(sortCondition, reference, itemsInIndex,
estimatedCost, coveredAttributes);
} else if (_engineType == ClusterEngineType::RocksDBEngine) {
return PersistentIndexAttributeMatcher::supportsSortCondition(this, sortCondition, reference,
itemsInIndex, estimatedCost,
coveredAttributes);
}
break;
case TRI_IDX_TYPE_GEO_INDEX:
case TRI_IDX_TYPE_GEO1_INDEX:
case TRI_IDX_TYPE_GEO2_INDEX:
@ -333,8 +354,13 @@ aql::AstNode* ClusterIndex::specializeCondition(aql::AstNode* node,
aql::Variable const* reference) const {
switch (_indexType) {
case TRI_IDX_TYPE_PRIMARY_INDEX: {
SimpleAttributeEqualityMatcher matcher(PrimaryIndexAttributes);
return matcher.specializeOne(this, node, reference);
if (_engineType == ClusterEngineType::MMFilesEngine) {
SimpleAttributeEqualityMatcher matcher(PrimaryIndexAttributes);
return matcher.specializeOne(this, node, reference);
} else if (_engineType == ClusterEngineType::RocksDBEngine) {
return SkiplistIndexAttributeMatcher::specializeCondition(this, node, reference);
}
return node;
}
// should not be called for these
case TRI_IDX_TYPE_GEO_INDEX:

View File

@ -28,6 +28,7 @@
#include "Basics/Common.h"
#include "Basics/Exceptions.h"
#include "Basics/Result.h"
#include "Basics/StaticStrings.h"
#include "Basics/StringRef.h"
#include "VocBase/LocalDocumentId.h"
#include "VocBase/voc-types.h"
@ -148,12 +149,18 @@ class Index {
}
/// @brief whether or not any attribute is expanded
inline bool attributeMatches(std::vector<arangodb::basics::AttributeName> const& attribute) const {
inline bool attributeMatches(std::vector<arangodb::basics::AttributeName> const& attribute,
bool isPrimary = false) const {
for (auto const& it : _fields) {
if (arangodb::basics::AttributeName::isIdentical(attribute, it, true)) {
return true;
}
}
if (isPrimary) {
static std::vector<arangodb::basics::AttributeName> const vec_id{
{StaticStrings::IdString, false}};
return arangodb::basics::AttributeName::isIdentical(attribute, vec_id, true);
}
return false;
}
@ -378,10 +385,13 @@ class Index {
mutable bool _unique;
mutable bool _sparse;
// use this with c++17 -- attributeMatches
// static inline std::vector<arangodb::basics::AttributeName> const vec_id {{ StaticStrings::IdString, false }};
};
} // namespace arangodb
std::ostream& operator<<(std::ostream&, arangodb::Index const*);
std::ostream& operator<<(std::ostream&, arangodb::Index const&);
#endif
#endif

View File

@ -26,6 +26,7 @@
#include "Aql/AstNode.h"
#include "Aql/SortCondition.h"
#include "Aql/Variable.h"
#include "Basics/StaticStrings.h"
#include "Basics/StringRef.h"
#include "Indexes/Index.h"
#include "Indexes/SimpleAttributeEqualityMatcher.h"
@ -34,17 +35,22 @@
using namespace arangodb;
bool SkiplistIndexAttributeMatcher::accessFitsIndex(
arangodb::Index const* idx, arangodb::aql::AstNode const* access,
arangodb::aql::AstNode const* other, arangodb::aql::AstNode const* op,
arangodb::aql::Variable const* reference,
std::unordered_map<size_t, std::vector<arangodb::aql::AstNode const*>>& found,
std::unordered_set<std::string>& nonNullAttributes, bool isExecution) {
arangodb::Index const* idx, // index
arangodb::aql::AstNode const* access, // attribute access
arangodb::aql::AstNode const* other, // eg const value
arangodb::aql::AstNode const* op, // binary operation that is parent of access and other
arangodb::aql::Variable const* reference, // variable used in access(es)
std::unordered_map<size_t /*offset in idx->fields()*/, std::vector<arangodb::aql::AstNode const*> /*conjunct - operation*/>& found, // marks operations covered by index-fields
std::unordered_set<std::string>& nonNullAttributes, // set of stringified op-childeren (access other) that may not be null
bool isExecution // skip usage check in execution phase
) {
if (!idx->canUseConditionPart(access, other, op, reference, nonNullAttributes, isExecution)) {
return false;
}
arangodb::aql::AstNode const* what = access;
std::pair<arangodb::aql::Variable const*, std::vector<arangodb::basics::AttributeName>> attributeData;
bool const isPrimaryIndex = idx->type() == arangodb::Index::IndexType::TRI_IDX_TYPE_PRIMARY_INDEX;
if (op->type != arangodb::aql::NODE_TYPE_OPERATOR_BINARY_IN) {
if (!what->isAttributeAccessForVariable(attributeData) || attributeData.first != reference) {
@ -63,25 +69,19 @@ bool SkiplistIndexAttributeMatcher::accessFitsIndex(
// ok, we do have an IN here... check if it's something like 'value' IN
// doc.value[*]
TRI_ASSERT(op->type == arangodb::aql::NODE_TYPE_OPERATOR_BINARY_IN);
bool canUse = false;
if (what->isAttributeAccessForVariable(attributeData) && attributeData.first == reference &&
!arangodb::basics::TRI_AttributeNamesHaveExpansion(attributeData.second) &&
idx->attributeMatches(attributeData.second)) {
idx->attributeMatches(attributeData.second, isPrimaryIndex)) {
// doc.value IN 'value'
// can use this index
canUse = true;
} else {
} else if (other->isAttributeAccessForVariable(attributeData) &&
attributeData.first == reference &&
idx->isAttributeExpanded(attributeData.second) &&
idx->attributeMatches(attributeData.second, isPrimaryIndex)) {
// check for 'value' IN doc.value AND 'value' IN doc.value[*]
what = other;
if (what->isAttributeAccessForVariable(attributeData) && attributeData.first == reference &&
idx->isAttributeExpanded(attributeData.second) &&
idx->attributeMatches(attributeData.second)) {
canUse = true;
}
}
if (!canUse) {
what = other; // if what should be used later
} else {
return false;
}
}
@ -102,6 +102,15 @@ bool SkiplistIndexAttributeMatcher::accessFitsIndex(
bool match = arangodb::basics::AttributeName::isIdentical(idx->fields()[i],
fieldNames, true);
// make exception for primary index as we do not need to match "_key, _id"
// but can go directly for "_id"
if (!match &&
isPrimaryIndex &&
i == 0 &&
fieldNames[i].name == StaticStrings::IdString) {
match = true;
}
if (match) {
// mark ith attribute as being covered
found[i].emplace_back(op);
@ -127,6 +136,10 @@ void SkiplistIndexAttributeMatcher::matchAttributes(
arangodb::aql::Variable const* reference,
std::unordered_map<size_t, std::vector<arangodb::aql::AstNode const*>>& found,
size_t& values, std::unordered_set<std::string>& nonNullAttributes, bool isExecution) {
// assert we have a proper formed conditiona - naray conjunction
TRI_ASSERT(node->type == arangodb::aql::NODE_TYPE_OPERATOR_NARY_AND);
// inspect the the conjuncts - allowed are binary comparisons and a contains check
for (size_t i = 0; i < node->numMembers(); ++i) {
auto op = node->getMember(i);
@ -147,7 +160,7 @@ void SkiplistIndexAttributeMatcher::matchAttributes(
case arangodb::aql::NODE_TYPE_OPERATOR_BINARY_IN:
if (accessFitsIndex(idx, op->getMember(0), op->getMember(1), op,
reference, found, nonNullAttributes, isExecution)) {
if (op->getMember(1)->isAttributeAccessForVariable(reference, false)) {
if (op->getMember(1)->isAttributeAccessForVariable(reference, /*indexed access*/ false)) {
// 'abc' IN doc.attr[*]
++values;
} else {
@ -255,8 +268,7 @@ bool SkiplistIndexAttributeMatcher::supportsFilterCondition(
}
estimatedItems = values;
// ALTERNATIVE: estimatedCost = static_cast<double>(estimatedItems *
// values);
// ALTERNATIVE: estimatedCost = static_cast<double>(estimatedItems * values);
estimatedCost = (std::max)(static_cast<double>(1),
std::log2(static_cast<double>(itemsInIndex)) * values);
@ -453,6 +465,7 @@ arangodb::aql::AstNode* SkiplistIndexAttributeMatcher::specializeCondition(
TRI_ASSERT(it->type != arangodb::aql::NODE_TYPE_OPERATOR_BINARY_NE);
node->addMember(it);
}
return node;
}

View File

@ -105,10 +105,9 @@ class RocksDBGenericIterator {
~RocksDBGenericIterator() {}
// the following functions return if the iterator
// is valid and in bounds on return.
bool next(GenericCallback const& cb,
size_t count); // number of documents the callback should be applied to
//* The following functions returns true if the iterator is valid within bounds on return.
// @param limit - number of documents the callback should be applied to
bool next(GenericCallback const& cb, size_t limit);
// documents to skip, skipped documents
bool skip(uint64_t count, uint64_t& skipped);

View File

@ -99,6 +99,11 @@ RocksDBKeyBounds RocksDBKeyBounds::UniqueVPackIndex(uint64_t indexId, VPackSlice
return RocksDBKeyBounds(RocksDBEntryType::UniqueVPackIndexValue, indexId, left, right);
}
RocksDBKeyBounds RocksDBKeyBounds::PrimaryIndex(uint64_t indexId, std::string const& left,
std::string const& right) {
return RocksDBKeyBounds(RocksDBEntryType::PrimaryIndexValue, indexId, left, right);
}
/// used for point lookups
RocksDBKeyBounds RocksDBKeyBounds::UniqueVPackIndex(uint64_t indexId, VPackSlice const& left) {
return RocksDBKeyBounds(RocksDBEntryType::UniqueVPackIndexValue, indexId, left);
@ -225,6 +230,38 @@ rocksdb::ColumnFamilyHandle* RocksDBKeyBounds::columnFamily() const {
THROW_ARANGO_EXCEPTION(TRI_ERROR_TYPE_ERROR);
}
/// bounds to iterate over specified word or edge
RocksDBKeyBounds::RocksDBKeyBounds(RocksDBEntryType type, uint64_t id,
std::string const& lower, std::string const& upper)
: _type(type) {
switch (_type) {
case RocksDBEntryType::PrimaryIndexValue: {
// format: id lower id upper
// start end
_internals.reserve(sizeof(id) + (lower.size() + sizeof(_stringSeparator)) +
sizeof(id) + (upper.size() + sizeof(_stringSeparator)));
// id - lower
uint64ToPersistent(_internals.buffer(), id);
_internals.buffer().append(lower.data(), lower.length());
_internals.push_back(_stringSeparator);
// set separator
_internals.separate();
// id - upper
uint64ToPersistent(_internals.buffer(), id);
_internals.buffer().append(upper.data(), upper.length());
_internals.push_back(_stringSeparator);
break;
}
default:
THROW_ARANGO_EXCEPTION(TRI_ERROR_BAD_PARAMETER);
}
}
// constructor for an empty bound. do not use for anything but to
// default-construct a key bound!
RocksDBKeyBounds::RocksDBKeyBounds()

View File

@ -70,6 +70,13 @@ class RocksDBKeyBounds {
//////////////////////////////////////////////////////////////////////////////
static RocksDBKeyBounds PrimaryIndex(uint64_t indexId);
//////////////////////////////////////////////////////////////////////////////
/// @brief Bounds for all index-entries- within a range belonging to a
/// specified primary index
//////////////////////////////////////////////////////////////////////////////
static RocksDBKeyBounds PrimaryIndex(uint64_t indexId, std::string const& lower,
std::string const& upper);
//////////////////////////////////////////////////////////////////////////////
/// @brief Bounds for all index-entries belonging to a specified edge index
//////////////////////////////////////////////////////////////////////////////
@ -209,6 +216,8 @@ class RocksDBKeyBounds {
RocksDBKeyBounds(RocksDBEntryType type, uint64_t first,
VPackSlice const& second, VPackSlice const& third);
RocksDBKeyBounds(RocksDBEntryType type, uint64_t first, uint64_t second, uint64_t third);
RocksDBKeyBounds(RocksDBEntryType type, uint64_t id, std::string const& lower,
std::string const& upper);
private:
// private class that will hold both bounds in a single buffer (with only one

View File

@ -22,6 +22,7 @@
////////////////////////////////////////////////////////////////////////////////
#include "RocksDBPrimaryIndex.h"
#include "Aql/Ast.h"
#include "Aql/AstNode.h"
#include "Basics/Exceptions.h"
#include "Basics/StaticStrings.h"
@ -29,7 +30,8 @@
#include "Cache/CachedValue.h"
#include "Cache/TransactionalCache.h"
#include "Cluster/ServerState.h"
#include "Indexes/SimpleAttributeEqualityMatcher.h"
#include "Indexes/PersistentIndexAttributeMatcher.h"
#include "Indexes/SkiplistIndexAttributeMatcher.h"
#include "Logger/Logger.h"
#include "RocksDBEngine/RocksDBCollection.h"
#include "RocksDBEngine/RocksDBCommon.h"
@ -63,6 +65,116 @@
using namespace arangodb;
namespace {
std::string const lowest; // smallest possible key
std::string const highest = "\xFF"; // greatest possible key
}
RocksDBPrimaryIndexRangeIterator::RocksDBPrimaryIndexRangeIterator(
LogicalCollection* collection, transaction::Methods* trx,
arangodb::RocksDBPrimaryIndex const* index, bool reverse, RocksDBKeyBounds&& bounds)
: IndexIterator(collection, trx),
_index(index),
_cmp(index->comparator()),
_reverse(reverse),
_bounds(std::move(bounds)) {
TRI_ASSERT(index->columnFamily() == RocksDBColumnFamily::primary());
RocksDBMethods* mthds = RocksDBTransactionState::toMethods(trx);
rocksdb::ReadOptions options = mthds->iteratorReadOptions();
// we need to have a pointer to a slice for the upper bound
// so we need to assign the slice to an instance variable here
if (reverse) {
_rangeBound = _bounds.start();
options.iterate_lower_bound = &_rangeBound;
} else {
_rangeBound = _bounds.end();
options.iterate_upper_bound = &_rangeBound;
}
TRI_ASSERT(options.prefix_same_as_start);
_iterator = mthds->NewIterator(options, index->columnFamily());
if (reverse) {
_iterator->SeekForPrev(_bounds.end());
} else {
_iterator->Seek(_bounds.start());
}
}
/// @brief Reset the cursor
void RocksDBPrimaryIndexRangeIterator::reset() {
TRI_ASSERT(_trx->state()->isRunning());
if (_reverse) {
_iterator->SeekForPrev(_bounds.end());
} else {
_iterator->Seek(_bounds.start());
}
}
bool RocksDBPrimaryIndexRangeIterator::outOfRange() const {
TRI_ASSERT(_trx->state()->isRunning());
if (_reverse) {
return (_cmp->Compare(_iterator->key(), _bounds.start()) < 0);
} else {
return (_cmp->Compare(_iterator->key(), _bounds.end()) > 0);
}
}
bool RocksDBPrimaryIndexRangeIterator::next(LocalDocumentIdCallback const& cb, size_t limit) {
TRI_ASSERT(_trx->state()->isRunning());
if (limit == 0 || !_iterator->Valid() || outOfRange()) {
// No limit no data, or we are actually done. The last call should have
// returned false
TRI_ASSERT(limit > 0); // Someone called with limit == 0. Api broken
return false;
}
while (limit > 0) {
TRI_ASSERT(_index->objectId() == RocksDBKey::objectId(_iterator->key()));
cb(RocksDBValue::documentId(_iterator->value()));
--limit;
if (_reverse) {
_iterator->Prev();
} else {
_iterator->Next();
}
if (!_iterator->Valid() || outOfRange()) {
return false;
}
}
return true;
}
void RocksDBPrimaryIndexRangeIterator::skip(uint64_t count, uint64_t& skipped) {
TRI_ASSERT(_trx->state()->isRunning());
if (!_iterator->Valid() || outOfRange()) {
return;
}
while (count > 0) {
TRI_ASSERT(_index->objectId() == RocksDBKey::objectId(_iterator->key()));
--count;
++skipped;
if (_reverse) {
_iterator->Prev();
} else {
_iterator->Next();
}
if (!_iterator->Valid() || outOfRange()) {
return;
}
}
}
// ================ Primary Index Iterator ================
/// @brief hard-coded vector of the index attributes
@ -162,7 +274,7 @@ bool RocksDBPrimaryIndexInIterator::next(LocalDocumentIdCallback const& cb, size
if (!_iterator.valid()) {
return false;
}
}
}
return true;
}
@ -187,7 +299,7 @@ bool RocksDBPrimaryIndexInIterator::nextCovering(DocumentCallback const& cb, siz
if (!_iterator.valid()) {
return false;
}
}
} while (true);
return true;
}
@ -415,8 +527,24 @@ bool RocksDBPrimaryIndex::supportsFilterCondition(
std::vector<std::shared_ptr<arangodb::Index>> const& allIndexes,
arangodb::aql::AstNode const* node, arangodb::aql::Variable const* reference,
size_t itemsInIndex, size_t& estimatedItems, double& estimatedCost) const {
SimpleAttributeEqualityMatcher matcher(IndexAttributes);
return matcher.matchOne(this, node, reference, itemsInIndex, estimatedItems, estimatedCost);
std::unordered_map<size_t, std::vector<arangodb::aql::AstNode const*>> found;
std::unordered_set<std::string> nonNullAttributes;
std::size_t values = 0;
SkiplistIndexAttributeMatcher::matchAttributes(this, node, reference, found,
values, nonNullAttributes,
/*skip evaluation (during execution)*/ false);
estimatedItems = values;
return !found.empty();
}
bool RocksDBPrimaryIndex::supportsSortCondition(arangodb::aql::SortCondition const* sortCondition,
arangodb::aql::Variable const* reference,
size_t itemsInIndex, double& estimatedCost,
size_t& coveredAttributes) const {
return PersistentIndexAttributeMatcher::supportsSortCondition(this, sortCondition, reference,
itemsInIndex, estimatedCost,
coveredAttributes);
}
/// @brief creates an IndexIterator for the given Condition
@ -424,49 +552,215 @@ IndexIterator* RocksDBPrimaryIndex::iteratorForCondition(
transaction::Methods* trx, ManagedDocumentResult*, arangodb::aql::AstNode const* node,
arangodb::aql::Variable const* reference, IndexIteratorOptions const& opts) {
TRI_ASSERT(!isSorted() || opts.sorted);
if (node == nullptr) {
// full range scan
return new RocksDBPrimaryIndexRangeIterator(
&_collection /*logical collection*/, trx, this, !opts.ascending /*reverse*/,
RocksDBKeyBounds::PrimaryIndex(_objectId, ::lowest, ::highest));
}
TRI_ASSERT(node->type == aql::NODE_TYPE_OPERATOR_NARY_AND);
TRI_ASSERT(node->numMembers() == 1);
auto comp = node->getMember(0);
// assume a.b == value
auto attrNode = comp->getMember(0);
auto valNode = comp->getMember(1);
size_t const n = node->numMembers();
TRI_ASSERT(n >= 1);
if (attrNode->type != aql::NODE_TYPE_ATTRIBUTE_ACCESS) {
// value == a.b -> flip the two sides
attrNode = comp->getMember(1);
valNode = comp->getMember(0);
}
TRI_ASSERT(attrNode->type == aql::NODE_TYPE_ATTRIBUTE_ACCESS);
if (n == 1) {
auto comp = node->getMember(0);
// assume a.b == value
auto attrNode = comp->getMember(0);
auto valNode = comp->getMember(1);
if (comp->type == aql::NODE_TYPE_OPERATOR_BINARY_EQ) {
// a.b == value
return createEqIterator(trx, attrNode, valNode);
}
if (comp->type == aql::NODE_TYPE_OPERATOR_BINARY_IN) {
// a.b IN values
if (valNode->isArray()) {
// a.b IN array
return createInIterator(trx, attrNode, valNode);
if (attrNode->type != aql::NODE_TYPE_ATTRIBUTE_ACCESS) {
// value == a.b -> flip the two sides
attrNode = comp->getMember(1);
valNode = comp->getMember(0);
}
TRI_ASSERT(attrNode->type == aql::NODE_TYPE_ATTRIBUTE_ACCESS);
if (comp->type == aql::NODE_TYPE_OPERATOR_BINARY_EQ) {
// a.b == value
return createEqIterator(trx, attrNode, valNode);
}
if (comp->type == aql::NODE_TYPE_OPERATOR_BINARY_IN) {
// a.b IN values
if (valNode->isArray()) {
// a.b IN array
return createInIterator(trx, attrNode, valNode, opts.ascending);
}
}
// fall-through intentional here
}
auto removeCollectionFromString =
[this, &trx](bool isId, std::string& value) -> int {
if (isId) {
char const* key = nullptr;
size_t outLength = 0;
std::shared_ptr<LogicalCollection> collection;
Result res = trx->resolveId(value.data(), value.length(), collection, key, outLength);
if (!res.ok()) {
// using the name of an unknown collection
if (_isRunningInCluster) {
// translate from our own shard name to "real" collection name
return value.compare(trx->resolver()->getCollectionName(_collection.id()));
}
return value.compare(_collection.name());
}
TRI_ASSERT(key);
TRI_ASSERT(collection);
if (!_isRunningInCluster && collection->id() != _collection.id()) {
// using the name of a different collection...
return value.compare(_collection.name());
} else if (_isRunningInCluster && collection->planId() != _collection.planId()) {
// using a different collection
// translate from our own shard name to "real" collection name
return value.compare(trx->resolver()->getCollectionName(_collection.id()));
}
// strip collection name prefix
value = std::string(key, outLength);
}
// usage of _key or same collection name
return 0;
};
std::string lower;
std::string upper;
bool lowerFound = false;
bool upperFound = false;
for (size_t i = 0; i < n; ++i) {
aql::AstNode const* comp = node->getMemberUnchecked(i);
if (comp == nullptr) {
continue;
}
auto type = comp->type;
if (!(type == aql::NODE_TYPE_OPERATOR_BINARY_LE ||
type == aql::NODE_TYPE_OPERATOR_BINARY_LT || type == aql::NODE_TYPE_OPERATOR_BINARY_GE ||
type == aql::NODE_TYPE_OPERATOR_BINARY_GT ||
type == aql::NODE_TYPE_OPERATOR_BINARY_EQ
)) {
return new EmptyIndexIterator(&_collection, trx);
}
auto attrNode = comp->getMember(0);
auto valNode = comp->getMember(1);
if (attrNode->type != aql::NODE_TYPE_ATTRIBUTE_ACCESS) {
// value == a.b -> flip the two sides
attrNode = comp->getMember(1);
valNode = comp->getMember(0);
type = aql::Ast::ReverseOperator(type);
}
TRI_ASSERT(attrNode->type == aql::NODE_TYPE_ATTRIBUTE_ACCESS);
bool const isId = (attrNode->stringEquals(StaticStrings::IdString));
std::string value; // empty string == lower bound
if (valNode->isStringValue()) {
value = valNode->getString();
} else if (valNode->isObject() || valNode->isArray()) {
// any array or object value is bigger than any potential key
value = ::highest;
} else if (valNode->isNullValue() || valNode->isBoolValue() || valNode->isIntValue()) {
// any null, bool or numeric value is lower than any potential key
// keep lower bound
} else {
THROW_ARANGO_EXCEPTION_MESSAGE(TRI_ERROR_INTERNAL, std::string("unhandled type for valNode: ") + valNode->getTypeString());
}
// strip collection name prefix from comparison value
int const cmpResult = removeCollectionFromString(isId, value);
if (type == aql::NODE_TYPE_OPERATOR_BINARY_EQ) {
if (cmpResult != 0) {
// doc._id == different collection
return new EmptyIndexIterator(&_collection, trx);
}
if (!upperFound || value < upper) {
upper = value;
upperFound = true;
}
if (!lowerFound || value < lower) {
lower = std::move(value);
lowerFound = true;
}
} else if (type == aql::NODE_TYPE_OPERATOR_BINARY_LE || type == aql::NODE_TYPE_OPERATOR_BINARY_LT) {
// a.b < value
if (cmpResult > 0) {
// doc._id < collection with "bigger" name
upper = ::highest;
} else if (cmpResult < 0) {
// doc._id < collection with "lower" name
return new EmptyIndexIterator(&_collection, trx);
} else {
if (type == aql::NODE_TYPE_OPERATOR_BINARY_LT && !value.empty()) {
value.back() -= 0x01U; // modify upper bound so that it is not included
}
if (!upperFound || value < upper) {
upper = std::move(value);
}
}
upperFound = true;
} else if (type == aql::NODE_TYPE_OPERATOR_BINARY_GE || type == aql::NODE_TYPE_OPERATOR_BINARY_GT) {
// a.b > value
if (cmpResult < 0) {
// doc._id > collection with "smaller" name
lower = ::lowest;
} else if (cmpResult > 0) {
// doc._id > collection with "bigger" name
return new EmptyIndexIterator(&_collection, trx);
} else {
if (type == aql::NODE_TYPE_OPERATOR_BINARY_GE && !value.empty()) {
value.back() -= 0x01U; // modify lower bound so it is included
}
if (!lowerFound || value > lower) {
lower = std::move(value);
}
}
lowerFound = true;
}
} // for nodes
// if only one bound is given select the other (lowest or highest) accordingly
if (upperFound && !lowerFound) {
lower = ::lowest;
lowerFound = true;
} else if (lowerFound && !upperFound) {
upper = ::highest;
upperFound = true;
}
if (lowerFound && upperFound) {
return new RocksDBPrimaryIndexRangeIterator(
&_collection /*logical collection*/, trx, this, !opts.ascending /*reverse*/,
RocksDBKeyBounds::PrimaryIndex(_objectId, lower, upper));
}
// operator type unsupported or IN used on non-array
return new EmptyIndexIterator(&_collection, trx);
}
};
/// @brief specializes the condition for use with the index
arangodb::aql::AstNode* RocksDBPrimaryIndex::specializeCondition(
arangodb::aql::AstNode* node, arangodb::aql::Variable const* reference) const {
SimpleAttributeEqualityMatcher matcher(IndexAttributes);
return matcher.specializeOne(this, node, reference);
return SkiplistIndexAttributeMatcher::specializeCondition(this, node, reference);
}
/// @brief create the iterator, for a single attribute, IN operator
IndexIterator* RocksDBPrimaryIndex::createInIterator(transaction::Methods* trx,
arangodb::aql::AstNode const* attrNode,
arangodb::aql::AstNode const* valNode) {
arangodb::aql::AstNode const* valNode,
bool ascending) {
// _key or _id?
bool const isId = (attrNode->stringEquals(StaticStrings::IdString));
@ -480,10 +774,21 @@ IndexIterator* RocksDBPrimaryIndex::createInIterator(transaction::Methods* trx,
size_t const n = valNode->numMembers();
// only leave the valid elements
for (size_t i = 0; i < n; ++i) {
handleValNode(trx, keys.get(), valNode->getMemberUnchecked(i), isId);
TRI_IF_FAILURE("PrimaryIndex::iteratorValNodes") {
THROW_ARANGO_EXCEPTION(TRI_ERROR_DEBUG);
if (ascending) {
for (size_t i = 0; i < n; ++i) {
handleValNode(trx, keys.get(), valNode->getMemberUnchecked(i), isId);
TRI_IF_FAILURE("PrimaryIndex::iteratorValNodes") {
THROW_ARANGO_EXCEPTION(TRI_ERROR_DEBUG);
}
}
} else {
size_t i = n;
while (i > 0) {
--i;
handleValNode(trx, keys.get(), valNode->getMemberUnchecked(i), isId);
TRI_IF_FAILURE("PrimaryIndex::iteratorValNodes") {
THROW_ARANGO_EXCEPTION(TRI_ERROR_DEBUG);
}
}
}
@ -492,7 +797,7 @@ IndexIterator* RocksDBPrimaryIndex::createInIterator(transaction::Methods* trx,
}
keys->close();
return new RocksDBPrimaryIndexInIterator(&_collection, trx, this, std::move(keys), !isId);
}

View File

@ -103,6 +103,45 @@ class RocksDBPrimaryIndexInIterator final : public IndexIterator {
bool const _allowCoveringIndexOptimization;
};
class RocksDBPrimaryIndexRangeIterator final : public IndexIterator {
private:
friend class RocksDBVPackIndex;
public:
RocksDBPrimaryIndexRangeIterator(LogicalCollection* collection, transaction::Methods* trx,
arangodb::RocksDBPrimaryIndex const* index,
bool reverse, RocksDBKeyBounds&& bounds);
~RocksDBPrimaryIndexRangeIterator() = default;
public:
char const* typeName() const override { return "rocksdb-range-index-iterator"; }
/// @brief Get the next limit many elements in the index
bool next(LocalDocumentIdCallback const& cb, size_t limit) override;
void skip(uint64_t count, uint64_t& skipped) override;
/// @brief Reset the cursor
void reset() override;
/// @brief we provide a method to provide the index attribute values
/// while scanning the index
bool hasCovering() const override { return false; }
// bool nextCovering(DocumentCallback const& cb, size_t limit) override;
private:
bool outOfRange() const;
arangodb::RocksDBPrimaryIndex const* _index;
rocksdb::Comparator const* _cmp;
std::unique_ptr<rocksdb::Iterator> _iterator;
bool const _reverse;
RocksDBKeyBounds _bounds;
// used for iterate_upper_bound iterate_lower_bound
rocksdb::Slice _rangeBound;
};
class RocksDBPrimaryIndex final : public RocksDBIndex {
friend class RocksDBPrimaryIndexEqIterator;
friend class RocksDBPrimaryIndexInIterator;
@ -125,7 +164,7 @@ class RocksDBPrimaryIndex final : public RocksDBIndex {
bool hasCoveringIterator() const override { return true; }
bool isSorted() const override { return false; }
bool isSorted() const override { return true; }
bool hasSelectivityEstimate() const override { return true; }
@ -154,6 +193,10 @@ class RocksDBPrimaryIndex final : public RocksDBIndex {
arangodb::aql::AstNode const*,
arangodb::aql::Variable const*, size_t, size_t&,
double&) const override;
bool supportsSortCondition(arangodb::aql::SortCondition const*,
arangodb::aql::Variable const*, size_t, double&,
size_t&) const override;
IndexIterator* iteratorForCondition(transaction::Methods*, ManagedDocumentResult*,
arangodb::aql::AstNode const*,
@ -184,7 +227,7 @@ class RocksDBPrimaryIndex final : public RocksDBIndex {
private:
/// @brief create the iterator, for a single attribute, IN operator
IndexIterator* createInIterator(transaction::Methods*, arangodb::aql::AstNode const*,
arangodb::aql::AstNode const*);
arangodb::aql::AstNode const*, bool ascending);
/// @brief create the iterator, for a single attribute, EQ operator
IndexIterator* createEqIterator(transaction::Methods*, arangodb::aql::AstNode const*,

View File

@ -170,6 +170,7 @@ void Task::shutdownTasks() {
}
// wait for the tasks to be cleaned up
int iterations = 0;
while (true) {
size_t size;
{
@ -177,12 +178,14 @@ void Task::shutdownTasks() {
size = _tasks.size();
}
if (size > 0) {
LOG_TOPIC(INFO, Logger::FIXME) << "Waiting for " << size << " Tasks to complete.";
std::this_thread::sleep_for(std::chrono::seconds(1));
} else {
if (size == 0) {
break;
}
if (++iterations % 10 == 0) {
LOG_TOPIC(INFO, Logger::FIXME) << "waiting for " << size << " task(s) to complete";
}
std::this_thread::sleep_for(std::chrono::milliseconds(200));
}
}

View File

@ -201,12 +201,7 @@
query += this.setFiltersForQuery(bindVars);
// Sort result, only useful for a small number of docs
if (this.getTotal() < this.MAX_SORT) {
if (this.getSort() === '_key') {
query += ' SORT TO_NUMBER(x.' + this.getSort() + ') == 0 ? x.' +
this.getSort() + ' : TO_NUMBER(x.' + this.getSort() + ')';
} else if (this.getSort() !== '') {
query += ' SORT x.' + this.getSort();
}
}
if (bindVars.count !== 'all') {

View File

@ -296,13 +296,13 @@ function ensureIndexSuite() {
assertTrue(idx1.unique);
assertTrue(idx1.sparse);
assertEqual([ "b", "c" ], idx1.fields);
var idx2 = collection.ensureIndex({ type: "hash", unique: true, fields: [ "b", "c" ], sparse: false });
assertEqual("hash", idx2.type);
assertTrue(idx2.unique);
assertFalse(idx2.sparse);
assertEqual([ "b", "c" ], idx2.fields);
var idx3 = collection.ensureIndex({ type: "hash", unique: false, fields: [ "b", "c" ], sparse: false });
assertEqual("hash", idx3.type);
assertFalse(idx3.unique);
@ -315,14 +315,14 @@ function ensureIndexSuite() {
assertTrue(idx1.sparse);
assertEqual([ "b", "c" ], res.fields);
assertEqual(idx1.id, res.id);
res = collection.getIndexes()[collection.getIndexes().length - 2];
assertEqual("hash", res.type);
assertTrue(res.unique);
assertFalse(idx2.sparse);
assertEqual([ "b", "c" ], res.fields);
assertEqual(idx2.id, res.id);
res = collection.getIndexes()[collection.getIndexes().length - 1];
assertEqual("hash", res.type);
assertFalse(res.unique);
@ -424,15 +424,15 @@ function ensureIndexSuite() {
assertEqual("test" + i, doc._key);
assertEqual(i, doc.value);
}
var query = "FOR doc IN " + collection.name() + " FILTER doc._key == 'test1' && doc.value == 1 RETURN doc";
var st = db._createStatement({ query: query });
var found = false;
st.explain().plan.nodes.forEach(function(node) {
var found = false;
st.explain().plan.nodes.forEach(function(node) {
if (node.type === "IndexNode") {
assertTrue(node.indexes[0].type === "primary" || node.indexes[0].type === "hash");
found = true;
found = true;
}
});
assertTrue(found);
@ -566,13 +566,13 @@ function ensureIndexSuite() {
assertTrue(idx1.unique);
assertTrue(idx1.sparse);
assertEqual([ "b", "c" ], idx1.fields);
var idx2 = collection.ensureIndex({ type: "skiplist", unique: true, fields: [ "b", "c" ], sparse: false });
assertEqual("skiplist", idx2.type);
assertTrue(idx2.unique);
assertFalse(idx2.sparse);
assertEqual([ "b", "c" ], idx2.fields);
var idx3 = collection.ensureIndex({ type: "skiplist", unique: false, fields: [ "b", "c" ], sparse: false });
assertEqual("skiplist", idx3.type);
assertFalse(idx3.unique);
@ -585,14 +585,14 @@ function ensureIndexSuite() {
assertTrue(idx1.sparse);
assertEqual([ "b", "c" ], res.fields);
assertEqual(idx1.id, res.id);
res = collection.getIndexes()[collection.getIndexes().length - 2];
assertEqual("skiplist", res.type);
assertTrue(res.unique);
assertFalse(idx2.sparse);
assertEqual([ "b", "c" ], res.fields);
assertEqual(idx2.id, res.id);
res = collection.getIndexes()[collection.getIndexes().length - 1];
assertEqual("skiplist", res.type);
assertFalse(res.unique);
@ -627,12 +627,12 @@ function ensureIndexSuite() {
var query = "FOR doc IN " + collection.name() + " FILTER doc._rev >= 0 SORT doc._rev RETURN doc";
var st = db._createStatement({ query: query });
var found = false;
st.explain().plan.nodes.forEach(function(node) {
var found = false;
st.explain().plan.nodes.forEach(function(node) {
if (node.type === "IndexNode") {
assertEqual("skiplist", node.indexes[0].type);
found = true;
found = true;
}
});
assertTrue(found);
@ -671,15 +671,15 @@ function ensureIndexSuite() {
assertEqual("test" + i, doc._key);
assertEqual(i, doc.value);
}
var query = "FOR doc IN " + collection.name() + " FILTER doc._key >= 0 SORT doc._key RETURN doc";
var st = db._createStatement({ query: query });
var found = false;
st.explain().plan.nodes.forEach(function(node) {
var found = false;
st.explain().plan.nodes.forEach(function(node) {
if (node.type === "IndexNode") {
assertEqual("skiplist", node.indexes[0].type);
found = true;
assertTrue([ "skiplist", "primary" ].includes(node.indexes[0].type), node.indexes[0].type + " is not in [ 'skiplist', 'primary' ]");
found = true;
}
});
assertTrue(found);
@ -721,12 +721,12 @@ function ensureIndexSuite() {
var query = "FOR doc IN " + collection.name() + " FILTER doc._key == 'test1' && doc.value == 1 RETURN doc";
var st = db._createStatement({ query: query });
var found = false;
st.explain().plan.nodes.forEach(function(node) {
var found = false;
st.explain().plan.nodes.forEach(function(node) {
if (node.type === "IndexNode") {
assertTrue(node.indexes[0].type === "primary" || node.indexes[0].type === "skiplist");
found = true;
found = true;
}
});
assertTrue(found);
@ -964,12 +964,12 @@ function ensureIndexEdgesSuite() {
var query = "FOR doc IN " + edge.name() + " FILTER doc._from >= 0 SORT doc._from RETURN doc";
var st = db._createStatement({ query: query });
var found = false;
st.explain().plan.nodes.forEach(function(node) {
var found = false;
st.explain().plan.nodes.forEach(function(node) {
if (node.type === "IndexNode") {
assertEqual("skiplist", node.indexes[0].type);
found = true;
found = true;
}
});
assertTrue(found);
@ -1001,12 +1001,12 @@ function ensureIndexEdgesSuite() {
var query = "FOR doc IN " + edge.name() + " FILTER doc._to >= 0 SORT doc._to RETURN doc";
var st = db._createStatement({ query: query });
var found = false;
st.explain().plan.nodes.forEach(function(node) {
var found = false;
st.explain().plan.nodes.forEach(function(node) {
if (node.type === "IndexNode") {
assertEqual("skiplist", node.indexes[0].type);
found = true;
found = true;
}
});
assertTrue(found);
@ -1036,16 +1036,16 @@ function ensureIndexEdgesSuite() {
assertEqual(idx.id, res.id);
var query = "FOR doc IN " + edge.name() +
var query = "FOR doc IN " + edge.name() +
" FILTER doc._from == 'UnitTestsCollectionIdxVertex/test1' && " +
"doc._to == 'UnitTestsCollectionIdxVertex' RETURN doc";
var st = db._createStatement({ query: query });
var found = false;
st.explain().plan.nodes.forEach(function(node) {
var found = false;
st.explain().plan.nodes.forEach(function(node) {
if (node.type === "IndexNode") {
assertTrue(node.indexes[0].type === "hash");
found = true;
found = true;
}
});
assertTrue(found);
@ -1075,16 +1075,16 @@ function ensureIndexEdgesSuite() {
assertEqual(idx.id, res.id);
var query = "FOR doc IN " + edge.name() +
" FILTER doc._from == 'UnitTestsCollectionIdxVertex/test1' && " +
var query = "FOR doc IN " + edge.name() +
" FILTER doc._from == 'UnitTestsCollectionIdxVertex/test1' && " +
"doc._to == 'UnitTestsCollectionIdxVertex' RETURN doc";
var st = db._createStatement({ query: query });
var found = false;
st.explain().plan.nodes.forEach(function(node) {
var found = false;
st.explain().plan.nodes.forEach(function(node) {
if (node.type === "IndexNode") {
assertTrue(node.indexes[0].type === "skiplist");
found = true;
found = true;
}
});
assertTrue(found);

View File

@ -415,7 +415,7 @@ function ahuacatlFailureSuite () {
testEnumerateCollectionBlock : function () {
internal.debugSetFailAt("EnumerateCollectionBlock::moreDocuments");
assertFailingQuery("FOR i IN " + c.name() + " FILTER i.value2 == 9 COLLECT key = i._key RETURN key");
assertFailingQuery("FOR i IN " + c.name() + " FILTER i.value2 == 9 COLLECT key = i.value2 RETURN key");
},
////////////////////////////////////////////////////////////////////////////////

View File

@ -46,7 +46,7 @@ function optimizerCollectMethodsTestSuite () {
c = db._create("UnitTestsCollection", { numberOfShards: 3 });
for (var i = 0; i < 1500; ++i) {
c.save({ group: "test" + (i % 10), value: i });
c.save({ group: "test" + (i % 10), value: i, haxe: "test" + i });
}
},
@ -96,7 +96,7 @@ function optimizerCollectMethodsTestSuite () {
testNumberOfPlansWithInto : function () {
var queries = [
"FOR j IN " + c.name() + " COLLECT value = j INTO g RETURN g",
"FOR j IN " + c.name() + " COLLECT value = j INTO g = j._key RETURN g",
"FOR j IN " + c.name() + " COLLECT value = j INTO g = j.haxe RETURN g",
"FOR j IN " + c.name() + " COLLECT value = j INTO g RETURN [ value, g ]",
"FOR j IN " + c.name() + " COLLECT value = j INTO g KEEP j RETURN g"
];
@ -115,7 +115,7 @@ function optimizerCollectMethodsTestSuite () {
testHashed : function () {
var queries = [
[ "FOR j IN " + c.name() + " COLLECT value = j RETURN value", 1500 ],
[ "FOR j IN " + c.name() + " COLLECT value = j._key RETURN value", 1500 ],
[ "FOR j IN " + c.name() + " COLLECT value = j.haxe RETURN value", 1500 ],
[ "FOR j IN " + c.name() + " COLLECT value = j.group RETURN value", 10 ],
[ "FOR j IN " + c.name() + " COLLECT value1 = j.group, value2 = j.value RETURN [ value1, value2 ]", 1500 ],
[ "FOR j IN " + c.name() + " COLLECT value = j.group WITH COUNT INTO l RETURN [ value, l ]", 10 ],
@ -159,7 +159,7 @@ function optimizerCollectMethodsTestSuite () {
var queries = [
[ "FOR j IN " + c.name() + " COLLECT value = j RETURN value", 1500 ],
[ "FOR j IN " + c.name() + " COLLECT value = j._key RETURN value", 1500 ],
[ "FOR j IN " + c.name() + " COLLECT value = j.haxe RETURN value", 1500 ],
[ "FOR j IN " + c.name() + " COLLECT value = j.group RETURN value", 10 ],
[ "FOR j IN " + c.name() + " COLLECT value1 = j.group, value2 = j.value RETURN [ value1, value2 ]", 1500 ],
[ "FOR j IN " + c.name() + " COLLECT value = j.group WITH COUNT INTO l RETURN [ value, l ]", 10 ],
@ -204,7 +204,7 @@ function optimizerCollectMethodsTestSuite () {
var queries = [
[ "FOR j IN " + c.name() + " COLLECT value = j RETURN value", 1500, false],
[ "FOR j IN " + c.name() + " COLLECT value = j._key RETURN value", 1500, false],
[ "FOR j IN " + c.name() + " COLLECT value = j.haxe RETURN value", 1500, false],
[ "FOR j IN " + c.name() + " COLLECT value = j.group RETURN value", 10, true],
[ "FOR j IN " + c.name() + " COLLECT value1 = j.group, value2 = j.value RETURN [ value1, value2 ]", 1500, true ],
[ "FOR j IN " + c.name() + " COLLECT value = j.group WITH COUNT INTO l RETURN [ value, l ]", 10, true ],
@ -312,7 +312,7 @@ function optimizerCollectMethodsTestSuite () {
testSortRemoval : function () {
var queries = [
[ "FOR j IN " + c.name() + " COLLECT value = j SORT null RETURN value", 1500 ],
[ "FOR j IN " + c.name() + " COLLECT value = j._key SORT null RETURN value", 1500 ],
[ "FOR j IN " + c.name() + " COLLECT value = j.haxe SORT null RETURN value", 1500 ],
[ "FOR j IN " + c.name() + " COLLECT value = j.group SORT null RETURN value", 10 ],
[ "FOR j IN " + c.name() + " COLLECT value1 = j.group, value2 = j.value SORT null RETURN [ value1, value2 ]", 1500 ],
[ "FOR j IN " + c.name() + " COLLECT value = j.group WITH COUNT INTO l SORT null RETURN [ value, l ]", 10 ],

View File

@ -0,0 +1,512 @@
/*jshint globalstrict:false, strict:false, maxlen: 500 */
/*global assertTrue, assertEqual, assertNotEqual, AQL_EXPLAIN, AQL_EXECUTE */
////////////////////////////////////////////////////////////////////////////////
/// @brief tests for index usage
///
/// @file
///
/// DISCLAIMER
///
/// Copyright 2010-2012 triagens GmbH, Cologne, Germany
///
/// Licensed under the Apache License, Version 2.0 (the "License");
/// you may not use this file except in compliance with the License.
/// You may obtain a copy of the License at
///
/// http://www.apache.org/licenses/LICENSE-2.0
///
/// Unless required by applicable law or agreed to in writing, software
/// distributed under the License is distributed on an "AS IS" BASIS,
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
/// See the License for the specific language governing permissions and
/// limitations under the License.
///
/// Copyright holder is triAGENS GmbH, Cologne, Germany
///
/// @author Jan Christoph Uhde
/// @author Copyright 2018, triAGENS GmbH, Cologne, Germany
////////////////////////////////////////////////////////////////////////////////
var jsunity = require("jsunity");
var db = require("@arangodb").db;
////////////////////////////////////////////////////////////////////////////////
/// @brief test suite
////////////////////////////////////////////////////////////////////////////////
function optimizerIndexesRangesTestSuite () {
let c;
return {
setUpAll : function () {
db._drop("UnitTestsCollection");
c = db._create("UnitTestsCollection");
for (let i = 0; i < 2000; ++i) {
c.save({ _key: "test" + String(i).padStart(4, '0') });
}
},
tearDownAll : function () {
db._drop("UnitTestsCollection");
},
////////////////////////////////////////////////////////////////////////////////
/// @brief test primary index usage
////////////////////////////////////////////////////////////////////////////////
testPrimaryIndexRanges : function () {
let queries = [ // queries[0] - query -- queries[1] - expected result
// _key and _id mixed
[
"FOR i IN " + c.name() + " FILTER (i._key >= 'test1990' && i._id <= '" + c.name() + "/test2') RETURN i._key",
[ "test1990", "test1991", "test1992", "test1993", "test1994", "test1995", "test1996", "test1997", "test1998", "test1999" ]
],[
"FOR i IN " + c.name() + " FILTER (i._key > 'test1990' && i._id < '" + c.name() + "/test1999') RETURN i._key",
[ "test1991", "test1992", "test1993", "test1994", "test1995", "test1996", "test1997", "test1998" ]
],[
"FOR i IN " + c.name() + " FILTER (i._key > 'test1990' && i._id < '" + c.name() + "/test1999' && i._key < 'test1996') RETURN i._key",
[ "test1991", "test1992", "test1993", "test1994", "test1995" ]
],
// more than one condition
[
"FOR i IN " + c.name() + " FILTER (i._key >= 'test1990' && i._key <= 'test2') RETURN i._key",
[ "test1990", "test1991", "test1992", "test1993", "test1994", "test1995", "test1996", "test1997", "test1998", "test1999" ]
],[
"for i in " + c.name() + " filter (i._key > 'test1990' && i._key < 'test1999') return i._key",
[ "test1991", "test1992", "test1993", "test1994", "test1995", "test1996", "test1997", "test1998" ]
],[
"for i in " + c.name() + " filter (i._key > 'test1990' && i._key < 'test1999' && i._key < 'test1996') return i._key",
[ "test1991", "test1992", "test1993", "test1994", "test1995" ]
],
// tests with out-of-bounds compare keys
[ "FOR i IN " + c.name() + " FILTER (i._key == null) RETURN i._key", [] ],
[ "FOR i IN " + c.name() + " FILTER (i._key == false) RETURN i._key", [] ],
[ "FOR i IN " + c.name() + " FILTER (i._key == true) RETURN i._key", [] ],
[ "FOR i IN " + c.name() + " FILTER (i._key == 99999) RETURN i._key", [] ],
[ "FOR i IN " + c.name() + " FILTER (i._key == '') RETURN i._key", [] ],
[ "FOR i IN " + c.name() + " FILTER (i._key == ' ') RETURN i._key", [] ],
[ "FOR i IN " + c.name() + " FILTER (i._key == []) RETURN i._key", [] ],
[ "FOR i IN " + c.name() + " FILTER (i._key == {}) RETURN i._key", [] ],
[ "FOR i IN " + c.name() + " FILTER (i._key IN [null]) RETURN i._key", [] ],
[ "FOR i IN " + c.name() + " FILTER (i._key IN [false]) RETURN i._key", [] ],
[ "FOR i IN " + c.name() + " FILTER (i._key IN [true]) RETURN i._key", [] ],
[ "FOR i IN " + c.name() + " FILTER (i._key IN [99999]) RETURN i._key", [] ],
[ "FOR i IN " + c.name() + " FILTER (i._key IN ['']) RETURN i._key", [] ],
[ "FOR i IN " + c.name() + " FILTER (i._key IN [' ']) RETURN i._key", [] ],
[ "FOR i IN " + c.name() + " FILTER (i._key IN [[]]) RETURN i._key", [] ],
[ "FOR i IN " + c.name() + " FILTER (i._key IN [{}]) RETURN i._key", [] ],
[ "FOR i IN " + c.name() + " FILTER (i._key < null) RETURN i._key", [] ],
[ "FOR i IN " + c.name() + " FILTER (i._key < false) RETURN i._key", [] ],
[ "FOR i IN " + c.name() + " FILTER (i._key < true) RETURN i._key", [] ],
[ "FOR i IN " + c.name() + " FILTER (i._key < 99999) RETURN i._key", [] ],
[ "FOR i IN " + c.name() + " FILTER (i._key < '') RETURN i._key", [] ],
[ "FOR i IN " + c.name() + " FILTER (i._key < ' ') RETURN i._key", [] ],
[ "FOR i IN " + c.name() + " FILTER (i._key <= null) RETURN i._key", [] ],
[ "FOR i IN " + c.name() + " FILTER (i._key <= false) RETURN i._key", [] ],
[ "FOR i IN " + c.name() + " FILTER (i._key <= true) RETURN i._key", [] ],
[ "FOR i IN " + c.name() + " FILTER (i._key <= 99999) RETURN i._key", [] ],
[ "FOR i IN " + c.name() + " FILTER (i._key <= '') RETURN i._key", [] ],
[ "FOR i IN " + c.name() + " FILTER (i._key <= ' ') RETURN i._key", [] ],
[ "FOR i IN " + c.name() + " FILTER (i._key > 'zzz') RETURN i._key", [] ],
[ "FOR i IN " + c.name() + " FILTER (i._key > []) RETURN i._key", [] ],
[ "FOR i IN " + c.name() + " FILTER (i._key > {}) RETURN i._key", [] ],
[ "FOR i IN " + c.name() + " FILTER (i._key >= 'zzz') RETURN i._key", [] ],
[ "FOR i IN " + c.name() + " FILTER (i._key >= []) RETURN i._key", [] ],
[ "FOR i IN " + c.name() + " FILTER (i._key >= {}) RETURN i._key", [] ],
// tests for _key
[
"FOR i IN " + c.name() + " FILTER (i._key < 'test0002') RETURN i._key",
[ "test0000", "test0001" ]
],[
"FOR i IN " + c.name() + " FILTER ('test0002' > i._key) RETURN i._key",
[ "test0000", "test0001" ]
],[
"FOR i IN " + c.name() + " FILTER (i._key > 'test1997') RETURN i._key",
[ "test1998", "test1999" ]
],[
"FOR i IN " + c.name() + " FILTER ('test1997' < i._key) RETURN i._key",
[ "test1998", "test1999" ]
],[
"FOR i IN " + c.name() + " FILTER (i._key <= 'test0002') RETURN i._key",
[ "test0000", "test0001", "test0002" ]
],[
"FOR i IN " + c.name() + " FILTER ('test0002' >= i._key) RETURN i._key",
[ "test0000", "test0001", "test0002" ]
],[
"FOR i IN " + c.name() + " FILTER (i._key >= 'test1997') RETURN i._key",
[ "test1997", "test1998", "test1999" ]
],[
"FOR i IN " + c.name() + " FILTER ('test1997' <= i._key) RETURN i._key",
[ "test1997", "test1998", "test1999" ]
],[
"FOR i IN " + c.name() + " FILTER (i._key >= 9 && i._key < 'test0003') RETURN i._key",
[ "test0000", "test0001", "test0002" ]
],[
"FOR i IN " + c.name() + " FILTER (i._key >= null && i._key < 'test0003') RETURN i._key",
[ "test0000", "test0001", "test0002" ]
],[
"FOR i IN " + c.name() + " FILTER (i._key > 9 && i._key < 'test0003') RETURN i._key",
[ "test0000", "test0001", "test0002" ]
],[
"FOR i IN " + c.name() + " FILTER (i._key > null && i._key <= 'test0003') RETURN i._key",
[ "test0000", "test0001", "test0002", "test0003" ]
],[
"FOR i IN " + c.name() + " FILTER (i._key >= 9 && i._key <= 'test0003') RETURN i._key",
[ "test0000", "test0001", "test0002", "test0003" ]
],[
"FOR i IN " + c.name() + " FILTER (i._key >= null && i._key <= 'test0003') RETURN i._key",
[ "test0000", "test0001", "test0002", "test0003" ]
],[
"FOR i IN " + c.name() + " FILTER (i._key > 9 && i._key <= 'test0003') RETURN i._key",
[ "test0000", "test0001", "test0002", "test0003" ]
],[
"FOR i IN " + c.name() + " FILTER (i._key > null && i._key <= 'test0003') RETURN i._key",
[ "test0000", "test0001", "test0002", "test0003" ]
],[
"FOR i IN " + c.name() + " FILTER (i._key > 'test1997' && i._key < []) RETURN i._key",
[ "test1998", "test1999" ]
],[
"FOR i IN " + c.name() + " FILTER (i._key > 'test1997' && i._key < {}) RETURN i._key",
[ "test1998", "test1999" ]
],[
"FOR i IN " + c.name() + " FILTER (i._key > 'test1997' && i._key <= []) RETURN i._key",
[ "test1998", "test1999" ]
],[
"FOR i IN " + c.name() + " FILTER (i._key > 'test1997' && i._key <= {}) RETURN i._key",
[ "test1998", "test1999" ]
],[
"FOR i IN " + c.name() + " FILTER (i._key > 'test1997' && i._key < []) RETURN i._key",
[ "test1998", "test1999" ]
],[
"FOR i IN " + c.name() + " FILTER (i._key > 'test1997' && i._key < {}) RETURN i._key",
[ "test1998", "test1999" ]
],[
"FOR i IN " + c.name() + " FILTER (i._key > 'test1997' && i._key <= []) RETURN i._key",
[ "test1998", "test1999" ]
],[
"FOR i IN " + c.name() + " FILTER (i._key > 'test1997' && i._key <= {}) RETURN i._key",
[ "test1998", "test1999" ]
],[
"FOR i IN " + c.name() + " FILTER i._key IN ['test1999', 'test1998', 'test1997'] RETURN i._key",
[ "test1997", "test1998", "test1999" ]
],[
"FOR i IN " + c.name() + " FILTER i._key IN ['test1999', 'test1998', 'test1997'] SORT i._key RETURN i._key",
[ "test1997", "test1998", "test1999" ]
],[
"FOR i IN " + c.name() + " FILTER i._key IN ['test1997', 'test1998', 'test1999'] RETURN i._key",
[ "test1997", "test1998", "test1999" ]
],[
"FOR i IN " + c.name() + " FILTER i._key IN ['test1997', 'test1998', 'test1999'] SORT i._key RETURN i._key",
[ "test1997", "test1998", "test1999" ]
],[
"FOR i IN " + c.name() + " FILTER (i._key < 'test0002') SORT i._key DESC RETURN i._key",
[ "test0001", "test0000" ]
],[
"FOR i IN " + c.name() + " FILTER ('test0002' > i._key) SORT i._key DESC RETURN i._key",
[ "test0001", "test0000" ]
],[
"FOR i IN " + c.name() + " FILTER (i._key > 'test1997') SORT i._key DESC RETURN i._key",
[ "test1999", "test1998" ]
],[
"FOR i IN " + c.name() + " FILTER ('test1997' < i._key) SORT i._key DESC RETURN i._key",
[ "test1999", "test1998" ]
],[
"FOR i IN " + c.name() + " FILTER (i._key <= 'test0002') SORT i._key DESC RETURN i._key",
[ "test0002", "test0001", "test0000" ]
],[
"FOR i IN " + c.name() + " FILTER ('test0002' >= i._key) SORT i._key DESC RETURN i._key",
[ "test0002", "test0001", "test0000" ]
],[
"FOR i IN " + c.name() + " FILTER (i._key >= 'test1997') SORT i._key DESC RETURN i._key",
[ "test1999", "test1998", "test1997" ]
],[
"FOR i IN " + c.name() + " FILTER ('test1997' <= i._key) SORT i._key DESC RETURN i._key",
[ "test1999", "test1998", "test1997" ]
],[
"FOR i IN " + c.name() + " FILTER (i._key >= 9 && i._key < 'test0003') SORT i._key DESC RETURN i._key",
[ "test0002", "test0001", "test0000" ]
],[
"FOR i IN " + c.name() + " FILTER (i._key >= null && i._key < 'test0003') SORT i._key DESC RETURN i._key",
[ "test0002", "test0001", "test0000" ]
],[
"FOR i IN " + c.name() + " FILTER (i._key > 9 && i._key < 'test0003') SORT i._key DESC RETURN i._key",
[ "test0002", "test0001", "test0000" ]
],[
"FOR i IN " + c.name() + " FILTER (i._key > null && i._key <= 'test0003') SORT i._key DESC RETURN i._key",
[ "test0003", "test0002", "test0001", "test0000" ]
],[
"FOR i IN " + c.name() + " FILTER (i._key >= 9 && i._key <= 'test0003') SORT i._key DESC RETURN i._key",
[ "test0003", "test0002", "test0001", "test0000" ]
],[
"FOR i IN " + c.name() + " FILTER (i._key >= null && i._key <= 'test0003') SORT i._key DESC RETURN i._key",
[ "test0003", "test0002", "test0001", "test0000" ]
],[
"FOR i IN " + c.name() + " FILTER (i._key > 9 && i._key <= 'test0003') SORT i._key DESC RETURN i._key",
[ "test0003", "test0002", "test0001", "test0000" ]
],[
"FOR i IN " + c.name() + " FILTER (i._key > null && i._key <= 'test0003') SORT i._key DESC RETURN i._key",
[ "test0003", "test0002", "test0001", "test0000" ]
],[
"FOR i IN " + c.name() + " FILTER (i._key > 'test1997' && i._key < []) SORT i._key DESC RETURN i._key",
[ "test1999", "test1998" ]
],[
"FOR i IN " + c.name() + " FILTER (i._key > 'test1997' && i._key < {}) SORT i._key DESC RETURN i._key",
[ "test1999", "test1998" ]
],[
"FOR i IN " + c.name() + " FILTER (i._key > 'test1997' && i._key <= []) SORT i._key DESC RETURN i._key",
[ "test1999", "test1998" ]
],[
"FOR i IN " + c.name() + " FILTER (i._key > 'test1997' && i._key <= {}) SORT i._key DESC RETURN i._key",
[ "test1999", "test1998" ]
],[
"FOR i IN " + c.name() + " FILTER (i._key > 'test1997' && i._key < []) SORT i._key DESC RETURN i._key",
[ "test1999", "test1998" ]
],[
"FOR i IN " + c.name() + " FILTER (i._key > 'test1997' && i._key < {}) SORT i._key DESC RETURN i._key",
[ "test1999", "test1998" ]
],[
"FOR i IN " + c.name() + " FILTER (i._key > 'test1997' && i._key <= []) SORT i._key DESC RETURN i._key",
[ "test1999", "test1998" ]
],[
"FOR i IN " + c.name() + " FILTER (i._key > 'test1997' && i._key <= {}) SORT i._key DESC RETURN i._key",
[ "test1999", "test1998" ]
],[
"FOR i IN " + c.name() + " FILTER i._key IN ['test1999', 'test1998', 'test1997'] SORT i._key DESC RETURN i._key",
[ "test1999", "test1998", "test1997" ]
],[
"FOR i IN " + c.name() + " FILTER i._key IN ['test1997', 'test1998', 'test1999'] SORT i._key DESC RETURN i._key",
[ "test1999", "test1998", "test1997" ]
],
// tests for _id
[
"FOR i IN " + c.name() + " FILTER (i._id < '" + c.name() + "/test0002') RETURN i._key",
[ "test0000", "test0001" ]
],[
"FOR i IN " + c.name() + " FILTER ('" + c.name() + "/test0002' > i._id) RETURN i._key",
[ "test0000", "test0001" ]
],[
"FOR i IN " + c.name() + " FILTER (i._id > '" + c.name() + "/test1997') RETURN i._key",
[ "test1998", "test1999" ]
],[
"FOR i IN " + c.name() + " FILTER ('" + c.name() + "/test1997' < i._id) RETURN i._key",
[ "test1998", "test1999" ]
],[
"FOR i IN " + c.name() + " FILTER (i._id <= '" + c.name() + "/test0002') RETURN i._key",
[ "test0000", "test0001", "test0002" ]
],[
"FOR i IN " + c.name() + " FILTER ('" + c.name() + "/test0002' >= i._id) RETURN i._key",
[ "test0000", "test0001", "test0002" ]
],[
"FOR i IN " + c.name() + " FILTER (i._id >= '" + c.name() + "/test1997') RETURN i._key",
[ "test1997", "test1998", "test1999" ]
],[
"FOR i IN " + c.name() + " FILTER ('" + c.name() + "/test1997' <= i._id) RETURN i._key",
[ "test1997", "test1998", "test1999" ]
],
// test with sort
[
"FOR i IN " + c.name() + " FILTER (i._key > 'test0002' && i._key <= 'test0005') SORT i._key RETURN i._key",
[ "test0003", "test0004", "test0005" ]
],[
"FOR i IN " + c.name() + " FILTER (i._key >= 'test0002' && i._key <= 'test0005') SORT i._key RETURN i._key",
[ "test0002", "test0003", "test0004", "test0005" ]
],[
"FOR i IN " + c.name() + " FILTER (i._key > 'test0002' && i._key < 'test0005') SORT i._key RETURN i._key",
[ "test0003", "test0004" ]
],[
"FOR i IN " + c.name() + " FILTER (i._key >= 'test0002' && i._key < 'test0005') SORT i._key RETURN i._key",
[ "test0002", "test0003", "test0004" ]
],
// test with sort DESC
[
"FOR i IN " + c.name() + " FILTER (i._key > 'test0002' && i._key <= 'test0005') SORT i._key DESC RETURN i._key",
[ "test0005", "test0004", "test0003" ]
],[
"FOR i IN " + c.name() + " FILTER (i._key >= 'test0002' && i._key <= 'test0005') SORT i._key DESC RETURN i._key",
[ "test0005", "test0004", "test0003", "test0002" ]
],[
"FOR i IN " + c.name() + " FILTER (i._key > 'test0002' && i._key < 'test0005') SORT i._key DESC RETURN i._key",
[ "test0004", "test0003" ]
],[
"FOR i IN " + c.name() + " FILTER (i._key >= 'test0002' && i._key < 'test0005') SORT i._key DESC RETURN i._key",
[ "test0004", "test0003", "test0002" ]
],
//// edge cases
[
// one element in range
"FOR i IN " + c.name() + " FILTER ('test1997' <= i._key && 'test1997' >= i._key) RETURN i._key",
[ "test1997" ]
]
]; //end of array
queries.forEach(function(query) {
let plan = AQL_EXPLAIN(query[0]).plan;
let nodeTypes = plan.nodes.map(function(node) {
return node.type;
});
// ensure an index is used
assertTrue(nodeTypes.indexOf("IndexNode") !== -1 ||
nodeTypes.indexOf("SingleRemoteOperationNode") !== -1, query);
// must never have a SortNode, as we use the index for sorting
assertEqual(-1, nodeTypes.indexOf("SortNode"), query);
let results = AQL_EXECUTE(query[0]);
assertEqual(query[1].length , results.json.length, query);
assertEqual(query[1], results.json, query);
assertEqual(0, results.stats.scannedFull);
});
},
testPrimaryIndexRangesEdgeCases : function () {
let queries = [ // queries[0] - query -- queries[1] - expected result
[
// upper is greater than lower
"FOR i IN " + c.name() + " FILTER (i._key <= 'test1997' && i._key >= 'test1998') RETURN i._key",
[ ]
],[
"FOR i IN " + c.name() + " FILTER (i._key == 'test1997' && i._key >= 'test1997') RETURN i._key",
[ 'test1997' ]
],[
"FOR i IN " + c.name() + " FILTER (i._key == 'test1997' && i._key == 'test1998') RETURN i._key",
[ ]
],[
"FOR i IN " + c.name() + " FILTER (i._key < true) RETURN i._key",
[ ]
]
]; //end of array
queries.forEach(function(query) {
let plan = AQL_EXPLAIN(query[0]).plan;
let nodeTypes = plan.nodes.map(function(node) {
return node.type;
});
let results = AQL_EXECUTE(query[0]);
assertEqual(query[1].length , results.json.length, query);
results.json.forEach(function(value) {
assertNotEqual(-1, query[1].indexOf(value));
});
assertEqual(0, results.stats.scannedFull);
});
},
};
}
function optimizerIndexesRangesCollectionsTestSuite () {
return {
setUp : function () {
db._drop("UnitTestsCollection1");
db._drop("UnitTestsCollection2");
db._drop("UnitTestsCollection3");
for (let i = 1; i <= 3; ++i) {
let c = db._create("UnitTestsCollection" + i);
for (let j = 0; j < 2000; ++j) {
c.save({ _key: "test" + String(j).padStart(4, '0') });
}
}
},
tearDown : function () {
db._drop("UnitTestsCollection1");
db._drop("UnitTestsCollection2");
db._drop("UnitTestsCollection3");
},
testPrimaryIndexRangesIdWithDifferentCollections : function () {
let all = [];
for (let i = 0; i < 2000; ++i) {
all.push("test" + String(i).padStart(4, '0'));
}
let queries = [
[
"FOR i IN UnitTestsCollection1 FILTER i._id == 'UnitTestsCollection1/test1996' RETURN i._key",
[ "test1996" ]
],[
"FOR i IN UnitTestsCollection1 FILTER i._id >= 'UnitTestsCollection1/test1996' RETURN i._key",
[ "test1996", "test1997", "test1998", "test1999" ]
],[
"FOR i IN UnitTestsCollection1 FILTER i._id > 'UnitTestsCollection1/test1996' RETURN i._key",
[ "test1997", "test1998", "test1999" ]
],[
"FOR i IN UnitTestsCollection1 FILTER i._id <= 'UnitTestsCollection1/test0003' RETURN i._key",
[ "test0000", "test0001", "test0002", "test0003" ]
],[
"FOR i IN UnitTestsCollection1 FILTER i._id < 'UnitTestsCollection1/test0003' RETURN i._key",
[ "test0000", "test0001", "test0002" ]
],[
"FOR i IN UnitTestsCollection1 FILTER i._id == 'UnitTestsCollection3/test0003' RETURN i._key",
[ ]
],[
"FOR i IN UnitTestsCollection3 FILTER i._id < 'UnitTestsCollection1/test0003' RETURN i._key",
[ ]
],[
"FOR i IN UnitTestsCollection3 FILTER i._id <= 'UnitTestsCollection1/test0003' RETURN i._key",
[ ]
],[
"FOR i IN UnitTestsCollection3 FILTER i._id > 'UnitTestsCollection1/test0003' RETURN i._key",
all
],[
"FOR i IN UnitTestsCollection3 FILTER i._id >= 'UnitTestsCollection1/test0003' RETURN i._key",
all
],[
"FOR i IN UnitTestsCollection1 FILTER i._id < 'UnitTestsCollection3/test0003' RETURN i._key",
all
],[
"FOR i IN UnitTestsCollection1 FILTER i._id <= 'UnitTestsCollection3/test0003' RETURN i._key",
all
],[
"FOR i IN UnitTestsCollection1 FILTER i._id > 'UnitTestsCollection3/test0003' RETURN i._key",
[ ]
],[
"FOR i IN UnitTestsCollection1 FILTER i._id >= 'UnitTestsCollection3/test0003' RETURN i._key",
[ ]
]
];
queries.forEach(function(query) {
let plan = AQL_EXPLAIN(query[0]).plan;
let nodeTypes = plan.nodes.map(function(node) {
return node.type;
});
// ensure an index is used
assertTrue(nodeTypes.indexOf("IndexNode") !== -1 ||
nodeTypes.indexOf("SingleRemoteOperationNode") !== -1, query);
// must never have a SortNode, as we use the index for sorting
assertEqual(-1, nodeTypes.indexOf("SortNode"), query);
let results = AQL_EXECUTE(query[0]);
assertEqual(query[1].length , results.json.length, query);
assertEqual(query[1], results.json, query);
assertEqual(0, results.stats.scannedFull);
});
},
};
}
jsunity.run(optimizerIndexesRangesTestSuite);
jsunity.run(optimizerIndexesRangesCollectionsTestSuite);
return jsunity.done();

View File

@ -840,7 +840,7 @@ function arrayIndexNonArraySuite () {
});
assertNotEqual(-1, nodeTypes.indexOf("IndexNode"));
const query2 = `FOR x IN ${cName} FILTER null IN x.a[*] SORT x._key RETURN x._key`;
const query2 = `FOR x IN ${cName} FILTER null IN x.a[*] SORT x._rev RETURN x._key`;
plan = AQL_EXPLAIN(query2).plan;
nodeTypes = plan.nodes.map(function(node) {
return node.type;
@ -1157,7 +1157,7 @@ function arrayIndexNonArraySuite () {
});
assertNotEqual(-1, nodeTypes.indexOf("IndexNode"));
const query2 = `FOR x IN ${cName} FILTER null IN x.a[*] SORT x._key RETURN x._key`;
const query2 = `FOR x IN ${cName} FILTER null IN x.a[*] SORT x._rev RETURN x._key`;
plan = AQL_EXPLAIN(query2).plan;
nodeTypes = plan.nodes.map(function(node) {
return node.type;

View File

@ -41,16 +41,16 @@ var db = internal.db;
function ahuacatlQueryOptimizerInTestSuite () {
var c = null;
var cn = "UnitTestsAhuacatlOptimizerIn";
var explain = function (query, params) {
return helper.getCompactPlan(AQL_EXPLAIN(query, params, { optimizer: { rules: [ "-all", "+use-indexes" ] } })).map(function(node) { return node.type; });
};
var ruleIsUsed = function (query) {
var result = AQL_EXPLAIN(query, {}, { optimizer: { rules: [ "-all", "+use-indexes" ] } });
assertTrue(result.plan.rules.indexOf("use-indexes") !== -1, query);
};
var ruleIsNotUsed = function (query) {
var result = AQL_EXPLAIN(query, {}, { optimizer: { rules: [ "-all", "+use-indexes" ] } });
assertTrue(result.plan.rules.indexOf("use-indexes") === -1, query);
@ -95,7 +95,7 @@ function ahuacatlQueryOptimizerInTestSuite () {
var actual = getQueryResults(query);
assertEqual(16, actual.length);
},
testSortedArrayInStatic : function () {
var values = [
"WJItoWBuRBMBMajh", "WJIuWmBuRBMBMbdR",
@ -171,12 +171,34 @@ function ahuacatlQueryOptimizerInTestSuite () {
c.save({ _key: "test" + i, parent: "test" + (i - 1), parents: [ "test" + (i - 1) ] });
}
var expected = [ 'test5', 'test7' ];
var query = "LET parents = (FOR c IN " + cn + " FILTER c._key IN [ 'test5', 'test7' ] RETURN c._key) FOR c IN " + cn + " FILTER c._key IN parents SORT c._key RETURN c._key";
// ascending order - input ascending
var expected = [ 'test4', 'test6' ];
var query = "LET parents = (FOR c IN " + cn + " FILTER c._key IN [ 'test4', 'test6' ] RETURN c._key) FOR c IN " + cn + " FILTER c._key IN parents SORT c._key RETURN c._key";
var actual = getQueryResults(query);
assertEqual(expected, actual);
assertEqual([ "SingletonNode", "SubqueryNode", "IndexNode", "CalculationNode", "FilterNode", "CalculationNode", "SortNode", "CalculationNode", "ReturnNode" ], explain(query));
// ascending order - input descending
expected = [ 'test5', 'test7' ];
query = "LET parents = (FOR c IN " + cn + " FILTER c._key IN [ 'test7', 'test5' ] RETURN c._key) FOR c IN " + cn + " FILTER c._key IN parents SORT c._key RETURN c._key";
actual = getQueryResults(query);
assertEqual(expected, actual);
assertEqual([ "SingletonNode", "SubqueryNode", "IndexNode", "CalculationNode", "FilterNode", "CalculationNode", "SortNode", "CalculationNode", "ReturnNode" ], explain(query));
// descending order - input ascending
expected = [ 'test8', 'test6' ];
query = "LET parents = (FOR c IN " + cn + " FILTER c._key IN [ 'test6', 'test8' ] RETURN c._key) FOR c IN " + cn + " FILTER c._key IN parents SORT c._key DESC RETURN c._key";
actual = getQueryResults(query);
assertEqual(expected, actual);
assertEqual([ "SingletonNode", "SubqueryNode", "IndexNode", "CalculationNode", "FilterNode", "CalculationNode", "SortNode", "CalculationNode", "ReturnNode" ], explain(query));
// descending order - input descending
expected = [ 'test9', 'test7' ];
query = "LET parents = (FOR c IN " + cn + " FILTER c._key IN [ 'test9', 'test7' ] RETURN c._key) FOR c IN " + cn + " FILTER c._key IN parents SORT c._key DESC RETURN c._key";
actual = getQueryResults(query);
assertEqual(expected, actual);
assertEqual([ "SingletonNode", "SubqueryNode", "IndexNode", "CalculationNode", "FilterNode", "CalculationNode", "SortNode", "CalculationNode", "ReturnNode" ], explain(query));
},
////////////////////////////////////////////////////////////////////////////////
@ -224,7 +246,7 @@ function ahuacatlQueryOptimizerInTestSuite () {
var query = "LET parents = [ 'test5', 'test7' ] FOR c IN " + cn + " FILTER c.code IN parents SORT c.code RETURN c.code";
var actual = getQueryResults(query);
assertEqual(expected, actual);
assertEqual([ "SingletonNode", "CalculationNode", "IndexNode", "CalculationNode", "FilterNode", "CalculationNode", "SortNode", "CalculationNode", "ReturnNode" ], explain(query));
},
@ -243,7 +265,7 @@ function ahuacatlQueryOptimizerInTestSuite () {
var query = "LET parents = (FOR c IN " + cn + " FILTER c.code IN [ 'test5', 'test7' ] RETURN c.code) FOR c IN " + cn + " FILTER c.code IN parents SORT c.code RETURN c.code";
var actual = getQueryResults(query);
assertEqual(expected, actual);
assertEqual([ "SingletonNode", "SubqueryNode", "IndexNode", "CalculationNode", "FilterNode", "CalculationNode", "SortNode", "CalculationNode", "ReturnNode" ], explain(query));
},
@ -328,7 +350,7 @@ function ahuacatlQueryOptimizerInTestSuite () {
var query = "LET parents = [ 'test5', 'test7' ] FOR c IN " + cn + " FILTER c.code IN parents SORT c.code RETURN c.code";
var actual = getQueryResults(query);
assertEqual(expected, actual);
assertEqual([ "SingletonNode", "CalculationNode", "IndexNode", "CalculationNode", "FilterNode", "CalculationNode", "SortNode", "CalculationNode", "ReturnNode" ], explain(query));
},
@ -347,7 +369,7 @@ function ahuacatlQueryOptimizerInTestSuite () {
var query = "LET parents = (FOR c IN " + cn + " FILTER c.code IN [ 'test5', 'test7' ] RETURN c.code) FOR c IN " + cn + " FILTER c.code IN parents SORT c.code RETURN c.code";
var actual = getQueryResults(query);
assertEqual(expected, actual);
assertEqual([ "SingletonNode", "SubqueryNode", "IndexNode", "CalculationNode", "FilterNode", "CalculationNode", "SortNode", "CalculationNode", "ReturnNode" ], explain(query));
},
@ -393,11 +415,11 @@ function ahuacatlQueryOptimizerInTestSuite () {
for (i = 1; i < 100; ++i) {
c.save({ _key: "test" + i });
}
var en = cn + "Edge";
internal.db._drop(en);
var e = internal.db._createEdgeCollection(en);
for (i = 1; i < 100; ++i) {
e.save(cn + "/test" + i, cn + "/test" + (i - 1), { });
}
@ -406,9 +428,9 @@ function ahuacatlQueryOptimizerInTestSuite () {
var query = "LET parents = [ '" + cn + "/test5', '" + cn + "/test7' ] FOR c IN " + en + " FILTER c._from IN parents SORT c._to RETURN c._to";
var actual = getQueryResults(query);
assertEqual(expected, actual);
assertEqual([ "SingletonNode", "CalculationNode", "IndexNode", "CalculationNode", "FilterNode", "CalculationNode", "SortNode", "CalculationNode", "ReturnNode" ], explain(query));
internal.db._drop(en);
},
@ -422,11 +444,11 @@ function ahuacatlQueryOptimizerInTestSuite () {
for (i = 1; i < 100; ++i) {
c.save({ _key: "test" + i });
}
var en = cn + "Edge";
internal.db._drop(en);
var e = internal.db._createEdgeCollection(en);
for (i = 1; i < 100; ++i) {
e.save(cn + "/test" + i, cn + "/test" + (i - 1), { });
}
@ -435,9 +457,9 @@ function ahuacatlQueryOptimizerInTestSuite () {
var query = "LET parents = (FOR c IN " + cn + " FILTER c._key IN [ 'test5', 'test7' ] RETURN c._id) FOR c IN " + en + " FILTER c._from IN parents SORT c._to RETURN c._to";
var actual = getQueryResults(query);
assertEqual(expected, actual);
assertEqual([ "SingletonNode", "SubqueryNode", "IndexNode", "CalculationNode", "FilterNode", "CalculationNode", "SortNode", "CalculationNode", "ReturnNode" ], explain(query));
internal.db._drop(en);
},
@ -451,11 +473,11 @@ function ahuacatlQueryOptimizerInTestSuite () {
for (i = 1; i < 100; ++i) {
c.save({ _key: "test" + i });
}
var en = cn + "Edge";
internal.db._drop(en);
var e = internal.db._createEdgeCollection(en);
for (i = 1; i < 100; ++i) {
e.save(cn + "/test" + i, cn + "/test" + (i - 1), { });
}
@ -463,7 +485,7 @@ function ahuacatlQueryOptimizerInTestSuite () {
var expected = [ { keys: [ cn + '/test4' ] }, { keys: [ cn + '/test6' ] } ];
var actual = getQueryResults("FOR c IN " + cn + " FILTER c._key IN [ 'test5', 'test7' ] SORT c._key RETURN { keys: (FOR c2 IN " + en + " FILTER c2._from IN [ c._id ] RETURN c2._to) }");
assertEqual(expected, actual);
internal.db._drop(en);
},
@ -477,11 +499,11 @@ function ahuacatlQueryOptimizerInTestSuite () {
for (i = 1; i < 100; ++i) {
c.save({ _key: "test" + i, ids: [ cn + "/test" + i ] });
}
var en = cn + "Edge";
internal.db._drop(en);
var e = internal.db._createEdgeCollection(en);
for (i = 1; i < 100; ++i) {
e.save(cn + "/test" + i, cn + "/test" + (i - 1), { });
}
@ -489,7 +511,7 @@ function ahuacatlQueryOptimizerInTestSuite () {
var expected = [ { keys: [ cn + '/test4' ] }, { keys: [ cn + '/test6' ] } ];
var actual = getQueryResults("FOR c IN " + cn + " FILTER c._key IN [ 'test5', 'test7' ] SORT c._key RETURN { keys: (FOR c2 IN " + en + " FILTER c2._from IN c.ids RETURN c2._to) }");
assertEqual(expected, actual);
internal.db._drop(en);
},
@ -589,37 +611,37 @@ function ahuacatlQueryOptimizerInTestSuite () {
c.save({ value: 12 });
c.save({ value: false });
c.save({ value: null });
var actual = getQueryResults("FOR i IN " + cn + " FILTER i.value IN [ 'red', 'green' ] SORT i.value RETURN i.value");
assertEqual([ "green", "red" ], actual);
actual = getQueryResults("FOR i IN " + cn + " FILTER i.value NOT IN [ 'red', 'green' ] SORT i.value RETURN i.value");
assertEqual([ null, false, 12, "blue" ], actual);
actual = getQueryResults("FOR i IN " + cn + " FILTER i.value IN [ 'green', 'blue' ] SORT i.value RETURN i.value");
assertEqual([ "blue", "green" ], actual);
actual = getQueryResults("FOR i IN " + cn + " FILTER i.value NOT IN [ 'green', 'blue' ] SORT i.value RETURN i.value");
assertEqual([ null, false, 12, "red" ], actual);
actual = getQueryResults("FOR i IN " + cn + " FILTER i.value IN [ 'foo', 'bar' ] SORT i.value RETURN i.value");
assertEqual([ ], actual);
actual = getQueryResults("FOR i IN " + cn + " FILTER i.value NOT IN [ 'foo', 'bar' ] SORT i.value RETURN i.value");
assertEqual([ null, false, 12, "blue", "green", "red" ], actual);
actual = getQueryResults("FOR i IN " + cn + " FILTER i.value IN [ 12, false ] SORT i.value RETURN i.value");
assertEqual([ false, 12 ], actual);
actual = getQueryResults("FOR i IN " + cn + " FILTER i.value NOT IN [ 12, false ] SORT i.value RETURN i.value");
assertEqual([ null, "blue", "green", "red" ], actual);
actual = getQueryResults("FOR i IN " + cn + " FILTER i.value IN [ 23, 'black', 'red', null ] SORT i.value RETURN i.value");
assertEqual([ null, 'red' ], actual);
actual = getQueryResults("FOR i IN " + cn + " FILTER i.value NOT IN [ 23, 'black', 'red', null ] SORT i.value RETURN i.value");
assertEqual([ false, 12, "blue", "green" ], actual);
c.truncate();
c.save({ value: [ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, "red", "blue" ]});
actual = getQueryResults("FOR i IN " + cn + " FILTER 12 IN i.value SORT i.value RETURN LENGTH(i.value)");
@ -630,28 +652,28 @@ function ahuacatlQueryOptimizerInTestSuite () {
actual = getQueryResults("FOR i IN " + cn + " FILTER 12 NOT IN i.value SORT i.value RETURN LENGTH(i.value)");
assertEqual([ ], actual);
actual = getQueryResults("FOR i IN " + cn + " FILTER 13 IN i.value SORT i.value RETURN LENGTH(i.value)");
assertEqual([ ], actual);
actual = getQueryResults("FOR i IN " + cn + " FILTER 13 NOT IN i.value SORT i.value RETURN LENGTH(i.value)");
assertEqual([ 14 ], actual);
actual = getQueryResults("FOR i IN " + cn + " FILTER 'red' IN i.value SORT i.value RETURN LENGTH(i.value)");
assertEqual([ 14 ], actual);
actual = getQueryResults("FOR i IN " + cn + " FILTER 'red' NOT IN i.value SORT i.value RETURN LENGTH(i.value)");
assertEqual([ ], actual);
actual = getQueryResults("FOR i IN " + cn + " FILTER 'fuchsia' NOT IN i.value SORT i.value RETURN LENGTH(i.value)");
assertEqual([ 14 ], actual);
},
////////////////////////////////////////////////////////////////////////////////
/// @brief check IN
/// @brief check IN
////////////////////////////////////////////////////////////////////////////////
testInListHashIndex : function () {
testInListHashIndex : function () {
for (var i = 1; i < 100; ++i) {
c.save({ value: i });
}
@ -662,8 +684,8 @@ function ahuacatlQueryOptimizerInTestSuite () {
assertEqual(expected, actual);
ruleIsUsed(query);
},
testInListSkiplist : function () {
testInListSkiplist : function () {
for (var i = 1; i < 100; ++i) {
c.save({ value: i });
}
@ -674,8 +696,8 @@ function ahuacatlQueryOptimizerInTestSuite () {
assertEqual(expected, actual);
ruleIsUsed(query);
},
testInListPrimaryIndex : function () {
testInListPrimaryIndex : function () {
for (var i = 1; i < 100; ++i) {
c.save({ _key: "a" + i });
}
@ -687,10 +709,10 @@ function ahuacatlQueryOptimizerInTestSuite () {
},
////////////////////////////////////////////////////////////////////////////////
/// @brief check overlapping IN
/// @brief check overlapping IN
////////////////////////////////////////////////////////////////////////////////
testOverlappingInListHashIndex1 : function () {
testOverlappingInListHashIndex1 : function () {
for (var i = 1; i < 100; ++i) {
c.save({ value: i });
}
@ -701,8 +723,8 @@ function ahuacatlQueryOptimizerInTestSuite () {
assertEqual(expected, actual);
ruleIsUsed(query);
},
testOverlappingInListSkiplist1 : function () {
testOverlappingInListSkiplist1 : function () {
for (var i = 1; i < 100; ++i) {
c.save({ value: i });
}
@ -713,8 +735,8 @@ function ahuacatlQueryOptimizerInTestSuite () {
assertEqual(expected, actual);
ruleIsUsed(query);
},
testOverlappingInListSkiplist1Rev : function () {
testOverlappingInListSkiplist1Rev : function () {
for (var i = 1; i < 100; ++i) {
c.save({ value: i });
}
@ -725,8 +747,8 @@ function ahuacatlQueryOptimizerInTestSuite () {
assertEqual(expected, actual);
ruleIsUsed(query);
},
testOverlappingInListHashIndex2 : function () {
testOverlappingInListHashIndex2 : function () {
for (var i = 1; i < 100; ++i) {
c.save({ value: i });
}
@ -737,8 +759,8 @@ function ahuacatlQueryOptimizerInTestSuite () {
assertEqual(expected, actual);
ruleIsUsed(query);
},
testOverlappingInListSkiplist2 : function () {
testOverlappingInListSkiplist2 : function () {
for (var i = 1; i < 100; ++i) {
c.save({ value: i });
}
@ -749,8 +771,8 @@ function ahuacatlQueryOptimizerInTestSuite () {
assertEqual(expected, actual);
ruleIsUsed(query);
},
testOverlappingInListSkiplist2Rev : function () {
testOverlappingInListSkiplist2Rev : function () {
for (var i = 1; i < 100; ++i) {
c.save({ value: i });
}
@ -762,7 +784,7 @@ function ahuacatlQueryOptimizerInTestSuite () {
ruleIsUsed(query);
},
testOverlappingInListHashIndex3 : function () {
testOverlappingInListHashIndex3 : function () {
for (var i = 1; i < 100; ++i) {
c.save({ value: i });
}
@ -773,8 +795,8 @@ function ahuacatlQueryOptimizerInTestSuite () {
assertEqual(expected, actual);
ruleIsUsed(query);
},
testOverlappingInListSkiplist3 : function () {
testOverlappingInListSkiplist3 : function () {
for (var i = 1; i < 100; ++i) {
c.save({ value: i });
}
@ -785,8 +807,8 @@ function ahuacatlQueryOptimizerInTestSuite () {
assertEqual(expected, actual);
ruleIsUsed(query);
},
testOverlappingInListSkiplist3Rev : function () {
testOverlappingInListSkiplist3Rev : function () {
for (var i = 1; i < 100; ++i) {
c.save({ value: i });
}
@ -797,8 +819,8 @@ function ahuacatlQueryOptimizerInTestSuite () {
assertEqual(expected, actual);
ruleIsUsed(query);
},
testOverlappingInListHashIndex4 : function () {
testOverlappingInListHashIndex4 : function () {
for (var i = 1; i < 100; ++i) {
c.save({ value: i });
}
@ -809,8 +831,8 @@ function ahuacatlQueryOptimizerInTestSuite () {
assertEqual(expected, actual);
ruleIsUsed(query);
},
testOverlappingInListSkiplist4 : function () {
testOverlappingInListSkiplist4 : function () {
for (var i = 1; i < 100; ++i) {
c.save({ value: i });
}
@ -821,8 +843,8 @@ function ahuacatlQueryOptimizerInTestSuite () {
assertEqual(expected, actual);
ruleIsUsed(query);
},
testOverlappingInListSkiplist4Rev : function () {
testOverlappingInListSkiplist4Rev : function () {
for (var i = 1; i < 100; ++i) {
c.save({ value: i });
}
@ -833,8 +855,8 @@ function ahuacatlQueryOptimizerInTestSuite () {
assertEqual(expected, actual);
ruleIsUsed(query);
},
testDuplicatesListHashIndex : function () {
testDuplicatesListHashIndex : function () {
for (var i = 1; i < 100; ++i) {
c.save({ value: i });
}
@ -845,8 +867,8 @@ function ahuacatlQueryOptimizerInTestSuite () {
assertEqual(expected, actual);
ruleIsUsed(query);
},
testDuplicatesListSkiplist : function () {
testDuplicatesListSkiplist : function () {
for (var i = 1; i < 100; ++i) {
c.save({ value: i });
}
@ -857,8 +879,8 @@ function ahuacatlQueryOptimizerInTestSuite () {
assertEqual(expected, actual);
ruleIsUsed(query);
},
testDuplicatesOrHashIndex : function () {
testDuplicatesOrHashIndex : function () {
for (var i = 1; i < 100; ++i) {
c.save({ value: i });
}
@ -869,8 +891,8 @@ function ahuacatlQueryOptimizerInTestSuite () {
assertEqual(expected, actual);
ruleIsUsed(query);
},
testDuplicatesOrSkiplist : function () {
testDuplicatesOrSkiplist : function () {
for (var i = 1; i < 100; ++i) {
c.save({ value: i });
}
@ -881,8 +903,8 @@ function ahuacatlQueryOptimizerInTestSuite () {
assertEqual(expected, actual);
ruleIsUsed(query);
},
testDuplicatesListHashIndexDynamic : function () {
testDuplicatesListHashIndexDynamic : function () {
for (var i = 1; i < 100; ++i) {
c.save({ value: i });
}
@ -893,8 +915,8 @@ function ahuacatlQueryOptimizerInTestSuite () {
assertEqual(expected, actual);
ruleIsUsed(query);
},
testDuplicatesListSkiplistDynamic : function () {
testDuplicatesListSkiplistDynamic : function () {
for (var i = 1; i < 100; ++i) {
c.save({ value: i });
}
@ -905,8 +927,8 @@ function ahuacatlQueryOptimizerInTestSuite () {
assertEqual(expected, actual);
ruleIsUsed(query);
},
testDuplicatesOrHashIndexDynamic : function () {
testDuplicatesOrHashIndexDynamic : function () {
for (var i = 1; i < 100; ++i) {
c.save({ value: i });
}
@ -917,8 +939,8 @@ function ahuacatlQueryOptimizerInTestSuite () {
assertEqual(expected, actual);
ruleIsUsed(query);
},
testDuplicatesOrSkiplistDynamic : function () {
testDuplicatesOrSkiplistDynamic : function () {
for (var i = 1; i < 100; ++i) {
c.save({ value: i });
}
@ -930,7 +952,7 @@ function ahuacatlQueryOptimizerInTestSuite () {
ruleIsUsed(query);
},
testOverlappingRangesListSkiplist1 : function () {
testOverlappingRangesListSkiplist1 : function () {
for (var i = 1; i < 100; ++i) {
c.save({ value: i });
}
@ -944,8 +966,8 @@ function ahuacatlQueryOptimizerInTestSuite () {
assertEqual(expected, actual);
ruleIsUsed(query);
},
testOverlappingRangesListHashIndex1 : function () {
testOverlappingRangesListHashIndex1 : function () {
for (var i = 1; i < 100; ++i) {
c.save({ value: i });
}
@ -958,7 +980,7 @@ function ahuacatlQueryOptimizerInTestSuite () {
}
},
testOverlappingRangesListSkiplist2 : function () {
testOverlappingRangesListSkiplist2 : function () {
for (var i = 1; i < 100; ++i) {
c.save({ value: i });
}
@ -969,8 +991,8 @@ function ahuacatlQueryOptimizerInTestSuite () {
assertEqual(expected, actual);
ruleIsUsed(query);
},
testOverlappingRangesListSkiplist2Rev : function () {
testOverlappingRangesListSkiplist2Rev : function () {
for (var i = 1; i < 100; ++i) {
c.save({ value: i });
}
@ -981,8 +1003,8 @@ function ahuacatlQueryOptimizerInTestSuite () {
assertEqual(expected, actual);
ruleIsUsed(query);
},
testOverlappingRangesListHashIndex2 : function () {
testOverlappingRangesListHashIndex2 : function () {
for (var i = 1; i < 100; ++i) {
c.save({ value: i });
}
@ -1030,7 +1052,7 @@ function ahuacatlQueryOptimizerInTestSuite () {
assertEqual(expected, actual);
ruleIsUsed(query);
},
testNestedOrSkiplistSortedDesc : function () {
for (var i = 1; i < 5; ++i) {
c.save({ value: i });
@ -1054,7 +1076,7 @@ function ahuacatlQueryOptimizerInTestSuite () {
assertEqual(expected, actual);
ruleIsUsed(query);
},
testOverlappingDynamicAndNonDynamic: function () {
for (var i = 1; i <= 5; ++i) {
c.save({ value1: i, value2: i + 5 });
@ -1066,7 +1088,7 @@ function ahuacatlQueryOptimizerInTestSuite () {
assertEqual(expected, actual);
ruleIsUsed(query);
},
testSkiplistMoreThanOne1 : function () {
for (var i = 1; i <= 10; i++) {
for (var j = 1; j <= 10; j++) {
@ -1085,8 +1107,8 @@ function ahuacatlQueryOptimizerInTestSuite () {
});
assertEqual(expected, actual);
ruleIsUsed(query);
},
},
testSkiplistMoreThanOne1Desc : function () {
for (var i = 1; i <= 10; i++) {
for (var j = 1; j <= 10; j++) {
@ -1100,8 +1122,8 @@ function ahuacatlQueryOptimizerInTestSuite () {
var actual = getQueryResults(query);
assertEqual(expected, actual);
ruleIsUsed(query);
},
},
testSkiplistMoreThanOne2 : function () {
for (var i = 1; i <= 10; i++) {
for (var j = 1; j <= 10; j++) {
@ -1121,7 +1143,7 @@ function ahuacatlQueryOptimizerInTestSuite () {
assertEqual(expected, actual);
ruleIsUsed(query);
},
testSkiplistMoreThanOne2Desc : function () {
for (var i = 1; i <= 10; i++) {
for (var j = 1; j <= 10; j++) {
@ -1136,7 +1158,7 @@ function ahuacatlQueryOptimizerInTestSuite () {
assertEqual(expected, actual);
ruleIsUsed(query);
},
testSkiplistMoreThanOne3 : function () {
for (var i = 1; i <= 10; i++) {
for (var j = 1; j <= 10; j++) {
@ -1160,7 +1182,7 @@ function ahuacatlQueryOptimizerInTestSuite () {
testSkiplistMoreThanOne4 : function () {
for (var i = 1;i <= 10;i++) {
for (var j = 1; j <= 30; j++) {
c.save({value1 : i, value2: j, value3: i + j,
c.save({value1 : i, value2: j, value3: i + j,
value4: 'somethings' + 2*j });
}
}
@ -1168,10 +1190,10 @@ function ahuacatlQueryOptimizerInTestSuite () {
c.ensureSkiplist("value1", "value2", "value3", "value4");
var query = "FOR x IN " + cn + " FILTER (x.value1 IN [1, 2, 3] && x.value1 IN [2, 3, 4] && x.value2 == 10 && x.value3 <= 20) || (x.value1 == 1 && x.value2 == 2 && x.value3 >= 0 && x.value3 <= 6 && x.value4 in ['somethings2', 'somethings4'] ) RETURN [x.value1, x.value2, x.value3, x.value4]";
var expected = [
var expected = [
[ 1, 2, 3, "somethings4" ],
[ 2, 10, 12, "somethings20" ],
[ 3, 10, 13, "somethings20" ],
[ 2, 10, 12, "somethings20" ],
[ 3, 10, 13, "somethings20" ],
];
var actual = getQueryResults(query);
// Sorting is not guaranteed any more.
@ -1185,11 +1207,11 @@ function ahuacatlQueryOptimizerInTestSuite () {
assertEqual(expected, actual);
ruleIsUsed(query);
},
testSkiplistMoreThanOne4Desc : function () {
for (var i = 1;i <= 10;i++) {
for (var j = 1; j <= 30; j++) {
c.save({value1 : i, value2: j, value3: i + j,
c.save({value1 : i, value2: j, value3: i + j,
value4: 'somethings' + 2*j });
}
}
@ -1197,9 +1219,9 @@ function ahuacatlQueryOptimizerInTestSuite () {
c.ensureSkiplist("value1", "value2", "value3", "value4");
var query = "FOR x IN " + cn + " FILTER (x.value1 IN [1, 2, 3] && x.value1 IN [2, 3, 4] && x.value2 == 10 && x.value3 <= 20) || (x.value1 == 1 && x.value2 == 2 && x.value3 >= 0 && x.value3 <= 6 && x.value4 in ['somethings2', 'somethings4'] ) SORT x.value1 DESC RETURN [x.value1, x.value2, x.value3, x.value4]";
var expected = [
[ 3, 10, 13, "somethings20" ],
[ 2, 10, 12, "somethings20" ],
var expected = [
[ 3, 10, 13, "somethings20" ],
[ 2, 10, 12, "somethings20" ],
[ 1, 2, 3, "somethings4" ],
];
var actual = getQueryResults(query);
@ -1212,7 +1234,7 @@ function ahuacatlQueryOptimizerInTestSuite () {
for (var i = 1;i <= 10;i++) {
for (var j = 1; j <= 10; j++) {
for (var k = 1; k <= 10; k++) {
c.save({value1 : i, value2: j, value3: k,
c.save({value1 : i, value2: j, value3: k,
value4: 'somethings' + j * 2 });
}
}
@ -1221,12 +1243,12 @@ function ahuacatlQueryOptimizerInTestSuite () {
c.ensureSkiplist("value1", "value2", "value3", "value4");
var query = "FOR x IN " + cn + " FILTER (x.value1 IN [PASSTHRU(1), PASSTHRU(2), PASSTHRU(3)] && x.value1 IN [2, 3, 4] && x.value2 == PASSTHRU(10) && x.value3 <= 2) || (x.value1 == 1 && x.value2 == 2 && x.value3 >= 0 && x.value3 == PASSTHRU(6) && x.value4 in ['somethings2', PASSTHRU('somethings4')] ) RETURN [x.value1, x.value2, x.value3, x.value4]";
var expected = [
var expected = [
[ 1, 2, 6, "somethings4" ] ,
[ 2, 10, 1, "somethings20" ],
[ 2, 10, 2, "somethings20" ],
[ 3, 10, 1, "somethings20" ],
[ 3, 10, 2, "somethings20" ]
[ 2, 10, 1, "somethings20" ],
[ 2, 10, 2, "somethings20" ],
[ 3, 10, 1, "somethings20" ],
[ 3, 10, 2, "somethings20" ]
];
var actual = getQueryResults(query);
// Sorting is not guaranteed any more.
@ -1240,13 +1262,13 @@ function ahuacatlQueryOptimizerInTestSuite () {
assertEqual(expected, actual);
ruleIsUsed(query);
},
testSkiplistMoreThanOne5Desc : function () {
for (var i = 1;i <= 10;i++) {
for (var j = 1; j <= 10; j++) {
for (var k = 1; k <= 10; k++) {
c.save({value1 : i, value2: j, value3: k,
c.save({value1 : i, value2: j, value3: k,
value4: 'somethings' + 2*j });
}
}
@ -1255,24 +1277,24 @@ function ahuacatlQueryOptimizerInTestSuite () {
c.ensureSkiplist("value1", "value2", "value3", "value4");
var query = "FOR x IN " + cn + " FILTER (x.value1 IN [PASSTHRU(1), PASSTHRU(2), PASSTHRU(3)] && x.value1 IN [2, 3, 4] && x.value2 == PASSTHRU(10) && x.value3 <= 2) || (x.value1 == 1 && x.value2 == 2 && x.value3 >= 0 && x.value3 == PASSTHRU(6) && x.value4 in ['somethings2', PASSTHRU('somethings4')] ) SORT x.value1 DESC RETURN [x.value1, x.value2, x.value3, x.value4]";
var expected = [
[ 3, 10, 2, "somethings20" ],
var expected = [
[ 3, 10, 2, "somethings20" ],
[ 3, 10, 1, "somethings20" ],
[ 2, 10, 2, "somethings20" ],
[ 2, 10, 1, "somethings20" ],
[ 2, 10, 2, "somethings20" ],
[ 2, 10, 1, "somethings20" ],
[ 1, 2, 6, "somethings4" ] ,
];
var actual = getQueryResults(query);
assertEqual(expected, actual);
ruleIsUsed(query);
},
testSkiplistMoreThanOne6 : function () {
for (var i = 1;i <= 10;i++) {
for (var j = 1; j <= 10; j++) {
for (var k = 1; k <= 10; k++) {
c.save({value1 : i, value2: j, value3: k,
c.save({value1 : i, value2: j, value3: k,
value4: 'somethings' + 2*j });
}
}
@ -1281,12 +1303,12 @@ function ahuacatlQueryOptimizerInTestSuite () {
c.ensureSkiplist("value1", "value2", "value3", "value4");
var query = "FOR x IN " + cn + " FILTER (x.value1 IN [PASSTHRU(1), PASSTHRU(2), PASSTHRU(3)] && x.value1 IN PASSTHRU([2, 3, 4]) && x.value2 == PASSTHRU(10) && x.value3 <= 2) || (x.value1 == 1 && x.value2 == 2 && x.value3 >= 0 && x.value3 == PASSTHRU(6) && x.value4 in ['somethings2', PASSTHRU('somethings4')] ) RETURN [x.value1, x.value2, x.value3, x.value4]";
var expected = [
var expected = [
[ 1, 2, 6, "somethings4" ] ,
[ 2, 10, 1, "somethings20" ],
[ 2, 10, 2, "somethings20" ],
[ 3, 10, 1, "somethings20" ],
[ 3, 10, 2, "somethings20" ]
[ 2, 10, 1, "somethings20" ],
[ 2, 10, 2, "somethings20" ],
[ 3, 10, 1, "somethings20" ],
[ 3, 10, 2, "somethings20" ]
];
var actual = getQueryResults(query);
// Sorting is not guaranteed any more.
@ -1311,7 +1333,7 @@ function ahuacatlQueryOptimizerInTestSuite () {
function ahuacatlQueryOptimizerInWithLongArraysTestSuite () {
var c = null;
var cn = "UnitTestsAhuacatlOptimizerInWithLongArrays";
return {
////////////////////////////////////////////////////////////////////////////////
@ -1341,11 +1363,11 @@ function ahuacatlQueryOptimizerInWithLongArraysTestSuite () {
c.insert({ value: i });
comp.push(i);
}
var result;
result = AQL_EXECUTE("FOR doc IN @@cn FILTER doc.value IN @comp RETURN 1", { "@cn": cn, comp: comp }).json;
assertEqual(1000, result.length);
result = AQL_EXECUTE("FOR doc IN @@cn FILTER doc.value NOT IN @comp RETURN 1", { "@cn": cn, comp: comp }).json;
assertEqual(0, result.length);
},
@ -1360,11 +1382,11 @@ function ahuacatlQueryOptimizerInWithLongArraysTestSuite () {
c.insert({ value: i });
comp.push(1000 - 1 - i);
}
var result;
result = AQL_EXECUTE("FOR doc IN @@cn FILTER doc.value IN @comp RETURN 1", { "@cn": cn, comp: comp }).json;
assertEqual(1000, result.length);
result = AQL_EXECUTE("FOR doc IN @@cn FILTER doc.value NOT IN @comp RETURN 1", { "@cn": cn, comp: comp }).json;
assertEqual(0, result.length);
},
@ -1379,11 +1401,11 @@ function ahuacatlQueryOptimizerInWithLongArraysTestSuite () {
c.insert({ value: "test" + i });
comp.push("test" + i);
}
var result;
result = AQL_EXECUTE("FOR doc IN @@cn FILTER doc.value IN @comp RETURN 1", { "@cn": cn, comp: comp }).json;
assertEqual(1000, result.length);
result = AQL_EXECUTE("FOR doc IN @@cn FILTER doc.value NOT IN @comp RETURN 1", { "@cn": cn, comp: comp }).json;
assertEqual(0, result.length);
},
@ -1398,11 +1420,11 @@ function ahuacatlQueryOptimizerInWithLongArraysTestSuite () {
c.insert({ value: "test" + i });
comp.push("test" + (1000 - 1 - i));
}
var result;
result = AQL_EXECUTE("FOR doc IN @@cn FILTER doc.value IN @comp RETURN 1", { "@cn": cn, comp: comp }).json;
assertEqual(1000, result.length);
result = AQL_EXECUTE("FOR doc IN @@cn FILTER doc.value NOT IN @comp RETURN 1", { "@cn": cn, comp: comp }).json;
assertEqual(0, result.length);
},
@ -1417,11 +1439,11 @@ function ahuacatlQueryOptimizerInWithLongArraysTestSuite () {
c.insert({ value1: "test" + i, value2: i });
comp.push({ value1: "test" + i, value2: i });
}
var result;
result = AQL_EXECUTE("FOR doc IN @@cn FILTER { value1: doc.value1, value2: doc.value2 } IN @comp RETURN 1", { "@cn": cn, comp: comp }).json;
assertEqual(1000, result.length);
result = AQL_EXECUTE("FOR doc IN @@cn FILTER { value1: doc.value1, value2: doc.value2 } NOT IN @comp RETURN 1", { "@cn": cn, comp: comp }).json;
assertEqual(0, result.length);
},
@ -1436,11 +1458,11 @@ function ahuacatlQueryOptimizerInWithLongArraysTestSuite () {
c.insert({ value1: "test" + i, value2: 1000 - 1 - i });
comp.push({ value1: "test" + i, value2: 1000 - 1 - i });
}
var result;
result = AQL_EXECUTE("FOR doc IN @@cn FILTER { value1: doc.value1, value2: doc.value2 } IN @comp RETURN 1", { "@cn": cn, comp: comp }).json;
assertEqual(1000, result.length);
result = AQL_EXECUTE("FOR doc IN @@cn FILTER { value1: doc.value1, value2: doc.value2 } NOT IN @comp RETURN 1", { "@cn": cn, comp: comp }).json;
assertEqual(0, result.length);
},
@ -1455,11 +1477,11 @@ function ahuacatlQueryOptimizerInWithLongArraysTestSuite () {
c.insert({ value1: "test" + (1000 - 1 - i), value2: 1000 - 1 - i });
comp.push({ value1: "test" + (1000 - 1 - i), value2: 1000 - 1 - i });
}
var result;
result = AQL_EXECUTE("FOR doc IN @@cn FILTER { value1: doc.value1, value2: doc.value2 } IN @comp RETURN 1", { "@cn": cn, comp: comp }).json;
assertEqual(1000, result.length);
result = AQL_EXECUTE("FOR doc IN @@cn FILTER { value1: doc.value1, value2: doc.value2 } NOT IN @comp RETURN 1", { "@cn": cn, comp: comp }).json;
assertEqual(0, result.length);
},
@ -1474,11 +1496,11 @@ function ahuacatlQueryOptimizerInWithLongArraysTestSuite () {
c.insert({ value1: "test" + (1000 - 1 - i), value2: i });
comp.push({ value1: "test" + (1000 - 1 - i), value2: i });
}
var result;
result = AQL_EXECUTE("FOR doc IN @@cn FILTER { value1: doc.value1, value2: doc.value2 } IN @comp RETURN 1", { "@cn": cn, comp: comp }).json;
assertEqual(1000, result.length);
result = AQL_EXECUTE("FOR doc IN @@cn FILTER { value1: doc.value1, value2: doc.value2 } NOT IN @comp RETURN 1", { "@cn": cn, comp: comp }).json;
assertEqual(0, result.length);
}