//////////////////////////////////////////////////////////////////////////////// /// @brief Ahuacatl, optimiser /// /// @file /// /// DISCLAIMER /// /// Copyright 2014 ArangoDB GmbH, Cologne, Germany /// Copyright 2004-2014 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 ArangoDB GmbH, Cologne, Germany /// /// @author Jan Steemann /// @author Copyright 2014, ArangoDB GmbH, Cologne, Germany /// @author Copyright 2012-2013, triAGENS GmbH, Cologne, Germany //////////////////////////////////////////////////////////////////////////////// #include "Ahuacatl/ahuacatl-optimiser.h" #include "Basics/logging.h" #include "Basics/tri-strings.h" #include "Ahuacatl/ahuacatl-ast-node.h" #include "Ahuacatl/ahuacatl-access-optimiser.h" #include "Ahuacatl/ahuacatl-collections.h" #include "Ahuacatl/ahuacatl-conversions.h" #include "Ahuacatl/ahuacatl-functions.h" #include "Ahuacatl/ahuacatl-scope.h" #include "Ahuacatl/ahuacatl-statement-walker.h" #include "Ahuacatl/ahuacatl-variable.h" #include "VocBase/document-collection.h" #include "VocBase/index.h" #include "V8/v8-execution.h" // ----------------------------------------------------------------------------- // --SECTION-- forwards // ----------------------------------------------------------------------------- static TRI_aql_node_t* OptimiseNode (TRI_aql_statement_walker_t* const, TRI_aql_node_t*); static TRI_aql_node_t* ProcessStatement (TRI_aql_statement_walker_t* const, TRI_aql_node_t*); // ----------------------------------------------------------------------------- // --SECTION-- private defines // ----------------------------------------------------------------------------- //////////////////////////////////////////////////////////////////////////////// /// @brief maximum buffer length when comparing sort conditions //////////////////////////////////////////////////////////////////////////////// #define COMPARE_LENGTH 128 // ----------------------------------------------------------------------------- // --SECTION-- private types // ----------------------------------------------------------------------------- //////////////////////////////////////////////////////////////////////////////// /// @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 // ----------------------------------------------------------------------------- //////////////////////////////////////////////////////////////////////////////// /// @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) { TRI_ASSERT(optimiser); TRI_Free(TRI_UNKNOWN_MEM_ZONE, optimiser); } //////////////////////////////////////////////////////////////////////////////// /// @brief get a vector of sort criteria from a SORT statement in stringified /// format (e.g. ["u.name", "u._id"] //////////////////////////////////////////////////////////////////////////////// static bool GetSorts (TRI_aql_statement_walker_t* const walker, const TRI_aql_node_t* const node, TRI_vector_pointer_t* const sorts) { TRI_aql_node_t* list = TRI_AQL_NODE_MEMBER(node, 0); TRI_string_buffer_t buffer; size_t i; if (! list || list->_members._length == 0) { // no sorts defined. this should not happen if we get here return false; } TRI_InitStringBuffer(&buffer, TRI_UNKNOWN_MEM_ZONE); for (i = 0; i < list->_members._length; ++i) { // sort element TRI_aql_node_t* element = TRI_AQL_NODE_MEMBER(list, i); TRI_aql_node_t* expression = TRI_AQL_NODE_MEMBER(element, 0); if (! expression) { TRI_DestroyStringBuffer(&buffer); return false; } // check sort order. we can only optimise ascending sorts if (TRI_AQL_NODE_BOOL(element)) { // order is ascending char* copy; TRI_ClearStringBuffer(&buffer); TRI_NodeStringAql(&buffer, expression); copy = TRI_DuplicateStringZ(TRI_UNKNOWN_MEM_ZONE, buffer._buffer); if (copy == NULL) { // out of memory TRI_DestroyStringBuffer(&buffer); return false; } TRI_PushBackVectorPointer(sorts, copy); } else { // order is descending TRI_DestroyStringBuffer(&buffer); return false; } } TRI_DestroyStringBuffer(&buffer); return true; } //////////////////////////////////////////////////////////////////////////////// /// @brief check if the rhs vector of sort conditions is fully contained in the /// lhs vector of sort conditions //////////////////////////////////////////////////////////////////////////////// static bool IsSortContained (const TRI_vector_pointer_t* const lhs, const TRI_vector_pointer_t* const rhs) { size_t i; for (i = 0; i < rhs->_length; ++i) { char* lhsName = (char*) TRI_AtVectorPointer(lhs, i); char* rhsName = (char*) TRI_AtVectorPointer(rhs, i); if (! TRI_EqualString(lhsName, rhsName)) { return false; } } return true; } //////////////////////////////////////////////////////////////////////////////// /// @brief check if a sort can be avoided. this compares sort criteria /// implicitly introduced by using an index with explicit sort criteria defined /// by a SORT statement. If the SORT criteria is fully covered by using the /// index, the SORT statement can be avoided //////////////////////////////////////////////////////////////////////////////// static bool CanAvoidSort (TRI_aql_statement_walker_t* const walker, const TRI_aql_node_t* const node) { TRI_aql_scope_t* scope; TRI_vector_pointer_t nodeSorts; size_t i; bool canUse; bool result; scope = TRI_GetCurrentScopeStatementWalkerAql(walker); TRI_ASSERT(scope != NULL); if (scope->_type == TRI_AQL_SCOPE_MAIN) { // will not optimise main scope return false; } if (scope->_sorts._length == 0) { // no sort criteria collected return false; } TRI_InitVectorPointer(&nodeSorts, TRI_UNKNOWN_MEM_ZONE); result = false; // get all sort criteria from the SORT statement canUse = GetSorts(walker, node, &nodeSorts); if (canUse && nodeSorts._length > 0 && nodeSorts._length <= scope->_sorts._length) { result = IsSortContained(&scope->_sorts, &nodeSorts); } // free all sort criteria fetched from the node for (i = 0; i < nodeSorts._length; ++i) { char* criterion = (char*) TRI_AtVectorPointer(&nodeSorts, i); TRI_Free(TRI_UNKNOWN_MEM_ZONE, criterion); } TRI_DestroyVectorPointer(&nodeSorts); return result; } //////////////////////////////////////////////////////////////////////////////// /// @brief pick an index for the ranges found //////////////////////////////////////////////////////////////////////////////// static void AttachCollectionHint (TRI_aql_context_t* const context, TRI_aql_node_t* const node) { TRI_aql_node_t* nameNode = TRI_AQL_NODE_MEMBER(node, 0); TRI_vector_pointer_t* availableIndexes; TRI_aql_collection_hint_t* hint; TRI_aql_index_t* idx; TRI_aql_collection_t* collection; char* collectionName; collectionName = TRI_AQL_NODE_STRING(nameNode); TRI_ASSERT(collectionName); hint = (TRI_aql_collection_hint_t*) TRI_AQL_NODE_DATA(node); if (hint == NULL) { TRI_SetErrorContextAql(__FILE__, __LINE__, context, TRI_ERROR_OUT_OF_MEMORY, NULL); return; } if (hint->_ranges == NULL) { // no ranges found to be used as indexes return; } collection = TRI_GetCollectionAql(context, collectionName); if (collection == NULL) { TRI_SetErrorContextAql(__FILE__, __LINE__, context, TRI_ERROR_OUT_OF_MEMORY, NULL); return; } availableIndexes = TRI_GetIndexesCollectionAql(context, collection); if (! context->_isCoordinator && availableIndexes == NULL) { return; } hint->_collection = collection; if (availableIndexes == NULL) { TRI_SetErrorContextAql(__FILE__, __LINE__, context, TRI_ERROR_OUT_OF_MEMORY, NULL); return; } idx = TRI_DetermineIndexAql(context, availableIndexes, collectionName, hint->_ranges); hint->_index = idx; } //////////////////////////////////////////////////////////////////////////////// /// @brief copy the sort criteria implicitly introduced by using an index to /// the current scope. when we encounter a SORT statement, we'll compare the /// sort criteria in the statement and in the scope, and if they match, we /// can avoid the extra sorting //////////////////////////////////////////////////////////////////////////////// static void AttachSort (TRI_aql_scope_t* const scope, const TRI_aql_collection_hint_t* const hint) { TRI_index_t* idx; char tmp[COMPARE_LENGTH + 2]; size_t offset; size_t i; TRI_ASSERT(hint); TRI_ASSERT(scope); if (scope->_type == TRI_AQL_SCOPE_MAIN) { // will not optimise sort in main scope return; } if (scope->_sorts._length > 0) { // sorts already defined for the scope. do not overwrite return; } if (hint->_variableName == NULL) { // no variable name set up for the hint. we don't know enough about it to use it return; } if (hint->_index == NULL || hint->_index->_idx == NULL) { return; } idx = hint->_index->_idx; // got a valid index access for the collection // we are looking for skiplist indexes only, as they are sorted if (idx->_type != TRI_IDX_TYPE_SKIPLIST_INDEX) { // not a skiplist return; } // offset for variable name, containing the dot (e.g. 2 for "u") offset = strlen(hint->_variableName); if (offset > COMPARE_LENGTH) { // variable name is too long return; } // copy variable name into buffer for comparisions memcpy(tmp, hint->_variableName, offset); tmp[offset++] = '.'; for (i = 0; i < idx->_fields._length; ++i) { char* indexName = (char*) idx->_fields._buffer[i]; char* copy; size_t len; len = strlen(indexName); if (len + offset > COMPARE_LENGTH) { // name is too long return; } memcpy(tmp + offset, indexName, len); tmp[offset + len] = '\0'; copy = TRI_DuplicateString2Z(TRI_UNKNOWN_MEM_ZONE, tmp, offset + len); if (copy == NULL) { // out of memory. now free all criteria we have collected size_t j; for (j = 0; j < scope->_sorts._length; ++j) { char* criterion = (char*) scope->_sorts._buffer[j]; if (criterion != NULL) { TRI_Free(TRI_UNKNOWN_MEM_ZONE, criterion); } } return; } TRI_PushBackVectorPointer(&scope->_sorts, copy); } } //////////////////////////////////////////////////////////////////////////////// /// @brief annotate a node with context information /// /// this is a callback function used by the statement walker //////////////////////////////////////////////////////////////////////////////// static TRI_aql_node_t* AnnotateNode (TRI_aql_statement_walker_t* const walker, TRI_aql_node_t* node) { if (node->_type == TRI_AQL_NODE_COLLECTION) { aql_optimiser_t* optimiser; TRI_aql_scope_t* scope; TRI_aql_collection_hint_t* hint; optimiser = (aql_optimiser_t*) walker->_data; AttachCollectionHint(optimiser->_context, node); hint = (TRI_aql_collection_hint_t*) TRI_AQL_NODE_DATA(node); scope = TRI_GetCurrentScopeStatementWalkerAql(walker); TRI_ASSERT(scope != NULL); // check if an index is to be used if (hint != NULL) { AttachSort(scope, hint); } // check if we can apply a scope limit and push it into the collection if (scope->_limit._status == TRI_AQL_LIMIT_USE && ! scope->_limit._hasFilter) { // yes! LOG_TRACE("using limit hint for collection"); if (hint != NULL) { hint->_limit._offset = scope->_limit._offset; hint->_limit._limit = scope->_limit._limit; hint->_limit._status = TRI_AQL_LIMIT_USE; } // deactive this limit for any further tries scope->_limit._status = TRI_AQL_LIMIT_IGNORE; } } return node; } //////////////////////////////////////////////////////////////////////////////// /// @brief annotate a statement with context information /// /// this is a callback function used by the statement walker //////////////////////////////////////////////////////////////////////////////// static TRI_aql_node_t* AnnotateLoop (TRI_aql_statement_walker_t* const walker, TRI_aql_node_t* node) { if (node->_type == TRI_AQL_NODE_FOR) { TRI_aql_scope_t* scope; scope = TRI_GetCurrentScopeStatementWalkerAql(walker); TRI_ASSERT(scope != NULL); // check if we can apply a scope limit and push it into the for loop if (scope->_limit._status == TRI_AQL_LIMIT_USE) { // yes! TRI_aql_node_t* expression = TRI_AQL_NODE_MEMBER(node, 1); if (expression->_type == TRI_AQL_NODE_COLLECTION && ! scope->_limit._hasFilter) { // move limit into the COLLECTION node TRI_aql_collection_hint_t* hint = (TRI_aql_collection_hint_t*) TRI_AQL_NODE_DATA(expression); if (hint != NULL) { hint->_limit._offset = scope->_limit._offset; hint->_limit._limit = scope->_limit._limit; hint->_limit._status = TRI_AQL_LIMIT_USE; hint->_limit._hasFilter = scope->_limit._hasFilter; } } else { // move limit into the FOR node TRI_aql_for_hint_t* hint = (TRI_aql_for_hint_t*) TRI_AQL_NODE_DATA(node); if (hint != NULL) { // we'll now modify the hint for the for loop hint->_limit._offset = scope->_limit._offset; hint->_limit._limit = scope->_limit._limit; hint->_limit._status = TRI_AQL_LIMIT_USE; hint->_limit._hasFilter = scope->_limit._hasFilter; LOG_TRACE("using limit hint for for loop"); } } // deactive this limit for any further tries scope->_limit._status = TRI_AQL_LIMIT_IGNORE; } } else if (node->_type == TRI_AQL_NODE_SORT) { // we have found a SORT statement // now check if we can avoid it. this will be the case if we access the // collection via a sorted index (skiplist) TRI_aql_scope_t* scope; if (CanAvoidSort(walker, node)) { LOG_TRACE("removing unnecessary sort statement"); return TRI_GetDummyNopNodeAql(); } // we have found a SORT statement, but it must be preserved // we must now free the sort criteria we collected for the scope // otherwise we would get potentially invalid SORT results scope = TRI_GetCurrentScopeStatementWalkerAql(walker); TRI_ASSERT(scope != NULL); while (scope->_sorts._length > 0) { char* criterion = static_cast (TRI_RemoveVectorPointer(&scope->_sorts, (size_t) (scope->_sorts._length - 1))); if (criterion != NULL) { TRI_Free(TRI_UNKNOWN_MEM_ZONE, criterion); } } } return node; } //////////////////////////////////////////////////////////////////////////////// /// @brief create javascript function code for a relational operation //////////////////////////////////////////////////////////////////////////////// static TRI_string_buffer_t* RelationCode (const char* const name, const TRI_aql_node_t* const lhs, const TRI_aql_node_t* const rhs) { TRI_string_buffer_t* buffer; if (lhs == NULL || rhs == NULL) { return NULL; } buffer = TRI_CreateStringBuffer(TRI_UNKNOWN_MEM_ZONE); if (buffer == NULL) { return NULL; } if (TRI_AppendStringStringBuffer(buffer, "(function(){ var aql = require(\"org/arangodb/ahuacatl\"); return aql.RELATIONAL_") != TRI_ERROR_NO_ERROR) { TRI_FreeStringBuffer(TRI_UNKNOWN_MEM_ZONE, buffer); return NULL; } if (TRI_AppendStringStringBuffer(buffer, name) != TRI_ERROR_NO_ERROR) { TRI_FreeStringBuffer(TRI_UNKNOWN_MEM_ZONE, buffer); return NULL; } if (TRI_AppendStringStringBuffer(buffer, "(") != TRI_ERROR_NO_ERROR) { TRI_FreeStringBuffer(TRI_UNKNOWN_MEM_ZONE, buffer); return NULL; } if (! TRI_NodeJavascriptAql(buffer, lhs)) { TRI_FreeStringBuffer(TRI_UNKNOWN_MEM_ZONE, buffer); return NULL; } if (TRI_AppendCharStringBuffer(buffer, ',') != TRI_ERROR_NO_ERROR) { TRI_FreeStringBuffer(TRI_UNKNOWN_MEM_ZONE, buffer); return NULL; } if (! TRI_NodeJavascriptAql(buffer, rhs)) { TRI_FreeStringBuffer(TRI_UNKNOWN_MEM_ZONE, buffer); return NULL; } if (TRI_AppendStringStringBuffer(buffer, ");})") != TRI_ERROR_NO_ERROR) { TRI_FreeStringBuffer(TRI_UNKNOWN_MEM_ZONE, buffer); return NULL; } return buffer; } //////////////////////////////////////////////////////////////////////////////// /// @brief create javascript function code for a function call //////////////////////////////////////////////////////////////////////////////// static TRI_string_buffer_t* FcallCode (const char* const name, const TRI_aql_node_t* const args) { TRI_string_buffer_t* buffer = TRI_CreateStringBuffer(TRI_UNKNOWN_MEM_ZONE); size_t i; size_t n; if (! buffer) { return NULL; } if (TRI_AppendStringStringBuffer(buffer, "(function(){ var aql = require(\"org/arangodb/ahuacatl\"); return aql.") != TRI_ERROR_NO_ERROR) { TRI_FreeStringBuffer(TRI_UNKNOWN_MEM_ZONE, buffer); return NULL; } if (TRI_AppendStringStringBuffer(buffer, name) != TRI_ERROR_NO_ERROR) { TRI_FreeStringBuffer(TRI_UNKNOWN_MEM_ZONE, buffer); return NULL; } if (TRI_AppendCharStringBuffer(buffer, '(') != TRI_ERROR_NO_ERROR) { TRI_FreeStringBuffer(TRI_UNKNOWN_MEM_ZONE, buffer); return NULL; } n = args->_members._length; for (i = 0; i < n; ++i) { TRI_aql_node_t* arg = (TRI_aql_node_t*) args->_members._buffer[i]; if (i > 0) { if (TRI_AppendCharStringBuffer(buffer, ',') != TRI_ERROR_NO_ERROR) { TRI_FreeStringBuffer(TRI_UNKNOWN_MEM_ZONE, buffer); return NULL; } } if (! TRI_NodeJavascriptAql(buffer, arg)) { TRI_FreeStringBuffer(TRI_UNKNOWN_MEM_ZONE, buffer); return NULL; } } if (TRI_AppendStringStringBuffer(buffer, ");})") != TRI_ERROR_NO_ERROR) { TRI_FreeStringBuffer(TRI_UNKNOWN_MEM_ZONE, buffer); return NULL; } return buffer; } //////////////////////////////////////////////////////////////////////////////// /// @brief optimise a function call //////////////////////////////////////////////////////////////////////////////// static TRI_aql_node_t* OptimiseFcall (TRI_aql_context_t* const context, TRI_aql_node_t* node) { TRI_aql_node_t* args = TRI_AQL_NODE_MEMBER(node, 0); TRI_aql_function_t* function; TRI_js_exec_context_t* execContext; TRI_string_buffer_t* code; TRI_json_t* json; size_t i, n; int res; function = (TRI_aql_function_t*) TRI_AQL_NODE_DATA(node); TRI_ASSERT(function); // check if function is deterministic if (! function->_isDeterministic) { return node; } // check if function call arguments are deterministic n = args->_members._length; for (i = 0; i < n; ++i) { TRI_aql_node_t* arg = (TRI_aql_node_t*) args->_members._buffer[i]; if (! arg || ! TRI_IsConstantValueNodeAql(arg)) { return node; } } // all arguments are constants // create the function code code = FcallCode(function->_internalName, args); if (code == NULL) { TRI_SetErrorContextAql(__FILE__, __LINE__, context, TRI_ERROR_OUT_OF_MEMORY, NULL); return node; } // execute the function code execContext = TRI_CreateExecutionContext(TRI_BeginStringBuffer(code), TRI_LengthStringBuffer(code)); TRI_FreeStringBuffer(TRI_UNKNOWN_MEM_ZONE, code); if (execContext == NULL) { TRI_SetErrorContextAql(__FILE__, __LINE__, context, TRI_ERROR_OUT_OF_MEMORY, NULL); return node; } res = execContext->_error; if (res != TRI_ERROR_NO_ERROR) { TRI_FreeExecutionContext(execContext); TRI_SetErrorContextAql(__FILE__, __LINE__, context, res, NULL); return node; } json = TRI_ExecuteResultContext(execContext); res = execContext->_error; TRI_FreeExecutionContext(execContext); if (res == TRI_ERROR_REQUEST_CANCELED) { TRI_SetErrorContextAql(__FILE__, __LINE__, context, res, NULL); return node; } if (json == NULL) { // cannot optimise the function call due to an internal error // TODO: check whether we can validate the arguments here already and return an error early // TRI_SetErrorContextAql(__FILE__, __LINE__, context, TRI_ERROR_QUERY_SCRIPT, "function optimisation"); return node; } // use the constant values instead of the function call node node = TRI_JsonNodeAql(context, json); if (node == NULL) { TRI_SetErrorContextAql(__FILE__, __LINE__, context, TRI_ERROR_OUT_OF_MEMORY, NULL); } TRI_FreeJson(TRI_UNKNOWN_MEM_ZONE, json); LOG_TRACE("optimised function call"); return node; } //////////////////////////////////////////////////////////////////////////////// /// @brief optimise a FOR statement /// this will remove the complete statement if the value to iterate over is /// an empty list //////////////////////////////////////////////////////////////////////////////// static TRI_aql_node_t* OptimiseFor (TRI_aql_statement_walker_t* const walker, TRI_aql_node_t* node) { TRI_aql_node_t* expression = TRI_AQL_NODE_MEMBER(node, 1); if (expression->_type == TRI_AQL_NODE_LIST && expression->_members._length == 0) { // for statement with a list expression // list is empty => we can eliminate the for statement LOG_TRACE("optimised away empty for loop"); TRI_EmptyScopeStatementWalkerAql(walker); } return node; } //////////////////////////////////////////////////////////////////////////////// /// @brief optimise a SORT statement /// this will remove constant sort expressions (which should not have /// any impact on the result) //////////////////////////////////////////////////////////////////////////////// static TRI_aql_node_t* OptimiseSort (TRI_aql_statement_walker_t* const walker, TRI_aql_node_t* node) { TRI_aql_node_t* list = TRI_AQL_NODE_MEMBER(node, 0); size_t i, n; if (! list) { return node; } i = 0; n = list->_members._length; while (i < n) { // sort element TRI_aql_node_t* element = TRI_AQL_NODE_MEMBER(list, i); TRI_aql_node_t* expression = TRI_AQL_NODE_MEMBER(element, 0); // check if the sort element is constant if (! expression || ! TRI_IsConstantValueNodeAql(expression)) { ++i; continue; } // sort element is constant so it can be removed TRI_RemoveVectorPointer(&list->_members, i); --n; LOG_TRACE("optimised away sort element"); } if (n == 0) { // no members left => sort removed LOG_TRACE("optimised away sort"); return TRI_GetDummyNopNodeAql(); } // if we got here, at least one sort criterion remained return node; } //////////////////////////////////////////////////////////////////////////////// /// @brief optimise a LIMIT statement /// optimise away a limit x, 0 statement //////////////////////////////////////////////////////////////////////////////// static TRI_aql_node_t* OptimiseLimit (TRI_aql_statement_walker_t* const walker, TRI_aql_node_t* node) { TRI_aql_scope_t* scope; TRI_aql_node_t* limit; aql_optimiser_t* optimiser = (aql_optimiser_t*) walker->_data; int64_t limitValue; TRI_ASSERT(node); scope = TRI_GetCurrentScopeStatementWalkerAql(walker); TRI_ASSERT(scope != NULL); limit = TRI_AQL_NODE_MEMBER(node, 1); if (limit->_type != TRI_AQL_NODE_VALUE) { return node; } if (limit->_value._type == TRI_AQL_TYPE_INT) { limitValue = TRI_AQL_NODE_INT(limit); } else if (limit->_value._type == TRI_AQL_TYPE_DOUBLE) { limitValue = (int64_t) TRI_AQL_NODE_DOUBLE(limit); } else if (limit->_value._type == TRI_AQL_TYPE_NULL) { limitValue = 0; } else if (limit->_value._type == TRI_AQL_TYPE_BOOL) { limitValue = (int64_t) TRI_AQL_NODE_BOOL(limit); } else { TRI_SetErrorContextAql(__FILE__, __LINE__, optimiser->_context, TRI_ERROR_QUERY_NUMBER_OUT_OF_RANGE, NULL); return node; } // check for the easy case, a limit value of 0, e.g. LIMIT 10, 0 if (limitValue == 0) { // LIMIT x, 0 makes the complete scope useless LOG_TRACE("optimised away limit"); TRI_EmptyScopeStatementWalkerAql(walker); return node; } // we will not optimise in the main scope, e.g. LIMIT 5 RETURN 1 if (scope->_type == TRI_AQL_SCOPE_MAIN || scope->_type == TRI_AQL_SCOPE_FOR_NESTED) { return node; } // now for the more complex checks // is a limit optimisation allowed in the scope? if (scope->_limit._status == TRI_AQL_LIMIT_USE || scope->_limit._status == TRI_AQL_LIMIT_UNDEFINED) { // we have found a limit that we can potentially use // we'll only push up the first limit in a scope, as there might be queries such as LIMIT 10 LIMIT 2 if (++scope->_limit._found == 1) { // we can push the limit up, into the for loop or the collection access LOG_TRACE("pushed up limit"); return TRI_GetDummyNopNodeAql(); } } return node; } //////////////////////////////////////////////////////////////////////////////// /// @brief optimise a constant FILTER expression /// constant filters that evaluate to true will be replaced by a NOP node /// constant filters that evaluate to false will be replaced by an empty node /// that will remove the complete scope on statement list compaction //////////////////////////////////////////////////////////////////////////////// static TRI_aql_node_t* OptimiseConstantFilter (TRI_aql_statement_walker_t* const walker, TRI_aql_node_t* const node, TRI_aql_node_t* const expression) { if (TRI_GetBooleanNodeValueAql(expression)) { // filter expression is always true => remove it LOG_TRACE("optimised away constant (true) filter"); return TRI_GetDummyNopNodeAql(); } // filter expression is always false => invalidate surrounding scope(s) LOG_TRACE("optimised away scope"); TRI_EmptyScopeStatementWalkerAql(walker); return node; } //////////////////////////////////////////////////////////////////////////////// /// @brief optimise a FILTER statement //////////////////////////////////////////////////////////////////////////////// static TRI_aql_node_t* OptimiseFilter (TRI_aql_statement_walker_t* const walker, TRI_aql_node_t* node) { aql_optimiser_t* optimiser = (aql_optimiser_t*) walker->_data; TRI_aql_node_t* expression = TRI_AQL_NODE_MEMBER(node, 0); TRI_aql_scope_t* scope; while (true) { TRI_vector_pointer_t* oldRanges; TRI_vector_pointer_t* newRanges; bool changed; if (! expression) { return node; } if (TRI_IsConstantValueNodeAql(expression)) { // filter expression is a constant value return OptimiseConstantFilter(walker, node, expression); } // filter expression is non-constant oldRanges = TRI_GetCurrentRangesStatementWalkerAql(walker); changed = false; newRanges = TRI_OptimiseRangesAql(optimiser->_context, expression, &changed, oldRanges); if (newRanges) { TRI_SetCurrentRangesStatementWalkerAql(walker, newRanges); } if (! changed) { break; } // expression code was changed, set pointer to new value re-optimise it node->_members._buffer[0] = OptimiseNode(walker, expression); expression = TRI_AQL_NODE_MEMBER(node, 0); // next iteration } // in case we got here, the filter was not optimised away completely scope = TRI_GetCurrentScopeStatementWalkerAql(walker); TRI_ASSERT(scope != NULL); // mark in the scope that we found a filter scope->_limit._hasFilter = true; return node; } //////////////////////////////////////////////////////////////////////////////// /// @brief optimise a reference expression /// /// this looks up the source node that defines the variable and checks if the /// variable has a constant value. if yes, then the reference is replaced with /// the constant value /// e.g. in the query "let a = 1 for x in a ...", the latter a would be replaced /// by the value "1". //////////////////////////////////////////////////////////////////////////////// static TRI_aql_node_t* OptimiseReference (TRI_aql_statement_walker_t* const walker, TRI_aql_node_t* node) { TRI_aql_variable_t* variable; TRI_aql_node_t* definingNode; char* variableName = (char*) TRI_AQL_NODE_STRING(node); size_t scopeCount; // ignored TRI_ASSERT(variableName); variable = TRI_GetVariableStatementWalkerAql(walker, variableName, &scopeCount); if (variable == NULL) { return node; } if (variable->_isUpdated) { // do not optimise variables that are updated, such as LET b = b + 1 return node; } definingNode = variable->_definingNode; if (definingNode == NULL) { return node; } if (definingNode->_type == TRI_AQL_NODE_LET) { // variable is defined via a let TRI_aql_node_t* expressionNode; expressionNode = TRI_AQL_NODE_MEMBER(definingNode, 1); if (expressionNode && TRI_IsConstantValueNodeAql(expressionNode)) { // the source variable is constant, so we can replace the reference with // the source's value return expressionNode; } } return node; } //////////////////////////////////////////////////////////////////////////////// /// @brief optimise an arithmetic operation with one operand //////////////////////////////////////////////////////////////////////////////// static TRI_aql_node_t* OptimiseUnaryArithmeticOperation (TRI_aql_context_t* const context, TRI_aql_node_t* node) { TRI_aql_node_t* operand = TRI_AQL_NODE_MEMBER(node, 0); TRI_ASSERT(node->_type == TRI_AQL_NODE_OPERATOR_UNARY_PLUS || node->_type == TRI_AQL_NODE_OPERATOR_UNARY_MINUS); if (! operand || ! TRI_IsConstantValueNodeAql(operand)) { return node; } if (! TRI_IsNumericValueNodeAql(operand)) { TRI_SetErrorContextAql(__FILE__, __LINE__, context, TRI_ERROR_QUERY_INVALID_ARITHMETIC_VALUE, NULL); return node; } if (node->_type == TRI_AQL_NODE_OPERATOR_UNARY_PLUS) { // + number => number node = operand; } else if (node->_type == TRI_AQL_NODE_OPERATOR_UNARY_MINUS) { // - number => eval! double value = - TRI_GetNumericNodeValueAql(operand); // check for result validity if (value != value) { // IEEE754 NaN values have an interesting property that we can exploit... // if the architecture does not use IEEE754 values then this shouldn't do // any harm either LOG_TRACE("nan value detected after arithmetic optimisation"); TRI_SetErrorContextAql(__FILE__, __LINE__, context, TRI_ERROR_QUERY_INVALID_ARITHMETIC_VALUE, NULL); return NULL; } // test for infinity if (value == HUGE_VAL || value == -HUGE_VAL) { LOG_TRACE("inf value detected after arithmetic optimisation"); TRI_SetErrorContextAql(__FILE__, __LINE__, context, TRI_ERROR_QUERY_NUMBER_OUT_OF_RANGE, NULL); return NULL; } node = TRI_CreateNodeValueDoubleAql(context, value); if (node == NULL) { TRI_SetErrorContextAql(__FILE__, __LINE__, context, TRI_ERROR_OUT_OF_MEMORY, NULL); } } return node; } //////////////////////////////////////////////////////////////////////////////// /// @brief optimise a boolean operation with one operand //////////////////////////////////////////////////////////////////////////////// static TRI_aql_node_t* OptimiseUnaryLogicalOperation (TRI_aql_context_t* const context, TRI_aql_node_t* node) { TRI_aql_node_t* operand = TRI_AQL_NODE_MEMBER(node, 0); TRI_ASSERT(node->_type == TRI_AQL_NODE_OPERATOR_UNARY_NOT); if (! operand || ! TRI_IsConstantValueNodeAql(operand)) { // node is not a constant value return node; } if (! TRI_IsBooleanValueNodeAql(operand)) { // value type is not boolean => error TRI_SetErrorContextAql(__FILE__, __LINE__, context, TRI_ERROR_QUERY_INVALID_LOGICAL_VALUE, NULL); return node; } // ! (bool value) => evaluate and replace with result node = TRI_CreateNodeValueBoolAql(context, ! TRI_GetBooleanNodeValueAql(operand)); if (! node) { TRI_SetErrorContextAql(__FILE__, __LINE__, context, TRI_ERROR_OUT_OF_MEMORY, NULL); } LOG_TRACE("optimised away unary logical operation"); return node; } //////////////////////////////////////////////////////////////////////////////// /// @brief optimise a boolean operation with two operands //////////////////////////////////////////////////////////////////////////////// static TRI_aql_node_t* OptimiseBinaryLogicalOperation (TRI_aql_context_t* const context, TRI_aql_node_t* node) { TRI_aql_node_t* lhs = TRI_AQL_NODE_MEMBER(node, 0); TRI_aql_node_t* rhs = TRI_AQL_NODE_MEMBER(node, 1); bool isEligibleLhs; bool isEligibleRhs; bool lhsValue; if (! lhs || ! rhs) { return node; } isEligibleLhs = TRI_IsConstantValueNodeAql(lhs); isEligibleRhs = TRI_IsConstantValueNodeAql(rhs); if (isEligibleLhs && ! TRI_IsBooleanValueNodeAql(lhs)) { // value type is not boolean => error TRI_SetErrorContextAql(__FILE__, __LINE__, context, TRI_ERROR_QUERY_INVALID_LOGICAL_VALUE, NULL); return node; } if (isEligibleRhs && ! TRI_IsBooleanValueNodeAql(rhs)) { // value type is not boolean => error TRI_SetErrorContextAql(__FILE__, __LINE__, context, TRI_ERROR_QUERY_INVALID_LOGICAL_VALUE, NULL); return node; } if (! isEligibleLhs || ! isEligibleRhs) { // node is not a constant value return node; } lhsValue = TRI_GetBooleanNodeValueAql(lhs); TRI_ASSERT(node->_type == TRI_AQL_NODE_OPERATOR_BINARY_AND || node->_type == TRI_AQL_NODE_OPERATOR_BINARY_OR); LOG_TRACE("optimised away binary logical operation"); if (node->_type == TRI_AQL_NODE_OPERATOR_BINARY_AND) { if (lhsValue) { // if (true && rhs) => rhs return rhs; } // if (false && rhs) => false return lhs; } else if (node->_type == TRI_AQL_NODE_OPERATOR_BINARY_OR) { if (lhsValue) { // if (true || rhs) => true return lhs; } // if (false || rhs) => rhs return rhs; } TRI_ASSERT(false); return node; } //////////////////////////////////////////////////////////////////////////////// /// @brief optimise a relational operation with two operands //////////////////////////////////////////////////////////////////////////////// static TRI_aql_node_t* OptimiseBinaryRelationalOperation (TRI_aql_context_t* const context, TRI_aql_node_t* node) { TRI_aql_node_t* lhs = TRI_AQL_NODE_MEMBER(node, 0); TRI_aql_node_t* rhs = TRI_AQL_NODE_MEMBER(node, 1); TRI_js_exec_context_t* execContext; TRI_string_buffer_t* code; TRI_json_t* json; char const* func; int res; if (! lhs || ! TRI_IsConstantValueNodeAql(lhs) || ! rhs || ! TRI_IsConstantValueNodeAql(rhs)) { return node; } if (node->_type == TRI_AQL_NODE_OPERATOR_BINARY_EQ) { func = "EQUAL"; } else if (node->_type == TRI_AQL_NODE_OPERATOR_BINARY_NE) { func = "UNEQUAL"; } else if (node->_type == TRI_AQL_NODE_OPERATOR_BINARY_GT) { func = "GREATER"; } else if (node->_type == TRI_AQL_NODE_OPERATOR_BINARY_GE) { func = "GREATEREQUAL"; } else if (node->_type == TRI_AQL_NODE_OPERATOR_BINARY_LT) { func = "LESS"; } else if (node->_type == TRI_AQL_NODE_OPERATOR_BINARY_LE) { func = "LESSEQUAL"; } else if (node->_type == TRI_AQL_NODE_OPERATOR_BINARY_IN) { if (rhs->_type != TRI_AQL_NODE_LIST) { // oops, rhs is no list. cannot run IN operator TRI_SetErrorContextAql(__FILE__, __LINE__, context, TRI_ERROR_QUERY_LIST_EXPECTED, NULL); return node; } func = "IN"; } else { // not what we expected, however, simply continue return node; } code = RelationCode(func, lhs, rhs); if (code == NULL) { TRI_SetErrorContextAql(__FILE__, __LINE__, context, TRI_ERROR_OUT_OF_MEMORY, NULL); return node; } // execute the function code execContext = TRI_CreateExecutionContext(TRI_BeginStringBuffer(code), TRI_LengthStringBuffer(code)); TRI_FreeStringBuffer(TRI_UNKNOWN_MEM_ZONE, code); if (execContext == NULL) { TRI_SetErrorContextAql(__FILE__, __LINE__, context, TRI_ERROR_OUT_OF_MEMORY, NULL); return node; } // check if an error occurred during context creation res = execContext->_error; if (res != TRI_ERROR_NO_ERROR) { TRI_FreeExecutionContext(execContext); if (res == TRI_ERROR_REQUEST_CANCELED) { TRI_SetErrorContextAql(__FILE__, __LINE__, context, res, NULL); } return node; } json = TRI_ExecuteResultContext(execContext); res = execContext->_error; TRI_FreeExecutionContext(execContext); if (res == TRI_ERROR_REQUEST_CANCELED) { TRI_SetErrorContextAql(__FILE__, __LINE__, context, res, NULL); return node; } if (json == NULL) { TRI_SetErrorContextAql(__FILE__, __LINE__, context, TRI_ERROR_QUERY_SCRIPT, NULL); return NULL; } // use the constant values instead of the function call node node = TRI_JsonNodeAql(context, json); if (node == NULL) { TRI_SetErrorContextAql(__FILE__, __LINE__, context, TRI_ERROR_OUT_OF_MEMORY, NULL); } LOG_TRACE("optimised away binary relational operation"); TRI_FreeJson(TRI_UNKNOWN_MEM_ZONE, json); return node; } //////////////////////////////////////////////////////////////////////////////// /// @brief optimise an arithmetic operation with two operands //////////////////////////////////////////////////////////////////////////////// static TRI_aql_node_t* OptimiseBinaryArithmeticOperation (TRI_aql_context_t* const context, TRI_aql_node_t* node) { TRI_aql_node_t* lhs = TRI_AQL_NODE_MEMBER(node, 0); TRI_aql_node_t* rhs = TRI_AQL_NODE_MEMBER(node, 1); bool isEligibleLhs; bool isEligibleRhs; double value; if (! lhs || ! rhs) { return node; } isEligibleLhs = TRI_IsConstantValueNodeAql(lhs); isEligibleRhs = TRI_IsConstantValueNodeAql(rhs); if (isEligibleLhs && ! TRI_IsNumericValueNodeAql(lhs)) { // node is not a numeric value => error TRI_SetErrorContextAql(__FILE__, __LINE__, context, TRI_ERROR_QUERY_INVALID_ARITHMETIC_VALUE, NULL); return node; } if (isEligibleRhs && ! TRI_IsNumericValueNodeAql(rhs)) { // node is not a numeric value => error TRI_SetErrorContextAql(__FILE__, __LINE__, context, TRI_ERROR_QUERY_INVALID_ARITHMETIC_VALUE, NULL); return node; } if (! isEligibleLhs || ! isEligibleRhs) { return node; } TRI_ASSERT(node->_type == TRI_AQL_NODE_OPERATOR_BINARY_PLUS || node->_type == TRI_AQL_NODE_OPERATOR_BINARY_MINUS || node->_type == TRI_AQL_NODE_OPERATOR_BINARY_TIMES || node->_type == TRI_AQL_NODE_OPERATOR_BINARY_DIV || node->_type == TRI_AQL_NODE_OPERATOR_BINARY_MOD); if (node->_type == TRI_AQL_NODE_OPERATOR_BINARY_PLUS) { value = TRI_GetNumericNodeValueAql(lhs) + TRI_GetNumericNodeValueAql(rhs); } else if (node->_type == TRI_AQL_NODE_OPERATOR_BINARY_MINUS) { value = TRI_GetNumericNodeValueAql(lhs) - TRI_GetNumericNodeValueAql(rhs); } else if (node->_type == TRI_AQL_NODE_OPERATOR_BINARY_TIMES) { value = TRI_GetNumericNodeValueAql(lhs) * TRI_GetNumericNodeValueAql(rhs); } else if (node->_type == TRI_AQL_NODE_OPERATOR_BINARY_DIV) { if (TRI_GetNumericNodeValueAql(rhs) == 0.0) { // division by zero TRI_SetErrorContextAql(__FILE__, __LINE__, context, TRI_ERROR_QUERY_DIVISION_BY_ZERO, NULL); return node; } value = TRI_GetNumericNodeValueAql(lhs) / TRI_GetNumericNodeValueAql(rhs); } else if (node->_type == TRI_AQL_NODE_OPERATOR_BINARY_MOD) { if (TRI_GetNumericNodeValueAql(rhs) == 0.0) { // division by zero TRI_SetErrorContextAql(__FILE__, __LINE__, context, TRI_ERROR_QUERY_DIVISION_BY_ZERO, NULL); return node; } value = fmod(TRI_GetNumericNodeValueAql(lhs), TRI_GetNumericNodeValueAql(rhs)); } else { value = 0.0; } // check for result validity if (value != value) { // IEEE754 NaN values have an interesting property that we can exploit... // if the architecture does not use IEEE754 values then this shouldn't do // any harm either LOG_TRACE("nan value detected after arithmetic optimisation"); TRI_SetErrorContextAql(__FILE__, __LINE__, context, TRI_ERROR_QUERY_INVALID_ARITHMETIC_VALUE, NULL); return NULL; } // test for infinity if (value == HUGE_VAL || value == -HUGE_VAL) { LOG_TRACE("inf value detected after arithmetic optimisation"); TRI_SetErrorContextAql(__FILE__, __LINE__, context, TRI_ERROR_QUERY_NUMBER_OUT_OF_RANGE, NULL); return NULL; } node = TRI_CreateNodeValueDoubleAql(context, value); if (node == NULL) { TRI_SetErrorContextAql(__FILE__, __LINE__, context, TRI_ERROR_OUT_OF_MEMORY, NULL); return NULL; } LOG_TRACE("optimised away binary arithmetic operation"); return node; } //////////////////////////////////////////////////////////////////////////////// /// @brief optimise the ternary operation //////////////////////////////////////////////////////////////////////////////// static TRI_aql_node_t* OptimiseTernaryOperation (TRI_aql_context_t* const context, TRI_aql_node_t* node) { TRI_aql_node_t* condition = TRI_AQL_NODE_MEMBER(node, 0); TRI_aql_node_t* truePart = TRI_AQL_NODE_MEMBER(node, 1); TRI_aql_node_t* falsePart = TRI_AQL_NODE_MEMBER(node, 2); TRI_ASSERT(node->_type == TRI_AQL_NODE_OPERATOR_TERNARY); if (! condition || ! TRI_IsConstantValueNodeAql(condition)) { // node is not a constant value return node; } if (! TRI_IsBooleanValueNodeAql(condition)) { // node is not a boolean value => error TRI_SetErrorContextAql(__FILE__, __LINE__, context, TRI_ERROR_QUERY_INVALID_LOGICAL_VALUE, NULL); return node; } if (! truePart || ! falsePart) { // true or false parts not defined // should not happen but we must not continue in this case return node; } LOG_TRACE("optimised away ternary operation"); // evaluate condition if (TRI_GetBooleanNodeValueAql(condition)) { // condition is true, replace with truePart return truePart; } // condition is true, replace with falsePart return falsePart; } //////////////////////////////////////////////////////////////////////////////// /// @brief optimise a specific node /// /// This is a callback function called by the statement walker //////////////////////////////////////////////////////////////////////////////// static TRI_aql_node_t* OptimiseNode (TRI_aql_statement_walker_t* const walker, TRI_aql_node_t* node) { TRI_aql_context_t* context = ((aql_optimiser_t*) walker->_data)->_context; if (context->_error._code != TRI_ERROR_NO_ERROR) { return node; } TRI_ASSERT(node); // node optimisations switch (node->_type) { case TRI_AQL_NODE_OPERATOR_UNARY_PLUS: case TRI_AQL_NODE_OPERATOR_UNARY_MINUS: return OptimiseUnaryArithmeticOperation(context, node); case TRI_AQL_NODE_OPERATOR_UNARY_NOT: return OptimiseUnaryLogicalOperation(context, node); case TRI_AQL_NODE_OPERATOR_BINARY_AND: case TRI_AQL_NODE_OPERATOR_BINARY_OR: return OptimiseBinaryLogicalOperation(context, node); case TRI_AQL_NODE_OPERATOR_BINARY_EQ: case TRI_AQL_NODE_OPERATOR_BINARY_NE: case TRI_AQL_NODE_OPERATOR_BINARY_LT: case TRI_AQL_NODE_OPERATOR_BINARY_LE: case TRI_AQL_NODE_OPERATOR_BINARY_GT: case TRI_AQL_NODE_OPERATOR_BINARY_GE: case TRI_AQL_NODE_OPERATOR_BINARY_IN: return OptimiseBinaryRelationalOperation(context, node); case TRI_AQL_NODE_OPERATOR_BINARY_PLUS: case TRI_AQL_NODE_OPERATOR_BINARY_MINUS: case TRI_AQL_NODE_OPERATOR_BINARY_TIMES: case TRI_AQL_NODE_OPERATOR_BINARY_DIV: case TRI_AQL_NODE_OPERATOR_BINARY_MOD: return OptimiseBinaryArithmeticOperation(context, node); case TRI_AQL_NODE_OPERATOR_TERNARY: return OptimiseTernaryOperation(context, node); case TRI_AQL_NODE_FCALL: return OptimiseFcall(context, node); case TRI_AQL_NODE_REFERENCE: return OptimiseReference(walker, node); default: break; } return node; } //////////////////////////////////////////////////////////////////////////////// /// @brief optimise statement, first iteration /// this will only recurse into certain top-level statements //////////////////////////////////////////////////////////////////////////////// static TRI_aql_node_t* OptimiseStatement (TRI_aql_statement_walker_t* const walker, TRI_aql_node_t* node) { TRI_ASSERT(walker); TRI_ASSERT(node); // node optimisations switch (node->_type) { case TRI_AQL_NODE_FOR: return OptimiseFor(walker, node); case TRI_AQL_NODE_SORT: return OptimiseSort(walker, node); case TRI_AQL_NODE_LIMIT: return OptimiseLimit(walker, node); case TRI_AQL_NODE_FILTER: return OptimiseFilter(walker, node); default: { } } return node; } //////////////////////////////////////////////////////////////////////////////// /// @brief patch variables with range information //////////////////////////////////////////////////////////////////////////////// static void PatchVariables (TRI_aql_statement_walker_t* const walker) { TRI_aql_context_t* context = ((aql_optimiser_t*) walker->_data)->_context; TRI_vector_pointer_t* ranges; size_t i, n; ranges = TRI_GetCurrentRangesStatementWalkerAql(walker); if (ranges == NULL) { // no ranges defined, exit early return; } // iterate over all ranges found n = ranges->_length; for (i = 0; i < n; ++i) { TRI_aql_field_access_t* fieldAccess; TRI_aql_variable_t* variable; TRI_aql_node_t* definingNode; TRI_aql_node_t* expressionNode; char* variableName; size_t scopeCount; bool isReference; fieldAccess = (TRI_aql_field_access_t*) TRI_AtVectorPointer(ranges, i); TRI_ASSERT(fieldAccess); TRI_ASSERT(fieldAccess->_fullName); TRI_ASSERT(fieldAccess->_variableNameLength > 0); variableName = TRI_DuplicateString2Z(TRI_UNKNOWN_MEM_ZONE, fieldAccess->_fullName, fieldAccess->_variableNameLength); if (variableName == NULL) { // out of memory! TRI_SetErrorContextAql(__FILE__, __LINE__, context, TRI_ERROR_OUT_OF_MEMORY, NULL); return; } isReference = (fieldAccess->_type == TRI_AQL_ACCESS_REFERENCE); variable = TRI_GetVariableStatementWalkerAql(walker, variableName, &scopeCount); if (variable == NULL) { TRI_FreeString(TRI_UNKNOWN_MEM_ZONE, variableName); continue; } if (isReference && scopeCount > 0) { // unfortunately, the referenced variable is in an outer scope, so we cannot use it TRI_FreeString(TRI_UNKNOWN_MEM_ZONE, variableName); continue; } // note: we must not modify outer variables of subqueries // get the node that defines the variable definingNode = variable->_definingNode; TRI_ASSERT(definingNode != NULL); expressionNode = NULL; switch (definingNode->_type) { case TRI_AQL_NODE_LET: expressionNode = TRI_AQL_NODE_MEMBER(definingNode, 1); break; case TRI_AQL_NODE_FOR: expressionNode = TRI_AQL_NODE_MEMBER(definingNode, 1); break; default: { } } if (expressionNode != NULL) { if (expressionNode->_type == TRI_AQL_NODE_FCALL) { // the defining node is a function call // get the function name TRI_aql_function_t* function = static_cast (TRI_AQL_NODE_DATA(expressionNode)); if (function->optimise != NULL) { // call the function's optimise callback function->optimise(expressionNode, context, fieldAccess); } } if (expressionNode->_type == TRI_AQL_NODE_COLLECTION) { TRI_aql_collection_hint_t* hint = (TRI_aql_collection_hint_t*) (TRI_AQL_NODE_DATA(expressionNode)); if (hint->_variableName == NULL) { // note the variable name used for the collection (e.g. "u" for "FOR u IN users") hint->_variableName = TRI_DuplicateStringZ(TRI_UNKNOWN_MEM_ZONE, variableName); } // set new value hint->_ranges = TRI_AddAccessAql(context, hint->_ranges, fieldAccess); } } TRI_FreeString(TRI_UNKNOWN_MEM_ZONE, variableName); } } //////////////////////////////////////////////////////////////////////////////// /// @brief note LIMIT values for the current scope /// these values may be used later for pushing a LIMIT into a for-loop or a /// collection accessor //////////////////////////////////////////////////////////////////////////////// static void NoteLimit (TRI_aql_statement_walker_t* const walker, const TRI_aql_node_t* const node) { TRI_aql_node_t* offset = TRI_AQL_NODE_MEMBER(node, 0); TRI_aql_node_t* limit = TRI_AQL_NODE_MEMBER(node, 1); int64_t offsetValue; int64_t limitValue; TRI_aql_scope_t* scope; aql_optimiser_t* optimiser; optimiser = static_cast(walker->_data); if (offset->_type != TRI_AQL_NODE_VALUE || limit->_type != TRI_AQL_NODE_VALUE) { TRI_SetErrorContextAql(__FILE__, __LINE__, optimiser->_context, TRI_ERROR_QUERY_NUMBER_OUT_OF_RANGE, NULL); return; } if (offset->_value._type == TRI_AQL_TYPE_INT) { offsetValue = TRI_AQL_NODE_INT(offset); } else if (offset->_value._type == TRI_AQL_TYPE_DOUBLE) { offsetValue = (int64_t) TRI_AQL_NODE_DOUBLE(offset); } else { TRI_SetErrorContextAql(__FILE__, __LINE__, optimiser->_context, TRI_ERROR_QUERY_NUMBER_OUT_OF_RANGE, NULL); return; } if (offsetValue < 0) { TRI_SetErrorContextAql(__FILE__, __LINE__, optimiser->_context, TRI_ERROR_QUERY_NUMBER_OUT_OF_RANGE, NULL); return; } if (limit->_value._type == TRI_AQL_TYPE_INT) { limitValue = TRI_AQL_NODE_INT(limit); } else if (limit->_value._type == TRI_AQL_TYPE_DOUBLE) { limitValue = (int64_t) TRI_AQL_NODE_DOUBLE(limit); } else { TRI_SetErrorContextAql(__FILE__, __LINE__, optimiser->_context, TRI_ERROR_QUERY_NUMBER_OUT_OF_RANGE, NULL); return; } if (limitValue < 0) { TRI_SetErrorContextAql(__FILE__, __LINE__, optimiser->_context, TRI_ERROR_QUERY_NUMBER_OUT_OF_RANGE, NULL); return; } scope = TRI_GetCurrentScopeStatementWalkerAql(walker); TRI_ASSERT(scope != NULL); if (scope->_type != TRI_AQL_SCOPE_MAIN) { // will not optimise limit in main scope, e.g. "LIMIT 5, 0 RETURN 1" TRI_SetCurrentLimitStatementWalkerAql(walker, offsetValue, limitValue); } } //////////////////////////////////////////////////////////////////////////////// /// @brief optimise a statement /// /// this is a callback function used by the statement walker /// it is called after the node is initially optimised //////////////////////////////////////////////////////////////////////////////// static TRI_aql_node_t* ProcessStatement (TRI_aql_statement_walker_t* const walker, TRI_aql_node_t* node) { TRI_aql_context_t* context = ((aql_optimiser_t*) walker->_data)->_context; if (context->_error._code != TRI_ERROR_NO_ERROR) { return node; } if (node) { if (node->_type == TRI_AQL_NODE_SORT) { // SORT means we must not push a following LIMIT clause up TRI_IgnoreCurrentLimitStatementWalkerAql(walker); } else if (node->_type == TRI_AQL_NODE_COLLECT) { // COLLECT means we must not push a following LIMIT clause up TRI_IgnoreCurrentLimitStatementWalkerAql(walker); } else if (node->_type == TRI_AQL_NODE_FILTER) { // FILTER means we can push a following LIMIT clause up to the for loop, but not into the collection accessor TRI_RestrictCurrentLimitStatementWalkerAql(walker); } else if (node->_type == TRI_AQL_NODE_LIMIT) { // note the current limit, we might use it later, pushing it up NoteLimit(walker, node); } // this may change the node pointer node = OptimiseStatement(walker, node); // patch variables with range infos if (node->_type == TRI_AQL_NODE_SCOPE_END) { PatchVariables(walker); } } return node; } //////////////////////////////////////////////////////////////////////////////// /// @brief optimise the AST, first iteration //////////////////////////////////////////////////////////////////////////////// static bool OptimiseAst (aql_optimiser_t* const optimiser) { TRI_aql_statement_walker_t* walker; walker = TRI_CreateStatementWalkerAql((void*) optimiser, true, &OptimiseNode, NULL, &ProcessStatement); if (walker == NULL) { TRI_SetErrorContextAql(__FILE__, __LINE__, optimiser->_context, TRI_ERROR_OUT_OF_MEMORY, NULL); return false; } TRI_WalkStatementsAql(walker, optimiser->_context->_statements); TRI_FreeStatementWalkerAql(walker); return true; } //////////////////////////////////////////////////////////////////////////////// /// @brief determine which indexes to use in the query //////////////////////////////////////////////////////////////////////////////// static bool DetermineIndexes (aql_optimiser_t* const optimiser) { TRI_aql_statement_walker_t* walker; walker = TRI_CreateStatementWalkerAql((void*) optimiser, true, &AnnotateNode, &AnnotateLoop, NULL); if (walker == NULL) { TRI_SetErrorContextAql(__FILE__, __LINE__, optimiser->_context, TRI_ERROR_OUT_OF_MEMORY, NULL); return false; } TRI_WalkStatementsAql(walker, optimiser->_context->_statements); TRI_FreeStatementWalkerAql(walker); return true; } // ----------------------------------------------------------------------------- // --SECTION-- public functions // ----------------------------------------------------------------------------- //////////////////////////////////////////////////////////////////////////////// /// @brief optimise the query //////////////////////////////////////////////////////////////////////////////// bool TRI_OptimiseAql (TRI_aql_context_t* const context) { aql_optimiser_t* optimiser; bool result; optimiser = CreateOptimiser(context); if (optimiser == NULL) { TRI_SetErrorContextAql(__FILE__, __LINE__, context, TRI_ERROR_OUT_OF_MEMORY, NULL); return false; } result = (OptimiseAst(optimiser) && DetermineIndexes(optimiser)); FreeOptimiser(optimiser); return result; } // ----------------------------------------------------------------------------- // --SECTION-- END-OF-FILE // ----------------------------------------------------------------------------- // Local Variables: // mode: outline-minor // outline-regexp: "/// @brief\\|/// {@inheritDoc}\\|/// @page\\|// --SECTION--\\|/// @\\}" // End: