Skip to content

[WIP] Expose query constrains on the public Query class #178

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 20 additions & 20 deletions firebase-firestore/mapping.txt
Original file line number Diff line number Diff line change
Expand Up @@ -672,7 +672,7 @@ com.google.firebase.firestore.core.Bound -> com.google.firebase.firestore.b.zza:
42:42:boolean isBefore() -> zzb
47:56:java.lang.String canonicalString() -> zzc
61:89:boolean sortsBeforeDocument(java.util.List,com.google.firebase.firestore.model.Document) -> zza
1035:1035:com.google.firebase.firestore.model.FieldPath com.google.firebase.firestore.core.OrderBy.getField():35:35 -> zza
1035:1035:com.google.firebase.firestore.model.FieldPath com.google.firebase.firestore.OrderBy.getField():35:35 -> zza
1035:1035:boolean sortsBeforeDocument(java.util.List,com.google.firebase.firestore.model.Document):74 -> zza
94:103:boolean equals(java.lang.Object) -> equals
108:110:int hashCode() -> hashCode
Expand Down Expand Up @@ -882,29 +882,29 @@ com.google.firebase.firestore.core.OnlineState -> com.google.firebase.firestore.
com.google.firebase.firestore.core.OnlineState[] $VALUES -> zzd
9:9:void <init>(java.lang.String,int) -> <init>
9:31:void <clinit>() -> <clinit>
com.google.firebase.firestore.core.OrderBy -> com.google.firebase.firestore.b.zzw:
com.google.firebase.firestore.core.OrderBy$Direction direction -> zzb
com.google.firebase.firestore.OrderBy -> com.google.firebase.firestore.b.zzw:
com.google.firebase.firestore.OrderBy$Direction direction -> zzb
com.google.firebase.firestore.model.FieldPath field -> zza
27:27:com.google.firebase.firestore.core.OrderBy getInstance(com.google.firebase.firestore.core.OrderBy$Direction,com.google.firebase.firestore.model.FieldPath) -> zza
31:31:com.google.firebase.firestore.core.OrderBy$Direction getDirection() -> zza
27:27:com.google.firebase.firestore.OrderBy getInstance(com.google.firebase.firestore.OrderBy$Direction,com.google.firebase.firestore.model.FieldPath) -> zza
31:31:com.google.firebase.firestore.OrderBy$Direction getDirection() -> zza
35:35:com.google.firebase.firestore.model.FieldPath getField() -> zzb
41:44:void <init>(com.google.firebase.firestore.core.OrderBy$Direction,com.google.firebase.firestore.model.FieldPath) -> <init>
41:44:void <init>(com.google.firebase.firestore.OrderBy$Direction,com.google.firebase.firestore.model.FieldPath) -> <init>
47:54:int compare(com.google.firebase.firestore.model.Document,com.google.firebase.firestore.model.Document) -> zza
60:65:boolean equals(java.lang.Object) -> equals
70:73:int hashCode() -> hashCode
78:78:java.lang.String toString() -> toString
com.google.firebase.firestore.core.OrderBy$Direction -> com.google.firebase.firestore.b.zzw$zza:
com.google.firebase.firestore.core.OrderBy$Direction ASCENDING -> zza
com.google.firebase.firestore.core.OrderBy$Direction DESCENDING -> zzb
com.google.firebase.firestore.OrderBy$Direction -> com.google.firebase.firestore.b.zzw$zza:
com.google.firebase.firestore.OrderBy$Direction ASCENDING -> zza
com.google.firebase.firestore.OrderBy$Direction DESCENDING -> zzb
int comparisonModifier -> zzc
com.google.firebase.firestore.core.OrderBy$Direction[] $VALUES -> zzd
com.google.firebase.firestore.OrderBy$Direction[] $VALUES -> zzd
17:19:void <init>(java.lang.String,int,int) -> <init>
22:22:int getComparisonModifier() -> zza
11:13:void <clinit>() -> <clinit>
com.google.firebase.firestore.core.Query -> com.google.firebase.firestore.b.zzx:
long NO_LIMIT -> zza
com.google.firebase.firestore.core.OrderBy KEY_ORDERING_ASC -> zzb
com.google.firebase.firestore.core.OrderBy KEY_ORDERING_DESC -> zzc
com.google.firebase.firestore.OrderBy KEY_ORDERING_ASC -> zzb
com.google.firebase.firestore.OrderBy KEY_ORDERING_DESC -> zzc
java.util.List explicitSortOrder -> zzd
java.util.List memoizedOrderBy -> zze
java.util.List filters -> zzf
Expand All @@ -922,26 +922,26 @@ com.google.firebase.firestore.core.Query -> com.google.firebase.firestore.b.zzx:
81:81:com.google.firebase.firestore.core.Bound getStartAt() -> zzf
85:85:com.google.firebase.firestore.core.Bound getEndAt() -> zzg
89:92:com.google.firebase.firestore.model.FieldPath getFirstOrderByField() -> zzh
1035:1035:com.google.firebase.firestore.model.FieldPath com.google.firebase.firestore.core.OrderBy.getField():35:35 -> zzh
1035:1035:com.google.firebase.firestore.model.FieldPath com.google.firebase.firestore.OrderBy.getField():35:35 -> zzh
1035:1035:com.google.firebase.firestore.model.FieldPath getFirstOrderByField():92 -> zzh
97:105:com.google.firebase.firestore.model.FieldPath inequalityField() -> zzi
109:131:com.google.firebase.firestore.core.Query filter(com.google.firebase.firestore.core.Filter) -> zza
135:146:com.google.firebase.firestore.core.Query orderBy(com.google.firebase.firestore.core.OrderBy) -> zza
135:146:com.google.firebase.firestore.core.Query orderBy(com.google.firebase.firestore.OrderBy) -> zza
150:150:com.google.firebase.firestore.core.Query limit(long) -> zza
154:154:com.google.firebase.firestore.core.Query startAt(com.google.firebase.firestore.core.Bound) -> zza
158:158:com.google.firebase.firestore.core.Query endAt(com.google.firebase.firestore.core.Bound) -> zzb
162:162:java.util.List getExplicitOrderBy() -> zzj
166:201:java.util.List getOrderBy() -> zzk
1128:1128:boolean com.google.firebase.firestore.model.FieldPath.isKeyField():128:128 -> zzk
1128:1128:java.util.List getOrderBy():173 -> zzk
2035:2035:com.google.firebase.firestore.model.FieldPath com.google.firebase.firestore.core.OrderBy.getField():35:35 -> zzk
2035:2035:com.google.firebase.firestore.model.FieldPath com.google.firebase.firestore.OrderBy.getField():35:35 -> zzk
2035:2035:java.util.List getOrderBy():185 -> zzk
247:247:boolean matches(com.google.firebase.firestore.model.Document) -> zza
2205:2209:boolean matchesPath(com.google.firebase.firestore.model.Document):205:209 -> zza
2205:2209:boolean matches(com.google.firebase.firestore.model.Document):247 -> zza
2226:2232:boolean matchesOrderBy(com.google.firebase.firestore.model.Document):226:232 -> zza
2226:2232:boolean matches(com.google.firebase.firestore.model.Document):247 -> zza
3035:3035:com.google.firebase.firestore.model.FieldPath com.google.firebase.firestore.core.OrderBy.getField():35:35 -> zza
3035:3035:com.google.firebase.firestore.model.FieldPath com.google.firebase.firestore.OrderBy.getField():35:35 -> zza
3035:3035:boolean matchesOrderBy(com.google.firebase.firestore.model.Document):228 -> zza
3035:3035:boolean matches(com.google.firebase.firestore.model.Document):247 -> zza
3214:3219:boolean matchesFilters(com.google.firebase.firestore.model.Document):214:219 -> zza
Expand All @@ -954,7 +954,7 @@ com.google.firebase.firestore.core.Query -> com.google.firebase.firestore.b.zzx:
4060:4060:java.lang.String getCanonicalId():287 -> zzm
4068:4068:java.util.List getFilters():68:68 -> zzm
4068:4068:java.lang.String getCanonicalId():291 -> zzm
5035:5035:com.google.firebase.firestore.model.FieldPath com.google.firebase.firestore.core.OrderBy.getField():35:35 -> zzm
5035:5035:com.google.firebase.firestore.model.FieldPath com.google.firebase.firestore.OrderBy.getField():35:35 -> zzm
5035:5035:java.lang.String getCanonicalId():298 -> zzm
323:347:boolean equals(java.lang.Object) -> equals
352:358:int hashCode() -> hashCode
Expand All @@ -963,7 +963,7 @@ com.google.firebase.firestore.core.Query -> com.google.firebase.firestore.b.zzx:
com.google.firebase.firestore.core.Query$QueryComparator -> com.google.firebase.firestore.b.zzx$zza:
java.util.List sortOrder -> zza
257:266:void <init>(java.util.List) -> <init>
1035:1035:com.google.firebase.firestore.model.FieldPath com.google.firebase.firestore.core.OrderBy.getField():35:35 -> <init>
1035:1035:com.google.firebase.firestore.model.FieldPath com.google.firebase.firestore.OrderBy.getField():35:35 -> <init>
1035:1035:void <init>(java.util.List):260 -> <init>
254:254:int compare(java.lang.Object,java.lang.Object) -> compare
1270:1276:int compare(com.google.firebase.firestore.model.Document,com.google.firebase.firestore.model.Document):270:276 -> compare
Expand Down Expand Up @@ -2697,14 +2697,14 @@ com.google.firebase.firestore.remote.RemoteSerializer -> com.google.firebase.fir
5800:5809:com.google.firebase.firestore.core.Filter decodeUnaryFilter(com.google.firestore.v1beta1.StructuredQuery$UnaryFilter):800:809 -> zza
5800:5809:java.util.List decodeFilters(com.google.firestore.v1beta1.StructuredQuery$Filter):760 -> zza
5800:5809:com.google.firebase.firestore.core.Query decodeQueryTarget(com.google.firestore.v1beta1.Target$QueryTarget):682 -> zza
5865:5877:com.google.firebase.firestore.core.OrderBy decodeOrderBy(com.google.firestore.v1beta1.StructuredQuery$Order):865:877 -> zza
5865:5877:com.google.firebase.firestore.OrderBy decodeOrderBy(com.google.firestore.v1beta1.StructuredQuery$Order):865:877 -> zza
5865:5877:com.google.firebase.firestore.core.Query decodeQueryTarget(com.google.firestore.v1beta1.Target$QueryTarget):692 -> zza
719:733:com.google.firestore.v1beta1.StructuredQuery$Filter encodeFilters(java.util.List) -> zza
772:776:com.google.firestore.v1beta1.StructuredQuery$Filter encodeRelationFilter(com.google.firebase.firestore.core.RelationFilter) -> zza
787:796:com.google.firestore.v1beta1.StructuredQuery$Filter encodeUnaryFilter(com.google.firebase.firestore.core.Filter) -> zza
814:814:com.google.firestore.v1beta1.StructuredQuery$FieldReference encodeFieldPath(com.google.firebase.firestore.model.FieldPath) -> zza
818:830:com.google.firestore.v1beta1.StructuredQuery$FieldFilter$Operator encodeRelationFilterOperator(com.google.firebase.firestore.core.RelationFilter$Operator) -> zza
854:861:com.google.firestore.v1beta1.StructuredQuery$Order encodeOrderBy(com.google.firebase.firestore.core.OrderBy) -> zza
854:861:com.google.firestore.v1beta1.StructuredQuery$Order encodeOrderBy(com.google.firebase.firestore.OrderBy) -> zza
883:888:com.google.firestore.v1beta1.Cursor encodeBound(com.google.firebase.firestore.core.Bound) -> zza
892:899:com.google.firebase.firestore.core.Bound decodeBound(com.google.firestore.v1beta1.Cursor) -> zza
907:977:com.google.firebase.firestore.remote.WatchChange decodeWatchChange(com.google.firestore.v1beta1.ListenResponse) -> zza
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
package com.google.firebase.firestore;

import com.google.firebase.annotations.PublicApi;
import com.google.firebase.firestore.core.NaNFilter;
import com.google.firebase.firestore.core.NullFilter;
import com.google.firebase.firestore.core.RelationFilter;

import javax.annotation.Nullable;

@PublicApi
public final class Filter {

@PublicApi
public enum Operator {
LESS_THAN("<"),
LESS_THAN_OR_EQUAL("<="),
EQUAL("=="),
GREATER_THAN(">"),
GREATER_THAN_OR_EQUAL(">="),
ARRAY_CONTAINS("array_contains");

private final String text;

Operator(String text) {
this.text = text;
}

@Override
public String toString() {
return text;
}
}

private final com.google.firebase.firestore.core.Filter filter;

/** Initializes a filter */
public Filter(com.google.firebase.firestore.core.Filter filter) {
this.filter = filter;
}

@PublicApi
public boolean isRelationFilter() {
return filter instanceof RelationFilter;
}

@PublicApi
public String getFieldPath() {
return filter.getField().canonicalString();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Returning the field path as a String here using .canonicalString() exposes the underlying backend representation of field paths which we don't currently expose in our API, specifically:

  1. Escaped field paths. i.e. If you do a query on FieldPath.of("emails", "[email protected]") then the string returned here will be something like "emails.`test@example\.com`" (since we have to escape . when it's contained within a field).
  2. A query on FieldPath.documentId() will show up as "__name__" here.

I need to discuss this with the team, but I think my suggestion would be to:

  1. Return FieldPath here instead of String
  2. Add a getSegments() method to FieldPath that returns a String[] containing the path segments.
  3. Add a isDocumentId() method that returns a bool indicating whether the FieldPath is equal to FieldPath.documentId().

}

@Nullable
@PublicApi
public Operator getOperator() {
if (isRelationFilter()) {
return ((RelationFilter) filter).getOperator();
} else {
return null;
}
}

@Nullable
@PublicApi
public Object getFieldValue() {
if (filter instanceof NaNFilter) {
return Double.NaN;
} else {
if (isRelationFilter()) {
return ((RelationFilter) filter).getValue().value();
} else {
return null;
}
}
}


}
Original file line number Diff line number Diff line change
Expand Up @@ -12,16 +12,21 @@
// See the License for the specific language governing permissions and
// limitations under the License.

package com.google.firebase.firestore.core;
package com.google.firebase.firestore;

import android.support.annotation.NonNull;

import com.google.firebase.annotations.PublicApi;
import com.google.firebase.firestore.model.Document;
import com.google.firebase.firestore.model.FieldPath;
import com.google.firebase.firestore.model.value.FieldValue;
import com.google.firebase.firestore.util.Assert;

/** Represents a sort order for a Firestore Query */
@PublicApi
public class OrderBy {
/** The direction of the ordering */
/** @hide **/
public enum Direction {
ASCENDING(1),
DESCENDING(-1);
Expand All @@ -45,19 +50,28 @@ public Direction getDirection() {
return direction;
}

@NonNull
@PublicApi
public Query.Direction getSortDirection() {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd drop "Sort" and just make this getDirection()

return direction == Direction.ASCENDING ?
Query.Direction.ASCENDING : Query.Direction.DESCENDING;
}

@PublicApi
public FieldPath getField() {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note: As mentioned above, we probably need to add methods to FieldPath so you can actually inspect its contents (getSegments(), isDocumentId()), else returning a FieldPath here isn't very useful.

return field;
}

private final Direction direction;
final FieldPath field;
private final FieldPath field;

private OrderBy(Direction direction, FieldPath field) {
this.direction = direction;
this.field = field;
}

int compare(Document d1, Document d2) {
/** @hide **/
public int compare(Document d1, Document d2) {
if (field.equals(FieldPath.KEY_PATH)) {
return direction.getComparisonModifier() * d1.getKey().compareTo(d2.getKey());
} else {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,7 @@
import com.google.firebase.firestore.core.Bound;
import com.google.firebase.firestore.core.EventManager.ListenOptions;
import com.google.firebase.firestore.core.Filter;
import com.google.firebase.firestore.core.Filter.Operator;
import com.google.firebase.firestore.core.OrderBy;
import com.google.firebase.firestore.Filter.Operator;
import com.google.firebase.firestore.core.QueryListener;
import com.google.firebase.firestore.core.RelationFilter;
import com.google.firebase.firestore.core.ViewSnapshot;
Expand Down Expand Up @@ -900,6 +899,47 @@ private ListenerRegistration addSnapshotListenerInternal(
firestore.getClient(), queryListener, activity, wrappedListener);
}

@PublicApi
public String getPath() {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note to self: It's tempting to use a CollectionReference here but that only works for collection queries, not the soon-to-be-added "collection group" queries which are rooted at a document path. Still, it would be nice to avoid exposing just a raw string path.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note to self: We need to figure out how we want to expose Collection Group queries (#233) in general.

return query.getPath().canonicalString();
}

@PublicApi
public List<com.google.firebase.firestore.Filter> getFilters() {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note to self: Since we're exposing filters as Filter objects, should we have a Query.addFilter(...) method in order to re-construct the query? Else, you'd need to do a switch on the operator and then call the appropriate Query.where...() method to match the operator which would be tedious.

com.google.firebase.firestore.Filter newFilter;
List<com.google.firebase.firestore.Filter> filters = new ArrayList<>();
for (Filter filter : query.getFilters()) {
newFilter = new com.google.firebase.firestore.Filter(filter);
filters.add(newFilter);
}
return filters;
}

@PublicApi
public List<OrderBy> getOrderBy() {
return query.getExplicitOrderBy();
}

@PublicApi
public boolean hasLimit() {
return query.hasLimit();
}

@PublicApi
public long getLimit() {
return query.getLimit();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note to myself: It's tempting to drop hasLimit() and make this return a nullable Long.

}

@PublicApi
public @Nullable String getStartAt() {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think getStartAt() / getEndAt() need to return Object, like getFieldValue() on Filter does, so that you could pass the value back into .startAt(...) to reconstruct the query... But we also need to be able to distinguish the lack of a startAt from the null startAt. So we may need .hasStartAt() that returns bool and .getStartAt() that returns the Object value of the startAt (and probably throws an exception if there is no startAt).

return query.getStartAt().getFieldValue();
}

@PublicApi
public @Nullable String getEndAt() {
return query.getEndAt().getFieldValue();
}

@Override
public boolean equals(Object o) {
if (this == o) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,8 @@

import static com.google.firebase.firestore.util.Assert.hardAssert;

import com.google.firebase.firestore.core.OrderBy.Direction;
import com.google.firebase.firestore.OrderBy;
import com.google.firebase.firestore.OrderBy.Direction;
import com.google.firebase.firestore.model.Document;
import com.google.firebase.firestore.model.DocumentKey;
import com.google.firebase.firestore.model.FieldPath;
Expand Down Expand Up @@ -70,14 +71,15 @@ public String canonicalString() {
return builder.toString();
}


/** Returns true if a document sorts before a bound using the provided sort order. */
public boolean sortsBeforeDocument(List<OrderBy> orderBy, Document document) {
hardAssert(position.size() <= orderBy.size(), "Bound has more components than query's orderBy");
int comparison = 0;
for (int i = 0; i < position.size(); i++) {
OrderBy orderByComponent = orderBy.get(i);
FieldValue component = position.get(i);
if (orderByComponent.field.equals(FieldPath.KEY_PATH)) {
if (orderByComponent.getField().equals(FieldPath.KEY_PATH)) {
Object refValue = component.value();
hardAssert(
refValue instanceof DocumentKey,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,28 +19,10 @@
import com.google.firebase.firestore.model.value.DoubleValue;
import com.google.firebase.firestore.model.value.FieldValue;
import com.google.firebase.firestore.model.value.NullValue;
import com.google.firebase.firestore.Filter.Operator;

/** Interface used for all query filters. */
public abstract class Filter {
public enum Operator {
LESS_THAN("<"),
LESS_THAN_OR_EQUAL("<="),
EQUAL("=="),
GREATER_THAN(">"),
GREATER_THAN_OR_EQUAL(">="),
ARRAY_CONTAINS("array_contains");

private final String text;

Operator(String text) {
this.text = text;
}

@Override
public String toString() {
return text;
}
}

/**
* Gets a Filter instance for the provided path, operator, and value.
Expand All @@ -50,14 +32,14 @@ public String toString() {
*/
public static Filter create(FieldPath path, Operator operator, FieldValue value) {
if (value.equals(NullValue.nullValue())) {
if (operator != Filter.Operator.EQUAL) {
if (operator != Operator.EQUAL) {
throw new IllegalArgumentException(
"Invalid Query. You can only perform equality comparisons on null (via "
+ "whereEqualTo()).");
}
return new NullFilter(path);
} else if (value.equals(DoubleValue.NaN)) {
if (operator != Filter.Operator.EQUAL) {
if (operator != Operator.EQUAL) {
throw new IllegalArgumentException(
"Invalid Query. You can only perform equality comparisons on NaN (via "
+ "whereEqualTo()).");
Expand Down
Loading