17
17
import static com .google .firebase .firestore .util .Assert .fail ;
18
18
import static com .google .firebase .firestore .util .Assert .hardAssert ;
19
19
20
+ import android .text .TextUtils ;
20
21
import androidx .annotation .Nullable ;
21
22
import com .google .firebase .firestore .core .Bound ;
22
23
import com .google .firebase .firestore .core .FieldFilter ;
@@ -134,8 +135,8 @@ public void addIndexEntries(Document document) {
134
135
fieldIndex );
135
136
}
136
137
137
- List < byte []> encodeValues = encodeDocumentValues (fieldIndex , values );
138
- for (byte [] encoded : encodeValues ) {
138
+ Object [] encodeValues = encodeDocumentValues (fieldIndex , values );
139
+ for (Object encoded : encodeValues ) {
139
140
// TODO(indexing): Handle different values for different users
140
141
db .execute (
141
142
"INSERT OR IGNORE INTO index_entries ("
@@ -176,8 +177,6 @@ public Set<DocumentKey> getDocumentsMatchingTarget(Target target) {
176
177
if (fieldIndex == null ) return null ;
177
178
178
179
Bound lowerBound = target .getLowerBound (fieldIndex );
179
- String lowerBoundOp = lowerBound .isBefore () ? ">=" : ">" ; // `startAt()` versus `startAfter()`
180
-
181
180
@ Nullable Bound upperBound = target .getUpperBound (fieldIndex );
182
181
183
182
if (Logger .isDebugEnabled ()) {
@@ -191,49 +190,59 @@ public Set<DocumentKey> getDocumentsMatchingTarget(Target target) {
191
190
}
192
191
193
192
Set <DocumentKey > result = new HashSet <>();
193
+ BindArgs bindArgs ;
194
194
195
+ Object [] lowerBoundValues = encodeTargetValues (fieldIndex , target , lowerBound .getPosition ());
196
+ String lowerBoundOp = lowerBound .isBefore () ? ">=" : ">" ; // `startAt()` versus `startAfter()`
195
197
if (upperBound != null ) {
196
- List <byte []> lowerBoundValues =
197
- encodeTargetValues (fieldIndex , target , lowerBound .getPosition ());
198
- List <byte []> upperBoundValues =
199
- encodeTargetValues (fieldIndex , target , upperBound .getPosition ());
200
-
201
- hardAssert (
202
- lowerBoundValues .size () == upperBoundValues .size (),
203
- "Expected upper and lower bound size to match" );
204
-
198
+ Object [] upperBoundValues = encodeTargetValues (fieldIndex , target , upperBound .getPosition ());
205
199
String upperBoundOp = upperBound .isBefore () ? "<" : "<=" ; // `endBefore()` versus `endAt()`
206
-
207
- // TODO(indexing): To avoid reading the same documents multiple times, we should ideally only
208
- // send one query that combines all clauses.
209
- // TODO(indexing): Add limit handling
210
- for (int i = 0 ; i < lowerBoundValues .size (); ++i ) {
211
- db .query (
212
- String .format (
213
- "SELECT document_name from index_entries WHERE index_id = ? AND index_value %s ? AND index_value %s ?" ,
214
- lowerBoundOp , upperBoundOp ))
215
- .binding (fieldIndex .getIndexId (), lowerBoundValues .get (i ), upperBoundValues .get (i ))
216
- .forEach (
217
- row -> result .add (DocumentKey .fromPath (ResourcePath .fromString (row .getString (0 )))));
218
- }
200
+ bindArgs = generateBindArgs (lowerBoundValues , lowerBoundOp , upperBoundValues , upperBoundOp );
219
201
} else {
220
- List <byte []> lowerBoundValues =
221
- encodeTargetValues (fieldIndex , target , lowerBound .getPosition ());
222
- for (byte [] lowerBoundValue : lowerBoundValues ) {
223
- db .query (
224
- String .format (
225
- "SELECT document_name from index_entries WHERE index_id = ? AND index_value %s ?" ,
226
- lowerBoundOp ))
227
- .binding (fieldIndex .getIndexId (), lowerBoundValue )
228
- .forEach (
229
- row -> result .add (DocumentKey .fromPath (ResourcePath .fromString (row .getString (0 )))));
230
- }
202
+ bindArgs = generateBindArgs (lowerBoundValues , lowerBoundOp );
231
203
}
232
204
205
+ db .query (
206
+ String .format (
207
+ "SELECT document_name from index_entries WHERE index_id = ? AND (%s)" ,
208
+ bindArgs .sql ))
209
+ .binding (fieldIndex .getIndexId (), bindArgs .bindArgs )
210
+ .forEach (
211
+ row -> result .add (DocumentKey .fromPath (ResourcePath .fromString (row .getString (0 )))));
212
+ // TODO(indexing): Add limit handling
213
+
233
214
Logger .debug (TAG , "Index scan returned %s documents" , result .size ());
234
215
return result ;
235
216
}
236
217
218
+ /** Returns a SQL filter that concatenates all {@code value} into a disjunction. */
219
+ private BindArgs generateBindArgs (Object [] values , String op ) {
220
+ String [] filters = new String [values .length ];
221
+ for (int i = 0 ; i < values .length ; ++i ) {
222
+ filters [i ] = String .format ("index_value %s ?" , op );
223
+ }
224
+ return new BindArgs (TextUtils .join (" OR " , filters ), values );
225
+ }
226
+
227
+ /**
228
+ * Returns a SQL filter that combines each element from the left list with each element from the
229
+ * right list and returns all combinations (e.g. `(left1 AND right1) OR (left1 AND right2) ...`).
230
+ */
231
+ private BindArgs generateBindArgs (Object [] left , String leftOp , Object [] right , String rightOp ) {
232
+ String [] filters = new String [left .length * right .length ];
233
+ Object [] bingArgs = new Object [left .length * right .length * 2 ];
234
+ int i = 0 ;
235
+ for (Object value1 : left ) {
236
+ for (Object value2 : right ) {
237
+ filters [i ] = String .format ("index_value %s ? AND index_value %s ?" , leftOp , rightOp );
238
+ bingArgs [i * 2 ] = value1 ;
239
+ bingArgs [i * 2 + 1 ] = value2 ;
240
+ ++i ;
241
+ }
242
+ }
243
+ return new BindArgs (TextUtils .join (" OR " , filters ), bingArgs );
244
+ }
245
+
237
246
/**
238
247
* Returns an index that can be used to serve the provided target. Returns {@code null} if no
239
248
* index is configured.
@@ -278,7 +287,7 @@ public Set<DocumentKey> getDocumentsMatchingTarget(Target target) {
278
287
* Encodes the given field values according to the specification in {@code fieldIndex}. For
279
288
* CONTAINS indices, a list of possible values is returned.
280
289
*/
281
- private List < byte []> encodeDocumentValues (FieldIndex fieldIndex , List <Value > values ) {
290
+ private Object [] encodeDocumentValues (FieldIndex fieldIndex , List <Value > values ) {
282
291
List <IndexByteEncoder > encoders = new ArrayList <>();
283
292
encoders .add (new IndexByteEncoder ());
284
293
for (int i = 0 ; i < fieldIndex .segmentCount (); ++i ) {
@@ -302,8 +311,7 @@ private List<byte[]> encodeDocumentValues(FieldIndex fieldIndex, List<Value> val
302
311
* Encodes the given field values according to the specification in {@code target}. For IN and
303
312
* ArrayContainsAny queries, a list of possible values is returned.
304
313
*/
305
- private List <byte []> encodeTargetValues (
306
- FieldIndex fieldIndex , Target target , List <Value > values ) {
314
+ private Object [] encodeTargetValues (FieldIndex fieldIndex , Target target , List <Value > values ) {
307
315
List <IndexByteEncoder > encoders = new ArrayList <>();
308
316
encoders .add (new IndexByteEncoder ());
309
317
for (int i = 0 ; i < fieldIndex .segmentCount (); ++i ) {
@@ -321,10 +329,10 @@ private List<byte[]> encodeTargetValues(
321
329
}
322
330
323
331
/** Returns the byte representation for all encoders. */
324
- private List < byte []> getEncodedBytes (List <IndexByteEncoder > encoders ) {
325
- List < byte []> result = new ArrayList <>() ;
326
- for (IndexByteEncoder encoder : encoders ) {
327
- result . add ( encoder .getEncodedBytes () );
332
+ private Object [] getEncodedBytes (List <IndexByteEncoder > encoders ) {
333
+ Object [] result = new Object [ encoders . size ()] ;
334
+ for (int i = 0 ; i < encoders . size (); ++ i ) {
335
+ result [ i ] = encoders . get ( i ) .getEncodedBytes ();
328
336
}
329
337
return result ;
330
338
}
@@ -368,4 +376,15 @@ private boolean isMultiValueFilter(Target target, FieldPath fieldPath) {
368
376
private byte [] encodeFieldIndex (FieldIndex fieldIndex ) {
369
377
return serializer .encodeFieldIndex (fieldIndex ).toByteArray ();
370
378
}
379
+
380
+ /** Stores a SQL statement of filters and their corresponding bind arguments. */
381
+ static class BindArgs {
382
+ final String sql ;
383
+ final Object [] bindArgs ;
384
+
385
+ BindArgs (String sql , Object [] bindArgs ) {
386
+ this .sql = sql ;
387
+ this .bindArgs = bindArgs ;
388
+ }
389
+ }
371
390
}
0 commit comments