Skip to content

Commit 1039147

Browse files
authored
Merge bd55fbf into 6df2784
2 parents 6df2784 + bd55fbf commit 1039147

File tree

4 files changed

+577
-1
lines changed

4 files changed

+577
-1
lines changed

firebase-firestore/src/main/java/com/google/firebase/firestore/util/LogicUtils.java

Lines changed: 38 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@
1919
import com.google.firebase.firestore.core.CompositeFilter;
2020
import com.google.firebase.firestore.core.FieldFilter;
2121
import com.google.firebase.firestore.core.Filter;
22+
import com.google.firebase.firestore.core.InFilter;
23+
import com.google.firestore.v1.Value;
2224
import java.util.ArrayList;
2325
import java.util.Arrays;
2426
import java.util.Collections;
@@ -286,6 +288,39 @@ protected static Filter computeDistributedNormalForm(Filter filter) {
286288
return runningResult;
287289
}
288290

291+
/**
292+
* The `in` filter is only a syntactic sugar over a disjunction of equalities. For instance: `a in
293+
* [1,2,3]` is in fact `a==1 || a==2 || a==3`. This method expands any `in` filter in the given
294+
* input into a disjunction of equality filters and returns the expanded filter.
295+
*/
296+
protected static Filter computeInExpansion(Filter filter) {
297+
assertFieldFilterOrCompositeFilter(filter);
298+
299+
List<Filter> expandedFilters = new ArrayList<>();
300+
301+
if (filter instanceof FieldFilter) {
302+
if (filter instanceof InFilter) {
303+
// We have reached a field filter with `in` operator.
304+
for (Value value : ((InFilter) filter).getValue().getArrayValue().getValuesList()) {
305+
expandedFilters.add(
306+
FieldFilter.create(
307+
((InFilter) filter).getField(), FieldFilter.Operator.EQUAL, value));
308+
}
309+
return new CompositeFilter(expandedFilters, CompositeFilter.Operator.OR);
310+
} else {
311+
// We have reached other kinds of field filters.
312+
return filter;
313+
}
314+
}
315+
316+
// We have a composite filter.
317+
CompositeFilter compositeFilter = (CompositeFilter) filter;
318+
for (Filter subfilter : compositeFilter.getFilters()) {
319+
expandedFilters.add(computeInExpansion(subfilter));
320+
}
321+
return new CompositeFilter(expandedFilters, compositeFilter.getOperator());
322+
}
323+
289324
/**
290325
* Given a composite filter, returns the list of terms in its disjunctive normal form.
291326
*
@@ -302,7 +337,9 @@ public static List<Filter> getDnfTerms(CompositeFilter filter) {
302337
return Collections.emptyList();
303338
}
304339

305-
Filter result = computeDistributedNormalForm(filter);
340+
// The `in` operator is a syntactic sugar over a disjunction of equalities. We should first
341+
// replace such filters with equality filters before running the DNF transform.
342+
Filter result = computeDistributedNormalForm(computeInExpansion(filter));
306343

307344
hardAssert(
308345
isDisjunctiveNormalForm(result),

firebase-firestore/src/test/java/com/google/firebase/firestore/local/QueryEngineTestCase.java

Lines changed: 202 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -640,4 +640,206 @@ public void orQueryWithArrayMembership() throws Exception {
640640
expectFullCollectionScan(() -> runQuery(query2, MISSING_LAST_LIMBO_FREE_SNAPSHOT));
641641
assertEquals(docSet(query2.comparator(), doc1, doc4, doc6), result2);
642642
}
643+
644+
@Test
645+
public void queryWithMultipleInsOnTheSameField() throws Exception {
646+
MutableDocument doc1 = doc("coll/1", 1, map("a", 1, "b", 0));
647+
MutableDocument doc2 = doc("coll/2", 1, map("b", 1));
648+
MutableDocument doc3 = doc("coll/3", 1, map("a", 3, "b", 2));
649+
MutableDocument doc4 = doc("coll/4", 1, map("a", 1, "b", 3));
650+
MutableDocument doc5 = doc("coll/5", 1, map("a", 1));
651+
MutableDocument doc6 = doc("coll/6", 1, map("a", 2));
652+
addDocument(doc1, doc2, doc3, doc4, doc5, doc6);
653+
654+
// a IN [1,2,3] && a IN [0,1,4] should result in "a==1".
655+
Query query1 =
656+
query("coll")
657+
.filter(
658+
andFilters(
659+
filter("a", "in", Arrays.asList(1, 2, 3)),
660+
filter("a", "in", Arrays.asList(0, 1, 4))));
661+
DocumentSet result1 =
662+
expectFullCollectionScan(() -> runQuery(query1, MISSING_LAST_LIMBO_FREE_SNAPSHOT));
663+
assertEquals(docSet(query1.comparator(), doc1, doc4, doc5), result1);
664+
665+
// a IN [2,3] && a IN [0,1,4] is never true and so the result should be an empty set.
666+
Query query2 =
667+
query("coll")
668+
.filter(
669+
andFilters(
670+
filter("a", "in", Arrays.asList(2, 3)),
671+
filter("a", "in", Arrays.asList(0, 1, 4))));
672+
DocumentSet result2 =
673+
expectFullCollectionScan(() -> runQuery(query2, MISSING_LAST_LIMBO_FREE_SNAPSHOT));
674+
assertEquals(docSet(query2.comparator()), result2);
675+
676+
// a IN [0,3] || a IN [0,2] should union them (similar to: a IN [0,2,3]).
677+
Query query3 =
678+
query("coll")
679+
.filter(
680+
orFilters(
681+
filter("a", "in", Arrays.asList(0, 3)),
682+
filter("a", "in", Arrays.asList(0, 2))));
683+
684+
DocumentSet result3 =
685+
expectFullCollectionScan(() -> runQuery(query3, MISSING_LAST_LIMBO_FREE_SNAPSHOT));
686+
assertEquals(docSet(query3.comparator(), doc3, doc6), result3);
687+
}
688+
689+
@Test
690+
public void queryWithMultipleInsOnDifferentFields() throws Exception {
691+
MutableDocument doc1 = doc("coll/1", 1, map("a", 1, "b", 0));
692+
MutableDocument doc2 = doc("coll/2", 1, map("b", 1));
693+
MutableDocument doc3 = doc("coll/3", 1, map("a", 3, "b", 2));
694+
MutableDocument doc4 = doc("coll/4", 1, map("a", 1, "b", 3));
695+
MutableDocument doc5 = doc("coll/5", 1, map("a", 1));
696+
MutableDocument doc6 = doc("coll/6", 1, map("a", 2));
697+
addDocument(doc1, doc2, doc3, doc4, doc5, doc6);
698+
699+
Query query1 =
700+
query("coll")
701+
.filter(
702+
orFilters(
703+
filter("a", "in", Arrays.asList(2, 3)),
704+
filter("b", "in", Arrays.asList(0, 2))));
705+
DocumentSet result1 =
706+
expectFullCollectionScan(() -> runQuery(query1, MISSING_LAST_LIMBO_FREE_SNAPSHOT));
707+
assertEquals(docSet(query1.comparator(), doc1, doc3, doc6), result1);
708+
709+
Query query2 =
710+
query("coll")
711+
.filter(
712+
andFilters(
713+
filter("a", "in", Arrays.asList(2, 3)),
714+
filter("b", "in", Arrays.asList(0, 2))));
715+
DocumentSet result2 =
716+
expectFullCollectionScan(() -> runQuery(query2, MISSING_LAST_LIMBO_FREE_SNAPSHOT));
717+
assertEquals(docSet(query2.comparator(), doc3), result2);
718+
}
719+
720+
@Test
721+
public void queryInWithArrayContainsAny() throws Exception {
722+
MutableDocument doc1 = doc("coll/1", 1, map("a", 1, "b", Arrays.asList(0)));
723+
MutableDocument doc2 = doc("coll/2", 1, map("b", Arrays.asList(1)));
724+
MutableDocument doc3 = doc("coll/3", 1, map("a", 3, "b", Arrays.asList(2, 7), "c", 10));
725+
MutableDocument doc4 = doc("coll/4", 1, map("a", 1, "b", Arrays.asList(3, 7)));
726+
MutableDocument doc5 = doc("coll/5", 1, map("a", 1));
727+
MutableDocument doc6 = doc("coll/6", 1, map("a", 2, "c", 20));
728+
addDocument(doc1, doc2, doc3, doc4, doc5, doc6);
729+
730+
Query query1 =
731+
query("coll")
732+
.filter(
733+
orFilters(
734+
filter("a", "in", Arrays.asList(2, 3)),
735+
filter("b", "array-contains-any", Arrays.asList(0, 7))));
736+
DocumentSet result1 =
737+
expectFullCollectionScan(() -> runQuery(query1, MISSING_LAST_LIMBO_FREE_SNAPSHOT));
738+
assertEquals(docSet(query1.comparator(), doc1, doc3, doc4, doc6), result1);
739+
740+
Query query2 =
741+
query("coll")
742+
.filter(
743+
andFilters(
744+
filter("a", "in", Arrays.asList(2, 3)),
745+
filter("b", "array-contains-any", Arrays.asList(0, 7))));
746+
747+
DocumentSet result2 =
748+
expectFullCollectionScan(() -> runQuery(query2, MISSING_LAST_LIMBO_FREE_SNAPSHOT));
749+
assertEquals(docSet(query2.comparator(), doc3), result2);
750+
751+
Query query3 =
752+
query("coll")
753+
.filter(
754+
orFilters(
755+
andFilters(filter("a", "in", Arrays.asList(2, 3)), filter("c", "==", 10)),
756+
filter("b", "array-contains-any", Arrays.asList(0, 7))));
757+
DocumentSet result3 =
758+
expectFullCollectionScan(() -> runQuery(query3, MISSING_LAST_LIMBO_FREE_SNAPSHOT));
759+
assertEquals(docSet(query3.comparator(), doc1, doc3, doc4), result3);
760+
761+
Query query4 =
762+
query("coll")
763+
.filter(
764+
andFilters(
765+
filter("a", "in", Arrays.asList(2, 3)),
766+
orFilters(
767+
filter("b", "array-contains-any", Arrays.asList(0, 7)),
768+
filter("c", "==", 20))));
769+
DocumentSet result4 =
770+
expectFullCollectionScan(() -> runQuery(query4, MISSING_LAST_LIMBO_FREE_SNAPSHOT));
771+
assertEquals(docSet(query4.comparator(), doc3, doc6), result4);
772+
}
773+
774+
@Test
775+
public void queryInWithArrayContains() throws Exception {
776+
MutableDocument doc1 = doc("coll/1", 1, map("a", 1, "b", Arrays.asList(0)));
777+
MutableDocument doc2 = doc("coll/2", 1, map("b", Arrays.asList(1)));
778+
MutableDocument doc3 = doc("coll/3", 1, map("a", 3, "b", Arrays.asList(2, 7), "c", 10));
779+
MutableDocument doc4 = doc("coll/4", 1, map("a", 1, "b", Arrays.asList(3, 7)));
780+
MutableDocument doc5 = doc("coll/5", 1, map("a", 1));
781+
MutableDocument doc6 = doc("coll/6", 1, map("a", 2, "c", 20));
782+
addDocument(doc1, doc2, doc3, doc4, doc5, doc6);
783+
784+
Query query1 =
785+
query("coll")
786+
.filter(
787+
orFilters(
788+
filter("a", "in", Arrays.asList(2, 3)), filter("b", "array-contains", 3)));
789+
DocumentSet result1 =
790+
expectFullCollectionScan(() -> runQuery(query1, MISSING_LAST_LIMBO_FREE_SNAPSHOT));
791+
assertEquals(docSet(query1.comparator(), doc3, doc4, doc6), result1);
792+
793+
Query query2 =
794+
query("coll")
795+
.filter(
796+
andFilters(
797+
filter("a", "in", Arrays.asList(2, 3)), filter("b", "array-contains", 7)));
798+
799+
DocumentSet result2 =
800+
expectFullCollectionScan(() -> runQuery(query2, MISSING_LAST_LIMBO_FREE_SNAPSHOT));
801+
assertEquals(docSet(query2.comparator(), doc3), result2);
802+
803+
Query query3 =
804+
query("coll")
805+
.filter(
806+
orFilters(
807+
filter("a", "in", Arrays.asList(2, 3)),
808+
andFilters(filter("b", "array-contains", 3), filter("a", "==", 1))));
809+
DocumentSet result3 =
810+
expectFullCollectionScan(() -> runQuery(query3, MISSING_LAST_LIMBO_FREE_SNAPSHOT));
811+
assertEquals(docSet(query3.comparator(), doc3, doc4, doc6), result3);
812+
813+
Query query4 =
814+
query("coll")
815+
.filter(
816+
andFilters(
817+
filter("a", "in", Arrays.asList(2, 3)),
818+
orFilters(filter("b", "array-contains", 7), filter("a", "==", 1))));
819+
DocumentSet result4 =
820+
expectFullCollectionScan(() -> runQuery(query4, MISSING_LAST_LIMBO_FREE_SNAPSHOT));
821+
assertEquals(docSet(query4.comparator(), doc3), result4);
822+
}
823+
824+
@Test
825+
public void orderByEquality() throws Exception {
826+
MutableDocument doc1 = doc("coll/1", 1, map("a", 1, "b", Arrays.asList(0)));
827+
MutableDocument doc2 = doc("coll/2", 1, map("b", Arrays.asList(1)));
828+
MutableDocument doc3 = doc("coll/3", 1, map("a", 3, "b", Arrays.asList(2, 7), "c", 10));
829+
MutableDocument doc4 = doc("coll/4", 1, map("a", 1, "b", Arrays.asList(3, 7)));
830+
MutableDocument doc5 = doc("coll/5", 1, map("a", 1));
831+
MutableDocument doc6 = doc("coll/6", 1, map("a", 2, "c", 20));
832+
addDocument(doc1, doc2, doc3, doc4, doc5, doc6);
833+
834+
Query query1 = query("coll").filter(filter("a", "==", 1)).orderBy(orderBy("a"));
835+
DocumentSet result1 =
836+
expectFullCollectionScan(() -> runQuery(query1, MISSING_LAST_LIMBO_FREE_SNAPSHOT));
837+
assertEquals(docSet(query1.comparator(), doc1, doc4, doc5), result1);
838+
839+
Query query2 =
840+
query("coll").filter(filter("a", "in", Arrays.asList(2, 3))).orderBy(orderBy("a"));
841+
DocumentSet result2 =
842+
expectFullCollectionScan(() -> runQuery(query2, MISSING_LAST_LIMBO_FREE_SNAPSHOT));
843+
assertEquals(docSet(query2.comparator(), doc6, doc3), result2);
844+
}
643845
}

0 commit comments

Comments
 (0)