1
0
Fork 0
arangodb/tests/js/server/aql/aql-optimizer-rule-late-doc...

372 lines
16 KiB
JavaScript

/*jshint globalstrict:false, strict:false, maxlen: 500 */
/*global assertTrue, assertFalse, assertEqual, assertNotEqual, AQL_EXECUTE, AQL_EXPLAIN */
////////////////////////////////////////////////////////////////////////////////
/// @brief tests for late document materialization arangosearch rule
///
/// @file
///
/// DISCLAIMER
///
/// Copyright 2019 ArangoDB 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 ArangoDB GmbH, Cologne, Germany
///
/// @author Yuriy Popov
/// @author Copyright 2019, ArangoDB GmbH, Cologne, Germany
////////////////////////////////////////////////////////////////////////////////
let jsunity = require("jsunity");
let db = require("@arangodb").db;
let isCluster = require("internal").isCluster();
function lateDocumentMaterializationArangoSearch2RuleTestSuite () {
const ruleName = "late-document-materialization-arangosearch";
const cn = "UnitTestsCollection";
const cn1 = "UnitTestsCollection1";
const vn = "UnitTestsView";
const svn = "SortedTestsView";
return {
setUpAll : function () {
db._dropView(vn);
db._dropView(svn);
db._drop(cn);
db._drop(cn1);
let c = db._create(cn, { numberOfShards: 3 });
let c2 = db._create(cn1, { numberOfShards: 3 });
db._createView(vn, "arangosearch", {
links: {
[cn] : { includeAllFields: true, analyzers : [ "identity" ],
fields : { str : { "analyzers" : [ "text_en" ] }}, },
[cn1] : { includeAllFields: true, analyzers : [ "identity" ],
fields : { str : { "analyzers" : [ "text_en" ] }}}
}});
db._createView(svn, "arangosearch", {
consolidationIntervalMsec: 5000,
primarySort: [{"field": "value", "direction": "asc"}, {"field": "foo", "direction": "desc"}],
links: {
[cn] : { includeAllFields: true },
[cn1] : { includeAllFields: true }
}});
c.save({ _key: 'c0', str: 'cat', foo: 'foo0', value: 0 });
c2.save({_key: 'c_0', str: 'cat', foo: 'foo1', value: 10 });
c.save({ _key: 'c1', str: 'cat', foo: 'foo2', value: 1 });
c2.save({_key: 'c_1', str: 'cat', foo: 'foo3', value: 11 });
c.save({ _key: 'c2', str: 'cat', foo: 'foo4', value: 2 });
c2.save({_key: 'c_2', str: 'cat', foo: 'foo5', value: 12 });
c.save({ _key: 'c3', str: 'cat', foo: 'foo6', value: 3 });
c2.save({_key: 'c_3', str: 'cat', foo: 'foo7', value: 13 });
// trigger view sync
db._query("FOR d IN " + vn + " OPTIONS { waitForSync: true } RETURN d");
db._query("FOR d IN " + svn + " OPTIONS { waitForSync: true } RETURN d");
},
tearDownAll : function () {
try { db._dropView(vn); } catch(e) {}
try { db._dropView(svn); } catch(e) {}
try { db._drop(cn); } catch(e) {}
try { db._drop(cn1); } catch(e) {}
},
testNotAppliedDueToNoPrimarySort() {
let query = "FOR d IN " + vn + " SEARCH d.value IN [1, 2] SORT d.foo DESC LIMIT 10 RETURN d";
let plan = AQL_EXPLAIN(query).plan;
assertEqual(-1, plan.rules.indexOf(ruleName));
},
testNotAppliedDueToSortElimination() {
let query = "FOR d IN " + svn + " LET c = d.value + RAND() SORT d.value LIMIT 10 RETURN d";
let plan = AQL_EXPLAIN(query).plan;
assertEqual(-1, plan.rules.indexOf(ruleName));
},
testNotAppliedDueToNoSort() {
let query = "FOR d IN " + svn + " LIMIT 10 RETURN d";
let plan = AQL_EXPLAIN(query).plan;
assertEqual(-1, plan.rules.indexOf(ruleName));
},
testNotAppliedDueToUsedInInnerSort() {
let query = "FOR d IN " + svn + " SORT NOOPT(d.str) SORT d.value DESC LIMIT 10 RETURN d";
let plan = AQL_EXPLAIN(query).plan;
assertEqual(-1, plan.rules.indexOf(ruleName));
},
testNotAppliedDueToNoLimit() {
let query = "FOR d IN " + svn + " SORT d.value DESC RETURN d";
let plan = AQL_EXPLAIN(query).plan;
assertEqual(-1, plan.rules.indexOf(ruleName));
},
testNotAppliedDueToSubqueryWithDocumentAccess() {
let query = "FOR d IN " + svn + " SEARCH d.value IN [1, 2, 11, 12] " +
"LET a = NOOPT(d.foo) " +
"LET e = SUM(FOR c IN " + vn + " LET p = CONCAT(d, c.foo) RETURN p) " +
"SORT CONCAT(a, e) LIMIT 10 RETURN d";
let plan = AQL_EXPLAIN(query).plan;
assertEqual(-1, plan.rules.indexOf(ruleName));
},
testNotAppliedDueToSubqueryWithDocumentAccessByAttribute() { // should be supported later
let query = "FOR d IN " + svn + " SEARCH d.value IN [1, 2, 11, 12] " +
"LET a = NOOPT(d.foo) " +
"LET e = SUM(FOR c IN " + vn + " LET p = CONCAT(d.foo, c.foo) RETURN p) " +
"SORT CONCAT(a, e) LIMIT 10 RETURN d";
let plan = AQL_EXPLAIN(query).plan;
assertEqual(-1, plan.rules.indexOf(ruleName));
},
testQueryResultsWithSubqueryWithoutDocumentAccess() {
let query = "FOR d IN " + svn + " SEARCH d.value IN [1, 2, 11, 12] " +
"LET a = NOOPT(d.foo) " +
"LET e = SUM(FOR c IN " + vn + " LET p = CONCAT(c.foo, c.foo) RETURN p) " +
"SORT CONCAT(a, e) LIMIT 10 RETURN d";
let plan = AQL_EXPLAIN(query).plan;
if (!isCluster) {
assertNotEqual(-1, plan.rules.indexOf(ruleName));
let result = AQL_EXECUTE(query);
assertEqual(4, result.json.length);
let expectedKeys = new Set(['c1', 'c2', 'c_1', 'c_2']);
result.json.forEach(function(doc) {
assertTrue(expectedKeys.has(doc._key));
expectedKeys.delete(doc._key);
});
assertEqual(0, expectedKeys.size);
} else {
// on cluster this will not be applied as remote node placed before sort node
assertEqual(-1, plan.rules.indexOf(ruleName));
}
},
testQueryResultsWithFilter() {
let query = "FOR d IN " + svn + " FILTER d.value IN [1, 2] SORT d.foo DESC LIMIT 10 RETURN d";
let plan = AQL_EXPLAIN(query).plan;
assertNotEqual(-1, plan.rules.indexOf(ruleName));
let result = AQL_EXECUTE(query);
assertEqual(2, result.json.length);
let expectedKeys = new Set(['c1', 'c2']);
result.json.forEach(function(doc) {
assertTrue(expectedKeys.has(doc._key));
expectedKeys.delete(doc._key);
});
assertEqual(0, expectedKeys.size);
},
testQueryResultsWithTwoSorts() {
let query = "FOR d IN " + svn + " SEARCH d.value IN [1, 2, 11, 12] " +
"SORT d.value DESC LET c = BM25(d) * 2 SORT CONCAT(BM25(d), c) LIMIT 10 RETURN d";
let plan = AQL_EXPLAIN(query).plan;
assertNotEqual(-1, plan.rules.indexOf(ruleName));
let result = AQL_EXECUTE(query);
assertEqual(4, result.json.length);
let expectedKeys = new Set(['c1', 'c2', 'c_1', 'c_2']);
result.json.forEach(function(doc) {
assertTrue(expectedKeys.has(doc._key));
expectedKeys.delete(doc._key);
});
assertEqual(0, expectedKeys.size);
},
testQueryResultsWithRandomSort() {
let query = "FOR d IN " + svn + " SEARCH d.value IN [1, 2, 11, 12] SORT RAND(), d.value DESC LIMIT 10 RETURN d";
let plan = AQL_EXPLAIN(query).plan;
assertNotEqual(-1, plan.rules.indexOf(ruleName));
let result = AQL_EXECUTE(query);
assertEqual(4, result.json.length);
let expectedKeys = new Set(['c1', 'c2', 'c_1', 'c_2']);
result.json.forEach(function(doc) {
assertTrue(expectedKeys.has(doc._key));
expectedKeys.delete(doc._key);
});
assertEqual(0, expectedKeys.size);
},
testQueryResultsWithTwoSortFields() {
let query = "FOR d IN " + svn + " SEARCH d.value IN [1, 2, 11, 12] SORT d.value DESC, d.foo LIMIT 10 RETURN d";
let plan = AQL_EXPLAIN(query).plan;
assertNotEqual(-1, plan.rules.indexOf(ruleName));
let result = AQL_EXECUTE(query);
assertEqual(4, result.json.length);
let expectedKeys = new Set(['c1', 'c2', 'c_1', 'c_2']);
result.json.forEach(function(doc) {
assertTrue(expectedKeys.has(doc._key));
expectedKeys.delete(doc._key);
});
assertEqual(0, expectedKeys.size);
},
testQueryResultsWithMultipleCollections() {
let query = "FOR d IN " + svn + " SEARCH d.value IN [1, 2, 11, 12] SORT d.value DESC LIMIT 10 RETURN d";
let plan = AQL_EXPLAIN(query).plan;
assertNotEqual(-1, plan.rules.indexOf(ruleName));
let result = AQL_EXECUTE(query);
assertEqual(4, result.json.length);
let expectedKeys = new Set(['c1', 'c2', 'c_1', 'c_2']);
result.json.forEach(function(doc) {
assertTrue(expectedKeys.has(doc._key));
expectedKeys.delete(doc._key);
});
assertEqual(0, expectedKeys.size);
},
testQueryResultsWithMultipleCollectionsWithAfterSort() {
let query = "FOR d IN " + svn + " SEARCH d.value IN [1, 2, 11, 12] SORT d.value DESC LIMIT 10 SORT NOOPT(d.value) ASC RETURN d";
let plan = AQL_EXPLAIN(query).plan;
assertNotEqual(-1, plan.rules.indexOf(ruleName));
let result = AQL_EXECUTE(query);
assertEqual(4, result.json.length);
let expectedKeys = new Set(['c1', 'c2', 'c_1', 'c_2']);
let currentValue = 0;
result.json.forEach(function(doc) {
assertTrue(expectedKeys.has(doc._key));
expectedKeys.delete(doc._key);
// check after sort asc order
assertTrue(currentValue < doc.value);
currentValue = doc.value;
});
assertEqual(0, expectedKeys.size);
},
testQueryResultsWithMultipleCollectionsWithMultiSort() {
let query = "FOR d IN " + svn + " SEARCH d.value IN [1, 2, 11, 12] " +
"SORT d.value DESC LIMIT 10 SORT TFIDF(d) DESC LIMIT 4 RETURN d";
let plan = AQL_EXPLAIN(query).plan;
assertNotEqual(-1, plan.rules.indexOf(ruleName));
let materializeNodeFound = false;
let nodeDependency = null;
plan.nodes.forEach(function(node) {
if (node.type === "MaterializeNode") {
// there should be no materializer before (e.g. double materialization)
assertFalse(materializeNodeFound);
materializeNodeFound = true;
// the other sort node should be limited but not have a materializer
// d.value node on single and TFIDF on cluster as for cluster
// only first sort will be on DBServers
assertEqual(nodeDependency.limit, isCluster ? 10 : 4);
}
nodeDependency = node; // as we walk the plan this will be next node dependency
});
// materilizer should be there
assertTrue(materializeNodeFound);
let result = AQL_EXECUTE(query);
assertEqual(4, result.json.length);
// should be sorted by increasing cat frequency
let expectedKeys = ['c_2', 'c_1', 'c2', 'c1'];
result.json.forEach(function(doc) {
assertEqual(expectedKeys[0], doc._key);
expectedKeys.shift();
});
assertEqual(0, expectedKeys.length);
},
testQueryResultsWithMultipleCollectionsAfterCalc() {
let query = "FOR d IN " + svn + " SEARCH d.value IN [1, 2, 11, 12] SORT d.value DESC LIMIT 10 LET c = CONCAT(NOOPT(d._key), '-C') RETURN c";
let plan = AQL_EXPLAIN(query).plan;
assertNotEqual(-1, plan.rules.indexOf(ruleName));
let result = AQL_EXECUTE(query);
assertEqual(4, result.json.length);
let expected = new Set(['c1-C', 'c2-C', 'c_1-C', 'c_2-C']);
result.json.forEach(function(doc) {
assertTrue(expected.has(doc));
expected.delete(doc);
});
assertEqual(0, expected.size);
},
testQueryResultsSkipSome() {
let query = "FOR d IN " + svn + " SEARCH d.value IN [1, 2, 11, 12] SORT d.value DESC LIMIT 3, 1 RETURN d";
let plan = AQL_EXPLAIN(query).plan;
assertNotEqual(-1, plan.rules.indexOf(ruleName));
let result = AQL_EXECUTE(query);
assertEqual(1, result.json.length);
assertEqual(result.json[0]._key, 'c1');
},
testQueryResultsSkipAll() {
let query = "FOR d IN " + svn + " SEARCH d.value IN [1, 2, 11, 12] SORT d.value DESC LIMIT 5, 10 RETURN d";
let plan = AQL_EXPLAIN(query).plan;
assertNotEqual(-1, plan.rules.indexOf(ruleName));
let result = AQL_EXECUTE(query);
assertEqual(0, result.json.length);
},
testQueryResultsInSubquery() {
let query = "FOR c IN " + vn + " SEARCH c.value == 1 " +
"FOR d IN " + svn + " SEARCH d.value IN [c.value, c.value + 1] SORT d.value DESC LIMIT 10 RETURN d";
let plan = AQL_EXPLAIN(query).plan;
assertNotEqual(-1, plan.rules.indexOf(ruleName));
let result = AQL_EXECUTE(query);
assertEqual(2, result.json.length);
let expected = new Set(['c1', 'c2']);
result.json.forEach(function(doc) {
assertTrue(expected.has(doc._key));
expected.delete(doc._key);
});
assertEqual(0, expected.size);
},
testQueryResultsInOuterSubquery() {
let query = "FOR c IN " + svn + " SEARCH c.value == 1 SORT c.value DESC LIMIT 10 " +
"FOR d IN " + vn + " SEARCH d.value IN [c.value, c.value + 1] RETURN d";
let plan = AQL_EXPLAIN(query).plan;
assertNotEqual(-1, plan.rules.indexOf(ruleName));
let result = AQL_EXECUTE(query);
assertEqual(2, result.json.length);
let expected = new Set(['c1', 'c2']);
result.json.forEach(function(doc) {
assertTrue(expected.has(doc._key));
expected.delete(doc._key);
});
assertEqual(0, expected.size);
},
testQueryResultsMultipleLimits() {
let query = "FOR d IN " + svn + " SEARCH d.value > 5 SORT d.value DESC " +
"LIMIT 1, 5 SORT d.foo LIMIT 1, 3 SORT NOOPT(d.str) DESC " +
"LIMIT 1, 1 RETURN d";
let plan = AQL_EXPLAIN(query).plan;
assertNotEqual(-1, plan.rules.indexOf(ruleName));
let materializeNodeFound = false;
let nodeDependency = null;
// sort by d.value node`s limit must be appended with materializer (identified by limit value = 1)
// as last SORT needs materialized document
// and SORT by d.foo is not lowest possible variant
// However in cluster only first sort suitable, as later sorts depend
// on all db servers results and performed on coordinator
plan.nodes.forEach(function(node) {
if (node.type === "MaterializeNode") {
assertFalse(materializeNodeFound); // no double materialization
assertEqual(nodeDependency.limit, isCluster ? 6 : 3);
materializeNodeFound = true;
}
nodeDependency = node;
});
assertTrue(materializeNodeFound);
},
testQueryResultsMultipleLimits2() {
// almost the same as testQueryResultsMultipleLimits but without last sort - this
// will not create addition variable for sort
// value but it should not affect results especially on cluster!
let query = " FOR d IN " + svn + " SEARCH d.value > 5 SORT d.value DESC " +
" LIMIT 1, 5 SORT d.foo LIMIT 1, 3 " +
" RETURN d ";
let plan = AQL_EXPLAIN(query).plan;
assertNotEqual(-1, plan.rules.indexOf(ruleName));
let materializeNodeFound = false;
// sort by d.foo node`s limit must be appended with materializer (identified by limit value = 3)
// as SORT by d.value is not lowest possible variant
// However in cluster only first sort suitable, as later sorts depend
// on all db servers results and performed on coordinator
let nodeDependency = null;
plan.nodes.forEach(function(node) {
if (node.type === "MaterializeNode") {
assertFalse(materializeNodeFound);
assertEqual(nodeDependency.limit, isCluster ? 6 : 3);
materializeNodeFound = true;
}
nodeDependency = node;
});
assertTrue(materializeNodeFound);
},
};
}
jsunity.run(lateDocumentMaterializationArangoSearch2RuleTestSuite);
return jsunity.done();