1 // Copyright 2009 Google Inc. All Rights Reserved.
2 package com
.google
.appengine
.api
.datastore
;
4 import static com
.google
.common
.base
.Preconditions
.checkNotNull
;
6 import com
.google
.appengine
.api
.datastore
.MultiQueryComponent
.Order
;
7 import com
.google
.appengine
.api
.datastore
.Query
.FilterPredicate
;
8 import com
.google
.common
.collect
.Iterators
;
9 import com
.google
.common
.collect
.Lists
;
11 import java
.util
.Collections
;
12 import java
.util
.Iterator
;
13 import java
.util
.List
;
16 * A class that generates multiple lists of query filters to execute to satisfy
17 * the {@link MultiQueryComponent}s given to it.
19 * Each component contains multiple {@link List}s of {@link FilterPredicate}s
20 * and an order in which MultiQuery should produce queries that contain these
23 * If the order of a {@link MultiQueryComponent} is set to {@link Order#SERIAL}
24 * the given filters are applied to sequentially generated sets of queries.
25 * This denotes that the result of the sequentially generated queries can be
26 * concatenated to produce a valid result set. However this class makes no
27 * guarantees to this effect and this assurance must come from the code that
28 * constructs this class.
30 * If the order of a {@link MultiQueryComponent} is set to {@link
31 * Order#PARALLEL} the given filters are used to generate multiple queries (one
32 * for each list of filters) that are returned together. This is used to denote
33 * that results must be merged in memory to create a valid result set. However
34 * this class makes no guarantees to this effect and this assurance must come from
35 * the code that constructs this class.
37 * {@link MultiQueryBuilder#iterator()} will provide an iterator that generates
38 * the filters for these queries in the given order. The iterator generates each
39 * list of filters as they are needed. In this way, if the user request is
40 * satisfied the first few sets of queries, the remaining queries need never be
41 * created. This is important because the total number of queries generated by
42 * this class is mult_i(|component_i.filters|).
44 * This class preserves the order in which {@link MultiQueryComponent} are
45 * given to it when applying the filters. However filters are generated most
46 * optimally when {@link Order#PARALLEL} are the last to be applied. Thus to
47 * provide a convenient way to push components with {@link Order#PARALLEL} to
48 * the back, {@link MultiQueryComponent} are sortable.
50 * {@link MultiQueryComponent}s with different {@link Order}s can be added to
51 * the same {@link MultiQueryBuilder} in any configuration.
54 class MultiQueryBuilder
implements Iterable
<List
<List
<FilterPredicate
>>> {
55 final List
<FilterPredicate
> baseFilters
;
56 final List
<MultiQueryComponent
> components
;
57 final int parallelQuerySize
;
59 MultiQueryBuilder(List
<FilterPredicate
> baseFilters
,
60 List
<MultiQueryComponent
> components
, int parallelQuerySize
) {
61 this.baseFilters
= checkNotNull(baseFilters
);
62 this.components
= components
;
63 this.parallelQuerySize
= parallelQuerySize
;
66 public MultiQueryBuilder(List
<FilterPredicate
> baseFilters
,
67 List
<QuerySplitComponent
> splitComponents
, boolean hasSort
) {
68 this.baseFilters
= checkNotNull(baseFilters
);
70 if (splitComponents
.isEmpty()) {
71 components
= Collections
.emptyList();
72 parallelQuerySize
= 1;
74 components
= Lists
.newArrayListWithCapacity(splitComponents
.size());
76 Collections
.sort(splitComponents
);
78 MultiQueryComponent
.Order applyToRemaining
=
79 hasSort ?
null : MultiQueryComponent
.Order
.SERIAL
;
81 int currentSortIndex
= 0;
82 int totalParallelQueries
= 1;
83 for (QuerySplitComponent component
: splitComponents
) {
84 if ((applyToRemaining
== null) && (component
.getSortIndex() != currentSortIndex
)) {
85 if (component
.getSortIndex() == currentSortIndex
+ 1) {
88 applyToRemaining
= MultiQueryComponent
.Order
.PARALLEL
;
92 components
.add(new MultiQueryComponent(
93 applyToRemaining
!= null ? applyToRemaining
: MultiQueryComponent
.Order
.SERIAL
,
94 component
.getFilters()));
96 if (applyToRemaining
== MultiQueryComponent
.Order
.PARALLEL
) {
97 totalParallelQueries
*= component
.getFilters().size();
100 this.parallelQuerySize
= totalParallelQueries
;
105 public Iterator
<List
<List
<FilterPredicate
>>> iterator() {
106 if (components
.isEmpty()) {
107 return Iterators
.singletonIterator(Collections
.singletonList(baseFilters
));
109 return new MultiQueryIterator(baseFilters
, components
);
112 public List
<FilterPredicate
> getBaseFilters() {
116 public boolean isSingleton() {
117 return components
.isEmpty();
121 * @return the parallelQuerySize
123 public int getParallelQuerySize() {
124 return parallelQuerySize
;
128 public String
toString() {
129 return "MultiQueryBuilder [baseFilters=" + baseFilters
+ ", components=" + components
+ "]";