1
0
Fork 0
arangodb/arangod/Ahuacatl/ahuacatl-optimiser.c

1164 lines
37 KiB
C

////////////////////////////////////////////////////////////////////////////////
/// @brief Ahuacatl, optimiser
///
/// @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
////////////////////////////////////////////////////////////////////////////////
#include "Ahuacatl/ahuacatl-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 "V8/v8-execution.h"
// -----------------------------------------------------------------------------
// --SECTION-- forwards
// -----------------------------------------------------------------------------
////////////////////////////////////////////////////////////////////////////////
/// @addtogroup Ahuacatl
/// @{
////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
/// @brief optimise a node recursively
////////////////////////////////////////////////////////////////////////////////
static TRI_aql_node_t* OptimiseNode (TRI_aql_statement_walker_t* const,
TRI_aql_node_t*);
////////////////////////////////////////////////////////////////////////////////
/// @brief optimise a statement
////////////////////////////////////////////////////////////////////////////////
static TRI_aql_node_t* ProcessStatement (TRI_aql_statement_walker_t* const,
TRI_aql_node_t*);
////////////////////////////////////////////////////////////////////////////////
/// @}
////////////////////////////////////////////////////////////////////////////////
// -----------------------------------------------------------------------------
// --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
// -----------------------------------------------------------------------------
////////////////////////////////////////////////////////////////////////////////
/// @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
////////////////////////////////////////////////////////////////////////////////
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);
assert(collectionName);
hint = (TRI_aql_collection_hint_t*) TRI_AQL_NODE_DATA(node);
if (hint == NULL) {
TRI_SetErrorContextAql(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(context, TRI_ERROR_OUT_OF_MEMORY, NULL);
return;
}
hint->_collection = collection;
availableIndexes = &(((TRI_document_collection_t*) collection->_collection->_collection)->_allIndexes);
if (availableIndexes == NULL) {
TRI_SetErrorContextAql(context, TRI_ERROR_OUT_OF_MEMORY, NULL);
return;
}
idx = TRI_DetermineIndexAql(context,
availableIndexes,
collectionName,
hint->_ranges);
hint->_index = idx;
}
////////////////////////////////////////////////////////////////////////////////
/// @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) {
aql_optimiser_t* optimiser;
if (node->_type != TRI_AQL_NODE_COLLECTION) {
return node;
}
optimiser = (aql_optimiser_t*) walker->_data;
AttachCollectionHint(optimiser->_context, node);
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 = TRI_CreateStringBuffer(TRI_UNKNOWN_MEM_ZONE);
if (!lhs || !rhs) {
return NULL;
}
if (TRI_AppendStringStringBuffer(buffer, "(function(){return AHUACATL_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(){return AHUACATL_FCALL(") != 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;
}
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;
size_t n;
function = (TRI_aql_function_t*) TRI_AQL_NODE_DATA(node);
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) {
TRI_SetErrorContextAql(context, TRI_ERROR_OUT_OF_MEMORY, NULL);
return node;
}
// execute the function code
execContext = TRI_CreateExecutionContext(code->_buffer);
TRI_FreeStringBuffer(TRI_UNKNOWN_MEM_ZONE, code);
if (!execContext) {
TRI_SetErrorContextAql(context, TRI_ERROR_OUT_OF_MEMORY, NULL);
return node;
}
json = TRI_ExecuteResultContext(execContext);
TRI_FreeExecutionContext(execContext);
if (!json) {
// 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(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) {
TRI_SetErrorContextAql(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
////////////////////////////////////////////////////////////////////////////////
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) {
// for statement with a list expression
if (expression->_members._length == 0) {
// list is empty => we can eliminate the for statement
LOG_TRACE("optimised away empty for loop");
return TRI_GetDummyReturnEmptyNodeAql();
}
}
return node;
}
////////////////////////////////////////////////////////////////////////////////
/// @brief optimise a SORT statement
////////////////////////////////////////////////////////////////////////////////
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();
}
return node;
}
////////////////////////////////////////////////////////////////////////////////
/// @brief optimise a constant FILTER expression
////////////////////////////////////////////////////////////////////////////////
static TRI_aql_node_t* OptimiseConstantFilter (TRI_aql_node_t* const node) {
if (TRI_GetBooleanNodeValueAql(node)) {
// 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");
return TRI_GetDummyReturnEmptyNodeAql();
}
////////////////////////////////////////////////////////////////////////////////
/// @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);
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(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
}
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
assert(variableName);
variable = TRI_GetVariableStatementWalkerAql(walker, variableName, &scopeCount);
if (variable == NULL) {
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);
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(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!
node = TRI_CreateNodeValueDoubleAql(context, - TRI_GetNumericNodeValueAql(operand));
if (node == NULL) {
TRI_SetErrorContextAql(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);
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(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(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(context, TRI_ERROR_QUERY_INVALID_LOGICAL_VALUE, NULL);
return node;
}
if (isEligibleRhs && !TRI_IsBooleanValueNodeAql(rhs)) {
// value type is not boolean => error
TRI_SetErrorContextAql(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);
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;
}
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* func;
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) {
func = "IN";
}
else {
// not what we expected, however, simply continue
return node;
}
code = RelationCode(func, lhs, rhs);
if (!code) {
TRI_SetErrorContextAql(context, TRI_ERROR_OUT_OF_MEMORY, NULL);
return node;
}
// execute the function code
execContext = TRI_CreateExecutionContext(code->_buffer);
TRI_FreeStringBuffer(TRI_UNKNOWN_MEM_ZONE, code);
if (!execContext) {
TRI_SetErrorContextAql(context, TRI_ERROR_OUT_OF_MEMORY, NULL);
return node;
}
json = TRI_ExecuteResultContext(execContext);
TRI_FreeExecutionContext(execContext);
if (!json) {
TRI_SetErrorContextAql(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) {
TRI_SetErrorContextAql(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(context, TRI_ERROR_QUERY_INVALID_ARITHMETIC_VALUE, NULL);
return node;
}
if (isEligibleRhs && !TRI_IsNumericValueNodeAql(rhs)) {
// node is not a numeric value => error
TRI_SetErrorContextAql(context, TRI_ERROR_QUERY_INVALID_ARITHMETIC_VALUE, NULL);
return node;
}
if (!isEligibleLhs || !isEligibleRhs) {
return node;
}
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(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(context, TRI_ERROR_QUERY_DIVISION_BY_ZERO, NULL);
return node;
}
value = fmod(TRI_GetNumericNodeValueAql(lhs), TRI_GetNumericNodeValueAql(rhs));
}
else {
value = 0.0;
}
node = TRI_CreateNodeValueDoubleAql(context, value);
if (!node) {
TRI_SetErrorContextAql(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);
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(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;
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
////////////////////////////////////////////////////////////////////////////////
static TRI_aql_node_t* OptimiseStatement (TRI_aql_statement_walker_t* const walker,
TRI_aql_node_t* node) {
assert(walker);
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_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);
assert(fieldAccess);
assert(fieldAccess->_fullName);
assert(fieldAccess->_variableNameLength > 0);
variableName = TRI_DuplicateString2Z(TRI_UNKNOWN_MEM_ZONE, fieldAccess->_fullName, fieldAccess->_variableNameLength);
if (variableName == NULL) {
// out of memory!
TRI_SetErrorContextAql(context, TRI_ERROR_OUT_OF_MEMORY, NULL);
return;
}
isReference = (fieldAccess->_type == TRI_AQL_ACCESS_REFERENCE);
variable = TRI_GetVariableStatementWalkerAql(walker, variableName, &scopeCount);
TRI_FreeString(TRI_UNKNOWN_MEM_ZONE, variableName);
if (variable == NULL) {
continue;
}
if (isReference && scopeCount > 0) {
// unfortunately, the referenced variable is in an outer scope, so we cannot use it
continue;
}
// note: we must not modify outer variables of subqueries
// get the node that defines the variable
definingNode = variable->_definingNode;
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 = 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));
// set new value
hint->_ranges = TRI_AddAccessAql(context, hint->_ranges, fieldAccess);
}
}
}
}
////////////////////////////////////////////////////////////////////////////////
/// @brief optimise a statement
///
/// this is a callback function used by the statement walker
////////////////////////////////////////////////////////////////////////////////
static TRI_aql_node_t* ProcessStatement (TRI_aql_statement_walker_t* const walker,
TRI_aql_node_t* node) {
if (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(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,
false,
&AnnotateNode,
NULL,
NULL);
if (walker == NULL) {
TRI_SetErrorContextAql(optimiser->_context, TRI_ERROR_OUT_OF_MEMORY, NULL);
return false;
}
TRI_WalkStatementsAql(walker, optimiser->_context->_statements);
TRI_FreeStatementWalkerAql(walker);
return true;
}
////////////////////////////////////////////////////////////////////////////////
/// @}
////////////////////////////////////////////////////////////////////////////////
// -----------------------------------------------------------------------------
// --SECTION-- public functions
// -----------------------------------------------------------------------------
////////////////////////////////////////////////////////////////////////////////
/// @addtogroup Ahuacatl
/// @{
////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
/// @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(context, TRI_ERROR_OUT_OF_MEMORY, NULL);
return false;
}
result = (OptimiseAst(optimiser) && DetermineIndexes(optimiser));
FreeOptimiser(optimiser);
return result;
}
////////////////////////////////////////////////////////////////////////////////
/// @}
////////////////////////////////////////////////////////////////////////////////
// Local Variables:
// mode: outline-minor
// outline-regexp: "^\\(/// @brief\\|/// {@inheritDoc}\\|/// @addtogroup\\|// --SECTION--\\|/// @\\}\\)"
// End: