Skip to content

Commit 4403f0a

Browse files
committed
Remove $facet from array output subqueries
1 parent d7276d7 commit 4403f0a

File tree

4 files changed

+51
-104
lines changed

4 files changed

+51
-104
lines changed

django_mongodb_backend/fields/array.py

Lines changed: 13 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -310,37 +310,24 @@ 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)}},
313314
{
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-
}
315+
"$unwind": "$subquery_results",
328316
},
329317
{
330-
"$project": {
331-
field_name: {
332-
"$ifNull": [
333-
{
334-
"$getField": {
335-
"input": {"$arrayElemAt": ["$group", 0]},
336-
"field": "tmp_name",
337-
}
338-
},
339-
[],
340-
]
341-
}
318+
"$group": {
319+
"_id": None,
320+
"subquery_results": {"$addToSet": "$subquery_results"},
342321
}
343322
},
323+
# Workaround for https://jira.mongodb.org/browse/SERVER-114196:
324+
# $$NOW becomes unavailable after $unionWith, so it must be stored
325+
# beforehand to ensure it remains accessible later in the pipeline.
326+
{"$addFields": {"__now": "$$NOW"}},
327+
# Add an empty extra document to handle default values on empty results.
328+
{"$unionWith": {"pipeline": [{"$documents": [{"subquery_results": []}]}]}},
329+
{"$limit": 1},
330+
{"$project": {field_name: "$subquery_results"}},
344331
]
345332

346333
def as_mql_expr(self, compiler, connection):

django_mongodb_backend/fields/embedded_model_array.py

Lines changed: 20 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -150,44 +150,31 @@ 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+
# To concatenate all the values from the RHS subquery,
155+
# use an $unwind followed by a $group.
153156
{
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-
}
157+
"$unwind": "$subquery_results",
175158
},
159+
# The $group stage collects values into an array using
160+
# $addToSet. The use of {_id: null} results in a
161+
# single grouped array. However, because arrays from
162+
# multiple documents are aggregated, the result is a
163+
# list of lists.
176164
{
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-
}
165+
"$group": {
166+
"_id": None,
167+
"subquery_results": {"$addToSet": "$subquery_results"},
189168
}
190169
},
170+
# Workaround for https://jira.mongodb.org/browse/SERVER-114196:
171+
# $$NOW becomes unavailable after $unionWith, so it must be stored
172+
# beforehand to ensure it remains accessible later in the pipeline.
173+
{"$addFields": {"__now": "$$NOW"}},
174+
# Add a dummy document in case of empty result.
175+
{"$unionWith": {"pipeline": [{"$documents": [{"subquery_results": []}]}]}},
176+
{"$limit": 1},
177+
{"$project": {field_name: "$subquery_results"}},
191178
]
192179

193180

django_mongodb_backend/lookups.py

Lines changed: 12 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -56,34 +56,20 @@ 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 empty extra document to handle default values on empty results.
70+
{"$unionWith": {"pipeline": [{"$documents": [{"subquery_results": []}]}]}},
71+
{"$limit": 1},
72+
{"$project": {field_name: "$subquery_results"}},
8773
]
8874

8975

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)