summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--buildscripts/resmokeconfig/suites/concurrency_sharded_causal_consistency_and_balancer.yml4
-rw-r--r--buildscripts/resmokeconfig/suites/concurrency_sharded_replication_with_balancer.yml4
-rw-r--r--buildscripts/resmokeconfig/suites/concurrency_sharded_with_stepdowns.yml3
-rw-r--r--buildscripts/resmokeconfig/suites/concurrency_sharded_with_stepdowns_and_balancer.yml6
-rw-r--r--buildscripts/resmokeconfig/suites/replica_sets_initsync_jscore_passthrough.yml1
-rw-r--r--buildscripts/resmokeconfig/suites/replica_sets_multi_stmt_txn_jscore_passthrough.yml1
-rw-r--r--jstests/aggregation/bugs/server6530.js36
-rw-r--r--jstests/aggregation/bugs/server7781.js140
-rw-r--r--jstests/aggregation/mongos_merge.js5
-rw-r--r--jstests/aggregation/sources/geonear/distancefield_and_includelocs.js148
-rw-r--r--jstests/aggregation/sources/geonear/mindistance_and_maxdistance.js99
-rw-r--r--jstests/auth/lib/commands_lib.js53
-rw-r--r--jstests/concurrency/fsm_workloads/yield_geo_near.js43
-rw-r--r--jstests/concurrency/fsm_workloads/yield_geo_near_dedup.js39
-rw-r--r--jstests/core/collation.js178
-rw-r--r--jstests/core/commands_namespace_parsing.js4
-rw-r--r--jstests/core/expr.js44
-rw-r--r--jstests/core/geo2.js4
-rw-r--r--jstests/core/geo3.js152
-rw-r--r--jstests/core/geo5.js17
-rw-r--r--jstests/core/geo_array2.js11
-rw-r--r--jstests/core/geo_borders.js27
-rw-r--r--jstests/core/geo_center_sphere2.js36
-rw-r--r--jstests/core/geo_mindistance.js179
-rw-r--r--jstests/core/geo_nearwithin.js77
-rw-r--r--jstests/core/geo_oob_sphere.js19
-rw-r--r--jstests/core/geo_operator_crs.js74
-rw-r--r--jstests/core/geo_s2near.js226
-rw-r--r--jstests/core/geo_s2near_equator_opposite.js87
-rw-r--r--jstests/core/geo_s2nearwithin.js117
-rw-r--r--jstests/core/geo_s2twofields.js138
-rw-r--r--jstests/core/geo_uniqueDocs.js29
-rw-r--r--jstests/core/geo_uniqueDocs2.js80
-rw-r--r--jstests/core/geo_validate.js11
-rw-r--r--jstests/core/geob.js73
-rw-r--r--jstests/core/geod.js11
-rw-r--r--jstests/core/geonear_cmd_input_validation.js30
-rw-r--r--jstests/core/geonear_key.js35
-rw-r--r--jstests/core/json_schema/misc_validation.js45
-rw-r--r--jstests/core/operation_latency_histogram.js14
-rw-r--r--jstests/core/profile_geonear.js57
-rw-r--r--jstests/core/txns/commands_not_allowed_in_txn.js1
-rw-r--r--jstests/core/txns/statement_ids_accepted.js13
-rw-r--r--jstests/core/views/views_all_commands.js5
-rw-r--r--jstests/libs/geo_near_random.js62
-rw-r--r--jstests/libs/override_methods/set_read_and_write_concerns.js1
-rw-r--r--jstests/libs/override_methods/set_read_preference_secondary.js1
-rw-r--r--jstests/libs/parallelTester.js1
-rw-r--r--jstests/noPassthrough/commands_handle_kill.js19
-rw-r--r--jstests/noPassthrough/commands_preserve_exec_error_code.js3
-rw-r--r--jstests/noPassthrough/currentop_query.js35
-rw-r--r--jstests/noPassthrough/geo_full.js82
-rw-r--r--jstests/noPassthrough/global_operation_latency_histogram.js14
-rw-r--r--jstests/noPassthrough/log_format_slowms_samplerate_loglevel.js19
-rw-r--r--jstests/noPassthrough/readConcern_snapshot.js13
-rw-r--r--jstests/noPassthrough/readConcern_snapshot_mongos.js9
-rw-r--r--jstests/noPassthrough/read_concern_snapshot_yielding.js16
-rw-r--r--jstests/noPassthrough/read_majority_reads.js28
-rw-r--r--jstests/noPassthrough/shell_can_use_read_concern.js16
-rw-r--r--jstests/noPassthroughWithMongod/geo_axis_aligned.js11
-rw-r--r--jstests/readonly/geo.js21
-rw-r--r--jstests/sharding/causal_consistency_shell_support.js19
-rw-r--r--jstests/sharding/collation_targeting.js71
-rw-r--r--jstests/sharding/collation_targeting_inherited.js70
-rw-r--r--jstests/sharding/database_and_shard_versioning_all_commands.js15
-rw-r--r--jstests/sharding/geo_near_sharded.js17
-rw-r--r--jstests/sharding/read_pref_cmd.js1
-rw-r--r--jstests/sharding/safe_secondary_reads_drop_recreate.js6
-rw-r--r--jstests/sharding/safe_secondary_reads_single_migration_suspend_range_deletion.js23
-rw-r--r--jstests/sharding/safe_secondary_reads_single_migration_waitForDelete.js14
-rw-r--r--jstests/sharding/shard7.js6
-rw-r--r--src/mongo/db/SConscript1
-rw-r--r--src/mongo/db/commands/SConscript1
-rw-r--r--src/mongo/db/commands/geo_near_cmd.cpp404
-rw-r--r--src/mongo/db/pipeline/cluster_aggregation_planner.cpp9
-rw-r--r--src/mongo/db/pipeline/dependencies.cpp89
-rw-r--r--src/mongo/db/pipeline/dependencies.h94
-rw-r--r--src/mongo/db/pipeline/dependencies_test.cpp10
-rw-r--r--src/mongo/db/pipeline/document.cpp29
-rw-r--r--src/mongo/db/pipeline/document.h24
-rw-r--r--src/mongo/db/pipeline/document_internal.h36
-rw-r--r--src/mongo/db/pipeline/document_source.h2
-rw-r--r--src/mongo/db/pipeline/document_source_add_fields_test.cpp2
-rw-r--r--src/mongo/db/pipeline/document_source_bucket_auto_test.cpp6
-rw-r--r--src/mongo/db/pipeline/document_source_cursor.cpp10
-rw-r--r--src/mongo/db/pipeline/document_source_cursor.h32
-rw-r--r--src/mongo/db/pipeline/document_source_facet.cpp10
-rw-r--r--src/mongo/db/pipeline/document_source_facet_test.cpp8
-rw-r--r--src/mongo/db/pipeline/document_source_geo_near.cpp271
-rw-r--r--src/mongo/db/pipeline/document_source_geo_near.h136
-rw-r--r--src/mongo/db/pipeline/document_source_geo_near_cursor.cpp118
-rw-r--r--src/mongo/db/pipeline/document_source_geo_near_cursor.h102
-rw-r--r--src/mongo/db/pipeline/document_source_geo_near_test.cpp73
-rw-r--r--src/mongo/db/pipeline/document_source_group_test.cpp2
-rw-r--r--src/mongo/db/pipeline/document_source_limit_test.cpp2
-rw-r--r--src/mongo/db/pipeline/document_source_match.cpp2
-rw-r--r--src/mongo/db/pipeline/document_source_match_test.cpp42
-rw-r--r--src/mongo/db/pipeline/document_source_project_test.cpp4
-rw-r--r--src/mongo/db/pipeline/document_source_replace_root_test.cpp2
-rw-r--r--src/mongo/db/pipeline/document_source_sort.cpp2
-rw-r--r--src/mongo/db/pipeline/document_source_sort_test.cpp2
-rw-r--r--src/mongo/db/pipeline/document_source_unwind_test.cpp2
-rw-r--r--src/mongo/db/pipeline/expression.cpp2
-rw-r--r--src/mongo/db/pipeline/expression_test.cpp8
-rw-r--r--src/mongo/db/pipeline/parsed_exclusion_projection_test.cpp2
-rw-r--r--src/mongo/db/pipeline/pipeline.cpp19
-rw-r--r--src/mongo/db/pipeline/pipeline.h11
-rw-r--r--src/mongo/db/pipeline/pipeline_d.cpp197
-rw-r--r--src/mongo/db/pipeline/pipeline_d.h33
-rw-r--r--src/mongo/db/pipeline/pipeline_test.cpp18
-rw-r--r--src/mongo/embedded/capi_test.cpp1
-rw-r--r--src/mongo/s/commands/SConscript1
-rw-r--r--src/mongo/s/commands/cluster_geo_near_cmd.cpp186
113 files changed, 2548 insertions, 2426 deletions
diff --git a/buildscripts/resmokeconfig/suites/concurrency_sharded_causal_consistency_and_balancer.yml b/buildscripts/resmokeconfig/suites/concurrency_sharded_causal_consistency_and_balancer.yml
index ad37d18622b..31b27153d44 100644
--- a/buildscripts/resmokeconfig/suites/concurrency_sharded_causal_consistency_and_balancer.yml
+++ b/buildscripts/resmokeconfig/suites/concurrency_sharded_causal_consistency_and_balancer.yml
@@ -40,10 +40,6 @@ selector:
- jstests/concurrency/fsm_workloads/map_reduce_replace_nonexistent.js
- jstests/concurrency/fsm_workloads/map_reduce_replace_remove.js
- # Disabled due to SERVER-13364, 'The geoNear command doesn't handle shard versioning, so a
- # concurrent chunk migration may cause duplicate or missing results'
- - jstests/concurrency/fsm_workloads/yield_geo_near_dedup.js
-
# Disabled due to MongoDB restrictions and/or workload restrictions
# These workloads sometimes trigger 'Could not lock auth data update lock'
diff --git a/buildscripts/resmokeconfig/suites/concurrency_sharded_replication_with_balancer.yml b/buildscripts/resmokeconfig/suites/concurrency_sharded_replication_with_balancer.yml
index 1dcd16d7350..dc5161df8e8 100644
--- a/buildscripts/resmokeconfig/suites/concurrency_sharded_replication_with_balancer.yml
+++ b/buildscripts/resmokeconfig/suites/concurrency_sharded_replication_with_balancer.yml
@@ -37,10 +37,6 @@ selector:
- jstests/concurrency/fsm_workloads/map_reduce_replace_nonexistent.js
- jstests/concurrency/fsm_workloads/map_reduce_replace_remove.js
- # Disabled due to SERVER-13364, 'The geoNear command doesn't handle shard versioning, so a
- # concurrent chunk migration may cause duplicate or missing results'
- - jstests/concurrency/fsm_workloads/yield_geo_near_dedup.js
-
# Disabled due to MongoDB restrictions and/or workload restrictions
# These workloads sometimes trigger 'Could not lock auth data update lock'
diff --git a/buildscripts/resmokeconfig/suites/concurrency_sharded_with_stepdowns.yml b/buildscripts/resmokeconfig/suites/concurrency_sharded_with_stepdowns.yml
index 1ac613d9aa7..44a614fda26 100644
--- a/buildscripts/resmokeconfig/suites/concurrency_sharded_with_stepdowns.yml
+++ b/buildscripts/resmokeconfig/suites/concurrency_sharded_with_stepdowns.yml
@@ -152,8 +152,6 @@ selector:
- jstests/concurrency/fsm_workloads/update_multifield_multiupdate.js
- jstests/concurrency/fsm_workloads/update_multifield_multiupdate_noindex.js
- jstests/concurrency/fsm_workloads/update_ordered_bulk_inc.js
- - jstests/concurrency/fsm_workloads/yield_geo_near.js
- - jstests/concurrency/fsm_workloads/yield_geo_near_dedup.js
- jstests/concurrency/fsm_workloads/yield_id_hack.js
# Uses non retryable commands.
@@ -177,6 +175,7 @@ selector:
exclude_with_any_tags:
- uses_transactions
- requires_replication
+ - requires_non_retryable_writes
executor:
archive:
diff --git a/buildscripts/resmokeconfig/suites/concurrency_sharded_with_stepdowns_and_balancer.yml b/buildscripts/resmokeconfig/suites/concurrency_sharded_with_stepdowns_and_balancer.yml
index 4d3d5be805e..c7c360a362e 100644
--- a/buildscripts/resmokeconfig/suites/concurrency_sharded_with_stepdowns_and_balancer.yml
+++ b/buildscripts/resmokeconfig/suites/concurrency_sharded_with_stepdowns_and_balancer.yml
@@ -37,10 +37,6 @@ selector:
- jstests/concurrency/fsm_workloads/map_reduce_replace_nonexistent.js
- jstests/concurrency/fsm_workloads/map_reduce_replace_remove.js
- # Disabled due to SERVER-13364, 'The geoNear command doesn't handle shard versioning, so a
- # concurrent chunk migration may cause duplicate or missing results'
- - jstests/concurrency/fsm_workloads/yield_geo_near_dedup.js
-
# Disabled due to MongoDB restrictions and/or workload restrictions
# These workloads sometimes trigger 'Could not lock auth data update lock'
@@ -158,7 +154,6 @@ selector:
- jstests/concurrency/fsm_workloads/update_multifield_multiupdate.js
- jstests/concurrency/fsm_workloads/update_multifield_multiupdate_noindex.js
- jstests/concurrency/fsm_workloads/update_ordered_bulk_inc.js
- - jstests/concurrency/fsm_workloads/yield_geo_near.js
- jstests/concurrency/fsm_workloads/yield_id_hack.js
# Uses non retryable commands.
@@ -181,6 +176,7 @@ selector:
exclude_with_any_tags:
- uses_transactions
- requires_replication
+ - requires_non_retryable_writes
executor:
archive:
diff --git a/buildscripts/resmokeconfig/suites/replica_sets_initsync_jscore_passthrough.yml b/buildscripts/resmokeconfig/suites/replica_sets_initsync_jscore_passthrough.yml
index 297d53936db..8122140e2cc 100644
--- a/buildscripts/resmokeconfig/suites/replica_sets_initsync_jscore_passthrough.yml
+++ b/buildscripts/resmokeconfig/suites/replica_sets_initsync_jscore_passthrough.yml
@@ -69,7 +69,6 @@ selector:
- jstests/core/profile_distinct.js
- jstests/core/profile_find.js
- jstests/core/profile_findandmodify.js
- - jstests/core/profile_geonear.js
- jstests/core/profile_getmore.js
- jstests/core/profile_insert.js
- jstests/core/profile_sampling.js
diff --git a/buildscripts/resmokeconfig/suites/replica_sets_multi_stmt_txn_jscore_passthrough.yml b/buildscripts/resmokeconfig/suites/replica_sets_multi_stmt_txn_jscore_passthrough.yml
index f5d2c288232..bbdfa6024b5 100644
--- a/buildscripts/resmokeconfig/suites/replica_sets_multi_stmt_txn_jscore_passthrough.yml
+++ b/buildscripts/resmokeconfig/suites/replica_sets_multi_stmt_txn_jscore_passthrough.yml
@@ -37,6 +37,7 @@ selector:
- jstests/core/find_getmore_bsonsize.js
- jstests/core/find_getmore_cmd.js
- jstests/core/find9.js
+ - jstests/core/geonear_key.js
- jstests/core/index_big1.js
- jstests/core/index_bigkeys.js
- jstests/core/index_decimal.js
diff --git a/jstests/aggregation/bugs/server6530.js b/jstests/aggregation/bugs/server6530.js
index 22d26143e39..36a5d3deb3f 100644
--- a/jstests/aggregation/bugs/server6530.js
+++ b/jstests/aggregation/bugs/server6530.js
@@ -1,6 +1,32 @@
-// server-6530: disallow $near queries in $match operations
-load('jstests/aggregation/extras/utils.js');
+/**
+ * Test that $near queries are disallowed in $match stages.
+ */
+(function() {
+ "use strict";
+ load("jstests/aggregation/extras/utils.js");
-assertErrorCode(db.foo, {$match: {$near: [0, 0]}}, ErrorCodes.BadValue);
-assertErrorCode(db.foo, {$match: {$nearSphere: [2, 2]}}, ErrorCodes.BadValue);
-assertErrorCode(db.foo, {$match: {$geoNear: [2, 2]}}, ErrorCodes.BadValue);
+ const coll = db.getCollection("no_near_in_match");
+ coll.drop();
+
+ // Create indexes that could satisfy various $near queries.
+ assert.commandWorked(coll.createIndex({point2d: "2d"}));
+ assert.commandWorked(coll.createIndex({point2dsphere: "2dsphere"}));
+
+ // Populate the collection so that successful queries can return at least one result.
+ assert.writeOK(coll.insert({point2d: [0.25, 0.35]}));
+ assert.writeOK(coll.insert({point2dsphere: [0.25, 0.35]}));
+
+ const nearQuery = {point2d: {$near: [0, 0]}};
+ const nearSphereQuery = {point2dsphere: {$nearSphere: [0, 0]}};
+ const geoNearQuery = {point2d: {$geoNear: [0, 0]}};
+
+ // Test that normal finds return a result.
+ assert.eq(1, coll.find(nearQuery).count());
+ assert.eq(1, coll.find(nearSphereQuery).count());
+ assert.eq(1, coll.find(geoNearQuery).count());
+
+ // Test that we refuse to run $match with a near query.
+ assertErrorCode(coll, {$match: nearQuery}, ErrorCodes.BadValue);
+ assertErrorCode(coll, {$match: nearSphereQuery}, ErrorCodes.BadValue);
+ assertErrorCode(coll, {$match: geoNearQuery}, ErrorCodes.BadValue);
+}());
diff --git a/jstests/aggregation/bugs/server7781.js b/jstests/aggregation/bugs/server7781.js
index 5ed2d58a10e..9f227513c87 100644
--- a/jstests/aggregation/bugs/server7781.js
+++ b/jstests/aggregation/bugs/server7781.js
@@ -16,32 +16,37 @@
() => db[coll].aggregate(
[{$match: {x: 1}}, {$geoNear: {near: [1, 1], spherical: true, distanceField: 'dis'}}]));
- function checkOutput(cmdOut, aggOut, expectedNum) {
- assert.commandWorked(cmdOut, "geoNear command");
-
- // the output arrays are accessed differently
- cmdOut = cmdOut.results;
- aggOut = aggOut.toArray();
-
- assert.eq(cmdOut.length, expectedNum);
- assert.eq(aggOut.length, expectedNum);
-
- var allSame = true;
- var massaged; // massage geoNear command output to match output from agg pipeline
- for (var i = 0; i < cmdOut.length; i++) {
- massaged = {};
- Object.extend(massaged, cmdOut[i].obj, /*deep=*/true);
- massaged.stats = {'dis': cmdOut[i].dis, 'loc': cmdOut[i].loc};
-
- if (!friendlyEqual(massaged, aggOut[i])) {
- allSame = false; // don't bail yet since we want to print all differences
- print("Difference detected at index " + i + " of " + expectedNum);
- print("from geoNear command:" + tojson(massaged));
- print("from aggregate command:" + tojson(aggOut[i]));
- }
- }
-
- assert(allSame);
+ const kDistanceField = "dis";
+ const kIncludeLocsField = "loc";
+
+ /**
+ * Tests the output of the $geoNear command. This function expects a document with the following
+ * fields:
+ * - 'geoNearSpec' is the specification for a $geoNear aggregation stage.
+ * - 'limit' is an integer limiting the number of pipeline results.
+ * - 'batchSize', if specified, is the batchSize to use for the aggregation.
+ */
+ function testGeoNearStageOutput({geoNearSpec, limit, batchSize}) {
+ const aggOptions = batchSize ? {batchSize: batchSize} : {};
+ const result =
+ db[coll].aggregate([{$geoNear: geoNearSpec}, {$limit: limit}], aggOptions).toArray();
+ const errmsg = () => tojson(result);
+
+ // Verify that we got the expected number of results.
+ assert.eq(result.length, limit, errmsg);
+
+ // Run though the array, checking for proper sort order and sane computed distances.
+ result.reduce((lastDist, curDoc) => {
+ const curDist = curDoc[kDistanceField];
+
+ // Verify that distances are in increasing order.
+ assert.lte(lastDist, curDist, errmsg);
+
+ // Verify that the computed distance is correct.
+ const computed = Geo.sphereDistance(geoNearSpec["near"], curDoc[kIncludeLocsField]);
+ assert.close(computed, curDist, errmsg);
+ return curDist;
+ }, 0);
}
// We use this to generate points. Using a single global to avoid reseting RNG in each pass.
@@ -85,67 +90,28 @@
db[coll].ensureIndex({loc: indexType});
- // test with defaults
- var queryPoint = pointMaker.mkPt(0.25); // stick to center of map
- var geoCmd = {geoNear: coll, near: queryPoint, includeLocs: true, spherical: true};
- var aggCmd = {
- $geoNear: {
- near: queryPoint,
- includeLocs: 'stats.loc',
- distanceField: 'stats.dis',
- spherical: true
- }
- };
- checkOutput(db.runCommand(geoCmd), db[coll].aggregate(aggCmd), 100);
-
- // test with num
- queryPoint = pointMaker.mkPt(0.25);
- geoCmd.num = 75;
- geoCmd.near = queryPoint;
- aggCmd.$geoNear.num = 75;
- aggCmd.$geoNear.near = queryPoint;
- checkOutput(db.runCommand(geoCmd), db[coll].aggregate(aggCmd), 75);
-
- // test with limit instead of num (they mean the same thing, but want to test both)
- queryPoint = pointMaker.mkPt(0.25);
- geoCmd.near = queryPoint;
- delete geoCmd.num;
- geoCmd.limit = 70;
- aggCmd.$geoNear.near = queryPoint;
- delete aggCmd.$geoNear.num;
- aggCmd.$geoNear.limit = 70;
- checkOutput(db.runCommand(geoCmd), db[coll].aggregate(aggCmd), 70);
-
- // test spherical
- queryPoint = pointMaker.mkPt(0.25);
- geoCmd.spherical = true;
- geoCmd.near = queryPoint;
- aggCmd.$geoNear.spherical = true;
- aggCmd.$geoNear.near = queryPoint;
- checkOutput(db.runCommand(geoCmd), db[coll].aggregate(aggCmd), 70);
-
- // test $geoNear + $limit coalescing
- queryPoint = pointMaker.mkPt(0.25);
- geoCmd.num = 40;
- geoCmd.near = queryPoint;
- aggCmd.$geoNear.near = queryPoint;
- var aggArr = [aggCmd, {$limit: 50}, {$limit: 60}, {$limit: 40}];
- checkOutput(db.runCommand(geoCmd), db[coll].aggregate(aggArr), 40);
-
- // Test $geoNear with an initial batchSize of 0. Regression test for SERVER-20935.
- queryPoint = pointMaker.mkPt(0.25);
- geoCmd.spherical = true;
- geoCmd.near = queryPoint;
- geoCmd.limit = 70;
- delete geoCmd.num;
- aggCmd.$geoNear.spherical = true;
- aggCmd.$geoNear.near = queryPoint;
- aggCmd.$geoNear.limit = 70;
- delete aggCmd.$geoNear.num;
- var cmdRes = db[coll].runCommand("aggregate", {pipeline: [aggCmd], cursor: {batchSize: 0}});
- assert.commandWorked(cmdRes);
- var cmdCursor = new DBCommandCursor(db, cmdRes, 0);
- checkOutput(db.runCommand(geoCmd), cmdCursor, 70);
+ // Test $geoNear with spherical coordinates.
+ testGeoNearStageOutput({
+ geoNearSpec: {
+ near: pointMaker.mkPt(0.25),
+ distanceField: kDistanceField,
+ includeLocs: kIncludeLocsField,
+ spherical: true,
+ },
+ limit: 100
+ });
+
+ // Test $geoNear with an initial batchSize of 1.
+ testGeoNearStageOutput({
+ geoNearSpec: {
+ near: pointMaker.mkPt(0.25),
+ distanceField: kDistanceField,
+ includeLocs: kIncludeLocsField,
+ spherical: true,
+ },
+ limit: 70,
+ batchSize: 1
+ });
}
test(db, false, '2d');
diff --git a/jstests/aggregation/mongos_merge.js b/jstests/aggregation/mongos_merge.js
index d81f6e91cc8..13ab1b2431e 100644
--- a/jstests/aggregation/mongos_merge.js
+++ b/jstests/aggregation/mongos_merge.js
@@ -205,7 +205,8 @@
assertMergeOnMongoS({
testName: "agg_mongos_merge_geo_near",
pipeline: [
- {$geoNear: {near: [0, 0], distanceField: "distance", spherical: true, limit: 300}}
+ {$geoNear: {near: [0, 0], distanceField: "distance", spherical: true}},
+ {$limit: 300}
],
allowDiskUse: allowDiskUse,
expectedCount: 300
@@ -409,7 +410,7 @@
assertMergeOnMongoS({
testName: "agg_mongos_merge_all_mongos_runnable_stages",
pipeline: [
- {$geoNear: {near: [0, 0], distanceField: "distance", spherical: true, limit: 400}},
+ {$geoNear: {near: [0, 0], distanceField: "distance", spherical: true}},
{$sort: {a: 1}},
{$skip: 150},
{$limit: 150},
diff --git a/jstests/aggregation/sources/geonear/distancefield_and_includelocs.js b/jstests/aggregation/sources/geonear/distancefield_and_includelocs.js
new file mode 100644
index 00000000000..540a3f61caf
--- /dev/null
+++ b/jstests/aggregation/sources/geonear/distancefield_and_includelocs.js
@@ -0,0 +1,148 @@
+/**
+ * Tests the behavior of the $geoNear stage by varying 'distanceField' and 'includeLocs'
+ * (specifically, by specifying nested fields, overriding existing fields, and so on).
+ */
+(function() {
+ "use strict";
+
+ const coll = db.getCollection("geonear_distancefield_and_includelocs");
+ coll.drop();
+
+ /**
+ * Runs an aggregation with a $geoNear stage using 'geoSpec' and an optional $project stage
+ * using 'projSpec'. Returns the first result; that is, the result closest to the "near" point.
+ */
+ function firstGeoNearResult(geoSpec, projSpec) {
+ geoSpec.spherical = true;
+ const pipeline = [{$geoNear: geoSpec}, {$limit: 1}];
+ if (projSpec) {
+ pipeline.push({$project: projSpec});
+ }
+
+ const res = coll.aggregate(pipeline).toArray();
+ assert.eq(1, res.length, tojson(res));
+ return res[0];
+ }
+
+ // Use documents with a variety of different fields: scalars, arrays, legacy points and GeoJSON
+ // objects.
+ const docWithLegacyPoint = {
+ _id: "legacy",
+ geo: [1, 1],
+ ptForNearQuery: [1, 1],
+ scalar: "foo",
+ arr: [{a: 1, b: 1}, {a: 2, b: 2}],
+ };
+ const docWithGeoPoint = {
+ _id: "point",
+ geo: {type: "Point", coordinates: [1, 0]},
+ ptForNearQuery: [1, 0],
+ scalar: "bar",
+ arr: [{a: 3, b: 3}, {a: 4, b: 4}],
+ };
+ const docWithGeoLine = {
+ _id: "linestring",
+ geo: {type: "LineString", coordinates: [[0, 0], [-1, -1]]},
+ ptForNearQuery: [-1, -1],
+ scalar: "baz",
+ arr: [{a: 5, b: 5}, {a: 6, b: 6}],
+ };
+
+ // We test with a 2dsphere index, since 2d indexes can't support GeoJSON objects.
+ assert.commandWorked(coll.createIndex({geo: "2dsphere"}));
+
+ // Populate the collection.
+ assert.writeOK(coll.insert(docWithLegacyPoint));
+ assert.writeOK(coll.insert(docWithGeoPoint));
+ assert.writeOK(coll.insert(docWithGeoLine));
+
+ [docWithLegacyPoint, docWithGeoPoint, docWithGeoLine].forEach(doc => {
+ const docPlusNewFields = (newDoc) => Object.extend(Object.extend({}, doc), newDoc);
+
+ //
+ // Tests for "distanceField".
+ //
+ const expectedDistance = 0;
+
+ // Test that "distanceField" can be computed in a new field.
+ assert.docEq(firstGeoNearResult({near: doc.ptForNearQuery, distanceField: "newField"}),
+ docPlusNewFields({newField: expectedDistance}));
+
+ // Test that "distanceField" can be computed in a new nested field.
+ assert.docEq(firstGeoNearResult({near: doc.ptForNearQuery, distanceField: "nested.field"}),
+ docPlusNewFields({nested: {field: expectedDistance}}));
+
+ // Test that "distanceField" can overwrite an existing scalar field.
+ assert.docEq(firstGeoNearResult({near: doc.ptForNearQuery, distanceField: "scalar"}),
+ docPlusNewFields({scalar: expectedDistance}));
+
+ // Test that "distanceField" can completely overwrite an existing array field.
+ assert.docEq(firstGeoNearResult({near: doc.ptForNearQuery, distanceField: "arr"}),
+ docPlusNewFields({arr: expectedDistance}));
+
+ // TODO (SERVER-35561): When "includeLocs" shares a path prefix with an existing field, the
+ // fields are overwritten, even if they could be preserved.
+ assert.docEq(firstGeoNearResult({near: doc.ptForNearQuery, distanceField: "arr.b"}),
+ docPlusNewFields({arr: {b: expectedDistance}}));
+
+ //
+ // Tests for both "includeLocs" and "distanceField".
+ //
+
+ // Test that "distanceField" and "includeLocs" can both be specified.
+ assert.docEq(firstGeoNearResult(
+ {near: doc.ptForNearQuery, distanceField: "dist", includeLocs: "loc"}),
+ docPlusNewFields({dist: expectedDistance, loc: doc.geo}));
+
+ // Test that "distanceField" and "includeLocs" can be the same path. The result is arbitrary
+ // ("includeLocs" wins).
+ assert.docEq(
+ firstGeoNearResult(
+ {near: doc.ptForNearQuery, distanceField: "newField", includeLocs: "newField"}),
+ docPlusNewFields({newField: doc.geo}));
+
+ // Test that "distanceField" and "includeLocs" are both preserved when their paths share a
+ // prefix but do not conflict.
+ assert.docEq(
+ firstGeoNearResult(
+ {near: doc.ptForNearQuery, distanceField: "comp.dist", includeLocs: "comp.loc"}),
+ docPlusNewFields({comp: {dist: expectedDistance, loc: doc.geo}}));
+
+ //
+ // Tests for "includeLocs" only. Project out the distance field.
+ //
+ const removeDistFieldProj = {d: 0};
+
+ // Test that "includeLocs" can be computed in a new field.
+ assert.docEq(firstGeoNearResult(
+ {near: doc.ptForNearQuery, distanceField: "d", includeLocs: "newField"},
+ removeDistFieldProj),
+ docPlusNewFields({newField: doc.geo}));
+
+ // Test that "includeLocs" can be computed in a new nested field.
+ assert.docEq(
+ firstGeoNearResult(
+ {near: doc.ptForNearQuery, distanceField: "d", includeLocs: "nested.field"},
+ removeDistFieldProj),
+ docPlusNewFields({nested: {field: doc.geo}}));
+
+ // Test that "includeLocs" can overwrite an existing scalar field.
+ assert.docEq(firstGeoNearResult(
+ {near: doc.ptForNearQuery, distanceField: "d", includeLocs: "scalar"},
+ removeDistFieldProj),
+ docPlusNewFields({scalar: doc.geo}));
+
+ // Test that "includeLocs" can completely overwrite an existing array field.
+ assert.docEq(
+ firstGeoNearResult({near: doc.ptForNearQuery, distanceField: "d", includeLocs: "arr"},
+ removeDistFieldProj),
+ docPlusNewFields({arr: doc.geo}));
+
+ // TODO (SERVER-35561): When "includeLocs" shares a path prefix with an existing field, the
+ // fields are overwritten, even if they could be preserved.
+ assert.docEq(
+ firstGeoNearResult({near: doc.ptForNearQuery, distanceField: "d", includeLocs: "arr.a"},
+ removeDistFieldProj),
+ docPlusNewFields({arr: {a: doc.geo}}));
+ });
+}());
diff --git a/jstests/aggregation/sources/geonear/mindistance_and_maxdistance.js b/jstests/aggregation/sources/geonear/mindistance_and_maxdistance.js
new file mode 100644
index 00000000000..7c5e6c750f3
--- /dev/null
+++ b/jstests/aggregation/sources/geonear/mindistance_and_maxdistance.js
@@ -0,0 +1,99 @@
+/**
+ * Tests the behavior of the $geoNear stage with varying values of 'minDistance' and 'maxDistance'.
+ */
+(function() {
+ "use strict";
+
+ const coll = db.getCollection("geonear_mindistance_maxdistance");
+
+ const kMaxDistance = Math.PI * 2.0;
+
+ // Test points that are exactly at the "near" point, close to the point, and far from the point.
+ // Distances are purposely chosen to be small so that distances in meters and radians are close.
+ const origin = {pt: [0, 0]};
+ const near = {pt: [0.23, -0.32]};
+ const far = {pt: [5.9, 0.0]};
+
+ ["2d", "2dsphere"].forEach(geoType => {
+ jsTestLog(`Testing $geoNear with index {pt: "${geoType}"}`);
+ coll.drop();
+
+ // Create the desired index type and populate the collection.
+ assert.commandWorked(coll.createIndex({pt: geoType}));
+ [origin, near, far].forEach(doc => {
+ doc.distFromOrigin = (geoType === "2dsphere") ? Geo.sphereDistance(doc.pt, origin.pt)
+ : Geo.distance(doc.pt, origin.pt);
+ assert.commandWorked(coll.insert(doc));
+ });
+
+ /**
+ * Helper function that runs a $geoNear aggregation near the origin, setting the minimum
+ * and/or maximum search distance using the object 'minMaxOpts', and asserting that the
+ * results match 'expected'.
+ */
+ function assertGeoNearResults(minMaxOpts, expected) {
+ const geoNearStage = {
+ $geoNear: Object.extend(
+ {near: origin.pt, distanceField: "dist", spherical: (geoType === "2dsphere")},
+ minMaxOpts)
+ };
+ const projStage = {$project: {_id: 0, dist: 0}};
+ const res = coll.aggregate([geoNearStage, projStage]).toArray();
+ assert.eq(
+ res,
+ expected,
+ () => `Unexpected results from ${tojson(geoNearStage)} using a ${geoType} index`);
+ }
+
+ // If no minimum nor maximum distance is set, all points are returned.
+ assertGeoNearResults({}, [origin, near, far]);
+
+ //
+ // Tests for minDistance.
+ //
+
+ // Negative values and non-numeric values are illegal.
+ assert.throws(() => assertGeoNearResults({minDistance: -1.1}));
+ assert.throws(() => assertGeoNearResults({minDistance: "3.2"}));
+
+ // A minimum distance of 0 returns all points.
+ assertGeoNearResults({minDistance: -0.0}, [origin, near, far]);
+ assertGeoNearResults({minDistance: 0.0}, [origin, near, far]);
+
+ // Larger minimum distances exclude closer points.
+ assertGeoNearResults({minDistance: (near.distFromOrigin / 2)}, [near, far]);
+ assertGeoNearResults({minDistance: (far.distFromOrigin / 2)}, [far]);
+ assertGeoNearResults({minDistance: kMaxDistance}, []);
+
+ //
+ // Tests for maxDistance.
+ //
+
+ // Negative values and non-numeric values are illegal.
+ assert.throws(() => assertGeoNearResults({maxDistance: -1.1}));
+ assert.throws(() => assertGeoNearResults({maxDistance: "3.2"}));
+
+ // A maximum distance of 0 returns only the origin.
+ assertGeoNearResults({maxDistance: 0.0}, [origin]);
+ assertGeoNearResults({maxDistance: -0.0}, [origin]);
+
+ // Larger maximum distances include more points.
+ assertGeoNearResults({maxDistance: (near.distFromOrigin + 0.01)}, [origin, near]);
+ assertGeoNearResults({maxDistance: (far.distFromOrigin + 0.01)}, [origin, near, far]);
+
+ //
+ // Tests for minDistance and maxDistance together.
+ //
+
+ // Cast a wide net and all points should be returned.
+ assertGeoNearResults({minDistance: 0.0, maxDistance: kMaxDistance}, [origin, near, far]);
+
+ // A narrower range excludes the origin and the far point.
+ assertGeoNearResults(
+ {minDistance: (near.distFromOrigin / 2), maxDistance: (near.distFromOrigin + 0.01)},
+ [near]);
+
+ // An impossible range is legal but returns no results.
+ assertGeoNearResults({minDistance: 3.0, maxDistance: 1.0}, []);
+ });
+}());
diff --git a/jstests/auth/lib/commands_lib.js b/jstests/auth/lib/commands_lib.js
index 9a9b724e015..329659e295d 100644
--- a/jstests/auth/lib/commands_lib.js
+++ b/jstests/auth/lib/commands_lib.js
@@ -1719,6 +1719,35 @@ var authCommandsLib = {
]
},
{
+ testname: "aggregate_geoNear",
+ command: {
+ aggregate: "coll",
+ cursor: {},
+ pipeline: [{$geoNear: {near: [50, 50], distanceField: "dist"}}]
+ },
+ setup: (db) => {
+ db.coll.drop();
+ assert.commandWorked(db.coll.createIndex({loc: "2d"}));
+ assert.commandWorked(db.coll.insert({loc: [45.32, 51.12]}));
+ },
+ teardown: (db) => {
+ db.coll.drop();
+ },
+ testcases: [
+ {
+ runOnDb: firstDbName,
+ roles: roles_read,
+ privileges: [{resource: {db: firstDbName, collection: "coll"}, actions: ["find"]}]
+ },
+ {
+ runOnDb: secondDbName,
+ roles: roles_readAny,
+ privileges:
+ [{resource: {db: secondDbName, collection: "coll"}, actions: ["find"]}]
+ },
+ ],
+ },
+ {
testname: "buildInfo",
command: {buildInfo: 1},
testcases: [
@@ -3903,30 +3932,6 @@ var authCommandsLib = {
]
},
{
- testname: "geoNear",
- command: {geoNear: "x", near: [50, 50], num: 1},
- setup: function(db) {
- db.x.drop();
- db.x.save({loc: [50, 50]});
- db.x.ensureIndex({loc: "2d"});
- },
- teardown: function(db) {
- db.x.drop();
- },
- testcases: [
- {
- runOnDb: firstDbName,
- roles: roles_read,
- privileges: [{resource: {db: firstDbName, collection: "x"}, actions: ["find"]}]
- },
- {
- runOnDb: secondDbName,
- roles: roles_readAny,
- privileges: [{resource: {db: secondDbName, collection: "x"}, actions: ["find"]}]
- }
- ]
- },
- {
testname: "geoSearch",
command: {geoSearch: "x", near: [50, 50], maxDistance: 6, limit: 1, search: {}},
skipSharded: true,
diff --git a/jstests/concurrency/fsm_workloads/yield_geo_near.js b/jstests/concurrency/fsm_workloads/yield_geo_near.js
index 3ed79835906..b025b7ec23b 100644
--- a/jstests/concurrency/fsm_workloads/yield_geo_near.js
+++ b/jstests/concurrency/fsm_workloads/yield_geo_near.js
@@ -1,35 +1,40 @@
'use strict';
/*
- * yield_geo_near.js (extends yield.js)
- *
- * Intersperse geo $near queries with updates and deletes of documents they may match.
+ * Intersperses $geoNear aggregations with updates and deletes of documents they may match.
+ * @tags: [requires_non_retryable_writes]
*/
load('jstests/concurrency/fsm_libs/extend_workload.js'); // for extendWorkload
load('jstests/concurrency/fsm_workloads/yield.js'); // for $config
var $config = extendWorkload($config, function($config, $super) {
-
- /*
- * Use geo $near query to find points near the origin. Note this should be done using the
- * geoNear command, rather than a $near query, as the $near query doesn't work in a sharded
- * environment. Unfortunately this means we cannot batch the request.
- */
$config.states.query = function geoNear(db, collName) {
// This distance gets about 80 docs around the origin. There is one doc inserted
// every 1m^2 and the area scanned by a 5m radius is PI*(5m)^2 ~ 79.
- var maxDistance = 5;
+ const maxDistance = 5;
+ const cursor = db[collName].aggregate([{
+ $geoNear: {
+ near: [0, 0],
+ distanceField: "dist",
+ maxDistance: maxDistance,
+ }
+ }]);
- var res = db.runCommand({geoNear: collName, near: [0, 0], maxDistance: maxDistance});
- assertWhenOwnColl.commandWorked(res); // Could fail if more than 1 2d index.
+ // We only run the verification when workloads are run on separate collections, since the
+ // aggregation may fail if we don't have exactly one 2d index to use.
assertWhenOwnColl(function verifyResults() {
- var results = res.results;
- var prevDoc = {dis: 0}; // distance should never be less than 0
- for (var i = 0; i < results.length; i++) {
- var doc = results[i];
- assertAlways.lte(NumberInt(doc.dis), maxDistance); // satisfies query
- assertAlways.lte(prevDoc.dis, doc.dis); // returned in the correct order
- prevDoc = doc;
+ // We manually verify the results ourselves rather than calling advanceCursor(). In the
+ // event of a failure, the aggregation cursor cannot support explain().
+ let lastDistanceSeen = 0;
+ while (cursor.hasNext()) {
+ const doc = cursor.next();
+ assertAlways.lte(doc.dist,
+ maxDistance,
+ `dist in ${tojson(doc)} exceeds max allowable $geoNear distance`);
+ assertAlways.lte(lastDistanceSeen,
+ doc.dist,
+ `dist in ${tojson(doc)} is not less than the previous distance`);
+ lastDistanceSeen = doc.dist;
}
});
};
diff --git a/jstests/concurrency/fsm_workloads/yield_geo_near_dedup.js b/jstests/concurrency/fsm_workloads/yield_geo_near_dedup.js
index 9f3476873f7..ac338902911 100644
--- a/jstests/concurrency/fsm_workloads/yield_geo_near_dedup.js
+++ b/jstests/concurrency/fsm_workloads/yield_geo_near_dedup.js
@@ -1,9 +1,8 @@
'use strict';
/*
- * yield_geo_near_dedup.js (extends yield_geo_near.js)
- *
- * Intersperse geo $near queries with updates of non-geo fields to test deduplication.
+ * Intersperses $geoNear aggregations with updates of non-geo fields to test deduplication.
+ * @tags: [requires_non_retryable_writes]
*/
load('jstests/concurrency/fsm_libs/extend_workload.js'); // for extendWorkload
load('jstests/concurrency/fsm_workloads/yield_geo_near.js'); // for $config
@@ -43,27 +42,31 @@ var $config = extendWorkload($config, function($config, $super) {
$config.states.query = function geoNear(db, collName) {
// This distance gets about 80 docs around the origin. There is one doc inserted
// every 1m^2 and the area scanned by a 5m radius is PI*(5m)^2 ~ 79.
- var maxDistance = 5;
+ const maxDistance = 5;
+ const cursor = db[collName].aggregate([{
+ $geoNear: {
+ near: [0, 0],
+ distanceField: "dist",
+ maxDistance: maxDistance,
+ spherical: true,
+ }
+ }]);
- var res = db.runCommand(
- {geoNear: collName, near: [0, 0], maxDistance: maxDistance, spherical: true});
- assertWhenOwnColl.commandWorked(res);
+ // We only run the verification when workloads are run on separate collections, since the
+ // aggregation may fail if we don't have exactly one 2d index to use.
assertWhenOwnColl(function verifyResults() {
- var results = res.results;
- var seenObjs = [];
- for (var i = 0; i < results.length; i++) {
- var doc = results[i].obj;
+ const seenObjs = [];
+ while (cursor.hasNext()) {
+ const doc = cursor.next();
// The pair (_id, timesInserted) is the smallest set of attributes that uniquely
// identifies a document.
- var objToSearchFor = {_id: doc._id, timesInserted: doc.timesInserted};
- var found = seenObjs.some(function(obj) {
- return bsonWoCompare(obj, objToSearchFor) === 0;
- });
+ const objToSearch = {_id: doc._id, timesInserted: doc.timesInserted};
+ const found = seenObjs.some(obj => bsonWoCompare(obj, objToSearch) === 0);
assertWhenOwnColl(!found,
- 'geoNear command returned the document ' + tojson(doc) +
- ' multiple times: ' + tojson(seenObjs));
- seenObjs.push(objToSearchFor);
+ `$geoNear returned document ${tojson(doc)} multiple ` +
+ `times: ${tojson(seenObjs)}`);
+ seenObjs.push(objToSearch);
}
});
};
diff --git a/jstests/core/collation.js b/jstests/core/collation.js
index 8458db5e4b3..44eea63d21c 100644
--- a/jstests/core/collation.js
+++ b/jstests/core/collation.js
@@ -1306,153 +1306,99 @@
}
//
- // Collation tests for geoNear.
+ // Collation tests for the $geoNear aggregation stage.
//
- // geoNear should return "collection doesn't exist" error when collation specified and
- // collection does not exist.
+ // $geoNear should fail when collation is specified but the collection does not exist.
coll.drop();
- assert.commandFailed(db.runCommand({
- geoNear: coll.getName(),
- near: {type: "Point", coordinates: [0, 0]},
- spherical: true,
- query: {str: "ABC"},
+ assert.commandFailedWithCode(db.runCommand({
+ aggregate: coll.getName(),
+ cursor: {},
+ pipeline: [{
+ $geoNear: {
+ near: {type: "Point", coordinates: [0, 0]},
+ distanceField: "dist",
+ }
+ }],
collation: {locale: "en_US", strength: 2}
- }));
+ }),
+ ErrorCodes.NamespaceNotFound);
- // geoNear should return correct results when collation specified and string predicate not
- // indexed.
+ // $geoNear rejects the now-deprecated "collation" option.
coll.drop();
assert.writeOK(coll.insert({geo: {type: "Point", coordinates: [0, 0]}, str: "abc"}));
+ assert.commandFailedWithCode(db.runCommand({
+ aggregate: coll.getName(),
+ cursor: {},
+ pipeline: [{
+ $geoNear: {
+ near: {type: "Point", coordinates: [0, 0]},
+ distanceField: "dist",
+ collation: {locale: "en_US"},
+ }
+ }],
+ }),
+ 40227);
+
+ const geoNearStage = {
+ $geoNear: {
+ near: {type: "Point", coordinates: [0, 0]},
+ distanceField: "dist",
+ spherical: true,
+ query: {str: "ABC"}
+ }
+ };
+
+ // $geoNear should return correct results when collation specified and string predicate not
+ // indexed.
assert.commandWorked(coll.ensureIndex({geo: "2dsphere"}));
- assert.eq(0,
- assert
- .commandWorked(db.runCommand({
- geoNear: coll.getName(),
- near: {type: "Point", coordinates: [0, 0]},
- spherical: true,
- query: {str: "ABC"}
- }))
- .results.length);
- assert.eq(1,
- assert
- .commandWorked(db.runCommand({
- geoNear: coll.getName(),
- near: {type: "Point", coordinates: [0, 0]},
- spherical: true,
- query: {str: "ABC"},
- collation: {locale: "en_US", strength: 2}
- }))
- .results.length);
-
- // geoNear should return correct results when no collation specified and string predicate
+ assert.eq(0, coll.aggregate([geoNearStage]).itcount());
+ assert.eq(
+ 1, coll.aggregate([geoNearStage], {collation: {locale: "en_US", strength: 2}}).itcount());
+
+ // $geoNear should return correct results when no collation specified and string predicate
// indexed.
assert.commandWorked(coll.dropIndexes());
assert.commandWorked(coll.ensureIndex({geo: "2dsphere", str: 1}));
- assert.eq(0,
- assert
- .commandWorked(db.runCommand({
- geoNear: coll.getName(),
- near: {type: "Point", coordinates: [0, 0]},
- spherical: true,
- query: {str: "ABC"}
- }))
- .results.length);
- assert.eq(1,
- assert
- .commandWorked(db.runCommand({
- geoNear: coll.getName(),
- near: {type: "Point", coordinates: [0, 0]},
- spherical: true,
- query: {str: "ABC"},
- collation: {locale: "en_US", strength: 2}
- }))
- .results.length);
-
- // geoNear should return correct results when collation specified and collation on index is
+ assert.eq(0, coll.aggregate([geoNearStage]).itcount());
+ assert.eq(
+ 1, coll.aggregate([geoNearStage], {collation: {locale: "en_US", strength: 2}}).itcount());
+
+ // $geoNear should return correct results when collation specified and collation on index is
// incompatible with string predicate.
assert.commandWorked(coll.dropIndexes());
assert.commandWorked(
coll.ensureIndex({geo: "2dsphere", str: 1}, {collation: {locale: "en_US", strength: 3}}));
- assert.eq(0,
- assert
- .commandWorked(db.runCommand({
- geoNear: coll.getName(),
- near: {type: "Point", coordinates: [0, 0]},
- spherical: true,
- query: {str: "ABC"}
- }))
- .results.length);
- assert.eq(1,
- assert
- .commandWorked(db.runCommand({
- geoNear: coll.getName(),
- near: {type: "Point", coordinates: [0, 0]},
- spherical: true,
- query: {str: "ABC"},
- collation: {locale: "en_US", strength: 2}
- }))
- .results.length);
-
- // geoNear should return correct results when collation specified and collation on index is
+ assert.eq(0, coll.aggregate([geoNearStage]).itcount());
+ assert.eq(
+ 1, coll.aggregate([geoNearStage], {collation: {locale: "en_US", strength: 2}}).itcount());
+
+ // $geoNear should return correct results when collation specified and collation on index is
// compatible with string predicate.
assert.commandWorked(coll.dropIndexes());
assert.commandWorked(
coll.ensureIndex({geo: "2dsphere", str: 1}, {collation: {locale: "en_US", strength: 2}}));
- assert.eq(0,
- assert
- .commandWorked(db.runCommand({
- geoNear: coll.getName(),
- near: {type: "Point", coordinates: [0, 0]},
- spherical: true,
- query: {str: "ABC"}
- }))
- .results.length);
- assert.eq(1,
- assert
- .commandWorked(db.runCommand({
- geoNear: coll.getName(),
- near: {type: "Point", coordinates: [0, 0]},
- spherical: true,
- query: {str: "ABC"},
- collation: {locale: "en_US", strength: 2}
- }))
- .results.length);
-
- // geoNear should return correct results when no collation specified and collection has a
+ assert.eq(0, coll.aggregate([geoNearStage]).itcount());
+ assert.eq(
+ 1, coll.aggregate([geoNearStage], {collation: {locale: "en_US", strength: 2}}).itcount());
+
+ // $geoNear should return correct results when no collation specified and collection has a
// default collation.
coll.drop();
assert.commandWorked(
db.createCollection(coll.getName(), {collation: {locale: "en_US", strength: 2}}));
assert.commandWorked(coll.ensureIndex({geo: "2dsphere"}));
assert.writeOK(coll.insert({geo: {type: "Point", coordinates: [0, 0]}, str: "abc"}));
- assert.eq(1,
- assert
- .commandWorked(db.runCommand({
- geoNear: coll.getName(),
- near: {type: "Point", coordinates: [0, 0]},
- spherical: true,
- query: {str: "ABC"}
- }))
- .results.length);
-
- // geoNear should return correct results when "simple" collation specified and collection has
+ assert.eq(1, coll.aggregate([geoNearStage]).itcount());
+
+ // $geoNear should return correct results when "simple" collation specified and collection has
// a default collation.
coll.drop();
assert.commandWorked(
db.createCollection(coll.getName(), {collation: {locale: "en_US", strength: 2}}));
assert.commandWorked(coll.ensureIndex({geo: "2dsphere"}));
assert.writeOK(coll.insert({geo: {type: "Point", coordinates: [0, 0]}, str: "abc"}));
- assert.eq(0,
- assert
- .commandWorked(db.runCommand({
- geoNear: coll.getName(),
- near: {type: "Point", coordinates: [0, 0]},
- spherical: true,
- query: {str: "ABC"},
- collation: {locale: "simple"}
- }))
- .results.length);
+ assert.eq(0, coll.aggregate([geoNearStage], {collation: {locale: "simple"}}).itcount());
//
// Collation tests for find with $nearSphere.
diff --git a/jstests/core/commands_namespace_parsing.js b/jstests/core/commands_namespace_parsing.js
index c3ba15e37a9..ca7f287c6cb 100644
--- a/jstests/core/commands_namespace_parsing.js
+++ b/jstests/core/commands_namespace_parsing.js
@@ -100,10 +100,6 @@
isNotFullyQualified,
isNotAdminCommand);
- // Test geoNear fails with an invalid collection name.
- assertFailsWithInvalidNamespacesForField(
- "geoNear", {geoNear: "", near: [0.0, 0.0]}, isNotFullyQualified, isNotAdminCommand);
-
if (!isMongos) {
// Test geoSearch fails with an invalid collection name.
assertFailsWithInvalidNamespacesForField(
diff --git a/jstests/core/expr.js b/jstests/core/expr.js
index 541c32a4bdc..5a5284474d2 100644
--- a/jstests/core/expr.js
+++ b/jstests/core/expr.js
@@ -172,32 +172,38 @@
}
//
- // $expr in geoNear.
+ // $expr in the $geoNear stage.
//
coll.drop();
assert.writeOK(coll.insert({geo: {type: "Point", coordinates: [0, 0]}, a: 0}));
assert.commandWorked(coll.ensureIndex({geo: "2dsphere"}));
assert.eq(1,
- assert
- .commandWorked(db.runCommand({
- geoNear: coll.getName(),
- near: {type: "Point", coordinates: [0, 0]},
- spherical: true,
- query: {$expr: {$eq: ["$a", 0]}}
- }))
- .results.length);
- assert.commandFailed(db.runCommand({
- geoNear: coll.getName(),
- near: {type: "Point", coordinates: [0, 0]},
- spherical: true,
- query: {$expr: {$eq: ["$a", "$$unbound"]}}
+ coll.aggregate({
+ $geoNear: {
+ near: {type: "Point", coordinates: [0, 0]},
+ distanceField: "dist",
+ spherical: true,
+ query: {$expr: {$eq: ["$a", 0]}}
+ }
+ })
+ .toArray()
+ .length);
+ assert.throws(() => coll.aggregate({
+ $geoNear: {
+ near: {type: "Point", coordinates: [0, 0]},
+ distanceField: "dist",
+ spherical: true,
+ query: {$expr: {$eq: ["$a", "$$unbound"]}}
+ }
}));
- assert.commandFailed(db.runCommand({
- geoNear: coll.getName(),
- near: {type: "Point", coordinates: [0, 0]},
- spherical: true,
- query: {$expr: {$divide: [1, "$a"]}}
+ assert.throws(() => coll.aggregate({
+ $geoNear: {
+ near: {type: "Point", coordinates: [0, 0]},
+ distanceField: "dist",
+ spherical: true,
+ query: {$expr: {$divide: [1, "$a"]}}
+ }
}));
//
diff --git a/jstests/core/geo2.js b/jstests/core/geo2.js
index 558e6c81099..e9fe6239677 100644
--- a/jstests/core/geo2.js
+++ b/jstests/core/geo2.js
@@ -16,8 +16,6 @@ assert.eq(t.count(), n - 1);
t.ensureIndex({loc: "2d"});
-fast = db.runCommand({geoNear: t.getName(), near: [50, 50], num: 10});
-
function a(cur) {
var total = 0;
var outof = 0;
@@ -29,9 +27,7 @@ function a(cur) {
return total / outof;
}
-assert.close(fast.stats.avgDistance, a(t.find({loc: {$near: [50, 50]}}).limit(10)), "B1");
assert.close(1.33333, a(t.find({loc: {$near: [50, 50]}}).limit(3)), "B2");
-assert.close(fast.stats.avgDistance, a(t.find({loc: {$near: [50, 50]}}).limit(10)), "B3");
printjson(t.find({loc: {$near: [50, 50]}}).explain());
diff --git a/jstests/core/geo3.js b/jstests/core/geo3.js
index de8e1b2bf6a..e1afa5da5eb 100644
--- a/jstests/core/geo3.js
+++ b/jstests/core/geo3.js
@@ -1,81 +1,77 @@
// @tags: [requires_fastcount]
-t = db.geo3;
-t.drop();
-
-n = 1;
-arr = [];
-for (var x = -100; x < 100; x += 2) {
- for (var y = -100; y < 100; y += 2) {
- arr.push({_id: n++, loc: [x, y], a: Math.abs(x) % 5, b: Math.abs(y) % 5});
+(function() {
+ t = db.geo3;
+ t.drop();
+
+ n = 1;
+ arr = [];
+ for (var x = -100; x < 100; x += 2) {
+ for (var y = -100; y < 100; y += 2) {
+ arr.push({_id: n++, loc: [x, y], a: Math.abs(x) % 5, b: Math.abs(y) % 5});
+ }
}
-}
-t.insert(arr);
-assert.eq(t.count(), 100 * 100);
-assert.eq(t.count(), n - 1);
-
-t.ensureIndex({loc: "2d"});
-
-fast = db.runCommand({geoNear: t.getName(), near: [50, 50], num: 10});
-
-// test filter
-
-filtered1 = db.runCommand({geoNear: t.getName(), near: [50, 50], num: 10, query: {a: 2}});
-assert.eq(10, filtered1.results.length, "B1");
-filtered1.results.forEach(function(z) {
- assert.eq(2, z.obj.a, "B2: " + tojson(z));
-});
-// printjson( filtered1.stats );
-
-function avgA(q, len) {
- if (!len)
- len = 10;
- var realq = {loc: {$near: [50, 50]}};
- if (q)
- Object.extend(realq, q);
- var as = t.find(realq).limit(len).map(function(z) {
- return z.a;
- });
- assert.eq(len, as.length, "length in avgA");
- return Array.avg(as);
-}
-
-function testFiltering(msg) {
- assert.gt(2, avgA({}), msg + " testFiltering 1 ");
- assert.eq(2, avgA({a: 2}), msg + " testFiltering 2 ");
- assert.eq(4, avgA({a: 4}), msg + " testFiltering 3 ");
-}
-
-testFiltering("just loc");
-
-assert.commandWorked(t.dropIndex({loc: "2d"}));
-assert.commandWorked(t.ensureIndex({loc: "2d", a: 1}));
-
-filtered2 = db.runCommand({geoNear: t.getName(), near: [50, 50], num: 10, query: {a: 2}});
-assert.eq(10, filtered2.results.length, "B3");
-filtered2.results.forEach(function(z) {
- assert.eq(2, z.obj.a, "B4: " + tojson(z));
-});
-
-assert.eq(filtered1.stats.avgDistance, filtered2.stats.avgDistance, "C1");
-assert.gt(filtered1.stats.objectsLoaded, filtered2.stats.objectsLoaded, "C3");
-
-testFiltering("loc and a");
-
-assert.commandWorked(t.dropIndex({loc: "2d", a: 1}));
-assert.commandWorked(t.ensureIndex({loc: "2d", b: 1}));
-
-testFiltering("loc and b");
-
-q = {
- loc: {$near: [50, 50]}
-};
-assert.eq(100, t.find(q).limit(100).itcount(), "D1");
-assert.eq(100, t.find(q).limit(100).size(), "D2");
-
-assert.eq(20, t.find(q).limit(20).itcount(), "D3");
-assert.eq(20, t.find(q).limit(20).size(), "D4");
-
-// SERVER-14039 Wrong limit after skip with $nearSphere, 2d index
-assert.eq(10, t.find(q).skip(10).limit(10).itcount(), "D5");
-assert.eq(10, t.find(q).skip(10).limit(10).size(), "D6");
+ t.insert(arr);
+ assert.eq(t.count(), 100 * 100);
+ assert.eq(t.count(), n - 1);
+
+ t.ensureIndex({loc: "2d"});
+
+ // Test the "query" parameter in $geoNear.
+
+ let res = t.aggregate([
+ {$geoNear: {near: [50, 50], distanceField: "dist", query: {a: 2}}},
+ {$limit: 10},
+ ]).toArray();
+ assert.eq(10, res.length, tojson(res));
+ res.forEach(doc => assert.eq(2, doc.a, tojson(doc)));
+
+ function avgA(q, len) {
+ if (!len)
+ len = 10;
+ var realq = {loc: {$near: [50, 50]}};
+ if (q)
+ Object.extend(realq, q);
+ var as = t.find(realq).limit(len).map(function(z) {
+ return z.a;
+ });
+ assert.eq(len, as.length, "length in avgA");
+ return Array.avg(as);
+ }
+
+ function testFiltering(msg) {
+ assert.gt(2, avgA({}), msg + " testFiltering 1 ");
+ assert.eq(2, avgA({a: 2}), msg + " testFiltering 2 ");
+ assert.eq(4, avgA({a: 4}), msg + " testFiltering 3 ");
+ }
+
+ testFiltering("just loc");
+
+ assert.commandWorked(t.dropIndex({loc: "2d"}));
+ assert.commandWorked(t.ensureIndex({loc: "2d", a: 1}));
+
+ res = t.aggregate([
+ {$geoNear: {near: [50, 50], distanceField: "dist", query: {a: 2}}},
+ {$limit: 10},
+ ]).toArray();
+ assert.eq(10, res.length, "B3");
+ res.forEach(doc => assert.eq(2, doc.a, tojson(doc)));
+
+ testFiltering("loc and a");
+
+ assert.commandWorked(t.dropIndex({loc: "2d", a: 1}));
+ assert.commandWorked(t.ensureIndex({loc: "2d", b: 1}));
+
+ testFiltering("loc and b");
+
+ q = {loc: {$near: [50, 50]}};
+ assert.eq(100, t.find(q).limit(100).itcount(), "D1");
+ assert.eq(100, t.find(q).limit(100).size(), "D2");
+
+ assert.eq(20, t.find(q).limit(20).itcount(), "D3");
+ assert.eq(20, t.find(q).limit(20).size(), "D4");
+
+ // SERVER-14039 Wrong limit after skip with $nearSphere, 2d index
+ assert.eq(10, t.find(q).skip(10).limit(10).itcount(), "D5");
+ assert.eq(10, t.find(q).skip(10).limit(10).size(), "D6");
+}());
diff --git a/jstests/core/geo5.js b/jstests/core/geo5.js
deleted file mode 100644
index bbaa84c1d17..00000000000
--- a/jstests/core/geo5.js
+++ /dev/null
@@ -1,17 +0,0 @@
-t = db.geo5;
-t.drop();
-
-t.insert({p: [0, 0]});
-t.ensureIndex({p: "2d"});
-
-res = t.runCommand("geoNear", {near: [1, 1]});
-assert.eq(1, res.results.length, "A1");
-
-t.insert({p: [1, 1]});
-t.insert({p: [-1, -1]});
-res = t.runCommand("geoNear", {near: [50, 50]});
-assert.eq(3, res.results.length, "A2");
-
-t.insert({p: [-1, -1]});
-res = t.runCommand("geoNear", {near: [50, 50]});
-assert.eq(4, res.results.length, "A3");
diff --git a/jstests/core/geo_array2.js b/jstests/core/geo_array2.js
index 6195e038de3..bd9a8507999 100644
--- a/jstests/core/geo_array2.js
+++ b/jstests/core/geo_array2.js
@@ -40,9 +40,10 @@ for (var t = 0; t < 2; t++) {
// Do near check
var nearResults =
- db.runCommand({geoNear: "geoarray2", near: center, num: count, query: {type: type}})
- .results;
- // printjson( nearResults )
+ db.geoarray2
+ .find({loc: {$near: center}, type: type}, {dis: {$meta: "geoNearDistance"}})
+ .limit(count)
+ .toArray();
var objsFound = {};
var lastResult = 0;
@@ -51,12 +52,10 @@ for (var t = 0; t < 2; t++) {
assert.gt(1.5, nearResults[k].dis);
// Distances should be increasing
assert.lte(lastResult, nearResults[k].dis);
- // Objs should be of the right type
- assert.eq(type, nearResults[k].obj.type);
lastResult = nearResults[k].dis;
- var objKey = "" + nearResults[k].obj._id;
+ var objKey = "" + nearResults[k]._id;
if (objKey in objsFound)
objsFound[objKey]++;
diff --git a/jstests/core/geo_borders.js b/jstests/core/geo_borders.js
index f8a94d997dd..7f7a3eee13b 100644
--- a/jstests/core/geo_borders.js
+++ b/jstests/core/geo_borders.js
@@ -184,22 +184,25 @@ assert.eq(4, t.find({loc: {$near: offCenter, $maxDistance: step * 1.9}}).count()
// Command Tests
// **************
// Make sure we can get all nearby points to point in range
-assert.eq(overallMax, db.runCommand({geoNear: "borders", near: offCenter}).results[0].obj.loc.y);
+assert.eq(overallMax,
+ t.aggregate({$geoNear: {near: offCenter, distanceField: "d"}}).toArray()[0].loc.y);
// Make sure we can get all nearby points to point on boundary
-assert.eq(overallMin, db.runCommand({geoNear: "borders", near: onBoundsNeg}).results[0].obj.loc.y);
+assert.eq(overallMin,
+ t.aggregate({$geoNear: {near: onBoundsNeg, distanceField: "d"}}).toArray()[0].loc.y);
// Make sure we can't get all nearby points to point over boundary
-// TODO: SERVER-9986 clean up wrapping rules for different CRS queries - not sure this is an error
-/*
-assert.commandFailed( db.runCommand( { geoNear : "borders", near : offBounds } ));
-*/
-
-// Make sure we can't get all nearby points to point on max boundary
-assert.commandWorked(db.runCommand({geoNear: "borders", near: onBounds}));
+assert.commandFailedWithCode(db.runCommand({
+ aggregate: "borders",
+ cursor: {},
+ pipeline: [{$geoNear: {near: offBounds, distanceField: "d"}}]
+}),
+ 16433);
+assert.eq(numItems, t.aggregate({$geoNear: {near: onBounds, distanceField: "d"}}).toArray().length);
// Make sure we can get all nearby points within one step (4 points in top
// corner)
-assert.eq(
- 4,
- db.runCommand({geoNear: "borders", near: offCenter, maxDistance: step * 1.5}).results.length);
+assert.eq(4,
+ t.aggregate({$geoNear: {near: offCenter, maxDistance: step * 1.5, distanceField: "d"}})
+ .toArray()
+ .length);
diff --git a/jstests/core/geo_center_sphere2.js b/jstests/core/geo_center_sphere2.js
index 29865844813..761cb5b7403 100644
--- a/jstests/core/geo_center_sphere2.js
+++ b/jstests/core/geo_center_sphere2.js
@@ -126,36 +126,24 @@ for (var test = 0; test < numTests; test++) {
distance = minNewDistance;
}
- // geoNear
- results = db.runCommand({
- geoNear: "sphere",
- near: startPoint,
- maxDistance: radius,
- num: 2 * pointsIn,
- spherical: true
- }).results;
-
- /*
- printjson( results );
-
- for ( var j = 0; j < results[0].obj.loc.length; j++ ) {
- var newDistance = Geo.sphereDistance( startPoint, results[0].obj.loc[j] )
- if( newDistance <= radius ) print( results[0].obj.loc[j] + " : " + newDistance )
- }
- */
-
- assert.eq(docsIn, results.length);
+ // Test $geoNear.
+ results = t.aggregate({
+ $geoNear: {
+ near: startPoint,
+ distanceField: "dis",
+ maxDistance: radius,
+ spherical: true,
+ }
+ }).toArray();
+ assert.eq(docsIn, results.length, tojson(results));
var distance = 0;
for (var i = 0; i < results.length; i++) {
var retDistance = results[i].dis;
- // print( "Dist from : " + results[i].loc + " to " + startPoint + " is "
- // + retDistance + " vs " + radius )
-
var distInObj = false;
- for (var j = 0; j < results[i].obj.loc.length && distInObj == false; j++) {
- var newDistance = Geo.sphereDistance(startPoint, results[i].obj.loc[j]);
+ for (var j = 0; j < results[i].loc.length && distInObj == false; j++) {
+ var newDistance = Geo.sphereDistance(startPoint, results[i].loc[j]);
distInObj =
(newDistance >= retDistance - 0.0001 && newDistance <= retDistance + 0.0001);
}
diff --git a/jstests/core/geo_mindistance.js b/jstests/core/geo_mindistance.js
index fe694ea2c0e..92ccc617cf5 100644
--- a/jstests/core/geo_mindistance.js
+++ b/jstests/core/geo_mindistance.js
@@ -1,6 +1,5 @@
-// Test $minDistance option for $near and $nearSphere queries, and geoNear command. SERVER-9395.
-//
-// @tags: [requires_fastcount]
+// Test $minDistance option for $near and $nearSphere queries, and the $geoNear aggregation stage.
+// @tags: [requires_fastcount, requires_getmore]
(function() {
"use strict";
@@ -24,16 +23,20 @@
* the existing $maxDistance option to test the newer $minDistance option's behavior.
*/
function n_docs_within(radius_km) {
- // geoNear's distances are in meters for geoJSON points.
- var cmdResult = db.runCommand({
- geoNear: t.getName(),
- near: {type: 'Point', coordinates: [0, 0]},
- spherical: true,
- maxDistance: radius_km * km,
- num: 1000
- });
-
- return cmdResult.results.length;
+ // $geoNear's distances are in meters for geoJSON points.
+ return t
+ .aggregate([
+ {
+ $geoNear: {
+ near: {type: 'Point', coordinates: [0, 0]},
+ distanceField: "dis",
+ spherical: true,
+ maxDistance: radius_km * km,
+ }
+ },
+ {$limit: 1000}
+ ])
+ .itcount();
}
//
@@ -133,62 +136,68 @@
n_bw500_and_1000_count);
//
- // Test geoNear command with GeoJSON point. Distances are in meters.
+ // Test $geoNear aggregation stage with GeoJSON point. Distances are in meters.
//
- var cmdResult = db.runCommand({
- geoNear: t.getName(),
- near: {type: 'Point', coordinates: [0, 0]},
- minDistance: 1400 * km,
- spherical: true // spherical required for 2dsphere index
- });
+ let geoNearCount = t.aggregate({
+ $geoNear: {
+ near: {type: 'Point', coordinates: [0, 0]},
+ minDistance: 1400 * km,
+ spherical: true,
+ distanceField: "d",
+ }
+ }).itcount();
assert.eq(n_docs - n_docs_within(1400),
- cmdResult.results.length,
+ geoNearCount,
"Expected " + (n_docs - n_docs_within(1400)) +
- " points geoNear (0, 0) with $minDistance 1400 km, got " +
- cmdResult.results.length);
-
- cmdResult = db.runCommand({
- geoNear: t.getName(),
- near: {type: 'Point', coordinates: [0, 0]},
- minDistance: 500 * km,
- maxDistance: 1000 * km,
- spherical: true
- });
+ " points geoNear (0, 0) with $minDistance 1400 km, got " + geoNearCount);
+
+ geoNearCount = t.aggregate({
+ $geoNear: {
+ near: {type: 'Point', coordinates: [0, 0]},
+ minDistance: 500 * km,
+ maxDistance: 1000 * km,
+ spherical: true,
+ distanceField: "d",
+ }
+ }).itcount();
assert.eq(n_docs_within(1000) - n_docs_within(500),
- cmdResult.results.length,
+ geoNearCount,
"Expected " + (n_docs_within(1000) - n_docs_within(500)) +
" points geoNear (0, 0) with $minDistance 500 km and $maxDistance 1000 km, got " +
- cmdResult.results.length);
+ geoNearCount);
//
- // Test geoNear command with legacy point. Distances are in radians.
+ // Test $geoNear aggregation stage with legacy point. Distances are in radians.
//
- cmdResult = db.runCommand({
- geoNear: t.getName(),
- near: legacyPoint,
- minDistance: metersToRadians(1400 * km),
- spherical: true // spherical required for 2dsphere index
- });
+ geoNearCount = t.aggregate({
+ $geoNear: {
+ near: legacyPoint,
+ minDistance: metersToRadians(1400 * km),
+ spherical: true,
+ distanceField: "d",
+ }
+ }).itcount();
assert.eq(n_docs - n_docs_within(1400),
- cmdResult.results.length,
+ geoNearCount,
"Expected " + (n_docs - n_docs_within(1400)) +
- " points geoNear (0, 0) with $minDistance 1400 km, got " +
- cmdResult.results.length);
-
- cmdResult = db.runCommand({
- geoNear: t.getName(),
- near: legacyPoint,
- minDistance: metersToRadians(500 * km),
- maxDistance: metersToRadians(1000 * km),
- spherical: true
- });
+ " points geoNear (0, 0) with $minDistance 1400 km, got " + geoNearCount);
+
+ geoNearCount = t.aggregate({
+ $geoNear: {
+ near: legacyPoint,
+ minDistance: metersToRadians(500 * km),
+ maxDistance: metersToRadians(1000 * km),
+ spherical: true,
+ distanceField: "d",
+ }
+ }).itcount();
assert.eq(n_docs_within(1000) - n_docs_within(500),
- cmdResult.results.length,
+ geoNearCount,
"Expected " + (n_docs_within(1000) - n_docs_within(500)) +
" points geoNear (0, 0) with $minDistance 500 km and $maxDistance 1000 km, got " +
- cmdResult.results.length);
+ geoNearCount);
t.drop();
assert.commandWorked(t.createIndex({loc: "2d"}));
@@ -204,32 +213,42 @@
assert.eq(3, t.find({loc: {$nearSphere: [0, 0]}}).itcount());
assert.eq(1, t.find({loc: {$nearSphere: [0, 0], $minDistance: deg2rad(41.5)}}).itcount());
- // Test minDistance for 2d index with geoNear command and spherical=false.
- cmdResult = db.runCommand({geoNear: t.getName(), near: [0, 0], spherical: false});
- assert.commandWorked(cmdResult);
- assert.eq(3, cmdResult.results.length);
- assert.eq(40, cmdResult.results[0].dis);
- assert.eq(41, cmdResult.results[1].dis);
- assert.eq(42, cmdResult.results[2].dis);
-
- cmdResult =
- db.runCommand({geoNear: t.getName(), near: [0, 0], minDistance: 41.5, spherical: false});
- assert.commandWorked(cmdResult);
- assert.eq(1, cmdResult.results.length);
- assert.eq(42, cmdResult.results[0].dis);
-
- // Test minDistance for 2d index with geoNear command and spherical=true. Distances are in
+ // Test minDistance for 2d index with $geoNear stage and spherical=false.
+ let cmdResult =
+ t.aggregate({$geoNear: {near: [0, 0], spherical: false, distanceField: "dis"}}).toArray();
+ assert.eq(3, cmdResult.length);
+ assert.eq(40, cmdResult[0].dis);
+ assert.eq(41, cmdResult[1].dis);
+ assert.eq(42, cmdResult[2].dis);
+
+ cmdResult = t.aggregate({
+ $geoNear: {
+ near: [0, 0],
+ minDistance: 41.5,
+ spherical: false,
+ distanceField: "dis",
+ }
+ }).toArray();
+ assert.eq(1, cmdResult.length);
+ assert.eq(42, cmdResult[0].dis);
+
+ // Test minDistance for 2d index with $geoNear stage and spherical=true. Distances are in
// radians.
- cmdResult = db.runCommand({geoNear: t.getName(), near: [0, 0], spherical: true});
- assert.commandWorked(cmdResult);
- assert.eq(3, cmdResult.results.length);
- assertApproxEqual(deg2rad(40), cmdResult.results[0].dis, 1e-3);
- assertApproxEqual(deg2rad(41), cmdResult.results[1].dis, 1e-3);
- assertApproxEqual(deg2rad(42), cmdResult.results[2].dis, 1e-3);
-
- cmdResult = db.runCommand(
- {geoNear: t.getName(), near: [0, 0], minDistance: deg2rad(41.5), spherical: true});
- assert.commandWorked(cmdResult);
- assert.eq(1, cmdResult.results.length);
- assertApproxEqual(deg2rad(42), cmdResult.results[0].dis, 1e-3);
+ cmdResult =
+ t.aggregate({$geoNear: {near: [0, 0], spherical: true, distanceField: "dis"}}).toArray();
+ assert.eq(3, cmdResult.length);
+ assertApproxEqual(deg2rad(40), cmdResult[0].dis, 1e-3);
+ assertApproxEqual(deg2rad(41), cmdResult[1].dis, 1e-3);
+ assertApproxEqual(deg2rad(42), cmdResult[2].dis, 1e-3);
+
+ cmdResult = t.aggregate({
+ $geoNear: {
+ near: [0, 0],
+ minDistance: deg2rad(41.5),
+ spherical: true,
+ distanceField: "dis",
+ }
+ }).toArray();
+ assert.eq(1, cmdResult.length);
+ assertApproxEqual(deg2rad(42), cmdResult[0].dis, 1e-3);
}());
diff --git a/jstests/core/geo_nearwithin.js b/jstests/core/geo_nearwithin.js
index 69eaac51ffe..a63871c3195 100644
--- a/jstests/core/geo_nearwithin.js
+++ b/jstests/core/geo_nearwithin.js
@@ -1,39 +1,40 @@
-// Test geoNear + $within.
-t = db.geo_nearwithin;
-t.drop();
-
-points = 10;
-for (var x = -points; x < points; x += 1) {
- for (var y = -points; y < points; y += 1) {
- t.insert({geo: [x, y]});
- }
-}
-
-t.ensureIndex({geo: "2d"});
-
-resNear = db.runCommand(
- {geoNear: t.getName(), near: [0, 0], query: {geo: {$within: {$center: [[0, 0], 1]}}}});
-assert.eq(resNear.results.length, 5);
-resNear = db.runCommand(
- {geoNear: t.getName(), near: [0, 0], query: {geo: {$within: {$center: [[0, 0], 0]}}}});
-assert.eq(resNear.results.length, 1);
-resNear = db.runCommand(
- {geoNear: t.getName(), near: [0, 0], query: {geo: {$within: {$center: [[1, 0], 0.5]}}}});
-assert.eq(resNear.results.length, 1);
-resNear = db.runCommand(
- {geoNear: t.getName(), near: [0, 0], query: {geo: {$within: {$center: [[1, 0], 1.5]}}}});
-assert.eq(resNear.results.length, 9);
-
-// We want everything distance >1 from us but <1.5
-// These points are (-+1, -+1)
-resNear = db.runCommand({
- geoNear: t.getName(),
- near: [0, 0],
- query: {
- $and: [
- {geo: {$within: {$center: [[0, 0], 1.5]}}},
- {geo: {$not: {$within: {$center: [[0, 0], 1]}}}}
- ]
+// Test $near + $within.
+(function() {
+ t = db.geo_nearwithin;
+ t.drop();
+
+ points = 10;
+ for (var x = -points; x < points; x += 1) {
+ for (var y = -points; y < points; y += 1) {
+ assert.commandWorked(t.insert({geo: [x, y]}));
+ }
}
-});
-assert.eq(resNear.results.length, 4);
+
+ assert.commandWorked(t.ensureIndex({geo: "2d"}));
+
+ const runQuery = (center) =>
+ t.find({$and: [{geo: {$near: [0, 0]}}, {geo: {$within: {$center: center}}}]}).toArray();
+
+ resNear = runQuery([[0, 0], 1]);
+ assert.eq(resNear.length, 5);
+
+ resNear = runQuery([[0, 0], 0]);
+ assert.eq(resNear.length, 1);
+
+ resNear = runQuery([[1, 0], 0.5]);
+ assert.eq(resNear.length, 1);
+
+ resNear = runQuery([[1, 0], 1.5]);
+ assert.eq(resNear.length, 9);
+
+ // We want everything distance >1 from us but <1.5
+ // These points are (-+1, -+1)
+ resNear = t.find({
+ $and: [
+ {geo: {$near: [0, 0]}},
+ {geo: {$within: {$center: [[0, 0], 1.5]}}},
+ {geo: {$not: {$within: {$center: [[0, 0], 1]}}}}
+ ]
+ }).toArray();
+ assert.eq(resNear.length, 4);
+}());
diff --git a/jstests/core/geo_oob_sphere.js b/jstests/core/geo_oob_sphere.js
index 40249766355..57857e383bd 100644
--- a/jstests/core/geo_oob_sphere.js
+++ b/jstests/core/geo_oob_sphere.js
@@ -27,11 +27,20 @@ assert.throws(function() {
t.find({loc: {$within: {$centerSphere: [[-180, -91], 0.25]}}}).count();
});
-var res;
-res =
- db.runCommand({geoNear: "geooobsphere", near: [179, -91], maxDistance: 0.25, spherical: true});
-assert.commandFailed(res);
-printjson(res);
+// In a spherical geometry, this point is out-of-bounds.
+assert.commandFailedWithCode(t.runCommand("find", {filter: {loc: {$nearSphere: [179, -91]}}}),
+ 17444);
+assert.commandFailedWithCode(t.runCommand("aggregate", {
+ cursor: {},
+ pipeline: [{
+ $geoNear: {
+ near: [179, -91],
+ distanceField: "dis",
+ spherical: true,
+ }
+ }]
+}),
+ 17444);
// TODO: SERVER-9986 - it's not clear that throwing is correct behavior here
// res = db.runCommand({ geoNear : "geooobsphere", near : [30, 89], maxDistance : 0.25, spherical :
diff --git a/jstests/core/geo_operator_crs.js b/jstests/core/geo_operator_crs.js
index 89fb7ca8585..b2cc8fe0439 100644
--- a/jstests/core/geo_operator_crs.js
+++ b/jstests/core/geo_operator_crs.js
@@ -3,56 +3,56 @@
//
// Tests that the correct CRSes are used for geo queries (based on input geometry)
//
+(function() {
+ var coll = db.geo_operator_crs;
+ coll.drop();
-var coll = db.geo_operator_crs;
-coll.drop();
+ //
+ // Test 2dsphere index
+ //
-//
-// Test 2dsphere index
-//
+ assert.commandWorked(coll.ensureIndex({geo: "2dsphere"}));
-assert.commandWorked(coll.ensureIndex({geo: "2dsphere"}));
+ var legacyZeroPt = [0, 0];
+ var jsonZeroPt = {type: "Point", coordinates: [0, 0]};
+ var legacy90Pt = [90, 0];
+ var json90Pt = {type: "Point", coordinates: [90, 0]};
-var legacyZeroPt = [0, 0];
-var jsonZeroPt = {type: "Point", coordinates: [0, 0]};
-var legacy90Pt = [90, 0];
-var json90Pt = {type: "Point", coordinates: [90, 0]};
+ assert.writeOK(coll.insert({geo: json90Pt}));
-assert.writeOK(coll.insert({geo: json90Pt}));
+ var earthRadiusMeters = 6378.1 * 1000;
+ var result = null;
-var earthRadiusMeters = 6378.1 * 1000;
-var result = null;
+ const runQuery = (point) =>
+ coll.find({geo: {$nearSphere: point}}, {dis: {$meta: "geoNearDistance"}}).toArray();
-result = coll.getDB().runCommand({geoNear: coll.getName(), near: legacyZeroPt, spherical: true});
-assert.commandWorked(result);
-assert.close(result.results[0].dis, Math.PI / 2);
+ result = runQuery(legacyZeroPt);
+ assert.close(result[0].dis, Math.PI / 2);
-result = coll.getDB().runCommand({geoNear: coll.getName(), near: jsonZeroPt, spherical: true});
-assert.commandWorked(result);
-assert.close(result.results[0].dis, (Math.PI / 2) * earthRadiusMeters);
+ result = runQuery(jsonZeroPt);
+ assert.close(result[0].dis, (Math.PI / 2) * earthRadiusMeters);
-assert.writeOK(coll.remove({}));
-assert.commandWorked(coll.dropIndexes());
+ assert.writeOK(coll.remove({}));
+ assert.commandWorked(coll.dropIndexes());
-//
-// Test 2d Index
-//
+ //
+ // Test 2d Index
+ //
-assert.commandWorked(coll.ensureIndex({geo: "2d"}));
+ assert.commandWorked(coll.ensureIndex({geo: "2d"}));
-assert.writeOK(coll.insert({geo: legacy90Pt}));
+ assert.writeOK(coll.insert({geo: legacy90Pt}));
-result = coll.getDB().runCommand({geoNear: coll.getName(), near: legacyZeroPt, spherical: true});
-assert.commandWorked(result);
-assert.close(result.results[0].dis, Math.PI / 2);
+ result = runQuery(legacyZeroPt);
+ assert.close(result[0].dis, Math.PI / 2);
-// GeoJSON not supported unless there's a 2dsphere index
+ // GeoJSON not supported unless there's a 2dsphere index
-//
-// Test with a 2d and 2dsphere index
-//
+ //
+ // Test with a 2d and 2dsphere index using the aggregation $geoNear stage.
+ //
-assert.commandWorked(coll.ensureIndex({geo: "2dsphere"}));
-result = coll.getDB().runCommand({geoNear: coll.getName(), near: jsonZeroPt, spherical: true});
-assert.commandWorked(result);
-assert.close(result.results[0].dis, (Math.PI / 2) * earthRadiusMeters);
+ assert.commandWorked(coll.ensureIndex({geo: "2dsphere"}));
+ result = coll.aggregate({$geoNear: {near: jsonZeroPt, distanceField: "dis"}}).toArray();
+ assert.close(result[0].dis, (Math.PI / 2) * earthRadiusMeters);
+}());
diff --git a/jstests/core/geo_s2near.js b/jstests/core/geo_s2near.js
index 0306a60adb9..d0a591d45e6 100644
--- a/jstests/core/geo_s2near.js
+++ b/jstests/core/geo_s2near.js
@@ -1,117 +1,143 @@
// @tags: [requires_getmore]
-// Test 2dsphere near search, called via find and geoNear.
-t = db.geo_s2near;
-t.drop();
+// Test 2dsphere near search, called via find and $geoNear.
+(function() {
+ t = db.geo_s2near;
+ t.drop();
-// Make sure that geoNear gives us back loc
-goldenPoint = {
- type: "Point",
- coordinates: [31.0, 41.0]
-};
-t.insert({geo: goldenPoint});
-t.ensureIndex({geo: "2dsphere"});
-resNear = db.runCommand(
- {geoNear: t.getName(), near: [30, 40], num: 1, spherical: true, includeLocs: true});
-assert.eq(resNear.results[0].loc, goldenPoint);
+ // Make sure that geoNear gives us back loc
+ goldenPoint = {type: "Point", coordinates: [31.0, 41.0]};
+ t.insert({geo: goldenPoint});
+ t.ensureIndex({geo: "2dsphere"});
+ resNear =
+ t.aggregate([
+ {$geoNear: {near: [30, 40], distanceField: "d", spherical: true, includeLocs: "loc"}},
+ {$limit: 1}
+ ]).toArray();
+ assert.eq(resNear.length, 1, tojson(resNear));
+ assert.eq(resNear[0].loc, goldenPoint);
-// FYI:
-// One degree of long @ 0 is 111km or so.
-// One degree of lat @ 0 is 110km or so.
-lat = 0;
-lng = 0;
-points = 10;
-for (var x = -points; x < points; x += 1) {
- for (var y = -points; y < points; y += 1) {
- t.insert({geo: {"type": "Point", "coordinates": [lng + x / 1000.0, lat + y / 1000.0]}});
+ // FYI:
+ // One degree of long @ 0 is 111km or so.
+ // One degree of lat @ 0 is 110km or so.
+ lat = 0;
+ lng = 0;
+ points = 10;
+ for (var x = -points; x < points; x += 1) {
+ for (var y = -points; y < points; y += 1) {
+ t.insert({geo: {"type": "Point", "coordinates": [lng + x / 1000.0, lat + y / 1000.0]}});
+ }
}
-}
-origin = {
- "type": "Point",
- "coordinates": [lng, lat]
-};
+ origin = {"type": "Point", "coordinates": [lng, lat]};
-t.ensureIndex({geo: "2dsphere"});
+ t.ensureIndex({geo: "2dsphere"});
-// Near only works when the query is a point.
-someline = {
- "type": "LineString",
- "coordinates": [[40, 5], [41, 6]]
-};
-somepoly = {
- "type": "Polygon",
- "coordinates": [[[40, 5], [40, 6], [41, 6], [41, 5], [40, 5]]]
-};
-assert.throws(function() {
- return t.find({"geo": {"$near": {"$geometry": someline}}}).count();
-});
-assert.throws(function() {
- return t.find({"geo": {"$near": {"$geometry": somepoly}}}).count();
-});
-assert.throws(function() {
- return db.runCommand({geoNear: t.getName(), near: someline, spherical: true}).results.length;
-});
-assert.throws(function() {
- return db.runCommand({geoNear: t.getName(), near: somepoly, spherical: true}).results.length;
-});
+ // Near only works when the query is a point.
+ someline = {"type": "LineString", "coordinates": [[40, 5], [41, 6]]};
+ somepoly = {"type": "Polygon", "coordinates": [[[40, 5], [40, 6], [41, 6], [41, 5], [40, 5]]]};
+ assert.throws(function() {
+ return t.find({"geo": {"$near": {"$geometry": someline}}}).count();
+ });
+ assert.throws(function() {
+ return t.find({"geo": {"$near": {"$geometry": somepoly}}}).count();
+ });
+ assert.throws(function() {
+ return t.aggregate({$geoNear: {near: someline, distanceField: "dis", spherical: true}});
+ });
+ assert.throws(function() {
+ return t.aggregate({$geoNear: {near: somepoly, distanceField: "dis", spherical: true}});
+ });
-// Do some basic near searches.
-res = t.find({"geo": {"$near": {"$geometry": origin, $maxDistance: 2000}}}).limit(10);
-resNear = db.runCommand(
- {geoNear: t.getName(), near: [0, 0], num: 10, maxDistance: Math.PI, spherical: true});
-assert.eq(res.itcount(), resNear.results.length, "10");
+ // Do some basic near searches.
+ res = t.find({"geo": {"$near": {"$geometry": origin, $maxDistance: 2000}}}).limit(10);
+ resNear = t.aggregate([
+ {$geoNear: {near: [0, 0], distanceField: "dis", maxDistance: Math.PI, spherical: true}},
+ {$limit: 10},
+ ]);
+ assert.eq(res.itcount(), resNear.itcount(), "10");
-res = t.find({"geo": {"$near": {"$geometry": origin}}}).limit(10);
-resNear = db.runCommand({geoNear: t.getName(), near: [0, 0], num: 10, spherical: true});
-assert.eq(res.itcount(), resNear.results.length, "10");
+ res = t.find({"geo": {"$near": {"$geometry": origin}}}).limit(10);
+ resNear = t.aggregate([
+ {$geoNear: {near: [0, 0], distanceField: "dis", spherical: true}},
+ {$limit: 10},
+ ]);
+ assert.eq(res.itcount(), resNear.itcount(), "10");
-// Find all the points!
-res = t.find({"geo": {"$near": {"$geometry": origin}}}).limit(10000);
-resNear = db.runCommand({geoNear: t.getName(), near: [0, 0], num: 10000, spherical: true});
-assert.eq(resNear.results.length, res.itcount(), ((2 * points) * (2 * points)).toString());
+ // Find all the points!
+ res = t.find({"geo": {"$near": {"$geometry": origin}}}).limit(10000);
+ resNear = t.aggregate([
+ {$geoNear: {near: [0, 0], distanceField: "dis", spherical: true}},
+ {$limit: 10000},
+ ]);
+ assert.eq(res.itcount(), resNear.itcount(), ((2 * points) * (2 * points)).toString());
-// longitude goes -180 to 180
-// latitude goes -90 to 90
-// Let's put in some perverse (polar) data and make sure we get it back.
-// Points go long, lat.
-t.insert({geo: {"type": "Point", "coordinates": [-180, -90]}});
-t.insert({geo: {"type": "Point", "coordinates": [180, -90]}});
-t.insert({geo: {"type": "Point", "coordinates": [180, 90]}});
-t.insert({geo: {"type": "Point", "coordinates": [-180, 90]}});
-res = t.find({"geo": {"$near": {"$geometry": origin}}}).limit(10000);
-resNear = db.runCommand({geoNear: t.getName(), near: [0, 0], num: 10000, spherical: true});
-assert.eq(res.itcount(), resNear.results.length, ((2 * points) * (2 * points) + 4).toString());
+ // longitude goes -180 to 180
+ // latitude goes -90 to 90
+ // Let's put in some perverse (polar) data and make sure we get it back.
+ // Points go long, lat.
+ t.insert({geo: {"type": "Point", "coordinates": [-180, -90]}});
+ t.insert({geo: {"type": "Point", "coordinates": [180, -90]}});
+ t.insert({geo: {"type": "Point", "coordinates": [180, 90]}});
+ t.insert({geo: {"type": "Point", "coordinates": [-180, 90]}});
+ res = t.find({"geo": {"$near": {"$geometry": origin}}}).limit(10000);
+ resNear = t.aggregate([
+ {$geoNear: {near: [0, 0], distanceField: "dis", spherical: true}},
+ {$limit: 10000},
+ ]);
+ assert.eq(res.itcount(), resNear.itcount(), ((2 * points) * (2 * points) + 4).toString());
-function testRadAndDegreesOK(distance) {
- // Distance for old style points is radians.
- resRadians = t.find({geo: {$nearSphere: [0, 0], $maxDistance: (distance / (6378.1 * 1000))}});
- // Distance for new style points is meters.
- resMeters = t.find({"geo": {"$near": {"$geometry": origin, $maxDistance: distance}}});
- // And we should get the same # of results no matter what.
- assert.eq(resRadians.itcount(), resMeters.itcount());
+ function testRadAndDegreesOK(distance) {
+ // Distance for old style points is radians.
+ resRadians =
+ t.find({geo: {$nearSphere: [0, 0], $maxDistance: (distance / (6378.1 * 1000))}});
+ // Distance for new style points is meters.
+ resMeters = t.find({"geo": {"$near": {"$geometry": origin, $maxDistance: distance}}});
+ // And we should get the same # of results no matter what.
+ assert.eq(resRadians.itcount(), resMeters.itcount());
- // Also, geoNear should behave the same way.
- resGNMeters =
- db.runCommand({geoNear: t.getName(), near: origin, maxDistance: distance, spherical: true});
- resGNRadians = db.runCommand({
- geoNear: t.getName(),
- near: [0, 0],
- maxDistance: (distance / (6378.1 * 1000)),
- spherical: true
- });
- assert.eq(resGNRadians.results.length, resGNMeters.results.length);
- for (var i = 0; i < resGNRadians.length; ++i) {
- // Radius of earth * radians = distance in meters.
- assert.close(resGNRadians.results[i].dis * 6378.1 * 1000, resGNMeters.results[i].dis);
+ // Also, $geoNear should behave the same way.
+ resGNMeters = t.aggregate({
+ $geoNear: {
+ near: origin,
+ distanceField: "dis",
+ maxDistance: distance,
+ spherical: true,
+ }
+ }).toArray();
+ resGNRadians = t.aggregate({
+ $geoNear: {
+ near: [0, 0],
+ distanceField: "dis",
+ maxDistance: (distance / (6378.1 * 1000)),
+ spherical: true,
+ }
+ }).toArray();
+ const errmsg = `$geoNear using meter distances returned ${tojson(resGNMeters)}, but ` +
+ `$geoNear using radian distances returned ${tojson(resGNRadians)}`;
+ assert.eq(resGNRadians.length, resGNMeters.length, errmsg);
+ for (var i = 0; i < resGNRadians.length; ++i) {
+ // Radius of earth * radians = distance in meters.
+ assert.close(resGNRadians[i].dis * 6378.1 * 1000, resGNMeters[i].dis);
+ }
}
-}
-testRadAndDegreesOK(1);
-testRadAndDegreesOK(10);
-testRadAndDegreesOK(50);
-testRadAndDegreesOK(10000);
+ testRadAndDegreesOK(1);
+ testRadAndDegreesOK(10);
+ testRadAndDegreesOK(50);
+ testRadAndDegreesOK(10000);
-// SERVER-13666 legacy coordinates must be in bounds for spherical near queries.
-assert.commandFailed(
- db.runCommand({geoNear: t.getName(), near: [1210.466, 31.2051], spherical: true, num: 10}));
+ // SERVER-13666 legacy coordinates must be in bounds for spherical near queries.
+ assert.commandFailedWithCode(db.runCommand({
+ aggregate: t.getName(),
+ cursor: {},
+ pipeline: [{
+ $geoNear: {
+ near: [1210.466, 31.2051],
+ distanceField: "dis",
+ spherical: true,
+ }
+ }]
+ }),
+ 17444);
+}());
diff --git a/jstests/core/geo_s2near_equator_opposite.js b/jstests/core/geo_s2near_equator_opposite.js
index 754c27e523d..fb17310030b 100644
--- a/jstests/core/geo_s2near_equator_opposite.js
+++ b/jstests/core/geo_s2near_equator_opposite.js
@@ -1,37 +1,56 @@
// Tests geo near with 2 points diametrically opposite to each other
// on the equator
// First reported in SERVER-11830 as a regression in 2.5
-
-var t = db.geos2nearequatoropposite;
-
-t.drop();
-
-t.insert({loc: {type: 'Point', coordinates: [0, 0]}});
-t.insert({loc: {type: 'Point', coordinates: [-1, 0]}});
-
-t.ensureIndex({loc: '2dsphere'});
-
-// upper bound for half of earth's circumference in meters
-var dist = 40075000 / 2 + 1;
-
-var nearSphereCount =
- t.find({
- loc: {
- $nearSphere: {$geometry: {type: 'Point', coordinates: [180, 0]}, $maxDistance: dist}
- }
- }).itcount();
-var nearCount =
- t.find({
- loc: {$near: {$geometry: {type: 'Point', coordinates: [180, 0]}, $maxDistance: dist}}
- }).itcount();
-var geoNearResult = db.runCommand(
- {geoNear: t.getName(), near: {type: 'Point', coordinates: [180, 0]}, spherical: true});
-
-print('nearSphere count = ' + nearSphereCount);
-print('near count = ' + nearCount);
-print('geoNearResults = ' + tojson(geoNearResult));
-
-assert.eq(2, nearSphereCount, 'unexpected document count for nearSphere');
-assert.eq(2, nearCount, 'unexpected document count for near');
-assert.eq(2, geoNearResult.results.length, 'unexpected document count in geoNear results');
-assert.gt(dist, geoNearResult.stats.maxDistance, 'unexpected maximum distance in geoNear results');
+(function() {
+ var t = db.geos2nearequatoropposite;
+
+ t.drop();
+
+ t.insert({loc: {type: 'Point', coordinates: [0, 0]}});
+ t.insert({loc: {type: 'Point', coordinates: [-1, 0]}});
+
+ t.ensureIndex({loc: '2dsphere'});
+
+ // upper bound for half of earth's circumference in meters
+ var dist = 40075000 / 2 + 1;
+
+ var nearSphereCount =
+ t.find({
+ loc: {
+ $nearSphere:
+ {$geometry: {type: 'Point', coordinates: [180, 0]}, $maxDistance: dist}
+ }
+ }).itcount();
+ var nearCount =
+ t.find({
+ loc: {$near: {$geometry: {type: 'Point', coordinates: [180, 0]}, $maxDistance: dist}}
+ }).itcount();
+ var geoNearResult = t.aggregate([
+ {
+ $geoNear: {
+ near: {type: 'Point', coordinates: [180, 0]},
+ spherical: true,
+ distanceField: "dist",
+ }
+ },
+ {
+ $group: {
+ _id: null,
+ nResults: {$sum: 1},
+ maxDistance: {$max: "$distanceField"},
+ }
+ }
+ ]).toArray();
+
+ assert.eq(2, nearSphereCount, 'unexpected document count for nearSphere');
+ assert.eq(2, nearCount, 'unexpected document count for near');
+ assert.eq(1, geoNearResult.length, `unexpected $geoNear result: ${tojson(geoNearResult)}`);
+
+ const geoNearStats = geoNearResult[0];
+ assert.eq(2,
+ geoNearStats.nResults,
+ `unexpected document count for $geoNear: ${tojson(geoNearStats)}`);
+ assert.gt(dist,
+ geoNearStats.maxDistance,
+ `unexpected maximum distance in $geoNear results: ${tojson(geoNearStats)}`);
+}());
diff --git a/jstests/core/geo_s2nearwithin.js b/jstests/core/geo_s2nearwithin.js
index 1bcec709643..6df9a1940df 100644
--- a/jstests/core/geo_s2nearwithin.js
+++ b/jstests/core/geo_s2nearwithin.js
@@ -1,64 +1,57 @@
-// Test geoNear + $within.
-t = db.geo_s2nearwithin;
-t.drop();
-
-points = 10;
-for (var x = -points; x < points; x += 1) {
- for (var y = -points; y < points; y += 1) {
- t.insert({geo: [x, y]});
+// Test $geoNear + $within.
+(function() {
+ t = db.geo_s2nearwithin;
+ t.drop();
+
+ points = 10;
+ for (var x = -points; x < points; x += 1) {
+ for (var y = -points; y < points; y += 1) {
+ assert.commandWorked(t.insert({geo: [x, y]}));
+ }
}
-}
-
-origin = {
- "type": "Point",
- "coordinates": [0, 0]
-};
-
-t.ensureIndex({geo: "2dsphere"});
-// Near requires an index, and 2dsphere is an index. Spherical isn't
-// specified so this doesn't work.
-assert.commandFailed(db.runCommand(
- {geoNear: t.getName(), near: [0, 0], query: {geo: {$within: {$center: [[0, 0], 1]}}}}));
-
-// Spherical is specified so this does work. Old style points are weird
-// because you can use them with both $center and $centerSphere. Points are
-// the only things we will do this conversion for.
-resNear = db.runCommand({
- geoNear: t.getName(),
- near: [0, 0],
- spherical: true,
- query: {geo: {$within: {$center: [[0, 0], 1]}}}
-});
-assert.eq(resNear.results.length, 5);
-
-resNear = db.runCommand({
- geoNear: t.getName(),
- near: [0, 0],
- spherical: true,
- query: {geo: {$within: {$centerSphere: [[0, 0], Math.PI / 180.0]}}}
-});
-assert.eq(resNear.results.length, 5);
-
-resNear = db.runCommand({
- geoNear: t.getName(),
- near: [0, 0],
- spherical: true,
- query: {geo: {$within: {$centerSphere: [[0, 0], 0]}}}
-});
-assert.eq(resNear.results.length, 1);
-
-resNear = db.runCommand({
- geoNear: t.getName(),
- near: [0, 0],
- spherical: true,
- query: {geo: {$within: {$centerSphere: [[1, 0], 0.5 * Math.PI / 180.0]}}}
-});
-assert.eq(resNear.results.length, 1);
-resNear = db.runCommand({
- geoNear: t.getName(),
- near: [0, 0],
- spherical: true,
- query: {geo: {$within: {$center: [[1, 0], 1.5]}}}
-});
-assert.eq(resNear.results.length, 9);
+ origin = {"type": "Point", "coordinates": [0, 0]};
+
+ assert.commandWorked(t.ensureIndex({geo: "2dsphere"}));
+ // Near requires an index, and 2dsphere is an index. Spherical isn't
+ // specified so this doesn't work.
+ let res = assert.commandFailedWithCode(t.runCommand("aggregate", {
+ cursor: {},
+ pipeline: [{
+ $geoNear: {
+ near: [0, 0],
+ distanceField: "d",
+ query: {geo: {$within: {$center: [[0, 0], 1]}}}
+ }
+ }],
+ }),
+ ErrorCodes.BadValue);
+ assert(res.errmsg.includes("unable to find index for $geoNear query"), tojson(res));
+
+ // Spherical is specified so this does work. Old style points are weird
+ // because you can use them with both $center and $centerSphere. Points are
+ // the only things we will do this conversion for.
+ const runGeoNear = (within) => t.aggregate({
+ $geoNear: {
+ near: [0, 0],
+ distanceField: "d",
+ spherical: true,
+ query: {geo: {$within: within}},
+ }
+ }).toArray();
+
+ resNear = runGeoNear({$center: [[0, 0], 1]});
+ assert.eq(resNear.length, 5);
+
+ resNear = runGeoNear({$centerSphere: [[0, 0], Math.PI / 180.0]});
+ assert.eq(resNear.length, 5);
+
+ resNear = runGeoNear({$centerSphere: [[0, 0], 0]});
+ assert.eq(resNear.length, 1);
+
+ resNear = runGeoNear({$centerSphere: [[1, 0], 0.5 * Math.PI / 180.0]});
+ assert.eq(resNear.length, 1);
+
+ resNear = runGeoNear({$center: [[1, 0], 1.5]});
+ assert.eq(resNear.length, 9);
+}());
diff --git a/jstests/core/geo_s2twofields.js b/jstests/core/geo_s2twofields.js
index ada5e96f6d5..517c8c8df12 100644
--- a/jstests/core/geo_s2twofields.js
+++ b/jstests/core/geo_s2twofields.js
@@ -3,82 +3,86 @@
//
// @tags: [requires_fastcount]
-var t = db.geo_s2twofields;
-t.drop();
+(function() {
+ var t = db.geo_s2twofields;
+ t.drop();
-Random.setRandomSeed();
-var random = Random.rand;
-var PI = Math.PI;
+ Random.setRandomSeed();
+ var random = Random.rand;
+ var PI = Math.PI;
-function randomCoord(center, minDistDeg, maxDistDeg) {
- var dx = random() * (maxDistDeg - minDistDeg) + minDistDeg;
- var dy = random() * (maxDistDeg - minDistDeg) + minDistDeg;
- return [center[0] + dx, center[1] + dy];
-}
+ function randomCoord(center, minDistDeg, maxDistDeg) {
+ var dx = random() * (maxDistDeg - minDistDeg) + minDistDeg;
+ var dy = random() * (maxDistDeg - minDistDeg) + minDistDeg;
+ return [center[0] + dx, center[1] + dy];
+ }
-var nyc = {type: "Point", coordinates: [-74.0064, 40.7142]};
-var miami = {type: "Point", coordinates: [-80.1303, 25.7903]};
-var maxPoints = 10000;
-var degrees = 5;
+ var nyc = {type: "Point", coordinates: [-74.0064, 40.7142]};
+ var miami = {type: "Point", coordinates: [-80.1303, 25.7903]};
+ var maxPoints = 10000;
+ var degrees = 5;
-var arr = [];
-for (var i = 0; i < maxPoints; ++i) {
- var fromCoord = randomCoord(nyc.coordinates, 0, degrees);
- var toCoord = randomCoord(miami.coordinates, 0, degrees);
+ var arr = [];
+ for (var i = 0; i < maxPoints; ++i) {
+ var fromCoord = randomCoord(nyc.coordinates, 0, degrees);
+ var toCoord = randomCoord(miami.coordinates, 0, degrees);
- arr.push(
- {from: {type: "Point", coordinates: fromCoord}, to: {type: "Point", coordinates: toCoord}});
-}
-res = t.insert(arr);
-assert.writeOK(res);
-assert.eq(t.count(), maxPoints);
+ arr.push({
+ from: {type: "Point", coordinates: fromCoord},
+ to: {type: "Point", coordinates: toCoord}
+ });
+ }
+ res = t.insert(arr);
+ assert.writeOK(res);
+ assert.eq(t.count(), maxPoints);
-function semiRigorousTime(func) {
- var lowestTime = func();
- var iter = 2;
- for (var i = 0; i < iter; ++i) {
- var run = func();
- if (run < lowestTime) {
- lowestTime = run;
+ function semiRigorousTime(func) {
+ var lowestTime = func();
+ var iter = 2;
+ for (var i = 0; i < iter; ++i) {
+ var run = func();
+ if (run < lowestTime) {
+ lowestTime = run;
+ }
}
+ return lowestTime;
}
- return lowestTime;
-}
-function timeWithoutAndWithAnIndex(index, query) {
- t.dropIndex(index);
- var withoutTime = semiRigorousTime(function() {
- return t.find(query).explain("executionStats").executionStats.executionTimeMillis;
- });
- t.ensureIndex(index);
- var withTime = semiRigorousTime(function() {
- return t.find(query).explain("executionStats").executionStats.executionTimeMillis;
- });
- t.dropIndex(index);
- return [withoutTime, withTime];
-}
+ function timeWithoutAndWithAnIndex(index, query) {
+ t.dropIndex(index);
+ var withoutTime = semiRigorousTime(function() {
+ return t.find(query).explain("executionStats").executionStats.executionTimeMillis;
+ });
+ t.ensureIndex(index);
+ var withTime = semiRigorousTime(function() {
+ return t.find(query).explain("executionStats").executionStats.executionTimeMillis;
+ });
+ t.dropIndex(index);
+ return [withoutTime, withTime];
+ }
-var maxQueryRad = 0.5 * PI / 180.0;
-// When we're not looking at ALL the data, anything indexed should beat not-indexed.
-var smallQuery = timeWithoutAndWithAnIndex({to: "2dsphere", from: "2dsphere"}, {
- from: {$within: {$centerSphere: [nyc.coordinates, maxQueryRad]}},
- to: {$within: {$centerSphere: [miami.coordinates, maxQueryRad]}}
-});
-print("Indexed time " + smallQuery[1] + " unindexed " + smallQuery[0]);
-// assert(smallQuery[0] > smallQuery[1]);
+ var maxQueryRad = 0.5 * PI / 180.0;
+ // When we're not looking at ALL the data, anything indexed should beat not-indexed.
+ var smallQuery = timeWithoutAndWithAnIndex({to: "2dsphere", from: "2dsphere"}, {
+ from: {$within: {$centerSphere: [nyc.coordinates, maxQueryRad]}},
+ to: {$within: {$centerSphere: [miami.coordinates, maxQueryRad]}}
+ });
+ print("Indexed time " + smallQuery[1] + " unindexed " + smallQuery[0]);
+ // assert(smallQuery[0] > smallQuery[1]);
-// Let's just index one field.
-var smallQuery = timeWithoutAndWithAnIndex({to: "2dsphere"}, {
- from: {$within: {$centerSphere: [nyc.coordinates, maxQueryRad]}},
- to: {$within: {$centerSphere: [miami.coordinates, maxQueryRad]}}
-});
-print("Indexed time " + smallQuery[1] + " unindexed " + smallQuery[0]);
-// assert(smallQuery[0] > smallQuery[1]);
+ // Let's just index one field.
+ var smallQuery = timeWithoutAndWithAnIndex({to: "2dsphere"}, {
+ from: {$within: {$centerSphere: [nyc.coordinates, maxQueryRad]}},
+ to: {$within: {$centerSphere: [miami.coordinates, maxQueryRad]}}
+ });
+ print("Indexed time " + smallQuery[1] + " unindexed " + smallQuery[0]);
+ // assert(smallQuery[0] > smallQuery[1]);
-// And the other one.
-var smallQuery = timeWithoutAndWithAnIndex({from: "2dsphere"}, {
- from: {$within: {$centerSphere: [nyc.coordinates, maxQueryRad]}},
- to: {$within: {$centerSphere: [miami.coordinates, maxQueryRad]}}
-});
-print("Indexed time " + smallQuery[1] + " unindexed " + smallQuery[0]);
-// assert(smallQuery[0] > smallQuery[1]);
+ // And the other one.
+ var smallQuery = timeWithoutAndWithAnIndex({from: "2dsphere"}, {
+ from: {$within: {$centerSphere: [nyc.coordinates, maxQueryRad]}},
+ to: {$within: {$centerSphere: [miami.coordinates, maxQueryRad]}}
+ });
+ print("Indexed time " + smallQuery[1] + " unindexed " + smallQuery[0]);
+ // assert(smallQuery[0] > smallQuery[1]);
+}());
diff --git a/jstests/core/geo_uniqueDocs.js b/jstests/core/geo_uniqueDocs.js
index d66d5243c01..6a46337966b 100644
--- a/jstests/core/geo_uniqueDocs.js
+++ b/jstests/core/geo_uniqueDocs.js
@@ -1,25 +1,34 @@
-// Test uniqueDocs option for $within and geoNear queries SERVER-3139
+// Test uniqueDocs option for $within queries and the $geoNear aggregation stage. SERVER-3139
// SERVER-12120 uniqueDocs is deprecated. Server always returns unique documents.
collName = 'geo_uniqueDocs_test';
t = db.geo_uniqueDocs_test;
t.drop();
-t.save({locs: [[0, 2], [3, 4]]});
-t.save({locs: [[6, 8], [10, 10]]});
+assert.commandWorked(t.save({locs: [[0, 2], [3, 4]]}));
+assert.commandWorked(t.save({locs: [[6, 8], [10, 10]]}));
-t.ensureIndex({locs: '2d'});
+assert.commandWorked(t.ensureIndex({locs: '2d'}));
-// geoNear tests
+// $geoNear tests
// uniqueDocs option is ignored.
-assert.eq(2, db.runCommand({geoNear: collName, near: [0, 0]}).results.length);
-assert.eq(2, db.runCommand({geoNear: collName, near: [0, 0], uniqueDocs: false}).results.length);
-assert.eq(2, db.runCommand({geoNear: collName, near: [0, 0], uniqueDocs: true}).results.length);
-results = db.runCommand({geoNear: collName, near: [0, 0], num: 2}).results;
+assert.eq(2, t.aggregate({$geoNear: {near: [0, 0], distanceField: "dis"}}).toArray().length);
+assert.eq(2,
+ t.aggregate({$geoNear: {near: [0, 0], distanceField: "dis", uniqueDocs: false}})
+ .toArray()
+ .length);
+assert.eq(2,
+ t.aggregate({$geoNear: {near: [0, 0], distanceField: "dis", uniqueDocs: true}})
+ .toArray()
+ .length);
+results = t.aggregate([{$geoNear: {near: [0, 0], distanceField: "dis"}}, {$limit: 2}]).toArray();
assert.eq(2, results.length);
assert.close(2, results[0].dis);
assert.close(10, results[1].dis);
-results = db.runCommand({geoNear: collName, near: [0, 0], num: 2, uniqueDocs: true}).results;
+results = t.aggregate([
+ {$geoNear: {near: [0, 0], distanceField: "dis", uniqueDocs: true}},
+ {$limit: 2}
+ ]).toArray();
assert.eq(2, results.length);
assert.close(2, results[0].dis);
assert.close(10, results[1].dis);
diff --git a/jstests/core/geo_uniqueDocs2.js b/jstests/core/geo_uniqueDocs2.js
index 6b0aafb92ae..23aa831f560 100644
--- a/jstests/core/geo_uniqueDocs2.js
+++ b/jstests/core/geo_uniqueDocs2.js
@@ -34,53 +34,69 @@ assert.eq(1, t.count({loc: {$within: {$center: [[30, 30], 10], $uniqueDocs: true
assert.eq(1, t.count({loc: {$within: {$center: [[30, 30], 10], $uniqueDocs: false}}}));
// Check number and character of results with geoNear / uniqueDocs / includeLocs.
-notUniqueNotInclude = db.runCommand(
- {geoNear: collName, near: [50, 50], num: 10, uniqueDocs: false, includeLocs: false});
-uniqueNotInclude = db.runCommand(
- {geoNear: collName, near: [50, 50], num: 10, uniqueDocs: true, includeLocs: false});
-notUniqueInclude = db.runCommand(
- {geoNear: collName, near: [50, 50], num: 10, uniqueDocs: false, includeLocs: true});
-uniqueInclude = db.runCommand(
- {geoNear: collName, near: [50, 50], num: 10, uniqueDocs: true, includeLocs: true});
-
-// Check that only unique docs are returned.
-assert.eq(1, notUniqueNotInclude.results.length);
-assert.eq(1, uniqueNotInclude.results.length);
-assert.eq(1, notUniqueInclude.results.length);
-assert.eq(1, uniqueInclude.results.length);
+notUniqueNotInclude =
+ t.aggregate({$geoNear: {near: [50, 50], distanceField: "dis", uniqueDocs: false}}).toArray();
+uniqueNotInclude =
+ t.aggregate({$geoNear: {near: [50, 50], distanceField: "dis", uniqueDocs: true}}).toArray();
+notUniqueInclude = t.aggregate({
+ $geoNear: {
+ near: [50, 50],
+ distanceField: "dis",
+ uniqueDocs: false,
+ includeLocs: "point",
+ }
+ }).toArray();
+uniqueInclude = t.aggregate({
+ $geoNear: {
+ near: [50, 50],
+ distanceField: "dis",
+ uniqueDocs: true,
+ includeLocs: "point",
+ }
+ }).toArray();
+
+// Check that only unique results are returned, regardless of the value of "uniqueDocs" parameter.
+assert.eq(1, notUniqueNotInclude.length);
+assert.eq(1, uniqueNotInclude.length);
+assert.eq(1, notUniqueInclude.length);
+assert.eq(1, uniqueInclude.length);
// Check that locs are included.
-assert(!notUniqueNotInclude.results[0].loc);
-assert(!uniqueNotInclude.results[0].loc);
-assert(notUniqueInclude.results[0].loc);
-assert(uniqueInclude.results[0].loc);
-
-// For geoNear / uniqueDocs, 'num' limit seems to apply to locs.
-assert.eq(1,
- db.runCommand(
- {geoNear: collName, near: [50, 50], num: 1, uniqueDocs: false, includeLocs: false})
- .results.length);
+assert(!notUniqueNotInclude[0].point);
+assert(!uniqueNotInclude[0].point);
+assert(notUniqueInclude[0].point);
+assert(uniqueInclude[0].point);
// Check locs returned in includeLocs mode.
t.remove({});
objLocs = [{x: 20, y: 30, z: ['loc1', 'loca']}, {x: 40, y: 50, z: ['loc2', 'locb']}];
t.save({loc: objLocs});
-results = db.runCommand(
- {geoNear: collName, near: [50, 50], num: 10, uniqueDocs: false, includeLocs: true})
- .results;
-assert.contains(results[0].loc, objLocs);
+results = t.aggregate({
+ $geoNear: {
+ near: [50, 50],
+ distanceField: "dis",
+ uniqueDocs: false,
+ includeLocs: "point",
+ }
+ }).toArray();
+assert.contains(results[0].point, objLocs);
// Check locs returned in includeLocs mode, where locs are arrays.
t.remove({});
arrLocs = [[20, 30], [40, 50]];
t.save({loc: arrLocs});
-results = db.runCommand(
- {geoNear: collName, near: [50, 50], num: 10, uniqueDocs: false, includeLocs: true})
- .results;
+results = t.aggregate({
+ $geoNear: {
+ near: [50, 50],
+ distanceField: "dis",
+ uniqueDocs: false,
+ includeLocs: "point",
+ }
+ }).toArray();
// The original loc arrays are returned as objects.
expectedLocs = arrLocs;
-assert.contains(results[0].loc, expectedLocs);
+assert.contains(results[0].point, expectedLocs);
// Test a large number of locations in the array.
t.drop();
diff --git a/jstests/core/geo_validate.js b/jstests/core/geo_validate.js
index 6d92e5736ce..190f7886298 100644
--- a/jstests/core/geo_validate.js
+++ b/jstests/core/geo_validate.js
@@ -77,17 +77,6 @@ assert.throws(function() {
});
//
-//
-// Make sure we can't do a near search with a negative limit
-assert.commandFailed(
- db.runCommand({geoNear: coll.getName(), near: [0, 0], spherical: true, num: -1}));
-assert.commandFailed(
- db.runCommand({geoNear: coll.getName(), near: [0, 0], spherical: true, num: -Infinity}));
-// NaN is interpreted as limit 0
-assert.commandWorked(
- db.runCommand({geoNear: coll.getName(), near: [0, 0], spherical: true, num: NaN}));
-
-//
// SERVER-17241 Polygon has no loop
assert.writeError(coll.insert({geo: {type: 'Polygon', coordinates: []}}));
diff --git a/jstests/core/geob.js b/jstests/core/geob.js
index c711a676b2b..2664d6c5921 100644
--- a/jstests/core/geob.js
+++ b/jstests/core/geob.js
@@ -1,35 +1,38 @@
-var t = db.geob;
-t.drop();
-
-var a = {p: [0, 0]};
-var b = {p: [1, 0]};
-var c = {p: [3, 4]};
-var d = {p: [0, 6]};
-
-t.save(a);
-t.save(b);
-t.save(c);
-t.save(d);
-t.ensureIndex({p: "2d"});
-
-var res = t.runCommand("geoNear", {near: [0, 0]});
-assert.close(3, res.stats.avgDistance, "A");
-
-assert.close(0, res.results[0].dis, "B1");
-assert.eq(a._id, res.results[0].obj._id, "B2");
-
-assert.close(1, res.results[1].dis, "C1");
-assert.eq(b._id, res.results[1].obj._id, "C2");
-
-assert.close(5, res.results[2].dis, "D1");
-assert.eq(c._id, res.results[2].obj._id, "D2");
-
-assert.close(6, res.results[3].dis, "E1");
-assert.eq(d._id, res.results[3].obj._id, "E2");
-
-res = t.runCommand("geoNear", {near: [0, 0], distanceMultiplier: 2});
-assert.close(6, res.stats.avgDistance, "F");
-assert.close(0, res.results[0].dis, "G");
-assert.close(2, res.results[1].dis, "H");
-assert.close(10, res.results[2].dis, "I");
-assert.close(12, res.results[3].dis, "J");
+(function() {
+ "use strict";
+ var t = db.geob;
+ t.drop();
+
+ var a = {p: [0, 0]};
+ var b = {p: [1, 0]};
+ var c = {p: [3, 4]};
+ var d = {p: [0, 6]};
+
+ t.save(a);
+ t.save(b);
+ t.save(c);
+ t.save(d);
+ t.ensureIndex({p: "2d"});
+
+ let res = t.aggregate({$geoNear: {near: [0, 0], distanceField: "dis"}}).toArray();
+
+ assert.close(0, res[0].dis, "B1");
+ assert.eq(a._id, res[0]._id, "B2");
+
+ assert.close(1, res[1].dis, "C1");
+ assert.eq(b._id, res[1]._id, "C2");
+
+ assert.close(5, res[2].dis, "D1");
+ assert.eq(c._id, res[2]._id, "D2");
+
+ assert.close(6, res[3].dis, "E1");
+ assert.eq(d._id, res[3]._id, "E2");
+
+ res = t.aggregate({
+ $geoNear: {near: [0, 0], distanceField: "dis", distanceMultiplier: 2.0}
+ }).toArray();
+ assert.close(0, res[0].dis, "G");
+ assert.close(2, res[1].dis, "H");
+ assert.close(10, res[2].dis, "I");
+ assert.close(12, res[3].dis, "J");
+}());
diff --git a/jstests/core/geod.js b/jstests/core/geod.js
index 35844d0f914..055453254a5 100644
--- a/jstests/core/geod.js
+++ b/jstests/core/geod.js
@@ -7,8 +7,11 @@ t.ensureIndex({loc: "2d"});
// should match no points in the dataset.
dists = [.49, .51, 1.0];
for (idx in dists) {
- b = db.runCommand({geoNear: "geod", near: [1, 0], num: 2, maxDistance: dists[idx]});
- assert.eq(b.errmsg, undefined, "A" + idx);
- l = b.results.length;
- assert.eq(l, idx, "B" + idx);
+ b = db.geod
+ .aggregate([
+ {$geoNear: {near: [1, 0], distanceField: "d", maxDistance: dists[idx]}},
+ {$limit: 2},
+ ])
+ .toArray();
+ assert.eq(b.length, idx, "B" + idx);
}
diff --git a/jstests/core/geonear_cmd_input_validation.js b/jstests/core/geonear_cmd_input_validation.js
index 661e2113952..9cc82cb6f25 100644
--- a/jstests/core/geonear_cmd_input_validation.js
+++ b/jstests/core/geonear_cmd_input_validation.js
@@ -23,9 +23,18 @@ indexTypes.forEach(function(indexType) {
pointDescription = (isLegacy ? "legacy coordinates" : "GeoJSON point");
function makeCommand(distance) {
- var command = {geoNear: t.getName(), near: pointType, spherical: spherical};
- command[optionName] = distance;
- return command;
+ let geoNearSpec = {
+ near: pointType,
+ distanceField: "dist",
+ spherical: spherical
+ };
+ geoNearSpec[optionName] = distance;
+
+ return {
+ aggregate: t.getName(),
+ cursor: {},
+ pipeline: [{$geoNear: geoNearSpec}],
+ };
}
// Unsupported combinations should return errors.
@@ -40,17 +49,22 @@ indexTypes.forEach(function(indexType) {
return;
}
- // This is a supported combination. No error.
- assert.commandWorked(
- db.runCommand({geoNear: t.getName(), near: pointType, spherical: spherical}));
+ // Test that there is no error when not specifying a min or max distance.
+ assert.commandWorked(db.runCommand({
+ aggregate: t.getName(),
+ cursor: {},
+ pipeline: [
+ {$geoNear: {near: pointType, distanceField: "dist", spherical: spherical}}
+ ],
+ }));
// No error with min/maxDistance 1.
- db.runCommand(makeCommand(1));
+ assert.commandWorked(db.runCommand(makeCommand(1)));
var outOfRangeDistances = [];
if (indexType == '2d') {
// maxDistance unlimited; no error.
- db.runCommand(makeCommand(1e10));
+ assert.commandWorked(db.runCommand(makeCommand(1e10)));
}
// Try several bad values for min/maxDistance.
diff --git a/jstests/core/geonear_key.js b/jstests/core/geonear_key.js
index 747b6deacd7..41fcfb0a5da 100644
--- a/jstests/core/geonear_key.js
+++ b/jstests/core/geonear_key.js
@@ -1,5 +1,5 @@
/**
- * Tests for the 'key' field accepted by the geoNear command and the $geoNear aggregation stage.
+ * Tests for the 'key' field accepted by the $geoNear aggregation stage.
*/
(function() {
"use strict";
@@ -29,36 +29,39 @@
}
/**
- * Runs the near described by 'nearParams' as both a geoNear command and a $geoNear aggregation.
- * Verifies that in both cases, the operation fails with 'code'.
+ * Runs the near described by 'nearParams' as a $geoNear aggregation and verifies that the
+ * operation fails with 'code'.
*/
function assertGeoNearFails(nearParams, code) {
- assert.commandFailedWithCode(coll.runCommand("geoNear", nearParams), code);
assert.commandFailedWithCode(runNearAgg(nearParams), code);
}
/**
- * Runs the near described by 'nearParams' as both a geoNear command and a $geoNear aggregation.
- * Verifies that in both cases, the operation succeeds and returns the _id values in
- * 'expectedIds', in order.
+ * Runs the near described by 'nearParams' as a $geoNear aggregation and verifies that the
+ * operation returns the _id values in 'expectedIds', in order.
*/
function assertGeoNearSucceedsAndReturnsIds(nearParams, expectedIds) {
- let cmdResult = assert.commandWorked(coll.runCommand("geoNear", nearParams));
let aggResult = assert.commandWorked(runNearAgg(nearParams));
+ let res = aggResult.cursor.firstBatch;
+ let errfn = () => `expected ids ${tojson(expectedIds)}, but these documents were ` +
+ `returned: ${tojson(res)}`;
+
+ assert.eq(expectedIds.length, res.length, errfn);
for (let i = 0; i < expectedIds.length; i++) {
- assert.eq(expectedIds[i], cmdResult.results[i].obj._id);
- assert.eq(expectedIds[i], aggResult.cursor.firstBatch[i]._id);
+ assert.eq(expectedIds[i], aggResult.cursor.firstBatch[i]._id, errfn);
}
}
- // Verify that the geoNear fails when the key field is not a string.
+ // Verify that $geoNear fails when the key field is not a string.
assertGeoNearFails({near: [0, 0], key: 1}, ErrorCodes.TypeMismatch);
- // Verify that the geoNear fails when the key field the empty string.
+ // Verify that $geoNear fails when the key field the empty string.
assertGeoNearFails({near: [0, 0], key: ""}, ErrorCodes.BadValue);
- // Verify that geoNear fails when there are no eligible indexes.
+ // Verify that $geoNear fails when there are no eligible indexes.
assertGeoNearFails({near: [0, 0]}, ErrorCodes.IndexNotFound);
+
+ // Verify that the query system raises an error when an index is specified that doesn't exist.
assertGeoNearFails({near: [0, 0], key: "a"}, ErrorCodes.BadValue);
// Create a number of 2d and 2dsphere indexes.
@@ -67,7 +70,7 @@
assert.commandWorked(coll.createIndex({"b.c": "2d"}));
assert.commandWorked(coll.createIndex({"b.d": "2dsphere"}));
- // Verify that geoNear fails when the index to use is ambiguous because of the absence of the
+ // Verify that $geoNear fails when the index to use is ambiguous because of the absence of the
// key field.
assertGeoNearFails({near: [0, 0]}, ErrorCodes.IndexNotFound);
@@ -85,12 +88,12 @@
assertGeoNearSucceedsAndReturnsIds(
{near: {type: "Point", coordinates: [0, 0]}, spherical: true, key: "a"}, [0, 1]);
- // Verify that geoNear fails when a GeoJSON point is used with a 'key' path that only has a 2d
+ // Verify that $geoNear fails when a GeoJSON point is used with a 'key' path that only has a 2d
// index. GeoJSON points can only be used for spherical geometry.
assertGeoNearFails({near: {type: "Point", coordinates: [0, 0]}, key: "b.c"},
ErrorCodes.BadValue);
- // Verify that geoNear fails when:
+ // Verify that $geoNear fails when:
// -- The only index available over the 'key' path is 2dsphere.
// -- spherical=false.
// -- The search point is a legacy coordinate pair.
diff --git a/jstests/core/json_schema/misc_validation.js b/jstests/core/json_schema/misc_validation.js
index ede7b55f55d..c8f5fcaf54b 100644
--- a/jstests/core/json_schema/misc_validation.js
+++ b/jstests/core/json_schema/misc_validation.js
@@ -50,18 +50,23 @@
coll.count({$jsonSchema: invalidSchema});
});
- // Test that an invalid $jsonSchema fails to parse in a geoNear command.
+ // Test that an invalid $jsonSchema fails to parse in a $geoNear query.
assert.commandWorked(coll.createIndex({geo: "2dsphere"}));
let res = testDB.runCommand({
- geoNear: coll.getName(),
- near: [30, 40],
- spherical: true,
- query: {$jsonSchema: invalidSchema}
+ aggregate: coll.getName(),
+ cursor: {},
+ pipeline: [{
+ $geoNear: {
+ near: [30, 40],
+ distanceField: "dis",
+ query: {$jsonSchema: invalidSchema},
+ }
+ }],
});
- assert.commandFailed(res);
+ assert.commandFailedWithCode(res, ErrorCodes.FailedToParse);
assert.neq(-1,
- res.errmsg.indexOf("Can't parse filter"),
- "geoNear command failed for a reason other than invalid query");
+ res.errmsg.indexOf("Unknown $jsonSchema keyword"),
+ `$geoNear failed for a reason other than invalid query: ${tojson(res)}`);
// Test that an invalid $jsonSchema fails to parse in a distinct command.
assert.throws(function() {
@@ -83,21 +88,23 @@
assert.eq(1,
coll.count({$jsonSchema: {properties: {a: {type: "number"}, b: {type: "string"}}}}));
- // Test that a valid $jsonSchema is legal in a geoNear command.
+ // Test that a valid $jsonSchema is legal in a $geoNear stage.
const point = {type: "Point", coordinates: [31.0, 41.0]};
assert.writeOK(coll.insert({geo: point, a: 1}));
assert.writeOK(coll.insert({geo: point, a: 0}));
assert.commandWorked(coll.createIndex({geo: "2dsphere"}));
- res = testDB.runCommand({
- geoNear: coll.getName(),
- near: [30, 40],
- spherical: true,
- includeLocs: true,
- query: {$jsonSchema: {properties: {a: {minimum: 1}}}}
- });
- assert.commandWorked(res);
- assert.eq(1, res.results.length);
- assert.eq(res.results[0].loc, point);
+ res = coll.aggregate({
+ $geoNear: {
+ near: [30, 40],
+ spherical: true,
+ query: {$jsonSchema: {properties: {a: {minimum: 1}}}},
+ distanceField: "dis",
+ includeLocs: "loc",
+ }
+ })
+ .toArray();
+ assert.eq(1, res.length, tojson(res));
+ assert.eq(res[0].loc, point, tojson(res));
// Test that a valid $jsonSchema is legal in a distinct command.
coll.drop();
diff --git a/jstests/core/operation_latency_histogram.js b/jstests/core/operation_latency_histogram.js
index 12885ade938..da9f14c6f8c 100644
--- a/jstests/core/operation_latency_histogram.js
+++ b/jstests/core/operation_latency_histogram.js
@@ -126,11 +126,17 @@
// TODO SERVER-24705: createIndex is not currently counted in Top.
lastHistogram = assertHistogramDiffEq(testColl, lastHistogram, 0, 0, 0);
- // GeoNear
+ // $geoNear aggregation stage
assert.commandWorked(testDB.runCommand({
- geoNear: testColl.getName(),
- near: {type: "Point", coordinates: [0, 0]},
- spherical: true
+ aggregate: testColl.getName(),
+ pipeline: [{
+ $geoNear: {
+ near: {type: "Point", coordinates: [0, 0]},
+ spherical: true,
+ distanceField: "dist",
+ }
+ }],
+ cursor: {},
}));
lastHistogram = assertHistogramDiffEq(testColl, lastHistogram, 1, 0, 0);
diff --git a/jstests/core/profile_geonear.js b/jstests/core/profile_geonear.js
deleted file mode 100644
index c17cfc84bb6..00000000000
--- a/jstests/core/profile_geonear.js
+++ /dev/null
@@ -1,57 +0,0 @@
-// @tags: [does_not_support_stepdowns, requires_profiling]
-
-// Confirms that profiled geonear execution contains all expected metrics with proper values.
-
-(function() {
- "use strict";
-
- // For getLatestProfilerEntry and getProfilerProtocolStringForCommand
- load("jstests/libs/profiler.js");
-
- var testDB = db.getSiblingDB("profile_geonear");
- assert.commandWorked(testDB.dropDatabase());
- var conn = testDB.getMongo();
- var coll = testDB.getCollection("test");
-
- testDB.setProfilingLevel(2);
-
- //
- // Confirm metrics for distinct with query.
- //
- var i;
- for (i = 0; i < 10; ++i) {
- assert.writeOK(coll.insert({a: i, loc: {type: "Point", coordinates: [i, i]}}));
- }
- assert.commandWorked(coll.createIndex({loc: "2dsphere"}));
-
- assert.commandWorked(testDB.runCommand({
- geoNear: "test",
- near: {type: "Point", coordinates: [1, 1]},
- spherical: true,
- collation: {locale: "fr"}
- }));
-
- var profileObj = getLatestProfilerEntry(testDB);
-
- assert.eq(profileObj.ns, coll.getFullName(), tojson(profileObj));
- assert.eq(profileObj.op, "command", tojson(profileObj));
- assert.eq(profileObj.keysExamined, 82, tojson(profileObj));
- assert.eq(profileObj.docsExamined, 10, tojson(profileObj));
- assert.eq(
- profileObj.planSummary, "GEO_NEAR_2DSPHERE { loc: \"2dsphere\" }", tojson(profileObj));
- assert(profileObj.hasOwnProperty("execStats"), tojson(profileObj));
- assert.eq(profileObj.protocol, getProfilerProtocolStringForCommand(conn), tojson(profileObj));
- assert.eq(coll.getName(), profileObj.command.geoNear, tojson(profileObj));
- assert.eq({locale: "fr"}, profileObj.command.collation, tojson(profileObj));
- assert(profileObj.hasOwnProperty("responseLength"), tojson(profileObj));
- assert(profileObj.hasOwnProperty("millis"), tojson(profileObj));
- assert(profileObj.hasOwnProperty("numYield"), tojson(profileObj));
- assert(profileObj.hasOwnProperty("locks"), tojson(profileObj));
- assert.eq(profileObj.appName, "MongoDB Shell", tojson(profileObj));
-
- // We cannot confirm "fromMultiPlanner" or "replanned" metrics as there can be at most one
- // valid index choice for geoNear. The reason for this is:
- // - geoNear requires at least one "2d" or "2dsphere" index
- // - geoNear requires there be at most one 2dsphere and at most one 2d index
- // - geoNear will always prefer a 2d index over a 2dsphere index if both are defined
-})();
diff --git a/jstests/core/txns/commands_not_allowed_in_txn.js b/jstests/core/txns/commands_not_allowed_in_txn.js
index a7e9586ba47..4ecd712946b 100644
--- a/jstests/core/txns/commands_not_allowed_in_txn.js
+++ b/jstests/core/txns/commands_not_allowed_in_txn.js
@@ -97,7 +97,6 @@
{eval: "function() {return 1;}"},
{"$eval": "function() {return 1;}"},
{filemd5: 1, root: "fs"},
- {geoNear: collName, near: [0, 0]},
{mapReduce: collName, map: function() {}, reduce: function(key, vals) {}, out: "out"},
{parallelCollectionScan: collName, numCursors: 1},
];
diff --git a/jstests/core/txns/statement_ids_accepted.js b/jstests/core/txns/statement_ids_accepted.js
index 35760755ac6..68d57ac46dc 100644
--- a/jstests/core/txns/statement_ids_accepted.js
+++ b/jstests/core/txns/statement_ids_accepted.js
@@ -185,7 +185,8 @@
stmtId: NumberInt(0),
autocommit: false
}));
- jsTestLog("Check that geoNear accepts a statement ID");
+
+ jsTestLog("Check that geoSearch accepts a statement ID");
assert.writeOK(testColl.insert({geo: {type: "Point", coordinates: [0, 0]}, a: 0}),
{writeConcern: {w: "majority"}});
assert.writeOK(testColl.insert({geoh: {lat: 0, long: 0}, b: 0}),
@@ -204,16 +205,6 @@
return true;
});
assert.commandWorked(sessionDb.runCommand({
- geoNear: collName,
- near: {type: "Point", coordinates: [0, 0]},
- spherical: true,
- readConcern: {level: "snapshot"},
- txnNumber: NumberLong(txnNumber++),
- stmtId: NumberInt(0)
- }));
-
- jsTestLog("Check that geoSearch accepts a statement ID");
- assert.commandWorked(sessionDb.runCommand({
geoSearch: collName,
search: {b: 0},
near: [0, 0],
diff --git a/jstests/core/views/views_all_commands.js b/jstests/core/views/views_all_commands.js
index de5c5dd1676..094c74a15cd 100644
--- a/jstests/core/views/views_all_commands.js
+++ b/jstests/core/views/views_all_commands.js
@@ -250,11 +250,6 @@
fsync: {skip: isUnrelated},
fsyncUnlock: {skip: isUnrelated},
getDatabaseVersion: {skip: isUnrelated},
- geoNear: {
- command:
- {geoNear: "view", near: {type: "Point", coordinates: [-50, 37]}, spherical: true},
- expectFailure: true
- },
geoSearch: {
command: {
geoSearch: "view",
diff --git a/jstests/libs/geo_near_random.js b/jstests/libs/geo_near_random.js
index 207ca1302d9..d5de7aa70a7 100644
--- a/jstests/libs/geo_near_random.js
+++ b/jstests/libs/geo_near_random.js
@@ -43,17 +43,17 @@ GeoNearRandomTest.prototype.insertPts = function(nPts, indexBounds, scale) {
this.t.ensureIndex({loc: '2d'}, indexBounds);
};
-GeoNearRandomTest.prototype.assertIsPrefix = function(short, long) {
+GeoNearRandomTest.prototype.assertIsPrefix = function(short, long, errmsg) {
for (var i = 0; i < short.length; i++) {
- var xS = short[i].obj ? short[i].obj.loc[0] : short[i].loc[0];
- var yS = short[i].obj ? short[i].obj.loc[1] : short[i].loc[1];
- var dS = short[i].obj ? short[i].dis : 1;
+ var xS = short[i] ? short[i].loc[0] : short[i].loc[0];
+ var yS = short[i] ? short[i].loc[1] : short[i].loc[1];
+ var dS = short[i] ? short[i].dis : 1;
- var xL = long[i].obj ? long[i].obj.loc[0] : long[i].loc[0];
- var yL = long[i].obj ? long[i].obj.loc[1] : long[i].loc[1];
- var dL = long[i].obj ? long[i].dis : 1;
+ var xL = long[i] ? long[i].loc[0] : long[i].loc[0];
+ var yL = long[i] ? long[i].loc[1] : long[i].loc[1];
+ var dL = long[i] ? long[i].dis : 1;
- assert.eq([xS, yS, dS], [xL, yL, dL]);
+ assert.eq([xS, yS, dS], [xL, yL, dL], errmsg);
}
};
@@ -66,37 +66,29 @@ GeoNearRandomTest.prototype.testPt = function(pt, opts) {
print("testing point: " + tojson(pt) + " opts: " + tojson(opts));
- var cmd = {geoNear: this.t.getName(), near: pt, num: 1, spherical: opts.sphere};
+ let query = {loc: {}};
+ query.loc[opts.sphere ? '$nearSphere' : '$near'] = pt;
+ const proj = {dis: {$meta: "geoNearDistance"}};
+ const runQuery = (limit) => this.t.find(query, proj).limit(opts.nToTest).toArray();
- var last = this.db.runCommand(cmd).results;
+ let last = runQuery(1);
for (var i = 2; i <= opts.nToTest; i++) {
- // print(i); // uncomment to watch status
- cmd.num = i;
- var ret = this.db.runCommand(cmd).results;
-
- try {
- this.assertIsPrefix(last, ret);
- } catch (e) {
- print("*** failed while compairing " + (i - 1) + " and " + i);
- printjson(cmd);
- throw e; // rethrow
- }
-
- // Make sure distances are in increasing order
- assert.gte(ret[ret.length - 1].dis, last[last.length - 1].dis);
+ let ret = runQuery(i);
+ this.assertIsPrefix(last, ret, `Unexpected result when comparing ${i-1} and ${i}`);
+ // Make sure distances are in increasing order.
+ assert.gte(ret[ret.length - 1].dis, last[last.length - 1].dis);
last = ret;
}
- last = last.map(function(x) {
- return x.obj;
- });
-
- var query = {loc: {}};
- query.loc[opts.sphere ? '$nearSphere' : '$near'] = pt;
- var near = this.t.find(query).limit(opts.nToTest).toArray();
-
- // Test that a query using $near/$nearSphere with a limit of 'nToTest' returns the same points
- // (in order) as the geoNear command with num=nToTest.
- assert.eq(last, near);
+ // Test that a query using $near or $nearSphere returns the same points in order as the $geoNear
+ // aggregation stage.
+ const queryResults = runQuery(opts.nToTest);
+ const aggResults = this.t
+ .aggregate([
+ {$geoNear: {near: pt, distanceField: "dis", spherical: opts.sphere}},
+ {$limit: opts.nToTest}
+ ])
+ .toArray();
+ assert.eq(queryResults, aggResults);
};
diff --git a/jstests/libs/override_methods/set_read_and_write_concerns.js b/jstests/libs/override_methods/set_read_and_write_concerns.js
index f3b95d68ec7..d120ce08734 100644
--- a/jstests/libs/override_methods/set_read_and_write_concerns.js
+++ b/jstests/libs/override_methods/set_read_and_write_concerns.js
@@ -26,7 +26,6 @@
"count",
"distinct",
"find",
- "geoNear",
"geoSearch",
"parallelCollectionScan",
]);
diff --git a/jstests/libs/override_methods/set_read_preference_secondary.js b/jstests/libs/override_methods/set_read_preference_secondary.js
index 5d5efedae97..58b80a5bc0c 100644
--- a/jstests/libs/override_methods/set_read_preference_secondary.js
+++ b/jstests/libs/override_methods/set_read_preference_secondary.js
@@ -14,7 +14,6 @@
"dbStats",
"distinct",
"find",
- "geoNear",
"geoSearch",
"mapReduce",
"mapreduce",
diff --git a/jstests/libs/parallelTester.js b/jstests/libs/parallelTester.js
index 2fbde0eb4f8..59f9b43f74d 100644
--- a/jstests/libs/parallelTester.js
+++ b/jstests/libs/parallelTester.js
@@ -259,7 +259,6 @@ if (typeof _threadInject != "undefined") {
parallelFilesDir + "/profile_distinct.js",
parallelFilesDir + "/profile_find.js",
parallelFilesDir + "/profile_findandmodify.js",
- parallelFilesDir + "/profile_geonear.js",
parallelFilesDir + "/profile_getmore.js",
parallelFilesDir + "/profile_insert.js",
parallelFilesDir + "/profile_list_collections.js",
diff --git a/jstests/noPassthrough/commands_handle_kill.js b/jstests/noPassthrough/commands_handle_kill.js
index f03e40036a1..59470f63620 100644
--- a/jstests/noPassthrough/commands_handle_kill.js
+++ b/jstests/noPassthrough/commands_handle_kill.js
@@ -179,10 +179,21 @@ if (${ canYield }) {
{findAndModify: collName, filter: {fakeField: {$gt: 0}}, update: {$inc: {a: 1}}});
assertCommandPropogatesPlanExecutorKillReason(
- {geoNear: collName, near: {type: "Point", coordinates: [0, 0]}, spherical: true}, {
- customSetup: function() {
- assert.commandWorked(coll.createIndex({geoField: "2dsphere"}));
- }
+ {
+ aggregate: collName,
+ cursor: {},
+ pipeline: [{
+ $geoNear: {
+ near: {type: "Point", coordinates: [0, 0]},
+ spherical: true,
+ distanceField: "dis"
+ }
+ }]
+ },
+ {
+ customSetup: function() {
+ assert.commandWorked(coll.createIndex({geoField: "2dsphere"}));
+ }
});
assertCommandPropogatesPlanExecutorKillReason({find: coll.getName(), filter: {}});
diff --git a/jstests/noPassthrough/commands_preserve_exec_error_code.js b/jstests/noPassthrough/commands_preserve_exec_error_code.js
index 38e12da2740..373461ad4f0 100644
--- a/jstests/noPassthrough/commands_preserve_exec_error_code.js
+++ b/jstests/noPassthrough/commands_preserve_exec_error_code.js
@@ -37,8 +37,9 @@
assertFailsWithInternalError(() => coll.deleteOne({_id: 1}));
assertFailsWithInternalError(() => coll.count({_id: 1}));
assertFailsWithInternalError(() => coll.aggregate([]).itcount());
+ assertFailsWithInternalError(
+ () => coll.aggregate([{$geoNear: {near: [0, 0], distanceField: "d"}}]).itcount());
assertCmdFailsWithInternalError({distinct: coll.getName(), key: "_id"});
- assertCmdFailsWithInternalError({geoNear: coll.getName(), near: [0, 0]});
assertCmdFailsWithInternalError(
{findAndModify: coll.getName(), query: {_id: 1}, update: {$set: {x: 2}}});
diff --git a/jstests/noPassthrough/currentop_query.js b/jstests/noPassthrough/currentop_query.js
index 1eb1655f035..dfa7ae80fee 100644
--- a/jstests/noPassthrough/currentop_query.js
+++ b/jstests/noPassthrough/currentop_query.js
@@ -337,30 +337,39 @@
}
//
- // Confirm currentOp content for geoNear.
+ // Confirm currentOp content for the $geoNear aggregation stage.
//
dropAndRecreateTestCollection();
for (let i = 0; i < 10; ++i) {
- assert.writeOK(coll.insert({a: i, loc: {type: "Point", coordinates: [i, i]}}));
+ assert.commandWorked(
+ coll.insert({a: i, loc: {type: "Point", coordinates: [i, i]}}));
}
assert.commandWorked(coll.createIndex({loc: "2dsphere"}));
-
confirmCurrentOpContents({
test: function(db) {
assert.commandWorked(db.runCommand({
- geoNear: "currentop_query",
- near: {type: "Point", coordinates: [1, 1]},
- spherical: true,
- query: {$comment: "currentop_query"},
- collation: {locale: "fr"}
+ aggregate: "currentop_query",
+ cursor: {},
+ pipeline: [{
+ $geoNear: {
+ near: {type: "Point", coordinates: [1, 1]},
+ distanceField: "dist",
+ spherical: true,
+ query: {$comment: "currentop_query"},
+ }
+ }],
+ collation: {locale: "fr"},
+ comment: "currentop_query",
}));
},
- command: "geoNear",
planSummary: "GEO_NEAR_2DSPHERE { loc: \"2dsphere\" }",
- currentOpFilter: {
- "command.query.$comment": "currentop_query",
- "command.collation": {locale: "fr"}
- }
+ currentOpFilter: commandOrOriginatingCommand({
+ "aggregate": {$exists: true},
+ "pipeline.0.$geoNear.query.$comment": "currentop_query",
+ "collation": {locale: "fr"},
+ "comment": "currentop_query",
+ },
+ isRemoteShardCurOp)
});
//
diff --git a/jstests/noPassthrough/geo_full.js b/jstests/noPassthrough/geo_full.js
index 6486679ea4e..b4622f46527 100644
--- a/jstests/noPassthrough/geo_full.js
+++ b/jstests/noPassthrough/geo_full.js
@@ -500,8 +500,6 @@
"poly.docIn": randYesQuery()
}).count());
- var defaultDocLimit = 100;
-
// $near
print("Near query...");
assert.eq(
@@ -523,60 +521,38 @@
results.sphere.locsIn);
}
- // geoNear
- // results limited by size of objects
- if (data.maxLocs < defaultDocLimit) {
- // GeoNear query
- print("GeoNear query...");
- // GeoNear command has a default doc limit 100.
- assert.eq(
- Math.min(defaultDocLimit, results.center.docsIn),
- t.getDB()
- .runCommand(
- {geoNear: "testAllGeo", near: query.center, maxDistance: query.radius})
- .results.length,
- "GeoNear query: center: " + query.center + "; radius: " + query.radius +
- "; docs: " + results.center.docsIn + "; locs: " + results.center.locsIn);
-
- var num = Math.min(2 * defaultDocLimit, 2 * results.center.docsIn);
-
- var output = db.runCommand({
- geoNear: "testAllGeo",
- near: query.center,
- maxDistance: query.radius,
- includeLocs: true,
- num: num
- }).results;
-
- assert.eq(Math.min(num, results.center.docsIn),
- output.length,
- "GeoNear query with limit of " + num + ": center: " + query.center +
- "; radius: " + query.radius + "; docs: " + results.center.docsIn +
- "; locs: " + results.center.locsIn);
-
- var distance = 0;
+ // $geoNear aggregation stage.
+ const aggregationLimit = 2 * results.center.docsIn;
+ if (aggregationLimit > 0) {
+ var output = t.aggregate([
+ {
+ $geoNear: {
+ near: query.center,
+ maxDistance: query.radius,
+ includeLocs: "pt",
+ distanceField: "dis",
+ }
+ },
+ {$limit: aggregationLimit}
+ ]).toArray();
+
+ const errmsg = {
+ limit: aggregationLimit,
+ center: query.center,
+ radius: query.radius,
+ docs: results.center.docsIn,
+ locs: results.center.locsIn,
+ actualResult: output
+ };
+ assert.eq(results.center.docsIn, output.length, tojson(errmsg));
+
+ let lastDistance = 0;
for (var i = 0; i < output.length; i++) {
var retDistance = output[i].dis;
- var retLoc = locArray(output[i].loc);
-
- var arrLocs = locsArray(output[i].obj.locs);
-
- assert.contains(retLoc, arrLocs);
-
- var distInObj = false;
- for (var j = 0; j < arrLocs.length && distInObj == false; j++) {
- var newDistance = Geo.distance(locArray(query.center), arrLocs[j]);
- distInObj = (newDistance >= retDistance - 0.0001 &&
- newDistance <= retDistance + 0.0001);
- }
-
- assert(distInObj);
- assert.between(retDistance - 0.0001,
- Geo.distance(locArray(query.center), retLoc),
- retDistance + 0.0001);
+ assert.close(retDistance, Geo.distance(locArray(query.center), output[i].pt));
assert.lte(retDistance, query.radius);
- assert.gte(retDistance, distance);
- distance = retDistance;
+ assert.gte(retDistance, lastDistance);
+ lastDistance = retDistance;
}
}
diff --git a/jstests/noPassthrough/global_operation_latency_histogram.js b/jstests/noPassthrough/global_operation_latency_histogram.js
index 3e5b4bcd8c8..d6ed2cc3c6e 100644
--- a/jstests/noPassthrough/global_operation_latency_histogram.js
+++ b/jstests/noPassthrough/global_operation_latency_histogram.js
@@ -102,11 +102,17 @@
assert.commandWorked(testColl.createIndex({pt: "2dsphere"}));
lastHistogram = checkHistogramDiff(0, 0, 1);
- // GeoNear
+ // $geoNear aggregation stage
assert.commandWorked(testDB.runCommand({
- geoNear: testColl.getName(),
- near: {type: "Point", coordinates: [0, 0]},
- spherical: true
+ aggregate: testColl.getName(),
+ pipeline: [{
+ $geoNear: {
+ near: {type: "Point", coordinates: [0, 0]},
+ spherical: true,
+ distanceField: "dist",
+ }
+ }],
+ cursor: {},
}));
lastHistogram = checkHistogramDiff(1, 0, 0);
diff --git a/jstests/noPassthrough/log_format_slowms_samplerate_loglevel.js b/jstests/noPassthrough/log_format_slowms_samplerate_loglevel.js
index 82893ddd1a2..3d0aa0dfbf3 100644
--- a/jstests/noPassthrough/log_format_slowms_samplerate_loglevel.js
+++ b/jstests/noPassthrough/log_format_slowms_samplerate_loglevel.js
@@ -320,25 +320,6 @@
keysDeleted: 1
})
},
- {
- test: function(db) {
- assert.commandWorked(db.runCommand({
- geoNear: coll.getName(),
- near: {type: "Point", coordinates: [1, 1]},
- spherical: true,
- query: {$comment: logFormatTestComment},
- collation: {locale: "fr"}
- }));
- },
- logFields: {
- command: "geoNear",
- geoNear: coll.getName(),
- near: {type: "Point", coordinates: [1, 1]},
- planSummary: "GEO_NEAR_2DSPHERE { loc: \"2dsphere\" }",
- query: {$comment: logFormatTestComment},
- collation: {locale: "fr"}
- }
- }
];
// Confirm log contains collation for find command.
diff --git a/jstests/noPassthrough/readConcern_snapshot.js b/jstests/noPassthrough/readConcern_snapshot.js
index 7927593c911..8878e596987 100644
--- a/jstests/noPassthrough/readConcern_snapshot.js
+++ b/jstests/noPassthrough/readConcern_snapshot.js
@@ -158,18 +158,5 @@
assert.commandWorked(session.abortTransaction_forTesting());
session.endSession();
- // TODO: SERVER-34113 Remove this test when we completely remove snapshot
- // reads since this command is not supported with transaction api.
- // readConcern 'snapshot' is supported by geoNear.
- session = rst.getPrimary().getDB(dbName).getMongo().startSession({causalConsistency: false});
- sessionDb = session.getDatabase(dbName);
- assert.commandWorked(sessionDb.runCommand({
- geoNear: collName,
- near: [0, 0],
- readConcern: {level: "snapshot"},
- txnNumber: NumberLong(0),
- }));
-
- session.endSession();
rst.stopSet();
}());
diff --git a/jstests/noPassthrough/readConcern_snapshot_mongos.js b/jstests/noPassthrough/readConcern_snapshot_mongos.js
index 2d780eec4d5..61e7404a4ca 100644
--- a/jstests/noPassthrough/readConcern_snapshot_mongos.js
+++ b/jstests/noPassthrough/readConcern_snapshot_mongos.js
@@ -143,14 +143,5 @@
}),
ErrorCodes.InvalidOptions);
- // TODO SERVER-33712: Add snapshot support for geoNear on mongos.
- assert.commandFailedWithCode(sessionDb.runCommand({
- geoNear: collName,
- near: [0, 0],
- readConcern: {level: "snapshot"},
- txnNumber: NumberLong(txnNumber++)
- }),
- ErrorCodes.InvalidOptions);
-
st.stop();
}());
diff --git a/jstests/noPassthrough/read_concern_snapshot_yielding.js b/jstests/noPassthrough/read_concern_snapshot_yielding.js
index a88ddb5fe05..2a44a57d750 100644
--- a/jstests/noPassthrough/read_concern_snapshot_yielding.js
+++ b/jstests/noPassthrough/read_concern_snapshot_yielding.js
@@ -242,22 +242,6 @@
assert.eq(res.cursor.firstBatch.length, TestData.numDocs, tojson(res));
}, {"command.pipeline": [{$match: {x: 1}}]});
- // TODO: SERVER-34113 Remove this test when we completely remove snapshot
- // reads since this command is not supported with transaction api.
- // Test geoNear.
- testCommand(function() {
- const sessionId = db.getMongo().startSession({causalConsistency: false}).getSessionId();
- const res = assert.commandWorked(db.runCommand({
- geoNear: "coll",
- near: [0, 0],
- readConcern: {level: "snapshot"},
- lsid: sessionId,
- txnNumber: NumberLong(0)
- }));
- assert(res.hasOwnProperty("results"));
- assert.eq(res.results.length, TestData.numDocs, tojson(res));
- }, {"command.geoNear": "coll"});
-
// Test getMore with an initial find batchSize of 0. Interrupt behavior of a getMore is not
// expected to change with a change of batchSize in the originating command.
testCommand(function() {
diff --git a/jstests/noPassthrough/read_majority_reads.js b/jstests/noPassthrough/read_majority_reads.js
index eec0ddc80cb..c10a86eac41 100644
--- a/jstests/noPassthrough/read_majority_reads.js
+++ b/jstests/noPassthrough/read_majority_reads.js
@@ -7,7 +7,6 @@
* - distinct
* - count
* - parallelCollectionScan
- * - geoNear
* - geoSearch
*
* Each operation is tested on a single node, and (if supported) through mongos on both sharded and
@@ -55,6 +54,13 @@
'aggregate',
{readConcern: {level: 'majority'}, cursor: {batchSize: 0}, pipeline: []})));
},
+ aggregateGeoNear: function(coll) {
+ return makeCursor(coll.getDB(), assert.commandWorked(coll.runCommand('aggregate', {
+ readConcern: {level: 'majority'},
+ cursor: {batchSize: 0},
+ pipeline: [{$geoNear: {near: [0, 0], distanceField: "d", spherical: true}}]
+ })));
+ },
parallelCollectionScan: function(coll) {
var res = coll.runCommand('parallelCollectionScan',
{readConcern: {level: 'majority'}, numCursors: 1});
@@ -101,20 +107,6 @@
expectedBefore: 'before',
expectedAfter: 'after',
},
- geoNear: {
- run: function(coll) {
- var res = coll.runCommand('geoNear', {
- readConcern: {level: 'majority'},
- near: [0, 0],
- spherical: true,
- });
- assert.commandWorked(res);
- assert.eq(res.results.length, 1, tojson(res));
- return res.results[0].obj.state;
- },
- expectedBefore: 'before',
- expectedAfter: 'after',
- },
geoSearch: {
run: function(coll) {
var res = coll.runCommand('geoSearch', {
@@ -140,20 +132,21 @@
assert.commandWorked(mongodConnection.adminCommand({"setCommittedSnapshot": snapshot}));
}
+ assert.commandWorked(coll.createIndex({point: '2dsphere'}));
for (var testName in cursorTestCases) {
jsTestLog('Running ' + testName + ' against ' + coll.toString());
var getCursor = cursorTestCases[testName];
// Setup initial state.
assert.writeOK(coll.remove({}));
- assert.writeOK(coll.save({_id: 1, state: 'before'}));
+ assert.writeOK(coll.save({_id: 1, state: 'before', point: [0, 0]}));
setCommittedSnapshot(makeSnapshot());
// Check initial conditions.
assert.eq(getCursor(coll).next().state, 'before');
// Change state without making it committed.
- assert.writeOK(coll.save({_id: 1, state: 'after'}));
+ assert.writeOK(coll.save({_id: 1, state: 'after', point: [0, 0]}));
// Cursor still sees old state.
assert.eq(getCursor(coll).next().state, 'before');
@@ -171,7 +164,6 @@
assert.eq(oldCursor.next().state, 'after');
}
- assert.commandWorked(coll.ensureIndex({point: '2dsphere'}));
assert.commandWorked(coll.ensureIndex({point: 'geoHaystack', _id: 1}, {bucketSize: 1}));
for (var testName in nonCursorTestCases) {
jsTestLog('Running ' + testName + ' against ' + coll.toString());
diff --git a/jstests/noPassthrough/shell_can_use_read_concern.js b/jstests/noPassthrough/shell_can_use_read_concern.js
index 5a3134af9cb..dd9f7ed6ec1 100644
--- a/jstests/noPassthrough/shell_can_use_read_concern.js
+++ b/jstests/noPassthrough/shell_can_use_read_concern.js
@@ -226,22 +226,6 @@
});
//
- // Tests for the "geoNear" command.
- //
-
- testCommandCanBeCausallyConsistent(function() {
- assert.commandWorked(coll.createIndex({loc: "2dsphere"}));
- }, {expectedSession: withSession, expectedAfterClusterTime: false});
-
- testCommandCanBeCausallyConsistent(function() {
- assert.commandWorked(db.runCommand({
- geoNear: coll.getName(),
- near: {type: "Point", coordinates: [0, 0]},
- spherical: true
- }));
- });
-
- //
// Tests for the "geoSearch" command.
//
diff --git a/jstests/noPassthroughWithMongod/geo_axis_aligned.js b/jstests/noPassthroughWithMongod/geo_axis_aligned.js
index 5e08a6c1739..0849b6b7acc 100644
--- a/jstests/noPassthroughWithMongod/geo_axis_aligned.js
+++ b/jstests/noPassthroughWithMongod/geo_axis_aligned.js
@@ -89,9 +89,14 @@ for (var b = 0; b < bits.length; b++) {
print(" DOING DIST QUERY ");
- a = db.runCommand({geoNear: "axisaligned", near: center[j], maxDistance: radius[i]})
- .results;
- assert.eq(5, a.length);
+ a = t.aggregate({
+ $geoNear: {
+ near: center[j],
+ distanceField: "dis",
+ maxDistance: radius[i],
+ }
+ }).toArray();
+ assert.eq(5, a.length, tojson(a));
var distance = 0;
for (var k = 0; k < a.length; k++) {
diff --git a/jstests/readonly/geo.js b/jstests/readonly/geo.js
index 13705af0408..2ba43f597b4 100644
--- a/jstests/readonly/geo.js
+++ b/jstests/readonly/geo.js
@@ -30,14 +30,19 @@ runReadOnlyTest(function() {
writableCollection.insertMany(locDocs);
},
exec: function(readableCollection) {
- var res = readableCollection.runCommand({
- geoNear: readableCollection.getName(),
- near: {type: "Point", coordinates: [40.7211404, -73.9591494]},
- spherical: true,
- limit: 1
- });
- assert.commandWorked(res);
- assert.eq(res.results[0].obj.name, "The Counting Room", printjson(res));
+ const res = readableCollection
+ .aggregate([
+ {
+ $geoNear: {
+ near: {type: "Point", coordinates: [40.7211404, -73.9591494]},
+ distanceField: "dist",
+ spherical: true,
+ }
+ },
+ {$limit: 1}
+ ])
+ .toArray();
+ assert.eq(res[0].name, "The Counting Room", printjson(res));
}
};
}());
diff --git a/jstests/sharding/causal_consistency_shell_support.js b/jstests/sharding/causal_consistency_shell_support.js
index 2ac39c6b66b..5dd66ffc771 100644
--- a/jstests/sharding/causal_consistency_shell_support.js
+++ b/jstests/sharding/causal_consistency_shell_support.js
@@ -156,15 +156,24 @@
runCommandAndCheckLogicalTimes(findCmd, testDB, false);
commandReturnsExpectedResult(findCmd, testDB, findCallback);
- // GeoNear command.
+ // Aggregate command with $geoNear.
let geoNearColl = "geoNearColl";
let geoNearCmd = {
- geoNear: geoNearColl,
- near: {type: "Point", coordinates: [-10, 10]},
- spherical: true
+ aggregate: geoNearColl,
+ cursor: {},
+ pipeline: [
+ {
+ $geoNear: {
+ near: {type: "Point", coordinates: [-10, 10]},
+ distanceField: "dist",
+ spherical: true
+ }
+ },
+ ],
};
let geoNearCallback = function(res) {
- assert.eq(res.results[0].obj, {_id: 1, loc: {type: "Point", coordinates: [-10, 10]}});
+ assert.eq(res.cursor.firstBatch,
+ [{_id: 1, loc: {type: "Point", coordinates: [-10, 10]}, dist: 0}]);
};
assert.commandWorked(testDB[geoNearColl].createIndex({loc: "2dsphere"}));
diff --git a/jstests/sharding/collation_targeting.js b/jstests/sharding/collation_targeting.js
index 21740339d0f..fc2b9c193eb 100644
--- a/jstests/sharding/collation_targeting.js
+++ b/jstests/sharding/collation_targeting.js
@@ -13,7 +13,7 @@
assert.commandWorked(testDB.adminCommand({enableSharding: testDB.getName()}));
st.ensurePrimaryShard(testDB.getName(), st.shard1.shardName);
- // Create a collection sharded on {a: 1}. Add 2dsphere index to test geoNear.
+ // Create a collection sharded on {a: 1}. Add 2dsphere index to test $geoNear.
var coll = testDB.getCollection("simple_collation");
coll.drop();
assert.commandWorked(coll.createIndex({a: 1}));
@@ -38,7 +38,7 @@
// st.shard0.shardName: {a: 1}
// st.shard1.shardName: {a: 100}, {a: "FOO"}
// shard0002: {a: "foo"}
- // Include geo field to test geoNear.
+ // Include geo field to test $geoNear.
var a_1 = {_id: 0, a: 1, geo: {type: "Point", coordinates: [0, 0]}};
var a_100 = {_id: 1, a: 100, geo: {type: "Point", coordinates: [0, 0]}};
var a_FOO = {_id: 2, a: "FOO", geo: {type: "Point", coordinates: [0, 0]}};
@@ -70,6 +70,46 @@
assert.commandWorked(explain);
assert.eq(1, Object.keys(explain.shards).length);
+ // Aggregate with $geoNear.
+ const geoJSONPoint = {type: "Point", coordinates: [0, 0]};
+
+ // Test $geoNear with a query on strings with a non-simple collation. This should
+ // scatter-gather.
+ const geoNearStageStringQuery = [{
+ $geoNear: {
+ near: geoJSONPoint,
+ distanceField: "dist",
+ spherical: true,
+ query: {a: "foo"},
+ }
+ }];
+ assert.eq(2, coll.aggregate(geoNearStageStringQuery, {collation: caseInsensitive}).itcount());
+ explain = coll.explain().aggregate(geoNearStageStringQuery, {collation: caseInsensitive});
+ assert.commandWorked(explain);
+ assert.eq(3, Object.keys(explain.shards).length);
+
+ // Test $geoNear with a query on strings with a simple collation. This should be single-shard.
+ assert.eq(1, coll.aggregate(geoNearStageStringQuery).itcount());
+ explain = coll.explain().aggregate(geoNearStageStringQuery);
+ assert.commandWorked(explain);
+ assert.eq(1, Object.keys(explain.shards).length);
+
+ // Test a $geoNear with a query on numbers with a non-simple collation. This should be
+ // single-shard.
+ const geoNearStageNumericalQuery = [{
+ $geoNear: {
+ near: geoJSONPoint,
+ distanceField: "dist",
+ spherical: true,
+ query: {a: 100},
+ }
+ }];
+ assert.eq(1,
+ coll.aggregate(geoNearStageNumericalQuery, {collation: caseInsensitive}).itcount());
+ explain = coll.explain().aggregate(geoNearStageNumericalQuery, {collation: caseInsensitive});
+ assert.commandWorked(explain);
+ assert.eq(1, Object.keys(explain.shards).length);
+
// Count.
// Test a count command on strings with a non-simple collation. This should be scatter-gather.
@@ -168,31 +208,6 @@
assert.commandWorked(explain);
assert.eq(1, explain.queryPlanner.winningPlan.shards.length);
- // GeoNear.
-
- // Test geoNear on strings with a non-simple collation.
- assert.eq(2,
- assert
- .commandWorked(testDB.runCommand({
- geoNear: coll.getName(),
- near: {type: "Point", coordinates: [0, 0]},
- spherical: true,
- query: {a: "foo"},
- collation: caseInsensitive
- }))
- .results.length);
-
- // Test geoNear on strings with a simple collation.
- assert.eq(1,
- assert
- .commandWorked(testDB.runCommand({
- geoNear: coll.getName(),
- near: {type: "Point", coordinates: [0, 0]},
- spherical: true,
- query: {a: "foo"}
- }))
- .results.length);
-
// MapReduce.
// Test mapReduce on strings with a non-simple collation.
@@ -444,4 +459,4 @@
}
st.stop();
-})(); \ No newline at end of file
+})();
diff --git a/jstests/sharding/collation_targeting_inherited.js b/jstests/sharding/collation_targeting_inherited.js
index b9e88059dc1..4c68e23fbc7 100644
--- a/jstests/sharding/collation_targeting_inherited.js
+++ b/jstests/sharding/collation_targeting_inherited.js
@@ -80,6 +80,49 @@
assert.commandWorked(explain);
assert.eq(1, Object.keys(explain.shards).length);
+ // Aggregate with $geoNear.
+ const geoJSONPoint = {type: "Point", coordinates: [0, 0]};
+
+ // Test $geoNear with a query on strings with a non-simple collation inherited from the
+ // collection default. This should scatter-gather.
+ const geoNearStageStringQuery = [{
+ $geoNear: {
+ near: geoJSONPoint,
+ distanceField: "dist",
+ spherical: true,
+ query: {a: "foo"},
+ }
+ }];
+ assert.eq(2, collCaseInsensitive.aggregate(geoNearStageStringQuery).itcount());
+ explain = collCaseInsensitive.explain().aggregate(geoNearStageStringQuery);
+ assert.commandWorked(explain);
+ assert.eq(3, Object.keys(explain.shards).length);
+
+ // Test $geoNear with a query on strings with a simple collation. This should be single-shard.
+ assert.eq(
+ 1,
+ collCaseInsensitive.aggregate(geoNearStageStringQuery, {collation: {locale: "simple"}})
+ .itcount());
+ explain = collCaseInsensitive.explain().aggregate(geoNearStageStringQuery,
+ {collation: {locale: "simple"}});
+ assert.commandWorked(explain);
+ assert.eq(1, Object.keys(explain.shards).length);
+
+ // Test a $geoNear with a query on numbers with a non-simple collation inherited from the
+ // collection default. This should be single-shard.
+ const geoNearStageNumericalQuery = [{
+ $geoNear: {
+ near: geoJSONPoint,
+ distanceField: "dist",
+ spherical: true,
+ query: {a: 100},
+ }
+ }];
+ assert.eq(1, collCaseInsensitive.aggregate(geoNearStageNumericalQuery).itcount());
+ explain = collCaseInsensitive.explain().aggregate(geoNearStageNumericalQuery);
+ assert.commandWorked(explain);
+ assert.eq(1, Object.keys(explain.shards).length);
+
// Count.
// Test a count command on strings with a non-simple collation inherited from the collection
@@ -184,31 +227,6 @@
assert.commandWorked(explain);
assert.eq(1, explain.queryPlanner.winningPlan.shards.length);
- // GeoNear.
-
- // Test geoNear on strings with a non-simple collation inherited from collection default.
- assert.eq(2,
- assert
- .commandWorked(testDB.runCommand({
- geoNear: collCaseInsensitive.getName(),
- near: {type: "Point", coordinates: [0, 0]},
- spherical: true,
- query: {a: "foo"}
- }))
- .results.length);
-
- // Test geoNear on strings with a simple collation.
- assert.eq(1,
- assert
- .commandWorked(testDB.runCommand({
- geoNear: collCaseInsensitive.getName(),
- near: {type: "Point", coordinates: [0, 0]},
- spherical: true,
- query: {a: "foo"},
- collation: {locale: "simple"}
- }))
- .results.length);
-
// MapReduce.
// Test mapReduce on strings with a non-simple collation inherited from collection default.
@@ -461,4 +479,4 @@
assert.eq(1, explain.queryPlanner.winningPlan.shards.length);
st.stop();
-})(); \ No newline at end of file
+})();
diff --git a/jstests/sharding/database_and_shard_versioning_all_commands.js b/jstests/sharding/database_and_shard_versioning_all_commands.js
index 5ffc2abb8d3..37f3ef5fbd0 100644
--- a/jstests/sharding/database_and_shard_versioning_all_commands.js
+++ b/jstests/sharding/database_and_shard_versioning_all_commands.js
@@ -227,21 +227,6 @@
},
flushRouterConfig: {skip: "executes locally on mongos (not sent to any remote node)"},
fsync: {skip: "broadcast to all shards"},
- geoNear: {
- sendsDbVersion: true,
- sendsShardVersion: true,
- setUp: function(mongosConn) {
- // Expects the collection to exist with a geo index, and does not implicitly create
- // the collection or index.
- assert.commandWorked(mongosConn.getCollection(ns).runCommand(
- {createIndexes: collName, indexes: [{key: {loc: "2d"}, name: "loc_2d"}]}));
- assert.writeOK(mongosConn.getCollection(ns).insert({x: 1, loc: [1, 1]}));
- },
- command: {geoNear: collName, near: [1, 1]},
- cleanUp: function(mongosConn) {
- assert(mongosConn.getDB(dbName).getCollection(collName).drop());
- }
- },
getCmdLineOpts: {skip: "executes locally on mongos (not sent to any remote node)"},
getDiagnosticData: {skip: "executes locally on mongos (not sent to any remote node)"},
getLastError: {skip: "does not forward command to primary shard"},
diff --git a/jstests/sharding/geo_near_sharded.js b/jstests/sharding/geo_near_sharded.js
index f297b789747..361468cec18 100644
--- a/jstests/sharding/geo_near_sharded.js
+++ b/jstests/sharding/geo_near_sharded.js
@@ -41,9 +41,20 @@
assert.commandWorked(db[coll].ensureIndex({loc: indexType}));
- assert.commandWorked(
- db.runCommand({geoNear: coll, near: [0, 0], spherical: true, includeLocs: true}),
- tojson({sharded: sharded, indexType: indexType}));
+ let res = assert.commandWorked(db.runCommand({
+ aggregate: coll,
+ cursor: {},
+ pipeline: [{
+ $geoNear: {
+ near: [0, 0],
+ spherical: true,
+ includeLocs: "match",
+ distanceField: "dist",
+ }
+ }]
+ }),
+ tojson({sharded: sharded, indexType: indexType}));
+ assert.gt(res.cursor.firstBatch.length, 0, tojson(res));
}
// TODO: SERVER-33954 Remove shardAsReplicaSet: false
diff --git a/jstests/sharding/read_pref_cmd.js b/jstests/sharding/read_pref_cmd.js
index 2c3e3e2f95e..0576d71231a 100644
--- a/jstests/sharding/read_pref_cmd.js
+++ b/jstests/sharding/read_pref_cmd.js
@@ -164,7 +164,6 @@ var testReadPreference = function(conn, hostList, isMongos, mode, tagSets, secEx
testDB.user.ensureIndex({loc: '2d'});
testDB.user.ensureIndex({position: 'geoHaystack', type: 1}, {bucketSize: 10});
testDB.runCommand({getLastError: 1, w: NODE_COUNT});
- cmdTest({geoNear: 'user', near: [1, 1]}, true, formatProfileQuery({geoNear: 'user'}));
// Mongos doesn't implement geoSearch; test it only with ReplicaSetConnection.
if (!isMongos) {
diff --git a/jstests/sharding/safe_secondary_reads_drop_recreate.js b/jstests/sharding/safe_secondary_reads_drop_recreate.js
index d8f4f7fe8d2..d8a336073c3 100644
--- a/jstests/sharding/safe_secondary_reads_drop_recreate.js
+++ b/jstests/sharding/safe_secondary_reads_drop_recreate.js
@@ -178,7 +178,11 @@
{createIndexes: coll, indexes: [{key: {loc: "2d"}, name: "loc_2d"}]}));
assert.writeOK(mongosConn.getCollection(nss).insert({x: 1, loc: [1, 1]}));
},
- command: {geoNear: coll, near: [1, 1]},
+ command: {
+ aggregate: coll,
+ cursor: {},
+ pipeline: [{$geoNear: {near: [1, 1], distanceField: "d"}}]
+ },
checkResults: function(res) {
// The command should fail on the new collection, because the geo index was dropped.
assert.commandFailed(res);
diff --git a/jstests/sharding/safe_secondary_reads_single_migration_suspend_range_deletion.js b/jstests/sharding/safe_secondary_reads_single_migration_suspend_range_deletion.js
index 13605561e74..a205a0ca8a1 100644
--- a/jstests/sharding/safe_secondary_reads_single_migration_suspend_range_deletion.js
+++ b/jstests/sharding/safe_secondary_reads_single_migration_suspend_range_deletion.js
@@ -202,29 +202,6 @@
forceerror: {skip: "does not return user data"},
fsync: {skip: "does not return user data"},
fsyncUnlock: {skip: "does not return user data"},
- geoNear: {
- setUp: function(mongosConn) {
- assert.commandWorked(mongosConn.getCollection(nss).runCommand(
- {createIndexes: coll, indexes: [{key: {loc: "2d"}, name: "loc_2d"}]}));
- assert.writeOK(mongosConn.getCollection(nss).insert({x: 1, loc: [1, 1]}));
- },
- command: {geoNear: coll, near: [1, 1]},
- checkResults: function(res) {
- // The command should work and return orphaned results, because it doesn't do
- // filtering and also because the collection is sharded, it will get broadcast to
- // both shards.
- assert.commandWorked(res);
- assert.eq(2, res.results.length, tojson(res));
- },
- checkAvailableReadConcernResults: function(res) {
- // The command should work and return orphaned results, because it doesn't do
- // filtering. The expected result is 1, because the stale mongos assumes the
- // collection is still on only 1 shard and will not broadcast it to both.
- assert.commandWorked(res);
- assert.eq(1, res.results.length, tojson(res));
- },
- behavior: "versioned"
- },
geoSearch: {skip: "not supported in mongos"},
getCmdLineOpts: {skip: "does not return user data"},
getDiagnosticData: {skip: "does not return user data"},
diff --git a/jstests/sharding/safe_secondary_reads_single_migration_waitForDelete.js b/jstests/sharding/safe_secondary_reads_single_migration_waitForDelete.js
index 85f236dbae1..c748456fb1b 100644
--- a/jstests/sharding/safe_secondary_reads_single_migration_waitForDelete.js
+++ b/jstests/sharding/safe_secondary_reads_single_migration_waitForDelete.js
@@ -175,20 +175,6 @@
forceerror: {skip: "does not return user data"},
fsync: {skip: "does not return user data"},
fsyncUnlock: {skip: "does not return user data"},
- geoNear: {
- setUp: function(mongosConn) {
- assert.commandWorked(mongosConn.getCollection(nss).runCommand(
- {createIndexes: coll, indexes: [{key: {loc: "2d"}, name: "loc_2d"}]}));
- assert.writeOK(mongosConn.getCollection(nss).insert({x: 1, loc: [1, 1]}));
- },
- command: {geoNear: coll, near: [1, 1]},
- checkResults: function(res) {
- // The command should work and return correct results due to rerouting
- assert.commandWorked(res);
- assert.eq(1, res.results.length, tojson(res));
- },
- behavior: "versioned"
- },
geoSearch: {skip: "not supported in mongos"},
getCmdLineOpts: {skip: "does not return user data"},
getDiagnosticData: {skip: "does not return user data"},
diff --git a/jstests/sharding/shard7.js b/jstests/sharding/shard7.js
index 5ec1801b7c4..20122b60e24 100644
--- a/jstests/sharding/shard7.js
+++ b/jstests/sharding/shard7.js
@@ -49,8 +49,8 @@ assert.eq(0, c.count({c: 1}));
c.ensureIndex({loc: '2d'});
c.save({a: 2, b: 2, loc: [0, 0]});
-near = db.runCommand({geoNear: 'foo', near: [0, 0], query: unsatisfiable});
-assert.commandWorked(near);
-assert.eq(0, near.results.length);
+near =
+ c.aggregate({$geoNear: {near: [0, 0], query: unsatisfiable, distanceField: "dist"}}).toArray();
+assert.eq(0, near.length, tojson(near));
s.stop();
diff --git a/src/mongo/db/SConscript b/src/mongo/db/SConscript
index d329ef5b1ea..736619ab543 100644
--- a/src/mongo/db/SConscript
+++ b/src/mongo/db/SConscript
@@ -976,6 +976,7 @@ env.Library(
'query/explain.cpp',
'query/find.cpp',
'pipeline/document_source_cursor.cpp',
+ 'pipeline/document_source_geo_near_cursor.cpp',
'pipeline/pipeline_d.cpp',
'query/get_executor.cpp',
'query/internal_plans.cpp',
diff --git a/src/mongo/db/commands/SConscript b/src/mongo/db/commands/SConscript
index 6efa6d3e148..8e718100a33 100644
--- a/src/mongo/db/commands/SConscript
+++ b/src/mongo/db/commands/SConscript
@@ -212,7 +212,6 @@ env.Library(
"explain_cmd.cpp",
"find_and_modify.cpp",
"find_cmd.cpp",
- "geo_near_cmd.cpp",
"get_last_error.cpp",
"getmore_cmd.cpp",
"index_filter_commands.cpp",
diff --git a/src/mongo/db/commands/geo_near_cmd.cpp b/src/mongo/db/commands/geo_near_cmd.cpp
deleted file mode 100644
index 7385f6645f5..00000000000
--- a/src/mongo/db/commands/geo_near_cmd.cpp
+++ /dev/null
@@ -1,404 +0,0 @@
-/**
-* Copyright (C) 2012-2014 MongoDB Inc.
-*
-* This program is free software: you can redistribute it and/or modify
-* it under the terms of the GNU Affero General Public License, version 3,
-* as published by the Free Software Foundation.
-*
-* This program is distributed in the hope that it will be useful,
-* but WITHOUT ANY WARRANTY; without even the implied warranty of
-* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-* GNU Affero General Public License for more details.
-*
-* You should have received a copy of the GNU Affero General Public License
-* along with this program. If not, see <http://www.gnu.org/licenses/>.
-*
-* As a special exception, the copyright holders give permission to link the
-* code of portions of this program with the OpenSSL library under certain
-* conditions as described in each individual source file and distribute
-* linked combinations including the program with the OpenSSL library. You
-* must comply with the GNU Affero General Public License in all respects for
-* all of the code used other than as permitted herein. If you modify file(s)
-* with this exception, you may extend this exception to your version of the
-* file(s), but you are not obligated to do so. If you do not wish to do so,
-* delete this exception statement from your version. If you delete this
-* exception statement from all source files in the program, then also delete
-* it in the license file.
-*/
-
-#define MONGO_LOG_DEFAULT_COMPONENT ::mongo::logger::LogComponent::kCommand
-
-#include <vector>
-
-#include "mongo/bson/util/bson_extract.h"
-#include "mongo/db/auth/action_set.h"
-#include "mongo/db/auth/action_type.h"
-#include "mongo/db/auth/privilege.h"
-#include "mongo/db/catalog/collection.h"
-#include "mongo/db/catalog/database.h"
-#include "mongo/db/catalog/index_catalog.h"
-#include "mongo/db/client.h"
-#include "mongo/db/commands.h"
-#include "mongo/db/curop.h"
-#include "mongo/db/db_raii.h"
-#include "mongo/db/exec/working_set_common.h"
-#include "mongo/db/geo/geoconstants.h"
-#include "mongo/db/geo/geoparser.h"
-#include "mongo/db/index/index_descriptor.h"
-#include "mongo/db/index_names.h"
-#include "mongo/db/jsobj.h"
-#include "mongo/db/matcher/expression_geo.h"
-#include "mongo/db/matcher/extensions_callback_real.h"
-#include "mongo/db/pipeline/document_source_geo_near.h"
-#include "mongo/db/query/explain.h"
-#include "mongo/db/query/find_common.h"
-#include "mongo/db/query/get_executor.h"
-#include "mongo/db/query/plan_summary_stats.h"
-#include "mongo/util/log.h"
-
-namespace mongo {
-
-using std::unique_ptr;
-using std::stringstream;
-
-/**
- * The geoNear command is deprecated. Users should prefer the $near query operator, the $nearSphere
- * query operator, or the $geoNear aggregation stage. See
- * http://dochub.mongodb.org/core/geoNear-deprecation for more detail.
- */
-class Geo2dFindNearCmd : public ErrmsgCommandDeprecated {
-public:
- Geo2dFindNearCmd() : ErrmsgCommandDeprecated("geoNear") {}
-
- virtual bool supportsWriteConcern(const BSONObj& cmd) const override {
- return false;
- }
- AllowedOnSecondary secondaryAllowed(ServiceContext*) const override {
- return AllowedOnSecondary::kAlways;
- }
- bool supportsReadConcern(const std::string& dbName,
- const BSONObj& cmdObj,
- repl::ReadConcernLevel level) const final {
- return true;
- }
-
- ReadWriteType getReadWriteType() const {
- return ReadWriteType::kRead;
- }
-
- std::size_t reserveBytesForReply() const override {
- return FindCommon::kInitReplyBufferSize;
- }
-
- std::string help() const override {
- return "http://dochub.mongodb.org/core/geo#GeospatialIndexing-geoNearCommand";
- }
-
- virtual void addRequiredPrivileges(const std::string& dbname,
- const BSONObj& cmdObj,
- std::vector<Privilege>* out) const {
- ActionSet actions;
- actions.addAction(ActionType::find);
- out->push_back(Privilege(parseResourcePattern(dbname, cmdObj), actions));
- }
-
- bool errmsgRun(OperationContext* opCtx,
- const string& dbname,
- const BSONObj& cmdObj,
- string& errmsg,
- BSONObjBuilder& result) {
- // Do not log the deprecation warning when in a direct client, since the $geoNear
- // aggregation stage runs the geoNear command in a direct client.
- RARELY if (!opCtx->getClient()->isInDirectClient()) {
- warning() << "Support for the geoNear command has been deprecated. Please plan to "
- "rewrite geoNear commands using the $near query operator, the $nearSphere "
- "query operator, or the $geoNear aggregation stage. See "
- "http://dochub.mongodb.org/core/geoNear-deprecation.";
- }
-
- if (!cmdObj["start"].eoo()) {
- errmsg = "using deprecated 'start' argument to geoNear";
- return false;
- }
-
- const NamespaceString nss(CommandHelpers::parseNsCollectionRequired(dbname, cmdObj));
- AutoGetCollectionForReadCommand ctx(opCtx, nss);
-
- Collection* collection = ctx.getCollection();
- if (!collection) {
- errmsg = "can't find ns";
- return false;
- }
-
- auto nearFieldName = getFieldName(opCtx, collection, cmdObj);
-
- PointWithCRS point;
- uassert(17304,
- "'near' field must be point",
- GeoParser::parseQueryPoint(cmdObj["near"], &point).isOK());
-
- bool isSpherical = cmdObj["spherical"].trueValue();
-
- // Build the $near expression for the query.
- BSONObjBuilder nearBob;
- if (isSpherical) {
- nearBob.append("$nearSphere", cmdObj["near"].Obj());
- } else {
- nearBob.append("$near", cmdObj["near"].Obj());
- }
-
- if (!cmdObj["maxDistance"].eoo()) {
- uassert(17299, "maxDistance must be a number", cmdObj["maxDistance"].isNumber());
- nearBob.append("$maxDistance", cmdObj["maxDistance"].number());
- }
-
- if (!cmdObj["minDistance"].eoo()) {
- uassert(17300, "minDistance must be a number", cmdObj["minDistance"].isNumber());
- nearBob.append("$minDistance", cmdObj["minDistance"].number());
- }
-
- if (!cmdObj["uniqueDocs"].eoo()) {
- warning() << nss << ": ignoring deprecated uniqueDocs option in geoNear command";
- }
-
- // And, build the full query expression.
- BSONObjBuilder queryBob;
- queryBob.append(nearFieldName, nearBob.obj());
- if (!cmdObj["query"].eoo() && cmdObj["query"].isABSONObj()) {
- queryBob.appendElements(cmdObj["query"].Obj());
- }
- BSONObj rewritten = queryBob.obj();
-
- // Extract the collation, if it exists.
- BSONObj collation;
- {
- BSONElement collationElt;
- Status collationEltStatus =
- bsonExtractTypedField(cmdObj, "collation", BSONType::Object, &collationElt);
- if (!collationEltStatus.isOK() && (collationEltStatus != ErrorCodes::NoSuchKey)) {
- uassertStatusOK(collationEltStatus);
- }
- if (collationEltStatus.isOK()) {
- collation = collationElt.Obj();
- }
- }
-
- long long numWanted = 100;
- const char* limitName = !cmdObj["num"].eoo() ? "num" : "limit";
- BSONElement eNumWanted = cmdObj[limitName];
- if (!eNumWanted.eoo()) {
- uassert(17303, "limit must be number", eNumWanted.isNumber());
- numWanted = eNumWanted.safeNumberLong();
- uassert(17302, "limit must be >=0", numWanted >= 0);
- }
-
- bool includeLocs = false;
- if (!cmdObj["includeLocs"].eoo()) {
- includeLocs = cmdObj["includeLocs"].trueValue();
- }
-
- double distanceMultiplier = 1.0;
- BSONElement eDistanceMultiplier = cmdObj["distanceMultiplier"];
- if (!eDistanceMultiplier.eoo()) {
- uassert(17296, "distanceMultiplier must be a number", eDistanceMultiplier.isNumber());
- distanceMultiplier = eDistanceMultiplier.number();
- uassert(17297, "distanceMultiplier must be non-negative", distanceMultiplier >= 0);
- }
-
- BSONObj projObj = BSON("$pt" << BSON("$meta" << QueryRequest::metaGeoNearPoint) << "$dis"
- << BSON("$meta" << QueryRequest::metaGeoNearDistance));
-
- auto qr = stdx::make_unique<QueryRequest>(nss);
- qr->setFilter(rewritten);
- qr->setProj(projObj);
- qr->setLimit(numWanted);
- qr->setCollation(collation);
- const ExtensionsCallbackReal extensionsCallback(opCtx, &nss);
- const boost::intrusive_ptr<ExpressionContext> expCtx;
- auto statusWithCQ =
- CanonicalQuery::canonicalize(opCtx,
- std::move(qr),
- expCtx,
- extensionsCallback,
- MatchExpressionParser::kAllowAllSpecialFeatures);
- if (!statusWithCQ.isOK()) {
- errmsg = "Can't parse filter / create query";
- return false;
- }
- unique_ptr<CanonicalQuery> cq = std::move(statusWithCQ.getValue());
-
- // Prevent chunks from being cleaned up during yields - this allows us to only check the
- // version on initial entry into geoNear.
- auto rangePreserver = CollectionShardingState::get(opCtx, nss)->getMetadata(opCtx);
-
- const auto& readConcernArgs = repl::ReadConcernArgs::get(opCtx);
- const PlanExecutor::YieldPolicy yieldPolicy =
- readConcernArgs.getLevel() == repl::ReadConcernLevel::kSnapshotReadConcern
- ? PlanExecutor::INTERRUPT_ONLY
- : PlanExecutor::YIELD_AUTO;
- auto exec = uassertStatusOK(getExecutor(opCtx, collection, std::move(cq), yieldPolicy, 0));
-
- auto curOp = CurOp::get(opCtx);
- {
- stdx::lock_guard<Client> lk(*opCtx->getClient());
- curOp->setPlanSummary_inlock(Explain::getPlanSummary(exec.get()));
- }
-
- double totalDistance = 0;
- BSONObjBuilder resultBuilder(result.subarrayStart("results"));
- double farthestDist = 0;
-
- BSONObj currObj;
- long long results = 0;
- PlanExecutor::ExecState state;
- while (PlanExecutor::ADVANCED == (state = exec->getNext(&currObj, NULL))) {
- // Come up with the correct distance.
- double dist = currObj["$dis"].number() * distanceMultiplier;
- totalDistance += dist;
- if (dist > farthestDist) {
- farthestDist = dist;
- }
-
- // Strip out '$dis' and '$pt' from the result obj. The rest gets added as 'obj'
- // in the command result.
- BSONObjIterator resIt(currObj);
- BSONObjBuilder resBob;
- while (resIt.more()) {
- BSONElement elt = resIt.next();
- if (!mongoutils::str::equals("$pt", elt.fieldName()) &&
- !mongoutils::str::equals("$dis", elt.fieldName())) {
- resBob.append(elt);
- }
- }
- BSONObj resObj = resBob.obj();
-
- // Don't make a too-big result object.
- if (resultBuilder.len() + resObj.objsize() > BSONObjMaxUserSize) {
- warning() << "Too many geoNear results for query " << redact(rewritten)
- << ", truncating output.";
- break;
- }
-
- // Add the next result to the result builder.
- BSONObjBuilder oneResultBuilder(
- resultBuilder.subobjStart(BSONObjBuilder::numStr(results)));
- oneResultBuilder.append("dis", dist);
- if (includeLocs) {
- oneResultBuilder.appendAs(currObj["$pt"], "loc");
- }
- oneResultBuilder.append("obj", resObj);
- oneResultBuilder.done();
-
- ++results;
-
- // Break if we have the number of requested result documents.
- if (results >= numWanted) {
- break;
- }
- }
-
- resultBuilder.done();
-
- // Return an error if execution fails for any reason.
- if (PlanExecutor::FAILURE == state || PlanExecutor::DEAD == state) {
- log() << "Plan executor error during geoNear command: " << PlanExecutor::statestr(state)
- << ", stats: " << redact(Explain::getWinningPlanStats(exec.get()));
-
- uassertStatusOK(WorkingSetCommon::getMemberObjectStatus(currObj).withContext(
- "Executor error during geoNear command"));
- }
-
- PlanSummaryStats summary;
- Explain::getSummaryStats(*exec, &summary);
-
- // Fill out the stats subobj.
- BSONObjBuilder stats(result.subobjStart("stats"));
-
- stats.appendNumber("nscanned", summary.totalKeysExamined);
- stats.appendNumber("objectsLoaded", summary.totalDocsExamined);
-
- if (results > 0) {
- stats.append("avgDistance", totalDistance / results);
- }
- stats.append("maxDistance", farthestDist);
- stats.appendIntOrLL("time",
- durationCount<Microseconds>(curOp->elapsedTimeExcludingPauses()));
- stats.done();
-
- collection->infoCache()->notifyOfQuery(opCtx, summary.indexesUsed);
-
- curOp->debug().setPlanSummaryMetrics(summary);
-
- if (curOp->shouldDBProfile()) {
- BSONObjBuilder execStatsBob;
- Explain::getWinningPlanStats(exec.get(), &execStatsBob);
- curOp->debug().execStats = execStatsBob.obj();
- }
-
- return true;
- }
-
-private:
- /**
- * Given a collection and the geoNear command parameters, returns the field path over which
- * the geoNear should operate.
- *
- * Throws an assertion with ErrorCodes::IndexNotFound if there is no single geo index
- * which this geoNear command should use.
- */
- StringData getFieldName(OperationContext* opCtx, Collection* collection, BSONObj cmdObj) {
- if (auto keyElt = cmdObj[DocumentSourceGeoNear::kKeyFieldName]) {
- uassert(ErrorCodes::TypeMismatch,
- str::stream() << "geoNear parameter '" << DocumentSourceGeoNear::kKeyFieldName
- << "' must be of type string but found type: "
- << typeName(keyElt.type()),
- keyElt.type() == BSONType::String);
- auto fieldName = keyElt.valueStringData();
- uassert(ErrorCodes::BadValue,
- str::stream() << "$geoNear parameter '" << DocumentSourceGeoNear::kKeyFieldName
- << "' cannot be the empty string",
- !fieldName.empty());
- return fieldName;
- }
-
- vector<IndexDescriptor*> idxs;
-
- // First, try 2d.
- collection->getIndexCatalog()->findIndexByType(opCtx, IndexNames::GEO_2D, idxs);
- uassert(ErrorCodes::IndexNotFound,
- "more than one 2d index, not sure which to run geoNear on",
- idxs.size() <= 1u);
-
- if (1 == idxs.size()) {
- BSONObj indexKp = idxs[0]->keyPattern();
- BSONObjIterator kpIt(indexKp);
- while (kpIt.more()) {
- BSONElement elt = kpIt.next();
- if (BSONType::String == elt.type() && IndexNames::GEO_2D == elt.valuestr()) {
- return elt.fieldNameStringData();
- }
- }
- }
-
- // Next, 2dsphere.
- idxs.clear();
- collection->getIndexCatalog()->findIndexByType(opCtx, IndexNames::GEO_2DSPHERE, idxs);
- uassert(ErrorCodes::IndexNotFound, "no geo indices for geoNear", !idxs.empty());
- uassert(ErrorCodes::IndexNotFound,
- "more than one 2dsphere index, not sure which to run geoNear on",
- idxs.size() == 1u);
-
- // 1 == idx.size().
- BSONObj indexKp = idxs[0]->keyPattern();
- BSONObjIterator kpIt(indexKp);
- while (kpIt.more()) {
- BSONElement elt = kpIt.next();
- if (BSONType::String == elt.type() && IndexNames::GEO_2DSPHERE == elt.valuestr()) {
- return elt.fieldNameStringData();
- }
- }
-
- MONGO_UNREACHABLE;
- }
-} geo2dFindNearCmd;
-} // namespace mongo
diff --git a/src/mongo/db/pipeline/cluster_aggregation_planner.cpp b/src/mongo/db/pipeline/cluster_aggregation_planner.cpp
index 53ea48dd68f..5148d56f888 100644
--- a/src/mongo/db/pipeline/cluster_aggregation_planner.cpp
+++ b/src/mongo/db/pipeline/cluster_aggregation_planner.cpp
@@ -100,10 +100,7 @@ void moveFinalUnwindFromShardsToMerger(Pipeline* shardPipe, Pipeline* mergePipe)
* Documents.
*/
void limitFieldsSentFromShardsToMerger(Pipeline* shardPipe, Pipeline* mergePipe) {
- auto depsMetadata = DocumentSourceMatch::isTextQuery(shardPipe->getInitialQuery())
- ? DepsTracker::MetadataAvailable::kTextScore
- : DepsTracker::MetadataAvailable::kNoMetadata;
- DepsTracker mergeDeps(mergePipe->getDependencies(depsMetadata));
+ DepsTracker mergeDeps(mergePipe->getDependencies(DepsTracker::kAllMetadataAvailable));
if (mergeDeps.needWholeDocument)
return; // the merge needs all fields, so nothing we can do.
@@ -113,7 +110,7 @@ void limitFieldsSentFromShardsToMerger(Pipeline* shardPipe, Pipeline* mergePipe)
// Remove metadata from dependencies since it automatically flows through projection and we
// don't want to project it in to the document.
- mergeDeps.setNeedTextScore(false);
+ mergeDeps.setNeedsMetadata(DepsTracker::MetadataType::TEXT_SCORE, false);
// HEURISTIC: only apply optimization if none of the shard stages have an exhaustive list of
// field dependencies. While this may not be 100% ideal in all cases, it is simple and
@@ -125,7 +122,7 @@ void limitFieldsSentFromShardsToMerger(Pipeline* shardPipe, Pipeline* mergePipe)
// 2) Optimization IS NOT applied immediately following a $project or $group since it would
// add an unnecessary project (and therefore a deep-copy).
for (auto&& source : shardPipe->getSources()) {
- DepsTracker dt(depsMetadata);
+ DepsTracker dt(DepsTracker::kAllMetadataAvailable);
if (source->getDependencies(&dt) & DocumentSource::EXHAUSTIVE_FIELDS)
return;
}
diff --git a/src/mongo/db/pipeline/dependencies.cpp b/src/mongo/db/pipeline/dependencies.cpp
index a1fc3b88a19..df24a8bb9ed 100644
--- a/src/mongo/db/pipeline/dependencies.cpp
+++ b/src/mongo/db/pipeline/dependencies.cpp
@@ -41,6 +41,8 @@ using std::vector;
namespace str = mongoutils::str;
+constexpr DepsTracker::MetadataAvailable DepsTracker::kAllGeoNearDataAvailable;
+
bool DepsTracker::_appendMetaProjections(BSONObjBuilder* projectionBuilder) const {
if (_needTextScore) {
projectionBuilder->append(Document::metaFieldTextScore,
@@ -52,7 +54,17 @@ bool DepsTracker::_appendMetaProjections(BSONObjBuilder* projectionBuilder) cons
BSON("$meta"
<< "sortKey"));
}
- return (_needTextScore || _needSortKey);
+ if (_needGeoNearDistance) {
+ projectionBuilder->append(Document::metaFieldGeoNearDistance,
+ BSON("$meta"
+ << "geoNearDistance"));
+ }
+ if (_needGeoNearPoint) {
+ projectionBuilder->append(Document::metaFieldGeoNearPoint,
+ BSON("$meta"
+ << "geoNearPoint"));
+ }
+ return (_needTextScore || _needSortKey || _needGeoNearDistance || _needGeoNearPoint);
}
BSONObj DepsTracker::toProjection() const {
@@ -134,6 +146,81 @@ boost::optional<ParsedDeps> DepsTracker::toParsedDeps() const {
return ParsedDeps(md.freeze());
}
+bool DepsTracker::getNeedsMetadata(MetadataType type) const {
+ switch (type) {
+ case MetadataType::TEXT_SCORE:
+ return _needTextScore;
+ case MetadataType::SORT_KEY:
+ return _needSortKey;
+ case MetadataType::GEO_NEAR_DISTANCE:
+ return _needGeoNearDistance;
+ case MetadataType::GEO_NEAR_POINT:
+ return _needGeoNearPoint;
+ }
+ MONGO_UNREACHABLE;
+}
+
+bool DepsTracker::isMetadataAvailable(MetadataType type) const {
+ switch (type) {
+ case MetadataType::TEXT_SCORE:
+ return _metadataAvailable & MetadataAvailable::kTextScore;
+ case MetadataType::SORT_KEY:
+ MONGO_UNREACHABLE;
+ case MetadataType::GEO_NEAR_DISTANCE:
+ return _metadataAvailable & MetadataAvailable::kGeoNearDistance;
+ case MetadataType::GEO_NEAR_POINT:
+ return _metadataAvailable & MetadataAvailable::kGeoNearPoint;
+ }
+ MONGO_UNREACHABLE;
+}
+
+void DepsTracker::setNeedsMetadata(MetadataType type, bool required) {
+ switch (type) {
+ case MetadataType::TEXT_SCORE:
+ uassert(40218,
+ "pipeline requires text score metadata, but there is no text score available",
+ !required || isMetadataAvailable(type));
+ _needTextScore = required;
+ return;
+ case MetadataType::SORT_KEY:
+ invariant(required || !_needSortKey);
+ _needSortKey = required;
+ return;
+ case MetadataType::GEO_NEAR_DISTANCE:
+ uassert(50860,
+ "pipeline requires $geoNear distance metadata, but it is not available",
+ !required || isMetadataAvailable(type));
+ invariant(required || !_needGeoNearDistance);
+ _needGeoNearDistance = required;
+ return;
+ case MetadataType::GEO_NEAR_POINT:
+ uassert(50859,
+ "pipeline requires $geoNear point metadata, but it is not available",
+ !required || isMetadataAvailable(type));
+ invariant(required || !_needGeoNearPoint);
+ _needGeoNearPoint = required;
+ return;
+ }
+ MONGO_UNREACHABLE;
+}
+
+std::vector<DepsTracker::MetadataType> DepsTracker::getAllRequiredMetadataTypes() const {
+ std::vector<MetadataType> reqs;
+ if (_needTextScore) {
+ reqs.push_back(MetadataType::TEXT_SCORE);
+ }
+ if (_needSortKey) {
+ reqs.push_back(MetadataType::SORT_KEY);
+ }
+ if (_needGeoNearDistance) {
+ reqs.push_back(MetadataType::GEO_NEAR_DISTANCE);
+ }
+ if (_needGeoNearPoint) {
+ reqs.push_back(MetadataType::GEO_NEAR_POINT);
+ }
+ return reqs;
+}
+
namespace {
// Mutually recursive with arrayHelper
Document documentHelper(const BSONObj& bson, const Document& neededFields, int nFieldsNeeded = -1);
diff --git a/src/mongo/db/pipeline/dependencies.h b/src/mongo/db/pipeline/dependencies.h
index 133fc1d9893..dec5296060f 100644
--- a/src/mongo/db/pipeline/dependencies.h
+++ b/src/mongo/db/pipeline/dependencies.h
@@ -43,9 +43,43 @@ class ParsedDeps;
*/
struct DepsTracker {
/**
+ * Represents the type of metadata a pipeline might request.
+ */
+ enum class MetadataType {
+ // The score associated with a text match.
+ TEXT_SCORE,
+
+ // The key to use for sorting.
+ SORT_KEY,
+
+ // The computed distance for a near query.
+ GEO_NEAR_DISTANCE,
+
+ // The point used in the computation of the GEO_NEAR_DISTANCE.
+ GEO_NEAR_POINT,
+ };
+
+ /**
* Represents what metadata is available on documents that are input to the pipeline.
*/
- enum MetadataAvailable { kNoMetadata = 0, kTextScore = 1 };
+ enum MetadataAvailable {
+ kNoMetadata = 0,
+ kTextScore = 1 << 1,
+ kGeoNearDistance = 1 << 2,
+ kGeoNearPoint = 1 << 3,
+ };
+
+ /**
+ * Represents a state where all geo metadata is available.
+ */
+ static constexpr auto kAllGeoNearDataAvailable =
+ MetadataAvailable(MetadataAvailable::kGeoNearDistance | MetadataAvailable::kGeoNearPoint);
+
+ /**
+ * Represents a state where all metadata is available.
+ */
+ static constexpr auto kAllMetadataAvailable =
+ MetadataAvailable(kTextScore | kGeoNearDistance | kGeoNearPoint);
DepsTracker(MetadataAvailable metadataAvailable = kNoMetadata)
: _metadataAvailable(metadataAvailable) {}
@@ -71,36 +105,44 @@ struct DepsTracker {
return !match.empty();
}
+ /**
+ * Returns a value with bits set indicating the types of metadata available.
+ */
MetadataAvailable getMetadataAvailable() const {
return _metadataAvailable;
}
- bool isTextScoreAvailable() const {
- return _metadataAvailable & MetadataAvailable::kTextScore;
- }
+ /**
+ * Returns true if the DepsTracker the metadata 'type' is available to the pipeline. It is
+ * illegal to call this with MetadataType::SORT_KEY, since the sort key will always be available
+ * if needed.
+ */
+ bool isMetadataAvailable(MetadataType type) const;
- bool getNeedTextScore() const {
- return _needTextScore;
- }
+ /**
+ * Sets whether or not metadata 'type' is required. Throws if 'required' is true but that
+ * metadata is not available to the pipeline.
+ *
+ * Except for MetadataType::SORT_KEY, once 'type' is required, it cannot be unset.
+ */
+ void setNeedsMetadata(MetadataType type, bool required);
- void setNeedTextScore(bool needTextScore) {
- if (needTextScore && !isTextScoreAvailable()) {
- uasserted(
- 40218,
- "pipeline requires text score metadata, but there is no text score available");
- }
- _needTextScore = needTextScore;
- }
+ /**
+ * Returns true if the DepsTracker requires that metadata of type 'type' is present.
+ */
+ bool getNeedsMetadata(MetadataType type) const;
- bool getNeedSortKey() const {
- return _needSortKey;
+ /**
+ * Returns true if there exists a type of metadata required by the DepsTracker.
+ */
+ bool getNeedsAnyMetadata() const {
+ return _needTextScore || _needSortKey || _needGeoNearDistance || _needGeoNearPoint;
}
- void setNeedSortKey(bool needSortKey) {
- // We don't expect to ever unset '_needSortKey'.
- invariant(!_needSortKey || needSortKey);
- _needSortKey = needSortKey;
- }
+ /**
+ * Returns a vector containing all the types of metadata required by this DepsTracker.
+ */
+ std::vector<MetadataType> getAllRequiredMetadataTypes() const;
std::set<std::string> fields; // Names of needed fields in dotted notation.
std::set<Variables::Id> vars; // IDs of referenced variables.
@@ -114,8 +156,12 @@ private:
bool _appendMetaProjections(BSONObjBuilder* bb) const;
MetadataAvailable _metadataAvailable;
- bool _needTextScore = false; // if true, add a {$meta: "textScore"} to the projection.
- bool _needSortKey = false; // if true, add a {$meta: "sortKey"} to the projection.
+
+ // Each member variable influences a different $meta projection.
+ bool _needTextScore = false; // {$meta: "textScore"}
+ bool _needSortKey = false; // {$meta: "sortKey"}
+ bool _needGeoNearDistance = false; // {$meta: "geoNearDistance"}
+ bool _needGeoNearPoint = false; // {$meta: "geoNearPoint"}
};
/**
diff --git a/src/mongo/db/pipeline/dependencies_test.cpp b/src/mongo/db/pipeline/dependencies_test.cpp
index cb33e1fcc9f..733cc94dd60 100644
--- a/src/mongo/db/pipeline/dependencies_test.cpp
+++ b/src/mongo/db/pipeline/dependencies_test.cpp
@@ -116,7 +116,7 @@ TEST(DependenciesToProjectionTest, ShouldOnlyRequestTextScoreIfEntireDocumentAnd
DepsTracker deps(DepsTracker::MetadataAvailable::kTextScore);
deps.fields = arrayToSet(array);
deps.needWholeDocument = true;
- deps.setNeedTextScore(true);
+ deps.setNeedsMetadata(DepsTracker::MetadataType::TEXT_SCORE, true);
ASSERT_BSONOBJ_EQ(deps.toProjection(), BSON(Document::metaFieldTextScore << metaTextScore));
}
@@ -125,7 +125,7 @@ TEST(DependenciesToProjectionTest,
const char* array[] = {"a"}; // needTextScore without needWholeDocument
DepsTracker deps(DepsTracker::MetadataAvailable::kTextScore);
deps.fields = arrayToSet(array);
- deps.setNeedTextScore(true);
+ deps.setNeedsMetadata(DepsTracker::MetadataType::TEXT_SCORE, true);
ASSERT_BSONOBJ_EQ(
deps.toProjection(),
BSON(Document::metaFieldTextScore << metaTextScore << "a" << 1 << "_id" << 0));
@@ -135,7 +135,7 @@ TEST(DependenciesToProjectionTest, ShouldProduceEmptyObjectIfThereAreNoDependenc
DepsTracker deps(DepsTracker::MetadataAvailable::kTextScore);
deps.fields = {};
deps.needWholeDocument = false;
- deps.setNeedTextScore(false);
+ deps.setNeedsMetadata(DepsTracker::MetadataType::TEXT_SCORE, false);
ASSERT_BSONOBJ_EQ(deps.toProjection(), BSONObj());
}
@@ -143,7 +143,7 @@ TEST(DependenciesToProjectionTest, ShouldAttemptToExcludeOtherFieldsIfOnlyTextSc
DepsTracker deps(DepsTracker::MetadataAvailable::kTextScore);
deps.fields = {};
deps.needWholeDocument = false;
- deps.setNeedTextScore(true);
+ deps.setNeedsMetadata(DepsTracker::MetadataType::TEXT_SCORE, true);
ASSERT_BSONOBJ_EQ(deps.toProjection(),
BSON(Document::metaFieldTextScore << metaTextScore << "_id" << 0
<< "$noFieldsNeeded"
@@ -155,7 +155,7 @@ TEST(DependenciesToProjectionTest,
DepsTracker deps(DepsTracker::MetadataAvailable::kTextScore);
deps.fields = {};
deps.needWholeDocument = true;
- deps.setNeedTextScore(true);
+ deps.setNeedsMetadata(DepsTracker::MetadataType::TEXT_SCORE, true);
ASSERT_BSONOBJ_EQ(deps.toProjection(), BSON(Document::metaFieldTextScore << metaTextScore));
}
diff --git a/src/mongo/db/pipeline/document.cpp b/src/mongo/db/pipeline/document.cpp
index 4f531fe72f3..f6f88b504fc 100644
--- a/src/mongo/db/pipeline/document.cpp
+++ b/src/mongo/db/pipeline/document.cpp
@@ -45,8 +45,11 @@ using std::vector;
const DocumentStorage DocumentStorage::kEmptyDoc;
-const std::vector<StringData> Document::allMetadataFieldNames = {
- Document::metaFieldTextScore, Document::metaFieldRandVal, Document::metaFieldSortKey};
+const std::vector<StringData> Document::allMetadataFieldNames = {Document::metaFieldTextScore,
+ Document::metaFieldRandVal,
+ Document::metaFieldSortKey,
+ Document::metaFieldGeoNearDistance,
+ Document::metaFieldGeoNearPoint};
Position DocumentStorage::findField(StringData requested) const {
int reqSize = requested.size(); // get size calculation out of the way if needed
@@ -205,6 +208,8 @@ intrusive_ptr<DocumentStorage> DocumentStorage::clone() const {
out->_textScore = _textScore;
out->_randVal = _randVal;
out->_sortKey = _sortKey.getOwned();
+ out->_geoNearDistance = _geoNearDistance;
+ out->_geoNearPoint = _geoNearPoint.getOwned();
// Tell values that they have been memcpyed (updates ref counts)
for (DocumentStorageIterator it = out->iteratorAll(); !it.atEnd(); it.advance()) {
@@ -272,6 +277,8 @@ BSONObj Document::toBson() const {
constexpr StringData Document::metaFieldTextScore;
constexpr StringData Document::metaFieldRandVal;
constexpr StringData Document::metaFieldSortKey;
+constexpr StringData Document::metaFieldGeoNearDistance;
+constexpr StringData Document::metaFieldGeoNearPoint;
BSONObj Document::toBsonWithMetaData() const {
BSONObjBuilder bb;
@@ -282,6 +289,10 @@ BSONObj Document::toBsonWithMetaData() const {
bb.append(metaFieldRandVal, getRandMetaField());
if (hasSortKeyMetaField())
bb.append(metaFieldSortKey, getSortKeyMetaField());
+ if (hasGeoNearDistance())
+ bb.append(metaFieldGeoNearDistance, getGeoNearDistance());
+ if (hasGeoNearPoint())
+ getGeoNearPoint().addToBsonObj(&bb, metaFieldGeoNearPoint);
return bb.obj();
}
@@ -302,6 +313,20 @@ Document Document::fromBsonWithMetaData(const BSONObj& bson) {
} else if (fieldName == metaFieldSortKey) {
md.setSortKeyMetaField(elem.Obj());
continue;
+ } else if (fieldName == metaFieldGeoNearDistance) {
+ md.setGeoNearDistance(elem.Double());
+ continue;
+ } else if (fieldName == metaFieldGeoNearPoint) {
+ Value val;
+ if (elem.type() == BSONType::Array) {
+ val = Value(BSONArray(elem.embeddedObject()));
+ } else {
+ invariant(elem.type() == BSONType::Object);
+ val = Value(elem.embeddedObject());
+ }
+
+ md.setGeoNearPoint(val);
+ continue;
}
}
diff --git a/src/mongo/db/pipeline/document.h b/src/mongo/db/pipeline/document.h
index eb7438b776e..76d207276ce 100644
--- a/src/mongo/db/pipeline/document.h
+++ b/src/mongo/db/pipeline/document.h
@@ -93,6 +93,8 @@ public:
static constexpr StringData metaFieldTextScore = "$textScore"_sd;
static constexpr StringData metaFieldRandVal = "$randVal"_sd;
static constexpr StringData metaFieldSortKey = "$sortKey"_sd;
+ static constexpr StringData metaFieldGeoNearDistance = "$dis"_sd;
+ static constexpr StringData metaFieldGeoNearPoint = "$pt"_sd;
static const std::vector<StringData> allMetadataFieldNames;
@@ -266,6 +268,20 @@ public:
return storage().getSortKeyMetaField();
}
+ bool hasGeoNearDistance() const {
+ return storage().hasGeoNearDistance();
+ }
+ double getGeoNearDistance() const {
+ return storage().getGeoNearDistance();
+ }
+
+ bool hasGeoNearPoint() const {
+ return storage().hasGeoNearPoint();
+ }
+ Value getGeoNearPoint() const {
+ return storage().getGeoNearPoint();
+ }
+
/// members for Sorter
struct SorterDeserializeSettings {}; // unused
void serializeForSorter(BufBuilder& buf) const;
@@ -518,6 +534,14 @@ public:
storage().setSortKeyMetaField(sortKey);
}
+ void setGeoNearDistance(double dist) {
+ storage().setGeoNearDistance(dist);
+ }
+
+ void setGeoNearPoint(Value point) {
+ storage().setGeoNearPoint(std::move(point));
+ }
+
/** Convert to a read-only document and release reference.
*
* Call this to indicate that you are done with this Document and will
diff --git a/src/mongo/db/pipeline/document_internal.h b/src/mongo/db/pipeline/document_internal.h
index baa68e60658..e1f7c299fc6 100644
--- a/src/mongo/db/pipeline/document_internal.h
+++ b/src/mongo/db/pipeline/document_internal.h
@@ -189,7 +189,8 @@ public:
_hashTabMask(0),
_metaFields(),
_textScore(0),
- _randVal(0) {}
+ _randVal(0),
+ _geoNearDistance(0) {}
~DocumentStorage();
@@ -197,7 +198,10 @@ public:
TEXT_SCORE,
RAND_VAL,
SORT_KEY,
+ GEONEAR_DIST,
+ GEONEAR_POINT,
+ // New fields must be added before the NUM_FIELDS sentinel.
NUM_FIELDS
};
@@ -284,6 +288,12 @@ public:
if (source.hasSortKeyMetaField()) {
setSortKeyMetaField(source.getSortKeyMetaField());
}
+ if (source.hasGeoNearDistance()) {
+ setGeoNearDistance(source.getGeoNearDistance());
+ }
+ if (source.hasGeoNearPoint()) {
+ setGeoNearPoint(source.getGeoNearPoint());
+ }
}
bool hasTextScore() const {
@@ -319,6 +329,28 @@ public:
_sortKey = sortKey.getOwned();
}
+ bool hasGeoNearDistance() const {
+ return _metaFields.test(MetaType::GEONEAR_DIST);
+ }
+ double getGeoNearDistance() const {
+ return _geoNearDistance;
+ }
+ void setGeoNearDistance(double dist) {
+ _metaFields.set(MetaType::GEONEAR_DIST);
+ _geoNearDistance = dist;
+ }
+
+ bool hasGeoNearPoint() const {
+ return _metaFields.test(MetaType::GEONEAR_POINT);
+ }
+ Value getGeoNearPoint() const {
+ return _geoNearPoint;
+ }
+ void setGeoNearPoint(Value point) {
+ _metaFields.set(MetaType::GEONEAR_POINT);
+ _geoNearPoint = std::move(point);
+ }
+
private:
/// Same as lastElement->next() or firstElement() if empty.
const ValueElement* end() const {
@@ -401,6 +433,8 @@ private:
double _textScore;
double _randVal;
BSONObj _sortKey;
+ double _geoNearDistance;
+ Value _geoNearPoint;
// When adding a field, make sure to update clone() method
// Defined in document.cpp
diff --git a/src/mongo/db/pipeline/document_source.h b/src/mongo/db/pipeline/document_source.h
index de383b154e2..6e706876aff 100644
--- a/src/mongo/db/pipeline/document_source.h
+++ b/src/mongo/db/pipeline/document_source.h
@@ -648,7 +648,7 @@ public:
/**
* Get the dependencies this operation needs to do its job. If overridden, subclasses must add
* all paths needed to apply their transformation to 'deps->fields', and call
- * 'deps->setNeedTextScore()' if the text score is required.
+ * 'deps->setNeedsMetadata()' to indicate what metadata (e.g. text score), if any, is required.
*
* See GetDepsReturn above for the possible return values and what they mean.
*/
diff --git a/src/mongo/db/pipeline/document_source_add_fields_test.cpp b/src/mongo/db/pipeline/document_source_add_fields_test.cpp
index fa9b27f5d61..8fa400e731a 100644
--- a/src/mongo/db/pipeline/document_source_add_fields_test.cpp
+++ b/src/mongo/db/pipeline/document_source_add_fields_test.cpp
@@ -133,7 +133,7 @@ TEST_F(AddFieldsTest, ShouldAddReferencedFieldsToDependencies) {
ASSERT_EQUALS(1U, dependencies.fields.count("c"));
ASSERT_EQUALS(1U, dependencies.fields.count("d"));
ASSERT_EQUALS(false, dependencies.needWholeDocument);
- ASSERT_EQUALS(true, dependencies.getNeedTextScore());
+ ASSERT_EQUALS(true, dependencies.getNeedsMetadata(DepsTracker::MetadataType::TEXT_SCORE));
}
TEST_F(AddFieldsTest, ShouldPropagatePauses) {
diff --git a/src/mongo/db/pipeline/document_source_bucket_auto_test.cpp b/src/mongo/db/pipeline/document_source_bucket_auto_test.cpp
index df19b4eb497..157c2aad4f1 100644
--- a/src/mongo/db/pipeline/document_source_bucket_auto_test.cpp
+++ b/src/mongo/db/pipeline/document_source_bucket_auto_test.cpp
@@ -447,7 +447,7 @@ TEST_F(BucketAutoTests, ShouldAddDependenciesOfGroupByFieldAndComputedFields) {
ASSERT_EQUALS(1U, dependencies.fields.count("b"));
ASSERT_EQUALS(false, dependencies.needWholeDocument);
- ASSERT_EQUALS(false, dependencies.getNeedTextScore());
+ ASSERT_EQUALS(false, dependencies.getNeedsMetadata(DepsTracker::MetadataType::TEXT_SCORE));
}
TEST_F(BucketAutoTests, ShouldNeedTextScoreInDependenciesFromGroupByField) {
@@ -459,7 +459,7 @@ TEST_F(BucketAutoTests, ShouldNeedTextScoreInDependenciesFromGroupByField) {
ASSERT_EQUALS(0U, dependencies.fields.size());
ASSERT_EQUALS(false, dependencies.needWholeDocument);
- ASSERT_EQUALS(true, dependencies.getNeedTextScore());
+ ASSERT_EQUALS(true, dependencies.getNeedsMetadata(DepsTracker::MetadataType::TEXT_SCORE));
}
TEST_F(BucketAutoTests, ShouldNeedTextScoreInDependenciesFromOutputField) {
@@ -475,7 +475,7 @@ TEST_F(BucketAutoTests, ShouldNeedTextScoreInDependenciesFromOutputField) {
ASSERT_EQUALS(1U, dependencies.fields.count("x"));
ASSERT_EQUALS(false, dependencies.needWholeDocument);
- ASSERT_EQUALS(true, dependencies.getNeedTextScore());
+ ASSERT_EQUALS(true, dependencies.getNeedsMetadata(DepsTracker::MetadataType::TEXT_SCORE));
}
TEST_F(BucketAutoTests, SerializesDefaultAccumulatorIfOutputFieldIsNotSpecified) {
diff --git a/src/mongo/db/pipeline/document_source_cursor.cpp b/src/mongo/db/pipeline/document_source_cursor.cpp
index 10fe39a1484..aeb16094abb 100644
--- a/src/mongo/db/pipeline/document_source_cursor.cpp
+++ b/src/mongo/db/pipeline/document_source_cursor.cpp
@@ -63,6 +63,10 @@ DocumentSource::GetNextResult DocumentSourceCursor::getNext() {
return std::move(out);
}
+Document DocumentSourceCursor::transformBSONObjToDocument(const BSONObj& obj) const {
+ return _dependencies ? _dependencies->extractFields(obj) : Document::fromBsonWithMetaData(obj);
+}
+
void DocumentSourceCursor::loadBatch() {
if (!_exec || _exec->isDisposed()) {
// No more documents.
@@ -85,10 +89,8 @@ void DocumentSourceCursor::loadBatch() {
while ((state = _exec->getNext(&resultObj, nullptr)) == PlanExecutor::ADVANCED) {
if (_shouldProduceEmptyDocs) {
_currentBatch.push_back(Document());
- } else if (_dependencies) {
- _currentBatch.push_back(_dependencies->extractFields(resultObj));
} else {
- _currentBatch.push_back(Document::fromBsonWithMetaData(resultObj));
+ _currentBatch.push_back(transformBSONObjToDocument(resultObj));
}
if (_limit) {
@@ -302,6 +304,8 @@ DocumentSourceCursor::DocumentSourceCursor(
_docsAddedToBatches(0),
_exec(std::move(exec)),
_outputSorts(_exec->getOutputSorts()) {
+ // Later code in the DocumentSourceCursor lifecycle expects that '_exec' is in a saved state.
+ _exec->saveState();
_planSummary = Explain::getPlanSummary(_exec.get());
recordPlanSummaryStats();
diff --git a/src/mongo/db/pipeline/document_source_cursor.h b/src/mongo/db/pipeline/document_source_cursor.h
index fc9b37f9403..fa10f450d4e 100644
--- a/src/mongo/db/pipeline/document_source_cursor.h
+++ b/src/mongo/db/pipeline/document_source_cursor.h
@@ -43,14 +43,17 @@ namespace mongo {
/**
* Constructs and returns Documents from the BSONObj objects produced by a supplied PlanExecutor.
*/
-class DocumentSourceCursor final : public DocumentSource {
+class DocumentSourceCursor : public DocumentSource {
public:
// virtuals from DocumentSource
GetNextResult getNext() final;
- const char* getSourceName() const final;
- BSONObjSet getOutputSorts() final {
+
+ const char* getSourceName() const override;
+
+ BSONObjSet getOutputSorts() override {
return _outputSorts;
}
+
Value serialize(boost::optional<ExplainOptions::Verbosity> explain = boost::none) const final;
StageConstraints constraints(Pipeline::SplitState pipeState) const final {
@@ -150,6 +153,12 @@ public:
}
protected:
+ DocumentSourceCursor(Collection* collection,
+ std::unique_ptr<PlanExecutor, PlanExecutor::Deleter> exec,
+ const boost::intrusive_ptr<ExpressionContext>& pExpCtx);
+
+ ~DocumentSourceCursor();
+
/**
* Disposes of '_exec' if it hasn't been disposed already. This involves taking a collection
* lock.
@@ -162,12 +171,15 @@ protected:
Pipeline::SourceContainer::iterator doOptimizeAt(Pipeline::SourceContainer::iterator itr,
Pipeline::SourceContainer* container) final;
-private:
- DocumentSourceCursor(Collection* collection,
- std::unique_ptr<PlanExecutor, PlanExecutor::Deleter> exec,
- const boost::intrusive_ptr<ExpressionContext>& pExpCtx);
- ~DocumentSourceCursor();
+ /**
+ * If '_shouldProduceEmptyDocs' is false, this function hook is called on each 'obj' returned by
+ * '_exec' when loading a batch and returns a Document to be added to '_currentBatch'.
+ *
+ * The default implementation is a dependency-aware BSONObj-to-Document transformation.
+ */
+ virtual Document transformBSONObjToDocument(const BSONObj& obj) const;
+private:
/**
* Acquires the appropriate locks, then destroys and de-registers '_exec'. '_exec' must be
* non-null.
@@ -180,12 +192,14 @@ private:
void cleanupExecutor(const AutoGetCollectionForRead& readLock);
/**
- * Reads a batch of data from '_exec'.
+ * Reads a batch of data from '_exec'. Subclasses can specify custom behavior to be performed on
+ * each document by overloading transformBSONObjToDocument().
*/
void loadBatch();
void recordPlanSummaryStats();
+ // Batches results returned from the underlying PlanExecutor.
std::deque<Document> _currentBatch;
// BSONObj members must outlive _projection and cursor.
diff --git a/src/mongo/db/pipeline/document_source_facet.cpp b/src/mongo/db/pipeline/document_source_facet.cpp
index bc49ab6371f..8b1ce60f6e1 100644
--- a/src/mongo/db/pipeline/document_source_facet.cpp
+++ b/src/mongo/db/pipeline/document_source_facet.cpp
@@ -276,11 +276,17 @@ DocumentSource::GetDepsReturn DocumentSourceFacet::getDependencies(DepsTracker*
deps->vars.insert(subDepsTracker.vars.begin(), subDepsTracker.vars.end());
deps->needWholeDocument = deps->needWholeDocument || subDepsTracker.needWholeDocument;
- deps->setNeedTextScore(deps->getNeedTextScore() || subDepsTracker.getNeedTextScore());
+
+ // The text score is the only type of metadata that could be needed by $facet.
+ deps->setNeedsMetadata(
+ DepsTracker::MetadataType::TEXT_SCORE,
+ deps->getNeedsMetadata(DepsTracker::MetadataType::TEXT_SCORE) ||
+ subDepsTracker.getNeedsMetadata(DepsTracker::MetadataType::TEXT_SCORE));
// If there are variables defined at this stage's scope, there may be dependencies upon
// them in subsequent pipelines. Keep enumerating.
- if (deps->needWholeDocument && deps->getNeedTextScore() && !scopeHasVariables) {
+ if (deps->needWholeDocument &&
+ deps->getNeedsMetadata(DepsTracker::MetadataType::TEXT_SCORE) && !scopeHasVariables) {
break;
}
}
diff --git a/src/mongo/db/pipeline/document_source_facet_test.cpp b/src/mongo/db/pipeline/document_source_facet_test.cpp
index 25f8cdc7a07..bc88b491229 100644
--- a/src/mongo/db/pipeline/document_source_facet_test.cpp
+++ b/src/mongo/db/pipeline/document_source_facet_test.cpp
@@ -564,7 +564,7 @@ TEST_F(DocumentSourceFacetTest, ShouldUnionDependenciesOfInnerPipelines) {
DepsTracker deps(DepsTracker::MetadataAvailable::kNoMetadata);
ASSERT_EQ(facetStage->getDependencies(&deps), DocumentSource::GetDepsReturn::EXHAUSTIVE_ALL);
ASSERT_FALSE(deps.needWholeDocument);
- ASSERT_FALSE(deps.getNeedTextScore());
+ ASSERT_FALSE(deps.getNeedsMetadata(DepsTracker::MetadataType::TEXT_SCORE));
ASSERT_EQ(deps.fields.size(), 2UL);
ASSERT_EQ(deps.fields.count("a"), 1UL);
ASSERT_EQ(deps.fields.count("b"), 1UL);
@@ -602,7 +602,7 @@ TEST_F(DocumentSourceFacetTest, ShouldRequireWholeDocumentIfAnyPipelineRequiresW
DepsTracker deps(DepsTracker::MetadataAvailable::kNoMetadata);
ASSERT_EQ(facetStage->getDependencies(&deps), DocumentSource::GetDepsReturn::EXHAUSTIVE_ALL);
ASSERT_TRUE(deps.needWholeDocument);
- ASSERT_FALSE(deps.getNeedTextScore());
+ ASSERT_FALSE(deps.getNeedsMetadata(DepsTracker::MetadataType::TEXT_SCORE));
}
/**
@@ -611,7 +611,7 @@ TEST_F(DocumentSourceFacetTest, ShouldRequireWholeDocumentIfAnyPipelineRequiresW
class DocumentSourceNeedsOnlyTextScore : public DocumentSourcePassthrough {
public:
GetDepsReturn getDependencies(DepsTracker* deps) const override {
- deps->setNeedTextScore(true);
+ deps->setNeedsMetadata(DepsTracker::MetadataType::TEXT_SCORE, true);
return GetDepsReturn::EXHAUSTIVE_ALL;
}
static boost::intrusive_ptr<DocumentSourceNeedsOnlyTextScore> create() {
@@ -641,7 +641,7 @@ TEST_F(DocumentSourceFacetTest, ShouldRequireTextScoreIfAnyPipelineRequiresTextS
DepsTracker deps(DepsTracker::MetadataAvailable::kTextScore);
ASSERT_EQ(facetStage->getDependencies(&deps), DocumentSource::GetDepsReturn::EXHAUSTIVE_ALL);
ASSERT_TRUE(deps.needWholeDocument);
- ASSERT_TRUE(deps.getNeedTextScore());
+ ASSERT_TRUE(deps.getNeedsMetadata(DepsTracker::MetadataType::TEXT_SCORE));
}
TEST_F(DocumentSourceFacetTest, ShouldThrowIfAnyPipelineRequiresTextScoreButItIsNotAvailable) {
diff --git a/src/mongo/db/pipeline/document_source_geo_near.cpp b/src/mongo/db/pipeline/document_source_geo_near.cpp
index 39a31253573..7ba5e2a4f8e 100644
--- a/src/mongo/db/pipeline/document_source_geo_near.cpp
+++ b/src/mongo/db/pipeline/document_source_geo_near.cpp
@@ -33,84 +33,26 @@
#include "mongo/db/pipeline/document_source_geo_near.h"
#include "mongo/db/pipeline/document.h"
-#include "mongo/db/pipeline/document_source_limit.h"
#include "mongo/db/pipeline/document_source_sort.h"
#include "mongo/db/pipeline/lite_parsed_document_source.h"
-#include "mongo/rpc/get_status_from_command_result.h"
#include "mongo/util/log.h"
namespace mongo {
using boost::intrusive_ptr;
+constexpr StringData DocumentSourceGeoNear::kKeyFieldName;
+constexpr const char* DocumentSourceGeoNear::kStageName;
+
REGISTER_DOCUMENT_SOURCE(geoNear,
LiteParsedDocumentSourceDefault::parse,
DocumentSourceGeoNear::createFromBson);
-const long long DocumentSourceGeoNear::kDefaultLimit = 100;
-
-constexpr StringData DocumentSourceGeoNear::kKeyFieldName;
-
-const char* DocumentSourceGeoNear::getSourceName() const {
- return "$geoNear";
-}
-
-DocumentSource::GetNextResult DocumentSourceGeoNear::getNext() {
- pExpCtx->checkForInterrupt();
-
- if (!resultsIterator)
- runCommand();
-
- if (!resultsIterator->more())
- return GetNextResult::makeEOF();
-
- // Each result from the geoNear command is wrapped in a wrapper object with "obj",
- // "dis" and maybe "loc" fields. We want to take the object from "obj" and inject the
- // other fields into it.
- Document result(resultsIterator->next().embeddedObject());
- MutableDocument output(result["obj"].getDocument());
- output.setNestedField(*distanceField, result["dis"]);
- if (includeLocs)
- output.setNestedField(*includeLocs, result["loc"]);
-
- // In a cluster, $geoNear output will be merged via $sort, so add the sort key.
- if (pExpCtx->needsMerge) {
- output.setSortKeyMetaField(BSON("" << result["dis"]));
- }
-
- return output.freeze();
-}
-
-Pipeline::SourceContainer::iterator DocumentSourceGeoNear::doOptimizeAt(
- Pipeline::SourceContainer::iterator itr, Pipeline::SourceContainer* container) {
- invariant(*itr == this);
-
- auto nextLimit = dynamic_cast<DocumentSourceLimit*>((*std::next(itr)).get());
-
- if (nextLimit) {
- // If the next stage is a $limit, we can combine it with ourselves.
- limit = std::min(limit, nextLimit->getLimit());
- container->erase(std::next(itr));
- return itr;
- }
- return std::next(itr);
-}
-
-// This command is sent as-is to the shards.
-intrusive_ptr<DocumentSource> DocumentSourceGeoNear::getShardSource() {
- return this;
-}
-// On mongoS this becomes a merge sort by distance (nearest-first) with limit.
-std::list<intrusive_ptr<DocumentSource>> DocumentSourceGeoNear::getMergeSources() {
- return {DocumentSourceSort::create(
- pExpCtx, BSON(distanceField->fullPath() << 1 << "$mergePresorted" << true), limit)};
-}
-
Value DocumentSourceGeoNear::serialize(boost::optional<ExplainOptions::Verbosity> explain) const {
MutableDocument result;
- if (!keyFieldPath.empty()) {
- result.setField(kKeyFieldName, Value(keyFieldPath));
+ if (keyFieldPath) {
+ result.setField(kKeyFieldName, Value(keyFieldPath->fullPath()));
}
if (coordsIsArray) {
@@ -119,20 +61,21 @@ Value DocumentSourceGeoNear::serialize(boost::optional<ExplainOptions::Verbosity
result.setField("near", Value(coords));
}
- // not in buildGeoNearCmd
result.setField("distanceField", Value(distanceField->fullPath()));
- result.setField("limit", Value(limit));
-
- if (maxDistance > 0)
- result.setField("maxDistance", Value(maxDistance));
+ if (maxDistance) {
+ result.setField("maxDistance", Value(*maxDistance));
+ }
- if (minDistance > 0)
- result.setField("minDistance", Value(minDistance));
+ if (minDistance) {
+ result.setField("minDistance", Value(*minDistance));
+ }
result.setField("query", Value(query));
result.setField("spherical", Value(spherical));
- result.setField("distanceMultiplier", Value(distanceMultiplier));
+ if (distanceMultiplier) {
+ result.setField("distanceMultiplier", Value(*distanceMultiplier));
+ }
if (includeLocs)
result.setField("includeLocs", Value(includeLocs->fullPath()));
@@ -140,60 +83,6 @@ Value DocumentSourceGeoNear::serialize(boost::optional<ExplainOptions::Verbosity
return Value(DOC(getSourceName() << result.freeze()));
}
-BSONObj DocumentSourceGeoNear::buildGeoNearCmd() const {
- // this is very similar to sourceToBson, but slightly different.
- // differences will be noted.
-
- BSONObjBuilder geoNear; // not building a subField
-
- geoNear.append("geoNear", pExpCtx->ns.coll()); // not in toBson
-
- if (coordsIsArray) {
- geoNear.appendArray("near", coords);
- } else {
- geoNear.append("near", coords);
- }
-
- geoNear.append("num", limit); // called limit in toBson
-
- if (maxDistance > 0)
- geoNear.append("maxDistance", maxDistance);
-
- if (minDistance > 0)
- geoNear.append("minDistance", minDistance);
-
- geoNear.append("query", query);
- if (pExpCtx->getCollator()) {
- geoNear.append("collation", pExpCtx->getCollator()->getSpec().toBSON());
- } else {
- geoNear.append("collation", CollationSpec::kSimpleSpec);
- }
-
- geoNear.append("spherical", spherical);
- geoNear.append("distanceMultiplier", distanceMultiplier);
-
- if (includeLocs)
- geoNear.append("includeLocs", true); // String in toBson
-
- if (!keyFieldPath.empty()) {
- geoNear.append(kKeyFieldName, keyFieldPath);
- }
-
- return geoNear.obj();
-}
-
-void DocumentSourceGeoNear::runCommand() {
- massert(16603, "Already ran geoNearCommand", !resultsIterator);
-
- bool ok = pExpCtx->mongoProcessInterface->directClient()->runCommand(
- pExpCtx->ns.db().toString(), buildGeoNearCmd(), cmdOutput);
- if (!ok) {
- uassertStatusOK(getStatusFromCommandResult(cmdOutput));
- }
-
- resultsIterator.reset(new BSONObjIterator(cmdOutput["results"].embeddedObject()));
-}
-
intrusive_ptr<DocumentSourceGeoNear> DocumentSourceGeoNear::create(
const intrusive_ptr<ExpressionContext>& pCtx) {
intrusive_ptr<DocumentSourceGeoNear> source(new DocumentSourceGeoNear(pCtx));
@@ -208,8 +97,25 @@ intrusive_ptr<DocumentSource> DocumentSourceGeoNear::createFromBson(
}
void DocumentSourceGeoNear::parseOptions(BSONObj options) {
- // near and distanceField are required
+ // First, check for explicitly-disallowed fields.
+ // The old geoNear command used to accept a collation. We explicitly ban it here, since the
+ // $geoNear stage should respect the collation associated with the entire pipeline.
+ uassert(40227,
+ "$geoNear does not accept the 'collation' parameter. Instead, specify a collation "
+ "for the entire aggregation command.",
+ !options["collation"]);
+
+ // The following fields were present in older versions but are no longer supported.
+ uassert(50858,
+ "$geoNear no longer supports the 'limit' parameter. Use a $limit stage instead.",
+ !options["limit"]);
+ uassert(50857,
+ "$geoNear no longer supports the 'num' parameter. Use a $limit stage instead.",
+ !options["num"]);
+ uassert(50856, "$geoNear no longer supports the 'start' argument.", !options["start"]);
+
+ // The "near" and "distanceField" parameters are required.
uassert(16605,
"$geoNear requires a 'near' option as an Array",
options["near"].isABSONObj()); // Array or Object (Object is deprecated)
@@ -221,33 +127,47 @@ void DocumentSourceGeoNear::parseOptions(BSONObj options) {
options["distanceField"].type() == String);
distanceField.reset(new FieldPath(options["distanceField"].str()));
- // remaining fields are optional
-
- // num and limit are synonyms
- if (options["limit"].isNumber())
- limit = options["limit"].numberLong();
- if (options["num"].isNumber())
- limit = options["num"].numberLong();
-
- if (options["maxDistance"].isNumber())
+ // The remaining fields are optional.
+ if (auto maxDistElem = options["maxDistance"]) {
+ uassert(ErrorCodes::TypeMismatch,
+ "maxDistance must be a number",
+ isNumericBSONType(maxDistElem.type()));
maxDistance = options["maxDistance"].numberDouble();
+ uassert(ErrorCodes::BadValue, "maxDistance must be nonnegative", *maxDistance >= 0);
+ }
- if (options["minDistance"].isNumber())
+ if (auto minDistElem = options["minDistance"]) {
+ uassert(ErrorCodes::TypeMismatch,
+ "minDistance must be a number",
+ isNumericBSONType(minDistElem.type()));
minDistance = options["minDistance"].numberDouble();
+ uassert(ErrorCodes::BadValue, "minDistance must be nonnegative", *minDistance >= 0);
+ }
- if (options["query"].type() == Object)
- query = options["query"].embeddedObject().getOwned();
+ if (auto distMultElem = options["distanceMultiplier"]) {
+ uassert(ErrorCodes::TypeMismatch,
+ "distanceMultiplier must be a number",
+ isNumericBSONType(distMultElem.type()));
+ distanceMultiplier = options["distanceMultiplier"].numberDouble();
+ uassert(ErrorCodes::BadValue,
+ "distanceMultiplier must be nonnegative",
+ *distanceMultiplier >= 0);
+ }
- spherical = options["spherical"].trueValue();
+ if (auto queryElem = options["query"]) {
+ uassert(ErrorCodes::TypeMismatch,
+ "query must be an object",
+ queryElem.type() == BSONType::Object);
+ query = queryElem.embeddedObject().getOwned();
+ }
- if (options["distanceMultiplier"].isNumber())
- distanceMultiplier = options["distanceMultiplier"].numberDouble();
+ spherical = options["spherical"].trueValue();
if (options.hasField("includeLocs")) {
uassert(16607,
"$geoNear requires that 'includeLocs' option is a String",
options["includeLocs"].type() == String);
- includeLocs.reset(new FieldPath(options["includeLocs"].str()));
+ includeLocs = FieldPath(options["includeLocs"].str());
}
if (options.hasField("uniqueDocs"))
@@ -259,27 +179,64 @@ void DocumentSourceGeoNear::parseOptions(BSONObj options) {
<< "' must be of type string but found type: "
<< typeName(keyElt.type()),
keyElt.type() == BSONType::String);
- keyFieldPath = keyElt.str();
+ const auto keyFieldStr = keyElt.valueStringData();
uassert(ErrorCodes::BadValue,
str::stream() << "$geoNear parameter '" << DocumentSourceGeoNear::kKeyFieldName
<< "' cannot be the empty string",
- !keyFieldPath.empty());
+ !keyFieldStr.empty());
+ keyFieldPath = FieldPath(keyFieldStr);
}
+}
- // The collation field is disallowed, even though it is accepted by the geoNear command, since
- // the $geoNear operation should respect the collation associated with the entire pipeline.
- uassert(40227,
- "$geoNear does not accept the 'collation' parameter. Instead, specify a collation "
- "for the entire aggregation command.",
- !options["collation"]);
+BSONObj DocumentSourceGeoNear::asNearQuery(StringData nearFieldName) const {
+ BSONObjBuilder queryBuilder;
+ queryBuilder.appendElements(query);
+
+ BSONObjBuilder nearBuilder(queryBuilder.subobjStart(nearFieldName));
+ if (spherical) {
+ if (coordsIsArray) {
+ nearBuilder.appendArray("$nearSphere", coords);
+ } else {
+ nearBuilder.append("$nearSphere", coords);
+ }
+ } else {
+ if (coordsIsArray) {
+ nearBuilder.appendArray("$near", coords);
+ } else {
+ nearBuilder.append("$near", coords);
+ }
+ }
+ if (minDistance) {
+ nearBuilder.append("$minDistance", *minDistance);
+ }
+ if (maxDistance) {
+ nearBuilder.append("$maxDistance", *maxDistance);
+ }
+ nearBuilder.doneFast();
+ return queryBuilder.obj();
+}
+
+bool DocumentSourceGeoNear::needsGeoNearPoint() const {
+ return static_cast<bool>(includeLocs);
+}
+
+DocumentSource::GetDepsReturn DocumentSourceGeoNear::getDependencies(DepsTracker* deps) const {
+ // TODO (SERVER-35424): Implement better dependency tracking. For example, 'distanceField' is
+ // produced by this stage, and we could inform the query system that it need not include it in
+ // its response. For now, assume that we require the entire document as well as the appropriate
+ // geoNear metadata.
+ deps->setNeedsMetadata(DepsTracker::MetadataType::GEO_NEAR_DISTANCE, true);
+ deps->setNeedsMetadata(DepsTracker::MetadataType::GEO_NEAR_POINT, needsGeoNearPoint());
+
+ deps->needWholeDocument = true;
+ return GetDepsReturn::EXHAUSTIVE_FIELDS;
}
DocumentSourceGeoNear::DocumentSourceGeoNear(const intrusive_ptr<ExpressionContext>& pExpCtx)
- : DocumentSource(pExpCtx),
- coordsIsArray(false),
- limit(DocumentSourceGeoNear::kDefaultLimit),
- maxDistance(-1.0),
- minDistance(-1.0),
- spherical(false),
- distanceMultiplier(1.0) {}
+ : DocumentSource(pExpCtx), coordsIsArray(false), spherical(false) {}
+
+std::list<boost::intrusive_ptr<DocumentSource>> DocumentSourceGeoNear::getMergeSources() {
+ return {DocumentSourceSort::create(
+ pExpCtx, BSON(distanceField->fullPath() << 1 << "$mergePresorted" << true))};
}
+} // namespace mongo
diff --git a/src/mongo/db/pipeline/document_source_geo_near.h b/src/mongo/db/pipeline/document_source_geo_near.h
index fa67ab1c71a..7d77fa5a422 100644
--- a/src/mongo/db/pipeline/document_source_geo_near.h
+++ b/src/mongo/db/pipeline/document_source_geo_near.h
@@ -35,86 +35,120 @@ namespace mongo {
class DocumentSourceGeoNear : public DocumentSource, public NeedsMergerDocumentSource {
public:
- static const long long kDefaultLimit;
-
static constexpr StringData kKeyFieldName = "key"_sd;
+ static constexpr auto kStageName = "$geoNear";
- // virtuals from DocumentSource
- GetNextResult getNext() final;
- const char* getSourceName() const final;
/**
- * Attempts to combine with a subsequent limit stage, setting the internal limit field
- * as a result.
+ * Only exposed for testing.
*/
- Pipeline::SourceContainer::iterator doOptimizeAt(Pipeline::SourceContainer::iterator itr,
- Pipeline::SourceContainer* container) final;
+ static boost::intrusive_ptr<DocumentSourceGeoNear> create(
+ const boost::intrusive_ptr<ExpressionContext>&);
+
+ const char* getSourceName() const final {
+ return kStageName;
+ }
StageConstraints constraints(Pipeline::SplitState pipeState) const final {
- StageConstraints constraints(StreamType::kStreaming,
- PositionRequirement::kFirst,
- HostTypeRequirement::kAnyShard,
- DiskUseRequirement::kNoDiskUse,
- FacetRequirement::kNotAllowed,
- TransactionRequirement::kAllowed);
-
- constraints.requiresInputDocSource = false;
- return constraints;
+ return {StreamType::kStreaming,
+ PositionRequirement::kFirst,
+ HostTypeRequirement::kAnyShard,
+ DiskUseRequirement::kNoDiskUse,
+ FacetRequirement::kNotAllowed,
+ TransactionRequirement::kAllowed};
}
- Value serialize(boost::optional<ExplainOptions::Verbosity> explain = boost::none) const final;
- BSONObjSet getOutputSorts() final {
- return SimpleBSONObjComparator::kInstance.makeBSONObjSet(
- {BSON(distanceField->fullPath() << -1)});
+ /**
+ * DocumentSourceGeoNear should always be replaced by a DocumentSourceGeoNearCursor before
+ * executing a pipeline, so this method should never be called.
+ */
+ GetNextResult getNext() final {
+ MONGO_UNREACHABLE;
}
- // Virtuals for NeedsMergerDocumentSource
- boost::intrusive_ptr<DocumentSource> getShardSource() final;
- std::list<boost::intrusive_ptr<DocumentSource>> getMergeSources() final;
+ Value serialize(boost::optional<ExplainOptions::Verbosity> explain = boost::none) const final;
static boost::intrusive_ptr<DocumentSource> createFromBson(
BSONElement elem, const boost::intrusive_ptr<ExpressionContext>& pCtx);
- static char geoNearName[];
-
- long long getLimit() {
- return limit;
- }
-
+ /**
+ * A query predicate to apply to the documents in addition to the "near" predicate.
+ */
BSONObj getQuery() const {
return query;
};
- // this should only be used for testing
- static boost::intrusive_ptr<DocumentSourceGeoNear> create(
- const boost::intrusive_ptr<ExpressionContext>& pCtx);
+ /**
+ * The field in which the computed distance will be stored.
+ */
+ FieldPath getDistanceField() const {
+ return *distanceField;
+ }
+
+ /**
+ * The field in which the matching point will be stored, if requested.
+ */
+ boost::optional<FieldPath> getLocationField() const {
+ return includeLocs;
+ }
+
+ /**
+ * The field over which to apply the "near" predicate, if specified.
+ */
+ boost::optional<FieldPath> getKeyField() const {
+ return keyFieldPath;
+ }
+
+ /**
+ * A scaling factor to apply to the distance, if specified by the user.
+ */
+ boost::optional<double> getDistanceMultiplier() const {
+ return distanceMultiplier;
+ }
+
+ GetDepsReturn getDependencies(DepsTracker* deps) const final;
+
+ /**
+ * Returns true if the $geoNear specification requires the geoNear point metadata.
+ */
+ bool needsGeoNearPoint() const;
+
+ /**
+ * Converts this $geoNear aggregation stage into an equivalent $near or $nearSphere query on
+ * 'nearFieldName'.
+ */
+ BSONObj asNearQuery(StringData nearFieldName) const;
+
+ /**
+ * This document source is sent as-is to the shards.
+ */
+ boost::intrusive_ptr<DocumentSource> getShardSource() final {
+ return this;
+ }
+
+ /**
+ * In a sharded cluster, this becomes a merge sort by distance, from nearest to furthest.
+ */
+ std::list<boost::intrusive_ptr<DocumentSource>> getMergeSources() final;
private:
explicit DocumentSourceGeoNear(const boost::intrusive_ptr<ExpressionContext>& pExpCtx);
+ /**
+ * Parses the fields in the object 'options', throwing if an error occurs.
+ */
void parseOptions(BSONObj options);
- BSONObj buildGeoNearCmd() const;
- void runCommand();
// These fields describe the command to run.
- // coords and distanceField are required, rest are optional
+ // 'coords' and 'distanceField' are required; the rest are optional.
BSONObj coords; // "near" option, but near is a reserved keyword on windows
bool coordsIsArray;
std::unique_ptr<FieldPath> distanceField; // Using unique_ptr because FieldPath can't be empty
- long long limit;
- double maxDistance;
- double minDistance;
BSONObj query;
bool spherical;
- double distanceMultiplier;
- std::unique_ptr<FieldPath> includeLocs;
-
- // The field path over which the command should run, extracted from the 'key' parameter passed
- // by the user. Or the empty string the user did not provide a 'key'.
- std::string keyFieldPath;
-
- // these fields are used while processing the results
- BSONObj cmdOutput;
- std::unique_ptr<BSONObjIterator> resultsIterator; // iterator over cmdOutput["results"]
+ boost::optional<double> maxDistance;
+ boost::optional<double> minDistance;
+ boost::optional<double> distanceMultiplier;
+ boost::optional<FieldPath> includeLocs;
+ boost::optional<FieldPath> keyFieldPath;
};
-
} // namespace mongo
diff --git a/src/mongo/db/pipeline/document_source_geo_near_cursor.cpp b/src/mongo/db/pipeline/document_source_geo_near_cursor.cpp
new file mode 100644
index 00000000000..4b1a9596fd9
--- /dev/null
+++ b/src/mongo/db/pipeline/document_source_geo_near_cursor.cpp
@@ -0,0 +1,118 @@
+/**
+ * Copyright (C) 2018 MongoDB Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License, version 3,
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * As a special exception, the copyright holders give permission to link the
+ * code of portions of this program with the OpenSSL library under certain
+ * conditions as described in each individual source file and distribute
+ * linked combinations including the program with the OpenSSL library. You
+ * must comply with the GNU Affero General Public License in all respects
+ * for all of the code used other than as permitted herein. If you modify
+ * file(s) with this exception, you may extend this exception to your
+ * version of the file(s), but you are not obligated to do so. If you do not
+ * wish to do so, delete this exception statement from your version. If you
+ * delete this exception statement from all source files in the program,
+ * then also delete it in the license file.
+ */
+
+#include "mongo/platform/basic.h"
+
+#include "mongo/db/pipeline/document_source_geo_near_cursor.h"
+
+#include <boost/intrusive_ptr.hpp>
+#include <boost/optional.hpp>
+#include <list>
+#include <memory>
+
+#include "mongo/base/string_data.h"
+#include "mongo/bson/bsonelement.h"
+#include "mongo/bson/bsonobj.h"
+#include "mongo/bson/simple_bsonobj_comparator.h"
+#include "mongo/db/catalog/collection.h"
+#include "mongo/db/pipeline/document.h"
+#include "mongo/db/pipeline/document_source_cursor.h"
+#include "mongo/db/pipeline/document_source_sort.h"
+#include "mongo/db/pipeline/expression_context.h"
+#include "mongo/db/pipeline/field_path.h"
+#include "mongo/db/query/plan_executor.h"
+
+namespace mongo {
+constexpr const char* DocumentSourceGeoNearCursor::kStageName;
+
+boost::intrusive_ptr<DocumentSourceGeoNearCursor> DocumentSourceGeoNearCursor::create(
+ Collection* collection,
+ std::unique_ptr<PlanExecutor, PlanExecutor::Deleter> exec,
+ const boost::intrusive_ptr<ExpressionContext>& expCtx,
+ FieldPath distanceField,
+ boost::optional<FieldPath> locationField,
+ double distanceMultiplier) {
+ return {new DocumentSourceGeoNearCursor(collection,
+ std::move(exec),
+ expCtx,
+ std::move(distanceField),
+ std::move(locationField),
+ distanceMultiplier)};
+}
+
+DocumentSourceGeoNearCursor::DocumentSourceGeoNearCursor(
+ Collection* collection,
+ std::unique_ptr<PlanExecutor, PlanExecutor::Deleter> exec,
+ const boost::intrusive_ptr<ExpressionContext>& expCtx,
+ FieldPath distanceField,
+ boost::optional<FieldPath> locationField,
+ double distanceMultiplier)
+ : DocumentSourceCursor(collection, std::move(exec), expCtx),
+ _distanceField(std::move(distanceField)),
+ _locationField(std::move(locationField)),
+ _distanceMultiplier(distanceMultiplier) {
+ invariant(_distanceMultiplier >= 0);
+}
+
+const char* DocumentSourceGeoNearCursor::getSourceName() const {
+ return kStageName;
+}
+
+BSONObjSet DocumentSourceGeoNearCursor::getOutputSorts() {
+ return SimpleBSONObjComparator::kInstance.makeBSONObjSet(
+ {BSON(_distanceField.fullPath() << 1)});
+}
+
+Document DocumentSourceGeoNearCursor::transformBSONObjToDocument(const BSONObj& obj) const {
+ MutableDocument output(Document::fromBsonWithMetaData(obj));
+
+ // Scale the distance by the requested factor.
+ invariant(output.peek().hasGeoNearDistance(),
+ str::stream()
+ << "Query returned a document that is unexpectedly missing the geoNear distance: "
+ << obj.jsonString());
+ const auto distance = output.peek().getGeoNearDistance() * _distanceMultiplier;
+
+ output.setNestedField(_distanceField, Value(distance));
+ if (_locationField) {
+ invariant(
+ output.peek().hasGeoNearPoint(),
+ str::stream()
+ << "Query returned a document that is unexpectedly missing the geoNear point: "
+ << obj.jsonString());
+ output.setNestedField(*_locationField, output.peek().getGeoNearPoint());
+ }
+
+ // In a cluster, $geoNear will be merged via $sort, so add the sort key.
+ if (pExpCtx->needsMerge) {
+ output.setSortKeyMetaField(BSON("" << distance));
+ }
+
+ return output.freeze();
+}
+} // namespace mongo
diff --git a/src/mongo/db/pipeline/document_source_geo_near_cursor.h b/src/mongo/db/pipeline/document_source_geo_near_cursor.h
new file mode 100644
index 00000000000..1e95ba5daa6
--- /dev/null
+++ b/src/mongo/db/pipeline/document_source_geo_near_cursor.h
@@ -0,0 +1,102 @@
+/**
+ * Copyright (C) 2018 MongoDB Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License, version 3,
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * As a special exception, the copyright holders give permission to link the
+ * code of portions of this program with the OpenSSL library under certain
+ * conditions as described in each individual source file and distribute
+ * linked combinations including the program with the OpenSSL library. You
+ * must comply with the GNU Affero General Public License in all respects
+ * for all of the code used other than as permitted herein. If you modify
+ * file(s) with this exception, you may extend this exception to your
+ * version of the file(s), but you are not obligated to do so. If you do not
+ * wish to do so, delete this exception statement from your version. If you
+ * delete this exception statement from all source files in the program,
+ * then also delete it in the license file.
+ */
+
+#pragma once
+
+#include <boost/intrusive_ptr.hpp>
+#include <boost/optional.hpp>
+#include <list>
+#include <memory>
+
+#include "mongo/base/string_data.h"
+#include "mongo/bson/bsonobj.h"
+#include "mongo/db/catalog/collection.h"
+#include "mongo/db/pipeline/document.h"
+#include "mongo/db/pipeline/document_source_cursor.h"
+#include "mongo/db/pipeline/expression_context.h"
+#include "mongo/db/pipeline/field_path.h"
+#include "mongo/db/query/plan_executor.h"
+
+namespace mongo {
+/**
+ * Like DocumentSourceCursor, this stage returns Documents from BSONObjs produced by a PlanExecutor,
+ * but does extra work to compute distances to satisfy a $near or $nearSphere query.
+ */
+class DocumentSourceGeoNearCursor final : public DocumentSourceCursor {
+public:
+ /**
+ * The name of this stage.
+ */
+ static constexpr auto kStageName = "$geoNearCursor";
+
+ /**
+ * Create a new DocumentSourceGeoNearCursor. If specified, 'distanceMultiplier' must be
+ * nonnegative.
+ */
+ static boost::intrusive_ptr<DocumentSourceGeoNearCursor> create(
+ Collection*,
+ std::unique_ptr<PlanExecutor, PlanExecutor::Deleter>,
+ const boost::intrusive_ptr<ExpressionContext>&,
+ FieldPath distanceField,
+ boost::optional<FieldPath> locationField = boost::none,
+ double distanceMultiplier = 1.0);
+
+ const char* getSourceName() const final;
+
+ /**
+ * $geoNear returns documents ordered from nearest to furthest, which is an ascending sort on
+ * '_distanceField'.
+ */
+ BSONObjSet getOutputSorts() final;
+
+private:
+ DocumentSourceGeoNearCursor(Collection*,
+ std::unique_ptr<PlanExecutor, PlanExecutor::Deleter>,
+ const boost::intrusive_ptr<ExpressionContext>&,
+ FieldPath distanceField,
+ boost::optional<FieldPath> locationField,
+ double distanceMultiplier);
+
+ ~DocumentSourceGeoNearCursor() = default;
+
+ /**
+ * Transforms 'obj' into a Document, calculating the distance.
+ */
+ Document transformBSONObjToDocument(const BSONObj& obj) const final;
+
+ // The output field in which to store the computed distance.
+ FieldPath _distanceField;
+
+ // The output field to store the point that matched, if specified.
+ boost::optional<FieldPath> _locationField;
+
+ // A multiplicative factor applied to each distance. For example, you can use this to convert
+ // radian distances into meters by multiplying by the radius of the Earth.
+ double _distanceMultiplier = 1.0;
+};
+} // namespace mongo
diff --git a/src/mongo/db/pipeline/document_source_geo_near_test.cpp b/src/mongo/db/pipeline/document_source_geo_near_test.cpp
index e1fdbc6d5e6..f6fe24096f5 100644
--- a/src/mongo/db/pipeline/document_source_geo_near_test.cpp
+++ b/src/mongo/db/pipeline/document_source_geo_near_test.cpp
@@ -44,45 +44,6 @@ namespace {
// This provides access to getExpCtx(), but we'll use a different name for this test suite.
using DocumentSourceGeoNearTest = AggregationContextFixture;
-TEST_F(DocumentSourceGeoNearTest, ShouldAbsorbSubsequentLimitStage) {
- auto geoNear = DocumentSourceGeoNear::create(getExpCtx());
-
- Pipeline::SourceContainer container;
- container.push_back(geoNear);
-
- ASSERT_EQUALS(geoNear->getLimit(), DocumentSourceGeoNear::kDefaultLimit);
-
- container.push_back(DocumentSourceLimit::create(getExpCtx(), 200));
- geoNear->optimizeAt(container.begin(), &container);
-
- ASSERT_EQUALS(container.size(), 1U);
- ASSERT_EQUALS(geoNear->getLimit(), DocumentSourceGeoNear::kDefaultLimit);
-
- container.push_back(DocumentSourceLimit::create(getExpCtx(), 50));
- geoNear->optimizeAt(container.begin(), &container);
-
- ASSERT_EQUALS(container.size(), 1U);
- ASSERT_EQUALS(geoNear->getLimit(), 50);
-
- container.push_back(DocumentSourceLimit::create(getExpCtx(), 30));
- geoNear->optimizeAt(container.begin(), &container);
-
- ASSERT_EQUALS(container.size(), 1U);
- ASSERT_EQUALS(geoNear->getLimit(), 30);
-}
-
-TEST_F(DocumentSourceGeoNearTest, ShouldReportOutputsAreSortedByDistanceField) {
- BSONObj queryObj = fromjson(
- "{geoNear: { near: {type: 'Point', coordinates: [0, 0]}, distanceField: 'dist', "
- "maxDistance: 2}}");
- auto geoNear = DocumentSourceGeoNear::createFromBson(queryObj.firstElement(), getExpCtx());
-
- BSONObjSet outputSort = geoNear->getOutputSorts();
-
- ASSERT_EQUALS(outputSort.count(BSON("dist" << -1)), 1U);
- ASSERT_EQUALS(outputSort.size(), 1U);
-}
-
TEST_F(DocumentSourceGeoNearTest, FailToParseIfKeyFieldNotAString) {
auto stageObj = fromjson("{$geoNear: {distanceField: 'dist', near: [0, 0], key: 1}}");
ASSERT_THROWS_CODE(DocumentSourceGeoNear::createFromBson(stageObj.firstElement(), getExpCtx()),
@@ -97,6 +58,35 @@ TEST_F(DocumentSourceGeoNearTest, FailToParseIfKeyIsTheEmptyString) {
ErrorCodes::BadValue);
}
+TEST_F(DocumentSourceGeoNearTest, FailToParseIfDistanceMultiplierIsNegative) {
+ auto stageObj =
+ fromjson("{$geoNear: {distanceField: 'dist', near: [0, 0], distanceMultiplier: -1.0}}");
+ ASSERT_THROWS_CODE(DocumentSourceGeoNear::createFromBson(stageObj.firstElement(), getExpCtx()),
+ AssertionException,
+ ErrorCodes::BadValue);
+}
+
+TEST_F(DocumentSourceGeoNearTest, FailToParseIfLimitFieldSpecified) {
+ auto stageObj = fromjson("{$geoNear: {distanceField: 'dist', near: [0, 0], limit: 1}}");
+ ASSERT_THROWS_CODE(DocumentSourceGeoNear::createFromBson(stageObj.firstElement(), getExpCtx()),
+ AssertionException,
+ 50858);
+}
+
+TEST_F(DocumentSourceGeoNearTest, FailToParseIfNumFieldSpecified) {
+ auto stageObj = fromjson("{$geoNear: {distanceField: 'dist', near: [0, 0], num: 1}}");
+ ASSERT_THROWS_CODE(DocumentSourceGeoNear::createFromBson(stageObj.firstElement(), getExpCtx()),
+ AssertionException,
+ 50857);
+}
+
+TEST_F(DocumentSourceGeoNearTest, FailToParseIfStartOptionIsSpecified) {
+ auto stageObj = fromjson("{$geoNear: {distanceField: 'dist', near: [0, 0], start: {}}}");
+ ASSERT_THROWS_CODE(DocumentSourceGeoNear::createFromBson(stageObj.firstElement(), getExpCtx()),
+ AssertionException,
+ 50856);
+}
+
TEST_F(DocumentSourceGeoNearTest, CanParseAndSerializeKeyField) {
auto stageObj = fromjson("{$geoNear: {distanceField: 'dist', near: [0, 0], key: 'a.b'}}");
auto geoNear = DocumentSourceGeoNear::createFromBson(stageObj.firstElement(), getExpCtx());
@@ -108,12 +98,9 @@ TEST_F(DocumentSourceGeoNearTest, CanParseAndSerializeKeyField) {
Value{Document{{"key", "a.b"_sd},
{"near", std::vector<Value>{Value{0}, Value{0}}},
{"distanceField", "dist"_sd},
- {"limit", 100},
{"query", BSONObj()},
- {"spherical", false},
- {"distanceMultiplier", 1}}}}}};
+ {"spherical", false}}}}}};
ASSERT_VALUE_EQ(expectedSerialization, serialized[0]);
}
-
} // namespace
} // namespace mongo
diff --git a/src/mongo/db/pipeline/document_source_group_test.cpp b/src/mongo/db/pipeline/document_source_group_test.cpp
index de99452f3d7..8d0ce4a6679 100644
--- a/src/mongo/db/pipeline/document_source_group_test.cpp
+++ b/src/mongo/db/pipeline/document_source_group_test.cpp
@@ -767,7 +767,7 @@ public:
ASSERT_EQUALS(1U, dependencies.fields.count("u"));
ASSERT_EQUALS(1U, dependencies.fields.count("v"));
ASSERT_EQUALS(false, dependencies.needWholeDocument);
- ASSERT_EQUALS(false, dependencies.getNeedTextScore());
+ ASSERT_EQUALS(false, dependencies.getNeedsMetadata(DepsTracker::MetadataType::TEXT_SCORE));
}
};
diff --git a/src/mongo/db/pipeline/document_source_limit_test.cpp b/src/mongo/db/pipeline/document_source_limit_test.cpp
index 6294a897ff2..b776de9789b 100644
--- a/src/mongo/db/pipeline/document_source_limit_test.cpp
+++ b/src/mongo/db/pipeline/document_source_limit_test.cpp
@@ -98,7 +98,7 @@ TEST_F(DocumentSourceLimitTest, ShouldNotIntroduceAnyDependencies) {
ASSERT_EQUALS(DocumentSource::SEE_NEXT, limit->getDependencies(&dependencies));
ASSERT_EQUALS(0U, dependencies.fields.size());
ASSERT_EQUALS(false, dependencies.needWholeDocument);
- ASSERT_EQUALS(false, dependencies.getNeedTextScore());
+ ASSERT_EQUALS(false, dependencies.getNeedsMetadata(DepsTracker::MetadataType::TEXT_SCORE));
}
TEST_F(DocumentSourceLimitTest, ShouldPropagatePauses) {
diff --git a/src/mongo/db/pipeline/document_source_match.cpp b/src/mongo/db/pipeline/document_source_match.cpp
index 47e85a4c6e7..45bc8c8629e 100644
--- a/src/mongo/db/pipeline/document_source_match.cpp
+++ b/src/mongo/db/pipeline/document_source_match.cpp
@@ -485,7 +485,7 @@ DocumentSource::GetDepsReturn DocumentSourceMatch::getDependencies(DepsTracker*
// A $text aggregation field should return EXHAUSTIVE_FIELDS, since we don't necessarily
// know what field it will be searching without examining indices.
deps->needWholeDocument = true;
- deps->setNeedTextScore(true);
+ deps->setNeedsMetadata(DepsTracker::MetadataType::TEXT_SCORE, true);
return EXHAUSTIVE_FIELDS;
}
diff --git a/src/mongo/db/pipeline/document_source_match_test.cpp b/src/mongo/db/pipeline/document_source_match_test.cpp
index 4e14a990c7a..5f81dc71986 100644
--- a/src/mongo/db/pipeline/document_source_match_test.cpp
+++ b/src/mongo/db/pipeline/document_source_match_test.cpp
@@ -219,7 +219,7 @@ TEST_F(DocumentSourceMatchTest, ShouldAddDependenciesOfAllBranchesOfOrClause) {
ASSERT_EQUALS(1U, dependencies.fields.count("x.y"));
ASSERT_EQUALS(2U, dependencies.fields.size());
ASSERT_EQUALS(false, dependencies.needWholeDocument);
- ASSERT_EQUALS(false, dependencies.getNeedTextScore());
+ ASSERT_EQUALS(false, dependencies.getNeedsMetadata(DepsTracker::MetadataType::TEXT_SCORE));
}
TEST_F(DocumentSourceMatchTest, TextSearchShouldRequireWholeDocumentAndTextScore) {
@@ -227,7 +227,7 @@ TEST_F(DocumentSourceMatchTest, TextSearchShouldRequireWholeDocumentAndTextScore
DepsTracker dependencies(DepsTracker::MetadataAvailable::kTextScore);
ASSERT_EQUALS(DocumentSource::EXHAUSTIVE_FIELDS, match->getDependencies(&dependencies));
ASSERT_EQUALS(true, dependencies.needWholeDocument);
- ASSERT_EQUALS(true, dependencies.getNeedTextScore());
+ ASSERT_EQUALS(true, dependencies.getNeedsMetadata(DepsTracker::MetadataType::TEXT_SCORE));
}
TEST_F(DocumentSourceMatchTest, ShouldOnlyAddOuterFieldAsDependencyOfImplicitEqualityPredicate) {
@@ -238,7 +238,7 @@ TEST_F(DocumentSourceMatchTest, ShouldOnlyAddOuterFieldAsDependencyOfImplicitEqu
ASSERT_EQUALS(1U, dependencies.fields.count("a"));
ASSERT_EQUALS(1U, dependencies.fields.size());
ASSERT_EQUALS(false, dependencies.needWholeDocument);
- ASSERT_EQUALS(false, dependencies.getNeedTextScore());
+ ASSERT_EQUALS(false, dependencies.getNeedsMetadata(DepsTracker::MetadataType::TEXT_SCORE));
}
TEST_F(DocumentSourceMatchTest, ShouldOnlyAddOuterFieldAsDependencyOfClausesWithinElemMatch) {
@@ -249,7 +249,7 @@ TEST_F(DocumentSourceMatchTest, ShouldOnlyAddOuterFieldAsDependencyOfClausesWith
ASSERT_EQUALS(1U, dependencies.fields.count("a"));
ASSERT_EQUALS(1U, dependencies.fields.size());
ASSERT_EQUALS(false, dependencies.needWholeDocument);
- ASSERT_EQUALS(false, dependencies.getNeedTextScore());
+ ASSERT_EQUALS(false, dependencies.getNeedsMetadata(DepsTracker::MetadataType::TEXT_SCORE));
}
TEST_F(DocumentSourceMatchTest,
@@ -266,7 +266,7 @@ TEST_F(DocumentSourceMatchTest,
ASSERT_EQUALS(1U, dependencies.fields.count("a"));
ASSERT_EQUALS(1U, dependencies.fields.size());
ASSERT_EQUALS(false, dependencies.needWholeDocument);
- ASSERT_EQUALS(false, dependencies.getNeedTextScore());
+ ASSERT_EQUALS(false, dependencies.getNeedsMetadata(DepsTracker::MetadataType::TEXT_SCORE));
}
TEST_F(DocumentSourceMatchTest,
@@ -277,7 +277,7 @@ TEST_F(DocumentSourceMatchTest,
ASSERT_EQUALS(DocumentSource::SEE_NEXT, match->getDependencies(&dependencies));
ASSERT_EQUALS(0U, dependencies.fields.size());
ASSERT_EQUALS(true, dependencies.needWholeDocument);
- ASSERT_EQUALS(false, dependencies.getNeedTextScore());
+ ASSERT_EQUALS(false, dependencies.getNeedsMetadata(DepsTracker::MetadataType::TEXT_SCORE));
}
TEST_F(DocumentSourceMatchTest,
@@ -288,7 +288,7 @@ TEST_F(DocumentSourceMatchTest,
ASSERT_EQUALS(DocumentSource::SEE_NEXT, match->getDependencies(&dependencies1));
ASSERT_EQUALS(0U, dependencies1.fields.size());
ASSERT_EQUALS(true, dependencies1.needWholeDocument);
- ASSERT_EQUALS(false, dependencies1.getNeedTextScore());
+ ASSERT_EQUALS(false, dependencies1.getNeedsMetadata(DepsTracker::MetadataType::TEXT_SCORE));
query = fromjson("{a: {$_internalSchemaObjectMatch: {$_internalSchemaMaxProperties: 1}}}");
match = DocumentSourceMatch::create(query, getExpCtx());
@@ -297,7 +297,7 @@ TEST_F(DocumentSourceMatchTest,
ASSERT_EQUALS(1U, dependencies2.fields.size());
ASSERT_EQUALS(1U, dependencies2.fields.count("a"));
ASSERT_EQUALS(false, dependencies2.needWholeDocument);
- ASSERT_EQUALS(false, dependencies2.getNeedTextScore());
+ ASSERT_EQUALS(false, dependencies2.getNeedsMetadata(DepsTracker::MetadataType::TEXT_SCORE));
}
TEST_F(DocumentSourceMatchTest,
@@ -310,7 +310,7 @@ TEST_F(DocumentSourceMatchTest,
ASSERT_EQUALS(DocumentSource::SEE_NEXT, match->getDependencies(&dependencies));
ASSERT_EQUALS(1U, dependencies.fields.size());
ASSERT_EQUALS(true, dependencies.needWholeDocument);
- ASSERT_EQUALS(false, dependencies.getNeedTextScore());
+ ASSERT_EQUALS(false, dependencies.getNeedsMetadata(DepsTracker::MetadataType::TEXT_SCORE));
}
TEST_F(DocumentSourceMatchTest,
@@ -321,7 +321,7 @@ TEST_F(DocumentSourceMatchTest,
ASSERT_EQUALS(DocumentSource::SEE_NEXT, match->getDependencies(&dependencies));
ASSERT_EQUALS(0U, dependencies.fields.size());
ASSERT_EQUALS(true, dependencies.needWholeDocument);
- ASSERT_EQUALS(false, dependencies.getNeedTextScore());
+ ASSERT_EQUALS(false, dependencies.getNeedsMetadata(DepsTracker::MetadataType::TEXT_SCORE));
}
TEST_F(DocumentSourceMatchTest, ShouldAddCorrectDependenciesForClausesWithInternalSchemaType) {
@@ -332,7 +332,7 @@ TEST_F(DocumentSourceMatchTest, ShouldAddCorrectDependenciesForClausesWithIntern
ASSERT_EQUALS(1U, dependencies.fields.size());
ASSERT_EQUALS(1U, dependencies.fields.count("a"));
ASSERT_EQUALS(false, dependencies.needWholeDocument);
- ASSERT_EQUALS(false, dependencies.getNeedTextScore());
+ ASSERT_EQUALS(false, dependencies.getNeedsMetadata(DepsTracker::MetadataType::TEXT_SCORE));
}
TEST_F(DocumentSourceMatchTest, ShouldAddCorrectDependenciesForClausesWithInternalSchemaCond) {
@@ -345,7 +345,7 @@ TEST_F(DocumentSourceMatchTest, ShouldAddCorrectDependenciesForClausesWithIntern
ASSERT_EQUALS(1U, dependencies.fields.count("b"));
ASSERT_EQUALS(1U, dependencies.fields.count("c"));
ASSERT_EQUALS(false, dependencies.needWholeDocument);
- ASSERT_EQUALS(false, dependencies.getNeedTextScore());
+ ASSERT_EQUALS(false, dependencies.getNeedsMetadata(DepsTracker::MetadataType::TEXT_SCORE));
}
TEST_F(DocumentSourceMatchTest, ShouldAddCorrectDependenciesForClausesWithInternalSchemaXor) {
@@ -358,7 +358,7 @@ TEST_F(DocumentSourceMatchTest, ShouldAddCorrectDependenciesForClausesWithIntern
ASSERT_EQUALS(1U, dependencies.fields.count("b"));
ASSERT_EQUALS(1U, dependencies.fields.count("c"));
ASSERT_EQUALS(false, dependencies.needWholeDocument);
- ASSERT_EQUALS(false, dependencies.getNeedTextScore());
+ ASSERT_EQUALS(false, dependencies.getNeedsMetadata(DepsTracker::MetadataType::TEXT_SCORE));
}
TEST_F(DocumentSourceMatchTest, ShouldAddCorrectDependenciesForClausesWithEmptyJSONSchema) {
@@ -368,7 +368,7 @@ TEST_F(DocumentSourceMatchTest, ShouldAddCorrectDependenciesForClausesWithEmptyJ
ASSERT_EQUALS(DocumentSource::SEE_NEXT, match->getDependencies(&dependencies));
ASSERT_EQUALS(0U, dependencies.fields.size());
ASSERT_EQUALS(false, dependencies.needWholeDocument);
- ASSERT_EQUALS(false, dependencies.getNeedTextScore());
+ ASSERT_EQUALS(false, dependencies.getNeedsMetadata(DepsTracker::MetadataType::TEXT_SCORE));
}
TEST_F(DocumentSourceMatchTest, ShouldAddCorrectDependenciesForClausesWithJSONSchemaProperties) {
@@ -379,7 +379,7 @@ TEST_F(DocumentSourceMatchTest, ShouldAddCorrectDependenciesForClausesWithJSONSc
ASSERT_EQUALS(1U, dependencies.fields.count("a"));
ASSERT_EQUALS(1U, dependencies.fields.size());
ASSERT_EQUALS(false, dependencies.needWholeDocument);
- ASSERT_EQUALS(false, dependencies.getNeedTextScore());
+ ASSERT_EQUALS(false, dependencies.getNeedsMetadata(DepsTracker::MetadataType::TEXT_SCORE));
}
TEST_F(DocumentSourceMatchTest, ShouldAddCorrectDependenciesForMultiplePredicatesWithJSONSchema) {
@@ -391,7 +391,7 @@ TEST_F(DocumentSourceMatchTest, ShouldAddCorrectDependenciesForMultiplePredicate
ASSERT_EQUALS(1U, dependencies.fields.count("a"));
ASSERT_EQUALS(1U, dependencies.fields.count("b"));
ASSERT_EQUALS(false, dependencies.needWholeDocument);
- ASSERT_EQUALS(false, dependencies.getNeedTextScore());
+ ASSERT_EQUALS(false, dependencies.getNeedsMetadata(DepsTracker::MetadataType::TEXT_SCORE));
}
TEST_F(DocumentSourceMatchTest, ShouldAddOuterFieldToDependenciesIfElemMatchContainsNoFieldNames) {
@@ -402,7 +402,7 @@ TEST_F(DocumentSourceMatchTest, ShouldAddOuterFieldToDependenciesIfElemMatchCont
ASSERT_EQUALS(1U, dependencies.fields.count("a"));
ASSERT_EQUALS(1U, dependencies.fields.size());
ASSERT_EQUALS(false, dependencies.needWholeDocument);
- ASSERT_EQUALS(false, dependencies.getNeedTextScore());
+ ASSERT_EQUALS(false, dependencies.getNeedsMetadata(DepsTracker::MetadataType::TEXT_SCORE));
}
TEST_F(DocumentSourceMatchTest, ShouldAddNotClausesFieldAsDependency) {
@@ -412,7 +412,7 @@ TEST_F(DocumentSourceMatchTest, ShouldAddNotClausesFieldAsDependency) {
ASSERT_EQUALS(1U, dependencies.fields.count("b"));
ASSERT_EQUALS(1U, dependencies.fields.size());
ASSERT_EQUALS(false, dependencies.needWholeDocument);
- ASSERT_EQUALS(false, dependencies.getNeedTextScore());
+ ASSERT_EQUALS(false, dependencies.getNeedsMetadata(DepsTracker::MetadataType::TEXT_SCORE));
}
TEST_F(DocumentSourceMatchTest, ShouldAddDependenciesOfEachNorClause) {
@@ -424,7 +424,7 @@ TEST_F(DocumentSourceMatchTest, ShouldAddDependenciesOfEachNorClause) {
ASSERT_EQUALS(1U, dependencies.fields.count("b.c"));
ASSERT_EQUALS(2U, dependencies.fields.size());
ASSERT_EQUALS(false, dependencies.needWholeDocument);
- ASSERT_EQUALS(false, dependencies.getNeedTextScore());
+ ASSERT_EQUALS(false, dependencies.getNeedsMetadata(DepsTracker::MetadataType::TEXT_SCORE));
}
TEST_F(DocumentSourceMatchTest, CommentShouldNotAddAnyDependencies) {
@@ -433,7 +433,7 @@ TEST_F(DocumentSourceMatchTest, CommentShouldNotAddAnyDependencies) {
ASSERT_EQUALS(DocumentSource::SEE_NEXT, match->getDependencies(&dependencies));
ASSERT_EQUALS(0U, dependencies.fields.size());
ASSERT_EQUALS(false, dependencies.needWholeDocument);
- ASSERT_EQUALS(false, dependencies.getNeedTextScore());
+ ASSERT_EQUALS(false, dependencies.getNeedsMetadata(DepsTracker::MetadataType::TEXT_SCORE));
}
TEST_F(DocumentSourceMatchTest, ClauseAndedWithCommentShouldAddDependencies) {
@@ -444,7 +444,7 @@ TEST_F(DocumentSourceMatchTest, ClauseAndedWithCommentShouldAddDependencies) {
ASSERT_EQUALS(1U, dependencies.fields.count("a"));
ASSERT_EQUALS(1U, dependencies.fields.size());
ASSERT_EQUALS(false, dependencies.needWholeDocument);
- ASSERT_EQUALS(false, dependencies.getNeedTextScore());
+ ASSERT_EQUALS(false, dependencies.getNeedsMetadata(DepsTracker::MetadataType::TEXT_SCORE));
}
TEST_F(DocumentSourceMatchTest, MultipleMatchStagesShouldCombineIntoOne) {
diff --git a/src/mongo/db/pipeline/document_source_project_test.cpp b/src/mongo/db/pipeline/document_source_project_test.cpp
index 5688d343675..c991eebbbc4 100644
--- a/src/mongo/db/pipeline/document_source_project_test.cpp
+++ b/src/mongo/db/pipeline/document_source_project_test.cpp
@@ -181,7 +181,7 @@ TEST_F(ProjectStageTest, InclusionShouldAddDependenciesOfIncludedAndComputedFiel
ASSERT_EQUALS(1U, dependencies.fields.count("c"));
ASSERT_EQUALS(1U, dependencies.fields.count("d"));
ASSERT_EQUALS(false, dependencies.needWholeDocument);
- ASSERT_EQUALS(true, dependencies.getNeedTextScore());
+ ASSERT_EQUALS(true, dependencies.getNeedsMetadata(DepsTracker::MetadataType::TEXT_SCORE));
}
TEST_F(ProjectStageTest, ExclusionShouldNotAddDependencies) {
@@ -192,7 +192,7 @@ TEST_F(ProjectStageTest, ExclusionShouldNotAddDependencies) {
ASSERT_EQUALS(0U, dependencies.fields.size());
ASSERT_EQUALS(false, dependencies.needWholeDocument);
- ASSERT_EQUALS(false, dependencies.getNeedTextScore());
+ ASSERT_EQUALS(false, dependencies.getNeedsMetadata(DepsTracker::MetadataType::TEXT_SCORE));
}
TEST_F(ProjectStageTest, InclusionProjectionReportsIncludedPathsFromGetModifiedPaths) {
diff --git a/src/mongo/db/pipeline/document_source_replace_root_test.cpp b/src/mongo/db/pipeline/document_source_replace_root_test.cpp
index 321afbc8220..0ac8a9f9db0 100644
--- a/src/mongo/db/pipeline/document_source_replace_root_test.cpp
+++ b/src/mongo/db/pipeline/document_source_replace_root_test.cpp
@@ -268,7 +268,7 @@ TEST_F(ReplaceRootBasics, OnlyDependentFieldIsNewRoot) {
// Should not need any other fields.
ASSERT_EQUALS(false, dependencies.needWholeDocument);
- ASSERT_EQUALS(false, dependencies.getNeedTextScore());
+ ASSERT_EQUALS(false, dependencies.getNeedsMetadata(DepsTracker::MetadataType::TEXT_SCORE));
}
TEST_F(ReplaceRootBasics, ReplaceRootModifiesAllFields) {
diff --git a/src/mongo/db/pipeline/document_source_sort.cpp b/src/mongo/db/pipeline/document_source_sort.cpp
index 30ee3a5b2e2..a3374521faf 100644
--- a/src/mongo/db/pipeline/document_source_sort.cpp
+++ b/src/mongo/db/pipeline/document_source_sort.cpp
@@ -226,7 +226,7 @@ DocumentSource::GetDepsReturn DocumentSourceSort::getDependencies(DepsTracker* d
}
if (pExpCtx->needsMerge) {
// Include the sort key if we will merge several sorted streams later.
- deps->setNeedSortKey(true);
+ deps->setNeedsMetadata(DepsTracker::MetadataType::SORT_KEY, true);
}
return SEE_NEXT;
diff --git a/src/mongo/db/pipeline/document_source_sort_test.cpp b/src/mongo/db/pipeline/document_source_sort_test.cpp
index fca4caaaf28..9f1f9606220 100644
--- a/src/mongo/db/pipeline/document_source_sort_test.cpp
+++ b/src/mongo/db/pipeline/document_source_sort_test.cpp
@@ -169,7 +169,7 @@ TEST_F(DocumentSourceSortTest, Dependencies) {
ASSERT_EQUALS(1U, dependencies.fields.count("a"));
ASSERT_EQUALS(1U, dependencies.fields.count("b.c"));
ASSERT_EQUALS(false, dependencies.needWholeDocument);
- ASSERT_EQUALS(false, dependencies.getNeedTextScore());
+ ASSERT_EQUALS(false, dependencies.getNeedsMetadata(DepsTracker::MetadataType::TEXT_SCORE));
}
TEST_F(DocumentSourceSortTest, OutputSort) {
diff --git a/src/mongo/db/pipeline/document_source_unwind_test.cpp b/src/mongo/db/pipeline/document_source_unwind_test.cpp
index 8c01c50bde9..af04d436083 100644
--- a/src/mongo/db/pipeline/document_source_unwind_test.cpp
+++ b/src/mongo/db/pipeline/document_source_unwind_test.cpp
@@ -680,7 +680,7 @@ TEST_F(UnwindStageTest, AddsUnwoundPathToDependencies) {
ASSERT_EQUALS(1U, dependencies.fields.size());
ASSERT_EQUALS(1U, dependencies.fields.count("x.y.z"));
ASSERT_EQUALS(false, dependencies.needWholeDocument);
- ASSERT_EQUALS(false, dependencies.getNeedTextScore());
+ ASSERT_EQUALS(false, dependencies.getNeedsMetadata(DepsTracker::MetadataType::TEXT_SCORE));
}
TEST_F(UnwindStageTest, TruncatesOutputSortAtUnwoundPath) {
diff --git a/src/mongo/db/pipeline/expression.cpp b/src/mongo/db/pipeline/expression.cpp
index 9094071c9d0..5cb0122f565 100644
--- a/src/mongo/db/pipeline/expression.cpp
+++ b/src/mongo/db/pipeline/expression.cpp
@@ -2534,7 +2534,7 @@ Value ExpressionMeta::evaluate(const Document& root) const {
void ExpressionMeta::_doAddDependencies(DepsTracker* deps) const {
if (_metaType == MetaType::TEXT_SCORE) {
- deps->setNeedTextScore(true);
+ deps->setNeedsMetadata(DepsTracker::MetadataType::TEXT_SCORE, true);
}
}
diff --git a/src/mongo/db/pipeline/expression_test.cpp b/src/mongo/db/pipeline/expression_test.cpp
index 374056289c3..2d7c4da922a 100644
--- a/src/mongo/db/pipeline/expression_test.cpp
+++ b/src/mongo/db/pipeline/expression_test.cpp
@@ -244,7 +244,7 @@ protected:
}
ASSERT_BSONOBJ_EQ(expectedDependencies, dependenciesBson.arr());
ASSERT_EQUALS(false, dependencies.needWholeDocument);
- ASSERT_EQUALS(false, dependencies.getNeedTextScore());
+ ASSERT_EQUALS(false, dependencies.getNeedsMetadata(DepsTracker::MetadataType::TEXT_SCORE));
}
void assertContents(const intrusive_ptr<Testable>& expr, const BSONArray& expectedContents) {
@@ -1565,7 +1565,7 @@ public:
ASSERT_EQUALS(1U, dependencies.fields.size());
ASSERT_EQUALS(1U, dependencies.fields.count("a.b"));
ASSERT_EQUALS(false, dependencies.needWholeDocument);
- ASSERT_EQUALS(false, dependencies.getNeedTextScore());
+ ASSERT_EQUALS(false, dependencies.getNeedsMetadata(DepsTracker::MetadataType::TEXT_SCORE));
}
};
@@ -2071,7 +2071,7 @@ public:
expression->addDependencies(&dependencies);
ASSERT_EQUALS(0U, dependencies.fields.size());
ASSERT_EQUALS(false, dependencies.needWholeDocument);
- ASSERT_EQUALS(false, dependencies.getNeedTextScore());
+ ASSERT_EQUALS(false, dependencies.getNeedsMetadata(DepsTracker::MetadataType::TEXT_SCORE));
}
};
@@ -2502,7 +2502,7 @@ public:
ASSERT_EQUALS(1U, dependencies.fields.size());
ASSERT_EQUALS(1U, dependencies.fields.count("a.b"));
ASSERT_EQUALS(false, dependencies.needWholeDocument);
- ASSERT_EQUALS(false, dependencies.getNeedTextScore());
+ ASSERT_EQUALS(false, dependencies.getNeedsMetadata(DepsTracker::MetadataType::TEXT_SCORE));
}
};
diff --git a/src/mongo/db/pipeline/parsed_exclusion_projection_test.cpp b/src/mongo/db/pipeline/parsed_exclusion_projection_test.cpp
index 348044018e9..33d915a47a9 100644
--- a/src/mongo/db/pipeline/parsed_exclusion_projection_test.cpp
+++ b/src/mongo/db/pipeline/parsed_exclusion_projection_test.cpp
@@ -120,7 +120,7 @@ TEST(ExclusionProjection, ShouldNotAddAnyDependencies) {
ASSERT_EQ(deps.fields.size(), 0UL);
ASSERT_FALSE(deps.needWholeDocument);
- ASSERT_FALSE(deps.getNeedTextScore());
+ ASSERT_FALSE(deps.getNeedsMetadata(DepsTracker::MetadataType::TEXT_SCORE));
}
TEST(ExclusionProjection, ShouldReportExcludedFieldsAsModified) {
diff --git a/src/mongo/db/pipeline/pipeline.cpp b/src/mongo/db/pipeline/pipeline.cpp
index 79dd962d871..6654b836a3e 100644
--- a/src/mongo/db/pipeline/pipeline.cpp
+++ b/src/mongo/db/pipeline/pipeline.cpp
@@ -69,6 +69,9 @@ using DiskUseRequirement = DocumentSource::StageConstraints::DiskUseRequirement;
using FacetRequirement = DocumentSource::StageConstraints::FacetRequirement;
using StreamType = DocumentSource::StageConstraints::StreamType;
+constexpr MatchExpressionParser::AllowedFeatureSet Pipeline::kAllowedMatcherFeatures;
+constexpr MatchExpressionParser::AllowedFeatureSet Pipeline::kGeoNearMatcherFeatures;
+
Pipeline::Pipeline(const intrusive_ptr<ExpressionContext>& pTheCtx) : pCtx(pTheCtx) {}
Pipeline::Pipeline(SourceContainer stages, const intrusive_ptr<ExpressionContext>& expCtx)
@@ -509,12 +512,9 @@ DepsTracker Pipeline::getDependencies(DepsTracker::MetadataAvailable metadataAva
}
if (!knowAllMeta) {
- if (localDeps.getNeedTextScore())
- deps.setNeedTextScore(true);
-
- if (localDeps.getNeedSortKey())
- deps.setNeedSortKey(true);
-
+ for (auto&& req : localDeps.getAllRequiredMetadataTypes()) {
+ deps.setNeedsMetadata(req, true);
+ }
knowAllMeta = status & DocumentSource::EXHAUSTIVE_META;
}
@@ -531,11 +531,12 @@ DepsTracker Pipeline::getDependencies(DepsTracker::MetadataAvailable metadataAva
if (metadataAvailable & DepsTracker::MetadataAvailable::kTextScore) {
// If there is a text score, assume we need to keep it if we can't prove we don't. If we are
// the first half of a pipeline which has been split, future stages might need it.
- if (!knowAllMeta)
- deps.setNeedTextScore(true);
+ if (!knowAllMeta) {
+ deps.setNeedsMetadata(DepsTracker::MetadataType::TEXT_SCORE, true);
+ }
} else {
// If there is no text score available, then we don't need to ask for it.
- deps.setNeedTextScore(false);
+ deps.setNeedsMetadata(DepsTracker::MetadataType::TEXT_SCORE, false);
}
return deps;
diff --git a/src/mongo/db/pipeline/pipeline.h b/src/mongo/db/pipeline/pipeline.h
index 22a8f8a8f88..afcedc14ee6 100644
--- a/src/mongo/db/pipeline/pipeline.h
+++ b/src/mongo/db/pipeline/pipeline.h
@@ -69,7 +69,7 @@ public:
enum class SplitState { kUnsplit, kSplitForShards, kSplitForMerge };
/**
- * List of supported match expression features in a pipeline.
+ * The list of default supported match expression features.
*/
static constexpr MatchExpressionParser::AllowedFeatureSet kAllowedMatcherFeatures =
MatchExpressionParser::AllowedFeatures::kText |
@@ -77,6 +77,15 @@ public:
MatchExpressionParser::AllowedFeatures::kJSONSchema;
/**
+ * The match expression features allowed when running a pipeline with $geoNear.
+ */
+ static constexpr MatchExpressionParser::AllowedFeatureSet kGeoNearMatcherFeatures =
+ MatchExpressionParser::AllowedFeatures::kText |
+ MatchExpressionParser::AllowedFeatures::kExpr |
+ MatchExpressionParser::AllowedFeatures::kJSONSchema |
+ MatchExpressionParser::AllowedFeatures::kGeoNear;
+
+ /**
* Parses a Pipeline from a vector of BSONObjs. Returns a non-OK status if it failed to parse.
* The returned pipeline is not optimized, but the caller may convert it to an optimized
* pipeline by calling optimizePipeline().
diff --git a/src/mongo/db/pipeline/pipeline_d.cpp b/src/mongo/db/pipeline/pipeline_d.cpp
index 7b5cd468130..9fec47e815d 100644
--- a/src/mongo/db/pipeline/pipeline_d.cpp
+++ b/src/mongo/db/pipeline/pipeline_d.cpp
@@ -58,6 +58,8 @@
#include "mongo/db/pipeline/document_source.h"
#include "mongo/db/pipeline/document_source_change_stream.h"
#include "mongo/db/pipeline/document_source_cursor.h"
+#include "mongo/db/pipeline/document_source_geo_near.h"
+#include "mongo/db/pipeline/document_source_geo_near_cursor.h"
#include "mongo/db/pipeline/document_source_match.h"
#include "mongo/db/pipeline/document_source_merge_cursors.h"
#include "mongo/db/pipeline/document_source_sample.h"
@@ -181,7 +183,8 @@ StatusWith<std::unique_ptr<PlanExecutor, PlanExecutor::Deleter>> attemptToGetExe
BSONObj projectionObj,
BSONObj sortObj,
const AggregationRequest* aggRequest,
- const size_t plannerOpts) {
+ const size_t plannerOpts,
+ const MatchExpressionParser::AllowedFeatureSet& matcherFeatures) {
auto qr = stdx::make_unique<QueryRequest>(nss);
qr->setTailableMode(pExpCtx->tailableMode);
qr->setOplogReplay(oplogReplay);
@@ -206,7 +209,7 @@ StatusWith<std::unique_ptr<PlanExecutor, PlanExecutor::Deleter>> attemptToGetExe
const ExtensionsCallbackReal extensionsCallback(pExpCtx->opCtx, &nss);
auto cq = CanonicalQuery::canonicalize(
- opCtx, std::move(qr), pExpCtx, extensionsCallback, Pipeline::kAllowedMatcherFeatures);
+ opCtx, std::move(qr), pExpCtx, extensionsCallback, matcherFeatures);
if (!cq.isOK()) {
// Return an error instead of uasserting, since there are cases where the combination of
@@ -226,6 +229,50 @@ BSONObj removeSortKeyMetaProjection(BSONObj projectionObj) {
}
return projectionObj.removeField(Document::metaFieldSortKey);
}
+
+/**
+ * Examines the indexes in 'collection' and returns the field name of a geo-indexed field suitable
+ * for use in $geoNear. 2d indexes are given priority over 2dsphere indexes.
+ *
+ * The 'collection' is required to exist. Throws if no usable 2d or 2dsphere index could be found.
+ */
+StringData extractGeoNearFieldFromIndexes(OperationContext* opCtx, Collection* collection) {
+ invariant(collection);
+
+ std::vector<IndexDescriptor*> idxs;
+ collection->getIndexCatalog()->findIndexByType(opCtx, IndexNames::GEO_2D, idxs);
+ uassert(ErrorCodes::IndexNotFound,
+ str::stream() << "There is more than one 2d index on " << collection->ns().ns()
+ << "; unsure which to use for $geoNear",
+ idxs.size() <= 1U);
+ if (idxs.size() == 1U) {
+ for (auto&& elem : idxs.front()->keyPattern()) {
+ if (elem.type() == BSONType::String && elem.valueStringData() == IndexNames::GEO_2D) {
+ return elem.fieldNameStringData();
+ }
+ }
+ MONGO_UNREACHABLE;
+ }
+
+ // If there are no 2d indexes, look for a 2dsphere index.
+ idxs.clear();
+ collection->getIndexCatalog()->findIndexByType(opCtx, IndexNames::GEO_2DSPHERE, idxs);
+ uassert(ErrorCodes::IndexNotFound,
+ "$geoNear requires a 2d or 2dsphere index, but none were found",
+ !idxs.empty());
+ uassert(ErrorCodes::IndexNotFound,
+ str::stream() << "There is more than one 2dsphere index on " << collection->ns().ns()
+ << "; unsure which to use for $geoNear",
+ idxs.size() <= 1U);
+
+ invariant(idxs.size() == 1U);
+ for (auto&& elem : idxs.front()->keyPattern()) {
+ if (elem.type() == BSONType::String && elem.valueStringData() == IndexNames::GEO_2DSPHERE) {
+ return elem.fieldNameStringData();
+ }
+ }
+ MONGO_UNREACHABLE;
+}
} // namespace
void PipelineD::prepareCursorSource(Collection* collection,
@@ -260,16 +307,32 @@ void PipelineD::prepareCursorSource(Collection* collection,
expCtx, sampleSize, idString, numRecords));
addCursorSource(
- collection,
pipeline,
- expCtx,
- std::move(exec),
+ DocumentSourceCursor::create(collection, std::move(exec), expCtx),
pipeline->getDependencies(DepsTracker::MetadataAvailable::kNoMetadata));
return;
}
}
}
+ // If the first stage is $geoNear, prepare a special DocumentSourceGeoNearCursor stage;
+ // otherwise, create a generic DocumentSourceCursor.
+ const auto geoNearStage =
+ sources.empty() ? nullptr : dynamic_cast<DocumentSourceGeoNear*>(sources.front().get());
+ if (geoNearStage) {
+ prepareGeoNearCursorSource(collection, nss, aggRequest, pipeline);
+ } else {
+ prepareGenericCursorSource(collection, nss, aggRequest, pipeline);
+ }
+}
+
+void PipelineD::prepareGenericCursorSource(Collection* collection,
+ const NamespaceString& nss,
+ const AggregationRequest* aggRequest,
+ Pipeline* pipeline) {
+ Pipeline::SourceContainer& sources = pipeline->_sources;
+ auto expCtx = pipeline->getContext();
+
// Look for an initial match. This works whether we got an initial query or not. If not, it
// results in a "{}" query, which will be what we want in that case.
bool oplogReplay = false;
@@ -283,7 +346,7 @@ void PipelineD::prepareCursorSource(Collection* collection,
sources.pop_front();
} else {
// A $geoNear stage, the only other stage that can produce an initial query, is also
- // a valid initial stage and will be handled above.
+ // a valid initial stage. However, we should be in prepareGeoNearCursorSource() instead.
MONGO_UNREACHABLE;
}
}
@@ -321,6 +384,7 @@ void PipelineD::prepareCursorSource(Collection* collection,
deps,
queryObj,
aggRequest,
+ Pipeline::kAllowedMatcherFeatures,
&sortObj,
&projForQuery));
@@ -335,8 +399,71 @@ void PipelineD::prepareCursorSource(Collection* collection,
}
}
- addCursorSource(
- collection, pipeline, expCtx, std::move(exec), deps, queryObj, sortObj, projForQuery);
+ addCursorSource(pipeline,
+ DocumentSourceCursor::create(collection, std::move(exec), expCtx),
+ deps,
+ queryObj,
+ sortObj,
+ projForQuery);
+}
+
+void PipelineD::prepareGeoNearCursorSource(Collection* collection,
+ const NamespaceString& nss,
+ const AggregationRequest* aggRequest,
+ Pipeline* pipeline) {
+ uassert(ErrorCodes::NamespaceNotFound,
+ str::stream() << "$geoNear requires a geo index to run, but " << nss.ns()
+ << " does not exist",
+ collection);
+
+ Pipeline::SourceContainer& sources = pipeline->_sources;
+ auto expCtx = pipeline->getContext();
+ const auto geoNearStage = dynamic_cast<DocumentSourceGeoNear*>(sources.front().get());
+ invariant(geoNearStage);
+
+ auto deps = pipeline->getDependencies(DepsTracker::kAllGeoNearDataAvailable);
+
+ // If the user specified a "key" field, use that field to satisfy the "near" query. Otherwise,
+ // look for a geo-indexed field in 'collection' that can.
+ auto nearFieldName =
+ (geoNearStage->getKeyField() ? geoNearStage->getKeyField()->fullPath()
+ : extractGeoNearFieldFromIndexes(expCtx->opCtx, collection))
+ .toString();
+
+ // Create a PlanExecutor whose query is the "near" predicate on 'nearFieldName' combined with
+ // the optional "query" argument in the $geoNear stage.
+ BSONObj fullQuery = geoNearStage->asNearQuery(nearFieldName);
+ BSONObj proj = deps.toProjection();
+ BSONObj sortFromQuerySystem;
+ auto exec = uassertStatusOK(prepareExecutor(expCtx->opCtx,
+ collection,
+ nss,
+ pipeline,
+ expCtx,
+ false, /* oplogReplay */
+ nullptr, /* sortStage */
+ deps,
+ std::move(fullQuery),
+ aggRequest,
+ Pipeline::kGeoNearMatcherFeatures,
+ &sortFromQuerySystem,
+ &proj));
+
+ invariant(sortFromQuerySystem.isEmpty(),
+ str::stream() << "Unexpectedly got the following sort from the query system: "
+ << sortFromQuerySystem.jsonString());
+
+ auto geoNearCursor =
+ DocumentSourceGeoNearCursor::create(collection,
+ std::move(exec),
+ expCtx,
+ geoNearStage->getDistanceField(),
+ geoNearStage->getLocationField(),
+ geoNearStage->getDistanceMultiplier().value_or(1.0));
+
+ // Remove the initial $geoNear; it will be replaced by $geoNearCursor.
+ sources.pop_front();
+ addCursorSource(pipeline, std::move(geoNearCursor), std::move(deps));
}
StatusWith<std::unique_ptr<PlanExecutor, PlanExecutor::Deleter>> PipelineD::prepareExecutor(
@@ -350,6 +477,7 @@ StatusWith<std::unique_ptr<PlanExecutor, PlanExecutor::Deleter>> PipelineD::prep
const DepsTracker& deps,
const BSONObj& queryObj,
const AggregationRequest* aggRequest,
+ const MatchExpressionParser::AllowedFeatureSet& matcherFeatures,
BSONObj* sortObj,
BSONObj* projectionObj) {
// The query system has the potential to use an index to provide a non-blocking sort and/or to
@@ -378,11 +506,11 @@ StatusWith<std::unique_ptr<PlanExecutor, PlanExecutor::Deleter>> PipelineD::prep
plannerOpts |= QueryPlannerParams::IS_COUNT;
}
- // The only way to get a text score or the sort key is to let the query system handle the
- // projection. In all other cases, unless the query system can do an index-covered projection
- // and avoid going to the raw record at all, it is faster to have ParsedDeps filter the fields
- // we need.
- if (!deps.getNeedTextScore() && !deps.getNeedSortKey()) {
+ // The only way to get meta information (e.g. the text score) is to let the query system handle
+ // the projection. In all other cases, unless the query system can do an index-covered
+ // projection and avoid going to the raw record at all, it is faster to have ParsedDeps filter
+ // the fields we need.
+ if (!deps.getNeedsAnyMetadata()) {
plannerOpts |= QueryPlannerParams::NO_UNCOVERED_PROJECTIONS;
}
@@ -405,7 +533,8 @@ StatusWith<std::unique_ptr<PlanExecutor, PlanExecutor::Deleter>> PipelineD::prep
expCtx->needsMerge ? metaSortProjection : emptyProjection,
*sortObj,
aggRequest,
- plannerOpts);
+ plannerOpts,
+ matcherFeatures);
if (swExecutorSort.isOK()) {
// Success! Now see if the query system can also cover the projection.
@@ -418,7 +547,8 @@ StatusWith<std::unique_ptr<PlanExecutor, PlanExecutor::Deleter>> PipelineD::prep
*projectionObj,
*sortObj,
aggRequest,
- plannerOpts);
+ plannerOpts,
+ matcherFeatures);
std::unique_ptr<PlanExecutor, PlanExecutor::Deleter> exec;
if (swExecutorSortAndProj.isOK()) {
@@ -458,7 +588,9 @@ StatusWith<std::unique_ptr<PlanExecutor, PlanExecutor::Deleter>> PipelineD::prep
// sort.
dassert(sortObj->isEmpty());
*projectionObj = removeSortKeyMetaProjection(*projectionObj);
- if (deps.getNeedSortKey() && !deps.getNeedTextScore()) {
+ const auto metadataRequired = deps.getAllRequiredMetadataTypes();
+ if (metadataRequired.size() == 1 &&
+ metadataRequired.front() == DepsTracker::MetadataType::SORT_KEY) {
// A sort key requirement would have prevented us from being able to add this parameter
// before, but now we know the query system won't cover the sort, so we will be able to
// compute the sort key ourselves during the $sort stage, and thus don't need a query
@@ -476,7 +608,8 @@ StatusWith<std::unique_ptr<PlanExecutor, PlanExecutor::Deleter>> PipelineD::prep
*projectionObj,
*sortObj,
aggRequest,
- plannerOpts);
+ plannerOpts,
+ matcherFeatures);
if (swExecutorProj.isOK()) {
// Success! We have a covered projection.
return std::move(swExecutorProj.getValue());
@@ -499,34 +632,24 @@ StatusWith<std::unique_ptr<PlanExecutor, PlanExecutor::Deleter>> PipelineD::prep
*projectionObj,
*sortObj,
aggRequest,
- plannerOpts);
+ plannerOpts,
+ matcherFeatures);
}
-void PipelineD::addCursorSource(Collection* collection,
- Pipeline* pipeline,
- const intrusive_ptr<ExpressionContext>& expCtx,
- unique_ptr<PlanExecutor, PlanExecutor::Deleter> exec,
+void PipelineD::addCursorSource(Pipeline* pipeline,
+ boost::intrusive_ptr<DocumentSourceCursor> cursor,
DepsTracker deps,
const BSONObj& queryObj,
const BSONObj& sortObj,
const BSONObj& projectionObj) {
- // DocumentSourceCursor expects a yielding PlanExecutor that has had its state saved.
- exec->saveState();
-
- // Put the PlanExecutor into a DocumentSourceCursor and add it to the front of the pipeline.
- intrusive_ptr<DocumentSourceCursor> pSource =
- DocumentSourceCursor::create(collection, std::move(exec), expCtx);
-
- // Note the query, sort, and projection for explain.
- pSource->setQuery(queryObj);
- pSource->setSort(sortObj);
-
+ cursor->setQuery(queryObj);
+ cursor->setSort(sortObj);
if (deps.hasNoRequirements()) {
- pSource->shouldProduceEmptyDocs();
+ cursor->shouldProduceEmptyDocs();
}
if (!projectionObj.isEmpty()) {
- pSource->setProjection(projectionObj, boost::none);
+ cursor->setProjection(projectionObj, boost::none);
} else {
// There may be fewer dependencies now if the sort was covered.
if (!sortObj.isEmpty()) {
@@ -535,9 +658,9 @@ void PipelineD::addCursorSource(Collection* collection,
: DepsTracker::MetadataAvailable::kNoMetadata);
}
- pSource->setProjection(deps.toProjection(), deps.toParsedDeps());
+ cursor->setProjection(deps.toProjection(), deps.toParsedDeps());
}
- pipeline->addInitialSource(pSource);
+ pipeline->addInitialSource(std::move(cursor));
}
Timestamp PipelineD::getLatestOplogTimestamp(const Pipeline* pipeline) {
diff --git a/src/mongo/db/pipeline/pipeline_d.h b/src/mongo/db/pipeline/pipeline_d.h
index 4064b38e7c8..f85b13d1001 100644
--- a/src/mongo/db/pipeline/pipeline_d.h
+++ b/src/mongo/db/pipeline/pipeline_d.h
@@ -35,6 +35,7 @@
#include "mongo/db/dbdirectclient.h"
#include "mongo/db/namespace_string.h"
#include "mongo/db/pipeline/aggregation_request.h"
+#include "mongo/db/pipeline/document_source_cursor.h"
#include "mongo/db/pipeline/mongo_process_common.h"
#include "mongo/db/query/plan_executor.h"
@@ -136,7 +137,7 @@ public:
/**
* If the first stage in the pipeline does not generate its own output documents, attaches a
- * DocumentSourceCursor to the front of the pipeline which will output documents from the
+ * cursor document source to the front of the pipeline which will output documents from the
* collection to feed into the pipeline.
*
* This method looks for early pipeline stages that can be folded into the underlying
@@ -154,6 +155,24 @@ public:
Pipeline* pipeline);
/**
+ * Prepare a generic DocumentSourceCursor for 'pipeline'.
+ */
+ static void prepareGenericCursorSource(Collection* collection,
+ const NamespaceString& nss,
+ const AggregationRequest* aggRequest,
+ Pipeline* pipeline);
+
+ /**
+ * Prepare a special DocumentSourceGeoNearCursor for 'pipeline'. Unlike
+ * 'prepareGenericCursorSource()', throws if 'collection' does not exist, as the $geoNearCursor
+ * requires a 2d or 2dsphere index.
+ */
+ static void prepareGeoNearCursorSource(Collection* collection,
+ const NamespaceString& nss,
+ const AggregationRequest* aggRequest,
+ Pipeline* pipeline);
+
+ /**
* Injects a MongodInterface into stages which require access to mongod-specific functionality.
*/
static void injectMongodInterface(Pipeline* pipeline);
@@ -187,17 +206,17 @@ private:
const DepsTracker& deps,
const BSONObj& queryObj,
const AggregationRequest* aggRequest,
+ const MatchExpressionParser::AllowedFeatureSet& matcherFeatures,
BSONObj* sortObj,
BSONObj* projectionObj);
/**
- * Creates a DocumentSourceCursor from the given PlanExecutor and adds it to the front of the
- * Pipeline.
+ * Adds 'cursor' to the front of 'pipeline', using 'deps' to inform the cursor of its
+ * dependencies. If specified, 'queryObj', 'sortObj' and 'projectionObj' are passed to the
+ * cursor for explain reporting.
*/
- static void addCursorSource(Collection* collection,
- Pipeline* pipeline,
- const boost::intrusive_ptr<ExpressionContext>& expCtx,
- std::unique_ptr<PlanExecutor, PlanExecutor::Deleter> exec,
+ static void addCursorSource(Pipeline* pipeline,
+ boost::intrusive_ptr<DocumentSourceCursor> cursor,
DepsTracker deps,
const BSONObj& queryObj = BSONObj(),
const BSONObj& sortObj = BSONObj(),
diff --git a/src/mongo/db/pipeline/pipeline_test.cpp b/src/mongo/db/pipeline/pipeline_test.cpp
index c53262cfc47..a0573014a1b 100644
--- a/src/mongo/db/pipeline/pipeline_test.cpp
+++ b/src/mongo/db/pipeline/pipeline_test.cpp
@@ -2492,11 +2492,11 @@ TEST_F(PipelineDependenciesTest, EmptyPipelineShouldRequireWholeDocument) {
auto depsTracker = pipeline->getDependencies(DepsTracker::MetadataAvailable::kNoMetadata);
ASSERT_TRUE(depsTracker.needWholeDocument);
- ASSERT_FALSE(depsTracker.getNeedTextScore());
+ ASSERT_FALSE(depsTracker.getNeedsMetadata(DepsTracker::MetadataType::TEXT_SCORE));
depsTracker = pipeline->getDependencies(DepsTracker::MetadataAvailable::kTextScore);
ASSERT_TRUE(depsTracker.needWholeDocument);
- ASSERT_TRUE(depsTracker.getNeedTextScore());
+ ASSERT_TRUE(depsTracker.getNeedsMetadata(DepsTracker::MetadataType::TEXT_SCORE));
}
//
@@ -2557,7 +2557,7 @@ public:
class DocumentSourceNeedsOnlyTextScore : public DocumentSourceDependencyDummy {
public:
GetDepsReturn getDependencies(DepsTracker* deps) const final {
- deps->setNeedTextScore(true);
+ deps->setNeedsMetadata(DepsTracker::MetadataType::TEXT_SCORE, true);
return GetDepsReturn::EXHAUSTIVE_META;
}
@@ -2586,7 +2586,7 @@ TEST_F(PipelineDependenciesTest, ShouldRequireWholeDocumentIfAnyStageDoesNotSupp
auto depsTracker = pipeline->getDependencies(DepsTracker::MetadataAvailable::kNoMetadata);
ASSERT_TRUE(depsTracker.needWholeDocument);
// The inputs did not have a text score available, so we should not require a text score.
- ASSERT_FALSE(depsTracker.getNeedTextScore());
+ ASSERT_FALSE(depsTracker.getNeedsMetadata(DepsTracker::MetadataType::TEXT_SCORE));
// Now in the other order.
pipeline = unittest::assertGet(Pipeline::create({notSupported, needsASeeNext}, ctx));
@@ -2625,7 +2625,7 @@ TEST_F(PipelineDependenciesTest, ShouldNotAddAnyRequiredFieldsAfterFirstStageWit
auto depsTracker = pipeline->getDependencies(DepsTracker::MetadataAvailable::kNoMetadata);
ASSERT_FALSE(depsTracker.needWholeDocument);
- ASSERT_FALSE(depsTracker.getNeedTextScore());
+ ASSERT_FALSE(depsTracker.getNeedsMetadata(DepsTracker::MetadataType::TEXT_SCORE));
// 'needsOnlyB' claims to know all its field dependencies, so we shouldn't add any from
// 'needsASeeNext'.
@@ -2638,7 +2638,7 @@ TEST_F(PipelineDependenciesTest, ShouldNotRequireTextScoreIfThereIsNoScoreAvaila
auto pipeline = unittest::assertGet(Pipeline::create({}, ctx));
auto depsTracker = pipeline->getDependencies(DepsTracker::MetadataAvailable::kNoMetadata);
- ASSERT_FALSE(depsTracker.getNeedTextScore());
+ ASSERT_FALSE(depsTracker.getNeedsMetadata(DepsTracker::MetadataType::TEXT_SCORE));
}
TEST_F(PipelineDependenciesTest, ShouldThrowIfTextScoreIsNeededButNotPresent) {
@@ -2655,12 +2655,12 @@ TEST_F(PipelineDependenciesTest, ShouldRequireTextScoreIfAvailableAndNoStageRetu
auto pipeline = unittest::assertGet(Pipeline::create({}, ctx));
auto depsTracker = pipeline->getDependencies(DepsTracker::MetadataAvailable::kTextScore);
- ASSERT_TRUE(depsTracker.getNeedTextScore());
+ ASSERT_TRUE(depsTracker.getNeedsMetadata(DepsTracker::MetadataType::TEXT_SCORE));
auto needsASeeNext = DocumentSourceNeedsASeeNext::create();
pipeline = unittest::assertGet(Pipeline::create({needsASeeNext}, ctx));
depsTracker = pipeline->getDependencies(DepsTracker::MetadataAvailable::kTextScore);
- ASSERT_TRUE(depsTracker.getNeedTextScore());
+ ASSERT_TRUE(depsTracker.getNeedsMetadata(DepsTracker::MetadataType::TEXT_SCORE));
}
TEST_F(PipelineDependenciesTest, ShouldNotRequireTextScoreIfAvailableButDefinitelyNotNeeded) {
@@ -2673,7 +2673,7 @@ TEST_F(PipelineDependenciesTest, ShouldNotRequireTextScoreIfAvailableButDefinite
// 'stripsTextScore' claims that no further stage will need metadata information, so we
// shouldn't have the text score as a dependency.
- ASSERT_FALSE(depsTracker.getNeedTextScore());
+ ASSERT_FALSE(depsTracker.getNeedsMetadata(DepsTracker::MetadataType::TEXT_SCORE));
}
} // namespace Dependencies
diff --git a/src/mongo/embedded/capi_test.cpp b/src/mongo/embedded/capi_test.cpp
index 0efaacba25f..2d35c8495bd 100644
--- a/src/mongo/embedded/capi_test.cpp
+++ b/src/mongo/embedded/capi_test.cpp
@@ -542,7 +542,6 @@ TEST_F(MongodbCAPITest, RunListCommands) {
"explain",
"find",
"findAndModify",
- "geoNear",
"getLastError",
"getMore",
"getParameter",
diff --git a/src/mongo/s/commands/SConscript b/src/mongo/s/commands/SConscript
index 9a6680c10ed..cfb4aca929c 100644
--- a/src/mongo/s/commands/SConscript
+++ b/src/mongo/s/commands/SConscript
@@ -51,7 +51,6 @@ env.Library(
'cluster_flush_router_config_cmd.cpp',
'cluster_fsync_cmd.cpp',
'cluster_ftdc_commands.cpp',
- 'cluster_geo_near_cmd.cpp',
'cluster_get_last_error_cmd.cpp',
'cluster_get_prev_error_cmd.cpp',
'cluster_get_shard_version_cmd.cpp',
diff --git a/src/mongo/s/commands/cluster_geo_near_cmd.cpp b/src/mongo/s/commands/cluster_geo_near_cmd.cpp
deleted file mode 100644
index 6871be7565e..00000000000
--- a/src/mongo/s/commands/cluster_geo_near_cmd.cpp
+++ /dev/null
@@ -1,186 +0,0 @@
-/**
- * Copyright (C) 2018 MongoDB Inc.
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License, version 3,
- * as published by the Free Software Foundation.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- *
- * As a special exception, the copyright holders give permission to link the
- * code of portions of this program with the OpenSSL library under certain
- * conditions as described in each individual source file and distribute
- * linked combinations including the program with the OpenSSL library. You
- * must comply with the GNU Affero General Public License in all respects for
- * all of the code used other than as permitted herein. If you modify file(s)
- * with this exception, you may extend this exception to your version of the
- * file(s), but you are not obligated to do so. If you do not wish to do so,
- * delete this exception statement from your version. If you delete this
- * exception statement from all source files in the program, then also delete
- * it in the license file.
- */
-
-#define MONGO_LOG_DEFAULT_COMPONENT ::mongo::logger::LogComponent::kCommand
-
-#include "mongo/platform/basic.h"
-
-#include "mongo/db/commands.h"
-#include "mongo/rpc/get_status_from_command_result.h"
-#include "mongo/s/commands/cluster_commands_helpers.h"
-#include "mongo/s/grid.h"
-#include "mongo/util/log.h"
-
-namespace mongo {
-namespace {
-
-class Geo2dFindNearCmd : public BasicCommand {
-public:
- Geo2dFindNearCmd() : BasicCommand("geoNear") {}
-
- std::string help() const override {
- return "The geoNear command is deprecated. See "
- "http://dochub.mongodb.org/core/geoNear-deprecation for more detail on its "
- "replacement.";
- }
-
- AllowedOnSecondary secondaryAllowed(ServiceContext*) const override {
- return AllowedOnSecondary::kAlways;
- }
-
- bool adminOnly() const override {
- return false;
- }
-
- bool supportsWriteConcern(const BSONObj& cmd) const override {
- return false;
- }
-
- std::string parseNs(const std::string& dbname, const BSONObj& cmdObj) const override {
- return CommandHelpers::parseNsCollectionRequired(dbname, cmdObj).ns();
- }
-
- void addRequiredPrivileges(const std::string& dbname,
- const BSONObj& cmdObj,
- std::vector<Privilege>* out) const override {
- ActionSet actions;
- actions.addAction(ActionType::find);
- out->push_back(Privilege(parseResourcePattern(dbname, cmdObj), actions));
- }
-
- bool run(OperationContext* opCtx,
- const std::string& dbName,
- const BSONObj& cmdObj,
- BSONObjBuilder& result) override {
- RARELY {
- warning() << "Support for the geoNear command has been deprecated. Please plan to "
- "rewrite geoNear commands using the $near query operator, the $nearSphere "
- "query operator, or the $geoNear aggregation stage. See "
- "http://dochub.mongodb.org/core/geoNear-deprecation.";
- }
-
- const NamespaceString nss(parseNs(dbName, cmdObj));
-
- // We support both "num" and "limit" options to control limit
- long long limit = 100;
- if (cmdObj["num"].isNumber())
- limit = cmdObj["num"].safeNumberLong();
- else if (cmdObj["limit"].isNumber())
- limit = cmdObj["limit"].safeNumberLong();
-
- const auto query = extractQuery(cmdObj);
- const auto collation = extractCollation(cmdObj);
-
- const auto routingInfo =
- uassertStatusOK(Grid::get(opCtx)->catalogCache()->getCollectionRoutingInfo(opCtx, nss));
-
- auto shardResponses = scatterGatherVersionedTargetByRoutingTable(
- opCtx,
- nss.db(),
- nss,
- routingInfo,
- CommandHelpers::filterCommandRequestForPassthrough(cmdObj),
- ReadPreferenceSetting::get(opCtx),
- Shard::RetryPolicy::kIdempotent,
- query,
- collation);
-
- std::multimap<double, BSONObj> results;
- BSONArrayBuilder shardArray;
- std::string nearStr;
- double time = 0;
- double btreelocs = 0;
- double nscanned = 0;
- double objectsLoaded = 0;
-
- for (const auto& shardResponse : shardResponses) {
- const auto response = uassertStatusOK(shardResponse.swResponse);
- uassertStatusOK(getStatusFromCommandResult(response.data));
-
- shardArray.append(shardResponse.shardId.toString());
- const auto& shardResult = response.data;
-
- if (shardResult.hasField("near")) {
- nearStr = shardResult["near"].String();
- }
- time += shardResult["stats"]["time"].Number();
- if (!shardResult["stats"]["btreelocs"].eoo()) {
- btreelocs += shardResult["stats"]["btreelocs"].Number();
- }
- nscanned += shardResult["stats"]["nscanned"].Number();
- if (!shardResult["stats"]["objectsLoaded"].eoo()) {
- objectsLoaded += shardResult["stats"]["objectsLoaded"].Number();
- }
-
- BSONForEach(obj, shardResult["results"].embeddedObject()) {
- results.insert(
- std::make_pair(obj["dis"].Number(), obj.embeddedObject().getOwned()));
- }
-
- // TODO: maybe shrink results if size() > limit
- }
-
- result.append("ns", nss.ns());
- result.append("near", nearStr);
-
- long long outCount = 0;
- double totalDistance = 0;
- double maxDistance = 0;
- {
- BSONArrayBuilder sub(result.subarrayStart("results"));
- for (std::multimap<double, BSONObj>::const_iterator it(results.begin()),
- end(results.end());
- it != end && outCount < limit;
- ++it, ++outCount) {
- totalDistance += it->first;
- maxDistance = it->first; // guaranteed to be highest so far
-
- sub.append(it->second);
- }
- sub.done();
- }
-
- {
- BSONObjBuilder sub(result.subobjStart("stats"));
- sub.append("time", time);
- sub.append("btreelocs", btreelocs);
- sub.append("nscanned", nscanned);
- sub.append("objectsLoaded", objectsLoaded);
- sub.append("avgDistance", (outCount == 0) ? 0 : (totalDistance / outCount));
- sub.append("maxDistance", maxDistance);
- sub.append("shards", shardArray.arr());
- sub.done();
- }
-
- return true;
- }
-
-} geo2dFindNearCmd;
-
-} // namespace
-} // namespace mongo