16
16
17
17
import static com .google .firebase .firestore .util .Assert .fail ;
18
18
import static com .google .firebase .firestore .util .Assert .hardAssert ;
19
+ import static com .google .firebase .firestore .util .Util .repeatSequence ;
19
20
20
21
import androidx .annotation .Nullable ;
21
22
import com .google .firebase .firestore .core .Bound ;
@@ -96,8 +97,6 @@ public void addFieldIndex(FieldIndex index) {
96
97
db .query ("SELECT MAX(index_id) FROM index_configuration" )
97
98
.firstValue (input -> input .isNull (0 ) ? 0 : input .getInt (0 ));
98
99
99
- // TODO(indexing): Properly dedupe indices to avoid duplicate index entries (by comparing
100
- // collection_group+index_proto)
101
100
db .execute (
102
101
"INSERT OR IGNORE INTO index_configuration ("
103
102
+ "index_id, "
@@ -145,8 +144,8 @@ public void addIndexEntries(Document document) {
145
144
fieldIndex );
146
145
}
147
146
148
- List < byte []> encodeValues = encodeDocumentValues (fieldIndex , values );
149
- for (byte [] encoded : encodeValues ) {
147
+ Object [] encodeValues = encodeDocumentValues (fieldIndex , values );
148
+ for (Object encoded : encodeValues ) {
150
149
// TODO(indexing): Handle different values for different users
151
150
db .execute (
152
151
"INSERT OR IGNORE INTO index_entries ("
@@ -187,12 +186,10 @@ public Set<DocumentKey> getDocumentsMatchingTarget(Target target) {
187
186
if (fieldIndex == null ) return null ;
188
187
189
188
Bound lowerBound = target .getLowerBound (fieldIndex );
190
- String lowerBoundOp = lowerBound .isInclusive () ? ">=" : ">" ;
191
-
192
189
@ Nullable Bound upperBound = target .getUpperBound (fieldIndex );
193
190
194
191
if (Logger .isDebugEnabled ()) {
195
- Logger .warn (
192
+ Logger .debug (
196
193
TAG ,
197
194
"Using index '%s' to execute '%s' (Lower bound: %s, Upper bound: %s)" ,
198
195
fieldIndex ,
@@ -202,49 +199,75 @@ public Set<DocumentKey> getDocumentsMatchingTarget(Target target) {
202
199
}
203
200
204
201
Set <DocumentKey > result = new HashSet <>();
202
+ SQLitePersistence .Query query ;
205
203
204
+ Object [] lowerBoundValues = encodeTargetValues (fieldIndex , target , lowerBound .getPosition ());
205
+ String lowerBoundOp = lowerBound .isInclusive () ? ">=" : ">" ;
206
206
if (upperBound != null ) {
207
- List <byte []> lowerBoundValues =
208
- encodeTargetValues (fieldIndex , target , lowerBound .getPosition ());
209
- List <byte []> upperBoundValues =
210
- encodeTargetValues (fieldIndex , target , upperBound .getPosition ());
211
-
212
- hardAssert (
213
- lowerBoundValues .size () == upperBoundValues .size (),
214
- "Expected upper and lower bound size to match" );
215
-
207
+ Object [] upperBoundValues = encodeTargetValues (fieldIndex , target , upperBound .getPosition ());
216
208
String upperBoundOp = upperBound .isInclusive () ? "<=" : "<" ;
217
-
218
- // TODO(indexing): To avoid reading the same documents multiple times, we should ideally only
219
- // send one query that combines all clauses.
220
- // TODO(indexing): Add limit handling
221
- for (int i = 0 ; i < lowerBoundValues .size (); ++i ) {
222
- db .query (
223
- String .format (
224
- "SELECT document_name from index_entries WHERE index_id = ? AND index_value %s ? AND index_value %s ?" ,
225
- lowerBoundOp , upperBoundOp ))
226
- .binding (fieldIndex .getIndexId (), lowerBoundValues .get (i ), upperBoundValues .get (i ))
227
- .forEach (
228
- row -> result .add (DocumentKey .fromPath (ResourcePath .fromString (row .getString (0 )))));
229
- }
209
+ query =
210
+ generateQuery (
211
+ fieldIndex .getIndexId (),
212
+ lowerBoundValues ,
213
+ lowerBoundOp ,
214
+ upperBoundValues ,
215
+ upperBoundOp );
230
216
} else {
231
- List <byte []> lowerBoundValues =
232
- encodeTargetValues (fieldIndex , target , lowerBound .getPosition ());
233
- for (byte [] lowerBoundValue : lowerBoundValues ) {
234
- db .query (
235
- String .format (
236
- "SELECT document_name from index_entries WHERE index_id = ? AND index_value %s ?" ,
237
- lowerBoundOp ))
238
- .binding (fieldIndex .getIndexId (), lowerBoundValue )
239
- .forEach (
240
- row -> result .add (DocumentKey .fromPath (ResourcePath .fromString (row .getString (0 )))));
241
- }
217
+ query = generateQuery (fieldIndex .getIndexId (), lowerBoundValues , lowerBoundOp );
242
218
}
243
219
220
+ query .forEach (
221
+ row -> result .add (DocumentKey .fromPath (ResourcePath .fromString (row .getString (0 )))));
222
+
223
+ // TODO(indexing): Add limit handling
224
+
244
225
Logger .debug (TAG , "Index scan returned %s documents" , result .size ());
245
226
return result ;
246
227
}
247
228
229
+ /** Returns a SQL query on 'index_entries' that unions all bounds. */
230
+ private SQLitePersistence .Query generateQuery (int indexId , Object [] bounds , String op ) {
231
+ String statement =
232
+ String .format (
233
+ "SELECT document_name FROM index_entries WHERE index_id = ? AND index_value %s ?" , op );
234
+ String sql = repeatSequence (statement , bounds .length , " UNION " );
235
+
236
+ Object [] bingArgs = new Object [bounds .length * 2 ];
237
+ for (int i = 0 ; i < bounds .length ; ++i ) {
238
+ bingArgs [i * 2 ] = indexId ;
239
+ bingArgs [i * 2 + 1 ] = bounds [i ];
240
+ }
241
+
242
+ return db .query (sql ).binding (bingArgs );
243
+ }
244
+
245
+ /** Returns a SQL query on 'index_entries' that unions all bounds. */
246
+ private SQLitePersistence .Query generateQuery (
247
+ int indexId ,
248
+ Object [] lowerBounds ,
249
+ String lowerBoundOp ,
250
+ Object [] upperBounds ,
251
+ String upperBoundOp ) {
252
+ String statement =
253
+ String .format (
254
+ "SELECT document_name FROM index_entries WHERE index_id = ? AND index_value %s ? AND index_value %s ?" ,
255
+ lowerBoundOp , upperBoundOp );
256
+ String sql = repeatSequence (statement , lowerBounds .length * upperBounds .length , " UNION " );
257
+
258
+ Object [] bingArgs = new Object [lowerBounds .length * upperBounds .length * 3 ];
259
+ int i = 0 ;
260
+ for (Object value1 : lowerBounds ) {
261
+ for (Object value2 : upperBounds ) {
262
+ bingArgs [i ++] = indexId ;
263
+ bingArgs [i ++] = value1 ;
264
+ bingArgs [i ++] = value2 ;
265
+ }
266
+ }
267
+
268
+ return db .query (sql ).binding (bingArgs );
269
+ }
270
+
248
271
/**
249
272
* Returns an index that can be used to serve the provided target. Returns {@code null} if no
250
273
* index is configured.
@@ -293,7 +316,7 @@ public Set<DocumentKey> getDocumentsMatchingTarget(Target target) {
293
316
* Encodes the given field values according to the specification in {@code fieldIndex}. For
294
317
* CONTAINS indices, a list of possible values is returned.
295
318
*/
296
- private List < byte []> encodeDocumentValues (FieldIndex fieldIndex , List <Value > values ) {
319
+ private Object [] encodeDocumentValues (FieldIndex fieldIndex , List <Value > values ) {
297
320
List <IndexByteEncoder > encoders = new ArrayList <>();
298
321
encoders .add (new IndexByteEncoder ());
299
322
for (int i = 0 ; i < fieldIndex .segmentCount (); ++i ) {
@@ -317,8 +340,7 @@ private List<byte[]> encodeDocumentValues(FieldIndex fieldIndex, List<Value> val
317
340
* Encodes the given field values according to the specification in {@code target}. For IN and
318
341
* ArrayContainsAny queries, a list of possible values is returned.
319
342
*/
320
- private List <byte []> encodeTargetValues (
321
- FieldIndex fieldIndex , Target target , List <Value > values ) {
343
+ private Object [] encodeTargetValues (FieldIndex fieldIndex , Target target , List <Value > values ) {
322
344
List <IndexByteEncoder > encoders = new ArrayList <>();
323
345
encoders .add (new IndexByteEncoder ());
324
346
for (int i = 0 ; i < fieldIndex .segmentCount (); ++i ) {
@@ -336,10 +358,10 @@ private List<byte[]> encodeTargetValues(
336
358
}
337
359
338
360
/** Returns the byte representation for all encoders. */
339
- private List < byte []> getEncodedBytes (List <IndexByteEncoder > encoders ) {
340
- List < byte []> result = new ArrayList <>() ;
341
- for (IndexByteEncoder encoder : encoders ) {
342
- result . add ( encoder .getEncodedBytes () );
361
+ private Object [] getEncodedBytes (List <IndexByteEncoder > encoders ) {
362
+ Object [] result = new Object [ encoders . size ()] ;
363
+ for (int i = 0 ; i < encoders . size (); ++ i ) {
364
+ result [ i ] = encoders . get ( i ) .getEncodedBytes ();
343
365
}
344
366
return result ;
345
367
}
0 commit comments