1
0
Fork 0

allow AQL to use indexes in additional cases

This commit is contained in:
Jan Steemann 2012-10-08 12:51:27 +02:00
parent 75d62962a6
commit 5da08eb283
5 changed files with 271 additions and 4 deletions

View File

@ -1618,6 +1618,7 @@ UNITTESTS_SERVER = $(addprefix --javascript.unit-tests ,$(SHELL_SERVER))
################################################################################
SHELL_SERVER_AHUACATL = @top_srcdir@/js/server/tests/ahuacatl-ranges.js \
@top_srcdir@/js/server/tests/ahuacatl-queries-optimiser.js \
@top_srcdir@/js/server/tests/ahuacatl-queries-optimiser-ref.js \
@top_srcdir@/js/server/tests/ahuacatl-escaping.js \
@top_srcdir@/js/server/tests/ahuacatl-functions.js \
@top_srcdir@/js/server/tests/ahuacatl-variables.js \

View File

@ -242,6 +242,7 @@ unittests-shell-server:
SHELL_SERVER_AHUACATL = @top_srcdir@/js/server/tests/ahuacatl-ranges.js \
@top_srcdir@/js/server/tests/ahuacatl-queries-optimiser.js \
@top_srcdir@/js/server/tests/ahuacatl-queries-optimiser-ref.js \
@top_srcdir@/js/server/tests/ahuacatl-escaping.js \
@top_srcdir@/js/server/tests/ahuacatl-functions.js \
@top_srcdir@/js/server/tests/ahuacatl-variables.js \

View File

@ -2032,22 +2032,31 @@ static TRI_vector_pointer_t* ProcessNode (TRI_aql_context_t* const context,
node->_type == TRI_AQL_NODE_OPERATOR_BINARY_IN) {
TRI_aql_node_t* lhs = TRI_AQL_NODE_MEMBER(node, 0);
TRI_aql_node_t* rhs = TRI_AQL_NODE_MEMBER(node, 1);
TRI_vector_pointer_t* previous;
TRI_aql_attribute_name_t* field;
TRI_aql_node_t* node1;
TRI_aql_node_t* node2;
TRI_aql_node_type_e operator;
bool useBoth;
if (node->_type == TRI_AQL_NODE_OPERATOR_BINARY_IN && rhs->_type != TRI_AQL_NODE_LIST) {
// in operator is special. if right operand is not a list, we must abort here
return NULL;
}
useBoth = false;
if ((lhs->_type == TRI_AQL_NODE_REFERENCE || lhs->_type == TRI_AQL_NODE_ATTRIBUTE_ACCESS) &&
(TRI_IsConstantValueNodeAql(rhs) || rhs->_type == TRI_AQL_NODE_REFERENCE || rhs->_type == TRI_AQL_NODE_ATTRIBUTE_ACCESS)) {
// collection.attribute|reference operator const value|reference|attribute access
node1 = lhs;
node2 = rhs;
operator = node->_type;
if (rhs->_type == TRI_AQL_NODE_REFERENCE || rhs->_type == TRI_AQL_NODE_ATTRIBUTE_ACCESS) {
// expression of type reference|attribute access operator reference|attribute access
useBoth = true;
}
}
else if ((rhs->_type == TRI_AQL_NODE_REFERENCE || rhs->_type == TRI_AQL_NODE_ATTRIBUTE_ACCESS) &&
(TRI_IsConstantValueNodeAql(lhs) || lhs->_type == TRI_AQL_NODE_REFERENCE || lhs->_type == TRI_AQL_NODE_ATTRIBUTE_ACCESS)) {
@ -2056,6 +2065,11 @@ static TRI_vector_pointer_t* ProcessNode (TRI_aql_context_t* const context,
node2 = lhs;
operator = TRI_ReverseOperatorRelationalAql(node->_type);
assert(operator != TRI_AQL_NODE_NOP);
if (lhs->_type == TRI_AQL_NODE_REFERENCE || lhs->_type == TRI_AQL_NODE_ATTRIBUTE_ACCESS) {
// expression of type reference|attribute access operator reference|attribute access
useBoth = true;
}
}
else {
return NULL;
@ -2070,6 +2084,10 @@ static TRI_vector_pointer_t* ProcessNode (TRI_aql_context_t* const context,
return NULL;
}
previous = NULL;
again:
// we'll get back here for expressions of type a.x == b.y (where both sides are references)
field = GetAttributeName(context, node1);
if (field) {
@ -2082,7 +2100,7 @@ static TRI_vector_pointer_t* ProcessNode (TRI_aql_context_t* const context,
result = MergeVectors(context,
TRI_AQL_NODE_OPERATOR_BINARY_AND,
Vectorize(context, attributeAccess),
NULL,
previous,
inheritedRestrictions);
if (result == NULL) {
@ -2090,7 +2108,7 @@ static TRI_vector_pointer_t* ProcessNode (TRI_aql_context_t* const context,
}
else {
if (TRI_ContainsImpossibleAql(result)) {
// inject a dummy false == true node into the true if the condition is always false
// inject a dummy false == true node into the tree if the condition is always false
node->_type = TRI_AQL_NODE_OPERATOR_BINARY_EQ;
node->_members._buffer[0] = TRI_CreateNodeValueBoolAql(context, false);
node->_members._buffer[1] = TRI_CreateNodeValueBoolAql(context, true);
@ -2100,6 +2118,24 @@ static TRI_vector_pointer_t* ProcessNode (TRI_aql_context_t* const context,
}
}
if (useBoth) {
// in this situation, we have an expression of type a.x == b.y
// we'll have to process both sides of the expression
TRI_aql_node_t* tempNode;
// swap node1 and node2
tempNode = node1;
node1 = node2;
node2 = tempNode;
operator = TRI_ReverseOperatorRelationalAql(node->_type);
// and try again
previous = result;
useBoth = false;
goto again;
}
return result;
}
}

View File

@ -60,7 +60,7 @@ static void LogIndexString (const char* const what,
TRI_AppendStringStringBuffer(buffer, idx->_fields._buffer[i]);
}
LOG_TRACE("%s %s index (%s) for '%s'",
LOG_DEBUG("%s %s index (%s) for '%s'",
what,
TRI_TypeNameIndex(idx),
buffer->_buffer,
@ -290,7 +290,7 @@ TRI_aql_index_t* TRI_DetermineIndexAql (TRI_aql_context_t* const context,
// these indexes are valid candidates
break;
}
LogIndexString("checking", idx, collectionName);
TRI_ClearVectorPointer(&matches);

View File

@ -0,0 +1,229 @@
////////////////////////////////////////////////////////////////////////////////
/// @brief tests for query language, reference optimiser
///
/// @file
///
/// DISCLAIMER
///
/// Copyright 2010-2012 triagens GmbH, Cologne, Germany
///
/// Licensed under the Apache License, Version 2.0 (the "License");
/// you may not use this file except in compliance with the License.
/// You may obtain a copy of the License at
///
/// http://www.apache.org/licenses/LICENSE-2.0
///
/// Unless required by applicable law or agreed to in writing, software
/// distributed under the License is distributed on an "AS IS" BASIS,
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
/// See the License for the specific language governing permissions and
/// limitations under the License.
///
/// Copyright holder is triAGENS GmbH, Cologne, Germany
///
/// @author Jan Steemann
/// @author Copyright 2012, triAGENS GmbH, Cologne, Germany
////////////////////////////////////////////////////////////////////////////////
var jsunity = require("jsunity");
////////////////////////////////////////////////////////////////////////////////
/// @brief test suite
////////////////////////////////////////////////////////////////////////////////
function ahuacatlQueryOptimiserRefTestSuite () {
var users = null;
var cn = "UnitTestsAhuacatlOptimiserRef";
////////////////////////////////////////////////////////////////////////////////
/// @brief execute a given query
////////////////////////////////////////////////////////////////////////////////
function executeQuery (query) {
var cursor = AHUACATL_RUN(query, undefined);
if (cursor instanceof ArangoError) {
print(query, cursor.errorMessage);
}
assertFalse(cursor instanceof ArangoError);
return cursor;
}
////////////////////////////////////////////////////////////////////////////////
/// @brief execute a given query and return the results as an array
////////////////////////////////////////////////////////////////////////////////
function getQueryResults (query, isFlat) {
var result = executeQuery(query).getRows();
var results = [ ];
for (var i in result) {
if (!result.hasOwnProperty(i)) {
continue;
}
var row = result[i];
if (isFlat) {
results.push(row);
}
else {
var keys = [ ];
for (var k in row) {
if (row.hasOwnProperty(k) && k != '_id' && k != '_rev') {
keys.push(k);
}
}
keys.sort();
var resultRow = { };
for (var k in keys) {
if (keys.hasOwnProperty(k)) {
resultRow[keys[k]] = row[keys[k]];
}
}
results.push(resultRow);
}
}
return results;
}
return {
////////////////////////////////////////////////////////////////////////////////
/// @brief set up
////////////////////////////////////////////////////////////////////////////////
setUp : function () {
users = internal.db._create(cn);
users.save({ "id" : 100, "name" : "John", "age" : 37, "active" : true, "gender" : "m" });
users.save({ "id" : 101, "name" : "Fred", "age" : 36, "active" : true, "gender" : "m" });
users.save({ "id" : 102, "name" : "Jacob", "age" : 35, "active" : false, "gender" : "m" });
users.save({ "id" : 103, "name" : "Ethan", "age" : 34, "active" : false, "gender" : "m" });
users.save({ "id" : 104, "name" : "Michael", "age" : 33, "active" : true, "gender" : "m" });
users.save({ "id" : 105, "name" : "Alexander", "age" : 32, "active" : true, "gender" : "m" });
users.save({ "id" : 106, "name" : "Daniel", "age" : 31, "active" : true, "gender" : "m" });
users.save({ "id" : 107, "name" : "Anthony", "age" : 30, "active" : true, "gender" : "m" });
users.save({ "id" : 108, "name" : "Jim", "age" : 29, "active" : true, "gender" : "m" });
users.save({ "id" : 109, "name" : "Diego", "age" : 28, "active" : true, "gender" : "m" });
},
////////////////////////////////////////////////////////////////////////////////
/// @brief tear down
////////////////////////////////////////////////////////////////////////////////
tearDown : function () {
internal.db._drop(cn);
},
////////////////////////////////////////////////////////////////////////////////
/// @brief check a ref access without any indexes
////////////////////////////////////////////////////////////////////////////////
testRefAccess1 : function () {
var expected = [ { "name" : "John" }, { "name" : "Fred" }, { "name" : "Jacob" }, { "name" : "Ethan" }, { "name" : "Michael" }, { "name" : "Alexander" }, { "name" : "Daniel" }, { "name" : "Anthony" }, { "name" : "Jim"} , { "name" : "Diego" } ];
var actual = getQueryResults("FOR u1 IN " + cn + " FOR u2 IN " + cn + " FILTER u1.name == u2.name SORT u1.id RETURN { \"name\" : u1.name }", true);
assertEqual(expected, actual);
},
////////////////////////////////////////////////////////////////////////////////
/// @brief check a ref access without any indexes (reverted expression)
////////////////////////////////////////////////////////////////////////////////
testRefAccess2 : function () {
var expected = [ { "name" : "John" }, { "name" : "Fred" }, { "name" : "Jacob" }, { "name" : "Ethan" }, { "name" : "Michael" }, { "name" : "Alexander" }, { "name" : "Daniel" }, { "name" : "Anthony" }, { "name" : "Jim"} , { "name" : "Diego" } ];
var actual = getQueryResults("FOR u1 IN " + cn + " FOR u2 IN " + cn + " FILTER u2.name == u1.name SORT u1.id RETURN { \"name\" : u1.name }", true);
assertEqual(expected, actual);
},
////////////////////////////////////////////////////////////////////////////////
/// @brief check a ref access with _id filter
////////////////////////////////////////////////////////////////////////////////
testRefAccessId1 : function () {
var expected = [ { "name" : "John" }, { "name" : "Fred" }, { "name" : "Jacob" }, { "name" : "Ethan" }, { "name" : "Michael" }, { "name" : "Alexander" }, { "name" : "Daniel" }, { "name" : "Anthony" }, { "name" : "Jim"} , { "name" : "Diego" } ];
var actual = getQueryResults("FOR u1 IN " + cn + " FOR u2 IN " + cn + " FILTER u1._id == u2._id SORT u1.id RETURN { \"name\" : u1.name }", true);
assertEqual(expected, actual);
},
////////////////////////////////////////////////////////////////////////////////
/// @brief check a ref access with _id filter (reverted expression)
////////////////////////////////////////////////////////////////////////////////
testRefAccessId2 : function () {
var expected = [ { "name" : "John" }, { "name" : "Fred" }, { "name" : "Jacob" }, { "name" : "Ethan" }, { "name" : "Michael" }, { "name" : "Alexander" }, { "name" : "Daniel" }, { "name" : "Anthony" }, { "name" : "Jim"} , { "name" : "Diego" } ];
var actual = getQueryResults("FOR u1 IN " + cn + " FOR u2 IN " + cn + " FILTER u2._id == u1._id SORT u1.id RETURN { \"name\" : u1.name }", true);
assertEqual(expected, actual);
},
////////////////////////////////////////////////////////////////////////////////
/// @brief check a ref access with index
////////////////////////////////////////////////////////////////////////////////
testRefAccessIndex1 : function () {
users.ensureHashIndex("name");
var expected = [ { "name" : "John" }, { "name" : "Fred" }, { "name" : "Jacob" }, { "name" : "Ethan" }, { "name" : "Michael" }, { "name" : "Alexander" }, { "name" : "Daniel" }, { "name" : "Anthony" }, { "name" : "Jim"} , { "name" : "Diego" } ];
var actual = getQueryResults("FOR u1 IN " + cn + " FOR u2 IN " + cn + " FILTER u1.name == u2.name SORT u1.id RETURN { \"name\" : u1.name }", true);
assertEqual(expected, actual);
},
////////////////////////////////////////////////////////////////////////////////
/// @brief check a ref access with index (reverted expression)
////////////////////////////////////////////////////////////////////////////////
testRefAccessIndex2 : function () {
users.ensureHashIndex("name");
var expected = [ { "name" : "John" }, { "name" : "Fred" }, { "name" : "Jacob" }, { "name" : "Ethan" }, { "name" : "Michael" }, { "name" : "Alexander" }, { "name" : "Daniel" }, { "name" : "Anthony" }, { "name" : "Jim"} , { "name" : "Diego" } ];
var actual = getQueryResults("FOR u1 IN " + cn + " FOR u2 IN " + cn + " FILTER u2.name == u1.name SORT u1.id RETURN { \"name\" : u1.name }", true);
assertEqual(expected, actual);
},
////////////////////////////////////////////////////////////////////////////////
/// @brief check a ref access with index (multiple filters)
////////////////////////////////////////////////////////////////////////////////
testRefAccessIndex3 : function () {
users.ensureHashIndex("name");
var expected = [ { "name" : "John" }, { "name" : "Fred" }, { "name" : "Jacob" }, { "name" : "Ethan" }, { "name" : "Michael" }, { "name" : "Alexander" }, { "name" : "Daniel" }, { "name" : "Anthony" }, { "name" : "Jim"} , { "name" : "Diego" } ];
var actual = getQueryResults("FOR u1 IN " + cn + " FOR u2 IN " + cn + " FILTER u2.name == u1.name && u1.name == u2.name SORT u1.id RETURN { \"name\" : u1.name }", true);
assertEqual(expected, actual);
},
////////////////////////////////////////////////////////////////////////////////
/// @brief check a ref access with index (multiple filters)
////////////////////////////////////////////////////////////////////////////////
testRefAccessIndex4 : function () {
users.ensureHashIndex("name");
var expected = [ { "name" : "John" }, { "name" : "Fred" }, { "name" : "Jacob" }, { "name" : "Ethan" }, { "name" : "Michael" }, { "name" : "Alexander" }, { "name" : "Daniel" }, { "name" : "Anthony" }, { "name" : "Jim"} , { "name" : "Diego" } ];
var actual = getQueryResults("FOR u1 IN " + cn + " FOR u2 IN " + cn + " FILTER u2.name == u1.name && u1.name == u2.name && u2.name == u1.name && u1._id == u2._id SORT u1.id RETURN { \"name\" : u1.name }", true);
assertEqual(expected, actual);
},
}
}
////////////////////////////////////////////////////////////////////////////////
/// @brief executes the test suite
////////////////////////////////////////////////////////////////////////////////
jsunity.run(ahuacatlQueryOptimiserRefTestSuite);
return jsunity.done();
// Local Variables:
// mode: outline-minor
// outline-regexp: "^\\(/// @brief\\|/// @addtogroup\\|// --SECTION--\\|/// @page\\|/// @}\\)"
// End: