mirror of https://gitee.com/bigwinds/arangodb
1764 lines
59 KiB
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:
|
|
|