1616
1717import static com .google .firebase .firestore .util .Assert .fail ;
1818import static com .google .firebase .firestore .util .Assert .hardAssert ;
19+ import static com .google .firebase .firestore .util .Util .repeatSequence ;
1920
2021import androidx .annotation .Nullable ;
2122import com .google .firebase .firestore .core .Bound ;
@@ -96,8 +97,6 @@ public void addFieldIndex(FieldIndex index) {
9697 db .query ("SELECT MAX(index_id) FROM index_configuration" )
9798 .firstValue (input -> input .isNull (0 ) ? 0 : input .getInt (0 ));
9899
99- // TODO(indexing): Properly dedupe indices to avoid duplicate index entries (by comparing
100- // collection_group+index_proto)
101100 db .execute (
102101 "INSERT OR IGNORE INTO index_configuration ("
103102 + "index_id, "
@@ -145,8 +144,8 @@ public void addIndexEntries(Document document) {
145144 fieldIndex );
146145 }
147146
148- List < byte []> encodeValues = encodeDocumentValues (fieldIndex , values );
149- for (byte [] encoded : encodeValues ) {
147+ Object [] encodeValues = encodeDocumentValues (fieldIndex , values );
148+ for (Object encoded : encodeValues ) {
150149 // TODO(indexing): Handle different values for different users
151150 db .execute (
152151 "INSERT OR IGNORE INTO index_entries ("
@@ -187,12 +186,10 @@ public Set<DocumentKey> getDocumentsMatchingTarget(Target target) {
187186 if (fieldIndex == null ) return null ;
188187
189188 Bound lowerBound = target .getLowerBound (fieldIndex );
190- String lowerBoundOp = lowerBound .isInclusive () ? ">=" : ">" ;
191-
192189 @ Nullable Bound upperBound = target .getUpperBound (fieldIndex );
193190
194191 if (Logger .isDebugEnabled ()) {
195- Logger .warn (
192+ Logger .debug (
196193 TAG ,
197194 "Using index '%s' to execute '%s' (Lower bound: %s, Upper bound: %s)" ,
198195 fieldIndex ,
@@ -202,49 +199,75 @@ public Set<DocumentKey> getDocumentsMatchingTarget(Target target) {
202199 }
203200
204201 Set <DocumentKey > result = new HashSet <>();
202+ SQLitePersistence .Query query ;
205203
204+ Object [] lowerBoundValues = encodeTargetValues (fieldIndex , target , lowerBound .getPosition ());
205+ String lowerBoundOp = lowerBound .isInclusive () ? ">=" : ">" ;
206206 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 ());
216208 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 );
230216 } 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 );
242218 }
243219
220+ query .forEach (
221+ row -> result .add (DocumentKey .fromPath (ResourcePath .fromString (row .getString (0 )))));
222+
223+ // TODO(indexing): Add limit handling
224+
244225 Logger .debug (TAG , "Index scan returned %s documents" , result .size ());
245226 return result ;
246227 }
247228
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+
248271 /**
249272 * Returns an index that can be used to serve the provided target. Returns {@code null} if no
250273 * index is configured.
@@ -293,7 +316,7 @@ public Set<DocumentKey> getDocumentsMatchingTarget(Target target) {
293316 * Encodes the given field values according to the specification in {@code fieldIndex}. For
294317 * CONTAINS indices, a list of possible values is returned.
295318 */
296- private List < byte []> encodeDocumentValues (FieldIndex fieldIndex , List <Value > values ) {
319+ private Object [] encodeDocumentValues (FieldIndex fieldIndex , List <Value > values ) {
297320 List <IndexByteEncoder > encoders = new ArrayList <>();
298321 encoders .add (new IndexByteEncoder ());
299322 for (int i = 0 ; i < fieldIndex .segmentCount (); ++i ) {
@@ -317,8 +340,7 @@ private List<byte[]> encodeDocumentValues(FieldIndex fieldIndex, List<Value> val
317340 * Encodes the given field values according to the specification in {@code target}. For IN and
318341 * ArrayContainsAny queries, a list of possible values is returned.
319342 */
320- private List <byte []> encodeTargetValues (
321- FieldIndex fieldIndex , Target target , List <Value > values ) {
343+ private Object [] encodeTargetValues (FieldIndex fieldIndex , Target target , List <Value > values ) {
322344 List <IndexByteEncoder > encoders = new ArrayList <>();
323345 encoders .add (new IndexByteEncoder ());
324346 for (int i = 0 ; i < fieldIndex .segmentCount (); ++i ) {
@@ -336,10 +358,10 @@ private List<byte[]> encodeTargetValues(
336358 }
337359
338360 /** 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 ();
343365 }
344366 return result ;
345367 }
0 commit comments