Skip to content

Commit e37a2da

Browse files
WaVEVtimgraham
authored andcommitted
INTPYTHON-833 Remove $facet from array output subqueries
1 parent 61661d9 commit e37a2da

File tree

4 files changed

+52
-108
lines changed

4 files changed

+52
-108
lines changed

django_mongodb_backend/fields/array.py

Lines changed: 14 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -310,37 +310,23 @@ class ArrayOverlap(ArrayRHSMixin, FieldGetDbPrepValueMixin, Lookup):
310310

311311
def get_subquery_wrapping_pipeline(self, compiler, connection, field_name, expr):
312312
return [
313+
{"$project": {"subquery_results": expr.as_mql(compiler, connection, as_expr=True)}},
314+
{"$unwind": "$subquery_results"},
313315
{
314-
"$facet": {
315-
"group": [
316-
{"$project": {"tmp_name": expr.as_mql(compiler, connection, as_expr=True)}},
317-
{
318-
"$unwind": "$tmp_name",
319-
},
320-
{
321-
"$group": {
322-
"_id": None,
323-
"tmp_name": {"$addToSet": "$tmp_name"},
324-
}
325-
},
326-
]
327-
}
328-
},
329-
{
330-
"$project": {
331-
field_name: {
332-
"$ifNull": [
333-
{
334-
"$getField": {
335-
"input": {"$arrayElemAt": ["$group", 0]},
336-
"field": "tmp_name",
337-
}
338-
},
339-
[],
340-
]
341-
}
316+
"$group": {
317+
"_id": None,
318+
"subquery_results": {"$addToSet": "$subquery_results"},
342319
}
343320
},
321+
# Workaround for https://jira.mongodb.org/browse/SERVER-114196:
322+
# $$NOW becomes unavailable after $unionWith, so it must be stored
323+
# beforehand to ensure it remains accessible later in the pipeline.
324+
{"$addFields": {"__now": "$$NOW"}},
325+
# Add an extra empty document to handle default values on empty
326+
# results.
327+
{"$unionWith": {"pipeline": [{"$documents": [{"subquery_results": []}]}]}},
328+
{"$limit": 1},
329+
{"$project": {field_name: "$subquery_results"}},
344330
]
345331

346332
def as_mql_expr(self, compiler, connection):

django_mongodb_backend/fields/embedded_model_array.py

Lines changed: 19 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -150,44 +150,28 @@ def get_subquery_wrapping_pipeline(self, compiler, connection, field_name, expr)
150150
# structure of EmbeddedModelArrayField on the RHS behaves similar to
151151
# ArrayField.
152152
return [
153+
{"$project": {"subquery_results": expr.as_mql(compiler, connection, as_expr=True)}},
154+
# Use an $unwind followed by a $group to concatenate all the values
155+
# from the RHS subquery.
156+
{"$unwind": "$subquery_results"},
157+
# The $group stage collects values into an array using $addToSet.
158+
# The use of {_id: null} results in a single grouped array, but
159+
# because arrays from multiple documents are aggregated, the result
160+
# is a list of lists.
153161
{
154-
"$facet": {
155-
"gathered_data": [
156-
{"$project": {"tmp_name": expr.as_mql(compiler, connection, as_expr=True)}},
157-
# To concatenate all the values from the RHS subquery,
158-
# use an $unwind followed by a $group.
159-
{
160-
"$unwind": "$tmp_name",
161-
},
162-
# The $group stage collects values into an array using
163-
# $addToSet. The use of {_id: null} results in a
164-
# single grouped array. However, because arrays from
165-
# multiple documents are aggregated, the result is a
166-
# list of lists.
167-
{
168-
"$group": {
169-
"_id": None,
170-
"tmp_name": {"$addToSet": "$tmp_name"},
171-
}
172-
},
173-
]
174-
}
175-
},
176-
{
177-
"$project": {
178-
field_name: {
179-
"$ifNull": [
180-
{
181-
"$getField": {
182-
"input": {"$arrayElemAt": ["$gathered_data", 0]},
183-
"field": "tmp_name",
184-
}
185-
},
186-
[],
187-
]
188-
}
162+
"$group": {
163+
"_id": None,
164+
"subquery_results": {"$addToSet": "$subquery_results"},
189165
}
190166
},
167+
# Workaround for https://jira.mongodb.org/browse/SERVER-114196:
168+
# $$NOW becomes unavailable after $unionWith, so it must be stored
169+
# beforehand to ensure it remains accessible later in the pipeline.
170+
{"$addFields": {"__now": "$$NOW"}},
171+
# Add a dummy document in case of an empty result.
172+
{"$unionWith": {"pipeline": [{"$documents": [{"subquery_results": []}]}]}},
173+
{"$limit": 1},
174+
{"$project": {field_name: "$subquery_results"}},
191175
]
192176

193177

django_mongodb_backend/lookups.py

Lines changed: 13 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -56,34 +56,21 @@ def inner(self, compiler, connection):
5656
def get_subquery_wrapping_pipeline(self, compiler, connection, field_name, expr): # noqa: ARG001
5757
return [
5858
{
59-
"$facet": {
60-
"group": [
61-
{
62-
"$group": {
63-
"_id": None,
64-
"tmp_name": {
65-
"$addToSet": expr.as_mql(compiler, connection, as_expr=True)
66-
},
67-
}
68-
}
69-
]
70-
}
71-
},
72-
{
73-
"$project": {
74-
field_name: {
75-
"$ifNull": [
76-
{
77-
"$getField": {
78-
"input": {"$arrayElemAt": ["$group", 0]},
79-
"field": "tmp_name",
80-
}
81-
},
82-
[],
83-
]
84-
}
59+
"$group": {
60+
"_id": None,
61+
# Use a temporal name in order to support field_name="_id".
62+
"subquery_results": {"$addToSet": expr.as_mql(compiler, connection, as_expr=True)},
8563
}
8664
},
65+
# Workaround for https://jira.mongodb.org/browse/SERVER-114196:
66+
# $$NOW becomes unavailable after $unionWith, so it must be stored
67+
# beforehand to ensure it remains accessible later in the pipeline.
68+
{"$addFields": {"__now": "$$NOW"}},
69+
# Add an extra empty document to handle default values on empty
70+
# results.
71+
{"$unionWith": {"pipeline": [{"$documents": [{"subquery_results": []}]}]}},
72+
{"$limit": 1},
73+
{"$project": {field_name: "$subquery_results"}},
8774
]
8875

8976

tests/lookup_/tests.py

Lines changed: 6 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -137,28 +137,15 @@ def test_subquery_filter_constant(self):
137137
"let": {},
138138
"pipeline": [
139139
{"$match": {"num": {"$gt": 2}}},
140+
{"$group": {"_id": None, "subquery_results": {"$addToSet": "$num"}}},
141+
{"$addFields": {"__now": "$$NOW"}},
140142
{
141-
"$facet": {
142-
"group": [
143-
{"$group": {"_id": None, "tmp_name": {"$addToSet": "$num"}}}
144-
]
145-
}
146-
},
147-
{
148-
"$project": {
149-
"num": {
150-
"$ifNull": [
151-
{
152-
"$getField": {
153-
"input": {"$arrayElemAt": ["$group", 0]},
154-
"field": "tmp_name",
155-
}
156-
},
157-
[],
158-
]
159-
}
143+
"$unionWith": {
144+
"pipeline": [{"$documents": [{"subquery_results": []}]}]
160145
}
161146
},
147+
{"$limit": 1},
148+
{"$project": {"num": "$subquery_results"}},
162149
],
163150
}
164151
},

0 commit comments

Comments
 (0)