Skip to content

Commit aa85e8d

Browse files
committed
fix latency compensated query
1 parent e4a0d62 commit aa85e8d

File tree

3 files changed

+246
-0
lines changed

3 files changed

+246
-0
lines changed

Firestore/Example/Tests/SpecTests/json/query_spec_test.json

Lines changed: 203 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1166,5 +1166,208 @@
11661166
]
11671167
}
11681168
]
1169+
},
1170+
"Latency-compensated updates are included in query results": {
1171+
"describeName": "Queries:",
1172+
"itName": "Latency-compensated updates are included in query results",
1173+
"tags": [],
1174+
"config": {
1175+
"useGarbageCollection": true,
1176+
"numClients": 1
1177+
},
1178+
"steps": [
1179+
{
1180+
"userListen": [
1181+
2,
1182+
{
1183+
"path": "collection",
1184+
"filters": [],
1185+
"orderBys": []
1186+
}
1187+
],
1188+
"stateExpect": {
1189+
"activeTargets": {
1190+
"2": {
1191+
"query": {
1192+
"path": "collection",
1193+
"filters": [],
1194+
"orderBys": []
1195+
},
1196+
"resumeToken": ""
1197+
}
1198+
}
1199+
}
1200+
},
1201+
{
1202+
"watchAck": [
1203+
2
1204+
]
1205+
},
1206+
{
1207+
"watchEntity": {
1208+
"docs": [
1209+
{
1210+
"key": "collection/a",
1211+
"version": 1000,
1212+
"value": {
1213+
"match": false
1214+
},
1215+
"options": {
1216+
"hasLocalMutations": false,
1217+
"hasCommittedMutations": false
1218+
}
1219+
}
1220+
],
1221+
"targets": [
1222+
2
1223+
]
1224+
}
1225+
},
1226+
{
1227+
"watchCurrent": [
1228+
[
1229+
2
1230+
],
1231+
"resume-token-1000"
1232+
]
1233+
},
1234+
{
1235+
"watchSnapshot": {
1236+
"version": 1000,
1237+
"targetIds": []
1238+
},
1239+
"expect": [
1240+
{
1241+
"query": {
1242+
"path": "collection",
1243+
"filters": [],
1244+
"orderBys": []
1245+
},
1246+
"added": [
1247+
{
1248+
"key": "collection/a",
1249+
"version": 1000,
1250+
"value": {
1251+
"match": false
1252+
},
1253+
"options": {
1254+
"hasLocalMutations": false,
1255+
"hasCommittedMutations": false
1256+
}
1257+
}
1258+
],
1259+
"errorCode": 0,
1260+
"fromCache": false,
1261+
"hasPendingWrites": false
1262+
}
1263+
]
1264+
},
1265+
{
1266+
"userPatch": [
1267+
"collection/a",
1268+
{
1269+
"match": true
1270+
}
1271+
],
1272+
"expect": [
1273+
{
1274+
"query": {
1275+
"path": "collection",
1276+
"filters": [],
1277+
"orderBys": []
1278+
},
1279+
"modified": [
1280+
{
1281+
"key": "collection/a",
1282+
"version": 1000,
1283+
"value": {
1284+
"match": true
1285+
},
1286+
"options": {
1287+
"hasLocalMutations": true,
1288+
"hasCommittedMutations": false
1289+
}
1290+
}
1291+
],
1292+
"errorCode": 0,
1293+
"fromCache": false,
1294+
"hasPendingWrites": true
1295+
}
1296+
]
1297+
},
1298+
{
1299+
"userListen": [
1300+
4,
1301+
{
1302+
"path": "collection",
1303+
"filters": [
1304+
[
1305+
"match",
1306+
"==",
1307+
true
1308+
]
1309+
],
1310+
"orderBys": []
1311+
}
1312+
],
1313+
"stateExpect": {
1314+
"activeTargets": {
1315+
"2": {
1316+
"query": {
1317+
"path": "collection",
1318+
"filters": [],
1319+
"orderBys": []
1320+
},
1321+
"resumeToken": ""
1322+
},
1323+
"4": {
1324+
"query": {
1325+
"path": "collection",
1326+
"filters": [
1327+
[
1328+
"match",
1329+
"==",
1330+
true
1331+
]
1332+
],
1333+
"orderBys": []
1334+
},
1335+
"resumeToken": ""
1336+
}
1337+
}
1338+
},
1339+
"expect": [
1340+
{
1341+
"query": {
1342+
"path": "collection",
1343+
"filters": [
1344+
[
1345+
"match",
1346+
"==",
1347+
true
1348+
]
1349+
],
1350+
"orderBys": []
1351+
},
1352+
"added": [
1353+
{
1354+
"key": "collection/a",
1355+
"version": 1000,
1356+
"value": {
1357+
"match": true
1358+
},
1359+
"options": {
1360+
"hasLocalMutations": true,
1361+
"hasCommittedMutations": false
1362+
}
1363+
}
1364+
],
1365+
"errorCode": 0,
1366+
"fromCache": true,
1367+
"hasPendingWrites": true
1368+
}
1369+
]
1370+
}
1371+
]
11691372
}
11701373
}

Firestore/core/src/firebase/firestore/local/local_documents_view.h

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,15 @@ class LocalDocumentsView {
103103
/** Queries the remote documents and overlays mutations. */
104104
model::DocumentMap GetDocumentsMatchingCollectionQuery(FSTQuery* query);
105105

106+
/**
107+
* Find all `PatchMutation`s, and if their base documents are not in
108+
* `existingDocs` add them to the returning `DocumentMap`. The returned map
109+
* also has every document in `existingDocs`.
110+
*/
111+
model::DocumentMap AddMissingBaseDocument(
112+
const std::vector<FSTMutationBatch*>& matchingBatches,
113+
const model::DocumentMap& existingDocs);
114+
106115
RemoteDocumentCache* remote_document_cache_;
107116
MutationQueue* mutation_queue_;
108117
IndexManager* index_manager_;

Firestore/core/src/firebase/firestore/local/local_documents_view.mm

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -171,6 +171,8 @@
171171
std::vector<FSTMutationBatch*> matchingBatches =
172172
mutation_queue_->AllMutationBatchesAffectingQuery(query);
173173

174+
results = AddMissingBaseDocument(matchingBatches, results);
175+
174176
for (FSTMutationBatch* batch : matchingBatches) {
175177
for (FSTMutation* mutation : [batch mutations]) {
176178
// Only process documents belonging to the collection.
@@ -215,6 +217,38 @@
215217
return results;
216218
}
217219

220+
// It is possible that a `PatchMutation` can make a document match a query, even
221+
// if the version in the `RemoteDocumentCache` is not a match yet (waiting for
222+
// server to ack). To handle this, we find all document keys affected by the
223+
// `PatchMutation`s that are not in `existingDocs` yet, and back fill them via
224+
// `remoteDocumentCache.getAll`, otherwise those `PatchMutation`s will be
225+
// ignored because no base document can be found, and lead to missing results
226+
// for the query.
227+
DocumentMap LocalDocumentsView::AddMissingBaseDocument(
228+
const std::vector<FSTMutationBatch*>& matchingBatches,
229+
const DocumentMap& existingDocs) {
230+
DocumentKeySet missingDocKeys;
231+
for (FSTMutationBatch* batch : matchingBatches) {
232+
for (FSTMutation* mutation : [batch mutations]) {
233+
if ([mutation isKindOfClass:[FSTPatchMutation class]] &&
234+
existingDocs.underlying_map().find([mutation key]) ==
235+
existingDocs.underlying_map().end()) {
236+
missingDocKeys = missingDocKeys.insert([mutation key]);
237+
}
238+
}
239+
}
240+
241+
DocumentMap mergedDocs = existingDocs;
242+
MaybeDocumentMap missingDocs = remote_document_cache_->GetAll(missingDocKeys);
243+
for (const auto& kv : missingDocs) {
244+
if (kv.second != nil && [kv.second isKindOfClass:[FSTDocument class]]) {
245+
mergedDocs = mergedDocs.insert(kv.first, (FSTDocument*)kv.second);
246+
}
247+
}
248+
249+
return mergedDocs;
250+
}
251+
218252
} // namespace local
219253
} // namespace firestore
220254
} // namespace firebase

0 commit comments

Comments
 (0)