// SERVER-14580: Test $natural // Iterate cursor and check that field is increasing or decreasing // name - Test name // cur - cursor // field - field name to check // dir - direction for test (-1 for decreasing or 1 for increasing) // isMongos - true/false // errorExpected - expected error true/false function checkQuery(name, cur, field, dir, isMongos, errorExpected) { var minVal = -1; var maxVal = Number.MAX_VALUE; var lastVal = minVal; var lastDiskLoc = {file: minVal, offset: minVal, initOffset: minVal}; if (dir === -1) { lastVal = maxVal; lastDiskLoc = {file: maxVal, offset: maxVal, initOffset: maxVal}; } try { while(cur.hasNext()) { // Field is an increasing or decreasing value var val = cur.next(); delete(val.pad); // Only test diskLoc if specified from projection in query var testdl = false; if ("diskLoc" in val) { testdl = true; // Reset last offset if file changes if (val.diskLoc.file != lastDiskLoc.file) { lastDiskLoc.offset = lastDiskLoc.initOffset; } } if (dir === 1) { assert.gte(val[field], lastVal, name+" "+field); // diskLoc tests are informational, not assertable if (testdl) { if (val.diskLoc.file < lastDiskLoc.file) { print("Warning: "+name+" diskLoc file: "+ val.diskLoc.file+" < "+lastDiskLoc.file); printjson(val); } if (val.diskLoc.offset <= lastDiskLoc.offset) { print("Warning: "+name+" diskLoc file: "+ val.diskLoc.offset+" <= "+lastDiskLoc.offset); printjson(val); } } } else { assert.lte(val[field], lastVal, name+" "+field); // diskLoc tests are informational, not assertable if (testdl) { if (val.diskLoc.file > lastDiskLoc.file) { print("Warning: "+name+" diskLoc file: "+ val.diskLoc.file+" > "+lastDiskLoc.file); printjson(val); } if (val.diskLoc.offset >= lastDiskLoc.offset) { print("Warning: "+name+" diskLoc file: "+ val.diskLoc.offset+" >= "+lastDiskLoc.offset); printjson(val); } } } lastVal = val[field]; lastDiskLoc = val.diskLoc; } } // Check if error is expected catch(err) { // Rethrow if no error is expected if (!errorExpected) { // isMongos should swallow assert, as docs do not have a preserved order if (!isMongos) { throw(err); } } } } // Run test cases // test - test object // db - connection to DB // isMongo - connected to mongos, true/false var runTests = function(test, db, isMongos) { var name = test.name; if (isMongos) { name += " sharding"; // Not a shard test if no shardKey specified if (!("shardKey" in test)) { return; } } // print test name and test info jsTest.log(name); printjson(test); // Set collection name, with prefix var collName = "Natural "+name; var collOptions = {}; if ("collOptions" in test) { collOptions = test.collOptions; } // Optional test attributes // Test throws error var err = false; if ("errorExpected" in test) { err = test.errorExpected; } db.createCollection(collName, collOptions); var coll = db[collName]; coll.remove({}); // Set indexes on collection test.indexes.forEach(function(idx) { // Test has second arg to ensureIndex var idxOpt = null; if ("indexOpt" in idx) { idxOpt = idx.indexOpt; } coll.ensureIndex(idx.index, idxOpt); }); // shard collection if connected to mongos if (isMongo) { db.getMongo().getDB("admin").runCommand({shardCollection : coll+"", key: test.shardKey}); // pre-split collection on shardKey var splitCmd = {split: coll+"", middle: {}}; for (var k in test.shardKey) { splitCmd.middle[k] = docNum/2; } db.getMongo().getDB("admin").runCommand(splitCmd); } // Insert docs test.inserts(coll); printjson(coll.getDB().stats()); // Note: $meta not supported before 2.6 var dl = {diskLoc: {$meta: "diskloc"}}; var projs = []; if ("projections" in test) { projs = test.projections; } var order = test.orderField; test.queries.forEach(function(q) { // Tests with hint should override sort $natural if ("hints" in test) { test.hints.forEach(function(hint) { checkQuery(name+" hint override", coll.find(q).sort({$natural: -1}).hint(hint), order, 1, isMongos, err); }); } // Check all queries with sort & hint $natural in both directions: -1 & 1 // Project the diskLoc else { [-1, 1].forEach(function(dir) { checkQuery(name+" sort "+dir, coll.find(q, dl).sort({$natural: dir}), order, dir, isMongos, err); checkQuery(name+" hint "+dir, coll.find(q, dl).hint({$natural: dir}), order, dir, isMongos, err); checkQuery(name+" sort/hint "+dir, coll.find(q, dl).sort({$natural: dir}).hint({$natural: dir}), order, dir, isMongos, err); checkQuery(name+" sort/hint opposite", coll.find(q, dl).sort({$natural: dir}).hint({$natural: dir*-1}), order, dir, isMongos, err); // Test various projections projs.forEach(function(proj) { checkQuery(name+" proj sort "+dir, coll.find(q, proj).sort({$natural: dir}), order, dir, isMongos, err); checkQuery(name+" proj hint "+dir, coll.find(q, proj).hint({$natural: dir}), order, dir, isMongos, err); checkQuery(name+" proj sort/hint "+dir, coll.find(q, proj).sort({$natural: dir}).hint({$natural: dir}), order, dir, isMongos, err); checkQuery(name+" proj sort/hint opposite", coll.find(q, proj).sort({$natural: dir}).hint({$natural: dir*-1}), order, dir, isMongos, err); }); }); } }); // Drop collection assert.doesNotThrow(function() {return coll.drop();}, [], "Coll drop"+coll); }; // Test cases, each test is a doc // name - Test name // indexes - array of index docs // shardKey - shard key to use for sharding test // inserts - function to insert docs under test into collection // queries - array of query docs to execute on collection // projections - array of projections to apply to query // orderField - field name which is inserted in order // hints - array of hint docs // errorExpected - if test is expected to fail (default false is unspecified) var docNum = 1000; var padSize = Math.pow(2,16); var padding = ""; for (var i=0; i< padSize; i++) { padding += "a"; } tests = [ {name: "Regular index", indexes: [{index: {a: 1}}], shardKey: {a: 1}, inserts: function(c) {for (var i=0; i< docNum; i++) {c.insert({a: Math.floor(Math.random()*docNum), b: i%7, c: i, pad: padding});}}, queries: [{}, {a: {$gt: 80}}, {a: {$lt: 30}}], orderField: "c" }, {name: "Regular index - covered query", indexes: [{index: {a: 1}}], shardKey: {a: 1}, inserts: function(c) {for (var i=0; i< docNum; i++) { c.insert({a: Math.floor(Math.random()*docNum), b: i%7, c: i, pad: padding});}}, queries: [{}, {a: {$gt: 80}}, {a: {$lt: 30}}], projections: [{a: 1, c: 1, _id: 0}, {c: 1, _id: 0}], orderField: "c" }, {name: "Hint index", indexes: [{index: {a: 1}}, {index: {b: 1}}], shardKey: {a: 1}, inserts: function(c) {for (var i=0; i< docNum; i++) { c.insert({a: Math.floor(Math.random()*docNum*10), b: i%7, c: i, pad: padding});}}, queries: [{}, {a: {$gt: 90000}}, {a: {$lt: 20000}}], orderField: "a", hints: [{a: 1}, "a_1"] }, {name: "Regular index duplicate", indexes: [{index: {b: 1}}], shardKey: {a: 1}, inserts: function(c) {for (var i=0; i< docNum; i++) { c.insert({a: Math.floor(Math.random()*docNum), b: i%7, c: i, pad: padding});}}, queries: [{b: {$lt: 4}}, {b: {$gt: 5}}], orderField: "c" }, {name: "Compound index", indexes: [{index: {a: 1, b: 1}}], shardKey: {a: 1, b: 1}, inserts: function(c) {for (var i=0; i< docNum; i++) { c.insert({a: Math.floor(Math.random()*docNum), b: i%7, c: i, pad: padding});}}, queries: [{a: {$gt: 20}, b: {$lt: 4}}, {a: {$gt: 20}, b: {$gt: 5}}], orderField: "c" }, {name: "Unique index", indexes: [{index: {a: 1, unique: true}}], shardKey: {a: 1}, inserts: function(c) {for (var i=0; i< docNum; i++) { c.insert({a: Math.floor(Math.random()*docNum), b: i%7, c: i, pad: padding});}}, queries: [{a: {$gt: 80}}, {a: {$gt: 30}}], orderField: "c" }, {name: "Geo 2d index", indexes: [{index: {b: "2d"}}], shardKey: {b: 1}, inserts: function(c) {for (var i=0; i< docNum; i++) { c.insert({a: Math.floor(Math.random()*docNum), b: {loc: [i%90, 45-i%45]}, c: i, pad: padding});}}, queries: [{b: {$near: [1, 2]}}], orderField: "c", errorExpected: true }, {name: "Geo 2dsphere index", indexes: [{index: {b: "2dsphere"}}], shardKey: {b: 1}, inserts: function(c) {for (var i=0; i< docNum; i++) { c.insert({a: Math.floor(Math.random()*docNum), b: {type: "Point", coordinates: [i%90, 45-i%45]}, c: i, pad: padding});}}, queries: [{b: {$near: {$geometry: {type: "Point", coordinates: [1, 2]}}}}], orderField: "c", errorExpected: true }, {name: "Geo geoHaystack index", indexes: [{index: {b: "geoHaystack"}}], shardKey: {b: 1}, inserts: function(c) {for (var i=0; i< docNum; i++) { c.insert({a: Math.floor(Math.random()*docNum), b: {type: "Point", coordinates: [i%90, 45-i%45]}, c: i});}}, queries: [{b: {$near: {$geometry: {type: "Point", coordinates: [1, 2]}}}}], orderField: "c", errorExpected: true }, {name: "Text index", indexes: [{index: {b: "text"}}], shardKey: {b: 1}, inserts: function(c) {var words = ["bird", "cat", "dog", "goat"]; for (var i=0; i< docNum; i++) { c.insert({a: Math.floor(Math.random()*docNum), b: words[i%4], c: i, pad: padding});}}, queries: [{$text: {$search: "cat"}}, {b: "cat"}], orderField: "c", errorExpected: true }, {name: "Sparse index", indexes: [{index: {b: 1}, indexOpt: {sparse: true}}], shardKey: {c: 1}, inserts: function(c) {for (var i=0; i< docNum; i++) { c.insert({a: Math.floor(Math.random()*docNum), b: i%7, c: i, pad: padding});}}, queries: [{b: {$gt: 3}}], orderField: "c" }, {name: "Hashed index", indexes: [{index: {b: "hashed"}}], shardKey: {b: 1}, inserts: function(c) {for (var i=0; i< docNum; i++) { c.insert({a: Math.floor(Math.random()*docNum), b: i%7, c: i, pad: padding});}}, queries: [{b: {$lt: 4}}, {b: {$gt: 5}}], orderField: "c" }, {name: "TTL index", // expire is set to 5 years indexes: [{index: {a: 1}, index2: {expireAfterSeconds: 60*60*24*365*5}}], shardKey: {a: 1}, inserts: function(c) {var myDate = new Date(); for (var i=0; i< docNum; i++) { // Year is set to a random value from 2000-2019 myDate.setYear(Math.floor(Math.random()*docNum%20+2000)); c.insert({a: myDate, b: i%7, c: i, pad: padding});}}, queries: [{a: {$lt: ISODate("2012-01-01T00:00:00Z")}}, {a: {$gt: ISODate("2015-01-01T00:00:00Z")}}], orderField: "c" }, {name: "Capped", // cap collection at 75% of docNum collOptions: {capped: true, size: (padSize+100)*(docNum-(docNum*0.25)), max: docNum-(docNum*0.25)}, indexes: [{index: {a: 1}}], inserts: function(c) {for (var i=0; i< docNum; i++) {c.insert({a: Math.floor(Math.random()*docNum), b: i%7, c: i, pad: padding});}}, queries: [{}, {a: {$gt: 80}}, {a: {$lt: 30}}], orderField: "c" } ]; // Main program // Determine if test is standalone or sharded (connected to Mongos) var isMongo = ("isdbgrid" == db.runCommand("ismaster").msg); // Execute tests for (var i=0; i