diff --git a/Firestore/core/src/model/server_timestamp_util.cc b/Firestore/core/src/model/server_timestamp_util.cc index 13d80d0df56..700c1740bd2 100644 --- a/Firestore/core/src/model/server_timestamp_util.cc +++ b/Firestore/core/src/model/server_timestamp_util.cc @@ -29,7 +29,7 @@ const char kPreviousValueKey[] = "__previous_value__"; const char kServerTimestampSentinel[] = "server_timestamp"; google_firestore_v1_Value EncodeServerTimestamp( - google_protobuf_Timestamp local_write_time, + const Timestamp& local_write_time, absl::optional previous_value) { google_firestore_v1_Value result{}; result.which_value_type = google_firestore_v1_Value_map_value_tag; diff --git a/Firestore/core/src/model/server_timestamp_util.h b/Firestore/core/src/model/server_timestamp_util.h index ba1fb08f5f7..781f2752f9a 100644 --- a/Firestore/core/src/model/server_timestamp_util.h +++ b/Firestore/core/src/model/server_timestamp_util.h @@ -18,6 +18,7 @@ #define FIRESTORE_CORE_SRC_MODEL_SERVER_TIMESTAMP_UTIL_H_ #include "Firestore/Protos/nanopb/google/firestore/v1/document.nanopb.h" +#include "Firestore/core/include/firebase/firestore/timestamp.h" #include "absl/types/optional.h" namespace firebase { @@ -27,11 +28,9 @@ namespace model { // Utility methods to handle ServerTimestamps, which are stored using special // sentinel fields in MapValues. -/** - * Encodes the backing data for a server timestamp in a Value proto. - */ +/** Encodes the backing data for a server timestamp in a Value proto. */ google_firestore_v1_Value EncodeServerTimestamp( - google_protobuf_Timestamp local_write_time, + const Timestamp& local_write_time, absl::optional previous_value); /** * Returns whether the provided value is a field map that contains the diff --git a/Firestore/core/src/model/value_util.cc b/Firestore/core/src/model/value_util.cc index 694f6245b41..0905f439ca1 100644 --- a/Firestore/core/src/model/value_util.cc +++ b/Firestore/core/src/model/value_util.cc @@ -17,9 +17,13 @@ #include "Firestore/core/src/model/value_util.h" #include +#include +#include #include #include +#include "Firestore/core/src/model/database_id.h" +#include "Firestore/core/src/model/document_key.h" #include "Firestore/core/src/model/server_timestamp_util.h" #include "Firestore/core/src/nanopb/nanopb_util.h" #include "Firestore/core/src/util/comparison.h" @@ -123,9 +127,10 @@ ComparisonResult CompareBlobs(const google_firestore_v1_Value& left, ? util::ComparisonResultFromInt(cmp) : util::Compare(left.bytes_value->size, right.bytes_value->size); } else { - // An empty blob is represented by a nullptr - return util::Compare(left.bytes_value != nullptr, - right.bytes_value != nullptr); + // An empty blob is represented by a nullptr (or an empty byte array) + return util::Compare( + !(left.bytes_value == nullptr || left.bytes_value->size == 0), + !(right.bytes_value == nullptr || right.bytes_value->size == 0)); } } @@ -247,8 +252,8 @@ ComparisonResult Compare(const google_firestore_v1_Value& left, } } -bool NumberEquals(const firebase::firestore::google_firestore_v1_Value& left, - const firebase::firestore::google_firestore_v1_Value& right) { +bool NumberEquals(const google_firestore_v1_Value& left, + const google_firestore_v1_Value& right) { if (left.which_value_type == google_firestore_v1_Value_integer_value_tag && right.which_value_type == google_firestore_v1_Value_integer_value_tag) { return left.integer_value == right.integer_value; @@ -261,17 +266,14 @@ bool NumberEquals(const firebase::firestore::google_firestore_v1_Value& left, return false; } -bool ArrayEquals(const firebase::firestore::google_firestore_v1_Value& left, - const firebase::firestore::google_firestore_v1_Value& right) { - const google_firestore_v1_ArrayValue& left_array = left.array_value; - const google_firestore_v1_ArrayValue& right_array = right.array_value; - - if (left_array.values_count != right_array.values_count) { +bool ArrayEquals(const google_firestore_v1_ArrayValue& left, + const google_firestore_v1_ArrayValue& right) { + if (left.values_count != right.values_count) { return false; } - for (size_t i = 0; i < left_array.values_count; ++i) { - if (left_array.values[i] != right_array.values[i]) { + for (size_t i = 0; i < left.values_count; ++i) { + if (left.values[i] != right.values[i]) { return false; } } @@ -279,24 +281,21 @@ bool ArrayEquals(const firebase::firestore::google_firestore_v1_Value& left, return true; } -bool ObjectEquals(const firebase::firestore::google_firestore_v1_Value& left, - const firebase::firestore::google_firestore_v1_Value& right) { - google_firestore_v1_MapValue left_map = left.map_value; - google_firestore_v1_MapValue right_map = right.map_value; - - if (left_map.fields_count != right_map.fields_count) { +bool ObjectEquals(const google_firestore_v1_MapValue& left, + const google_firestore_v1_MapValue& right) { + if (left.fields_count != right.fields_count) { return false; } // Porting Note: MapValues in iOS are always kept in sorted order. We // therefore do no need to sort them before comparing. - for (size_t i = 0; i < right_map.fields_count; ++i) { - if (nanopb::MakeStringView(left_map.fields[i].key) != - nanopb::MakeStringView(right_map.fields[i].key)) { + for (size_t i = 0; i < right.fields_count; ++i) { + if (nanopb::MakeStringView(left.fields[i].key) != + nanopb::MakeStringView(right.fields[i].key)) { return false; } - if (left_map.fields[i].value != right_map.fields[i].value) { + if (left.fields[i].value != right.fields[i].value) { return false; } } @@ -349,16 +348,21 @@ bool Equals(const google_firestore_v1_Value& lhs, lhs.geo_point_value.longitude == rhs.geo_point_value.longitude; case TypeOrder::kArray: - return ArrayEquals(lhs, rhs); + return ArrayEquals(lhs.array_value, rhs.array_value); case TypeOrder::kMap: - return ObjectEquals(lhs, rhs); + return ObjectEquals(lhs.map_value, rhs.map_value); default: HARD_FAIL("Invalid type value: %s", left_type); } } +bool Equals(const google_firestore_v1_ArrayValue& lhs, + const google_firestore_v1_ArrayValue& rhs) { + return ArrayEquals(lhs, rhs); +} + std::string CanonifyTimestamp(const google_firestore_v1_Value& value) { return absl::StrFormat("time(%d,%d)", value.timestamp_value.seconds, value.timestamp_value.nanos); @@ -381,13 +385,11 @@ std::string CanonifyGeoPoint(const google_firestore_v1_Value& value) { value.geo_point_value.longitude); } -std::string CanonifyArray(const google_firestore_v1_Value& value) { - const auto& array = value.array_value; - +std::string CanonifyArray(const google_firestore_v1_ArrayValue& array_value) { std::string result = "["; - for (size_t i = 0; i < array.values_count; ++i) { - absl::StrAppend(&result, CanonicalId(array.values[i])); - if (i != array.values_count - 1) { + for (size_t i = 0; i < array_value.values_count; ++i) { + absl::StrAppend(&result, CanonicalId(array_value.values[i])); + if (i != array_value.values_count - 1) { absl::StrAppend(&result, ","); } } @@ -444,7 +446,7 @@ std::string CanonicalId(const google_firestore_v1_Value& value) { return CanonifyGeoPoint(value); case google_firestore_v1_Value_array_value_tag: - return CanonifyArray(value); + return CanonifyArray(value.array_value); case google_firestore_v1_Value_map_value_tag: { return CanonifyObject(value); @@ -455,12 +457,61 @@ std::string CanonicalId(const google_firestore_v1_Value& value) { } } -google_firestore_v1_Value DeepClone(google_firestore_v1_Value source) { +std::string CanonicalId(const google_firestore_v1_ArrayValue& value) { + return CanonifyArray(value); +} + +bool Contains(google_firestore_v1_ArrayValue haystack, + google_firestore_v1_Value needle) { + for (pb_size_t i = 0; i < haystack.values_count; ++i) { + if (Equals(haystack.values[i], needle)) { + return true; + } + } + return false; +} + +google_firestore_v1_Value NullValue() { + google_firestore_v1_Value null_value{}; + null_value.which_value_type = google_firestore_v1_Value_null_value_tag; + return null_value; +} + +bool IsNullValue(const google_firestore_v1_Value& value) { + return value.which_value_type == google_firestore_v1_Value_null_value_tag; +} + +google_firestore_v1_Value NaNValue() { + google_firestore_v1_Value nan_value{}; + nan_value.which_value_type = google_firestore_v1_Value_double_value_tag; + nan_value.double_value = std::numeric_limits::quiet_NaN(); + return nan_value; +} + +bool IsNaNValue(const google_firestore_v1_Value& value) { + return value.which_value_type == google_firestore_v1_Value_double_value_tag && + std::isnan(value.double_value); +} + +google_firestore_v1_Value RefValue(const model::DatabaseId& database_id, + const model::DocumentKey& document_key) { + google_firestore_v1_Value result{}; + result.which_value_type = google_firestore_v1_Value_reference_value_tag; + result.string_value = nanopb::MakeBytesArray(util::StringFormat( + "projects/%s/databases/%s/documents/%s", database_id.project_id(), + database_id.database_id(), document_key.ToString())); + return result; +} + +google_firestore_v1_Value DeepClone(const google_firestore_v1_Value& source) { google_firestore_v1_Value target = source; switch (source.which_value_type) { case google_firestore_v1_Value_string_value_tag: - target.string_value = nanopb::MakeBytesArray(source.string_value->bytes, - source.string_value->size); + target.string_value = + source.string_value + ? nanopb::MakeBytesArray(source.string_value->bytes, + source.string_value->size) + : nullptr; break; case google_firestore_v1_Value_reference_value_tag: @@ -469,8 +520,10 @@ google_firestore_v1_Value DeepClone(google_firestore_v1_Value source) { break; case google_firestore_v1_Value_bytes_value_tag: - target.bytes_value = nanopb::MakeBytesArray(source.bytes_value->bytes, - source.bytes_value->size); + target.bytes_value = + source.bytes_value ? nanopb::MakeBytesArray(source.bytes_value->bytes, + source.bytes_value->size) + : nullptr; break; case google_firestore_v1_Value_array_value_tag: @@ -499,6 +552,18 @@ google_firestore_v1_Value DeepClone(google_firestore_v1_Value source) { return target; } +google_firestore_v1_ArrayValue DeepClone( + const google_firestore_v1_ArrayValue& source) { + google_firestore_v1_ArrayValue target = source; + target.values_count = source.values_count; + target.values = + nanopb::MakeArray(source.values_count); + for (pb_size_t i = 0; i < source.values_count; ++i) { + target.values[i] = DeepClone(source.values[i]); + } + return target; +} + } // namespace model } // namespace firestore } // namespace firebase diff --git a/Firestore/core/src/model/value_util.h b/Firestore/core/src/model/value_util.h index d39ef08b997..47dbf696579 100644 --- a/Firestore/core/src/model/value_util.h +++ b/Firestore/core/src/model/value_util.h @@ -19,8 +19,10 @@ #include #include +#include #include "Firestore/Protos/nanopb/google/firestore/v1/document.nanopb.h" +#include "absl/types/optional.h" namespace firebase { namespace firestore { @@ -31,6 +33,9 @@ enum class ComparisonResult; namespace model { +class DocumentKey; +class DatabaseId; + /** * The order of types in Firestore. This order is based on the backend's * ordering, but modified to support server timestamps. @@ -58,14 +63,70 @@ util::ComparisonResult Compare(const google_firestore_v1_Value& left, bool Equals(const google_firestore_v1_Value& left, const google_firestore_v1_Value& right); +bool Equals(const google_firestore_v1_ArrayValue& left, + const google_firestore_v1_ArrayValue& right); + /** - * Generate the canonical ID for the provided field value (as used in Target + * Generates the canonical ID for the provided field value (as used in Target * serialization). */ std::string CanonicalId(const google_firestore_v1_Value& value); +/** + * Generates the canonical ID for the provided array value (as used in Target + * serialization). + */ +std::string CanonicalId(const google_firestore_v1_ArrayValue& value); + +/** Returns true if the array value contains the specified element. */ +bool Contains(google_firestore_v1_ArrayValue haystack, + google_firestore_v1_Value needle); + +/** Returns a null Protobuf value. */ +google_firestore_v1_Value NullValue(); + +/** Returns `true` if `value` is null in its Protobuf representation. */ +bool IsNullValue(const google_firestore_v1_Value& value); + +/** Returns `NaN` in its Protobuf representation. */ +google_firestore_v1_Value NaNValue(); + +/** Returns `true` if `value` is `NaN` in its Protobuf representation. */ +bool IsNaNValue(const google_firestore_v1_Value& value); + +/** Returns a Protobuf reference value representing the given location. */ +google_firestore_v1_Value RefValue(const DatabaseId& database_id, + const DocumentKey& document_key); + /** Creates a copy of the contents of the Value proto. */ -google_firestore_v1_Value DeepClone(google_firestore_v1_Value source); +google_firestore_v1_Value DeepClone(const google_firestore_v1_Value& source); + +/** Creates a copy of the contents of the ArrayValue proto. */ +google_firestore_v1_ArrayValue DeepClone( + const google_firestore_v1_ArrayValue& source); + +/** Returns true if `value` is a INTEGER_VALUE. */ +inline bool IsInteger(const absl::optional& value) { + return value && + value->which_value_type == google_firestore_v1_Value_integer_value_tag; +} + +/** Returns true if `value` is a DOUBLE_VALUE. */ +inline bool IsDouble(const absl::optional& value) { + return value && + value->which_value_type == google_firestore_v1_Value_double_value_tag; +} + +/** Returns true if `value` is either a INTEGER_VALUE or a DOUBLE_VALUE. */ +inline bool IsNumber(const absl::optional& value) { + return IsInteger(value) || IsDouble(value); +} + +/** Returns true if `value` is an ARRAY_VALUE. */ +inline bool IsArray(const absl::optional& value) { + return value && + value->which_value_type == google_firestore_v1_Value_array_value_tag; +} } // namespace model @@ -79,6 +140,16 @@ inline bool operator!=(const google_firestore_v1_Value& lhs, return !model::Equals(lhs, rhs); } +inline bool operator==(const google_firestore_v1_ArrayValue& lhs, + const google_firestore_v1_ArrayValue& rhs) { + return model::Equals(lhs, rhs); +} + +inline bool operator!=(const google_firestore_v1_ArrayValue& lhs, + const google_firestore_v1_ArrayValue& rhs) { + return !model::Equals(lhs, rhs); +} + inline std::ostream& operator<<(std::ostream& out, const google_firestore_v1_Value& value) { return out << model::CanonicalId(value);