Skip to content

Commit ebc90c8

Browse files
author
Xin Dong
committed
Fixed the bug where path expansion code does not handle sparse arrays correctly.
1 parent b091b95 commit ebc90c8

File tree

2 files changed

+21
-6
lines changed

2 files changed

+21
-6
lines changed

src/QLExpression.actor.cpp

Lines changed: 21 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -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
*/
102106
ACTOR 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
}

test/correctness/smoke/test_update.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,6 @@
2525
import pytest
2626

2727

28-
@pytest.mark.xfail
2928
def test_update_array_containing_none_value(fixture_collection):
3029
collection = fixture_collection
3130

0 commit comments

Comments
 (0)