1
0
Fork 0

added tests

This commit is contained in:
Jan Steemann 2015-06-16 15:13:53 +02:00
parent 3e99e76b4f
commit d6f3436cc9
15 changed files with 914 additions and 560 deletions

View File

@ -169,6 +169,34 @@ void Ast::addOperation (AstNode* node) {
_root->addMember(node);
}
////////////////////////////////////////////////////////////////////////////////
/// @brief find the bottom-most expansion subnodes (if any)
////////////////////////////////////////////////////////////////////////////////
AstNode const* Ast::findExpansionSubNode (AstNode const* current) const {
while (true) {
TRI_ASSERT(current->type == NODE_TYPE_EXPANSION);
if (current->getMember(1)->type != NODE_TYPE_EXPANSION) {
return current;
}
current = current->getMember(1);
}
}
////////////////////////////////////////////////////////////////////////////////
/// @brief create an AST passhthru node
/// note: this type of node is only used during parsing and optimized away later
////////////////////////////////////////////////////////////////////////////////
AstNode* Ast::createNodePassthru (AstNode const* what) {
AstNode* node = createNode(NODE_TYPE_PASSTHRU);
node->addMember(what);
return node;
}
////////////////////////////////////////////////////////////////////////////////
/// @brief create an AST example node
@ -776,33 +804,61 @@ AstNode* Ast::createNodeIndexedAccess (AstNode const* accessed,
}
////////////////////////////////////////////////////////////////////////////////
/// @brief create an AST expansion node, without filter
/// @brief create an AST array limit node (offset, count)
////////////////////////////////////////////////////////////////////////////////
AstNode* Ast::createNodeExpansion (AstNode const* iterator,
AstNode const* expanded,
bool multiExpand) {
AstNode* node = createNode(NODE_TYPE_EXPANSION);
node->setBoolValue(multiExpand);
AstNode* Ast::createNodeArrayLimit (AstNode const* offset,
AstNode const* count) {
AstNode* node = createNode(NODE_TYPE_ARRAY_LIMIT);
node->addMember(iterator);
node->addMember(expanded);
if (offset == nullptr) {
offset = createNodeValueInt(0);
}
node->addMember(offset);
node->addMember(count);
return node;
}
////////////////////////////////////////////////////////////////////////////////
/// @brief create an AST expansion node, with a filter
/// @brief create an AST expansion node, with or without a filter
////////////////////////////////////////////////////////////////////////////////
AstNode* Ast::createNodeExpansion (AstNode const* iterator,
AstNode* Ast::createNodeExpansion (int64_t levels,
AstNode const* iterator,
AstNode const* expanded,
AstNode const* filter) {
AstNode const* filter,
AstNode const* limit,
AstNode const* projection) {
AstNode* node = createNode(NODE_TYPE_EXPANSION);
node->setIntValue(levels);
node->addMember(iterator);
node->addMember(expanded);
node->addMember(filter);
if (filter == nullptr) {
node->addMember(createNodeNop());
}
else {
node->addMember(filter);
}
if (limit == nullptr) {
node->addMember(createNodeNop());
}
else {
TRI_ASSERT(limit->type == NODE_TYPE_ARRAY_LIMIT);
node->addMember(limit);
}
if (projection == nullptr) {
node->addMember(createNodeNop());
}
else {
node->addMember(projection);
}
TRI_ASSERT(node->numMembers() == 5);
return node;
}
@ -1153,6 +1209,7 @@ AstNode* Ast::replaceVariables (AstNode* node,
if (variable != nullptr) {
auto it = replacements.find(variable->id);
if (it != replacements.end()) {
// overwrite the node in place
node->setData((*it).second);
@ -1296,6 +1353,12 @@ void Ast::validateAndOptimize () {
return this->optimizeTernaryOperator(node);
}
// passthru node
if (node->type == NODE_TYPE_PASSTHRU) {
// optimize away passthru node. this type of node is only used during parsing
return node->getMember(0);
}
// call to built-in function
if (node->type == NODE_TYPE_FCALL) {
auto func = static_cast<Function*>(node->getData());

View File

@ -214,6 +214,19 @@ namespace triagens {
void addOperation (AstNode*);
////////////////////////////////////////////////////////////////////////////////
/// @brief find the bottom-most expansion subnodes (if any)
////////////////////////////////////////////////////////////////////////////////
AstNode const* findExpansionSubNode (AstNode const*) const;
////////////////////////////////////////////////////////////////////////////////
/// @brief create an AST passthru node
/// note: this type of node is only used during parsing and optimized away later
////////////////////////////////////////////////////////////////////////////////
AstNode* createNodePassthru (AstNode const*);
////////////////////////////////////////////////////////////////////////////////
/// @brief create an AST example node
////////////////////////////////////////////////////////////////////////////////
@ -446,18 +459,20 @@ namespace triagens {
AstNode const*);
////////////////////////////////////////////////////////////////////////////////
/// @brief create an AST expansion node, without filter
/// @brief create an AST array limit node (offset, count)
////////////////////////////////////////////////////////////////////////////////
AstNode* createNodeExpansion (AstNode const*,
AstNode* createNodeArrayLimit (AstNode const*,
AstNode const*);
////////////////////////////////////////////////////////////////////////////////
/// @brief create an AST expansion node
////////////////////////////////////////////////////////////////////////////////
AstNode* createNodeExpansion (int64_t,
AstNode const*,
AstNode const*,
AstNode const*,
bool);
////////////////////////////////////////////////////////////////////////////////
/// @brief create an AST expansion node, with a filter
////////////////////////////////////////////////////////////////////////////////
AstNode* createNodeExpansion (AstNode const*,
AstNode const*,
AstNode const*);

View File

@ -146,7 +146,9 @@ std::unordered_map<int, std::string const> const AstNode::TypeNames{
{ static_cast<int>(NODE_TYPE_COLLECT_COUNT), "collect count" },
{ static_cast<int>(NODE_TYPE_COLLECT_EXPRESSION), "collect expression" },
{ static_cast<int>(NODE_TYPE_CALCULATED_OBJECT_ELEMENT),"calculated object element" },
{ static_cast<int>(NODE_TYPE_EXAMPLE), "example" }
{ static_cast<int>(NODE_TYPE_EXAMPLE), "example" },
{ static_cast<int>(NODE_TYPE_PASSTHRU), "passthru" },
{ static_cast<int>(NODE_TYPE_ARRAY_LIMIT), "array limit" }
};
////////////////////////////////////////////////////////////////////////////////
@ -515,6 +517,10 @@ AstNode::AstNode (Ast* ast,
setStringValue(query->registerString(JsonHelper::getStringValue(json.json(), "name", ""), false));
break;
}
case NODE_TYPE_EXPANSION: {
setIntValue(JsonHelper::checkAndGetNumericValue<int64_t>(json.json(), "levels"));
break;
}
case NODE_TYPE_OBJECT:
case NODE_TYPE_ROOT:
case NODE_TYPE_FOR:
@ -555,13 +561,14 @@ AstNode::AstNode (Ast* ast,
case NODE_TYPE_SUBQUERY:
case NODE_TYPE_BOUND_ATTRIBUTE_ACCESS:
case NODE_TYPE_INDEXED_ACCESS:
case NODE_TYPE_EXPANSION:
case NODE_TYPE_ITERATOR:
case NODE_TYPE_ARRAY:
case NODE_TYPE_RANGE:
case NODE_TYPE_NOP:
case NODE_TYPE_CALCULATED_OBJECT_ELEMENT:
case NODE_TYPE_EXAMPLE:
case NODE_TYPE_PASSTHRU:
case NODE_TYPE_ARRAY_LIMIT:
break;
}
@ -732,7 +739,7 @@ TRI_json_t* AstNode::toJsonValue (TRI_memory_zone_t* zone) const {
}
for (size_t i = 0; i < n; ++i) {
auto member = getMember(i);
auto member = getMemberUnchecked(i);
if (member != nullptr) {
TRI_json_t* j = member->toJsonValue(zone);
@ -750,7 +757,7 @@ TRI_json_t* AstNode::toJsonValue (TRI_memory_zone_t* zone) const {
TRI_json_t* object = TRI_CreateObjectJson(zone, n);
for (size_t i = 0; i < n; ++i) {
auto member = getMember(i);
auto member = getMemberUnchecked(i);
if (member != nullptr) {
TRI_json_t* j = member->getMember(0)->toJsonValue(zone);
@ -862,9 +869,13 @@ TRI_json_t* AstNode::toJson (TRI_memory_zone_t* zone,
TRI_Insert3ObjectJson(zone, node, "name", TRI_CreateStringCopyJson(zone, variable->name.c_str(), variable->name.size()));
TRI_Insert3ObjectJson(zone, node, "id", TRI_CreateNumberJson(zone, static_cast<double>(variable->id)));
}
if (type == NODE_TYPE_EXPANSION) {
TRI_Insert3ObjectJson(zone, node, "levels", TRI_CreateNumberJson(zone, static_cast<double>(getIntValue(true))));
}
// dump sub-nodes
size_t const n = members._length;
size_t const n = TRI_LengthVectorPointer(&members);
if (n > 0) {
TRI_json_t* subNodes = TRI_CreateArrayJson(zone, n);
@ -876,8 +887,8 @@ TRI_json_t* AstNode::toJson (TRI_memory_zone_t* zone,
try {
for (size_t i = 0; i < n; ++i) {
AstNode* member = getMember(i);
if (member != nullptr && member->type != NODE_TYPE_NOP) {
AstNode* member = getMemberUnchecked(i);
if (member != nullptr) {
member->toJson(subNodes, zone, verbose);
}
}
@ -1266,7 +1277,8 @@ bool AstNode::isSimple () const {
type == NODE_TYPE_OPERATOR_BINARY_IN ||
type == NODE_TYPE_OPERATOR_BINARY_NIN ||
type == NODE_TYPE_RANGE ||
type == NODE_TYPE_INDEXED_ACCESS) {
type == NODE_TYPE_INDEXED_ACCESS ||
type == NODE_TYPE_PASSTHRU) {
// a logical operator is simple if its operands are simple
// a comparison operator is simple if both bounds are simple
// a range is simple if both bounds are simple
@ -1687,6 +1699,16 @@ void AstNode::stringify (triagens::basics::StringBuffer* buffer,
buffer->appendChar(')');
return;
}
if (type == NODE_TYPE_ARRAY_LIMIT) {
// not used by V8
buffer->appendText("_LIMIT(");
getMember(0)->stringify(buffer, verbose, failIfLong);
buffer->appendChar(',');
getMember(1)->stringify(buffer, verbose, failIfLong);
buffer->appendChar(')');
return;
}
if (type == NODE_TYPE_EXPANSION) {
// not used by V8
@ -1694,10 +1716,27 @@ void AstNode::stringify (triagens::basics::StringBuffer* buffer,
getMember(0)->stringify(buffer, verbose, failIfLong);
buffer->appendChar(',');
getMember(1)->stringify(buffer, verbose, failIfLong);
if (numMembers() > 2) {
buffer->appendChar(',');
getMember(2)->stringify(buffer, verbose, failIfLong);
// filter
buffer->appendChar(',');
auto filterNode = getMember(2);
if (filterNode != nullptr) {
buffer->appendText(" FILTER ");
filterNode->getMember(0)->stringify(buffer, verbose, failIfLong);
}
auto limitNode = getMember(3);
if (limitNode != nullptr) {
buffer->appendText(" LIMIT ");
limitNode->getMember(0)->stringify(buffer, verbose, failIfLong);
buffer->appendChar(',');
limitNode->getMember(1)->stringify(buffer, verbose, failIfLong);
}
auto returnNode = getMember(4);
if (returnNode != nullptr) {
buffer->appendText(" RETURN ");
returnNode->getMember(0)->stringify(buffer, verbose, failIfLong);
}
buffer->appendChar(')');
return;
}

View File

@ -171,7 +171,9 @@ namespace triagens {
NODE_TYPE_COLLECT_EXPRESSION = 52,
NODE_TYPE_CALCULATED_OBJECT_ELEMENT = 53,
NODE_TYPE_UPSERT = 54,
NODE_TYPE_EXAMPLE = 55
NODE_TYPE_EXAMPLE = 55,
NODE_TYPE_PASSTHRU = 56,
NODE_TYPE_ARRAY_LIMIT = 57
};
static_assert(NODE_TYPE_VALUE < NODE_TYPE_ARRAY, "incorrect node types");
@ -509,7 +511,7 @@ namespace triagens {
/// @brief return the number of members
////////////////////////////////////////////////////////////////////////////////
inline size_t numMembers () const {
inline size_t numMembers () const throw() {
return members._length;
}

View File

@ -262,6 +262,12 @@ std::unordered_map<std::string, Function const> const Executor::FunctionNames{
size_t const Executor::DefaultLiteralSizeThreshold = 32;
////////////////////////////////////////////////////////////////////////////////
/// @brief maxmium number of array members created from range accesses
////////////////////////////////////////////////////////////////////////////////
int64_t const Executor::MaxRangeAccessArraySize = 1024 * 1024 * 32;
// -----------------------------------------------------------------------------
// --SECTION-- constructors / destructors
// -----------------------------------------------------------------------------
@ -305,7 +311,7 @@ V8Expression* Executor::generateExpression (AstNode const* node) {
// std::cout << "Executor::generateExpression: " << std::string(_buffer->c_str(), _buffer->length()) << "\n";
v8::Handle<v8::Object> constantValues = v8::Object::New(isolate);
for (auto& it : _constantRegisters) {
for (auto const& it : _constantRegisters) {
std::string name = "r";
name.append(std::to_string(it.second));
@ -657,11 +663,56 @@ void Executor::generateCodeArray (AstNode const* node) {
_buffer->appendChar(',');
}
generateCodeNode(node->getMember(i));
generateCodeNode(node->getMemberUnchecked(i));
}
_buffer->appendChar(']');
}
////////////////////////////////////////////////////////////////////////////////
/// @brief generate JavaScript code for an array
////////////////////////////////////////////////////////////////////////////////
void Executor::generateCodeForcedArray (AstNode const* node,
int64_t levels) {
TRI_ASSERT(node != nullptr);
if (levels > 1) {
_buffer->appendText("_AQL.AQL_FLATTEN(");
}
bool castToArray = true;
if (node->type == NODE_TYPE_ARRAY) {
// value is an array already
castToArray = false;
}
else if (node->type == NODE_TYPE_EXPANSION &&
node->getMember(0)->type == NODE_TYPE_ARRAY) {
// value is an expansion over an array
castToArray = false;
}
else if (node->type == NODE_TYPE_ITERATOR &&
node->getMember(1)->type == NODE_TYPE_ARRAY) {
castToArray = false;
}
if (castToArray) {
// force the value to be an array
_buffer->appendText("_AQL.AQL_TO_ARRAY(");
generateCodeNode(node);
_buffer->appendChar(')');
}
else {
// value already is an array
generateCodeNode(node);
}
if (levels > 1) {
_buffer->appendChar(',');
_buffer->appendInteger(levels - 1);
_buffer->appendChar(')');
}
}
////////////////////////////////////////////////////////////////////////////////
/// @brief generate JavaScript code for an object
////////////////////////////////////////////////////////////////////////////////
@ -689,7 +740,7 @@ void Executor::generateCodeDynamicObject (AstNode const* node) {
_buffer->appendText("(function() { var o={};");
for (size_t i = 0; i < n; ++i) {
auto member = node->getMember(i);
auto member = node->getMemberUnchecked(i);
if (member->type == NODE_TYPE_OBJECT_ELEMENT) {
_buffer->appendText("o[", 2);
@ -963,47 +1014,66 @@ void Executor::generateCodeUserFunctionCall (AstNode const* node) {
void Executor::generateCodeExpansion (AstNode const* node) {
TRI_ASSERT(node != nullptr);
TRI_ASSERT(node->numMembers() >= 2);
TRI_ASSERT(node->numMembers() == 5);
auto levels = node->getIntValue(true);
auto iterator = node->getMember(0);
auto variable = static_cast<Variable*>(iterator->getMember(0)->getData());
_buffer->appendText("(function () { return _AQL.AQL_TO_LIST(");
if (node->getBoolValue()) {
_buffer->appendText("_AQL.COMPACT(");
generateCodeNode(node->getMember(0));
_buffer->appendText(")");
}
else {
generateCodeNode(node->getMember(0));
// start LIMIT
auto limitNode = node->getMember(3);
if (limitNode->type != NODE_TYPE_NOP) {
_buffer->appendText("_AQL.AQL_SLICE(");
}
if (node->numMembers() > 2) {
// use a filter expression
_buffer->appendText(").filter(function (v) { ");
generateCodeForcedArray(node->getMember(0), levels);
// FILTER
auto filterNode = node->getMember(2);
if (filterNode->type != NODE_TYPE_NOP) {
_buffer->appendText(".filter(function (v) { ");
_buffer->appendText("vars[\"");
_buffer->appendText(variable->name);
_buffer->appendText("\"]=v; ");
_buffer->appendText("return _AQL.AQL_TO_BOOL(");
generateCodeNode(node->getMember(2));
_buffer->appendText("); }");
generateCodeNode(filterNode);
_buffer->appendText("); })");
}
_buffer->appendText(").map(function (v) { ");
// finish LIMIT
if (limitNode->type != NODE_TYPE_NOP) {
_buffer->appendChar(',');
generateCodeNode(limitNode->getMember(0));
_buffer->appendChar(',');
generateCodeNode(limitNode->getMember(1));
_buffer->appendChar(')');
}
// RETURN
_buffer->appendText(".map(function (v) { ");
_buffer->appendText("vars[\"");
_buffer->appendText(variable->name);
_buffer->appendText("\"]=v; ");
_buffer->appendText("return ");
generateCodeNode(node->getMember(1));
_buffer->appendText("; }); })()");
if (node->getMember(4)->type != NODE_TYPE_NOP) {
generateCodeNode(node->getMember(4));
}
else {
generateCodeNode(node->getMember(1));
}
_buffer->appendText("; })");
}
////////////////////////////////////////////////////////////////////////////////
/// @brief generate JavaScript code for an expansion iterator
////////////////////////////////////////////////////////////////////////////////
void Executor::generateCodeExpandIterator (AstNode const* node) {
void Executor::generateCodeExpansionIterator (AstNode const* node) {
TRI_ASSERT(node != nullptr);
TRI_ASSERT(node->numMembers() == 2);
@ -1064,25 +1134,12 @@ void Executor::generateCodeIndexedAccess (AstNode const* node) {
TRI_ASSERT(node != nullptr);
TRI_ASSERT(node->numMembers() == 2);
auto index = node->getMember(1);
if (index->type == NODE_TYPE_RANGE) {
// range access
_buffer->appendText("_AQL.GET_RANGE(");
generateCodeNode(node->getMember(0));
_buffer->appendChar(',');
generateCodeNode(index->getMember(0));
_buffer->appendChar(',');
generateCodeNode(index->getMember(1));
_buffer->appendChar(')');
}
else {
// indexed access
_buffer->appendText("_AQL.GET_INDEX(");
generateCodeNode(node->getMember(0));
_buffer->appendChar(',');
generateCodeNode(index);
_buffer->appendChar(')');
}
// indexed access
_buffer->appendText("_AQL.GET_INDEX(");
generateCodeNode(node->getMember(0));
_buffer->appendChar(',');
generateCodeNode(node->getMember(1));
_buffer->appendChar(')');
}
////////////////////////////////////////////////////////////////////////////////
@ -1148,13 +1205,13 @@ void Executor::generateCodeNode (AstNode const* node) {
case NODE_TYPE_FCALL_USER:
generateCodeUserFunctionCall(node);
break;
case NODE_TYPE_EXPANSION:
generateCodeExpansion(node);
break;
case NODE_TYPE_ITERATOR:
generateCodeExpandIterator(node);
generateCodeExpansionIterator(node);
break;
case NODE_TYPE_RANGE:
@ -1175,11 +1232,13 @@ void Executor::generateCodeNode (AstNode const* node) {
case NODE_TYPE_VARIABLE:
case NODE_TYPE_PARAMETER:
case NODE_TYPE_PASSTHRU:
case NODE_TYPE_ARRAY_LIMIT:
// we're not expecting these types here
THROW_ARANGO_EXCEPTION_MESSAGE(TRI_ERROR_INTERNAL, "unexpected node type in code generator");
THROW_ARANGO_EXCEPTION_MESSAGE(TRI_ERROR_INTERNAL, "unexpected node type in generateCodeNode");
default:
THROW_ARANGO_EXCEPTION_MESSAGE(TRI_ERROR_NOT_IMPLEMENTED, "node type not implemented");
THROW_ARANGO_EXCEPTION_MESSAGE(TRI_ERROR_NOT_IMPLEMENTED, "node type not implemented in generateCodeNode");
}
}
@ -1195,7 +1254,7 @@ triagens::basics::StringBuffer* Executor::initializeBuffer () {
THROW_ARANGO_EXCEPTION(TRI_ERROR_OUT_OF_MEMORY);
}
_buffer->reserve(256);
_buffer->reserve(512);
}
else {
_buffer->clear();

View File

@ -150,6 +150,13 @@ namespace triagens {
void generateCodeArray (AstNode const*);
////////////////////////////////////////////////////////////////////////////////
/// @brief generate JavaScript code for a forced array
////////////////////////////////////////////////////////////////////////////////
void generateCodeForcedArray (AstNode const*,
int64_t);
////////////////////////////////////////////////////////////////////////////////
/// @brief generate JavaScript code for an object
////////////////////////////////////////////////////////////////////////////////
@ -228,7 +235,7 @@ namespace triagens {
/// @brief generate JavaScript code for an expansion iterator
////////////////////////////////////////////////////////////////////////////////
void generateCodeExpandIterator (AstNode const*);
void generateCodeExpansionIterator (AstNode const*);
////////////////////////////////////////////////////////////////////////////////
/// @brief generate JavaScript code for a range (i.e. 1..10)
@ -308,12 +315,24 @@ namespace triagens {
static std::unordered_map<std::string, Function const> const FunctionNames;
// -----------------------------------------------------------------------------
// --SECTION-- public static variables
// -----------------------------------------------------------------------------
public:
////////////////////////////////////////////////////////////////////////////////
/// @brief minimum number of array members / object attributes for considering
/// an array / object literal "big" and pulling it out of the expression
////////////////////////////////////////////////////////////////////////////////
static size_t const DefaultLiteralSizeThreshold;
////////////////////////////////////////////////////////////////////////////////
/// @brief maxmium number of array members created from range accesses
////////////////////////////////////////////////////////////////////////////////
static int64_t const MaxRangeAccessArraySize;
};
}

View File

@ -281,78 +281,6 @@ void Expression::invalidate () {
// --SECTION-- private methods
// -----------------------------------------------------------------------------
////////////////////////////////////////////////////////////////////////////////
/// @brief extracts a range from an array
////////////////////////////////////////////////////////////////////////////////
AqlValue Expression::extractRange (AqlValue const& result,
AqlValue const& indexResult,
TRI_document_collection_t const* collection,
triagens::arango::AqlTransaction* trx) const {
TRI_ASSERT(result.isArray());
size_t const length = result.arraySize();
if (length == 0) {
// cannot extract anything from an empty array
return AqlValue(new Json(Json::Array));
}
int64_t low = indexResult.getRange()->_low;
int64_t high = indexResult.getRange()->_high;
// negative positions are translated into their positive counterparts
if (low < 0) {
low = static_cast<int64_t>(length) + low;
}
if (high < 0) {
high = static_cast<int64_t>(length) + high;
}
std::unique_ptr<Json> array(new Json(Json::Array));
if (low <= high) {
// forward iteration
++high;
if (low < high) {
if (low < 0) {
low = 0;
}
if (high >= static_cast<int64_t>(length)) {
high = static_cast<int64_t>(length);
}
if (low < high) {
array->reserve(static_cast<size_t>(high - low));
for (int64_t position = low; position < high; ++position) {
auto j = result.extractArrayMember(trx, collection, position, true);
array->add(j);
}
}
}
}
else {
// backward iteration
--high;
if (low >= static_cast<int64_t>(length)) {
low = static_cast<int64_t>(length) - 1;
}
if (high < -1) {
high = -1;
}
if (low - high > 0) {
array->reserve(static_cast<size_t>(low - high));
// array->reserve(static_cast<size_t>(high - low + 1));
for (int64_t position = low; position > high; --position) {
auto j = result.extractArrayMember(trx, collection, position, true);
array->add(j);
}
}
}
return AqlValue(array.release());
}
////////////////////////////////////////////////////////////////////////////////
/// @brief find a value in an AQL list node
/// this performs either a binary search (if the node is sorted) or a
@ -592,19 +520,6 @@ AqlValue Expression::executeSimpleExpression (AstNode const* node,
// no number found.
}
}
else if (indexResult.isRange()) {
try {
auto range = extractRange(result, indexResult, myCollection, trx);
indexResult.destroy();
result.destroy();
return range;
}
catch (...) {
indexResult.destroy();
result.destroy();
throw;
}
}
// fall-through to returning null
}
else if (result.isObject()) {

View File

@ -296,15 +296,6 @@ namespace triagens {
private:
////////////////////////////////////////////////////////////////////////////////
/// @brief extracts a range from an array
////////////////////////////////////////////////////////////////////////////////
AqlValue extractRange (AqlValue const&,
AqlValue const&,
TRI_document_collection_t const*,
triagens::arango::AqlTransaction*) const;
////////////////////////////////////////////////////////////////////////////////
/// @brief find a value in an array
////////////////////////////////////////////////////////////////////////////////

View File

@ -1611,6 +1611,7 @@ int triagens::aql::removeRedundantCalculationsRule (Optimizer* opt,
auto target = outvars[0];
while (target != nullptr) {
auto it = replacements.find(target->id);
if (it != replacements.end()) {
target = (*it).second;
}
@ -1656,7 +1657,6 @@ int triagens::aql::removeRedundantCalculationsRule (Optimizer* opt,
if (! replacements.empty()) {
// finally replace the variables
RedundantCalculationsReplacer finder(replacements);
plan->root()->walk(&finder);
plan->findVarUsage();

View File

@ -268,7 +268,9 @@ Variable const* Scopes::getCurrentVariable () const {
if (_currentVariables.empty()) {
THROW_ARANGO_EXCEPTION_PARAMS(TRI_ERROR_QUERY_VARIABLE_NAME_UNKNOWN, Variable::NAME_CURRENT);
}
return _currentVariables.back();
auto result = _currentVariables.back();
TRI_ASSERT(result != nullptr);
return result;
}
////////////////////////////////////////////////////////////////////////////////

View File

@ -140,10 +140,10 @@ void Aqlerror (YYLTYPE* locp,
%left T_PLUS T_MINUS
%left T_TIMES T_DIV T_MOD
%right UMINUS UPLUS T_NOT
%left EXPANSION
%left FUNCCALL
%left REFERENCE
%left INDEXED
%left EXPANSION
%left T_SCOPE
/* define token return types */
@ -180,6 +180,10 @@ void Aqlerror (YYLTYPE* locp,
%type <node> object_elements_list;
%type <node> object_element;
%type <strval> object_element_name;
%type <intval> array_filter_operator;
%type <node> optional_array_filter;
%type <node> optional_array_limit;
%type <node> optional_array_return;
%type <node> reference;
%type <node> simple_value;
%type <node> value_literal;
@ -971,6 +975,45 @@ object_element:
}
;
array_filter_operator:
T_TIMES {
$$ = 1;
}
| array_filter_operator T_TIMES {
$$ = $1 + 1;
}
;
optional_array_filter:
/* empty */ {
$$ = nullptr;
}
| T_FILTER expression {
$$ = $2;
}
;
optional_array_limit:
/* empty */ {
$$ = nullptr;
}
| T_LIMIT expression {
$$ = parser->ast()->createNodeArrayLimit(nullptr, $2);
}
| T_LIMIT expression T_COMMA expression {
$$ = parser->ast()->createNodeArrayLimit($2, $4);
}
;
optional_array_return:
/* empty */ {
$$ = nullptr;
}
| T_RETURN expression {
$$ = $2;
}
;
reference:
T_STRING {
// variable or collection
@ -1028,7 +1071,14 @@ reference:
}
}
| T_OPEN expression T_CLOSE {
$$ = $2;
if ($2->type == NODE_TYPE_EXPANSION) {
// create a dummy passthru node that reduces and evaluates the expansion first
// and the expansion on top of the stack won't be chained with any other expansions
$$ = parser->ast()->createNodePassthru($2);
}
else {
$$ = $2;
}
}
| T_OPEN {
if (parser->isModificationQuery()) {
@ -1050,8 +1100,11 @@ reference:
// named variable access, e.g. variable.reference
if ($1->type == NODE_TYPE_EXPANSION) {
// if left operand is an expansion already...
// patch the existing expansion
$1->changeMember(1, parser->ast()->createNodeAttributeAccess($1->getMember(1), $3));
// dive into the expansion's right-hand child nodes for further expansion and
// patch the bottom-most one
auto current = const_cast<AstNode*>(parser->ast()->findExpansionSubNode($1));
TRI_ASSERT(current->type == NODE_TYPE_EXPANSION);
current->changeMember(1, parser->ast()->createNodeAttributeAccess(current->getMember(1), $3));
$$ = $1;
}
else {
@ -1063,7 +1116,9 @@ reference:
if ($1->type == NODE_TYPE_EXPANSION) {
// if left operand is an expansion already...
// patch the existing expansion
$1->changeMember(1, parser->ast()->createNodeBoundAttributeAccess($1->getMember(1), $3));
auto current = const_cast<AstNode*>(parser->ast()->findExpansionSubNode($1));
TRI_ASSERT(current->type == NODE_TYPE_EXPANSION);
current->changeMember(1, parser->ast()->createNodeBoundAttributeAccess(current->getMember(1), $3));
$$ = $1;
}
else {
@ -1075,48 +1130,62 @@ reference:
if ($1->type == NODE_TYPE_EXPANSION) {
// if left operand is an expansion already...
// patch the existing expansion
$1->changeMember(1, parser->ast()->createNodeIndexedAccess($1->getMember(1), $3));
auto current = const_cast<AstNode*>(parser->ast()->findExpansionSubNode($1));
TRI_ASSERT(current->type == NODE_TYPE_EXPANSION);
current->changeMember(1, parser->ast()->createNodeIndexedAccess(current->getMember(1), $3));
$$ = $1;
}
else {
$$ = parser->ast()->createNodeIndexedAccess($1, $3);
}
}
| reference T_ARRAY_OPEN T_TIMES T_ARRAY_CLOSE %prec EXPANSION {
// variable expansion, e.g. variable[*]
| reference T_ARRAY_OPEN array_filter_operator {
// variable expansion, e.g. variable[*], with optional FILTER, LIMIT and RETURN clauses
if ($3 > 1 && $1->type == NODE_TYPE_EXPANSION) {
// create a dummy passthru node that reduces and evaluates the expansion first
// and the expansion on top of the stack won't be chained with any other expansions
$1 = parser->ast()->createNodePassthru($1);
}
// create a temporary iterator variable
std::string const nextName = parser->ast()->variables()->nextName() + "_";
char const* iteratorName = nextName.c_str();
auto iterator = parser->ast()->createNodeIterator(iteratorName, $1);
$$ = parser->ast()->createNodeExpansion(iterator, parser->ast()->createNodeReference(iteratorName), false);
}
| reference T_ARRAY_OPEN T_TIMES T_TIMES T_ARRAY_CLOSE %prec EXPANSION {
// variable expansion, e.g. variable[**]
// create a temporary iterator variable
std::string const nextName = parser->ast()->variables()->nextName() + "_";
char const* iteratorName = nextName.c_str();
auto iterator = parser->ast()->createNodeIterator(iteratorName, $1);
$$ = parser->ast()->createNodeExpansion(iterator, parser->ast()->createNodeReference(iteratorName), true);
}
| reference T_ARRAY_OPEN T_FILTER {
// variable expansion, e.g. variable[*]
// create a temporary iterator variable
std::string const nextName = parser->ast()->variables()->nextName() + "_";
char const* iteratorName = nextName.c_str();
auto iterator = parser->ast()->createNodeIterator(iteratorName, $1);
parser->pushStack(iterator);
if ($1->type == NODE_TYPE_EXPANSION) {
auto iterator = parser->ast()->createNodeIterator(iteratorName, $1->getMember(1));
parser->pushStack(iterator);
}
else {
auto iterator = parser->ast()->createNodeIterator(iteratorName, $1);
parser->pushStack(iterator);
}
auto scopes = parser->ast()->scopes();
scopes->stackCurrentVariable(scopes->getVariable(iteratorName));
} expression T_ARRAY_CLOSE %prec EXPANSION {
} optional_array_filter optional_array_limit optional_array_return T_ARRAY_CLOSE %prec EXPANSION {
auto scopes = parser->ast()->scopes();
scopes->unstackCurrentVariable();
auto iterator = static_cast<AstNode*>(parser->popStack());
auto iterator = static_cast<AstNode const*>(parser->popStack());
auto variableNode = iterator->getMember(0);
TRI_ASSERT(variableNode->type == NODE_TYPE_VARIABLE);
auto variable = static_cast<Variable const*>(variableNode->getData());
$$ = parser->ast()->createNodeExpansion(iterator, parser->ast()->createNodeReference(variable->name.c_str()), $5);
if ($1->type == NODE_TYPE_EXPANSION) {
auto expand = parser->ast()->createNodeExpansion($3, iterator, parser->ast()->createNodeReference(variable->name.c_str()), $5, $6, $7);
$1->changeMember(1, expand);
$$ = $1;
/*
auto current = const_cast<AstNode*>(parser->ast()->findExpansionSubNode($1));
TRI_ASSERT(current->type == NODE_TYPE_EXPANSION);
auto expand = parser->ast()->createNodeExpansion($3, iterator, parser->ast()->createNodeReference(variable->name.c_str()), $5, $6, $7);
current->changeMember(1, expand);
$$ = $1;
*/
}
else {
$$ = parser->ast()->createNodeExpansion($3, iterator, parser->ast()->createNodeReference(variable->name.c_str()), $5, $6, $7);
}
}
;

View File

@ -986,85 +986,6 @@ function GET_INDEX (value, index) {
return result;
}
////////////////////////////////////////////////////////////////////////////////
/// @brief get an indexed value from an array or document (e.g. users[3])
////////////////////////////////////////////////////////////////////////////////
function GET_RANGE (value, low, high) {
'use strict';
if (TYPEWEIGHT(value) !== TYPEWEIGHT_ARRAY) {
return null;
}
low = parseInt(low, 10);
high = parseInt(high, 10);
var result = [ ], position;
if (low < 0) {
low = value.length + low;
}
if (high < 0) {
high = value.length + high;
}
if (low <= high) {
++high;
if (low < high) {
if (low < 0) {
low = 0;
}
if (high >= value.length) {
high = value.length;
}
if (low < high) {
for (position = low; position < high; ++position) {
result.push(value[position]);
}
}
}
}
else {
--high;
if (low >= value.length) {
low = value.length - 1;
}
if (high < -1) {
high = -1;
}
if (low - high > 0) {
for (position = low; position > high; --position) {
result.push(value[position]);
}
}
}
return result;
}
function COMPACT (value) {
'use strict';
if (TYPEWEIGHT(value) !== TYPEWEIGHT_ARRAY) {
return [ ];
}
var result = [ ];
for (var i = 0; i < value.length; ++i) {
var v = value[i];
if (TYPEWEIGHT(v) === TYPEWEIGHT_ARRAY) {
for (var j = 0; j < v.length; ++j) {
result.push(v[j]);
}
}
else {
result.push(v);
}
}
return result;
}
////////////////////////////////////////////////////////////////////////////////
/// @brief normalize a value for comparison, sorting etc.
////////////////////////////////////////////////////////////////////////////////
@ -3187,7 +3108,7 @@ function AQL_SHIFT (list) {
}
////////////////////////////////////////////////////////////////////////////////
/// @brief extract a slice from a list
/// @brief extract a slice from an array
////////////////////////////////////////////////////////////////////////////////
function AQL_SLICE (value, from, to) {
@ -8435,7 +8356,6 @@ function AQL_GRAPH_DIAMETER (graphName, options) {
exports.FCALL_USER = FCALL_USER;
exports.KEYS = KEYS;
exports.GET_INDEX = GET_INDEX;
exports.GET_RANGE = GET_RANGE;
exports.DOCUMENT_MEMBER = DOCUMENT_MEMBER;
exports.GET_DOCUMENTS = GET_DOCUMENTS;
exports.TERNARY_OPERATOR = TERNARY_OPERATOR;
@ -8458,7 +8378,6 @@ exports.ARITHMETIC_MINUS = ARITHMETIC_MINUS;
exports.ARITHMETIC_TIMES = ARITHMETIC_TIMES;
exports.ARITHMETIC_DIVIDE = ARITHMETIC_DIVIDE;
exports.ARITHMETIC_MODULUS = ARITHMETIC_MODULUS;
exports.COMPACT = COMPACT;
exports.AQL_DOCUMENT = AQL_DOCUMENT;
exports.AQL_COLLECTIONS = AQL_COLLECTIONS;

View File

@ -35,14 +35,59 @@ var jsunity = require("jsunity");
////////////////////////////////////////////////////////////////////////////////
function arrayAccessTestSuite () {
var values = [
var persons = [
{ name: "sir alfred", age: 60, loves: [ "lettuce", "flowers" ] },
{ person: { name: "gadgetto", age: 50, loves: "gadgets" } },
{ name: "everybody", loves: "sunshine" },
{ name: "judge", loves: [ "order", "policing", "weapons" ] },
"someone"
"something"
];
var continents = [
{
name: "Europe", countries: [
{ id: "UK", capital: "London" },
{ id: "FR", capital: "Paris" },
{ id: "IT", capital: "Rome" }
]
},
{
name: "Asia", countries: [
{ id: "JP", capital: "Tokyo" },
{ id: "CN", capital: "Beijing" },
{ id: "KR", capital: "Seoul" },
{ id: "IN", capital: "Delhi" }
]
}
];
var tags = [
{
values: [
{ name: "javascript", count: 2, payload: [ { name: "foo" }, { name: "bar" } ] },
{ name: "cpp", count: 4, payload: [ { name: "baz" }, { name: "qux" } ] },
{ name: "php", count: 2, payload: [ ] },
{ name: "ruby", count: 5, payload: [ { name: "test" }, { name: 23 } ] },
{ name: "python", count: 3, payload: [ { name: "one" }, { name: "two" }, { name: "three" }, { name: "four" } ] }
]
}
];
var arrayOfArrays = [
[
[ "one", "two", "three" ],
[ "four", "five" ],
[ "six" ]
],
[
[ "seven", "eight" ]
],
[
[ "nine", "ten" ],
[ "eleven", "twelve" ]
]
];
return {
////////////////////////////////////////////////////////////////////////////////
@ -154,296 +199,512 @@ function arrayAccessTestSuite () {
////////////////////////////////////////////////////////////////////////////////
testSubqueryResult : function () {
for (var i = 0; i < values.length; ++i) {
var result = AQL_EXECUTE("RETURN (FOR value IN @values RETURN value)[" + i + "]", { values: values }).json;
assertEqual([ values[i] ], result);
for (var i = 0; i < persons.length; ++i) {
var result = AQL_EXECUTE("RETURN (FOR value IN @persons RETURN value)[" + i + "]", { persons: persons }).json;
assertEqual([ persons[i] ], result);
}
},
////////////////////////////////////////////////////////////////////////////////
/// @brief test range result
////////////////////////////////////////////////////////////////////////////////
testSubqueryResultRangeForward1 : function () {
for (var to = 0; to < 10; ++to) {
var result = AQL_EXECUTE("RETURN (FOR value IN @values RETURN value)[0.." + to + "]", { values: values }).json;
var expected = [ ];
for (var i = 0; i < Math.min(to + 1, values.length); ++i) {
expected.push(values[i]);
}
assertEqual([ expected ], result);
}
testStarExtractScalar : function () {
var expected = [ "Europe", "Asia" ];
var result = AQL_EXECUTE("RETURN @value[*].name", { value : continents }).json;
assertEqual([ expected ], result);
},
////////////////////////////////////////////////////////////////////////////////
/// @brief test range result
////////////////////////////////////////////////////////////////////////////////
testSubqueryResultRangeForward2 : function () {
for (var from = 0; from < 10; ++from) {
var result = AQL_EXECUTE("RETURN (FOR value IN @values RETURN value)[" + from + "..99]", { values: values }).json;
var expected = [ ];
for (var i = Math.min(from, values.length); i < values.length; ++i) {
expected.push(values[i]);
}
assertEqual([ expected ], result);
}
testStarExtractScalarUndefinedAttribute : function () {
var expected = [ null, null ];
var result = AQL_EXECUTE("RETURN @value[*].foobar", { value : continents }).json;
assertEqual([ expected ], result);
},
////////////////////////////////////////////////////////////////////////////////
/// @brief test range result
////////////////////////////////////////////////////////////////////////////////
testSubqueryResultRangeForwardMisc : function () {
var test = function (from, to, v) {
var result = AQL_EXECUTE("RETURN (FOR value IN @values RETURN value)[" + from + ".." + to + "]", { values: values }).json;
var expected = v.map(function(v) { return values[v]; });
assertEqual([ expected ], result);
};
test(0, 0, [ 0 ]);
test(0, 1, [ 0, 1 ]);
test(0, 2, [ 0, 1, 2 ]);
test(0, 3, [ 0, 1, 2, 3 ]);
test(0, 4, [ 0, 1, 2, 3, 4 ]);
test(0, 5, [ 0, 1, 2, 3, 4 ]);
test(0, 6, [ 0, 1, 2, 3, 4 ]);
test(0, 99, [ 0, 1, 2, 3, 4 ]);
test(1, 1, [ 1 ]);
test(1, 2, [ 1, 2 ]);
test(1, 3, [ 1, 2, 3 ]);
test(1, 4, [ 1, 2, 3, 4 ]);
test(1, 5, [ 1, 2, 3, 4 ]);
test(2, 2, [ 2 ]);
test(2, 3, [ 2, 3 ]);
test(4, 4, [ 4 ]);
test(4, 5, [ 4 ]);
test(5, 5, [ ]);
test(5, 6, [ ]);
test(1000, 1000, [ ]);
test(1000000, 1000000, [ ]);
test(0, -1, [ 0, 1, 2, 3, 4 ]);
test(0, -2, [ 0, 1, 2, 3 ]);
test(0, -3, [ 0, 1, 2 ]);
test(0, -4, [ 0, 1 ]);
test(0, -5, [ 0 ]);
test(0, -6, [ 0 ]);
test(0, -7, [ 0 ]);
test(1, 0, [ 1, 0 ]);
test(1, -1, [ 1, 2, 3, 4 ]);
test(1, -2, [ 1, 2, 3 ]);
test(1, -3, [ 1, 2 ]);
test(1, -4, [ 1 ]);
test(1, -5, [ 1, 0 ]);
test(1, -6, [ 1, 0 ]);
test(1, -7, [ 1, 0 ]);
test(2, 0, [ 2, 1, 0 ]);
test(2, 1, [ 2, 1 ]);
test(2, -1, [ 2, 3, 4 ]);
test(2, -2, [ 2, 3 ]);
test(2, -3, [ 2 ]);
test(2, -4, [ 2, 1 ]);
test(2, -5, [ 2, 1, 0 ]);
test(2, -6, [ 2, 1, 0 ]);
test(2, -7, [ 2, 1, 0 ]);
test(3, 0, [ 3, 2, 1, 0 ]);
test(3, 1, [ 3, 2, 1 ]);
test(3, 2, [ 3, 2 ]);
test(3, -1, [ 3, 4 ]);
test(3, -2, [ 3 ]);
test(3, -3, [ 3, 2 ]);
test(3, -4, [ 3, 2, 1 ]);
test(3, -5, [ 3, 2, 1, 0 ]);
test(3, -6, [ 3, 2, 1, 0 ]);
test(4, 0, [ 4, 3, 2, 1, 0 ]);
test(4, 1, [ 4, 3, 2, 1 ]);
test(4, 2, [ 4, 3, 2 ]);
test(4, 3, [ 4, 3 ]);
test(4, -1, [ 4 ]);
test(4, -2, [ 4, 3 ]);
test(4, -3, [ 4, 3, 2 ]);
test(4, -4, [ 4, 3, 2, 1 ]);
test(4, -5, [ 4, 3, 2, 1, 0 ]);
test(4, -6, [ 4, 3, 2, 1, 0 ]);
test(5, 0, [ 4, 3, 2, 1, 0 ]);
test(5, 1, [ 4, 3, 2, 1 ]);
test(5, 2, [ 4, 3, 2 ]);
test(5, 3, [ 4, 3 ]);
test(5, -1, [ 4 ]);
test(5, -2, [ 4, 3 ]);
test(5, -3, [ 4, 3, 2 ]);
test(5, -4, [ 4, 3, 2, 1 ]);
test(5, -5, [ 4, 3, 2, 1, 0 ]);
test(5, -6, [ 4, 3, 2, 1, 0 ]);
test(6, 6, [ ]);
test(6, 7, [ ]);
test(7, 6, [ ]);
test(10, 10, [ ]);
test(100, 100, [ ]);
test(100, 1000, [ ]);
test(1000, 100, [ ]);
test(-6, -6, [ ]);
test(-6, -7, [ ]);
test(-7, -6, [ ]);
test(-10, -10, [ ]);
test(-100, -100, [ ]);
test(-100, -1000, [ ]);
test(-1000, -100, [ ]);
testStarExtractScalarWithFilter : function () {
var expected = [ "Europe" ];
var result = AQL_EXECUTE("RETURN @value[* FILTER CURRENT.name == 'Europe'].name", { value : continents }).json;
assertEqual([ expected ], result);
},
////////////////////////////////////////////////////////////////////////////////
/// @brief test range result
////////////////////////////////////////////////////////////////////////////////
testV8SubqueryResultRangeForward1 : function () {
for (var to = 0; to < 10; ++to) {
var result = AQL_EXECUTE("RETURN NOOPT(V8(FOR value IN @values RETURN value))[0.." + to + "]", { values: values }).json;
var expected = [ ];
for (var i = 0; i < Math.min(to + 1, values.length); ++i) {
expected.push(values[i]);
}
assertEqual([ expected ], result);
}
testStarExtractScalarWithNonMatchingFilter : function () {
var expected = [ ];
var result = AQL_EXECUTE("RETURN @value[* FILTER CURRENT.name == 'foobar'].name", { value : continents }).json;
assertEqual([ expected ], result);
},
testStarExtractArray : function () {
var expected = [
[
{ id: "UK", capital: "London" },
{ id: "FR", capital: "Paris" },
{ id: "IT", capital: "Rome" }
],
[
{ id: "JP", capital: "Tokyo" },
{ id: "CN", capital: "Beijing" },
{ id: "KR", capital: "Seoul" },
{ id: "IN", capital: "Delhi" }
]
];
var result = AQL_EXECUTE("RETURN @value[*].countries", { value : continents }).json;
assertEqual([ expected ], result);
},
////////////////////////////////////////////////////////////////////////////////
/// @brief test range result
////////////////////////////////////////////////////////////////////////////////
testV8SubqueryResultRangeForward2 : function () {
for (var from = 0; from < 10; ++from) {
var result = AQL_EXECUTE("RETURN NOOPT(V8(FOR value IN @values RETURN value))[" + from + "..99]", { values: values }).json;
var expected = [ ];
for (var i = Math.min(from, values.length); i < values.length; ++i) {
expected.push(values[i]);
}
assertEqual([ expected ], result);
}
testStarExtractArrayWithFilter : function () {
var expected = [ continents[0].countries ];
var result = AQL_EXECUTE("RETURN @value[* FILTER CURRENT.name == 'Europe'].countries", { value : continents }).json;
assertEqual([ expected ], result);
},
////////////////////////////////////////////////////////////////////////////////
/// @brief test range result
////////////////////////////////////////////////////////////////////////////////
testStarExtractSubArrayIndexed1 : function () {
var expected = [
{ id: "UK", capital: "London" },
{ id: "JP", capital: "Tokyo" }
];
var result = AQL_EXECUTE("RETURN @value[*].countries[0]", { value : continents }).json;
assertEqual([ expected ], result);
},
testV8SubqueryResultRangeForwardMisc : function () {
var test = function (from, to, v) {
var result = AQL_EXECUTE("RETURN NOOPT(V8(FOR value IN @values RETURN value))[" + from + ".." + to + "]", { values: values }).json;
var expected = v.map(function(v) { return values[v]; });
assertEqual([ expected ], result);
};
testStarExtractSubArrayIndexed2 : function () {
var expected = [
{ id: "IT", capital: "Rome" },
{ id: "IN", capital: "Delhi" }
];
var result = AQL_EXECUTE("RETURN @value[*].countries[-1]", { value : continents }).json;
assertEqual([ expected ], result);
},
test(0, 0, [ 0 ]);
test(0, 1, [ 0, 1 ]);
test(0, 2, [ 0, 1, 2 ]);
test(0, 3, [ 0, 1, 2, 3 ]);
test(0, 4, [ 0, 1, 2, 3, 4 ]);
test(0, 5, [ 0, 1, 2, 3, 4 ]);
test(0, 6, [ 0, 1, 2, 3, 4 ]);
test(0, 99, [ 0, 1, 2, 3, 4 ]);
testStarExtractSubArrayIndexed3 : function () {
var expected = [
null,
null
];
var result = AQL_EXECUTE("RETURN @value[*].countries[99]", { value : continents }).json;
assertEqual([ expected ], result);
},
test(1, 1, [ 1 ]);
test(1, 2, [ 1, 2 ]);
test(1, 3, [ 1, 2, 3 ]);
test(1, 4, [ 1, 2, 3, 4 ]);
test(1, 5, [ 1, 2, 3, 4 ]);
testStarExtractSubArrayIndexedTotal1 : function () {
var expected = [
{ id: "UK", capital: "London" },
{ id: "FR", capital: "Paris" },
{ id: "IT", capital: "Rome" }
];
var result = AQL_EXECUTE("RETURN (@value[*].countries)[0]", { value : continents }).json;
assertEqual([ expected ], result);
},
test(2, 2, [ 2 ]);
test(2, 3, [ 2, 3 ]);
testStarExtractSubArrayIndexedTotal2 : function () {
var expected = [
{ id: "JP", capital: "Tokyo" },
{ id: "CN", capital: "Beijing" },
{ id: "KR", capital: "Seoul" },
{ id: "IN", capital: "Delhi" }
];
var result = AQL_EXECUTE("RETURN (@value[*].countries)[-1]", { value : continents }).json;
assertEqual([ expected ], result);
},
test(4, 4, [ 4 ]);
test(4, 5, [ 4 ]);
testStarExtractSubAttribute1 : function () {
var expected = [
[ "UK", "FR", "IT" ],
[ "JP", "CN", "KR", "IN" ]
];
var result = AQL_EXECUTE("RETURN @value[*].countries[*].id", { value : continents }).json;
assertEqual([ expected ], result);
},
test(5, 5, [ ]);
test(5, 6, [ ]);
test(1000, 1000, [ ]);
test(1000000, 1000000, [ ]);
test(0, -1, [ 0, 1, 2, 3, 4 ]);
test(0, -2, [ 0, 1, 2, 3 ]);
test(0, -3, [ 0, 1, 2 ]);
test(0, -4, [ 0, 1 ]);
test(0, -5, [ 0 ]);
test(0, -6, [ 0 ]);
test(0, -7, [ 0 ]);
testStarExtractSubAttribute2 : function () {
var expected = [
[ "London", "Paris", "Rome" ],
[ "Tokyo", "Beijing", "Seoul", "Delhi" ]
];
var result = AQL_EXECUTE("RETURN @value[*].countries[*].capital", { value : continents }).json;
assertEqual([ expected ], result);
},
test(1, 0, [ 1, 0 ]);
test(1, -1, [ 1, 2, 3, 4 ]);
test(1, -2, [ 1, 2, 3 ]);
test(1, -3, [ 1, 2 ]);
test(1, -4, [ 1 ]);
test(1, -5, [ 1, 0 ]);
test(1, -6, [ 1, 0 ]);
test(1, -7, [ 1, 0 ]);
testStarExtractSubAttributeExtraOperator1 : function () {
var expected = [
[ "UK", "FR", "IT" ],
[ "JP", "CN", "KR", "IN" ]
];
var result = AQL_EXECUTE("RETURN @value[*].countries[*].id[*]", { value : continents }).json;
assertEqual([ expected ], result);
},
test(2, 0, [ 2, 1, 0 ]);
test(2, 1, [ 2, 1 ]);
test(2, -1, [ 2, 3, 4 ]);
test(2, -2, [ 2, 3 ]);
test(2, -3, [ 2 ]);
test(2, -4, [ 2, 1 ]);
test(2, -5, [ 2, 1, 0 ]);
test(2, -6, [ 2, 1, 0 ]);
test(2, -7, [ 2, 1, 0 ]);
testStarExtractSubAttributeExtraOperator2 : function () {
var expected = [
[ "London", "Paris", "Rome" ],
[ "Tokyo", "Beijing", "Seoul", "Delhi" ]
];
var result = AQL_EXECUTE("RETURN @value[*].countries[*].capital[*]", { value : continents }).json;
assertEqual([ expected ], result);
},
test(3, 0, [ 3, 2, 1, 0 ]);
test(3, 1, [ 3, 2, 1 ]);
test(3, 2, [ 3, 2 ]);
test(3, -1, [ 3, 4 ]);
test(3, -2, [ 3 ]);
test(3, -3, [ 3, 2 ]);
test(3, -4, [ 3, 2, 1 ]);
test(3, -5, [ 3, 2, 1, 0 ]);
test(3, -6, [ 3, 2, 1, 0 ]);
testStarExtractSubAttribute1Combine : function () {
var expected = [
"UK", "FR", "IT", "JP", "CN", "KR", "IN"
];
var result = AQL_EXECUTE("RETURN @value[*].countries[**].id", { value : continents }).json;
assertEqual([ expected ], result);
},
test(4, 0, [ 4, 3, 2, 1, 0 ]);
test(4, 1, [ 4, 3, 2, 1 ]);
test(4, 2, [ 4, 3, 2 ]);
test(4, 3, [ 4, 3 ]);
test(4, -1, [ 4 ]);
test(4, -2, [ 4, 3 ]);
test(4, -3, [ 4, 3, 2 ]);
test(4, -4, [ 4, 3, 2, 1 ]);
test(4, -5, [ 4, 3, 2, 1, 0 ]);
test(4, -6, [ 4, 3, 2, 1, 0 ]);
testStarExtractSubAttribute2Combine : function () {
var expected = [
"London", "Paris", "Rome", "Tokyo", "Beijing", "Seoul", "Delhi"
];
var result = AQL_EXECUTE("RETURN @value[*].countries[**].capital", { value : continents }).json;
assertEqual([ expected ], result);
},
test(5, 0, [ 4, 3, 2, 1, 0 ]);
test(5, 1, [ 4, 3, 2, 1 ]);
test(5, 2, [ 4, 3, 2 ]);
test(5, 3, [ 4, 3 ]);
test(5, -1, [ 4 ]);
test(5, -2, [ 4, 3 ]);
test(5, -3, [ 4, 3, 2 ]);
test(5, -4, [ 4, 3, 2, 1 ]);
test(5, -5, [ 4, 3, 2, 1, 0 ]);
test(5, -6, [ 4, 3, 2, 1, 0 ]);
test(6, 6, [ ]);
test(6, 7, [ ]);
test(7, 6, [ ]);
test(10, 10, [ ]);
test(100, 100, [ ]);
test(100, 1000, [ ]);
test(1000, 100, [ ]);
testStarExtractSubAttribute3Combine : function () {
var expected = [
"London", "Paris", "Rome", "Tokyo", "Beijing", "Seoul", "Delhi"
];
// more than 2 asterisks won't change the result
var result = AQL_EXECUTE("RETURN @value[*].countries[****].capital", { value : continents }).json;
assertEqual([ expected ], result);
},
test(-6, -6, [ ]);
test(-6, -7, [ ]);
test(-7, -6, [ ]);
test(-10, -10, [ ]);
test(-100, -100, [ ]);
test(-100, -1000, [ ]);
test(-1000, -100, [ ]);
testStarExtractSubAttributeNonExisting : function () {
var expected = [
[ null, null, null ],
[ null, null, null, null ]
];
var result = AQL_EXECUTE("RETURN @value[*].countries[*].id.foobar", { value : continents }).json;
assertEqual([ expected ], result);
},
testStarExtractSubAttributeIndexed1 : function () {
var expected = [
[ null, null, null ],
[ null, null, null, null ]
];
var result = AQL_EXECUTE("RETURN @value[*].countries[*].id[0]", { value : continents }).json;
assertEqual([ expected ], result);
},
testStarExtractSubAttributeIndexed2 : function () {
var expected = [
[ null, null, null ],
[ null, null, null, null ]
];
var result = AQL_EXECUTE("RETURN @value[*].countries[*].id[1]", { value : continents }).json;
assertEqual([ expected ], result);
},
testStarExtractSubAttributeIndexedTotal1 : function () {
var expected = [
"UK", "FR", "IT"
];
var result = AQL_EXECUTE("RETURN (@value[*].countries[*].id)[0]", { value : continents }).json;
assertEqual([ expected ], result);
},
testStarExtractSubAttributeIndexedTotal2 : function () {
var expected = [
"JP", "CN", "KR", "IN"
];
var result = AQL_EXECUTE("RETURN (@value[*].countries[*].id)[1]", { value : continents }).json;
assertEqual([ expected ], result);
},
testStarExtractFiltered1 : function () {
var expected = [
"javascript", "php"
];
var result = AQL_EXECUTE("RETURN @value[0].values[* FILTER CURRENT.count == 2].name", { value : tags }).json;
assertEqual([ expected ], result);
},
testStarExtractFiltered2 : function () {
var expected = [
"cpp", "ruby", "python"
];
var result = AQL_EXECUTE("RETURN @value[0].values[* FILTER CURRENT.count != 2].name", { value : tags }).json;
assertEqual([ expected ], result);
},
testStarExtractFiltered3 : function () {
var expected = [
"ruby"
];
var result = AQL_EXECUTE("RETURN @value[0].values[* FILTER CURRENT.count != 2 && CURRENT.name == 'ruby'].name", { value : tags }).json;
assertEqual([ expected ], result);
},
testStarExtractFiltered4 : function () {
var expected = [
3
];
var result = AQL_EXECUTE("RETURN @value[0].values[* FILTER LENGTH(CURRENT.payload) == 4].count", { value : tags }).json;
assertEqual([ expected ], result);
},
testStarExtractFiltered5 : function () {
var expected = [
"php", "ruby", "python"
];
var result = AQL_EXECUTE("RETURN @value[0].values[* FILTER CURRENT.name IN [ 'php', 'python', 'ruby' ]].name", { value : tags }).json;
assertEqual([ expected ], result);
},
testStarExtractFiltered6 : function () {
var expected = [
[ "javascript", "php" ]
];
var result = AQL_EXECUTE("RETURN @value[*].values[* FILTER CURRENT.count == 2].name", { value : tags }).json;
assertEqual([ expected ], result);
},
testStarExtractFiltered7 : function () {
var expected = [
[ "cpp", "ruby", "python" ]
];
var result = AQL_EXECUTE("RETURN @value[*].values[* FILTER CURRENT.count != 2].name", { value : tags }).json;
assertEqual([ expected ], result);
},
testStarExtractFiltered8 : function () {
var expected = [
[ "ruby" ]
];
var result = AQL_EXECUTE("RETURN @value[*].values[* FILTER CURRENT.count != 2 && CURRENT.name == 'ruby'].name", { value : tags }).json;
assertEqual([ expected ], result);
},
testStarExtractFiltered9 : function () {
var expected = [
[ 3 ]
];
var result = AQL_EXECUTE("RETURN @value[*].values[* FILTER LENGTH(CURRENT.payload) == 4].count", { value : tags }).json;
assertEqual([ expected ], result);
},
testStarExtractFiltered10 : function () {
var expected = [
[ "php", "ruby", "python" ]
];
var result = AQL_EXECUTE("RETURN @value[*].values[* FILTER CURRENT.name IN [ 'php', 'python', 'ruby' ]].name", { value : tags }).json;
assertEqual([ expected ], result);
},
testStarExtractProject1 : function () {
var expected = [
2, 2, 0, 2, 4
];
var result = AQL_EXECUTE("RETURN @value[0].values[* RETURN LENGTH(CURRENT.payload)][**]", { value : tags }).json;
assertEqual([ expected ], result);
},
testStarExtractProject2 : function () {
var expected = [
"x-javascript", "x-cpp", "x-php", "x-ruby", "x-python"
];
var result = AQL_EXECUTE("RETURN @value[0].values[*].name[* RETURN CONCAT('x-', CURRENT)][**]", { value : tags }).json;
assertEqual([ expected ], result);
},
testStarExtractProjectFilter : function () {
var expected = [
"x-cpp", "x-ruby", "x-python"
];
var result = AQL_EXECUTE("RETURN @value[0].values[* FILTER CURRENT.count >= 3].name[* RETURN CONCAT('x-', CURRENT)][**]", { value : tags }).json;
assertEqual([ expected ], result);
},
testStarExtractLimitEmpty1 : function () {
var expected = [
];
var result = AQL_EXECUTE("RETURN @value[0].values[*].name[** LIMIT 0]", { value : tags }).json;
assertEqual([ expected ], result);
},
testStarExtractLimitEmpty2 : function () {
var expected = [
];
var result = AQL_EXECUTE("RETURN @value[0].values[* LIMIT 0].name", { value : tags }).json;
assertEqual([ expected ], result);
},
testStarExtractLimitEmpty3 : function () {
var expected = [
];
var result = AQL_EXECUTE("RETURN @value[0].values[* LIMIT -10, 1].name", { value : tags }).json;
assertEqual([ expected ], result);
},
testStarExtractLimit0 : function () {
var expected = [
"javascript"
];
var result = AQL_EXECUTE("RETURN @value[0].values[* LIMIT 1].name", { value : tags }).json;
assertEqual([ expected ], result);
},
testStarExtractLimit1 : function () {
var expected = [
"javascript"
];
var result = AQL_EXECUTE("RETURN @value[0].values[*].name[** LIMIT 1]", { value : tags }).json;
assertEqual([ expected ], result);
},
testStarExtractLimit2 : function () {
var expected = [
"javascript"
];
var result = AQL_EXECUTE("RETURN @value[0].values[*].name[** LIMIT 0, 1]", { value : tags }).json;
assertEqual([ expected ], result);
},
testStarExtractLimit3 : function () {
var expected = [
"javascript", "cpp", "php", "ruby", "python"
];
var result = AQL_EXECUTE("RETURN @value[0].values[*].name[** LIMIT 0, 10]", { value : tags }).json;
assertEqual([ expected ], result);
},
testStarExtractLimit4 : function () {
var expected = [
"javascript", "cpp", "php", "ruby"
];
var result = AQL_EXECUTE("RETURN @value[0].values[*].name[** LIMIT 0, 4]", { value : tags }).json;
assertEqual([ expected ], result);
},
testStarExtractLimit5 : function () {
var expected = [
"javascript", "cpp"
];
var result = AQL_EXECUTE("RETURN @value[0].values[*].name[** LIMIT 0, 2]", { value : tags }).json;
assertEqual([ expected ], result);
},
testStarExtractLimit6 : function () {
var expected = [
"cpp", "php", "ruby", "python"
];
var result = AQL_EXECUTE("RETURN @value[0].values[*].name[** LIMIT 1, 4]", { value : tags }).json;
assertEqual([ expected ], result);
},
testStarExtractLimit7 : function () {
var expected = [
"ruby", "python"
];
var result = AQL_EXECUTE("RETURN @value[0].values[*].name[** LIMIT 3, 4]", { value : tags }).json;
assertEqual([ expected ], result);
},
testStarExtractLimit8 : function () {
var expected = [
"javascript", "cpp"
];
var result = AQL_EXECUTE("RETURN @value[0].values[* FILTER CURRENT.name IN [ 'javascript', 'php', 'cpp' ] LIMIT 0, 2].name[**]", { value : tags }).json;
assertEqual([ expected ], result);
},
testStarExtractLimitProject1 : function () {
var expected = [
"javascript-x", "cpp-x"
];
var result = AQL_EXECUTE("RETURN @value[0].values[** LIMIT 0, 2 RETURN CONCAT(CURRENT.name, '-x')]", { value : tags }).json;
assertEqual([ expected ], result);
},
testStarExtractLimitProject2 : function () {
var expected = [
"javascript", "cpp"
];
var result = AQL_EXECUTE("RETURN @value[0].values[* FILTER CURRENT.name IN [ 'javascript', 'php', 'cpp' ] LIMIT 0, 2 RETURN CURRENT.name]", { value : tags }).json;
assertEqual([ expected ], result);
},
testCollapse1 : function () {
var expected = arrayOfArrays;
var result = AQL_EXECUTE("RETURN @value[*]", { value : arrayOfArrays }).json;
assertEqual([ expected ], result);
},
testCollapse2 : function () {
var expected = [ [ "one", "two", "three" ], [ "four", "five" ], [ "six" ], [ "seven", "eight" ], [ "nine", "ten" ], [ "eleven", "twelve" ] ];
var result = AQL_EXECUTE("RETURN @value[**]", { value : arrayOfArrays }).json;
assertEqual([ expected ], result);
},
testCollapse3 : function () {
var expected = [ "one", "two", "three", "four", "five", "six", "seven", "eight", "nine", "ten", "eleven", "twelve" ];
var result = AQL_EXECUTE("RETURN @value[***]", { value : arrayOfArrays }).json;
assertEqual([ expected ], result);
},
testCollapseUnmodified : function () {
var data = [ [ [ [ 1, 2 ] ], [ 3, 4 ], 5, [ 6, 7 ] ], [ 8 ] ];
var expected = data;
var result = AQL_EXECUTE("RETURN @value", { value : data }).json;
assertEqual([ expected ], result);
},
testCollapseMixed1 : function () {
var data = [ [ [ [ 1, 2 ] ], [ 3, 4 ], 5, [ 6, 7 ] ], [ 8 ] ];
var expected = data;
var result = AQL_EXECUTE("RETURN @value[*]", { value : data }).json;
assertEqual([ expected ], result);
},
testCollapseMixed2 : function () {
var data = [ [ [ [ 1, 2 ] ], [ 3, 4 ], 5, [ 6, 7 ] ], [ 8 ] ];
var expected = [ [ [ 1, 2 ] ], [ 3, 4 ], 5, [ 6, 7 ], 8 ];
var result = AQL_EXECUTE("RETURN @value[**]", { value : data }).json;
assertEqual([ expected ], result);
},
testCollapseMixed3 : function () {
var data = [ [ [ [ 1, 2 ] ], [ 3, 4 ], 5, [ 6, 7 ] ], [ 8 ] ];
var expected = [ [ 1, 2 ], 3, 4, 5, 6, 7, 8 ];
var result = AQL_EXECUTE("RETURN @value[***]", { value : data }).json;
assertEqual([ expected ], result);
},
testCollapseMixed4 : function () {
var data = [ [ [ [ 1, 2 ] ], [ 3, 4 ], 5, [ 6, 7 ] ], [ 8 ] ];
var expected = [ 1, 2, 3, 4, 5, 6, 7, 8 ];
var result = AQL_EXECUTE("RETURN @value[****]", { value : data }).json;
assertEqual([ expected ], result);
},
testCollapseMixed5 : function () {
var data = [ [ [ [ 1, 2 ] ], [ 3, 4 ], 5, [ 6, 7 ] ], [ 8 ] ];
var expected = [ 1, 2, 3, 4, 5, 6, 7, 8 ];
var result = AQL_EXECUTE("RETURN @value[*****]", { value : data }).json;
assertEqual([ expected ], result);
},
testCollapseFilter : function () {
var data = [ 1, 2, 3, 4, 5, 6, 7, 8 ];
var expected = [ 1, 3, 5, 7 ];
var result = AQL_EXECUTE("RETURN @value[**** FILTER CURRENT % 2 != 0]", { value : data }).json;
assertEqual([ expected ], result);
},
testCollapseProject : function () {
var data = [ 1, 2, 3, 4, 5, 6, 7, 8 ];
var expected = [ 2, 4, 6, 8, 10, 12, 14, 16 ];
var result = AQL_EXECUTE("RETURN @value[**** RETURN CURRENT * 2]", { value : data }).json;
assertEqual([ expected ], result);
},
testCollapseFilterProject : function () {
var data = [ 1, 2, 3, 4, 5, 6, 7, 8 ];
var expected = [ 4, 8, 12, 16 ];
var result = AQL_EXECUTE("RETURN @value[**** FILTER CURRENT % 2 == 0 RETURN CURRENT * 2]", { value : data }).json;
assertEqual([ expected ], result);
},
testCollapseFilterProjectLimit : function () {
var data = [ 1, 2, 3, 4, 5, 6, 7, 8 ];
var expected = [ 4, 8, 12 ];
var result = AQL_EXECUTE("RETURN @value[**** FILTER CURRENT % 2 == 0 LIMIT 3 RETURN CURRENT * 2]", { value : data }).json;
assertEqual([ expected ], result);
}
};

View File

@ -103,7 +103,7 @@ function optimizerRuleTestSuite () {
"whether the execution of '" + query +
"' this plan gave the wrong results: " + JSON.stringify(allresults.plans[j]) +
" Should be: '" + JSON.stringify(allresults.results[0]) +
"' but Is: " + JSON.stringify(allresults.results[j]) + "'"
"' but is: " + JSON.stringify(allresults.results[j]) + "'"
);
}
}
@ -136,7 +136,7 @@ function optimizerRuleTestSuite () {
"whether the execution of '" + query +
"' this plan gave the wrong results: " + JSON.stringify(allresults.plans[j]) +
" Should be: '" + JSON.stringify(allresults.results[0]) +
"' but Is: " + JSON.stringify(allresults.results[j]) + "'"
"' but is: " + JSON.stringify(allresults.results[j]) + "'"
);
}
});
@ -173,7 +173,7 @@ function optimizerRuleTestSuite () {
"whether the execution of '" + query +
"' this plan gave the wrong results: " + JSON.stringify(allresults.plans[j]) +
" Should be: '" + JSON.stringify(allresults.results[0]) +
"' but Is: " + JSON.stringify(allresults.results[j]) + "'"
"' but is: " + JSON.stringify(allresults.results[j]) + "'"
);
}
},
@ -215,7 +215,7 @@ function optimizerRuleTestSuite () {
"whether the execution of '" + query[0] +
"' this plan gave the wrong results: " + JSON.stringify(allresults.plans[j]) +
" Should be: '" + JSON.stringify(allresults.results[0]) +
"' but Is: " + JSON.stringify(allresults.results[j]) + "'"
"' but is: " + JSON.stringify(allresults.results[j]) + "'"
);
}
});

View File

@ -163,7 +163,7 @@ function ahuacatlQueryVariablesTestSuite () {
////////////////////////////////////////////////////////////////////////////////
testListExpansion6 : function () {
var query = "FOR a IN " + JSON.stringify(airports) + " RETURN a.continent.countries[*].airports[*][0].name";
var query = "FOR a IN " + JSON.stringify(airports) + " RETURN a.continent.countries[*].airports[0].name";
var expected = [["CGN", "LHR", "CDG"], ["LGA", "YTO", "BOG"], ["NRT"]];
var actual = getQueryResults(query);
@ -175,7 +175,7 @@ function ahuacatlQueryVariablesTestSuite () {
////////////////////////////////////////////////////////////////////////////////
testListExpansion7 : function () {
var query = "FOR a IN " + JSON.stringify(airports) + " RETURN a.continent.countries[*].airports[*][1].name";
var query = "FOR a IN " + JSON.stringify(airports) + " RETURN a.continent.countries[*].airports[1].name";
var expected = [["DTM", "LGW", "ORY"], ["JFK", "YVR", null], ["HND"]];
var actual = getQueryResults(query);