17
17
import static com .google .firebase .firestore .model .Values .isArray ;
18
18
import static com .google .firebase .firestore .util .Assert .fail ;
19
19
import static com .google .firebase .firestore .util .Assert .hardAssert ;
20
+ import static com .google .firebase .firestore .util .Util .advanceIterator ;
20
21
import static com .google .firebase .firestore .util .Util .repeatSequence ;
21
22
import static java .lang .Math .max ;
22
23
31
32
import com .google .firebase .firestore .index .DirectionalIndexByteEncoder ;
32
33
import com .google .firebase .firestore .index .FirestoreIndexValueWriter ;
33
34
import com .google .firebase .firestore .index .IndexByteEncoder ;
35
+ import com .google .firebase .firestore .index .IndexEntry ;
34
36
import com .google .firebase .firestore .model .Document ;
35
37
import com .google .firebase .firestore .model .DocumentKey ;
36
38
import com .google .firebase .firestore .model .FieldIndex ;
51
53
import java .util .List ;
52
54
import java .util .Map ;
53
55
import java .util .Set ;
56
+ import java .util .SortedSet ;
57
+ import java .util .TreeSet ;
54
58
55
59
/** A persisted implementation of IndexManager. */
56
60
final class SQLiteIndexManager implements IndexManager {
61
+
57
62
private static final String TAG = SQLiteIndexManager .class .getSimpleName ();
58
63
59
64
/**
@@ -224,10 +229,12 @@ public void updateIndexEntries(Collection<Document> documents) {
224
229
for (Document document : documents ) {
225
230
Collection <FieldIndex > fieldIndexes = getFieldIndexes (document .getKey ().getCollectionGroup ());
226
231
for (FieldIndex fieldIndex : fieldIndexes ) {
227
- boolean modified = writeEntries (document , fieldIndex );
228
- if (modified ) {
232
+ List <IndexEntry > existingEntries = getIndexEntries (document , fieldIndex );
233
+ List <IndexEntry > newEntries = computeEntries (document , fieldIndex );
234
+ if (!existingEntries .equals (newEntries )) {
229
235
// TODO(indexing): This would be much simpler with a sequence counter since we would
230
236
// always update the index to the next sequence value.
237
+ updateEntries (document , existingEntries , newEntries );
231
238
FieldIndex latestIndex =
232
239
updatedFieldIndexes .get (fieldIndex .getIndexId ()) != null
233
240
? updatedFieldIndexes .get (fieldIndex .getIndexId ())
@@ -245,6 +252,50 @@ public void updateIndexEntries(Collection<Document> documents) {
245
252
}
246
253
}
247
254
255
+ private void updateEntries (
256
+ Document document , List <IndexEntry > existingEntries , List <IndexEntry > updatedEntries ) {
257
+ if (Logger .isDebugEnabled ()) {
258
+ Logger .debug (TAG , "Updating index entries for document '%s'" , document .getKey ());
259
+ }
260
+
261
+ SortedSet <IndexEntry > existingIndexes = new TreeSet <>(existingEntries );
262
+ Iterator <IndexEntry > existingIt = existingIndexes .iterator ();
263
+ @ Nullable IndexEntry existingValue = advanceIterator (existingIt );
264
+
265
+ List <IndexEntry > updatedIndexes = new ArrayList <>(updatedEntries );
266
+ Iterator <IndexEntry > updatedIt = updatedIndexes .iterator ();
267
+ @ Nullable IndexEntry updatedValue = advanceIterator (updatedIt );
268
+
269
+ while (existingValue != null || updatedValue != null ) {
270
+ boolean deleted = false ;
271
+ boolean updated = false ;
272
+
273
+ if (existingValue != null && updatedValue != null ) {
274
+ int cmp = existingValue .compareTo (updatedValue );
275
+ if (cmp < 0 ) {
276
+ deleted = true ;
277
+ } else if (cmp > 0 ) {
278
+ updated = true ;
279
+ }
280
+ } else if (existingValue != null ) {
281
+ deleted = true ;
282
+ } else {
283
+ updated = true ;
284
+ }
285
+
286
+ if (deleted ) {
287
+ deleteIndexEntry (document , existingValue );
288
+ existingValue = advanceIterator (existingIt );
289
+ } else if (updated ) {
290
+ addIndexEntry (document , updatedValue );
291
+ updatedValue = advanceIterator (updatedIt );
292
+ } else {
293
+ existingValue = advanceIterator (existingIt );
294
+ updatedValue = advanceIterator (updatedIt );
295
+ }
296
+ }
297
+ }
298
+
248
299
@ Override
249
300
public Collection <FieldIndex > getFieldIndexes (String collectionGroup ) {
250
301
hardAssert (started , "IndexManager not started" );
@@ -276,84 +327,85 @@ private FieldIndex getPostUpdateIndex(FieldIndex baseIndex, SnapshotVersion newR
276
327
}
277
328
}
278
329
279
- /**
280
- * If applicable, writes index entries for the given document. Returns whether any index entry was
281
- * written.
282
- */
283
- private boolean writeEntries (Document document , FieldIndex fieldIndex ) {
284
- @ Nullable byte [] directionalValue = encodeDirectionalElements (fieldIndex , document );
285
- if (directionalValue == null ) {
286
- return false ;
287
- }
288
-
289
- @ Nullable FieldIndex .Segment arraySegment = fieldIndex .getArraySegment ();
290
- if (arraySegment != null ) {
291
- Value value = document .getField (arraySegment .getFieldPath ());
292
- if (!isArray (value )) {
293
- return false ;
294
- }
295
-
296
- for (Value arrayValue : value .getArrayValue ().getValuesList ()) {
297
- addSingleEntry (
298
- document , fieldIndex .getIndexId (), encodeSingleElement (arrayValue ), directionalValue );
299
- }
300
- return true ;
301
- } else {
302
- addSingleEntry (document , fieldIndex .getIndexId (), /* arrayValue= */ null , directionalValue );
303
- return true ;
304
- }
305
- }
306
-
307
- @ Override
308
- public void handleDocumentChange (@ Nullable Document oldDocument , @ Nullable Document newDocument ) {
309
- hardAssert (started , "IndexManager not started" );
310
- hardAssert (oldDocument == null , "Support for updating documents is not yet available" );
311
- hardAssert (newDocument != null , "Support for removing documents is not yet available" );
312
-
313
- DocumentKey documentKey = newDocument .getKey ();
314
- Collection <FieldIndex > fieldIndices = getFieldIndexes (documentKey .getCollectionGroup ());
315
- addIndexEntry (newDocument , fieldIndices );
316
- }
330
+ /** Creates a list of index entries for the given document. */
331
+ private List <IndexEntry > computeEntries (Document document , FieldIndex fieldIndex ) {
332
+ List <IndexEntry > indexEntries = new ArrayList <>();
317
333
318
- /**
319
- * Writes index entries for the field indexes that apply to the provided document.
320
- *
321
- * @param document The provided document to index.
322
- * @param fieldIndexes A list of field indexes to apply.
323
- */
324
- private void addIndexEntry (Document document , Collection <FieldIndex > fieldIndexes ) {
325
- List <FieldIndex > updatedIndexes = new ArrayList <>();
326
-
327
- for (FieldIndex fieldIndex : fieldIndexes ) {
328
- boolean modified = writeEntries (document , fieldIndex );
329
- if (modified ) {
330
- updatedIndexes .add (getPostUpdateIndex (fieldIndex , document .getVersion ()));
334
+ @ Nullable byte [] directionalValue = encodeDirectionalElements (fieldIndex , document );
335
+ if (directionalValue != null ) {
336
+ @ Nullable FieldIndex .Segment arraySegment = fieldIndex .getArraySegment ();
337
+ if (arraySegment != null ) {
338
+ Value value = document .getField (arraySegment .getFieldPath ());
339
+ if (isArray (value )) {
340
+ for (Value arrayValue : value .getArrayValue ().getValuesList ()) {
341
+ indexEntries .add (
342
+ IndexEntry .create (
343
+ fieldIndex .getIndexId (),
344
+ document .getKey ().getName (),
345
+ document .hasLocalMutations () ? user .getUid () : null ,
346
+ directionalValue ,
347
+ encodeSingleElement (arrayValue )));
348
+ }
349
+ }
350
+ } else {
351
+ indexEntries .add (
352
+ IndexEntry .create (
353
+ fieldIndex .getIndexId (),
354
+ document .getKey ().getName (),
355
+ document .hasLocalMutations () ? user .getUid () : null ,
356
+ directionalValue ,
357
+ /* arrayValue= */ null ));
331
358
}
332
359
}
333
360
334
- for (FieldIndex updatedIndex : updatedIndexes ) {
335
- updateFieldIndex (updatedIndex );
336
- }
361
+ return indexEntries ;
337
362
}
338
363
339
364
/** Adds a single index entry into the index entries table. */
340
- private void addSingleEntry (
341
- Document document , int indexId , @ Nullable Object arrayValue , Object directionalValue ) {
342
- if (Logger .isDebugEnabled ()) {
343
- Logger .debug (
344
- TAG , "Adding index values for document '%s' to index '%s'" , document .getKey (), indexId );
345
- }
346
-
365
+ private void addIndexEntry (Document document , IndexEntry indexEntry ) {
347
366
db .execute (
348
367
"INSERT INTO index_entries (index_id, uid, array_value, directional_value, document_name) "
349
368
+ "VALUES(?, ?, ?, ?, ?)" ,
350
- indexId ,
369
+ indexEntry . getIndexId () ,
351
370
document .hasLocalMutations () ? user .getUid () : null ,
352
- arrayValue ,
353
- directionalValue ,
371
+ indexEntry . getArrayValue () ,
372
+ indexEntry . getDirectionalValue () ,
354
373
document .getKey ().toString ());
355
374
}
356
375
376
+ /** Removes a single index entry from the index entries table. */
377
+ private void deleteIndexEntry (Document document , IndexEntry indexEntry ) {
378
+ if (document .hasLocalMutations ()) {
379
+ db .execute (
380
+ "DELETE FROM index_entries WHERE index_id = ? AND uid = ? AND document_name = ?" ,
381
+ indexEntry .getIndexId (),
382
+ document .hasLocalMutations (),
383
+ document .getKey ().toString ());
384
+ } else {
385
+ db .execute (
386
+ "DELETE FROM index_entries WHERE index_id = ? AND uid IS NULL AND document_name = ?" ,
387
+ indexEntry .getIndexId (),
388
+ document .getKey ().toString ());
389
+ }
390
+ }
391
+
392
+ private List <IndexEntry > getIndexEntries (Document document , FieldIndex fieldIndex ) {
393
+ List <IndexEntry > results = new ArrayList <>();
394
+ db .query (
395
+ "SELECT user_id, directional_value, array_value, FROM index_entries WHERE index_id = ? AND document_name = ? AND (user_id IS NULL OR user_id = ?)" )
396
+ .binding (fieldIndex .getIndexId (), document .getKey ().getName (), user .getUid ())
397
+ .forEach (
398
+ row ->
399
+ results .add (
400
+ IndexEntry .create (
401
+ fieldIndex .getIndexId (),
402
+ document .getKey ().getName (),
403
+ row .isNull (1 ) ? null : row .getString (1 ),
404
+ row .getBlob (2 ),
405
+ row .isNull (3 ) ? null : row .getBlob (3 ))));
406
+ return results ;
407
+ }
408
+
357
409
@ Override
358
410
public Set <DocumentKey > getDocumentsMatchingTarget (FieldIndex fieldIndex , Target target ) {
359
411
hardAssert (started , "IndexManager not started" );
0 commit comments