|
I have confirmed that this was introduced by SERVER-21612, it works on 3.5.5 and not 3.5.6.
It looks like the issue is that we don't correctly descend the path on the $geoWithin predicate. There is code to convert a match like {"asPath.x": {$eq: 4}} into just {x: {$eq: 4}} so it can be absorbed into the foreign query, but it looks like it doesn't properly handle the $geoWithin predicate. You can see that when I explain a normal equality predicate on a sub-path of 'location', the $lookup stage in the explain output has removed the 'location' prefix of the path:
black-cherry(mongod-3.3.6) test> db.items.explain().aggregate([{$match:{id: 1}}, {$lookup:{from:"locations",localField:"location_id",foreignField:"id",as:"location"}},{$unwind:"$location"}, {$match:{"location.coordinates": 4}}])
|
{
|
"waitedMS": NumberLong("0"),
|
"stages": [
|
{
|
"$cursor": {
|
"query": {
|
"id": 1
|
},
|
"queryPlanner": {
|
"plannerVersion": 1,
|
"namespace": "test.items",
|
"indexFilterSet": false,
|
"parsedQuery": {
|
"id": {
|
"$eq": 1
|
}
|
},
|
"winningPlan": {
|
"stage": "COLLSCAN",
|
"filter": {
|
"id": {
|
"$eq": 1
|
}
|
},
|
"direction": "forward"
|
},
|
"rejectedPlans": [ ]
|
}
|
}
|
},
|
{
|
"$lookup": {
|
"from": "locations",
|
"as": "location",
|
"localField": "location_id",
|
"foreignField": "id",
|
"unwinding": {
|
"preserveNullAndEmptyArrays": false
|
},
|
"matching": {
|
"coordinates": {
|
"$eq": 4
|
}
|
}
|
}
|
}
|
],
|
"ok": 1
|
}
|
Contrast that to the explain output when I use a $geoWithin predicate:
{
|
"waitedMS": NumberLong("0"),
|
"stages": [
|
{
|
"$cursor": {
|
"query": {
|
"id": 1
|
},
|
"queryPlanner": {
|
"plannerVersion": 1,
|
"namespace": "test.items",
|
"indexFilterSet": false,
|
"parsedQuery": {
|
"id": {
|
"$eq": 1
|
}
|
},
|
"winningPlan": {
|
"stage": "COLLSCAN",
|
"filter": {
|
"id": {
|
"$eq": 1
|
}
|
},
|
"direction": "forward"
|
},
|
"rejectedPlans": [ ]
|
}
|
}
|
},
|
{
|
"$lookup": {
|
"from": "locations",
|
"as": "location",
|
"localField": "location_id",
|
"foreignField": "id",
|
"unwinding": {
|
"preserveNullAndEmptyArrays": false
|
},
|
"matching": {
|
"location.coordinates": {
|
"$geoWithin": {
|
"$geometry": {
|
"type": "MultiPolygon",
|
"coordinates": [
|
[
|
[
|
[
|
20,
|
70
|
],
|
[
|
30,
|
70
|
],
|
[
|
30,
|
50
|
],
|
[
|
20,
|
50
|
],
|
[
|
20,
|
70
|
]
|
]
|
]
|
]
|
}
|
}
|
}
|
}
|
}
|
}
|
],
|
"ok": 1
|
}
|
It looks like the problem is that the parsed GeoMatchExpression incorrectly implements serialize() as just appending the raw BSON that was input:
|
expression_geo.cpp(3.5.6)
|
372
|
void GeoMatchExpression::serialize(BSONObjBuilder* out) const {
|
373
|
out->appendElements(_rawObj);
|
374
|
}
|
This should instead re-build the BSON object, or somehow take into account that the path might have been modified since its construction.
|
|
I can reproduce this. I have 3.2.11 and 3.4.4 installed, but the same problem exists:
// 3.2.11
|
black-cherry(mongod-3.2.11) test> db.createCollection("locations")
|
{
|
"ok": 1
|
}
|
black-cherry(mongod-3.2.11) test> db.locations.insert({"coordinates":[25.266, 60.36938],"id":42})
|
Inserted 1 record(s) in 1ms
|
WriteResult({
|
"nInserted": 1
|
})
|
black-cherry(mongod-3.2.11) test> db.items.insert({"id":1, "location_id":42})
|
Inserted 1 record(s) in 20ms
|
WriteResult({
|
"nInserted": 1
|
})
|
black-cherry(mongod-3.2.11) test> db.items.aggregate([{$match:{id: 1}}, {$lookup:{from:"locations",localField:"location_id",foreignField:"id",as:"location"}},{$unwind:"$location"},
|
... {$match:{"location.coordinates":{$geoWithin:{"$geometry":{"type":"MultiPolygon","coordinates":[[[[20.0,70.0],[30.0,70.0],[30.0,50.0],[20.0,50.0],[20.0,70.0]]]]}}}}}])
|
{
|
"waitedMS": NumberLong("0"),
|
"cursor": {
|
"id": NumberLong("0"),
|
"ns": "test.items",
|
"firstBatch": [
|
{
|
"_id": ObjectId("5941367b4e05dd2ff966b156"),
|
"id": 1,
|
"location_id": 42,
|
"location": {
|
"_id": ObjectId("5941367b4e05dd2ff966b155"),
|
"coordinates": [
|
25.266,
|
60.36938
|
],
|
"id": 42
|
}
|
}
|
]
|
},
|
"ok": 1
|
}
|
black-cherry(mongod-3.2.11) test> db.items.explain().aggregate([{$match:{id: 1}}, {$lookup:{from:"locations",localField:"location_id",foreignField:"id",as:"location"}},{$unwind:"$location"}, {$match:{"location.coordinates":{$geoWithin:{"$geometry":{"type":"MultiPolygon","coordinates":[[[[20.0,70.0],[30.0,70.0],[30.0,50.0],[20.0,50.0],[20.0,70.0]]]]}}}}}])
|
{
|
"waitedMS": NumberLong("0"),
|
"stages": [
|
{
|
"$cursor": {
|
"query": {
|
"id": 1
|
},
|
"queryPlanner": {
|
"plannerVersion": 1,
|
"namespace": "test.items",
|
"indexFilterSet": false,
|
"parsedQuery": {
|
"id": {
|
"$eq": 1
|
}
|
},
|
"winningPlan": {
|
"stage": "COLLSCAN",
|
"filter": {
|
"id": {
|
"$eq": 1
|
}
|
},
|
"direction": "forward"
|
},
|
"rejectedPlans": [ ]
|
}
|
}
|
},
|
{
|
"$lookup": {
|
"from": "locations",
|
"as": "location",
|
"localField": "location_id",
|
"foreignField": "id",
|
"unwinding": {
|
"preserveNullAndEmptyArrays": false
|
}
|
}
|
},
|
{
|
"$match": {
|
"location.coordinates": {
|
"$geoWithin": {
|
"$geometry": {
|
"type": "MultiPolygon",
|
"coordinates": [
|
[
|
[
|
[
|
20,
|
70
|
],
|
[
|
30,
|
70
|
],
|
[
|
30,
|
50
|
],
|
[
|
20,
|
50
|
],
|
[
|
20,
|
70
|
]
|
]
|
]
|
]
|
}
|
}
|
}
|
}
|
}
|
],
|
"ok": 1
|
}
|
// 3.4.4
|
black-cherry(mongod-3.4.4) test> db.createCollection("items")
|
{
|
"ok": 1
|
}
|
black-cherry(mongod-3.4.4) test> db.createCollection("locations")
|
{
|
"ok": 1
|
}
|
black-cherry(mongod-3.4.4) test> db.locations.insert({"coordinates":[25.266, 60.36938],"id":42})
|
Inserted 1 record(s) in 2ms
|
WriteResult({
|
"nInserted": 1
|
})
|
black-cherry(mongod-3.4.4) test> db.items.insert({"id":1, "location_id":42})
|
Inserted 1 record(s) in 1ms
|
WriteResult({
|
"nInserted": 1
|
})
|
black-cherry(mongod-3.4.4) test> db.items.aggregate([{$match:{id: 1}}, {$lookup:{from:"locations",localField:"location_id",foreignField:"id",as:"location"}},{$unwind:"$location"},
|
... {$match:{"location.coordinates":{$geoWithin:{"$geometry":{"type":"MultiPolygon","coordinates":[[[[20.0,70.0],[30.0,70.0],[30.0,50.0],[20.0,50.0],[20.0,70.0]]]]}}}}}])
|
{
|
"cursor": {
|
"id": NumberLong("0"),
|
"ns": "test.items",
|
"firstBatch": [ ]
|
},
|
"ok": 1
|
}
|
black-cherry(mongod-3.4.4) test> db.items.explain().aggregate([{$match:{id: 1}}, {$lookup:{from:"locations",localField:"location_id",foreignField:"id",as:"location"}},{$unwind:"$location"}, {$match:{"location.coordinates":{$geoWithin:{"$geometry":{"type":"MultiPolygon","coordinates":[[[[20.0,70.0],[30.0,70.0],[30.0,50.0],[20.0,50.0],[20.0,70.0]]]]}}}}}])
|
{
|
"stages": [
|
{
|
"$cursor": {
|
"query": {
|
"id": 1
|
},
|
"queryPlanner": {
|
"plannerVersion": 1,
|
"namespace": "test.items",
|
"indexFilterSet": false,
|
"parsedQuery": {
|
"id": {
|
"$eq": 1
|
}
|
},
|
"winningPlan": {
|
"stage": "COLLSCAN",
|
"filter": {
|
"id": {
|
"$eq": 1
|
}
|
},
|
"direction": "forward"
|
},
|
"rejectedPlans": [ ]
|
}
|
}
|
},
|
{
|
"$lookup": {
|
"from": "locations",
|
"as": "location",
|
"localField": "location_id",
|
"foreignField": "id",
|
"unwinding": {
|
"preserveNullAndEmptyArrays": false
|
},
|
"matching": {
|
"location.coordinates": {
|
"$geoWithin": {
|
"$geometry": {
|
"type": "MultiPolygon",
|
"coordinates": [
|
[
|
[
|
[
|
20,
|
70
|
],
|
[
|
30,
|
70
|
],
|
[
|
30,
|
50
|
],
|
[
|
20,
|
50
|
],
|
[
|
20,
|
70
|
]
|
]
|
]
|
]
|
}
|
}
|
}
|
}
|
}
|
}
|
],
|
"ok": 1
|
}
|
You can see that in 3.4.4 we are absorbing the match into the $lookup stage, so my initial guess is that something about this process is going wrong (see SERVER-21612, fixed in 3.3.6). I'll check to see if this works on 3.3.5 but not 3.3.6.
|