1
0
Fork 0
arangodb/QL/optimize.c

1764 lines
59 KiB
C

////////////////////////////////////////////////////////////////////////////////
/// @brief AST optimization functions
///
/// @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 "QL/ast-node.h"
#include "QL/optimize.h"
// -----------------------------------------------------------------------------
// --SECTION-- private functions
// -----------------------------------------------------------------------------
////////////////////////////////////////////////////////////////////////////////
/// @addtogroup QL
/// @{
////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
/// @brief Hash a member name for comparisons
////////////////////////////////////////////////////////////////////////////////
static uint64_t QLOptimizeGetMemberNameHash (QL_ast_node_t* node) {
QL_ast_node_t *lhs, *rhs;
uint64_t hashValue;
lhs = node->_lhs;
hashValue = TRI_FnvHashString(lhs->_value._stringValue);
rhs = node->_rhs;
node = rhs->_next;
while (node) {
hashValue ^= TRI_FnvHashString(node->_value._stringValue);
node = node->_next;
}
return hashValue;
}
////////////////////////////////////////////////////////////////////////////////
/// @brief Create a string from a member name
///
/// The result string may or may not include the collection name
////////////////////////////////////////////////////////////////////////////////
static TRI_string_buffer_t* QLOptimizeGetMemberNameString (QL_ast_node_t* node,
bool includeCollection) {
QL_ast_node_t *lhs, *rhs;
TRI_string_buffer_t* buffer;
buffer = (TRI_string_buffer_t*) TRI_Allocate(sizeof(TRI_string_buffer_t));
if (!buffer) {
return 0;
}
TRI_InitStringBuffer(buffer);
if (includeCollection) {
// add collection part
lhs = node->_lhs;
TRI_AppendStringStringBuffer(buffer, lhs->_value._stringValue);
TRI_AppendCharStringBuffer(buffer, '.');
}
rhs = node->_rhs;
node = rhs->_next;
while (node) {
// add individual name parts
TRI_AppendStringStringBuffer(buffer, node->_value._stringValue);
node = node->_next;
if (node) {
TRI_AppendCharStringBuffer(buffer, '.');
}
}
return buffer;
}
////////////////////////////////////////////////////////////////////////////////
/// @brief check whether a node is optimizable as an arithmetic operand
////////////////////////////////////////////////////////////////////////////////
bool QLOptimizeCanBeUsedAsArithmeticOperand (const QL_ast_node_t const* node) {
switch (node->_type) {
case QLNodeValueNumberDouble:
case QLNodeValueNumberDoubleString:
case QLNodeValueBool:
case QLNodeValueNull: // NULL is equal to 0 in this case, i.e. NULL + 1 == 1, NULL -1 == -1 etc.
return true;
default:
return false;
}
}
////////////////////////////////////////////////////////////////////////////////
/// @brief check whether a node is optimizable as a relational operand
////////////////////////////////////////////////////////////////////////////////
bool QLOptimizeCanBeUsedAsRelationalOperand (const QL_ast_node_t const* node) {
switch (node->_type) {
case QLNodeValueNumberDouble:
case QLNodeValueNumberDoubleString:
case QLNodeValueBool:
return true;
default:
return false;
}
}
////////////////////////////////////////////////////////////////////////////////
/// @brief check whether a node is optimizable as a logical operand
////////////////////////////////////////////////////////////////////////////////
bool QLOptimizeCanBeUsedAsLogicalOperand (const QL_ast_node_t const* node) {
switch (node->_type) {
case QLNodeValueNumberDouble:
case QLNodeValueNumberDoubleString:
case QLNodeValueBool:
case QLNodeValueNull:
return true;
default:
return false;
}
}
////////////////////////////////////////////////////////////////////////////////
/// @brief return a node value, converted to a bool
////////////////////////////////////////////////////////////////////////////////
bool QLOptimizeGetBool (const QL_ast_node_t const* node) {
double d;
if (node->_type == QLNodeValueNumberDouble) {
return (node->_value._doubleValue != 0.0 ? true : false);
}
if (node->_type == QLNodeValueNumberDoubleString) {
d = TRI_DoubleString(node->_value._stringValue);
if (TRI_errno() != TRI_ERROR_NO_ERROR && d != 0.0) {
return true;
}
return (d != 0.0);
}
if (node->_type == QLNodeValueNumberInt) {
return (node->_value._intValue != 0 ? true : false);
}
if (node->_type == QLNodeValueBool) {
return (node->_value._boolValue ? true : false);
}
if (node->_type == QLNodeValueNull) {
return false;
}
return false;
}
////////////////////////////////////////////////////////////////////////////////
/// @brief return a node value, converted to a double
////////////////////////////////////////////////////////////////////////////////
double QLOptimizeGetDouble (const QL_ast_node_t const* node) {
if (node->_type == QLNodeValueNumberDouble) {
return node->_value._doubleValue;
}
if (node->_type == QLNodeValueNumberDoubleString) {
return TRI_DoubleString(node->_value._stringValue);
}
if (node->_type == QLNodeValueNumberInt) {
return (double) node->_value._intValue;
}
if (node->_type == QLNodeValueBool) {
return (node->_value._boolValue ? 1.0 : 0.0);
}
if (node->_type == QLNodeValueNull) {
return 0.0;
}
return 0.0;
}
////////////////////////////////////////////////////////////////////////////////
/// @brief check if a document declaration is static or dynamic
////////////////////////////////////////////////////////////////////////////////
bool QLOptimizeIsStaticDocument (QL_ast_node_t* node) {
bool result;
if (node->_next) {
while (node->_next) {
result = QLOptimizeIsStaticDocument(node->_next);
if (!result) {
return false;
}
node = node->_next;
}
return true;
}
if (node->_lhs) {
result = QLOptimizeIsStaticDocument(node->_lhs);
if (!result) {
return false;
}
}
if (node->_rhs) {
result = QLOptimizeIsStaticDocument(node->_rhs);
if (!result) {
return false;
}
}
if (node->_type == QLNodeReferenceCollectionAlias ||
node->_type == QLNodeControlFunctionCall ||
node->_type == QLNodeControlTernary ||
node->_type == QLNodeContainerMemberAccess) {
return false;
}
return true;
}
////////////////////////////////////////////////////////////////////////////////
/// @brief convert a node to a null value node
////////////////////////////////////////////////////////////////////////////////
void QLOptimizeMakeValueNull (QL_ast_node_t* node) {
node->_type = QLNodeValueNull;
node->_lhs = 0;
node->_rhs = 0;
}
////////////////////////////////////////////////////////////////////////////////
/// @brief convert a node to a bool value node
////////////////////////////////////////////////////////////////////////////////
void QLOptimizeMakeValueBool (QL_ast_node_t* node, bool value) {
node->_type = QLNodeValueBool;
node->_value._boolValue = value;
node->_lhs = 0;
node->_rhs = 0;
}
////////////////////////////////////////////////////////////////////////////////
/// @brief convert a node to a double value node
////////////////////////////////////////////////////////////////////////////////
void QLOptimizeMakeValueNumberDouble (QL_ast_node_t* node, double value) {
node->_type = QLNodeValueNumberDouble;
node->_value._doubleValue = value;
node->_lhs = 0;
node->_rhs = 0;
}
////////////////////////////////////////////////////////////////////////////////
/// @brief make a node a copy of another node
////////////////////////////////////////////////////////////////////////////////
void QLOptimizeClone (QL_ast_node_t* target, QL_ast_node_t* source) {
target->_type = source->_type;
target->_value = source->_value;
target->_lhs = source->_lhs;
target->_rhs = source->_rhs;
}
////////////////////////////////////////////////////////////////////////////////
/// @}
////////////////////////////////////////////////////////////////////////////////
// -----------------------------------------------------------------------------
// --SECTION-- public functions
// -----------------------------------------------------------------------------
////////////////////////////////////////////////////////////////////////////////
/// @addtogroup QL
/// @{
////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
/// @brief optimization function for unary operators
////////////////////////////////////////////////////////////////////////////////
void QLOptimizeUnaryOperator (QL_ast_node_t* node) {
QL_ast_node_t* lhs;
QL_ast_node_type_e type;
lhs = node->_lhs;
if (lhs == 0) {
// node has no child
return;
}
type = node->_type;
if (type != QLNodeUnaryOperatorMinus &&
type != QLNodeUnaryOperatorPlus &&
type != QLNodeUnaryOperatorNot) {
return;
}
if (!QLOptimizeCanBeUsedAsLogicalOperand(lhs)) {
// child node is not suitable for optimization
return;
}
if (type == QLNodeUnaryOperatorPlus) {
// unary plus. This will make the result a numeric value
QLOptimizeMakeValueNumberDouble(node, QLOptimizeGetDouble(lhs));
}
else if (type == QLNodeUnaryOperatorMinus) {
// unary minus. This will make the result a numeric value
QLOptimizeMakeValueNumberDouble(node, 0.0 - QLOptimizeGetDouble(lhs));
}
else if (type == QLNodeUnaryOperatorNot) {
// logical !
QLOptimizeMakeValueBool(node, !QLOptimizeGetBool(lhs));
}
}
////////////////////////////////////////////////////////////////////////////////
/// @brief optimize an arithmetic operation
////////////////////////////////////////////////////////////////////////////////
void QLOptimizeArithmeticOperator (QL_ast_node_t* node) {
double lhsValue, rhsValue;
QL_ast_node_t *lhs, *rhs;
QL_ast_node_type_e type;
type = node->_type;
lhs = node->_lhs;
rhs = node->_rhs;
if (QLOptimizeCanBeUsedAsArithmeticOperand(lhs) &&
QLOptimizeCanBeUsedAsArithmeticOperand(rhs)) {
// both operands are constants and can be merged into one result
lhsValue = QLOptimizeGetDouble(lhs);
rhsValue = QLOptimizeGetDouble(rhs);
if (type == QLNodeBinaryOperatorAdd) {
// const + const ==> merge
QLOptimizeMakeValueNumberDouble(node, lhsValue + rhsValue);
}
else if (type == QLNodeBinaryOperatorSubtract) {
// const - const ==> merge
QLOptimizeMakeValueNumberDouble(node, lhsValue - rhsValue);
}
else if (type == QLNodeBinaryOperatorMultiply) {
// const * const ==> merge
QLOptimizeMakeValueNumberDouble(node, lhsValue * rhsValue);
}
else if (type == QLNodeBinaryOperatorDivide && rhsValue != 0.0) {
// ignore division by zero. div0 will be handled in JS
// const / const ==> merge
QLOptimizeMakeValueNumberDouble(node, lhsValue / rhsValue);
}
else if (type == QLNodeBinaryOperatorModulus && rhsValue != 0.0) {
// ignore division by zero. div0 will be handled in JS
// const % const ==> merge
QLOptimizeMakeValueNumberDouble(node, fmod(lhsValue, rhsValue));
}
}
else if (QLOptimizeCanBeUsedAsArithmeticOperand(lhs)) {
// only left operand is a constant
lhsValue = QLOptimizeGetDouble(lhs);
if (type == QLNodeBinaryOperatorAdd && lhsValue == 0.0) {
// 0 + x ==> x
// TODO: by adding 0, the result would become a double. Just copying over rhs is not enough!
// QLOptimizeClone(node, rhs);
}
else if (type == QLNodeBinaryOperatorMultiply && lhsValue == 0.0) {
// 0 * x ==> 0
QLOptimizeMakeValueNumberDouble(node, 0.0);
}
else if (type == QLNodeBinaryOperatorMultiply && lhsValue == 1.0) {
// 1 * x ==> x
// TODO: by adding 0, the result would become a double. Just copying over rhs is not enough!
// QLOptimizeClone(node, rhs);
}
}
else if (QLOptimizeCanBeUsedAsArithmeticOperand(rhs)) {
// only right operand is a constant
rhsValue = QLOptimizeGetDouble(rhs);
if (type == QLNodeBinaryOperatorAdd && rhsValue == 0.0) {
// x + 0 ==> x
// TODO: by adding 0, the result would become a double. Just copying over lhs is not enough!
QLOptimizeClone(node, lhs);
}
else if (type == QLNodeBinaryOperatorSubtract && rhsValue == 0.0) {
// x - 0 ==> x
// TODO: by adding 0, the result would become a double. Just copying over lhs is not enough!
QLOptimizeClone(node, lhs);
}
else if (type == QLNodeBinaryOperatorMultiply && rhsValue == 0.0) {
// x * 0 ==> 0
QLOptimizeMakeValueNumberDouble(node, 0.0);
}
else if (type == QLNodeBinaryOperatorMultiply && rhsValue == 1.0) {
// x * 1 ==> x
// TODO: by adding 0, the result would become a double. Just copying over lhs is not enough!
QLOptimizeClone(node, lhs);
}
}
}
////////////////////////////////////////////////////////////////////////////////
/// @brief optimize a logical operation
////////////////////////////////////////////////////////////////////////////////
void QLOptimizeLogicalOperator (QL_ast_node_t* node) {
bool lhsValue;
QL_ast_node_t *lhs, *rhs;
QL_ast_node_type_e type;
type = node->_type;
lhs = node->_lhs;
rhs = node->_rhs;
if (type == QLNodeBinaryOperatorAnd) {
// logical and
if (QLOptimizeCanBeUsedAsLogicalOperand(lhs)) {
lhsValue = QLOptimizeGetBool(lhs);
if (lhsValue) {
// true && r ==> r
QLOptimizeClone(node, rhs);
}
else {
// false && r ==> l (and l evals to false)
QLOptimizeClone(node, lhs);
}
}
}
else if (type == QLNodeBinaryOperatorOr) {
// logical or
if (QLOptimizeCanBeUsedAsLogicalOperand(lhs)) {
lhsValue = QLOptimizeGetBool(lhs);
if (lhsValue) {
// true || r ==> true
QLOptimizeMakeValueBool(node, true);
}
else {
// false || r ==> r
QLOptimizeClone(node, rhs);
}
}
}
}
////////////////////////////////////////////////////////////////////////////////
/// @brief optimize a constant string comparison
////////////////////////////////////////////////////////////////////////////////
static bool QLOptimizeStringComparison (QL_ast_node_t* node) {
QL_ast_node_t *lhs, *rhs;
QL_ast_node_type_e type;
int compareResult;
lhs = node->_lhs;
rhs = node->_rhs;
compareResult = strcmp(lhs->_value._stringValue, rhs->_value._stringValue);
type = node->_type;
if (type == QLNodeBinaryOperatorIdentical || type == QLNodeBinaryOperatorEqual) {
QLOptimizeMakeValueBool(node, compareResult == 0);
return true;
}
if (type == QLNodeBinaryOperatorUnidentical || type == QLNodeBinaryOperatorUnequal) {
QLOptimizeMakeValueBool(node, compareResult != 0);
return true;
}
if (type == QLNodeBinaryOperatorGreater) {
QLOptimizeMakeValueBool(node, compareResult > 0);
return true;
}
if (type == QLNodeBinaryOperatorGreaterEqual) {
QLOptimizeMakeValueBool(node, compareResult >= 0);
return true;
}
if (type == QLNodeBinaryOperatorLess) {
QLOptimizeMakeValueBool(node, compareResult < 0);
return true;
}
if (type == QLNodeBinaryOperatorLessEqual) {
QLOptimizeMakeValueBool(node, compareResult <= 0);
return true;
}
return false;
}
////////////////////////////////////////////////////////////////////////////////
/// @brief optimize a member comparison
////////////////////////////////////////////////////////////////////////////////
static bool QLOptimizeMemberComparison (QL_ast_node_t* node) {
QL_ast_node_t *lhs, *rhs;
QL_ast_node_type_e type;
bool isSameMember;
lhs = node->_lhs;
rhs = node->_rhs;
type = node->_type;
isSameMember = (QLOptimizeGetMemberNameHash(lhs) == QLOptimizeGetMemberNameHash(rhs));
if (isSameMember) {
if (type == QLNodeBinaryOperatorIdentical ||
type == QLNodeBinaryOperatorEqual ||
type == QLNodeBinaryOperatorGreaterEqual ||
type == QLNodeBinaryOperatorLessEqual) {
QLOptimizeMakeValueBool(node, true);
return true;
}
if (type == QLNodeBinaryOperatorUnidentical ||
type == QLNodeBinaryOperatorUnequal ||
type == QLNodeBinaryOperatorGreater ||
type == QLNodeBinaryOperatorLess) {
QLOptimizeMakeValueBool(node, false);
return true;
}
}
// caller function must handle this
return false;
}
////////////////////////////////////////////////////////////////////////////////
/// @brief optimize a relational operation
////////////////////////////////////////////////////////////////////////////////
void QLOptimizeRelationalOperator (QL_ast_node_t* node) {
double lhsValue, rhsValue;
QL_ast_node_t *lhs, *rhs;
QL_ast_node_type_e type;
lhs = node->_lhs;
rhs = node->_rhs;
type = node->_type;
if (lhs->_type == QLNodeValueString && rhs->_type == QLNodeValueString) {
// both operands are constant strings
if (QLOptimizeStringComparison(node)) {
return;
}
}
if (lhs->_type == QLNodeContainerMemberAccess &&
rhs->_type == QLNodeContainerMemberAccess) {
// both operands are collections members (document properties)
if (QLOptimizeMemberComparison(node)) {
return;
}
}
if (QLOptimizeCanBeUsedAsRelationalOperand(lhs) &&
QLOptimizeCanBeUsedAsRelationalOperand(rhs)) {
// both operands are constants and can be merged into one result
lhsValue = QLOptimizeGetDouble(lhs);
rhsValue = QLOptimizeGetDouble(rhs);
if (type == QLNodeBinaryOperatorIdentical) {
QLOptimizeMakeValueBool(node, (lhsValue == rhsValue) && (lhs->_type == rhs->_type));
}
else if (type == QLNodeBinaryOperatorUnidentical) {
QLOptimizeMakeValueBool(node, (lhsValue != rhsValue) || (lhs->_type != rhs->_type));
}
else if (type == QLNodeBinaryOperatorEqual) {
QLOptimizeMakeValueBool(node, lhsValue == rhsValue);
}
else if (type == QLNodeBinaryOperatorUnequal) {
QLOptimizeMakeValueBool(node, lhsValue != rhsValue);
}
else if (type == QLNodeBinaryOperatorLess) {
QLOptimizeMakeValueBool(node, lhsValue < rhsValue);
}
else if (type == QLNodeBinaryOperatorGreater) {
QLOptimizeMakeValueBool(node, lhsValue > rhsValue);
}
else if (type == QLNodeBinaryOperatorLessEqual) {
QLOptimizeMakeValueBool(node, lhsValue <= rhsValue);
}
else if (type == QLNodeBinaryOperatorGreaterEqual) {
QLOptimizeMakeValueBool(node, lhsValue >= rhsValue);
}
}
}
////////////////////////////////////////////////////////////////////////////////
/// @brief optimization function for binary operators
////////////////////////////////////////////////////////////////////////////////
void QLOptimizeBinaryOperator (QL_ast_node_t* node) {
if (QLAstNodeIsArithmeticOperator(node)) {
// optimize arithmetic operation
QLOptimizeArithmeticOperator(node);
}
else if (QLAstNodeIsLogicalOperator(node)) {
// optimize logical operation
QLOptimizeLogicalOperator(node);
}
else if (QLAstNodeIsRelationalOperator(node)) {
// optimize relational operation
QLOptimizeRelationalOperator(node);
}
}
////////////////////////////////////////////////////////////////////////////////
/// @brief optimization function for the ternary operator
////////////////////////////////////////////////////////////////////////////////
void QLOptimizeTernaryOperator (QL_ast_node_t* node) {
QL_ast_node_t *lhs, *rhs;
bool lhsValue;
// condition part
lhs = node->_lhs;
if (QLOptimizeCanBeUsedAsLogicalOperand(lhs)) {
lhsValue = QLOptimizeGetBool(lhs);
// true and false parts
rhs = node->_rhs;
if (lhsValue) {
QLOptimizeClone(node, rhs->_lhs);
}
else {
QLOptimizeClone(node, rhs->_rhs);
}
}
}
////////////////////////////////////////////////////////////////////////////////
/// @brief optimize order by by removing constant parts
////////////////////////////////////////////////////////////////////////////////
void QLOptimizeOrder (QL_ast_node_t* node) {
QL_ast_node_t* responsibleNode;
responsibleNode = node;
node = node->_next;
while (node != 0) {
// lhs contains the order expression, rhs contains the sort order
QLOptimizeExpression(node->_lhs);
if (QLAstNodeIsBooleanizable(node->_lhs)) {
// skip constant parts in order by
responsibleNode->_next = node->_next;
}
else {
responsibleNode = node;
}
node = node->_next;
}
}
////////////////////////////////////////////////////////////////////////////////
/// @brief recursively optimize nodes in an expression AST
////////////////////////////////////////////////////////////////////////////////
void QLOptimizeExpression (QL_ast_node_t* node) {
QL_ast_node_type_e type;
QL_ast_node_t *lhs, *rhs, *next;
if (node == 0) {
return;
}
type = node->_type;
if (type == QLNodeContainerList) {
next = node->_next;
while (next) {
if (!QLAstNodeIsValueNode(node)) {
// no need to optimize value nodes
QLOptimizeExpression(next);
}
next = next->_next;
}
}
if (QLAstNodeIsValueNode(node)) {
// exit early, no need to optimize value nodes
return;
}
lhs = node->_lhs;
if (lhs != 0) {
QLOptimizeExpression(lhs);
}
rhs = node->_rhs;
if (rhs != 0) {
QLOptimizeExpression(rhs);
}
if (QLAstNodeIsUnaryOperator(node)) {
QLOptimizeUnaryOperator(node);
}
else if (QLAstNodeIsBinaryOperator(node)) {
QLOptimizeBinaryOperator(node);
}
else if (QLAstNodeIsTernaryOperator(node)) {
QLOptimizeTernaryOperator(node);
}
}
////////////////////////////////////////////////////////////////////////////////
/// @brief Reference count all collections in an AST part by walking it
/// recursively
///
/// For each found collection, the counter value will be increased by one.
/// Reference counting is necessary to detect which collections in the from
/// clause are not used in the select, where and order by operations. Unused
/// collections that are left or list join'd can be removed.
////////////////////////////////////////////////////////////////////////////////
static void QLOptimizeRefCountCollections (const QL_parser_context_t* context,
const QL_ast_node_t* node) {
QL_ast_node_t *lhs, *rhs, *next;
if (node == 0) {
return;
}
if (node->_type == QLNodeContainerList) {
next = node->_next;
while (next) {
QLOptimizeRefCountCollections(context, next);
next = next->_next;
}
}
if (node->_type == QLNodeReferenceCollectionAlias) {
QLAstQueryAddRefCount(context->_query, node->_value._stringValue);
}
lhs = node->_lhs;
if (lhs != 0) {
QLOptimizeRefCountCollections(context, lhs);
}
rhs = node->_rhs;
if (rhs != 0) {
QLOptimizeRefCountCollections(context, rhs);
}
}
////////////////////////////////////////////////////////////////////////////////
/// @brief Reference count all used collections in a query
///
/// Reference counting is later used to remove unnecessary joins
////////////////////////////////////////////////////////////////////////////////
static void QLOptimizeCountRefs (const QL_parser_context_t* context) {
QL_ast_node_t* next = 0;
QL_ast_node_t* node = (QL_ast_node_t*) context->_query->_from._base;
QL_ast_node_t* alias;
if (context->_query->_from._collections._nrUsed < 2) {
// we don't have a join, no need to refcount anything
return;
}
// mark collections used in select, where and order
QLOptimizeRefCountCollections(context, context->_query->_select._base);
QLOptimizeRefCountCollections(context, context->_query->_where._base);
QLOptimizeRefCountCollections(context, context->_query->_order._base);
// mark collections used in on clauses
node = node->_next;
while (node != 0) {
next = node->_next;
if (next == 0) {
break;
}
alias = (QL_ast_node_t*) ((QL_ast_node_t*) next->_lhs)->_rhs;
if ((QLAstQueryGetRefCount(context->_query, alias->_value._stringValue) > 0) ||
(next->_type == QLNodeJoinInner)) {
QLOptimizeRefCountCollections(context, next->_rhs);
}
node = node->_next;
}
}
////////////////////////////////////////////////////////////////////////////////
/// @brief optimize from/joins
////////////////////////////////////////////////////////////////////////////////
void QLOptimizeFrom (const QL_parser_context_t* context) {
QL_ast_node_t* temp;
QL_ast_node_t* alias;
QL_ast_node_t* responsibleNode;
QL_ast_node_t* next = 0;
QL_ast_node_t* node = (QL_ast_node_t*) context->_query->_from._base;
QLOptimizeCountRefs(context);
responsibleNode = node;
node = node->_next;
// iterate over all joins
while (node != 0) {
if (node->_rhs) {
// optimize on clause
QLOptimizeExpression(node->_rhs);
}
next = node->_next;
if (next == 0) {
break;
}
assert(next->_lhs);
alias = (QL_ast_node_t*) ((QL_ast_node_t*) next->_lhs)->_rhs;
if ((QLAstQueryGetRefCount(context->_query, alias->_value._stringValue) < 1) &&
(next->_type == QLNodeJoinLeft ||
next->_type == QLNodeJoinRight ||
next->_type == QLNodeJoinList)) {
// remove unused list or outer joined collections
// move joined collection one up
node->_next = next->_next;
// continue at the same position as the new collection at the current
// position might also be removed if it is useless
continue;
}
if (next->_type == QLNodeJoinRight) {
// convert a right join into a left join
next->_type = QLNodeJoinLeft;
temp = next->_lhs;
node->_next = 0;
next->_lhs = node;
temp->_next = next;
responsibleNode->_next = temp;
node = temp;
}
responsibleNode = node;
node = node->_next;
}
}
////////////////////////////////////////////////////////////////////////////////
/// @brief Find a specific range in a range vector
///
/// The value is looked up using its hash value. A value of 0 will be returned
/// to indicate the range is not contained in the vector. Otherwise, a value
/// of >= 1 will be returned that indicates the range's position in the vector.
////////////////////////////////////////////////////////////////////////////////
static QL_optimize_range_t* QLOptimizeGetRangeByHash (const uint64_t hash,
TRI_vector_pointer_t* ranges) {
QL_optimize_range_t* range;
size_t i;
assert(ranges);
for (i = 0; i < ranges->_length; i++) {
range = (QL_optimize_range_t*) ranges->_buffer[i];
if (range && range->_hash == hash) {
return range;
}
}
// range is not contained in the vector
return NULL;
}
////////////////////////////////////////////////////////////////////////////////
/// @brief Free all existing ranges in a range vector
////////////////////////////////////////////////////////////////////////////////
void QLOptimizeFreeRangeVector (TRI_vector_pointer_t* vector) {
QL_optimize_range_t* range;
size_t i;
for (i = 0; i < vector->_length; i++) {
range = (QL_optimize_range_t*) vector->_buffer[i];
if (!range) {
continue;
}
if (range->_field) {
TRI_Free(range->_field);
}
if (range->_refValue._field) {
TRI_FreeString(range->_refValue._field);
}
TRI_Free(range);
}
TRI_DestroyVectorPointer(vector);
}
////////////////////////////////////////////////////////////////////////////////
/// @brief Combine multiple ranges into less ranges if possible
///
/// Multiple ranges for the same field will be merged into one range if
/// possible. Definitely senseless ranges will be removed and replaced by (bool)
/// false values. They can then be removed later by further expression
/// optimization.
////////////////////////////////////////////////////////////////////////////////
static TRI_vector_pointer_t* QLOptimizeCombineRanges (const QL_ast_node_type_e type,
QL_ast_node_t* node,
TRI_vector_pointer_t* ranges) {
TRI_vector_pointer_t* vector;
QL_optimize_range_t* range;
QL_optimize_range_t* previous;
size_t i;
int compareResult;
vector = (TRI_vector_pointer_t*) TRI_Allocate(sizeof(TRI_vector_pointer_t));
if (!vector) {
return NULL;
}
TRI_InitVectorPointer(vector);
for (i = 0; i < ranges->_length; i++) {
range = (QL_optimize_range_t*) ranges->_buffer[i];
if (!range) {
if (type == QLNodeBinaryOperatorAnd) {
goto INVALIDATE_NODE;
}
continue;
}
assert(range);
if (type == QLNodeBinaryOperatorAnd) {
if (range->_minStatus == RANGE_VALUE_INFINITE &&
range->_maxStatus == RANGE_VALUE_INFINITE) {
// ignore !== and != operators in logical &&
continue;
}
}
previous = QLOptimizeGetRangeByHash(range->_hash, vector);
if (type == QLNodeBinaryOperatorOr) {
// only use logical || operator for same field. if field name differs, an ||
// effectively kills all ranges
if (vector->_length >0 && !previous) {
QLOptimizeFreeRangeVector(vector);
TRI_InitVectorPointer(vector);
goto EXIT;
}
}
if (!previous) {
// push range into result vector
TRI_PushBackVectorPointer(vector, range);
// remove range from original vector to avoid double freeing
ranges->_buffer[i] = NULL;
continue;
}
if (type == QLNodeBinaryOperatorOr) {
// logical || operator
if (range->_minStatus == RANGE_VALUE_INFINITE &&
range->_maxStatus == RANGE_VALUE_INFINITE) {
// !== and != operators in an || always set result range to infinite
previous->_minStatus = range->_minStatus;
previous->_maxStatus = range->_maxStatus;
continue;
}
if ((previous->_maxStatus == RANGE_VALUE_INFINITE &&
range->_minStatus == RANGE_VALUE_INFINITE) ||
(previous->_minStatus == RANGE_VALUE_INFINITE &&
range->_maxStatus == RANGE_VALUE_INFINITE)) {
previous->_minStatus = RANGE_VALUE_INFINITE;
previous->_maxStatus = RANGE_VALUE_INFINITE;
continue;
}
if (previous->_valueType != range->_valueType) {
// The two ranges have different data types.
// Thus set result range to infinite because we cannot merge these ranges
previous->_minStatus = RANGE_VALUE_INFINITE;
previous->_maxStatus = RANGE_VALUE_INFINITE;
continue;
}
if (previous->_valueType == RANGE_TYPE_DOUBLE) {
// combine two double ranges
if (previous->_minStatus != RANGE_VALUE_INFINITE &&
range->_minStatus != RANGE_VALUE_INFINITE) {
if (range->_minValue._doubleValue <= previous->_minValue._doubleValue) {
// adjust lower bound
if (range->_minValue._doubleValue == previous->_minValue._doubleValue) {
if (previous->_minStatus == RANGE_VALUE_INCLUDED ||
range->_minStatus == RANGE_VALUE_INCLUDED) {
previous->_minStatus = RANGE_VALUE_INCLUDED;
}
else {
previous->_minStatus = RANGE_VALUE_EXCLUDED;
}
}
else {
previous->_minStatus = range->_minStatus;
}
previous->_minValue._doubleValue = range->_minValue._doubleValue;
}
}
if (previous->_maxStatus != RANGE_VALUE_INFINITE &&
range->_maxStatus != RANGE_VALUE_INFINITE) {
if (range->_maxValue._doubleValue >= previous->_maxValue._doubleValue) {
// adjust upper bound
if (range->_maxValue._doubleValue == previous->_maxValue._doubleValue) {
if (previous->_maxStatus == RANGE_VALUE_INCLUDED ||
range->_maxStatus == RANGE_VALUE_INCLUDED) {
previous->_maxStatus = RANGE_VALUE_INCLUDED;
}
else {
previous->_maxStatus = RANGE_VALUE_EXCLUDED;
}
}
else {
previous->_maxStatus = range->_maxStatus;
}
previous->_maxValue._doubleValue = range->_maxValue._doubleValue;
}
}
}
else if (previous->_valueType == RANGE_TYPE_STRING) {
// combine two string ranges
if (previous->_minStatus != RANGE_VALUE_INFINITE &&
range->_minStatus != RANGE_VALUE_INFINITE) {
compareResult = strcmp(range->_minValue._stringValue,
previous->_minValue._stringValue);
if (compareResult <= 0) {
// adjust lower bound
if (compareResult == 0) {
if (previous->_minStatus == RANGE_VALUE_INCLUDED ||
range->_minStatus == RANGE_VALUE_INCLUDED) {
previous->_minStatus = RANGE_VALUE_INCLUDED;
}
else {
previous->_minStatus = RANGE_VALUE_EXCLUDED;
}
}
else {
previous->_minStatus = range->_minStatus;
}
if (compareResult == 0) {
if (previous->_minStatus == RANGE_VALUE_INCLUDED ||
range->_minStatus == RANGE_VALUE_INCLUDED) {
previous->_minStatus = RANGE_VALUE_INCLUDED;
}
else {
previous->_minStatus = RANGE_VALUE_EXCLUDED;
}
}
else {
previous->_minStatus = range->_minStatus;
}
previous->_minValue._stringValue = range->_minValue._stringValue;
}
}
if (previous->_maxStatus != RANGE_VALUE_INFINITE &&
range->_maxStatus != RANGE_VALUE_INFINITE) {
compareResult = strcmp(range->_maxValue._stringValue,
previous->_maxValue._stringValue);
if (compareResult >= 0) {
// adjust upper bound
if (compareResult == 0) {
if (previous->_maxStatus == RANGE_VALUE_INCLUDED ||
range->_maxStatus == RANGE_VALUE_INCLUDED) {
previous->_maxStatus = RANGE_VALUE_INCLUDED;
}
else {
previous->_maxStatus = RANGE_VALUE_EXCLUDED;
}
}
else {
previous->_maxStatus = range->_maxStatus;
}
previous->_maxValue._stringValue = range->_maxValue._stringValue;
}
}
}
}
else {
// logical && operator
if (previous->_valueType != range->_valueType) {
// ranges have different data types. set result range to infinite
previous->_minStatus = RANGE_VALUE_INFINITE;
previous->_maxStatus = RANGE_VALUE_INFINITE;
continue;
}
if (previous->_valueType == RANGE_TYPE_DOUBLE) {
// combine two double ranges
if (previous->_minStatus != RANGE_VALUE_INFINITE &&
range->_maxStatus != RANGE_VALUE_INFINITE) {
if (range->_maxValue._doubleValue < previous->_minValue._doubleValue ||
(range->_maxValue._doubleValue <= previous->_minValue._doubleValue &&
previous->_minStatus == RANGE_VALUE_EXCLUDED)) {
// new upper bound is lower than previous lower bound => empty range
// old: | |
// new: | |
goto INVALIDATE_NODE;
}
}
if (previous->_maxStatus != RANGE_VALUE_INFINITE &&
range->_minStatus != RANGE_VALUE_INFINITE) {
if (range->_minValue._doubleValue < previous->_maxValue._doubleValue ||
(range->_minValue._doubleValue <= previous->_maxValue._doubleValue &&
previous->_maxStatus == RANGE_VALUE_EXCLUDED)) {
// new lower bound is higher than previous upper bound => empty range
// old: | |
// new: | |
goto INVALIDATE_NODE;
}
}
if (previous->_minStatus != RANGE_VALUE_INFINITE) {
if (range->_minStatus == RANGE_VALUE_INFINITE ||
previous->_minValue._doubleValue > range->_minValue._doubleValue) {
// adjust lower bound
range->_minValue._doubleValue = previous->_minValue._doubleValue;
range->_minStatus = previous->_minStatus;
}
}
if (previous->_maxStatus != RANGE_VALUE_INFINITE) {
if (range->_maxStatus == RANGE_VALUE_INFINITE ||
previous->_maxValue._doubleValue < range->_maxValue._doubleValue) {
// adjust upper bound
range->_maxValue._doubleValue = previous->_maxValue._doubleValue;
range->_maxStatus = previous->_maxStatus;
}
}
if (range->_minStatus != RANGE_VALUE_INFINITE &&
range->_maxStatus != RANGE_VALUE_INFINITE) {
if (range->_minValue._doubleValue > range->_maxValue._doubleValue) {
goto INVALIDATE_NODE;
}
}
previous->_minValue._doubleValue = range->_minValue._doubleValue;
previous->_maxValue._doubleValue = range->_maxValue._doubleValue;
previous->_minStatus = range->_minStatus;
previous->_maxStatus = range->_maxStatus;
}
else if (previous->_valueType == RANGE_TYPE_STRING) {
// combine two string ranges
if (previous->_minStatus != RANGE_VALUE_INFINITE &&
range->_maxStatus != RANGE_VALUE_INFINITE) {
compareResult = strcmp(range->_maxValue._stringValue,
previous->_minValue._stringValue);
if (compareResult < 0 ||
(compareResult <= 0 && previous->_minStatus == RANGE_VALUE_EXCLUDED)) {
// new upper bound is lower than previous lower bound => empty range
// old: | |
// new: | |
goto INVALIDATE_NODE;
}
}
if (previous->_maxStatus != RANGE_VALUE_INFINITE &&
range->_minStatus != RANGE_VALUE_INFINITE) {
compareResult = strcmp(range->_minValue._stringValue,
previous->_maxValue._stringValue);
if (compareResult < 0 ||
(compareResult <= 0 && previous->_maxStatus == RANGE_VALUE_EXCLUDED)) {
// new lower bound is higher than previous upper bound => empty range
// old: | |
// new: | |
goto INVALIDATE_NODE;
}
}
if (previous->_minStatus != RANGE_VALUE_INFINITE) {
if (range->_minStatus == RANGE_VALUE_INFINITE) {
compareResult = 1;
}
else {
compareResult = strcmp(previous->_minValue._stringValue,
range->_minValue._stringValue);
}
if (range->_minStatus == RANGE_VALUE_INFINITE || compareResult > 0) {
// adjust lower bound
range->_minValue._stringValue = previous->_minValue._stringValue;
range->_minStatus = previous->_minStatus;
}
}
if (previous->_maxStatus != RANGE_VALUE_INFINITE) {
if (range->_maxStatus == RANGE_VALUE_INFINITE) {
compareResult = -1;
}
else {
compareResult = strcmp(previous->_maxValue._stringValue,
range->_maxValue._stringValue);
}
if (range->_maxStatus == RANGE_VALUE_INFINITE || compareResult < 0) {
// adjust upper bound
range->_maxValue._stringValue = previous->_maxValue._stringValue;
range->_maxStatus = previous->_maxStatus;
}
}
if (range->_minStatus != RANGE_VALUE_INFINITE &&
range->_maxStatus != RANGE_VALUE_INFINITE) {
compareResult = strcmp(range->_minValue._stringValue,
range->_maxValue._stringValue);
if (compareResult > 0) {
goto INVALIDATE_NODE;
}
}
previous->_minValue._stringValue = range->_minValue._stringValue;
previous->_maxValue._stringValue = range->_maxValue._stringValue;
previous->_minStatus = range->_minStatus;
previous->_maxStatus = range->_maxStatus;
}
}
}
goto EXIT;
INVALIDATE_NODE:
QLOptimizeMakeValueBool(node, false);
QLOptimizeFreeRangeVector(vector);
TRI_InitVectorPointer(vector);
// push nil pointer to indicate range is invalid
TRI_PushBackVectorPointer(vector, NULL);
EXIT:
QLOptimizeFreeRangeVector(ranges);
TRI_Free(ranges);
return vector;
}
////////////////////////////////////////////////////////////////////////////////
/// @brief Merge two range vectors into one
////////////////////////////////////////////////////////////////////////////////
static TRI_vector_pointer_t* QLOptimizeMergeRangeVectors (TRI_vector_pointer_t* left,
TRI_vector_pointer_t* right) {
size_t i;
if (!left && !right) {
// both vectors invalid => nothing to do
return NULL;
}
if (left && !right) {
// left vector is valid, right is not => return left vector
return left;
}
if (!left && right) {
// right vector is valid, left is not => return right vector
return right;
}
// both vectors are valid, move elements from right vector into left one
for (i = 0; i < right->_length; i++) {
TRI_PushBackVectorPointer(left, right->_buffer[i]);
}
TRI_DestroyVectorPointer(right);
TRI_Free(right);
return left;
}
////////////////////////////////////////////////////////////////////////////////
/// @brief create a vector for ranges with one initial element
////////////////////////////////////////////////////////////////////////////////
static TRI_vector_pointer_t* QLOptimizeCreateRangeVector (QL_optimize_range_t* range) {
TRI_vector_pointer_t* vector;
if (!range) {
return NULL;
}
vector = (TRI_vector_pointer_t*) TRI_Allocate(sizeof(TRI_vector_pointer_t));
if (!vector) {
return NULL;
}
TRI_PushBackVectorPointer(vector, range);
return vector;
}
////////////////////////////////////////////////////////////////////////////////
/// @brief create a value range from a name, relop, value combination
///
/// This function is called for each (name relop value) combination found.
/// The range will get a type matching the data type for the comparison.
/// Currently supported data types are doubles and strings.
/// The range will have a lower and an upper bound (minValue and maxValue), both
/// of which can be infinite.
///
/// The ranges will be composed as follows:
///
/// Comparison type I/E Lower value Upper value I/E
/// -----------------------------------------------------------------------------
/// - equality (field == value) I value value I
/// - unequality (field != value) - -inf +inf -
/// - greater (field > value) E value +inf -
/// - greater eq (field >= value) I value +inf -
/// - less (field < value) - -inf value E
/// - less eq (field <= value) - -inf value I
///
/// "I" means that the value itself is included in the range.
/// "E" means that the value itself is excluded from the range.
/// "-" means "not relevant"
///
/// The ranges created are used later to combined for logical && and ||
/// operations and reduced to simpler or impossible ranges if possible.
////////////////////////////////////////////////////////////////////////////////
static QL_optimize_range_t* QLOptimizeCreateRange (QL_ast_node_t* memberNode,
QL_ast_node_t* valueNode,
const QL_ast_node_type_e type) {
QL_optimize_range_t* range;
TRI_string_buffer_t* name;
QL_ast_node_t* lhs;
QL_javascript_conversion_t* documentJs;
// get the field name
name = QLOptimizeGetMemberNameString(memberNode, false);
if (!name) {
return NULL;
}
range = (QL_optimize_range_t*) TRI_Allocate(sizeof(QL_optimize_range_t));
if (!range) {
// clean up
TRI_FreeStringBuffer(name);
TRI_Free(name);
return NULL;
}
range->_refValue._field = NULL;
range->_refValue._collection = NULL;
// get value
if (valueNode->_type == QLNodeValueNumberDouble ||
valueNode->_type == QLNodeValueNumberDoubleString) {
// range is of type double
range->_valueType = RANGE_TYPE_DOUBLE;
}
else if (valueNode->_type == QLNodeValueString) {
// range is of type string
range->_valueType = RANGE_TYPE_STRING;
}
else if (valueNode->_type == QLNodeValueDocument) {
range->_valueType = RANGE_TYPE_JSON;
}
else if (valueNode->_type == QLNodeContainerMemberAccess) {
range->_valueType = RANGE_TYPE_FIELD;
}
else {
assert(false);
}
// store collection, field name and hash
lhs = memberNode->_lhs;
range->_collection = lhs->_value._stringValue;
range->_field = TRI_DuplicateString(name->_buffer);
range->_hash = QLOptimizeGetMemberNameHash(memberNode);
// we can now free the temporary name buffer
TRI_FreeStringBuffer(name);
TRI_Free(name);
if (type == QLNodeBinaryOperatorIdentical ||
type == QLNodeBinaryOperatorEqual) {
// === and == , range is [ value (inc) ... value (inc) ]
if (range->_valueType == RANGE_TYPE_FIELD) {
range->_refValue._collection =
((QL_ast_node_t*) valueNode->_lhs)->_value._stringValue;
name = QLOptimizeGetMemberNameString(valueNode, false);
if (name) {
range->_refValue._field = TRI_DuplicateString(name->_buffer);
TRI_FreeStringBuffer(name);
TRI_Free(name);
}
}
else if (range->_valueType == RANGE_TYPE_DOUBLE) {
range->_minValue._doubleValue = QLOptimizeGetDouble(valueNode);
range->_maxValue._doubleValue = range->_minValue._doubleValue;
}
else if (range->_valueType == RANGE_TYPE_STRING) {
range->_minValue._stringValue = valueNode->_value._stringValue;
range->_maxValue._stringValue = range->_minValue._stringValue;
}
else if (range->_valueType == RANGE_TYPE_JSON) {
documentJs = QLJavascripterInit();
if (!documentJs) {
TRI_FreeStringBuffer(name);
TRI_Free(name);
TRI_Free(range);
return NULL;
}
QLJavascripterConvert(documentJs, valueNode);
range->_minValue._stringValue = documentJs->_buffer->_buffer;
range->_maxValue._stringValue = range->_minValue._stringValue;
QLJavascripterFree(documentJs);
}
range->_minStatus = RANGE_VALUE_INCLUDED;
range->_maxStatus = RANGE_VALUE_INCLUDED;
}
else if (type == QLNodeBinaryOperatorUnidentical ||
type == QLNodeBinaryOperatorUnequal) {
// !== and != , range is [ -inf ... +inf ]
range->_minStatus = RANGE_VALUE_INFINITE;
range->_maxStatus = RANGE_VALUE_INFINITE;
}
else if (type == QLNodeBinaryOperatorGreaterEqual ||
type == QLNodeBinaryOperatorGreater) {
// >= and > , range is [ value ... +inf ]
if (range->_valueType == RANGE_TYPE_DOUBLE) {
range->_minValue._doubleValue = QLOptimizeGetDouble(valueNode);
}
else if (range->_valueType == RANGE_TYPE_STRING) {
range->_minValue._stringValue = valueNode->_value._stringValue;
}
if (type == QLNodeBinaryOperatorGreaterEqual) {
// value is included (>=), range is [ value (inc) ... +inf ]
range->_minStatus = RANGE_VALUE_INCLUDED;
}
else {
// value is excluded (>), range is [ value (enc) ... +inf ]
range->_minStatus = RANGE_VALUE_EXCLUDED;
}
range->_maxStatus = RANGE_VALUE_INFINITE;
}
else if (type == QLNodeBinaryOperatorLessEqual ||
type == QLNodeBinaryOperatorLess) {
// <= and < , range is [ -inf ... value ]
if (range->_valueType == RANGE_TYPE_DOUBLE) {
range->_maxValue._doubleValue = QLOptimizeGetDouble(valueNode);
}
else if (range->_valueType == RANGE_TYPE_STRING) {
range->_maxValue._stringValue = valueNode->_value._stringValue;
}
range->_minStatus = RANGE_VALUE_INFINITE;
if (type == QLNodeBinaryOperatorLessEqual) {
// value is included (<=) , range is [ -inf ... value (inc) ]
range->_maxStatus = RANGE_VALUE_INCLUDED;
}
else {
// value is excluded (<) , range is [ -inf ... value (exc) ]
range->_maxStatus = RANGE_VALUE_EXCLUDED;
}
}
return range;
}
////////////////////////////////////////////////////////////////////////////////
/// @brief recursively optimize nodes in an expression AST
////////////////////////////////////////////////////////////////////////////////
TRI_vector_pointer_t* QLOptimizeCondition (QL_ast_node_t* node) {
QL_ast_node_t *lhs, *rhs;
TRI_vector_pointer_t* ranges;
TRI_vector_pointer_t* combinedRanges;
QL_ast_node_type_e type;
if (node == 0) {
return 0;
}
if (QLAstNodeIsValueNode(node)) {
return 0;
}
type = node->_type;
lhs = node->_lhs;
rhs = node->_rhs;
if (type == QLNodeBinaryOperatorAnd || type == QLNodeBinaryOperatorOr) {
// logical && or logical ||
// get the range vectors from both operands
ranges = QLOptimizeMergeRangeVectors(QLOptimizeCondition(lhs),
QLOptimizeCondition(rhs));
if (ranges) {
if (ranges->_length > 0) {
// try to merge the ranges
combinedRanges = QLOptimizeCombineRanges(type, node, ranges);
}
else {
combinedRanges = NULL;
}
return combinedRanges;
}
}
else if (type == QLNodeBinaryOperatorIdentical ||
type == QLNodeBinaryOperatorUnidentical ||
type == QLNodeBinaryOperatorEqual ||
type == QLNodeBinaryOperatorUnequal ||
type == QLNodeBinaryOperatorLess ||
type == QLNodeBinaryOperatorGreater ||
type == QLNodeBinaryOperatorLessEqual ||
type == QLNodeBinaryOperatorGreaterEqual) {
// comparison operator
if (lhs->_type == QLNodeContainerMemberAccess &&
rhs->_type == QLNodeContainerMemberAccess) {
// collection.attribute relop collection.attribute
return QLOptimizeMergeRangeVectors(
QLOptimizeCreateRangeVector(QLOptimizeCreateRange(lhs, rhs, type)),
QLOptimizeCreateRangeVector(QLOptimizeCreateRange(rhs, lhs, type))
);
}
else if (lhs->_type == QLNodeContainerMemberAccess &&
(type == QLNodeBinaryOperatorIdentical ||
type == QLNodeBinaryOperatorEqual) &&
rhs->_type == QLNodeValueDocument &&
QLOptimizeIsStaticDocument(rhs)) {
// collection.attribute == document
return QLOptimizeCreateRangeVector(QLOptimizeCreateRange(lhs, rhs, type));
}
else if (lhs->_type == QLNodeContainerMemberAccess &&
(rhs->_type == QLNodeValueNumberDouble ||
rhs->_type == QLNodeValueNumberDoubleString ||
rhs->_type == QLNodeValueString)) {
// collection.attribute relop value
return QLOptimizeCreateRangeVector(QLOptimizeCreateRange(lhs, rhs, type));
}
else if (rhs->_type == QLNodeContainerMemberAccess &&
(type == QLNodeBinaryOperatorIdentical ||
type == QLNodeBinaryOperatorEqual) &&
lhs->_type == QLNodeValueDocument &&
QLOptimizeIsStaticDocument(lhs)) {
// document == collection.attribute
return QLOptimizeCreateRangeVector(QLOptimizeCreateRange(rhs, lhs, type));
} else if (rhs->_type == QLNodeContainerMemberAccess &&
(lhs->_type == QLNodeValueNumberDouble ||
lhs->_type == QLNodeValueNumberDoubleString ||
lhs->_type == QLNodeValueString)) {
// value relop collection.attrbiute
return QLOptimizeCreateRangeVector(
QLOptimizeCreateRange(rhs, lhs, QLAstNodeGetReversedRelationalOperator(type)));
}
}
return 0;
}
////////////////////////////////////////////////////////////////////////////////
/// @brief get the type of a query's SELECT part
////////////////////////////////////////////////////////////////////////////////
QL_ast_query_select_type_e QLOptimizeGetSelectType (const QL_ast_query_t* query) {
char* alias;
QL_ast_node_t* selectNode = query->_select._base;
if (selectNode == 0) {
return QLQuerySelectTypeUndefined;
}
if (selectNode->_type == QLNodeValueIdentifier && selectNode->_value._stringValue != 0) {
alias = QLAstQueryGetPrimaryAlias(query);
if (alias != 0 && strcmp(alias, selectNode->_value._stringValue) == 0) {
// primary document alias specified as (only) SELECT part
return QLQuerySelectTypeSimple;
}
}
// SELECT part must be evaluated for all rows
return QLQuerySelectTypeEvaluated;
}
////////////////////////////////////////////////////////////////////////////////
/// @brief get the type of a query's WHERE/ON condition
////////////////////////////////////////////////////////////////////////////////
QL_ast_query_where_type_e QLOptimizeGetWhereType (const QL_ast_node_t* node) {
if (node == 0) {
// query does not have a WHERE part
return QLQueryWhereTypeAlwaysTrue;
}
if (QLAstNodeIsBooleanizable(node)) {
// WHERE part is constant
if (QLOptimizeGetBool(node)) {
// WHERE is always true
return QLQueryWhereTypeAlwaysTrue;
}
// WHERE is always false
return QLQueryWhereTypeAlwaysFalse;
}
// WHERE must be checked for all records
return QLQueryWhereTypeMustEvaluate;
}
////////////////////////////////////////////////////////////////////////////////
/// @brief get the type of a query's ORDER BY condition
////////////////////////////////////////////////////////////////////////////////
QL_ast_query_order_type_e QLOptimizeGetOrderType (const QL_ast_node_t* node) {
QL_ast_node_t* condition;
if (node == 0) {
// query does not have an ORDER BY part
return QLQueryOrderTypeNone;
}
node = node->_next;
while (node) {
condition = (QL_ast_node_t*) node->_lhs;
if (!QLAstNodeIsBooleanizable(condition)) {
// ORDER BY must be evaluated for all records
return QLQueryOrderTypeMustEvaluate;
}
node = node->_next;
}
// ORDER BY is constant (same for all records) and can be ignored
return QLQueryOrderTypeNone;
}
////////////////////////////////////////////////////////////////////////////////
/// @brief determine which indexes to use for a query
////////////////////////////////////////////////////////////////////////////////
void QLOptimizeDetermineIndexes (QL_ast_query_t* query) {
TRI_vector_pointer_t* ranges;
QL_optimize_range_t* range;
TRI_vector_pointer_t indexDefinitions;
TRI_index_definition_t* indexDefinition;
QL_ast_node_t* node;
char* collectionName;
char* alias;
size_t i, j, k, matches;
size_t count = 0;
return;
node = (QL_ast_node_t*) query->_from._base->_next;
assert(node != 0);
// enum all collections used in query
while (node != 0) {
ranges = 0;
if (count++ == 0) {
collectionName = ((QL_ast_node_t*) node->_lhs)->_value._stringValue;
alias = ((QL_ast_node_t*) node->_rhs)->_value._stringValue;
ranges = QLOptimizeCondition(query->_where._base);
}
else {
collectionName = ((QL_ast_node_t*) ((QL_ast_node_t*) node->_lhs)->_lhs)->_value._stringValue;
alias = ((QL_ast_node_t*) ((QL_ast_node_t*) node->_lhs)->_rhs)->_value._stringValue;
}
// accessType = TABLE_SCAN;
if (ranges) {
indexDefinitions = TRI_GetCollectionIndexes(query->_vocbase, collectionName);
// enum all indexes
for (i = 0; i < indexDefinitions._length; i++) {
indexDefinition = (TRI_index_definition_t*) indexDefinitions._buffer[i];
matches = 0;
for (j = 0 ; j < indexDefinition->_fields._length; j++) {
for (k = 0; k < ranges->_length; k++) {
range = (QL_optimize_range_t*) ranges->_buffer[k];
// check if collection name matches
if (strcmp(range->_collection, alias) != 0) {
continue;
}
// check if field names match
if (strcmp(indexDefinition->_fields._buffer[j], range->_field) != 0) {
continue;
}
if (indexDefinition->_type == TRI_IDX_TYPE_PRIMARY_INDEX ||
indexDefinition->_type == TRI_IDX_TYPE_HASH_INDEX) {
// check if index can be used (primary and hash index only support equality comparisons)
if (range->_minStatus == RANGE_VALUE_INFINITE ||
range->_maxStatus == RANGE_VALUE_INFINITE) {
continue;
}
if (range->_valueType == RANGE_TYPE_DOUBLE &&
range->_minValue._doubleValue != range->_maxValue._doubleValue) {
continue;
}
if ((range->_valueType == RANGE_TYPE_STRING ||
range->_valueType == RANGE_TYPE_JSON) &&
strcmp(range->_minValue._stringValue, range->_maxValue._stringValue) != 0) {
continue;
}
}
matches++;
break;
}
}
if (matches == indexDefinition->_fields._length) {
printf("PICKING INDEX iid: %lu, TYPE: %lu UNIQUE: %lu\n",(unsigned long) indexDefinition->_iid, (unsigned long) indexDefinition->_type, (unsigned long) indexDefinition->_isUnique);
for (j = 0; j < indexDefinition->_fields._length; j++) {
printf("- FIELD: %s\n", indexDefinition->_fields._buffer[j]);
}
}
}
TRI_DestroyVectorPointer(&indexDefinitions);
QLOptimizeFreeRangeVector(ranges);
TRI_Free(ranges);
}
node = node->_next;
}
}
////////////////////////////////////////////////////////////////////////////////
/// @}
////////////////////////////////////////////////////////////////////////////////
// Local Variables:
// mode: outline-minor
// outline-regexp: "^\\(/// @brief\\|/// {@inheritDoc}\\|/// @addtogroup\\|// --SECTION--\\|/// @\\}\\)"
// End: