/** * Tests counters for match expressions. * @tags: [requires_fcv_50] */ (function() { "use strict"; const mongod = MongoRunner.runMongod(); const db = mongod.getDB(jsTest.name()); const collName = jsTest.name(); const coll = db[collName]; coll.drop(); assert.commandWorked(coll.insert({_id: 0, a: 0, b: "foo", c: [10, 50]})); assert.commandWorked(coll.insert({_id: 1, a: 1, b: "foo", c: [10, 20, 30]})); assert.commandWorked(coll.insert({_id: 2, a: 2, b: "bar"})); assert.commandWorked(coll.insert({_id: 3, a: 3, c: [20]})); /** * Run the command and check that the specified match expression counters are increased by the given * value. * - countersToIncrease: document with elements : < increment value>. */ function checkCountersWithValues(command, countersToIncrease) { let beforeMetrics = {}; const metrics = db.serverStatus().metrics.operatorCounters.match; for (let [exprName, inc] of Object.entries(countersToIncrease)) { beforeMetrics[exprName] = metrics[exprName]; } command(); const afterMetrics = db.serverStatus().metrics.operatorCounters.match; for (let [exprName, inc] of Object.entries(countersToIncrease)) { assert.eq(afterMetrics[exprName], beforeMetrics[exprName] + inc, exprName); } } /** * Simplified version of the above function where the counters are expected to increase by 1. * - countersToIncrease: array of match expression names or a single expression name. */ function checkCounters(command, countersToIncrease) { if (!Array.isArray(countersToIncrease)) { countersToIncrease = [countersToIncrease]; } checkCountersWithValues(command, countersToIncrease.reduce((acc, val) => { acc[val] = 1; return acc; }, {})); } /** * Run a command expected to fail and check that the specified match expression counters are not * increased. * - countersNotToIncrease: array of match expression names. */ function checkCountersWithError(command, errorCode, countersNotToIncrease) { let beforeMetrics = {}; const metrics = db.serverStatus().metrics.operatorCounters.match; countersNotToIncrease.forEach((exprName) => { beforeMetrics[exprName] = metrics[exprName]; }); const error = assert.throws(command); assert.commandFailedWithCode(error, errorCode); const afterMetrics = db.serverStatus().metrics.operatorCounters.match; countersNotToIncrease.forEach((exprName) => { assert.eq(afterMetrics[exprName], beforeMetrics[exprName], exprName); }); } // Match expression counters in find. checkCounters(() => assert.eq(1, coll.find({a: 1}).itcount()), "$eq"); checkCounters(() => assert.eq(2, coll.find({b: {$eq: "foo"}}).itcount()), "$eq"); checkCounters(() => assert.eq(2, coll.find({a: {$gt: 1}}).itcount()), "$gt"); checkCounters(() => assert.eq(3, coll.find({a: {$gte: 1}}).itcount()), "$gte"); checkCounters(() => assert.eq(1, coll.find({a: {$lt: 1}}).itcount()), "$lt"); checkCounters(() => assert.eq(2, coll.find({a: {$lte: 1}}).itcount()), "$lte"); checkCounters(() => assert.eq(3, coll.find({a: {$ne: 1}}).itcount()), "$ne"); checkCounters(() => assert.eq(2, coll.find({a: {$in: [1, 2]}}).itcount()), "$in"); checkCounters(() => assert.eq(2, coll.find({a: {$nin: [0, 2]}}).itcount()), "$nin"); checkCounters(() => assert.eq(1, coll.find({a: {$gt: 1, $in: [1, 2]}}).itcount()), ["$in", "$gt"]); checkCounters(() => assert.eq(3, coll.find({c: {$exists: true}}).itcount()), "$exists"); checkCounters(() => assert.eq(4, coll.find({a: {$type: 1}}).itcount()), "$type"); checkCounters(() => assert.eq(2, coll.find({a: {$mod: [2, 0]}}).itcount()), "$mod"); checkCounters(() => assert.eq(2, coll.find({b: {$regex: /^f/}}).itcount()), "$regex"); checkCounters(() => assert.eq(2, coll.find({b: /^f/}).itcount()), "$regex"); checkCounters(() => assert.eq(2, coll.find({b: {$not: /^f/}}).itcount()), ["$regex", "$not"]); checkCounters(() => assert.eq(2, coll.find({$jsonSchema: {required: ["a", "b", "c"]}}).itcount()), "$jsonSchema"); checkCounters(() => assert.eq(4, coll.find({$alwaysTrue: 1}).itcount()), "$alwaysTrue"); checkCounters(() => assert.eq(0, coll.find({$alwaysFalse: 1}).itcount()), "$alwaysFalse"); // Increments only the $expr counter, counters for $rand, $lt, and $add are recorded in // operatorCounters.expressions metrics. checkCounters( () => assert.eq(4, coll.find({$expr: {$lt: ["$a", {$add: [{$rand: {}}, 10]}]}}).itcount()), "$expr"); checkCounters( () => assert.eq(2, coll.find({a: {$mod: [2, 0]}, $comment: "Find even values."}).itcount()), ["$mod", "$comment"]); checkCounters(() => assert.eq(1, coll.find({ $where: function() { return (this.a == 1); } }) .itcount()), "$where"); // Array query operators. checkCounters(() => assert.eq(2, coll.find({c: {$elemMatch: {$gt: 10, $lt: 50}}}).itcount()), ["$elemMatch", "$gt", "$lt"]); checkCounters(() => assert.eq(1, coll.find({c: {$size: 2}}).itcount()), "$size"); checkCounters(() => assert.eq(1, coll.find({c: {$all: [10, 20]}}).itcount()), "$all"); // Logical operators. checkCountersWithValues(() => assert.eq(1, coll.find({$and: [{c: 10}, {c: 20}]}).itcount()), {"$and": 1, "$eq": 2}); checkCounters(() => assert.eq(2, coll.find({$and: [{c: 10}, {a: {$lt: 2}}]}).itcount()), ["$and", "$lt", "$eq"]); checkCountersWithValues(() => assert.eq(2, coll.find({$or: [{c: 50}, {a: 2}]}).itcount()), {"$or": 1, "$eq": 2}); checkCounters(() => assert.eq(1, coll.find({$nor: [{a: {$gt: 1}}, {c: 50}]}).itcount()), ["$nor", "$eq", "$gt"]); checkCounters(() => assert.eq(2, coll.find({b: {$not: {$eq: "foo"}}}).itcount()), ["$not", "$eq"]); // Bitwise query operators. checkCounters(() => assert.eq(2, coll.find({a: {$bitsAllClear: [0]}}).itcount()), "$bitsAllClear"); checkCounters(() => assert.eq(1, coll.find({a: {$bitsAllSet: [0, 1]}}).itcount()), "$bitsAllSet"); checkCounters(() => assert.eq(3, coll.find({a: {$bitsAnyClear: [0, 1]}}).itcount()), "$bitsAnyClear"); checkCounters(() => assert.eq(3, coll.find({a: {$bitsAnySet: [0, 1]}}).itcount()), "$bitsAnySet"); // Invalid expressions do not increment any counter. checkCountersWithError(() => coll.find({$or: [{c: {$size: 'a'}}, {a: 2}]}).itcount(), ErrorCodes.BadValue, ["$or", "$eq", "$size"]); // Match expression counters in aggregation pipelines. let pipeline = [{$match: {_id: 1}}]; checkCounters(() => assert.eq(1, coll.aggregate(pipeline).itcount()), "$eq"); pipeline = [{$match: {_id: {$gt: 0}}}]; checkCounters(() => assert.eq(3, coll.aggregate(pipeline).itcount()), "$gt"); pipeline = [{$match: {$and: [{c: 10}, {a: {$lt: 2}}]}}]; checkCounters(() => assert.eq(2, coll.aggregate(pipeline).itcount()), ["$and", "$eq", "$lt"]); pipeline = [{$match: {c: {$type: 4}}}]; checkCounters(() => assert.eq(3, coll.aggregate(pipeline).itcount()), "$type"); pipeline = [{$match: {c: {$exists: false}}}]; checkCounters(() => assert.eq(1, coll.aggregate(pipeline).itcount()), "$exists"); pipeline = [{$match: {c: {$size: 1}}}]; checkCounters(() => assert.eq(1, coll.aggregate(pipeline).itcount()), "$size"); pipeline = [{$match: {a: {$bitsAllSet: [0, 1]}}}]; checkCounters(() => assert.eq(1, coll.aggregate(pipeline).itcount()), "$bitsAllSet"); pipeline = [{$match: {$sampleRate: 0.25}}]; checkCounters(() => assert.lte(0, coll.aggregate(pipeline).itcount()), "$sampleRate"); // Invalid expressions do not increment any counter. checkCountersWithError( () => coll.aggregate([{$match: {$and: [{b: /^fo/}, {a: {$mod: [2, 'z']}}]}}]).itcount(), ErrorCodes.BadValue, ["$and", "$regex", "$mod"]); // Text search. const textCollName = "myTextCollection"; const textColl = db[textCollName]; textColl.drop(); assert.commandWorked(textColl.insert({_id: 0, title: "coffee"})); assert.commandWorked(textColl.insert({_id: 1, title: "Bake a cake"})); assert.commandWorked(textColl.insert({_id: 2, title: "Cake with coffee"})); assert.commandWorked(textColl.createIndex({title: "text"})); checkCounters( () => assert.eq(1, textColl.find({$text: {$search: "cake", $caseSensitive: true}}).itcount()), "$text"); // Geospatial expressions. const geoCollName = "myGeoCollection"; const geoColl = db[geoCollName]; geoColl.drop(); assert.commandWorked(geoColl.insert({_id: 0, location: {type: "Point", coordinates: [10, 20]}})); assert.commandWorked(geoColl.createIndex({location: "2dsphere"})); checkCounters(() => assert.eq(1, geoColl .find({ location: { $near: { $geometry: {type: "Point", coordinates: [10, 20]}, $minDistance: 0, $maxDistance: 100 } } }) .itcount()), "$near"); checkCounters(() => assert.eq(1, geoColl .find({ location: { $nearSphere: { $geometry: {type: "Point", coordinates: [10.001, 20]}, $minDistance: 100, $maxDistance: 500 } } }) .itcount()), "$nearSphere"); checkCounters( () => assert.eq( 1, geoColl .find({ location: { $geoWithin: { $geometry: { type: "Polygon", coordinates: [[[12, 18], [12, 21], [8, 21], [8, 18], [12, 18]]] }, } } }) .itcount()), "$geoWithin"); checkCounters( () => assert.eq( 1, geoColl .find({ location: { $geoIntersects: { $geometry: { type: "Polygon", coordinates: [[[12, 18], [12, 21], [8, 21], [8, 18], [12, 18]]] }, } } }) .itcount()), "$geoIntersects"); MongoRunner.stopMongod(mongod); })();