-
Notifications
You must be signed in to change notification settings - Fork 617
Support > and != on the same field #3454
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
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -14,8 +14,6 @@ | |
|
||
package com.google.firebase.firestore.core; | ||
|
||
import static com.google.firebase.firestore.model.Values.MAX_VALUE; | ||
import static com.google.firebase.firestore.model.Values.MIN_VALUE; | ||
import static com.google.firebase.firestore.model.Values.max; | ||
import static com.google.firebase.firestore.model.Values.min; | ||
|
||
|
@@ -25,10 +23,11 @@ | |
import com.google.firebase.firestore.model.FieldPath; | ||
import com.google.firebase.firestore.model.ResourcePath; | ||
import com.google.firebase.firestore.model.Values; | ||
import com.google.firestore.v1.ArrayValue; | ||
import com.google.firestore.v1.Value; | ||
import java.util.ArrayList; | ||
import java.util.Collection; | ||
import java.util.Collections; | ||
import java.util.LinkedHashMap; | ||
import java.util.List; | ||
|
||
/** | ||
|
@@ -153,23 +152,21 @@ private List<FieldFilter> getFieldFiltersForPath(FieldPath path) { | |
* Returns the list of values that are used in != or NOT_IN filters. Returns {@code null} if there | ||
* are no such filters. | ||
*/ | ||
public @Nullable List<Value> getNotInValues(FieldIndex fieldIndex) { | ||
List<Value> values = new ArrayList<>(); | ||
|
||
public @Nullable Collection<Value> getNotInValues(FieldIndex fieldIndex) { | ||
LinkedHashMap<FieldPath, Value> values = new LinkedHashMap<>(); | ||
for (FieldIndex.Segment segment : fieldIndex.getDirectionalSegments()) { | ||
for (FieldFilter fieldFilter : getFieldFiltersForPath(segment.getFieldPath())) { | ||
switch (fieldFilter.getOperator()) { | ||
case EQUAL: | ||
case IN: | ||
// Encode equality prefix, which is encoded in the index value before the inequality | ||
// (e.g. `a == 'a' && b != 'b'` is encoded to `value != 'ab'`). | ||
values.add(fieldFilter.getValue()); | ||
values.put(segment.getFieldPath(), fieldFilter.getValue()); | ||
break; | ||
case NOT_IN: | ||
case NOT_EQUAL: | ||
// NotIn/NotEqual is always a suffix | ||
values.add(fieldFilter.getValue()); | ||
return values; | ||
values.put(segment.getFieldPath(), fieldFilter.getValue()); | ||
return values.values(); // NotIn/NotEqual is always a suffix | ||
} | ||
} | ||
} | ||
|
@@ -211,13 +208,8 @@ public Bound getLowerBound(FieldIndex fieldIndex) { | |
filterInclusive = false; | ||
break; | ||
case NOT_EQUAL: | ||
filterValue = Values.MIN_VALUE; | ||
break; | ||
case NOT_IN: | ||
filterValue = | ||
Value.newBuilder() | ||
.setArrayValue(ArrayValue.newBuilder().addValues(MIN_VALUE)) | ||
.build(); | ||
filterValue = Values.MIN_VALUE; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Not In and Not Equal use the same bound, which makes the comparison logic at the end of this method work as we do not want to compare to an array but to an actual minimum. |
||
break; | ||
default: | ||
// Remaining filters cannot be used as lower bounds. | ||
|
@@ -291,13 +283,8 @@ public Bound getLowerBound(FieldIndex fieldIndex) { | |
filterInclusive = false; | ||
break; | ||
case NOT_EQUAL: | ||
filterValue = Values.MAX_VALUE; | ||
break; | ||
case NOT_IN: | ||
filterValue = | ||
Value.newBuilder() | ||
.setArrayValue(ArrayValue.newBuilder().addValues(MAX_VALUE)) | ||
.build(); | ||
filterValue = Values.MAX_VALUE; | ||
break; | ||
default: | ||
// Remaining filters cannot be used as upper bounds. | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -448,7 +448,7 @@ public Set<DocumentKey> getDocumentsMatchingTarget(Target target) { | |
} | ||
|
||
@Nullable List<Value> arrayValues = subTarget.getArrayValues(fieldIndex); | ||
@Nullable List<Value> notInValues = subTarget.getNotInValues(fieldIndex); | ||
@Nullable Collection<Value> notInValues = subTarget.getNotInValues(fieldIndex); | ||
@Nullable Bound lowerBound = subTarget.getLowerBound(fieldIndex); | ||
@Nullable Bound upperBound = subTarget.getUpperBound(fieldIndex); | ||
|
||
|
@@ -564,7 +564,7 @@ private Object[] generateQueryAndBindings( | |
Object[] bindArgs = | ||
fillBounds(statementCount, indexId, arrayValues, lowerBounds, upperBounds, notIn); | ||
|
||
List<Object> result = new ArrayList<Object>(); | ||
List<Object> result = new ArrayList<>(); | ||
result.add(sql.toString()); | ||
result.addAll(Arrays.asList(bindArgs)); | ||
return result.toArray(); | ||
|
@@ -673,7 +673,7 @@ private byte[] encodeSingleElement(Value value) { | |
* queries, a list of possible values is returned. | ||
*/ | ||
private @Nullable Object[] encodeValues( | ||
FieldIndex fieldIndex, Target target, @Nullable List<Value> values) { | ||
FieldIndex fieldIndex, Target target, @Nullable Collection<Value> values) { | ||
if (values == null) return null; | ||
|
||
List<IndexByteEncoder> encoders = new ArrayList<>(); | ||
|
@@ -740,8 +740,10 @@ private boolean isInFilter(Target target, FieldPath fieldPath) { | |
for (Filter filter : target.getFilters()) { | ||
if ((filter instanceof FieldFilter) && ((FieldFilter) filter).getField().equals(fieldPath)) { | ||
FieldFilter.Operator operator = ((FieldFilter) filter).getOperator(); | ||
return operator.equals(FieldFilter.Operator.IN) | ||
|| operator.equals(FieldFilter.Operator.NOT_IN); | ||
if (operator.equals(FieldFilter.Operator.IN) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. A not-in filter does not have to appear on the first filter for a field. If there are multiple filters for the field path, not in can appear later. |
||
|| operator.equals(FieldFilter.Operator.NOT_IN)) { | ||
return true; | ||
} | ||
} | ||
} | ||
return false; | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -41,16 +41,15 @@ public class Values { | |
public static final Value NULL_VALUE = | ||
Value.newBuilder().setNullValue(NullValue.NULL_VALUE).build(); | ||
public static final Value MIN_VALUE = NULL_VALUE; | ||
private static final Value MAX_VALUE_TYPE = Value.newBuilder().setStringValue("__max__").build(); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. All changes in this file are to support correct ordering for MAX_VALUE. |
||
public static final Value MAX_VALUE = | ||
Value.newBuilder() | ||
.setMapValue( | ||
MapValue.newBuilder() | ||
.putFields("__type__", Value.newBuilder().setStringValue("__max__").build())) | ||
.setMapValue(MapValue.newBuilder().putFields("__type__", MAX_VALUE_TYPE)) | ||
.build(); | ||
|
||
/** | ||
* The order of types in Firestore. This order is based on the backend's ordering, but modified to | ||
* support server timestamps. | ||
* support server timestamps and {@link #MAX_VALUE}. | ||
*/ | ||
public static final int TYPE_ORDER_NULL = 0; | ||
|
||
|
@@ -65,6 +64,8 @@ public class Values { | |
public static final int TYPE_ORDER_ARRAY = 9; | ||
public static final int TYPE_ORDER_MAP = 10; | ||
|
||
public static final int TYPE_ORDER_MAX_VALUE = Integer.MAX_VALUE; | ||
|
||
/** Returns the backend's type order of the given Value type. */ | ||
public static int typeOrder(Value value) { | ||
switch (value.getValueTypeCase()) { | ||
|
@@ -91,8 +92,11 @@ public static int typeOrder(Value value) { | |
case MAP_VALUE: | ||
if (isServerTimestamp(value)) { | ||
return TYPE_ORDER_SERVER_TIMESTAMP; | ||
} else if (isMaxValue(value)) { | ||
return TYPE_ORDER_MAX_VALUE; | ||
} else { | ||
return TYPE_ORDER_MAP; | ||
} | ||
return TYPE_ORDER_MAP; | ||
default: | ||
throw fail("Invalid value type: " + value.getValueTypeCase()); | ||
} | ||
|
@@ -122,6 +126,8 @@ public static boolean equals(Value left, Value right) { | |
return objectEquals(left, right); | ||
case TYPE_ORDER_SERVER_TIMESTAMP: | ||
return getLocalWriteTime(left).equals(getLocalWriteTime(right)); | ||
case TYPE_ORDER_MAX_VALUE: | ||
return true; | ||
default: | ||
return left.equals(right); | ||
} | ||
|
@@ -195,6 +201,7 @@ public static int compare(Value left, Value right) { | |
|
||
switch (leftType) { | ||
case TYPE_ORDER_NULL: | ||
case TYPE_ORDER_MAX_VALUE: | ||
return 0; | ||
case TYPE_ORDER_BOOLEAN: | ||
return Util.compareBooleans(left.getBooleanValue(), right.getBooleanValue()); | ||
|
@@ -531,6 +538,6 @@ public static Value getUpperBound(Value.ValueTypeCase valueTypeCase) { | |
|
||
/** Returns true if the Value represents the canonical {@link #MAX_VALUE} . */ | ||
public static boolean isMaxValue(Value value) { | ||
return equals(value, MAX_VALUE); | ||
return MAX_VALUE_TYPE.equals(value.getMapValue().getFieldsMap().get("__type__")); | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This method now supports a == 1 && a != 2, which now correctly just returns a != 2 instead of the concatenation of both.