Skip to content

Adding test utilities to create Value types #1158

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

Merged
merged 10 commits into from
Jan 24, 2020
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,6 @@ public int hashCode() {

@Override
public int compareTo(@NonNull Blob other) {
return Util.compareByteString(bytes, other.bytes);
return Util.compareByteStrings(bytes, other.bytes);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,14 @@
// See the License for the specific language governing permissions and
// limitations under the License.

package com.google.firebase.firestore.util;
package com.google.firebase.firestore.model;

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

import androidx.annotation.Nullable;
import com.google.common.base.Splitter;
import com.google.firebase.firestore.model.value.FieldValue;
import com.google.firebase.firestore.util.Util;
import com.google.firestore.v1.ArrayValue;
import com.google.firestore.v1.MapValue;
import com.google.firestore.v1.Value;
Expand All @@ -28,7 +28,8 @@
import java.util.Map;
import java.util.TreeMap;

public class ProtoValueUtil {
// TODO(mrschmidt): Make package-private
public class ProtoValues {

public static int typeOrder(Value value) {

Expand Down Expand Up @@ -73,35 +74,17 @@ public static boolean equals(Value left, Value right) {
}

switch (leftType) {
case FieldValue.TYPE_ORDER_ARRAY:
return arrayEquals(left, right);
case FieldValue.TYPE_ORDER_NUMBER:
return numberEquals(left, right);
case FieldValue.TYPE_ORDER_ARRAY:
return arrayEquals(left, right);
case FieldValue.TYPE_ORDER_OBJECT:
return objectEquals(left, right);
default:
return left.equals(right);
}
}

private static boolean objectEquals(Value left, Value right) {
MapValue leftMap = left.getMapValue();
MapValue rightMap = right.getMapValue();

if (leftMap.getFieldsCount() != rightMap.getFieldsCount()) {
return false;
}

for (Map.Entry<String, Value> entry : leftMap.getFieldsMap().entrySet()) {
Value otherEntry = rightMap.getFieldsMap().get(entry.getKey());
if (!entry.getValue().equals(otherEntry)) {
return false;
}
}

return true;
}

private static boolean numberEquals(Value left, Value right) {
if (left.getValueTypeCase() == Value.ValueTypeCase.INTEGER_VALUE
&& right.getValueTypeCase() == Value.ValueTypeCase.INTEGER_VALUE) {
Expand Down Expand Up @@ -132,6 +115,24 @@ private static boolean arrayEquals(Value left, Value right) {
return true;
}

private static boolean objectEquals(Value left, Value right) {
MapValue leftMap = left.getMapValue();
MapValue rightMap = right.getMapValue();

if (leftMap.getFieldsCount() != rightMap.getFieldsCount()) {
return false;
}

for (Map.Entry<String, Value> entry : leftMap.getFieldsMap().entrySet()) {
Value otherEntry = rightMap.getFieldsMap().get(entry.getKey());
if (!entry.getValue().equals(otherEntry)) {
return false;
}
}

return true;
}

public static int compare(Value left, Value right) {
int leftType = typeOrder(left);
int rightType = typeOrder(right);
Expand All @@ -152,7 +153,7 @@ public static int compare(Value left, Value right) {
case FieldValue.TYPE_ORDER_STRING:
return left.getStringValue().compareTo(right.getStringValue());
case FieldValue.TYPE_ORDER_BLOB:
return Util.compareByteString(left.getBytesValue(), right.getBytesValue());
return Util.compareByteStrings(left.getBytesValue(), right.getBytesValue());
case FieldValue.TYPE_ORDER_REFERENCE:
return compareReferences(left, right);
case FieldValue.TYPE_ORDER_GEOPOINT:
Expand All @@ -166,39 +167,46 @@ public static int compare(Value left, Value right) {
}
}

private static int compareMaps(Value left, Value right) {
Iterator<Map.Entry<String, Value>> iterator1 =
new TreeMap<>(left.getMapValue().getFieldsMap()).entrySet().iterator();
Iterator<Map.Entry<String, Value>> iterator2 =
new TreeMap<>(right.getMapValue().getFieldsMap()).entrySet().iterator();
while (iterator1.hasNext() && iterator2.hasNext()) {
Map.Entry<String, Value> entry1 = iterator1.next();
Map.Entry<String, Value> entry2 = iterator2.next();
int keyCompare = entry1.getKey().compareTo(entry2.getKey());
if (keyCompare != 0) {
return keyCompare;
private static int compareNumbers(Value left, Value right) {
if (left.getValueTypeCase() == Value.ValueTypeCase.DOUBLE_VALUE) {
double thisDouble = left.getDoubleValue();
if (right.getValueTypeCase() == Value.ValueTypeCase.DOUBLE_VALUE) {
return Util.compareDoubles(thisDouble, right.getDoubleValue());
} else if (right.getValueTypeCase() == Value.ValueTypeCase.INTEGER_VALUE) {
return Util.compareMixed(thisDouble, right.getIntegerValue());
}
int valueCompare = compare(entry1.getValue(), entry2.getValue());
if (valueCompare != 0) {
return valueCompare;
} else if (left.getValueTypeCase() == Value.ValueTypeCase.INTEGER_VALUE) {
long thisLong = left.getIntegerValue();
if (right.getValueTypeCase() == Value.ValueTypeCase.INTEGER_VALUE) {
return Util.compareLongs(thisLong, right.getIntegerValue());
} else if (right.getValueTypeCase() == Value.ValueTypeCase.DOUBLE_VALUE) {
return -1 * Util.compareMixed(right.getDoubleValue(), thisLong);
}
}

// Only equal if both iterators are exhausted.
return Util.compareBooleans(iterator1.hasNext(), iterator2.hasNext());
throw fail("Unexpected values: %s vs %s", left, right);
}

private static int compareArrays(Value left, Value right) {
int minLength =
Math.min(left.getArrayValue().getValuesCount(), right.getArrayValue().getValuesCount());
private static int compareTimestamps(Value left, Value right) {
if (left.getTimestampValue().getSeconds() == right.getTimestampValue().getSeconds()) {
return Integer.signum(
left.getTimestampValue().getNanos() - right.getTimestampValue().getNanos());
}
return Long.signum(
left.getTimestampValue().getSeconds() - right.getTimestampValue().getSeconds());
}

private static int compareReferences(Value left, Value right) {
List<String> leftSegments = Splitter.on('/').splitToList(left.getReferenceValue());
List<String> rightSegments = Splitter.on('/').splitToList(right.getReferenceValue());
int minLength = Math.min(leftSegments.size(), rightSegments.size());
for (int i = 0; i < minLength; i++) {
int cmp = compare(left.getArrayValue().getValues(i), right.getArrayValue().getValues(i));
int cmp = leftSegments.get(i).compareTo(rightSegments.get(i));
if (cmp != 0) {
return cmp;
}
}
return Util.compareIntegers(
left.getArrayValue().getValuesCount(), right.getArrayValue().getValuesCount());
return Util.compareIntegers(leftSegments.size(), rightSegments.size());
}

private static int compareGeoPoints(Value left, Value right) {
Expand All @@ -212,55 +220,38 @@ private static int compareGeoPoints(Value left, Value right) {
return comparison;
}

private static int compareReferences(Value left, Value right) {
List<String> leftSegments = Splitter.on('/').splitToList(left.getReferenceValue());
List<String> rightSegments = Splitter.on('/').splitToList(right.getReferenceValue());
int minLength = Math.min(leftSegments.size(), rightSegments.size());
private static int compareArrays(Value left, Value right) {
int minLength =
Math.min(left.getArrayValue().getValuesCount(), right.getArrayValue().getValuesCount());
for (int i = 0; i < minLength; i++) {
int cmp = leftSegments.get(i).compareTo(rightSegments.get(i));
int cmp = compare(left.getArrayValue().getValues(i), right.getArrayValue().getValues(i));
if (cmp != 0) {
return cmp;
}
}
return Util.compareIntegers(leftSegments.size(), rightSegments.size());
}

private static int compareTimestamps(Value left, Value right) {
if (left.getTimestampValue().getSeconds() == right.getTimestampValue().getSeconds()) {
return Integer.signum(
left.getTimestampValue().getNanos() - right.getTimestampValue().getNanos());
}
return Long.signum(
left.getTimestampValue().getSeconds() - right.getTimestampValue().getSeconds());
return Util.compareIntegers(
left.getArrayValue().getValuesCount(), right.getArrayValue().getValuesCount());
}

private static int compareNumbers(Value left, Value right) {
if (left.getValueTypeCase() == Value.ValueTypeCase.DOUBLE_VALUE) {
double thisDouble = left.getDoubleValue();
if (right.getValueTypeCase() == Value.ValueTypeCase.DOUBLE_VALUE) {
return Util.compareDoubles(thisDouble, right.getDoubleValue());
} else {
hardAssert(
right.getValueTypeCase() == Value.ValueTypeCase.INTEGER_VALUE,
"Unexpected value type: %s",
right);
return Util.compareMixed(thisDouble, right.getIntegerValue());
private static int compareMaps(Value left, Value right) {
Iterator<Map.Entry<String, Value>> iterator1 =
new TreeMap<>(left.getMapValue().getFieldsMap()).entrySet().iterator();
Iterator<Map.Entry<String, Value>> iterator2 =
new TreeMap<>(right.getMapValue().getFieldsMap()).entrySet().iterator();
while (iterator1.hasNext() && iterator2.hasNext()) {
Map.Entry<String, Value> entry1 = iterator1.next();
Map.Entry<String, Value> entry2 = iterator2.next();
int keyCompare = entry1.getKey().compareTo(entry2.getKey());
if (keyCompare != 0) {
return keyCompare;
}
} else {
hardAssert(
left.getValueTypeCase() == Value.ValueTypeCase.INTEGER_VALUE,
"Unexpected value type: %s",
left);
long thisLong = left.getIntegerValue();
if (right.getValueTypeCase() == Value.ValueTypeCase.INTEGER_VALUE) {
return Util.compareLongs(thisLong, right.getIntegerValue());
} else {
hardAssert(
right.getValueTypeCase() == Value.ValueTypeCase.DOUBLE_VALUE,
"Unexpected value type: %s",
right);
return -1 * Util.compareMixed(right.getDoubleValue(), thisLong);
int valueCompare = compare(entry1.getValue(), entry2.getValue());
if (valueCompare != 0) {
return valueCompare;
}
}

// Only equal if both iterators are exhausted.
return Util.compareBooleans(iterator1.hasNext(), iterator2.hasNext());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -218,7 +218,7 @@ public static void crashMainThread(RuntimeException exception) {
});
}

public static int compareByteString(ByteString left, ByteString right) {
public static int compareByteStrings(ByteString left, ByteString right) {
int size = Math.min(left.size(), right.size());
for (int i = 0; i < size; i++) {
// Make sure the bytes are unsigned
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,62 +22,71 @@
import com.google.protobuf.NullValue;
import com.google.type.LatLng;
import java.util.List;
import java.util.Map;

/** Test helper to create Firestore Value protos from Java types. */
public class ValueHelper {
public class Values {

// TODO(mrschmidt): Move into UserDataConverter
public static Value valueOf(Object o) {
if (o instanceof Value) {
return (Value) o;
} else if (o instanceof String) {
return (Value.newBuilder().setStringValue((String) o).build());
} else if (o == null) {
return Value.newBuilder().setNullValue(NullValue.NULL_VALUE).build();
} else if (o instanceof Boolean) {
return Value.newBuilder().setBooleanValue((Boolean) o).build();
} else if (o instanceof Integer) {
return (Value.newBuilder().setIntegerValue((long) (Integer) o).build());
return Value.newBuilder().setIntegerValue((Integer) o).build();
} else if (o instanceof Long) {
return (Value.newBuilder().setIntegerValue((Long) o).build());
return Value.newBuilder().setIntegerValue((Long) o).build();
} else if (o instanceof Double) {
return (Value.newBuilder().setDoubleValue((Double) o).build());
} else if (o instanceof Boolean) {
return (Value.newBuilder().setBooleanValue((Boolean) o).build());
return Value.newBuilder().setDoubleValue((Double) o).build();
} else if (o instanceof Timestamp) {
Timestamp timestamp = (Timestamp) o;
return (Value.newBuilder()
return Value.newBuilder()
.setTimestampValue(
com.google.protobuf.Timestamp.newBuilder()
.setSeconds(timestamp.getSeconds())
.setNanos(timestamp.getNanoseconds())
.build())
.build());
.setNanos(timestamp.getNanoseconds()))
.build();
} else if (o instanceof String) {
return Value.newBuilder().setStringValue((String) o).build();
} else if (o instanceof Blob) {
return Value.newBuilder().setBytesValue(((Blob) o).toByteString()).build();

Copy link
Contributor

Choose a reason for hiding this comment

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

nit: excess newline.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Sorry - I forgot to address this before merging. I fixed it in the follow up PR.

} else if (o instanceof DocumentReference) {
return Value.newBuilder()
.setReferenceValue(
"projects/project/databases/(default)/documents/" + ((DocumentReference) o).getPath())
.build();
} else if (o instanceof GeoPoint) {
GeoPoint geoPoint = (GeoPoint) o;
return (Value.newBuilder()
return Value.newBuilder()
.setGeoPointValue(
LatLng.newBuilder()
.setLatitude(geoPoint.getLatitude())
.setLongitude(geoPoint.getLongitude())
.build())
.build());
} else if (o instanceof Blob) {
return (Value.newBuilder().setBytesValue(((Blob) o).toByteString()).build());
} else if (o instanceof DocumentReference) {
return (Value.newBuilder()
.setReferenceValue(
"projects/projectId/databases/(default)/documents/"
+ ((DocumentReference) o).getPath())
.build());
.setLongitude(geoPoint.getLongitude()))
.build();

} else if (o instanceof List) {
ArrayValue.Builder list = ArrayValue.newBuilder();
for (Object element : (List) o) {
list.addValues(valueOf(element));
}
return (Value.newBuilder().setArrayValue(list).build());
} else if (o == null) {
return (Value.newBuilder().setNullValue(NullValue.NULL_VALUE).build());
return Value.newBuilder().setArrayValue(list).build();
} else if (o instanceof Map) {
com.google.firestore.v1.MapValue.Builder builder =
Copy link
Contributor

Choose a reason for hiding this comment

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

nit: import MapValue?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done

com.google.firestore.v1.MapValue.newBuilder();
for (Map.Entry<String, Object> entry : ((Map<String, Object>) o).entrySet()) {
builder.putFields(entry.getKey(), valueOf(entry.getValue()));
}
return Value.newBuilder().setMapValue(builder).build();
}

throw new UnsupportedOperationException();
throw new UnsupportedOperationException("Failed to serialize object: " + o);
}

/** Creates a MapValue from a list of key/value arguments. */
public static Value map(Object... entries) {
com.google.firestore.v1.MapValue.Builder builder =
com.google.firestore.v1.MapValue.newBuilder();
Expand All @@ -87,7 +96,7 @@ public static Value map(Object... entries) {
return Value.newBuilder().setMapValue(builder).build();
}

public static Value wrapRef(DatabaseId dbId, DocumentKey key) {
public static Value refValue(DatabaseId dbId, DocumentKey key) {
return Value.newBuilder()
.setReferenceValue(
String.format(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -268,7 +268,7 @@ private void addMutationBatch(SQLiteDatabase db, int batchId, String uid, String
Write.newBuilder()
.setUpdate(
Document.newBuilder()
.setName("projects/projectId/databases/(default)/documents/" + doc)));
.setName("projects/project/databases/(default)/documents/" + doc)));
}

db.execSQL(
Expand Down