1
0
Fork 0
arangodb/tests/js/server/aql/aql-optimizer-collect-count.js

439 lines
18 KiB
JavaScript

/*jshint globalstrict:false, strict:false, maxlen: 500 */
/*global assertTrue, assertEqual, assertNotEqual, AQL_EXECUTE, AQL_EXPLAIN */
////////////////////////////////////////////////////////////////////////////////
/// @brief tests for COLLECT w/ COUNT
///
/// @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");
var internal = require("internal");
var errors = internal.errors;
var db = require("@arangodb").db;
var helper = require("@arangodb/aql-helper");
var assertQueryError = helper.assertQueryError;
const isCluster = require("@arangodb/cluster").isCluster();
////////////////////////////////////////////////////////////////////////////////
/// @brief test suite
////////////////////////////////////////////////////////////////////////////////
function optimizerCountTestSuite () {
var c;
return {
setUp : function () {
db._drop("UnitTestsCollection");
c = db._create("UnitTestsCollection", { numberOfShards: 4 });
for (var i = 0; i < 1000; ++i) {
c.save({ group: "test" + (i % 10), value: i });
}
},
tearDown : function () {
db._drop("UnitTestsCollection");
},
////////////////////////////////////////////////////////////////////////////////
/// @brief test no into, no count
////////////////////////////////////////////////////////////////////////////////
testInvalidSyntax : function () {
assertQueryError(errors.ERROR_QUERY_PARSE.code, "FOR i IN " + c.name() + " COLLECT RETURN 1");
assertQueryError(errors.ERROR_QUERY_PARSE.code, "FOR i IN " + c.name() + " COLLECT COUNT RETURN 1");
assertQueryError(errors.ERROR_QUERY_PARSE.code, "FOR i IN " + c.name() + " COLLECT WITH COUNT RETURN 1");
assertQueryError(errors.ERROR_QUERY_PARSE.code, "FOR i IN " + c.name() + " COLLECT WITH COUNT i RETURN 1");
assertQueryError(errors.ERROR_QUERY_PARSE.code, "FOR i IN " + c.name() + " COLLECT WITH COUNT INTO RETURN 1");
assertQueryError(errors.ERROR_QUERY_PARSE.code, "FOR i IN " + c.name() + " COLLECT WITH COUNT g RETURN 1");
assertQueryError(errors.ERROR_QUERY_PARSE.code, "FOR i IN " + c.name() + " COLLECT COUNT INTO g RETURN 1");
assertQueryError(errors.ERROR_QUERY_PARSE.code, "FOR i IN " + c.name() + " COLLECT g WITH COUNT RETURN 1");
assertQueryError(errors.ERROR_QUERY_PARSE.code, "FOR i IN " + c.name() + " COLLECT g WITH COUNT INTO RETURN 1");
assertQueryError(errors.ERROR_QUERY_PARSE.code, "FOR i IN " + c.name() + " COLLECT class = i.group WITH COUNT RETURN 1");
assertQueryError(errors.ERROR_QUERY_PARSE.code, "FOR i IN " + c.name() + " COLLECT class = i.group WITH COUNT i RETURN 1");
assertQueryError(errors.ERROR_QUERY_PARSE.code, "FOR i IN " + c.name() + " COLLECT class = i.group WITH COUNT i INTO group RETURN 1");
assertQueryError(errors.ERROR_QUERY_PARSE.code, "FOR i IN " + c.name() + " COLLECT class = i.group WITH COUNT COUNT INTO group RETURN 1");
assertQueryError(errors.ERROR_QUERY_PARSE.code, "FOR i IN " + c.name() + " COLLECT AGGREGATE doc = MIN(i.group) WITH COUNT INTO group RETURN 1");
assertQueryError(errors.ERROR_QUERY_PARSE.code, "FOR i IN " + c.name() + " COLLECT class = i.group AGGREGATE doc = MIN(i.group) WITH COUNT INTO group RETURN 1");
},
////////////////////////////////////////////////////////////////////////////////
/// @brief test count
////////////////////////////////////////////////////////////////////////////////
testCountTotalSimple : function () {
var query = "FOR i IN " + c.name() + " COLLECT WITH COUNT INTO count RETURN count";
var results = AQL_EXECUTE(query);
assertEqual(1, results.json.length);
assertEqual(1000, results.json[0]);
var plan = AQL_EXPLAIN(query).plan;
// must not have a SortNode
assertEqual(-1, plan.nodes.map(function(node) { return node.type; }).indexOf("SortNode"));
if (isCluster) {
assertNotEqual(-1, plan.rules.indexOf("collect-in-cluster"));
}
},
////////////////////////////////////////////////////////////////////////////////
/// @brief test count
////////////////////////////////////////////////////////////////////////////////
testCountTotalFiltered : function () {
var query = "FOR i IN " + c.name() + " FILTER i.group == 'test4' COLLECT WITH COUNT INTO count RETURN count";
var results = AQL_EXECUTE(query);
assertEqual(1, results.json.length);
assertEqual(100, results.json[0]);
var plan = AQL_EXPLAIN(query).plan;
// must not have a SortNode
assertEqual(-1, plan.nodes.map(function(node) { return node.type; }).indexOf("SortNode"));
if (isCluster) {
assertNotEqual(-1, plan.rules.indexOf("collect-in-cluster"));
}
},
////////////////////////////////////////////////////////////////////////////////
/// @brief test count
////////////////////////////////////////////////////////////////////////////////
testCountTotalFilteredMulti : function () {
var query = "FOR i IN " + c.name() + " FILTER i.group >= 'test2' && i.group <= 'test4' COLLECT WITH COUNT INTO count RETURN count";
var results = AQL_EXECUTE(query);
assertEqual(1, results.json.length);
assertEqual(300, results.json[0]);
var plan = AQL_EXPLAIN(query).plan;
// must not have a SortNode
assertEqual(-1, plan.nodes.map(function(node) { return node.type; }).indexOf("SortNode"));
if (isCluster) {
assertNotEqual(-1, plan.rules.indexOf("collect-in-cluster"));
}
},
////////////////////////////////////////////////////////////////////////////////
/// @brief test count
////////////////////////////////////////////////////////////////////////////////
testCountTotalFilteredEmpty : function () {
var query = "FOR i IN " + c.name() + " FILTER i.group >= 'test99' COLLECT WITH COUNT INTO count RETURN count";
var results = AQL_EXECUTE(query);
assertEqual(1, results.json.length);
assertEqual(0, results.json[0]);
var plan = AQL_EXPLAIN(query).plan;
// must not have a SortNode
assertEqual(-1, plan.nodes.map(function(node) { return node.type; }).indexOf("SortNode"));
if (isCluster) {
assertNotEqual(-1, plan.rules.indexOf("collect-in-cluster"));
}
},
////////////////////////////////////////////////////////////////////////////////
/// @brief test count
////////////////////////////////////////////////////////////////////////////////
testCountTotalFilteredBig : function () {
var i;
for (i = 0; i < 10000; ++i) {
c.save({ age: 10 + (i % 80), type: 1 });
}
for (i = 0; i < 10000; ++i) {
c.save({ age: 10 + (i % 80), type: 2 });
}
var query = "FOR i IN " + c.name() + " FILTER i.age >= 20 && i.age < 50 && i.type == 1 COLLECT WITH COUNT INTO count RETURN count";
var results = AQL_EXECUTE(query);
assertEqual(1, results.json.length);
assertEqual(125 * 30, results.json[0]);
var plan = AQL_EXPLAIN(query).plan;
// must not have a SortNode
assertEqual(-1, plan.nodes.map(function(node) { return node.type; }).indexOf("SortNode"));
if (isCluster) {
assertNotEqual(-1, plan.rules.indexOf("collect-in-cluster"));
}
},
////////////////////////////////////////////////////////////////////////////////
/// @brief test count
////////////////////////////////////////////////////////////////////////////////
testCountTotal : function () {
var query = "FOR j IN " + c.name() + " COLLECT WITH COUNT INTO count RETURN count";
var results = AQL_EXECUTE(query);
assertEqual(1, results.json.length);
assertEqual(1000, results.json[0]);
var plan = AQL_EXPLAIN(query).plan;
// must not have a SortNode
assertEqual(-1, plan.nodes.map(function(node) { return node.type; }).indexOf("SortNode"));
if (isCluster) {
assertNotEqual(-1, plan.rules.indexOf("collect-in-cluster"));
}
},
////////////////////////////////////////////////////////////////////////////////
/// @brief test count
////////////////////////////////////////////////////////////////////////////////
testCountTotalNested : function () {
var query = "FOR i IN 1..2 FOR j IN " + c.name() + " COLLECT WITH COUNT INTO count RETURN count";
var results = AQL_EXECUTE(query, null, { optimizer: { rules: ["-interchange-adjacent-enumerations"] } });
assertEqual(1, results.json.length);
assertEqual(2000, results.json[0]);
var plan = AQL_EXPLAIN(query).plan;
// must not have a SortNode
assertEqual(-1, plan.nodes.map(function(node) { return node.type; }).indexOf("SortNode"));
if (isCluster) {
assertNotEqual(-1, plan.rules.indexOf("collect-in-cluster"));
}
},
////////////////////////////////////////////////////////////////////////////////
/// @brief test count
////////////////////////////////////////////////////////////////////////////////
testCountTotalNested2 : function () {
var query = "FOR j IN " + c.name() + " FOR i IN 1..2 COLLECT WITH COUNT INTO count RETURN count";
var results = AQL_EXECUTE(query, null, { optimizer: { rules: ["-interchange-adjacent-enumerations"] } });
assertEqual(1, results.json.length);
assertEqual(2000, results.json[0]);
var plan = AQL_EXPLAIN(query).plan;
// must not have a SortNode
assertEqual(-1, plan.nodes.map(function(node) { return node.type; }).indexOf("SortNode"));
if (isCluster) {
assertNotEqual(-1, plan.rules.indexOf("collect-in-cluster"));
}
},
////////////////////////////////////////////////////////////////////////////////
/// @brief test count
////////////////////////////////////////////////////////////////////////////////
testCountSimple : function () {
var query = "FOR i IN " + c.name() + " COLLECT class = i.group WITH COUNT INTO count RETURN [ class, count ]";
var results = AQL_EXECUTE(query);
assertEqual(10, results.json.length);
for (var i = 0; i < results.json.length; ++i) {
var group = results.json[i];
assertTrue(Array.isArray(group));
assertEqual("test" + i, group[0]);
assertEqual(100, group[1]);
}
var plan = AQL_EXPLAIN(query).plan;
// must have a SortNode
assertNotEqual(-1, plan.nodes.map(function(node) { return node.type; }).indexOf("SortNode"));
if (isCluster) {
assertNotEqual(-1, plan.rules.indexOf("collect-in-cluster"));
}
},
////////////////////////////////////////////////////////////////////////////////
/// @brief test count
////////////////////////////////////////////////////////////////////////////////
testCountFiltered : function () {
var query = "FOR i IN " + c.name() + " FILTER i.group >= 'test1' && i.group <= 'test4' COLLECT class = i.group WITH COUNT INTO count RETURN [ class, count ]";
var results = AQL_EXECUTE(query);
assertEqual(4, results.json.length);
for (var i = 0; i < results.json.length; ++i) {
var group = results.json[i];
assertTrue(Array.isArray(group));
assertEqual("test" + (i + 1), group[0]);
assertEqual(100, group[1]);
}
var plan = AQL_EXPLAIN(query).plan;
// must have a SortNode
assertNotEqual(-1, plan.nodes.map(function(node) { return node.type; }).indexOf("SortNode"));
if (isCluster) {
assertNotEqual(-1, plan.rules.indexOf("collect-in-cluster"));
}
},
////////////////////////////////////////////////////////////////////////////////
/// @brief test count
////////////////////////////////////////////////////////////////////////////////
testCountFilteredEmpty : function () {
var query = "FOR i IN " + c.name() + " FILTER i.group >= 'test99' COLLECT class = i.group WITH COUNT INTO count RETURN [ class, count ]";
var results = AQL_EXECUTE(query);
assertEqual(0, results.json.length);
var plan = AQL_EXPLAIN(query).plan;
// must have a SortNode
assertNotEqual(-1, plan.nodes.map(function(node) { return node.type; }).indexOf("SortNode"));
if (isCluster) {
assertNotEqual(-1, plan.rules.indexOf("collect-in-cluster"));
}
},
////////////////////////////////////////////////////////////////////////////////
/// @brief test count
////////////////////////////////////////////////////////////////////////////////
testCountFilteredBig : function () {
var i;
for (i = 0; i < 10000; ++i) {
c.save({ age: 10 + (i % 80), type: 1 });
}
for (i = 0; i < 10000; ++i) {
c.save({ age: 10 + (i % 80), type: 2 });
}
var query = "FOR i IN " + c.name() + " FILTER i.age >= 20 && i.age < 50 && i.type == 1 COLLECT age = i.age WITH COUNT INTO count RETURN [ age, count ]";
var results = AQL_EXECUTE(query);
assertEqual(30, results.json.length);
for (i = 0; i < results.json.length; ++i) {
var group = results.json[i];
assertTrue(Array.isArray(group));
assertEqual(20 + i, group[0]);
assertEqual(125, group[1]);
}
var plan = AQL_EXPLAIN(query).plan;
// must have a SortNode
assertNotEqual(-1, plan.nodes.map(function(node) { return node.type; }).indexOf("SortNode"));
if (isCluster) {
assertNotEqual(-1, plan.rules.indexOf("collect-in-cluster"));
}
},
////////////////////////////////////////////////////////////////////////////////
/// @brief test count
////////////////////////////////////////////////////////////////////////////////
testCountNested : function () {
var query = "FOR i IN 1..2 FOR j IN " + c.name() + " COLLECT class1 = i, class2 = j.group WITH COUNT INTO count RETURN [ class1, class2, count ]";
var results = AQL_EXECUTE(query), x = 0;
assertEqual(20, results.json.length);
for (var i = 1; i <= 2; ++i) {
for (var j = 0; j < 10; ++j) {
var group = results.json[x++];
assertTrue(Array.isArray(group));
assertEqual(i, group[0]);
assertEqual("test" + j, group[1]);
assertEqual(100, group[2]);
}
}
var plan = AQL_EXPLAIN(query).plan;
// must have a SortNode
assertNotEqual(-1, plan.nodes.map(function(node) { return node.type; }).indexOf("SortNode"));
if (isCluster) {
assertNotEqual(-1, plan.rules.indexOf("collect-in-cluster"));
}
},
////////////////////////////////////////////////////////////////////////////////
/// @brief test count shaped
////////////////////////////////////////////////////////////////////////////////
testCountShaped : function () {
var query = "FOR j IN " + c.name() + " COLLECT doc = j WITH COUNT INTO count RETURN doc";
var results = AQL_EXECUTE(query);
// expectation is that we get 1000 different docs and do not crash (issue #1265)
assertEqual(1000, results.json.length);
if (isCluster) {
var plan = AQL_EXPLAIN(query).plan;
assertNotEqual(-1, plan.rules.indexOf("collect-in-cluster"));
}
},
// This test is not necessary for hashed collect, as hashed collect will not
// be used without group variables.
testCollectSortedUndefined: function () {
const randomDocumentID = db["UnitTestsCollection"].any()._id;
const query = 'LET start = DOCUMENT("' + randomDocumentID + '")._key FOR i IN [] COLLECT AGGREGATE count = count(i) RETURN {count, start}';
const bindParams = {};
const options = {optimizer: {rules: ['-remove-unnecessary-calculations','-remove-unnecessary-calculations-2']}};
const planNodes = AQL_EXPLAIN(query, {}, options).plan.nodes;
assertEqual(
[ "SingletonNode", "CalculationNode", "CalculationNode",
"EnumerateListNode", "CollectNode", "CalculationNode", "ReturnNode" ],
planNodes.map(n => n.type));
assertEqual("sorted", planNodes[4].collectOptions.method);
const results = db._query(query, bindParams, options).toArray();
// expectation is that we exactly get one result
// count will be 0, start will be undefined
assertEqual(1, results.length);
assertEqual(0, results[0].count);
assertEqual(undefined, results[0].start);
},
testCollectCountUndefined: function () {
const randomDocumentID = db["UnitTestsCollection"].any()._id;
const query = 'LET start = DOCUMENT("' + randomDocumentID + '")._key FOR i IN [] COLLECT WITH COUNT INTO count RETURN {count, start}';
const bindParams = {};
const options = {optimizer: {rules: ['-remove-unnecessary-calculations','-remove-unnecessary-calculations-2']}};
const planNodes = AQL_EXPLAIN(query, {}, options).plan.nodes;
assertEqual(
[ "SingletonNode", "CalculationNode", "CalculationNode",
"EnumerateListNode", "CollectNode", "CalculationNode", "ReturnNode" ],
planNodes.map(n => n.type));
assertEqual("count", planNodes[4].collectOptions.method);
const results = db._query(query, bindParams, options).toArray();
// expectation is that we exactly get one result
// count will be 0, start will be undefined
assertEqual(1, results.length);
assertEqual(0, results[0].count);
assertEqual(undefined, results[0].start);
}
};
}
////////////////////////////////////////////////////////////////////////////////
/// @brief executes the test suite
////////////////////////////////////////////////////////////////////////////////
jsunity.run(optimizerCountTestSuite);
return jsunity.done();