diff --git a/CHANGELOG b/CHANGELOG index cb6d71a58d..d00f0dbbe0 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -32,6 +32,9 @@ v1.1.beta1 (2012-XX-XX) - if the length of the HTTP headers is greated than the maximum allowed size (1 MB), the server will fail with HTTP 431 (request header fields too large) +* issue #248: allow AQL optimiser to pull out completely uncorrelated subqueries to the + top level, resulting in less repeated evaluation of the subquery + * upgraded to Doxygen 1.8.0 * issue #247: added AQL function MERGE_RECURSIVE diff --git a/UnitTests/Makefile.files b/UnitTests/Makefile.files index 81a861aae2..77641741ea 100755 --- a/UnitTests/Makefile.files +++ b/UnitTests/Makefile.files @@ -259,12 +259,12 @@ SHELL_SERVER_AHUACATL = @top_srcdir@/js/server/tests/ahuacatl-ranges.js \ @top_srcdir@/js/server/tests/ahuacatl-skiplist.js \ @top_srcdir@/js/server/tests/ahuacatl-refaccess-variable.js \ @top_srcdir@/js/server/tests/ahuacatl-refaccess-attribute.js \ - @top_srcdir@/js/server/tests/ahuacatl-skiplist.js \ @top_srcdir@/js/server/tests/ahuacatl-queries-simple.js \ @top_srcdir@/js/server/tests/ahuacatl-queries-variables.js \ @top_srcdir@/js/server/tests/ahuacatl-queries-geo.js \ @top_srcdir@/js/server/tests/ahuacatl-queries-collection.js \ @top_srcdir@/js/server/tests/ahuacatl-queries-noncollection.js \ + @top_srcdir@/js/server/tests/ahuacatl-subquery.js \ @top_srcdir@/js/server/tests/ahuacatl-operators.js SHELL_SERVER_AHUACATL_EXTENDED = \ diff --git a/arangod/Ahuacatl/ahuacatl-codegen.c b/arangod/Ahuacatl/ahuacatl-codegen.c index 5e12916130..bf2f38fd4c 100644 --- a/arangod/Ahuacatl/ahuacatl-codegen.c +++ b/arangod/Ahuacatl/ahuacatl-codegen.c @@ -143,11 +143,11 @@ static inline TRI_aql_codegen_scope_t* CurrentScope (TRI_aql_codegen_js_t* const static inline bool OutputString (TRI_string_buffer_t* const buffer, const char* const value) { - if (!buffer) { + if (buffer == NULL) { return true; } - if (!value) { + if (value == NULL) { return false; } @@ -160,7 +160,7 @@ static inline bool OutputString (TRI_string_buffer_t* const buffer, static inline bool OutputChar (TRI_string_buffer_t* const buffer, const char value) { - if (!buffer) { + if (buffer == NULL) { return true; } @@ -173,7 +173,7 @@ static inline bool OutputChar (TRI_string_buffer_t* const buffer, static inline bool OutputUInt (TRI_string_buffer_t* const buffer, const uint64_t value) { - if (!buffer) { + if (buffer == NULL) { return true; } @@ -186,7 +186,7 @@ static inline bool OutputUInt (TRI_string_buffer_t* const buffer, static inline bool OutputInt (TRI_string_buffer_t* const buffer, const int64_t value) { - if (!buffer) { + if (buffer == NULL) { return true; } @@ -201,7 +201,7 @@ static inline void ScopeOutput (TRI_aql_codegen_js_t* const generator, const char* const value) { TRI_aql_codegen_scope_t* scope = CurrentScope(generator); - if (!OutputString(scope->_buffer, value)) { + if (! OutputString(scope->_buffer, value)) { generator->_errorCode = TRI_ERROR_OUT_OF_MEMORY; } } @@ -214,7 +214,7 @@ static inline void ScopeOutputInt (TRI_aql_codegen_js_t* const generator, const int64_t value) { TRI_aql_codegen_scope_t* scope = CurrentScope(generator); - if (!OutputInt(scope->_buffer, value)) { + if (! OutputInt(scope->_buffer, value)) { generator->_errorCode = TRI_ERROR_OUT_OF_MEMORY; } } @@ -227,7 +227,7 @@ static inline void ScopeOutputUInt (TRI_aql_codegen_js_t* const generator, const uint64_t value) { TRI_aql_codegen_scope_t* scope = CurrentScope(generator); - if (!OutputUInt(scope->_buffer, value)) { + if (! OutputUInt(scope->_buffer, value)) { generator->_errorCode = TRI_ERROR_OUT_OF_MEMORY; } } @@ -240,13 +240,13 @@ static inline void ScopeOutputQuoted (TRI_aql_codegen_js_t* const generator, const char* const value) { TRI_aql_codegen_scope_t* scope = CurrentScope(generator); - if (!OutputChar(scope->_buffer, '\'')) { + if (! OutputChar(scope->_buffer, '\'')) { generator->_errorCode = TRI_ERROR_OUT_OF_MEMORY; } - if (!OutputString(scope->_buffer, value)) { + if (! OutputString(scope->_buffer, value)) { generator->_errorCode = TRI_ERROR_OUT_OF_MEMORY; } - if (!OutputChar(scope->_buffer, '\'')) { + if (! OutputChar(scope->_buffer, '\'')) { generator->_errorCode = TRI_ERROR_OUT_OF_MEMORY; } } @@ -261,13 +261,13 @@ static inline void ScopeOutputQuoted2 (TRI_aql_codegen_js_t* const generator, char* escaped; size_t outLength; - if (!OutputChar(scope->_buffer, '"')) { + if (! OutputChar(scope->_buffer, '"')) { generator->_errorCode = TRI_ERROR_OUT_OF_MEMORY; } escaped = TRI_EscapeUtf8StringZ(TRI_UNKNOWN_MEM_ZONE, value, strlen(value), false, &outLength); if (escaped) { - if (!OutputString(scope->_buffer, escaped)) { + if (! OutputString(scope->_buffer, escaped)) { generator->_errorCode = TRI_ERROR_OUT_OF_MEMORY; } @@ -277,7 +277,7 @@ static inline void ScopeOutputQuoted2 (TRI_aql_codegen_js_t* const generator, generator->_errorCode = TRI_ERROR_OUT_OF_MEMORY; } - if (!OutputChar(scope->_buffer, '"')) { + if (! OutputChar(scope->_buffer, '"')) { generator->_errorCode = TRI_ERROR_OUT_OF_MEMORY; } } @@ -290,7 +290,7 @@ static inline void ScopeOutputJson (TRI_aql_codegen_js_t* const generator, const TRI_json_t* const json) { TRI_aql_codegen_scope_t* scope = CurrentScope(generator); - if (!scope->_buffer) { + if (! scope->_buffer) { return; } @@ -321,10 +321,10 @@ static inline void ScopeOutputFunction (TRI_aql_codegen_js_t* const generator, const TRI_aql_codegen_register_t functionIndex) { TRI_aql_codegen_scope_t* scope = CurrentScope(generator); - if (!OutputString(scope->_buffer, FUNCTION_PREFIX)) { + if (! OutputString(scope->_buffer, FUNCTION_PREFIX)) { generator->_errorCode = TRI_ERROR_OUT_OF_MEMORY; } - if (!OutputInt(scope->_buffer, (int64_t) functionIndex)) { + if (! OutputInt(scope->_buffer, (int64_t) functionIndex)) { generator->_errorCode = TRI_ERROR_OUT_OF_MEMORY; } } @@ -337,10 +337,10 @@ static inline void ScopeOutputRegister (TRI_aql_codegen_js_t* const generator, const TRI_aql_codegen_register_t registerIndex) { TRI_aql_codegen_scope_t* scope = CurrentScope(generator); - if (!OutputString(scope->_buffer, REGISTER_PREFIX)) { + if (! OutputString(scope->_buffer, REGISTER_PREFIX)) { generator->_errorCode = TRI_ERROR_OUT_OF_MEMORY; } - if (!OutputInt(scope->_buffer, (int64_t) registerIndex)) { + if (! OutputInt(scope->_buffer, (int64_t) registerIndex)) { generator->_errorCode = TRI_ERROR_OUT_OF_MEMORY; } } @@ -432,7 +432,7 @@ static void StartScope (TRI_aql_codegen_js_t* const generator, TRI_aql_codegen_scope_t* scope; scope = (TRI_aql_codegen_scope_t*) TRI_Allocate(TRI_UNKNOWN_MEM_ZONE, sizeof(TRI_aql_codegen_scope_t), false); - if (!scope) { + if (scope == NULL) { generator->_errorCode = TRI_ERROR_OUT_OF_MEMORY; return; } @@ -633,7 +633,7 @@ static void EnterSymbol (TRI_aql_codegen_js_t* const generator, TRI_aql_codegen_scope_t* scope = CurrentScope(generator); TRI_aql_codegen_variable_t* variable = CreateVariable(name, registerIndex); - if (!variable) { + if (variable == NULL) { generator->_errorCode = TRI_ERROR_OUT_OF_MEMORY; return; } @@ -2368,7 +2368,8 @@ char* TRI_GenerateCodeAql (TRI_aql_context_t* const context) { generator->_errorCode = TRI_ERROR_OUT_OF_MEMORY; } } - + + // errorCode might have changed, no else! if (generator->_errorCode != TRI_ERROR_NO_ERROR) { // register the error TRI_SetErrorContextAql(context, generator->_errorCode, generator->_errorValue); diff --git a/arangod/Ahuacatl/ahuacatl-collections.c b/arangod/Ahuacatl/ahuacatl-collections.c index 1030efbbce..530630d08f 100644 --- a/arangod/Ahuacatl/ahuacatl-collections.c +++ b/arangod/Ahuacatl/ahuacatl-collections.c @@ -146,7 +146,6 @@ bool OpenCollections (TRI_aql_context_t* const context) { n = context->_collections._length; for (i = 0; i < n; ++i) { TRI_aql_collection_t* collection = context->_collections._buffer[i]; - char* name; assert(collection); assert(collection->_name); @@ -155,11 +154,10 @@ bool OpenCollections (TRI_aql_context_t* const context) { assert(!collection->_readLocked); LOG_TRACE("locking collection '%s'", collection->_name); + collection->_collection = TRI_UseCollectionByNameVocBase(context->_vocbase, collection->_name); + if (collection->_collection == NULL) { + TRI_SetErrorContextAql(context, TRI_ERROR_QUERY_COLLECTION_NOT_FOUND, collection->_name); - name = collection->_name; - collection->_collection = TRI_UseCollectionByNameVocBase(context->_vocbase, name); - if (!collection->_collection) { - TRI_SetErrorContextAql(context, TRI_ERROR_QUERY_COLLECTION_NOT_FOUND, name); return false; } } @@ -341,7 +339,7 @@ void TRI_UnlockCollectionsAql (TRI_aql_context_t* const context) { assert(collection); assert(collection->_name); - if (!collection->_collection) { + if (collection->_collection == NULL) { // collection not yet opened continue; } diff --git a/arangod/Ahuacatl/ahuacatl-context.c b/arangod/Ahuacatl/ahuacatl-context.c index 31db6d117e..041d646ba1 100644 --- a/arangod/Ahuacatl/ahuacatl-context.c +++ b/arangod/Ahuacatl/ahuacatl-context.c @@ -156,6 +156,8 @@ TRI_aql_context_t* TRI_CreateContextAql (TRI_vocbase_t* vocbase, assert(vocbase); assert(query); + + LOG_TRACE("creating context"); context = (TRI_aql_context_t*) TRI_Allocate(TRI_UNKNOWN_MEM_ZONE, sizeof(TRI_aql_context_t), false); if (context == NULL) { @@ -235,6 +237,8 @@ TRI_aql_context_t* TRI_CreateContextAql (TRI_vocbase_t* vocbase, void TRI_FreeContextAql (TRI_aql_context_t* const context) { assert(context); + LOG_TRACE("freeing context"); + // remove barriers for all collections used TRI_RemoveBarrierCollectionsAql(context); @@ -347,6 +351,7 @@ bool TRI_OptimiseQueryContextAql (TRI_aql_context_t* const context) { } TRI_CompactStatementListAql(context->_statements); + TRI_PulloutStatementListAql(context->_statements); // TRI_DumpStatementsAql(context->_statements); diff --git a/arangod/Ahuacatl/ahuacatl-grammar.c b/arangod/Ahuacatl/ahuacatl-grammar.c index f8f036f7ca..351031171b 100644 --- a/arangod/Ahuacatl/ahuacatl-grammar.c +++ b/arangod/Ahuacatl/ahuacatl-grammar.c @@ -1769,7 +1769,7 @@ yyreduce: ABORT_OOM } - if (!TRI_AddStatementListAql(context->_statements, node)) { + if (!TRI_AppendStatementListAql(context->_statements, node)) { ABORT_OOM } ;} @@ -1785,7 +1785,7 @@ yyreduce: ABORT_OOM } - if (!TRI_AddStatementListAql(context->_statements, node)) { + if (!TRI_AppendStatementListAql(context->_statements, node)) { ABORT_OOM } ;} @@ -1801,7 +1801,7 @@ yyreduce: ABORT_OOM } - if (!TRI_AddStatementListAql(context->_statements, node)) { + if (!TRI_AppendStatementListAql(context->_statements, node)) { ABORT_OOM } ;} @@ -1832,7 +1832,7 @@ yyreduce: ABORT_OOM } - if (!TRI_AddStatementListAql(context->_statements, node)) { + if (!TRI_AppendStatementListAql(context->_statements, node)) { ABORT_OOM } ;} @@ -1914,7 +1914,7 @@ yyreduce: ABORT_OOM } - if (!TRI_AddStatementListAql(context->_statements, node)) { + if (!TRI_AppendStatementListAql(context->_statements, node)) { ABORT_OOM } ;} @@ -1993,11 +1993,11 @@ yyreduce: ABORT_OOM } - if (!TRI_AddStatementListAql(context->_statements, node)) { + if (!TRI_AppendStatementListAql(context->_statements, node)) { ABORT_OOM } - if (!TRI_AddStatementListAql(context->_statements, node)) { + if (!TRI_AppendStatementListAql(context->_statements, node)) { ABORT_OOM } ;} @@ -2013,7 +2013,7 @@ yyreduce: ABORT_OOM } - if (!TRI_AddStatementListAql(context->_statements, node)) { + if (!TRI_AppendStatementListAql(context->_statements, node)) { ABORT_OOM } ;} @@ -2029,7 +2029,7 @@ yyreduce: ABORT_OOM } - if (!TRI_AddStatementListAql(context->_statements, node)) { + if (!TRI_AppendStatementListAql(context->_statements, node)) { ABORT_OOM } @@ -2080,7 +2080,7 @@ yyreduce: ABORT_OOM } - if (!TRI_AddStatementListAql(context->_statements, subQuery)) { + if (!TRI_AppendStatementListAql(context->_statements, subQuery)) { ABORT_OOM } @@ -2674,7 +2674,7 @@ yyreduce: // push the actual expand node into the statement list expand = TRI_CreateNodeExpandAql(context, varname, expanded, (yyvsp[(4) - (4)].node)); - if (!TRI_AddStatementListAql(context->_statements, expand)) { + if (!TRI_AppendStatementListAql(context->_statements, expand)) { ABORT_OOM } diff --git a/arangod/Ahuacatl/ahuacatl-grammar.y b/arangod/Ahuacatl/ahuacatl-grammar.y index cb7325105f..7b3790f517 100644 --- a/arangod/Ahuacatl/ahuacatl-grammar.y +++ b/arangod/Ahuacatl/ahuacatl-grammar.y @@ -212,7 +212,7 @@ for_statement: ABORT_OOM } - if (!TRI_AddStatementListAql(context->_statements, node)) { + if (!TRI_AppendStatementListAql(context->_statements, node)) { ABORT_OOM } } @@ -225,7 +225,7 @@ filter_statement: ABORT_OOM } - if (!TRI_AddStatementListAql(context->_statements, node)) { + if (!TRI_AppendStatementListAql(context->_statements, node)) { ABORT_OOM } } @@ -238,7 +238,7 @@ let_statement: ABORT_OOM } - if (!TRI_AddStatementListAql(context->_statements, node)) { + if (!TRI_AppendStatementListAql(context->_statements, node)) { ABORT_OOM } } @@ -259,7 +259,7 @@ collect_statement: ABORT_OOM } - if (!TRI_AddStatementListAql(context->_statements, node)) { + if (!TRI_AppendStatementListAql(context->_statements, node)) { ABORT_OOM } } @@ -310,7 +310,7 @@ sort_statement: ABORT_OOM } - if (!TRI_AddStatementListAql(context->_statements, node)) { + if (!TRI_AppendStatementListAql(context->_statements, node)) { ABORT_OOM } } @@ -359,11 +359,11 @@ limit_statement: ABORT_OOM } - if (!TRI_AddStatementListAql(context->_statements, node)) { + if (!TRI_AppendStatementListAql(context->_statements, node)) { ABORT_OOM } - if (!TRI_AddStatementListAql(context->_statements, node)) { + if (!TRI_AppendStatementListAql(context->_statements, node)) { ABORT_OOM } } @@ -373,7 +373,7 @@ limit_statement: ABORT_OOM } - if (!TRI_AddStatementListAql(context->_statements, node)) { + if (!TRI_AppendStatementListAql(context->_statements, node)) { ABORT_OOM } } @@ -386,7 +386,7 @@ return_statement: ABORT_OOM } - if (!TRI_AddStatementListAql(context->_statements, node)) { + if (!TRI_AppendStatementListAql(context->_statements, node)) { ABORT_OOM } @@ -422,7 +422,7 @@ expression: ABORT_OOM } - if (!TRI_AddStatementListAql(context->_statements, subQuery)) { + if (!TRI_AppendStatementListAql(context->_statements, subQuery)) { ABORT_OOM } @@ -767,7 +767,7 @@ reference: // push the actual expand node into the statement list expand = TRI_CreateNodeExpandAql(context, varname, expanded, $4); - if (!TRI_AddStatementListAql(context->_statements, expand)) { + if (!TRI_AppendStatementListAql(context->_statements, expand)) { ABORT_OOM } diff --git a/arangod/Ahuacatl/ahuacatl-optimiser.c b/arangod/Ahuacatl/ahuacatl-optimiser.c index 90c8b220df..e3cab15ad6 100644 --- a/arangod/Ahuacatl/ahuacatl-optimiser.c +++ b/arangod/Ahuacatl/ahuacatl-optimiser.c @@ -60,6 +60,29 @@ static TRI_aql_node_t* ProcessStatement (TRI_aql_statement_walker_t* const, //////////////////////////////////////////////////////////////////////////////// /// @} //////////////////////////////////////////////////////////////////////////////// + +// ----------------------------------------------------------------------------- +// --SECTION-- private types +// ----------------------------------------------------------------------------- + +//////////////////////////////////////////////////////////////////////////////// +/// @addtogroup Ahuacatl +/// @{ +//////////////////////////////////////////////////////////////////////////////// + +//////////////////////////////////////////////////////////////////////////////// +/// @brief a local optimiser structure that is used temporarily during the +/// AST traversal +//////////////////////////////////////////////////////////////////////////////// + +typedef struct aql_optimiser_s { + TRI_aql_context_t* _context; +} +aql_optimiser_t; + +//////////////////////////////////////////////////////////////////////////////// +/// @} +//////////////////////////////////////////////////////////////////////////////// // ----------------------------------------------------------------------------- // --SECTION-- private functions @@ -69,6 +92,34 @@ static TRI_aql_node_t* ProcessStatement (TRI_aql_statement_walker_t* const, /// @addtogroup Ahuacatl /// @{ //////////////////////////////////////////////////////////////////////////////// + +//////////////////////////////////////////////////////////////////////////////// +/// @brief create an optimiser structure +//////////////////////////////////////////////////////////////////////////////// + +static aql_optimiser_t* CreateOptimiser (TRI_aql_context_t* const context) { + aql_optimiser_t* optimiser; + + optimiser = (aql_optimiser_t*) TRI_Allocate(TRI_UNKNOWN_MEM_ZONE, sizeof(aql_optimiser_t), false); + + if (optimiser == NULL) { + return NULL; + } + + optimiser->_context = context; + + return optimiser; +} + +//////////////////////////////////////////////////////////////////////////////// +/// @brief free an optimiser structure +//////////////////////////////////////////////////////////////////////////////// + +static void FreeOptimiser (aql_optimiser_t* const optimiser) { + assert(optimiser); + + TRI_Free(TRI_UNKNOWN_MEM_ZONE, optimiser); +} //////////////////////////////////////////////////////////////////////////////// /// @brief pick an index for the ranges found @@ -133,15 +184,15 @@ static void AttachCollectionHint (TRI_aql_context_t* const context, static TRI_aql_node_t* AnnotateNode (TRI_aql_statement_walker_t* const walker, TRI_aql_node_t* node) { - TRI_aql_context_t* context; + aql_optimiser_t* optimiser; if (node->_type != TRI_AQL_NODE_COLLECTION) { return node; } - context = (TRI_aql_context_t*) walker->_data; + optimiser = (aql_optimiser_t*) walker->_data; - AttachCollectionHint(context, node); + AttachCollectionHint(optimiser->_context, node); return node; } @@ -323,7 +374,7 @@ static TRI_aql_node_t* OptimiseFcall (TRI_aql_context_t* const context, } //////////////////////////////////////////////////////////////////////////////// -/// @brief optimise a for statement +/// @brief optimise a FOR statement //////////////////////////////////////////////////////////////////////////////// static TRI_aql_node_t* OptimiseFor (TRI_aql_statement_walker_t* const walker, @@ -342,9 +393,9 @@ static TRI_aql_node_t* OptimiseFor (TRI_aql_statement_walker_t* const walker, return node; } - + //////////////////////////////////////////////////////////////////////////////// -/// @brief optimise a sort statement +/// @brief optimise a SORT statement //////////////////////////////////////////////////////////////////////////////// static TRI_aql_node_t* OptimiseSort (TRI_aql_statement_walker_t* const walker, @@ -387,7 +438,7 @@ static TRI_aql_node_t* OptimiseSort (TRI_aql_statement_walker_t* const walker, } //////////////////////////////////////////////////////////////////////////////// -/// @brief optimise a constant filter expression +/// @brief optimise a constant FILTER expression //////////////////////////////////////////////////////////////////////////////// static TRI_aql_node_t* OptimiseConstantFilter (TRI_aql_node_t* const node) { @@ -405,12 +456,12 @@ static TRI_aql_node_t* OptimiseConstantFilter (TRI_aql_node_t* const node) { } //////////////////////////////////////////////////////////////////////////////// -/// @brief optimise a filter statement +/// @brief optimise a FILTER statement //////////////////////////////////////////////////////////////////////////////// static TRI_aql_node_t* OptimiseFilter (TRI_aql_statement_walker_t* const walker, TRI_aql_node_t* node) { - TRI_aql_context_t* context = (TRI_aql_context_t*) walker->_data; + aql_optimiser_t* optimiser = (aql_optimiser_t*) walker->_data; TRI_aql_node_t* expression = TRI_AQL_NODE_MEMBER(node, 0); while (true) { @@ -431,7 +482,7 @@ static TRI_aql_node_t* OptimiseFilter (TRI_aql_statement_walker_t* const walker, oldRanges = TRI_GetCurrentRangesStatementWalkerAql(walker); changed = false; - newRanges = TRI_OptimiseRangesAql(context, expression, &changed, oldRanges); + newRanges = TRI_OptimiseRangesAql(optimiser->_context, expression, &changed, oldRanges); if (newRanges) { TRI_SetCurrentRangesStatementWalkerAql(walker, newRanges); @@ -840,7 +891,7 @@ static TRI_aql_node_t* OptimiseTernaryOperation (TRI_aql_context_t* const contex static TRI_aql_node_t* OptimiseNode (TRI_aql_statement_walker_t* const walker, TRI_aql_node_t* node) { - TRI_aql_context_t* context = (TRI_aql_context_t*) walker->_data; + TRI_aql_context_t* context = ((aql_optimiser_t*) walker->_data)->_context; assert(node); @@ -910,7 +961,7 @@ static TRI_aql_node_t* OptimiseStatement (TRI_aql_statement_walker_t* const walk //////////////////////////////////////////////////////////////////////////////// static void PatchVariables (TRI_aql_statement_walker_t* const walker) { - TRI_aql_context_t* context = (TRI_aql_context_t*) walker->_data; + TRI_aql_context_t* context = ((aql_optimiser_t*) walker->_data)->_context; TRI_vector_pointer_t* ranges; size_t i, n; @@ -1012,21 +1063,21 @@ static TRI_aql_node_t* ProcessStatement (TRI_aql_statement_walker_t* const walke /// @brief optimise the AST, first iteration //////////////////////////////////////////////////////////////////////////////// -static bool OptimiseAst (TRI_aql_context_t* const context) { +static bool OptimiseAst (aql_optimiser_t* const optimiser) { TRI_aql_statement_walker_t* walker; - walker = TRI_CreateStatementWalkerAql((void*) context, + walker = TRI_CreateStatementWalkerAql((void*) optimiser, true, &OptimiseNode, NULL, &ProcessStatement); if (walker == NULL) { - TRI_SetErrorContextAql(context, TRI_ERROR_OUT_OF_MEMORY, NULL); + TRI_SetErrorContextAql(optimiser->_context, TRI_ERROR_OUT_OF_MEMORY, NULL); return false; } - TRI_WalkStatementsAql(walker, context->_statements); + TRI_WalkStatementsAql(walker, optimiser->_context->_statements); TRI_FreeStatementWalkerAql(walker); @@ -1037,21 +1088,21 @@ static bool OptimiseAst (TRI_aql_context_t* const context) { /// @brief determine which indexes to use in the query //////////////////////////////////////////////////////////////////////////////// -static bool DetermineIndexes (TRI_aql_context_t* const context) { +static bool DetermineIndexes (aql_optimiser_t* const optimiser) { TRI_aql_statement_walker_t* walker; - walker = TRI_CreateStatementWalkerAql((void*) context, + walker = TRI_CreateStatementWalkerAql((void*) optimiser, false, &AnnotateNode, NULL, NULL); if (walker == NULL) { - TRI_SetErrorContextAql(context, TRI_ERROR_OUT_OF_MEMORY, NULL); + TRI_SetErrorContextAql(optimiser->_context, TRI_ERROR_OUT_OF_MEMORY, NULL); return false; } - TRI_WalkStatementsAql(walker, context->_statements); + TRI_WalkStatementsAql(walker, optimiser->_context->_statements); TRI_FreeStatementWalkerAql(walker); @@ -1076,15 +1127,19 @@ static bool DetermineIndexes (TRI_aql_context_t* const context) { //////////////////////////////////////////////////////////////////////////////// bool TRI_OptimiseAql (TRI_aql_context_t* const context) { - if (!OptimiseAst(context)) { + aql_optimiser_t* optimiser; + bool result; + + optimiser = CreateOptimiser(context); + if (optimiser == NULL) { + TRI_SetErrorContextAql(context, TRI_ERROR_OUT_OF_MEMORY, NULL); return false; } - if (!DetermineIndexes(context)) { - return false; - } + result = (OptimiseAst(optimiser) && DetermineIndexes(optimiser)); + FreeOptimiser(optimiser); - return true; + return result; } //////////////////////////////////////////////////////////////////////////////// diff --git a/arangod/Ahuacatl/ahuacatl-optimiser.h b/arangod/Ahuacatl/ahuacatl-optimiser.h index 95a9e3560f..6c34401949 100644 --- a/arangod/Ahuacatl/ahuacatl-optimiser.h +++ b/arangod/Ahuacatl/ahuacatl-optimiser.h @@ -46,19 +46,6 @@ extern "C" { #endif -// ----------------------------------------------------------------------------- -// --SECTION-- public types -// ----------------------------------------------------------------------------- - -//////////////////////////////////////////////////////////////////////////////// -/// @addtogroup Ahuacatl -/// @{ -//////////////////////////////////////////////////////////////////////////////// - -//////////////////////////////////////////////////////////////////////////////// -/// @} -//////////////////////////////////////////////////////////////////////////////// - // ----------------------------------------------------------------------------- // --SECTION-- public functions // ----------------------------------------------------------------------------- diff --git a/arangod/Ahuacatl/ahuacatl-scope.c b/arangod/Ahuacatl/ahuacatl-scope.c index 6ee1583455..d0cc2dcf86 100644 --- a/arangod/Ahuacatl/ahuacatl-scope.c +++ b/arangod/Ahuacatl/ahuacatl-scope.c @@ -118,6 +118,7 @@ static TRI_aql_scope_t* CreateScope (TRI_aql_context_t* const context, scope->_id = NextId(context); scope->_type = NextType(context, type); scope->_ranges = NULL; + scope->_selfContained = true; TRI_InitAssociativePointer(&scope->_variables, TRI_UNKNOWN_MEM_ZONE, @@ -250,7 +251,7 @@ bool TRI_StartScopeAql (TRI_aql_context_t* const context, const TRI_aql_scope_e return false; } - if (!TRI_AddStatementListAql(context->_statements, node)) { + if (! TRI_AppendStatementListAql(context->_statements, node)) { return false; } @@ -279,7 +280,7 @@ bool TRI_EndScopeAql (TRI_aql_context_t* const context) { return false; } - if (!TRI_AddStatementListAql(context->_statements, node)) { + if (! TRI_AppendStatementListAql(context->_statements, node)) { return false; } @@ -318,7 +319,7 @@ bool TRI_EndScopeByReturnAql (TRI_aql_context_t* const context) { return false; } - if (!TRI_AddStatementListAql(context->_statements, node)) { + if (! TRI_AppendStatementListAql(context->_statements, node)) { return false; } @@ -388,7 +389,7 @@ bool TRI_AddVariableScopeAql (TRI_aql_context_t* const context, } scope = CurrentScope(context); - assert(!TRI_InsertKeyAssociativePointer(&scope->_variables, variable->_name, (void*) variable, false)); + assert(! TRI_InsertKeyAssociativePointer(&scope->_variables, variable->_name, (void*) variable, false)); return true; } diff --git a/arangod/Ahuacatl/ahuacatl-scope.h b/arangod/Ahuacatl/ahuacatl-scope.h index 645ff05d0c..5a66ba8d5f 100644 --- a/arangod/Ahuacatl/ahuacatl-scope.h +++ b/arangod/Ahuacatl/ahuacatl-scope.h @@ -68,6 +68,7 @@ typedef struct TRI_aql_scope_s { TRI_associative_pointer_t _variables; TRI_vector_pointer_t* _ranges; TRI_aql_scope_e _type; + bool _selfContained; size_t _id; } TRI_aql_scope_t; diff --git a/arangod/Ahuacatl/ahuacatl-statement-walker.c b/arangod/Ahuacatl/ahuacatl-statement-walker.c index 7f84853861..3101c4e120 100644 --- a/arangod/Ahuacatl/ahuacatl-statement-walker.c +++ b/arangod/Ahuacatl/ahuacatl-statement-walker.c @@ -226,6 +226,12 @@ TRI_vector_pointer_t* TRI_GetScopesStatementWalkerAql (TRI_aql_statement_walker_ //////////////////////////////////////////////////////////////////////////////// /// @brief return a pointer to a variable, identified by its name +/// +/// The variable will be searched in the current and the surrounding scopes. +/// If the variable is not found and the reference to it is coming from a +/// subquery scope, we'll mark the subquery as not self-contained. This prevents +/// moving of the subquery to some other position (which would break the query +/// in this case) //////////////////////////////////////////////////////////////////////////////// TRI_aql_variable_t* TRI_GetVariableStatementWalkerAql (TRI_aql_statement_walker_t* const walker, @@ -253,7 +259,14 @@ TRI_aql_variable_t* TRI_GetVariableStatementWalkerAql (TRI_aql_statement_walker_ if (n == 0 || scope->_type == TRI_AQL_SCOPE_SUBQUERY) { // reached the outermost scope - break; + if (scope->_type == TRI_AQL_SCOPE_SUBQUERY) { + // variable not found but we reached the end of the scope + // we must mark the scope as not self-contained so it is not moved to + // some other position + + scope->_selfContained = false; + } + return NULL; } // increase the scope counter diff --git a/arangod/Ahuacatl/ahuacatl-statementlist.c b/arangod/Ahuacatl/ahuacatl-statementlist.c index fce2f8330c..645c3a87cb 100644 --- a/arangod/Ahuacatl/ahuacatl-statementlist.c +++ b/arangod/Ahuacatl/ahuacatl-statementlist.c @@ -26,6 +26,9 @@ //////////////////////////////////////////////////////////////////////////////// #include "Ahuacatl/ahuacatl-statementlist.h" + +#include "BasicsC/logging.h" + #include "Ahuacatl/ahuacatl-ast-node.h" #include "Ahuacatl/ahuacatl-scope.h" @@ -177,11 +180,106 @@ TRI_aql_node_t* TRI_GetDummyReturnEmptyNodeAql (void) { } //////////////////////////////////////////////////////////////////////////////// -/// @brief add a statement to the statement list +/// @brief pull out uncorrelated subqueries from the middle of the statement +/// list to the beginning //////////////////////////////////////////////////////////////////////////////// -bool TRI_AddStatementListAql (TRI_aql_statement_list_t* const list, - TRI_aql_node_t* const node) { +void TRI_PulloutStatementListAql (TRI_aql_statement_list_t* const list) { + size_t i, n; + size_t scopes = 0; + size_t targetScope = 0; + size_t moveStart = 0; + bool watch = false; + + assert(list); + + i = 0; + n = list->_statements._length; + + while (i < n) { + TRI_aql_node_t* node = StatementAt(list, i); + TRI_aql_node_type_e type = node->_type; + + if (type == TRI_AQL_NODE_SCOPE_START) { + // node is a scope start + TRI_aql_scope_t* scope = (TRI_aql_scope_t*) TRI_AQL_NODE_DATA(node); + + if (scope->_type == TRI_AQL_SCOPE_SUBQUERY && scope->_selfContained) { + if (! watch && scopes > 0) { + watch = true; + targetScope = scopes; + moveStart = i; + } + } + + ++scopes; + } + else if (type == TRI_AQL_NODE_SCOPE_END) { + // node is a scope end + --scopes; + + if (watch && scopes == targetScope) { + watch = false; + + node = StatementAt(list, i + 1); + + // check if next statement is a subquery statement + if (i + 1 < n && node->_type == TRI_AQL_NODE_SUBQUERY) { + size_t j = moveStart; + size_t inserted = 0; + + // moving statements from the middle to the beginning of the list will also + // modify the positions we're moving from + while (j < i + 2) { + node = StatementAt(list, j + inserted); + if (! TRI_InsertStatementListAql(list, node, inserted + 0)) { + return; + } + + // insert a dummy node in place of the moved node + list->_statements._buffer[j + inserted + 1] = TRI_GetDummyNopNodeAql(); + + // next + ++j; + ++inserted; + } + + // moving statements from the middle to the beginning of the list will also + // change the list length and the position we'll be continuing from + n += inserted; + i = j + inserted; + } + + } + } + + ++i; + } +} + +//////////////////////////////////////////////////////////////////////////////// +/// @brief add a statement to the front of the statement list +//////////////////////////////////////////////////////////////////////////////// + +bool TRI_InsertStatementListAql (TRI_aql_statement_list_t* const list, + TRI_aql_node_t* const node, + const size_t position) { + int result; + + assert(list); + assert(node); + + result = TRI_InsertVectorPointer(&list->_statements, node, position); + + return result == TRI_ERROR_NO_ERROR; +} + +//////////////////////////////////////////////////////////////////////////////// +/// @brief add a statement to the end of the statement list +//////////////////////////////////////////////////////////////////////////////// + +bool TRI_AppendStatementListAql (TRI_aql_statement_list_t* const list, + TRI_aql_node_t* const node) { TRI_aql_node_type_e type; int result; diff --git a/arangod/Ahuacatl/ahuacatl-statementlist.h b/arangod/Ahuacatl/ahuacatl-statementlist.h index 8f59eac349..0a58a4c219 100644 --- a/arangod/Ahuacatl/ahuacatl-statementlist.h +++ b/arangod/Ahuacatl/ahuacatl-statementlist.h @@ -114,6 +114,13 @@ TRI_aql_node_t* TRI_GetDummyNopNodeAql (void); TRI_aql_node_t* TRI_GetDummyReturnEmptyNodeAql (void); +//////////////////////////////////////////////////////////////////////////////// +/// @brief pull out subqueries in the statement list from the middle to the +/// beginning +//////////////////////////////////////////////////////////////////////////////// + +void TRI_PulloutStatementListAql (TRI_aql_statement_list_t* const); + //////////////////////////////////////////////////////////////////////////////// /// @brief remove all non-ops from the statement list /// @@ -131,11 +138,19 @@ size_t TRI_InvalidateStatementListAql (TRI_aql_statement_list_t* const, const size_t); //////////////////////////////////////////////////////////////////////////////// -/// @brief add a statement to the statement list +/// @brief insert a statement into the statement list //////////////////////////////////////////////////////////////////////////////// -bool TRI_AddStatementListAql (TRI_aql_statement_list_t* const list, - TRI_aql_node_t* const); +bool TRI_InsertStatementListAql (TRI_aql_statement_list_t* const, + TRI_aql_node_t* const, + const size_t); + +//////////////////////////////////////////////////////////////////////////////// +/// @brief add a statement to the end of the statement list +//////////////////////////////////////////////////////////////////////////////// + +bool TRI_AppendStatementListAql (TRI_aql_statement_list_t* const, + TRI_aql_node_t* const); //////////////////////////////////////////////////////////////////////////////// /// @} diff --git a/js/server/tests/ahuacatl-subquery.js b/js/server/tests/ahuacatl-subquery.js new file mode 100644 index 0000000000..4ecbb3659a --- /dev/null +++ b/js/server/tests/ahuacatl-subquery.js @@ -0,0 +1,173 @@ +//////////////////////////////////////////////////////////////////////////////// +/// @brief tests for Ahuacatl, subqueries +/// +/// @file +/// +/// DISCLAIMER +/// +/// Copyright 2010-2012 triagens GmbH, Cologne, Germany +/// +/// Licensed under the Apache License, Version 2.0 (the "License"); +/// you may not use this file except in compliance with the License. +/// You may obtain a copy of the License at +/// +/// http://www.apache.org/licenses/LICENSE-2.0 +/// +/// Unless required by applicable law or agreed to in writing, software +/// distributed under the License is distributed on an "AS IS" BASIS, +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +/// See the License for the specific language governing permissions and +/// limitations under the License. +/// +/// Copyright holder is triAGENS GmbH, Cologne, Germany +/// +/// @author Jan Steemann +/// @author Copyright 2012, triAGENS GmbH, Cologne, Germany +//////////////////////////////////////////////////////////////////////////////// + +var internal = require("internal"); +var jsunity = require("jsunity"); + +//////////////////////////////////////////////////////////////////////////////// +/// @brief test suite +//////////////////////////////////////////////////////////////////////////////// + +function ahuacatlSubqueryTestSuite () { + +//////////////////////////////////////////////////////////////////////////////// +/// @brief execute a given query +//////////////////////////////////////////////////////////////////////////////// + + function executeQuery (query, bindVars) { + var cursor = AHUACATL_RUN(query, bindVars); + return cursor; + } + +//////////////////////////////////////////////////////////////////////////////// +/// @brief execute a given query and return the results as an array +//////////////////////////////////////////////////////////////////////////////// + + function getQueryResults (query, bindVars) { + var result = executeQuery(query, bindVars).getRows(); + var results = [ ]; + + for (var i in result) { + if (!result.hasOwnProperty(i)) { + continue; + } + + results.push(result[i]); + } + + return results; + } + + + return { + +//////////////////////////////////////////////////////////////////////////////// +/// @brief set up +//////////////////////////////////////////////////////////////////////////////// + + setUp : function () { + }, + +//////////////////////////////////////////////////////////////////////////////// +/// @brief tear down +//////////////////////////////////////////////////////////////////////////////// + + tearDown : function () { + }, + +//////////////////////////////////////////////////////////////////////////////// +/// @brief test subquery evaluation +//////////////////////////////////////////////////////////////////////////////// + + testSubqueryDependent1 : function () { + var expected = [ 2, 4, 6 ]; + var actual = getQueryResults("FOR i IN [ 1, 2, 3 ] LET s = (FOR j IN [ 1, 2, 3 ] RETURN i * 2) RETURN s[i - 1]"); + + assertEqual(expected, actual); + }, + +//////////////////////////////////////////////////////////////////////////////// +/// @brief test subquery evaluation +//////////////////////////////////////////////////////////////////////////////// + + testSubqueryDependent2 : function () { + var expected = [ 2, 4, 6 ]; + var actual = getQueryResults("FOR i IN [ 1, 2, 3 ] LET t = (FOR k IN [ 1, 2, 3 ] RETURN k) LET s = (FOR j IN [ 1, 2, 3 ] RETURN t[i - 1] * 2) RETURN s[i - 1]"); + + assertEqual(expected, actual); + }, + +//////////////////////////////////////////////////////////////////////////////// +/// @brief test subquery evaluation +//////////////////////////////////////////////////////////////////////////////// + + testSubqueryDependent3 : function () { + var expected = [ 4, 5, 6, 8, 10, 12, 12, 15, 18 ]; + var actual = getQueryResults("FOR i IN [ 1, 2, 3 ] FOR j IN [ 4, 5, 6 ] LET s = (FOR k IN [ 1 ] RETURN i * j) RETURN s[0]"); + + assertEqual(expected, actual); + }, + +//////////////////////////////////////////////////////////////////////////////// +/// @brief test subquery optimisation +//////////////////////////////////////////////////////////////////////////////// + + testSubqueryIndependent1 : function () { + var expected = [ 9, 8, 7 ]; + var actual = getQueryResults("FOR i IN [ 1, 2, 3 ] LET s = (FOR j IN [ 9, 8, 7 ] RETURN j) RETURN s[i - 1]"); + + assertEqual(expected, actual); + }, + +//////////////////////////////////////////////////////////////////////////////// +/// @brief test subquery optimisation +//////////////////////////////////////////////////////////////////////////////// + + testSubqueryIndependent2 : function () { + var expected = [ [ 9, 8, 7 ] ]; + var actual = getQueryResults("LET s = (FOR j IN [ 9, 8, 7 ] RETURN j) RETURN s"); + + assertEqual(expected, actual); + }, + +//////////////////////////////////////////////////////////////////////////////// +/// @brief test subquery optimisation +//////////////////////////////////////////////////////////////////////////////// + + testSubqueryIndependent3 : function () { + var expected = [ [ 25 ], [ 25 ], [ 25 ], [ 25 ], [ 25 ], [ 25 ], [ 25 ], [ 25 ], [ 25 ] ]; + var actual = getQueryResults("FOR i IN [ 1, 2, 3 ] FOR j IN [ 1, 2, 3 ] LET s = (FOR k IN [ 25 ] RETURN k) RETURN s"); + + assertEqual(expected, actual); + }, + +//////////////////////////////////////////////////////////////////////////////// +/// @brief test subquery optimisation +//////////////////////////////////////////////////////////////////////////////// + + testSubqueryIndependent4 : function () { + var expected = [ [ 2, 4, 6 ] ]; + var actual = getQueryResults("LET a = (FOR i IN [ 1, 2, 3 ] LET s = (FOR j IN [ 1, 2 ] RETURN j) RETURN i * s[1]) RETURN a"); + + assertEqual(expected, actual); + }, + + }; +} + +//////////////////////////////////////////////////////////////////////////////// +/// @brief executes the test suite +//////////////////////////////////////////////////////////////////////////////// + +jsunity.run(ahuacatlSubqueryTestSuite); + +return jsunity.done(); + +// Local Variables: +// mode: outline-minor +// outline-regexp: "^\\(/// @brief\\|/// @addtogroup\\|// --SECTION--\\|/// @page\\|/// @}\\)" +// End: