@@ -98,6 +98,10 @@ ACTOR static Future<Void> doPathExpansionIfNotArray(PromiseStream<Reference<IRea
9898 * - Case 3: Component after array root is numeric and treating it as field name. Ex: a.b.1.c, b is array
9999 * of size 3 and treating "1" as field name. This can be expanded to a.b.0.1.c, a.b.1.1.c and a.b.2.1.c. As
100100 * Doc Layer serializes numeric field name and array index same way, we have to pass this information.
101+ *
102+ * NOTE: MongoDB does NOT do nested array expansion. For exmaple if the query is a.c and a happens to be an array of
103+ * size 2, the query will be expanded into a.0.c and a.1.c. Then if a[0] is again an array, it will NOT do the expansion
104+ * again. That's why in case 3 we call doPathExpansionIfNotArray(), instead of the normal doPathExpansion().
101105 */
102106ACTOR static Future<Void> doArrayExpansion (PromiseStream<Reference<IReadContext>> promises,
103107 Standalone<StringRef> queryPath,
@@ -126,6 +130,7 @@ ACTOR static Future<Void> doArrayExpansion(PromiseStream<Reference<IReadContext>
126130
127131 // Case 3 - Treat next component as field name.
128132 if (nextComponentIsNumeric) {
133+ // Do not do array expansion again.
129134 futures.push_back (doPathExpansionIfNotArray (promises, StringRef (newPath), arrayElementPath, document,
130135 expandLastArray, imputeNulls));
131136 } else { // Case 1
@@ -169,6 +174,12 @@ ACTOR static Future<Void> doPathExpansion(PromiseStream<Reference<IReadContext>>
169174 // expansion. So we also need to check that if the "most recent existing ancestor" is a primitive value, then
170175 // its direct parent is also not an array. If the MREA is an object, then no check is necessary, since MongoDB
171176 // switches back into null-imputing mode if that happens (even if we expanded an array right before).
177+
178+ // NOTE: since arrays are stored as sparse arrays, i.e. null values in the array will not be persisted at all
179+ // it has to impute a null if:
180+ // - last part of the path is numeric
181+ // - it's immediate parent is an array
182+ // - it's within the length of the array
172183 if (imputeNulls && !v.present () && !arrayAncestor.present ()) {
173184 std::vector<Future<Optional<DataValue>>> futureAncestors;
174185 futureAncestors.push_back (document->get (StringRef ()));
@@ -177,14 +188,19 @@ ACTOR static Future<Void> doPathExpansion(PromiseStream<Reference<IReadContext>>
177188
178189 std::vector<Optional<DataValue>> ancestors = wait (getAll (futureAncestors));
179190
180- for (auto j = ancestors.rbegin ();
181- !(j == ancestors.rend ()) /* yes, it can be !=, but MSVC has a bug, hence !(==)*/ ; ++j) {
182- if (j->present ()) {
183- DVTypeCode type = j->get ().getSortType ();
191+ for (int j = ancestors.size () - 1 ; j >= 0 ; --j) {
192+ if (ancestors[j].present ()) {
193+ DVTypeCode type = ancestors[j].get ().getSortType ();
184194 if (type == DVTypeCode::OBJECT ||
185195 (type != DVTypeCode::ARRAY &&
186- (j + 1 == ancestors. rend () || (j + 1 )-> get ().getSortType () != DVTypeCode::ARRAY))) {
196+ (j == 0 || ancestors[j - 1 ]. get ().getSortType () != DVTypeCode::ARRAY))) {
187197 promises.send (Reference<NullContext>(new NullContext ()));
198+ } else if (j == ancestors.size () - 2 && dk[dk.size () - 1 ][0 ] == (uint8_t )DVTypeCode::NUMBER &&
199+ type == DVTypeCode::ARRAY) {
200+ DataValue dv = DataValue::decode_key_part (dk[dk.size () - 1 ]);
201+ if (dv.getDouble () < ancestors[j].get ().getArraysize ()) {
202+ promises.send (Reference<NullContext>(new NullContext ()));
203+ }
188204 }
189205 break ;
190206 }
0 commit comments