From dc8fdf8ca010b5de60c2f85ec2b5e2aa97667c86 Mon Sep 17 00:00:00 2001 From: Jan Steemann Date: Fri, 10 Jan 2014 16:42:46 +0100 Subject: [PATCH] defer evaluation of subqueries --- CHANGELOG | 2 + arangod/Ahuacatl/ahuacatl-codegen.c | 413 +++++++++++------- arangod/Ahuacatl/ahuacatl-codegen.h | 3 +- arangod/Ahuacatl/ahuacatl-explain.c | 3 +- arangod/Ahuacatl/ahuacatl-node.c | 3 + arangod/Ahuacatl/ahuacatl-node.h | 1 + arangod/Ahuacatl/ahuacatl-statementlist.c | 2 + .../tests/ahuacatl-queries-noncollection.js | 91 ++++ 8 files changed, 352 insertions(+), 166 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index ba85803172..c115686455 100755 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,6 +1,8 @@ v1.4.5-rc1 (XXXX-XX-XX) ------------------- +* defer evaluation of AQL subqueries and logical operators (lazy evaluation) + * generally allow function return values as call parameters to AQL functions * fixed potential deadlock in global context method execution diff --git a/arangod/Ahuacatl/ahuacatl-codegen.c b/arangod/Ahuacatl/ahuacatl-codegen.c index 619c59ead3..365eb4073e 100644 --- a/arangod/Ahuacatl/ahuacatl-codegen.c +++ b/arangod/Ahuacatl/ahuacatl-codegen.c @@ -94,6 +94,124 @@ static void ProcessNode (TRI_aql_codegen_js_t* const, /// @{ //////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +/// @brief free a scope variable +//////////////////////////////////////////////////////////////////////////////// + +static void FreeVariable (TRI_aql_codegen_variable_t* const variable) { + assert(variable); + assert(variable->_name); + + TRI_Free(TRI_UNKNOWN_MEM_ZONE, variable->_name); + TRI_Free(TRI_UNKNOWN_MEM_ZONE, variable); +} + +//////////////////////////////////////////////////////////////////////////////// +/// @brief create a scope variable +//////////////////////////////////////////////////////////////////////////////// + +static TRI_aql_codegen_variable_t* CreateVariable (const char* const name, + const TRI_aql_codegen_register_t registerIndex, + bool isCachedSubquery) { + TRI_aql_codegen_variable_t* variable; + + variable = (TRI_aql_codegen_variable_t*) TRI_Allocate(TRI_UNKNOWN_MEM_ZONE, sizeof(TRI_aql_codegen_variable_t), false); + + if (variable == NULL) { + return NULL; + } + + variable->_register = registerIndex; + variable->_name = TRI_DuplicateStringZ(TRI_UNKNOWN_MEM_ZONE, name); + variable->_isCachedSubquery = isCachedSubquery; + + if (variable->_name == NULL) { + TRI_Free(TRI_UNKNOWN_MEM_ZONE, variable); + + return NULL; + } + + return variable; +} + +//////////////////////////////////////////////////////////////////////////////// +/// @brief get the current scope +//////////////////////////////////////////////////////////////////////////////// + +static inline TRI_aql_codegen_scope_t* CurrentScope (TRI_aql_codegen_js_t* const generator) { + size_t n; + + if (generator->_errorCode != TRI_ERROR_NO_ERROR) { + return NULL; + } + + n = generator->_scopes._length; + if (n == 0) { + generator->_errorCode = TRI_ERROR_OUT_OF_MEMORY; + return NULL; + } + + return (TRI_aql_codegen_scope_t*) TRI_AtVectorPointer(&generator->_scopes, n - 1); +} + +//////////////////////////////////////////////////////////////////////////////// +/// @brief enter a variable into the symbol table +//////////////////////////////////////////////////////////////////////////////// + +static void EnterSymbol (TRI_aql_codegen_js_t* const generator, + const char* const name, + const TRI_aql_codegen_register_t registerIndex, + bool isCachedSubquery) { + TRI_aql_codegen_scope_t* scope = CurrentScope(generator); + TRI_aql_codegen_variable_t* variable; + + if (scope == NULL) { + generator->_errorCode = TRI_ERROR_OUT_OF_MEMORY; + return; + } + + variable = CreateVariable(name, registerIndex, isCachedSubquery); + + if (variable == NULL) { + generator->_errorCode = TRI_ERROR_OUT_OF_MEMORY; + return; + } + + if (TRI_InsertKeyAssociativePointer(&scope->_variables, name, (void*) variable, false) != NULL) { + // variable already exists in symbol table. this should never happen + LOG_TRACE("variable already registered: %s", name); + generator->_errorCode = TRI_ERROR_QUERY_VARIABLE_REDECLARED; + generator->_errorValue = (char*) name; + + FreeVariable(variable); + } +} + +//////////////////////////////////////////////////////////////////////////////// +/// @brief look up a variable in the symbol table +//////////////////////////////////////////////////////////////////////////////// + +static TRI_aql_codegen_variable_t* LookupVariable (TRI_aql_codegen_js_t* const generator, + const char* const name) { + size_t i = generator->_scopes._length; + + assert(name); + + // iterate from current scope to the top level scope + while (i-- > 0) { + TRI_aql_codegen_scope_t* scope = (TRI_aql_codegen_scope_t*) generator->_scopes._buffer[i]; + TRI_aql_codegen_variable_t* variable; + + variable = (TRI_aql_codegen_variable_t*) TRI_LookupByKeyAssociativePointer(&scope->_variables, name); + // variable found in scope + if (variable != NULL) { + return variable; + } + } + + return NULL; +} + //////////////////////////////////////////////////////////////////////////////// /// @brief hash a variable //////////////////////////////////////////////////////////////////////////////// @@ -133,26 +251,6 @@ static inline TRI_aql_codegen_register_t IncFunction (TRI_aql_codegen_js_t* cons return ++generator->_functionIndex; } -//////////////////////////////////////////////////////////////////////////////// -/// @brief get the current scope -//////////////////////////////////////////////////////////////////////////////// - -static inline TRI_aql_codegen_scope_t* CurrentScope (TRI_aql_codegen_js_t* const generator) { - size_t n; - - if (generator->_errorCode != TRI_ERROR_NO_ERROR) { - return NULL; - } - - n = generator->_scopes._length; - if (n == 0) { - generator->_errorCode = TRI_ERROR_OUT_OF_MEMORY; - return NULL; - } - - return (TRI_aql_codegen_scope_t*) TRI_AtVectorPointer(&generator->_scopes, n - 1); -} - //////////////////////////////////////////////////////////////////////////////// /// @brief append a string to the buffer //////////////////////////////////////////////////////////////////////////////// @@ -387,6 +485,41 @@ static inline void ScopeOutputRegister (TRI_aql_codegen_js_t* const generator, } } +//////////////////////////////////////////////////////////////////////////////// +/// @brief print a register name in the current scope +//////////////////////////////////////////////////////////////////////////////// + +static void ScopeOutputSymbol (TRI_aql_codegen_js_t* const generator, + char const* name) { + TRI_aql_codegen_scope_t* scope = CurrentScope(generator); + TRI_aql_codegen_variable_t* variable; + + if (scope == NULL) { + return; + } + + variable = LookupVariable(generator, name); + + if (variable == NULL) { + generator->_errorCode = TRI_ERROR_QUERY_VARIABLE_NAME_UNKNOWN; + generator->_errorValue = (char*) name; + return; + } + + if (variable->_isCachedSubquery) { + ScopeOutput(generator, "("); + ScopeOutputRegister(generator, variable->_register); + ScopeOutput(generator, ".executed ? "); + ScopeOutputRegister(generator, variable->_register); + ScopeOutput(generator, ".data : "); + ScopeOutputRegister(generator, variable->_register); + ScopeOutput(generator, ".execute())"); + } + else { + ScopeOutputRegister(generator, variable->_register); + } +} + //////////////////////////////////////////////////////////////////////////////// /// @brief generate code for indexed register access //////////////////////////////////////////////////////////////////////////////// @@ -400,44 +533,6 @@ static void ScopeOutputIndexedName (TRI_aql_codegen_js_t* const generator, ScopeOutput(generator, "]"); } -//////////////////////////////////////////////////////////////////////////////// -/// @brief free a scope variable -//////////////////////////////////////////////////////////////////////////////// - -static void FreeVariable (TRI_aql_codegen_variable_t* const variable) { - assert(variable); - assert(variable->_name); - - TRI_Free(TRI_UNKNOWN_MEM_ZONE, variable->_name); - TRI_Free(TRI_UNKNOWN_MEM_ZONE, variable); -} - -//////////////////////////////////////////////////////////////////////////////// -/// @brief create a scope variable -//////////////////////////////////////////////////////////////////////////////// - -static TRI_aql_codegen_variable_t* CreateVariable (const char* const name, - const TRI_aql_codegen_register_t registerIndex) { - TRI_aql_codegen_variable_t* variable; - - variable = (TRI_aql_codegen_variable_t*) TRI_Allocate(TRI_UNKNOWN_MEM_ZONE, sizeof(TRI_aql_codegen_variable_t), false); - - if (variable == NULL) { - return NULL; - } - - variable->_register = registerIndex; - variable->_name = TRI_DuplicateStringZ(TRI_UNKNOWN_MEM_ZONE, name); - - if (variable->_name == NULL) { - TRI_Free(TRI_UNKNOWN_MEM_ZONE, variable); - - return NULL; - } - - return variable; -} - //////////////////////////////////////////////////////////////////////////////// /// @brief get the type for the next scope //////////////////////////////////////////////////////////////////////////////// @@ -510,16 +605,17 @@ static void StartScope (TRI_aql_codegen_js_t* const generator, } } - scope->_buffer = buffer; - scope->_type = NextScopeType(generator, type); - scope->_listRegister = listRegister; - scope->_keyRegister = keyRegister; - scope->_ownRegister = ownRegister; - scope->_offsetRegister = offsetRegister; - scope->_limitRegister = limitRegister; - scope->_resultRegister = resultRegister; - scope->_prefix = NULL; - scope->_hint = hint; // for-loop hint + scope->_buffer = buffer; + scope->_type = NextScopeType(generator, type); + scope->_listRegister = listRegister; + scope->_keyRegister = keyRegister; + scope->_ownRegister = ownRegister; + scope->_offsetRegister = offsetRegister; + scope->_limitRegister = limitRegister; + scope->_resultRegister = resultRegister; + scope->_subqueryRegister = 0; + scope->_prefix = NULL; + scope->_hint = hint; // for-loop hint // init symbol table res = TRI_InitAssociativePointer(&scope->_variables, @@ -763,69 +859,6 @@ static void InitArray (TRI_aql_codegen_js_t* const generator, ScopeOutput(generator, " = { };\n"); } -//////////////////////////////////////////////////////////////////////////////// -/// @brief enter a variable into the symbol table -//////////////////////////////////////////////////////////////////////////////// - -static void EnterSymbol (TRI_aql_codegen_js_t* const generator, - const char* const name, - const TRI_aql_codegen_register_t registerIndex) { - TRI_aql_codegen_scope_t* scope = CurrentScope(generator); - TRI_aql_codegen_variable_t* variable; - - if (scope == NULL) { - generator->_errorCode = TRI_ERROR_OUT_OF_MEMORY; - return; - } - - variable = CreateVariable(name, registerIndex); - - if (variable == NULL) { - generator->_errorCode = TRI_ERROR_OUT_OF_MEMORY; - return; - } - - if (TRI_InsertKeyAssociativePointer(&scope->_variables, name, (void*) variable, false) != NULL) { - // variable already exists in symbol table. this should never happen - LOG_TRACE("variable already registered: %s", name); - generator->_errorCode = TRI_ERROR_QUERY_VARIABLE_REDECLARED; - generator->_errorValue = (char*) name; - - FreeVariable(variable); - } -} - -//////////////////////////////////////////////////////////////////////////////// -/// @brief look up a variable in the symbol table -//////////////////////////////////////////////////////////////////////////////// - -static TRI_aql_codegen_register_t LookupSymbol (TRI_aql_codegen_js_t* const generator, - const char* const name) { - size_t i = generator->_scopes._length; - - assert(name); - - // iterate from current scope to the top level scope - while (i-- > 0) { - TRI_aql_codegen_scope_t* scope = (TRI_aql_codegen_scope_t*) generator->_scopes._buffer[i]; - TRI_aql_codegen_variable_t* variable; - - variable = (TRI_aql_codegen_variable_t*) TRI_LookupByKeyAssociativePointer(&scope->_variables, name); - // variable found in scope - if (variable) { - // return the variable register - return variable->_register; - } - } - - // variable not found. this should never happen - LOG_TRACE("variable not found: %s", name); - generator->_errorCode = TRI_ERROR_QUERY_VARIABLE_NAME_UNKNOWN; - generator->_errorValue = (char*) name; - - return 0; -} - //////////////////////////////////////////////////////////////////////////////// /// @brief start a for loop //////////////////////////////////////////////////////////////////////////////// @@ -861,7 +894,7 @@ static void StartFor (TRI_aql_codegen_js_t* const generator, } if (variableName) { - EnterSymbol(generator, variableName, ownRegister); + EnterSymbol(generator, variableName, ownRegister, false); } ScopeOutput(generator, "var "); @@ -1135,7 +1168,7 @@ static void RestoreSymbols (TRI_aql_codegen_js_t* const generator, // re-enter symbols if (variableName) { - EnterSymbol(generator, variableName, registerIndex); + EnterSymbol(generator, variableName, registerIndex, false); } // unpack variables from current scope's iterator register @@ -1188,7 +1221,7 @@ static void GeneratePrimaryAccess (TRI_aql_codegen_js_t* const generator, fieldAccess->_value._reference._operator == TRI_AQL_NODE_OPERATOR_BINARY_IN); if (fieldAccess->_value._reference._type == TRI_AQL_REFERENCE_VARIABLE) { - ScopeOutputRegister(generator, LookupSymbol(generator, fieldAccess->_value._reference._ref._name)); + ScopeOutputSymbol(generator, fieldAccess->_value._reference._ref._name); } else { ProcessAttributeAccess(generator, fieldAccess->_value._reference._ref._node); @@ -1240,7 +1273,7 @@ static void GenerateHashAccess (TRI_aql_codegen_js_t* const generator, ScopeOutput(generator, ", "); if (fieldAccess->_value._reference._type == TRI_AQL_REFERENCE_VARIABLE) { - ScopeOutputRegister(generator, LookupSymbol(generator, fieldAccess->_value._reference._ref._name)); + ScopeOutputSymbol(generator, fieldAccess->_value._reference._ref._name); } else { ProcessAttributeAccess(generator, fieldAccess->_value._reference._ref._node); @@ -1278,7 +1311,7 @@ static void GenerateHashAccess (TRI_aql_codegen_js_t* const generator, fieldAccess->_value._reference._operator == TRI_AQL_NODE_OPERATOR_BINARY_IN); if (fieldAccess->_value._reference._type == TRI_AQL_REFERENCE_VARIABLE) { - ScopeOutputRegister(generator, LookupSymbol(generator, fieldAccess->_value._reference._ref._name)); + ScopeOutputSymbol(generator, fieldAccess->_value._reference._ref._name); } else { ProcessAttributeAccess(generator, fieldAccess->_value._reference._ref._node); @@ -1331,7 +1364,7 @@ static void GenerateEdgeAccess (TRI_aql_codegen_js_t* const generator, fieldAccess->_value._reference._operator == TRI_AQL_NODE_OPERATOR_BINARY_IN); if (fieldAccess->_value._reference._type == TRI_AQL_REFERENCE_VARIABLE) { - ScopeOutputRegister(generator, LookupSymbol(generator, fieldAccess->_value._reference._ref._name)); + ScopeOutputSymbol(generator, fieldAccess->_value._reference._ref._name); } else { ProcessAttributeAccess(generator, fieldAccess->_value._reference._ref._node); @@ -1383,7 +1416,7 @@ static void GenerateSkiplistAccess (TRI_aql_codegen_js_t* const generator, ScopeOutputQuoted2(generator, fieldAccess->_fullName + fieldAccess->_variableNameLength + 1); ScopeOutput(generator, ", "); if (fieldAccess->_value._reference._type == TRI_AQL_REFERENCE_VARIABLE) { - ScopeOutputRegister(generator, LookupSymbol(generator, fieldAccess->_value._reference._ref._name)); + ScopeOutputSymbol(generator, fieldAccess->_value._reference._ref._name); } else { ProcessAttributeAccess(generator, fieldAccess->_value._reference._ref._node); @@ -1448,7 +1481,7 @@ static void GenerateSkiplistAccess (TRI_aql_codegen_js_t* const generator, ScopeOutput(generator, "\", "); if (fieldAccess->_value._reference._type == TRI_AQL_REFERENCE_VARIABLE) { - ScopeOutputRegister(generator, LookupSymbol(generator, fieldAccess->_value._reference._ref._name)); + ScopeOutputSymbol(generator, fieldAccess->_value._reference._ref._name); } else { ProcessAttributeAccess(generator, fieldAccess->_value._reference._ref._node); @@ -1504,7 +1537,7 @@ static void GenerateBitarrayAccess (TRI_aql_codegen_js_t* const generator, ScopeOutputQuoted2(generator, fieldAccess->_fullName + fieldAccess->_variableNameLength + 1); ScopeOutput(generator, ", "); if (fieldAccess->_value._reference._type == TRI_AQL_REFERENCE_VARIABLE) { - ScopeOutputRegister(generator, LookupSymbol(generator, fieldAccess->_value._reference._ref._name)); + ScopeOutputSymbol(generator, fieldAccess->_value._reference._ref._name); } else { ProcessAttributeAccess(generator, fieldAccess->_value._reference._ref._node); @@ -1545,7 +1578,7 @@ static void GenerateBitarrayAccess (TRI_aql_codegen_js_t* const generator, case TRI_AQL_ACCESS_REFERENCE: { if (fieldAccess->_value._reference._type == TRI_AQL_REFERENCE_VARIABLE) { - ScopeOutputRegister(generator, LookupSymbol(generator, fieldAccess->_value._reference._ref._name)); + ScopeOutputSymbol(generator, fieldAccess->_value._reference._ref._name); } else { ProcessAttributeAccess(generator, fieldAccess->_value._reference._ref._node); @@ -1589,8 +1622,7 @@ static void ProcessReference (TRI_aql_codegen_js_t* const generator, } else { // regular scope, we are using a register - TRI_aql_codegen_register_t registerIndex = LookupSymbol(generator, name); - ScopeOutputRegister(generator, registerIndex); + ScopeOutputSymbol(generator, name); } } @@ -1887,7 +1919,7 @@ static void ProcessExpand (TRI_aql_codegen_js_t* const generator, // for StartFor(generator, scope->_buffer, sourceRegister, isList, NULL, NULL); - EnterSymbol(generator, TRI_AQL_NODE_STRING(nameNode1), CurrentScope(generator)->_ownRegister); + EnterSymbol(generator, TRI_AQL_NODE_STRING(nameNode1), CurrentScope(generator)->_ownRegister, false); ScopeOutputRegister(generator, resultRegister); ScopeOutput(generator, ".push("); @@ -1901,7 +1933,7 @@ static void ProcessExpand (TRI_aql_codegen_js_t* const generator, EndScope(generator); // register the variable for the result - EnterSymbol(generator, TRI_AQL_NODE_STRING(nameNode2), resultRegister); + EnterSymbol(generator, TRI_AQL_NODE_STRING(nameNode2), resultRegister, false); } //////////////////////////////////////////////////////////////////////////////// @@ -2142,24 +2174,59 @@ static void ProcessTernary (TRI_aql_codegen_js_t* const generator, static void ProcessSubquery (TRI_aql_codegen_js_t* const generator, const TRI_aql_node_t* const node) { - TRI_aql_codegen_register_t subQueryRegister; TRI_aql_codegen_register_t resultRegister = IncRegister(generator); TRI_aql_node_t* nameNode = TRI_AQL_NODE_MEMBER(node, 0); + TRI_aql_codegen_scope_t* scope = CurrentScope(generator); - // get the subquery scope's result register - subQueryRegister = generator->_lastResultRegister; + if (scope == NULL) { + return; + } ScopeOutput(generator, "var "); ScopeOutputRegister(generator, resultRegister); ScopeOutput(generator, " = "); - ScopeOutputRegister(generator, subQueryRegister); -#if AQL_VERBOSE - ScopeOutput(generator, "; /* subquery */\n"); -#else - ScopeOutput(generator, ";\n"); -#endif + if (scope->_subqueryRegister > 0) { + ScopeOutputRegister(generator, scope->_subqueryRegister); - EnterSymbol(generator, nameNode->_value._value._string, resultRegister); +#if AQL_VERBOSE + ScopeOutput(generator, "(); /* subquery */\n"); +#else + ScopeOutput(generator, "();\n"); +#endif + } + else { + // subquery has been optimised away + ScopeOutput(generator, "[ ];\n"); + } + + EnterSymbol(generator, nameNode->_value._value._string, resultRegister, false); +} + +//////////////////////////////////////////////////////////////////////////////// +/// @brief generate code for cached subqueries (nested fors etc.) +//////////////////////////////////////////////////////////////////////////////// + +static void ProcessSubqueryCached (TRI_aql_codegen_js_t* const generator, + const TRI_aql_node_t* const node) { + TRI_aql_codegen_register_t resultRegister = IncRegister(generator); + TRI_aql_node_t* nameNode = TRI_AQL_NODE_MEMBER(node, 0); + TRI_aql_codegen_scope_t* scope = CurrentScope(generator); + + if (scope == NULL) { + return; + } + + ScopeOutput(generator, "var "); + ScopeOutputRegister(generator, resultRegister); + ScopeOutput(generator, " = { data: null, executed: false, fn: "); + ScopeOutputRegister(generator, scope->_subqueryRegister); +#if AQL_VERBOSE + ScopeOutput(generator, ", execute: function () { this.executed = true; return this.data = this.fn(); } }; /* subquery */\n"); +#else + ScopeOutput(generator, ", execute: function () { this.executed = true; return this.data = this.fn(); } };\n"); +#endif + + EnterSymbol(generator, nameNode->_value._value._string, resultRegister, true); } //////////////////////////////////////////////////////////////////////////////// @@ -2197,10 +2264,23 @@ static void ProcessScopeStart (TRI_aql_codegen_js_t* const generator, TRI_aql_codegen_register_t resultRegister; TRI_aql_codegen_register_t sourceRegister; TRI_aql_scope_t* scope = (TRI_aql_scope_t*) TRI_AQL_NODE_DATA(node); + TRI_aql_codegen_scope_t* s; if (scope->_type != TRI_AQL_SCOPE_SUBQUERY) { return; } + + s = CurrentScope(generator); + + if (s == NULL) { + return; + } + + s->_subqueryRegister = IncRegister(generator); + + ScopeOutput(generator, "var "); + ScopeOutputRegister(generator, s->_subqueryRegister); + ScopeOutput(generator, " = function() {\n"); resultRegister = IncRegister(generator); InitList(generator, resultRegister); @@ -2226,12 +2306,18 @@ static void ProcessScopeStart (TRI_aql_codegen_js_t* const generator, static void ProcessScopeEnd (TRI_aql_codegen_js_t* const generator, const TRI_aql_node_t* const node) { TRI_aql_scope_t* scope = (TRI_aql_scope_t*) TRI_AQL_NODE_DATA(node); + TRI_aql_codegen_register_t resultRegister; if (scope->_type != TRI_AQL_SCOPE_SUBQUERY) { return; } + resultRegister = CurrentScope(generator)->_resultRegister; EndScope(generator); + + ScopeOutput(generator, "return "); + ScopeOutputRegister(generator, resultRegister); + ScopeOutput(generator, ";\n};\n"); } //////////////////////////////////////////////////////////////////////////////// @@ -2444,7 +2530,7 @@ static void ProcessCollect (TRI_aql_codegen_js_t* const generator, ScopeOutputIndexedName(generator, scope->_ownRegister, TRI_AQL_NODE_STRING(varNode)); ScopeOutput(generator, ";\n"); - EnterSymbol(generator, TRI_AQL_NODE_STRING(varNode), varRegister); + EnterSymbol(generator, TRI_AQL_NODE_STRING(varNode), varRegister, false); } // re-enter symbol for into @@ -2459,7 +2545,7 @@ static void ProcessCollect (TRI_aql_codegen_js_t* const generator, ScopeOutputIndexedName(generator, scope->_ownRegister, TRI_AQL_NODE_STRING(nameNode)); ScopeOutput(generator, ";\n"); - EnterSymbol(generator, TRI_AQL_NODE_STRING(nameNode), intoRegister); + EnterSymbol(generator, TRI_AQL_NODE_STRING(nameNode), intoRegister, false); } #if AQL_VERBOSE @@ -2560,8 +2646,6 @@ static void ProcessReturn (TRI_aql_codegen_js_t* const generator, ScopeOutputRegister(generator, rowRegister); ScopeOutput(generator, ");\n"); - generator->_lastResultRegister = scope->_resultRegister; - // } CloseLoops(generator); } @@ -2583,8 +2667,6 @@ static void ProcessReturnEmpty (TRI_aql_codegen_js_t* const generator, ScopeOutput(generator, " = [ ];\n"); #endif - generator->_lastResultRegister = resultRegister; - // } CloseLoops(generator); } @@ -2606,7 +2688,7 @@ static void ProcessLet (TRI_aql_codegen_js_t* const generator, ScopeOutput(generator, ";\n"); // enter the new variable in the symbol table - EnterSymbol(generator, nameNode->_value._value._string, resultRegister); + EnterSymbol(generator, nameNode->_value._value._string, resultRegister, false); } //////////////////////////////////////////////////////////////////////////////// @@ -2643,7 +2725,7 @@ static void ProcessAssign (TRI_aql_codegen_js_t* const generator, } // enter the new variable in the symbol table - EnterSymbol(generator, nameNode->_value._value._string, resultRegister); + EnterSymbol(generator, nameNode->_value._value._string, resultRegister, false); } //////////////////////////////////////////////////////////////////////////////// @@ -2827,6 +2909,9 @@ static void ProcessNode (TRI_aql_codegen_js_t* const generator, const TRI_aql_no case TRI_AQL_NODE_SUBQUERY: ProcessSubquery(generator, node); break; + case TRI_AQL_NODE_SUBQUERY_CACHED: + ProcessSubqueryCached(generator, node); + break; case TRI_AQL_NODE_SCOPE_START: ProcessScopeStart(generator, node); break; @@ -2925,7 +3010,7 @@ static TRI_aql_codegen_js_t* CreateGenerator (TRI_aql_context_t* const context) generator->_errorValue = NULL; generator->_registerIndex = 0; generator->_functionIndex = 0; - + return generator; } diff --git a/arangod/Ahuacatl/ahuacatl-codegen.h b/arangod/Ahuacatl/ahuacatl-codegen.h index efed74c68f..b853f0df0b 100644 --- a/arangod/Ahuacatl/ahuacatl-codegen.h +++ b/arangod/Ahuacatl/ahuacatl-codegen.h @@ -66,6 +66,7 @@ typedef uint32_t TRI_aql_codegen_register_t; typedef struct TRI_aql_codegen_variable_s { char* _name; // variable name TRI_aql_codegen_register_t _register; // the assigned register + bool _isCachedSubquery; } TRI_aql_codegen_variable_t; @@ -82,6 +83,7 @@ typedef struct TRI_aql_codegen_scope_s { TRI_aql_codegen_register_t _resultRegister; TRI_aql_codegen_register_t _offsetRegister; // limit offset, limit TRI_aql_codegen_register_t _limitRegister; // limit offset, limit + TRI_aql_codegen_register_t _subqueryRegister; TRI_associative_pointer_t _variables; // list of variables in scope char* _prefix; // prefix for variable names, used in FUNCTION scopes only TRI_aql_for_hint_t* _hint; // generic hint @@ -97,7 +99,6 @@ typedef struct TRI_aql_codegen_js_s { TRI_string_buffer_t _buffer; TRI_string_buffer_t _functionBuffer; TRI_vector_pointer_t _scopes; - TRI_aql_codegen_register_t _lastResultRegister; TRI_aql_codegen_register_t _registerIndex; TRI_aql_codegen_register_t _functionIndex; int _errorCode; // error number diff --git a/arangod/Ahuacatl/ahuacatl-explain.c b/arangod/Ahuacatl/ahuacatl-explain.c index 0844ea2c4b..dc5c4708c7 100644 --- a/arangod/Ahuacatl/ahuacatl-explain.c +++ b/arangod/Ahuacatl/ahuacatl-explain.c @@ -292,7 +292,8 @@ static TRI_aql_node_t* ProcessStatement (TRI_aql_statement_walker_t* const walke break; } - case TRI_AQL_NODE_SUBQUERY: { + case TRI_AQL_NODE_SUBQUERY: + case TRI_AQL_NODE_SUBQUERY_CACHED: { TRI_json_t* row; row = GetRowProtoType(explain, node->_type); diff --git a/arangod/Ahuacatl/ahuacatl-node.c b/arangod/Ahuacatl/ahuacatl-node.c index 54c26cb04d..0c551876ef 100644 --- a/arangod/Ahuacatl/ahuacatl-node.c +++ b/arangod/Ahuacatl/ahuacatl-node.c @@ -40,6 +40,7 @@ bool TRI_IsTopLevelTypeAql (const TRI_aql_node_type_e type) { if (type == TRI_AQL_NODE_SCOPE_START || type == TRI_AQL_NODE_SCOPE_END || type == TRI_AQL_NODE_SUBQUERY || + type == TRI_AQL_NODE_SUBQUERY_CACHED || type == TRI_AQL_NODE_EXPAND || type == TRI_AQL_NODE_FOR || type == TRI_AQL_NODE_FILTER || @@ -165,6 +166,8 @@ const char* TRI_NodeNameAql (const TRI_aql_node_type_e type) { return "ternary"; case TRI_AQL_NODE_SUBQUERY: return "subquery"; + case TRI_AQL_NODE_SUBQUERY_CACHED: + return "subquery (cached)"; case TRI_AQL_NODE_ATTRIBUTE_ACCESS: return "attribute access"; case TRI_AQL_NODE_BOUND_ATTRIBUTE_ACCESS: diff --git a/arangod/Ahuacatl/ahuacatl-node.h b/arangod/Ahuacatl/ahuacatl-node.h index a55fd1f704..9897ef02b0 100644 --- a/arangod/Ahuacatl/ahuacatl-node.h +++ b/arangod/Ahuacatl/ahuacatl-node.h @@ -171,6 +171,7 @@ typedef enum { TRI_AQL_NODE_OPERATOR_BINARY_IN, TRI_AQL_NODE_OPERATOR_TERNARY, TRI_AQL_NODE_SUBQUERY, + TRI_AQL_NODE_SUBQUERY_CACHED, TRI_AQL_NODE_ATTRIBUTE_ACCESS, TRI_AQL_NODE_BOUND_ATTRIBUTE_ACCESS, TRI_AQL_NODE_INDEXED, diff --git a/arangod/Ahuacatl/ahuacatl-statementlist.c b/arangod/Ahuacatl/ahuacatl-statementlist.c index 115ad70492..98ec18df0a 100644 --- a/arangod/Ahuacatl/ahuacatl-statementlist.c +++ b/arangod/Ahuacatl/ahuacatl-statementlist.c @@ -228,6 +228,8 @@ void TRI_PulloutStatementListAql (TRI_aql_statement_list_t* const list) { size_t j = moveStart; size_t inserted = 0; + node->_type = TRI_AQL_NODE_SUBQUERY_CACHED; + // moving statements from the middle to the beginning of the list will also // modify the positions we're moving from while (j < i + 2) { diff --git a/js/server/tests/ahuacatl-queries-noncollection.js b/js/server/tests/ahuacatl-queries-noncollection.js index 498de4f891..c63e9941f6 100644 --- a/js/server/tests/ahuacatl-queries-noncollection.js +++ b/js/server/tests/ahuacatl-queries-noncollection.js @@ -51,6 +51,97 @@ function ahuacatlQueryNonCollectionTestSuite () { tearDown : function () { }, +//////////////////////////////////////////////////////////////////////////////// +/// @brief multiple subqueries +//////////////////////////////////////////////////////////////////////////////// + + testMultipleSubqueries1 : function () { + var expected = [ [ [ 2, 4 ], [ 6, 8 ] ] ]; + var actual = getQueryResults("LET a = (FOR i IN [ 1, 2 ] RETURN i * 2), b = (FOR i IN [ 3, 4 ] RETURN i * 2) RETURN [ a, b ]"); + assertEqual(expected, actual); + }, + +//////////////////////////////////////////////////////////////////////////////// +/// @brief multiple subqueries +//////////////////////////////////////////////////////////////////////////////// + + testMultipleSubqueries2 : function () { + var expected = [ [ [ 2, 4 ], [ 6, 8 ] ] ]; + var actual = getQueryResults("LET a = (FOR i IN [ 1, 2 ] RETURN i * 2) LET b = (FOR i IN [ 3, 4 ] RETURN i * 2) RETURN [ a, b ]"); + assertEqual(expected, actual); + }, + +//////////////////////////////////////////////////////////////////////////////// +/// @brief test deferred evaluation +//////////////////////////////////////////////////////////////////////////////// + + testDeferredEval1 : function () { + var expected = [ ]; + + var actual = getQueryResults("FOR year IN [ 2010, 2011, 2012 ] FILTER year > 2013 && FAIL() RETURN year"); + assertEqual(expected, actual); + }, + +//////////////////////////////////////////////////////////////////////////////// +/// @brief test deferred evaluation +//////////////////////////////////////////////////////////////////////////////// + + testDeferredEval2 : function () { + var expected = [ ]; + + var actual = getQueryResults("FOR year IN [ 2010, 2011, 2012 ] FILTER year > 2013 RETURN FAIL()"); + assertEqual(expected, actual); + }, + +//////////////////////////////////////////////////////////////////////////////// +/// @brief test deferred evaluation +//////////////////////////////////////////////////////////////////////////////// + + testDeferredEval3 : function () { + var expected = [ ]; + + var actual = getQueryResults("FOR year IN [ 2010, 2011, 2012 ] FILTER year > 2013 && FAIL() RETURN FAIL()"); + assertEqual(expected, actual); + }, + +//////////////////////////////////////////////////////////////////////////////// +/// @brief test deferred evaluation +//////////////////////////////////////////////////////////////////////////////// + + testDeferredEval4 : function () { + var expected = [ ]; + + var actual = getQueryResults("FOR year IN [ 2010, 2011, 2012 ] FILTER year > 2020 LET x = (FOR i IN [ 1, 2 ] RETURN FAIL()) RETURN year"); + assertEqual(expected, actual); + }, + +//////////////////////////////////////////////////////////////////////////////// +/// @brief test deferred evaluation +//////////////////////////////////////////////////////////////////////////////// + + testDeferredEval5 : function () { + var expected = [ ]; + + var actual = getQueryResults("FOR year IN [ 2010, 2011, 2012 ] FILTER year > 2020 LET x = (FOR i IN [ 1, 2 ] RETURN FAIL()) RETURN FAIL()"); + assertEqual(expected, actual); + }, + +//////////////////////////////////////////////////////////////////////////////// +/// @brief test deferred evaluation +//////////////////////////////////////////////////////////////////////////////// + + testDeferredEval6 : function () { + assertException(function() { getQueryResults("FOR year IN [ 2010, 2011, 2012 ] FILTER year < 2020 RETURN FAIL()"); }); + }, + +//////////////////////////////////////////////////////////////////////////////// +/// @brief test deferred evaluation +//////////////////////////////////////////////////////////////////////////////// + + testDeferredEval7 : function () { + assertException(function() { getQueryResults("FOR year IN [ 2010, 2011, 2012 ] FILTER year < 2020 LET x = (FOR i IN [ 1, 2 ] RETURN FAIL()) RETURN year") }); + }, + //////////////////////////////////////////////////////////////////////////////// /// @brief return a single value from a list ////////////////////////////////////////////////////////////////////////////////