From 5da08eb283d24053374e4f1204ed60dd427d487b Mon Sep 17 00:00:00 2001 From: Jan Steemann Date: Mon, 8 Oct 2012 12:51:27 +0200 Subject: [PATCH] allow AQL to use indexes in additional cases --- Makefile.in | 1 + UnitTests/Makefile.files | 1 + arangod/Ahuacatl/ahuacatl-access-optimiser.c | 40 ++- arangod/Ahuacatl/ahuacatl-index.c | 4 +- .../tests/ahuacatl-queries-optimiser-ref.js | 229 ++++++++++++++++++ 5 files changed, 271 insertions(+), 4 deletions(-) create mode 100644 js/server/tests/ahuacatl-queries-optimiser-ref.js diff --git a/Makefile.in b/Makefile.in index 1500c12734..a6b1b33aa1 100644 --- a/Makefile.in +++ b/Makefile.in @@ -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 \ diff --git a/UnitTests/Makefile.files b/UnitTests/Makefile.files index aeb92c6e2b..2e15176f1b 100755 --- a/UnitTests/Makefile.files +++ b/UnitTests/Makefile.files @@ -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 \ diff --git a/arangod/Ahuacatl/ahuacatl-access-optimiser.c b/arangod/Ahuacatl/ahuacatl-access-optimiser.c index c052836b14..78aadf189e 100644 --- a/arangod/Ahuacatl/ahuacatl-access-optimiser.c +++ b/arangod/Ahuacatl/ahuacatl-access-optimiser.c @@ -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; } } diff --git a/arangod/Ahuacatl/ahuacatl-index.c b/arangod/Ahuacatl/ahuacatl-index.c index f6a9da01fa..dda7238fa9 100644 --- a/arangod/Ahuacatl/ahuacatl-index.c +++ b/arangod/Ahuacatl/ahuacatl-index.c @@ -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); diff --git a/js/server/tests/ahuacatl-queries-optimiser-ref.js b/js/server/tests/ahuacatl-queries-optimiser-ref.js new file mode 100644 index 0000000000..05d9cac33a --- /dev/null +++ b/js/server/tests/ahuacatl-queries-optimiser-ref.js @@ -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: