|
23 | 23 |
|
24 | 24 | import android.text.TextUtils;
|
25 | 25 | import androidx.annotation.Nullable;
|
26 |
| -import com.google.common.collect.ObjectArrays; |
27 | 26 | import com.google.firebase.Timestamp;
|
28 | 27 | import com.google.firebase.database.collection.ImmutableSortedMap;
|
29 | 28 | import com.google.firebase.firestore.auth.User;
|
|
39 | 38 | import com.google.firebase.firestore.model.Document;
|
40 | 39 | import com.google.firebase.firestore.model.DocumentKey;
|
41 | 40 | import com.google.firebase.firestore.model.FieldIndex;
|
| 41 | +import com.google.firebase.firestore.model.FieldIndex.IndexOffset; |
42 | 42 | import com.google.firebase.firestore.model.FieldPath;
|
43 | 43 | import com.google.firebase.firestore.model.ResourcePath;
|
44 | 44 | import com.google.firebase.firestore.model.SnapshotVersion;
|
@@ -76,6 +76,7 @@ final class SQLiteIndexManager implements IndexManager {
|
76 | 76 | * Maps from a target to its equivalent list of sub-targets. Each sub-target contains only one
|
77 | 77 | * term from the target's disjunctive normal form (DNF).
|
78 | 78 | */
|
| 79 | + // TODO(orquery): Find a way for the GC algorithm to remove the mapping once we remove a target. |
79 | 80 | private final Map<Target, List<Target>> targetToDnfSubTargets = new HashMap<>();
|
80 | 81 |
|
81 | 82 | /**
|
@@ -296,29 +297,34 @@ public boolean canServeFromIndex(Target target) {
|
296 | 297 | return true;
|
297 | 298 | }
|
298 | 299 |
|
299 |
| - @Override |
300 |
| - public FieldIndex.IndexOffset getLeastRecentIndexOffset(Collection<FieldIndex> fieldIndexes) { |
| 300 | + private IndexOffset getLeastRecentIndexOffset(Collection<FieldIndex> fieldIndexes) { |
301 | 301 | hardAssert(
|
302 | 302 | !fieldIndexes.isEmpty(),
|
303 | 303 | "Found empty index group when looking for least recent index offset.");
|
304 | 304 |
|
305 | 305 | Iterator<FieldIndex> it = fieldIndexes.iterator();
|
306 |
| - FieldIndex.IndexOffset minOffset = it.next().getIndexState().getOffset(); |
| 306 | + IndexOffset minOffset = it.next().getIndexState().getOffset(); |
307 | 307 | int minBatchId = minOffset.getLargestBatchId();
|
308 | 308 | while (it.hasNext()) {
|
309 |
| - FieldIndex.IndexOffset newOffset = it.next().getIndexState().getOffset(); |
| 309 | + IndexOffset newOffset = it.next().getIndexState().getOffset(); |
310 | 310 | if (newOffset.compareTo(minOffset) < 0) {
|
311 | 311 | minOffset = newOffset;
|
312 | 312 | }
|
313 | 313 | minBatchId = Math.max(newOffset.getLargestBatchId(), minBatchId);
|
314 | 314 | }
|
315 | 315 |
|
316 |
| - return FieldIndex.IndexOffset.create( |
317 |
| - minOffset.getReadTime(), minOffset.getDocumentKey(), minBatchId); |
| 316 | + return IndexOffset.create(minOffset.getReadTime(), minOffset.getDocumentKey(), minBatchId); |
| 317 | + } |
| 318 | + |
| 319 | + @Override |
| 320 | + public IndexOffset minOffset(String collectionGroup) { |
| 321 | + Collection<FieldIndex> fieldIndexes = getFieldIndexes(collectionGroup); |
| 322 | + hardAssert(!fieldIndexes.isEmpty(), "minOffset was called for collection without indexes"); |
| 323 | + return getLeastRecentIndexOffset(fieldIndexes); |
318 | 324 | }
|
319 | 325 |
|
320 | 326 | @Override
|
321 |
| - public FieldIndex.IndexOffset getLeastRecentIndexOffset(Target target) { |
| 327 | + public IndexOffset minOffset(Target target) { |
322 | 328 | hardAssert(
|
323 | 329 | canServeFromIndex(target),
|
324 | 330 | "Cannot find least recent index offset if target cannot be served from index.");
|
@@ -494,14 +500,19 @@ public Set<DocumentKey> getDocumentsMatchingTarget(Target target) {
|
494 | 500 | bindings.addAll(Arrays.asList(subQueryAndBindings).subList(1, subQueryAndBindings.length));
|
495 | 501 | }
|
496 | 502 |
|
497 |
| - String queryString = |
498 |
| - subQueries.size() == 1 |
499 |
| - ? |
500 |
| - // If there's only one subQuery, just execute the one subQuery. |
501 |
| - subQueries.get(0) |
502 |
| - : |
503 |
| - // Construct "subQuery1 UNION subQuery2 UNION ... LIMIT N" |
504 |
| - TextUtils.join(" UNION ", subQueries) + " LIMIT " + target.getLimit(); |
| 503 | + String queryString; |
| 504 | + if (subQueries.size() == 1) { |
| 505 | + // If there's only one subQuery, just execute the one subQuery. |
| 506 | + queryString = subQueries.get(0); |
| 507 | + } else { |
| 508 | + // Construct "SELECT * FROM (subQuery1 UNION subQuery2 UNION ...) LIMIT N" |
| 509 | + queryString = "SELECT * FROM (" + TextUtils.join(" UNION ", subQueries) + ")"; |
| 510 | + if (target.getLimit() != -1) { |
| 511 | + queryString = queryString + " LIMIT " + target.getLimit(); |
| 512 | + } |
| 513 | + } |
| 514 | + |
| 515 | + hardAssert(bindings.size() < 1000, "Cannot perform query with more than 999 bind elements"); |
505 | 516 |
|
506 | 517 | SQLitePersistence.Query query = db.query(queryString).binding(bindings.toArray());
|
507 | 518 |
|
@@ -569,7 +580,11 @@ private Object[] generateQueryAndBindings(
|
569 | 580 | // Fill in the bind ("question marks") variables.
|
570 | 581 | Object[] bindArgs =
|
571 | 582 | fillBounds(statementCount, indexId, arrayValues, lowerBounds, upperBounds, notIn);
|
572 |
| - return ObjectArrays.concat(sql.toString(), bindArgs); |
| 583 | + |
| 584 | + List<Object> result = new ArrayList<Object>(); |
| 585 | + result.add(sql.toString()); |
| 586 | + result.addAll(Arrays.asList(bindArgs)); |
| 587 | + return result.toArray(); |
573 | 588 | }
|
574 | 589 |
|
575 | 590 | /** Returns the bind arguments for all {@code statementCount} statements. */
|
@@ -758,7 +773,7 @@ private byte[] encodeSegments(FieldIndex fieldIndex) {
|
758 | 773 | }
|
759 | 774 |
|
760 | 775 | @Override
|
761 |
| - public void updateCollectionGroup(String collectionGroup, FieldIndex.IndexOffset offset) { |
| 776 | + public void updateCollectionGroup(String collectionGroup, IndexOffset offset) { |
762 | 777 | hardAssert(started, "IndexManager not started");
|
763 | 778 |
|
764 | 779 | ++memoizedMaxSequenceNumber;
|
|
0 commit comments